@posthog/agent 2.3.261 → 2.3.263

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.
@@ -5683,7 +5683,7 @@ var import_hono = require("hono");
5683
5683
  // package.json
5684
5684
  var package_default = {
5685
5685
  name: "@posthog/agent",
5686
- version: "2.3.261",
5686
+ version: "2.3.263",
5687
5687
  repository: "https://github.com/PostHog/code",
5688
5688
  description: "TypeScript agent framework wrapping Claude Agent SDK with Git-based task execution for PostHog",
5689
5689
  exports: {
@@ -5844,7 +5844,9 @@ var POSTHOG_NOTIFICATIONS = {
5844
5844
  /** Marks a boundary for log compaction */
5845
5845
  COMPACT_BOUNDARY: "_posthog/compact_boundary",
5846
5846
  /** Token usage update for a session turn */
5847
- USAGE_UPDATE: "_posthog/usage_update"
5847
+ USAGE_UPDATE: "_posthog/usage_update",
5848
+ /** Response to a relayed permission request (plan approval, question) */
5849
+ PERMISSION_RESPONSE: "_posthog/permission_response"
5848
5850
  };
5849
5851
  function isNotification(method, notification) {
5850
5852
  if (!method) return false;
@@ -8281,6 +8283,11 @@ async function canUseTool(context) {
8281
8283
  if (planFileResult) {
8282
8284
  return planFileResult;
8283
8285
  }
8286
+ if (session.permissionMode === "plan") {
8287
+ const message = `This tool is not available in plan mode. Write your plan to a file in ${getClaudePlansDir()} and call ExitPlanMode when ready.`;
8288
+ await emitToolDenial(context, message);
8289
+ return { behavior: "deny", message, interrupt: false };
8290
+ }
8284
8291
  return handleDefaultPermissionFlow(context);
8285
8292
  }
8286
8293
 
@@ -12219,13 +12226,27 @@ var userMessageParamsSchema = import_v4.z.object({
12219
12226
  import_v4.z.array(import_v4.z.record(import_v4.z.string(), import_v4.z.unknown())).min(1, "Content is required")
12220
12227
  ])
12221
12228
  });
12229
+ var permissionResponseParamsSchema = import_v4.z.object({
12230
+ requestId: import_v4.z.string().min(1, "requestId is required"),
12231
+ optionId: import_v4.z.string().min(1, "optionId is required"),
12232
+ customInput: import_v4.z.string().optional(),
12233
+ answers: import_v4.z.record(import_v4.z.string(), import_v4.z.string()).optional()
12234
+ });
12235
+ var setConfigOptionParamsSchema = import_v4.z.object({
12236
+ configId: import_v4.z.string().min(1, "configId is required"),
12237
+ value: import_v4.z.string().min(1, "value is required")
12238
+ });
12222
12239
  var commandParamsSchemas = {
12223
12240
  user_message: userMessageParamsSchema,
12224
12241
  "posthog/user_message": userMessageParamsSchema,
12225
12242
  cancel: import_v4.z.object({}).optional(),
12226
12243
  "posthog/cancel": import_v4.z.object({}).optional(),
12227
12244
  close: import_v4.z.object({}).optional(),
12228
- "posthog/close": import_v4.z.object({}).optional()
12245
+ "posthog/close": import_v4.z.object({}).optional(),
12246
+ permission_response: permissionResponseParamsSchema,
12247
+ "posthog/permission_response": permissionResponseParamsSchema,
12248
+ set_config_option: setConfigOptionParamsSchema,
12249
+ "posthog/set_config_option": setConfigOptionParamsSchema
12229
12250
  };
12230
12251
  function validateCommandParams(method, params) {
12231
12252
  const schema = commandParamsSchemas[method] ?? commandParamsSchemas[method.replace("posthog/", "")];
@@ -12342,6 +12363,7 @@ var AgentServer = class _AgentServer {
12342
12363
  // causing a second session to be created and duplicate Slack messages to be sent.
12343
12364
  initializationPromise = null;
12344
12365
  pendingEvents = [];
12366
+ pendingPermissions = /* @__PURE__ */ new Map();
12345
12367
  detachSseController(controller) {
12346
12368
  if (this.session?.sseController === controller) {
12347
12369
  this.session.sseController = null;
@@ -12379,6 +12401,9 @@ var AgentServer = class _AgentServer {
12379
12401
  getEffectiveMode(payload) {
12380
12402
  return payload.mode ?? this.config.mode;
12381
12403
  }
12404
+ getSessionPermissionMode() {
12405
+ return this.session?.permissionMode ?? "default";
12406
+ }
12382
12407
  createApp() {
12383
12408
  const app = new import_hono.Hono();
12384
12409
  app.get("/health", (c) => {
@@ -12423,6 +12448,7 @@ var AgentServer = class _AgentServer {
12423
12448
  await this.initializeSession(payload, sseController);
12424
12449
  } else {
12425
12450
  this.session.sseController = sseController;
12451
+ this.session.hasDesktopConnected = true;
12426
12452
  this.replayPendingEvents();
12427
12453
  }
12428
12454
  this.sendSseEvent(sseController, {
@@ -12662,6 +12688,43 @@ var AgentServer = class _AgentServer {
12662
12688
  await this.cleanupSession();
12663
12689
  return { closed: true };
12664
12690
  }
12691
+ case "posthog/set_config_option":
12692
+ case "set_config_option": {
12693
+ const configId = params.configId;
12694
+ const value = params.value;
12695
+ this.logger.info("Set config option requested", { configId, value });
12696
+ const result = await this.session.clientConnection.setSessionConfigOption({
12697
+ sessionId: this.session.acpSessionId,
12698
+ configId,
12699
+ value
12700
+ });
12701
+ return {
12702
+ configOptions: result.configOptions
12703
+ };
12704
+ }
12705
+ case POSTHOG_NOTIFICATIONS.PERMISSION_RESPONSE:
12706
+ case "permission_response": {
12707
+ const requestId = params.requestId;
12708
+ const optionId = params.optionId;
12709
+ const customInput = params.customInput;
12710
+ const answers = params.answers;
12711
+ this.logger.info("Permission response received", {
12712
+ requestId,
12713
+ optionId
12714
+ });
12715
+ const resolved = this.resolvePermission(
12716
+ requestId,
12717
+ optionId,
12718
+ customInput,
12719
+ answers
12720
+ );
12721
+ if (!resolved) {
12722
+ throw new Error(
12723
+ `No pending permission request found for id: ${requestId}`
12724
+ );
12725
+ }
12726
+ return { resolved: true };
12727
+ }
12665
12728
  default:
12666
12729
  throw new Error(`Unknown method: ${method}`);
12667
12730
  }
@@ -12780,6 +12843,8 @@ var AgentServer = class _AgentServer {
12780
12843
  if (prUrl) {
12781
12844
  this.detectedPrUrl = prUrl;
12782
12845
  }
12846
+ const runState = preTaskRun?.state;
12847
+ const initialPermissionMode = typeof runState?.initial_permission_mode === "string" ? runState.initial_permission_mode : "bypassPermissions";
12783
12848
  const sessionResponse = await clientConnection.newSession({
12784
12849
  cwd: this.config.repositoryPath ?? "/tmp/workspace",
12785
12850
  mcpServers: this.config.mcpServers ?? [],
@@ -12789,6 +12854,7 @@ var AgentServer = class _AgentServer {
12789
12854
  systemPrompt: this.buildSessionSystemPrompt(prUrl),
12790
12855
  allowedDomains: this.config.allowedDomains,
12791
12856
  jsonSchema: preTask?.json_schema ?? null,
12857
+ permissionMode: initialPermissionMode,
12792
12858
  ...this.config.claudeCode?.plugins?.length && {
12793
12859
  claudeCode: {
12794
12860
  options: {
@@ -12811,7 +12877,9 @@ var AgentServer = class _AgentServer {
12811
12877
  treeTracker,
12812
12878
  sseController,
12813
12879
  deviceInfo,
12814
- logWriter
12880
+ logWriter,
12881
+ permissionMode: initialPermissionMode,
12882
+ hasDesktopConnected: sseController !== null
12815
12883
  };
12816
12884
  this.logger = new Logger({
12817
12885
  debug: true,
@@ -12825,6 +12893,7 @@ var AgentServer = class _AgentServer {
12825
12893
  this.logger.info(
12826
12894
  `Agent version: ${this.config.version ?? package_default.version}`
12827
12895
  );
12896
+ this.logger.info(`Initial permission mode: ${initialPermissionMode}`);
12828
12897
  this.posthogAPI.updateTaskRun(payload.task_id, payload.run_id, {
12829
12898
  status: "in_progress"
12830
12899
  }).catch(
@@ -13315,14 +13384,16 @@ ${attributionInstructions}
13315
13384
  this.logger.debug("Permission request", {
13316
13385
  mode,
13317
13386
  interactionOrigin,
13387
+ kind: params.toolCall?.kind,
13318
13388
  options: params.options
13319
13389
  });
13320
13390
  const allowOption = params.options.find(
13321
13391
  (o) => o.kind === "allow_once" || o.kind === "allow_always"
13322
13392
  );
13323
13393
  const selectedOptionId = allowOption?.optionId ?? params.options[0].optionId;
13394
+ const codeToolKind = params.toolCall?._meta?.codeToolKind;
13395
+ const isPlanApproval = params.toolCall?.kind === "switch_mode";
13324
13396
  if (interactionOrigin === "slack") {
13325
- const codeToolKind = params.toolCall?._meta?.codeToolKind;
13326
13397
  if (codeToolKind === "question") {
13327
13398
  return this.buildSlackQuestionRelayResponse(
13328
13399
  payload,
@@ -13330,6 +13401,19 @@ ${attributionInstructions}
13330
13401
  );
13331
13402
  }
13332
13403
  }
13404
+ {
13405
+ const isQuestion = codeToolKind === "question";
13406
+ const sessionPermissionMode = this.getSessionPermissionMode();
13407
+ const needsRelay = isQuestion || isPlanApproval || sessionPermissionMode === "default";
13408
+ if (needsRelay && this.session?.hasDesktopConnected) {
13409
+ this.logger.info("Relaying permission to connected client", {
13410
+ kind: params.toolCall?.kind,
13411
+ isQuestion,
13412
+ sessionPermissionMode
13413
+ });
13414
+ return this.relayPermissionToClient(params);
13415
+ }
13416
+ }
13333
13417
  if (this.shouldBlockPublishPermission(params)) {
13334
13418
  return {
13335
13419
  outcome: { outcome: "cancelled" },
@@ -13349,6 +13433,12 @@ ${attributionInstructions}
13349
13433
  this.logger.debug("Extension notification", { method, params });
13350
13434
  },
13351
13435
  sessionUpdate: async (params) => {
13436
+ if (params.update?.sessionUpdate === "current_mode_update" && typeof params.update?.currentModeId === "string" && this.session) {
13437
+ this.session.permissionMode = params.update.currentModeId;
13438
+ this.logger.info("Permission mode updated", {
13439
+ mode: params.update.currentModeId
13440
+ });
13441
+ }
13352
13442
  if (params.update?.sessionUpdate === "tool_call_update") {
13353
13443
  const meta = params.update?._meta?.claudeCode;
13354
13444
  const toolName = meta?.toolName;
@@ -13527,6 +13617,13 @@ ${attributionInstructions}
13527
13617
  } catch (error) {
13528
13618
  this.logger.error("Failed to flush session logs", error);
13529
13619
  }
13620
+ for (const [, pending] of this.pendingPermissions) {
13621
+ pending.resolve({
13622
+ outcome: { outcome: "selected", optionId: "reject" },
13623
+ _meta: { customInput: "Session is shutting down." }
13624
+ });
13625
+ }
13626
+ this.pendingPermissions.clear();
13530
13627
  try {
13531
13628
  await this.session.acpConnection.cleanup();
13532
13629
  } catch (error) {
@@ -13604,6 +13701,39 @@ ${attributionInstructions}
13604
13701
  this.detachSseController(controller);
13605
13702
  }
13606
13703
  }
13704
+ /**
13705
+ * Relay a permission request (e.g., plan approval) to the connected desktop
13706
+ * app via SSE and wait for a response via the `/command` endpoint.
13707
+ *
13708
+ * The promise waits indefinitely — if SSE is disconnected, the event is
13709
+ * buffered by broadcastEvent and replayed when the client reconnects. Session
13710
+ * cleanup force-resolves all pending permissions, so there is no leak.
13711
+ */
13712
+ relayPermissionToClient(params) {
13713
+ const requestId = crypto.randomUUID();
13714
+ this.broadcastEvent({
13715
+ type: "permission_request",
13716
+ requestId,
13717
+ options: params.options,
13718
+ toolCall: params.toolCall
13719
+ });
13720
+ return new Promise((resolve4) => {
13721
+ this.pendingPermissions.set(requestId, { resolve: resolve4 });
13722
+ });
13723
+ }
13724
+ resolvePermission(requestId, optionId, customInput, answers) {
13725
+ const pending = this.pendingPermissions.get(requestId);
13726
+ if (!pending) return false;
13727
+ this.pendingPermissions.delete(requestId);
13728
+ const meta = {};
13729
+ if (customInput) meta.customInput = customInput;
13730
+ if (answers) meta.answers = answers;
13731
+ pending.resolve({
13732
+ outcome: { outcome: "selected", optionId },
13733
+ ...Object.keys(meta).length > 0 ? { _meta: meta } : {}
13734
+ });
13735
+ return true;
13736
+ }
13607
13737
  };
13608
13738
 
13609
13739
  // src/server/bin.ts