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,3 +1,6 @@
1
+ import Is from 'strong-type';
2
+ const is=new Is(false);
3
+
1
4
  /**
2
5
  * First-class browser module for Arcane's capability-gated Ollama service.
3
6
  * Apps should import this module instead of connecting to localhost:11434.
@@ -59,7 +62,7 @@ export class Ollama{
59
62
  try{
60
63
  const response=await this.version()
61
64
  const version=String(
62
- typeof response==='string' ? response : response?.version||''
65
+ is.string(response) ? response : response?.version||''
63
66
  ).trim()||null
64
67
  return {ready:true,version,errorCode:null}
65
68
  }catch(error){
@@ -95,8 +98,8 @@ export class Ollama{
95
98
  publicDetail:{ready:true,reason}
96
99
  }
97
100
  )
98
- if(typeof globalThis.CustomEvent==='function'
99
- &&typeof globalThis.dispatchEvent==='function'){
101
+ if(is.function(globalThis.CustomEvent)
102
+ &&is.function(globalThis.dispatchEvent)){
100
103
  projectArcaneDOMEvent(globalThis,occurrence)
101
104
  }
102
105
  }
@@ -1,3 +1,6 @@
1
+ import Is from 'strong-type';
2
+ const is=new Is(false);
3
+
1
4
  const OLLAMA_MODEL_IDENTIFIER=
2
5
  /^[A-Za-z0-9][A-Za-z0-9._/-]{0,191}(?::[A-Za-z0-9][A-Za-z0-9._-]{0,63})?$/;
3
6
 
@@ -7,7 +10,7 @@ const OLLAMA_MODEL_IDENTIFIER=
7
10
  * compatibility from the name.
8
11
  */
9
12
  export function normalizeOllamaModelIdentifier(value){
10
- if(typeof value!=='string'||value!==value.trim()){
13
+ if(!is.string(value)||value!==value.trim()){
11
14
  return null;
12
15
  }
13
16
  if(!OLLAMA_MODEL_IDENTIFIER.test(value)||value.toUpperCase()==='TWIN'){
@@ -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 ApiModelDatabase from './ApiModelDatabase.js';
3
6
  import {WeatherDay,WeatherLocation,WeatherObservation,WeatherSnapshot} from '../entities/Weather.js';
@@ -41,18 +44,18 @@ function signalLike(value){
41
44
  return value===undefined
42
45
  ||value===null
43
46
  ||(
44
- typeof value==='object'
45
- &&typeof value.aborted==='boolean'
46
- &&typeof value.addEventListener==='function'
47
- &&typeof value.removeEventListener==='function'
47
+ is.object(value)
48
+ &&is.boolean(value.aborted)
49
+ &&is.function(value.addEventListener)
50
+ &&is.function(value.removeEventListener)
48
51
  );
49
52
  }
50
53
 
51
54
  function defineWeatherError(error,contract,message){
52
- const candidate=error&&(typeof error==='object'||typeof error==='function')
55
+ const candidate=error&&(is.object(error)||is.function(error))
53
56
  ?error
54
- :new Error(typeof error==='string'&&error.trim()?error:message);
55
- const priorCode=typeof candidate.code==='string'&&candidate.code?candidate.code:null;
57
+ :new Error(is.string(error)&&error.trim()?error:message);
58
+ const priorCode=is.string(candidate.code)&&candidate.code?candidate.code:null;
56
59
  try{
57
60
  if(priorCode&&priorCode!==contract.code&&!Object.hasOwn(candidate,'providerCode')){
58
61
  Object.defineProperty(candidate,'providerCode',{configurable:true,enumerable:false,value:priorCode,writable:true});
@@ -65,7 +68,7 @@ function defineWeatherError(error,contract,message){
65
68
  return candidate;
66
69
  }catch{
67
70
  const replacement=new Error(
68
- typeof candidate.message==='string'&&candidate.message.trim()?candidate.message:message
71
+ is.string(candidate.message)&&candidate.message.trim()?candidate.message:message
69
72
  );
70
73
  replacement.code=contract.code;
71
74
  replacement.reason=contract.reason;
@@ -111,7 +114,7 @@ function normalizedOperationError(error,record){
111
114
 
112
115
  function operationOptions(value){
113
116
  if(value===undefined)return {signal:null};
114
- if(!value||typeof value!=='object'||Array.isArray(value)){
117
+ if(!value||!is.object(value)||is.array(value)){
115
118
  throw invalidOptionsError('Open-Meteo operation options must be an object.');
116
119
  }
117
120
  if(!signalLike(value.signal)){
@@ -207,7 +210,7 @@ export default class OpenMeteoWeatherProvider extends EventTarget{
207
210
  }
208
211
 
209
212
  #releaseSignal(record){
210
- if(!Array.isArray(record.cleanup))return;
213
+ if(!is.array(record.cleanup))return;
211
214
  for(const remove of record.cleanup.splice(0))remove();
212
215
  }
213
216
 
@@ -295,7 +298,7 @@ export default class OpenMeteoWeatherProvider extends EventTarget{
295
298
  #forwardRequest(kind,event){
296
299
  if(this.#disposed||this.#events.disposed)return;
297
300
  const operationId=event?.operationId??event?.detail?.requestId;
298
- if(typeof operationId!=='string'||!operationId)return;
301
+ if(!is.string(operationId)||!operationId)return;
299
302
  const record=this.#operations.get(operationId);
300
303
  this.#dispatch(
301
304
  'request',
@@ -307,7 +310,7 @@ export default class OpenMeteoWeatherProvider extends EventTarget{
307
310
  #forwardError(kind,event){
308
311
  if(this.#disposed||this.#events.disposed)return;
309
312
  const operationId=event?.operationId??event?.detail?.requestId;
310
- if(typeof operationId!=='string'||!operationId)return;
313
+ if(!is.string(operationId)||!operationId)return;
311
314
  const record=this.#operations.get(operationId);
312
315
  if(record){
313
316
  if(record.errorPublished)return;
@@ -394,7 +397,7 @@ export default class OpenMeteoWeatherProvider extends EventTarget{
394
397
  precipitationUnit='inch',
395
398
  forecastDays
396
399
  }=options;
397
- if(forecastDays!==undefined&&(!Number.isSafeInteger(forecastDays)||forecastDays<=0)){
400
+ if(forecastDays!==undefined&&(!is.safeInteger(forecastDays)||forecastDays<=0)){
398
401
  throw invalidOptionsError('Open-Meteo forecastDays must be a positive integer when provided.');
399
402
  }
400
403
  const record=this.#startOperation('forecast-load',options.signal??null);
@@ -458,8 +461,8 @@ export default class OpenMeteoWeatherProvider extends EventTarget{
458
461
  'The weather event type is invalid.'
459
462
  );
460
463
  }
461
- const operationId=(typeof detail?.operationId==='string'&&detail.operationId)
462
- ||(typeof detail?.requestId==='string'&&detail.requestId)
464
+ const operationId=(is.string(detail?.operationId)&&detail.operationId)
465
+ ||(is.string(detail?.requestId)&&detail.requestId)
463
466
  ||`${this.#events.instanceId}:emit:${(++this.#operationSequence).toString(36)}`;
464
467
  this.#dispatch(type,detail,operationId);
465
468
  }
@@ -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 ChatEntity from '../entities/Chat.js';
3
6
  import ConfiguredAIChatSession,{
@@ -28,7 +31,7 @@ function coded(error,code){
28
31
  }
29
32
 
30
33
  function isPlainRecord(value){
31
- if(!value||typeof value!=='object'||Array.isArray(value))return false;
34
+ if(!value||!is.object(value)||is.array(value))return false;
32
35
  const prototype=Object.getPrototypeOf(value);
33
36
  return prototype===Object.prototype||prototype===null;
34
37
  }
@@ -40,16 +43,16 @@ function assertKnownKeys(value,allowed,label){
40
43
 
41
44
  function boolean(value,label,defaultValue){
42
45
  if(value===undefined) return defaultValue;
43
- if(typeof value!=='boolean') throw new TypeError(`${label} must be a boolean.`);
46
+ if(!is.boolean(value)) throw new TypeError(`${label} must be a boolean.`);
44
47
  return value;
45
48
  }
46
49
 
47
50
  function signalLike(value){
48
51
  return value===undefined||value===null||(
49
- typeof value==='object'
50
- &&typeof value.aborted==='boolean'
51
- &&typeof value.addEventListener==='function'
52
- &&typeof value.removeEventListener==='function'
52
+ is.object(value)
53
+ &&is.boolean(value.aborted)
54
+ &&is.function(value.addEventListener)
55
+ &&is.function(value.removeEventListener)
53
56
  );
54
57
  }
55
58
 
@@ -61,7 +64,7 @@ function providerRequestWithoutLifecycleCallbacks(value){
61
64
 
62
65
  async function configuredArcaneChat(request){
63
66
  const api=globalThis.Arcane?.ai;
64
- if(typeof api?.chat!=='function'){
67
+ if(!is.function(api?.chat)){
65
68
  throw coded(
66
69
  new Error('The configured Arcane AI chat capability is unavailable.'),
67
70
  'AI_CHAT_UNAVAILABLE'
@@ -85,7 +88,7 @@ function normalizeStreamHandlers(value){
85
88
  'Persistent chat stream handlers',
86
89
  );
87
90
  for(const key of ['onChunk','onDataChunk','onDataResult','onToolCall']){
88
- if(value[key]!==undefined&&typeof value[key]!=='function'){
91
+ if(value[key]!==undefined&&!is.function(value[key])){
89
92
  throw new TypeError(`${key} must be a function when provided.`);
90
93
  }
91
94
  }
@@ -99,7 +102,7 @@ function normalizeStreamHandlers(value){
99
102
 
100
103
  function normalizeStreamResponse(terminal,output){
101
104
  const value=terminal??output;
102
- if(typeof value==='string'){
105
+ if(is.string(value)){
103
106
  return {message:{role:'assistant',content:value}};
104
107
  }
105
108
  if(isPlainRecord(value)) return value;
@@ -120,14 +123,14 @@ function terminalStructuralToolCalls(response){
120
123
  }
121
124
  const messages=hasMessage
122
125
  ?[response.message]
123
- :hasChoices&&Array.isArray(response.choices)
126
+ :hasChoices&&is.array(response.choices)
124
127
  ?response.choices.map(choice=>choice?.message).filter(Boolean)
125
128
  :[];
126
129
  const calls=[];
127
130
  for(let messageIndex=0;messageIndex<messages.length;messageIndex++){
128
131
  const value=messages[messageIndex]?.tool_calls;
129
132
  if(value===undefined) continue;
130
- if(!Array.isArray(value)){
133
+ if(!is.array(value)){
131
134
  throw coded(
132
135
  new TypeError('The AI stream terminal response contains invalid structural tool calls.'),
133
136
  'AI_CHAT_INVALID_TOOL_CALL',
@@ -146,12 +149,12 @@ function terminalStructuralToolCalls(response){
146
149
 
147
150
  function sameDataValue(left,right,seen=new Map()){
148
151
  if(Object.is(left,right)) return true;
149
- if(!left||!right||typeof left!=='object'||typeof right!=='object') return false;
150
- if(Array.isArray(left)!==Array.isArray(right)) return false;
152
+ if(!left||!right||!is.object(left)||!is.object(right)) return false;
153
+ if(is.array(left)!==is.array(right)) return false;
151
154
  const matched=seen.get(left);
152
155
  if(matched!==undefined) return matched===right;
153
156
  seen.set(left,right);
154
- if(Array.isArray(left)){
157
+ if(is.array(left)){
155
158
  return left.length===right.length
156
159
  &&left.every((value,index)=>sameDataValue(value,right[index],seen));
157
160
  }
@@ -168,8 +171,8 @@ function sameStructuralToolCall(left,right){
168
171
  }
169
172
 
170
173
  function sameStructuralToolCalls(left,right){
171
- return Array.isArray(left)
172
- &&Array.isArray(right)
174
+ return is.array(left)
175
+ &&is.array(right)
173
176
  &&left.length===right.length
174
177
  &&left.every((call,index)=>sameStructuralToolCall(call,right[index]));
175
178
  }
@@ -183,13 +186,13 @@ function normalizeSend(input){
183
186
  throw new TypeError('Persistent chat input must contain exactly one message or messages field.');
184
187
  }
185
188
  const sourceMessages=hasMessages?input.messages:[input.message];
186
- if(!Array.isArray(sourceMessages)||!sourceMessages.length){
189
+ if(!is.array(sourceMessages)||!sourceMessages.length){
187
190
  throw new TypeError('messages must be a nonempty array of tool-result messages.');
188
191
  }
189
192
  const normalizedMessages=sourceMessages.map((value,index)=>{
190
193
  const label=hasMessages?`messages[${index}]`:'message';
191
194
  if(!isPlainRecord(value)) throw new TypeError(`${label} must be a plain object.`);
192
- if(typeof value.content!=='string'||!value.content.trim()){
195
+ if(!is.string(value.content)||!value.content.trim()){
193
196
  throw new TypeError(`${label}.content must contain text.`);
194
197
  }
195
198
  const role=value.role??'user';
@@ -202,24 +205,24 @@ function normalizeSend(input){
202
205
  let persistenceName=null;
203
206
  let persistenceStatus=null;
204
207
  if(role==='tool'){
205
- if(typeof value.tool_call_id!=='string'||!value.tool_call_id.trim()){
208
+ if(!is.string(value.tool_call_id)||!value.tool_call_id.trim()){
206
209
  throw new TypeError(`${label}.tool_call_id is required for tool messages.`);
207
210
  }
208
211
  toolCallId=value.tool_call_id;
209
212
  if(value.message!==undefined){
210
- if(typeof value.message!=='string'||!value.message.trim()){
213
+ if(!is.string(value.message)||!value.message.trim()){
211
214
  throw new TypeError(`${label}.message must contain user-facing text when provided.`);
212
215
  }
213
216
  persistenceMessage=value.message;
214
217
  }
215
218
  if(value.name!==undefined){
216
- if(typeof value.name!=='string'||!value.name.trim()){
219
+ if(!is.string(value.name)||!value.name.trim()){
217
220
  throw new TypeError(`${label}.name must contain text when provided.`);
218
221
  }
219
222
  persistenceName=value.name;
220
223
  }
221
224
  if(value.status!==undefined){
222
- if(typeof value.status!=='string'||!value.status.trim()){
225
+ if(!is.string(value.status)||!value.status.trim()){
223
226
  throw new TypeError(`${label}.status must contain text when provided.`);
224
227
  }
225
228
  persistenceStatus=value.status;
@@ -292,7 +295,7 @@ function normalizeOpening(input){
292
295
  assertKnownKeys(input,new Set(['message','request','signal']),'Persistent chat opening input');
293
296
  if(!isPlainRecord(input.message)) throw new TypeError('message must be a plain object.');
294
297
  assertKnownKeys(input.message,new Set(['content','persist','role']),'message');
295
- if(typeof input.message.content!=='string'||!input.message.content.trim()){
298
+ if(!is.string(input.message.content)||!input.message.content.trim()){
296
299
  throw new TypeError('message.content must contain text.');
297
300
  }
298
301
  const role=input.message.role??'user';
@@ -317,7 +320,7 @@ function normalizeOpening(input){
317
320
  }
318
321
 
319
322
  function fileName(value){
320
- if(typeof value!=='string'||value.length===0){
323
+ if(!is.string(value)||value.length===0){
321
324
  throw new TypeError('chatFileName must be a nonempty string.');
322
325
  }
323
326
  return value;
@@ -355,8 +358,8 @@ class PersistentAIChatSession{
355
358
  }
356
359
  if(options.ai!==undefined&&(
357
360
  !options.ai
358
- ||typeof options.ai!=='object'
359
- ||typeof options.ai.fetchRequest!=='function'
361
+ ||!is.object(options.ai)
362
+ ||!is.function(options.ai.fetchRequest)
360
363
  )){
361
364
  throw new TypeError('ai must expose fetchRequest(request).');
362
365
  }
@@ -369,7 +372,7 @@ class PersistentAIChatSession{
369
372
  const chat=options.ai===undefined
370
373
  ?options.chat??configuredArcaneChat
371
374
  :configuredAIChat(options.ai);
372
- if(typeof chat!=='function') throw new TypeError('chat must be a function.');
375
+ if(!is.function(chat)) throw new TypeError('chat must be a function.');
373
376
  if(options.request!==undefined&&!isPlainRecord(options.request)){
374
377
  throw new TypeError('request must be a plain object.');
375
378
  }
@@ -381,12 +384,12 @@ class PersistentAIChatSession{
381
384
  throw new TypeError(`request.${managedRequestField} is managed by the chat session.`);
382
385
  }
383
386
  for(const key of ['parallelToolCalls','parallel_tool_calls']){
384
- if(Object.hasOwn(request,key)&&typeof request[key]!=='boolean'){
387
+ if(Object.hasOwn(request,key)&&!is.boolean(request[key])){
385
388
  throw new TypeError(`request.${key} must be a boolean when provided.`);
386
389
  }
387
390
  }
388
391
  const systemPrompt=options.systemPrompt??'';
389
- if(typeof systemPrompt!=='string') throw new TypeError('systemPrompt must be a string.');
392
+ if(!is.string(systemPrompt)) throw new TypeError('systemPrompt must be a string.');
390
393
  this.#entity=options.chatEntity??new ChatEntity(systemPrompt);
391
394
  if(options.chatFileName!==undefined){
392
395
  this.#entity.fileName=fileName(options.chatFileName);
@@ -418,7 +421,7 @@ class PersistentAIChatSession{
418
421
  async #requestConfiguredAI(request){
419
422
  const providerRequest=providerRequestWithoutLifecycleCallbacks(request);
420
423
  if(!this.#activeStream) return this.#fetchChat(providerRequest);
421
- if(typeof this.#options.ai?.streamRequest!=='function'){
424
+ if(!is.function(this.#options.ai?.streamRequest)){
422
425
  const response=await this.#fetchChat(providerRequest);
423
426
  await this.#activeStream.onDataResult(response,providerRequest.id??null);
424
427
  return response;
@@ -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 Preference,{preferenceSchema} from '../entities/Preference.js';
3
6
  import {resolveApplicationLocalStorageKey} from './AppDataScope.js';
@@ -47,17 +50,17 @@ function operationAbortedError(reason){
47
50
  }
48
51
 
49
52
  function isPlainRecord(value){
50
- if(!value||typeof value!=='object'||Array.isArray(value)) return false;
53
+ if(!value||!is.object(value)||is.array(value)) return false;
51
54
  const prototype=Object.getPrototypeOf(value);
52
55
  return prototype===Object.prototype||prototype===null;
53
56
  }
54
57
 
55
58
  function isAbortSignal(value){
56
59
  return Boolean(value)
57
- &&typeof value==='object'
58
- &&typeof value.aborted==='boolean'
59
- &&typeof value.addEventListener==='function'
60
- &&typeof value.removeEventListener==='function';
60
+ &&is.object(value)
61
+ &&is.boolean(value.aborted)
62
+ &&is.function(value.addEventListener)
63
+ &&is.function(value.removeEventListener);
61
64
  }
62
65
 
63
66
  function normalizeOperationOptions(value={}){
@@ -108,10 +111,10 @@ function completePreferenceValues(values){
108
111
 
109
112
  function validateAdapter(adapter){
110
113
  if(!adapter
111
- ||typeof adapter.get!=='function'
112
- ||typeof adapter.set!=='function'
113
- ||typeof adapter.delete!=='function'
114
- ||(Reflect.has(adapter,'setMany')&&typeof adapter.setMany!=='function')){
114
+ ||!is.function(adapter.get)
115
+ ||!is.function(adapter.set)
116
+ ||!is.function(adapter.delete)
117
+ ||(Reflect.has(adapter,'setMany')&&!is.function(adapter.setMany))){
115
118
  throw preferenceStoreError(
116
119
  PREFERENCE_STORE_ERROR_CODES.adapterInvalid,
117
120
  'preference-storage-adapter-invalid',
@@ -144,9 +147,9 @@ function localAdapter(prefix){
144
147
 
145
148
  function nativeAdapter(){
146
149
  const preferences=globalThis.Arcane?.preferences;
147
- if(typeof preferences?.get!=='function'
148
- ||typeof preferences?.set!=='function'
149
- ||typeof preferences?.delete!=='function') return null;
150
+ if(!is.function(preferences?.get)
151
+ ||!is.function(preferences?.set)
152
+ ||!is.function(preferences?.delete)) return null;
150
153
  return preferences;
151
154
  }
152
155
 
@@ -156,7 +159,7 @@ function isUnsupportedNativeAdapter(error){
156
159
 
157
160
  function preferenceAdapter(){
158
161
  const local=localAdapter('arcane.preferences');
159
- if(typeof globalThis.arcaneAndroid?.postMessage==='function') return local;
162
+ if(is.function(globalThis.arcaneAndroid?.postMessage)) return local;
160
163
  const native=nativeAdapter();
161
164
  if(!native) return local;
162
165
  let active=native;
@@ -167,7 +170,7 @@ function preferenceAdapter(){
167
170
  if(active!==native||!isUnsupportedNativeAdapter(error)) throw error;
168
171
  active=local;
169
172
  delete adapter.setMany;
170
- if(typeof active[method]!=='function') throw error;
173
+ if(!is.function(active[method])) throw error;
171
174
  return active[method](...args);
172
175
  }
173
176
  }
@@ -182,7 +185,7 @@ function preferenceAdapter(){
182
185
  return call('delete',[key]);
183
186
  }
184
187
  };
185
- if(typeof native.setMany==='function'){
188
+ if(is.function(native.setMany)){
186
189
  adapter.setMany=async function setMany(entries,context){
187
190
  return call('setMany',[entries,context]);
188
191
  };
@@ -327,7 +330,7 @@ export default class PreferenceStore extends EventTarget{
327
330
  return this.#enqueueOperation(
328
331
  async function setAllPreferences(commitPreferenceOperation){
329
332
  const context={operationId,signal:operation.signal};
330
- if(typeof store.adapter.setMany==='function'){
333
+ if(is.function(store.adapter.setMany)){
331
334
  const batch={};
332
335
  for(const entry of entries){
333
336
  setDataProperty(batch,entry.storageKey,entry.value);
@@ -437,7 +440,7 @@ export default class PreferenceStore extends EventTarget{
437
440
  const publicDetail={
438
441
  namespace:this.namespace,
439
442
  values,
440
- ...(typeof detail.key==='string'
443
+ ...(is.string(detail.key)
441
444
  ?{preferenceId:detail.key,value:detail.value}
442
445
  :{})
443
446
  };