@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 +2 -2
- package/dist/dispatch.js +14 -14
- package/dist/index.js +36 -13
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +6 -8
- package/package.json +4 -4
- package/src/config.ts +2 -2
- package/src/dispatch.ts +14 -14
- package/src/index.ts +33 -13
- package/src/workspace.ts +6 -8
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.
|
|
29
|
+
const stateDir = resolvePath(env.PRLL_STATE_DIR?.trim()
|
|
30
30
|
|| path.join(env.HOME || os.homedir(), ".parall-agent"));
|
|
31
|
-
const workspaceDir = resolvePath(env.
|
|
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?.(`
|
|
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?.(`
|
|
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?.(
|
|
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?.(`
|
|
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?.(`
|
|
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?.(`
|
|
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?.(`
|
|
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?.("
|
|
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?.(`
|
|
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?.(`
|
|
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?.(`
|
|
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?.(`
|
|
488
|
+
logger?.info?.(`app-server subprocess ${reason} during graceful stop`);
|
|
489
489
|
}
|
|
490
490
|
else {
|
|
491
|
-
logger?.warn?.(`
|
|
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
|
-
//
|
|
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
|
-
|
|
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,
|
|
35
|
-
|
|
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,
|
|
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,
|
|
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
|
-
|
|
78
|
+
agentLog.info(`platform config: model=${platformDefaults.model} (${platformManaged ? "applied" : "ignored, model_management=self"})`);
|
|
58
79
|
if (platformDefaults.thinkingEffort)
|
|
59
|
-
|
|
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
|
-
|
|
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
|
-
|
|
155
|
+
activeLog.error(`fatal: ${String(err)}`);
|
|
133
156
|
process.exitCode = 1;
|
|
134
157
|
});
|
package/dist/workspace.d.ts.map
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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.
|
|
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/
|
|
30
|
-
"@parall/
|
|
31
|
-
"@parall/sdk": "1.
|
|
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.
|
|
66
|
+
env.PRLL_STATE_DIR?.trim()
|
|
67
67
|
|| path.join(env.HOME || os.homedir(), ".parall-agent"),
|
|
68
68
|
);
|
|
69
|
-
const workspaceDir = resolvePath(env.
|
|
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
|
-
`
|
|
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?.(`
|
|
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
|
-
|
|
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
|
-
`
|
|
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?.(`
|
|
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?.(`
|
|
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?.(`
|
|
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?.("
|
|
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?.(`
|
|
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?.(`
|
|
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?.(`
|
|
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?.(`
|
|
538
|
+
logger?.info?.(`app-server subprocess ${reason} during graceful stop`);
|
|
539
539
|
} else {
|
|
540
|
-
logger?.warn?.(`
|
|
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
|
-
//
|
|
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
|
-
|
|
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,
|
|
47
|
-
|
|
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,
|
|
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
|
-
|
|
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)
|
|
75
|
-
if (platformDefaults.thinkingEffort)
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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";
|