* fix(daemon): state the skill read-only boundary instead of letting agents guess
A user asked the agent to update one of his own skills. Every write to the
skill directory came back `Operation not permitted` — by design:
`resolveChatExtraAllowedDirs` keeps skill roots out of the writable
allowlist, and Codex is given no skill directory at all because it treats
`--add-dir` as write access (#622).
Nothing in the composed system prompt said so. With no stated boundary and
no stated exit, the agent invented one: it told the user to open Open
Design's settings and add the directory to a "文件系统 / 工作区 / 可写目录 /
writable roots" option. No such setting exists — not for writable roots, not
for a sandbox mode, not for an approval policy. The user went looking for
it, could not find it, and filed a diagnostics bundle (0.21.0, packaged
stable).
Adds an `## Editing skills` section that states the boundary and its
symptom, names the real edit surface (the Integration view's Skills tab —
NOT a "Settings > Skills" page, which does not exist), and forbids inventing
a settings path to explain the failure. The missing settings are worded as a
statement about the current build rather than a refusal, so contributors do
not read it as "we will never build this"; a controlled skill-write path is
still on the table.
Gated on `executionProfile === 'filesystem'`. Deliberately NOT gated on
`isSlimCore` — slim is the production default, so that gate would ship this
to nobody — nor on an active skill, since "update my skill" arrives on turns
where no skill is bound. Ask mode and API/BYOK mode have no file tools and
so cannot hit the boundary; the matrix snapshot pins that (18 of 20
scenarios receive it, `ask-mode-full-context` and `api-mode-byok` do not).
Red spec: tests/skill-write-guidance.test.ts, 5/5 red on this branch with
the `parts.push` removed, and 1/5 red (the wording guard alone) when
"does not currently expose" is rewritten to an absolute "we do not support".
Assertions are scoped to the extracted section rather than the whole prompt:
an earlier draft asserted /today/i document-wide and passed with no
implementation at all.
Note: like any prompt change this shifts the stable-prompt hash once, so
existing conversations take a single `stable-prompt-changed` cache miss on
first turn after upgrade.
Tested: pnpm --filter @open-design/daemon exec vitest run tests/skill-write-guidance.test.ts tests/system-prompt-template.test.ts tests/prompt-templates.test.ts tests/intent-signal-stable-prompt-cache.test.ts tests/official-system-prompt.test.ts tests/prompt-telemetry.test.ts tests/prompts tests/chat-route.test.ts (20 files, 355 tests)
Tested: pnpm --filter @open-design/daemon build
Tested: pnpm guard
Tested: pnpm typecheck
Not-tested: live packaged client — prompt-only change, no runtime surface
* fix(daemon): name the staged .od-skills copy as a non-persisting write
The boundary section told agents that writing a skill returns `Operation
not permitted`. That is true for the real skill root, and false for the
one skill path the prompt stack actually advertises:
`withSkillRootPreamble` names `<cwd>/.od-skills/<folder>/` as the primary
**Skill root**, and it sits inside the agent's writable project cwd. A
`Write`/`Edit`/`Bash` against `.od-skills/<id>/SKILL.md` therefore
succeeds, reads as an install, and is discarded when `stageActiveSkill`
re-stages the copy on the next turn — the same misdirection the section
was written to stop, minus the error that would have stopped it.
The handover line made that landing spot likely rather than incidental:
"write the proposed `SKILL.md` into the project folder" pointed at a
folder that already contains a directory labelled the skill root.
So: name the staged copy and say a write there does not persist, and
send the proposal to a new `.md` file in the project folder, explicitly
not under `.od-skills/`. The two new assertions are section-scoped and
go red against the previous wording — `/operation not permitted/` could
not, because on this path nothing is denied.
* fix(od-next): state the same skill read-only boundary on the strategy path
`server.ts` picks one composer per run:
const prompt = odNextStrategyRecipe
? composeOdNextStrategyCorePromptV2(odNextStrategyRecipe)
: composeSystemPrompt(systemPromptInputs);
so everything the previous commit added lives on the classic branch only.
When the OD Next strategy is assigned, `composeSystemPrompt` is never
called and there is no always-resident block underneath — the boundary
section disappears wholesale, and the reported failure comes back.
OD Next is not a path where this is hypothetical. It is the path with the
sharper version of the same trap: `materializeFrozenSkillPackage` writes
the frozen Skill package into `<cwd>/.od-skills/`, and the strategy prompt
hands the agent those exact roots — `materializedRoot` in the Skill roster
(`renderFrozenSkillRosterContext`) and `Frozen side-file root:` in each
Skill body (`resolveFrozenSkillBundleBodies`). Writable directory,
advertised path, no stated boundary, and a write there additionally breaks
the frozen identity the daemon verifies.
So the same four statements land in the core strategy prompt's
`## Agent and runtime boundaries`: the boundary, the materialized roots
that accept a write without persisting it, the real Skills-tab exit, and
the prohibition on inventing a sandbox / writable-roots / approval-policy
setting.
`assets.core.version` moves 2.1.0 -> 2.2.0 with the text. Package identity
is content-hashed from these bytes at apply time
(`buildStrategyPackageIdentity`), and `plugins-strategy-recipe.test.ts`
copies the real bundled folder, so that suite exercises identity against
the edited asset rather than a fixture.
The new assertions are scoped to the section (`.od-skills` also appears in
the task-profile assets, so a document-wide match could not go red) and
whitespace-normalized, since the asset is hard-wrapped prose. Five of the
six go red against the previous content; the sixth is labelled in-file as a
locator check that stays green on `main` by design.
* fix(collab): show last published team project
* fix(collab): preserve legacy team project discovery
---------
Co-authored-by: lefarcen <935902669@qq.com>
* test(desktop): give Electron probes a CI-sized timeout
The PPTX layered-background and base-href tests each spawn a throwaway
Electron process (xvfb-run on Linux) and gave it 10-20s. A cold Electron
start on a busy GitHub runner regularly exceeds that, and execFile then
reports a bare "Command failed: xvfb-run … electron …" with no stderr —
which is how an unrelated PR turns red on one probe (see #7772, four
consecutive runs, and #7768 on the same case) while the file passes locally.
Both files now share one probe budget (60s) and one per-test budget (90s)
behind named constants instead of scattered literals. A probe that really
hangs still fails, just later.
* test(desktop): run the PNG paint probe sandbox-less and surface its output
The paint probe was the one Electron spawn in the file without
--no-sandbox --disable-gpu, and the only one that rethrew a failed spawn
without the child's stdout/stderr. On the CI runner that combination is a
probe that exits before app.whenReady() with a bare "Command failed" —
the failure #7774's first run still showed with the larger budget. Align
it with the other probes and attach the child's output on failure so the
next flake, if any, says why.
* feat(contracts,daemon): report awaiting-input projects on the runs list
`ChatRunStatus` cannot express "waiting on the user": the run that asked a
`<question-form>` reports `succeeded` and exits while the project stays
blocked, so a client folding `GET /api/runs` into a per-project status shows
such a project as finished. Ship `awaitingInputProjectIds` beside the runs,
composed from the same `listProjectsAwaitingInput` read `GET /api/projects`
uses and intersected with the projects the returned runs already reveal, so
the field can never widen what a caller may see.
Ported 1:1 from upstream #7635 for OPEND-2553 (PR-A).
* feat(web): derive per-project run status from the runs feed
`Project.status` only arrives on the unscoped `GET /api/projects`, so a
workspace-scoped session cannot read it. Add the pure fold
(`foldRunsToProjectStatuses`) that mirrors the daemon's composition, the
per-project `listRunsForProject` read plus `useProjectRunStatuses` hook that
keeps it live off `RUNS_CHANGED_EVENT`, the one glyph per status
(`ProjectRunStatusIcon` over the SiriOrb), and the two conversation reducers
(`latestUserPromptText`, `runProgressSteps`) the Design Files empty state
will read.
No visual change on its own. Ported 1:1 from upstream #7635 for OPEND-2553
(PR-A).
* feat(web): list recent projects in the entry nav rail
Adds the 最近浏览过 disclosure under 插件 in the signed-in rail: the head of
the same recent catalog 全部项目 shows, newest first, capped at eight rows.
Each row leads with the project's live run status (running / needs input /
completed / failed) from the runs feed, opens through the shell's pull-first
opener, floats a cover preview beside the rail on hover, and carries a ⋮ menu
with rename, whole-project export and a two-step delete. An acknowledged ✓ is
spent by opening the project and re-raised by the next run.
The Home page's recent-projects grid stays; this only adds the rail entry.
Row geometry, easing and colours are pinned to the Demo (upstream #7635) by
`tests/styles/entry-rail-recent.test.ts`. OPEND-2553 (PR-A).
* fix(web): acknowledge the rail ✓ per finished run
The 最近浏览过 rows spent a project's ✓ by remembering the PROJECT id and only
forgot it after observing a live queued/running status. The section polls
only while it is open, so a run that started and finished while it was
collapsed was never observed: on re-expanding, the stale acknowledgement
still matched and the new completion's ✓ never showed. (The Demo, #7635,
carries the same defect; this deliberately diverges from it.)
Acknowledge by run identity instead. `foldRunsToProjectRunSummaries` reports
the newest terminal run's id beside each project's status (the status-only
fold is derived from it and unchanged for its callers), `useProjectRunSummaries`
exposes it live, and the rail stores `{ projectId: acknowledgedRunId }`: a ✓
is acknowledged for one specific finished run, and a newer finished run is a
new notice. The reset-on-running effect is gone. A store in the old array
shape reads as nothing acknowledged, so at worst one already-seen ✓ shows
once more.
Red spec: tests/components/EntryNavRail.recent-section.test.tsx
"re-raises the ✓ for a newer finished run even when its running phase was
never seen". OPEND-2553 (PR-A).
* feat(web): add the SiriOrb run indicator
Ports the Siri Orb (SmoothUI, MIT) from #7635 as a standalone component: a rotating neon sphere with idle / thinking / streaming speeds, drawn entirely in its CSS Module. Unused by this PR; the rail run-status icon (PR-A of OPEND-2553) consumes it.
* feat(web): vendor the composer running orb
Adds the thinking-orbs 0.3.1 'solving' state at its 20px preset (MIT, attributed in the file) as a local component instead of a new runtime dependency: the composer needs one of the package's nine animations, pinned to the dark palette, and the transcription keeps the geometry, preset scaling, painter and frame loop of the published build so the canvas output is identical. Refs OPEND-2553 / #7635.
* feat(web): add the rotating Home headline i18n keys
homeHero.title now carries the {word} slot in every locale together with homeHero.titleWords (the comma-separated nouns the headline cycles), homeHero.subtitle and the attachment band's paging labels. Every locale owns its own sentence and noun list so the placeholder-parity check stays green (#7635 only rewrote en / zh-CN / zh-TW). Refs OPEND-2553.
* test(web): re-pin the Home hero and composer specs to the refreshed surfaces
Follows #7635's hero/composer contract: type row instead of the template dropdown, display+clear type pill, no Home mode picker, no resource submenus in the '+' menu, references in the working-directory row, lead-chip truncation, the 116px hero offset, the 220px execution-chip cap, the flat app wash and the stable send arrow. Carries over the Demo's new specs (dock collapse, Backspace-at-start, modelVersionLabel, icon-only design-system pill) and drops the ones for removed surfaces. Refs OPEND-2553.
* test(e2e): follow the Home type row and composer menu
Ports #7635's Playwright helper (homeTypeRow / clearHomeTemplate / pickHomeTemplate) and the home-hero-rail, entry-chrome-flows, project-management-flows, visual-navigation and entry-topbar changes that reach the composer through the type row, the working-directory group and the trimmed '+' menu. Refs OPEND-2553.
* feat(web): refresh the Home hero and composer
Replicates the Home hero + composer subset of #7635 on current main (OPEND-2553, PR-B): plain headline with the rotating noun and subtitle, a typeless cold Home whose type row (3 + 2 behind More) retires into the composer's display+clear pill, upload disc + design-system palette + type pill in the foot row, the supplied send mark and border beam, two-line staged-file chips in a paging band, the lead template chip with Backspace-at-start, references and linked checkouts in the working-directory row (one slot), the trimmed '+' menu without resource submenus or the mode picker, poster rows for examples, the model chip's brand mark and version-only label, the running orb + stop square in the project composer, and the flat app wash. HomeView gains the page/dock variant plumbing and drops the default type seed and the AppWashKineticGrid mount; the recent-projects grid stays for PR-C. Adds task_chip_clear to the composer click contract.
* test(e2e): follow the Home composer and type-row refresh
Migrates the Playwright specs that named surfaces #7635 removed or moved: the composer session-mode picker (every turn now carries the conversation's stored design mode, and design turns carry no message chip), the Home examples loading shell (a cold Home is typeless and shows only the type row while the catalogue loads), the fixed type row (three inline pills plus two behind More; every other create type reaches the hero through the cross-surface hand-off, which the shared pickHomeTemplate helper now drives), the working-directory row's single slot for references and linked checkouts, the '+' menu's working-dir group, the lead chip's eight-character title, the 32px send disc, and the flat app wash. The Home recent-projects specs keep main's shape (PR-C owns that move). Refs OPEND-2553.
* test(e2e): repair the visual captures the composer refresh broke
The projects captures clicked the rail's drafts item, which only renders
with a workspace context the visual fixture never has; Home already shows
the recent-projects strip, so the click is dropped. The integrations
capture reached its page by direct load, whose default tab is MCP and
whose cold mount outlasts a bare expect; it now clicks the connectors tab
like the MCP capture does. The BYOK switcher capture expected the link
glyph, but the chip leads with the selected model's brand mark and only
falls back to the glyph for a vendor without one. OPEND-2553 (PR-B).
Validate allowlisted root artifacts with lstat before ignoring them during fresh managed-base claims. Directories, symlinks, and stat failures remain real content and cannot silently claim a foreign root. Add directory and symlink regressions covering the no-sentinel rejection path.
Fixes#7602
Co-authored-by: Seungjae Lee <seungjae.lee@hyundai.com>
* fix(daemon): preserve terminal delivery lifecycle evidence
* fix(daemon): preserve per-attempt terminal evidence
Generated-By: looper 0.12.0 (runner=fixer, agent=codex)
* fix(daemon): preserve terminal attempt and persistence boundaries
Generated-By: looper 0.12.0 (runner=fixer, agent=codex)
* fix(daemon): settle terminal claims before capture
Defer the one-shot run_finished envelope by one check phase so adjacent
duplicate and late terminal claims are included in telemetry, and bind the
daemon lifecycle type directly to the shared API contract.
Generated-By: looper 0.12.0 (runner=fixer, agent=codex)
* fix(daemon): preserve replay and teardown evidence
Generated-By: looper 0.12.0 (runner=fixer, agent=codex)
* fix(web): validate the onboarding runtime before Continue is pressed
Continue on the Local Agent step ran the runtime smoke test inside the click
handler, so every user paid a full agent spawn plus a real model round trip
between pressing it and reaching the next screen: 7.1s for Claude Code and
12.1s for Codex CLI measured against a local daemon, against a 45s budget.
The button was only greyed out through that wait and still read "Continue",
so it looked like the click had done nothing. Before #7164 the click itself
was instant, because the validation came from the separate Test button.
Start the validation as soon as the selection settles instead, the way BYOK
already validates a settled provider, so it overlaps with the time the user
spends reading the panel and picking a model. Continue then takes the
already-validated result with no round trip at all. #7164's guarantee is
unchanged: onboarding still cannot complete on an unproven runtime.
`startOrJoinInlineTest` keeps that to one round trip per selection. A click
landing mid-flight joins the pass already validating those inputs rather than
being swallowed or spawning the agent a second time, and an attempt whose
inputs the user has moved past is aborted — the daemon kills the agent child
along with the request. A background pass no longer disables Continue; only a
click that is itself waiting marks the button busy, with a spinner and
"Testing connection…" in place of the label.
Internal report: OPEND-2281.
* chore(web): drop an internal tracker id from the onboarding spec comment
The surrounding sentences already carry the reason the validation has to be
under way before Continue is pressed, so the id added nothing a reader of
this tree could act on.
* fix(web): release the runtime validation when its setup step is left
Review caught two lifecycle gaps in the background validation this branch
introduces.
The pass spawns a real agent CLI, so abandoning one is not free, and nothing
aborted `agentTestRunRef` / `providerTestRunRef` when the user left the setup
step. Back, a runtime switch, or leaving onboarding all left that child
occupying the daemon until its own timeout — a cost that did not exist while
validation only ever started from an explicit click, and one a few abandoned
visits multiply. Abort both runs whenever the setup step is left; React runs
the same cleanup on unmount, so that path is covered by construction.
The BYOK auto-validation effect also still bailed while a run was in flight,
so editing the key, base URL, or model mid-flight scheduled nothing: the
inputs the user was now looking at stayed unproven until the request they
replaced resolved or timed out. That is the exact bail the local effect had
already dropped, and keeping it defeated the cancellation path
`startOrJoinInlineTest` was built for. Both effects now schedule through the
helper and let it abort whatever it supersedes.
* fix(web): make an aborted runtime validation undo its own bookkeeping
Review found three ways the cancellation added in the previous commit and the
attempt bookkeeping around it had drifted apart.
Aborting released the request but kept `*AutoTestKeyRef` claiming those inputs
were validated, while the aborted run deliberately writes no result. Coming
back to the same selection therefore hit the key guard, scheduled nothing, and
left the panel rendering a "testing" line no run would ever finish — the
automatic path was dead until Continue was pressed by hand. Aborting now clears
the key and returns a `running` state to idle. A run that already finished is
left alone: its verdict still describes the current selection, and re-proving
it would cost another spawn.
Releasing was also keyed on leaving the step, but inputs can stop being
testable while the user stays: clearing a BYOK key, or a daemon that goes away
under a local agent, leaves a request nothing can consume — the same situation
as walking out, and now the same cleanup. The condition is what the step can
still act on, not where the user is.
Finally, the debounce outlived a manual Test press, because pressing Test
disturbs none of the effect's inputs. A manual check answering inside that
window let the armed timer spend a second request on inputs that were just
proven, which also let a transient second failure overwrite the first success.
Both timers re-read the bookkeeping at fire time instead of trusting the check
made when they were armed.
* fix(web): do not validate a selection the setup step is still asking for
Being selectable is not the same as being provable, and the background
validation only checked the first. `canTestAgent` asks whether a selection
exists and the daemon is up; the picker deliberately keeps a setup-required
DeepSeek Harness entry in that list so the user has somewhere to start setup
from, and `selectDefaultCliAgent` only reassigns the saved selection when some
other agent is actually available. With that entry saved and nothing else
installed, opening the Local Agent step spent a `/api/test/connection` on it.
The entry carries a resolvable `path` — that is part of what identifies it as
setup-required — so the daemon does not short-circuit on `agent_not_installed`
and genuinely tries to start the runtime. The panel then reports a failure on
the same screen that is telling the user to finish installing the companion,
which reads as the setup being broken rather than unfinished. Continue was
already gated on `selectedAgent?.available === true`; the unprompted spawn was
not, and the two disagreed.
Start the background pass only on a selection the step would actually let the
user continue on. Pressing Test stays their call. Releasing keeps the wider
condition on purpose: a run started by hand on a setup-required entry still
has to be aborted when they leave, so what may start on its own is a stricter
question than what must be cleaned up.
---------
Co-authored-by: Cheems <94773058+itscheems@users.noreply.github.com>
* chore: remove extracted landing-page from the monorepo
The marketing site now lives in a separate repository. Drop the
Astro app, its GitHub workflows/scripts, and the remaining
monorepo coupling so this workspace no longer builds or deploys it.
* fix: drop leftover landing-page coupling from Teams and skills
Point development enterprise URLs at the deployed marketing origin,
remove nextjs-app skill outputs that no longer emit a page, and stop
advertising apps/landing-page in plugin marketplace metadata.
Generated-By: looper 0.12.0 (runner=fixer, agent=omp)
* fix(ci): restore DSH installer E2E Vitest routing
Removing dsh-bootstrap-identity dropped the only planner route from
tools/release/resources/dsh-bootstrap/ to web_tests_required, so
installer-only PRs skipped e2e/tests/dsh-installer-version-policy.test.ts.
Generated-By: looper 0.12.0 (runner=fixer, agent=omp)
* docs: drop leftover landing-page paths from template assets
The skill contract no longer advertises the extracted Astro app, but
styles.css, compose.ts, and example.html still named deleted
apps/landing-page sources. Describe the standalone template behavior
instead and keep the bundled plugin example in sync.
Generated-By: looper 0.12.0 (runner=fixer, agent=omp)
* docs: keep landing-page off review and test surfaces
Add apps/landing-page to the forbidden-surface review list and stop naming the extracted path in plugin registry acceptance tables.
Generated-By: looper 0.12.0 (runner=fixer, agent=omp)
* fix: drop leftover landing-page refs and bump plugin version
Stop naming the deleted apps/landing-page in the landing-deck demo and
plugin-registry plan. Bump example-open-design-landing to 0.1.1 and keep
the official catalog version aligned with the bundled manifest.
Generated-By: looper 0.12.0 (runner=fixer, agent=omp)
* fix(release): quote catalog latest pointer If-Match etags
R2 412s If-Match when GET returns an unquoted or weak ETag. Normalize to a
strong quoted validator before the CAS PUT so catalog-publish can advance
latest.json after the first snapshot.
* fix(release): reject weak ETags on catalog latest CAS
If-Match is a strong comparison. Do not strip W/ and invent a strong
validator; only quote a bare strong ETag. A weak GET fails closed and
leaves latest.json unchanged.
* chore(catalog): temporarily allow catalog-publish on CAS etag branch
Verification only. Revert after the dispatch run proves latest.json CAS.
* fix(release): GET catalog objects with identity encoding
R2/fetch returned a weak ETag on latest.json, so If-Match never matched.
Request uncompressed bytes so the validator stays strong.
* Revert "chore(catalog): temporarily allow catalog-publish on CAS etag branch"
This reverts commit e5703d6c78.
`deploy/Dockerfile` copies skills, design-systems, craft, prompt-templates,
assets and plugins/_official, but not data/ — so
`data/plugin-previews/manifest.json`, which CI commits precisely so deployments
can find the bakes, is absent at runtime. `resolvePluginPreviewsDir()` defaults
to <projectRoot>/data/plugin-previews, `loadManifest()` returns {},
`applyBakedPreviews()` returns the records untouched, and
`inferPluginPreview(record, { preferBaked: true })` falls through to the live
iframe for every plugin.
Measured on 0.19.2 with 461 registered plugins: the checked-in manifest has 176
entries and 0 records carried od.bakedPreview. With data/ present the same
daemon reports 176 — deck 79, prototype 79, video 10, scenario 4, image 1,
audio 1, template 2. The packaged app reaches the manifest through its own
packaging path, so only Docker/self-hosted deployments are affected.
Also warn once per path when the manifest is absent. `loadManifest()` already
carries the reasoning for the malformed case — «would otherwise silently
disable every baked preview with no trace; surface it so it's diagnosable» —
but the absent branch returned {} with no message, which is the same failure
with the same lack of trace.
Fixes#7018
Co-authored-by: Jason <>
* fix(release): make catalog pack git-safe in the Playwright container
Checkout's set-safe-directory did not cover later git calls, and Meta
swallowed a failed rev-parse under sh -e, so export ran with an empty
source commit. Mark GITHUB_WORKSPACE safe and pin CATALOG_SOURCE_COMMIT
to github.sha.
* fix(release): temporarily allow catalog-publish on the git-safe branch
Let pack and publish run on fix/catalog-publish-container-git so the
container git fix can be verified end-to-end before reverting the guard.
* fix(release): restore catalog-publish main-only job guards
Drop the temporary fix-branch allowlist now that the container git
workaround is verified end-to-end.
* docs: map the two prompt implementations behind the OD Next fork
`composeSystemPrompt` returns early for OD Next runs, so the legacy stack below that return — the designer charter, the discovery layer, and the deck framework directive that embeds deck protocol v1 — never reaches those runs. Nothing in the repository said so, and prompt changes have been landing on one side only. The deck case is live: the OD Next `ppt.md` pins none of `data-od-deck-protocol`, the `od:deck-ready` announce, the `od:slide-state` posts, the `od:slide` listener, `id="deck-stage"`, or the print stylesheet, so a deck generated on that path can fail to be recognized as a v1 deck, paged from the host toolbar, or exported to PDF. Eligibility is re-evaluated per run and can latch down mid-session, which is why the divergence shows up as an intermittent bug.
Adds `apps/daemon/src/prompts/AGENTS.md` (fork point, the four variant axes, and a host runtime contract table naming which side carries each contract today) and `plugins/_official/scenarios/od-next-strategy/AGENTS.md` (asset roster and package-hash rules, and why maintenance notes must stay out of `assets/`, which is sent to the model verbatim). Root AGENTS.md gains an index entry and a Prompt variants section. Pointers go at the two fork sites, both deck-framework copies, and the OD Next atom registry, which is itself declared twice.
Documentation and comments only; no prompt bytes change. Validated with pnpm typecheck, pnpm guard, the daemon prompt suites (207 tests), and the contracts system-prompt / web-clone / deck-protocol suites (28 tests).
* docs: add PR #7568 as the worked example of a one-sided prompt change
The variant map says the two sides exist; it did not show what missing one costs a careful author. #7568 (merged 2026-08-29, `973e868ce`) is the case: it introduced deck protocol v1 across 18 files, handled the daemon/contracts prompt-copy split completely — shared constant, both copies changed, a guard added on each side — ran the full web and contracts suites plus two packaged DMG end-to-end runs, and touched nothing under `plugins/_official/scenarios/`. Its own stated goal was to stop new navigation dialects appearing; the OD Next path kept generating decks with no protocol at all, reported two days after merge.
Records the three reasons the miss stayed invisible, each still true today: both guards it added call `composeSystemPrompt` without an OD Next recipe so they cannot pass the fork (one is named 'ships new Agent decks with OD Deck Protocol v1' and is green), the prompt matrix snapshot updated cleanly because every scenario in it takes the legacy branch, and sharing a constant fixed only the machine-readable half — the surrounding directive prose had already drifted between the two copies before #7568 and still differs on main.
Documentation only. Validated with pnpm guard in a worktree on this branch.
* docs: mark the deck fork at the sites an agent actually edits
The file-top and fork-point pointers only reach an agent that reads from line 1. The edit an agent actually makes for a deck rule lands in the middle of DECK_FRAMEWORK_DIRECTIVE (368 lines below the daemon file's header) or at the push site (446 lines below the fork), typically reached by grepping and reading a short span — where no pointer was in view. Put a two-line marker at each of those four sites instead: above DECK_FRAMEWORK_DIRECTIVE in both prompt copies, and above the deck/freeform push guard in both, which covers both push branches. Line references in the two AGENTS.md files are re-pinned for the shift.
Comments only, placed outside every template literal. Verified byte-identical prompt output against origin/main: 8 scenarios x both composition paths, sha256 and length compared in one worktree with the source files swapped and contracts rebuilt — all 16 pairs identical. pnpm typecheck, pnpm guard, daemon tests/prompts (207), contracts system-prompt / web-clone / deck-protocol (28).
* docs: move the prompt variant map to docs/ and refresh it for #7651
Two changes in one, because main invalidated the old shape while it sat in review. Structural: the guidance moves from two nested AGENTS.md files to one `docs/prompt-composition.md`, matching how the root index already carries `docs/architecture.md` and `docs/skills-protocol.md`. Nested AGENTS.md is not auto-loaded on file access, so proximity bought no delivery that the root pointer and the in-file markers do not already provide. The plugin folder keeps a short AGENTS.md for the one rule that is only meaningful there — never write maintenance notes into `assets/`, which reaches the model verbatim. Naming it after the structure rather than the experiment keeps it valid if OD Next is retired or graduates: two of the four axes are independent of it.
Factual: #7651 closed the deck gap this document reported, by collapsing the scaffold to one source in contracts and reaching OD Next through `resolveOdNextDeckFrameworkMode` rather than the markdown assets. The contract table's deck rows now read supported on both sides, the daemon deck-framework file is a re-export, and the two markers that claimed OD Next never receives the directive are removed rather than reworded. #7568 and #7651 are now presented as a pair — a one-sided change and the follow-up nearly twice its size that repaid it. Remaining gaps are recorded: five daemon/contracts prompt pairs still hand-maintained, and `core-slim.ts` absent on the contracts side.
Documentation and comments only. Prompt output verified byte-identical against the merge base: 8 scenarios x both composition paths, sha256 and length, markers reverted and reapplied in place. pnpm typecheck (0 errors), pnpm guard, daemon tests/prompts (210), contracts system-prompt / web-clone / deck-protocol / od-next-prompt-recipe (53).
---------
Co-authored-by: Cheems <94773058+itscheems@users.noreply.github.com>
#6971 fixed the placement for both layouts through one shared render site,
but its regression coverage only exercised the list row. Grid is the default
view, and that is the layout #6916 reports, so the reported scenario had no
witness.
Verified red/green against the placement rule: with
.recent-projects__card-menu[data-placement='up'] removed both this case and
the existing list-row case fail on the menu-above-trigger assertion; with it
restored both pass.
* feat(web): explain the rail's Discord / X / mail icons on hover
The three icon-only links at the bottom of the nav rail carried native
`title` text that only named the destination ("Join the OpenDesign
Discord"), so nothing told a user what they get by clicking — least of
all that Discord is where free credits are handed out.
Move them onto the shared `.od-tooltip` layer (the same bubble the
sidebar's expand control uses) and rewrite each label to lead with the
payoff. One string per link now doubles as the accessible name and the
tooltip, so the copy stays in one place.
X was hardcoded to "@OpenDesignHQ" and could not be translated; it now
has an `entry.xAria` key like the other two, filled in for all 19
locales.
The rail sits on the leading edge, so tooltips open away from it —
right in LTR, left once RTL moves the whole rail to the right edge.
Without that flip the bubble is clamped against the viewport and lands
back on top of the icons it describes. `isRtlLocale` is now exported
from the i18n provider rather than re-declaring the locale list at the
callsite.
* test(web): gate the question-form answer on the form being submittable
The assertion fired as soon as onSubmitQuestionForm existed, but the
handler returns false while currentConversationActionDisabled is still
true, so a loaded CI runner failed with 'expected false to be true'.
Wait for questionFormSubmitDisabled, like the sibling test does.
---------
Co-authored-by: elifive555555 <296440099+elifive555555@users.noreply.github.com>
* fix(analytics): separate admission phase from failure attribution
* fix(analytics): recognize ACP replay boundary
Generated-By: looper 0.12.0 (runner=fixer, agent=codex)
* fix(analytics): retain admission evidence across truncation
Preserve terminal-attempt evidence before the run event ring buffer truncates, and recognize guarded stdout plus persisted artifact activity.
Generated-By: looper 0.12.0 (runner=fixer, agent=codex)
* fix(analytics): flush Antigravity output before close errors
Emit guarded non-OAuth stdout before terminal close errors so admission evidence survives in both the live ledger and durable event log.
Generated-By: looper 0.12.0 (runner=fixer, agent=codex)
* fix(analytics): require admission attempt boundary
Keep fallback admission phase unknown when no start event proves that an attempt began.
Generated-By: looper 0.12.0 (runner=fixer, agent=codex)
* fix(analytics): honor confirmed ACP model selection
Use the final pre-error ACP model status so session/new metadata does not invalidate a subsequently confirmed requested model.
Generated-By: looper 0.12.0 (runner=fixer, agent=codex)
Revert d83d4e032f to keep the current weekly release on its established baseline. The sidecar convergence work and QA acceptance remain valid and will be relanded after the release window closes.