support for codex
This commit is contained in:
parent
65a9553bb9
commit
c1fccaaf37
10
package-lock.json
generated
10
package-lock.json
generated
@ -10,6 +10,7 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@anthropic-ai/claude-agent-sdk": "^0.2.19",
|
"@anthropic-ai/claude-agent-sdk": "^0.2.19",
|
||||||
|
"@openai/codex-sdk": "^0.91.0",
|
||||||
"chalk": "^5.3.0",
|
"chalk": "^5.3.0",
|
||||||
"commander": "^12.1.0",
|
"commander": "^12.1.0",
|
||||||
"yaml": "^2.4.5",
|
"yaml": "^2.4.5",
|
||||||
@ -979,6 +980,15 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@openai/codex-sdk": {
|
||||||
|
"version": "0.91.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@openai/codex-sdk/-/codex-sdk-0.91.0.tgz",
|
||||||
|
"integrity": "sha512-YYf8QNkpQyuzNgn9Mf9D3G1pp0ObI98ADCNqASBpdlpxqykMyABgQdMRdc4c/l1KdoTnGVkUw0ljXaCHurs5vA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||||
"version": "4.56.0",
|
"version": "4.56.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.56.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.56.0.tgz",
|
||||||
|
|||||||
@ -49,6 +49,7 @@
|
|||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@anthropic-ai/claude-agent-sdk": "^0.2.19",
|
"@anthropic-ai/claude-agent-sdk": "^0.2.19",
|
||||||
|
"@openai/codex-sdk": "^0.91.0",
|
||||||
"chalk": "^5.3.0",
|
"chalk": "^5.3.0",
|
||||||
"commander": "^12.1.0",
|
"commander": "^12.1.0",
|
||||||
"yaml": "^2.4.5",
|
"yaml": "^2.4.5",
|
||||||
|
|||||||
@ -13,6 +13,9 @@ default_workflow: default
|
|||||||
# Log level: debug, info, warn, error
|
# Log level: debug, info, warn, error
|
||||||
log_level: info
|
log_level: info
|
||||||
|
|
||||||
|
# Provider runtime: claude or codex
|
||||||
|
provider: claude
|
||||||
|
|
||||||
# Debug settings (optional)
|
# Debug settings (optional)
|
||||||
# debug:
|
# debug:
|
||||||
# enabled: false
|
# enabled: false
|
||||||
|
|||||||
@ -13,6 +13,9 @@ default_workflow: default
|
|||||||
# ログレベル: debug, info, warn, error
|
# ログレベル: debug, info, warn, error
|
||||||
log_level: info
|
log_level: info
|
||||||
|
|
||||||
|
# プロバイダー: claude または codex
|
||||||
|
provider: claude
|
||||||
|
|
||||||
# デバッグ設定 (オプション)
|
# デバッグ設定 (オプション)
|
||||||
# debug:
|
# debug:
|
||||||
# enabled: false
|
# enabled: false
|
||||||
|
|||||||
@ -119,6 +119,17 @@ describe('CustomAgentConfigSchema', () => {
|
|||||||
expect(result.claude_agent).toBe('architect');
|
expect(result.claude_agent).toBe('architect');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should accept agent with provider override', () => {
|
||||||
|
const config = {
|
||||||
|
name: 'my-agent',
|
||||||
|
prompt: 'You are a helpful assistant.',
|
||||||
|
provider: 'codex',
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = CustomAgentConfigSchema.parse(config);
|
||||||
|
expect(result.provider).toBe('codex');
|
||||||
|
});
|
||||||
|
|
||||||
it('should reject agent without any prompt source', () => {
|
it('should reject agent without any prompt source', () => {
|
||||||
const config = {
|
const config = {
|
||||||
name: 'my-agent',
|
name: 'my-agent',
|
||||||
@ -136,6 +147,7 @@ describe('GlobalConfigSchema', () => {
|
|||||||
expect(result.trusted_directories).toEqual([]);
|
expect(result.trusted_directories).toEqual([]);
|
||||||
expect(result.default_workflow).toBe('default');
|
expect(result.default_workflow).toBe('default');
|
||||||
expect(result.log_level).toBe('info');
|
expect(result.log_level).toBe('info');
|
||||||
|
expect(result.provider).toBe('claude');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should accept valid config', () => {
|
it('should accept valid config', () => {
|
||||||
@ -175,4 +187,3 @@ describe('GENERIC_STATUS_PATTERNS', () => {
|
|||||||
expect(new RegExp(GENERIC_STATUS_PATTERNS.improve).test('[MAGI:IMPROVE]')).toBe(true);
|
expect(new RegExp(GENERIC_STATUS_PATTERNS.improve).test('[MAGI:IMPROVE]')).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -13,7 +13,10 @@ import {
|
|||||||
ClaudeCallOptions,
|
ClaudeCallOptions,
|
||||||
} from '../claude/client.js';
|
} from '../claude/client.js';
|
||||||
import { type StreamCallback, type PermissionHandler, type AskUserQuestionHandler } from '../claude/process.js';
|
import { type StreamCallback, type PermissionHandler, type AskUserQuestionHandler } from '../claude/process.js';
|
||||||
|
import { callCodex, callCodexCustom, type CodexCallOptions } from '../codex/client.js';
|
||||||
import { loadCustomAgents, loadAgentPrompt } from '../config/loader.js';
|
import { loadCustomAgents, loadAgentPrompt } from '../config/loader.js';
|
||||||
|
import { loadGlobalConfig } from '../config/globalConfig.js';
|
||||||
|
import { loadProjectConfig } from '../config/projectConfig.js';
|
||||||
import type { AgentResponse, CustomAgentConfig } from '../models/types.js';
|
import type { AgentResponse, CustomAgentConfig } from '../models/types.js';
|
||||||
|
|
||||||
export type { StreamCallback };
|
export type { StreamCallback };
|
||||||
@ -23,6 +26,7 @@ export interface RunAgentOptions {
|
|||||||
cwd: string;
|
cwd: string;
|
||||||
sessionId?: string;
|
sessionId?: string;
|
||||||
model?: string;
|
model?: string;
|
||||||
|
provider?: 'claude' | 'codex';
|
||||||
/** Resolved path to agent prompt file */
|
/** Resolved path to agent prompt file */
|
||||||
agentPath?: string;
|
agentPath?: string;
|
||||||
onStream?: StreamCallback;
|
onStream?: StreamCallback;
|
||||||
@ -40,6 +44,22 @@ const DEFAULT_AGENT_TOOLS: Record<string, string[]> = {
|
|||||||
planner: ['Read', 'Glob', 'Grep', 'Bash', 'WebSearch', 'WebFetch'],
|
planner: ['Read', 'Glob', 'Grep', 'Bash', 'WebSearch', 'WebFetch'],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type AgentProvider = 'claude' | 'codex';
|
||||||
|
|
||||||
|
function resolveProvider(cwd: string, options?: RunAgentOptions, agentConfig?: CustomAgentConfig): AgentProvider {
|
||||||
|
if (options?.provider) return options.provider;
|
||||||
|
if (agentConfig?.provider) return agentConfig.provider;
|
||||||
|
const projectConfig = loadProjectConfig(cwd);
|
||||||
|
if (projectConfig.provider) return projectConfig.provider;
|
||||||
|
try {
|
||||||
|
const globalConfig = loadGlobalConfig();
|
||||||
|
if (globalConfig.provider) return globalConfig.provider;
|
||||||
|
} catch {
|
||||||
|
// Ignore missing global config; fallback below
|
||||||
|
}
|
||||||
|
return 'claude';
|
||||||
|
}
|
||||||
|
|
||||||
/** Get git diff for review context */
|
/** Get git diff for review context */
|
||||||
export function getGitDiff(cwd: string): string {
|
export function getGitDiff(cwd: string): string {
|
||||||
try {
|
try {
|
||||||
@ -102,6 +122,18 @@ export async function runCustomAgent(
|
|||||||
// Custom agent with prompt
|
// Custom agent with prompt
|
||||||
const systemPrompt = loadAgentPrompt(agentConfig);
|
const systemPrompt = loadAgentPrompt(agentConfig);
|
||||||
const tools = agentConfig.allowedTools || ['Read', 'Glob', 'Grep', 'WebSearch', 'WebFetch'];
|
const tools = agentConfig.allowedTools || ['Read', 'Glob', 'Grep', 'WebSearch', 'WebFetch'];
|
||||||
|
const provider = resolveProvider(options.cwd, options, agentConfig);
|
||||||
|
if (provider === 'codex') {
|
||||||
|
const callOptions: CodexCallOptions = {
|
||||||
|
cwd: options.cwd,
|
||||||
|
sessionId: options.sessionId,
|
||||||
|
model: options.model || agentConfig.model,
|
||||||
|
statusPatterns: agentConfig.statusPatterns,
|
||||||
|
onStream: options.onStream,
|
||||||
|
};
|
||||||
|
return callCodexCustom(agentConfig.name, task, systemPrompt, callOptions);
|
||||||
|
}
|
||||||
|
|
||||||
const callOptions: ClaudeCallOptions = {
|
const callOptions: ClaudeCallOptions = {
|
||||||
cwd: options.cwd,
|
cwd: options.cwd,
|
||||||
sessionId: options.sessionId,
|
sessionId: options.sessionId,
|
||||||
@ -167,6 +199,18 @@ export async function runAgent(
|
|||||||
}
|
}
|
||||||
const systemPrompt = loadAgentPromptFromPath(options.agentPath);
|
const systemPrompt = loadAgentPromptFromPath(options.agentPath);
|
||||||
const tools = DEFAULT_AGENT_TOOLS[agentName] || ['Read', 'Glob', 'Grep', 'WebSearch', 'WebFetch'];
|
const tools = DEFAULT_AGENT_TOOLS[agentName] || ['Read', 'Glob', 'Grep', 'WebSearch', 'WebFetch'];
|
||||||
|
const provider = resolveProvider(options.cwd, options);
|
||||||
|
|
||||||
|
if (provider === 'codex') {
|
||||||
|
const callOptions: CodexCallOptions = {
|
||||||
|
cwd: options.cwd,
|
||||||
|
sessionId: options.sessionId,
|
||||||
|
model: options.model,
|
||||||
|
systemPrompt,
|
||||||
|
onStream: options.onStream,
|
||||||
|
};
|
||||||
|
return callCodex(agentName, task, callOptions);
|
||||||
|
}
|
||||||
|
|
||||||
const callOptions: ClaudeCallOptions = {
|
const callOptions: ClaudeCallOptions = {
|
||||||
cwd: options.cwd,
|
cwd: options.cwd,
|
||||||
|
|||||||
184
src/codex/client.ts
Normal file
184
src/codex/client.ts
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
/**
|
||||||
|
* Codex SDK integration for agent interactions
|
||||||
|
*
|
||||||
|
* Uses @openai/codex-sdk for native TypeScript integration.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Codex } from '@openai/codex-sdk';
|
||||||
|
import type { AgentResponse, Status } from '../models/types.js';
|
||||||
|
import { GENERIC_STATUS_PATTERNS } from '../models/schemas.js';
|
||||||
|
import { detectStatus } from '../claude/client.js';
|
||||||
|
import type { StreamCallback } from '../claude/process.js';
|
||||||
|
import { createLogger } from '../utils/debug.js';
|
||||||
|
|
||||||
|
const log = createLogger('codex-sdk');
|
||||||
|
|
||||||
|
/** Options for calling Codex */
|
||||||
|
export interface CodexCallOptions {
|
||||||
|
cwd: string;
|
||||||
|
sessionId?: string;
|
||||||
|
model?: string;
|
||||||
|
systemPrompt?: string;
|
||||||
|
statusPatterns?: Record<string, string>;
|
||||||
|
/** Enable streaming mode with callback (best-effort) */
|
||||||
|
onStream?: StreamCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractThreadId(value: unknown): string | undefined {
|
||||||
|
if (!value || typeof value !== 'object') return undefined;
|
||||||
|
const record = value as Record<string, unknown>;
|
||||||
|
const id = record.id ?? record.thread_id ?? record.threadId;
|
||||||
|
return typeof id === 'string' ? id : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeCodexResult(result: unknown): string {
|
||||||
|
if (result == null) return '';
|
||||||
|
if (typeof result === 'string') return result;
|
||||||
|
if (typeof result !== 'object') return String(result);
|
||||||
|
|
||||||
|
const record = result as Record<string, unknown>;
|
||||||
|
const directFields = ['output_text', 'output', 'content', 'text', 'message'];
|
||||||
|
for (const field of directFields) {
|
||||||
|
const value = record[field];
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(record.output)) {
|
||||||
|
const first = record.output[0];
|
||||||
|
if (typeof first === 'string') return first;
|
||||||
|
if (first && typeof first === 'object') {
|
||||||
|
const text = (first as Record<string, unknown>).text;
|
||||||
|
if (typeof text === 'string') return text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(record.choices)) {
|
||||||
|
const firstChoice = record.choices[0] as Record<string, unknown> | undefined;
|
||||||
|
const message = firstChoice?.message as Record<string, unknown> | undefined;
|
||||||
|
const content = message?.content;
|
||||||
|
if (typeof content === 'string') return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return JSON.stringify(result, null, 2);
|
||||||
|
} catch {
|
||||||
|
return String(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function emitInit(
|
||||||
|
onStream: StreamCallback | undefined,
|
||||||
|
model: string | undefined,
|
||||||
|
sessionId: string | undefined
|
||||||
|
): void {
|
||||||
|
if (!onStream) return;
|
||||||
|
onStream({
|
||||||
|
type: 'init',
|
||||||
|
data: {
|
||||||
|
model: model || 'codex',
|
||||||
|
sessionId: sessionId || 'unknown',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function emitText(onStream: StreamCallback | undefined, text: string): void {
|
||||||
|
if (!onStream || !text) return;
|
||||||
|
onStream({ type: 'text', data: { text } });
|
||||||
|
}
|
||||||
|
|
||||||
|
function emitResult(
|
||||||
|
onStream: StreamCallback | undefined,
|
||||||
|
success: boolean,
|
||||||
|
result: string,
|
||||||
|
sessionId: string | undefined
|
||||||
|
): void {
|
||||||
|
if (!onStream) return;
|
||||||
|
onStream({
|
||||||
|
type: 'result',
|
||||||
|
data: {
|
||||||
|
result,
|
||||||
|
sessionId: sessionId || 'unknown',
|
||||||
|
success,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function determineStatus(content: string, patterns: Record<string, string>): Status {
|
||||||
|
return detectStatus(content, patterns);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call Codex with an agent prompt.
|
||||||
|
*/
|
||||||
|
export async function callCodex(
|
||||||
|
agentType: string,
|
||||||
|
prompt: string,
|
||||||
|
options: CodexCallOptions
|
||||||
|
): Promise<AgentResponse> {
|
||||||
|
const codex = new Codex();
|
||||||
|
const thread = options.sessionId
|
||||||
|
? await codex.resumeThread(options.sessionId)
|
||||||
|
: await codex.startThread();
|
||||||
|
const threadId = extractThreadId(thread) || options.sessionId;
|
||||||
|
|
||||||
|
const fullPrompt = options.systemPrompt
|
||||||
|
? `${options.systemPrompt}\n\n${prompt}`
|
||||||
|
: prompt;
|
||||||
|
|
||||||
|
emitInit(options.onStream, options.model, threadId);
|
||||||
|
|
||||||
|
try {
|
||||||
|
log.debug('Executing Codex thread', {
|
||||||
|
agentType,
|
||||||
|
model: options.model,
|
||||||
|
hasSystemPrompt: !!options.systemPrompt,
|
||||||
|
});
|
||||||
|
|
||||||
|
const runOptions = options.model ? { model: options.model } : undefined;
|
||||||
|
const result = await (thread as { run: (p: string, o?: unknown) => Promise<unknown> })
|
||||||
|
.run(fullPrompt, runOptions);
|
||||||
|
|
||||||
|
const content = normalizeCodexResult(result).trim();
|
||||||
|
emitText(options.onStream, content);
|
||||||
|
emitResult(options.onStream, true, content, threadId);
|
||||||
|
|
||||||
|
const patterns = options.statusPatterns || GENERIC_STATUS_PATTERNS;
|
||||||
|
const status = determineStatus(content, patterns);
|
||||||
|
|
||||||
|
return {
|
||||||
|
agent: agentType,
|
||||||
|
status,
|
||||||
|
content,
|
||||||
|
timestamp: new Date(),
|
||||||
|
sessionId: threadId,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
|
emitResult(options.onStream, false, message, threadId);
|
||||||
|
|
||||||
|
return {
|
||||||
|
agent: agentType,
|
||||||
|
status: 'blocked',
|
||||||
|
content: message,
|
||||||
|
timestamp: new Date(),
|
||||||
|
sessionId: threadId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call Codex with a custom agent configuration (system prompt + prompt).
|
||||||
|
*/
|
||||||
|
export async function callCodexCustom(
|
||||||
|
agentName: string,
|
||||||
|
prompt: string,
|
||||||
|
systemPrompt: string,
|
||||||
|
options: CodexCallOptions
|
||||||
|
): Promise<AgentResponse> {
|
||||||
|
return callCodex(agentName, prompt, {
|
||||||
|
...options,
|
||||||
|
systemPrompt,
|
||||||
|
});
|
||||||
|
}
|
||||||
5
src/codex/index.ts
Normal file
5
src/codex/index.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
/**
|
||||||
|
* Codex integration exports
|
||||||
|
*/
|
||||||
|
|
||||||
|
export * from './client.js';
|
||||||
@ -29,6 +29,7 @@ export function loadGlobalConfig(): GlobalConfig {
|
|||||||
trustedDirectories: parsed.trusted_directories,
|
trustedDirectories: parsed.trusted_directories,
|
||||||
defaultWorkflow: parsed.default_workflow,
|
defaultWorkflow: parsed.default_workflow,
|
||||||
logLevel: parsed.log_level,
|
logLevel: parsed.log_level,
|
||||||
|
provider: parsed.provider,
|
||||||
debug: parsed.debug ? {
|
debug: parsed.debug ? {
|
||||||
enabled: parsed.debug.enabled,
|
enabled: parsed.debug.enabled,
|
||||||
logFile: parsed.debug.log_file,
|
logFile: parsed.debug.log_file,
|
||||||
@ -44,6 +45,7 @@ export function saveGlobalConfig(config: GlobalConfig): void {
|
|||||||
trusted_directories: config.trustedDirectories,
|
trusted_directories: config.trustedDirectories,
|
||||||
default_workflow: config.defaultWorkflow,
|
default_workflow: config.defaultWorkflow,
|
||||||
log_level: config.logLevel,
|
log_level: config.logLevel,
|
||||||
|
provider: config.provider,
|
||||||
};
|
};
|
||||||
if (config.debug) {
|
if (config.debug) {
|
||||||
raw.debug = {
|
raw.debug = {
|
||||||
@ -71,6 +73,13 @@ export function setLanguage(language: Language): void {
|
|||||||
saveGlobalConfig(config);
|
saveGlobalConfig(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Set provider setting */
|
||||||
|
export function setProvider(provider: 'claude' | 'codex'): void {
|
||||||
|
const config = loadGlobalConfig();
|
||||||
|
config.provider = provider;
|
||||||
|
saveGlobalConfig(config);
|
||||||
|
}
|
||||||
|
|
||||||
/** Add a trusted directory */
|
/** Add a trusted directory */
|
||||||
export function addTrustedDirectory(dir: string): void {
|
export function addTrustedDirectory(dir: string): void {
|
||||||
const config = loadGlobalConfig();
|
const config = loadGlobalConfig();
|
||||||
|
|||||||
@ -22,7 +22,7 @@ import {
|
|||||||
copyLanguageResourcesToDir,
|
copyLanguageResourcesToDir,
|
||||||
copyProjectResourcesToDir,
|
copyProjectResourcesToDir,
|
||||||
} from '../resources/index.js';
|
} from '../resources/index.js';
|
||||||
import { setLanguage } from './globalConfig.js';
|
import { setLanguage, setProvider } from './globalConfig.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if language-specific resources need to be initialized.
|
* Check if language-specific resources need to be initialized.
|
||||||
@ -51,6 +51,22 @@ export async function promptLanguageSelection(): Promise<Language> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prompt user to select provider for resources.
|
||||||
|
*/
|
||||||
|
export async function promptProviderSelection(): Promise<'claude' | 'codex'> {
|
||||||
|
const options: { label: string; value: 'claude' | 'codex' }[] = [
|
||||||
|
{ label: 'Claude Code', value: 'claude' },
|
||||||
|
{ label: 'Codex', value: 'codex' },
|
||||||
|
];
|
||||||
|
|
||||||
|
return await selectOptionWithDefault(
|
||||||
|
'Select provider (Claude Code or Codex) / プロバイダーを選択してください:',
|
||||||
|
options,
|
||||||
|
'claude'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize global takt directory structure with language selection.
|
* Initialize global takt directory structure with language selection.
|
||||||
* If agents/workflows don't exist, prompts user for language preference.
|
* If agents/workflows don't exist, prompts user for language preference.
|
||||||
@ -65,12 +81,14 @@ export async function initGlobalDirs(): Promise<void> {
|
|||||||
if (needsSetup) {
|
if (needsSetup) {
|
||||||
// Ask user for language preference
|
// Ask user for language preference
|
||||||
const lang = await promptLanguageSelection();
|
const lang = await promptLanguageSelection();
|
||||||
|
const provider = await promptProviderSelection();
|
||||||
|
|
||||||
// Copy language-specific resources (agents, workflows, config.yaml)
|
// Copy language-specific resources (agents, workflows, config.yaml)
|
||||||
copyLanguageResourcesToDir(getGlobalConfigDir(), lang);
|
copyLanguageResourcesToDir(getGlobalConfigDir(), lang);
|
||||||
|
|
||||||
// Explicitly save the selected language (handles case where config.yaml existed)
|
// Explicitly save the selected language (handles case where config.yaml existed)
|
||||||
setLanguage(lang);
|
setLanguage(lang);
|
||||||
|
setProvider(provider);
|
||||||
} else {
|
} else {
|
||||||
// Just copy base global resources (won't overwrite existing)
|
// Just copy base global resources (won't overwrite existing)
|
||||||
copyGlobalResourcesToDir(getGlobalConfigDir());
|
copyGlobalResourcesToDir(getGlobalConfigDir());
|
||||||
|
|||||||
@ -24,6 +24,8 @@ export type ProjectPermissionMode = PermissionMode;
|
|||||||
export interface ProjectLocalConfig {
|
export interface ProjectLocalConfig {
|
||||||
/** Current workflow name */
|
/** Current workflow name */
|
||||||
workflow?: string;
|
workflow?: string;
|
||||||
|
/** Provider selection for agent runtime */
|
||||||
|
provider?: 'claude' | 'codex';
|
||||||
/** Permission mode setting */
|
/** Permission mode setting */
|
||||||
permissionMode?: PermissionMode;
|
permissionMode?: PermissionMode;
|
||||||
/** @deprecated Use permissionMode instead. Auto-approve all permissions in this project */
|
/** @deprecated Use permissionMode instead. Auto-approve all permissions in this project */
|
||||||
|
|||||||
@ -66,6 +66,7 @@ function normalizeWorkflowConfig(raw: unknown, workflowDir: string): WorkflowCon
|
|||||||
agent: step.agent,
|
agent: step.agent,
|
||||||
agentDisplayName: step.agent_name || extractAgentDisplayName(step.agent),
|
agentDisplayName: step.agent_name || extractAgentDisplayName(step.agent),
|
||||||
agentPath: resolveAgentPathForWorkflow(step.agent, workflowDir),
|
agentPath: resolveAgentPathForWorkflow(step.agent, workflowDir),
|
||||||
|
provider: step.provider,
|
||||||
instructionTemplate: step.instruction_template || step.instruction || '{task}',
|
instructionTemplate: step.instruction_template || step.instruction || '{task}',
|
||||||
transitions: step.transitions.map((t) => ({
|
transitions: step.transitions.map((t) => ({
|
||||||
condition: t.condition,
|
condition: t.condition,
|
||||||
|
|||||||
@ -13,6 +13,9 @@ export * from './config/index.js';
|
|||||||
// Claude integration
|
// Claude integration
|
||||||
export * from './claude/index.js';
|
export * from './claude/index.js';
|
||||||
|
|
||||||
|
// Codex integration
|
||||||
|
export * from './codex/index.js';
|
||||||
|
|
||||||
// Agent execution
|
// Agent execution
|
||||||
export * from './agents/index.js';
|
export * from './agents/index.js';
|
||||||
|
|
||||||
|
|||||||
@ -59,6 +59,7 @@ export const WorkflowStepRawSchema = z.object({
|
|||||||
agent: z.string().min(1),
|
agent: z.string().min(1),
|
||||||
/** Display name for the agent (shown in output). Falls back to agent basename if not specified */
|
/** Display name for the agent (shown in output). Falls back to agent basename if not specified */
|
||||||
agent_name: z.string().optional(),
|
agent_name: z.string().optional(),
|
||||||
|
provider: z.enum(['claude', 'codex']).optional(),
|
||||||
instruction: z.string().optional(),
|
instruction: z.string().optional(),
|
||||||
instruction_template: z.string().optional(),
|
instruction_template: z.string().optional(),
|
||||||
pass_previous_response: z.boolean().optional().default(true),
|
pass_previous_response: z.boolean().optional().default(true),
|
||||||
@ -90,6 +91,7 @@ export const CustomAgentConfigSchema = z.object({
|
|||||||
status_patterns: z.record(z.string(), z.string()).optional(),
|
status_patterns: z.record(z.string(), z.string()).optional(),
|
||||||
claude_agent: z.string().optional(),
|
claude_agent: z.string().optional(),
|
||||||
claude_skill: z.string().optional(),
|
claude_skill: z.string().optional(),
|
||||||
|
provider: z.enum(['claude', 'codex']).optional(),
|
||||||
model: z.string().optional(),
|
model: z.string().optional(),
|
||||||
}).refine(
|
}).refine(
|
||||||
(data) => data.prompt_file || data.prompt || data.claude_agent || data.claude_skill,
|
(data) => data.prompt_file || data.prompt || data.claude_agent || data.claude_skill,
|
||||||
@ -111,6 +113,7 @@ export const GlobalConfigSchema = z.object({
|
|||||||
trusted_directories: z.array(z.string()).optional().default([]),
|
trusted_directories: z.array(z.string()).optional().default([]),
|
||||||
default_workflow: z.string().optional().default('default'),
|
default_workflow: z.string().optional().default('default'),
|
||||||
log_level: z.enum(['debug', 'info', 'warn', 'error']).optional().default('info'),
|
log_level: z.enum(['debug', 'info', 'warn', 'error']).optional().default('info'),
|
||||||
|
provider: z.enum(['claude', 'codex']).optional().default('claude'),
|
||||||
debug: DebugConfigSchema.optional(),
|
debug: DebugConfigSchema.optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -118,6 +121,7 @@ export const GlobalConfigSchema = z.object({
|
|||||||
export const ProjectConfigSchema = z.object({
|
export const ProjectConfigSchema = z.object({
|
||||||
workflow: z.string().optional(),
|
workflow: z.string().optional(),
|
||||||
agents: z.array(CustomAgentConfigSchema).optional(),
|
agents: z.array(CustomAgentConfigSchema).optional(),
|
||||||
|
provider: z.enum(['claude', 'codex']).optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -134,5 +138,3 @@ export const GENERIC_STATUS_PATTERNS: Record<string, string> = {
|
|||||||
done: '\\[\\w+:(DONE|FIXED)\\]',
|
done: '\\[\\w+:(DONE|FIXED)\\]',
|
||||||
blocked: '\\[\\w+:BLOCKED\\]',
|
blocked: '\\[\\w+:BLOCKED\\]',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -62,6 +62,8 @@ export interface WorkflowStep {
|
|||||||
agentDisplayName: string;
|
agentDisplayName: string;
|
||||||
/** Resolved absolute path to agent prompt file (set by loader) */
|
/** Resolved absolute path to agent prompt file (set by loader) */
|
||||||
agentPath?: string;
|
agentPath?: string;
|
||||||
|
/** Provider override for this step */
|
||||||
|
provider?: 'claude' | 'codex';
|
||||||
instructionTemplate: string;
|
instructionTemplate: string;
|
||||||
transitions: WorkflowTransition[];
|
transitions: WorkflowTransition[];
|
||||||
passPreviousResponse: boolean;
|
passPreviousResponse: boolean;
|
||||||
@ -119,6 +121,7 @@ export interface CustomAgentConfig {
|
|||||||
statusPatterns?: Record<string, string>;
|
statusPatterns?: Record<string, string>;
|
||||||
claudeAgent?: string;
|
claudeAgent?: string;
|
||||||
claudeSkill?: string;
|
claudeSkill?: string;
|
||||||
|
provider?: 'claude' | 'codex';
|
||||||
model?: string;
|
model?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,6 +140,7 @@ export interface GlobalConfig {
|
|||||||
trustedDirectories: string[];
|
trustedDirectories: string[];
|
||||||
defaultWorkflow: string;
|
defaultWorkflow: string;
|
||||||
logLevel: 'debug' | 'info' | 'warn' | 'error';
|
logLevel: 'debug' | 'info' | 'warn' | 'error';
|
||||||
|
provider?: 'claude' | 'codex';
|
||||||
debug?: DebugConfig;
|
debug?: DebugConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,4 +148,5 @@ export interface GlobalConfig {
|
|||||||
export interface ProjectConfig {
|
export interface ProjectConfig {
|
||||||
workflow?: string;
|
workflow?: string;
|
||||||
agents?: CustomAgentConfig[];
|
agents?: CustomAgentConfig[];
|
||||||
|
provider?: 'claude' | 'codex';
|
||||||
}
|
}
|
||||||
|
|||||||
@ -147,6 +147,7 @@ export class WorkflowEngine extends EventEmitter {
|
|||||||
cwd: this.cwd,
|
cwd: this.cwd,
|
||||||
sessionId,
|
sessionId,
|
||||||
agentPath: step.agentPath,
|
agentPath: step.agentPath,
|
||||||
|
provider: step.provider,
|
||||||
onStream: this.options.onStream,
|
onStream: this.options.onStream,
|
||||||
onPermissionRequest: this.options.onPermissionRequest,
|
onPermissionRequest: this.options.onPermissionRequest,
|
||||||
onAskUserQuestion: this.options.onAskUserQuestion,
|
onAskUserQuestion: this.options.onAskUserQuestion,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user