import type { WorkflowStep } from '../../domain/index.ts'; type StepExecution = 'delegated' | 'main'; const formatList = (values: ReadonlyArray): string => values.length > 0 ? values.join(', ') : '(none)'; type BuildResourceSectionOptions = { readonly execution: StepExecution; readonly step: WorkflowStep; }; /** * Builds the resource-policy section shared by delegated and main steps. * * @param options - Step execution kind and resource permissions. * @returns Lines for the resource-policy prompt section. */ export function buildResourceSection({ execution, step, }: BuildResourceSectionOptions): ReadonlyArray { const isDelegated = execution === 'delegated'; return [ `## Enforced ${isDelegated ? 'child' : 'step'} resources`, '', `Pi tools: ${formatList(step.permissions.tools)}`, `MCP selectors: ${formatList(step.permissions.mcp)}`, `Skills: ${formatList(step.permissions.skills)}`, `Bash policy: ${step.permissions.bash.mode}`, `Bash allow rules: ${ step.permissions.bash.allow.length > 0 ? JSON.stringify(step.permissions.bash.allow) : '(none)' }`, '', `Project-local skills are always allowed. The listed skills restrict only global skills from ~/.agents/skills/ and ~/.pi/agent/skills/. Tool calls are enforced ${isDelegated ? 'inside this child process' : 'by the workflow harness'}.`, '', ]; } /** * Builds the explicit previous-step handoff section for a delegated child. * * @param handoff - Compact handoff supplied to the fresh child context. * @returns Lines for the previous-step handoff section. */ export function buildDelegatedHandoffSection( handoff: string, ): ReadonlyArray { return [ '## Previous step handoff', '', handoff || '(none; this is the first workflow step)', '', ]; } /** Builds the immutable same-worktree constraint for a restarted iteration. */ export function buildRestartWorkspaceSection( workspaceCwd: string | undefined, ): ReadonlyArray { if (!workspaceCwd) return []; return [ '## Restart workspace constraint', '', `This iteration must reuse and rebind exactly this existing workspace: ${workspaceCwd}`, 'Do not create or substitute another workspace. If it cannot be safely reused, complete with a configured non-binding outcome that pauses the workflow.', '', ]; } /** * Builds non-interactive recovery guidance specific to delegated steps. * * @param step - Active delegated workflow step. * @returns Delegated completion-guidance lines. */ export function buildDelegatedCompletionInstructions(): ReadonlyArray { return [ 'This child is non-interactive. Never call `contact_supervisor`, `agent_supervisor`, or `intercom`.', 'Follow the step instructions when choosing one valid outcome; outcome names have no built-in domain meaning.', 'Stay within the configured permissions and do not broaden mutation targets or external side effects.', ]; } /** * Builds the shared operator-facing format for non-successful step results. * * The summary is posted verbatim to chat and is the only context available to * a fresh child, so it must remain actionable without becoming a transcript. */ export function buildNonSuccessSummaryInstructions( outcomes: ReadonlyArray, ): ReadonlyArray { const nonSuccessOutcomes = outcomes.filter((outcome) => ['blocked', 'handoff'].includes(outcome), ); if (nonSuccessOutcomes.length === 0) return []; return [ '## Human-readable non-success results', '', `For ${nonSuccessOutcomes.map((outcome) => `\`${outcome}\``).join(', ')}, provide a decision-first typed handoff. The extension formats it for the operator and fresh child.`, '', 'Use only plain-text `completed` and `remaining` fields. Do not include Markdown, headings, list markers, or additional handoff fields.', 'For `blocked`, include at least one user question ending in `?` in `remaining`. For `handoff`, list only non-question actionable work in `remaining`. `ready` requires exactly `No active-step work remains.`.', '', 'Do not include a process narrative, raw logs, repeated policy constraints, successful checks, clean-state notes, or statements that merely say the child lacks authority. Mention a passed check only when it directly explains the remaining issue. Name the missing prerequisite and who can supply it. Keep only details needed to make the decision or complete the next action.', '', ]; }