arcane-os 0.3.4 → 0.3.6

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 +22 -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 +469 -220
  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 +776 -151
  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 +23 -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
@@ -4,17 +4,17 @@ const CONVERSATION_TIMEBOX_TOOL_NAME='conversation_timebox';
4
4
  const CONVERSATION_TIMEBOX_TICK_MS=1000;
5
5
  const MAX_DATE_MILLISECONDS=8_640_000_000_000_000;
6
6
 
7
- export const CONVERSATION_TIMEBOX_EVENT_TYPES=Object.freeze({
7
+ const conversationTimeboxEventTypes={
8
8
  change:'conversation-timebox-change'
9
- });
9
+ };
10
10
 
11
- export const CONVERSATION_TIMEBOX_ERROR_CODES=Object.freeze({
11
+ const conversationTimeboxErrorCodes={
12
12
  disposed:'ARCANE_CONVERSATION_TIMEBOX_DISPOSED',
13
13
  subscriptionHandlerInvalid:'ARCANE_CONVERSATION_TIMEBOX_SUBSCRIPTION_HANDLER_INVALID',
14
14
  subscriptionOptionsInvalid:'ARCANE_CONVERSATION_TIMEBOX_SUBSCRIPTION_OPTIONS_INVALID'
15
- });
15
+ };
16
16
 
17
- export const CONVERSATION_TIMEBOX_REASONS=Object.freeze({
17
+ const conversationTimeboxReasons={
18
18
  deadlineReached:'conversation-timebox-deadline-reached',
19
19
  disposed:'conversation-timebox-disposed',
20
20
  elapsedTimeAdvanced:'conversation-timebox-elapsed-time-advanced',
@@ -23,47 +23,49 @@ export const CONVERSATION_TIMEBOX_REASONS=Object.freeze({
23
23
  limitSet:'conversation-timebox-limit-set',
24
24
  snapshotReplayed:'conversation-timebox-snapshot-replayed',
25
25
  started:'conversation-timebox-started'
26
- });
26
+ };
27
+
28
+ export const CONVERSATION_TIMEBOX_EVENT_TYPES={...conversationTimeboxEventTypes};
29
+ export const CONVERSATION_TIMEBOX_ERROR_CODES={...conversationTimeboxErrorCodes};
30
+ export const CONVERSATION_TIMEBOX_REASONS={...conversationTimeboxReasons};
27
31
 
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". Every call must include a nonempty message containing the brief plain-language progress or next-step text shown to the user; do not claim the time check changed before the tool result confirms it. 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.`;
32
+ 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". Every call must include a nonempty message containing the brief plain-language progress or next-step text shown to the user; do not claim the time check changed before the tool result confirms it. 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. 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.`;
29
33
 
30
34
  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.';
31
35
 
32
- const conversationTimeboxTool=Object.freeze({
36
+ const conversationTimeboxTool={
33
37
  type:'function',
34
- function:Object.freeze({
38
+ function:{
35
39
  name:CONVERSATION_TIMEBOX_TOOL_NAME,
36
- description:'Control the current conversation time check whenever the user explicitly sets, adds, removes, or continues past it. Use this as the sole tool call. Use action "set" to replace the deadline relative to now, "adjust" to add time to the active deadline, and "clear" to remove the deadline, including continuing after a due check without a new duration. Convert exact stated units to whole milliseconds; never infer, round, or choose a default.',
37
- parameters:Object.freeze({
40
+ description:'Control the current conversation time check whenever the user explicitly sets, adds, removes, or continues past it. Use action "set" to replace the deadline relative to now, "adjust" to add time to the active deadline, and "clear" to remove the deadline, including continuing after a due check without a new duration. Convert exact stated units to whole milliseconds; never infer, round, or choose a default.',
41
+ parameters:{
38
42
  type:'object',
39
- additionalProperties:false,
40
- properties:Object.freeze({
41
- action:Object.freeze({
43
+ properties:{
44
+ action:{
42
45
  type:'string',
43
- enum:Object.freeze(['set','adjust','clear']),
46
+ enum:['set','adjust','clear'],
44
47
  description:'Set a new deadline, add time to the active deadline, or clear the deadline.'
45
- }),
48
+ },
46
49
  message:{
47
50
  type:'string',
48
51
  minLength:1,
49
52
  description:'Brief plain-language progress or next-step text shown to the user for this tool call. Do not claim the time check changed before the tool result confirms it.'
50
53
  },
51
- duration_milliseconds:Object.freeze({
54
+ duration_milliseconds:{
52
55
  type:'integer',
53
56
  minimum:0,
54
57
  description:'The exact positive whole-millisecond duration. Required for "set" and "adjust"; ignored for "clear". Examples: 75 seconds = 75000; 10 minutes = 600000.'
55
- })
56
- }),
57
- required:Object.freeze(['action','message'])
58
- })
59
- })
60
- });
58
+ }
59
+ },
60
+ required:['action','message']
61
+ }
62
+ }
63
+ };
61
64
 
62
65
  function isPlainRecord(value){
63
- return Boolean(value)
64
- &&typeof value==='object'
65
- &&!Array.isArray(value)
66
- &&Object.getPrototypeOf(value)===Object.prototype;
66
+ if(!value||typeof value!=='object'||Array.isArray(value))return false;
67
+ const prototype=Object.getPrototypeOf(value);
68
+ return prototype===Object.prototype||prototype===null;
67
69
  }
68
70
 
69
71
  function parseToolArguments(value){
@@ -89,14 +91,6 @@ function parseToolArguments(value){
89
91
 
90
92
  function normalizeConversationTimeboxCommand(value){
91
93
  const input=parseToolArguments(value);
92
- const allowed=new Set(['action','duration_milliseconds','message']);
93
- const unsupported=Object.keys(input).find(function findUnsupportedTimeboxField(key){
94
- return !allowed.has(key);
95
- });
96
-
97
- if(unsupported){
98
- throw new TypeError(`Unsupported conversation timebox field: ${unsupported}`);
99
- }
100
94
  if(!['set','adjust','clear'].includes(input.action)){
101
95
  throw new TypeError('Conversation timebox action must be "set", "adjust", or "clear".');
102
96
  }
@@ -104,16 +98,17 @@ function normalizeConversationTimeboxCommand(value){
104
98
  throw new TypeError('Conversation timebox message must contain user-facing text.');
105
99
  }
106
100
  if(input.action==='clear'){
107
- return Object.freeze({action:'clear',message:input.message});
101
+ return {...input,action:'clear',message:input.message};
108
102
  }
109
103
  if(!Number.isSafeInteger(input.duration_milliseconds)||input.duration_milliseconds<=0){
110
104
  throw new TypeError('duration_milliseconds must be an explicit positive whole number.');
111
105
  }
112
- return Object.freeze({
106
+ return {
107
+ ...input,
113
108
  action:input.action,
114
109
  durationMilliseconds:input.duration_milliseconds,
115
110
  message:input.message
116
- });
111
+ };
117
112
  }
118
113
 
119
114
  function appendConversationTimeboxOpeningInstruction(message=''){
@@ -195,16 +190,12 @@ function normalizeSubscriptionOptions(value={}){
195
190
  if(!isPlainRecord(value)){
196
191
  throw conversationTimeboxError(
197
192
  'Conversation timebox subscription options must be a plain object.',
198
- CONVERSATION_TIMEBOX_ERROR_CODES.subscriptionOptionsInvalid,
193
+ conversationTimeboxErrorCodes.subscriptionOptionsInvalid,
199
194
  'conversation-timebox-subscription-options-invalid',
200
195
  TypeError
201
196
  );
202
197
  }
203
- const unsupported=Reflect.ownKeys(value).find(function findUnsupportedTimeboxSubscriptionOption(key){
204
- return key!=='once'&&key!=='signal';
205
- });
206
- if(unsupported!==undefined||(
207
- value.once!==undefined
198
+ if((value.once!==undefined
208
199
  &&typeof value.once!=='boolean'
209
200
  )||(
210
201
  value.signal!==undefined
@@ -212,13 +203,13 @@ function normalizeSubscriptionOptions(value={}){
212
203
  &&!isAbortSignal(value.signal)
213
204
  )){
214
205
  throw conversationTimeboxError(
215
- 'Conversation timebox subscriptions support only boolean once and an AbortSignal.',
216
- CONVERSATION_TIMEBOX_ERROR_CODES.subscriptionOptionsInvalid,
206
+ 'Conversation timebox subscription once must be boolean and signal must be an AbortSignal.',
207
+ conversationTimeboxErrorCodes.subscriptionOptionsInvalid,
217
208
  'conversation-timebox-subscription-options-invalid',
218
209
  TypeError
219
210
  );
220
211
  }
221
- return Object.freeze({once:value.once===true,signal:value.signal??null});
212
+ return {once:value.once===true,signal:value.signal??null};
222
213
  }
223
214
 
224
215
  function defaultClock(){
@@ -285,14 +276,6 @@ class ConversationTimebox{
285
276
  if(!isPlainRecord(options)){
286
277
  throw new TypeError('Conversation timebox options must be a plain object.');
287
278
  }
288
- const allowed=new Set(['cancel','clock','onListenerError','schedule','tickMs']);
289
- const unsupported=Object.keys(options).find(function findUnsupportedTimeboxOption(key){
290
- return !allowed.has(key);
291
- });
292
- if(unsupported){
293
- throw new TypeError(`Unsupported conversation timebox option: ${unsupported}`);
294
- }
295
-
296
279
  this.#clock=options.clock??defaultClock;
297
280
  this.#schedule=options.schedule??defaultSchedule;
298
281
  this.#cancel=options.cancel??defaultCancel;
@@ -309,12 +292,12 @@ class ConversationTimebox{
309
292
  throw new TypeError(`${name} must be a function.`);
310
293
  }
311
294
  }
312
- if(!Number.isSafeInteger(this.#tickMs)||this.#tickMs<100||this.#tickMs>60_000){
313
- throw new RangeError('tickMs must be an integer between 100 and 60000.');
295
+ if(!Number.isFinite(this.#tickMs)||this.#tickMs<=0){
296
+ throw new RangeError('tickMs must be a positive finite number.');
314
297
  }
315
298
  this.#events=createArcaneEventSource(this,{
316
299
  source:'conversation-timebox',
317
- eventTypes:Object.freeze(Object.values(CONVERSATION_TIMEBOX_EVENT_TYPES)),
300
+ eventTypes:Object.values(conversationTimeboxEventTypes),
318
301
  onListenerError
319
302
  });
320
303
  }
@@ -333,7 +316,7 @@ class ConversationTimebox{
333
316
  return this.snapshot();
334
317
  }
335
318
  const now=this.#readClock();
336
- this.#startAt(now,true,CONVERSATION_TIMEBOX_REASONS.started);
319
+ this.#startAt(now,true,conversationTimeboxReasons.started);
337
320
  return this.snapshot();
338
321
  }
339
322
 
@@ -352,7 +335,7 @@ class ConversationTimebox{
352
335
  throw new RangeError('The requested conversation duration exceeds the supported date range.');
353
336
  }
354
337
  if(this.#startedAtMs===null){
355
- this.#startAt(now,false,CONVERSATION_TIMEBOX_REASONS.started);
338
+ this.#startAt(now,false,conversationTimeboxReasons.started);
356
339
  }
357
340
 
358
341
  this.#durationMilliseconds=milliseconds;
@@ -361,7 +344,7 @@ class ConversationTimebox{
361
344
  this.#dueRevision=null;
362
345
  this.#revision++;
363
346
  this.#nowMs=now;
364
- this.#notify('change',CONVERSATION_TIMEBOX_REASONS.limitSet,commandSource);
347
+ this.#notify('change',conversationTimeboxReasons.limitSet,commandSource);
365
348
  return this.snapshot();
366
349
  }
367
350
 
@@ -390,7 +373,7 @@ class ConversationTimebox{
390
373
  this.#dueRevision=null;
391
374
  this.#revision++;
392
375
  this.#nowMs=now;
393
- this.#notify('change',CONVERSATION_TIMEBOX_REASONS.limitAdjusted,commandSource);
376
+ this.#notify('change',conversationTimeboxReasons.limitAdjusted,commandSource);
394
377
  return this.snapshot();
395
378
  }
396
379
 
@@ -409,7 +392,7 @@ class ConversationTimebox{
409
392
  this.#dueRevision=null;
410
393
  this.#revision++;
411
394
  this.#nowMs=this.#readClock();
412
- this.#notify('change',CONVERSATION_TIMEBOX_REASONS.limitCleared,commandSource);
395
+ this.#notify('change',conversationTimeboxReasons.limitCleared,commandSource);
413
396
  return this.snapshot();
414
397
  }
415
398
 
@@ -436,7 +419,7 @@ class ConversationTimebox{
436
419
  if(typeof listener!=='function'){
437
420
  throw conversationTimeboxError(
438
421
  'Conversation timebox listener must be a function.',
439
- CONVERSATION_TIMEBOX_ERROR_CODES.subscriptionHandlerInvalid,
422
+ conversationTimeboxErrorCodes.subscriptionHandlerInvalid,
440
423
  'conversation-timebox-subscription-handler-invalid',
441
424
  TypeError
442
425
  );
@@ -447,7 +430,7 @@ class ConversationTimebox{
447
430
  listener(event.detail.state,event.detail.event);
448
431
  }
449
432
  const unsubscribe=this.#events.on(
450
- CONVERSATION_TIMEBOX_EVENT_TYPES.change,
433
+ conversationTimeboxEventTypes.change,
451
434
  forwardConversationTimeboxChange,
452
435
  normalized
453
436
  );
@@ -455,12 +438,12 @@ class ConversationTimebox{
455
438
  try{
456
439
  listener(
457
440
  this.snapshot(),
458
- Object.freeze({
441
+ {
459
442
  type:'snapshot',
460
- reason:CONVERSATION_TIMEBOX_REASONS.snapshotReplayed,
443
+ reason:conversationTimeboxReasons.snapshotReplayed,
461
444
  revision:this.#revision,
462
445
  commandSource:null
463
- })
446
+ }
464
447
  );
465
448
  }catch(error){
466
449
  unsubscribe();
@@ -487,7 +470,7 @@ class ConversationTimebox{
487
470
  ?'armed'
488
471
  :'unlimited';
489
472
 
490
- return Object.freeze({
473
+ return {
491
474
  status,
492
475
  startedAtMs:this.#startedAtMs,
493
476
  elapsedMs,
@@ -497,7 +480,7 @@ class ConversationTimebox{
497
480
  remainingMs,
498
481
  revision:this.#revision,
499
482
  dueRevision:this.#dueRevision
500
- });
483
+ };
501
484
  }
502
485
 
503
486
  dispose(){
@@ -509,7 +492,7 @@ class ConversationTimebox{
509
492
  this.#intervalHandle=null;
510
493
  }
511
494
  this.#disposed=true;
512
- this.#notify('change',CONVERSATION_TIMEBOX_REASONS.disposed);
495
+ this.#notify('change',conversationTimeboxReasons.disposed);
513
496
  const finalSnapshot=this.snapshot();
514
497
  this.#events.dispose();
515
498
  return finalSnapshot;
@@ -519,8 +502,8 @@ class ConversationTimebox{
519
502
  if(this.#disposed){
520
503
  throw conversationTimeboxError(
521
504
  'The conversation timebox has been disposed.',
522
- CONVERSATION_TIMEBOX_ERROR_CODES.disposed,
523
- CONVERSATION_TIMEBOX_REASONS.disposed
505
+ conversationTimeboxErrorCodes.disposed,
506
+ conversationTimeboxReasons.disposed
524
507
  );
525
508
  }
526
509
  }
@@ -563,31 +546,31 @@ class ConversationTimebox{
563
546
  this.#notify(
564
547
  becameDue?'due':'tick',
565
548
  becameDue
566
- ?CONVERSATION_TIMEBOX_REASONS.deadlineReached
567
- :CONVERSATION_TIMEBOX_REASONS.elapsedTimeAdvanced
549
+ ?conversationTimeboxReasons.deadlineReached
550
+ :conversationTimeboxReasons.elapsedTimeAdvanced
568
551
  );
569
552
  }
570
553
 
571
554
  #notify(type,reason,commandSource=null){
572
555
  const state=this.snapshot();
573
- const event=Object.freeze({
556
+ const event={
574
557
  type,
575
558
  reason,
576
559
  revision:this.#revision,
577
560
  commandSource
578
- });
561
+ };
579
562
  const operationId=`${this.#events.instanceId}:change:${(++this.#operationSequence).toString(36)}`;
580
563
  this.#events.dispatch(
581
- CONVERSATION_TIMEBOX_EVENT_TYPES.change,
582
- Object.freeze({state,event}),
564
+ conversationTimeboxEventTypes.change,
565
+ {state:{...state},event:{...event}},
583
566
  {
584
567
  operationId,
585
- publicDetail:Object.freeze({
568
+ publicDetail:{
586
569
  reason,
587
570
  status:state.status,
588
571
  revision:state.revision,
589
572
  commandSource
590
- })
573
+ }
591
574
  }
592
575
  );
593
576
  }
@@ -602,41 +585,30 @@ function consumeConversationTimeboxCall(calls,controller){
602
585
  }
603
586
  const remainingCalls={...calls};
604
587
  if(!Object.hasOwn(remainingCalls,CONVERSATION_TIMEBOX_TOOL_NAME)){
605
- return Object.freeze({
588
+ return {
606
589
  handled:false,
607
- remainingCalls:Object.freeze(remainingCalls),
590
+ remainingCalls,
608
591
  result:null
609
- });
592
+ };
610
593
  }
611
594
 
612
595
  const argumentsValue=remainingCalls[CONVERSATION_TIMEBOX_TOOL_NAME];
613
596
  delete remainingCalls[CONVERSATION_TIMEBOX_TOOL_NAME];
614
- if(Object.keys(remainingCalls).length){
615
- const reason=new TypeError(
616
- 'The conversation timebox must be the sole tool call in a response.'
617
- );
618
- reason.code='CONVERSATION_TIMEBOX_MUST_BE_SOLE_CALL';
619
- return Object.freeze({
620
- handled:true,
621
- remainingCalls:Object.freeze({}),
622
- result:Object.freeze({status:'rejected',reason})
623
- });
624
- }
625
597
  try{
626
- return Object.freeze({
598
+ return {
627
599
  handled:true,
628
- remainingCalls:Object.freeze(remainingCalls),
629
- result:Object.freeze({
600
+ remainingCalls,
601
+ result:{
630
602
  status:'fulfilled',
631
603
  value:controller.applyCommand(argumentsValue)
632
- })
633
- });
604
+ }
605
+ };
634
606
  }catch(reason){
635
- return Object.freeze({
607
+ return {
636
608
  handled:true,
637
- remainingCalls:Object.freeze(remainingCalls),
638
- result:Object.freeze({status:'rejected',reason})
639
- });
609
+ remainingCalls,
610
+ result:{status:'rejected',reason}
611
+ };
640
612
  }
641
613
  }
642
614
 
@@ -8,12 +8,14 @@ import {
8
8
  } from 'arcane-os/event-manager';
9
9
 
10
10
  const PUBLISH_DBLS_READY=Symbol('publish-dbls-ready');
11
- export const DBLS_EVENT_TYPES=Object.freeze({
11
+ const dblsEventTypes={
12
12
  ready:'dbls-ready'
13
- });
14
- export const DBLS_REASONS=Object.freeze({
13
+ };
14
+ const dblsReasons={
15
15
  ready:'local-storage-adapter-ready'
16
- });
16
+ };
17
+ export const DBLS_EVENT_TYPES={...dblsEventTypes};
18
+ export const DBLS_REASONS={...dblsReasons};
17
19
 
18
20
  class DBLS {
19
21
  #events;
@@ -43,7 +45,7 @@ class DBLS {
43
45
  this.storagePrefix=`${APP_LOCAL_STORAGE_PREFIX}${this.applicationId}:`;
44
46
  this.#events=createArcaneEventSource(this,{
45
47
  source:'dbls',
46
- eventTypes:Object.freeze(Object.values(DBLS_EVENT_TYPES))
48
+ eventTypes:Object.values(dblsEventTypes)
47
49
  });
48
50
  }
49
51
 
@@ -173,21 +175,21 @@ class DBLS {
173
175
  }
174
176
 
175
177
  [PUBLISH_DBLS_READY](){
176
- const detail=Object.freeze({
178
+ const detail={
177
179
  dbls:this,
178
180
  applicationId:this.applicationId,
179
- reason:DBLS_REASONS.ready
180
- });
181
+ reason:dblsReasons.ready
182
+ };
181
183
  const {occurrence}=this.#events.dispatch(
182
- DBLS_EVENT_TYPES.ready,
184
+ dblsEventTypes.ready,
183
185
  detail,
184
186
  {
185
187
  operationId:`dbls-ready-${this.#events.instanceId}`,
186
- publicDetail:Object.freeze({
188
+ publicDetail:{
187
189
  applicationId:this.applicationId,
188
190
  ready:true,
189
- reason:DBLS_REASONS.ready
190
- })
191
+ reason:dblsReasons.ready
192
+ }
191
193
  }
192
194
  );
193
195
  projectArcaneDOMEvent(window,occurrence);
@@ -6,14 +6,16 @@ import {
6
6
  } from 'arcane-os/event-manager';
7
7
  const is=new Is(false);
8
8
 
9
- export const DBOPFS_EVENT_TYPES=Object.freeze({
9
+ const dbopfsEventTypes={
10
10
  ready:'dbopfs-ready'
11
- });
12
- export const DBOPFS_REASONS=Object.freeze({
11
+ };
12
+ const dbopfsReasons={
13
13
  ready:'opfs-database-ready'
14
- });
14
+ };
15
+ export const DBOPFS_EVENT_TYPES={...dbopfsEventTypes};
16
+ export const DBOPFS_REASONS={...dbopfsReasons};
15
17
 
16
- const DEFAULT_TABLE_DIRECTORIES=Object.freeze({
18
+ const DEFAULT_TABLE_DIRECTORIES={
17
19
  users:'users',
18
20
  scores:'scores',
19
21
  chats:'chats',
@@ -26,7 +28,7 @@ const DEFAULT_TABLE_DIRECTORIES=Object.freeze({
26
28
  reports:'reports',
27
29
  errors:'errors',
28
30
  memories:'memory'
29
- });
31
+ };
30
32
 
31
33
  function parseFileValue(fileName='',textContent=''){
32
34
  let value=textContent;
@@ -42,9 +44,12 @@ function parseFileValue(fileName='',textContent=''){
42
44
  case 'ndjson':
43
45
  value=[];
44
46
  for(const row of textContent.split('\n')){
47
+ if(!row.trim())continue;
45
48
  try{
46
49
  value.push(JSON.parse(row.trim()));
47
- }catch{}
50
+ }catch{
51
+ value.push(row);
52
+ }
48
53
  }
49
54
  break;
50
55
  }
@@ -285,7 +290,7 @@ class DBOPFS {
285
290
 
286
291
  this.#events=createArcaneEventSource(this,{
287
292
  source:'dbopfs',
288
- eventTypes:Object.freeze(Object.values(DBOPFS_EVENT_TYPES))
293
+ eventTypes:Object.values(dbopfsEventTypes)
289
294
  });
290
295
  this.readyPromise=this.#init(options);
291
296
  }
@@ -312,21 +317,21 @@ class DBOPFS {
312
317
  this.ready=true;
313
318
 
314
319
  const {occurrence}=this.#events.dispatch(
315
- DBOPFS_EVENT_TYPES.ready,
316
- Object.freeze({
320
+ dbopfsEventTypes.ready,
321
+ {
317
322
  dbopfs:this,
318
323
  applicationId:this.#applicationId,
319
324
  storagePath:this.#storagePath,
320
- reason:DBOPFS_REASONS.ready
321
- }),
325
+ reason:dbopfsReasons.ready
326
+ },
322
327
  {
323
328
  operationId:`dbopfs-ready-${this.#events.instanceId}`,
324
- publicDetail:Object.freeze({
329
+ publicDetail:{
325
330
  applicationId:this.#applicationId,
326
331
  ready:true,
327
- reason:DBOPFS_REASONS.ready,
332
+ reason:dbopfsReasons.ready,
328
333
  storagePath:this.#storagePath
329
- })
334
+ }
330
335
  }
331
336
  );
332
337
  projectArcaneDOMEvent(window,occurrence);