Skip to content

Commit 85b96cd

Browse files
authored
Adding tests; Cleanups (#63)
1 parent 1d13a9c commit 85b96cd

50 files changed

Lines changed: 2665 additions & 791 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,25 +8,3 @@
88

99
<!-- What was changed and how. List specific modifications. -->
1010

11-
-
12-
13-
## Screenshots
14-
15-
<!-- If applicable, add screenshots or GIFs showing before/after. Delete this section if not needed. -->
16-
17-
| Before | After |
18-
|:---:|:---:|
19-
| | |
20-
21-
## Testing
22-
23-
<!-- How to verify this PR works correctly. Delete if not applicable. -->
24-
25-
```bash
26-
bun run typecheck
27-
bun test
28-
```
29-
30-
## Related Issues
31-
32-
<!-- Link related issues. Delete if not applicable. -->

.github/workflows/ci.yml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main, master]
6+
pull_request:
7+
branches: [main, master]
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
13+
strategy:
14+
matrix:
15+
bun-version: [latest]
16+
17+
steps:
18+
- name: Checkout code
19+
uses: actions/checkout@v4
20+
21+
- name: Setup Bun
22+
uses: oven-sh/setup-bun@v2
23+
with:
24+
bun-version: ${{ matrix.bun-version }}
25+
26+
- name: Install dependencies
27+
run: bun install --frozen-lockfile
28+
29+
- name: Run typecheck
30+
run: bun run typecheck
31+
32+
- name: Run tests
33+
run: bun test
34+
35+
- name: Build
36+
run: bun run build

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -525,7 +525,6 @@ The installer generates this file based on your providers. You can manually cust
525525
| `tmux.enabled` | boolean | `false` | Enable tmux pane spawning for sub-agents |
526526
| `tmux.layout` | string | `"main-vertical"` | Layout preset: `main-vertical`, `main-horizontal`, `tiled`, `even-horizontal`, `even-vertical` |
527527
| `tmux.main_pane_size` | number | `60` | Main pane size as percentage (20-80) |
528-
| `disabled_agents` | string[] | `[]` | Agent IDs to disable (e.g., `"explorer"`) |
529528
| `disabled_mcps` | string[] | `[]` | MCP server IDs to disable (e.g., `"websearch"`) |
530529
| `agents.<name>.model` | string | - | Override the LLM for a specific agent |
531530
| `agents.<name>.variant` | string | - | Reasoning effort: `"low"`, `"medium"`, `"high"` |

README.zh-CN.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -542,7 +542,6 @@ bunx oh-my-opencode-slim install --help
542542
| `tmux.enabled` | boolean | `false` | 是否启用子代理的 tmux 窗格 |
543543
| `tmux.layout` | string | `"main-vertical"` | 布局预设:`main-vertical``main-horizontal``tiled``even-horizontal``even-vertical` |
544544
| `tmux.main_pane_size` | number | `60` | 主窗格大小百分比(20-80) |
545-
| `disabled_agents` | string[] | `[]` | 要禁用的代理 ID(如 `"explorer"`|
546545
| `disabled_mcps` | string[] | `[]` | 要禁用的 MCP 服务器 ID(如 `"websearch"`|
547546
| `agents.<name>.model` | string | - | 覆盖特定代理的模型 |
548547
| `agents.<name>.variant` | string | - | 推理强度:`"low"``"medium"``"high"` |

bun.lock

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@
4545
"@modelcontextprotocol/sdk": "^1.25.1",
4646
"@opencode-ai/plugin": "^1.1.19",
4747
"@opencode-ai/sdk": "^1.1.19",
48+
"vscode-jsonrpc": "^8.2.0",
49+
"vscode-languageserver-protocol": "^3.17.5",
4850
"zod": "^4.1.8"
4951
},
5052
"devDependencies": {

src/agents/designer.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,5 @@
11
import type { AgentDefinition } from "./orchestrator";
22

3-
export function createDesignerAgent(model: string): AgentDefinition {
4-
return {
5-
name: "designer",
6-
description: "UI/UX design and implementation. Use for styling, responsive design, component architecture and visual polish.",
7-
config: {
8-
model,
9-
temperature: 0.7,
10-
prompt: DESIGNER_PROMPT,
11-
},
12-
};
13-
}
14-
153
const DESIGNER_PROMPT = `You are a Designer - a frontend UI/UX engineer.
164
175
**Role**: Craft stunning UI/UX even without design mockups.
@@ -24,3 +12,15 @@ const DESIGNER_PROMPT = `You are a Designer - a frontend UI/UX engineer.
2412
- Match existing design system if present
2513
- Use existing component libraries when available
2614
- Prioritize visual excellence over code perfection`;
15+
16+
export function createDesignerAgent(model: string): AgentDefinition {
17+
return {
18+
name: "designer",
19+
description: "UI/UX design and implementation. Use for styling, responsive design, component architecture and visual polish.",
20+
config: {
21+
model,
22+
temperature: 0.7,
23+
prompt: DESIGNER_PROMPT,
24+
},
25+
};
26+
}

src/agents/explorer.ts

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,5 @@
11
import type { AgentDefinition } from "./orchestrator";
22

3-
export function createExplorerAgent(model: string): AgentDefinition {
4-
return {
5-
name: "explorer",
6-
description: "Fast codebase search and pattern matching. Use for finding files, locating code patterns, and answering 'where is X?' questions.",
7-
config: {
8-
model,
9-
temperature: 0.1,
10-
prompt: EXPLORER_PROMPT,
11-
},
12-
};
13-
}
14-
153
const EXPLORER_PROMPT = `You are Explorer - a fast codebase navigation specialist.
164
175
**Role**: Quick contextual grep for codebases. Answer "Where is X?", "Find Y", "Which file has Z".
@@ -50,3 +38,16 @@ Concise answer to the question
5038
- READ-ONLY: Search and report, don't modify
5139
- Be exhaustive but concise
5240
- Include line numbers when relevant`;
41+
42+
export function createExplorerAgent(model: string): AgentDefinition {
43+
return {
44+
name: "explorer",
45+
description: "Fast codebase search and pattern matching. Use for finding files, locating code patterns, and answering 'where is X?' questions.",
46+
config: {
47+
model,
48+
temperature: 0.1,
49+
prompt: EXPLORER_PROMPT,
50+
},
51+
};
52+
}
53+

src/agents/fixer.ts

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,5 @@
11
import type { AgentDefinition } from "./orchestrator";
22

3-
export function createFixerAgent(model: string): AgentDefinition {
4-
return {
5-
name: "fixer",
6-
description: "Fast implementation specialist. Receives complete context and task spec, executes code changes efficiently.",
7-
config: {
8-
model,
9-
temperature: 0.2,
10-
prompt: FIXER_PROMPT,
11-
},
12-
};
13-
}
14-
153
const FIXER_PROMPT = `You are Fixer - a fast, focused implementation specialist.
164
175
**Role**: Execute code changes efficiently. You receive complete context from research agents and clear task specifications from the Orchestrator. Your job is to implement, not plan or research.
@@ -50,4 +38,16 @@ No changes required
5038
<verification>
5139
- Tests passed: [not run - reason]
5240
- LSP diagnostics: [not run - reason]
53-
</verification>`;
41+
</verification>`;
42+
43+
export function createFixerAgent(model: string): AgentDefinition {
44+
return {
45+
name: "fixer",
46+
description: "Fast implementation specialist. Receives complete context and task spec, executes code changes efficiently.",
47+
config: {
48+
model,
49+
temperature: 0.2,
50+
prompt: FIXER_PROMPT,
51+
},
52+
};
53+
}

src/agents/index.test.ts

Lines changed: 88 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { describe, expect, test } from "bun:test";
2-
import { createAgents, getAgentConfigs, getSubagentNames, getPrimaryAgentNames } from "./index";
2+
import { createAgents, getAgentConfigs, isSubagent } from "./index";
3+
import { SUBAGENT_NAMES } from "../config";
34
import type { PluginConfig } from "../config";
45

56
describe("agent alias backward compatibility", () => {
@@ -63,31 +64,94 @@ describe("agent alias backward compatibility", () => {
6364
});
6465
});
6566

66-
describe("agent classification", () => {
67-
test("getPrimaryAgentNames returns only orchestrator", () => {
68-
const names = getPrimaryAgentNames();
69-
expect(names).toEqual(["orchestrator"]);
67+
describe("fixer agent fallback", () => {
68+
test("fixer inherits librarian model when no fixer config provided", () => {
69+
const config: PluginConfig = {
70+
agents: {
71+
librarian: { model: "librarian-custom-model" },
72+
},
73+
};
74+
const agents = createAgents(config);
75+
const fixer = agents.find((a) => a.name === "fixer");
76+
const librarian = agents.find((a) => a.name === "librarian");
77+
expect(fixer!.config.model).toBe(librarian!.config.model);
7078
});
7179

72-
test("getSubagentNames excludes orchestrator", () => {
73-
const names = getSubagentNames();
74-
expect(names).not.toContain("orchestrator");
75-
expect(names).toContain("explorer");
76-
expect(names).toContain("fixer");
80+
test("fixer uses its own model when explicitly configured", () => {
81+
const config: PluginConfig = {
82+
agents: {
83+
librarian: { model: "librarian-model" },
84+
fixer: { model: "fixer-specific-model" },
85+
},
86+
};
87+
const agents = createAgents(config);
88+
const fixer = agents.find((a) => a.name === "fixer");
89+
expect(fixer!.config.model).toBe("fixer-specific-model");
90+
});
91+
});
92+
93+
describe("orchestrator agent", () => {
94+
test("orchestrator is first in agents array", () => {
95+
const agents = createAgents();
96+
expect(agents[0].name).toBe("orchestrator");
97+
});
98+
99+
test("orchestrator has question permission set to allow", () => {
100+
const agents = createAgents();
101+
const orchestrator = agents.find((a) => a.name === "orchestrator");
102+
expect(orchestrator!.config.permission).toBeDefined();
103+
expect((orchestrator!.config.permission as any).question).toBe("allow");
104+
});
105+
106+
test("orchestrator accepts overrides", () => {
107+
const config: PluginConfig = {
108+
agents: {
109+
orchestrator: { model: "custom-orchestrator-model", temperature: 0.3 },
110+
},
111+
};
112+
const agents = createAgents(config);
113+
const orchestrator = agents.find((a) => a.name === "orchestrator");
114+
expect(orchestrator!.config.model).toBe("custom-orchestrator-model");
115+
expect(orchestrator!.config.temperature).toBe(0.3);
116+
});
117+
});
118+
119+
describe("isSubagent type guard", () => {
120+
test("returns true for valid subagent names", () => {
121+
expect(isSubagent("explorer")).toBe(true);
122+
expect(isSubagent("librarian")).toBe(true);
123+
expect(isSubagent("oracle")).toBe(true);
124+
expect(isSubagent("designer")).toBe(true);
125+
expect(isSubagent("fixer")).toBe(true);
126+
});
127+
128+
test("returns false for orchestrator", () => {
129+
expect(isSubagent("orchestrator")).toBe(false);
130+
});
131+
132+
test("returns false for invalid agent names", () => {
133+
expect(isSubagent("invalid-agent")).toBe(false);
134+
expect(isSubagent("")).toBe(false);
135+
expect(isSubagent("explore")).toBe(false); // old alias, not actual agent name
136+
});
137+
});
138+
139+
describe("agent classification", () => {
140+
test("SUBAGENT_NAMES excludes orchestrator", () => {
141+
expect(SUBAGENT_NAMES).not.toContain("orchestrator");
142+
expect(SUBAGENT_NAMES).toContain("explorer");
143+
expect(SUBAGENT_NAMES).toContain("fixer");
77144
});
78145

79146
test("getAgentConfigs applies correct classification visibility and mode", () => {
80147
const configs = getAgentConfigs();
81148

82149
// Primary agent
83150
expect(configs["orchestrator"].mode).toBe("primary");
84-
expect(configs["orchestrator"].hidden).toBeFalsy();
85151

86152
// Subagents
87-
const subagents = getSubagentNames();
88-
for (const name of subagents) {
153+
for (const name of SUBAGENT_NAMES) {
89154
expect(configs[name].mode).toBe("subagent");
90-
expect(configs[name].hidden).toBe(true);
91155
}
92156
});
93157
});
@@ -101,18 +165,12 @@ describe("createAgents", () => {
101165
expect(names).toContain("designer");
102166
expect(names).toContain("oracle");
103167
expect(names).toContain("librarian");
168+
expect(names).toContain("fixer");
104169
});
105170

106-
test("respects disabled_agents", () => {
107-
const config: PluginConfig = {
108-
disabled_agents: ["explorer", "designer"],
109-
};
110-
const agents = createAgents(config);
111-
const names = agents.map((a) => a.name);
112-
expect(names).not.toContain("explorer");
113-
expect(names).not.toContain("designer");
114-
expect(names).toContain("orchestrator");
115-
expect(names).toContain("oracle");
171+
test("creates exactly 6 agents (1 primary + 5 subagents)", () => {
172+
const agents = createAgents();
173+
expect(agents.length).toBe(6);
116174
});
117175
});
118176

@@ -123,4 +181,10 @@ describe("getAgentConfigs", () => {
123181
expect(configs["explorer"]).toBeDefined();
124182
expect(configs["orchestrator"].model).toBeDefined();
125183
});
184+
185+
test("includes description in SDK config", () => {
186+
const configs = getAgentConfigs();
187+
expect(configs["orchestrator"].description).toBeDefined();
188+
expect(configs["explorer"].description).toBeDefined();
189+
});
126190
});

0 commit comments

Comments
 (0)