@kodrunhq/opencode-autopilot 1.15.0 → 1.15.2
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 +14 -0
- package/package.json +1 -1
- package/src/agents/index.ts +60 -15
- package/src/agents/researcher.ts +1 -1
- package/src/health/checks.ts +79 -0
- package/src/health/runner.ts +3 -0
- package/src/index.ts +2 -1
- package/src/tools/doctor.ts +14 -3
package/README.md
CHANGED
|
@@ -62,6 +62,20 @@ npm install -g @kodrunhq/opencode-autopilot
|
|
|
62
62
|
|
|
63
63
|
Launch OpenCode. The plugin auto-installs agents, skills, and commands on first load and shows a welcome toast.
|
|
64
64
|
|
|
65
|
+
### Agent visibility defaults
|
|
66
|
+
|
|
67
|
+
Primary Tab-cycle agents provided by this plugin are:
|
|
68
|
+
|
|
69
|
+
- `autopilot`
|
|
70
|
+
- `coder`
|
|
71
|
+
- `debugger`
|
|
72
|
+
- `planner`
|
|
73
|
+
- `researcher`
|
|
74
|
+
- `reviewer`
|
|
75
|
+
|
|
76
|
+
OpenCode native `plan` and `build` are suppressed by the plugin config hook to avoid
|
|
77
|
+
duplicate planning/building entries in the primary Tab menu.
|
|
78
|
+
|
|
65
79
|
### Verify your setup
|
|
66
80
|
|
|
67
81
|
```bash
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kodrunhq/opencode-autopilot",
|
|
3
|
-
"version": "1.15.
|
|
3
|
+
"version": "1.15.2",
|
|
4
4
|
"description": "Curated agents, skills, and commands for the OpenCode AI coding CLI — autonomous orchestrator, multi-agent code review, model fallback, and in-session asset creation tools.",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"keywords": [
|
package/src/agents/index.ts
CHANGED
|
@@ -71,6 +71,60 @@ function registerAgents(
|
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
const nativeSuppressionPatch = Object.freeze({
|
|
75
|
+
disable: true,
|
|
76
|
+
mode: "subagent" as const,
|
|
77
|
+
hidden: true,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const optionalNativePlanBuildKeys = Object.freeze(["Plan", "Build", "Planner", "Builder"] as const);
|
|
81
|
+
|
|
82
|
+
function isObjectRecord(value: unknown): value is Record<string, unknown> {
|
|
83
|
+
return typeof value === "object" && value !== null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function mergeSuppressionPatch(entry: unknown): Record<string, unknown> {
|
|
87
|
+
if (isObjectRecord(entry)) {
|
|
88
|
+
return {
|
|
89
|
+
...entry,
|
|
90
|
+
...nativeSuppressionPatch,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return { ...nativeSuppressionPatch };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function suppressNativePlanBuildAgents(config: Config): void {
|
|
98
|
+
if (!config.agent) return;
|
|
99
|
+
|
|
100
|
+
const agentRef = config.agent as Record<string, unknown>;
|
|
101
|
+
|
|
102
|
+
// Deterministically suppress native lowercase keys even if OpenCode did not
|
|
103
|
+
// pre-populate them before configHook execution.
|
|
104
|
+
for (const key of ["plan", "build"] as const) {
|
|
105
|
+
agentRef[key] = mergeSuppressionPatch(agentRef[key]);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Also suppress optional native variants when present.
|
|
109
|
+
for (const key of optionalNativePlanBuildKeys) {
|
|
110
|
+
if (agentRef[key] !== undefined) {
|
|
111
|
+
agentRef[key] = mergeSuppressionPatch(agentRef[key]);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function suppressLegacyModePlanBuild(config: Config): void {
|
|
117
|
+
if (!config.mode) return;
|
|
118
|
+
|
|
119
|
+
const modeRef = config.mode as Record<string, unknown>;
|
|
120
|
+
for (const key of ["plan", "build", ...optionalNativePlanBuildKeys] as const) {
|
|
121
|
+
const existing = modeRef[key];
|
|
122
|
+
if (existing === undefined) continue;
|
|
123
|
+
if (!isObjectRecord(existing)) continue;
|
|
124
|
+
modeRef[key] = mergeSuppressionPatch(existing);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
74
128
|
export async function configHook(config: Config, configPath?: string): Promise<void> {
|
|
75
129
|
if (!config.agent) {
|
|
76
130
|
config.agent = {};
|
|
@@ -89,25 +143,16 @@ export async function configHook(config: Config, configPath?: string): Promise<v
|
|
|
89
143
|
const groups: Readonly<Record<string, GroupModelAssignment>> = pluginConfig?.groups ?? {};
|
|
90
144
|
const overrides: Readonly<Record<string, AgentOverride>> = pluginConfig?.overrides ?? {};
|
|
91
145
|
|
|
92
|
-
// Snapshot built-in agent keys BEFORE we register ours — we only suppress
|
|
93
|
-
// built-in Plan variants, not our own custom "planner" agent.
|
|
94
|
-
const builtInKeys = new Set(Object.keys(config.agent));
|
|
95
|
-
|
|
96
146
|
// Register standard agents and pipeline agents (v2 orchestrator subagents)
|
|
97
147
|
registerAgents(agents, config, groups, overrides);
|
|
98
148
|
registerAgents(pipelineAgents, config, groups, overrides);
|
|
99
149
|
|
|
100
|
-
// Suppress built-in Plan
|
|
101
|
-
//
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
...config.agent[variant],
|
|
107
|
-
disable: true,
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
}
|
|
150
|
+
// Suppress native built-in Plan/Build agents. This is deterministic and does
|
|
151
|
+
// not rely on whether OpenCode pre-populated keys before configHook runs.
|
|
152
|
+
suppressNativePlanBuildAgents(config);
|
|
153
|
+
|
|
154
|
+
// Backward compatibility for legacy mode.plan/mode.build config shape.
|
|
155
|
+
suppressLegacyModePlanBuild(config);
|
|
111
156
|
}
|
|
112
157
|
|
|
113
158
|
export { autopilotAgent } from "./autopilot";
|
package/src/agents/researcher.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { AgentConfig } from "@opencode-ai/sdk";
|
|
|
2
2
|
|
|
3
3
|
export const researcherAgent: Readonly<AgentConfig> = Object.freeze({
|
|
4
4
|
description: "Searches the web about a topic and produces a comprehensive report with sources",
|
|
5
|
-
mode: "
|
|
5
|
+
mode: "all",
|
|
6
6
|
prompt: `You are a research specialist. Your job is to thoroughly investigate a given topic and produce a clear, well-organized report.
|
|
7
7
|
|
|
8
8
|
## Instructions
|
package/src/health/checks.ts
CHANGED
|
@@ -90,6 +90,85 @@ export async function agentHealthCheck(config: Config | null): Promise<HealthRes
|
|
|
90
90
|
});
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Check that OpenCode native plan/build agents are suppressed by the plugin.
|
|
95
|
+
* Contract: both entries must have disable=true, mode=subagent, hidden=true.
|
|
96
|
+
*/
|
|
97
|
+
export async function nativeAgentSuppressionHealthCheck(
|
|
98
|
+
config: Config | null,
|
|
99
|
+
): Promise<HealthResult> {
|
|
100
|
+
if (!config?.agent) {
|
|
101
|
+
return Object.freeze({
|
|
102
|
+
name: "native-agent-suppression",
|
|
103
|
+
status: "fail" as const,
|
|
104
|
+
message: "No OpenCode config or agent map available",
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const agentMap = config.agent as Record<string, unknown>;
|
|
109
|
+
const issues: string[] = [];
|
|
110
|
+
const requiredKeys = ["plan", "build"] as const;
|
|
111
|
+
const optionalKeys = ["Plan", "Build", "Planner", "Builder"] as const;
|
|
112
|
+
|
|
113
|
+
for (const key of requiredKeys) {
|
|
114
|
+
const raw = agentMap[key];
|
|
115
|
+
if (raw === undefined) {
|
|
116
|
+
issues.push(`${key}: missing config entry`);
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (typeof raw !== "object" || raw === null) {
|
|
120
|
+
issues.push(`${key}: invalid config entry type`);
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const entry = raw as Record<string, unknown>;
|
|
125
|
+
if (entry.disable !== true) {
|
|
126
|
+
issues.push(`${key}: disable must be true`);
|
|
127
|
+
}
|
|
128
|
+
if (entry.mode !== "subagent") {
|
|
129
|
+
issues.push(`${key}: mode must be subagent`);
|
|
130
|
+
}
|
|
131
|
+
if (entry.hidden !== true) {
|
|
132
|
+
issues.push(`${key}: hidden must be true`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
for (const key of optionalKeys) {
|
|
137
|
+
const raw = agentMap[key];
|
|
138
|
+
if (raw === undefined) continue;
|
|
139
|
+
if (typeof raw !== "object" || raw === null) {
|
|
140
|
+
issues.push(`${key}: invalid config entry type`);
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const entry = raw as Record<string, unknown>;
|
|
145
|
+
if (entry.disable !== true) {
|
|
146
|
+
issues.push(`${key}: disable must be true`);
|
|
147
|
+
}
|
|
148
|
+
if (entry.mode !== "subagent") {
|
|
149
|
+
issues.push(`${key}: mode must be subagent`);
|
|
150
|
+
}
|
|
151
|
+
if (entry.hidden !== true) {
|
|
152
|
+
issues.push(`${key}: hidden must be true`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (issues.length > 0) {
|
|
157
|
+
return Object.freeze({
|
|
158
|
+
name: "native-agent-suppression",
|
|
159
|
+
status: "fail" as const,
|
|
160
|
+
message: `${issues.length} native suppression issue(s) found`,
|
|
161
|
+
details: Object.freeze([...issues]),
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return Object.freeze({
|
|
166
|
+
name: "native-agent-suppression",
|
|
167
|
+
status: "pass" as const,
|
|
168
|
+
message: "Native plan/build agents are suppressed",
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
93
172
|
/**
|
|
94
173
|
* Check that the source and target asset directories exist and are accessible.
|
|
95
174
|
*/
|
package/src/health/runner.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
commandHealthCheck,
|
|
6
6
|
configHealthCheck,
|
|
7
7
|
memoryHealthCheck,
|
|
8
|
+
nativeAgentSuppressionHealthCheck,
|
|
8
9
|
skillHealthCheck,
|
|
9
10
|
} from "./checks";
|
|
10
11
|
import type { HealthReport, HealthResult } from "./types";
|
|
@@ -45,6 +46,7 @@ export async function runHealthChecks(options?: {
|
|
|
45
46
|
const settled = await Promise.allSettled([
|
|
46
47
|
configHealthCheck(options?.configPath),
|
|
47
48
|
agentHealthCheck(options?.openCodeConfig ?? null),
|
|
49
|
+
nativeAgentSuppressionHealthCheck(options?.openCodeConfig ?? null),
|
|
48
50
|
assetHealthCheck(options?.assetsDir, options?.targetDir),
|
|
49
51
|
skillHealthCheck(options?.projectRoot ?? process.cwd()),
|
|
50
52
|
memoryHealthCheck(options?.targetDir),
|
|
@@ -54,6 +56,7 @@ export async function runHealthChecks(options?: {
|
|
|
54
56
|
const fallbackNames = [
|
|
55
57
|
"config-validity",
|
|
56
58
|
"agent-injection",
|
|
59
|
+
"native-agent-suppression",
|
|
57
60
|
"asset-directories",
|
|
58
61
|
"skill-loading",
|
|
59
62
|
"memory-db",
|
package/src/index.ts
CHANGED
|
@@ -34,7 +34,7 @@ import {
|
|
|
34
34
|
import { ocCreateAgent } from "./tools/create-agent";
|
|
35
35
|
import { ocCreateCommand } from "./tools/create-command";
|
|
36
36
|
import { ocCreateSkill } from "./tools/create-skill";
|
|
37
|
-
import { ocDoctor } from "./tools/doctor";
|
|
37
|
+
import { ocDoctor, setOpenCodeConfig as setDoctorOpenCodeConfig } from "./tools/doctor";
|
|
38
38
|
import { ocForensics } from "./tools/forensics";
|
|
39
39
|
import { ocHashlineEdit } from "./tools/hashline-edit";
|
|
40
40
|
import { ocLogs } from "./tools/logs";
|
|
@@ -255,6 +255,7 @@ const plugin: Plugin = async (input) => {
|
|
|
255
255
|
config: async (cfg: Config) => {
|
|
256
256
|
openCodeConfig = cfg;
|
|
257
257
|
setOpenCodeConfig(cfg);
|
|
258
|
+
setDoctorOpenCodeConfig(cfg);
|
|
258
259
|
await configHook(cfg);
|
|
259
260
|
},
|
|
260
261
|
"chat.message": async (
|
package/src/tools/doctor.ts
CHANGED
|
@@ -6,6 +6,12 @@ import { runHealthChecks } from "../health/runner";
|
|
|
6
6
|
import type { HealthResult } from "../health/types";
|
|
7
7
|
import { getProjectArtifactDir } from "../utils/paths";
|
|
8
8
|
|
|
9
|
+
let openCodeConfig: Config | null = null;
|
|
10
|
+
|
|
11
|
+
export function setOpenCodeConfig(config: Config | null): void {
|
|
12
|
+
openCodeConfig = config;
|
|
13
|
+
}
|
|
14
|
+
|
|
9
15
|
/**
|
|
10
16
|
* A single check in the doctor report, with an optional fix suggestion.
|
|
11
17
|
*/
|
|
@@ -63,6 +69,8 @@ const FIX_SUGGESTIONS: Readonly<Record<string, string>> = Object.freeze({
|
|
|
63
69
|
"config-validity":
|
|
64
70
|
"Run `bunx @kodrunhq/opencode-autopilot configure` to reconfigure, or delete ~/.config/opencode/opencode-autopilot.json to reset",
|
|
65
71
|
"agent-injection": "Restart OpenCode to trigger agent re-injection via config hook",
|
|
72
|
+
"native-agent-suppression":
|
|
73
|
+
"Restart OpenCode and verify plugin config hook runs. If issue persists, check for conflicting OpenCode config or another plugin overriding agent entries",
|
|
66
74
|
"asset-directories": "Restart OpenCode to trigger asset reinstallation",
|
|
67
75
|
"skill-loading": "Ensure skills directory exists in ~/.config/opencode/skills/",
|
|
68
76
|
"memory-db":
|
|
@@ -143,9 +151,12 @@ export async function doctorCore(options?: DoctorOptions): Promise<string> {
|
|
|
143
151
|
export const ocDoctor = tool({
|
|
144
152
|
description:
|
|
145
153
|
"Run plugin health diagnostics. Reports pass/fail status for config, agents, " +
|
|
146
|
-
"assets, and hooks. Like `brew doctor` for opencode-autopilot.",
|
|
154
|
+
"native suppression, assets, and hooks. Like `brew doctor` for opencode-autopilot.",
|
|
147
155
|
args: {},
|
|
148
|
-
async execute() {
|
|
149
|
-
return doctorCore(
|
|
156
|
+
async execute(_args, context) {
|
|
157
|
+
return doctorCore({
|
|
158
|
+
openCodeConfig,
|
|
159
|
+
projectRoot: context.directory,
|
|
160
|
+
});
|
|
150
161
|
},
|
|
151
162
|
});
|