-
-
Notifications
You must be signed in to change notification settings - Fork 432
Expand file tree
/
Copy pathcodexLocal.ts
More file actions
103 lines (88 loc) · 3.17 KB
/
codexLocal.ts
File metadata and controls
103 lines (88 loc) · 3.17 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
import { logger } from '@/ui/logger';
import { spawnWithTerminalGuard } from '@/utils/spawnWithTerminalGuard';
import {
buildMcpServerConfigArgs,
buildDeveloperInstructionsArg,
buildSessionStartHookConfigArgs
} from './utils/codexMcpConfig';
import { codexSystemPrompt } from './utils/systemPrompt';
import type { ReasoningEffort } from './appServerTypes';
/**
* Filter out HAPI-managed session subcommands which are handled internally.
* Codex CLI format is `codex <subcommand> <session-id>`, so the subcommand is always first.
*/
export function filterManagedSessionSubcommand(args: string[]): string[] {
if (args.length === 0 || (args[0] !== 'resume' && args[0] !== 'fork')) {
return args;
}
// First arg is 'resume' or 'fork'; filter it and optional session ID
if (args.length > 1 && !args[1].startsWith('-')) {
logger.debug(`[CodexLocal] Filtered '${args[0]} ${args[1]}' - session managed by hapi`);
return args.slice(2);
}
logger.debug(`[CodexLocal] Filtered '${args[0]}' - session managed by hapi`);
return args.slice(1);
}
export async function codexLocal(opts: {
abort: AbortSignal;
resumeSessionId: string | null;
forkSessionId?: string;
path: string;
model?: string;
modelReasoningEffort?: ReasoningEffort;
sandbox?: 'read-only' | 'workspace-write' | 'danger-full-access';
onSessionFound: (id: string) => void;
codexArgs?: string[];
mcpServers?: Record<string, { command: string; args: string[] }>;
sessionHook?: {
port: number;
token: string;
};
}): Promise<void> {
const args: string[] = [];
if (opts.forkSessionId) {
args.push('fork', opts.forkSessionId);
} else if (opts.resumeSessionId) {
args.push('resume', opts.resumeSessionId);
opts.onSessionFound(opts.resumeSessionId);
}
if (opts.model) {
args.push('--model', opts.model);
}
if (opts.modelReasoningEffort) {
args.push('--model-reasoning-effort', opts.modelReasoningEffort);
}
if (opts.sandbox) {
args.push('--sandbox', opts.sandbox);
}
// Add MCP server configuration
if (opts.mcpServers && Object.keys(opts.mcpServers).length > 0) {
args.push(...buildMcpServerConfigArgs(opts.mcpServers));
}
if (opts.sessionHook) {
args.push(...buildSessionStartHookConfigArgs(opts.sessionHook.port, opts.sessionHook.token));
}
// Add developer instructions (system prompt)
args.push(...buildDeveloperInstructionsArg(codexSystemPrompt));
if (opts.codexArgs) {
const safeArgs = filterManagedSessionSubcommand(opts.codexArgs);
args.push(...safeArgs);
}
logger.debug(`[CodexLocal] Spawning codex with args: ${JSON.stringify(args)}`);
if (opts.abort.aborted) {
logger.debug('[CodexLocal] Abort already signaled before spawn; skipping launch');
return;
}
await spawnWithTerminalGuard({
command: 'codex',
args,
cwd: opts.path,
env: process.env,
signal: opts.abort,
logLabel: 'CodexLocal',
spawnName: 'codex',
installHint: 'Codex CLI',
includeCause: true,
logExit: true
});
}