/** * Just-in-time agent instructions for live mode. * * The live scripts, not the reference doc, own situational plumbing: every * event printed by live-poll carries an `_instructions` string describing * exactly what to do NEXT, with real ids, paths, and line numbers already * substituted and only the active path's rules included (a svelte-component * session never sees JSX guidance, and vice versa). live.md stays lean: the * session contract, harness policy, and design-quality guidance that is not * situational (identity lock, variation axes, parameter budgets). * * Keep these strings imperative, concrete, and short. They are read by an * agent mid-session; every sentence must earn its tokens. Instructions are * versioned with the scripts, so they cannot drift from behavior the way a * hand-maintained doc can. */ const PLAN_POINTER = 'Plan per live.md section 4: extract the identity lock, pick default vs departure mode, commit each variant to a DIFFERENT primary axis, squint-test the trio. Size parameter knobs per section 7 budgets.'; function pollCmd(scriptsPath) { return `node ${scriptsPath}/live-poll.mjs`; } function replyCmd(scriptsPath, id, rest) { return `${pollCmd(scriptsPath)} --reply ${id} ${rest}`; } export function instructionsForEvent(event, { scriptsPath = '{{scripts_path}}' } = {}) { if (!event || typeof event !== 'object') return undefined; switch (event.type) { case 'generate': return generateInstructions(event, scriptsPath); case 'steer': return `Do what the message asks (page edits, navigation help, or a short answer). Then reply exactly once: ${replyCmd(scriptsPath, event.id, 'steer_done ["optional short toast"]')} (on failure: --reply ${event.id} error "Short reason"). No pickup ack; poll again immediately after.`; case 'prefetch': return `Speculative pre-read, no reply owed: resolve ${JSON.stringify(event.pageUrl || '/')} to its source file (root "/" is usually the boot's pageFile; multi-page sites map /foo to public/foo/index.html; SPAs map all routes to one entry), read it into context, then poll again. Skip if you cannot resolve it confidently.`; case 'variant_mount_failed': return `The browser could NOT render variant ${event.variant}${event.url ? ` (module: ${event.url})` : ''}${event.error ? `: ${String(event.error).slice(0, 200)}` : ''}. The user sees a persistent error card, not variants. Fix the variant source files, then reply ${replyCmd(scriptsPath, event.id, 'done --file ')}; the browser retries on its own. Poll again after the reply.`; case 'accept': return acceptInstructions(event, scriptsPath); case 'discard': return event?._completionAck?.ok === true ? 'Original restored and durable completion acknowledged; nothing to do. Poll again.' : `Completion was not acknowledged: run node ${scriptsPath}/live-complete.mjs --id ${event.id} --discarded, then poll again.`; case 'manual_edit_apply': return `The user already clicked Apply; never ask, discard, or redirect. Delegate the source edits to the impeccable_manual_edit_applier subagent when available (pass cwd, scripts path, event id, page URL, chunk/deadline, batch, evidencePath); it must not poll or reply. ${event.repair ? 'A `repair` payload is present: the previous Apply changed source but validation failed; fix the CURRENT source, never roll back yourself. ' : ''}Reply exactly once: ${replyCmd(scriptsPath, event.id, `done --data '{"status":"done","appliedEntryIds":[...],"failed":[],"files":[...],"notes":[]}'`)} (status "partial"/"error" with failed[] when not every entry applied). Then poll again.`; case 'timeout': return 'No event arrived; poll again immediately.'; case 'exit': return `Session over: kill any background poll, then node ${scriptsPath}/live-server.mjs stop (removes the injected script tag). Sweep leftover impeccable-variants-start / impeccable-carbonize-start markers from source.`; default: return undefined; } } function generateInstructions(event, scriptsPath) { const id = event.id; const scaffold = event.scaffold; const steps = []; if (event.screenshotPath) { steps.push(`Read the annotated screenshot first: ${event.screenshotPath}. Comment {x,y} positions bind text to the child under that point; strokes read by shape (loop = emphasis on this thing, arrow = direction, cross = delete).`); } else { steps.push('No screenshot was sent (the user did not annotate); do not ask for one and do not screenshot the page. Work from element.outerHTML, the computed styles, and the prompt.'); } if (event.mode === 'insert') { steps.push(insertScaffoldInstructions(event, scriptsPath)); } else if (scaffold?.previewMode === 'svelte-component') { steps.push(svelteComponentInstructions(event, scaffold, scriptsPath)); } else if (scaffold && scaffold.sourceWritten === false) { steps.push(deferredWrapperInstructions(event, scaffold, scriptsPath)); } else if (scaffold) { steps.push(`The wrapper is already written into ${scaffold.file}. Splice preview CSS plus all ${event.count} variants at line ${scaffold.insertLine} in ONE edit, following the returned cssAuthoring contract (styleTag, selector strategy, forbidden patterns). Each variant div holds exactly ONE top-level element (same tag as the original); first visible, others display: none.`); } else { steps.push(`Preflight could not scaffold${event.scaffoldError ? ` (${event.scaffoldError})` : ''}. Run node ${scriptsPath}/live-wrap.mjs --id ${id} --count ${event.count} --element-id "${event.element?.id || ''}" --classes "${(event.element?.classes || []).join(',')}" --tag "${event.element?.tagName || ''}" --text "". Keep the flags separate; --text disambiguates repeated siblings. On a fallback error, follow live.md's Handle fallback.`); } steps.push(event.action && event.action !== 'impeccable' ? `Action is "${event.action}": read reference/${event.action}.md before planning; its MUST params are non-negotiable. ${PLAN_POINTER}` : `Freeform action: work from SKILL.md rules plus craft-floor.md; no sub-command file. ${PLAN_POINTER}`); steps.push(`When all ${event.count} variants are delivered: ${replyCmd(scriptsPath, id, 'done --file ')}. Then poll again. If generation fails after the browser flipped to GENERATING, reply --reply ${id} error "Short reason" so the bar resets (never live-accept --discard for this).`); return steps.map((s, i) => `${i + 1}. ${s}`).join('\n'); } function svelteComponentInstructions(event, scaffold, scriptsPath) { const dir = scaffold.componentDir; const count = event.count; return `Svelte component preview. EDIT the existing stubs ${dir}/v1.svelte ... v${count}.svelte in place; never delete or recreate them; do not read them back (the prop-substituted markup is in scaffold.componentStubMarkup). Keep the stub's control flow ({#each}, {#if}) and propContract prop names exactly; never flatten a loop into literal items. The stub