From d40b1d4da82f7823f40d9998ce90901046d95ee2 Mon Sep 17 00:00:00 2001 From: Charles Vien Date: Mon, 21 Sep 2026 15:18:07 -0700 Subject: [PATCH 01/17] Isolate agent-facing constants in shared --- .../packages/shared/src/bedrock-gateway.ts | 22 ++++++++++++++++ .../desktop/packages/shared/src/constants.ts | 7 +----- .../desktop/packages/shared/src/flags.test.ts | 11 ++++++++ products/desktop/packages/shared/src/flags.ts | 25 ++++--------------- .../packages/shared/src/sandbox-env.ts | 6 +++++ 5 files changed, 45 insertions(+), 26 deletions(-) create mode 100644 products/desktop/packages/shared/src/bedrock-gateway.ts create mode 100644 products/desktop/packages/shared/src/flags.test.ts create mode 100644 products/desktop/packages/shared/src/sandbox-env.ts diff --git a/products/desktop/packages/shared/src/bedrock-gateway.ts b/products/desktop/packages/shared/src/bedrock-gateway.ts new file mode 100644 index 000000000000..1fddf51ff949 --- /dev/null +++ b/products/desktop/packages/shared/src/bedrock-gateway.ts @@ -0,0 +1,22 @@ +/** + * Serves a session's Claude traffic from Bedrock instead of Anthropic. The + * `test` variant sends `x-posthog-provider: bedrock`, which the gateway routes + * to its Bedrock backend; `control` sends nothing and the gateway keeps its + * `anthropic` default. + * + * The variants differ in resilience, not just in provider. `control` keeps the + * gateway's Bedrock *failover* (`x-posthog-use-bedrock-fallback`), which retries + * against Bedrock when Anthropic returns 5xx/429 or blocks on billing. `test` + * cannot use it: the gateway dispatches on the provider header and returns + * before reading the fallback one, and its direct-Bedrock path has no reverse + * fallback to Anthropic. So a Bedrock outage fails a `test` session outright. + * + * The key is a literal because the agent runtime reads it and must not depend + * on the desktop flag registry. It must match `feature-flag-keys.json`. + */ +export const BEDROCK_LLM_GATEWAY_FLAG = "bedrock-llm-gateway"; + +/** Variants of {@link BEDROCK_LLM_GATEWAY_FLAG}. */ +export const BEDROCK_GATEWAY_VARIANTS = ["test", "control"] as const; + +export type BedrockGatewayVariant = (typeof BEDROCK_GATEWAY_VARIANTS)[number]; diff --git a/products/desktop/packages/shared/src/constants.ts b/products/desktop/packages/shared/src/constants.ts index 200ca0426c88..4a19c4b3bee6 100644 --- a/products/desktop/packages/shared/src/constants.ts +++ b/products/desktop/packages/shared/src/constants.ts @@ -4,12 +4,7 @@ export const SELF_DRIVING_SETUP_TASK_FLAG = featureFlagKeys.SELF_DRIVING_SETUP_TASK_FLAG; export const POSTHOG_CODE_INTERNAL_CHILD_ENV = "POSTHOG_CODE_INTERNAL_CHILD"; -/** - * Names the tools a custom sandbox image carries. The image spec builder - * writes it into the image's env; the agent reads it at session start, since - * nothing else tells it what was installed. - */ -export const IMAGE_TOOLS_ENV_KEY = "POSTHOG_IMAGE_TOOLS"; +export { IMAGE_TOOLS_ENV_KEY } from "./sandbox-env"; // Mirrors --color-background (dark) in packages/ui globals.css, for surfaces // that cannot read CSS variables: the Electron window and the boot error screen. diff --git a/products/desktop/packages/shared/src/flags.test.ts b/products/desktop/packages/shared/src/flags.test.ts new file mode 100644 index 000000000000..58c9f618b7a9 --- /dev/null +++ b/products/desktop/packages/shared/src/flags.test.ts @@ -0,0 +1,11 @@ +import { describe, expect, it } from "vitest"; +import { BEDROCK_LLM_GATEWAY_FLAG } from "./bedrock-gateway"; +import featureFlagKeys from "./feature-flag-keys.json" with { type: "json" }; + +describe("flags", () => { + it("keeps the Bedrock gateway flag literal in step with the flag registry", () => { + expect(BEDROCK_LLM_GATEWAY_FLAG).toBe( + featureFlagKeys.BEDROCK_LLM_GATEWAY_FLAG, + ); + }); +}); diff --git a/products/desktop/packages/shared/src/flags.ts b/products/desktop/packages/shared/src/flags.ts index e0476543643b..6df215a219ff 100644 --- a/products/desktop/packages/shared/src/flags.ts +++ b/products/desktop/packages/shared/src/flags.ts @@ -79,26 +79,11 @@ export const CHANNEL_REPORTS_FLAG = featureFlagKeys.CHANNEL_REPORTS_FLAG; */ export const TRIAGE_FOCUS_FLAG = featureFlagKeys.TRIAGE_FOCUS_FLAG; -/** - * Serves a session's Claude traffic from Bedrock instead of Anthropic. The - * `test` variant sends `x-posthog-provider: bedrock`, which the gateway routes - * to its Bedrock backend; `control` sends nothing and the gateway keeps its - * `anthropic` default. - * - * The variants differ in resilience, not just in provider. `control` keeps the - * gateway's Bedrock *failover* (`x-posthog-use-bedrock-fallback`), which retries - * against Bedrock when Anthropic returns 5xx/429 or blocks on billing. `test` - * cannot use it: the gateway dispatches on the provider header and returns - * before reading the fallback one, and its direct-Bedrock path has no reverse - * fallback to Anthropic. So a Bedrock outage fails a `test` session outright. - */ -export const BEDROCK_LLM_GATEWAY_FLAG = - featureFlagKeys.BEDROCK_LLM_GATEWAY_FLAG; - -/** Variants of {@link BEDROCK_LLM_GATEWAY_FLAG}. */ -export const BEDROCK_GATEWAY_VARIANTS = ["test", "control"] as const; - -export type BedrockGatewayVariant = (typeof BEDROCK_GATEWAY_VARIANTS)[number]; +export { + BEDROCK_GATEWAY_VARIANTS, + BEDROCK_LLM_GATEWAY_FLAG, + type BedrockGatewayVariant, +} from "./bedrock-gateway"; /** Gates the organization context wiki: the Context explorer in the nav rails. */ export const CONTEXT_LAYER_FLAG = featureFlagKeys.CONTEXT_LAYER_FLAG; /** Gates the "set up this space for" step in the create-space flow. */ diff --git a/products/desktop/packages/shared/src/sandbox-env.ts b/products/desktop/packages/shared/src/sandbox-env.ts new file mode 100644 index 000000000000..9a8512eb9fce --- /dev/null +++ b/products/desktop/packages/shared/src/sandbox-env.ts @@ -0,0 +1,6 @@ +/** + * Names the tools a custom sandbox image carries. The image spec builder + * writes it into the image's env; the agent reads it at session start, since + * nothing else tells it what was installed. + */ +export const IMAGE_TOOLS_ENV_KEY = "POSTHOG_IMAGE_TOOLS"; From 2400f43e56791b2cd2c86d16f8f214cc6876ddf5 Mon Sep 17 00:00:00 2001 From: Charles Vien Date: Mon, 21 Sep 2026 15:32:46 -0700 Subject: [PATCH 02/17] Split agent contracts out of shared --- .agents/skills/adding-inbox-sources/SKILL.md | 4 +- .claude/rules/task-model-catalog.md | 4 +- .github/workflows/ci-python.yml | 4 +- .../desktop-agent-release-verify.yml | 2 +- .github/workflows/desktop-build-test.yml | 6 +- .github/workflows/desktop-release.yml | 6 +- .github/workflows/desktop-update-e2e.yml | 2 +- docs/internal/sandboxes-setup-guide.md | 2 +- posthog/object_tags/kinds.py | 2 +- posthog/object_tags/projection.py | 2 +- products/desktop/AGENTS.md | 6 +- .../apps/code/scripts/build-linux-docker.sh | 2 +- products/desktop/apps/code/vite.shared.mts | 14 + products/desktop/apps/mobile/metro.config.js | 32 +- products/desktop/apps/web/vite.aliases.ts | 1 + products/desktop/biome.jsonc | 28 +- products/desktop/knip.json | 5 + .../packages/agent-contracts/package.json | 36 + .../src/adapter.ts | 0 .../src/agent-actions.test.ts | 0 .../src/agent-actions.ts | 0 .../src/agent-conversation.ts | 0 .../src/agent-runtime.ts | 0 .../src/api-http-client.test.ts | 0 .../src/api-http-client.ts | 0 .../src/backoff.test.ts | 0 .../src/backoff.ts | 0 .../src/bedrock-gateway.ts | 3 +- .../src/binary.test.ts | 0 .../{shared => agent-contracts}/src/binary.ts | 0 .../src/call-tool-result.test.ts | 0 .../src/call-tool-result.ts | 0 .../src/cloud-prompt.test.ts | 0 .../src/cloud-prompt.ts | 0 .../src/cloud-task-models.test.ts | 0 .../src/cloud-task-models.ts | 0 .../src/custom-cloud.test.ts | 0 .../src/custom-cloud.ts | 0 .../src/dismissal-reasons.test.ts | 0 .../src/dismissal-reasons.ts | 0 .../src/domain-types.test.ts | 0 .../agent-contracts/src/domain-types.ts | 1221 ++++++++++++++++ .../src/enrichment.ts | 2 +- .../src/errors.test.ts | 0 .../{shared => agent-contracts}/src/errors.ts | 0 .../src/exec-types.ts | 0 .../src/execution-modes.test.ts | 0 .../src/execution-modes.ts | 0 .../src/git-types.ts | 0 .../src/image.test.ts | 0 .../{shared => agent-contracts}/src/image.ts | 0 .../src/inbox-types.test.ts | 0 .../src/inbox-types.ts | 0 .../packages/agent-contracts/src/index.ts | 299 ++++ .../src/local-mcp-domain.ts | 0 .../src/mcp-tool-policy-domain.ts | 0 .../src/model-catalog.generated.ts | 0 .../src/model-catalog.test.ts | 0 .../agent-contracts/src/model-catalog.ts | 146 ++ .../src/models.test.ts | 0 .../{shared => agent-contracts}/src/models.ts | 0 .../src/oauth.test.ts | 0 .../{shared => agent-contracts}/src/oauth.ts | 0 .../src/objectTagKinds.generated.ts | 0 .../src/orchestration.ts | 0 .../src/pi-tool-call.test.ts | 0 .../src/pi-tool-call.ts | 0 .../src/posthog-products.ts | 0 .../src/posthog-property-headers.test.ts | 0 .../src/posthog-property-headers.ts | 223 +++ .../src/pr-urls.test.ts | 0 .../src/pr-urls.ts | 0 .../src/private-network.test.ts | 0 .../src/private-network.ts | 0 .../src/product-engineer-prompt.test.ts | 0 .../src/product-engineer-prompt.ts | 47 + .../src/reasoning-effort.test.ts | 0 .../src/reasoning-effort.ts | 0 .../src/regions.test.ts | 0 .../src/regions.ts | 0 .../src/rich-output-prompt.test.ts | 0 .../agent-contracts/src/rich-output-prompt.ts | 38 + .../{shared => agent-contracts}/src/saga.ts | 0 .../src/sandbox-env.ts | 0 .../packages/agent-contracts/src/schemas.ts | 44 + .../src/session-events.ts | 0 .../src/signal-types.ts | 0 .../src/skills.test.ts | 0 .../{shared => agent-contracts}/src/skills.ts | 0 .../src/slash-commands.test.ts | 0 .../src/slash-commands.ts | 0 .../agent-contracts/src/task-context.ts | 14 + .../src/task.test.ts | 0 .../{shared => agent-contracts}/src/task.ts | 0 .../src/tool-meta.test.ts | 0 .../src/tool-meta.ts | 0 .../src/transcript-neutral-messages.ts | 0 .../{shared => agent-contracts}/src/urls.ts | 0 .../packages/agent-contracts/tsconfig.json | 23 + .../packages/agent-contracts/tsup.config.ts | 13 + .../packages/agent-contracts/vitest.config.ts | 12 + products/desktop/packages/agent/e2e/config.ts | 2 +- .../agent/e2e/session-startup.e2e.test.ts | 2 +- products/desktop/packages/agent/package.json | 2 +- .../agent/src/adapters/acp-connection.ts | 2 +- .../agent/src/adapters/base-acp-agent.ts | 2 +- .../agent/src/adapters/claude/claude-agent.ts | 2 +- .../adapters/claude/conversion/acp-to-sdk.ts | 4 +- .../adapters/claude/conversion/sdk-to-acp.ts | 2 +- .../src/adapters/claude/image-sanitization.ts | 2 +- .../src/adapters/claude/session/mcp-config.ts | 4 +- .../adapters/claude/session/models.test.ts | 5 +- .../src/adapters/claude/session/models.ts | 6 +- .../src/adapters/claude/session/options.ts | 16 +- .../agent/src/adapters/claude/types.ts | 4 +- .../adapters/codex-app-server/approvals.ts | 2 +- .../codex-app-server-agent.ts | 12 +- .../src/adapters/codex-app-server/mapping.ts | 2 +- .../src/adapters/codex-app-server/models.ts | 6 +- .../codex-app-server/session-config.test.ts | 2 +- .../codex-app-server/session-config.ts | 6 +- .../src/adapters/error-classification.ts | 2 +- .../agent/src/adapters/reasoning-effort.ts | 4 +- .../agent/src/adapters/session-meta.ts | 2 +- products/desktop/packages/agent/src/agent.ts | 6 +- .../packages/agent/src/execution-mode.ts | 7 +- .../packages/agent/src/gateway-models.ts | 6 +- .../src/pi/conversation/toolTranslator.ts | 2 +- .../pi/conversation/tools/bashTranslator.ts | 2 +- .../pi/conversation/tools/editTranslator.ts | 2 +- .../pi/conversation/tools/findTranslator.ts | 2 +- .../pi/conversation/tools/grepTranslator.ts | 2 +- .../src/pi/conversation/tools/lsTranslator.ts | 2 +- .../pi/conversation/tools/readTranslator.ts | 2 +- .../conversation/translatePiConversation.ts | 5 +- .../src/pi/conversation/translatePiMessage.ts | 10 +- .../agent/src/pi/remote-rpc-client.ts | 2 +- .../packages/agent/src/pi/rpc-client.test.ts | 2 +- .../packages/agent/src/pi/rpc-client.ts | 8 +- .../desktop/packages/agent/src/pi/rpc-host.ts | 8 +- .../desktop/packages/agent/src/pi/runtime.ts | 2 +- .../packages/agent/src/posthog-api.test.ts | 2 +- .../desktop/packages/agent/src/posthog-api.ts | 2 +- .../agent/src/sagas/resume-saga.test.ts | 2 +- .../packages/agent/src/sagas/resume-saga.ts | 2 +- .../packages/agent/src/sagas/test-fixtures.ts | 2 +- .../agent/src/server/agent-server.test.ts | 5 +- .../packages/agent/src/server/agent-server.ts | 36 +- .../desktop/packages/agent/src/server/bin.ts | 5 +- .../packages/agent/src/server/cloud-prompt.ts | 5 +- .../src/server/cloud-session-prompt.test.ts | 4 +- .../packages/agent/src/server/gateway-env.ts | 2 +- .../agent/src/server/mcp-relay-server.ts | 2 +- .../agent/src/server/pi-agent-server.test.ts | 2 +- .../agent/src/server/pi-agent-server.ts | 16 +- .../packages/agent/src/server/schemas.ts | 2 +- .../packages/agent/src/server/types.ts | 7 +- .../packages/agent/src/session-log-writer.ts | 2 +- .../packages/agent/src/smoke/run-turn.ts | 2 +- products/desktop/packages/agent/src/types.ts | 10 +- .../packages/agent/src/utils/gateway.ts | 2 +- .../packages/agent/src/utils/streams.ts | 2 +- .../desktop/packages/agent/tsup.config.ts | 4 +- .../desktop/packages/enricher/package.json | 2 +- .../packages/enricher/src/serialize.ts | 4 +- .../desktop/packages/enricher/src/types.ts | 2 +- products/desktop/packages/git/package.json | 2 +- products/desktop/packages/git/src/git-saga.ts | 2 +- products/desktop/packages/git/src/queries.ts | 2 +- .../desktop/packages/git/src/sagas/clone.ts | 2 +- products/desktop/packages/git/src/worktree.ts | 2 +- .../desktop/packages/harness/package.json | 2 +- .../agent-instructions/instructions.test.ts | 2 +- .../harness/src/extensions/benjamin/ste100.ts | 2 +- .../extensions/enrichment/extension.test.ts | 2 +- .../src/extensions/enrichment/extension.ts | 2 +- .../extensions/enrichment/file-enricher.ts | 2 +- .../local-tools/task-tools-client.ts | 2 +- .../local-tools/tools/artifact-upload.ts | 2 +- .../local-tools/tools/show-actions.ts | 2 +- .../local-tools/tools/upload-artifact.ts | 2 +- .../harness/src/extensions/mcp/tool-bridge.ts | 2 +- .../src/extensions/orchestration/run-agent.ts | 2 +- .../orchestration/tools/subagent-tool.test.ts | 2 +- .../orchestration/tools/subagent-tool.ts | 2 +- .../orchestration/ui/subagent-render.ts | 2 +- .../orchestration/ui/workflow-render.ts | 2 +- .../posthog-mcp-policy/extension.test.ts | 2 +- .../posthog-mcp-policy/extension.ts | 2 +- .../extensions/posthog-mcp-policy/products.ts | 5 +- .../extensions/posthog-provider/gateway.ts | 2 +- .../posthog-provider/model-catalog.test.ts | 2 +- .../posthog-provider/model-catalog.ts | 10 +- .../src/extensions/posthog-provider/models.ts | 4 +- .../extensions/posthog-provider/oauth.test.ts | 2 +- .../src/extensions/posthog-provider/oauth.ts | 2 +- .../posthog-provider/provider.test.ts | 2 +- .../extensions/posthog-provider/provider.ts | 2 +- .../product-engineer/extension.test.ts | 2 +- .../extensions/product-engineer/extension.ts | 2 +- .../skills-store/store-skills.test.ts | 2 +- .../extensions/skills-store/store-skills.ts | 2 +- .../task-system-prompt/extension.test.ts | 2 +- .../task-system-prompt/extension.ts | 2 +- .../extensions/task-system-prompt/prompt.ts | 2 +- .../src/extensions/web-access/web-fetch.ts | 7 +- products/desktop/packages/shared/package.json | 1 + .../packages/shared/src/analytics-events.ts | 6 +- .../desktop/packages/shared/src/constants.ts | 2 +- .../packages/shared/src/dismissalReasons.ts | 2 +- .../packages/shared/src/domain-types.ts | 1222 +---------------- .../desktop/packages/shared/src/flags.test.ts | 2 +- products/desktop/packages/shared/src/flags.ts | 2 +- products/desktop/packages/shared/src/index.ts | 299 +--- .../packages/shared/src/model-catalog.ts | 147 +- .../shared/src/posthog-property-headers.ts | 224 +-- .../shared/src/product-engineer-prompt.ts | 48 +- .../packages/shared/src/refund-reasons.ts | 2 +- .../packages/shared/src/rich-output-prompt.ts | 39 +- .../desktop/packages/shared/src/schemas.ts | 45 +- .../desktop/packages/shared/src/sessions.ts | 11 +- .../packages/shared/src/task-context.ts | 15 +- .../shared/src/task-creation-domain.ts | 12 +- products/desktop/packages/shared/src/types.ts | 6 +- .../desktop/packages/shared/src/video.test.ts | 2 +- products/desktop/packages/shared/src/video.ts | 2 +- .../packages/ui/src/features/inbox/AGENTS.md | 2 +- products/desktop/pnpm-lock.yaml | 42 +- products/signals/backend/views.py | 2 +- .../frontend/inbox/utils/dismissalReasons.ts | 2 +- .../backend/logic/services/docker_sandbox.py | 4 +- .../backend/logic/services/local_packages.py | 4 +- .../logic/services/test_docker_sandbox.py | 4 +- products/tasks/backend/model_catalog.py | 2 +- .../sandbox/images/Dockerfile.sandbox-local | 2 +- .../tasks/scripts/model_catalog_projection.py | 4 +- .../hogli_commands/projections.py | 4 +- 237 files changed, 2499 insertions(+), 2292 deletions(-) create mode 100644 products/desktop/packages/agent-contracts/package.json rename products/desktop/packages/{shared => agent-contracts}/src/adapter.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/agent-actions.test.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/agent-actions.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/agent-conversation.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/agent-runtime.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/api-http-client.test.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/api-http-client.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/backoff.test.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/backoff.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/bedrock-gateway.ts (86%) rename products/desktop/packages/{shared => agent-contracts}/src/binary.test.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/binary.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/call-tool-result.test.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/call-tool-result.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/cloud-prompt.test.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/cloud-prompt.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/cloud-task-models.test.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/cloud-task-models.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/custom-cloud.test.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/custom-cloud.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/dismissal-reasons.test.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/dismissal-reasons.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/domain-types.test.ts (100%) create mode 100644 products/desktop/packages/agent-contracts/src/domain-types.ts rename products/desktop/packages/{shared => agent-contracts}/src/enrichment.ts (94%) rename products/desktop/packages/{shared => agent-contracts}/src/errors.test.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/errors.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/exec-types.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/execution-modes.test.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/execution-modes.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/git-types.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/image.test.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/image.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/inbox-types.test.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/inbox-types.ts (100%) create mode 100644 products/desktop/packages/agent-contracts/src/index.ts rename products/desktop/packages/{shared => agent-contracts}/src/local-mcp-domain.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/mcp-tool-policy-domain.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/model-catalog.generated.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/model-catalog.test.ts (100%) create mode 100644 products/desktop/packages/agent-contracts/src/model-catalog.ts rename products/desktop/packages/{shared => agent-contracts}/src/models.test.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/models.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/oauth.test.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/oauth.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/objectTagKinds.generated.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/orchestration.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/pi-tool-call.test.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/pi-tool-call.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/posthog-products.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/posthog-property-headers.test.ts (100%) create mode 100644 products/desktop/packages/agent-contracts/src/posthog-property-headers.ts rename products/desktop/packages/{shared => agent-contracts}/src/pr-urls.test.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/pr-urls.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/private-network.test.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/private-network.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/product-engineer-prompt.test.ts (100%) create mode 100644 products/desktop/packages/agent-contracts/src/product-engineer-prompt.ts rename products/desktop/packages/{shared => agent-contracts}/src/reasoning-effort.test.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/reasoning-effort.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/regions.test.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/regions.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/rich-output-prompt.test.ts (100%) create mode 100644 products/desktop/packages/agent-contracts/src/rich-output-prompt.ts rename products/desktop/packages/{shared => agent-contracts}/src/saga.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/sandbox-env.ts (100%) create mode 100644 products/desktop/packages/agent-contracts/src/schemas.ts rename products/desktop/packages/{shared => agent-contracts}/src/session-events.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/signal-types.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/skills.test.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/skills.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/slash-commands.test.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/slash-commands.ts (100%) create mode 100644 products/desktop/packages/agent-contracts/src/task-context.ts rename products/desktop/packages/{shared => agent-contracts}/src/task.test.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/task.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/tool-meta.test.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/tool-meta.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/transcript-neutral-messages.ts (100%) rename products/desktop/packages/{shared => agent-contracts}/src/urls.ts (100%) create mode 100644 products/desktop/packages/agent-contracts/tsconfig.json create mode 100644 products/desktop/packages/agent-contracts/tsup.config.ts create mode 100644 products/desktop/packages/agent-contracts/vitest.config.ts diff --git a/.agents/skills/adding-inbox-sources/SKILL.md b/.agents/skills/adding-inbox-sources/SKILL.md index ffb0f8ce49cf..f8327327a16d 100644 --- a/.agents/skills/adding-inbox-sources/SKILL.md +++ b/.agents/skills/adding-inbox-sources/SKILL.md @@ -108,7 +108,7 @@ source-list-relevant is a place you must add the new product. The canonical list ### Type gates (every source) -1. `packages/shared/src/inbox-types.ts` — add `"jira"` to the `SourceProduct` union. +1. `packages/agent-contracts/src/inbox-types.ts` — add `"jira"` to the `SourceProduct` union. 2. `packages/api-client/src/posthog-client.ts` — add to `SignalSourceConfig.source_product` union; add a new `source_type` value only if the record type isn't already `issue`/`ticket`. ### Live UI path (every source) @@ -145,7 +145,7 @@ service, symbol, or router — do not clone `linear.ts`/`linear-integration.rout ### Verify -- `pnpm --filter @posthog/shared build` after touching `inbox-types.ts` (it's a published type). +- `pnpm --filter @posthog/shared... build` after touching `inbox-types.ts` (it's a published type). - `pnpm typecheck` (whole repo — the unions are consumed across packages). - `biome lint packages/core packages/ui` — zero `noRestrictedImports`, imports ordered. diff --git a/.claude/rules/task-model-catalog.md b/.claude/rules/task-model-catalog.md index 0f3e76858f69..61aa28951735 100644 --- a/.claude/rules/task-model-catalog.md +++ b/.claude/rules/task-model-catalog.md @@ -3,7 +3,7 @@ paths: - 'products/tasks/backend/model_catalog.py' - 'products/tasks/scripts/model_catalog_projection.py' - 'products/tasks/frontend/modelCatalog.generated.ts' - - 'products/desktop/packages/shared/src/model-catalog.generated.ts' + - 'products/desktop/packages/agent-contracts/src/model-catalog.generated.ts' --- `products/tasks/backend/model_catalog.py` is the single definition of a task run's triple: runtime adapter, model, and reasoning effort. @@ -12,7 +12,7 @@ The web composer and the desktop app each read a checked-in TypeScript projectio After changing the catalog, run `hogli build:projections` and commit both regenerated files. The command needs no dev stack and takes well under a second, so run it rather than reasoning about whether the output moved. -Never hand-edit `products/tasks/frontend/modelCatalog.generated.ts` or `products/desktop/packages/shared/src/model-catalog.generated.ts`. +Never hand-edit `products/tasks/frontend/modelCatalog.generated.ts` or `products/desktop/packages/agent-contracts/src/model-catalog.generated.ts`. `hogli build:projections --check` re-renders both and compares them byte for byte in CI, so an edit that the renderer would not produce fails CI rather than shipping. Adding a model means one row in `MODELS`. Two things do not follow from that row and are worth checking: diff --git a/.github/workflows/ci-python.yml b/.github/workflows/ci-python.yml index 0ff43c9196df..0a1e180ffe5c 100644 --- a/.github/workflows/ci-python.yml +++ b/.github/workflows/ci-python.yml @@ -130,10 +130,10 @@ jobs: - 'frontend/src/taxonomy/core-filter-definitions-by-group.json' - 'services/mcp/src/lib/trace-property-allowlist.generated.ts' - 'products/desktop/packages/core/src/inbox/objectKinds.generated.ts' - - 'products/desktop/packages/shared/src/objectTagKinds.generated.ts' + - 'products/desktop/packages/agent-contracts/src/objectTagKinds.generated.ts' - 'frontend/src/lib/components/AgentObjectTags/objectKinds.generated.ts' - 'products/tasks/frontend/modelCatalog.generated.ts' - - 'products/desktop/packages/shared/src/model-catalog.generated.ts' + - 'products/desktop/packages/agent-contracts/src/model-catalog.generated.ts' - 'services/mcp/src/lib/oauth-scopes.generated.ts' # hogli CLI changes need validation - 'tools/hogli/**' diff --git a/.github/workflows/desktop-agent-release-verify.yml b/.github/workflows/desktop-agent-release-verify.yml index 777e8cc716cd..1ae3fe91aa50 100644 --- a/.github/workflows/desktop-agent-release-verify.yml +++ b/.github/workflows/desktop-agent-release-verify.yml @@ -12,7 +12,7 @@ on: - 'products/desktop/packages/enricher/**' - 'products/desktop/packages/git/**' - 'products/desktop/packages/harness/**' - - 'products/desktop/packages/shared/**' + - 'products/desktop/packages/agent-contracts/**' - '.github/actions/desktop-build-agent-release/**' - '.github/workflows/desktop-agent-release.yml' - '.github/workflows/desktop-agent-release-verify.yml' diff --git a/.github/workflows/desktop-build-test.yml b/.github/workflows/desktop-build-test.yml index 01f7601d2234..58d617e8a510 100644 --- a/.github/workflows/desktop-build-test.yml +++ b/.github/workflows/desktop-build-test.yml @@ -135,7 +135,7 @@ jobs: run: | pnpm --filter @posthog/electron-trpc run build pnpm --filter @posthog/platform run build - pnpm --filter @posthog/shared run build + pnpm --filter @posthog/shared... run build pnpm --filter @posthog/git run build pnpm --filter @posthog/enricher run build pnpm --filter @posthog/harness run build @@ -363,7 +363,7 @@ jobs: run: | pnpm --filter @posthog/electron-trpc run build pnpm --filter @posthog/platform run build - pnpm --filter @posthog/shared run build + pnpm --filter @posthog/shared... run build pnpm --filter @posthog/git run build pnpm --filter @posthog/enricher run build pnpm --filter @posthog/harness run build @@ -489,7 +489,7 @@ jobs: run: | pnpm --filter @posthog/electron-trpc run build pnpm --filter @posthog/platform run build - pnpm --filter @posthog/shared run build + pnpm --filter @posthog/shared... run build pnpm --filter @posthog/git run build pnpm --filter @posthog/enricher run build pnpm --filter @posthog/harness run build diff --git a/.github/workflows/desktop-release.yml b/.github/workflows/desktop-release.yml index a9ae38ec68f2..cd7b49c03042 100644 --- a/.github/workflows/desktop-release.yml +++ b/.github/workflows/desktop-release.yml @@ -163,7 +163,7 @@ jobs: run: pnpm --filter @posthog/platform run build - name: Build shared package - run: pnpm --filter @posthog/shared run build + run: pnpm --filter @posthog/shared... run build - name: Build git package run: pnpm --filter @posthog/git run build @@ -468,7 +468,7 @@ jobs: run: pnpm --filter @posthog/platform run build - name: Build shared package - run: pnpm --filter @posthog/shared run build + run: pnpm --filter @posthog/shared... run build - name: Build git package run: pnpm --filter @posthog/git run build @@ -662,7 +662,7 @@ jobs: run: pnpm --filter @posthog/platform run build - name: Build shared package - run: pnpm --filter @posthog/shared run build + run: pnpm --filter @posthog/shared... run build - name: Build git package run: pnpm --filter @posthog/git run build diff --git a/.github/workflows/desktop-update-e2e.yml b/.github/workflows/desktop-update-e2e.yml index c58860cdc67d..baed8e61ef30 100644 --- a/.github/workflows/desktop-update-e2e.yml +++ b/.github/workflows/desktop-update-e2e.yml @@ -110,7 +110,7 @@ jobs: run: | pnpm --filter @posthog/electron-trpc run build pnpm --filter @posthog/platform run build - pnpm --filter @posthog/shared run build + pnpm --filter @posthog/shared... run build pnpm --filter @posthog/git run build pnpm --filter @posthog/enricher run build pnpm --filter @posthog/harness run build diff --git a/docs/internal/sandboxes-setup-guide.md b/docs/internal/sandboxes-setup-guide.md index d00892d39355..c8f71118be32 100644 --- a/docs/internal/sandboxes-setup-guide.md +++ b/docs/internal/sandboxes-setup-guide.md @@ -405,7 +405,7 @@ Mirroring failures are logged and never break the run's log write. When both `SANDBOX_PROVIDER=MODAL_DOCKER` and `LOCAL_POSTHOG_CODE_MONOREPO_ROOT` are set: 1. The selected sandbox Dockerfile is built in a temporary context -2. External runtime dependencies from local `packages/agent`, `packages/shared`, and `packages/git` manifests that are missing from the published image are installed at `/scripts`; required system compatibility packages such as musl for Codex are installed with them, while `workspace:*` dependencies continue to resolve through the overlaid packages +2. External runtime dependencies from local `packages/agent`, `packages/agent-contracts`, and `packages/git` manifests that are missing from the published image are installed at `/scripts`; required system compatibility packages such as musl for Codex are installed with them, while `workspace:*` dependencies continue to resolve through the overlaid packages 3. Each local package's built `dist/` directory is mounted over the published package's compiled output 4. The image runs in a separate Modal app (`posthog-sandbox-modal-docker-default`) so it doesn't affect production 5. The first build takes a few minutes; subsequent builds reuse Modal's layer cache diff --git a/posthog/object_tags/kinds.py b/posthog/object_tags/kinds.py index d2de02d5f0ab..089048a02edb 100644 --- a/posthog/object_tags/kinds.py +++ b/posthog/object_tags/kinds.py @@ -12,7 +12,7 @@ the regenerated files: - ``products/desktop/packages/core/src/inbox/objectKinds.generated.ts`` -- ``products/desktop/packages/shared/src/objectTagKinds.generated.ts`` +- ``products/desktop/packages/agent-contracts/src/objectTagKinds.generated.ts`` - ``frontend/src/lib/components/AgentObjectTags/objectKinds.generated.ts`` Python consumers import this module directly. diff --git a/posthog/object_tags/projection.py b/posthog/object_tags/projection.py index 8f21ef7d95d7..e753d164a77c 100755 --- a/posthog/object_tags/projection.py +++ b/posthog/object_tags/projection.py @@ -98,6 +98,6 @@ def render() -> dict[str, str]: registry = render_registry_ts() return { "products/desktop/packages/core/src/inbox/objectKinds.generated.ts": registry, - "products/desktop/packages/shared/src/objectTagKinds.generated.ts": render_prompt_kinds_ts(), + "products/desktop/packages/agent-contracts/src/objectTagKinds.generated.ts": render_prompt_kinds_ts(), "frontend/src/lib/components/AgentObjectTags/objectKinds.generated.ts": registry, } diff --git a/products/desktop/AGENTS.md b/products/desktop/AGENTS.md index 80912c5d00bd..c2b5dfd8b350 100644 --- a/products/desktop/AGENTS.md +++ b/products/desktop/AGENTS.md @@ -22,7 +22,8 @@ Principle: logic is portable; hosts are thin. | Package | Owns | Must not contain | | --- | --- | --- | | `@posthog/platform` | Host-capability interfaces and DI tokens. Host-neutral, zero runtime dependencies. | Implementations, Node, DOM, tRPC, Electron | -| `@posthog/shared` | Host-neutral primitives, types, Saga pattern, cloud-prompt encoding. Depends on published packages only. | Internal package imports, I/O | +| `@posthog/agent-contracts` | Types, schemas and helpers the agent runtime shares with its hosts: task and run domain types, model catalog, Saga pattern, cloud-prompt encoding. Depends on published packages only. | Internal package imports, I/O, desktop-only code | +| `@posthog/shared` | Host-neutral desktop primitives and types. Re-exports `@posthog/agent-contracts`, so desktop code imports both through `@posthog/shared`. | Internal package imports other than `agent-contracts`, I/O | | `@posthog/api-client` | PostHog/Django HTTPS client. Constructed by factory, not DI. | UI, Node-only host syscalls | | `@posthog/workspace-client` | Thin tRPC client for local or sandbox workspace-server. Runs in any JS environment. | Business logic, UI | | `@posthog/workspace-server` | Node backend services and colocated tRPC routers for git, fs, watchers, processes. | UI, core, Electron | @@ -64,7 +65,8 @@ Hard boundary: no new `@radix-ui/*` imports anywhere in the repo. Enforced by Biome `noRestrictedImports`. -- `platform` and `shared` import no internal packages. +- `platform` and `agent-contracts` import no internal packages. `shared` imports `agent-contracts` only. +- `agent`, `harness`, `git`, and `enricher` import `agent-contracts`, never `shared`, so the agent runtime does not depend on desktop code. - `api-client` and `workspace-client` may import `shared` and relevant `platform` contracts. No UI or Node host syscalls. - `workspace-server` may import `shared`, `platform` contracts, Node modules, and workspace-server code. Never `core` or `ui`. - `core` may import `shared`, `platform`, `workspace-client`, `api-client`, and other core code. Never `ui`, `workspace-server`, `electron`, `node:*`, `trpcClient`, or host-router runtime. diff --git a/products/desktop/apps/code/scripts/build-linux-docker.sh b/products/desktop/apps/code/scripts/build-linux-docker.sh index 2f5e7d29714d..7e275f24e225 100755 --- a/products/desktop/apps/code/scripts/build-linux-docker.sh +++ b/products/desktop/apps/code/scripts/build-linux-docker.sh @@ -88,7 +88,7 @@ COPYFILE_DISABLE=1 tar -cf - \ pnpm install --frozen-lockfile pnpm --filter @posthog/electron-trpc build pnpm --filter @posthog/platform build - pnpm --filter @posthog/shared build + pnpm --filter @posthog/shared... build pnpm --filter @posthog/git build pnpm --filter @posthog/enricher build pnpm --filter @posthog/agent build diff --git a/products/desktop/apps/code/vite.shared.mts b/products/desktop/apps/code/vite.shared.mts index 3e47350c99df..03920c0473c7 100644 --- a/products/desktop/apps/code/vite.shared.mts +++ b/products/desktop/apps/code/vite.shared.mts @@ -56,6 +56,20 @@ const baseAliases: Alias[] = [ ]; export const workspaceAliases: Alias[] = [ + { + find: /^@posthog\/agent-contracts\/(.+)$/, + replacement: path.resolve( + __dirname, + "../../packages/agent-contracts/src/$1", + ), + }, + { + find: /^@posthog\/agent-contracts$/, + replacement: path.resolve( + __dirname, + "../../packages/agent-contracts/src/index.ts", + ), + }, { find: /^@posthog\/agent\/(.+)$/, replacement: path.resolve(__dirname, "../../packages/agent/src/$1.ts"), diff --git a/products/desktop/apps/mobile/metro.config.js b/products/desktop/apps/mobile/metro.config.js index 5d98b33ee23c..0a828a4df55c 100644 --- a/products/desktop/apps/mobile/metro.config.js +++ b/products/desktop/apps/mobile/metro.config.js @@ -39,28 +39,32 @@ nativeWindConfig.resolver = { sourceExts: [...nativeWindConfig.resolver.sourceExts, "svg"], }; -// Resolve @posthog/shared to its TypeScript source so Babel transpiles it. -// Its package.json `exports` point at `dist/`, which is only present after a +// Resolve @posthog/shared, and the @posthog/agent-contracts package it +// re-exports, to their TypeScript source so Babel transpiles them. +// Their package.json `exports` point at `dist/`, which is only present after a // `pnpm build` -- EAS Build never runs one, so bundling there fails outright. // This must be a resolveRequest hook rather than an `extraNodeModules` alias: // pnpm symlinks the package into node_modules, so Metro resolves it there // first and never consults the alias, and an alias also cannot map subpath // imports like `@posthog/shared/domain-types`. -const SHARED_PACKAGE = "@posthog/shared"; -const sharedSrc = path.resolve(monorepoRoot, "packages/shared/src"); +const SOURCE_RESOLVED_PACKAGES = { + "@posthog/shared": path.resolve(monorepoRoot, "packages/shared/src"), + "@posthog/agent-contracts": path.resolve( + monorepoRoot, + "packages/agent-contracts/src", + ), +}; const upstreamResolveRequest = nativeWindConfig.resolver.resolveRequest; nativeWindConfig.resolver.resolveRequest = (context, moduleName, platform) => { - if ( - moduleName === SHARED_PACKAGE || - moduleName.startsWith(`${SHARED_PACKAGE}/`) - ) { - const subpath = moduleName.slice(SHARED_PACKAGE.length + 1) || "index"; - return context.resolveRequest( - context, - path.join(sharedSrc, subpath), - platform, - ); + for (const [packageName, src] of Object.entries(SOURCE_RESOLVED_PACKAGES)) { + if ( + moduleName === packageName || + moduleName.startsWith(`${packageName}/`) + ) { + const subpath = moduleName.slice(packageName.length + 1) || "index"; + return context.resolveRequest(context, path.join(src, subpath), platform); + } } return (upstreamResolveRequest ?? context.resolveRequest)( diff --git a/products/desktop/apps/web/vite.aliases.ts b/products/desktop/apps/web/vite.aliases.ts index 5a528dde85c8..cc361f937a23 100644 --- a/products/desktop/apps/web/vite.aliases.ts +++ b/products/desktop/apps/web/vite.aliases.ts @@ -18,6 +18,7 @@ export const posthogSrcAliases = [ subpath("ui"), subpath("core"), subpath("shared"), + subpath("agent-contracts"), subpath("host-router"), subpath("host-trpc"), subpath("platform"), diff --git a/products/desktop/biome.jsonc b/products/desktop/biome.jsonc index 0be06e4bf375..cecc40f8ec7b 100644 --- a/products/desktop/biome.jsonc +++ b/products/desktop/biome.jsonc @@ -128,7 +128,7 @@ // compares against, so no formatter may touch it. "includes": [ "packages/core/src/inbox/objectKinds.generated.ts", - "packages/shared/src/objectTagKinds.generated.ts" + "packages/agent-contracts/src/objectTagKinds.generated.ts" ], "formatter": { "enabled": false @@ -280,6 +280,32 @@ } } }, + { + "includes": [ + "packages/agent/**/*.ts", + "packages/agent-contracts/**/*.ts", + "packages/enricher/**/*.ts", + "packages/git/**/*.ts", + "packages/harness/**/*.ts" + ], + "linter": { + "rules": { + "style": { + "noRestrictedImports": { + "level": "error", + "options": { + "patterns": [ + { + "group": ["@posthog/shared", "@posthog/shared/*"], + "message": "The agent runtime must not depend on desktop code. Import from @posthog/agent-contracts." + } + ] + } + } + } + } + } + }, { "includes": [ "packages/platform/src/**/*.ts", diff --git a/products/desktop/knip.json b/products/desktop/knip.json index 813b6199f34c..9f979176b7d2 100644 --- a/products/desktop/knip.json +++ b/products/desktop/knip.json @@ -48,6 +48,11 @@ "includeEntryExports": false, "entry": ["src/**/*.bench.ts"] }, + "packages/agent-contracts": { + "entry": ["src/**/*.test.ts"], + "project": ["src/**/*.ts"], + "includeEntryExports": false + }, "packages/api-client": { "entry": ["src/**/*.test.ts"], "project": ["src/**/*.{ts,tsx}"], diff --git a/products/desktop/packages/agent-contracts/package.json b/products/desktop/packages/agent-contracts/package.json new file mode 100644 index 000000000000..83634cae3f40 --- /dev/null +++ b/products/desktop/packages/agent-contracts/package.json @@ -0,0 +1,36 @@ +{ + "name": "@posthog/agent-contracts", + "version": "1.0.0", + "description": "Types, schemas and helpers the agent runtime and its hosts share", + "type": "module", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + }, + "./*": { + "types": "./dist/*.d.ts", + "import": "./dist/*.js" + } + }, + "scripts": { + "build": "tsup", + "dev": "tsup --watch", + "typecheck": "tsc --noEmit", + "test": "vitest run", + "clean": "node ../../scripts/rimraf.mjs dist .turbo" + }, + "dependencies": { + "zod": "^4.1.12" + }, + "devDependencies": { + "@agentclientprotocol/sdk": "0.19.0", + "tsup": "^8.5.1", + "typescript": "^5.5.0", + "vitest": "^4.1.8" + }, + "files": [ + "dist/**/*", + "src/**/*" + ] +} diff --git a/products/desktop/packages/shared/src/adapter.ts b/products/desktop/packages/agent-contracts/src/adapter.ts similarity index 100% rename from products/desktop/packages/shared/src/adapter.ts rename to products/desktop/packages/agent-contracts/src/adapter.ts diff --git a/products/desktop/packages/shared/src/agent-actions.test.ts b/products/desktop/packages/agent-contracts/src/agent-actions.test.ts similarity index 100% rename from products/desktop/packages/shared/src/agent-actions.test.ts rename to products/desktop/packages/agent-contracts/src/agent-actions.test.ts diff --git a/products/desktop/packages/shared/src/agent-actions.ts b/products/desktop/packages/agent-contracts/src/agent-actions.ts similarity index 100% rename from products/desktop/packages/shared/src/agent-actions.ts rename to products/desktop/packages/agent-contracts/src/agent-actions.ts diff --git a/products/desktop/packages/shared/src/agent-conversation.ts b/products/desktop/packages/agent-contracts/src/agent-conversation.ts similarity index 100% rename from products/desktop/packages/shared/src/agent-conversation.ts rename to products/desktop/packages/agent-contracts/src/agent-conversation.ts diff --git a/products/desktop/packages/shared/src/agent-runtime.ts b/products/desktop/packages/agent-contracts/src/agent-runtime.ts similarity index 100% rename from products/desktop/packages/shared/src/agent-runtime.ts rename to products/desktop/packages/agent-contracts/src/agent-runtime.ts diff --git a/products/desktop/packages/shared/src/api-http-client.test.ts b/products/desktop/packages/agent-contracts/src/api-http-client.test.ts similarity index 100% rename from products/desktop/packages/shared/src/api-http-client.test.ts rename to products/desktop/packages/agent-contracts/src/api-http-client.test.ts diff --git a/products/desktop/packages/shared/src/api-http-client.ts b/products/desktop/packages/agent-contracts/src/api-http-client.ts similarity index 100% rename from products/desktop/packages/shared/src/api-http-client.ts rename to products/desktop/packages/agent-contracts/src/api-http-client.ts diff --git a/products/desktop/packages/shared/src/backoff.test.ts b/products/desktop/packages/agent-contracts/src/backoff.test.ts similarity index 100% rename from products/desktop/packages/shared/src/backoff.test.ts rename to products/desktop/packages/agent-contracts/src/backoff.test.ts diff --git a/products/desktop/packages/shared/src/backoff.ts b/products/desktop/packages/agent-contracts/src/backoff.ts similarity index 100% rename from products/desktop/packages/shared/src/backoff.ts rename to products/desktop/packages/agent-contracts/src/backoff.ts diff --git a/products/desktop/packages/shared/src/bedrock-gateway.ts b/products/desktop/packages/agent-contracts/src/bedrock-gateway.ts similarity index 86% rename from products/desktop/packages/shared/src/bedrock-gateway.ts rename to products/desktop/packages/agent-contracts/src/bedrock-gateway.ts index 1fddf51ff949..2a0b1b7c07de 100644 --- a/products/desktop/packages/shared/src/bedrock-gateway.ts +++ b/products/desktop/packages/agent-contracts/src/bedrock-gateway.ts @@ -11,8 +11,7 @@ * before reading the fallback one, and its direct-Bedrock path has no reverse * fallback to Anthropic. So a Bedrock outage fails a `test` session outright. * - * The key is a literal because the agent runtime reads it and must not depend - * on the desktop flag registry. It must match `feature-flag-keys.json`. + * Literal rather than read from the desktop flag registry; flags.test.ts keeps it in step. */ export const BEDROCK_LLM_GATEWAY_FLAG = "bedrock-llm-gateway"; diff --git a/products/desktop/packages/shared/src/binary.test.ts b/products/desktop/packages/agent-contracts/src/binary.test.ts similarity index 100% rename from products/desktop/packages/shared/src/binary.test.ts rename to products/desktop/packages/agent-contracts/src/binary.test.ts diff --git a/products/desktop/packages/shared/src/binary.ts b/products/desktop/packages/agent-contracts/src/binary.ts similarity index 100% rename from products/desktop/packages/shared/src/binary.ts rename to products/desktop/packages/agent-contracts/src/binary.ts diff --git a/products/desktop/packages/shared/src/call-tool-result.test.ts b/products/desktop/packages/agent-contracts/src/call-tool-result.test.ts similarity index 100% rename from products/desktop/packages/shared/src/call-tool-result.test.ts rename to products/desktop/packages/agent-contracts/src/call-tool-result.test.ts diff --git a/products/desktop/packages/shared/src/call-tool-result.ts b/products/desktop/packages/agent-contracts/src/call-tool-result.ts similarity index 100% rename from products/desktop/packages/shared/src/call-tool-result.ts rename to products/desktop/packages/agent-contracts/src/call-tool-result.ts diff --git a/products/desktop/packages/shared/src/cloud-prompt.test.ts b/products/desktop/packages/agent-contracts/src/cloud-prompt.test.ts similarity index 100% rename from products/desktop/packages/shared/src/cloud-prompt.test.ts rename to products/desktop/packages/agent-contracts/src/cloud-prompt.test.ts diff --git a/products/desktop/packages/shared/src/cloud-prompt.ts b/products/desktop/packages/agent-contracts/src/cloud-prompt.ts similarity index 100% rename from products/desktop/packages/shared/src/cloud-prompt.ts rename to products/desktop/packages/agent-contracts/src/cloud-prompt.ts diff --git a/products/desktop/packages/shared/src/cloud-task-models.test.ts b/products/desktop/packages/agent-contracts/src/cloud-task-models.test.ts similarity index 100% rename from products/desktop/packages/shared/src/cloud-task-models.test.ts rename to products/desktop/packages/agent-contracts/src/cloud-task-models.test.ts diff --git a/products/desktop/packages/shared/src/cloud-task-models.ts b/products/desktop/packages/agent-contracts/src/cloud-task-models.ts similarity index 100% rename from products/desktop/packages/shared/src/cloud-task-models.ts rename to products/desktop/packages/agent-contracts/src/cloud-task-models.ts diff --git a/products/desktop/packages/shared/src/custom-cloud.test.ts b/products/desktop/packages/agent-contracts/src/custom-cloud.test.ts similarity index 100% rename from products/desktop/packages/shared/src/custom-cloud.test.ts rename to products/desktop/packages/agent-contracts/src/custom-cloud.test.ts diff --git a/products/desktop/packages/shared/src/custom-cloud.ts b/products/desktop/packages/agent-contracts/src/custom-cloud.ts similarity index 100% rename from products/desktop/packages/shared/src/custom-cloud.ts rename to products/desktop/packages/agent-contracts/src/custom-cloud.ts diff --git a/products/desktop/packages/shared/src/dismissal-reasons.test.ts b/products/desktop/packages/agent-contracts/src/dismissal-reasons.test.ts similarity index 100% rename from products/desktop/packages/shared/src/dismissal-reasons.test.ts rename to products/desktop/packages/agent-contracts/src/dismissal-reasons.test.ts diff --git a/products/desktop/packages/shared/src/dismissal-reasons.ts b/products/desktop/packages/agent-contracts/src/dismissal-reasons.ts similarity index 100% rename from products/desktop/packages/shared/src/dismissal-reasons.ts rename to products/desktop/packages/agent-contracts/src/dismissal-reasons.ts diff --git a/products/desktop/packages/shared/src/domain-types.test.ts b/products/desktop/packages/agent-contracts/src/domain-types.test.ts similarity index 100% rename from products/desktop/packages/shared/src/domain-types.test.ts rename to products/desktop/packages/agent-contracts/src/domain-types.test.ts diff --git a/products/desktop/packages/agent-contracts/src/domain-types.ts b/products/desktop/packages/agent-contracts/src/domain-types.ts new file mode 100644 index 000000000000..32009619ec85 --- /dev/null +++ b/products/desktop/packages/agent-contracts/src/domain-types.ts @@ -0,0 +1,1221 @@ +import { z } from "zod"; +import type { Adapter } from "./adapter"; +import type { AgentRuntime } from "./agent-runtime"; +import type { ReportStateReason } from "./dismissal-reasons"; +import { REASONING_EFFORT_LABELS } from "./model-catalog.generated"; +import type { StoredLogEntry } from "./session-events"; +import type { UploadableSkillSource } from "./skills"; + +// Execution mode schema and type - shared between main and renderer +const executionModeSchema = z.enum([ + "default", + "acceptEdits", + "plan", + "bypassPermissions", + "auto", + "read-only", + "full-access", +]); + +import type { ExecutionMode } from "./exec-types"; +export type { ExecutionMode }; + +// Effort level schema and type - shared between main and renderer +export const effortLevelSchema = z.enum([ + "low", + "medium", + "high", + "xhigh", + "max", + "ultracode", +]); +export type EffortLevel = z.infer; + +/** All effort levels in ascending order of depth. */ +export const EFFORT_LEVELS = effortLevelSchema.options; + +/** + * OpenAI service tiers a Codex run can request. "flex" is the cheaper, slower + * queue; "priority" the faster one; "default" pins standard routing explicitly. + * Codex only sends a tier its model catalogue advertises for the model in use. + */ +export const serviceTierSchema = z.enum(["default", "priority", "flex"]); +export type ServiceTier = z.infer; + +export const SERVICE_TIERS = serviceTierSchema.options; + +// The annotation is the check: a depth added to `EffortLevel` but not to the catalog (or +// the reverse) fails to compile here. +export const EFFORT_LEVEL_LABELS: Record = + REASONING_EFFORT_LABELS; + +/** Claude Code docs for the tiers that need explaining. */ +export const EFFORT_LEVEL_DOCS_URLS: Partial> = { + ultracode: "https://code.claude.com/docs/en/workflows", +}; + +/** Adapter-specific docs for the fast mode toggle. */ +export const FAST_MODE_DOCS_URLS: Record = { + claude: "https://code.claude.com/docs/en/model-config", + codex: "https://developers.openai.com/codex", +}; + +export interface UserBasic { + id: number; + uuid: string; + distinct_id?: string | null; + first_name?: string; + last_name?: string; + email: string; + is_email_verified?: boolean | null; +} + +/** One row from the org members list; trimmed to what mention pickers need. */ +export interface OrganizationMemberBasic { + id: string; + user: UserBasic; +} + +export interface Task { + id: string; + task_number: number | null; + slug: string; + title: string; + title_manually_set?: boolean; + description: string; + // First characters of the description, present instead of the full body when the + // list was fetched with basic=true. Absent on the full and single-task responses. + description_preview?: string; + created_at: string; + updated_at: string; + /** + * When something last happened in the session, as opposed to `updated_at` (when the row was + * last written). The timestamp a "recent activity" sort reads. Empty on older responses. + */ + last_activity_at?: string; + created_by?: UserBasic | null; + origin_product: string; + repository?: string | null; // Format: "organization/repository" (e.g., "posthog/posthog-js") + repositories?: string[]; + github_integration?: number | null; + github_user_integration?: string | null; + json_schema?: Record | null; + signal_report?: string | null; + internal?: boolean; + /** Key of the server-side flow that created the task, e.g. `desktop_onboarding_session:`. */ + origin_key?: string | null; + runtime?: AgentRuntime; + /** Backend channel (tasks product Channel UUID) this task is owned by. */ + channel?: string | null; + latest_run?: TaskRun; +} + +export interface TaskSearchResult { + id: string; + kind: "task" | "pull_request" | "artifact" | "channel" | "canvas"; + title: string; + subtitle: string; + task_id: string | null; + task_run_id: string | null; + channel_id: string | null; + created_by?: UserBasic | null; + /** What created the containing task, e.g. "slack". */ + origin_product?: string | null; + latest_run?: TaskSearchResultRun | null; + updated_at: string; + metadata: Record; +} + +export interface TaskSearchResultRun { + id: string; + status: TaskRunStatus | null; + environment: TaskRunEnvironment | null; +} + +/** + * A backend task channel — the single channel identity: it owns the task feed, + * threads, instructions (CONTEXT.md) and filed canvases. `personal` is the + * user's private "#me" channel. `starred` is per-user. + */ +export interface ProvisionedTaskChannels { + channels: TaskChannel[]; + personal_created: boolean; + general_created: boolean; +} + +export interface TaskChannel { + id: string; + name: string; + channel_type: "public" | "personal" | "private"; + starred: boolean; + github_integration?: number | null; + repositories?: string[]; + auto_archive_after_days?: number | null; + created_at: string; + created_by?: UserBasic | null; + system_role?: "personal" | "general" | null; +} + +/** Lifecycle events a client may post into a channel's feed. */ +export type ChannelFeedMessageEvent = "context_md_building"; + +export type { + SpaceFeatureInput, + SpaceGoalDirection, + SpaceGoalInput, + SpaceGoalPeriod, + SpaceSetupInput, + SpaceSetupKind, + SpaceSetupStarted, +} from "./schemas"; + +/** + * A durable, team-visible "PostHog agent" announcement in a channel's feed — + * rendered alongside task cards (e.g. "Adam created this context"). `author` is + * the user whose action produced the row; `author_kind` says who authored it. + * `payload` carries structured event data (e.g. `{ context_name }`) so rendering + * survives renames. + */ +export interface ChannelFeedMessage { + id: string; + channel: string; + author?: UserBasic | null; + author_kind: "human" | "system" | "agent"; + event: ChannelFeedMessageEvent | string; + payload: Record; + content: string; + created_at: string; +} + +/** + * One human message in a task's thread. Thread messages never reach the agent + * unless the task author forwards one, which stamps the forwarded_* fields. + */ +export interface TaskThreadMessage { + id: string; + task: string; + /** Who authored the row; agent rows are server-emitted announcements. Absent on older backends. */ + author_kind?: "human" | "system" | "agent"; + /** Stable event key for non-human rows (e.g. "canvas_created", "turn_complete"). */ + event?: string; + /** Structured event payload; turn_complete carries `{ run_id }` so a client rendering a run's live agent turns can dedupe the durable row. */ + payload?: Record; + content: string; + created_at: string; + author?: UserBasic | null; + forwarded_to_agent_at?: string | null; + forwarded_by?: UserBasic | null; + /** Users mentioned in the row, indexed at write time. Absent on older backends. */ + mentioned_user_ids?: number[]; +} + +/** The latest resolve or reopen on a comment thread. */ +export interface TaskCommentStateEvent { + state: "resolved" | "open"; + author?: UserBasic | null; + created_at: string; +} + +/** + * One comment thread on a task, collapsed the way the activity timeline shows it + * (`/thread_messages/comment_activity/`). Mirrors `TaskCommentActivityDTO`. + */ +export interface TaskCommentThreadSummary { + id: string; + target: { id: string; type: string; name: string }; + content: string; + content_truncated: boolean; + selected_text: string | null; + author?: UserBasic | null; + created_at: string; + last_activity_at: string; + reply_count: number; + participants: UserBasic[]; + mentioned_user_ids: number[]; + resolved: boolean; + state_event: TaskCommentStateEvent | null; + latest_reply: { + author?: UserBasic | null; + content: string; + /** The excerpt is bounded, so a long reply comes back cut. */ + content_truncated: boolean; + created_at: string; + } | null; +} + +/** + * One @-mention of the current user in a task's thread, from the backend + * mentions index (`/task_mentions/`). Mirrors `TaskMentionDTO`. + */ +export interface TaskMention { + id: string; + message_id: string; + task_id: string; + task_title: string; + channel_id?: string | null; + channel_name?: string | null; + author?: UserBasic | null; + content: string; + created_at: string; +} + +/** Which signal produced an activity row; mirrors the backend `activity_kind`. */ +export type TaskActivityKind = + | "awaiting_input" + | "completed" + | "message" + | "mention" + | "thread_reply" + | "owned_item_comment" + | "created"; + +/** + * One task the current user is involved in, from the backend task-activity feed + * (`/task_activity/`). One row per task, newest activity first. Mirrors + * `TaskActivityDTO`. + */ +export interface TaskActivity { + id: string; + task_id: string; + task_title: string; + channel_id?: string | null; + channel_name?: string | null; + activity_at: string; + activity_kind: TaskActivityKind; + snippet: string; + latest_author?: UserBasic | null; + latest_message_id?: string | null; + latest_comment_id?: string | null; + latest_comment_scope?: string | null; + latest_comment_item_id?: string | null; + is_unread: boolean; +} + +export interface TaskActivityPage { + results: TaskActivity[]; + /** Unread tasks across the whole feed, not just this page. Backs the sidebar badge. */ + unread_count: number; + next_before?: string | null; + next_before_id?: string | null; +} + +export interface TaskActivityReadMarker { + task_id: string; + seen_before: string; + activity_id?: string; +} + +export interface TaskActivityMarkReadResult { + marked_read: number; + unread_count: number; +} + +export type TaskRunStatus = + | "not_started" + | "queued" + | "in_progress" + | "completed" + | "failed" + | "cancelled"; + +export type TaskRunEnvironment = "local" | "cloud"; + +const optionalField = ( + field: T, +): z.ZodCatch> => field.optional().catch(undefined); + +const pendingFollowupMessageSchema = z.object({ + id: z.string(), + content: z.string(), + ts: z.string().optional(), +}); + +export type PendingFollowupMessage = z.infer< + typeof pendingFollowupMessageSchema +>; + +export function readPendingFollowupMessages( + state: Record | undefined, +): PendingFollowupMessage[] { + const parsed = z + .array(pendingFollowupMessageSchema) + .safeParse(state?.pending_followup_messages); + return parsed.success ? parsed.data : []; +} + +/** + * One skills-store skill the sandbox agent lists as a local skill. The task + * worker resolves the list into run state; the agent renders a pointer + * SKILL.md per entry and fetches the body over MCP only when it is invoked. + */ +const storeSkillStubSchema = z.object({ + name: z.string(), + description: z.string(), + version: z.number(), +}); + +export type StoreSkillStub = z.infer; + +const taskRunStateFields = { + ai_agent_name: optionalField(z.string()), + ai_stage: optionalField(z.string()), + auto_publish: optionalField(z.boolean()), + benjamin_version: optionalField(z.string()), + initial_permission_mode: optionalField(executionModeSchema), + initial_prompt_override: optionalField(z.string()), + pending_followup_messages: optionalField( + z.array(pendingFollowupMessageSchema), + ), + pending_user_artifact_ids: optionalField(z.array(z.string())), + pending_user_message: optionalField(z.string()), + pending_user_message_id: optionalField(z.string()), + prewarmed: optionalField(z.boolean()), + reasoning_effort: optionalField( + z.union([effortLevelSchema, z.enum(["off", "minimal"])]), + ), + resume_from_run_id: optionalField(z.string()), + sandbox_environment_id: optionalField(z.string()), + slack_artifact_delivery: optionalField( + z.enum(["none", "message", "canvas_file"]), + ), + slack_chart_delivery: optionalField(z.boolean()), + slack_notified_pr_url: optionalField(z.string()), + slack_thread_url: optionalField(z.string()), + snapshot_kind: optionalField(z.string()), + store_skills: optionalField(z.array(storeSkillStubSchema)), + token_usage: optionalField(z.record(z.string(), z.unknown())), +} satisfies z.ZodRawShape; + +export const taskRunStateSchema = z.looseObject(taskRunStateFields).catch({}); + +export type TaskRunState = z.infer; +export type TaskRunStateField = keyof typeof taskRunStateFields; + +export type ArtifactType = + | "plan" + | "context" + | "reference" + | "output" + | "artifact" + | "user_attachment" + | "skill_bundle"; + +export type ArtifactSource = + | "agent_output" + | "user_attachment" + | "posthog_code_skill" + | "posthog_object"; + +export interface SkillBundleArtifactMetadata { + skill_name: string; + skill_source: UploadableSkillSource; + content_sha256: string; + bundle_format: "zip"; + schema_version: number; +} + +export interface PostHogObjectArtifactMetadata { + reference_type: "posthog_object"; + object_kind: string; + object_id: string; + source_message_ids: string[]; + occurrence_count: number; +} + +export type TaskRunArtifactMetadata = + | SkillBundleArtifactMetadata + | PostHogObjectArtifactMetadata; + +export function isSkillBundleArtifactMetadata( + metadata: TaskRunArtifactMetadata | undefined, +): metadata is SkillBundleArtifactMetadata { + return metadata !== undefined && "skill_name" in metadata; +} + +export interface TaskRunArtifact { + id?: string; + name: string; + type: ArtifactType; + source?: ArtifactSource; + size?: number; + content_type?: string; + metadata?: TaskRunArtifactMetadata; + storage_path?: string; + uploaded_at?: string; + uploaded_by?: "agent" | "user"; + uploaded_by_user_id?: number; + dismissed_at?: string | null; +} + +export const TERMINAL_STATUSES = ["completed", "failed", "cancelled"] as const; + +export function isTerminalStatus( + status: TaskRunStatus | string | null | undefined, +): boolean { + return ( + status !== null && + status !== undefined && + TERMINAL_STATUSES.includes(status as (typeof TERMINAL_STATUSES)[number]) + ); +} + +export function isContentlessTask(task: { + title?: string | null; + description?: string | null; +}): boolean { + return !task.title?.trim() && !task.description?.trim(); +} + +export interface TaskRun { + id: string; + task: string; // Task ID + team: number; + branch: string | null; + runtime_adapter?: Adapter | null; + model?: string | null; + reasoning_effort?: EffortLevel | null; + stage?: string | null; // Current stage (e.g., 'research', 'plan', 'build') + environment?: TaskRunEnvironment; + status: TaskRunStatus; + log_url: string; + error_message: string | null; + output: Record | null; // Structured output (PR URL, commit SHA, etc.) + state: TaskRunState; + artifacts?: TaskRunArtifact[]; + created_at: string; + updated_at: string; + completed_at: string | null; +} + +export type NetworkAccessLevel = "trusted" | "full" | "custom"; + +export interface SandboxEnvironment { + id: string; + name: string; + network_access_level: NetworkAccessLevel; + allowed_domains: string[]; + include_default_domains: boolean; + repositories: string[]; + has_environment_variables: boolean; + /** + * Names of the variables that are set. Values are write-only and never returned. + * Optional because desktop releases are not orchestrated with backend deploys, so a + * client can reach an API that predates this field. + */ + environment_variable_keys?: string[]; + private: boolean; + effective_domains: string[]; + custom_image_id: string | null; + custom_image_name: string | null; + custom_image_status: string | null; + created_by?: UserBasic | null; + created_at: string; + updated_at: string; +} + +export interface SandboxEnvironmentInput { + name: string; + network_access_level: NetworkAccessLevel; + allowed_domains?: string[]; + include_default_domains?: boolean; + repositories?: string[]; + environment_variables?: Record; + private?: boolean; + custom_image_id?: string | null; +} + +export type SandboxCustomImageStatus = + | "draft" + | "scanning" + | "scan_failed" + | "building" + | "build_failed" + | "ready" + | "archived"; + +export function isImageBuildInProgress( + status: SandboxCustomImageStatus, +): boolean { + return status === "scanning" || status === "building"; +} + +export function isImageBuildFailed(status: SandboxCustomImageStatus): boolean { + return status === "scan_failed" || status === "build_failed"; +} + +export interface SandboxCustomImageScanFinding { + severity: string; + detail: string; +} + +export interface SandboxCustomImage { + id: string; + name: string; + description: string; + status: SandboxCustomImageStatus; + version: number; + modal_image_name: string; + repository: string; + private: boolean; + spec: Record; + spec_yaml: string; + scan_result: { + passed?: boolean; + findings?: SandboxCustomImageScanFinding[]; + }; + error: string; + build_log: string; + builder_task_id: string | null; + created_by?: UserBasic | null; + created_at: string; + updated_at: string; +} + +interface CloudTaskUpdateBase { + taskId: string; + runId: string; +} + +export interface CloudTaskLogsUpdate extends CloudTaskUpdateBase { + kind: "logs"; + newEntries: StoredLogEntry[]; + totalEntryCount: number; +} + +export interface CloudTaskStatusUpdate extends CloudTaskUpdateBase { + kind: "status"; + status?: TaskRunStatus; + stage?: string | null; + output?: Record | null; + errorMessage?: string | null; + branch?: string | null; + sandboxAlive?: boolean | null; +} + +export interface CloudTaskSnapshotUpdate extends CloudTaskUpdateBase { + kind: "snapshot"; + newEntries: StoredLogEntry[]; + totalEntryCount: number; + /** Chain index of newEntries[0] when the snapshot is a tail window rather + * than the full history; older entries page in on demand. Absent means + * the snapshot starts at the head of the chain. */ + windowStart?: number; + rebuilt?: boolean; + status?: TaskRunStatus; + stage?: string | null; + output?: Record | null; + errorMessage?: string | null; + branch?: string | null; + sandboxAlive?: boolean | null; +} + +export interface CloudTaskErrorUpdate extends CloudTaskUpdateBase { + kind: "error"; + errorTitle: string; + errorMessage: string; + retryable: boolean; +} + +export interface CloudPermissionOption { + kind: string; + optionId: string; + name: string; + _meta?: Record; +} + +export interface CloudTaskPermissionRequestUpdate extends CloudTaskUpdateBase { + kind: "permission_request"; + requestId: string; + toolCall: { + toolCallId: string; + title: string; + kind: string; + content?: unknown[]; + rawInput?: Record; + _meta?: Record; + }; + options: CloudPermissionOption[]; +} + +export type CloudTaskUpdatePayload = + | CloudTaskLogsUpdate + | CloudTaskStatusUpdate + | CloudTaskSnapshotUpdate + | CloudTaskErrorUpdate + | CloudTaskPermissionRequestUpdate; + +// Mention types for editors +type MentionType = + | "file" + | "folder" + | "error" + | "experiment" + | "insight" + | "feature_flag" + | "generic"; + +export interface MentionItem { + // File items + path?: string; + name?: string; + kind?: "file" | "directory"; + // URL items + url?: string; + type?: MentionType; + label?: string; + id?: string; + urlId?: string; +} + +// Git file status types +import type { GitFileStatus } from "./git-types"; +export type { GitFileStatus }; + +export type GitBusyOperation = "rebase" | "merge" | "cherry-pick" | "revert"; + +export type GitBusyState = + | { busy: false } + | { busy: true; operation: GitBusyOperation }; + +export interface ChangedFile { + path: string; + status: GitFileStatus; + originalPath?: string; // For renames: the old path + linesAdded?: number; + linesRemoved?: number; + staged?: boolean; + patch?: string; // Unified diff patch from GitHub API + sha?: string; +} + +// External apps detection types +export type ExternalAppType = + | "editor" + | "terminal" + | "file-manager" + | "git-client"; + +export interface DetectedApplication { + id: string; // "vscode", "cursor", "iterm" + name: string; // "Visual Studio Code" + type: ExternalAppType; + path: string; // "/Applications/Visual Studio Code.app" + command: string; // Launch command + icon?: string; // Base64 data URL +} + +import type { SignalReportStatus } from "./signal-types"; +export type { SignalReportStatus }; + +/** Actionability priority from the researched report (actionability judgment artefact). */ +export type SignalReportPriority = "P0" | "P1" | "P2" | "P3" | "P4"; + +/** Latest known state of a report's implementation PR. */ +export type SignalReportPrState = + | "unknown" + | "draft" + | "open" + | "closed" + | "merged"; + +/** Actionability choice from the researched report. */ +export type SignalReportActionability = + | "immediately_actionable" + | "requires_human_input" + | "not_actionable"; + +/** + * One or more `SignalReportStatus` values joined by commas, e.g. `potential` or `potential,candidate,ready`. + * This looks horrendous but it's superb, trust me bro. + */ +export type CommaSeparatedSignalReportStatuses = + | SignalReportStatus + | `${SignalReportStatus},${SignalReportStatus}` + | `${SignalReportStatus},${SignalReportStatus},${SignalReportStatus}` + | `${SignalReportStatus},${SignalReportStatus},${SignalReportStatus},${SignalReportStatus}` + | `${SignalReportStatus},${SignalReportStatus},${SignalReportStatus},${SignalReportStatus},${SignalReportStatus}`; + +export type SignalReportChartSize = "small" | "medium" | "large"; + +/** + * One chart attached to a report (`SignalReport.charts` on the backend serializer). + * `query` is stored unparsed; the backend only guarantees `kind` is one of + * InsightVizNode, DataVisualizationNode, or SavedInsightNode. + */ +export interface SignalReportChart { + chart_id: string; + title: string; + query: unknown; + caption?: string | null; + size?: SignalReportChartSize | null; +} + +export interface SignalReport { + id: string; + title: string | null; + summary: string | null; + status: SignalReportStatus; + total_weight: number; + signal_count: number; + signals_at_run?: number; + created_at: string; + updated_at: string; + artefact_count: number; + collapsed_note_count?: number; + /** P0–P4 from priority judgment when the report is researched */ + priority?: SignalReportPriority | null; + /** Actionability choice from the actionability judgment artefact. */ + actionability?: SignalReportActionability | null; + /** Whether the issue appears already fixed, from the actionability judgment artefact. */ + already_addressed?: boolean | null; + /** Reason code from the latest dismiss or resolve artefact. */ + dismissal_reason?: ReportStateReason | null; + /** Free-form note captured alongside the dismissal reason. */ + dismissal_note?: string | null; + /** Whether the current user is a suggested reviewer for this report (server-annotated). */ + is_suggested_reviewer?: boolean; + /** Distinct source products contributing signals to this report. */ + source_products?: string[]; + /** PR URL from the latest implementation task run, if available. */ + implementation_pr_url?: string | null; + work_state?: "unclaimed" | "working" | "in_review" | "done"; + assignee?: { + kind: "user" | "task" | "agent" | "system"; + task_id: string | null; + } | null; + /** + * Whether that PR merged (GitHub webhook). A merged PR is history, not work + * in flight: a report can outlive its fix when evidence keeps arriving, and + * its old PR must not read as reviewable or continuable. + */ + implementation_pr_merged?: boolean; + /** Latest known state of that PR, per the GitHub webhook. */ + implementation_pr_state?: SignalReportPrState | null; + /** Link to the tracker issue self-driving opened for this report's PR, when the project tracks issues. */ + tracker_issue_url?: string | null; + /** How that issue reads in its provider, for example '#12' or 'ENG-123'. */ + tracker_issue_reference?: string | null; + /** Why the tracker issue could not be opened, for a project that wants one. Null when it exists. */ + tracker_issue_error?: string | null; + /** Charts the report shows, placed by `[label](chart:)` links in the summary. */ + charts?: SignalReportChart[]; + /** The report's PR refund, when one exists (one refund per report, ever). */ + refund?: SignalReportRefund | null; + /** Marks reports that were never billable ("Free"), so there is nothing to refund. */ + billing_exempt_reason?: string | null; + /** Backend-owned refund eligibility: why a refund would be rejected right now, null when it would be accepted. */ + refund_ineligibility_reason?: string | null; + /** The space (task channel) this report is assigned to, or null when unassigned. The general view lists every report regardless of this value. */ + channel_id?: string | null; +} + +export type SignalReportRefundReason = + | "pr_incorrect" + | "pr_not_useful" + | "duplicate" + | "other"; + +export interface SignalReportRefund { + id: string; + reason: SignalReportRefundReason; + note?: string | null; + created_at?: string; +} + +export interface SignalReportArtefactContent { + session_id: string; + start_time: string; + end_time: string; + distinct_id: string; + content: string; + distance_to_centroid: number | null; +} + +/** + * Fields shared by every artefact row. `created_by` / `task_id` carry attribution: + * at most one is set — `created_by` for user writes, `task_id` for agent writes, + * neither for system (pipeline) writes. + */ +interface SignalReportArtefactBase { + id: string; + created_at: string; + updated_at?: string | null; + /** User the artefact is attributed to, when a user produced it. */ + created_by?: UserBasic | null; + /** Task the artefact is attributed to, when an agent produced it. */ + task_id?: string | null; + /** + * True when the row's content did not match its type's expected shape and was + * normalized to a plain text preview instead — the entry still renders rather + * than silently vanishing from the activity log. + */ + degraded?: boolean; +} + +export interface SignalReportArtefact extends SignalReportArtefactBase { + type: string; + content: SignalReportArtefactContent; +} + +/** Artefact with `type: "priority_judgment"` — priority assessment from the agentic report. */ +export interface PriorityJudgmentArtefact extends SignalReportArtefactBase { + type: "priority_judgment"; + content: PriorityJudgmentContent; +} + +export interface PriorityJudgmentContent { + explanation: string; + priority: SignalReportPriority; +} + +/** Artefact with `type: "actionability_judgment"` — actionability assessment from the agentic report. */ +export interface ActionabilityJudgmentArtefact + extends SignalReportArtefactBase { + type: "actionability_judgment"; + content: ActionabilityJudgmentContent; +} + +export interface ActionabilityJudgmentContent { + explanation: string; + actionability: SignalReportActionability; + already_addressed: boolean; +} + +/** Artefact with `type: "safety_judgment"` — the prompt-injection safety verdict for the report. */ +export interface SafetyJudgmentArtefact extends SignalReportArtefactBase { + type: "safety_judgment"; + content: SafetyJudgmentContent; +} + +export interface SafetyJudgmentContent { + /** True when the report's signals are judged safe to act on. */ + choice: boolean; + /** Why the report was judged unsafe; null when safe. */ + explanation: string | null; +} + +/** Artefact with `type: "signal_finding"` — per-signal research finding from the agentic report. */ +export interface SignalFindingArtefact extends SignalReportArtefactBase { + type: "signal_finding"; + content: SignalFindingContent; +} + +export interface SignalFindingContent { + signal_id: string; + relevant_code_paths: string[]; + relevant_commit_hashes: Record; + data_queried: string; + verified: boolean; +} + +/** Artefact with `type: "repo_selection"` - selected repository for the report run. */ +export interface RepoSelectionArtefact extends SignalReportArtefactBase { + type: "repo_selection"; + content: RepoSelectionContent; +} + +export interface RepoSelectionContent { + repository: string | null; + reason: string; +} + +/** Artefact with `type: "suggested_reviewers"` — content is an enriched reviewer list. */ +export interface SuggestedReviewersArtefact extends SignalReportArtefactBase { + type: "suggested_reviewers"; + content: SuggestedReviewer[]; +} + +/** Artefact with `type: "dismissal"` — captures the user's rationale when suppressing or resolving a report. */ +export interface DismissalArtefact extends SignalReportArtefactBase { + type: "dismissal"; + content: DismissalContent; +} + +export interface DismissalContent { + reason: ReportStateReason; + /** Optional free-form detail provided alongside the reason. */ + note: string; + /** PostHog numeric user id of the dismisser, when available. */ + user_id: number | null; + /** PostHog UUID of the dismisser, when available. */ + user_uuid: string | null; +} + +// ── Log artefacts ──────────────────────────────────────────────────────────── +// Append-but-deletable "work log" entries that accumulate on a report. Distinct +// from the status artefacts above (judgments, reviewers) which are latest-wins. +// Content shapes mirror products/signals/backend/artefact_schemas.py. + +/** Artefact with `type: "code_reference"` — a contiguous span of source lines. */ +export interface CodeReferenceArtefact extends SignalReportArtefactBase { + type: "code_reference"; + content: CodeReferenceContent; +} + +export interface CodeReferenceContent { + file_path: string; + start_line: number; + end_line: number; + contents: string; + relevance_note: string; +} + +/** Artefact with `type: "line_reference"` — a single source line callout (a point). */ +export interface LineReferenceArtefact extends SignalReportArtefactBase { + type: "line_reference"; + content: LineReferenceContent; +} + +export interface LineReferenceContent { + file_path: string; + line: number; + note: string; + /** The exact source text of the referenced line, if available. */ + contents?: string | null; +} + +/** Artefact with `type: "commit"` — one commit pushed in relation to the report. */ +export interface CommitArtefact extends SignalReportArtefactBase { + type: "commit"; + content: CommitContent; +} + +export interface CommitContent { + repository: string; + branch: string; + commit_sha: string; + message: string; + note?: string | null; +} + +/** Artefact with `type: "task_run"` — a reference to a `tasks.Task` run for the report. */ +export interface TaskRunArtefact extends SignalReportArtefactBase { + type: "task_run"; + content: TaskRunArtefactContent; +} + +export interface TaskRunArtefactContent { + task_id: string; + run_id?: string | null; + /** + * Product that ran the task — `signals` for the built-in pipeline, or a custom agent's + * product identifier (mirrors backend TaskRunArtefact). + */ + product: string; + /** + * Task type within the product — e.g. `research` / `implementation` / `repo_selection` for the + * signals pipeline, or a custom agent's type identifier. + */ + type: string; +} + +/** Artefact with `type: "note"` — a free-form note authored by an agent or by code. */ +export interface NoteArtefact extends SignalReportArtefactBase { + type: "note"; + content: NoteContent; +} + +export interface NoteContent { + note: string; + author?: string | null; +} + +/** Response from the `commit` artefact diff endpoint — the commit rendered against its parent. */ +export interface CommitDiffResponse { + /** Unified diff (patch) text introduced by the commit. */ + diff: string; + /** True when the diff was too large to return in full and has been truncated. */ + truncated: boolean; +} + +export interface SuggestedReviewerCommit { + sha: string; + url: string; + reason: string; +} + +export interface SuggestedReviewerUser { + id: number; + uuid: string; + email: string; + first_name: string; + last_name: string; +} + +import type { AvailableSuggestedReviewer } from "./inbox-types"; +export type { AvailableSuggestedReviewer }; + +export interface SuggestedReviewer { + /** Null for a reviewer with no linked GitHub account — `user` identifies them instead. */ + github_login: string | null; + /** Null on entries written before reviewers carried one; `user` still resolves from the login. */ + user_uuid?: string | null; + github_name: string | null; + relevant_commits: SuggestedReviewerCommit[]; + user: SuggestedReviewerUser | null; + reason?: string | null; + source_skill?: string | null; + source_label?: string; + explanation?: string | null; +} + +export interface SuggestedReviewerWriteEntry { + github_login?: string; + user_uuid?: string; + github_name?: string; +} + +interface MatchedSignalMetadata { + parent_signal_id: string; + match_query: string; + reason: string; +} + +interface NoMatchSignalMetadata { + reason: string; + rejected_signal_ids: string[]; +} + +export type SignalMatchMetadata = MatchedSignalMetadata | NoMatchSignalMetadata; + +export interface Signal { + signal_id: string; + content: string; + source_product: string; + source_type: string; + source_id: string; + weight: number; + timestamp: string; + extra: Record; + match_metadata?: SignalMatchMetadata | null; +} + +export interface SignalReportsResponse { + results: SignalReport[]; + count: number; +} + +export interface SignalProcessingStateResponse { + paused_until: string | null; +} + +export interface AvailableSuggestedReviewersResponse { + results: AvailableSuggestedReviewer[]; + count: number; +} + +export interface SignalReportSignalsResponse { + report: SignalReport | null; + signals: Signal[]; +} + +/** Any artefact returned by the report `artefacts/` endpoint, discriminated on `type`. */ +export type AnySignalReportArtefact = + | SignalReportArtefact + | PriorityJudgmentArtefact + | ActionabilityJudgmentArtefact + | SafetyJudgmentArtefact + | SignalFindingArtefact + | RepoSelectionArtefact + | SuggestedReviewersArtefact + | DismissalArtefact + | CodeReferenceArtefact + | LineReferenceArtefact + | CommitArtefact + | TaskRunArtefact + | NoteArtefact; + +export interface SignalReportArtefactsResponse { + results: AnySignalReportArtefact[]; + count: number; + unavailableReason?: + | "forbidden" + | "not_found" + | "invalid_payload" + | "request_failed"; +} + +import type { SignalReportOrderingField } from "./signal-types"; +export type { SignalReportOrderingField }; + +export interface SignalReportsQueryParams { + unread?: boolean; + search?: string; + limit?: number; + offset?: number; + status?: CommaSeparatedSignalReportStatuses | string; + /** + * Comma-separated sort keys (prefix `-` for descending). `status` is semantic stage + * rank (not lexicographic `status` column order). Also: `signal_count`, `total_weight`, + * `created_at`, `updated_at`, `id`. Example: `status,-total_weight`. + */ + ordering?: string; + /** Comma-separated source products — only returns reports with signals from these sources. */ + source_product?: string; + /** Comma-separated PostHog user UUIDs — only returns reports with these suggested reviewers. */ + suggested_reviewers?: string; + /** Comma-separated `P0`–`P4` priorities — only returns reports with one of these priorities. */ + priority?: string; + /** Comma-separated actionability choices. Only returns reports with one of these latest judgments. */ + actionability?: string; + /** Return the filtered total without fetching or enriching report rows. */ + count_only?: boolean; + /** + * Filter by whether a shipped implementation pull request exists. `true` keeps only PR + * reports, `false` only non-PR reports. + */ + has_implementation_pr?: boolean; + /** A space (task channel) UUID — only returns reports assigned to that space. Omit for the general view, which returns every report. */ + channel_id?: string; +} + +export interface SignalTeamConfig { + id: string; + default_autostart_priority: SignalReportPriority; + /** Team-wide default `channel_id|#channel-name` target for inbox notifications. `null` = no team default. */ + default_slack_notification_channel?: string | null; + autostart_base_branches?: Record | null; + /** Daily cap on new reports reaching the inbox, counted per project-timezone day. `null` = unlimited. */ + max_reports_per_day?: number | null; + /** Reports that first became visible today. `0` when there is no cap. Read-only. */ + reports_generated_today?: number; + /** Whether the cap is reached, pausing new reports until local midnight. `false` when there is no cap. Read-only. */ + daily_report_limit_reached?: boolean; + created_at: string; + updated_at: string; +} + +export interface SignalUserAutonomyConfig { + id?: string; + autostart_priority: SignalReportPriority | null; + /** ID of the team-scoped Slack `Integration` row used to deliver inbox-item notifications. */ + slack_notification_integration_id?: number | null; + /** `channel_id|#channel-name` target — same convention used by Insight Alerts. */ + slack_notification_channel?: string | null; + /** Minimum priority that triggers a notification (P0 highest). `null` = every priority. */ + slack_notification_min_priority?: SignalReportPriority | null; + created_at?: string; + updated_at?: string; +} + +export interface SlackChannelOption { + id: string; + name: string; + is_private: boolean; + is_member: boolean; + is_ext_shared: boolean; + is_private_without_access: boolean; +} + +export interface SlackChannelsResponse { + channels: SlackChannelOption[]; + lastRefreshedAt?: string; + has_more?: boolean; +} + +export interface SlackChannelsQueryParams { + search?: string; + limit?: number; + offset?: number; + channelId?: string; +} diff --git a/products/desktop/packages/shared/src/enrichment.ts b/products/desktop/packages/agent-contracts/src/enrichment.ts similarity index 94% rename from products/desktop/packages/shared/src/enrichment.ts rename to products/desktop/packages/agent-contracts/src/enrichment.ts index 660a6c197a8d..4c05df54de47 100644 --- a/products/desktop/packages/shared/src/enrichment.ts +++ b/products/desktop/packages/agent-contracts/src/enrichment.ts @@ -1,6 +1,6 @@ // PostHog enrichment boundary data types. These are the serialized output of the // (workspace-server) enrichment scan, consumed by the renderer to render flag/event -// annotations. They live in @posthog/shared so both the renderer (ui) and the +// annotations. They live in @posthog/agent-contracts so both the renderer (ui) and the // enricher/ws-server can import them without crossing layer boundaries. // @posthog/enricher re-exports these for its own consumers. diff --git a/products/desktop/packages/shared/src/errors.test.ts b/products/desktop/packages/agent-contracts/src/errors.test.ts similarity index 100% rename from products/desktop/packages/shared/src/errors.test.ts rename to products/desktop/packages/agent-contracts/src/errors.test.ts diff --git a/products/desktop/packages/shared/src/errors.ts b/products/desktop/packages/agent-contracts/src/errors.ts similarity index 100% rename from products/desktop/packages/shared/src/errors.ts rename to products/desktop/packages/agent-contracts/src/errors.ts diff --git a/products/desktop/packages/shared/src/exec-types.ts b/products/desktop/packages/agent-contracts/src/exec-types.ts similarity index 100% rename from products/desktop/packages/shared/src/exec-types.ts rename to products/desktop/packages/agent-contracts/src/exec-types.ts diff --git a/products/desktop/packages/shared/src/execution-modes.test.ts b/products/desktop/packages/agent-contracts/src/execution-modes.test.ts similarity index 100% rename from products/desktop/packages/shared/src/execution-modes.test.ts rename to products/desktop/packages/agent-contracts/src/execution-modes.test.ts diff --git a/products/desktop/packages/shared/src/execution-modes.ts b/products/desktop/packages/agent-contracts/src/execution-modes.ts similarity index 100% rename from products/desktop/packages/shared/src/execution-modes.ts rename to products/desktop/packages/agent-contracts/src/execution-modes.ts diff --git a/products/desktop/packages/shared/src/git-types.ts b/products/desktop/packages/agent-contracts/src/git-types.ts similarity index 100% rename from products/desktop/packages/shared/src/git-types.ts rename to products/desktop/packages/agent-contracts/src/git-types.ts diff --git a/products/desktop/packages/shared/src/image.test.ts b/products/desktop/packages/agent-contracts/src/image.test.ts similarity index 100% rename from products/desktop/packages/shared/src/image.test.ts rename to products/desktop/packages/agent-contracts/src/image.test.ts diff --git a/products/desktop/packages/shared/src/image.ts b/products/desktop/packages/agent-contracts/src/image.ts similarity index 100% rename from products/desktop/packages/shared/src/image.ts rename to products/desktop/packages/agent-contracts/src/image.ts diff --git a/products/desktop/packages/shared/src/inbox-types.test.ts b/products/desktop/packages/agent-contracts/src/inbox-types.test.ts similarity index 100% rename from products/desktop/packages/shared/src/inbox-types.test.ts rename to products/desktop/packages/agent-contracts/src/inbox-types.test.ts diff --git a/products/desktop/packages/shared/src/inbox-types.ts b/products/desktop/packages/agent-contracts/src/inbox-types.ts similarity index 100% rename from products/desktop/packages/shared/src/inbox-types.ts rename to products/desktop/packages/agent-contracts/src/inbox-types.ts diff --git a/products/desktop/packages/agent-contracts/src/index.ts b/products/desktop/packages/agent-contracts/src/index.ts new file mode 100644 index 000000000000..435b0df035b8 --- /dev/null +++ b/products/desktop/packages/agent-contracts/src/index.ts @@ -0,0 +1,299 @@ +export * from "./adapter"; +export { + buildActionUrl, + openAgentActionInput, + type ShowActionButton, + showActionSchema, + splitShowAction, +} from "./agent-actions"; +export type { + AgentContent, + AgentConversationEvent, + AgentToolCall, + AgentToolCallContent, + AgentToolCallContentBlock, + AgentToolCallLocation, + AgentToolCallStatus, + AgentToolKind, + AgentTurnUsage, +} from "./agent-conversation"; +export * from "./agent-runtime"; +export type { TaskRunUpdate } from "./api-http-client"; +export { + API_DOWNLOAD_TIMEOUT_MS, + API_TRANSFER_TIMEOUT_MS, + PostHogHttpClient, + PostHogHttpError, + transferTimeoutMs, +} from "./api-http-client"; +export { + type BackoffOptions, + getBackoffDelay, + sleepWithBackoff, +} from "./backoff"; +export * from "./bedrock-gateway"; +export { isBinaryFile } from "./binary"; +export { + boundPersistedMcpResult, + MAX_PERSISTED_MCP_RESULT_BYTES, + omitNullCallToolResultFields, +} from "./call-tool-result"; +export { + deserializeCloudPrompt, + promptBlocksToText, + serializeCloudPrompt, +} from "./cloud-prompt"; +export { + adapterForModelId, + buildCloudTaskConfigOptions, + buildProviderModelGroups, + type CloudTaskConfigOption, + type CloudTaskConfigSelectGroup, + type CloudTaskConfigSelectOption, + compareModelsForPicker, + DEFAULT_CODEX_MODEL, + DEFAULT_GATEWAY_MODEL, + formatGatewayModelName, + formatModelId, + type GatewayModel, + getClaudeModelRecency, + getCloudTaskGatewayUrl, + getProviderName, + isAnthropicModel, + isAnthropicModelId, + isBasetenModel, + isCloudflareModel, + isCloudflareModelId, + isDeepseekModelId, + isModalModel, + isModalModelId, + isOpenAIModel, + normalizeGatewayModelsResponse, + pickAllowedModel, +} from "./cloud-task-models"; +export { + type CustomCloud, + configureCustomCloud, + customCloudSchema, + getCustomCloud, + isCustomCloudHost, + normalizeCustomCloud, +} from "./custom-cloud"; +export { + DISMISSAL_REASON_OPTIONS, + type DismissalReasonOptionValue, + dismissalReasonLabel, + isDismissalReasonSnooze, + RESOLVE_REASON_OPTIONS, + type ReportStateReason, + type ResolveReasonOptionValue, +} from "./dismissal-reasons"; +export { + type ArtifactSource, + type ArtifactType, + type CloudPermissionOption, + type CloudTaskPermissionRequestUpdate, + type CloudTaskUpdatePayload, + isSkillBundleArtifactMetadata, + isTerminalStatus, + type PendingFollowupMessage, + type StoreSkillStub, + type Task, + type TaskRun, + type TaskRunArtifact, + type TaskRunArtifactMetadata, + type TaskRunEnvironment, + type TaskRunState, + type TaskRunStateField, + type TaskRunStatus, + TERMINAL_STATUSES, + taskRunStateSchema, +} from "./domain-types"; +export * from "./enrichment"; +export { + classifyGatewayLimitError, + classifyPromptFailure, + type GatewayLimitCause, + getErrorMessage, + isAuthError, + isFatalSessionError, + isNotAuthenticatedError, + isRateLimitError, + isTransientUpstreamError, + isTurnEndedWithoutResponseError, + NotAuthenticatedError, + type PromptFailure, + type PromptFailureKind, + serializeError, +} from "./errors"; +export type { ExecutionMode } from "./exec-types"; +export { + CODEX_MODE_PRESETS, + type CodexModePreset, + resolveCloudInitialPermissionMode, +} from "./execution-modes"; +export { + ALLOWED_IMAGE_MIME_TYPES, + buildImageDataUrl, + estimateBase64Bytes, + getImageMimeType, + IMAGE_MIME_TYPES, + isAllowedImageMimeType, + isClaudeImageFile, + isClaudeImageMimeType, + isGifFile, + isImageFile, + isRasterImageFile, + MAX_CLAUDE_IMAGE_BYTES, + MAX_IMAGE_BASE64_LENGTH, + parseImageDataUrl, +} from "./image"; +export type { + AvailableSuggestedReviewer, + SignalRecordKind, + SourceProduct, + SourceType, + ToggleableSourceProduct, +} from "./inbox-types"; +export { + EXTERNAL_INBOX_SOURCE_BY_PRODUCT, + EXTERNAL_INBOX_SOURCES, + filterInboxSourceOptions, + sourceNeedsFullRefresh, +} from "./inbox-types"; +export type { + AcpMcpServer, + CloudMcpServerRelayDesignation, + LocalMcpServerDescriptor, + LocalMcpServerScope, + LocalMcpTransport, + McpServerConnection, +} from "./local-mcp-domain"; +export { toAcpMcpServers } from "./local-mcp-domain"; +export { + MCP_TOOL_PERMISSION_OPTIONS, + type McpToolApprovalState, + type McpToolPermissionDecision, + type McpToolPermissionRequest, + type McpToolPolicy, +} from "./mcp-tool-policy-domain"; +export { + isOfferedModel, + isRetiredModel, + supports1MContext, + supportsFastMode, +} from "./model-catalog"; +export { + customModelMeta, + DEFAULT_OPTION_META_KEY, + defaultEligibleModel, + isCustomModelOption, + isDefaultSelectOption, + isRestrictedModelOption, + modelHarnessMeta, + OPTION_DOCS_URL_META_KEY, + restrictedModelMeta, + selectOptionDocsUrl, + selectOptionHarness, +} from "./models"; +export { + DEV_CALLBACK_PORT, + DEV_REDIRECT_URI, + getOauthClientIdFromRegion, + OAUTH_SCOPE_VERSION, + OAUTH_SCOPES, +} from "./oauth"; +export { + type AgentRunState, + agentRunStateSchema, + type PiSubagentToolCall, + type PiSubagentToolDetails, + type PiWorkflowToolDetails, + piSubagentToolCallSchema, + piSubagentToolDetailsSchema, + piWorkflowToolDetailsSchema, + type WorkflowAgentState, + workflowAgentStateSchema, +} from "./orchestration"; +export { + createPiToolCallRecord, + formatMcpToolLabel, + formatPiMcpToolName, + isPiToolName, + PI_TOOL_KIND_BY_NAME, + type PiMcpCallDetails, + type PiToolCallInput, + type PiToolCallRecord, + type PiToolName, + parsePiMcpCallDetails, + readPiMcpCallDetails, +} from "./pi-tool-call"; +export { POSTHOG_PRODUCTS, type PostHogProductId } from "./posthog-products"; +export { + buildPrOutput, + mergePrUrls, + promotePrUrl, + readPrSummaries, + readPrUrls, +} from "./pr-urls"; +export { isPrivateIpv4Octets, isPrivateIpv6Literal } from "./private-network"; +export { + type CapabilityNotch, + DEFAULT_REASONING_EFFORT, + getCapabilityLadder, + getReasoningEffortOptions, + isSupportedReasoningEffort, + type SupportedReasoningEffort, +} from "./reasoning-effort"; +export { + CLOUD_REGIONS, + type CloudRegion, + describeRegion, + REGION_LABELS, + type RegionLabel, +} from "./regions"; +export { Saga, type SagaLogger, type SagaResult } from "./saga"; +export { + type AcpMessage, + IDLE_RESUME_STOP_REASON, + IMPORTED_USER_PROMPT_META_KEY, + isIdleResumeTurnComplete, + isJsonRpcNotification, + isJsonRpcRequest, + isJsonRpcResponse, + type JsonRpcMessage, + type JsonRpcRequest, + type StoredLogEntry, + type UserShellExecuteParams, + type UserShellExecuteResult, +} from "./session-events"; +export type { + ExportedSkill, + SkillFileEntry, + SkillInfo, + SkillSource, + UploadableSkillSource, +} from "./skills"; +export { + DISABLE_MODEL_INVOCATION_METADATA_KEY, + isIgnoredSkillEntry, + isIgnoredSkillPath, + SKILL_EXISTS_MARKER, + serializeSkillMarkdown, + stripFrontmatter, +} from "./skills"; +export { leadingSlashCommand } from "./slash-commands"; +export type { PostHogAPIConfig } from "./task"; +export { + mcpToolKey, + parseMcpToolName, + posthogToolMeta, + readAgentToolName, + readMcpInstallationId, + readMcpProxyCallDetails, + readMcpToolDescriptor, + readMcpToolName, + readParentToolCallId, +} from "./tool-meta"; +export { TranscriptBoundaries } from "./transcript-neutral-messages"; +export { getCloudUrlFromRegion } from "./urls"; diff --git a/products/desktop/packages/shared/src/local-mcp-domain.ts b/products/desktop/packages/agent-contracts/src/local-mcp-domain.ts similarity index 100% rename from products/desktop/packages/shared/src/local-mcp-domain.ts rename to products/desktop/packages/agent-contracts/src/local-mcp-domain.ts diff --git a/products/desktop/packages/shared/src/mcp-tool-policy-domain.ts b/products/desktop/packages/agent-contracts/src/mcp-tool-policy-domain.ts similarity index 100% rename from products/desktop/packages/shared/src/mcp-tool-policy-domain.ts rename to products/desktop/packages/agent-contracts/src/mcp-tool-policy-domain.ts diff --git a/products/desktop/packages/shared/src/model-catalog.generated.ts b/products/desktop/packages/agent-contracts/src/model-catalog.generated.ts similarity index 100% rename from products/desktop/packages/shared/src/model-catalog.generated.ts rename to products/desktop/packages/agent-contracts/src/model-catalog.generated.ts diff --git a/products/desktop/packages/shared/src/model-catalog.test.ts b/products/desktop/packages/agent-contracts/src/model-catalog.test.ts similarity index 100% rename from products/desktop/packages/shared/src/model-catalog.test.ts rename to products/desktop/packages/agent-contracts/src/model-catalog.test.ts diff --git a/products/desktop/packages/agent-contracts/src/model-catalog.ts b/products/desktop/packages/agent-contracts/src/model-catalog.ts new file mode 100644 index 000000000000..eb9c42e653fc --- /dev/null +++ b/products/desktop/packages/agent-contracts/src/model-catalog.ts @@ -0,0 +1,146 @@ +import type { Adapter } from "./adapter"; +import { + type CatalogModel, + FALLBACK_REASONING_EFFORTS, + FAMILY_REASONING_EFFORTS, + MODELS, + PROVIDER_BY_RUNTIME_ADAPTER, + type ReasoningEffort, +} from "./model-catalog.generated"; + +export { + type CatalogModel, + COST_BASELINE_LABEL, + COST_BASELINE_MODEL, + DEFAULT_MODEL_BY_RUNTIME_ADAPTER, + MODELS, + type ModelCost, + PROVIDER_BY_RUNTIME_ADAPTER, + REASONING_EFFORTS, + type ReasoningEffort, + RUNTIME_ADAPTERS, + RUNTIME_OPTIONS, + type Runtime, + type RuntimeAdapter, + type RuntimeOption, +} from "./model-catalog.generated"; + +/** + * The form a model id is looked up under. The gateway serves some models both bare and + * provider-qualified (`openai/gpt-5.6-sol`) and a picker may hand back either, so folding + * the two together is what stops one model from having two answers. Only the provider + * prefixes the catalog knows are stripped, leaving ids that carry a slash of their own + * (`@cf/zai-org/glm-5.2`) intact. + */ +export function normalizeModelId(modelId: string): string { + const normalized = modelId.trim().toLowerCase(); + for (const provider of Object.values(PROVIDER_BY_RUNTIME_ADAPTER)) { + if (normalized.startsWith(`${provider}/`)) { + return normalized.slice(provider.length + 1); + } + } + return normalized; +} + +/** + * The efforts this model may run at, empty when it takes no effort at all. + * + * Resolved in three steps: the exact id, then the family it belongs to, then what the + * adapter accepts generally. Codex passes any `gpt-*` identifier through, so a newly + * served id still runs, while Claude has no fallback and yields nothing — which is what + * makes the backend reject it. Mirrors `reasoning_efforts_for` in + * products/tasks/backend/model_catalog.py, so a selection this offers is one a run can + * actually use. + */ +export function reasoningEffortsForModel( + adapter: Adapter, + modelId: string, +): readonly ReasoningEffort[] { + const normalized = normalizeModelId(modelId); + const model = MODELS.find( + (candidate) => + candidate.runtimeAdapter === adapter && candidate.id === normalized, + ); + if (model) return model.reasoningEfforts; + // Longest matching prefix wins, so the table's declaration order is free. + const family = FAMILY_REASONING_EFFORTS.filter( + (candidate) => + candidate.runtimeAdapter === adapter && + normalized.startsWith(candidate.prefix), + ).sort((a, b) => b.prefix.length - a.prefix.length)[0]; + return family?.reasoningEfforts ?? FALLBACK_REASONING_EFFORTS[adapter] ?? []; +} + +/** + * The catalog's entry for a model id in any form a picker hands back. The gateway serves + * ids the catalog omits, so a miss is the ordinary case and never an error. + */ +export function catalogModelFor(modelId: string): CatalogModel | undefined { + const normalized = normalizeModelId(modelId); + return MODELS.find((candidate) => candidate.id === normalized); +} + +/** + * Every access flag the catalog gates a model behind, without duplicates. + * + * A picker resolves these and hands the answers back to the filter, so gating a model is + * done by giving it an `access_flag` in products/tasks/backend/model_catalog.py and no + * picker has to learn the flag's name. Empty while no model is behind a rollout. + */ +export const MODEL_ACCESS_FLAGS: readonly string[] = [ + ...new Set( + MODELS.map((model) => model.accessFlag).filter( + (flag): flag is string => flag !== undefined, + ), + ), +]; + +/** + * The feature flag a person needs before a picker offers this model, or `undefined` when + * it is generally available. + * + * Governs display only. The server decides whether a run may use the model, so a stale + * projection can hide a model a person may use, never the other way round. + */ +export function accessFlagForModel(modelId: string): string | undefined { + return catalogModelFor(modelId)?.accessFlag; +} + +/** + * The name the catalog pins for a model, or `undefined` to let the caller format the id. + * + * Only the ids whose derived name reads wrong carry one, so a caller keeps its formatter + * for everything else. Mirrors `label_for_model` in + * products/tasks/backend/model_catalog.py, so both surfaces name a model identically. + */ +export function labelForModel(modelId: string): string | undefined { + return catalogModelFor(modelId)?.label; +} + +/** + * Whether a picker may offer this model. A model the catalog omits is not offered however + * the gateway answers, and a retired one stays listed only so a pinned session keeps + * running under a name and a price. + */ +export function isOfferedModel(modelId: string): boolean { + const model = catalogModelFor(modelId); + return model !== undefined && !model.retired; +} + +/** + * Whether the catalog has retired this model. Narrower than the inverse of `isOfferedModel`: + * an id the catalog never lists is not retired, because a runtime can still drive one — a + * person's own OpenAI subscription runs vendor variants such as `gpt-5.5-codex` that the + * gateway never serves. Ask this about the model a run uses, `isOfferedModel` about a picker. + */ +export function isRetiredModel(modelId: string): boolean { + return catalogModelFor(modelId)?.retired === true; +} + +export function supports1MContext(modelId: string): boolean { + return catalogModelFor(modelId)?.supports1MContext ?? false; +} + +export function supportsFastMode(modelId: string): boolean { + return catalogModelFor(modelId)?.supportsFastMode ?? false; +} diff --git a/products/desktop/packages/shared/src/models.test.ts b/products/desktop/packages/agent-contracts/src/models.test.ts similarity index 100% rename from products/desktop/packages/shared/src/models.test.ts rename to products/desktop/packages/agent-contracts/src/models.test.ts diff --git a/products/desktop/packages/shared/src/models.ts b/products/desktop/packages/agent-contracts/src/models.ts similarity index 100% rename from products/desktop/packages/shared/src/models.ts rename to products/desktop/packages/agent-contracts/src/models.ts diff --git a/products/desktop/packages/shared/src/oauth.test.ts b/products/desktop/packages/agent-contracts/src/oauth.test.ts similarity index 100% rename from products/desktop/packages/shared/src/oauth.test.ts rename to products/desktop/packages/agent-contracts/src/oauth.test.ts diff --git a/products/desktop/packages/shared/src/oauth.ts b/products/desktop/packages/agent-contracts/src/oauth.ts similarity index 100% rename from products/desktop/packages/shared/src/oauth.ts rename to products/desktop/packages/agent-contracts/src/oauth.ts diff --git a/products/desktop/packages/shared/src/objectTagKinds.generated.ts b/products/desktop/packages/agent-contracts/src/objectTagKinds.generated.ts similarity index 100% rename from products/desktop/packages/shared/src/objectTagKinds.generated.ts rename to products/desktop/packages/agent-contracts/src/objectTagKinds.generated.ts diff --git a/products/desktop/packages/shared/src/orchestration.ts b/products/desktop/packages/agent-contracts/src/orchestration.ts similarity index 100% rename from products/desktop/packages/shared/src/orchestration.ts rename to products/desktop/packages/agent-contracts/src/orchestration.ts diff --git a/products/desktop/packages/shared/src/pi-tool-call.test.ts b/products/desktop/packages/agent-contracts/src/pi-tool-call.test.ts similarity index 100% rename from products/desktop/packages/shared/src/pi-tool-call.test.ts rename to products/desktop/packages/agent-contracts/src/pi-tool-call.test.ts diff --git a/products/desktop/packages/shared/src/pi-tool-call.ts b/products/desktop/packages/agent-contracts/src/pi-tool-call.ts similarity index 100% rename from products/desktop/packages/shared/src/pi-tool-call.ts rename to products/desktop/packages/agent-contracts/src/pi-tool-call.ts diff --git a/products/desktop/packages/shared/src/posthog-products.ts b/products/desktop/packages/agent-contracts/src/posthog-products.ts similarity index 100% rename from products/desktop/packages/shared/src/posthog-products.ts rename to products/desktop/packages/agent-contracts/src/posthog-products.ts diff --git a/products/desktop/packages/shared/src/posthog-property-headers.test.ts b/products/desktop/packages/agent-contracts/src/posthog-property-headers.test.ts similarity index 100% rename from products/desktop/packages/shared/src/posthog-property-headers.test.ts rename to products/desktop/packages/agent-contracts/src/posthog-property-headers.test.ts diff --git a/products/desktop/packages/agent-contracts/src/posthog-property-headers.ts b/products/desktop/packages/agent-contracts/src/posthog-property-headers.ts new file mode 100644 index 000000000000..7bb92ae24dca --- /dev/null +++ b/products/desktop/packages/agent-contracts/src/posthog-property-headers.ts @@ -0,0 +1,223 @@ +export type PosthogPropertyValue = string | number | boolean | null | undefined; + +export type PosthogProperties = Record; + +export const POSTHOG_PROJECT_ID_HEADER = "X-PostHog-Project-Id"; +export const POSTHOG_TASK_RUN_ID_HEADER = "X-PostHog-Task-Run-Id"; + +/** + * Make a value safe to embed in an HTTP header value. Only printable ASCII + * survives: latin1 is valid per RFC 9110 and undici accepts it, but Bun's + * fetch — which the Claude Code CLI uses for `ANTHROPIC_CUSTOM_HEADERS` — + * rejects any non-ASCII header value. NFKD plus the final strip is what + * transliterates accented letters to their ASCII base (`più` → `piu`); the + * combining-mark pass just keeps a stray mark from splitting a newline run + * before it is collapsed to a single space. + */ +function sanitizeHeaderValue(value: string): string { + return value + .normalize("NFKD") + .replace(/[\u0300-\u036f]/g, "") + .replace(/[\r\n]+/g, " ") + .replace(/[^\x20-\x7e]/g, ""); +} + +function buildEntries(properties: PosthogProperties): Array<[string, string]> { + const entries: Array<[string, string]> = []; + for (const [key, value] of Object.entries(properties)) { + if (value === null || value === undefined) continue; + entries.push([ + `x-posthog-property-${key}`, + sanitizeHeaderValue(String(value)), + ]); + } + return entries; +} + +function taskRunIdHeader(properties: PosthogProperties): string | null { + const taskRunId = properties.task_run_id; + return typeof taskRunId === "string" ? sanitizeHeaderValue(taskRunId) : null; +} + +/** + * Build a `Record` of `x-posthog-property-` headers + * suitable for `fetch()` init.headers. The LLM gateway lifts each header + * onto the `$ai_generation` event it captures + * (see `services/llm-gateway/src/llm_gateway/request_context.py` in + * posthog/posthog). `null`/`undefined` values are dropped; values are + * sanitized via {@link sanitizeHeaderValue}. + */ +export function buildPosthogPropertyHeaderRecord( + properties: PosthogProperties, +): Record { + const taskRunId = taskRunIdHeader(properties); + return { + ...Object.fromEntries(buildEntries(properties)), + ...(taskRunId ? { [POSTHOG_TASK_RUN_ID_HEADER]: taskRunId } : {}), + }; +} + +/** + * Same property semantics as {@link buildPosthogPropertyHeaderRecord}, but + * returns a newline-joined string of `key: value` lines — the format + * `ANTHROPIC_CUSTOM_HEADERS` expects when wiring headers into the Claude + * Agent SDK. + */ +export function buildPosthogPropertyHeaderLines( + properties: PosthogProperties, +): string { + return Object.entries(buildPosthogPropertyHeaderRecord(properties)) + .map(([key, value]) => `${key}: ${value}`) + .join("\n"); +} + +/** + * Attribution node header for the person a request is spent on behalf of. The + * gateway keys its per-user spend limit on this value, so it must be the same + * node the spend-limit endpoint writes the limit against: the user's distinct + * id, not their uuid (see products/ai_gateway/backend/logic.py, _spend_node). + * + * Trust model: for local sessions this header is asserted by the client, so + * the limit it keys is a self-imposed guardrail, not a security boundary. + * Cloud runs pin the node server-side into the run's scoped token. + */ +const POSTHOG_USER_HEADER = "X-PostHog-User"; + +export function buildPosthogUserHeaderRecord( + userNode: string | null | undefined, +): Record { + return userNode + ? { [POSTHOG_USER_HEADER]: sanitizeHeaderValue(userNode) } + : {}; +} + +export function buildPosthogUserHeaderLines( + userNode: string | null | undefined, +): string { + return userNode + ? `${POSTHOG_USER_HEADER}: ${sanitizeHeaderValue(userNode)}` + : ""; +} + +export function buildPosthogProjectHeaderRecord( + projectId: number | null | undefined, +): Record { + return projectId ? { [POSTHOG_PROJECT_ID_HEADER]: String(projectId) } : {}; +} + +export function buildPosthogProjectHeaderLines( + projectId: number | null | undefined, +): string { + return projectId ? `${POSTHOG_PROJECT_ID_HEADER}: ${projectId}` : ""; +} + +export function buildPosthogScopedPropertyHeaderRecord( + properties: PosthogProperties, + projectId: number | null | undefined, +): Record { + return { + ...buildPosthogPropertyHeaderRecord(properties), + ...buildPosthogProjectHeaderRecord(projectId), + }; +} + +export function buildPosthogScopedPropertyHeaderLines( + properties: PosthogProperties, + projectId: number | null | undefined, +): string { + return [ + buildPosthogPropertyHeaderLines(properties), + buildPosthogProjectHeaderLines(projectId), + ] + .filter(Boolean) + .join("\n"); +} + +/** Header carrying the whole property set as one JSON object. */ +const POSTHOG_PROPERTIES_HEADER = "X-PostHog-Properties"; + +/** + * Byte cap the Go gateway enforces on {@link POSTHOG_PROPERTIES_HEADER}; a + * larger blob is rejected outright, losing every property including + * `ai_product`. Keep in sync with `maxPropertiesLen` in the gateway's + * `internal/httpapi/dispatch.go`. + */ +const MAX_PROPERTIES_BYTES = 8192; + +const encoder = new TextEncoder(); + +function byteLength(value: string): number { + return encoder.encode(value).length; +} + +/** + * Serialize properties for {@link POSTHOG_PROPERTIES_HEADER}, or `""` when + * there is nothing to send. + * + * Keys beginning with `$` are dropped: the gateway strips reserved `$ai_*` + * keys at the header boundary, so sending them silently loses them. Values + * are sanitized like the per-property headers so a stray control character + * cannot break the header block. + * + * Callers supply free-text values (task titles), so the blob can exceed + * {@link MAX_PROPERTIES_BYTES}. Rather than let the gateway reject the whole + * header, the longest string values are dropped one at a time until it fits, + * preferring to lose descriptive text over attribution keys, which are short. + */ +export function buildPosthogPropertiesBlob( + properties: PosthogProperties, +): string { + const clean: PosthogProperties = {}; + for (const [key, value] of Object.entries(properties)) { + if (value === null || value === undefined) continue; + if (key.startsWith("$")) continue; + // Keys are sanitized too: unlike the per-property headers, where the key + // becomes the header name, here it is serialized into the header value, + // so a non-ASCII key would make the whole request unsendable. + const safeKey = sanitizeHeaderValue(key); + if (!safeKey) continue; + clean[safeKey] = + typeof value === "string" ? sanitizeHeaderValue(value) : value; + } + if (Object.keys(clean).length === 0) return ""; + + let blob = JSON.stringify(clean); + while (byteLength(blob) > MAX_PROPERTIES_BYTES) { + const longest = Object.entries(clean) + .filter(([, value]) => typeof value === "string") + .sort((a, b) => (b[1] as string).length - (a[1] as string).length)[0]; + // Only non-string values remain and they still overflow: send nothing + // rather than a blob the gateway will reject. + if (!longest) return ""; + delete clean[longest[0]]; + blob = JSON.stringify(clean); + } + return blob; +} + +/** + * {@link buildPosthogPropertiesBlob} as a `fetch()`-ready header record, empty + * when there is nothing to send. + */ +export function buildPosthogPropertiesHeaderRecord( + properties: PosthogProperties, +): Record { + const blob = buildPosthogPropertiesBlob(properties); + const taskRunId = taskRunIdHeader(properties); + return { + ...(blob ? { [POSTHOG_PROPERTIES_HEADER]: blob } : {}), + ...(taskRunId ? { [POSTHOG_TASK_RUN_ID_HEADER]: taskRunId } : {}), + }; +} + +/** + * {@link buildPosthogPropertiesHeaderRecord} as `key: value` lines for + * `ANTHROPIC_CUSTOM_HEADERS`, empty when there is nothing to send. + */ +export function buildPosthogPropertiesHeaderLines( + properties: PosthogProperties, +): string { + return Object.entries(buildPosthogPropertiesHeaderRecord(properties)) + .map(([key, value]) => `${key}: ${value}`) + .join("\n"); +} diff --git a/products/desktop/packages/shared/src/pr-urls.test.ts b/products/desktop/packages/agent-contracts/src/pr-urls.test.ts similarity index 100% rename from products/desktop/packages/shared/src/pr-urls.test.ts rename to products/desktop/packages/agent-contracts/src/pr-urls.test.ts diff --git a/products/desktop/packages/shared/src/pr-urls.ts b/products/desktop/packages/agent-contracts/src/pr-urls.ts similarity index 100% rename from products/desktop/packages/shared/src/pr-urls.ts rename to products/desktop/packages/agent-contracts/src/pr-urls.ts diff --git a/products/desktop/packages/shared/src/private-network.test.ts b/products/desktop/packages/agent-contracts/src/private-network.test.ts similarity index 100% rename from products/desktop/packages/shared/src/private-network.test.ts rename to products/desktop/packages/agent-contracts/src/private-network.test.ts diff --git a/products/desktop/packages/shared/src/private-network.ts b/products/desktop/packages/agent-contracts/src/private-network.ts similarity index 100% rename from products/desktop/packages/shared/src/private-network.ts rename to products/desktop/packages/agent-contracts/src/private-network.ts diff --git a/products/desktop/packages/shared/src/product-engineer-prompt.test.ts b/products/desktop/packages/agent-contracts/src/product-engineer-prompt.test.ts similarity index 100% rename from products/desktop/packages/shared/src/product-engineer-prompt.test.ts rename to products/desktop/packages/agent-contracts/src/product-engineer-prompt.test.ts diff --git a/products/desktop/packages/agent-contracts/src/product-engineer-prompt.ts b/products/desktop/packages/agent-contracts/src/product-engineer-prompt.ts new file mode 100644 index 000000000000..aa2522c302ff --- /dev/null +++ b/products/desktop/packages/agent-contracts/src/product-engineer-prompt.ts @@ -0,0 +1,47 @@ +export const SIMPLIFIED_TECHNICAL_ENGLISH_INSTRUCTION = + "Talk and write only in Simplified Technical English (ASD-STE100)."; + +export const PRODUCT_ENGINEER_PROMPT = `Operate as an expert product engineer. Use PostHog as the default platform for understanding users, observing quality, and shipping changes safely: +- Start from the user problem, desired experience, and product context. Understand why the work matters before deciding what to build. +- Use available evidence such as user feedback, product data, support signals, market context, and company strategy. Ask for missing context when it would change the decision. +- Exercise product judgment. Help decide both what to build and how to build it instead of treating the request as a fixed specification. +- Choose the smallest valuable solution that creates a fast, useful feedback loop. Prototype, descope, and avoid polishing assumptions that real users have not validated. +- Own the complete experience across implementation, usability, reliability, privacy, security, rollout, documentation, and support. Do not stop when the code compiles or the narrow task is complete. +- Ship safely and learn from reality. Use tests, feature flags, staged rollouts, and experiments when they fit the risk and uncertainty. +- Define what success means and make it observable. Measure adoption, outcomes, and failures. +- Follow through after shipping. Inspect usage and feedback, iterate on what works, and remove complexity that does not earn its place. + +Choose the smallest set of PostHog products that solves the user problem. Know PostHog's product catalog: +- Product Analytics: Analyze event-based user behavior with trends, funnels, retention, paths, and cohorts. +- Web Analytics: Measure website traffic, acquisition, pages, conversions, and performance. +- Session Replay: Record and replay real user sessions to see behavior and friction. +- Feature Flags: Control who gets features and manage progressive rollouts. +- Experiments: Run A/B tests and measure causal impact against product metrics. +- Surveys: Collect in-product user feedback and link responses to behavior. +- Error Tracking: Capture, group, and diagnose exceptions with user and session context. +- Managed warehouse: Use a PostHog-managed analytical Postgres database that combines PostHog and connected source data. +- Data pipelines: Send event and warehouse data to external destinations in real time or batch. +- PostHog AI: Query product data, build insights, write SQL, find replays, and explain findings in plain English. +- AI Observability: Trace LLM calls, prompts, responses, tools, latency, tokens, and cost. +- Logs: Ingest, search, and retain structured application logs. +- Workflows: Trigger automated messages and actions from product behavior. +- Inbox: Review and steer prioritized self-driving reports and pull requests before they ship. +- Replay Vision: Use AI to turn session recordings into structured, queryable data. + +Use PostHog throughout the product loop: +- Before building, use \`mcp\` to inspect relevant PostHog project data, existing events, feature flags, errors, logs, traces, insights, and user feedback when they would improve the decision. Search before calling. Read or list before writing or creating, and reuse existing resources. +- Before adding instrumentation, inspect the existing PostHog SDK setup and invoke the matching bundled instrumentation skill. Do not install duplicate SDKs or create parallel initialization paths. +- Use product analytics for meaningful user actions and outcomes, not low-value implementation events. Never capture secrets, sensitive content, or unnecessary personal data. +- Use PostHog feature flags for uncertain or risky rollouts. Use PostHog error tracking, logs, and traces to make failures diagnosable. Use PostHog AI Observability for AI model calls. +- After shipping, use PostHog to verify adoption, outcomes, regressions, and rollout health. Let observed behavior drive the next iteration. + +Prefer customer impact and product quality over technical novelty. Treat code as one tool for creating useful, measurable outcomes.`; + +export function prependProductEngineerPrompt(prompt: string): string { + if (prompt.includes(PRODUCT_ENGINEER_PROMPT)) { + return prompt; + } + return prompt + ? `${PRODUCT_ENGINEER_PROMPT}\n\n${prompt}` + : PRODUCT_ENGINEER_PROMPT; +} diff --git a/products/desktop/packages/shared/src/reasoning-effort.test.ts b/products/desktop/packages/agent-contracts/src/reasoning-effort.test.ts similarity index 100% rename from products/desktop/packages/shared/src/reasoning-effort.test.ts rename to products/desktop/packages/agent-contracts/src/reasoning-effort.test.ts diff --git a/products/desktop/packages/shared/src/reasoning-effort.ts b/products/desktop/packages/agent-contracts/src/reasoning-effort.ts similarity index 100% rename from products/desktop/packages/shared/src/reasoning-effort.ts rename to products/desktop/packages/agent-contracts/src/reasoning-effort.ts diff --git a/products/desktop/packages/shared/src/regions.test.ts b/products/desktop/packages/agent-contracts/src/regions.test.ts similarity index 100% rename from products/desktop/packages/shared/src/regions.test.ts rename to products/desktop/packages/agent-contracts/src/regions.test.ts diff --git a/products/desktop/packages/shared/src/regions.ts b/products/desktop/packages/agent-contracts/src/regions.ts similarity index 100% rename from products/desktop/packages/shared/src/regions.ts rename to products/desktop/packages/agent-contracts/src/regions.ts diff --git a/products/desktop/packages/shared/src/rich-output-prompt.test.ts b/products/desktop/packages/agent-contracts/src/rich-output-prompt.test.ts similarity index 100% rename from products/desktop/packages/shared/src/rich-output-prompt.test.ts rename to products/desktop/packages/agent-contracts/src/rich-output-prompt.test.ts diff --git a/products/desktop/packages/agent-contracts/src/rich-output-prompt.ts b/products/desktop/packages/agent-contracts/src/rich-output-prompt.ts new file mode 100644 index 000000000000..f2a093d7ffb4 --- /dev/null +++ b/products/desktop/packages/agent-contracts/src/rich-output-prompt.ts @@ -0,0 +1,38 @@ +import { OBJECT_TAG_PROMPT_KIND_LIST } from "./objectTagKinds.generated"; + +/** + * Prompt block teaching an agent the object-tag vocabulary the desktop + * renders as live references (chips, hover previews, chart cards). Shared by + * every agent runtime so its syntax stays in sync with what `remarkObjectTags` + * parses; the kind list itself is generated from the registry + * (posthog/object_tags/kinds.py) so the prompt cannot drift from what the + * renderers understand. + */ +export const RICH_OUTPUT_TAGS_PROMPT = `Embed the PostHog objects behind your conclusions as XML tags, the same convention as \`\` attachments. Every tag is a live reference the app resolves when shown - never restate the object's data in your text, and never put tags inside code fences. +- Inline reference: \`short human label\` inside a sentence, e.g. \`The checkout funnel dropped after new-checkout-flow rolled out.\` Kinds: ${OBJECT_TAG_PROMPT_KIND_LIST}. Use the object's id (insights: the short id; feature flags: the numeric id, falling back to the key; Inbox reports: the report uuid; persons: the uuid). It renders as a chip with a live hover preview that opens the object in PostHog. +- Inline SQL: \`SELECT count() FROM events WHERE ...\` - the SQL is the tag body, the label is what the sentence shows. Hovering runs the query live; clicking opens the SQL editor. +- Full-size chart, for any numeric or time-series answer (always prefer this over a markdown table): a saved insight \`\` or a query \`SELECT ...\`. The chart executes live on every view. Include the time range in the title, and keep blank lines out of the SQL body. +- Some PostHog MCP query tools render their result as an interactive chart in the conversation, and the tool result says so. When the tool result says the user already sees the result as an interactive view, do not embed the same data again as a \`\` chart; write the conclusion in text and let that view carry the data. When it does not, the tool renders nothing on its own, so follow the full-size chart rule above. +- Recording card: \`\` renders the recording's details with a link into PostHog's player. Use it when a specific session is the evidence.`; + +export function appendRichOutputPrompt( + prompt: string, + interactionOrigin?: string | null, +): string { + if ( + interactionOrigin && + interactionOrigin !== "desktop" && + interactionOrigin !== "signal_report" + ) { + return prompt + .replaceAll( + `\n\n## Rich output in replies\n${RICH_OUTPUT_TAGS_PROMPT}`, + "", + ) + .replaceAll(RICH_OUTPUT_TAGS_PROMPT, ""); + } + if (prompt.includes(RICH_OUTPUT_TAGS_PROMPT)) { + return prompt; + } + return `${prompt}\n\n## Rich output in replies\n${RICH_OUTPUT_TAGS_PROMPT}`; +} diff --git a/products/desktop/packages/shared/src/saga.ts b/products/desktop/packages/agent-contracts/src/saga.ts similarity index 100% rename from products/desktop/packages/shared/src/saga.ts rename to products/desktop/packages/agent-contracts/src/saga.ts diff --git a/products/desktop/packages/shared/src/sandbox-env.ts b/products/desktop/packages/agent-contracts/src/sandbox-env.ts similarity index 100% rename from products/desktop/packages/shared/src/sandbox-env.ts rename to products/desktop/packages/agent-contracts/src/sandbox-env.ts diff --git a/products/desktop/packages/agent-contracts/src/schemas.ts b/products/desktop/packages/agent-contracts/src/schemas.ts new file mode 100644 index 000000000000..32b774b82aa7 --- /dev/null +++ b/products/desktop/packages/agent-contracts/src/schemas.ts @@ -0,0 +1,44 @@ +import { z } from "zod"; + +const spaceGoalPeriodSchema = z.enum(["day", "week", "month"]); +const spaceGoalDirectionSchema = z.enum(["at_least", "at_most"]); + +export const spaceGoalInputSchema = z.object({ + statement: z.string().trim().min(1).max(2000), + period: spaceGoalPeriodSchema, + direction: spaceGoalDirectionSchema, + target: z.string().max(64).nullish(), + deadline: z.iso.date().nullish(), + insight_short_id: z.string().max(64).nullish(), +}); + +export const spaceFeatureInputSchema = z.object({ + name: z.string().trim().min(1).max(200), + description: z.string().max(2000).optional(), + flag_key: z.string().max(400).nullish(), +}); + +const repositorySchema = z.string().max(255).nullish(); + +export const spaceSetupInputSchema = z.discriminatedUnion("kind", [ + z.object({ + kind: z.literal("goal"), + goal: spaceGoalInputSchema, + repository: repositorySchema, + }), + z.object({ + kind: z.literal("feature"), + feature: spaceFeatureInputSchema, + repository: repositorySchema, + }), +]); + +export const spaceSetupStartedSchema = z.object({ task_id: z.uuid() }); + +export type SpaceGoalPeriod = z.infer; +export type SpaceGoalDirection = z.infer; +export type SpaceGoalInput = z.infer; +export type SpaceFeatureInput = z.infer; +export type SpaceSetupInput = z.infer; +export type SpaceSetupKind = SpaceSetupInput["kind"]; +export type SpaceSetupStarted = z.infer; diff --git a/products/desktop/packages/shared/src/session-events.ts b/products/desktop/packages/agent-contracts/src/session-events.ts similarity index 100% rename from products/desktop/packages/shared/src/session-events.ts rename to products/desktop/packages/agent-contracts/src/session-events.ts diff --git a/products/desktop/packages/shared/src/signal-types.ts b/products/desktop/packages/agent-contracts/src/signal-types.ts similarity index 100% rename from products/desktop/packages/shared/src/signal-types.ts rename to products/desktop/packages/agent-contracts/src/signal-types.ts diff --git a/products/desktop/packages/shared/src/skills.test.ts b/products/desktop/packages/agent-contracts/src/skills.test.ts similarity index 100% rename from products/desktop/packages/shared/src/skills.test.ts rename to products/desktop/packages/agent-contracts/src/skills.test.ts diff --git a/products/desktop/packages/shared/src/skills.ts b/products/desktop/packages/agent-contracts/src/skills.ts similarity index 100% rename from products/desktop/packages/shared/src/skills.ts rename to products/desktop/packages/agent-contracts/src/skills.ts diff --git a/products/desktop/packages/shared/src/slash-commands.test.ts b/products/desktop/packages/agent-contracts/src/slash-commands.test.ts similarity index 100% rename from products/desktop/packages/shared/src/slash-commands.test.ts rename to products/desktop/packages/agent-contracts/src/slash-commands.test.ts diff --git a/products/desktop/packages/shared/src/slash-commands.ts b/products/desktop/packages/agent-contracts/src/slash-commands.ts similarity index 100% rename from products/desktop/packages/shared/src/slash-commands.ts rename to products/desktop/packages/agent-contracts/src/slash-commands.ts diff --git a/products/desktop/packages/agent-contracts/src/task-context.ts b/products/desktop/packages/agent-contracts/src/task-context.ts new file mode 100644 index 000000000000..59b3fbbed86c --- /dev/null +++ b/products/desktop/packages/agent-contracts/src/task-context.ts @@ -0,0 +1,14 @@ +export interface TaskContextInput { + taskId: string; + cwd: string; + customInstructions?: string; + additionalDirectories?: string[]; + channelMode?: boolean; +} + +export interface TaskContext extends TaskContextInput { + projectId: number; + apiHost: string; + environment: "local" | "cloud"; + additionalInstructions?: string; +} diff --git a/products/desktop/packages/shared/src/task.test.ts b/products/desktop/packages/agent-contracts/src/task.test.ts similarity index 100% rename from products/desktop/packages/shared/src/task.test.ts rename to products/desktop/packages/agent-contracts/src/task.test.ts diff --git a/products/desktop/packages/shared/src/task.ts b/products/desktop/packages/agent-contracts/src/task.ts similarity index 100% rename from products/desktop/packages/shared/src/task.ts rename to products/desktop/packages/agent-contracts/src/task.ts diff --git a/products/desktop/packages/shared/src/tool-meta.test.ts b/products/desktop/packages/agent-contracts/src/tool-meta.test.ts similarity index 100% rename from products/desktop/packages/shared/src/tool-meta.test.ts rename to products/desktop/packages/agent-contracts/src/tool-meta.test.ts diff --git a/products/desktop/packages/shared/src/tool-meta.ts b/products/desktop/packages/agent-contracts/src/tool-meta.ts similarity index 100% rename from products/desktop/packages/shared/src/tool-meta.ts rename to products/desktop/packages/agent-contracts/src/tool-meta.ts diff --git a/products/desktop/packages/shared/src/transcript-neutral-messages.ts b/products/desktop/packages/agent-contracts/src/transcript-neutral-messages.ts similarity index 100% rename from products/desktop/packages/shared/src/transcript-neutral-messages.ts rename to products/desktop/packages/agent-contracts/src/transcript-neutral-messages.ts diff --git a/products/desktop/packages/shared/src/urls.ts b/products/desktop/packages/agent-contracts/src/urls.ts similarity index 100% rename from products/desktop/packages/shared/src/urls.ts rename to products/desktop/packages/agent-contracts/src/urls.ts diff --git a/products/desktop/packages/agent-contracts/tsconfig.json b/products/desktop/packages/agent-contracts/tsconfig.json new file mode 100644 index 000000000000..a5668cdab418 --- /dev/null +++ b/products/desktop/packages/agent-contracts/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "outDir": "dist", + "lib": ["ESNext"], + "target": "ES2022", + "module": "ESNext", + "moduleDetection": "force", + "allowJs": true, + "types": ["node"], + "moduleResolution": "bundler", + "resolveJsonModule": true, + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + "noUnusedLocals": false, + "noUnusedParameters": false + }, + "include": ["src/**/*"], + "exclude": ["node_modules"] +} diff --git a/products/desktop/packages/agent-contracts/tsup.config.ts b/products/desktop/packages/agent-contracts/tsup.config.ts new file mode 100644 index 000000000000..03bd436ced86 --- /dev/null +++ b/products/desktop/packages/agent-contracts/tsup.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from "tsup"; + +export default defineConfig({ + entry: ["src/**/*.ts", "!src/**/*.test.ts"], + format: ["esm"], + dts: true, + sourcemap: true, + clean: true, + // One module instance across entries, so class identity survives mixed barrel and subpath imports. + splitting: true, + outDir: "dist", + target: "node20", +}); diff --git a/products/desktop/packages/agent-contracts/vitest.config.ts b/products/desktop/packages/agent-contracts/vitest.config.ts new file mode 100644 index 000000000000..407c36b9538d --- /dev/null +++ b/products/desktop/packages/agent-contracts/vitest.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from "vitest/config"; +import { trunkTestOptions } from "../../vitest.config.base"; + +export default defineConfig({ + test: { + globals: true, + ...trunkTestOptions, + environment: "node", + include: ["src/**/*.test.ts"], + exclude: ["**/node_modules/**", "**/dist/**"], + }, +}); diff --git a/products/desktop/packages/agent/e2e/config.ts b/products/desktop/packages/agent/e2e/config.ts index 66d8a8b950b4..4df94bc061fe 100644 --- a/products/desktop/packages/agent/e2e/config.ts +++ b/products/desktop/packages/agent/e2e/config.ts @@ -1,6 +1,6 @@ import { existsSync } from "node:fs"; import { join } from "node:path"; -import type { Adapter } from "@posthog/shared"; +import type { Adapter } from "@posthog/agent-contracts"; export type { Adapter }; diff --git a/products/desktop/packages/agent/e2e/session-startup.e2e.test.ts b/products/desktop/packages/agent/e2e/session-startup.e2e.test.ts index 25e296a4714d..716ca4e67234 100644 --- a/products/desktop/packages/agent/e2e/session-startup.e2e.test.ts +++ b/products/desktop/packages/agent/e2e/session-startup.e2e.test.ts @@ -12,7 +12,7 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { pathToFileURL } from "node:url"; import { ClientSideConnection, ndJsonStream } from "@agentclientprotocol/sdk"; -import { DEFAULT_GATEWAY_MODEL } from "@posthog/shared"; +import { DEFAULT_GATEWAY_MODEL } from "@posthog/agent-contracts"; import { expect, it, vi } from "vitest"; import { createAcpConnection } from "../src/adapters/acp-connection"; import { withTimeout } from "../src/utils/common"; diff --git a/products/desktop/packages/agent/package.json b/products/desktop/packages/agent/package.json index 307dd345a9e8..c69c13544e84 100644 --- a/products/desktop/packages/agent/package.json +++ b/products/desktop/packages/agent/package.json @@ -159,7 +159,7 @@ "node": ">=20.0.0" }, "devDependencies": { - "@posthog/shared": "workspace:*", + "@posthog/agent-contracts": "workspace:*", "@posthog/git": "workspace:*", "@posthog/harness": "workspace:*", "@types/bun": "1.3.14", diff --git a/products/desktop/packages/agent/src/adapters/acp-connection.ts b/products/desktop/packages/agent/src/adapters/acp-connection.ts index 4a686b043296..6b4b5e8a3257 100644 --- a/products/desktop/packages/agent/src/adapters/acp-connection.ts +++ b/products/desktop/packages/agent/src/adapters/acp-connection.ts @@ -1,6 +1,6 @@ import { AgentSideConnection, ndJsonStream } from "@agentclientprotocol/sdk"; +import type { Adapter } from "@posthog/agent-contracts"; import type { ContextWikiEnv } from "@posthog/harness/extensions/context-wiki"; -import type { Adapter } from "@posthog/shared"; import { withTurnTraceId } from "../acp-extensions"; import type { ModelInfo } from "../gateway-models"; import type { SessionLogWriter } from "../session-log-writer"; diff --git a/products/desktop/packages/agent/src/adapters/base-acp-agent.ts b/products/desktop/packages/agent/src/adapters/base-acp-agent.ts index 53ced5c1dfa5..3c5b1adbfae5 100644 --- a/products/desktop/packages/agent/src/adapters/base-acp-agent.ts +++ b/products/desktop/packages/agent/src/adapters/base-acp-agent.ts @@ -20,7 +20,7 @@ import { customModelMeta, isAnthropicModelId, restrictedModelMeta, -} from "@posthog/shared"; +} from "@posthog/agent-contracts"; import { compareModelsForPicker, DEFAULT_GATEWAY_MODEL, diff --git a/products/desktop/packages/agent/src/adapters/claude/claude-agent.ts b/products/desktop/packages/agent/src/adapters/claude/claude-agent.ts index 28ab7be510fb..f6352f3a486a 100644 --- a/products/desktop/packages/agent/src/adapters/claude/claude-agent.ts +++ b/products/desktop/packages/agent/src/adapters/claude/claude-agent.ts @@ -45,6 +45,7 @@ import { type SDKUserMessage, type SlashCommand, } from "@anthropic-ai/claude-agent-sdk"; +import { leadingSlashCommand, serializeError } from "@posthog/agent-contracts"; import type { ContextWikiEnv } from "@posthog/harness/extensions/context-wiki"; import { createEnrichment, @@ -64,7 +65,6 @@ import { type PostHogProductId, resolvePostHogExecPermissionRegex, } from "@posthog/harness/extensions/posthog-mcp-policy"; -import { leadingSlashCommand, serializeError } from "@posthog/shared"; import { v7 as uuidv7 } from "uuid"; import packageJson from "../../../package.json" with { type: "json" }; import { diff --git a/products/desktop/packages/agent/src/adapters/claude/conversion/acp-to-sdk.ts b/products/desktop/packages/agent/src/adapters/claude/conversion/acp-to-sdk.ts index 3a7eb4be5494..65e9b3dc443b 100644 --- a/products/desktop/packages/agent/src/adapters/claude/conversion/acp-to-sdk.ts +++ b/products/desktop/packages/agent/src/adapters/claude/conversion/acp-to-sdk.ts @@ -8,13 +8,13 @@ import { isClaudeImageMimeType, isImageFile, MAX_CLAUDE_IMAGE_BYTES, -} from "@posthog/shared"; +} from "@posthog/agent-contracts"; import { isLocalSkillCommandChunk } from "../../local-skill"; const PDF_EXTENSIONS = new Set(["pdf"]); // Video-only on purpose: audio formats get the default "large text" hint. -// Do not replace with AUDIO_VIDEO_EXTENSIONS from @posthog/shared. +// Do not replace with AUDIO_VIDEO_EXTENSIONS from @posthog/agent-contracts. const VIDEO_EXTENSIONS = new Set([ "mp4", "mov", diff --git a/products/desktop/packages/agent/src/adapters/claude/conversion/sdk-to-acp.ts b/products/desktop/packages/agent/src/adapters/claude/conversion/sdk-to-acp.ts index 8aab8b168ce4..513347ad0d71 100644 --- a/products/desktop/packages/agent/src/adapters/claude/conversion/sdk-to-acp.ts +++ b/products/desktop/packages/agent/src/adapters/claude/conversion/sdk-to-acp.ts @@ -21,7 +21,7 @@ import type { BetaContentBlock, BetaRawContentBlockDelta, } from "@anthropic-ai/sdk/resources/beta.mjs"; -import { IMPORTED_USER_PROMPT_META_KEY } from "@posthog/shared"; +import { IMPORTED_USER_PROMPT_META_KEY } from "@posthog/agent-contracts"; import { POSTHOG_NOTIFICATIONS } from "@/acp-extensions"; import { image, text } from "../../../utils/acp-content"; import { unreachable } from "../../../utils/common"; diff --git a/products/desktop/packages/agent/src/adapters/claude/image-sanitization.ts b/products/desktop/packages/agent/src/adapters/claude/image-sanitization.ts index 272ff008d73d..3fefcfa256c7 100644 --- a/products/desktop/packages/agent/src/adapters/claude/image-sanitization.ts +++ b/products/desktop/packages/agent/src/adapters/claude/image-sanitization.ts @@ -2,7 +2,7 @@ import { estimateBase64Bytes, isClaudeImageMimeType, MAX_CLAUDE_IMAGE_BYTES, -} from "@posthog/shared"; +} from "@posthog/agent-contracts"; function unprocessableImageReason(value: unknown): string | null { if (!value || typeof value !== "object") return null; diff --git a/products/desktop/packages/agent/src/adapters/claude/session/mcp-config.ts b/products/desktop/packages/agent/src/adapters/claude/session/mcp-config.ts index d629f618291e..657fca1262ef 100644 --- a/products/desktop/packages/agent/src/adapters/claude/session/mcp-config.ts +++ b/products/desktop/packages/agent/src/adapters/claude/session/mcp-config.ts @@ -7,7 +7,7 @@ import type { LocalMcpServerDescriptor, LocalMcpServerScope, LocalMcpTransport, -} from "@posthog/shared"; +} from "@posthog/agent-contracts"; import type { Logger } from "../../../utils/logger"; export interface ClaudeJsonMcpServerEntry { @@ -163,7 +163,7 @@ function toTransport(config: McpServerConfig): LocalMcpTransport { /** * The user's ~/.claude.json MCP servers as host-agnostic descriptors - * (`@posthog/shared`), with the raw config normalized per transport and stdio + * (`@posthog/agent-contracts`), with the raw config normalized per transport and stdio * `env` values dropped — they routinely hold secrets consumers of the * descriptor shape have no use for. */ diff --git a/products/desktop/packages/agent/src/adapters/claude/session/models.test.ts b/products/desktop/packages/agent/src/adapters/claude/session/models.test.ts index 33abb3032fea..0e4812bc0dfd 100644 --- a/products/desktop/packages/agent/src/adapters/claude/session/models.test.ts +++ b/products/desktop/packages/agent/src/adapters/claude/session/models.test.ts @@ -1,4 +1,7 @@ -import { isDefaultSelectOption, selectOptionDocsUrl } from "@posthog/shared"; +import { + isDefaultSelectOption, + selectOptionDocsUrl, +} from "@posthog/agent-contracts"; import { describe, expect, it } from "vitest"; import { DEFAULT_EFFORT, diff --git a/products/desktop/packages/agent/src/adapters/claude/session/models.ts b/products/desktop/packages/agent/src/adapters/claude/session/models.ts index 2dc40c272a0a..c10517d87a0c 100644 --- a/products/desktop/packages/agent/src/adapters/claude/session/models.ts +++ b/products/desktop/packages/agent/src/adapters/claude/session/models.ts @@ -3,12 +3,12 @@ import { OPTION_DOCS_URL_META_KEY, supports1MContext, supportsFastMode, -} from "@posthog/shared"; +} from "@posthog/agent-contracts"; import { EFFORT_LEVEL_DOCS_URLS, EFFORT_LEVEL_LABELS, -} from "@posthog/shared/domain-types"; -import { reasoningEffortsForModel } from "@posthog/shared/model-catalog"; +} from "@posthog/agent-contracts/domain-types"; +import { reasoningEffortsForModel } from "@posthog/agent-contracts/model-catalog"; import type { EffortLevel } from "../types"; export const DEFAULT_MODEL = "opus"; diff --git a/products/desktop/packages/agent/src/adapters/claude/session/options.ts b/products/desktop/packages/agent/src/adapters/claude/session/options.ts index 3e5f0581fc7d..54b358a5acbe 100644 --- a/products/desktop/packages/agent/src/adapters/claude/session/options.ts +++ b/products/desktop/packages/agent/src/adapters/claude/session/options.ts @@ -13,6 +13,14 @@ import type { SpawnedProcess, SpawnOptions, } from "@anthropic-ai/claude-agent-sdk"; +import { + BEDROCK_LLM_GATEWAY_FLAG, + type BedrockGatewayVariant, +} from "@posthog/agent-contracts"; +import { + buildPosthogProjectHeaderLines, + buildPosthogPropertyHeaderLines, +} from "@posthog/agent-contracts/posthog-property-headers"; import { buildAppendedInstructions } from "@posthog/harness/extensions/agent-instructions"; import { applyContextWikiEnv, @@ -21,14 +29,6 @@ import { } from "@posthog/harness/extensions/context-wiki"; import type { FileEnrichmentDeps } from "@posthog/harness/extensions/enrichment"; import { resolveRtkPrefix } from "@posthog/harness/extensions/rtk"; -import { - BEDROCK_LLM_GATEWAY_FLAG, - type BedrockGatewayVariant, -} from "@posthog/shared"; -import { - buildPosthogProjectHeaderLines, - buildPosthogPropertyHeaderLines, -} from "@posthog/shared/posthog-property-headers"; import { IS_ROOT } from "../../../utils/common"; import type { Logger } from "../../../utils/logger"; import type { TaskState } from "../conversion/task-state"; diff --git a/products/desktop/packages/agent/src/adapters/claude/types.ts b/products/desktop/packages/agent/src/adapters/claude/types.ts index 1bae51de1c1f..6dba6788f6cf 100644 --- a/products/desktop/packages/agent/src/adapters/claude/types.ts +++ b/products/desktop/packages/agent/src/adapters/claude/types.ts @@ -10,9 +10,9 @@ import type { Query, SDKUserMessage, } from "@anthropic-ai/claude-agent-sdk"; +import type { BedrockGatewayVariant } from "@posthog/agent-contracts"; +import type { EffortLevel } from "@posthog/agent-contracts/domain-types"; import type { PostHogProductId } from "@posthog/harness/extensions/posthog-mcp-policy"; -import type { BedrockGatewayVariant } from "@posthog/shared"; -import type { EffortLevel } from "@posthog/shared/domain-types"; import type { SteerDeclineCause } from "../../acp-extensions"; import type { AgentMode } from "../../types"; import type { Pushable } from "../../utils/streams"; diff --git a/products/desktop/packages/agent/src/adapters/codex-app-server/approvals.ts b/products/desktop/packages/agent/src/adapters/codex-app-server/approvals.ts index 06048b73fb02..d38594c2ad60 100644 --- a/products/desktop/packages/agent/src/adapters/codex-app-server/approvals.ts +++ b/products/desktop/packages/agent/src/adapters/codex-app-server/approvals.ts @@ -11,7 +11,7 @@ import type { PermissionOption, RequestPermissionResponse, } from "@agentclientprotocol/sdk"; -import { mcpToolKey, posthogToolMeta } from "@posthog/shared"; +import { mcpToolKey, posthogToolMeta } from "@posthog/agent-contracts"; import type { Logger } from "../../utils/logger"; import { OPTION_PREFIX } from "../claude/questions/utils"; import { APP_SERVER_REQUESTS } from "./protocol"; diff --git a/products/desktop/packages/agent/src/adapters/codex-app-server/codex-app-server-agent.ts b/products/desktop/packages/agent/src/adapters/codex-app-server/codex-app-server-agent.ts index 1f2a68bb5aa2..27e3d23162a8 100644 --- a/products/desktop/packages/agent/src/adapters/codex-app-server/codex-app-server-agent.ts +++ b/products/desktop/packages/agent/src/adapters/codex-app-server/codex-app-server-agent.ts @@ -22,6 +22,12 @@ import type { StopReason, } from "@agentclientprotocol/sdk"; import { RequestError } from "@agentclientprotocol/sdk"; +import { + classifyGatewayLimitError, + mcpToolKey, + posthogToolMeta, + serializeError, +} from "@posthog/agent-contracts"; import { buildContextWikiInstructions, type ContextWikiEnv, @@ -34,12 +40,6 @@ import { matchesPostHogExecPermission, resolvePostHogExecPermissionRegex, } from "@posthog/harness/extensions/posthog-mcp-policy"; -import { - classifyGatewayLimitError, - mcpToolKey, - posthogToolMeta, - serializeError, -} from "@posthog/shared"; import { isMethod, type NativeGoalState, diff --git a/products/desktop/packages/agent/src/adapters/codex-app-server/mapping.ts b/products/desktop/packages/agent/src/adapters/codex-app-server/mapping.ts index fe87e4adaf6e..77c513707614 100644 --- a/products/desktop/packages/agent/src/adapters/codex-app-server/mapping.ts +++ b/products/desktop/packages/agent/src/adapters/codex-app-server/mapping.ts @@ -7,7 +7,7 @@ import { boundPersistedMcpResult, mcpToolKey, posthogToolMeta, -} from "@posthog/shared"; +} from "@posthog/agent-contracts"; import { APP_SERVER_NOTIFICATIONS } from "./protocol"; import { readTokenUsage } from "./token-usage"; diff --git a/products/desktop/packages/agent/src/adapters/codex-app-server/models.ts b/products/desktop/packages/agent/src/adapters/codex-app-server/models.ts index 1cceab0b41eb..cebb1092d6a2 100644 --- a/products/desktop/packages/agent/src/adapters/codex-app-server/models.ts +++ b/products/desktop/packages/agent/src/adapters/codex-app-server/models.ts @@ -3,9 +3,9 @@ import type { SessionConfigSelectGroup, SessionConfigSelectOption, } from "@agentclientprotocol/sdk"; -import { DEFAULT_OPTION_META_KEY } from "@posthog/shared"; -import { EFFORT_LEVEL_LABELS } from "@posthog/shared/domain-types"; -import { reasoningEffortsForModel } from "@posthog/shared/model-catalog"; +import { DEFAULT_OPTION_META_KEY } from "@posthog/agent-contracts"; +import { EFFORT_LEVEL_LABELS } from "@posthog/agent-contracts/domain-types"; +import { reasoningEffortsForModel } from "@posthog/agent-contracts/model-catalog"; interface ReasoningEffortOption { value: string; diff --git a/products/desktop/packages/agent/src/adapters/codex-app-server/session-config.test.ts b/products/desktop/packages/agent/src/adapters/codex-app-server/session-config.test.ts index c9b51dba34df..42e5ab401c4e 100644 --- a/products/desktop/packages/agent/src/adapters/codex-app-server/session-config.test.ts +++ b/products/desktop/packages/agent/src/adapters/codex-app-server/session-config.test.ts @@ -1,7 +1,7 @@ import { DEFAULT_OPTION_META_KEY, isRestrictedModelOption, -} from "@posthog/shared"; +} from "@posthog/agent-contracts"; import { describe, expect, it } from "vitest"; import { buildCodexModes, diff --git a/products/desktop/packages/agent/src/adapters/codex-app-server/session-config.ts b/products/desktop/packages/agent/src/adapters/codex-app-server/session-config.ts index 4c25ad422eaf..8b829687e71b 100644 --- a/products/desktop/packages/agent/src/adapters/codex-app-server/session-config.ts +++ b/products/desktop/packages/agent/src/adapters/codex-app-server/session-config.ts @@ -9,8 +9,8 @@ import { type ExecutionMode, resolveCloudInitialPermissionMode, restrictedModelMeta, -} from "@posthog/shared"; -import { EFFORT_LEVEL_LABELS } from "@posthog/shared/domain-types"; +} from "@posthog/agent-contracts"; +import { EFFORT_LEVEL_LABELS } from "@posthog/agent-contracts/domain-types"; import { type GatewayModel, isOpenAIModel, @@ -70,7 +70,7 @@ function editableSandboxPolicy(platform: string): CodexSandboxPolicy { } // Flattened Claude-style presets: the `{id, name, description}` literals live -// in @posthog/shared (one copy for every picker); this map owns the behavior. +// in @posthog/agent-contracts (one copy for every picker); this map owns the behavior. // Restriction is driven by approvalPolicy + sandboxPolicy: plan/read-only block // edits, auto/full-access restore the platform's editable sandbox. function modePolicies( diff --git a/products/desktop/packages/agent/src/adapters/error-classification.ts b/products/desktop/packages/agent/src/adapters/error-classification.ts index d07691fa438d..fbdf3ca23ac0 100644 --- a/products/desktop/packages/agent/src/adapters/error-classification.ts +++ b/products/desktop/packages/agent/src/adapters/error-classification.ts @@ -1,4 +1,4 @@ -import { getErrorMessage } from "@posthog/shared"; +import { getErrorMessage } from "@posthog/agent-contracts"; export type AgentErrorClassification = | "upstream_stream_terminated" diff --git a/products/desktop/packages/agent/src/adapters/reasoning-effort.ts b/products/desktop/packages/agent/src/adapters/reasoning-effort.ts index 3d0f01a25cea..87a6d8382d8e 100644 --- a/products/desktop/packages/agent/src/adapters/reasoning-effort.ts +++ b/products/desktop/packages/agent/src/adapters/reasoning-effort.ts @@ -2,8 +2,8 @@ import { type Adapter, type CapabilityNotch, getCapabilityLadder, -} from "@posthog/shared"; -import type { EffortLevel } from "@posthog/shared/domain-types"; +} from "@posthog/agent-contracts"; +import type { EffortLevel } from "@posthog/agent-contracts/domain-types"; import { getContextWindowOptions as getClaudeContextWindowOptions, getEffortOptions as getClaudeEffortOptions, diff --git a/products/desktop/packages/agent/src/adapters/session-meta.ts b/products/desktop/packages/agent/src/adapters/session-meta.ts index 9b0abffc34d0..c727199d67bc 100644 --- a/products/desktop/packages/agent/src/adapters/session-meta.ts +++ b/products/desktop/packages/agent/src/adapters/session-meta.ts @@ -1,4 +1,4 @@ -import type { BedrockGatewayVariant } from "@posthog/shared"; +import type { BedrockGatewayVariant } from "@posthog/agent-contracts"; /** Minimal shape needed to resolve the effective task id from session meta. */ interface TaskIdSource { diff --git a/products/desktop/packages/agent/src/agent.ts b/products/desktop/packages/agent/src/agent.ts index f348ce55196b..161e067030cf 100644 --- a/products/desktop/packages/agent/src/agent.ts +++ b/products/desktop/packages/agent/src/agent.ts @@ -3,14 +3,14 @@ import { isAnthropicModelId, mergePrUrls, readPrUrls, -} from "@posthog/shared"; -import { isRetiredModel } from "@posthog/shared/model-catalog"; +} from "@posthog/agent-contracts"; +import { isRetiredModel } from "@posthog/agent-contracts/model-catalog"; import { buildPosthogPropertyHeaderLines, buildPosthogPropertyHeaderRecord, buildPosthogUserHeaderLines, buildPosthogUserHeaderRecord, -} from "@posthog/shared/posthog-property-headers"; +} from "@posthog/agent-contracts/posthog-property-headers"; import { createAcpConnection, type InProcessAcpConnection, diff --git a/products/desktop/packages/agent/src/execution-mode.ts b/products/desktop/packages/agent/src/execution-mode.ts index 7ebf75e86bed..513dfcec2cc4 100644 --- a/products/desktop/packages/agent/src/execution-mode.ts +++ b/products/desktop/packages/agent/src/execution-mode.ts @@ -1,4 +1,7 @@ -import { CODEX_MODE_PRESETS, type ExecutionMode } from "@posthog/shared"; +import { + CODEX_MODE_PRESETS, + type ExecutionMode, +} from "@posthog/agent-contracts"; import { ALLOW_BYPASS } from "./utils/common"; export interface ModeInfo { @@ -74,7 +77,7 @@ export function isCodexNativeMode(mode: string): mode is CodexNativeMode { return (CODEX_NATIVE_MODES as readonly string[]).includes(mode); } -// The preset literals live in @posthog/shared (one copy for every picker and +// The preset literals live in @posthog/agent-contracts (one copy for every picker and // the app-server adapter's CODEX_MODES); this module only owns the gating. export function getAvailableCodexModes(): ModeInfo[] { return ALLOW_BYPASS diff --git a/products/desktop/packages/agent/src/gateway-models.ts b/products/desktop/packages/agent/src/gateway-models.ts index 4d3e9a41064f..3f4aae79621e 100644 --- a/products/desktop/packages/agent/src/gateway-models.ts +++ b/products/desktop/packages/agent/src/gateway-models.ts @@ -1,8 +1,8 @@ import { type GatewayModel, normalizeGatewayModelsResponse, -} from "@posthog/shared"; -import { buildPosthogProjectHeaderRecord } from "@posthog/shared/posthog-property-headers"; +} from "@posthog/agent-contracts"; +import { buildPosthogProjectHeaderRecord } from "@posthog/agent-contracts/posthog-property-headers"; export { adapterForModelId, @@ -29,7 +29,7 @@ export { isOfferedModel, isOpenAIModel, pickAllowedModel, -} from "@posthog/shared"; +} from "@posthog/agent-contracts"; export interface FetchGatewayModelsOptions { gatewayUrl: string; diff --git a/products/desktop/packages/agent/src/pi/conversation/toolTranslator.ts b/products/desktop/packages/agent/src/pi/conversation/toolTranslator.ts index be9017193f5d..1ca05fd54383 100644 --- a/products/desktop/packages/agent/src/pi/conversation/toolTranslator.ts +++ b/products/desktop/packages/agent/src/pi/conversation/toolTranslator.ts @@ -2,7 +2,7 @@ import type { ImageContent, TextContent } from "@earendil-works/pi-ai"; import type { AgentToolCallContent, AgentToolCallLocation, -} from "@posthog/shared"; +} from "@posthog/agent-contracts"; export interface PiToolTranslatorInput { toolCallId: string; diff --git a/products/desktop/packages/agent/src/pi/conversation/tools/bashTranslator.ts b/products/desktop/packages/agent/src/pi/conversation/tools/bashTranslator.ts index 694dc0de16bc..d92fe60d0aaa 100644 --- a/products/desktop/packages/agent/src/pi/conversation/tools/bashTranslator.ts +++ b/products/desktop/packages/agent/src/pi/conversation/tools/bashTranslator.ts @@ -1,4 +1,4 @@ -import type { AgentToolCallContent } from "@posthog/shared"; +import type { AgentToolCallContent } from "@posthog/agent-contracts"; import type { PiToolTranslator } from "../toolTranslator"; export const bashTranslator: PiToolTranslator = ({ resultContent }) => { diff --git a/products/desktop/packages/agent/src/pi/conversation/tools/editTranslator.ts b/products/desktop/packages/agent/src/pi/conversation/tools/editTranslator.ts index 1b717624d0ac..492889d2bafd 100644 --- a/products/desktop/packages/agent/src/pi/conversation/tools/editTranslator.ts +++ b/products/desktop/packages/agent/src/pi/conversation/tools/editTranslator.ts @@ -5,7 +5,7 @@ import type { import type { AgentToolCallContent, AgentToolCallLocation, -} from "@posthog/shared"; +} from "@posthog/agent-contracts"; import type { PiToolTranslator } from "../toolTranslator"; export const editTranslator: PiToolTranslator = ({ diff --git a/products/desktop/packages/agent/src/pi/conversation/tools/findTranslator.ts b/products/desktop/packages/agent/src/pi/conversation/tools/findTranslator.ts index cfa526b69ef8..d11ba3fc793f 100644 --- a/products/desktop/packages/agent/src/pi/conversation/tools/findTranslator.ts +++ b/products/desktop/packages/agent/src/pi/conversation/tools/findTranslator.ts @@ -1,5 +1,5 @@ import type { FindToolInput } from "@earendil-works/pi-coding-agent"; -import type { AgentToolCallLocation } from "@posthog/shared"; +import type { AgentToolCallLocation } from "@posthog/agent-contracts"; import type { PiToolTranslator } from "../toolTranslator"; export const findTranslator: PiToolTranslator = ({ diff --git a/products/desktop/packages/agent/src/pi/conversation/tools/grepTranslator.ts b/products/desktop/packages/agent/src/pi/conversation/tools/grepTranslator.ts index 0686d7bfb497..a6f6d4083fc5 100644 --- a/products/desktop/packages/agent/src/pi/conversation/tools/grepTranslator.ts +++ b/products/desktop/packages/agent/src/pi/conversation/tools/grepTranslator.ts @@ -5,7 +5,7 @@ import type { import type { AgentToolCallContentBlock, AgentToolCallLocation, -} from "@posthog/shared"; +} from "@posthog/agent-contracts"; import type { PiToolTranslator } from "../toolTranslator"; export const grepTranslator: PiToolTranslator = ({ diff --git a/products/desktop/packages/agent/src/pi/conversation/tools/lsTranslator.ts b/products/desktop/packages/agent/src/pi/conversation/tools/lsTranslator.ts index d50893580ec1..932f36cd8dfa 100644 --- a/products/desktop/packages/agent/src/pi/conversation/tools/lsTranslator.ts +++ b/products/desktop/packages/agent/src/pi/conversation/tools/lsTranslator.ts @@ -2,7 +2,7 @@ import type { LsToolInput } from "@earendil-works/pi-coding-agent"; import type { AgentToolCallContent, AgentToolCallLocation, -} from "@posthog/shared"; +} from "@posthog/agent-contracts"; import type { PiToolTranslator } from "../toolTranslator"; export const lsTranslator: PiToolTranslator = ({ diff --git a/products/desktop/packages/agent/src/pi/conversation/tools/readTranslator.ts b/products/desktop/packages/agent/src/pi/conversation/tools/readTranslator.ts index 4086394c947e..6f5fc91e2280 100644 --- a/products/desktop/packages/agent/src/pi/conversation/tools/readTranslator.ts +++ b/products/desktop/packages/agent/src/pi/conversation/tools/readTranslator.ts @@ -2,7 +2,7 @@ import type { ReadToolDetails, ReadToolInput, } from "@earendil-works/pi-coding-agent"; -import type { AgentToolCallContent } from "@posthog/shared"; +import type { AgentToolCallContent } from "@posthog/agent-contracts"; import type { PiToolTranslator } from "../toolTranslator"; export const readTranslator: PiToolTranslator = ({ diff --git a/products/desktop/packages/agent/src/pi/conversation/translatePiConversation.ts b/products/desktop/packages/agent/src/pi/conversation/translatePiConversation.ts index 7ba9edfc9104..274c51e1136d 100644 --- a/products/desktop/packages/agent/src/pi/conversation/translatePiConversation.ts +++ b/products/desktop/packages/agent/src/pi/conversation/translatePiConversation.ts @@ -1,6 +1,9 @@ import type { AssistantMessage, Message, Usage } from "@earendil-works/pi-ai"; import type { JsonAgentSessionEvent } from "@earendil-works/pi-coding-agent"; -import type { AgentConversationEvent, AgentTurnUsage } from "@posthog/shared"; +import type { + AgentConversationEvent, + AgentTurnUsage, +} from "@posthog/agent-contracts"; import { createPiMessageTranslator } from "./translatePiMessage"; type AgentMessage = Extract< diff --git a/products/desktop/packages/agent/src/pi/conversation/translatePiMessage.ts b/products/desktop/packages/agent/src/pi/conversation/translatePiMessage.ts index 7d1d1df9bfc7..365037771f7a 100644 --- a/products/desktop/packages/agent/src/pi/conversation/translatePiMessage.ts +++ b/products/desktop/packages/agent/src/pi/conversation/translatePiMessage.ts @@ -4,10 +4,6 @@ import type { ToolResultMessage, UserMessage, } from "@earendil-works/pi-ai"; -import type { - McpCallDetails, - McpResultMeta, -} from "@posthog/harness/extensions/mcp/tool-bridge"; import { type AgentContent, type AgentConversationEvent, @@ -20,7 +16,11 @@ import { type PiToolName, parsePiMcpCallDetails, posthogToolMeta, -} from "@posthog/shared"; +} from "@posthog/agent-contracts"; +import type { + McpCallDetails, + McpResultMeta, +} from "@posthog/harness/extensions/mcp/tool-bridge"; import { z } from "zod"; import { bashTranslator } from "./tools/bashTranslator"; import { editTranslator } from "./tools/editTranslator"; diff --git a/products/desktop/packages/agent/src/pi/remote-rpc-client.ts b/products/desktop/packages/agent/src/pi/remote-rpc-client.ts index 3a576b292d86..04921e73dc55 100644 --- a/products/desktop/packages/agent/src/pi/remote-rpc-client.ts +++ b/products/desktop/packages/agent/src/pi/remote-rpc-client.ts @@ -3,7 +3,7 @@ import type { RpcCommand, RpcResponse, } from "@earendil-works/pi-coding-agent"; -import type { AgentConversationEvent } from "@posthog/shared"; +import type { AgentConversationEvent } from "@posthog/agent-contracts"; import { createPiConversationTranslator } from "./conversation/translatePiConversation"; import { type PiRpcTransport, parsePiRpcResponse } from "./rpc-transport"; diff --git a/products/desktop/packages/agent/src/pi/rpc-client.test.ts b/products/desktop/packages/agent/src/pi/rpc-client.test.ts index bb734804f6d8..0fcc20166a51 100644 --- a/products/desktop/packages/agent/src/pi/rpc-client.test.ts +++ b/products/desktop/packages/agent/src/pi/rpc-client.test.ts @@ -2,7 +2,7 @@ import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { RpcClient } from "@earendil-works/pi-coding-agent"; -import type { TaskContext } from "@posthog/shared/task-context"; +import type { TaskContext } from "@posthog/agent-contracts/task-context"; import { describe, expect, it, vi } from "vitest"; import { createPiRpcClient, diff --git a/products/desktop/packages/agent/src/pi/rpc-client.ts b/products/desktop/packages/agent/src/pi/rpc-client.ts index 856d7be6e598..8325337ef644 100644 --- a/products/desktop/packages/agent/src/pi/rpc-client.ts +++ b/products/desktop/packages/agent/src/pi/rpc-client.ts @@ -9,16 +9,16 @@ import { type RpcClientOptions, type RpcEventListener, } from "@earendil-works/pi-coding-agent"; -import type { PiEnrichmentConfig } from "@posthog/harness/extensions/enrichment"; -import type { McpConfig } from "@posthog/harness/extensions/mcp/config"; import type { CloudRegion, McpServerConnection, McpToolPermissionDecision, McpToolPermissionRequest, McpToolPolicy, -} from "@posthog/shared"; -import type { TaskContext } from "@posthog/shared/task-context"; +} from "@posthog/agent-contracts"; +import type { TaskContext } from "@posthog/agent-contracts/task-context"; +import type { PiEnrichmentConfig } from "@posthog/harness/extensions/enrichment"; +import type { McpConfig } from "@posthog/harness/extensions/mcp/config"; import { buildLocalToolsServer } from "../adapters/codex-app-server/local-tools-mcp"; import { safePiEnvironment } from "./rpc-environment"; import type { diff --git a/products/desktop/packages/agent/src/pi/rpc-host.ts b/products/desktop/packages/agent/src/pi/rpc-host.ts index 59a5c601f582..eff1175252c5 100644 --- a/products/desktop/packages/agent/src/pi/rpc-host.ts +++ b/products/desktop/packages/agent/src/pi/rpc-host.ts @@ -3,6 +3,10 @@ import { type InlineExtension, SessionManager, } from "@earendil-works/pi-coding-agent"; +import type { + McpToolPermissionDecision, + McpToolPermissionRequest, +} from "@posthog/agent-contracts"; import { createHarnessRuntime, runRpcMode } from "@posthog/harness"; import { createAutoPublishExtension } from "@posthog/harness/extensions/auto-publish"; import { createPiContextWikiExtension } from "@posthog/harness/extensions/context-wiki"; @@ -11,10 +15,6 @@ import { createPiTaskSystemPromptExtension, resolvePiTaskContext, } from "@posthog/harness/extensions/task-system-prompt"; -import type { - McpToolPermissionDecision, - McpToolPermissionRequest, -} from "@posthog/shared"; import { POSTHOG_PI_QUEUE_ENTRY_TYPE, readPersistedPiQueue, diff --git a/products/desktop/packages/agent/src/pi/runtime.ts b/products/desktop/packages/agent/src/pi/runtime.ts index 163d83ff3646..6f422dda28f3 100644 --- a/products/desktop/packages/agent/src/pi/runtime.ts +++ b/products/desktop/packages/agent/src/pi/runtime.ts @@ -3,7 +3,7 @@ import type { RpcCommand, RpcResponse, } from "@earendil-works/pi-coding-agent"; -import type { AgentConversationEvent } from "@posthog/shared"; +import type { AgentConversationEvent } from "@posthog/agent-contracts"; import { createPiConversationTranslator, type PiConversationTranslator, diff --git a/products/desktop/packages/agent/src/posthog-api.test.ts b/products/desktop/packages/agent/src/posthog-api.test.ts index 86919d0a85b5..37c11852a172 100644 --- a/products/desktop/packages/agent/src/posthog-api.test.ts +++ b/products/desktop/packages/agent/src/posthog-api.test.ts @@ -1,4 +1,4 @@ -import { API_DOWNLOAD_TIMEOUT_MS } from "@posthog/shared"; +import { API_DOWNLOAD_TIMEOUT_MS } from "@posthog/agent-contracts"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { PostHogAPIClient } from "./posthog-api"; diff --git a/products/desktop/packages/agent/src/posthog-api.ts b/products/desktop/packages/agent/src/posthog-api.ts index 09946ff0f2a1..ad976bb6be29 100644 --- a/products/desktop/packages/agent/src/posthog-api.ts +++ b/products/desktop/packages/agent/src/posthog-api.ts @@ -8,7 +8,7 @@ import { type StoredLogEntry, type TaskRunUpdate, taskRunStateSchema, -} from "@posthog/shared"; +} from "@posthog/agent-contracts"; import packageJson from "../package.json" with { type: "json" }; import type { PostHogAPIConfig, StoredEntry, Task, TaskRun } from "./types"; import { getGatewayUsageUrl, getLlmGatewayUrl } from "./utils/gateway"; diff --git a/products/desktop/packages/agent/src/sagas/resume-saga.test.ts b/products/desktop/packages/agent/src/sagas/resume-saga.test.ts index ba6833f16dbc..a73d5171abaf 100644 --- a/products/desktop/packages/agent/src/sagas/resume-saga.test.ts +++ b/products/desktop/packages/agent/src/sagas/resume-saga.test.ts @@ -1,4 +1,4 @@ -import type { SagaLogger } from "@posthog/shared"; +import type { SagaLogger } from "@posthog/agent-contracts"; import { afterEach, beforeEach, describe, expect, it, type vi } from "vitest"; import { POSTHOG_NOTIFICATIONS } from "../acp-extensions"; import type { PostHogAPIClient } from "../posthog-api"; diff --git a/products/desktop/packages/agent/src/sagas/resume-saga.ts b/products/desktop/packages/agent/src/sagas/resume-saga.ts index 6f11e6ebb591..aa9e419c4ac6 100644 --- a/products/desktop/packages/agent/src/sagas/resume-saga.ts +++ b/products/desktop/packages/agent/src/sagas/resume-saga.ts @@ -1,5 +1,5 @@ import type { ContentBlock } from "@agentclientprotocol/sdk"; -import { Saga } from "@posthog/shared"; +import { Saga } from "@posthog/agent-contracts"; import { isNotification, type NativeGoalState, diff --git a/products/desktop/packages/agent/src/sagas/test-fixtures.ts b/products/desktop/packages/agent/src/sagas/test-fixtures.ts index f0f3ee39545c..015fb1bac663 100644 --- a/products/desktop/packages/agent/src/sagas/test-fixtures.ts +++ b/products/desktop/packages/agent/src/sagas/test-fixtures.ts @@ -4,7 +4,7 @@ import { mkdir, readFile, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { promisify } from "node:util"; -import type { SagaLogger } from "@posthog/shared"; +import type { SagaLogger } from "@posthog/agent-contracts"; import { vi } from "vitest"; import type { PostHogAPIClient } from "../posthog-api"; import type { StoredNotification, TaskRun } from "../types"; diff --git a/products/desktop/packages/agent/src/server/agent-server.test.ts b/products/desktop/packages/agent/src/server/agent-server.test.ts index 9eac71ed379d..c6901cb80920 100644 --- a/products/desktop/packages/agent/src/server/agent-server.test.ts +++ b/products/desktop/packages/agent/src/server/agent-server.test.ts @@ -13,8 +13,11 @@ import { join } from "node:path"; import { type ContentBlock, RequestError } from "@agentclientprotocol/sdk"; import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http"; import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http"; +import { + type Adapter, + IDLE_RESUME_STOP_REASON, +} from "@posthog/agent-contracts"; import { SIMPLIFIED_TECHNICAL_ENGLISH_INSTRUCTION as STE100_INSTRUCTION } from "@posthog/harness/extensions/benjamin"; -import { type Adapter, IDLE_RESUME_STOP_REASON } from "@posthog/shared"; import { zipSync } from "fflate"; import jwt from "jsonwebtoken"; import { HttpResponse, http } from "msw"; diff --git a/products/desktop/packages/agent/src/server/agent-server.ts b/products/desktop/packages/agent/src/server/agent-server.ts index b71a82cbe7ee..37b62a43f052 100644 --- a/products/desktop/packages/agent/src/server/agent-server.ts +++ b/products/desktop/packages/agent/src/server/agent-server.ts @@ -15,6 +15,24 @@ import { RequestError, } from "@agentclientprotocol/sdk"; import { type ServerType, serve } from "@hono/node-server"; +import { + type AcpMcpServer, + type Adapter, + buildPrOutput, + getErrorMessage, + IDLE_RESUME_STOP_REASON, + isIgnoredSkillPath, + isSkillBundleArtifactMetadata, + type McpServerConnection, + mergePrUrls, + parseMcpToolName, + readMcpToolDescriptor, + readPrUrls, + sleepWithBackoff, + toAcpMcpServers, +} from "@posthog/agent-contracts"; +import { prependProductEngineerPrompt } from "@posthog/agent-contracts/product-engineer-prompt"; +import { appendRichOutputPrompt } from "@posthog/agent-contracts/rich-output-prompt"; import { execGh } from "@posthog/git/gh"; import { getCurrentBranch, getRemoteUrl } from "@posthog/git/queries"; import { ghTokenEnv } from "@posthog/git/signed-commit"; @@ -44,24 +62,6 @@ import { CloudTaskPrompt, parseLocalSkillInvocation, } from "@posthog/harness/extensions/task-system-prompt"; -import { - type AcpMcpServer, - type Adapter, - buildPrOutput, - getErrorMessage, - IDLE_RESUME_STOP_REASON, - isIgnoredSkillPath, - isSkillBundleArtifactMetadata, - type McpServerConnection, - mergePrUrls, - parseMcpToolName, - readMcpToolDescriptor, - readPrUrls, - sleepWithBackoff, - toAcpMcpServers, -} from "@posthog/shared"; -import { prependProductEngineerPrompt } from "@posthog/shared/product-engineer-prompt"; -import { appendRichOutputPrompt } from "@posthog/shared/rich-output-prompt"; import { unzipSync } from "fflate"; import { Hono } from "hono"; import { z } from "zod"; diff --git a/products/desktop/packages/agent/src/server/bin.ts b/products/desktop/packages/agent/src/server/bin.ts index 187df01d8386..54b7a182b542 100644 --- a/products/desktop/packages/agent/src/server/bin.ts +++ b/products/desktop/packages/agent/src/server/bin.ts @@ -1,8 +1,11 @@ #!/usr/bin/env node import { closeSync, readFileSync } from "node:fs"; import { fileURLToPath } from "node:url"; +import { + EFFORT_LEVELS, + SERVICE_TIERS, +} from "@posthog/agent-contracts/domain-types"; import { DEFAULT_POSTHOG_EXEC_PERMISSION_REGEX_SOURCE } from "@posthog/harness/extensions/posthog-mcp-policy"; -import { EFFORT_LEVELS, SERVICE_TIERS } from "@posthog/shared/domain-types"; import { Command } from "commander"; import { z } from "zod/v4"; import { isSupportedReasoningEffort } from "../adapters/reasoning-effort"; diff --git a/products/desktop/packages/agent/src/server/cloud-prompt.ts b/products/desktop/packages/agent/src/server/cloud-prompt.ts index e003eb212d96..a786920db6a1 100644 --- a/products/desktop/packages/agent/src/server/cloud-prompt.ts +++ b/products/desktop/packages/agent/src/server/cloud-prompt.ts @@ -1,5 +1,8 @@ import type { ContentBlock } from "@agentclientprotocol/sdk"; -import { deserializeCloudPrompt, promptBlocksToText } from "@posthog/shared"; +import { + deserializeCloudPrompt, + promptBlocksToText, +} from "@posthog/agent-contracts"; export { promptBlocksToText }; diff --git a/products/desktop/packages/agent/src/server/cloud-session-prompt.test.ts b/products/desktop/packages/agent/src/server/cloud-session-prompt.test.ts index e3082364a2e2..16ffb542f8b9 100644 --- a/products/desktop/packages/agent/src/server/cloud-session-prompt.test.ts +++ b/products/desktop/packages/agent/src/server/cloud-session-prompt.test.ts @@ -1,5 +1,5 @@ -import { PRODUCT_ENGINEER_PROMPT } from "@posthog/shared/product-engineer-prompt"; -import { RICH_OUTPUT_TAGS_PROMPT } from "@posthog/shared/rich-output-prompt"; +import { PRODUCT_ENGINEER_PROMPT } from "@posthog/agent-contracts/product-engineer-prompt"; +import { RICH_OUTPUT_TAGS_PROMPT } from "@posthog/agent-contracts/rich-output-prompt"; import { describe, expect, it } from "vitest"; import { buildCloudSessionSystemPrompt } from "./agent-server"; diff --git a/products/desktop/packages/agent/src/server/gateway-env.ts b/products/desktop/packages/agent/src/server/gateway-env.ts index 40b619903c7a..93988c6f390a 100644 --- a/products/desktop/packages/agent/src/server/gateway-env.ts +++ b/products/desktop/packages/agent/src/server/gateway-env.ts @@ -4,7 +4,7 @@ import { buildPosthogPropertiesHeaderRecord, buildPosthogScopedPropertyHeaderLines, buildPosthogScopedPropertyHeaderRecord, -} from "@posthog/shared/posthog-property-headers"; +} from "@posthog/agent-contracts/posthog-property-headers"; import type { GatewayEnv } from "../adapters/claude/session/options"; import type { Task } from "../types"; import { resolveGatewayProduct, resolveGatewayTarget } from "../utils/gateway"; diff --git a/products/desktop/packages/agent/src/server/mcp-relay-server.ts b/products/desktop/packages/agent/src/server/mcp-relay-server.ts index e13dcb8ac609..8e98029363d5 100644 --- a/products/desktop/packages/agent/src/server/mcp-relay-server.ts +++ b/products/desktop/packages/agent/src/server/mcp-relay-server.ts @@ -1,6 +1,6 @@ import crypto from "node:crypto"; import { type ServerType, serve } from "@hono/node-server"; -import type { McpServerConnection } from "@posthog/shared"; +import type { McpServerConnection } from "@posthog/agent-contracts"; import { Hono } from "hono"; import type { Logger } from "../utils/logger"; diff --git a/products/desktop/packages/agent/src/server/pi-agent-server.test.ts b/products/desktop/packages/agent/src/server/pi-agent-server.test.ts index 3c7438fd9654..f5bf796c6a3b 100644 --- a/products/desktop/packages/agent/src/server/pi-agent-server.test.ts +++ b/products/desktop/packages/agent/src/server/pi-agent-server.test.ts @@ -1,7 +1,7 @@ import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import type { AgentConversationEvent } from "@posthog/shared"; +import type { AgentConversationEvent } from "@posthog/agent-contracts"; import { describe, expect, it, vi } from "vitest"; import { PiAgentServer } from "./pi-agent-server"; import type { AgentServerConfig } from "./types"; diff --git a/products/desktop/packages/agent/src/server/pi-agent-server.ts b/products/desktop/packages/agent/src/server/pi-agent-server.ts index a1eca10292c3..b0ae213afd3b 100644 --- a/products/desktop/packages/agent/src/server/pi-agent-server.ts +++ b/products/desktop/packages/agent/src/server/pi-agent-server.ts @@ -4,11 +4,6 @@ import { basename, dirname, join } from "node:path"; import type { RpcSessionState } from "@earendil-works/pi-coding-agent"; import type { ServerType } from "@hono/node-server"; import { serve } from "@hono/node-server"; -import { resolveContextWikiPath } from "@posthog/harness/extensions/context-wiki"; -import { - buildStoreSkillsInstructions, - syncStoreSkills, -} from "@posthog/harness/extensions/skills-store"; import { type AgentConversationEvent, type AgentTurnUsage, @@ -20,9 +15,14 @@ import { type StoredLogEntry, serializeError, type TaskRunArtifact, -} from "@posthog/shared"; -import { buildPosthogPropertyHeaderRecord } from "@posthog/shared/posthog-property-headers"; -import type { TaskContext } from "@posthog/shared/task-context"; +} from "@posthog/agent-contracts"; +import { buildPosthogPropertyHeaderRecord } from "@posthog/agent-contracts/posthog-property-headers"; +import type { TaskContext } from "@posthog/agent-contracts/task-context"; +import { resolveContextWikiPath } from "@posthog/harness/extensions/context-wiki"; +import { + buildStoreSkillsInstructions, + syncStoreSkills, +} from "@posthog/harness/extensions/skills-store"; import { Hono } from "hono"; import { z } from "zod/v4"; import packageJson from "../../package.json" with { type: "json" }; diff --git a/products/desktop/packages/agent/src/server/schemas.ts b/products/desktop/packages/agent/src/server/schemas.ts index eb24080eb519..7615d479829f 100644 --- a/products/desktop/packages/agent/src/server/schemas.ts +++ b/products/desktop/packages/agent/src/server/schemas.ts @@ -1,4 +1,4 @@ -import type { McpServerConnection } from "@posthog/shared"; +import type { McpServerConnection } from "@posthog/agent-contracts"; import { z } from "zod/v4"; export { posthogExecPermissionRegexSchema } from "@posthog/harness/extensions/posthog-mcp-policy"; diff --git a/products/desktop/packages/agent/src/server/types.ts b/products/desktop/packages/agent/src/server/types.ts index 98b0d15abc64..a34685f9a694 100644 --- a/products/desktop/packages/agent/src/server/types.ts +++ b/products/desktop/packages/agent/src/server/types.ts @@ -1,5 +1,8 @@ -import type { Adapter, McpServerConnection } from "@posthog/shared"; -import type { EffortLevel, ServiceTier } from "@posthog/shared/domain-types"; +import type { Adapter, McpServerConnection } from "@posthog/agent-contracts"; +import type { + EffortLevel, + ServiceTier, +} from "@posthog/agent-contracts/domain-types"; import type { AgentMode } from "../types"; import type { RtkSavingsSummary } from "./rtk-savings"; diff --git a/products/desktop/packages/agent/src/session-log-writer.ts b/products/desktop/packages/agent/src/session-log-writer.ts index 81ed39b9ebe4..0d2f349db642 100644 --- a/products/desktop/packages/agent/src/session-log-writer.ts +++ b/products/desktop/packages/agent/src/session-log-writer.ts @@ -1,7 +1,7 @@ import fs from "node:fs"; import fsp from "node:fs/promises"; import path from "node:path"; -import { serializeError, TranscriptBoundaries } from "@posthog/shared"; +import { serializeError, TranscriptBoundaries } from "@posthog/agent-contracts"; import { type PostHogAPIClient, PostHogAPIError } from "./posthog-api"; import type { StoredNotification } from "./types"; import { isEmptyContentBlock } from "./utils/acp-content"; diff --git a/products/desktop/packages/agent/src/smoke/run-turn.ts b/products/desktop/packages/agent/src/smoke/run-turn.ts index d3807a769a9c..0a2715623555 100644 --- a/products/desktop/packages/agent/src/smoke/run-turn.ts +++ b/products/desktop/packages/agent/src/smoke/run-turn.ts @@ -7,7 +7,7 @@ import { ClientSideConnection, ndJsonStream, } from "@agentclientprotocol/sdk"; -import type { Adapter } from "@posthog/shared"; +import type { Adapter } from "@posthog/agent-contracts"; import { type AcpConnection, createAcpConnection, diff --git a/products/desktop/packages/agent/src/types.ts b/products/desktop/packages/agent/src/types.ts index 5b7b7e26ef86..3fab38ec6ffd 100644 --- a/products/desktop/packages/agent/src/types.ts +++ b/products/desktop/packages/agent/src/types.ts @@ -1,6 +1,10 @@ +import type { + Adapter, + ModelAccess, + PostHogAPIConfig, +} from "@posthog/agent-contracts"; +import type { EffortLevel } from "@posthog/agent-contracts/domain-types"; import type { ContextWikiEnv } from "@posthog/harness/extensions/context-wiki"; -import type { Adapter, ModelAccess, PostHogAPIConfig } from "@posthog/shared"; -import type { EffortLevel } from "@posthog/shared/domain-types"; export type { ArtifactSource, @@ -13,7 +17,7 @@ export type { TaskRunState, TaskRunStateField, TaskRunStatus, -} from "@posthog/shared"; +} from "@posthog/agent-contracts"; /** * Stored custom notification following ACP extensibility model. diff --git a/products/desktop/packages/agent/src/utils/gateway.ts b/products/desktop/packages/agent/src/utils/gateway.ts index d1429224d132..d2a6ce7590f5 100644 --- a/products/desktop/packages/agent/src/utils/gateway.ts +++ b/products/desktop/packages/agent/src/utils/gateway.ts @@ -1,4 +1,4 @@ -import { getCloudTaskGatewayUrl } from "@posthog/shared"; +import { getCloudTaskGatewayUrl } from "@posthog/agent-contracts"; export type GatewayProduct = | "posthog_code" diff --git a/products/desktop/packages/agent/src/utils/streams.ts b/products/desktop/packages/agent/src/utils/streams.ts index 96a437a89140..cc85d953e9e6 100644 --- a/products/desktop/packages/agent/src/utils/streams.ts +++ b/products/desktop/packages/agent/src/utils/streams.ts @@ -1,6 +1,6 @@ import type { Readable, Writable } from "node:stream"; import { ReadableStream, WritableStream } from "node:stream/web"; -import { serializeError } from "@posthog/shared"; +import { serializeError } from "@posthog/agent-contracts"; import type { Logger } from "./logger"; export class Pushable implements AsyncIterable { diff --git a/products/desktop/packages/agent/tsup.config.ts b/products/desktop/packages/agent/tsup.config.ts index 1056c9ff947b..bbd034be5c4a 100644 --- a/products/desktop/packages/agent/tsup.config.ts +++ b/products/desktop/packages/agent/tsup.config.ts @@ -133,7 +133,7 @@ const sharedOptions = { outDir: "dist", target: "node20", noExternal: [ - "@posthog/shared", + "@posthog/agent-contracts", "@posthog/git", "@posthog/enricher", "@posthog/harness", @@ -166,7 +166,7 @@ export default defineConfig([ outDir: "dist", target: "es2022", platform: "browser", - external: ["@earendil-works/pi-ai", "@posthog/shared", "zod"], + external: ["@earendil-works/pi-ai", "@posthog/agent-contracts", "zod"], }, { entry: [ diff --git a/products/desktop/packages/enricher/package.json b/products/desktop/packages/enricher/package.json index 4f859e981eb6..e8d302a2e247 100644 --- a/products/desktop/packages/enricher/package.json +++ b/products/desktop/packages/enricher/package.json @@ -18,7 +18,7 @@ "test": "vitest run" }, "dependencies": { - "@posthog/shared": "workspace:*", + "@posthog/agent-contracts": "workspace:*", "web-tree-sitter": "^0.24.7" }, "devDependencies": { diff --git a/products/desktop/packages/enricher/src/serialize.ts b/products/desktop/packages/enricher/src/serialize.ts index 06b9b2a48964..efdc6ce81ddf 100644 --- a/products/desktop/packages/enricher/src/serialize.ts +++ b/products/desktop/packages/enricher/src/serialize.ts @@ -2,14 +2,14 @@ import type { SerializedEnrichment, SerializedEvent, SerializedFlag, -} from "@posthog/shared"; +} from "@posthog/agent-contracts"; import type { EnrichedResult } from "./enriched-result.js"; export type { SerializedEnrichment, SerializedEvent, SerializedFlag, -} from "@posthog/shared"; +} from "@posthog/agent-contracts"; export function toSerializable(enriched: EnrichedResult): SerializedEnrichment { const flags: SerializedFlag[] = enriched.flags.map((f) => ({ diff --git a/products/desktop/packages/enricher/src/types.ts b/products/desktop/packages/enricher/src/types.ts index 11c03b8c977f..be1d9abc4fcf 100644 --- a/products/desktop/packages/enricher/src/types.ts +++ b/products/desktop/packages/enricher/src/types.ts @@ -167,7 +167,7 @@ export interface EventDefinition { // ── Stale flag types ── -import type { FlagType, StalenessReason } from "@posthog/shared"; +import type { FlagType, StalenessReason } from "@posthog/agent-contracts"; export type { FlagType, StalenessReason }; // ── Enricher types ── diff --git a/products/desktop/packages/git/package.json b/products/desktop/packages/git/package.json index 1d6ab9db5441..d82f22dbc021 100644 --- a/products/desktop/packages/git/package.json +++ b/products/desktop/packages/git/package.json @@ -25,7 +25,7 @@ "src/**/*" ], "dependencies": { - "@posthog/shared": "workspace:*", + "@posthog/agent-contracts": "workspace:*", "git-url-parse": "^16.1.0", "simple-git": "^3.36.0" } diff --git a/products/desktop/packages/git/src/git-saga.ts b/products/desktop/packages/git/src/git-saga.ts index c2cfa7ea9a09..82ed6f07e089 100644 --- a/products/desktop/packages/git/src/git-saga.ts +++ b/products/desktop/packages/git/src/git-saga.ts @@ -1,4 +1,4 @@ -import { Saga } from "@posthog/shared"; +import { Saga } from "@posthog/agent-contracts"; import type { GitClient } from "./client"; import { getGitOperationManager } from "./operation-manager"; diff --git a/products/desktop/packages/git/src/queries.ts b/products/desktop/packages/git/src/queries.ts index 8d0b13c86efe..523ba0c6252b 100644 --- a/products/desktop/packages/git/src/queries.ts +++ b/products/desktop/packages/git/src/queries.ts @@ -3,7 +3,7 @@ import { createReadStream, readFileSync, statSync } from "node:fs"; import * as fs from "node:fs/promises"; import * as path from "node:path"; import { promisify } from "node:util"; -import { isBinaryFile } from "@posthog/shared"; +import { isBinaryFile } from "@posthog/agent-contracts"; import type { CreateGitClientOptions } from "./client"; import { mapWithConcurrency } from "./concurrency"; import { getGitOperationManager } from "./operation-manager"; diff --git a/products/desktop/packages/git/src/sagas/clone.ts b/products/desktop/packages/git/src/sagas/clone.ts index 08df9bbf248c..db94a3d3476d 100644 --- a/products/desktop/packages/git/src/sagas/clone.ts +++ b/products/desktop/packages/git/src/sagas/clone.ts @@ -1,5 +1,5 @@ import * as fs from "node:fs/promises"; -import { Saga } from "@posthog/shared"; +import { Saga } from "@posthog/agent-contracts"; import { createGitClient } from "../client"; import { getCleanEnv, getGitOperationManager } from "../operation-manager"; diff --git a/products/desktop/packages/git/src/worktree.ts b/products/desktop/packages/git/src/worktree.ts index 5cb3c5c27909..f19961319cc8 100644 --- a/products/desktop/packages/git/src/worktree.ts +++ b/products/desktop/packages/git/src/worktree.ts @@ -1,7 +1,7 @@ import { type ChildProcess, execFile, spawn } from "node:child_process"; import * as fs from "node:fs/promises"; import * as path from "node:path"; -import type { SagaLogger } from "@posthog/shared"; +import type { SagaLogger } from "@posthog/agent-contracts"; import { matchesExcludePatterns, parseExcludePatterns, diff --git a/products/desktop/packages/harness/package.json b/products/desktop/packages/harness/package.json index 0c62b62e0558..42a1d29869ae 100644 --- a/products/desktop/packages/harness/package.json +++ b/products/desktop/packages/harness/package.json @@ -132,7 +132,7 @@ "@modelcontextprotocol/sdk": "^1.29.0", "@posthog/enricher": "workspace:*", "@posthog/git": "workspace:*", - "@posthog/shared": "workspace:*", + "@posthog/agent-contracts": "workspace:*", "fflate": "^0.8.2", "lru-cache": "^11.1.0", "turndown": "^7.2.4", diff --git a/products/desktop/packages/harness/src/extensions/agent-instructions/instructions.test.ts b/products/desktop/packages/harness/src/extensions/agent-instructions/instructions.test.ts index b5b9067b544c..c259537556d4 100644 --- a/products/desktop/packages/harness/src/extensions/agent-instructions/instructions.test.ts +++ b/products/desktop/packages/harness/src/extensions/agent-instructions/instructions.test.ts @@ -1,4 +1,4 @@ -import { IMAGE_TOOLS_ENV_KEY } from "@posthog/shared/constants"; +import { IMAGE_TOOLS_ENV_KEY } from "@posthog/agent-contracts/sandbox-env"; import { describe, expect, it, vi } from "vitest"; import { buildAppendedInstructions, diff --git a/products/desktop/packages/harness/src/extensions/benjamin/ste100.ts b/products/desktop/packages/harness/src/extensions/benjamin/ste100.ts index c1d4a8a03bd8..c21aec0530ef 100644 --- a/products/desktop/packages/harness/src/extensions/benjamin/ste100.ts +++ b/products/desktop/packages/harness/src/extensions/benjamin/ste100.ts @@ -1,4 +1,4 @@ -import { SIMPLIFIED_TECHNICAL_ENGLISH_INSTRUCTION } from "@posthog/shared/product-engineer-prompt"; +import { SIMPLIFIED_TECHNICAL_ENGLISH_INSTRUCTION } from "@posthog/agent-contracts/product-engineer-prompt"; import { isBenjaminEnabled } from "./guidance"; export { SIMPLIFIED_TECHNICAL_ENGLISH_INSTRUCTION }; diff --git a/products/desktop/packages/harness/src/extensions/enrichment/extension.test.ts b/products/desktop/packages/harness/src/extensions/enrichment/extension.test.ts index 20366fde2894..c4b6d016845b 100644 --- a/products/desktop/packages/harness/src/extensions/enrichment/extension.test.ts +++ b/products/desktop/packages/harness/src/extensions/enrichment/extension.test.ts @@ -3,7 +3,7 @@ import type { ExtensionContext, ToolResultEvent, } from "@earendil-works/pi-coding-agent"; -import { RICH_OUTPUT_TAGS_PROMPT } from "@posthog/shared/rich-output-prompt"; +import { RICH_OUTPUT_TAGS_PROMPT } from "@posthog/agent-contracts/rich-output-prompt"; import { afterEach, describe, expect, it, vi } from "vitest"; import { createPiEnrichmentExtension } from "./extension"; diff --git a/products/desktop/packages/harness/src/extensions/enrichment/extension.ts b/products/desktop/packages/harness/src/extensions/enrichment/extension.ts index 1088208522ae..4bf047a3f1c2 100644 --- a/products/desktop/packages/harness/src/extensions/enrichment/extension.ts +++ b/products/desktop/packages/harness/src/extensions/enrichment/extension.ts @@ -4,7 +4,7 @@ import { type ExtensionFactory, isReadToolResult, } from "@earendil-works/pi-coding-agent"; -import { appendRichOutputPrompt } from "@posthog/shared/rich-output-prompt"; +import { appendRichOutputPrompt } from "@posthog/agent-contracts/rich-output-prompt"; import { createEnrichment, enrichFileForAgent } from "./file-enricher"; export interface PiEnrichmentConfig { diff --git a/products/desktop/packages/harness/src/extensions/enrichment/file-enricher.ts b/products/desktop/packages/harness/src/extensions/enrichment/file-enricher.ts index 81ee1e78d3ad..97874e7b0c18 100644 --- a/products/desktop/packages/harness/src/extensions/enrichment/file-enricher.ts +++ b/products/desktop/packages/harness/src/extensions/enrichment/file-enricher.ts @@ -1,6 +1,6 @@ import * as path from "node:path"; +import type { PostHogAPIConfig } from "@posthog/agent-contracts"; import { enrichSource, PostHogEnricher } from "@posthog/enricher"; -import type { PostHogAPIConfig } from "@posthog/shared"; export interface FileEnrichmentDeps { enricher: PostHogEnricher; diff --git a/products/desktop/packages/harness/src/extensions/local-tools/task-tools-client.ts b/products/desktop/packages/harness/src/extensions/local-tools/task-tools-client.ts index 2afbce762427..0fdf5928fe57 100644 --- a/products/desktop/packages/harness/src/extensions/local-tools/task-tools-client.ts +++ b/products/desktop/packages/harness/src/extensions/local-tools/task-tools-client.ts @@ -8,7 +8,7 @@ import { type TaskRunArtifact, type TaskRunUpdate, transferTimeoutMs, -} from "@posthog/shared"; +} from "@posthog/agent-contracts"; export { API_TRANSFER_TIMEOUT_MS, transferTimeoutMs, type TaskRunUpdate }; diff --git a/products/desktop/packages/harness/src/extensions/local-tools/tools/artifact-upload.ts b/products/desktop/packages/harness/src/extensions/local-tools/tools/artifact-upload.ts index af1376625700..77a2ed788e2d 100644 --- a/products/desktop/packages/harness/src/extensions/local-tools/tools/artifact-upload.ts +++ b/products/desktop/packages/harness/src/extensions/local-tools/tools/artifact-upload.ts @@ -2,7 +2,7 @@ import type { ArtifactSource, ArtifactType, TaskRunArtifact, -} from "@posthog/shared"; +} from "@posthog/agent-contracts"; import { type TaskToolsApiClient, transferTimeoutMs, diff --git a/products/desktop/packages/harness/src/extensions/local-tools/tools/show-actions.ts b/products/desktop/packages/harness/src/extensions/local-tools/tools/show-actions.ts index 8398b1e5a804..a88e33d066b1 100644 --- a/products/desktop/packages/harness/src/extensions/local-tools/tools/show-actions.ts +++ b/products/desktop/packages/harness/src/extensions/local-tools/tools/show-actions.ts @@ -1,4 +1,4 @@ -import { showActionSchema } from "@posthog/shared"; +import { showActionSchema } from "@posthog/agent-contracts"; import { z } from "zod"; import { defineLocalTool, type LocalToolResult } from "../registry"; diff --git a/products/desktop/packages/harness/src/extensions/local-tools/tools/upload-artifact.ts b/products/desktop/packages/harness/src/extensions/local-tools/tools/upload-artifact.ts index 295d3fc259c9..53440cb79bf2 100644 --- a/products/desktop/packages/harness/src/extensions/local-tools/tools/upload-artifact.ts +++ b/products/desktop/packages/harness/src/extensions/local-tools/tools/upload-artifact.ts @@ -1,6 +1,6 @@ import { readFile, realpath, stat } from "node:fs/promises"; import path from "node:path"; -import type { TaskRunArtifact } from "@posthog/shared"; +import type { TaskRunArtifact } from "@posthog/agent-contracts"; import { z } from "zod"; import { defineLocalTool, type LocalToolResult } from "../registry"; import { createSandboxPosthogClient } from "../signed-commit-artefacts"; diff --git a/products/desktop/packages/harness/src/extensions/mcp/tool-bridge.ts b/products/desktop/packages/harness/src/extensions/mcp/tool-bridge.ts index 323e25774859..35136941453f 100644 --- a/products/desktop/packages/harness/src/extensions/mcp/tool-bridge.ts +++ b/products/desktop/packages/harness/src/extensions/mcp/tool-bridge.ts @@ -22,7 +22,7 @@ import { CallToolResultSchema, ListToolsResultSchema, } from "@modelcontextprotocol/sdk/types.js"; -import { boundPersistedMcpResult } from "@posthog/shared"; +import { boundPersistedMcpResult } from "@posthog/agent-contracts"; import type { McpServerConfig, McpSettings } from "./config"; import { McpError } from "./errors"; import { renderMcpToolCall, stripTerminalSequences } from "./render"; diff --git a/products/desktop/packages/harness/src/extensions/orchestration/run-agent.ts b/products/desktop/packages/harness/src/extensions/orchestration/run-agent.ts index 092c0f15efea..140e60e2d27f 100644 --- a/products/desktop/packages/harness/src/extensions/orchestration/run-agent.ts +++ b/products/desktop/packages/harness/src/extensions/orchestration/run-agent.ts @@ -15,7 +15,7 @@ import { type AgentRunState, createPiToolCallRecord, type PiSubagentToolCall, -} from "@posthog/shared"; +} from "@posthog/agent-contracts"; import { createWebAccessExtension } from "../web-access/extension"; import type { AgentConfig } from "./agents"; import { diff --git a/products/desktop/packages/harness/src/extensions/orchestration/tools/subagent-tool.test.ts b/products/desktop/packages/harness/src/extensions/orchestration/tools/subagent-tool.test.ts index a132fef60040..1b1277691ce8 100644 --- a/products/desktop/packages/harness/src/extensions/orchestration/tools/subagent-tool.test.ts +++ b/products/desktop/packages/harness/src/extensions/orchestration/tools/subagent-tool.test.ts @@ -2,7 +2,7 @@ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; -import { createPiToolCallRecord } from "@posthog/shared"; +import { createPiToolCallRecord } from "@posthog/agent-contracts"; import { beforeEach, describe, expect, it, vi } from "vitest"; const { runAgentMock } = vi.hoisted(() => ({ diff --git a/products/desktop/packages/harness/src/extensions/orchestration/tools/subagent-tool.ts b/products/desktop/packages/harness/src/extensions/orchestration/tools/subagent-tool.ts index e10100d21e35..54d70b1a22f5 100644 --- a/products/desktop/packages/harness/src/extensions/orchestration/tools/subagent-tool.ts +++ b/products/desktop/packages/harness/src/extensions/orchestration/tools/subagent-tool.ts @@ -1,7 +1,7 @@ import { StringEnum } from "@earendil-works/pi-ai"; import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; import { defineTool } from "@earendil-works/pi-coding-agent"; -import type { PiSubagentToolDetails } from "@posthog/shared"; +import type { PiSubagentToolDetails } from "@posthog/agent-contracts"; import { Type } from "typebox"; import type { AgentConfig } from "../agents"; import { diff --git a/products/desktop/packages/harness/src/extensions/orchestration/ui/subagent-render.ts b/products/desktop/packages/harness/src/extensions/orchestration/ui/subagent-render.ts index 3e5e516cb0da..7521f3a66d4f 100644 --- a/products/desktop/packages/harness/src/extensions/orchestration/ui/subagent-render.ts +++ b/products/desktop/packages/harness/src/extensions/orchestration/ui/subagent-render.ts @@ -20,7 +20,7 @@ import { Text, truncateToWidth, } from "@earendil-works/pi-tui"; -import type { AgentRunState } from "@posthog/shared"; +import type { AgentRunState } from "@posthog/agent-contracts"; import { getFinalOutput } from "../format"; import { isFailedResult, diff --git a/products/desktop/packages/harness/src/extensions/orchestration/ui/workflow-render.ts b/products/desktop/packages/harness/src/extensions/orchestration/ui/workflow-render.ts index 7c70062a0b02..e695df22b874 100644 --- a/products/desktop/packages/harness/src/extensions/orchestration/ui/workflow-render.ts +++ b/products/desktop/packages/harness/src/extensions/orchestration/ui/workflow-render.ts @@ -21,7 +21,7 @@ import type { PiSubagentToolCall, PiWorkflowToolDetails, WorkflowAgentState, -} from "@posthog/shared"; +} from "@posthog/agent-contracts"; import { extractWorkflowName, type WorkflowInputs, diff --git a/products/desktop/packages/harness/src/extensions/posthog-mcp-policy/extension.test.ts b/products/desktop/packages/harness/src/extensions/posthog-mcp-policy/extension.test.ts index 2e76152c1d56..e3539006da09 100644 --- a/products/desktop/packages/harness/src/extensions/posthog-mcp-policy/extension.test.ts +++ b/products/desktop/packages/harness/src/extensions/posthog-mcp-policy/extension.test.ts @@ -2,7 +2,7 @@ import type { ExtensionAPI, ToolCallEvent, } from "@earendil-works/pi-coding-agent"; -import type { McpToolPermissionDecision } from "@posthog/shared"; +import type { McpToolPermissionDecision } from "@posthog/agent-contracts"; import { describe, expect, it, vi } from "vitest"; import { createPosthogMcpPolicyExtension } from "./extension"; diff --git a/products/desktop/packages/harness/src/extensions/posthog-mcp-policy/extension.ts b/products/desktop/packages/harness/src/extensions/posthog-mcp-policy/extension.ts index b0ee9ab59daf..635fdf0ef422 100644 --- a/products/desktop/packages/harness/src/extensions/posthog-mcp-policy/extension.ts +++ b/products/desktop/packages/harness/src/extensions/posthog-mcp-policy/extension.ts @@ -7,7 +7,7 @@ import type { McpToolPermissionDecision, McpToolPermissionRequest, McpToolPolicy, -} from "@posthog/shared"; +} from "@posthog/agent-contracts"; import { buildToolName } from "../mcp/tool-bridge"; export interface PosthogMcpPolicyOptions { diff --git a/products/desktop/packages/harness/src/extensions/posthog-mcp-policy/products.ts b/products/desktop/packages/harness/src/extensions/posthog-mcp-policy/products.ts index 559c9cc96a5c..8a101fb15353 100644 --- a/products/desktop/packages/harness/src/extensions/posthog-mcp-policy/products.ts +++ b/products/desktop/packages/harness/src/extensions/posthog-mcp-policy/products.ts @@ -12,7 +12,10 @@ * the single source of truth for the product id → label set; the renderer maps * ids to icons/styling for display. */ -import { POSTHOG_PRODUCTS, type PostHogProductId } from "@posthog/shared"; +import { + POSTHOG_PRODUCTS, + type PostHogProductId, +} from "@posthog/agent-contracts"; export { POSTHOG_PRODUCTS, type PostHogProductId }; diff --git a/products/desktop/packages/harness/src/extensions/posthog-provider/gateway.ts b/products/desktop/packages/harness/src/extensions/posthog-provider/gateway.ts index ac846ec8e7c1..ab476ddb7117 100644 --- a/products/desktop/packages/harness/src/extensions/posthog-provider/gateway.ts +++ b/products/desktop/packages/harness/src/extensions/posthog-provider/gateway.ts @@ -1,4 +1,4 @@ -import { type CloudRegion, getCustomCloud } from "@posthog/shared"; +import { type CloudRegion, getCustomCloud } from "@posthog/agent-contracts"; export const GATEWAY_PRODUCT = "posthog_code"; diff --git a/products/desktop/packages/harness/src/extensions/posthog-provider/model-catalog.test.ts b/products/desktop/packages/harness/src/extensions/posthog-provider/model-catalog.test.ts index f51a6e163655..a30b16f993e0 100644 --- a/products/desktop/packages/harness/src/extensions/posthog-provider/model-catalog.test.ts +++ b/products/desktop/packages/harness/src/extensions/posthog-provider/model-catalog.test.ts @@ -148,7 +148,7 @@ describe("resolvePosthogPiModelCatalog", () => { }); // One retired model and one the catalog never listed. `isOfferedModel` owns the full table, - // in packages/shared/src/model-catalog.test.ts; these two prove the filter is wired up here. + // in packages/agent-contracts/src/model-catalog.test.ts; these two prove the filter is wired up here. it.each(["claude-opus-4-7", "gpt-5.4"])( "excludes %s from the Pi catalog", (id) => { diff --git a/products/desktop/packages/harness/src/extensions/posthog-provider/model-catalog.ts b/products/desktop/packages/harness/src/extensions/posthog-provider/model-catalog.ts index 5fe3e60e53ea..803e7a6254ef 100644 --- a/products/desktop/packages/harness/src/extensions/posthog-provider/model-catalog.ts +++ b/products/desktop/packages/harness/src/extensions/posthog-provider/model-catalog.ts @@ -3,8 +3,14 @@ import { type ModelThinkingLevel, } from "@earendil-works/pi-ai"; import type { ModelInfo } from "@earendil-works/pi-coding-agent"; -import { type CloudRegion, formatGatewayModelName } from "@posthog/shared"; -import { isOfferedModel, labelForModel } from "@posthog/shared/model-catalog"; +import { + type CloudRegion, + formatGatewayModelName, +} from "@posthog/agent-contracts"; +import { + isOfferedModel, + labelForModel, +} from "@posthog/agent-contracts/model-catalog"; import { fetchPosthogGatewayModels, type GatewayModel, diff --git a/products/desktop/packages/harness/src/extensions/posthog-provider/models.ts b/products/desktop/packages/harness/src/extensions/posthog-provider/models.ts index bb60e1fb2f8d..f676cb42f9e6 100644 --- a/products/desktop/packages/harness/src/extensions/posthog-provider/models.ts +++ b/products/desktop/packages/harness/src/extensions/posthog-provider/models.ts @@ -1,8 +1,8 @@ import type { ThinkingLevelMap } from "@earendil-works/pi-ai"; import { getBuiltinModels } from "@earendil-works/pi-ai/providers/all"; import type { ProviderModelConfig } from "@earendil-works/pi-coding-agent"; -import type { CloudRegion } from "@posthog/shared"; -import { buildPosthogProjectHeaderRecord } from "@posthog/shared/posthog-property-headers"; +import type { CloudRegion } from "@posthog/agent-contracts"; +import { buildPosthogProjectHeaderRecord } from "@posthog/agent-contracts/posthog-property-headers"; import { getLlmGatewayUrl } from "./gateway"; export const DEFAULT_MODEL = "claude-opus-5-5"; diff --git a/products/desktop/packages/harness/src/extensions/posthog-provider/oauth.test.ts b/products/desktop/packages/harness/src/extensions/posthog-provider/oauth.test.ts index 80d3fa43700e..a8cbcb1eace6 100644 --- a/products/desktop/packages/harness/src/extensions/posthog-provider/oauth.test.ts +++ b/products/desktop/packages/harness/src/extensions/posthog-provider/oauth.test.ts @@ -4,7 +4,7 @@ import { getCloudUrlFromRegion, getOauthClientIdFromRegion, OAUTH_SCOPES, -} from "@posthog/shared"; +} from "@posthog/agent-contracts"; import { afterEach, beforeEach, diff --git a/products/desktop/packages/harness/src/extensions/posthog-provider/oauth.ts b/products/desktop/packages/harness/src/extensions/posthog-provider/oauth.ts index 35a8e11bc443..f53577473518 100644 --- a/products/desktop/packages/harness/src/extensions/posthog-provider/oauth.ts +++ b/products/desktop/packages/harness/src/extensions/posthog-provider/oauth.ts @@ -11,7 +11,7 @@ import { getCustomCloud, getOauthClientIdFromRegion, OAUTH_SCOPES, -} from "@posthog/shared"; +} from "@posthog/agent-contracts"; const OAUTH_TIMEOUT_MS = 180_000; const TOKEN_FETCH_TIMEOUT_MS = 30_000; diff --git a/products/desktop/packages/harness/src/extensions/posthog-provider/provider.test.ts b/products/desktop/packages/harness/src/extensions/posthog-provider/provider.test.ts index 0b1838a1f53b..7aa7feee8796 100644 --- a/products/desktop/packages/harness/src/extensions/posthog-provider/provider.test.ts +++ b/products/desktop/packages/harness/src/extensions/posthog-provider/provider.test.ts @@ -1,5 +1,5 @@ import type { Api, Model } from "@earendil-works/pi-ai"; -import type { CloudRegion } from "@posthog/shared"; +import type { CloudRegion } from "@posthog/agent-contracts"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { getLlmGatewayUrl } from "./gateway"; import { diff --git a/products/desktop/packages/harness/src/extensions/posthog-provider/provider.ts b/products/desktop/packages/harness/src/extensions/posthog-provider/provider.ts index f53ecfbddfc1..62324e3d5b98 100644 --- a/products/desktop/packages/harness/src/extensions/posthog-provider/provider.ts +++ b/products/desktop/packages/harness/src/extensions/posthog-provider/provider.ts @@ -8,7 +8,7 @@ import type { ProviderConfig, ProviderModelConfig, } from "@earendil-works/pi-coding-agent"; -import type { CloudRegion } from "@posthog/shared"; +import type { CloudRegion } from "@posthog/agent-contracts"; import { getLlmGatewayUrl, resolveExplicitRegion, diff --git a/products/desktop/packages/harness/src/extensions/product-engineer/extension.test.ts b/products/desktop/packages/harness/src/extensions/product-engineer/extension.test.ts index 26f08966d0b8..36593d12c5af 100644 --- a/products/desktop/packages/harness/src/extensions/product-engineer/extension.test.ts +++ b/products/desktop/packages/harness/src/extensions/product-engineer/extension.test.ts @@ -1,6 +1,6 @@ import { fileURLToPath } from "node:url"; import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; -import { PRODUCT_ENGINEER_PROMPT } from "@posthog/shared/product-engineer-prompt"; +import { PRODUCT_ENGINEER_PROMPT } from "@posthog/agent-contracts/product-engineer-prompt"; import { describe, expect, it } from "vitest"; import { createProductEngineerExtension } from "./extension"; diff --git a/products/desktop/packages/harness/src/extensions/product-engineer/extension.ts b/products/desktop/packages/harness/src/extensions/product-engineer/extension.ts index b9521562ad9f..733c010505fc 100644 --- a/products/desktop/packages/harness/src/extensions/product-engineer/extension.ts +++ b/products/desktop/packages/harness/src/extensions/product-engineer/extension.ts @@ -4,7 +4,7 @@ import type { ExtensionAPI, ExtensionFactory, } from "@earendil-works/pi-coding-agent"; -import { prependProductEngineerPrompt } from "@posthog/shared/product-engineer-prompt"; +import { prependProductEngineerPrompt } from "@posthog/agent-contracts/product-engineer-prompt"; function resolveProductEngineerResource(relativePath: string): string { const adjacentPath = fileURLToPath(new URL(relativePath, import.meta.url)); diff --git a/products/desktop/packages/harness/src/extensions/skills-store/store-skills.test.ts b/products/desktop/packages/harness/src/extensions/skills-store/store-skills.test.ts index e596b7744bc0..de3359b73d31 100644 --- a/products/desktop/packages/harness/src/extensions/skills-store/store-skills.test.ts +++ b/products/desktop/packages/harness/src/extensions/skills-store/store-skills.test.ts @@ -8,7 +8,7 @@ import { } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import type { StoreSkillStub } from "@posthog/shared"; +import type { StoreSkillStub } from "@posthog/agent-contracts"; import { afterEach, describe, expect, it, vi } from "vitest"; import { buildStoreSkillsInstructions, diff --git a/products/desktop/packages/harness/src/extensions/skills-store/store-skills.ts b/products/desktop/packages/harness/src/extensions/skills-store/store-skills.ts index c5785e270a0c..377ec88b7272 100644 --- a/products/desktop/packages/harness/src/extensions/skills-store/store-skills.ts +++ b/products/desktop/packages/harness/src/extensions/skills-store/store-skills.ts @@ -9,7 +9,7 @@ import { } from "node:fs/promises"; import { homedir } from "node:os"; import { join } from "node:path"; -import type { StoreSkillStub, TaskRunState } from "@posthog/shared"; +import type { StoreSkillStub, TaskRunState } from "@posthog/agent-contracts"; /** * The store stamps every stub's frontmatter with `metadata.source`. Only a diff --git a/products/desktop/packages/harness/src/extensions/task-system-prompt/extension.test.ts b/products/desktop/packages/harness/src/extensions/task-system-prompt/extension.test.ts index e5fa36f13c0a..4dcb0282b05f 100644 --- a/products/desktop/packages/harness/src/extensions/task-system-prompt/extension.test.ts +++ b/products/desktop/packages/harness/src/extensions/task-system-prompt/extension.test.ts @@ -3,7 +3,7 @@ import type { SessionEntry, SessionManager, } from "@earendil-works/pi-coding-agent"; -import type { TaskContext } from "@posthog/shared/task-context"; +import type { TaskContext } from "@posthog/agent-contracts/task-context"; import { describe, expect, it, vi } from "vitest"; import { createPiTaskSystemPromptExtension, diff --git a/products/desktop/packages/harness/src/extensions/task-system-prompt/extension.ts b/products/desktop/packages/harness/src/extensions/task-system-prompt/extension.ts index 139ebf7890e9..11d354a3408c 100644 --- a/products/desktop/packages/harness/src/extensions/task-system-prompt/extension.ts +++ b/products/desktop/packages/harness/src/extensions/task-system-prompt/extension.ts @@ -4,7 +4,7 @@ import type { SessionEntry, SessionManager, } from "@earendil-works/pi-coding-agent"; -import type { TaskContext } from "@posthog/shared/task-context"; +import type { TaskContext } from "@posthog/agent-contracts/task-context"; import { buildTaskSystemPrompt, type TaskPromptCapabilities } from "./prompt"; export const POSTHOG_PI_TASK_CONTEXT_ENTRY_TYPE = "posthog.pi.task-context"; diff --git a/products/desktop/packages/harness/src/extensions/task-system-prompt/prompt.ts b/products/desktop/packages/harness/src/extensions/task-system-prompt/prompt.ts index e87808a747d6..e43ccca99749 100644 --- a/products/desktop/packages/harness/src/extensions/task-system-prompt/prompt.ts +++ b/products/desktop/packages/harness/src/extensions/task-system-prompt/prompt.ts @@ -1,4 +1,4 @@ -import type { TaskContext } from "@posthog/shared/task-context"; +import type { TaskContext } from "@posthog/agent-contracts/task-context"; import { buildTaskSummaryInstructions } from "./task-summary"; export interface TaskPromptCapabilities { diff --git a/products/desktop/packages/harness/src/extensions/web-access/web-fetch.ts b/products/desktop/packages/harness/src/extensions/web-access/web-fetch.ts index 7cacd7db584c..d12a6aeb06bb 100644 --- a/products/desktop/packages/harness/src/extensions/web-access/web-fetch.ts +++ b/products/desktop/packages/harness/src/extensions/web-access/web-fetch.ts @@ -1,6 +1,9 @@ import { isIP } from "node:net"; import { defineTool } from "@earendil-works/pi-coding-agent"; -import { isPrivateIpv4Octets, isPrivateIpv6Literal } from "@posthog/shared"; +import { + isPrivateIpv4Octets, + isPrivateIpv6Literal, +} from "@posthog/agent-contracts"; import { LRUCache } from "lru-cache"; import TurndownService from "turndown"; import { Type } from "typebox"; @@ -41,7 +44,7 @@ function ipv4Octets(hostname: string): number[] | undefined { * That would require enforcing the check at connection time, not URL-parse * time — out of scope here, but worth remembering as a residual gap. The * IPv4-range and IPv6-literal kernels are shared with the other private-host - * classifiers via `@posthog/shared`. + * classifiers via `@posthog/agent-contracts`. */ function isBlockedHost(rawHostname: string): boolean { // `URL#hostname` keeps IPv6 literals bracketed ("[::1]"); `net.isIP`/our diff --git a/products/desktop/packages/shared/package.json b/products/desktop/packages/shared/package.json index 942b00ffe5cb..b0acbfd21e93 100644 --- a/products/desktop/packages/shared/package.json +++ b/products/desktop/packages/shared/package.json @@ -81,6 +81,7 @@ "clean": "node ../../scripts/rimraf.mjs dist .turbo" }, "dependencies": { + "@posthog/agent-contracts": "workspace:*", "@posthog/brand": "catalog:", "zod": "^4.1.12" }, diff --git a/products/desktop/packages/shared/src/analytics-events.ts b/products/desktop/packages/shared/src/analytics-events.ts index 1852b7881d25..a5553bbcb9d9 100644 --- a/products/desktop/packages/shared/src/analytics-events.ts +++ b/products/desktop/packages/shared/src/analytics-events.ts @@ -1,8 +1,8 @@ // Analytics event types and properties -import type { Adapter, ModelAccess } from "./adapter"; -import type { EffortLevel } from "./domain-types"; -import type { SourceProduct } from "./inbox-types"; +import type { Adapter, ModelAccess } from "@posthog/agent-contracts/adapter"; +import type { EffortLevel } from "@posthog/agent-contracts/domain-types"; +import type { SourceProduct } from "@posthog/agent-contracts/inbox-types"; export interface PromptHistoryOpenedProperties { entry_count: number; diff --git a/products/desktop/packages/shared/src/constants.ts b/products/desktop/packages/shared/src/constants.ts index 4a19c4b3bee6..d09705809a1c 100644 --- a/products/desktop/packages/shared/src/constants.ts +++ b/products/desktop/packages/shared/src/constants.ts @@ -4,7 +4,7 @@ export const SELF_DRIVING_SETUP_TASK_FLAG = featureFlagKeys.SELF_DRIVING_SETUP_TASK_FLAG; export const POSTHOG_CODE_INTERNAL_CHILD_ENV = "POSTHOG_CODE_INTERNAL_CHILD"; -export { IMAGE_TOOLS_ENV_KEY } from "./sandbox-env"; +export { IMAGE_TOOLS_ENV_KEY } from "@posthog/agent-contracts/sandbox-env"; // Mirrors --color-background (dark) in packages/ui globals.css, for surfaces // that cannot read CSS variables: the Electron window and the boot error screen. diff --git a/products/desktop/packages/shared/src/dismissalReasons.ts b/products/desktop/packages/shared/src/dismissalReasons.ts index a01b6b13b9ab..d0703e89bdfd 100644 --- a/products/desktop/packages/shared/src/dismissalReasons.ts +++ b/products/desktop/packages/shared/src/dismissalReasons.ts @@ -6,4 +6,4 @@ export { RESOLVE_REASON_OPTIONS, type ReportStateReason, type ResolveReasonOptionValue, -} from "./dismissal-reasons"; +} from "@posthog/agent-contracts/dismissal-reasons"; diff --git a/products/desktop/packages/shared/src/domain-types.ts b/products/desktop/packages/shared/src/domain-types.ts index 32009619ec85..928a1c5a8532 100644 --- a/products/desktop/packages/shared/src/domain-types.ts +++ b/products/desktop/packages/shared/src/domain-types.ts @@ -1,1221 +1 @@ -import { z } from "zod"; -import type { Adapter } from "./adapter"; -import type { AgentRuntime } from "./agent-runtime"; -import type { ReportStateReason } from "./dismissal-reasons"; -import { REASONING_EFFORT_LABELS } from "./model-catalog.generated"; -import type { StoredLogEntry } from "./session-events"; -import type { UploadableSkillSource } from "./skills"; - -// Execution mode schema and type - shared between main and renderer -const executionModeSchema = z.enum([ - "default", - "acceptEdits", - "plan", - "bypassPermissions", - "auto", - "read-only", - "full-access", -]); - -import type { ExecutionMode } from "./exec-types"; -export type { ExecutionMode }; - -// Effort level schema and type - shared between main and renderer -export const effortLevelSchema = z.enum([ - "low", - "medium", - "high", - "xhigh", - "max", - "ultracode", -]); -export type EffortLevel = z.infer; - -/** All effort levels in ascending order of depth. */ -export const EFFORT_LEVELS = effortLevelSchema.options; - -/** - * OpenAI service tiers a Codex run can request. "flex" is the cheaper, slower - * queue; "priority" the faster one; "default" pins standard routing explicitly. - * Codex only sends a tier its model catalogue advertises for the model in use. - */ -export const serviceTierSchema = z.enum(["default", "priority", "flex"]); -export type ServiceTier = z.infer; - -export const SERVICE_TIERS = serviceTierSchema.options; - -// The annotation is the check: a depth added to `EffortLevel` but not to the catalog (or -// the reverse) fails to compile here. -export const EFFORT_LEVEL_LABELS: Record = - REASONING_EFFORT_LABELS; - -/** Claude Code docs for the tiers that need explaining. */ -export const EFFORT_LEVEL_DOCS_URLS: Partial> = { - ultracode: "https://code.claude.com/docs/en/workflows", -}; - -/** Adapter-specific docs for the fast mode toggle. */ -export const FAST_MODE_DOCS_URLS: Record = { - claude: "https://code.claude.com/docs/en/model-config", - codex: "https://developers.openai.com/codex", -}; - -export interface UserBasic { - id: number; - uuid: string; - distinct_id?: string | null; - first_name?: string; - last_name?: string; - email: string; - is_email_verified?: boolean | null; -} - -/** One row from the org members list; trimmed to what mention pickers need. */ -export interface OrganizationMemberBasic { - id: string; - user: UserBasic; -} - -export interface Task { - id: string; - task_number: number | null; - slug: string; - title: string; - title_manually_set?: boolean; - description: string; - // First characters of the description, present instead of the full body when the - // list was fetched with basic=true. Absent on the full and single-task responses. - description_preview?: string; - created_at: string; - updated_at: string; - /** - * When something last happened in the session, as opposed to `updated_at` (when the row was - * last written). The timestamp a "recent activity" sort reads. Empty on older responses. - */ - last_activity_at?: string; - created_by?: UserBasic | null; - origin_product: string; - repository?: string | null; // Format: "organization/repository" (e.g., "posthog/posthog-js") - repositories?: string[]; - github_integration?: number | null; - github_user_integration?: string | null; - json_schema?: Record | null; - signal_report?: string | null; - internal?: boolean; - /** Key of the server-side flow that created the task, e.g. `desktop_onboarding_session:`. */ - origin_key?: string | null; - runtime?: AgentRuntime; - /** Backend channel (tasks product Channel UUID) this task is owned by. */ - channel?: string | null; - latest_run?: TaskRun; -} - -export interface TaskSearchResult { - id: string; - kind: "task" | "pull_request" | "artifact" | "channel" | "canvas"; - title: string; - subtitle: string; - task_id: string | null; - task_run_id: string | null; - channel_id: string | null; - created_by?: UserBasic | null; - /** What created the containing task, e.g. "slack". */ - origin_product?: string | null; - latest_run?: TaskSearchResultRun | null; - updated_at: string; - metadata: Record; -} - -export interface TaskSearchResultRun { - id: string; - status: TaskRunStatus | null; - environment: TaskRunEnvironment | null; -} - -/** - * A backend task channel — the single channel identity: it owns the task feed, - * threads, instructions (CONTEXT.md) and filed canvases. `personal` is the - * user's private "#me" channel. `starred` is per-user. - */ -export interface ProvisionedTaskChannels { - channels: TaskChannel[]; - personal_created: boolean; - general_created: boolean; -} - -export interface TaskChannel { - id: string; - name: string; - channel_type: "public" | "personal" | "private"; - starred: boolean; - github_integration?: number | null; - repositories?: string[]; - auto_archive_after_days?: number | null; - created_at: string; - created_by?: UserBasic | null; - system_role?: "personal" | "general" | null; -} - -/** Lifecycle events a client may post into a channel's feed. */ -export type ChannelFeedMessageEvent = "context_md_building"; - -export type { - SpaceFeatureInput, - SpaceGoalDirection, - SpaceGoalInput, - SpaceGoalPeriod, - SpaceSetupInput, - SpaceSetupKind, - SpaceSetupStarted, -} from "./schemas"; - -/** - * A durable, team-visible "PostHog agent" announcement in a channel's feed — - * rendered alongside task cards (e.g. "Adam created this context"). `author` is - * the user whose action produced the row; `author_kind` says who authored it. - * `payload` carries structured event data (e.g. `{ context_name }`) so rendering - * survives renames. - */ -export interface ChannelFeedMessage { - id: string; - channel: string; - author?: UserBasic | null; - author_kind: "human" | "system" | "agent"; - event: ChannelFeedMessageEvent | string; - payload: Record; - content: string; - created_at: string; -} - -/** - * One human message in a task's thread. Thread messages never reach the agent - * unless the task author forwards one, which stamps the forwarded_* fields. - */ -export interface TaskThreadMessage { - id: string; - task: string; - /** Who authored the row; agent rows are server-emitted announcements. Absent on older backends. */ - author_kind?: "human" | "system" | "agent"; - /** Stable event key for non-human rows (e.g. "canvas_created", "turn_complete"). */ - event?: string; - /** Structured event payload; turn_complete carries `{ run_id }` so a client rendering a run's live agent turns can dedupe the durable row. */ - payload?: Record; - content: string; - created_at: string; - author?: UserBasic | null; - forwarded_to_agent_at?: string | null; - forwarded_by?: UserBasic | null; - /** Users mentioned in the row, indexed at write time. Absent on older backends. */ - mentioned_user_ids?: number[]; -} - -/** The latest resolve or reopen on a comment thread. */ -export interface TaskCommentStateEvent { - state: "resolved" | "open"; - author?: UserBasic | null; - created_at: string; -} - -/** - * One comment thread on a task, collapsed the way the activity timeline shows it - * (`/thread_messages/comment_activity/`). Mirrors `TaskCommentActivityDTO`. - */ -export interface TaskCommentThreadSummary { - id: string; - target: { id: string; type: string; name: string }; - content: string; - content_truncated: boolean; - selected_text: string | null; - author?: UserBasic | null; - created_at: string; - last_activity_at: string; - reply_count: number; - participants: UserBasic[]; - mentioned_user_ids: number[]; - resolved: boolean; - state_event: TaskCommentStateEvent | null; - latest_reply: { - author?: UserBasic | null; - content: string; - /** The excerpt is bounded, so a long reply comes back cut. */ - content_truncated: boolean; - created_at: string; - } | null; -} - -/** - * One @-mention of the current user in a task's thread, from the backend - * mentions index (`/task_mentions/`). Mirrors `TaskMentionDTO`. - */ -export interface TaskMention { - id: string; - message_id: string; - task_id: string; - task_title: string; - channel_id?: string | null; - channel_name?: string | null; - author?: UserBasic | null; - content: string; - created_at: string; -} - -/** Which signal produced an activity row; mirrors the backend `activity_kind`. */ -export type TaskActivityKind = - | "awaiting_input" - | "completed" - | "message" - | "mention" - | "thread_reply" - | "owned_item_comment" - | "created"; - -/** - * One task the current user is involved in, from the backend task-activity feed - * (`/task_activity/`). One row per task, newest activity first. Mirrors - * `TaskActivityDTO`. - */ -export interface TaskActivity { - id: string; - task_id: string; - task_title: string; - channel_id?: string | null; - channel_name?: string | null; - activity_at: string; - activity_kind: TaskActivityKind; - snippet: string; - latest_author?: UserBasic | null; - latest_message_id?: string | null; - latest_comment_id?: string | null; - latest_comment_scope?: string | null; - latest_comment_item_id?: string | null; - is_unread: boolean; -} - -export interface TaskActivityPage { - results: TaskActivity[]; - /** Unread tasks across the whole feed, not just this page. Backs the sidebar badge. */ - unread_count: number; - next_before?: string | null; - next_before_id?: string | null; -} - -export interface TaskActivityReadMarker { - task_id: string; - seen_before: string; - activity_id?: string; -} - -export interface TaskActivityMarkReadResult { - marked_read: number; - unread_count: number; -} - -export type TaskRunStatus = - | "not_started" - | "queued" - | "in_progress" - | "completed" - | "failed" - | "cancelled"; - -export type TaskRunEnvironment = "local" | "cloud"; - -const optionalField = ( - field: T, -): z.ZodCatch> => field.optional().catch(undefined); - -const pendingFollowupMessageSchema = z.object({ - id: z.string(), - content: z.string(), - ts: z.string().optional(), -}); - -export type PendingFollowupMessage = z.infer< - typeof pendingFollowupMessageSchema ->; - -export function readPendingFollowupMessages( - state: Record | undefined, -): PendingFollowupMessage[] { - const parsed = z - .array(pendingFollowupMessageSchema) - .safeParse(state?.pending_followup_messages); - return parsed.success ? parsed.data : []; -} - -/** - * One skills-store skill the sandbox agent lists as a local skill. The task - * worker resolves the list into run state; the agent renders a pointer - * SKILL.md per entry and fetches the body over MCP only when it is invoked. - */ -const storeSkillStubSchema = z.object({ - name: z.string(), - description: z.string(), - version: z.number(), -}); - -export type StoreSkillStub = z.infer; - -const taskRunStateFields = { - ai_agent_name: optionalField(z.string()), - ai_stage: optionalField(z.string()), - auto_publish: optionalField(z.boolean()), - benjamin_version: optionalField(z.string()), - initial_permission_mode: optionalField(executionModeSchema), - initial_prompt_override: optionalField(z.string()), - pending_followup_messages: optionalField( - z.array(pendingFollowupMessageSchema), - ), - pending_user_artifact_ids: optionalField(z.array(z.string())), - pending_user_message: optionalField(z.string()), - pending_user_message_id: optionalField(z.string()), - prewarmed: optionalField(z.boolean()), - reasoning_effort: optionalField( - z.union([effortLevelSchema, z.enum(["off", "minimal"])]), - ), - resume_from_run_id: optionalField(z.string()), - sandbox_environment_id: optionalField(z.string()), - slack_artifact_delivery: optionalField( - z.enum(["none", "message", "canvas_file"]), - ), - slack_chart_delivery: optionalField(z.boolean()), - slack_notified_pr_url: optionalField(z.string()), - slack_thread_url: optionalField(z.string()), - snapshot_kind: optionalField(z.string()), - store_skills: optionalField(z.array(storeSkillStubSchema)), - token_usage: optionalField(z.record(z.string(), z.unknown())), -} satisfies z.ZodRawShape; - -export const taskRunStateSchema = z.looseObject(taskRunStateFields).catch({}); - -export type TaskRunState = z.infer; -export type TaskRunStateField = keyof typeof taskRunStateFields; - -export type ArtifactType = - | "plan" - | "context" - | "reference" - | "output" - | "artifact" - | "user_attachment" - | "skill_bundle"; - -export type ArtifactSource = - | "agent_output" - | "user_attachment" - | "posthog_code_skill" - | "posthog_object"; - -export interface SkillBundleArtifactMetadata { - skill_name: string; - skill_source: UploadableSkillSource; - content_sha256: string; - bundle_format: "zip"; - schema_version: number; -} - -export interface PostHogObjectArtifactMetadata { - reference_type: "posthog_object"; - object_kind: string; - object_id: string; - source_message_ids: string[]; - occurrence_count: number; -} - -export type TaskRunArtifactMetadata = - | SkillBundleArtifactMetadata - | PostHogObjectArtifactMetadata; - -export function isSkillBundleArtifactMetadata( - metadata: TaskRunArtifactMetadata | undefined, -): metadata is SkillBundleArtifactMetadata { - return metadata !== undefined && "skill_name" in metadata; -} - -export interface TaskRunArtifact { - id?: string; - name: string; - type: ArtifactType; - source?: ArtifactSource; - size?: number; - content_type?: string; - metadata?: TaskRunArtifactMetadata; - storage_path?: string; - uploaded_at?: string; - uploaded_by?: "agent" | "user"; - uploaded_by_user_id?: number; - dismissed_at?: string | null; -} - -export const TERMINAL_STATUSES = ["completed", "failed", "cancelled"] as const; - -export function isTerminalStatus( - status: TaskRunStatus | string | null | undefined, -): boolean { - return ( - status !== null && - status !== undefined && - TERMINAL_STATUSES.includes(status as (typeof TERMINAL_STATUSES)[number]) - ); -} - -export function isContentlessTask(task: { - title?: string | null; - description?: string | null; -}): boolean { - return !task.title?.trim() && !task.description?.trim(); -} - -export interface TaskRun { - id: string; - task: string; // Task ID - team: number; - branch: string | null; - runtime_adapter?: Adapter | null; - model?: string | null; - reasoning_effort?: EffortLevel | null; - stage?: string | null; // Current stage (e.g., 'research', 'plan', 'build') - environment?: TaskRunEnvironment; - status: TaskRunStatus; - log_url: string; - error_message: string | null; - output: Record | null; // Structured output (PR URL, commit SHA, etc.) - state: TaskRunState; - artifacts?: TaskRunArtifact[]; - created_at: string; - updated_at: string; - completed_at: string | null; -} - -export type NetworkAccessLevel = "trusted" | "full" | "custom"; - -export interface SandboxEnvironment { - id: string; - name: string; - network_access_level: NetworkAccessLevel; - allowed_domains: string[]; - include_default_domains: boolean; - repositories: string[]; - has_environment_variables: boolean; - /** - * Names of the variables that are set. Values are write-only and never returned. - * Optional because desktop releases are not orchestrated with backend deploys, so a - * client can reach an API that predates this field. - */ - environment_variable_keys?: string[]; - private: boolean; - effective_domains: string[]; - custom_image_id: string | null; - custom_image_name: string | null; - custom_image_status: string | null; - created_by?: UserBasic | null; - created_at: string; - updated_at: string; -} - -export interface SandboxEnvironmentInput { - name: string; - network_access_level: NetworkAccessLevel; - allowed_domains?: string[]; - include_default_domains?: boolean; - repositories?: string[]; - environment_variables?: Record; - private?: boolean; - custom_image_id?: string | null; -} - -export type SandboxCustomImageStatus = - | "draft" - | "scanning" - | "scan_failed" - | "building" - | "build_failed" - | "ready" - | "archived"; - -export function isImageBuildInProgress( - status: SandboxCustomImageStatus, -): boolean { - return status === "scanning" || status === "building"; -} - -export function isImageBuildFailed(status: SandboxCustomImageStatus): boolean { - return status === "scan_failed" || status === "build_failed"; -} - -export interface SandboxCustomImageScanFinding { - severity: string; - detail: string; -} - -export interface SandboxCustomImage { - id: string; - name: string; - description: string; - status: SandboxCustomImageStatus; - version: number; - modal_image_name: string; - repository: string; - private: boolean; - spec: Record; - spec_yaml: string; - scan_result: { - passed?: boolean; - findings?: SandboxCustomImageScanFinding[]; - }; - error: string; - build_log: string; - builder_task_id: string | null; - created_by?: UserBasic | null; - created_at: string; - updated_at: string; -} - -interface CloudTaskUpdateBase { - taskId: string; - runId: string; -} - -export interface CloudTaskLogsUpdate extends CloudTaskUpdateBase { - kind: "logs"; - newEntries: StoredLogEntry[]; - totalEntryCount: number; -} - -export interface CloudTaskStatusUpdate extends CloudTaskUpdateBase { - kind: "status"; - status?: TaskRunStatus; - stage?: string | null; - output?: Record | null; - errorMessage?: string | null; - branch?: string | null; - sandboxAlive?: boolean | null; -} - -export interface CloudTaskSnapshotUpdate extends CloudTaskUpdateBase { - kind: "snapshot"; - newEntries: StoredLogEntry[]; - totalEntryCount: number; - /** Chain index of newEntries[0] when the snapshot is a tail window rather - * than the full history; older entries page in on demand. Absent means - * the snapshot starts at the head of the chain. */ - windowStart?: number; - rebuilt?: boolean; - status?: TaskRunStatus; - stage?: string | null; - output?: Record | null; - errorMessage?: string | null; - branch?: string | null; - sandboxAlive?: boolean | null; -} - -export interface CloudTaskErrorUpdate extends CloudTaskUpdateBase { - kind: "error"; - errorTitle: string; - errorMessage: string; - retryable: boolean; -} - -export interface CloudPermissionOption { - kind: string; - optionId: string; - name: string; - _meta?: Record; -} - -export interface CloudTaskPermissionRequestUpdate extends CloudTaskUpdateBase { - kind: "permission_request"; - requestId: string; - toolCall: { - toolCallId: string; - title: string; - kind: string; - content?: unknown[]; - rawInput?: Record; - _meta?: Record; - }; - options: CloudPermissionOption[]; -} - -export type CloudTaskUpdatePayload = - | CloudTaskLogsUpdate - | CloudTaskStatusUpdate - | CloudTaskSnapshotUpdate - | CloudTaskErrorUpdate - | CloudTaskPermissionRequestUpdate; - -// Mention types for editors -type MentionType = - | "file" - | "folder" - | "error" - | "experiment" - | "insight" - | "feature_flag" - | "generic"; - -export interface MentionItem { - // File items - path?: string; - name?: string; - kind?: "file" | "directory"; - // URL items - url?: string; - type?: MentionType; - label?: string; - id?: string; - urlId?: string; -} - -// Git file status types -import type { GitFileStatus } from "./git-types"; -export type { GitFileStatus }; - -export type GitBusyOperation = "rebase" | "merge" | "cherry-pick" | "revert"; - -export type GitBusyState = - | { busy: false } - | { busy: true; operation: GitBusyOperation }; - -export interface ChangedFile { - path: string; - status: GitFileStatus; - originalPath?: string; // For renames: the old path - linesAdded?: number; - linesRemoved?: number; - staged?: boolean; - patch?: string; // Unified diff patch from GitHub API - sha?: string; -} - -// External apps detection types -export type ExternalAppType = - | "editor" - | "terminal" - | "file-manager" - | "git-client"; - -export interface DetectedApplication { - id: string; // "vscode", "cursor", "iterm" - name: string; // "Visual Studio Code" - type: ExternalAppType; - path: string; // "/Applications/Visual Studio Code.app" - command: string; // Launch command - icon?: string; // Base64 data URL -} - -import type { SignalReportStatus } from "./signal-types"; -export type { SignalReportStatus }; - -/** Actionability priority from the researched report (actionability judgment artefact). */ -export type SignalReportPriority = "P0" | "P1" | "P2" | "P3" | "P4"; - -/** Latest known state of a report's implementation PR. */ -export type SignalReportPrState = - | "unknown" - | "draft" - | "open" - | "closed" - | "merged"; - -/** Actionability choice from the researched report. */ -export type SignalReportActionability = - | "immediately_actionable" - | "requires_human_input" - | "not_actionable"; - -/** - * One or more `SignalReportStatus` values joined by commas, e.g. `potential` or `potential,candidate,ready`. - * This looks horrendous but it's superb, trust me bro. - */ -export type CommaSeparatedSignalReportStatuses = - | SignalReportStatus - | `${SignalReportStatus},${SignalReportStatus}` - | `${SignalReportStatus},${SignalReportStatus},${SignalReportStatus}` - | `${SignalReportStatus},${SignalReportStatus},${SignalReportStatus},${SignalReportStatus}` - | `${SignalReportStatus},${SignalReportStatus},${SignalReportStatus},${SignalReportStatus},${SignalReportStatus}`; - -export type SignalReportChartSize = "small" | "medium" | "large"; - -/** - * One chart attached to a report (`SignalReport.charts` on the backend serializer). - * `query` is stored unparsed; the backend only guarantees `kind` is one of - * InsightVizNode, DataVisualizationNode, or SavedInsightNode. - */ -export interface SignalReportChart { - chart_id: string; - title: string; - query: unknown; - caption?: string | null; - size?: SignalReportChartSize | null; -} - -export interface SignalReport { - id: string; - title: string | null; - summary: string | null; - status: SignalReportStatus; - total_weight: number; - signal_count: number; - signals_at_run?: number; - created_at: string; - updated_at: string; - artefact_count: number; - collapsed_note_count?: number; - /** P0–P4 from priority judgment when the report is researched */ - priority?: SignalReportPriority | null; - /** Actionability choice from the actionability judgment artefact. */ - actionability?: SignalReportActionability | null; - /** Whether the issue appears already fixed, from the actionability judgment artefact. */ - already_addressed?: boolean | null; - /** Reason code from the latest dismiss or resolve artefact. */ - dismissal_reason?: ReportStateReason | null; - /** Free-form note captured alongside the dismissal reason. */ - dismissal_note?: string | null; - /** Whether the current user is a suggested reviewer for this report (server-annotated). */ - is_suggested_reviewer?: boolean; - /** Distinct source products contributing signals to this report. */ - source_products?: string[]; - /** PR URL from the latest implementation task run, if available. */ - implementation_pr_url?: string | null; - work_state?: "unclaimed" | "working" | "in_review" | "done"; - assignee?: { - kind: "user" | "task" | "agent" | "system"; - task_id: string | null; - } | null; - /** - * Whether that PR merged (GitHub webhook). A merged PR is history, not work - * in flight: a report can outlive its fix when evidence keeps arriving, and - * its old PR must not read as reviewable or continuable. - */ - implementation_pr_merged?: boolean; - /** Latest known state of that PR, per the GitHub webhook. */ - implementation_pr_state?: SignalReportPrState | null; - /** Link to the tracker issue self-driving opened for this report's PR, when the project tracks issues. */ - tracker_issue_url?: string | null; - /** How that issue reads in its provider, for example '#12' or 'ENG-123'. */ - tracker_issue_reference?: string | null; - /** Why the tracker issue could not be opened, for a project that wants one. Null when it exists. */ - tracker_issue_error?: string | null; - /** Charts the report shows, placed by `[label](chart:)` links in the summary. */ - charts?: SignalReportChart[]; - /** The report's PR refund, when one exists (one refund per report, ever). */ - refund?: SignalReportRefund | null; - /** Marks reports that were never billable ("Free"), so there is nothing to refund. */ - billing_exempt_reason?: string | null; - /** Backend-owned refund eligibility: why a refund would be rejected right now, null when it would be accepted. */ - refund_ineligibility_reason?: string | null; - /** The space (task channel) this report is assigned to, or null when unassigned. The general view lists every report regardless of this value. */ - channel_id?: string | null; -} - -export type SignalReportRefundReason = - | "pr_incorrect" - | "pr_not_useful" - | "duplicate" - | "other"; - -export interface SignalReportRefund { - id: string; - reason: SignalReportRefundReason; - note?: string | null; - created_at?: string; -} - -export interface SignalReportArtefactContent { - session_id: string; - start_time: string; - end_time: string; - distinct_id: string; - content: string; - distance_to_centroid: number | null; -} - -/** - * Fields shared by every artefact row. `created_by` / `task_id` carry attribution: - * at most one is set — `created_by` for user writes, `task_id` for agent writes, - * neither for system (pipeline) writes. - */ -interface SignalReportArtefactBase { - id: string; - created_at: string; - updated_at?: string | null; - /** User the artefact is attributed to, when a user produced it. */ - created_by?: UserBasic | null; - /** Task the artefact is attributed to, when an agent produced it. */ - task_id?: string | null; - /** - * True when the row's content did not match its type's expected shape and was - * normalized to a plain text preview instead — the entry still renders rather - * than silently vanishing from the activity log. - */ - degraded?: boolean; -} - -export interface SignalReportArtefact extends SignalReportArtefactBase { - type: string; - content: SignalReportArtefactContent; -} - -/** Artefact with `type: "priority_judgment"` — priority assessment from the agentic report. */ -export interface PriorityJudgmentArtefact extends SignalReportArtefactBase { - type: "priority_judgment"; - content: PriorityJudgmentContent; -} - -export interface PriorityJudgmentContent { - explanation: string; - priority: SignalReportPriority; -} - -/** Artefact with `type: "actionability_judgment"` — actionability assessment from the agentic report. */ -export interface ActionabilityJudgmentArtefact - extends SignalReportArtefactBase { - type: "actionability_judgment"; - content: ActionabilityJudgmentContent; -} - -export interface ActionabilityJudgmentContent { - explanation: string; - actionability: SignalReportActionability; - already_addressed: boolean; -} - -/** Artefact with `type: "safety_judgment"` — the prompt-injection safety verdict for the report. */ -export interface SafetyJudgmentArtefact extends SignalReportArtefactBase { - type: "safety_judgment"; - content: SafetyJudgmentContent; -} - -export interface SafetyJudgmentContent { - /** True when the report's signals are judged safe to act on. */ - choice: boolean; - /** Why the report was judged unsafe; null when safe. */ - explanation: string | null; -} - -/** Artefact with `type: "signal_finding"` — per-signal research finding from the agentic report. */ -export interface SignalFindingArtefact extends SignalReportArtefactBase { - type: "signal_finding"; - content: SignalFindingContent; -} - -export interface SignalFindingContent { - signal_id: string; - relevant_code_paths: string[]; - relevant_commit_hashes: Record; - data_queried: string; - verified: boolean; -} - -/** Artefact with `type: "repo_selection"` - selected repository for the report run. */ -export interface RepoSelectionArtefact extends SignalReportArtefactBase { - type: "repo_selection"; - content: RepoSelectionContent; -} - -export interface RepoSelectionContent { - repository: string | null; - reason: string; -} - -/** Artefact with `type: "suggested_reviewers"` — content is an enriched reviewer list. */ -export interface SuggestedReviewersArtefact extends SignalReportArtefactBase { - type: "suggested_reviewers"; - content: SuggestedReviewer[]; -} - -/** Artefact with `type: "dismissal"` — captures the user's rationale when suppressing or resolving a report. */ -export interface DismissalArtefact extends SignalReportArtefactBase { - type: "dismissal"; - content: DismissalContent; -} - -export interface DismissalContent { - reason: ReportStateReason; - /** Optional free-form detail provided alongside the reason. */ - note: string; - /** PostHog numeric user id of the dismisser, when available. */ - user_id: number | null; - /** PostHog UUID of the dismisser, when available. */ - user_uuid: string | null; -} - -// ── Log artefacts ──────────────────────────────────────────────────────────── -// Append-but-deletable "work log" entries that accumulate on a report. Distinct -// from the status artefacts above (judgments, reviewers) which are latest-wins. -// Content shapes mirror products/signals/backend/artefact_schemas.py. - -/** Artefact with `type: "code_reference"` — a contiguous span of source lines. */ -export interface CodeReferenceArtefact extends SignalReportArtefactBase { - type: "code_reference"; - content: CodeReferenceContent; -} - -export interface CodeReferenceContent { - file_path: string; - start_line: number; - end_line: number; - contents: string; - relevance_note: string; -} - -/** Artefact with `type: "line_reference"` — a single source line callout (a point). */ -export interface LineReferenceArtefact extends SignalReportArtefactBase { - type: "line_reference"; - content: LineReferenceContent; -} - -export interface LineReferenceContent { - file_path: string; - line: number; - note: string; - /** The exact source text of the referenced line, if available. */ - contents?: string | null; -} - -/** Artefact with `type: "commit"` — one commit pushed in relation to the report. */ -export interface CommitArtefact extends SignalReportArtefactBase { - type: "commit"; - content: CommitContent; -} - -export interface CommitContent { - repository: string; - branch: string; - commit_sha: string; - message: string; - note?: string | null; -} - -/** Artefact with `type: "task_run"` — a reference to a `tasks.Task` run for the report. */ -export interface TaskRunArtefact extends SignalReportArtefactBase { - type: "task_run"; - content: TaskRunArtefactContent; -} - -export interface TaskRunArtefactContent { - task_id: string; - run_id?: string | null; - /** - * Product that ran the task — `signals` for the built-in pipeline, or a custom agent's - * product identifier (mirrors backend TaskRunArtefact). - */ - product: string; - /** - * Task type within the product — e.g. `research` / `implementation` / `repo_selection` for the - * signals pipeline, or a custom agent's type identifier. - */ - type: string; -} - -/** Artefact with `type: "note"` — a free-form note authored by an agent or by code. */ -export interface NoteArtefact extends SignalReportArtefactBase { - type: "note"; - content: NoteContent; -} - -export interface NoteContent { - note: string; - author?: string | null; -} - -/** Response from the `commit` artefact diff endpoint — the commit rendered against its parent. */ -export interface CommitDiffResponse { - /** Unified diff (patch) text introduced by the commit. */ - diff: string; - /** True when the diff was too large to return in full and has been truncated. */ - truncated: boolean; -} - -export interface SuggestedReviewerCommit { - sha: string; - url: string; - reason: string; -} - -export interface SuggestedReviewerUser { - id: number; - uuid: string; - email: string; - first_name: string; - last_name: string; -} - -import type { AvailableSuggestedReviewer } from "./inbox-types"; -export type { AvailableSuggestedReviewer }; - -export interface SuggestedReviewer { - /** Null for a reviewer with no linked GitHub account — `user` identifies them instead. */ - github_login: string | null; - /** Null on entries written before reviewers carried one; `user` still resolves from the login. */ - user_uuid?: string | null; - github_name: string | null; - relevant_commits: SuggestedReviewerCommit[]; - user: SuggestedReviewerUser | null; - reason?: string | null; - source_skill?: string | null; - source_label?: string; - explanation?: string | null; -} - -export interface SuggestedReviewerWriteEntry { - github_login?: string; - user_uuid?: string; - github_name?: string; -} - -interface MatchedSignalMetadata { - parent_signal_id: string; - match_query: string; - reason: string; -} - -interface NoMatchSignalMetadata { - reason: string; - rejected_signal_ids: string[]; -} - -export type SignalMatchMetadata = MatchedSignalMetadata | NoMatchSignalMetadata; - -export interface Signal { - signal_id: string; - content: string; - source_product: string; - source_type: string; - source_id: string; - weight: number; - timestamp: string; - extra: Record; - match_metadata?: SignalMatchMetadata | null; -} - -export interface SignalReportsResponse { - results: SignalReport[]; - count: number; -} - -export interface SignalProcessingStateResponse { - paused_until: string | null; -} - -export interface AvailableSuggestedReviewersResponse { - results: AvailableSuggestedReviewer[]; - count: number; -} - -export interface SignalReportSignalsResponse { - report: SignalReport | null; - signals: Signal[]; -} - -/** Any artefact returned by the report `artefacts/` endpoint, discriminated on `type`. */ -export type AnySignalReportArtefact = - | SignalReportArtefact - | PriorityJudgmentArtefact - | ActionabilityJudgmentArtefact - | SafetyJudgmentArtefact - | SignalFindingArtefact - | RepoSelectionArtefact - | SuggestedReviewersArtefact - | DismissalArtefact - | CodeReferenceArtefact - | LineReferenceArtefact - | CommitArtefact - | TaskRunArtefact - | NoteArtefact; - -export interface SignalReportArtefactsResponse { - results: AnySignalReportArtefact[]; - count: number; - unavailableReason?: - | "forbidden" - | "not_found" - | "invalid_payload" - | "request_failed"; -} - -import type { SignalReportOrderingField } from "./signal-types"; -export type { SignalReportOrderingField }; - -export interface SignalReportsQueryParams { - unread?: boolean; - search?: string; - limit?: number; - offset?: number; - status?: CommaSeparatedSignalReportStatuses | string; - /** - * Comma-separated sort keys (prefix `-` for descending). `status` is semantic stage - * rank (not lexicographic `status` column order). Also: `signal_count`, `total_weight`, - * `created_at`, `updated_at`, `id`. Example: `status,-total_weight`. - */ - ordering?: string; - /** Comma-separated source products — only returns reports with signals from these sources. */ - source_product?: string; - /** Comma-separated PostHog user UUIDs — only returns reports with these suggested reviewers. */ - suggested_reviewers?: string; - /** Comma-separated `P0`–`P4` priorities — only returns reports with one of these priorities. */ - priority?: string; - /** Comma-separated actionability choices. Only returns reports with one of these latest judgments. */ - actionability?: string; - /** Return the filtered total without fetching or enriching report rows. */ - count_only?: boolean; - /** - * Filter by whether a shipped implementation pull request exists. `true` keeps only PR - * reports, `false` only non-PR reports. - */ - has_implementation_pr?: boolean; - /** A space (task channel) UUID — only returns reports assigned to that space. Omit for the general view, which returns every report. */ - channel_id?: string; -} - -export interface SignalTeamConfig { - id: string; - default_autostart_priority: SignalReportPriority; - /** Team-wide default `channel_id|#channel-name` target for inbox notifications. `null` = no team default. */ - default_slack_notification_channel?: string | null; - autostart_base_branches?: Record | null; - /** Daily cap on new reports reaching the inbox, counted per project-timezone day. `null` = unlimited. */ - max_reports_per_day?: number | null; - /** Reports that first became visible today. `0` when there is no cap. Read-only. */ - reports_generated_today?: number; - /** Whether the cap is reached, pausing new reports until local midnight. `false` when there is no cap. Read-only. */ - daily_report_limit_reached?: boolean; - created_at: string; - updated_at: string; -} - -export interface SignalUserAutonomyConfig { - id?: string; - autostart_priority: SignalReportPriority | null; - /** ID of the team-scoped Slack `Integration` row used to deliver inbox-item notifications. */ - slack_notification_integration_id?: number | null; - /** `channel_id|#channel-name` target — same convention used by Insight Alerts. */ - slack_notification_channel?: string | null; - /** Minimum priority that triggers a notification (P0 highest). `null` = every priority. */ - slack_notification_min_priority?: SignalReportPriority | null; - created_at?: string; - updated_at?: string; -} - -export interface SlackChannelOption { - id: string; - name: string; - is_private: boolean; - is_member: boolean; - is_ext_shared: boolean; - is_private_without_access: boolean; -} - -export interface SlackChannelsResponse { - channels: SlackChannelOption[]; - lastRefreshedAt?: string; - has_more?: boolean; -} - -export interface SlackChannelsQueryParams { - search?: string; - limit?: number; - offset?: number; - channelId?: string; -} +export * from "@posthog/agent-contracts/domain-types"; diff --git a/products/desktop/packages/shared/src/flags.test.ts b/products/desktop/packages/shared/src/flags.test.ts index 58c9f618b7a9..cb7152bb6027 100644 --- a/products/desktop/packages/shared/src/flags.test.ts +++ b/products/desktop/packages/shared/src/flags.test.ts @@ -1,5 +1,5 @@ +import { BEDROCK_LLM_GATEWAY_FLAG } from "@posthog/agent-contracts/bedrock-gateway"; import { describe, expect, it } from "vitest"; -import { BEDROCK_LLM_GATEWAY_FLAG } from "./bedrock-gateway"; import featureFlagKeys from "./feature-flag-keys.json" with { type: "json" }; describe("flags", () => { diff --git a/products/desktop/packages/shared/src/flags.ts b/products/desktop/packages/shared/src/flags.ts index 6df215a219ff..8dae8249ef09 100644 --- a/products/desktop/packages/shared/src/flags.ts +++ b/products/desktop/packages/shared/src/flags.ts @@ -83,7 +83,7 @@ export { BEDROCK_GATEWAY_VARIANTS, BEDROCK_LLM_GATEWAY_FLAG, type BedrockGatewayVariant, -} from "./bedrock-gateway"; +} from "@posthog/agent-contracts/bedrock-gateway"; /** Gates the organization context wiki: the Context explorer in the nav rails. */ export const CONTEXT_LAYER_FLAG = featureFlagKeys.CONTEXT_LAYER_FLAG; /** Gates the "set up this space for" step in the create-space flow. */ diff --git a/products/desktop/packages/shared/src/index.ts b/products/desktop/packages/shared/src/index.ts index 3c4bc617ea5b..c2f65035d9e5 100644 --- a/products/desktop/packages/shared/src/index.ts +++ b/products/desktop/packages/shared/src/index.ts @@ -1,40 +1,7 @@ -export * from "./adapter"; -export { - buildActionUrl, - openAgentActionInput, - type ShowActionButton, - showActionSchema, - splitShowAction, -} from "./agent-actions"; -export type { - AgentContent, - AgentConversationEvent, - AgentToolCall, - AgentToolCallContent, - AgentToolCallContentBlock, - AgentToolCallLocation, - AgentToolCallStatus, - AgentToolKind, - AgentTurnUsage, -} from "./agent-conversation"; -export * from "./agent-runtime"; +export * from "@posthog/agent-contracts"; export * from "./analytics-events"; -export type { TaskRunUpdate } from "./api-http-client"; -export { - API_DOWNLOAD_TIMEOUT_MS, - API_TRANSFER_TIMEOUT_MS, - PostHogHttpClient, - PostHogHttpError, - transferTimeoutMs, -} from "./api-http-client"; export type { ArchivedTask } from "./archive-domain"; export { withTimeout } from "./async"; -export { - type BackoffOptions, - getBackoffDelay, - sleepWithBackoff, -} from "./backoff"; -export { isBinaryFile } from "./binary"; export { closeTab, closeTabs, @@ -60,55 +27,9 @@ export { tabsSnapshotSchema, tabViewStateSchema, } from "./browser-tabs-schemas"; -export { - boundPersistedMcpResult, - MAX_PERSISTED_MCP_RESULT_BYTES, - omitNullCallToolResultFields, -} from "./call-tool-result"; export * from "./canvas-contracts"; export * from "./canvas-platform"; export type { CloudRunSource, PrAuthorshipMode } from "./cloud"; -export { - deserializeCloudPrompt, - promptBlocksToText, - serializeCloudPrompt, -} from "./cloud-prompt"; -export { - adapterForModelId, - buildCloudTaskConfigOptions, - buildProviderModelGroups, - type CloudTaskConfigOption, - type CloudTaskConfigSelectGroup, - type CloudTaskConfigSelectOption, - compareModelsForPicker, - DEFAULT_CODEX_MODEL, - DEFAULT_GATEWAY_MODEL, - formatGatewayModelName, - formatModelId, - type GatewayModel, - getClaudeModelRecency, - getCloudTaskGatewayUrl, - getProviderName, - isAnthropicModel, - isAnthropicModelId, - isBasetenModel, - isCloudflareModel, - isCloudflareModelId, - isDeepseekModelId, - isModalModel, - isModalModelId, - isOpenAIModel, - normalizeGatewayModelsResponse, - pickAllowedModel, -} from "./cloud-task-models"; -export { - type CustomCloud, - configureCustomCloud, - customCloudSchema, - getCustomCloud, - isCustomCloudHost, - normalizeCustomCloud, -} from "./custom-cloud"; export { buildLoopDeeplink, buildScoutDeeplink, @@ -119,156 +40,20 @@ export { type NewTaskSharedParams, parseGitHubIssueUrl, } from "./deep-links"; -export { - DISMISSAL_REASON_OPTIONS, - type DismissalReasonOptionValue, - dismissalReasonLabel, - isDismissalReasonSnooze, - RESOLVE_REASON_OPTIONS, - type ReportStateReason, - type ResolveReasonOptionValue, -} from "./dismissal-reasons"; -export { - type ArtifactSource, - type ArtifactType, - type CloudPermissionOption, - type CloudTaskPermissionRequestUpdate, - type CloudTaskUpdatePayload, - isSkillBundleArtifactMetadata, - isTerminalStatus, - type PendingFollowupMessage, - type StoreSkillStub, - type Task, - type TaskRun, - type TaskRunArtifact, - type TaskRunArtifactMetadata, - type TaskRunEnvironment, - type TaskRunState, - type TaskRunStateField, - type TaskRunStatus, - TERMINAL_STATUSES, - taskRunStateSchema, -} from "./domain-types"; -export * from "./enrichment"; -export { - classifyGatewayLimitError, - classifyPromptFailure, - type GatewayLimitCause, - getErrorMessage, - isAuthError, - isFatalSessionError, - isNotAuthenticatedError, - isRateLimitError, - isTransientUpstreamError, - isTurnEndedWithoutResponseError, - NotAuthenticatedError, - type PromptFailure, - type PromptFailureKind, - serializeError, -} from "./errors"; -export type { ExecutionMode } from "./exec-types"; -export { - CODEX_MODE_PRESETS, - type CodexModePreset, - resolveCloudInitialPermissionMode, -} from "./execution-modes"; export * from "./flags"; export * from "./git-domain"; export * from "./git-naming"; -export { - ALLOWED_IMAGE_MIME_TYPES, - buildImageDataUrl, - estimateBase64Bytes, - getImageMimeType, - IMAGE_MIME_TYPES, - isAllowedImageMimeType, - isClaudeImageFile, - isClaudeImageMimeType, - isGifFile, - isImageFile, - isRasterImageFile, - MAX_CLAUDE_IMAGE_BYTES, - MAX_IMAGE_BASE64_LENGTH, - parseImageDataUrl, -} from "./image"; export { buildDiscussReportPrompt, buildLocalCodeSnapshotPrompt, CODE_CONTEXT_DISCLOSURE, NO_CHECKOUT_DISCLOSURE, } from "./inbox-prompts"; -export type { - AvailableSuggestedReviewer, - SignalRecordKind, - SourceProduct, - SourceType, - ToggleableSourceProduct, -} from "./inbox-types"; -export { - EXTERNAL_INBOX_SOURCE_BY_PRODUCT, - EXTERNAL_INBOX_SOURCES, - filterInboxSourceOptions, - sourceNeedsFullRefresh, -} from "./inbox-types"; export { EXTERNAL_LINKS } from "./links"; -export type { - AcpMcpServer, - CloudMcpServerRelayDesignation, - LocalMcpServerDescriptor, - LocalMcpServerScope, - LocalMcpTransport, - McpServerConnection, -} from "./local-mcp-domain"; -export { toAcpMcpServers } from "./local-mcp-domain"; -export { - MCP_TOOL_PERMISSION_OPTIONS, - type McpToolApprovalState, - type McpToolPermissionDecision, - type McpToolPermissionRequest, - type McpToolPolicy, -} from "./mcp-tool-policy-domain"; export { formatMention, splitMentionSegments, } from "./mentions"; -export { - isOfferedModel, - isRetiredModel, - supports1MContext, - supportsFastMode, -} from "./model-catalog"; -export { - customModelMeta, - DEFAULT_OPTION_META_KEY, - defaultEligibleModel, - isCustomModelOption, - isDefaultSelectOption, - isRestrictedModelOption, - modelHarnessMeta, - OPTION_DOCS_URL_META_KEY, - restrictedModelMeta, - selectOptionDocsUrl, - selectOptionHarness, -} from "./models"; -export { - DEV_CALLBACK_PORT, - DEV_REDIRECT_URI, - getOauthClientIdFromRegion, - OAUTH_SCOPE_VERSION, - OAUTH_SCOPES, -} from "./oauth"; -export { - type AgentRunState, - agentRunStateSchema, - type PiSubagentToolCall, - type PiSubagentToolDetails, - type PiWorkflowToolDetails, - piSubagentToolCallSchema, - piSubagentToolDetailsSchema, - piWorkflowToolDetailsSchema, - type WorkflowAgentState, - workflowAgentStateSchema, -} from "./orchestration"; export { CLIPBOARD_ATTACHMENT_DIR_NAME, CLIPBOARD_ATTACHMENT_PREFIX, @@ -282,62 +67,10 @@ export { toRelativePath, } from "./path"; export type { PiMessagingMode, PiRuntimeHealth } from "./pi-session"; -export { - createPiToolCallRecord, - formatMcpToolLabel, - formatPiMcpToolName, - isPiToolName, - PI_TOOL_KIND_BY_NAME, - type PiMcpCallDetails, - type PiToolCallInput, - type PiToolCallRecord, - type PiToolName, - parsePiMcpCallDetails, - readPiMcpCallDetails, -} from "./pi-tool-call"; -export { POSTHOG_PRODUCTS, type PostHogProductId } from "./posthog-products"; -export { - buildPrOutput, - mergePrUrls, - promotePrUrl, - readPrSummaries, - readPrUrls, -} from "./pr-urls"; -export { isPrivateIpv4Octets, isPrivateIpv6Literal } from "./private-network"; -export { - type CapabilityNotch, - DEFAULT_REASONING_EFFORT, - getCapabilityLadder, - getReasoningEffortOptions, - isSupportedReasoningEffort, - type SupportedReasoningEffort, -} from "./reasoning-effort"; export { REFUND_REASON_OPTIONS } from "./refund-reasons"; -export { - CLOUD_REGIONS, - type CloudRegion, - describeRegion, - REGION_LABELS, - type RegionLabel, -} from "./regions"; export { normalizeRepoKey } from "./repo"; export { getTaskRepository, parseRepository } from "./repository"; export { rewriteSavedLocation } from "./route-migrations"; -export { Saga, type SagaLogger, type SagaResult } from "./saga"; -export { - type AcpMessage, - IDLE_RESUME_STOP_REASON, - IMPORTED_USER_PROMPT_META_KEY, - isIdleResumeTurnComplete, - isJsonRpcNotification, - isJsonRpcRequest, - isJsonRpcResponse, - type JsonRpcMessage, - type JsonRpcRequest, - type StoredLogEntry, - type UserShellExecuteParams, - type UserShellExecuteResult, -} from "./session-events"; export { type AgentSession, cycleModeOption, @@ -357,23 +90,6 @@ export { sessionSupportsSideQuestion, TRANSCRIPT_TAIL_WINDOW, } from "./sessions"; -export type { - ExportedSkill, - SkillFileEntry, - SkillInfo, - SkillSource, - UploadableSkillSource, -} from "./skills"; -export { - DISABLE_MODEL_INVOCATION_METADATA_KEY, - isIgnoredSkillEntry, - isIgnoredSkillPath, - SKILL_EXISTS_MARKER, - serializeSkillMarkdown, - stripFrontmatter, -} from "./skills"; -export { leadingSlashCommand } from "./slash-commands"; -export type { PostHogAPIConfig } from "./task"; export type { TaskCreationInput, TaskCreationOutput, @@ -390,25 +106,12 @@ export { getRelativeDateGroup, } from "./time"; export { singleLineTitle } from "./title-text"; -export { - mcpToolKey, - parseMcpToolName, - posthogToolMeta, - readAgentToolName, - readMcpInstallationId, - readMcpProxyCallDetails, - readMcpToolDescriptor, - readMcpToolName, - readParentToolCallId, -} from "./tool-meta"; -export { TranscriptBoundaries } from "./transcript-neutral-messages"; export { TypedEventEmitter } from "./typed-event-emitter"; export { isSafeExternalUrl, isSafeGitHubPullRequestUrl, isSafePostHogUrl, } from "./url"; -export { getCloudUrlFromRegion } from "./urls"; export { buildVideoDataUrl, getVideoMimeType, diff --git a/products/desktop/packages/shared/src/model-catalog.ts b/products/desktop/packages/shared/src/model-catalog.ts index eb9c42e653fc..7a0cba38157d 100644 --- a/products/desktop/packages/shared/src/model-catalog.ts +++ b/products/desktop/packages/shared/src/model-catalog.ts @@ -1,146 +1 @@ -import type { Adapter } from "./adapter"; -import { - type CatalogModel, - FALLBACK_REASONING_EFFORTS, - FAMILY_REASONING_EFFORTS, - MODELS, - PROVIDER_BY_RUNTIME_ADAPTER, - type ReasoningEffort, -} from "./model-catalog.generated"; - -export { - type CatalogModel, - COST_BASELINE_LABEL, - COST_BASELINE_MODEL, - DEFAULT_MODEL_BY_RUNTIME_ADAPTER, - MODELS, - type ModelCost, - PROVIDER_BY_RUNTIME_ADAPTER, - REASONING_EFFORTS, - type ReasoningEffort, - RUNTIME_ADAPTERS, - RUNTIME_OPTIONS, - type Runtime, - type RuntimeAdapter, - type RuntimeOption, -} from "./model-catalog.generated"; - -/** - * The form a model id is looked up under. The gateway serves some models both bare and - * provider-qualified (`openai/gpt-5.6-sol`) and a picker may hand back either, so folding - * the two together is what stops one model from having two answers. Only the provider - * prefixes the catalog knows are stripped, leaving ids that carry a slash of their own - * (`@cf/zai-org/glm-5.2`) intact. - */ -export function normalizeModelId(modelId: string): string { - const normalized = modelId.trim().toLowerCase(); - for (const provider of Object.values(PROVIDER_BY_RUNTIME_ADAPTER)) { - if (normalized.startsWith(`${provider}/`)) { - return normalized.slice(provider.length + 1); - } - } - return normalized; -} - -/** - * The efforts this model may run at, empty when it takes no effort at all. - * - * Resolved in three steps: the exact id, then the family it belongs to, then what the - * adapter accepts generally. Codex passes any `gpt-*` identifier through, so a newly - * served id still runs, while Claude has no fallback and yields nothing — which is what - * makes the backend reject it. Mirrors `reasoning_efforts_for` in - * products/tasks/backend/model_catalog.py, so a selection this offers is one a run can - * actually use. - */ -export function reasoningEffortsForModel( - adapter: Adapter, - modelId: string, -): readonly ReasoningEffort[] { - const normalized = normalizeModelId(modelId); - const model = MODELS.find( - (candidate) => - candidate.runtimeAdapter === adapter && candidate.id === normalized, - ); - if (model) return model.reasoningEfforts; - // Longest matching prefix wins, so the table's declaration order is free. - const family = FAMILY_REASONING_EFFORTS.filter( - (candidate) => - candidate.runtimeAdapter === adapter && - normalized.startsWith(candidate.prefix), - ).sort((a, b) => b.prefix.length - a.prefix.length)[0]; - return family?.reasoningEfforts ?? FALLBACK_REASONING_EFFORTS[adapter] ?? []; -} - -/** - * The catalog's entry for a model id in any form a picker hands back. The gateway serves - * ids the catalog omits, so a miss is the ordinary case and never an error. - */ -export function catalogModelFor(modelId: string): CatalogModel | undefined { - const normalized = normalizeModelId(modelId); - return MODELS.find((candidate) => candidate.id === normalized); -} - -/** - * Every access flag the catalog gates a model behind, without duplicates. - * - * A picker resolves these and hands the answers back to the filter, so gating a model is - * done by giving it an `access_flag` in products/tasks/backend/model_catalog.py and no - * picker has to learn the flag's name. Empty while no model is behind a rollout. - */ -export const MODEL_ACCESS_FLAGS: readonly string[] = [ - ...new Set( - MODELS.map((model) => model.accessFlag).filter( - (flag): flag is string => flag !== undefined, - ), - ), -]; - -/** - * The feature flag a person needs before a picker offers this model, or `undefined` when - * it is generally available. - * - * Governs display only. The server decides whether a run may use the model, so a stale - * projection can hide a model a person may use, never the other way round. - */ -export function accessFlagForModel(modelId: string): string | undefined { - return catalogModelFor(modelId)?.accessFlag; -} - -/** - * The name the catalog pins for a model, or `undefined` to let the caller format the id. - * - * Only the ids whose derived name reads wrong carry one, so a caller keeps its formatter - * for everything else. Mirrors `label_for_model` in - * products/tasks/backend/model_catalog.py, so both surfaces name a model identically. - */ -export function labelForModel(modelId: string): string | undefined { - return catalogModelFor(modelId)?.label; -} - -/** - * Whether a picker may offer this model. A model the catalog omits is not offered however - * the gateway answers, and a retired one stays listed only so a pinned session keeps - * running under a name and a price. - */ -export function isOfferedModel(modelId: string): boolean { - const model = catalogModelFor(modelId); - return model !== undefined && !model.retired; -} - -/** - * Whether the catalog has retired this model. Narrower than the inverse of `isOfferedModel`: - * an id the catalog never lists is not retired, because a runtime can still drive one — a - * person's own OpenAI subscription runs vendor variants such as `gpt-5.5-codex` that the - * gateway never serves. Ask this about the model a run uses, `isOfferedModel` about a picker. - */ -export function isRetiredModel(modelId: string): boolean { - return catalogModelFor(modelId)?.retired === true; -} - -export function supports1MContext(modelId: string): boolean { - return catalogModelFor(modelId)?.supports1MContext ?? false; -} - -export function supportsFastMode(modelId: string): boolean { - return catalogModelFor(modelId)?.supportsFastMode ?? false; -} +export * from "@posthog/agent-contracts/model-catalog"; diff --git a/products/desktop/packages/shared/src/posthog-property-headers.ts b/products/desktop/packages/shared/src/posthog-property-headers.ts index 7bb92ae24dca..e41649cfae3a 100644 --- a/products/desktop/packages/shared/src/posthog-property-headers.ts +++ b/products/desktop/packages/shared/src/posthog-property-headers.ts @@ -1,223 +1 @@ -export type PosthogPropertyValue = string | number | boolean | null | undefined; - -export type PosthogProperties = Record; - -export const POSTHOG_PROJECT_ID_HEADER = "X-PostHog-Project-Id"; -export const POSTHOG_TASK_RUN_ID_HEADER = "X-PostHog-Task-Run-Id"; - -/** - * Make a value safe to embed in an HTTP header value. Only printable ASCII - * survives: latin1 is valid per RFC 9110 and undici accepts it, but Bun's - * fetch — which the Claude Code CLI uses for `ANTHROPIC_CUSTOM_HEADERS` — - * rejects any non-ASCII header value. NFKD plus the final strip is what - * transliterates accented letters to their ASCII base (`più` → `piu`); the - * combining-mark pass just keeps a stray mark from splitting a newline run - * before it is collapsed to a single space. - */ -function sanitizeHeaderValue(value: string): string { - return value - .normalize("NFKD") - .replace(/[\u0300-\u036f]/g, "") - .replace(/[\r\n]+/g, " ") - .replace(/[^\x20-\x7e]/g, ""); -} - -function buildEntries(properties: PosthogProperties): Array<[string, string]> { - const entries: Array<[string, string]> = []; - for (const [key, value] of Object.entries(properties)) { - if (value === null || value === undefined) continue; - entries.push([ - `x-posthog-property-${key}`, - sanitizeHeaderValue(String(value)), - ]); - } - return entries; -} - -function taskRunIdHeader(properties: PosthogProperties): string | null { - const taskRunId = properties.task_run_id; - return typeof taskRunId === "string" ? sanitizeHeaderValue(taskRunId) : null; -} - -/** - * Build a `Record` of `x-posthog-property-` headers - * suitable for `fetch()` init.headers. The LLM gateway lifts each header - * onto the `$ai_generation` event it captures - * (see `services/llm-gateway/src/llm_gateway/request_context.py` in - * posthog/posthog). `null`/`undefined` values are dropped; values are - * sanitized via {@link sanitizeHeaderValue}. - */ -export function buildPosthogPropertyHeaderRecord( - properties: PosthogProperties, -): Record { - const taskRunId = taskRunIdHeader(properties); - return { - ...Object.fromEntries(buildEntries(properties)), - ...(taskRunId ? { [POSTHOG_TASK_RUN_ID_HEADER]: taskRunId } : {}), - }; -} - -/** - * Same property semantics as {@link buildPosthogPropertyHeaderRecord}, but - * returns a newline-joined string of `key: value` lines — the format - * `ANTHROPIC_CUSTOM_HEADERS` expects when wiring headers into the Claude - * Agent SDK. - */ -export function buildPosthogPropertyHeaderLines( - properties: PosthogProperties, -): string { - return Object.entries(buildPosthogPropertyHeaderRecord(properties)) - .map(([key, value]) => `${key}: ${value}`) - .join("\n"); -} - -/** - * Attribution node header for the person a request is spent on behalf of. The - * gateway keys its per-user spend limit on this value, so it must be the same - * node the spend-limit endpoint writes the limit against: the user's distinct - * id, not their uuid (see products/ai_gateway/backend/logic.py, _spend_node). - * - * Trust model: for local sessions this header is asserted by the client, so - * the limit it keys is a self-imposed guardrail, not a security boundary. - * Cloud runs pin the node server-side into the run's scoped token. - */ -const POSTHOG_USER_HEADER = "X-PostHog-User"; - -export function buildPosthogUserHeaderRecord( - userNode: string | null | undefined, -): Record { - return userNode - ? { [POSTHOG_USER_HEADER]: sanitizeHeaderValue(userNode) } - : {}; -} - -export function buildPosthogUserHeaderLines( - userNode: string | null | undefined, -): string { - return userNode - ? `${POSTHOG_USER_HEADER}: ${sanitizeHeaderValue(userNode)}` - : ""; -} - -export function buildPosthogProjectHeaderRecord( - projectId: number | null | undefined, -): Record { - return projectId ? { [POSTHOG_PROJECT_ID_HEADER]: String(projectId) } : {}; -} - -export function buildPosthogProjectHeaderLines( - projectId: number | null | undefined, -): string { - return projectId ? `${POSTHOG_PROJECT_ID_HEADER}: ${projectId}` : ""; -} - -export function buildPosthogScopedPropertyHeaderRecord( - properties: PosthogProperties, - projectId: number | null | undefined, -): Record { - return { - ...buildPosthogPropertyHeaderRecord(properties), - ...buildPosthogProjectHeaderRecord(projectId), - }; -} - -export function buildPosthogScopedPropertyHeaderLines( - properties: PosthogProperties, - projectId: number | null | undefined, -): string { - return [ - buildPosthogPropertyHeaderLines(properties), - buildPosthogProjectHeaderLines(projectId), - ] - .filter(Boolean) - .join("\n"); -} - -/** Header carrying the whole property set as one JSON object. */ -const POSTHOG_PROPERTIES_HEADER = "X-PostHog-Properties"; - -/** - * Byte cap the Go gateway enforces on {@link POSTHOG_PROPERTIES_HEADER}; a - * larger blob is rejected outright, losing every property including - * `ai_product`. Keep in sync with `maxPropertiesLen` in the gateway's - * `internal/httpapi/dispatch.go`. - */ -const MAX_PROPERTIES_BYTES = 8192; - -const encoder = new TextEncoder(); - -function byteLength(value: string): number { - return encoder.encode(value).length; -} - -/** - * Serialize properties for {@link POSTHOG_PROPERTIES_HEADER}, or `""` when - * there is nothing to send. - * - * Keys beginning with `$` are dropped: the gateway strips reserved `$ai_*` - * keys at the header boundary, so sending them silently loses them. Values - * are sanitized like the per-property headers so a stray control character - * cannot break the header block. - * - * Callers supply free-text values (task titles), so the blob can exceed - * {@link MAX_PROPERTIES_BYTES}. Rather than let the gateway reject the whole - * header, the longest string values are dropped one at a time until it fits, - * preferring to lose descriptive text over attribution keys, which are short. - */ -export function buildPosthogPropertiesBlob( - properties: PosthogProperties, -): string { - const clean: PosthogProperties = {}; - for (const [key, value] of Object.entries(properties)) { - if (value === null || value === undefined) continue; - if (key.startsWith("$")) continue; - // Keys are sanitized too: unlike the per-property headers, where the key - // becomes the header name, here it is serialized into the header value, - // so a non-ASCII key would make the whole request unsendable. - const safeKey = sanitizeHeaderValue(key); - if (!safeKey) continue; - clean[safeKey] = - typeof value === "string" ? sanitizeHeaderValue(value) : value; - } - if (Object.keys(clean).length === 0) return ""; - - let blob = JSON.stringify(clean); - while (byteLength(blob) > MAX_PROPERTIES_BYTES) { - const longest = Object.entries(clean) - .filter(([, value]) => typeof value === "string") - .sort((a, b) => (b[1] as string).length - (a[1] as string).length)[0]; - // Only non-string values remain and they still overflow: send nothing - // rather than a blob the gateway will reject. - if (!longest) return ""; - delete clean[longest[0]]; - blob = JSON.stringify(clean); - } - return blob; -} - -/** - * {@link buildPosthogPropertiesBlob} as a `fetch()`-ready header record, empty - * when there is nothing to send. - */ -export function buildPosthogPropertiesHeaderRecord( - properties: PosthogProperties, -): Record { - const blob = buildPosthogPropertiesBlob(properties); - const taskRunId = taskRunIdHeader(properties); - return { - ...(blob ? { [POSTHOG_PROPERTIES_HEADER]: blob } : {}), - ...(taskRunId ? { [POSTHOG_TASK_RUN_ID_HEADER]: taskRunId } : {}), - }; -} - -/** - * {@link buildPosthogPropertiesHeaderRecord} as `key: value` lines for - * `ANTHROPIC_CUSTOM_HEADERS`, empty when there is nothing to send. - */ -export function buildPosthogPropertiesHeaderLines( - properties: PosthogProperties, -): string { - return Object.entries(buildPosthogPropertiesHeaderRecord(properties)) - .map(([key, value]) => `${key}: ${value}`) - .join("\n"); -} +export * from "@posthog/agent-contracts/posthog-property-headers"; diff --git a/products/desktop/packages/shared/src/product-engineer-prompt.ts b/products/desktop/packages/shared/src/product-engineer-prompt.ts index aa2522c302ff..4291086094c5 100644 --- a/products/desktop/packages/shared/src/product-engineer-prompt.ts +++ b/products/desktop/packages/shared/src/product-engineer-prompt.ts @@ -1,47 +1 @@ -export const SIMPLIFIED_TECHNICAL_ENGLISH_INSTRUCTION = - "Talk and write only in Simplified Technical English (ASD-STE100)."; - -export const PRODUCT_ENGINEER_PROMPT = `Operate as an expert product engineer. Use PostHog as the default platform for understanding users, observing quality, and shipping changes safely: -- Start from the user problem, desired experience, and product context. Understand why the work matters before deciding what to build. -- Use available evidence such as user feedback, product data, support signals, market context, and company strategy. Ask for missing context when it would change the decision. -- Exercise product judgment. Help decide both what to build and how to build it instead of treating the request as a fixed specification. -- Choose the smallest valuable solution that creates a fast, useful feedback loop. Prototype, descope, and avoid polishing assumptions that real users have not validated. -- Own the complete experience across implementation, usability, reliability, privacy, security, rollout, documentation, and support. Do not stop when the code compiles or the narrow task is complete. -- Ship safely and learn from reality. Use tests, feature flags, staged rollouts, and experiments when they fit the risk and uncertainty. -- Define what success means and make it observable. Measure adoption, outcomes, and failures. -- Follow through after shipping. Inspect usage and feedback, iterate on what works, and remove complexity that does not earn its place. - -Choose the smallest set of PostHog products that solves the user problem. Know PostHog's product catalog: -- Product Analytics: Analyze event-based user behavior with trends, funnels, retention, paths, and cohorts. -- Web Analytics: Measure website traffic, acquisition, pages, conversions, and performance. -- Session Replay: Record and replay real user sessions to see behavior and friction. -- Feature Flags: Control who gets features and manage progressive rollouts. -- Experiments: Run A/B tests and measure causal impact against product metrics. -- Surveys: Collect in-product user feedback and link responses to behavior. -- Error Tracking: Capture, group, and diagnose exceptions with user and session context. -- Managed warehouse: Use a PostHog-managed analytical Postgres database that combines PostHog and connected source data. -- Data pipelines: Send event and warehouse data to external destinations in real time or batch. -- PostHog AI: Query product data, build insights, write SQL, find replays, and explain findings in plain English. -- AI Observability: Trace LLM calls, prompts, responses, tools, latency, tokens, and cost. -- Logs: Ingest, search, and retain structured application logs. -- Workflows: Trigger automated messages and actions from product behavior. -- Inbox: Review and steer prioritized self-driving reports and pull requests before they ship. -- Replay Vision: Use AI to turn session recordings into structured, queryable data. - -Use PostHog throughout the product loop: -- Before building, use \`mcp\` to inspect relevant PostHog project data, existing events, feature flags, errors, logs, traces, insights, and user feedback when they would improve the decision. Search before calling. Read or list before writing or creating, and reuse existing resources. -- Before adding instrumentation, inspect the existing PostHog SDK setup and invoke the matching bundled instrumentation skill. Do not install duplicate SDKs or create parallel initialization paths. -- Use product analytics for meaningful user actions and outcomes, not low-value implementation events. Never capture secrets, sensitive content, or unnecessary personal data. -- Use PostHog feature flags for uncertain or risky rollouts. Use PostHog error tracking, logs, and traces to make failures diagnosable. Use PostHog AI Observability for AI model calls. -- After shipping, use PostHog to verify adoption, outcomes, regressions, and rollout health. Let observed behavior drive the next iteration. - -Prefer customer impact and product quality over technical novelty. Treat code as one tool for creating useful, measurable outcomes.`; - -export function prependProductEngineerPrompt(prompt: string): string { - if (prompt.includes(PRODUCT_ENGINEER_PROMPT)) { - return prompt; - } - return prompt - ? `${PRODUCT_ENGINEER_PROMPT}\n\n${prompt}` - : PRODUCT_ENGINEER_PROMPT; -} +export * from "@posthog/agent-contracts/product-engineer-prompt"; diff --git a/products/desktop/packages/shared/src/refund-reasons.ts b/products/desktop/packages/shared/src/refund-reasons.ts index 0005f1bbee1f..7503576a81a0 100644 --- a/products/desktop/packages/shared/src/refund-reasons.ts +++ b/products/desktop/packages/shared/src/refund-reasons.ts @@ -1,4 +1,4 @@ -import type { SignalReportRefundReason } from "./domain-types"; +import type { SignalReportRefundReason } from "@posthog/agent-contracts/domain-types"; /** * Canonical refund reasons shown when refunding a report's PR. Values are diff --git a/products/desktop/packages/shared/src/rich-output-prompt.ts b/products/desktop/packages/shared/src/rich-output-prompt.ts index f2a093d7ffb4..e4111e72eaf5 100644 --- a/products/desktop/packages/shared/src/rich-output-prompt.ts +++ b/products/desktop/packages/shared/src/rich-output-prompt.ts @@ -1,38 +1 @@ -import { OBJECT_TAG_PROMPT_KIND_LIST } from "./objectTagKinds.generated"; - -/** - * Prompt block teaching an agent the object-tag vocabulary the desktop - * renders as live references (chips, hover previews, chart cards). Shared by - * every agent runtime so its syntax stays in sync with what `remarkObjectTags` - * parses; the kind list itself is generated from the registry - * (posthog/object_tags/kinds.py) so the prompt cannot drift from what the - * renderers understand. - */ -export const RICH_OUTPUT_TAGS_PROMPT = `Embed the PostHog objects behind your conclusions as XML tags, the same convention as \`\` attachments. Every tag is a live reference the app resolves when shown - never restate the object's data in your text, and never put tags inside code fences. -- Inline reference: \`short human label\` inside a sentence, e.g. \`The checkout funnel dropped after new-checkout-flow rolled out.\` Kinds: ${OBJECT_TAG_PROMPT_KIND_LIST}. Use the object's id (insights: the short id; feature flags: the numeric id, falling back to the key; Inbox reports: the report uuid; persons: the uuid). It renders as a chip with a live hover preview that opens the object in PostHog. -- Inline SQL: \`SELECT count() FROM events WHERE ...\` - the SQL is the tag body, the label is what the sentence shows. Hovering runs the query live; clicking opens the SQL editor. -- Full-size chart, for any numeric or time-series answer (always prefer this over a markdown table): a saved insight \`\` or a query \`SELECT ...\`. The chart executes live on every view. Include the time range in the title, and keep blank lines out of the SQL body. -- Some PostHog MCP query tools render their result as an interactive chart in the conversation, and the tool result says so. When the tool result says the user already sees the result as an interactive view, do not embed the same data again as a \`\` chart; write the conclusion in text and let that view carry the data. When it does not, the tool renders nothing on its own, so follow the full-size chart rule above. -- Recording card: \`\` renders the recording's details with a link into PostHog's player. Use it when a specific session is the evidence.`; - -export function appendRichOutputPrompt( - prompt: string, - interactionOrigin?: string | null, -): string { - if ( - interactionOrigin && - interactionOrigin !== "desktop" && - interactionOrigin !== "signal_report" - ) { - return prompt - .replaceAll( - `\n\n## Rich output in replies\n${RICH_OUTPUT_TAGS_PROMPT}`, - "", - ) - .replaceAll(RICH_OUTPUT_TAGS_PROMPT, ""); - } - if (prompt.includes(RICH_OUTPUT_TAGS_PROMPT)) { - return prompt; - } - return `${prompt}\n\n## Rich output in replies\n${RICH_OUTPUT_TAGS_PROMPT}`; -} +export * from "@posthog/agent-contracts/rich-output-prompt"; diff --git a/products/desktop/packages/shared/src/schemas.ts b/products/desktop/packages/shared/src/schemas.ts index 32b774b82aa7..75d5fc2f48f0 100644 --- a/products/desktop/packages/shared/src/schemas.ts +++ b/products/desktop/packages/shared/src/schemas.ts @@ -1,44 +1 @@ -import { z } from "zod"; - -const spaceGoalPeriodSchema = z.enum(["day", "week", "month"]); -const spaceGoalDirectionSchema = z.enum(["at_least", "at_most"]); - -export const spaceGoalInputSchema = z.object({ - statement: z.string().trim().min(1).max(2000), - period: spaceGoalPeriodSchema, - direction: spaceGoalDirectionSchema, - target: z.string().max(64).nullish(), - deadline: z.iso.date().nullish(), - insight_short_id: z.string().max(64).nullish(), -}); - -export const spaceFeatureInputSchema = z.object({ - name: z.string().trim().min(1).max(200), - description: z.string().max(2000).optional(), - flag_key: z.string().max(400).nullish(), -}); - -const repositorySchema = z.string().max(255).nullish(); - -export const spaceSetupInputSchema = z.discriminatedUnion("kind", [ - z.object({ - kind: z.literal("goal"), - goal: spaceGoalInputSchema, - repository: repositorySchema, - }), - z.object({ - kind: z.literal("feature"), - feature: spaceFeatureInputSchema, - repository: repositorySchema, - }), -]); - -export const spaceSetupStartedSchema = z.object({ task_id: z.uuid() }); - -export type SpaceGoalPeriod = z.infer; -export type SpaceGoalDirection = z.infer; -export type SpaceGoalInput = z.infer; -export type SpaceFeatureInput = z.infer; -export type SpaceSetupInput = z.infer; -export type SpaceSetupKind = SpaceSetupInput["kind"]; -export type SpaceSetupStarted = z.infer; +export * from "@posthog/agent-contracts/schemas"; diff --git a/products/desktop/packages/shared/src/sessions.ts b/products/desktop/packages/shared/src/sessions.ts index 93d15397b26e..090c5c1da2f8 100644 --- a/products/desktop/packages/shared/src/sessions.ts +++ b/products/desktop/packages/shared/src/sessions.ts @@ -6,11 +6,14 @@ import type { SessionConfigSelectOption, SessionConfigSelectOptions, } from "@agentclientprotocol/sdk"; -import type { Adapter, ModelAccess } from "./adapter"; +import type { Adapter, ModelAccess } from "@posthog/agent-contracts/adapter"; +import type { + TaskRunArtifact, + TaskRunStatus, +} from "@posthog/agent-contracts/domain-types"; +import type { ExecutionMode } from "@posthog/agent-contracts/exec-types"; +import type { AcpMessage } from "@posthog/agent-contracts/session-events"; import type { SkillButtonId } from "./analytics-events"; -import type { TaskRunArtifact, TaskRunStatus } from "./domain-types"; -import type { ExecutionMode } from "./exec-types"; -import type { AcpMessage } from "./session-events"; export type { Adapter }; diff --git a/products/desktop/packages/shared/src/task-context.ts b/products/desktop/packages/shared/src/task-context.ts index 59b3fbbed86c..4f84ef459e7f 100644 --- a/products/desktop/packages/shared/src/task-context.ts +++ b/products/desktop/packages/shared/src/task-context.ts @@ -1,14 +1 @@ -export interface TaskContextInput { - taskId: string; - cwd: string; - customInstructions?: string; - additionalDirectories?: string[]; - channelMode?: boolean; -} - -export interface TaskContext extends TaskContextInput { - projectId: number; - apiHost: string; - environment: "local" | "cloud"; - additionalInstructions?: string; -} +export * from "@posthog/agent-contracts/task-context"; diff --git a/products/desktop/packages/shared/src/task-creation-domain.ts b/products/desktop/packages/shared/src/task-creation-domain.ts index 531cca99cadf..01b729638572 100644 --- a/products/desktop/packages/shared/src/task-creation-domain.ts +++ b/products/desktop/packages/shared/src/task-creation-domain.ts @@ -1,12 +1,12 @@ -import type { Adapter, ModelAccess } from "./adapter"; -import type { AgentRuntime } from "./agent-runtime"; -import type { CloudRunSource, PrAuthorshipMode } from "./cloud"; -import type { Task } from "./domain-types"; -import type { ExecutionMode } from "./exec-types"; +import type { Adapter, ModelAccess } from "@posthog/agent-contracts/adapter"; +import type { AgentRuntime } from "@posthog/agent-contracts/agent-runtime"; +import type { Task } from "@posthog/agent-contracts/domain-types"; +import type { ExecutionMode } from "@posthog/agent-contracts/exec-types"; import type { CloudMcpServerRelayDesignation, McpServerConnection, -} from "./local-mcp-domain"; +} from "@posthog/agent-contracts/local-mcp-domain"; +import type { CloudRunSource, PrAuthorshipMode } from "./cloud"; import type { WorkspaceMode } from "./workspace"; import type { Workspace } from "./workspace-domain"; diff --git a/products/desktop/packages/shared/src/types.ts b/products/desktop/packages/shared/src/types.ts index 4635212a7a0e..bc6672e9e398 100644 --- a/products/desktop/packages/shared/src/types.ts +++ b/products/desktop/packages/shared/src/types.ts @@ -1,6 +1,6 @@ -export * from "./domain-types"; -export * from "./inbox-types"; +export * from "@posthog/agent-contracts/domain-types"; +export * from "@posthog/agent-contracts/inbox-types"; export type { SignalReportOrderingField, SignalReportStatus, -} from "./signal-types"; +} from "@posthog/agent-contracts/signal-types"; diff --git a/products/desktop/packages/shared/src/video.test.ts b/products/desktop/packages/shared/src/video.test.ts index cc9e97f4a17f..d79e8908cffa 100644 --- a/products/desktop/packages/shared/src/video.test.ts +++ b/products/desktop/packages/shared/src/video.test.ts @@ -1,5 +1,5 @@ +import { isBinaryFile } from "@posthog/agent-contracts/binary"; import { describe, expect, it } from "vitest"; -import { isBinaryFile } from "./binary"; import { getVideoMimeType, isAllowedVideoMimeType, diff --git a/products/desktop/packages/shared/src/video.ts b/products/desktop/packages/shared/src/video.ts index 2d5b04d85dfd..d961c0e8e55e 100644 --- a/products/desktop/packages/shared/src/video.ts +++ b/products/desktop/packages/shared/src/video.ts @@ -1,4 +1,4 @@ -import { extensionOf } from "./image"; +import { extensionOf } from "@posthog/agent-contracts/image"; // Container formats Chromium (and Electron, which bundles proprietary codecs) // can decode in a