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
@@ -1,5 +1,5 @@
1
1
  import { arcaneLogging } from 'arcane-os/logging';
2
- import Is from '../../node_modules/strong-type/index.js';
2
+ import Is from 'strong-type';
3
3
  import '../modules/DBOPFS.js';
4
4
  import '../modules/AI.js';
5
5
  import {
@@ -12,7 +12,7 @@ import {normalizeMemoryContent} from '../modules/MemoryRecords.js';
12
12
  const is = new Is(false);
13
13
 
14
14
  function plainRecord(value){
15
- if(!value||typeof value!=='object'||Array.isArray(value))return false;
15
+ if(!value||!is.object(value)||is.array(value))return false;
16
16
  const prototype=Object.getPrototypeOf(value);
17
17
  return prototype===Object.prototype||prototype===null;
18
18
  }
@@ -23,9 +23,9 @@ function coded(error,code){
23
23
  }
24
24
 
25
25
  function copyCompleteValue(value,seen=new Map()){
26
- if(value===null||typeof value!=='object')return value;
26
+ if(value===null||!is.object(value))return value;
27
27
  if(seen.has(value))return seen.get(value);
28
- if(Array.isArray(value)){
28
+ if(is.array(value)){
29
29
  const result=[];
30
30
  seen.set(value,result);
31
31
  for(const item of value)result.push(copyCompleteValue(item,seen));
@@ -45,7 +45,7 @@ function copyCompleteValue(value,seen=new Map()){
45
45
 
46
46
  function copyToolCalls(value){
47
47
  if(value===undefined) return null;
48
- if(!Array.isArray(value)){
48
+ if(!is.array(value)){
49
49
  const error=new TypeError('assistantMessage.tool_calls must be an array.');
50
50
  error.code='AI_CHAT_INVALID_TOOL_CALL';
51
51
  throw error;
@@ -61,12 +61,12 @@ function copyToolCalls(value){
61
61
  const name=call.function.name;
62
62
  const argumentValue=call.function.arguments;
63
63
  if(
64
- typeof id!=='string'
64
+ !is.string(id)
65
65
  ||!id.trim()
66
66
  ||ids.has(id)
67
- ||typeof name!=='string'
67
+ ||!is.string(name)
68
68
  ||!name.trim()
69
- ||typeof argumentValue!=='string'
69
+ ||!is.string(argumentValue)
70
70
  ){
71
71
  const error=new TypeError(`assistantMessage.tool_calls[${index}] is invalid.`);
72
72
  error.code='AI_CHAT_INVALID_TOOL_CALL';
@@ -90,7 +90,7 @@ function copyToolCalls(value){
90
90
  error.code='AI_CHAT_INVALID_TOOL_CALL';
91
91
  throw error;
92
92
  }
93
- if(typeof argumentRecord.message!=='string'||!argumentRecord.message.trim()){
93
+ if(!is.string(argumentRecord.message)||!argumentRecord.message.trim()){
94
94
  const error=new TypeError(
95
95
  `assistantMessage.tool_calls[${index}].function.arguments.message must contain user-facing text.`
96
96
  );
@@ -114,7 +114,7 @@ function structuralToolMessage(call){
114
114
 
115
115
  function storedStructuralToolCalls(value){
116
116
  if(value===undefined) return [];
117
- if(!Array.isArray(value)){
117
+ if(!is.array(value)){
118
118
  arcaneLogging.error('Arcane stored assistant tool calls were not an array and were not retained.');
119
119
  return [];
120
120
  }
@@ -133,12 +133,12 @@ function storedStructuralToolCalls(value){
133
133
  }
134
134
 
135
135
  function storedToolRecord({content,name,status,timestamp}){
136
- if(typeof content!=='string'||!content.trim()) return [];
136
+ if(!is.string(content)||!content.trim()) return [];
137
137
  return [{
138
138
  role:'tool',
139
139
  content,
140
- ...(typeof name==='string'&&name.trim()?{name}:{}),
141
- ...(typeof status==='string'&&status.trim()?{status}:{}),
140
+ ...(is.string(name)&&name.trim()?{name}:{}),
141
+ ...(is.string(status)&&status.trim()?{status}:{}),
142
142
  ...(timestamp!==undefined?{timestamp}:{}),
143
143
  }];
144
144
  }
@@ -151,7 +151,7 @@ function storedChatRecords(messages,{memoryOnly=false}={}){
151
151
  if(memoryOnly&&message.memory_excluded===true) continue;
152
152
  const timestamp=message.timestamp;
153
153
  if(message.role==='user'){
154
- if(typeof message.content!=='string') continue;
154
+ if(!is.string(message.content)) continue;
155
155
  result.push({
156
156
  role:'user',
157
157
  content:message.content,
@@ -181,7 +181,7 @@ function storedChatRecords(messages,{memoryOnly=false}={}){
181
181
  continue;
182
182
  }
183
183
  if(message.role==='tool'){
184
- const protocolResult=typeof message.tool_call_id==='string'&&message.tool_call_id;
184
+ const protocolResult=is.string(message.tool_call_id)&&message.tool_call_id;
185
185
  result.push(...storedToolRecord({
186
186
  content:protocolResult?message.persistence_message:message.content,
187
187
  name:message.persistence_name??message.name,
@@ -198,12 +198,12 @@ function retainedChatMessages(messages){
198
198
  }
199
199
 
200
200
  function turnMessage(value,label){
201
- if(!plainRecord(value)||!['tool','user'].includes(value.role)||typeof value.content!=='string'){
201
+ if(!plainRecord(value)||!['tool','user'].includes(value.role)||!is.string(value.content)){
202
202
  throw new TypeError(`${label} must be a user or tool message.`);
203
203
  }
204
204
  if(value.role==='tool'){
205
205
  const toolCallId=value.tool_call_id;
206
- if(typeof toolCallId!=='string'||!toolCallId.trim()){
206
+ if(!is.string(toolCallId)||!toolCallId.trim()){
207
207
  throw coded(
208
208
  new TypeError(`${label}.tool_call_id is invalid.`),
209
209
  'AI_CHAT_INVALID_TOOL_MESSAGE'
@@ -277,13 +277,13 @@ function pendingToolCalls(messages){
277
277
  if(toolCallId===undefined){
278
278
  continue;
279
279
  }
280
- if(typeof toolCallId!=='string'||!toolCallId.trim()){
280
+ if(!is.string(toolCallId)||!toolCallId.trim()){
281
281
  throw coded(
282
282
  new TypeError(`Chat message ${index+1} has an invalid tool_call_id.`),
283
283
  'AI_CHAT_INCOHERENT_PERSISTENCE'
284
284
  );
285
285
  }
286
- if(typeof message.content!=='string'||!message.content.trim()){
286
+ if(!is.string(message.content)||!message.content.trim()){
287
287
  throw coded(
288
288
  new TypeError(`Chat message ${index+1} has a blank tool result.`),
289
289
  'AI_CHAT_INCOHERENT_PERSISTENCE'
@@ -308,7 +308,7 @@ function turnMessages(requestMessage,requestMessages){
308
308
  throw new TypeError('Provide exactly one of requestMessage or requestMessages.');
309
309
  }
310
310
  const values=hasMessages?requestMessages:[requestMessage];
311
- if(!Array.isArray(values)||values.length===0){
311
+ if(!is.array(values)||values.length===0){
312
312
  throw new TypeError('requestMessages must be a nonempty array.');
313
313
  }
314
314
  const records=values.map((value,index)=>
@@ -513,7 +513,7 @@ class ChatEntity{
513
513
  * @returns {Array<*>}
514
514
  */
515
515
  set messages(v){
516
- if(!Array.isArray(v)){
516
+ if(!is.array(v)){
517
517
  throw new TypeError('messages must be an array.');
518
518
  }
519
519
  this.#messages=v.map(message=>copyCompleteValue(message));
@@ -538,7 +538,7 @@ class ChatEntity{
538
538
  * @returns {boolean}
539
539
  */
540
540
  set saved(v){
541
- if(typeof v!=='boolean'){
541
+ if(!is.boolean(v)){
542
542
  throw new TypeError('saved must be a boolean.');
543
543
  }
544
544
  this.#saved=v;
@@ -646,9 +646,9 @@ class ChatEntity{
646
646
  const toolCallId=id;
647
647
  const toolName=name;
648
648
  if(
649
- typeof toolCallId!=='string'
649
+ !is.string(toolCallId)
650
650
  ||!toolCallId.trim()
651
- ||typeof toolName!=='string'
651
+ ||!is.string(toolName)
652
652
  ||!toolName.trim()
653
653
  ){
654
654
  throw new TypeError('Tool exchanges require an id and name.');
@@ -663,13 +663,13 @@ class ChatEntity{
663
663
  );
664
664
  }
665
665
 
666
- const serializedArguments=typeof argumentValue==='string'
666
+ const serializedArguments=is.string(argumentValue)
667
667
  ?argumentValue
668
668
  :JSON.stringify(argumentValue);
669
- const serializedResult=typeof result==='string'
669
+ const serializedResult=is.string(result)
670
670
  ?result
671
671
  :JSON.stringify(result);
672
- if(typeof serializedArguments!=='string'||typeof serializedResult!=='string'){
672
+ if(!is.string(serializedArguments)||!is.string(serializedResult)){
673
673
  throw new TypeError('Tool exchange arguments and results must be JSON-compatible.');
674
674
  }
675
675
  const toolCall=copyToolCalls([{
@@ -723,7 +723,7 @@ class ChatEntity{
723
723
  if(!is.boolean(messagePersist)||!is.boolean(responsePersist)||!is.boolean(extractMemory)){
724
724
  throw new TypeError('Turn persistence and memory options must be boolean.');
725
725
  }
726
- if(typeof memoryRequest!=='function'){
726
+ if(!is.function(memoryRequest)){
727
727
  throw new TypeError('memoryRequest must be a function.');
728
728
  }
729
729
  if(messagePersist!==responsePersist){
@@ -762,7 +762,7 @@ class ChatEntity{
762
762
  const toolCalls=copyToolCalls(assistantMessage.tool_calls);
763
763
  const assistantContent=assistantMessage.content??'';
764
764
  const assistantReasoning=assistantMessage.reasoning_content;
765
- if(assistantReasoning!==undefined&&typeof assistantReasoning!=='string'){
765
+ if(assistantReasoning!==undefined&&!is.string(assistantReasoning)){
766
766
  throw new TypeError('assistantMessage.reasoning_content must be a string when provided.');
767
767
  }
768
768
  if(
@@ -770,7 +770,7 @@ class ChatEntity{
770
770
  ||(
771
771
  !String(assistantContent)
772
772
  &&!toolCalls?.length
773
- &&!(typeof assistantReasoning==='string'&&assistantReasoning.length)
773
+ &&!(is.string(assistantReasoning)&&assistantReasoning.length)
774
774
  )
775
775
  ){
776
776
  throw new TypeError(
@@ -823,7 +823,7 @@ class ChatEntity{
823
823
  this.fileName
824
824
  );
825
825
 
826
- if(!content||(Array.isArray(content)&&content.length===0)){
826
+ if(!content||(is.array(content)&&content.length===0)){
827
827
  const systemMessage=this.#messages.find(message=>message.role==='system');
828
828
  this.#messages=systemMessage?[systemMessage]:[];
829
829
  this.#persistedMessageCount=0;
@@ -832,7 +832,7 @@ class ChatEntity{
832
832
  return this.transcript;
833
833
  }
834
834
 
835
- const loadedMessages=Array.isArray(content)
835
+ const loadedMessages=is.array(content)
836
836
  ?content.map(message=>copyCompleteValue(message))
837
837
  :String(content)
838
838
  .split('\n')
@@ -858,7 +858,7 @@ class ChatEntity{
858
858
  }
859
859
 
860
860
  async getMemoriesAboutUser({request=messages=>ai.fetch(messages)}={}){
861
- if(typeof request!=='function'){
861
+ if(!is.function(request)){
862
862
  throw new TypeError('Memory request must be a function.');
863
863
  }
864
864
  return this.#writeMemory(
@@ -1,10 +1,13 @@
1
+ import Is from 'strong-type';
2
+ const is=new Is(false);
3
+
1
4
  const CHANNELS=new Set(['email','sms','mms','rcs','whatsapp','other']);
2
5
  const DIRECTIONS=new Set(['inbound','outbound']);
3
6
  const STATUSES=new Set(['draft','queued','sending','sent','delivered','read','failed','received']);
4
7
 
5
8
  function required(value,label){const text=String(value??'').trim();if(!text) throw new TypeError(`${label} is required.`);return text;}
6
9
  function strings(value){return Array.from(value||[],item=>String(item??'').trim()).filter(Boolean);}
7
- function instant(value){const date=value instanceof Date?value:new Date(value||Date.now());if(Number.isNaN(date.valueOf())) throw new TypeError('Message timestamp is invalid.');return date.toISOString();}
10
+ function instant(value){const date=value instanceof Date?value:new Date(value||Date.now());if(is.NaN(date.valueOf())) throw new TypeError('Message timestamp is invalid.');return date.toISOString();}
8
11
 
9
12
  export default class CommunicationMessage{
10
13
  constructor(input={}){
@@ -1,3 +1,6 @@
1
+ import Is from 'strong-type';
2
+ const is=new Is(false);
3
+
1
4
  import CommunicationMessage from './CommunicationMessage.js';
2
5
 
3
6
  function required(value,label){const text=String(value??'').trim();if(!text) throw new TypeError(`${label} is required.`);return text;}
@@ -11,7 +14,7 @@ export default class CommunicationThread{
11
14
  this.participants=Object.freeze(Array.from(input.participants||[],value=>String(value??'').trim()).filter(Boolean));
12
15
  this.preview=String(input.preview||'');
13
16
  const date=new Date(input.updatedAt||Date.now());
14
- if(Number.isNaN(date.valueOf())) throw new TypeError('Thread update time is invalid.');
17
+ if(is.NaN(date.valueOf())) throw new TypeError('Thread update time is invalid.');
15
18
  this.updatedAt=date.toISOString();
16
19
  this.unreadCount=Math.max(0,Number.parseInt(input.unreadCount||0,10)||0);
17
20
  this.messages=Object.freeze(Array.from(input.messages||[],value=>value instanceof CommunicationMessage?value:new CommunicationMessage({...value,threadId:value.threadId||this.id,providerId:value.providerId||this.providerId,channel:value.channel||this.channel})));
@@ -1,4 +1,4 @@
1
- import Is from '../../node_modules/strong-type/index.js';
1
+ import Is from 'strong-type';
2
2
  import '../modules/DBOPFS.js';
3
3
  import MD from '../modules/MD.js';
4
4
 
@@ -1,3 +1,6 @@
1
+ import Is from 'strong-type';
2
+ const is=new Is(false);
3
+
1
4
  import FileEntity from './File.js';
2
5
 
3
6
  /**
@@ -34,11 +37,11 @@ class ImageEntity extends FileEntity {
34
37
  * @returns {boolean}
35
38
  */
36
39
  static isImageFile(file) {
37
- if (!file || typeof file.name !== 'string') {
40
+ if (!file || !is.string(file.name)) {
38
41
  return false;
39
42
  }
40
43
 
41
- const hasImageMime = typeof file.type === 'string' && file.type.startsWith('image/');
44
+ const hasImageMime = is.string(file.type) && file.type.startsWith('image/');
42
45
  const hasImageExt = /\.(png|jpe?g|svg)$/i.test(file.name);
43
46
 
44
47
  return hasImageMime || hasImageExt;
@@ -51,12 +54,12 @@ class ImageEntity extends FileEntity {
51
54
  * @returns {boolean}
52
55
  */
53
56
  static isPNGFile(file) {
54
- if (!file || typeof file.name !== 'string') {
57
+ if (!file || !is.string(file.name)) {
55
58
  return false;
56
59
  }
57
60
 
58
61
  const lowerName = file.name.toLowerCase();
59
- const hasPngMime = typeof file.type === 'string' && file.type.toLowerCase() === 'image/png';
62
+ const hasPngMime = is.string(file.type) && file.type.toLowerCase() === 'image/png';
60
63
  const hasPngExt = lowerName.endsWith('.png');
61
64
 
62
65
  return hasPngMime || hasPngExt;
@@ -125,7 +128,7 @@ class ImageEntity extends FileEntity {
125
128
  * @returns {void}
126
129
  */
127
130
  static revokeBlobURL(url = '') {
128
- if (typeof url === 'string' && url.startsWith('blob:')) {
131
+ if (is.string(url) && url.startsWith('blob:')) {
129
132
  URL.revokeObjectURL(url);
130
133
  }
131
134
  }
@@ -1,3 +1,6 @@
1
+ import Is from 'strong-type';
2
+ const is=new Is(false);
3
+
1
4
  const IDENTIFIER_MAXIMUM_CHARACTERS = 128;
2
5
 
3
6
  const SCHEMA = 'arcane.intent-envelope';
@@ -60,7 +63,7 @@ function fail(code, path, message, options) {
60
63
  }
61
64
 
62
65
  function isReservedKey(key) {
63
- return typeof key === 'string' && RESERVED_KEYS.has(key);
66
+ return is.string(key) && RESERVED_KEYS.has(key);
64
67
  }
65
68
 
66
69
  function validateUnicode(value, path) {
@@ -86,7 +89,7 @@ function normalizedString(value, path, options = {}) {
86
89
  if (nullable && value === null) {
87
90
  return null;
88
91
  }
89
- if (typeof value !== 'string') {
92
+ if (!is.string(value)) {
90
93
  fail('INTENT_ENVELOPE_INVALID', path, 'Intent envelope field has an invalid type.');
91
94
  }
92
95
  validateUnicode(value, path);
@@ -102,7 +105,7 @@ function identifier(value, path, options = {}) {
102
105
  if (nullable && value === null) {
103
106
  return null;
104
107
  }
105
- if (typeof value !== 'string') {
108
+ if (!is.string(value)) {
106
109
  fail('INTENT_ENVELOPE_INVALID', path, 'Intent envelope identifier has an invalid type.');
107
110
  }
108
111
  validateUnicode(value, path);
@@ -113,14 +116,14 @@ function identifier(value, path, options = {}) {
113
116
  }
114
117
 
115
118
  function enumeration(value, path, values) {
116
- if (typeof value !== 'string' || !values.has(value)) {
119
+ if (!is.string(value) || !values.has(value)) {
117
120
  fail('INTENT_ENVELOPE_INVALID', path, 'Intent envelope enum value is invalid.');
118
121
  }
119
122
  return value;
120
123
  }
121
124
 
122
125
  function plainRecord(value, path, allowedKeys, active) {
123
- if (!value || typeof value !== 'object' || Array.isArray(value)) {
126
+ if (!value || !is.object(value) || is.array(value)) {
124
127
  fail('INTENT_ENVELOPE_INVALID', path, 'Intent envelope field must be a plain record.');
125
128
  }
126
129
  const prototype = Object.getPrototypeOf(value);
@@ -139,7 +142,7 @@ function plainRecord(value, path, allowedKeys, active) {
139
142
  const ownKeys = Reflect.ownKeys(value);
140
143
 
141
144
  for (const key of ownKeys) {
142
- if (typeof key !== 'string' || isReservedKey(key) || !allowed.has(key)) {
145
+ if (!is.string(key) || isReservedKey(key) || !allowed.has(key)) {
143
146
  active.delete(value);
144
147
  fail('INTENT_ENVELOPE_UNKNOWN_FIELD', path, 'Intent envelope contains an unsupported field.');
145
148
  }
@@ -172,7 +175,7 @@ function ownValue(record, key, path, options = {}) {
172
175
  }
173
176
 
174
177
  function denseArray(value, path, active) {
175
- if (!Array.isArray(value) || Object.getPrototypeOf(value) !== Array.prototype) {
178
+ if (!is.array(value) || Object.getPrototypeOf(value) !== Array.prototype) {
176
179
  fail('INTENT_ENVELOPE_INVALID', path, 'Intent envelope field must be an array.');
177
180
  }
178
181
  if (active.has(value)) {
@@ -184,7 +187,7 @@ function denseArray(value, path, active) {
184
187
  if (key === 'length') {
185
188
  continue;
186
189
  }
187
- if (typeof key !== 'string' || isReservedKey(key) || !/^(0|[1-9]\d*)$/.test(key) || Number(key) >= value.length) {
190
+ if (!is.string(key) || isReservedKey(key) || !/^(0|[1-9]\d*)$/.test(key) || Number(key) >= value.length) {
188
191
  active.delete(value);
189
192
  fail('INTENT_ENVELOPE_UNKNOWN_FIELD', path, 'Intent envelope array contains an unsupported field.');
190
193
  }
@@ -208,24 +211,24 @@ function finishArray(value, active) {
208
211
  }
209
212
 
210
213
  function normalizeTimestamp(value, path) {
211
- if (typeof value !== 'string') {
214
+ if (!is.string(value)) {
212
215
  fail('INTENT_ENVELOPE_INVALID', path, 'Intent envelope timestamp is invalid.');
213
216
  }
214
217
  const instant = new Date(value);
215
- if (Number.isNaN(instant.valueOf()) || instant.toISOString() !== value) {
218
+ if (is.NaN(instant.valueOf()) || instant.toISOString() !== value) {
216
219
  fail('INTENT_ENVELOPE_INVALID', path, 'Intent envelope timestamp is invalid.');
217
220
  }
218
221
  return value;
219
222
  }
220
223
 
221
224
  function normalizeTrustedTimestamp(value, path) {
222
- if (!(typeof value === 'string' || value instanceof Date)) {
225
+ if (!(is.string(value) || value instanceof Date)) {
223
226
  fail('INTENT_ENVELOPE_INVALID', path, 'Intent envelope timestamp is invalid.');
224
227
  }
225
228
 
226
229
  const instant = new Date(value);
227
230
 
228
- if (Number.isNaN(instant.valueOf())) {
231
+ if (is.NaN(instant.valueOf())) {
229
232
  fail('INTENT_ENVELOPE_INVALID', path, 'Intent envelope timestamp is invalid.');
230
233
  }
231
234
 
@@ -245,7 +248,7 @@ function normalizeGoal(value, path, active) {
245
248
  const version = identifier(versionValue, `${path}.version`);
246
249
  const confidenceValue = ownValue(record, 'confidence', path);
247
250
  if (confidenceValue !== null && (
248
- typeof confidenceValue !== 'number' || !Number.isFinite(confidenceValue) || confidenceValue < 0 || confidenceValue > 1
251
+ !is.number(confidenceValue) || !is.finite(confidenceValue) || confidenceValue < 0 || confidenceValue > 1
249
252
  )) {
250
253
  finishRecord(record, active);
251
254
  fail('INTENT_ENVELOPE_INVALID', `${path}.confidence`, 'Intent envelope confidence is invalid.');
@@ -256,15 +259,15 @@ function normalizeGoal(value, path, active) {
256
259
  }
257
260
 
258
261
  function normalizeScalar(value, path) {
259
- if (value === null || typeof value === 'string' || typeof value === 'boolean') {
260
- if (typeof value === 'string') {
262
+ if (value === null || is.string(value) || is.boolean(value)) {
263
+ if (is.string(value)) {
261
264
  validateUnicode(value, path);
262
265
  }
263
266
 
264
267
  return value;
265
268
  }
266
269
 
267
- if (typeof value === 'number' && Number.isFinite(value)) {
270
+ if (is.number(value) && is.finite(value)) {
268
271
  return value;
269
272
  }
270
273
 
@@ -704,7 +707,7 @@ export function createIntentEnvelope(payload, trustedContext) {
704
707
  }
705
708
 
706
709
  export function rehydrateIntentEnvelope(canonical) {
707
- if (typeof canonical === 'string') {
710
+ if (is.string(canonical)) {
708
711
  try {
709
712
  const parsed = JSON.parse(canonical);
710
713
  return buildEnvelope(parsed);
@@ -1,3 +1,6 @@
1
+ import Is from 'strong-type';
2
+ const is=new Is(false);
3
+
1
4
  const KEY_PATTERN=/^[A-Za-z0-9][A-Za-z0-9._:-]*$/;
2
5
  const TYPES=new Set(['boolean','number','select','text']);
3
6
 
@@ -18,9 +21,9 @@ export default class Preference{
18
21
  this.description=String(definition.description||'');
19
22
  this.defaultValue=this.normalize(definition.defaultValue);
20
23
  this.options=type==='select'?this.normalizeOptions(definition.options):[];
21
- this.minimum=Number.isFinite(Number(definition.minimum))?Number(definition.minimum):undefined;
22
- this.maximum=Number.isFinite(Number(definition.maximum))?Number(definition.maximum):undefined;
23
- this.step=Number.isFinite(Number(definition.step))?Number(definition.step):undefined;
24
+ this.minimum=is.finite(Number(definition.minimum))?Number(definition.minimum):undefined;
25
+ this.maximum=is.finite(Number(definition.maximum))?Number(definition.maximum):undefined;
26
+ this.step=is.finite(Number(definition.step))?Number(definition.step):undefined;
24
27
 
25
28
  if(type==='select'&&!this.options.some(option=>Object.is(option.value,this.defaultValue))){
26
29
  throw new TypeError(`Preference ${key} has a default value outside its options.`);
@@ -28,9 +31,9 @@ export default class Preference{
28
31
  }
29
32
 
30
33
  normalizeOptions(options=[]){
31
- if(!Array.isArray(options)||!options.length) throw new TypeError('Select preferences require options.');
34
+ if(!is.array(options)||!options.length) throw new TypeError('Select preferences require options.');
32
35
  return options.map(option=>{
33
- const normalized=typeof option==='object'&&option!==null
36
+ const normalized=is.object(option)&&option!==null
34
37
  ?{label:String(option.label??option.value),value:this.normalize(option.value)}
35
38
  :{label:String(option),value:this.normalize(option)};
36
39
  return normalized;
@@ -47,7 +50,7 @@ export default class Preference{
47
50
  case 'boolean': return value===true||value==='true'||value===1;
48
51
  case 'number': {
49
52
  const number=Number(value);
50
- if(!Number.isFinite(number)) throw new TypeError('Numeric preferences require finite values.');
53
+ if(!is.finite(number)) throw new TypeError('Numeric preferences require finite values.');
51
54
  return number;
52
55
  }
53
56
  default: return String(value);
@@ -1,3 +1,6 @@
1
+ import Is from 'strong-type';
2
+ const is=new Is(false);
3
+
1
4
  const HEX_COLOR=/^#([0-9a-f]{6})$/i;
2
5
  const RGB_COLOR=/^(rgb|rgba)\(\s*(\d{1,3})(?:\s*,\s*|\s+)(\d{1,3})(?:\s*,\s*|\s+)(\d{1,3})(?:\s*(?:,|\/)\s*(0|1|0?\.\d+))?\s*\)$/i;
3
6
 
@@ -74,7 +77,7 @@ export default class Theme{
74
77
  constructor(input={}){
75
78
  this.name=normalizeName(input.name||'My Arcane skin');
76
79
  this.scheme=input.scheme==='dark'?'dark':'light';
77
- const source=input.tokens&&typeof input.tokens==='object'?input.tokens:{};
80
+ const source=input.tokens&&is.object(input.tokens)?input.tokens:{};
78
81
  const defaults=this.scheme==='dark'?arcaneDarkThemeTokens:arcaneLightThemeTokens;
79
82
  this.tokens=Object.freeze(Object.fromEntries(themeTokens.map(token=>[
80
83
  token.key,
@@ -101,7 +104,7 @@ export default class Theme{
101
104
 
102
105
  static fromJSON(value){
103
106
  if(value instanceof Theme) return value;
104
- const parsed=typeof value==='string'?JSON.parse(value):value;
107
+ const parsed=is.string(value)?JSON.parse(value):value;
105
108
  return new Theme(parsed);
106
109
  }
107
110
  }
@@ -1,5 +1,5 @@
1
1
  import { arcaneLogging } from 'arcane-os/logging';
2
- import Is from '../../node_modules/strong-type/index.js';
2
+ import Is from 'strong-type';
3
3
  import DBLS from '../modules/DBLS.js';
4
4
  import {
5
5
  arcaneEvents,
@@ -30,14 +30,14 @@ function createDefaultDashboard(){
30
30
  }
31
31
 
32
32
  function normalizeDashboard(dashboard={}){
33
- const source=dashboard&&typeof dashboard==='object'&&!Array.isArray(dashboard)
34
- &&dashboard.charts&&typeof dashboard.charts==='object'&&!Array.isArray(dashboard.charts)
33
+ const source=dashboard&&is.object(dashboard)&&!is.array(dashboard)
34
+ &&dashboard.charts&&is.object(dashboard.charts)&&!is.array(dashboard.charts)
35
35
  ? dashboard.charts
36
36
  : {};
37
37
  const charts={};
38
38
 
39
39
  for(const [key,value] of Object.entries(source)){
40
- if(typeof value==='boolean'){
40
+ if(is.boolean(value)){
41
41
  charts[key]=value;
42
42
  }
43
43
  }
@@ -646,9 +646,9 @@ class UserEntity {
646
646
  /** @param {number} v */
647
647
  set firstBootUp(v){
648
648
  if(
649
- typeof v!=='number'
650
- ||!Number.isFinite(v)
651
- ||!Number.isSafeInteger(v)
649
+ !is.number(v)
650
+ ||!is.finite(v)
651
+ ||!is.safeInteger(v)
652
652
  ||v<0
653
653
  ){
654
654
  throw new Error(
@@ -757,7 +757,7 @@ class UserEntity {
757
757
 
758
758
  /** @param {{charts:Object<string,boolean>}} v */
759
759
  set dashboard(v){
760
- if(!v||typeof v!=='object'||Array.isArray(v)){
760
+ if(!v||!is.object(v)||is.array(v)){
761
761
  throw new Error('dashboard must be an object');
762
762
  }
763
763
 
@@ -885,7 +885,7 @@ class UserEntity {
885
885
  }
886
886
 
887
887
  withFreshExplicit(operation){
888
- if(typeof operation!=='function'){
888
+ if(!is.function(operation)){
889
889
  throw new TypeError('A fresh UserEntity operation is required.');
890
890
  }
891
891
  const result=this.#explicitUpdateQueue.then(()=>
@@ -916,13 +916,13 @@ class UserEntity {
916
916
  #withExplicitLock(operation){
917
917
  const locks=globalThis.navigator?.locks;
918
918
 
919
- if(typeof locks?.request==='function'){
919
+ if(is.function(locks?.request)){
920
920
  return locks.request(
921
921
  `user-entity:${this.fileName}`,
922
922
  operation
923
923
  );
924
924
  }
925
- if(typeof globalThis.window==='object'){
925
+ if(is.object(globalThis.window)){
926
926
  const error=new Error(
927
927
  'This browser cannot safely coordinate profile changes across tabs.'
928
928
  );
@@ -1,6 +1,9 @@
1
- function number(value,label,{minimum=-Infinity,maximum=Infinity}={}){const result=Number(value);if(!Number.isFinite(result)||result<minimum||result>maximum)throw new TypeError(`${label} is invalid.`);return result;}
1
+ import Is from 'strong-type';
2
+ const is=new Is(false);
3
+
4
+ function number(value,label,{minimum=-Infinity,maximum=Infinity}={}){const result=Number(value);if(!is.finite(result)||result<minimum||result>maximum)throw new TypeError(`${label} is invalid.`);return result;}
2
5
  function text(value,label){const result=String(value??'');if(!result.trim())throw new TypeError(`${label} is required.`);return result;}
3
- function instant(value,label){const date=new Date(value);if(Number.isNaN(date.valueOf()))throw new TypeError(`${label} is invalid.`);return date.toISOString();}
6
+ function instant(value,label){const date=new Date(value);if(is.NaN(date.valueOf()))throw new TypeError(`${label} is invalid.`);return date.toISOString();}
4
7
 
5
8
  export class WeatherLocation{
6
9
  constructor({id='',name,region='',country='',latitude,longitude,timezone='auto'}={}){this.id=String(id||`${latitude},${longitude}`);this.name=text(name,'Weather location name');this.region=String(region||'');this.country=String(country||'');this.latitude=number(latitude,'Latitude',{minimum:-90,maximum:90});this.longitude=number(longitude,'Longitude',{minimum:-180,maximum:180});this.timezone=String(timezone||'auto');}