@cortexkit/opencode-magic-context 0.36.1 → 0.37.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/dist/config/index.d.ts.map +1 -1
- package/dist/config/project-security.d.ts +2 -1
- package/dist/config/project-security.d.ts.map +1 -1
- package/dist/config/schema/magic-context.d.ts +10 -0
- package/dist/config/schema/magic-context.d.ts.map +1 -1
- package/dist/{conflict-warning-hook-mt0ttc4x.js → conflict-warning-hook-an4fxhwf.js} +3 -3
- package/dist/{derive-budgets-88mxk2w1.js → derive-budgets-0f8xpyw2.js} +3 -2
- package/dist/features/magic-context/context-authority.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/evaluate-smart-notes.d.ts.map +1 -1
- package/dist/features/magic-context/memory/embedding-openai.d.ts +3 -1
- package/dist/features/magic-context/memory/embedding-openai.d.ts.map +1 -1
- package/dist/features/magic-context/memory/project-identity.d.ts +6 -0
- package/dist/features/magic-context/memory/project-identity.d.ts.map +1 -1
- package/dist/features/magic-context/migrations.d.ts.map +1 -1
- package/dist/features/magic-context/overflow-detection.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/runner.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/wake-plane.d.ts +21 -0
- package/dist/features/magic-context/smart-notes/wake-plane.d.ts.map +1 -0
- package/dist/features/magic-context/storage-db.d.ts +13 -2
- package/dist/features/magic-context/storage-db.d.ts.map +1 -1
- package/dist/features/magic-context/storage-meta-persisted.d.ts +22 -0
- 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-shared.d.ts.map +1 -1
- package/dist/features/magic-context/storage-primers.d.ts +11 -0
- package/dist/features/magic-context/storage-primers.d.ts.map +1 -1
- package/dist/features/magic-context/storage-schema-helpers.d.ts.map +1 -1
- package/dist/features/magic-context/user-memory/storage-user-memory.d.ts +7 -0
- package/dist/features/magic-context/user-memory/storage-user-memory.d.ts.map +1 -1
- package/dist/features/magic-context/window-report-ledger.d.ts +70 -0
- package/dist/features/magic-context/window-report-ledger.d.ts.map +1 -0
- package/dist/hooks/magic-context/command-handler.d.ts.map +1 -1
- package/dist/hooks/magic-context/event-handler.d.ts.map +1 -1
- package/dist/hooks/magic-context/event-resolvers.d.ts +4 -0
- package/dist/hooks/magic-context/event-resolvers.d.ts.map +1 -1
- package/dist/hooks/magic-context/execute-status.d.ts +2 -1
- package/dist/hooks/magic-context/execute-status.d.ts.map +1 -1
- package/dist/hooks/magic-context/hook.d.ts.map +1 -1
- package/dist/hooks/magic-context/inject-compartments.d.ts.map +1 -1
- package/dist/hooks/magic-context/lkg-replay.d.ts.map +1 -1
- package/dist/hooks/magic-context/lkg-slot.d.ts +20 -0
- package/dist/hooks/magic-context/lkg-slot.d.ts.map +1 -1
- package/dist/hooks/magic-context/module-state-sync.d.ts +8 -0
- package/dist/hooks/magic-context/module-state-sync.d.ts.map +1 -1
- package/dist/hooks/magic-context/module-transport.d.ts.map +1 -1
- package/dist/hooks/magic-context/module-wire.d.ts +6 -1
- package/dist/hooks/magic-context/module-wire.d.ts.map +1 -1
- package/dist/hooks/magic-context/rust-mode-transform.d.ts +17 -0
- package/dist/hooks/magic-context/rust-mode-transform.d.ts.map +1 -1
- package/dist/hooks/magic-context/strip-content.d.ts +25 -0
- package/dist/hooks/magic-context/strip-content.d.ts.map +1 -1
- package/dist/hooks/magic-context/system-prompt-hash.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform-postprocess-phase.d.ts +11 -3
- package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform.d.ts +10 -0
- package/dist/hooks/magic-context/transform.d.ts.map +1 -1
- package/dist/hooks/magic-context/wrapup-orchestrator.d.ts.map +1 -1
- package/dist/{index-1skzp4dc.js → index-0ze9p983.js} +572 -92
- package/dist/{index-nsx2n9eg.js → index-4cz5nfxf.js} +3 -3
- package/dist/{index-f1tnb3vm.js → index-8pqfcghm.js} +21 -9
- package/dist/{index-zp6x6s6a.js → index-cae89eas.js} +25 -6
- package/dist/index-cgyfn1s2.js +53 -0
- package/dist/{index-3zqcdwqr.js → index-crke4z5j.js} +7 -4
- package/dist/{index-tsgs7td8.js → index-pck9vadr.js} +1 -1
- package/dist/index-q0t189ay.js +741 -0
- package/dist/{index-3tnkymva.js → index-sw8gnq34.js} +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1172 -287
- package/dist/plugin/hooks/create-session-hooks.d.ts +3 -0
- package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
- package/dist/plugin/rpc-handlers.d.ts.map +1 -1
- package/dist/{recomp-orchestrator-a0g9b52q.js → recomp-orchestrator-c3ff16k7.js} +7 -6
- package/dist/{safe-notification-target-c11fdda2.js → safe-notification-target-b693f9x3.js} +2 -2
- package/dist/{send-session-notification-bgtf4853.js → send-session-notification-dxske5zm.js} +3 -3
- package/dist/shared/conflict-detector.d.ts +63 -0
- package/dist/shared/conflict-detector.d.ts.map +1 -1
- package/dist/shared/harness-provider-map.d.ts +8 -0
- package/dist/shared/harness-provider-map.d.ts.map +1 -1
- package/dist/shared/logger.d.ts +8 -0
- package/dist/shared/logger.d.ts.map +1 -1
- package/dist/shared/models-dev-cache.d.ts +5 -0
- package/dist/shared/models-dev-cache.d.ts.map +1 -1
- package/dist/shared/prompt-surface-runtime.d.ts.map +1 -1
- package/dist/shared/prompt-surface.d.ts +2 -0
- package/dist/shared/prompt-surface.d.ts.map +1 -1
- package/dist/shared/rpc-client.d.ts.map +1 -1
- package/dist/shared/rpc-types.d.ts +14 -0
- package/dist/shared/rpc-types.d.ts.map +1 -1
- package/dist/shared/rpc-utils.d.ts +19 -9
- package/dist/shared/rpc-utils.d.ts.map +1 -1
- package/dist/shared/window-geometry.d.ts +89 -0
- package/dist/shared/window-geometry.d.ts.map +1 -0
- package/dist/{storage-meta-session-df9tstzr.js → storage-meta-session-mk4gsnke.js} +4 -3
- package/dist/tools/ctx-memory/tools.d.ts.map +1 -1
- package/dist/tools/ctx-note/tools.d.ts.map +1 -1
- package/dist/tui/data/context-db.d.ts.map +1 -1
- package/dist/tui-compiled/data/context-db.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/config/index.ts +2 -0
- package/src/config/latch-permanence-guard.test.ts +218 -0
- package/src/config/project-security.ts +10 -1
- package/src/config/schema/magic-context.ts +10 -0
- package/src/shared/__fixtures__/window-overlay.v1.json +337 -0
- package/src/shared/conflict-detector.test.ts +176 -1
- package/src/shared/conflict-detector.ts +126 -3
- package/src/shared/harness-provider-map.test.ts +20 -0
- package/src/shared/harness-provider-map.ts +19 -0
- package/src/shared/logger.test.ts +119 -0
- package/src/shared/logger.ts +33 -15
- package/src/shared/models-dev-cache.test.ts +17 -0
- package/src/shared/models-dev-cache.ts +58 -6
- package/src/shared/prompt-surface-runtime.ts +5 -3
- package/src/shared/prompt-surface.test.ts +38 -0
- package/src/shared/prompt-surface.ts +15 -2
- package/src/shared/rpc-client.ts +4 -2
- package/src/shared/rpc-types.ts +14 -0
- package/src/shared/rpc-utils.test.ts +137 -13
- package/src/shared/rpc-utils.ts +144 -31
- package/src/shared/window-geometry.test.ts +309 -0
- package/src/shared/window-geometry.ts +629 -0
- package/src/tui/data/context-db.ts +5 -0
- package/src/tui/index.tsx +22 -0
- package/src/tui-compiled/data/context-db.ts +5 -0
- package/src/tui-compiled/index.tsx +319 -255
- package/dist/index-wgvxkqpx.js +0 -342
package/src/shared/rpc-utils.ts
CHANGED
|
@@ -50,13 +50,22 @@ export function legacyRpcPortFilePath(storageDir: string, directory: string): st
|
|
|
50
50
|
return join(rpcPortDir(storageDir, directory), "port");
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
export
|
|
54
|
-
|
|
53
|
+
export type PidLiveness = "alive" | "dead" | "inconclusive";
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Check whether the platform confirms a PID is live without treating a denied
|
|
57
|
+
* probe as confirmation. Windows uses tasklist because MSYS2/Cygwin ps does not
|
|
58
|
+
* support the options used by the Unix probe. Sandboxes commonly reject
|
|
59
|
+
* `kill(pid, 0)` with EPERM even when the PID does not exist outside their view.
|
|
60
|
+
*/
|
|
61
|
+
export function isPidAlive(pid: number): PidLiveness {
|
|
62
|
+
if (!Number.isInteger(pid) || pid <= 0) return "dead";
|
|
63
|
+
if (rpcIdentityPlatform === "win32") return readWindowsProcess(pid).state;
|
|
55
64
|
try {
|
|
56
|
-
|
|
57
|
-
return
|
|
58
|
-
} catch (
|
|
59
|
-
return (
|
|
65
|
+
rpcIdentityProcessKill(pid, 0);
|
|
66
|
+
return "alive";
|
|
67
|
+
} catch (error) {
|
|
68
|
+
return (error as NodeJS.ErrnoException).code === "ESRCH" ? "dead" : "inconclusive";
|
|
60
69
|
}
|
|
61
70
|
}
|
|
62
71
|
|
|
@@ -64,9 +73,12 @@ const RPC_IDENTITY_SKEW_TOLERANCE_MS = 120_000;
|
|
|
64
73
|
const LINUX_CLOCK_TICKS_PER_SECOND = 100;
|
|
65
74
|
const PS_PROBE_TIMEOUT_MS = 1_000;
|
|
66
75
|
const OPEN_CODE_COMMAND_MARKERS = ["opencode", "node", "bun", "electron"];
|
|
76
|
+
const TASKLIST_NO_TASKS_PATTERN =
|
|
77
|
+
/^INFO:\s+No tasks are running which match the specified criteria\.?$/im;
|
|
67
78
|
|
|
68
79
|
let rpcIdentityReadFileSync: typeof readFileSync = readFileSync;
|
|
69
80
|
let rpcIdentityExecFileSync: typeof execFileSync = execFileSync;
|
|
81
|
+
let rpcIdentityProcessKill: typeof process.kill = process.kill;
|
|
70
82
|
let rpcProcessListExecFileSync: typeof execFileSync = execFileSync;
|
|
71
83
|
let rpcProcessListTestOverride = false;
|
|
72
84
|
let rpcIdentityPlatform: NodeJS.Platform = process.platform;
|
|
@@ -116,6 +128,7 @@ function readPsProcessStartTime(pid: number): number | null {
|
|
|
116
128
|
const output = rpcIdentityExecFileSync("ps", ["-p", String(pid), "-o", "lstart="], {
|
|
117
129
|
encoding: "utf8",
|
|
118
130
|
timeout: PS_PROBE_TIMEOUT_MS,
|
|
131
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
119
132
|
});
|
|
120
133
|
const processStartTime = Date.parse(String(output).trim());
|
|
121
134
|
return Number.isFinite(processStartTime) ? processStartTime : null;
|
|
@@ -124,6 +137,72 @@ function readPsProcessStartTime(pid: number): number | null {
|
|
|
124
137
|
}
|
|
125
138
|
}
|
|
126
139
|
|
|
140
|
+
interface ProcessListEntry {
|
|
141
|
+
pid: number;
|
|
142
|
+
command: string;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function parseCsvLine(line: string): string[] | null {
|
|
146
|
+
const fields: string[] = [];
|
|
147
|
+
let field = "";
|
|
148
|
+
let quoted = false;
|
|
149
|
+
for (let index = 0; index < line.length; index += 1) {
|
|
150
|
+
const character = line[index];
|
|
151
|
+
if (character === '"') {
|
|
152
|
+
if (quoted && line[index + 1] === '"') {
|
|
153
|
+
field += '"';
|
|
154
|
+
index += 1;
|
|
155
|
+
} else {
|
|
156
|
+
quoted = !quoted;
|
|
157
|
+
}
|
|
158
|
+
} else if (character === "," && !quoted) {
|
|
159
|
+
fields.push(field);
|
|
160
|
+
field = "";
|
|
161
|
+
} else {
|
|
162
|
+
field += character;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if (quoted) return null;
|
|
166
|
+
fields.push(field);
|
|
167
|
+
return fields;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function parseTasklistOutput(output: string): ProcessListEntry[] | null {
|
|
171
|
+
const entries: ProcessListEntry[] = [];
|
|
172
|
+
let sawHeader = false;
|
|
173
|
+
for (const rawLine of output.split(/\r?\n/)) {
|
|
174
|
+
const line = rawLine.trim();
|
|
175
|
+
if (!line) continue;
|
|
176
|
+
if (TASKLIST_NO_TASKS_PATTERN.test(line)) return [];
|
|
177
|
+
const fields = parseCsvLine(line);
|
|
178
|
+
if (!fields) continue;
|
|
179
|
+
if (fields[1]?.trim().toLowerCase() === "pid") {
|
|
180
|
+
sawHeader = true;
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
const pid = Number(fields[1]);
|
|
184
|
+
if (!Number.isInteger(pid) || pid <= 0 || !fields[0]) continue;
|
|
185
|
+
entries.push({ pid, command: fields[0] });
|
|
186
|
+
}
|
|
187
|
+
return entries.length > 0 || sawHeader ? entries : null;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function readWindowsProcess(pid: number): { state: PidLiveness; command?: string } {
|
|
191
|
+
try {
|
|
192
|
+
const output = rpcIdentityExecFileSync("tasklist", ["/FO", "CSV", "/FI", `PID eq ${pid}`], {
|
|
193
|
+
encoding: "utf8",
|
|
194
|
+
timeout: PS_PROBE_TIMEOUT_MS,
|
|
195
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
196
|
+
});
|
|
197
|
+
const entries = parseTasklistOutput(String(output));
|
|
198
|
+
if (entries === null) return { state: "inconclusive" };
|
|
199
|
+
const process = entries.find((entry) => entry.pid === pid);
|
|
200
|
+
return process ? { state: "alive", command: process.command } : { state: "dead" };
|
|
201
|
+
} catch {
|
|
202
|
+
return { state: "inconclusive" };
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
127
206
|
function readLinuxProcessCommand(pid: number): string | null {
|
|
128
207
|
try {
|
|
129
208
|
return String(rpcIdentityReadFileSync(`/proc/${pid}/cmdline`, "utf8"));
|
|
@@ -137,6 +216,7 @@ function readPsProcessCommand(pid: number): string | null {
|
|
|
137
216
|
const output = rpcIdentityExecFileSync("ps", ["-p", String(pid), "-o", "command="], {
|
|
138
217
|
encoding: "utf8",
|
|
139
218
|
timeout: PS_PROBE_TIMEOUT_MS,
|
|
219
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
140
220
|
});
|
|
141
221
|
return String(output);
|
|
142
222
|
} catch {
|
|
@@ -153,40 +233,51 @@ function commandLooksLikeOpenCode(command: string): boolean {
|
|
|
153
233
|
* Verify that a live PID still belongs to the process that wrote a port record.
|
|
154
234
|
*
|
|
155
235
|
* A PID can be reused after its original process exits. On Linux, procfs gives
|
|
156
|
-
* us a process start time without spawning a helper;
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
236
|
+
* us a process start time without spawning a helper; macOS and other Unix-like
|
|
237
|
+
* platforms use `ps`, while Windows uses `tasklist`, only on this cold
|
|
238
|
+
* database-open guard path. Legacy records without a start time use a weaker
|
|
239
|
+
* command-name check. A failed filesystem or process probe is inconclusive,
|
|
240
|
+
* not proof that this port record still belongs to OpenCode.
|
|
160
241
|
*/
|
|
161
|
-
export
|
|
162
|
-
|
|
242
|
+
export type PidIdentityPlausibility = "plausible" | "implausible" | "inconclusive";
|
|
243
|
+
|
|
244
|
+
export function isPidIdentityPlausible(record: RpcPortFileRecord): PidIdentityPlausibility {
|
|
245
|
+
if (!Number.isInteger(record.pid) || record.pid <= 0) return "implausible";
|
|
163
246
|
|
|
164
247
|
if (Number.isFinite(record.started_at) && record.started_at > 0) {
|
|
165
248
|
const processStartTime =
|
|
166
249
|
rpcIdentityPlatform === "linux"
|
|
167
250
|
? readLinuxProcessStartTime(record.pid)
|
|
168
|
-
:
|
|
169
|
-
|
|
170
|
-
|
|
251
|
+
: rpcIdentityPlatform === "win32"
|
|
252
|
+
? null
|
|
253
|
+
: readPsProcessStartTime(record.pid);
|
|
254
|
+
if (processStartTime === null) return "inconclusive";
|
|
255
|
+
return processStartTime <= record.started_at + RPC_IDENTITY_SKEW_TOLERANCE_MS
|
|
256
|
+
? "plausible"
|
|
257
|
+
: "implausible";
|
|
171
258
|
}
|
|
172
259
|
|
|
173
260
|
const command =
|
|
174
261
|
rpcIdentityPlatform === "linux"
|
|
175
262
|
? readLinuxProcessCommand(record.pid)
|
|
176
|
-
:
|
|
177
|
-
|
|
178
|
-
|
|
263
|
+
: rpcIdentityPlatform === "win32"
|
|
264
|
+
? (readWindowsProcess(record.pid).command ?? null)
|
|
265
|
+
: readPsProcessCommand(record.pid);
|
|
266
|
+
if (command === null) return "inconclusive";
|
|
267
|
+
return commandLooksLikeOpenCode(command) ? "plausible" : "implausible";
|
|
179
268
|
}
|
|
180
269
|
|
|
181
270
|
export function __setRpcIdentityTestHooks(hooks: {
|
|
182
271
|
readFileSync?: typeof readFileSync;
|
|
183
272
|
execFileSync?: typeof execFileSync;
|
|
273
|
+
processKill?: typeof process.kill;
|
|
184
274
|
processListExecFileSync?: typeof execFileSync;
|
|
185
275
|
platform?: NodeJS.Platform;
|
|
186
276
|
nowMs?: () => number;
|
|
187
277
|
}): void {
|
|
188
278
|
rpcIdentityReadFileSync = hooks.readFileSync ?? readFileSync;
|
|
189
279
|
rpcIdentityExecFileSync = hooks.execFileSync ?? execFileSync;
|
|
280
|
+
rpcIdentityProcessKill = hooks.processKill ?? process.kill;
|
|
190
281
|
rpcProcessListExecFileSync = hooks.processListExecFileSync ?? execFileSync;
|
|
191
282
|
rpcProcessListTestOverride = hooks.processListExecFileSync !== undefined;
|
|
192
283
|
rpcIdentityPlatform = hooks.platform ?? process.platform;
|
|
@@ -196,6 +287,7 @@ export function __setRpcIdentityTestHooks(hooks: {
|
|
|
196
287
|
export function __resetRpcIdentityTestHooks(): void {
|
|
197
288
|
rpcIdentityReadFileSync = readFileSync;
|
|
198
289
|
rpcIdentityExecFileSync = execFileSync;
|
|
290
|
+
rpcIdentityProcessKill = process.kill;
|
|
199
291
|
rpcProcessListExecFileSync = execFileSync;
|
|
200
292
|
rpcProcessListTestOverride = false;
|
|
201
293
|
rpcIdentityPlatform = process.platform;
|
|
@@ -228,28 +320,49 @@ export interface PiProcessDiscovery {
|
|
|
228
320
|
|
|
229
321
|
/**
|
|
230
322
|
* Inspect Pi/OMP processes without converting a failed process-list probe into
|
|
231
|
-
* false evidence that no harness is running.
|
|
232
|
-
*
|
|
233
|
-
*
|
|
323
|
+
* false evidence that no harness is running. Callers choose their own policy
|
|
324
|
+
* for the unreadable state: destructive maintenance can fail closed, while a
|
|
325
|
+
* migration guard can proceed after reporting that no live Pi process was confirmed.
|
|
234
326
|
*/
|
|
235
327
|
export function inspectLivePiProcesses(): PiProcessDiscovery {
|
|
236
328
|
if (process.env.NODE_ENV === "test" && !rpcProcessListTestOverride) {
|
|
237
329
|
return { state: "known", processIds: [] };
|
|
238
330
|
}
|
|
239
331
|
try {
|
|
332
|
+
const isWindows = rpcIdentityPlatform === "win32";
|
|
240
333
|
const output = String(
|
|
241
|
-
rpcProcessListExecFileSync(
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
334
|
+
rpcProcessListExecFileSync(
|
|
335
|
+
isWindows ? "tasklist" : "ps",
|
|
336
|
+
isWindows ? ["/FO", "CSV"] : ["-axo", "pid=,command="],
|
|
337
|
+
{
|
|
338
|
+
encoding: "utf8",
|
|
339
|
+
timeout: PS_PROBE_TIMEOUT_MS,
|
|
340
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
341
|
+
},
|
|
342
|
+
),
|
|
245
343
|
);
|
|
246
344
|
const pids = new Set<number>();
|
|
247
|
-
|
|
248
|
-
const
|
|
249
|
-
if (
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
345
|
+
if (isWindows) {
|
|
346
|
+
const entries = parseTasklistOutput(output);
|
|
347
|
+
if (entries === null) {
|
|
348
|
+
return {
|
|
349
|
+
state: "unreadable",
|
|
350
|
+
processIds: [],
|
|
351
|
+
error: "tasklist output unavailable",
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
for (const entry of entries) {
|
|
355
|
+
if (entry.pid === process.pid) continue;
|
|
356
|
+
if (commandLooksLikePi(entry.command)) pids.add(entry.pid);
|
|
357
|
+
}
|
|
358
|
+
} else {
|
|
359
|
+
for (const line of output.split(/\r?\n/)) {
|
|
360
|
+
const match = /^\s*(\d+)\s+(.+)$/.exec(line);
|
|
361
|
+
if (!match) continue;
|
|
362
|
+
const pid = Number(match[1]);
|
|
363
|
+
if (!Number.isInteger(pid) || pid <= 0 || pid === process.pid) continue;
|
|
364
|
+
if (commandLooksLikePi(match[2])) pids.add(pid);
|
|
365
|
+
}
|
|
253
366
|
}
|
|
254
367
|
return { state: "known", processIds: [...pids].sort((left, right) => left - right) };
|
|
255
368
|
} catch (error) {
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import { afterEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { resolveLimit } from "./models-dev-cache";
|
|
6
|
+
import {
|
|
7
|
+
deriveWindowGeometry,
|
|
8
|
+
formatWindowDerivationLine,
|
|
9
|
+
parseWindowOverlay,
|
|
10
|
+
placeholderFilteredOutput,
|
|
11
|
+
readWindowOverlayFile,
|
|
12
|
+
resolveWindowOverlayFacts,
|
|
13
|
+
scalarizeFact,
|
|
14
|
+
type WindowOverlay,
|
|
15
|
+
type WindowOverlayFact,
|
|
16
|
+
} from "./window-geometry";
|
|
17
|
+
|
|
18
|
+
const fixture = JSON.parse(
|
|
19
|
+
readFileSync(new URL("./__fixtures__/window-overlay.v1.json", import.meta.url), "utf8"),
|
|
20
|
+
) as unknown;
|
|
21
|
+
const parsedFixture = parseWindowOverlay(fixture).overlay as WindowOverlay;
|
|
22
|
+
const tempDirs: string[] = [];
|
|
23
|
+
|
|
24
|
+
function fact(
|
|
25
|
+
value: WindowOverlayFact["value"],
|
|
26
|
+
overrides: Partial<WindowOverlayFact> = {},
|
|
27
|
+
): WindowOverlayFact {
|
|
28
|
+
return {
|
|
29
|
+
value,
|
|
30
|
+
grade: "measured",
|
|
31
|
+
units: "provider",
|
|
32
|
+
boundary: "Observed",
|
|
33
|
+
source_ref: "test",
|
|
34
|
+
observed_at: "2026-08-13T00:00:00Z",
|
|
35
|
+
...overrides,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function overlay(
|
|
40
|
+
provider: string,
|
|
41
|
+
model: string,
|
|
42
|
+
facts: Record<string, WindowOverlayFact>,
|
|
43
|
+
): WindowOverlay {
|
|
44
|
+
return {
|
|
45
|
+
schema: "fusiform-window-overlay/v1",
|
|
46
|
+
generated_at: "2026-08-13T00:00:00Z",
|
|
47
|
+
minted_provider_ids: [],
|
|
48
|
+
cells: [{ provider_id: provider, model_id: model, facts }],
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
afterEach(() => {
|
|
53
|
+
for (const path of tempDirs.splice(0)) rmSync(path, { recursive: true, force: true });
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe("Fusiform overlay v1", () => {
|
|
57
|
+
test("scalarizes every tagged union conservatively", () => {
|
|
58
|
+
expect(scalarizeFact({ kind: "stated", value: 272_000 })).toBe(272_000);
|
|
59
|
+
expect(scalarizeFact({ kind: "bracket", at_least: 340_000, below: 372_001 })).toBe(340_000);
|
|
60
|
+
expect(scalarizeFact({ kind: "bracket", below: 372_001 })).toBeUndefined();
|
|
61
|
+
expect(scalarizeFact({ kind: "unknown", why: "never_measured" })).toBeUndefined();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("preserves a half-open bracket and derives exactly from at_least", () => {
|
|
65
|
+
const halfOpen = parseWindowOverlay({
|
|
66
|
+
schema: "fusiform-window-overlay/v1",
|
|
67
|
+
generated_at: "2026-08-13T00:00:00Z",
|
|
68
|
+
minted_provider_ids: [],
|
|
69
|
+
cells: [
|
|
70
|
+
{
|
|
71
|
+
provider_id: "openai-chatgpt-oauth",
|
|
72
|
+
model_id: "gpt-5.6-sol",
|
|
73
|
+
facts: {
|
|
74
|
+
"window.advertised": fact({ kind: "stated", value: 272_000 }),
|
|
75
|
+
"window.enforced": fact({ kind: "bracket", at_least: 340_000 }),
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
}).overlay;
|
|
80
|
+
const parsedValue = halfOpen?.cells[0]?.facts["window.enforced"]?.value;
|
|
81
|
+
expect(parsedValue).toEqual({ kind: "bracket", at_least: 340_000 });
|
|
82
|
+
expect(parsedValue && "below" in parsedValue).toBe(false);
|
|
83
|
+
const halfOpenResult = deriveWindowGeometry(
|
|
84
|
+
"openai-chatgpt-oauth",
|
|
85
|
+
"gpt-5.6-sol",
|
|
86
|
+
{ context: 272_000, output: 128_000 },
|
|
87
|
+
{
|
|
88
|
+
overlay: resolveWindowOverlayFacts("openai-chatgpt-oauth", "gpt-5.6-sol", halfOpen),
|
|
89
|
+
},
|
|
90
|
+
);
|
|
91
|
+
const statedResult = deriveWindowGeometry(
|
|
92
|
+
"openai-chatgpt-oauth",
|
|
93
|
+
"gpt-5.6-sol",
|
|
94
|
+
{ context: 272_000, output: 128_000 },
|
|
95
|
+
{
|
|
96
|
+
overlay: resolveWindowOverlayFacts(
|
|
97
|
+
"openai-chatgpt-oauth",
|
|
98
|
+
"gpt-5.6-sol",
|
|
99
|
+
overlay("openai-chatgpt-oauth", "gpt-5.6-sol", {
|
|
100
|
+
"window.enforced": fact({ kind: "stated", value: 340_000 }),
|
|
101
|
+
}),
|
|
102
|
+
),
|
|
103
|
+
},
|
|
104
|
+
);
|
|
105
|
+
expect(halfOpenResult).toEqual(statedResult);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("specific cells override wildcard facts per fact", () => {
|
|
109
|
+
const data: WindowOverlay = {
|
|
110
|
+
schema: "fusiform-window-overlay/v1",
|
|
111
|
+
generated_at: "2026-08-13T00:00:00Z",
|
|
112
|
+
minted_provider_ids: [],
|
|
113
|
+
cells: [
|
|
114
|
+
{
|
|
115
|
+
provider_id: "ollama-cloud",
|
|
116
|
+
model_id: "*",
|
|
117
|
+
facts: {
|
|
118
|
+
"output.enforced": fact({ kind: "stated", value: 65_536 }),
|
|
119
|
+
geometry: fact({ kind: "stated", value: "shared_upfront" }),
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
provider_id: "ollama-cloud",
|
|
124
|
+
model_id: "deepseek-v4",
|
|
125
|
+
facts: {
|
|
126
|
+
"output.enforced": fact({ kind: "stated", value: 32_000 }),
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
],
|
|
130
|
+
};
|
|
131
|
+
const facts = resolveWindowOverlayFacts("ollama-cloud", "deepseek-v4", data)?.facts;
|
|
132
|
+
expect(scalarizeFact(facts?.["output.enforced"]?.value as never)).toBe(32_000);
|
|
133
|
+
expect(facts?.geometry?.value).toEqual({ kind: "stated", value: "shared_upfront" });
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("refuses an unrecognized schema before considering familiar cells", () => {
|
|
137
|
+
const v2 = {
|
|
138
|
+
...(fixture as Record<string, unknown>),
|
|
139
|
+
schema: "fusiform-window-overlay/v2",
|
|
140
|
+
};
|
|
141
|
+
const parsed = parseWindowOverlay(v2);
|
|
142
|
+
expect(parsed.overlay).toBeUndefined();
|
|
143
|
+
expect(parsed.refusal).toContain("unrecognized schema");
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("skips bad cells once and keeps valid cells", () => {
|
|
147
|
+
const badCell = {
|
|
148
|
+
provider_id: "bad",
|
|
149
|
+
model_id: "model",
|
|
150
|
+
facts: { "window.enforced": { value: { kind: "stated", value: 1_000_000 } } },
|
|
151
|
+
};
|
|
152
|
+
const parsed = parseWindowOverlay({
|
|
153
|
+
...(fixture as Record<string, unknown>),
|
|
154
|
+
cells: [badCell, ...(fixture as { cells: unknown[] }).cells],
|
|
155
|
+
});
|
|
156
|
+
expect(parsed.badCells).toBe(1);
|
|
157
|
+
expect(parsed.overlay?.cells.length).toBe(parsedFixture.cells.length);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("a missing file is silent and a bad file logs one summary", () => {
|
|
161
|
+
const dir = mkdtempSync(join(tmpdir(), "window-overlay-test-"));
|
|
162
|
+
tempDirs.push(dir);
|
|
163
|
+
const logs: string[] = [];
|
|
164
|
+
expect(
|
|
165
|
+
readWindowOverlayFile(join(dir, "missing.json"), (message) => logs.push(message)),
|
|
166
|
+
).toBeUndefined();
|
|
167
|
+
expect(logs).toEqual([]);
|
|
168
|
+
const path = join(dir, "bad.json");
|
|
169
|
+
writeFileSync(path, JSON.stringify({ ...(fixture as object), schema: "v2" }));
|
|
170
|
+
expect(readWindowOverlayFile(path, (message) => logs.push(message))).toBeUndefined();
|
|
171
|
+
expect(logs).toHaveLength(1);
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
describe("window geometry", () => {
|
|
176
|
+
test("filters placeholders independently in both directions", () => {
|
|
177
|
+
expect(placeholderFilteredOutput(0, 1_000_000)).toBeUndefined();
|
|
178
|
+
expect(placeholderFilteredOutput(1_000_000, 1_000_000)).toBeUndefined();
|
|
179
|
+
expect(placeholderFilteredOutput(1_000_001, 1_000_000)).toBeUndefined();
|
|
180
|
+
expect(placeholderFilteredOutput(65_536, 1_000_000)).toBe(65_536);
|
|
181
|
+
expect(
|
|
182
|
+
deriveWindowGeometry("xai", "mixed", {
|
|
183
|
+
context: 500_000,
|
|
184
|
+
input: 300_000,
|
|
185
|
+
output: 500_000,
|
|
186
|
+
})?.usableSoft,
|
|
187
|
+
).toBe(300_000);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test("fixture table yields the 65% execution bases from the spec", () => {
|
|
191
|
+
const cases = [
|
|
192
|
+
["openai-chatgpt-oauth", "gpt-5.6", 272_000, 128_000, 200_200],
|
|
193
|
+
["anthropic", "claude-opus-5", 1_000_000, 128_000, 629_200],
|
|
194
|
+
// Producer batch downgraded google's geometry to considered-unknown
|
|
195
|
+
// (Google's docs contradict the separate-quota reading), so the
|
|
196
|
+
// derivation demotes to shared_upfront and reserves OpenCode's
|
|
197
|
+
// actual request cap B = min(65_536, 32_000):
|
|
198
|
+
// floor(0.65 * (1_048_576 - 32_000)) — not the no-reserve 681_574.
|
|
199
|
+
["google", "gemini-3.5-flash", 1_048_576, 65_536, 660_774],
|
|
200
|
+
["xai", "grok-4.6", 500_000, 500_000, 304_200],
|
|
201
|
+
["ollama-cloud", "deepseek-v4-flash", 1_048_576, 384_000, 660_774],
|
|
202
|
+
["moonshotai", "kimi-k3", 1_048_576, 1_048_576, 660_774],
|
|
203
|
+
] as const;
|
|
204
|
+
for (const [provider, model, context, output, expectedExecute] of cases) {
|
|
205
|
+
const result = deriveWindowGeometry(
|
|
206
|
+
provider,
|
|
207
|
+
model,
|
|
208
|
+
{ context, output },
|
|
209
|
+
{
|
|
210
|
+
overlay: resolveWindowOverlayFacts(provider, model, parsedFixture),
|
|
211
|
+
},
|
|
212
|
+
);
|
|
213
|
+
expect(Math.floor((result?.usableSoft ?? 0) * 0.65)).toBe(expectedExecute);
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
test("grok derives from geometry, window, and default output without an enforced output", () => {
|
|
218
|
+
const facts = resolveWindowOverlayFacts("xai", "grok-4.6", parsedFixture);
|
|
219
|
+
expect(facts?.facts["output.enforced"]).toBeUndefined();
|
|
220
|
+
const result = deriveWindowGeometry(
|
|
221
|
+
"xai",
|
|
222
|
+
"grok-4.6",
|
|
223
|
+
{ context: 500_000, output: 500_000 },
|
|
224
|
+
{ overlay: facts },
|
|
225
|
+
);
|
|
226
|
+
expect(result?.geometry).toBe("shared_truncating");
|
|
227
|
+
expect(result?.usableSoft).toBe(468_000);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
test("kimi reserves its default output instead of placeholder advertised output", () => {
|
|
231
|
+
const result = deriveWindowGeometry(
|
|
232
|
+
"moonshotai",
|
|
233
|
+
"kimi-k3",
|
|
234
|
+
{ context: 1_048_576, output: 1_048_576 },
|
|
235
|
+
{
|
|
236
|
+
overlay: resolveWindowOverlayFacts("moonshotai", "kimi-k3", parsedFixture),
|
|
237
|
+
},
|
|
238
|
+
);
|
|
239
|
+
expect(result?.derivation.reserve).toBe(32_000);
|
|
240
|
+
expect(result?.usableSoft).toBe(1_016_576);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
test("provider hook wins over overlay, while overlay beats catalog", () => {
|
|
244
|
+
const data = overlay("provider", "model", {
|
|
245
|
+
"window.enforced": fact({ kind: "stated", value: 300_000 }),
|
|
246
|
+
"output.enforced": fact({ kind: "stated", value: 20_000 }),
|
|
247
|
+
});
|
|
248
|
+
const overlayOnly = deriveWindowGeometry(
|
|
249
|
+
"provider",
|
|
250
|
+
"model",
|
|
251
|
+
{ context: 200_000, output: 10_000 },
|
|
252
|
+
{ overlay: resolveWindowOverlayFacts("provider", "model", data) },
|
|
253
|
+
);
|
|
254
|
+
const hooked = deriveWindowGeometry(
|
|
255
|
+
"provider",
|
|
256
|
+
"model",
|
|
257
|
+
{ context: 200_000, output: 10_000 },
|
|
258
|
+
{
|
|
259
|
+
overlay: resolveWindowOverlayFacts("provider", "model", data),
|
|
260
|
+
providerLimit: { context: 400_000, output: 30_000 },
|
|
261
|
+
},
|
|
262
|
+
);
|
|
263
|
+
expect(overlayOnly?.derivation.window).toBe(300_000);
|
|
264
|
+
expect(hooked?.derivation.window).toBe(400_000);
|
|
265
|
+
expect(hooked?.derivation.reserve).toBe(30_000);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
test("clamps an inversion and logs it", () => {
|
|
269
|
+
const logs: string[] = [];
|
|
270
|
+
const result = deriveWindowGeometry(
|
|
271
|
+
"provider",
|
|
272
|
+
"model",
|
|
273
|
+
{ context: 200_000, input: 190_000, output: 10_000 },
|
|
274
|
+
{
|
|
275
|
+
providerLimit: { context: 192_000, input: 190_000, output: 10_000 },
|
|
276
|
+
log: (message) => logs.push(message),
|
|
277
|
+
},
|
|
278
|
+
);
|
|
279
|
+
expect(result?.usableHard).toBe(result?.usableSoft);
|
|
280
|
+
expect(logs).toHaveLength(1);
|
|
281
|
+
expect(logs[0]).toContain("clamped");
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
test("keeps no-overlay usableSoft byte-identical to legacy resolveLimit", () => {
|
|
285
|
+
const corpus = [
|
|
286
|
+
["openai", "real", { context: 200_000, output: 32_000 }],
|
|
287
|
+
["openai", "zero", { context: 200_000, output: 0 }],
|
|
288
|
+
["xai", "placeholder", { context: 500_000, output: 500_000 }],
|
|
289
|
+
["google", "separate", { context: 1_000_000, output: 65_536 }],
|
|
290
|
+
["anthropic", "input", { context: 1_000_000, input: 900_000, output: 128_000 }],
|
|
291
|
+
] as const;
|
|
292
|
+
for (const [provider, model, limit] of corpus) {
|
|
293
|
+
const result = deriveWindowGeometry(provider, model, limit);
|
|
294
|
+
expect(result?.usableSoft).toBe(resolveLimit(limit, provider, model));
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
test("display percentage uses the same usableSoft scheduler base", () => {
|
|
299
|
+
const result = deriveWindowGeometry("openai", "model", {
|
|
300
|
+
context: 204_000,
|
|
301
|
+
output: 30_600,
|
|
302
|
+
}) as NonNullable<ReturnType<typeof deriveWindowGeometry>>;
|
|
303
|
+
const input = 105_900;
|
|
304
|
+
const schedulerPercentage = (input / result.usableSoft) * 100;
|
|
305
|
+
expect(formatWindowDerivationLine(input, result)).toContain(
|
|
306
|
+
`(${schedulerPercentage.toFixed(1)}%)`,
|
|
307
|
+
);
|
|
308
|
+
});
|
|
309
|
+
});
|