arcane-os 0.2.3 → 0.3.1
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 +30 -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 +3242 -391
- package/browser-runtime/ai/browser-speech-providers.mjs +1209 -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 +249 -32
- package/browser-runtime/ai/speech-worker-runtime.mjs +2322 -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 +18 -20
- package/docs/reference/ai/browser-speech-package-authority.json +835 -0
- package/docs/reference/ai/browser-speech.md +1205 -246
- package/docs/reference/ai/browser-wasm.md +18 -7
- package/docs/reference/availability-and-normalization.md +2 -2
- package/docs/reference/behavioral-testing.md +28 -5
- package/docs/reference/cli.md +115 -7
- 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 +86 -37
- package/docs/reference/inventory/runtime-modules.json +126 -49
- package/docs/reference/mail.md +316 -0
- package/docs/reference/protocols.md +185 -33
- package/docs/reference/runtime-components.md +192 -80
- package/docs/reference/runtime-modules.md +589 -70
- package/docs/reference/sdk-api.md +1017 -24
- 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 +453 -131
- 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 +244 -72
- 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 +2117 -130
- package/runtime/arcane/modules/AIProviderRuntime.js +756 -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 +235 -37
- 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 +130 -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-runtime.mjs +804 -22
- package/src/workspace.mjs +1 -1
|
@@ -1,5 +1,79 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createArcaneEventSource,
|
|
3
|
+
projectArcaneDOMEvent
|
|
4
|
+
} from 'arcane-os/event-manager';
|
|
5
|
+
|
|
1
6
|
const own=(value,key)=>Object.prototype.hasOwnProperty.call(value,key);
|
|
2
7
|
|
|
8
|
+
const STT_ACTIVATION_EVENT_TYPES=Object.freeze({
|
|
9
|
+
error:'speech-stt-activation-error',
|
|
10
|
+
request:'speech-stt-activation-request'
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const STT_ACTIVATION_ERROR_CODES=Object.freeze({
|
|
14
|
+
buttonInvalid:'ARCANE_STT_ACTIVATION_BUTTON_INVALID',
|
|
15
|
+
buttonListenerFailed:'ARCANE_STT_ACTIVATION_BUTTON_LISTENER_FAILED',
|
|
16
|
+
domProjectionUnavailable:'ARCANE_STT_ACTIVATION_DOM_PROJECTION_UNAVAILABLE',
|
|
17
|
+
eventClassInvalid:'ARCANE_STT_ACTIVATION_EVENT_CLASS_INVALID',
|
|
18
|
+
eventSourceInvalid:'ARCANE_STT_ACTIVATION_EVENT_SOURCE_INVALID',
|
|
19
|
+
hostInvalid:'ARCANE_STT_ACTIVATION_HOST_INVALID',
|
|
20
|
+
onChangeInvalid:'ARCANE_STT_ACTIVATION_ON_CHANGE_INVALID',
|
|
21
|
+
presentationCallbackFailed:'ARCANE_STT_ACTIVATION_PRESENTATION_CALLBACK_FAILED',
|
|
22
|
+
requestRejected:'ARCANE_STT_ACTIVATION_REQUEST_REJECTED'
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const STT_ACTIVATION_REASONS=Object.freeze({
|
|
26
|
+
explicitRequest:'explicit-request',
|
|
27
|
+
presentationCallbackThrew:'activation-presentation-callback-threw',
|
|
28
|
+
requestRejected:'activation-request-rejected'
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
function codedError(message,code,ErrorClass=Error,cause){
|
|
32
|
+
const error=new ErrorClass(message);
|
|
33
|
+
error.code=code;
|
|
34
|
+
if(cause!==undefined){
|
|
35
|
+
error.cause=cause;
|
|
36
|
+
}
|
|
37
|
+
return error;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function projectSTTActivationEvent(
|
|
41
|
+
host,
|
|
42
|
+
occurrence,
|
|
43
|
+
EventClass,
|
|
44
|
+
options={}
|
|
45
|
+
){
|
|
46
|
+
if(typeof globalThis.CustomEvent!=='function'){
|
|
47
|
+
throw codedError(
|
|
48
|
+
'The STT activation DOM compatibility projection requires CustomEvent.',
|
|
49
|
+
STT_ACTIVATION_ERROR_CODES.domProjectionUnavailable
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
if(EventClass===globalThis.CustomEvent){
|
|
53
|
+
return projectArcaneDOMEvent(host,occurrence,options);
|
|
54
|
+
}
|
|
55
|
+
const compatibilityTarget={
|
|
56
|
+
dispatchEvent(projectedEvent){
|
|
57
|
+
const event=new EventClass(
|
|
58
|
+
projectedEvent.type,
|
|
59
|
+
{
|
|
60
|
+
detail:projectedEvent.detail,
|
|
61
|
+
bubbles:projectedEvent.bubbles,
|
|
62
|
+
composed:projectedEvent.composed,
|
|
63
|
+
cancelable:projectedEvent.cancelable
|
|
64
|
+
}
|
|
65
|
+
);
|
|
66
|
+
const accepted=host.dispatchEvent(event)!==false
|
|
67
|
+
&&!event.defaultPrevented;
|
|
68
|
+
if(!accepted&&projectedEvent.cancelable){
|
|
69
|
+
projectedEvent.preventDefault();
|
|
70
|
+
}
|
|
71
|
+
return accepted;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
return projectArcaneDOMEvent(compatibilityTarget,occurrence,options);
|
|
75
|
+
}
|
|
76
|
+
|
|
3
77
|
function record(value,label){
|
|
4
78
|
if(value===undefined){
|
|
5
79
|
return {};
|
|
@@ -331,12 +405,66 @@ function createSTTActivationController({
|
|
|
331
405
|
host,
|
|
332
406
|
button,
|
|
333
407
|
onChange,
|
|
334
|
-
EventClass=CustomEvent
|
|
408
|
+
EventClass=globalThis.CustomEvent,
|
|
409
|
+
eventSource=null
|
|
335
410
|
}){
|
|
411
|
+
if(!host||typeof host.dispatchEvent!=='function'
|
|
412
|
+
||typeof host.requestSTTActivation!=='function'){
|
|
413
|
+
throw codedError(
|
|
414
|
+
'The STT activation host must provide dispatchEvent() and requestSTTActivation().',
|
|
415
|
+
STT_ACTIVATION_ERROR_CODES.hostInvalid,
|
|
416
|
+
TypeError
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
if(typeof EventClass!=='function'){
|
|
420
|
+
throw codedError(
|
|
421
|
+
'The STT activation event class must be a constructor.',
|
|
422
|
+
STT_ACTIVATION_ERROR_CODES.eventClassInvalid,
|
|
423
|
+
TypeError
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
if(!button
|
|
427
|
+
||typeof button.addEventListener!=='function'
|
|
428
|
+
||typeof button.removeEventListener!=='function'){
|
|
429
|
+
throw codedError(
|
|
430
|
+
'The STT activation button must provide addEventListener() and removeEventListener().',
|
|
431
|
+
STT_ACTIVATION_ERROR_CODES.buttonInvalid,
|
|
432
|
+
TypeError
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
if(typeof onChange!=='function'){
|
|
436
|
+
throw codedError(
|
|
437
|
+
'The STT activation onChange callback must be a function.',
|
|
438
|
+
STT_ACTIVATION_ERROR_CODES.onChangeInvalid,
|
|
439
|
+
TypeError
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
if(eventSource!==null
|
|
443
|
+
&&(
|
|
444
|
+
typeof eventSource!=='object'
|
|
445
|
+
||typeof eventSource.dispatch!=='function'
|
|
446
|
+
||typeof eventSource.dispose!=='function'
|
|
447
|
+
||typeof eventSource.instanceId!=='string'
|
|
448
|
+
)){
|
|
449
|
+
throw codedError(
|
|
450
|
+
'The STT activation event source must be a compatible Arcane event source.',
|
|
451
|
+
STT_ACTIVATION_ERROR_CODES.eventSourceInvalid,
|
|
452
|
+
TypeError
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
const ownsEventSource=eventSource===null;
|
|
456
|
+
const events=eventSource||createArcaneEventSource(
|
|
457
|
+
host,
|
|
458
|
+
{
|
|
459
|
+
source:'arcane.module.component-contracts.stt-activation',
|
|
460
|
+
eventTypes:Object.values(STT_ACTIVATION_EVENT_TYPES)
|
|
461
|
+
}
|
|
462
|
+
);
|
|
336
463
|
let role=null;
|
|
337
464
|
let requestPending=false;
|
|
338
465
|
let requestError='';
|
|
339
466
|
let requestGeneration=0;
|
|
467
|
+
let operationSequence=0;
|
|
340
468
|
let destroyed=false;
|
|
341
469
|
|
|
342
470
|
function visibleError(error,fallback){
|
|
@@ -468,34 +596,51 @@ function createSTTActivationController({
|
|
|
468
596
|
}
|
|
469
597
|
);
|
|
470
598
|
const activationRequest=Object.freeze({intent,state:role});
|
|
471
|
-
const
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
bubbles:true,
|
|
475
|
-
composed:true,
|
|
476
|
-
cancelable:true,
|
|
477
|
-
detail:activationRequest
|
|
478
|
-
}
|
|
479
|
-
);
|
|
599
|
+
const operationId=
|
|
600
|
+
`${events.instanceId}:stt-activation:${(++operationSequence).toString(36)}`;
|
|
601
|
+
let requestInvoked=false;
|
|
480
602
|
requestPending=true;
|
|
481
|
-
const accepted=host.dispatchEvent(activationEvent);
|
|
482
|
-
if(!accepted
|
|
483
|
-
||destroyed
|
|
484
|
-
||generation!==requestGeneration
|
|
485
|
-
||action()!==nextAction){
|
|
486
|
-
if(!destroyed&&generation===requestGeneration){
|
|
487
|
-
requestPending=false;
|
|
488
|
-
onChange();
|
|
489
|
-
}
|
|
490
|
-
return false;
|
|
491
|
-
}
|
|
492
603
|
try{
|
|
604
|
+
const publication=events.dispatch(
|
|
605
|
+
STT_ACTIVATION_EVENT_TYPES.request,
|
|
606
|
+
activationRequest,
|
|
607
|
+
{
|
|
608
|
+
cancelable:true,
|
|
609
|
+
operationId,
|
|
610
|
+
publicDetail:{
|
|
611
|
+
role:'stt',
|
|
612
|
+
action:nextAction,
|
|
613
|
+
reason:STT_ACTIVATION_REASONS.explicitRequest,
|
|
614
|
+
state:role.state,
|
|
615
|
+
roleOperationId:typeof role?.operationId==='string'
|
|
616
|
+
?role.operationId
|
|
617
|
+
:null
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
);
|
|
621
|
+
if(!publication.accepted
|
|
622
|
+
||!projectSTTActivationEvent(
|
|
623
|
+
host,
|
|
624
|
+
publication.occurrence,
|
|
625
|
+
EventClass,
|
|
626
|
+
{
|
|
627
|
+
bubbles:true,
|
|
628
|
+
composed:true,
|
|
629
|
+
cancelable:true
|
|
630
|
+
}
|
|
631
|
+
)
|
|
632
|
+
||destroyed
|
|
633
|
+
||generation!==requestGeneration
|
|
634
|
+
||action()!==nextAction){
|
|
635
|
+
return false;
|
|
636
|
+
}
|
|
493
637
|
onChange();
|
|
494
638
|
if(destroyed
|
|
495
639
|
||generation!==requestGeneration
|
|
496
640
|
||action()!==nextAction){
|
|
497
641
|
return false;
|
|
498
642
|
}
|
|
643
|
+
requestInvoked=true;
|
|
499
644
|
await host.requestSTTActivation(intent);
|
|
500
645
|
if(destroyed||generation!==requestGeneration){
|
|
501
646
|
return false;
|
|
@@ -515,21 +660,42 @@ function createSTTActivationController({
|
|
|
515
660
|
`The transcription ${nextAction} request failed.`
|
|
516
661
|
);
|
|
517
662
|
requestError=message;
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
663
|
+
const code=requestInvoked
|
|
664
|
+
?STT_ACTIVATION_ERROR_CODES.requestRejected
|
|
665
|
+
:STT_ACTIVATION_ERROR_CODES.presentationCallbackFailed;
|
|
666
|
+
const reason=requestInvoked
|
|
667
|
+
?STT_ACTIVATION_REASONS.requestRejected
|
|
668
|
+
:STT_ACTIVATION_REASONS.presentationCallbackThrew;
|
|
669
|
+
const errorPublication=events.dispatch(
|
|
670
|
+
STT_ACTIVATION_EVENT_TYPES.error,
|
|
671
|
+
Object.freeze(
|
|
521
672
|
{
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
{
|
|
526
|
-
request:activationRequest,
|
|
527
|
-
error,
|
|
528
|
-
message
|
|
529
|
-
}
|
|
530
|
-
)
|
|
673
|
+
request:activationRequest,
|
|
674
|
+
error,
|
|
675
|
+
message
|
|
531
676
|
}
|
|
532
|
-
)
|
|
677
|
+
),
|
|
678
|
+
{
|
|
679
|
+
operationId,
|
|
680
|
+
publicDetail:{
|
|
681
|
+
role:'stt',
|
|
682
|
+
action:nextAction,
|
|
683
|
+
code,
|
|
684
|
+
reason,
|
|
685
|
+
causeCode:typeof error?.code==='string'
|
|
686
|
+
?error.code
|
|
687
|
+
:null
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
);
|
|
691
|
+
projectSTTActivationEvent(
|
|
692
|
+
host,
|
|
693
|
+
errorPublication.occurrence,
|
|
694
|
+
EventClass,
|
|
695
|
+
{
|
|
696
|
+
bubbles:true,
|
|
697
|
+
composed:true
|
|
698
|
+
}
|
|
533
699
|
);
|
|
534
700
|
return false;
|
|
535
701
|
}finally{
|
|
@@ -547,7 +713,11 @@ function createSTTActivationController({
|
|
|
547
713
|
function activateSelectedSTT(){
|
|
548
714
|
const nextAction=action();
|
|
549
715
|
if(nextAction){
|
|
550
|
-
void request(nextAction)
|
|
716
|
+
void request(nextAction).catch(
|
|
717
|
+
function reportSTTActivationRequestFailure(error){
|
|
718
|
+
console.error('The STT activation request could not settle.',error);
|
|
719
|
+
}
|
|
720
|
+
);
|
|
551
721
|
}
|
|
552
722
|
}
|
|
553
723
|
|
|
@@ -575,10 +745,35 @@ function createSTTActivationController({
|
|
|
575
745
|
destroyed=true;
|
|
576
746
|
requestGeneration+=1;
|
|
577
747
|
requestPending=false;
|
|
578
|
-
|
|
748
|
+
try{
|
|
749
|
+
button.removeEventListener('click',activateSelectedSTT);
|
|
750
|
+
}catch(error){
|
|
751
|
+
throw codedError(
|
|
752
|
+
'The STT activation button listener could not be removed.',
|
|
753
|
+
STT_ACTIVATION_ERROR_CODES.buttonListenerFailed,
|
|
754
|
+
Error,
|
|
755
|
+
error
|
|
756
|
+
);
|
|
757
|
+
}finally{
|
|
758
|
+
if(ownsEventSource){
|
|
759
|
+
events.dispose();
|
|
760
|
+
}
|
|
761
|
+
}
|
|
579
762
|
}
|
|
580
763
|
|
|
581
|
-
|
|
764
|
+
try{
|
|
765
|
+
button.addEventListener('click',activateSelectedSTT);
|
|
766
|
+
}catch(error){
|
|
767
|
+
if(ownsEventSource){
|
|
768
|
+
events.dispose();
|
|
769
|
+
}
|
|
770
|
+
throw codedError(
|
|
771
|
+
'The STT activation button listener could not be installed.',
|
|
772
|
+
STT_ACTIVATION_ERROR_CODES.buttonListenerFailed,
|
|
773
|
+
Error,
|
|
774
|
+
error
|
|
775
|
+
);
|
|
776
|
+
}
|
|
582
777
|
return Object.freeze(
|
|
583
778
|
{
|
|
584
779
|
get action(){return action();},
|
|
@@ -841,6 +1036,9 @@ export {
|
|
|
841
1036
|
DASHBOARD_LABELS,
|
|
842
1037
|
MARKDOWN_FORMATS,
|
|
843
1038
|
MARKDOWN_LABELS,
|
|
1039
|
+
STT_ACTIVATION_ERROR_CODES,
|
|
1040
|
+
STT_ACTIVATION_EVENT_TYPES,
|
|
1041
|
+
STT_ACTIVATION_REASONS,
|
|
844
1042
|
VOICE_LABELS,
|
|
845
1043
|
VOICE_MESSAGES,
|
|
846
1044
|
appendTranscription,
|
|
@@ -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
|
|