@engineeros/connector 0.15.3 → 0.16.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 +1 -1
- package/bin/engineeros-connector.mjs +92 -47
- package/package.json +1 -1
- package/src/acp-client.mjs +9 -8
- package/src/assessment-spool.mjs +59 -4
- package/src/codex-app-server.mjs +3 -2
- package/src/connection.mjs +8 -2
- package/src/runner.mjs +36 -25
package/README.md
CHANGED
|
@@ -47,7 +47,7 @@ npx --yes @engineeros/connector@latest pair PAIRING-CODE --url https://your-engi
|
|
|
47
47
|
|
|
48
48
|
The connector uploads a bounded ZIP snapshot for a safe file inventory, then stays online for deep assessments, rescans, and Goal Runs. Inventory never executes repository code. It excludes known secrets, dependency directories, build output, compiled binaries, files larger than 5 MB, agent-tool caches, Git metadata, and connector state before upload.
|
|
49
49
|
|
|
50
|
-
From **Project steering -> Workspace**, run the workspace assessment to use the connected agent subscription already authenticated on that computer. Select any combination of fifteen brownfield assessment domains, collectively covering 133 baseline checks from product and architecture through security, privacy, project-specific compliance, delivery, reliability, governance, and modernization planning. Every domain uses a detailed structured-Markdown contract. Connector `0.
|
|
50
|
+
From **Project steering -> Workspace**, run the workspace assessment to use the connected agent subscription already authenticated on that computer. Select any combination of fifteen brownfield assessment domains, collectively covering 133 baseline checks from product and architecture through security, privacy, project-specific compliance, delivery, reliability, governance, and modernization planning. Every domain uses a detailed structured-Markdown contract. Connector `0.16.0` starts assessments with three parallel workers and accepts the run's UI-selected limit from three through eight for every coding-agent provider. It atomically spools each completed Markdown report and each active stage's resumable agent-session checkpoint under `.engineeros/assessments/<run-id>`. ACP agents such as OpenCode use isolated stage sessions within one persistent coding-agent process. The connector owns the worker pool and sends one aggregate heartbeat containing every active worker's phase, progress, last activity, and event count; report content is no longer streamed over the WebSocket or written into backend assessment JSON. The backend validates stage checkpoints so dependency fan-out remains authoritative, while the connector retains completed reports and reloads interrupted ACP or Codex sessions after reconnect when the coding agent supports session restoration. Capability detail and project-specific compliance controls fan out after their catalogs. Final synthesis reads the connector-local Markdown files, then one completed bundle is revalidated, persisted, and published as canonical artifacts. The connector removes its spool only after EngineerOS acknowledges the complete bundle. Compliance findings describe repository-verifiable engineering readiness and missing external context, never legal compliance or certification. The connector prints each worker's safe activity and elapsed time every 30 seconds, stops a worker that produces no agent activity for ten minutes instead of waiting indefinitely, and applies separate bounded structure, semantic, and corrected-response formatting repairs before failing a stage. Assessment cannot modify tracked workspace source.
|
|
51
51
|
|
|
52
52
|
Every backend `422` content-validation response is returned to the stage agent with the exact failed item. Distinct validation failures continue through correction and resubmission until the complete stage is accepted. Three repetitions of the same unresolved validation error stop the automatic loop while preserving the latest local report for retry. Authentication, cancellation, source drift, and transport failures remain operational errors rather than agent-correction prompts.
|
|
53
53
|
|
|
@@ -36,9 +36,11 @@ import {
|
|
|
36
36
|
import {
|
|
37
37
|
assessmentCheckpointPayload,
|
|
38
38
|
assessmentCompletionBundle,
|
|
39
|
-
clearAssessmentSpool,
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
clearAssessmentSpool,
|
|
40
|
+
loadAssessmentStageSession,
|
|
41
|
+
loadAssessmentStageResult,
|
|
42
|
+
persistAssessmentStageResult,
|
|
43
|
+
persistAssessmentStageSession,
|
|
42
44
|
prepareLocalAssessmentAssignment,
|
|
43
45
|
removeAssessmentStageResult,
|
|
44
46
|
} from "../src/assessment-spool.mjs";
|
|
@@ -50,11 +52,12 @@ import {
|
|
|
50
52
|
registeredAgents,
|
|
51
53
|
} from "../src/agent-registry.mjs";
|
|
52
54
|
import {
|
|
53
|
-
describeRejectedResponse,
|
|
54
|
-
describeWebSocketError,
|
|
55
|
-
protocolFailureMessage,
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
describeRejectedResponse,
|
|
56
|
+
describeWebSocketError,
|
|
57
|
+
protocolFailureMessage,
|
|
58
|
+
sendConnectionMessage,
|
|
59
|
+
startConnectionWatchdog,
|
|
60
|
+
} from "../src/connection.mjs";
|
|
58
61
|
import { advertisedCapabilities } from "../src/capabilities.mjs";
|
|
59
62
|
import { parseConnectorArgs } from "../src/cli-args.mjs";
|
|
60
63
|
import { runMcpServer } from "../src/mcp-server.mjs";
|
|
@@ -250,25 +253,47 @@ process.on("SIGINT", async () => {
|
|
|
250
253
|
|
|
251
254
|
await connect();
|
|
252
255
|
|
|
253
|
-
async function connect() {
|
|
254
|
-
console.log(`Connecting ${config.name} to ${config.server_url}`);
|
|
255
|
-
connectionRejected = false;
|
|
256
|
-
lastConnectionError = undefined;
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
256
|
+
async function connect() {
|
|
257
|
+
console.log(`Connecting ${config.name} to ${config.server_url}`);
|
|
258
|
+
connectionRejected = false;
|
|
259
|
+
lastConnectionError = undefined;
|
|
260
|
+
const connection = new WebSocket(config.server_url);
|
|
261
|
+
socket = connection;
|
|
262
|
+
const clearWatchdog = startConnectionWatchdog(connection, config.server_url, {
|
|
263
|
+
onTimeout: (message) => {
|
|
264
|
+
if (socket !== connection) return;
|
|
265
|
+
lastConnectionError = message;
|
|
266
|
+
console.error(message);
|
|
267
|
+
scheduleReconnect();
|
|
268
|
+
},
|
|
269
|
+
});
|
|
270
|
+
clearConnectionWatchdog = clearWatchdog;
|
|
271
|
+
connection.addEventListener("open", () => {
|
|
272
|
+
if (socket !== connection) {
|
|
273
|
+
connection.close();
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
try {
|
|
277
|
+
if (sendConnectionMessage(socket, connection, firstMessage)) return;
|
|
278
|
+
throw new Error("The WebSocket was not open when authentication started.");
|
|
279
|
+
} catch (error) {
|
|
280
|
+
lastConnectionError = protocolFailureMessage(error);
|
|
281
|
+
console.error(
|
|
282
|
+
`Could not authenticate the WebSocket connection: ${lastConnectionError}`,
|
|
283
|
+
);
|
|
284
|
+
try {
|
|
285
|
+
connection.close();
|
|
286
|
+
} catch {
|
|
287
|
+
// The reconnect timer below owns recovery.
|
|
288
|
+
}
|
|
289
|
+
scheduleReconnect();
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
connection.addEventListener("message", async (event) => {
|
|
293
|
+
if (socket !== connection) return;
|
|
294
|
+
const message = JSON.parse(String(event.data));
|
|
295
|
+
if (message.type === "paired") {
|
|
296
|
+
clearWatchdog();
|
|
272
297
|
const reusedConnector =
|
|
273
298
|
existingConfig?.connector_id === message.connector.id;
|
|
274
299
|
config = mergePairedConfig(
|
|
@@ -294,8 +319,8 @@ async function connect() {
|
|
|
294
319
|
if (config.onboarding_pending) void submitWorkspaceSnapshot();
|
|
295
320
|
return;
|
|
296
321
|
}
|
|
297
|
-
if (message.type === "authenticated") {
|
|
298
|
-
|
|
322
|
+
if (message.type === "authenticated") {
|
|
323
|
+
clearWatchdog();
|
|
299
324
|
console.log("Connected and waiting for EngineerOS runs.");
|
|
300
325
|
reconnectDelay = 1_000;
|
|
301
326
|
startPings();
|
|
@@ -345,10 +370,10 @@ async function connect() {
|
|
|
345
370
|
await stopProcess(active.child);
|
|
346
371
|
return;
|
|
347
372
|
}
|
|
348
|
-
if (message.type === "connector.revoked") {
|
|
349
|
-
stopped = true;
|
|
350
|
-
console.error("This connector was revoked in EngineerOS.");
|
|
351
|
-
|
|
373
|
+
if (message.type === "connector.revoked") {
|
|
374
|
+
stopped = true;
|
|
375
|
+
console.error("This connector was revoked in EngineerOS.");
|
|
376
|
+
connection.close();
|
|
352
377
|
return;
|
|
353
378
|
}
|
|
354
379
|
if (message.type === "run.error") {
|
|
@@ -364,9 +389,10 @@ async function connect() {
|
|
|
364
389
|
console.error(`EngineerOS: ${message.message}`);
|
|
365
390
|
}
|
|
366
391
|
});
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
392
|
+
connection.addEventListener("close", (event) => {
|
|
393
|
+
clearWatchdog();
|
|
394
|
+
if (socket !== connection) return;
|
|
395
|
+
clearInterval(pingTimer);
|
|
370
396
|
for (const promptState of activePrompts.values())
|
|
371
397
|
void cancelPrompt(promptState);
|
|
372
398
|
for (const assessmentState of activeAssessments.values()) {
|
|
@@ -382,8 +408,9 @@ async function connect() {
|
|
|
382
408
|
if (stopped) return;
|
|
383
409
|
scheduleReconnect();
|
|
384
410
|
});
|
|
385
|
-
|
|
386
|
-
|
|
411
|
+
connection.addEventListener("error", (event) => {
|
|
412
|
+
if (socket !== connection) return;
|
|
413
|
+
lastConnectionError = describeWebSocketError(event);
|
|
387
414
|
console.error(
|
|
388
415
|
`WebSocket connection to ${config.server_url} failed: ${lastConnectionError}`,
|
|
389
416
|
);
|
|
@@ -724,7 +751,16 @@ async function executeAssessment(assignment, assessmentState) {
|
|
|
724
751
|
);
|
|
725
752
|
}
|
|
726
753
|
}, 15_000);
|
|
727
|
-
const assessmentAgentCallbacks = {
|
|
754
|
+
const assessmentAgentCallbacks = {
|
|
755
|
+
onSession: async (sessionId) => {
|
|
756
|
+
if (!isActiveAssessment()) return;
|
|
757
|
+
await persistAssessmentStageSession(
|
|
758
|
+
config.workspace,
|
|
759
|
+
assessmentId,
|
|
760
|
+
stage,
|
|
761
|
+
sessionId,
|
|
762
|
+
);
|
|
763
|
+
},
|
|
728
764
|
onController: (controller) => {
|
|
729
765
|
if (isActiveAssessment()) assessmentState.controller = controller;
|
|
730
766
|
},
|
|
@@ -773,20 +809,29 @@ async function executeAssessment(assignment, assessmentState) {
|
|
|
773
809
|
config.workspace,
|
|
774
810
|
assignment,
|
|
775
811
|
);
|
|
776
|
-
let result = await loadAssessmentStageResult(
|
|
812
|
+
let result = await loadAssessmentStageResult(
|
|
777
813
|
config.workspace,
|
|
778
814
|
assessmentId,
|
|
779
815
|
stage,
|
|
780
816
|
);
|
|
781
|
-
if (result) {
|
|
817
|
+
if (result) {
|
|
782
818
|
attachStoredCorrection(result, preparedAssignment);
|
|
783
819
|
reportProgress("Recovered completed stage report from connector storage");
|
|
784
|
-
} else {
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
);
|
|
820
|
+
} else {
|
|
821
|
+
const previousSessionId = await loadAssessmentStageSession(
|
|
822
|
+
config.workspace,
|
|
823
|
+
assessmentId,
|
|
824
|
+
stage,
|
|
825
|
+
);
|
|
826
|
+
if (previousSessionId) {
|
|
827
|
+
reportProgress("Restoring interrupted agent session");
|
|
828
|
+
}
|
|
829
|
+
result = await executeWorkspaceAssessment(
|
|
830
|
+
preparedAssignment,
|
|
831
|
+
config,
|
|
832
|
+
assessmentAgentCallbacks,
|
|
833
|
+
{ previousSessionId },
|
|
834
|
+
);
|
|
790
835
|
await persistAssessmentStageResult(
|
|
791
836
|
config.workspace,
|
|
792
837
|
assessmentId,
|
package/package.json
CHANGED
package/src/acp-client.mjs
CHANGED
|
@@ -201,19 +201,20 @@ class AcpRuntime {
|
|
|
201
201
|
this.clearIdleTimer();
|
|
202
202
|
const initialized = await this.ready;
|
|
203
203
|
let session = this.sessions.get(sessionKey);
|
|
204
|
-
if (!session) {
|
|
205
|
-
session = await this.openSession(
|
|
204
|
+
if (!session) {
|
|
205
|
+
session = await this.openSession(
|
|
206
206
|
sessionKey,
|
|
207
207
|
previousSessionId,
|
|
208
208
|
sandbox,
|
|
209
209
|
profile,
|
|
210
210
|
);
|
|
211
|
-
callbacks.onEvent?.({
|
|
212
|
-
type: "agent.connected",
|
|
213
|
-
message: `Agent connected with protocol ${initialized.protocolVersion}`,
|
|
214
|
-
});
|
|
215
|
-
}
|
|
216
|
-
|
|
211
|
+
callbacks.onEvent?.({
|
|
212
|
+
type: "agent.connected",
|
|
213
|
+
message: `Agent connected with protocol ${initialized.protocolVersion}`,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
await callbacks.onSession?.(session.sessionId);
|
|
217
|
+
if (this.turns.has(session.sessionId)) {
|
|
217
218
|
throw new Error(
|
|
218
219
|
"The agent is already answering another prompt in this session.",
|
|
219
220
|
);
|
package/src/assessment-spool.mjs
CHANGED
|
@@ -53,7 +53,7 @@ export async function persistAssessmentStageResult(
|
|
|
53
53
|
return { ...metadata, report_markdown: report };
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
export async function loadAssessmentStageResult(
|
|
56
|
+
export async function loadAssessmentStageResult(
|
|
57
57
|
workspace,
|
|
58
58
|
assessmentId,
|
|
59
59
|
stage,
|
|
@@ -89,7 +89,58 @@ export async function loadAssessmentStageResult(
|
|
|
89
89
|
if (error?.code === "ENOENT") return null;
|
|
90
90
|
throw error;
|
|
91
91
|
}
|
|
92
|
-
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export async function persistAssessmentStageSession(
|
|
95
|
+
workspace,
|
|
96
|
+
assessmentId,
|
|
97
|
+
stage,
|
|
98
|
+
sessionId,
|
|
99
|
+
) {
|
|
100
|
+
const normalizedSessionId = String(sessionId || "").trim();
|
|
101
|
+
if (!normalizedSessionId) {
|
|
102
|
+
throw new Error(`Assessment stage '${stage}' returned an empty session id.`);
|
|
103
|
+
}
|
|
104
|
+
const directory = assessmentRunDirectory(workspace, assessmentId);
|
|
105
|
+
await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
106
|
+
const checkpoint = {
|
|
107
|
+
stage,
|
|
108
|
+
agent_session_id: normalizedSessionId,
|
|
109
|
+
};
|
|
110
|
+
await atomicWrite(
|
|
111
|
+
assessmentStageSessionPath(directory, stage),
|
|
112
|
+
`${JSON.stringify(checkpoint, null, 2)}\n`,
|
|
113
|
+
);
|
|
114
|
+
return checkpoint;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export async function loadAssessmentStageSession(
|
|
118
|
+
workspace,
|
|
119
|
+
assessmentId,
|
|
120
|
+
stage,
|
|
121
|
+
) {
|
|
122
|
+
const directory = assessmentRunDirectory(workspace, assessmentId);
|
|
123
|
+
try {
|
|
124
|
+
const checkpoint = JSON.parse(
|
|
125
|
+
await readFile(assessmentStageSessionPath(directory, stage), "utf8"),
|
|
126
|
+
);
|
|
127
|
+
if (checkpoint.stage !== stage) {
|
|
128
|
+
throw new Error(
|
|
129
|
+
`Assessment session checkpoint does not match stage '${stage}'.`,
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
const sessionId = String(checkpoint.agent_session_id || "").trim();
|
|
133
|
+
if (!sessionId) {
|
|
134
|
+
throw new Error(
|
|
135
|
+
`Assessment session checkpoint for '${stage}' has no session id.`,
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
return sessionId;
|
|
139
|
+
} catch (error) {
|
|
140
|
+
if (error?.code === "ENOENT") return null;
|
|
141
|
+
throw error;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
93
144
|
|
|
94
145
|
export async function removeAssessmentStageResult(
|
|
95
146
|
workspace,
|
|
@@ -226,14 +277,18 @@ function validateLocalAssessmentResult(assignment, result) {
|
|
|
226
277
|
}
|
|
227
278
|
}
|
|
228
279
|
|
|
229
|
-
function assessmentStageBasename(stage) {
|
|
280
|
+
function assessmentStageBasename(stage) {
|
|
230
281
|
const slug =
|
|
231
282
|
String(stage)
|
|
232
283
|
.replace(/[^a-zA-Z0-9_-]+/g, "-")
|
|
233
284
|
.replace(/^-+|-+$/g, "")
|
|
234
285
|
.slice(0, 64) || "stage";
|
|
235
286
|
return `${slug}-${sha256(String(stage)).slice(0, 12)}`;
|
|
236
|
-
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function assessmentStageSessionPath(directory, stage) {
|
|
290
|
+
return path.join(directory, `${assessmentStageBasename(stage)}.session.json`);
|
|
291
|
+
}
|
|
237
292
|
|
|
238
293
|
function sha256(value) {
|
|
239
294
|
return createHash("sha256").update(value).digest("hex");
|
package/src/codex-app-server.mjs
CHANGED
|
@@ -217,8 +217,9 @@ export function launchCodexAppServer({
|
|
|
217
217
|
model: profile.model || null,
|
|
218
218
|
ephemeral: false,
|
|
219
219
|
});
|
|
220
|
-
threadId = threadResult.thread.id;
|
|
221
|
-
callbacks.
|
|
220
|
+
threadId = threadResult.thread.id;
|
|
221
|
+
await callbacks.onSession?.(threadId);
|
|
222
|
+
callbacks.onEvent?.({ type: "thread.started", thread_id: threadId });
|
|
222
223
|
const turnResult = await request("turn/start", {
|
|
223
224
|
threadId,
|
|
224
225
|
input: [{ type: "text", text: prompt }],
|
package/src/connection.mjs
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
const PROTOCOL_FAILURE_MESSAGE_MAX_LENGTH = 2_000;
|
|
2
2
|
const TRUNCATED_FAILURE_SUFFIX = "\n… [truncated by EngineerOS connector]";
|
|
3
3
|
|
|
4
|
-
export function describeWebSocketError(event) {
|
|
4
|
+
export function describeWebSocketError(event) {
|
|
5
5
|
return (
|
|
6
6
|
event?.error?.message ||
|
|
7
7
|
event?.message ||
|
|
8
8
|
"The WebSocket connection failed without providing an error detail."
|
|
9
9
|
);
|
|
10
|
-
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function sendConnectionMessage(activeSocket, connection, message) {
|
|
13
|
+
if (activeSocket !== connection || connection.readyState !== 1) return false;
|
|
14
|
+
connection.send(JSON.stringify(message));
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
11
17
|
|
|
12
18
|
export function protocolFailureMessage(error) {
|
|
13
19
|
const raw = error instanceof Error ? error.message : String(error ?? "");
|
package/src/runner.mjs
CHANGED
|
@@ -361,11 +361,12 @@ export function parseVerificationReport(
|
|
|
361
361
|
});
|
|
362
362
|
}
|
|
363
363
|
|
|
364
|
-
export async function executeWorkspaceAssessment(
|
|
365
|
-
assignment,
|
|
366
|
-
config,
|
|
367
|
-
callbacks,
|
|
368
|
-
|
|
364
|
+
export async function executeWorkspaceAssessment(
|
|
365
|
+
assignment,
|
|
366
|
+
config,
|
|
367
|
+
callbacks,
|
|
368
|
+
{ previousSessionId } = {},
|
|
369
|
+
) {
|
|
369
370
|
const execution = workspaceAssessmentExecution(assignment);
|
|
370
371
|
const assessmentAcpContext =
|
|
371
372
|
config.agent_protocol === "acp"
|
|
@@ -421,7 +422,7 @@ export async function executeWorkspaceAssessment(
|
|
|
421
422
|
changeImpact.markdown,
|
|
422
423
|
)
|
|
423
424
|
: execution.prompt;
|
|
424
|
-
const controller = launchAssessmentAgent(prompt);
|
|
425
|
+
const controller = launchAssessmentAgent(prompt, previousSessionId);
|
|
425
426
|
let completed = await controller.completed;
|
|
426
427
|
const recovered = await recoverAssessmentStageOutput({
|
|
427
428
|
completed,
|
|
@@ -1674,8 +1675,10 @@ function launchCodexProcess(
|
|
|
1674
1675
|
child.stdin.end(prompt);
|
|
1675
1676
|
let output = "";
|
|
1676
1677
|
let buffer = "";
|
|
1677
|
-
let finalMessage = "";
|
|
1678
|
-
let sessionId = previousSessionId || "";
|
|
1678
|
+
let finalMessage = "";
|
|
1679
|
+
let sessionId = previousSessionId || "";
|
|
1680
|
+
let sessionCheckpoint = Promise.resolve();
|
|
1681
|
+
let sessionCheckpointError;
|
|
1679
1682
|
child.stdout.setEncoding("utf8");
|
|
1680
1683
|
child.stdout.on("data", (chunk) => {
|
|
1681
1684
|
output += chunk;
|
|
@@ -1686,12 +1689,17 @@ function launchCodexProcess(
|
|
|
1686
1689
|
if (!line.trim()) continue;
|
|
1687
1690
|
try {
|
|
1688
1691
|
const event = JSON.parse(line);
|
|
1689
|
-
if (
|
|
1690
|
-
event.type === "thread.started" &&
|
|
1691
|
-
typeof event.thread_id === "string"
|
|
1692
|
-
) {
|
|
1693
|
-
sessionId = event.thread_id;
|
|
1694
|
-
|
|
1692
|
+
if (
|
|
1693
|
+
event.type === "thread.started" &&
|
|
1694
|
+
typeof event.thread_id === "string"
|
|
1695
|
+
) {
|
|
1696
|
+
sessionId = event.thread_id;
|
|
1697
|
+
sessionCheckpoint = Promise.resolve()
|
|
1698
|
+
.then(() => callbacks.onSession?.(sessionId))
|
|
1699
|
+
.catch((error) => {
|
|
1700
|
+
sessionCheckpointError = error;
|
|
1701
|
+
});
|
|
1702
|
+
}
|
|
1695
1703
|
if (
|
|
1696
1704
|
event.type === "item.completed" &&
|
|
1697
1705
|
event.item?.type === "agent_message" &&
|
|
@@ -1718,17 +1726,20 @@ function launchCodexProcess(
|
|
|
1718
1726
|
});
|
|
1719
1727
|
const completed = new Promise((resolve, reject) => {
|
|
1720
1728
|
child.once("error", reject);
|
|
1721
|
-
child.once("close", (code) => {
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1729
|
+
child.once("close", (code) => {
|
|
1730
|
+
void sessionCheckpoint.then(() => {
|
|
1731
|
+
if (sessionCheckpointError) reject(sessionCheckpointError);
|
|
1732
|
+
else if (code === 0 && sessionId)
|
|
1733
|
+
resolve({ output: output.slice(-20_000), finalMessage, sessionId });
|
|
1734
|
+
else if (code === 0)
|
|
1735
|
+
reject(
|
|
1736
|
+
new Error(
|
|
1737
|
+
"Agent completed without announcing a resumable session id.",
|
|
1738
|
+
),
|
|
1739
|
+
);
|
|
1740
|
+
else reject(new Error(codexFailureMessage(output, code)));
|
|
1741
|
+
});
|
|
1742
|
+
});
|
|
1732
1743
|
});
|
|
1733
1744
|
return { child, completed };
|
|
1734
1745
|
}
|