@cortexkit/opencode-magic-context 0.40.0 → 0.41.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.
- package/README.md +1 -1
- package/dist/agents/permissions.d.ts +5 -3
- package/dist/agents/permissions.d.ts.map +1 -1
- package/dist/{announcement-5eg4t64s.js → announcement-3g9xg433.js} +2 -2
- package/dist/{conflict-warning-hook-f406ggbw.js → conflict-warning-hook-xj1r88a7.js} +4 -4
- package/dist/{derive-budgets-xk8cy5e6.js → derive-budgets-3xfyphvk.js} +3 -3
- package/dist/features/builtin-commands/commands.d.ts +35 -2
- package/dist/features/builtin-commands/commands.d.ts.map +1 -1
- package/dist/features/magic-context/context-authority.d.ts +10 -0
- package/dist/features/magic-context/context-authority.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/classify.d.ts +1 -0
- package/dist/features/magic-context/dreamer/classify.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/lease.d.ts +12 -27
- package/dist/features/magic-context/dreamer/lease.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/map-memories-prompt.d.ts +1 -1
- package/dist/features/magic-context/dreamer/map-memories-prompt.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/map-memories.d.ts +2 -0
- package/dist/features/magic-context/dreamer/map-memories.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/memory-claim-safety.d.ts +9 -0
- package/dist/features/magic-context/dreamer/memory-claim-safety.d.ts.map +1 -0
- package/dist/features/magic-context/dreamer/task-executor.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/task-registry.d.ts +2 -0
- package/dist/features/magic-context/dreamer/task-registry.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/verify-prompt.d.ts +7 -2
- package/dist/features/magic-context/dreamer/verify-prompt.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/verify.d.ts +13 -10
- package/dist/features/magic-context/dreamer/verify.d.ts.map +1 -1
- package/dist/features/magic-context/memory/embedding-local.d.ts +19 -0
- package/dist/features/magic-context/memory/embedding-local.d.ts.map +1 -1
- package/dist/features/magic-context/memory/storage-memory-verifications.d.ts +5 -1
- package/dist/features/magic-context/memory/storage-memory-verifications.d.ts.map +1 -1
- package/dist/features/magic-context/memory/storage-memory.d.ts.map +1 -1
- package/dist/features/magic-context/migrations.d.ts.map +1 -1
- package/dist/features/magic-context/project-embedding-registry.d.ts +3 -0
- package/dist/features/magic-context/project-embedding-registry.d.ts.map +1 -1
- package/dist/features/magic-context/storage-db.d.ts +1 -1
- package/dist/features/magic-context/storage-db.d.ts.map +1 -1
- package/dist/features/magic-context/storage-identity-merge.d.ts.map +1 -1
- package/dist/features/magic-context/storage-meta-persisted.d.ts +29 -3
- package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
- package/dist/features/magic-context/storage-meta-session.d.ts.map +1 -1
- package/dist/features/magic-context/storage-meta.d.ts +1 -1
- package/dist/features/magic-context/storage-meta.d.ts.map +1 -1
- package/dist/features/magic-context/storage-tags.d.ts +7 -0
- package/dist/features/magic-context/storage-tags.d.ts.map +1 -1
- package/dist/features/magic-context/storage.d.ts +1 -1
- package/dist/features/magic-context/storage.d.ts.map +1 -1
- package/dist/hooks/magic-context/channel2-delivery.d.ts +2 -4
- package/dist/hooks/magic-context/channel2-delivery.d.ts.map +1 -1
- package/dist/hooks/magic-context/command-handler.d.ts +7 -0
- package/dist/hooks/magic-context/command-handler.d.ts.map +1 -1
- package/dist/hooks/magic-context/compaction-marker-manager.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-incremental.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-recomp.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-trigger.d.ts +1 -0
- package/dist/hooks/magic-context/compartment-trigger.d.ts.map +1 -1
- package/dist/hooks/magic-context/ctx-reduce-nudge.d.ts +15 -14
- package/dist/hooks/magic-context/ctx-reduce-nudge.d.ts.map +1 -1
- package/dist/hooks/magic-context/emergency-drop.d.ts +6 -8
- package/dist/hooks/magic-context/emergency-drop.d.ts.map +1 -1
- package/dist/hooks/magic-context/event-resolvers.d.ts +2 -2
- package/dist/hooks/magic-context/execute-status.d.ts +4 -1
- package/dist/hooks/magic-context/execute-status.d.ts.map +1 -1
- package/dist/hooks/magic-context/heuristic-cleanup.d.ts +6 -0
- package/dist/hooks/magic-context/heuristic-cleanup.d.ts.map +1 -1
- package/dist/hooks/magic-context/hook-handlers.d.ts +21 -8
- package/dist/hooks/magic-context/hook-handlers.d.ts.map +1 -1
- package/dist/hooks/magic-context/hook.d.ts +16 -1
- package/dist/hooks/magic-context/hook.d.ts.map +1 -1
- package/dist/hooks/magic-context/inject-compartments.d.ts +6 -16
- package/dist/hooks/magic-context/inject-compartments.d.ts.map +1 -1
- package/dist/hooks/magic-context/maintenance-authority.d.ts +3 -0
- package/dist/hooks/magic-context/maintenance-authority.d.ts.map +1 -0
- package/dist/hooks/magic-context/module-transport.d.ts.map +1 -1
- package/dist/hooks/magic-context/module-wire.d.ts.map +1 -1
- package/dist/hooks/magic-context/protected-tail-boundary.d.ts.map +1 -1
- package/dist/hooks/magic-context/read-session-true-raw-tokens.d.ts +1 -0
- package/dist/hooks/magic-context/read-session-true-raw-tokens.d.ts.map +1 -1
- package/dist/hooks/magic-context/rust-mode-transform.d.ts +9 -0
- package/dist/hooks/magic-context/rust-mode-transform.d.ts.map +1 -1
- package/dist/hooks/magic-context/strip-content.d.ts +8 -0
- package/dist/hooks/magic-context/strip-content.d.ts.map +1 -1
- package/dist/hooks/magic-context/stripped-command.d.ts +19 -0
- package/dist/hooks/magic-context/stripped-command.d.ts.map +1 -0
- package/dist/hooks/magic-context/supersession-reclaim.d.ts +11 -0
- package/dist/hooks/magic-context/supersession-reclaim.d.ts.map +1 -1
- package/dist/hooks/magic-context/tag-messages.d.ts +6 -0
- package/dist/hooks/magic-context/tag-messages.d.ts.map +1 -1
- package/dist/hooks/magic-context/tail-hygiene-walk.d.ts +11 -0
- package/dist/hooks/magic-context/tail-hygiene-walk.d.ts.map +1 -1
- package/dist/hooks/magic-context/temporal-awareness.d.ts +1 -1
- package/dist/hooks/magic-context/temporal-awareness.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform-operations.d.ts +1 -1
- package/dist/hooks/magic-context/transform-operations.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform-postprocess-phase.d.ts +5 -1
- package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform.d.ts.map +1 -1
- package/dist/{index-pck9vadr.js → index-26ds03jd.js} +1 -1
- package/dist/{index-mg1yx1ph.js → index-2bykz3bx.js} +2 -2
- package/dist/{index-mmrhhk9a.js → index-37yzyeca.js} +36 -8
- package/dist/{index-btj2qhmc.js → index-88za3sz8.js} +3 -3
- package/dist/index-b4gv9f7g.js +284 -0
- package/dist/{index-ehpzdy0v.js → index-bf799gbq.js} +8 -1
- package/dist/{index-1h9t9xb5.js → index-dmgbar7r.js} +2 -2
- package/dist/{index-qf20mv7e.js → index-dmvy29z0.js} +1 -1
- package/dist/{index-sw8gnq34.js → index-jmwq2wxr.js} +2 -2
- package/dist/{index-3dkzzh0t.js → index-knna83k0.js} +1 -1
- package/dist/{index-axpym70t.js → index-w8rkbhn6.js} +617 -324
- package/dist/{index-rw0qrkvq.js → index-x9dmzyp5.js} +17 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2901 -2305
- package/dist/{model-resolution-r9jbcz86.js → model-resolution-jgrkt0z2.js} +3 -1
- package/dist/plugin/dream-timer.d.ts.map +1 -1
- package/dist/plugin/hooks/create-session-hooks.d.ts +14 -0
- package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
- package/dist/plugin/rpc-handlers.d.ts +10 -0
- package/dist/plugin/rpc-handlers.d.ts.map +1 -1
- package/dist/{recomp-orchestrator-dmyvqekw.js → recomp-orchestrator-1w4xj6jt.js} +7 -7
- package/dist/{safe-notification-target-tepzc2xa.js → safe-notification-target-p0c0csxw.js} +3 -3
- package/dist/{send-session-notification-6v5b72b4.js → send-session-notification-kn34g7mx.js} +4 -4
- package/dist/shared/data-path.d.ts +12 -3
- package/dist/shared/data-path.d.ts.map +1 -1
- package/dist/shared/logger.d.ts.map +1 -1
- package/dist/shared/model-resolution.d.ts +1 -0
- package/dist/shared/model-resolution.d.ts.map +1 -1
- package/dist/shared/rpc-types.d.ts +5 -0
- package/dist/shared/rpc-types.d.ts.map +1 -1
- package/dist/shared/rust-mode-status.d.ts +3 -0
- package/dist/shared/rust-mode-status.d.ts.map +1 -0
- package/dist/shared/status-detail-text.d.ts.map +1 -1
- package/dist/shared/subagent-runner.d.ts +4 -0
- package/dist/shared/subagent-runner.d.ts.map +1 -1
- package/dist/shared/write-transaction-timing.d.ts +11 -0
- package/dist/shared/write-transaction-timing.d.ts.map +1 -0
- package/dist/{storage-meta-session-yvtbaw7c.js → storage-meta-session-snbzd26z.js} +4 -4
- package/dist/{task-config-a1qmj9bx.js → task-config-ty08ptc2.js} +2 -2
- package/dist/tools/ctx-expand/tools.d.ts.map +1 -1
- package/dist/tools/ctx-memory/tools.d.ts.map +1 -1
- package/dist/tui/data/context-db.d.ts +10 -3
- package/dist/tui/data/context-db.d.ts.map +1 -1
- package/dist/tui-compiled/data/context-db.d.ts +10 -3
- package/dist/tui-compiled/data/context-db.d.ts.map +1 -1
- package/package.json +2 -1
- package/src/agents/permissions.ts +14 -4
- package/src/config/index.test.ts +38 -0
- package/src/features/magic-context/dreamer/task-registry.ts +2 -0
- package/src/shared/data-path.test.ts +44 -4
- package/src/shared/data-path.ts +54 -11
- package/src/shared/logger.test.ts +15 -0
- package/src/shared/logger.ts +9 -4
- package/src/shared/model-resolution.test.ts +39 -0
- package/src/shared/model-resolution.ts +8 -0
- package/src/shared/redaction.ts +2 -2
- package/src/shared/rpc-types.ts +5 -0
- package/src/shared/rust-mode-status.ts +3 -0
- package/src/shared/status-detail-text.test.ts +13 -0
- package/src/shared/status-detail-text.ts +2 -0
- package/src/shared/subagent-runner.ts +4 -0
- package/src/shared/write-transaction-timing.ts +25 -0
- package/src/tui/data/context-db.test.ts +31 -1
- package/src/tui/data/context-db.ts +17 -61
- package/src/tui/index.tsx +36 -5
- package/src/tui-compiled/data/context-db.ts +17 -61
- package/src/tui-compiled/index.tsx +229 -191
- package/dist/index-8pqfcghm.js +0 -81
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
getLegacyOpenCodeMagicContextStorageDir,
|
|
10
10
|
getMagicContextLogPath,
|
|
11
11
|
getMagicContextStorageDir,
|
|
12
|
+
getMagicContextStorageResolution,
|
|
12
13
|
getOpenCodeCacheDir,
|
|
13
14
|
getOpenCodeStorageDir,
|
|
14
15
|
getProjectMagicContextDir,
|
|
@@ -18,6 +19,7 @@ import {
|
|
|
18
19
|
const savedEnv = {
|
|
19
20
|
XDG_CACHE_HOME: process.env.XDG_CACHE_HOME,
|
|
20
21
|
XDG_DATA_HOME: process.env.XDG_DATA_HOME,
|
|
22
|
+
MAGIC_CONTEXT_STORAGE_DIR: process.env.MAGIC_CONTEXT_STORAGE_DIR,
|
|
21
23
|
LOCALAPPDATA: process.env.LOCALAPPDATA,
|
|
22
24
|
MAGIC_CONTEXT_LOG_PATH: process.env.MAGIC_CONTEXT_LOG_PATH,
|
|
23
25
|
MAGIC_CONTEXT_TEST_DATA_DIR: process.env.MAGIC_CONTEXT_TEST_DATA_DIR,
|
|
@@ -28,11 +30,14 @@ describe("data-path", () => {
|
|
|
28
30
|
beforeEach(() => {
|
|
29
31
|
process.env.XDG_CACHE_HOME = undefined;
|
|
30
32
|
process.env.XDG_DATA_HOME = undefined;
|
|
33
|
+
delete process.env.MAGIC_CONTEXT_TEST_DATA_DIR;
|
|
34
|
+
delete process.env.NODE_ENV;
|
|
31
35
|
process.env.LOCALAPPDATA = undefined;
|
|
32
36
|
process.env.MAGIC_CONTEXT_LOG_PATH = undefined;
|
|
33
37
|
// Bun's env handling: explicit delete for unset
|
|
34
38
|
delete process.env.XDG_CACHE_HOME;
|
|
35
39
|
delete process.env.XDG_DATA_HOME;
|
|
40
|
+
delete process.env.MAGIC_CONTEXT_STORAGE_DIR;
|
|
36
41
|
delete process.env.LOCALAPPDATA;
|
|
37
42
|
delete process.env.MAGIC_CONTEXT_LOG_PATH;
|
|
38
43
|
});
|
|
@@ -140,17 +145,52 @@ describe("data-path", () => {
|
|
|
140
145
|
);
|
|
141
146
|
});
|
|
142
147
|
|
|
143
|
-
test("
|
|
144
|
-
|
|
145
|
-
|
|
148
|
+
test("test storage isolation takes precedence over MAGIC_CONTEXT_STORAGE_DIR", () => {
|
|
149
|
+
process.env.MAGIC_CONTEXT_TEST_DATA_DIR = "/tmp/mc-test-isolation";
|
|
150
|
+
process.env.MAGIC_CONTEXT_STORAGE_DIR = "/tmp/production-shared-data";
|
|
151
|
+
expect(getMagicContextStorageDir()).toBe(
|
|
152
|
+
path.join("/tmp/mc-test-isolation", "cortexkit", "magic-context"),
|
|
153
|
+
);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test("a per-test XDG_DATA_HOME overrides the preload root without escaping test isolation", () => {
|
|
146
157
|
process.env.MAGIC_CONTEXT_TEST_DATA_DIR = "/tmp/mc-test-isolation";
|
|
158
|
+
process.env.XDG_DATA_HOME = "/tmp/custom-test-data";
|
|
159
|
+
process.env.MAGIC_CONTEXT_STORAGE_DIR = "/tmp/production-shared-data";
|
|
160
|
+
expect(getMagicContextStorageResolution()).toEqual({
|
|
161
|
+
path: path.join("/tmp/custom-test-data", "cortexkit", "magic-context"),
|
|
162
|
+
source: "test isolation",
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test("getMagicContextStorageDir honors XDG_DATA_HOME", () => {
|
|
147
167
|
process.env.XDG_DATA_HOME = "/tmp/custom-data";
|
|
148
168
|
expect(getMagicContextStorageDir()).toBe(
|
|
149
169
|
path.join("/tmp/custom-data", "cortexkit", "magic-context"),
|
|
150
170
|
);
|
|
151
171
|
});
|
|
152
172
|
|
|
153
|
-
test("getMagicContextStorageDir honors
|
|
173
|
+
test("getMagicContextStorageDir honors MAGIC_CONTEXT_STORAGE_DIR", () => {
|
|
174
|
+
process.env.XDG_DATA_HOME = "/tmp/private-agent-data";
|
|
175
|
+
process.env.MAGIC_CONTEXT_STORAGE_DIR = "/tmp/shared-magic-context";
|
|
176
|
+
expect(getMagicContextStorageDir()).toBe("/tmp/shared-magic-context");
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("MAGIC_CONTEXT_STORAGE_DIR takes precedence over XDG_DATA_HOME", () => {
|
|
180
|
+
process.env.XDG_DATA_HOME = "/tmp/private-agent-data";
|
|
181
|
+
process.env.MAGIC_CONTEXT_STORAGE_DIR = "/tmp/shared-magic-context";
|
|
182
|
+
expect(getMagicContextStorageDir()).toBe("/tmp/shared-magic-context");
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test("relative MAGIC_CONTEXT_STORAGE_DIR is rejected", () => {
|
|
186
|
+
process.env.MAGIC_CONTEXT_STORAGE_DIR = "./shared-magic-context";
|
|
187
|
+
expect(() => getMagicContextStorageDir()).toThrow(
|
|
188
|
+
"MAGIC_CONTEXT_STORAGE_DIR must be an absolute path",
|
|
189
|
+
);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test("blank MAGIC_CONTEXT_STORAGE_DIR falls back to XDG_DATA_HOME", () => {
|
|
193
|
+
process.env.MAGIC_CONTEXT_STORAGE_DIR = " ";
|
|
154
194
|
process.env.XDG_DATA_HOME = "/tmp/custom-data";
|
|
155
195
|
expect(getMagicContextStorageDir()).toBe(
|
|
156
196
|
path.join("/tmp/custom-data", "cortexkit", "magic-context"),
|
package/src/shared/data-path.ts
CHANGED
|
@@ -166,7 +166,9 @@ export function getOpenCodeStorageDir(): string {
|
|
|
166
166
|
* - Shared Dreamer runs (one per project per machine)
|
|
167
167
|
* - Future cross-harness session migration
|
|
168
168
|
*
|
|
169
|
-
*
|
|
169
|
+
* Resolution precedence: test isolation, MAGIC_CONTEXT_STORAGE_DIR, XDG data
|
|
170
|
+
* home, then the platform default. The explicit override is a complete storage
|
|
171
|
+
* directory and must be absolute so every process on a host selects one store.
|
|
170
172
|
*
|
|
171
173
|
* TEST-ISOLATION GUARD. `openDatabase()` has been guarded in
|
|
172
174
|
* `resolveDatabasePath()` since the 2026-06-01 (v26) and 2026-06-19 (v41)
|
|
@@ -192,20 +194,61 @@ export function getOpenCodeStorageDir(): string {
|
|
|
192
194
|
* `PRAGMA integrity_check`, announcements, the models.dev cache) are covered
|
|
193
195
|
* too — they never go through the DB resolver.
|
|
194
196
|
*
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
+
* In production, MAGIC_CONTEXT_STORAGE_DIR takes precedence over XDG_DATA_HOME.
|
|
198
|
+
* Test isolation remains authoritative so an ambient override cannot redirect
|
|
199
|
+
* a test into a user's real shared database.
|
|
197
200
|
*/
|
|
198
|
-
export
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
201
|
+
export type MagicContextStorageSource =
|
|
202
|
+
| "test isolation"
|
|
203
|
+
| "environment override"
|
|
204
|
+
| "XDG_DATA_HOME"
|
|
205
|
+
| "platform default";
|
|
206
|
+
|
|
207
|
+
export interface MagicContextStorageResolution {
|
|
208
|
+
path: string;
|
|
209
|
+
source: MagicContextStorageSource;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function getMagicContextStorageResolution(): MagicContextStorageResolution {
|
|
213
|
+
const testDataDir = process.env.MAGIC_CONTEXT_TEST_DATA_DIR?.trim();
|
|
214
|
+
if (testDataDir) {
|
|
215
|
+
const perTestDataHome = process.env.XDG_DATA_HOME?.trim();
|
|
216
|
+
if (perTestDataHome && path.resolve(perTestDataHome) !== path.resolve(testDataDir)) {
|
|
217
|
+
return {
|
|
218
|
+
path: path.join(perTestDataHome, "cortexkit", "magic-context"),
|
|
219
|
+
source: "test isolation",
|
|
220
|
+
};
|
|
203
221
|
}
|
|
204
|
-
|
|
205
|
-
|
|
222
|
+
return {
|
|
223
|
+
path: path.join(testDataDir, "cortexkit", "magic-context"),
|
|
224
|
+
source: "test isolation",
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
if (process.env.NODE_ENV === "test") {
|
|
228
|
+
return { path: getTestBackstopStorageDir(), source: "test isolation" };
|
|
229
|
+
}
|
|
230
|
+
const explicitStorageDir = process.env.MAGIC_CONTEXT_STORAGE_DIR?.trim();
|
|
231
|
+
if (explicitStorageDir) {
|
|
232
|
+
if (!path.isAbsolute(explicitStorageDir)) {
|
|
233
|
+
throw new Error("MAGIC_CONTEXT_STORAGE_DIR must be an absolute path");
|
|
206
234
|
}
|
|
235
|
+
return { path: explicitStorageDir, source: "environment override" };
|
|
236
|
+
}
|
|
237
|
+
const xdgDataHome = process.env.XDG_DATA_HOME?.trim();
|
|
238
|
+
if (xdgDataHome) {
|
|
239
|
+
return {
|
|
240
|
+
path: path.join(xdgDataHome, "cortexkit", "magic-context"),
|
|
241
|
+
source: "XDG_DATA_HOME",
|
|
242
|
+
};
|
|
207
243
|
}
|
|
208
|
-
return
|
|
244
|
+
return {
|
|
245
|
+
path: path.join(os.homedir(), ".local", "share", "cortexkit", "magic-context"),
|
|
246
|
+
source: "platform default",
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export function getMagicContextStorageDir(): string {
|
|
251
|
+
return getMagicContextStorageResolution().path;
|
|
209
252
|
}
|
|
210
253
|
|
|
211
254
|
let testBackstopStorageDir: string | null = null;
|
|
@@ -30,6 +30,10 @@ const logger = await import(loggerModuleUrl);
|
|
|
30
30
|
const logPath = path.join(root, "nested", "magic-context.log");
|
|
31
31
|
process.env.MAGIC_CONTEXT_LOG_PATH = logPath;
|
|
32
32
|
logger.log("first");
|
|
33
|
+
logger.log(
|
|
34
|
+
"tokens.input=45000 api_key=sk-proj-abcdefghijklmnopqrstuvwxyzABCDEFGH",
|
|
35
|
+
{ authorization: "Bearer abcdefghijklmnop", totalInputTokens: 132000 },
|
|
36
|
+
);
|
|
33
37
|
logger.flushLogger();
|
|
34
38
|
const healthyDiagnostics = logger.getLoggerDiagnostics();
|
|
35
39
|
|
|
@@ -102,6 +106,17 @@ describe("logger", () => {
|
|
|
102
106
|
expect(result.content).toContain("second");
|
|
103
107
|
});
|
|
104
108
|
|
|
109
|
+
test("redacts secrets while preserving numeric diagnostics", async () => {
|
|
110
|
+
const result = await runLoggerScenario("diagnostics");
|
|
111
|
+
|
|
112
|
+
expect(result.content).toContain("tokens.input=45000");
|
|
113
|
+
expect(result.content).toContain('"totalInputTokens":132000');
|
|
114
|
+
expect(result.content).toContain("<REDACTED:api_key>");
|
|
115
|
+
expect(result.content).toContain("<REDACTED:authorization>");
|
|
116
|
+
expect(result.content).not.toContain("abcdefghijklmnopqrstuvwxyzABCDEFGH");
|
|
117
|
+
expect(result.content).not.toContain("abcdefghijklmnop");
|
|
118
|
+
});
|
|
119
|
+
|
|
105
120
|
test("reports swallowed writes while healthy writes leave the counter at zero", async () => {
|
|
106
121
|
const result = await runLoggerScenario("diagnostics");
|
|
107
122
|
|
package/src/shared/logger.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { getMagicContextLogPath } from "./data-path";
|
|
4
|
+
import { sanitizeConfigValue, sanitizeDiagnosticText } from "./redaction";
|
|
4
5
|
|
|
5
6
|
const isTestEnv = process.env.NODE_ENV === "test";
|
|
6
7
|
|
|
@@ -22,7 +23,9 @@ let lastErrorTime: string | null = null;
|
|
|
22
23
|
function recordSwallowedWrite(error: unknown): void {
|
|
23
24
|
try {
|
|
24
25
|
swallowedWriteCount++;
|
|
25
|
-
lastErrorMessage =
|
|
26
|
+
lastErrorMessage = sanitizeDiagnosticText(
|
|
27
|
+
error instanceof Error ? error.message : String(error),
|
|
28
|
+
);
|
|
26
29
|
lastErrorTime = new Date().toISOString();
|
|
27
30
|
} catch {
|
|
28
31
|
// Diagnostics must not make the logger throw either.
|
|
@@ -66,9 +69,11 @@ export function log(message: string, data?: unknown): void {
|
|
|
66
69
|
data === undefined
|
|
67
70
|
? ""
|
|
68
71
|
: data instanceof Error
|
|
69
|
-
? ` ${
|
|
70
|
-
|
|
71
|
-
|
|
72
|
+
? ` ${sanitizeDiagnosticText(
|
|
73
|
+
`${data.message}${data.stack ? `\n${data.stack}` : ""}`,
|
|
74
|
+
)}`
|
|
75
|
+
: ` ${JSON.stringify(sanitizeConfigValue(data))}`;
|
|
76
|
+
buffer.push(`[${timestamp}] ${sanitizeDiagnosticText(message)}${serialized}\n`);
|
|
72
77
|
if (buffer.length >= BUFFER_SIZE_LIMIT) {
|
|
73
78
|
flush();
|
|
74
79
|
} else {
|
|
@@ -4,10 +4,30 @@ import {
|
|
|
4
4
|
normalizeModelEntry,
|
|
5
5
|
resolveDreamerTaskModel,
|
|
6
6
|
resolveFallbackEntries,
|
|
7
|
+
resolveHistorianAgentOverrides,
|
|
7
8
|
resolveHistorianModel,
|
|
8
9
|
} from "./model-resolution";
|
|
9
10
|
|
|
10
11
|
describe("model-resolution", () => {
|
|
12
|
+
test("pins the historian calibration defaults while preserving explicit overrides", () => {
|
|
13
|
+
expect(resolveHistorianAgentOverrides(undefined)).toEqual({
|
|
14
|
+
temperature: 0.1,
|
|
15
|
+
maxTokens: 32_000,
|
|
16
|
+
});
|
|
17
|
+
expect(
|
|
18
|
+
resolveHistorianAgentOverrides({
|
|
19
|
+
temperature: 0.2,
|
|
20
|
+
maxTokens: 16_000,
|
|
21
|
+
opencode: { model: "google/flash", variant: "low" },
|
|
22
|
+
}),
|
|
23
|
+
).toEqual({
|
|
24
|
+
temperature: 0.2,
|
|
25
|
+
maxTokens: 16_000,
|
|
26
|
+
model: "google/flash",
|
|
27
|
+
variant: "low",
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
11
31
|
test("normalizes string and object entries to the same model identity", () => {
|
|
12
32
|
expect(normalizeModelEntry("anthropic/sonnet", "opencode")).toEqual({
|
|
13
33
|
model: "anthropic/sonnet",
|
|
@@ -36,6 +56,25 @@ describe("model-resolution", () => {
|
|
|
36
56
|
]);
|
|
37
57
|
});
|
|
38
58
|
|
|
59
|
+
test("applies the flash-calibrated historian generation triple while preserving overrides", () => {
|
|
60
|
+
expect(
|
|
61
|
+
resolveHistorianAgentOverrides({
|
|
62
|
+
opencode: { model: { model: "google/flash", variant: "fast" } },
|
|
63
|
+
pi: { model: "pi/ignored" },
|
|
64
|
+
two_pass: true,
|
|
65
|
+
}),
|
|
66
|
+
).toEqual({
|
|
67
|
+
temperature: 0.1,
|
|
68
|
+
maxTokens: 32_000,
|
|
69
|
+
two_pass: true,
|
|
70
|
+
model: "google/flash",
|
|
71
|
+
variant: "fast",
|
|
72
|
+
});
|
|
73
|
+
expect(
|
|
74
|
+
resolveHistorianAgentOverrides({ temperature: 0.2, maxTokens: 12_000 }),
|
|
75
|
+
).toMatchObject({ temperature: 0.2, maxTokens: 12_000 });
|
|
76
|
+
});
|
|
77
|
+
|
|
39
78
|
test("does not inherit a block qualifier into unqualified fallbacks", () => {
|
|
40
79
|
const config = {
|
|
41
80
|
historian: {
|
|
@@ -225,3 +225,11 @@ export function resolveOpenCodeAgentOverrides(agent: unknown): Record<string, un
|
|
|
225
225
|
...(primary?.qualifier ? { variant: primary.qualifier } : {}),
|
|
226
226
|
};
|
|
227
227
|
}
|
|
228
|
+
|
|
229
|
+
export function resolveHistorianAgentOverrides(agent: unknown): Record<string, unknown> {
|
|
230
|
+
return {
|
|
231
|
+
temperature: 0.1,
|
|
232
|
+
maxTokens: 32_000,
|
|
233
|
+
...resolveOpenCodeAgentOverrides(agent),
|
|
234
|
+
};
|
|
235
|
+
}
|
package/src/shared/redaction.ts
CHANGED
|
@@ -108,7 +108,7 @@ export function isSecretKey(key: string): boolean {
|
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
export function sanitizePathString(value: string): string {
|
|
111
|
-
const home = homedir();
|
|
111
|
+
const home = process.env.HOME || process.env.USERPROFILE || homedir();
|
|
112
112
|
const username = userInfo().username;
|
|
113
113
|
let sanitized = value;
|
|
114
114
|
if (home) {
|
|
@@ -132,7 +132,7 @@ const SECRET_TEXT_PATTERNS: Array<{
|
|
|
132
132
|
replacement: "<ANTHROPIC_API_KEY_REDACTED>",
|
|
133
133
|
},
|
|
134
134
|
{
|
|
135
|
-
pattern: /\bsk-(?:proj-)?[A-Za-z0-9_-]{
|
|
135
|
+
pattern: /\bsk-(?:proj-)?[A-Za-z0-9_-]{12,}/g,
|
|
136
136
|
replacement: "<OPENAI_API_KEY_REDACTED>",
|
|
137
137
|
},
|
|
138
138
|
{
|
package/src/shared/rpc-types.ts
CHANGED
|
@@ -141,12 +141,17 @@ export interface SidebarSnapshot {
|
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
export interface StatusDetail extends SidebarSnapshot {
|
|
144
|
+
/** True when Rust authority has rerouted host tool and historian paths to the module. */
|
|
145
|
+
hostBackendsModuleSide?: boolean;
|
|
144
146
|
/** User-owned model profile selected for this project, or null for the base config. */
|
|
145
147
|
activeProfile: string | null;
|
|
146
148
|
tagCounter: number;
|
|
147
149
|
activeTags: number;
|
|
148
150
|
droppedTags: number;
|
|
149
151
|
totalTags: number;
|
|
152
|
+
/** False when Rust authority supplies only the exact total; active/dropped host-mirror
|
|
153
|
+
* counts are not presented as module truth. Omitted by older RPC servers means true. */
|
|
154
|
+
tagCountsAuthoritative?: boolean;
|
|
150
155
|
activeBytes: number;
|
|
151
156
|
lastResponseTime: number;
|
|
152
157
|
lastNudgeTokens: number;
|
|
@@ -79,4 +79,17 @@ describe("formatStatusDetailMarkdown", () => {
|
|
|
79
79
|
- **Tags:** 4 active, 1 dropped; 2 pending drops
|
|
80
80
|
- **Execute threshold:** 65.0%`);
|
|
81
81
|
});
|
|
82
|
+
|
|
83
|
+
test("shows module-routed host paths only in Rust-mode chat fallback", () => {
|
|
84
|
+
const rustStatus = formatStatusDetailMarkdown({
|
|
85
|
+
...STATUS_FIXTURE,
|
|
86
|
+
hostBackendsModuleSide: true,
|
|
87
|
+
});
|
|
88
|
+
const tsStatus = formatStatusDetailMarkdown(STATUS_FIXTURE);
|
|
89
|
+
|
|
90
|
+
expect(rustStatus).toContain(
|
|
91
|
+
"Host backends → MODULE: ctx_memory, ctx_note; historian: module-side",
|
|
92
|
+
);
|
|
93
|
+
expect(tsStatus).not.toContain("Host backends → MODULE");
|
|
94
|
+
});
|
|
82
95
|
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { StatusDetail } from "./rpc-types";
|
|
2
|
+
import { RUST_MODE_HOST_PATHS_LINE } from "./rust-mode-status";
|
|
2
3
|
|
|
3
4
|
function formatCount(value: number): string {
|
|
4
5
|
return Math.round(value).toLocaleString();
|
|
@@ -39,6 +40,7 @@ export function formatStatusDetailMarkdown(detail: StatusDetail): string {
|
|
|
39
40
|
`- **Usage:** ${detail.usagePercentage.toFixed(1)}% (${formatCount(detail.inputTokens)} / ${usableLimit})`,
|
|
40
41
|
`- **Cache lane:** ${formatCacheLane(detail)}`,
|
|
41
42
|
`- **Historian:** ${[historianState, ...historianDetails].join("; ")}`,
|
|
43
|
+
...(detail.hostBackendsModuleSide ? [`- ${RUST_MODE_HOST_PATHS_LINE}`] : []),
|
|
42
44
|
`- **Memory:** ${formatCount(detail.memoryCount)} active; ${formatCount(detail.memoryBlockCount)} injected`,
|
|
43
45
|
`- **Tags:** ${formatCount(detail.activeTags)} active, ${formatCount(detail.droppedTags)} dropped; ${formatCount(detail.pendingOpsCount)} pending drops`,
|
|
44
46
|
`- **Execute threshold:** ${detail.executeThreshold.toFixed(1)}%${detail.executeThresholdClamped ? " (clamped)" : ""}`,
|
|
@@ -67,6 +67,10 @@ export interface SubagentRunOptions {
|
|
|
67
67
|
model?: string | undefined;
|
|
68
68
|
fallbackModels?: readonly ModelInput[];
|
|
69
69
|
timeoutMs?: number | undefined;
|
|
70
|
+
/** Sampling temperature for the provider request when the harness can enforce it. */
|
|
71
|
+
temperature?: number | undefined;
|
|
72
|
+
/** Requested output-token budget for the provider request. */
|
|
73
|
+
maxOutputTokens?: number | undefined;
|
|
70
74
|
cwd?: string | undefined;
|
|
71
75
|
signal?: AbortSignal | undefined;
|
|
72
76
|
/**
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { log } from "./logger";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A one-second threshold catches transactions that can noticeably hold back
|
|
5
|
+
* sibling writers without turning normal SQLite work into log noise.
|
|
6
|
+
*/
|
|
7
|
+
export const SLOW_WRITE_TRANSACTION_THRESHOLD_MS = 1_000;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Log a slow write only after its COMMIT has completed. The timing diagnostic is
|
|
11
|
+
* deliberately best-effort so it can never change the transaction's behavior.
|
|
12
|
+
*/
|
|
13
|
+
export function logSlowWriteTransaction(
|
|
14
|
+
site: string,
|
|
15
|
+
startedAt: number,
|
|
16
|
+
thresholdMs = SLOW_WRITE_TRANSACTION_THRESHOLD_MS,
|
|
17
|
+
): void {
|
|
18
|
+
try {
|
|
19
|
+
const durationMs = performance.now() - startedAt;
|
|
20
|
+
if (durationMs < thresholdMs) return;
|
|
21
|
+
log(`[magic-context] slow write transaction: site=${site} held=${durationMs.toFixed(1)}ms`);
|
|
22
|
+
} catch {
|
|
23
|
+
// Timing diagnostics must never make a committed write appear to fail.
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -4,9 +4,17 @@ import { tmpdir } from "node:os";
|
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { MagicContextRpcServer } from "../../shared/rpc-server";
|
|
6
6
|
import type { SidebarSnapshot } from "../../shared/rpc-types";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
closeRpc,
|
|
9
|
+
getCompartmentCount,
|
|
10
|
+
initRpcClient,
|
|
11
|
+
loadSidebarSnapshot,
|
|
12
|
+
loadStatusDetail,
|
|
13
|
+
} from "./context-db";
|
|
8
14
|
|
|
9
15
|
const originalXdgDataHome = process.env.XDG_DATA_HOME;
|
|
16
|
+
const originalTestDataDir = process.env.MAGIC_CONTEXT_TEST_DATA_DIR;
|
|
17
|
+
const originalStorageDir = process.env.MAGIC_CONTEXT_STORAGE_DIR;
|
|
10
18
|
const tempDirs: string[] = [];
|
|
11
19
|
const servers: MagicContextRpcServer[] = [];
|
|
12
20
|
|
|
@@ -18,12 +26,19 @@ afterEach(() => {
|
|
|
18
26
|
}
|
|
19
27
|
if (originalXdgDataHome === undefined) delete process.env.XDG_DATA_HOME;
|
|
20
28
|
else process.env.XDG_DATA_HOME = originalXdgDataHome;
|
|
29
|
+
if (originalTestDataDir === undefined) delete process.env.MAGIC_CONTEXT_TEST_DATA_DIR;
|
|
30
|
+
else process.env.MAGIC_CONTEXT_TEST_DATA_DIR = originalTestDataDir;
|
|
31
|
+
if (originalStorageDir === undefined) delete process.env.MAGIC_CONTEXT_STORAGE_DIR;
|
|
32
|
+
else process.env.MAGIC_CONTEXT_STORAGE_DIR = originalStorageDir;
|
|
21
33
|
});
|
|
22
34
|
|
|
23
35
|
function makeDataHome(): string {
|
|
24
36
|
const dir = mkdtempSync(join(tmpdir(), "mc-context-db-"));
|
|
25
37
|
tempDirs.push(dir);
|
|
26
38
|
process.env.XDG_DATA_HOME = dir;
|
|
39
|
+
// The shared resolver gives test isolation priority. Keep both values
|
|
40
|
+
// aligned so the RPC client and fixture server exercise one directory.
|
|
41
|
+
process.env.MAGIC_CONTEXT_TEST_DATA_DIR = dir;
|
|
27
42
|
return dir;
|
|
28
43
|
}
|
|
29
44
|
|
|
@@ -96,6 +111,21 @@ describe("TUI context RPC data", () => {
|
|
|
96
111
|
expect((await loadSidebarSnapshot(sessionId, directory)).inputTokens).toBe(0);
|
|
97
112
|
});
|
|
98
113
|
|
|
114
|
+
test("does not turn a status authority failure into an empty session", async () => {
|
|
115
|
+
const dataHome = makeDataHome();
|
|
116
|
+
const directory = "/repo-status-error";
|
|
117
|
+
const server = await startServer(dataHome, directory, () => ({}));
|
|
118
|
+
server.handle("status-detail", async () => ({
|
|
119
|
+
error: "Rust module status unavailable; canonical session state was not read",
|
|
120
|
+
}));
|
|
121
|
+
initRpcClient(directory);
|
|
122
|
+
|
|
123
|
+
expect(await loadStatusDetail("ses_rust", directory)).toEqual({
|
|
124
|
+
ok: false,
|
|
125
|
+
error: "Rust module status unavailable; canonical session state was not read",
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
99
129
|
test("distinguishes a real zero compartment count from an RPC failure", async () => {
|
|
100
130
|
const dataHome = makeDataHome();
|
|
101
131
|
const directory = "/repo-count";
|
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
* TUI data layer — pure RPC client, no direct SQLite access.
|
|
3
3
|
* All data is fetched from the server plugin via HTTP RPC.
|
|
4
4
|
*/
|
|
5
|
-
import
|
|
6
|
-
import path from "node:path";
|
|
5
|
+
import { getMagicContextStorageDir } from "../../shared/data-path";
|
|
7
6
|
import { MagicContextRpcClient } from "../../shared/rpc-client";
|
|
8
7
|
import type { EmbedDetail, SidebarSnapshot, StatusDetail } from "../../shared/rpc-types";
|
|
9
8
|
|
|
@@ -12,18 +11,9 @@ export type { EmbedDetail, SidebarSnapshot, StatusDetail };
|
|
|
12
11
|
let rpcClient: MagicContextRpcClient | null = null;
|
|
13
12
|
let rpcGeneration = 0;
|
|
14
13
|
|
|
15
|
-
function getStorageDir(): string {
|
|
16
|
-
// Plugin v0.16+ uses the shared cortexkit/magic-context path so OpenCode
|
|
17
|
-
// and Pi can share state. The TUI just needs to point its RPC client at
|
|
18
|
-
// the same storage directory the server plugin uses for the lock-file
|
|
19
|
-
// discovery convention.
|
|
20
|
-
const dataDir = process.env.XDG_DATA_HOME ?? path.join(os.homedir(), ".local", "share");
|
|
21
|
-
return path.join(dataDir, "cortexkit", "magic-context");
|
|
22
|
-
}
|
|
23
|
-
|
|
24
14
|
/** Initialize the RPC client. Call once on TUI startup. */
|
|
25
15
|
export function initRpcClient(directory: string): void {
|
|
26
|
-
const storageDir =
|
|
16
|
+
const storageDir = getMagicContextStorageDir();
|
|
27
17
|
// Bump the generation before replacing the client so late notification
|
|
28
18
|
// responses from a disposed client are ignored (the WS socket observes the
|
|
29
19
|
// new generation and abandons its in-flight connect).
|
|
@@ -172,63 +162,26 @@ export async function loadSidebarSnapshot(
|
|
|
172
162
|
}
|
|
173
163
|
}
|
|
174
164
|
|
|
175
|
-
|
|
165
|
+
export type StatusDetailResult = { ok: true; detail: StatusDetail } | { ok: false; error: string };
|
|
166
|
+
|
|
167
|
+
/** Fetch full status detail without presenting transport failure as an empty session. */
|
|
176
168
|
export async function loadStatusDetail(
|
|
177
169
|
sessionId: string,
|
|
178
170
|
directory: string,
|
|
179
171
|
modelKey?: string,
|
|
180
|
-
): Promise<
|
|
181
|
-
|
|
182
|
-
...EMPTY_SNAPSHOT,
|
|
183
|
-
sessionId,
|
|
184
|
-
activeProfile: null,
|
|
185
|
-
tagCounter: 0,
|
|
186
|
-
activeTags: 0,
|
|
187
|
-
droppedTags: 0,
|
|
188
|
-
totalTags: 0,
|
|
189
|
-
activeBytes: 0,
|
|
190
|
-
lastResponseTime: 0,
|
|
191
|
-
lastNudgeTokens: 0,
|
|
192
|
-
lastTransformError: null,
|
|
193
|
-
isSubagent: false,
|
|
194
|
-
pendingOps: [],
|
|
195
|
-
contextLimit: 0,
|
|
196
|
-
cacheTtlMs: 0,
|
|
197
|
-
cacheRemainingMs: 0,
|
|
198
|
-
cacheExpired: false,
|
|
199
|
-
cacheNeverExpires: false,
|
|
200
|
-
executeThreshold: 65,
|
|
201
|
-
executeThresholdMode: "percentage",
|
|
202
|
-
protectedTagCount: 20,
|
|
203
|
-
historyBudgetPercentage: 0.15,
|
|
204
|
-
historyBlockTokens: 0,
|
|
205
|
-
compressionBudget: null,
|
|
206
|
-
compressionUsage: null,
|
|
207
|
-
toastDurationMs: 5000,
|
|
208
|
-
loggerDiagnostics: {
|
|
209
|
-
swallowedWriteCount: 0,
|
|
210
|
-
lastErrorMessage: null,
|
|
211
|
-
lastErrorTime: null,
|
|
212
|
-
},
|
|
213
|
-
storage_versions: {
|
|
214
|
-
context_db_schema_version: null,
|
|
215
|
-
plugin_supported_version: 0,
|
|
216
|
-
},
|
|
217
|
-
};
|
|
218
|
-
|
|
219
|
-
if (!rpcClient) return emptyDetail;
|
|
172
|
+
): Promise<StatusDetailResult> {
|
|
173
|
+
if (!rpcClient) return { ok: false, error: "RPC client is not initialized" };
|
|
220
174
|
try {
|
|
221
175
|
const result = await rpcClient.call<StatusDetail>("status-detail", {
|
|
222
176
|
sessionId,
|
|
223
177
|
directory,
|
|
224
178
|
modelKey,
|
|
225
179
|
});
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
return emptyDetail;
|
|
180
|
+
const error = (result as unknown as Record<string, unknown>).error;
|
|
181
|
+
if (typeof error === "string") return { ok: false, error };
|
|
182
|
+
return { ok: true, detail: result };
|
|
183
|
+
} catch (error) {
|
|
184
|
+
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
232
185
|
}
|
|
233
186
|
}
|
|
234
187
|
|
|
@@ -262,12 +215,15 @@ export async function loadEmbedDetail(sessionId: string, directory: string): Pro
|
|
|
262
215
|
export type CompartmentCountResult = { ok: true; count: number } | { ok: false; error: string };
|
|
263
216
|
|
|
264
217
|
/** Get compartment count without making transport failure look like a real zero. */
|
|
265
|
-
export async function getCompartmentCount(
|
|
218
|
+
export async function getCompartmentCount(
|
|
219
|
+
sessionId: string,
|
|
220
|
+
directory?: string,
|
|
221
|
+
): Promise<CompartmentCountResult> {
|
|
266
222
|
if (!rpcClient) return { ok: false, error: "RPC client is not initialized" };
|
|
267
223
|
try {
|
|
268
224
|
const result = await rpcClient.call<{ count?: number; error?: string }>(
|
|
269
225
|
"compartment-count",
|
|
270
|
-
{ sessionId },
|
|
226
|
+
{ sessionId, directory },
|
|
271
227
|
);
|
|
272
228
|
if (typeof result.error === "string") return { ok: false, error: result.error };
|
|
273
229
|
if (typeof result.count !== "number" || !Number.isFinite(result.count)) {
|