@parall/codex-agent 1.28.1 → 1.29.1
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/dist/dispatch.d.ts +4 -14
- package/dist/dispatch.d.ts.map +1 -1
- package/dist/dispatch.js +108 -11
- package/dist/index.js +3 -10
- package/dist/jsonrpc-client.d.ts +2 -1
- package/dist/jsonrpc-client.d.ts.map +1 -1
- package/dist/jsonrpc-client.js +9 -2
- package/package.json +4 -4
- package/src/dispatch.ts +108 -10
- package/src/index.ts +3 -10
- package/src/jsonrpc-client.ts +6 -1
package/dist/dispatch.d.ts
CHANGED
|
@@ -7,20 +7,6 @@ type CodexAppServerAdapterOptions = Pick<CodexAgentConfig, "approvalPolicy" | "c
|
|
|
7
7
|
contextFilePath?: string;
|
|
8
8
|
useParallProvider?: boolean;
|
|
9
9
|
};
|
|
10
|
-
/**
|
|
11
|
-
* Bridge driver backed by `codex app-server --listen stdio://`.
|
|
12
|
-
*
|
|
13
|
-
* Lifecycle:
|
|
14
|
-
* - bridge startup: spawn one `codex app-server` subprocess, do the
|
|
15
|
-
* `initialize` / `initialized` handshake once, keep the pipe open
|
|
16
|
-
* - per Parall dispatch: `thread/start` (first time) or `thread/resume`,
|
|
17
|
-
* then `turn/start`; forward server notifications until `turn/completed`
|
|
18
|
-
* - fork-on-busy: `thread/fork` creates a disposable sibling thread
|
|
19
|
-
*
|
|
20
|
-
* Concurrency: agent-core routes one dispatch per sessionKey at a time, so
|
|
21
|
-
* main + fork can interleave turns on the same stdio pipe. We route
|
|
22
|
-
* notifications by threadId the server stamps on every item/turn event.
|
|
23
|
-
*/
|
|
24
10
|
export declare class CodexAppServerAdapter implements DispatchAdapter {
|
|
25
11
|
private readonly opts;
|
|
26
12
|
private client;
|
|
@@ -28,6 +14,8 @@ export declare class CodexAppServerAdapter implements DispatchAdapter {
|
|
|
28
14
|
private initialized;
|
|
29
15
|
private startPromise;
|
|
30
16
|
private readonly activeTurns;
|
|
17
|
+
private readonly activeTurnIds;
|
|
18
|
+
private readonly pendingInjections;
|
|
31
19
|
private readonly resumedThreadIds;
|
|
32
20
|
private stopping;
|
|
33
21
|
/**
|
|
@@ -44,6 +32,8 @@ export declare class CodexAppServerAdapter implements DispatchAdapter {
|
|
|
44
32
|
model?: string | null;
|
|
45
33
|
reasoningEffort?: string | null;
|
|
46
34
|
}): void;
|
|
35
|
+
enqueueDuringDispatch(sessionKey: string, body: string): Promise<boolean>;
|
|
36
|
+
hasPendingInjections(sessionKey: string): boolean;
|
|
47
37
|
dispatch({ event, bodyForAgent, sessionKey, context }: DispatchOpts): AsyncIterable<RuntimeEvent>;
|
|
48
38
|
getBranchPoint(_sessionKey: string): string | undefined;
|
|
49
39
|
forkSession({ sessionKey: parentSessionKey }: ForkOpts): Promise<ForkSessionHandle | null>;
|
package/dist/dispatch.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../src/dispatch.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../src/dispatch.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EACV,eAAe,EACf,eAAe,EACf,YAAY,EACZ,QAAQ,EACR,iBAAiB,EACjB,aAAa,EACb,YAAY,EACb,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAIpD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAEhE,KAAK,4BAA4B,GAAG,IAAI,CACtC,gBAAgB,EACd,gBAAgB,GAChB,UAAU,GACV,WAAW,GACX,OAAO,GACP,iBAAiB,GACjB,SAAS,GACT,cAAc,CACjB,GAAG;IACF,cAAc,EAAE,mBAAmB,CAAC;IACpC,GAAG,CAAC,EAAE,aAAa,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B,CAAC;AAmCF,qBAAa,qBAAsB,YAAW,eAAe;IA+B/C,OAAO,CAAC,QAAQ,CAAC,IAAI;IA9BjC,OAAO,CAAC,MAAM,CAAmC;IACjD,OAAO,CAAC,IAAI,CAA+C;IAC3D,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,YAAY,CAA8B;IAClD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA+B;IAC3D,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA6B;IAC3D,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAA6B;IAC/D,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAqB;IACtD,OAAO,CAAC,QAAQ,CAAS;IAEzB;;;;;;;OAOG;IACH,OAAO,CAAC,aAAa;gBAYQ,IAAI,EAAE,4BAA4B;IAE/D,YAAY,CAAC,MAAM,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI;IAKhF,qBAAqB,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAqB/E,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;IAI1C,QAAQ,CAAC,EAAE,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,OAAO,EAAE,EAAE,YAAY,GAAG,aAAa,CAAC,YAAY,CAAC;IA6NxG,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAMjD,WAAW,CAAC,EAAE,UAAU,EAAE,gBAAgB,EAAE,EAAE,QAAQ,GAAG,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC;IA+BhG,WAAW,CAAC,EAAE,IAAI,EAAE,EAAE,eAAe;IAIrC,OAAO,CAAC,cAAc;IAKhB,IAAI;YAiBI,aAAa;YA2Bb,OAAO;IAyErB,OAAO,CAAC,qBAAqB;YAmCf,UAAU;IA2CxB,OAAO,CAAC,iBAAiB;CA4B1B"}
|
package/dist/dispatch.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
|
-
import { execSync } from "node:child_process";
|
|
1
|
+
import { execSync, spawn } from "node:child_process";
|
|
3
2
|
import { randomUUID } from "node:crypto";
|
|
4
3
|
import * as fs from "node:fs";
|
|
5
4
|
import { appendPreparedLocalAttachmentRefs, ensureLocalAttachmentGitExclude, pinLocalAttachmentPaths, } from "@parall/agent-core/internal/attachment-input";
|
|
@@ -20,6 +19,21 @@ import { JsonRpcStdioClient } from "./jsonrpc-client.js";
|
|
|
20
19
|
* main + fork can interleave turns on the same stdio pipe. We route
|
|
21
20
|
* notifications by threadId the server stamps on every item/turn event.
|
|
22
21
|
*/
|
|
22
|
+
const IS_WIN32 = process.platform === "win32";
|
|
23
|
+
function quoteWin32Arg(arg) {
|
|
24
|
+
if (!/[\s"&|^<>()]/.test(arg))
|
|
25
|
+
return arg;
|
|
26
|
+
return `"${arg.replace(/"/g, '""')}"`;
|
|
27
|
+
}
|
|
28
|
+
function killWin32Tree(pid) {
|
|
29
|
+
try {
|
|
30
|
+
execSync(`taskkill /T /F /PID ${pid}`, { windowsHide: true, stdio: "ignore" });
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
23
37
|
export class CodexAppServerAdapter {
|
|
24
38
|
opts;
|
|
25
39
|
client = null;
|
|
@@ -27,6 +41,8 @@ export class CodexAppServerAdapter {
|
|
|
27
41
|
initialized = false;
|
|
28
42
|
startPromise = null;
|
|
29
43
|
activeTurns = new Map();
|
|
44
|
+
activeTurnIds = new Map();
|
|
45
|
+
pendingInjections = new Map();
|
|
30
46
|
resumedThreadIds = new Set();
|
|
31
47
|
stopping = false;
|
|
32
48
|
/**
|
|
@@ -55,7 +71,49 @@ export class CodexAppServerAdapter {
|
|
|
55
71
|
if (config.reasoningEffort !== undefined)
|
|
56
72
|
this.opts.reasoningEffort = config.reasoningEffort ?? undefined;
|
|
57
73
|
}
|
|
74
|
+
async enqueueDuringDispatch(sessionKey, body) {
|
|
75
|
+
const client = this.client;
|
|
76
|
+
if (!client || client.isDisposed())
|
|
77
|
+
return false;
|
|
78
|
+
const threadId = this.opts.sessionManager.getThreadId(sessionKey);
|
|
79
|
+
if (!threadId)
|
|
80
|
+
return false;
|
|
81
|
+
const turnId = this.activeTurnIds.get(threadId);
|
|
82
|
+
if (!turnId)
|
|
83
|
+
return false;
|
|
84
|
+
try {
|
|
85
|
+
await client.sendRequest("turn/steer", {
|
|
86
|
+
threadId,
|
|
87
|
+
expectedTurnId: turnId,
|
|
88
|
+
input: buildTurnInput(body, []),
|
|
89
|
+
});
|
|
90
|
+
this.pendingInjections.set(sessionKey, (this.pendingInjections.get(sessionKey) ?? 0) + 1);
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
this.opts.log?.warn?.(`codex-agent: turn/steer failed: ${errToString(err)}`);
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
hasPendingInjections(sessionKey) {
|
|
99
|
+
return (this.pendingInjections.get(sessionKey) ?? 0) > 0;
|
|
100
|
+
}
|
|
58
101
|
async *dispatch({ event, bodyForAgent, sessionKey, context }) {
|
|
102
|
+
const pending = this.pendingInjections.get(sessionKey) ?? 0;
|
|
103
|
+
if (pending > 0) {
|
|
104
|
+
this.pendingInjections.delete(sessionKey);
|
|
105
|
+
const threadId = this.opts.sessionManager.getThreadId(sessionKey);
|
|
106
|
+
if (threadId && this.client && !this.client.isDisposed()) {
|
|
107
|
+
(this.opts.log ?? context.log)?.info?.(`codex-agent[${context.accountId}]: ${pending} steer injection(s) already sent; skipping turn/start`);
|
|
108
|
+
yield {
|
|
109
|
+
type: "runtime_session",
|
|
110
|
+
runtimeSessionId: threadId,
|
|
111
|
+
runtimeLaneKey: sessionKey,
|
|
112
|
+
};
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
(this.opts.log ?? context.log)?.warn?.(`codex-agent[${context.accountId}]: pending steer invalidated (subprocess died); falling through to normal dispatch`);
|
|
116
|
+
}
|
|
59
117
|
await this.ensureStarted(context.log);
|
|
60
118
|
// After ensureStarted resolves the subprocess could still die before we
|
|
61
119
|
// capture the client (handleSubprocessClose nulls this.client). Yield a
|
|
@@ -210,7 +268,9 @@ export class CodexAppServerAdapter {
|
|
|
210
268
|
this.resumedThreadIds.add(freshThreadId);
|
|
211
269
|
threadId = freshThreadId;
|
|
212
270
|
}
|
|
213
|
-
|
|
271
|
+
const turnId = extractTurnId(turnStartResult);
|
|
272
|
+
if (turnId)
|
|
273
|
+
this.activeTurnIds.set(threadId, turnId);
|
|
214
274
|
yield {
|
|
215
275
|
type: "runtime_session",
|
|
216
276
|
runtimeSessionId: threadId,
|
|
@@ -250,8 +310,8 @@ export class CodexAppServerAdapter {
|
|
|
250
310
|
finally {
|
|
251
311
|
releasePreparedAttachments();
|
|
252
312
|
this.activeTurns.delete(threadId);
|
|
313
|
+
this.activeTurnIds.delete(threadId);
|
|
253
314
|
if (!sawTurnEnd) {
|
|
254
|
-
// Defensive: if we bailed early, make sure we leave no dangling sink.
|
|
255
315
|
sink.close();
|
|
256
316
|
}
|
|
257
317
|
}
|
|
@@ -270,10 +330,19 @@ export class CodexAppServerAdapter {
|
|
|
270
330
|
return null;
|
|
271
331
|
const handle = this.opts.sessionManager.createForkSessionKey();
|
|
272
332
|
try {
|
|
273
|
-
const
|
|
333
|
+
const forkParams = {
|
|
274
334
|
threadId: parentThreadId,
|
|
275
335
|
ephemeral: true,
|
|
276
|
-
}
|
|
336
|
+
};
|
|
337
|
+
if (this.opts.useParallProvider) {
|
|
338
|
+
forkParams.modelProvider = "parall";
|
|
339
|
+
}
|
|
340
|
+
if (this.opts.model)
|
|
341
|
+
forkParams.model = this.opts.model;
|
|
342
|
+
if (this.opts.reasoningEffort) {
|
|
343
|
+
forkParams.config = { modelReasoningEffort: this.opts.reasoningEffort };
|
|
344
|
+
}
|
|
345
|
+
const result = await client.sendRequest("thread/fork", forkParams);
|
|
277
346
|
const forkedThreadId = extractThreadId(result);
|
|
278
347
|
if (!forkedThreadId) {
|
|
279
348
|
this.opts.log?.warn?.("codex-agent: thread/fork returned no thread id");
|
|
@@ -300,10 +369,14 @@ export class CodexAppServerAdapter {
|
|
|
300
369
|
this.proc = null;
|
|
301
370
|
this.client = null;
|
|
302
371
|
this.initialized = false;
|
|
372
|
+
this.activeTurnIds.clear();
|
|
373
|
+
this.pendingInjections.clear();
|
|
303
374
|
if (client)
|
|
304
375
|
client.dispose(new Error("adapter stopped"));
|
|
305
376
|
if (proc && proc.exitCode === null && proc.signalCode === null) {
|
|
306
|
-
proc.
|
|
377
|
+
if (!IS_WIN32 || !proc.pid || !killWin32Tree(proc.pid)) {
|
|
378
|
+
proc.kill("SIGTERM");
|
|
379
|
+
}
|
|
307
380
|
}
|
|
308
381
|
}
|
|
309
382
|
async ensureStarted(log) {
|
|
@@ -313,6 +386,8 @@ export class CodexAppServerAdapter {
|
|
|
313
386
|
activeSink.close();
|
|
314
387
|
}
|
|
315
388
|
this.activeTurns.clear();
|
|
389
|
+
this.activeTurnIds.clear();
|
|
390
|
+
this.pendingInjections.clear();
|
|
316
391
|
this.client = null;
|
|
317
392
|
this.proc = null;
|
|
318
393
|
this.initialized = false;
|
|
@@ -355,10 +430,11 @@ export class CodexAppServerAdapter {
|
|
|
355
430
|
}
|
|
356
431
|
const args = ["app-server", "--listen", "stdio://"];
|
|
357
432
|
(log ?? this.opts.log)?.info?.(`codex-agent: spawning ${this.opts.codexBin} ${args.join(" ")}`);
|
|
358
|
-
const proc = spawn(this.opts.codexBin, args, {
|
|
433
|
+
const proc = spawn(IS_WIN32 ? quoteWin32Arg(this.opts.codexBin) : this.opts.codexBin, IS_WIN32 ? args.map(quoteWin32Arg) : args, {
|
|
359
434
|
cwd: this.opts.workspaceDir,
|
|
360
435
|
env,
|
|
361
436
|
stdio: ["pipe", "pipe", "pipe"],
|
|
437
|
+
shell: IS_WIN32,
|
|
362
438
|
});
|
|
363
439
|
proc.stderr.setEncoding("utf8");
|
|
364
440
|
proc.stderr.on("data", (chunk) => {
|
|
@@ -369,7 +445,11 @@ export class CodexAppServerAdapter {
|
|
|
369
445
|
// start), we kill the half-spawned subprocess and clear local refs here
|
|
370
446
|
// — leaving them on `this` would leak the orphan process and cause the
|
|
371
447
|
// next ensureStarted() to spawn a second app-server on top of it.
|
|
372
|
-
const client = new JsonRpcStdioClient(proc)
|
|
448
|
+
const client = new JsonRpcStdioClient(proc, undefined, (p) => {
|
|
449
|
+
if (!IS_WIN32 || !p.pid || !killWin32Tree(p.pid)) {
|
|
450
|
+
p.kill("SIGTERM");
|
|
451
|
+
}
|
|
452
|
+
});
|
|
373
453
|
client.setNotificationHandler((method, params) => this.routeNotification(method, params));
|
|
374
454
|
proc.once("close", (code, signal) => this.handleSubprocessClose(proc, code, signal, log));
|
|
375
455
|
proc.once("error", (err) => this.handleSubprocessClose(proc, null, null, log, err));
|
|
@@ -382,8 +462,11 @@ export class CodexAppServerAdapter {
|
|
|
382
462
|
}
|
|
383
463
|
catch (err) {
|
|
384
464
|
client.dispose(err instanceof Error ? err : new Error(String(err)));
|
|
385
|
-
if (proc.exitCode === null && proc.signalCode === null)
|
|
386
|
-
proc.
|
|
465
|
+
if (proc.exitCode === null && proc.signalCode === null) {
|
|
466
|
+
if (!IS_WIN32 || !proc.pid || !killWin32Tree(proc.pid)) {
|
|
467
|
+
proc.kill("SIGTERM");
|
|
468
|
+
}
|
|
469
|
+
}
|
|
387
470
|
throw err;
|
|
388
471
|
}
|
|
389
472
|
this.proc = proc;
|
|
@@ -413,6 +496,8 @@ export class CodexAppServerAdapter {
|
|
|
413
496
|
activeSink.close();
|
|
414
497
|
}
|
|
415
498
|
this.activeTurns.clear();
|
|
499
|
+
this.activeTurnIds.clear();
|
|
500
|
+
this.pendingInjections.clear();
|
|
416
501
|
this.resumedThreadIds.clear();
|
|
417
502
|
this.client = null;
|
|
418
503
|
this.proc = null;
|
|
@@ -478,6 +563,7 @@ export class CodexAppServerAdapter {
|
|
|
478
563
|
sink.push({ kind: "runtime", event });
|
|
479
564
|
}
|
|
480
565
|
if (method === "turn/completed") {
|
|
566
|
+
this.activeTurnIds.delete(threadId);
|
|
481
567
|
sink.push({ kind: "turn_end", threadId });
|
|
482
568
|
}
|
|
483
569
|
}
|
|
@@ -568,6 +654,17 @@ function extractThreadId(result) {
|
|
|
568
654
|
return thread.id;
|
|
569
655
|
return undefined;
|
|
570
656
|
}
|
|
657
|
+
function extractTurnId(result) {
|
|
658
|
+
if (!result || typeof result !== "object")
|
|
659
|
+
return undefined;
|
|
660
|
+
const r = result;
|
|
661
|
+
if (typeof r.turnId === "string")
|
|
662
|
+
return r.turnId;
|
|
663
|
+
const turn = r.turn;
|
|
664
|
+
if (turn && typeof turn.id === "string")
|
|
665
|
+
return turn.id;
|
|
666
|
+
return undefined;
|
|
667
|
+
}
|
|
571
668
|
function extractThreadIdFromNotification(params) {
|
|
572
669
|
if (!params || typeof params !== "object")
|
|
573
670
|
return undefined;
|
package/dist/index.js
CHANGED
|
@@ -1,18 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import * as os from "node:os";
|
|
3
|
-
import { ParallAgentGateway, createPlatformConfigManager, isPlatformManagedProfile, parseShutdownDeadlineMs } from "@parall/agent-core";
|
|
3
|
+
import { ParallAgentGateway, createPlatformConfigManager, createLogger, isPlatformManagedProfile, parseShutdownDeadlineMs } from "@parall/agent-core";
|
|
4
4
|
import { ApiError, ParallClient, ParallWs } from "@parall/sdk";
|
|
5
5
|
import { buildCodexRuntimeKey, contextFilePathForSession, resolveCodexAgentConfig, resolveWsUrl, sessionStateFilePathForRuntime, stepIdFilePathForSession, } from "./config.js";
|
|
6
6
|
import { CodexAppServerAdapter } from "./dispatch.js";
|
|
7
7
|
import { CodexSessionManager } from "./session-manager.js";
|
|
8
8
|
import { ensureCodexWorkspace, ensureParallProvider, ensureWorkspaceTrusted, isParallProxyMode } from "./workspace.js";
|
|
9
|
-
|
|
10
|
-
return {
|
|
11
|
-
info: (msg) => console.log(`[${prefix}] ${msg}`),
|
|
12
|
-
warn: (msg) => console.warn(`[${prefix}] ${msg}`),
|
|
13
|
-
error: (msg) => console.error(`[${prefix}] ${msg}`),
|
|
14
|
-
};
|
|
15
|
-
}
|
|
9
|
+
const log = createLogger("codex-agent");
|
|
16
10
|
async function getAgentMeWithLegacyFallback(client, orgId) {
|
|
17
11
|
try {
|
|
18
12
|
return await client.getAgentMe(orgId);
|
|
@@ -27,7 +21,6 @@ async function getAgentMeWithLegacyFallback(client, orgId) {
|
|
|
27
21
|
}
|
|
28
22
|
async function main() {
|
|
29
23
|
const config = resolveCodexAgentConfig(process.env);
|
|
30
|
-
const log = createLogger("codex-agent");
|
|
31
24
|
const client = new ParallClient({
|
|
32
25
|
baseUrl: config.apiUrl,
|
|
33
26
|
token: config.apiKey,
|
|
@@ -136,6 +129,6 @@ async function main() {
|
|
|
136
129
|
}
|
|
137
130
|
}
|
|
138
131
|
main().catch((err) => {
|
|
139
|
-
|
|
132
|
+
log.error(`fatal: ${String(err)}`);
|
|
140
133
|
process.exitCode = 1;
|
|
141
134
|
});
|
package/dist/jsonrpc-client.d.ts
CHANGED
|
@@ -33,12 +33,13 @@ export type NotificationHandler = (method: string, params: unknown) => void;
|
|
|
33
33
|
export declare class JsonRpcStdioClient {
|
|
34
34
|
private readonly proc;
|
|
35
35
|
private readonly requestTimeoutMs;
|
|
36
|
+
private readonly killProcess?;
|
|
36
37
|
private nextId;
|
|
37
38
|
private readonly pending;
|
|
38
39
|
private buffer;
|
|
39
40
|
private onNotification;
|
|
40
41
|
private disposed;
|
|
41
|
-
constructor(proc: ChildProcessWithoutNullStreams, requestTimeoutMs?: number);
|
|
42
|
+
constructor(proc: ChildProcessWithoutNullStreams, requestTimeoutMs?: number, killProcess?: ((proc: ChildProcessWithoutNullStreams) => void) | undefined);
|
|
42
43
|
setNotificationHandler(handler: NotificationHandler): void;
|
|
43
44
|
sendRequest(method: string, params?: unknown): Promise<unknown>;
|
|
44
45
|
sendNotification(method: string, params?: unknown): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jsonrpc-client.d.ts","sourceRoot":"","sources":["../src/jsonrpc-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,oBAAoB,CAAC;AAEzE,KAAK,SAAS,GAAG,MAAM,GAAG,MAAM,CAAC;AAEjC,MAAM,MAAM,cAAc,GAAG;IAC3B,OAAO,EAAE,KAAK,CAAC;IACf,EAAE,EAAE,SAAS,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,KAAK,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,EAAE,KAAK,CAAC;IACf,EAAE,EAAE,SAAS,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;CAC3D,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;AAW5E;;;;;;;GAOG;AACH,qBAAa,kBAAkB;IAQ3B,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,gBAAgB;
|
|
1
|
+
{"version":3,"file":"jsonrpc-client.d.ts","sourceRoot":"","sources":["../src/jsonrpc-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,oBAAoB,CAAC;AAEzE,KAAK,SAAS,GAAG,MAAM,GAAG,MAAM,CAAC;AAEjC,MAAM,MAAM,cAAc,GAAG;IAC3B,OAAO,EAAE,KAAK,CAAC;IACf,EAAE,EAAE,SAAS,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,KAAK,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,EAAE,KAAK,CAAC;IACf,EAAE,EAAE,SAAS,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;CAC3D,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;AAW5E;;;;;;;GAOG;AACH,qBAAa,kBAAkB;IAQ3B,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IACjC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC;IAT/B,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiC;IACzD,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,cAAc,CAAoC;IAC1D,OAAO,CAAC,QAAQ,CAAS;gBAGN,IAAI,EAAE,8BAA8B,EACpC,gBAAgB,GAAE,MAAmC,EACrD,WAAW,CAAC,GAAE,CAAC,IAAI,EAAE,8BAA8B,KAAK,IAAI,aAAA;IAS/E,sBAAsB,CAAC,OAAO,EAAE,mBAAmB;IAInD,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAoB/D,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO;IAMjD,UAAU,IAAI,OAAO;IAIrB,OAAO,CAAC,GAAG,EAAE,KAAK;IAUlB,OAAO,CAAC,UAAU;IAQlB,OAAO,CAAC,aAAa;IAWrB,OAAO,CAAC,MAAM;IAWd,OAAO,CAAC,UAAU;CAyBnB"}
|
package/dist/jsonrpc-client.js
CHANGED
|
@@ -11,14 +11,16 @@ const DEFAULT_REQUEST_TIMEOUT_MS = 120_000;
|
|
|
11
11
|
export class JsonRpcStdioClient {
|
|
12
12
|
proc;
|
|
13
13
|
requestTimeoutMs;
|
|
14
|
+
killProcess;
|
|
14
15
|
nextId = 1;
|
|
15
16
|
pending = new Map();
|
|
16
17
|
buffer = "";
|
|
17
18
|
onNotification = null;
|
|
18
19
|
disposed = false;
|
|
19
|
-
constructor(proc, requestTimeoutMs = DEFAULT_REQUEST_TIMEOUT_MS) {
|
|
20
|
+
constructor(proc, requestTimeoutMs = DEFAULT_REQUEST_TIMEOUT_MS, killProcess) {
|
|
20
21
|
this.proc = proc;
|
|
21
22
|
this.requestTimeoutMs = requestTimeoutMs;
|
|
23
|
+
this.killProcess = killProcess;
|
|
22
24
|
proc.stdout.setEncoding("utf8");
|
|
23
25
|
proc.stdout.on("data", (chunk) => this.ingest(chunk));
|
|
24
26
|
proc.once("close", () => this.dispose(new Error("app-server subprocess closed")));
|
|
@@ -78,7 +80,12 @@ export class JsonRpcStdioClient {
|
|
|
78
80
|
killUnhealthy(err) {
|
|
79
81
|
this.dispose(err);
|
|
80
82
|
if (this.proc.exitCode === null && this.proc.signalCode === null) {
|
|
81
|
-
this.
|
|
83
|
+
if (this.killProcess) {
|
|
84
|
+
this.killProcess(this.proc);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
this.proc.kill("SIGTERM");
|
|
88
|
+
}
|
|
82
89
|
}
|
|
83
90
|
}
|
|
84
91
|
ingest(chunk) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parall/codex-agent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.29.1",
|
|
4
4
|
"description": "Codex CLI bridge runtime for self-hosted Parall agents",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -26,9 +26,9 @@
|
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"smol-toml": "^1.6.1",
|
|
29
|
-
"@parall/agent-core": "1.
|
|
30
|
-
"@parall/cli": "1.
|
|
31
|
-
"@parall/sdk": "1.
|
|
29
|
+
"@parall/agent-core": "1.29.1",
|
|
30
|
+
"@parall/cli": "1.29.1",
|
|
31
|
+
"@parall/sdk": "1.29.1"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@types/node": "^22.0.0",
|
package/src/dispatch.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process";
|
|
2
|
-
import { execSync } from "node:child_process";
|
|
1
|
+
import { execSync, spawn, type ChildProcessWithoutNullStreams } from "node:child_process";
|
|
3
2
|
import { randomUUID } from "node:crypto";
|
|
4
3
|
import * as fs from "node:fs";
|
|
5
4
|
import {
|
|
@@ -58,12 +57,28 @@ type TurnEventEnvelope =
|
|
|
58
57
|
* main + fork can interleave turns on the same stdio pipe. We route
|
|
59
58
|
* notifications by threadId the server stamps on every item/turn event.
|
|
60
59
|
*/
|
|
60
|
+
const IS_WIN32 = process.platform === "win32";
|
|
61
|
+
|
|
62
|
+
function quoteWin32Arg(arg: string): string {
|
|
63
|
+
if (!/[\s"&|^<>()]/.test(arg)) return arg;
|
|
64
|
+
return `"${arg.replace(/"/g, '""')}"`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function killWin32Tree(pid: number): boolean {
|
|
68
|
+
try {
|
|
69
|
+
execSync(`taskkill /T /F /PID ${pid}`, { windowsHide: true, stdio: "ignore" });
|
|
70
|
+
return true;
|
|
71
|
+
} catch { return false; }
|
|
72
|
+
}
|
|
73
|
+
|
|
61
74
|
export class CodexAppServerAdapter implements DispatchAdapter {
|
|
62
75
|
private client: JsonRpcStdioClient | null = null;
|
|
63
76
|
private proc: ChildProcessWithoutNullStreams | null = null;
|
|
64
77
|
private initialized = false;
|
|
65
78
|
private startPromise: Promise<void> | null = null;
|
|
66
79
|
private readonly activeTurns = new Map<string, TurnSink>();
|
|
80
|
+
private readonly activeTurnIds = new Map<string, string>();
|
|
81
|
+
private readonly pendingInjections = new Map<string, number>();
|
|
67
82
|
private readonly resumedThreadIds = new Set<string>();
|
|
68
83
|
private stopping = false;
|
|
69
84
|
|
|
@@ -94,7 +109,52 @@ export class CodexAppServerAdapter implements DispatchAdapter {
|
|
|
94
109
|
if (config.reasoningEffort !== undefined) this.opts.reasoningEffort = config.reasoningEffort ?? undefined;
|
|
95
110
|
}
|
|
96
111
|
|
|
112
|
+
async enqueueDuringDispatch(sessionKey: string, body: string): Promise<boolean> {
|
|
113
|
+
const client = this.client;
|
|
114
|
+
if (!client || client.isDisposed()) return false;
|
|
115
|
+
const threadId = this.opts.sessionManager.getThreadId(sessionKey);
|
|
116
|
+
if (!threadId) return false;
|
|
117
|
+
const turnId = this.activeTurnIds.get(threadId);
|
|
118
|
+
if (!turnId) return false;
|
|
119
|
+
try {
|
|
120
|
+
await client.sendRequest("turn/steer", {
|
|
121
|
+
threadId,
|
|
122
|
+
expectedTurnId: turnId,
|
|
123
|
+
input: buildTurnInput(body, []),
|
|
124
|
+
});
|
|
125
|
+
this.pendingInjections.set(sessionKey, (this.pendingInjections.get(sessionKey) ?? 0) + 1);
|
|
126
|
+
return true;
|
|
127
|
+
} catch (err) {
|
|
128
|
+
this.opts.log?.warn?.(`codex-agent: turn/steer failed: ${errToString(err)}`);
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
hasPendingInjections(sessionKey: string): boolean {
|
|
134
|
+
return (this.pendingInjections.get(sessionKey) ?? 0) > 0;
|
|
135
|
+
}
|
|
136
|
+
|
|
97
137
|
async *dispatch({ event, bodyForAgent, sessionKey, context }: DispatchOpts): AsyncIterable<RuntimeEvent> {
|
|
138
|
+
const pending = this.pendingInjections.get(sessionKey) ?? 0;
|
|
139
|
+
if (pending > 0) {
|
|
140
|
+
this.pendingInjections.delete(sessionKey);
|
|
141
|
+
const threadId = this.opts.sessionManager.getThreadId(sessionKey);
|
|
142
|
+
if (threadId && this.client && !this.client.isDisposed()) {
|
|
143
|
+
(this.opts.log ?? context.log)?.info?.(
|
|
144
|
+
`codex-agent[${context.accountId}]: ${pending} steer injection(s) already sent; skipping turn/start`,
|
|
145
|
+
);
|
|
146
|
+
yield {
|
|
147
|
+
type: "runtime_session",
|
|
148
|
+
runtimeSessionId: threadId,
|
|
149
|
+
runtimeLaneKey: sessionKey,
|
|
150
|
+
};
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
(this.opts.log ?? context.log)?.warn?.(
|
|
154
|
+
`codex-agent[${context.accountId}]: pending steer invalidated (subprocess died); falling through to normal dispatch`,
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
98
158
|
await this.ensureStarted(context.log);
|
|
99
159
|
// After ensureStarted resolves the subprocess could still die before we
|
|
100
160
|
// capture the client (handleSubprocessClose nulls this.client). Yield a
|
|
@@ -247,7 +307,8 @@ export class CodexAppServerAdapter implements DispatchAdapter {
|
|
|
247
307
|
threadId = freshThreadId;
|
|
248
308
|
}
|
|
249
309
|
|
|
250
|
-
|
|
310
|
+
const turnId = extractTurnId(turnStartResult);
|
|
311
|
+
if (turnId) this.activeTurnIds.set(threadId, turnId);
|
|
251
312
|
yield {
|
|
252
313
|
type: "runtime_session",
|
|
253
314
|
runtimeSessionId: threadId,
|
|
@@ -287,8 +348,8 @@ export class CodexAppServerAdapter implements DispatchAdapter {
|
|
|
287
348
|
} finally {
|
|
288
349
|
releasePreparedAttachments();
|
|
289
350
|
this.activeTurns.delete(threadId!);
|
|
351
|
+
this.activeTurnIds.delete(threadId!);
|
|
290
352
|
if (!sawTurnEnd) {
|
|
291
|
-
// Defensive: if we bailed early, make sure we leave no dangling sink.
|
|
292
353
|
sink.close();
|
|
293
354
|
}
|
|
294
355
|
}
|
|
@@ -307,10 +368,18 @@ export class CodexAppServerAdapter implements DispatchAdapter {
|
|
|
307
368
|
if (!parentThreadId) return null;
|
|
308
369
|
const handle = this.opts.sessionManager.createForkSessionKey();
|
|
309
370
|
try {
|
|
310
|
-
const
|
|
371
|
+
const forkParams: Record<string, unknown> = {
|
|
311
372
|
threadId: parentThreadId,
|
|
312
373
|
ephemeral: true,
|
|
313
|
-
}
|
|
374
|
+
};
|
|
375
|
+
if (this.opts.useParallProvider) {
|
|
376
|
+
forkParams.modelProvider = "parall";
|
|
377
|
+
}
|
|
378
|
+
if (this.opts.model) forkParams.model = this.opts.model;
|
|
379
|
+
if (this.opts.reasoningEffort) {
|
|
380
|
+
forkParams.config = { modelReasoningEffort: this.opts.reasoningEffort };
|
|
381
|
+
}
|
|
382
|
+
const result = await client.sendRequest("thread/fork", forkParams);
|
|
314
383
|
const forkedThreadId = extractThreadId(result);
|
|
315
384
|
if (!forkedThreadId) {
|
|
316
385
|
this.opts.log?.warn?.("codex-agent: thread/fork returned no thread id");
|
|
@@ -339,9 +408,13 @@ export class CodexAppServerAdapter implements DispatchAdapter {
|
|
|
339
408
|
this.proc = null;
|
|
340
409
|
this.client = null;
|
|
341
410
|
this.initialized = false;
|
|
411
|
+
this.activeTurnIds.clear();
|
|
412
|
+
this.pendingInjections.clear();
|
|
342
413
|
if (client) client.dispose(new Error("adapter stopped"));
|
|
343
414
|
if (proc && proc.exitCode === null && proc.signalCode === null) {
|
|
344
|
-
proc.
|
|
415
|
+
if (!IS_WIN32 || !proc.pid || !killWin32Tree(proc.pid)) {
|
|
416
|
+
proc.kill("SIGTERM");
|
|
417
|
+
}
|
|
345
418
|
}
|
|
346
419
|
}
|
|
347
420
|
|
|
@@ -352,6 +425,8 @@ export class CodexAppServerAdapter implements DispatchAdapter {
|
|
|
352
425
|
activeSink.close();
|
|
353
426
|
}
|
|
354
427
|
this.activeTurns.clear();
|
|
428
|
+
this.activeTurnIds.clear();
|
|
429
|
+
this.pendingInjections.clear();
|
|
355
430
|
this.client = null;
|
|
356
431
|
this.proc = null;
|
|
357
432
|
this.initialized = false;
|
|
@@ -393,10 +468,13 @@ export class CodexAppServerAdapter implements DispatchAdapter {
|
|
|
393
468
|
}
|
|
394
469
|
const args = ["app-server", "--listen", "stdio://"];
|
|
395
470
|
(log ?? this.opts.log)?.info?.(`codex-agent: spawning ${this.opts.codexBin} ${args.join(" ")}`);
|
|
396
|
-
const proc = spawn(
|
|
471
|
+
const proc = spawn(
|
|
472
|
+
IS_WIN32 ? quoteWin32Arg(this.opts.codexBin) : this.opts.codexBin,
|
|
473
|
+
IS_WIN32 ? args.map(quoteWin32Arg) : args, {
|
|
397
474
|
cwd: this.opts.workspaceDir,
|
|
398
475
|
env,
|
|
399
476
|
stdio: ["pipe", "pipe", "pipe"],
|
|
477
|
+
shell: IS_WIN32,
|
|
400
478
|
}) as ChildProcessWithoutNullStreams;
|
|
401
479
|
|
|
402
480
|
proc.stderr.setEncoding("utf8");
|
|
@@ -409,7 +487,11 @@ export class CodexAppServerAdapter implements DispatchAdapter {
|
|
|
409
487
|
// start), we kill the half-spawned subprocess and clear local refs here
|
|
410
488
|
// — leaving them on `this` would leak the orphan process and cause the
|
|
411
489
|
// next ensureStarted() to spawn a second app-server on top of it.
|
|
412
|
-
const client = new JsonRpcStdioClient(proc)
|
|
490
|
+
const client = new JsonRpcStdioClient(proc, undefined, (p) => {
|
|
491
|
+
if (!IS_WIN32 || !p.pid || !killWin32Tree(p.pid)) {
|
|
492
|
+
p.kill("SIGTERM");
|
|
493
|
+
}
|
|
494
|
+
});
|
|
413
495
|
client.setNotificationHandler((method, params) => this.routeNotification(method, params));
|
|
414
496
|
|
|
415
497
|
proc.once("close", (code, signal) => this.handleSubprocessClose(proc, code, signal, log));
|
|
@@ -423,7 +505,11 @@ export class CodexAppServerAdapter implements DispatchAdapter {
|
|
|
423
505
|
client.sendNotification("initialized", {});
|
|
424
506
|
} catch (err) {
|
|
425
507
|
client.dispose(err instanceof Error ? err : new Error(String(err)));
|
|
426
|
-
if (proc.exitCode === null && proc.signalCode === null)
|
|
508
|
+
if (proc.exitCode === null && proc.signalCode === null) {
|
|
509
|
+
if (!IS_WIN32 || !proc.pid || !killWin32Tree(proc.pid)) {
|
|
510
|
+
proc.kill("SIGTERM");
|
|
511
|
+
}
|
|
512
|
+
}
|
|
427
513
|
throw err;
|
|
428
514
|
}
|
|
429
515
|
|
|
@@ -459,6 +545,8 @@ export class CodexAppServerAdapter implements DispatchAdapter {
|
|
|
459
545
|
activeSink.close();
|
|
460
546
|
}
|
|
461
547
|
this.activeTurns.clear();
|
|
548
|
+
this.activeTurnIds.clear();
|
|
549
|
+
this.pendingInjections.clear();
|
|
462
550
|
this.resumedThreadIds.clear();
|
|
463
551
|
this.client = null;
|
|
464
552
|
this.proc = null;
|
|
@@ -531,6 +619,7 @@ export class CodexAppServerAdapter implements DispatchAdapter {
|
|
|
531
619
|
sink.push({ kind: "runtime", event });
|
|
532
620
|
}
|
|
533
621
|
if (method === "turn/completed") {
|
|
622
|
+
this.activeTurnIds.delete(threadId);
|
|
534
623
|
sink.push({ kind: "turn_end", threadId });
|
|
535
624
|
}
|
|
536
625
|
}
|
|
@@ -627,6 +716,15 @@ function extractThreadId(result: unknown): string | undefined {
|
|
|
627
716
|
return undefined;
|
|
628
717
|
}
|
|
629
718
|
|
|
719
|
+
function extractTurnId(result: unknown): string | undefined {
|
|
720
|
+
if (!result || typeof result !== "object") return undefined;
|
|
721
|
+
const r = result as Record<string, unknown>;
|
|
722
|
+
if (typeof r.turnId === "string") return r.turnId;
|
|
723
|
+
const turn = r.turn as Record<string, unknown> | undefined;
|
|
724
|
+
if (turn && typeof turn.id === "string") return turn.id;
|
|
725
|
+
return undefined;
|
|
726
|
+
}
|
|
727
|
+
|
|
630
728
|
function extractThreadIdFromNotification(params: unknown): string | undefined {
|
|
631
729
|
if (!params || typeof params !== "object") return undefined;
|
|
632
730
|
const p = params as Record<string, unknown>;
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import * as os from "node:os";
|
|
4
|
-
import { ParallAgentGateway, createPlatformConfigManager, isPlatformManagedProfile, parseShutdownDeadlineMs } from "@parall/agent-core";
|
|
4
|
+
import { ParallAgentGateway, createPlatformConfigManager, createLogger, isPlatformManagedProfile, parseShutdownDeadlineMs } from "@parall/agent-core";
|
|
5
5
|
import { ApiError, ParallClient, ParallWs } from "@parall/sdk";
|
|
6
6
|
import {
|
|
7
7
|
buildCodexRuntimeKey,
|
|
@@ -15,13 +15,7 @@ import { CodexAppServerAdapter } from "./dispatch.js";
|
|
|
15
15
|
import { CodexSessionManager } from "./session-manager.js";
|
|
16
16
|
import { ensureCodexWorkspace, ensureParallProvider, ensureWorkspaceTrusted, isParallProxyMode } from "./workspace.js";
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
return {
|
|
20
|
-
info: (msg: string) => console.log(`[${prefix}] ${msg}`),
|
|
21
|
-
warn: (msg: string) => console.warn(`[${prefix}] ${msg}`),
|
|
22
|
-
error: (msg: string) => console.error(`[${prefix}] ${msg}`),
|
|
23
|
-
};
|
|
24
|
-
}
|
|
18
|
+
const log = createLogger("codex-agent");
|
|
25
19
|
|
|
26
20
|
async function getAgentMeWithLegacyFallback(client: ParallClient, orgId: string) {
|
|
27
21
|
try {
|
|
@@ -37,7 +31,6 @@ async function getAgentMeWithLegacyFallback(client: ParallClient, orgId: string)
|
|
|
37
31
|
|
|
38
32
|
async function main() {
|
|
39
33
|
const config = resolveCodexAgentConfig(process.env);
|
|
40
|
-
const log = createLogger("codex-agent");
|
|
41
34
|
|
|
42
35
|
const client = new ParallClient({
|
|
43
36
|
baseUrl: config.apiUrl,
|
|
@@ -156,6 +149,6 @@ async function main() {
|
|
|
156
149
|
}
|
|
157
150
|
|
|
158
151
|
main().catch((err) => {
|
|
159
|
-
|
|
152
|
+
log.error(`fatal: ${String(err)}`);
|
|
160
153
|
process.exitCode = 1;
|
|
161
154
|
});
|
package/src/jsonrpc-client.ts
CHANGED
|
@@ -51,6 +51,7 @@ export class JsonRpcStdioClient {
|
|
|
51
51
|
constructor(
|
|
52
52
|
private readonly proc: ChildProcessWithoutNullStreams,
|
|
53
53
|
private readonly requestTimeoutMs: number = DEFAULT_REQUEST_TIMEOUT_MS,
|
|
54
|
+
private readonly killProcess?: (proc: ChildProcessWithoutNullStreams) => void,
|
|
54
55
|
) {
|
|
55
56
|
proc.stdout.setEncoding("utf8");
|
|
56
57
|
proc.stdout.on("data", (chunk: string) => this.ingest(chunk));
|
|
@@ -114,7 +115,11 @@ export class JsonRpcStdioClient {
|
|
|
114
115
|
private killUnhealthy(err: Error) {
|
|
115
116
|
this.dispose(err);
|
|
116
117
|
if (this.proc.exitCode === null && this.proc.signalCode === null) {
|
|
117
|
-
this.
|
|
118
|
+
if (this.killProcess) {
|
|
119
|
+
this.killProcess(this.proc);
|
|
120
|
+
} else {
|
|
121
|
+
this.proc.kill("SIGTERM");
|
|
122
|
+
}
|
|
118
123
|
}
|
|
119
124
|
}
|
|
120
125
|
|