@lgcyaxi/oh-my-claude 1.0.0 → 1.0.1
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/CHANGELOG.md +31 -0
- package/README.md +28 -11
- package/README.zh-CN.md +28 -11
- package/bin/oh-my-claude.js +4 -2
- package/changelog/v1.0.0.md +28 -0
- package/changelog/v1.0.1.md +28 -0
- package/dist/cli.js +94 -2
- package/dist/index-5ars1tn4.js +7348 -0
- package/dist/index.js +11 -1
- package/dist/mcp/server.js +138 -17
- package/package.json +3 -3
- package/src/agents/document-writer.ts +5 -0
- package/src/agents/explore.ts +5 -0
- package/src/agents/frontend-ui-ux.ts +5 -0
- package/src/agents/librarian.ts +5 -0
- package/src/agents/oracle.ts +5 -0
- package/src/agents/types.ts +11 -0
- package/src/cli.ts +126 -1
- package/src/config/loader.ts +73 -0
- package/src/config/schema.ts +25 -2
- package/src/generators/agent-generator.ts +17 -1
- package/src/mcp/background-agent-server/server.ts +6 -2
- package/src/mcp/background-agent-server/task-manager.ts +30 -4
- package/src/providers/router.ts +28 -0
package/src/config/schema.ts
CHANGED
|
@@ -15,6 +15,13 @@ export const ProviderConfigSchema = z.object({
|
|
|
15
15
|
note: z.string().optional(),
|
|
16
16
|
});
|
|
17
17
|
|
|
18
|
+
// Agent fallback configuration schema
|
|
19
|
+
export const AgentFallbackConfigSchema = z.object({
|
|
20
|
+
provider: z.string(),
|
|
21
|
+
model: z.string(),
|
|
22
|
+
executionMode: z.enum(["task", "mcp"]).optional(),
|
|
23
|
+
});
|
|
24
|
+
|
|
18
25
|
// Agent configuration schema
|
|
19
26
|
export const AgentConfigSchema = z.object({
|
|
20
27
|
provider: z.string(),
|
|
@@ -27,6 +34,7 @@ export const AgentConfigSchema = z.object({
|
|
|
27
34
|
budget_tokens: z.number().optional(),
|
|
28
35
|
})
|
|
29
36
|
.optional(),
|
|
37
|
+
fallback: AgentFallbackConfigSchema.optional(),
|
|
30
38
|
});
|
|
31
39
|
|
|
32
40
|
// Category configuration schema
|
|
@@ -77,6 +85,7 @@ export const OhMyClaudeConfigSchema = z.object({
|
|
|
77
85
|
}),
|
|
78
86
|
|
|
79
87
|
agents: z.record(z.string(), AgentConfigSchema).default({
|
|
88
|
+
// Claude subscription agents (no fallback needed)
|
|
80
89
|
Sisyphus: { provider: "claude", model: "claude-opus-4-5" },
|
|
81
90
|
"claude-reviewer": {
|
|
82
91
|
provider: "claude",
|
|
@@ -88,22 +97,36 @@ export const OhMyClaudeConfigSchema = z.object({
|
|
|
88
97
|
model: "claude-haiku-4-5",
|
|
89
98
|
temperature: 0.3,
|
|
90
99
|
},
|
|
100
|
+
// External API agents (with Claude fallbacks)
|
|
91
101
|
oracle: {
|
|
92
102
|
provider: "deepseek",
|
|
93
103
|
model: "deepseek-reasoner",
|
|
94
104
|
temperature: 0.1,
|
|
105
|
+
fallback: { provider: "claude", model: "claude-opus-4-5", executionMode: "task" },
|
|
106
|
+
},
|
|
107
|
+
librarian: {
|
|
108
|
+
provider: "zhipu",
|
|
109
|
+
model: "glm-4.7",
|
|
110
|
+
temperature: 0.3,
|
|
111
|
+
fallback: { provider: "claude", model: "claude-sonnet-4-5", executionMode: "task" },
|
|
112
|
+
},
|
|
113
|
+
explore: {
|
|
114
|
+
provider: "deepseek",
|
|
115
|
+
model: "deepseek-chat",
|
|
116
|
+
temperature: 0.1,
|
|
117
|
+
fallback: { provider: "claude", model: "claude-haiku-4-5", executionMode: "task" },
|
|
95
118
|
},
|
|
96
|
-
librarian: { provider: "zhipu", model: "glm-4.7", temperature: 0.3 },
|
|
97
|
-
explore: { provider: "deepseek", model: "deepseek-chat", temperature: 0.1 },
|
|
98
119
|
"frontend-ui-ux": {
|
|
99
120
|
provider: "zhipu",
|
|
100
121
|
model: "glm-4v-flash",
|
|
101
122
|
temperature: 0.7,
|
|
123
|
+
fallback: { provider: "claude", model: "claude-sonnet-4-5", executionMode: "task" },
|
|
102
124
|
},
|
|
103
125
|
"document-writer": {
|
|
104
126
|
provider: "minimax",
|
|
105
127
|
model: "MiniMax-M2.1",
|
|
106
128
|
temperature: 0.5,
|
|
129
|
+
fallback: { provider: "claude", model: "claude-sonnet-4-5", executionMode: "task" },
|
|
107
130
|
},
|
|
108
131
|
}),
|
|
109
132
|
|
|
@@ -35,7 +35,7 @@ export function generateAgentMarkdown(agent: AgentDefinition): string {
|
|
|
35
35
|
lines.push(`> ${agent.description}`);
|
|
36
36
|
lines.push("");
|
|
37
37
|
|
|
38
|
-
// Execution mode note
|
|
38
|
+
// Execution mode note with fallback info
|
|
39
39
|
if (agent.executionMode === "task") {
|
|
40
40
|
lines.push(
|
|
41
41
|
`<!-- Execution: Claude Code Task tool (sync) - Uses Claude subscription -->`
|
|
@@ -44,12 +44,28 @@ export function generateAgentMarkdown(agent: AgentDefinition): string {
|
|
|
44
44
|
lines.push(
|
|
45
45
|
`<!-- Execution: oh-my-claude MCP server (async) - Uses ${agent.defaultProvider} API -->`
|
|
46
46
|
);
|
|
47
|
+
// Add fallback info for MCP agents
|
|
48
|
+
if (agent.fallback) {
|
|
49
|
+
lines.push(
|
|
50
|
+
`<!-- Fallback: ${agent.fallback.provider}/${agent.fallback.model} via Task tool (when ${agent.defaultProvider.toUpperCase()}_API_KEY is not set) -->`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
47
53
|
}
|
|
48
54
|
lines.push("");
|
|
49
55
|
|
|
50
56
|
// The actual prompt
|
|
51
57
|
lines.push(agent.prompt);
|
|
52
58
|
|
|
59
|
+
// Add fallback usage note for MCP agents
|
|
60
|
+
if (agent.executionMode === "mcp" && agent.fallback) {
|
|
61
|
+
lines.push("");
|
|
62
|
+
lines.push("## Fallback Mode");
|
|
63
|
+
lines.push("");
|
|
64
|
+
lines.push(`If \`${agent.defaultProvider.toUpperCase()}_API_KEY\` is not configured, this agent will automatically fall back to using **${agent.fallback.model}** via Claude's Task tool.`);
|
|
65
|
+
lines.push("");
|
|
66
|
+
lines.push("The fallback provides similar capabilities using your Claude subscription, though the primary provider may offer specialized features.");
|
|
67
|
+
}
|
|
68
|
+
|
|
53
69
|
return lines.join("\n");
|
|
54
70
|
}
|
|
55
71
|
|
|
@@ -50,6 +50,9 @@ Use this for async operations that should run in parallel without blocking.
|
|
|
50
50
|
- visual-engineering: UI/UX work
|
|
51
51
|
- writing: Documentation and text
|
|
52
52
|
|
|
53
|
+
**Fallback Behavior**:
|
|
54
|
+
If the provider's API key is not configured, the task will return with status "fallback_required" and include fallback info. When this happens, use Claude Code's Task tool with the specified fallback model instead.
|
|
55
|
+
|
|
53
56
|
Note: Agents using Claude subscription (sisyphus, claude-reviewer, claude-scout) should use Claude Code's Task tool instead.`,
|
|
54
57
|
inputSchema: {
|
|
55
58
|
type: "object",
|
|
@@ -117,8 +120,8 @@ Note: Agents using Claude subscription (sisyphus, claude-reviewer, claude-scout)
|
|
|
117
120
|
properties: {
|
|
118
121
|
status: {
|
|
119
122
|
type: "string",
|
|
120
|
-
enum: ["pending", "running", "completed", "failed", "cancelled"],
|
|
121
|
-
description: "Filter by status",
|
|
123
|
+
enum: ["pending", "running", "completed", "failed", "cancelled", "fallback_required"],
|
|
124
|
+
description: "Filter by status. 'fallback_required' means the provider API key is not configured and Claude Task tool should be used instead.",
|
|
122
125
|
},
|
|
123
126
|
limit: {
|
|
124
127
|
type: "number",
|
|
@@ -320,6 +323,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
320
323
|
completed: new Date(t.completedAt).toISOString(),
|
|
321
324
|
}),
|
|
322
325
|
...(t.error && { error: t.error }),
|
|
326
|
+
...(t.fallback && { fallback: t.fallback }),
|
|
323
327
|
})),
|
|
324
328
|
}),
|
|
325
329
|
},
|
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
* - Handle concurrency limits per provider
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { routeByAgent, routeByCategory } from "../../providers/router";
|
|
10
|
+
import { routeByAgent, routeByCategory, FallbackRequiredError } from "../../providers/router";
|
|
11
11
|
import { getAgent } from "../../agents";
|
|
12
12
|
import type { ChatMessage } from "../../providers/types";
|
|
13
13
|
|
|
14
|
-
export type TaskStatus = "pending" | "running" | "completed" | "failed" | "cancelled";
|
|
14
|
+
export type TaskStatus = "pending" | "running" | "completed" | "failed" | "cancelled" | "fallback_required";
|
|
15
15
|
|
|
16
16
|
export interface Task {
|
|
17
17
|
id: string;
|
|
@@ -21,6 +21,13 @@ export interface Task {
|
|
|
21
21
|
status: TaskStatus;
|
|
22
22
|
result?: string;
|
|
23
23
|
error?: string;
|
|
24
|
+
/** Fallback info when primary provider is not configured */
|
|
25
|
+
fallback?: {
|
|
26
|
+
provider: string;
|
|
27
|
+
model: string;
|
|
28
|
+
executionMode?: string;
|
|
29
|
+
reason: string;
|
|
30
|
+
};
|
|
24
31
|
createdAt: number;
|
|
25
32
|
startedAt?: number;
|
|
26
33
|
completedAt?: number;
|
|
@@ -123,8 +130,20 @@ async function runTask(task: Task, systemPrompt?: string): Promise<void> {
|
|
|
123
130
|
task.completedAt = Date.now();
|
|
124
131
|
tasks.set(task.id, task);
|
|
125
132
|
} catch (error) {
|
|
126
|
-
|
|
127
|
-
|
|
133
|
+
// Handle fallback required error specially
|
|
134
|
+
if (error instanceof FallbackRequiredError) {
|
|
135
|
+
task.status = "fallback_required";
|
|
136
|
+
task.error = error.message;
|
|
137
|
+
task.fallback = {
|
|
138
|
+
provider: error.fallback.provider,
|
|
139
|
+
model: error.fallback.model,
|
|
140
|
+
executionMode: error.fallback.executionMode,
|
|
141
|
+
reason: error.reason,
|
|
142
|
+
};
|
|
143
|
+
} else {
|
|
144
|
+
task.status = "failed";
|
|
145
|
+
task.error = error instanceof Error ? error.message : String(error);
|
|
146
|
+
}
|
|
128
147
|
task.completedAt = Date.now();
|
|
129
148
|
tasks.set(task.id, task);
|
|
130
149
|
}
|
|
@@ -144,6 +163,12 @@ export function pollTask(taskId: string): {
|
|
|
144
163
|
status: TaskStatus;
|
|
145
164
|
result?: string;
|
|
146
165
|
error?: string;
|
|
166
|
+
fallback?: {
|
|
167
|
+
provider: string;
|
|
168
|
+
model: string;
|
|
169
|
+
executionMode?: string;
|
|
170
|
+
reason: string;
|
|
171
|
+
};
|
|
147
172
|
} {
|
|
148
173
|
const task = tasks.get(taskId);
|
|
149
174
|
|
|
@@ -155,6 +180,7 @@ export function pollTask(taskId: string): {
|
|
|
155
180
|
status: task.status,
|
|
156
181
|
result: task.result,
|
|
157
182
|
error: task.error,
|
|
183
|
+
fallback: task.fallback,
|
|
158
184
|
};
|
|
159
185
|
}
|
|
160
186
|
|
package/src/providers/router.ts
CHANGED
|
@@ -16,6 +16,8 @@ import {
|
|
|
16
16
|
resolveProviderForAgent,
|
|
17
17
|
resolveProviderForCategory,
|
|
18
18
|
getProviderDetails,
|
|
19
|
+
shouldUseFallback,
|
|
20
|
+
isProviderConfigured,
|
|
19
21
|
type OhMyClaudeConfig,
|
|
20
22
|
} from "../config";
|
|
21
23
|
|
|
@@ -80,6 +82,21 @@ function getProviderClient(
|
|
|
80
82
|
return client;
|
|
81
83
|
}
|
|
82
84
|
|
|
85
|
+
/**
|
|
86
|
+
* Custom error class for fallback scenarios
|
|
87
|
+
*/
|
|
88
|
+
export class FallbackRequiredError extends Error {
|
|
89
|
+
constructor(
|
|
90
|
+
message: string,
|
|
91
|
+
public readonly agentName: string,
|
|
92
|
+
public readonly fallback: { provider: string; model: string; executionMode?: string },
|
|
93
|
+
public readonly reason: string
|
|
94
|
+
) {
|
|
95
|
+
super(message);
|
|
96
|
+
this.name = "FallbackRequiredError";
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
83
100
|
/**
|
|
84
101
|
* Route a request to the appropriate provider based on agent name
|
|
85
102
|
*/
|
|
@@ -106,6 +123,17 @@ export async function routeByAgent(
|
|
|
106
123
|
);
|
|
107
124
|
}
|
|
108
125
|
|
|
126
|
+
// Check if fallback should be used (primary provider not configured)
|
|
127
|
+
const fallbackCheck = shouldUseFallback(config, agentName);
|
|
128
|
+
if (fallbackCheck.useFallback && fallbackCheck.fallback) {
|
|
129
|
+
throw new FallbackRequiredError(
|
|
130
|
+
`Agent "${agentName}" requires fallback: ${fallbackCheck.reason}. Use Task tool with ${fallbackCheck.fallback.model} instead.`,
|
|
131
|
+
agentName,
|
|
132
|
+
fallbackCheck.fallback,
|
|
133
|
+
fallbackCheck.reason ?? "Provider not configured"
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
109
137
|
const client = getProviderClient(agentConfig.provider, config);
|
|
110
138
|
|
|
111
139
|
const request: ChatCompletionRequest = {
|