@parall/codex-agent 1.25.0 → 1.26.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 CHANGED
@@ -11,14 +11,14 @@ export type CodexAgentConfig = {
11
11
  model?: string;
12
12
  reasoningEffort?: string;
13
13
  /**
14
- * Only set when the user explicitly specified `PRLL_CODEX_SANDBOX`.
15
- * When undefined, the bridge deliberately does NOT pass `sandbox` on
16
- * `thread/{start,resume}` so codex reads whatever the user configured
17
- * in `~/.codex/config.toml` (`sandbox_mode`). This matches the self-
18
- * hosted philosophy: user's codex setup is authoritative; bridge only
19
- * overrides when the user asks it to.
14
+ * Sandbox mode passed to codex on `thread/{start,resume}`. Defaults to
15
+ * `danger-full-access` same as the hosted container because codex's
16
+ * inner bubblewrap sandbox requires unprivileged user namespaces that
17
+ * most environments lack, and the agent needs unrestricted shell access
18
+ * (e.g. `parall messages send`). Users can still override via
19
+ * `PRLL_CODEX_SANDBOX` env var.
20
20
  */
21
- sandbox?: string;
21
+ sandbox: string;
22
22
  /**
23
23
  * Approval policy is forced to `"never"` by default (not configurable
24
24
  * via config.toml override): Parall's HITL contract routes approvals
@@ -51,5 +51,6 @@ export declare function normalizeApprovalPolicy(value: string): string;
51
51
  export declare function resolveWsUrl(apiUrl: string, explicitWsUrl?: string, swimlaneName?: string): string;
52
52
  export declare function buildCodexRuntimeKey(agentUserId: string): string;
53
53
  export declare function sessionStateFilePathForRuntime(stateDir: string, runtimeKey: string): string;
54
+ export declare function contextFilePathForSession(stateDir: string, sessionKey: string): string;
54
55
  export declare function stepIdFilePathForSession(stateDir: string, sessionKey: string): string;
55
56
  //# sourceMappingURL=config.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;OAKG;IACH,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAcF,wBAAgB,uBAAuB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,gBAAgB,CAwC9F;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAUtD;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAW7D;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAMlG;AAED,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAEhE;AAED,wBAAgB,8BAA8B,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAG3F;AAED,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAGrF"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;;;OAOG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAcF,wBAAgB,uBAAuB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,gBAAgB,CAwC9F;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAUtD;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAW7D;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAMlG;AAED,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAEhE;AAED,wBAAgB,8BAA8B,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAG3F;AAED,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAGtF;AAED,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAGrF"}
package/dist/config.js CHANGED
@@ -41,7 +41,7 @@ export function resolveCodexAgentConfig(env = process.env) {
41
41
  workspaceDir,
42
42
  model: env.PRLL_CODEX_MODEL?.trim() || undefined,
43
43
  reasoningEffort: env.PRLL_CODEX_REASONING_EFFORT?.trim() || undefined,
44
- sandbox: env.PRLL_CODEX_SANDBOX?.trim() || undefined,
44
+ sandbox: env.PRLL_CODEX_SANDBOX?.trim() || "danger-full-access",
45
45
  approvalPolicy: env.PRLL_CODEX_APPROVAL?.trim() || "never",
46
46
  runtimeKey: env.PRLL_CODEX_RUNTIME_KEY?.trim() || undefined,
47
47
  };
@@ -101,6 +101,10 @@ export function sessionStateFilePathForRuntime(stateDir, runtimeKey) {
101
101
  const fileName = Buffer.from(runtimeKey).toString("base64url");
102
102
  return path.join(stateDir, "threads", `${fileName}.json`);
103
103
  }
104
+ export function contextFilePathForSession(stateDir, sessionKey) {
105
+ const fileName = Buffer.from(sessionKey).toString("base64url");
106
+ return path.join(stateDir, "dispatch-context", `${fileName}.json`);
107
+ }
104
108
  export function stepIdFilePathForSession(stateDir, sessionKey) {
105
109
  const fileName = Buffer.from(sessionKey).toString("base64url");
106
110
  return path.join(stateDir, "step-ids", `${fileName}.txt`);
@@ -4,6 +4,7 @@ import type { CodexSessionManager } from "./session-manager.js";
4
4
  type CodexAppServerAdapterOptions = Pick<CodexAgentConfig, "approvalPolicy" | "codexBin" | "codexHome" | "model" | "reasoningEffort" | "sandbox" | "workspaceDir"> & {
5
5
  sessionManager: CodexSessionManager;
6
6
  log?: GatewayLogger;
7
+ contextFilePath?: string;
7
8
  };
8
9
  /**
9
10
  * Bridge driver backed by `codex app-server --listen stdio://`.
@@ -38,6 +39,10 @@ export declare class CodexAppServerAdapter implements DispatchAdapter {
38
39
  */
39
40
  private setActiveTurn;
40
41
  constructor(opts: CodexAppServerAdapterOptions);
42
+ updateConfig(config: {
43
+ model?: string | null;
44
+ reasoningEffort?: string | null;
45
+ }): void;
41
46
  dispatch({ event, bodyForAgent, sessionKey, context }: DispatchOpts): AsyncIterable<RuntimeEvent>;
42
47
  forkSession({ sessionKey: parentSessionKey }: ForkOpts): Promise<ForkSessionHandle | null>;
43
48
  cleanupFork({ fork }: CleanupForkOpts): void;
@@ -1 +1 @@
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"}
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;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,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;IAE/D,YAAY,CAAC,MAAM,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI;IAK/E,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;IA8DrB,OAAO,CAAC,qBAAqB;YAiCf,UAAU;IAwCxB,OAAO,CAAC,iBAAiB;CA2B1B"}
package/dist/dispatch.js CHANGED
@@ -49,6 +49,12 @@ export class CodexAppServerAdapter {
49
49
  constructor(opts) {
50
50
  this.opts = opts;
51
51
  }
52
+ updateConfig(config) {
53
+ if (config.model !== undefined)
54
+ this.opts.model = config.model ?? undefined;
55
+ if (config.reasoningEffort !== undefined)
56
+ this.opts.reasoningEffort = config.reasoningEffort ?? undefined;
57
+ }
52
58
  async *dispatch({ event, bodyForAgent, sessionKey, context }) {
53
59
  await this.ensureStarted(context.log);
54
60
  // After ensureStarted resolves the subprocess could still die before we
@@ -304,6 +310,9 @@ export class CodexAppServerAdapter {
304
310
  FORCE_COLOR: "0",
305
311
  NO_COLOR: "1",
306
312
  };
313
+ if (this.opts.contextFilePath) {
314
+ env.PRLL_CONTEXT_FILE = this.opts.contextFilePath;
315
+ }
307
316
  const args = ["app-server", "--listen", "stdio://"];
308
317
  (log ?? this.opts.log)?.info?.(`codex-agent: spawning ${this.opts.codexBin} ${args.join(" ")}`);
309
318
  const proc = spawn(this.opts.codexBin, args, {
@@ -382,15 +391,7 @@ export class CodexAppServerAdapter {
382
391
  const commonParams = {
383
392
  approvalPolicy: normalizeApprovalPolicy(this.opts.approvalPolicy),
384
393
  };
385
- // Only override sandbox when the user explicitly set PRLL_CODEX_SANDBOX.
386
- // Otherwise let codex read `sandbox_mode` from ~/.codex/config.toml — the
387
- // user's own configuration is authoritative for a self-hosted bridge,
388
- // and codex 0.120.0's workspace-write mode has a write_stdin bug that
389
- // our previous `workspace-write` default tripped for users whose config
390
- // had danger-full-access.
391
- if (this.opts.sandbox) {
392
- commonParams.sandbox = normalizeSandbox(this.opts.sandbox);
393
- }
394
+ commonParams.sandbox = normalizeSandbox(this.opts.sandbox);
394
395
  if (this.opts.model)
395
396
  commonParams.model = this.opts.model;
396
397
  if (this.opts.reasoningEffort) {
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import * as os from "node:os";
3
- import { ParallAgentGateway, parseShutdownDeadlineMs } from "@parall/agent-core";
3
+ import { ParallAgentGateway, createPlatformConfigManager, isPlatformManagedProfile, parseShutdownDeadlineMs } from "@parall/agent-core";
4
4
  import { ApiError, ParallClient, ParallWs } from "@parall/sdk";
5
- import { buildCodexRuntimeKey, resolveCodexAgentConfig, resolveWsUrl, sessionStateFilePathForRuntime, stepIdFilePathForSession, } from "./config.js";
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 } from "./workspace.js";
@@ -41,6 +41,7 @@ async function main() {
41
41
  description: me.agent_profile?.description ?? undefined,
42
42
  });
43
43
  const runtimeKey = config.runtimeKey || buildCodexRuntimeKey(agentUserId);
44
+ const mainContextFilePath = contextFilePathForSession(config.stateDir, runtimeKey);
44
45
  const sessionStateFilePath = sessionStateFilePathForRuntime(config.stateDir, runtimeKey);
45
46
  const sessionManager = new CodexSessionManager(runtimeKey, sessionStateFilePath, log);
46
47
  const resolvedWsUrl = resolveWsUrl(config.apiUrl, config.wsUrl, config.swimlaneName);
@@ -48,16 +49,26 @@ async function main() {
48
49
  getTicket: () => client.getWsTicket(),
49
50
  wsUrl: resolvedWsUrl,
50
51
  });
52
+ const configMgr = createPlatformConfigManager({ client, stateDir: config.stateDir, runtimeType: "codex", log });
53
+ const platformDefaults = await configMgr.fetch();
54
+ let platformManaged = isPlatformManagedProfile(me.agent_profile);
55
+ const resolvedModel = platformManaged ? (platformDefaults.model ?? config.model) : config.model;
56
+ const resolvedEffort = platformManaged ? (platformDefaults.thinkingEffort ?? config.reasoningEffort) : config.reasoningEffort;
57
+ if (platformDefaults.model)
58
+ log.info(`platform config: model=${platformDefaults.model} (${platformManaged ? "applied" : "ignored, model_management=self"})`);
59
+ if (platformDefaults.thinkingEffort)
60
+ log.info(`platform config: reasoning_effort=${platformDefaults.thinkingEffort} (${platformManaged ? "applied" : "ignored, model_management=self"})`);
51
61
  const adapter = new CodexAppServerAdapter({
52
62
  codexBin: config.codexBin,
53
63
  codexHome: config.codexHome,
54
64
  workspaceDir: config.workspaceDir,
55
- model: config.model,
56
- reasoningEffort: config.reasoningEffort,
65
+ model: resolvedModel,
66
+ reasoningEffort: resolvedEffort,
57
67
  sandbox: config.sandbox,
58
68
  approvalPolicy: config.approvalPolicy,
59
69
  sessionManager,
60
70
  log,
71
+ contextFilePath: mainContextFilePath,
61
72
  });
62
73
  const gateway = new ParallAgentGateway({
63
74
  accountId: agentUserId,
@@ -82,7 +93,26 @@ async function main() {
82
93
  dispatchAdapter: adapter,
83
94
  log,
84
95
  shutdownDeadlineMs: parseShutdownDeadlineMs(process.env.PRLL_SHUTDOWN_DEADLINE_MS),
96
+ contextFilePathForSession: (sessionKey) => contextFilePathForSession(config.stateDir, sessionKey),
85
97
  stepIdFilePathForSession: (sessionKey) => stepIdFilePathForSession(config.stateDir, sessionKey),
98
+ onConfigUpdate: async () => {
99
+ const refreshed = await getAgentMeWithLegacyFallback(client, config.orgId);
100
+ platformManaged = isPlatformManagedProfile(refreshed.agent_profile);
101
+ const updated = await configMgr.fetch();
102
+ adapter.updateConfig({
103
+ model: platformManaged ? (updated.model ?? config.model) : (config.model ?? null),
104
+ reasoningEffort: platformManaged ? (updated.thinkingEffort ?? config.reasoningEffort) : (config.reasoningEffort ?? null),
105
+ });
106
+ },
107
+ onSessionReady: async () => {
108
+ const refreshed = await getAgentMeWithLegacyFallback(client, config.orgId);
109
+ platformManaged = isPlatformManagedProfile(refreshed.agent_profile);
110
+ const updated = await configMgr.fetch();
111
+ adapter.updateConfig({
112
+ model: platformManaged ? (updated.model ?? config.model) : (config.model ?? null),
113
+ reasoningEffort: platformManaged ? (updated.thinkingEffort ?? config.reasoningEffort) : (config.reasoningEffort ?? null),
114
+ });
115
+ },
86
116
  });
87
117
  const abortController = new AbortController();
88
118
  const abort = () => abortController.abort();
@@ -1 +1 @@
1
- {"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../src/workspace.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAExD,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,QA+B9B"}
1
+ {"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../src/workspace.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAExD,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,32 +1,21 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
- import { BRIDGE_WORKSPACE_INSTRUCTIONS, PRLL_BEHAVIOR, PRLL_REFERENCE_GUIDE, buildIdentity, } from "@parall/agent-core";
3
+ import { BRIDGE_WORKSPACE_INSTRUCTIONS, PRLL_BEHAVIOR, PRLL_REFERENCE_GUIDE, buildIdentity, buildSkillReferences, writeSkillFiles, } from "@parall/agent-core";
4
4
  export function ensureCodexWorkspace(workspaceDir, log, agentIdentity) {
5
- const AGENTS_MD = [
5
+ const systemPrompt = [
6
6
  buildIdentity(agentIdentity),
7
7
  BRIDGE_WORKSPACE_INSTRUCTIONS,
8
8
  PRLL_BEHAVIOR,
9
9
  PRLL_REFERENCE_GUIDE,
10
+ buildSkillReferences(workspaceDir),
10
11
  ].join("\n\n");
11
12
  fs.mkdirSync(workspaceDir, { recursive: true });
12
- // AGENTS.md is bridge-managed: always overwrite so the workspace picks up
13
- // updated guardrails / command templates on upgrade. Operators must not
14
- // hand-edit this file — workspace customisations should go elsewhere in
15
- // the Codex home dir. Surface a warning when we replace divergent content
16
- // so an operator who did edit it locally sees a signal instead of silently
17
- // losing changes. Mirrors the Claude bridge behaviour.
18
- const agentsMdPath = path.join(workspaceDir, "AGENTS.md");
19
- if (log) {
20
- try {
21
- const existing = fs.readFileSync(agentsMdPath, "utf8");
22
- if (existing !== AGENTS_MD) {
23
- log.warn(`codex-agent: overwriting divergent ${agentsMdPath} with bridge-managed template ` +
24
- `(local edits to AGENTS.md are not preserved — customize other files in the workspace instead)`);
25
- }
26
- }
27
- catch {
28
- // file missing or unreadable — first-boot case, no warning needed
29
- }
30
- }
31
- fs.writeFileSync(agentsMdPath, AGENTS_MD, "utf8");
13
+ const parallDir = path.join(workspaceDir, ".parall");
14
+ fs.mkdirSync(parallDir, { recursive: true });
15
+ fs.writeFileSync(path.join(parallDir, "system-prompt.md"), systemPrompt, "utf8");
16
+ writeSkillFiles(path.join(parallDir, "skills"));
17
+ const codexConfigDir = path.join(workspaceDir, ".codex");
18
+ fs.mkdirSync(codexConfigDir, { recursive: true });
19
+ const toml = `developer_instructions = """\n${systemPrompt.replace(/\\/g, "\\\\").replace(/"""/g, '\\"""')}\n"""\n`;
20
+ fs.writeFileSync(path.join(codexConfigDir, "config.toml"), toml, "utf8");
32
21
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@parall/codex-agent",
3
- "version": "1.25.0",
3
+ "version": "1.26.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.25.0",
29
- "@parall/cli": "1.25.0",
30
- "@parall/sdk": "1.25.0"
28
+ "@parall/agent-core": "1.26.0",
29
+ "@parall/cli": "1.26.0",
30
+ "@parall/sdk": "1.26.0"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@types/node": "^22.0.0",
package/src/config.ts CHANGED
@@ -14,14 +14,14 @@ export type CodexAgentConfig = {
14
14
  model?: string;
15
15
  reasoningEffort?: string;
16
16
  /**
17
- * Only set when the user explicitly specified `PRLL_CODEX_SANDBOX`.
18
- * When undefined, the bridge deliberately does NOT pass `sandbox` on
19
- * `thread/{start,resume}` so codex reads whatever the user configured
20
- * in `~/.codex/config.toml` (`sandbox_mode`). This matches the self-
21
- * hosted philosophy: user's codex setup is authoritative; bridge only
22
- * overrides when the user asks it to.
17
+ * Sandbox mode passed to codex on `thread/{start,resume}`. Defaults to
18
+ * `danger-full-access` same as the hosted container because codex's
19
+ * inner bubblewrap sandbox requires unprivileged user namespaces that
20
+ * most environments lack, and the agent needs unrestricted shell access
21
+ * (e.g. `parall messages send`). Users can still override via
22
+ * `PRLL_CODEX_SANDBOX` env var.
23
23
  */
24
- sandbox?: string;
24
+ sandbox: string;
25
25
  /**
26
26
  * Approval policy is forced to `"never"` by default (not configurable
27
27
  * via config.toml override): Parall's HITL contract routes approvals
@@ -80,7 +80,7 @@ export function resolveCodexAgentConfig(env: NodeJS.ProcessEnv = process.env): C
80
80
  workspaceDir,
81
81
  model: env.PRLL_CODEX_MODEL?.trim() || undefined,
82
82
  reasoningEffort: env.PRLL_CODEX_REASONING_EFFORT?.trim() || undefined,
83
- sandbox: env.PRLL_CODEX_SANDBOX?.trim() || undefined,
83
+ sandbox: env.PRLL_CODEX_SANDBOX?.trim() || "danger-full-access",
84
84
  approvalPolicy: env.PRLL_CODEX_APPROVAL?.trim() || "never",
85
85
  runtimeKey: env.PRLL_CODEX_RUNTIME_KEY?.trim() || undefined,
86
86
  };
@@ -145,6 +145,11 @@ export function sessionStateFilePathForRuntime(stateDir: string, runtimeKey: str
145
145
  return path.join(stateDir, "threads", `${fileName}.json`);
146
146
  }
147
147
 
148
+ export function contextFilePathForSession(stateDir: string, sessionKey: string): string {
149
+ const fileName = Buffer.from(sessionKey).toString("base64url");
150
+ return path.join(stateDir, "dispatch-context", `${fileName}.json`);
151
+ }
152
+
148
153
  export function stepIdFilePathForSession(stateDir: string, sessionKey: string): string {
149
154
  const fileName = Buffer.from(sessionKey).toString("base64url");
150
155
  return path.join(stateDir, "step-ids", `${fileName}.txt`);
package/src/dispatch.ts CHANGED
@@ -35,6 +35,7 @@ type CodexAppServerAdapterOptions = Pick<
35
35
  > & {
36
36
  sessionManager: CodexSessionManager;
37
37
  log?: GatewayLogger;
38
+ contextFilePath?: string;
38
39
  };
39
40
 
40
41
  type TurnEventEnvelope =
@@ -87,6 +88,11 @@ export class CodexAppServerAdapter implements DispatchAdapter {
87
88
 
88
89
  constructor(private readonly opts: CodexAppServerAdapterOptions) {}
89
90
 
91
+ updateConfig(config: { model?: string | null; reasoningEffort?: string | null }): void {
92
+ if (config.model !== undefined) this.opts.model = config.model ?? undefined;
93
+ if (config.reasoningEffort !== undefined) this.opts.reasoningEffort = config.reasoningEffort ?? undefined;
94
+ }
95
+
90
96
  async *dispatch({ event, bodyForAgent, sessionKey, context }: DispatchOpts): AsyncIterable<RuntimeEvent> {
91
97
  await this.ensureStarted(context.log);
92
98
  // After ensureStarted resolves the subprocess could still die before we
@@ -334,12 +340,15 @@ export class CodexAppServerAdapter implements DispatchAdapter {
334
340
  // (git/ssh/npm/etc.). Earlier versions also rewrote HOME, which broke
335
341
  // tooling for users who set a custom CODEX_HOME pointing somewhere
336
342
  // other than their shell home.
337
- const env = {
343
+ const env: Record<string, string | undefined> = {
338
344
  ...process.env,
339
345
  CODEX_HOME: this.opts.codexHome,
340
346
  FORCE_COLOR: "0",
341
347
  NO_COLOR: "1",
342
348
  };
349
+ if (this.opts.contextFilePath) {
350
+ env.PRLL_CONTEXT_FILE = this.opts.contextFilePath;
351
+ }
343
352
  const args = ["app-server", "--listen", "stdio://"];
344
353
  (log ?? this.opts.log)?.info?.(`codex-agent: spawning ${this.opts.codexBin} ${args.join(" ")}`);
345
354
  const proc = spawn(this.opts.codexBin, args, {
@@ -430,15 +439,7 @@ export class CodexAppServerAdapter implements DispatchAdapter {
430
439
  const commonParams: Record<string, unknown> = {
431
440
  approvalPolicy: normalizeApprovalPolicy(this.opts.approvalPolicy),
432
441
  };
433
- // Only override sandbox when the user explicitly set PRLL_CODEX_SANDBOX.
434
- // Otherwise let codex read `sandbox_mode` from ~/.codex/config.toml — the
435
- // user's own configuration is authoritative for a self-hosted bridge,
436
- // and codex 0.120.0's workspace-write mode has a write_stdin bug that
437
- // our previous `workspace-write` default tripped for users whose config
438
- // had danger-full-access.
439
- if (this.opts.sandbox) {
440
- commonParams.sandbox = normalizeSandbox(this.opts.sandbox);
441
- }
442
+ commonParams.sandbox = normalizeSandbox(this.opts.sandbox);
442
443
  if (this.opts.model) commonParams.model = this.opts.model;
443
444
  if (this.opts.reasoningEffort) {
444
445
  // The app-server JSON-RPC surface uses camelCase for overrides —
package/src/index.ts CHANGED
@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import * as os from "node:os";
4
- import { ParallAgentGateway, parseShutdownDeadlineMs } from "@parall/agent-core";
4
+ import { ParallAgentGateway, createPlatformConfigManager, isPlatformManagedProfile, parseShutdownDeadlineMs } from "@parall/agent-core";
5
5
  import { ApiError, ParallClient, ParallWs } from "@parall/sdk";
6
6
  import {
7
7
  buildCodexRuntimeKey,
8
+ contextFilePathForSession,
8
9
  resolveCodexAgentConfig,
9
10
  resolveWsUrl,
10
11
  sessionStateFilePathForRuntime,
@@ -52,6 +53,7 @@ async function main() {
52
53
  description: me.agent_profile?.description ?? undefined,
53
54
  });
54
55
  const runtimeKey = config.runtimeKey || buildCodexRuntimeKey(agentUserId);
56
+ const mainContextFilePath = contextFilePathForSession(config.stateDir, runtimeKey);
55
57
  const sessionStateFilePath = sessionStateFilePathForRuntime(config.stateDir, runtimeKey);
56
58
  const sessionManager = new CodexSessionManager(
57
59
  runtimeKey,
@@ -65,16 +67,25 @@ async function main() {
65
67
  wsUrl: resolvedWsUrl,
66
68
  });
67
69
 
70
+ const configMgr = createPlatformConfigManager({ client, stateDir: config.stateDir, runtimeType: "codex", log });
71
+ const platformDefaults = await configMgr.fetch();
72
+ let platformManaged = isPlatformManagedProfile(me.agent_profile);
73
+ const resolvedModel = platformManaged ? (platformDefaults.model ?? config.model) : config.model;
74
+ const resolvedEffort = platformManaged ? (platformDefaults.thinkingEffort ?? config.reasoningEffort) : config.reasoningEffort;
75
+ if (platformDefaults.model) log.info(`platform config: model=${platformDefaults.model} (${platformManaged ? "applied" : "ignored, model_management=self"})`);
76
+ if (platformDefaults.thinkingEffort) log.info(`platform config: reasoning_effort=${platformDefaults.thinkingEffort} (${platformManaged ? "applied" : "ignored, model_management=self"})`);
77
+
68
78
  const adapter = new CodexAppServerAdapter({
69
79
  codexBin: config.codexBin,
70
80
  codexHome: config.codexHome,
71
81
  workspaceDir: config.workspaceDir,
72
- model: config.model,
73
- reasoningEffort: config.reasoningEffort,
82
+ model: resolvedModel,
83
+ reasoningEffort: resolvedEffort,
74
84
  sandbox: config.sandbox,
75
85
  approvalPolicy: config.approvalPolicy,
76
86
  sessionManager,
77
87
  log,
88
+ contextFilePath: mainContextFilePath,
78
89
  });
79
90
 
80
91
  const gateway = new ParallAgentGateway({
@@ -100,7 +111,26 @@ async function main() {
100
111
  dispatchAdapter: adapter,
101
112
  log,
102
113
  shutdownDeadlineMs: parseShutdownDeadlineMs(process.env.PRLL_SHUTDOWN_DEADLINE_MS),
114
+ contextFilePathForSession: (sessionKey) => contextFilePathForSession(config.stateDir, sessionKey),
103
115
  stepIdFilePathForSession: (sessionKey) => stepIdFilePathForSession(config.stateDir, sessionKey),
116
+ onConfigUpdate: async () => {
117
+ const refreshed = await getAgentMeWithLegacyFallback(client, config.orgId);
118
+ platformManaged = isPlatformManagedProfile(refreshed.agent_profile);
119
+ const updated = await configMgr.fetch();
120
+ adapter.updateConfig({
121
+ model: platformManaged ? (updated.model ?? config.model) : (config.model ?? null),
122
+ reasoningEffort: platformManaged ? (updated.thinkingEffort ?? config.reasoningEffort) : (config.reasoningEffort ?? null),
123
+ });
124
+ },
125
+ onSessionReady: async () => {
126
+ const refreshed = await getAgentMeWithLegacyFallback(client, config.orgId);
127
+ platformManaged = isPlatformManagedProfile(refreshed.agent_profile);
128
+ const updated = await configMgr.fetch();
129
+ adapter.updateConfig({
130
+ model: platformManaged ? (updated.model ?? config.model) : (config.model ?? null),
131
+ reasoningEffort: platformManaged ? (updated.thinkingEffort ?? config.reasoningEffort) : (config.reasoningEffort ?? null),
132
+ });
133
+ },
104
134
  });
105
135
 
106
136
  const abortController = new AbortController();
package/src/workspace.ts CHANGED
@@ -5,6 +5,8 @@ import {
5
5
  PRLL_BEHAVIOR,
6
6
  PRLL_REFERENCE_GUIDE,
7
7
  buildIdentity,
8
+ buildSkillReferences,
9
+ writeSkillFiles,
8
10
  } from "@parall/agent-core";
9
11
  import type { AgentIdentity } from "@parall/agent-core";
10
12
 
@@ -12,34 +14,25 @@ export function ensureCodexWorkspace(
12
14
  workspaceDir: string,
13
15
  log?: { warn: (msg: string) => void },
14
16
  agentIdentity?: AgentIdentity,
15
- ) {
16
- const AGENTS_MD = [
17
+ ): void {
18
+ const systemPrompt = [
17
19
  buildIdentity(agentIdentity),
18
20
  BRIDGE_WORKSPACE_INSTRUCTIONS,
19
21
  PRLL_BEHAVIOR,
20
22
  PRLL_REFERENCE_GUIDE,
23
+ buildSkillReferences(workspaceDir),
21
24
  ].join("\n\n");
25
+
22
26
  fs.mkdirSync(workspaceDir, { recursive: true });
23
27
 
24
- // AGENTS.md is bridge-managed: always overwrite so the workspace picks up
25
- // updated guardrails / command templates on upgrade. Operators must not
26
- // hand-edit this file — workspace customisations should go elsewhere in
27
- // the Codex home dir. Surface a warning when we replace divergent content
28
- // so an operator who did edit it locally sees a signal instead of silently
29
- // losing changes. Mirrors the Claude bridge behaviour.
30
- const agentsMdPath = path.join(workspaceDir, "AGENTS.md");
31
- if (log) {
32
- try {
33
- const existing = fs.readFileSync(agentsMdPath, "utf8");
34
- if (existing !== AGENTS_MD) {
35
- log.warn(
36
- `codex-agent: overwriting divergent ${agentsMdPath} with bridge-managed template ` +
37
- `(local edits to AGENTS.md are not preserved — customize other files in the workspace instead)`,
38
- );
39
- }
40
- } catch {
41
- // file missing or unreadable — first-boot case, no warning needed
42
- }
43
- }
44
- fs.writeFileSync(agentsMdPath, AGENTS_MD, "utf8");
28
+ const parallDir = path.join(workspaceDir, ".parall");
29
+ fs.mkdirSync(parallDir, { recursive: true });
30
+ fs.writeFileSync(path.join(parallDir, "system-prompt.md"), systemPrompt, "utf8");
31
+
32
+ writeSkillFiles(path.join(parallDir, "skills"));
33
+
34
+ const codexConfigDir = path.join(workspaceDir, ".codex");
35
+ fs.mkdirSync(codexConfigDir, { recursive: true });
36
+ const toml = `developer_instructions = """\n${systemPrompt.replace(/\\/g, "\\\\").replace(/"""/g, '\\"""')}\n"""\n`;
37
+ fs.writeFileSync(path.join(codexConfigDir, "config.toml"), toml, "utf8");
45
38
  }