Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion e2e-harness/__tests__/e2e-flow-snapshot.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { WizardStore } from '@ui/tui/store';
import { InkUI } from '@ui/tui/ink-ui';
import { setUI } from '@ui/index';
import { buildSession, RunPhase } from '@lib/wizard-session';
import { HostResolution } from '@lib/host-resolution';
import { Integration } from '@lib/constants';
import { FRAMEWORK_REGISTRY } from '@lib/registry';
import { WizardReadiness } from '@lib/health-checks/readiness';
Expand Down Expand Up @@ -66,7 +67,7 @@ function traceFlow(
store.setCredentials({
accessToken: 'phx_x',
projectApiKey: 'phc_x',
host: 'https://us.posthog.com',
host: HostResolution.fromApiHost('https://us.posthog.com'),
projectId: 1,
});
} else if (screen === ScreenId.Run) {
Expand Down
5 changes: 3 additions & 2 deletions e2e-harness/__tests__/wizard-ci-driver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { WizardStore } from '@ui/tui/store';
import { InkUI } from '@ui/tui/ink-ui';
import { setUI } from '@ui/index';
import { buildSession, RunPhase, McpOutcome } from '@lib/wizard-session';
import { HostResolution } from '@lib/host-resolution';
import { Integration } from '@lib/constants';
import { FRAMEWORK_REGISTRY } from '@lib/registry';
import { WizardReadiness } from '@lib/health-checks/readiness';
Expand Down Expand Up @@ -72,7 +73,7 @@ describe('WizardCiDriver — full integration flow', () => {
store.setCredentials({
accessToken: 'phx_secret_should_not_leak',
projectApiKey: 'phc_public',
host: 'https://us.posthog.com',
host: HostResolution.fromApiHost('https://us.posthog.com'),
projectId: 42,
});

Expand Down Expand Up @@ -112,7 +113,7 @@ describe('WizardCiDriver — full integration flow', () => {
store.setCredentials({
accessToken: 'phx_secret_should_not_leak',
projectApiKey: 'phc_public',
host: 'https://us.posthog.com',
host: HostResolution.fromApiHost('https://us.posthog.com'),
projectId: 7,
});
const state = driver.readState();
Expand Down
6 changes: 5 additions & 1 deletion src/commands/doctor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,11 @@ async function runDoctorCI(options: Record<string, unknown>): Promise<void> {
baseUrl: options.baseUrl as string | undefined,
});

const issues = await fetchHealthIssues(accessToken, host, projectId);
const issues = await fetchHealthIssues(
accessToken,
host.apiHost,
projectId,
);
if (issues.length === 0) {
getUI().log.success('No active issues — your project looks healthy.');
process.exit(0);
Expand Down
3 changes: 2 additions & 1 deletion src/lib/agent/__tests__/agent-prompt-loader.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
type OrchestratorPromptContext,
} from '../agent-prompt-loader';
import { QueueStore } from '@lib/agent/runner/sequence/orchestrator/queue';
import { HostResolution } from '@lib/host-resolution';

function tmpDir(): string {
return fs.mkdtempSync(path.join(os.tmpdir(), 'agent-loader-test-'));
Expand Down Expand Up @@ -280,7 +281,7 @@ describe('assembleTaskPrompt', () => {
const ctx: OrchestratorPromptContext = {
projectId: 1,
projectApiKey: 'phc_x',
host: 'https://us.posthog.com',
host: HostResolution.fromApiHost('https://us.posthog.com'),
};

it('points the agent at its installed task instructions', () => {
Expand Down
3 changes: 2 additions & 1 deletion src/lib/agent/__tests__/agent-prompt.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { assemblePrompt, type PromptContext } from '@lib/agent/agent-prompt';
import type { ProgramRun } from '@lib/agent/agent-runner';
import { HostResolution } from '@lib/host-resolution';

function makeRunDef(overrides: Partial<ProgramRun> = {}): ProgramRun {
return {
Expand All @@ -16,7 +17,7 @@ function makeRunDef(overrides: Partial<ProgramRun> = {}): ProgramRun {
const baseCtx: PromptContext = {
projectId: 42,
projectApiKey: 'phc_test123',
host: 'https://app.posthog.com',
host: HostResolution.fromApiHost('https://app.posthog.com'),
};

describe('assemblePrompt', () => {
Expand Down
10 changes: 3 additions & 7 deletions src/lib/agent/agent-interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
} from '@lib/wizard-session';
import { wizardAbort, WizardError } from '@utils/wizard-abort';
import { createCustomHeaders } from '@utils/custom-headers';
import { HostResolution } from '@lib/host-resolution';
import type { HostResolution } from '@lib/host-resolution';
import { LINTING_TOOLS } from '@lib/safe-tools';
import { createWizardToolsServer, WIZARD_TOOL_NAMES } from '@lib/wizard-tools';
import {
Expand Down Expand Up @@ -176,7 +176,7 @@ export type AgentConfig = {
workingDirectory: string;
posthogMcpUrl: string;
posthogApiKey: string;
posthogApiHost: string;
host: HostResolution;
additionalMcpServers?: Record<string, { url: string }>;
detectPackageManager: PackageManagerDetector;
/** Base URL for the skills server (context-mill dev or GitHub releases) */
Expand Down Expand Up @@ -620,16 +620,12 @@ export async function initializeAgent(
logToFile('Install directory:', options.installDir);

try {
// TODO: clean up in #755
const gatewayUrl = HostResolution.fromApiHost(
config.posthogApiHost,
).gatewayUrl;

// Configure model routing (inherited by the SDK subprocess). All model
// calls route through the PostHog LLM gateway, authed with the user's
// OAuth token.
// Disable experimental betas (like input_examples) the gateway doesn't support.
process.env.CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS = 'true';
const gatewayUrl = config.host.gatewayUrl;
process.env.ANTHROPIC_BASE_URL = gatewayUrl;
process.env.ANTHROPIC_AUTH_TOKEN = config.posthogApiKey;

Expand Down
5 changes: 3 additions & 2 deletions src/lib/agent/agent-prompt-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import type {
QueuedTask,
} from './runner/sequence/orchestrator/queue';
import type { ResolvedTask } from './runner/sequence/orchestrator/executor';
import type { HostResolution } from '@lib/host-resolution';
import { DEFAULT_AGENT_MODEL } from '@lib/constants';

/**
Expand All @@ -31,7 +32,7 @@ import { DEFAULT_AGENT_MODEL } from '@lib/constants';
export interface OrchestratorPromptContext {
projectId: number;
projectApiKey: string;
host: string;
host: HostResolution;
/** Path to the framework's reference implementation (EXAMPLE.md), if available. */
examplePath?: string;
/** Path to the framework's rules (COMMANDMENTS.md), if available. */
Expand All @@ -44,7 +45,7 @@ function projectContext(ctx: OrchestratorPromptContext): string {
Project context:
- PostHog Project ID: ${ctx.projectId}
- PostHog public token: ${ctx.projectApiKey}
- PostHog Host: ${ctx.host}`;
- PostHog Host: ${ctx.host.apiHost}`;
}

/** Points the agent at the framework's reference integration to learn patterns from. */
Expand Down
5 changes: 3 additions & 2 deletions src/lib/agent/agent-prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@
*/

import type { ProgramRun } from './agent-runner.js';
import type { HostResolution } from '@lib/host-resolution';

/**
* Values available to prompt builders after OAuth completes.
*/
export interface PromptContext {
projectId: number;
projectApiKey: string;
host: string;
host: HostResolution;
/** Set when skillId was provided and the skill was installed successfully. */
skillPath?: string;
/**
Expand Down Expand Up @@ -44,7 +45,7 @@ function defaultProjectPrompt(ctx: PromptContext): string {
Project context:
- PostHog Project ID: ${ctx.projectId}
- PostHog public token: ${ctx.projectApiKey}
- PostHog Host: ${ctx.host}`;
- PostHog Host: ${ctx.host.apiHost}`;
}

function skillPrompt(skillPath: string, reportFile: string): string {
Expand Down
13 changes: 2 additions & 11 deletions src/lib/agent/mcp-prompt-streaming.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
import type { AgentChunk } from '@ui/tui/services/mcp-suggested-prompts-services';
import type { Credentials } from '@lib/wizard-session';
import { DEFAULT_AGENT_MODEL, WIZARD_USER_AGENT } from '@lib/constants';
import { HostResolution, mcpUrlFor } from '@lib/host-resolution';
import { logToFile } from '@utils/debug';
import { buildAgentEnv } from '@lib/agent/agent-interface';
import { sanitizeAgentSubprocessEnv } from '@lib/agent/agent-env-isolation';
Expand All @@ -42,13 +41,6 @@ const MODEL = DEFAULT_AGENT_MODEL;
// telemetry on average turn counts per prompt.
const MAX_TURNS = 30;

// One MCP url for every region: the server resolves the user's region from
// the bearer token, so the EU subdomain (a Claude Code OAuth workaround) is
// not needed here.
function resolveMcpUrl(): string {
return mcpUrlFor(false);
}

/**
* Extract a short, single-line summary from an arbitrary value. Used
* for tool-call args and tool-result bodies so the screen has something
Expand Down Expand Up @@ -201,8 +193,7 @@ export async function* runMcpPromptViaSdk(args: {
process.env.CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS = 'true';

// Route through the PostHog LLM gateway, authed with the user's OAuth token.
// TODO: clean up in #755
const gatewayUrl = HostResolution.fromApiHost(credentials.host).gatewayUrl;
const gatewayUrl = credentials.host.gatewayUrl;
process.env.ANTHROPIC_BASE_URL = gatewayUrl;
process.env.ANTHROPIC_AUTH_TOKEN = credentials.accessToken;
process.env.CLAUDE_CODE_OAUTH_TOKEN = credentials.accessToken;
Expand All @@ -226,7 +217,7 @@ export async function* runMcpPromptViaSdk(args: {
once: true,
});

const mcpUrl = resolveMcpUrl();
const mcpUrl = credentials.host.mcpUrl;
logToFile(
`[runMcpPromptViaSdk] mcpUrl=${mcpUrl} model=${MODEL} resume=${
resumeSessionId ?? '(none)'
Expand Down
20 changes: 7 additions & 13 deletions src/lib/agent/runner/harness/anthropic/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,22 +43,16 @@ export const anthropicBackend: AgentHarness = {
middleware,
model,
} = inputs;
const {
skillsBaseUrl,
accessToken,
host,
mcpUrl,
wizardFlags,
wizardMetadata,
} = boot;
const { skillsBaseUrl, credentials, wizardFlags, wizardMetadata } = boot;
const { accessToken, host } = credentials;

getUI().log.step('Initializing Claude agent...');
const agent = await initializeAgent(
{
workingDirectory: session.installDir,
posthogMcpUrl: mcpUrl,
posthogMcpUrl: host.mcpUrl,
posthogApiKey: accessToken,
posthogApiHost: host,
host,
additionalMcpServers: config.additionalMcpServers,
detectPackageManager:
config.detectPackageManager ?? detectNodePackageManagers,
Expand Down Expand Up @@ -124,9 +118,9 @@ export const anthropicBackend: AgentHarness = {
const agent = await initializeAgent(
{
workingDirectory: session.installDir,
posthogMcpUrl: boot.mcpUrl,
posthogApiKey: boot.accessToken,
posthogApiHost: boot.host,
posthogMcpUrl: boot.credentials.host.mcpUrl,
posthogApiKey: boot.credentials.accessToken,
host: boot.credentials.host,
detectPackageManager: detectNodePackageManagers,
skillsBaseUrl: boot.skillsBaseUrl,
wizardFlags: boot.wizardFlags,
Expand Down
22 changes: 12 additions & 10 deletions src/lib/agent/runner/harness/pi/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import fs from 'fs';
import path from 'path';
import { getUI } from '@ui';
import { getLogFilePath, logToFile } from '@utils/debug';
import { getLlmGatewayUrl } from '@utils/urls';
import {
Harness,
POSTHOG_FLAG_HEADER_PREFIX,
Expand Down Expand Up @@ -95,16 +94,16 @@ function piMcpContext(boot: BootstrapResult, instructions?: string): string {
return ['', '## PostHog MCP server', instructions].join('\n');
}
const project = boot.project?.name
? `${boot.project.name} (id ${boot.projectId})`
: `id ${boot.projectId}`;
? `${boot.project.name} (id ${boot.credentials.projectId})`
: `id ${boot.credentials.projectId}`;
// Fallback: a `## PostHog project` block with name/id, host, region.
return [
'',
'## PostHog project',
'Your `posthog_exec` calls run against this project:',
`- Project: ${project}`,
`- Host: ${boot.host}`,
`- Region: ${boot.cloudRegion}`,
`- Host: ${boot.credentials.host.apiHost}`,
`- Region: ${boot.credentials.host.region}`,
].join('\n');
}

Expand Down Expand Up @@ -314,14 +313,14 @@ export const piBackend: AgentHarness = {
// `/v1` the Anthropic SDK strips.
const api = gatewayApiFor(modelId);
const caps = modelCapabilities(modelId, boot.wizardFlags);
const gatewayUrl = getLlmGatewayUrl(boot.host);
const gatewayUrl = boot.credentials.host.gatewayUrl;
const baseUrl =
api === 'openai-completions' ? `${gatewayUrl}/v1` : gatewayUrl;
const registry = ModelRegistry.inMemory(AuthStorage.create());
registry.registerProvider(GATEWAY_PROVIDER, {
name: 'PostHog Gateway',
baseUrl,
apiKey: boot.accessToken,
apiKey: boot.credentials.accessToken,
authHeader: true,
api,
headers: buildGatewayHeaders(boot.wizardMetadata, boot.wizardFlags),
Expand Down Expand Up @@ -368,7 +367,10 @@ export const piBackend: AgentHarness = {
// so it gets the bare gateway URL regardless of which API shape the
// agent's model uses. Without this, pi has no ANTHROPIC_* env (it
// auths programmatically) and triage would silently no-op.
triageAuth: { baseURL: gatewayUrl, authToken: boot.accessToken },
triageAuth: {
baseURL: gatewayUrl,
authToken: boot.credentials.accessToken,
},
// Where pi's bash runs; the rm allowance is confined to this tree.
workingDirectory: session.installDir,
});
Expand All @@ -392,8 +394,8 @@ export const piBackend: AgentHarness = {
const { setupPostHogMcp } = await import('./mcp');
const mcp = await setupPostHogMcp({
agentDir: getAgentDir(),
mcpUrl: boot.mcpUrl,
accessToken: boot.accessToken,
mcpUrl: boot.credentials.host.mcpUrl,
accessToken: boot.credentials.accessToken,
userAgent: WIZARD_USER_AGENT,
});
extensionFactories.push(mcp.extensionFactory);
Expand Down
33 changes: 5 additions & 28 deletions src/lib/agent/runner/sequence/linear.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { OutroKind } from '../../../wizard-session';
import { getUI } from '../../../../ui';
import { AgentErrorType, AgentSignals } from '../../agent-interface';
import { restoreClaudeSettings } from '../../claude-settings';
import { HostResolution } from '@lib/host-resolution';
import { logToFile } from '../../../../utils/debug';
import { createBenchmarkPipeline } from '../../../middleware/benchmark';
import {
Expand Down Expand Up @@ -40,16 +39,8 @@ export async function runLinearProgram(
boot: BootstrapResult,
composed = false,
): Promise<void> {
const {
skillsBaseUrl,
projectApiKey,
host,
accessToken,
projectId,
cloudRegion,
wizardFlags,
project,
} = boot;
const { skillsBaseUrl, credentials, wizardFlags, project } = boot;
const { projectApiKey, host, projectId } = credentials;

// 5. Skill install (if skillId provided)
let skillPath: string | undefined;
Expand Down Expand Up @@ -270,12 +261,7 @@ export async function runLinearProgram(

// 10. Post-run hooks
if (config.postRun) {
await config.postRun(session, {
accessToken,
projectApiKey,
host,
projectId,
});
await config.postRun(session, credentials);
}

// A composed sub-run (integration inside self-driving) skips the terminal
Expand All @@ -291,23 +277,14 @@ export async function runLinearProgram(
// that the screen never reads. UI.setOutroData() goes through the store
// and also merges in any post-snapshot URLs from the live session.
const outroData = config.buildOutroData
? config.buildOutroData(
session,
{ accessToken, projectApiKey, host, projectId },
cloudRegion,
)
? config.buildOutroData(session, credentials)
: {
kind: OutroKind.Success,
message: config.successMessage,
reportFile: config.reportFile,
docsUrl: config.docsUrl,
// TODO: clean up in #755
continueUrl: session.signup
? `${
HostResolution.fromRegion(cloudRegion, {
baseUrl: session.baseUrl,
}).appHost
}/products?source=wizard`
? `${host.appHost}/products?source=wizard`
: undefined,
};
if (outroData) {
Expand Down
Loading
Loading