opencode-pilot 0.24.8 → 0.24.9

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.
@@ -1,8 +1,8 @@
1
1
  class OpencodePilot < Formula
2
2
  desc "Automation daemon for OpenCode - polls GitHub/Linear issues and spawns sessions"
3
3
  homepage "https://github.com/athal7/opencode-pilot"
4
- url "https://github.com/athal7/opencode-pilot/archive/refs/tags/v0.24.7.tar.gz"
5
- sha256 "3d97d488ca8695f9785171e767b8a7a3eb65d2a8c5aca9eab3862b70cf6fc144"
4
+ url "https://github.com/athal7/opencode-pilot/archive/refs/tags/v0.24.8.tar.gz"
5
+ sha256 "23b550fd74fee8f2fd8c60c8358f67065ee77af921fdc3bc2ff26a6cfef68ae6"
6
6
  license "MIT"
7
7
 
8
8
  depends_on "node"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-pilot",
3
- "version": "0.24.8",
3
+ "version": "0.24.9",
4
4
  "type": "module",
5
5
  "main": "plugin/index.js",
6
6
  "description": "Automation daemon for OpenCode - polls for work and spawns sessions",
@@ -639,26 +639,17 @@ export async function sendMessageToSession(serverUrl, sessionId, directory, prom
639
639
  * @param {string} directory - Working directory to match
640
640
  * @param {object} [options] - Options
641
641
  * @param {function} [options.fetch] - Custom fetch function (for testing)
642
- * @param {string} [options.projectDirectory] - Project directory for session lookup
643
- * (used instead of directory when provided, so sessions created with the project
644
- * directory are found instead of old worktree-scoped sessions)
645
642
  * @returns {Promise<object|null>} Session to reuse, or null
646
643
  */
647
644
  export async function findReusableSession(serverUrl, directory, options = {}) {
648
- // Use project directory for session lookup when available.
649
- // Sessions created with v0.24.7+ are scoped to the project directory,
650
- // while messages use the worktree directory. Query the project directory
651
- // to find correctly-scoped sessions.
652
- const lookupDirectory = options.projectDirectory || directory;
653
-
654
645
  // Get sessions for this directory
655
646
  const sessions = await listSessions(serverUrl, {
656
- directory: lookupDirectory,
647
+ directory,
657
648
  fetch: options.fetch
658
649
  });
659
650
 
660
651
  if (sessions.length === 0) {
661
- debug(`findReusableSession: no sessions found for ${lookupDirectory}`);
652
+ debug(`findReusableSession: no sessions found for ${directory}`);
662
653
  return null;
663
654
  }
664
655
 
@@ -666,11 +657,11 @@ export async function findReusableSession(serverUrl, directory, options = {}) {
666
657
  const activeSessions = sessions.filter(s => !isSessionArchived(s));
667
658
 
668
659
  if (activeSessions.length === 0) {
669
- debug(`findReusableSession: all ${sessions.length} sessions are archived for ${lookupDirectory}`);
660
+ debug(`findReusableSession: all ${sessions.length} sessions are archived for ${directory}`);
670
661
  return null;
671
662
  }
672
663
 
673
- debug(`findReusableSession: found ${activeSessions.length} active sessions for ${lookupDirectory}`);
664
+ debug(`findReusableSession: found ${activeSessions.length} active sessions for ${directory}`);
674
665
 
675
666
  // Get statuses to prefer idle sessions
676
667
  const statuses = await getSessionStatuses(serverUrl, { fetch: options.fetch });
@@ -891,14 +882,16 @@ async function executeInDirectory(serverUrl, cwd, item, config, options = {}, pr
891
882
  }
892
883
  }
893
884
 
894
- // Check if we should try to reuse an existing session
885
+ // Check if we should try to reuse an existing session.
886
+ // Skip reuse when working in a worktree (projectDirectory differs from cwd),
887
+ // because querying by worktree dir finds old sessions with projectID "global",
888
+ // and querying by project dir finds unrelated sessions for other PRs.
889
+ // Each worktree should get its own correctly-scoped session.
895
890
  const reuseActiveSession = config.reuse_active_session !== false; // default true
891
+ const inWorktree = projectDirectory && projectDirectory !== cwd;
896
892
 
897
- if (reuseActiveSession && !options.dryRun) {
898
- const existingSession = await findReusableSession(serverUrl, cwd, {
899
- fetch: options.fetch,
900
- projectDirectory: projectDirectory,
901
- });
893
+ if (reuseActiveSession && !inWorktree && !options.dryRun) {
894
+ const existingSession = await findReusableSession(serverUrl, cwd, { fetch: options.fetch });
902
895
 
903
896
  if (existingSession) {
904
897
  debug(`executeInDirectory: found reusable session ${existingSession.id} for ${cwd}`);
@@ -622,14 +622,15 @@ describe("integration: worktree creation with worktree_name", () => {
622
622
  assert.strictEqual(sessionDirectory, "/proj", "Session should be scoped to project directory");
623
623
  });
624
624
 
625
- it("session reuse queries by project directory, not worktree directory", async () => {
626
- // This tests the key fix: when reprocessing an item with a worktree,
627
- // findReusableSession should query by the project directory (e.g., /proj)
628
- // so it finds sessions created with correct scoping (v0.24.7+), rather
629
- // than finding old sessions created with the worktree directory (projectID "global").
625
+ it("skips session reuse when working in a worktree", async () => {
626
+ // When working in a worktree (projectDirectory differs from cwd), session
627
+ // reuse is skipped entirely. Querying by worktree dir finds old sessions
628
+ // with projectID "global", querying by project dir finds unrelated sessions
629
+ // for other PRs. Each worktree should get its own correctly-scoped session.
630
630
 
631
- let sessionQueryDirectory = null;
631
+ let sessionListQueried = false;
632
632
  let sessionCreated = false;
633
+ let sessionCreateDirectory = null;
633
634
 
634
635
  const existingWorktreeDir = "/worktree/calm-wizard";
635
636
 
@@ -643,25 +644,16 @@ describe("integration: worktree creation with worktree_name", () => {
643
644
  "GET /experimental/worktree": () => ({
644
645
  body: [existingWorktreeDir],
645
646
  }),
646
- "GET /session": (req) => {
647
- sessionQueryDirectory = req.directory;
648
- // Return a session ONLY if queried by project directory
649
- if (req.directory === "/proj") {
650
- return {
651
- body: [{ id: "ses_proj_scoped", directory: "/proj", time: { created: 1000, updated: 2000 } }],
652
- };
653
- }
654
- // Old worktree-scoped sessions should NOT be found when querying by project dir
647
+ "GET /session": () => {
648
+ sessionListQueried = true;
655
649
  return { body: [] };
656
650
  },
657
651
  "GET /session/status": () => ({ body: {} }),
658
652
  "POST /session": (req) => {
659
653
  sessionCreated = true;
654
+ sessionCreateDirectory = req.query?.directory;
660
655
  return { body: { id: "ses_new" } };
661
656
  },
662
- "PATCH /session/ses_proj_scoped": () => ({ body: {} }),
663
- "POST /session/ses_proj_scoped/message": () => ({ body: { success: true } }),
664
- "POST /session/ses_proj_scoped/command": () => ({ body: { success: true } }),
665
657
  "PATCH /session/ses_new": () => ({ body: {} }),
666
658
  "POST /session/ses_new/message": () => ({ body: { success: true } }),
667
659
  "POST /session/ses_new/command": () => ({ body: { success: true } }),
@@ -679,12 +671,13 @@ describe("integration: worktree creation with worktree_name", () => {
679
671
  );
680
672
 
681
673
  assert.ok(result.success, "Action should succeed");
682
- // The session lookup should use the PROJECT directory, not the worktree directory
683
- assert.strictEqual(sessionQueryDirectory, "/proj",
684
- "findReusableSession should query by project directory, not worktree");
685
- // Should reuse the project-scoped session, NOT create a new one
686
- assert.strictEqual(result.sessionReused, true, "Should reuse the project-scoped session");
687
- assert.strictEqual(sessionCreated, false, "Should NOT create a new session when project-scoped session exists");
674
+ // Should NOT query for existing sessions when in a worktree
675
+ assert.strictEqual(sessionListQueried, false,
676
+ "Should skip session reuse entirely when in a worktree");
677
+ // Should create a new session scoped to the project directory
678
+ assert.ok(sessionCreated, "Should create a new session");
679
+ assert.strictEqual(sessionCreateDirectory, "/proj",
680
+ "New session should be scoped to project directory");
688
681
  });
689
682
  });
690
683
 
@@ -2147,60 +2147,6 @@ Check for bugs and security issues.`;
2147
2147
  assert.strictEqual(result.id, 'ses_new');
2148
2148
  });
2149
2149
 
2150
- test('uses projectDirectory for session lookup when provided', async () => {
2151
- const { findReusableSession } = await import('../../service/actions.js');
2152
-
2153
- let queriedDirectory = null;
2154
- const mockFetch = async (url) => {
2155
- if (url.includes('/session/status')) {
2156
- return { ok: true, json: async () => ({}) };
2157
- }
2158
- // Capture the directory used in the session query
2159
- const urlObj = new URL(url);
2160
- queriedDirectory = urlObj.searchParams.get('directory');
2161
- return {
2162
- ok: true,
2163
- json: async () => [
2164
- { id: 'ses_proj', time: { created: 1000, updated: 2000 } },
2165
- ],
2166
- };
2167
- };
2168
-
2169
- const result = await findReusableSession('http://localhost:4096', '/worktree/pr-415', {
2170
- fetch: mockFetch,
2171
- projectDirectory: '/home/user/code/odin',
2172
- });
2173
-
2174
- assert.strictEqual(result.id, 'ses_proj');
2175
- assert.strictEqual(queriedDirectory, '/home/user/code/odin',
2176
- 'Should query sessions using projectDirectory, not the worktree directory');
2177
- });
2178
-
2179
- test('falls back to directory when projectDirectory not provided', async () => {
2180
- const { findReusableSession } = await import('../../service/actions.js');
2181
-
2182
- let queriedDirectory = null;
2183
- const mockFetch = async (url) => {
2184
- if (url.includes('/session/status')) {
2185
- return { ok: true, json: async () => ({}) };
2186
- }
2187
- const urlObj = new URL(url);
2188
- queriedDirectory = urlObj.searchParams.get('directory');
2189
- return {
2190
- ok: true,
2191
- json: async () => [
2192
- { id: 'ses_1', time: { created: 1000, updated: 2000 } },
2193
- ],
2194
- };
2195
- };
2196
-
2197
- const result = await findReusableSession('http://localhost:4096', '/test/dir', { fetch: mockFetch });
2198
-
2199
- assert.strictEqual(result.id, 'ses_1');
2200
- assert.strictEqual(queriedDirectory, '/test/dir',
2201
- 'Should query sessions using directory when projectDirectory not provided');
2202
- });
2203
-
2204
2150
  test('falls back to busy session when no idle available', async () => {
2205
2151
  const { findReusableSession } = await import('../../service/actions.js');
2206
2152