agent-working-memory 0.10.0 → 0.12.0

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 (136) hide show
  1. package/README.md +118 -19
  2. package/dist/adapters/claude-code.d.ts.map +1 -1
  3. package/dist/adapters/claude-code.js +63 -3
  4. package/dist/adapters/claude-code.js.map +1 -1
  5. package/dist/adapters/common.d.ts.map +1 -1
  6. package/dist/adapters/common.js +329 -302
  7. package/dist/adapters/common.js.map +1 -1
  8. package/dist/api/routes.d.ts.map +1 -1
  9. package/dist/api/routes.js +31 -8
  10. package/dist/api/routes.js.map +1 -1
  11. package/dist/cli/migrate.js +29 -29
  12. package/dist/cli.js +82 -2
  13. package/dist/cli.js.map +1 -1
  14. package/dist/coordination/circuit-breaker.js +23 -23
  15. package/dist/coordination/routes.d.ts.map +1 -1
  16. package/dist/coordination/routes.js +174 -170
  17. package/dist/coordination/routes.js.map +1 -1
  18. package/dist/core/embeddings.d.ts.map +1 -1
  19. package/dist/core/embeddings.js +3 -0
  20. package/dist/core/embeddings.js.map +1 -1
  21. package/dist/core/entity-extract.d.ts +3 -0
  22. package/dist/core/entity-extract.d.ts.map +1 -0
  23. package/dist/core/entity-extract.js +47 -0
  24. package/dist/core/entity-extract.js.map +1 -0
  25. package/dist/core/salience.d.ts.map +1 -1
  26. package/dist/core/salience.js +14 -2
  27. package/dist/core/salience.js.map +1 -1
  28. package/dist/core/whoami.d.ts +24 -0
  29. package/dist/core/whoami.d.ts.map +1 -0
  30. package/dist/core/whoami.js +66 -0
  31. package/dist/core/whoami.js.map +1 -0
  32. package/dist/core/write-pipeline.d.ts +9 -0
  33. package/dist/core/write-pipeline.d.ts.map +1 -1
  34. package/dist/core/write-pipeline.js +109 -68
  35. package/dist/core/write-pipeline.js.map +1 -1
  36. package/dist/core/write-telemetry.d.ts +33 -0
  37. package/dist/core/write-telemetry.d.ts.map +1 -0
  38. package/dist/core/write-telemetry.js +110 -0
  39. package/dist/core/write-telemetry.js.map +1 -0
  40. package/dist/engine/activation.d.ts +22 -12
  41. package/dist/engine/activation.d.ts.map +1 -1
  42. package/dist/engine/activation.js +133 -17
  43. package/dist/engine/activation.js.map +1 -1
  44. package/dist/engine/consolidation-scheduler.d.ts +1 -1
  45. package/dist/engine/consolidation-scheduler.js +1 -1
  46. package/dist/engine/consolidation.d.ts +1 -0
  47. package/dist/engine/consolidation.d.ts.map +1 -1
  48. package/dist/engine/consolidation.js +18 -0
  49. package/dist/engine/consolidation.js.map +1 -1
  50. package/dist/engine/eval.d.ts.map +1 -1
  51. package/dist/engine/eval.js +5 -1
  52. package/dist/engine/eval.js.map +1 -1
  53. package/dist/index.js +21 -2
  54. package/dist/index.js.map +1 -1
  55. package/dist/mcp.d.ts +2 -1
  56. package/dist/mcp.d.ts.map +1 -1
  57. package/dist/mcp.js +212 -97
  58. package/dist/mcp.js.map +1 -1
  59. package/dist/onboard/index.d.ts +68 -0
  60. package/dist/onboard/index.d.ts.map +1 -0
  61. package/dist/onboard/index.js +265 -0
  62. package/dist/onboard/index.js.map +1 -0
  63. package/dist/recipes/index.d.ts +57 -0
  64. package/dist/recipes/index.d.ts.map +1 -0
  65. package/dist/recipes/index.js +81 -0
  66. package/dist/recipes/index.js.map +1 -0
  67. package/dist/storage/pglite-schema.d.ts.map +1 -1
  68. package/dist/storage/pglite-schema.js +170 -143
  69. package/dist/storage/pglite-schema.js.map +1 -1
  70. package/dist/storage/pglite.d.ts +5 -0
  71. package/dist/storage/pglite.d.ts.map +1 -1
  72. package/dist/storage/pglite.js +180 -138
  73. package/dist/storage/pglite.js.map +1 -1
  74. package/dist/storage/postgres.d.ts +5 -0
  75. package/dist/storage/postgres.d.ts.map +1 -1
  76. package/dist/storage/postgres.js +44 -2
  77. package/dist/storage/postgres.js.map +1 -1
  78. package/dist/storage/sqlite.d.ts +9 -0
  79. package/dist/storage/sqlite.d.ts.map +1 -1
  80. package/dist/storage/sqlite.js +394 -326
  81. package/dist/storage/sqlite.js.map +1 -1
  82. package/dist/types/engram.d.ts +14 -0
  83. package/dist/types/engram.d.ts.map +1 -1
  84. package/dist/types/engram.js.map +1 -1
  85. package/dist/version.d.ts +2 -0
  86. package/dist/version.d.ts.map +1 -0
  87. package/dist/version.js +27 -0
  88. package/dist/version.js.map +1 -0
  89. package/package.json +9 -1
  90. package/src/adapters/claude-code.ts +66 -3
  91. package/src/adapters/common.ts +538 -511
  92. package/src/api/index.ts +3 -3
  93. package/src/api/routes.ts +999 -970
  94. package/src/cli/migrate.ts +307 -307
  95. package/src/cli.ts +77 -2
  96. package/src/coordination/circuit-breaker.ts +83 -83
  97. package/src/coordination/failure-modes.ts +50 -50
  98. package/src/coordination/routes.ts +2155 -2150
  99. package/src/core/decay.ts +63 -63
  100. package/src/core/embeddings.ts +113 -110
  101. package/src/core/entity-extract.ts +47 -0
  102. package/src/core/index.ts +5 -5
  103. package/src/core/logger.ts +36 -36
  104. package/src/core/ml-worker-entry.ts +194 -194
  105. package/src/core/ml-worker.ts +281 -281
  106. package/src/core/query-expander.ts +122 -122
  107. package/src/core/reranker.ts +119 -119
  108. package/src/core/salience.ts +529 -514
  109. package/src/core/whoami.ts +92 -0
  110. package/src/core/write-pipeline.ts +60 -8
  111. package/src/core/write-telemetry.ts +131 -0
  112. package/src/engine/activation.ts +1468 -1369
  113. package/src/engine/confidence.ts +120 -120
  114. package/src/engine/consolidation-scheduler.ts +242 -242
  115. package/src/engine/consolidation.ts +887 -869
  116. package/src/engine/eval.ts +107 -102
  117. package/src/engine/eviction.ts +101 -101
  118. package/src/engine/index.ts +8 -8
  119. package/src/engine/retraction.ts +366 -366
  120. package/src/engine/staging.ts +74 -74
  121. package/src/index.ts +248 -226
  122. package/src/mcp.ts +1341 -1211
  123. package/src/onboard/index.ts +298 -0
  124. package/src/recipes/index.ts +125 -0
  125. package/src/storage/index.ts +3 -3
  126. package/src/storage/pglite-schema.ts +193 -166
  127. package/src/storage/pglite.ts +1420 -1372
  128. package/src/storage/postgres.ts +50 -2
  129. package/src/storage/sqlite.ts +1936 -1861
  130. package/src/storage/store.ts +80 -80
  131. package/src/types/agent.ts +67 -67
  132. package/src/types/checkpoint.ts +46 -46
  133. package/src/types/engram.ts +22 -0
  134. package/src/types/eval.ts +100 -100
  135. package/src/types/index.ts +6 -6
  136. package/src/version.ts +26 -0
@@ -1,194 +1,194 @@
1
- // Copyright 2026 Robert Winter / Complete Ideas
2
- // SPDX-License-Identifier: Apache-2.0
3
- /**
4
- * ML worker entry — runs INSIDE a worker_thread.
5
- *
6
- * Loaded once per worker, sets up the model for the assigned role
7
- * (embed | rerank | expand), then handles request messages from the
8
- * main thread via the parentPort.
9
- *
10
- * Protocol:
11
- * Main thread → worker: { id, op, args } (op matches the worker's role)
12
- * Worker → main thread: { id, ok: true, result } or { id, ok: false, error }
13
- * Worker → main thread: { ready: true } (one-time signal after model load)
14
- * Main thread → worker: { shutdown: true } (drain queue, then terminate)
15
- *
16
- * The worker stays loaded — the model lives in memory for the worker's lifetime.
17
- */
18
-
19
- import { parentPort, workerData } from 'node:worker_threads';
20
-
21
- if (!parentPort) {
22
- throw new Error('ml-worker-entry: must be loaded as a worker_thread');
23
- }
24
-
25
- type WorkerRole = 'embed' | 'rerank' | 'expand';
26
- const role: WorkerRole = workerData?.role;
27
- if (role !== 'embed' && role !== 'rerank' && role !== 'expand') {
28
- throw new Error(`ml-worker-entry: invalid role '${role}'`);
29
- }
30
-
31
- // --- Lazy model loaders (each worker loads only its own model) ---
32
-
33
- let embedderPipeline: any = null;
34
- let rerankerTokenizer: any = null;
35
- let rerankerModel: any = null;
36
- let expanderPipeline: any = null;
37
-
38
- // Inside worker_threads we must use the WASM ONNX backend, not the native one.
39
- // onnxruntime-node's native bindings store V8 handles that get invalidated when
40
- // crossing isolate boundaries — calling from a worker crashes with
41
- // `v8::HandleScope::CreateHandle()` failures. The WASM backend is V8-safe.
42
- const WORKER_DEVICE = 'wasm' as const;
43
-
44
- async function loadEmbedder(): Promise<void> {
45
- const { pipeline } = await import('@huggingface/transformers');
46
- const modelId = process.env.AWM_EMBED_MODEL ?? 'Xenova/bge-small-en-v1.5';
47
- embedderPipeline = await pipeline('feature-extraction', modelId, { dtype: 'fp32', device: WORKER_DEVICE });
48
- }
49
-
50
- async function loadReranker(): Promise<void> {
51
- const { AutoTokenizer, AutoModelForSequenceClassification } = await import('@huggingface/transformers');
52
- const modelId = process.env.AWM_RERANKER_MODEL || 'Xenova/ms-marco-MiniLM-L-6-v2';
53
- rerankerTokenizer = await AutoTokenizer.from_pretrained(modelId);
54
- rerankerModel = await AutoModelForSequenceClassification.from_pretrained(modelId, { dtype: 'fp32', device: WORKER_DEVICE });
55
- }
56
-
57
- async function loadExpander(): Promise<void> {
58
- const { pipeline } = await import('@huggingface/transformers');
59
- expanderPipeline = await pipeline('text2text-generation', 'Xenova/flan-t5-small', { dtype: 'fp32', device: WORKER_DEVICE });
60
- }
61
-
62
- // --- Per-role inference handlers ---
63
-
64
- function sigmoid(x: number): number {
65
- return 1 / (1 + Math.exp(-x));
66
- }
67
-
68
- async function handleEmbed(args: { texts: string[]; pooling: 'cls' | 'mean'; dimensions: number }): Promise<number[][]> {
69
- if (!embedderPipeline) throw new Error('embedder not loaded');
70
- const { texts, pooling, dimensions } = args;
71
- if (texts.length === 0) return [];
72
- const result = await embedderPipeline(texts, { pooling, normalize: true });
73
- const data = result.data as Float32Array;
74
- const vectors: number[][] = [];
75
- for (let i = 0; i < texts.length; i++) {
76
- vectors.push(Array.from(data.slice(i * dimensions, (i + 1) * dimensions)));
77
- }
78
- return vectors;
79
- }
80
-
81
- interface RerankResult { index: number; score: number; }
82
-
83
- async function handleRerank(args: { query: string; passages: string[] }): Promise<RerankResult[]> {
84
- if (!rerankerTokenizer || !rerankerModel) throw new Error('reranker not loaded');
85
- const { query, passages } = args;
86
- if (passages.length === 0) return [];
87
-
88
- // Batch path
89
- try {
90
- const queries = passages.map(() => query);
91
- const inputs = rerankerTokenizer(queries, {
92
- text_pair: passages,
93
- padding: true,
94
- truncation: true,
95
- return_tensors: 'pt',
96
- });
97
- const output = await rerankerModel(inputs);
98
- const logits = output.logits ?? output.last_hidden_state;
99
- const data = logits.data as Float32Array | number[];
100
- const results: RerankResult[] = [];
101
- for (let i = 0; i < passages.length; i++) {
102
- const rawLogit = Number(data[i] ?? 0);
103
- results.push({ index: i, score: sigmoid(rawLogit) });
104
- }
105
- results.sort((a, b) => b.score - a.score);
106
- return results;
107
- } catch {
108
- // Per-passage fallback
109
- const results: RerankResult[] = [];
110
- for (let i = 0; i < passages.length; i++) {
111
- try {
112
- const inputs = rerankerTokenizer(query, {
113
- text_pair: passages[i],
114
- padding: true,
115
- truncation: true,
116
- return_tensors: 'pt',
117
- });
118
- const output = await rerankerModel(inputs);
119
- const logits = output.logits ?? output.last_hidden_state;
120
- const rawLogit = logits.data[0] as number;
121
- results.push({ index: i, score: sigmoid(rawLogit) });
122
- } catch {
123
- results.push({ index: i, score: 0 });
124
- }
125
- }
126
- results.sort((a, b) => b.score - a.score);
127
- return results;
128
- }
129
- }
130
-
131
- async function handleExpand(args: { prompt: string; maxNewTokens: number; noRepeatNgramSize: number }): Promise<string> {
132
- if (!expanderPipeline) throw new Error('expander not loaded');
133
- const result = await expanderPipeline(args.prompt, {
134
- max_new_tokens: args.maxNewTokens,
135
- no_repeat_ngram_size: args.noRepeatNgramSize,
136
- });
137
- const text = Array.isArray(result) ? (result[0] as any)?.generated_text ?? '' : '';
138
- return String(text).trim();
139
- }
140
-
141
- // --- Main loop ---
142
-
143
- let shuttingDown = false;
144
- const inflight = new Set<Promise<void>>();
145
-
146
- async function loadModel(): Promise<void> {
147
- switch (role) {
148
- case 'embed': await loadEmbedder(); break;
149
- case 'rerank': await loadReranker(); break;
150
- case 'expand': await loadExpander(); break;
151
- }
152
- }
153
-
154
- async function handleMessage(msg: { id: number; op: WorkerRole; args: any }): Promise<void> {
155
- try {
156
- let result: unknown;
157
- switch (msg.op) {
158
- case 'embed': result = await handleEmbed(msg.args); break;
159
- case 'rerank': result = await handleRerank(msg.args); break;
160
- case 'expand': result = await handleExpand(msg.args); break;
161
- }
162
- parentPort!.postMessage({ id: msg.id, ok: true, result });
163
- } catch (err) {
164
- parentPort!.postMessage({ id: msg.id, ok: false, error: String((err as Error)?.message ?? err) });
165
- }
166
- }
167
-
168
- (async () => {
169
- try {
170
- await loadModel();
171
- parentPort!.postMessage({ ready: true, role });
172
- } catch (err) {
173
- parentPort!.postMessage({ ready: false, role, error: String((err as Error)?.message ?? err) });
174
- process.exit(1);
175
- }
176
- })();
177
-
178
- parentPort.on('message', (msg: any) => {
179
- if (msg?.shutdown) {
180
- shuttingDown = true;
181
- // Wait for in-flight work, then exit
182
- void Promise.allSettled([...inflight]).then(() => {
183
- parentPort!.postMessage({ shutdown: 'done' });
184
- process.exit(0);
185
- });
186
- return;
187
- }
188
- if (shuttingDown) return;
189
- if (typeof msg?.id !== 'number' || typeof msg?.op !== 'string') return;
190
-
191
- const promise = handleMessage(msg);
192
- inflight.add(promise);
193
- void promise.finally(() => inflight.delete(promise));
194
- });
1
+ // Copyright 2026 Robert Winter / Complete Ideas
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * ML worker entry — runs INSIDE a worker_thread.
5
+ *
6
+ * Loaded once per worker, sets up the model for the assigned role
7
+ * (embed | rerank | expand), then handles request messages from the
8
+ * main thread via the parentPort.
9
+ *
10
+ * Protocol:
11
+ * Main thread → worker: { id, op, args } (op matches the worker's role)
12
+ * Worker → main thread: { id, ok: true, result } or { id, ok: false, error }
13
+ * Worker → main thread: { ready: true } (one-time signal after model load)
14
+ * Main thread → worker: { shutdown: true } (drain queue, then terminate)
15
+ *
16
+ * The worker stays loaded — the model lives in memory for the worker's lifetime.
17
+ */
18
+
19
+ import { parentPort, workerData } from 'node:worker_threads';
20
+
21
+ if (!parentPort) {
22
+ throw new Error('ml-worker-entry: must be loaded as a worker_thread');
23
+ }
24
+
25
+ type WorkerRole = 'embed' | 'rerank' | 'expand';
26
+ const role: WorkerRole = workerData?.role;
27
+ if (role !== 'embed' && role !== 'rerank' && role !== 'expand') {
28
+ throw new Error(`ml-worker-entry: invalid role '${role}'`);
29
+ }
30
+
31
+ // --- Lazy model loaders (each worker loads only its own model) ---
32
+
33
+ let embedderPipeline: any = null;
34
+ let rerankerTokenizer: any = null;
35
+ let rerankerModel: any = null;
36
+ let expanderPipeline: any = null;
37
+
38
+ // Inside worker_threads we must use the WASM ONNX backend, not the native one.
39
+ // onnxruntime-node's native bindings store V8 handles that get invalidated when
40
+ // crossing isolate boundaries — calling from a worker crashes with
41
+ // `v8::HandleScope::CreateHandle()` failures. The WASM backend is V8-safe.
42
+ const WORKER_DEVICE = 'wasm' as const;
43
+
44
+ async function loadEmbedder(): Promise<void> {
45
+ const { pipeline } = await import('@huggingface/transformers');
46
+ const modelId = process.env.AWM_EMBED_MODEL ?? 'Xenova/bge-small-en-v1.5';
47
+ embedderPipeline = await pipeline('feature-extraction', modelId, { dtype: 'fp32', device: WORKER_DEVICE });
48
+ }
49
+
50
+ async function loadReranker(): Promise<void> {
51
+ const { AutoTokenizer, AutoModelForSequenceClassification } = await import('@huggingface/transformers');
52
+ const modelId = process.env.AWM_RERANKER_MODEL || 'Xenova/ms-marco-MiniLM-L-6-v2';
53
+ rerankerTokenizer = await AutoTokenizer.from_pretrained(modelId);
54
+ rerankerModel = await AutoModelForSequenceClassification.from_pretrained(modelId, { dtype: 'fp32', device: WORKER_DEVICE });
55
+ }
56
+
57
+ async function loadExpander(): Promise<void> {
58
+ const { pipeline } = await import('@huggingface/transformers');
59
+ expanderPipeline = await pipeline('text2text-generation', 'Xenova/flan-t5-small', { dtype: 'fp32', device: WORKER_DEVICE });
60
+ }
61
+
62
+ // --- Per-role inference handlers ---
63
+
64
+ function sigmoid(x: number): number {
65
+ return 1 / (1 + Math.exp(-x));
66
+ }
67
+
68
+ async function handleEmbed(args: { texts: string[]; pooling: 'cls' | 'mean'; dimensions: number }): Promise<number[][]> {
69
+ if (!embedderPipeline) throw new Error('embedder not loaded');
70
+ const { texts, pooling, dimensions } = args;
71
+ if (texts.length === 0) return [];
72
+ const result = await embedderPipeline(texts, { pooling, normalize: true });
73
+ const data = result.data as Float32Array;
74
+ const vectors: number[][] = [];
75
+ for (let i = 0; i < texts.length; i++) {
76
+ vectors.push(Array.from(data.slice(i * dimensions, (i + 1) * dimensions)));
77
+ }
78
+ return vectors;
79
+ }
80
+
81
+ interface RerankResult { index: number; score: number; }
82
+
83
+ async function handleRerank(args: { query: string; passages: string[] }): Promise<RerankResult[]> {
84
+ if (!rerankerTokenizer || !rerankerModel) throw new Error('reranker not loaded');
85
+ const { query, passages } = args;
86
+ if (passages.length === 0) return [];
87
+
88
+ // Batch path
89
+ try {
90
+ const queries = passages.map(() => query);
91
+ const inputs = rerankerTokenizer(queries, {
92
+ text_pair: passages,
93
+ padding: true,
94
+ truncation: true,
95
+ return_tensors: 'pt',
96
+ });
97
+ const output = await rerankerModel(inputs);
98
+ const logits = output.logits ?? output.last_hidden_state;
99
+ const data = logits.data as Float32Array | number[];
100
+ const results: RerankResult[] = [];
101
+ for (let i = 0; i < passages.length; i++) {
102
+ const rawLogit = Number(data[i] ?? 0);
103
+ results.push({ index: i, score: sigmoid(rawLogit) });
104
+ }
105
+ results.sort((a, b) => b.score - a.score);
106
+ return results;
107
+ } catch {
108
+ // Per-passage fallback
109
+ const results: RerankResult[] = [];
110
+ for (let i = 0; i < passages.length; i++) {
111
+ try {
112
+ const inputs = rerankerTokenizer(query, {
113
+ text_pair: passages[i],
114
+ padding: true,
115
+ truncation: true,
116
+ return_tensors: 'pt',
117
+ });
118
+ const output = await rerankerModel(inputs);
119
+ const logits = output.logits ?? output.last_hidden_state;
120
+ const rawLogit = logits.data[0] as number;
121
+ results.push({ index: i, score: sigmoid(rawLogit) });
122
+ } catch {
123
+ results.push({ index: i, score: 0 });
124
+ }
125
+ }
126
+ results.sort((a, b) => b.score - a.score);
127
+ return results;
128
+ }
129
+ }
130
+
131
+ async function handleExpand(args: { prompt: string; maxNewTokens: number; noRepeatNgramSize: number }): Promise<string> {
132
+ if (!expanderPipeline) throw new Error('expander not loaded');
133
+ const result = await expanderPipeline(args.prompt, {
134
+ max_new_tokens: args.maxNewTokens,
135
+ no_repeat_ngram_size: args.noRepeatNgramSize,
136
+ });
137
+ const text = Array.isArray(result) ? (result[0] as any)?.generated_text ?? '' : '';
138
+ return String(text).trim();
139
+ }
140
+
141
+ // --- Main loop ---
142
+
143
+ let shuttingDown = false;
144
+ const inflight = new Set<Promise<void>>();
145
+
146
+ async function loadModel(): Promise<void> {
147
+ switch (role) {
148
+ case 'embed': await loadEmbedder(); break;
149
+ case 'rerank': await loadReranker(); break;
150
+ case 'expand': await loadExpander(); break;
151
+ }
152
+ }
153
+
154
+ async function handleMessage(msg: { id: number; op: WorkerRole; args: any }): Promise<void> {
155
+ try {
156
+ let result: unknown;
157
+ switch (msg.op) {
158
+ case 'embed': result = await handleEmbed(msg.args); break;
159
+ case 'rerank': result = await handleRerank(msg.args); break;
160
+ case 'expand': result = await handleExpand(msg.args); break;
161
+ }
162
+ parentPort!.postMessage({ id: msg.id, ok: true, result });
163
+ } catch (err) {
164
+ parentPort!.postMessage({ id: msg.id, ok: false, error: String((err as Error)?.message ?? err) });
165
+ }
166
+ }
167
+
168
+ (async () => {
169
+ try {
170
+ await loadModel();
171
+ parentPort!.postMessage({ ready: true, role });
172
+ } catch (err) {
173
+ parentPort!.postMessage({ ready: false, role, error: String((err as Error)?.message ?? err) });
174
+ process.exit(1);
175
+ }
176
+ })();
177
+
178
+ parentPort.on('message', (msg: any) => {
179
+ if (msg?.shutdown) {
180
+ shuttingDown = true;
181
+ // Wait for in-flight work, then exit
182
+ void Promise.allSettled([...inflight]).then(() => {
183
+ parentPort!.postMessage({ shutdown: 'done' });
184
+ process.exit(0);
185
+ });
186
+ return;
187
+ }
188
+ if (shuttingDown) return;
189
+ if (typeof msg?.id !== 'number' || typeof msg?.op !== 'string') return;
190
+
191
+ const promise = handleMessage(msg);
192
+ inflight.add(promise);
193
+ void promise.finally(() => inflight.delete(promise));
194
+ });