arcane-os 0.2.0 → 0.2.2

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 (38) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.md +8 -8
  3. package/browser-runtime/ARCANE_SDK_BROWSER_RELEASE.json +5 -5
  4. package/browser-runtime/ai/browser-speech-providers.mjs +331 -25
  5. package/docs/architecture.md +2 -2
  6. package/docs/reference/README.md +79 -13
  7. package/docs/reference/ai/browser-speech.md +336 -0
  8. package/docs/reference/ai/browser-wasm.md +207 -82
  9. package/docs/reference/availability-and-normalization.md +30 -4
  10. package/docs/reference/behavioral-testing.md +4 -1
  11. package/docs/reference/cli.md +29 -10
  12. package/docs/reference/core/arcane-ai-contracts.md +43 -9
  13. package/docs/reference/inventory/package-api.json +110 -14
  14. package/docs/reference/inventory/runtime-components.json +19 -6
  15. package/docs/reference/inventory/runtime-modules.json +113 -9
  16. package/docs/reference/protocols.md +260 -38
  17. package/docs/reference/runtime-components.md +108 -15
  18. package/docs/reference/runtime-modules.md +422 -8
  19. package/docs/reference/sdk-api.md +626 -85
  20. package/package.json +1 -1
  21. package/runtime/ARCANE_RUNTIME_RELEASE.json +19 -19
  22. package/runtime/arcane/components/chat.html +288 -52
  23. package/runtime/arcane/components/speech.html +339 -15
  24. package/runtime/arcane/modules/AI.js +1245 -136
  25. package/runtime/arcane/modules/AIProviderRuntime.js +299 -30
  26. package/runtime/arcane/modules/AIRuntimeState.js +23 -4
  27. package/runtime/arcane/modules/ConfiguredAIChatSession.js +93 -8
  28. package/runtime/arcane/modules/DBOPFSDocumentLibrary.js +448 -24
  29. package/runtime/arcane/modules/LocalAIReadinessController.js +1 -1
  30. package/schemas/arcane-lock.schema.json +6 -4
  31. package/src/dev-server.mjs +29 -13
  32. package/src/doctor.mjs +1 -3
  33. package/src/import-map.mjs +134 -83
  34. package/src/packager/core.mjs +311 -39
  35. package/src/scaffold.mjs +45 -17
  36. package/src/templates/workspace-template.mjs +23 -4
  37. package/src/toolchain.mjs +10 -2
  38. package/src/workspace.mjs +177 -24
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arcane-os",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Arcane OS JavaScript SDK, project-local CLI, browser runtime, and repository-portable application packager.",
5
5
  "type": "module",
6
6
  "main": "./src/index.mjs",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "builder": "arcane-sdk-runtime-v1",
4
- "sdkVersion": "0.2.0",
4
+ "sdkVersion": "0.2.2",
5
5
  "source": {
6
6
  "authority": "sdk-canonical",
7
7
  "repository": "https://github.com/TheWizardNexus/arcane-os-sdk.git",
@@ -14,8 +14,8 @@
14
14
  }
15
15
  },
16
16
  "fileCount": 160,
17
- "totalBytes": 3549207,
18
- "contentSha256": "fa3bcddb5479817971a400049dbeff8431c6499d708e9a8b714176715c5a015a",
17
+ "totalBytes": 3643083,
18
+ "contentSha256": "0b09b1fd548152416f583d5e117197c7886b5a9e63071e43493577b4090e4a8c",
19
19
  "files": [
20
20
  {
21
21
  "path": "arcane/components/app-bar.html",
@@ -39,8 +39,8 @@
39
39
  },
40
40
  {
41
41
  "path": "arcane/components/chat.html",
42
- "bytes": 52132,
43
- "sha256": "42d4769a074e70b5c2768e850eaf29a25c93ee9e8734724b72b2ad585e19b2ca"
42
+ "bytes": 60478,
43
+ "sha256": "2f3a7f454c1e3da64c9eaeedb0869d49f04636b20ff59e5116a671e0d0a17973"
44
44
  },
45
45
  {
46
46
  "path": "arcane/components/conversation-view.html",
@@ -159,8 +159,8 @@
159
159
  },
160
160
  {
161
161
  "path": "arcane/components/speech.html",
162
- "bytes": 38217,
163
- "sha256": "61786685b7f4f257da77db2cbbe8230b379c56292ad635081f5b0763d5f48e1e"
162
+ "bytes": 49596,
163
+ "sha256": "2a8b2929d28699ab9011f358b09b5fd2d53a0703542b2fee5cda8334261c6c86"
164
164
  },
165
165
  {
166
166
  "path": "arcane/components/summary-strip.html",
@@ -374,8 +374,8 @@
374
374
  },
375
375
  {
376
376
  "path": "arcane/modules/AI.js",
377
- "bytes": 81061,
378
- "sha256": "8e34445ad394064dae970583147b635de81d878a6caebb8a60d64b34dbc9e658"
377
+ "bytes": 120607,
378
+ "sha256": "703a5342ed0c0758cccdfd8469fbae6d86e91de8c43ec01c893619daf97c2d34"
379
379
  },
380
380
  {
381
381
  "path": "arcane/modules/AIPreferenceRuntime.js",
@@ -389,8 +389,8 @@
389
389
  },
390
390
  {
391
391
  "path": "arcane/modules/AIProviderRuntime.js",
392
- "bytes": 83261,
393
- "sha256": "32aa65b464ea9a9317d47f382063fc052e1f47e63e6c65c28e58898e0ab4911a"
392
+ "bytes": 93507,
393
+ "sha256": "d1b33f678431128cefe6f43daabe177794e758c770394098871cf4ac726926b8"
394
394
  },
395
395
  {
396
396
  "path": "arcane/modules/AIResponseLength.js",
@@ -404,8 +404,8 @@
404
404
  },
405
405
  {
406
406
  "path": "arcane/modules/AIRuntimeState.js",
407
- "bytes": 24783,
408
- "sha256": "8b9bfaf208eb44455b581d8e700cec785044f81595dcf839dcdfa3e06ed0f5fa"
407
+ "bytes": 25379,
408
+ "sha256": "492162cec5e6a8b9e9b437833172905b2cb4e352ca1ef30104c65ec29cf3fa29"
409
409
  },
410
410
  {
411
411
  "path": "arcane/modules/AnsiText.js",
@@ -499,8 +499,8 @@
499
499
  },
500
500
  {
501
501
  "path": "arcane/modules/ConfiguredAIChatSession.js",
502
- "bytes": 21462,
503
- "sha256": "12e02b169be6867d82ef8be75bcb1c2b367e778609e8d065389ed109d896c4c8"
502
+ "bytes": 24501,
503
+ "sha256": "f95c3421bf07397e987f77fc3d02166cf780059dd3d6de90ceaded990752f51b"
504
504
  },
505
505
  {
506
506
  "path": "arcane/modules/ConversationActionItems.js",
@@ -534,8 +534,8 @@
534
534
  },
535
535
  {
536
536
  "path": "arcane/modules/DBOPFSDocumentLibrary.js",
537
- "bytes": 29977,
538
- "sha256": "4c551b5917d1577b8b46b08457cf762d7ab7548222abd6f4a72c7c7ab5c697b7"
537
+ "bytes": 50695,
538
+ "sha256": "2831470ce54c3393595bf068d60c2e774804cbd51dba7491ad734bbfbea7f32f"
539
539
  },
540
540
  {
541
541
  "path": "arcane/modules/DBOPFSWorker.js",
@@ -599,8 +599,8 @@
599
599
  },
600
600
  {
601
601
  "path": "arcane/modules/LocalAIReadinessController.js",
602
- "bytes": 4334,
603
- "sha256": "32d4ce2639557b89a283754a67435226777b56df696ce7a76231ec6c0274b25c"
602
+ "bytes": 4340,
603
+ "sha256": "f80d51d0ad499664f265b4f2d52a46ede521a78d56714fecb0e13b939b261007"
604
604
  },
605
605
  {
606
606
  "path": "arcane/modules/MD.js",
@@ -100,6 +100,7 @@
100
100
  }
101
101
 
102
102
  .chat_area[data-conversation-complete="true"] .conversation_timebox,
103
+ .chat_area[data-conversation-complete="true"] .ai_activation,
103
104
  .chat_area[data-conversation-complete="true"] .chat_input_area,
104
105
  .chat_area[data-conversation-complete="true"] > #speech,
105
106
  .chat_area[data-conversation-complete="true"] > .controls{
@@ -209,6 +210,63 @@
209
210
  box-shadow: var(--arcane-shadow-small, 0 .35rem 1rem var(--shadow-color));
210
211
  }
211
212
 
213
+ .ai_activation {
214
+ display:flex;
215
+ flex:0 0 auto;
216
+ box-sizing:border-box;
217
+ width:100%;
218
+ padding:var(--arcane-space-3, .75rem);
219
+ align-items:center;
220
+ justify-content:space-between;
221
+ gap:var(--arcane-space-3, .75rem);
222
+ border:1px solid var(--arcane-border, var(--border-color));
223
+ border-radius:var(--arcane-chat-radius);
224
+ background:var(--arcane-surface-raised, var(--modal-background));
225
+ color:var(--text-color);
226
+ }
227
+
228
+ .ai_activation[hidden] {
229
+ display:none;
230
+ }
231
+
232
+ .ai_activation_copy {
233
+ min-width:0;
234
+ }
235
+
236
+ .ai_activation_copy strong,
237
+ .ai_activation_copy span {
238
+ display:block;
239
+ overflow-wrap:anywhere;
240
+ }
241
+
242
+ .ai_activation_copy span {
243
+ margin-block-start:.22rem;
244
+ font-size:.9rem;
245
+ }
246
+
247
+ .ai_activation button {
248
+ flex:0 0 auto;
249
+ min-height:2.65rem;
250
+ padding:.55rem .8rem;
251
+ border:1px solid var(--arcane-action, var(--primary-color));
252
+ border-radius:var(--arcane-radius-medium, .7rem);
253
+ background:var(--arcane-action, var(--primary-color));
254
+ color:var(--arcane-action-text, var(--button-text-color, var(--text-color)));
255
+ font:inherit;
256
+ font-weight:600;
257
+ cursor:pointer;
258
+ }
259
+
260
+ .ai_activation button:focus-visible {
261
+ outline:.2rem solid var(--arcane-focus, var(--focus-color));
262
+ outline-offset:.18rem;
263
+ }
264
+
265
+ .ai_activation button:disabled {
266
+ cursor:not-allowed;
267
+ opacity:.65;
268
+ }
269
+
212
270
  .chat_input {
213
271
  box-sizing: border-box;
214
272
  width: 100%;
@@ -421,6 +479,20 @@
421
479
  >Starting the elapsed timer.</span>
422
480
  </section>
423
481
  <ul type='text' class='chat_output' id='chat_output' tabindex='-1' aria-label='Conversation transcript'></ul>
482
+ <section
483
+ id="ai_activation"
484
+ class="ai_activation"
485
+ role="status"
486
+ aria-live="polite"
487
+ aria-busy="false"
488
+ hidden
489
+ >
490
+ <span class="ai_activation_copy">
491
+ <strong id="ai_activation_title">Language model not active</strong>
492
+ <span id="ai_activation_status">Start the selected language model to enable chat.</span>
493
+ </span>
494
+ <button type="button" id="ai_activation_button">Start language model</button>
495
+ </section>
424
496
  <div class="chat_input_area">
425
497
  <textarea type='text' class='chat_input' id='chat_input'></textarea>
426
498
  <button type="button" id="chat_upload" class="chat_upload" title="Upload files" aria-label="Upload files">
@@ -624,11 +696,15 @@
624
696
  const conversationTimeboxPanel=shadowRoot.querySelector('#conversation_timebox');
625
697
  const conversationTimeboxElapsed=shadowRoot.querySelector('#conversation_timebox_elapsed');
626
698
  const conversationTimeboxStatus=shadowRoot.querySelector('#conversation_timebox_status');
699
+ const aiActivationPanel=shadowRoot.querySelector('#ai_activation');
700
+ const aiActivationTitle=shadowRoot.querySelector('#ai_activation_title');
701
+ const aiActivationStatus=shadowRoot.querySelector('#ai_activation_status');
702
+ const aiActivationButton=shadowRoot.querySelector('#ai_activation_button');
627
703
  const [
628
704
  {default:MD},
629
705
  {default:FileEntity},
630
706
  {ConversationSubmissionBarrier,requireConversationTimeboxDelivery},
631
- {subscribeAIRuntimeState}
707
+ {requestAIRuntimeIntent,subscribeAIRuntimeState}
632
708
  ]=await Promise.all([
633
709
  import('../modules/MD.js'),
634
710
  import('../entities/File.js'),
@@ -643,8 +719,7 @@
643
719
  ?speech.initialMuted
644
720
  :true;
645
721
  let initialSpeechMuteListenerInstalled=false;
646
- let pendingSpeechAvailability=null;
647
- let speechAvailabilityListenerInstalled=false;
722
+ let latestAIRuntimeRoles=null;
648
723
  const hostSubmissionBarrier=new ConversationSubmissionBarrier();
649
724
  const aiRuntimeStateAbortController=new AbortController();
650
725
  let programmaticSubmissionQueue=Promise.resolve(true);
@@ -663,6 +738,18 @@
663
738
  console.warn('html-import chat.html `host.languageChanged` method not implemented.\n\nThis can also be done by listening for the "chat-language-changed" event.\n\nIt is still best practice to implement the `.languageChanged` method atleast with an empty function.\n\n')
664
739
  }
665
740
 
741
+ host.requestAIActivation=host.requestAIActivation||function requestAIActivation(intent){
742
+ return requestAIRuntimeIntent(intent);
743
+ }
744
+
745
+ const aiActivationController=createAIActivationController({
746
+ host,
747
+ panel:aiActivationPanel,
748
+ title:aiActivationTitle,
749
+ status:aiActivationStatus,
750
+ button:aiActivationButton
751
+ });
752
+
666
753
  host.streamMessage=streamMessage;
667
754
  host.setMessageProgress=setMessageProgress;
668
755
  host.setAIAvailability=setAIAvailability;
@@ -822,36 +909,32 @@
822
909
  }
823
910
 
824
911
  function setAIAvailability(input={}){
825
- const explicitSpeechAvailability={};
826
912
  const availability={
827
- llm:Object.prototype.hasOwnProperty.call(input,'llm')
913
+ llm:hasSelectedAIRuntimeRole('llm')
914
+ ?latestAIRuntimeRoles.llm.state==='ready'
915
+ :Object.prototype.hasOwnProperty.call(input,'llm')
828
916
  ?Boolean(input.llm)
829
917
  :host.aiAvailability.llm,
830
- stt:Object.prototype.hasOwnProperty.call(input,'stt')
831
- ?Boolean(input.stt)
918
+ stt:latestAIRuntimeRoles
919
+ ?latestAIRuntimeRoles.stt.state==='ready'
832
920
  :host.aiAvailability.stt,
833
- tts:Object.prototype.hasOwnProperty.call(input,'tts')
834
- ?Boolean(input.tts)
921
+ tts:latestAIRuntimeRoles
922
+ ?latestAIRuntimeRoles.tts.state==='ready'
835
923
  :host.aiAvailability.tts
836
924
  };
837
925
 
838
- if(Object.prototype.hasOwnProperty.call(input,'stt')){
839
- explicitSpeechAvailability.stt=availability.stt;
840
- }
841
- if(Object.prototype.hasOwnProperty.call(input,'tts')){
842
- explicitSpeechAvailability.tts=availability.tts;
843
- }
844
926
  applyAIAvailability(availability);
845
- if(Object.keys(explicitSpeechAvailability).length){
846
- pendingSpeechAvailability={
847
- ...(pendingSpeechAvailability||{}),
848
- ...explicitSpeechAvailability
849
- };
850
- synchronizeSpeechCompatibilityAvailability();
851
- }
852
927
  return {...availability};
853
928
  }
854
929
 
930
+ function hasSelectedAIRuntimeRole(role){
931
+ const record=latestAIRuntimeRoles?.[role];
932
+ return typeof record?.providerId==='string'
933
+ &&record.providerId.length>0
934
+ &&typeof record?.modelId==='string'
935
+ &&record.modelId.length>0;
936
+ }
937
+
855
938
  function applyAIAvailability(availability){
856
939
  host.aiAvailability={...availability};
857
940
  send.disabled=host.conversationComplete||!availability.llm;
@@ -861,43 +944,195 @@
861
944
  send.setAttribute('aria-label',send.title);
862
945
  }
863
946
 
864
- function synchronizeAIRuntimeState(snapshot){
865
- applyAIAvailability({
866
- llm:snapshot.roles.llm.state==='ready',
867
- stt:snapshot.roles.stt.state==='ready',
868
- tts:snapshot.roles.tts.state==='ready'
869
- });
870
- }
947
+ function createAIActivationController({host,panel,title,status,button,EventClass=CustomEvent}){
948
+ let role=null;
949
+ let requestPending=false;
950
+ let requestError='';
951
+ let destroyed=false;
871
952
 
872
- function synchronizeSpeechCompatibilityAvailability(){
873
- if(!pendingSpeechAvailability){
874
- return;
953
+ function visibleError(error,fallback){
954
+ const message=typeof error?.message==='string'
955
+ ?error.message.trim()
956
+ :'';
957
+ return (message||fallback).slice(0,240);
875
958
  }
876
- if(speech.componentReady){
877
- const availability=pendingSpeechAvailability;
878
- pendingSpeechAvailability=null;
879
- speech.setAvailability?.({...availability});
880
- return;
959
+
960
+ function cancellation(error){
961
+ return error?.name==='AbortError'
962
+ ||['ARCANE_AI_REQUEST_ABORTED','AI_CHAT_ABORTED'].includes(error?.code);
881
963
  }
882
- if(speechAvailabilityListenerInstalled){
883
- return;
964
+
965
+ function selected(){
966
+ return typeof role?.providerId==='string'
967
+ &&role.providerId.length>0
968
+ &&typeof role?.modelId==='string'
969
+ &&role.modelId.length>0;
884
970
  }
885
971
 
886
- speechAvailabilityListenerInstalled=true;
887
- speech.addEventListener(
888
- 'speech-ready',
889
- applyPendingSpeechAvailability,
890
- {once:true}
891
- );
892
- }
972
+ function progressMessage(){
973
+ if(!role?.progress){
974
+ return 'The selected language model is loading.';
975
+ }
976
+ const progress=role.progress;
977
+ const amount=progress.total===null
978
+ ?`${progress.completed} ${progress.unit}`
979
+ :`${progress.completed} of ${progress.total} ${progress.unit}`;
980
+ return `${progress.phase}, ${amount}${progress.heartbeat?', active heartbeat':''}.`;
981
+ }
982
+
983
+ function render(){
984
+ const visible=selected()&&role.state!=='ready';
985
+ panel.hidden=!visible;
986
+ panel.setAttribute(
987
+ 'aria-busy',
988
+ String(requestPending||['loading','unloading'].includes(role?.state))
989
+ );
990
+ if(!visible){
991
+ return;
992
+ }
993
+ if(requestPending){
994
+ title.textContent='Requesting language model activation';
995
+ status.textContent='Waiting for the selected language model runtime.';
996
+ button.textContent='Requesting…';
997
+ button.disabled=true;
998
+ return;
999
+ }
1000
+ if(role.state==='loading'){
1001
+ title.textContent='Starting language model';
1002
+ status.textContent=progressMessage();
1003
+ button.textContent='Cancel loading';
1004
+ button.disabled=false;
1005
+ return;
1006
+ }
1007
+ if(role.state==='unloading'){
1008
+ title.textContent='Canceling language model load';
1009
+ status.textContent='Waiting for the selected language model runtime to stop.';
1010
+ button.textContent='Canceling…';
1011
+ button.disabled=true;
1012
+ return;
1013
+ }
1014
+ if(role.state==='error'||requestError){
1015
+ title.textContent='Language model activation failed';
1016
+ status.textContent=requestError||visibleError(
1017
+ role.error,
1018
+ 'The selected language model could not start.'
1019
+ );
1020
+ button.textContent='Try again';
1021
+ button.disabled=false;
1022
+ return;
1023
+ }
1024
+ if(role.state==='unloaded'){
1025
+ title.textContent='Language model not active';
1026
+ status.textContent='Start the selected language model to enable chat.';
1027
+ button.textContent='Start language model';
1028
+ button.disabled=false;
1029
+ return;
1030
+ }
1031
+ title.textContent='Language model unavailable';
1032
+ status.textContent='The selected language model cannot be activated in this state.';
1033
+ button.textContent='Unavailable';
1034
+ button.disabled=true;
1035
+ }
893
1036
 
894
- function applyPendingSpeechAvailability(){
895
- speechAvailabilityListenerInstalled=false;
896
- if(pendingSpeechAvailability){
897
- const availability=pendingSpeechAvailability;
898
- pendingSpeechAvailability=null;
899
- speech.setAvailability?.({...availability});
1037
+ async function request(action){
1038
+ if(destroyed||requestPending||!selected()){
1039
+ return false;
1040
+ }
1041
+ const permitted=action==='load'
1042
+ ?['unloaded','error'].includes(role.state)
1043
+ :action==='unload'&&role.state==='loading';
1044
+ if(!permitted){
1045
+ return false;
1046
+ }
1047
+ requestError='';
1048
+ const intent=Object.freeze({role:'llm',action,reason:'user'});
1049
+ const request=Object.freeze({intent,state:role});
1050
+ const activationEvent=new EventClass(
1051
+ 'chat-ai-activation-request',
1052
+ {
1053
+ bubbles:true,
1054
+ composed:true,
1055
+ cancelable:true,
1056
+ detail:request
1057
+ }
1058
+ );
1059
+ if(!host.dispatchEvent(activationEvent)){
1060
+ render();
1061
+ return false;
1062
+ }
1063
+ requestPending=true;
1064
+ render();
1065
+ try{
1066
+ await host.requestAIActivation(intent);
1067
+ return true;
1068
+ }catch(error){
1069
+ if(action==='load'
1070
+ &&cancellation(error)
1071
+ &&['unloaded','unloading'].includes(role?.state)){
1072
+ return false;
1073
+ }
1074
+ const message=visibleError(
1075
+ error,
1076
+ `The language model ${action} request failed.`
1077
+ );
1078
+ requestError=message;
1079
+ host.dispatchEvent(
1080
+ new EventClass(
1081
+ 'chat-ai-activation-error',
1082
+ {
1083
+ bubbles:true,
1084
+ composed:true,
1085
+ detail:Object.freeze({request,error,message})
1086
+ }
1087
+ )
1088
+ );
1089
+ return false;
1090
+ }finally{
1091
+ requestPending=false;
1092
+ render();
1093
+ }
1094
+ }
1095
+
1096
+ function activateSelectedAI(){
1097
+ const action=role?.state==='loading'?'unload':'load';
1098
+ void request(action);
1099
+ }
1100
+
1101
+ function synchronize(nextRole){
1102
+ if(destroyed){
1103
+ return;
1104
+ }
1105
+ if(role?.state!==nextRole.state
1106
+ ||role?.providerId!==nextRole.providerId
1107
+ ||role?.modelId!==nextRole.modelId){
1108
+ requestError='';
1109
+ requestPending=false;
1110
+ }
1111
+ role=nextRole;
1112
+ render();
900
1113
  }
1114
+
1115
+ function destroy(){
1116
+ if(destroyed){
1117
+ return;
1118
+ }
1119
+ destroyed=true;
1120
+ button.removeEventListener('click',activateSelectedAI);
1121
+ panel.hidden=true;
1122
+ }
1123
+
1124
+ button.addEventListener('click',activateSelectedAI);
1125
+ return Object.freeze({request,synchronize,destroy});
1126
+ }
1127
+
1128
+ function synchronizeAIRuntimeState(snapshot){
1129
+ latestAIRuntimeRoles=snapshot.roles;
1130
+ aiActivationController.synchronize(snapshot.roles.llm);
1131
+ applyAIAvailability({
1132
+ llm:snapshot.roles.llm.state==='ready',
1133
+ stt:snapshot.roles.stt.state==='ready',
1134
+ tts:snapshot.roles.tts.state==='ready'
1135
+ });
901
1136
  }
902
1137
 
903
1138
  function formatConversationTime(milliseconds=0){
@@ -1432,6 +1667,7 @@
1432
1667
 
1433
1668
  function destroy(){
1434
1669
  aiRuntimeStateAbortController.abort();
1670
+ aiActivationController.destroy();
1435
1671
  window.removeEventListener('pagehide',destroy);
1436
1672
  speech.destroy?.();
1437
1673
  host.ready=false;