You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This commit was created on GitHub.com and signed with GitHub’s verified signature.
Built against wicked-crew-api-types0.38.0 (unchanged since 0.5.11).
System seat cards: opencode provider login + Log out action (#293).not_required seats that carry a login_invocation now offer provider Sign in (the free_tier note demoted to a small secondary line below the row); a new Log out action runs the command-in-terminal through the same terminal modal, with the logout line derived client-side from login_invocation (trailing login→logout, env prefix preserved; underivable ⇒ no button). UI-only — no new daemon route.
This commit was created on GitHub.com and signed with GitHub’s verified signature.
Built against wicked-crew-api-types0.38.0 (unchanged since 0.5.10). FIX-IT-ALL wave 3 for the studio: #290 (L8 — escalation copy table, repo-check classification render, wire-fact readers, full-diff merge-base, one Send predicate, workflow round-trip, System auth rows) · #291 (L5 — the picker reads the daemon's chat_admission, an ok reply is the answer, Retry re-seats a refused seat). Bundled by wicked-crew 0.7.36.
Chat: an ok reply is the answer; the seat picker offers only the seats the daemon would seat; a refused seat gets Retry on the live chat (F-W1-004 / F-W1-005, wave-1 P6 gate; R-L5-2, R-L5-3).
finalizePending: an ok: truechatReply IS the bubble (core-ts ≥ 0.7.27 makes chatReply.text the block after the seat's last tool call — the "Let me explore… Now let me read…" narration streamed as deltas and is not repeated); a NOT-ok reply (an eviction's budget sentence + partial) keeps retainOnFinalize's longer-text rule, so nothing streamed before a cut is lost (E4). On an older engine the reply ⊇ the stream — unchanged.
[+ Add] and the default chips read the daemon's chat_admission verdict for the CURRENT scope mode (crew ≥ 0.7.36 GET /roster; the same predicate its POST /chats pre-filter runs — one source of truth, no client-side copy of the rule; the F-W1-003 decision lands in that verdict daemon-side). Seats the daemon would refuse are not offered; one line under the picker names them with the daemon's reason. A pristine selection re-seeds when the scope mode changes before the first send. Without the verdict (older daemon) the picker behaves as before: every seat offered, the incapable ones labeled "no chat config".
A failed seat chip on a live chat carries Retry → POST /chats/:id/seats (the engine's per-seat ensure in the recorded scope; same chat id, same pool key) — the chip folds the answer (ready / failed with the daemon's reason, narrated), a re-seated seat rejoins the next send's audience; a daemon without the route says so on the chip.
Escalation gates say what happened, from the engine's own class × denying layer (fixall L8-8E(i);
crew #559 / F-RC1-047 = F-RC2-061; DES-L8 r2 §5 PR-8E). One copy table (denialCopy.ts escalationCopy(condition, denialSource, facts)) shared by the narrator feed and the run timeline: boundary_deny × {input_governance, ""} (the hook-veto arm whose source the engine folds away) reads
"a command was refused by governance: <cmd>" — a governance-refused ls no longer reads "tried to
write outside its workspace"; evaluator_mutated_worktree branches on restored (restored + N paths
discarded + the suggestion ref, or "could NOT be restored; inspect the worktree"); dead_seat, floor_failed × {repo_checks, repo_checks_timeout, pinned_validator, substance, deliverables}, verdict_not_pass × {agent_validator, worker_failure, evaluator_verdict} each have their sentence; defGate / outputCaptured become footnotes; an unknown pair keeps today's wording (nothing is
guessed). denialSourceLabel learns repo_checks_timeout, dead_seat, evaluator_verdict.
Repository checks render the engine's head-vs-base classification (D3 of the benchmark review;
api-types 0.38.0 RepoCheckRun.classification/preExisting/regressions).GateFloorCheck carries
the three fields; checkOutcome reads regression as "regression — N new failure(s)" (red) and pre_existing_in_sandbox (or the legacy floor_env_mismatch) as "failed on head AND base —
pre-existing, not this change" (not red); a frame without the field reads exactly as before.
Wire facts that had no reader now render: the unit header gains discipline: <name> §<role> from unitDispatched.baseSkill, with "discipline named only (not handed)" ONLY on handed === false
(absent ⇒ unknown, core #479 / studio #275); sandboxPosture (F-E2E-039) and worktreeRetained
(F-RC1-064) frames get timeline rows; the narrator reads the watchdog's workerStalled.quietForMs
when stalledSecs is absent (no more "quiet for ?s") and narrates workerStallEscalated — "Needs
you — worker silent N min — automatic recoveries spent" (studio #284 / F-BM-006, the minimal live
render; the reload-safe needs-you card needs crew to persist the frames — coordinator to place).
Full diff asks for base=merge-base (fixall L8-8E(ii); studio #244; BC-54).api.getRunDiff
gains a third argument and the run page's Full-diff viewer sends it: committed + uncommitted run work
vs the fork point, instead of the worktree vs HEAD (which showed only uncommitted files).
Send and its line agree by construction (fixall L8-8E(iii); F-089 = F-E2E-035, F-RC2-005/024/045).
The composer's "Ready / Not ready to send" prefix reads the SAME canSubmit predicate that disables
the button. "Revise PR #N" pre-fill (DES-L9 §5): RetryPrefill.revisesPr deposits the PR; the
body carries revisesPr ONLY when GET /health.capabilities.revisesPr === true (else the chip reads
"this daemon cannot revise a PR — upgrade wicked-crew" and nothing is sent), with deliver: 'pr'.
Saving a workflow round-trips skill_ref / allowed_skills / required_deliverables / validator_pin (fixall L8-8E(iv); F-RC1-093). One def→builder mapper (builderPhaseOf) feeds the
editor and the JSON import; buildDef writes the four fields back verbatim instead of nulling them.
System reads seat standing off auth, offers Re-authenticate, and names the daemon's settings
path (fixall L8-8E(v); F-E2E-040 = F-RC2-043, F-004/013/010, studio #280 item 4; BC-56). Seat rows
use the health rail's seatStandingWord fold (signed_in / signed_out / not_required (free tier)
/ unknown); a seat whose own stderr reported the failure (auth_source: 'seat-stderr') reads
"sign-in failed: " and its button says Re-authenticate; the page shows GET /settings.path (crew 0.7.36) instead of a hard-coded home-relative literal, and says when the
daemon does not report it.
Run clocks prefer the run's own record (crew #496 / studio #230; BC-52).runWhenWord takes AgentSession.created_at (the sidebar and the command palette pass it); runEndedWord renders ended_at as "finished N ago" and null for an undated run — never derived from now.
Code Graph tile reads "150 of 5,470 shown" (crew #505 / F-RC1-100 / F-E2E-022; BC-53) from CodeGraphData.totals when the daemon sends it; the slice alone on an older daemon.
Housekeeping: the Testing page's gap hint drops the embedder clause (F-E2E housekeeping, TestingPage).
This commit was created on GitHub.com and signed with GitHub’s verified signature.
The published bundle is built against wicked-crew-api-types0.38.0 — the exact
devDependency pin bumped from 0.37.0 in #283 (the wave-1 train's one api-types release; additive: ChatDetailResponse.messages, UnitDistributedEvent.distinctnessFallback, GateEvaluatedEvent.evaluatorVerdict, the skills unchanged / claude-dispatch / baseSkill shapes),
with both wire mirrors re-vendored from the published index.d.ts. FIX-IT-ALL wave 1 for the studio: #283 · #285 · #286 · #287 · #288 — plus #273, #281 and #282, landed since 0.5.9. wicked-crew 0.7.35
bundles this dist as its default local skin.
Added
Documents root control on the project dashboard (#279). The one lever that isolates a project's documents — interactiveRoot — was API-only. The header's meta region now shows the project's binding (or "the daemon's default root — this project's own partition") with Set… / Change… / Clear, through the new setProjectInteractiveRoot (api/wave6-wire.ts) over crew's existing PATCH /projects/:id {interactiveRoot}; the daemon's refusal is shown at the control and the docs tile re-lists off the new root. The default project is read-only here (the route refuses it).
Fixed
The "Capture learnings" card files its run under the ambient project (FIX-IT-ALL L4-⑩; F-RC1-049 /
F-E2E-015). From a project page the capture-learnings launch now carries projectId: <ambient project> on the existing launchRun wire (LaunchRunBody.projectId, api-types 0.38.0), so
the run lands under /p/<proj>/… like every other launch from that page; from the flat /repos
page no projectId is sent and the run stays Unfiled honestly (there is no repo→project map to
guess from). The button title says "filed under the current project" when it applies.
Skills page: Publish re-reads the engine line, and an unchanged publish says so (fixall L6-4a; F-RC1-017 / crew#547 item 3; DES-L6 r2 §5 PR-L6-4). After an applied Publish the page now calls loadEngine() explicitly: the catalog re-read refreshed the engine line only when it succeeded, so a failed re-read left "generation N" on the previous generation until a page reload. A publish that answers unchanged: true (api-types 0.38.0 — the daemon minted nothing because the tree hashes to the current generation) reads "Unchanged — generation N is still current (…); nothing was re-published." instead of announcing a new generation. The claude-dispatch portability copy (typed ahead of its detector in 0.38.0) is edited to the operator wording the design pins — "invokes a Claude-only tool (Task/Skill/AskUserQuestion) — a dispatch no other seat can follow" — not re-added (review-L8-283 N3).
Seatless-run failure card: headline truncated at (Failed): and "sign a seat in" remedy shown for tool-only failures (F-E2E-014, refs #272). cleanPrompt split on the first [, which in a triage-escalation prompt is the opening bracket of the engine's cause — so the cause was demoted to the collapsed "why this gate fired" disclosure (starting mid-token, since slice(bracketIdx + 1) stripped the bracket) and the headline stopped at (Failed):. The ReassignControl lever rendered for any failure escalation regardless of assigned_cli, so a run that never had a seat was told to retry on another seat / sign one in.
cleanPrompt now keeps the leading [ in the extracted footnote text (slice(bracketIdx, …) not slice(bracketIdx + 1, …)).
SteeringGate skips footnote extraction entirely for escalation prompts (isFailureEscalation): the full prompt — cause included — renders in the headline, which now carries overflow-wrap: anywhere so a long unbroken cause wraps instead of overflowing.
isSeatFailure(escalation, failedCli) (new predicate in gateVerdictModel.ts) gates ReassignControl in both SteeringGate and CenterDashboard: a PROVEN seatless escalation (failedCli === null — the unit is known and has no seat) renders no seat lever; a seat failure keeps the existing lever and Approve label unchanged; a host that cannot resolve the unit's seat (failedSeatOf → undefined: no units passed — the steering-author and testing-launch panels, the landing inbox before the run is loaded) keeps the lever as before, never reading "unknown" as "seatless" (#274, found by the independent review of the first cut).
Chat: the send targets the seat chips minus the seats refused at open, so an evicted seat is re-seated by the next message; chatSeatRefused renders; a rejoin replays the persisted transcript; a reply shows what it cost (DES-L5 wave 1 — studio#277 / F-RC1-114, studio#237 / F-RC1-115, F-RC1-111).
GroupChat posts targets on every POST /chats/:id/messages (client.ts already accepted them): the header's seat chips minus refusedRef — the 201's refused[] + ok: false outcomes, GET /chats/:id.refused on a rejoin, and every chatSeatRefused frame. A seat the engine EVICTED (a turn over its budget, a dropped session) stays a greyed chip and, named in targets, is re-warmed by the engine on that send — was: the warm pool only, so an evicted seat never came back (P6: claude evicted twice, the chat quietly went on with one seat).
NEW case 'chatSeatRefused' in the frame switch: "not seated: ()" as a fail-tone narration line with the seat chip, the chip greyed with the same reason, the seat out of the audience.
Rejoin replays GET /chats/:id.messages (api-types 0.38.0, crew ≥ 0.7.35) as bubbles — turn ordinals by first-seen turnId, the send counter continues — and the boundary note reads "Rejoined — N earlier messages restored"; a seat that spoke but is no longer warm keeps a failed chip wearing its last reason. An older daemon (field absent) keeps today's wording and an empty log.
chatReply.usage (additive; null on pi/agy) lands on the SeatMsg and renders as the bubble's footer (12.3k in · 800 out · $0.04; no $ when costUsd is null; cache counts on the title). The composer carries the static budget copy once seats are warm ("Replies are budgeted per turn; …", no number — the engine's WICKED_CHAT_TURN_SECS is env-only).
Project dashboard DOCUMENTS tile stayed "0 — No documents yet" for any project without its own root binding (#233, F-048; DES-L7 §5 I3). ProjectDashboard returned early from the listDocs effect when interactiveRootOf(project) was null — but the daemon resolves every project's root itself (the binding, else WICKED_INTERACTIVE_ROOT, else the project's own partition of the default root), so a default-partition project's documents were listed by the bridge and invisible on its page. The effect now waits only for the project row.
A recording that fails over the bus is now visible on the storyboard (#278, the live half). video-record-error rendered only the POST's own catch; the thread store already folds the bridge's status.posted {state:"error"} into lastError (live over /ws, or hydrated from GET /api/conversation after a reload), and the storyboard never read it. VideoStoryboard now renders lastError[key] unconditionally beside the request error (same data-testid="video-record-error", data-source="thread"), hidden only while a new attempt is in flight. The persistence half (the owning bridge writing the line) landed in interactive 0.9.3.
Disclose distinctnessFallback: 'creator_seat' beside unit routing and in the run-head note, even when no seats were benched (#276). Include any degraded reason; older daemons with an absent or null fallback retain existing rendering.
Security
site: patch Astro AVIF/SVG advisory chain (#227).site/package.json lifts the Astro
constraint from ^7.1.3 to ^7.2.8; npm resolves to 7.3.2, patching GHSA-26w7-cxv4-gfx2 (Astro < 7.2.8 Sharp/libheif
AVIF RCE). The updated Astro tree also pulls svgo 4.1.0, patching GHSA-w27v-7q3p-w38r and GHSA-4vpr-x523-8j87 (svgo removeScripts
SVG sanitisation bypasses). npm audit reports 0 vulnerabilities after the update.
This commit was created on GitHub.com and signed with GitHub’s verified signature.
The published bundle is built against wicked-crew-api-types0.37.0 — the exact
devDependency pin bumped from 0.36.0 in #270 (the deliver-gate wire: HealthResponse.capabilities.deliverGate, LaunchRunBody.deliverGate, AgentSession.auto_deliver); this cut lands the Archive control (#268, the
harness-delivered fix for #219) and the composer's deliver-gate posture (#269, F-E2E-030).
Fixed
Archive control for terminal runs in the run header and on WorkPage rows (#219, refs #211).
Studio could unarchive a run (crew#265) but never archive one — archiveRun(id, false) was the
wire's only caller, so a terminal run left the active work list only through the API and the
seed-surfaces suite's RUN-ARC journey (#211) had to run [SUBSTITUTE]. The run header of every
terminal run (completed / failed / cancelled, on both /runs/:id and /p/:pid/build/:runId)
now carries Archive (run-archive) in the slot Cancel occupies on a live run: confirm-gated
(run-archive-confirm, Yes / Keep, Escape = Keep), refusals surfaced inline as role="alert"
(run-archive-error) with the confirm held open, and on success the run index refreshes and the
view navigates back so the run leaves the list. Every terminal Work row — the Completed / Failed /
Cancelled groups on the All tab and the filtered Completed / Failed / Cancelled tab views — gets
an inline Archive button (run-archive-row) beside the run, the shipped Unarchive-row pattern;
Active rows and live runs get nothing. Both paths POST /runs/:id/archive {archived: true};
Unarchive is unchanged.
Review findings landed in-wave (independent review of #268, M-1 / L-1 / L-2): this entry; the
header offers no Archive on an already-archived run (session.archived_at set — reachable
through the Archived chip), so there is nothing to re-archive; and the WorkPage tests now cover
the filtered tab views and the Failed / Cancelled groups, not only Completed.
The composer promised a push with no gate (acceptance finding F-E2E-030). Under the default
"First gate" posture the deliver notice read "When this finishes it pushes its branch → opens a
PR" — and that is exactly what run 0ab5ccb8 did, unattended. The engine now gates the deliver
phase by default (wicked-core#456) and crew accepts deliverGate: 'human' | 'auto'
(wicked-crew#543). The composer says WHEN the push happens: the default postures read "pauses
at the deliver gate; approve it and the run pushes its branch → opens a PR on ", the
confirm line gains deliver: after you approve the deliver gate (launch-confirm-deliver), and
the body sends NO opt-out. Only the explicitly unattended postures — Autonomous, or the gate
option now labelled "No gates · auto-deliver" — send deliverGate: 'auto', and their notice
says "with NO deliver gate — this posture is auto-deliver". The intake plan's deliver row names
the gate from session.auto_deliver ("human gate before it pushes its branch + opens the PR" /
"auto-deliver — … no gate"; intake-plan-deliver-gate) and stays silent on an engine that
predates the gate, so no promise is made that the engine cannot keep. The composer makes the
same promise only when the DAEMON can keep it: it reads GET /health.capabilities.deliverGate
(crew ≥ 0.7.33) and, against a daemon without it, says "this daemon delivers WITHOUT a deliver
gate (upgrade crew to 0.7.33+ to confirm the push first)", offers no auto-deliver option and
never sends deliverGate (the older launch schema rejects it). "No gates" is labelled
auto-deliver only where the select is honoured (not in Ask mode, where every unit is gated).
Changed
Pin wicked-crew-api-types 0.37.0 (the deliver-gate wire, crew#543) and re-vendor both wire
mirrors from the installed index.d.ts. Built against api-types 0.37.0: HealthResponse
(GET /health, previously undeclared) with capabilities.deliverGate, LaunchRunBody.deliverGate: 'human' | 'auto', and AgentSession.auto_deliver — the three shapes #269 hand-declared as
"≥ 0.37" in src/api/client.ts (getHealth) and src/api/types.ts (LaunchBodyWithDeliver) now
come from the package, and tests/deliverGateWire.test.ts pins them to the installed index.d.ts
(compile-time satisfies + the declaration lines) so a pin that loses them fails the suite. 0.37.0
is ADDITIVE and touches neither the skills block nor the wave-6 shapes: all 16 VERBATIM regions of src/api/skills-wire.ts (+ its fixture, now tests/fixtures/api-types-0.37.0-skills.d.ts; index.d.ts:1995-2445 / 4720-4779) and src/api/wave6-wire.ts are byte-identical to 0.36.0 and
relabelled by line range only (+26 above the LaunchRunBody addition, +38 below it).
This commit was created on GitHub.com and signed with GitHub’s verified signature.
The published bundle is built against wicked-crew-api-types0.36.0 — the exact
devDependency pin, unchanged from 0.5.7 (#264); this cut lands the fix that reads the test_sets
that wire actually declares (#266).
Fixed
The Test landing reads the daemon's top-level test_sets (api-types 0.36.0) — counts, PLAN and
PR per produced set. The 0.5.7 landing (CampaignsPage, the Home "Test" door, campaignStats)
read a PROVISIONAL row-level Campaign.test_set / RunGroup.test_set join that 0.36.0 never
declared, so against a 0.36.0 daemon no card showed a produced set. The daemon serves the sets as CampaignsListResponse.test_sets: TestSet[] (snake_case, run_id-keyed, tagged with the qe-tests-<repo> label an authoring run is filed under); listCampaigns now normalizes them
(testSets: null = a pre-0.36 daemon — absence, never a fabricated zero), the store holds them,
and the fold joins them onto each campaign / label-group card by run_id (and by label for a
group). Each set renders its verified chip, produced · executed · passed · failed (plus
"· N not executed" when the verify phase left tests unrun), the PLAN (opens the producing run)
and the engine's PR (isPrUrl-gated); the Tests tile's context and the Home door append the
registered sets once the wire carries them. The provisional join, its testSetOf row reader and
the dead POST /testing/recon + workflow ladder rung (qe-author-tests shipped together with POST /testing/author, so no daemon lists the workflow without the route; 0.36.0's TestingReconBody declares no workflow key) are deleted — tests/wave6Wire.test.ts now guards
that neither shape returns. Fixtures (tests/fixtures/wave6.ts, e2e/uxfix_fixture.py) serve the
real 0.36.0 shape; the testid inventory gains campaign-card-testset-{verified,pr,more}.
Review findings landed in-wave (independent review of #266, F-1..F-4, R2-1): the Tests
tile's context now LEADS with the sets word — painted as 1 set · 11/11 passed, sized to clear
the … glyph at 1440 px, with the unabridged 1 test set · 11/11 passed … line as the span's
hover title — the redundant "N ad-hoc group" word is gone, and every StatTile context
carries its full text as title (stat-context; a contextTitle prop when the painted line
is an abridgement); sets no card can show are said as · N unattributed and test_sets rows served without a run_id as · N malformed (never
folded away — listCampaigns now returns malformedTestSets); a 0.36 daemon's real zero
renders as no test sets registered yet on the tile and N tests · 0 test sets on the Home
door, while a pre-0.36 daemon still says nothing about sets; the launch panel reads /testing/author's runs[].label and says filed under qe-tests-<repo> on the Test landing — the set fills in when the verify phase registers it (testing-launch-filed-label) instead
of "appears … when the run registers its test set". The Chrome rig asserts the sets word is
VISIBLE (glyph box inside the context span), not merely present in textContent.
This commit was created on GitHub.com and signed with GitHub’s verified signature.
The published bundle is built against wicked-crew-api-types0.36.0 — the exact
devDependency pin on this cut (#264), and the wire the bundle's mirrors and satisfies checks are
typed against: both wire mirrors (src/api/skills-wire.ts, src/api/wave6-wire.ts) are byte-pinned
to it. Two wire gaps (a row-level Campaign.test_set / RunGroup.test_set join; TestingReconBody.workflow) stay studio-worded and test-guarded — see Changed below.
Added
New test launches the governed qe-author-tests workflow; the Test landing shows the produced
set; honest UNGATED / degraded gates; a files view once the worktree is gone (wave 6 — the
governed testing journey: acceptance findings F-075 / F-076 / F-7R2-003 / -005 / -006 / -008 /
-009 / -010 / -011 / -012 / -013 / -014 / -017, studio half). Develops against a PROVISIONAL wire
mirror (src/api/wave6-wire.ts) spelled exactly as the wave-6 briefs name the fields; every
reader is null-safe, so an older daemon changes nothing. tests/wave6Wire.test.ts pins the
posture: a pin bump to ≥ 0.36.0 without a VERBATIM re-vendor fails the suite (the #257 pattern).
Routes (F-075 / F-7R2-009): /testing and the retired /testing/harness land on the TEST
landing (/testing/campaigns), as src/api/testing.ts documented all along; Evals keeps /testing/evals as a sub-page; the rail heading follows. Home's "Run recon" verb is now New test and opens the launch panel (?new=test). "Add with chat" on the landing is
labelled Add testing rules — it authors testing STEERING RULES, not tests.
New test is governed (F-7R2-003 / -004 / -012): the panel reads GET /workflows on mount;
a daemon that lists qe-author-tests gets the governed launch (the chip names the five
phases: recon → author → verify → review → deliver — the ENGINE's deliver phase opens the PR,
never the worker), and launchGovernedTest walks the wire ladder, each step only when the
previous wire is ABSENT: POST /testing/author → POST /testing/recon + workflow (a strict
schema naming it unrecognized ⇒) → one POST /runs {workflow, repoRef, projectId, humanConfirm: 'before:1'[, groupLabel]} per resolved repo. A daemon that lists no such
workflow shows the honest banner "this daemon has no governed test workflow — plain run"
BEFORE the launch and takes today's free-text recon. Every named refusal surfaces untouched.
Project chips are droppable (F-076 / F-7R2-010): "attach the project, drop repos". A
narrowed project launches one POST /runs per remaining repo — repoRef scopes, projectId
FILES — so an explicit single repo keeps its project_id (the pinned recon body's projectId
would union the dropped members back in); the dropped line names them and offers "restore
all"; every member dropped is refused on the button, before any wire call.
After launch (F-7R2-011): the panel LINKS every launched run (testing-launch-fanout-run,
the single run too), names the workflow, the wire it rode (testing-launch-route) and the
test/group label, then the waiting line; the intake gate arrives on the app's one /ws fold.
The intake card shows the PLAN (F-7R2-008): on the pre-run gate for the run's first unit —
the panel's copy of the card and the run page's — IntakePlan lists every planned phase with
its executor (agent / tool), skill, writes code, evaluator ≠ creator, and seat (the one
routed, else "council picks from "), read once off GET /runs/:id when the gate arrives.
The Test landing shows the produced set (F-7R2-014): a card's test_set (the campaign
registration a completed run lands) renders "N test files · T tests · E executed · P passed ·
F failed" — the counts the VERIFY phase re-derived, with "K never executed" when executed < tests — plus the PLAN path; the workflow chip reads qe-author-tests off the live
runs from launch. A pre-0.36 row renders no counts (absence, never a fabricated zero). The
empty state says what fills it.
Honest UNGATED gates (F-7R2-005 / -017): gateEvaluated.ungated / ungatedReason win over
the card's fold — the gate card reads "UNGATED — no eligible judge seat" (the floor that DID
run is still listed; the judge axis is said not held), the run-page verdict card carries the
same line, and the narrator says "Gate UNGATED on — ; repository checks ran,
no distinct judge" — never "Checks ran — pass" for a judge-less gate.
Degraded councils (F-7R2-006 / F-4R2-007): unitDistributed.degradedReason renders on the
run head (run-degraded: "council degraded: 4 of 5 seats benched: …", with the affected-unit
count) and on the routing line in the feed. The narrator now reads the camelCase agreementPct the engine actually emits (api-types ≤ 0.35.0 declared agreement_pct, which
the wire never carried — so the pct was always missing); the snake_case read stays as the
fallback until the 0.36.0 pin declares camelCase.
The remote-write fence (F-7R2-012): a workerToolCallDenied (a creator/evaluator seat's git push / gh pr create …) renders in the feed with the seat, role, the refused command as
code and the remedy — the engine's, or "delivery is performed by the run's deliver phase".
Files view once the worktree is gone (F-7R2-013): GET /runs/:id/diff answering source: "branch" is labelled as the run branch vs its base (committed work shown; an empty
branch says so); a pre-0.36 daemon's 409 cause card names the run branch and the upgrade. The
run page's Files section offers Full diff on EVERY state — the empty one included, which
was exactly the completed run with no files view.
Tests: the launch ladder (22), the panel (14), the gate model + cards (9), the narrator (13),
the files view (6), the landing card (8), the run head (6), the mirror posture (7); the
Playwright loopback rig e2e/governed_testing_test.py (fixture switch governed_testing / governed_testing_workflow_absent: GET /workflows, POST /testing/author + the intake gate
over /ws, GET /campaigns with test_set, the completed run's degraded / UNGATED / refused-write
trail, the branch-source diff) at 1440x700 and 400px; testid inventory regenerated. Wire gaps
recorded for the crew PR: the recon body's projectId cannot express a narrowed project; the
campaign registration shape (test_set) and the /testing/author route are provisional names.
Pin: wicked-crew-api-types0.35.0 exact (crew#533 — published while this landed; 0.36.0,
the wave-6 wire, was not). The skills mirror + tests/fixtures/api-types-0.35.0-skills.d.ts are
re-vendored by label (the 0.34.0 skills and diagnostics.skills blocks are byte-identical in
0.35.0, shifted to index.d.ts:1768-2186 / 4223-4272); two additive catch-ups — the Health
rail names the widened info finding severity, the wave-2 fixture's legacyOutbox carries the
new required scope. The wave-6 mirror stays PROVISIONAL under 0.35.0 (none of its names are
declared there — tests/wave6Wire.test.ts asserts exactly that).
Skills page recovery from GET /skills 503 (acceptance findings F-A45-001 HIGH / F-A45-002
MEDIUM — the F-083 stale-rules refusal "current does not point at a valid published snapshot …
re-publish or remove the link"). The unavailable card used to offer only Refresh (a second 503);
the remedy the finding names was unreachable. It now carries the engine's word — GET /diagnostics → skills.state + every findings[] message (skills-recovery-finding) — and two
controls with pending/result states: Refresh baseline (POST /skills/refresh-baseline) and Publish (POST /skills/publish). Every mutation is CAS-guarded by the revision the 503
withholds, so the page learns it through POST /skills/analyze (the dry run reads the MANIFEST,
not current) and says so when analyze 503s too (the manifest itself is unreadable — the
daemon host's job). After a Refresh the baseline is STAGED and the catalog still answers 503
until Publish: the result renders inline ("garden 12.33.0 staged (… taken · kept · added ·
removed · conflicts) — publish to activate"), the engine line is re-read, the catalog is NOT
(F-A45-002). A Publish that writes a snapshot re-reads the catalog and flips the page to the
loaded state with the note; a blocked publish renders its findings on the card. A 409 says the
catalog moved and re-learns the revision on the next click.
One roster story on the composer and the rail (F-A45-006 studio half). The composer's seat
warning derived from the signed_in file/env heuristic alone, so it said "codex + opencode
aren't signed in" while the Health rail — reading crew#533's auth / council_eligible / free_tier — showed opencode green "no sign-in needed". Both now read the rail's seatStandingWord: auth: not_required is never a sign-in problem, auth: signed_out warns
even when the heuristic is null, a daemon-declared council_eligible: false gets its own
sentence with the daemon's reason (ineligible-warning); a pre-0.35 roster keeps the heuristic.
/vibe and the Home door count what the daemon serves — without spawning a bridge per project
(F-A45-008 MEDIUM, bounded by the independent review of #263, F-1/F-2). The corpus listed only
"projects opened this session" (the docs cache's deposits), so a fresh browser on a daemon holding
three documents read "DOCUMENTS 0" and Home said "Vibe 0 documents". A per-project docs GET
(GET /projects/:id/interactive/api/docs, the only per-project route) MATERIALIZES the project's
partition and cold-starts one wicked-interactive bridge (~60 s) — so NOTHING fans out on mount.
The one request the corpus su...
This commit was created on GitHub.com and signed with GitHub’s verified signature.
The published bundle is built against wicked-crew-api-types0.34.0 — the exact
devDependency pin on this cut, and the wire the bundle's mirrors and satisfies checks are typed
against. The crew#533 roster fields named below (auth / free_tier / council_eligible / council_ineligible_reason, api-types 0.35.0) are read defensively when a daemon sends them;
the pin itself moves in a later release.
Added
Reassign to + retry on a failure-escalation gate (phase7-r2 acceptance finding
F-7R2-007, HIGH). At every "Unit N failed and triage escalated" gate the card offered Approve — a
retry on the SAME dead seat — Approve + steer, Reject and Cancel; recovery was POST /api/v1/runs/:id/reassign {cli} by hand, five times, racing the re-dispatch window. Both
gate cards (the run page's SteeringGate, the landing inbox's card) now carry ReassignControl:
the run's OTHER seats (session.clis minus the seat that failed the unit) with the roster's word
on each — signed-in first, a seat with no sign-in observed hedged as "may fail or be benched"
(today's roster carries no council-eligibility field; crew#533's auth / council_eligible / council_ineligible_reason / free_tier are read when a daemon sends them — seatStanding),
inactive last, daemon-declared ineligible after that — and one action that approves the retry
(the steer text rides it), waits for the run to resume (GET /runs/:id until executing, 30 s
bounded — the daemon reassigns only an executing run), then calls the existing POST /runs/:id/reassign {cli} (api.reassignRun). Every step is stated
(steering-reassign-status); a refused reassign leaves the approve standing, shows the daemon's
sentence and offers the reassign alone again. Plain Approve is relabelled "Approve (retry on
)" on that gate. A host without the run view (the steering-author and testing-launch panels)
reads the run once for its pool on a failure escalation only. Recorded on the steering timeline as reassign. (Wire gap, recorded: the reassign route refuses an awaiting_human run, so the approve
must precede it.)
The wicked-core#431 wire on the gate, the delivery card, the run head and the feed (#250/#431
consumer follow-through — pins wicked-crew-api-types 0.33.0, the wire wicked-crew#527 publishes).
Every field is read off the frames the daemon sends and rendered only when present, so an older
daemon changes nothing.
Gate card — the judge seat (gateEvaluated.judgeCli / judgeDistinct): the verdict header
names WHO judged (· judge: codex); judgeDistinct: false adds a same-seat warning — the judge
fell back to the single default runner, so evaluator ≠ creator is not held on that verdict.
Denial card — the restored tree (evaluatorMutatedWorktree.restored + worktreeRestored):
a worktree-guard denial the engine already remedied says "the evaluator's edit was discarded and
the creator's verified tree restored", lists the discarded paths from the restore record, and
gives git show refs/wicked/suggestions/<run>/<ord>/<attempt> as copyable code — with a real,
keyboard-reachable copy button beside it — when the edit was pinned (an honest "not pinned"
otherwise). Approve is relabelled "Retry against the restored tree" (and "Retry + steer") on
exactly that gate, on the run page's gate card AND the landing inbox's card, from one predicate
that mirrors the engine's own guard (denial.source === 'worktree_guard' and restored) — keyed
on the evidence frames, not on the prompt, which the engine also changed ("confirm to retry the phase" → "Approve to retry the phase
against the restored tree"; the card's NOT PASS match holds for both spellings). A failed
restore (restored: false) is said, with the engine's error, and the manual remedy stands.
Delivery card / deliver gate — the lift (deliverLiftEvaluated, the deliver ord's repoChecksEvaluated, the deliver unit's deliver: refusal): the rail's Delivery body and a
gate opened on the deliver unit render what the pre-push lift did — unchanged / lifted
(base and tree before → after, the re-verify per check with the forced-install source package-lock.json (forced: lockfile drift)) / conflict (the files, "nothing was rebased and
nothing was pushed", the LIFT-CONFLICT remedy) / skipped / failed — and the engine's
refusal as the wire carries it (stepFailed.detail is a head+tail excerpt; the elision marker
renders dimmed between the kept words), once: the gate card omits its copy when the engine's
triage-escalate prompt already quotes it, the rail when the rejected unit's framed denial_reason does. A red check exposes its recorded stderr / stdout tail (RepoCheckRun,
declared since 0.31.0) as a collapsed, monospace, phone-width-wrapping block — on the deliver
lift and on the gate card's floor. A deliver unit refused BEFORE the lift (a HEAD off the run branch)
has no lift frame and renders its deliver: text on its own; a passed: false re-verify over
all-green rows is explained as the checks having CHANGED the worktree.
Run head / timeline — the base (runBaseResolved): a base row on the run's context card and
a based on head row on the evidence timeline — "origin/main @ f57069d · 5 behind · lifted to
the tip" — plus timeline rows and detail panels for the restore, the lift and a refused write.
Feed: narration lines for the run base, the creator-tree restore, each lift outcome, a refused
write-class tool call (evaluatorToolCallDenied) and the one deliberate ACP reroute
(acpFallback.fallbackKind: 'read_only_requires_wrapped' — routing, not a failure); every other acpFallback kind stays silent as before.
Tests: unit suites over synthetic frames in the wire's exact spelling (tests/fixtures/wire433.ts,
mirroring wicked-crew's wire-contract literals) for each surface — every frame declared satisfies
its 0.33.0 named type, and tests/wire433.shapes.test.ts re-derives the key-set and union diff
against the installed index.d.ts at run time; the deliver fixtures carry what the WIRE carries
(stepFailed.detail as the engine's 150/250 head+tail excerpt, the triage-escalate prompt quoting
the 450/750 excerpt, denial_reason framed as Worker FAILED on unit N …); the loopback rig e2e/wire433_test.py (fixture switch wire433) drives the three surfaces in a real browser.
Changed
Skills page: a badge per KIND of portability reason, and the claude-only KPI split (#256,
F-079 — pins wicked-crew-api-types 0.34.0, whose additive SkillEntry.portability {portable, reasons[], evidence?[]} is the publisher's per-reason verdict, crew#531). The one claude-only badge lumped "the author used a Claude-only path" together with "this skill needs
the Claude harness" and hid the fix. Now any AUTHORING reason (plugin-root, skill-dir-var, cwd-script, relative-link, cross-skill-path) renders not portable
(skills-not-portable-badge; the hover title lists the reasons and the first file:line
anchor, and is the badge's accessible name) while requires-harness:claude alone renders needs Claude harness (skills-needs-claude-badge; title = the reason). The wrapper keeps skills-claude-only-badge for one release so existing selectors resolve; a daemon that predates
the field (no portability) falls back to portable alone — the generic not portable badge
with the previous sentence, never a fabricated reason. The Portable tile's context reads N not portable · M need Claude harness (the value stays the portable count); the chips not-portable + needs-claude replace claude-only; the drawer gains a Portability line —
every reason with one clause of "why", and every file:line anchor as monospace text that wraps
at phone width. skillCounts gains notPortable / needsClaude (portable + notPortable + needsClaude === total). The wire mirror (src/api/skills-wire.ts) and its parity fixture move
to the 0.34.0 block (picking up the 0.29.0 installer-copy source kind and the skills.source / skills.manifest diagnostics findings the 0.27.0 mirror lagged). The Reach KPI group is as wide
as the two-tile groups so the context line never ellipsizes, the badge's ink is --ink-high on the
amber fill so it reads in both themes, and the page header + verbs wrap at phone width. Review
follow-through: a non-portable analyze/publish finding wears its portabilityReason as a chip
(hover = the reason's one clause), and a portability verdict that disagrees with portable is
named — data-contradiction on the row and badge, a hint line in the drawer — never swallowed and
never a different badge (portable stays the admission key).
Export bar — readiness per format (F-4R2-016): ExportMenu looked up the FIRST ready answer for
the version, so an un-consumed HTML download shadowed the PDF that finished after it — the PDF
button spun for 120 s while the file already sat in the thread. Each format button now asks for
its own (version, format) answer and flips the moment its own reply lands. The bridge's additive
layout report (wicked-interactive#219: layout, layout_source, page_size, pages) is read
null-safely off the export response AND the export.generated echo and rendered where present —
"PDF ready — 2 pages · A4 portrait" under the row, on the anchor's hover text, and on the thread
line; an older bridge that sends none of it changes nothing.
Heartbeat narration (F-4R2-005): crew's seams re-emit the current phase's line every ≤15 s, so
one draft read as 39 narration rows ("Crew phase 2/3: wr...
This commit was created on GitHub.com and signed with GitHub’s verified signature.
Added
Wave-2 consumers: the repo card renders the engine's findings, the Health rail renders diagnostics.governance, and New Chat carries a scope control (#251, #246, #248 — pins wicked-crew-api-types 0.32.0, the wave-2 crew release).
Repo findings (#251, wicked-core#406 via crew#517): RepoEntry.findings[] renders on the
Repositories fleet card, the repo detail header and the project page's repo rows — one labelled
row per finding with the engine's own message. An in-tree .codegraph/ beside a live graph is a
tidy-up warning; an in-tree graph with NO live graph (the F-024 checkouts after the upgrade) is an
error whose row carries Re-run onboarding, wired to each surface's existing onboard trigger
(POST /repos/:id/onboard); code_graph_root_unresolvable is an error naming the daemon-
environment fault. Silent for a clean checkout and for a daemon that predates the field.
Governance in Health (#246, crew#495 / F-022): expanding the rail's Health section also reads GET /diagnostics and renders the governance block — the store path and which rule chose it,
the record counts (an honest "engine cannot count" for null, never 0), the dead-letter fold
(count as a floor when truncated, by type / by reason, the timestamp range, the outbox, the
pre-fix HOME outbox) and every finding as a severity-styled row whose message carries the wicked-crew governance replay … recipe. A store: null boot and any error finding turn the
heart red (and show the collapsed-header dot) and degrade the home board's Governed tile
(fail-coloured, the reason underneath — never a clean percentage over evidence that is not
landing); a warning degrades the heart to amber. A daemon without the block, or without the
route, reads "not reported by this daemon".
Scoped New Chat (#248, crew#502 / F-067): the create window carries a Scope control — All project repos (the default once a project is bound: projectId alone rides the open and
the daemon scopes to every crew.repo member), Choose repos… (a multi-select from GET /repos, loaded on that gesture; sends repoRefs by id) and Unscoped (an explicit click).
An Unfiled chat with no choice does not open on send — the gap is stated on the row, nothing is
posted, the draft stays. A scoped open with the default (untouched) seat chips omits clis, so
the daemon admits only governed seats and the 201 re-seeds the chips; an edited selection rides
as asked and refused seats say why. The opened chat states ChatOpenResponse.scope under the header: the
repositories (names; paths on hover), read-only, whether a code graph grounds the seats and the
daemon's reason when not, and any project member the registry no longer knows; a rejoin states ChatDetailResponse.scope, and a daemon that said nothing is reported as "not stated". The
route's refusals render as inline sentences by status — 404 (every missing ref named), 400
(an ambiguous name → name it by id), 409 (a daemon-side conflict), 501 (the engine predates chat
scope, with a Continue unscoped fallback that mints a fresh unscoped chat outside the shell).
Fixed
Gate cards state the evaluator verdict they are asking about (#250, acceptance finding
F-3R2-006 — the UI half of wicked-core F-036/F-039). The verify pre-run gate ("Approve unit 4
before it runs") and the deliver gate showed only their prompt while the fix phase's PASS —
criterion, deterministic floor, the judge's reasoning — sat in the already-hydrated event log;
the DENIED gate said "Unit 4 verdict is NOT PASS — confirm to retry…" while the reason
(evaluator≠creator, the changed path, the restore command) was only in an expandable thread
line. SteeringGate now renders a gate-verdict block from the run's own gateEvaluated (the
last one at or below the gate's ord), with the F-039 repoChecksEvaluated floor per check
(name · exit code · duration · manifest source, plus what was skipped) and the F-036 evaluatorMutatedWorktree record (seat, phase, changed paths, tree ids) attached from the SAME
fold — a retry's verdict never inherits the previous attempt's evidence. A denial names the
layer (denial.source: worktree guard, repository checks, …) and quotes the engine's reason
verbatim, backticked commands rendered as copyable code. An ungated phase is labelled a
default-allow, never a pass (FINDING-025); no evaluation yet ⇒ no block, never a verdict
fabricated from the prompt. Zero new requests. wicked-crew-api-types 0.30.0 → 0.31.0
(purely additive: UnitDenial, GateEvaluatedEvent.denial, RepoChecksEvaluatedEvent, EvaluatorMutatedWorktreeEvent). The judge SEAT is not on this wire, so the card claims none.
The landing's delivery strip no longer counts onboarding runs as "Vacuous — needs retry"
(#250, F-3R2-018). The daemon stamps delivery: 'vacuous' on every completed repo-scoped run
whose worktree is untouched — the DESIGNED outcome of onboarding and the other system
workflows — so a fresh install read "9 Vacuous — needs retry" and buried the one real signal. deliveryCounts (shared by the strip and the KPI ribbon's Review tile) now licenses the
vacuous bucket with the Delivery section's own canDeliver rule — a deliver unit on the run, or
a workflow positively known not to be a system one (is_system, the one budgeted GET /workflows) — and the cell reads "Vacuous — no change to deliver": the condition, not a
prescription. The licence can only withhold a count, never invent one.
.codegraph/estate.db is no longer tracked (#220). A fresh clone shipped the operator repo's code-graph
identity, so onboarding the clone failed with REPO COLLISION (and an older wicked-core wrote into the
tracked file); the graph is per-checkout, built by wicked-estate index under the daemon state home
(wicked-core#406). The file is untracked (local copies are left on disk) and .codegraph/ is ignored.
This commit was created on GitHub.com and signed with GitHub’s verified signature.
Fixed
Document thread — bare status frames file under the doc's MOUNTED thread (acceptance finding
F-045, belt and braces). Crew's own interactive seams narrated their governed runs with document_id alone, so every heartbeat was filed under the Unfiled mount while the project-bound
thread heard nothing and, 90 s into a live run, showed "no worker has picked this up — the
generation service may be down" with a Retry that would have injected a duplicate. Crew now stamps project_id on every seam emit (wicked-crew, F-045); independently, docThread.ingest files a
frame that names a doc but no project under the project a DocumentThread is currently MOUNTED for
that doc (bindDoc/unbindDoc, registered by the component — never inferred from retained
history, which a previous same-slug thread would poison). The composer claims the doc for its
project the moment the create is SENT — under the bridge's CANONICAL id (docSlug, the bridge's
own DOC_NAME + slugify rule replicated byte for byte and pinned against observed ids), which
is what every frame carries — as a pending binding the mounting thread adopts (released on a
refused create), so a frame that beats the bridge's answer files on the project thread; a frame
with no binding at all is HELD and released exactly once onto the thread that binds, expiring to
Unfiled only when nothing is bound for 10 s (never while a pending create or an ambiguous pair
is open; an unmount that leaves one thread releases the held frames to it). The stall banner
therefore appears only after a genuine 90 s silence.
Added
Launch composer — what the document (or demo) is ABOUT and in what format (F-046, studio half). DocSubjectPicker offers the project's crew.repo members by name as toggles on BOTH the Document
and the Video launch composer (sent on the create as repo_refs; crew validates them against the
project and grounds the governed draft/demo run on THOSE repositories instead of the project's first
member — the demo wizard carries them through demoDraftBody) and the bridge's four formats on
both composers (sent as style, the demo flow included; "from the brief" sends nothing and lets
crew infer it from the brief's format words, so a print/A4 brief reaches the bridge's print
instructions). Discovery has visible
loading / error states with a retry: the composer refuses to submit while the repositories are
unknown, unless the user explicitly chooses to create without repository grounding — which the
thread then records. The picks reset when the launch context changes and after a create.
Create body typed from the shared wire declaration.CreateDocBody is now wicked-crew-api-types 0.30.0's InteractiveDocCreateRequest (and DemoStepDraft its InteractiveDemoStepDraft) — no local mirror to drift. wicked-crew-api-types is pinned to 0.30.0 exactly; the package publishes from wicked-crew on the F-045/F-046 merge.
This commit was created on GitHub.com and signed with GitHub’s verified signature.
Fixed
Launch composer: a multi-repo project must be told which repo a build run works in (F-028).
Choosing a project auto-attached every one of its repos as chips and the launch body took repoRef = repoRefs[0], so an explicit tick on the repo the operator meant was silently outranked
by whichever project member happened to be listed first — acceptance run 1f12f9ab dispatched a
studio bug fix into wicked-core with four councils voting before it could be stopped. The chips are
now context: the one repo the run works in is derived by resolveLaunchTarget
(src/components/launchTarget.ts, the single definition the Send guard, the wire body, the deliver
notice and the pre-send summary all read) — Target-repo choice > explicit popover tick > the lone
attached repo > for build-kind work with several candidates ambiguous, no default (a
required launch-target-repo select with launch-target-reason; Send disabled, Cmd+Enter fires
nothing; the deliver notice reads no-target); non-build launches keep the first repo as context, as
before. Auto-attached chips keep their (from project) marker after a tick (data-auto-attached
is now per chip) and the target chip carries data-target="true". The deliver notice names the
repo the PR lands on — → opens a PR on owner/repo off the registered git_url, the registered
name otherwise (repoSlugOf; data-deliver-repo). A pre-send confirmation step (launch-confirm with launch-confirm-workflow / -target / -gate; data-workflow / data-target / data-gate) reads workflow + target repo + gate posture before
Send. The operator's latest act stands: a tick made after a Target choice wins ("select A,
then tick B" sends B), and removing the chosen repo drops the choice. The popover's gate select is
now launch-gate (its former launch-confirm testid names the confirmation step). Wire unchanged
(LaunchRunBody.repoRef). Tests: tests/launchTarget.test.ts, tests/ChatInput.target.test.tsx; seed suite: LNCH-T (e2e/seed_surfaces_test.py, authored,
not yet executed — see docs/testing/seed-surfaces-plan.md §3).
Run page: Cancel run for every non-terminal status, outside gates (F-029). A run in distributing had no cancel anywhere on /runs/:id — steering-cancel lives inside a gate card
that had not opened, and the header's stop control was an unlabelled icon that read as decoration —
so a mis-bound run burned seats until the operator hit POST /runs/:id/cancel by hand. The header
now carries a labelled run-cancel button for planning / distributing / executing / awaiting_human;
it asks first (run-cancel-confirm: run-cancel-yes / run-cancel-keep, Escape keeps), speaks the
wire directly (api.cancelRun) and refreshes the run index; a refusal stays on screen
(run-cancel-error, role="alert") instead of being swallowed. Terminal runs offer none. ChatPanel no longer
takes an onKill prop (the header speaks the wire itself; the Ctrl/⌘+Shift+K shortcut and the
palette verb are unchanged). Tests: tests/ChatPanel.cancel.test.tsx; seed suite: RUN-CXL
(observed at TST-1's gate on a second page; authored, not yet executed).