arcane-os 0.5.17 → 0.5.19

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 (186) hide show
  1. package/CHANGELOG.md +34 -1
  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 +158 -135
  7. package/browser-runtime/ai/browser-wasm.mjs +6 -3
  8. package/browser-runtime/ai/browser-wllama-runtime.mjs +104 -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/architecture.md +1 -1
  18. package/docs/publishing.md +10 -4
  19. package/docs/reference/ai/browser-speech.md +198 -5
  20. package/docs/reference/ai/browser-wasm.md +29 -6
  21. package/docs/reference/availability-and-normalization.md +40 -0
  22. package/docs/reference/inventory/package-api.json +1 -1
  23. package/docs/reference/inventory/runtime-components.json +1 -1
  24. package/docs/reference/inventory/runtime-modules.json +20 -24
  25. package/docs/reference/protocols.md +1 -1
  26. package/docs/reference/runtime-modules.md +125 -35
  27. package/docs/reference/sdk-api.md +23 -7
  28. package/examples/wasm-ai-demo/index.html +1 -0
  29. package/node_modules/event-pubsub/node_modules/strong-type/README.md +408 -0
  30. package/node_modules/event-pubsub/node_modules/strong-type/assets/strong-type-header.png +0 -0
  31. package/node_modules/event-pubsub/node_modules/strong-type/index.js +1151 -0
  32. package/node_modules/event-pubsub/node_modules/strong-type/licence +21 -0
  33. package/node_modules/event-pubsub/node_modules/strong-type/node.js +125 -0
  34. package/node_modules/event-pubsub/node_modules/strong-type/package.json +61 -0
  35. package/node_modules/strong-type/README.md +42 -24
  36. package/node_modules/strong-type/benchmark/core.js +324 -0
  37. package/node_modules/strong-type/index.js +207 -82
  38. package/node_modules/strong-type/package.json +14 -6
  39. package/package.json +3 -3
  40. package/runtime/arcane/components/app-bar.html +5 -2
  41. package/runtime/arcane/components/assistant-panel.html +5 -2
  42. package/runtime/arcane/components/chart.html +15 -12
  43. package/runtime/arcane/components/chat.html +170 -148
  44. package/runtime/arcane/components/dashboard-config.html +6 -3
  45. package/runtime/arcane/components/data-view.html +4 -1
  46. package/runtime/arcane/components/directory-picker.html +5 -2
  47. package/runtime/arcane/components/document-inspector.html +6 -3
  48. package/runtime/arcane/components/file-drop.html +5 -2
  49. package/runtime/arcane/components/file-inspector.html +9 -6
  50. package/runtime/arcane/components/file-manager.html +16 -13
  51. package/runtime/arcane/components/local-ai-status.html +5 -2
  52. package/runtime/arcane/components/markdown-document.html +14 -11
  53. package/runtime/arcane/components/markdown-editor.html +4 -1
  54. package/runtime/arcane/components/modal.html +4 -1
  55. package/runtime/arcane/components/output-panel.html +6 -3
  56. package/runtime/arcane/components/preferences-form.html +4 -1
  57. package/runtime/arcane/components/record-timeline.html +8 -5
  58. package/runtime/arcane/components/relationship-board.html +9 -6
  59. package/runtime/arcane/components/source-code-viewer.html +12 -9
  60. package/runtime/arcane/components/source-explanation.html +8 -5
  61. package/runtime/arcane/components/speech.html +24 -21
  62. package/runtime/arcane/components/summary-strip.html +4 -1
  63. package/runtime/arcane/components/task-progress.html +6 -3
  64. package/runtime/arcane/components/terminal-workspace.html +4 -1
  65. package/runtime/arcane/components/voice-transcription.html +11 -8
  66. package/runtime/arcane/components/web-navigator.html +5 -2
  67. package/runtime/arcane/entities/ApiModelRecord.js +5 -2
  68. package/runtime/arcane/entities/Calculation.js +5 -2
  69. package/runtime/arcane/entities/Chat.js +33 -33
  70. package/runtime/arcane/entities/CommunicationMessage.js +4 -1
  71. package/runtime/arcane/entities/CommunicationThread.js +4 -1
  72. package/runtime/arcane/entities/File.js +1 -1
  73. package/runtime/arcane/entities/Image.js +8 -5
  74. package/runtime/arcane/entities/IntentEnvelope.js +20 -17
  75. package/runtime/arcane/entities/Preference.js +9 -6
  76. package/runtime/arcane/entities/Theme.js +5 -2
  77. package/runtime/arcane/entities/User.js +11 -11
  78. package/runtime/arcane/entities/Weather.js +5 -2
  79. package/runtime/arcane/modules/AI.js +491 -165
  80. package/runtime/arcane/modules/AIPreferenceRuntime.js +5 -2
  81. package/runtime/arcane/modules/AIPreferenceTuple.js +9 -6
  82. package/runtime/arcane/modules/AIProviderRuntime.js +88 -85
  83. package/runtime/arcane/modules/AIResponseURLPolicy.js +148 -62
  84. package/runtime/arcane/modules/AIRuntimeState.js +29 -26
  85. package/runtime/arcane/modules/AnsiText.js +4 -1
  86. package/runtime/arcane/modules/ApiModelDatabase.js +22 -19
  87. package/runtime/arcane/modules/AppDataScope.js +8 -5
  88. package/runtime/arcane/modules/ArcaneCommunicationBridge.js +4 -1
  89. package/runtime/arcane/modules/ArcaneNavigationPolicy.js +8 -5
  90. package/runtime/arcane/modules/ArcaneNetworkPolicy.js +18 -15
  91. package/runtime/arcane/modules/AsyncBoundary.js +13 -10
  92. package/runtime/arcane/modules/BrowserTestSuite.js +19 -16
  93. package/runtime/arcane/modules/CalculatorEngine.js +5 -2
  94. package/runtime/arcane/modules/ChatRecords.js +18 -15
  95. package/runtime/arcane/modules/CommunicationAppController.js +9 -6
  96. package/runtime/arcane/modules/CommunicationHub.js +9 -6
  97. package/runtime/arcane/modules/CommunicationPreferences.js +4 -1
  98. package/runtime/arcane/modules/CommunicationProviderRegistry.js +4 -1
  99. package/runtime/arcane/modules/ComponentContracts.js +56 -53
  100. package/runtime/arcane/modules/ConfiguredAIChatSession.js +30 -27
  101. package/runtime/arcane/modules/ConversationActionItems.js +15 -12
  102. package/runtime/arcane/modules/ConversationClosingReport.js +11 -8
  103. package/runtime/arcane/modules/ConversationTimebox.js +28 -25
  104. package/runtime/arcane/modules/CoreLocalModelCatalog.js +17 -14
  105. package/runtime/arcane/modules/DBLS.js +7 -4
  106. package/runtime/arcane/modules/DBOPFS.js +7 -7
  107. package/runtime/arcane/modules/DBOPFSDocumentLibrary.js +34 -31
  108. package/runtime/arcane/modules/DevelopmentWorkspace.js +4 -1
  109. package/runtime/arcane/modules/DirectoryPicker.js +8 -5
  110. package/runtime/arcane/modules/DocumentLexicalSearch.js +11 -8
  111. package/runtime/arcane/modules/DocumentNavigation.js +7 -4
  112. package/runtime/arcane/modules/Errors.js +28 -25
  113. package/runtime/arcane/modules/HTMLImport.js +7 -4
  114. package/runtime/arcane/modules/IsolatedModelQuestionRunner.js +15 -12
  115. package/runtime/arcane/modules/LocalAIReadiness.js +21 -18
  116. package/runtime/arcane/modules/LocalAIReadinessController.js +6 -3
  117. package/runtime/arcane/modules/MD.js +1 -1
  118. package/runtime/arcane/modules/Mail.js +46 -43
  119. package/runtime/arcane/modules/MailOutbox.mjs +48 -45
  120. package/runtime/arcane/modules/MailTransport.mjs +29 -26
  121. package/runtime/arcane/modules/MemoryRecords.js +7 -4
  122. package/runtime/arcane/modules/MessageAdvisory.js +6 -3
  123. package/runtime/arcane/modules/ModelDefinition.js +7 -4
  124. package/runtime/arcane/modules/Ollama.js +6 -3
  125. package/runtime/arcane/modules/OllamaModelIdentifier.js +4 -1
  126. package/runtime/arcane/modules/OpenMeteoWeatherProvider.js +18 -15
  127. package/runtime/arcane/modules/PersistentAIChatSession.js +33 -30
  128. package/runtime/arcane/modules/PreferenceStore.js +20 -17
  129. package/runtime/arcane/modules/PreparedSpeech.js +485 -0
  130. package/runtime/arcane/modules/Questionnaire.js +6 -3
  131. package/runtime/arcane/modules/RecordLinkIndex.js +4 -1
  132. package/runtime/arcane/modules/RecordPassageIndex.js +9 -6
  133. package/runtime/arcane/modules/RecordReviewStore.js +12 -9
  134. package/runtime/arcane/modules/RiskSignalAnalyzer.js +4 -1
  135. package/runtime/arcane/modules/ScopedOPFSCache.js +6 -3
  136. package/runtime/arcane/modules/ScreenCapture.js +20 -17
  137. package/runtime/arcane/modules/SpeechPlayback.js +29 -26
  138. package/runtime/arcane/modules/StaticDocumentCatalog.js +33 -30
  139. package/runtime/arcane/modules/SystemAppearance.js +5 -2
  140. package/runtime/arcane/modules/SystemToolRegistry.js +6 -3
  141. package/runtime/arcane/modules/TerminalClient.js +14 -11
  142. package/runtime/arcane/modules/TerminalCommandRegistry.js +4 -1
  143. package/runtime/arcane/modules/ThemeBootstrap.js +9 -6
  144. package/runtime/arcane/modules/ThemeManager.js +5 -2
  145. package/runtime/arcane/modules/TimeGuard.js +1 -1
  146. package/runtime/arcane/modules/ToolCallRouter.js +11 -8
  147. package/runtime/arcane/modules/WaitForComponent.js +19 -16
  148. package/runtime/arcane/modules/YouTubeMedia.js +4 -1
  149. package/runtime/strong-type/index.js +1276 -352
  150. package/runtime/strong-type/package.json +69 -45
  151. package/src/app-descriptor.mjs +17 -14
  152. package/src/application-tests.mjs +4 -1
  153. package/src/cli/main.mjs +8 -5
  154. package/src/constants.mjs +4 -1
  155. package/src/dev-server.mjs +8 -5
  156. package/src/doctor.mjs +5 -2
  157. package/src/dom-event-instrumentation.mjs +24 -20
  158. package/src/errors.mjs +7 -3
  159. package/src/event-manager.mjs +111 -108
  160. package/src/event-queue.mjs +8 -5
  161. package/src/events.mjs +12 -9
  162. package/src/import-map.mjs +35 -27
  163. package/src/installed-sdk-runtime.mjs +4 -1
  164. package/src/integrated-provider-loader.mjs +9 -6
  165. package/src/mail-credentials.mjs +16 -13
  166. package/src/mail-server.mjs +53 -50
  167. package/src/mail.mjs +18 -15
  168. package/src/native-plan.mjs +12 -9
  169. package/src/native-provider-loader.mjs +7 -4
  170. package/src/packager/core.mjs +18 -15
  171. package/src/process.mjs +6 -3
  172. package/src/release-bundle.mjs +14 -11
  173. package/src/runtime.mjs +4 -1
  174. package/src/scaffold.mjs +9 -6
  175. package/src/sdk-browser-runtime.mjs +4 -1
  176. package/src/source-server.mjs +17 -14
  177. package/src/targets/index.mjs +5 -2
  178. package/src/templates/workspace-template.mjs +13 -6
  179. package/src/testing-loader.mjs +7 -4
  180. package/src/testing.mjs +10 -7
  181. package/src/toolchain.mjs +13 -10
  182. package/src/update-check.mjs +9 -6
  183. package/src/workspace-operation-lock.mjs +14 -11
  184. package/src/workspace-runtime.mjs +4 -1
  185. package/src/workspace.mjs +17 -14
  186. package/runtime/arcane/modules/AIResponseLength.js +0 -32
@@ -538,59 +538,35 @@
538
538
  }
539
539
  }
540
540
 
541
- table {
542
- width:calc(100% - .5em);
543
- margin: auto; /* Center the table horizontally */
541
+ /* Native table layout shares columns across rows and grows with complete cell content. */
542
+ .chat_output table {
543
+ width: 100%;
544
+ margin: .75em 0;
544
545
  border-collapse: collapse;
545
- overflow-x: auto; /* Enables horizontal scrolling */
546
- display:table;
547
- background: var(--modal-background);
548
- box-shadow: 0 0 0.7em var(--border-color);
549
- border-radius: 0.5em 0.5em 0.5em 0.5em;
550
- border-spacing: unset;
551
- }
552
-
553
- thead, tbody {
554
- display: table; /* These elements behave like table rows */
555
- width: 100%; /* Table rows take full available space */
556
- table-layout: fixed; /* Ensures the table layout respects the width settings */
557
- }
558
-
559
- tbody tr{
560
- height: 130px;
561
- }
562
-
563
- thead tr{
564
- background: var(--background);
565
- color: var(--platinum);
566
- border-radius: .5em .5em 0 0;
567
- }
568
-
569
- tr{
570
- display: grid;
571
- grid-template-columns:
572
- 6em 1fr 1fr 1fr;
546
+ border-spacing: 0;
547
+ background: var(--arcane-surface, var(--background));
548
+ color: var(--text-color);
573
549
  }
574
550
 
575
- tr:hover{
576
- background:var(--secondary-color);
551
+ .chat_output th,
552
+ .chat_output td {
553
+ padding: .6em .75em;
554
+ border: 1px solid var(--arcane-border, var(--border-color));
555
+ vertical-align: top;
556
+ overflow-wrap: anywhere;
577
557
  }
578
558
 
579
- th, td {
580
- text-align: left;
581
- word-wrap: break-word;
559
+ .chat_output th:not([align]),
560
+ .chat_output td:not([align]) {
561
+ text-align: start;
582
562
  }
583
563
 
584
- th{
585
- border:none;
586
- padding:.4em;
564
+ .chat_output thead {
565
+ background: var(--arcane-surface-muted, var(--secondary-color));
587
566
  }
588
567
 
589
- td{
590
- background: var(--border-color);
591
- padding:.4em;
592
- border: 1px solid var(--accent-color);
593
- overflow: visible;
568
+ .chat_output tbody tr:hover {
569
+ background: var(--arcane-surface-muted, var(--secondary-color));
594
570
  }
595
571
  </style>
596
572
 
@@ -833,6 +809,9 @@
833
809
  </section>
834
810
 
835
811
  <script type="module">
812
+ const {default:Is}=await import('strong-type');
813
+ const is=new Is(false);
814
+
836
815
  const {arcaneLogging}=await import('arcane-os/logging');
837
816
 
838
817
  const {
@@ -909,7 +888,7 @@
909
888
  host.language=host.language||'English';
910
889
  host.name = host.name||'User';
911
890
  host.aiName='AI';
912
- let requestedInitialSpeechMuted=typeof speech.initialMuted==='boolean'
891
+ let requestedInitialSpeechMuted=is.boolean(speech.initialMuted)
913
892
  ?speech.initialMuted
914
893
  :true;
915
894
  let initialSpeechMuteListenerInstalled=false;
@@ -1017,8 +996,8 @@
1017
996
  let causeCode='';
1018
997
  let message='';
1019
998
  try{
1020
- causeCode=typeof error?.code==='string'?error.code:'';
1021
- message=typeof error?.message==='string'?error.message:'';
999
+ causeCode=is.string(error?.code)?error.code:'';
1000
+ message=is.string(error?.message)?error.message:'';
1022
1001
  }catch{}
1023
1002
  return {
1024
1003
  error,
@@ -1035,7 +1014,7 @@
1035
1014
  }
1036
1015
 
1037
1016
  function isPlainRecord(value){
1038
- if(!value||typeof value!=='object'||Array.isArray(value))return false;
1017
+ if(!value||!is.object(value)||is.array(value))return false;
1039
1018
  const prototype=Object.getPrototypeOf(value);
1040
1019
  return prototype===Object.prototype||prototype===null;
1041
1020
  }
@@ -1045,10 +1024,10 @@
1045
1024
  // Only errors deliberately classified by their owner may provide UI copy.
1046
1025
  // Provider and protocol diagnostics remain complete in console/events.
1047
1026
  if(error?.userSafe===true){
1048
- const message=typeof error.userMessage==='string'
1027
+ const message=is.string(error.userMessage)
1049
1028
  ?error.userMessage
1050
1029
  :error.message;
1051
- if(typeof message==='string'&&message.trim()){
1030
+ if(is.string(message)&&message.trim()){
1052
1031
  return message;
1053
1032
  }
1054
1033
  }
@@ -1057,10 +1036,10 @@
1057
1036
  }
1058
1037
 
1059
1038
  function visibleProgressText(value,fallback,field){
1060
- if(typeof value==='string'){
1039
+ if(is.string(value)){
1061
1040
  return value||fallback;
1062
1041
  }
1063
- if(value&&typeof value==='object'){
1042
+ if(value&&is.object(value)){
1064
1043
  arcaneLogging.error(`Arcane chat progress ${field} is not text.`,value);
1065
1044
  return visibleErrorMessage(value,fallback);
1066
1045
  }
@@ -1085,10 +1064,10 @@
1085
1064
 
1086
1065
  function compatibleChatSession(session){
1087
1066
  return Boolean(session)
1088
- &&typeof session==='object'
1089
- &&typeof session.ready==='function'
1090
- &&typeof session.history==='function'
1091
- &&typeof session.send==='function';
1067
+ &&is.object(session)
1068
+ &&is.function(session.ready)
1069
+ &&is.function(session.history)
1070
+ &&is.function(session.send);
1092
1071
  }
1093
1072
 
1094
1073
  function scrollTranscriptToBottom(){
@@ -1102,7 +1081,7 @@
1102
1081
  const value=timestamp instanceof Date
1103
1082
  ?timestamp
1104
1083
  :new Date(timestamp);
1105
- if(timestamp!==undefined&&timestamp!==null&&Number.isFinite(value.getTime())){
1084
+ if(timestamp!==undefined&&timestamp!==null&&is.finite(value.getTime())){
1106
1085
  time.dateTime=value.toISOString();
1107
1086
  time.textContent=value.toLocaleTimeString([],{
1108
1087
  hour:'2-digit',
@@ -1119,7 +1098,7 @@
1119
1098
 
1120
1099
  function normalizeVisibleToolCalls(value){
1121
1100
  if(value===undefined) return [];
1122
- if(!Array.isArray(value)){
1101
+ if(!is.array(value)){
1123
1102
  const failure=new TypeError('Chat session tool calls must be an array.');
1124
1103
  failure.code='AI_CHAT_INVALID_TOOL_CALL';
1125
1104
  throw failure;
@@ -1128,18 +1107,18 @@
1128
1107
  return value.map((call,index)=>{
1129
1108
  if(
1130
1109
  !call
1131
- ||typeof call!=='object'
1132
- ||Array.isArray(call)
1110
+ ||!is.object(call)
1111
+ ||is.array(call)
1133
1112
  ||call.type!=='function'
1134
1113
  ||!call.function
1135
- ||typeof call.function!=='object'
1136
- ||Array.isArray(call.function)
1137
- ||typeof call.id!=='string'
1114
+ ||!is.object(call.function)
1115
+ ||is.array(call.function)
1116
+ ||!is.string(call.id)
1138
1117
  ||!call.id.trim()
1139
1118
  ||ids.has(call.id)
1140
- ||typeof call.function.name!=='string'
1119
+ ||!is.string(call.function.name)
1141
1120
  ||!call.function.name.trim()
1142
- ||typeof call.function.arguments!=='string'
1121
+ ||!is.string(call.function.arguments)
1143
1122
  ){
1144
1123
  const failure=new TypeError(`Chat session tool call ${index+1} is invalid.`);
1145
1124
  failure.code='AI_CHAT_INVALID_TOOL_CALL';
@@ -1157,9 +1136,9 @@
1157
1136
  }
1158
1137
 
1159
1138
  function copyCompleteChatData(value,seen=new Map()){
1160
- if(value===null||typeof value!=='object') return value;
1139
+ if(value===null||!is.object(value)) return value;
1161
1140
  if(seen.has(value)) return seen.get(value);
1162
- if(Array.isArray(value)){
1141
+ if(is.array(value)){
1163
1142
  const result=[];
1164
1143
  seen.set(value,result);
1165
1144
  for(const item of value) result.push(copyCompleteChatData(item,seen));
@@ -1177,12 +1156,12 @@
1177
1156
 
1178
1157
  function sameCompleteChatData(left,right,seen=new Map()){
1179
1158
  if(Object.is(left,right)) return true;
1180
- if(!left||!right||typeof left!=='object'||typeof right!=='object') return false;
1181
- if(Array.isArray(left)!==Array.isArray(right)) return false;
1159
+ if(!left||!right||!is.object(left)||!is.object(right)) return false;
1160
+ if(is.array(left)!==is.array(right)) return false;
1182
1161
  const matched=seen.get(left);
1183
1162
  if(matched!==undefined) return matched===right;
1184
1163
  seen.set(left,right);
1185
- if(Array.isArray(left)){
1164
+ if(is.array(left)){
1186
1165
  return left.length===right.length
1187
1166
  &&left.every((value,index)=>sameCompleteChatData(value,right[index],seen));
1188
1167
  }
@@ -1216,7 +1195,7 @@
1216
1195
  failure.code='AI_CHAT_INVALID_TOOL_CALL';
1217
1196
  throw failure;
1218
1197
  }
1219
- if(typeof argumentsRecord.message!=='string'||!argumentsRecord.message.trim()){
1198
+ if(!is.string(argumentsRecord.message)||!argumentsRecord.message.trim()){
1220
1199
  const failure=new TypeError(
1221
1200
  'Structural tool call arguments must include a nonempty user-facing message.'
1222
1201
  );
@@ -1227,7 +1206,7 @@
1227
1206
  }
1228
1207
 
1229
1208
  function setPendingStructuralToolCalls(calls=[]){
1230
- if(!Array.isArray(calls)||calls.length===0){
1209
+ if(!is.array(calls)||calls.length===0){
1231
1210
  pendingStructuralToolCalls=[];
1232
1211
  pendingStructuralToolMessage='';
1233
1212
  return [];
@@ -1244,8 +1223,8 @@
1244
1223
  }
1245
1224
 
1246
1225
  function sameStructuralToolCalls(left,right){
1247
- return Array.isArray(left)
1248
- &&Array.isArray(right)
1226
+ return is.array(left)
1227
+ &&is.array(right)
1249
1228
  &&left.length===right.length
1250
1229
  &&left.every((call,index)=>sameStructuralToolCall(call,right[index]));
1251
1230
  }
@@ -1308,7 +1287,7 @@
1308
1287
  }
1309
1288
 
1310
1289
  function setTranscriptMessageContent(item,content,timestamp){
1311
- if(typeof content!=='string'){
1290
+ if(!is.string(content)){
1312
1291
  throw new TypeError('Chat session message content must be a string.');
1313
1292
  }
1314
1293
  item.querySelector('.thinking')?.remove();
@@ -1345,8 +1324,8 @@
1345
1324
  }
1346
1325
 
1347
1326
  function latestCommittedTranscriptTurn(transcript,requestMessages){
1348
- if(!Array.isArray(transcript)) return null;
1349
- const expected=Array.isArray(requestMessages)
1327
+ if(!is.array(transcript)) return null;
1328
+ const expected=is.array(requestMessages)
1350
1329
  ?requestMessages
1351
1330
  :[requestMessages];
1352
1331
  if(!expected.length) return null;
@@ -1362,7 +1341,7 @@
1362
1341
  }
1363
1342
 
1364
1343
  function createTranscriptMessage(role,content,name,{timestamp,toolCalls}={}){
1365
- if(typeof content!=='string'){
1344
+ if(!is.string(content)){
1366
1345
  throw new TypeError('Chat session message content must be a string.');
1367
1346
  }
1368
1347
  const item=chatOutput.ownerDocument.createElement('li');
@@ -1402,7 +1381,7 @@
1402
1381
  }
1403
1382
 
1404
1383
  function createSavedRecordFallback(message,index){
1405
- const content=isPlainRecord(message)&&typeof message.content==='string'
1384
+ const content=isPlainRecord(message)&&is.string(message.content)
1406
1385
  ?message.content
1407
1386
  :'Saved record content is available in details.';
1408
1387
  const item=createTranscriptMessage(
@@ -1425,7 +1404,7 @@
1425
1404
  }
1426
1405
 
1427
1406
  function renderSessionHistory(history){
1428
- if(!Array.isArray(history)){
1407
+ if(!is.array(history)){
1429
1408
  throw new TypeError('Chat session history must be an array.');
1430
1409
  }
1431
1410
  const fragment=chatOutput.ownerDocument.createDocumentFragment();
@@ -1442,7 +1421,7 @@
1442
1421
  'AI_CHAT_INCOHERENT_PERSISTENCE'
1443
1422
  );
1444
1423
  }
1445
- if(typeof message.content!=='string'){
1424
+ if(!is.string(message.content)){
1446
1425
  throw chatError(
1447
1426
  'Saved chat contains a record without text content.',
1448
1427
  'AI_CHAT_INCOHERENT_PERSISTENCE'
@@ -1465,7 +1444,7 @@
1465
1444
  if(message.role==='tool'){
1466
1445
  if(message.tool_call_id!==undefined){
1467
1446
  if(
1468
- typeof message.tool_call_id!=='string'
1447
+ !is.string(message.tool_call_id)
1469
1448
  ||!message.tool_call_id.trim()
1470
1449
  ||!message.content.trim()
1471
1450
  ||!restoredPendingToolCalls.has(message.tool_call_id)
@@ -1494,7 +1473,7 @@
1494
1473
  const name=message.role==='user'
1495
1474
  ?host.name
1496
1475
  :message.role==='tool'
1497
- ?typeof message.name==='string'&&message.name.trim()
1476
+ ?is.string(message.name)&&message.name.trim()
1498
1477
  ?`Tool · ${message.name}`
1499
1478
  :'Tool'
1500
1479
  :message.role==='system'
@@ -1573,7 +1552,7 @@
1573
1552
  ai
1574
1553
  });
1575
1554
  await session.ready();
1576
- const history=typeof session.transcript==='function'
1555
+ const history=is.function(session.transcript)
1577
1556
  ?await session.transcript()
1578
1557
  :await session.history();
1579
1558
  if(destroyed||generation!==sessionBindingGeneration){
@@ -1653,10 +1632,10 @@
1653
1632
 
1654
1633
  function isAbortSignal(value){
1655
1634
  return value!==null
1656
- &&typeof value==='object'
1657
- &&typeof value.aborted==='boolean'
1658
- &&typeof value.addEventListener==='function'
1659
- &&typeof value.removeEventListener==='function';
1635
+ &&is.object(value)
1636
+ &&is.boolean(value.aborted)
1637
+ &&is.function(value.addEventListener)
1638
+ &&is.function(value.removeEventListener);
1660
1639
  }
1661
1640
 
1662
1641
  function createChatSubmissionAbort(reason,message){
@@ -1793,7 +1772,7 @@
1793
1772
  host.destroy=destroy;
1794
1773
  host.session=null;
1795
1774
  host.sessionStatus={state:'idle',message:'Chat session is not connected.'};
1796
- host.modelName=typeof host.modelName==='string'?host.modelName:'';
1775
+ host.modelName=is.string(host.modelName)?host.modelName:'';
1797
1776
  host.aiAvailability={llm:false,stt:false,tts:false};
1798
1777
  host.conversationComplete=false;
1799
1778
  applyAIAvailability(host.aiAvailability);
@@ -1826,7 +1805,7 @@
1826
1805
  );
1827
1806
 
1828
1807
  function setConversationComplete(complete=true){
1829
- if(typeof complete!=='boolean'){
1808
+ if(!is.boolean(complete)){
1830
1809
  throw new TypeError('Conversation completion state must be boolean.');
1831
1810
  }
1832
1811
 
@@ -1847,7 +1826,7 @@
1847
1826
  }
1848
1827
 
1849
1828
  function setInitialSpeechMuted(initialMuted){
1850
- if(typeof initialMuted!=='boolean'){
1829
+ if(!is.boolean(initialMuted)){
1851
1830
  throw new TypeError('initialMuted must be boolean');
1852
1831
  }
1853
1832
 
@@ -1883,13 +1862,13 @@
1883
1862
  }={}
1884
1863
  ){
1885
1864
  const messageId=String(id);
1886
- const normalizedTotal=Number.isFinite(Number(total))
1865
+ const normalizedTotal=is.finite(Number(total))
1887
1866
  ?Number(total)
1888
1867
  :0;
1889
- const normalizedCurrent=Number.isFinite(Number(current))
1868
+ const normalizedCurrent=is.finite(Number(current))
1890
1869
  ?Number(current)
1891
1870
  :0;
1892
- const normalizedFailed=Number.isFinite(Number(failed))
1871
+ const normalizedFailed=is.finite(Number(failed))
1893
1872
  ?Number(failed)
1894
1873
  :0;
1895
1874
  const message=messageId
@@ -1998,9 +1977,9 @@
1998
1977
 
1999
1978
  function hasSelectedAIRuntimeRole(role){
2000
1979
  const record=latestAIRuntimeRoles?.[role];
2001
- return typeof record?.providerId==='string'
1980
+ return is.string(record?.providerId)
2002
1981
  &&record.providerId.length>0
2003
- &&typeof record?.modelId==='string'
1982
+ &&is.string(record?.modelId)
2004
1983
  &&record.modelId.length>0;
2005
1984
  }
2006
1985
 
@@ -2046,26 +2025,40 @@
2046
2025
  }
2047
2026
 
2048
2027
  function selected(){
2049
- return typeof role?.providerId==='string'
2028
+ return is.string(role?.providerId)
2050
2029
  &&role.providerId.length>0
2051
- &&typeof role?.modelId==='string'
2030
+ &&is.string(role?.modelId)
2052
2031
  &&role.modelId.length>0;
2053
2032
  }
2054
2033
 
2055
2034
  function modelDisplayName(){
2056
- const configured=typeof host.modelName==='string'
2035
+ const configured=is.string(host.modelName)
2057
2036
  ?host.modelName.trim()
2058
2037
  :'';
2059
2038
  return configured||role?.modelId||'the selected language model';
2060
2039
  }
2061
2040
 
2062
2041
  function determinateProgress(){
2042
+ if(role?.progress?.phase==='initialize'){
2043
+ const {completed,total,unit}=role.progress;
2044
+ if(
2045
+ !is.finite(total)
2046
+ ||total<=0
2047
+ ||!is.finite(completed)
2048
+ ||completed<0
2049
+ ||!is.string(unit)
2050
+ ||!unit
2051
+ ){
2052
+ return null;
2053
+ }
2054
+ return {completed,total,unit,byteTelemetry:false};
2055
+ }
2063
2056
  const totalBytes=role?.progress?.totalBytes;
2064
2057
  const loadedBytes=role?.progress?.loadedBytes;
2065
2058
  if(
2066
- Number.isFinite(totalBytes)
2059
+ is.finite(totalBytes)
2067
2060
  &&totalBytes>0
2068
- &&Number.isFinite(loadedBytes)
2061
+ &&is.finite(loadedBytes)
2069
2062
  &&loadedBytes>=0
2070
2063
  ){
2071
2064
  return {
@@ -2077,13 +2070,13 @@
2077
2070
  }
2078
2071
  const total=Number(role?.progress?.total);
2079
2072
  const completed=Number(role?.progress?.completed);
2080
- const unit=typeof role?.progress?.unit==='string'&&role.progress.unit
2073
+ const unit=is.string(role?.progress?.unit)&&role.progress.unit
2081
2074
  ?role.progress.unit
2082
2075
  :'items';
2083
2076
  if(
2084
- !Number.isFinite(total)
2077
+ !is.finite(total)
2085
2078
  ||total<=0
2086
- ||!Number.isFinite(completed)
2079
+ ||!is.finite(completed)
2087
2080
  ){
2088
2081
  return null;
2089
2082
  }
@@ -2096,7 +2089,7 @@
2096
2089
  }
2097
2090
 
2098
2091
  function formatBytes(value){
2099
- if(!Number.isFinite(value)||value<0){
2092
+ if(!is.finite(value)||value<0){
2100
2093
  return '';
2101
2094
  }
2102
2095
  const bytes=value;
@@ -2127,7 +2120,7 @@
2127
2120
  }
2128
2121
 
2129
2122
  function approximateDuration(value){
2130
- if(!Number.isFinite(value)||value<0){
2123
+ if(!is.finite(value)||value<0){
2131
2124
  return '';
2132
2125
  }
2133
2126
  const suppliedSeconds=value;
@@ -2151,13 +2144,13 @@
2151
2144
  function activeTransferText(){
2152
2145
  const active=role?.progress?.activeTransfers;
2153
2146
  const limit=role?.progress?.transferLimit;
2154
- const mode=typeof role?.progress?.transferMode==='string'
2147
+ const mode=is.string(role?.progress?.transferMode)
2155
2148
  ?role.progress.transferMode.trim().toLowerCase()
2156
2149
  :'';
2157
2150
  if(
2158
- !Number.isSafeInteger(active)
2151
+ !is.safeInteger(active)
2159
2152
  ||active<0
2160
- ||!Number.isSafeInteger(limit)
2153
+ ||!is.safeInteger(limit)
2161
2154
  ||limit<1
2162
2155
  ||!mode
2163
2156
  ){
@@ -2181,9 +2174,11 @@
2181
2174
  return `${active} of ${limit} ${noun}${limit===1?'':'s'} active`;
2182
2175
  }
2183
2176
 
2184
- function progressElapsedText(){
2185
- const elapsedMilliseconds=Number(role?.progress?.elapsedMs);
2186
- if(!Number.isFinite(elapsedMilliseconds)||elapsedMilliseconds<0){
2177
+ function progressElapsedText(
2178
+ elapsedMilliseconds,
2179
+ suffix='elapsed'
2180
+ ){
2181
+ if(!is.finite(elapsedMilliseconds)||elapsedMilliseconds<0){
2187
2182
  return '';
2188
2183
  }
2189
2184
  const elapsedSeconds=Math.floor(elapsedMilliseconds/1000);
@@ -2191,20 +2186,27 @@
2191
2186
  const minutes=Math.floor(elapsedSeconds%3600/60);
2192
2187
  const seconds=elapsedSeconds%60;
2193
2188
  if(hours>0){
2194
- return `${hours}h ${minutes}m elapsed`;
2189
+ return `${hours}h ${minutes}m ${suffix}`;
2195
2190
  }
2196
2191
  if(minutes>0){
2197
- return `${minutes}m ${String(seconds).padStart(2,'0')}s elapsed`;
2192
+ return `${minutes}m ${String(seconds).padStart(2,'0')}s ${suffix}`;
2198
2193
  }
2199
- return `${seconds}s elapsed`;
2194
+ return `${seconds}s ${suffix}`;
2200
2195
  }
2201
2196
 
2202
2197
  function progressMessage(){
2203
2198
  const measured=determinateProgress();
2204
- const phaseValue=typeof role?.progress?.phase==='string'&&role.progress.phase
2199
+ const phaseValue=is.string(role?.progress?.phase)&&role.progress.phase
2205
2200
  ?role.progress.phase
2206
2201
  :'loading';
2207
- const phase=phaseValue.replaceAll('-',' ');
2202
+ const initializing=phaseValue==='initialize';
2203
+ const phase=initializing
2204
+ ?is.string(role?.progress?.message)&&role.progress.message
2205
+ ?role.progress.message
2206
+ :is.string(role?.progress?.stage)&&role.progress.stage
2207
+ ?role.progress.stage.replaceAll('-',' ')
2208
+ :'Initializing the selected model'
2209
+ :phaseValue.replaceAll('-',' ');
2208
2210
  const parts=[
2209
2211
  `Loading ${modelDisplayName()} through the Arcane SDK`,
2210
2212
  phase
@@ -2217,7 +2219,7 @@
2217
2219
  );
2218
2220
  parts.push(`${formatBytes(remainingBytes)} remaining`);
2219
2221
  const bytesPerSecond=role?.progress?.bytesPerSecond;
2220
- if(Number.isFinite(bytesPerSecond)&&bytesPerSecond>=0){
2222
+ if(is.finite(bytesPerSecond)&&bytesPerSecond>=0){
2221
2223
  parts.push(`${formatBytes(bytesPerSecond)}/s`);
2222
2224
  }else{
2223
2225
  parts.push('Calculating speed');
@@ -2232,18 +2234,38 @@
2232
2234
  :measured.unit;
2233
2235
  parts.push(`${measured.completed} of ${measured.total} ${suffix}`);
2234
2236
  }
2235
- const transfers=activeTransferText();
2237
+ const transfers=initializing?'':activeTransferText();
2236
2238
  if(transfers){
2237
2239
  parts.push(transfers);
2238
2240
  }
2239
- const elapsed=progressElapsedText();
2241
+ const elapsed=progressElapsedText(Number(role?.progress?.elapsedMs));
2240
2242
  if(elapsed){
2241
2243
  parts.push(elapsed);
2242
- if(role?.progress?.heartbeat===true){
2244
+ if(!initializing&&role?.progress?.heartbeat===true){
2243
2245
  parts.push('Still working');
2244
2246
  }
2245
2247
  }
2246
- if(!measured?.byteTelemetry){
2248
+ if(initializing){
2249
+ const phaseElapsed=progressElapsedText(
2250
+ role.progress.phaseElapsedMs,
2251
+ 'in this stage'
2252
+ );
2253
+ if(phaseElapsed){
2254
+ parts.push(phaseElapsed);
2255
+ }
2256
+ if(role.progress.heartbeat===true){
2257
+ const activityElapsed=progressElapsedText(
2258
+ role.progress.activityElapsedMs,
2259
+ 'ago'
2260
+ );
2261
+ if(activityElapsed){
2262
+ parts.push(`Last runtime activity ${activityElapsed}`);
2263
+ }
2264
+ }
2265
+ parts.push(measured
2266
+ ?'Keep this tab open'
2267
+ :'Completion percentage unavailable; keep this tab open');
2268
+ }else if(!measured?.byteTelemetry){
2247
2269
  parts.push('The first activation can take several minutes; keep this tab open');
2248
2270
  }
2249
2271
  return parts.join(' · ');
@@ -2342,7 +2364,7 @@
2342
2364
  requestError='';
2343
2365
  const intent={role:'llm',action,reason:'user'};
2344
2366
  const request={intent,state:role};
2345
- const operationId=typeof role?.operationId==='string'&&role.operationId
2367
+ const operationId=is.string(role?.operationId)&&role.operationId
2346
2368
  ?role.operationId
2347
2369
  :createOperationId('llm-activation');
2348
2370
  const generation=++requestGeneration;
@@ -2480,9 +2502,9 @@
2480
2502
  if(
2481
2503
  !controller
2482
2504
  ||requiredMethods.some(function missingConversationTimeboxMethod(name){
2483
- return typeof controller[name]!=='function';
2505
+ return !is.function(controller[name]);
2484
2506
  })
2485
- ||typeof controller.limitReachedMessage!=='string'
2507
+ ||!is.string(controller.limitReachedMessage)
2486
2508
  ){
2487
2509
  throw new TypeError('A compatible conversation timebox controller is required.');
2488
2510
  }
@@ -2583,7 +2605,7 @@
2583
2605
  }
2584
2606
 
2585
2607
  function queueConversationTimeboxMessage(message,context,guard){
2586
- const operationId=typeof context?.operationId==='string'&&context.operationId
2608
+ const operationId=is.string(context?.operationId)&&context.operationId
2587
2609
  ?context.operationId
2588
2610
  :nextChatOperationId('conversation-timebox');
2589
2611
  const deliveryContext={...context,operationId};
@@ -2597,7 +2619,7 @@
2597
2619
  if(destroyed||aiRuntimeStateAbortController.signal.aborted){
2598
2620
  return false;
2599
2621
  }
2600
- if(typeof guard==='function'&&!guard()){
2622
+ if(is.function(guard)&&!guard()){
2601
2623
  return false;
2602
2624
  }
2603
2625
  let lastError;
@@ -2615,7 +2637,7 @@
2615
2637
  if(destroyed||aiRuntimeStateAbortController.signal.aborted){
2616
2638
  return false;
2617
2639
  }
2618
- if(typeof guard==='function'&&!guard()){
2640
+ if(is.function(guard)&&!guard()){
2619
2641
  return false;
2620
2642
  }
2621
2643
  if(
@@ -2638,7 +2660,7 @@
2638
2660
  if(destroyed||aiRuntimeStateAbortController.signal.aborted){
2639
2661
  return false;
2640
2662
  }
2641
- if(typeof guard==='function'&&!guard()){
2663
+ if(is.function(guard)&&!guard()){
2642
2664
  return false;
2643
2665
  }
2644
2666
  return requireConversationTimeboxDelivery(delivered);
@@ -2657,7 +2679,7 @@
2657
2679
  if(destroyed||aiRuntimeStateAbortController.signal.aborted){
2658
2680
  return false;
2659
2681
  }
2660
- if(typeof guard==='function'&&!guard()){
2682
+ if(is.function(guard)&&!guard()){
2661
2683
  return false;
2662
2684
  }
2663
2685
  if(result!==false){
@@ -2673,7 +2695,7 @@
2673
2695
  if(destroyed||aiRuntimeStateAbortController.signal.aborted){
2674
2696
  return false;
2675
2697
  }
2676
- if(typeof guard==='function'&&!guard()){
2698
+ if(is.function(guard)&&!guard()){
2677
2699
  return false;
2678
2700
  }
2679
2701
  setConversationTimeboxError(
@@ -2746,7 +2768,7 @@
2746
2768
  );
2747
2769
 
2748
2770
  async function streamMessage(text='', id='', isThinking){
2749
- if(typeof text!=='string'){
2771
+ if(!is.string(text)){
2750
2772
  if(text instanceof Error){
2751
2773
  arcaneLogging.error('Arcane chat stream delivered an Error as content.',text);
2752
2774
  throw text;
@@ -2816,7 +2838,7 @@
2816
2838
  &&speech.muted===false
2817
2839
  ){
2818
2840
  const aiRuntime=boundChatAI??globalThis.ai;
2819
- if(typeof aiRuntime?.streamTTS==='function'){
2841
+ if(is.function(aiRuntime?.streamTTS)){
2820
2842
  void Promise.resolve(aiRuntime.streamTTS(text)).catch(
2821
2843
  reportTTSError
2822
2844
  );
@@ -3070,7 +3092,7 @@
3070
3092
  );
3071
3093
  }
3072
3094
 
3073
- const sessionRequestMessages=Array.isArray(sessionRequest)
3095
+ const sessionRequestMessages=is.array(sessionRequest)
3074
3096
  ?sessionRequest
3075
3097
  :[sessionRequest];
3076
3098
  if(!sessionRequestMessages.length){
@@ -3103,7 +3125,7 @@
3103
3125
  status:'Working'
3104
3126
  }
3105
3127
  );
3106
- const result=typeof session.stream==='function'
3128
+ const result=is.function(session.stream)
3107
3129
  ?await session.stream(
3108
3130
  request,
3109
3131
  {
@@ -3154,13 +3176,13 @@
3154
3176
  ){
3155
3177
  return false;
3156
3178
  }
3157
- if(!result||typeof result!=='object'||Array.isArray(result)){
3179
+ if(!result||!is.object(result)||is.array(result)){
3158
3180
  throw new TypeError('The chat session returned an invalid result.');
3159
3181
  }
3160
- if(!result.message||typeof result.message.content!=='string'){
3182
+ if(!result.message||!is.string(result.message.content)){
3161
3183
  throw new TypeError('The chat session result must contain assistant message content.');
3162
3184
  }
3163
- const committedTurn=typeof session.transcript==='function'
3185
+ const committedTurn=is.function(session.transcript)
3164
3186
  ?latestCommittedTranscriptTurn(
3165
3187
  await session.transcript(),
3166
3188
  sessionRequestMessages
@@ -3351,7 +3373,7 @@
3351
3373
  error.code='AI_CHAT_TOOL_RESULT_NOT_PENDING';
3352
3374
  throw error;
3353
3375
  }
3354
- if(!Array.isArray(options.results)||options.results.length===0){
3376
+ if(!is.array(options.results)||options.results.length===0){
3355
3377
  throw new TypeError('Tool results must be a nonempty array.');
3356
3378
  }
3357
3379
  const statuses=new Map([
@@ -3393,7 +3415,7 @@
3393
3415
  }
3394
3416
  const toolCallId=result.toolCallId;
3395
3417
  if(
3396
- typeof toolCallId!=='string'
3418
+ !is.string(toolCallId)
3397
3419
  ||!pendingById.has(toolCallId)
3398
3420
  ||resultsById.has(toolCallId)
3399
3421
  ){
@@ -3416,11 +3438,11 @@
3416
3438
  'Tool-result status must be executed, declined, cancelled, or not-executed.'
3417
3439
  );
3418
3440
  }
3419
- if(typeof result.message!=='string'||!result.message.trim()){
3441
+ if(!is.string(result.message)||!result.message.trim()){
3420
3442
  throw new TypeError('Tool-result message must contain user-facing text.');
3421
3443
  }
3422
3444
  const persist=result.persist??true;
3423
- if(typeof persist!=='boolean'){
3445
+ if(!is.boolean(persist)){
3424
3446
  throw new TypeError('Tool-result persist must be boolean.');
3425
3447
  }
3426
3448
  resultsById.set(toolCallId,{
@@ -3450,7 +3472,7 @@
3450
3472
  error.code='AI_CHAT_INCOHERENT_PERSISTENCE';
3451
3473
  throw error;
3452
3474
  }
3453
- const operationId=typeof context.operationId==='string'&&context.operationId
3475
+ const operationId=is.string(context.operationId)&&context.operationId
3454
3476
  ?context.operationId
3455
3477
  :nextChatOperationId('tool-results');
3456
3478
  const ownership=createChatSubmissionOwnership(context.signal??null);
@@ -3580,10 +3602,10 @@
3580
3602
  if(destroyed){
3581
3603
  return false;
3582
3604
  }
3583
- if(typeof textOverride!=='string'){
3605
+ if(!is.string(textOverride)){
3584
3606
  throw new TypeError('Programmatic chat messages must be strings.');
3585
3607
  }
3586
- if(!context||typeof context!=='object'||Array.isArray(context)){
3608
+ if(!context||!is.object(context)||is.array(context)){
3587
3609
  throw new TypeError('Chat submission context must be an object.');
3588
3610
  }
3589
3611
  if(host.conversationComplete){
@@ -3610,7 +3632,7 @@
3610
3632
  if(!text.trim()){
3611
3633
  return false;
3612
3634
  }
3613
- const operationId=typeof submissionContext.operationId==='string'
3635
+ const operationId=is.string(submissionContext.operationId)
3614
3636
  &&submissionContext.operationId
3615
3637
  ?submissionContext.operationId
3616
3638
  :nextChatOperationId('message');
@@ -3680,7 +3702,7 @@
3680
3702
  if(host.aiAvailability.tts&&speech.muted===false){
3681
3703
  const aiRuntime=boundChatAI??globalThis.ai;
3682
3704
  aiRuntime?.stopAudio?.();
3683
- if(typeof aiRuntime?.resumeAudio==='function'){
3705
+ if(is.function(aiRuntime?.resumeAudio)){
3684
3706
  void Promise.resolve(aiRuntime.resumeAudio()).catch(
3685
3707
  function reportPlaybackResumeFailure(error){
3686
3708
  reportTTSError(error,'playback-resume');
@@ -3717,7 +3739,7 @@
3717
3739
  if(destroyed){
3718
3740
  return false;
3719
3741
  }
3720
- if(typeof speech.reportTTSError==='function'){
3742
+ if(is.function(speech.reportTTSError)){
3721
3743
  try{
3722
3744
  speech.reportTTSError(error,boundary);
3723
3745
  return true;