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 +1 -1
- package/service/actions.js +31 -139
- package/test/unit/actions.test.js +3 -9
package/package.json
CHANGED
package/service/actions.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Supports prompt_template for custom prompts (e.g., to invoke /devcontainer).
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {
|
|
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
|
|
613
|
-
|
|
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
|
-
//
|
|
647
|
-
|
|
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
|
-
//
|
|
686
|
-
|
|
687
|
-
|
|
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
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
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
|
-
|
|
757
|
-
|
|
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('
|
|
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.
|
|
570
|
-
assert.
|
|
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');
|