@posthog/agent 2.3.647 → 2.3.656

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.
Files changed (44) hide show
  1. package/dist/adapters/claude/permissions/permission-options.js +700 -0
  2. package/dist/adapters/claude/permissions/permission-options.js.map +1 -1
  3. package/dist/adapters/claude/tools.js +700 -0
  4. package/dist/adapters/claude/tools.js.map +1 -1
  5. package/dist/adapters/codex/local-tools-mcp-server.d.ts +2 -0
  6. package/dist/adapters/codex/local-tools-mcp-server.js +1172 -0
  7. package/dist/adapters/codex/local-tools-mcp-server.js.map +1 -0
  8. package/dist/agent.js +1488 -219
  9. package/dist/agent.js.map +1 -1
  10. package/dist/execution-mode.js +700 -0
  11. package/dist/execution-mode.js.map +1 -1
  12. package/dist/handoff-checkpoint.js.map +1 -1
  13. package/dist/posthog-api.d.ts +1 -1
  14. package/dist/posthog-api.js +1 -1
  15. package/dist/posthog-api.js.map +1 -1
  16. package/dist/server/agent-server.js +1637 -342
  17. package/dist/server/agent-server.js.map +1 -1
  18. package/dist/server/bin.cjs +1553 -261
  19. package/dist/server/bin.cjs.map +1 -1
  20. package/dist/types.d.ts +1 -1
  21. package/dist/types.js.map +1 -1
  22. package/package.json +1 -1
  23. package/src/adapters/claude/claude-agent.ts +32 -2
  24. package/src/adapters/claude/hooks.test.ts +54 -0
  25. package/src/adapters/claude/hooks.ts +86 -0
  26. package/src/adapters/claude/mcp/local-tools.test.ts +50 -0
  27. package/src/adapters/claude/mcp/local-tools.ts +40 -0
  28. package/src/adapters/claude/session/options.ts +14 -9
  29. package/src/adapters/claude/types.ts +1 -0
  30. package/src/adapters/codex/codex-agent.ts +117 -22
  31. package/src/adapters/codex/local-tools-mcp-server.ts +71 -0
  32. package/src/adapters/local-tools/index.ts +22 -0
  33. package/src/adapters/local-tools/registry.test.ts +57 -0
  34. package/src/adapters/local-tools/registry.ts +81 -0
  35. package/src/adapters/local-tools/tools/signed-commit.ts +26 -0
  36. package/src/adapters/session-meta.ts +16 -0
  37. package/src/adapters/signed-commit-shared.ts +82 -0
  38. package/src/server/agent-server.configure-environment.test.ts +64 -1
  39. package/src/server/agent-server.test.ts +2 -4
  40. package/src/server/agent-server.ts +60 -35
  41. package/src/types.ts +2 -1
  42. package/src/utils/common.ts +14 -0
  43. package/src/utils/gateway.test.ts +70 -0
  44. package/src/utils/gateway.ts +31 -1
@@ -900,10 +900,8 @@ describe("AgentServer HTTP Mode", () => {
900
900
  expect(prompt).toContain(
901
901
  "gh pr checkout https://github.com/org/repo/pull/1",
902
902
  );
903
- expect(prompt).toContain(
904
- "Stage and commit all changes with a clear commit message",
905
- );
906
- expect(prompt).toContain("Push to the existing PR branch");
903
+ expect(prompt).toContain("git_signed_commit");
904
+ expect(prompt).toContain("Committing (signed commits required)");
907
905
  expect(prompt).not.toContain("Create a draft pull request");
908
906
  // Review-comment thread handling: reply + resolve
909
907
  expect(prompt).toContain("review thread");
@@ -25,6 +25,7 @@ import {
25
25
  type AgentErrorClassification,
26
26
  classifyAgentError,
27
27
  } from "../adapters/error-classification";
28
+ import { SIGNED_COMMIT_QUALIFIED_TOOL_NAME } from "../adapters/signed-commit-shared";
28
29
  import type { PermissionMode } from "../execution-mode";
29
30
  import { DEFAULT_CODEX_MODEL } from "../gateway-models";
30
31
  import { HandoffCheckpointTracker } from "../handoff-checkpoint";
@@ -47,7 +48,11 @@ import type {
47
48
  } from "../types";
48
49
  import { resourceLink } from "../utils/acp-content";
49
50
  import { AsyncMutex } from "../utils/async-mutex";
50
- import { type GatewayProduct, getLlmGatewayUrl } from "../utils/gateway";
51
+ import {
52
+ buildGatewayPropertyHeaders,
53
+ getLlmGatewayUrl,
54
+ resolveGatewayProduct,
55
+ } from "../utils/gateway";
51
56
  import { Logger } from "../utils/logger";
52
57
  import { logAgentshRuntimeInfo } from "./agentsh-runtime";
53
58
  import {
@@ -844,7 +849,13 @@ export class AgentServer {
844
849
  }),
845
850
  ]);
846
851
 
847
- this.configureEnvironment({ isInternal: preTask?.internal === true });
852
+ this.configureEnvironment({
853
+ isInternal: preTask?.internal === true,
854
+ originProduct: preTask?.origin_product,
855
+ taskId: payload.task_id,
856
+ taskRunId: payload.run_id,
857
+ taskUserId: payload.user_id,
858
+ });
848
859
 
849
860
  const prUrl = getTaskRunStateString(preTaskRun, "slack_notified_pr_url");
850
861
 
@@ -949,6 +960,7 @@ export class AgentServer {
949
960
  _meta: {
950
961
  sessionId: payload.run_id,
951
962
  taskRunId: payload.run_id,
963
+ taskId: payload.task_id,
952
964
  systemPrompt: sessionSystemPrompt,
953
965
  ...(this.config.model && { model: this.config.model }),
954
966
  allowedDomains: this.config.allowedDomains,
@@ -1599,24 +1611,21 @@ export class AgentServer {
1599
1611
  private buildCloudSystemPrompt(prUrl?: string | null): string {
1600
1612
  const taskId = this.config.taskId;
1601
1613
  const shouldAutoCreatePr = this.shouldAutoPublishCloudChanges();
1602
- const attributionInstructions = `
1603
- ## Attribution
1604
- Do NOT use Claude Code's default attribution (no "Co-Authored-By" trailers, no "Generated with [Claude Code]" lines).
1614
+ const signedCommitInstructions = `
1615
+ ## Committing (signed commits required)
1616
+ Commits MUST be signed. \`git commit\` and \`git push\` are blocked in this environment.
1617
+ To commit: stage your changes with \`git add\`, then call the \`git_signed_commit\` tool (full
1618
+ name \`${SIGNED_COMMIT_QUALIFIED_TOOL_NAME}\`) with a \`message\` (and optional \`body\`/\`paths\`).
1619
+ It creates a GitHub-signed ("Verified") commit on the branch and keeps your local checkout in
1620
+ sync. To start a new branch, pass \`branch\` (prefixed with \`posthog-code/\`) — the tool creates
1621
+ it on the remote for you.
1605
1622
 
1606
- If you create a commit, add the following trailers to the commit message (after a blank line at the end):
1623
+ ## Attribution
1624
+ Do NOT add "Co-Authored-By" trailers or "Generated with [Claude Code]" lines to your
1625
+ commit messages. The \`git_signed_commit\` tool automatically appends the only trailers
1626
+ we want:
1607
1627
  Generated-By: PostHog Code
1608
- Task-Id: ${taskId}
1609
-
1610
- Example:
1611
- \`\`\`
1612
- git commit -m "$(cat <<'EOF'
1613
- fix: resolve login redirect loop
1614
-
1615
- Generated-By: PostHog Code
1616
- Task-Id: ${taskId}
1617
- EOF
1618
- )"
1619
- \`\`\``;
1628
+ Task-Id: ${taskId}`;
1620
1629
 
1621
1630
  if (prUrl) {
1622
1631
  if (!shouldAutoCreatePr) {
@@ -1630,7 +1639,7 @@ Do the requested work, but stop with local changes ready for review.
1630
1639
  Important:
1631
1640
  - Do NOT create new commits, push to the branch, or update the pull request unless the user explicitly asks.
1632
1641
  - Do NOT create a new branch or a new pull request.
1633
- ${attributionInstructions}
1642
+ ${signedCommitInstructions}
1634
1643
  `;
1635
1644
  }
1636
1645
 
@@ -1641,9 +1650,8 @@ This task already has an open pull request: ${prUrl}
1641
1650
 
1642
1651
  After completing the requested changes:
1643
1652
  1. Check out the existing PR branch with \`gh pr checkout ${prUrl}\`
1644
- 2. Stage and commit all changes with a clear commit message
1645
- 3. Push to the existing PR branch
1646
- 4. For every PR review comment or review thread you addressed, treat the thread as done only after BOTH of these:
1653
+ 2. Stage your changes with \`git add\`, then call the \`git_signed_commit\` tool with a clear \`message\` (do NOT use \`git commit\`/\`git push\` — they are blocked). This commits to the existing PR branch.
1654
+ 3. For every PR review comment or review thread you addressed, treat the thread as done only after BOTH of these:
1647
1655
  - Reply on the thread with a short note describing what changed (reference the commit SHA when useful) using \`gh api -X POST /repos/{owner}/{repo}/pulls/{n}/comments/{id}/replies -f body='...'\`.
1648
1656
  - Resolve the thread via the \`resolveReviewThread\` GraphQL mutation: \`gh api graphql -f query='mutation($id:ID!){resolveReviewThread(input:{threadId:$id}){thread{isResolved}}}' -f id="<thread-node-id>"\`.
1649
1657
  List unresolved threads first with \`gh api graphql -f query='{repository(owner:"<owner>",name:"<repo>"){pullRequest(number:<n>){reviewThreads(first:100){nodes{id isResolved comments(first:1){nodes{body}}}}}}}'\` so you can resolve each one you fixed.
@@ -1651,7 +1659,7 @@ After completing the requested changes:
1651
1659
  Important:
1652
1660
  - Do NOT create a new branch or a new pull request.
1653
1661
  - Do NOT push fixes for review comments without replying to and resolving each related thread.
1654
- ${attributionInstructions}
1662
+ ${signedCommitInstructions}
1655
1663
  `;
1656
1664
  }
1657
1665
 
@@ -1666,7 +1674,7 @@ When the user asks for code changes:
1666
1674
  When the user explicitly asks to clone or work in a GitHub repository:
1667
1675
  - Clone the repository into /tmp/workspace/repos/<owner>/<repo> using \`gh repo clone <owner>/<repo> /tmp/workspace/repos/<owner>/<repo>\`
1668
1676
  - Work from inside that cloned repository for follow-up code changes
1669
- - If the user explicitly asks you to open or update a pull request, create a branch, commit the requested changes, push it, and open a draft pull request from inside the clone. Before opening the PR, check the cloned repo for a PR template at \`.github/pull_request_template.md\` (or variants; fall back to the org's \`.github\` repo via \`gh api\`) and use it as the body structure, and search for matching open issues with \`gh issue list --search\` to include \`Closes #<n>\` / \`Refs #<n>\` links.
1677
+ - If the user explicitly asks you to open or update a pull request, create a branch, stage your changes with \`git add\` and commit them with the \`git_signed_commit\` tool (do NOT use \`git commit\`/\`git push\` — they are blocked), and open a draft pull request from inside the clone. Before opening the PR, check the cloned repo for a PR template at \`.github/pull_request_template.md\` (or variants; fall back to the org's \`.github\` repo via \`gh api\`) and use it as the body structure, and search for matching open issues with \`gh issue list --search\` to include \`Closes #<n>\` / \`Refs #<n>\` links.
1670
1678
  - Do NOT create branches, commits, push changes, or open pull requests unless the user explicitly asks for that`;
1671
1679
 
1672
1680
  return `
@@ -1686,7 +1694,7 @@ ${publishInstructions}
1686
1694
 
1687
1695
  Important:
1688
1696
  - Prefer using MCP tools to answer questions with real data over giving generic advice.
1689
- ${attributionInstructions}
1697
+ ${signedCommitInstructions}
1690
1698
  `;
1691
1699
  }
1692
1700
 
@@ -1698,7 +1706,7 @@ Do the requested work, but stop with local changes ready for review.
1698
1706
 
1699
1707
  Important:
1700
1708
  - Do NOT create a branch, commit, push, or open a pull request unless the user explicitly asks.
1701
- ${attributionInstructions}
1709
+ ${signedCommitInstructions}
1702
1710
  `;
1703
1711
  }
1704
1712
 
@@ -1706,14 +1714,13 @@ ${attributionInstructions}
1706
1714
  # Cloud Task Execution
1707
1715
 
1708
1716
  After completing the requested changes:
1709
- 1. Create a new branch prefixed with \`posthog-code/\` (e.g. \`posthog-code/fix-login-redirect\`) based on the work done
1710
- 2. Stage and commit all changes with a clear commit message
1711
- 3. Push the branch to origin
1712
- 4. Before opening the PR, prepare the body:
1717
+ 1. Pick a new branch name prefixed with \`posthog-code/\` (e.g. \`posthog-code/fix-login-redirect\`)
1718
+ 2. Stage your changes with \`git add\`, then call the \`git_signed_commit\` tool with \`branch\` set to that name and a clear \`message\` (do NOT use \`git commit\`/\`git push\` — they are blocked). The tool creates the branch on the remote and a signed commit on it.
1719
+ 3. Before opening the PR, prepare the body:
1713
1720
  - Check the repo for a PR template at \`.github/pull_request_template.md\` (also try \`.github/PULL_REQUEST_TEMPLATE.md\`, \`docs/pull_request_template.md\`, and root variants). If one exists, use its exact section headings as the PR body — do NOT fall back to a generic Summary/Test plan format.
1714
1721
  - If no repo-level template exists, check the org's \`.github\` repo via \`gh api /repos/<owner>/.github/contents/.github/pull_request_template.md\` (and other common paths) and use that as a fallback.
1715
1722
  - Search for matching open issues with \`gh issue list --state open --search '<keywords>'\` (derive keywords from the branch name, commits, and changed files; \`gh issue view <n>\` to confirm relevance). For every issue this PR would resolve, include a \`Closes #<n>\` line in the body so GitHub auto-links and auto-closes it on merge. For issues that are related but not fully resolved, use \`Refs #<n>\` instead.
1716
- 5. Create a draft pull request using \`gh pr create --draft${this.config.baseBranch ? ` --base ${this.config.baseBranch}` : ""}\` with a descriptive title and the body prepared above. Add the following footer at the end of the PR description:
1723
+ 4. Create a draft pull request using \`gh pr create --draft${this.config.baseBranch ? ` --base ${this.config.baseBranch}` : ""}\` with a descriptive title and the body prepared above. Add the following footer at the end of the PR description:
1717
1724
  \`\`\`
1718
1725
  ---
1719
1726
  *Created with [PostHog Code](https://posthog.com/code?ref=pr)*
@@ -1721,7 +1728,7 @@ After completing the requested changes:
1721
1728
 
1722
1729
  Important:
1723
1730
  - Always create the PR as a draft. Do not ask for confirmation.
1724
- ${attributionInstructions}
1731
+ ${signedCommitInstructions}
1725
1732
  `;
1726
1733
  }
1727
1734
 
@@ -1804,18 +1811,35 @@ ${attributionInstructions}
1804
1811
 
1805
1812
  private configureEnvironment({
1806
1813
  isInternal = false,
1814
+ originProduct,
1815
+ taskId,
1816
+ taskRunId,
1817
+ taskUserId,
1807
1818
  }: {
1808
1819
  isInternal?: boolean;
1820
+ originProduct?: string | null;
1821
+ taskId?: string | null;
1822
+ taskRunId?: string | null;
1823
+ taskUserId?: number | null;
1809
1824
  } = {}): void {
1810
1825
  const { apiKey, apiUrl, projectId } = this.config;
1811
- const product: GatewayProduct = isInternal
1812
- ? "background_agents"
1813
- : "posthog_code";
1826
+ const product = resolveGatewayProduct({ isInternal, originProduct });
1814
1827
  const gatewayUrl =
1815
1828
  process.env.LLM_GATEWAY_URL || getLlmGatewayUrl(apiUrl, product);
1816
1829
  const openaiBaseUrl = gatewayUrl.endsWith("/v1")
1817
1830
  ? gatewayUrl
1818
1831
  : `${gatewayUrl}/v1`;
1832
+ // Forward task metadata as `x-posthog-property-*` headers so the gateway
1833
+ // lifts them onto the $ai_generation event. Routes through the Anthropic
1834
+ // SDK's ANTHROPIC_CUSTOM_HEADERS env var; the OpenAI/codex path has no
1835
+ // equivalent today.
1836
+ const customHeaders = buildGatewayPropertyHeaders({
1837
+ task_origin_product: originProduct,
1838
+ task_internal: isInternal,
1839
+ task_id: taskId,
1840
+ task_run_id: taskRunId,
1841
+ task_user_id: taskUserId,
1842
+ });
1819
1843
 
1820
1844
  Object.assign(process.env, {
1821
1845
  // PostHog
@@ -1828,6 +1852,7 @@ ${attributionInstructions}
1828
1852
  ANTHROPIC_API_KEY: apiKey,
1829
1853
  ANTHROPIC_AUTH_TOKEN: apiKey,
1830
1854
  ANTHROPIC_BASE_URL: gatewayUrl,
1855
+ ANTHROPIC_CUSTOM_HEADERS: customHeaders,
1831
1856
  // OpenAI (for models like GPT-4, o1, etc.)
1832
1857
  OPENAI_API_KEY: apiKey,
1833
1858
  OPENAI_BASE_URL: openaiBaseUrl,
package/src/types.ts CHANGED
@@ -37,7 +37,8 @@ export interface Task {
37
37
  | "eval_clusters"
38
38
  | "user_created"
39
39
  | "support_queue"
40
- | "session_summaries";
40
+ | "session_summaries"
41
+ | "signal_report";
41
42
  github_integration?: number | null;
42
43
  repository: string; // Format: "organization/repository" (e.g., "posthog/posthog-js")
43
44
  json_schema?: Record<string, unknown> | null; // JSON schema for task output validation
@@ -1,3 +1,4 @@
1
+ import { readGithubTokenFromEnv } from "@posthog/git/signed-commit";
1
2
  import type { Logger } from "./logger";
2
3
 
3
4
  /**
@@ -25,6 +26,19 @@ export const IS_ROOT =
25
26
 
26
27
  export const ALLOW_BYPASS = !IS_ROOT || !!process.env.IS_SANDBOX;
27
28
 
29
+ /**
30
+ * A cloud sandbox run, as opposed to a local desktop session. Cloud sandboxes
31
+ * always set IS_SANDBOX and carry a taskRunId; desktop sessions have neither.
32
+ */
33
+ export function isCloudRun(meta: { taskRunId?: string } | undefined): boolean {
34
+ return !!process.env.IS_SANDBOX || !!meta?.taskRunId;
35
+ }
36
+
37
+ /** The GitHub token available to the sandbox, if any. */
38
+ export function resolveGithubToken(): string | undefined {
39
+ return readGithubTokenFromEnv();
40
+ }
41
+
28
42
  export function unreachable(value: never, logger: Logger): void {
29
43
  let valueAsString: string;
30
44
  try {
@@ -0,0 +1,70 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { buildGatewayPropertyHeaders, resolveGatewayProduct } from "./gateway";
3
+
4
+ describe("resolveGatewayProduct", () => {
5
+ it.each([
6
+ { isInternal: false, originProduct: undefined, expected: "posthog_code" },
7
+ {
8
+ isInternal: undefined,
9
+ originProduct: undefined,
10
+ expected: "posthog_code",
11
+ },
12
+ {
13
+ isInternal: false,
14
+ originProduct: "signal_report",
15
+ expected: "posthog_code",
16
+ },
17
+ {
18
+ isInternal: true,
19
+ originProduct: undefined,
20
+ expected: "background_agents",
21
+ },
22
+ {
23
+ isInternal: true,
24
+ originProduct: "session_summaries",
25
+ expected: "background_agents",
26
+ },
27
+ { isInternal: true, originProduct: "signal_report", expected: "signals" },
28
+ ] as const)(
29
+ "isInternal=$isInternal originProduct=$originProduct -> $expected",
30
+ ({ isInternal, originProduct, expected }) => {
31
+ expect(resolveGatewayProduct({ isInternal, originProduct })).toBe(
32
+ expected,
33
+ );
34
+ },
35
+ );
36
+ });
37
+
38
+ describe("buildGatewayPropertyHeaders", () => {
39
+ it("renders each property as an x-posthog-property header line", () => {
40
+ expect(
41
+ buildGatewayPropertyHeaders({
42
+ task_origin_product: "signal_report",
43
+ task_internal: true,
44
+ }),
45
+ ).toBe(
46
+ "x-posthog-property-task_origin_product: signal_report\nx-posthog-property-task_internal: true",
47
+ );
48
+ });
49
+
50
+ it("drops null and undefined values but keeps falsy primitives", () => {
51
+ expect(
52
+ buildGatewayPropertyHeaders({
53
+ task_origin_product: null,
54
+ task_internal: false,
55
+ task_count: 0,
56
+ }),
57
+ ).toBe(
58
+ "x-posthog-property-task_internal: false\nx-posthog-property-task_count: 0",
59
+ );
60
+ });
61
+
62
+ it("returns an empty string when no usable properties remain", () => {
63
+ expect(
64
+ buildGatewayPropertyHeaders({
65
+ task_origin_product: null,
66
+ task_internal: undefined,
67
+ }),
68
+ ).toBe("");
69
+ });
70
+ });
@@ -1,4 +1,34 @@
1
- export type GatewayProduct = "posthog_code" | "background_agents";
1
+ export type GatewayProduct = "posthog_code" | "background_agents" | "signals";
2
+
3
+ export function resolveGatewayProduct({
4
+ isInternal,
5
+ originProduct,
6
+ }: {
7
+ isInternal?: boolean;
8
+ originProduct?: string | null;
9
+ } = {}): GatewayProduct {
10
+ if (isInternal) {
11
+ return originProduct === "signal_report" ? "signals" : "background_agents";
12
+ }
13
+ return "posthog_code";
14
+ }
15
+
16
+ /**
17
+ * Build `x-posthog-property-<name>: <value>` header lines that the LLM
18
+ * gateway lifts onto the `$ai_generation` event it captures for each call
19
+ * (see `services/llm-gateway/src/llm_gateway/request_context.py`).
20
+ *
21
+ * Returns a newline-joined string ready for `ANTHROPIC_CUSTOM_HEADERS`.
22
+ * `null`/`undefined` property values are dropped.
23
+ */
24
+ export function buildGatewayPropertyHeaders(
25
+ properties: Record<string, string | number | boolean | null | undefined>,
26
+ ): string {
27
+ return Object.entries(properties)
28
+ .filter(([, value]) => value !== null && value !== undefined)
29
+ .map(([key, value]) => `x-posthog-property-${key}: ${value}`)
30
+ .join("\n");
31
+ }
2
32
 
3
33
  function getGatewayBaseUrl(posthogHost: string): string {
4
34
  const url = new URL(posthogHost);