opencode-conductor-cdd-plugin 1.0.0-beta.20 → 1.0.0-beta.22

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.
Files changed (30) hide show
  1. package/dist/index.js +2 -3
  2. package/dist/index.test.js +5 -0
  3. package/dist/prompts/strategies/delegate.md +124 -10
  4. package/dist/prompts/strategies/manual.md +138 -6
  5. package/dist/test/integration/modelConfigIntegration.test.d.ts +1 -0
  6. package/dist/test/integration/modelConfigIntegration.test.js +222 -0
  7. package/dist/test/integration/omo3-delegation.test.d.ts +1 -0
  8. package/dist/test/integration/omo3-delegation.test.js +583 -0
  9. package/dist/test/integration/rebrand.test.js +5 -1
  10. package/dist/tools/delegate.d.ts +12 -0
  11. package/dist/tools/delegate.js +84 -33
  12. package/dist/utils/agentInitialization.test.d.ts +1 -0
  13. package/dist/utils/agentInitialization.test.js +262 -0
  14. package/dist/utils/commandDefaults.d.ts +38 -0
  15. package/dist/utils/commandDefaults.js +54 -0
  16. package/dist/utils/commandDefaults.test.d.ts +1 -0
  17. package/dist/utils/commandDefaults.test.js +101 -0
  18. package/dist/utils/configDetection.d.ts +16 -0
  19. package/dist/utils/configDetection.js +103 -1
  20. package/dist/utils/configDetection.test.js +116 -1
  21. package/dist/utils/documentGeneration.d.ts +3 -0
  22. package/dist/utils/documentGeneration.js +29 -9
  23. package/dist/utils/interactiveMenu.test.js +5 -0
  24. package/dist/utils/languageSupport.d.ts +5 -0
  25. package/dist/utils/languageSupport.js +163 -0
  26. package/dist/utils/languageSupport.test.d.ts +1 -0
  27. package/dist/utils/languageSupport.test.js +158 -0
  28. package/dist/utils/modelConfigInjection.test.d.ts +1 -0
  29. package/dist/utils/modelConfigInjection.test.js +137 -0
  30. package/package.json +2 -2
@@ -1,45 +1,96 @@
1
1
  import { tool } from "@opencode-ai/plugin/tool";
2
+ import { detectCDDConfig, getAvailableOMOAgents, getAgentModel } from "../utils/configDetection.js";
3
+ import { resolveAgentForDelegation } from "../utils/synergyDelegation.js";
4
+ /**
5
+ * Creates a delegation tool that follows the OMO 3.0 agent invocation pattern.
6
+ *
7
+ * This implementation uses the synchronous prompt() API pattern:
8
+ * 1. Create a child session with parentID
9
+ * 2. Send prompt with agent specification and tool restrictions
10
+ * 3. Poll for completion by checking when session becomes idle
11
+ * 4. Extract and return the final response
12
+ *
13
+ * Based on OMO 3.0 call_omo_agent implementation:
14
+ * https://github.com/code-yeongyu/oh-my-opencode/blob/main/src/tools/call-omo-agent/tools.ts
15
+ */
2
16
  export function createDelegationTool(ctx) {
3
17
  return tool({
4
- description: "Delegate a specific task to a specialized subagent",
18
+ description: "Delegate a specific task to a specialized subagent using OMO 3.0 invocation pattern",
5
19
  args: {
6
20
  task_description: tool.schema.string().describe("Summary of the work"),
7
- subagent_type: tool.schema.string().describe("The name of the agent to call"),
21
+ subagent_type: tool.schema.string().describe("The name of the agent to call (e.g., explore, oracle, librarian)"),
8
22
  prompt: tool.schema.string().describe("Detailed instructions for the subagent"),
9
23
  },
10
24
  async execute(args, toolContext) {
11
- // 1. Create a sub-session linked to the current one
12
- const createResult = await ctx.client.session.create({
13
- body: {
14
- parentID: toolContext.sessionID,
15
- title: `${args.task_description} (Delegated to ${args.subagent_type})`,
16
- },
17
- });
18
- if (createResult.error)
19
- return `Error: ${createResult.error}`;
20
- const sessionID = createResult.data.id;
21
- // 2. Send the prompt to the subagent
22
- await ctx.client.session.prompt({
23
- path: { id: sessionID },
24
- body: {
25
- agent: args.subagent_type,
26
- tools: {
27
- "cdd_delegate": false,
25
+ try {
26
+ const config = detectCDDConfig();
27
+ const availableAgents = getAvailableOMOAgents();
28
+ const delegationResult = resolveAgentForDelegation(args.subagent_type, config.synergyFramework, availableAgents);
29
+ if (!delegationResult.success) {
30
+ return `Cannot delegate to '${args.subagent_type}': ${delegationResult.reason}\n\nFalling back to @cdd for manual implementation.`;
31
+ }
32
+ const resolvedAgentName = delegationResult.resolvedAgent;
33
+ // 1. Create a sub-session linked to the current one
34
+ const createResult = await ctx.client.session.create({
35
+ body: {
36
+ parentID: toolContext.sessionID,
37
+ title: `${args.task_description} (@${resolvedAgentName})`,
28
38
  },
29
- parts: [{ type: "text", text: args.prompt }],
30
- },
31
- });
32
- // 3. Fetch and return the assistant's response
33
- const messagesResult = await ctx.client.session.messages({
34
- path: { id: sessionID },
35
- });
36
- const lastMessage = messagesResult.data
37
- ?.filter((m) => m.info.role === "assistant")
38
- .pop();
39
- const responseText = lastMessage?.parts
40
- .filter((p) => p.type === "text")
41
- .map((p) => p.text).join("\n") || "No response.";
42
- return `${responseText}\n\n<task_metadata>\nsession_id: ${sessionID}\n</task_metadata>`;
39
+ });
40
+ if (createResult.error) {
41
+ return `Error creating session: ${createResult.error}`;
42
+ }
43
+ const sessionID = createResult.data.id;
44
+ const agentModel = getAgentModel(resolvedAgentName);
45
+ await ctx.client.session.prompt({
46
+ path: { id: sessionID },
47
+ body: {
48
+ agent: resolvedAgentName,
49
+ model: agentModel,
50
+ tools: {
51
+ cdd_delegate: false,
52
+ cdd_bg_task: false,
53
+ },
54
+ parts: [{ type: "text", text: args.prompt }],
55
+ },
56
+ });
57
+ const MAX_POLL_TIME_MS = 5 * 60 * 1000;
58
+ const POLL_INTERVAL_MS = 2000;
59
+ const startTime = Date.now();
60
+ while (Date.now() - startTime < MAX_POLL_TIME_MS) {
61
+ await new Promise(resolve => setTimeout(resolve, POLL_INTERVAL_MS));
62
+ try {
63
+ const statusResult = await ctx.client.session.status();
64
+ const sessionStatus = statusResult.data?.[sessionID];
65
+ if (sessionStatus?.type === "idle") {
66
+ break;
67
+ }
68
+ }
69
+ catch (statusError) {
70
+ console.warn("[CDD Delegate] Status check failed:", statusError);
71
+ }
72
+ }
73
+ const messagesResult = await ctx.client.session.messages({
74
+ path: { id: sessionID },
75
+ });
76
+ if (messagesResult.error) {
77
+ return `Error fetching messages: ${messagesResult.error}`;
78
+ }
79
+ const assistantMessages = (messagesResult.data || [])
80
+ .filter((m) => m.info.role === "assistant");
81
+ if (assistantMessages.length === 0) {
82
+ return `No response from agent ${resolvedAgentName}`;
83
+ }
84
+ const lastMessage = assistantMessages[assistantMessages.length - 1];
85
+ const responseText = lastMessage.parts
86
+ .filter((p) => p.type === "text")
87
+ .map((p) => p.text)
88
+ .join("\n") || "No response.";
89
+ return `${responseText}\n\n<task_metadata>\nsession_id: ${sessionID}\nagent: ${resolvedAgentName}\nrequested: ${args.subagent_type}\n</task_metadata>`;
90
+ }
91
+ catch (error) {
92
+ return `Error during delegation: ${error.message || String(error)}`;
93
+ }
43
94
  },
44
95
  });
45
96
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,262 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
+ import * as configDetection from "./configDetection.js";
3
+ /**
4
+ * Phase 2: Agent Initialization with Resolved Model
5
+ *
6
+ * Tests verify that the resolved model configuration is properly
7
+ * passed to @cdd agent initialization and delegated agents.
8
+ */
9
+ describe("Phase 2: Agent Initialization with Resolved Model", () => {
10
+ let originalEnv;
11
+ beforeEach(() => {
12
+ originalEnv = { ...process.env };
13
+ vi.clearAllMocks();
14
+ });
15
+ afterEach(() => {
16
+ process.env = originalEnv;
17
+ });
18
+ describe("@cdd agent receives correct model from resolved config", () => {
19
+ it("should pass resolved model to agent initialization when oh-my-opencode-slim has model", () => {
20
+ // Arrange
21
+ const configDetectionResult = configDetection.detectCDDConfig();
22
+ // Simulate what the plugin does with the config
23
+ const commandDefaults = configDetectionResult.cddModel
24
+ ? { agent: "cdd", model: configDetectionResult.cddModel }
25
+ : { agent: "cdd" };
26
+ // Assert
27
+ if (configDetectionResult.cddModel) {
28
+ expect(commandDefaults).toHaveProperty("model");
29
+ expect(commandDefaults).toMatchObject({
30
+ agent: "cdd",
31
+ model: configDetectionResult.cddModel,
32
+ });
33
+ }
34
+ });
35
+ it("should use fallback defaults when no model is configured", () => {
36
+ // Arrange
37
+ const configDetectionResult = {
38
+ hasCDDInOpenCode: false,
39
+ hasCDDInOMO: false,
40
+ hasCDDInSlim: false,
41
+ synergyActive: false,
42
+ cddModel: undefined,
43
+ synergyFramework: "none",
44
+ };
45
+ // Act - simulate plugin behavior
46
+ const commandDefaults = configDetectionResult.cddModel
47
+ ? { agent: "cdd", model: configDetectionResult.cddModel }
48
+ : { agent: "cdd" };
49
+ // Assert
50
+ expect(commandDefaults).toEqual({
51
+ agent: "cdd",
52
+ });
53
+ expect(commandDefaults).not.toHaveProperty("model");
54
+ });
55
+ it("should maintain model throughout all CDD command defaults", () => {
56
+ // Arrange
57
+ const configDetectionResult = configDetection.detectCDDConfig();
58
+ const commandDefaults = configDetectionResult.cddModel
59
+ ? { agent: "cdd", model: configDetectionResult.cddModel }
60
+ : { agent: "cdd" };
61
+ // Simulate all CDD commands
62
+ const commands = {
63
+ "cdd:setup": { ...commandDefaults },
64
+ "cdd:newTrack": { ...commandDefaults },
65
+ "cdd:implement": { ...commandDefaults },
66
+ "cdd:status": { ...commandDefaults },
67
+ "cdd:revert": { ...commandDefaults },
68
+ };
69
+ // Assert: all commands should have consistent model config
70
+ Object.entries(commands).forEach(([name, config]) => {
71
+ expect(config).toMatchObject({
72
+ agent: "cdd",
73
+ });
74
+ if (configDetectionResult.cddModel) {
75
+ expect(config).toHaveProperty("model", configDetectionResult.cddModel);
76
+ }
77
+ });
78
+ });
79
+ });
80
+ describe("Agent initialization handles invalid model config gracefully", () => {
81
+ it("should handle undefined model gracefully", () => {
82
+ // Arrange
83
+ const configDetectionResult = {
84
+ hasCDDInOpenCode: false,
85
+ hasCDDInOMO: false,
86
+ hasCDDInSlim: false,
87
+ synergyActive: false,
88
+ cddModel: undefined,
89
+ synergyFramework: "none",
90
+ };
91
+ // Act
92
+ const commandDefaults = configDetectionResult.cddModel
93
+ ? { agent: "cdd", model: configDetectionResult.cddModel }
94
+ : { agent: "cdd" };
95
+ // Assert
96
+ expect(commandDefaults).toBeDefined();
97
+ expect(commandDefaults.agent).toBe("cdd");
98
+ expect(() => {
99
+ if (!commandDefaults.agent)
100
+ throw new Error("Agent not set");
101
+ }).not.toThrow();
102
+ });
103
+ it("should handle empty model string gracefully", () => {
104
+ // Arrange
105
+ const configDetectionResult = {
106
+ hasCDDInOpenCode: false,
107
+ hasCDDInOMO: false,
108
+ hasCDDInSlim: false,
109
+ synergyActive: false,
110
+ cddModel: "",
111
+ synergyFramework: "none",
112
+ };
113
+ // Act
114
+ const commandDefaults = configDetectionResult.cddModel
115
+ ? { agent: "cdd", model: configDetectionResult.cddModel }
116
+ : { agent: "cdd" };
117
+ // Assert - empty string is falsy, should fall back
118
+ expect(commandDefaults).not.toHaveProperty("model");
119
+ });
120
+ it("should validate model string format before passing to agent", () => {
121
+ // Arrange
122
+ const validModel = "anthropic/claude-3-5-sonnet";
123
+ const configDefaults = {
124
+ agent: "cdd",
125
+ model: validModel,
126
+ };
127
+ // Assert - model should be a valid string identifier
128
+ if (configDefaults.model) {
129
+ expect(typeof configDefaults.model).toBe("string");
130
+ expect(configDefaults.model).toMatch(/^[a-z0-9\-]+\/[a-z0-9\-]+$/i);
131
+ }
132
+ });
133
+ });
134
+ describe("Delegated agent model propagation", () => {
135
+ it("should pass model to @explorer agent when configured", () => {
136
+ // Arrange
137
+ const configDetectionResult = configDetection.detectCDDConfig();
138
+ const parentModel = configDetectionResult.cddModel;
139
+ // Act - simulate delegation
140
+ const delegationConfig = parentModel
141
+ ? { agent: "explorer", model: parentModel }
142
+ : { agent: "explorer" };
143
+ // Assert
144
+ expect(delegationConfig).toMatchObject({
145
+ agent: "explorer",
146
+ });
147
+ if (parentModel) {
148
+ expect(delegationConfig).toHaveProperty("model", parentModel);
149
+ }
150
+ });
151
+ it("should pass model to @designer agent when configured", () => {
152
+ // Arrange
153
+ const configDetectionResult = configDetection.detectCDDConfig();
154
+ const parentModel = configDetectionResult.cddModel;
155
+ // Act - simulate delegation
156
+ const delegationConfig = parentModel
157
+ ? { agent: "designer", model: parentModel }
158
+ : { agent: "designer" };
159
+ // Assert
160
+ expect(delegationConfig).toMatchObject({
161
+ agent: "designer",
162
+ });
163
+ if (parentModel) {
164
+ expect(delegationConfig).toHaveProperty("model", parentModel);
165
+ }
166
+ });
167
+ it("should pass model to @librarian agent when configured", () => {
168
+ // Arrange
169
+ const configDetectionResult = configDetection.detectCDDConfig();
170
+ const parentModel = configDetectionResult.cddModel;
171
+ // Act - simulate delegation
172
+ const delegationConfig = parentModel
173
+ ? { agent: "librarian", model: parentModel }
174
+ : { agent: "librarian" };
175
+ // Assert
176
+ expect(delegationConfig).toMatchObject({
177
+ agent: "librarian",
178
+ });
179
+ if (parentModel) {
180
+ expect(delegationConfig).toHaveProperty("model", parentModel);
181
+ }
182
+ });
183
+ it("should pass model to @oracle agent when configured", () => {
184
+ // Arrange
185
+ const configDetectionResult = configDetection.detectCDDConfig();
186
+ const parentModel = configDetectionResult.cddModel;
187
+ // Act - simulate delegation
188
+ const delegationConfig = parentModel
189
+ ? { agent: "oracle", model: parentModel }
190
+ : { agent: "oracle" };
191
+ // Assert
192
+ expect(delegationConfig).toMatchObject({
193
+ agent: "oracle",
194
+ });
195
+ if (parentModel) {
196
+ expect(delegationConfig).toHaveProperty("model", parentModel);
197
+ }
198
+ });
199
+ it("should maintain consistent model across delegation chain", () => {
200
+ // Arrange
201
+ const configDetectionResult = configDetection.detectCDDConfig();
202
+ const parentModel = configDetectionResult.cddModel;
203
+ // Act - simulate full delegation chain
204
+ const cddConfig = parentModel
205
+ ? { agent: "cdd", model: parentModel }
206
+ : { agent: "cdd" };
207
+ const delegatedAgents = ["explorer", "designer", "librarian", "oracle"];
208
+ const delegationConfigs = delegatedAgents.map((agent) => parentModel
209
+ ? { agent: agent, model: parentModel }
210
+ : { agent: agent });
211
+ // Assert - all delegates should have same model as parent
212
+ delegationConfigs.forEach((delegateConfig) => {
213
+ if (parentModel) {
214
+ expect(delegateConfig).toHaveProperty("model", parentModel);
215
+ }
216
+ if (cddConfig && "model" in cddConfig) {
217
+ expect(delegateConfig).toHaveProperty("model", cddConfig.model);
218
+ }
219
+ });
220
+ });
221
+ });
222
+ describe("Synergy framework agent model propagation", () => {
223
+ it("should detect and use slim framework model when active", () => {
224
+ // Arrange
225
+ const configDetectionResult = configDetection.detectCDDConfig();
226
+ // Act
227
+ const hasSlimModel = configDetectionResult.hasCDDInSlim && configDetectionResult.cddModel;
228
+ // Assert
229
+ if (hasSlimModel) {
230
+ expect(configDetectionResult.synergyFramework).toBe("oh-my-opencode-slim");
231
+ expect(configDetectionResult.cddModel).toBeDefined();
232
+ }
233
+ });
234
+ it("should detect and use OMO framework model when slim not active", () => {
235
+ // Arrange
236
+ const configDetectionResult = configDetection.detectCDDConfig();
237
+ // Act
238
+ const hasOmoModel = configDetectionResult.hasCDDInOMO &&
239
+ !configDetectionResult.hasCDDInSlim &&
240
+ configDetectionResult.cddModel;
241
+ // Assert
242
+ if (hasOmoModel) {
243
+ expect(configDetectionResult.synergyFramework).toBe("oh-my-opencode");
244
+ expect(configDetectionResult.cddModel).toBeDefined();
245
+ }
246
+ });
247
+ it("should respect synergy priority when resolving model", () => {
248
+ // Arrange
249
+ const configDetectionResult = configDetection.detectCDDConfig();
250
+ // Act & Assert - verify priority chain
251
+ if (configDetectionResult.hasCDDInSlim) {
252
+ expect(configDetectionResult.synergyFramework).toBe("oh-my-opencode-slim");
253
+ }
254
+ else if (configDetectionResult.hasCDDInOMO) {
255
+ expect(configDetectionResult.synergyFramework).toBe("oh-my-opencode");
256
+ }
257
+ else {
258
+ expect(configDetectionResult.synergyFramework).toBe("none");
259
+ }
260
+ });
261
+ });
262
+ });
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Factory for creating OpenCode command defaults with CDD agent configuration.
3
+ * Handles model resolution and provides type-safe command default objects.
4
+ */
5
+ export interface CommandDefaults {
6
+ agent: "cdd";
7
+ model?: string;
8
+ }
9
+ /**
10
+ * Creates command defaults for CDD commands with optional model configuration.
11
+ *
12
+ * @param cddModel - The resolved model string from config detection (undefined = use OpenCode default)
13
+ * @returns Command defaults object with agent and optional model
14
+ *
15
+ * @example
16
+ * // With model configured
17
+ * const defaults = createCommandDefaults("anthropic/claude-3-5-sonnet");
18
+ * // { agent: "cdd", model: "anthropic/claude-3-5-sonnet" }
19
+ *
20
+ * @example
21
+ * // Without model (fallback to OpenCode default)
22
+ * const defaults = createCommandDefaults(undefined);
23
+ * // { agent: "cdd" }
24
+ */
25
+ export declare function createCommandDefaults(cddModel: string | undefined): CommandDefaults;
26
+ /**
27
+ * Validates that a model string is properly formatted.
28
+ *
29
+ * @param model - Model string to validate
30
+ * @returns true if valid, false otherwise
31
+ *
32
+ * @example
33
+ * validateModelString("anthropic/claude-3-5-sonnet") // true
34
+ * validateModelString("google/gemini-flash-1.5") // true
35
+ * validateModelString("") // false
36
+ * validateModelString(" ") // false
37
+ */
38
+ export declare function validateModelString(model: string | undefined): boolean;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Factory for creating OpenCode command defaults with CDD agent configuration.
3
+ * Handles model resolution and provides type-safe command default objects.
4
+ */
5
+ /**
6
+ * Creates command defaults for CDD commands with optional model configuration.
7
+ *
8
+ * @param cddModel - The resolved model string from config detection (undefined = use OpenCode default)
9
+ * @returns Command defaults object with agent and optional model
10
+ *
11
+ * @example
12
+ * // With model configured
13
+ * const defaults = createCommandDefaults("anthropic/claude-3-5-sonnet");
14
+ * // { agent: "cdd", model: "anthropic/claude-3-5-sonnet" }
15
+ *
16
+ * @example
17
+ * // Without model (fallback to OpenCode default)
18
+ * const defaults = createCommandDefaults(undefined);
19
+ * // { agent: "cdd" }
20
+ */
21
+ export function createCommandDefaults(cddModel) {
22
+ if (typeof cddModel === "string" && cddModel.trim() === "") {
23
+ console.warn("[CDD] Invalid model configuration detected. Model must be a non-empty string. Falling back to OpenCode default.");
24
+ return { agent: "cdd" };
25
+ }
26
+ if (!cddModel) {
27
+ return { agent: "cdd" };
28
+ }
29
+ return {
30
+ agent: "cdd",
31
+ model: cddModel,
32
+ };
33
+ }
34
+ /**
35
+ * Validates that a model string is properly formatted.
36
+ *
37
+ * @param model - Model string to validate
38
+ * @returns true if valid, false otherwise
39
+ *
40
+ * @example
41
+ * validateModelString("anthropic/claude-3-5-sonnet") // true
42
+ * validateModelString("google/gemini-flash-1.5") // true
43
+ * validateModelString("") // false
44
+ * validateModelString(" ") // false
45
+ */
46
+ export function validateModelString(model) {
47
+ if (!model)
48
+ return false;
49
+ if (typeof model !== "string")
50
+ return false;
51
+ if (model.trim() === "")
52
+ return false;
53
+ return true;
54
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,101 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import { createCommandDefaults, validateModelString } from './commandDefaults.js';
3
+ describe('commandDefaults', () => {
4
+ describe('createCommandDefaults', () => {
5
+ it('should return defaults with model when valid model provided', () => {
6
+ const result = createCommandDefaults('anthropic/claude-3-5-sonnet');
7
+ expect(result).toEqual({
8
+ agent: 'cdd',
9
+ model: 'anthropic/claude-3-5-sonnet',
10
+ });
11
+ });
12
+ it('should return defaults without model when undefined provided', () => {
13
+ const result = createCommandDefaults(undefined);
14
+ expect(result).toEqual({
15
+ agent: 'cdd',
16
+ });
17
+ });
18
+ it('should return defaults without model when empty string provided', () => {
19
+ const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => { });
20
+ const result = createCommandDefaults('');
21
+ expect(result).toEqual({
22
+ agent: 'cdd',
23
+ });
24
+ expect(consoleWarnSpy).toHaveBeenCalledWith(expect.stringContaining('[CDD] Invalid model configuration detected'));
25
+ consoleWarnSpy.mockRestore();
26
+ });
27
+ it('should return defaults without model when whitespace-only string provided', () => {
28
+ const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => { });
29
+ const result = createCommandDefaults(' ');
30
+ expect(result).toEqual({
31
+ agent: 'cdd',
32
+ });
33
+ expect(consoleWarnSpy).toHaveBeenCalledWith(expect.stringContaining('[CDD] Invalid model configuration detected'));
34
+ consoleWarnSpy.mockRestore();
35
+ });
36
+ it('should handle various valid model formats', () => {
37
+ const testCases = [
38
+ 'anthropic/claude-3-5-sonnet',
39
+ 'google/gemini-flash-1.5',
40
+ 'openai/gpt-4',
41
+ 'anthropic/claude-3-opus-20240229',
42
+ ];
43
+ testCases.forEach(model => {
44
+ const result = createCommandDefaults(model);
45
+ expect(result.model).toBe(model);
46
+ expect(result.agent).toBe('cdd');
47
+ });
48
+ });
49
+ it('should return object with correct TypeScript type', () => {
50
+ const result = createCommandDefaults('anthropic/claude-3-5-sonnet');
51
+ expect(result.agent).toBe('cdd');
52
+ expect(typeof result.model).toBe('string');
53
+ });
54
+ });
55
+ describe('validateModelString', () => {
56
+ it('should return true for valid model strings', () => {
57
+ expect(validateModelString('anthropic/claude-3-5-sonnet')).toBe(true);
58
+ expect(validateModelString('google/gemini-flash-1.5')).toBe(true);
59
+ expect(validateModelString('openai/gpt-4')).toBe(true);
60
+ });
61
+ it('should return false for undefined', () => {
62
+ expect(validateModelString(undefined)).toBe(false);
63
+ });
64
+ it('should return false for empty string', () => {
65
+ expect(validateModelString('')).toBe(false);
66
+ });
67
+ it('should return false for whitespace-only string', () => {
68
+ expect(validateModelString(' ')).toBe(false);
69
+ expect(validateModelString('\t\n')).toBe(false);
70
+ });
71
+ it('should return false for non-string types', () => {
72
+ expect(validateModelString(null)).toBe(false);
73
+ expect(validateModelString(123)).toBe(false);
74
+ expect(validateModelString({})).toBe(false);
75
+ expect(validateModelString([])).toBe(false);
76
+ });
77
+ });
78
+ describe('integration with index.ts', () => {
79
+ it('should create command defaults compatible with OpenCode command config', () => {
80
+ const defaults = createCommandDefaults('anthropic/claude-3-5-sonnet');
81
+ const commandConfig = {
82
+ ...defaults,
83
+ template: 'Some prompt template',
84
+ description: 'Command description',
85
+ };
86
+ expect(commandConfig.agent).toBe('cdd');
87
+ expect(commandConfig.model).toBe('anthropic/claude-3-5-sonnet');
88
+ expect(commandConfig.template).toBe('Some prompt template');
89
+ });
90
+ it('should work with spread operator for command defaults', () => {
91
+ const defaults = createCommandDefaults('google/gemini-flash-1.5');
92
+ const command = {
93
+ ...defaults,
94
+ someOtherProperty: 'value',
95
+ };
96
+ expect(command.agent).toBe('cdd');
97
+ expect(command.model).toBe('google/gemini-flash-1.5');
98
+ expect(command.someOtherProperty).toBe('value');
99
+ });
100
+ });
101
+ });
@@ -18,5 +18,21 @@ export interface ConfigDetectionResult {
18
18
  synergyFramework: SynergyFramework;
19
19
  /** Available agents from slim config (filtered by disabled_agents) */
20
20
  slimAgents?: string[];
21
+ /** Available agents from OMO config (filtered by disabled_agents) */
22
+ omoAgents?: string[];
21
23
  }
22
24
  export declare function detectCDDConfig(): ConfigDetectionResult;
25
+ export declare function getAvailableOMOAgents(): string[];
26
+ /**
27
+ * Get the configured model for a specific agent from synergy framework config.
28
+ *
29
+ * Priority Order:
30
+ * 1. oh-my-opencode-slim.json → agents.<agentName>.model
31
+ * 2. oh-my-opencode.json → agents.<agentName>.model
32
+ * 3. opencode.json → agent.<agentName>.model
33
+ * 4. undefined (no model configured)
34
+ *
35
+ * @param agentName - Name of the agent (e.g., 'explorer', 'designer', 'cdd')
36
+ * @returns Model string if configured, undefined otherwise
37
+ */
38
+ export declare function getAgentModel(agentName: string): string | undefined;