|
| 1 | +import fs from 'fs'; |
| 2 | +import path from 'path'; |
1 | 3 | import { |
2 | 4 | DEFAULT_PACKAGE_INSTALLATION, |
3 | 5 | type FrameworkConfig, |
@@ -39,9 +41,9 @@ import { |
39 | 41 | } from '../utils/wizard-abort'; |
40 | 42 | import { formatScanReport, writeScanReport } from './yara-hooks'; |
41 | 43 | import { |
42 | | - createInitialWizardWorkflowQueue, |
| 44 | + createPostBootstrapQueue, |
| 45 | + parseWorkflowStepsFromSkillMd, |
43 | 46 | type WizardWorkflowQueueItem, |
44 | | - type WorkflowStepSeed, |
45 | 47 | } from './workflow-queue'; |
46 | 48 |
|
47 | 49 | const WIZARD_SKILL_ID_SIGNAL = '[WIZARD-SKILL-ID]'; |
@@ -274,76 +276,106 @@ export async function runAgentWizard( |
274 | 276 | ? createBenchmarkPipeline(spinner, sessionToOptions(session)) |
275 | 277 | : undefined; |
276 | 278 |
|
277 | | - // TODO: S5 — seed from context-mill workflow manifest instead of this static list |
278 | | - const workflowSteps: WorkflowStepSeed[] = [ |
279 | | - { |
280 | | - stepId: '1.0-begin', |
281 | | - referenceFilename: 'basic-integration-1.0-begin.md', |
282 | | - }, |
283 | | - { stepId: '1.1-edit', referenceFilename: 'basic-integration-1.1-edit.md' }, |
284 | | - { |
285 | | - stepId: '1.2-revise', |
286 | | - referenceFilename: 'basic-integration-1.2-revise.md', |
287 | | - }, |
| 279 | + // ── Step 1: Bootstrap — install the skill and get its ID ── |
| 280 | + |
| 281 | + let agentResult = await runAgent( |
| 282 | + agent, |
| 283 | + buildBootstrapPrompt(config, promptContext, frameworkContext), |
| 284 | + sessionToOptions(session), |
| 285 | + spinner, |
288 | 286 | { |
289 | | - stepId: '1.3-conclude', |
290 | | - referenceFilename: 'basic-integration-1.3-conclude.md', |
| 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, |
291 | 296 | }, |
292 | | - ]; |
293 | | - const queue = createInitialWizardWorkflowQueue(workflowSteps); |
294 | | - let queuedSessionId: string | undefined; |
295 | | - let installedSkillId: string | undefined; |
296 | | - let agentResult: Awaited<ReturnType<typeof runAgent>> = {}; |
297 | | - |
298 | | - while (queue.length > 0) { |
299 | | - const queueItem = queue.dequeue()!; |
300 | | - const prompt = buildQueuedPrompt( |
301 | | - queueItem, |
302 | | - config, |
303 | | - promptContext, |
304 | | - frameworkContext, |
| 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', |
305 | 319 | installedSkillId, |
| 320 | + 'SKILL.md', |
306 | 321 | ); |
| 322 | + const skillMdContent = fs.readFileSync(skillMdPath, 'utf-8'); |
| 323 | + const workflowSteps = parseWorkflowStepsFromSkillMd(skillMdContent); |
307 | 324 |
|
308 | | - agentResult = await runAgent( |
309 | | - agent, |
310 | | - prompt, |
311 | | - sessionToOptions(session), |
312 | | - spinner, |
313 | | - { |
314 | | - estimatedDurationMinutes: config.ui.estimatedDurationMinutes, |
315 | | - spinnerMessage: getQueueSpinnerMessage(queueItem), |
316 | | - successMessage: getQueueSuccessMessage(queueItem, config), |
317 | | - errorMessage: `Integration failed during ${queueItem.id}`, |
318 | | - additionalFeatureQueue: |
319 | | - queueItem.id === 'env-vars' ? session.additionalFeatureQueue : [], |
320 | | - resumeSessionId: queuedSessionId, |
321 | | - requestRemark: queueItem.id === 'env-vars', |
322 | | - captureOutputText: queueItem.kind === 'bootstrap', |
323 | | - captureSessionId: true, |
324 | | - finalizeMiddleware: queue.length === 0, |
325 | | - }, |
326 | | - middleware, |
| 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(', ')}`, |
327 | 340 | ); |
328 | 341 |
|
329 | | - queuedSessionId = agentResult.sessionId ?? queuedSessionId; |
| 342 | + // ── Step 3: Execute workflow steps + env-vars from the queue ── |
330 | 343 |
|
331 | | - if (queueItem.kind === 'bootstrap') { |
332 | | - installedSkillId = |
333 | | - extractInstalledSkillId(agentResult.outputText ?? '') ?? undefined; |
334 | | - if (!installedSkillId) { |
335 | | - await wizardAbort({ |
336 | | - message: |
337 | | - 'The wizard could not determine which integration skill was installed during bootstrap.', |
338 | | - error: new WizardError( |
339 | | - 'Bootstrap step did not emit installed skill id', |
340 | | - ), |
341 | | - }); |
342 | | - } |
343 | | - } |
| 344 | + const queue = createPostBootstrapQueue(workflowSteps); |
| 345 | + |
| 346 | + while (queue.length > 0) { |
| 347 | + const queueItem = queue.dequeue()!; |
| 348 | + const prompt = buildQueuedPrompt( |
| 349 | + queueItem, |
| 350 | + config, |
| 351 | + promptContext, |
| 352 | + installedSkillId, |
| 353 | + ); |
| 354 | + |
| 355 | + agentResult = await runAgent( |
| 356 | + agent, |
| 357 | + prompt, |
| 358 | + sessionToOptions(session), |
| 359 | + spinner, |
| 360 | + { |
| 361 | + estimatedDurationMinutes: config.ui.estimatedDurationMinutes, |
| 362 | + spinnerMessage: getQueueSpinnerMessage(queueItem), |
| 363 | + successMessage: getQueueSuccessMessage(queueItem, config), |
| 364 | + errorMessage: `Integration failed during ${queueItem.id}`, |
| 365 | + additionalFeatureQueue: |
| 366 | + queueItem.id === 'env-vars' ? session.additionalFeatureQueue : [], |
| 367 | + resumeSessionId: queuedSessionId, |
| 368 | + requestRemark: queueItem.id === 'env-vars', |
| 369 | + captureOutputText: false, |
| 370 | + captureSessionId: false, |
| 371 | + finalizeMiddleware: queue.length === 0, |
| 372 | + }, |
| 373 | + middleware, |
| 374 | + ); |
344 | 375 |
|
345 | | - if (agentResult.error) { |
346 | | - break; |
| 376 | + if (agentResult.error) { |
| 377 | + break; |
| 378 | + } |
347 | 379 | } |
348 | 380 | } |
349 | 381 |
|
@@ -467,17 +499,9 @@ function buildQueuedPrompt( |
467 | 499 | host: string; |
468 | 500 | projectId: number; |
469 | 501 | }, |
470 | | - frameworkContext: Record<string, unknown>, |
471 | | - installedSkillId?: string, |
| 502 | + installedSkillId: string, |
472 | 503 | ): string { |
473 | | - if (queueItem.kind === 'bootstrap') { |
474 | | - return buildBootstrapPrompt(config, context, frameworkContext); |
475 | | - } |
476 | | - |
477 | 504 | if (queueItem.kind === 'workflow') { |
478 | | - if (!installedSkillId) { |
479 | | - throw new Error('Workflow step requires installed skill id'); |
480 | | - } |
481 | 505 | return buildWorkflowStepPrompt( |
482 | 506 | queueItem.referenceFilename, |
483 | 507 | installedSkillId, |
|
0 commit comments