15 Commits

Author SHA1 Message Date
open-design-crew[bot] c8f6db60c0 perf(od-next): streamline tool work and reinforce protocol output (#7804)
* perf(od-next): streamline tool use and media input prompts

* fix(od-next): drop the runtime-state literal from the core tool rule

The v2.1.1 core prompt named `open-design-runtime-state` inside the
"not a tool name" bullet. In the two post-optimization eval batches the
model called that exact name as a tool in 5 of 29 deliveries (1 of 30
before the sentence existed): a negated mention raises the name's
salience right where the model is about to emit the runtime-state block.

Keep the rule, drop the literal: protocol blocks are structured text
output, not tools. Bump core asset to 2.1.2 and the bundled package to
2.0.2; orchestration is untouched and stays 2.0.1.

* fix(od-next): carry protocol output guidance into tool optimization

* fix(od-next): preserve run workspace scope in dynamic skill reads

---------

Co-authored-by: bone3deep1962-collab <bone3deep1962@gmail.com>
Co-authored-by: NJUHua <113895241+NJUHua@users.noreply.github.com>
2026-09-08 11:48:16 +00:00
open-design-crew[bot] f1a73f0d89 feat(od-next): structure-only layout primitives for the prototype task profile (#7327)
* feat(od-next): ship structure-only layout primitives with the prototype task profile

Blind evals and the pet-grooming runs showed the same layout defects over and
over — stacked spans that never became blocks, chrome height constants reused
as padding, authored copy crammed until it wrapped — while the prose rules that
forbid them sat unchanged in the prompt. The rules are now backed by something
the Build can copy instead of re-derive:

- `assets/task-profiles/prototype/layout.css`: a ~50-line, structure-only
  stylesheet (`@layer od-layout`, `:where()`, `od-` prefix) for stacked
  information, truncation/clamp of data text, media ratio, rails, and an
  optional screen skeleton. Declared as a prototype resource, staged into
  `.od-frames/` under the existing ownership manifest, and quoted into the
  stable request context as the `layout-primitives` fact on every prototype
  run.
- Prototype rule card 2.2.0: a "Variable-length text and stacked information"
  section that separates authored copy (rewritten to a budget, never
  truncated) from data text (truncate/clamp in lists, wrap in full on
  confirmation and detail screens), keyed to the real class names; the quality
  floor's layout bullet now points at the primitives.
- Core 2.1.0: the variable-length-text paragraph rewritten as actions
  (three fates before the text is written) instead of prohibitions.
- Run-finish observation `od_next_layout_primitives`
  (verbatim / modified / linked / absent) so the rollout can tell whether a
  model tier reproduces the block faithfully enough for inlining.

* fix(od-next): let aspect-ratio win on .od-media and pin two rule-card gaps

The first verification runs exposed one primitive bug and two recurring model
slips: `.od-media` lacked `height: auto`, so an `<img height=…>` attribute
kept its pixel height and the ratio never applied (tall cropped thumbnails);
cards built on `<a>` shipped with browser-default link styling; and product
tokens declared on an inner wrapper left sheet/dialog overlays mounted in the
shell's screen without a surface. The stylesheet and the rule card now say so.

* fix(od-next): keep .od-media at its intrinsic ratio; make cropping an explicit opt-in

Review: the fallback aspect-ratio of 1 plus an unconditional object-fit:
cover turned every unset .od-media into a square crop — contradicting the
core image-geometry rule (content-bearing images render their full frame;
cover is reserved for deliberate crops) and the primitive's own comment,
exactly on the media the rule card points at product shots. The primitive
now defaults to aspect-ratio: var(--od-ratio, auto) with no object-fit;
a uniform tile crop sets --od-ratio and adds .od-media-cover together,
and the commerce-card row of the rule card says so.

* fix(od-next): carry finish-time observations on run_finished, not the frozen baseProps

Review: od_next_device_platform*/od_next_layout_primitives were spread into
baseProps, which is built at run creation — before any deliverable exists —
and run_finished only spreads that frozen object. The daemon log printed the
observations while the analytics event never carried them, which defeats the
rollout signal these fields exist for. They now join finishedProperties,
read live from the run after the physical run resolves. (The device-shell
fields shipped with the same trap in the shells PR; fixed here on the same
lines.)

---------

Co-authored-by: NJUHua <113895241+NJUHua@users.noreply.github.com>
2026-08-25 03:58:46 +00:00
open-design-crew[bot] 7320f9b8ab feat(daemon): make OD Next the default design strategy (#7016)
* refactor(observability): isolate legacy run exporter

* refactor(daemon): extract internal run creation service

Route /api/runs and compatibility /api/chat through one daemon-owned create, claim,
resume, and start seam. Preserve HTTP responses, SSE timing, session bookkeeping,
and durable Run formats.

Validated with daemon tests/build/typecheck, workspace guard, and workspace typecheck.

* test(plugin-runtime): freeze OD Next strategy fixture boundaries

Add exact pre-Build route and stage cases plus deterministic deletion-boundary mutations without shipping an evaluation harness.

Preserve the current official and community fallback pipeline and composed verification/critique prompt sections as golden witnesses.

Validated with plugin-runtime tests and types, focused daemon prompt composition, daemon types, guard, and workspace typecheck.

* fix(daemon): finalize terminal telemetry delivery

Persist a versioned delivery record so normal failures stop after bounded retries
and only an unfinished crash window resumes on restart.

Reuse the persisted identity across Vela and Relay transport, isolate stable
direct Langfuse event ids by delivery purpose, and log only redacted sink data.

Validated with focused daemon and contracts tests, workspace typechecks, and
guard. The full daemon suite had one timeout; its four-test file passed alone.

* feat(plugins): add OD Next strategy scenario and contracts

Ship the V2 strategy package as inactive bundled content and validate its contracts without changing ordinary plugin manifest compatibility.

Keep bundled strategy bytes out of the catalog and generic apply path until a later hash-gated activation owner binds them.

* feat(observability): add normalized agent observation contract

Define a pure versioned contract for identity, Prompt, usage, timing, and status evidence with explicit availability and per-value provenance.

Add a structured main-Run compatibility adapter that preserves unknown facts, separates clocks, and omits provenance-ambiguous derived totals without changing legacy export behavior.

* feat(plugins): bind OD Next strategy content identity

* feat(daemon): persist OD Next strategy task chains

Add the daemon-owned SQLite truth for immutable strategy task identity, ordered physical Run mappings, canonical Plan Contracts, revision-CAS transitions, and terminal reconciliation.

Pin task-owned Snapshots and exclude their durable references from pruning so mixed GC sweeps remain safe.

Validated with 103 focused daemon tests, contracts tests/typecheck, daemon and workspace typechecks, and guard.

* feat(daemon): gate OD Next runtime capabilities by fixtures

Add exact CLI/adapter/fixture tuple resolution, stable capability snapshots, and offline
replay graph invariants for the four initial runtime paths.

Keep production registry empty until sanitized real fixtures exist; synthetic evidence and
version drift remain unknown.

Validated with contracts tests, focused daemon and e2e boundary suites, guard, and
workspace typecheck.

* feat(daemon): add hash-gated OD Next prompt recipe

Compose the bundled strategy from validated content, one Task Skill, and the exact stage policy.
Fail closed on package, profile, pipeline, or atom drift while preserving ordinary quality paths.

Validated with focused tests, package typechecks, workspace typecheck, and guard.

* feat(observability): aggregate OD Next task observations

Build stable task, Run, Child, model, and tool hierarchies from durable StrategyTaskExecution mappings and normalized facts while keeping missing coverage explicit.

Map task aggregates to legacy ingestion behind the consent gate. Preserve owner/exclude hierarchy while accounting usage once, enforce kind-specific Prompt boundaries, and accept absolute time only from one Unix-epoch evidence record.

Validated with 228 focused daemon tests, 353 contracts tests, daemon/contracts and workspace typechecks, guard, and the task harness.

* feat(daemon): collect bounded Codex child evidence

Read declared Codex rollout sessions without scanning arbitrary homes.
Normalize verified child facts while redacting prompts and preserving Run semantics.

* feat(daemon): add OD Next planning coordinator

Add a daemon-owned planning reducer, deterministic Resolver and Preflight facts, and a stream-safe machine protocol boundary backed by the durable task store.

Lock one clarification and one serialization-only repair across restart, preserve the Plan hash, and keep ordinary Run text unchanged.

* feat(daemon): collect native OpenCode child evidence

* feat(observability): add Langfuse OTLP task exporter

Map normalized task observations to complete OTLP/JSON v4 spans with stable parent context, allowlisted metadata, usage accounting, and bounded terminal delivery.

Fail closed on drift across root and operation input, model, timing, type, hierarchy, status, and complete usage provenance while dual remains a local-only OTLP shadow.

Keep legacy rollback, consent gates, and X5 live self-host verification boundaries intact.

* feat(daemon): normalize Claude native child evidence

Collect matched Claude Task sidechain lifecycle facts without changing main stream turn, stdin, or error bookkeeping, and adapt them to provider-neutral observations with unavailable Prompt and usage evidence.

Bind session, Task id, and parent identity immutably; poison rebinding, session drift, and conflicting terminals so ambiguous evidence cannot reach L2. Keep production capability unknown until X1 supplies an exact-version sanitized real fixture.

* feat(daemon): consume versioned Vela child evidence

Negotiate schema v1 only on AMR and enforce run-scoped start-to-terminal graph, status/source coherence, and exact usage source/completeness bindings. Emit allowlisted Normalized child facts without Vela private I/O.

Pin the unpublished producer golden for offline compatibility while keeping production capability unknown until the X1/X3 evidence gates are satisfied.

Validated with 147 focused daemon tests, 353 contract tests, daemon/workspace typechecks, guard, and the task harness.

* feat(daemon): automate OD Next simple production

Connect parsed OD Next terminal output to the durable coordinator, claim clarification, repair, and production Runs atomically, and start continuations only after the source Run ends.

Converge task failure and cancellation before physical end, require daemon-owned execution facts and canonical delivery, and keep ordinary Runs compatible.

Validated with focused daemon route and coordinator tests, contracts tests, daemon and workspace typechecks, guard, and the task harness.

* feat(daemon): enforce verified complex production

* feat(web): follow OD Next task chains across Web and CLI

Persist daemon-issued task handles and visible predecessor-prefix boundaries, then follow projected active Runs through one terminal outcome in Web and od run.

Normalize reloads to daemon activeRunId before subscribing, transfer controller ownership atomically across successors, discard stale cursors, and preserve visible prefixes without replaying predecessor machine payloads.

Validated with focused Web and daemon tests, contracts tests, workspace typecheck, guard, diff checks, and the task harness.

* feat(daemon): roll out OD Next task observability

* feat(daemon): add controlled OD Next rollout

* fix(daemon): align OpenCode and Vela evidence tuples

Pass GPT-5.6 Sol's verified high variant to OpenCode, pin native evidence to 1.18.18, retain locally recorded success and failure seeds as explicitly unattested, and align the Vela consumer golden to the local versioned producer.

Validated with 5 focused daemon files (175 tests), daemon and workspace typechecks, guard, the real local Vela ACP Sol smoke, and a minimal test-relay telemetry write.

* fix(daemon): honor OpenCode model variant capabilities

Parse the live verbose OpenCode catalog per model and forward only variants advertised by the exact detected binary. Keep fallback and one-off OPENCODE_BIN connection tests on the base model when variant support is unknown.

Record machine-checkable OpenCode 1.18.18 root, tool, child, recovery, cancel, timeout, and resume evidence without promoting the unattested seed into the production capability registry.

Validated with focused runtime and connection tests, host process-group and watchdog tests, contracts tests, daemon/workspace typechecks, guard, and an isolated SIGKILL delivery-recovery exercise.

* feat(daemon): add OD Next analytics operations report

Reduce normalized task aggregates into source-aware operations metrics without
copying the Langfuse dashboard or exposing user trace links.

Preserve main/Child accounting, stage-scoped coverage, unavailable facts, and
external-signal-only quality inputs.

Validated with focused and contract tests, the full daemon suite, guard,
workspace typecheck, and exact-trace Langfuse readback.

* feat(observability): complete child prompt and runtime provenance

Export bounded redacted child-injected Prompt content and exact CLI, companion, and
adapter versions through task-level legacy and OTLP mappings.

Register the OpenCode 1.18.18 best-effort replay tuple while keeping unknown versions
and incomplete Codex evidence fail closed.

Validated with 353 contracts tests, 196 focused daemon tests, daemon/workspace
typechecks, and guard.

* fix(daemon): recognize Codex child failure terminals

Parse Codex 0.147.0 Child task_complete.error as the runtime-owned failed terminal, honor session_meta id over its legacy root session_id, and map interrupt_agent aborts to canceled.

Add a redacted seven-path best-effort replay and exact Codex capability tuple. Unknown versions remain fail-closed and ordinary Codex runs are unchanged.

Validated with 92 focused daemon tests, 353 contracts tests, daemon and workspace typechecks, guard, and the 25-unit task harness.

* fix(daemon): report verified child runtime versions

Use versions observed from resolved CLI probes or Vela's ACP handshake when annotating Child observations. Correct Native OpenCode's allowlist key, add Claude and Vela/OpenCode companion provenance, reject Vela probe/handshake disagreement, and ignore the legacy 0.0.0 placeholder.

Validated with 252 focused daemon tests, daemon and workspace typechecks, guard, the task harness, and a real local Vela --version/-v/ACP initialize build smoke.

* test(observability): record Vela child evidence matrix

Add a sanitized provider-backed Terra replay for main, tool, child success, child failure with parent recovery, cancel, timeout, and cross-process resume. Keep the unpublished Vela tuple outside the production registry while pinning its exact candidate and OpenCode companion evidence.

Validated with focused Vela consumer/capability tests, daemon typecheck, workspace typecheck, guard, and the task harness.

* feat(daemon): enable OD Next production profiles

Make prototype, presentation, marketing, and HyperFrames the exact global OD Next production set while preserving explicit off/observe, user plugin authority, and fail-closed runtime capability gates. Feed runtime-owned profile, route, output, input, and capability facts into the planning contract and keep all other artifact kinds on ordinary Open Design.

Use bounded executable-scoped CLI version probes so headless and restarted daemons can resolve exact tuples without a prior agent-list request or cross-binary cache contamination.

Validated with four provider-backed Terra CLI chains, focused daemon/contracts/plugin-runtime suites, daemon and workspace typechecks, guard, and the 25-unit task harness.

* feat(daemon): classify OD Next exact agent inputs

Route prompt contributors through one daemon-owned choke point and version their final-text, semantic-fact, transport, and exclusion ownership for the forthcoming canonical bundle. Send OD Next continuation stages directly through native sessions without rewrapping or re-seeding stable instructions.

Validated with focused daemon and contracts tests, daemon/workspace typechecks, and guard.

* fix(daemon): close OD Next input ownership gaps

Bind semantic request facts to their real daemon-system or request producers, cover the DSH profile exact-text transport, and reject lookalike prompt-bundle root names.

Validated with focused exact-input, production-server and DSH tests, daemon typecheck, task harness, and guard.

* feat(daemon): emit canonical OD Next prompt bundles

Add strict pure TypeScript Bundle and request-Turn serializers, then route OD Next request and continuation text through their versioned XML contracts.

Split Web prior transcript from the current turn without text subtraction, keep mutable Skill and attachment inputs explicitly pending, and preserve ordinary prompt behavior.

Validated with focused contracts, daemon and web tests, the real server fixture, package and workspace typechecks, and guard.

* fix(daemon): align OD Next bundle input ownership

Register the real canonical Bundle contributors and keep current-turn, prior-transcript, pending-fact, and research-query ownership explicit at the production seam.

Reject continuation task_run_index zero and cover Web framing, ordinary compatibility, fail-closed contributor drift, and real server prompt capture.

* feat(daemon): freeze OD Next user skills

Capture explicit Skill bodies and audited side files before task claim, persist verified task-owned bytes, and replay only the frozen package for OD Next runs.

Keep legacy single-Skill CLI requests and ordinary live staging compatible while adding Web/CLI multi-Skill coverage and fail-closed snapshot checks. Validated with focused server, contract, CLI, Web, typecheck, and guard suites.

* fix(daemon): harden OD Next skill snapshots

Require an explicit frozen package for every mapped OD Next task and fail corrupt task state before any live Skill staging.

Use bounded no-follow descriptor reads with race checks, preserve legacy CLI compatibility, and make materialized roots collision-resistant. Validated with focused, real-server, workspace typecheck, and guard suites.

* feat(daemon): freeze OD Next task input snapshots

Freeze OD Next attachments and canonical configuration before the atomic run/task claim, then route runtime access through durable task-scoped snapshots while preserving ordinary attachment behavior.

Validate immutable identities, transport references, continuation propagation, tamper failure, and the four production task types with focused contract and daemon coverage.

* fix(daemon): isolate OD Next runtime input projections

Keep canonical task snapshots daemon-only, rebuild a read-only per-Run projection, and deliver frozen document resources through ACP or OD_TASK_INPUT_DIR.

Harden source and snapshot reads with no-follow same-fd identity checks and bounded caps. Validated with focused runtime tests, contracts, daemon/workspace typechecks, guard, and the task harness.

* fix(daemon): harden OD Next snapshot transport boundaries

Reject intermediate symlinks and realpath escapes while loading canonical snapshot files, including identical-byte replacement targets.

Preserve ordinary ACP duplicate image ordering while suppressing only OD Next resource/image cross-duplicates. Validated with focused tests, daemon typecheck, guard, and the task harness.

* fix(daemon): preserve structured OD Next snapshot failures

Defer frozen Skill package errors from the Task 04 preclaim lookup to the existing post-claim failure boundary so mapped Runs remain durable JSON failures without orphan input snapshots.

Validated with the combined real-server repair, missing-package, and tampered-package regressions.

* fix(daemon): keep OD Next continuation cwd stable

Separate authoritative project cwd resolution from best-effort file inventory and tolerate entries deleted between directory enumeration and stat.

This keeps native-session identity stable while frozen task inputs replace live attachments during continuation.

* feat(daemon): persist exact OD Next prompt inputs

Persist the canonical first-turn Bundle and every deterministic continuation Turn with UTF-8 and SHA-256 identities, then send only those validated texts through runtime transports.

Fail closed on mapped task drift, tamper, legacy NULL identities, missing mappings, and cold continuation while leaving ordinary Runs on the existing live composer.

Validated with the 15-case automatic server acceptance twice, 223 focused daemon tests, daemon typecheck, guard, and the task harness.

* feat(daemon): observe exact OD Next prompt inputs

* feat(daemon): bind and observe Claude native subagents

Register the replay-verified Claude 2.1.233 tuple, bind complex Build Packages to daemon-issued native Agent handles, and derive completion only from durable child lifecycle evidence.

Export bounded parent-tool, Child Agent, and Child-tool spans through the existing legacy and OTLP task hierarchy without exposing raw tool inputs, outputs, provider ids, paths, or credentials.

Validated with focused contracts and daemon suites, the 17-case real-server chain, workspace typecheck, guard, and the 26-unit task harness.

* fix(daemon): repair OD Next rollout authority and controls

* fix(daemon): harden OD Next public canaries

* refactor(daemon): extract OD Next prompt preparation

* test(daemon): align plugin fixture with project schema

* test(e2e): assert daemon-owned prototype routing

* fix(daemon): restore OD Next task observability

Default eligible OD Next tasks to privacy-aware Langfuse hierarchy delivery through relay or direct sinks, with a versioned safe quality projection.

Persist lightweight representation ownership before suppressing mapped Runs, retain best-effort startup retry semantics, and preserve single-Run compatibility when Task delivery is not eligible.

Adopt the active payload relay for historical packaged launchers and cover the real sidecar environment handoff.

Validated with the full daemon suite, focused contracts/packaged/e2e/tools-pack suites, workspace guard, and workspace typecheck.

* fix(daemon): make OD Next capability fallback invisible

Admit capability-certified runtime adapters across CLI version drift while keeping recorded fixture versions as auditable evidence and reserving a narrow incompatibility denylist.

Fall back before Run claim on preparation failures, keep continuation bound to the frozen capability snapshot, and report truthful child coverage across Langfuse legacy and OTLP without replaying an active task.

Validated with focused contracts and daemon suites, the automatic server suite, pnpm guard, pnpm typecheck, daemon typecheck, LSP diagnostics, and git diff --check.

* fix(strategy): isolate OD Next automatic task routing

* fix(daemon): persist blocked strategy-task attribution

Every blocked strategy task now stores its gate reason codes and the
agent-visible turn text (blocked_reason_codes_json / blocked_visible_text)
so operators can diagnose a blocked task from the store alone. Covers the
coordinator blockTask gate, the automatic-continuation fail-closed path,
and startup run-terminal reconciliation.

* fix(daemon): accept form-only clarification turns by deterministic inference

A first Full-Plan request turn that renders exactly one question form and no
machine-protocol block has exactly one valid protocol meaning: outcome
clarification_required with an unlocked execution mode. The coordinator now
recovers that state deterministically instead of dead-ending the task as
blocked, stamps od_next_protocol_runtime_state_inferred into the accepted
state's reasonCodes for attribution, and keeps every ambiguous shape (extra
forms, recovered plan blocks, later stages, spent clarification budget,
any other protocol issue) fail-closed. This closes the observed field
failure where the agent's first reply produced a discovery form, the task
went blocked, and the user's form answer then failed with 409
STRATEGY_TASK_STATE_MISMATCH.

* feat(contracts,daemon): project blocked strategy-task attribution to clients

A task the OD Next protocol gate settles as blocked persists its
attribution (reasonCodes + agent-visible text) since 8bc7805e62, but the
client-facing StrategyTaskProjectionV2 dropped it, so the UI could not
tell a blocked task from any other failure. Add the optional
blockedContext field to the projection contract (additive, strict shape
mirroring the task store) and have projectStrategyTask carry it through
every run-status / SSE assembly point, only on a blocked outcome.

* fix(web): terminate question-form interaction when a strategy task is blocked

A blocked OD Next task is a sticky terminal verdict, but the web client
kept the turn's discovery/clarification form fully submittable; the
answer then 409'd with STRATEGY_TASK_STATE_MISMATCH and surfaced only
the generic 'Task failed' card.

- ChatMessage gains additive strategyTaskBlocked / strategyTaskBlockedText
  fields; strategyBlockedMessageFields derives them from any terminal
  blocked projection (visibleText preferred, trimmed, else null).
- streamViaDaemon/reattachDaemonRun expose onStrategyTaskSettled with the
  terminal task projection; ProjectView stamps the blocked verdict onto
  the assistant message on live settle, on reattach settle, and on the
  reattach status probe, and persists it so it survives reloads.
- AssistantMessage disables the inline question form for a blocked turn
  (including the auto-continue countdown) and renders the gate's visible
  text - or a generic localized notice - under the form.
- resolveRunFailureUi maps STRATEGY_TASK_STATE_MISMATCH to dedicated
  halted-task copy instead of chat.runError.title.generic.
- New i18n keys (title, 409 message, form notice) added to types.ts and
  all 19 locales.

Blocked-state interaction shape is a minimal safe default pending a
product decision: disable submission and explain, no auto-retry, no
degraded re-run.

* fix(daemon): normalize premature clarification execution mode

The observed 2/2 field failure on vague first prompts: the agent emits a
well-formed Runtime State block for clarification_required but predicts
"executionMode": "simple" alongside it. The mode is daemon-locked only
after clarification resolves, so the prediction carries no authority — yet
strict schema validation classified the whole block as
od_next_protocol_runtime_state_invalid_schema (non-repairable) and
dead-ended the task. The protocol stream now discards exactly that field
before validation, records the normalization for attribution, and the
prompt recipe gains an explicit rule plus a clarification-shape Runtime
State example so agents stop copying the plan_ready example's mode.

* fix(strategy): declare the runnable-entry deliverable convention to agents

The deliverable validator requires a root-level runnable entry (root
index.html for html deliverables), but neither the frozen prompt bundle nor
the production continuation ever stated that convention — the agent was
told only 'editable prototype source with a stable runnable entry', built a
framework scaffold in good faith, and the completed task then blocked with
od_next_canonical_deliverable_invalid. The production continuation and the
prototype Task Skill now state the root-entry convention and forbid
unplanned framework/dependency scaffolds, so the contract the daemon
validates is the contract the agent was given.

* fix(daemon): stop the rollout only for real machine-contract failures

rolloutStopSignalForBlockedContinuation mapped every reason code containing
'protocol' to machine_contract_leak, whose stop mode is a hard instance-wide
off that survives restart. Two vague prompts were therefore enough to
silently disable OD Next for the entire daemon: the agent emitted a
clarification runtime state carrying a premature executionMode, the task
blocked, and every later request in the daemon fell back to the legacy
plugin with no UI signal and no automatic recovery.

The machine block never reaches the user — the protocol stream suppresses
undelimited and oversized bodies — so an agent turn that omits, duplicates,
or mis-populates a block is a per-task defect, already fail-closed for that
task and now attributable from the store. Only the two block-delimiting
failures keep raising the leak signal.

* fix(strategy): state the entry-resolution constraint without inventing a product rule

The previous wording told agents that a prototype's runnable entry must be a
root index.html and that framework scaffolds are forbidden. Neither is in the
product spec, which says only that the user must be able to open and use the
deliverable from a real entry point, and neither matches the validator, which
resolves an entry through root index.html, then a single root-level html file,
then a single kind-matching file. That turned one fallback branch into a
product constraint the agent then had to obey.

Both surfaces now state the resolution rule the daemon actually applies and
why a delivery is rejected without it, and take no position on whether a
prototype may be an application project. Whether the validator should also
resolve framework-project entries is a product decision and stays open.

* fix(strategy): assemble the OD Next prompt bundle as an XML tree

The first-turn payload was an XML envelope around four flat CDATA blobs,
with one of them holding 25.3KB of joined Markdown (89% of the bundle).
Three layers each threw structure away: the contract typed the bundle as
four strings, the daemon joined 15 individually-named contributors with
'\n\n---\n\n', and the recipe re-joined 10 sections while demoting the
stage/atom map to Markdown headings. The result inverted heading levels in
7 of 46 places and put a per-task UUID 1,268 bytes into the payload, which
pushed 23KB of byte-identical strategy content past the point where a
provider prompt cache can share a prefix.

Introduce a canonical XML tree primitive and a v2 bundle schema that nests
one element per spec slot. Because system_prompt is now the shared cache
prefix, it may hold only cross-task-constant content, so the recipe
identity, runtime planning facts, runtime tool environment, client system
prompt and form override all move into context, and user_prompt moves last.

Measured on the real strategy assets: 28,325 -> 26,010 bytes, 4 -> 16 CDATA
leaves, 7 -> 0 heading inversions, and the first byte that differs between
two tasks moves from offset 1,268 to 24,709 (past the end of system_prompt).

Persisted rows keep working: final_text_schema is dispatched per row, so a
v1 bundle still parses at its own version while new tasks mint v2.

* feat(daemon): let the main Agent choose the task route

Product spec 3.1 makes the main Agent the party that decides Direct Edit vs
Full Plan, and 6.1 puts that decision inside MVP scope. The daemon instead
locked the route before the Bundle was ever sent: routes/runs.ts passed
preference 'full_plan' with all five Direct Edit eligibility facts hardcoded
false, and decideStrategyRequestRoute short-circuits on that preference, so
Direct Edit was structurally unreachable. A local edit on an existing
artifact therefore paid a full planning round — measured at 2 Request Turns
and 61s for a colour change that the Agent itself correctly scoped to one
property.

Four of the five eligibility facts require reading the request, which the
daemon cannot do before dispatch. So the route now stays unlocked through
the request turn — a state the task store and the projection contract both
already model — and the daemon adopts the route the Agent declares in its
Runtime State. Every later turn keeps the existing lock: a route may never
change once set.

The Full-Plan-only guards for clarification inference, serialization repair,
and the repair anchor now also accept the unrouted first turn, since each of
those paths is itself a Full Plan declaration and locks the route as it
transitions. blockTask settles an unroutable turn on the spec's documented
fallback route rather than refusing to record it.

No contract field, DB column, or prompt byte changes: the recipe already
tells the Agent to choose the allowed route on the request stage, and
Runtime State has always carried .

* fix(strategy): tell the Agent it owns the request-stage route choice

Unlocking the route in the daemon was necessary but not sufficient. The
recipe still read "use the supplied task facts to choose the allowed route"
and immediately followed with "For a Full Plan route, the request and
clarification stages are planning-only", so the Agent kept assuming a Full
Plan had already been chosen for it. Verified through the real UI: an
unambiguous local edit still came back as full_plan with the Agent stating
'this turn will not modify files'.

The request-stage section now says the choice is the Agent's, that Open
Design leaves the route unset until the first Runtime State declares it,
and that the active orchestration Skill's eligibility conditions decide
between the two. It states the mechanism and defers the criteria to the
Skill rather than restating them.

Verified end-to-end in Chrome against a real codex run: a local colour edit
on an existing artifact now routes direct_edit / simple and completes in
ONE Request Turn (50s), matching spec 3.4. Ambiguous requests still route
full_plan, and a request needing clarification still routes full_plan
because Direct Edit is request-only.

* fix(observability): restore failed-Run process evidence in the task hierarchy

PR #7016 replaced the per-Run Langfuse trace with a task hierarchy
(`strategy-task:<taskExecutionId>` + `strategy-stage:*` child spans) and
durably suppresses the single-Run trace. Failure classification survived
the move, but the terminal process evidence the single-Run trace carried
— stderr/stdout tails, host close diagnostics, exit code and signal —
did not reach Langfuse at all, so a failed Run lost the only facts that
explain *why* it failed.

Carry those facts through the existing safe-projection boundary:

- `SafeRunQualityV1` gains an optional `process` section
  (`exitCode`, `signal`, `stderr`, `stdout`, `diagnostics`), with
  `SafeObservationStreamTailV1` and a bounded `SafeRunDiagnosticsV1`
  record that admits only booleans and identifier-shaped values.
- `buildSafeRunQualityProjectionV1` builds it. Stream tails reuse the
  producer's existing 20-line / 4 KiB cap plus `redactSecrets`, then add
  the wider Prompt-stack local-path masking before `safeQualityText`.
  stderr follows the run error-message rule; stdout is the agent's own
  output and needs content consent, reporting its line count with an
  explicit limitation when withheld.
- `buildSafeRunQualityProjectionFromDaemon` collects the same summaries
  the single-Run bridge collected, and the aggregation projects them
  onto the `strategy-stage:*` span metadata for both the legacy and OTLP
  exporters.

The smoke test now asserts the task-hierarchy shape (trace id, child
span id/name, failure metadata keyed by runId) and re-asserts the
diagnostics coverage there, including that a local path in stderr is
masked before transport. Each case gets its own daemon and `OD_DATA_DIR`
via `vi.resetModules()`: the OD Next rollout stop latch lives in SQLite
and fires per daemon instance, so a shared data root silently pushed
later cases onto the compatibility single-Run reporter. The one case
that legitimately stays on that reporter (deepseek, outside the OD Next
runtime path) now asserts the same coverage on the single-Run trace.

* test(daemon): make the run-failure smoke deterministic under load

The failed-run smoke could report `expected 'process_exit' to be 'auth'`
under CPU pressure. Two independent sources, both closed here.

1. The fake `claude` was not a valid OD Next Claude runtime. `detectAgents`
   parses `claude -p --help` for every flag in the runtime def's
   `capabilityFlags`; once a physical Run is mapped to an OD Next strategy
   task, `buildArgs` sets `observeNativeChildBehavior` and THROWS when the
   probe never saw `--forward-subagent-text`. The fake advertised neither
   that flag nor `--agents`, so whenever the task mapping was already
   visible to `startChatRun` the Run died on that host-side TypeError
   before the agent was ever spawned — reported as `AGENT_EXECUTION_FAILED`
   / `process_exit` instead of the auth, rate-limit or timeout failure the
   case scripted. Captured in the wild on the `hang_timeout` case
   ("Claude native Child behavior observation requires advertised
   --forward-subagent-text support.", 9 events, no spawn) and reproduced
   deterministically against `buildArgs`: old help text throws, derived
   help text builds args containing the flag.

   The fake's help text is now derived from `claudeAgentDef.capabilityFlags`,
   so it cannot drift from what the daemon probes for and the Run always
   reaches the scripted failure regardless of mapping order.

   This gate pre-existed the task-hierarchy change — the same fake and the
   same daemon check are on `main`. But that commit gave every sub-case its
   own daemon and data root, which keeps the OD Next rollout active for all
   five instead of only the first, so exposure went from one Run per file
   run to five.

2. The events log was sampled at an arbitrary moment. `GET /api/runs/:id`
   publishes terminal status before the buffered `events.jsonl` write has
   necessarily reached disk, so re-deriving a classification from that file
   could see a log whose error record was still in flight.
   `readCompletedRunEvents` waits for the log's own completion marker — the
   `end` record the daemon writes last, before closing the stream — rather
   than for a fixed delay or for the assertion to pass.

Each case now also asserts the daemon's own settled verdict
(`run.failureCategory` / `failureDetail` from the terminal status), which is
what the chat UI, the persisted message and telemetry actually consume. The
one case rejected before the runtime finish path declares
`daemonPublishesVerdict: false` rather than tolerating a null.

Verified under load average ~45-50 on a 10-core box (22 spinners plus a
concurrent full daemon suite): case 1 alone 6/6, file + tests/observability
6/6, file + observability + telemetry-message-finalization +
prompt-telemetry 5/5, every case in isolation 4/4.

* refactor(strategy): reshape the OD Next Prompt Bundle to the PRD layout

The emitted v2 bundle wrapped the cache-stable head in a `system_prompt`
element the PRD never asked for, and named three blocks differently from
the spec. Adopt the PRD-literal naming with the wrapper removed:

  open_design_prompt_bundle
    open_design_core_system_prompt   (was system_prompt/core_system_prompt)
      execution_boundary / native_execution
      discovery_and_planning_surface / core_strategy
      output_contract / echo_guard   (moved in: both are output constraints)
    session_skills
    active_stages
    task_metadata                    (was task_config)
    context
    user_first_prompt                (was user_prompt)

Block order stays load-bearing: the first three blocks are byte-identical
across tasks of the same strategy version, task type, and execution
profile, so the shared provider-cache prefix now runs through the end of
`active_stages` instead of `</system_prompt>`.

Three noise cleanups land with the reshape:

- `frozen_skill_package` and `request_input_facts` are omitted when they
  carry no substantive content, generalizing the rule the PRD already
  mandates for `attachments`. An empty Skill roster and an all-zero
  workspace/comment/MCP state cost ~540 bytes and told the model nothing.
- `recipe_identity` drops `strategy_package_hash`, `task_skill_digest`,
  and `stable_prompt_identity`. They are audit-only; observability and the
  persisted strategy task record keep them.
- Prompt text naming the old tags is updated: the echo guard constant, the
  form-override strings in both call sites, and the map notes.

Schema id `open-design.od-next-prompt-bundle/v2` is unchanged — it has
never shipped — and the v1 parser stays intact for persisted rows.

* test(daemon): make ingestion waits report what actually arrived

A bare "timed out waiting for the task observation owning run <id>" does not
say whether the daemon delivered nothing, delivered the compatibility
single-Run trace instead, or delivered the span under a different Run — so a
CI-only occurrence costs a reproduction cycle to classify. Both ingestion
waits now append the received trace/span/generation ids, names and runIds to
the timeout message.

Diagnostics only; no assertion, budget or behaviour change.

* fix(daemon): gate OD Next admission on what the installed CLI advertises

Making OD Next the default design strategy turned every prototype/ppt/
marketing/hyperframes Run with `claude` into an OD Next Run, and every
admitted Claude strategy Run asks the CLI for native Child behaviour
observation. `claude.buildArgs` refuses that unless the installed build
advertises `--forward-subagent-text`, and the refusal is a thrown
TypeError inside the spawn path, so the Run dies as
AGENT_EXECUTION_FAILED before the agent ever starts.

Admission never established that fact. `runtimeCapabilityVerified` was
derived from a bundled capability fixture keyed by runtime path + agent
id + adapter version, plus an `ensureDetectedRuntimeVersions` probe that
reads only `--version`. The fixture proves what the claude-code runtime
*path* can do; it says nothing about the build on the user's disk. So the
daemon admitted a task it could not launch.

The symptom hid on developer machines. A real Claude Code install had
already populated the process-wide `agentCapabilities` map through full
detection, and that stale entry answered "advertised" for a fake CLI that
advertised nothing — which is why four pre-existing daemon suites and one
e2e suite passed locally and failed on CI hosts with no Claude Code
installed. Ambient developer state was deciding the verdict.

Establish the fact at admission instead. `ensureDetectedRuntimeCapabilities`
runs the same bounded `-p --help` read full detection already uses, keyed
by the resolved launch path so a configured CLAUDE_BIN cannot be answered
from another binary's cached probe. An installed build that does not
advertise what OD Next will demand loses admission and takes the ordinary
route, reported as `od_next_rollout_capability_advertised_capability_missing`
with the missing keys on the `[od-next-rollout]` line.

`customAgents` is required alongside `forwardSubagentText` because a simple
task may escalate to complex after planning, and discovering that gap at
the production stage would strand the task mid-flight.

Red spec: apps/daemon/tests/od-next-advertised-capability-admission.test.ts

* fix(daemon): let a re-entered turn resume its own session

latestCompletedAssistantMessageId excludes the caller's in-flight assistant
placeholder so it cannot count as conversation advancement. Its
resumableMessageId parameter exists to admit the stored session cursor back
through that filter — but the exclusion was evaluated first, so when the
placeholder IS the cursor the row was already gone and the admission never
applied.

That collision happens when a daemon-internal restart re-enters
startChatRun with the same assistant message after that message was already
persisted as the session cursor: post-tool recovery writes the cursor, and
nativeSessionContinuePending is consumed once, so a later safe-retry restart
arrives without it and falls through to the ordinary guard. The live session
was then abandoned and the whole transcript re-seeded. For an OD Next
continuation it is worse than a slow turn — the non-request stage refuses to
cold re-seed and blocks the task instead.

The exclusion now yields to the admission for exactly that row. Advancement
detection is untouched: any later succeeded message still wins on
position DESC and still fails the cursor comparison, which a new test pins
so the fix cannot later be simplified into an unconditional cursor match.

* fix(web): hide internal OD Next rollout controls

* fix(daemon): stop OD Next from discarding delivered work

Seven defects found by running the four OD Next runtimes end to end. Each one
silently threw away a turn that had actually done its job.

- OpenCode wrote deliverables outside the project. It resolves its workspace by
  walking up to the nearest enclosing git root rather than using its process
  cwd, so a managed project directory — never a repository, and in a
  development install nested under the checkout — made it adopt the whole
  repository. The project stayed empty, artifact detection saw nothing, and the
  Run reported `no_artifact`. The BYOK definition already carried this
  workaround; the native one was missed. Sent unconditionally like BYOK does:
  gating it would turn a missing flag back into silent data loss, where an
  OpenCode build without `--dir` should fail loudly at spawn.

- The `--help` capability probe read only stdout. OpenCode writes its entire
  help to stderr, so every one of its advertised flags resolved to `false` and
  the capability mechanism was inert for that runtime — `--dir` above, and
  `--dangerously-skip-permissions` on builds that support it.

- Optional argv flags were dropped on a cold daemon. `agentCapabilities` was
  only warmed by full detection, so a turn that spawned before Settings or
  `/api/agents` built argv from an empty map. Resolve the advertised flags on
  the run path first.

- One ordinary simple Run could disable OD Next daemon-wide. A child-evidence
  coverage gap latched the rollout stop regardless of execution mode, but only
  the complex production gate consumes per-child coverage; a simple task has no
  Child agents, so `unavailable` there is accurate missingness. Recovery needed
  an operator `od strategy rollout reset`.

- A Direct Edit that delivered but never declared it was refused. The agent
  writes the canonical deliverable, `validateRunDeliverable` resolves it, and
  then the turn carries no machine block at all. No repair could rescue it:
  serialization repair needs a recovered Plan Contract to anchor on. Infer the
  completion from delivery Open Design verified itself, fail-closed on every
  weaker signal.

- A production block named nothing. Every other blocking path persists its
  reason codes; this one did not, so the most common production block reached
  the client anonymous.

- The web client remapped a blocked verdict onto a Run that had succeeded and
  delivered, hiding the artifact behind a generic failure card.

Also: tell the request stage the canonical-deliverable rule that judges a
Direct Edit completion. It shipped only in the production continuation, so
Direct Edit agents were graded on a contract they were never given.

Blocked diagnostics now report which machine blocks the turn actually emitted,
because "malformed block" and "no block at all" have different remedies and the
reason code alone cannot tell them apart.

* feat(daemon,contracts): expose which turn a strategy Run belongs to

A Full Plan task runs as several physical Runs (request -> production) that
the user asked for once, and the daemon-issued continuation carries no user
prompt of its own. Clients had no way to tell that those Runs are one
conversation turn, so the continuation surfaced as an answer nobody asked
for.

The daemon stays the single writer of one message per Run. It now also
reports the logical-task position alongside each message
(strategyTaskExecutionId + strategyTaskRunIndex, resolved in bulk from the
Run mapping) and emits a strategy_task_continuation diagnostic before the
source stream closes, so a live client learns the turn is still open. The
SSE diagnostic channel is contract-typed for the first time — the daemon has
always emitted it, ChatSseEvent just never declared it.

The read-side projection deliberately bypasses the record parser: it is for
display, so it must stay cheap across a conversation and must never let one
unverifiable task record fail the whole message list. Existing conversations
resolve their history without migration.

* fix(web): render a Full Plan task as one conversation turn

Each physical Run of a strategy task arrived as its own assistant message,
so a Full Plan answered one question with two turns: the planning Run
finished mid-answer — author line, "completed", copy/branch/rate actions —
and the production Run then opened a second turn that no user prompt
preceded.

The fold happens purely in the view. Messages sharing a task execution id
are folded into the turn's first message in Run order, appending content,
events and produced files, so nothing is dropped and nothing is repeated.
The turn reports the latest Run's status: an intermediate Run finishing is
an internal step, not the turn ending. Direct Edit (a single Run) and
ordinary runs are returned untouched.

Folding in the view rather than by re-pointing a message at the successor
Run is deliberate — the daemon owns message writes, and a client that also
re-points would have the continuation's output persisted twice.

* fix(daemon): let every runtime finish an OD Next complex task

Complex production was reachable for Codex, native OpenCode and AMR but
finishable only by Claude, and the refusal arrived after a full production Run
had already been spent — then latched OD Next off for the whole daemon.

- Scope Build Package ownership to the runtimes that can answer it. Completion
  demanded `attributes.buildPackageId` on every Child, but ownership rides on
  Claude's `--agents` / `subagent_type` transport and only that adapter stamps
  it. Assertions a runtime cannot answer are now skipped for it while every
  lifecycle, graph, parent and ordering assertion still applies to all four.
  This also makes those lifecycle checks reachable at last: a failed package
  check used to `continue` past the started/terminal verdict for that Child.

- Publish child-evidence coverage for ACP. The AMR branch emitted none, so
  every AMR task aggregated as `child_lifecycle_unavailable_not_zero` — unable
  to distinguish "no Child agents" from "nobody was observing". The consumer
  already accumulated bindings, negotiation state and rejection reasons; it now
  summarizes them, and reports `unavailable` rather than `complete` when the
  capability was never negotiated. The branch had no child-close handler at all.

- Promote native OpenCode child evidence to L2. The live root stream filters
  child sessions out, so it can only yield a terminal-only candidate that fails
  the evidence graph on `child_started_missing` and refuses every complex task.
  `opencode export --sanitize` is the one surface pairing a child's parentID
  with its transcript, and it can only be read once the child has exited. Half
  a lifecycle is worse than none — it adds an id that fails the graph while
  displacing the L1 candidate carrying the bounded Prompt — so a partial or
  unavailable export leaves that Child wholly on L1.

- Recover an undeclared production completion. A production turn is only ever
  entered from a locked Full Plan and its schema admits no non-terminal
  outcome, so a turn that ran the frozen plan, delivered a canonical entry Open
  Design resolved itself, and then answered in prose has exactly one thing it
  could have declared. Refusing it discarded a finished multi-page deliverable
  already sitting in the project.

Two diagnosis surfaces that hid the above:

- A schema failure reported only Zod's first message, so an absent required
  field read as the bare word "Required" with no way to tell which field the
  agent omitted. Carry the path.

- Repair-anchor recovery only matched a block that was nothing but a fence, so
  an agent narrating either side of its JSON produced no anchor and the one
  allowed serialization repair could not engage. Recover the first balanced
  object too; it still has to satisfy the full schema and is still only ever an
  anchor, so a wrong extraction yields nothing.

* fix(daemon,contracts): judge Build Package ownership by the evidence

Complex completion asked whether the selected agent was Claude, then skipped or
demanded `attributes.buildPackageId` on that answer. Ownership is a best-effort
attribute riding on Claude's `--agents` / structured `subagent_type` transport,
so an agent allowlist gets all three of its edge cases wrong: a runtime that
starts stamping ownership stays unverified and its real evidence is discarded, a
Claude turn whose model slipped is refused outright and latches OD Next off for
the whole daemon, and the list needs maintaining for every future runtime.

Ask the evidence instead. Ownership is asserted when every Child observation
names a package and skipped when none can, so verification follows capability
without anyone maintaining a list. Every lifecycle, graph, parent and ordering
assertion applies either way.

The binding factory keeps its own `claude` check: constructing `--agents`
handles is transport-specific and genuinely belongs to that runtime, which is a
different question from whether recorded evidence can be verified.

Also tell the Agent whether the runtime has the verified structured native Child
lifecycle that complex mode requires. The core strategy makes it a precondition
for locking complex, and the capability snapshot already answers it, but only
its hash reached the prompt — leaving the Agent to judge a capability it cannot
observe.

* fix(contracts): teach the plan contract the shapes only a complex plan emits

The canonical Plan Contract example is a simple plan, and the schema rejects a
simple plan that declares Build Packages, so that example necessarily carries
`buildPackages: []`. The prose beside it spells out the element shape of
`buildRequirements` and `readinessArtifacts` but never of `buildPackages`,
leaving the one array unique to complex mode with neither a template nor a
description. A model asked for complex had to invent seven `.strict()` field
names: codex and opencode independently guessed `dependencies` plus a stray
`boundary`, and both terminated on
`od_next_protocol_plan_contract_invalid_schema`.

The same single-item example hides a second rule. It makes
`canonicalDeliverable.id` equal the sole `requiredDeliverables` id, so the
membership invariant reads as a coincidence rather than a constraint. A complex
plan declares one deliverable per page and picks one as canonical, at which
point codex emitted a canonical id that appeared nowhere in the list.

Describe both in the output contract, next to the element shapes already
documented there. With this, codex and opencode both reach
`executionMode: complex`, and opencode carries a complex plan through to
`outcome: completed` with three native `task` children and
`child_evidence_coverage_v1` reporting complete coverage of all three.

* fix(daemon): name the codex rollout root so a complex Run can see its Children

`collectCodexChildEvidence` reads native Child lifecycle out of
`<CODEX_HOME>/sessions/YYYY/MM/DD/rollout-*.jsonl`, and deliberately refuses a
homedir fallback so it can never attribute one install's sessions to another.
That leaves the caller owing it an explicit root — and no caller ever supplied
one. `spawnEnvForAgent`'s codex branch was a pure pass-through, so the
collector's `CODEX_HOME` guard was false on every default install and the
collector has been unreachable since it landed.

The visible symptom was a complex codex Run that locked `executionMode:
complex`, spawned three native sub-agents, delivered four valid files, and was
then refused certification for child evidence the daemon had never gone
looking for. The rollouts were on disk the whole time, with each child
declaring the parent thread id.

Backfill the root before launch, mirroring the AMR `HOME` and
`OPENCODE_TEST_HOME` backfills next to it. The collector's no-fallback
invariant is preserved: the caller still names the exact root it hands the
runtime.

* fix(daemon): report unobserved Children as missing evidence, not malformed

`resolveDaemonOwnedOdNextComplexRuntimeEvidence` always brackets the Child list
with a running/completed root pair, so the empty-observation branch in the
complex gate is unreachable in production: a Run whose Children were never
observed still arrives with two perfectly valid observations and falls through
to the graph check, which reports `od_next_complex_child_evidence_invalid`.

That sends whoever debugs it hunting a corrupt payload. In the case that
prompted this, nothing was malformed and nothing was observed — the collector
had never run at all. Name the two states apart so the reason code says which
one happened.

* fix(daemon): stop one corrupt task record from voiding a whole boot's terminals

Reshaping the OD Next Prompt Bundle changed the v2 child tag set but kept the
schema id `open-design.od-next-prompt-bundle/v2`, on the reasoning that v2 had
never shipped. That holds for releases and not for a developer's own daemon:
the previous v2 composer was HEAD long enough to persist rows that still claim
today's label over a layout its parser cannot read. Those rows raise a raw
`TypeError` out of `parseOdNextPromptBundleV2`.

Startup reconciliation called `reconcileStrategyTaskRunTerminal` unguarded, so
that TypeError escaped `reconcileDurableRunTerminals` entirely. One unreadable
record therefore skipped the task-fact seeding, the whole PostHog `run_finished`
and Langfuse replay loop, and the task-observation crash-window sweep — for
every other Run on that boot, not just its own. Twelve consecutive boots of a
local daemon lost their terminal telemetry this way, and it only stopped when
the data directory was wiped.

Absorb the failure per Run, the way `strategyTaskTurnsForRunIds` already does
for the message list: a task row nobody can read is that Run's problem alone,
and startup still owes every sibling its terminal obligation.

* test(daemon): expect the pinned workspace in the OpenCode connection argv

Pinning OpenCode's workspace with an unconditional `--dir` also reaches the
connection-test probe, which spawns in a per-probe temp directory. That is the
intended reach — a probe must not adopt the repository root as its worktree
either — but the argv assertion was still written against the pre-`--dir` shape
and had been failing since.

Assert around the minted path instead of removing the check: the fixed prefix,
the flag, an absolute value, and the remaining flags in order. The 1.3
compatibility this test guards is a property of the argument order, so the
order stays asserted byte-for-byte on both sides of the temp path.

* fix(daemon): let an agent probe end even when the CLI swallows SIGTERM

`execFile`'s `timeout` is not a settle guarantee. On expiry it only *sends*
`killSignal`, and the promise settles on `'close'` — which needs the child to
actually exit. An agent CLI that traps SIGTERM and keeps a handle open leaves
the probe pending forever, and no caller's `catch` ever runs, so the
degrade-on-probe-failure path every caller already has is unreachable.

That became a hang on the Run path once `startChatRun` began awaiting
`ensureDetectedRuntimeCapabilities`, so the argv it builds matches a daemon
that has served `GET /api/agents`. The probe's cache key includes the resolved
launch path, so a fresh binary always misses and always probes. Against a
SIGTERM-trapping CLI the Run then parked in `queued` roughly 490 lines short of
`run.status = 'running'`: never spawned, so the inactivity watchdog never
armed, so it never reached a terminal status either.

Kill probes outright instead. They are read-only metadata calls with nothing
to flush, and every caller already treats a rejection as "probe failed, use the
safe baseline". `killSignal` is listed before the option spread so a caller
that needs a gentler signal can still ask for one.

* fix(daemon): stop discarding a codex Child that its parent re-invoked

A Codex sub-agent does not run once. The parent re-invokes it, and every
invocation opens another turn in the Child's own rollout, so `started` followed
by N-1 `interacted` is the ordinary shape of one delegated Build Package. The
collector treated a Child with more than one own turn as ambiguous and dropped
it, which meant a complex Run delegating three packages had all three Children
discarded and reported `knownChildCount: 0` with
`availability: partial` — while three complete Child lifecycles sat on disk,
each declaring the parent thread id.

The guard was defending something real: terminals are read from the parent's
activity list, so handing every turn the whole session's activities would let
one parent record terminate them all and stamp them with a single `endedAtMs`.
Slice the activities by the turn they occurred in instead — the parent's
records align with the child's turn starts within tens of milliseconds — so
each turn reads its own terminal, and a turn the parent never closed falls back
to the terminal its own rollout recorded.

Known gap, left for a follow-up: `knownChildCount` is keyed per (session, turn),
so it now reports one entry per invocation rather than per sub-agent. It feeds
the coverage diagnostic only — the complex gate counts observations — but it
makes codex's figure incomparable with the per-child count OpenCode reports.

* feat(strategy): port spec normative content into OD Next assets (#7141)

* feat(daemon): admit Vela to the OD Next capability registry

Vela drives the same native OpenCode runtime this registry already admits under
`native-opencode`. Its Child mechanism is that runtime's, reached over the ACP
extension instead of the CLI stream, and its fixture passes all seven evidence
paths. Holding the tuple out therefore never withheld an unproven capability —
it refused OD Next to an agent on the separate grounds that the build is not
yet immutable, which left every AMR run on the legacy plugin with
`od_next_rollout_x1_capability_fixture_unverified`.

Admit it. Re-pin `agentCliVersion` here once Vela publishes a build reporting a
stable producer version; that is the condition the old comment was really
waiting on, and it is a version pin, not an admission question.

Verified against a live daemon: AMR now resolves `od_next_rollout_eligible` /
`active`, and a simple full-plan task runs through OD Next to
`outcome: completed` with a valid deliverable. Complex still blocks for a
separate and honest reason — the installed Vela advertises no
`child_agent_lifecycle` producer, so no Child can be observed at all — which is
a Vela-side capability, not a gate.

* fix(daemon): stop one runtime's unverifiable Children from disabling OD Next

The rollout stop latch is `daemon_instance`-scoped and survives restart, so it
must be reserved for failures of the machine contract itself — those are true
whichever agent hit them. Unverifiable Children are not: they are a property of
one runtime. Vela ships no child-lifecycle producer today, so an AMR complex
Run cannot be certified at all, and every one of them took OD Next away from
Codex, Claude and OpenCode until an operator ran `od strategy rollout reset`.

The same latch also fired after a SUCCESSFUL run. `availability` demotes to
`partial` on any limitation, and a Codex collection always carries
`codex_inherited_turn_excluded` — children replay their parent's turns — so a
complex task that completed and delivered still disabled the strategy behind
itself. This was observed on a live daemon: the task reached
`outcome: completed`, and the rollout latched `complex_child_unverified`
seconds later.

Child evidence now raises no daemon-wide signal. That task is already
fail-closed with its reason codes persisted, which is the proportionate
response; route/mode drift and machine-contract leaks still latch.
`complex_child_unverified` stays in the reason-code union so daemons already
holding that latch, and the records explaining them, keep parsing.

* fix(daemon,contracts): count Child agents, not their parent's re-entries

`knownChildCount` had no stated unit, so each adapter picked one. OpenCode
counts distinct Child ids; Codex identifies an observation per (session, turn)
and the count was derived from observation ids at the call site, so a Child its
parent re-entered was counted once per invocation. A run with three sub-agents
reported four, and the two runtimes' figures were silently incomparable.

Say the unit in the contract — distinct Child agents, not invocations — and let
the Codex adapter own its own figure, the way the OpenCode adapter already
does. The per-turn observation identity is unchanged: it is what keeps each
invocation's lifecycle separate, which the complex gate still reads.

Verified on a live codex complex run: three native sub-agents now report
`knownChildCount: 3`.

* fix(daemon): stop inferring a complex completion nobody verified

The production-completion inference recovers a task the agent finished but
failed to declare, resting on Open Design having resolved the evidence itself.
For a simple plan that evidence IS the canonical deliverable, so the inference
is sound. A complex plan additionally owes verified native Child lifecycle —
the property that makes it complex — and no deliverable check substitutes for
it.

Accepting any locked execution mode therefore certified Children nobody
observed. Caught on a live AMR complex run: the installed Vela advertises no
child-lifecycle producer, the collector reported `knownChildCount: 0` with
`availability: unavailable`, the turn declared nothing, and the task landed
`outcome: completed` — walking straight past
`evaluateOdNextComplexChildEvidence`. The same run blocks correctly once the
inference is confined to simple.

* docs(daemon): record that Vela's Child producer is verified end to end

A local Vela built from the unmerged child-evidence branch negotiates
`vela.opencode.child_agent_lifecycle`, and an OD Next complex task on AMR now
reaches `outcome: completed` with `availability: complete`,
`source: vela_opencode_acp`, `knownChildCount: 3` and no limitations — three
native OpenCode sub-agents, one per Build Package, each observed through the
ACP extension. `verifiedRuntimeSupport` is the flag that makes exactly that
claim, so it is no longer false. `published` stays false: the producer is not
on Vela's main, so no user can install a build carrying it.

Also write down why this needs no version gate. Child evidence is reached by
capability negotiation, not by sniffing `agentCliVersion`: a Vela without the
producer never advertises it, so a complex task blocks on honestly missing
evidence, and one that has it works the moment it is installed. Admitting the
tuple therefore cannot make an older Vela claim Children it never reported,
and nothing has to be walked back when the producer ships.

* fix(strategy): default prototype demo content to real referents

Field test on beta.104 (claude, active, completed): the model read the
authentic-imagery clause, then legitimately routed around it by choosing
fictional demo books with CSS-drawn typographic covers — the clause is
conditional on content referencing a real entity, and nothing constrained
that upstream choice. The user-intended behavior (real books with real
fetched covers, for texture and credibility) never triggered.

Close the loophole with an affirmative default: prototype sample data
defaults to real, well-known referents with their real fetched images;
fictional stand-ins are a disclosed exception (user request or licensing),
and de-realizing content to avoid the fetch is named and forbidden. Core
baseline gets the matching one-liner so the default reads as baseline
discipline, not a profile-local whim.

Guard sweep zero hits; plugin-runtime 71/71, contracts 397/397.

* fix(od-next): stop offering to continue work the task already delivered

A finished Full Plan turn could read as unfinished, offer to continue itself,
and have that offer end in a second task that could only fail.

A Run carries two accounts of whether its declared work is done. OD Next's own
verdict reaches `completed` only after the coordinator confirmed BOTH a
succeeded process AND a canonical deliverable resolved on disk — the entry file
exists, is readable, was touched by this Run, and matches the project kind. The
other account is the agent's last TodoWrite snapshot, unverified narration that
agents routinely leave with a stale `pending` item after writing the artifact.

The snapshot was winning. QA saw a task settle `completed` with seven artifacts
delivered, stamped `endedWithUnfinishedWork`, offering "continue remaining
tasks"; taking that offer opened a second task with nothing left to write,
which validated as `no_artifact` and settled `blocked`. One project then held a
completed and a blocked task for the same work.

Let the verified verdict outrank the narration, through one predicate shared by
the daemon's terminal classifier and both surfaces that read the snapshot — the
pinned todo card's continue affordance and the per-message footer. A
mid-generation truncation stays an independent term, so a turn cut off by
max_tokens is unfinished whatever verdict was recorded.

The verdict also has to survive a reload. The messages table has no strategy
column, so it lives only in the task store, and the client's projection probe
skips a task that is already terminal — reopening the project brought the bogus
offer straight back. The conversation read path already joins the task store to
place each message in its logical turn; it now carries the verdict along the
same join.

Watching a real Full Plan run in Chrome surfaced a second defect on the way in.
The live stream re-points ONE assistant message at each successor Run, so that
message accumulates every stage's output, while the daemon persists one message
per Run. On the post-run refresh the two views meet and
`mergeServerMessageWithLocal`'s "a longer local body is fresher" rule (#6396)
reads the accumulation as freshness and keeps it, beside the successor's own
row: the production answer rendered twice, live, until a reload cleared it.
Scope that rule to messages with no later sibling in the same task — the final
Run has nothing to have absorbed, so the guard is untouched for it and for
every ordinary turn.

Deliberately unchanged: `artifactCount <= 0 -> no_artifact` still blocks. That
guard is what stops a turn claiming completion it never delivered, and the
recovery case QA hit disappears once the offer stops appearing on finished
work. A turn that legitimately has nothing left to write needs a "no change
required" outcome in the protocol, which is a product decision, not a defect
fix. Note also that a settled task cannot be resumed — the daemon rejects any
continuation of a terminal task with 409 — so a continue affordance has no
choice but to open a new task.

Also drops `onStrategyTaskContinuation`, a client callback with no consumer
whose contract described re-pointing the existing message at the successor Run
— the very shape that prints the answer twice. The daemon still emits the
diagnostic into the Run event log, where it documents the hand-off for anyone
reconstructing a multi-Run turn.

Red first: the end-to-end spec fails on the pre-fix daemon with
`endedWithUnfinishedWork` true, again with the reload verdict `undefined`, and
the merge spec with the production answer counted twice.

* fix(web): stop leaking the strategy package above a user prompt

An OD Next turn opened with two rows of chrome the user never asked for: a
"Design" session-mode chip and a "Using OD Next Strategy V2" disclosure that
named the internal strategy package and its version. Neither is context the
user attached to the message — the daemon binds the strategy package itself,
and the mode chip only restates the default that a strategy-owned design turn
already implies.

Key the suppression on `AppliedPluginSnapshot.strategy`, which the daemon
stamps only for an internal strategy apply (verified against real persisted
snapshots: OD Next rows carry `strategy_json`, ordinary plugin rows store
NULL). A user-picked plugin, skill, design system or workspace reference still
renders exactly as before, and Ask/Plan stay labelled everywhere because those
are per-turn behaviour choices the user made.

An OD Next design turn now opens with the prompt itself.

* fix(strategy): add action-level layout mechanics to the baseline

Field test on beta.105 (first AMR active sample, deepseek-v4-flash,
prototype completed): the artifact shipped two elementary geometry
defects — the city switcher and daily-judgment regions overlapped, and a
stat numeral's percent sign wrapped to a second line inside a fixed-size
ring. The existing rules could not have prevented either: they are
outcome-level ('overflow-free layout', 'no overflow or clipping risk'),
and under ship-on-write the model never sees the rendered outcome it is
asked to guarantee.

Replace the bet on outcomes with rules about the source itself — verifiable
while writing: flow-only layout for sibling content regions (no absolute
positioning / negative margins / transforms to stack them), fixed-size
boxes only for fixed-size content, clamp()-sized display numerals with the
unit pinned to the same line, block-level display declared before sizing
track-style elements, no hidden-overflow masking, no 1–2 character
orphans. Core gains the Layout mechanics baseline bullet; the prototype
quality floor names the two observed failure shapes.

Guard sweep zero hits; plugin-runtime 71/71, contracts 397/397.

* fix(strategy): add measure-then-size image geometry to the baseline

Follow-on field sample: a fetched, localized movie poster rendered
cropped inside a container whose fixed ratio the model invented — a
failure class created by the real-first imagery clause itself, since
fetched assets carry intrinsic dimensions the model never authored.

The fix is procedural, not aspirational: read the file's intrinsic
width/height during Build (input probing sits inside the ship-on-write
allowance), give the container the measured ratio (aspect-ratio or
natural flow), reserve cover-cropping for decorative backdrops, and
render content-bearing images — posters, covers, artwork, product
shots — at their full frame with no double-locked axes. Core gains the
Image geometry baseline bullet; the prototype quality floor names the
observed failure shape.

Guard sweep zero hits; plugin-runtime 71/71, contracts 397/397.

* fix(strategy): anchor overlays on the anchoring axes only

Address PerishCode's review: 'explicit inset on every side' would, taken
literally, set all four offsets on an auto-sized absolutely positioned
badge and stretch it to fill its containing block — the exact malformed
overlay geometry this baseline exists to prevent. Anchor with offsets
only on the anchoring axes (top plus right for a corner badge), size
constrained independently, and name the stretch trap explicitly.

* revert(strategy): split image geometry out of this bugfix

Per review: the measure-then-size image-geometry policy addresses a
separate field failure (localized poster cropped by an invented fixed
ratio) and belongs in its own focused PR with that reproduction. This
branch returns to its stated objective: region overlap, stat-unit
wrapping, and track sizing.

* fix(strategy): add measure-then-size image geometry to the baseline

Field reproduction (beta.105, od-next active, prototype route): the
real-first imagery clause now makes runs fetch and localize real assets
— and the first such sample shipped a localized movie poster rendered
cropped, because the model invented a fixed container ratio for an
image whose intrinsic dimensions it never authored, then let cover-crop
hide the mismatch. The page showed a partial poster frame: wrong ratio,
content cut off. This failure class is created by fetched assets
carrying dimensions the model has not seen, so it cannot be prevented
by the existing outcome-level rules.

The fix is procedural and write-time-verifiable: read the localized
file's intrinsic width/height during Build (input probing sits inside
the ship-on-write allowance), give the container the measured ratio
(aspect-ratio or natural width:100%/height:auto flow), reserve
cover-cropping for decorative backdrops, and render content-bearing
images — posters, covers, artwork, product shots — at their full frame
with no double-locked axes. Core gains the Image geometry baseline
bullet; the prototype quality floor names the observed failure shape.

Split out of #7169 per review scope discipline.

Guard sweep zero hits; plugin-runtime 71/71, contracts 397/397.

* fix(strategy): allow intentional fixed and sticky chrome

Address PerishCode's review: 'normal flow only' contradicted the
prototype profile's own environment rules, which expect fixed headers,
bottom navigation, and floating controls that reserve matching padding —
a model following the core rule literally could strip valid mobile
bottom navigation. Narrow the prohibition to its actual target
(accidental stacking of sibling content regions) and state the chrome
allowance with its padding obligation in both the core bullet and the
prototype quality-floor line.

* fix(web,daemon): keep internal turn chrome and control markers out of the chat

Two acceptance findings, both the same shape: daemon-internal plumbing reaching
the reader.

The session-mode chip flashed "Design" above a freshly sent prompt and dropped
it a beat later. The optimistic user message renders before POST /api/runs
answers, so the strategy binding the previous fix keyed on simply is not there
yet. Key the chip on the mode alone instead: it labels a turn the user steered
away from the default, so Ask and Plan stay labelled and Design — the mode every
design project already runs in — carries no chip. That is decided the moment the
message renders, with no snapshot to wait for.

`<od-title>…</od-title>` rendered as message body. The stripper was armed only
for Runs that requested a title, and the web only requests one on the first turn
of a conversation, so from turn two onward nothing consumed the marker while the
directive was still sitting in the agent's own resumed session history. Arm the
stripper on every Run and swap only the announce callback, then strip again in
the chat: the OD Next machine protocol blocks join the title marker there, and
messages already persisted with a leaked marker render clean, which no
daemon-side fix can reach. Renderable artifacts (question-form, od-card,
artifact) are deliberately excluded.

* fix(web,daemon,strategy): stop a stray question-form marker from stalling a turn

An OD Next turn that needed no clarification narrated its decision straight
into the marker — `<question-form> 无需提出——…`, an unclosed tag whose body is
prose. Four surfaces independently read "an open marker is present" as "a form
is being generated", at three different strictness levels, and none of them had
a state for markup that can never render:

- the web parser raised a loading skeleton and truncated every character after
  the marker, so the turn's real output was invisible;
- `hasPendingQuestionForm` held the turn in a clarification handshake;
- `resolveDesignDeliveryOutcome` returned `awaiting_input` before looking at
  what the run had delivered;
- `listProjectsAwaitingInput` matched the marker with a raw SQL `LIKE`, latching
  the project on `Needs input` until the user typed something.

The daemon's own strict detector meanwhile scored the stray marker as zero
forms, so the coordinator accepted the turn with an empty reason-code list. The
one component that could have reported the contract break was blind to it by
construction.

Give every one of those decisions the same precondition — an open marker only
means a form is in flight while the text after it could still parse as the form
body — and make the malformed case reportable instead of silent. The two new
coordinator codes are attribution, never a gate: every code `validateAcceptedTurn`
returns blocks the turn, so a stray marker on an otherwise valid planning turn
still reaches production.

The awaiting-input queries needed care. They take the newest form-bearing
message per partition and test only that one for an answer, so the strict check
has to run before the newest-per-partition pick. Post-filtering the winning rows
would let a stray marker win its partition, fail the check, and drop a project
that is genuinely waiting on a real unanswered form. Both directions of that
ordering are pinned by specs, and the scan streams without retaining message
content because it runs on every `GET /api/projects`.

The strategy prompt now also forbids restating the marker to announce that no
form is needed. Every clarification rule was phrased as "when to emit a form" /
"skip the form"; nothing forbade writing the bare marker as a label, which is
what produced the turn.

* fix(strategy): define the session-skill conflict order (#7188)

The Instruction order named the three session-skill layers but gave no
tie-break when they conflict inside their shared applicable scope, so a
user-selected skill and a bundled profile could pull the model in
opposite directions with no principled resolution. Make the order
explicit: user-selected skills first, then the general orchestration
Skill, then the task-type profile — and state that the tie-break never
unlocks what higher rules forbid (ship-on-write actions, the V2 machine
contract, the user's explicit requirements). Spec source of truth
updated with matching wording in both languages.

Co-authored-by: NJUHua <113895241+NJUHua@users.noreply.github.com>

* feat(od-next): restore anti-cliché visual rules across the strategy package

The od-next rewrite dropped the legacy anti-slop blacklist (core-slim /
discovery ❌ lists), leaving the package with no background-tone rules at
all — prototype.md even referenced 'the anti-cliché rules above' that did
not exist. Models revert to their warm-cream 'AI look' prior on every
task type.

- core-system-prompt.md: new cross-task 'Anti-cliché defaults' block in
  the Design baseline (warm beige/cream grounds, purple-gradient wash,
  stock UI display faces, left-border accent cards, icon-per-heading,
  gray hover, hand-drawn SVG people, invented metrics)
- prototype.md: resolve the dangling reference with a labeled rule
  cluster; add the designer/demo-controls ban
- ppt.md: new 'Avoid deck clichés' section (gradient-wash covers,
  icon-per-bullet, center-everything, corner blobs)
- marketing.md: cream/stock-gradient ground ban + never invent awards,
  badges, press quotes, or testimonials
- hyperframes.md: cream color-wash grade + particle/glow filler ban

Asset versions unchanged: package identity tracks content via sha256
digests; tests pin '2.0.0' literally.

* feat(web,daemon,contracts): carry an official example card into OD Next

Clicking an official example card under a task type pinned that example as an
executable plugin. The pin stamped explicit_user authority, which took the task
off the automatic route entirely — no strategy task, no plan, no clarification
round — while free text under the very same chip entered OD Next normally.

An example card is a more specific choice inside a task type, not a claim on
the strategy. So send its identity instead of a plugin pin: `exampleReference`,
which the daemon re-resolves through the same local catalogue lookup
`/apply-local` performs, reading the example's bytes from the resolved path and
never from the request body. Its SKILL.md and the side files its prose names
then travel where user-chosen material belongs — `session_skills/
user_selected_skills`, a bundle region that already existed in exactly this
shape and had no producer. No bundle format change, no schema bump.

The discriminator has to be the call site, not the record: the same
`InstalledPluginRecord` is simultaneously an example card, a chip's default
scenario plugin, and a Community entry, and nothing in its data tells the three
apart. `examplePick` is therefore set only by the example-rail handler, and a
genuine plugin pick from the Community page keeps its explicit authority. An
example card under a chip with no OD Next route — wireframe, mobile, image,
video — behaves exactly as before.

Drop the planning/Build-only guard from user-selected Skill capture. Measured
against real content it rejected most of the corpus it was applied to while
protecting almost nothing: it is 15 English regexes, so the same sentence
passes or fails on language alone — "After the build, run through the checklist
and fix any defects" is refused and its Chinese equivalent is admitted. It also
false-positives on prose that merely names the word, and it never inspected
side files at all, so a `references/checklist.md` always walked past it. 47 of
183 shipped examples tripped it, including all three default cards. Conflicting
instructions are resolved by the Instruction order the core system prompt
already defines; first-party strategy assets keep the guard, where the corpus
is controlled and the rule works.

Two latent bugs in that rail surfaced once it had a caller, both of the same
shape — one bad side file deleting an entire Skill. A prose link to a file that
was since flattened away (`example-hyperframes`) and a bundled screenshot past
the per-file byte cap (`example-open-design-landing`) each sank the whole
capture. Neither says anything about whether the Skill is safe to carry, so
both are skipped; a symlink, a symlinked parent, or a file that changes under
the read still fails closed. The byte cap keeps its purpose — oversized bytes
are never read — and `readBoundedNoFollow` still enforces it on what is read.

Answer the brief's template from the card's own input defaults. `od.useCase.
query` is a template that the ordinary route fills from the applied snapshot's
inputs; an example card deliberately creates no snapshot, so without this the
Agent received `{{artifactKind}}` and `{{audience}}` raw — a broken template
rather than an assignment. A field with no declared default keeps its token
instead of inventing an answer the card never gave.

Verified against the shipped corpus: 182 of 183 example folders capture and
serialize into a `user_selected_skills` region, the one exception shipping no
SKILL.md and belonging to no OD Next route. 102 cards across Prototype, Deck
and HyperFrames now enter the automatic route; the other 81 are untouched.

* fix(web,contracts): keep a nested Prototype scene on the OD Next route

Picking a second-level scene under Prototype — Mobile app or Wireframe — took
the task out of OD Next entirely, for free text and example cards alike, while
the first-level Prototype chip worked. Two independent layers excluded them and
cross-check each other fail-closed, so neither could be relaxed alone: the
composer swapped the route id to the scene's own catalog action id, which the
route table does not carry, and the metadata re-derivation separately rejected
`fidelity: 'wireframe'` and mobile platform targets.

Neither exclusion was protecting anything. The two scenes are the Prototype
route plus metadata, as their own chip definitions say — both bind
`example-web-prototype` with `kind: 'prototype'` and `automaticDefault: true`,
differing only by `platformTargets` or `fidelity`. And the Prototype task
profile already branches on exactly those two: it downgrades content and
interaction-state requirements when fidelity is wireframe or lo-fi, and it
carries mobile guidance down to touch reach and the 375/768/1024/1440
breakpoints. The route was being denied to the cases the profile was written
for.

A second-level scene refines WHAT to build, never WHETHER the parent task
type's route applies. Make that structural rather than enumerated: routing
resolves the first-level task type through `taskTypeChipIdForChipId` instead of
reading whichever catalog action the scene happens to bind, so a scene has no
route id of its own to route by and a scene added later cannot strand its
parent. Adding table rows would have fixed today's two and left every future
scene a silent-regression candidate.

On the contracts side `intent` is now the only field that can move a project
off a route — it is the only one naming a different pipeline. `fidelity` and
`platformTargets` stay in the signature to record that the decision has seen
them and chosen not to gate on them.

Deck and HyperFrames were never affected and now have witnesses that say so:
Deck's second-level rail is a card filter that never leaves the hero, and
HyperFrames has no rail at all. A catalog sweep pins the routed chip set to
exactly deck, hyperframes, mobile, prototype and wireframe, so Image, Video,
Audio, Document, Website clone, WebGL, Figma and the rest cannot join it
silently.

* refactor(web): make a nested Prototype scene a second-level type

#6914 moved Mobile app and Wireframe out of Home's first-level rail and into
Prototype's second-level scene rail — but only in the UI. Underneath, both
stayed full first-level chips with their own `apply-scenario` action, merely
hidden from the create rail, and the scene pointed back at them through
`actionChipId`. Its own comment recorded the compromise: keep their catalog
action ids so the nested scene preserves the metadata those actions stamp.

A thing that renders as a refinement of Prototype while still being a task type
in its own right is what dropped these two scenes out of OD Next: the composer
resolved the route from whichever action the scene bound, and Mobile and
Wireframe are not routes. `f360f46e0` stopped the routing site from asking.
This removes the ambiguity it was working around.

A scene now carries its own refinement — Mobile stamps the handheld platform
targets, Wireframe stamps the lo-fi fidelity — and binds its parent. The two
chips leave the catalog, because they were never task types. The refinement
type cannot restate its parent's `kind`, so a scene is structurally incapable
of becoming a type again.

Nothing the user sees changes: same eight scenes, same labels, same metadata
stamped byte for byte, same Prototype route, same example-card behaviour. The
integration assertion was tightened from `objectContaining` to an exact match
to say so, and the unit specs compare the serialized metadata rather than its
fields, since project metadata is persisted and compared as JSON.

Legacy ids still arrive from outside the catalog, so normalization moved to the
two boundaries where that actually happens — a persisted composer draft and a
queued one-shot chip intent — instead of living at the routing site. The queued
intent had no coverage and relied on `pickChip` normalizing before `findChip`;
it has a spec now. `taskTypeChipIdForChipId` is gone: with the scenes out of
the catalog, `active.chipId` is a task type by construction, and every producer
was traced to confirm none can yield a scene.

The placeholder carousel had the same latent shape — three scenario lines bound
a scene as if it were a chip — so they bind Prototype plus a scene id, and the
carousel narrows by scene slug. A one-click create from a scene's carousel card
now has a spec proving it keeps both the refinement and the route.

* fix(web): stop a restored composer pick from costing its task type

Dismissing the pill of an example card returned the Home composer to nothing —
no task type, no OD Next route, and the user's typed brief wiped with Send left
disabled — but only after the composer had been remounted. Opening a project,
visiting Settings, any EntryView swap: the composer comes back looking
pixel-identical, same type selected, same scene, same pill, same brief, and the
dismiss now destroys instead of stepping back.

The persisted chip draft records what was picked and never how. A restored
example card therefore comes back as an ordinary chip binding, while still
rendering its own pill and dismiss control — that check asks whether the record
differs from the chip's default plugin, which is true for every example card.
The dismiss handler keyed its step-back branch on the pick being explicit, so a
restored pick missed it and fell through to the reset that clears everything.

Persist how the pick was made and restore it, so a pick is the same object
after a remount as before one. Then key the step-back on the pick naming a task
type rather than on it being explicit: whatever a pick was, if it named a type
the user chose, dismissing it returns there. Only a pick that never named one
resets the composer, and the typed draft is left exactly as typed either way.
Drafts written before this carry no flags and restore exactly as they do today.

The same lost provenance had a second victim: an example card that was never
dismissed stopped sending its `exampleReference` after a remount, so the card's
own material silently vanished from the run while the route survived — on all
three OD Next routes.

The reproduction drives the real rails and the real bundled catalog through ten
first-level and second-level combinations, each on a fresh mount and across a
remount. The differentiator was never the type or the scene: every combination
loses its route once the pick's provenance has been through storage.

* feat(od-next): ship handheld device shells with the prototype task profile

Blind evals against the live slim-sp strategy showed OD Next losing every
phone-app prototype on one detail: the live route hands the agent a real
handset seed (design-templates/mobile-app), OD Next only described one in
prose and the agent drew a rounded card by hand.

- Declare task-profile `resources` in the bundled strategy contract; the
  prototype profile ships iPhone / Android / platform-neutral shells that
  enter the package identity with the profile (rule-card and shell edits
  move the hash alike) and are re-verified at consumption.
- Stage the shells into `<cwd>/.od-frames/` on every strategy run and
  add a "Handheld device shell" section to the prototype rule card with
  the brief→shell table and the shell invariants.
- Resolve the platform from project metadata and the user's own words
  (ios / android / mobile-neutral; responsive and desktop briefs veto the
  platform-less class), latch it per conversation, and quote the selected
  shell into the stable request context as `device-frame` +
  `device-frame-shell` facts.
- Observe at run finish whether the delivered entry kept the shell and
  expose it on run_finished (od_next_device_platform / _source /
  _shell_present) so the rollout can measure adoption.
- Shells collapse to a full-bleed phone page below 480px and scale on short
  viewports so the artifact still meets the 375px rule.

* docs(od-next): describe task-profile resources in the strategy package SKILL.md

* fix(od-next): stage device shells non-destructively and cover the collab exclusion

Review: `.od-frames/` is a name this PR introduces inside a user-owned
project directory, so wiping it on every run could delete pre-existing
content. Staging now keeps an ownership manifest
(`.od-frames/.od-next-device-frames.json`): only files it recorded earlier
are replaced or retired (and retired only while their bytes are still
ours); a pre-existing file under a managed name is left untouched and
reported as skipped; unrelated files are never read, written, or deleted.
Tests prove an unrelated file survives, a foreign `iphone.html` survives
repeated staging, and a user-edited staged file is not retired.

CI: `.od-frames` joins the expected member-mirror exclusion list in
`vela-cli-resource-adapter.test.ts`, which pins that list exactly.

* fix(od-next): prove device shell bytes are ours before replacing them

Review: listing a name in the ownership manifest was not enough to claim
it. After the first staging a user could edit `.od-frames/iphone.html`
and the next run's unconditional `writeFile` destroyed that edit, and a
pre-existing `.od-next-device-frames.json` under an unrelated schema was
read as an empty manifest and then overwritten.

Ownership is now provisional on both sides. A managed name is written or
retired only when the manifest claims it AND the bytes on disk still hash
to the recorded digest — the rule the retirement path already followed,
now applied to replacement too. A digest that moved makes the file the
user's: it is reported as skipped and drops out of the manifest, so later
stagings keep their hands off it. The control file gets the same
treatment: `readOwnership` classifies the name as absent / ours / foreign
instead of collapsing unreadable, malformed, and unrelated content into
an empty manifest, and a foreign control file makes the whole directory
off limits — nothing written, nothing deleted, every managed name
reported as skipped. In each case the quoted `device-frame-shell` fact
still carries the shell source, so the run itself is unaffected.

Two fixture cases, both red on the previous head: a shell edited after
staging survives repeated staging byte-for-byte, and a pre-existing
invalid manifest plus the user's own `iphone.html` survive untouched with
no shells created.

* fix(od-next): bound the device shell manifest to the names it can stage

Review: the manifest lives inside a directory the project controls, so
its key list was letting the project widen this feature's deletion set.
The parser accepted any slash-free basename with a SHA-256 digest, and
the retirement loop then deleted any accepted entry missing from the
current package while its bytes still matched. A project holding a
same-schema `.od-next-device-frames.json` that claims `leftover.html`
with that file's real digest therefore lost `leftover.html` on the next
strategy run — verified, not hypothetical: the new fixture fails on the
previous head with `ENOENT` on that file.

The bound is now this feature's own filenames rather than whatever the
manifest names. `MANAGED_SHELL_FILES` is derived from
`OD_NEXT_DEVICE_FRAME_FILES`, and a manifest is `ours` only when every
key is in that set and every value is a 64-hex digest — that is exactly
what this materializer writes, so anything else came from somewhere else
and the whole record is refused as foreign, leaving the directory alone.
Refusing beats salvaging the well-formed entries here: salvaging is what
would let a crafted entry keep nominating an unrelated file. The
retirement loop re-checks the allowlist as well, so the deletion set
stays bounded even if the parser is later loosened.

Manifests written by the previous head hold only shell names and valid
digests, so they keep reading as ours; no migration.

* fix(od-next): let an @-mentioned Skill ride the automatic route

Mentioning a Skill in the composer took the turn off OD Next entirely. The
project fell back to its task type's legacy plugin, so the chat header
announced "正在使用 Web Prototype" and the run reached the agent as ordinary
prose with no Prompt Bundle at all — no Runtime State, no Plan Contract, none
of the strategy the task type was supposed to own.

Three layers each dropped the Skill or the route, and every one of them had to
give for the turn to survive intact:

- The admission gate counted a named Skill as explicit user authority, next to
  a pinned plugin and a pinned snapshot. A Skill is not an executable surface
  and cannot run a strategy; it is material for one. It now stays out of that
  read, and a context plugin remains the only non-plugin signal that still
  claims the route.
- Home treated the task type and the Skill as mutually exclusive: picking
  either one wiped the other, and submit dropped whichever survived. That rule
  came from #2972, which asked for a defined answer when the prompt's intent
  and the picked card disagree. "The Skill wins, drop the card" is defined, but
  it routed a user who picked 幻灯片 into a prototype project, which is exactly
  the silent misrouting #2972 objected to. The task type now owns the route and
  the Skill is material carried inside it, so the conflict resolves without
  discarding either.
- The create handoff nulled `skillId` the moment a task profile was claimed,
  which would have swallowed the Skill one layer after Home stopped dropping it.

The catalogue capture entry point for exactly this already existed and had
never had a production caller — the gate above made it unreachable. It is now
wired through a single session capture that converges both channels a session
can select material through, because one task freezes ONE package: an
@-mentioned Skill and an official example card land in the same
`session_skills/user_selected_skills` slot, the mention first, which is the
authority order the strategy's own conflict rule already assumes.

Capture is fail-soft on every leg, matching the example-card rail: a Skill
deleted between picking and running, a catalogue that will not list, a
selection past the package bounds — each drops that material and runs the task
without it. The ordinary route drops an unresolvable Skill silently too, so
this is the same contract rather than a new leniency, and an unfreezable
selection can never cost the project the example card it was created from.

Skill-catalogue scoping moved to one helper shared with the system-prompt
composer. A Skill one surface can resolve is now a Skill the other can freeze;
resolving them from separately-derived partitions would drop a Workspace Skill
on whichever side guessed narrower.

Coverage: the three request shapes that carry a Skill (`ChatRequest.skillId`,
`skillIds`, and the Skill persisted on the project row by Home's create) each
have an HTTP witness asserting the Bundle text; all four routed task types are
covered for a mention arriving alongside their default card. Two server tests
that encoded the old "Skills route ordinary" contract, and the two Home specs
that encoded the mutual exclusion, were rewritten rather than deleted — each
now states the rule that replaced it.

* fix(od-next): accept a HyperFrames composition as its canonical deliverable

A HyperFrames run that wrote an editable root `index.html` was blocked with
`od_next_canonical_deliverable_invalid`, surfacing as "任务执行失败" even though
the artifact was sitting in the project and working. Every HyperFrames
production turn in the reported diagnostics bundle hit it.

`validateRunDeliverable` gated the entry's file kind on `metadata.kind`, and
`kind: 'video'` accepted only `video` files. But `kind` records which Home
surface a project was created from, not the shape of what it delivers. For
every other kind those coincide; HyperFrames breaks the identity — it is a
local HTML-to-video renderer, so the HTML composition is the authored artifact
and the MP4 is a render of it, produced on demand and not necessarily present.

OD Next already said so on the way in: `DAEMON_OWNED_OUTPUT_KINDS.hyperframes`
admits `html` and `source` as supported deliverable kinds at plan time, so the
same daemon approved a plan the completion check then rejected.

Resolve the accepted kinds from the project's metadata instead of its bare
`kind`, via one named invariant used by both entry inference and the kind gate.
Generative video (fal / Veo / Sora / Volcengine) keeps the strict video-only
contract: there the provider returns a video file, and an HTML preview page in
its place is a genuine delivery failure.

The `hyperframes-html` discriminator moves next to `ProjectMetadata` so
delivery validation and analytics `project_kind` read one constant.

Red spec: `apps/daemon/tests/run-deliverable-validation.test.ts` reproduced the
production `type_mismatch` before the fix, and covers both guards after it.

---------

Co-authored-by: Cheems <94773058+itscheems@users.noreply.github.com>
Co-authored-by: lefarcen <935902669@qq.com>
Co-authored-by: Cheems <cheems1229282@proton.me>
Co-authored-by: 黄桃 <huangtao@huangtaodeMacBook-Pro.local>
Co-authored-by: open-design-crew[bot] <299007234+open-design-crew[bot]@users.noreply.github.com>
Co-authored-by: NJUHua <113895241+NJUHua@users.noreply.github.com>
Co-authored-by: hihijack111 <216108319+hihijack111@users.noreply.github.com>
2026-08-24 08:53:36 +00:00
Joey-nexu f1a0b60c6c chore: unify product name display to OpenDesign repo-wide (#6998)
* chore: unify product name display to OpenDesign repo-wide

Rename the user-facing product name from "Open Design" to "OpenDesign"
across documentation, web/desktop UI strings, i18n locales, prompts,
plugin/skill/design-system content, changelogs, and specs, matching the
landing-page rename already in flight.

Deliberately excluded so installed-app identity stays untouched:
- packages/release PRODUCT_NAME and everything derived from it
  (app bundle names, installer/registry identity, updater feeds)
- tools/pack, tools/release, apps/packaged, landing page (PR #6983)
- mocks/ recorded traces and LICENSE
- test fixtures and comments that encode real on-disk install paths
  (Open Design.app, Program Files\Open Design, AppData paths)

Validated with pnpm guard, pnpm typecheck, and targeted web/desktop/host
test runs over the renamed strings.

* fix(web): keep two-letter author monogram for CamelCase brand names

authorInitials split words on whitespace only, so the renamed single-token
author name "OpenDesign" produced a one-letter monogram. Treat lower-to-upper
CamelCase boundaries as word breaks so the avatar fallback stays "OD".

* fix(release): restore spaced Windows source artifact names in win.ps1

tools/pack still derives builder outputs from the unchanged PRODUCT_NAME
("Open Design"), so the asset script must look for the spaced filenames.
Matches the mac/mac-intel/linux asset scripts, which were already kept on
the original names.

---------

Co-authored-by: Joey <236967869+joeylee12629-star@users.noreply.github.com>
2026-08-18 07:09:45 +00:00
Ethan Guo c110e40e8d fix(frontmatter): parse flush-left YAML sequences, quoted inline arrays, and deep block scalars (#5499) 2026-07-16 07:34:41 +00:00
Tom Huang bc6a246c65 feat(slides): enhance deck project experience (#5083)
* feat(deck): enhance slide deck functionality with speaker notes and navigation improvements

- Added a switch for including speaker notes in slide decks, defaulting to true.
- Updated keyboard navigation to include 'R' for resetting to the first slide.
- Implemented click navigation for half-slide transitions.
- Enhanced the deck framework to support hidden programmatic prev/next controls.
- Improved handling of file inputs in question forms to support file submissions.

This update aims to improve user experience in creating and presenting slide decks, ensuring better accessibility and functionality.

* feat(artifacts): enforce semantic naming for user-facing deliverables

- Updated guidelines to require semantic filenames derived from user briefs for new artifacts, moving away from defaulting to `index.html`.
- Enhanced documentation to clarify naming conventions for HTML files, emphasizing the use of descriptive names like `investor-pitch-deck.html`.
- Adjusted prompts and hints across the system to reflect these changes, ensuring consistency in user-facing outputs.
- Improved test coverage to validate the new naming conventions and ensure proper implementation in various contexts.

This update aims to enhance clarity and usability in project file management, promoting better organization and user experience.

* feat(file-viewer): enhance speaker notes display and improve file workspace functionality

- Updated the speaker notes section to include a styled placeholder for empty notes, improving user experience.
- Changed the icon in the file workspace from "slides" to "panel-left" for better clarity.
- Introduced new sub-category filtering options in the file workspace, enhancing organization and navigation.
- Improved styling for the page creator search input and added visual feedback for loading states.

These changes aim to enhance usability and visual consistency across the file viewer and workspace components.

* test(web): align workspace tests with pages switcher

* test(e2e): open design files through pages menu

* fix(deck): keep source mode reachable

* fix(workspace): restore uploaded file tabs

* feat(deck-thumbnail): implement virtualized thumbnail rail for improved performance

- Introduced a new `DeckThumbnailRail` component to optimize the rendering of deck thumbnails, reducing memory usage and improving load times.
- Enhanced the `FileViewer` to integrate the new thumbnail rail, allowing for lazy loading of thumbnails based on viewport visibility.
- Added tests for the `DeckThumbnailRail` to ensure correct functionality and caching behavior.
- Updated styles for better visual consistency and performance during thumbnail rendering.

These changes aim to enhance user experience by providing a smoother and more efficient way to navigate through deck slides.

* feat(deck): enhance deck rendering with thumbnail support and keyboard shortcuts

- Integrated `DeckSlideThumbnail` for rendering individual slides in a shadow root, improving performance and reducing main-thread load.
- Updated `DeckThumbnailRail` to utilize the new thumbnail component, allowing for efficient slide navigation.
- Implemented keyboard shortcuts for deck navigation, enhancing user experience during presentations.
- Added tests for `buildDeckRenderInput` to ensure proper functionality of the deck stage fallback.
- Improved styling for loading states in the viewer, providing a smoother visual experience.

These changes aim to optimize the deck viewing experience by reducing resource consumption and enhancing interactivity.

* feat(deck): improve thumbnail loading and speaker notes editing experience

- Refactored `DeckSlideThumbnail` to enhance loading states with a new shimmer effect, improving visual feedback during thumbnail rendering.
- Updated `DeckThumbnailRail` to manage loading states for thumbnails, ensuring a smoother user experience.
- Simplified the speaker notes editing interface by removing the toggle switch, allowing direct editing from the notes body.
- Enhanced accessibility by adding roles and focus states to speaker notes elements.
- Added tests to verify the new loading states and speaker notes functionality.

These changes aim to optimize the user experience in deck navigation and speaker notes management, providing a more intuitive and responsive interface.

* feat(file-viewer): enhance version export functionality and speaker notes interaction

- Introduced new export options for file versions, including PDF and image formats, improving the ability to download and share content.
- Implemented a streamlined process for exporting version content, with progress indicators and error handling for a better user experience.
- Enhanced speaker notes editing by refining event handling to prevent unintended focus shifts, ensuring a smoother editing experience.
- Updated styles for improved visual consistency in the file version download interface.
- Added tests to verify the functionality of version exports and speaker notes interactions.

These changes aim to optimize the file viewer's usability and enhance the overall experience when managing and exporting project files.

* fix(web): polish export menus and creator filters

* fix(file-viewer): fall back to preview exports

* test(file-viewer): cover version export fallbacks

* test(file-viewer): assert loading skeleton by role

* fix(file-viewer): restore html source mode affordance

* fix(file-viewer): capture full version fallback snapshots

* docs: trim slides handoff eof

* test(exports): cover full preview snapshot requests

* test(file-viewer): account for snapshot options arg

* refactor(file-viewer, file-workspace): streamline export functions and enhance page creator dialog visibility

* test(file-workspace): enhance slide template creation and mock plugin responses

* feat(export): add support for versioned exports and enhance HTML export functionality

* feat(export): implement versioned export support across multiple formats and enhance related tests

* feat(file-viewer): enhance manual edit functionality with commit sequence tracking and add test for inline text session saving

* style(speaker-notes): update filmstrip and thumb-frame styles for improved responsiveness and add corresponding tests

* feat(file-viewer): enhance comment composer functionality with portal metrics and improve draw toolbar layout for mobile views

* feat(analytics): track slide deck viewing experience

Add analytics for the new deck/slides experience introduced in this PR:
the deck viewer surface, in-deck slide navigation, thumbnail rail, and
speaker-notes authoring. Presentation entry stays on the existing
present_popover click to avoid double-counting.

- contracts: DeckViewerSurfaceViewProps (surface_view),
  DeckViewerClickProps (ui_click), SpeakerNotesSaveResultProps
  (new speaker_notes_save_result event), wired into the event unions.
- web: track* wrappers + fire helpers in FileViewer — deck_viewer
  surface_view on deck mount, slide_prev/next/reset, thumbnail_select,
  thumbnail_rail_toggle (expand/collapse), speaker_notes_edit, and
  speaker_notes_save_result (preview + presenter surfaces).
- tests: deck analytics suite asserting the emitted (event, props) pairs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(file-viewer, file-workspace): enhance export toast functionality and improve localization for page creation

* feat(home-view, plugins-home-section, agent-skill): update default facet preference, enhance category selection handling, and improve localization support

- Set `preferDefaultFacet` to true in HomeView component for better user experience.
- Introduced `handlePickCategory` function in PluginsHomeSection to manage category selection based on card layout visibility.
- Updated PRIMARY_CATEGORIES to reflect the new order and added localization for titles and descriptions in adaptAgentSkill.
- Enhanced tests to verify the correct handling of display copy and tags from SKILL frontmatter.

* feat(plugins-home, file-workspace): introduce commercial category labels and enhance deck handling

- Added a new module for resolving and localizing commercial category labels for plugins.
- Updated the FileWorkspace and PluginCard components to display the commercial category label on deck cards.
- Enhanced the PageCreatorPresetFrame to adjust frame classes based on deck presets.
- Refactored subcategory handling in facets to align with the new commercial category taxonomy.
- Improved localization support for new category labels across multiple languages.

* feat(deck-preview): implement deck preview scaling and enhance home hero layout

- Introduced `useDeckPreviewScale` hook to manage scaling of deck previews within a fixed 1280×720 logical viewport, ensuring proportional rendering across various templates.
- Updated `FileWorkspace`, `HomeHero`, and `PluginCard` components to utilize the new scaling functionality for improved visual consistency.
- Enhanced the layout of the home hero section to accommodate new subtype chips and descriptions, including a "More" dropdown for overflow handling.
- Improved localization support for new UI elements across multiple languages.

* fix(PreviewModal): clarify sidebar prop documentation and update test description

- Updated the documentation for the `PreviewSidebar` interface to specify that the sidebar's stage-edge handles are not shown when the sidebar prop is absent.
- Modified the test suite description for `PreviewModal` to be more concise and relevant.

* test(e2e): select prototype visual card

---------

Co-authored-by: free666799 <293857035+free666799@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 09:41:47 +00:00
Nagendhra Madishetti dc12e0938f fix(frontmatter): a lone quote character is no longer parsed as an empty string (#4182)
* fix frontmatter parser coercing leading-zero values to numbers

The minimal YAML frontmatter parser (and its plugin-runtime mirror)
coerced any `-?\d+` token to a Number, so a leading-zero value like
`01234` (zip code, zero-padded id) silently became `1234`, and `007`
became `7`. Per YAML 1.2 a leading-zero decimal is a string — the zero is
significant. Restrict integer/float coercion to values without a redundant
leading zero (`0` alone still coerces to the number 0).

Also fix a related edge case where a value that is a single quote character
(`"`) was treated as an empty quoted string (slice(1,-1) of a 1-char
string), now requiring at least two characters before stripping quotes.

Both parser copies are kept in sync per their "mirror" contract.

Adds tests/frontmatter.test.ts for the daemon parser and extends the
existing plugin-runtime parsers test.

* fix(frontmatter): coerce leading-zero numerics per YAML 1.2 core schema

Restore numeric coercion for leading-zero scalars (01234, 007, -01, 00.5)
to match the YAML 1.2 core-schema int/float resolution, per review. The
lone-quote guard (require length >= 2 before stripping matching quotes)
stays in place. Tests updated to assert the numeric results.

Applies to both the daemon parser and the plugin-runtime mirror.

---------

Co-authored-by: Nagendhra <nagendhra405@gmail.com>
2026-06-19 03:19:19 +00:00
Vivaan Dhawan 5643d64310 perf: cache OpenCode bootstrap loading (#4224)
* Add OpenCode PR review

* Update OpenCode review

* Add issue trigger to OpenCode workflow

* Fix OpenCode workflow auth

* Fix OpenCode workflow: use_github_token

* Fix GITHUB_TOKEN

* Fix: Pass GITHUB_TOKEN directly to env

* Remove legacy cloud action in favor of stable local looper daemon

* perf(plugin-runtime): harden frontmatter parsing

* fix(frontmatter): tighten closing delimiter validation
2026-06-13 15:33:21 +00:00
chaoxiaoche 912c7e380a fix(plugin): infer semantic roles for token maps (#3231)
Co-authored-by: chaoxiaoche <chaoxiaoche@chaoxiaochedeMacBook-Pro.local>
2026-05-29 03:50:56 +00:00
PerishFire bd48c597b0 chore: pin dependency versions and harden CI caches (#2189)
* chore: pin dependency versions

* ci: enforce pinned dependency specs

* ci: fix pnpm executable invocation
2026-05-19 13:58:27 +08:00
pftom 2ac5854432 feat(plugin-inputs): enhance plugin input handling with file upload support
- Added support for file input fields in the PluginInputsForm, allowing users to upload files with serializable metadata.
- Updated the HomeHero component to improve the layout and interaction of input fields, enhancing user experience.
- Adjusted CSS styles for better visual representation of input fields and their states.
- Modified HomeView to reflect changes in authoring chip IDs for better clarity in plugin actions.
- Enhanced tests to cover new file input functionality and ensure correct behavior in various scenarios.

This update significantly improves the plugin input handling, enabling users to upload files seamlessly and enhancing the overall interaction model.
2026-05-14 15:52:21 +08:00
pftom 0edbf38171 feat(plugins): add specVersion and version fields to plugin and marketplace schemas
- Introduced `specVersion` and `version` fields to the plugin and marketplace schemas, ensuring better versioning and compatibility tracking.
- Updated various components and functions to handle the new fields, including database migrations, plugin snapshots, and marketplace management.
- Enhanced tests to validate the presence and correctness of the new fields in plugin manifests and marketplace entries.
- Improved documentation to reflect the changes in schema requirements and provide guidance on the new versioning system.

This update strengthens the plugin ecosystem by providing clear versioning, enhancing the reliability and maintainability of plugins and marketplaces.
2026-05-13 22:24:50 +08:00
pftom 443aea72c5 feat(daemon, web): enhance plugin handling and UI integration
- Introduced a new plugin upload mechanism with file size limits and memory storage, allowing users to upload plugins directly.
- Implemented fallback logic for plugin application, ensuring projects can be created without explicit plugin requests.
- Enhanced the UI to support plugin selection and integration, including a new `PluginsView` component for managing plugins.
- Updated various components to utilize localized text for plugin queries, improving user experience across different languages.
- Added tests for new plugin functionalities and local skill loading, ensuring reliability and correctness.

This update significantly improves the plugin management experience, providing users with better tools for plugin integration and interaction.
2026-05-12 20:42:40 +08:00
Cursor Agent 694289295a feat(plugins): wire bundled-scenario pipeline fallback through apply (spec §23.3.3)
Plan O1 / spec §23.3.3.

When a plugin omits `od.pipeline`, applyPlugin() now consults the
bundled scenario plugins registered by the daemon's bundled boot
walker (apps/daemon/src/plugins/bundled.ts) and copies the matching
scenario's pipeline verbatim into the snapshot. Match key:
`od.taskKind` (defaulting to 'new-generation' when absent).

Pure-side (packages/plugin-runtime):

  - new resolveAppliedPipeline({ manifest, scenarios }) →
      { pipeline, source: 'declared' | 'scenario' | 'none', scenarioId? }
  - RegistryView gains optional `scenarios?: ScenarioRegistryEntry[]`
    so the daemon registry view propagates the lookup table without
    coupling the pure resolver to SQLite.

Daemon-side (apps/daemon):

  - applyPlugin() calls resolveAppliedPipeline and stores the result on
    BOTH `ApplyResult.pipeline` and `AppliedPluginSnapshot.pipeline`
    so a replay reproduces the same stages without re-consulting the
    scenario registry.
  - server.loadPluginRegistryView() now collects bundled scenarios
    from `installed_plugins` (source_kind='bundled', od.kind='scenario',
    non-empty pipeline). Third-party scenarios are intentionally NOT
    eligible — only rows seeded by the bundled boot walker.

Invariants preserved:
  - Apply stays pure: scenarios are read once at the registry-view
    construction site and passed in; apply never opens a DB itself.
  - Scenario plugins themselves never fall back to themselves
    (resolveAppliedPipeline returns 'none' when od.kind='scenario').
  - manifestSourceDigest is unaffected by the fallback (digest is over
    manifest + inputs + resolvedContextRefs); replay invariance held.

Tests: +15 (plugin-runtime: 7 cases on resolveAppliedPipeline;
daemon: 8 cases on plugins-scenario-fallback covering declared
pipeline win, scenario fallback by taskKind, default to
new-generation, scenario plugins don't recurse, no scenarios →
pipeline=undefined, manifestSourceDigest stability across applies,
DB collector round-trip).

Co-authored-by: Tom Huang <1043269994@qq.com>
2026-05-09 15:05:54 +00:00
pftom 4c7cd5d9f2 feat(plugins): introduce plugin system with installation and management capabilities
- Added support for a new plugin system, allowing users to install, uninstall, and manage plugins through the daemon.
- Implemented API endpoints for listing installed plugins, retrieving plugin details, and applying plugins with input validation.
- Introduced a plugin doctor feature to validate plugin manifests and check for issues before application.
- Established a plugin persistence layer with SQLite migrations for managing installed plugins and their metadata.
- Enhanced the CLI with commands for plugin operations, improving user interaction with the plugin ecosystem.
2026-05-09 18:24:44 +08:00