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,8 +1,111 @@
1
+ import {createArcaneEventSource} from 'arcane-os/event-manager';
1
2
  import {resolveApplicationLocalStorageKey} from './AppDataScope.js';
2
3
 
4
+ export const RECORD_REVIEW_STORE_ERROR_CODES=Object.freeze({
5
+ adapterInvalid:'ARCANE_RECORD_REVIEW_STORE_ADAPTER_INVALID',
6
+ disposed:'ARCANE_RECORD_REVIEW_STORE_DISPOSED',
7
+ operationAborted:'ARCANE_RECORD_REVIEW_STORE_OPERATION_ABORTED',
8
+ operationOptionsInvalid:'ARCANE_RECORD_REVIEW_STORE_OPERATION_OPTIONS_INVALID',
9
+ recordIdInvalid:'ARCANE_RECORD_REVIEW_ID_INVALID',
10
+ storedRecordsInvalid:'ARCANE_RECORD_REVIEW_STORED_RECORDS_INVALID'
11
+ });
12
+
13
+ export const RECORD_REVIEW_STORE_EVENT_TYPES=Object.freeze({
14
+ change:'record-review-change'
15
+ });
16
+
17
+ function recordReviewStoreError(code,reason,message,ErrorType=Error,cause){
18
+ const error=new ErrorType(message);
19
+ error.code=code;
20
+ error.reason=reason;
21
+ if(cause!==undefined) error.cause=cause;
22
+ return error;
23
+ }
24
+
25
+ function disposedError(){
26
+ return recordReviewStoreError(
27
+ RECORD_REVIEW_STORE_ERROR_CODES.disposed,
28
+ 'record-review-store-disposed',
29
+ 'The record review store has been disposed.'
30
+ );
31
+ }
32
+
33
+ function operationAbortedError(reason){
34
+ const error=recordReviewStoreError(
35
+ RECORD_REVIEW_STORE_ERROR_CODES.operationAborted,
36
+ 'record-review-operation-aborted',
37
+ 'The record review operation was aborted.',
38
+ Error,
39
+ reason
40
+ );
41
+ error.name='AbortError';
42
+ return error;
43
+ }
44
+
45
+ function isPlainRecord(value){
46
+ if(!value||typeof value!=='object'||Array.isArray(value)) return false;
47
+ const prototype=Object.getPrototypeOf(value);
48
+ return prototype===Object.prototype||prototype===null;
49
+ }
50
+
51
+ function isAbortSignal(value){
52
+ return Boolean(value)
53
+ &&typeof value==='object'
54
+ &&typeof value.aborted==='boolean'
55
+ &&typeof value.addEventListener==='function'
56
+ &&typeof value.removeEventListener==='function';
57
+ }
58
+
59
+ function normalizeOperationOptions(value={}){
60
+ if(!isPlainRecord(value)){
61
+ throw recordReviewStoreError(
62
+ RECORD_REVIEW_STORE_ERROR_CODES.operationOptionsInvalid,
63
+ 'record-review-operation-options-invalid',
64
+ 'Record review operation options must be a plain object.',
65
+ TypeError
66
+ );
67
+ }
68
+ const keys=Reflect.ownKeys(value);
69
+ if(keys.some(function hasUnsupportedRecordReviewOperationOption(key){
70
+ return key!=='signal';
71
+ })){
72
+ throw recordReviewStoreError(
73
+ RECORD_REVIEW_STORE_ERROR_CODES.operationOptionsInvalid,
74
+ 'record-review-operation-options-invalid',
75
+ 'Record review operation options support only signal.',
76
+ TypeError
77
+ );
78
+ }
79
+ const signal=value.signal??null;
80
+ if(signal!==null&&!isAbortSignal(signal)){
81
+ throw recordReviewStoreError(
82
+ RECORD_REVIEW_STORE_ERROR_CODES.operationOptionsInvalid,
83
+ 'record-review-operation-signal-invalid',
84
+ 'Record review operation signal must be an AbortSignal.',
85
+ TypeError
86
+ );
87
+ }
88
+ return Object.freeze({signal});
89
+ }
90
+
91
+ function setDataProperty(target,key,value){
92
+ Object.defineProperty(
93
+ target,
94
+ key,
95
+ {configurable:true,enumerable:true,value,writable:true}
96
+ );
97
+ }
98
+
3
99
  function normalizeRecordId(value=''){
4
100
  const id=String(value).trim();
5
- if(!id||id.length>160||/[\x00-\x1f]/.test(id)) throw new TypeError('A valid record id is required.');
101
+ if(!id||id.length>160||/[\x00-\x1f]/.test(id)){
102
+ throw recordReviewStoreError(
103
+ RECORD_REVIEW_STORE_ERROR_CODES.recordIdInvalid,
104
+ 'record-review-id-invalid',
105
+ 'A valid record id is required.',
106
+ TypeError
107
+ );
108
+ }
6
109
  return id;
7
110
  }
8
111
 
@@ -13,18 +116,73 @@ function normalizeReview(value={}){
13
116
  for(const [key,value] of Object.entries(source.attributes).slice(0,40)){
14
117
  const normalizedKey=String(key).replace(/[^a-z0-9._-]/gi,'-').slice(0,80);
15
118
  if(!normalizedKey) continue;
16
- attributes[normalizedKey]=Array.isArray(value)
17
- ?value.slice(0,100).map(item=>String(item).slice(0,500))
18
- :String(value??'').slice(0,10000);
119
+ setDataProperty(
120
+ attributes,
121
+ normalizedKey,
122
+ Array.isArray(value)
123
+ ?Object.freeze(value.slice(0,100).map(function normalizeReviewAttributeItem(item){
124
+ return String(item).slice(0,500);
125
+ }))
126
+ :String(value??'').slice(0,10000)
127
+ );
19
128
  }
20
129
  }
21
- return {
130
+ return Object.freeze({
22
131
  status:String(source.status||'not-reviewed').trim().slice(0,80)||'not-reviewed',
23
132
  classification:String(source.classification||'unassigned').trim().slice(0,80)||'unassigned',
24
- attributes,
133
+ attributes:Object.freeze(attributes),
25
134
  notes:String(source.notes||'').slice(0,10000),
26
135
  updatedAt:source.updatedAt?String(source.updatedAt):null
27
- };
136
+ });
137
+ }
138
+
139
+ function frozenRecordMap(records){
140
+ const copy={};
141
+ for(const [id,review] of Object.entries(records)){
142
+ setDataProperty(copy,id,normalizeReview(review));
143
+ }
144
+ return Object.freeze(copy);
145
+ }
146
+
147
+ function normalizedStoredRecords(value){
148
+ if(!isPlainRecord(value)) return Object.freeze({});
149
+ const records={};
150
+ for(const [recordId,review] of Object.entries(value)){
151
+ let id;
152
+ try{
153
+ id=normalizeRecordId(recordId);
154
+ }catch(error){
155
+ throw recordReviewStoreError(
156
+ RECORD_REVIEW_STORE_ERROR_CODES.storedRecordsInvalid,
157
+ 'record-review-stored-id-invalid',
158
+ 'Stored record reviews contain an invalid record id.',
159
+ TypeError,
160
+ error
161
+ );
162
+ }
163
+ if(Object.prototype.hasOwnProperty.call(records,id)){
164
+ throw recordReviewStoreError(
165
+ RECORD_REVIEW_STORE_ERROR_CODES.storedRecordsInvalid,
166
+ 'record-review-stored-id-collision',
167
+ 'Stored record reviews contain colliding record ids.',
168
+ TypeError
169
+ );
170
+ }
171
+ setDataProperty(records,id,normalizeReview(review));
172
+ }
173
+ return Object.freeze(records);
174
+ }
175
+
176
+ function validateAdapter(adapter){
177
+ if(!adapter||typeof adapter.get!=='function'||typeof adapter.set!=='function'){
178
+ throw recordReviewStoreError(
179
+ RECORD_REVIEW_STORE_ERROR_CODES.adapterInvalid,
180
+ 'record-review-storage-adapter-invalid',
181
+ 'The record review storage adapter must provide get and set methods.',
182
+ TypeError
183
+ );
184
+ }
185
+ return adapter;
28
186
  }
29
187
 
30
188
  function localAdapter(namespace){
@@ -33,7 +191,11 @@ function localAdapter(namespace){
33
191
  async get(){
34
192
  const raw=globalThis.localStorage?.getItem(key);
35
193
  if(!raw) return {};
36
- try{return JSON.parse(raw);}catch{return {};}
194
+ try{
195
+ return JSON.parse(raw);
196
+ }catch{
197
+ return {};
198
+ }
37
199
  },
38
200
  async set(value){
39
201
  globalThis.localStorage?.setItem(key,JSON.stringify(value));
@@ -51,24 +213,56 @@ function nativeAdapter(namespace){
51
213
  const result=await storage.get(key);
52
214
  return result?.value??result??{};
53
215
  },
54
- async set(value){ await storage.set(key,value); return value; }
216
+ async set(value){
217
+ await storage.set(key,value);
218
+ return value;
219
+ }
55
220
  };
56
221
  }
57
222
 
58
223
  class RecordReviewStore extends EventTarget{
224
+ #disposed=false;
225
+ #events;
226
+ #operationSequence=0;
227
+ #operationTail=Promise.resolve();
228
+ #pendingOperations=new Set();
229
+
59
230
  constructor({namespace='records',adapter=null}={}){
60
231
  super();
61
232
  this.namespace=String(namespace||'records').replace(/[^a-z0-9._-]/gi,'-').slice(0,120);
62
- this.adapter=adapter||nativeAdapter(this.namespace)||localAdapter(this.namespace);
63
- this.records={};
233
+ this.adapter=validateAdapter(adapter||nativeAdapter(this.namespace)||localAdapter(this.namespace));
234
+ this.records=Object.freeze({});
64
235
  this.loaded=false;
236
+ this.#events=createArcaneEventSource(
237
+ this,
238
+ {
239
+ source:'record-review-store',
240
+ eventTypes:Object.freeze(Object.values(RECORD_REVIEW_STORE_EVENT_TYPES))
241
+ }
242
+ );
65
243
  }
66
244
 
67
- async load(){
68
- const stored=await this.adapter.get();
69
- this.records=stored&&typeof stored==='object'&&!Array.isArray(stored)?stored:{};
70
- this.loaded=true;
71
- return this.snapshot();
245
+ addEventListener(type,listener,options){return this.#events.addEventListener(type,listener,options);}
246
+ removeEventListener(type,listener,options){return this.#events.removeEventListener(type,listener,options);}
247
+ on(type,listener,options){return this.#events.on(type,listener,options);}
248
+ dispatchEvent(value){return this.#events.dispatchEvent(value);}
249
+
250
+ async load(options={}){
251
+ const operation=normalizeOperationOptions(options);
252
+ const operationId=this.#nextOperationId('load');
253
+ const store=this;
254
+ return this.#enqueueOperation(
255
+ async function loadRecordReviews(){
256
+ const stored=await store.adapter.get(
257
+ Object.freeze({operationId,signal:operation.signal})
258
+ );
259
+ store.#assertOperationActive(operation.signal);
260
+ store.records=normalizedStoredRecords(stored);
261
+ store.loaded=true;
262
+ return store.snapshot();
263
+ },
264
+ operation.signal
265
+ );
72
266
  }
73
267
 
74
268
  get(recordId){
@@ -76,17 +270,122 @@ class RecordReviewStore extends EventTarget{
76
270
  return normalizeReview(this.records[id]);
77
271
  }
78
272
 
79
- async set(recordId,value={}){
273
+ async set(recordId,value={},options={}){
274
+ const operation=normalizeOperationOptions(options);
80
275
  const id=normalizeRecordId(recordId);
81
- const review=normalizeReview({...this.get(id),...value,updatedAt:new Date().toISOString()});
82
- this.records={...this.records,[id]:review};
83
- await this.adapter.set(this.records);
84
- this.dispatchEvent(new CustomEvent('record-review-change',{detail:{recordId:id,review:{...review}}}));
85
- return {...review};
276
+ const operationId=this.#nextOperationId('set');
277
+ const store=this;
278
+ return this.#enqueueOperation(
279
+ async function setRecordReview(){
280
+ const review=normalizeReview({
281
+ ...store.get(id),
282
+ ...value,
283
+ updatedAt:new Date().toISOString()
284
+ });
285
+ const records=frozenRecordMap({...store.records,[id]:review});
286
+ await store.adapter.set(
287
+ records,
288
+ Object.freeze({operationId,signal:operation.signal})
289
+ );
290
+ store.#assertOperationActive(operation.signal);
291
+ store.records=records;
292
+ const detail=Object.freeze({
293
+ namespace:store.namespace,
294
+ recordId:id,
295
+ review:normalizeReview(review)
296
+ });
297
+ store.#events.dispatch(
298
+ RECORD_REVIEW_STORE_EVENT_TYPES.change,
299
+ detail,
300
+ {operationId,publicDetail:detail}
301
+ );
302
+ return normalizeReview(review);
303
+ },
304
+ operation.signal
305
+ );
86
306
  }
87
307
 
88
308
  snapshot(){
89
- return Object.fromEntries(Object.entries(this.records).map(([id,value])=>[id,normalizeReview(value)]));
309
+ return frozenRecordMap(this.records);
310
+ }
311
+
312
+ dispose(){
313
+ if(this.#disposed) return false;
314
+ this.#disposed=true;
315
+ const error=disposedError();
316
+ for(const cancel of [...this.#pendingOperations]) cancel(error);
317
+ return this.#events.dispose();
318
+ }
319
+
320
+ destroy(){return this.dispose();}
321
+
322
+ #assertOpen(){
323
+ if(this.#disposed) throw disposedError();
324
+ }
325
+
326
+ #assertOperationActive(signal){
327
+ this.#assertOpen();
328
+ if(signal?.aborted) throw operationAbortedError(signal.reason);
329
+ }
330
+
331
+ #nextOperationId(action){
332
+ this.#assertOpen();
333
+ this.#operationSequence+=1;
334
+ return `${this.#events.instanceId}:${action}:${this.#operationSequence.toString(36)}`;
335
+ }
336
+
337
+ #enqueueOperation(operation,signal){
338
+ this.#assertOperationActive(signal);
339
+ const store=this;
340
+ let abortHandler=null;
341
+ let rejectResult;
342
+ let resolveResult;
343
+ let settled=false;
344
+ const result=new Promise(function captureRecordReviewOperationSettlement(resolve,reject){
345
+ resolveResult=resolve;
346
+ rejectResult=reject;
347
+ });
348
+ function cleanupRecordReviewOperation(){
349
+ signal?.removeEventListener('abort',abortHandler);
350
+ store.#pendingOperations.delete(cancelRecordReviewOperation);
351
+ }
352
+ function settleRecordReviewOperation(handler,value){
353
+ if(settled) return false;
354
+ settled=true;
355
+ cleanupRecordReviewOperation();
356
+ handler(value);
357
+ return true;
358
+ }
359
+ function cancelRecordReviewOperation(error){
360
+ return settleRecordReviewOperation(rejectResult,error);
361
+ }
362
+ abortHandler=function abortRecordReviewOperation(){
363
+ cancelRecordReviewOperation(operationAbortedError(signal.reason));
364
+ };
365
+ store.#pendingOperations.add(cancelRecordReviewOperation);
366
+ signal?.addEventListener('abort',abortHandler,{once:true});
367
+ if(signal?.aborted) abortHandler();
368
+
369
+ async function runRecordReviewOperation(){
370
+ if(settled) return;
371
+ try{
372
+ store.#assertOperationActive(signal);
373
+ const value=await operation();
374
+ store.#assertOperationActive(signal);
375
+ settleRecordReviewOperation(resolveResult,value);
376
+ }catch(error){
377
+ settleRecordReviewOperation(rejectResult,error);
378
+ }
379
+ }
380
+ const queued=this.#operationTail.then(
381
+ runRecordReviewOperation,
382
+ runRecordReviewOperation
383
+ );
384
+ this.#operationTail=queued.then(
385
+ function completeRecordReviewOperationLane(){},
386
+ function recoverRecordReviewOperationLane(){}
387
+ );
388
+ return result;
90
389
  }
91
390
  }
92
391