instar 1.3.783 → 1.3.785

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 (64) hide show
  1. package/dist/commands/server.d.ts.map +1 -1
  2. package/dist/commands/server.js +41 -15
  3. package/dist/commands/server.js.map +1 -1
  4. package/dist/core/MachineIdentity.d.ts +13 -0
  5. package/dist/core/MachineIdentity.d.ts.map +1 -1
  6. package/dist/core/MachineIdentity.js +31 -0
  7. package/dist/core/MachineIdentity.js.map +1 -1
  8. package/dist/core/MeteredSpendGate.d.ts +8 -0
  9. package/dist/core/MeteredSpendGate.d.ts.map +1 -1
  10. package/dist/core/MeteredSpendGate.js +20 -0
  11. package/dist/core/MeteredSpendGate.js.map +1 -1
  12. package/dist/core/SpendAlertDispatcher.d.ts +94 -0
  13. package/dist/core/SpendAlertDispatcher.d.ts.map +1 -0
  14. package/dist/core/SpendAlertDispatcher.js +168 -0
  15. package/dist/core/SpendAlertDispatcher.js.map +1 -0
  16. package/dist/core/SpendAlertEmitters.d.ts +118 -0
  17. package/dist/core/SpendAlertEmitters.d.ts.map +1 -0
  18. package/dist/core/SpendAlertEmitters.js +220 -0
  19. package/dist/core/SpendAlertEmitters.js.map +1 -0
  20. package/dist/core/SpendAlertResolver.d.ts +12 -0
  21. package/dist/core/SpendAlertResolver.d.ts.map +1 -1
  22. package/dist/core/SpendAlertResolver.js +34 -0
  23. package/dist/core/SpendAlertResolver.js.map +1 -1
  24. package/dist/core/TelegramSpendTopicChannel.d.ts +50 -0
  25. package/dist/core/TelegramSpendTopicChannel.d.ts.map +1 -0
  26. package/dist/core/TelegramSpendTopicChannel.js +106 -0
  27. package/dist/core/TelegramSpendTopicChannel.js.map +1 -0
  28. package/dist/core/routingSpendView.d.ts +16 -0
  29. package/dist/core/routingSpendView.d.ts.map +1 -1
  30. package/dist/core/routingSpendView.js +23 -0
  31. package/dist/core/routingSpendView.js.map +1 -1
  32. package/dist/core/types.d.ts +22 -2
  33. package/dist/core/types.d.ts.map +1 -1
  34. package/dist/core/types.js.map +1 -1
  35. package/dist/monitoring/FeatureMetricsLedger.d.ts +6 -0
  36. package/dist/monitoring/FeatureMetricsLedger.d.ts.map +1 -1
  37. package/dist/monitoring/FeatureMetricsLedger.js +7 -2
  38. package/dist/monitoring/FeatureMetricsLedger.js.map +1 -1
  39. package/dist/monitoring/ProviderCostReportStore.d.ts +138 -0
  40. package/dist/monitoring/ProviderCostReportStore.d.ts.map +1 -0
  41. package/dist/monitoring/ProviderCostReportStore.js +0 -0
  42. package/dist/monitoring/ProviderCostReportStore.js.map +1 -0
  43. package/dist/monitoring/ProviderReconciliationSweep.d.ts +72 -0
  44. package/dist/monitoring/ProviderReconciliationSweep.d.ts.map +1 -0
  45. package/dist/monitoring/ProviderReconciliationSweep.js +97 -0
  46. package/dist/monitoring/ProviderReconciliationSweep.js.map +1 -0
  47. package/dist/server/AgentServer.d.ts +19 -0
  48. package/dist/server/AgentServer.d.ts.map +1 -1
  49. package/dist/server/AgentServer.js +251 -46
  50. package/dist/server/AgentServer.js.map +1 -1
  51. package/dist/server/routes.d.ts +2 -0
  52. package/dist/server/routes.d.ts.map +1 -1
  53. package/dist/server/routes.js +40 -0
  54. package/dist/server/routes.js.map +1 -1
  55. package/dist/testing/selfActionRegistry.d.ts.map +1 -1
  56. package/dist/testing/selfActionRegistry.js +166 -0
  57. package/dist/testing/selfActionRegistry.js.map +1 -1
  58. package/package.json +1 -1
  59. package/src/data/builtin-manifest.json +47 -47
  60. package/src/data/state-coherence-registry.json +1241 -1223
  61. package/upgrades/1.3.784.md +59 -0
  62. package/upgrades/1.3.785.md +56 -0
  63. package/upgrades/side-effects/routing-spend-increment-1c.md +44 -0
  64. package/upgrades/side-effects/routing-spend-increment-c.md +44 -0
@@ -91,6 +91,14 @@ import { MeteredSpendGate } from '../core/MeteredSpendGate.js';
91
91
  import { RenderedPlanStore } from '../core/RenderedPlanStore.js';
92
92
  import { PinAttemptStore } from '../core/PinAttemptStore.js';
93
93
  import { SpendAlertResolver, buildStalePriceAlert } from '../core/SpendAlertResolver.js';
94
+ import { SpendAlertDispatcher } from '../core/SpendAlertDispatcher.js';
95
+ import { TelegramSpendTopicChannel, spendAlertDeliveryId } from '../core/TelegramSpendTopicChannel.js';
96
+ import { SpendAlertEmitters } from '../core/SpendAlertEmitters.js';
97
+ import { MachineIdentityManager } from '../core/MachineIdentity.js';
98
+ import { PendingRelayStore as SpendAlertRelayStore } from '../messaging/pending-relay-store.js';
99
+ import { ProviderCostReportStore } from '../monitoring/ProviderCostReportStore.js';
100
+ import { ProviderReconciliationSweep } from '../monitoring/ProviderReconciliationSweep.js';
101
+ import { meteredKeysFromChains } from '../core/routingSpendView.js';
94
102
  import { DEFAULT_FRESHNESS_SLA_DAYS } from '../core/routingPriceAuthority.js';
95
103
  import { METERED_ROUTING_DOORS } from '../data/llmBenchCoverage.js';
96
104
  import { DEFAULT_METERED_CAPS } from '../core/routingSpendView.js';
@@ -188,6 +196,14 @@ export class AgentServer {
188
196
  meteredSweepTimer = null;
189
197
  spendAlertResolver = null;
190
198
  spendAlertTimer = null;
199
+ /** Increment C alert layer (dryRun-first live-on-dev — FD-16). */
200
+ spendAlertDispatcher = null;
201
+ spendAlertEmitters = null;
202
+ /** Lazily-opened durable relay handle for money-critical alert delivery. */
203
+ spendAlertRelayStore = null;
204
+ /** Layer 1c provider-report store + reconciliation sweep (reporting-only; FD-21). */
205
+ providerCostReportStore = null;
206
+ reconSweepTimer = null;
191
207
  featureMetricsPruneTimer = null;
192
208
  a2aDeliveryTracker = null;
193
209
  tokenLedgerPoller = null;
@@ -654,6 +670,72 @@ export class AgentServer {
654
670
  console.warn('[instar] pin-attempt store init failed (non-fatal — in-memory lockout only):', err);
655
671
  this.pinAttemptStore = null;
656
672
  }
673
+ // ── Layer 1c provider-report store + reconciliation sweep (FD-21 —
674
+ // REPORTING-only; rides the same dev-gated view flag as the summary;
675
+ // structurally excluded from the money gate, which imports nothing here).
676
+ if (routingSpendOn) {
677
+ try {
678
+ const rsCfg = options.config.routingSpend;
679
+ this.providerCostReportStore = new ProviderCostReportStore({
680
+ dbPath: path.join(options.config.stateDir, 'server-data', 'provider-cost-reports.db'),
681
+ retentionDays: rsCfg?.providerReportRetentionDays ?? 400,
682
+ });
683
+ this.providerCostReportStore.prune(); // boot prune (batched — scal-F4)
684
+ const doorToKey = new Map(meteredKeysFromChains().map((m) => [m.door, m.keyRef]));
685
+ const sweep = new ProviderReconciliationSweep({
686
+ store: this.providerCostReportStore,
687
+ // Internal-derived per (keyRef, door): the metered doors' daily token
688
+ // buckets × the as-of reviewed price (the same reporting math the
689
+ // summary uses) — reporting-side reads only, never the money mutex.
690
+ internalDerivedUsd: (sinceMs, untilMs) => {
691
+ const ledger = this.featureMetricsLedger;
692
+ const prices = this.routingPriceAuthority;
693
+ if (!ledger || !prices)
694
+ return [];
695
+ prices.reloadIfChanged();
696
+ const sinceDays = Math.max(1, Math.ceil((untilMs - sinceMs) / 86_400_000) + 1);
697
+ const buckets = ledger.spendTokenRollupDaily({ sinceDays });
698
+ const agg = new Map();
699
+ for (const b of buckets) {
700
+ const keyRef = doorToKey.get(b.door);
701
+ if (!keyRef)
702
+ continue; // metered doors only
703
+ if (b.bucketStartMs < sinceMs - 86_400_000 || b.bucketStartMs > untilMs)
704
+ continue;
705
+ const res = prices.resolve(b.door, b.modelId, b.bucketStartMs);
706
+ const cost = prices.reportingCost(res, b.tokensIn, b.tokensOut, b.tokensCached);
707
+ const e = agg.get(`${keyRef} ${b.door}`) ?? { keyRef, door: b.door, internalUsd: 0 };
708
+ e.internalUsd += cost.grossUsd;
709
+ agg.set(`${keyRef} ${b.door}`, e);
710
+ }
711
+ return [...agg.values()];
712
+ },
713
+ committedUsd: (keyRef) => {
714
+ try {
715
+ return this.meteredSpendLedger?.committed(keyRef).committedLifetimeUsd ?? null;
716
+ }
717
+ catch {
718
+ // @silent-fallback-ok: committed is holder-known enrichment; a
719
+ // read failure records the comparison provider-vs-internal only.
720
+ return null;
721
+ }
722
+ },
723
+ // The Increment-C emit surface (late-bound; dispatcher dryRun-soaked).
724
+ onDrift: (keyRef, door, driftPct) => this.spendAlertEmitters?.onReconciliationDrift(keyRef, door, driftPct),
725
+ driftAlertPct: rsCfg?.reconciliation?.driftAlertPct ?? 10,
726
+ });
727
+ const sweepMs = Math.max(1, rsCfg?.reconciliation?.sweepIntervalHours ?? 6) * 60 * 60 * 1000;
728
+ this.reconSweepTimer = setInterval(() => void sweep.run(), sweepMs);
729
+ this.reconSweepTimer.unref?.();
730
+ }
731
+ catch (err) {
732
+ // @silent-fallback-ok: loud warn — a broken provider-report layer
733
+ // degrades every row to internal-derived (labeled); it never touches
734
+ // money admission or the router path.
735
+ console.warn('[instar] provider-cost report layer init failed (non-fatal — internal-derived reporting remains):', err);
736
+ this.providerCostReportStore = null;
737
+ }
738
+ }
657
739
  // ── Increment B MONEY layer (routing-control-room-spend §Layer 3 / Surface 2).
658
740
  // EXPLICIT enable only (`routingSpend.money.enabled === true`) — a documented
659
741
  // DARK_GATE_EXCLUSIONS action-bearing case (FD-16): NEVER resolveDevAgentGate.
@@ -679,6 +761,9 @@ export class AgentServer {
679
761
  prices: this.routingPriceAuthority,
680
762
  capsStore: this.routingSpendCapsStore,
681
763
  machineId,
764
+ // Increment C observer (signal-only, late-bound — the alert layer
765
+ // constructs after this block; null while dark).
766
+ onGateEvent: (ev) => this.spendAlertEmitters?.onGateEvent(ev),
682
767
  // Single-machine trivially self-confirms (it IS the pool). On a
683
768
  // multi-machine pool the REAL designation re-confirmation plumbing
684
769
  // lands with the metered dispatch seam — until then the gate
@@ -700,51 +785,7 @@ export class AgentServer {
700
785
  const alertsCfg = options.config.routingSpend;
701
786
  const tg = this.telegramAdapter;
702
787
  if (tg) {
703
- const persistPath = path.join(options.config.stateDir, 'state', 'routing-spend-alert-topic.json');
704
- this.spendAlertResolver = new SpendAlertResolver({
705
- configuredTopicId: () => {
706
- const id = alertsCfg?.alerts?.telegramTopicId;
707
- return typeof id === 'number' && Number.isFinite(id) ? id : undefined;
708
- },
709
- readPersistedTopicId: () => {
710
- try {
711
- const raw = JSON.parse(fs.readFileSync(persistPath, 'utf-8'));
712
- return typeof raw.topicId === 'number' ? raw.topicId : undefined;
713
- }
714
- catch {
715
- // @silent-fallback-ok: no persisted record yet — rung 2 simply misses.
716
- return undefined;
717
- }
718
- },
719
- persistTopicId: (topicId) => {
720
- try {
721
- const tmp = persistPath + '.tmp';
722
- fs.writeFileSync(tmp, JSON.stringify({ topicId }), { mode: 0o600 });
723
- fs.renameSync(tmp, persistPath);
724
- }
725
- catch {
726
- // @silent-fallback-ok: a failed persist means the next resolve re-runs
727
- // the ladder; creation stays fenced, so no duplicate can result.
728
- }
729
- },
730
- // Single machine trivially self-confirms (it IS the serving lease); on a
731
- // multi-machine pool the confirmed-lease plumbing rides the C increment —
732
- // until then this machine does NOT create (fail toward the lifeline).
733
- servingLeaseConfirmedAgoMs: () => (poolOn ? null : 0),
734
- createTopic: async () => {
735
- const t = await tg.createForumTopic('💰 Routing & Spend Alerts', undefined, {
736
- origin: 'system',
737
- bounded: true,
738
- label: 'routing-spend-alerts',
739
- });
740
- return t.topicId;
741
- },
742
- sendToTopic: async (topicId, text) => {
743
- await tg.sendToTopic(topicId, text);
744
- return true;
745
- },
746
- lifelineTopicId: () => tg.getLifelineTopicId(),
747
- });
788
+ this.spendAlertResolver ??= this.buildSpendAlertResolver(tg, options.config, poolOn);
748
789
  const staleCheck = () => {
749
790
  try {
750
791
  const prices = this.routingPriceAuthority;
@@ -760,7 +801,15 @@ export class AgentServer {
760
801
  const res = prices.resolve(door, '__staleness-probe__', Date.now());
761
802
  if (res.priceStale && res.newestPointAgeDays !== null) {
762
803
  const meta = prices.doorMetaFor(door);
763
- void resolver.emit(buildStalePriceAlert(door, res.newestPointAgeDays, meta?.freshnessSlaDays ?? DEFAULT_FRESHNESS_SLA_DAYS));
804
+ const alert = buildStalePriceAlert(door, res.newestPointAgeDays, meta?.freshnessSlaDays ?? DEFAULT_FRESHNESS_SLA_DAYS);
805
+ // Increment C: prefer the dispatcher (lane dedup + coalescing +
806
+ // dryRun soak); the B resolver-direct path remains the fallback
807
+ // when the alert layer is dark (byte-identical B behavior).
808
+ const dispatcher = this.spendAlertDispatcher;
809
+ if (dispatcher)
810
+ void dispatcher.dispatch(alert);
811
+ else
812
+ void resolver.emit(alert);
764
813
  }
765
814
  }
766
815
  }
@@ -786,6 +835,84 @@ export class AgentServer {
786
835
  this.spendPlanStore = null;
787
836
  }
788
837
  }
838
+ // ── Increment C alert layer (routing-control-room-spend §Surface 2 Alerts).
839
+ // dryRun-FIRST live-on-dev (FD-16): `routingSpend.alerts.enabled` rides the
840
+ // dev-agent gate; `alerts.dryRun` defaults TRUE so a dev agent soaks the
841
+ // dispatch decisions in logs/routing-spend-alerts.jsonl before anything is
842
+ // delivered. Independent of the money layer — door-dark/fallback alerts can
843
+ // flow with no door ever armed (that is WHY the SERVING lease owns creation).
844
+ try {
845
+ const alertsCfgC = options.config.routingSpend?.alerts;
846
+ const alertsOn = resolveDevAgentGate(alertsCfgC?.enabled, options.config);
847
+ const tgC = this.telegramAdapter;
848
+ if (alertsOn && tgC) {
849
+ const poolOnC = options.config.multiMachine?.sessionPool?.enabled === true;
850
+ this.spendAlertResolver ??= this.buildSpendAlertResolver(tgC, options.config, poolOnC);
851
+ const auditPath = path.join(options.config.projectDir, 'logs', 'routing-spend-alerts.jsonl');
852
+ const auditSink = (entry) => {
853
+ try {
854
+ fs.mkdirSync(path.dirname(auditPath), { recursive: true });
855
+ fs.appendFileSync(auditPath, JSON.stringify({ ts: new Date().toISOString(), ...entry }) + '\n', { mode: 0o600 });
856
+ }
857
+ catch {
858
+ // @silent-fallback-ok: the audit line is observability — never blocks dispatch.
859
+ }
860
+ };
861
+ const channel = new TelegramSpendTopicChannel({
862
+ resolver: this.spendAlertResolver,
863
+ sendToTopic: async (topicId, text) => {
864
+ await tgC.sendToTopic(topicId, text);
865
+ return true;
866
+ },
867
+ lifelineTopicId: () => tgC.getLifelineTopicId(),
868
+ // Money-critical kinds ride the durable relay when the store opens
869
+ // (retry-until-delivered — delivery-robustness Layers 2/3; the
870
+ // DeliveryFailureSentinel drains this store). Opened lazily,
871
+ // per-agent-id isolated, degrade-to-direct-send on ANY failure.
872
+ enqueueDurable: (topicId, text) => {
873
+ try {
874
+ this.spendAlertRelayStore ??= SpendAlertRelayStore.open(options.config.projectName, options.config.stateDir);
875
+ const store = this.spendAlertRelayStore;
876
+ const id = spendAlertDeliveryId(topicId, text);
877
+ return store.enqueue({
878
+ delivery_id: id,
879
+ topic_id: topicId,
880
+ text_hash: id,
881
+ text,
882
+ http_code: 0,
883
+ attempted_port: options.config.port ?? 0,
884
+ next_attempt_at: new Date().toISOString(),
885
+ });
886
+ }
887
+ catch {
888
+ // @silent-fallback-ok: no sqlite / store unavailable — the channel
889
+ // degrades to the direct send + lifeline ladder (delivery degrades,
890
+ // never dies; the dispatcher's latch still requires a confirmed leg).
891
+ return false;
892
+ }
893
+ },
894
+ audit: auditSink,
895
+ });
896
+ this.spendAlertDispatcher = new SpendAlertDispatcher({
897
+ channels: [channel],
898
+ dryRun: alertsCfgC?.dryRun !== false, // FD-16 dryRun-first
899
+ auditPath,
900
+ });
901
+ const machineIdC = options.config.machineId ?? 'single-machine';
902
+ this.spendAlertEmitters = new SpendAlertEmitters({
903
+ dispatcher: this.spendAlertDispatcher,
904
+ machineId: machineIdC,
905
+ });
906
+ }
907
+ }
908
+ catch (err) {
909
+ // @silent-fallback-ok: loud warn — a broken alert layer degrades to the
910
+ // Increment-B behavior (resolver-direct stale-price emission); it never
911
+ // touches money admission or the router path.
912
+ console.warn('[instar] routing-spend alert layer init failed (non-fatal — B-level alerts remain):', err);
913
+ this.spendAlertDispatcher = null;
914
+ this.spendAlertEmitters = null;
915
+ }
789
916
  // Observable Intelligence × Responsible Resource: the audit trail is kept
790
917
  // long enough to see behaviour/performance trends, then aged out — never
791
918
  // hoarded forever. Default 30d; tune via monitoring.featureMetrics.retentionDays
@@ -2126,6 +2253,7 @@ export class AgentServer {
2126
2253
  meteredSpendGate: this.meteredSpendGate,
2127
2254
  spendPlanStore: this.spendPlanStore,
2128
2255
  pinAttemptStore: this.pinAttemptStore,
2256
+ providerCostReportStore: this.providerCostReportStore,
2129
2257
  resourceLedger: this.resourceLedger,
2130
2258
  processFootprintMonitor: this.processFootprintMonitor,
2131
2259
  approvalLedger: this.approvalLedger,
@@ -3917,6 +4045,15 @@ export class AgentServer {
3917
4045
  clearInterval(this.spendAlertTimer);
3918
4046
  }
3919
4047
  catch { /* @silent-fallback-ok: timer teardown is best-effort cleanup at shutdown */ }
4048
+ try {
4049
+ await this.spendAlertDispatcher?.flushDigest();
4050
+ }
4051
+ catch { /* @silent-fallback-ok: a failed final digest flush loses only a coalesced NOTICE at shutdown */ }
4052
+ try {
4053
+ if (this.reconSweepTimer)
4054
+ clearInterval(this.reconSweepTimer);
4055
+ }
4056
+ catch { /* @silent-fallback-ok: timer teardown is best-effort cleanup at shutdown */ }
3920
4057
  this.featureMetricsPruneTimer = null;
3921
4058
  }
3922
4059
  if (this.tokenLedger) {
@@ -4017,6 +4154,74 @@ export class AgentServer {
4017
4154
  * its map in memory, so constructing a second instance on the same file would
4018
4155
  * lose updates between the two caches.
4019
4156
  */
4157
+ /** Increment C — the router fan-out + gate observer reach the emitters here (late-bound; null while dark). */
4158
+ getSpendAlertEmitters() {
4159
+ return this.spendAlertEmitters;
4160
+ }
4161
+ /**
4162
+ * Shared builder for the "💰 Routing & Spend Alerts" topic resolver — used by
4163
+ * BOTH the Increment-B money block (stale-price cadence) and the Increment-C
4164
+ * alert layer, whichever constructs first. Rung 2 carries both halves: the
4165
+ * machine-local persisted record AND the pool-published registry field
4166
+ * (FD-6 — a future serving-lease holder inherits the id, never re-creates).
4167
+ */
4168
+ buildSpendAlertResolver(tg, config, poolOn) {
4169
+ const persistPath = path.join(config.stateDir, 'state', 'routing-spend-alert-topic.json');
4170
+ const machineId = config.machineId;
4171
+ const registry = new MachineIdentityManager(config.stateDir);
4172
+ return new SpendAlertResolver({
4173
+ configuredTopicId: () => {
4174
+ const id = config.routingSpend?.alerts?.telegramTopicId;
4175
+ return typeof id === 'number' && Number.isFinite(id) ? id : undefined;
4176
+ },
4177
+ readPersistedTopicId: () => {
4178
+ try {
4179
+ const raw = JSON.parse(fs.readFileSync(persistPath, 'utf-8'));
4180
+ return typeof raw.topicId === 'number' ? raw.topicId : undefined;
4181
+ }
4182
+ catch {
4183
+ // @silent-fallback-ok: no persisted record yet — rung 2 simply misses.
4184
+ return undefined;
4185
+ }
4186
+ },
4187
+ persistTopicId: (topicId) => {
4188
+ try {
4189
+ const tmp = persistPath + '.tmp';
4190
+ fs.writeFileSync(tmp, JSON.stringify({ topicId }), { mode: 0o600 });
4191
+ fs.renameSync(tmp, persistPath);
4192
+ }
4193
+ catch {
4194
+ // @silent-fallback-ok: a failed persist means the next resolve re-runs
4195
+ // the ladder; creation stays fenced, so no duplicate can result.
4196
+ }
4197
+ },
4198
+ // FD-6 rung 2, pool half: the replicated machine-registry field.
4199
+ readPoolPublishedTopicId: () => registry.readAnyRoutingSpendAlertTopic(),
4200
+ publishTopicId: (topicId) => {
4201
+ if (!machineId)
4202
+ return; // single-machine installs: the local record suffices
4203
+ registry.updateRoutingSpendAlertTopic(machineId, topicId);
4204
+ },
4205
+ // Single machine trivially self-confirms (it IS the serving lease); on a
4206
+ // multi-machine pool the confirmed-lease plumbing is a tracked follow-up —
4207
+ // until then this machine does NOT create (fail toward the lifeline)
4208
+ // <!-- tracked: CMT-1929 -->.
4209
+ servingLeaseConfirmedAgoMs: () => (poolOn ? null : 0),
4210
+ createTopic: async () => {
4211
+ const t = await tg.createForumTopic('💰 Routing & Spend Alerts', undefined, {
4212
+ origin: 'system',
4213
+ bounded: true,
4214
+ label: 'routing-spend-alerts',
4215
+ });
4216
+ return t.topicId;
4217
+ },
4218
+ sendToTopic: async (topicId, text) => {
4219
+ await tg.sendToTopic(topicId, text);
4220
+ return true;
4221
+ },
4222
+ lifelineTopicId: () => tg.getLifelineTopicId(),
4223
+ });
4224
+ }
4020
4225
  getTopicOperatorStore() {
4021
4226
  return this.topicOperatorStore;
4022
4227
  }