arcane-os 0.3.4 → 0.3.5

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 (35) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +7 -7
  3. package/browser-runtime/ai/browser-wasm-llm-provider.mjs +315 -119
  4. package/browser-runtime/ai/model-controller.mjs +439 -95
  5. package/package.json +1 -1
  6. package/runtime/arcane/components/assistant-panel.html +2 -1
  7. package/runtime/arcane/components/chat.html +466 -206
  8. package/runtime/arcane/components/speech.html +33 -13
  9. package/runtime/arcane/components/voice-transcription.html +27 -3
  10. package/runtime/arcane/entities/Chat.js +165 -97
  11. package/runtime/arcane/entities/IntentEnvelope.js +52 -118
  12. package/runtime/arcane/entities/TWiNPolicyDecision.js +33 -130
  13. package/runtime/arcane/entities/User.js +38 -44
  14. package/runtime/arcane/modules/AI.js +569 -302
  15. package/runtime/arcane/modules/AIProviderRuntime.js +765 -135
  16. package/runtime/arcane/modules/AIRuntimeState.js +22 -24
  17. package/runtime/arcane/modules/ApiModelDatabase.js +54 -48
  18. package/runtime/arcane/modules/CaseEvidenceIndexer.js +6 -10
  19. package/runtime/arcane/modules/CommunicationHub.js +90 -94
  20. package/runtime/arcane/modules/ComponentContracts.js +34 -9
  21. package/runtime/arcane/modules/ConfiguredAIChatSession.js +77 -77
  22. package/runtime/arcane/modules/ConversationTimebox.js +76 -104
  23. package/runtime/arcane/modules/DBLS.js +14 -12
  24. package/runtime/arcane/modules/DBOPFS.js +20 -15
  25. package/runtime/arcane/modules/Errors.js +196 -436
  26. package/runtime/arcane/modules/HTMLImport.js +49 -32
  27. package/runtime/arcane/modules/Ollama.js +16 -14
  28. package/runtime/arcane/modules/PersistentAIChatSession.js +174 -93
  29. package/runtime/arcane/modules/RecordReviewStore.js +40 -35
  30. package/runtime/arcane/modules/TerminalClient.js +12 -14
  31. package/runtime/arcane/modules/ThemeBootstrap.js +7 -7
  32. package/runtime/arcane/modules/ThemeManager.js +5 -5
  33. package/runtime/arcane/modules/TimeGuard.js +5 -65
  34. package/runtime/arcane/modules/WaitForComponent.js +43 -40
  35. package/src/installed-sdk-runtime.mjs +11 -1
@@ -65,6 +65,15 @@ function createComponentFragment(html,resolvedHref){
65
65
  return template.content;
66
66
  }
67
67
 
68
+ function resolveRelativeDynamicImports(source,baseHref){
69
+ return source.replace(
70
+ /\bimport\s*\(\s*(['"])(\.{1,2}\/[^'"]+)\1\s*\)/gu,
71
+ function resolveDynamicImport(_match,_quote,specifier){
72
+ return `import(${JSON.stringify(new URL(specifier,baseHref).href)})`;
73
+ }
74
+ );
75
+ }
76
+
68
77
  function samePropertyDescriptor(left,right){
69
78
  if(!left||!right)return left===right;
70
79
  return left.configurable===right.configurable
@@ -77,7 +86,7 @@ function samePropertyDescriptor(left,right){
77
86
 
78
87
  class HTMLImport extends HTMLElement {
79
88
  #connectionGeneration=0;
80
- #eventOwner=Object.freeze({kind:'html-import-lifecycle-owner'});
89
+ #eventOwner={kind:'html-import-lifecycle-owner'};
81
90
  #events;
82
91
  #importedDestroy=null;
83
92
  #loadController=null;
@@ -93,10 +102,10 @@ class HTMLImport extends HTMLElement {
93
102
  #createEvents(){
94
103
  this.#events=createArcaneEventSource(this.#eventOwner,{
95
104
  source:'html-import',
96
- eventTypes:Object.freeze([
105
+ eventTypes:[
97
106
  'html-import-error',
98
107
  'html-import-ready'
99
- ])
108
+ ]
100
109
  });
101
110
  return this.#events;
102
111
  }
@@ -128,17 +137,11 @@ class HTMLImport extends HTMLElement {
128
137
  }
129
138
 
130
139
  try{
131
- const baseURL=new URL(document.baseURI);
132
- const resolvedURL=new URL(href,baseURL);
140
+ const resolvedURL=new URL(href,document.baseURI);
133
141
  resolvedHref=resolvedURL.href;
134
- if(resolvedURL.origin!==baseURL.origin){
135
- throw new Error('HTML imports must use a same-origin URL.');
136
- }
137
142
  const response=await fetch(resolvedURL.href,{
138
143
  cache:'default',
139
- credentials:'same-origin',
140
144
  method:'GET',
141
- redirect:'error',
142
145
  signal:controller.signal
143
146
  });
144
147
  if(!this.#isCurrentConnection(generation,controller))return;
@@ -149,19 +152,19 @@ class HTMLImport extends HTMLElement {
149
152
  return;
150
153
  }
151
154
  console.error('Error loading HTML component:',err);
152
- const detail=Object.freeze({
155
+ const detail={
153
156
  code:'HTML_IMPORT_FAILED',
154
157
  href,
155
158
  message:'The component could not be loaded.',
156
159
  reason:'component-import-rejected',
157
160
  resolvedHref
158
- });
161
+ };
159
162
  const {occurrence}=this.#events.dispatch(
160
163
  'html-import-error',
161
164
  detail,
162
165
  {
163
166
  operationId,
164
- publicDetail:Object.freeze({code:detail.code,reason:detail.reason})
167
+ publicDetail:{code:detail.code,reason:detail.reason}
165
168
  }
166
169
  );
167
170
  projectArcaneDOMEvent(this,occurrence,{
@@ -200,18 +203,18 @@ class HTMLImport extends HTMLElement {
200
203
  #reportImportedDestroyError(error,{events,href,operationId}){
201
204
  console.error('Error destroying imported HTML component:',error);
202
205
  if(!events||events.disposed)return;
203
- const detail=Object.freeze({
206
+ const detail={
204
207
  code:'HTML_IMPORT_IMPORTED_COMPONENT_DESTROY_REJECTED',
205
208
  href,
206
209
  message:'The imported component could not be destroyed.',
207
210
  reason:'imported-component-destroy-rejected'
208
- });
211
+ };
209
212
  const {occurrence}=events.dispatch(
210
213
  'html-import-error',
211
214
  detail,
212
215
  {
213
216
  operationId,
214
- publicDetail:Object.freeze({code:detail.code,reason:detail.reason})
217
+ publicDetail:{code:detail.code,reason:detail.reason}
215
218
  }
216
219
  );
217
220
  projectArcaneDOMEvent(this,occurrence,{
@@ -272,9 +275,10 @@ class HTMLImport extends HTMLElement {
272
275
  if(!this.#isCurrentConnection(generation,controller))return false;
273
276
  await this.#destroyImportedHost();
274
277
  if(!this.#isCurrentConnection(generation,controller))return false;
275
- this.shadowRoot.replaceChildren(createComponentFragment(html,resolvedHref));
278
+ const contentBaseHref=response.url||resolvedHref;
279
+ this.shadowRoot.replaceChildren(createComponentFragment(html,contentBaseHref));
276
280
 
277
- await this.#executeScripts();
281
+ await this.#executeScripts(contentBaseHref,generation,controller);
278
282
  if(!this.#isCurrentConnection(generation,controller)){
279
283
  await this.#destroyImportedHost();
280
284
  return false;
@@ -283,8 +287,8 @@ class HTMLImport extends HTMLElement {
283
287
  this.ready=true;
284
288
  const {occurrence}=this.#events.dispatch(
285
289
  'html-import-ready',
286
- Object.freeze({href,resolvedHref}),
287
- {operationId,publicDetail:Object.freeze({ready:true})}
290
+ {href,resolvedHref},
291
+ {operationId,publicDetail:{ready:true}}
288
292
  );
289
293
  projectArcaneDOMEvent(this,occurrence,{
290
294
  bubbles:true,
@@ -293,36 +297,49 @@ class HTMLImport extends HTMLElement {
293
297
  return true;
294
298
  }
295
299
 
296
- async #executeScripts() {
300
+ async #executeScripts(contentBaseHref,generation,controller) {
297
301
  const scripts = Array.from(this.shadowRoot.querySelectorAll('script'));
298
302
  const previousDescriptor=Object.getOwnPropertyDescriptor(this,'destroy')??null;
299
303
  const previousDestroy=this.destroy;
300
304
  try{
301
305
  for(const script of scripts){
302
- if (script.src) {
303
- console.error('ONLY INLINE SCRIPTS SUPPORTED AT THIS TIME FOR SECURITY REASONS');
304
- console.warn('script src path will need to be limited to ./{text}.js, ../{text}.js), or acceptable sub folders. This can be complex.');
305
- return;
306
- //newScript.src = script.src;
306
+ if(!this.#isCurrentConnection(generation,controller))return;
307
+ let source=script.textContent||'';
308
+ let sourceBaseHref=contentBaseHref;
309
+ const scriptSource=script.getAttribute('src');
310
+ if(scriptSource!==null){
311
+ const requestedScriptHref=new URL(scriptSource,contentBaseHref).href;
312
+ const response=await fetch(requestedScriptHref,{
313
+ cache:'default',
314
+ method:'GET',
315
+ signal:controller.signal
316
+ });
317
+ if(!response.ok){
318
+ throw new Error(`HTML import script request failed with status ${response.status}.`);
319
+ }
320
+ source=await response.text();
321
+ sourceBaseHref=response.url||requestedScriptHref;
322
+ if(!this.#isCurrentConnection(generation,controller))return;
307
323
  }
308
324
 
309
- const source=(script.textContent||'').replace(
310
- /\bimport\s*\(\s*(['"])(\.{1,2}\/[^'"]+)\1\s*\)/g,
311
- (_match,_quote,specifier)=>{
312
- return `import(${JSON.stringify(new URL(specifier,import.meta.url).href)})`;
313
- }
325
+ const executableSource=resolveRelativeDynamicImports(
326
+ source,
327
+ sourceBaseHref
314
328
  );
315
329
  const executable=document.createElement('script');
316
330
  const hostToken=`html-import-${Date.now()}-${htmlImportScriptId++}`;
317
331
 
318
332
  executable.dataset.arcaneHostToken=hostToken;
319
- executable.textContent=`(()=>{const registry=globalThis[Symbol.for('arcane.html-import.hosts')];const token=document.currentScript&&document.currentScript.dataset.arcaneHostToken;const binding=registry instanceof Map&&token?registry.get(token):null;if(!binding?.host)throw new Error('HTML import host binding is unavailable.');binding.promise=(async function(){${source}}).call(binding.host);})()`;
333
+ executable.textContent=`(()=>{const registry=globalThis[Symbol.for('arcane.html-import.hosts')];const token=document.currentScript&&document.currentScript.dataset.arcaneHostToken;const binding=registry instanceof Map&&token?registry.get(token):null;if(!binding?.host)throw new Error('HTML import host binding is unavailable.');binding.promise=(async function(){${executableSource}}).call(binding.host);})()`;
320
334
  script.parentNode.removeChild(script);
321
335
 
322
336
  const binding={host:this,promise:null};
323
337
  htmlImportHostRegistry.set(hostToken,binding);
324
338
  try{
325
339
  document.head.appendChild(executable);
340
+ if(!binding.promise||typeof binding.promise.then!=='function'){
341
+ throw new Error('The HTML import script did not start.');
342
+ }
326
343
  await binding.promise;
327
344
  }finally{
328
345
  executable.remove();
@@ -9,12 +9,14 @@ import {
9
9
 
10
10
  const PUBLISH_OLLAMA_READY=Symbol('publish-ollama-ready');
11
11
 
12
- export const OLLAMA_EVENT_TYPES=Object.freeze({
12
+ const ollamaEventTypes={
13
13
  ready:'arcane-ollama-ready'
14
- });
15
- export const OLLAMA_REASONS=Object.freeze({
14
+ };
15
+ const ollamaReasons={
16
16
  ready:'ollama-module-ready'
17
- });
17
+ };
18
+ export const OLLAMA_EVENT_TYPES={...ollamaEventTypes};
19
+ export const OLLAMA_REASONS={...ollamaReasons};
18
20
 
19
21
  function api(){
20
22
  const client=globalThis.Arcane?.ollama
@@ -29,7 +31,7 @@ function api(){
29
31
  export class Ollama{
30
32
  #events=createArcaneEventSource(this,{
31
33
  source:'ollama',
32
- eventTypes:Object.freeze(Object.values(OLLAMA_EVENT_TYPES))
34
+ eventTypes:Object.values(ollamaEventTypes)
33
35
  })
34
36
 
35
37
  version(){ return api().version() }
@@ -59,13 +61,13 @@ export class Ollama{
59
61
  const version=String(
60
62
  typeof response==='string' ? response : response?.version||''
61
63
  ).trim()||null
62
- return Object.freeze({ready:true,version,errorCode:null})
64
+ return {ready:true,version,errorCode:null}
63
65
  }catch(error){
64
- return Object.freeze({
66
+ return {
65
67
  ready:false,
66
68
  version:null,
67
69
  errorCode:String(error?.code||'OLLAMA_UNAVAILABLE')
68
- })
70
+ }
69
71
  }
70
72
  }
71
73
 
@@ -84,13 +86,13 @@ export class Ollama{
84
86
  }
85
87
 
86
88
  [PUBLISH_OLLAMA_READY](){
87
- const reason=OLLAMA_REASONS.ready;
89
+ const reason=ollamaReasons.ready;
88
90
  const {occurrence}=this.#events.dispatch(
89
- OLLAMA_EVENT_TYPES.ready,
90
- Object.freeze({ollama:this,reason}),
91
+ ollamaEventTypes.ready,
92
+ {ollama:this,reason},
91
93
  {
92
94
  operationId:`${this.#events.instanceId}:ready:1`,
93
- publicDetail:Object.freeze({ready:true,reason})
95
+ publicDetail:{ready:true,reason}
94
96
  }
95
97
  )
96
98
  if(typeof globalThis.CustomEvent==='function'
@@ -100,10 +102,10 @@ export class Ollama{
100
102
  }
101
103
  }
102
104
 
103
- export const ollama=Object.freeze(new Ollama())
105
+ export const ollama=new Ollama()
104
106
  export default ollama
105
107
 
106
108
  if(!Object.prototype.hasOwnProperty.call(globalThis,'arcaneOllama')){
107
- Object.defineProperty(globalThis,'arcaneOllama',{ value:ollama,enumerable:true,configurable:false,writable:false })
109
+ Object.defineProperty(globalThis,'arcaneOllama',{ value:ollama,enumerable:true,configurable:true,writable:true })
108
110
  }
109
111
  ollama[PUBLISH_OLLAMA_READY]()