mirror of
https://github.com/nexu-io/open-design.git
synced 2026-10-27 09:51:12 +01:00
c893b60aa39ce8e69e1f5bf86acfeca6514172d8
420
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
ad5a7d6ba1 |
Revert "feat(web): add guided inspiration and deliverable workflow (#5899)" (#6120)
This reverts commit
|
||
|
|
c3de4d5b95 | fix(daemon): classify model and timeout failures (#6103) | ||
|
|
a63eca6a4a |
fix(desktop): cover clear-cache in updater IPC teardown and host bridge guard (#6101)
* fix(desktop): include clear-cache in updater IPC teardown and host bridge guard The od:update:clear-cache handler was registered but missing from UPDATER_IPC_CHANNELS, so runtime teardown never removed it and a second createDesktopRuntime() in the same process would throw on re-registration. The host bridge structural guard likewise accepted an updater bridge that lacks the clear-cache method, deferring the failure to call time. Red specs: the boundary test now derives the teardown table from the registered od:update:* handlers, and the host contract test rejects a bridge missing clear-cache. * refactor(desktop): deduplicate deferred-installer timeout and update action literals The mac and Windows deferred-installer timeouts were two constants with one value branched on platform; fold them into one. Route handle()'s action dispatch through DESKTOP_UPDATE_ACTIONS instead of repeating the string literals. * docs(pack): correct the beta-s acceptance feed path to the betas channel release-beta-s publishes RELEASE_CHANNEL=betas, so the internal feed lives under betas/latest, not beta/latest. |
||
|
|
340cbb9b94 |
feat(web): add guided inspiration and deliverable workflow (#5899)
* feat(cli): add project rename command to CLI Introduced a new command `od project rename <id> --name "<title>"` to allow users to rename existing projects. The command validates input and sends a PATCH request to update the project name in the backend. Updated CLI help documentation to include this new command. * feat(web): enhance InspirationPicker with reference-site shortcuts and selection chips Added functionality to the InspirationPicker component, allowing users to browse reference sites (e.g., Dribbble, Behance) directly from the picker. Implemented a selection chips feature that lists all current picks, enabling easy removal across different families. Updated styles for better visual representation of selected items. Enhanced internationalization support with new translations for the added features. * feat(web): enhance InspirationPicker with detailed previews and selection management Updated the InspirationPicker component to include a detailed preview feature for selected items, allowing users to view full documents directly from the picker. Implemented a "Selected" section that displays current picks with a remove option, improving user interaction. Adjusted styles for better visual consistency and added new translations for enhanced internationalization support. * fix(web): restore uploaded inspiration thumbs and drop preview elapsed Resolve locked inspiration-upload previews via the submitted path map so answered forms still show real images, and keep the floating preview status pill as a stage label only. * feat(web): implement TaskDeliverableCard for displaying task outputs Added a new TaskDeliverableCard component to showcase the primary deliverable and its associated outputs. This includes a preview feature for various file types, an actions menu for sharing and downloading, and improved styling for better user experience. Enhanced internationalization support with new translations for deliverable-related terms. Updated InspirationPicker to utilize the new deliverable ranking and searching functionalities. * fix(inspiration): unify design system previews and source priority * fix(web): align deliverable card regression coverage * test(web): scope question form assertions * fix(web): keep the deliverable card off failed and canceled runs The card's status label is an unconditional Done, so gating it on the shared terminal predicate let a failed or canceled run that wrote files present a partial artifact as delivered while the same message reported the error. Gate on the existing runSucceeded predicate instead; those turns fall back to the plain file ledger, so the files stay reachable without the false completion signal. * fix(daemon): serialize DESIGN.md font stacks through a strict font-family grammar extractFonts is deliberately permissive and its output was interpolated verbatim into the generated <style> raw text, so a Body font value like "Karla</style><img src=…>" could close the style element and inject active markup into every auto-loaded card iframe. sanitizeFontStack now rebuilds each stack from validated family names only (quoted names, unquoted generic/system keywords), dropping anything else and falling back to the trusted default stack; both the full preview and the compact card renderer route every font interpolation through it. Card and preview fixtures with a </style> payload pin the boundary. * fix(daemon): ground additional inspiration systems in the run prompt The picker's extra design-system picks reached the agent as ids on a metadata line only, so a user-authored system contributed none of its colors, fonts, or rules. The daemon now resolves each additional id through the same DESIGN.md reader chain as the primary (workspace file first, then registry), bounds the result (3 systems, 6000 chars each), and composes an 'Additional inspiration systems' section with explicit primary-wins precedence. Prompt tests pin a unique secondary token reaching the composed prompt, both at the composer seam and end-to-end through the chat route with bento's #FAD4C0. * test(e2e): stabilize preview comment click * test(web): update runtime override history expectation --------- Co-authored-by: Amy <1184569493@qq.com> |
||
|
|
b99a9fdc3d |
feat(manual-edit): add direct manipulation and reliable history (#5890)
* feat(web): manual edit v2 direct manipulation (v2.2–v2.4) Selection overlay with action bar and drag/resize handles, floating text toolbar with range-format highlight (queryCommandState round trip), Manus color picker, image replace/crop/paste/drop, element copy/duplicate/delete, and the systematized in-place content pipeline: every element-scoped patch now reconciles the live iframe DOM from the saved source over od-edit-apply-dom (replace/insert-after/append-child/prepend-child/remove) with positional identity restamping, so commits, undo and redo never reload the canvas, never flash, and never move the scroll position. Frozen-source reloads remain only as a scroll-capturing fallback. Includes the Chromium Home/End double-booking fix, the dblclick word-selection guard, and light targets-broadcast dedupe. * test(web): manual edit v2 unit and e2e coverage Bridge (in-place apply ops, positional restamp, selection format state, Home/End consumption, dblclick selection guard), source-patches (insert readback, restore descriptors, sanitization), gestures/shortcuts/color units, and host-level FileViewer suites proving content commits mutate the canvas without a srcDoc reload, hand selection to inserted elements, and record labeled manual versions for undo/redo. Also realigns the manual-edit-history and srcdoc-reload-races helpers with the v2.1 opt-in inspector two-step flow. * docs(specs): manual edit v2 architecture spec Six-layer map (identity/chrome/style/content/history/external-sync), core invariants (DOM equals saved source without reloads; positional identity restamps on structural change; every save is a labeled manual version), patch-kind application matrix, performance contract, and the v2.1–v2.4 revision log including the real-browser verification round. * fix(tools-dev): fail fast when a sidecar process dies during startup wait waitForDaemonRuntime/waitForWebRuntime now accept a liveness probe for the spawned pid, so a crashed daemon/web process surfaces immediately instead of burning the full startup timeout. * fix(web): edit brand-kit pages in place — no reload flash, no scroll jump Brand-kit targets carry runtime-annotated ids with no markup of their own in the saved source (edits persist into the payload / runtime overrides), so the in-place readback came up empty and every commit fell back to a srcDoc reload — the flash-and-jump-to-top the user hit when editing text after an image. New od-edit-apply-dom op 'apply-content' mirrors the patch fields (text/href/src/alt/html/attributes, on*/data-od-* filtered) onto the live element instead, and set-outer-html reuses replace with the patch html. The reload fallback's scroll restore also gains a clamped-only retry ladder (600–4000ms) so async-rendering pages (brand kit renders after load) no longer land at the top when a reload is genuinely needed. * refactor(FileViewer): unify manual edit history management and enhance undo/redo functionality This update introduces a consistent approach to managing manual edit history and undo/redo stacks by utilizing refs to mirror state. The new `updateManualEditHistoryStack` and `updateManualEditUndoneStack` functions ensure that the state remains in sync with the refs, preventing stale data issues during undo/redo operations. Additionally, the handling of in-session undo (Cmd+Z) has been improved to escalate to the host's global history when no local changes remain, ensuring a seamless user experience. Tests have been added to verify the correct behavior of these features. * feat(manual-edit): enhance manual edit functionality with space scaling and improved gesture handling This update introduces a new `ManualEditSpaceScale` interface to manage the scaling between viewport pixels and CSS pixels, ensuring accurate positioning during manual edits. The `manualEditMovePreviewTransform` and `manualEditResizeStyles` functions have been updated to utilize this scaling, improving the precision of drag and resize gestures. Additionally, the `ManualEditSelectionOverlay` component now maintains pointer capture during gestures to prevent interaction issues with nested iframes. Tests have been added to verify the correct behavior of these enhancements, ensuring a smoother user experience in manual editing scenarios. * feat(FileViewer): add localized toasts for image upload process in manual edit This update enhances the manual editing experience by introducing localized toast notifications for the image upload process. New messages for uploading, processing, and successful addition of images have been added to the i18n files. The `insertManualEditImage` function now manages these notifications, providing users with real-time feedback during image uploads. Additionally, tests have been implemented to ensure the correct display of these toasts across different locales. * fix(manual-edit): drag whole image to move + lock selection frame to element box (#5888) Two direct-manipulation fixes reported from real prototype/landing/deck edits: 1. Move by dragging the image body, not only the top pill. Adds a full-interior moveBody surface inside the selection frame that starts the same undoable move gesture; scoped to image targets so text/link bodies keep click-to-edit. Rendered under the resize handles so edges still resize. 2. Keep the selection frame locked to the element during resize. The frame previously drew the gesture-intent x/width and only adopted the iframe's measured y/height, so an element that re-centers, caps at max-width, or reflows on width change left the frame drifting horizontally off the picture. adoptMeasuredExtent now takes the full measured rect and displayRect follows it, so the chrome always wraps the element. Adds ManualEditSelectionOverlay tests for body-drag move and measured-box frame sync; records the change as spec v2.5. Co-authored-by: Claude <noreply@anthropic.com> * test(web): stabilize manual edit browser validation * fix(manual-edit): address structural edit review blockers * fix(manual-edit): preserve runtime formatting and restore order * fix(manual-edit): flush toolbar styles before navigation * fix(manual-edit): preserve runtime target identity * fix(manual-edit): serialize saves and image handoff * fix(manual-edit): clear stale runtime clipboard * fix(manual-edit): scrub rich text identities * fix(manual-edit): save inspector text after formatting * fix(manual-edit): action-bar boundary avoidance, select-without-reflow, image paste shortcut (#5892) Three edit-mode canvas fixes from user feedback: 1. Action bar no longer covers the element frame/handles. A small or edge-hugging element previously had its move pill and resize handles hidden under the floating action bar (clamped to the top), so it could not be moved or resized. New pure helper manualEditActionBarTop places the bar above the frame, flips it below when the element hugs the top edge, and keeps it inside the canvas; horizontal clamping already handles the side edges. Adds a data-placement attribute. 2. Selecting a text element no longer makes it jump/grow. The bridge fused 'select' and 'enter inline edit' into one click, and making a deck text box contenteditable releases its line-clamp/height cap so it visibly reflows larger the instant it is selected. The first click now only selects (no contenteditable, no reflow); editing begins on a second click of the already-selected element or a double-click — matching how images/containers already select. 3. A selected image/container can be pasted with Cmd/Ctrl+V. The native paste event only fires while an editable element holds the caret, which never happens for non-text selections, so the shortcut did nothing for images. The bridge keydown handler now forwards od-edit-paste-request for a selected non-editable element (paired with the existing Cmd/Ctrl+C copy). Updates the 14 inline-edit bridge tests to enter edit via double-click and adds coverage for all three fixes; recorded as spec v2.6. Co-authored-by: Claude <noreply@anthropic.com> * fix(manual-edit): image corner scaling, nowrap unlock on resize, restore image paste Three edit-mode canvas fixes from user feedback: 1. Images get four corner anchors that scale aspect-locked from any corner, anchored at the opposite corner (new resize-nw/ne/sw/se gesture kinds). The dominant pointer axis drives the scale, the floor binds the larger dimension, and corner gestures skip snapping so the ratio never breaks. Corner resizes persist width AND height (space-scale corrected) plus the offsets that keep the anchored corner fixed. 2. Resizing an element with white-space: nowrap (or pre) now unlocks wrapping (normal / pre-wrap) alongside the width — a narrowed heading previously stayed on one line and overflowed its own new box. ManualEditStyles and the bridge style props gain whiteSpace. 3. Image paste works again. The v2.6 Cmd/Ctrl+V keydown branch called preventDefault, which suppresses the native paste event — the only place clipboard image bytes are readable — so pasting an image did nothing. The shortcut now arms a 150ms one-shot element-paste fallback instead; any real paste event disarms it and routes image vs element itself, so the two channels never double-fire. Adds gesture/overlay/bridge coverage for all three; recorded as spec v2.7. * fix(manual-edit): replay runtime history in place * fix(brands): reject scraped CSS junk font families before token emission An extraction can capture CSS source text instead of a real font family (e.g. Tailwind v4's `--theme(--default-font-family` from aliyun.com). Interpolated raw into the kit's `--brand-font-family`, the unbalanced parenthesis swallows every later :root declaration, so the component kit renders unstyled (UA serif, collapsed buttons) while earlier color tokens still work. isRenderableFontFamily() now guards both emission seams: the seed's fontStack (kit/variables/artifacts token block) and brandFontAssets' display-family stack (h1-h3 face). Junk families drop out and the renderable fallbacks + system stack take over. * fix(manual-edit): replay runtime-only style history from the persisted override rule Runtime-only brand-kit targets persist set-style patches in the style[data-od-manual-edit-runtime-overrides] rule, not as element inline styles. The in-place history replay read inline styles only, so redo of a runtime style change replayed empty values while reporting success — the file held the redone style while the canvas stayed on the undone one. readManualEditSavedStyles() now reads a target's persisted styles wherever they live (inline attribute for source-backed nodes, the runtime override rule otherwise), and the set-style history branch replays through it. The same inline-only read also made reconcileManualEditStyleSave() treat every runtime style save as "target vanished" — erroring, dropping the selection, and reloading the canvas — so it now consults hasManualEditRuntimeStyleOverride() before taking that path. Regressions: source-patches unit coverage for the saved-styles read and the override predicate, plus a FileViewer spec that styles a runtime target, undoes and redoes it without any srcdoc change, and asserts the redo preview carries the persisted value (red on the previous head). * fix(manual-edit): merge partial runtime style patches into the persisted rule Successive debounced toolbar autosaves send only the touched property, but setRuntimeStyleOverride() rebuilt the target's override rule solely from the incoming patch, so a runtime-only brand-kit target lost its earlier saved declarations (fontSize save, then color save -> font-size silently dropped). Persistence now merges the incoming patch into the existing rule declarations, treating empty incoming values as deletions and removing the rule once its last declaration is deleted. --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
7787556cf1 |
fix(daemon): full ACP tool transcript and richer usage telemetry (#6057)
* fix(daemon): full ACP tool transcript and richer usage telemetry Map all ACP tool_call frames to canonical tool_use/tool_result for run.events, PostHog run_finished, and Langfuse spans. Preserve artifact_count via Write/Edit normalization, tighten path extraction, forward truncated results, and emit real tool startedAt. Enrich usage scanning (thought_tokens, anthropic vs openai cache semantics), canonical tool-family PostHog props, analytics recovery finish snapshot, and shared content-tool redaction. * fix(daemon): prefer ACP kind over noncanonical tool names Normalize recognized ACP kinds (e.g. kind:read + name:read_file → Read) before emitting tool transcripts so Langfuse content-tool redaction matches canonical families and does not leak file contents. Generated-By: looper 0.11.0 (runner=fixer, agent=grok-build) * fix(daemon): fail-closed redaction for unknown ACP tool payloads Generated-By: looper 0.11.0 (runner=fixer, agent=grok-build) * fix(daemon): flush open ACP tools as errors on session fail When a session dies mid-tool (timeout, exit, RPC error), emit pending tool_use/tool_result pairs with isError so transcripts stay complete. Generated-By: looper 0.11.0 (runner=fixer, agent=grok-build) * fix(daemon): redact ACP Bash stdout in tool transcripts Execute tools can dump private files (e.g. cat .env); replace Bash tool_result content with a length summary before the canonical transcript ships so Langfuse cannot receive raw execute output. Generated-By: looper 0.11.0 (runner=fixer, agent=grok-build) * fix(daemon): redact Linux home paths in Langfuse Bash tool spans Extend local-path masking for ACP Bash partial redaction to cover /home and /root (not only /Users and Windows Users), so Linux home paths do not leak into Langfuse tool inputs when content telemetry is on. Generated-By: looper 0.11.0 (runner=fixer, agent=grok-build) * fix(daemon): redact custom ACP tool names before Langfuse telemetry Sanitize path/token-like kind:other tool names at transcript emit and canonicalize non-allowlisted names for Langfuse span labels, toolName metadata, and redaction placeholders so untrusted custom names cannot leak even when content telemetry is off. Generated-By: looper 0.11.0 (runner=fixer, agent=grok-build) * test(e2e): force-click unstable comment targets in preview iframe UI P0 project-workspace timed out on comment attachment when Playwright kept waiting for hero-title stability under auto-fit zoom / bridge inject. Click once visible with force so the critical flow stays reliable. Generated-By: looper 0.11.0 (runner=fixer, agent=grok-build) * fix(daemon): merge partial usage frames in reverse analytics scan Keep provider_usage for thought/cache-only trailing usage events while continuing the reverse scan to the latest primary input/output/total record so run_finished and Langfuse do not drop complete token counts. Generated-By: looper 0.11.0 (runner=fixer, agent=grok-build) * fix(daemon): flush open ACP tools before AMR no-output fail Clean-flush pending non-think tools on successful session/prompt before evaluating emittedConcreteToolEvent so missing terminal tool_call_update does not mark a productive run as acp_no_visible_output. Generated-By: looper 0.11.0 (runner=fixer, agent=grok-build) * fix(daemon): merge partial primary usage and flush tools on ACP abort Require both input and output before ending the reverse usage scan so output-only or input-only trailing frames still merge earlier counters. Flush deferred open ACP tools as errored on abort so user-canceled runs keep complete tool transcripts for Langfuse/PostHog. Generated-By: looper 0.11.0 (runner=fixer, agent=grok-build) * fix(daemon): hash path-like ACP toolCallIds before telemetry Adapters can embed user paths in toolCallId (e.g. read:/home/alice/.env). Hash those values at tool_use/tool_result emit so Langfuse span ids and metadata.toolCallId cannot leak secrets, while keeping the raw id as the local frame-correlation map key. Generated-By: looper 0.11.0 (runner=fixer, agent=grok-build) * fix(daemon): always hash ACP adapter toolCallIds for telemetry Identifier-shaped secrets (sk-proj-*, ghp_*, JWTs) passed the prior allowlist because they contain no path characters. Hash every non-empty adapter toolCallId before tool_use/tool_result emission so Langfuse span ids and metadata.toolCallId cannot leak tokens. Generated-By: looper 0.11.0 (runner=fixer, agent=grok-build) * fix(daemon): keep reverse usage scan open for missing cache fields When a newer usage frame already has input+output but omits cache counters, continue scanning older frames so cache_read/cache_creation still merge into run_finished and Langfuse. Generated-By: looper 0.11.0 (runner=fixer, agent=grok-build) * fix(daemon): retain flushed ACP tool IDs against late terminals Keep acpToolRunEventState entries after flushOpenAcpTools so a racy tool_call_update cannot recreate the same id and emit a second tool_use/tool_result pair after timeout, abort, or prompt flush. Generated-By: looper 0.11.0 (runner=fixer, agent=grok-build) * fix(daemon): block kind:other Bash-like tool names Custom ACP tools with kind:other must not emit Bash/shell/execute/terminal names, which would unlock Langfuse partial redaction and leak rawInput. Generated-By: looper 0.11.0 (runner=fixer, agent=grok-build) * fix(daemon): fail closed on Bash names without execute kind ACP tools missing kind must not preserve name/title Bash labels that unlock Langfuse partial redaction; only recognized execute-family kinds may emit Bash. Generated-By: looper 0.11.0 (runner=fixer, agent=grok-build) |
||
|
|
de2020fb88 |
docs: reconcile code-evolution drift (#6083)
* docs: reconcile code-evolution drift * fix(web): preserve generic-disconnect recovery retries Generated-By: looper 0.11.0 (runner=fixer, agent=codex) * fix(web): retry capped reattach probe after cleanup Generated-By: looper 0.11.0 (runner=fixer, agent=codex) |
||
|
|
a2ba3ee2ad |
feat(updater): manual cache clear, installer-reinstall floor, and crash-recovery lifecycle (#6032)
* feat(updater): manual cache clear + installer-reinstall floor on the installed outer version
Two disaster-recovery capabilities for the packaged updater:
- Reuse control.launcher.version.min properly: the gate now compares the
physically installed outer package version (read at check time from the
outer bundle's open-design-config.json via the launcher launch path;
OD_UPDATE_INSTALLED_VERSION overrides for tests), not the running payload
version. When the floor trips - or min is set but the outer version is
unreadable (conservative) - the updater selects the installer artifact and
offers a same-version installer reinstall when no newer release exists
(clamped to min <= candidate so the offer always converges). The status
snapshot carries a reinstall block {reason, installedVersion, minVersion,
url}; UpdateDialog/UpdaterPopup render reinstall copy with an optional
operator jump link. tools-release publish-metadata publishes the block from
RELEASE_LAUNCHER_VERSION_MIN(_URL) (per-channel repo vars with a _STABLE
fallback wired in all release workflows) and hard-fails when min exceeds
the release version; verify-metadata re-checks the published block and
summary-metadata surfaces the floor.
- Manual clear-cache action (od:update:clear-cache IPC, sidecar clear-cache
action, Settings > About row with a two-stage inline confirm): resets
one-shot update state (downloaded release, install freeze) back to idle,
purges releases/staging/downloads/.back, removes a stale launcher
attempt.json plus any non-confirmed desktop-handoff journal, and deletes
non-retained payload versions. Runtime active/lastSuccessful versions,
retained cleanup entries, install.json, and a confirmed handoff journal
are never touched; locked files defer through the existing cleanup.json
retry machinery.
Also: tools-pack --update-action now validates through sidecar-proto's
exported isDesktopUpdateAction so clear-cache reaches the ops surface;
tools-serve updater fixture gains control min/url knobs and CLI flags;
9 new i18n keys across all 19 locales; tools/pack/AGENTS.md documents the
gate semantics and clear-cache boundaries.
Validated locally per the updater matrix (desktop/web/tools-serve/tools-pack
suites, guard, typechecks) plus a mac release-beta closed loop: platform full
spec, same-version reinstall against a real outer/payload version split,
clear-cache recovery, and a real-feed payload update to 0.15.1-beta.4.
* test(updater): close Windows recovery acceptance
* test(updater): mirror Windows same-version reinstall recovery on the mac lane
Port the Windows runSameVersionUpdaterRecoveryAcceptance shape into the mac
full profile: after the payload cold-start acceptance, restart the packaged
app against a fixture publishing an installed-outer floor at the running
payload version and assert the same-version installer reinstall snapshot
(reason/installedVersion/minVersion/url), the popup reinstall learn-more
link, clear-cache back to idle with launcher pointers untouched, explicit
re-check recovery, and the installer open in dry-run mode — macOS has no
silent DMG install to execute, unlike the Windows NSIS transaction lane.
Extends the popup eval expression/validator with reinstallLinkVisible and
records an updaterRecovery block in the smoke summary. Full-profile test
timeout raised 240s -> 360s for the extra app restart.
Validated locally: release-smoke full profile green in 80s with the recovery
summary populated (reinstall block exact, cleared idle, recovered dmg,
dryRun install).
* refactor(release): centralize launcher version floor channel policy in one resolver
Channel policy for control.launcher.version.{min,url} is now managed as one
repo-vars pair per channel (RELEASE_LAUNCHER_VERSION_MIN[_URL]_<CHANNEL>).
Workflows pass the channel pair plus the STABLE pair through verbatim — the
|| fallback expressions are gone from YAML — and the shared resolver in
tools/release/src/storage/launcher-version-floor.ts owns resolution and
validation: pair-level stable fallback for non-stable channels (never mixing
one channel's min with another's url), version format, http(s) url, orphan
url rejection, and the floor <= releaseVersion guard. publish-metadata and
verify-metadata consume the same resolver, so verify re-derives the expected
block from the same channel policy instead of trusting a pre-resolved env.
release-workflows.test.ts pins the verbatim passthrough keys in all five
release lanes and rejects any reintroduced YAML fallback.
* feat(updater): let clear-cache rebuild a provably-owned corrupt update store
A corrupt store is one of the blocking scenarios the manual clear exists to
recover from, yet the action previously bailed on the very store errors it
should heal. clear-cache now rebuilds the update root when — and only when —
ownership is provable: the current-generation sentinel is the proof, and
everything else inside an owned root is updater cache by definition. Roots
without a sentinel, with a foreign-generation marker, or failing IO are never
touched; the original store error surfaces unchanged. Four specs pin the
boundary (corrupt metadata rebuilt, stray entries rebuilt, unowned root
refused with files intact, foreign-generation marker refused).
* fix(launcher): pre-arm delegated payload launches so pre-bookkeeping crashes roll back
A payload that spawned successfully but died before reaching its own launcher
bookkeeping left no attempt.json behind, so every later cold start re-selected
the same broken active pointer and re-delegated to the dead payload — a
permanent dead loop the installer fallback could never surface (found while
designing the crash-rollback acceptance).
The delegating parent now arms attempt.json BEFORE spawning the payload and
passes the pointer as --od-launcher-delegated-* argv. Selection gains an
active-delegated reason: an attempt matching active AND the delegated pointer
is the launch in progress, not a previous failure; only an exact active match
qualifies, so a stale pointer cannot defeat the rollback. Rollback
(last-successful) delegations deliberately carry no marker and never re-arm —
the attempt on disk IS the rollback evidence the child must re-derive.
Covered end to end: packaged outer delegation pre-arms (and skips pre-arm on
rollback), the updater's payload activation pre-arms and threads the pointer
through the deferred relaunch, and confirm clears the attempt for delegated
launches. launcher-proto 18, packaged 237, desktop 264 all green.
* fix(updater): scrub a stale payload install freeze after a rollback
A payload install freezes the updater with installResult.activeVersion
awaiting the relaunch. When that payload crashed and the launcher rolled
back, the app restarts on the OLD version with the freeze still persisted —
every later check became a frozen no-op, so a rolled-back install never saw
another update offer (the crash-rollback acceptance exposed this in its
self-heal phase). Cold-start restore now detects the broken promise —
installResult.activeVersion newer than the running version on a live process
— and drops the freeze and stale install result while keeping the verified
downloaded release actionable. The installer-artifact freeze (no
activeVersion) keeps its existing restore semantics. Red spec:
'clears a stale payload install freeze when the promised relaunch never
became the running version'.
* test(updater): full-lifecycle acceptance — silent startup, crash rollback, coverage map
Two new packaged acceptance tests on both platform lanes (mac validated
locally; win symmetric for the Windows box):
- 'applies a downloaded payload silently on the next cold start': enables the
daemon-owned allowSilentUpdates through the production GET+PUT
/api/app-config path, restarts, and asserts the first scheduler tick
installs, quits, and relaunches with converged pointers — no updater action
issued.
- 'rolls back a crashing payload and self-heals on the next good update':
repacks the real payload archive with a crash-before-bookkeeping stub
(mac: shebang script via ditto; win: a real executable — System32 where.exe
— via the installed app's bundled 7z, since a script cannot stand in for an
exe), drives the update through the popup, asserts the pre-armed attempt
survives the crash as rollback evidence, cold-starts into the rolled-back
base version (degraded steady state pinned: broken pointer stays active
with its attempt), then self-heals via a version-bumped healthy payload —
real recovery releases ship as version+1.
Spec hygiene: resetPackagedRuntimeState wipes launcher/runtime/daemon state
before each lifecycle test (uninstall keeps runtime data by design, and
leftover pointers silently delegate fresh installs onto stale payloads);
mac waitForHealthyDesktopVersion gains win's requireSettledLauncher opt-out
for rollback waits.
docs/testing/updater-lifecycle.md is the new lifecycle-to-test coverage map
(incl. deliberate manual-only nodes), linked from tools/pack/AGENTS.md and
the e2e coverage status doc.
* fix(updater): harden Windows full-lifecycle acceptance
* test(release): make channel-prepare hermetic against local git tags
prepare-preview derives its latest-stable floor from the clone's real
open-design-v* tags, so the channel-prepare tests were green on tagless CI
checkouts but permanently red on any developer clone once shipped stable
tags advanced past main's package version (0.15.1 vs v0.15.1/v0.16.0 today).
The spawned scripts now run with GIT_DIR pointing at a throwaway repo
holding exactly the tags each test declares, and a new negative case pins
the floor rejection itself (packaged version below a v99.0.0 stable tag).
* test(e2e): model delegated payload launch
Pass the updater's pre-armed payload pointer into the cross-runtime launcher simulation so it represents the spawned payload process instead of a later crash-recovery launch.
Generated-By: looper 0.11.0 (runner=fixer, agent=codex)
* test(web): wait for question form readiness
Wait for the asynchronously derived submit-disabled state before asserting it, removing the suite-order race observed by the Web workspace tests check.
Generated-By: looper 0.11.0 (runner=fixer, agent=codex)
* fix(updater): preserve recovery state across locks and restarts
Generated-By: looper 0.11.0 (runner=fixer, agent=codex)
---------
Co-authored-by: Looper <looper@noreply.github.com>
|
||
|
|
ca4ecf0bf2 |
chore(release): bump main to 0.16.1 ahead of stable 0.16.0 (#6000)
Co-authored-by: open-design-release-bot[bot] <open-design-release-bot[bot]@users.noreply.github.com> |
||
|
|
dcdb0c4204 |
fix packaged updates after payload cwd cleanup (#5967)
* fix packaged payload startup after cwd cleanup * fix deferred installer helper cwd Generated-By: looper 0.11.0 (runner=fixer, agent=codex) --------- Co-authored-by: Looper <looper@noreply.github.com> |
||
|
|
2337932714 |
feat: expose GPT-5.5 Fast service tier (#4287)
* feat: support Codex service tiers * fix: forward service tiers through MCP runs * fix: clear default service tier selections * fix: preserve service tier on model option updates * fix: preserve service tier without explicit model * fix: preserve service tier in connection tests * test: stabilize API empty stream UI check * fix: preserve codex service tier labels * fix: preserve service tier without saved model * fix: address service tier review follow-ups * fix: pin legacy chat assistant rows before start * fix: accept bare model catalog responses |
||
|
|
2e377a5152 |
name every packaged startup failure instead of bucketing it as unknown (#5956)
* name every packaged startup failure instead of bucketing it as unknown
The weekly `packaged_runtime_failed` alert is dominated by failures the event
had enough evidence to name and reported as opaque anyway. Triaging a 7d window
turned up three places where the emitter threw away a cause it was already
holding:
- 62 devices died with the daemon log tail read successfully, but
`parseDaemonLogTail` only ever matched `ERR_*`. A daemon that threw anything
else (SQLite corruption, EPERM, a plain throw) reported error_code=null AND
missing_module=null with its reason printed in the tail we had just read.
- 21 devices hit win32 CreateProcess failures. Node puts the real cause on the
error's `code`/`errno`/`syscall`; the event sent `.message` only, so
`spawn UNKNOWN` was indistinguishable from any other opaque failure.
- 149 devices produced an Error with an empty `.message` and no `.stack`. Every
free-form field went null, leaving residue that could not even be counted
apart from "no error at all".
So: parse the sidecar's own fatal line as a backstop when `ERR_*` misses, carry
the errno triplet off the thrown object, classify a rejected spawn as its own
`spawn-failed` kind rather than `unknown`, and guarantee a thrown error never
serializes to error_message=null — an unnamed leftover is now a named, countable
bucket.
Classification-only; the failures themselves are unchanged. Every test input is
a verbatim field payload from the alert window.
* reduce syscall to its operation token before sending it
Review catch, and a real leak. Node does not guarantee `syscall` holds only the
operation name: spawning a missing binary sets it to the whole invocation.
Verified against real Node —
spawn('/Users/elian/nonexistent/bin/thing')
→ syscall === 'spawn /Users/elian/nonexistent/bin/thing'
Forwarding that verbatim would have put the local username and install path
into an event that is retained even for opted-out users, while every other path
this module sends goes through scrubUserPaths.
Keep the first token — the operation is the whole analytic value, and the path
is already available (scrubbed) via error_message — then scrub what survives
and cap it, as a second line of defence. `code` is capped too.
This also fixes a classification miss the same review implies: the spawn-failed
branch compared the RAW syscall to 'spawn', so a spawn error carrying a path
never matched and fell through to `unknown` — the exact bucket this PR exists
to drain.
|
||
|
|
bb6c79c86d | fix packaged Windows obsolete outer cleanup (#5955) | ||
|
|
447b18b98e | fix packaged mac relaunch blocked by obsolete outer (#5940) | ||
|
|
1226773663 |
feat(daemon): route completed-run telemetry through Vela (#5666)
* feat(daemon): route completed-run telemetry through Vela * fix(daemon): migrate test telemetry relay domain * feat(daemon): send Vela feedback scores with completed-run telemetry Map score-create into the Vela envelope and use the same Vela sink for thumbs feedback, binding scores via the client run id instead of skipping them when account-aware telemetry is selected. * fix(daemon): skip anonymous fallback for Vela feedback auth errors Score-only feedback batches must stay on the account-scoped Vela path. When Vela returns 401/403, fail the delivery instead of posting to the anonymous relay, which has no matching trace for those scores. Generated-By: looper 0.11.0-beta.1 (runner=fixer, agent=grok-build) |
||
|
|
d1372da029 |
fix(daemon): reconcile run terminals after restart (#5817)
* reconcile run terminals after restart * fix(daemon): preserve replayed failure taxonomy Reclassify analytics-incomplete terminal failures from their persisted state and events while keeping the interrupted template exclusive to daemon-restart recovery. Generated-By: looper 0.11.0 (runner=fixer, agent=codex) * fix(daemon): preserve repaired errors and skip completed logs The repair path previously collapsed every failed message to restart copy and hydrated event logs before checking completed telemetry checkpoints. Use durable recovery metadata for status detail and gate log reads on outstanding PostHog or Langfuse work. Generated-By: looper 0.11.0 (runner=fixer, agent=codex) |
||
|
|
6fb644a57d |
feat(analytics): report conversation turn index (#5917)
Co-authored-by: bone3deep1962-collab <bone3deep1962@gmail.com> |
||
|
|
4e8db9a9b7 | add runtime version and retry provenance (#5814) | ||
|
|
ef35a08e24 |
feat(daemon): E-lite root-cause telemetry for inactivity_timeout attribution (#5775)
* feat(daemon): E-lite root-cause telemetry for inactivity_timeout attribution Add four run_finished fields that separate WHY a stalled run died, which the coarse `last_observed_phase` (WHICH phase) cannot: - approval_requested: an ACP approval/permission gate fired. Only the ACP path is daemon-observable (stream/CLI runtimes pass a skip-permissions flag so no gate fires); surfaced via an acp_approval_request diagnostic and folded in by summarizeRunDiagnosticsForAnalytics. - stdin_backpressure: the prompt write to the child's stdin was queued because the OS pipe buffer was full (the child was not draining stdin) — the corroborating signal for a stdin_write-phase stall. - tool_result_sent: a tool_result came back for the run's LAST tool_use. A stall with tool_call_seen && !tool_result_sent is the tool-result-not-delivered root cause. - last_progress_age_ms: age of the last agent activity at finish (the inactivity-watchdog clock). Near the ceiling on a stall, ~0 on a clean finish. Contract fields on RunFinishedProps; daemon populates two off the run object (stdin_backpressure, last_progress_age_ms) and derives two from run.events in the diagnostics summarizer (tool_result_sent, approval_requested). * fix(daemon): pair tool_result_sent by id so parallel tools attribute correctly Review catch (nettee/Looper): the first cut flipped `tool_result_sent` to true on any tool_result after a tool_use, so a multi-tool turn like tool_use(A), tool_use(B), tool_result(A) reported delivered even though B was still outstanding — bucketing multi-tool stalls as "provider stalled after delivery" instead of "tool result not delivered". Now paired by id (`tool_use.id` <-> `tool_result.toolUseId`, the same pairing summarizeRunTimingAnalytics uses): `tool_result_sent` is true only when EVERY committed tool_use received a matching tool_result (outstanding set empty). Tests cover the reviewer's regression case plus parallel all-resolved and sequential-last-outstanding. * fix(daemon): count-pair id-less tool events so degraded stalls aren't "delivered" Second review catch (nettee/Looper): id-based pairing alone still fell through to `tool_result_sent: true` when a runtime emits a tool_use with a null id, because those were counted in `sawAnyToolUse` but never tracked as outstanding. That is not hypothetical — `agent-protocol/pi-rpc/events.ts` and `copilot-stream.ts` both emit `toolCallId ?? null` for tool_use.id AND tool_result.toolUseId on degraded provider events, so an unpaired id-less tool call would mask exactly the stall this metric attributes. Id-less tool events are now paired by count alongside the id-keyed set: `tool_result_sent` requires both an empty outstanding-id set and at least as many id-less results as id-less uses. Tests cover an unpaired id-less stall and an id-less pair that did resolve. * fix(daemon): capture stdin backpressure on the default text-input path Third review catch (nettee/Looper): `stdin_backpressure` never recorded anything for non-`stream-json` runtimes. `child.stdin.end(composed, ...)` returns the stream rather than a boolean, and `writableNeedDrain` is already back to false by the time it returns — even for a chunk that `write(chunk)` would have rejected. Claude is the only runtime on the stream-json path, so the field was permanently false on exactly the runs whose `stdin_write` stalls it exists to attribute. The write and the close are now issued separately through `writePromptAndEndStdin`, which returns the boolean the OS pipe actually gave us. Extracted into chat-run-lifecycle.ts so it is unit-testable: tests cover the backpressured write, the accepted write, and the flush callback still firing (the `stdin_write_end` lifecycle mark). Verified red against the previous behavior. |
||
|
|
c9e622d0a9 |
fix(daemon): ship baseline OpenCode for Windows and stop retrying AVX2 illegal-instruction crashes (#5733)
* Classify AVX2 illegal-instruction agent crashes as cpu_unsupported and stop retrying them The Windows opencode.exe bundled with the AMR runtime is a Bun build that requires AVX2. On CPUs without it (Intel Atom/Celeron/Pentium N-series through 2021) the binary dies with STATUS_ILLEGAL_INSTRUCTION before readiness; vela surfaces an ACP fatal, the daemon classified it as a retryable fatal_rpc_error, and the automatic retry deterministically failed again. 88 of 105 pre-readiness Windows crash traces in Langfuse are this single failure. The crash text (Bun 'Illegal instruction' banner with a no_avx2 CPU feature line, or the raw 0xC000001D / decimal 3221225501 exit status) now classifies as process_exit/cpu_unsupported ahead of the fatal_rpc_error close-reason promotion, is never auto-retried, and renders a dedicated error card telling the user to update Open Design instead of a dead Retry button. * Bump @powerformer/vela-cli to 0.0.23 so packaged Windows builds bundle the baseline (non-AVX2) OpenCode The 0.0.23 win32-x64 platform package compiles the OpenCode fork with --target=windows-x64-baseline, so the bundled opencode.exe no longer requires AVX2 and starts on Intel Atom/Celeron/Pentium N-series CPUs. Verified in vela CI: the build log shows the baseline target and the new post-publish smoke job boots the published bundle's opencode.exe on a real Windows runner (powerformer/vela#838). * Refresh Nix pnpm deps hashes for the vela-cli 0.0.23 lockfile change * Add the abort-after-panic (exit status 3) production shape to cpu_unsupported specs A user on an AVX-but-no-AVX2 CPU (Sandy/Ivy Bridge era) hit the same illegal-instruction crash, but Bun panicked during its own panic handler and abort()ed, so vela reported exit status 3 instead of STATUS_ILLEGAL_INSTRUCTION. The stderr banner (no_avx2 / Illegal instruction) is the only signal; encode the real Langfuse trace shape so the classifier keeps catching it. * Bump @powerformer/vela-cli to 0.0.25 so packaged Windows builds bundle the baseline (non-AVX2) OpenCode 0.0.25 is the first stable cut after powerformer/vela#838 merged: its win32-x64 package compiles the OpenCode fork with --target=windows-x64-baseline (verified in the release CI log), the post-publish smoke boots the published bundle on a real Windows runner, and the lockfile regenerator now pins the baseline asset so a routine version bump cannot silently flip back. 0.0.24 predated that merge and still shipped the AVX2 build. * Tighten cpu_unsupported to precise signals only (no bare 'Illegal instruction') Review is right that a bare illegal-instruction line is too broad: an unrelated SIGILL on an AVX2-capable machine (runtime bug, corrupted jump) would be mislabeled 'Processor not supported' and lose its retry. Bun's crash banner always prints the CPU-feature line, so machines actually missing AVX2 are identified by no_avx2; the bare Windows STATUS_ILLEGAL_INSTRUCTION renderings (0xC000001D / 3221225501) stay because those runs carry no banner at all. Generic illegal-instruction banners without no_avx2 return to process_crashed / fatal_rpc_error, locked by a new negative spec. * Refresh Nix pnpm deps hashes for the vela-cli 0.0.25 lockfile change * Gate raw STATUS_ILLEGAL_INSTRUCTION on the bundled-opencode startup context |
||
|
|
df50cffbd2 |
fix(platform): discover Grok Build on Windows (#5843)
Co-authored-by: Kirill Turanskiy <thebtf@users.noreply.github.com> |
||
|
|
7c742163c5 | fix: support pnpm install --ignore-scripts with pnpm bootstrap (#5874) | ||
|
|
d3e091e157 | fix(daemon): classify deterministic failures and preserve retry attribution (#5882) | ||
|
|
30a4c284c6 |
feat(web): extend visual style catalog to visual artifacts (#5746)
* feat(web): extend visual style catalog to visual artifacts * feat(web): expand visual style catalog * Simplify chat execution disclosures * test: cover collapsed file disclosures * refactor(web): consolidate chat task activity * fix(web): show small file outputs inline * fix(web): separate file outputs from reads * fix(web): collapse completed todo cards * refactor(web): compact completed todo lists * fix(web): make completed todos expandable * fix(web): keep four output files visible * refactor(web): clarify chat activity timeline * fix(web): make file output disclosure interactive * feat(web): emphasize chat run states * style(web): simplify chat run status colors * style(web): remove chat run status icons * feat(web): stream one chat activity at a time * style(web): align chat completion row * Revert "style(web): align chat completion row" This reverts commit |
||
|
|
888d35ce96 |
feat(desktop): add macOS check for updates flow (#5789)
* feat(desktop): add macOS check for updates flow * fix(web): refine update dialog layout * fix(web): refine update-ready copy * fix(web): refine updater dialog states * fix(desktop): map preflight denials to active-run copy, skip menu rebuild on progress ticks Address review feedback on PR #5789: UpdaterPopup and Settings About now surface the active-runs-blocked/active-runs-unknown restart-safety denial as user copy instead of a generic failure, and the app-menu preflight response no longer publishes a synthetic error onto the shared updater status channel. The mac app-menu rebuild now compares a stable derived menu-item key so download progress ticks stop triggering a full Menu.setApplicationMenu rebuild. --------- Co-authored-by: bone3deep1962-collab <bone3deep1962@gmail.com> Co-authored-by: NJUHua <113895241+NJUHua@users.noreply.github.com> |
||
|
|
2192a7f6b9 |
fix incomplete BYOK configuration preflight (#5745)
* fix BYOK configuration preflight * fix(web): short-circuit non-BYOK run preflight Local-agent test fixtures omit BYOK-only fields, but provider resolution read model before rejecting non-BYOK protocols. Return before touching those fields and keep the pending-prompt API fixture explicitly valid. Generated-By: looper 0.10.4 (runner=fixer, agent=codex) * fix(web): persist committed BYOK key clears Treat a blank active-provider key as draft-only while it is being edited, then persist the clear once the field is committed. This preserves incomplete replacement drafts while invalidating saved account-scoped model state. Generated-By: looper 0.10.4 (runner=fixer, agent=codex) * test(web): align BYOK draft expectations Keep incomplete model discovery edits in the provider-scoped draft while the active configuration remains unchanged, and expect the current DeepSeek preset default. Generated-By: looper 0.10.4 (runner=fixer, agent=codex) |
||
|
|
3447f60a34 |
fix packaged payload desktop handoff (#5678)
* fix packaged payload desktop handoff * fix beta mac full smoke fixture wiring * fix tools-pack mac payload cold start * test payload desktop update restart * fix Nix daemon launcher protocol build * fix(pack): harden Windows long-jump updates * fix(pack): harden mac long-jump acceptance * fix(handoff): defer armed journal writes until spawn and shutdown succeed Previously executeLegacyPayloadDesktopHandoff wrote the armed handoff journal, attempt, and runtime state before spawning the payload desktop and before the old desktop accepted shutdown. When spawn() threw or the shutdown request failed, the armed journal was stranded on disk, so the next cold start bailed out of prepareLegacyPayloadDesktopHandoff() with reason "already-armed" and the install stayed pinned to the old desktop generation indefinitely. Move the three writes to after both the payload child has spawned and the shutdown has been accepted. The old desktop is still alive while it acks the shutdown, and the payload waits for the old pid to exit before resuming, so the state still lands before the payload reads it. Add regression coverage for both the spawn-failed and shutdown-failed paths, asserting the journal stays "prepared" and a later cold start can retry. Generated-By: looper 0.10.3 (runner=fixer, agent=claude-code) * fix(updater): await payload spawn acceptance * fix(packaged): advance confirmed handoff recovery --------- Co-authored-by: Looper <looper@noreply.github.com> |
||
|
|
6b90486c97 |
docs: reconcile stale documentation with current code (#5829)
* fix(docs): align question guidance with form flow * docs: reconcile stale documentation with current code * fix(docs): remove retired template metadata from guides Generated-By: looper 0.10.6 (runner=fixer, agent=codex) * fix(e2e): stabilize visual workspace helper Generated-By: looper 0.10.6 (runner=fixer, agent=codex) * fix(docs): align localized template guide metadata Generated-By: looper 0.10.6 (runner=fixer, agent=codex) |
||
|
|
4ddfc6e447 | fix(media): retry transient image generation responses (#5702) | ||
|
|
e49d1abc42 |
fix: resolve media-backed memory extraction keys (#5767)
* fix: resolve media-backed memory extraction keys Generated-By: looper 0.0.0-dev (runner=worker, agent=codex) * fix: refine unsupported memory provider detection |
||
|
|
6bee94be03 |
fix(deploy): honor target preview/production for Cloudflare Pages (#4483) (#4576)
* fix(deploy): honour target for Cloudflare Pages branch and URL selection (#4483) - deployToCloudflarePages: add optional target ('preview'|'production', default 'production') - Branch derivation: preview→'preview', production/omitted→'main' - URL selection: preview uses per-deploy alias; production uses production root - routes/deploy.ts: read target from request body (default 'production'), pass to deployToCloudflarePages, persist actual target (not hardcoded 'preview') - db.ts normalizeDeployment: read target from DB row instead of hardcoding 'preview' - contracts: widen target fields to 'preview'|'production'; add target to DeployProjectFileRequest * test(deploy): type FormData branch capture as string | undefined (#4483) The Phase-1 branch-capture objects were typed `{ branch?: string }` but assigned `string | undefined`, which fails `tsc` under `exactOptionalPropertyTypes`. Type the field as `branch: string | undefined` so the daemon typecheck gate passes; assertions are unchanged. * fix(deploy): reject invalid deploy target with HTTP 400 (#4576) The deploy route coerced any non-'preview' target to 'production', so a malformed value (typo, empty string, wrong case) silently published to the production Cloudflare branch instead of a gated preview. Validate the supplied target up front and return 400 for anything outside preview|production; an omitted target still defaults to production (non-breaking). Addresses review feedback on #4576 / #4483. * feat(deploy): add `od deploy` CLI subcommand with --target (#4483) Land the CLI surface for the preview/production deploy capability so it is not API-only. `od deploy <projectId> --file <name> [--target preview| production] [--provider vercel-self|cloudflare-pages] [--cf-zone-id] [--cf-zone-name] [--cf-domain-prefix] [--json] [--daemon-url]` POSTs to the same `/api/projects/:id/deploy` endpoint the web UI uses, with local validation of `--target` before any request (omitted target = no key, server defaults to production). Addresses the surface-closure review feedback on #4576 by shipping the embeddability (CLI) surface alongside the API contract. The human web-UI preview/production selector remains tracked as a follow-up in #4577. Tests: apps/daemon/tests/cli-deploy.test.ts (11 cases, written test-first). * feat(deploy): add web UI target selector for Cloudflare Pages deploys Closes the web-UI surface gap PerishCode flagged on #4576: deploy target (preview/production) had HTTP + od CLI support but no way to choose it from the deploy modal. Adds a target <select> to the Cloudflare Pages deploy panel, defaulted to Preview, threaded through deployProjectFile's new optional target argument and forwarded in the deploy POST body. References #4576. * fix(deploy): reject vercel-self target=production instead of silently deploying preview POST /api/projects/:id/deploy resolved the requested target but only threaded it into deployToCloudflarePages(); deployToVercel() never received it and always behaved as preview. A caller explicitly requesting target=production against the Vercel provider was silently deployed as preview with no error (Codex P2 finding on PR #4576). Real Vercel production-target support is out of scope for this PR, so reject the explicit request with 400 BAD_REQUEST instead. The guard gates on the raw requested value, not the resolved target, so an omitted target (which defaults to 'production' for Cloudflare Pages parity) keeps deploying Vercel as preview exactly as before. * fix(web): default Cloudflare Pages deploy target to production The deploy target selector defaulted to 'preview', both in its initial useState and when syncing from the (currently placeholder, always-'preview') GET /api/deploy/config response. A user who opened the Cloudflare Pages deploy modal and clicked Deploy without touching the selector silently deployed to preview instead of production, contradicting the daemon's documented default for an omitted target and pre-PR behavior. Default both sites to 'production'; explicit user selection of either target is untouched. FileViewer.deploy-target.test.tsx covers the default and both explicit selections. Fixes #4483 |
||
|
|
c110e40e8d | fix(frontmatter): parse flush-left YAML sequences, quoted inline arrays, and deep block scalars (#5499) | ||
|
|
265aa51630 |
feat(web): make Studio discovery visual and inline (#5496)
* feat(web): make Studio discovery visual and inline Move clarification forms into the chat flow, add artifact-aware visual style choices, and keep follow-up actions hidden until discovery is complete. * feat(web): keep clarification entirely in chat Remove the legacy Questions workspace tab, preserve discovery analytics in the inline form, and keep Skip, Submit, and answered summaries in one conversation flow. * docs: split Studio Chat input and response work Frame visual discovery as the input-side initiative and record response aggregation as a separately reviewed follow-up so the current PR stays bounded. * docs: reorganize Studio Chat experience plans Separate the overall journey, visual discovery, and response architecture so each scope can be reviewed and delivered independently. * docs: define first-artifact satisfaction prompt Limit the feedback prompt to the first successful previewable artifact so it gathers a useful signal without interrupting every iteration. * docs: keep Studio Chat plans independent Preserve the existing discovery proposal as one document and keep response architecture as a separate review surface without an extra master layer. * fix(web): preserve inline discovery behavior Keep visual direction single-select while restoring custom input, optional auto-continue, analytics, and current end-to-end coverage for the inline flow. * fix(web): preserve files when skipping discovery step * fix(web): exclude skipped file answers * fix(web): align inline discovery with slim prompt Preserve the latest discovery contract while keeping inline forms, uploads, drafts, and schema-driven visual selections consistent after the rebase. * feat(web): add style catalog preview images * fix(web): clarify discovery form completion * fix(web): preserve question form context * fix(web): time out unanswered question forms * test(web): cover inline question form next steps * fix(web): roll back partial inline uploads * ci: trigger validation after rebase * fix(web): unlock blocked inline question submits * fix(web): retain previewless visual answers * fix(web): load visual style previews from R2 * fix(web): clean blocked inline uploads * test(web): expect R2 style preview assets * feat(web): show full visual style catalog * fix(web): keep inline question answers consistent --------- Co-authored-by: xiaoche-hub <298951296+xiaoche-hub@users.noreply.github.com> |
||
|
|
66e4061274 |
fix(memory): retire chat auto-extraction and clean up its artifacts (#5708)
- chatExtractionEnabled now defaults OFF (explicit opt-in only); it gates the regex pack, chat-form profile capture, and LLM extractor together while leaving memory injection untouched - add a one-time boot migration (memory-cleanup.ts) that deletes regex-pack artifacts by name+template fingerprint, prunes user_profile to the canonical field set the settings editor writes, and forces the legacy config flag off once; marker-gated so a conscious re-opt-in sticks - persist writer provenance (frontmatter source:) on every new entry so future audits can tell auto-extracted facts from hand-written ones - LLM-extracted entries are deliberately kept: on-disk they are indistinguishable from hand-written prose, and they are the least harmful of the retired pipelines' output Co-authored-by: NJUHua <113895241+NJUHua@users.noreply.github.com> |
||
|
|
a219a933eb |
fix(memory): forward BYOK chat model through /api/memory/extract (#5177)
* fix: forward BYOK chat model to memory extraction (#5162) Root cause: Two issues preventing non-OpenAI BYOK providers (e.g. MiniMax) from working with memory-llm extraction: 1. Web side (ProjectView.tsx): The BYOK chatProvider snapshot sent to /api/memory/extract was missing the `model` field. Without it, pickProvider() fell through to PROVIDER_DEFAULTS.openai.model ('gpt-4o-mini'), which doesn't exist on non-OpenAI endpoints. 2. Daemon route (memory.ts): The /api/memory/extract POST handler did not extract body.chatModel, unlike every other memory route. Even when the web side passes a model, it was silently dropped. Changes: - ProjectView.tsx: Add `model: byokOpenCodeProvider.model` to the BYOK chatProvider snapshot so pickProvider() can use the user's actual chat model (e.g. 'MiniMax-M3'). - routes/memory.ts: Read `body.chatModel` and forward it to extractWithLLM options, matching the pattern used by all other memory routes. Testing: typecheck passes for apps/web (affected packages). The daemon typecheck requires pre-built @open-design/release which is a separate toolchain concern. * fix(contracts): add model field to ByokChatProviderConfig for BYOK memory extraction (#5177) The web-side memory snapshot was reading byokOpenCodeProvider.model but the BYOK config contract had no model property, causing a compile-time type error. Add an optional model field so the daemon can forward the user's chat-model picker choice to memory extraction. * fix(byok): copy config.model into byokOpenCodeProvider snapshot Address review comment on PR #5177: byokOpenCodeProviderFromConfig() did not copy config.model into the returned ByokChatProviderConfig, so byokChatProvider.model serialized as undefined and /api/memory/extract fell back to the hardcoded default (gpt-4o-mini) for OpenAI-compatible BYOK endpoints like MiniMax. Threading config.model through ensures the chat-selected model reaches memory extraction. Reviewed-by: mrcfps (review comments on ProjectView.tsx:5049) * test: add model field to BYOK payload expectations in web workspace tests * fix(daemon): forward BYOK chatProvider to post-turn memory extraction The daemon close handler creates memoryOptions for pickProvider() but never forwarded the per-call BYOK provider snapshot ('byokProvider' from the chat request body). This meant post-turn LLM memory extraction always fell through to environment/default provider detection instead of using the user's selected BYOK provider and endpoint. Added 'chatProvider: byokProvider ?? null' to the memoryOptions object so pickProvider() can run 'Same as chat' extraction against the user's actual provider/config, matching the behavior of the pre-turn /api/memory/extract path. Addresses reviewer feedback on PR #5177 (fix #5162). * fix(daemon): normalize byokProvider shape for post-turn memory extraction (#5177) The close handler forwarded byokProvider ({ protocol, ... }) directly as chatProvider, but memory-llm.pickProvider expects { provider, ... } with provider being a PROVIDER_DEFAULTS key. Now we map protocol → provider the same way the web pre-turn path does (ProjectView.tsx L5894). * fix(daemon): support keyless BYOK endpoints in post-turn memory extraction (#5177) Three-part fix for keyless BYOK (local vLLM / Ollama / openai-compatible servers with requiresApiKey=false): 1. server.ts normalization: thread requiresApiKey through to the memoryChatProvider snapshot so pickProvider() can distinguish keyless endpoints from missing-provider cases. 2. memory-llm pickProvider() BYOK gate: relax the apiKey guard so endpoints explicitly marked with requiresApiKey=false enter the BYOK branch instead of falling through to env/media-config fallbacks (which would route to unrelated OpenAI credentials / gpt-4o-mini). 3. memory-llm callOpenAI(): conditionally emit Authorization header only when apiKey is non-empty, preventing empty Bearer from being sent to keyless local servers that would reject it. |
||
|
|
c67048516c |
fix(web): polish preview status action (#5696)
* fix(web): polish preview status action * fix(web): keep preview failure hint passive --------- Co-authored-by: xiaoche-hub <298951296+xiaoche-hub@users.noreply.github.com> |
||
|
|
167db9de20 |
feat(web): add preview delivery status feedback (#5557)
* feat(web): add preview delivery status feedback
* fix(web): keep delivery retry in chat
* feat(web): stage preview delivery progress
* refine(web): clarify preview delivery stages
* fix(web): preserve preview delivery feedback
* fix(web): address preview delivery review
* refine(web): keep preview delivery status passive
* Revert "refine(web): keep preview delivery status passive"
This reverts commit
|
||
|
|
75af2b13cf |
fix(branding): preserve authored seed overrides (#5613)
* fix(branding): preserve authored seed overrides * fix(contracts): expose brand seed overrides --------- Co-authored-by: Rasmus Lovgren <booodhi@gmail.com> |
||
|
|
a1cbe0ec57 |
chore(release): bump main to 0.15.1 ahead of stable 0.15.0 (#5661)
Co-authored-by: open-design-release-bot[bot] <open-design-release-bot[bot]@users.noreply.github.com> |
||
|
|
4b660237c0 |
feat(prompts): land the slim system-prompt line as the default charter (#5603)
* test(daemon): freeze scenario x section gating matrix for composeSystemPrompt
composeSystemPrompt splices ~30 conditional sections whose gates form an
implicit matrix (session mode x design system x skill surface x memory x
execution profile). Before any prompt-slimming refactor, this golden
snapshot freezes which sections each representative scenario receives,
so a re-gating regression surfaces as a snapshot diff instead of a
silent production behavior change.
Detection is line-anchored heading markers: discovery quotes headings
like "## Active plugin" mid-sentence, so bare substring matching would
false-positive on sections that were never spliced in. A companion
assertion fails when a marker goes stale (no scenario produces it), and
position invariants pin injection-resistance first / role-marker guard
last across every scenario.
* perf(prompts): stop shipping rare-path media/deck payloads on every run
Langfuse telemetry (24h, 2k-trace sample) puts the composed charter at
p50 96KB (~24K tokens) with a 21% stable-cache hit rate — most turns
resend it whole. Two always-on blocks serve paths most runs never take:
- MEDIA_DISPATCH_HINT (2.8KB into every non-media project) embedded a
40-line worked bash polling loop. The loop moves to 'od media help'
(the CLI already prints the exact next wait command on handoff), and
the hint keeps only the dispatch rules agents historically fumbled:
no provider REST calls, no API-key asks, POSIX not PowerShell,
python3 not jq, fal-ai/* passthrough. 2,771 -> 1,547 chars.
- The freeform maybe-deck branch injected the full 20.7KB deck
framework into every kind=other / no-metadata run 'just in case'.
New detectDeckIntentSignal() scans the same request text the agent
will see (transcript included, so a deck mention anywhere in the
conversation stays sticky) for generous EN/CJK deck vocabulary and
skips the framework when nothing matches: 78.5K -> 57.7K chars on
non-deck freeform runs. False positives fall back to today's
behavior; deck-kind projects stay unconditional. An absent signal
(contracts/BYOK composer, tests) preserves legacy always-inject.
Also relieves the POSIX argv budget: DS-active runs sat within 4KB of
the 100KB hard cap that fails argv-bound adapters (aider/deepseek).
Baseline check: tests/media/policy-routes.test.ts (7) and
tests/connection-test.test.ts (1, local Kimi CLI version) fail
identically on the unmodified tree — no new failures from this change.
* test(daemon): refresh golden matrix snapshot for upstream memory prompt hardening
Rebase onto main picked up #4913, which reworded the memory
rule-proposal block (+230 chars in memory-bearing scenarios). Section
gating is unchanged — size-only refresh.
* feat(prompts): rewritten slim core charter behind OD_PROMPT_CORE=slim
The always-on doctrine layer (DISCOVERY_AND_PHILOSOPHY ~28K chars +
designer charter ~14K + three tail overrides that re-assert rules the
layered stack states in softer or contradictory forms) is rewritten as
ONE ~15.5K-char document: core-slim.ts. Same composed inputs drop from
65.7K to 36.0K chars (-45%); freeform non-deck runs from 57.7K to 28.9K.
Design rules of the rewrite:
- Every rule stated exactly once, under an explicit precedence ladder
(user request > active skill > design system > memory > charter).
- Contradictions resolved instead of stacked: one verification budget
(static pass + P0 checklist + craft scan + at most ONE render; the
'two passes is normal' re-score loop is gone), one turn-1 form
decision path (four skip conditions listed in one place, including
the memory task-brief replacement), Tweaks reconciled (iteration
surface on request, never demo controls in product files).
- Protocol markers are frozen API and survive verbatim: question-form
shapes/ids, pick_direction/brand_spec/reference_match branch values,
task-type routing options, data-od-id, EDITMODE markers, pinned
React/Babel/Motion tags, artifact handoff shapes.
- Emphasis quota: no IMPORTANT/CRITICAL inflation; imperatives carry
the weight.
Rollout: ComposeInput.promptCoreVariant ('classic' default | 'slim'),
daemon flips via OD_PROMPT_CORE=slim — zero behavior change until set,
A/B-able per daemon. Ask mode keeps the clarifying-questions tail (it
composes no core charter). Guards in tests/prompts/core-slim.test.ts:
a 16KB byte budget that fails any silent regrowth, frozen-marker
assertions, handoff profile switch, and classic-parity checks; matrix
snapshot gains slim scenarios.
Not yet covered (follow-ups): the packages/contracts BYOK composer
still ships only the classic stack; instruction-following A/B against
classic is the sign-off gate before changing the default.
* feat(prompts): shrink the slim core to an 8KB budget by evicting what a system prompt shouldn't carry
The 15.5K v1 slim charter still carried content that has no claim to an
always-on seat. v2 evicts it by category:
- Already owned elsewhere: the od-default task-type router form ships in
plugins/_official/scenarios/od-default/SKILL.md and reaches the prompt
as '## Active skill'; the charter now just defers to skill-owned
turn-1 forms. An ownership test pins the form to the skill file.
- Conditional-path content: per-platform delivery contracts (breakpoint
matrix, per-target files, native chrome) move to
PLATFORM_CONTRACTS_BLOCK, injected only when the project declares a
platform or multiple targets. Deck rules were already duplicated by
the deck-gated directive - dropped from the charter entirely.
- Model-competence content: PDF extraction how-tos, attached-image
explanations, JSON syntax reminders - deleted.
- Recital: the default discovery form example keeps only the brand
question verbatim (its pick_direction/brand_spec/reference_match
values are the one protocol-matched part); the other six questions
become a one-line recipe. Option labels were always model-localized
per language, so exact English menus were never stable anyway.
- React pins keep versions (the actual bug fix: 18.3.1 pairing +
framer-motion React build) but drop the SRI hashes and script-tag
boilerplate - reviewers should weigh the integrity-attribute loss.
Charter now renders at 8,187 bytes (budget test tightened 16KB -> 8KB).
Composed prompt for the common design path: 65.7K classic -> 29.6K slim
(-55%); freeform non-deck: 57.7K -> 21.5K (-63%).
* perf(prompts): evict the remaining always-on payloads from the slim stack
Follow-up to the 8KB charter: the composed slim prompt still carried
~13K of non-charter payload. Four evictions, all slim-only (classic
stays byte-stable for the A/B):
- Project metadata becomes a fact sheet ('facts' style). The classic
block had grown seven doctrine rules plus a responsive contract and
cross-platform rule - prose owned by the slim charter's Craft section
and PLATFORM_CONTRACTS_BLOCK. facts keeps key-value fields, media
workflow data, and two compact delivery lines (screen files, product
depth). kind=other: 3.6K -> ~1.1K.
- Memory two-loop scaffolding compressed ~5.5K -> ~2.9K: identical
headings and od-card shapes (web parses the card types; the daemon
checks the verify-scorecard), repeated rationale prose cut.
- zh-CN locale override drops the worked quick-brief sample copy under
slim - it mirrored the classic form verbatim, which slim recipes
instead; the form contract already mandates localization. 2K -> 0.85K.
- MEDIA_DISPATCH_HINT gates on detectMediaIntentSignal (same
transcript-sticky pattern as the deck signal): most conversations
never mention media, and the hint returns for the rest of the session
the moment one does.
- Direction library becomes a pull layer: slim composes a ~1.3K
id+label index and the agent fetches the chosen direction's full
palette/posture spec via the new 'od tools directions --id <id>'
CLI (also --label / --json / bare list). One tool call on the
direction-pick path, which runs at most once per project, replaces
6.7K of always-on palette data. Classic keeps the inline library.
Composed slim sizes: freeform non-deck 21.5K -> 12.6K; design full
stack 29.6K -> 22.2K (fixture DS; media hint drops another 1.4K on
non-media conversations). Baseline classic: 57.7K / 65.7K.
* refactor(prompts): restructure the slim prompt as static-first layers for cache and hierarchy
Two structural defects in the slim composition, both flagged against
prompt best-practice guidance:
1. Heading hierarchy was inverted: the document opened with an H2
('## Security: prompt injection resistance') and the H1 charter
appeared six sections later. The security section now lives INSIDE
the charter as an H2 right after Precedence (single source moved to
core-slim.ts; the classic stack imports it for its unchanged
standalone-first position), so the composed document reads
identity-first with a coherent H1 > H2 tree.
2. Ordering was cache-hostile: conditional blocks (mode override,
locale, direction index, platform contracts) preceded the static
charter — the exact 'conditional prefix sections' anti-pattern from
the prompt-caching guidance, where every flag combination becomes a
distinct prefix and nothing is shared. Slim now composes in
variability tiers:
static charter (identical across all runs — shared cacheable
prefix) -> conversation-stable (mode, locale) -> project-stable
(direction index, frames, platform, memory, instructions, DS,
skill, metadata, MCP) -> turn-variable (maybe-deck and media-hint
signal blocks, deferred to after MCP so a mid-conversation signal
flip only invalidates the suffix) -> recency-pinned role guard.
The charter's Precedence section now states that a session-mode
directive appearing after it takes precedence, replacing the
positional 'read first' authority the old ordering relied on.
Classic composition is byte-untouched (A/B baseline); ask mode keeps
the classic head. Charter budget 8KB -> 9.25KB (absorbed the ~1KB
security section; composed totals unchanged: freeform 12.7K,
full-stack 22.4K). New layered-ordering test pins the tier sequence
with line-anchored markers; the matrix position invariant now asserts
the per-variant head.
* fix(prompts): resolve five structure/expression defects from the charter review
Findings from the senior-review pass, each with a concrete failure mode:
1. Turn-ordinal section names misfire mid-session. 'Turn 1/2/3+' made
the model treat 'now is not turn 1' as license to skip the fresh-
brief flow when a NEW artifact is requested at turn 8 — and,
conversely, an event-based fix alone would have re-asked questions
the session already answered. Sections are now event-named ('On a
fresh brief' / 'When the brand answer arrives' / 'Once direction is
locked') AND a session-inheritance rule makes the mid-session case
explicit: a fresh brief inherits the conversation's locked
direction/brand/audience, asks only genuinely new unknowns, and
builds directly when there are none — matching current production
behavior with zero extra rounds or cache cost.
2. The handoff rule — the single highest-cost-if-violated output
protocol (never emit <artifact> in filesystem runs) — was the only
rule block without a heading. Promoted to '## Handoff'.
3. The form example read backwards: it showed the LAST question
(brand) and described the earlier six in reverse ('preceded
in-form by'), the only place in the prompt requiring the model to
assemble structure in reverse. Now forward-order: output question
shown in full, brand question shown in full (its values are the
protocol-matched part), the middle questions listed as
insert-between recipes.
4. Dangling reference: Craft said 'single family only for
tech/utility' — a direction name defined only in the Direction
index, which is absent whenever a design system is active.
Reworded direction-independent.
5. direction-cards was dead vocabulary: listed as a legal control but
every mention forbade it. Its one legitimate path is now named —
only when the user explicitly asks to see direction options.
Charter budget 9.25KB -> 10KB: fix #3 cost ~0.7KB (restoring the full
output-question JSON), accepted deliberately — it also restores
few-shot density for weaker fleet models, and the review's own
conclusion was that the last byte-squeezing round had negative ROI.
* fix(prompts): identity-first head for slim ask mode
The last remaining hierarchy inversion: slim ask still opened with the
H2 security block before the H1 ask override. Ask now matches design
mode's structure — the ask override (the turn's whole charter) opens
the document and the security section reads as its first subsection.
Both blocks are static, so the swap is cache-neutral. Classic remains
byte-untouched.
* fix(prompts): repair five slim-vs-classic regressions from the second audit
Second senior review against the shipping classic stack surfaced two real
bugs and four wrongly-compressed classic behaviors in the slim variant:
- text_artifact/BYOK runs got the direction INDEX plus an instruction to
run the `od tools directions` CLI they do not have; classic inlined the
palettes. Slim now profile-switches: filesystem keeps the 1.3K pull
index, text_artifact gets the full inline library back (+1.35K tokens
on that path only; still ~68% below classic, and now under deepseek's
30KB Windows argv cap, which classic never was).
- API_MODE_OVERRIDE's literal scope ('every LATER instruction … is
overridden') inverted when slim moved the charter ahead of it: the
charter's TodoWrite/render-check instructions escaped the override.
Plain-stream slim runs now compose the override first again. Pure
reorder, zero bytes.
- PLATFORM_CONTRACTS_BLOCK gated only on project metadata, missing
freeform briefs that name a platform in text ('做个 iOS app 原型').
New detectPlatformIntentSignal (same generous EN/CJK shape as the
deck/media signals) ORs into the gate; server wires it.
- media-contract's 'explicit layer overrides' paragraph cross-referenced
the classic-only layer names; reworded layer-agnostic (classic +19B).
- Restored four classic rules the compression pass misfiled as
model-competence filler: never hot-link user-attached images by URL
(product constraint), skill/DS per-domain precedence clause, expressive
form-control guidance, and the modern-CSS encouragement line. Charter
budget 10,240 -> 10,752 with rationale in the test comment.
Classic bytes untouched except the shared media-contract rewording.
Golden matrix: gating unchanged; sizes only (classic +19, slim +537).
Pre-existing failures unrelated to this change and failing on the
unmodified baseline: tests/media/policy-routes.test.ts (7), Kimi
connection test (local CLI version mismatch).
* fix(prompts): defer signal-triggered platform contracts to the turn-variable suffix
Two follow-ups from the third structure audit:
- PLATFORM_CONTRACTS_BLOCK sat in the project-stable zone while its gate
now includes the turn-variable conversation-text signal. A mid-session
'make it an iOS app' would insert the block early and break the cached
prefix for every section after it (DS, skill, memory, metadata, MCP).
The gate is now split by trigger stability: metadata triggers keep the
early slot (fixed at project creation), signal-only triggers defer to
slimTurnVariableParts like the deck/media signal blocks. The
slimTurnVariableParts declaration hoists above the design section so
the gate can reach it; no byte or ordering change for any existing
gating combination.
- Slim ask mode on a plain stream composed CHAT -> injection -> API
override, inverting classic's authority order (API before CHAT, so
'overrides every rule below' covers the chat charter). The ask head
now leads with the API override on plain streams; the later push is
gated on !isSlimCore. Zero-byte reorder; the two overrides do not
conflict in content, this restores parity with the shipping order.
* fix(prompts): restore form-tailoring mandate and first-message form scope
The slim-vs-classic blind A/B eval (2026-07-04, 17 pairs, double-order
opus judging) surfaced two form regressions with converging judge
rationales:
- Form quality lost 12:1:4 to classic. Mechanism: classic's discovery
layer carries extensive per-brief form-tailoring guidance; slim
compressed it to the single word 'tailor'. Models shipped the default
recipe verbatim — app-platform checkboxes on landing-page briefs, the
fundraising ask/traction buried in one textarea placeholder. Restored
as an explicit mandate with concrete per-brief examples, a
plausible-targets-only rule for the platform options, and a
translate-the-labels note on the tone recipe.
- DS + detailed-brief projects skipped the turn-1 form 3/6 vs classic
1/6: the mid-session inheritance clause generalized to first
messages. Added the explicit scope limit: inheritance never applies
to a project's first message.
Charter budget 10,752 -> 11,264 with the eval citation in the test
comment. Golden matrix: sizes only.
* fix(prompts): template tokens in the seed must be replaced before handoff
The 40-task x 5-turn A/B eval (2026-07-05) found slim's remaining T2
quality gap is driven by catastrophic failures, not taste: 3 of 39
pairs shipped the live-artifact skill seed with unrendered {{mustache}}
tokens (classic did this once). Mechanism: slim compressed classic's
seed-handling guidance to 'copy the seed, paste layouts', losing the
explicit fill-with-real-content step.
- Read-before-write bullet now ends with: replace EVERY template token
with real content; a {{placeholder}} or empty section reaching the
user is a failed delivery. (Also covers the eval's 4 thin-content
failures.)
- Self-check static pass adds 'leftover template tokens' to its scan.
Charter 11,155B, within the existing 11,264 budget — no bump needed.
* fix(prompts): first build ships complete, no stub sections
Clean-scope T2 analysis of the 40-task eval (excluding the 10 tasks
confounded by the live-artifact skill protocol) still shows a first
build completeness gap: main 7.34 vs slim 6.10, driven by thin-content
deliveries (empty canvases, placeholder-looking blocks) on 4 of 29
pairs. The charter's 'show something visible early' line, uncushioned,
reads as license to hand off the early skeleton.
- Qualify it: '…but the turn ships the COMPLETE artifact: no stub
sections.'
- Self-check static pass adds 'empty or stub sections' to its scan.
11,215B, within the 11,264 budget.
* refactor(prompts): split the overloaded turn-1 form section by concern
The slim charter's '## On a fresh brief' H2 bundled six distinct
concerns under one heading — trigger, default action, mid-session
inheritance, skip conditions, form-shape tailoring, and the form JSON
contract — so a maintainer could not scan the outline to find any one
rule, and the cross-cutting form contract (valid on ANY turn) was buried
under a heading scoped to fresh briefs.
Behavior-equivalent restructure (every directive's wording preserved;
only headings added and the form contract's 'any turn' note moved from a
parenthetical to the section lead):
- ## Turn 1 — the discovery form (trigger + default action)
- ### When to skip or inherit the form (inheritance + skip conditions)
- ## Writing a `<question-form>` (cross-cutting: shape + tailoring + example + ordering)
- ### Form contract (JSON validity + type enum + localization)
The charter outline now reads as a per-concern document. Frozen protocol
markers (form JSON, brand values, type enum, allowCustom) unchanged.
Charter 11,242 -> 11,322B; budget raised to 12,288 for readability
headroom at the maintainer's direction (budget explicitly deprioritized
for this change). Golden snapshot regenerated; 113 prompt tests green.
* refactor(prompts): readability second pass + multi-turn DS/constraint adherence
Two changes, from a critical re-read plus production feedback.
A) Readability / logic second pass (behavior-equivalent, wording preserved):
- Heading style: the two remaining imperative-tail H2s became event
labels with the directive moved to the body lead ('## When the brand
answer arrives', '## Once direction is locked'), so the outline reads in
one consistent grammar. Dropping '…hand off' also fixes a heading that
promised handoff while the rules live in the next section.
- The self-check bullet's inline (1)(2)(3)(4) run-on became a four-item
sub-list — the least-scannable line is now the most scannable.
- The 70-word 'Read before you write' sentence split into shorter units;
'show early / ship complete' separated into its own bullet so the two
ideas stop reading as contradictory.
- Precedence: skill and DS collapsed into one rank split by domain (was
numbered 2 vs 3, contradicting the 'each highest in its own domain'
clause) — removes the only real logical seam in the ladder.
B) Multi-turn edit adherence (new '## On an edit or tweak' section):
Production feedback reports the design system and explicit user
constraints frequently drift during multi-turn edits. The charter was
turn-1/first-build heavy and had no standing edit-mode invariant. Added,
in the lifecycle between build and handoff:
- an edit touches ONLY what the user named; untouched sections/values
stay as they were; re-read the file and edit in place.
- the design system binds on EVERY turn, not just first build — no
drift, no raw hex, no palette re-pick because this turn was about
something else.
- every hard constraint stated this session persists across later turns
until the user relaxes it; a new request adds to constraints, never
silently resets them.
Charter 11,322 -> 12,029B, within the 12,288 budget (no raise). New
frozen assertion pins the adherence invariants against future
compression. 114 prompt tests + guard + typecheck green; golden snapshot
regenerated.
* refactor(prompts): two-tier charter — lifecycle spine, grouped pillars, precedence precision
From a senior word-by-word re-read. The charter was 13 flat H2s spanning
three conceptual tiers (foundations / lifecycle / reference) with no
visible spine, and the precedence wording could invert authority.
Structure (behavior-equivalent, rule wording preserved):
- 13 flat H2 -> 5 H2: two foundations (Precedence, Security) + three
pillars, each grouping its H3s —
## Discovery — ask before you build (Turn 1, skip/inherit, writing, contract)
## Delivery — brand → build → iterate (brand answer, build, edit, handoff)
## Craft & contracts (craft, technical, conduct)
The delivery lifecycle is now one connected, ordered spine — 'Editing
an existing artifact' reads as a named phase of it, which should help
the multi-turn drift the edit-adherence rules target.
Precision fixes:
- Precedence: 'On conflict, higher wins: 1…2…3…4' was ambiguous (which
number is higher?) — reworded to an explicit list, 'the one higher on
this list wins — the user's request is the highest authority, this
charter the lowest', and the trailing pronoun tightened to 'overrides
it wherever the two conflict'.
- 'no tool calls or file reads first' -> 'before it'; 'Resolve the
source' -> 'Resolve the brand source'; 'restyle the whole thing' ->
'restyle beyond the request'.
- Trimmed one of three localization restatements (kept the authoritative
one in Form contract, now naming labels/options/placeholders) and the
'however complete the brief reads' duplicate.
Handoff/Craft/Technical/Conduct headings demote to H3 under their
pillars (local constants, not shared with classic). Charter 12,029 ->
12,294B; budget 12,288 -> 13,312. Ordering/adherence test anchors and
golden snapshot updated. 114 tests + guard + typecheck green.
* fix(prompts): make edit-adherence forceful — do exactly what was asked, verify it landed
Production feedback and maintainer review: the edit section addressed
only scope (don't over-change) and persistence (DS/constraints), but not
the core instruction-following failure — 'the user asked for A and the
result isn't A'. The whole section was defensive; the positive imperative
that the requested change must actually happen, completely, was missing.
Strengthened '### Editing an existing artifact':
- Leads with the imperative: the delivered file must actually be A — do
exactly what was asked, in full, in every place it applies, with
complete-vs-partial examples ('dark green' = every element, not one).
- Forbids reinterpreting, 'improving on', partially applying, or
substituting own taste for the user's literal words — ties to
Precedence #1 (user's request is highest authority).
- New 'Verify before you hand off' bullet: open the file, confirm the
change is actually present and complete and no in-force constraint
broke; never report a change you did not make (the claim-vs-reality
failure).
- Kept the scope / DS-binding / constraint-persistence bullets.
Charter 12,294 -> 13,122B, within the 13,312 budget. Frozen assertion
updated to pin the positive imperative and the verify bullet.
* fix(prompts): a later explicit request overrides a conflicting earlier constraint
Edge case raised in review: turn 2 'fix the color to blue, don't change
it', turn 4 'change everything to yellow'. Correct behavior is turn 4
wins (the user is changing their own mind; yellow becomes the new
standing constraint), which Precedence #1 + 'persist until the user
relaxes it' already imply — but the old bullet also said 'a new request
ADDS to those constraints', which a literal model could read as
additive-only and stall on the turn-2 'don't touch it'.
Rewrote the constraint-persistence bullet to make the override explicit:
constraints carry forward, only the user can lift or change one, and a
later explicit request overrides a conflicting earlier constraint — with
the blue->yellow turns as the in-prompt example. The anti-silent-drop
guard (model must never drop a standing constraint on its own) is kept.
Charter 13,122 -> 13,412B; budget 13,312 -> 14,336.
* fix(prompts): make the plan step agent-agnostic — TodoWrite is Claude-only
Open Design drives many local code agents; the real session mix is ~65%
non-Claude (codex, opencode, gemini, ACP family, …). The slim charter's
plan step hardcoded 'first tool call is TodoWrite' + in_progress/completed
— a Claude Code tool those agents don't have. For ~2/3 of traffic that
instruction is dead: the agent either skips planning (losing the exact
lever that helps multi-turn adherence) or hallucinates a tool it lacks.
Everything else in the slim charter was already agent-agnostic (render
checks and direction pulls go through the shell OD CLI wrapper; protocol
markup is assistant text), so TodoWrite was the one hardcoding.
- Charter plan step → generic: 'lay out a short, updatable plan; if your
runtime has a structured plan/todo/task tool, use it, else a numbered
list; never call a tool you don't have.'
- Composer injects a concrete note ('Your plan tool is TodoWrite; the
host renders it as a live Todos card; mark in_progress/completed') only
for Claude-family runs (streamFormat claude-stream-json: claude,
codebuddy, amp) — the only agents with TodoWrite and the host's live
Todos card. Agent-specifics live in the composer, portable doctrine in
the charter.
- API_MODE_OVERRIDE reframed from 'TodoWrite/Read/… are unavailable' to
'any tool call — TodoWrite/…/or whatever your runtime exposes — will
not execute'. Keeps the specific names (issue #313 anti-pseudo-tool,
test-pinned) while covering non-Claude runtimes.
Two frozen tests pin: charter has no TodoWrite + generic wording;
Claude-family gets the note, json-event-stream doesn't. 116 tests +
guard + typecheck green. Charter 13412 to 13543B (budget 14336).
* feat(prompts,live-artifacts): render one-level repeats, tighten slim charter craft guards
- live-artifacts/render: implement the documented `data-od-repeat="item in data.path"`
one-level repeat directive (specified in the schema but never rendered), with
loop-scoped `{{item.*}}` bindings, single-pass expansion so data values are never
re-interpolated, and explicit rejection of nested repeats / non-array sources.
- live-artifact SKILL.md: state the html_template_v1 binding contract in the workflow
(flat data.* scalars + data-od-repeat lists; no hand-loops, no bare loop vars) so
templates stay within the renderer's capability.
- core-slim charter: "replace every template token" now yields to a skill's
template-plus-data protocol; add an objective Layout-integrity craft rule (no
overlap, no clipping, oversized display type must fit its column, charts use fills
not outlines) and name those failure modes in the self-check.
* feat(prompts): add agent-agnostic production-value craft rule to slim charter
Visual-first pages regressed on slim: hero images were hand-drawn CSS boxes
instead of generated art (P1), and controls read flat/greyscale (P5). The slim
charter had dropped all of main's imagery + visual-system guidance.
Add one "Production value — feel shipped, not greyscale" craft rule: generate a
real image via the Open Design media tool (referenced through the injected
`"$OD_NODE_BIN" "$OD_BIN"` wrapper, so it is runtime-agnostic — not tied to any
one code agent) when a hero/product shot would carry the page; ship a real
product palette, colored interaction states, and elevated primary controls.
Bump SLIM_CORE_BYTE_BUDGET 14_336 -> 15_360 to seat this load-bearing guidance.
* feat(prompts): generalize the production-value imagery rule beyond hero shots
40-task eval + user review (C3 tea gift-box, P1) showed slim under-generates real
imagery on visual-first work, defaulting to hand-drawn schematic graphics. Reframe
the rule from a narrow "hero/product shot" trigger to a general default: whenever a
real picture would lift the artifact (product, place, food, person, hero, texture),
generate it via the media tool; drop to a diagram/UI mock only when that genuinely
serves the content better. Still agent-agnostic (media tool via the injected
"$OD_NODE_BIN" "$OD_BIN" wrapper).
* fix(prompts): let the production-value rule use the run's own image generation
Hard-pointing imagery at `od media generate` only works when a daemon spawned the
agent (it injects OD_PROJECT_ID); a standalone agent (eval harness, external
runtime) can't reach it, so slim silently fell back to hand-drawn placeholders
while codex-run peers used their native imagegen. Broaden the rule: generate through
whatever image generation the run offers — the Open Design media tool when wired up,
otherwise the runtime's own native image generation. Verified: C3 slim now emits a
real generated product photo instead of a schematic box.
* test(daemon): drop connected-external-mcp from the golden matrix
Upstream moved the connected-external-MCP directive out of
composeSystemPrompt into the per-turn slice (live OAuth token state must
stay out of the cached stable prefix), so it is no longer a compose
input or a gateable section.
* chore(prompts): default to the slim charter on the validation branch
VALIDATION-ONLY commit for feat/system-prompt: packaged beta builds must
exercise the rewritten slim charter, and the packaged sidecar env
allowlist does not forward OD_PROMPT_CORE, so the default flips to slim
here. OD_PROMPT_CORE=classic restores the classic stack. Do not carry
this default into main.
* perf(prompts): batch slim tool work, with quality guards restored (#5555)
* perf(prompts): batch slim tool work
* fix(prompts): restore seed-copy rule and unconditional own-browser ban
The tool-economy pass (
|
||
|
|
d7151b2b60 |
feat(analytics): link downloads and first-party browser opens (#5442)
* feat(analytics): attribute desktop downloads * fix(analytics): initialize attribution routes after HTTP deps * feat(analytics): bridge first-party browser opens * feat(analytics): bridge first-party anchor opens * fix(analytics): make attribution claims idempotent * fix(analytics): record pending attribution results * chore(analytics): bind landing attribution KV * fix(ci): remove landing preview wrangler config override * fix(landing): resolve download attribution function * fix(analytics): harden attribution token consumption * fix(analytics): preserve same-installation claim retries * fix(analytics): merge recovered attribution retries * fix(analytics): preserve attributed browser links * fix(analytics): clear attribution on privacy reset * fix(analytics): bridge stopped AMR console links * fix(ci): deploy attribution schema before Pages * fix(ci): run attribution migrations with Wrangler 4 --------- Co-authored-by: Siri-Ray <2667192167@qq.com> Co-authored-by: PerishCode <perishcode@gmail.com> |
||
|
|
da7c4ae051 |
fix(web): rank Community "Newest" by shipped publishedAt, not local install timestamps (#5597)
* fix(web): rank Community "Newest" by shipped publishedAt, not local install timestamps
The Community gallery Newest toggle sorted by the installed-record
updatedAt/installedAt. Those are stamped when a machine seeds its own
database, so a fresh install (or an upgrade that re-stamps the catalog)
writes the whole bundled catalog milliseconds apart in folder-walk
order — Newest showed reverse walk order (roughly reverse-alphabetical),
unrelated to publication recency.
Root-cause fix: bundled templates now ship a real publication date.
- packages/contracts: add optional manifest field publishedAt (ISO 8601).
- apps/web sortByNewest: freshness = publishedAt when parseable, else
updatedAt (right signal for user-installed plugins); tie-breaks and
stability unchanged.
- plugins/_official: backfill publishedAt into all 460 bundled manifests
from each folder's first-appearance commit date in upstream git
history.
- e2e: repository-resource guard requiring every bundled manifest to
declare a parseable publishedAt, so the signal cannot silently erode.
- docs: field reference entries in plugins-spec (en + zh-CN).
Issue: https://plane.powerformer.net/open-design/projects/49832a02-3158-4faf-bf2f-d0e39c40c7e6/issues/ca0778af-97f5-4fc0-bc63-739280904979
Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code)
* worker: 模板用最新筛选,筛选出来的不是最新的
Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code)
* fix(web): keep shipped appeal order inside same-publishedAt batches
Review follow-up (#5597, nettee): after the freshness tie on an equal
publishedAt, the comparator still consulted local updatedAt/installedAt,
which a fresh install stamps in folder-walk order — so every
same-publication batch (largest: 322 manifests on one catalog date)
re-sorted per machine instead of keeping the shipped visual-appeal
ranking. Same-publishedAt records now preserve their incoming position;
local timestamps break ties only when at least one side has no usable
publishedAt. Adds the requested regression spec pinning
same-publishedAt / different-updatedAt records.
Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code)
* fix(web): gate publishedAt freshness to bundled records; untrack oversized looper attachment
Review follow-up (#5597, nettee round 2): publishedAt was overriding
updatedAt for every visible plugin, so a user-installed github/local/
marketplace plugin carrying an old manifest publishedAt would be buried
behind bundled templates despite a newer local install. publishedAtMs
now returns null for non-bundled records — "Newest" means install
recency for user installs, catalog publication for bundled ones. Adds
the requested regression (non-bundled, old publishedAt, newer
updatedAt) and clarifies the field docs (en + zh-CN).
CI repair: commit
|
||
|
|
9d055a0fa6 |
feat(amr): add artifact-based upgrade guidance for Free users (#5591)
* feat(amr): add artifact-based upgrade guidance * fix(amr): fail open when billing plan is unavailable --------- Co-authored-by: bone3deep1962-collab <bone3deep1962@gmail.com> |
||
|
|
ebada4cac3 |
fix(daemon): pin mermaid dark-theme discipline into the deck framework prompt (#5595)
Dark-themed decks and pages that embed a Mermaid diagram had zero guidance on diagram theming, so the model shipped Mermaid's default (light) theme on dark slides: near-black edge labels, black strokes, and a transparent svg background sitting directly on the dark slide background. Reproduced on a dark deck slide (--bg #101014): edge labels measured ~3.3:1 contrast (below WCAG AA) and connector lines were nearly invisible (~1.9:1) — matching the 0.14.1 acceptance report "暗黑模式下 mermaid 图表的文字与背景对比度不足". Add a compact "Mermaid diagram theme discipline" section to DECK_FRAMEWORK_DIRECTIVE, sibling to the data-chart discipline (#5542): prefer deck-token-styled HTML/CSS/SVG diagrams; when embedding Mermaid, derive the theme from the slide background at initialize time (dark background → theme: 'dark', or theme: 'base' + themeVariables with a dark primaryColor — darkMode alone keeps the cream node fill default); never rely on the transparent svg backdrop; plus a drift-mode ban list and a pre-handoff/pre-emit self-check item. Mirror the identical section into the contracts copy so API/BYOK mode is covered too, keeping each side's self-check heading intact. With the disciplined init, the same dark slide renders the diagram at ≥7.9:1 (edge labels) and 10.5:1 (node text) contrast. Anchor the change with prompt-composition tests on both sides asserting the directive carries the mermaid-theme markers (red on main, green here). Source: https://plane.powerformer.net/open-design/projects/49832a02-3158-4faf-bf2f-d0e39c40c7e6/issues/9cd2904d-e52c-4813-9e4f-e7a3512aaa12 Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code) |
||
|
|
83d67d5a62 |
fix(packaged): widen win32 daemon status-wait budget + back off polling (#5515)
* fix(packaged): widen win32 daemon status-wait budget + back off polling Windows first launches are the single biggest packaged startup-crash bucket (packaged_runtime_failed, failure_kind=unknown): ~178 events / 61 devices/day, all status-wait timeouts with no exit code, signal, or daemon log. PostHog shows ~90% of those devices opened the app on a later launch — the daemon was slow, not dead — because Defender scanning freshly-written packaged binaries inflates the cold start (native better-sqlite3 load + first SQLite open/migrate + status-pipe bind) past the 35s wait budget. - resolveDaemonStatusTimeoutMs is now platform-aware: win32 gets 90s of AV-scan headroom, other OSes keep 35s. The OD_LEGACY_DATA_DIR migration override still wins on every platform. The web sidecar wait uses the same widened baseline (it has no legacy path). - waitForStatus backs off geometrically (150ms -> 1.5s cap) between connect polls instead of a flat 150ms, so a not-yet-bound pipe is not hammered with connects that add contention to an already-slow start. - classifyStartupFailure tags the timeout message as `status-timeout` instead of `unknown`, so the bucket is measurable — the signal for whether the budget raise actually drained the AV-scan-slow launches. * fix(packaged): keep timeoutMs a hard bound — cap the backoff sleep to remaining budget Review follow-up (@nettee): with the new geometric backoff, waitForStatus could overshoot the configured status-wait budget by a full backoff interval — a slow/dead sidecar could sleep the whole 1.5s cap after the deadline had passed before the while-condition re-checked it (~2.3s over, vs ~0.95s pre-change). Since this PR intentionally widens the win32 budget, the overshoot matters. Cap each poll sleep to the time remaining before the deadline (and break out immediately when none remains), so timeoutMs stays a hard-ish upper bound; the only residual overshoot is the single in-flight requestJsonIpc connect timeout. |
||
|
|
bdc66c978e |
fix(daemon): pin data-chart discipline into the deck framework prompt (#5542)
Chat-generated decks that fall through to DECK_FRAMEWORK_DIRECTIVE (no-skill-seed deck projects + the freeform slide-deck path) had zero guidance for data charts, so the model hand-wrote div/CSS bar charts with two stable failure modes that match the reported symptoms: bar lengths eyeballed as magic `height: NN%` numbers that don't track the data, and value labels clipped away inside fixed-height bars. Add a compact "Data chart discipline" section to the directive: a paste-ready skeleton that carries each value as an inline `--v`, declares one shared `--max` on the chart container, and computes bar length via `calc(var(--v) / var(--max) * 100%)`; a rule that every data point renders a visible category + value label outside the bar (never inside a clipping fixed-height track, same root cause as the brand-deck `funnel-label-clipped` guard); a drift-mode ban list; and two pre-handoff / pre-emit self-check items. Mirror the identical section into the contracts copy so API/BYOK mode is covered too, keeping each side's self-check heading intact. Anchor the change with prompt-composition tests on both sides asserting the directive now carries the chart-discipline markers (red on main, green here). Source: https://plane.powerformer.net/open-design/projects/49832a02-3158-4faf-bf2f-d0e39c40c7e6/issues/34ed7e1b-5676-48d6-8195-a4be71124ad4 Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code) |
||
|
|
6426847fba |
fix(web): separate run success from result delivery (#5525)
* fix(web): distinguish run success from result delivery Persist delivery outcomes separately so successful design runs cannot report completion before a usable artifact is available. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): recover missing design delivery after reload * chore(ci): rerun pull request checks * fix(web): avoid reattaching local delivery finalization * fix(web): retry delivery failures * chore(ci): rerun pull request checks * chore(ci): retrigger managed checks --------- Co-authored-by: xiaoche-hub <298951296+xiaoche-hub@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
d4762a9522 |
fix(desktop): break renderer crash-reload loops with a circuit breaker (#5322)
* fix(desktop): break renderer crash-reload loops with a circuit breaker A renderer that crashes deterministically — a GPU/V8 CHECK failure, a corrupt profile that aborts on every load — is reloaded forever by the desktop poll loop: load → crash → reload → crash. One 0.14.0 device did exactly this, emitting 26,011 desktop_renderer_crash events in a single day (96% of all renderer crashes on the release) while its window stayed blank the whole time. Add RendererCrashLoopBreaker (pure, unit-tested): after 5 crashes inside a 60s rolling window it opens. The poll loop then parks on a static, dependency-free error screen that explains recovery is automatic, rather than cycling the crash; markRendererFailed no-ops while open. Per-crash telemetry is suppressed once open — the tripping crash is still reported, flagged loop_tripped — so one wedged device can't emit tens of thousands of identical events. The breaker re-arms after a 5-minute quiet cooldown so a transient fault still self-heals without a reinstall. * feat(desktop): make crash-loop recovery active + observable Review feedback: the breaker only "waited" — its cooldown re-arm reloaded the same URL from the same cache, and the whole flow was near-invisible. On re-arm it now actively recovers: clears the HTTP + V8 code caches (non-destructive — no cookies or storage) before the retry reload, targeting the most common recoverable cause (a corrupt cached bundle or shader) instead of hoping conditions changed on their own. And it's now observable: a console.warn when the breaker opens (parking), a console.info per recovery attempt, and a bounded recovery-attempt analytics signal (reason:"recovery-attempt", recovery_attempt: N, at most one per cooldown). Triage can tell a chronic looper (index keeps climbing) from a device that recovered (events simply stop) without re-introducing the per-crash spam the breaker was built to kill. * revert(desktop): drop active cache-clear recovery, keep it passive + observable Reviewer worry: clearing caches on every cooldown could amplify churn on a wedged device without helping the dominant crash cause (a GPU/V8 CHECK, exit 0x80000003), i.e. pour oil on the fire. Back the breaker off to "stop the loop + passive retry": on re-arm it reloads without mutating any device state and lets a transient fault clear on its own. The observability added alongside stays: a warn on breaker-open, an info per recovery attempt, and the bounded recovery-attempt analytics signal (recovery_attempt: N) so chronic loopers are still visible. * feat(desktop): add report + save-logs actions to the renderer crash screen The recovery screen was informational only. On a renderer crash the daemon is still alive, so give the wedged user an actionable exit — reusing IPC the preload already exposes, no new surface: - "Report a problem" → window.__od__.openExternal() opens a prefilled GitHub new-issue whose title + body carry the app version, OS, and the renderer exit code (e.g. 0x80000003) a triager needs, and ask the user to attach the logs. - "Save logs…" → window.openDesignDesktop.exportDiagnostics() runs the existing diagnostics-bundle export (save dialog → zip). Neither an issue URL nor mailto can carry a file attachment, so the flow is save-then-attach. Each button hides itself if its bridge is missing (preload failed to load) to avoid a dead control. * feat(desktop): add "Email us" (support@open-design.ai) to the crash screen Alongside "Report a problem" / "Save logs…", the recovery screen now offers a prefilled mailto to our support address for users who'd rather email than open a GitHub account. It reuses the existing openExternal bridge; the shell:open-external handler now also allows a mailto addressed strictly to the support address (isSupportMailtoUrl, unit-tested) — no other scheme or address opens, so widening past http can't be abused into arbitrary-scheme launches. The link hides itself if the bridge is absent, like the other actions. * style(desktop): match crash screen to the app's monochrome brand palette The crash screen used a stray blue accent that didn't match the app. Repaint it with the neutral design tokens from apps/web (tokens.css): warm off-white #faf9f7 + near-black #1a1916, a monochrome near-black primary button, and a plain underlined (not blue) email link. Dark mode inverts the pair to a light button on the dark bg (a near-black button would vanish), mirroring the black/white onboarding look. * fix(desktop): address review — tighten support mailto + reveal crash screen Two Looper findings on the crash-screen actions: - Security: isSupportMailtoUrl validated only protocol + pathname, so `mailto:support@open-design.ai?bcc=attacker@example.com` (or ?to=/?cc=) still passed and shell.openExternal would launch the mail client with extra recipients/headers through the renderer-exposed bridge. Now reject any query key other than the crash screen's own subject/body. - Startup lifecycle: parking on the crash screen left `revealed` false and the splash open when the loop tripped before revealWhenReady(), so the runtime's show() kept focusing the splash — a user re-focusing during a startup crash loop was sent back to the boot splash. Route the park through revealMainWindow() so the recovery screen becomes the revealed, focused window and the splash is torn down (no-op after normal reveal). * feat(diagnostics): make the support bundle root-cause native renderer crashes Audit of "Save logs…" for the crash-recovery screen: on a renderer crash the bundle collected text logs but NOT the one artifact that can explain an opaque native abort like 0x80000003 (a V8/Chromium CHECK) — a minidump. crashReporter was never started (no dumps existed anywhere), the crash-report collector was macOS-only, and there was no GPU info (the usual suspect for such crashes). Close the gap end to end: - apps/desktop: start crashReporter (uploadToServer:false — on-device only) and relocate Electron's crashDumps into <logs/desktop>/crashes, inside the log tree the export already bundles; snapshot GPU + feature-status + runtime info to <logs/desktop>/gpu-info.json at startup. - packages/diagnostics: add a `binary` LogSource kind (copied verbatim, no text decode/redaction so a minidump isn't corrupted) and findCrashDumps() to collect .dmp files; buildDiagnosticsZip gains a `crashDumps` input. - apps/daemon: bundle logs/desktop/gpu-info.json and the relocated crash minidumps (cross-platform, capped, within 14 days). The renderer crash context (reason/exit code/url) is already captured — the main-process console.error lands in logs/desktop/latest.log — so a bundle now carries stack (minidump) + GPU state + context: enough to root-cause. * fix(desktop): mkdir crashDumps dir before setPath so first-crash dumps land in the log tree Review (PerishCode): app.setPath('crashDumps', dir) throws when the dir does not exist; on a fresh runtime logs/desktop/crashes won't exist yet, so setPath threw, we fell back to Electron's default crashDumps location, and the daemon export (which only scans <logs/desktop>/crashes) missed the minidump for exactly the first crash loop this is meant to root-cause. mkdirSync the dir (recursive) before setPath. * fix(desktop): reject CR/LF in the support mailto subject/body Review (PerishCode): the key allowlist rejected to/cc/bcc keys, but a decoded CRLF in an allowed value (subject=ok%0D%0ABcc:attacker@…) could still inject a mail header once the mailto reaches the mail client. Reject any subject/body value containing CR or LF. |
||
|
|
bc5b6f0588 |
fix(daemon): flag runs that end with unfinished work so status never reads "Completed" early (#5470)
A design-generation run that ended its turn with TodoWrite tasks still pending / in_progress / stopped — or was truncated by max_tokens — was stamped `succeeded` and read "Completed" on the Pet task center and the project pill, while the chat footer simultaneously read "Stopped with unfinished work". Users trusted "Completed", assumed the work was done, and re-requested, creating duplicate generations and eroding trust in the run-state UI (#1247 / #1060). Root cause: the daemon equates "the turn/process ended" with "succeeded" and never consults the agent's own declared work; multiple web surfaces then map `succeeded` -> "Completed" without any completeness signal, even though the chat footer already distinguishes unfinished work. Fix — one canonical signal, consumed everywhere so the surfaces cannot drift: - packages/contracts/api/run-completeness.ts defines the canonical "unfinished declared work" predicate (status !== 'completed', so pending / in_progress / stopped all count), matching the web footer's unfinishedTodosFromEvents exactly. - Daemon folds the last TodoWrite snapshot + a max_tokens truncation off the agent event stream (every runtime, via the single emitAgentEvent choke point) and derives run.endedWithUnfinishedWork once at finish(). - The project-status projection derives `incomplete` from the already persisted events_json — no schema migration — so it survives reload/reattach instead of flashing "Completed" once the in-memory run ages out. - Contracts carry endedWithUnfinishedWork on ChatRunStatusResponse and the SSE end payload (backward compatible: absent = finished), plus a new `incomplete` ProjectDisplayStatus. - Web surfaces (Pet task center, project pill, DesignsTab) render `incomplete` as amber needs-attention, never the success color; the footer now sources the same shared predicate. Truncation uses the display-only option (spec Option A): the run stays `succeeded` but is flagged incomplete, avoiding the code===0 / stdin-close hazards of reclassifying a truncated turn to failed. Regression coverage, red on main / green here: an HTTP-boundary e2e (e2e/tests/dialog/task-completeness.test.ts) drives the interactive chat path for unfinished / stopped / truncated / finished runs; daemon + contracts + web unit tests pin the projection and the canonical predicate. Ref: https://plane.powerformer.net/open-design/projects/49832a02-3158-4faf-bf2f-d0e39c40c7e6/issues/011f6f51-7a25-4043-ae94-182e0b8b0931 Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code) |
||
|
|
9166c3bf09 |
feat(contracts): agent-ready capability contracts + design RFCs (#5398) (#5400)
* feat(contracts): agent-ready capability contracts + design RFCs (#5398) Slice 1 of the "agent-ready OD" proposal (#5398): pure-TypeScript contract shapes for the assistant's capability layer, plus the design RFCs. No behavior code. - packages/contracts/src/agent-tools/: descriptor (declare), actions (call/result), manifest (list + paged search), registry (port), task (cross-project AgentTask). Discriminated on `surface`; dual-track law is structural; action identity is a daemon-minted `invocationId`. - docs/rfc-drafts/agent-ready.md: umbrella design + slice roadmap, the MCP/WebMCP-congruent capability model, tool-search options, and "options considered -> recommendation" for the load-bearing decisions. - docs/rfc-drafts/agent-ready-cross-project.md: the maintainer's separate reviewable permissions writeup (AgentTask + permission options). Registry runtime, endpoints, and behavior are later slices per the roadmap. * fix(contracts): narrow AgentToolSearchQuery.surface to 'api' Search is the PULL path over the persistent registry, which per the RFC holds only api-surface tools — browser availability is PUSH-advertised per-session and cannot be answered by a session-less search query. Narrow the searchable surface to 'api' so the contract does not encode a browser-tool discovery path the runtime would have to special-case or break. Addresses the blocking review on #5400. * fix(contracts): address review — narrow search result to api-only; distinct browser vs api call paths Resolves the two remaining blocking findings on PR #5400: - manifest.ts: narrow `AgentToolSearchResult.tools` from the full `AgentToolDescriptor` union to `ApiToolDescriptor[]`, enforcing the PULL/api-only search invariant on the response side too (the query `surface` was already narrowed to `'api'`). A registry impl can no longer type-check while leaking `browser` descriptors into a session-less search result. - agent-ready.md: stop presenting `BrowserActionRequest` as the generic `tools/call` for every capability. The two surfaces now have distinct request paths: `api` tools are invoked through their declared `/api/*` route + `od` subcommand (no round-trip envelope), while `BrowserActionRequest`/`Result` is the browser-surface path only — the genuinely new inbound seam. - agent-ready-cross-project.md: reconcile the slice-5 cross-project routing fields (`taskId?`/`targetProjectId?`) with the distinct-paths model — added on both the browser envelope and the api-call request, not bolted onto `BrowserActionRequest` for api tools. Validation: contracts typecheck pass; pnpm guard pass. --------- Co-authored-by: LA <la@LAs-MacBook-Pro.lan> |