@posthog/agent 2.3.46 → 2.3.62

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.
@@ -904,7 +904,7 @@ var import_hono = require("hono");
904
904
  // package.json
905
905
  var package_default = {
906
906
  name: "@posthog/agent",
907
- version: "2.3.46",
907
+ version: "2.3.62",
908
908
  repository: "https://github.com/PostHog/code",
909
909
  description: "TypeScript agent framework wrapping Claude Agent SDK with Git-based task execution for PostHog",
910
910
  exports: {
@@ -1729,6 +1729,14 @@ function isMcpToolReadOnly(toolName) {
1729
1729
  const metadata = mcpToolMetadataCache.get(toolName);
1730
1730
  return metadata?.readOnly === true;
1731
1731
  }
1732
+ function getConnectedMcpServerNames() {
1733
+ const names = /* @__PURE__ */ new Set();
1734
+ for (const key of mcpToolMetadataCache.keys()) {
1735
+ const parts = key.split("__");
1736
+ if (parts.length >= 3) names.add(parts[1]);
1737
+ }
1738
+ return [...names];
1739
+ }
1732
1740
 
1733
1741
  // src/adapters/claude/conversion/tool-use-to-acp.ts
1734
1742
  var SYSTEM_REMINDER_REGEX = /\s*<system-reminder>[\s\S]*?<\/system-reminder>/g;
@@ -2485,7 +2493,10 @@ function handleToolResultChunk(chunk, ctx) {
2485
2493
  toolCallId: chunk.tool_use_id,
2486
2494
  sessionUpdate: "tool_call_update",
2487
2495
  status: chunk.is_error ? "failed" : "completed",
2488
- rawOutput: chunk.content,
2496
+ rawOutput: ctx.mcpToolUseResult ? { ...ctx.mcpToolUseResult, isError: chunk.is_error ?? false } : {
2497
+ content: Array.isArray(chunk.content) ? chunk.content : typeof chunk.content === "string" ? [{ type: "text", text: chunk.content }] : [],
2498
+ isError: chunk.is_error ?? false
2499
+ },
2489
2500
  ...toolUpdate
2490
2501
  });
2491
2502
  return updates;
@@ -2539,7 +2550,7 @@ function processContentChunk(chunk, role, ctx) {
2539
2550
  return [];
2540
2551
  }
2541
2552
  }
2542
- function toAcpNotifications(content, role, sessionId, toolUseCache, fileContentCache, client, logger, parentToolCallId, registerHooks, supportsTerminalOutput, cwd) {
2553
+ function toAcpNotifications(content, role, sessionId, toolUseCache, fileContentCache, client, logger, parentToolCallId, registerHooks, supportsTerminalOutput, cwd, mcpToolUseResult) {
2543
2554
  if (typeof content === "string") {
2544
2555
  const update = {
2545
2556
  sessionUpdate: messageUpdateType(role),
@@ -2563,7 +2574,8 @@ function toAcpNotifications(content, role, sessionId, toolUseCache, fileContentC
2563
2574
  parentToolCallId,
2564
2575
  registerHooks,
2565
2576
  supportsTerminalOutput,
2566
- cwd
2577
+ cwd,
2578
+ mcpToolUseResult
2567
2579
  };
2568
2580
  const output = [];
2569
2581
  for (const chunk of content) {
@@ -2822,6 +2834,7 @@ async function handleUserAssistantMessage(message, context) {
2822
2834
  const content = message.message.content;
2823
2835
  const contentToProcess = message.type === "assistant" ? filterMessageContent(content) : content;
2824
2836
  const parentToolCallId = "parent_tool_use_id" in message ? message.parent_tool_use_id ?? void 0 : void 0;
2837
+ const mcpToolUseResult = message.type === "user" && message.tool_use_result != null ? message.tool_use_result : void 0;
2825
2838
  for (const notification of toAcpNotifications(
2826
2839
  contentToProcess,
2827
2840
  message.message.role,
@@ -2833,7 +2846,8 @@ async function handleUserAssistantMessage(message, context) {
2833
2846
  parentToolCallId,
2834
2847
  context.registerHooks,
2835
2848
  context.supportsTerminalOutput,
2836
- session.cwd
2849
+ session.cwd,
2850
+ mcpToolUseResult
2837
2851
  )) {
2838
2852
  await client.sessionUpdate(notification);
2839
2853
  session.notificationHistory.push(notification);
@@ -4673,11 +4687,17 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
4673
4687
  * Both populate caches used later — neither is needed to return configOptions.
4674
4688
  */
4675
4689
  deferBackgroundFetches(q) {
4690
+ this.logger.info("Starting background fetches (commands + MCP metadata)");
4676
4691
  Promise.all([
4677
4692
  new Promise((resolve4) => setTimeout(resolve4, 10)).then(
4678
4693
  () => this.sendAvailableCommandsUpdate()
4679
4694
  ),
4680
- fetchMcpToolMetadata(q, this.logger)
4695
+ fetchMcpToolMetadata(q, this.logger).then(() => {
4696
+ const serverNames = getConnectedMcpServerNames();
4697
+ if (serverNames.length > 0) {
4698
+ this.options?.onMcpServersReady?.(serverNames);
4699
+ }
4700
+ })
4681
4701
  ]).catch(
4682
4702
  (err) => this.logger.error("Background fetch failed", { error: err })
4683
4703
  );
@@ -11108,7 +11128,7 @@ var SessionLogWriter = class _SessionLogWriter {
11108
11128
  taskId: context.taskId,
11109
11129
  runId: context.runId
11110
11130
  });
11111
- this.sessions.set(sessionId, { context });
11131
+ this.sessions.set(sessionId, { context, currentTurnMessages: [] });
11112
11132
  this.lastFlushAttemptTime.set(sessionId, Date.now());
11113
11133
  if (this.localCachePath) {
11114
11134
  const sessionDir = import_node_path7.default.join(
@@ -11155,6 +11175,7 @@ var SessionLogWriter = class _SessionLogWriter {
11155
11175
  const nonChunkAgentText = this.extractAgentMessageText(message);
11156
11176
  if (nonChunkAgentText) {
11157
11177
  session.lastAgentMessage = nonChunkAgentText;
11178
+ session.currentTurnMessages.push(nonChunkAgentText);
11158
11179
  }
11159
11180
  const entry = {
11160
11181
  type: "notification",
@@ -11251,6 +11272,7 @@ var SessionLogWriter = class _SessionLogWriter {
11251
11272
  const { text: text2, firstTimestamp } = session.chunkBuffer;
11252
11273
  session.chunkBuffer = void 0;
11253
11274
  session.lastAgentMessage = text2;
11275
+ session.currentTurnMessages.push(text2);
11254
11276
  const entry = {
11255
11277
  type: "notification",
11256
11278
  timestamp: firstTimestamp,
@@ -11276,6 +11298,17 @@ var SessionLogWriter = class _SessionLogWriter {
11276
11298
  getLastAgentMessage(sessionId) {
11277
11299
  return this.sessions.get(sessionId)?.lastAgentMessage;
11278
11300
  }
11301
+ getFullAgentResponse(sessionId) {
11302
+ const session = this.sessions.get(sessionId);
11303
+ if (!session || session.currentTurnMessages.length === 0) return void 0;
11304
+ return session.currentTurnMessages.join("\n\n");
11305
+ }
11306
+ resetTurnMessages(sessionId) {
11307
+ const session = this.sessions.get(sessionId);
11308
+ if (session) {
11309
+ session.currentTurnMessages = [];
11310
+ }
11311
+ }
11279
11312
  extractAgentMessageText(message) {
11280
11313
  if (message.method !== "session/update") {
11281
11314
  return null;
@@ -11591,6 +11624,12 @@ var AgentServer = class _AgentServer {
11591
11624
  questionRelayedToSlack = false;
11592
11625
  detectedPrUrl = null;
11593
11626
  resumeState = null;
11627
+ // Guards against concurrent session initialization. autoInitializeSession() and
11628
+ // the GET /events SSE handler can both call initializeSession() — the SSE connection
11629
+ // often arrives while newSession() is still awaited (this.session is still null),
11630
+ // causing a second session to be created and duplicate Slack messages to be sent.
11631
+ initializationPromise = null;
11632
+ pendingEvents = [];
11594
11633
  emitConsoleLog = (level, _scope, message, data) => {
11595
11634
  if (!this.session) return;
11596
11635
  const formatted = data !== void 0 ? `${message} ${JSON.stringify(data)}` : message;
@@ -11670,6 +11709,7 @@ var AgentServer = class _AgentServer {
11670
11709
  await this.initializeSession(payload, sseController);
11671
11710
  } else {
11672
11711
  this.session.sseController = sseController;
11712
+ this.replayPendingEvents();
11673
11713
  }
11674
11714
  this.sendSseEvent(sseController, {
11675
11715
  type: "connected",
@@ -11849,6 +11889,7 @@ var AgentServer = class _AgentServer {
11849
11889
  this.logger.info(
11850
11890
  `Processing user message (detectedPrUrl=${this.detectedPrUrl ?? "none"}): ${content.substring(0, 100)}...`
11851
11891
  );
11892
+ this.session.logWriter.resetTurnMessages(this.session.payload.run_id);
11852
11893
  const result = await this.session.clientConnection.prompt({
11853
11894
  sessionId: this.session.acpSessionId,
11854
11895
  prompt: [{ type: "text", text: content }],
@@ -11864,7 +11905,24 @@ You MUST NOT create a new branch, close the existing PR, or create a new PR.`
11864
11905
  }
11865
11906
  });
11866
11907
  this.broadcastTurnComplete(result.stopReason);
11867
- return { stopReason: result.stopReason };
11908
+ if (result.stopReason === "end_turn") {
11909
+ this.relayAgentResponse(this.session.payload).catch(
11910
+ (err) => this.logger.warn("Failed to relay follow-up response", err)
11911
+ );
11912
+ }
11913
+ let assistantMessage;
11914
+ try {
11915
+ await this.session.logWriter.flush(this.session.payload.run_id);
11916
+ assistantMessage = this.session.logWriter.getFullAgentResponse(
11917
+ this.session.payload.run_id
11918
+ );
11919
+ } catch {
11920
+ this.logger.warn("Failed to extract assistant message from logs");
11921
+ }
11922
+ return {
11923
+ stopReason: result.stopReason,
11924
+ ...assistantMessage && { assistant_message: assistantMessage }
11925
+ };
11868
11926
  }
11869
11927
  case POSTHOG_NOTIFICATIONS.CANCEL:
11870
11928
  case "cancel": {
@@ -11887,6 +11945,28 @@ You MUST NOT create a new branch, close the existing PR, or create a new PR.`
11887
11945
  }
11888
11946
  }
11889
11947
  async initializeSession(payload, sseController) {
11948
+ if (this.initializationPromise) {
11949
+ this.logger.info("Waiting for in-progress initialization", {
11950
+ runId: payload.run_id
11951
+ });
11952
+ await this.initializationPromise;
11953
+ if (this.session && sseController) {
11954
+ this.session.sseController = sseController;
11955
+ this.replayPendingEvents();
11956
+ }
11957
+ return;
11958
+ }
11959
+ this.initializationPromise = this._doInitializeSession(
11960
+ payload,
11961
+ sseController
11962
+ );
11963
+ try {
11964
+ await this.initializationPromise;
11965
+ } finally {
11966
+ this.initializationPromise = null;
11967
+ }
11968
+ }
11969
+ async _doInitializeSession(payload, sseController) {
11890
11970
  if (this.session) {
11891
11971
  await this.cleanupSession();
11892
11972
  }
@@ -12073,6 +12153,7 @@ You MUST NOT create a new branch, close the existing PR, or create a new PR.`
12073
12153
  descriptionLength: initialPrompt.length,
12074
12154
  usedInitialPromptOverride: !!initialPromptOverride
12075
12155
  });
12156
+ this.session.logWriter.resetTurnMessages(payload.run_id);
12076
12157
  const result = await this.session.clientConnection.prompt({
12077
12158
  sessionId: this.session.acpSessionId,
12078
12159
  prompt: [{ type: "text", text: initialPrompt }]
@@ -12099,7 +12180,7 @@ You MUST NOT create a new branch, close the existing PR, or create a new PR.`
12099
12180
  this.resumeState.conversation
12100
12181
  );
12101
12182
  const pendingUserMessage = this.getPendingUserMessage(taskRun);
12102
- const sandboxContext = this.resumeState.snapshotApplied ? `The sandbox environment (all files, packages, and code changes) has been fully restored from a snapshot.` : `The sandbox could not be restored from a snapshot (it may have expired). You are starting with a fresh environment but have the full conversation history below.`;
12183
+ const sandboxContext = this.resumeState.snapshotApplied ? `The workspace environment (all files, packages, and code changes) has been fully restored from where you left off.` : `The workspace files from the previous session were not restored (the file snapshot may have expired), so you are starting with a fresh environment. Your conversation history is fully preserved below.`;
12103
12184
  let resumePrompt;
12104
12185
  if (pendingUserMessage) {
12105
12186
  resumePrompt = `You are resuming a previous conversation. ${sandboxContext}
@@ -12130,6 +12211,7 @@ Continue from where you left off. The user is waiting for your response.`;
12130
12211
  snapshotApplied: this.resumeState.snapshotApplied
12131
12212
  });
12132
12213
  this.resumeState = null;
12214
+ this.session.logWriter.resetTurnMessages(payload.run_id);
12133
12215
  const result = await this.session.clientConnection.prompt({
12134
12216
  sessionId: this.session.acpSessionId,
12135
12217
  prompt: [{ type: "text", text: resumePrompt }]
@@ -12138,6 +12220,9 @@ Continue from where you left off. The user is waiting for your response.`;
12138
12220
  stopReason: result.stopReason
12139
12221
  });
12140
12222
  this.broadcastTurnComplete(result.stopReason);
12223
+ if (result.stopReason === "end_turn") {
12224
+ await this.relayAgentResponse(payload);
12225
+ }
12141
12226
  } catch (error) {
12142
12227
  this.logger.error("Failed to send resume message", error);
12143
12228
  if (this.session) {
@@ -12236,6 +12321,26 @@ Important:
12236
12321
  - Do NOT create a new branch or a new pull request.
12237
12322
  - Do NOT add "Co-Authored-By" trailers to commit messages.
12238
12323
  - Do NOT add "Generated with [Claude Code]" or similar attribution lines to PR descriptions.
12324
+ `;
12325
+ }
12326
+ if (!this.config.repositoryPath) {
12327
+ return `
12328
+ # Cloud Task Execution \u2014 No Repository Mode
12329
+
12330
+ You are a helpful assistant with access to PostHog via MCP tools. You can help with both code tasks and data/analytics questions.
12331
+
12332
+ When the user asks about analytics, data, metrics, events, funnels, dashboards, feature flags, experiments, or anything PostHog-related:
12333
+ - Use your PostHog MCP tools to query data, search insights, and provide real answers
12334
+ - Do NOT tell the user to check an external analytics platform \u2014 you ARE the analytics platform
12335
+ - Use tools like insight-query, query-run, event-definitions-list, and others to answer questions directly
12336
+
12337
+ When the user asks for code changes or software engineering tasks:
12338
+ - Let them know you can help but don't have a repository connected for this session
12339
+ - Offer to write code snippets, scripts, or provide guidance
12340
+
12341
+ Important:
12342
+ - Do NOT create branches, commits, or pull requests in this mode.
12343
+ - Prefer using MCP tools to answer questions with real data over giving generic advice.
12239
12344
  `;
12240
12345
  }
12241
12346
  return `
@@ -12338,6 +12443,9 @@ Important:
12338
12443
  }
12339
12444
  };
12340
12445
  },
12446
+ extNotification: async (method, params) => {
12447
+ this.logger.debug("Extension notification", { method, params });
12448
+ },
12341
12449
  sessionUpdate: async (params) => {
12342
12450
  if (params.update?.sessionUpdate === "tool_call_update") {
12343
12451
  const meta = params.update?._meta?.claudeCode;
@@ -12370,7 +12478,7 @@ Important:
12370
12478
  error
12371
12479
  });
12372
12480
  }
12373
- const message = this.session.logWriter.getLastAgentMessage(payload.run_id);
12481
+ const message = this.session.logWriter.getFullAgentResponse(payload.run_id);
12374
12482
  if (!message) {
12375
12483
  this.logger.warn("No agent message found for Slack relay", {
12376
12484
  taskId: payload.task_id,
@@ -12523,6 +12631,7 @@ Important:
12523
12631
  if (this.session.sseController) {
12524
12632
  this.session.sseController.close();
12525
12633
  }
12634
+ this.pendingEvents = [];
12526
12635
  this.session = null;
12527
12636
  }
12528
12637
  async captureTreeState() {
@@ -12571,6 +12680,16 @@ Important:
12571
12680
  broadcastEvent(event) {
12572
12681
  if (this.session?.sseController) {
12573
12682
  this.sendSseEvent(this.session.sseController, event);
12683
+ } else if (this.session) {
12684
+ this.pendingEvents.push(event);
12685
+ }
12686
+ }
12687
+ replayPendingEvents() {
12688
+ if (!this.session?.sseController || this.pendingEvents.length === 0) return;
12689
+ const events = this.pendingEvents;
12690
+ this.pendingEvents = [];
12691
+ for (const event of events) {
12692
+ this.sendSseEvent(this.session.sseController, event);
12574
12693
  }
12575
12694
  }
12576
12695
  sendSseEvent(controller, data) {