arcane-os 0.2.2 → 0.3.0

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 (117) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +8 -8
  3. package/browser-runtime/ARCANE_SDK_BROWSER_RELEASE.json +29 -22
  4. package/browser-runtime/ai/ARCANE_AI_BROWSER_SPEECH_COMPONENTS.json +203 -0
  5. package/browser-runtime/ai/browser-kokoro-worker.mjs +11 -2
  6. package/browser-runtime/ai/browser-speech-artifacts.mjs +3230 -397
  7. package/browser-runtime/ai/browser-speech-providers.mjs +1141 -157
  8. package/browser-runtime/ai/browser-speech.mjs +2 -0
  9. package/browser-runtime/ai/browser-whisper-worker.mjs +11 -2
  10. package/browser-runtime/ai/model-controller.mjs +285 -95
  11. package/browser-runtime/ai/speech-worker-client.mjs +247 -32
  12. package/browser-runtime/ai/speech-worker-runtime.mjs +2310 -167
  13. package/browser-runtime/event-manager.mjs +1097 -1
  14. package/docs/architecture.md +2 -2
  15. package/docs/event-manager.md +155 -27
  16. package/docs/reference/README.md +27 -27
  17. package/docs/reference/ai/browser-speech-package-authority.json +835 -0
  18. package/docs/reference/ai/browser-speech.md +1162 -246
  19. package/docs/reference/ai/browser-wasm.md +18 -7
  20. package/docs/reference/availability-and-normalization.md +6 -3
  21. package/docs/reference/behavioral-testing.md +29 -6
  22. package/docs/reference/cli.md +117 -9
  23. package/docs/reference/core/arcane-ai-contracts.md +1 -1
  24. package/docs/reference/event-manager.md +577 -32
  25. package/docs/reference/inventory/package-api.json +478 -2
  26. package/docs/reference/inventory/runtime-components.json +108 -44
  27. package/docs/reference/inventory/runtime-modules.json +131 -53
  28. package/docs/reference/mail.md +316 -0
  29. package/docs/reference/protocols.md +157 -43
  30. package/docs/reference/runtime-components.md +258 -83
  31. package/docs/reference/runtime-modules.md +613 -77
  32. package/docs/reference/sdk-api.md +1014 -25
  33. package/package.json +5 -4
  34. package/runtime/ARCANE_RUNTIME_RELEASE.json +145 -140
  35. package/runtime/arcane/components/app-bar.html +34 -13
  36. package/runtime/arcane/components/assistant-panel.html +110 -57
  37. package/runtime/arcane/components/calculator.html +7 -4
  38. package/runtime/arcane/components/chart.html +58 -17
  39. package/runtime/arcane/components/chat.html +606 -136
  40. package/runtime/arcane/components/conversation-view.html +13 -6
  41. package/runtime/arcane/components/dashboard-config.html +96 -59
  42. package/runtime/arcane/components/data-maintenance.html +69 -14
  43. package/runtime/arcane/components/data-view.html +53 -7
  44. package/runtime/arcane/components/directory-picker.html +118 -32
  45. package/runtime/arcane/components/document-inspector.html +47 -10
  46. package/runtime/arcane/components/file-drop.html +81 -35
  47. package/runtime/arcane/components/file-inspector.html +72 -22
  48. package/runtime/arcane/components/file-manager.html +374 -79
  49. package/runtime/arcane/components/integration-settings.html +12 -5
  50. package/runtime/arcane/components/local-ai-status.html +48 -19
  51. package/runtime/arcane/components/markdown-document.html +161 -68
  52. package/runtime/arcane/components/markdown-editor.html +110 -33
  53. package/runtime/arcane/components/media-embed.html +8 -5
  54. package/runtime/arcane/components/modal.html +15 -5
  55. package/runtime/arcane/components/output-panel.html +28 -23
  56. package/runtime/arcane/components/preferences-form.html +22 -4
  57. package/runtime/arcane/components/record-timeline.html +18 -2
  58. package/runtime/arcane/components/relationship-board.html +23 -3
  59. package/runtime/arcane/components/screen-capture.html +10 -4
  60. package/runtime/arcane/components/source-code-viewer.html +76 -9
  61. package/runtime/arcane/components/source-explanation.html +23 -3
  62. package/runtime/arcane/components/speech.html +462 -384
  63. package/runtime/arcane/components/summary-strip.html +22 -11
  64. package/runtime/arcane/components/table.html +39 -21
  65. package/runtime/arcane/components/task-progress.html +79 -21
  66. package/runtime/arcane/components/terminal-workspace.html +7 -4
  67. package/runtime/arcane/components/theme-editor.html +7 -3
  68. package/runtime/arcane/components/unified-inbox.html +9 -4
  69. package/runtime/arcane/components/voice-transcription.html +639 -98
  70. package/runtime/arcane/components/weather-widget.html +5 -3
  71. package/runtime/arcane/components/web-navigator.html +48 -8
  72. package/runtime/arcane/entities/Chat.js +1 -1
  73. package/runtime/arcane/entities/User.js +110 -23
  74. package/runtime/arcane/modules/AI.js +2109 -130
  75. package/runtime/arcane/modules/AIProviderRuntime.js +720 -13
  76. package/runtime/arcane/modules/AIRuntimeState.js +109 -52
  77. package/runtime/arcane/modules/ApiModelDatabase.js +390 -17
  78. package/runtime/arcane/modules/BrowserTestSuite.js +205 -28
  79. package/runtime/arcane/modules/CalculatorEngine.js +63 -3
  80. package/runtime/arcane/modules/CommunicationAppController.js +588 -28
  81. package/runtime/arcane/modules/CommunicationHub.js +590 -10
  82. package/runtime/arcane/modules/ComponentContracts.js +470 -0
  83. package/runtime/arcane/modules/ConversationTimebox.js +152 -33
  84. package/runtime/arcane/modules/DBLS.js +40 -7
  85. package/runtime/arcane/modules/DBOPFS.js +35 -11
  86. package/runtime/arcane/modules/DataMaintenance.js +12 -2
  87. package/runtime/arcane/modules/Errors.js +65 -7
  88. package/runtime/arcane/modules/HTMLImport.js +198 -14
  89. package/runtime/arcane/modules/LocalAIReadinessController.js +208 -29
  90. package/runtime/arcane/modules/Mail.js +738 -115
  91. package/runtime/arcane/modules/MailOutbox.mjs +1395 -0
  92. package/runtime/arcane/modules/MailTransport.mjs +197 -39
  93. package/runtime/arcane/modules/Ollama.js +36 -1
  94. package/runtime/arcane/modules/OpenMeteoWeatherProvider.js +583 -7
  95. package/runtime/arcane/modules/PreferenceStore.js +367 -33
  96. package/runtime/arcane/modules/RecordReviewStore.js +322 -23
  97. package/runtime/arcane/modules/ScreenCapture.js +1397 -15
  98. package/runtime/arcane/modules/SpeechPlayback.js +438 -41
  99. package/runtime/arcane/modules/TerminalClient.js +277 -12
  100. package/runtime/arcane/modules/ThemeBootstrap.js +80 -6
  101. package/runtime/arcane/modules/ThemeManager.js +39 -7
  102. package/runtime/arcane/modules/TimeGuard.js +131 -20
  103. package/runtime/arcane/modules/WaitForComponent.js +386 -33
  104. package/schemas/arcane-lock.schema.json +2 -2
  105. package/src/cli/main.mjs +435 -9
  106. package/src/event-manager.mjs +1097 -1
  107. package/src/import-map.mjs +21 -3
  108. package/src/index.mjs +13 -0
  109. package/src/installed-sdk-runtime.mjs +112 -0
  110. package/src/mail-api.mjs +22 -0
  111. package/src/mail-credentials.mjs +667 -0
  112. package/src/mail-server.mjs +1769 -0
  113. package/src/mail.mjs +261 -0
  114. package/src/sdk-browser-runtime.mjs +85 -41
  115. package/src/testing-loader.mjs +7 -0
  116. package/src/toolchain.mjs +3 -0
  117. package/src/workspace.mjs +1 -1
@@ -1,16 +1,596 @@
1
+ import {createArcaneEventSource} from 'arcane-os/event-manager';
1
2
  import CommunicationMessage from '../entities/CommunicationMessage.js';
2
3
  import CommunicationThread from '../entities/CommunicationThread.js';
3
4
  import CommunicationProviderRegistry from './CommunicationProviderRegistry.js?v=2';
4
5
 
6
+ export const COMMUNICATION_HUB_EVENTS=Object.freeze({
7
+ refreshCancelled:'communications-refresh-cancelled',
8
+ refreshCompleted:'communications-refresh-completed',
9
+ refreshFailed:'communications-refresh-failed',
10
+ refreshLegacy:'communications-refresh',
11
+ refreshPartiallyCompleted:'communications-refresh-partially-completed',
12
+ refreshStarted:'communications-refresh-started'
13
+ });
14
+
15
+ export const COMMUNICATION_HUB_REFRESH_STATES=Object.freeze({
16
+ cancelled:'refresh-cancelled',
17
+ completed:'refresh-completed',
18
+ failed:'refresh-failed',
19
+ partiallyCompleted:'refresh-partially-completed',
20
+ started:'refresh-started'
21
+ });
22
+
23
+ export const COMMUNICATION_HUB_REFRESH_REASONS=Object.freeze({
24
+ allProviderThreadListsRejected:'all-provider-thread-lists-rejected',
25
+ boundaryThrew:'refresh-boundary-threw',
26
+ hubDisposed:'hub-disposed',
27
+ providerThreadListRejected:'provider-thread-list-rejected',
28
+ signalAborted:'refresh-signal-aborted',
29
+ startCancelled:'refresh-start-cancelled',
30
+ superseded:'refresh-superseded'
31
+ });
32
+
33
+ export const COMMUNICATION_HUB_ERROR_CODES=Object.freeze({
34
+ allProviderThreadListsRejected:'ARCANE_COMMUNICATION_PROVIDER_THREAD_LISTS_REJECTED',
35
+ disposed:'ARCANE_COMMUNICATION_HUB_DISPOSED',
36
+ providerThreadListRejected:'ARCANE_COMMUNICATION_PROVIDER_THREAD_LIST_REJECTED',
37
+ refreshAborted:'ARCANE_COMMUNICATION_HUB_REFRESH_ABORTED',
38
+ refreshFailed:'ARCANE_COMMUNICATION_HUB_REFRESH_FAILED',
39
+ refreshOptionsInvalid:'ARCANE_COMMUNICATION_HUB_REFRESH_OPTIONS_INVALID',
40
+ refreshSuperseded:'ARCANE_COMMUNICATION_HUB_REFRESH_SUPERSEDED'
41
+ });
42
+
43
+ function codedError(message,code,{cause,name='Error',ErrorType=Error}={}){
44
+ const error=new ErrorType(message);
45
+ error.name=name;
46
+ error.code=code;
47
+ if(cause!==undefined){
48
+ error.cause=cause;
49
+ }
50
+ return error;
51
+ }
52
+
53
+ function disposedError(){
54
+ return codedError(
55
+ 'The communication hub has been disposed.',
56
+ COMMUNICATION_HUB_ERROR_CODES.disposed
57
+ );
58
+ }
59
+
60
+ function refreshAbortError(reason,code=COMMUNICATION_HUB_ERROR_CODES.refreshAborted){
61
+ if(reason instanceof Error&&reason.name==='AbortError'&&reason.code===code){
62
+ return reason;
63
+ }
64
+ return codedError(
65
+ code===COMMUNICATION_HUB_ERROR_CODES.refreshSuperseded
66
+ ?'The communication refresh was superseded by a newer refresh.'
67
+ :'The communication refresh was aborted.',
68
+ code,
69
+ {
70
+ cause:reason,
71
+ name:'AbortError'
72
+ }
73
+ );
74
+ }
75
+
76
+ function refreshFailureError(error){
77
+ if(error instanceof Error&&error.code===COMMUNICATION_HUB_ERROR_CODES.refreshFailed){
78
+ return error;
79
+ }
80
+ return codedError(
81
+ 'The communication refresh boundary failed.',
82
+ COMMUNICATION_HUB_ERROR_CODES.refreshFailed,
83
+ {cause:error}
84
+ );
85
+ }
86
+
87
+ function isAbortSignal(value){
88
+ return Boolean(value)
89
+ &&typeof value==='object'
90
+ &&typeof value.aborted==='boolean'
91
+ &&typeof value.addEventListener==='function'
92
+ &&typeof value.removeEventListener==='function';
93
+ }
94
+
95
+ function normalizeRefreshOptions(value){
96
+ if(value===undefined){
97
+ return Object.freeze({signal:null});
98
+ }
99
+ if(!value||typeof value!=='object'||Array.isArray(value)){
100
+ throw codedError(
101
+ 'Communication refresh options must be a plain record.',
102
+ COMMUNICATION_HUB_ERROR_CODES.refreshOptionsInvalid,
103
+ {ErrorType:TypeError}
104
+ );
105
+ }
106
+ const descriptors=Object.getOwnPropertyDescriptors(value);
107
+ const unknown=Reflect.ownKeys(descriptors).find(
108
+ function findUnknownRefreshOption(key){
109
+ return key!=='signal';
110
+ }
111
+ );
112
+ if(unknown!==undefined||Object.values(descriptors).some(
113
+ function findRefreshOptionAccessor(descriptor){
114
+ return !Object.hasOwn(descriptor,'value');
115
+ }
116
+ )){
117
+ throw codedError(
118
+ 'Communication refresh options may contain only a data signal property.',
119
+ COMMUNICATION_HUB_ERROR_CODES.refreshOptionsInvalid,
120
+ {ErrorType:TypeError}
121
+ );
122
+ }
123
+ const signal=descriptors.signal?.value??null;
124
+ if(signal!==null&&!isAbortSignal(signal)){
125
+ throw codedError(
126
+ 'Communication refresh signal must be an AbortSignal.',
127
+ COMMUNICATION_HUB_ERROR_CODES.refreshOptionsInvalid,
128
+ {ErrorType:TypeError}
129
+ );
130
+ }
131
+ return Object.freeze({signal});
132
+ }
133
+
134
+ function normalizeProviderThreads(provider,values){
135
+ if(!Array.isArray(values)){
136
+ throw new TypeError(`Communication provider ${provider.id} must return a thread array.`);
137
+ }
138
+ return values.map(function normalizeProviderThread(value){
139
+ return value instanceof CommunicationThread
140
+ ?value
141
+ :new CommunicationThread(
142
+ {
143
+ ...value,
144
+ providerId:provider.id
145
+ }
146
+ );
147
+ });
148
+ }
149
+
150
+ function compareThreadsByUpdatedAt(left,right){
151
+ return right.updatedAt.localeCompare(left.updatedAt);
152
+ }
153
+
154
+ function providerFailure(provider){
155
+ return Object.freeze({
156
+ code:COMMUNICATION_HUB_ERROR_CODES.providerThreadListRejected,
157
+ providerId:provider.id,
158
+ reason:COMMUNICATION_HUB_REFRESH_REASONS.providerThreadListRejected
159
+ });
160
+ }
161
+
162
+ function settleWithAbort(operation,promise){
163
+ const signal=operation.controller.signal;
164
+ if(signal.aborted){
165
+ return Promise.reject(signal.reason);
166
+ }
167
+ return new Promise(function settleCommunicationRefresh(resolve,reject){
168
+ let settled=false;
169
+
170
+ function cleanup(){
171
+ signal.removeEventListener('abort',abortRefreshSettlement);
172
+ }
173
+
174
+ function resolveRefreshSettlement(value){
175
+ if(settled){
176
+ return;
177
+ }
178
+ settled=true;
179
+ cleanup();
180
+ resolve(value);
181
+ }
182
+
183
+ function rejectRefreshSettlement(error){
184
+ if(settled){
185
+ return;
186
+ }
187
+ settled=true;
188
+ cleanup();
189
+ reject(error);
190
+ }
191
+
192
+ function abortRefreshSettlement(){
193
+ rejectRefreshSettlement(signal.reason);
194
+ }
195
+
196
+ signal.addEventListener('abort',abortRefreshSettlement,{once:true});
197
+ promise.then(resolveRefreshSettlement,rejectRefreshSettlement);
198
+ });
199
+ }
200
+
5
201
  export default class CommunicationHub extends EventTarget{
6
- constructor({providers=[],enabledProviderIds=[]}={}){super();this.registry=providers instanceof CommunicationProviderRegistry?providers:new CommunicationProviderRegistry(providers);this.enabled=new Set(enabledProviderIds);this.threads=[];}
7
- setEnabled(ids=[]){this.enabled=new Set(Array.from(ids,String));return this;}
8
- enabledProviders(){return this.registry.list().filter(provider=>this.enabled.has(provider.id));}
9
- async refresh(){
10
- const results=await Promise.allSettled(this.enabledProviders().map(async provider=>(await provider.listThreads()).map(value=>value instanceof CommunicationThread?value:new CommunicationThread({...value,providerId:provider.id}))));
11
- const errors=[];const threads=[];for(const result of results){if(result.status==='fulfilled') threads.push(...result.value);else errors.push(result.reason);}
12
- this.threads=threads.sort((a,b)=>b.updatedAt.localeCompare(a.updatedAt));this.dispatchEvent(new CustomEvent('communications-refresh',{detail:{threads:[...this.threads],errors}}));return {threads:[...this.threads],errors};
13
- }
14
- async messages(thread){const record=thread instanceof CommunicationThread?thread:new CommunicationThread(thread);const values=await this.registry.get(record.providerId).getMessages(record.id);return values.map(value=>value instanceof CommunicationMessage?value:new CommunicationMessage({...value,threadId:record.id,providerId:record.providerId,channel:record.channel})).sort((a,b)=>a.timestamp.localeCompare(b.timestamp));}
15
- async send({providerId,threadId,channel,body,subject='',recipients=[]}={}){if(!String(body||'').trim()) throw new TypeError('A message body is required.');return this.registry.get(providerId).send({threadId,channel,body:String(body),subject:String(subject),recipients:Array.from(recipients||[])});}
202
+ #disposed=false;
203
+ #events;
204
+ #operationSequence=0;
205
+ #refreshOperation=null;
206
+
207
+ constructor({providers=[],enabledProviderIds=[]}={}){
208
+ super();
209
+ this.registry=providers instanceof CommunicationProviderRegistry
210
+ ?providers
211
+ :new CommunicationProviderRegistry(providers);
212
+ this.enabled=new Set(enabledProviderIds);
213
+ this.threads=[];
214
+ this.#events=createArcaneEventSource(
215
+ this,
216
+ {
217
+ source:'communication-hub',
218
+ eventTypes:Object.freeze(Object.values(COMMUNICATION_HUB_EVENTS))
219
+ }
220
+ );
221
+ }
222
+
223
+ addEventListener(type,listener,options){
224
+ return this.#events.addEventListener(type,listener,options);
225
+ }
226
+
227
+ removeEventListener(type,listener,options){
228
+ return this.#events.removeEventListener(type,listener,options);
229
+ }
230
+
231
+ on(type,listener,options){
232
+ return this.#events.on(type,listener,options);
233
+ }
234
+
235
+ dispatchEvent(value){
236
+ return this.#events.dispatchEvent(value);
237
+ }
238
+
239
+ #assertOpen(){
240
+ if(this.#disposed){
241
+ throw disposedError();
242
+ }
243
+ }
244
+
245
+ #operationId(){
246
+ this.#operationSequence+=1;
247
+ return `${this.#events.instanceId}:refresh:${this.#operationSequence.toString(36)}`;
248
+ }
249
+
250
+ #clearRefreshOperation(operation){
251
+ operation.signal?.removeEventListener('abort',operation.abortFromSignal);
252
+ if(this.#refreshOperation===operation){
253
+ this.#refreshOperation=null;
254
+ }
255
+ }
256
+
257
+ #publishCancellation(operation,error,reason){
258
+ if(operation.terminal){
259
+ return false;
260
+ }
261
+ operation.terminal=true;
262
+ operation.error=error;
263
+ this.#clearRefreshOperation(operation);
264
+ operation.controller.abort(error);
265
+ if(!this.#events.disposed){
266
+ this.#events.dispatch(
267
+ COMMUNICATION_HUB_EVENTS.refreshCancelled,
268
+ Object.freeze({error,reason}),
269
+ {
270
+ operationId:operation.operationId,
271
+ publicDetail:Object.freeze({
272
+ code:error.code,
273
+ reason,
274
+ state:COMMUNICATION_HUB_REFRESH_STATES.cancelled
275
+ })
276
+ }
277
+ );
278
+ }
279
+ return true;
280
+ }
281
+
282
+ #cancelActiveRefresh(error,reason){
283
+ const operation=this.#refreshOperation;
284
+ if(!operation){
285
+ return false;
286
+ }
287
+ return this.#publishCancellation(operation,error,reason);
288
+ }
289
+
290
+ setEnabled(ids=[]){
291
+ this.#assertOpen();
292
+ this.enabled=new Set(Array.from(ids,String));
293
+ return this;
294
+ }
295
+
296
+ enabledProviders(){
297
+ this.#assertOpen();
298
+ return this.registry.list().filter(
299
+ function selectEnabledCommunicationProvider(provider){
300
+ return this.enabled.has(provider.id);
301
+ },
302
+ this
303
+ );
304
+ }
305
+
306
+ async refresh(options={}){
307
+ this.#assertOpen();
308
+ const settings=normalizeRefreshOptions(options);
309
+ if(settings.signal?.aborted){
310
+ throw refreshAbortError(settings.signal.reason);
311
+ }
312
+ this.#cancelActiveRefresh(
313
+ refreshAbortError(
314
+ undefined,
315
+ COMMUNICATION_HUB_ERROR_CODES.refreshSuperseded
316
+ ),
317
+ COMMUNICATION_HUB_REFRESH_REASONS.superseded
318
+ );
319
+
320
+ const providers=this.enabledProviders();
321
+ const controller=new AbortController();
322
+ const operation={
323
+ abortFromSignal:null,
324
+ controller,
325
+ error:null,
326
+ operationId:this.#operationId(),
327
+ signal:settings.signal,
328
+ terminal:false
329
+ };
330
+ operation.abortFromSignal=function abortCommunicationRefreshFromSignal(){
331
+ this.#publishCancellation(
332
+ operation,
333
+ refreshAbortError(settings.signal.reason),
334
+ COMMUNICATION_HUB_REFRESH_REASONS.signalAborted
335
+ );
336
+ }.bind(this);
337
+ settings.signal?.addEventListener(
338
+ 'abort',
339
+ operation.abortFromSignal,
340
+ {once:true}
341
+ );
342
+ this.#refreshOperation=operation;
343
+
344
+ const providerIds=Object.freeze(providers.map(
345
+ function communicationProviderId(provider){
346
+ return provider.id;
347
+ }
348
+ ));
349
+ const startPublication=this.#events.dispatch(
350
+ COMMUNICATION_HUB_EVENTS.refreshStarted,
351
+ Object.freeze({providerIds}),
352
+ {
353
+ cancelable:true,
354
+ operationId:operation.operationId,
355
+ publicDetail:Object.freeze({
356
+ providerCount:providerIds.length,
357
+ providerIds,
358
+ state:COMMUNICATION_HUB_REFRESH_STATES.started
359
+ })
360
+ }
361
+ );
362
+ if(operation.terminal){
363
+ throw operation.error;
364
+ }
365
+ if(!startPublication.accepted){
366
+ const error=refreshAbortError();
367
+ this.#publishCancellation(
368
+ operation,
369
+ error,
370
+ COMMUNICATION_HUB_REFRESH_REASONS.startCancelled
371
+ );
372
+ throw error;
373
+ }
374
+ this.#assertOpen();
375
+ if(this.#refreshOperation!==operation){
376
+ throw operation.error??refreshAbortError(
377
+ undefined,
378
+ COMMUNICATION_HUB_ERROR_CODES.refreshSuperseded
379
+ );
380
+ }
381
+
382
+ const providerOperations=providers.map(
383
+ function startCommunicationProviderRefresh(provider){
384
+ return Promise.resolve().then(
385
+ function requestProviderThreads(){
386
+ if(controller.signal.aborted){
387
+ throw controller.signal.reason;
388
+ }
389
+ return provider.listThreads(
390
+ {
391
+ operationId:operation.operationId,
392
+ signal:controller.signal
393
+ }
394
+ );
395
+ }
396
+ ).then(
397
+ function normalizeCommunicationProviderThreads(values){
398
+ return normalizeProviderThreads(provider,values);
399
+ }
400
+ );
401
+ }
402
+ );
403
+ const providerSettlements=Promise.allSettled(providerOperations);
404
+
405
+ try{
406
+ const results=await settleWithAbort(operation,providerSettlements);
407
+ if(controller.signal.aborted){
408
+ throw controller.signal.reason;
409
+ }
410
+ this.#assertOpen();
411
+ if(this.#refreshOperation!==operation){
412
+ throw operation.error??refreshAbortError(
413
+ undefined,
414
+ COMMUNICATION_HUB_ERROR_CODES.refreshSuperseded
415
+ );
416
+ }
417
+
418
+ const errors=[];
419
+ const failures=[];
420
+ const threads=[];
421
+ for(let index=0;index<results.length;index+=1){
422
+ const result=results[index];
423
+ if(result.status==='fulfilled'){
424
+ threads.push(...result.value);
425
+ }else{
426
+ errors.push(result.reason);
427
+ failures.push(providerFailure(providers[index]));
428
+ }
429
+ }
430
+ threads.sort(compareThreadsByUpdatedAt);
431
+ if(controller.signal.aborted){
432
+ throw controller.signal.reason;
433
+ }
434
+ this.#assertOpen();
435
+ if(this.#refreshOperation!==operation){
436
+ throw operation.error??refreshAbortError(
437
+ undefined,
438
+ COMMUNICATION_HUB_ERROR_CODES.refreshSuperseded
439
+ );
440
+ }
441
+
442
+ this.threads=threads;
443
+ const returnedThreads=[...this.threads];
444
+ const returnedErrors=[...errors];
445
+ const frozenFailures=Object.freeze([...failures]);
446
+ const allProvidersRejected=providers.length>0
447
+ &&failures.length===providers.length;
448
+ const state=allProvidersRejected
449
+ ?COMMUNICATION_HUB_REFRESH_STATES.failed
450
+ :failures.length
451
+ ?COMMUNICATION_HUB_REFRESH_STATES.partiallyCompleted
452
+ :COMMUNICATION_HUB_REFRESH_STATES.completed;
453
+ const terminalType=allProvidersRejected
454
+ ?COMMUNICATION_HUB_EVENTS.refreshFailed
455
+ :failures.length
456
+ ?COMMUNICATION_HUB_EVENTS.refreshPartiallyCompleted
457
+ :COMMUNICATION_HUB_EVENTS.refreshCompleted;
458
+ const compatibilityDetail=Object.freeze({
459
+ errors:Object.freeze([...returnedErrors]),
460
+ failures:frozenFailures,
461
+ state,
462
+ threads:Object.freeze([...returnedThreads])
463
+ });
464
+ const publicDetail=Object.freeze({
465
+ ...(allProvidersRejected
466
+ ?{
467
+ code:COMMUNICATION_HUB_ERROR_CODES.allProviderThreadListsRejected,
468
+ reason:COMMUNICATION_HUB_REFRESH_REASONS.allProviderThreadListsRejected
469
+ }
470
+ :{}),
471
+ failureCount:failures.length,
472
+ failures:frozenFailures,
473
+ providerCount:providers.length,
474
+ state,
475
+ threadCount:threads.length
476
+ });
477
+ operation.terminal=true;
478
+ this.#clearRefreshOperation(operation);
479
+ this.#events.dispatch(
480
+ terminalType,
481
+ compatibilityDetail,
482
+ {
483
+ operationId:operation.operationId,
484
+ publicDetail
485
+ }
486
+ );
487
+ if(!this.#events.disposed){
488
+ this.#events.dispatch(
489
+ COMMUNICATION_HUB_EVENTS.refreshLegacy,
490
+ Object.freeze({
491
+ errors:compatibilityDetail.errors,
492
+ threads:compatibilityDetail.threads
493
+ }),
494
+ {
495
+ operationId:operation.operationId,
496
+ publicDetail
497
+ }
498
+ );
499
+ }
500
+ return {threads:returnedThreads,errors:returnedErrors};
501
+ }catch(error){
502
+ if(operation.terminal){
503
+ throw operation.error??error;
504
+ }
505
+ if(error instanceof Error&&error.name==='AbortError'){
506
+ this.#publishCancellation(
507
+ operation,
508
+ error,
509
+ error.code===COMMUNICATION_HUB_ERROR_CODES.refreshSuperseded
510
+ ?COMMUNICATION_HUB_REFRESH_REASONS.superseded
511
+ :COMMUNICATION_HUB_REFRESH_REASONS.signalAborted
512
+ );
513
+ throw error;
514
+ }
515
+ const failure=refreshFailureError(error);
516
+ operation.terminal=true;
517
+ operation.error=failure;
518
+ this.#clearRefreshOperation(operation);
519
+ if(!this.#events.disposed){
520
+ this.#events.dispatch(
521
+ COMMUNICATION_HUB_EVENTS.refreshFailed,
522
+ Object.freeze({error:failure}),
523
+ {
524
+ operationId:operation.operationId,
525
+ publicDetail:Object.freeze({
526
+ code:failure.code,
527
+ reason:COMMUNICATION_HUB_REFRESH_REASONS.boundaryThrew,
528
+ state:COMMUNICATION_HUB_REFRESH_STATES.failed
529
+ })
530
+ }
531
+ );
532
+ }
533
+ throw failure;
534
+ }
535
+ }
536
+
537
+ async messages(thread){
538
+ this.#assertOpen();
539
+ const record=thread instanceof CommunicationThread
540
+ ?thread
541
+ :new CommunicationThread(thread);
542
+ const values=await this.registry.get(record.providerId).getMessages(record.id);
543
+ this.#assertOpen();
544
+ return values.map(function normalizeCommunicationMessage(value){
545
+ return value instanceof CommunicationMessage
546
+ ?value
547
+ :new CommunicationMessage(
548
+ {
549
+ ...value,
550
+ threadId:record.id,
551
+ providerId:record.providerId,
552
+ channel:record.channel
553
+ }
554
+ );
555
+ }).sort(function compareCommunicationMessages(left,right){
556
+ return left.timestamp.localeCompare(right.timestamp);
557
+ });
558
+ }
559
+
560
+ async send({providerId,threadId,channel,body,subject='',recipients=[]}={}){
561
+ this.#assertOpen();
562
+ if(!String(body||'').trim()){
563
+ throw new TypeError('A message body is required.');
564
+ }
565
+ const result=await this.registry.get(providerId).send(
566
+ {
567
+ threadId,
568
+ channel,
569
+ body:String(body),
570
+ subject:String(subject),
571
+ recipients:Array.from(recipients||[])
572
+ }
573
+ );
574
+ this.#assertOpen();
575
+ return result;
576
+ }
577
+
578
+ dispose(){
579
+ if(this.#disposed){
580
+ return false;
581
+ }
582
+ this.#disposed=true;
583
+ this.#cancelActiveRefresh(
584
+ refreshAbortError(
585
+ disposedError(),
586
+ COMMUNICATION_HUB_ERROR_CODES.disposed
587
+ ),
588
+ COMMUNICATION_HUB_REFRESH_REASONS.hubDisposed
589
+ );
590
+ return this.#events.dispose();
591
+ }
592
+
593
+ destroy(){
594
+ return this.dispose();
595
+ }
16
596
  }