opencode-pilot 0.18.2 → 0.19.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.
@@ -22,6 +22,22 @@ describe("worktree", () => {
22
22
  assert.strictEqual(mockFetch.mock.calls[0].arguments[0], "http://localhost:4096/experimental/worktree");
23
23
  });
24
24
 
25
+ it("passes directory parameter when provided", async () => {
26
+ const mockFetch = mock.fn(async () => ({
27
+ ok: true,
28
+ json: async () => ["/path/to/worktree"],
29
+ }));
30
+
31
+ await listWorktrees("http://localhost:4096", {
32
+ fetch: mockFetch,
33
+ directory: "/path/to/project"
34
+ });
35
+
36
+ const calledUrl = mockFetch.mock.calls[0].arguments[0];
37
+ assert.ok(calledUrl.includes("directory="), "Should include directory param");
38
+ assert.ok(calledUrl.includes(encodeURIComponent("/path/to/project")), "Should encode directory path");
39
+ });
40
+
25
41
  it("returns empty array on error", async () => {
26
42
  const mockFetch = mock.fn(async () => ({
27
43
  ok: false,
@@ -189,14 +205,24 @@ describe("worktree", () => {
189
205
  });
190
206
 
191
207
  it("passes worktreeName when creating new worktree", async () => {
192
- const mockFetch = mock.fn(async () => ({
193
- ok: true,
194
- json: async () => ({
195
- name: "my-feature",
196
- branch: "opencode/my-feature",
197
- directory: "/data/worktree/abc123/my-feature",
198
- }),
199
- }));
208
+ const mockFetch = mock.fn(async (url, opts) => {
209
+ // First call: GET /experimental/worktree (list) - return empty to trigger creation
210
+ if (!opts || opts.method !== 'POST') {
211
+ return {
212
+ ok: true,
213
+ json: async () => [],
214
+ };
215
+ }
216
+ // Second call: POST /experimental/worktree (create)
217
+ return {
218
+ ok: true,
219
+ json: async () => ({
220
+ name: "my-feature",
221
+ branch: "opencode/my-feature",
222
+ directory: "/data/worktree/abc123/my-feature",
223
+ }),
224
+ };
225
+ });
200
226
 
201
227
  await resolveWorktreeDirectory(
202
228
  "http://localhost:4096",
@@ -205,7 +231,11 @@ describe("worktree", () => {
205
231
  { fetch: mockFetch }
206
232
  );
207
233
 
208
- const body = JSON.parse(mockFetch.mock.calls[0].arguments[1].body);
234
+ // Should have called both list (GET) and create (POST)
235
+ assert.strictEqual(mockFetch.mock.calls.length, 2);
236
+ // Second call should be POST with the worktree name
237
+ const postCall = mockFetch.mock.calls[1];
238
+ const body = JSON.parse(postCall.arguments[1].body);
209
239
  assert.strictEqual(body.name, "my-feature");
210
240
  });
211
241
 
@@ -250,6 +280,117 @@ describe("worktree", () => {
250
280
  assert.strictEqual(result.directory, "/data/worktree/abc123/my-feature");
251
281
  });
252
282
 
283
+ it("reuses existing sandbox when worktree='new' and worktreeName matches", async () => {
284
+ let postCalled = false;
285
+ let listUrl = null;
286
+ const mockFetch = mock.fn(async (url, opts) => {
287
+ // GET /experimental/worktree (list) - returns existing sandbox with matching name
288
+ if (!opts || opts.method !== 'POST') {
289
+ listUrl = url;
290
+ return {
291
+ ok: true,
292
+ json: async () => [
293
+ "/data/worktree/abc123/other-branch",
294
+ "/data/worktree/abc123/my-feature", // matches worktreeName
295
+ ],
296
+ };
297
+ }
298
+ // POST /experimental/worktree (create) - should NOT be called
299
+ postCalled = true;
300
+ return { ok: true, json: async () => ({}) };
301
+ });
302
+
303
+ const result = await resolveWorktreeDirectory(
304
+ "http://localhost:4096",
305
+ "/path/to/project",
306
+ { worktree: "new", worktreeName: "my-feature" },
307
+ { fetch: mockFetch }
308
+ );
309
+
310
+ // Should reuse existing sandbox
311
+ assert.strictEqual(result.directory, "/data/worktree/abc123/my-feature");
312
+ assert.strictEqual(result.worktreeReused, true);
313
+ assert.strictEqual(result.worktreeCreated, undefined);
314
+ // POST should NOT have been called
315
+ assert.strictEqual(postCalled, false, "Should not call POST when reusing existing sandbox");
316
+ // listWorktrees should be called with directory parameter
317
+ assert.ok(listUrl.includes("directory="), "Should pass directory to listWorktrees");
318
+ assert.ok(listUrl.includes(encodeURIComponent("/path/to/project")), "Should pass correct project directory");
319
+ });
320
+
321
+ it("creates new sandbox when worktree='new' and no matching name exists", async () => {
322
+ let getCalled = false;
323
+ let postCalled = false;
324
+ const mockFetch = mock.fn(async (url, opts) => {
325
+ // GET /experimental/worktree (list) - no matching name
326
+ if (!opts || opts.method !== 'POST') {
327
+ getCalled = true;
328
+ return {
329
+ ok: true,
330
+ json: async () => ["/data/worktree/abc123/other-branch"],
331
+ };
332
+ }
333
+ // POST /experimental/worktree (create)
334
+ postCalled = true;
335
+ return {
336
+ ok: true,
337
+ json: async () => ({
338
+ name: "new-feature",
339
+ branch: "opencode/new-feature",
340
+ directory: "/data/worktree/abc123/new-feature",
341
+ }),
342
+ };
343
+ });
344
+
345
+ const result = await resolveWorktreeDirectory(
346
+ "http://localhost:4096",
347
+ "/path/to/project",
348
+ { worktree: "new", worktreeName: "new-feature" },
349
+ { fetch: mockFetch }
350
+ );
351
+
352
+ // Should create new sandbox
353
+ assert.strictEqual(result.directory, "/data/worktree/abc123/new-feature");
354
+ assert.strictEqual(result.worktreeCreated, true);
355
+ assert.strictEqual(result.worktreeReused, undefined);
356
+ // Both GET and POST should have been called
357
+ assert.strictEqual(getCalled, true, "Should call GET to list existing worktrees");
358
+ assert.strictEqual(postCalled, true, "Should call POST to create new worktree");
359
+ });
360
+
361
+ it("skips sandbox reuse when preferExistingSandbox is false", async () => {
362
+ let getCalled = false;
363
+ const mockFetch = mock.fn(async (url, opts) => {
364
+ // GET /experimental/worktree - should NOT be called
365
+ if (!opts || opts.method !== 'POST') {
366
+ getCalled = true;
367
+ return { ok: true, json: async () => ["/data/worktree/abc123/my-feature"] };
368
+ }
369
+ // POST /experimental/worktree (create)
370
+ return {
371
+ ok: true,
372
+ json: async () => ({
373
+ name: "my-feature",
374
+ branch: "opencode/my-feature",
375
+ directory: "/data/worktree/abc123/my-feature-new",
376
+ }),
377
+ };
378
+ });
379
+
380
+ const result = await resolveWorktreeDirectory(
381
+ "http://localhost:4096",
382
+ "/path/to/project",
383
+ { worktree: "new", worktreeName: "my-feature", preferExistingSandbox: false },
384
+ { fetch: mockFetch }
385
+ );
386
+
387
+ // Should create new sandbox, not reuse
388
+ assert.strictEqual(result.directory, "/data/worktree/abc123/my-feature-new");
389
+ assert.strictEqual(result.worktreeCreated, true);
390
+ // GET should NOT have been called (skipped reuse check)
391
+ assert.strictEqual(getCalled, false, "Should skip GET when preferExistingSandbox is false");
392
+ });
393
+
253
394
  it("returns error when named worktree not found", async () => {
254
395
  const mockFetch = mock.fn(async () => ({
255
396
  ok: true,