@cortexkit/opencode-magic-context 0.41.1 → 0.41.2

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 (97) hide show
  1. package/dist/{derive-budgets-bb7mrpzs.js → derive-budgets-9q2d3r93.js} +1 -1
  2. package/dist/features/magic-context/compartment-chunk-embedding.d.ts +4 -1
  3. package/dist/features/magic-context/compartment-chunk-embedding.d.ts.map +1 -1
  4. package/dist/features/magic-context/compartment-embedding.d.ts.map +1 -1
  5. package/dist/features/magic-context/dreamer/curate-memory-safety.d.ts +21 -0
  6. package/dist/features/magic-context/dreamer/curate-memory-safety.d.ts.map +1 -0
  7. package/dist/features/magic-context/dreamer/task-executor.d.ts.map +1 -1
  8. package/dist/features/magic-context/dreamer/task-prompts.d.ts +1 -6
  9. package/dist/features/magic-context/dreamer/task-prompts.d.ts.map +1 -1
  10. package/dist/features/magic-context/memory/embedding-failure.d.ts +10 -2
  11. package/dist/features/magic-context/memory/embedding-failure.d.ts.map +1 -1
  12. package/dist/features/magic-context/memory/embedding-local.d.ts +7 -0
  13. package/dist/features/magic-context/memory/embedding-local.d.ts.map +1 -1
  14. package/dist/features/magic-context/memory/onnxruntime-node-wasm-shim.d.ts +3 -0
  15. package/dist/features/magic-context/memory/onnxruntime-node-wasm-shim.d.ts.map +1 -0
  16. package/dist/features/magic-context/memory/sharp-wasm-shim.d.ts +2 -0
  17. package/dist/features/magic-context/memory/sharp-wasm-shim.d.ts.map +1 -0
  18. package/dist/features/magic-context/memory/transformers-node-wasm-entry.d.ts +3 -0
  19. package/dist/features/magic-context/memory/transformers-node-wasm-entry.d.ts.map +1 -0
  20. package/dist/features/magic-context/message-fts-rowid-map.d.ts +28 -0
  21. package/dist/features/magic-context/message-fts-rowid-map.d.ts.map +1 -0
  22. package/dist/features/magic-context/message-index.d.ts.map +1 -1
  23. package/dist/features/magic-context/migrations.d.ts.map +1 -1
  24. package/dist/features/magic-context/overflow-detection.d.ts +15 -2
  25. package/dist/features/magic-context/overflow-detection.d.ts.map +1 -1
  26. package/dist/features/magic-context/project-embedding-registry.d.ts.map +1 -1
  27. package/dist/features/magic-context/storage-db.d.ts +2 -1
  28. package/dist/features/magic-context/storage-db.d.ts.map +1 -1
  29. package/dist/features/magic-context/storage-meta-persisted.d.ts +19 -2
  30. package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
  31. package/dist/features/magic-context/storage-meta-session.d.ts +3 -2
  32. package/dist/features/magic-context/storage-meta-session.d.ts.map +1 -1
  33. package/dist/features/magic-context/storage-meta.d.ts +2 -2
  34. package/dist/features/magic-context/storage-meta.d.ts.map +1 -1
  35. package/dist/features/magic-context/storage-schema-helpers.d.ts.map +1 -1
  36. package/dist/features/magic-context/storage-session-tables.d.ts +9 -1
  37. package/dist/features/magic-context/storage-session-tables.d.ts.map +1 -1
  38. package/dist/features/magic-context/storage-tags.d.ts +13 -0
  39. package/dist/features/magic-context/storage-tags.d.ts.map +1 -1
  40. package/dist/features/magic-context/storage.d.ts +1 -1
  41. package/dist/features/magic-context/storage.d.ts.map +1 -1
  42. package/dist/hooks/magic-context/event-handler.d.ts +4 -1
  43. package/dist/hooks/magic-context/event-handler.d.ts.map +1 -1
  44. package/dist/hooks/magic-context/format-embed-failure.d.ts.map +1 -1
  45. package/dist/hooks/magic-context/hook-handlers.d.ts +3 -0
  46. package/dist/hooks/magic-context/hook-handlers.d.ts.map +1 -1
  47. package/dist/hooks/magic-context/hook.d.ts +4 -2
  48. package/dist/hooks/magic-context/hook.d.ts.map +1 -1
  49. package/dist/hooks/magic-context/live-session-state.d.ts +3 -1
  50. package/dist/hooks/magic-context/live-session-state.d.ts.map +1 -1
  51. package/dist/hooks/magic-context/rust-mode-transform.d.ts +9 -1
  52. package/dist/hooks/magic-context/rust-mode-transform.d.ts.map +1 -1
  53. package/dist/hooks/magic-context/sentinel.d.ts +13 -37
  54. package/dist/hooks/magic-context/sentinel.d.ts.map +1 -1
  55. package/dist/hooks/magic-context/strip-content.d.ts +12 -5
  56. package/dist/hooks/magic-context/strip-content.d.ts.map +1 -1
  57. package/dist/hooks/magic-context/tag-id-fallback.d.ts +3 -1
  58. package/dist/hooks/magic-context/tag-id-fallback.d.ts.map +1 -1
  59. package/dist/hooks/magic-context/tag-messages.d.ts.map +1 -1
  60. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts +25 -2
  61. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
  62. package/dist/hooks/magic-context/transform.d.ts +1 -1
  63. package/dist/hooks/magic-context/transform.d.ts.map +1 -1
  64. package/dist/{index-2d4kf2ve.js → index-cvda5jb3.js} +6 -1
  65. package/dist/{index-dh0vfghk.js → index-j8r0ejb3.js} +923 -127
  66. package/dist/{index-z893kjyt.js → index-y1yr57fw.js} +79 -15
  67. package/dist/index.d.ts.map +1 -1
  68. package/dist/index.js +1503 -1059
  69. package/dist/plugin/dream-timer.d.ts +4 -0
  70. package/dist/plugin/dream-timer.d.ts.map +1 -1
  71. package/dist/plugin/hooks/create-session-hooks.d.ts +2 -2
  72. package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
  73. package/dist/{recomp-orchestrator-w7xg48e3.js → recomp-orchestrator-m37p3fq6.js} +3 -3
  74. package/dist/shared/index.d.ts +1 -0
  75. package/dist/shared/index.d.ts.map +1 -1
  76. package/dist/shared/rpc-utils.d.ts +3 -1
  77. package/dist/shared/rpc-utils.d.ts.map +1 -1
  78. package/dist/shared/summarize-child-stderr.d.ts +2 -0
  79. package/dist/shared/summarize-child-stderr.d.ts.map +1 -0
  80. package/dist/shared/window-geometry.d.ts +2 -0
  81. package/dist/shared/window-geometry.d.ts.map +1 -1
  82. package/dist/{storage-meta-session-gr4zn7sq.js → storage-meta-session-dse3qv6s.js} +3 -1
  83. package/dist/tools/ctx-memory/tools.d.ts.map +1 -1
  84. package/dist/tools/ctx-memory/types.d.ts +2 -0
  85. package/dist/tools/ctx-memory/types.d.ts.map +1 -1
  86. package/dist/tools/ctx-reduce/tools.d.ts.map +1 -1
  87. package/dist/transformers-node-wasm.js +25676 -0
  88. package/package.json +4 -3
  89. package/src/config/index.test.ts +41 -0
  90. package/src/config/latch-permanence-guard.test.ts +5 -0
  91. package/src/shared/index.ts +1 -0
  92. package/src/shared/model-resolution.test.ts +17 -13
  93. package/src/shared/rpc-utils.test.ts +246 -5
  94. package/src/shared/rpc-utils.ts +340 -49
  95. package/src/shared/summarize-child-stderr.test.ts +55 -0
  96. package/src/shared/summarize-child-stderr.ts +33 -0
  97. package/src/shared/window-geometry.ts +9 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cortexkit/opencode-magic-context",
3
- "version": "0.41.1",
3
+ "version": "0.41.2",
4
4
  "type": "module",
5
5
  "description": "OpenCode plugin for Magic Context — cross-session memory and context management",
6
6
  "main": "dist/index.js",
@@ -35,9 +35,10 @@
35
35
  "README.md"
36
36
  ],
37
37
  "scripts": {
38
- "build": "tsc -p ../retina-local-fs/tsconfig.build.json && bun run build:tui && rm -f dist/index.js dist/transformers-web.js dist/*-*.js && bun build src/features/magic-context/memory/transformers-web-entry.ts --outfile dist/transformers-web.js --target browser --format esm --external onnxruntime-web && bun build src/index.ts --outdir dist --target node --format esm --splitting --external @opencode-ai/plugin --external onnxruntime-node --external onnxruntime-web --external sharp --external bun:sqlite --external node:sqlite && tsc --emitDeclarationOnly",
38
+ "build": "tsc -p ../retina-local-fs/tsconfig.build.json && bun run build:tui && rm -f dist/index.js dist/transformers-web.js dist/transformers-node-wasm.js dist/*-*.js && bun build src/features/magic-context/memory/transformers-web-entry.ts --outfile dist/transformers-web.js --target browser --format esm --external onnxruntime-web && bun scripts/build-transformers-node-wasm.ts dist && bun build src/index.ts --outdir dist --target node --format esm --splitting --external @opencode-ai/plugin --external onnxruntime-node --external onnxruntime-web --external sharp --external bun:sqlite --external node:sqlite && tsc --emitDeclarationOnly",
39
39
  "build:tui": "bun scripts/build-tui.ts",
40
40
  "check:tui-compiled": "bun run build:tui && git diff --exit-code -- src/tui-compiled && test -z \"$(git status --porcelain -- src/tui-compiled)\"",
41
+ "check:transformers-node-wasm": "bun scripts/verify-transformers-node-wasm.ts",
41
42
  "typecheck": "tsc -p ../retina-local-fs/tsconfig.build.json && tsc --noEmit && tsc -p tsconfig.scripts.json",
42
43
  "test": "bun install --frozen-lockfile && bun test --parallel --timeout 30000",
43
44
  "test:serial": "bun install --frozen-lockfile && bun test --timeout 30000",
@@ -66,7 +67,7 @@
66
67
  },
67
68
  "optionalDependencies": {
68
69
  "onnxruntime-node": "1.24.3",
69
- "sharp": "^0.34.5"
70
+ "sharp": "^0.35.0"
70
71
  },
71
72
  "devDependencies": {
72
73
  "@ai-sdk/anthropic": "3.0.82",
@@ -5,6 +5,11 @@ import { join } from "node:path";
5
5
 
6
6
  import { resolveHistorianModel } from "../shared/model-resolution";
7
7
  import { createTestTempDir } from "../shared/test-temp-dir";
8
+ import {
9
+ getWindowOverlay,
10
+ reloadWindowOverlay,
11
+ setWindowOverlayPath,
12
+ } from "../shared/window-geometry";
8
13
  import { loadPluginConfig, loadPluginConfigDetailed } from "./index";
9
14
  import { resolveConfigProfile } from "./profiles";
10
15
  import { DEFAULT_LOCAL_EMBEDDING_MODEL } from "./schema/magic-context";
@@ -113,6 +118,42 @@ function loadWithUserAndProjectConfig(
113
118
  }
114
119
  }
115
120
 
121
+ describe("loadPluginConfig — Fusiform overlay reload", () => {
122
+ it("does not invalidate a same-path overlay during routine config reads", () => {
123
+ const overlayDir = mkdtempSync(join(tmpdir(), "mc-config-overlay-"));
124
+ const overlayPath = join(overlayDir, "window-overlay.json");
125
+ const configText = JSON.stringify({ models: { window_overlay_path: overlayPath } });
126
+ const writeOverlay = (modelId: string) =>
127
+ writeFileSync(
128
+ overlayPath,
129
+ JSON.stringify({
130
+ schema: "fusiform-window-overlay/v1",
131
+ generated_at: "2026-09-01T00:00:00Z",
132
+ minted_provider_ids: [],
133
+ cells: [{ provider_id: "hunt-provider", model_id: modelId, facts: {} }],
134
+ }),
135
+ );
136
+
137
+ try {
138
+ writeOverlay("before-rewrite");
139
+ loadWithUserConfig(configText);
140
+ expect(getWindowOverlay()?.cells[0]?.model_id).toBe("before-rewrite");
141
+
142
+ writeOverlay("after-rewrite");
143
+ expect(getWindowOverlay()?.cells[0]?.model_id).toBe("before-rewrite");
144
+
145
+ loadWithUserConfig(configText);
146
+ expect(getWindowOverlay()?.cells[0]?.model_id).toBe("before-rewrite");
147
+
148
+ reloadWindowOverlay(overlayPath);
149
+ expect(getWindowOverlay()?.cells[0]?.model_id).toBe("after-rewrite");
150
+ } finally {
151
+ setWindowOverlayPath(undefined);
152
+ rmSync(overlayDir, { recursive: true, force: true });
153
+ }
154
+ });
155
+ });
156
+
116
157
  describe("loadPluginConfig — preload user-config isolation", () => {
117
158
  it("resolves schema-default embedding config for a fixture with no config (#388)", () => {
118
159
  const projectDir = mkdtempSync(join(tmpdir(), "mc-config-preload-fixture-"));
@@ -27,6 +27,11 @@ const KNOWN_SLOTS: Record<string, KnownSlot> = {
27
27
  classification: "VERDICT",
28
28
  reason: "Correct: a missing or unloadable native binding needs an install repair, which this process cannot observe.",
29
29
  },
30
+ "packages/plugin/src/features/magic-context/memory/embedding-local.ts:localEmbeddingProcessFailure":
31
+ {
32
+ classification: "DIAGNOSTIC",
33
+ reason: "Most-recent local runtime diagnostic: successful initialization and test reset both clear it; the separate runtime-mode slot owns the process verdict.",
34
+ },
30
35
  "packages/plugin/src/features/magic-context/memory/embedding-synapse.ts:sharedClientPromise": {
31
36
  classification: "VERDICT",
32
37
  reason: "DEFECT: a rejected connection promise remains shared after the daemon recovers.",
@@ -4,3 +4,4 @@ export * from "./normalize-sdk-response";
4
4
  export * from "./prompt-surface";
5
5
  export * from "./prompt-surface-runtime";
6
6
  export * from "./resolve-fallbacks";
7
+ export * from "./summarize-child-stderr";
@@ -10,21 +10,25 @@ import {
10
10
 
11
11
  describe("model-resolution", () => {
12
12
  test("keeps historian temperature opt-in while preserving explicit overrides", () => {
13
- expect(resolveHistorianAgentOverrides(undefined)).toEqual({
14
- maxTokens: 32_000,
15
- });
16
- expect(
17
- resolveHistorianAgentOverrides({
18
- temperature: 0.2,
13
+ const fixtures = [
14
+ { temperature: undefined, expected: undefined },
15
+ { temperature: 0.1, expected: 0.1 },
16
+ { temperature: 0, expected: 0 },
17
+ ] as const;
18
+ for (const fixture of fixtures) {
19
+ const resolved = resolveHistorianAgentOverrides({
20
+ ...(fixture.temperature !== undefined ? { temperature: fixture.temperature } : {}),
19
21
  maxTokens: 16_000,
20
22
  opencode: { model: "google/flash", variant: "low" },
21
- }),
22
- ).toEqual({
23
- temperature: 0.2,
24
- maxTokens: 16_000,
25
- model: "google/flash",
26
- variant: "low",
27
- });
23
+ pi: { model: "pi/must-not-leak", thinking_level: "high" },
24
+ });
25
+ expect(resolved).toEqual({
26
+ maxTokens: 16_000,
27
+ ...(fixture.expected !== undefined ? { temperature: fixture.expected } : {}),
28
+ model: "google/flash",
29
+ variant: "low",
30
+ });
31
+ }
28
32
  });
29
33
 
30
34
  test("normalizes string and object entries to the same model identity", () => {
@@ -53,6 +53,45 @@ function tasklistOutput(entries: Array<[number, string]>): string {
53
53
  ].join("\r\n");
54
54
  }
55
55
 
56
+ const WINDOWS_LAUNCHER_PID = 42424;
57
+ const WINDOWS_FOREIGN_PID = 41001;
58
+ const PLUGIN_BUILD_MS = 1_700_000_000_000;
59
+ const OLDER_THAN_PLUGIN_MS = PLUGIN_BUILD_MS - 60_000;
60
+ const OMP_PI_ARC_COMMAND =
61
+ "C:\\Users\\qiiks\\.bun\\bin\\omp.exe C:\\Users\\qiiks\\AppData\\Roaming\\npm\\node_modules\\@oh-my-pi\\pi-coding-agent\\dist\\cli.js";
62
+ const BUN_PI_CHILD_COMMAND =
63
+ "C:\\Users\\qiiks\\.bun\\bin\\bun.exe C:\\Users\\qiiks\\AppData\\Roaming\\npm\\node_modules\\@oh-my-pi\\pi-coding-agent\\dist\\cli.js";
64
+ const BARE_OMP_COMMAND = "C:\\Users\\qiiks\\.bun\\bin\\omp.exe";
65
+
66
+ function padWmi(value: number, width = 2): string {
67
+ return String(value).padStart(width, "0");
68
+ }
69
+
70
+ /** Format an epoch millisecond timestamp as a WMI CreationDate string. */
71
+ function wmiCreationDate(ms: number, offsetMinutes = 0): string {
72
+ const shifted = new Date(ms + offsetMinutes * 60_000);
73
+ const sign = offsetMinutes >= 0 ? "+" : "-";
74
+ return `${shifted.getUTCFullYear()}${padWmi(shifted.getUTCMonth() + 1)}${padWmi(shifted.getUTCDate())}${padWmi(shifted.getUTCHours())}${padWmi(shifted.getUTCMinutes())}${padWmi(shifted.getUTCSeconds())}.000000${sign}${padWmi(Math.abs(offsetMinutes), 3)}`;
75
+ }
76
+
77
+ function cimOutput(
78
+ rows: Array<{
79
+ ProcessId: number;
80
+ ParentProcessId: number;
81
+ CommandLine: string | null;
82
+ CreationDate: string;
83
+ }>,
84
+ ): string {
85
+ return JSON.stringify(rows.length === 1 ? rows[0] : rows);
86
+ }
87
+
88
+ function windowsProcessListExec(
89
+ handler: (file: string, args: readonly string[]) => string,
90
+ ): typeof execFileSync {
91
+ return ((file: string | URL, args: readonly string[] = []) =>
92
+ handler(String(file), args)) as typeof execFileSync;
93
+ }
94
+
56
95
  afterEach(() => {
57
96
  __resetRpcIdentityTestHooks();
58
97
  });
@@ -97,7 +136,12 @@ describe("discoverLivePiProcessIds", () => {
97
136
  ].join("\n")) as typeof execFileSync,
98
137
  });
99
138
 
100
- expect(discoverLivePiProcessIds()).toEqual([41001, 41002, 41003, 41007]);
139
+ expect(inspectLivePiProcesses()).toEqual({
140
+ state: "known",
141
+ processIds: [41002, 41003],
142
+ inconclusivePids: [41001, 41007],
143
+ });
144
+ expect(discoverLivePiProcessIds()).toEqual([41002, 41003]);
101
145
  });
102
146
 
103
147
  test("reports uncertainty instead of treating an unavailable process list as empty", () => {
@@ -114,12 +158,13 @@ describe("discoverLivePiProcessIds", () => {
114
158
  });
115
159
  });
116
160
 
117
- test("uses tasklist instead of ps on Windows", () => {
161
+ test("tries PowerShell CIM before tasklist on Windows", () => {
118
162
  const calls: string[] = [];
119
163
  __setRpcIdentityTestHooks({
120
164
  platform: "win32",
121
165
  processListExecFileSync: ((file: string | URL) => {
122
166
  calls.push(String(file));
167
+ if (String(file) === "powershell") throw new Error("powershell unavailable");
123
168
  return tasklistOutput([
124
169
  [process.pid, "pi.exe"],
125
170
  [41001, "pi.exe"],
@@ -129,10 +174,197 @@ describe("discoverLivePiProcessIds", () => {
129
174
  });
130
175
 
131
176
  expect(inspectLivePiProcesses()).toEqual({
177
+ state: "inconclusive",
178
+ processIds: [],
179
+ inconclusivePids: [41001],
180
+ });
181
+ expect(calls).toEqual(["powershell", "tasklist"]);
182
+ });
183
+ });
184
+
185
+ describe("#411 Windows OMP/Pi live-process scan", () => {
186
+ test("(a) tasklist-only fallback skips a parent omp.exe as an ancestor", () => {
187
+ const calls: string[] = [];
188
+ __setRpcIdentityTestHooks({
189
+ platform: "win32",
190
+ processListExecFileSync: windowsProcessListExec((file) => {
191
+ calls.push(file);
192
+ if (file === "powershell") {
193
+ throw new Error("powershell unavailable");
194
+ }
195
+ return tasklistOutput([
196
+ [process.ppid, "omp.exe"],
197
+ [WINDOWS_FOREIGN_PID, "notepad.exe"],
198
+ ]);
199
+ }),
200
+ });
201
+
202
+ const discovery = inspectLivePiProcesses();
203
+ expect(discovery.processIds).not.toContain(process.ppid);
204
+ expect(discovery.skippedAncestorPids).toContain(process.ppid);
205
+ expect(calls[0]).toBe("powershell");
206
+ expect(calls).toContain("tasklist");
207
+ });
208
+
209
+ test("(a) CIM parent omp.exe with a Pi arc is skipped as an ancestor", () => {
210
+ __setRpcIdentityTestHooks({
211
+ platform: "win32",
212
+ nowMs: () => PLUGIN_BUILD_MS,
213
+ processListExecFileSync: windowsProcessListExec((file) => {
214
+ if (file !== "powershell") {
215
+ throw new Error(`${file} must not run when CIM succeeds`);
216
+ }
217
+ return cimOutput([
218
+ {
219
+ ProcessId: process.pid,
220
+ ParentProcessId: WINDOWS_LAUNCHER_PID,
221
+ CommandLine: BUN_PI_CHILD_COMMAND,
222
+ CreationDate: wmiCreationDate(PLUGIN_BUILD_MS),
223
+ },
224
+ {
225
+ ProcessId: WINDOWS_LAUNCHER_PID,
226
+ ParentProcessId: 4,
227
+ CommandLine: OMP_PI_ARC_COMMAND,
228
+ CreationDate: wmiCreationDate(OLDER_THAN_PLUGIN_MS),
229
+ },
230
+ ]);
231
+ }),
232
+ });
233
+
234
+ const discovery = inspectLivePiProcesses();
235
+ expect(discovery.processIds).not.toContain(WINDOWS_LAUNCHER_PID);
236
+ expect(discovery.processIds).not.toContain(process.pid);
237
+ expect(discovery.skippedAncestorPids).toContain(WINDOWS_LAUNCHER_PID);
238
+ expect(discovery.state).toBe("known");
239
+ });
240
+
241
+ test("(b) CIM unrelated omp.exe with a Pi arc and older CreationDate is a verified blocker", () => {
242
+ __setRpcIdentityTestHooks({
243
+ platform: "win32",
244
+ nowMs: () => PLUGIN_BUILD_MS,
245
+ processListExecFileSync: windowsProcessListExec((file) => {
246
+ if (file !== "powershell") {
247
+ throw new Error(`${file} must not run when CIM succeeds`);
248
+ }
249
+ return cimOutput([
250
+ {
251
+ ProcessId: process.pid,
252
+ ParentProcessId: process.ppid,
253
+ CommandLine: BUN_PI_CHILD_COMMAND,
254
+ CreationDate: wmiCreationDate(PLUGIN_BUILD_MS),
255
+ },
256
+ {
257
+ ProcessId: WINDOWS_FOREIGN_PID,
258
+ ParentProcessId: 8,
259
+ CommandLine: OMP_PI_ARC_COMMAND,
260
+ CreationDate: wmiCreationDate(OLDER_THAN_PLUGIN_MS, 60),
261
+ },
262
+ ]);
263
+ }),
264
+ });
265
+
266
+ const discovery = inspectLivePiProcesses();
267
+ expect(discovery.state).toBe("known");
268
+ expect(discovery.processIds).toEqual([WINDOWS_FOREIGN_PID]);
269
+ expect(readProcessProbeEvidence(WINDOWS_FOREIGN_PID)).toEqual({
270
+ startTime: OLDER_THAN_PLUGIN_MS,
271
+ commandLine: OMP_PI_ARC_COMMAND,
272
+ });
273
+ });
274
+
275
+ test("(c) CIM omp.exe with no Pi arc is inconclusive", () => {
276
+ __setRpcIdentityTestHooks({
277
+ platform: "win32",
278
+ processListExecFileSync: windowsProcessListExec((file) => {
279
+ if (file !== "powershell") {
280
+ throw new Error(`${file} must not run when CIM succeeds`);
281
+ }
282
+ return cimOutput([
283
+ {
284
+ ProcessId: WINDOWS_FOREIGN_PID,
285
+ ParentProcessId: 8,
286
+ CommandLine: BARE_OMP_COMMAND,
287
+ CreationDate: wmiCreationDate(OLDER_THAN_PLUGIN_MS),
288
+ },
289
+ ]);
290
+ }),
291
+ });
292
+
293
+ const discovery = inspectLivePiProcesses();
294
+ expect(discovery.state).toBe("inconclusive");
295
+ expect(discovery.processIds).toEqual([]);
296
+ expect(discovery.inconclusivePids).toEqual([WINDOWS_FOREIGN_PID]);
297
+ });
298
+
299
+ test("(d) PowerShell unavailable falls back to tasklist as inconclusive", () => {
300
+ const calls: string[] = [];
301
+ __setRpcIdentityTestHooks({
302
+ platform: "win32",
303
+ processListExecFileSync: windowsProcessListExec((file) => {
304
+ calls.push(file);
305
+ if (file === "powershell") {
306
+ throw new Error("powershell unavailable");
307
+ }
308
+ return tasklistOutput([
309
+ [WINDOWS_FOREIGN_PID, "omp.exe"],
310
+ [41002, "opencode.exe"],
311
+ ]);
312
+ }),
313
+ });
314
+
315
+ const discovery = inspectLivePiProcesses();
316
+ expect(discovery.state).toBe("inconclusive");
317
+ expect(discovery.processIds).toEqual([]);
318
+ expect(discovery.inconclusivePids).toEqual([WINDOWS_FOREIGN_PID]);
319
+ expect(calls).toEqual(["powershell", "tasklist"]);
320
+ });
321
+
322
+ test("(e) POSIX ancestor exclusion skips the parent Pi harness", () => {
323
+ __setRpcIdentityTestHooks({
324
+ platform: "darwin",
325
+ processListExecFileSync: windowsProcessListExec((file, args) => {
326
+ if (file === "ps" && args.includes("ppid=")) {
327
+ const pidIndex = args.indexOf("-p");
328
+ const pid = Number(args[pidIndex + 1]);
329
+ if (pid === process.pid) return ` ${process.ppid}\n`;
330
+ if (pid === process.ppid) return " 1\n";
331
+ return " 1\n";
332
+ }
333
+ return [
334
+ ` ${process.pid} bun /opt/node_modules/@oh-my-pi/pi-coding-agent/dist/cli.js`,
335
+ ` ${process.ppid} bun /opt/node_modules/@oh-my-pi/pi-coding-agent/dist/cli.js`,
336
+ " 41001 bun /opt/node_modules/@oh-my-pi/pi-coding-agent/dist/cli.js",
337
+ ].join("\n");
338
+ }),
339
+ });
340
+
341
+ const discovery = inspectLivePiProcesses();
342
+ expect(discovery.processIds).toEqual([41001]);
343
+ expect(discovery.skippedAncestorPids).toContain(process.ppid);
344
+ });
345
+
346
+ test("(f) CIM single-object JSON shape parses", () => {
347
+ __setRpcIdentityTestHooks({
348
+ platform: "win32",
349
+ processListExecFileSync: windowsProcessListExec((file) => {
350
+ if (file !== "powershell") {
351
+ throw new Error(`${file} must not run when CIM succeeds`);
352
+ }
353
+ return cimOutput([
354
+ {
355
+ ProcessId: WINDOWS_FOREIGN_PID,
356
+ ParentProcessId: 8,
357
+ CommandLine: OMP_PI_ARC_COMMAND,
358
+ CreationDate: wmiCreationDate(OLDER_THAN_PLUGIN_MS),
359
+ },
360
+ ]);
361
+ }),
362
+ });
363
+
364
+ expect(inspectLivePiProcesses()).toMatchObject({
132
365
  state: "known",
133
- processIds: [41001],
366
+ processIds: [WINDOWS_FOREIGN_PID],
134
367
  });
135
- expect(calls).toEqual(["tasklist"]);
136
368
  });
137
369
  });
138
370
 
@@ -369,6 +601,15 @@ describe("isPidIdentityPlausible", () => {
369
601
 
370
602
  calls.length = 0;
371
603
  expect(isPidIdentityPlausible(record(NOW_MS))).toBe("inconclusive");
372
- expect(calls).toEqual([]);
604
+ expect(calls).toEqual([
605
+ {
606
+ file: "powershell",
607
+ args: [
608
+ "-NoProfile",
609
+ "-Command",
610
+ "Get-CimInstance Win32_Process | Select-Object ProcessId,ParentProcessId,CommandLine,CreationDate | ConvertTo-Json -Compress",
611
+ ],
612
+ },
613
+ ]);
373
614
  });
374
615
  });