@parall/codex-agent 1.22.0 → 1.24.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/dispatch.d.ts +1 -1
- package/dist/dispatch.d.ts.map +1 -1
- package/dist/dispatch.js +45 -24
- package/dist/index.js +19 -3
- package/package.json +4 -4
- package/src/dispatch.ts +56 -26
- package/src/index.ts +19 -3
package/dist/dispatch.d.ts
CHANGED
|
@@ -38,7 +38,7 @@ export declare class CodexAppServerAdapter implements DispatchAdapter {
|
|
|
38
38
|
*/
|
|
39
39
|
private setActiveTurn;
|
|
40
40
|
constructor(opts: CodexAppServerAdapterOptions);
|
|
41
|
-
dispatch({ bodyForAgent, sessionKey, context }: DispatchOpts): AsyncIterable<RuntimeEvent>;
|
|
41
|
+
dispatch({ event, bodyForAgent, sessionKey, context }: DispatchOpts): AsyncIterable<RuntimeEvent>;
|
|
42
42
|
forkSession({ sessionKey: parentSessionKey }: ForkOpts): Promise<ForkSessionHandle | null>;
|
|
43
43
|
cleanupFork({ fork }: CleanupForkOpts): void;
|
|
44
44
|
private logForkFailure;
|
package/dist/dispatch.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../src/dispatch.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../src/dispatch.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EACV,eAAe,EACf,eAAe,EACf,YAAY,EACZ,QAAQ,EACR,iBAAiB,EACjB,aAAa,EACb,YAAY,EACb,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAIpD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAEhE,KAAK,4BAA4B,GAAG,IAAI,CACtC,gBAAgB,EACd,gBAAgB,GAChB,UAAU,GACV,WAAW,GACX,OAAO,GACP,iBAAiB,GACjB,SAAS,GACT,cAAc,CACjB,GAAG;IACF,cAAc,EAAE,mBAAmB,CAAC;IACpC,GAAG,CAAC,EAAE,aAAa,CAAC;CACrB,CAAC;AAOF;;;;;;;;;;;;;GAaG;AACH,qBAAa,qBAAsB,YAAW,eAAe;IA6B/C,OAAO,CAAC,QAAQ,CAAC,IAAI;IA5BjC,OAAO,CAAC,MAAM,CAAmC;IACjD,OAAO,CAAC,IAAI,CAA+C;IAC3D,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,YAAY,CAA8B;IAClD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA+B;IAC3D,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAqB;IACtD,OAAO,CAAC,QAAQ,CAAS;IAEzB;;;;;;;OAOG;IACH,OAAO,CAAC,aAAa;gBAYQ,IAAI,EAAE,4BAA4B;IAExD,QAAQ,CAAC,EAAE,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,OAAO,EAAE,EAAE,YAAY,GAAG,aAAa,CAAC,YAAY,CAAC;IAgLlG,WAAW,CAAC,EAAE,UAAU,EAAE,gBAAgB,EAAE,EAAE,QAAQ,GAAG,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC;IAuBhG,WAAW,CAAC,EAAE,IAAI,EAAE,EAAE,eAAe;IAIrC,OAAO,CAAC,cAAc;IAKhB,IAAI;YAaI,aAAa;YAcb,OAAO;IA2DrB,OAAO,CAAC,qBAAqB;YAiCf,UAAU;IAgDxB,OAAO,CAAC,iBAAiB;CA2B1B"}
|
package/dist/dispatch.js
CHANGED
|
@@ -2,6 +2,7 @@ import { spawn } from "node:child_process";
|
|
|
2
2
|
import { execSync } from "node:child_process";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
import * as fs from "node:fs";
|
|
5
|
+
import { appendPreparedLocalAttachmentRefs, ensureLocalAttachmentGitExclude, pinLocalAttachmentPaths, } from "@parall/agent-core/internal/attachment-input";
|
|
5
6
|
import { normalizeApprovalPolicy, normalizeSandbox } from "./config.js";
|
|
6
7
|
import { EventMapper } from "./event-mapping.js";
|
|
7
8
|
import { JsonRpcStdioClient } from "./jsonrpc-client.js";
|
|
@@ -48,7 +49,7 @@ export class CodexAppServerAdapter {
|
|
|
48
49
|
constructor(opts) {
|
|
49
50
|
this.opts = opts;
|
|
50
51
|
}
|
|
51
|
-
async *dispatch({ bodyForAgent, sessionKey, context }) {
|
|
52
|
+
async *dispatch({ event, bodyForAgent, sessionKey, context }) {
|
|
52
53
|
await this.ensureStarted(context.log);
|
|
53
54
|
// After ensureStarted resolves the subprocess could still die before we
|
|
54
55
|
// capture the client (handleSubprocessClose nulls this.client). Yield a
|
|
@@ -113,14 +114,37 @@ export class CodexAppServerAdapter {
|
|
|
113
114
|
this.setActiveTurn(threadId, sink, log);
|
|
114
115
|
const groupKey = randomUUID();
|
|
115
116
|
let sawTurnEnd = false;
|
|
117
|
+
let releasePreparedAttachments = () => { };
|
|
116
118
|
try {
|
|
119
|
+
let preparedBody = bodyForAgent;
|
|
120
|
+
let preparedImages = [];
|
|
121
|
+
try {
|
|
122
|
+
const prepared = await appendPreparedLocalAttachmentRefs(bodyForAgent, event, context, {
|
|
123
|
+
workspaceDir: this.opts.workspaceDir,
|
|
124
|
+
log,
|
|
125
|
+
});
|
|
126
|
+
preparedBody = prepared.body;
|
|
127
|
+
preparedImages = prepared.attachments.images;
|
|
128
|
+
releasePreparedAttachments = pinLocalAttachmentPaths(preparedImages);
|
|
129
|
+
}
|
|
130
|
+
catch (err) {
|
|
131
|
+
log?.warn?.(`codex-agent[${context.accountId}]: failed to prepare local attachments: ${errToString(err)}`);
|
|
132
|
+
}
|
|
133
|
+
// `localImage` is a first-class variant of `UserInput` in the Codex
|
|
134
|
+
// app-server protocol (codex-rs/app-server-protocol/src/protocol/v2.rs
|
|
135
|
+
// — it sits alongside `text` and `image`), so we send the combined
|
|
136
|
+
// text+localImage input directly. If a future Codex release breaks
|
|
137
|
+
// this contract, we want a hard error surfaced to the operator, not a
|
|
138
|
+
// silent fallback that hides real protocol drift.
|
|
139
|
+
const turnInput = buildTurnInput(preparedBody, preparedImages);
|
|
140
|
+
const startTurn = (targetThreadId) => client.sendRequest("turn/start", {
|
|
141
|
+
threadId: targetThreadId,
|
|
142
|
+
input: turnInput,
|
|
143
|
+
});
|
|
117
144
|
// Start the turn. If the stored threadId is dead, we retry once with a fresh thread.
|
|
118
145
|
let turnStartResult;
|
|
119
146
|
try {
|
|
120
|
-
turnStartResult = await
|
|
121
|
-
threadId,
|
|
122
|
-
input: buildTurnInput(bodyForAgent),
|
|
123
|
-
});
|
|
147
|
+
turnStartResult = await startTurn(threadId);
|
|
124
148
|
}
|
|
125
149
|
catch (err) {
|
|
126
150
|
const message = errToString(err);
|
|
@@ -148,10 +172,7 @@ export class CodexAppServerAdapter {
|
|
|
148
172
|
}
|
|
149
173
|
this.setActiveTurn(freshThreadId, sink, log);
|
|
150
174
|
try {
|
|
151
|
-
turnStartResult = await
|
|
152
|
-
threadId: freshThreadId,
|
|
153
|
-
input: buildTurnInput(bodyForAgent),
|
|
154
|
-
});
|
|
175
|
+
turnStartResult = await startTurn(freshThreadId);
|
|
155
176
|
}
|
|
156
177
|
catch (retryErr) {
|
|
157
178
|
// Retry also failed — likely transient or systemic, not a stale-thread
|
|
@@ -179,24 +200,25 @@ export class CodexAppServerAdapter {
|
|
|
179
200
|
yield { type: "error", message: envelope.message };
|
|
180
201
|
continue;
|
|
181
202
|
}
|
|
182
|
-
const
|
|
183
|
-
if (
|
|
184
|
-
yield
|
|
203
|
+
const runtimeEvent = envelope.event;
|
|
204
|
+
if (runtimeEvent.type === "error") {
|
|
205
|
+
yield runtimeEvent;
|
|
185
206
|
continue;
|
|
186
207
|
}
|
|
187
|
-
if (
|
|
208
|
+
if (runtimeEvent.type === "text") {
|
|
188
209
|
// Layer 0 symmetric output contract: Codex's plain text is never
|
|
189
210
|
// projected as a chat message. Outbound messages must come from the
|
|
190
211
|
// agent explicitly invoking `@parall/cli messages send` / `dm` via
|
|
191
212
|
// the shell/exec tool. Text events are still yielded so agent-core
|
|
192
213
|
// records them as suppressed session steps for audit.
|
|
193
|
-
yield { ...
|
|
214
|
+
yield { ...runtimeEvent, project: false, groupKey };
|
|
194
215
|
continue;
|
|
195
216
|
}
|
|
196
|
-
yield { ...
|
|
217
|
+
yield { ...runtimeEvent, groupKey };
|
|
197
218
|
}
|
|
198
219
|
}
|
|
199
220
|
finally {
|
|
221
|
+
releasePreparedAttachments();
|
|
200
222
|
this.activeTurns.delete(threadId);
|
|
201
223
|
if (!sawTurnEnd) {
|
|
202
224
|
// Defensive: if we bailed early, make sure we leave no dangling sink.
|
|
@@ -455,15 +477,6 @@ class TurnSink {
|
|
|
455
477
|
r?.({ kind: "turn_end" });
|
|
456
478
|
}
|
|
457
479
|
}
|
|
458
|
-
/**
|
|
459
|
-
* Codex app-server's `turn/start` expects `input` as an array of content
|
|
460
|
-
* items (each `{ type: "text", text: "..." }` or similar), not a raw string.
|
|
461
|
-
* Wrap the event body so the protocol contract is honoured — sending a bare
|
|
462
|
-
* string has worked historically via undocumented coercion but isn't stable.
|
|
463
|
-
*/
|
|
464
|
-
function buildTurnInput(body) {
|
|
465
|
-
return [{ type: "text", text: body }];
|
|
466
|
-
}
|
|
467
480
|
function ensureGitRepo(workingDirectory) {
|
|
468
481
|
fs.mkdirSync(workingDirectory, { recursive: true });
|
|
469
482
|
// Only `git init` if the workspace isn't already inside any git repo. A
|
|
@@ -472,6 +485,7 @@ function ensureGitRepo(workingDirectory) {
|
|
|
472
485
|
// and silently creating a nested repo there would mangle their layout.
|
|
473
486
|
try {
|
|
474
487
|
execSync("git rev-parse --is-inside-work-tree", { cwd: workingDirectory, stdio: "pipe" });
|
|
488
|
+
ensureLocalAttachmentGitExclude(workingDirectory);
|
|
475
489
|
return;
|
|
476
490
|
}
|
|
477
491
|
catch {
|
|
@@ -487,11 +501,18 @@ function ensureGitRepo(workingDirectory) {
|
|
|
487
501
|
try {
|
|
488
502
|
execSync("git init", { cwd: workingDirectory, stdio: "pipe", env });
|
|
489
503
|
execSync("git commit --allow-empty -m init", { cwd: workingDirectory, stdio: "pipe", env });
|
|
504
|
+
ensureLocalAttachmentGitExclude(workingDirectory);
|
|
490
505
|
}
|
|
491
506
|
catch {
|
|
492
507
|
// Non-fatal: codex app-server may still accept a bare directory. Let it raise at turn time.
|
|
493
508
|
}
|
|
494
509
|
}
|
|
510
|
+
function buildTurnInput(body, images) {
|
|
511
|
+
return [
|
|
512
|
+
{ type: "text", text: body },
|
|
513
|
+
...images.map((image) => ({ type: "localImage", path: image.localPath })),
|
|
514
|
+
];
|
|
515
|
+
}
|
|
495
516
|
function extractThreadId(result) {
|
|
496
517
|
if (!result || typeof result !== "object")
|
|
497
518
|
return undefined;
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import * as os from "node:os";
|
|
3
3
|
import { ParallAgentGateway, parseShutdownDeadlineMs } from "@parall/agent-core";
|
|
4
|
-
import { ParallClient, ParallWs } from "@parall/sdk";
|
|
4
|
+
import { ApiError, ParallClient, ParallWs } from "@parall/sdk";
|
|
5
5
|
import { buildCodexRuntimeKey, resolveCodexAgentConfig, resolveWsUrl, sessionStateFilePathForRuntime, stepIdFilePathForSession, } from "./config.js";
|
|
6
6
|
import { CodexAppServerAdapter } from "./dispatch.js";
|
|
7
7
|
import { CodexSessionManager } from "./session-manager.js";
|
|
@@ -13,6 +13,18 @@ function createLogger(prefix) {
|
|
|
13
13
|
error: (msg) => console.error(`[${prefix}] ${msg}`),
|
|
14
14
|
};
|
|
15
15
|
}
|
|
16
|
+
async function getAgentMeWithLegacyFallback(client, orgId) {
|
|
17
|
+
try {
|
|
18
|
+
return await client.getAgentMe(orgId);
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
if (!(err instanceof ApiError && err.status === 404)) {
|
|
22
|
+
throw err;
|
|
23
|
+
}
|
|
24
|
+
const user = await client.getMe();
|
|
25
|
+
return { ...user, agent_profile: null };
|
|
26
|
+
}
|
|
27
|
+
}
|
|
16
28
|
async function main() {
|
|
17
29
|
const config = resolveCodexAgentConfig(process.env);
|
|
18
30
|
const log = createLogger("codex-agent");
|
|
@@ -21,9 +33,13 @@ async function main() {
|
|
|
21
33
|
token: config.apiKey,
|
|
22
34
|
swimlaneName: config.swimlaneName,
|
|
23
35
|
});
|
|
24
|
-
const me = await client.
|
|
36
|
+
const me = await getAgentMeWithLegacyFallback(client, config.orgId);
|
|
25
37
|
const agentUserId = me.id;
|
|
26
|
-
ensureCodexWorkspace(config.workspaceDir, log, {
|
|
38
|
+
ensureCodexWorkspace(config.workspaceDir, log, {
|
|
39
|
+
userId: agentUserId,
|
|
40
|
+
displayName: me.display_name,
|
|
41
|
+
description: me.agent_profile?.description ?? undefined,
|
|
42
|
+
});
|
|
27
43
|
const runtimeKey = config.runtimeKey || buildCodexRuntimeKey(agentUserId);
|
|
28
44
|
const sessionStateFilePath = sessionStateFilePathForRuntime(config.stateDir, runtimeKey);
|
|
29
45
|
const sessionManager = new CodexSessionManager(runtimeKey, sessionStateFilePath, log);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parall/codex-agent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.24.0",
|
|
4
4
|
"description": "Codex CLI bridge runtime for self-hosted Parall agents",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -25,9 +25,9 @@
|
|
|
25
25
|
"src"
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@parall/agent-core": "1.
|
|
29
|
-
"@parall/cli": "1.
|
|
30
|
-
"@parall/sdk": "1.
|
|
28
|
+
"@parall/agent-core": "1.24.0",
|
|
29
|
+
"@parall/cli": "1.24.0",
|
|
30
|
+
"@parall/sdk": "1.24.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@types/node": "^22.0.0",
|
package/src/dispatch.ts
CHANGED
|
@@ -2,7 +2,12 @@ import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process";
|
|
|
2
2
|
import { execSync } from "node:child_process";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
import * as fs from "node:fs";
|
|
5
|
-
import
|
|
5
|
+
import {
|
|
6
|
+
appendPreparedLocalAttachmentRefs,
|
|
7
|
+
ensureLocalAttachmentGitExclude,
|
|
8
|
+
pinLocalAttachmentPaths,
|
|
9
|
+
} from "@parall/agent-core/internal/attachment-input";
|
|
10
|
+
import type { PreparedLocalImage } from "@parall/agent-core/internal/attachment-input";
|
|
6
11
|
import type {
|
|
7
12
|
CleanupForkOpts,
|
|
8
13
|
DispatchAdapter,
|
|
@@ -82,7 +87,7 @@ export class CodexAppServerAdapter implements DispatchAdapter {
|
|
|
82
87
|
|
|
83
88
|
constructor(private readonly opts: CodexAppServerAdapterOptions) {}
|
|
84
89
|
|
|
85
|
-
async *dispatch({ bodyForAgent, sessionKey, context }: DispatchOpts): AsyncIterable<RuntimeEvent> {
|
|
90
|
+
async *dispatch({ event, bodyForAgent, sessionKey, context }: DispatchOpts): AsyncIterable<RuntimeEvent> {
|
|
86
91
|
await this.ensureStarted(context.log);
|
|
87
92
|
// After ensureStarted resolves the subprocess could still die before we
|
|
88
93
|
// capture the client (handleSubprocessClose nulls this.client). Yield a
|
|
@@ -145,15 +150,39 @@ export class CodexAppServerAdapter implements DispatchAdapter {
|
|
|
145
150
|
this.setActiveTurn(threadId, sink, log);
|
|
146
151
|
const groupKey = randomUUID();
|
|
147
152
|
let sawTurnEnd = false;
|
|
153
|
+
let releasePreparedAttachments = () => {};
|
|
148
154
|
|
|
149
155
|
try {
|
|
156
|
+
let preparedBody = bodyForAgent;
|
|
157
|
+
let preparedImages: PreparedLocalImage[] = [];
|
|
158
|
+
try {
|
|
159
|
+
const prepared = await appendPreparedLocalAttachmentRefs(bodyForAgent, event, context, {
|
|
160
|
+
workspaceDir: this.opts.workspaceDir,
|
|
161
|
+
log,
|
|
162
|
+
});
|
|
163
|
+
preparedBody = prepared.body;
|
|
164
|
+
preparedImages = prepared.attachments.images;
|
|
165
|
+
releasePreparedAttachments = pinLocalAttachmentPaths(preparedImages);
|
|
166
|
+
} catch (err) {
|
|
167
|
+
log?.warn?.(`codex-agent[${context.accountId}]: failed to prepare local attachments: ${errToString(err)}`);
|
|
168
|
+
}
|
|
169
|
+
// `localImage` is a first-class variant of `UserInput` in the Codex
|
|
170
|
+
// app-server protocol (codex-rs/app-server-protocol/src/protocol/v2.rs
|
|
171
|
+
// — it sits alongside `text` and `image`), so we send the combined
|
|
172
|
+
// text+localImage input directly. If a future Codex release breaks
|
|
173
|
+
// this contract, we want a hard error surfaced to the operator, not a
|
|
174
|
+
// silent fallback that hides real protocol drift.
|
|
175
|
+
const turnInput = buildTurnInput(preparedBody, preparedImages);
|
|
176
|
+
const startTurn = (targetThreadId: string): Promise<unknown> =>
|
|
177
|
+
client.sendRequest("turn/start", {
|
|
178
|
+
threadId: targetThreadId,
|
|
179
|
+
input: turnInput,
|
|
180
|
+
});
|
|
181
|
+
|
|
150
182
|
// Start the turn. If the stored threadId is dead, we retry once with a fresh thread.
|
|
151
183
|
let turnStartResult: unknown;
|
|
152
184
|
try {
|
|
153
|
-
turnStartResult = await
|
|
154
|
-
threadId,
|
|
155
|
-
input: buildTurnInput(bodyForAgent),
|
|
156
|
-
});
|
|
185
|
+
turnStartResult = await startTurn(threadId);
|
|
157
186
|
} catch (err) {
|
|
158
187
|
const message = errToString(err);
|
|
159
188
|
// For the main session we attempt one fresh-thread retry. Codex CLI's
|
|
@@ -179,10 +208,7 @@ export class CodexAppServerAdapter implements DispatchAdapter {
|
|
|
179
208
|
}
|
|
180
209
|
this.setActiveTurn(freshThreadId, sink, log);
|
|
181
210
|
try {
|
|
182
|
-
turnStartResult = await
|
|
183
|
-
threadId: freshThreadId,
|
|
184
|
-
input: buildTurnInput(bodyForAgent),
|
|
185
|
-
});
|
|
211
|
+
turnStartResult = await startTurn(freshThreadId);
|
|
186
212
|
} catch (retryErr) {
|
|
187
213
|
// Retry also failed — likely transient or systemic, not a stale-thread
|
|
188
214
|
// issue. Don't clobber the persisted threadId; next dispatch will try
|
|
@@ -211,23 +237,24 @@ export class CodexAppServerAdapter implements DispatchAdapter {
|
|
|
211
237
|
yield { type: "error", message: envelope.message };
|
|
212
238
|
continue;
|
|
213
239
|
}
|
|
214
|
-
const
|
|
215
|
-
if (
|
|
216
|
-
yield
|
|
240
|
+
const runtimeEvent = envelope.event;
|
|
241
|
+
if (runtimeEvent.type === "error") {
|
|
242
|
+
yield runtimeEvent;
|
|
217
243
|
continue;
|
|
218
244
|
}
|
|
219
|
-
if (
|
|
245
|
+
if (runtimeEvent.type === "text") {
|
|
220
246
|
// Layer 0 symmetric output contract: Codex's plain text is never
|
|
221
247
|
// projected as a chat message. Outbound messages must come from the
|
|
222
248
|
// agent explicitly invoking `@parall/cli messages send` / `dm` via
|
|
223
249
|
// the shell/exec tool. Text events are still yielded so agent-core
|
|
224
250
|
// records them as suppressed session steps for audit.
|
|
225
|
-
yield { ...
|
|
251
|
+
yield { ...runtimeEvent, project: false, groupKey };
|
|
226
252
|
continue;
|
|
227
253
|
}
|
|
228
|
-
yield { ...
|
|
254
|
+
yield { ...runtimeEvent, groupKey };
|
|
229
255
|
}
|
|
230
256
|
} finally {
|
|
257
|
+
releasePreparedAttachments();
|
|
231
258
|
this.activeTurns.delete(threadId!);
|
|
232
259
|
if (!sawTurnEnd) {
|
|
233
260
|
// Defensive: if we bailed early, make sure we leave no dangling sink.
|
|
@@ -505,16 +532,6 @@ class TurnSink {
|
|
|
505
532
|
}
|
|
506
533
|
}
|
|
507
534
|
|
|
508
|
-
/**
|
|
509
|
-
* Codex app-server's `turn/start` expects `input` as an array of content
|
|
510
|
-
* items (each `{ type: "text", text: "..." }` or similar), not a raw string.
|
|
511
|
-
* Wrap the event body so the protocol contract is honoured — sending a bare
|
|
512
|
-
* string has worked historically via undocumented coercion but isn't stable.
|
|
513
|
-
*/
|
|
514
|
-
function buildTurnInput(body: string): Array<{ type: "text"; text: string }> {
|
|
515
|
-
return [{ type: "text", text: body }];
|
|
516
|
-
}
|
|
517
|
-
|
|
518
535
|
function ensureGitRepo(workingDirectory: string): void {
|
|
519
536
|
fs.mkdirSync(workingDirectory, { recursive: true });
|
|
520
537
|
// Only `git init` if the workspace isn't already inside any git repo. A
|
|
@@ -523,6 +540,7 @@ function ensureGitRepo(workingDirectory: string): void {
|
|
|
523
540
|
// and silently creating a nested repo there would mangle their layout.
|
|
524
541
|
try {
|
|
525
542
|
execSync("git rev-parse --is-inside-work-tree", { cwd: workingDirectory, stdio: "pipe" });
|
|
543
|
+
ensureLocalAttachmentGitExclude(workingDirectory);
|
|
526
544
|
return;
|
|
527
545
|
} catch {
|
|
528
546
|
// Not inside a repo — fall through to init.
|
|
@@ -537,11 +555,23 @@ function ensureGitRepo(workingDirectory: string): void {
|
|
|
537
555
|
try {
|
|
538
556
|
execSync("git init", { cwd: workingDirectory, stdio: "pipe", env });
|
|
539
557
|
execSync("git commit --allow-empty -m init", { cwd: workingDirectory, stdio: "pipe", env });
|
|
558
|
+
ensureLocalAttachmentGitExclude(workingDirectory);
|
|
540
559
|
} catch {
|
|
541
560
|
// Non-fatal: codex app-server may still accept a bare directory. Let it raise at turn time.
|
|
542
561
|
}
|
|
543
562
|
}
|
|
544
563
|
|
|
564
|
+
type CodexTurnInput =
|
|
565
|
+
| { type: "text"; text: string }
|
|
566
|
+
| { type: "localImage"; path: string };
|
|
567
|
+
|
|
568
|
+
function buildTurnInput(body: string, images: PreparedLocalImage[]): CodexTurnInput[] {
|
|
569
|
+
return [
|
|
570
|
+
{ type: "text", text: body },
|
|
571
|
+
...images.map((image) => ({ type: "localImage" as const, path: image.localPath })),
|
|
572
|
+
];
|
|
573
|
+
}
|
|
574
|
+
|
|
545
575
|
function extractThreadId(result: unknown): string | undefined {
|
|
546
576
|
if (!result || typeof result !== "object") return undefined;
|
|
547
577
|
const r = result as Record<string, unknown>;
|
package/src/index.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import * as os from "node:os";
|
|
4
4
|
import { ParallAgentGateway, parseShutdownDeadlineMs } from "@parall/agent-core";
|
|
5
|
-
import { ParallClient, ParallWs } from "@parall/sdk";
|
|
5
|
+
import { ApiError, ParallClient, ParallWs } from "@parall/sdk";
|
|
6
6
|
import {
|
|
7
7
|
buildCodexRuntimeKey,
|
|
8
8
|
resolveCodexAgentConfig,
|
|
@@ -22,6 +22,18 @@ function createLogger(prefix: string) {
|
|
|
22
22
|
};
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
async function getAgentMeWithLegacyFallback(client: ParallClient, orgId: string) {
|
|
26
|
+
try {
|
|
27
|
+
return await client.getAgentMe(orgId);
|
|
28
|
+
} catch (err) {
|
|
29
|
+
if (!(err instanceof ApiError && err.status === 404)) {
|
|
30
|
+
throw err;
|
|
31
|
+
}
|
|
32
|
+
const user = await client.getMe();
|
|
33
|
+
return { ...user, agent_profile: null };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
25
37
|
async function main() {
|
|
26
38
|
const config = resolveCodexAgentConfig(process.env);
|
|
27
39
|
const log = createLogger("codex-agent");
|
|
@@ -32,9 +44,13 @@ async function main() {
|
|
|
32
44
|
swimlaneName: config.swimlaneName,
|
|
33
45
|
});
|
|
34
46
|
|
|
35
|
-
const me = await client.
|
|
47
|
+
const me = await getAgentMeWithLegacyFallback(client, config.orgId);
|
|
36
48
|
const agentUserId = me.id;
|
|
37
|
-
ensureCodexWorkspace(config.workspaceDir, log, {
|
|
49
|
+
ensureCodexWorkspace(config.workspaceDir, log, {
|
|
50
|
+
userId: agentUserId,
|
|
51
|
+
displayName: me.display_name,
|
|
52
|
+
description: me.agent_profile?.description ?? undefined,
|
|
53
|
+
});
|
|
38
54
|
const runtimeKey = config.runtimeKey || buildCodexRuntimeKey(agentUserId);
|
|
39
55
|
const sessionStateFilePath = sessionStateFilePathForRuntime(config.stateDir, runtimeKey);
|
|
40
56
|
const sessionManager = new CodexSessionManager(
|