@zvada/agent-server 0.3.4 → 0.3.5

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/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.5
4
+
5
+ - Switching Codex ChatGPT accounts or API keys now recreates the native process
6
+ and resumes the same conversation. Native Codex keeps model WebSockets
7
+ authenticated as the account that opened them, so a login update alone could
8
+ keep using the previous account. Renewing a token for the same account remains
9
+ warm; no credential fields are added to the public protocol.
10
+
3
11
  ## 0.3.4
4
12
 
5
13
  - Update the managed Codex CLI and development SDK to 0.153.4. This clears
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zvada/agent-server",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
4
4
  "description": "Harness-agnostic agent execution engine: run Claude Code, Codex (SDK/CLI + app-server), and any ACP agent behind one interface with a normalized event stream, multi-turn sessions, and resume. Root export is the wire contract; /core, /server, /client are the seats.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -39,8 +39,8 @@ export interface AppServerSession {
39
39
  sandbox: string;
40
40
  systemPromptAppend?: string;
41
41
  envFingerprint: string;
42
- /** Native CLI auth and externally managed tokens cannot share a warm process. */
43
- authMode?: CodexAppServerAuth["type"];
42
+ /** Native model connections retain the identity used at their first handshake. */
43
+ authFingerprint?: string;
44
44
  /** Credentials and permission broker belong only to the current execute. */
45
45
  turn?: AppServerTurn;
46
46
  idleTimer?: ReturnType<typeof setTimeout>;
@@ -169,6 +169,13 @@ export interface CodexChatGptAuth {
169
169
 
170
170
  export type CodexAppServerAuth = { type: "apiKey"; apiKey: string } | CodexChatGptAuth;
171
171
 
172
+ function authFingerprint(auth: CodexAppServerAuth | undefined): string | undefined {
173
+ if (!auth) return undefined;
174
+ return configFingerprint(
175
+ auth.type === "apiKey" ? auth : { type: auth.type, accountId: auth.tokens.chatgptAccountId },
176
+ );
177
+ }
178
+
172
179
  export interface CodexAppServerAgentOptions {
173
180
  createClient?: (options: CodexAppServerClientOptions) => CodexAppServerClient;
174
181
  idleTimeoutMs?: number;
@@ -186,7 +193,8 @@ export interface CodexAppServerAgentOptions {
186
193
  * Resolve the selected API key or ChatGPT account before EVERY turn, including warm
187
194
  * sessions. Operator-only: neither callbacks nor tokens belong in RunConfig.
188
195
  * Return undefined to select native CLI auth; throw when the selected external
189
- * account is unavailable. Switching back to native auth recreates the process.
196
+ * account is unavailable. Changing accounts, API keys, or auth modes recreates
197
+ * the process and resumes the same conversation.
190
198
  */
191
199
  resolveAuth?: (sessionId: string) => Promise<CodexAppServerAuth | undefined>;
192
200
  }
@@ -228,12 +236,13 @@ export class CodexAppServerAgent extends BaseAgent {
228
236
  const approvalPolicy = approvalPolicyFor(options);
229
237
  const sandbox = mapSandbox(options.permissionMode);
230
238
  const existing = this.sessions.get(options.sessionId);
231
- // Reuse a warm subprocess only when its (immutable) approval posture
232
- // matches; otherwise recreate it, resuming the same thread so context
233
- // survives. `resumeThreadId` carries that over to the spawn below.
239
+ const identity = authFingerprint(turn.auth);
240
+ // Codex caches an authenticated model WebSocket across turns. Login alone
241
+ // cannot switch its identity: restart on account/key changes and resume
242
+ // the same thread. Same-account token renewal can keep the process warm.
234
243
  let resumeThreadId = options.resumeSessionId;
235
244
  if (existing && !existing.client.closed) {
236
- if (appServerSessionCompatible(existing, options) && existing.authMode === turn.auth?.type) {
245
+ if (appServerSessionCompatible(existing, options) && existing.authFingerprint === identity) {
237
246
  this.sessions.clearIdle(existing);
238
247
  existing.turn = turn;
239
248
  const onAbort = () => existing.client.close();
@@ -284,7 +293,7 @@ export class CodexAppServerAgent extends BaseAgent {
284
293
  sandbox,
285
294
  systemPromptAppend: options.systemPromptAppend,
286
295
  envFingerprint: configFingerprint(options.env),
287
- authMode: turn.auth?.type,
296
+ authFingerprint: identity,
288
297
  turn,
289
298
  };
290
299
  this.bindRequests(session);