Skip to content

Commit 92963cb

Browse files
committed
Flags
1 parent ffea854 commit 92963cb

12 files changed

Lines changed: 386 additions & 202 deletions

src/lib/agent-interface.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -647,10 +647,15 @@ export async function initializeAgent(
647647
};
648648

649649
// Add in-process wizard tools (env files, package manager detection, skill loading)
650+
// Use v2 skill format when the queued workflow flag is on
651+
const skillFormat =
652+
config.wizardFlags?.['wizard-queued-workflow'] === 'true' ? 'v2' : 'v1';
653+
650654
const wizardToolsServer = await createWizardToolsServer({
651655
workingDirectory: config.workingDirectory,
652656
detectPackageManager: config.detectPackageManager,
653657
skillsBaseUrl: config.skillsBaseUrl,
658+
skillFormat,
654659
});
655660
mcpServers['wizard-tools'] = wizardToolsServer;
656661

src/lib/agent-runner.ts

Lines changed: 164 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import type { PackageDotJson } from '../utils/package-json';
1414
import type { WizardOptions } from '../utils/types';
1515
import { WIZARD_INTERACTION_EVENT_NAME } from './constants';
1616
import { analytics } from '../utils/analytics';
17-
import { getUI } from '../ui';
17+
import { getUI, type SpinnerHandle } from '../ui';
1818
import {
1919
initializeAgent,
2020
runAgent,
@@ -45,6 +45,7 @@ import {
4545
parseWorkflowStepsFromSkillMd,
4646
type WizardWorkflowQueueItem,
4747
} from './workflow-queue';
48+
import { runSingleQueryFlow } from './legacy/single-query-runner';
4849

4950
const WIZARD_SKILL_ID_SIGNAL = '[WIZARD-SKILL-ID]';
5051

@@ -276,114 +277,34 @@ export async function runAgentWizard(
276277
? createBenchmarkPipeline(spinner, sessionToOptions(session))
277278
: undefined;
278279

279-
// ── Step 1: Bootstrap — install the skill and get its ID ──
280+
// ── Feature flag: queued workflow vs old single-query flow ──
281+
const useQueuedWorkflow = wizardFlags['wizard-queued-workflow'] === 'true';
282+
logToFile(`[agent-runner] wizard-queued-workflow=${useQueuedWorkflow}`);
280283

281-
let agentResult = await runAgent(
282-
agent,
283-
buildBootstrapPrompt(config, promptContext, frameworkContext),
284-
sessionToOptions(session),
285-
spinner,
286-
{
287-
estimatedDurationMinutes: config.ui.estimatedDurationMinutes,
288-
spinnerMessage: 'Preparing integration...',
289-
successMessage: 'Integration prepared',
290-
errorMessage: 'Integration failed during bootstrap',
291-
additionalFeatureQueue: [],
292-
requestRemark: false,
293-
captureOutputText: true,
294-
captureSessionId: true,
295-
finalizeMiddleware: false,
296-
},
297-
middleware,
298-
);
299-
300-
const queuedSessionId = agentResult.sessionId;
301-
const installedSkillId =
302-
extractInstalledSkillId(agentResult.outputText ?? '') ?? undefined;
303-
304-
if (!installedSkillId) {
305-
await wizardAbort({
306-
message:
307-
'The wizard could not determine which integration skill was installed during bootstrap.',
308-
error: new WizardError('Bootstrap step did not emit installed skill id'),
309-
});
310-
}
311-
312-
// ── Step 2: Read SKILL.md and seed the queue from its frontmatter ──
313-
314-
if (!agentResult.error && installedSkillId) {
315-
const skillMdPath = path.join(
316-
session.installDir,
317-
'.claude',
318-
'skills',
319-
installedSkillId,
320-
'SKILL.md',
321-
);
322-
const skillMdContent = fs.readFileSync(skillMdPath, 'utf-8');
323-
const workflowSteps = parseWorkflowStepsFromSkillMd(skillMdContent);
284+
let agentResult: Awaited<ReturnType<typeof runAgent>>;
324285

325-
if (workflowSteps.length === 0) {
326-
logToFile(
327-
'[agent-runner] No workflow steps found in SKILL.md frontmatter, aborting',
328-
);
329-
await wizardAbort({
330-
message:
331-
'The installed skill does not contain workflow steps in its metadata.',
332-
error: new WizardError('No workflow steps in SKILL.md frontmatter'),
333-
});
334-
}
335-
336-
logToFile(
337-
`[agent-runner] Seeded queue from SKILL.md: ${workflowSteps
338-
.map((s) => s.stepId)
339-
.join(', ')}`,
286+
if (useQueuedWorkflow) {
287+
agentResult = await runQueuedWorkflow(
288+
agent,
289+
config,
290+
session,
291+
promptContext,
292+
frameworkContext,
293+
spinner,
294+
middleware,
340295
);
341-
342-
// ── Step 3: Execute workflow steps + env-vars from the queue ──
343-
344-
const queue = createPostBootstrapQueue(workflowSteps);
345-
getUI().setWorkQueue(queue);
346-
347-
while (queue.length > 0) {
348-
const queueItem = queue.dequeue()!;
349-
350-
getUI().setCurrentQueueItem({ id: queueItem.id, label: queueItem.label });
351-
352-
const prompt = buildQueuedPrompt(
353-
queueItem,
354-
config,
355-
promptContext,
356-
installedSkillId,
357-
);
358-
359-
agentResult = await runAgent(
360-
agent,
361-
prompt,
362-
sessionToOptions(session),
363-
spinner,
364-
{
365-
estimatedDurationMinutes: config.ui.estimatedDurationMinutes,
366-
spinnerMessage: getQueueSpinnerMessage(queueItem),
367-
successMessage: getQueueSuccessMessage(queueItem, config),
368-
errorMessage: `Integration failed during ${queueItem.id}`,
369-
additionalFeatureQueue:
370-
queueItem.id === 'env-vars' ? session.additionalFeatureQueue : [],
371-
resumeSessionId: queuedSessionId,
372-
requestRemark: queueItem.id === 'env-vars',
373-
captureOutputText: false,
374-
captureSessionId: false,
375-
finalizeMiddleware: queue.length === 0,
376-
},
377-
middleware,
378-
);
379-
380-
getUI().completeQueueItem({ id: queueItem.id, label: queueItem.label });
381-
382-
if (agentResult.error) {
383-
break;
384-
}
385-
}
386-
getUI().setCurrentQueueItem(null);
296+
} else {
297+
// OLD FLOW — single monolithic prompt (see legacy/ folder)
298+
agentResult = await runSingleQueryFlow({
299+
agent,
300+
config,
301+
session,
302+
options: sessionToOptions(session),
303+
spinner,
304+
promptContext,
305+
frameworkContext,
306+
middleware,
307+
});
387308
}
388309

389310
// Handle error cases detected in agent output
@@ -493,19 +414,141 @@ export async function runAgentWizard(
493414
await analytics.shutdown('success');
494415
}
495416

496-
/**
497-
* Build the integration prompt for the agent.
498-
*/
417+
// ── NEW FLOW: Queued workflow (wizard-queued-workflow=true) ──────────
418+
419+
type PromptContext = {
420+
frameworkVersion: string;
421+
typescript: boolean;
422+
projectApiKey: string;
423+
host: string;
424+
projectId: number;
425+
};
426+
427+
async function runQueuedWorkflow(
428+
agent: Awaited<ReturnType<typeof initializeAgent>>,
429+
config: FrameworkConfig,
430+
session: WizardSession,
431+
promptContext: PromptContext,
432+
frameworkContext: Record<string, unknown>,
433+
spinner: SpinnerHandle,
434+
middleware?: Parameters<typeof runAgent>[5],
435+
): Promise<Awaited<ReturnType<typeof runAgent>>> {
436+
// Step 1: Bootstrap — install the skill and get its ID
437+
let agentResult = await runAgent(
438+
agent,
439+
buildBootstrapPrompt(config, promptContext, frameworkContext),
440+
sessionToOptions(session),
441+
spinner,
442+
{
443+
estimatedDurationMinutes: config.ui.estimatedDurationMinutes,
444+
spinnerMessage: 'Preparing integration...',
445+
successMessage: 'Integration prepared',
446+
errorMessage: 'Integration failed during bootstrap',
447+
additionalFeatureQueue: [],
448+
requestRemark: false,
449+
captureOutputText: true,
450+
captureSessionId: true,
451+
finalizeMiddleware: false,
452+
},
453+
middleware,
454+
);
455+
456+
const queuedSessionId = agentResult.sessionId;
457+
const installedSkillId =
458+
extractInstalledSkillId(agentResult.outputText ?? '') ?? undefined;
459+
460+
if (!installedSkillId) {
461+
await wizardAbort({
462+
message:
463+
'The wizard could not determine which integration skill was installed during bootstrap.',
464+
error: new WizardError('Bootstrap step did not emit installed skill id'),
465+
});
466+
}
467+
468+
// Step 2: Read SKILL.md and seed the queue from its frontmatter
469+
if (!agentResult.error && installedSkillId) {
470+
const skillMdPath = path.join(
471+
session.installDir,
472+
'.claude',
473+
'skills',
474+
installedSkillId,
475+
'SKILL.md',
476+
);
477+
const skillMdContent = fs.readFileSync(skillMdPath, 'utf-8');
478+
const workflowSteps = parseWorkflowStepsFromSkillMd(skillMdContent);
479+
480+
if (workflowSteps.length === 0) {
481+
logToFile(
482+
'[agent-runner] No workflow steps found in SKILL.md frontmatter, aborting',
483+
);
484+
await wizardAbort({
485+
message:
486+
'The installed skill does not contain workflow steps in its metadata.',
487+
error: new WizardError('No workflow steps in SKILL.md frontmatter'),
488+
});
489+
}
490+
491+
logToFile(
492+
`[agent-runner] Seeded queue from SKILL.md: ${workflowSteps
493+
.map((s) => s.stepId)
494+
.join(', ')}`,
495+
);
496+
497+
// Step 3: Execute workflow steps + env-vars from the queue
498+
const queue = createPostBootstrapQueue(workflowSteps);
499+
getUI().setWorkQueue(queue);
500+
501+
while (queue.length > 0) {
502+
const queueItem = queue.dequeue()!;
503+
504+
getUI().setCurrentQueueItem({ id: queueItem.id, label: queueItem.label });
505+
506+
const prompt = buildQueuedPrompt(
507+
queueItem,
508+
config,
509+
promptContext,
510+
installedSkillId,
511+
);
512+
513+
agentResult = await runAgent(
514+
agent,
515+
prompt,
516+
sessionToOptions(session),
517+
spinner,
518+
{
519+
estimatedDurationMinutes: config.ui.estimatedDurationMinutes,
520+
spinnerMessage: getQueueSpinnerMessage(queueItem),
521+
successMessage: getQueueSuccessMessage(queueItem, config),
522+
errorMessage: `Integration failed during ${queueItem.id}`,
523+
additionalFeatureQueue:
524+
queueItem.id === 'env-vars' ? session.additionalFeatureQueue : [],
525+
resumeSessionId: queuedSessionId,
526+
requestRemark: queueItem.id === 'env-vars',
527+
captureOutputText: false,
528+
captureSessionId: false,
529+
finalizeMiddleware: queue.length === 0,
530+
},
531+
middleware,
532+
);
533+
534+
getUI().completeQueueItem({ id: queueItem.id, label: queueItem.label });
535+
536+
if (agentResult.error) {
537+
break;
538+
}
539+
}
540+
getUI().setCurrentQueueItem(null);
541+
}
542+
543+
return agentResult;
544+
}
545+
546+
// ── Queued workflow prompt builders ─────────────────────────────────
547+
499548
function buildQueuedPrompt(
500549
queueItem: WizardWorkflowQueueItem,
501550
config: FrameworkConfig,
502-
context: {
503-
frameworkVersion: string;
504-
typescript: boolean;
505-
projectApiKey: string;
506-
host: string;
507-
projectId: number;
508-
},
551+
context: PromptContext,
509552
installedSkillId: string,
510553
): string {
511554
if (queueItem.kind === 'workflow') {
@@ -520,13 +563,7 @@ function buildQueuedPrompt(
520563

521564
function buildProjectContextBlock(
522565
config: FrameworkConfig,
523-
context: {
524-
frameworkVersion: string;
525-
typescript: boolean;
526-
projectApiKey: string;
527-
host: string;
528-
projectId: number;
529-
},
566+
context: PromptContext,
530567
frameworkContext: Record<string, unknown>,
531568
): string {
532569
const additionalLines = config.prompts.getAdditionalContextLines
@@ -552,13 +589,7 @@ function buildProjectContextBlock(
552589

553590
function buildBootstrapPrompt(
554591
config: FrameworkConfig,
555-
context: {
556-
frameworkVersion: string;
557-
typescript: boolean;
558-
projectApiKey: string;
559-
host: string;
560-
projectId: number;
561-
},
592+
context: PromptContext,
562593
frameworkContext: Record<string, unknown>,
563594
): string {
564595
return `You have access to the PostHog MCP server which provides skills to integrate PostHog into this ${
@@ -603,6 +634,8 @@ function buildWorkflowStepPrompt(
603634
Read and follow this workflow reference:
604635
\`.claude/skills/${installedSkillId}/references/${referenceFilename}\`
605636
637+
Before starting work, use TodoWrite to create your task plan. Update it as you complete each task.
638+
606639
Important:
607640
- Complete only this workflow step.
608641
- Do NOT continue to any other workflow file.
@@ -612,13 +645,7 @@ Important:
612645

613646
function buildEnvVarPrompt(
614647
config: FrameworkConfig,
615-
context: {
616-
frameworkVersion: string;
617-
typescript: boolean;
618-
projectApiKey: string;
619-
host: string;
620-
projectId: number;
621-
},
648+
context: PromptContext,
622649
): string {
623650
return `Continue the existing conversation.
624651

0 commit comments

Comments
 (0)