Commit Graph
1614 Commits
Author SHA1 Message Date
open-design-bot faa77a940d chore(card): generated card 2026-05-26 12:59:04 +00:00
open-design-bot 371cf483e3 chore(events): append issue_opened_accepted 2026-05-26 12:59:03 +00:00
open-design-bot c97d817ad8 chore(contributors): @ashleytheash opened issue #3007 2026-05-26 12:59:01 +00:00
ashleyashli a922d077bf chore(test): reset contributors before resend-key end-to-end test 2026-05-26 20:58:01 +08:00
open-design-bot 2c968dd528 chore(card): generated card 2026-05-26 12:54:38 +00:00
open-design-bot 80149127e2 chore(events): append issue_opened_accepted 2026-05-26 12:54:37 +00:00
open-design-bot 7f2abf6b67 chore(contributors): @ashleytheash opened issue #3006 2026-05-26 12:54:35 +00:00
ashleyashli 31ac82b4e7 chore(test): reset contributors to test spark welcome flow end-to-end 2026-05-26 20:53:07 +08:00
open-design-bot 05ecfc335f chore(events): append issue_opened_accepted 2026-05-26 12:49:33 +00:00
open-design-bot 248f0e320a chore(contributors): @ashleytheash opened issue #3005 2026-05-26 12:49:32 +00:00
open-design-bot 687814a5ba chore(card): generated card 2026-05-26 12:48:56 +00:00
open-design-bot b2da15bcca chore(events): append issue_opened_accepted 2026-05-26 12:48:54 +00:00
open-design-bot 5f72423261 chore(contributors): @ashleyashli opened issue #3004 2026-05-26 12:48:53 +00:00
ashleyashli 0bad8c8591 chore(test): temporarily remove ashleyashli to re-trigger spark welcome flow 2026-05-26 20:48:38 +08:00
open-design-bot 6d3786024f chore(card): generated card 2026-05-26 12:44:52 +00:00
open-design-bot b1d518a39d chore(events): append issue_opened_accepted 2026-05-26 12:44:50 +00:00
open-design-bot 23f1b6b1ec chore(contributors): @ashleyashli opened issue #3003 2026-05-26 12:44:49 +00:00
open-design-bot 243dcab605 chore(card): generated card 2026-05-26 12:41:52 +00:00
open-design-bot 89d55b64d7 chore(events): append issue_opened_accepted 2026-05-26 12:41:50 +00:00
open-design-bot 9cc7460b9f chore(contributors): @ashleytheash opened issue #3002 2026-05-26 12:41:49 +00:00
fce444bcab Consolidate chat comments preview on main (#2906)
* feat(web): queue chat sends

* feat(web): render code comment directives

* feat(web): add preview comments and manual edits

* fix(web): polish shared chrome controls

* fix(web): align queued send loading state

* feat(web): open primary project artifacts

* fix(web): keep queued sends and tests aligned

* fix(web): restore docked comment tools layout

* fix(web): align preview comment toolbar

* fix(web): place local cli beside handoff

* fix(web): move agent menu beside handoff

* fix(web): make project instructions a direct header action

* fix(web): compact handoff and toolbar labels

* fix(web): clarify handoff menu and annotation label

* fix(web): restore compact cursor handoff trigger

* fix(web): align agent menu trigger with handoff

* fix(web): add draw toolbar close action

* fix(web): move inspect editing into edit mode

* fix(web): avoid reserving comment sidebar in annotation mode

* fix(web): float preview comments panel

* fix(web): keep edit canvas full width

* fix(web): polish preview annotation tools

* fix(web): highlight active preview comments

* fix(web): open comments panel after annotation save

* fix(web): polish comment handoff controls

* fix(web): remove palette preview tool

* fix(web): simplify draw annotation toolbar

* fix(web): restore queued tasks into composer

* fix(web): restore queued send strip styling

* fix(web): hide internal comment target ids

* fix(web): align manual edit panel header

* test(web): cover visual interaction contracts

* fix(web): address PR feedback regressions

* fix(web): preserve artifact chrome state

* fix(daemon): restore project raw file routes

---------

Co-authored-by: chaoxiaoche <chaoxiaoche@chaoxiaochedeMacBook-Pro.local>
Co-authored-by: mrcfps <mrc@powerformer.com>
2026-05-26 10:31:19 +00:00
lefarcenandGitHub b5bf28060b Add sandboxed agent PR exploration (#2604) 2026-05-26 07:52:42 +00:00
ceb636aa1b feat(landing-page): plugin detail page interactive preview + share dialog (#2958)
* feat(landing-page): plugin detail page interactive preview + share dialog

The new `/plugins/<manifest-id>/` detail page that shipped in #2926
landed without the two affordances PR #2679 added to the legacy
`/skills/<slug>/` and `/templates/<slug>/` pages: a click-to-expand
iframe of the live artifact, and a share dialog with brand-keyword
copy plus four-channel jump buttons (X / LinkedIn / Reddit /
Facebook). This restores both, sourced from the bundled-plugin
manifest under `plugins/_official/<bucket>/<slug>/open-design.json`.

## Interactive preview

Three preview-type behaviours, gated on `od.preview.type`:

  - `video` (Cloudflare Stream URLs already in the manifest) —
    inline `<video controls poster=...>` with the playable MP4 as
    `<source>`. Detail-page row is unchanged from #2926; controls
    double as the open-full affordance.
  - `html` (a local `example.html` referenced by `od.preview.entry`,
    only the `examples/` bucket today) — `<details>` toggle wraps the
    poster image as the summary; clicking opens a sandboxed
    `<iframe>` that loads the entry HTML lazily, with an
    "Open in new tab ↗" pill in the frame's top-right corner so the
    artifact can be inspected at full screen.
  - `image` or no entry — static `<img>` (existing behaviour).

`copy-example-html.ts` is extended to mirror the local entry and any
`./assets/...` siblings to `out/plugins/<manifest-id>/<entry>` so the
iframe URL resolves on Cloudflare Pages instead of SPA-falling-back to
the homepage. The four examples carrying sibling-asset references
(flowai-live-dashboard-template, trading-analysis-dashboard-template,
open-design-landing, open-design-landing-deck) all render in-place.

## Share dialog

Same `<dialog data-share-dialog>` markup the legacy detail pages use,
so the global click handlers in `header-enhancer.astro`
(`data-share-open` / `data-share-copy` / `data-copy-link`) wire up
the open / copy actions automatically — no extra client bundle. Four
platform jumps (X / LinkedIn / Reddit / Facebook) plus a Copy-text /
Copy-link pair, with a single English template for now (the new
`/plugins/...` routes only generate English pages; localisation can
land alongside the i18n catch-all follow-up).

## Bundled in

- The `copy-example-html.ts` sibling-assets fix from open PR #2880.
  Without it the existing `/skills/<slug>/` iframe still 404s on
  Cloudflare Pages for after-hours-editorial-template and the four
  others; bundling it here means the same script handles both
  sources in one pass and sidesteps two PRs touching identical
  helper code.

* fix(plugins): remove dangling preview.entry from example-hyperframes

The hyperframes example folder ships a SKILL.md (it's an instruction
manual for using the HyperFrames HTML format) but no runnable
`example.html`. The manifest still claimed `preview.type: html` /
`preview.entry: ./example.html`, which made the marketing site try
to iframe a non-existent file and forced the preview pipeline into
its `Path 3` fallback card — leaving the catalog row visually
inconsistent with the eleven sibling `video-template-hyperframes-*`
plugins that have real Cloudflare-Stream poster URLs.

Drop the preview block entirely so the manifest stops promising a
demo it can't deliver. The landing-page detail row continues to
render the typographic fallback card (sourced from title /
description / mode), which is now the honest representation:
"this is an instruction skill, not a renderable template".

* fix(landing-page): address PR #2958 review feedback on plugin preview pipeline

Two blocking issues called out in code review:

1) `bundled-plugins.ts` exposed `previewEntryUrl` for every manifest
   that declared `preview.type: "html"`, even when the entry file
   wasn't shipped. Several first-party manifests fall in this state
   (example-design-brief's `./brief-preview.html`, example-x-research,
   example-pptx-html-fidelity-audit, example-hatch-pet,
   example-last30days, example-guizang-ppt, example-replit-deck,
   example-live-artifact, example-html-ppt, example-dcf-valuation).
   The detail page then rendered a click-to-expand iframe and popout
   link to a file that copy-example-html.ts had skipped, so the
   iframe URL SPA-fell-back to the homepage on Cloudflare Pages.

   `entryRelativeUrl()` now `existsSync()`-checks the resolved local
   path before returning a URL. When the file's missing the detail
   page falls through to the static thumbnail branch, exactly like
   plugins that ship no preview entry at all.

2) `copy-example-html.ts` recognised only `(src|href|poster)="./assets/..."`
   and then bulk-copied the entry's sibling `assets/` folder, so it
   missed two real ref shapes: bare-relative (`href="assets/styles.css"`,
   `src="assets/deck-stage.js"` under example-html-ppt-zhangzara-pin-and-paper)
   and cross-folder (`src="../open-design-landing/assets/hero.png"`
   under example-open-design-landing-deck).

   Replaced the heuristic with a generic walker that:
   - Parses every relative ref in the entry HTML
     (`(src|href|poster|srcset|data-src)=` plus `url(...)`), splitting
     srcset on whitespace/commas so multi-URL attrs are honoured.
   - Resolves each ref against `dirname(entrypointSrc)` for the source
     and against `dirname(iframeAbsPath)` for the destination —
     identical to how a browser resolves the same ref against the
     iframe URL. Files outside the source root or the iframe root
     are dropped.
   - Recurses into copied HTML / CSS / JS / SVG so multi-step chains
     (entry → assets/template.html → assets/fonts/foo.woff) don't
     strand intermediate files.
   - Tracks visited *destinations* rather than sources, so a single
     source that legitimately needs to land at two different out-paths
     (same-folder copy at /plugins/example-X/assets/foo.png AND a
     cross-folder copy at /plugins/open-design-landing/assets/foo.png
     for sibling decks that use `../open-design-landing/assets/foo.png`)
     gets both copies.

Verified manually:
- /plugins/example-html-ppt-zhangzara-pin-and-paper/assets/styles.css
  and assets/deck-stage.js → 200 (bare-relative)
- /plugins/open-design-landing/assets/hero.png and assets/about.png
  → 200 (cross-folder destination, no manifest-id prefix because
  iframe URL `..` collapses the prefix)
- /plugins/example-design-brief/ renders the static thumbnail only,
  no click-to-expand iframe (broken entry guard)
- /plugins/example-flowai-live-dashboard-template/assets/template.html
  → 200 (existing same-folder behaviour preserved)

Build now reports `copied 266 entry files + 65 referenced files`,
where the 65 includes both the same-folder `./assets/...` payloads
the previous heuristic captured and the bare-relative + cross-folder
shapes it didn't.

---------

Co-authored-by: Joey-nexu <joeylee12629@gmail.com>
2026-05-26 07:43:46 +00:00
Yuhao ChenandGitHub 3655230571 fix(web): align draw note enter action (#2971) 2026-05-26 07:36:34 +00:00
Marc ChanandGitHub d5659d82d4 chore(nix): streamline pnpm deps hash maintenance (#2919)
* chore(nix): streamline pnpm deps hash maintenance

Generated-By: looper 0.9.0 (runner=worker, agent=opencode)

* fix(ci): satisfy actionlint in nix hash autofix

Generated-By: looper 0.9.0 (runner=fixer, agent=opencode)

* fix(ci): allow nix hash autofix on fork PRs

Generated-By: looper 0.9.0 (runner=fixer, agent=opencode)

* fix(ci): follow up nix hash review

Generated-By: looper 0.9.0 (runner=fixer, agent=opencode)

* fix(ci): tolerate nix hash bot token failures

Generated-By: looper 0.9.0 (runner=fixer, agent=opencode)
2026-05-26 07:35:38 +00:00
Yuhao ChenandGitHub fb1e0c819f fix(plugins): reject symlinked plugin assets (#2036)
* fix(plugins): reject symlinked plugin assets

* test(plugins): cover asset directory symlink escapes

* fix(plugins): reject symlinked asset path segments
2026-05-26 07:03:22 +00:00
kamiandGitHub 9e8d01ee22 fix(daemon): fail disallowed connector tool selections (#2006)
* fix(daemon): fail disallowed connector tool selections

* fix(daemon): harden connector tool error parsing
2026-05-26 07:03:10 +00:00
b5b975769a fix Windows folder import from project modal (#2863)
Co-authored-by: Lanzhou3 <217479610+Lanzhou3@users.noreply.github.com>
2026-05-26 06:52:07 +00:00
Yuhao ChenandGitHub 1770789fa0 fix(web): hide recent project pagination chrome (#2966) 2026-05-26 06:47:48 +00:00
dbdf3b9637 feat(landing): add /share-out redirect to track X share button clicks (#2969)
* feat(landing): add /share-out redirect for X share button click tracking

Adds a Cloudflare Pages Function at /share-out/:eventId that records each
click of the "Share on X" button surfaced in the contributor card comments
on GitHub, then 302-redirects to the original twitter.com / x.com intent
URL (passed via ?to=, host-allowlisted).

Together with the existing /share/:eventId function this gives us both
sides of the X funnel without an X API key:

  - /share-out/:eventId  -> GitHub user clicked the X button   (funnel step 1)
  - /share/:eventId      -> someone on X clicked the posted tweet (funnel step 2)

Per-event KV storage is optional (SHARE_OUT_CLICK_EVENTS). When no KV is
bound the function falls back to console.log; aggregate counts are visible
in Cloudflare Pages analytics with no extra setup.

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore: retrigger CI

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: ashley li <ashleyli@ashleydeMacBook-Air-2.local>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-26 15:13:59 +08:00
netteeandGitHub 6c6ee44e07 docs(media): clarify custom providers and ComfyUI status (#479) (#2942)
Closes #479

Generated-By: looper 0.9.0 (runner=worker, agent=opencode)
2026-05-26 06:22:02 +00:00
Marc ChanandGitHub 125dcd0174 fix(ci): run fork visual reports from trusted code (#2935)
* fix: run fork visual reports from trusted code

* fix: auto-approve strict web visual capture

* fix: address visual report review feedback

Generated-By: looper 0.9.1 (runner=fixer, agent=opencode)

* fix: propagate visual report storage failures

Generated-By: looper 0.9.1 (runner=fixer, agent=opencode)

* fix: validate PR screenshots before upload

Generated-By: looper 0.9.1 (runner=fixer, agent=opencode)

* fix: validate visual PR identity before comment

* fix: harden fork visual report validation

Generated-By: looper 0.9.1 (runner=fixer, agent=opencode)

* fix: address remaining fork visual report review feedback

Generated-By: looper 0.9.1 (runner=fixer, agent=opencode)

* fix: handle stale fork visual report lookup

Generated-By: looper 0.9.1 (runner=fixer, agent=opencode)

* fix: allow stale fork visual report fallback

Generated-By: looper 0.9.1 (runner=fixer, agent=opencode)
2026-05-26 06:17:04 +00:00
f4d53486ec fix(web): warn BYOK users that API mode has no file-edit tools (#2943)
The BYOK chat path streams model text directly through the provider API and
does not run the agent-runtime scaffolding that wires up Read/Write/Edit
tools — so a model that "decides" to edit a file just emits HTML or code
back into the chat. When users switch from Local CLI (Claude Code) to
BYOK with an Anthropic key and then ask the agent to keep adjusting a
design, nothing on disk changes and the failure mode is silent.

Until the BYOK tool loop is implemented (#313 / #699 / #719), surface a
clear notice in the BYOK panel of Settings that explains the limitation
and points users at Local CLI mode for file edits.

Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code)

Co-authored-by: libertecode <libertecode@proton.me>
2026-05-26 06:16:14 +00:00
hahaloloandGitHub 1aa72b6d09 fix(docker): fix container startup crash due to missing OD_API_TOKEN (#2928)
* fix(docker): fix container startup crash due to missing OD_API_TOKEN

* fix(docker): forward OD_API_TOKEN to fix docker container boot loop

* fix(docker): enforce non-empty OD_API_TOKEN for docker-compose

* fix(deploy): automate OD_API_TOKEN generation in installer and close compose loop

* docs(readme): guide manual deployment users to configure OD_API_TOKEN

* docs(readme): align working directory paths for manual deployment instructions

* docs(readme): align working directory paths for manual deployment instructions

* docs(readme): restore git clone context for first-time users
2026-05-26 06:15:48 +00:00
bfcafc81fd feat(pack): add Windows portable zip target alongside NSIS installer (#2937)
Adds a new `--to zip` (and `--to all`) tools-pack Windows build target that
produces a portable `.zip` from the cached `win-unpacked` tree using the
bundled 7z. The zip lays files at the archive root so users can extract it
anywhere and launch `Open Design.exe` without going through the NSIS
installer, addressing the no-install download request.

Release plumbing is updated to publish the portable zip and its sha256
beside the existing installer on R2 for beta, preview, and stable channels
(default on, gated by `WINDOWS_INCLUDE_ZIP`/`WIN_INCLUDE_ZIP`). The
electron-updater `latest.yml` feed continues to point only at the
installer; the zip is a manual-download convenience and is intentionally
excluded from the in-app updater.

Closes #1121

Generated-By: looper 0.0.0-dev (runner=worker, agent=claude-code)

Co-authored-by: libertecode <libertecode@proton.me>
2026-05-26 06:14:44 +00:00
Prantik MedhiandGitHub 709fc0a497 fix: keep raw HTML source out of artifact chat prose (#2940)
* fix: keep raw HTML source out of chat prose for artifacts

* test: cover artifact chat prose rule
2026-05-26 06:14:28 +00:00
MrBeanDevandGitHub fc53feccd9 feat(web): add Aider and Trae CLI brand icons, refresh Pi mark (#2956)
AgentIcon fell back to an initial-letter pill for Aider and Trae CLI
because neither id was registered in ICON_EXT and no asset shipped. Add
the bundled brand marks so both agents render their real logo:

- aider.png — Aider's published avatar, downscaled to 96px (~2.6KB).
- trae-cli.png — Trae's app icon, downscaled to 96px (~2.3KB). Keyed on
  the `trae-cli` runtime id so the file and ICON_EXT entry match exactly.

Both vendors only publish rasterised marks, so they follow the existing
PNG-fallback path used for Devin.

Pi shipped a stale single-glyph silhouette in MONO_ICONS. Replace it
with the current dark-tile mark (white glyph on #09090b) and drop it
from MONO_ICONS — the tile has baked colors, so CSS-mask rendering with
currentColor would flatten it to a solid square. It now renders through
<img> like the other color-baked brands.

Adds AgentIcon test coverage for all three.
2026-05-26 06:14:15 +00:00
Nicholas-XiongandGitHub a6c01af2c2 fix: synchronously update URL when creating new conversation to prevent route-sync conflict (#2932)
When creating a new conversation, the route-sync effect could fight the
conversation switch if the URL was not updated synchronously. This caused
users to be unable to switch back to older conversations after creating
a new one.

The fix mirrors the approach already used in handleSelectConversation
(added in PR #1710): push the new conversation ID into the URL
synchronously so the route-sync effect sees a matching routeConversationId
before it can revert activeConversationId.

Without this synchronous URL update:
1. handleNewConversation sets activeConversationId to fresh.id
2. The URL-sync effect (L1336) eventually updates the URL
3. But the route-sync effect (L800-819) may see the stale routeConversationId
   and pull activeConversationId back to the old conversation
4. This prevents users from switching to other conversations

Fixes #2930
2026-05-26 06:13:32 +00:00
Nicholas-XiongandGitHub 0027aa2037 fix: re-activate srcDoc transport when exiting Edit mode to prevent blank preview (#2931)
When switching from Edit to Draw mode, the preview could go blank because:
1. exitManualEditModeAfterFlush() clears manualEditFrozenSource
2. previewSource switches back to livePreviewSource
3. But activateSrcDocTransport() was not triggered

This fix adds a useEffect that detects when manualEditMode transitions
from true to false, and explicitly calls activateSrcDocTransport() to
ensure the iframe content is refreshed.

Fixes #2912
2026-05-26 06:13:18 +00:00
2bab0c2add fix(landing-page): copy example.html sibling assets in post-build (#2880)
Some skill and design-template `example.html` files are thin shells that
iframe a neighbouring `./assets/<file>` (template HTML, mp4 showcases,
hero PNGs, etc.). The post-build copier only mirrored the entrypoint
file itself, so on Cloudflare Pages the asset path 404'd and the SPA
fallback served the OD homepage — users clicked "Click for live
preview" on /skills/after-hours-editorial-template/ and saw the landing
page rendered inside the iframe instead of the actual template.

Walk the entrypoint HTML for `(src|href|poster)="\./assets/..."` refs
and recursively mirror the sibling `assets/` directory only when one is
found. Most skills carry an `assets/` folder of reference PNGs the demo
never loads (open-design-landing alone is 22MB of unused mocks); a
relevance gate keeps the deploy from absorbing tens of MB per skill
that doesn't actually need them.

Six skills/templates currently match (after-hours-editorial-template,
8-bit-orbit-video-template, weread-year-in-review-video-template,
flowai-live-dashboard-template, trading-analysis-dashboard-template,
open-design-landing).

Co-authored-by: Joey-nexu <joeylee12629@gmail.com>
2026-05-26 06:12:56 +00:00
open-design-bot[bot]GitHubopen-design-bot[bot] <282769551+open-design-bot[bot]@users.noreply.github.com>
5051f318ea docs(readme): refresh contributors wall (#2868)
Co-authored-by: open-design-bot[bot] <282769551+open-design-bot[bot]@users.noreply.github.com>
2026-05-26 06:12:45 +00:00
c8af02e3a5 Clear manual edit selection after delete (#2864)
Co-authored-by: Shivam <shivam2931120@users.noreply.github.com>
2026-05-26 06:11:22 +00:00
2573318523 fix(web): live-update preview during Comment mode (#2844)
The raw HTML fetch for the preview source used no cache-bust hint, so
an agent edit while Comment mode was on returned stale bytes from the
browser HTTP cache. With identical source, srcDoc was byte-equal to the
last activated HTML, canActivateSrcDocTransport bailed via its dedupe
check, and the iframe stayed on the pre-edit frame until Comment was
toggled off (at which point url-load took over with its own ?v=mtime
cache-bust). Cache-bust on file.mtime + reloadKey + filesRefreshKey
so fresh HTML reaches the shell on every change.

A null mid-burst (chokidar emits agent rewrites as unlink+add+change)
would also blank source and snap srcDoc empty; ignore null responses
so the previous frame stays until valid HTML arrives.

Subsequent activations in the same shell would document.open + write
over the iframe. The window message listener survives, but
iframe.onLoad does not refire for document.write, so host-side re-init
(slide nav sync, scroll restore, bridge replay) is silently skipped —
the visible page can drift out of sync with the host's tracked state
(e.g. the bottom indicator reads 3 while the iframe rendered page 4 of
the freshly edited deck). Under Comment, force a fresh shell mount on
the second activation so onLoad fires and the full re-init pipeline
runs against the new HTML. Manual Edit keeps the postMessage path
(its patched HTML must not lose host-side scroll/slide state).

Co-authored-by: nicejames <nicejames@gmail.com>
2026-05-26 06:11:03 +00:00
open-design-bot[bot]GitHubopen-design-bot[bot] <282769551+open-design-bot[bot]@users.noreply.github.com>
6702060a0b Update docs/assets/github-metrics.svg (#2957)
Co-authored-by: open-design-bot[bot] <282769551+open-design-bot[bot]@users.noreply.github.com>
2026-05-26 06:02:50 +00:00
李晏丞andGitHub 53b9d779ac fix(daemon): widen HTTP keep-alive on the daemon listener (#2557)
* fix(daemon): widen HTTP keep-alive so SSE survives idle gaps

The daemon's `/api/runs/:id/events` SSE stream emits an in-band
`: keepalive` comment every 25s (`SSE_KEEPALIVE_INTERVAL_MS`), but
Node's default `server.keepAliveTimeout` is 5_000ms. When a run is
quiet for more than five seconds — e.g. the agent is still composing,
or the user briefly walks away — Node closes the underlying TCP
connection from under the SSE writer, the next 25s ping lands on a
dead socket, and the browser surfaces it as a generic
"network error" mid-stream.

This is most visible behind any keep-alive-aware middlebox (the
nginx running in the desktop bundle, the socat/docker bridges users
set up for remote access, EC2 security-group idle timers): the
default 5s window is shorter than every reasonable in-band keepalive
cadence, so the connection dies before the application gets a chance
to assert it's still alive.

Set the listener to:

- `keepAliveTimeout = 120_000` — 4.8× the in-band keepalive, plenty
  of slack for clock skew and slow flushes.
- `headersTimeout = 125_000` — must exceed `keepAliveTimeout` per the
  Node docs, otherwise a misbehaving client can stall request parsing
  indefinitely.
- `requestTimeout = 0` — disable the per-request timeout entirely;
  an SSE response intentionally runs for as long as the agent runs.

Verified by curling
`/api/runs/<id>/events` from inside the daemon container and
watching the connection stay open through three full 25s keepalive
cycles where it previously RST'd at ~5s.

* fix(daemon): address PR #2557 review — drop requestTimeout, add regression test

Three changes responding to @PerishCode's review (#2557):

1. Drop `server.requestTimeout = 0`. The reviewer is correct: that knob
   bounds how long the server waits to *receive* a complete request
   (headers + body) and is cleared the moment the request is fully
   parsed — it does not gate the duration of an SSE response. Setting
   it to 0 only removes Node 18+'s default 300s slow-loris guard, which
   is a real regression on a daemon that binds to 0.0.0.0 / Tailscale.

2. Rewrite the comment block. The previous comment claimed
   `keepAliveTimeout` "closes any idle SSE connection." Per the Node
   docs, `keepAliveTimeout` arms *after* a response finishes writing —
   it bounds the between-request idle gap on a kept-alive socket, not
   an in-flight streaming response. SSE drops mid-stream are almost
   always middlebox idle timers (nginx, socat/docker, EC2 NAT), not
   Node's own socket timeout, and this listener-side change cannot
   extend a connection past those middleboxes.

   What this PR actually fixes: routine kept-alive sockets used around
   an SSE stream (status polls, run-status fetches, the initial GET
   before the SSE upgrade) surviving normal client pauses. 120s gives
   comfortable headroom over the 25s in-band cadence so chat clients
   stop reconnect-storming between bursts.

3. Add `apps/daemon/tests/server-keepalive.test.ts` so a future
   refactor cannot silently restore the Node defaults. The test uses
   the existing `startServer({ port: 0, returnServer: true })` fixture
   (mirroring version-route.test.ts) and asserts the listener's
   `keepAliveTimeout` and `headersTimeout` invariants.

Verified:
- pnpm --filter @open-design/daemon run typecheck passes
- pnpm vitest run tests/server-keepalive.test.ts → 2 passed
2026-05-26 04:03:44 +00:00
7f8d750d8a Preserve composer drafts across refreshes (#2839)
Users can type a prompt in a conversation, reload the app, and expect that unsent text to remain tied to the same conversation. Store only the active conversation's composer draft under a project+conversation localStorage key and clear it once the draft is submitted or queued.

Constraint: The composer already remounts by activeConversationId, so persistence can stay local to ChatPane/ChatComposer without changing daemon contracts.

Rejected: Persist draft text in SQLite messages | unsent drafts are local UI state and should not appear in conversation history.

Confidence: high

Scope-risk: narrow

Directive: Keep initialDraft higher priority than stored drafts so seeded workflows are not overwritten by stale local text.

Tested: pnpm --filter @open-design/web test tests/components/ChatComposer.send-key.test.tsx tests/components/ChatComposer.queue-button.test.tsx

Tested: pnpm --filter @open-design/web typecheck

Co-authored-by: nicejames <nicejames@gmail.com>
2026-05-26 04:03:09 +00:00
MrBeanDevandGitHub 07fad5d56a feat(daemon): add Aider agent adapter (#1970)
Wire Aider (https://aider.chat) into the daemon agent registry alongside
the existing 16 CLIs. Aider is one of the most-used open-source coding
CLIs and routes through LiteLLM, so users can drive any provider they
already have a key for (OpenAI, Anthropic, DeepSeek, Gemini, OpenRouter,
local Ollama, etc.) without an extra adapter per provider.

Implementation follows the DeepSeek TUI pattern: prompt-via-argv with a
30 KB byte budget guard, plain stdout streaming, and the suppression
flags needed to keep aider runnable without a TTY (--yes-always,
--no-pretty, --no-git, --no-auto-commits, --no-suggest-shell-commands,
--no-show-model-warnings). `--message-file -` is not used because aider
treats `-` as a literal filename rather than a stdin sentinel.

Touchpoints mirror the other one-shot adapters:
- runtimes/defs/aider.ts            new RuntimeAgentDef
- runtimes/registry.ts              register in AGENT_DEFS
- runtimes/executables.ts           AIDER_BIN override
- app-config.ts                     AIDER_BIN in agent env set
- web/utils/agentLabels.ts          'Aider' display label + aliases
- tests/runtimes/agent-args.test.ts buildArgs shape coverage
- tests/runtimes/env-and-detection  bin override coverage
- tests/runtimes/helpers            shared `aider` test helper

Validated with `pnpm guard`, `pnpm typecheck`, and
`pnpm --filter @open-design/daemon test tests/runtimes` (123 passing).
End-to-end probe against the live aider binary against DeepSeek via the
exact argv the adapter produces returned the expected output.
2026-05-26 03:45:47 +00:00
Marc ChanandGitHub 3d358fc877 fix(web): keep Vercel static builds writing to out (#2946)
* fix(web): keep Vercel static builds writing to out (#1628)

Generated-By: looper 0.9.1 (runner=worker, agent=opencode)

* fix(web): preserve explicit dist dir overrides

Generated-By: looper 0.9.1 (runner=fixer, agent=opencode)

* fix(web): preserve explicit dist dir overrides

Generated-By: looper 0.9.1 (runner=fixer, agent=opencode)
2026-05-26 03:37:27 +00:00
YOMXXXandGitHub 2bd83b6e23 feat(daemon): structured diagnostics for agent connection test results (#2248 PR 1/N) (#2419)
* feat(daemon): attach structured diagnostics to agent connection test results

Local agent connection-test failures currently flatten everything into
a single free-form `detail` string (e.g. "exit 1"). Settings UI and CLI
consumers can't tell what phase failed, which binary the daemon picked,
or what the child's exit metadata looked like — they have to scrape the
human-readable text.

Add an optional `diagnostics` block on the connection-test response so
callers can read structured fields instead. The existing `kind` and
`detail` strings are kept bit-for-bit identical, so older UIs keep
rendering unchanged.

- packages/contracts: add `ConnectionTestPhase`
  (binary_resolution / version_probe / model_list / spawn /
  connection_smoke_test / output_parse) and a `ConnectionTestDiagnostics`
  interface with optional `binaryPath`, `binaryVersion`, `exitCode`,
  `signal`, `stdoutTail`, `stderrTail`; extend
  `ConnectionTestResponse.diagnostics?` to carry it.
- apps/daemon/connectionTest.ts: thread a `phase` tracker through
  testAgentConnectionInternal, flip it at the meaningful boundaries
  (binary_resolution → spawn → connection_smoke_test / output_parse),
  and stamp diagnostics into every result return point — the four
  result helpers plus both early returns. Tail data already buffered
  by `createAgentSink` is reused; nothing new is captured.
- tests: three regressions per #2248 — success path attaches
  phase='connection_smoke_test' + exitCode 0, exit-failed path
  attaches phase='spawn' + the failing exitCode + the stderr tail,
  and a missing-CLI path attaches an early-phase diagnostics block.

This is PR 1 of the #2248 plan (contracts + minimum daemon fill);
follow-ups will introduce a normalized failure classifier
(binary_not_found, unsupported_version, auth_failed, quota_exceeded,
network_failed, unsupported_flags, no_text_output, output_parse_failed,
spawn_failed), candidate-alternative reporting via
inspectAgentExecutableResolution, and the Settings "View details"
disclosure.

Refs #2248.

* fix(connectionTest): honor diagnostics contract on all local return paths

Two follow-ups from review of #2419:

- packages/contracts/src/api/connectionTest.ts advertises diagnostics
  as 'Always set on local agent test responses', but three local
  returns still bypassed buildDiagnostics(): the buildArgs failure
  around 1295, the preflight probeAgentAuthStatus().status === 'missing'
  branch around 1317, and the outer catch around 1566. Thread
  buildDiagnostics() through all three; phase is still 'binary_resolution'
  at the first two and whatever the runtime advanced to at the catch.
- resultFromAgentText() hard-coded exitCode: 0 even though
  resultFromChildExit() routes ACP clean-SIGTERM completion through
  this success helper (winner.code === null, winner.signal ===
  'SIGTERM' with acpCleanCompletion). Add an optional exit argument
  threaded from both call sites so the diagnostics reflect the actual
  child code/signal pair instead of a synthesized 0 that masks the
  SIGTERM teardown. Only synthesize 0 when no exit context is
  available (theoretical text-without-exit path).

Tests:
- regression locking the diagnostics contract for the preflight auth
  path on Cursor Agent (phase: binary_resolution, binaryPath set)

* docs(contracts): widen diagnostics contract to match early-failure paths

Reviewer flagged that the JSDoc-style comment on
ConnectionTestResponse.diagnostics still said 'Populated only when the
test actually spawned an agent CLI', but the previous follow-up made
the daemon stamp diagnostics on three pre-spawn local-agent failures
too: the unknown-agent and unresolved-binary branches around
connectionTest.ts:1123-1148 and the preflight auth return around
1338-1353. Reword the contract so Settings/CLI consumers do not
incorrectly special-case those early local failures as
diagnostics === undefined.

* fix(connectionTest): keep contracts browser-safe and fold probe output into preflight diagnostics

Two follow-ups from review of #2419:

- ConnectionTestDiagnostics.signal was typed as
  `NodeJS.Signals | string | null`, which made the generated .d.ts of
  the shared @open-design/contracts surface depend on ambient Node
  types. Downstream consumers reading a plain HTTP response shape
  should not need @types/node. Narrow to `string | null` (NodeJS.Signals
  literals are strings, so the daemon write site is unchanged) and
  document the boundary in the field comment.
- The Cursor-style preflight auth path stamped diagnostics built from
  the smoke-test sink, which is always empty at that point because the
  smoke spawn never happened. As a result the diagnostics block
  silently dropped `cursor-agent status`'s own stderr/stdout/exit
  context — the only structured failure information available on that
  path. Thread the probe output back out of probeAgentAuthStatus()
  via new optional stdoutTail/stderrTail/exitCode/signal fields, then
  merge them into the diagnostics overrides in connectionTest.ts so
  Settings/CLI consumers can render the auth-failure context instead
  of just the guidance string.

Tests:
- extended the Cursor preflight regression to assert that diagnostics
  carries the probe's stderr ("Not logged in") and exit code (1).
2026-05-26 03:17:05 +00:00
7bc11b398d chore(deps): upgrade express 4 -> 5 in daemon (#2311)
* chore(deps): upgrade express 4.22.1 -> 5.2.1 and @types/express

Breaking changes addressed:
- Renamed all bare wildcard route segments from * to *splat across
  src/server.ts, src/static-resource-routes.ts, src/project-routes.ts,
  src/import-export-routes.ts, and all three test stubs that define
  app.get/options/delete routes using /raw/* or /raw/* patterns
- Updated wildcard param access from (req.params as any)[0] / req.params[0]
  to Array.isArray(req.params.splat) ? req.params.splat.join('/') : String(...)
  to handle the Express 5 / path-to-regexp v8 change where wildcard params
  are now string[] instead of string
- Updated app.get('*') SPA fallback to app.get('/*splat') in server.ts
- Annotated five connector route handlers with Request<{ connectorId: string }>
  so the typed param resolves as string, not string | string[], fixing the
  10 TS2345 / TS2322 errors that surfaced when @types/express moved to 5.0.6
- Fixed two app.listen() beforeAll callbacks in origin-validation.test.ts to
  accept and propagate the optional Error argument Express 5 now passes to
  the listen callback, resolving TS2769 overload mismatch

* chore(nix): refresh daemonHash for rebased lockfile

* fix(daemon): await res.sendFile() in async route handlers for Express 5 compatibility

Express 5 res.sendFile() returns a Promise. Without await, async route
handlers return before the response is sent, causing Express to call
next() and fall through to a 404. Add await to all res.sendFile() calls
in async handlers in static-resource-routes.ts and server.ts.

* fix(daemon): use readFile+send for spritesheet route instead of sendFile

Express 5 res.sendFile() returns undefined (not a Promise). ENOENT errors
call next() asynchronously after the route handler's try/catch has returned,
causing unhandled 404 responses. Replacing with fs.promises.readFile + res.send
keeps the error path fully within the handler's try/catch.

---------

Co-authored-by: Patrick A <259201958+eefynet@users.noreply.github.com>
2026-05-26 03:16:48 +00:00