Skip to content

Commit 426da5a

Browse files
committed
Merge remote-tracking branch 'origin/main' into e2e-control-plane
2 parents 72a1051 + b08f8c1 commit 426da5a

34 files changed

Lines changed: 1628 additions & 347 deletions

CHANGELOG.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,40 @@
11
# Changelog
22

3+
## [2.30.0](https://github.com/PostHog/wizard/compare/v2.29.0...v2.30.0) (2026-06-23)
4+
5+
6+
### Features
7+
8+
* Track steps progression for Wizard. ([#718](https://github.com/PostHog/wizard/issues/718)) ([896f190](https://github.com/PostHog/wizard/commit/896f190d6ddc73f2c7d5381f53f01bc001698116))
9+
10+
## [2.29.0](https://github.com/PostHog/wizard/compare/v2.28.1...v2.29.0) (2026-06-23)
11+
12+
13+
### Features
14+
15+
* agentic detection for source-map upload flow ([#689](https://github.com/PostHog/wizard/issues/689)) ([940bf67](https://github.com/PostHog/wizard/commit/940bf6791e80b54eb4964baa41b88f0e6742ca9e))
16+
* detect more data warehouse sources in onboarding ([#711](https://github.com/PostHog/wizard/issues/711)) ([b206169](https://github.com/PostHog/wizard/commit/b20616915158813f41ee7c9f0c0abc15386ea532))
17+
18+
19+
### Bug Fixes
20+
21+
* **analytics:** tag every event with program_id ([#714](https://github.com/PostHog/wizard/issues/714)) ([4db5a0f](https://github.com/PostHog/wizard/commit/4db5a0f7aa84a4daa86e70789f592adc6c5563d3))
22+
* **self-driving:** Rename "canonical" scouts to "built-in" in self-driving prompt ([#717](https://github.com/PostHog/wizard/issues/717)) ([e99450e](https://github.com/PostHog/wizard/commit/e99450e82951f1a5237f7fd75e9ebe9d981152e8))
23+
24+
## [2.28.1](https://github.com/PostHog/wizard/compare/v2.28.0...v2.28.1) (2026-06-23)
25+
26+
27+
### Bug Fixes
28+
29+
* **self-driving:** Differentiate the source vs scout tips, rename "fleet" to "troop" ([#712](https://github.com/PostHog/wizard/issues/712)) ([edacabc](https://github.com/PostHog/wizard/commit/edacabc57660f1f4944a0c1cecdfa52f5f88986a))
30+
31+
## [2.28.0](https://github.com/PostHog/wizard/compare/v2.27.0...v2.28.0) (2026-06-23)
32+
33+
34+
### Features
35+
36+
* **signals:** Add limits on scouts. ([#707](https://github.com/PostHog/wizard/issues/707)) ([06307d9](https://github.com/PostHog/wizard/commit/06307d9b03018a73f5716adf056eef592804eb60))
37+
338
## [2.27.0](https://github.com/PostHog/wizard/compare/v2.26.0...v2.27.0) (2026-06-22)
439

540

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@posthog/wizard",
3-
"version": "2.27.0",
3+
"version": "2.30.0",
44
"homepage": "https://github.com/PostHog/wizard",
55
"repository": "https://github.com/PostHog/wizard",
66
"description": "The PostHog wizard helps you to configure your project",

src/lib/agent/agent-interface.ts

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
WIZARD_VARIANTS,
1818
WIZARD_ORCHESTRATOR_FLAG_KEY,
1919
WIZARD_USER_AGENT,
20+
DEFAULT_AGENT_MODEL,
2021
} from '@lib/constants';
2122
import {
2223
type AdditionalFeature,
@@ -133,6 +134,11 @@ export type AgentConfig = {
133134
wizardMetadata?: Record<string, string>;
134135
/** Program identifier — selects the model for that program. */
135136
integrationLabel?: string;
137+
/**
138+
* Override the agent model for this run. Defaults to DEFAULT_AGENT_MODEL.
139+
* Use for cheap mechanical runs (e.g. source-map detection on HAIKU_MODEL).
140+
*/
141+
modelOverride?: string;
136142
/** Bridge that drives the `wizard_ask` overlay. Omit in non-interactive hosts. */
137143
askBridge?: import('@lib/wizard-ask-bridge').WizardAskBridge;
138144
/** Per-run cap on `wizard_ask` invocations. Defaults to 10. */
@@ -547,15 +553,16 @@ export async function initializeAgent(
547553
logToFile('Install directory:', options.installDir);
548554

549555
try {
550-
// Configure LLM gateway environment variables (inherited by SDK subprocess)
556+
// Configure model routing (inherited by the SDK subprocess). All model
557+
// calls route through the PostHog LLM gateway, authed with the user's
558+
// OAuth token.
559+
// Disable experimental betas (like input_examples) the gateway doesn't support.
560+
process.env.CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS = 'true';
551561
const gatewayUrl = getLlmGatewayUrlFromHost(config.posthogApiHost);
552562
process.env.ANTHROPIC_BASE_URL = gatewayUrl;
553563
process.env.ANTHROPIC_AUTH_TOKEN = config.posthogApiKey;
554564
// Use CLAUDE_CODE_OAUTH_TOKEN to override any stored /login credentials
555565
process.env.CLAUDE_CODE_OAUTH_TOKEN = config.posthogApiKey;
556-
// Disable experimental betas (like input_examples) that the LLM gateway doesn't support
557-
process.env.CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS = 'true';
558-
559566
logToFile('Configured LLM gateway:', gatewayUrl);
560567
logToFile(
561568
'API key prefix:',
@@ -603,7 +610,7 @@ export async function initializeAgent(
603610

604611
// Bare model IDs (no `anthropic/` prefix) so the LLM gateway's Bedrock
605612
// fallback can match map_to_bedrock_model()'s strict lookup.
606-
const model = 'claude-sonnet-4-6';
613+
const model = config.modelOverride ?? DEFAULT_AGENT_MODEL;
607614

608615
const agentRunConfig: AgentRunConfig = {
609616
workingDirectory: config.workingDirectory,
@@ -677,6 +684,11 @@ export async function runAgent(
677684
errorMessage?: string;
678685
additionalFeatureQueue?: readonly AdditionalFeature[];
679686
abortCases?: readonly AbortCaseMatcher[];
687+
/**
688+
* Emit a `wizard: step` event on each agent task transition. Threaded from
689+
* `ProgramRun.trackStepProgress`; defaults off for every other caller.
690+
*/
691+
emitStepEvents?: boolean;
680692
/** Request the end-of-run reflection remark. Defaults to true. */
681693
requestRemark?: boolean;
682694
/**
@@ -695,6 +707,7 @@ export async function runAgent(
695707
successMessage = 'PostHog integration complete',
696708
errorMessage = 'Integration failed',
697709
abortCases = [],
710+
emitStepEvents = false,
698711
} = config ?? {};
699712

700713
logToFile('Starting agent run');
@@ -905,7 +918,8 @@ export async function runAgent(
905918
},
906919
env: {
907920
...process.env,
908-
// Prevent user's Anthropic API key from overriding the wizard's OAuth token
921+
// Drop any shell ANTHROPIC_API_KEY so it can't override the wizard's
922+
// OAuth gateway token.
909923
ANTHROPIC_API_KEY: undefined,
910924
// Defer MCP tool schemas to avoid bloating the system prompt.
911925
// The posthog-wizard MCP exposes many query tools with large schemas;
@@ -921,6 +935,7 @@ export async function runAgent(
921935
// disables tool search deferral and re-inflates the system prompt by
922936
// ~113k tokens (the reason ENABLE_TOOL_SEARCH=auto:0 is set above).
923937
MCP_CONNECTION_NONBLOCKING: '0',
938+
// PostHog gateway headers: Bedrock fallback + property/flag tags.
924939
ANTHROPIC_CUSTOM_HEADERS: buildAgentEnv(
925940
agentConfig.wizardMetadata ?? {},
926941
agentConfig.wizardFlags ?? {},
@@ -1014,6 +1029,7 @@ export async function runAgent(
10141029
receivedSuccessResult,
10151030
tasks,
10161031
isOrchestratorEnabled(agentConfig.wizardFlags ?? {}),
1032+
emitStepEvents,
10171033
);
10181034

10191035
// [ABORT] detection: the skill emits "[ABORT] <reason>" when it
@@ -1246,6 +1262,8 @@ type TaskEntry = { content: string; status: string; activeForm?: string };
12461262
interface TaskStore {
12471263
tasks: Map<string, TaskEntry>;
12481264
sync: () => void;
1265+
/** When true, emit a `wizard: step` event on each status transition. */
1266+
emitStepEvents?: boolean;
12491267
}
12501268

12511269
interface ToolUseBlock {
@@ -1285,6 +1303,36 @@ function handleTaskUpdate(block: ToolUseBlock, store: TaskStore): void {
12851303
if (input.status === 'deleted') {
12861304
store.tasks.delete(input.taskId);
12871305
} else {
1306+
// Per-step drop-off signal for programs that opt in via `trackStepProgress`
1307+
// (threaded here as `emitStepEvents`). Emit `wizard: step` on each real
1308+
// status transition so analytics can see how far a run got — even a silent
1309+
// step (no wizard_ask) that dies mid-run surfaces as its last `in_progress`
1310+
// with no matching `completed`. Generic: the step name is whatever the
1311+
// agent set; the `command` tag already identifies the program.
1312+
if (
1313+
store.emitStepEvents &&
1314+
input.status &&
1315+
input.status !== existing.status &&
1316+
(input.status === 'in_progress' || input.status === 'completed')
1317+
) {
1318+
const keys = [...store.tasks.keys()];
1319+
analytics.wizardCapture('step', {
1320+
// The task's display label lives on `activeForm` (what the TUI renders,
1321+
// e.g. "Checking access"); `content`/`subject` are typically empty on a
1322+
// status-only TaskUpdate. Prefer the stored entry, then the update, so
1323+
// the name is never null. Named `step_name` (not `step`): a bare
1324+
// `properties.step` doesn't resolve in HogQL — `step_name` queries
1325+
// cleanly, like `step_index` / `step_count`.
1326+
step_name:
1327+
existing.activeForm ??
1328+
input.activeForm ??
1329+
existing.content ??
1330+
input.subject,
1331+
status: input.status,
1332+
step_index: keys.indexOf(input.taskId),
1333+
step_count: keys.length,
1334+
});
1335+
}
12881336
store.tasks.set(input.taskId, {
12891337
content: input.subject ?? existing.content,
12901338
status: input.status ?? existing.status,
@@ -1368,6 +1416,9 @@ function handleSDKMessage(
13681416
// The orchestrator owns the TUI task panel (it renders its queue). Suppress the
13691417
// agent's own TaskCreate/TaskUpdate rendering so it does not clobber the queue.
13701418
suppressTaskRender = false,
1419+
// Opt-in per-step analytics, threaded from runAgent's `emitStepEvents`
1420+
// (ProgramRun.trackStepProgress). Off for every program that doesn't opt in.
1421+
emitStepEvents = false,
13711422
): void {
13721423
// Map preserves insertion order (the order the agent created the tasks).
13731424
// Within that, group by status: completed first, then in_progress, then
@@ -1453,6 +1504,7 @@ function handleSDKMessage(
14531504
dispatchTaskToolUse(block as ToolUseBlock, {
14541505
tasks,
14551506
sync: syncTasks,
1507+
emitStepEvents,
14561508
});
14571509
}
14581510

src/lib/agent/mcp-prompt-streaming.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -194,11 +194,12 @@ export async function* runMcpPromptViaSdk(args: {
194194
// before its query() call. Without these the SDK tries to
195195
// authenticate directly against Anthropic and 401s with "Invalid
196196
// authentication credentials".
197+
process.env.CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS = 'true';
198+
// Route through the PostHog LLM gateway, authed with the user's OAuth token.
197199
const gatewayUrl = getLlmGatewayUrlFromHost(credentials.host);
198200
process.env.ANTHROPIC_BASE_URL = gatewayUrl;
199201
process.env.ANTHROPIC_AUTH_TOKEN = credentials.accessToken;
200202
process.env.CLAUDE_CODE_OAUTH_TOKEN = credentials.accessToken;
201-
process.env.CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS = 'true';
202203
logToFile(
203204
`[runMcpPromptViaSdk] gatewayUrl=${gatewayUrl} tokenPrefix=${
204205
credentials.accessToken
@@ -315,10 +316,8 @@ export async function* runMcpPromptViaSdk(args: {
315316
allowedTools: ['mcp__posthog-wizard__*'],
316317
env: {
317318
...process.env,
318-
// Without this the SDK picks up a user's personal
319-
// ANTHROPIC_API_KEY from their shell and silently bypasses
320-
// the PostHog LLM gateway — defeats quota tracking and the
321-
// OAuth flow even though our other env vars are correct.
319+
// Drop any shell ANTHROPIC_API_KEY so it can't silently bypass the
320+
// PostHog LLM gateway and defeat quota tracking and the OAuth flow.
322321
ANTHROPIC_API_KEY: undefined,
323322
// Defer MCP tool schemas to avoid bloating the system prompt.
324323
// posthog-wizard exposes many query tools with large schemas;

src/lib/agent/runner/linear.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ export async function runLinearProgram(
163163
errorMessage: config.errorMessage ?? `${config.integrationLabel} failed`,
164164
additionalFeatureQueue: config.additionalFeatureQueue ?? [],
165165
abortCases: config.abortCases,
166+
emitStepEvents: config.trackStepProgress ?? false,
166167
},
167168
middleware,
168169
);

src/lib/agent/runner/shared/bootstrap.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,22 @@ export async function bootstrapProgram(
210210
await getUI().waitForAiOptIn();
211211
logToFile('[agent-runner] AI opt-in gate cleared');
212212

213+
// Park for any interactive step the user must complete AFTER authenticating
214+
// but BEFORE the agent runs — e.g. the source-maps project picker, which
215+
// needs credentials to scan and writes its choice to frameworkContext that
216+
// the run prompt reads. Generic: await every gated step between auth and run.
217+
const authIndex = programConfig.steps.findIndex((s) => s.screenId === 'auth');
218+
const runIndex = programConfig.steps.findIndex((s) => s.screenId === 'run');
219+
if (authIndex !== -1 && runIndex > authIndex) {
220+
for (const step of programConfig.steps.slice(authIndex + 1, runIndex)) {
221+
if (step.gate) {
222+
logToFile(`[agent-runner] awaiting post-auth gate: ${step.id}`);
223+
await getUI().waitForGate(step.id);
224+
logToFile(`[agent-runner] post-auth gate cleared: ${step.id}`);
225+
}
226+
}
227+
}
228+
213229
// Feature flags, variant metadata, and MCP url. Both arms need these, and the
214230
// fork decision reads the flags.
215231
const wizardFlags = await analytics.getAllFlagsForWizard();

src/lib/agent/runner/shared/types.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,14 @@ export interface ProgramRun {
7979
* key in the browser) before they can answer.
8080
*/
8181
askTimeoutMs?: number;
82+
/**
83+
* Emit a `wizard: step` analytics event on each agent task-list transition
84+
* (in_progress / completed) so this program can build a step-level drop-off
85+
* funnel — including silent steps that ask the user nothing. The step name is
86+
* whatever the agent set on the task. Defaults to off, so no other program's
87+
* analytics change; opt in per program.
88+
*/
89+
trackStepProgress?: boolean;
8290
}
8391

8492
/**

src/lib/constants.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,20 @@
44

55
import { VERSION } from './version';
66

7+
// ── Models ──────────────────────────────────────────────────────────
8+
9+
/**
10+
* Default model for agent runs. Bare model IDs (no `anthropic/` prefix) so the
11+
* LLM gateway's Bedrock fallback can match map_to_bedrock_model().
12+
*/
13+
export const DEFAULT_AGENT_MODEL = 'claude-sonnet-4-6';
14+
15+
/**
16+
* Cheaper, faster model for mechanical agent work (e.g. repo classification
17+
* during source-map detection). Passed via AgentConfig.modelOverride.
18+
*/
19+
export const HAIKU_MODEL = 'claude-haiku-4-5-20251001';
20+
721
// ── Integration / CLI ───────────────────────────────────────────────
822

923
/**
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { coerceAgenticReport, manifestGlob } from '@lib/detection/agentic';
2+
3+
const TARGETS = ['nextjs', 'node', 'vite'];
4+
5+
describe('manifestGlob', () => {
6+
it('is one brace-expansion glob covering JS, Python, Ruby, PHP and native manifests', () => {
7+
const glob = manifestGlob();
8+
expect(glob.startsWith('**/{')).toBe(true);
9+
expect(glob.endsWith('}')).toBe(true);
10+
for (const name of [
11+
'package.json',
12+
'pnpm-workspace.yaml',
13+
'requirements.txt',
14+
'Gemfile',
15+
'composer.json',
16+
'Cargo.toml',
17+
'go.mod',
18+
'build.gradle',
19+
'pubspec.yaml',
20+
]) {
21+
expect(glob).toContain(name);
22+
}
23+
});
24+
});
25+
26+
describe('coerceAgenticReport', () => {
27+
it('keeps a targetId that is in the valid set', () => {
28+
const report = coerceAgenticReport(
29+
{
30+
repoType: 'single',
31+
projects: [
32+
{
33+
path: '.',
34+
framework: 'Next.js',
35+
targetId: 'nextjs',
36+
hasPostHog: true,
37+
},
38+
],
39+
},
40+
TARGETS,
41+
);
42+
43+
expect(report.repoType).toBe('single');
44+
expect(report.projects[0].targetId).toBe('nextjs');
45+
expect(report.projects[0].hasPostHog).toBe(true);
46+
});
47+
48+
it('clamps an unknown targetId to null', () => {
49+
const report = coerceAgenticReport(
50+
{
51+
repoType: 'monorepo',
52+
projects: [
53+
{
54+
path: 'apps/api',
55+
framework: 'Rust',
56+
targetId: 'rocket',
57+
hasPostHog: true,
58+
},
59+
],
60+
},
61+
TARGETS,
62+
);
63+
64+
expect(report.projects[0].targetId).toBeNull();
65+
});
66+
67+
it('defaults malformed fields and an absent projects array', () => {
68+
expect(coerceAgenticReport({}, TARGETS).projects).toEqual([]);
69+
expect(coerceAgenticReport(null, TARGETS).projects).toEqual([]);
70+
71+
const report = coerceAgenticReport({ projects: [{}] }, TARGETS);
72+
const p = report.projects[0];
73+
expect(p.path).toBe('.');
74+
expect(p.framework).toBe('Unknown');
75+
expect(p.targetId).toBeNull();
76+
expect(p.hasPostHog).toBe(false);
77+
});
78+
});

0 commit comments

Comments
 (0)