@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.
- package/dist/agent.js +30 -27
- package/dist/agent.js.map +1 -1
- package/dist/posthog-api.d.ts +5 -3
- package/dist/posthog-api.js +10 -21
- package/dist/posthog-api.js.map +1 -1
- package/dist/server/agent-server.d.ts +7 -0
- package/dist/server/agent-server.js +186 -39
- package/dist/server/agent-server.js.map +1 -1
- package/dist/server/bin.cjs +186 -39
- package/dist/server/bin.cjs.map +1 -1
- package/dist/types.d.ts +3 -1
- package/package.json +4 -4
- package/src/adapters/claude/conversion/acp-to-sdk.test.ts +49 -0
- package/src/adapters/claude/conversion/acp-to-sdk.ts +23 -6
- package/src/posthog-api.test.ts +32 -0
- package/src/posthog-api.ts +13 -30
- package/src/server/agent-server.test.ts +96 -0
- package/src/server/agent-server.ts +207 -11
- package/src/server/schemas.test.ts +10 -0
- package/src/server/schemas.ts +25 -6
- package/src/test/mocks/msw-handlers.ts +4 -1
- package/src/types.ts +4 -1
package/dist/server/bin.cjs
CHANGED
|
@@ -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.
|
|
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(
|
|
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
|
-
|
|
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.
|
|
17394
|
-
`/api/projects/${teamId}/tasks/${taskId}/runs/${runId}/artifacts/
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
19837
|
-
|
|
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;
|