opencode-pilot 0.13.0 → 0.14.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 +0 -14
- package/bin/opencode-pilot +4 -4
- package/package.json +1 -1
- package/plugin/index.js +1 -1
- package/service/poller.js +3 -2
- package/service/presets/github.yaml +19 -28
- package/service/repo-config.js +1 -1
- package/test/unit/poll-service.test.js +7 -7
- package/test/unit/repo-config.test.js +24 -19
package/README.md
CHANGED
|
@@ -106,20 +106,6 @@ When using `server_port` to attach sessions to a global OpenCode server (e.g., O
|
|
|
106
106
|
|
|
107
107
|
**Upstream issue**: [anomalyco/opencode#7376](https://github.com/anomalyco/opencode/issues/7376)
|
|
108
108
|
|
|
109
|
-
### Working directory doesn't switch when templates create worktrees/devcontainers
|
|
110
|
-
|
|
111
|
-
When a template instructs the agent to create a git worktree or switch to a devcontainer, OpenCode's internal working directory context (`Instance.directory`) doesn't update. This means:
|
|
112
|
-
|
|
113
|
-
- The "Session changes" panel shows diffs from the original directory
|
|
114
|
-
- File tools may resolve paths relative to the wrong location
|
|
115
|
-
- The agent works in the new directory, but OpenCode doesn't follow
|
|
116
|
-
|
|
117
|
-
**Workaround**: Start OpenCode directly in the target directory, or use separate terminal sessions.
|
|
118
|
-
|
|
119
|
-
**Upstream issue**: [anomalyco/opencode#6697](https://github.com/anomalyco/opencode/issues/6697)
|
|
120
|
-
|
|
121
|
-
**Related**: [opencode-devcontainers#103](https://github.com/athal7/opencode-devcontainers/issues/103)
|
|
122
|
-
|
|
123
109
|
## Related
|
|
124
110
|
|
|
125
111
|
- [opencode-devcontainers](https://github.com/athal7/opencode-devcontainers) - Run multiple devcontainer instances for OpenCode
|
package/bin/opencode-pilot
CHANGED
|
@@ -39,8 +39,8 @@ function findServiceDir() {
|
|
|
39
39
|
const serviceDir = findServiceDir();
|
|
40
40
|
|
|
41
41
|
// Paths
|
|
42
|
-
const PILOT_CONFIG_FILE = join(os.homedir(), ".config/opencode
|
|
43
|
-
const PILOT_TEMPLATES_DIR = join(os.homedir(), ".config/opencode
|
|
42
|
+
const PILOT_CONFIG_FILE = join(os.homedir(), ".config/opencode/pilot/config.yaml");
|
|
43
|
+
const PILOT_TEMPLATES_DIR = join(os.homedir(), ".config/opencode/pilot/templates");
|
|
44
44
|
|
|
45
45
|
// Default port
|
|
46
46
|
const DEFAULT_PORT = 4097;
|
|
@@ -231,7 +231,7 @@ async function configCommand() {
|
|
|
231
231
|
console.log(`Config file not found: ${PILOT_CONFIG_FILE}`);
|
|
232
232
|
console.log("");
|
|
233
233
|
console.log("Create one by copying the example:");
|
|
234
|
-
console.log(" cp node_modules/opencode-pilot/examples/config.yaml ~/.config/opencode
|
|
234
|
+
console.log(" cp node_modules/opencode-pilot/examples/config.yaml ~/.config/opencode/pilot/config.yaml");
|
|
235
235
|
process.exit(1);
|
|
236
236
|
}
|
|
237
237
|
|
|
@@ -666,7 +666,7 @@ async function clearCommand(flags) {
|
|
|
666
666
|
// No flags - show current state summary
|
|
667
667
|
console.log("Poll state summary:");
|
|
668
668
|
console.log(` Total entries: ${beforeCount}`);
|
|
669
|
-
console.log(` State file: ~/.config/opencode
|
|
669
|
+
console.log(` State file: ~/.config/opencode/pilot/poll-state.json`);
|
|
670
670
|
console.log("");
|
|
671
671
|
console.log("Usage:");
|
|
672
672
|
console.log(" opencode-pilot clear --all Clear all entries");
|
package/package.json
CHANGED
package/plugin/index.js
CHANGED
|
@@ -10,7 +10,7 @@ import { homedir } from 'os'
|
|
|
10
10
|
import YAML from 'yaml'
|
|
11
11
|
|
|
12
12
|
const DEFAULT_PORT = 4097
|
|
13
|
-
const CONFIG_PATH = join(homedir(), '.config', 'opencode
|
|
13
|
+
const CONFIG_PATH = join(homedir(), '.config', 'opencode', 'pilot', 'config.yaml')
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Load port from config file
|
package/service/poller.js
CHANGED
|
@@ -255,8 +255,9 @@ async function executeCliCommand(command, args, timeout) {
|
|
|
255
255
|
}
|
|
256
256
|
return part;
|
|
257
257
|
});
|
|
258
|
-
// Quote parts with spaces
|
|
259
|
-
|
|
258
|
+
// Quote parts with spaces or shell special characters
|
|
259
|
+
const shellSpecialChars = /[ <>|&;$`"'\\!*?#~=\[\]{}()]/;
|
|
260
|
+
cmdStr = expandedCmd.map(p => shellSpecialChars.test(p) ? `"${p.replace(/"/g, '\\"')}"` : p).join(' ');
|
|
260
261
|
} else {
|
|
261
262
|
// String command - substitute ${argName} patterns
|
|
262
263
|
cmdStr = command.replace(/\$\{(\w+)\}/g, (_, name) => {
|
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
# GitHub source presets
|
|
1
|
+
# GitHub source presets (using gh CLI)
|
|
2
|
+
#
|
|
3
|
+
# These presets use the `gh` CLI instead of requiring a GitHub MCP server.
|
|
4
|
+
# The gh CLI must be installed and authenticated (gh auth login).
|
|
2
5
|
|
|
3
6
|
# Provider-level config (applies to all GitHub presets)
|
|
4
7
|
_provider:
|
|
5
|
-
response_key: items
|
|
6
8
|
mappings:
|
|
7
|
-
#
|
|
8
|
-
|
|
9
|
+
# Map gh CLI fields to standard fields
|
|
10
|
+
html_url: url
|
|
11
|
+
repository_full_name: repository.nameWithOwner
|
|
12
|
+
updated_at: updatedAt
|
|
9
13
|
# Reprocess items when state changes (e.g., reopened issues)
|
|
10
14
|
# Note: updated_at is NOT included because our own changes would trigger reprocessing
|
|
11
15
|
reprocess_on:
|
|
@@ -15,48 +19,35 @@ _provider:
|
|
|
15
19
|
my-issues:
|
|
16
20
|
name: my-issues
|
|
17
21
|
tool:
|
|
18
|
-
|
|
19
|
-
name: search_issues
|
|
20
|
-
args:
|
|
21
|
-
q: "is:issue assignee:@me state:open"
|
|
22
|
+
command: ["gh", "search", "issues", "--assignee=@me", "--state=open", "--json", "number,title,url,repository,state,body,updatedAt"]
|
|
22
23
|
item:
|
|
23
|
-
id: "{
|
|
24
|
-
repo: "{
|
|
24
|
+
id: "{url}"
|
|
25
|
+
repo: "{repository.nameWithOwner}"
|
|
25
26
|
session:
|
|
26
27
|
name: "{title}"
|
|
27
28
|
|
|
28
29
|
review-requests:
|
|
29
30
|
name: review-requests
|
|
30
31
|
tool:
|
|
31
|
-
|
|
32
|
-
name: search_issues
|
|
33
|
-
args:
|
|
34
|
-
q: "is:pr review-requested:@me state:open"
|
|
32
|
+
command: ["gh", "search", "prs", "--review-requested=@me", "--state=open", "--json", "number,title,url,repository,state,body,updatedAt"]
|
|
35
33
|
item:
|
|
36
|
-
id: "{
|
|
37
|
-
repo: "{
|
|
34
|
+
id: "{url}"
|
|
35
|
+
repo: "{repository.nameWithOwner}"
|
|
38
36
|
session:
|
|
39
37
|
name: "Review: {title}"
|
|
40
38
|
|
|
41
39
|
my-prs-feedback:
|
|
42
40
|
name: my-prs-feedback
|
|
43
41
|
tool:
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
args:
|
|
47
|
-
# Catches PRs with any review activity (comments, reviews, or changes requested)
|
|
48
|
-
# Note: comments:>0 includes both review comments and issue comments
|
|
49
|
-
q: "is:pr author:@me state:open comments:>0"
|
|
42
|
+
# comments:>0 filter ensures only PRs with feedback are returned
|
|
43
|
+
command: ["gh", "search", "prs", "--author=@me", "--state=open", "comments:>0", "--json", "number,title,url,repository,state,body,updatedAt"]
|
|
50
44
|
item:
|
|
51
|
-
id: "{
|
|
52
|
-
repo: "{
|
|
45
|
+
id: "{url}"
|
|
46
|
+
repo: "{repository.nameWithOwner}"
|
|
53
47
|
session:
|
|
54
48
|
name: "Feedback: {title}"
|
|
55
49
|
# Reprocess when PR is updated (new commits pushed, new comments, etc.)
|
|
56
50
|
# This ensures we re-trigger after addressing review feedback
|
|
57
51
|
reprocess_on:
|
|
58
52
|
- state
|
|
59
|
-
-
|
|
60
|
-
# Filter out PRs where all comments are from bots or the PR author
|
|
61
|
-
# Fetches comments via API and enriches items with _comments for readiness check
|
|
62
|
-
filter_bot_comments: true
|
|
53
|
+
- updatedAt
|
package/service/repo-config.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* repo-config.js - Configuration management
|
|
3
3
|
*
|
|
4
|
-
* Manages configuration stored in ~/.config/opencode
|
|
4
|
+
* Manages configuration stored in ~/.config/opencode/pilot/config.yaml
|
|
5
5
|
* Supports:
|
|
6
6
|
* - defaults: default values applied to all sources
|
|
7
7
|
* - repos: per-repository settings (use YAML anchors for sharing)
|
|
@@ -159,15 +159,15 @@ sources:
|
|
|
159
159
|
loadRepoConfig(configPath);
|
|
160
160
|
|
|
161
161
|
const source = getSources()[0];
|
|
162
|
-
// Item with
|
|
162
|
+
// Item with repository.nameWithOwner (gh CLI output format)
|
|
163
163
|
const item = {
|
|
164
|
-
|
|
164
|
+
repository: { nameWithOwner: 'myorg/backend' },
|
|
165
165
|
number: 123,
|
|
166
|
-
|
|
166
|
+
url: 'https://github.com/myorg/backend/issues/123'
|
|
167
167
|
};
|
|
168
168
|
|
|
169
|
-
// Source should have repo field from preset (uses
|
|
170
|
-
assert.strictEqual(source.repo, '{
|
|
169
|
+
// Source should have repo field from preset (uses gh CLI field)
|
|
170
|
+
assert.strictEqual(source.repo, '{repository.nameWithOwner}');
|
|
171
171
|
|
|
172
172
|
// resolveRepoForItem should extract repo key from item
|
|
173
173
|
const repoKeys = resolveRepoForItem(source, item);
|
|
@@ -195,9 +195,9 @@ sources:
|
|
|
195
195
|
loadRepoConfig(configPath);
|
|
196
196
|
|
|
197
197
|
const source = getSources()[0];
|
|
198
|
-
// Item with
|
|
198
|
+
// Item with repository.nameWithOwner (gh CLI output format)
|
|
199
199
|
const item = {
|
|
200
|
-
|
|
200
|
+
repository: { nameWithOwner: 'unknown/repo' },
|
|
201
201
|
number: 456
|
|
202
202
|
};
|
|
203
203
|
|
|
@@ -340,7 +340,7 @@ sources: []
|
|
|
340
340
|
tools:
|
|
341
341
|
github:
|
|
342
342
|
mappings:
|
|
343
|
-
|
|
343
|
+
custom_field: some_source
|
|
344
344
|
|
|
345
345
|
sources: []
|
|
346
346
|
`);
|
|
@@ -350,10 +350,11 @@ sources: []
|
|
|
350
350
|
|
|
351
351
|
const toolConfig = getToolProviderConfig('github');
|
|
352
352
|
|
|
353
|
-
// GitHub preset
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
353
|
+
// GitHub preset now uses gh CLI and doesn't need response_key
|
|
354
|
+
// User config custom_field should be merged with preset mappings
|
|
355
|
+
assert.strictEqual(toolConfig.mappings.custom_field, 'some_source');
|
|
356
|
+
// GitHub provider has mappings for gh CLI field normalization
|
|
357
|
+
assert.ok(toolConfig.mappings.html_url, 'Should have html_url mapping');
|
|
357
358
|
assert.ok(toolConfig.mappings.repository_full_name, 'Should have repository_full_name mapping');
|
|
358
359
|
});
|
|
359
360
|
|
|
@@ -534,9 +535,10 @@ sources:
|
|
|
534
535
|
|
|
535
536
|
assert.strictEqual(sources.length, 1);
|
|
536
537
|
assert.strictEqual(sources[0].name, 'my-issues');
|
|
537
|
-
|
|
538
|
-
assert.
|
|
539
|
-
assert.
|
|
538
|
+
// GitHub presets now use gh CLI instead of MCP
|
|
539
|
+
assert.ok(Array.isArray(sources[0].tool.command), 'tool.command should be an array');
|
|
540
|
+
assert.ok(sources[0].tool.command.includes('gh'), 'command should use gh CLI');
|
|
541
|
+
assert.strictEqual(sources[0].item.id, '{url}');
|
|
540
542
|
assert.strictEqual(sources[0].prompt, 'worktree');
|
|
541
543
|
});
|
|
542
544
|
|
|
@@ -551,7 +553,8 @@ sources:
|
|
|
551
553
|
const sources = getSources();
|
|
552
554
|
|
|
553
555
|
assert.strictEqual(sources[0].name, 'review-requests');
|
|
554
|
-
|
|
556
|
+
// GitHub presets now use gh CLI instead of MCP
|
|
557
|
+
assert.ok(sources[0].tool.command.includes('--review-requested=@me'), 'command should include review-requested filter');
|
|
555
558
|
});
|
|
556
559
|
|
|
557
560
|
test('expands github/my-prs-feedback preset', async () => {
|
|
@@ -565,9 +568,11 @@ sources:
|
|
|
565
568
|
const sources = getSources();
|
|
566
569
|
|
|
567
570
|
assert.strictEqual(sources[0].name, 'my-prs-feedback');
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
assert.
|
|
571
|
+
// GitHub presets now use gh CLI instead of MCP
|
|
572
|
+
assert.ok(sources[0].tool.command.includes('--author=@me'), 'command should include author filter');
|
|
573
|
+
assert.ok(sources[0].tool.command.includes('comments:>0'), 'command should filter for PRs with comments');
|
|
574
|
+
// This preset includes updatedAt in reprocess_on to catch new commits
|
|
575
|
+
assert.deepStrictEqual(sources[0].reprocess_on, ['state', 'updatedAt']);
|
|
571
576
|
});
|
|
572
577
|
|
|
573
578
|
test('expands linear/my-issues preset with required args', async () => {
|
|
@@ -632,12 +637,12 @@ sources:
|
|
|
632
637
|
loadRepoConfig(configPath);
|
|
633
638
|
const sources = getSources();
|
|
634
639
|
|
|
635
|
-
// All GitHub presets should have repo field that references
|
|
636
|
-
// (
|
|
637
|
-
const mockItem = {
|
|
640
|
+
// All GitHub presets should have repo field that references repository.nameWithOwner
|
|
641
|
+
// (gh CLI returns this field directly)
|
|
642
|
+
const mockItem = { repository: { nameWithOwner: 'myorg/backend' } };
|
|
638
643
|
|
|
639
644
|
for (const source of sources) {
|
|
640
|
-
assert.strictEqual(source.repo, '{
|
|
645
|
+
assert.strictEqual(source.repo, '{repository.nameWithOwner}', `Preset ${source.name} should have repo field`);
|
|
641
646
|
const repos = resolveRepoForItem(source, mockItem);
|
|
642
647
|
assert.deepStrictEqual(repos, ['myorg/backend'], `Preset ${source.name} should resolve repo from item`);
|
|
643
648
|
}
|
|
@@ -656,12 +661,12 @@ sources:
|
|
|
656
661
|
loadRepoConfig(configPath);
|
|
657
662
|
const source = getSources()[0];
|
|
658
663
|
|
|
659
|
-
// Item from allowed repo should resolve (
|
|
660
|
-
const allowedItem = {
|
|
664
|
+
// Item from allowed repo should resolve (gh CLI returns repository.nameWithOwner)
|
|
665
|
+
const allowedItem = { repository: { nameWithOwner: 'myorg/backend' } };
|
|
661
666
|
assert.deepStrictEqual(resolveRepoForItem(source, allowedItem), ['myorg/backend']);
|
|
662
667
|
|
|
663
668
|
// Item from non-allowed repo should return empty (filtered out)
|
|
664
|
-
const filteredItem = {
|
|
669
|
+
const filteredItem = { repository: { nameWithOwner: 'other/repo' } };
|
|
665
670
|
assert.deepStrictEqual(resolveRepoForItem(source, filteredItem), []);
|
|
666
671
|
});
|
|
667
672
|
|