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.
- package/CHANGELOG.md +29 -0
- package/README.md +8 -8
- package/browser-runtime/ARCANE_SDK_BROWSER_RELEASE.json +29 -22
- package/browser-runtime/ai/ARCANE_AI_BROWSER_SPEECH_COMPONENTS.json +203 -0
- package/browser-runtime/ai/browser-kokoro-worker.mjs +11 -2
- package/browser-runtime/ai/browser-speech-artifacts.mjs +3230 -397
- package/browser-runtime/ai/browser-speech-providers.mjs +1141 -157
- package/browser-runtime/ai/browser-speech.mjs +2 -0
- package/browser-runtime/ai/browser-whisper-worker.mjs +11 -2
- package/browser-runtime/ai/model-controller.mjs +285 -95
- package/browser-runtime/ai/speech-worker-client.mjs +247 -32
- package/browser-runtime/ai/speech-worker-runtime.mjs +2310 -167
- package/browser-runtime/event-manager.mjs +1097 -1
- package/docs/architecture.md +2 -2
- package/docs/event-manager.md +155 -27
- package/docs/reference/README.md +27 -27
- package/docs/reference/ai/browser-speech-package-authority.json +835 -0
- package/docs/reference/ai/browser-speech.md +1162 -246
- package/docs/reference/ai/browser-wasm.md +18 -7
- package/docs/reference/availability-and-normalization.md +6 -3
- package/docs/reference/behavioral-testing.md +29 -6
- package/docs/reference/cli.md +117 -9
- package/docs/reference/core/arcane-ai-contracts.md +1 -1
- package/docs/reference/event-manager.md +577 -32
- package/docs/reference/inventory/package-api.json +478 -2
- package/docs/reference/inventory/runtime-components.json +108 -44
- package/docs/reference/inventory/runtime-modules.json +131 -53
- package/docs/reference/mail.md +316 -0
- package/docs/reference/protocols.md +157 -43
- package/docs/reference/runtime-components.md +258 -83
- package/docs/reference/runtime-modules.md +613 -77
- package/docs/reference/sdk-api.md +1014 -25
- package/package.json +5 -4
- package/runtime/ARCANE_RUNTIME_RELEASE.json +145 -140
- package/runtime/arcane/components/app-bar.html +34 -13
- package/runtime/arcane/components/assistant-panel.html +110 -57
- package/runtime/arcane/components/calculator.html +7 -4
- package/runtime/arcane/components/chart.html +58 -17
- package/runtime/arcane/components/chat.html +606 -136
- package/runtime/arcane/components/conversation-view.html +13 -6
- package/runtime/arcane/components/dashboard-config.html +96 -59
- package/runtime/arcane/components/data-maintenance.html +69 -14
- package/runtime/arcane/components/data-view.html +53 -7
- package/runtime/arcane/components/directory-picker.html +118 -32
- package/runtime/arcane/components/document-inspector.html +47 -10
- package/runtime/arcane/components/file-drop.html +81 -35
- package/runtime/arcane/components/file-inspector.html +72 -22
- package/runtime/arcane/components/file-manager.html +374 -79
- package/runtime/arcane/components/integration-settings.html +12 -5
- package/runtime/arcane/components/local-ai-status.html +48 -19
- package/runtime/arcane/components/markdown-document.html +161 -68
- package/runtime/arcane/components/markdown-editor.html +110 -33
- package/runtime/arcane/components/media-embed.html +8 -5
- package/runtime/arcane/components/modal.html +15 -5
- package/runtime/arcane/components/output-panel.html +28 -23
- package/runtime/arcane/components/preferences-form.html +22 -4
- package/runtime/arcane/components/record-timeline.html +18 -2
- package/runtime/arcane/components/relationship-board.html +23 -3
- package/runtime/arcane/components/screen-capture.html +10 -4
- package/runtime/arcane/components/source-code-viewer.html +76 -9
- package/runtime/arcane/components/source-explanation.html +23 -3
- package/runtime/arcane/components/speech.html +462 -384
- package/runtime/arcane/components/summary-strip.html +22 -11
- package/runtime/arcane/components/table.html +39 -21
- package/runtime/arcane/components/task-progress.html +79 -21
- package/runtime/arcane/components/terminal-workspace.html +7 -4
- package/runtime/arcane/components/theme-editor.html +7 -3
- package/runtime/arcane/components/unified-inbox.html +9 -4
- package/runtime/arcane/components/voice-transcription.html +639 -98
- package/runtime/arcane/components/weather-widget.html +5 -3
- package/runtime/arcane/components/web-navigator.html +48 -8
- package/runtime/arcane/entities/Chat.js +1 -1
- package/runtime/arcane/entities/User.js +110 -23
- package/runtime/arcane/modules/AI.js +2109 -130
- package/runtime/arcane/modules/AIProviderRuntime.js +720 -13
- package/runtime/arcane/modules/AIRuntimeState.js +109 -52
- package/runtime/arcane/modules/ApiModelDatabase.js +390 -17
- package/runtime/arcane/modules/BrowserTestSuite.js +205 -28
- package/runtime/arcane/modules/CalculatorEngine.js +63 -3
- package/runtime/arcane/modules/CommunicationAppController.js +588 -28
- package/runtime/arcane/modules/CommunicationHub.js +590 -10
- package/runtime/arcane/modules/ComponentContracts.js +470 -0
- package/runtime/arcane/modules/ConversationTimebox.js +152 -33
- package/runtime/arcane/modules/DBLS.js +40 -7
- package/runtime/arcane/modules/DBOPFS.js +35 -11
- package/runtime/arcane/modules/DataMaintenance.js +12 -2
- package/runtime/arcane/modules/Errors.js +65 -7
- package/runtime/arcane/modules/HTMLImport.js +198 -14
- package/runtime/arcane/modules/LocalAIReadinessController.js +208 -29
- package/runtime/arcane/modules/Mail.js +738 -115
- package/runtime/arcane/modules/MailOutbox.mjs +1395 -0
- package/runtime/arcane/modules/MailTransport.mjs +197 -39
- package/runtime/arcane/modules/Ollama.js +36 -1
- package/runtime/arcane/modules/OpenMeteoWeatherProvider.js +583 -7
- package/runtime/arcane/modules/PreferenceStore.js +367 -33
- package/runtime/arcane/modules/RecordReviewStore.js +322 -23
- package/runtime/arcane/modules/ScreenCapture.js +1397 -15
- package/runtime/arcane/modules/SpeechPlayback.js +438 -41
- package/runtime/arcane/modules/TerminalClient.js +277 -12
- package/runtime/arcane/modules/ThemeBootstrap.js +80 -6
- package/runtime/arcane/modules/ThemeManager.js +39 -7
- package/runtime/arcane/modules/TimeGuard.js +131 -20
- package/runtime/arcane/modules/WaitForComponent.js +386 -33
- package/schemas/arcane-lock.schema.json +2 -2
- package/src/cli/main.mjs +435 -9
- package/src/event-manager.mjs +1097 -1
- package/src/import-map.mjs +21 -3
- package/src/index.mjs +13 -0
- package/src/installed-sdk-runtime.mjs +112 -0
- package/src/mail-api.mjs +22 -0
- package/src/mail-credentials.mjs +667 -0
- package/src/mail-server.mjs +1769 -0
- package/src/mail.mjs +261 -0
- package/src/sdk-browser-runtime.mjs +85 -41
- package/src/testing-loader.mjs +7 -0
- package/src/toolchain.mjs +3 -0
- package/src/workspace.mjs +1 -1
|
@@ -1,7 +1,30 @@
|
|
|
1
|
+
import {createArcaneEventSource} from 'arcane-os/event-manager';
|
|
2
|
+
|
|
1
3
|
const CONVERSATION_TIMEBOX_TOOL_NAME='conversation_timebox';
|
|
2
4
|
const CONVERSATION_TIMEBOX_TICK_MS=1000;
|
|
3
5
|
const MAX_DATE_MILLISECONDS=8_640_000_000_000_000;
|
|
4
6
|
|
|
7
|
+
export const CONVERSATION_TIMEBOX_EVENT_TYPES=Object.freeze({
|
|
8
|
+
change:'conversation-timebox-change'
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export const CONVERSATION_TIMEBOX_ERROR_CODES=Object.freeze({
|
|
12
|
+
disposed:'ARCANE_CONVERSATION_TIMEBOX_DISPOSED',
|
|
13
|
+
subscriptionHandlerInvalid:'ARCANE_CONVERSATION_TIMEBOX_SUBSCRIPTION_HANDLER_INVALID',
|
|
14
|
+
subscriptionOptionsInvalid:'ARCANE_CONVERSATION_TIMEBOX_SUBSCRIPTION_OPTIONS_INVALID'
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
export const CONVERSATION_TIMEBOX_REASONS=Object.freeze({
|
|
18
|
+
deadlineReached:'conversation-timebox-deadline-reached',
|
|
19
|
+
disposed:'conversation-timebox-disposed',
|
|
20
|
+
elapsedTimeAdvanced:'conversation-timebox-elapsed-time-advanced',
|
|
21
|
+
limitAdjusted:'conversation-timebox-limit-adjusted',
|
|
22
|
+
limitCleared:'conversation-timebox-limit-cleared',
|
|
23
|
+
limitSet:'conversation-timebox-limit-set',
|
|
24
|
+
snapshotReplayed:'conversation-timebox-snapshot-replayed',
|
|
25
|
+
started:'conversation-timebox-started'
|
|
26
|
+
});
|
|
27
|
+
|
|
5
28
|
const CONVERSATION_TIMEBOX_OPENING_INSTRUCTION=`Before any other intake question, ask exactly one timing question: "Do you have a limited amount of time to talk right now?" Ask no other question in that opening reply, and wait for the answer before continuing the app's normal intake. The elapsed conversation timer is already visible. A time limit applies only to this conversation. Never infer, round, or invent a duration. The conversation_timebox tool is available on every turn. When the user explicitly sets a duration, call it with action "set" and convert the exact stated duration to whole milliseconds. When the user explicitly adds time, call it with action "adjust" and convert the exact added duration to whole milliseconds. When the user explicitly says there is no limit, asks to remove it, or chooses to continue after a due check without setting another duration, call it with action "clear". A duration is always sent as duration_milliseconds; for example, 75 seconds is 75000 and 10 minutes is 600000. If the duration is vague or ranged, ask one focused question and do not call the tool yet. The timebox tool must be the sole tool call for its response. Adapt the pace and prioritize the most important next action within an active limit without skipping safety. A message that the agreed time check is due is a check-in, not a request to end: ask whether the user wants to end now or continue, and do not assume their choice.`;
|
|
6
29
|
|
|
7
30
|
const CONVERSATION_TIMEBOX_LIMIT_MESSAGE='The agreed conversation time check is due. Please ask whether I want to end now or continue, and do not assume my choice or set another limit unless I explicitly state one.';
|
|
@@ -144,6 +167,51 @@ function reportConversationTimeboxListenerError(error){
|
|
|
144
167
|
console.error('A conversation timebox listener failed.',error);
|
|
145
168
|
}
|
|
146
169
|
|
|
170
|
+
function conversationTimeboxError(message,code,reason,ErrorType=Error){
|
|
171
|
+
const error=new ErrorType(message);
|
|
172
|
+
error.code=code;
|
|
173
|
+
error.reason=reason;
|
|
174
|
+
return error;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function isAbortSignal(value){
|
|
178
|
+
return Boolean(value)
|
|
179
|
+
&&typeof value==='object'
|
|
180
|
+
&&typeof value.aborted==='boolean'
|
|
181
|
+
&&typeof value.addEventListener==='function'
|
|
182
|
+
&&typeof value.removeEventListener==='function';
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function normalizeSubscriptionOptions(value={}){
|
|
186
|
+
if(!isPlainRecord(value)){
|
|
187
|
+
throw conversationTimeboxError(
|
|
188
|
+
'Conversation timebox subscription options must be a plain object.',
|
|
189
|
+
CONVERSATION_TIMEBOX_ERROR_CODES.subscriptionOptionsInvalid,
|
|
190
|
+
'conversation-timebox-subscription-options-invalid',
|
|
191
|
+
TypeError
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
const unsupported=Reflect.ownKeys(value).find(function findUnsupportedTimeboxSubscriptionOption(key){
|
|
195
|
+
return key!=='once'&&key!=='signal';
|
|
196
|
+
});
|
|
197
|
+
if(unsupported!==undefined||(
|
|
198
|
+
value.once!==undefined
|
|
199
|
+
&&typeof value.once!=='boolean'
|
|
200
|
+
)||(
|
|
201
|
+
value.signal!==undefined
|
|
202
|
+
&&value.signal!==null
|
|
203
|
+
&&!isAbortSignal(value.signal)
|
|
204
|
+
)){
|
|
205
|
+
throw conversationTimeboxError(
|
|
206
|
+
'Conversation timebox subscriptions support only boolean once and an AbortSignal.',
|
|
207
|
+
CONVERSATION_TIMEBOX_ERROR_CODES.subscriptionOptionsInvalid,
|
|
208
|
+
'conversation-timebox-subscription-options-invalid',
|
|
209
|
+
TypeError
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
return Object.freeze({once:value.once===true,signal:value.signal??null});
|
|
213
|
+
}
|
|
214
|
+
|
|
147
215
|
function defaultClock(){
|
|
148
216
|
return Date.now();
|
|
149
217
|
}
|
|
@@ -194,10 +262,10 @@ class ConversationTimebox{
|
|
|
194
262
|
#dueAtMs=null;
|
|
195
263
|
#dueRevision=null;
|
|
196
264
|
#durationMilliseconds=null;
|
|
265
|
+
#events;
|
|
197
266
|
#intervalHandle=null;
|
|
198
|
-
#listeners=new Set();
|
|
199
267
|
#nowMs=null;
|
|
200
|
-
#
|
|
268
|
+
#operationSequence=0;
|
|
201
269
|
#revision=0;
|
|
202
270
|
#schedule;
|
|
203
271
|
#setAtMs=null;
|
|
@@ -219,14 +287,14 @@ class ConversationTimebox{
|
|
|
219
287
|
this.#clock=options.clock??defaultClock;
|
|
220
288
|
this.#schedule=options.schedule??defaultSchedule;
|
|
221
289
|
this.#cancel=options.cancel??defaultCancel;
|
|
222
|
-
|
|
290
|
+
const onListenerError=options.onListenerError??reportConversationTimeboxListenerError;
|
|
223
291
|
this.#tickMs=options.tickMs??CONVERSATION_TIMEBOX_TICK_MS;
|
|
224
292
|
|
|
225
293
|
for(const [name,value] of [
|
|
226
294
|
['clock',this.#clock],
|
|
227
295
|
['schedule',this.#schedule],
|
|
228
296
|
['cancel',this.#cancel],
|
|
229
|
-
['onListenerError',
|
|
297
|
+
['onListenerError',onListenerError]
|
|
230
298
|
]){
|
|
231
299
|
if(typeof value!=='function'){
|
|
232
300
|
throw new TypeError(`${name} must be a function.`);
|
|
@@ -235,6 +303,11 @@ class ConversationTimebox{
|
|
|
235
303
|
if(!Number.isSafeInteger(this.#tickMs)||this.#tickMs<100||this.#tickMs>60_000){
|
|
236
304
|
throw new RangeError('tickMs must be an integer between 100 and 60000.');
|
|
237
305
|
}
|
|
306
|
+
this.#events=createArcaneEventSource(this,{
|
|
307
|
+
source:'conversation-timebox',
|
|
308
|
+
eventTypes:Object.freeze(Object.values(CONVERSATION_TIMEBOX_EVENT_TYPES)),
|
|
309
|
+
onListenerError
|
|
310
|
+
});
|
|
238
311
|
}
|
|
239
312
|
|
|
240
313
|
get limitReachedMessage(){
|
|
@@ -251,7 +324,7 @@ class ConversationTimebox{
|
|
|
251
324
|
return this.snapshot();
|
|
252
325
|
}
|
|
253
326
|
const now=this.#readClock();
|
|
254
|
-
this.#startAt(now,true);
|
|
327
|
+
this.#startAt(now,true,CONVERSATION_TIMEBOX_REASONS.started);
|
|
255
328
|
return this.snapshot();
|
|
256
329
|
}
|
|
257
330
|
|
|
@@ -263,13 +336,14 @@ class ConversationTimebox{
|
|
|
263
336
|
if(typeof source!=='string'||!source.trim()){
|
|
264
337
|
throw new TypeError('Conversation timebox source must contain text.');
|
|
265
338
|
}
|
|
339
|
+
const commandSource=source.trim();
|
|
266
340
|
const now=this.#readClock();
|
|
267
341
|
const dueAtMs=now+milliseconds;
|
|
268
342
|
if(!Number.isSafeInteger(dueAtMs)||dueAtMs>MAX_DATE_MILLISECONDS){
|
|
269
343
|
throw new RangeError('The requested conversation duration exceeds the supported date range.');
|
|
270
344
|
}
|
|
271
345
|
if(this.#startedAtMs===null){
|
|
272
|
-
this.#startAt(now,false);
|
|
346
|
+
this.#startAt(now,false,CONVERSATION_TIMEBOX_REASONS.started);
|
|
273
347
|
}
|
|
274
348
|
|
|
275
349
|
this.#durationMilliseconds=milliseconds;
|
|
@@ -278,7 +352,7 @@ class ConversationTimebox{
|
|
|
278
352
|
this.#dueRevision=null;
|
|
279
353
|
this.#revision++;
|
|
280
354
|
this.#nowMs=now;
|
|
281
|
-
this.#notify('change');
|
|
355
|
+
this.#notify('change',CONVERSATION_TIMEBOX_REASONS.limitSet,commandSource);
|
|
282
356
|
return this.snapshot();
|
|
283
357
|
}
|
|
284
358
|
|
|
@@ -290,6 +364,7 @@ class ConversationTimebox{
|
|
|
290
364
|
if(typeof source!=='string'||!source.trim()){
|
|
291
365
|
throw new TypeError('Conversation timebox source must contain text.');
|
|
292
366
|
}
|
|
367
|
+
const commandSource=source.trim();
|
|
293
368
|
if(this.#dueAtMs===null){
|
|
294
369
|
throw new Error('An active conversation time check is required before adding time.');
|
|
295
370
|
}
|
|
@@ -306,7 +381,7 @@ class ConversationTimebox{
|
|
|
306
381
|
this.#dueRevision=null;
|
|
307
382
|
this.#revision++;
|
|
308
383
|
this.#nowMs=now;
|
|
309
|
-
this.#notify('change');
|
|
384
|
+
this.#notify('change',CONVERSATION_TIMEBOX_REASONS.limitAdjusted,commandSource);
|
|
310
385
|
return this.snapshot();
|
|
311
386
|
}
|
|
312
387
|
|
|
@@ -315,6 +390,7 @@ class ConversationTimebox{
|
|
|
315
390
|
if(typeof source!=='string'||!source.trim()){
|
|
316
391
|
throw new TypeError('Conversation timebox source must contain text.');
|
|
317
392
|
}
|
|
393
|
+
const commandSource=source.trim();
|
|
318
394
|
if(this.#startedAtMs===null){
|
|
319
395
|
this.start();
|
|
320
396
|
}
|
|
@@ -324,7 +400,7 @@ class ConversationTimebox{
|
|
|
324
400
|
this.#dueRevision=null;
|
|
325
401
|
this.#revision++;
|
|
326
402
|
this.#nowMs=this.#readClock();
|
|
327
|
-
this.#notify('change');
|
|
403
|
+
this.#notify('change',CONVERSATION_TIMEBOX_REASONS.limitCleared,commandSource);
|
|
328
404
|
return this.snapshot();
|
|
329
405
|
}
|
|
330
406
|
|
|
@@ -344,19 +420,41 @@ class ConversationTimebox{
|
|
|
344
420
|
);
|
|
345
421
|
}
|
|
346
422
|
|
|
347
|
-
subscribe(listener){
|
|
423
|
+
subscribe(listener,options={}){
|
|
348
424
|
if(typeof listener!=='function'){
|
|
349
|
-
throw
|
|
425
|
+
throw conversationTimeboxError(
|
|
426
|
+
'Conversation timebox listener must be a function.',
|
|
427
|
+
CONVERSATION_TIMEBOX_ERROR_CODES.subscriptionHandlerInvalid,
|
|
428
|
+
'conversation-timebox-subscription-handler-invalid',
|
|
429
|
+
TypeError
|
|
430
|
+
);
|
|
350
431
|
}
|
|
351
|
-
this.#
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
432
|
+
this.#assertActive();
|
|
433
|
+
const normalized=normalizeSubscriptionOptions(options);
|
|
434
|
+
function forwardConversationTimeboxChange(event){
|
|
435
|
+
listener(event.detail.state,event.detail.event);
|
|
436
|
+
}
|
|
437
|
+
const unsubscribe=this.#events.on(
|
|
438
|
+
CONVERSATION_TIMEBOX_EVENT_TYPES.change,
|
|
439
|
+
forwardConversationTimeboxChange,
|
|
440
|
+
normalized
|
|
355
441
|
);
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
442
|
+
if(normalized.signal?.aborted)return unsubscribe;
|
|
443
|
+
try{
|
|
444
|
+
listener(
|
|
445
|
+
this.snapshot(),
|
|
446
|
+
Object.freeze({
|
|
447
|
+
type:'snapshot',
|
|
448
|
+
reason:CONVERSATION_TIMEBOX_REASONS.snapshotReplayed,
|
|
449
|
+
revision:this.#revision,
|
|
450
|
+
commandSource:null
|
|
451
|
+
})
|
|
452
|
+
);
|
|
453
|
+
}catch(error){
|
|
454
|
+
unsubscribe();
|
|
455
|
+
throw error;
|
|
456
|
+
}
|
|
457
|
+
return unsubscribe;
|
|
360
458
|
}
|
|
361
459
|
|
|
362
460
|
snapshot(){
|
|
@@ -399,15 +497,19 @@ class ConversationTimebox{
|
|
|
399
497
|
this.#intervalHandle=null;
|
|
400
498
|
}
|
|
401
499
|
this.#disposed=true;
|
|
402
|
-
this.#notify('change');
|
|
500
|
+
this.#notify('change',CONVERSATION_TIMEBOX_REASONS.disposed);
|
|
403
501
|
const finalSnapshot=this.snapshot();
|
|
404
|
-
this.#
|
|
502
|
+
this.#events.dispose();
|
|
405
503
|
return finalSnapshot;
|
|
406
504
|
}
|
|
407
505
|
|
|
408
506
|
#assertActive(){
|
|
409
507
|
if(this.#disposed){
|
|
410
|
-
throw
|
|
508
|
+
throw conversationTimeboxError(
|
|
509
|
+
'The conversation timebox has been disposed.',
|
|
510
|
+
CONVERSATION_TIMEBOX_ERROR_CODES.disposed,
|
|
511
|
+
CONVERSATION_TIMEBOX_REASONS.disposed
|
|
512
|
+
);
|
|
411
513
|
}
|
|
412
514
|
}
|
|
413
515
|
|
|
@@ -419,7 +521,7 @@ class ConversationTimebox{
|
|
|
419
521
|
return this.#nowMs===null?now:Math.max(now,this.#nowMs);
|
|
420
522
|
}
|
|
421
523
|
|
|
422
|
-
#startAt(now,notify){
|
|
524
|
+
#startAt(now,notify,reason){
|
|
423
525
|
const intervalHandle=this.#schedule(
|
|
424
526
|
this.#tick.bind(this),
|
|
425
527
|
this.#tickMs
|
|
@@ -428,7 +530,7 @@ class ConversationTimebox{
|
|
|
428
530
|
this.#nowMs=now;
|
|
429
531
|
this.#intervalHandle=intervalHandle;
|
|
430
532
|
if(notify){
|
|
431
|
-
this.#notify('change');
|
|
533
|
+
this.#notify('change',reason);
|
|
432
534
|
}
|
|
433
535
|
}
|
|
434
536
|
|
|
@@ -446,19 +548,36 @@ class ConversationTimebox{
|
|
|
446
548
|
this.#dueRevision=this.#revision;
|
|
447
549
|
becameDue=true;
|
|
448
550
|
}
|
|
449
|
-
this.#notify(
|
|
551
|
+
this.#notify(
|
|
552
|
+
becameDue?'due':'tick',
|
|
553
|
+
becameDue
|
|
554
|
+
?CONVERSATION_TIMEBOX_REASONS.deadlineReached
|
|
555
|
+
:CONVERSATION_TIMEBOX_REASONS.elapsedTimeAdvanced
|
|
556
|
+
);
|
|
450
557
|
}
|
|
451
558
|
|
|
452
|
-
#notify(type=
|
|
559
|
+
#notify(type,reason,commandSource=null){
|
|
453
560
|
const state=this.snapshot();
|
|
454
|
-
const event=Object.freeze({
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
561
|
+
const event=Object.freeze({
|
|
562
|
+
type,
|
|
563
|
+
reason,
|
|
564
|
+
revision:this.#revision,
|
|
565
|
+
commandSource
|
|
566
|
+
});
|
|
567
|
+
const operationId=`${this.#events.instanceId}:change:${(++this.#operationSequence).toString(36)}`;
|
|
568
|
+
this.#events.dispatch(
|
|
569
|
+
CONVERSATION_TIMEBOX_EVENT_TYPES.change,
|
|
570
|
+
Object.freeze({state,event}),
|
|
571
|
+
{
|
|
572
|
+
operationId,
|
|
573
|
+
publicDetail:Object.freeze({
|
|
574
|
+
reason,
|
|
575
|
+
status:state.status,
|
|
576
|
+
revision:state.revision,
|
|
577
|
+
commandSource
|
|
578
|
+
})
|
|
460
579
|
}
|
|
461
|
-
|
|
580
|
+
);
|
|
462
581
|
}
|
|
463
582
|
}
|
|
464
583
|
|
|
@@ -2,8 +2,22 @@ import {
|
|
|
2
2
|
APP_LOCAL_STORAGE_PREFIX,
|
|
3
3
|
resolveBrowserApplicationId
|
|
4
4
|
} from './AppDataScope.js';
|
|
5
|
+
import {
|
|
6
|
+
createArcaneEventSource,
|
|
7
|
+
projectArcaneDOMEvent
|
|
8
|
+
} from 'arcane-os/event-manager';
|
|
9
|
+
|
|
10
|
+
const PUBLISH_DBLS_READY=Symbol('publish-dbls-ready');
|
|
11
|
+
export const DBLS_EVENT_TYPES=Object.freeze({
|
|
12
|
+
ready:'dbls-ready'
|
|
13
|
+
});
|
|
14
|
+
export const DBLS_REASONS=Object.freeze({
|
|
15
|
+
ready:'local-storage-adapter-ready'
|
|
16
|
+
});
|
|
5
17
|
|
|
6
18
|
class DBLS {
|
|
19
|
+
#events;
|
|
20
|
+
|
|
7
21
|
constructor({
|
|
8
22
|
applicationId=null,
|
|
9
23
|
documentObject=globalThis.document,
|
|
@@ -27,6 +41,10 @@ class DBLS {
|
|
|
27
41
|
});
|
|
28
42
|
this.storage=storage;
|
|
29
43
|
this.storagePrefix=`${APP_LOCAL_STORAGE_PREFIX}${this.applicationId}:`;
|
|
44
|
+
this.#events=createArcaneEventSource(this,{
|
|
45
|
+
source:'dbls',
|
|
46
|
+
eventTypes:Object.freeze(Object.values(DBLS_EVENT_TYPES))
|
|
47
|
+
});
|
|
30
48
|
}
|
|
31
49
|
|
|
32
50
|
ready=false;
|
|
@@ -153,19 +171,34 @@ class DBLS {
|
|
|
153
171
|
count() {
|
|
154
172
|
return this.logicalKeys().length;
|
|
155
173
|
}
|
|
174
|
+
|
|
175
|
+
[PUBLISH_DBLS_READY](){
|
|
176
|
+
const detail=Object.freeze({
|
|
177
|
+
dbls:this,
|
|
178
|
+
applicationId:this.applicationId,
|
|
179
|
+
reason:DBLS_REASONS.ready
|
|
180
|
+
});
|
|
181
|
+
const {occurrence}=this.#events.dispatch(
|
|
182
|
+
DBLS_EVENT_TYPES.ready,
|
|
183
|
+
detail,
|
|
184
|
+
{
|
|
185
|
+
operationId:`dbls-ready-${this.#events.instanceId}`,
|
|
186
|
+
publicDetail:Object.freeze({
|
|
187
|
+
applicationId:this.applicationId,
|
|
188
|
+
ready:true,
|
|
189
|
+
reason:DBLS_REASONS.ready
|
|
190
|
+
})
|
|
191
|
+
}
|
|
192
|
+
);
|
|
193
|
+
projectArcaneDOMEvent(window,occurrence);
|
|
194
|
+
}
|
|
156
195
|
}
|
|
157
196
|
|
|
158
197
|
if(typeof window.dbls?.get !== "function"){
|
|
159
198
|
window.dbls=new DBLS();
|
|
160
199
|
window.dbls.ready=true;
|
|
161
200
|
|
|
162
|
-
|
|
163
|
-
'dbls-ready', {
|
|
164
|
-
detail: { dbls: window.dbls }
|
|
165
|
-
}
|
|
166
|
-
);
|
|
167
|
-
|
|
168
|
-
window.dispatchEvent(dblsReady);
|
|
201
|
+
window.dbls[PUBLISH_DBLS_READY]();
|
|
169
202
|
}
|
|
170
203
|
|
|
171
204
|
export default DBLS;
|
|
@@ -1,7 +1,18 @@
|
|
|
1
1
|
import Is from "../../node_modules/strong-type/index.js";
|
|
2
2
|
import {openApplicationDataDirectory} from './AppDataScope.js';
|
|
3
|
+
import {
|
|
4
|
+
createArcaneEventSource,
|
|
5
|
+
projectArcaneDOMEvent
|
|
6
|
+
} from 'arcane-os/event-manager';
|
|
3
7
|
const is=new Is(false);
|
|
4
8
|
|
|
9
|
+
export const DBOPFS_EVENT_TYPES=Object.freeze({
|
|
10
|
+
ready:'dbopfs-ready'
|
|
11
|
+
});
|
|
12
|
+
export const DBOPFS_REASONS=Object.freeze({
|
|
13
|
+
ready:'opfs-database-ready'
|
|
14
|
+
});
|
|
15
|
+
|
|
5
16
|
const DEFAULT_TABLE_DIRECTORIES=Object.freeze({
|
|
6
17
|
users:'users',
|
|
7
18
|
scores:'scores',
|
|
@@ -94,6 +105,8 @@ if(navigator.storage?.persist){
|
|
|
94
105
|
*/
|
|
95
106
|
class DBOPFS {
|
|
96
107
|
|
|
108
|
+
#events;
|
|
109
|
+
|
|
97
110
|
/** @type {FileSystemDirectoryHandle|Object} */
|
|
98
111
|
#db={}
|
|
99
112
|
|
|
@@ -270,6 +283,10 @@ class DBOPFS {
|
|
|
270
283
|
return window.dbopfs;
|
|
271
284
|
}
|
|
272
285
|
|
|
286
|
+
this.#events=createArcaneEventSource(this,{
|
|
287
|
+
source:'dbopfs',
|
|
288
|
+
eventTypes:Object.freeze(Object.values(DBOPFS_EVENT_TYPES))
|
|
289
|
+
});
|
|
273
290
|
this.readyPromise=this.#init(options);
|
|
274
291
|
}
|
|
275
292
|
|
|
@@ -294,18 +311,25 @@ class DBOPFS {
|
|
|
294
311
|
|
|
295
312
|
this.ready=true;
|
|
296
313
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
}
|
|
307
|
-
|
|
314
|
+
const {occurrence}=this.#events.dispatch(
|
|
315
|
+
DBOPFS_EVENT_TYPES.ready,
|
|
316
|
+
Object.freeze({
|
|
317
|
+
dbopfs:this,
|
|
318
|
+
applicationId:this.#applicationId,
|
|
319
|
+
storagePath:this.#storagePath,
|
|
320
|
+
reason:DBOPFS_REASONS.ready
|
|
321
|
+
}),
|
|
322
|
+
{
|
|
323
|
+
operationId:`dbopfs-ready-${this.#events.instanceId}`,
|
|
324
|
+
publicDetail:Object.freeze({
|
|
325
|
+
applicationId:this.#applicationId,
|
|
326
|
+
ready:true,
|
|
327
|
+
reason:DBOPFS_REASONS.ready,
|
|
328
|
+
storagePath:this.#storagePath
|
|
329
|
+
})
|
|
330
|
+
}
|
|
308
331
|
);
|
|
332
|
+
projectArcaneDOMEvent(window,occurrence);
|
|
309
333
|
}
|
|
310
334
|
|
|
311
335
|
async #createDefaultTables(){
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import './DBOPFS.js';
|
|
2
2
|
import {hasUserEntry} from './ChatRecords.js';
|
|
3
3
|
import {hasMemoryContent} from './MemoryRecords.js';
|
|
4
|
+
import {arcaneEvents} from 'arcane-os/event-manager';
|
|
4
5
|
|
|
5
6
|
async function clearEmptyChatsAndMemories(){
|
|
6
7
|
await waitForDBOPFS();
|
|
@@ -70,12 +71,21 @@ function waitForDBOPFS(){
|
|
|
70
71
|
|
|
71
72
|
return new Promise(
|
|
72
73
|
function waitForDBOPFSPromise(resolve){
|
|
74
|
+
let settled=false;
|
|
75
|
+
let unsubscribe;
|
|
73
76
|
function ready(){
|
|
74
|
-
window.
|
|
77
|
+
if(settled||window.dbopfs?.ready!==true){
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
settled=true;
|
|
81
|
+
unsubscribe?.();
|
|
75
82
|
resolve(window.dbopfs);
|
|
76
83
|
}
|
|
77
84
|
|
|
78
|
-
|
|
85
|
+
unsubscribe=arcaneEvents.subscribe(
|
|
86
|
+
'dbopfs-ready',
|
|
87
|
+
ready
|
|
88
|
+
);
|
|
79
89
|
|
|
80
90
|
if(window.dbopfs?.ready){
|
|
81
91
|
ready();
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import waitForComponent from './WaitForComponent.js';
|
|
2
|
+
import {
|
|
3
|
+
arcaneEvents,
|
|
4
|
+
createArcaneEventSource
|
|
5
|
+
} from 'arcane-os/event-manager';
|
|
2
6
|
|
|
3
7
|
const DEFAULT_DELAY_MS=2_000;
|
|
4
8
|
const DEFAULT_MAX_REPORTS_PER_SESSION=10;
|
|
@@ -10,6 +14,19 @@ const MAX_DETAIL_LENGTH=8_000;
|
|
|
10
14
|
const HANDLER_MARKER=Symbol.for('arcane.global-errors.handler');
|
|
11
15
|
const DEVELOPER_MODAL_HREF=new URL('../components/modal.html?v=13',import.meta.url).href;
|
|
12
16
|
|
|
17
|
+
export const GLOBAL_ERROR_EVENT_TYPES=Object.freeze({
|
|
18
|
+
browserErrorCaptured:'arcane-error-captured',
|
|
19
|
+
unhandledRejectionCaptured:'arcane-unhandled-rejection-captured'
|
|
20
|
+
});
|
|
21
|
+
export const GLOBAL_ERROR_EVENT_CODES=Object.freeze({
|
|
22
|
+
browserErrorCaptured:'ARCANE_BROWSER_ERROR_CAPTURED',
|
|
23
|
+
unhandledRejectionCaptured:'ARCANE_UNHANDLED_REJECTION_CAPTURED'
|
|
24
|
+
});
|
|
25
|
+
export const GLOBAL_ERROR_REASONS=Object.freeze({
|
|
26
|
+
browserErrorCaptured:'browser-error-captured',
|
|
27
|
+
unhandledRejectionCaptured:'unhandled-promise-rejection-captured'
|
|
28
|
+
});
|
|
29
|
+
|
|
13
30
|
const MESSAGE_STYLE=[
|
|
14
31
|
'Write a simple plain-text email showing the error and, when the available details support it, a possible solution.',
|
|
15
32
|
'If loop_detected or error_storm_detected is true, put that warning first and clearly state that further notifications have been suppressed.',
|
|
@@ -374,6 +391,12 @@ async function presentDeveloperIncidentModal(target,incident,fingerprint){
|
|
|
374
391
|
}
|
|
375
392
|
|
|
376
393
|
class Errors {
|
|
394
|
+
#events;
|
|
395
|
+
|
|
396
|
+
#operationSequence=0;
|
|
397
|
+
|
|
398
|
+
#stopUserLoaded=null;
|
|
399
|
+
|
|
377
400
|
constructor(options={}) {
|
|
378
401
|
const target=options.target||globalThis.window;
|
|
379
402
|
if(!target||typeof target.addEventListener!=='function'){
|
|
@@ -384,6 +407,13 @@ class Errors {
|
|
|
384
407
|
return target.errors;
|
|
385
408
|
}
|
|
386
409
|
|
|
410
|
+
this.#events=createArcaneEventSource(this,{
|
|
411
|
+
source:'global-error-handler',
|
|
412
|
+
eventTypes:Object.freeze([
|
|
413
|
+
GLOBAL_ERROR_EVENT_TYPES.browserErrorCaptured,
|
|
414
|
+
GLOBAL_ERROR_EVENT_TYPES.unhandledRejectionCaptured
|
|
415
|
+
])
|
|
416
|
+
});
|
|
387
417
|
this[HANDLER_MARKER]=true;
|
|
388
418
|
this.target=target;
|
|
389
419
|
this.delayMs=options.delayMs??DEFAULT_DELAY_MS;
|
|
@@ -674,17 +704,15 @@ class Errors {
|
|
|
674
704
|
}
|
|
675
705
|
|
|
676
706
|
this.waitingForUser=true;
|
|
677
|
-
this.
|
|
707
|
+
this.#stopUserLoaded=arcaneEvents.subscribe(
|
|
678
708
|
'user-entity-loaded',
|
|
679
|
-
this.onUserLoaded
|
|
709
|
+
this.onUserLoaded,
|
|
710
|
+
{once:true}
|
|
680
711
|
);
|
|
681
712
|
}
|
|
682
713
|
|
|
683
714
|
onUserLoaded(){
|
|
684
|
-
this
|
|
685
|
-
'user-entity-loaded',
|
|
686
|
-
this.onUserLoaded
|
|
687
|
-
);
|
|
715
|
+
this.#stopUserLoaded=null;
|
|
688
716
|
this.waitingForUser=false;
|
|
689
717
|
|
|
690
718
|
const deferred=this.deferredDeveloperIncident;
|
|
@@ -754,6 +782,33 @@ class Errors {
|
|
|
754
782
|
return false;
|
|
755
783
|
}
|
|
756
784
|
|
|
785
|
+
const incidentKind=incident?.type==='unhandledrejection'
|
|
786
|
+
?'unhandled-promise-rejection'
|
|
787
|
+
:'browser-error';
|
|
788
|
+
const eventType=incidentKind==='unhandled-promise-rejection'
|
|
789
|
+
?GLOBAL_ERROR_EVENT_TYPES.unhandledRejectionCaptured
|
|
790
|
+
:GLOBAL_ERROR_EVENT_TYPES.browserErrorCaptured;
|
|
791
|
+
const code=incidentKind==='unhandled-promise-rejection'
|
|
792
|
+
?GLOBAL_ERROR_EVENT_CODES.unhandledRejectionCaptured
|
|
793
|
+
:GLOBAL_ERROR_EVENT_CODES.browserErrorCaptured;
|
|
794
|
+
const reason=incidentKind==='unhandled-promise-rejection'
|
|
795
|
+
?GLOBAL_ERROR_REASONS.unhandledRejectionCaptured
|
|
796
|
+
:GLOBAL_ERROR_REASONS.browserErrorCaptured;
|
|
797
|
+
this.#operationSequence+=1;
|
|
798
|
+
this.#events.dispatch(
|
|
799
|
+
eventType,
|
|
800
|
+
Object.freeze({...incident,code,fingerprint,reason}),
|
|
801
|
+
{
|
|
802
|
+
operationId:`global-error-handler-${this.#events.instanceId}-${this.#operationSequence.toString(36)}`,
|
|
803
|
+
publicDetail:Object.freeze({
|
|
804
|
+
id:fingerprint,
|
|
805
|
+
code,
|
|
806
|
+
kind:incidentKind,
|
|
807
|
+
reason
|
|
808
|
+
})
|
|
809
|
+
}
|
|
810
|
+
);
|
|
811
|
+
|
|
757
812
|
this.offerDeveloperIncident(incident,fingerprint);
|
|
758
813
|
|
|
759
814
|
if(this.circuitOpen){
|
|
@@ -976,7 +1031,8 @@ class Errors {
|
|
|
976
1031
|
this.destroyed=true;
|
|
977
1032
|
this.target.removeEventListener('error',this.onError,true);
|
|
978
1033
|
this.target.removeEventListener('unhandledrejection',this.onRejection,true);
|
|
979
|
-
this
|
|
1034
|
+
this.#stopUserLoaded?.();
|
|
1035
|
+
this.#stopUserLoaded=null;
|
|
980
1036
|
this.waitingForUser=false;
|
|
981
1037
|
this.deferredDeveloperIncident=null;
|
|
982
1038
|
|
|
@@ -1015,6 +1071,8 @@ class Errors {
|
|
|
1015
1071
|
this.target.errors=undefined;
|
|
1016
1072
|
}
|
|
1017
1073
|
}
|
|
1074
|
+
|
|
1075
|
+
this.#events.dispose();
|
|
1018
1076
|
}
|
|
1019
1077
|
}
|
|
1020
1078
|
|