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
  const APPLICATION_ID_PATTERN=/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
2
5
  const APPLICATION_ID_MAX_LENGTH=64;
3
6
  const APP_DATA_DIRECTORY='apps';
@@ -18,7 +21,7 @@ function scopeError(code,message){
18
21
  * @returns {string}
19
22
  */
20
23
  export function canonicalApplicationId(value,label='applicationId'){
21
- if(typeof value!=='string'
24
+ if(!is.string(value)
22
25
  ||value.length<1
23
26
  ||value.length>APPLICATION_ID_MAX_LENGTH
24
27
  ||!APPLICATION_ID_PATTERN.test(value)){
@@ -112,13 +115,13 @@ export function resolveApplicationLocalStorageKey(logicalKey='',options={}){
112
115
  }
113
116
 
114
117
  async function nativeApplicationId(arcane=globalThis.Arcane){
115
- if(typeof arcane?.app?.current!=='function'){
118
+ if(!is.function(arcane?.app?.current)){
116
119
  return null;
117
120
  }
118
121
 
119
122
  const descriptor=await arcane.app.current();
120
123
 
121
- if(!descriptor||typeof descriptor!=='object'){
124
+ if(!descriptor||!is.object(descriptor)){
122
125
  throw scopeError(
123
126
  'APP_DATA_SCOPE_INVALID',
124
127
  'Arcane returned an invalid bound application descriptor.'
@@ -200,7 +203,7 @@ export async function openApplicationDataDirectory({
200
203
  arcane=globalThis.Arcane,
201
204
  create=true
202
205
  }={}){
203
- if(!storage||typeof storage.getDirectory!=='function'){
206
+ if(!storage||!is.function(storage.getDirectory)){
204
207
  throw scopeError(
205
208
  'APP_DATA_STORAGE_UNAVAILABLE',
206
209
  'Origin Private File System storage is unavailable in this browser.'
@@ -214,7 +217,7 @@ export async function openApplicationDataDirectory({
214
217
  });
215
218
  const root=await storage.getDirectory();
216
219
 
217
- if(!root||typeof root.getDirectoryHandle!=='function'){
220
+ if(!root||!is.function(root.getDirectoryHandle)){
218
221
  throw scopeError(
219
222
  'APP_DATA_STORAGE_UNAVAILABLE',
220
223
  'Arcane could not open the Origin Private File System root.'
@@ -1,3 +1,6 @@
1
+ import Is from 'strong-type';
2
+ const is=new Is(false);
3
+
1
4
  import CommunicationMessage from '../entities/CommunicationMessage.js';
2
5
  import CommunicationThread from '../entities/CommunicationThread.js';
3
6
 
@@ -8,7 +11,7 @@ export default class ArcaneCommunicationBridge{
8
11
  this.id=String(id||'arcane-bridge');this.label=String(label||'Arcane communications bridge');this.channels=Array.from(channels||['other']);this.endpoint=cleanEndpoint(endpoint);this.fetchImpl=fetchImpl;
9
12
  }
10
13
  async request(path,options={}){
11
- if(typeof this.fetchImpl!=='function') throw new Error('Network access is unavailable.');
14
+ if(!is.function(this.fetchImpl)) throw new Error('Network access is unavailable.');
12
15
  const response=await this.fetchImpl(`${this.endpoint}${path}`,{...options,headers:{Accept:'application/json',...(options.body?{'Content-Type':'application/json'}:{}),...options.headers}});
13
16
  const text=await response.text();let body={};if(text){try{body=JSON.parse(text);}catch{throw new Error('The communications bridge returned invalid JSON.');}}
14
17
  if(!response.ok) throw new Error(body.error||`Communications bridge request failed (${response.status}).`);
@@ -1,11 +1,14 @@
1
+ import Is from 'strong-type';
2
+ const is=new Is(false);
3
+
1
4
  import * as ArcaneNetworkPolicy from './ArcaneNetworkPolicy.js?v=3';
2
5
 
3
6
  function completeText(value,fallback){
4
- return typeof value==='string'&&value.length>0?value:fallback;
7
+ return is.string(value)&&value.length>0?value:fallback;
5
8
  }
6
9
 
7
10
  function decisionId(){
8
- if(typeof globalThis.crypto?.randomUUID==='function')return globalThis.crypto.randomUUID();
11
+ if(is.function(globalThis.crypto?.randomUUID))return globalThis.crypto.randomUUID();
9
12
  return `navigation-${Date.now().toString(36)}-${Math.random().toString(36).replace(/^0\./u,'')}`;
10
13
  }
11
14
 
@@ -83,7 +86,7 @@ function unavailableDecision(target,error){
83
86
  }
84
87
 
85
88
  function requireNetworkMatcher(networkMatcher){
86
- if(typeof networkMatcher==='function')return networkMatcher;
89
+ if(is.function(networkMatcher))return networkMatcher;
87
90
  const error=new Error('Arcane cannot evaluate literal-IP navigation because the canonical network matcher is unavailable.');
88
91
  error.code='ARCANE_NETWORK_POLICY_MATCHER_UNAVAILABLE';
89
92
  throw error;
@@ -95,7 +98,7 @@ export function createArcaneNavigationGuard({
95
98
  onDecision=null,
96
99
  networkMatcher=ArcaneNetworkPolicy.findDeniedNetworkRule
97
100
  }={}){
98
- if(typeof secure!=='boolean')throw new TypeError('secure must be a boolean.');
101
+ if(!is.boolean(secure))throw new TypeError('secure must be a boolean.');
99
102
  return async function guardArcaneNavigation(value,context={}){
100
103
  const target=destinationContext(value);
101
104
  let decision;
@@ -140,7 +143,7 @@ export function createArcaneNavigationGuard({
140
143
  }catch(error){
141
144
  decision=unavailableDecision(target,error);
142
145
  }
143
- if(typeof onDecision==='function'){
146
+ if(is.function(onDecision)){
144
147
  await onDecision(decision,{intent:completeText(context.intent,'embedded')});
145
148
  }
146
149
  return decision;
@@ -1,3 +1,6 @@
1
+ import Is from 'strong-type';
2
+ const is=new Is(false);
3
+
1
4
  export const ARCANE_NETWORK_POLICY_SCHEMA_VERSION=1;
2
5
  export const ARCANE_NETWORK_POLICY_URL=new URL('../security/arcane-network-policy.json',import.meta.url);
3
6
 
@@ -24,11 +27,11 @@ let defaultPolicyPromise=null;
24
27
  const DEFAULT_POLICY_LOAD_TIMEOUT_MS=5000;
25
28
 
26
29
  function fail(message){const error=new TypeError(`ARCANE_NETWORK_POLICY_INVALID: ${message}`);error.code='ARCANE_NETWORK_POLICY_INVALID';throw error;}
27
- function isRecord(value){return Boolean(value)&&typeof value==='object'&&!Array.isArray(value);}
30
+ function isRecord(value){return Boolean(value)&&is.object(value)&&!is.array(value);}
28
31
  function assertOnlyKeys(value,allowed,label){for(const key of Object.keys(value)){if(!allowed.has(key))fail(`${label} contains unsupported field ${key}.`);}}
29
32
  function boundedText(value,label,maximum,{nullable=false}={}){
30
33
  if(nullable&&value===null)return null;
31
- if(typeof value!=='string'||value!==value.trim()||value.length<1||value.length>maximum||/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/.test(value))fail(`${label} must be bounded plain text.`);
34
+ if(!is.string(value)||value!==value.trim()||value.length<1||value.length>maximum||/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/.test(value))fail(`${label} must be bounded plain text.`);
32
35
  return value;
33
36
  }
34
37
  function identifier(value,label){const text=boundedText(value,label,80);if(!/^[a-z0-9]+(?:[._-][a-z0-9]+)*$/.test(text))fail(`${label} must be a lowercase identifier.`);return text;}
@@ -43,7 +46,7 @@ function normalizeSource(value,label){
43
46
  function isIpLiteral(value){return /^\d+(?:\.\d+){3}$/.test(value)||value.includes(':');}
44
47
 
45
48
  export function canonicalNetworkHostname(value){
46
- if(typeof value!=='string')return null;
49
+ if(!is.string(value))return null;
47
50
  const raw=value.trim(),candidate=raw.endsWith('.')?raw.slice(0,-1):raw;
48
51
  if(!candidate||candidate.endsWith('.')||candidate.includes('/')||candidate.includes('@')||candidate.includes(':'))return null;
49
52
  try{
@@ -66,7 +69,7 @@ function normalizeDomainRule(value,index,ids){
66
69
  function parseIpv4(value){
67
70
  const parts=value.split('.');if(parts.length!==4)return null;
68
71
  const octets=parts.map(part=>/^(?:0|[1-9]\d{0,2})$/.test(part)?Number(part):NaN);
69
- if(octets.some(part=>!Number.isInteger(part)||part<0||part>255))return null;
72
+ if(octets.some(part=>!is.integer(part)||part<0||part>255))return null;
70
73
  return {numeric:octets.reduce((total,part)=>((total<<8)>>>0)+part,0)>>>0,text:octets.join('.')};
71
74
  }
72
75
  function parseIpv6(value){
@@ -92,7 +95,7 @@ function formatIpv6(numeric){
92
95
  return new URL(`http://[${groups.join(':')}]/`).hostname.slice(1,-1);
93
96
  }
94
97
  function normalizeCidr(value,label){
95
- if(typeof value!=='string'||value!==value.trim()||value.split('/').length!==2)fail(`${label} must be a canonical IPv4 or IPv6 CIDR.`);
98
+ if(!is.string(value)||value!==value.trim()||value.split('/').length!==2)fail(`${label} must be a canonical IPv4 or IPv6 CIDR.`);
96
99
  const [address,prefixText]=value.split('/');if(!/^(?:0|[1-9]\d{0,2})$/.test(prefixText))fail(`${label} has an invalid prefix.`);
97
100
  const prefix=Number(prefixText),ipv4=parseIpv4(address);
98
101
  if(ipv4){if(prefix>32)fail(`${label} has an invalid IPv4 prefix.`);const mask=prefix===0?0:(0xffffffff<<(32-prefix))>>>0;const network=(ipv4.numeric&mask)>>>0;const normalized=`${[(network>>>24)&255,(network>>>16)&255,(network>>>8)&255,network&255].join('.')}/${prefix}`;if(normalized!==value)fail(`${label} must use its canonical network address.`);return normalized;}
@@ -102,11 +105,11 @@ function normalizeCidr(value,label){
102
105
  }
103
106
  function normalizePortRanges(value,label){
104
107
  if(value===undefined)return Object.freeze([]);
105
- if(!Array.isArray(value)||value.length>64)fail(`${label} must be a bounded array.`);
108
+ if(!is.array(value)||value.length>64)fail(`${label} must be a bounded array.`);
106
109
  let previous=0;
107
110
  return Object.freeze(value.map((range,index)=>{
108
111
  const itemLabel=`${label}[${index}]`;if(!isRecord(range))fail(`${itemLabel} must be an object.`);assertOnlyKeys(range,PORT_KEYS,itemLabel);
109
- const from=range.from,to=range.to;if(!Number.isInteger(from)||!Number.isInteger(to)||from<1||to>65535||from>to||from<=previous)fail(`${itemLabel} must be a sorted, non-overlapping port range.`);previous=to;
112
+ const from=range.from,to=range.to;if(!is.integer(from)||!is.integer(to)||from<1||to>65535||from>to||from<=previous)fail(`${itemLabel} must be a sorted, non-overlapping port range.`);previous=to;
110
113
  return Object.freeze({from,to});
111
114
  }));
112
115
  }
@@ -160,8 +163,8 @@ indexNormalizedPolicy(EMPTY_POLICY);
160
163
  export function validateArcaneNetworkPolicy(value){
161
164
  if(!isRecord(value))fail('policy must be an object.');assertOnlyKeys(value,POLICY_KEYS,'policy');
162
165
  if(value.schemaVersion!==ARCANE_NETWORK_POLICY_SCHEMA_VERSION)fail('schemaVersion is unsupported.');
163
- if(!Number.isSafeInteger(value.generation)||value.generation<1)fail('generation must be a positive safe integer.');
164
- if(!Array.isArray(value.domainRules)||!Array.isArray(value.networkRules)||value.domainRules.length>50_000||value.networkRules.length>50_000)fail('rule collections must be bounded arrays.');
166
+ if(!is.safeInteger(value.generation)||value.generation<1)fail('generation must be a positive safe integer.');
167
+ if(!is.array(value.domainRules)||!is.array(value.networkRules)||value.domainRules.length>50_000||value.networkRules.length>50_000)fail('rule collections must be bounded arrays.');
165
168
  const ids=new Set(),domainRules=value.domainRules.map((rule,index)=>normalizeDomainRule(rule,index,ids)),networkRules=value.networkRules.map((rule,index)=>normalizeNetworkRule(rule,index,ids));
166
169
  const normalized=Object.freeze({schemaVersion:value.schemaVersion,generation:value.generation,domainRules:Object.freeze(domainRules),networkRules:Object.freeze(networkRules)});
167
170
  NORMALIZED_POLICIES.add(normalized);
@@ -170,7 +173,7 @@ export function validateArcaneNetworkPolicy(value){
170
173
  }
171
174
 
172
175
  function hostnameFromInput(value){
173
- if(typeof value!=='string')return null;
176
+ if(!is.string(value))return null;
174
177
  try{if(/^[a-z][a-z0-9+.-]*:\/\//i.test(value))return canonicalNetworkHostname(new URL(value).hostname);}
175
178
  catch{return null;}
176
179
  return canonicalNetworkHostname(value);
@@ -192,12 +195,12 @@ function normalizeNetworkMatchContext(value){
192
195
  for(const key of Object.keys(value))if(!NETWORK_MATCH_CONTEXT_KEYS.has(key))networkQueryFail(`context contains unsupported field ${key}.`);
193
196
  const protocol=value.protocol;
194
197
  if(protocol!==undefined&&!NETWORK_MATCH_PROTOCOLS.has(protocol))networkQueryFail('context.protocol must be tcp, udp, icmp, or icmpv6.');
195
- for(const key of ['localPort','remotePort'])if(value[key]!==undefined&&(!Number.isInteger(value[key])||value[key]<1||value[key]>65535))networkQueryFail(`context.${key} must be an integer from 1 through 65535.`);
198
+ for(const key of ['localPort','remotePort'])if(value[key]!==undefined&&(!is.integer(value[key])||value[key]<1||value[key]>65535))networkQueryFail(`context.${key} must be an integer from 1 through 65535.`);
196
199
  if((value.localPort!==undefined||value.remotePort!==undefined)&&protocol!==undefined&&protocol!=='tcp'&&protocol!=='udp')networkQueryFail('port match context requires protocol tcp or udp when protocol is supplied.');
197
200
  return {protocol,localPort:value.localPort,remotePort:value.remotePort};
198
201
  }
199
202
  function parsedIpLiteral(value){
200
- if(typeof value!=='string')return null;
203
+ if(!is.string(value))return null;
201
204
  const trimmed=value.trim(),candidate=trimmed.startsWith('[')&&trimmed.endsWith(']')?trimmed.slice(1,-1):trimmed,ipv4=parseIpv4(candidate);if(ipv4)return {family:4,numeric:ipv4.numeric};
202
205
  const ipv6=parseIpv6(candidate.toLowerCase());return ipv6?{family:6,numeric:ipv6.numeric}:null;
203
206
  }
@@ -238,10 +241,10 @@ export function invalidateArcaneNetworkPolicyCache(){defaultPolicyPromise=null;}
238
241
 
239
242
  export async function loadArcaneNetworkPolicy({url=ARCANE_NETWORK_POLICY_URL,fetchImpl=globalThis.fetch,refresh=false,timeoutMs=DEFAULT_POLICY_LOAD_TIMEOUT_MS}={}){
240
243
  const isDefault=String(url)===String(ARCANE_NETWORK_POLICY_URL)&&fetchImpl===globalThis.fetch;
241
- if(typeof refresh!=='boolean')networkQueryFail('refresh must be a boolean.');
242
- if(!Number.isSafeInteger(timeoutMs)||timeoutMs<1||timeoutMs>60000)networkQueryFail('timeoutMs must be an integer from 1 through 60000.');
244
+ if(!is.boolean(refresh))networkQueryFail('refresh must be a boolean.');
245
+ if(!is.safeInteger(timeoutMs)||timeoutMs<1||timeoutMs>60000)networkQueryFail('timeoutMs must be an integer from 1 through 60000.');
243
246
  if(isDefault&&!refresh&&defaultPolicyPromise)return defaultPolicyPromise;
244
- if(typeof fetchImpl!=='function')fail('a fetch implementation is required to load policy.');
247
+ if(!is.function(fetchImpl))fail('a fetch implementation is required to load policy.');
245
248
  const load=(async()=>{
246
249
  const controller=new AbortController();let timeoutId;
247
250
  const operation=(async()=>{const response=await fetchImpl(url,{cache:'no-store',credentials:'same-origin',signal:controller.signal});if(!response?.ok)throw new Error(`ARCANE_NETWORK_POLICY_LOAD_FAILED: ${response?.status||'unavailable'}`);return validateArcaneNetworkPolicy(await response.json());})();
@@ -1,3 +1,6 @@
1
+ import Is from 'strong-type';
2
+ const is=new Is(false);
3
+
1
4
  const DEFAULT_TIMEOUT_MS=10000;
2
5
  const MAX_TIMEOUT_MS=300000;
3
6
 
@@ -11,17 +14,17 @@ function invalid(message,ErrorType=TypeError){
11
14
  }
12
15
 
13
16
  function isPlainRecord(value){
14
- if(!value||typeof value!=='object'||Array.isArray(value)) return false;
17
+ if(!value||!is.object(value)||is.array(value)) return false;
15
18
  const prototype=Object.getPrototypeOf(value);
16
19
  return prototype===Object.prototype||prototype===null;
17
20
  }
18
21
 
19
22
  function isAbortSignal(value){
20
23
  return Boolean(value)
21
- &&typeof value==='object'
22
- &&typeof value.aborted==='boolean'
23
- &&typeof value.addEventListener==='function'
24
- &&typeof value.removeEventListener==='function';
24
+ &&is.object(value)
25
+ &&is.boolean(value.aborted)
26
+ &&is.function(value.addEventListener)
27
+ &&is.function(value.removeEventListener);
25
28
  }
26
29
 
27
30
  function normalizeOptions(value){
@@ -30,7 +33,7 @@ function normalizeOptions(value){
30
33
  if(unknown) throw invalid(`Async boundary options contain an unsupported field: ${unknown}.`);
31
34
 
32
35
  const timeoutMs=value.timeoutMs??DEFAULT_TIMEOUT_MS;
33
- if(!Number.isSafeInteger(timeoutMs)||timeoutMs<1||timeoutMs>MAX_TIMEOUT_MS){
36
+ if(!is.safeInteger(timeoutMs)||timeoutMs<1||timeoutMs>MAX_TIMEOUT_MS){
34
37
  throw invalid(`timeoutMs must be an integer from 1 through ${MAX_TIMEOUT_MS}.`,RangeError);
35
38
  }
36
39
 
@@ -43,13 +46,13 @@ function normalizeOptions(value){
43
46
  }
44
47
 
45
48
  function normalizeOperation(value){
46
- if(typeof value==='function'){
49
+ if(is.function(value)){
47
50
  return Object.freeze({kind:'function',value});
48
51
  }
49
- if(!value||(typeof value!=='object'&&typeof value!=='function')){
52
+ if(!value||(!is.object(value)&&!is.function(value))){
50
53
  throw coded(new TypeError('The asynchronous operation must be a promise or function.'),'ASYNC_BOUNDARY_INVALID_OPERATION');
51
54
  }
52
- if(typeof value.then!=='function'){
55
+ if(!is.function(value.then)){
53
56
  throw coded(new TypeError('The asynchronous operation must be a promise or function.'),'ASYNC_BOUNDARY_INVALID_OPERATION');
54
57
  }
55
58
  return Object.freeze({kind:'promise',value});
@@ -101,7 +104,7 @@ export function runAsyncBoundary(operation,options={}){
101
104
  try{
102
105
  settings=normalizeOptions(options);
103
106
  descriptor=normalizeOperation(operation);
104
- if(typeof globalThis.AbortController!=='function'){
107
+ if(!is.function(globalThis.AbortController)){
105
108
  throw coded(new Error('AbortController is unavailable in this environment.'),'ASYNC_BOUNDARY_UNAVAILABLE');
106
109
  }
107
110
  }catch(error){
@@ -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
 
3
6
  const RESULT_STATUSES=new Set(['fail','pass','skip']);
@@ -41,8 +44,8 @@ export const BROWSER_TEST_SUITE_REASONS={
41
44
 
42
45
  function isPlainRecord(value){
43
46
  return Boolean(value)
44
- &&typeof value==='object'
45
- &&!Array.isArray(value)
47
+ &&is.object(value)
48
+ &&!is.array(value)
46
49
  &&Object.getPrototypeOf(value)===Object.prototype;
47
50
  }
48
51
 
@@ -56,7 +59,7 @@ function fail(message,code,ErrorType=TypeError){
56
59
  }
57
60
 
58
61
  function descriptorText(value,label){
59
- if(typeof value!=='string') fail(`${label} must be a string.`,'BROWSER_TEST_INVALID_DESCRIPTOR');
62
+ if(!is.string(value)) fail(`${label} must be a string.`,'BROWSER_TEST_INVALID_DESCRIPTOR');
60
63
  if(!value.trim()) fail(`${label} cannot be empty.`,'BROWSER_TEST_INVALID_DESCRIPTOR');
61
64
  return value;
62
65
  }
@@ -68,14 +71,14 @@ function resultMessage(value,fallback){
68
71
  }
69
72
 
70
73
  function normalizeTests(value){
71
- if(!Array.isArray(value)) fail('tests must be an array.','BROWSER_TEST_INVALID_OPTIONS');
74
+ if(!is.array(value)) fail('tests must be an array.','BROWSER_TEST_INVALID_OPTIONS');
72
75
  const seen=new Set();
73
76
  return value.map((item,index)=>{
74
77
  if(!isPlainRecord(item)) fail(`Test descriptor ${index+1} must be a plain object.`,'BROWSER_TEST_INVALID_DESCRIPTOR');
75
78
  const id=descriptorText(item.id,`Test descriptor ${index+1} id`);
76
79
  if(seen.has(id)) fail(`Test descriptors contain a duplicate id: ${id}.`,'BROWSER_TEST_CASE_COLLISION');
77
80
  seen.add(id);
78
- if(typeof item.run!=='function') fail(`Test descriptor ${index+1} run must be a function.`,'BROWSER_TEST_INVALID_DESCRIPTOR');
81
+ if(!is.function(item.run)) fail(`Test descriptor ${index+1} run must be a function.`,'BROWSER_TEST_INVALID_DESCRIPTOR');
79
82
  return {
80
83
  ...item,
81
84
  id,
@@ -92,7 +95,7 @@ function defaultNow(){
92
95
  function normalizeOptions(input){
93
96
  if(!isPlainRecord(input)) fail('Browser test suite options must be a plain object.','BROWSER_TEST_INVALID_OPTIONS');
94
97
  const now=input.now??defaultNow;
95
- if(typeof now!=='function') fail('now must be a function.','BROWSER_TEST_INVALID_OPTIONS');
98
+ if(!is.function(now)) fail('now must be a function.','BROWSER_TEST_INVALID_OPTIONS');
96
99
  return {
97
100
  now,
98
101
  tests:normalizeTests(input.tests??[]),
@@ -103,23 +106,23 @@ function normalizeRunOptions(input){
103
106
  if(!isPlainRecord(input)) fail('Test run options must be a plain object.','BROWSER_TEST_INVALID_OPTIONS');
104
107
  const signal=input.signal??null;
105
108
  if(signal!==null&&(
106
- typeof signal!=='object'
107
- ||typeof signal.aborted!=='boolean'
108
- ||typeof signal.addEventListener!=='function'
109
- ||typeof signal.removeEventListener!=='function'
109
+ !is.object(signal)
110
+ ||!is.boolean(signal.aborted)
111
+ ||!is.function(signal.addEventListener)
112
+ ||!is.function(signal.removeEventListener)
110
113
  )) fail('signal must be an AbortSignal.','BROWSER_TEST_INVALID_OPTIONS');
111
114
  return {context:input.context,signal};
112
115
  }
113
116
 
114
117
  function elapsed(now,start){
115
118
  const end=Number(now());
116
- if(!Number.isFinite(end)) fail('now() must return a finite number.','BROWSER_TEST_INVALID_CLOCK');
119
+ if(!is.finite(end)) fail('now() must return a finite number.','BROWSER_TEST_INVALID_CLOCK');
117
120
  return Math.max(0,end-start);
118
121
  }
119
122
 
120
123
  function startTime(now){
121
124
  const value=Number(now());
122
- if(!Number.isFinite(value)) fail('now() must return a finite number.','BROWSER_TEST_INVALID_CLOCK');
125
+ if(!is.finite(value)) fail('now() must return a finite number.','BROWSER_TEST_INVALID_CLOCK');
123
126
  return value;
124
127
  }
125
128
 
@@ -195,7 +198,7 @@ function outcomeFromError(error){
195
198
  return {
196
199
  status:'fail',
197
200
  message:resultMessage(error?.message,'The check failed.'),
198
- code:typeof error?.code==='string'?error.code:'BROWSER_TEST_ERROR',
201
+ code:is.string(error?.code)?error.code:'BROWSER_TEST_ERROR',
199
202
  errorName:resultMessage(error?.name,'Error'),
200
203
  error
201
204
  };
@@ -238,9 +241,9 @@ function browserTestResultReason(result){
238
241
  function browserTestPublicDetail(type,detail){
239
242
  const result=detail?.result;
240
243
  const test=detail?.test;
241
- const testCount=Number.isSafeInteger(detail?.total)
244
+ const testCount=is.safeInteger(detail?.total)
242
245
  ?detail.total
243
- :Number.isSafeInteger(detail?.totals?.total)
246
+ :is.safeInteger(detail?.totals?.total)
244
247
  ?detail.totals.total
245
248
  :null;
246
249
  if(type===BROWSER_TEST_SUITE_EVENT_TYPES.runStarted){
@@ -267,7 +270,7 @@ function browserTestPublicDetail(type,detail){
267
270
  testIndex:detail.index,
268
271
  testCount,
269
272
  status:result.status,
270
- ...(typeof result.code==='string'?{code:result.code}:{})
273
+ ...(is.string(result.code)?{code:result.code}:{})
271
274
  };
272
275
  }
273
276
  const reason=detail.status==='aborted'
@@ -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 Calculation from '../entities/Calculation.js';
3
6
 
@@ -19,7 +22,7 @@ function calculatorErrorCode(error){
19
22
  }
20
23
 
21
24
  function attachCalculatorErrorCode(error,code){
22
- if(!error||(typeof error!=='object'&&typeof error!=='function'))return error;
25
+ if(!error||(!is.object(error)&&!is.function(error)))return error;
23
26
  try{Object.defineProperty(error,'code',{configurable:true,enumerable:false,value:code,writable:true});}
24
27
  catch{try{error.code=code;}catch{}}
25
28
  return error;
@@ -42,7 +45,7 @@ export function evaluateExpression(input){const tokens=tokenize(input);let curso
42
45
  function power(){let value=unary();if(peek().type==='^'){cursor++;value=Math.pow(value,power())}return value}
43
46
  function product(){let value=power();while(['*','/','%'].includes(peek().type)){const operator=tokens[cursor++].type,right=power();if((operator==='/'||operator==='%')&&right===0)throw new RangeError('Division by zero is undefined.');value=operator==='*'?value*right:operator==='/'?value/right:value%right}return value}
44
47
  function expression(){let value=product();while(['+','-'].includes(peek().type)){const operator=tokens[cursor++].type,right=product();value=operator==='+'?value+right:value-right}return value}
45
- const result=expression();if(peek().type!=='end')throw new SyntaxError('Unexpected content after the expression.');if(!Number.isFinite(result))throw new RangeError('The calculation did not produce a finite result.');return result;
48
+ const result=expression();if(peek().type!=='end')throw new SyntaxError('Unexpected content after the expression.');if(!is.finite(result))throw new RangeError('The calculation did not produce a finite result.');return result;
46
49
  }
47
50
 
48
51
  export default class CalculatorEngine{
@@ -1,5 +1,8 @@
1
+ import Is from 'strong-type';
2
+ const is=new Is(false);
3
+
1
4
  function hasUserEntry(chat=[]){
2
- const messages=Array.isArray(chat)
5
+ const messages=is.array(chat)
3
6
  ?chat
4
7
  :chat?.messages||[];
5
8
 
@@ -9,20 +12,20 @@ function hasUserEntry(chat=[]){
9
12
  }
10
13
 
11
14
  function hasConversationEntry(chat=[]){
12
- const messages=Array.isArray(chat)
15
+ const messages=is.array(chat)
13
16
  ?chat
14
17
  :chat?.messages||[];
15
18
 
16
19
  return hasUserEntry(messages)||messages.some(
17
20
  message=>
18
21
  message?.role==='assistant'
19
- &&typeof message.content==='string'
22
+ &&is.string(message.content)
20
23
  &&Boolean(message.content.trim())
21
24
  );
22
25
  }
23
26
 
24
27
  function plainRecord(value){
25
- if(!value||typeof value!=='object'||Array.isArray(value))return false;
28
+ if(!value||!is.object(value)||is.array(value))return false;
26
29
  const prototype=Object.getPrototypeOf(value);
27
30
  return prototype===Object.prototype||prototype===null;
28
31
  }
@@ -33,9 +36,9 @@ function coded(error,code){
33
36
  }
34
37
 
35
38
  function copyCompleteValue(value,seen=new Map()){
36
- if(value===null||typeof value!=='object')return value;
39
+ if(value===null||!is.object(value))return value;
37
40
  if(seen.has(value))return seen.get(value);
38
- if(Array.isArray(value)){
41
+ if(is.array(value)){
39
42
  const result=[];
40
43
  seen.set(value,result);
41
44
  for(const item of value)result.push(copyCompleteValue(item,seen));
@@ -72,7 +75,7 @@ function providerMessageCopy(message){
72
75
 
73
76
  function structuralToolMessage(call,label){
74
77
  const argumentValue=call?.function?.arguments;
75
- if(typeof argumentValue!=='string'){
78
+ if(!is.string(argumentValue)){
76
79
  throw coded(
77
80
  new TypeError(`${label}.function.arguments must be a JSON string.`),
78
81
  'AI_CHAT_INVALID_TOOL_CALL'
@@ -93,7 +96,7 @@ function structuralToolMessage(call,label){
93
96
  'AI_CHAT_INVALID_TOOL_CALL'
94
97
  );
95
98
  }
96
- if(typeof argumentsRecord.message!=='string'||!argumentsRecord.message.trim()){
99
+ if(!is.string(argumentsRecord.message)||!argumentsRecord.message.trim()){
97
100
  throw coded(
98
101
  new TypeError(`${label}.function.arguments.message must contain user-facing text.`),
99
102
  'AI_CHAT_TOOL_MESSAGE_REQUIRED'
@@ -109,14 +112,14 @@ function activeToolProtocolState(messages){
109
112
  const message=messages[index];
110
113
  if(message?.role==='assistant'){
111
114
  if(start>=0&&!pending.size)start=-1;
112
- if(Array.isArray(message.tool_calls)){
115
+ if(is.array(message.tool_calls)){
113
116
  if(!pending.size&&message.tool_calls.length)start=index;
114
117
  for(const call of message.tool_calls){
115
- if(typeof call?.id==='string'&&call.id)pending.add(call.id);
118
+ if(is.string(call?.id)&&call.id)pending.add(call.id);
116
119
  }
117
120
  }
118
121
  }
119
- if(message?.role==='tool'&&typeof message.tool_call_id==='string'){
122
+ if(message?.role==='tool'&&is.string(message.tool_call_id)){
120
123
  pending.delete(message.tool_call_id);
121
124
  }
122
125
  }
@@ -136,7 +139,7 @@ function appendOrdinaryMessages(result,message,index){
136
139
  result.push({role:'assistant',content:String(message.content)});
137
140
  }
138
141
  if(message.tool_calls===undefined)return;
139
- if(!Array.isArray(message.tool_calls)){
142
+ if(!is.array(message.tool_calls)){
140
143
  throw coded(
141
144
  new TypeError(`Chat message ${index+1}.tool_calls must be an array.`),
142
145
  'AI_CHAT_INVALID_TOOL_CALL'
@@ -154,10 +157,10 @@ function appendOrdinaryMessages(result,message,index){
154
157
  return;
155
158
  }
156
159
  if(message.role!=='tool')return;
157
- const content=typeof message.tool_call_id==='string'
160
+ const content=is.string(message.tool_call_id)
158
161
  ?message.persistence_message??message.message
159
162
  :message.content;
160
- if(typeof content==='string'&&content.trim()){
163
+ if(is.string(content)&&content.trim()){
161
164
  result.push({role:'assistant',content});
162
165
  }
163
166
  }
@@ -168,7 +171,7 @@ function appendOrdinaryMessages(result,message,index){
168
171
  * Settled exchanges become complete ordinary visible conversation messages.
169
172
  */
170
173
  function recurringChatMessages(chat=[],{settleCompleteToolTail=false}={}){
171
- const messages=Array.isArray(chat)
174
+ const messages=is.array(chat)
172
175
  ?chat
173
176
  :chat?.messages||[];
174
177
  const active=activeToolProtocolState(messages);
@@ -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 ArcaneCommunicationBridge from './ArcaneCommunicationBridge.js';
3
6
  import CommunicationHub from './CommunicationHub.js?v=2';
@@ -89,13 +92,13 @@ export default class CommunicationAppController{
89
92
  this.services=services;
90
93
  this.channels=channels;
91
94
  this.labels=labels;
92
- this.inspectMessage=typeof inspectMessage==='function'
95
+ this.inspectMessage=is.function(inspectMessage)
93
96
  ?inspectMessage
94
97
  :null;
95
- this.prepareMessageInspection=typeof prepareMessageInspection==='function'
98
+ this.prepareMessageInspection=is.function(prepareMessageInspection)
96
99
  ?prepareMessageInspection
97
100
  :null;
98
- this.onAdvisoryAction=typeof onAdvisoryAction==='function'
101
+ this.onAdvisoryAction=is.function(onAdvisoryAction)
99
102
  ?onAdvisoryAction
100
103
  :null;
101
104
  this.refreshVersion=0;
@@ -302,7 +305,7 @@ export default class CommunicationAppController{
302
305
  );
303
306
  const providers=enabled.map(
304
307
  function createCommunicationProvider(item){
305
- return typeof item.providerFactory==='function'
308
+ return is.function(item.providerFactory)
306
309
  ?item.providerFactory(
307
310
  {
308
311
  service:item,
@@ -355,9 +358,9 @@ export default class CommunicationAppController{
355
358
  return;
356
359
  }
357
360
  try{
358
- if(typeof hub.destroy==='function'){
361
+ if(is.function(hub.destroy)){
359
362
  hub.destroy();
360
- }else if(typeof hub.dispose==='function'){
363
+ }else if(is.function(hub.dispose)){
361
364
  hub.dispose();
362
365
  }
363
366
  }catch(error){
@@ -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 CommunicationMessage from '../entities/CommunicationMessage.js';
3
6
  import CommunicationThread from '../entities/CommunicationThread.js';
@@ -90,17 +93,17 @@ function refreshFailureError(error){
90
93
 
91
94
  function isAbortSignal(value){
92
95
  return Boolean(value)
93
- &&typeof value==='object'
94
- &&typeof value.aborted==='boolean'
95
- &&typeof value.addEventListener==='function'
96
- &&typeof value.removeEventListener==='function';
96
+ &&is.object(value)
97
+ &&is.boolean(value.aborted)
98
+ &&is.function(value.addEventListener)
99
+ &&is.function(value.removeEventListener);
97
100
  }
98
101
 
99
102
  function normalizeRefreshOptions(value){
100
103
  if(value===undefined){
101
104
  return {signal:null};
102
105
  }
103
- if(!value||typeof value!=='object'||Array.isArray(value)){
106
+ if(!value||!is.object(value)||is.array(value)){
104
107
  throw codedError(
105
108
  'Communication refresh options must be a plain record.',
106
109
  communicationHubErrorCodes.refreshOptionsInvalid,
@@ -119,7 +122,7 @@ function normalizeRefreshOptions(value){
119
122
  }
120
123
 
121
124
  function normalizeProviderThreads(provider,values){
122
- if(!Array.isArray(values)){
125
+ if(!is.array(values)){
123
126
  throw new TypeError(`Communication provider ${provider.id} must return a thread array.`);
124
127
  }
125
128
  return values.map(function normalizeProviderThread(value){
@@ -1,3 +1,6 @@
1
+ import Is from 'strong-type';
2
+ const is=new Is(false);
3
+
1
4
  import {resolveApplicationLocalStorageKey} from './AppDataScope.js';
2
5
 
3
6
  function adapter(){
@@ -8,6 +11,6 @@ function adapter(){
8
11
 
9
12
  export default class CommunicationPreferences{
10
13
  constructor(namespace='communications'){this.key=`arcane.communications.${String(namespace)}`;this.adapter=adapter();}
11
- async load(defaults={}){const result=await this.adapter.get(this.key);return result?.found&&result.value&&typeof result.value==='object'?{...defaults,...result.value}:{...defaults};}
14
+ async load(defaults={}){const result=await this.adapter.get(this.key);return result?.found&&result.value&&is.object(result.value)?{...defaults,...result.value}:{...defaults};}
12
15
  async save(values={}){const safe=Object.fromEntries(Object.entries(values).map(([id,value])=>[id,{enabled:Boolean(value?.enabled),endpoint:String(value?.endpoint||''),accountLabel:String(value?.accountLabel||''),status:String(value?.status||'Disconnected')} ]));await this.adapter.set(this.key,safe);return safe;}
13
16
  }