Skip to content
Open
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
2 changes: 2 additions & 0 deletions bin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ import { selfDrivingCommand } from './src/commands/self-driving';
import { slackCommand } from './src/commands/slack';
import { uploadSourcemapsCommand } from './src/commands/upload-sourcemaps';
import { errorTrackingCommand } from './src/commands/error-tracking';
import { featureFlagsCommand } from './src/commands/feature-flags';
import { skillCommand } from './src/commands/skill';
import { cliCommand } from './src/commands/cli';
import { recoverOrphanedSettingsBackups } from '@shared/claude-settings';
Expand Down Expand Up @@ -116,5 +117,6 @@ Wizard.use(basicIntegrationCommand)
.use(slackCommand)
.use(uploadSourcemapsCommand)
.use(errorTrackingCommand)
.use(featureFlagsCommand)
.use(skillCommand)
.init();
1 change: 1 addition & 0 deletions e2e-harness/action-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ export const ACTION_REGISTRY: Partial<Record<ScreenName, DriverAction[]>> = {
[ScreenId.AiObservabilityIntro]: [confirmSetupAction],
[ScreenId.MetricsIntro]: [confirmSetupAction],
[ScreenId.ErrorTrackingIntro]: [confirmSetupAction],
[ScreenId.FeatureFlagsIntro]: [confirmSetupAction],
[ScreenId.AuditIntro]: [confirmSetupAction],
[ScreenId.DoctorIntro]: [confirmSetupAction],
[ScreenId.WarehouseIntro]: [confirmSetupAction],
Expand Down
1 change: 1 addition & 0 deletions e2e-harness/e2e-profile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ export function decideE2eAction(
case ScreenId.AiObservabilityIntro:
case ScreenId.MetricsIntro:
case ScreenId.ErrorTrackingIntro:
case ScreenId.FeatureFlagsIntro:
case ScreenId.AuditIntro:
case ScreenId.SourceMapsIntro:
case ScreenId.DoctorIntro:
Expand Down
3 changes: 3 additions & 0 deletions e2e-harness/profiles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import replayVisionE2e from '@programs/replay-vision/test/e2e.json';
import selfDrivingE2e from '@programs/self-driving/test/e2e.json';
import sourceMapsE2e from '@programs/error-tracking-upload-source-maps/test/e2e.json';
import errorTrackingE2e from '@programs/error-tracking/test/e2e.json';
import featureFlagsE2e from '@programs/feature-flags/test/e2e.json';
import warehouseSourceE2e from '@programs/warehouse-source/test/e2e.json';
import auditE2e from '@programs/audit/test/e2e.json';

Expand All @@ -39,6 +40,7 @@ const PROFILES: Partial<Record<ProgramId, WizardE2eProfile>> = {
[Program.ErrorTrackingUploadSourceMaps]:
sourceMapsE2e.profile as WizardE2eProfile,
[Program.ErrorTracking]: errorTrackingE2e.profile as WizardE2eProfile,
[Program.FeatureFlags]: featureFlagsE2e.profile as WizardE2eProfile,
[Program.WarehouseSource]: warehouseSourceE2e.profile as WizardE2eProfile,
[Program.Audit]: auditE2e.profile as WizardE2eProfile,
};
Expand All @@ -51,6 +53,7 @@ const VARIATIONS: Partial<Record<ProgramId, WizardE2eVariation[]>> = {
[Program.Metrics]: metricsE2e.variations as WizardE2eVariation[],
[Program.ReplayVision]: replayVisionE2e.variations as WizardE2eVariation[],
[Program.ErrorTracking]: errorTrackingE2e.variations as WizardE2eVariation[],
[Program.FeatureFlags]: featureFlagsE2e.variations as WizardE2eVariation[],
[Program.WarehouseSource]:
warehouseSourceE2e.variations as WizardE2eVariation[],
[Program.Audit]: auditE2e.variations as WizardE2eVariation[],
Expand Down
6 changes: 6 additions & 0 deletions src/__tests__/architecture/known-violations.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"src/commands/error-tracking.ts -> src/programs/error-tracking/index.ts",
"src/commands/factories/family-command-factory.ts -> src/programs/dispatch-family.ts",
"src/commands/factories/family-picker.tsx -> src/commands/command.ts",
"src/commands/feature-flags.ts -> src/programs/feature-flags/index.ts",
"src/commands/mcp-analytics.ts -> src/programs/mcp-analytics/index.ts",
"src/commands/metrics.ts -> src/programs/metrics/index.ts",
"src/commands/migrate.ts -> src/programs/migration/index.ts",
Expand Down Expand Up @@ -67,6 +68,10 @@
"src/programs/error-tracking/index.ts -> src/ui/tui/decks/error-tracking/tips.ts",
"src/programs/events-audit/index.ts -> src/lib/wizard-session.ts",
"src/programs/events-audit/steps.ts -> src/lib/wizard-session.ts",
"src/programs/feature-flags/index.ts -> src/lib/headless-mode.ts",
"src/programs/feature-flags/index.ts -> src/lib/wizard-session.ts",
"src/programs/feature-flags/index.ts -> src/ui/tui/decks/feature-flags/index.tsx",
"src/programs/feature-flags/index.ts -> src/ui/tui/decks/feature-flags/tips.ts",
"src/programs/frameworks/astro/astro-wizard-agent.ts -> src/ui/index.ts",
"src/programs/frameworks/django/utils.ts -> src/ui/index.ts",
"src/programs/frameworks/fastapi/utils.ts -> src/ui/index.ts",
Expand Down Expand Up @@ -138,6 +143,7 @@
"src/ui/tui/screen-sequences.ts -> src/programs/program-step.ts",
"src/ui/tui/screens/ErrorTrackingDetectScreen.tsx -> src/programs/error-tracking/detect-agentic.ts",
"src/ui/tui/screens/ErrorTrackingDetectScreen.tsx -> src/programs/frameworks/registry.ts",
"src/ui/tui/screens/FeatureFlagsIntroScreen.tsx -> src/programs/feature-flags/prompts.ts",
"src/ui/tui/screens/PostHogIntegrationIntroScreen.tsx -> src/programs/frameworks/registry.ts",
"src/ui/tui/screens/RevenueIntroScreen.tsx -> src/programs/revenue-analytics/index.ts",
"src/ui/tui/screens/SelfDrivingGitHubScreen.tsx -> src/programs/self-driving/detect.ts",
Expand Down
5 changes: 5 additions & 0 deletions src/__tests__/headless-scope.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { auditCommand } from '../commands/audit';
import { aiObservabilityCommand } from '../commands/ai-observability';
import { basicIntegrationCommand } from '../commands/basic-integration';
import { featureFlagsCommand } from '../commands/feature-flags';
import { revenueCommand } from '../commands/revenue';
import { HEADLESS_FLAG } from '../lib/headless-mode';
import { GLOBAL_OPTIONS } from '../wizard';
Expand All @@ -26,6 +27,10 @@ describe('headless flag scope', () => {
expect(aiObservabilityCommand.options).toHaveProperty(HEADLESS_FLAG);
});

test('is declared on the feature-flags command', () => {
expect(featureFlagsCommand.options).toHaveProperty(HEADLESS_FLAG);
});

test('is NOT declared on an unrelated native command', () => {
expect(revenueCommand.options ?? {}).not.toHaveProperty(HEADLESS_FLAG);
});
Expand Down
31 changes: 31 additions & 0 deletions src/agent/__tests__/agent-prompt-loader.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
assembleSeedPrompt,
assembleTaskPrompt,
buildRegistry,
loadAgentRegistry,
parseAgentPrompt,
promptModelFor,
queueTools,
Expand Down Expand Up @@ -678,3 +679,33 @@ describe('allowsPostHogMcp', () => {
expect(allowsPostHogMcp(allowedTools)).toBe(true);
});
});

describe('loadAgentRegistry', () => {
const bundledSeed = `---
type: plan
flow: bundled
seed: true
---
Plan.`;
const bundledTask = `---
type: report
flow: bundled
sink: true
---
Report.`;

it('builds bundled prompts without fetching the agent menu', async () => {
const fetchSpy = vi.spyOn(global, 'fetch');

const registry = await loadAgentRegistry(
'http://skills.invalid',
'bundled',
undefined,
[bundledSeed, bundledTask],
);

expect(fetchSpy).not.toHaveBeenCalled();
expect(registry.seed?.type).toBe('plan');
expect(registry.sinkTypes).toEqual(['report']);
});
});
8 changes: 8 additions & 0 deletions src/agent/agent-prompt-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -455,7 +455,15 @@ export async function loadAgentRegistry(
skillsBaseUrl: string,
flow: string,
opts?: Parameters<typeof buildRegistry>[2],
bundledPrompts?: readonly string[],
): Promise<AgentRegistry> {
if (bundledPrompts) {
const prompts = bundledPrompts.map((text) =>
parseAgentPrompt(text, flow, flow),
);
return buildRegistry(prompts, flow, opts);
}

const menuRaw = await fetchText(`${skillsBaseUrl}/agent-menu.json`);
const menu = JSON.parse(menuRaw) as AgentMenu;

Expand Down
16 changes: 15 additions & 1 deletion src/agent/runner/__tests__/switchboard.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ describe('switchboard PROGRAM_BINDINGS', () => {
if (program === 'metrics') continue; // pinned below
if (program === 'replay-vision') continue; // pinned below
if (program === 'error-tracking') continue; // pinned below
if (program === 'feature-flags') continue; // pinned below
expect(resolveBinding({ program, flags: {} })).toEqual(DEFAULT_RESOLVED);
}
});
Expand Down Expand Up @@ -129,6 +130,17 @@ describe('switchboard PROGRAM_BINDINGS', () => {
},
trace: { harness: 'binding', model: 'binding', sequence: 'binding' },
},
{
name: 'binds feature-flags to the orchestrator on pi; stage models come from the bundled prompts',
ctx: { program: 'feature-flags', flags: {} },
binding: {
sequence: Sequence.orchestrator,
harness: Harness.pi,
model: DEFAULT_AGENT_MODEL,
thinkingLevel: undefined,
},
trace: { harness: 'binding', model: 'binding', sequence: 'binding' },
},
{
name: 'falls back to DEFAULT_BINDING for an unmapped program',
ctx: { program: 'not-a-program', flags: {} },
Expand Down Expand Up @@ -243,7 +255,9 @@ describe('switchboard composed clamp', () => {
model: GPT5_6_TERRA_MODEL,
thinkingLevel: 'high',
}
: program === 'metrics' || program === 'error-tracking'
: program === 'metrics' ||
program === 'error-tracking' ||
program === 'feature-flags'
? {
...DEFAULT_RESOLVED,
model: DEFAULT_AGENT_MODEL,
Expand Down
23 changes: 14 additions & 9 deletions src/agent/runner/sequence/orchestrator/orchestrator-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -623,15 +623,20 @@ async function executeOrchestrator(
// once up front: its types drive enqueue validation, and resolving a task to
// its run config is then synchronous, with no mid-drain network latency.
const flow = config.agentFlow ?? programId;
const registry = await loadAgentRegistry(boot.skillsBaseUrl, flow, {
exclude: effectiveExcludedTaskTypes(config, boot.wizardFlags),
// Baked into the prompts at load, so enqueue, dispatch, and telemetry all read one effective spec.
overrides: resolveStageOverrides(
programId,
boot.wizardFlags,
boot.wizardFlagPayloads,
),
});
const registry = await loadAgentRegistry(
boot.skillsBaseUrl,
flow,
{
exclude: effectiveExcludedTaskTypes(config, boot.wizardFlags),
// Baked into the prompts at load, so enqueue, dispatch, and telemetry all read one effective spec.
overrides: resolveStageOverrides(
programId,
boot.wizardFlags,
boot.wizardFlagPayloads,
),
},
config.agentPrompts,
);
if (signal?.aborted) return cancelledRun();
const seedPrompt = registry.seed;
if (!seedPrompt) {
Expand Down
1 change: 1 addition & 0 deletions src/agent/runner/shared/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ export interface RunConfig {
disallowedTools?: readonly string[];
/** Context-mill flow the orchestrator loads. Defaults to `programId`. */
agentFlow?: string;
agentPrompts?: readonly string[];
/** Task types the program excludes for these flags. The orchestrator adds the CI gates. */
excludedTaskTypes?: (flags: Record<string, string>) => readonly string[];
/** Tasks to queue before the orchestrator's planner runs. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,11 @@ describe('isolation — everything on at once', () => {
model: GPT5_6_TERRA_MODEL,
thinkingLevel: 'high',
});
} else if (program === 'metrics' || program === 'error-tracking') {
} else if (
program === 'metrics' ||
program === 'error-tracking' ||
program === 'feature-flags'
) {
// Orchestrator + pi from their OWN bindings, not the flag; stage
// models are pinned context-mill side in the flow frontmatter.
expect(resolved).toEqual({
Expand Down
5 changes: 5 additions & 0 deletions src/agent/runner/switchboard/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,11 @@ export const PROGRAM_BINDINGS: Partial<Record<ProgramId, ProgramBinding>> = {
harness: Harness.pi,
model: DEFAULT_AGENT_MODEL,
},
'feature-flags': {
sequence: Sequence.orchestrator,
harness: Harness.pi,
model: DEFAULT_AGENT_MODEL,
},
'ai-observability': {
sequence: Sequence.linear,
harness: Harness.pi,
Expand Down
7 changes: 7 additions & 0 deletions src/commands/feature-flags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { featureFlagsConfig } from '@programs/feature-flags/index';

import type { Command } from './command';
import { nativeCommandFactory } from './factories/native-command-factory';

export const featureFlagsCommand: Command =
nativeCommandFactory(featureFlagsConfig);
53 changes: 53 additions & 0 deletions src/programs/__tests__/__snapshots__/flow-traces.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,59 @@ exports[`flow traces per program > events-audit (node) 1`] = `
}
`;

exports[`flow traces per program > feature-flags (node) 1`] = `
{
"events": [
{
"event": "screen health-check",
"from": "feature-flags-intro",
"program": "feature-flags",
},
{
"event": "screen auth",
"from": "health-check",
"program": "feature-flags",
},
{
"event": "screen run",
"from": "auth",
"program": "feature-flags",
},
{
"event": "screen ai-opt-in",
"from": "run",
"program": "feature-flags",
},
{
"event": "screen run",
"from": "ai-opt-in",
"program": "feature-flags",
},
{
"event": "screen outro",
"from": "run",
"program": "feature-flags",
},
{
"event": "screen keep-skills",
"from": "outro",
"program": "feature-flags",
},
],
"program": "feature-flags",
"screens": [
"feature-flags-intro",
"health-check",
"auth",
"ai-opt-in",
"run",
"outro",
"keep-skills",
],
"stoppedOn": null,
}
`;

exports[`flow traces per program > mcp-add (node) 1`] = `
{
"events": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ exports[`post-auth gate ids per program > match the golden 1`] = `
"detect",
],
"events-audit": [],
"feature-flags": [],
"mcp-add": [],
"mcp-analytics": [],
"mcp-remove": [],
Expand Down
Loading
Loading