@polderlabs/bizar-plugin 0.6.0 → 0.6.1

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.
@@ -8,10 +8,11 @@ import { describe, test, expect } from "bun:test";
8
8
  import { fingerprint } from "../src/fingerprint";
9
9
  import { mkdirSync, rmSync, writeFileSync } from "node:fs";
10
10
  import path from "node:path";
11
+ import os from "node:os";
11
12
 
12
13
  /** Use a real temp directory as the worktree for path normalization tests */
13
- const WORKTREE = "/tmp/bizar-fingerprint-test-worktree";
14
- const OUTSIDE_TREE = "/tmp/bizar-outside-worktree";
14
+ const WORKTREE = path.join(os.tmpdir(), "bizar-fingerprint-test-worktree");
15
+ const OUTSIDE_TREE = path.join(os.tmpdir(), "bizar-outside-worktree");
15
16
 
16
17
  function setupWorktree() {
17
18
  try {
@@ -33,22 +34,22 @@ setupWorktree();
33
34
 
34
35
  describe("fingerprint — stable hash", () => {
35
36
  test("same args produce the same fingerprint", () => {
36
- const args = { path: "/tmp/foo.ts", recursive: false };
37
+ const args = { path: path.join(os.tmpdir(), "foo.ts"), recursive: false };
37
38
  const a = fingerprint("read", args, WORKTREE);
38
39
  const b = fingerprint("read", args, WORKTREE);
39
40
  expect(a).toBe(b);
40
41
  });
41
42
 
42
43
  test("different tool name produces different fingerprint", () => {
43
- const args = { path: "/tmp/foo.ts" };
44
+ const args = { path: path.join(os.tmpdir(), "foo.ts") };
44
45
  const a = fingerprint("read", args, WORKTREE);
45
46
  const b = fingerprint("edit", args, WORKTREE);
46
47
  expect(a).not.toBe(b);
47
48
  });
48
49
 
49
50
  test("different args produce different fingerprint", () => {
50
- const a = fingerprint("read", { path: "/tmp/foo.ts" }, WORKTREE);
51
- const b = fingerprint("read", { path: "/tmp/bar.ts" }, WORKTREE);
51
+ const a = fingerprint("read", { path: path.join(os.tmpdir(), "foo.ts") }, WORKTREE);
52
+ const b = fingerprint("read", { path: path.join(os.tmpdir(), "bar.ts") }, WORKTREE);
52
53
  expect(a).not.toBe(b);
53
54
  });
54
55
 
@@ -74,7 +75,7 @@ describe("fingerprint — path normalization", () => {
74
75
  const fp2 = fingerprint("read", { path: outside }, WORKTREE);
75
76
  expect(fp1).toBe(fp2);
76
77
  // Must NOT collide with a different outside path
77
- const different = "/tmp/different/path/file.txt";
78
+ const different = path.join(os.tmpdir(), "different/path/file.txt");
78
79
  const fp3 = fingerprint("read", { path: different }, WORKTREE);
79
80
  expect(fp1).not.toBe(fp3);
80
81
  });
@@ -90,38 +91,38 @@ describe("fingerprint — path normalization", () => {
90
91
 
91
92
  describe("fingerprint — noise field stripping", () => {
92
93
  test("strips timestamp fields", () => {
93
- const a = fingerprint("read", { path: "/tmp/foo.ts", createdAt: 1234567890 }, WORKTREE);
94
- const b = fingerprint("read", { path: "/tmp/foo.ts", createdAt: 9999999999 }, WORKTREE);
94
+ const a = fingerprint("read", { path: path.join(os.tmpdir(), "foo.ts"), createdAt: 1234567890 }, WORKTREE);
95
+ const b = fingerprint("read", { path: path.join(os.tmpdir(), "foo.ts"), createdAt: 9999999999 }, WORKTREE);
95
96
  expect(a).toBe(b);
96
97
  });
97
98
 
98
99
  test("strips updatedAt timestamp fields", () => {
99
- const a = fingerprint("read", { path: "/tmp/foo.ts", updatedAt: "2024-01-01T00:00:00Z" }, WORKTREE);
100
- const b = fingerprint("read", { path: "/tmp/foo.ts", updatedAt: "2025-12-31T23:59:59Z" }, WORKTREE);
100
+ const a = fingerprint("read", { path: path.join(os.tmpdir(), "foo.ts"), updatedAt: "2024-01-01T00:00:00Z" }, WORKTREE);
101
+ const b = fingerprint("read", { path: path.join(os.tmpdir(), "foo.ts"), updatedAt: "2025-12-31T23:59:59Z" }, WORKTREE);
101
102
  expect(a).toBe(b);
102
103
  });
103
104
 
104
105
  test("strips id field", () => {
105
- const a = fingerprint("read", { path: "/tmp/foo.ts", id: "abc123" }, WORKTREE);
106
- const b = fingerprint("read", { path: "/tmp/foo.ts", id: "xyz789" }, WORKTREE);
106
+ const a = fingerprint("read", { path: path.join(os.tmpdir(), "foo.ts"), id: "abc123" }, WORKTREE);
107
+ const b = fingerprint("read", { path: path.join(os.tmpdir(), "foo.ts"), id: "xyz789" }, WORKTREE);
107
108
  expect(a).toBe(b);
108
109
  });
109
110
 
110
111
  test("strips uuid field", () => {
111
- const a = fingerprint("read", { path: "/tmp/foo.ts", uuid: "550e8400-e29b-41d4-a716-446655440000" }, WORKTREE);
112
- const b = fingerprint("read", { path: "/tmp/foo.ts", uuid: "6ba7b810-9dad-11d1-80b4-00c04fd430c8" }, WORKTREE);
112
+ const a = fingerprint("read", { path: path.join(os.tmpdir(), "foo.ts"), uuid: "550e8400-e29b-41d4-a716-446655440000" }, WORKTREE);
113
+ const b = fingerprint("read", { path: path.join(os.tmpdir(), "foo.ts"), uuid: "6ba7b810-9dad-11d1-80b4-00c04fd430c8" }, WORKTREE);
113
114
  expect(a).toBe(b);
114
115
  });
115
116
 
116
117
  test("strips nonce field", () => {
117
- const a = fingerprint("read", { path: "/tmp/foo.ts", nonce: "random1" }, WORKTREE);
118
- const b = fingerprint("read", { path: "/tmp/foo.ts", nonce: "random2" }, WORKTREE);
118
+ const a = fingerprint("read", { path: path.join(os.tmpdir(), "foo.ts"), nonce: "random1" }, WORKTREE);
119
+ const b = fingerprint("read", { path: path.join(os.tmpdir(), "foo.ts"), nonce: "random2" }, WORKTREE);
119
120
  expect(a).toBe(b);
120
121
  });
121
122
 
122
123
  test("strips cwd field entirely", () => {
123
- const a = fingerprint("read", { path: "/tmp/foo.ts", cwd: "/home/user" }, WORKTREE);
124
- const b = fingerprint("read", { path: "/tmp/foo.ts", cwd: "/completely/different" }, WORKTREE);
124
+ const a = fingerprint("read", { path: path.join(os.tmpdir(), "foo.ts"), cwd: "/home/user" }, WORKTREE);
125
+ const b = fingerprint("read", { path: path.join(os.tmpdir(), "foo.ts"), cwd: "/completely/different" }, WORKTREE);
125
126
  expect(a).toBe(b);
126
127
  });
127
128
  });
@@ -130,10 +131,10 @@ describe("fingerprint — nested objects", () => {
130
131
  test("nested objects are normalized recursively", () => {
131
132
  const a = fingerprint("edit", {
132
133
  meta: { author: "Alice", timestamp: 1000 },
133
- path: "/tmp/foo.ts",
134
+ path: path.join(os.tmpdir(), "foo.ts"),
134
135
  }, WORKTREE);
135
136
  const b = fingerprint("edit", {
136
- path: "/tmp/foo.ts",
137
+ path: path.join(os.tmpdir(), "foo.ts"),
137
138
  meta: { timestamp: 9999, author: "Alice" },
138
139
  }, WORKTREE);
139
140
  expect(a).toBe(b);
@@ -6,6 +6,8 @@
6
6
  */
7
7
 
8
8
  import { describe, it, expect } from "bun:test";
9
+ import os from "node:os";
10
+ import path from "node:path";
9
11
 
10
12
  // ---------------------------------------------------------------------------
11
13
  // Types that mirror the expected HttpClient API from §1 / §2
@@ -126,12 +128,12 @@ describe("HttpClient.createSession", () => {
126
128
  parentID: "parent_sess",
127
129
  title: "bgr:mimir:bgr_01",
128
130
  agent: "mimir",
129
- directory: "/tmp/worktree",
131
+ directory: path.join(os.tmpdir(), "worktree"),
130
132
  });
131
133
 
132
134
  expect(session.id).toBeTruthy();
133
135
  expect(session.projectID).toBe("proj_test");
134
- expect(session.directory).toBe("/tmp/worktree");
136
+ expect(session.directory).toBe(path.join(os.tmpdir(), "worktree"));
135
137
  expect(session.parentID).toBe("parent_sess");
136
138
  expect(session.title).toBe("bgr:mimir:bgr_01");
137
139
  });
@@ -260,7 +262,7 @@ describe("HttpClient.sendPrompt", () => {
260
262
  messageID: "msg_model_test",
261
263
  prompt: "Use a specific model",
262
264
  agent: "mimir",
263
- model: { providerID: "minimax", modelID: "MiniMax-M3" },
265
+ model: { providerID: "openrouter", modelID: "minimax-m3" },
264
266
  directory: "/tmp",
265
267
  });
266
268
 
@@ -1,4 +1,6 @@
1
1
  import { describe, expect, test } from "bun:test";
2
+ import os from "node:os";
3
+ import path from "node:path";
2
4
  import {
3
5
  DEFAULT_OPTIONS,
4
6
  expandHome,
@@ -126,9 +128,9 @@ describe("normalizeOptions", () => {
126
128
  });
127
129
 
128
130
  test("custom logDir and stateDir are preserved", () => {
129
- const { options } = normalizeOptions({ logDir: "/tmp/my-logs", stateDir: "/tmp/my-state" });
130
- expect(options.logDir).toBe("/tmp/my-logs");
131
- expect(options.stateDir).toBe("/tmp/my-state");
131
+ const { options } = normalizeOptions({ logDir: path.join(os.tmpdir(), "my-logs"), stateDir: path.join(os.tmpdir(), "my-state") });
132
+ expect(options.logDir).toBe(path.join(os.tmpdir(), "my-logs"));
133
+ expect(options.stateDir).toBe(path.join(os.tmpdir(), "my-state"));
132
134
  });
133
135
  });
134
136
 
@@ -138,7 +140,7 @@ describe("findSecretDirMatch", () => {
138
140
  const home = process.env.HOME ?? "/home/test";
139
141
 
140
142
  test("returns null for safe paths", () => {
141
- expect(findSecretDirMatch("/tmp/foo")).toBeNull();
143
+ expect(findSecretDirMatch(path.join(os.tmpdir(), "foo"))).toBeNull();
142
144
  expect(findSecretDirMatch("/home/user/project")).toBeNull();
143
145
  });
144
146
 
@@ -181,8 +183,8 @@ describe("findOffendingPath", () => {
181
183
  test("returns null when both paths are safe", () => {
182
184
  const opts: NormalizedOptions = {
183
185
  ...D,
184
- logDir: "/tmp/bizar-logs",
185
- stateDir: "/tmp/bizar-state",
186
+ logDir: path.join(os.tmpdir(), "bizar-logs"),
187
+ stateDir: path.join(os.tmpdir(), "bizar-state"),
186
188
  };
187
189
  expect(findOffendingPath(opts)).toBeNull();
188
190
  });
@@ -192,7 +194,7 @@ describe("findOffendingPath", () => {
192
194
  const opts: NormalizedOptions = {
193
195
  ...D,
194
196
  logDir: `${home}/.ssh/evil`,
195
- stateDir: "/tmp/bizar-state",
197
+ stateDir: path.join(os.tmpdir(), "bizar-state"),
196
198
  };
197
199
  const result = findOffendingPath(opts);
198
200
  expect(result).not.toBeNull();
@@ -204,7 +206,7 @@ describe("findOffendingPath", () => {
204
206
  const home = process.env.HOME ?? "/home/test";
205
207
  const opts: NormalizedOptions = {
206
208
  ...D,
207
- logDir: "/tmp/bizar-logs",
209
+ logDir: path.join(os.tmpdir(), "bizar-logs"),
208
210
  stateDir: `${home}/.aws/creds`,
209
211
  };
210
212
  const result = findOffendingPath(opts);
@@ -44,7 +44,7 @@ class MockLogger {
44
44
  error(m: string) { this.messages.push({ level: "error", message: m }); }
45
45
  }
46
46
 
47
- const TEST_DIR = "/tmp/bizar-settings-test";
47
+ const TEST_DIR = path.join(os.tmpdir(), "bizar-settings-test");
48
48
 
49
49
  beforeEach(() => {
50
50
  try { rmSync(TEST_DIR, { recursive: true, force: true }); } catch { /* ok */ }
@@ -252,7 +252,7 @@ describe("SettingsStore — file path", () => {
252
252
  });
253
253
 
254
254
  test("settings written with ~ path land in the expanded location", async () => {
255
- const tmp = "/tmp/bizar-settings-expansion-test";
255
+ const tmp = path.join(os.tmpdir(), "bizar-settings-expansion-test");
256
256
  rmSync(tmp, { recursive: true, force: true });
257
257
  try {
258
258
  const logger = makeLogger();
@@ -16,6 +16,7 @@
16
16
  import { describe, it, expect, beforeEach, afterEach } from "bun:test";
17
17
  import { writeFileSync, mkdirSync, unlinkSync, rmSync } from "node:fs";
18
18
  import path from "node:path";
19
+ import os from "node:os";
19
20
 
20
21
  // ---------------------------------------------------------------------------
21
22
  // Group 1 — researchInterventionPrompt
@@ -130,7 +131,7 @@ const silentLogger = {
130
131
  };
131
132
 
132
133
  function makeTempDir(prefix: string): string {
133
- const dir = `/tmp/bizar-stall-test-${prefix}-${process.pid}`;
134
+ const dir = path.join(os.tmpdir(), `bizar-stall-test-${prefix}-${process.pid}`);
134
135
  mkdirSync(dir, { recursive: true });
135
136
  return dir;
136
137
  }
@@ -161,11 +162,11 @@ describe("BackgroundState schema backfill", () => {
161
162
  agent: "mimir",
162
163
  status: "running",
163
164
  startedAt,
164
- model: "minimax/MiniMax-M3",
165
+ model: "openrouter/minimax-m3",
165
166
  promptPreview: "Do the thing",
166
167
  toolCallCount: 0,
167
168
  parentAgent: "odin",
168
- logPath: "/tmp/test.log",
169
+ logPath: path.join(os.tmpdir(), "test.log"),
169
170
  timeoutMs: 300_000,
170
171
  // lastEventAt is intentionally absent
171
172
  lastToolOrTextAt: startedAt,
@@ -188,11 +189,11 @@ describe("BackgroundState schema backfill", () => {
188
189
  agent: "mimir",
189
190
  status: "running",
190
191
  startedAt,
191
- model: "minimax/MiniMax-M3",
192
+ model: "openrouter/minimax-m3",
192
193
  promptPreview: "Do the thing",
193
194
  toolCallCount: 0,
194
195
  parentAgent: "odin",
195
- logPath: "/tmp/test.log",
196
+ logPath: path.join(os.tmpdir(), "test.log"),
196
197
  timeoutMs: 300_000,
197
198
  lastEventAt: startedAt,
198
199
  // lastToolOrTextAt is intentionally absent
@@ -215,11 +216,11 @@ describe("BackgroundState schema backfill", () => {
215
216
  agent: "mimir",
216
217
  status: "running",
217
218
  startedAt,
218
- model: "minimax/MiniMax-M3",
219
+ model: "openrouter/minimax-m3",
219
220
  promptPreview: "Do the thing",
220
221
  toolCallCount: 0,
221
222
  parentAgent: "odin",
222
- logPath: "/tmp/test.log",
223
+ logPath: path.join(os.tmpdir(), "test.log"),
223
224
  timeoutMs: 300_000,
224
225
  lastEventAt: startedAt,
225
226
  lastToolOrTextAt: startedAt,
@@ -436,7 +437,7 @@ function makeBgState(overrides: Partial<BackgroundState> = {}): BackgroundState
436
437
  agent: "mimir",
437
438
  status: "running",
438
439
  startedAt: now,
439
- model: "minimax/MiniMax-M3",
440
+ model: "openrouter/minimax-m3",
440
441
  promptPreview: "Do the thing",
441
442
  resultPreview: undefined,
442
443
  resultMessageIds: [],
@@ -699,11 +700,11 @@ describe("bg-status toView — v0.3.0 fields", () => {
699
700
  agent: "mimir",
700
701
  status: "running",
701
702
  startedAt: now - 600_000,
702
- model: "minimax/MiniMax-M3",
703
+ model: "openrouter/minimax-m3",
703
704
  promptPreview: "Research X",
704
705
  toolCallCount: 0,
705
706
  parentAgent: "odin",
706
- logPath: "/tmp/test.log",
707
+ logPath: path.join(os.tmpdir(), "test.log"),
707
708
  timeoutMs: 300_000,
708
709
  lastEventAt: now - 60_000,
709
710
  lastToolOrTextAt: now - 60_000,
@@ -728,11 +729,11 @@ describe("bg-status toView — v0.3.0 fields", () => {
728
729
  agent: "mimir",
729
730
  status: "running",
730
731
  startedAt: now,
731
- model: "minimax/MiniMax-M3",
732
+ model: "openrouter/minimax-m3",
732
733
  promptPreview: "Research Y",
733
734
  toolCallCount: 0,
734
735
  parentAgent: "odin",
735
- logPath: "/tmp/test.log",
736
+ logPath: path.join(os.tmpdir(), "test.log"),
736
737
  timeoutMs: 300_000,
737
738
  // v0.3.0 fields are absent (no intervention yet)
738
739
  };
@@ -9,6 +9,7 @@ import { describe, test, expect, beforeEach, afterEach } from "bun:test";
9
9
  import { StateStore, SessionState, EMPTY_STATE } from "../src/state";
10
10
  import { mkdirSync, rmSync, writeFileSync, existsSync, utimesSync } from "node:fs";
11
11
  import path from "node:path";
12
+ import os from "node:os";
12
13
 
13
14
  // Minimal mock logger that collects all messages
14
15
  class MockLogger {
@@ -18,7 +19,7 @@ class MockLogger {
18
19
  }
19
20
  }
20
21
 
21
- const TEST_DIR = "/tmp/bizar-state-test";
22
+ const TEST_DIR = path.join(os.tmpdir(), "bizar-state-test");
22
23
  const TEST_SESSION_A = "session-a-123";
23
24
  const TEST_SESSION_B = "session-b-456";
24
25
 
@@ -27,13 +27,13 @@ function parseModel(model: string | undefined): { providerID: string; modelID: s
27
27
  const parts = model.split("/");
28
28
  if (parts.length !== 2) {
29
29
  throw new Error(
30
- `model must be in "providerID/modelID" format (e.g. "minimax/MiniMax-M3"). Omit to use the agent's default.`,
30
+ `model must be in "providerID/modelID" format (e.g. "openrouter/minimax-m3"). Omit to use the agent's default.`,
31
31
  );
32
32
  }
33
33
  const [providerID, modelID] = parts;
34
34
  if (!providerID || !modelID) {
35
35
  throw new Error(
36
- `model must be in "providerID/modelID" format (e.g. "minimax/MiniMax-M3"). Omit to use the agent's default.`,
36
+ `model must be in "providerID/modelID" format (e.g. "openrouter/minimax-m3"). Omit to use the agent's default.`,
37
37
  );
38
38
  }
39
39
  return { providerID, modelID };
@@ -132,9 +132,9 @@ describe("bizar_spawn_background — Odin-only", () => {
132
132
  // ---------------------------------------------------------------------------
133
133
 
134
134
  describe("bizar_spawn_background — model parsing (HIGH-3, LOW-34)", () => {
135
- it('"minimax/MiniMax-M3" parses to { providerID: "minimax", modelID: "MiniMax-M3" }', () => {
136
- const result = parseModel("minimax/MiniMax-M3");
137
- expect(result).toEqual({ providerID: "minimax", modelID: "MiniMax-M3" });
135
+ it('"openrouter/minimax-m3" parses to { providerID: "openrouter", modelID: "minimax-m3" }', () => {
136
+ const result = parseModel("openrouter/minimax-m3");
137
+ expect(result).toEqual({ providerID: "openrouter", modelID: "minimax-m3" });
138
138
  });
139
139
 
140
140
  it('"opencode/deepseek-v4-flash-free" parses correctly', () => {
@@ -142,20 +142,20 @@ describe("bizar_spawn_background — model parsing (HIGH-3, LOW-34)", () => {
142
142
  expect(result).toEqual({ providerID: "opencode", modelID: "deepseek-v4-flash-free" });
143
143
  });
144
144
 
145
- it('"MiniMax-M3" (no /) is rejected', () => {
146
- expect(() => parseModel("MiniMax-M3")).toThrow();
145
+ it('"minimax-m3" (no /) is rejected', () => {
146
+ expect(() => parseModel("minimax-m3")).toThrow();
147
147
  });
148
148
 
149
149
  it('"a/b/c" (multiple /) is rejected', () => {
150
150
  expect(() => parseModel("a/b/c")).toThrow();
151
151
  });
152
152
 
153
- it('"minimax/" (empty modelID) is rejected', () => {
154
- expect(() => parseModel("minimax/")).toThrow();
153
+ it('"openrouter/" (empty modelID) is rejected', () => {
154
+ expect(() => parseModel("openrouter/")).toThrow();
155
155
  });
156
156
 
157
- it('"/MiniMax-M3" (empty providerID) is rejected', () => {
158
- expect(() => parseModel("/MiniMax-M3")).toThrow();
157
+ it('"/minimax-m3" (empty providerID) is rejected', () => {
158
+ expect(() => parseModel("/minimax-m3")).toThrow();
159
159
  });
160
160
 
161
161
  it("undefined model returns undefined (agent uses its default)", () => {
@@ -168,7 +168,7 @@ describe("bizar_spawn_background — model parsing (HIGH-3, LOW-34)", () => {
168
168
 
169
169
  it("spawn tool includes parsed model in POST /session body", () => {
170
170
  const result = bizarre_spawn_background(
171
- { agent: "mimir", prompt: "Do X", model: "minimax/MiniMax-M3" },
171
+ { agent: "mimir", prompt: "Do X", model: "openrouter/minimax-m3" },
172
172
  { agent: "odin", sessionID: "sess_parent", worktree: "/tmp" },
173
173
  );
174
174
  expect(result).not.toHaveProperty("error");
@@ -52,7 +52,7 @@ function makeDraft(overrides: Partial<AddDraft> = {}): AddDraft {
52
52
  error: undefined,
53
53
  parentAgent: "odin",
54
54
  parentInstanceId: undefined,
55
- logPath: "/tmp/bgr_deadlock_test.log",
55
+ logPath: path.join(os.tmpdir(), "bgr_deadlock_test.log"),
56
56
  timeoutMs: 30_000,
57
57
  toolCallCount: 0,
58
58
  loopGuardTool: undefined,