opencode-pilot 0.24.7 → 0.24.8
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.
|
|
5
|
-
sha256 "
|
|
4
|
+
url "https://github.com/athal7/opencode-pilot/archive/refs/tags/v0.24.7.tar.gz"
|
|
5
|
+
sha256 "3d97d488ca8695f9785171e767b8a7a3eb65d2a8c5aca9eab3862b70cf6fc144"
|
|
6
6
|
license "MIT"
|
|
7
7
|
|
|
8
8
|
depends_on "node"
|
package/package.json
CHANGED
package/service/actions.js
CHANGED
|
@@ -639,17 +639,26 @@ 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)
|
|
642
645
|
* @returns {Promise<object|null>} Session to reuse, or null
|
|
643
646
|
*/
|
|
644
647
|
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
|
+
|
|
645
654
|
// Get sessions for this directory
|
|
646
655
|
const sessions = await listSessions(serverUrl, {
|
|
647
|
-
directory,
|
|
656
|
+
directory: lookupDirectory,
|
|
648
657
|
fetch: options.fetch
|
|
649
658
|
});
|
|
650
659
|
|
|
651
660
|
if (sessions.length === 0) {
|
|
652
|
-
debug(`findReusableSession: no sessions found for ${
|
|
661
|
+
debug(`findReusableSession: no sessions found for ${lookupDirectory}`);
|
|
653
662
|
return null;
|
|
654
663
|
}
|
|
655
664
|
|
|
@@ -657,11 +666,11 @@ export async function findReusableSession(serverUrl, directory, options = {}) {
|
|
|
657
666
|
const activeSessions = sessions.filter(s => !isSessionArchived(s));
|
|
658
667
|
|
|
659
668
|
if (activeSessions.length === 0) {
|
|
660
|
-
debug(`findReusableSession: all ${sessions.length} sessions are archived for ${
|
|
669
|
+
debug(`findReusableSession: all ${sessions.length} sessions are archived for ${lookupDirectory}`);
|
|
661
670
|
return null;
|
|
662
671
|
}
|
|
663
672
|
|
|
664
|
-
debug(`findReusableSession: found ${activeSessions.length} active sessions for ${
|
|
673
|
+
debug(`findReusableSession: found ${activeSessions.length} active sessions for ${lookupDirectory}`);
|
|
665
674
|
|
|
666
675
|
// Get statuses to prefer idle sessions
|
|
667
676
|
const statuses = await getSessionStatuses(serverUrl, { fetch: options.fetch });
|
|
@@ -886,7 +895,10 @@ async function executeInDirectory(serverUrl, cwd, item, config, options = {}, pr
|
|
|
886
895
|
const reuseActiveSession = config.reuse_active_session !== false; // default true
|
|
887
896
|
|
|
888
897
|
if (reuseActiveSession && !options.dryRun) {
|
|
889
|
-
const existingSession = await findReusableSession(serverUrl, cwd, {
|
|
898
|
+
const existingSession = await findReusableSession(serverUrl, cwd, {
|
|
899
|
+
fetch: options.fetch,
|
|
900
|
+
projectDirectory: projectDirectory,
|
|
901
|
+
});
|
|
890
902
|
|
|
891
903
|
if (existingSession) {
|
|
892
904
|
debug(`executeInDirectory: found reusable session ${existingSession.id} for ${cwd}`);
|
|
@@ -621,6 +621,71 @@ describe("integration: worktree creation with worktree_name", () => {
|
|
|
621
621
|
// Session creation uses the project directory (for correct projectID scoping)
|
|
622
622
|
assert.strictEqual(sessionDirectory, "/proj", "Session should be scoped to project directory");
|
|
623
623
|
});
|
|
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").
|
|
630
|
+
|
|
631
|
+
let sessionQueryDirectory = null;
|
|
632
|
+
let sessionCreated = false;
|
|
633
|
+
|
|
634
|
+
const existingWorktreeDir = "/worktree/calm-wizard";
|
|
635
|
+
|
|
636
|
+
mockServer = await createMockServer({
|
|
637
|
+
"GET /project": () => ({
|
|
638
|
+
body: [{ id: "proj_1", worktree: "/proj", sandboxes: [], time: { created: 1 } }],
|
|
639
|
+
}),
|
|
640
|
+
"GET /project/current": () => ({
|
|
641
|
+
body: { id: "proj_1", worktree: "/proj", sandboxes: [], time: { created: 1 } },
|
|
642
|
+
}),
|
|
643
|
+
"GET /experimental/worktree": () => ({
|
|
644
|
+
body: [existingWorktreeDir],
|
|
645
|
+
}),
|
|
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
|
|
655
|
+
return { body: [] };
|
|
656
|
+
},
|
|
657
|
+
"GET /session/status": () => ({ body: {} }),
|
|
658
|
+
"POST /session": (req) => {
|
|
659
|
+
sessionCreated = true;
|
|
660
|
+
return { body: { id: "ses_new" } };
|
|
661
|
+
},
|
|
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
|
+
"PATCH /session/ses_new": () => ({ body: {} }),
|
|
666
|
+
"POST /session/ses_new/message": () => ({ body: { success: true } }),
|
|
667
|
+
"POST /session/ses_new/command": () => ({ body: { success: true } }),
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
const result = await executeAction(
|
|
671
|
+
{ number: 42, title: "Review PR" },
|
|
672
|
+
{
|
|
673
|
+
path: "/proj",
|
|
674
|
+
prompt: "review",
|
|
675
|
+
worktree_name: "pr-{number}",
|
|
676
|
+
existing_directory: existingWorktreeDir,
|
|
677
|
+
},
|
|
678
|
+
{ discoverServer: async () => mockServer.url }
|
|
679
|
+
);
|
|
680
|
+
|
|
681
|
+
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");
|
|
688
|
+
});
|
|
624
689
|
});
|
|
625
690
|
|
|
626
691
|
describe("integration: cross-source deduplication", () => {
|
|
@@ -2147,6 +2147,60 @@ 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
|
+
|
|
2150
2204
|
test('falls back to busy session when no idle available', async () => {
|
|
2151
2205
|
const { findReusableSession } = await import('../../service/actions.js');
|
|
2152
2206
|
|