stratagate-dsh 0.2.77 → 0.2.79

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.
package/dist/plugin.js CHANGED
@@ -98,6 +98,11 @@ var FILLER_ONLY = /* @__PURE__ */ new Set([
98
98
  ]);
99
99
  var REPEATED_PASTE_MIN_CHARS = 80;
100
100
  var REPEATED_PASTE_MARKER = "[repeated paste omitted; original remains in L5]";
101
+ var DERIVATION_SOURCE_FIELD_MIN_CHARS = 240;
102
+ var DERIVATION_ARGUMENT_MAX_CHARS = 1200;
103
+ var DERIVATION_RESULT_MAX_CHARS = 2400;
104
+ var DERIVATION_TOOL_MESSAGE_MAX_CHARS = 2400;
105
+ var DERIVATION_SOURCE_FIELD = /^(?:code|command|program|script|source|sourcecode)$/u;
101
106
  function asBlockLevel(value) {
102
107
  return Math.max(0, Math.min(BLOCK_MAX_LEVEL, Math.round(value)));
103
108
  }
@@ -161,6 +166,73 @@ function resultSummary(value) {
161
166
  if (record2.ok === false) return "not completed";
162
167
  return "structured result returned";
163
168
  }
169
+ function serializedValue(value) {
170
+ if (typeof value === "string") return value;
171
+ try {
172
+ return JSON.stringify(value) ?? String(value);
173
+ } catch {
174
+ return String(value);
175
+ }
176
+ }
177
+ function sourceFieldName(value) {
178
+ return DERIVATION_SOURCE_FIELD.test(value.replace(/[\s_-]+/gu, "").toLocaleLowerCase());
179
+ }
180
+ function omitLargeSourceFields(value, key = "", seen = /* @__PURE__ */ new WeakSet()) {
181
+ if (typeof value === "string") {
182
+ if (!sourceFieldName(key) || value.length <= DERIVATION_SOURCE_FIELD_MIN_CHARS) return value;
183
+ return `[${key || "source"} omitted: ${value.length} chars; full value remains in L5]`;
184
+ }
185
+ if (!value || typeof value !== "object") return value;
186
+ if (seen.has(value)) return "[circular value omitted; full value remains in L5]";
187
+ seen.add(value);
188
+ if (Array.isArray(value)) return value.map((item) => omitLargeSourceFields(item, key, seen));
189
+ return Object.fromEntries(Object.entries(value).map(([nestedKey, nestedValue]) => [nestedKey, omitLargeSourceFields(nestedValue, nestedKey, seen)]));
190
+ }
191
+ function compactDerivationValue(value, maxChars, label) {
192
+ const rendered = serializedValue(value);
193
+ if (rendered.length <= maxChars) return value;
194
+ const summary = resultSummary(value);
195
+ const headChars = Math.max(160, Math.floor(maxChars * 0.48));
196
+ const tailChars = Math.max(80, Math.floor(maxChars * 0.2));
197
+ const omittedChars = Math.max(0, rendered.length - headChars - tailChars);
198
+ return {
199
+ _stratagate: `${label} compacted; full value remains in L5`,
200
+ originalChars: rendered.length,
201
+ ...summary ? { summary } : {},
202
+ head: rendered.slice(0, headChars),
203
+ tail: rendered.slice(-tailChars),
204
+ omittedChars
205
+ };
206
+ }
207
+ function compactDerivationText(value, maxChars, label) {
208
+ if (value.length <= maxChars) return value;
209
+ const headChars = Math.max(160, Math.floor(maxChars * 0.6));
210
+ const tailChars = Math.max(80, Math.floor(maxChars * 0.2));
211
+ const omittedChars = Math.max(0, value.length - headChars - tailChars);
212
+ return `${value.slice(0, headChars)}
213
+ [${label} compacted: ${omittedChars} chars omitted; full value remains in L5]
214
+ ${value.slice(-tailChars)}`;
215
+ }
216
+ function buildMemoryDerivationMessages(messages) {
217
+ return messages.map((message) => ({
218
+ ...message,
219
+ content: message.role === "tool" ? compactDerivationText(message.content, DERIVATION_TOOL_MESSAGE_MAX_CHARS, "tool message content") : message.content,
220
+ ...message.toolCalls ? {
221
+ toolCalls: message.toolCalls.map((trace) => {
222
+ const argumentsValue = trace.arguments === void 0 ? void 0 : compactDerivationValue(
223
+ omitLargeSourceFields(trace.arguments),
224
+ DERIVATION_ARGUMENT_MAX_CHARS,
225
+ "tool arguments"
226
+ );
227
+ return {
228
+ name: trace.name,
229
+ ...argumentsValue === void 0 ? {} : { arguments: argumentsValue },
230
+ ...trace.result === void 0 ? {} : { result: compactDerivationValue(trace.result, DERIVATION_RESULT_MAX_CHARS, "tool result") }
231
+ };
232
+ })
233
+ } : {}
234
+ }));
235
+ }
164
236
  function summarizeToolTrace(trace) {
165
237
  const summary = resultSummary(trace.result);
166
238
  return `Tool call: ${trace.name}${summary ? ` (${summary})` : ""}`;
@@ -5426,7 +5498,14 @@ function l2Neighbor(block) {
5426
5498
  }
5427
5499
  function extractorPayload(context) {
5428
5500
  return {
5429
- target: context.target,
5501
+ target: {
5502
+ blockId: context.target.id,
5503
+ sequence: context.target.sequence,
5504
+ startTurn: context.target.startTurn,
5505
+ endTurn: context.target.endTurn,
5506
+ createdAt: context.target.createdAt,
5507
+ messages: buildMemoryDerivationMessages(context.target.l5Raw)
5508
+ },
5430
5509
  neighbors: {
5431
5510
  previous: l2Neighbor(context.previous),
5432
5511
  next: l2Neighbor(context.next)
@@ -5672,6 +5751,7 @@ var DshModelBridge = class {
5672
5751
  this.structuredReasoningEffort = config.structuredReasoningEffort ?? "auto";
5673
5752
  this.ctx.on?.("llm/adapters-updated", () => {
5674
5753
  this.offCapabilities.clear();
5754
+ for (const listener of this.adaptersUpdatedListeners) listener();
5675
5755
  });
5676
5756
  }
5677
5757
  ctx;
@@ -5680,7 +5760,24 @@ var DshModelBridge = class {
5680
5760
  successfulResponses = [];
5681
5761
  offCapabilities = /* @__PURE__ */ new Map();
5682
5762
  warnedOffFallbackRoutes = /* @__PURE__ */ new Set();
5763
+ adaptersUpdatedListeners = /* @__PURE__ */ new Set();
5683
5764
  structuredReasoningEffort;
5765
+ /** True only when the exact provider route has a registered DSH adapter. */
5766
+ isReady(session) {
5767
+ try {
5768
+ const { provider } = this.resolveRoute(session);
5769
+ return this.ctx.llm.listProviders().some(({ id }) => id === provider);
5770
+ } catch {
5771
+ return false;
5772
+ }
5773
+ }
5774
+ /** Wake durable workers when DSH changes the adapter registry. */
5775
+ onAdaptersUpdated(listener) {
5776
+ this.adaptersUpdatedListeners.add(listener);
5777
+ return () => {
5778
+ this.adaptersUpdatedListeners.delete(listener);
5779
+ };
5780
+ }
5684
5781
  setStructuredReasoningEffort(mode) {
5685
5782
  this.structuredReasoningEffort = mode;
5686
5783
  }
@@ -5697,8 +5794,8 @@ var DshModelBridge = class {
5697
5794
  summarizer = async (messages) => {
5698
5795
  const raw = object(await this.callStructured(
5699
5796
  "summarizer",
5700
- `You compress agent conversations into durable memory blocks. Read the supplied messages and call ${STRUCTURED_TOOLS.summarizer.name} exactly once with l0Title, l0Tags, l1Summary, l2Keypoints, and shouldExtract. Preserve decisions, constraints, preferences, outcomes, and unresolved work. shouldExtract is true only when durable events or facts exist. Do not return the summary as text.`,
5701
- { messages }
5797
+ `You compress agent conversations into durable memory blocks. Read the supplied provenance-preserving derivation messages and call ${STRUCTURED_TOOLS.summarizer.name} exactly once with l0Title, l0Tags, l1Summary, l2Keypoints, and shouldExtract. Preserve decisions, constraints, preferences, outcomes, and unresolved work. Tool code and oversized tool payloads may be marked compacted; use the retained tool names, evidence summaries, and excerpts without inventing omitted details. shouldExtract is true only when durable events or facts exist. Do not return the summary as text.`,
5798
+ { messages: buildMemoryDerivationMessages(messages) }
5702
5799
  ));
5703
5800
  return {
5704
5801
  l0Title: text2(raw.l0Title).slice(0, 120),
@@ -5712,7 +5809,7 @@ var DshModelBridge = class {
5712
5809
  const validMessageIds = new Set(context.target.l5Raw.map((message) => message.id));
5713
5810
  const raw = object(await this.callStructured(
5714
5811
  "extractor",
5715
- `Extract only durable, evidence-backed events from target.l5Raw, then call ${STRUCTURED_TOOLS.extractor.name} exactly once. The target block is the only legal source of new facts, quotations, and sourceMessageIds. neighbors.previous and neighbors.next are context-only L2 summaries; never extract from them. Every sourceMessageIds entry must exactly match allowedSourceMessageIds. If a fact appears only in a neighbor, do not extract it in this call. Events must be understandable later without the original chat. Use project scope for repository decisions, user scope for stable preferences/identity, and session scope for temporary task state. temporal.eventType must use exactly one stable value: decision, release, task_completed, plan, change, cancellation, incident, meeting, collaboration, migration, or other. temporal.participants contains canonical entity names. Use ISO-8601 timestamps with the explicit +08:00 offset in temporal fields. Keep happened time separate from mentionedAt; when happened time is unknown omit it and set precision/basis to unknown. Do not turn an assistant statement that merely recalls older memory into a new event; require new human input or a new observable task/tool outcome from target.l5Raw. Do not return the result as text.`,
5812
+ `Extract only durable, evidence-backed events from target.messages, then call ${STRUCTURED_TOOLS.extractor.name} exactly once. target.messages is a provenance-preserving derivation view of the target block: message ids and conversational text are retained, while tool code and oversized tool payloads may be marked compacted. Use the retained tool names, evidence summaries, and excerpts without inventing omitted details. The target block is the only legal source of new facts, quotations, and sourceMessageIds. neighbors.previous and neighbors.next are context-only L2 summaries; never extract from them. Every sourceMessageIds entry must exactly match allowedSourceMessageIds. If a fact appears only in a neighbor, do not extract it in this call. Events must be understandable later without the original chat. Use project scope for repository decisions, user scope for stable preferences/identity, and session scope for temporary task state. temporal.eventType must use exactly one stable value: decision, release, task_completed, plan, change, cancellation, incident, meeting, collaboration, migration, or other. temporal.participants contains canonical entity names. Use ISO-8601 timestamps with the explicit +08:00 offset in temporal fields. Keep happened time separate from mentionedAt; when happened time is unknown omit it and set precision/basis to unknown. Do not turn an assistant statement that merely recalls older memory into a new event; require new human input or a new observable task/tool outcome from target.messages. Do not return the result as text.`,
5716
5813
  extractorPayload(context)
5717
5814
  ));
5718
5815
  const events = (Array.isArray(raw.events) ? raw.events : []).map((candidate) => {
@@ -5891,6 +5988,7 @@ var DshModelBridge = class {
5891
5988
  let lastError;
5892
5989
  let lastResponse = "";
5893
5990
  let attemptsUsed = 0;
5991
+ let noAdapterRetried = false;
5894
5992
  for (let attempt = 1; attempt <= JSON_RESPONSE_ATTEMPTS; attempt += 1) {
5895
5993
  attemptsUsed = attempt;
5896
5994
  const message = createUserMessage({
@@ -5921,6 +6019,11 @@ ${JSON_RETRY_INSTRUCTION}`,
5921
6019
  try {
5922
6020
  await this.consumeStructuredStream(request, assembler);
5923
6021
  } catch (error) {
6022
+ if (!noAdapterRetried && isNoAdapter(error)) {
6023
+ noAdapterRetried = true;
6024
+ attempt -= 1;
6025
+ continue;
6026
+ }
5924
6027
  if (useOff && isOffRejection(error)) {
5925
6028
  this.offCapabilities.set(routeKey, "unsupported");
5926
6029
  useOff = false;
@@ -5933,6 +6036,11 @@ ${JSON_RETRY_INSTRUCTION}`,
5933
6036
  const finish = assembler.finish;
5934
6037
  if (finish.kind === "error" || finish.kind === "aborted") {
5935
6038
  const failure = new Error(`StrataGate model call failed [${finish.failure.code}]: ${finish.failure.message}`);
6039
+ if (!noAdapterRetried && isNoAdapter(finish.failure)) {
6040
+ noAdapterRetried = true;
6041
+ attempt -= 1;
6042
+ continue;
6043
+ }
5936
6044
  if (useOff && isOffRejection(finish.failure)) {
5937
6045
  this.offCapabilities.set(routeKey, "unsupported");
5938
6046
  useOff = false;
@@ -6098,6 +6206,17 @@ ${JSON_RETRY_INSTRUCTION}`,
6098
6206
  return { provider: fallback.provider, model: fallback.model };
6099
6207
  }
6100
6208
  };
6209
+ function isNoAdapter(error) {
6210
+ const seen = /* @__PURE__ */ new Set();
6211
+ let current = error;
6212
+ while (current && typeof current === "object" && !seen.has(current)) {
6213
+ seen.add(current);
6214
+ const candidate = current;
6215
+ if (candidate.code === "NO_ADAPTER") return true;
6216
+ current = candidate.cause;
6217
+ }
6218
+ return false;
6219
+ }
6101
6220
  function isOffRejection(error) {
6102
6221
  let detail;
6103
6222
  try {
@@ -6539,6 +6658,7 @@ var StrataGateRuntime = class {
6539
6658
  this.openPath = openPath;
6540
6659
  this.blockTurnSize = config.blockTurnSize;
6541
6660
  this.blockDecayLambda = config.blockDecayLambda;
6661
+ this.disposeAdaptersUpdated = this.models.onAdaptersUpdated(() => this.wakeModelWorkers());
6542
6662
  this.scheduleBackgroundWorker(BACKGROUND_WORKER_INITIAL_DELAY_MS);
6543
6663
  }
6544
6664
  config;
@@ -6553,6 +6673,7 @@ var StrataGateRuntime = class {
6553
6673
  latestBatchIds = /* @__PURE__ */ new Map();
6554
6674
  workspaceNames = /* @__PURE__ */ new Map();
6555
6675
  migrationTimers = /* @__PURE__ */ new Map();
6676
+ migrationRuns = /* @__PURE__ */ new Map();
6556
6677
  derivationTimers = /* @__PURE__ */ new Map();
6557
6678
  derivationRuns = /* @__PURE__ */ new Map();
6558
6679
  knownSessions = /* @__PURE__ */ new Map();
@@ -6571,6 +6692,8 @@ var StrataGateRuntime = class {
6571
6692
  backgroundNamespaceRuns = /* @__PURE__ */ new Map();
6572
6693
  backgroundWorkerTimer;
6573
6694
  backgroundWorkerRun;
6695
+ backgroundWorkerWakePending = false;
6696
+ disposeAdaptersUpdated;
6574
6697
  settingsTail = Promise.resolve();
6575
6698
  batchSequence = 0;
6576
6699
  closed = false;
@@ -6586,13 +6709,15 @@ var StrataGateRuntime = class {
6586
6709
  this.backgroundWorkerRun = run;
6587
6710
  void run.finally(() => {
6588
6711
  if (this.backgroundWorkerRun === run) this.backgroundWorkerRun = void 0;
6589
- this.scheduleBackgroundWorker(BACKGROUND_WORKER_INTERVAL_MS);
6712
+ const delay = this.backgroundWorkerWakePending ? 0 : BACKGROUND_WORKER_INTERVAL_MS;
6713
+ this.backgroundWorkerWakePending = false;
6714
+ this.scheduleBackgroundWorker(delay);
6590
6715
  });
6591
6716
  }, delayMs);
6592
6717
  this.backgroundWorkerTimer.unref?.();
6593
6718
  }
6594
6719
  async runBackgroundWorker() {
6595
- if (this.closed) return;
6720
+ if (this.closed || !this.models.isReady()) return;
6596
6721
  let namespaces;
6597
6722
  try {
6598
6723
  namespaces = await this.adminNamespaces();
@@ -6610,6 +6735,7 @@ var StrataGateRuntime = class {
6610
6735
  }
6611
6736
  }
6612
6737
  async runBackgroundNamespace(namespace) {
6738
+ if (this.closed || !this.models.isReady()) return;
6613
6739
  const existing = this.backgroundNamespaceRuns.get(namespace);
6614
6740
  if (existing) return existing;
6615
6741
  const run = (async () => {
@@ -6625,9 +6751,11 @@ var StrataGateRuntime = class {
6625
6751
  const hasActiveSessionWork = [
6626
6752
  ...this.derivationTimers.keys(),
6627
6753
  ...this.derivationRuns.keys(),
6628
- ...this.migrationTimers.keys()
6754
+ ...this.migrationTimers.keys(),
6755
+ ...this.migrationRuns.keys()
6629
6756
  ].some((key) => key.startsWith(`${namespace}\0`) || key === namespace);
6630
6757
  if (hasActiveSessionWork) return;
6758
+ if (!this.models.isReady()) return;
6631
6759
  const resumed = await this.models.runDetached(
6632
6760
  `stratagate-worker:${namespace}`,
6633
6761
  () => memory.resumePendingWork()
@@ -6655,6 +6783,33 @@ var StrataGateRuntime = class {
6655
6783
  if (this.backgroundNamespaceRuns.get(namespace) === run) this.backgroundNamespaceRuns.delete(namespace);
6656
6784
  }
6657
6785
  }
6786
+ wakeBackgroundWorker() {
6787
+ if (this.closed) return;
6788
+ if (this.backgroundWorkerTimer) {
6789
+ clearTimeout(this.backgroundWorkerTimer);
6790
+ this.backgroundWorkerTimer = void 0;
6791
+ }
6792
+ if (this.backgroundWorkerRun) {
6793
+ this.backgroundWorkerWakePending = true;
6794
+ return;
6795
+ }
6796
+ this.scheduleBackgroundWorker(0);
6797
+ }
6798
+ wakeModelWorkers() {
6799
+ if (this.closed) return;
6800
+ this.wakeBackgroundWorker();
6801
+ for (const reference of this.knownSessions.values()) {
6802
+ const session = reference.deref();
6803
+ if (!session || !this.models.isReady(session)) continue;
6804
+ const opening = this.spaces.get(this.namespaceFor(session));
6805
+ if (!opening) continue;
6806
+ void opening.then((memory) => {
6807
+ if (this.closed || !this.models.isReady(session)) return;
6808
+ this.scheduleGraphMigration(session, memory);
6809
+ this.scheduleBlockDerivation(session, memory);
6810
+ }).catch((error) => this.onIngestError(error));
6811
+ }
6812
+ }
6658
6813
  acceptEvent(session, event) {
6659
6814
  if (this.closed) return;
6660
6815
  if (!this.config.ingestSubagents && session.header.origin === "subagent") return;
@@ -7138,6 +7293,7 @@ var StrataGateRuntime = class {
7138
7293
  async close() {
7139
7294
  if (this.closed) return;
7140
7295
  this.closed = true;
7296
+ this.disposeAdaptersUpdated();
7141
7297
  if (this.backgroundWorkerTimer) clearTimeout(this.backgroundWorkerTimer);
7142
7298
  this.backgroundWorkerTimer = void 0;
7143
7299
  for (const timer of this.migrationTimers.values()) clearTimeout(timer);
@@ -7155,6 +7311,7 @@ var StrataGateRuntime = class {
7155
7311
  const settled = await Promise.allSettled(this.spaces.values());
7156
7312
  await Promise.allSettled(this.backgroundNamespaceRuns.values());
7157
7313
  if (this.backgroundWorkerRun) await Promise.allSettled([this.backgroundWorkerRun]);
7314
+ await Promise.allSettled(this.migrationRuns.values());
7158
7315
  await Promise.allSettled(this.derivationRuns.values());
7159
7316
  await Promise.allSettled(this.externalImportRuns.values());
7160
7317
  await Promise.all(settled.flatMap((result) => result.status === "fulfilled" ? [result.value.close()] : []));
@@ -7691,6 +7848,9 @@ var StrataGateRuntime = class {
7691
7848
  if (kind === "event-extraction") return memory.retryEventExtraction(jobId);
7692
7849
  return memory.retryGraphProjection(jobId);
7693
7850
  };
7851
+ if (!this.models.isReady(session)) {
7852
+ throw new Error("StrataGate model adapter is not ready; retry after DSH finishes registering adapters");
7853
+ }
7694
7854
  const result = session && this.namespaceFor(session) === key ? await this.models.run(session, retry) : await this.models.runDetached(threadId, retry);
7695
7855
  await this.persistSuccessfulResponses(memory);
7696
7856
  const currentBlock = blockId ? memory.listBlocks().find(({ id: candidateId }) => candidateId === blockId) : void 0;
@@ -7890,7 +8050,7 @@ var StrataGateRuntime = class {
7890
8050
  scheduleBlockDerivation(session, memory) {
7891
8051
  const threadId = String(session.id);
7892
8052
  const key = `${this.namespaceFor(session)}\0${threadId}`;
7893
- if (this.closed || this.derivationTimers.has(key) || this.derivationRuns.has(key)) return;
8053
+ if (this.closed || !this.models.isReady(session) || this.derivationTimers.has(key) || this.derivationRuns.has(key)) return;
7894
8054
  const pendingBlockIds = new Set(memory.listBlocks().filter((block) => block.threadId === threadId && block.processingStatus === "pending").map((block) => block.id));
7895
8055
  if (pendingBlockIds.size === 0) return;
7896
8056
  const retryTimes = [
@@ -7901,7 +8061,7 @@ var StrataGateRuntime = class {
7901
8061
  const delay = Math.max(0, Math.min(...retryTimes) - Date.now());
7902
8062
  const timer = setTimeout(() => {
7903
8063
  this.derivationTimers.delete(key);
7904
- if (this.closed) return;
8064
+ if (this.closed || !this.models.isReady(session)) return;
7905
8065
  const failedBefore = this.failedCoreJobs(memory);
7906
8066
  const run = this.models.run(session, () => memory.resumePendingWork({ threadId })).then(async (resumed) => {
7907
8067
  await this.persistSuccessfulResponses(memory);
@@ -7929,24 +8089,29 @@ var StrataGateRuntime = class {
7929
8089
  }
7930
8090
  scheduleGraphMigration(session, memory) {
7931
8091
  const namespace = this.namespaceFor(session);
7932
- if (this.closed || this.migrationTimers.has(namespace)) return;
8092
+ if (this.closed || !this.models.isReady(session) || this.migrationTimers.has(namespace) || this.migrationRuns.has(namespace)) return;
7933
8093
  if (typeof memory.listGraphProjectionJobs !== "function") return;
7934
8094
  const pending = memory.listGraphProjectionJobs().some((job) => graphProjectionCanRun(job));
7935
8095
  if (!pending) return;
7936
8096
  const timer = setTimeout(() => {
7937
8097
  this.migrationTimers.delete(namespace);
7938
- if (this.closed) return;
8098
+ if (this.closed || !this.models.isReady(session)) return;
7939
8099
  const failedBefore = this.failedCoreJobs(memory);
7940
8100
  const completedBefore = memory.listGraphProjectionJobs().filter(({ status }) => status === "completed").length;
7941
- void this.models.run(session, () => memory.resumePendingWork()).then(async () => {
8101
+ let madeProgress = false;
8102
+ const run = this.models.run(session, () => memory.resumePendingWork()).then(async () => {
7942
8103
  await this.persistSuccessfulResponses(memory);
7943
8104
  this.noteNewCoreJobFailures(session, failedBefore, memory);
7944
8105
  const completedAfter = memory.listGraphProjectionJobs().filter(({ status }) => status === "completed").length;
7945
- if (completedAfter > completedBefore) this.scheduleGraphMigration(session, memory);
8106
+ madeProgress = completedAfter > completedBefore;
7946
8107
  }).catch((error) => {
7947
8108
  this.notePluginError(session, error);
7948
8109
  this.onIngestError(error);
8110
+ }).finally(() => {
8111
+ if (this.migrationRuns.get(namespace) === run) this.migrationRuns.delete(namespace);
8112
+ if (madeProgress) this.scheduleGraphMigration(session, memory);
7949
8113
  });
8114
+ this.migrationRuns.set(namespace, run);
7950
8115
  }, 1500);
7951
8116
  timer.unref?.();
7952
8117
  this.migrationTimers.set(namespace, timer);