@posthog/agent 2.3.341 → 2.3.349

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.
@@ -3912,6 +3912,9 @@ function isSupportedReasoningEffort(adapter, modelId, value) {
3912
3912
  }
3913
3913
 
3914
3914
  // src/server/agent-server.ts
3915
+ var import_promises5 = require("fs/promises");
3916
+ var import_node_path8 = require("path");
3917
+ var import_node_url2 = require("url");
3915
3918
  var import_sdk5 = require("@agentclientprotocol/sdk");
3916
3919
  var import_node_server = require("@hono/node-server");
3917
3920
 
@@ -8698,7 +8701,7 @@ var import_hono = require("hono");
8698
8701
  // package.json
8699
8702
  var package_default = {
8700
8703
  name: "@posthog/agent",
8701
- version: "2.3.341",
8704
+ version: "2.3.349",
8702
8705
  repository: "https://github.com/PostHog/code",
8703
8706
  description: "TypeScript agent framework wrapping Claude Agent SDK with Git-based task execution for PostHog",
8704
8707
  exports: {
@@ -8788,6 +8791,7 @@ var package_default = {
8788
8791
  devDependencies: {
8789
8792
  "@posthog/shared": "workspace:*",
8790
8793
  "@posthog/git": "workspace:*",
8794
+ "@posthog/enricher": "workspace:*",
8791
8795
  "@types/bun": "latest",
8792
8796
  "@types/tar": "^6.1.13",
8793
8797
  msw: "^2.12.7",
@@ -8807,7 +8811,6 @@ var package_default = {
8807
8811
  "@opentelemetry/resources": "^2.0.0",
8808
8812
  "@opentelemetry/sdk-logs": "^0.208.0",
8809
8813
  "@opentelemetry/semantic-conventions": "^1.28.0",
8810
- "@posthog/enricher": "workspace:*",
8811
8814
  "@types/jsonwebtoken": "^9.0.10",
8812
8815
  commander: "^14.0.2",
8813
8816
  hono: "^4.11.7",
@@ -12503,16 +12506,12 @@ var BaseAcpAgent = class {
12503
12506
 
12504
12507
  // src/adapters/claude/conversion/acp-to-sdk.ts
12505
12508
  var path5 = __toESM(require("path"), 1);
12509
+ var import_node_url = require("url");
12506
12510
  function sdkText(value) {
12507
12511
  return { type: "text", text: value };
12508
12512
  }
12509
12513
  function formatUriAsLink(uri) {
12510
12514
  try {
12511
- if (uri.startsWith("file://")) {
12512
- const filePath = uri.slice(7);
12513
- const name2 = path5.basename(filePath) || filePath;
12514
- return `[@${name2}](${uri})`;
12515
- }
12516
12515
  if (uri.startsWith("zed://")) {
12517
12516
  const name2 = path5.basename(uri) || uri;
12518
12517
  return `[@${name2}](${uri})`;
@@ -12522,6 +12521,20 @@ function formatUriAsLink(uri) {
12522
12521
  return uri;
12523
12522
  }
12524
12523
  }
12524
+ function formatFileAttachment(uri) {
12525
+ try {
12526
+ const filePath = (0, import_node_url.fileURLToPath)(uri);
12527
+ const name2 = path5.basename(filePath) || filePath;
12528
+ return [
12529
+ "Attached file available in the workspace:",
12530
+ `- name: ${name2}`,
12531
+ `- path: ${filePath}`,
12532
+ "Use the available tools to inspect this file if needed."
12533
+ ].join("\n");
12534
+ } catch {
12535
+ return `Attached file available at ${uri}`;
12536
+ }
12537
+ }
12525
12538
  function transformMcpCommand(text2) {
12526
12539
  const mcpMatch = text2.match(/^\/mcp:([^:\s]+):(\S+)(\s+.*)?$/);
12527
12540
  if (mcpMatch) {
@@ -12536,7 +12549,11 @@ function processPromptChunk(chunk, content, context) {
12536
12549
  content.push(sdkText(transformMcpCommand(chunk.text)));
12537
12550
  break;
12538
12551
  case "resource_link":
12539
- content.push(sdkText(formatUriAsLink(chunk.uri)));
12552
+ content.push(
12553
+ sdkText(
12554
+ chunk.uri.startsWith("file://") ? formatFileAttachment(chunk.uri) : formatUriAsLink(chunk.uri)
12555
+ )
12556
+ );
12540
12557
  break;
12541
12558
  case "resource":
12542
12559
  if ("text" in chunk.resource) {
@@ -17387,32 +17404,21 @@ var PostHogAPIClient = class {
17387
17404
  );
17388
17405
  return response.artifacts ?? [];
17389
17406
  }
17390
- async getArtifactPresignedUrl(taskId, runId, storagePath) {
17407
+ /**
17408
+ * Download artifact content by storage path
17409
+ * Streams the file through the PostHog backend so the sandbox does not need
17410
+ * direct access to object storage.
17411
+ */
17412
+ async downloadArtifact(taskId, runId, storagePath) {
17391
17413
  const teamId = this.getTeamId();
17392
17414
  try {
17393
- const response = await this.apiRequest(
17394
- `/api/projects/${teamId}/tasks/${taskId}/runs/${runId}/artifacts/presign/`,
17415
+ const response = await this.performRequestWithRetry(
17416
+ `/api/projects/${teamId}/tasks/${taskId}/runs/${runId}/artifacts/download/`,
17395
17417
  {
17396
17418
  method: "POST",
17397
17419
  body: JSON.stringify({ storage_path: storagePath })
17398
17420
  }
17399
17421
  );
17400
- return response.url;
17401
- } catch {
17402
- return null;
17403
- }
17404
- }
17405
- /**
17406
- * Download artifact content by storage path
17407
- * Gets a presigned URL and fetches the content
17408
- */
17409
- async downloadArtifact(taskId, runId, storagePath) {
17410
- const url = await this.getArtifactPresignedUrl(taskId, runId, storagePath);
17411
- if (!url) {
17412
- return null;
17413
- }
17414
- try {
17415
- const response = await fetch(url);
17416
17422
  if (!response.ok) {
17417
17423
  throw new Error(`Failed to download artifact: ${response.status}`);
17418
17424
  }
@@ -18883,8 +18889,16 @@ var userMessageParamsSchema = import_v4.z.object({
18883
18889
  content: import_v4.z.union([
18884
18890
  import_v4.z.string().min(1, "Content is required"),
18885
18891
  import_v4.z.array(import_v4.z.record(import_v4.z.string(), import_v4.z.unknown())).min(1, "Content is required")
18886
- ])
18887
- });
18892
+ ]).optional(),
18893
+ artifacts: import_v4.z.array(import_v4.z.record(import_v4.z.string(), import_v4.z.unknown())).optional()
18894
+ }).refine(
18895
+ (params) => {
18896
+ const hasContent = typeof params.content === "string" ? params.content.trim().length > 0 : Array.isArray(params.content) && params.content.length > 0;
18897
+ const hasArtifacts = Array.isArray(params.artifacts) && params.artifacts.length > 0;
18898
+ return hasContent || hasArtifacts;
18899
+ },
18900
+ { error: "Either content or artifacts are required" }
18901
+ );
18888
18902
  var permissionResponseParamsSchema = import_v4.z.object({
18889
18903
  requestId: import_v4.z.string().min(1, "requestId is required"),
18890
18904
  optionId: import_v4.z.string().min(1, "optionId is required"),
@@ -19139,7 +19153,7 @@ var AgentServer = class _AgentServer {
19139
19153
  });
19140
19154
  },
19141
19155
  cancel: () => {
19142
- this.logger.info("SSE connection closed");
19156
+ this.logger.debug("SSE connection closed");
19143
19157
  if (this.session?.sseController) {
19144
19158
  this.session.sseController = null;
19145
19159
  }
@@ -19307,9 +19321,22 @@ var AgentServer = class _AgentServer {
19307
19321
  switch (method) {
19308
19322
  case POSTHOG_NOTIFICATIONS.USER_MESSAGE:
19309
19323
  case "user_message": {
19310
- const prompt = normalizeCloudPromptContent(
19311
- params.content
19312
- );
19324
+ this.logger.info("Received user_message command", {
19325
+ hasContent: typeof params.content === "string" ? params.content.trim().length > 0 : Array.isArray(params.content) && params.content.length > 0,
19326
+ artifactCount: Array.isArray(params.artifacts) ? params.artifacts.length : 0
19327
+ });
19328
+ const prompt = await this.buildPromptFromContentAndArtifacts({
19329
+ content: params.content,
19330
+ artifacts: Array.isArray(params.artifacts) ? params.artifacts : [],
19331
+ taskId: this.session.payload.task_id,
19332
+ runId: this.session.payload.run_id
19333
+ });
19334
+ if (prompt.length === 0) {
19335
+ throw new Error("User message cannot be empty");
19336
+ }
19337
+ this.logger.info("Built user_message prompt", {
19338
+ blockTypes: prompt.map((block) => block.type)
19339
+ });
19313
19340
  const promptPreview = promptBlocksToText(prompt);
19314
19341
  this.logger.info(
19315
19342
  `Processing user message (detectedPrUrl=${this.detectedPrUrl ?? "none"}): ${promptPreview.substring(0, 100)}...`
@@ -19667,7 +19694,7 @@ var AgentServer = class _AgentServer {
19667
19694
  try {
19668
19695
  const task = await this.posthogAPI.getTask(payload.task_id);
19669
19696
  const initialPromptOverride = taskRun ? this.getInitialPromptOverride(taskRun) : null;
19670
- const pendingUserPrompt = this.getPendingUserPrompt(taskRun);
19697
+ const pendingUserPrompt = await this.getPendingUserPrompt(taskRun);
19671
19698
  let initialPrompt = [];
19672
19699
  if (pendingUserPrompt?.length) {
19673
19700
  initialPrompt = pendingUserPrompt;
@@ -19694,6 +19721,7 @@ var AgentServer = class _AgentServer {
19694
19721
  this.logger.info("Initial task message completed", {
19695
19722
  stopReason: result.stopReason
19696
19723
  });
19724
+ await this.clearPendingInitialPromptState(payload, taskRun);
19697
19725
  if (result.stopReason === "end_turn") {
19698
19726
  void this.syncCloudBranchMetadata(payload);
19699
19727
  }
@@ -19715,7 +19743,7 @@ var AgentServer = class _AgentServer {
19715
19743
  const conversationSummary = this.formatConversationForResume(
19716
19744
  this.resumeState.conversation
19717
19745
  );
19718
- const pendingUserPrompt = this.getPendingUserPrompt(taskRun);
19746
+ const pendingUserPrompt = await this.getPendingUserPrompt(taskRun);
19719
19747
  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.`;
19720
19748
  let resumePromptBlocks;
19721
19749
  if (pendingUserPrompt?.length) {
@@ -19826,15 +19854,134 @@ ${toolSummary}`);
19826
19854
  const trimmed2 = override.trim();
19827
19855
  return trimmed2.length > 0 ? trimmed2 : null;
19828
19856
  }
19829
- getPendingUserPrompt(taskRun) {
19857
+ async getPendingUserPrompt(taskRun) {
19830
19858
  if (!taskRun) return null;
19831
19859
  const state = taskRun.state;
19832
19860
  const message = state?.pending_user_message;
19833
- if (typeof message !== "string") {
19861
+ const artifactIds = Array.isArray(state?.pending_user_artifact_ids) ? state.pending_user_artifact_ids.filter(
19862
+ (artifactId) => typeof artifactId === "string" && artifactId.trim().length > 0
19863
+ ) : [];
19864
+ const prompt = await this.buildPromptFromContentAndArtifacts({
19865
+ content: typeof message === "string" ? message : void 0,
19866
+ artifacts: this.getArtifactsById(taskRun.artifacts, artifactIds),
19867
+ taskId: taskRun.task,
19868
+ runId: taskRun.id
19869
+ });
19870
+ this.logger.info("Built pending user prompt", {
19871
+ hasMessage: typeof message === "string" && message.trim().length > 0,
19872
+ requestedArtifactCount: artifactIds.length,
19873
+ blockTypes: prompt.map((block) => block.type)
19874
+ });
19875
+ return prompt.length > 0 ? prompt : null;
19876
+ }
19877
+ getClearedPendingUserState(taskRun) {
19878
+ const state = taskRun?.state && typeof taskRun.state === "object" ? taskRun.state : null;
19879
+ if (!state) {
19834
19880
  return null;
19835
19881
  }
19836
- const prompt = deserializeCloudPrompt(message);
19837
- return prompt.length > 0 ? prompt : null;
19882
+ const pendingKeys = [
19883
+ "pending_user_message",
19884
+ "pending_user_artifact_ids",
19885
+ "pending_user_message_ts"
19886
+ ].filter((key) => key in state);
19887
+ return pendingKeys.length > 0 ? pendingKeys : null;
19888
+ }
19889
+ async clearPendingInitialPromptState(payload, taskRun) {
19890
+ const stateRemoveKeys = this.getClearedPendingUserState(taskRun);
19891
+ if (!stateRemoveKeys) {
19892
+ return;
19893
+ }
19894
+ await this.posthogAPI.updateTaskRun(payload.task_id, payload.run_id, {
19895
+ state_remove_keys: stateRemoveKeys
19896
+ });
19897
+ }
19898
+ async buildPromptFromContentAndArtifacts({
19899
+ content,
19900
+ artifacts,
19901
+ taskId,
19902
+ runId
19903
+ }) {
19904
+ const contentBlocks = content ? normalizeCloudPromptContent(content) : [];
19905
+ const artifactBlocks = await this.hydrateArtifactsToPrompt(
19906
+ taskId,
19907
+ runId,
19908
+ artifacts ?? []
19909
+ );
19910
+ return [...contentBlocks, ...artifactBlocks];
19911
+ }
19912
+ getArtifactsById(artifacts, artifactIds) {
19913
+ if (!artifacts?.length || artifactIds.length === 0) {
19914
+ return [];
19915
+ }
19916
+ const artifactsById = new Map(
19917
+ artifacts.filter(
19918
+ (artifact) => typeof artifact.id === "string" && artifact.id.trim().length > 0
19919
+ ).map((artifact) => [artifact.id, artifact])
19920
+ );
19921
+ return artifactIds.flatMap((artifactId) => {
19922
+ const artifact = artifactsById.get(artifactId);
19923
+ if (!artifact) {
19924
+ this.logger.warn("Pending artifact missing from run manifest", {
19925
+ artifactId
19926
+ });
19927
+ return [];
19928
+ }
19929
+ return [artifact];
19930
+ });
19931
+ }
19932
+ async hydrateArtifactsToPrompt(taskId, runId, artifacts) {
19933
+ if (artifacts.length === 0) {
19934
+ return [];
19935
+ }
19936
+ this.logger.debug("Hydrating prompt artifacts", {
19937
+ taskId,
19938
+ runId,
19939
+ artifactCount: artifacts.length,
19940
+ artifactNames: artifacts.map((artifact) => artifact.name)
19941
+ });
19942
+ return (await Promise.all(
19943
+ artifacts.map(
19944
+ (artifact) => this.hydrateArtifactToPromptBlock(taskId, runId, artifact)
19945
+ )
19946
+ )).flatMap((artifactBlock) => artifactBlock ? [artifactBlock] : []);
19947
+ }
19948
+ async hydrateArtifactToPromptBlock(taskId, runId, artifact) {
19949
+ if (!artifact.storage_path) {
19950
+ this.logger.warn("Skipping artifact without storage path", {
19951
+ taskId,
19952
+ runId,
19953
+ artifactName: artifact.name
19954
+ });
19955
+ return null;
19956
+ }
19957
+ const data = await this.posthogAPI.downloadArtifact(
19958
+ taskId,
19959
+ runId,
19960
+ artifact.storage_path
19961
+ );
19962
+ if (!data) {
19963
+ throw new Error(`Failed to download artifact ${artifact.name}`);
19964
+ }
19965
+ const safeName = this.getSafeArtifactName(artifact.name);
19966
+ const artifactDir = (0, import_node_path8.join)(
19967
+ this.config.repositoryPath ?? "/tmp/workspace",
19968
+ ".posthog",
19969
+ "attachments",
19970
+ runId,
19971
+ artifact.id ?? safeName
19972
+ );
19973
+ await (0, import_promises5.mkdir)(artifactDir, { recursive: true });
19974
+ const artifactPath = (0, import_node_path8.join)(artifactDir, safeName);
19975
+ await (0, import_promises5.writeFile)(artifactPath, Buffer.from(data));
19976
+ return resourceLink((0, import_node_url2.pathToFileURL)(artifactPath).toString(), artifact.name, {
19977
+ ...artifact.content_type ? { mimeType: artifact.content_type } : {},
19978
+ ...typeof artifact.size === "number" ? { size: artifact.size } : {}
19979
+ });
19980
+ }
19981
+ getSafeArtifactName(name2) {
19982
+ const baseName = (0, import_node_path8.basename)(name2).trim();
19983
+ const normalizedName = baseName.replace(/[^\w.-]/g, "_");
19984
+ return normalizedName.length > 0 ? normalizedName : "attachment";
19838
19985
  }
19839
19986
  getResumeRunId(taskRun) {
19840
19987
  const envRunId = process.env.POSTHOG_RESUME_RUN_ID;