opencode-pilot 0.16.5 → 0.16.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-pilot",
3
- "version": "0.16.5",
3
+ "version": "0.16.6",
4
4
  "type": "module",
5
5
  "main": "plugin/index.js",
6
6
  "description": "Automation daemon for OpenCode - polls for work and spawns sessions",
@@ -5,7 +5,7 @@
5
5
  * Supports prompt_template for custom prompts (e.g., to invoke /devcontainer).
6
6
  */
7
7
 
8
- import { spawn, execSync } from "child_process";
8
+ import { 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";
@@ -417,39 +417,6 @@ export function buildCommand(item, config) {
417
417
  return cmdInfo.cwd ? `(cd ${cmdInfo.cwd} && ${cmdStr})` : cmdStr;
418
418
  }
419
419
 
420
- /**
421
- * Execute a spawn command and return a promise
422
- */
423
- function runSpawn(args, options = {}) {
424
- return new Promise((resolve, reject) => {
425
- const [cmd, ...cmdArgs] = args;
426
- const spawnOpts = {
427
- stdio: ["ignore", "pipe", "pipe"],
428
- ...options,
429
- };
430
- const child = spawn(cmd, cmdArgs, spawnOpts);
431
-
432
- let stdout = "";
433
- let stderr = "";
434
-
435
- child.stdout.on("data", (data) => {
436
- stdout += data.toString();
437
- });
438
-
439
- child.stderr.on("data", (data) => {
440
- stderr += data.toString();
441
- });
442
-
443
- child.on("close", (code) => {
444
- resolve({ stdout, stderr, exitCode: code, success: code === 0 });
445
- });
446
-
447
- child.on("error", (err) => {
448
- reject(err);
449
- });
450
- });
451
- }
452
-
453
420
  /**
454
421
  * Create a session via the OpenCode HTTP API
455
422
  *
@@ -605,13 +572,20 @@ export async function executeAction(item, config, options = {}) {
605
572
 
606
573
  debug(`executeAction: discovered server=${serverUrl} for baseCwd=${baseCwd}`);
607
574
 
575
+ // Require OpenCode server - pilot runs as a plugin, so server should always be available
576
+ if (!serverUrl) {
577
+ return {
578
+ success: false,
579
+ error: 'No OpenCode server found. Pilot requires OpenCode to be running.',
580
+ };
581
+ }
582
+
608
583
  // Resolve worktree directory if configured
609
584
  // This allows creating sessions in isolated worktrees instead of the main project
610
585
  let worktreeMode = config.worktree;
611
586
 
612
- // Auto-detect worktree support: if not explicitly configured and server is running,
613
- // check if the project has sandboxes (indicating worktree workflow is set up)
614
- if (!worktreeMode && serverUrl) {
587
+ // Auto-detect worktree support: check if the project has sandboxes
588
+ if (!worktreeMode) {
615
589
  // Look up project info for this specific directory (not just /project/current)
616
590
  const projectInfo = await getProjectInfoForDirectory(serverUrl, baseCwd, { fetch: options.fetch });
617
591
  if (projectInfo?.sandboxes?.length > 0) {
@@ -643,117 +617,35 @@ export async function executeAction(item, config, options = {}) {
643
617
 
644
618
  debug(`executeAction: using cwd=${cwd}`);
645
619
 
646
- // If a server is running, use the HTTP API to create the session
647
- // This is a workaround for the known issue where --attach doesn't support --dir
648
- // See: https://github.com/anomalyco/opencode/issues/7376
649
- if (serverUrl) {
650
- // Build prompt from template
651
- const prompt = buildPromptFromTemplate(config.prompt || "default", item);
652
-
653
- // Build session title
654
- const sessionTitle = config.session?.name
655
- ? buildSessionName(config.session.name, item)
656
- : (item.title || `session-${Date.now()}`);
657
-
658
- const apiCommand = `[API] POST ${serverUrl}/session?directory=${cwd}`;
659
- debug(`executeAction: using HTTP API - ${apiCommand}`);
660
-
661
- if (options.dryRun) {
662
- return {
663
- command: apiCommand,
664
- dryRun: true,
665
- method: 'api',
666
- };
667
- }
668
-
669
- const result = await createSessionViaApi(serverUrl, cwd, prompt, {
670
- title: sessionTitle,
671
- agent: config.agent,
672
- model: config.model,
673
- fetch: options.fetch,
674
- });
675
-
676
- return {
677
- command: apiCommand,
678
- success: result.success,
679
- sessionId: result.sessionId,
680
- error: result.error,
681
- method: 'api',
682
- };
683
- }
620
+ // Build prompt from template
621
+ const prompt = buildPromptFromTemplate(config.prompt || "default", item);
684
622
 
685
- // No server running - fall back to spawning opencode run
686
- // This works correctly because we set cwd on the spawn
687
- const cmdInfo = getCommandInfoNew(item, config, undefined, null);
623
+ // Build session title
624
+ const sessionTitle = config.session?.name
625
+ ? buildSessionName(config.session.name, item)
626
+ : (item.title || `session-${Date.now()}`);
627
+
628
+ const apiCommand = `[API] POST ${serverUrl}/session?directory=${cwd}`;
629
+ debug(`executeAction: using HTTP API - ${apiCommand}`);
688
630
 
689
- // Build command string for display
690
- const quoteArgs = (args) => args.map(a =>
691
- a.includes(" ") || a.includes("\n") ? `"${a.replace(/"/g, '\\"')}"` : a
692
- ).join(" ");
693
- const cmdStr = quoteArgs(cmdInfo.args);
694
- const command = cmdInfo.cwd ? `(cd ${cmdInfo.cwd} && ${cmdStr})` : cmdStr;
695
-
696
- debug(`executeAction: command=${command}`);
697
- debug(`executeAction: args=${JSON.stringify(cmdInfo.args)}, cwd=${cmdInfo.cwd}`);
698
-
699
631
  if (options.dryRun) {
700
632
  return {
701
- command,
633
+ command: apiCommand,
702
634
  dryRun: true,
703
- method: 'spawn',
704
635
  };
705
636
  }
706
-
707
- // Execute opencode run in background (detached)
708
- // We don't wait for completion since sessions can run for a long time
709
- debug(`executeAction: spawning opencode run (detached)`);
710
- const [cmd, ...cmdArgs] = cmdInfo.args;
711
- const child = spawn(cmd, cmdArgs, {
712
- cwd: cmdInfo.cwd,
713
- detached: true,
714
- stdio: 'ignore',
715
- });
716
- child.unref();
717
637
 
718
- debug(`executeAction: spawned pid=${child.pid}`);
719
- return {
720
- command,
721
- success: true,
722
- pid: child.pid,
723
- method: 'spawn',
724
- };
725
- }
726
-
727
- /**
728
- * Check if opencode is available
729
- * @returns {Promise<boolean>}
730
- */
731
- export async function checkOpencode() {
732
- return new Promise((resolve) => {
733
- const child = spawn("which", ["opencode"]);
734
- child.on("close", (code) => {
735
- resolve(code === 0);
736
- });
737
- child.on("error", () => {
738
- resolve(false);
739
- });
638
+ const result = await createSessionViaApi(serverUrl, cwd, prompt, {
639
+ title: sessionTitle,
640
+ agent: config.agent,
641
+ model: config.model,
642
+ fetch: options.fetch,
740
643
  });
741
- }
742
-
743
- /**
744
- * Validate that required tools are available
745
- * @returns {Promise<object>} { valid: boolean, missing?: string[] }
746
- */
747
- export async function validateTools() {
748
- const missing = [];
749
-
750
- const hasOpencode = await checkOpencode();
751
- if (!hasOpencode) {
752
- missing.push("opencode");
753
- }
754
-
644
+
755
645
  return {
756
- valid: missing.length === 0,
757
- missing,
646
+ command: apiCommand,
647
+ success: result.success,
648
+ sessionId: result.sessionId,
649
+ error: result.error,
758
650
  };
759
651
  }
@@ -543,13 +543,12 @@ describe('actions.js', () => {
543
543
  });
544
544
 
545
545
  assert.ok(result.dryRun);
546
- assert.strictEqual(result.method, 'api', 'Should use API method when server found');
547
546
  assert.ok(result.command.includes('POST'), 'Command should show POST request');
548
547
  assert.ok(result.command.includes('http://localhost:4096'), 'Command should include server URL');
549
548
  assert.ok(result.command.includes('directory='), 'Command should include directory param');
550
549
  });
551
550
 
552
- test('falls back to spawn when no server discovered (dry run)', async () => {
551
+ test('returns error when no server discovered', async () => {
553
552
  const { executeAction } = await import('../../service/actions.js');
554
553
 
555
554
  const item = { number: 123, title: 'Fix bug' };
@@ -566,10 +565,8 @@ describe('actions.js', () => {
566
565
  discoverServer: mockDiscoverServer
567
566
  });
568
567
 
569
- assert.ok(result.dryRun);
570
- assert.strictEqual(result.method, 'spawn', 'Should use spawn method when no server');
571
- assert.ok(!result.command.includes('--attach'), 'Command should not include --attach flag');
572
- assert.ok(result.command.includes('opencode run'), 'Command should include opencode run');
568
+ assert.strictEqual(result.success, false, 'Should fail when no server');
569
+ assert.ok(result.error.includes('No OpenCode server'), 'Should have descriptive error');
573
570
  });
574
571
 
575
572
  test('creates new worktree when worktree: "new" is configured (dry run)', async () => {
@@ -615,7 +612,6 @@ describe('actions.js', () => {
615
612
  });
616
613
 
617
614
  assert.ok(result.dryRun);
618
- assert.strictEqual(result.method, 'api', 'Should use API method');
619
615
  // The directory in the command should be the worktree directory
620
616
  assert.ok(result.command.includes('/data/worktree/proj123/feature-branch'),
621
617
  'Should use worktree directory in command');
@@ -655,7 +651,6 @@ describe('actions.js', () => {
655
651
  });
656
652
 
657
653
  assert.ok(result.dryRun);
658
- assert.strictEqual(result.method, 'api', 'Should use API method');
659
654
  assert.ok(result.command.includes('/data/worktree/proj123/my-feature'),
660
655
  'Should use looked up worktree path in command');
661
656
  });
@@ -692,7 +687,6 @@ describe('actions.js', () => {
692
687
  });
693
688
 
694
689
  assert.ok(result.dryRun);
695
- assert.strictEqual(result.method, 'api', 'Should still use API method');
696
690
  // Should fall back to base directory
697
691
  assert.ok(result.command.includes(tempDir),
698
692
  'Should fall back to base directory when worktree creation fails');