@posthog/agent 2.3.316 → 2.3.341

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.
Files changed (58) hide show
  1. package/dist/adapters/claude/conversion/tool-use-to-acp.d.ts +9 -0
  2. package/dist/adapters/claude/conversion/tool-use-to-acp.js +15 -1
  3. package/dist/adapters/claude/conversion/tool-use-to-acp.js.map +1 -1
  4. package/dist/adapters/claude/permissions/permission-options.js +18 -11
  5. package/dist/adapters/claude/permissions/permission-options.js.map +1 -1
  6. package/dist/adapters/claude/session/jsonl-hydration.js.map +1 -1
  7. package/dist/adapters/claude/session/models.d.ts +2 -2
  8. package/dist/adapters/claude/session/models.js +12 -6
  9. package/dist/adapters/claude/session/models.js.map +1 -1
  10. package/dist/adapters/claude/tools.js +15 -13
  11. package/dist/adapters/claude/tools.js.map +1 -1
  12. package/dist/adapters/reasoning-effort.d.ts +1 -1
  13. package/dist/adapters/reasoning-effort.js +11 -5
  14. package/dist/adapters/reasoning-effort.js.map +1 -1
  15. package/dist/agent.d.ts +2 -0
  16. package/dist/agent.js +6946 -587
  17. package/dist/agent.js.map +1 -1
  18. package/dist/execution-mode.d.ts +1 -1
  19. package/dist/execution-mode.js +14 -12
  20. package/dist/execution-mode.js.map +1 -1
  21. package/dist/posthog-api.js +4 -3
  22. package/dist/posthog-api.js.map +1 -1
  23. package/dist/server/agent-server.d.ts +1 -1
  24. package/dist/server/agent-server.js +8410 -2071
  25. package/dist/server/agent-server.js.map +1 -1
  26. package/dist/server/bin.cjs +11296 -4956
  27. package/dist/server/bin.cjs.map +1 -1
  28. package/dist/types.d.ts +8 -0
  29. package/package.json +7 -6
  30. package/src/adapters/acp-connection.ts +14 -1
  31. package/src/adapters/claude/UPSTREAM.md +24 -4
  32. package/src/adapters/claude/claude-agent.ts +161 -14
  33. package/src/adapters/claude/conversion/sdk-to-acp.ts +14 -2
  34. package/src/adapters/claude/conversion/tool-use-to-acp.ts +18 -1
  35. package/src/adapters/claude/hooks.test.ts +189 -0
  36. package/src/adapters/claude/hooks.ts +93 -3
  37. package/src/adapters/claude/permissions/permission-handlers.ts +2 -1
  38. package/src/adapters/claude/permissions/permission-options.ts +5 -0
  39. package/src/adapters/claude/session/models.ts +11 -5
  40. package/src/adapters/claude/session/options.ts +19 -3
  41. package/src/adapters/claude/session/settings.ts +17 -9
  42. package/src/adapters/claude/tools.ts +1 -1
  43. package/src/adapters/claude/types.ts +8 -1
  44. package/src/adapters/codex/codex-agent.ts +15 -2
  45. package/src/adapters/codex/codex-client.test.ts +112 -0
  46. package/src/adapters/codex/codex-client.ts +14 -1
  47. package/src/adapters/reasoning-effort.ts +6 -1
  48. package/src/agent.ts +6 -0
  49. package/src/enrichment/file-enricher.test.ts +163 -0
  50. package/src/enrichment/file-enricher.ts +82 -0
  51. package/src/execution-mode.test.ts +1 -0
  52. package/src/execution-mode.ts +13 -11
  53. package/src/server/agent-server.ts +18 -1
  54. package/src/server/bin.ts +1 -1
  55. package/src/server/schemas.test.ts +40 -0
  56. package/src/server/schemas.ts +8 -1
  57. package/src/server/types.ts +1 -1
  58. package/src/types.ts +6 -0
@@ -0,0 +1,112 @@
1
+ import type {
2
+ AgentSideConnection,
3
+ ReadTextFileRequest,
4
+ ReadTextFileResponse,
5
+ } from "@agentclientprotocol/sdk";
6
+ import { describe, expect, test, vi } from "vitest";
7
+ import type { FileEnrichmentDeps } from "../../enrichment/file-enricher";
8
+ import { Logger } from "../../utils/logger";
9
+
10
+ const enrichFileMock = vi.hoisted(() => vi.fn());
11
+ vi.mock("../../enrichment/file-enricher", () => ({
12
+ enrichFileForAgent: enrichFileMock,
13
+ }));
14
+
15
+ import { createCodexClient } from "./codex-client";
16
+ import { createSessionState } from "./session-state";
17
+
18
+ function makeUpstream(response: ReadTextFileResponse): AgentSideConnection & {
19
+ readTextFile: ReturnType<typeof vi.fn>;
20
+ } {
21
+ const mock = {
22
+ readTextFile: vi.fn(async (_: ReadTextFileRequest) => response),
23
+ writeTextFile: vi.fn(),
24
+ requestPermission: vi.fn(),
25
+ sessionUpdate: vi.fn(),
26
+ createTerminal: vi.fn(),
27
+ terminalOutput: vi.fn(),
28
+ releaseTerminal: vi.fn(),
29
+ waitForTerminalExit: vi.fn(),
30
+ killTerminal: vi.fn(),
31
+ extMethod: vi.fn(),
32
+ extNotification: vi.fn(),
33
+ };
34
+ return mock as unknown as AgentSideConnection & {
35
+ readTextFile: ReturnType<typeof vi.fn>;
36
+ };
37
+ }
38
+
39
+ describe("createCodexClient readTextFile", () => {
40
+ const logger = new Logger({ debug: false, prefix: "[test]" });
41
+ const sessionState = createSessionState("", "/tmp");
42
+
43
+ test("returns upstream response unchanged when enrichmentDeps is absent", async () => {
44
+ enrichFileMock.mockReset();
45
+ const upstream = makeUpstream({ content: "const x = 1;" });
46
+ const client = createCodexClient(upstream, logger, sessionState);
47
+
48
+ const result = await client.readTextFile?.({
49
+ sessionId: "s",
50
+ path: "/tmp/a.ts",
51
+ });
52
+ expect(result?.content).toBe("const x = 1;");
53
+ expect(enrichFileMock).not.toHaveBeenCalled();
54
+ });
55
+
56
+ test("returns enriched content when helper returns a string", async () => {
57
+ enrichFileMock.mockReset();
58
+ enrichFileMock.mockResolvedValueOnce("const x = 1; // [PostHog] Flag ...");
59
+
60
+ const upstream = makeUpstream({ content: "const x = 1;" });
61
+ const deps = {} as FileEnrichmentDeps;
62
+ const client = createCodexClient(upstream, logger, sessionState, {
63
+ enrichmentDeps: deps,
64
+ });
65
+
66
+ const result = await client.readTextFile?.({
67
+ sessionId: "s",
68
+ path: "/tmp/a.ts",
69
+ });
70
+ expect(result?.content).toBe("const x = 1; // [PostHog] Flag ...");
71
+ expect(enrichFileMock).toHaveBeenCalledWith(
72
+ deps,
73
+ "/tmp/a.ts",
74
+ "const x = 1;",
75
+ );
76
+ });
77
+
78
+ test("falls back to upstream response when helper returns null", async () => {
79
+ enrichFileMock.mockReset();
80
+ enrichFileMock.mockResolvedValueOnce(null);
81
+
82
+ const upstream = makeUpstream({ content: "no posthog here" });
83
+ const client = createCodexClient(upstream, logger, sessionState, {
84
+ enrichmentDeps: {} as FileEnrichmentDeps,
85
+ });
86
+
87
+ const result = await client.readTextFile?.({
88
+ sessionId: "s",
89
+ path: "/tmp/a.ts",
90
+ });
91
+ expect(result?.content).toBe("no posthog here");
92
+ });
93
+
94
+ test("calls upstream.readTextFile with original params (UI sees original)", async () => {
95
+ enrichFileMock.mockReset();
96
+ enrichFileMock.mockResolvedValueOnce("enriched");
97
+
98
+ const upstream = makeUpstream({ content: "original" });
99
+ const client = createCodexClient(upstream, logger, sessionState, {
100
+ enrichmentDeps: {} as FileEnrichmentDeps,
101
+ });
102
+
103
+ const params = {
104
+ sessionId: "s",
105
+ path: "/tmp/a.ts",
106
+ line: 10,
107
+ limit: 5,
108
+ };
109
+ await client.readTextFile?.(params);
110
+ expect(upstream.readTextFile).toHaveBeenCalledWith(params);
111
+ });
112
+ });
@@ -29,6 +29,10 @@ import type {
29
29
  WriteTextFileRequest,
30
30
  WriteTextFileResponse,
31
31
  } from "@agentclientprotocol/sdk";
32
+ import {
33
+ enrichFileForAgent,
34
+ type FileEnrichmentDeps,
35
+ } from "../../enrichment/file-enricher";
32
36
  import type { PermissionMode } from "../../execution-mode";
33
37
  import type { Logger } from "../../utils/logger";
34
38
  import type { CodexSessionState } from "./session-state";
@@ -36,6 +40,8 @@ import type { CodexSessionState } from "./session-state";
36
40
  export interface CodexClientCallbacks {
37
41
  /** Called when a usage_update session notification is received */
38
42
  onUsageUpdate?: (update: Record<string, unknown>) => void;
43
+ /** When set, Read responses are annotated with PostHog enrichment before reaching codex-acp. */
44
+ enrichmentDeps?: FileEnrichmentDeps;
39
45
  }
40
46
 
41
47
  const AUTO_APPROVED_KINDS: Record<PermissionMode, Set<ToolKind>> = {
@@ -152,7 +158,14 @@ export function createCodexClient(
152
158
  async readTextFile(
153
159
  params: ReadTextFileRequest,
154
160
  ): Promise<ReadTextFileResponse> {
155
- return upstreamClient.readTextFile(params);
161
+ const response = await upstreamClient.readTextFile(params);
162
+ if (!callbacks?.enrichmentDeps) return response;
163
+ const enriched = await enrichFileForAgent(
164
+ callbacks.enrichmentDeps,
165
+ params.path,
166
+ response.content,
167
+ );
168
+ return enriched ? { ...response, content: enriched } : response;
156
169
  },
157
170
 
158
171
  async writeTextFile(
@@ -3,7 +3,12 @@ import { getReasoningEffortOptions as getCodexReasoningEffortOptions } from "./c
3
3
 
4
4
  export type RuntimeAdapter = "claude" | "codex";
5
5
 
6
- export type SupportedReasoningEffort = "low" | "medium" | "high" | "max";
6
+ export type SupportedReasoningEffort =
7
+ | "low"
8
+ | "medium"
9
+ | "high"
10
+ | "xhigh"
11
+ | "max";
7
12
 
8
13
  export interface ReasoningEffortOption {
9
14
  value: SupportedReasoningEffort;
package/src/agent.ts CHANGED
@@ -19,6 +19,8 @@ export class Agent {
19
19
  private acpConnection?: InProcessAcpConnection;
20
20
  private taskRunId?: string;
21
21
  private sessionLogWriter?: SessionLogWriter;
22
+ private posthogApiConfig?: AgentConfig["posthog"];
23
+ private enricherEnabled: boolean;
22
24
 
23
25
  constructor(config: AgentConfig) {
24
26
  this.logger = new Logger({
@@ -29,7 +31,9 @@ export class Agent {
29
31
 
30
32
  if (config.posthog) {
31
33
  this.posthogAPI = new PostHogAPIClient(config.posthog);
34
+ this.posthogApiConfig = config.posthog;
32
35
  }
36
+ this.enricherEnabled = config.enricher?.enabled !== false;
33
37
 
34
38
  if (config.posthog && !config.skipLogPersistence) {
35
39
  this.sessionLogWriter = new SessionLogWriter({
@@ -121,6 +125,8 @@ export class Agent {
121
125
  processCallbacks: options.processCallbacks,
122
126
  onStructuredOutput: options.onStructuredOutput,
123
127
  allowedModelIds,
128
+ posthogApiConfig: this.posthogApiConfig,
129
+ enricherEnabled: this.enricherEnabled,
124
130
  codexOptions:
125
131
  options.adapter === "codex" && gatewayConfig
126
132
  ? {
@@ -0,0 +1,163 @@
1
+ import { describe, expect, test, vi } from "vitest";
2
+ import { enrichFileForAgent, type FileEnrichmentDeps } from "./file-enricher";
3
+
4
+ function makeDeps(overrides: {
5
+ toInlineCommentsReturn?: string;
6
+ callsCount?: number;
7
+ initCallsCount?: number;
8
+ parseRejects?: Error;
9
+ isSupported?: boolean;
10
+ getApiKey?: () => string | Promise<string>;
11
+ }): {
12
+ deps: FileEnrichmentDeps;
13
+ parseSpy: ReturnType<typeof vi.fn>;
14
+ enrichFromApiSpy: ReturnType<typeof vi.fn>;
15
+ getApiKeySpy: ReturnType<typeof vi.fn>;
16
+ } {
17
+ const enrichFromApiSpy = vi.fn(async () => ({
18
+ toInlineComments: () =>
19
+ overrides.toInlineCommentsReturn ?? "enriched content",
20
+ }));
21
+
22
+ const parseSpy = vi.fn(async () => {
23
+ if (overrides.parseRejects) throw overrides.parseRejects;
24
+ return {
25
+ calls: Array.from({ length: overrides.callsCount ?? 1 }),
26
+ initCalls: Array.from({ length: overrides.initCallsCount ?? 0 }),
27
+ enrichFromApi: enrichFromApiSpy,
28
+ };
29
+ });
30
+
31
+ const getApiKeySpy = vi.fn(overrides.getApiKey ?? (() => "phx_test"));
32
+
33
+ const deps: FileEnrichmentDeps = {
34
+ enricher: {
35
+ isSupported: vi.fn(() => overrides.isSupported ?? true),
36
+ parse: parseSpy,
37
+ } as unknown as FileEnrichmentDeps["enricher"],
38
+ apiConfig: {
39
+ apiUrl: "https://test.posthog.com",
40
+ projectId: 1,
41
+ getApiKey: getApiKeySpy,
42
+ },
43
+ };
44
+
45
+ return { deps, parseSpy, enrichFromApiSpy, getApiKeySpy };
46
+ }
47
+
48
+ describe("enrichFileForAgent", () => {
49
+ test("returns null for unsupported extension", async () => {
50
+ const { deps, parseSpy } = makeDeps({});
51
+ const result = await enrichFileForAgent(
52
+ deps,
53
+ "/tmp/notes.txt",
54
+ "some text",
55
+ );
56
+ expect(result).toBeNull();
57
+ expect(parseSpy).not.toHaveBeenCalled();
58
+ });
59
+
60
+ test("returns null for empty content", async () => {
61
+ const { deps, parseSpy } = makeDeps({});
62
+ const result = await enrichFileForAgent(deps, "/tmp/code.ts", "");
63
+ expect(result).toBeNull();
64
+ expect(parseSpy).not.toHaveBeenCalled();
65
+ });
66
+
67
+ test("returns null for content > 1MB", async () => {
68
+ const { deps, parseSpy } = makeDeps({});
69
+ const huge = "x".repeat(1_000_001);
70
+ const result = await enrichFileForAgent(deps, "/tmp/code.ts", huge);
71
+ expect(result).toBeNull();
72
+ expect(parseSpy).not.toHaveBeenCalled();
73
+ });
74
+
75
+ test("returns null when language not supported by enricher", async () => {
76
+ const { deps, parseSpy } = makeDeps({ isSupported: false });
77
+ const result = await enrichFileForAgent(
78
+ deps,
79
+ "/tmp/code.ts",
80
+ "posthog.capture('x');",
81
+ );
82
+ expect(result).toBeNull();
83
+ expect(parseSpy).not.toHaveBeenCalled();
84
+ });
85
+
86
+ test("returns null when no PostHog calls detected", async () => {
87
+ const { deps, enrichFromApiSpy } = makeDeps({
88
+ callsCount: 0,
89
+ initCallsCount: 0,
90
+ });
91
+ const result = await enrichFileForAgent(
92
+ deps,
93
+ "/tmp/code.ts",
94
+ "posthog.capture('x');",
95
+ );
96
+ expect(result).toBeNull();
97
+ expect(enrichFromApiSpy).not.toHaveBeenCalled();
98
+ });
99
+
100
+ test("returns null and skips parse when content has no posthog reference", async () => {
101
+ const { deps, parseSpy } = makeDeps({});
102
+ const result = await enrichFileForAgent(
103
+ deps,
104
+ "/tmp/code.ts",
105
+ "const x = 1;\nfunction foo() {}",
106
+ );
107
+ expect(result).toBeNull();
108
+ expect(parseSpy).not.toHaveBeenCalled();
109
+ });
110
+
111
+ test("returns null when getApiKey yields empty string", async () => {
112
+ const { deps, enrichFromApiSpy } = makeDeps({ getApiKey: () => "" });
113
+ const result = await enrichFileForAgent(
114
+ deps,
115
+ "/tmp/code.ts",
116
+ "posthog.capture('x');",
117
+ );
118
+ expect(result).toBeNull();
119
+ expect(enrichFromApiSpy).not.toHaveBeenCalled();
120
+ });
121
+
122
+ test("returns null when toInlineComments produces no change", async () => {
123
+ const original = "posthog.capture('x');";
124
+ const { deps } = makeDeps({ toInlineCommentsReturn: original });
125
+ const result = await enrichFileForAgent(deps, "/tmp/code.ts", original);
126
+ expect(result).toBeNull();
127
+ });
128
+
129
+ test("returns null and logs debug when enricher throws", async () => {
130
+ const logger = { debug: vi.fn() };
131
+ const { deps } = makeDeps({ parseRejects: new Error("boom") });
132
+ deps.logger = logger as unknown as FileEnrichmentDeps["logger"];
133
+ const result = await enrichFileForAgent(
134
+ deps,
135
+ "/tmp/code.ts",
136
+ "posthog.capture('x');",
137
+ );
138
+ expect(result).toBeNull();
139
+ expect(logger.debug).toHaveBeenCalledWith(
140
+ "File enrichment failed",
141
+ expect.objectContaining({ filePath: "/tmp/code.ts" }),
142
+ );
143
+ });
144
+
145
+ test("returns enriched string when happy path completes", async () => {
146
+ const { deps, enrichFromApiSpy } = makeDeps({
147
+ toInlineCommentsReturn: "posthog.capture('x'); // [PostHog] Event: \"x\"",
148
+ });
149
+ const result = await enrichFileForAgent(
150
+ deps,
151
+ "/tmp/code.ts",
152
+ "posthog.capture('x');",
153
+ );
154
+ expect(result).toBe("posthog.capture('x'); // [PostHog] Event: \"x\"");
155
+ expect(enrichFromApiSpy).toHaveBeenCalledWith(
156
+ expect.objectContaining({
157
+ apiKey: "phx_test",
158
+ host: "https://test.posthog.com",
159
+ projectId: 1,
160
+ }),
161
+ );
162
+ });
163
+ });
@@ -0,0 +1,82 @@
1
+ import * as path from "node:path";
2
+ import { EXT_TO_LANG_ID, PostHogEnricher } from "@posthog/enricher";
3
+ import type { PostHogAPIConfig } from "../types";
4
+ import type { Logger } from "../utils/logger";
5
+
6
+ export interface FileEnrichmentDeps {
7
+ enricher: PostHogEnricher;
8
+ apiConfig: PostHogAPIConfig;
9
+ logger?: Logger;
10
+ }
11
+
12
+ export interface Enrichment {
13
+ deps: FileEnrichmentDeps;
14
+ dispose(): void;
15
+ }
16
+
17
+ export function createEnrichment(
18
+ apiConfig: PostHogAPIConfig | undefined,
19
+ logger?: Logger,
20
+ ): Enrichment | undefined {
21
+ if (!apiConfig) return undefined;
22
+ const enricher = new PostHogEnricher();
23
+ return {
24
+ deps: { enricher, apiConfig, logger },
25
+ dispose: () => enricher.dispose(),
26
+ };
27
+ }
28
+
29
+ const MAX_ENRICHMENT_BYTES = 1_000_000;
30
+
31
+ export async function enrichFileForAgent(
32
+ deps: FileEnrichmentDeps,
33
+ filePath: string,
34
+ content: string,
35
+ ): Promise<string | null> {
36
+ if (!content || content.length > MAX_ENRICHMENT_BYTES) return null;
37
+
38
+ // Skip the tree-sitter parse for files with no PostHog references.
39
+ if (!/posthog/i.test(content)) return null;
40
+
41
+ const ext = path.extname(filePath).toLowerCase();
42
+ const langId = EXT_TO_LANG_ID[ext];
43
+ if (!langId || !deps.enricher.isSupported(langId)) return null;
44
+
45
+ try {
46
+ const parsed = await deps.enricher.parse(content, langId);
47
+ if (parsed.calls.length === 0 && parsed.initCalls.length === 0) {
48
+ return null;
49
+ }
50
+
51
+ const apiKey = await deps.apiConfig.getApiKey();
52
+ if (!apiKey) return null;
53
+
54
+ const enriched = await parsed.enrichFromApi({
55
+ apiKey,
56
+ host: deps.apiConfig.apiUrl,
57
+ projectId: deps.apiConfig.projectId,
58
+ timeoutMs: 5_000,
59
+ });
60
+
61
+ const annotated = enriched.toInlineComments();
62
+ if (annotated === content) {
63
+ deps.logger?.debug("File enrichment produced no changes", {
64
+ filePath,
65
+ calls: parsed.calls.length,
66
+ });
67
+ return null;
68
+ }
69
+ deps.logger?.debug("File enriched", {
70
+ filePath,
71
+ calls: parsed.calls.length,
72
+ });
73
+ return annotated;
74
+ } catch (err) {
75
+ const detail =
76
+ err instanceof Error
77
+ ? { message: err.message, name: err.name, stack: err.stack }
78
+ : { value: String(err) };
79
+ deps.logger?.debug("File enrichment failed", { filePath, ...detail });
80
+ return null;
81
+ }
82
+ }
@@ -8,6 +8,7 @@ describe("execution modes", () => {
8
8
  "acceptEdits",
9
9
  "plan",
10
10
  "bypassPermissions",
11
+ "auto",
11
12
  ]);
12
13
  });
13
14
 
@@ -25,19 +25,21 @@ const availableModes: ModeInfo[] = [
25
25
  name: "Plan Mode",
26
26
  description: "Planning mode, no actual tool execution",
27
27
  },
28
- // {
29
- // id: "dontAsk",
30
- // name: "Don't Ask",
31
- // description: "Don't prompt for permissions, deny if not pre-approved",
32
- // },
33
28
  ];
34
29
 
35
30
  if (ALLOW_BYPASS) {
36
- availableModes.push({
37
- id: "bypassPermissions",
38
- name: "Auto-accept Permissions",
39
- description: "Auto-accept all permission requests",
40
- });
31
+ availableModes.push(
32
+ {
33
+ id: "bypassPermissions",
34
+ name: "Bypass Permissions",
35
+ description: "Auto-accept all permission requests",
36
+ },
37
+ {
38
+ id: "auto",
39
+ name: "Auto Mode",
40
+ description: "Use a model classifier to approve/deny permission prompts",
41
+ },
42
+ );
41
43
  }
42
44
 
43
45
  // Expose execution mode IDs in type-safe order for type checks
@@ -45,8 +47,8 @@ export const CODE_EXECUTION_MODES = [
45
47
  "default",
46
48
  "acceptEdits",
47
49
  "plan",
48
- // "dontAsk",
49
50
  "bypassPermissions",
51
+ "auto",
50
52
  ] as const;
51
53
 
52
54
  export type CodeExecutionMode = (typeof CODE_EXECUTION_MODES)[number];
@@ -12,7 +12,7 @@ import { type ServerType, serve } from "@hono/node-server";
12
12
  import { getCurrentBranch } from "@posthog/git/queries";
13
13
  import { Hono } from "hono";
14
14
  import packageJson from "../../package.json" with { type: "json" };
15
- import { POSTHOG_NOTIFICATIONS } from "../acp-extensions";
15
+ import { POSTHOG_METHODS, POSTHOG_NOTIFICATIONS } from "../acp-extensions";
16
16
  import {
17
17
  createAcpConnection,
18
18
  type InProcessAcpConnection,
@@ -644,6 +644,23 @@ export class AgentServer {
644
644
  };
645
645
  }
646
646
 
647
+ case POSTHOG_METHODS.REFRESH_SESSION:
648
+ case "posthog/refresh_session":
649
+ case "refresh_session": {
650
+ const mcpServers = Array.isArray(params.mcpServers)
651
+ ? params.mcpServers
652
+ : [];
653
+
654
+ this.logger.info("Refresh session requested", {
655
+ serverCount: mcpServers.length,
656
+ });
657
+
658
+ return await this.session.clientConnection.extMethod(
659
+ POSTHOG_METHODS.REFRESH_SESSION,
660
+ { mcpServers },
661
+ );
662
+ }
663
+
647
664
  case POSTHOG_NOTIFICATIONS.PERMISSION_RESPONSE:
648
665
  case "permission_response": {
649
666
  const requestId = params.requestId as string;
package/src/server/bin.ts CHANGED
@@ -30,7 +30,7 @@ const envSchema = z.object({
30
30
  POSTHOG_CODE_RUNTIME_ADAPTER: z.enum(["claude", "codex"]).optional(),
31
31
  POSTHOG_CODE_MODEL: z.string().optional(),
32
32
  POSTHOG_CODE_REASONING_EFFORT: z
33
- .enum(["low", "medium", "high", "max"])
33
+ .enum(["low", "medium", "high", "xhigh", "max"])
34
34
  .optional(),
35
35
  });
36
36
 
@@ -184,4 +184,44 @@ describe("validateCommandParams", () => {
184
184
 
185
185
  expect(result.success).toBe(false);
186
186
  });
187
+
188
+ it("accepts _posthog/refresh_session with mcpServers", () => {
189
+ const result = validateCommandParams("_posthog/refresh_session", {
190
+ mcpServers: [
191
+ { type: "http", name: "mcp", url: "https://mcp.example.com" },
192
+ ],
193
+ });
194
+
195
+ expect(result.success).toBe(true);
196
+ });
197
+
198
+ it("accepts posthog/refresh_session with empty mcpServers", () => {
199
+ const result = validateCommandParams("posthog/refresh_session", {
200
+ mcpServers: [],
201
+ });
202
+
203
+ expect(result.success).toBe(true);
204
+ });
205
+
206
+ it("accepts bare refresh_session", () => {
207
+ const result = validateCommandParams("refresh_session", {
208
+ mcpServers: [],
209
+ });
210
+
211
+ expect(result.success).toBe(true);
212
+ });
213
+
214
+ it("rejects refresh_session without mcpServers", () => {
215
+ const result = validateCommandParams("_posthog/refresh_session", {});
216
+
217
+ expect(result.success).toBe(false);
218
+ });
219
+
220
+ it("rejects refresh_session with invalid mcpServers entry", () => {
221
+ const result = validateCommandParams("_posthog/refresh_session", {
222
+ mcpServers: [{ type: "stdio", name: "bad", command: "/bin/x" }],
223
+ });
224
+
225
+ expect(result.success).toBe(false);
226
+ });
187
227
  });
@@ -60,6 +60,10 @@ export const setConfigOptionParamsSchema = z.object({
60
60
  value: z.string().min(1, "value is required"),
61
61
  });
62
62
 
63
+ export const refreshSessionParamsSchema = z.object({
64
+ mcpServers: mcpServersSchema,
65
+ });
66
+
63
67
  export const commandParamsSchemas = {
64
68
  user_message: userMessageParamsSchema,
65
69
  "posthog/user_message": userMessageParamsSchema,
@@ -71,6 +75,9 @@ export const commandParamsSchemas = {
71
75
  "posthog/permission_response": permissionResponseParamsSchema,
72
76
  set_config_option: setConfigOptionParamsSchema,
73
77
  "posthog/set_config_option": setConfigOptionParamsSchema,
78
+ refresh_session: refreshSessionParamsSchema,
79
+ "posthog/refresh_session": refreshSessionParamsSchema,
80
+ "_posthog/refresh_session": refreshSessionParamsSchema,
74
81
  } as const;
75
82
 
76
83
  export type CommandMethod = keyof typeof commandParamsSchemas;
@@ -82,7 +89,7 @@ export function validateCommandParams(
82
89
  const schema =
83
90
  commandParamsSchemas[method as CommandMethod] ??
84
91
  commandParamsSchemas[
85
- method.replace("posthog/", "") as keyof typeof commandParamsSchemas
92
+ method.replace(/^_?posthog\//, "") as keyof typeof commandParamsSchemas
86
93
  ];
87
94
 
88
95
  if (!schema) {
@@ -26,5 +26,5 @@ export interface AgentServerConfig {
26
26
  allowedDomains?: string[];
27
27
  runtimeAdapter?: "claude" | "codex";
28
28
  model?: string;
29
- reasoningEffort?: "low" | "medium" | "high" | "max";
29
+ reasoningEffort?: "low" | "medium" | "high" | "xhigh" | "max";
30
30
  }
package/src/types.ts CHANGED
@@ -152,6 +152,12 @@ export interface AgentConfig {
152
152
  skipLogPersistence?: boolean;
153
153
  /** Local cache path for instant log loading (e.g., ~/.posthog-code) */
154
154
  localCachePath?: string;
155
+ /**
156
+ * Annotate files the agent reads with PostHog enrichment (event volume,
157
+ * flag rollout/staleness, experiment links). Defaults to enabled when
158
+ * `posthog` config is present; set `{ enabled: false }` to opt out.
159
+ */
160
+ enricher?: { enabled?: boolean };
155
161
  debug?: boolean;
156
162
  onLog?: OnLogCallback;
157
163
  }