@@ -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' ;
2122import {
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 };
12461262interface 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
12511269interface 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
0 commit comments