opencode-pilot 0.9.2 → 0.10.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.
package/README.md CHANGED
@@ -45,6 +45,7 @@ See [examples/config.yaml](examples/config.yaml) for a complete example with all
45
45
 
46
46
  ### Key Sections
47
47
 
48
+ - **`server_port`** - Preferred OpenCode server port (e.g., `4096`). When multiple OpenCode instances are running, pilot attaches sessions to this port.
48
49
  - **`defaults`** - Default values applied to all sources
49
50
  - **`sources`** - What to poll (presets, shorthand, or full config)
50
51
  - **`tools`** - Field mappings to normalize different MCP APIs
@@ -1,6 +1,11 @@
1
1
  # Example config.yaml for opencode-pilot
2
2
  # Copy to ~/.config/opencode-pilot/config.yaml
3
3
 
4
+ # Preferred OpenCode server port for attaching sessions
5
+ # When multiple OpenCode instances are running, pilot will attach new sessions
6
+ # to this port. If not set, pilot discovers servers automatically.
7
+ # server_port: 4096
8
+
4
9
  defaults:
5
10
  agent: plan
6
11
  prompt: default
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-pilot",
3
- "version": "0.9.2",
3
+ "version": "0.10.0",
4
4
  "type": "module",
5
5
  "main": "plugin/index.js",
6
6
  "description": "Automation daemon for OpenCode - polls for work and spawns sessions",
@@ -9,6 +9,7 @@ import { spawn, execSync } from "child_process";
9
9
  import { readFileSync, existsSync } from "fs";
10
10
  import { debug } from "./logger.js";
11
11
  import { getNestedValue } from "./utils.js";
12
+ import { getServerPort } from "./repo-config.js";
12
13
  import path from "path";
13
14
  import os from "os";
14
15
 
@@ -99,23 +100,27 @@ function isServerHealthy(project) {
99
100
  * Discover a running opencode server that matches the target directory
100
101
  *
101
102
  * Queries all running opencode servers and finds the best match based on:
102
- * 1. Exact sandbox match (highest priority)
103
- * 2. Exact worktree match
104
- * 3. Target is subdirectory of worktree
103
+ * 1. Configured server_port (highest priority if set and healthy)
104
+ * 2. Exact sandbox match
105
+ * 3. Exact worktree match
106
+ * 4. Target is subdirectory of worktree
107
+ * 5. Global server (worktree="/") as fallback
105
108
  *
106
- * NOTE: Global servers (worktree="/") are NOT used - sessions spawned via
107
- * pilot should run in isolated mode rather than attach to the global project,
108
- * since the global project doesn't have the right working directory context.
109
+ * Global servers are used as a fallback when no project-specific match is found,
110
+ * since OpenCode Desktop may be connected to a global server that can display
111
+ * sessions for any project.
109
112
  *
110
113
  * @param {string} targetDir - The directory we want to work in
111
114
  * @param {object} [options] - Options for testing/mocking
112
115
  * @param {function} [options.getPorts] - Function to get server ports
113
116
  * @param {function} [options.fetch] - Function to fetch URLs
117
+ * @param {number} [options.preferredPort] - Preferred port to use (overrides config)
114
118
  * @returns {Promise<string|null>} Server URL (e.g., "http://localhost:4096") or null
115
119
  */
116
120
  export async function discoverOpencodeServer(targetDir, options = {}) {
117
121
  const getPorts = options.getPorts || getOpencodePorts;
118
122
  const fetchFn = options.fetch || fetch;
123
+ const preferredPort = options.preferredPort ?? getServerPort();
119
124
 
120
125
  const ports = await getPorts();
121
126
  if (ports.length === 0) {
@@ -123,10 +128,28 @@ export async function discoverOpencodeServer(targetDir, options = {}) {
123
128
  return null;
124
129
  }
125
130
 
126
- debug(`discoverOpencodeServer: checking ${ports.length} servers for ${targetDir}`);
131
+ debug(`discoverOpencodeServer: checking ${ports.length} servers for ${targetDir}, preferredPort=${preferredPort}`);
132
+
133
+ // If preferred port is configured and running, check it first
134
+ if (preferredPort && ports.includes(preferredPort)) {
135
+ const url = `http://localhost:${preferredPort}`;
136
+ try {
137
+ const response = await fetchFn(`${url}/project/current`);
138
+ if (response.ok) {
139
+ const project = await response.json();
140
+ if (isServerHealthy(project)) {
141
+ debug(`discoverOpencodeServer: using preferred port ${preferredPort}`);
142
+ return url;
143
+ }
144
+ }
145
+ } catch (err) {
146
+ debug(`discoverOpencodeServer: preferred port ${preferredPort} error: ${err.message}`);
147
+ }
148
+ }
127
149
 
128
150
  let bestMatch = null;
129
151
  let bestScore = 0;
152
+ let globalServer = null;
130
153
 
131
154
  for (const port of ports) {
132
155
  const url = `http://localhost:${port}`;
@@ -148,9 +171,10 @@ export async function discoverOpencodeServer(targetDir, options = {}) {
148
171
  const worktree = project.worktree || '/';
149
172
  const sandboxes = project.sandboxes || [];
150
173
 
151
- // Skip global servers - pilot sessions should run isolated
174
+ // Track global server as fallback (but prefer project-specific matches)
152
175
  if (worktree === '/') {
153
- debug(`discoverOpencodeServer: ${url} is global project, skipping`);
176
+ debug(`discoverOpencodeServer: ${url} is global project, tracking as fallback`);
177
+ globalServer = url;
154
178
  continue;
155
179
  }
156
180
 
@@ -166,8 +190,10 @@ export async function discoverOpencodeServer(targetDir, options = {}) {
166
190
  }
167
191
  }
168
192
 
169
- debug(`discoverOpencodeServer: best match=${bestMatch} score=${bestScore}`);
170
- return bestMatch;
193
+ // Use project-specific match if found, otherwise fall back to global server
194
+ const result = bestMatch || globalServer;
195
+ debug(`discoverOpencodeServer: best match=${bestMatch} score=${bestScore}, global=${globalServer}, using=${result}`);
196
+ return result;
171
197
  }
172
198
 
173
199
  // Default templates directory
@@ -310,6 +310,15 @@ export function getCleanupTtlDays() {
310
310
  return config?.cleanup?.ttl_days ?? 30;
311
311
  }
312
312
 
313
+ /**
314
+ * Get preferred OpenCode server port from config
315
+ * @returns {number|null} Port number or null if not configured
316
+ */
317
+ export function getServerPort() {
318
+ const config = getRawConfig();
319
+ return config?.server_port ?? null;
320
+ }
321
+
313
322
  /**
314
323
  * Clear config cache (for testing)
315
324
  */
@@ -394,7 +394,7 @@ describe('actions.js', () => {
394
394
  assert.strictEqual(result, 'http://localhost:4000');
395
395
  });
396
396
 
397
- test('skips global project servers', async () => {
397
+ test('uses global project server as fallback when no specific match', async () => {
398
398
  const { discoverOpencodeServer } = await import('../../service/actions.js');
399
399
 
400
400
  const mockPorts = async () => [3000];
@@ -406,13 +406,13 @@ describe('actions.js', () => {
406
406
  return { ok: false };
407
407
  };
408
408
 
409
- // Global servers should be skipped - pilot sessions should run isolated
409
+ // Global servers should be used as fallback when no project-specific match
410
410
  const result = await discoverOpencodeServer('/Users/test/random/path', {
411
411
  getPorts: mockPorts,
412
412
  fetch: mockFetch
413
413
  });
414
414
 
415
- assert.strictEqual(result, null);
415
+ assert.strictEqual(result, 'http://localhost:3000');
416
416
  });
417
417
 
418
418
  test('returns null when fetch fails for all servers', async () => {