@engineeros/connector 0.4.1 → 0.4.4
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 +11 -0
- package/bin/engineeros-connector.mjs +15 -3
- package/package.json +1 -1
- package/src/runner.mjs +136 -20
package/README.md
CHANGED
|
@@ -36,3 +36,14 @@ Credentials are stored per workspace under `~/.engineeros/connectors` with owner
|
|
|
36
36
|
Keep the connector online to receive Goals assigned from EngineerOS. Each Goal runs in an isolated worktree below `~/.engineeros/runs`. Cancellation stops Codex. The connector returns changed paths, a bounded diff, and the exact repository ZIP; a human still performs independent attestation.
|
|
37
37
|
|
|
38
38
|
Requirements: Node.js 22 or newer, Git, and an authenticated Codex CLI (`codex login`).
|
|
39
|
+
|
|
40
|
+
## Codex CLI compatibility
|
|
41
|
+
|
|
42
|
+
The connector prints the exact Codex CLI version it will use before connecting. If the
|
|
43
|
+
configured model requires a newer CLI, update Codex and restart the connector:
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
npm install -g @openai/codex@latest
|
|
47
|
+
codex --version
|
|
48
|
+
npx @engineeros/connector start --workspace .
|
|
49
|
+
```
|
|
@@ -10,9 +10,11 @@ import {
|
|
|
10
10
|
workspaceUrl,
|
|
11
11
|
} from "../src/config.mjs";
|
|
12
12
|
import {
|
|
13
|
+
assessmentProgressMessage,
|
|
13
14
|
executeAssignment,
|
|
14
15
|
executeConnectedPrompt,
|
|
15
16
|
executeWorkspaceAssessment,
|
|
17
|
+
inspectCodexCli,
|
|
16
18
|
stopProcess,
|
|
17
19
|
workspaceSnapshot,
|
|
18
20
|
} from "../src/runner.mjs";
|
|
@@ -73,6 +75,17 @@ if (command === "pair") {
|
|
|
73
75
|
fail("Use `engineeros-connector pair`, `start`, or `status`.");
|
|
74
76
|
}
|
|
75
77
|
|
|
78
|
+
let codexCli;
|
|
79
|
+
try {
|
|
80
|
+
codexCli = await inspectCodexCli(config.workspace);
|
|
81
|
+
} catch (error) {
|
|
82
|
+
fail(error instanceof Error ? error.message : String(error));
|
|
83
|
+
}
|
|
84
|
+
console.log(`Using ${codexCli.version}.`);
|
|
85
|
+
if (firstMessage.type === "pair") {
|
|
86
|
+
firstMessage.capabilities.codex_cli_version = codexCli.version;
|
|
87
|
+
}
|
|
88
|
+
|
|
76
89
|
let stopped = false;
|
|
77
90
|
let active = null;
|
|
78
91
|
const available = [];
|
|
@@ -375,9 +388,8 @@ async function executeAssessment(assignment) {
|
|
|
375
388
|
if (active?.runId === assessmentId) active.child = child;
|
|
376
389
|
},
|
|
377
390
|
onEvent: (event) => {
|
|
378
|
-
const message =
|
|
379
|
-
|
|
380
|
-
reportProgress(message);
|
|
391
|
+
const message = assessmentProgressMessage(event);
|
|
392
|
+
if (message) reportProgress(message);
|
|
381
393
|
},
|
|
382
394
|
});
|
|
383
395
|
const response = await fetch(
|
package/package.json
CHANGED
package/src/runner.mjs
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
lstat,
|
|
5
|
+
mkdir,
|
|
6
|
+
readFile,
|
|
7
|
+
readdir,
|
|
8
|
+
stat,
|
|
9
|
+
writeFile,
|
|
10
|
+
} from "node:fs/promises";
|
|
4
11
|
import os from "node:os";
|
|
5
12
|
import path from "node:path";
|
|
6
13
|
import { promisify } from "node:util";
|
|
@@ -114,14 +121,30 @@ export async function executeAssignment(assignment, config, callbacks) {
|
|
|
114
121
|
};
|
|
115
122
|
}
|
|
116
123
|
|
|
117
|
-
export async function executeWorkspaceAssessment(
|
|
124
|
+
export async function executeWorkspaceAssessment(
|
|
125
|
+
assignment,
|
|
126
|
+
config,
|
|
127
|
+
callbacks,
|
|
128
|
+
) {
|
|
118
129
|
const execution = connectorExecution(assignment);
|
|
119
|
-
const
|
|
130
|
+
const startingRevision = await run(
|
|
120
131
|
"git",
|
|
121
132
|
["rev-parse", "HEAD"],
|
|
122
133
|
config.workspace,
|
|
123
134
|
{ allowFailure: true },
|
|
124
135
|
);
|
|
136
|
+
const startingHead =
|
|
137
|
+
startingRevision.code === 0
|
|
138
|
+
? startingRevision.stdout.trim().slice(0, 128)
|
|
139
|
+
: null;
|
|
140
|
+
if (
|
|
141
|
+
assignment.target_head_revision &&
|
|
142
|
+
startingHead !== assignment.target_head_revision
|
|
143
|
+
) {
|
|
144
|
+
throw new Error(
|
|
145
|
+
"This workspace is no longer at the commit inventoried by EngineerOS. Refresh the workspace inventory before assessing it.",
|
|
146
|
+
);
|
|
147
|
+
}
|
|
125
148
|
const controller = launchCodexProcess(
|
|
126
149
|
config.workspace,
|
|
127
150
|
execution.prompt,
|
|
@@ -132,10 +155,24 @@ export async function executeWorkspaceAssessment(assignment, config, callbacks)
|
|
|
132
155
|
const completed = await controller.completed;
|
|
133
156
|
const report = completed.finalMessage.trim();
|
|
134
157
|
if (!report) throw new Error("Codex completed without returning a response.");
|
|
158
|
+
const endingRevision = await run(
|
|
159
|
+
"git",
|
|
160
|
+
["rev-parse", "HEAD"],
|
|
161
|
+
config.workspace,
|
|
162
|
+
{ allowFailure: true },
|
|
163
|
+
);
|
|
164
|
+
const endingHead =
|
|
165
|
+
endingRevision.code === 0
|
|
166
|
+
? endingRevision.stdout.trim().slice(0, 128)
|
|
167
|
+
: null;
|
|
168
|
+
if (startingHead !== endingHead) {
|
|
169
|
+
throw new Error(
|
|
170
|
+
"The Git commit changed during assessment. Refresh the workspace inventory and assess the new commit.",
|
|
171
|
+
);
|
|
172
|
+
}
|
|
135
173
|
return {
|
|
136
174
|
report_markdown: report,
|
|
137
|
-
observed_head_revision:
|
|
138
|
-
revision.code === 0 ? revision.stdout.trim().slice(0, 128) : null,
|
|
175
|
+
observed_head_revision: endingHead,
|
|
139
176
|
};
|
|
140
177
|
}
|
|
141
178
|
|
|
@@ -156,6 +193,70 @@ export async function executeConnectedPrompt(assignment, config, callbacks) {
|
|
|
156
193
|
return { content, model: "codex-cli" };
|
|
157
194
|
}
|
|
158
195
|
|
|
196
|
+
export async function inspectCodexCli(workspace = process.cwd()) {
|
|
197
|
+
const command =
|
|
198
|
+
process.env.CODEX_BIN ||
|
|
199
|
+
(process.platform === "win32" ? "codex.cmd" : "codex");
|
|
200
|
+
const result = await runCodexCommand(command, ["--version"], workspace);
|
|
201
|
+
if (result.code !== 0) {
|
|
202
|
+
throw new Error(
|
|
203
|
+
"Codex CLI is unavailable. Install it with `npm install -g @openai/codex@latest`, run `codex login`, then restart this connector.",
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
const version = result.stdout.trim().slice(0, 100);
|
|
207
|
+
if (!version) {
|
|
208
|
+
throw new Error(
|
|
209
|
+
"Codex CLI returned no version. Reinstall @openai/codex, then restart this connector.",
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
return { command, version };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export function codexFailureMessage(output, code) {
|
|
216
|
+
if (/requires a newer version of Codex/i.test(output)) {
|
|
217
|
+
return (
|
|
218
|
+
"The configured model requires a newer Codex CLI. " +
|
|
219
|
+
"Run `npm install -g @openai/codex@latest`, verify with `codex --version`, " +
|
|
220
|
+
"then restart the EngineerOS connector and retry the assessment."
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
if (/not logged in|login required|authentication required/i.test(output)) {
|
|
224
|
+
return "Codex CLI is not authenticated. Run `codex login`, then restart the EngineerOS connector.";
|
|
225
|
+
}
|
|
226
|
+
return `Codex exited with code ${code}. ${output.slice(-1_000)}`;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function assessmentProgressMessage(event) {
|
|
230
|
+
if (!event || typeof event !== "object") return null;
|
|
231
|
+
if (event.type === "turn.started")
|
|
232
|
+
return "Codex started analyzing repository evidence";
|
|
233
|
+
if (event.type === "item.started") {
|
|
234
|
+
if (event.item?.type === "command_execution") {
|
|
235
|
+
const command = compactCommand(event.item.command);
|
|
236
|
+
return command
|
|
237
|
+
? `Inspecting with ${command}`
|
|
238
|
+
: "Inspecting repository files";
|
|
239
|
+
}
|
|
240
|
+
if (event.item?.type === "mcp_tool_call")
|
|
241
|
+
return "Consulting a connected analysis tool";
|
|
242
|
+
if (event.item?.type === "web_search")
|
|
243
|
+
return "Checking an external technical reference";
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
if (event.type === "item.completed" && event.item?.type === "agent_message") {
|
|
247
|
+
return "Assessment report prepared";
|
|
248
|
+
}
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function compactCommand(command) {
|
|
253
|
+
const value = Array.isArray(command)
|
|
254
|
+
? command.join(" ")
|
|
255
|
+
: String(command || "");
|
|
256
|
+
const compact = value.replace(/\s+/g, " ").trim();
|
|
257
|
+
return compact.length > 96 ? `${compact.slice(0, 93)}...` : compact;
|
|
258
|
+
}
|
|
259
|
+
|
|
159
260
|
export function connectorExecution(assignment) {
|
|
160
261
|
const prompt = assignment?.prompt_markdown;
|
|
161
262
|
if (typeof prompt !== "string" || !prompt.trim()) {
|
|
@@ -292,15 +393,7 @@ function launchCodexProcess(workspace, prompt, sandbox, callbacks) {
|
|
|
292
393
|
const command =
|
|
293
394
|
process.env.CODEX_BIN ||
|
|
294
395
|
(process.platform === "win32" ? "codex.cmd" : "codex");
|
|
295
|
-
const args = [
|
|
296
|
-
"exec",
|
|
297
|
-
"--json",
|
|
298
|
-
"--sandbox",
|
|
299
|
-
sandbox,
|
|
300
|
-
"-C",
|
|
301
|
-
workspace,
|
|
302
|
-
"-",
|
|
303
|
-
];
|
|
396
|
+
const args = ["exec", "--json", "--sandbox", sandbox, "-C", workspace, "-"];
|
|
304
397
|
const child = spawn(command, args, {
|
|
305
398
|
cwd: workspace,
|
|
306
399
|
env: process.env,
|
|
@@ -349,15 +442,31 @@ function launchCodexProcess(workspace, prompt, sandbox, callbacks) {
|
|
|
349
442
|
child.once("error", reject);
|
|
350
443
|
child.once("close", (code) => {
|
|
351
444
|
if (code === 0) resolve({ output: output.slice(-20_000), finalMessage });
|
|
352
|
-
else
|
|
353
|
-
reject(
|
|
354
|
-
new Error(`Codex exited with code ${code}. ${output.slice(-1_000)}`),
|
|
355
|
-
);
|
|
445
|
+
else reject(new Error(codexFailureMessage(output, code)));
|
|
356
446
|
});
|
|
357
447
|
});
|
|
358
448
|
return { child, completed };
|
|
359
449
|
}
|
|
360
450
|
|
|
451
|
+
function runCodexCommand(command, args, cwd) {
|
|
452
|
+
return new Promise((resolve, reject) => {
|
|
453
|
+
const child = spawn(command, args, {
|
|
454
|
+
cwd,
|
|
455
|
+
env: process.env,
|
|
456
|
+
shell: process.platform === "win32",
|
|
457
|
+
windowsHide: true,
|
|
458
|
+
});
|
|
459
|
+
let stdout = "";
|
|
460
|
+
let stderr = "";
|
|
461
|
+
child.stdout.setEncoding("utf8");
|
|
462
|
+
child.stderr.setEncoding("utf8");
|
|
463
|
+
child.stdout.on("data", (chunk) => (stdout += chunk));
|
|
464
|
+
child.stderr.on("data", (chunk) => (stderr += chunk));
|
|
465
|
+
child.once("error", reject);
|
|
466
|
+
child.once("close", (code) => resolve({ code: code ?? 1, stdout, stderr }));
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
|
|
361
470
|
async function changedFilePaths(workspace) {
|
|
362
471
|
const tracked = await run(
|
|
363
472
|
"git",
|
|
@@ -452,7 +561,12 @@ async function workspaceFiles(root, current = root) {
|
|
|
452
561
|
|
|
453
562
|
function isShareablePath(relative) {
|
|
454
563
|
const normalized = normalizePath(relative);
|
|
455
|
-
if (
|
|
564
|
+
if (
|
|
565
|
+
!normalized ||
|
|
566
|
+
normalized === ".." ||
|
|
567
|
+
normalized.startsWith("../") ||
|
|
568
|
+
path.isAbsolute(normalized)
|
|
569
|
+
) {
|
|
456
570
|
return false;
|
|
457
571
|
}
|
|
458
572
|
const parts = normalized.split("/");
|
|
@@ -482,7 +596,9 @@ function isShareablePath(relative) {
|
|
|
482
596
|
|
|
483
597
|
function isCodeBearing(relative) {
|
|
484
598
|
const name = path.posix.basename(relative).toLowerCase();
|
|
485
|
-
return
|
|
599
|
+
return (
|
|
600
|
+
CODE_MARKERS.has(name) || CODE_EXTENSIONS.has(path.posix.extname(name))
|
|
601
|
+
);
|
|
486
602
|
}
|
|
487
603
|
|
|
488
604
|
async function worktreeBase(workspace, requested) {
|