/** * Opt-in governed answering of wicked-interactive's STRUCTURAL edits (task #86, Phase 7c * final leg — the draft leg's sibling, see draft-events.ts). * * wicked-interactive's service is model-free: a feedback batch's deterministic items * (content/style/remove) are applied instantly by the service and NEVER reach this seam — * only `structural-change` items climb. The service lands the deterministic partial as * `_v{N}.html`, then emits `wicked.interactive.feedback.processed` with `awaiting_structural > 0` * and the structural items INLINE (selector + instruction + the element's CURRENT fragment), * expecting *something with intelligence* to answer with `wicked.interactive.edit.completed` * carrying the edited fragments. Today that answerer is an ad-hoc `assist` agent session * (assist skill Step 3); this module makes a crew-governed run the answerer instead. * * Same shape as the draft leg, with the structural deltas: * * - VERSIONED TARGETING: the dedupe unit is the HANDOFF — `:v` — not the doc * (one doc produces many structural handoffs over its life). The durable ledger keys on the * handoff, and the closing emit's idempotency key carries doc+version. * - INV-2 AT SCALE: unlike a first draft (no pre-existing anchors), an edited fragment MUST * retain every pre-existing `data-wid` BYTE-EXACT — the service silently rejects violations * (regenerate.js Inv2Error → the user's edit dies with no version landed). So this seam runs * a deterministic PRE-EMIT SELF-CHECK mirroring the assist skill's Step 2/Step 3c discipline: * parse the handed-off fragment's data-wids, verify the worker's fragment retains all of * them, and fail HONEST (error status, no emit) on any violation — a loud crew-side rejection * instead of a silent service-side one. * - HANDOFF BY FILE: fragments can be arbitrarily large and the PTY seat runner's prompt is * single-line, so the seam writes the items to a handoff JSON on disk and names its path in * the problem; the worker writes each edited fragment to a per-item output file (never JSON * with hand-escaped HTML — the exact failure the assist skill's Step 3c warns about). * - PROJECT ATTRIBUTION (the 7b surface): a project-bound doc's events carry `project_id` * (interactive's DES-PROJECT-001 enrichment); the governed run is launched with * `LaunchOptions.projectId` so the edit shows up in the project's activity feed. */ import { type RecallIntent } from './draft-events.js'; import { InteractiveHandoffLedger } from './ledger.js'; import { type SkillHeld } from './draft-skill.js'; import type { CoreAdapter } from '../core/adapter.js'; export declare const FEEDBACK_PROCESSED = "wicked.interactive.feedback.processed"; export declare const EDIT_COMPLETED = "wicked.interactive.edit.completed"; /** Exact-type filter with a domain guard — no wildcard, one event type is the whole trigger. */ export declare const INTERACTIVE_EDIT_BUS_FILTER = "wicked.interactive.feedback.processed@wicked-interactive"; /** * The governed workflow that fulfils a structural handoff: the wicked-core built-in preset * `interactive-edit` (X-MIG M9, wicked-core#860; crew#935). ONE creator step, `edit`: the target * fragments are already extracted and the instructions already written, so there is nothing left * to plan. The step carries the fragment-edit contract (preserve every pre-existing `data-wid` * byte-for-byte, mint none, keep the markup well-formed) and runs `wicked-garden-draft`. The engine * owns the steps (the PA's `pa-scope` first, its floor additions, the deliverable floor on the * creator step); this seam still runs its own pre-emit INV-2 self-check. Crew registers no def. */ export declare const INTERACTIVE_EDIT_WORKFLOW = "interactive-edit"; /** One structural item as handed off by the service (fragment = current outerHTML). */ export interface StructuralItem { selector: string; instruction: string; fragment: string; } /** The feedback-processed fields this seam acts on. */ export interface StructuralHandoff { documentId: string; /** The partial version the service just landed — the PARENT of the version this edit makes. */ version: number; items: StructuralItem[]; /** Present when the doc is project-bound (interactive's DES-PROJECT-001 enrichment). */ projectId?: string; } /** * Parse a bus frame into a {@link StructuralHandoff}, or `null` when it is not an actionable * structural handoff: wrong type, malformed payload, slug-invalid document_id, non-integer * version, or NO usable structural items. Deterministic-only batches (`awaiting_structural: 0`) * return `null` by construction — those edits already landed inside the service (edit-routing * rung 1) and must never climb to a crew. Items whose `fragment` is missing/null (the service * could not extract the element) are dropped: there is no current markup to edit. */ export declare function parseStructuralFeedback(eventType: string, payload: unknown): StructuralHandoff | null; /** The one dedupe unit of this seam: a handoff is doc+version, NOT doc-lifetime. */ export declare function handoffKey(documentId: string, version: number): string; /** Deterministic bus idempotency key for the one edit this seam may land per handoff. */ export declare function editIdempotencyKey(documentId: string, version: number): string; /** One row of the on-disk handoff file the worker reads. */ export interface HandoffFileItem extends StructuralItem { /** Absolute path the worker must write the edited fragment to. */ output_path: string; } /** Per-item output paths are INDEX-named (`fragment-1.html`, …), never selector-named — a * selector is bus-supplied data and must not name a filesystem path. */ export declare function handoffFileItems(handoff: StructuralHandoff, outDir: string): HandoffFileItem[]; /** * The run's problem statement. Single-line (PTY contract); the bulky fragments ride in the * handoff FILE, so only identity, the capped instruction gist, and the file path ride here. */ export declare function editProblem(handoff: StructuralHandoff, handoffPath: string, intent?: RecallIntent): string; /** Every `data-wid` value in a fragment, in document order (duplicates preserved). */ export declare function fragmentWids(fragment: string): string[]; /** The data-wids of `before` missing from `after`, byte-exact. Non-empty = INV-2 violation. * Deliberately conservative: a wid that survives with different quoting/escaping counts as * dropped — the service compares exact attribute values, and fail-closed here beats a silent * Inv2Error rejection there. */ export declare function droppedWids(before: string, after: string): string[]; /** One rejected item of a self-check. */ export interface EditViolation { selector: string; reason: string; } /** The self-check verdict over a whole handoff's worker output. */ export interface EditCollection { results: Array<{ selector: string; fragment: string; }>; violations: EditViolation[]; } /** * Read the worker's per-item output files and verify each against the handed-off fragment: * the file must exist non-empty, and every pre-existing `data-wid` must survive byte-exact. * ANY violation fails the whole handoff (all-or-nothing): a partial apply would land a version * the user believes complete, and the violating item would die silently service-side. */ export declare function collectEditResults(items: HandoffFileItem[]): EditCollection; /** Options for {@link startInteractiveEditSubscriber}. */ export interface InteractiveEditOptions { /** The bus db the daemon handed its engine (core/bus.ts); without one the seam does not arm. */ dbPath?: string; /** Poll cadence, ms (default 2000; tests shorten it). */ pollIntervalMs?: number; /** Heartbeat narration cadence while a run is in flight, ms (default 15000 — inside the * UI's ~20s `status.requested` window so the canvas never reads frozen). */ heartbeatMs?: number; /** Ledger file (default `~/.wicked-crew/interactive-edit-ledger.json`). */ ledgerPath?: string; /** Root under which each handoff gets its OWN subdirectory (`//`) holding * that handoff's JSON + edited fragments (default `~/.wicked-crew/interactive-edits`). Only * the per-handoff subdirectory is ever declared as a run's write root — see crew#314. */ editDir?: string; /** Seat roster JSON for the governed run (default: the production council roster). * The functional-test harness passes a deterministic stub seat here. */ clisJson?: string; /** The roster accessor used when `clisJson` is not set — the server wires the daemon's roster * WITH crew's standing (`api/roster-standing.ts`, F-RECON-002/003) so a signed-out seat reaches * the engine benched (`health {usable: false, reason}`) instead of being convened or elected. */ roster?: () => unknown[]; /** Does the daemon's PUBLISHED skills snapshot hold (and enable) a skill? Consulted ONCE at arm time * for `wicked-garden-draft` (interactive/draft-skill.ts), only for the arm log line: the preset * always runs the skill, and a snapshot without it fails the run before the document work starts (crew#935). * Default: `() => false` (a caller without a skills runtime has no snapshot to hold anything). */ skillHeld?: SkillHeld; /** crew#1145: the daemon's background skills ladder — the arm line waits for it. Absent = say it now. */ skillsSettled?: Promise; /** The docs root a handoff's doc manifest is read from — the KIND GATE only (CREW-UX-9): * a doc whose manifest says `kind: "demo"` is the demo seam's to answer (a demo refines by * re-authoring `demo.spec.mjs` + re-recording, assist SKILL.md Step 8c — a storyboard * fragment edit would change the chapter list, not the recording). A doc this daemon * cannot see (missing/unreadable manifest) stays THIS seam's, exactly as before CREW-UX-9 * — and the demo seam requires a readable `kind: "demo"` manifest, so exactly one seam * answers any handoff. Default: the shared-default resolution * (`WICKED_INTERACTIVE_ROOT` › `~/wicked-interactive/docs`); the server wires the * per-project `interactiveRoot` setting through here. */ resolveDocsRoot?: (projectId: string | undefined) => string; /** Called after a launch that FILED the run into a project (the handoff carried * `project_id`). The server wires this to the same post-commit half the launch route * performs: tag the run in the live membership index + emit `wicked.crew.membership.attached` * (the engine already attached the crew.run membership atomically with the launch). */ onRunFiled?: (runId: string, projectId: string) => void; /** Diagnostics sink (default: console.error). */ log?: (message: string) => void; /** Error-level logger for connection-fatal subscriber errors (the /diagnostics ring folds it); defaults to `log`. */ logError?: (message: string) => void; } /** Handle for a running subscription. */ export interface InteractiveEditSubscription { stop(): Promise | void; /** The durable ledger (diagnostics / tests). */ ledger: InteractiveHandoffLedger; /** Documents with an edit run currently in flight — the chat seam's per-doc serialization * (CREW-UX-5 contract (c)) consults this so an iteration ask never races a structural edit. */ inFlightDocs(): string[]; } /** * Arm the seam (it registers nothing: the run is the engine's `interactive-edit` preset), open a durable * `wicked.interactive.feedback.processed` subscription, and answer each handoff carrying * structural items with a governed run that ends in `wicked.interactive.edit.completed`. * * Graceful degradation mirrors the draft seam: a missing wicked-bus package or an unopenable * db LOGS and returns `null` — the daemon must still boot on a machine whose bus is broken; * interactive's assist loop remains the (always-available) fallback answerer. */ export declare function startInteractiveEditSubscriber(adapter: CoreAdapter, opts?: InteractiveEditOptions): Promise; //# sourceMappingURL=edit-events.d.ts.map