loreli 0.0.0 → 2.0.0
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/LICENSE +1 -1
- package/README.md +710 -97
- package/bin/loreli.js +89 -0
- package/package.json +77 -14
- package/packages/README.md +101 -0
- package/packages/action/README.md +98 -0
- package/packages/action/prompts/action.md +172 -0
- package/packages/action/src/index.js +684 -0
- package/packages/agent/README.md +606 -0
- package/packages/agent/src/backends/claude.js +387 -0
- package/packages/agent/src/backends/codex.js +351 -0
- package/packages/agent/src/backends/cursor.js +371 -0
- package/packages/agent/src/backends/index.js +486 -0
- package/packages/agent/src/base.js +138 -0
- package/packages/agent/src/cli.js +275 -0
- package/packages/agent/src/discover.js +396 -0
- package/packages/agent/src/factory.js +124 -0
- package/packages/agent/src/index.js +12 -0
- package/packages/agent/src/models.js +159 -0
- package/packages/agent/src/output.js +62 -0
- package/packages/agent/src/session.js +162 -0
- package/packages/agent/src/trace.js +186 -0
- package/packages/classify/README.md +136 -0
- package/packages/classify/prompts/blocker.md +12 -0
- package/packages/classify/prompts/feedback.md +14 -0
- package/packages/classify/prompts/pane-state.md +20 -0
- package/packages/classify/src/index.js +81 -0
- package/packages/config/README.md +898 -0
- package/packages/config/src/defaults.js +145 -0
- package/packages/config/src/index.js +223 -0
- package/packages/config/src/schema.js +291 -0
- package/packages/config/src/validate.js +160 -0
- package/packages/context/README.md +165 -0
- package/packages/context/src/index.js +198 -0
- package/packages/hub/README.md +338 -0
- package/packages/hub/src/base.js +154 -0
- package/packages/hub/src/github.js +1597 -0
- package/packages/hub/src/index.js +79 -0
- package/packages/hub/src/labels.js +48 -0
- package/packages/identity/README.md +288 -0
- package/packages/identity/src/index.js +620 -0
- package/packages/identity/src/themes/avatar.js +217 -0
- package/packages/identity/src/themes/digimon.js +217 -0
- package/packages/identity/src/themes/dragonball.js +217 -0
- package/packages/identity/src/themes/lotr.js +217 -0
- package/packages/identity/src/themes/marvel.js +217 -0
- package/packages/identity/src/themes/pokemon.js +217 -0
- package/packages/identity/src/themes/starwars.js +217 -0
- package/packages/identity/src/themes/transformers.js +217 -0
- package/packages/identity/src/themes/zelda.js +217 -0
- package/packages/knowledge/README.md +217 -0
- package/packages/knowledge/src/index.js +243 -0
- package/packages/log/README.md +93 -0
- package/packages/log/src/index.js +252 -0
- package/packages/marker/README.md +200 -0
- package/packages/marker/src/index.js +184 -0
- package/packages/mcp/README.md +323 -0
- package/packages/mcp/instructions.md +126 -0
- package/packages/mcp/scaffolding/.agents/skills/loreli-context/SKILL.md +89 -0
- package/packages/mcp/scaffolding/ISSUE_TEMPLATE/config.yml +2 -0
- package/packages/mcp/scaffolding/ISSUE_TEMPLATE/loreli.yml +83 -0
- package/packages/mcp/scaffolding/loreli.yml +491 -0
- package/packages/mcp/scaffolding/mcp-configs/.codex/config.toml +4 -0
- package/packages/mcp/scaffolding/mcp-configs/.cursor/mcp.json +14 -0
- package/packages/mcp/scaffolding/mcp-configs/.mcp.json +14 -0
- package/packages/mcp/scaffolding/pull-request.md +23 -0
- package/packages/mcp/src/index.js +600 -0
- package/packages/mcp/src/tools/agent-context.js +44 -0
- package/packages/mcp/src/tools/agents.js +450 -0
- package/packages/mcp/src/tools/context.js +200 -0
- package/packages/mcp/src/tools/github.js +1163 -0
- package/packages/mcp/src/tools/hitl.js +162 -0
- package/packages/mcp/src/tools/index.js +18 -0
- package/packages/mcp/src/tools/refactor.js +227 -0
- package/packages/mcp/src/tools/repo.js +44 -0
- package/packages/mcp/src/tools/start.js +904 -0
- package/packages/mcp/src/tools/status.js +149 -0
- package/packages/mcp/src/tools/work.js +134 -0
- package/packages/orchestrator/README.md +192 -0
- package/packages/orchestrator/src/index.js +1492 -0
- package/packages/planner/README.md +251 -0
- package/packages/planner/prompts/plan-reviewer.md +109 -0
- package/packages/planner/prompts/planner.md +191 -0
- package/packages/planner/prompts/tiebreaker-reviewer.md +71 -0
- package/packages/planner/src/index.js +1381 -0
- package/packages/review/README.md +129 -0
- package/packages/review/prompts/reviewer.md +158 -0
- package/packages/review/src/index.js +1403 -0
- package/packages/risk/README.md +178 -0
- package/packages/risk/prompts/risk.md +272 -0
- package/packages/risk/src/index.js +439 -0
- package/packages/session/README.md +165 -0
- package/packages/session/src/index.js +215 -0
- package/packages/test-utils/README.md +96 -0
- package/packages/test-utils/src/index.js +354 -0
- package/packages/tmux/README.md +261 -0
- package/packages/tmux/src/index.js +501 -0
- package/packages/workflow/README.md +317 -0
- package/packages/workflow/prompts/preamble.md +14 -0
- package/packages/workflow/src/index.js +660 -0
- package/packages/workflow/src/proof-of-life.js +74 -0
- package/packages/workspace/README.md +143 -0
- package/packages/workspace/src/index.js +1127 -0
- package/index.js +0 -8
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
import { writeFile, unlink, mkdir } from 'node:fs/promises';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { CliAgent } from '../cli.js';
|
|
5
|
+
import { resolve, env } from '../models.js';
|
|
6
|
+
import { codexToml, mcpJson } from 'loreli/workspace';
|
|
7
|
+
import { logger } from 'loreli/log';
|
|
8
|
+
|
|
9
|
+
const log = logger('codex');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Interval for polling pane content during readiness detection.
|
|
13
|
+
* @type {number}
|
|
14
|
+
*/
|
|
15
|
+
const READY_POLL = 1000;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Maximum time to wait for the Codex CLI to become ready.
|
|
19
|
+
* @type {number}
|
|
20
|
+
*/
|
|
21
|
+
const READY_TIMEOUT = 60000;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* OpenAI Codex CLI backend. Interactive: stays running in a tmux pane.
|
|
25
|
+
*
|
|
26
|
+
* Uses `-a never -s workspace-write` for sandboxed unattended execution and
|
|
27
|
+
* `--no-alt-screen` so tmux can capture pane output (the TUI's
|
|
28
|
+
* alternate screen buffer is invisible to `capture-pane`).
|
|
29
|
+
*
|
|
30
|
+
* MCP servers are injected via `-c` flags at CLI level because
|
|
31
|
+
* Codex only reads `~/.codex/config.toml` (global), not the local
|
|
32
|
+
* `.codex/config.toml` in the working directory.
|
|
33
|
+
*
|
|
34
|
+
* After the CLI is ready (welcome banner visible), prompts are
|
|
35
|
+
* delivered via file-based send (multi-line) or tmux send-keys
|
|
36
|
+
* (single-line).
|
|
37
|
+
*
|
|
38
|
+
* @extends CliAgent
|
|
39
|
+
*/
|
|
40
|
+
export class CodexBackend extends CliAgent {
|
|
41
|
+
/**
|
|
42
|
+
* Regex patterns that identify known Codex CLI blocking dialogs.
|
|
43
|
+
* Matched against pane output by diagnose() for rapid-death
|
|
44
|
+
* remediation and stall monitor fallback.
|
|
45
|
+
*
|
|
46
|
+
* Each entry carries a `remedy` — an array of tmux key names that
|
|
47
|
+
* the orchestrator sends to dismiss the dialog. Patterns are
|
|
48
|
+
* checked in order; first match wins.
|
|
49
|
+
*
|
|
50
|
+
* @type {Array<{pattern: RegExp, category: string, reasoning: string, remedy?: string[]}>}
|
|
51
|
+
*/
|
|
52
|
+
static patterns = [
|
|
53
|
+
{
|
|
54
|
+
pattern: /choose how you'd like.*to proceed/is,
|
|
55
|
+
category: 'option_dialog',
|
|
56
|
+
reasoning: 'Codex CLI showing model upgrade selection — selecting "Use existing model"',
|
|
57
|
+
remedy: ['Down', 'Enter']
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
pattern: /invalid model name/i,
|
|
61
|
+
category: 'fatal',
|
|
62
|
+
reasoning: 'Codex CLI received invalid model name — API key may not support the requested model'
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
pattern: /update available!.*\n.*press enter to continue/is,
|
|
66
|
+
category: 'option_dialog',
|
|
67
|
+
reasoning: 'Codex CLI showing update-available dialog requiring Enter to dismiss',
|
|
68
|
+
remedy: ['Enter']
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
pattern: /press enter to continue/i,
|
|
72
|
+
category: 'option_dialog',
|
|
73
|
+
reasoning: 'Codex CLI waiting for Enter keypress to continue',
|
|
74
|
+
remedy: ['Enter']
|
|
75
|
+
}
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Detect known Codex CLI blocking patterns in pane output.
|
|
80
|
+
*
|
|
81
|
+
* @param {string} output - Captured pane content.
|
|
82
|
+
* @returns {{category: string, reasoning: string, remedy?: string[]}|null} Diagnosis, or null if unrecognized.
|
|
83
|
+
*/
|
|
84
|
+
static diagnose(output) {
|
|
85
|
+
if (!output) return null;
|
|
86
|
+
for (const { pattern, category, reasoning, remedy } of CodexBackend.patterns) {
|
|
87
|
+
if (pattern.test(output)) return { category, reasoning, ...(remedy && { remedy }) };
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Return the scaffold descriptor for Codex workspaces.
|
|
94
|
+
*
|
|
95
|
+
* Codex uses TOML config with `env_vars` forwarding for
|
|
96
|
+
* GITHUB_TOKEN — whitelists the parent process env var for
|
|
97
|
+
* forwarding to MCP subprocesses.
|
|
98
|
+
*
|
|
99
|
+
* @param {object} [context] - Agent context for env/token injection.
|
|
100
|
+
* @returns {object} Scaffold descriptor.
|
|
101
|
+
*/
|
|
102
|
+
static scaffold(context) {
|
|
103
|
+
return {
|
|
104
|
+
configs: [
|
|
105
|
+
{
|
|
106
|
+
path: '.mcp.json',
|
|
107
|
+
content: mcpJson(context, context ? { tokenRef: '${GITHUB_TOKEN}' } : {}),
|
|
108
|
+
marker: 'loreli',
|
|
109
|
+
format: 'json'
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
path: '.codex/config.toml',
|
|
113
|
+
content: codexToml(context),
|
|
114
|
+
marker: 'mcp_servers.loreli',
|
|
115
|
+
format: 'toml'
|
|
116
|
+
}
|
|
117
|
+
],
|
|
118
|
+
hooks: [],
|
|
119
|
+
files: []
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* One-shot LLM call via `codex exec` (non-interactive mode).
|
|
124
|
+
*
|
|
125
|
+
* @param {string} prompt - Text prompt to send.
|
|
126
|
+
* @param {object} [opts] - Options.
|
|
127
|
+
* @param {string} [opts.model='fast'] - Model alias or exact string.
|
|
128
|
+
* @param {object} [opts.config] - Config instance for model resolution.
|
|
129
|
+
* @param {number} [opts.timeout=60000] - Max execution time in ms.
|
|
130
|
+
* @returns {Promise<string>} LLM response text.
|
|
131
|
+
*/
|
|
132
|
+
static async oneshot(prompt, { model, config, timeout, discovered } = {}) {
|
|
133
|
+
const resolved = resolve(model ?? 'fast', 'codex', 'openai', config, discovered);
|
|
134
|
+
const vars = env('codex', config) ?? {};
|
|
135
|
+
return CliAgent._exec('codex', [
|
|
136
|
+
'exec', '--ephemeral', '--skip-git-repo-check', prompt, '-m', resolved
|
|
137
|
+
], vars, timeout);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* @param {object} opts
|
|
142
|
+
* @param {object} opts.identity - Agent identity.
|
|
143
|
+
* @param {string} opts.role - Agent role.
|
|
144
|
+
* @param {string} opts.cwd - Working directory.
|
|
145
|
+
* @param {string} [opts.model='balanced'] - Model alias or exact string.
|
|
146
|
+
* @param {object} [opts.config] - Config instance for model resolution.
|
|
147
|
+
* @param {string} [opts.session='loreli'] - Tmux session name.
|
|
148
|
+
* @param {number} [opts.readyTimeout=60000] - Max ms to wait for CLI readiness.
|
|
149
|
+
*/
|
|
150
|
+
constructor(opts) {
|
|
151
|
+
const model = resolve(opts.model ?? 'balanced', 'codex', 'openai', opts.config, opts.discovered);
|
|
152
|
+
|
|
153
|
+
const denied = opts.config?.get?.('agents.disallowedTools') ?? [];
|
|
154
|
+
const mode = opts.role === 'planner' ? 'plan' : undefined;
|
|
155
|
+
|
|
156
|
+
super({
|
|
157
|
+
...opts,
|
|
158
|
+
command: buildCommand(model, opts.cwd, opts.context, denied, mode)
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
/** @type {string} Resolved model identifier. */
|
|
162
|
+
this.model = model;
|
|
163
|
+
|
|
164
|
+
/** @type {object|undefined} Backend-specific environment variables. */
|
|
165
|
+
this._env = env('codex', opts.config) ?? {};
|
|
166
|
+
|
|
167
|
+
if (opts.context?.token) this._env.GITHUB_TOKEN = opts.context.token;
|
|
168
|
+
|
|
169
|
+
/** @type {number} Max ms to wait for CLI readiness. */
|
|
170
|
+
this.readyTimeout = opts.readyTimeout ?? READY_TIMEOUT;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Spawn the Codex CLI and wait for readiness.
|
|
175
|
+
*
|
|
176
|
+
* Builds an inline shell command with env exports and passes it
|
|
177
|
+
* directly to tmux — no launcher scripts on disk.
|
|
178
|
+
*
|
|
179
|
+
* @returns {Promise<void>}
|
|
180
|
+
*/
|
|
181
|
+
async spawn() {
|
|
182
|
+
const cmd = this._shell(`exec ${this.command}`, this._env);
|
|
183
|
+
this.paneId = await this._launch(cmd);
|
|
184
|
+
|
|
185
|
+
log.info(`spawning ${this.identity?.name ?? '?'} in pane ${this.paneId}: ${this.command}`);
|
|
186
|
+
this.transition('spawned');
|
|
187
|
+
|
|
188
|
+
await this._navigate();
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Wait for Codex CLI to finish initializing, including MCP tool discovery.
|
|
193
|
+
*
|
|
194
|
+
* Two-phase readiness check:
|
|
195
|
+
* 1. Wait for the welcome banner (`OpenAI Codex`) in pane output
|
|
196
|
+
* 2. Wait for the MCP server to write a `.loreli/mcp-ready` marker file
|
|
197
|
+
*
|
|
198
|
+
* Phase 2 prevents a race condition where the prompt is sent before
|
|
199
|
+
* Codex finishes its MCP handshake, causing the agent to work
|
|
200
|
+
* without access to MCP tools.
|
|
201
|
+
*
|
|
202
|
+
* @returns {Promise<void>}
|
|
203
|
+
*/
|
|
204
|
+
async _navigate() {
|
|
205
|
+
if (this.readyTimeout <= 0) return;
|
|
206
|
+
|
|
207
|
+
const deadline = Date.now() + this.readyTimeout;
|
|
208
|
+
const name = this.identity?.name ?? '?';
|
|
209
|
+
let banner = false;
|
|
210
|
+
|
|
211
|
+
while (Date.now() < deadline) {
|
|
212
|
+
let output;
|
|
213
|
+
try {
|
|
214
|
+
output = await this.tmux.capture(this.paneId);
|
|
215
|
+
} catch {
|
|
216
|
+
log.warn(`${name} pane died during readiness check — proceeding`);
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (!banner && output.includes('OpenAI Codex')) {
|
|
221
|
+
log.info(`${name} ready — OpenAI Codex welcome banner detected`);
|
|
222
|
+
banner = true;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (banner) {
|
|
226
|
+
const ready = join(this.cwd, '.loreli', 'mcp-ready');
|
|
227
|
+
if (existsSync(ready)) {
|
|
228
|
+
log.info(`${name} MCP tools ready`);
|
|
229
|
+
try { await unlink(ready); } catch { /* already cleaned */ }
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
await new Promise(function wait(r) { setTimeout(r, READY_POLL); });
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
log.warn(`${name} readiness timeout after ${this.readyTimeout}ms — proceeding anyway`);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Send a prompt to Codex's interactive session.
|
|
242
|
+
*
|
|
243
|
+
* Multi-line prompts are written to a Markdown file and a single-line
|
|
244
|
+
* reference is sent via tmux send-keys. This avoids newline bytes
|
|
245
|
+
* being interpreted as Enter and fragmenting the prompt.
|
|
246
|
+
*
|
|
247
|
+
* Single-line messages are sent directly via send-keys.
|
|
248
|
+
*
|
|
249
|
+
* @param {string} message - The prompt text.
|
|
250
|
+
* @returns {Promise<void>}
|
|
251
|
+
*/
|
|
252
|
+
async send(message) {
|
|
253
|
+
if (!this.paneId) throw new Error('Agent not spawned — call spawn() first');
|
|
254
|
+
if (this.canTransition('working')) this.transition('working');
|
|
255
|
+
|
|
256
|
+
if (!message.includes('\n')) {
|
|
257
|
+
await this.tmux.send(this.paneId, message);
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const dir = join(this.cwd, '.loreli');
|
|
262
|
+
await mkdir(dir, { recursive: true });
|
|
263
|
+
const file = join(dir, `task-${Date.now()}.md`);
|
|
264
|
+
await writeFile(file, message, 'utf8');
|
|
265
|
+
log.info(`${this.identity?.name ?? '?'} prompt written to ${file}`);
|
|
266
|
+
|
|
267
|
+
await this.tmux.send(this.paneId,
|
|
268
|
+
`Read the complete task instructions from ${file} and execute them. Follow every instruction precisely.`
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Build `-c` flags for injecting loreli MCP server config into Codex CLI.
|
|
275
|
+
*
|
|
276
|
+
* Codex only reads `~/.codex/config.toml` (global), not the local
|
|
277
|
+
* `.codex/config.toml` in the working directory. The `-c` flag is
|
|
278
|
+
* the only reliable way to add MCP servers at runtime. When context
|
|
279
|
+
* is provided, LORELI_* env vars are also injected so the MCP server
|
|
280
|
+
* subprocess can hydrate session state on startup.
|
|
281
|
+
*
|
|
282
|
+
* @param {object} [context] - Agent context for env injection.
|
|
283
|
+
* @param {string} [context.session] - Session ID.
|
|
284
|
+
* @param {string} [context.agent] - Agent identity name.
|
|
285
|
+
* @param {string} [context.repo] - Target repository (owner/name).
|
|
286
|
+
* @param {string} [context.token] - GitHub token (forwarded via env_vars).
|
|
287
|
+
* @returns {string} `-c` flag string (with leading space) or empty string.
|
|
288
|
+
*/
|
|
289
|
+
function mcpFlags(context) {
|
|
290
|
+
const flags = [
|
|
291
|
+
`-c 'mcp_servers.loreli.command="npx"'`,
|
|
292
|
+
`-c 'mcp_servers.loreli.args=["loreli", "mcp"]'`
|
|
293
|
+
];
|
|
294
|
+
|
|
295
|
+
if (context?.session) {
|
|
296
|
+
flags.push(`-c 'mcp_servers.loreli.env.LORELI_SESSION="${context.session}"'`);
|
|
297
|
+
flags.push(`-c 'mcp_servers.loreli.env.LORELI_AGENT="${context.agent}"'`);
|
|
298
|
+
flags.push(`-c 'mcp_servers.loreli.env.LORELI_REPO="${context.repo}"'`);
|
|
299
|
+
if (context.home) flags.push(`-c 'mcp_servers.loreli.env.LORELI_HOME="${context.home}"'`);
|
|
300
|
+
// Forward token from parent process env without embedding the
|
|
301
|
+
// literal secret in CLI flags, logs, or launcher command strings.
|
|
302
|
+
if (context.token) flags.push(`-c 'mcp_servers.loreli.env_vars=["GITHUB_TOKEN"]'`);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return ' ' + flags.join(' ');
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Build `-c` flags for injecting `rules.prefix_rules` into Codex CLI.
|
|
310
|
+
*
|
|
311
|
+
* Codex supports `rules.prefix_rules` in its `config.toml` to forbid
|
|
312
|
+
* commands by token pattern. Since Codex has no hooks system, this is
|
|
313
|
+
* the only way to block tool execution at the CLI level.
|
|
314
|
+
*
|
|
315
|
+
* @param {string[]} denied - Commands to block.
|
|
316
|
+
* @returns {string} `-c` flag string (with leading space) or empty string.
|
|
317
|
+
*/
|
|
318
|
+
function rulesFlags(denied) {
|
|
319
|
+
if (!denied?.length) return '';
|
|
320
|
+
const rules = denied.map(function toRule(cmd) {
|
|
321
|
+
return `{pattern=[{token="${cmd}"}], decision="forbidden", justification="Use Loreli MCP tools instead of ${cmd}"}`;
|
|
322
|
+
});
|
|
323
|
+
return ` -c 'rules.prefix_rules=[${rules.join(', ')}]'`;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Build the codex CLI command string.
|
|
328
|
+
*
|
|
329
|
+
* Flags:
|
|
330
|
+
* - `-a never`: disable approval prompts for unattended operation.
|
|
331
|
+
* `--full-auto` maps to `-a on-request` which still allows the
|
|
332
|
+
* model to trigger interactive approval prompts, blocking the
|
|
333
|
+
* agent in tmux with no human to respond.
|
|
334
|
+
* - `-s read-only`: read-only sandbox (planner)
|
|
335
|
+
* - `-s workspace-write`: sandbox to workspace write access (action/reviewer)
|
|
336
|
+
* - `--no-alt-screen`: inline TUI mode for tmux capture compatibility
|
|
337
|
+
* - `-C ${cwd}`: set the working directory for codex
|
|
338
|
+
* - `-c mcp_servers.loreli.*`: inject loreli MCP server config
|
|
339
|
+
* - `-c rules.prefix_rules`: inject command deny rules
|
|
340
|
+
*
|
|
341
|
+
* @param {string} model - Resolved model identifier.
|
|
342
|
+
* @param {string} cwd - Working directory.
|
|
343
|
+
* @param {object} [context] - Agent context for MCP env injection.
|
|
344
|
+
* @param {string[]} [denied=[]] - Commands to block via prefix rules.
|
|
345
|
+
* @param {string} [mode] - Execution mode ('plan' for planner read-only mode).
|
|
346
|
+
* @returns {string} CLI command string.
|
|
347
|
+
*/
|
|
348
|
+
function buildCommand(model, cwd, context, denied = [], mode) {
|
|
349
|
+
const sandbox = mode === 'plan' ? 'read-only' : 'workspace-write';
|
|
350
|
+
return `codex --model ${model} -a never -s ${sandbox} --no-alt-screen -C '${cwd}'${mcpFlags(context)}${rulesFlags(denied)}`;
|
|
351
|
+
}
|