@parall/codex-agent 1.29.3 → 1.31.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/dist/config.js CHANGED
@@ -26,9 +26,9 @@ export function resolveCodexAgentConfig(env = process.env) {
26
26
  || path.join(env.HOME || os.homedir(), ".codex"));
27
27
  // Bridge state lives outside CODEX_HOME to avoid polluting codex's own
28
28
  // directory with non-codex files.
29
- const stateDir = resolvePath(env.PRLL_CODEX_STATE_DIR?.trim()
29
+ const stateDir = resolvePath(env.PRLL_STATE_DIR?.trim()
30
30
  || path.join(env.HOME || os.homedir(), ".parall-agent"));
31
- const workspaceDir = resolvePath(env.PRLL_CODEX_WORKSPACE_DIR?.trim() || path.join(stateDir, "workspace"));
31
+ const workspaceDir = resolvePath(env.PRLL_WORKSPACE_DIR?.trim() || path.join(stateDir, "workspace"));
32
32
  return {
33
33
  apiUrl,
34
34
  apiKey,
package/dist/dispatch.js CHANGED
@@ -56,7 +56,7 @@ export class CodexAppServerAdapter {
56
56
  setActiveTurn(threadId, sink, log) {
57
57
  const existing = this.activeTurns.get(threadId);
58
58
  if (existing) {
59
- (log ?? this.opts.log)?.warn?.(`codex-agent: thread ${threadId} already had an active turn; failing the previous dispatch`);
59
+ (log ?? this.opts.log)?.warn?.(`thread ${threadId} already had an active turn; failing the previous dispatch`);
60
60
  existing.push({ kind: "error", message: `thread ${threadId} replaced by concurrent turn` });
61
61
  existing.close();
62
62
  }
@@ -91,7 +91,7 @@ export class CodexAppServerAdapter {
91
91
  return true;
92
92
  }
93
93
  catch (err) {
94
- this.opts.log?.warn?.(`codex-agent: turn/steer failed: ${errToString(err)}`);
94
+ this.opts.log?.warn?.(`turn/steer failed: ${errToString(err)}`);
95
95
  return false;
96
96
  }
97
97
  }
@@ -104,7 +104,7 @@ export class CodexAppServerAdapter {
104
104
  this.pendingInjections.delete(sessionKey);
105
105
  const threadId = this.opts.sessionManager.getThreadId(sessionKey);
106
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`);
107
+ (this.opts.log ?? context.log)?.info?.(`${pending} steer injection(s) already sent; skipping turn/start`);
108
108
  yield {
109
109
  type: "runtime_session",
110
110
  runtimeSessionId: threadId,
@@ -112,7 +112,7 @@ export class CodexAppServerAdapter {
112
112
  };
113
113
  return;
114
114
  }
115
- (this.opts.log ?? context.log)?.warn?.(`codex-agent[${context.accountId}]: pending steer invalidated (subprocess died); falling through to normal dispatch`);
115
+ (this.opts.log ?? context.log)?.warn?.(`pending steer invalidated (subprocess died); falling through to normal dispatch`);
116
116
  }
117
117
  await this.ensureStarted(context.log);
118
118
  // After ensureStarted resolves the subprocess could still die before we
@@ -156,7 +156,7 @@ export class CodexAppServerAdapter {
156
156
  this.resumedThreadIds.add(threadId);
157
157
  }
158
158
  catch (err) {
159
- log?.warn?.(`codex-agent: thread/resume failed (${errToString(err)}); attempting one-shot fresh-thread start`);
159
+ log?.warn?.(`thread/resume failed (${errToString(err)}); attempting one-shot fresh-thread start`);
160
160
  let freshThreadId;
161
161
  try {
162
162
  freshThreadId = await this.openThread(client, { resumeId: undefined });
@@ -192,7 +192,7 @@ export class CodexAppServerAdapter {
192
192
  releasePreparedAttachments = pinLocalAttachmentPaths(preparedImages);
193
193
  }
194
194
  catch (err) {
195
- log?.warn?.(`codex-agent[${context.accountId}]: failed to prepare local attachments: ${errToString(err)}`);
195
+ log?.warn?.(`failed to prepare local attachments: ${errToString(err)}`);
196
196
  }
197
197
  // `localImage` is a first-class variant of `UserInput` in the Codex
198
198
  // app-server protocol (codex-rs/app-server-protocol/src/protocol/v2.rs
@@ -229,7 +229,7 @@ export class CodexAppServerAdapter {
229
229
  yield { type: "error", message: `Codex turn/start failed: ${message}` };
230
230
  return;
231
231
  }
232
- log?.warn?.(`codex-agent: turn/start on thread ${threadId} failed (${message}); attempting one-shot fresh-thread retry`);
232
+ log?.warn?.(`turn/start on thread ${threadId} failed (${message}); attempting one-shot fresh-thread retry`);
233
233
  this.activeTurns.delete(threadId);
234
234
  let freshThreadId;
235
235
  try {
@@ -345,7 +345,7 @@ export class CodexAppServerAdapter {
345
345
  const result = await client.sendRequest("thread/fork", forkParams);
346
346
  const forkedThreadId = extractThreadId(result);
347
347
  if (!forkedThreadId) {
348
- this.opts.log?.warn?.("codex-agent: thread/fork returned no thread id");
348
+ this.opts.log?.warn?.("thread/fork returned no thread id");
349
349
  return null;
350
350
  }
351
351
  this.opts.sessionManager.recordThreadId(handle.sessionKey, forkedThreadId);
@@ -359,7 +359,7 @@ export class CodexAppServerAdapter {
359
359
  this.opts.sessionManager.cleanupFork(fork.sessionKey);
360
360
  }
361
361
  logForkFailure(err) {
362
- this.opts.log?.warn?.(`codex-agent: thread/fork failed: ${errToString(err)}`);
362
+ this.opts.log?.warn?.(`thread/fork failed: ${errToString(err)}`);
363
363
  return null;
364
364
  }
365
365
  async stop() {
@@ -429,7 +429,7 @@ export class CodexAppServerAdapter {
429
429
  env.PRLL_CONTEXT_FILE = this.opts.contextFilePath;
430
430
  }
431
431
  const args = ["app-server", "--listen", "stdio://"];
432
- (log ?? this.opts.log)?.info?.(`codex-agent: spawning ${this.opts.codexBin} ${args.join(" ")}`);
432
+ (log ?? this.opts.log)?.info?.(`spawning ${this.opts.codexBin} ${args.join(" ")}`);
433
433
  const proc = spawn(IS_WIN32 ? quoteWin32Arg(this.opts.codexBin) : this.opts.codexBin, IS_WIN32 ? args.map(quoteWin32Arg) : args, {
434
434
  cwd: this.opts.workspaceDir,
435
435
  env,
@@ -438,7 +438,7 @@ export class CodexAppServerAdapter {
438
438
  });
439
439
  proc.stderr.setEncoding("utf8");
440
440
  proc.stderr.on("data", (chunk) => {
441
- (log ?? this.opts.log)?.warn?.(`codex-agent[stderr]: ${chunk.trim()}`);
441
+ (log ?? this.opts.log)?.warn?.(`[stderr] ${chunk.trim()}`);
442
442
  });
443
443
  // Don't publish proc/client until the initialize handshake succeeds. If
444
444
  // initialize rejects (timeout, malformed handshake, app-server crash on
@@ -485,10 +485,10 @@ export class CodexAppServerAdapter {
485
485
  : `exited (code=${code ?? "null"}${signal ? `, signal=${signal}` : ""})`;
486
486
  const logger = log ?? this.opts.log;
487
487
  if (this.stopping) {
488
- logger?.info?.(`codex-agent: app-server subprocess ${reason} during graceful stop`);
488
+ logger?.info?.(`app-server subprocess ${reason} during graceful stop`);
489
489
  }
490
490
  else {
491
- logger?.warn?.(`codex-agent: app-server subprocess ${reason}; resetting adapter`);
491
+ logger?.warn?.(`app-server subprocess ${reason}; resetting adapter`);
492
492
  }
493
493
  this.client?.dispose(err ?? new Error(`app-server ${reason}`));
494
494
  for (const activeSink of this.activeTurns.values()) {
@@ -611,7 +611,7 @@ function ensureGitRepo(workingDirectory) {
611
611
  fs.mkdirSync(workingDirectory, { recursive: true });
612
612
  // Only `git init` if the workspace isn't already inside any git repo. A
613
613
  // bare existsSync(.git) check would miss the common case of a user pointing
614
- // PRLL_CODEX_WORKSPACE_DIR at a subdirectory of their existing project,
614
+ // PRLL_WORKSPACE_DIR at a subdirectory of their existing project,
615
615
  // and silently creating a nested repo there would mangle their layout.
616
616
  try {
617
617
  execSync("git rev-parse --is-inside-work-tree", { cwd: workingDirectory, stdio: "pipe" });
package/dist/index.js CHANGED
@@ -1,12 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import * as os from "node:os";
3
- import { ParallAgentGateway, createPlatformConfigManager, createLogger, isPlatformManagedProfile, parseShutdownDeadlineMs } from "@parall/agent-core";
3
+ import { ParallAgentGateway, createPlatformConfigManager, createLogger, childLogger, isPlatformManagedProfile, parseShutdownDeadlineMs, parseProviderConfig, clearAllProviderCreds, llmSource } 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
9
  const log = createLogger("codex-agent");
10
+ let activeLog = log;
10
11
  async function getAgentMeWithLegacyFallback(client, orgId) {
11
12
  try {
12
13
  return await client.getAgentMe(orgId);
@@ -19,7 +20,25 @@ async function getAgentMeWithLegacyFallback(client, orgId) {
19
20
  return { ...user, agent_profile: null };
20
21
  }
21
22
  }
23
+ function resolveProviderEnv() {
24
+ const pc = parseProviderConfig(process.env);
25
+ if (!pc)
26
+ return;
27
+ clearAllProviderCreds(process.env);
28
+ const source = llmSource(pc);
29
+ if (source === "parall") {
30
+ process.env.OPENAI_API_KEY = process.env.PRLL_API_KEY;
31
+ process.env.OPENAI_BASE_URL = `${process.env.PRLL_API_URL}/api/llm/v1`;
32
+ }
33
+ else if (source === "custom") {
34
+ if (pc.openai_api_key)
35
+ process.env.OPENAI_API_KEY = pc.openai_api_key;
36
+ if (pc.openai_base_url)
37
+ process.env.OPENAI_BASE_URL = pc.openai_base_url;
38
+ }
39
+ }
22
40
  async function main() {
41
+ resolveProviderEnv();
23
42
  const config = resolveCodexAgentConfig(process.env);
24
43
  const client = new ParallClient({
25
44
  baseUrl: config.apiUrl,
@@ -28,13 +47,15 @@ async function main() {
28
47
  });
29
48
  const me = await getAgentMeWithLegacyFallback(client, config.orgId);
30
49
  const agentUserId = me.id;
31
- ensureWorkspaceTrusted(config.codexHome, config.workspaceDir, log);
50
+ const agentLog = childLogger(log, agentUserId);
51
+ activeLog = agentLog;
52
+ ensureWorkspaceTrusted(config.codexHome, config.workspaceDir, agentLog);
32
53
  const useParallProvider = isParallProxyMode();
33
54
  if (useParallProvider) {
34
- ensureParallProvider(config.codexHome, config.apiUrl, log);
35
- log.info("parall custom provider configured (Responses API HTTP/SSE mode)");
55
+ ensureParallProvider(config.codexHome, config.apiUrl, agentLog);
56
+ agentLog.info("parall custom provider configured (Responses API HTTP/SSE mode)");
36
57
  }
37
- ensureCodexWorkspace(config.workspaceDir, log, {
58
+ ensureCodexWorkspace(config.workspaceDir, agentLog, {
38
59
  userId: agentUserId,
39
60
  displayName: me.display_name,
40
61
  description: me.agent_profile?.description ?? undefined,
@@ -42,21 +63,21 @@ async function main() {
42
63
  const runtimeKey = config.runtimeKey || buildCodexRuntimeKey(agentUserId);
43
64
  const mainContextFilePath = contextFilePathForSession(config.stateDir, runtimeKey);
44
65
  const sessionStateFilePath = sessionStateFilePathForRuntime(config.stateDir, runtimeKey);
45
- const sessionManager = new CodexSessionManager(runtimeKey, sessionStateFilePath, log);
66
+ const sessionManager = new CodexSessionManager(runtimeKey, sessionStateFilePath, agentLog);
46
67
  const resolvedWsUrl = resolveWsUrl(config.apiUrl, config.wsUrl, config.swimlaneName);
47
68
  const ws = new ParallWs({
48
69
  getTicket: () => client.getWsTicket(),
49
70
  wsUrl: resolvedWsUrl,
50
71
  });
51
- const configMgr = createPlatformConfigManager({ client, stateDir: config.stateDir, runtimeType: "codex", log });
72
+ const configMgr = createPlatformConfigManager({ client, stateDir: config.stateDir, runtimeType: "codex", log: agentLog });
52
73
  const platformDefaults = await configMgr.fetch();
53
74
  let platformManaged = isPlatformManagedProfile(me.agent_profile);
54
75
  const resolvedModel = platformManaged ? (platformDefaults.model ?? config.model) : config.model;
55
76
  const resolvedEffort = platformManaged ? (platformDefaults.thinkingEffort ?? config.reasoningEffort) : config.reasoningEffort;
56
77
  if (platformDefaults.model)
57
- log.info(`platform config: model=${platformDefaults.model} (${platformManaged ? "applied" : "ignored, model_management=self"})`);
78
+ agentLog.info(`platform config: model=${platformDefaults.model} (${platformManaged ? "applied" : "ignored, model_management=self"})`);
58
79
  if (platformDefaults.thinkingEffort)
59
- log.info(`platform config: reasoning_effort=${platformDefaults.thinkingEffort} (${platformManaged ? "applied" : "ignored, model_management=self"})`);
80
+ agentLog.info(`platform config: reasoning_effort=${platformDefaults.thinkingEffort} (${platformManaged ? "applied" : "ignored, model_management=self"})`);
60
81
  const adapter = new CodexAppServerAdapter({
61
82
  codexBin: config.codexBin,
62
83
  codexHome: config.codexHome,
@@ -66,7 +87,7 @@ async function main() {
66
87
  sandbox: config.sandbox,
67
88
  approvalPolicy: config.approvalPolicy,
68
89
  sessionManager,
69
- log,
90
+ log: agentLog,
70
91
  contextFilePath: mainContextFilePath,
71
92
  useParallProvider,
72
93
  });
@@ -91,7 +112,7 @@ async function main() {
91
112
  driver: "app-server",
92
113
  },
93
114
  dispatchAdapter: adapter,
94
- log,
115
+ log: agentLog,
95
116
  shutdownDeadlineMs: parseShutdownDeadlineMs(process.env.PRLL_SHUTDOWN_DEADLINE_MS),
96
117
  contextFilePathForSession: (sessionKey) => contextFilePathForSession(config.stateDir, sessionKey),
97
118
  stepIdFilePathForSession: (sessionKey) => stepIdFilePathForSession(config.stateDir, sessionKey),
@@ -113,13 +134,15 @@ async function main() {
113
134
  reasoningEffort: platformManaged ? (updated.thinkingEffort ?? config.reasoningEffort) : (config.reasoningEffort ?? null),
114
135
  });
115
136
  },
137
+ onNewSession: () => { sessionManager.clearMainThread(); },
138
+ onSessionStale: () => { sessionManager.clearMainThread(); },
116
139
  });
117
140
  const abortController = new AbortController();
118
141
  const abort = () => abortController.abort();
119
142
  process.on("SIGINT", abort);
120
143
  process.on("SIGTERM", abort);
121
144
  try {
122
- log.info(`starting self-hosted Codex runtime (app-server driver) for ${me.display_name} (${agentUserId})`);
145
+ agentLog.info(`starting self-hosted Codex runtime (app-server driver) for ${me.display_name} (${agentUserId})`);
123
146
  await gateway.run(abortController.signal);
124
147
  }
125
148
  finally {
@@ -129,6 +152,6 @@ async function main() {
129
152
  }
130
153
  }
131
154
  main().catch((err) => {
132
- log.error(`fatal: ${String(err)}`);
155
+ activeLog.error(`fatal: ${String(err)}`);
133
156
  process.exitCode = 1;
134
157
  });
@@ -1 +1 @@
1
- {"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../src/workspace.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAExD;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,EACpB,GAAG,CAAC,EAAE;IAAE,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,GACpC,IAAI,CAyEN;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAK/E;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,GAAG,CAAC,EAAE;IAAE,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,GACpC,IAAI,CA+CN;AASD,wBAAgB,oBAAoB,CAClC,YAAY,EAAE,MAAM,EACpB,GAAG,CAAC,EAAE;IAAE,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,EACrC,aAAa,CAAC,EAAE,aAAa,GAC5B,IAAI,CAqBN"}
1
+ {"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../src/workspace.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAExD;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,EACpB,GAAG,CAAC,EAAE;IAAE,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,GACpC,IAAI,CAyEN;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAK/E;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,GAAG,CAAC,EAAE;IAAE,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,GACpC,IAAI,CA6CN;AASD,wBAAgB,oBAAoB,CAClC,YAAY,EAAE,MAAM,EACpB,GAAG,CAAC,EAAE;IAAE,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,EACrC,aAAa,CAAC,EAAE,aAAa,GAC5B,IAAI,CAqBN"}
package/dist/workspace.js CHANGED
@@ -145,14 +145,12 @@ export function ensureParallProvider(codexHome, apiUrl, log) {
145
145
  ].join("\n");
146
146
  const headerIdx = content.indexOf(sectionHeader);
147
147
  if (headerIdx !== -1) {
148
- const sectionEnd = findSectionEnd(content, headerIdx + sectionHeader.length + 1);
149
- // Only consume a contiguous [model_providers.parall.auth] subtable —
150
- // one that sits within the provider's own section, not after unrelated
151
- // tables that happen to appear between provider and auth.
152
- const authIdx = content.indexOf(authHeader, headerIdx);
153
- const blockEnd = authIdx !== -1 && authIdx < sectionEnd
154
- ? findSectionEnd(content, authIdx + authHeader.length + 1)
155
- : sectionEnd;
148
+ let blockEnd = findSectionEnd(content, headerIdx + sectionHeader.length + 1);
149
+ let authIdx = content.indexOf(authHeader, blockEnd);
150
+ while (authIdx !== -1 && content.substring(blockEnd, authIdx).trim() === "") {
151
+ blockEnd = findSectionEnd(content, authIdx + authHeader.length + 1);
152
+ authIdx = content.indexOf(authHeader, blockEnd);
153
+ }
156
154
  content = content.substring(0, headerIdx) + providerBlock + content.substring(blockEnd);
157
155
  }
158
156
  else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@parall/codex-agent",
3
- "version": "1.29.3",
3
+ "version": "1.31.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.29.3",
30
- "@parall/cli": "1.29.3",
31
- "@parall/sdk": "1.29.3"
29
+ "@parall/cli": "1.31.0",
30
+ "@parall/agent-core": "1.31.0",
31
+ "@parall/sdk": "1.31.0"
32
32
  },
33
33
  "devDependencies": {
34
34
  "@types/node": "^22.0.0",
package/src/config.ts CHANGED
@@ -63,10 +63,10 @@ export function resolveCodexAgentConfig(env: NodeJS.ProcessEnv = process.env): C
63
63
  // Bridge state lives outside CODEX_HOME to avoid polluting codex's own
64
64
  // directory with non-codex files.
65
65
  const stateDir = resolvePath(
66
- env.PRLL_CODEX_STATE_DIR?.trim()
66
+ env.PRLL_STATE_DIR?.trim()
67
67
  || path.join(env.HOME || os.homedir(), ".parall-agent"),
68
68
  );
69
- const workspaceDir = resolvePath(env.PRLL_CODEX_WORKSPACE_DIR?.trim() || path.join(stateDir, "workspace"));
69
+ const workspaceDir = resolvePath(env.PRLL_WORKSPACE_DIR?.trim() || path.join(stateDir, "workspace"));
70
70
 
71
71
  return {
72
72
  apiUrl,
package/src/dispatch.ts CHANGED
@@ -94,7 +94,7 @@ export class CodexAppServerAdapter implements DispatchAdapter {
94
94
  const existing = this.activeTurns.get(threadId);
95
95
  if (existing) {
96
96
  (log ?? this.opts.log)?.warn?.(
97
- `codex-agent: thread ${threadId} already had an active turn; failing the previous dispatch`,
97
+ `thread ${threadId} already had an active turn; failing the previous dispatch`,
98
98
  );
99
99
  existing.push({ kind: "error", message: `thread ${threadId} replaced by concurrent turn` });
100
100
  existing.close();
@@ -125,7 +125,7 @@ export class CodexAppServerAdapter implements DispatchAdapter {
125
125
  this.pendingInjections.set(sessionKey, (this.pendingInjections.get(sessionKey) ?? 0) + 1);
126
126
  return true;
127
127
  } catch (err) {
128
- this.opts.log?.warn?.(`codex-agent: turn/steer failed: ${errToString(err)}`);
128
+ this.opts.log?.warn?.(`turn/steer failed: ${errToString(err)}`);
129
129
  return false;
130
130
  }
131
131
  }
@@ -141,7 +141,7 @@ export class CodexAppServerAdapter implements DispatchAdapter {
141
141
  const threadId = this.opts.sessionManager.getThreadId(sessionKey);
142
142
  if (threadId && this.client && !this.client.isDisposed()) {
143
143
  (this.opts.log ?? context.log)?.info?.(
144
- `codex-agent[${context.accountId}]: ${pending} steer injection(s) already sent; skipping turn/start`,
144
+ `${pending} steer injection(s) already sent; skipping turn/start`,
145
145
  );
146
146
  yield {
147
147
  type: "runtime_session",
@@ -151,7 +151,7 @@ export class CodexAppServerAdapter implements DispatchAdapter {
151
151
  return;
152
152
  }
153
153
  (this.opts.log ?? context.log)?.warn?.(
154
- `codex-agent[${context.accountId}]: pending steer invalidated (subprocess died); falling through to normal dispatch`,
154
+ `pending steer invalidated (subprocess died); falling through to normal dispatch`,
155
155
  );
156
156
  }
157
157
 
@@ -195,7 +195,7 @@ export class CodexAppServerAdapter implements DispatchAdapter {
195
195
  this.opts.sessionManager.recordThreadId(sessionKey, threadId);
196
196
  this.resumedThreadIds.add(threadId);
197
197
  } catch (err) {
198
- log?.warn?.(`codex-agent: thread/resume failed (${errToString(err)}); attempting one-shot fresh-thread start`);
198
+ log?.warn?.(`thread/resume failed (${errToString(err)}); attempting one-shot fresh-thread start`);
199
199
  let freshThreadId: string;
200
200
  try {
201
201
  freshThreadId = await this.openThread(client, { resumeId: undefined });
@@ -231,7 +231,7 @@ export class CodexAppServerAdapter implements DispatchAdapter {
231
231
  preparedImages = prepared.attachments.images;
232
232
  releasePreparedAttachments = pinLocalAttachmentPaths(preparedImages);
233
233
  } catch (err) {
234
- log?.warn?.(`codex-agent[${context.accountId}]: failed to prepare local attachments: ${errToString(err)}`);
234
+ log?.warn?.(`failed to prepare local attachments: ${errToString(err)}`);
235
235
  }
236
236
  // `localImage` is a first-class variant of `UserInput` in the Codex
237
237
  // app-server protocol (codex-rs/app-server-protocol/src/protocol/v2.rs
@@ -269,7 +269,7 @@ export class CodexAppServerAdapter implements DispatchAdapter {
269
269
  yield { type: "error", message: `Codex turn/start failed: ${message}` };
270
270
  return;
271
271
  }
272
- log?.warn?.(`codex-agent: turn/start on thread ${threadId} failed (${message}); attempting one-shot fresh-thread retry`);
272
+ log?.warn?.(`turn/start on thread ${threadId} failed (${message}); attempting one-shot fresh-thread retry`);
273
273
  this.activeTurns.delete(threadId);
274
274
  let freshThreadId: string;
275
275
  try {
@@ -382,7 +382,7 @@ export class CodexAppServerAdapter implements DispatchAdapter {
382
382
  const result = await client.sendRequest("thread/fork", forkParams);
383
383
  const forkedThreadId = extractThreadId(result);
384
384
  if (!forkedThreadId) {
385
- this.opts.log?.warn?.("codex-agent: thread/fork returned no thread id");
385
+ this.opts.log?.warn?.("thread/fork returned no thread id");
386
386
  return null;
387
387
  }
388
388
  this.opts.sessionManager.recordThreadId(handle.sessionKey, forkedThreadId);
@@ -397,7 +397,7 @@ export class CodexAppServerAdapter implements DispatchAdapter {
397
397
  }
398
398
 
399
399
  private logForkFailure(err: unknown): null {
400
- this.opts.log?.warn?.(`codex-agent: thread/fork failed: ${errToString(err)}`);
400
+ this.opts.log?.warn?.(`thread/fork failed: ${errToString(err)}`);
401
401
  return null;
402
402
  }
403
403
 
@@ -467,7 +467,7 @@ export class CodexAppServerAdapter implements DispatchAdapter {
467
467
  env.PRLL_CONTEXT_FILE = this.opts.contextFilePath;
468
468
  }
469
469
  const args = ["app-server", "--listen", "stdio://"];
470
- (log ?? this.opts.log)?.info?.(`codex-agent: spawning ${this.opts.codexBin} ${args.join(" ")}`);
470
+ (log ?? this.opts.log)?.info?.(`spawning ${this.opts.codexBin} ${args.join(" ")}`);
471
471
  const proc = spawn(
472
472
  IS_WIN32 ? quoteWin32Arg(this.opts.codexBin) : this.opts.codexBin,
473
473
  IS_WIN32 ? args.map(quoteWin32Arg) : args, {
@@ -479,7 +479,7 @@ export class CodexAppServerAdapter implements DispatchAdapter {
479
479
 
480
480
  proc.stderr.setEncoding("utf8");
481
481
  proc.stderr.on("data", (chunk: string) => {
482
- (log ?? this.opts.log)?.warn?.(`codex-agent[stderr]: ${chunk.trim()}`);
482
+ (log ?? this.opts.log)?.warn?.(`[stderr] ${chunk.trim()}`);
483
483
  });
484
484
 
485
485
  // Don't publish proc/client until the initialize handshake succeeds. If
@@ -535,9 +535,9 @@ export class CodexAppServerAdapter implements DispatchAdapter {
535
535
  : `exited (code=${code ?? "null"}${signal ? `, signal=${signal}` : ""})`;
536
536
  const logger = log ?? this.opts.log;
537
537
  if (this.stopping) {
538
- logger?.info?.(`codex-agent: app-server subprocess ${reason} during graceful stop`);
538
+ logger?.info?.(`app-server subprocess ${reason} during graceful stop`);
539
539
  } else {
540
- logger?.warn?.(`codex-agent: app-server subprocess ${reason}; resetting adapter`);
540
+ logger?.warn?.(`app-server subprocess ${reason}; resetting adapter`);
541
541
  }
542
542
  this.client?.dispose(err ?? new Error(`app-server ${reason}`));
543
543
  for (const activeSink of this.activeTurns.values()) {
@@ -671,7 +671,7 @@ function ensureGitRepo(workingDirectory: string): void {
671
671
  fs.mkdirSync(workingDirectory, { recursive: true });
672
672
  // Only `git init` if the workspace isn't already inside any git repo. A
673
673
  // bare existsSync(.git) check would miss the common case of a user pointing
674
- // PRLL_CODEX_WORKSPACE_DIR at a subdirectory of their existing project,
674
+ // PRLL_WORKSPACE_DIR at a subdirectory of their existing project,
675
675
  // and silently creating a nested repo there would mangle their layout.
676
676
  try {
677
677
  execSync("git rev-parse --is-inside-work-tree", { cwd: workingDirectory, stdio: "pipe" });
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, createLogger, isPlatformManagedProfile, parseShutdownDeadlineMs } from "@parall/agent-core";
4
+ import { ParallAgentGateway, createPlatformConfigManager, createLogger, childLogger, isPlatformManagedProfile, parseShutdownDeadlineMs, parseProviderConfig, clearAllProviderCreds, llmSource } from "@parall/agent-core";
5
5
  import { ApiError, ParallClient, ParallWs } from "@parall/sdk";
6
6
  import {
7
7
  buildCodexRuntimeKey,
@@ -16,6 +16,7 @@ import { CodexSessionManager } from "./session-manager.js";
16
16
  import { ensureCodexWorkspace, ensureParallProvider, ensureWorkspaceTrusted, isParallProxyMode } from "./workspace.js";
17
17
 
18
18
  const log = createLogger("codex-agent");
19
+ let activeLog = log;
19
20
 
20
21
  async function getAgentMeWithLegacyFallback(client: ParallClient, orgId: string) {
21
22
  try {
@@ -29,7 +30,22 @@ async function getAgentMeWithLegacyFallback(client: ParallClient, orgId: string)
29
30
  }
30
31
  }
31
32
 
33
+ function resolveProviderEnv(): void {
34
+ const pc = parseProviderConfig(process.env);
35
+ if (!pc) return;
36
+ clearAllProviderCreds(process.env);
37
+ const source = llmSource(pc);
38
+ if (source === "parall") {
39
+ process.env.OPENAI_API_KEY = process.env.PRLL_API_KEY;
40
+ process.env.OPENAI_BASE_URL = `${process.env.PRLL_API_URL}/api/llm/v1`;
41
+ } else if (source === "custom") {
42
+ if (pc.openai_api_key) process.env.OPENAI_API_KEY = pc.openai_api_key;
43
+ if (pc.openai_base_url) process.env.OPENAI_BASE_URL = pc.openai_base_url;
44
+ }
45
+ }
46
+
32
47
  async function main() {
48
+ resolveProviderEnv();
33
49
  const config = resolveCodexAgentConfig(process.env);
34
50
 
35
51
  const client = new ParallClient({
@@ -40,13 +56,15 @@ async function main() {
40
56
 
41
57
  const me = await getAgentMeWithLegacyFallback(client, config.orgId);
42
58
  const agentUserId = me.id;
43
- ensureWorkspaceTrusted(config.codexHome, config.workspaceDir, log);
59
+ const agentLog = childLogger(log, agentUserId);
60
+ activeLog = agentLog;
61
+ ensureWorkspaceTrusted(config.codexHome, config.workspaceDir, agentLog);
44
62
  const useParallProvider = isParallProxyMode();
45
63
  if (useParallProvider) {
46
- ensureParallProvider(config.codexHome, config.apiUrl, log);
47
- log.info("parall custom provider configured (Responses API HTTP/SSE mode)");
64
+ ensureParallProvider(config.codexHome, config.apiUrl, agentLog);
65
+ agentLog.info("parall custom provider configured (Responses API HTTP/SSE mode)");
48
66
  }
49
- ensureCodexWorkspace(config.workspaceDir, log, {
67
+ ensureCodexWorkspace(config.workspaceDir, agentLog, {
50
68
  userId: agentUserId,
51
69
  displayName: me.display_name,
52
70
  description: me.agent_profile?.description ?? undefined,
@@ -57,7 +75,7 @@ async function main() {
57
75
  const sessionManager = new CodexSessionManager(
58
76
  runtimeKey,
59
77
  sessionStateFilePath,
60
- log,
78
+ agentLog,
61
79
  );
62
80
 
63
81
  const resolvedWsUrl = resolveWsUrl(config.apiUrl, config.wsUrl, config.swimlaneName);
@@ -66,13 +84,13 @@ async function main() {
66
84
  wsUrl: resolvedWsUrl,
67
85
  });
68
86
 
69
- const configMgr = createPlatformConfigManager({ client, stateDir: config.stateDir, runtimeType: "codex", log });
87
+ const configMgr = createPlatformConfigManager({ client, stateDir: config.stateDir, runtimeType: "codex", log: agentLog });
70
88
  const platformDefaults = await configMgr.fetch();
71
89
  let platformManaged = isPlatformManagedProfile(me.agent_profile);
72
90
  const resolvedModel = platformManaged ? (platformDefaults.model ?? config.model) : config.model;
73
91
  const resolvedEffort = platformManaged ? (platformDefaults.thinkingEffort ?? config.reasoningEffort) : config.reasoningEffort;
74
- if (platformDefaults.model) log.info(`platform config: model=${platformDefaults.model} (${platformManaged ? "applied" : "ignored, model_management=self"})`);
75
- if (platformDefaults.thinkingEffort) log.info(`platform config: reasoning_effort=${platformDefaults.thinkingEffort} (${platformManaged ? "applied" : "ignored, model_management=self"})`);
92
+ if (platformDefaults.model) agentLog.info(`platform config: model=${platformDefaults.model} (${platformManaged ? "applied" : "ignored, model_management=self"})`);
93
+ if (platformDefaults.thinkingEffort) agentLog.info(`platform config: reasoning_effort=${platformDefaults.thinkingEffort} (${platformManaged ? "applied" : "ignored, model_management=self"})`);
76
94
 
77
95
  const adapter = new CodexAppServerAdapter({
78
96
  codexBin: config.codexBin,
@@ -83,7 +101,7 @@ async function main() {
83
101
  sandbox: config.sandbox,
84
102
  approvalPolicy: config.approvalPolicy,
85
103
  sessionManager,
86
- log,
104
+ log: agentLog,
87
105
  contextFilePath: mainContextFilePath,
88
106
  useParallProvider,
89
107
  });
@@ -109,7 +127,7 @@ async function main() {
109
127
  driver: "app-server",
110
128
  },
111
129
  dispatchAdapter: adapter,
112
- log,
130
+ log: agentLog,
113
131
  shutdownDeadlineMs: parseShutdownDeadlineMs(process.env.PRLL_SHUTDOWN_DEADLINE_MS),
114
132
  contextFilePathForSession: (sessionKey) => contextFilePathForSession(config.stateDir, sessionKey),
115
133
  stepIdFilePathForSession: (sessionKey) => stepIdFilePathForSession(config.stateDir, sessionKey),
@@ -131,6 +149,8 @@ async function main() {
131
149
  reasoningEffort: platformManaged ? (updated.thinkingEffort ?? config.reasoningEffort) : (config.reasoningEffort ?? null),
132
150
  });
133
151
  },
152
+ onNewSession: () => { sessionManager.clearMainThread(); },
153
+ onSessionStale: () => { sessionManager.clearMainThread(); },
134
154
  });
135
155
 
136
156
  const abortController = new AbortController();
@@ -139,7 +159,7 @@ async function main() {
139
159
  process.on("SIGTERM", abort);
140
160
 
141
161
  try {
142
- log.info(`starting self-hosted Codex runtime (app-server driver) for ${me.display_name} (${agentUserId})`);
162
+ agentLog.info(`starting self-hosted Codex runtime (app-server driver) for ${me.display_name} (${agentUserId})`);
143
163
  await gateway.run(abortController.signal);
144
164
  } finally {
145
165
  await adapter.stop();
@@ -149,6 +169,6 @@ async function main() {
149
169
  }
150
170
 
151
171
  main().catch((err) => {
152
- log.error(`fatal: ${String(err)}`);
172
+ activeLog.error(`fatal: ${String(err)}`);
153
173
  process.exitCode = 1;
154
174
  });
package/src/workspace.ts CHANGED
@@ -159,14 +159,12 @@ export function ensureParallProvider(
159
159
 
160
160
  const headerIdx = content.indexOf(sectionHeader);
161
161
  if (headerIdx !== -1) {
162
- const sectionEnd = findSectionEnd(content, headerIdx + sectionHeader.length + 1);
163
- // Only consume a contiguous [model_providers.parall.auth] subtable —
164
- // one that sits within the provider's own section, not after unrelated
165
- // tables that happen to appear between provider and auth.
166
- const authIdx = content.indexOf(authHeader, headerIdx);
167
- const blockEnd = authIdx !== -1 && authIdx < sectionEnd
168
- ? findSectionEnd(content, authIdx + authHeader.length + 1)
169
- : sectionEnd;
162
+ let blockEnd = findSectionEnd(content, headerIdx + sectionHeader.length + 1);
163
+ let authIdx = content.indexOf(authHeader, blockEnd);
164
+ while (authIdx !== -1 && content.substring(blockEnd, authIdx).trim() === "") {
165
+ blockEnd = findSectionEnd(content, authIdx + authHeader.length + 1);
166
+ authIdx = content.indexOf(authHeader, blockEnd);
167
+ }
170
168
  content = content.substring(0, headerIdx) + providerBlock + content.substring(blockEnd);
171
169
  } else {
172
170
  content = content.trimEnd() + "\n\n" + providerBlock + "\n";