@almadar/runtime 3.1.0 → 3.1.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.
@@ -403,12 +403,35 @@ function processEvent(options) {
403
403
  guardResult: transition.guard ? true : void 0
404
404
  };
405
405
  }
406
+ var SINGLETON_SCOPE = "__singleton__";
407
+ function scopeOf(entityData) {
408
+ const id = entityData && entityData["id"];
409
+ return id ?? SINGLETON_SCOPE;
410
+ }
411
+ function compositeKey(traitName, scope) {
412
+ return `${traitName}::${scope}`;
413
+ }
406
414
  var StateMachineManager = class {
407
415
  traits = /* @__PURE__ */ new Map();
416
+ /**
417
+ * State map keyed by `${traitName}::${entityId | __singleton__}`.
418
+ *
419
+ * Each entity instance of an orbital gets its own copy of every
420
+ * trait's state machine. This is what enables N parallel subagents
421
+ * (one OrbitalProcess row per dispatched orbital) to all run their
422
+ * own Build/Validate/Fix cycle without colliding on shared trait
423
+ * state — the original cause of GAP-AGB-2.
424
+ *
425
+ * Initial state is created lazily on first event for each scope, so
426
+ * adding a trait does not pre-allocate state for every potential
427
+ * entity (and there's no need to know entity ids up front).
428
+ */
408
429
  states = /* @__PURE__ */ new Map();
409
430
  config;
410
431
  observer;
411
- // Actor-model per-trait queues
432
+ // Actor-model queues, also keyed by `${traitName}::${entityId}` so
433
+ // each entity gets its own actor loop and concurrent subagents do
434
+ // not serialize through a shared queue.
412
435
  queues = /* @__PURE__ */ new Map();
413
436
  processing = /* @__PURE__ */ new Set();
414
437
  constructor(traits = [], config = {}, observer) {
@@ -431,33 +454,101 @@ var StateMachineManager = class {
431
454
  */
432
455
  addTrait(trait) {
433
456
  this.traits.set(trait.name, trait);
434
- this.states.set(trait.name, createInitialTraitState(trait));
435
457
  }
436
458
  /**
437
459
  * Remove a trait from the manager.
438
460
  */
439
461
  removeTrait(traitName) {
440
462
  this.traits.delete(traitName);
441
- this.states.delete(traitName);
463
+ const prefix = `${traitName}::`;
464
+ for (const key of [...this.states.keys()]) {
465
+ if (key.startsWith(prefix)) this.states.delete(key);
466
+ }
467
+ for (const key of [...this.queues.keys()]) {
468
+ if (key.startsWith(prefix)) this.queues.delete(key);
469
+ }
470
+ for (const key of [...this.processing]) {
471
+ if (key.startsWith(prefix)) this.processing.delete(key);
472
+ }
473
+ }
474
+ /**
475
+ * Get-or-init the state row for a (trait, entityId) pair.
476
+ * Returns undefined if the trait isn't registered.
477
+ */
478
+ getOrInitState(traitName, scope) {
479
+ const trait = this.traits.get(traitName);
480
+ if (!trait) return void 0;
481
+ const key = compositeKey(traitName, scope);
482
+ let state = this.states.get(key);
483
+ if (!state) {
484
+ state = createInitialTraitState(trait);
485
+ this.states.set(key, state);
486
+ }
487
+ return state;
442
488
  }
443
489
  /**
444
490
  * Get current state for a trait.
491
+ *
492
+ * For single-entity orbitals, omit `entityId` — returns the singleton
493
+ * scope. For multi-entity orbitals (e.g. agent OrbitalSubagent), pass
494
+ * the entity row id to look up that specific instance's state.
445
495
  */
446
- getState(traitName) {
447
- return this.states.get(traitName);
496
+ getState(traitName, entityId) {
497
+ const scope = entityId ?? SINGLETON_SCOPE;
498
+ const key = compositeKey(traitName, scope);
499
+ const existing = this.states.get(key);
500
+ if (existing) return existing;
501
+ return this.getOrInitState(traitName, scope);
448
502
  }
449
503
  /**
450
- * Get all current states.
504
+ * Get a flat traitName→state map. For multi-entity orbitals this
505
+ * returns one entry per trait collapsed onto the singleton scope (or
506
+ * the first observed scope if singleton is empty). Use
507
+ * `getAllStatesByScope()` to get the full per-entity view.
451
508
  */
452
509
  getAllStates() {
453
- return new Map(this.states);
510
+ const out = /* @__PURE__ */ new Map();
511
+ for (const traitName of this.traits.keys()) {
512
+ const singleton = this.states.get(compositeKey(traitName, SINGLETON_SCOPE));
513
+ if (singleton) {
514
+ out.set(traitName, singleton);
515
+ continue;
516
+ }
517
+ for (const [key, state] of this.states) {
518
+ if (key.startsWith(`${traitName}::`)) {
519
+ out.set(traitName, state);
520
+ break;
521
+ }
522
+ }
523
+ }
524
+ return out;
525
+ }
526
+ /**
527
+ * Get every per-entity state row, grouped by trait. Useful for
528
+ * inspecting parallel subagent state.
529
+ */
530
+ getAllStatesByScope() {
531
+ const grouped = /* @__PURE__ */ new Map();
532
+ for (const [key, state] of this.states) {
533
+ const sep = key.indexOf("::");
534
+ if (sep < 0) continue;
535
+ const traitName = key.slice(0, sep);
536
+ const scope = key.slice(sep + 2);
537
+ let bucket = grouped.get(traitName);
538
+ if (!bucket) {
539
+ bucket = /* @__PURE__ */ new Map();
540
+ grouped.set(traitName, bucket);
541
+ }
542
+ bucket.set(scope, state);
543
+ }
544
+ return grouped;
454
545
  }
455
546
  /**
456
547
  * Check if a trait can handle an event from its current state.
457
548
  */
458
- canHandleEvent(traitName, eventKey) {
549
+ canHandleEvent(traitName, eventKey, entityId) {
459
550
  const trait = this.traits.get(traitName);
460
- const state = this.states.get(traitName);
551
+ const state = this.getOrInitState(traitName, entityId ?? SINGLETON_SCOPE);
461
552
  if (!trait || !state) return false;
462
553
  return !!findTransition(trait, state.currentState, normalizeEventKey(eventKey));
463
554
  }
@@ -468,9 +559,11 @@ var StateMachineManager = class {
468
559
  */
469
560
  sendEvent(eventKey, payload, entityData) {
470
561
  const results = [];
562
+ const scope = scopeOf(entityData);
471
563
  for (const [traitName, trait] of this.traits) {
472
- const traitState = this.states.get(traitName);
564
+ const traitState = this.getOrInitState(traitName, scope);
473
565
  if (!traitState) continue;
566
+ const key = compositeKey(traitName, scope);
474
567
  const result = processEvent({
475
568
  traitState,
476
569
  trait,
@@ -482,7 +575,7 @@ var StateMachineManager = class {
482
575
  contextExtensions: this.config.contextExtensions
483
576
  });
484
577
  if (result.executed) {
485
- this.states.set(traitName, {
578
+ this.states.set(key, {
486
579
  ...traitState,
487
580
  currentState: result.newState,
488
581
  previousState: result.previousState,
@@ -516,31 +609,29 @@ var StateMachineManager = class {
516
609
  * at a time per trait, effects fully awaited before the next event).
517
610
  */
518
611
  enqueueEvent(eventKey, payload, entityData) {
612
+ const scope = scopeOf(entityData);
519
613
  for (const [traitName] of this.traits) {
520
- const queue = this.queues.get(traitName) ?? [];
614
+ const key = compositeKey(traitName, scope);
615
+ const queue = this.queues.get(key) ?? [];
521
616
  queue.push({ eventKey, payload, entityData });
522
- this.queues.set(traitName, queue);
617
+ this.queues.set(key, queue);
523
618
  }
524
619
  }
525
620
  /**
526
- * Drain a single trait's event queue, processing events sequentially.
527
- *
528
- * This is the core actor loop: each event is fully processed (including
529
- * awaiting all effects) before the next event is dequeued. If the queue
530
- * is already being drained for this trait, this call is a no-op (the
531
- * running drain will pick up newly enqueued events).
532
- *
533
- * @param traitName - Which trait's queue to drain
534
- * @param executeEffects - Async callback to run effects for a successful transition
621
+ * Drain a single (trait, entity) pair's event queue, processing
622
+ * events sequentially. Pass `entityId` to drain a specific entity
623
+ * instance; omit it to drain the singleton scope.
535
624
  */
536
- async drainQueue(traitName, executeEffects) {
537
- if (this.processing.has(traitName)) return;
538
- this.processing.add(traitName);
539
- const queue = this.queues.get(traitName) ?? [];
625
+ async drainQueue(traitName, executeEffects, entityId) {
626
+ const scope = entityId ?? SINGLETON_SCOPE;
627
+ const key = compositeKey(traitName, scope);
628
+ if (this.processing.has(key)) return;
629
+ this.processing.add(key);
630
+ const queue = this.queues.get(key) ?? [];
540
631
  while (queue.length > 0) {
541
632
  const entry = queue.shift();
542
633
  const trait = this.traits.get(traitName);
543
- const traitState = this.states.get(traitName);
634
+ const traitState = this.getOrInitState(traitName, scope);
544
635
  if (!trait || !traitState) continue;
545
636
  const result = processEvent({
546
637
  traitState,
@@ -553,7 +644,7 @@ var StateMachineManager = class {
553
644
  contextExtensions: this.config.contextExtensions
554
645
  });
555
646
  if (result.executed) {
556
- this.states.set(traitName, {
647
+ this.states.set(key, {
557
648
  ...traitState,
558
649
  currentState: result.newState,
559
650
  previousState: result.previousState,
@@ -573,35 +664,51 @@ var StateMachineManager = class {
573
664
  await executeEffects(traitName, result, entry.payload);
574
665
  }
575
666
  }
576
- this.processing.delete(traitName);
667
+ this.processing.delete(key);
577
668
  }
578
669
  /**
579
670
  * Check whether a trait's queue is currently being drained.
580
671
  */
581
- isProcessing(traitName) {
582
- return this.processing.has(traitName);
672
+ isProcessing(traitName, entityId) {
673
+ return this.processing.has(compositeKey(traitName, entityId ?? SINGLETON_SCOPE));
583
674
  }
584
675
  /**
585
676
  * Get the number of pending events in a trait's queue.
586
677
  */
587
- getQueueLength(traitName) {
588
- return this.queues.get(traitName)?.length ?? 0;
678
+ getQueueLength(traitName, entityId) {
679
+ return this.queues.get(compositeKey(traitName, entityId ?? SINGLETON_SCOPE))?.length ?? 0;
589
680
  }
590
681
  /**
591
682
  * Reset a trait to its initial state.
683
+ *
684
+ * If `entityId` is provided, resets only that entity's scope.
685
+ * Otherwise resets every entity scope known for the trait.
592
686
  */
593
- resetTrait(traitName) {
687
+ resetTrait(traitName, entityId) {
594
688
  const trait = this.traits.get(traitName);
595
- if (trait) {
596
- this.states.set(traitName, createInitialTraitState(trait));
689
+ if (!trait) return;
690
+ if (entityId) {
691
+ this.states.set(compositeKey(traitName, entityId), createInitialTraitState(trait));
692
+ return;
693
+ }
694
+ const prefix = `${traitName}::`;
695
+ for (const key of [...this.states.keys()]) {
696
+ if (key.startsWith(prefix)) {
697
+ this.states.set(key, createInitialTraitState(trait));
698
+ }
597
699
  }
598
700
  }
599
701
  /**
600
- * Reset all traits to initial states.
702
+ * Reset all traits to initial states (every entity scope).
601
703
  */
602
704
  resetAll() {
603
705
  for (const [traitName, trait] of this.traits) {
604
- this.states.set(traitName, createInitialTraitState(trait));
706
+ const prefix = `${traitName}::`;
707
+ for (const key of [...this.states.keys()]) {
708
+ if (key.startsWith(prefix)) {
709
+ this.states.set(key, createInitialTraitState(trait));
710
+ }
711
+ }
605
712
  }
606
713
  }
607
714
  };
@@ -840,6 +947,10 @@ var EffectExecutor = class {
840
947
  case "set": {
841
948
  const [entityId, field, value] = args;
842
949
  this.handlers.set(entityId, field, value);
950
+ const entity = this.bindings.entity;
951
+ if (entity && entity["id"] === entityId) {
952
+ entity[field] = value;
953
+ }
843
954
  break;
844
955
  }
845
956
  case "persist": {
@@ -2393,5 +2504,5 @@ function parseNamespacedEvent(eventName) {
2393
2504
  }
2394
2505
 
2395
2506
  export { EffectExecutor, EventBus, HANDLER_MANIFEST, StateMachineManager, containsBindings, createContextFromBindings, createInitialTraitState, createTestExecutor, createUnifiedLoader, extractBindings, findInitialState, findTransition, getIsolatedCollectionName, getNamespacedEvent, interpolateProps, interpolateValue, isNamespacedEvent, normalizeEventKey, parseNamespacedEvent, preprocessSchema, processEvent };
2396
- //# sourceMappingURL=chunk-HIM4HJAN.js.map
2397
- //# sourceMappingURL=chunk-HIM4HJAN.js.map
2507
+ //# sourceMappingURL=chunk-KCXJNXQZ.js.map
2508
+ //# sourceMappingURL=chunk-KCXJNXQZ.js.map