@engineeros/connector 0.4.7 → 0.6.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/README.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # EngineerOS Connector
2
2
 
3
+ The connector uses an authenticated EngineerOS WebSocket for pairing, workspace identity, run lifecycle, and evidence. Coding work can run through any ACP v1-compatible agent over stdio. Codex CLI remains available as the default built-in adapter.
4
+
5
+ ## Pair an ACP coding agent
6
+
7
+ Run the command from the repository the agent should work in:
8
+
9
+ ```powershell
10
+ npx --yes @engineeros/connector@latest pair PAIRING-CODE --url http://localhost:8000 --workspace . --onboard --agent-name "My ACP agent" --agent-command my-agent --agent-args '["--acp"]'
11
+ ```
12
+
13
+ `--agent-command` must start an ACP v1 agent over NDJSON stdio. `--agent-args` is a JSON array so arguments are passed without invoking a shell. Omit both options to use the installed Codex CLI adapter.
14
+
3
15
  Connect a local Codex CLI workspace to EngineerOS through an outbound WebSocket.
4
16
 
5
17
  This package is only a connector. EngineerOS owns every prompt, Goal instruction, and
@@ -11,10 +11,11 @@ import {
11
11
  } from "../src/config.mjs";
12
12
  import {
13
13
  assessmentProgressMessage,
14
+ applyAcceptedChange,
14
15
  executeAssignment,
15
16
  executeConnectedPrompt,
16
17
  executeWorkspaceAssessment,
17
- inspectCodexCli,
18
+ inspectCodingAgent,
18
19
  stopProcess,
19
20
  workspaceSnapshot,
20
21
  } from "../src/runner.mjs";
@@ -46,6 +47,10 @@ if (command === "pair") {
46
47
  workspace: path.resolve(flags.workspace || process.cwd()),
47
48
  onboard: flags.onboard === true,
48
49
  onboarding_pending: flags.onboard === true,
50
+ agent_protocol: flags["agent-command"] ? "acp" : "codex",
51
+ agent_command: flags["agent-command"] || null,
52
+ agent_args: parseAgentArgs(flags["agent-args"]),
53
+ agent_name: flags["agent-name"] || null,
49
54
  name:
50
55
  flags.name ||
51
56
  `${os.hostname()} - ${path.basename(path.resolve(flags.workspace || process.cwd()))}`,
@@ -55,7 +60,9 @@ if (command === "pair") {
55
60
  pairing_code: pairingCode,
56
61
  name: config.name,
57
62
  capabilities: {
58
- codex_cli: true,
63
+ agent_protocols: flags["agent-command"] ? ["acp"] : ["codex"],
64
+ coding_agent: true,
65
+ codex_cli: !flags["agent-command"],
59
66
  platform: process.platform,
60
67
  workspace_name: path.basename(config.workspace),
61
68
  },
@@ -75,15 +82,16 @@ if (command === "pair") {
75
82
  fail("Use `engineeros-connector pair`, `start`, or `status`.");
76
83
  }
77
84
 
78
- let codexCli;
85
+ let codingAgent;
79
86
  try {
80
- codexCli = await inspectCodexCli(config.workspace);
87
+ codingAgent = await inspectCodingAgent(config, config.workspace);
81
88
  } catch (error) {
82
89
  fail(error instanceof Error ? error.message : String(error));
83
90
  }
84
- console.log(`Using ${codexCli.version}.`);
91
+ console.log(`Using ${codingAgent.name} through ${codingAgent.protocol} (${codingAgent.version}).`);
85
92
  if (firstMessage.type === "pair") {
86
- firstMessage.capabilities.codex_cli_version = codexCli.version;
93
+ firstMessage.capabilities.agent_name = codingAgent.name;
94
+ firstMessage.capabilities.agent_version = codingAgent.version;
87
95
  }
88
96
 
89
97
  let stopped = false;
@@ -318,7 +326,7 @@ function pump() {
318
326
  JSON.stringify({
319
327
  type: "run.claim",
320
328
  run_id: runId,
321
- runner_name: "Codex CLI",
329
+ runner_name: codingAgent.name,
322
330
  metadata: {
323
331
  hostname: os.hostname(),
324
332
  workspace_name: path.basename(config.workspace),
@@ -330,7 +338,7 @@ function pump() {
330
338
  async function executePrompt(assignment) {
331
339
  const promptId = assignment.prompt_id;
332
340
  console.log(
333
- `Answering ${assignment.purpose || "project"} prompt with Codex CLI.`,
341
+ `Answering ${assignment.purpose || "project"} prompt with ${codingAgent.name}.`,
334
342
  );
335
343
  try {
336
344
  const result = await executeConnectedPrompt(assignment, config, {
@@ -368,7 +376,7 @@ async function executePrompt(assignment) {
368
376
 
369
377
  async function executeAssessment(assignment) {
370
378
  const assessmentId = assignment.assessment_id;
371
- console.log(`Assessing workspace with Codex CLI (${assessmentId}).`);
379
+ console.log(`Assessing workspace with ${codingAgent.name} (${assessmentId}).`);
372
380
  let progress = 10;
373
381
  const reportedMilestones = new Set();
374
382
  const reportProgress = (message, { milestone = true } = {}) => {
@@ -420,7 +428,7 @@ async function executeAssessment(assignment) {
420
428
  `EngineerOS rejected the assessment (${response.status}): ${await response.text()}`,
421
429
  );
422
430
  }
423
- console.log("Codex workspace assessment is current in EngineerOS.");
431
+ console.log(`${codingAgent.name} workspace assessment is current in EngineerOS.`);
424
432
  } catch (error) {
425
433
  if (socket.readyState === WebSocket.OPEN) {
426
434
  socket.send(
@@ -441,7 +449,7 @@ async function executeAssessment(assignment) {
441
449
 
442
450
  async function execute(assignment) {
443
451
  const runId = assignment.run_id;
444
- console.log(`Running Goal ${runId} with Codex CLI.`);
452
+ console.log(`Running Goal ${runId} with ${codingAgent.name}.`);
445
453
  let progress = 10;
446
454
  const heartbeat = setInterval(() => {
447
455
  if (socket.readyState === WebSocket.OPEN && active?.runId === runId) {
@@ -451,7 +459,7 @@ async function execute(assignment) {
451
459
  type: "run.progress",
452
460
  run_id: runId,
453
461
  progress_percent: progress,
454
- message: "Codex is working",
462
+ message: `${codingAgent.name} is working`,
455
463
  }),
456
464
  );
457
465
  }
@@ -463,7 +471,7 @@ async function execute(assignment) {
463
471
  },
464
472
  onEvent: (event) => {
465
473
  const message =
466
- event.message || event.item?.text || event.type || "Codex is working";
474
+ event.message || event.item?.text || event.type || `${codingAgent.name} is working`;
467
475
  if (socket.readyState === WebSocket.OPEN) {
468
476
  socket.send(
469
477
  JSON.stringify({
@@ -492,7 +500,17 @@ async function execute(assignment) {
492
500
  throw new Error(
493
501
  `EngineerOS rejected the result (${response.status}): ${await response.text()}`,
494
502
  );
495
- console.log(`Run submitted. Review workspace: ${result.run_workspace}`);
503
+ const integration = await applyAcceptedChange(
504
+ config.workspace,
505
+ assignment.base_revision,
506
+ result.head_revision,
507
+ );
508
+ if (integration.applied) {
509
+ console.log(`Run accepted and applied to the connected repository at ${integration.revision}.`);
510
+ } else {
511
+ console.warn(`Run accepted but not applied locally. ${integration.reason}`);
512
+ console.warn(`The verified run workspace remains at ${result.run_workspace}.`);
513
+ }
496
514
  } catch (error) {
497
515
  if (!active?.cancelled && socket.readyState === WebSocket.OPEN) {
498
516
  socket.send(
@@ -525,6 +543,19 @@ function parseArgs(args) {
525
543
  return { command, positional, flags };
526
544
  }
527
545
 
546
+ function parseAgentArgs(value) {
547
+ if (!value) return [];
548
+ try {
549
+ const parsed = JSON.parse(value);
550
+ if (!Array.isArray(parsed) || parsed.some((item) => typeof item !== "string")) {
551
+ throw new Error();
552
+ }
553
+ return parsed;
554
+ } catch {
555
+ fail('--agent-args must be a JSON array, for example: --agent-args "[\\"acp\\"]"');
556
+ }
557
+ }
558
+
528
559
  function fail(message) {
529
560
  console.error(message);
530
561
  process.exit(1);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@engineeros/connector",
3
- "version": "0.4.7",
4
- "description": "Connect a local Codex CLI workspace to EngineerOS over an outbound WebSocket.",
3
+ "version": "0.6.0",
4
+ "description": "Connect a local coding agent to EngineerOS, using ACP when supported.",
5
5
  "private": false,
6
6
  "type": "module",
7
7
  "license": "UNLICENSED",
@@ -15,7 +15,7 @@
15
15
  },
16
16
  "scripts": {
17
17
  "start": "node ./bin/engineeros-connector.mjs",
18
- "test": "node --test",
18
+ "test": "node --test --test-concurrency=1",
19
19
  "type-check": "node --check ./bin/engineeros-connector.mjs && node --check ./src/runner.mjs"
20
20
  },
21
21
  "engines": {
@@ -31,5 +31,8 @@
31
31
  },
32
32
  "publishConfig": {
33
33
  "access": "public"
34
+ },
35
+ "dependencies": {
36
+ "@agentclientprotocol/sdk": "1.3.0"
34
37
  }
35
38
  }
@@ -0,0 +1,105 @@
1
+ import { spawn } from "node:child_process";
2
+ import { Readable, Writable } from "node:stream";
3
+ import * as acp from "@agentclientprotocol/sdk";
4
+
5
+ function permissionOutcome(options = []) {
6
+ const selected =
7
+ options.find((option) => option.kind === "allow_once") ??
8
+ options.find((option) => option.kind === "allow_always");
9
+ return selected
10
+ ? { outcome: { outcome: "selected", optionId: selected.optionId } }
11
+ : { outcome: { outcome: "cancelled" } };
12
+ }
13
+
14
+ export function launchAcpAgent(workspace, prompt, config, callbacks = {}) {
15
+ if (!config.agent_command) {
16
+ throw new Error(
17
+ "No ACP coding agent is configured. Pair again with --agent-command and optional --agent-args JSON.",
18
+ );
19
+ }
20
+ const child = spawn(
21
+ config.agent_command,
22
+ Array.isArray(config.agent_args) ? config.agent_args : [],
23
+ {
24
+ cwd: workspace,
25
+ env: process.env,
26
+ shell: false,
27
+ stdio: ["pipe", "pipe", "pipe"],
28
+ },
29
+ );
30
+ let stderr = "";
31
+ child.stderr.setEncoding("utf8");
32
+ child.stderr.on("data", (chunk) => {
33
+ stderr += chunk;
34
+ callbacks.onEvent?.({
35
+ type: "agent.stderr",
36
+ message: String(chunk).trim().slice(0, 500),
37
+ });
38
+ });
39
+
40
+ const stream = acp.ndJsonStream(
41
+ Writable.toWeb(child.stdin),
42
+ Readable.toWeb(child.stdout),
43
+ );
44
+ let finalMessage = "";
45
+ const completed = acp
46
+ .client({ name: "EngineerOS" })
47
+ .onRequest(acp.methods.client.session.requestPermission, (ctx) =>
48
+ permissionOutcome(ctx.params.options),
49
+ )
50
+ .connectWith(stream, async (ctx) => {
51
+ const initialized = await ctx.request(acp.methods.agent.initialize, {
52
+ protocolVersion: acp.PROTOCOL_VERSION,
53
+ clientCapabilities: {},
54
+ });
55
+ callbacks.onEvent?.({
56
+ type: "agent.connected",
57
+ message: `ACP agent connected with protocol ${initialized.protocolVersion}`,
58
+ });
59
+ return ctx.buildSession(workspace).withSession(async (session) => {
60
+ child.engineerOsCancel = () =>
61
+ ctx.notify(acp.methods.agent.session.cancel, {
62
+ sessionId: session.sessionId,
63
+ });
64
+ void session.prompt(prompt);
65
+ for (;;) {
66
+ const message = await session.nextUpdate();
67
+ if (message.kind === "stop") {
68
+ if (message.stopReason === "error") {
69
+ throw new Error("The ACP agent ended the task with an error.");
70
+ }
71
+ return { finalMessage, model: "acp-agent" };
72
+ }
73
+ const update = message.update;
74
+ if (
75
+ update.sessionUpdate === "agent_message_chunk" &&
76
+ update.content?.type === "text"
77
+ ) {
78
+ finalMessage += update.content.text;
79
+ }
80
+ callbacks.onEvent?.({
81
+ type: `acp.${update.sessionUpdate}`,
82
+ message:
83
+ update.title ??
84
+ update.content?.text ??
85
+ update.status ??
86
+ update.sessionUpdate,
87
+ });
88
+ }
89
+ });
90
+ })
91
+ .catch((error) => {
92
+ const detail = stderr.trim().slice(-1_000);
93
+ throw new Error(
94
+ `ACP agent failed: ${error instanceof Error ? error.message : String(error)}${detail ? ` ${detail}` : ""}`,
95
+ );
96
+ })
97
+ .finally(() => {
98
+ child.kill("SIGKILL");
99
+ child.stdin.destroy();
100
+ child.stdout.destroy();
101
+ child.stderr.destroy();
102
+ child.unref();
103
+ });
104
+ return { child, completed };
105
+ }
package/src/runner.mjs CHANGED
@@ -1,4 +1,3 @@
1
- import { createHash } from "node:crypto";
2
1
  import { spawn } from "node:child_process";
3
2
  import {
4
3
  lstat,
@@ -12,6 +11,7 @@ import os from "node:os";
12
11
  import path from "node:path";
13
12
  import { promisify } from "node:util";
14
13
  import { deflateRaw } from "node:zlib";
14
+ import { launchAcpAgent } from "./acp-client.mjs";
15
15
 
16
16
  const deflate = promisify(deflateRaw);
17
17
  const MAX_ARCHIVE_BYTES = 25 * 1024 * 1024;
@@ -95,10 +95,11 @@ export async function executeAssignment(assignment, config, callbacks) {
95
95
  assignment.run_id,
96
96
  assignment.base_revision,
97
97
  );
98
- const controller = launchCodexProcess(
98
+ const controller = launchAgentProcess(
99
99
  runWorkspace,
100
100
  execution.prompt,
101
101
  execution.sandboxMode,
102
+ config,
102
103
  callbacks,
103
104
  );
104
105
  callbacks.onProcess?.(controller.child);
@@ -106,21 +107,162 @@ export async function executeAssignment(assignment, config, callbacks) {
106
107
  const changedFiles = await changedFilePaths(runWorkspace);
107
108
  if (!changedFiles.length)
108
109
  throw new Error("Codex completed without changing any files.");
109
- const diffPatch = await boundedDiff(runWorkspace, changedFiles);
110
- const archive = await repositoryArchive(runWorkspace);
111
- const revision = `worktree-${createHash("sha256").update(diffPatch).digest("hex").slice(0, 55)}`;
110
+ const committed = await commitRunChange(
111
+ runWorkspace,
112
+ assignment.base_revision,
113
+ assignment.run_id,
114
+ );
115
+ const verifier = launchAgentProcess(
116
+ runWorkspace,
117
+ verificationPrompt(assignment, committed.revision, committed.changedFiles),
118
+ "read-only",
119
+ config,
120
+ callbacks,
121
+ );
122
+ callbacks.onProcess?.(verifier.child);
123
+ const verification = await verifier.completed;
124
+ const proofEvidence = parseVerificationReport(
125
+ verification.finalMessage,
126
+ assignment.proof_checks,
127
+ config.connector_id,
128
+ assignment.run_id,
129
+ );
112
130
  return {
113
- head_revision: revision,
131
+ head_revision: committed.revision,
114
132
  repository_locator: assignment.repository_locator,
115
133
  external_reference: `connector:${config.connector_id}/run:${assignment.run_id}`,
116
- diff_patch: diffPatch,
117
- changed_files: changedFiles,
118
- repository_archive_base64: archive.toString("base64"),
119
- repository_archive_media_type: "application/zip",
120
- proof_evidence: [],
134
+ diff_patch: committed.diffPatch,
135
+ changed_files: committed.changedFiles,
136
+ proof_evidence: proofEvidence,
137
+ verification_report: verification.finalMessage,
138
+ };
139
+ }
140
+
141
+ export async function applyAcceptedChange(workspace, baseRevision, headRevision) {
142
+ const current = await run("git", ["rev-parse", "HEAD"], workspace, {
143
+ allowFailure: true,
144
+ });
145
+ if (current.code !== 0) {
146
+ return { applied: false, reason: "The connected workspace is not a Git repository." };
147
+ }
148
+ const currentHead = current.stdout.trim();
149
+ const alreadyApplied = await run(
150
+ "git",
151
+ ["merge-base", "--is-ancestor", headRevision, currentHead],
152
+ workspace,
153
+ { allowFailure: true },
154
+ );
155
+ if (alreadyApplied.code === 0) return { applied: true, revision: currentHead };
156
+ const status = await run("git", ["status", "--porcelain"], workspace);
157
+ if (status.stdout.trim()) {
158
+ return {
159
+ applied: false,
160
+ reason: `The connected workspace has uncommitted changes. Apply accepted commit ${headRevision} after preserving them.`,
161
+ };
162
+ }
163
+ if (currentHead !== baseRevision) {
164
+ return {
165
+ applied: false,
166
+ reason: `The connected branch moved from frozen base ${baseRevision} to ${currentHead}. Apply accepted commit ${headRevision} with git cherry-pick.`,
167
+ };
168
+ }
169
+ const applied = await run("git", ["cherry-pick", headRevision], workspace, {
170
+ allowFailure: true,
171
+ });
172
+ if (applied.code !== 0) {
173
+ await run("git", ["cherry-pick", "--abort"], workspace, {
174
+ allowFailure: true,
175
+ });
176
+ return {
177
+ applied: false,
178
+ reason: `The accepted commit ${headRevision} could not be applied cleanly. Apply it manually with git cherry-pick.`,
179
+ };
180
+ }
181
+ const integrated = await run("git", ["rev-parse", "HEAD"], workspace);
182
+ return { applied: true, revision: integrated.stdout.trim() };
183
+ }
184
+
185
+ async function commitRunChange(workspace, baseRevision, runId) {
186
+ await run("git", ["add", "-A"], workspace);
187
+ await run(
188
+ "git",
189
+ [
190
+ "-c",
191
+ "user.name=EngineerOS Codex",
192
+ "-c",
193
+ "user.email=codex@engineeros.local",
194
+ "commit",
195
+ "-m",
196
+ `EngineerOS Goal Run ${runId}`,
197
+ ],
198
+ workspace,
199
+ );
200
+ const head = await run("git", ["rev-parse", "HEAD"], workspace);
201
+ const revision = head.stdout.trim();
202
+ const diff = await run(
203
+ "git",
204
+ ["diff", "--binary", baseRevision, revision, "--"],
205
+ workspace,
206
+ );
207
+ const paths = await run(
208
+ "git",
209
+ ["diff", "--name-only", "-z", baseRevision, revision, "--"],
210
+ workspace,
211
+ );
212
+ return {
213
+ revision,
214
+ diffPatch: diff.stdout,
215
+ changedFiles: gitPathList(paths.stdout).sort(),
121
216
  };
122
217
  }
123
218
 
219
+ export function verificationPrompt(assignment, revision, changedFiles) {
220
+ const checks = (assignment.proof_checks ?? [])
221
+ .map(
222
+ (proof, index) =>
223
+ `## Proof ${index + 1}\n- Check: ${proof.check ?? ""}\n- Expected: ${proof.expected ?? ""}`,
224
+ )
225
+ .join("\n\n");
226
+ return `# EngineerOS Connected Verification
227
+
228
+ Independently verify the frozen Goal at Git commit \`${revision}\`. Do not modify files. Run the commands or inspections needed for every Proof item, and check the Goal boundaries and constraints in the supplied packet.
229
+
230
+ Changed files:
231
+ ${changedFiles.map((item) => `- \`${item}\``).join("\n")}
232
+
233
+ ${checks}
234
+
235
+ Return structured Markdown only, with exactly one section per Proof:
236
+
237
+ ## Proof 1
238
+ - Status: passed or failed
239
+ - Exit code: integer or none
240
+ - Evidence: concise observed output and command
241
+
242
+ Do not claim a Proof passed unless you observed it directly.`;
243
+ }
244
+
245
+ export function parseVerificationReport(report, proofChecks, connectorId, runId) {
246
+ if (!report?.trim()) throw new Error("Codex returned no connected verification report.");
247
+ return (proofChecks ?? []).map((_, index) => {
248
+ const start = new RegExp(`^## Proof ${index + 1}\\s*$`, "im").exec(report);
249
+ const tail = start ? report.slice(start.index + start[0].length) : "";
250
+ const section = tail.split(/^## Proof \d+\s*$/im)[0] ?? "";
251
+ const status = /^- Status:\s*(passed|failed)\s*$/im.exec(section)?.[1] ?? "failed";
252
+ const exitValue = /^- Exit code:\s*(\d+|none)\s*$/im.exec(section)?.[1] ?? "none";
253
+ const evidence = /^- Evidence:\s*(.+)$/im.exec(section)?.[1]?.trim();
254
+ return {
255
+ proof_index: index,
256
+ status,
257
+ verifier_type: "agent_reported",
258
+ verifier_identity: "Connected Codex CLI verifier",
259
+ locator: `connector:${connectorId}/run:${runId}#proof-${index + 1}`,
260
+ output_excerpt: evidence || "The connected verifier did not provide evidence for this Proof.",
261
+ exit_code: exitValue === "none" ? null : Number(exitValue),
262
+ };
263
+ });
264
+ }
265
+
124
266
  export async function executeWorkspaceAssessment(
125
267
  assignment,
126
268
  config,
@@ -159,10 +301,11 @@ export async function executeWorkspaceAssessment(
159
301
  changeImpact.markdown,
160
302
  )
161
303
  : execution.prompt;
162
- const controller = launchCodexProcess(
304
+ const controller = launchAgentProcess(
163
305
  config.workspace,
164
306
  prompt,
165
307
  execution.sandboxMode,
308
+ config,
166
309
  callbacks,
167
310
  );
168
311
  callbacks.onProcess?.(controller.child);
@@ -280,10 +423,11 @@ function boundedText(value, maximum) {
280
423
 
281
424
  export async function executeConnectedPrompt(assignment, config, callbacks) {
282
425
  const execution = connectorExecution(assignment);
283
- const controller = launchCodexProcess(
426
+ const controller = launchAgentProcess(
284
427
  config.workspace,
285
428
  execution.prompt,
286
429
  execution.sandboxMode,
430
+ config,
287
431
  callbacks,
288
432
  );
289
433
  callbacks.onProcess?.(controller.child);
@@ -292,7 +436,22 @@ export async function executeConnectedPrompt(assignment, config, callbacks) {
292
436
  if (!content) {
293
437
  throw new Error("Codex completed without returning a response.");
294
438
  }
295
- return { content, model: "codex-cli" };
439
+ return { content, model: completed.model ?? config.agent_protocol ?? "coding-agent" };
440
+ }
441
+
442
+ export async function inspectCodingAgent(config, workspace = process.cwd()) {
443
+ if (config.agent_protocol === "acp") {
444
+ if (!config.agent_command) {
445
+ throw new Error("ACP requires --agent-command when pairing the connector.");
446
+ }
447
+ return {
448
+ protocol: "acp",
449
+ name: config.agent_name || path.basename(config.agent_command),
450
+ version: "ACP v1",
451
+ };
452
+ }
453
+ const codex = await inspectCodexCli(workspace);
454
+ return { protocol: "codex", name: "Codex CLI", version: codex.version };
296
455
  }
297
456
 
298
457
  export async function inspectCodexCli(workspace = process.cwd()) {
@@ -386,6 +545,7 @@ export function connectorExecution(assignment) {
386
545
 
387
546
  export async function stopProcess(child) {
388
547
  if (!child || child.exitCode !== null) return;
548
+ await child.engineerOsCancel?.();
389
549
  if (process.platform === "win32") {
390
550
  await run(
391
551
  "taskkill",
@@ -563,6 +723,13 @@ function launchCodexProcess(workspace, prompt, sandbox, callbacks) {
563
723
  return { child, completed };
564
724
  }
565
725
 
726
+ function launchAgentProcess(workspace, prompt, sandbox, config, callbacks) {
727
+ if (config.agent_protocol === "acp") {
728
+ return launchAcpAgent(workspace, prompt, config, callbacks);
729
+ }
730
+ return launchCodexProcess(workspace, prompt, sandbox, callbacks);
731
+ }
732
+
566
733
  function runCodexCommand(command, args, cwd) {
567
734
  return new Promise((resolve, reject) => {
568
735
  const child = spawn(command, args, {