@parall/codex-agent 1.28.0 → 1.29.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.
@@ -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;
@@ -1 +1 @@
1
- {"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../src/dispatch.ts"],"names":[],"mappings":"AAUA,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;AAOF;;;;;;;;;;;;;GAaG;AACH,qBAAa,qBAAsB,YAAW,eAAe;IA6B/C,OAAO,CAAC,QAAQ,CAAC,IAAI;IA5BjC,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,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;IAK/E,QAAQ,CAAC,EAAE,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,OAAO,EAAE,EAAE,YAAY,GAAG,aAAa,CAAC,YAAY,CAAC;IAwMxG,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;IAuBhG,WAAW,CAAC,EAAE,IAAI,EAAE,EAAE,eAAe;IAIrC,OAAO,CAAC,cAAc;IAKhB,IAAI;YAaI,aAAa;YAyBb,OAAO;IA8DrB,OAAO,CAAC,qBAAqB;YAiCf,UAAU;IA2CxB,OAAO,CAAC,iBAAiB;CA2B1B"}
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;IA6B/C,OAAO,CAAC,QAAQ,CAAC,IAAI;IA5BjC,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,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;IAK/E,QAAQ,CAAC,EAAE,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,OAAO,EAAE,EAAE,YAAY,GAAG,aAAa,CAAC,YAAY,CAAC;IAwMxG,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;IAuBhG,WAAW,CAAC,EAAE,IAAI,EAAE,EAAE,eAAe;IAIrC,OAAO,CAAC,cAAc;IAKhB,IAAI;YAeI,aAAa;YAyBb,OAAO;IAyErB,OAAO,CAAC,qBAAqB;YAiCf,UAAU;IA2CxB,OAAO,CAAC,iBAAiB;CA2B1B"}
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;
@@ -303,7 +317,9 @@ export class CodexAppServerAdapter {
303
317
  if (client)
304
318
  client.dispose(new Error("adapter stopped"));
305
319
  if (proc && proc.exitCode === null && proc.signalCode === null) {
306
- proc.kill("SIGTERM");
320
+ if (!IS_WIN32 || !proc.pid || !killWin32Tree(proc.pid)) {
321
+ proc.kill("SIGTERM");
322
+ }
307
323
  }
308
324
  }
309
325
  async ensureStarted(log) {
@@ -355,10 +371,11 @@ export class CodexAppServerAdapter {
355
371
  }
356
372
  const args = ["app-server", "--listen", "stdio://"];
357
373
  (log ?? this.opts.log)?.info?.(`codex-agent: spawning ${this.opts.codexBin} ${args.join(" ")}`);
358
- const proc = spawn(this.opts.codexBin, args, {
374
+ const proc = spawn(IS_WIN32 ? quoteWin32Arg(this.opts.codexBin) : this.opts.codexBin, IS_WIN32 ? args.map(quoteWin32Arg) : args, {
359
375
  cwd: this.opts.workspaceDir,
360
376
  env,
361
377
  stdio: ["pipe", "pipe", "pipe"],
378
+ shell: IS_WIN32,
362
379
  });
363
380
  proc.stderr.setEncoding("utf8");
364
381
  proc.stderr.on("data", (chunk) => {
@@ -369,7 +386,11 @@ export class CodexAppServerAdapter {
369
386
  // start), we kill the half-spawned subprocess and clear local refs here
370
387
  // — leaving them on `this` would leak the orphan process and cause the
371
388
  // next ensureStarted() to spawn a second app-server on top of it.
372
- const client = new JsonRpcStdioClient(proc);
389
+ const client = new JsonRpcStdioClient(proc, undefined, (p) => {
390
+ if (!IS_WIN32 || !p.pid || !killWin32Tree(p.pid)) {
391
+ p.kill("SIGTERM");
392
+ }
393
+ });
373
394
  client.setNotificationHandler((method, params) => this.routeNotification(method, params));
374
395
  proc.once("close", (code, signal) => this.handleSubprocessClose(proc, code, signal, log));
375
396
  proc.once("error", (err) => this.handleSubprocessClose(proc, null, null, log, err));
@@ -382,8 +403,11 @@ export class CodexAppServerAdapter {
382
403
  }
383
404
  catch (err) {
384
405
  client.dispose(err instanceof Error ? err : new Error(String(err)));
385
- if (proc.exitCode === null && proc.signalCode === null)
386
- proc.kill("SIGTERM");
406
+ if (proc.exitCode === null && proc.signalCode === null) {
407
+ if (!IS_WIN32 || !proc.pid || !killWin32Tree(proc.pid)) {
408
+ proc.kill("SIGTERM");
409
+ }
410
+ }
387
411
  throw err;
388
412
  }
389
413
  this.proc = proc;
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
- function createLogger(prefix) {
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
- console.error(`[codex-agent] fatal: ${String(err)}`);
132
+ log.error(`fatal: ${String(err)}`);
140
133
  process.exitCode = 1;
141
134
  });
@@ -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;IARnC,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;IASxE,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;IAOrB,OAAO,CAAC,MAAM;IAWd,OAAO,CAAC,UAAU;CAyBnB"}
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"}
@@ -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.proc.kill("SIGTERM");
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.28.0",
3
+ "version": "1.29.0",
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.28.0",
30
- "@parall/cli": "1.28.0",
31
- "@parall/sdk": "1.28.0"
29
+ "@parall/agent-core": "1.29.0",
30
+ "@parall/cli": "1.29.0",
31
+ "@parall/sdk": "1.29.0"
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,6 +57,20 @@ 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;
@@ -341,7 +354,9 @@ export class CodexAppServerAdapter implements DispatchAdapter {
341
354
  this.initialized = false;
342
355
  if (client) client.dispose(new Error("adapter stopped"));
343
356
  if (proc && proc.exitCode === null && proc.signalCode === null) {
344
- proc.kill("SIGTERM");
357
+ if (!IS_WIN32 || !proc.pid || !killWin32Tree(proc.pid)) {
358
+ proc.kill("SIGTERM");
359
+ }
345
360
  }
346
361
  }
347
362
 
@@ -393,10 +408,13 @@ export class CodexAppServerAdapter implements DispatchAdapter {
393
408
  }
394
409
  const args = ["app-server", "--listen", "stdio://"];
395
410
  (log ?? this.opts.log)?.info?.(`codex-agent: spawning ${this.opts.codexBin} ${args.join(" ")}`);
396
- const proc = spawn(this.opts.codexBin, args, {
411
+ const proc = spawn(
412
+ IS_WIN32 ? quoteWin32Arg(this.opts.codexBin) : this.opts.codexBin,
413
+ IS_WIN32 ? args.map(quoteWin32Arg) : args, {
397
414
  cwd: this.opts.workspaceDir,
398
415
  env,
399
416
  stdio: ["pipe", "pipe", "pipe"],
417
+ shell: IS_WIN32,
400
418
  }) as ChildProcessWithoutNullStreams;
401
419
 
402
420
  proc.stderr.setEncoding("utf8");
@@ -409,7 +427,11 @@ export class CodexAppServerAdapter implements DispatchAdapter {
409
427
  // start), we kill the half-spawned subprocess and clear local refs here
410
428
  // — leaving them on `this` would leak the orphan process and cause the
411
429
  // next ensureStarted() to spawn a second app-server on top of it.
412
- const client = new JsonRpcStdioClient(proc);
430
+ const client = new JsonRpcStdioClient(proc, undefined, (p) => {
431
+ if (!IS_WIN32 || !p.pid || !killWin32Tree(p.pid)) {
432
+ p.kill("SIGTERM");
433
+ }
434
+ });
413
435
  client.setNotificationHandler((method, params) => this.routeNotification(method, params));
414
436
 
415
437
  proc.once("close", (code, signal) => this.handleSubprocessClose(proc, code, signal, log));
@@ -423,7 +445,11 @@ export class CodexAppServerAdapter implements DispatchAdapter {
423
445
  client.sendNotification("initialized", {});
424
446
  } catch (err) {
425
447
  client.dispose(err instanceof Error ? err : new Error(String(err)));
426
- if (proc.exitCode === null && proc.signalCode === null) proc.kill("SIGTERM");
448
+ if (proc.exitCode === null && proc.signalCode === null) {
449
+ if (!IS_WIN32 || !proc.pid || !killWin32Tree(proc.pid)) {
450
+ proc.kill("SIGTERM");
451
+ }
452
+ }
427
453
  throw err;
428
454
  }
429
455
 
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
- function createLogger(prefix: string) {
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
- console.error(`[codex-agent] fatal: ${String(err)}`);
152
+ log.error(`fatal: ${String(err)}`);
160
153
  process.exitCode = 1;
161
154
  });
@@ -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.proc.kill("SIGTERM");
118
+ if (this.killProcess) {
119
+ this.killProcess(this.proc);
120
+ } else {
121
+ this.proc.kill("SIGTERM");
122
+ }
118
123
  }
119
124
  }
120
125