@engineeros/connector 0.5.0 → 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 +12 -0
- package/bin/engineeros-connector.mjs +45 -14
- package/package.json +6 -3
- package/src/acp-client.mjs +105 -0
- package/src/runner.mjs +77 -5
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
|
-
|
|
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
|
-
|
|
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
|
|
85
|
+
let codingAgent;
|
|
79
86
|
try {
|
|
80
|
-
|
|
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 ${
|
|
91
|
+
console.log(`Using ${codingAgent.name} through ${codingAgent.protocol} (${codingAgent.version}).`);
|
|
85
92
|
if (firstMessage.type === "pair") {
|
|
86
|
-
firstMessage.capabilities.
|
|
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:
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
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:
|
|
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 ||
|
|
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
|
-
|
|
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
|
-
"description": "Connect a local
|
|
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
|
@@ -11,6 +11,7 @@ import os from "node:os";
|
|
|
11
11
|
import path from "node:path";
|
|
12
12
|
import { promisify } from "node:util";
|
|
13
13
|
import { deflateRaw } from "node:zlib";
|
|
14
|
+
import { launchAcpAgent } from "./acp-client.mjs";
|
|
14
15
|
|
|
15
16
|
const deflate = promisify(deflateRaw);
|
|
16
17
|
const MAX_ARCHIVE_BYTES = 25 * 1024 * 1024;
|
|
@@ -94,10 +95,11 @@ export async function executeAssignment(assignment, config, callbacks) {
|
|
|
94
95
|
assignment.run_id,
|
|
95
96
|
assignment.base_revision,
|
|
96
97
|
);
|
|
97
|
-
const controller =
|
|
98
|
+
const controller = launchAgentProcess(
|
|
98
99
|
runWorkspace,
|
|
99
100
|
execution.prompt,
|
|
100
101
|
execution.sandboxMode,
|
|
102
|
+
config,
|
|
101
103
|
callbacks,
|
|
102
104
|
);
|
|
103
105
|
callbacks.onProcess?.(controller.child);
|
|
@@ -110,10 +112,11 @@ export async function executeAssignment(assignment, config, callbacks) {
|
|
|
110
112
|
assignment.base_revision,
|
|
111
113
|
assignment.run_id,
|
|
112
114
|
);
|
|
113
|
-
const verifier =
|
|
115
|
+
const verifier = launchAgentProcess(
|
|
114
116
|
runWorkspace,
|
|
115
117
|
verificationPrompt(assignment, committed.revision, committed.changedFiles),
|
|
116
118
|
"read-only",
|
|
119
|
+
config,
|
|
117
120
|
callbacks,
|
|
118
121
|
);
|
|
119
122
|
callbacks.onProcess?.(verifier.child);
|
|
@@ -135,6 +138,50 @@ export async function executeAssignment(assignment, config, callbacks) {
|
|
|
135
138
|
};
|
|
136
139
|
}
|
|
137
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
|
+
|
|
138
185
|
async function commitRunChange(workspace, baseRevision, runId) {
|
|
139
186
|
await run("git", ["add", "-A"], workspace);
|
|
140
187
|
await run(
|
|
@@ -254,10 +301,11 @@ export async function executeWorkspaceAssessment(
|
|
|
254
301
|
changeImpact.markdown,
|
|
255
302
|
)
|
|
256
303
|
: execution.prompt;
|
|
257
|
-
const controller =
|
|
304
|
+
const controller = launchAgentProcess(
|
|
258
305
|
config.workspace,
|
|
259
306
|
prompt,
|
|
260
307
|
execution.sandboxMode,
|
|
308
|
+
config,
|
|
261
309
|
callbacks,
|
|
262
310
|
);
|
|
263
311
|
callbacks.onProcess?.(controller.child);
|
|
@@ -375,10 +423,11 @@ function boundedText(value, maximum) {
|
|
|
375
423
|
|
|
376
424
|
export async function executeConnectedPrompt(assignment, config, callbacks) {
|
|
377
425
|
const execution = connectorExecution(assignment);
|
|
378
|
-
const controller =
|
|
426
|
+
const controller = launchAgentProcess(
|
|
379
427
|
config.workspace,
|
|
380
428
|
execution.prompt,
|
|
381
429
|
execution.sandboxMode,
|
|
430
|
+
config,
|
|
382
431
|
callbacks,
|
|
383
432
|
);
|
|
384
433
|
callbacks.onProcess?.(controller.child);
|
|
@@ -387,7 +436,22 @@ export async function executeConnectedPrompt(assignment, config, callbacks) {
|
|
|
387
436
|
if (!content) {
|
|
388
437
|
throw new Error("Codex completed without returning a response.");
|
|
389
438
|
}
|
|
390
|
-
return { content, model: "
|
|
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 };
|
|
391
455
|
}
|
|
392
456
|
|
|
393
457
|
export async function inspectCodexCli(workspace = process.cwd()) {
|
|
@@ -481,6 +545,7 @@ export function connectorExecution(assignment) {
|
|
|
481
545
|
|
|
482
546
|
export async function stopProcess(child) {
|
|
483
547
|
if (!child || child.exitCode !== null) return;
|
|
548
|
+
await child.engineerOsCancel?.();
|
|
484
549
|
if (process.platform === "win32") {
|
|
485
550
|
await run(
|
|
486
551
|
"taskkill",
|
|
@@ -658,6 +723,13 @@ function launchCodexProcess(workspace, prompt, sandbox, callbacks) {
|
|
|
658
723
|
return { child, completed };
|
|
659
724
|
}
|
|
660
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
|
+
|
|
661
733
|
function runCodexCommand(command, args, cwd) {
|
|
662
734
|
return new Promise((resolve, reject) => {
|
|
663
735
|
const child = spawn(command, args, {
|