@parall/codex-agent 1.23.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.
@@ -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;
@@ -1 +1 @@
1
- {"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../src/dispatch.ts"],"names":[],"mappings":"AAKA,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,YAAY,EAAE,UAAU,EAAE,OAAO,EAAE,EAAE,YAAY,GAAG,aAAa,CAAC,YAAY,CAAC;IA0J3F,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"}
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 client.sendRequest("turn/start", {
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 client.sendRequest("turn/start", {
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 event = envelope.event;
183
- if (event.type === "error") {
184
- yield event;
203
+ const runtimeEvent = envelope.event;
204
+ if (runtimeEvent.type === "error") {
205
+ yield runtimeEvent;
185
206
  continue;
186
207
  }
187
- if (event.type === "text") {
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 { ...event, project: false, groupKey };
214
+ yield { ...runtimeEvent, project: false, groupKey };
194
215
  continue;
195
216
  }
196
- yield { ...event, groupKey };
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.getMe();
36
+ const me = await getAgentMeWithLegacyFallback(client, config.orgId);
25
37
  const agentUserId = me.id;
26
- ensureCodexWorkspace(config.workspaceDir, log, { userId: agentUserId, displayName: me.display_name });
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.23.0",
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.23.0",
29
- "@parall/cli": "1.23.0",
30
- "@parall/sdk": "1.23.0"
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 * as path from "node:path";
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 client.sendRequest("turn/start", {
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 client.sendRequest("turn/start", {
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 event = envelope.event;
215
- if (event.type === "error") {
216
- yield event;
240
+ const runtimeEvent = envelope.event;
241
+ if (runtimeEvent.type === "error") {
242
+ yield runtimeEvent;
217
243
  continue;
218
244
  }
219
- if (event.type === "text") {
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 { ...event, project: false, groupKey };
251
+ yield { ...runtimeEvent, project: false, groupKey };
226
252
  continue;
227
253
  }
228
- yield { ...event, groupKey };
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.getMe();
47
+ const me = await getAgentMeWithLegacyFallback(client, config.orgId);
36
48
  const agentUserId = me.id;
37
- ensureCodexWorkspace(config.workspaceDir, log, { userId: agentUserId, displayName: me.display_name });
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(