@posthog/agent 2.3.261 → 2.3.267

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.
@@ -896,6 +896,125 @@ var require_dist2 = __commonJS({
896
896
  var import_commander = require("commander");
897
897
  var import_v42 = require("zod/v4");
898
898
 
899
+ // src/adapters/claude/session/models.ts
900
+ var DEFAULT_MODEL = "opus";
901
+ var GATEWAY_TO_SDK_MODEL = {
902
+ "claude-opus-4-5": "opus",
903
+ "claude-opus-4-6": "opus",
904
+ "claude-sonnet-4-5": "sonnet",
905
+ "claude-sonnet-4-6": "sonnet",
906
+ "claude-haiku-4-5": "haiku"
907
+ };
908
+ function toSdkModelId(modelId) {
909
+ return GATEWAY_TO_SDK_MODEL[modelId] ?? modelId;
910
+ }
911
+ var MODELS_WITH_1M_CONTEXT = /* @__PURE__ */ new Set([
912
+ "claude-opus-4-6",
913
+ "claude-sonnet-4-6"
914
+ ]);
915
+ function supports1MContext(modelId) {
916
+ return MODELS_WITH_1M_CONTEXT.has(modelId);
917
+ }
918
+ var MODELS_WITH_EFFORT = /* @__PURE__ */ new Set([
919
+ "claude-opus-4-5",
920
+ "claude-opus-4-6",
921
+ "claude-sonnet-4-6"
922
+ ]);
923
+ var MODELS_WITH_MAX_EFFORT = /* @__PURE__ */ new Set(["claude-opus-4-6"]);
924
+ function supportsEffort(modelId) {
925
+ return MODELS_WITH_EFFORT.has(modelId);
926
+ }
927
+ function supportsMaxEffort(modelId) {
928
+ return MODELS_WITH_MAX_EFFORT.has(modelId);
929
+ }
930
+ var MODELS_TO_EXCLUDE_MCP_TOOLS = /* @__PURE__ */ new Set(["claude-haiku-4-5"]);
931
+ function supportsMcpInjection(modelId) {
932
+ return !MODELS_TO_EXCLUDE_MCP_TOOLS.has(modelId);
933
+ }
934
+ function getEffortOptions(modelId) {
935
+ if (!supportsEffort(modelId)) return null;
936
+ const options = [
937
+ { value: "low", name: "Low" },
938
+ { value: "medium", name: "Medium" },
939
+ { value: "high", name: "High" }
940
+ ];
941
+ if (supportsMaxEffort(modelId)) {
942
+ options.push({ value: "max", name: "Max" });
943
+ }
944
+ return options;
945
+ }
946
+ var MODEL_CONTEXT_HINT_PATTERN = /\[(\d+m)\]$/i;
947
+ function tokenizeModelPreference(model) {
948
+ const lower = model.trim().toLowerCase();
949
+ const contextHint = lower.match(MODEL_CONTEXT_HINT_PATTERN)?.[1]?.toLowerCase();
950
+ const normalized = lower.replace(MODEL_CONTEXT_HINT_PATTERN, " $1 ");
951
+ const rawTokens = normalized.split(/[^a-z0-9]+/).filter(Boolean);
952
+ const tokens = rawTokens.map((token) => {
953
+ if (token === "opusplan") return "opus";
954
+ if (token === "best" || token === "default") return "";
955
+ return token;
956
+ }).filter((token) => token && token !== "claude").filter((token) => /[a-z]/.test(token) || token.endsWith("m"));
957
+ return { tokens, contextHint };
958
+ }
959
+ function scoreModelMatch(model, tokens, contextHint) {
960
+ const haystack = `${model.value} ${model.name ?? ""}`.toLowerCase();
961
+ let score = 0;
962
+ for (const token of tokens) {
963
+ if (haystack.includes(token)) {
964
+ score += token === contextHint ? 3 : 1;
965
+ }
966
+ }
967
+ return score;
968
+ }
969
+ function resolveModelPreference(preference, options) {
970
+ const trimmed2 = preference.trim();
971
+ if (!trimmed2) return null;
972
+ const lower = trimmed2.toLowerCase();
973
+ const directMatch = options.find(
974
+ (o) => o.value === trimmed2 || o.value.toLowerCase() === lower || o.name && o.name.toLowerCase() === lower
975
+ );
976
+ if (directMatch) return directMatch.value;
977
+ const includesMatch = options.find((o) => {
978
+ const value = o.value.toLowerCase();
979
+ const display = (o.name ?? "").toLowerCase();
980
+ return value.includes(lower) || display.includes(lower) || lower.includes(value);
981
+ });
982
+ if (includesMatch) return includesMatch.value;
983
+ const { tokens, contextHint } = tokenizeModelPreference(trimmed2);
984
+ if (tokens.length === 0) return null;
985
+ let bestMatch = null;
986
+ let bestScore = 0;
987
+ for (const model of options) {
988
+ const score = scoreModelMatch(model, tokens, contextHint);
989
+ if (0 < score && (!bestMatch || bestScore < score)) {
990
+ bestMatch = model;
991
+ bestScore = score;
992
+ }
993
+ }
994
+ return bestMatch?.value ?? null;
995
+ }
996
+
997
+ // src/adapters/codex/models.ts
998
+ var CODEX_REASONING_EFFORT_OPTIONS = [
999
+ { value: "low", name: "Low" },
1000
+ { value: "medium", name: "Medium" },
1001
+ { value: "high", name: "High" }
1002
+ ];
1003
+ function getReasoningEffortOptions(_modelId) {
1004
+ return CODEX_REASONING_EFFORT_OPTIONS;
1005
+ }
1006
+
1007
+ // src/adapters/reasoning-effort.ts
1008
+ function getReasoningEffortOptions2(adapter, modelId) {
1009
+ const options = adapter === "codex" ? getReasoningEffortOptions(modelId) : getEffortOptions(modelId);
1010
+ return options;
1011
+ }
1012
+ function isSupportedReasoningEffort(adapter, modelId, value) {
1013
+ return getReasoningEffortOptions2(adapter, modelId)?.some(
1014
+ (option) => option.value === value
1015
+ ) ?? false;
1016
+ }
1017
+
899
1018
  // src/server/agent-server.ts
900
1019
  var import_sdk5 = require("@agentclientprotocol/sdk");
901
1020
  var import_node_server = require("@hono/node-server");
@@ -5683,7 +5802,7 @@ var import_hono = require("hono");
5683
5802
  // package.json
5684
5803
  var package_default = {
5685
5804
  name: "@posthog/agent",
5686
- version: "2.3.261",
5805
+ version: "2.3.267",
5687
5806
  repository: "https://github.com/PostHog/code",
5688
5807
  description: "TypeScript agent framework wrapping Claude Agent SDK with Git-based task execution for PostHog",
5689
5808
  exports: {
@@ -5731,6 +5850,10 @@ var package_default = {
5731
5850
  types: "./dist/adapters/claude/session/models.d.ts",
5732
5851
  import: "./dist/adapters/claude/session/models.js"
5733
5852
  },
5853
+ "./adapters/reasoning-effort": {
5854
+ types: "./dist/adapters/reasoning-effort.d.ts",
5855
+ import: "./dist/adapters/reasoning-effort.js"
5856
+ },
5734
5857
  "./execution-mode": {
5735
5858
  types: "./dist/execution-mode.d.ts",
5736
5859
  import: "./dist/execution-mode.js"
@@ -5844,7 +5967,9 @@ var POSTHOG_NOTIFICATIONS = {
5844
5967
  /** Marks a boundary for log compaction */
5845
5968
  COMPACT_BOUNDARY: "_posthog/compact_boundary",
5846
5969
  /** Token usage update for a session turn */
5847
- USAGE_UPDATE: "_posthog/usage_update"
5970
+ USAGE_UPDATE: "_posthog/usage_update",
5971
+ /** Response to a relayed permission request (plan approval, question) */
5972
+ PERMISSION_RESPONSE: "_posthog/permission_response"
5848
5973
  };
5849
5974
  function isNotification(method, notification) {
5850
5975
  if (!method) return false;
@@ -6113,6 +6238,7 @@ function unreachable(value, logger) {
6113
6238
 
6114
6239
  // src/gateway-models.ts
6115
6240
  var DEFAULT_GATEWAY_MODEL = "claude-opus-4-6";
6241
+ var DEFAULT_CODEX_MODEL = "gpt-5.4";
6116
6242
  var BLOCKED_MODELS = /* @__PURE__ */ new Set(["gpt-5-mini", "openai/gpt-5-mini"]);
6117
6243
  var CACHE_TTL = 10 * 60 * 1e3;
6118
6244
  var gatewayModelsCache = null;
@@ -8281,6 +8407,11 @@ async function canUseTool(context) {
8281
8407
  if (planFileResult) {
8282
8408
  return planFileResult;
8283
8409
  }
8410
+ if (session.permissionMode === "plan") {
8411
+ const message = `This tool is not available in plan mode. Write your plan to a file in ${getClaudePlansDir()} and call ExitPlanMode when ready.`;
8412
+ await emitToolDenial(context, message);
8413
+ return { behavior: "deny", message, interrupt: false };
8414
+ }
8284
8415
  return handleDefaultPermissionFlow(context);
8285
8416
  }
8286
8417
 
@@ -8339,104 +8470,6 @@ function parseMcpServers(params) {
8339
8470
  return mcpServers;
8340
8471
  }
8341
8472
 
8342
- // src/adapters/claude/session/models.ts
8343
- var DEFAULT_MODEL = "opus";
8344
- var GATEWAY_TO_SDK_MODEL = {
8345
- "claude-opus-4-5": "opus",
8346
- "claude-opus-4-6": "opus",
8347
- "claude-sonnet-4-5": "sonnet",
8348
- "claude-sonnet-4-6": "sonnet",
8349
- "claude-haiku-4-5": "haiku"
8350
- };
8351
- function toSdkModelId(modelId) {
8352
- return GATEWAY_TO_SDK_MODEL[modelId] ?? modelId;
8353
- }
8354
- var MODELS_WITH_1M_CONTEXT = /* @__PURE__ */ new Set([
8355
- "claude-opus-4-6",
8356
- "claude-sonnet-4-6"
8357
- ]);
8358
- function supports1MContext(modelId) {
8359
- return MODELS_WITH_1M_CONTEXT.has(modelId);
8360
- }
8361
- var MODELS_WITH_EFFORT = /* @__PURE__ */ new Set([
8362
- "claude-opus-4-5",
8363
- "claude-opus-4-6",
8364
- "claude-sonnet-4-6"
8365
- ]);
8366
- var MODELS_WITH_MAX_EFFORT = /* @__PURE__ */ new Set(["claude-opus-4-6"]);
8367
- function supportsEffort(modelId) {
8368
- return MODELS_WITH_EFFORT.has(modelId);
8369
- }
8370
- function supportsMaxEffort(modelId) {
8371
- return MODELS_WITH_MAX_EFFORT.has(modelId);
8372
- }
8373
- var MODELS_TO_EXCLUDE_MCP_TOOLS = /* @__PURE__ */ new Set(["claude-haiku-4-5"]);
8374
- function supportsMcpInjection(modelId) {
8375
- return !MODELS_TO_EXCLUDE_MCP_TOOLS.has(modelId);
8376
- }
8377
- function getEffortOptions(modelId) {
8378
- if (!supportsEffort(modelId)) return null;
8379
- const options = [
8380
- { value: "low", name: "Low" },
8381
- { value: "medium", name: "Medium" },
8382
- { value: "high", name: "High" }
8383
- ];
8384
- if (supportsMaxEffort(modelId)) {
8385
- options.push({ value: "max", name: "Max" });
8386
- }
8387
- return options;
8388
- }
8389
- var MODEL_CONTEXT_HINT_PATTERN = /\[(\d+m)\]$/i;
8390
- function tokenizeModelPreference(model) {
8391
- const lower = model.trim().toLowerCase();
8392
- const contextHint = lower.match(MODEL_CONTEXT_HINT_PATTERN)?.[1]?.toLowerCase();
8393
- const normalized = lower.replace(MODEL_CONTEXT_HINT_PATTERN, " $1 ");
8394
- const rawTokens = normalized.split(/[^a-z0-9]+/).filter(Boolean);
8395
- const tokens = rawTokens.map((token) => {
8396
- if (token === "opusplan") return "opus";
8397
- if (token === "best" || token === "default") return "";
8398
- return token;
8399
- }).filter((token) => token && token !== "claude").filter((token) => /[a-z]/.test(token) || token.endsWith("m"));
8400
- return { tokens, contextHint };
8401
- }
8402
- function scoreModelMatch(model, tokens, contextHint) {
8403
- const haystack = `${model.value} ${model.name ?? ""}`.toLowerCase();
8404
- let score = 0;
8405
- for (const token of tokens) {
8406
- if (haystack.includes(token)) {
8407
- score += token === contextHint ? 3 : 1;
8408
- }
8409
- }
8410
- return score;
8411
- }
8412
- function resolveModelPreference(preference, options) {
8413
- const trimmed2 = preference.trim();
8414
- if (!trimmed2) return null;
8415
- const lower = trimmed2.toLowerCase();
8416
- const directMatch = options.find(
8417
- (o) => o.value === trimmed2 || o.value.toLowerCase() === lower || o.name && o.name.toLowerCase() === lower
8418
- );
8419
- if (directMatch) return directMatch.value;
8420
- const includesMatch = options.find((o) => {
8421
- const value = o.value.toLowerCase();
8422
- const display = (o.name ?? "").toLowerCase();
8423
- return value.includes(lower) || display.includes(lower) || lower.includes(value);
8424
- });
8425
- if (includesMatch) return includesMatch.value;
8426
- const { tokens, contextHint } = tokenizeModelPreference(trimmed2);
8427
- if (tokens.length === 0) return null;
8428
- let bestMatch = null;
8429
- let bestScore = 0;
8430
- for (const model of options) {
8431
- const score = scoreModelMatch(model, tokens, contextHint);
8432
- if (0 < score && (!bestMatch || bestScore < score)) {
8433
- bestMatch = model;
8434
- bestScore = score;
8435
- }
8436
- }
8437
- return bestMatch?.value ?? null;
8438
- }
8439
-
8440
8473
  // src/adapters/claude/session/options.ts
8441
8474
  var import_node_child_process2 = require("child_process");
8442
8475
  var fs4 = __toESM(require("fs"), 1);
@@ -10085,6 +10118,9 @@ function buildConfigArgs(options) {
10085
10118
  if (options.model) {
10086
10119
  args.push("-c", `model="${options.model}"`);
10087
10120
  }
10121
+ if (options.reasoningEffort) {
10122
+ args.push("-c", `model_reasoning_effort="${options.reasoningEffort}"`);
10123
+ }
10088
10124
  if (options.instructions) {
10089
10125
  const escaped = options.instructions.replace(/\\/g, "\\\\").replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/"/g, '\\"');
10090
10126
  args.push("-c", `instructions="${escaped}"`);
@@ -12219,13 +12255,27 @@ var userMessageParamsSchema = import_v4.z.object({
12219
12255
  import_v4.z.array(import_v4.z.record(import_v4.z.string(), import_v4.z.unknown())).min(1, "Content is required")
12220
12256
  ])
12221
12257
  });
12258
+ var permissionResponseParamsSchema = import_v4.z.object({
12259
+ requestId: import_v4.z.string().min(1, "requestId is required"),
12260
+ optionId: import_v4.z.string().min(1, "optionId is required"),
12261
+ customInput: import_v4.z.string().optional(),
12262
+ answers: import_v4.z.record(import_v4.z.string(), import_v4.z.string()).optional()
12263
+ });
12264
+ var setConfigOptionParamsSchema = import_v4.z.object({
12265
+ configId: import_v4.z.string().min(1, "configId is required"),
12266
+ value: import_v4.z.string().min(1, "value is required")
12267
+ });
12222
12268
  var commandParamsSchemas = {
12223
12269
  user_message: userMessageParamsSchema,
12224
12270
  "posthog/user_message": userMessageParamsSchema,
12225
12271
  cancel: import_v4.z.object({}).optional(),
12226
12272
  "posthog/cancel": import_v4.z.object({}).optional(),
12227
12273
  close: import_v4.z.object({}).optional(),
12228
- "posthog/close": import_v4.z.object({}).optional()
12274
+ "posthog/close": import_v4.z.object({}).optional(),
12275
+ permission_response: permissionResponseParamsSchema,
12276
+ "posthog/permission_response": permissionResponseParamsSchema,
12277
+ set_config_option: setConfigOptionParamsSchema,
12278
+ "posthog/set_config_option": setConfigOptionParamsSchema
12229
12279
  };
12230
12280
  function validateCommandParams(method, params) {
12231
12281
  const schema = commandParamsSchemas[method] ?? commandParamsSchemas[method.replace("posthog/", "")];
@@ -12325,6 +12375,14 @@ function createTappedWritableStream2(underlying, onMessage, logger) {
12325
12375
  }
12326
12376
  });
12327
12377
  }
12378
+ function getTaskRunStateString(taskRun, key) {
12379
+ const state = taskRun?.state;
12380
+ if (!state || typeof state !== "object") {
12381
+ return null;
12382
+ }
12383
+ const value = state[key];
12384
+ return typeof value === "string" ? value : null;
12385
+ }
12328
12386
  var AgentServer = class _AgentServer {
12329
12387
  config;
12330
12388
  logger;
@@ -12342,6 +12400,7 @@ var AgentServer = class _AgentServer {
12342
12400
  // causing a second session to be created and duplicate Slack messages to be sent.
12343
12401
  initializationPromise = null;
12344
12402
  pendingEvents = [];
12403
+ pendingPermissions = /* @__PURE__ */ new Map();
12345
12404
  detachSseController(controller) {
12346
12405
  if (this.session?.sseController === controller) {
12347
12406
  this.session.sseController = null;
@@ -12376,9 +12435,15 @@ var AgentServer = class _AgentServer {
12376
12435
  });
12377
12436
  this.app = this.createApp();
12378
12437
  }
12438
+ getRuntimeAdapter() {
12439
+ return this.config.runtimeAdapter ?? "claude";
12440
+ }
12379
12441
  getEffectiveMode(payload) {
12380
12442
  return payload.mode ?? this.config.mode;
12381
12443
  }
12444
+ getSessionPermissionMode() {
12445
+ return this.session?.permissionMode ?? "default";
12446
+ }
12382
12447
  createApp() {
12383
12448
  const app = new import_hono.Hono();
12384
12449
  app.get("/health", (c) => {
@@ -12423,6 +12488,7 @@ var AgentServer = class _AgentServer {
12423
12488
  await this.initializeSession(payload, sseController);
12424
12489
  } else {
12425
12490
  this.session.sseController = sseController;
12491
+ this.session.hasDesktopConnected = true;
12426
12492
  this.replayPendingEvents();
12427
12493
  }
12428
12494
  this.sendSseEvent(sseController, {
@@ -12662,6 +12728,43 @@ var AgentServer = class _AgentServer {
12662
12728
  await this.cleanupSession();
12663
12729
  return { closed: true };
12664
12730
  }
12731
+ case "posthog/set_config_option":
12732
+ case "set_config_option": {
12733
+ const configId = params.configId;
12734
+ const value = params.value;
12735
+ this.logger.info("Set config option requested", { configId, value });
12736
+ const result = await this.session.clientConnection.setSessionConfigOption({
12737
+ sessionId: this.session.acpSessionId,
12738
+ configId,
12739
+ value
12740
+ });
12741
+ return {
12742
+ configOptions: result.configOptions
12743
+ };
12744
+ }
12745
+ case POSTHOG_NOTIFICATIONS.PERMISSION_RESPONSE:
12746
+ case "permission_response": {
12747
+ const requestId = params.requestId;
12748
+ const optionId = params.optionId;
12749
+ const customInput = params.customInput;
12750
+ const answers = params.answers;
12751
+ this.logger.info("Permission response received", {
12752
+ requestId,
12753
+ optionId
12754
+ });
12755
+ const resolved = this.resolvePermission(
12756
+ requestId,
12757
+ optionId,
12758
+ customInput,
12759
+ answers
12760
+ );
12761
+ if (!resolved) {
12762
+ throw new Error(
12763
+ `No pending permission request found for id: ${requestId}`
12764
+ );
12765
+ }
12766
+ return { resolved: true };
12767
+ }
12665
12768
  default:
12666
12769
  throw new Error(`Unknown method: ${method}`);
12667
12770
  }
@@ -12701,6 +12804,30 @@ var AgentServer = class _AgentServer {
12701
12804
  name: process.env.HOSTNAME || "cloud-sandbox"
12702
12805
  };
12703
12806
  this.configureEnvironment();
12807
+ const [preTaskRun, preTask] = await Promise.all([
12808
+ this.posthogAPI.getTaskRun(payload.task_id, payload.run_id).catch((err) => {
12809
+ this.logger.warn("Failed to fetch task run for session context", {
12810
+ taskId: payload.task_id,
12811
+ runId: payload.run_id,
12812
+ error: err
12813
+ });
12814
+ return null;
12815
+ }),
12816
+ this.posthogAPI.getTask(payload.task_id).catch((err) => {
12817
+ this.logger.warn("Failed to fetch task for session context", {
12818
+ taskId: payload.task_id,
12819
+ error: err
12820
+ });
12821
+ return null;
12822
+ })
12823
+ ]);
12824
+ const prUrl = getTaskRunStateString(preTaskRun, "slack_notified_pr_url");
12825
+ if (prUrl) {
12826
+ this.detectedPrUrl = prUrl;
12827
+ }
12828
+ const runtimeAdapter = this.getRuntimeAdapter();
12829
+ const sessionSystemPrompt = this.buildSessionSystemPrompt(prUrl);
12830
+ const codexInstructions = runtimeAdapter === "codex" ? this.buildCodexInstructions(sessionSystemPrompt) : void 0;
12704
12831
  const posthogAPI = new PostHogAPIClient({
12705
12832
  apiUrl: this.config.apiUrl,
12706
12833
  projectId: this.config.projectId,
@@ -12719,10 +12846,20 @@ var AgentServer = class _AgentServer {
12719
12846
  logger: new Logger({ debug: true, prefix: "[SessionLogWriter]" })
12720
12847
  });
12721
12848
  const acpConnection = createAcpConnection({
12849
+ adapter: runtimeAdapter,
12722
12850
  taskRunId: payload.run_id,
12723
12851
  taskId: payload.task_id,
12724
12852
  deviceType: deviceInfo.type,
12725
12853
  logWriter,
12854
+ logger: this.logger,
12855
+ codexOptions: runtimeAdapter === "codex" ? {
12856
+ cwd: this.config.repositoryPath ?? "/tmp/workspace",
12857
+ apiBaseUrl: process.env.OPENAI_BASE_URL,
12858
+ apiKey: this.config.apiKey,
12859
+ model: this.config.model ?? DEFAULT_CODEX_MODEL,
12860
+ reasoningEffort: this.config.reasoningEffort,
12861
+ instructions: codexInstructions
12862
+ } : void 0,
12726
12863
  onStructuredOutput: async (output) => {
12727
12864
  await this.posthogAPI.setTaskRunOutput(
12728
12865
  payload.task_id,
@@ -12759,40 +12896,28 @@ var AgentServer = class _AgentServer {
12759
12896
  protocolVersion: import_sdk5.PROTOCOL_VERSION,
12760
12897
  clientCapabilities: {}
12761
12898
  });
12762
- const [preTaskRun, preTask] = await Promise.all([
12763
- this.posthogAPI.getTaskRun(payload.task_id, payload.run_id).catch((err) => {
12764
- this.logger.warn("Failed to fetch task run for session context", {
12765
- taskId: payload.task_id,
12766
- runId: payload.run_id,
12767
- error: err
12768
- });
12769
- return null;
12770
- }),
12771
- this.posthogAPI.getTask(payload.task_id).catch((err) => {
12772
- this.logger.warn("Failed to fetch task for session context", {
12773
- taskId: payload.task_id,
12774
- error: err
12775
- });
12776
- return null;
12777
- })
12778
- ]);
12779
- const prUrl = typeof preTaskRun?.state?.slack_notified_pr_url === "string" ? (preTaskRun?.state).slack_notified_pr_url : null;
12780
- if (prUrl) {
12781
- this.detectedPrUrl = prUrl;
12782
- }
12899
+ const runState = preTaskRun?.state;
12900
+ const initialPermissionMode = typeof runState?.initial_permission_mode === "string" ? runState.initial_permission_mode : "bypassPermissions";
12783
12901
  const sessionResponse = await clientConnection.newSession({
12784
12902
  cwd: this.config.repositoryPath ?? "/tmp/workspace",
12785
12903
  mcpServers: this.config.mcpServers ?? [],
12786
12904
  _meta: {
12787
12905
  sessionId: payload.run_id,
12788
12906
  taskRunId: payload.run_id,
12789
- systemPrompt: this.buildSessionSystemPrompt(prUrl),
12907
+ systemPrompt: sessionSystemPrompt,
12908
+ ...this.config.model && { model: this.config.model },
12790
12909
  allowedDomains: this.config.allowedDomains,
12791
12910
  jsonSchema: preTask?.json_schema ?? null,
12911
+ permissionMode: initialPermissionMode,
12792
12912
  ...this.config.claudeCode?.plugins?.length && {
12793
12913
  claudeCode: {
12794
12914
  options: {
12795
- plugins: this.config.claudeCode.plugins
12915
+ ...this.config.claudeCode?.plugins?.length && {
12916
+ plugins: this.config.claudeCode.plugins
12917
+ },
12918
+ ...runtimeAdapter === "claude" && this.config.reasoningEffort && {
12919
+ effort: this.config.reasoningEffort
12920
+ }
12796
12921
  }
12797
12922
  }
12798
12923
  }
@@ -12811,7 +12936,9 @@ var AgentServer = class _AgentServer {
12811
12936
  treeTracker,
12812
12937
  sseController,
12813
12938
  deviceInfo,
12814
- logWriter
12939
+ logWriter,
12940
+ permissionMode: initialPermissionMode,
12941
+ hasDesktopConnected: sseController !== null
12815
12942
  };
12816
12943
  this.logger = new Logger({
12817
12944
  debug: true,
@@ -12825,6 +12952,7 @@ var AgentServer = class _AgentServer {
12825
12952
  this.logger.info(
12826
12953
  `Agent version: ${this.config.version ?? package_default.version}`
12827
12954
  );
12955
+ this.logger.info(`Initial permission mode: ${initialPermissionMode}`);
12828
12956
  this.posthogAPI.updateTaskRun(payload.task_id, payload.run_id, {
12829
12957
  status: "in_progress"
12830
12958
  }).catch(
@@ -13074,6 +13202,9 @@ ${toolSummary}`);
13074
13202
  }
13075
13203
  return { append: cloudAppend };
13076
13204
  }
13205
+ buildCodexInstructions(systemPrompt) {
13206
+ return typeof systemPrompt === "string" ? systemPrompt : systemPrompt.append;
13207
+ }
13077
13208
  getCloudInteractionOrigin() {
13078
13209
  return process.env.POSTHOG_CODE_INTERACTION_ORIGIN ?? process.env.CODE_INTERACTION_ORIGIN ?? process.env.TWIG_INTERACTION_ORIGIN;
13079
13210
  }
@@ -13315,14 +13446,16 @@ ${attributionInstructions}
13315
13446
  this.logger.debug("Permission request", {
13316
13447
  mode,
13317
13448
  interactionOrigin,
13449
+ kind: params.toolCall?.kind,
13318
13450
  options: params.options
13319
13451
  });
13320
13452
  const allowOption = params.options.find(
13321
13453
  (o) => o.kind === "allow_once" || o.kind === "allow_always"
13322
13454
  );
13323
13455
  const selectedOptionId = allowOption?.optionId ?? params.options[0].optionId;
13456
+ const codeToolKind = params.toolCall?._meta?.codeToolKind;
13457
+ const isPlanApproval = params.toolCall?.kind === "switch_mode";
13324
13458
  if (interactionOrigin === "slack") {
13325
- const codeToolKind = params.toolCall?._meta?.codeToolKind;
13326
13459
  if (codeToolKind === "question") {
13327
13460
  return this.buildSlackQuestionRelayResponse(
13328
13461
  payload,
@@ -13330,6 +13463,19 @@ ${attributionInstructions}
13330
13463
  );
13331
13464
  }
13332
13465
  }
13466
+ {
13467
+ const isQuestion = codeToolKind === "question";
13468
+ const sessionPermissionMode = this.getSessionPermissionMode();
13469
+ const needsRelay = isQuestion || isPlanApproval || sessionPermissionMode === "default";
13470
+ if (needsRelay && this.session?.hasDesktopConnected) {
13471
+ this.logger.info("Relaying permission to connected client", {
13472
+ kind: params.toolCall?.kind,
13473
+ isQuestion,
13474
+ sessionPermissionMode
13475
+ });
13476
+ return this.relayPermissionToClient(params);
13477
+ }
13478
+ }
13333
13479
  if (this.shouldBlockPublishPermission(params)) {
13334
13480
  return {
13335
13481
  outcome: { outcome: "cancelled" },
@@ -13349,6 +13495,12 @@ ${attributionInstructions}
13349
13495
  this.logger.debug("Extension notification", { method, params });
13350
13496
  },
13351
13497
  sessionUpdate: async (params) => {
13498
+ if (params.update?.sessionUpdate === "current_mode_update" && typeof params.update?.currentModeId === "string" && this.session) {
13499
+ this.session.permissionMode = params.update.currentModeId;
13500
+ this.logger.info("Permission mode updated", {
13501
+ mode: params.update.currentModeId
13502
+ });
13503
+ }
13352
13504
  if (params.update?.sessionUpdate === "tool_call_update") {
13353
13505
  const meta = params.update?._meta?.claudeCode;
13354
13506
  const toolName = meta?.toolName;
@@ -13527,6 +13679,13 @@ ${attributionInstructions}
13527
13679
  } catch (error) {
13528
13680
  this.logger.error("Failed to flush session logs", error);
13529
13681
  }
13682
+ for (const [, pending] of this.pendingPermissions) {
13683
+ pending.resolve({
13684
+ outcome: { outcome: "selected", optionId: "reject" },
13685
+ _meta: { customInput: "Session is shutting down." }
13686
+ });
13687
+ }
13688
+ this.pendingPermissions.clear();
13530
13689
  try {
13531
13690
  await this.session.acpConnection.cleanup();
13532
13691
  } catch (error) {
@@ -13604,6 +13763,39 @@ ${attributionInstructions}
13604
13763
  this.detachSseController(controller);
13605
13764
  }
13606
13765
  }
13766
+ /**
13767
+ * Relay a permission request (e.g., plan approval) to the connected desktop
13768
+ * app via SSE and wait for a response via the `/command` endpoint.
13769
+ *
13770
+ * The promise waits indefinitely — if SSE is disconnected, the event is
13771
+ * buffered by broadcastEvent and replayed when the client reconnects. Session
13772
+ * cleanup force-resolves all pending permissions, so there is no leak.
13773
+ */
13774
+ relayPermissionToClient(params) {
13775
+ const requestId = crypto.randomUUID();
13776
+ this.broadcastEvent({
13777
+ type: "permission_request",
13778
+ requestId,
13779
+ options: params.options,
13780
+ toolCall: params.toolCall
13781
+ });
13782
+ return new Promise((resolve4) => {
13783
+ this.pendingPermissions.set(requestId, { resolve: resolve4 });
13784
+ });
13785
+ }
13786
+ resolvePermission(requestId, optionId, customInput, answers) {
13787
+ const pending = this.pendingPermissions.get(requestId);
13788
+ if (!pending) return false;
13789
+ this.pendingPermissions.delete(requestId);
13790
+ const meta = {};
13791
+ if (customInput) meta.customInput = customInput;
13792
+ if (answers) meta.answers = answers;
13793
+ pending.resolve({
13794
+ outcome: { outcome: "selected", optionId },
13795
+ ...Object.keys(meta).length > 0 ? { _meta: meta } : {}
13796
+ });
13797
+ return true;
13798
+ }
13607
13799
  };
13608
13800
 
13609
13801
  // src/server/bin.ts
@@ -13619,7 +13811,10 @@ var envSchema = import_v42.z.object({
13619
13811
  }).min(1, "POSTHOG_PERSONAL_API_KEY cannot be empty"),
13620
13812
  POSTHOG_PROJECT_ID: import_v42.z.string({
13621
13813
  error: "POSTHOG_PROJECT_ID is required for routing requests to the correct project"
13622
- }).regex(/^\d+$/, "POSTHOG_PROJECT_ID must be a numeric string").transform((val) => parseInt(val, 10))
13814
+ }).regex(/^\d+$/, "POSTHOG_PROJECT_ID must be a numeric string").transform((val) => parseInt(val, 10)),
13815
+ POSTHOG_CODE_RUNTIME_ADAPTER: import_v42.z.enum(["claude", "codex"]).optional(),
13816
+ POSTHOG_CODE_MODEL: import_v42.z.string().optional(),
13817
+ POSTHOG_CODE_REASONING_EFFORT: import_v42.z.enum(["low", "medium", "high", "max"]).optional()
13623
13818
  });
13624
13819
  var program = new import_commander.Command();
13625
13820
  function parseBooleanOption(raw, flag) {
@@ -13679,6 +13874,15 @@ ${errors}`);
13679
13874
  "--claudeCodeConfig"
13680
13875
  );
13681
13876
  const allowedDomains = options.allowedDomains ? options.allowedDomains.split(",").map((d) => d.trim()).filter(Boolean) : void 0;
13877
+ if (env.POSTHOG_CODE_RUNTIME_ADAPTER && env.POSTHOG_CODE_MODEL && env.POSTHOG_CODE_REASONING_EFFORT && !isSupportedReasoningEffort(
13878
+ env.POSTHOG_CODE_RUNTIME_ADAPTER,
13879
+ env.POSTHOG_CODE_MODEL,
13880
+ env.POSTHOG_CODE_REASONING_EFFORT
13881
+ )) {
13882
+ program.error(
13883
+ `POSTHOG_CODE_REASONING_EFFORT '${env.POSTHOG_CODE_REASONING_EFFORT}' is not supported for ${env.POSTHOG_CODE_RUNTIME_ADAPTER} model '${env.POSTHOG_CODE_MODEL}'.`
13884
+ );
13885
+ }
13682
13886
  const server = new AgentServer({
13683
13887
  port: parseInt(options.port, 10),
13684
13888
  jwtPublicKey: env.JWT_PUBLIC_KEY,
@@ -13693,7 +13897,10 @@ ${errors}`);
13693
13897
  mcpServers,
13694
13898
  baseBranch: options.baseBranch,
13695
13899
  claudeCode,
13696
- allowedDomains
13900
+ allowedDomains,
13901
+ runtimeAdapter: env.POSTHOG_CODE_RUNTIME_ADAPTER,
13902
+ model: env.POSTHOG_CODE_MODEL,
13903
+ reasoningEffort: env.POSTHOG_CODE_REASONING_EFFORT
13697
13904
  });
13698
13905
  process.on("SIGINT", async () => {
13699
13906
  await server.stop();