akemon 0.3.6 → 0.3.7

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 (51) hide show
  1. package/DATA_POLICY.md +11 -3
  2. package/README.md +133 -21
  3. package/dist/akemon-home.js +56 -0
  4. package/dist/akemon-message.js +107 -0
  5. package/dist/best-effort.js +8 -0
  6. package/dist/cli.js +1188 -100
  7. package/dist/cognitive-artifact-store.js +101 -0
  8. package/dist/cognitive-event-log.js +47 -0
  9. package/dist/config.js +45 -9
  10. package/dist/context.js +27 -6
  11. package/dist/core/contracts/layers.js +1 -0
  12. package/dist/core/contracts/permission.js +1 -0
  13. package/dist/core/contracts/workspace.js +1 -0
  14. package/dist/core-cognitive-module.js +768 -0
  15. package/dist/engine-peripheral.js +127 -26
  16. package/dist/engine-routing.js +58 -17
  17. package/dist/interactive-session.js +361 -0
  18. package/dist/local-interconnect.js +156 -0
  19. package/dist/local-registry.js +178 -0
  20. package/dist/mcp-server.js +4 -1
  21. package/dist/memory-proposal.js +379 -0
  22. package/dist/memory-recorder.js +368 -0
  23. package/dist/orphan-scan.js +36 -24
  24. package/dist/passive-reflection-cognitive-module.js +172 -0
  25. package/dist/peripheral-registry.js +235 -0
  26. package/dist/permission-audit.js +132 -0
  27. package/dist/relay-client.js +68 -9
  28. package/dist/relay-mode.js +34 -0
  29. package/dist/relay-peripheral.js +139 -49
  30. package/dist/runtime-platform.js +122 -0
  31. package/dist/secretariat/client.js +87 -0
  32. package/dist/self.js +15 -6
  33. package/dist/server.js +3675 -512
  34. package/dist/social-discovery.js +231 -0
  35. package/dist/software-agent-peripheral.js +185 -244
  36. package/dist/software-agent-transport.js +177 -0
  37. package/dist/task-module.js +243 -0
  38. package/dist/task-registry.js +756 -0
  39. package/dist/vendor/xterm/addon-fit.js +2 -0
  40. package/dist/vendor/xterm/addon-search.js +2 -0
  41. package/dist/vendor/xterm/addon-web-links.js +2 -0
  42. package/dist/vendor/xterm/xterm.css +285 -0
  43. package/dist/vendor/xterm/xterm.js +2 -0
  44. package/dist/work-memory.js +59 -15
  45. package/dist/workbench-peripheral-guide.js +79 -0
  46. package/dist/workbench-session.js +1074 -0
  47. package/dist/workbench.html +4011 -0
  48. package/package.json +8 -3
  49. package/scripts/build.cjs +24 -0
  50. package/scripts/check-architecture-baseline.cjs +68 -0
  51. package/scripts/test.cjs +38 -0
@@ -11,13 +11,17 @@
11
11
  * transport to app-server or a true persistent interactive session.
12
12
  */
13
13
  import { randomUUID } from "crypto";
14
- import { spawn, spawnSync } from "child_process";
14
+ import { spawnSync } from "child_process";
15
15
  import { existsSync, mkdirSync, readdirSync, readFileSync, unlinkSync, writeFileSync } from "fs";
16
16
  import { isAbsolute, join, relative, resolve as resolvePath } from "path";
17
- import { StringDecoder } from "string_decoder";
18
17
  import { SIG, sig } from "./types.js";
19
18
  import { sendTaskEnd, sendTaskStart, sendTaskStream } from "./relay-client.js";
20
- import { redactSecrets, StreamingRedactor } from "./redaction.js";
19
+ import { redactSecrets } from "./redaction.js";
20
+ import { terminateProcessTree } from "./runtime-platform.js";
21
+ import { actorRef } from "./akemon-message.js";
22
+ import { appendPermissionAuditRecordSync } from "./permission-audit.js";
23
+ import { logBestEffortError } from "./best-effort.js";
24
+ import { CodexExecTransport, } from "./software-agent-transport.js";
21
25
  const defaultTaskRelay = {
22
26
  sendTaskStart,
23
27
  sendTaskStream,
@@ -78,12 +82,19 @@ export class CodexSoftwareAgentPeripheral {
78
82
  activeTaskId = null;
79
83
  activeWorkdir = null;
80
84
  sessionId = randomUUID();
85
+ transport;
81
86
  constructor(config) {
82
87
  this.config = {
83
88
  ...config,
84
89
  envPolicy: normalizeSoftwareAgentEnvPolicy(config.envPolicy),
85
90
  envAllowlist: normalizeSoftwareAgentEnvAllowlist(config.envAllowlist),
86
91
  };
92
+ this.transport = config.transport || new CodexExecTransport({
93
+ command: config.command || "codex",
94
+ model: config.model,
95
+ sandbox: config.sandbox || "workspace-write",
96
+ spawnImpl: config.spawnImpl,
97
+ });
87
98
  this.id = config.id || "software-agent:codex";
88
99
  this.name = config.name || "Codex CLI Software Agent";
89
100
  }
@@ -103,18 +114,9 @@ export class CodexSoftwareAgentPeripheral {
103
114
  async resetSession() {
104
115
  const activePid = this.activeChild?.pid;
105
116
  if (activePid) {
106
- const processGroupId = -activePid;
107
- try {
108
- process.kill(processGroupId, "SIGTERM");
109
- }
110
- catch { }
111
- setTimeout(() => {
112
- try {
113
- process.kill(processGroupId, "SIGKILL");
114
- }
115
- catch { }
116
- }, 3000).unref();
117
+ terminateProcessTree(activePid, { signal: "SIGTERM", forceAfterMs: 3000 });
117
118
  }
119
+ await this.transport.reset?.();
118
120
  this.activeChild = null;
119
121
  this.activeTaskId = null;
120
122
  this.activeWorkdir = null;
@@ -127,8 +129,9 @@ export class CodexSoftwareAgentPeripheral {
127
129
  sessionId: this.sessionId,
128
130
  activeTaskId: this.activeTaskId,
129
131
  activeWorkdir: this.activeWorkdir,
130
- busy: !!this.activeChild,
131
- transport: "codex-exec",
132
+ busy: !!this.activeTaskId || !!this.activeChild,
133
+ transport: this.transport.kind,
134
+ transportContract: this.transport.describe(currentWorkdir),
132
135
  baseWorkdir: resolvePath(this.config.workdir),
133
136
  workdirStatus: this.collectWorkdirStatus(currentWorkdir),
134
137
  taskLedgerDir: this.config.taskLedgerDir,
@@ -155,7 +158,17 @@ export class CodexSoftwareAgentPeripheral {
155
158
  return sig(SIG.SOFTWARE_AGENT_RESPONSE, { ...result }, this.id);
156
159
  }
157
160
  async sendTask(envelope, taskOptions) {
158
- if (this.activeChild) {
161
+ if (this.activeTaskId || this.activeChild) {
162
+ this.writePermissionAudit({
163
+ envelope,
164
+ taskId: envelope.taskId,
165
+ workdir: envelope.workdir || this.config.workdir,
166
+ decision: {
167
+ result: "denied",
168
+ mode: "automatic",
169
+ reason: `Software agent busy (task=${this.activeTaskId})`,
170
+ },
171
+ });
159
172
  throw new Error(`Software agent busy (task=${this.activeTaskId})`);
160
173
  }
161
174
  const { signal, observer } = normalizeSoftwareAgentTaskOptions(taskOptions);
@@ -179,17 +192,11 @@ export class CodexSoftwareAgentPeripheral {
179
192
  const prompt = contextSession
180
193
  ? buildContextPacketLaunchPrompt(effectiveEnvelope, contextSession.audit.packetPath)
181
194
  : buildTaskEnvelopePrompt(effectiveEnvelope);
182
- const { cmd, args } = buildCodexExecCommand({
183
- command: this.config.command || "codex",
184
- workdir,
185
- model: this.config.model,
186
- sandbox: this.config.sandbox || "workspace-write",
187
- });
195
+ const transportDescriptor = this.transport.describe(workdir);
188
196
  const startedAt = Date.now();
189
197
  const startedAtIso = new Date(startedAt).toISOString();
190
198
  const relay = this.config.taskRelay || defaultTaskRelay;
191
- const commandLine = [cmd, ...args].join(" ");
192
- const spawnImpl = this.config.spawnImpl || spawn;
199
+ const commandLine = transportDescriptor.commandLine;
193
200
  const timeoutMs = envelope.timeoutMs || this.config.defaultTimeoutMs || DEFAULT_TIMEOUT_MS;
194
201
  const workdirStatus = this.collectWorkdirStatus(workdir);
195
202
  const taskMetadata = {
@@ -202,12 +209,25 @@ export class CodexSoftwareAgentPeripheral {
202
209
  allowlist: this.config.envAllowlist,
203
210
  sourceEnv: this.config.sourceEnv,
204
211
  });
212
+ this.writePermissionAudit({
213
+ envelope: effectiveEnvelope,
214
+ taskId,
215
+ workdir,
216
+ workdirSafety,
217
+ commandLine,
218
+ environment: childEnvironment.audit,
219
+ decision: {
220
+ result: "allowed",
221
+ mode: "owner-approved",
222
+ reason: "Owner local endpoint accepted the software-agent task envelope.",
223
+ },
224
+ });
205
225
  const baseTaskRecord = () => ({
206
226
  schemaVersion: 1,
207
227
  taskId,
208
228
  agentId: this.id,
209
229
  sessionId: this.sessionId,
210
- transport: "codex-exec",
230
+ transport: this.transport.kind,
211
231
  commandLine,
212
232
  envelope: effectiveEnvelope,
213
233
  startedAt: startedAtIso,
@@ -215,211 +235,103 @@ export class CodexSoftwareAgentPeripheral {
215
235
  contextSession: contextSession?.audit,
216
236
  workdirStatus,
217
237
  });
218
- return new Promise((resolve) => {
219
- this.activeTaskId = taskId;
220
- this.activeWorkdir = workdir;
221
- this.writeTaskRecord({
222
- ...baseTaskRecord(),
223
- status: "running",
224
- updatedAt: startedAtIso,
238
+ this.activeTaskId = taskId;
239
+ this.activeWorkdir = workdir;
240
+ this.writeTaskRecord({
241
+ ...baseTaskRecord(),
242
+ status: "running",
243
+ updatedAt: startedAtIso,
244
+ });
245
+ const origin = "software_agent";
246
+ relay.sendTaskStart(taskId, origin, commandLine);
247
+ observer?.onStart?.({ taskId, origin, commandLine, ...taskMetadata });
248
+ this.bus?.emit(SIG.TASK_STARTED, sig(SIG.TASK_STARTED, {
249
+ taskId,
250
+ taskType: "software_agent",
251
+ description: effectiveEnvelope.goal,
252
+ peripheral: this.id,
253
+ sessionId: this.sessionId,
254
+ }, this.id));
255
+ let transportResult;
256
+ try {
257
+ transportResult = await this.transport.run({
258
+ taskId,
259
+ prompt,
260
+ workdir,
261
+ env: childEnvironment.env,
262
+ timeoutMs,
263
+ signal,
264
+ startedAt,
265
+ onStream: (event) => {
266
+ relay.sendTaskStream(event.taskId, event.stream, event.chunk);
267
+ observer?.onStream?.(event);
268
+ },
269
+ onProcessStart: (child) => {
270
+ this.activeChild = child;
271
+ },
272
+ onProcessEnd: (child) => {
273
+ if (this.activeChild === child)
274
+ this.activeChild = null;
275
+ },
225
276
  });
226
- const origin = "software_agent";
227
- relay.sendTaskStart(taskId, origin, commandLine);
228
- observer?.onStart?.({ taskId, origin, commandLine, ...taskMetadata });
229
- this.bus?.emit(SIG.TASK_STARTED, sig(SIG.TASK_STARTED, {
277
+ }
278
+ catch (err) {
279
+ transportResult = {
230
280
  taskId,
231
- taskType: "software_agent",
232
- description: effectiveEnvelope.goal,
233
- peripheral: this.id,
234
- sessionId: this.sessionId,
235
- }, this.id));
236
- let child;
237
- try {
238
- child = spawnImpl(cmd, args, {
239
- cwd: workdir,
240
- env: childEnvironment.env,
241
- stdio: ["pipe", "pipe", "pipe"],
242
- detached: true,
243
- });
244
- }
245
- catch (err) {
246
- this.activeChild = null;
247
- this.activeTaskId = null;
248
- this.activeWorkdir = null;
249
- const durationMs = Date.now() - startedAt;
250
- const result = {
251
- success: false,
252
- taskId,
253
- output: "",
254
- error: err.message || String(err),
255
- exitCode: null,
256
- durationMs,
257
- ...taskMetadata,
258
- };
259
- relay.sendTaskEnd(taskId, null, durationMs);
260
- observer?.onEnd?.({
261
- taskId,
262
- exitCode: null,
263
- durationMs,
264
- result: redactSecrets(result),
265
- ...taskMetadata,
266
- });
267
- const completedAt = new Date().toISOString();
268
- this.writeTaskRecord({
269
- ...baseTaskRecord(),
270
- status: "failed",
271
- updatedAt: completedAt,
272
- completedAt,
273
- durationMs,
274
- result,
275
- stdoutSummary: summarizeText(""),
276
- stderrSummary: summarizeText(result.error || ""),
277
- });
278
- if (contextSession) {
279
- writeSoftwareAgentContextSessionState(contextSession.audit.statePath, effectiveEnvelope, result, completedAt);
280
- }
281
- this.bus?.emit(SIG.TASK_FAILED, sig(SIG.TASK_FAILED, result, this.id));
282
- resolve(result);
283
- return;
284
- }
285
- this.activeChild = child;
286
- let stdout = "";
287
- let stderr = "";
288
- let finished = false;
289
- let aborted = false;
290
- const outDecoder = new StringDecoder("utf8");
291
- const errDecoder = new StringDecoder("utf8");
292
- const outRedactor = new StreamingRedactor();
293
- const errRedactor = new StreamingRedactor();
294
- const emitSafeStream = (stream, text) => {
295
- if (!text)
296
- return;
297
- relay.sendTaskStream(taskId, stream, text);
298
- observer?.onStream?.({ taskId, stream, chunk: text });
299
- };
300
- const finish = (exitCode, error) => {
301
- if (finished)
302
- return;
303
- finished = true;
304
- signal?.removeEventListener("abort", onAbort);
305
- clearTimeout(timer);
306
- const tailOut = outDecoder.end();
307
- const tailErr = errDecoder.end();
308
- if (tailOut) {
309
- stdout += tailOut;
310
- emitSafeStream("stdout", outRedactor.push(tailOut));
311
- }
312
- if (tailErr) {
313
- stderr += tailErr;
314
- emitSafeStream("stderr", errRedactor.push(tailErr));
315
- }
316
- emitSafeStream("stdout", outRedactor.flush());
317
- emitSafeStream("stderr", errRedactor.flush());
318
- const durationMs = Date.now() - startedAt;
319
- this.activeChild = null;
320
- this.activeTaskId = null;
321
- this.activeWorkdir = null;
322
- const output = stdout.trim() || stderr.trim();
323
- const success = !error && !aborted && exitCode === 0;
324
- const result = {
325
- success,
326
- taskId,
327
- output,
328
- error: success ? undefined : error || stderr.trim() || `codex exited with code ${exitCode}`,
329
- exitCode,
330
- durationMs,
331
- ...taskMetadata,
332
- };
333
- relay.sendTaskEnd(taskId, exitCode, durationMs);
334
- observer?.onEnd?.({
335
- taskId,
336
- exitCode,
337
- durationMs,
338
- result: redactSecrets(result),
339
- ...taskMetadata,
340
- });
341
- const completedAt = new Date().toISOString();
342
- this.writeTaskRecord({
343
- ...baseTaskRecord(),
344
- status: success ? "completed" : "failed",
345
- updatedAt: completedAt,
346
- completedAt,
347
- durationMs,
348
- result,
349
- stdoutSummary: summarizeText(stdout),
350
- stderrSummary: summarizeText(stderr),
351
- });
352
- if (contextSession) {
353
- writeSoftwareAgentContextSessionState(contextSession.audit.statePath, effectiveEnvelope, result, completedAt);
354
- }
355
- this.bus?.emit(success ? SIG.TASK_COMPLETED : SIG.TASK_FAILED, sig(success ? SIG.TASK_COMPLETED : SIG.TASK_FAILED, {
356
- ...result,
357
- taskLabel: `software_agent:${this.id}`,
358
- }, this.id));
359
- resolve(result);
360
- };
361
- const onAbort = () => {
362
- if (aborted || !child.pid)
363
- return;
364
- aborted = true;
365
- try {
366
- process.kill(-child.pid, "SIGTERM");
367
- }
368
- catch { }
369
- setTimeout(() => {
370
- try {
371
- process.kill(-child.pid, "SIGKILL");
372
- }
373
- catch { }
374
- }, 3000).unref();
281
+ stdout: "",
282
+ stderr: "",
283
+ error: err.message || String(err),
284
+ exitCode: null,
285
+ durationMs: Date.now() - startedAt,
286
+ aborted: false,
287
+ success: false,
375
288
  };
376
- const timer = setTimeout(() => {
377
- if (!child.pid)
378
- return;
379
- aborted = true;
380
- try {
381
- process.kill(-child.pid, "SIGTERM");
382
- }
383
- catch { }
384
- setTimeout(() => {
385
- try {
386
- process.kill(-child.pid, "SIGKILL");
387
- }
388
- catch { }
389
- }, 3000).unref();
390
- }, timeoutMs);
391
- if (signal) {
392
- if (signal.aborted)
393
- onAbort();
394
- else
395
- signal.addEventListener("abort", onAbort, { once: true });
396
- }
397
- child.stdin?.on("error", () => { });
398
- child.stdin?.write(prompt);
399
- child.stdin?.end();
400
- child.stdout?.on("data", (chunk) => {
401
- const text = outDecoder.write(chunk);
402
- if (!text)
403
- return;
404
- stdout += text;
405
- emitSafeStream("stdout", outRedactor.push(text));
406
- });
407
- child.stderr?.on("data", (chunk) => {
408
- const text = errDecoder.write(chunk);
409
- if (!text)
410
- return;
411
- stderr += text;
412
- emitSafeStream("stderr", errRedactor.push(text));
413
- });
414
- child.on("close", (code) => {
415
- child.unref();
416
- finish(code);
417
- });
418
- child.on("error", (err) => {
419
- child.unref();
420
- finish(null, err.message);
421
- });
289
+ }
290
+ finally {
291
+ this.activeChild = null;
292
+ this.activeTaskId = null;
293
+ this.activeWorkdir = null;
294
+ }
295
+ const output = transportResult.stdout.trim() || transportResult.stderr.trim();
296
+ const transportLabel = this.transport.kind === "codex-exec" ? "codex" : this.transport.kind;
297
+ const result = {
298
+ success: transportResult.success,
299
+ taskId,
300
+ output,
301
+ error: transportResult.success
302
+ ? undefined
303
+ : transportResult.error || transportResult.stderr.trim() || `${transportLabel} exited with code ${transportResult.exitCode}`,
304
+ exitCode: transportResult.exitCode,
305
+ durationMs: transportResult.durationMs,
306
+ ...taskMetadata,
307
+ };
308
+ relay.sendTaskEnd(taskId, transportResult.exitCode, transportResult.durationMs);
309
+ observer?.onEnd?.({
310
+ taskId,
311
+ exitCode: transportResult.exitCode,
312
+ durationMs: transportResult.durationMs,
313
+ result: redactSecrets(result),
314
+ ...taskMetadata,
315
+ });
316
+ const completedAt = new Date().toISOString();
317
+ this.writeTaskRecord({
318
+ ...baseTaskRecord(),
319
+ status: result.success ? "completed" : "failed",
320
+ updatedAt: completedAt,
321
+ completedAt,
322
+ durationMs: transportResult.durationMs,
323
+ result,
324
+ stdoutSummary: summarizeText(transportResult.stdout),
325
+ stderrSummary: summarizeText(transportResult.stderr || result.error || ""),
422
326
  });
327
+ if (contextSession) {
328
+ writeSoftwareAgentContextSessionState(contextSession.audit.statePath, effectiveEnvelope, result, completedAt);
329
+ }
330
+ this.bus?.emit(result.success ? SIG.TASK_COMPLETED : SIG.TASK_FAILED, sig(result.success ? SIG.TASK_COMPLETED : SIG.TASK_FAILED, {
331
+ ...result,
332
+ taskLabel: `software_agent:${this.id}`,
333
+ }, this.id));
334
+ return result;
423
335
  }
424
336
  writeTaskRecord(record) {
425
337
  const dir = this.config.taskLedgerDir;
@@ -427,14 +339,54 @@ export class CodexSoftwareAgentPeripheral {
427
339
  return;
428
340
  try {
429
341
  mkdirSync(dir, { recursive: true });
430
- const safeTaskId = safeTaskFilename(record.taskId);
431
- writeFileSync(join(dir, `${safeTaskId}.json`), `${JSON.stringify(redactSecrets(record), null, 2)}\n`);
342
+ writeFileSync(softwareAgentTaskRecordPath(dir, record.taskId), `${JSON.stringify(redactSecrets(record), null, 2)}\n`);
432
343
  pruneSoftwareAgentTaskRecords(dir, this.config.taskLedgerMaxRecords, record.taskId);
433
344
  }
434
345
  catch (err) {
435
346
  console.error(`[software-agent] Failed to write task ledger: ${err.message || String(err)}`);
436
347
  }
437
348
  }
349
+ writePermissionAudit(input) {
350
+ if (!this.config.agentName)
351
+ return;
352
+ try {
353
+ const requesterKind = input.envelope.sourceModule === "owner-http" ? "owner" : "system";
354
+ appendPermissionAuditRecordSync(this.config.agentName, {
355
+ actionKind: "software-agent-task",
356
+ action: "software-agent.run",
357
+ requestedBy: actorRef(requesterKind, input.envelope.sourceModule || "owner", "local"),
358
+ performedBy: actorRef("agent", this.config.agentName, "software-agent"),
359
+ target: actorRef("external", this.id, "software-agent"),
360
+ sourceModule: input.envelope.sourceModule,
361
+ peripheralId: this.id,
362
+ riskLevel: input.envelope.riskLevel,
363
+ roleScope: input.envelope.roleScope,
364
+ memoryScope: input.envelope.memoryScope,
365
+ workdir: input.workdir || input.envelope.workdir,
366
+ projectScope: input.workdirSafety
367
+ ? (input.workdirSafety.outsideBaseWorkdir ? "outside-workdir" : "inside-workdir")
368
+ : "unknown",
369
+ transport: "software-agent",
370
+ decision: input.decision,
371
+ allowedActions: input.envelope.allowedActions,
372
+ forbiddenActions: input.envelope.forbiddenActions,
373
+ references: {
374
+ taskId: input.taskId || input.envelope.taskId,
375
+ contextSessionId: input.envelope.contextSessionId,
376
+ contextPacketPath: input.envelope.contextPacketPath,
377
+ },
378
+ metadata: {
379
+ purpose: input.envelope.purpose,
380
+ commandLine: input.commandLine,
381
+ environment: input.environment,
382
+ workMemoryDir: input.envelope.workMemoryDir,
383
+ },
384
+ });
385
+ }
386
+ catch (error) {
387
+ logBestEffortError("software-agent audit append", error);
388
+ }
389
+ }
438
390
  collectWorkdirStatus(workdir) {
439
391
  const impl = this.config.gitStatusImpl || readGitWorktreeStatus;
440
392
  return impl(workdir);
@@ -686,8 +638,10 @@ export function listSoftwareAgentTaskRecords(taskLedgerDir, limit = 20, opts = {
686
638
  }
687
639
  }
688
640
  export function readSoftwareAgentTaskRecord(taskLedgerDir, taskId) {
689
- const file = join(taskLedgerDir, `${safeTaskFilename(taskId)}.json`);
690
- return readSoftwareAgentTaskRecordFile(file);
641
+ return readSoftwareAgentTaskRecordFile(softwareAgentTaskRecordPath(taskLedgerDir, taskId));
642
+ }
643
+ export function softwareAgentTaskRecordPath(taskLedgerDir, taskId) {
644
+ return join(taskLedgerDir, `${safeTaskFilename(taskId)}.json`);
691
645
  }
692
646
  export function listSoftwareAgentContextSessions(contextSessionDir, limit = 20) {
693
647
  const safeLimit = normalizeTaskRecordLimit(limit);
@@ -1071,16 +1025,3 @@ function compareSoftwareAgentContextSessions(a, b) {
1071
1025
  return bTime - aTime;
1072
1026
  return b.sessionId.localeCompare(a.sessionId);
1073
1027
  }
1074
- function buildCodexExecCommand(opts) {
1075
- const args = [
1076
- "exec",
1077
- "--skip-git-repo-check",
1078
- "--color", "never",
1079
- "-s", opts.sandbox,
1080
- "-C", opts.workdir,
1081
- ];
1082
- if (opts.model)
1083
- args.push("-m", opts.model);
1084
- args.push("-");
1085
- return { cmd: opts.command, args };
1086
- }