@parall/codex-agent 1.30.0 → 1.32.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.d.ts.map +1 -1
- package/dist/config.js +34 -35
- package/dist/dispatch.d.ts +6 -5
- package/dist/dispatch.d.ts.map +1 -1
- package/dist/dispatch.js +102 -82
- package/dist/event-mapping.d.ts +1 -1
- package/dist/event-mapping.d.ts.map +1 -1
- package/dist/event-mapping.js +102 -82
- package/dist/index.js +169 -114
- package/dist/jsonrpc-client.d.ts +4 -4
- package/dist/jsonrpc-client.d.ts.map +1 -1
- package/dist/jsonrpc-client.js +15 -15
- package/dist/session-manager.d.ts +1 -1
- package/dist/session-manager.d.ts.map +1 -1
- package/dist/session-manager.js +7 -5
- package/dist/workspace.d.ts +1 -1
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +40 -39
- package/package.json +4 -4
- package/src/config.ts +41 -36
- package/src/dispatch.ts +140 -110
- package/src/event-mapping.ts +135 -109
- package/src/index.ts +199 -117
- package/src/jsonrpc-client.ts +22 -20
- package/src/session-manager.ts +10 -6
- package/src/workspace.ts +50 -43
package/dist/jsonrpc-client.js
CHANGED
|
@@ -14,28 +14,28 @@ export class JsonRpcStdioClient {
|
|
|
14
14
|
killProcess;
|
|
15
15
|
nextId = 1;
|
|
16
16
|
pending = new Map();
|
|
17
|
-
buffer =
|
|
17
|
+
buffer = '';
|
|
18
18
|
onNotification = null;
|
|
19
19
|
disposed = false;
|
|
20
20
|
constructor(proc, requestTimeoutMs = DEFAULT_REQUEST_TIMEOUT_MS, killProcess) {
|
|
21
21
|
this.proc = proc;
|
|
22
22
|
this.requestTimeoutMs = requestTimeoutMs;
|
|
23
23
|
this.killProcess = killProcess;
|
|
24
|
-
proc.stdout.setEncoding(
|
|
25
|
-
proc.stdout.on(
|
|
26
|
-
proc.once(
|
|
27
|
-
proc.once(
|
|
28
|
-
proc.stdin.on(
|
|
24
|
+
proc.stdout.setEncoding('utf8');
|
|
25
|
+
proc.stdout.on('data', (chunk) => this.ingest(chunk));
|
|
26
|
+
proc.once('close', () => this.dispose(new Error('app-server subprocess closed')));
|
|
27
|
+
proc.once('error', (err) => this.dispose(err));
|
|
28
|
+
proc.stdin.on('error', (err) => this.killUnhealthy(err));
|
|
29
29
|
}
|
|
30
30
|
setNotificationHandler(handler) {
|
|
31
31
|
this.onNotification = handler;
|
|
32
32
|
}
|
|
33
33
|
sendRequest(method, params) {
|
|
34
34
|
if (this.disposed) {
|
|
35
|
-
return Promise.reject(new Error(
|
|
35
|
+
return Promise.reject(new Error('JSON-RPC client disposed'));
|
|
36
36
|
}
|
|
37
37
|
const id = this.nextId++;
|
|
38
|
-
const request = { jsonrpc:
|
|
38
|
+
const request = { jsonrpc: '2.0', id, method, params };
|
|
39
39
|
const promise = new Promise((resolve, reject) => {
|
|
40
40
|
const timer = setTimeout(() => {
|
|
41
41
|
const pending = this.pending.get(id);
|
|
@@ -53,7 +53,7 @@ export class JsonRpcStdioClient {
|
|
|
53
53
|
sendNotification(method, params) {
|
|
54
54
|
if (this.disposed)
|
|
55
55
|
return;
|
|
56
|
-
const notification = { jsonrpc:
|
|
56
|
+
const notification = { jsonrpc: '2.0', method, params };
|
|
57
57
|
this.writeFrame(notification);
|
|
58
58
|
}
|
|
59
59
|
isDisposed() {
|
|
@@ -84,19 +84,19 @@ export class JsonRpcStdioClient {
|
|
|
84
84
|
this.killProcess(this.proc);
|
|
85
85
|
}
|
|
86
86
|
else {
|
|
87
|
-
this.proc.kill(
|
|
87
|
+
this.proc.kill('SIGTERM');
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
ingest(chunk) {
|
|
92
92
|
this.buffer += chunk;
|
|
93
|
-
let newlineIndex = this.buffer.indexOf(
|
|
93
|
+
let newlineIndex = this.buffer.indexOf('\n');
|
|
94
94
|
while (newlineIndex >= 0) {
|
|
95
95
|
const line = this.buffer.slice(0, newlineIndex).trim();
|
|
96
96
|
this.buffer = this.buffer.slice(newlineIndex + 1);
|
|
97
97
|
if (line)
|
|
98
98
|
this.handleLine(line);
|
|
99
|
-
newlineIndex = this.buffer.indexOf(
|
|
99
|
+
newlineIndex = this.buffer.indexOf('\n');
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
102
|
handleLine(line) {
|
|
@@ -114,7 +114,7 @@ export class JsonRpcStdioClient {
|
|
|
114
114
|
this.pending.delete(message.id);
|
|
115
115
|
clearTimeout(pending.timer);
|
|
116
116
|
if (message.error) {
|
|
117
|
-
pending.reject(new Error(message.error.message ||
|
|
117
|
+
pending.reject(new Error(message.error.message || 'JSON-RPC error'));
|
|
118
118
|
}
|
|
119
119
|
else {
|
|
120
120
|
pending.resolve(message.result);
|
|
@@ -127,8 +127,8 @@ export class JsonRpcStdioClient {
|
|
|
127
127
|
}
|
|
128
128
|
}
|
|
129
129
|
function isResponse(m) {
|
|
130
|
-
return !!m && typeof m ===
|
|
130
|
+
return !!m && typeof m === 'object' && 'id' in m && ('result' in m || 'error' in m);
|
|
131
131
|
}
|
|
132
132
|
function isNotification(m) {
|
|
133
|
-
return !!m && typeof m ===
|
|
133
|
+
return !!m && typeof m === 'object' && 'method' in m && !('id' in m);
|
|
134
134
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-manager.d.ts","sourceRoot":"","sources":["../src/session-manager.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAO5D,KAAK,MAAM,GAAG;IAAE,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC;AAE9C;;;;;GAKG;AACH,qBAAa,mBAAmB;IAI5B,QAAQ,CAAC,cAAc,EAAE,MAAM;IAC/B,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;IAL1B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA6B;gBAG5C,cAAc,EAAE,MAAM,EACd,aAAa,EAAE,MAAM,EACrB,MAAM,CAAC,EAAE,MAAM,YAAA;IAKlC,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;IAInC,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAInD,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;IAOnD,oBAAoB,IAAI,iBAAiB;IAOzC,WAAW,CAAC,UAAU,EAAE,MAAM;IAI9B,6EAA6E;IAC7E,eAAe;IAWf,OAAO,CAAC,OAAO;
|
|
1
|
+
{"version":3,"file":"session-manager.d.ts","sourceRoot":"","sources":["../src/session-manager.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAO5D,KAAK,MAAM,GAAG;IAAE,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC;AAE9C;;;;;GAKG;AACH,qBAAa,mBAAmB;IAI5B,QAAQ,CAAC,cAAc,EAAE,MAAM;IAC/B,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;IAL1B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA6B;gBAG5C,cAAc,EAAE,MAAM,EACd,aAAa,EAAE,MAAM,EACrB,MAAM,CAAC,EAAE,MAAM,YAAA;IAKlC,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;IAInC,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAInD,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;IAOnD,oBAAoB,IAAI,iBAAiB;IAOzC,WAAW,CAAC,UAAU,EAAE,MAAM;IAI9B,6EAA6E;IAC7E,eAAe;IAWf,OAAO,CAAC,OAAO;IAoBf,OAAO,CAAC,OAAO;CAqBhB"}
|
package/dist/session-manager.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import * as fs from
|
|
2
|
-
import * as path from
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
3
|
/**
|
|
4
4
|
* Persists the app-server `threadId` for the main Parall session so the
|
|
5
5
|
* bridge can resume across restarts via `thread/resume`. Fork sessions
|
|
@@ -50,14 +50,16 @@ export class CodexSessionManager {
|
|
|
50
50
|
}
|
|
51
51
|
restore() {
|
|
52
52
|
try {
|
|
53
|
-
const raw = fs.readFileSync(this.stateFilePath,
|
|
53
|
+
const raw = fs.readFileSync(this.stateFilePath, 'utf8');
|
|
54
54
|
const parsed = JSON.parse(raw);
|
|
55
|
-
if (parsed.runtimeKey === this.mainSessionKey &&
|
|
55
|
+
if (parsed.runtimeKey === this.mainSessionKey &&
|
|
56
|
+
typeof parsed.threadId === 'string' &&
|
|
57
|
+
parsed.threadId.trim()) {
|
|
56
58
|
this.threadIds.set(this.mainSessionKey, parsed.threadId.trim());
|
|
57
59
|
}
|
|
58
60
|
}
|
|
59
61
|
catch (error) {
|
|
60
|
-
if (error?.code !==
|
|
62
|
+
if (error?.code !== 'ENOENT') {
|
|
61
63
|
this.logger?.warn(`codex-agent: could not restore main thread state from ${this.stateFilePath}: ${String(error)}`);
|
|
62
64
|
}
|
|
63
65
|
}
|
package/dist/workspace.d.ts
CHANGED
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,
|
|
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,CAgFN;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
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import * as fs from
|
|
2
|
-
import * as path from
|
|
3
|
-
import { parse as parseToml } from
|
|
4
|
-
import { BRIDGE_WORKSPACE_INSTRUCTIONS, PRLL_BEHAVIOR, PRLL_REFERENCE_GUIDE, buildIdentity, buildSkillReferences, writeSkillFiles, } from
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { parse as parseToml } from 'smol-toml';
|
|
4
|
+
import { BRIDGE_WORKSPACE_INSTRUCTIONS, PRLL_BEHAVIOR, PRLL_REFERENCE_GUIDE, buildIdentity, buildSkillReferences, writeSkillFiles, } from '@parall/agent-core';
|
|
5
5
|
/**
|
|
6
6
|
* Ensure the workspace directory is marked as trusted in the global Codex
|
|
7
7
|
* config so that project-level `developer_instructions` are loaded at
|
|
@@ -13,15 +13,15 @@ import { BRIDGE_WORKSPACE_INSTRUCTIONS, PRLL_BEHAVIOR, PRLL_REFERENCE_GUIDE, bui
|
|
|
13
13
|
* the bridge from starting.
|
|
14
14
|
*/
|
|
15
15
|
export function ensureWorkspaceTrusted(codexHome, workspaceDir, log) {
|
|
16
|
-
const configPath = path.join(codexHome,
|
|
16
|
+
const configPath = path.join(codexHome, 'config.toml');
|
|
17
17
|
const normalizedPath = path.resolve(workspaceDir);
|
|
18
18
|
try {
|
|
19
|
-
let content =
|
|
19
|
+
let content = '';
|
|
20
20
|
try {
|
|
21
|
-
content = fs.readFileSync(configPath,
|
|
21
|
+
content = fs.readFileSync(configPath, 'utf8');
|
|
22
22
|
}
|
|
23
23
|
catch (err) {
|
|
24
|
-
if (err.code !==
|
|
24
|
+
if (err.code !== 'ENOENT')
|
|
25
25
|
throw err;
|
|
26
26
|
}
|
|
27
27
|
// Structured read — know exactly what state we're in.
|
|
@@ -37,10 +37,10 @@ export function ensureWorkspaceTrusted(codexHome, workspaceDir, log) {
|
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
const projects = parsed?.projects;
|
|
40
|
-
if (projects?.[normalizedPath]?.trust_level ===
|
|
40
|
+
if (projects?.[normalizedPath]?.trust_level === 'trusted')
|
|
41
41
|
return;
|
|
42
42
|
// TOML basic-string keys require backslash and double-quote escaping.
|
|
43
|
-
const escapedPath = normalizedPath.replace(/\\/g,
|
|
43
|
+
const escapedPath = normalizedPath.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
44
44
|
const sectionHeader = `[projects."${escapedPath}"]`;
|
|
45
45
|
const trustLine = 'trust_level = "trusted"';
|
|
46
46
|
const headerIdx = content.indexOf(sectionHeader);
|
|
@@ -49,7 +49,7 @@ export function ensureWorkspaceTrusted(codexHome, workspaceDir, log) {
|
|
|
49
49
|
// within the section (up to the next `[` header or EOF) for an
|
|
50
50
|
// existing trust_level key — it may not be the first line after
|
|
51
51
|
// the header if the user added comments or other keys.
|
|
52
|
-
const headerLineEnd = content.indexOf(
|
|
52
|
+
const headerLineEnd = content.indexOf('\n', headerIdx);
|
|
53
53
|
if (headerLineEnd === -1) {
|
|
54
54
|
content = `${content}\n${trustLine}\n`;
|
|
55
55
|
}
|
|
@@ -64,10 +64,11 @@ export function ensureWorkspaceTrusted(codexHome, workspaceDir, log) {
|
|
|
64
64
|
content = content.substring(0, matchStart) + trustLine + content.substring(matchEnd);
|
|
65
65
|
}
|
|
66
66
|
else {
|
|
67
|
-
content =
|
|
67
|
+
content =
|
|
68
|
+
content.substring(0, afterHeader) + trustLine + '\n' + content.substring(afterHeader);
|
|
68
69
|
}
|
|
69
70
|
}
|
|
70
|
-
fs.writeFileSync(configPath, content,
|
|
71
|
+
fs.writeFileSync(configPath, content, 'utf8');
|
|
71
72
|
}
|
|
72
73
|
else if (projects?.[normalizedPath] !== undefined) {
|
|
73
74
|
// smol-toml found the section but indexOf missed it — the header
|
|
@@ -75,7 +76,7 @@ export function ensureWorkspaceTrusted(codexHome, workspaceDir, log) {
|
|
|
75
76
|
// duplicate table. Skip rather than corrupt the file.
|
|
76
77
|
log?.warn(`Codex config.toml has non-canonical header for ${normalizedPath}; skipping trust write`);
|
|
77
78
|
}
|
|
78
|
-
else if (projects && !content.includes(
|
|
79
|
+
else if (projects && !content.includes('[projects.')) {
|
|
79
80
|
// `projects` exists in parsed output but no `[projects.` table
|
|
80
81
|
// headers in the raw text — it's an inline table. Appending a
|
|
81
82
|
// standard table header would produce invalid TOML.
|
|
@@ -84,7 +85,7 @@ export function ensureWorkspaceTrusted(codexHome, workspaceDir, log) {
|
|
|
84
85
|
else {
|
|
85
86
|
// Section doesn't exist — append.
|
|
86
87
|
fs.mkdirSync(codexHome, { recursive: true });
|
|
87
|
-
fs.appendFileSync(configPath, `\n${sectionHeader}\n${trustLine}\n`,
|
|
88
|
+
fs.appendFileSync(configPath, `\n${sectionHeader}\n${trustLine}\n`, 'utf8');
|
|
88
89
|
}
|
|
89
90
|
}
|
|
90
91
|
catch (err) {
|
|
@@ -97,8 +98,8 @@ export function ensureWorkspaceTrusted(codexHome, workspaceDir, log) {
|
|
|
97
98
|
* whether OPENAI_BASE_URL points to the Parall API.
|
|
98
99
|
*/
|
|
99
100
|
export function isParallProxyMode(env = process.env) {
|
|
100
|
-
const baseUrl = env.OPENAI_BASE_URL?.trim()?.replace(/\/+$/,
|
|
101
|
-
const apiUrl = env.PRLL_API_URL?.trim()?.replace(/\/+$/,
|
|
101
|
+
const baseUrl = env.OPENAI_BASE_URL?.trim()?.replace(/\/+$/, '');
|
|
102
|
+
const apiUrl = env.PRLL_API_URL?.trim()?.replace(/\/+$/, '');
|
|
102
103
|
if (!baseUrl || !apiUrl)
|
|
103
104
|
return false;
|
|
104
105
|
return baseUrl === apiUrl || baseUrl.startsWith(`${apiUrl}/`);
|
|
@@ -118,46 +119,46 @@ export function isParallProxyMode(env = process.env) {
|
|
|
118
119
|
* Provider-managed: overwritten on every boot (env vars are the SSOT).
|
|
119
120
|
*/
|
|
120
121
|
export function ensureParallProvider(codexHome, apiUrl, log) {
|
|
121
|
-
const configPath = path.join(codexHome,
|
|
122
|
-
const baseUrl = apiUrl.replace(/\/$/,
|
|
122
|
+
const configPath = path.join(codexHome, 'config.toml');
|
|
123
|
+
const baseUrl = apiUrl.replace(/\/$/, '') + '/api/llm/v1';
|
|
123
124
|
try {
|
|
124
|
-
let content =
|
|
125
|
+
let content = '';
|
|
125
126
|
try {
|
|
126
|
-
content = fs.readFileSync(configPath,
|
|
127
|
+
content = fs.readFileSync(configPath, 'utf8');
|
|
127
128
|
}
|
|
128
129
|
catch (err) {
|
|
129
|
-
if (err.code !==
|
|
130
|
+
if (err.code !== 'ENOENT')
|
|
130
131
|
throw err;
|
|
131
132
|
}
|
|
132
|
-
const sectionHeader =
|
|
133
|
-
const authHeader =
|
|
133
|
+
const sectionHeader = '[model_providers.parall]';
|
|
134
|
+
const authHeader = '[model_providers.parall.auth]';
|
|
134
135
|
const providerBlock = [
|
|
135
136
|
sectionHeader,
|
|
136
137
|
'name = "Parall Proxy"',
|
|
137
138
|
`base_url = "${baseUrl}"`,
|
|
138
139
|
'wire_api = "responses"',
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
140
|
+
'supports_websockets = false',
|
|
141
|
+
'requires_openai_auth = false',
|
|
142
|
+
'',
|
|
142
143
|
authHeader,
|
|
143
144
|
'command = "printenv"',
|
|
144
145
|
'args = ["OPENAI_API_KEY"]',
|
|
145
|
-
].join(
|
|
146
|
+
].join('\n');
|
|
146
147
|
const headerIdx = content.indexOf(sectionHeader);
|
|
147
148
|
if (headerIdx !== -1) {
|
|
148
149
|
let blockEnd = findSectionEnd(content, headerIdx + sectionHeader.length + 1);
|
|
149
150
|
let authIdx = content.indexOf(authHeader, blockEnd);
|
|
150
|
-
while (authIdx !== -1 && content.substring(blockEnd, authIdx).trim() ===
|
|
151
|
+
while (authIdx !== -1 && content.substring(blockEnd, authIdx).trim() === '') {
|
|
151
152
|
blockEnd = findSectionEnd(content, authIdx + authHeader.length + 1);
|
|
152
153
|
authIdx = content.indexOf(authHeader, blockEnd);
|
|
153
154
|
}
|
|
154
155
|
content = content.substring(0, headerIdx) + providerBlock + content.substring(blockEnd);
|
|
155
156
|
}
|
|
156
157
|
else {
|
|
157
|
-
content = content.trimEnd() +
|
|
158
|
+
content = content.trimEnd() + '\n\n' + providerBlock + '\n';
|
|
158
159
|
}
|
|
159
160
|
fs.mkdirSync(codexHome, { recursive: true });
|
|
160
|
-
fs.writeFileSync(configPath, content,
|
|
161
|
+
fs.writeFileSync(configPath, content, 'utf8');
|
|
161
162
|
}
|
|
162
163
|
catch (err) {
|
|
163
164
|
throw new Error(`failed to write Parall provider config: ${String(err)}`);
|
|
@@ -166,7 +167,7 @@ export function ensureParallProvider(codexHome, apiUrl, log) {
|
|
|
166
167
|
function findSectionEnd(content, fromIndex) {
|
|
167
168
|
// A TOML section ends at the next `[` that starts a new table header.
|
|
168
169
|
// Match `[` at the beginning of a line (not inside a value).
|
|
169
|
-
const nextHeader = content.indexOf(
|
|
170
|
+
const nextHeader = content.indexOf('\n[', fromIndex);
|
|
170
171
|
return nextHeader === -1 ? content.length : nextHeader;
|
|
171
172
|
}
|
|
172
173
|
export function ensureCodexWorkspace(workspaceDir, log, agentIdentity) {
|
|
@@ -176,14 +177,14 @@ export function ensureCodexWorkspace(workspaceDir, log, agentIdentity) {
|
|
|
176
177
|
PRLL_BEHAVIOR,
|
|
177
178
|
PRLL_REFERENCE_GUIDE,
|
|
178
179
|
buildSkillReferences(workspaceDir),
|
|
179
|
-
].join(
|
|
180
|
+
].join('\n\n');
|
|
180
181
|
fs.mkdirSync(workspaceDir, { recursive: true });
|
|
181
|
-
const parallDir = path.join(workspaceDir,
|
|
182
|
+
const parallDir = path.join(workspaceDir, '.parall');
|
|
182
183
|
fs.mkdirSync(parallDir, { recursive: true });
|
|
183
|
-
fs.writeFileSync(path.join(parallDir,
|
|
184
|
-
writeSkillFiles(path.join(parallDir,
|
|
185
|
-
const codexConfigDir = path.join(workspaceDir,
|
|
184
|
+
fs.writeFileSync(path.join(parallDir, 'system-prompt.md'), systemPrompt, 'utf8');
|
|
185
|
+
writeSkillFiles(path.join(parallDir, 'skills'));
|
|
186
|
+
const codexConfigDir = path.join(workspaceDir, '.codex');
|
|
186
187
|
fs.mkdirSync(codexConfigDir, { recursive: true });
|
|
187
|
-
const toml = `developer_instructions = """\n${systemPrompt.replace(/\\/g,
|
|
188
|
-
fs.writeFileSync(path.join(codexConfigDir,
|
|
188
|
+
const toml = `developer_instructions = """\n${systemPrompt.replace(/\\/g, '\\\\').replace(/"""/g, '\\"""')}\n"""\n`;
|
|
189
|
+
fs.writeFileSync(path.join(codexConfigDir, 'config.toml'), toml, 'utf8');
|
|
189
190
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parall/codex-agent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.32.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.
|
|
30
|
-
"@parall/cli": "1.
|
|
31
|
-
"@parall/sdk": "1.
|
|
29
|
+
"@parall/agent-core": "1.32.0",
|
|
30
|
+
"@parall/cli": "1.32.0",
|
|
31
|
+
"@parall/sdk": "1.32.0"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@types/node": "^22.0.0",
|
package/src/config.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import * as os from
|
|
2
|
-
import * as path from
|
|
1
|
+
import * as os from 'node:os';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
3
|
|
|
4
4
|
export type CodexAgentConfig = {
|
|
5
5
|
apiUrl: string;
|
|
@@ -45,9 +45,9 @@ function resolvePath(value: string): string {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
export function resolveCodexAgentConfig(env: NodeJS.ProcessEnv = process.env): CodexAgentConfig {
|
|
48
|
-
const apiUrl = requireEnv(env,
|
|
49
|
-
const apiKey = requireEnv(env,
|
|
50
|
-
const orgId = requireEnv(env,
|
|
48
|
+
const apiUrl = requireEnv(env, 'PRLL_API_URL');
|
|
49
|
+
const apiKey = requireEnv(env, 'PRLL_API_KEY');
|
|
50
|
+
const orgId = requireEnv(env, 'PRLL_ORG_ID');
|
|
51
51
|
// CODEX_HOME is where codex looks for `auth.json` / `config.toml`. Its
|
|
52
52
|
// own default is `$HOME/.codex`; falling back to `$HOME` here was wrong
|
|
53
53
|
// — it made the spawned app-server subprocess look for
|
|
@@ -56,17 +56,18 @@ export function resolveCodexAgentConfig(env: NodeJS.ProcessEnv = process.env): C
|
|
|
56
56
|
// WebSocket endpoint. Default to `$HOME/.codex` so `codex login`'s
|
|
57
57
|
// credentials are picked up transparently.
|
|
58
58
|
const codexHome = resolvePath(
|
|
59
|
-
env.PRLL_CODEX_HOME?.trim()
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
env.PRLL_CODEX_HOME?.trim() ||
|
|
60
|
+
env.CODEX_HOME?.trim() ||
|
|
61
|
+
path.join(env.HOME || os.homedir(), '.codex'),
|
|
62
62
|
);
|
|
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.
|
|
67
|
-
|
|
66
|
+
env.PRLL_STATE_DIR?.trim() || path.join(env.HOME || os.homedir(), '.parall-agent'),
|
|
67
|
+
);
|
|
68
|
+
const workspaceDir = resolvePath(
|
|
69
|
+
env.PRLL_WORKSPACE_DIR?.trim() || path.join(stateDir, 'workspace'),
|
|
68
70
|
);
|
|
69
|
-
const workspaceDir = resolvePath(env.PRLL_CODEX_WORKSPACE_DIR?.trim() || path.join(stateDir, "workspace"));
|
|
70
71
|
|
|
71
72
|
return {
|
|
72
73
|
apiUrl,
|
|
@@ -74,14 +75,14 @@ export function resolveCodexAgentConfig(env: NodeJS.ProcessEnv = process.env): C
|
|
|
74
75
|
orgId,
|
|
75
76
|
wsUrl: env.PRLL_WS_URL?.trim() || undefined,
|
|
76
77
|
swimlaneName: env.PRLL_SWIMLANE_NAME?.trim() || undefined,
|
|
77
|
-
codexBin: env.PRLL_CODEX_BIN?.trim() ||
|
|
78
|
+
codexBin: env.PRLL_CODEX_BIN?.trim() || 'codex',
|
|
78
79
|
codexHome,
|
|
79
80
|
stateDir,
|
|
80
81
|
workspaceDir,
|
|
81
82
|
model: env.PRLL_CODEX_MODEL?.trim() || undefined,
|
|
82
83
|
reasoningEffort: env.PRLL_CODEX_REASONING_EFFORT?.trim() || undefined,
|
|
83
|
-
sandbox: env.PRLL_CODEX_SANDBOX?.trim() ||
|
|
84
|
-
approvalPolicy: env.PRLL_CODEX_APPROVAL?.trim() ||
|
|
84
|
+
sandbox: env.PRLL_CODEX_SANDBOX?.trim() || 'danger-full-access',
|
|
85
|
+
approvalPolicy: env.PRLL_CODEX_APPROVAL?.trim() || 'never',
|
|
85
86
|
runtimeKey: env.PRLL_CODEX_RUNTIME_KEY?.trim() || undefined,
|
|
86
87
|
};
|
|
87
88
|
}
|
|
@@ -99,12 +100,12 @@ export function resolveCodexAgentConfig(env: NodeJS.ProcessEnv = process.env): C
|
|
|
99
100
|
*/
|
|
100
101
|
export function normalizeSandbox(value: string): string {
|
|
101
102
|
const map: Record<string, string> = {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
103
|
+
'workspace-write': 'workspace-write',
|
|
104
|
+
workspacewrite: 'workspace-write',
|
|
105
|
+
'danger-full-access': 'danger-full-access',
|
|
106
|
+
dangerfullaccess: 'danger-full-access',
|
|
107
|
+
'read-only': 'read-only',
|
|
108
|
+
readonly: 'read-only',
|
|
108
109
|
};
|
|
109
110
|
return map[value.toLowerCase()] ?? value;
|
|
110
111
|
}
|
|
@@ -117,22 +118,26 @@ export function normalizeSandbox(value: string): string {
|
|
|
117
118
|
*/
|
|
118
119
|
export function normalizeApprovalPolicy(value: string): string {
|
|
119
120
|
const map: Record<string, string> = {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
121
|
+
never: 'never',
|
|
122
|
+
'on-request': 'on-request',
|
|
123
|
+
onrequest: 'on-request',
|
|
124
|
+
'unless-trusted': 'unless-trusted',
|
|
125
|
+
unlesstrusted: 'unless-trusted',
|
|
126
|
+
'on-failure': 'on-failure',
|
|
127
|
+
onfailure: 'on-failure',
|
|
127
128
|
};
|
|
128
129
|
return map[value.toLowerCase()] ?? value;
|
|
129
130
|
}
|
|
130
131
|
|
|
131
|
-
export function resolveWsUrl(
|
|
132
|
-
|
|
132
|
+
export function resolveWsUrl(
|
|
133
|
+
apiUrl: string,
|
|
134
|
+
explicitWsUrl?: string,
|
|
135
|
+
swimlaneName?: string,
|
|
136
|
+
): string {
|
|
137
|
+
const base = explicitWsUrl || `${apiUrl.replace(/\/$/, '').replace(/^http/, 'ws')}/ws`;
|
|
133
138
|
if (!swimlaneName) return base;
|
|
134
139
|
const url = new URL(base);
|
|
135
|
-
url.searchParams.set(
|
|
140
|
+
url.searchParams.set('swimlane', swimlaneName);
|
|
136
141
|
return url.toString();
|
|
137
142
|
}
|
|
138
143
|
|
|
@@ -141,16 +146,16 @@ export function buildCodexRuntimeKey(agentUserId: string): string {
|
|
|
141
146
|
}
|
|
142
147
|
|
|
143
148
|
export function sessionStateFilePathForRuntime(stateDir: string, runtimeKey: string): string {
|
|
144
|
-
const fileName = Buffer.from(runtimeKey).toString(
|
|
145
|
-
return path.join(stateDir,
|
|
149
|
+
const fileName = Buffer.from(runtimeKey).toString('base64url');
|
|
150
|
+
return path.join(stateDir, 'threads', `${fileName}.json`);
|
|
146
151
|
}
|
|
147
152
|
|
|
148
153
|
export function contextFilePathForSession(stateDir: string, sessionKey: string): string {
|
|
149
|
-
const fileName = Buffer.from(sessionKey).toString(
|
|
150
|
-
return path.join(stateDir,
|
|
154
|
+
const fileName = Buffer.from(sessionKey).toString('base64url');
|
|
155
|
+
return path.join(stateDir, 'dispatch-context', `${fileName}.json`);
|
|
151
156
|
}
|
|
152
157
|
|
|
153
158
|
export function stepIdFilePathForSession(stateDir: string, sessionKey: string): string {
|
|
154
|
-
const fileName = Buffer.from(sessionKey).toString(
|
|
155
|
-
return path.join(stateDir,
|
|
159
|
+
const fileName = Buffer.from(sessionKey).toString('base64url');
|
|
160
|
+
return path.join(stateDir, 'step-ids', `${fileName}.txt`);
|
|
156
161
|
}
|