/** * Opt-in governed answering of wicked-interactive's first-draft generation (task #86 spike, * Phase 7c first leg). * * wicked-interactive's service is model-free: when a doc is created with `kind: "source"` it * seeds a placeholder v0 and emits `wicked.interactive.doc.created`, expecting *something with * intelligence* to answer with `wicked.interactive.draft.completed` carrying the first draft * (the service then instruments `data-wid` anchors, themes it, and lands `_v1.html`). Today * that answerer is an ad-hoc `assist` agent session. This module makes a crew-governed run the * answerer instead — same bus vocabulary, zero interactive-service changes beyond the additive * producer row (`wi-crew`) in interactive's events.js ownership table. * * Shape: graceful degradation when no engine holds the bus, a tap from the newest row and every emit through the engine that * holds the bus (`core/bus.ts`, wicked-core#631) — crew opens no SQLite of its own. * * Behavioral invariants honored (recon-verified against interactive): * - Heartbeat: the canvas shows a working veil and the browser fires ~20s * `status.requested` heartbeats; a silent answerer reads as a frozen UI. We narrate * `wicked.interactive.status.posted` on every phase transition AND on a ≤15s timer. * - Idempotency: interactive may re-emit (and a replay tool can re-post) a doc, and a replayed * `doc.created` must not produce a duplicate `_v2.html`. A durable per-doc ledger * (JSON file, atomic rename) gates the launch, and the final `draft.completed` emit carries * a deterministic idempotency key (`crew:interactive.draft::v1`) so even a double * emit dedupes at the bus (a duplicate key resolves to the existing row). * - INV-2 (`data-wid`): first drafts are whole documents with no pre-existing anchors — the * service instruments fresh ones — so the worker contract explicitly forbids inventing * `data-wid` attributes rather than requiring preservation. The feedback→edit leg (fragment * preservation at scale) is the structural seam next door: edit-events.ts. * - UNFILED DOCS ARE ANSWERED TOO: this seam originally rejected `doc.created` frames without * a `project_id` ("unbound docs are the assist skill's solo business") — superseded by * DES-UX-001 slice U (wicked-studio, §6.2 + §8.4.1 probe 3), which made unfiled docs a * first-class path created through crew's synthesized `default` mount with NO project field. * Nothing else answers those (BRIEF-UX-001 J3 CRITICAL: the doc sat on its placeholder * forever), so the launch simply omits `projectId` — an unfiled governed run (CREW-UX-2). */ import { InteractiveHandoffLedger } from './ledger.js'; import { type DraftFloorIo, type SkillHeld } from './draft-skill.js'; import { type DocGroundingStore, type GroundingRecord } from './doc-grounding.js'; import type { CoreAdapter } from '../core/adapter.js'; export declare const INTERACTIVE_DOMAIN = "wicked-interactive"; export declare const DOC_CREATED = "wicked.interactive.doc.created"; export declare const DRAFT_COMPLETED = "wicked.interactive.draft.completed"; export declare const STATUS_POSTED = "wicked.interactive.status.posted"; /** Exact-type filter with a domain guard — no wildcard, one event type is the whole trigger. */ export declare const INTERACTIVE_BUS_FILTER = "wicked.interactive.doc.created@wicked-interactive"; /** The producer identity stamped on every event crew's interactive seams emit (this module and * edit-events.ts). Must appear in interactive's events.js ownership table for DRAFT_COMPLETED, * EDIT_COMPLETED, and STATUS_POSTED — the additive vocabulary rows that are the only * interactive changes Phase 7c is allowed. */ export declare const INTERACTIVE_PRODUCER = "wi-crew"; /** Interactive's doc-name grammar (server.js DOC_NAME) — re-checked before any launch so a * malformed document_id can't name a ledger key or a draft file path. Shared with the * structural-edit seam (edit-events.ts), which guards the same identity. */ export declare const DOC_NAME: RegExp; /** * The doc-identity half of EVERY payload crew's interactive seams emit (acceptance finding F-045): * `document_id`, plus `project_id` when the document is project-bound. The bridge stamps * `project_id` on its own emits (DES-PROJECT-001 enrichment) and the studio files frames by it — * so a crew heartbeat carrying `document_id` alone was filed under the Unfiled mount while the * project-bound thread heard nothing and, 90 s later, told the user "the generation service may * be down" over a run that was executing. Shared by all four seams (draft/edit/chat/demo); an * unfiled doc keeps the field OFF — never a fabricated 'default'. */ export declare function docScope(documentId: string, projectId?: string | undefined): { document_id: string; project_id?: string; }; /** What every seam's `status.posted` payload carries (before `emitInteractive` stamps `ts`) — the * produced side of the published `InteractiveStatusPosted` frame (wire-contract.test.ts). */ export interface SeamStatusPayload { document_id: string; project_id?: string; state: 'processing' | 'working' | 'asking' | 'complete' | 'error'; message?: string; version?: number; /** The governed run this narration is about (wave 6, F-4R2-005): a skin keys narration per run * and per unit instead of one undifferentiated thread. Absent on a pre-launch status. */ run_id?: string; /** The unit (ord) the line narrates — the most recent engine frame's `ord`; absent before the * first unit-scoped frame. */ unit_ord?: number; /** crew#512: the document's repository grounding as data — on the ONE frame per draft launch that * carries the "Grounded on …" narration (the published `InteractiveDocGrounding`). */ grounding?: GroundingRecord; } /** * The `run_id` / `unit_ord` stamps every seam's narration and heartbeat carry (F-4R2-005): the run * the flight launched and the ord of the latest unit-scoped engine frame — so a repeated heartbeat * says which unit it is still waiting on, and a skin can render one council at a time. */ export declare function narrationStamps(flight: { runId?: string | undefined; narrationOrd?: number | undefined; }): { run_id?: string; unit_ord?: number; }; /** How long a seam waits for the proxy to record a document's create-time grounding binding when * a create for the same project is still in flight (doc-grounding.ts `waitFor`): the bridge emits * `doc.created` before it answers the create, so the bus can beat the record by a few ms. */ export declare const GROUNDING_BINDING_WAIT_MS = 3000; /** * The governed workflow that produces a first draft: the wicked-core built-in preset * `interactive-draft` (X-MIG M9, wicked-core#860; crew#935). Its one creator step plans, then * writes (crew#621: a separate neutral `outline` phase authored the whole deliverable in its * output, so the split was deleted rather than gated), and runs `wicked-garden-draft`, the document * quality floor. The engine owns the steps: it puts the PA's RISK rating (`pa-scope`) first, adds * its floor (`critique`, and more at higher bands), and judges the launch's declared deliverable on * the creator step (`LaunchOptions.deliverables`, written by THIS run). Crew registers no def for * it; its narration reads the run's planned units (`run-units.ts`). The preset always requires the * draft skill: a snapshot without it fails the run before the document work starts. */ export declare const INTERACTIVE_DRAFT_WORKFLOW = "interactive-draft"; /** The doc-creation fields this seam acts on. */ export interface SourceDocCreated { documentId: string; brief: string; sourcePaths: string[]; style: string; /** The crew project this doc is bound to. `undefined` = an UNFILED doc — a first-class path * since DES-UX-001 slice U (wicked-studio, §6.2 + §8.4.1 probe 3): the Make→Document picker's * Unfiled option creates through crew's synthesized `default` mount with NO project field, * and this seam is the only answerer of its generation. Never fabricate 'default' here — the * governed run is launched unfiled (CREW-UX-2 made `project_id: null` legitimate on the run * DTO), not filed into a project that is a mount alias, not a membership target. */ projectId?: string; } /** * Parse a bus frame into a {@link SourceDocCreated}, or `null` when it is not an actionable * `doc.created` (wrong type, non-`source` kind, missing/malformed document_id). `kind: "demo"` * and plain html docs are the assist loop's business, not this seam's. A missing `project_id` * is NOT a rejection: unfiled docs (DES-UX-001 slice U) are actionable with `projectId` * undefined — the earlier "unbound docs are the assist skill's solo business" gate is * superseded (nothing else answers a doc created through the default mount; BRIEF-UX-001 J3). */ export declare function parseSourceDocCreated(eventType: string, payload: unknown): SourceDocCreated | null; /** Collapse whitespace/newlines to single spaces and cap length — the intent must stay a * single line (the PTY seat runner refuses embedded newlines) and a pasted-novel brief must * not balloon the worker prompt. Shared with the structural-edit seam. */ export declare function oneLine(text: string, cap: number): string; /** A project's repo binding, resolved for a governed launch (CREW-UX-8). */ export interface ProjectRepo { /** The registered repo id — the verified registry identity behind `rootPath` (diagnostics * only: deliberately NEVER passed to `launchRun` as `repoRef` — see {@link resolveProjectRepo}). */ repoRef: string; /** The repo's root path — the SOURCE the launch-scoped snapshot is cloned/copied from * (v4). Never handed to the worker directly: an unbound worker's boundary denies reads of * it (wicked-core#294) — the grounding clause names the SNAPSHOT inside the inbox instead. */ rootPath: string; } /** * Resolve the repo a project is bound to (CREW-UX-8): the project's first `crew.repo` member, * verified against the repo registry so a stale membership (repo deleted after attach) never * grounds the task in a path the registry no longer vouches for. `undefined` when the project * has no repo member, the registry no longer knows the ref, or the adapter cannot answer (old * addon, engine hiccup) — every one of those degrades to today's behavior: an ungrounded launch. * * NO LONGER THE SEAM'S GROUNDING RULE (acceptance finding F-046): "the first member" grounded a * brochure about wicked-studio on wicked-core. The draft and demo seams now resolve through * `doc-grounding.ts` `resolveGroundingRepos` — the repos NAMED on the create request, else the * ones the brief names, else the project's sole repo, else none (narrated). Kept exported for * diagnostics and its callers. * * WHY: a doc created under a repo-backed project used to launch its governed draft/revision * run with NO repo context at all, so the worker could not read the project's actual code and * generated placeholder content (operator report, wicked-studio project). Shared by the draft * and chat seams. * * WHY the result feeds a SNAPSHOT, never a binding and never a direct read path (the v4 * design): the launch itself stays UNBOUND (no `repoRef`), even though the project verifiably * has one, because on a repoRef-bound run the worker's ACP tool-permission stream closes on * the FIRST call that needs a permission prompt, so the session dies before any work lands — * no write destination works, not the external inbox, not an in-repo path (wicked-core#293; * v2 of this seam tried both and the adversarial verifier killed each with run evidence). * v3 then handed the unbound worker the absolute `rootPath` to READ — but that rested on a * boundary-context-dependent premise: the "repo reads work" evidence came from BOUND runs, * and an UNBOUND worker's governance boundary is {sandbox, extraWriteRoots, * ~/.claude/plugins}, so its reads of the live repo root are governance-DENIED * (wicked-core#294). What an unbound worker can always read is the inbox the run already * writes to (write roots are readable, wicked-core#259) — so v4 grounds via a capped, * launch-scoped repo SNAPSHOT cloned into the inbox crew-side BEFORE the launch (see * repo-snapshot.ts), and the grounding clause names the snapshot. `rootPath` here is the * clone SOURCE only; `projectId` still passes on the launch — filing is unaffected. */ export declare function resolveProjectRepo(adapter: CoreAdapter, projectId: string, log?: (message: string) => void): Promise; /** The longest snapshot path the grounding clause will carry. NOT a truncation cap — the * clause embeds the path VERBATIM or not at all (see {@link groundablePath}): the snapshot * sits at exactly one spelling, so a flattened/truncated path would ground the worker on a * directory that does not exist (Copilot, crew#313). The budget exists for the PTY prompt * length; a dest over it degrades the launch to ungrounded, honestly narrated. */ export declare const SNAPSHOT_PATH_MAX = 300; /** `true` when a snapshot path can ride the grounding clause EXACTLY as spelled: single-line * (the PTY seat runner refuses embedded newlines — and `oneLine`'s whitespace collapse would * respell the path, so it is never applied to paths) and within the prompt budget. */ export declare function groundablePath(path: string): boolean; /** The facet axes a recall intent can carry. Every axis is OPTIONAL and only defined ones ride the * clause — `memory.recall` matches on a subset, so a `{project}`-only intent is legitimate. * `project` is the reliably-available axis threaded today; `cli`/`repo` are Phase 6. */ export interface RecallIntent { project?: string; cli?: string; repo?: string; } /** The non-empty subset of DEFINED axes for a recall intent, or `undefined` when no axis is set. * Only defined axes are included, so `JSON.stringify` never emits `undefined`/`null` keys — the * clause carries exactly the facets that were threaded. */ export declare function recallIntentObject(intent?: RecallIntent): Record | undefined; /** The recall clause (DES-MEM-FACETED-001 Phase 3): instruct the worker to call the read-only * wicked-estate MCP `memory.recall` with the faceted intent BEFORE it starts, then ground its * work in what returns. Empty (no axis) ⇒ `''` — omitted entirely, exactly like the grounding * clause omits the snapshot fallback when there is no snapshot. SINGLE-LINE by contract (the PTY * seat runner refuses embedded newlines; `JSON.stringify` of a flat string map never emits one). */ export declare function recallClause(intent?: RecallIntent): string; /** The propose clause (DES-MEM-FACETED-001 write side): instruct the worker to record a reusable * learning by calling the wicked-estate MCP `proposal.submit` tool. This is a SAFE write — the * proposal lands in a review queue, inert (never recalled or applied) until an operator approves * it, so it can never pollute anything. Unconditional (every worker may propose). SINGLE-LINE by * contract (the PTY seat runner refuses embedded newlines). Provenance is stamped by the server * from the run env, so the worker supplies only content/facets. */ export declare function proposeClause(): string; /** * The run's problem statement (the engine scopes it per phase and folds each phase's * instructions on top). Carries everything doc-specific: identity, brief, sources, style, and * the absolute path the finished HTML must land at. * * PRIMARY grounding (DES-GROUNDING-001 §3.3): every governed worker is now given the * wicked-estate MCP tools pointed at a code graph. This draft run is repo-LESS; when the project's * graph binding resolves it is passed on the launch and the tools span the project's indexed repos — * but for an UNFILED doc, or when `resolveProjectGraphBinding` returns null, the worker instead gets * whatever graph the run binds (its repo's, or none). The clause below therefore instructs the * worker — UNCONDITIONALLY — to research through * those tools (SearchEntity to find, FetchContent to read, ContextBundle to gather related * material, RetrieveEntity/TraverseGraph to follow references) and to ground every claim in what * they return, never in placeholders. The index is the grounding path. * * FALLBACK — the file snapshot: when a launch-scoped repo snapshot was cloned into the inbox * (CREW-UX-8 v4), its path is named ONLY as a secondary/offline backup the worker uses if the * estate tools are unavailable — no longer the whole grounding mechanism. `snapshotDir` is * embedded VERBATIM — never flattened, never truncated (the snapshot exists at exactly this * path; Copilot, crew#313). The caller guards it with {@link groundablePath} BEFORE snapshotting * and omits it (the estate-tool clause still stands alone) when the path cannot ride. * * SINGLE-LINE by contract: the PTY seat runner refuses any embedded newline (FINDING-011), so * every fragment here stays on one line and the brief is flattened+capped by {@link oneLine}. */ export declare function draftProblem(doc: SourceDocCreated, outPath: string, snapshotDir?: string, intent?: RecallIntent, grounding?: DraftGrounding): string; /** The subject-repo grounding a draft launch resolved (F-046): the repositories the document is * about — each with the offline snapshot that landed for it, when one did — or, when the project * has several repos and none was named, how many there were (the worker must not guess). */ export interface DraftGrounding { subjects: Array<{ name: string; snapshotDir?: string | undefined; }>; unnamedAmong?: number | undefined; } /** Deterministic bus idempotency key for the one draft this seam may land per document. */ export declare function draftIdempotencyKey(documentId: string, projectId?: string): string; /** The dedupe unit of the draft leg: one first draft per document PER PROJECT. `` when * unfiled (legacy spelling), `:draft:` when filed — it starts `:` like every * handoff key, so the doc↔run index and the delete sweep see it. */ export declare function draftHandoffKey(documentId: string, projectId?: string): string; /** Is `key` a draft row of the name `documentId` that is NOT this document's — another project's * (`:draft:`), or, when THIS document is filed, the UNFILED document's bare * `` row? The delete sweep keeps those: deleting one project's document must not make a * same-named document elsewhere forget that it was drafted (the review seam's * `isAnotherProjectsReviewKey`, for drafts; codex on crew#809 for the unfiled row). */ export declare function isAnotherProjectsDraftKey(key: string, documentId: string, projectId: string | undefined): boolean; /** The per-run directory under the draft root is `` for every project — studio's run↔thread * binding reads `interactive-drafts/` back off the run's write root and narrows by `project_id` * (`wicked-studio src/interactive/runBinding.ts`), so the directory grammar cannot carry the * partition. Residual, documented: two projects drafting the SAME slug at the SAME time share that * per-doc directory (deliverable name and snapshot subdir); their keys, announces and ledger rows * do not. */ /** wicked-studio's brand-learn scratch document (`src/theming/scratchDoc.ts`): a FIXED name, kind * `source`, a FIXED brief, one per project, created so a theme learn has a workspace to land tokens * in. It is never a document a person asked to have drafted — answering its creation with a * governed run spent a council and a worker on a scratch pad (crew#811). Matched on the name AND * the brief's fixed opening (codex: a person may legitimately name a document `brand-learn`). */ export declare const STUDIO_SCRATCH_DOC_NAME = "brand-learn"; export declare const STUDIO_SCRATCH_DOC_BRIEF_PREFIX = "Scratch document wicked-studio uses to learn brand themes"; export declare function isStudioScratchDoc(doc: { documentId: string; brief: string; }): boolean; export { InteractiveHandoffLedger, type HandoffLedgerEntry } from './ledger.js'; /** Options for {@link startInteractiveDraftSubscriber}. */ export interface InteractiveDraftOptions { /** 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-draft-ledger.json`). */ ledgerPath?: string; /** Root under which each launch gets its own per-run subdirectory (`//`) * holding the deliverable and, when grounded, the repo snapshot; only that subdirectory is * declared as the run's extra write root (per-run isolation — Copilot, crew#313). * Default `~/.wicked-crew/interactive-drafts`. */ draftDir?: 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; /** Repo-snapshot size budget in bytes (CREW-UX-8 v4; default ~200MB — see * {@link snapshotRepo}). A repo over budget degrades the launch to ungrounded, narrated. * Tests shrink it to exercise the degradation path without a 200MB fixture. */ repoSnapshotMaxBytes?: number; /** Called after a launch that FILED the run into a project (doc.created 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; /** Called after every successful `adapter.launchRun()` with the run id and provenance detail * (channel, actor from the create-time grounding binding). The server wires this to * `recordRunLaunched` so subscriber-launched runs appear in the audit trail and run DTOs. */ onRunLaunched?: (runId: string, detail: Record) => void; /** The create-time doc → subject-repo bindings the proxy recorded (F-046, `doc-grounding.ts` — * a `crew-grounding.json` sidecar beside the doc's `versions.json`); the server wires the * daemon's shared instance. Absent = nothing was ever named on a create request: grounding falls * back to the brief / sole-member rules. */ groundingStore?: DocGroundingStore; /** The docs root a doc's workspace lives under — where the grounding sidecar is READ (F-046). * Default: the shared-default resolution (`WICKED_INTERACTIVE_ROOT` › `~/wicked-interactive/docs`); * the server wires the per-project `interactiveRoot` setting through here, like the sibling seams. * Only consulted when a `groundingStore` is wired. */ resolveDocsRoot?: (projectId: string | undefined) => string; /** Injectable IO for the draft floor crew re-derives at finalize (crew#621/#504) — tests * substitute the plugin root and the check's answer; the daemon passes nothing. */ draftFloorIo?: DraftFloorIo; /** 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 InteractiveDraftSubscription { stop(): Promise | void; /** The durable ledger (diagnostics / tests). */ ledger: InteractiveHandoffLedger; /** Documents with a draft 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 first draft. */ inFlightDocs(): string[]; } /** * Arm the seam (it registers nothing: the run is the engine's `interactive-draft` preset), open a durable * `wicked.interactive.doc.created` subscription, and answer each `kind: "source"` creation * with a governed run that ends in `wicked.interactive.draft.completed`. * * Graceful degradation mirrors `startQeGateSubscriber`: 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 startInteractiveDraftSubscriber(adapter: CoreAdapter, opts?: InteractiveDraftOptions): Promise; //# sourceMappingURL=draft-events.d.ts.map