@posthog/agent 2.3.172 → 2.3.173

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@posthog/agent",
3
- "version": "2.3.172",
3
+ "version": "2.3.173",
4
4
  "repository": "https://github.com/PostHog/code",
5
5
  "description": "TypeScript agent framework wrapping Claude Agent SDK with Git-based task execution for PostHog",
6
6
  "exports": {
@@ -64,4 +64,7 @@ export const POSTHOG_NOTIFICATIONS = {
64
64
 
65
65
  /** Marks a boundary for log compaction */
66
66
  COMPACT_BOUNDARY: "_posthog/compact_boundary",
67
+
68
+ /** Token usage update for a session turn */
69
+ USAGE_UPDATE: "_posthog/usage_update",
67
70
  } as const;
@@ -44,6 +44,7 @@ import {
44
44
  } from "@anthropic-ai/claude-agent-sdk";
45
45
  import { v7 as uuidv7 } from "uuid";
46
46
  import packageJson from "../../../package.json" with { type: "json" };
47
+ import { POSTHOG_NOTIFICATIONS } from "../../acp-extensions";
47
48
  import { unreachable, withTimeout } from "../../utils/common";
48
49
  import { Logger } from "../../utils/logger";
49
50
  import { Pushable } from "../../utils/streams";
@@ -442,16 +443,19 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
442
443
  });
443
444
  }
444
445
 
445
- await this.client.extNotification("_posthog/usage_update", {
446
- sessionId: params.sessionId,
447
- used: {
448
- inputTokens: message.usage.input_tokens,
449
- outputTokens: message.usage.output_tokens,
450
- cachedReadTokens: message.usage.cache_read_input_tokens,
451
- cachedWriteTokens: message.usage.cache_creation_input_tokens,
446
+ await this.client.extNotification(
447
+ POSTHOG_NOTIFICATIONS.USAGE_UPDATE,
448
+ {
449
+ sessionId: params.sessionId,
450
+ used: {
451
+ inputTokens: message.usage.input_tokens,
452
+ outputTokens: message.usage.output_tokens,
453
+ cachedReadTokens: message.usage.cache_read_input_tokens,
454
+ cachedWriteTokens: message.usage.cache_creation_input_tokens,
455
+ },
456
+ cost: message.total_cost_usd,
452
457
  },
453
- cost: message.total_cost_usd,
454
- });
458
+ );
455
459
 
456
460
  const usage: Usage = {
457
461
  inputTokens: this.session.accumulatedUsage.inputTokens,
@@ -12,7 +12,6 @@
12
12
  import {
13
13
  type AgentSideConnection,
14
14
  type AuthenticateRequest,
15
- type CancelNotification,
16
15
  ClientSideConnection,
17
16
  type ForkSessionRequest,
18
17
  type ForkSessionResponse,
@@ -37,9 +36,8 @@ import {
37
36
  import packageJson from "../../../package.json" with { type: "json" };
38
37
  import { POSTHOG_NOTIFICATIONS } from "../../acp-extensions";
39
38
  import {
40
- CODEX_NATIVE_MODES,
41
- type CodexNativeMode,
42
- type PermissionMode,
39
+ CODE_EXECUTION_MODES,
40
+ type CodeExecutionMode,
43
41
  } from "../../execution-mode";
44
42
  import type { ProcessSpawnedCallback } from "../../types";
45
43
  import { Logger } from "../../utils/logger";
@@ -85,19 +83,26 @@ type CodexSession = BaseSession & {
85
83
  settingsManager: CodexSettingsManager;
86
84
  };
87
85
 
88
- function toPermissionMode(mode?: string): PermissionMode {
89
- if (mode && (CODEX_NATIVE_MODES as readonly string[]).includes(mode)) {
90
- return mode as CodexNativeMode;
86
+ function toCodeExecutionMode(mode?: string): CodeExecutionMode {
87
+ if (mode && (CODE_EXECUTION_MODES as readonly string[]).includes(mode)) {
88
+ return mode as CodeExecutionMode;
91
89
  }
92
- return "auto";
90
+ return "default";
93
91
  }
94
92
 
93
+ const CODEX_NATIVE_MODE: Record<CodeExecutionMode, string> = {
94
+ default: "default",
95
+ acceptEdits: "default",
96
+ plan: "plan",
97
+ bypassPermissions: "default",
98
+ };
99
+
95
100
  export class CodexAcpAgent extends BaseAcpAgent {
96
101
  readonly adapterName = "codex";
97
102
  declare session: CodexSession;
98
103
  private codexProcess: CodexProcess;
99
- private codexConnection!: ClientSideConnection;
100
- private sessionState!: CodexSessionState;
104
+ private codexConnection: ClientSideConnection;
105
+ private sessionState: CodexSessionState;
101
106
 
102
107
  constructor(client: AgentSideConnection, options: CodexAcpAgentOptions) {
103
108
  super(client);
@@ -126,29 +131,14 @@ export class CodexAcpAgent extends BaseAcpAgent {
126
131
  cancelled: false,
127
132
  };
128
133
 
134
+ this.sessionState = createSessionState("", cwd);
135
+
129
136
  // Create the ClientSideConnection to codex-acp.
130
137
  // The Client handler delegates all requests from codex-acp to the upstream
131
138
  // PostHog Code client via our AgentSideConnection.
132
139
  this.codexConnection = new ClientSideConnection(
133
140
  (_agent) =>
134
- createCodexClient(
135
- this.client,
136
- this.logger,
137
- this.sessionState ?? {
138
- sessionId: "",
139
- cwd: "",
140
- modeId: "auto",
141
- configOptions: [],
142
- accumulatedUsage: {
143
- inputTokens: 0,
144
- outputTokens: 0,
145
- cachedReadTokens: 0,
146
- cachedWriteTokens: 0,
147
- },
148
- permissionMode: "auto",
149
- cancelled: false,
150
- },
151
- ),
141
+ createCodexClient(this.client, this.logger, this.sessionState),
152
142
  codexStream,
153
143
  );
154
144
  }
@@ -195,7 +185,7 @@ export class CodexAcpAgent extends BaseAcpAgent {
195
185
  taskId: meta?.taskId ?? meta?.persistence?.taskId,
196
186
  modeId: response.modes?.currentModeId ?? "default",
197
187
  modelId: response.models?.currentModelId,
198
- permissionMode: toPermissionMode(meta?.permissionMode),
188
+ permissionMode: toCodeExecutionMode(meta?.permissionMode),
199
189
  });
200
190
  this.sessionId = response.sessionId;
201
191
  this.sessionState.configOptions = response.configOptions ?? [];
@@ -219,9 +209,11 @@ export class CodexAcpAgent extends BaseAcpAgent {
219
209
 
220
210
  async loadSession(params: LoadSessionRequest): Promise<LoadSessionResponse> {
221
211
  const response = await this.codexConnection.loadSession(params);
212
+ const meta = params._meta as NewSessionMeta | undefined;
222
213
 
223
- // Update session state
224
- this.sessionState = createSessionState(params.sessionId, params.cwd);
214
+ this.sessionState = createSessionState(params.sessionId, params.cwd, {
215
+ permissionMode: toCodeExecutionMode(meta?.permissionMode),
216
+ });
225
217
  this.sessionId = params.sessionId;
226
218
  this.sessionState.configOptions = response.configOptions ?? [];
227
219
 
@@ -238,11 +230,15 @@ export class CodexAcpAgent extends BaseAcpAgent {
238
230
  mcpServers: params.mcpServers ?? [],
239
231
  });
240
232
 
241
- this.sessionState = createSessionState(params.sessionId, params.cwd);
233
+ const meta = params._meta as NewSessionMeta | undefined;
234
+ this.sessionState = createSessionState(params.sessionId, params.cwd, {
235
+ taskRunId: meta?.taskRunId,
236
+ taskId: meta?.taskId ?? meta?.persistence?.taskId,
237
+ permissionMode: toCodeExecutionMode(meta?.permissionMode),
238
+ });
242
239
  this.sessionId = params.sessionId;
243
240
  this.sessionState.configOptions = loadResponse.configOptions ?? [];
244
241
 
245
- const meta = params._meta as NewSessionMeta | undefined;
246
242
  if (meta?.taskRunId) {
247
243
  await this.client.extNotification(POSTHOG_NOTIFICATIONS.SDK_SESSION, {
248
244
  taskRunId: meta.taskRunId,
@@ -268,7 +264,12 @@ export class CodexAcpAgent extends BaseAcpAgent {
268
264
  _meta: params._meta,
269
265
  });
270
266
 
271
- this.sessionState = createSessionState(newResponse.sessionId, params.cwd);
267
+ const meta = params._meta as NewSessionMeta | undefined;
268
+ this.sessionState = createSessionState(newResponse.sessionId, params.cwd, {
269
+ taskRunId: meta?.taskRunId,
270
+ taskId: meta?.taskId ?? meta?.persistence?.taskId,
271
+ permissionMode: toCodeExecutionMode(meta?.permissionMode),
272
+ });
272
273
  this.sessionId = newResponse.sessionId;
273
274
  this.sessionState.configOptions = newResponse.configOptions ?? [];
274
275
 
@@ -284,31 +285,21 @@ export class CodexAcpAgent extends BaseAcpAgent {
284
285
  async unstable_listSessions(
285
286
  params: ListSessionsRequest,
286
287
  ): Promise<ListSessionsResponse> {
287
- return this.codexConnection.listSessions(params);
288
+ return this.listSessions(params);
288
289
  }
289
290
 
290
291
  async prompt(params: PromptRequest): Promise<PromptResponse> {
291
- if (this.sessionState) {
292
- this.sessionState.cancelled = false;
293
- this.sessionState.interruptReason = undefined;
294
- resetUsage(this.sessionState);
295
- }
292
+ this.session.cancelled = false;
293
+ this.session.interruptReason = undefined;
294
+ resetUsage(this.sessionState);
296
295
 
297
296
  const response = await this.codexConnection.prompt(params);
298
297
 
299
- if (this.sessionState && response.usage) {
300
- // Accumulate token usage from the prompt response
301
- this.sessionState.accumulatedUsage.inputTokens +=
302
- response.usage.inputTokens ?? 0;
303
- this.sessionState.accumulatedUsage.outputTokens +=
304
- response.usage.outputTokens ?? 0;
305
- this.sessionState.accumulatedUsage.cachedReadTokens +=
306
- response.usage.cachedReadTokens ?? 0;
307
- this.sessionState.accumulatedUsage.cachedWriteTokens +=
308
- response.usage.cachedWriteTokens ?? 0;
309
- }
298
+ // Usage is already accumulated via sessionUpdate notifications in
299
+ // codex-client.ts. Do NOT also add response.usage here or tokens
300
+ // get double-counted.
310
301
 
311
- if (this.sessionState?.taskRunId) {
302
+ if (this.sessionState.taskRunId) {
312
303
  const { accumulatedUsage } = this.sessionState;
313
304
 
314
305
  await this.client.extNotification(POSTHOG_NOTIFICATIONS.TURN_COMPLETE, {
@@ -328,7 +319,7 @@ export class CodexAcpAgent extends BaseAcpAgent {
328
319
  });
329
320
 
330
321
  if (response.usage) {
331
- await this.client.extNotification("_posthog/usage_update", {
322
+ await this.client.extNotification(POSTHOG_NOTIFICATIONS.USAGE_UPDATE, {
332
323
  sessionId: params.sessionId,
333
324
  used: {
334
325
  inputTokens: response.usage.inputTokens ?? 0,
@@ -345,39 +336,24 @@ export class CodexAcpAgent extends BaseAcpAgent {
345
336
  }
346
337
 
347
338
  protected async interrupt(): Promise<void> {
348
- if (this.sessionState) {
349
- this.sessionState.cancelled = true;
350
- }
351
339
  await this.codexConnection.cancel({
352
340
  sessionId: this.sessionId,
353
341
  });
354
342
  }
355
343
 
356
- async cancel(params: CancelNotification): Promise<void> {
357
- if (this.sessionState) {
358
- this.sessionState.cancelled = true;
359
- const meta = params._meta as { interruptReason?: string } | undefined;
360
- if (meta?.interruptReason) {
361
- this.sessionState.interruptReason = meta.interruptReason;
362
- }
363
- }
364
- await this.codexConnection.cancel(params);
365
- }
366
-
367
344
  async setSessionMode(
368
345
  params: SetSessionModeRequest,
369
346
  ): Promise<SetSessionModeResponse> {
370
- const permissionMode = toPermissionMode(params.modeId);
347
+ const requestedMode = toCodeExecutionMode(params.modeId);
348
+ const nativeMode = CODEX_NATIVE_MODE[requestedMode];
371
349
 
372
350
  const response = await this.codexConnection.setSessionMode({
373
351
  ...params,
374
- modeId: permissionMode,
352
+ modeId: nativeMode,
375
353
  });
376
354
 
377
- if (this.sessionState) {
378
- this.sessionState.modeId = permissionMode;
379
- this.sessionState.permissionMode = permissionMode;
380
- }
355
+ this.sessionState.modeId = nativeMode;
356
+ this.sessionState.permissionMode = requestedMode;
381
357
  return response ?? {};
382
358
  }
383
359
 
@@ -385,7 +361,7 @@ export class CodexAcpAgent extends BaseAcpAgent {
385
361
  params: SetSessionConfigOptionRequest,
386
362
  ): Promise<SetSessionConfigOptionResponse> {
387
363
  const response = await this.codexConnection.setSessionConfigOption(params);
388
- if (this.sessionState && response.configOptions) {
364
+ if (response.configOptions) {
389
365
  this.sessionState.configOptions = response.configOptions;
390
366
  }
391
367
  return response;
@@ -397,6 +373,7 @@ export class CodexAcpAgent extends BaseAcpAgent {
397
373
 
398
374
  async closeSession(): Promise<void> {
399
375
  this.logger.info("Closing Codex session", { sessionId: this.sessionId });
376
+ this.session.abortController.abort();
400
377
  this.session.settingsManager.dispose();
401
378
  try {
402
379
  this.codexProcess.kill();
@@ -29,7 +29,7 @@ import type {
29
29
  WriteTextFileRequest,
30
30
  WriteTextFileResponse,
31
31
  } from "@agentclientprotocol/sdk";
32
- import type { PermissionMode } from "../../execution-mode";
32
+ import type { CodeExecutionMode } from "../../execution-mode";
33
33
  import type { Logger } from "../../utils/logger";
34
34
  import type { CodexSessionState } from "./session-state";
35
35
 
@@ -38,10 +38,11 @@ export interface CodexClientCallbacks {
38
38
  onUsageUpdate?: (update: Record<string, unknown>) => void;
39
39
  }
40
40
 
41
- const AUTO_APPROVED_KINDS: Partial<Record<PermissionMode, Set<ToolKind>>> = {
42
- auto: new Set(["read", "search", "fetch", "think"]),
43
- "read-only": new Set(["read", "search", "fetch", "think"]),
44
- "full-access": new Set([
41
+ const AUTO_APPROVED_KINDS: Record<CodeExecutionMode, Set<ToolKind>> = {
42
+ default: new Set(["read", "search", "fetch", "think"]),
43
+ acceptEdits: new Set(["read", "edit", "search", "fetch", "think"]),
44
+ plan: new Set(["read", "search", "fetch", "think"]),
45
+ bypassPermissions: new Set([
45
46
  "read",
46
47
  "edit",
47
48
  "delete",
@@ -56,10 +57,10 @@ const AUTO_APPROVED_KINDS: Partial<Record<PermissionMode, Set<ToolKind>>> = {
56
57
  };
57
58
 
58
59
  function shouldAutoApprove(
59
- mode: PermissionMode,
60
+ mode: CodeExecutionMode,
60
61
  kind: ToolKind | null | undefined,
61
62
  ): boolean {
62
- if (mode === "full-access") return true;
63
+ if (mode === "bypassPermissions") return true;
63
64
  if (!kind) return false;
64
65
  return AUTO_APPROVED_KINDS[mode]?.has(kind) ?? false;
65
66
  }
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import type { SessionConfigOption } from "@agentclientprotocol/sdk";
7
- import type { PermissionMode } from "../../execution-mode";
7
+ import type { CodeExecutionMode } from "../../execution-mode";
8
8
 
9
9
  export interface CodexUsage {
10
10
  inputTokens: number;
@@ -22,9 +22,7 @@ export interface CodexSessionState {
22
22
  accumulatedUsage: CodexUsage;
23
23
  contextSize?: number;
24
24
  contextUsed?: number;
25
- permissionMode: PermissionMode;
26
- cancelled: boolean;
27
- interruptReason?: string;
25
+ permissionMode: CodeExecutionMode;
28
26
  taskRunId?: string;
29
27
  taskId?: string;
30
28
  }
@@ -37,13 +35,13 @@ export function createSessionState(
37
35
  taskId?: string;
38
36
  modeId?: string;
39
37
  modelId?: string;
40
- permissionMode?: PermissionMode;
38
+ permissionMode?: CodeExecutionMode;
41
39
  },
42
40
  ): CodexSessionState {
43
41
  return {
44
42
  sessionId,
45
43
  cwd,
46
- modeId: opts?.modeId ?? "auto",
44
+ modeId: opts?.modeId ?? "default",
47
45
  modelId: opts?.modelId,
48
46
  configOptions: [],
49
47
  accumulatedUsage: {
@@ -52,8 +50,7 @@ export function createSessionState(
52
50
  cachedReadTokens: 0,
53
51
  cachedWriteTokens: 0,
54
52
  },
55
- permissionMode: opts?.permissionMode ?? "auto",
56
- cancelled: false,
53
+ permissionMode: opts?.permissionMode ?? "default",
57
54
  taskRunId: opts?.taskRunId,
58
55
  taskId: opts?.taskId,
59
56
  };
@@ -46,6 +46,8 @@ function buildConfigArgs(options: CodexProcessOptions): string[] {
46
46
  if (options.instructions) {
47
47
  const escaped = options.instructions
48
48
  .replace(/\\/g, "\\\\")
49
+ .replace(/\n/g, "\\n")
50
+ .replace(/\r/g, "\\r")
49
51
  .replace(/"/g, '\\"');
50
52
  args.push("-c", `instructions="${escaped}"`);
51
53
  }