Commit Graph
213 Commits
Author SHA1 Message Date
shangxinyu1 103c4ab688 Require absolute CODEX_BIN overrides 2026-05-07 14:48:28 +08:00
shangxinyu1 e2e92c9a56 Fix packaged config and connection test overrides 2026-05-07 14:41:54 +08:00
shangxinyu1 da521ba9e6 Seed local packaged app config from workspace 2026-05-07 14:21:35 +08:00
shangxinyu1 a173a5e8e3 Replace save-as-template prompts with an in-app dialog 2026-05-07 13:44:19 +08:00
shangxinyu1 7d95cbcbe9 Support overriding the Codex executable path 2026-05-07 13:44:19 +08:00
SidandGitHub 0b2b456694 fix(daemon): deliver Copilot prompt via stdin
Avoid Windows ENAMETOOLONG by keeping Copilot prompts out of argv and sending them through stdin instead.\n\nFixes #705
2026-05-07 11:52:30 +08:00
f45055a8bf docs+web: surface @nexudotio X account in README + entry sidebar (#696)
* docs(readme): add @nexudotio X link + Stay in the loop section

- Add X/Twitter follow badge in the header next to Discord, so visitors
  can subscribe to release notes and milestone threads in one click.
- Add a short "Stay in the loop" section above "Star us" pointing to
  @nexudotio on X. Discord is for chat, X is for the public milestones
  (releases, new skills, new design systems).

No other changes.

* feat(web): add Follow @nexudotio pill to entry sidebar foot

- Surface the official X account next to the Settings/Pet/Language pills
  so users can subscribe to release notes from inside the app, not only
  the README.
- Uses the existing .foot-pill style (already supports <a>, has
  text-decoration: none) and the existing 'external-link' Icon — no new
  CSS, no new icons, no i18n key required (single short English label).
- Opens in a new tab with rel="noreferrer noopener".

Pairs with the README badge added in this same PR.

---------

Co-authored-by: Tuola-waj <ge@nexu.io>
2026-05-07 11:34:59 +08:00
monshunterandGitHub e6e5928be1 feat(web): add connection tests for execution settings (#507)
* feat(settings): add connection test for providers and CLI agents

Adds a "Test" action in the Settings dialog that verifies the configured
provider (Anthropic/OpenAI/Azure/Google) or CLI agent without sending a
real chat. Backed by a new daemon endpoint and shared contracts, with
categorized inline statuses and i18n strings across all supported locales.

* fix(settings): address connection test review feedback

* fix(daemon): pass empty MCP servers for connection probes

* fix(connection-test): address review blockers

* fix(daemon): fail json stream runs on structured errors

* fix(contracts): build connection test subpath export

* Use draft CLI env in agent connection tests

* fix(i18n): add fallback ids for new curated content
2026-05-07 11:25:37 +08:00
832ea7d864 fix: batch of small bug fixes (#283, #275, #390) (#530)
* fix(web): add hover tooltips to Design Files action buttons (#283)

The batch-download, select-all, and clear-selection buttons in
DesignFilesPanel had no title attribute, so users hovering them saw no
tooltip. The other action buttons (refresh, new sketch, paste, upload)
already had titles. Added titles to the three missing ones using the
existing translation keys, so hover behavior is consistent across the
panel.

Closes #283.

* docs: point pi-ai links to pi-mono packages (#275)

The pi project moved from a standalone repo to the pi-mono monorepo.
The old URL https://github.com/mariozechner/pi-ai now 404s. Replaced
both shapes of reference:

- The reference-style [piai]: definition now points at
  https://github.com/badlogic/pi-mono/tree/main/packages/ai
  (the multi-provider LLM API package).
- Inline links whose visible text is the CLI tool 'pi' or 'Pi' now
  point at
  https://github.com/badlogic/pi-mono/tree/main/packages/coding-agent
  (the interactive coding-agent CLI), so a reader clicking 'pi' in
  the daemon-discovery section lands on the actual binary's docs.

Affected: README.md and 10 translated READMEs, plus docs/spec.md,
docs/architecture.md, docs/references.md, docs/roadmap.md.

Closes #275.

* fix(daemon): expand $HOME / ${HOME} in OD_DATA_DIR (#390)

Some launchers (systemd unit files, NixOS modules, certain Docker
entrypoints) pass OD_DATA_DIR with a literal '$HOME' or '${HOME}'
because no shell ever expands them. resolveDataDir previously only
handled '~/' shorthand, so '$HOME/.open-design' fell through to
path.resolve(PROJECT_ROOT, '$HOME/.open-design') and produced paths
like /opt/open-design/$HOME/.open-design.

resolveDataDir now expands '~', '~/...', '$HOME', '$HOME/...',
'${HOME}', and '${HOME}/...' to os.homedir() before the absolute /
relative branch runs. Rebuilds via path.join so the platform separator
is correct on Windows even when the input used forward slashes.

Tests: 7 unit tests cover empty/undefined, '~', '~/...', '$HOME',
'$HOME/...', '${HOME}/...', absolute paths, and relative paths.

Closes #390.

* fix(daemon): accept backslash separators + hermetic resolve-data-dir tests

Round 1 review feedback on PR #530.

The previous regex only matched forward-slash separators, so a Windows
launcher passing OD_DATA_DIR=$HOME\.open-design or ${HOME}\.open-design
fell through to path.resolve(projectRoot, ...) and produced a directory
named $HOME or ~ under projectRoot. The regex now accepts both forward
and back slashes for the home-prefix separator.

The previous tests called the real resolveDataDir against literal
~/od-test, $HOME/od-test, etc., which created and write-checked
directories under the developer's or CI runner's actual home. The tests
now stub os.homedir() with vi.spyOn to a per-test mkdtemp directory and
remove it in afterEach, so no test ever writes outside its own sandbox.
Added explicit fixtures for the Windows backslash forms ($HOME\od-test,
${HOME}\od-test, ~\od-test) so launcher coverage stays cross-platform.

12/12 resolve-data-dir tests pass, daemon typecheck clean.

* fix(docs,daemon): apply pi-mono links to README.es and await test cleanup

Round 2 review feedback on PR #530.

README.es.md was added in upstream #552 after my pi-mono link sweep
landed, so the daemon-discovery paragraph (line 222), the [piai]
reference (line 684), and the Pi table row (line 709) still pointed
at the broken https://github.com/mariozechner/pi-ai URL. Applied the
same replacements: the [piai] ref now points at packages/ai, and the
inline Pi link now points at packages/coding-agent. Spanish readers
get the same coverage as the other 11 locales.

The absolute-path test in tests/resolve-data-dir.test.ts dropped its
fixture via void rm(abs, ...), so a failed async removal could leak
rdd-abs-* directories from the suite. The test is now async and
awaits the rm in the finally block, matching the awaited cleanup in
afterEach. 12/12 resolve-data-dir tests still pass, daemon typecheck
clean.

* fix(daemon): share $HOME expander between OD_DATA_DIR and OD_MEDIA_CONFIG_DIR

Round 3 review feedback on PR #530.

resolveDataDir (server.ts) now expands $HOME / ${HOME} / ~, but
media-config.ts had its own resolveOverrideDir that only handled ~/.
Because configFile() falls back to OD_DATA_DIR when OD_MEDIA_CONFIG_DIR
is unset, setting OD_DATA_DIR=$HOME/.open-design split state: SQLite,
projects, and artifacts went to the expanded path while
media-config.json stayed under <projectRoot>/$HOME/.open-design.
Stored provider keys then appeared missing on the next read.

Extracted the home-prefix expansion into apps/daemon/src/home-expansion.ts
so resolveDataDir and resolveOverrideDir share one resolver. Both now
recognize ~ / $HOME / ${HOME} (bare tokens) and ~/, ~\, $HOME/, $HOME\,
${HOME}/, ${HOME}\ (prefix forms with either separator).

Three new media-config routing tests cover the OD_DATA_DIR fallback for
$HOME/..., ${HOME}/..., and the OD_MEDIA_CONFIG_DIR explicit-override
$HOME/... case so the co-location guarantee is locked down by tests.

Daemon typecheck clean. Tests pass on Linux CI; the existing pattern in
the file uses process.env.HOME which os.homedir() reads on POSIX.
Resolve-data-dir tests stay hermetic via vi.spyOn.

* docs(daemon): media-config comments reflect full $HOME / ${HOME} expansion

Round 3 review feedback on PR #530 (lefarcen, P3 non-blocking).

The file-header and resolveOverrideDir() function comment said only
~/ expands. Updated both to mention the shared expandHomePrefix()
helper and the full set of forms it handles (~, $HOME, ${HOME} with
either separator), so a future reader does not need to chase the
implementation to understand what env values are accepted.

* test(daemon): stub os.homedir() in media-config routing tests

Round 4 review feedback on PR #530.

The new $HOME / ${HOME} routing tests relied on process.env.HOME being
read by os.homedir(), which works on POSIX but is unreliable on Windows
(Node prefers USERPROFILE / profile APIs there). The tests would expand
to the real user home while fixtures were written under the per-test
homeDir, causing platform-specific failures in the same area this PR
is making cross-platform.

The inner describe block now stubs os.homedir() via vi.spyOn to return
the per-test homeDir, matching the pattern in resolve-data-dir.test.ts.
Restored in afterEach. The four $HOME-form routing tests now pass on
both POSIX and Windows.

Daemon typecheck clean. The two OAuth fallback test failures unrelated
to this change (real ChatGPT/Codex tokens in the local env) remain
out-of-scope.

* fix(i18n): drop duplicate uk.ts promptTemplates keys after rebase

Upstream #674 added the same Ukrainian translations my earlier
commit added. The rebase landed both copies; tsc rejects duplicate
property names. Drop my copies so #674 (which is now upstream) is
the single source for these keys.

---------

Co-authored-by: Nagendhra <nagendhra405@gmail.com>
2026-05-07 11:17:02 +08:00
Gabriel VazandGitHub 6abd7676c8 fix(daemon): unbreak Claude Design ZIP import on Node 24 and raise file ceiling (#591)
* fix(daemon): unbreak Claude Design ZIP import on Node 24 and raise file ceiling

- Skip inflateRawSync when an entry's central-directory uncompressedSize is 0;
  Node 24 rejects { maxOutputLength: 0 } with ERR_OUT_OF_RANGE, which silently
  killed the entire import for any zip containing an empty file or a streaming
  entry whose size is only present in the data descriptor.
- Raise MAX_FILES from 500 to 5000. Real-world design-system exports commonly
  exceed 500 files; MAX_TOTAL_BYTES (100 MB) and MAX_FILE_BYTES (25 MB) already
  cap pathological inputs.
- Add regression tests for both: zero-byte deflate entry, central directory
  advertising uncompressedSize=0, and a 600-file zip.

Refs #590

* fix(daemon): preserve real payload when central uncompressedSize is 0

Reviewers correctly flagged that the previous Buffer.alloc(0) fast-path
trusted the central directory's uncompressedSize, which is unreliable for
streaming/data-descriptor zips: an entry whose central record reports 0
can still carry real deflated bytes. The earlier fix wrote empty files to
disk in that case, and the post-condition body.length !== uncompressedSize
check still passed because both sides were 0.

- Inflate streaming entries with maxOutputLength = MAX_FILE_BYTES when the
  central directory advertises 0, so legitimate non-empty payloads decode
  fully instead of being silently truncated.
- Move size enforcement post-decode: per-file and total-byte budgets are now
  computed from the actual decoded length, and the strict equality check is
  skipped when central was 0 (i.e., genuinely unknown).
- Keep the empty-deflate degenerate case (compressed.length === 0) safe by
  short-circuiting before zlib instead of relying on uncompressedSize.

Tests:
- New: streaming-zip case with central uncompressedSize=0 + non-empty body
  asserts the on-disk file matches the original bytes (would have been
  silently truncated under the previous fix).
- New: oversized streaming entry (> MAX_FILE_BYTES) is still rejected even
  though the central directory under-reports.
- The original 0-byte and >500-file regressions remain covered.
2026-05-07 10:31:30 +08:00
Marc ChanandGitHub 5a29fed7d3 fix(web): align design system default test fixture (#708) 2026-05-07 09:07:28 +08:00
SidandGitHub 4c82e48e4f fix web design system selection persistence (#621) 2026-05-07 02:27:00 +08:00
SidandGitHub 1bd1f3a661 fix(daemon): surface OpenCode error frames + treat empty-output runs as failed (#700)
* fix(daemon): surface OpenCode error frames + treat empty-output runs as failed

Closes #691. OpenCode runs would silently complete in ~3 seconds without
producing any visible chat output and still be rendered as a successful
turn — three independent bugs along the structured-stream path conspired
to produce this silent-failure shape.

## Bug 1 — `apps/daemon/src/json-event-stream.ts:85-91`

OpenCode emits structured error frames on stdout (e.g. provider auth
failures, network errors, schema mismatches) and still exits 0. The
parser was downgrading these to `{type: 'raw', line: ...}`, which the
chat UI does not render as an assistant message. The error string was
discarded as "no-op output."

Fix: emit a proper `{type: 'error', message, raw}` event matching the
qoder-stream contract that the daemon's existing error-handling path
already recognises.

## Bug 2 — `apps/daemon/src/server.ts:4199-4205`

Even after Bug 1 was fixed, the json-event-stream branch wired the
parser to a bare `(ev) => send('agent', ev)` lambda — bypassing the
`sendAgentEvent` wrapper that interprets `type:'error'` events and
sets the `agentStreamError` flag the close handler reads to flip the
run to `failed`. So an emitted `error` event would just be forwarded
as a no-op `agent` SSE event with no lifecycle effect.

Fix: route json-event-stream through `sendAgentEvent`, mirroring the
qoder-stream-json wiring at line 4175.

## Bug 3 — `apps/daemon/src/server.ts:4220-4234`

Even after Bugs 1 and 2 are fixed, there's still a class of runs where
OpenCode never emits any error frame, never emits any substantive
event, and exits 0. Pre-fix this was marked `succeeded` and the user
saw a blank chat with no diagnostic.

Fix: track `agentProducedOutput` inside `sendAgentEvent` (set on
`text_delta`, `thinking_delta`, `tool_use`, `tool_result`, `artifact`
— deliberately NOT on `status` / `usage`, since a model can emit
token-usage numbers for an empty completion). When the close handler
sees `code === 0 && trackingSubstantiveOutput && !agentProducedOutput`
the run is marked `failed` with an explicit AGENT_EXECUTION_FAILED
SSE error so the chat shows a clear reason instead of a silent
empty turn.

The check is gated by `trackingSubstantiveOutput` so it only fires
on streams that actually contribute to the output flag (currently
qoder-stream-json and json-event-stream). ACP sessions and plain
stdout streams keep their existing success/failure determination.

## Tests

- 3 new unit tests in `apps/daemon/tests/json-event-stream.test.ts`
  pin the OpenCode error event shape: full repro
  (`error.data.message`), `error.name` fallback, and the
  generic-fallback shape when `error` is empty.
- All 60 daemon test files (851 tests) pass on `pnpm --filter
  @open-design/daemon test`. All 42 web test files (309 tests) pass
  on `pnpm --filter @open-design/web test`.
- Full repo `pnpm typecheck` clean.

## Live verification

Verified end-to-end via a stub `opencode` binary that mimics each of
the failure shapes against `pnpm tools-dev run web`:

1. Stub emits `{"type":"error",...}` then `exit 0` — run now ends as
   `failed` with the OpenCode error message surfaced as an SSE
   `error` event. Pre-fix this was `succeeded` with an empty chat.
2. Stub emits nothing then `exit 0` — run now ends as `failed` with
   "Agent completed without producing any output…" diagnostic.
   Pre-fix this was `succeeded` with an empty chat.
3. Stub emits a normal `step_start` / `text` / `step_finish` sequence
   then `exit 0` — run still succeeds. (Regression check.)

## Out of scope (mentioned for the next person)

- `claude-stream-json` and `copilot-stream-json` still wire to a bare
  `(ev) => send('agent', ev)` and don't currently parse `type:'error'`
  frames. If their CLIs ever start emitting structured error events
  the same pattern (route through `sendAgentEvent` + emit proper
  `type:'error'`) applies. Not in scope here because we have no
  evidence those CLIs do this today, and changing the wiring without
  a confirmed failure mode risks regressing currently-working flows.
- ACP sessions (`pi-rpc`, `acp-json-rpc`) own their own success /
  failure determination via `acpSession?.hasFatalError()` and the
  empty-output guard explicitly skips them via
  `trackingSubstantiveOutput`.
- Plain stdout streams have no event-level tracking, so the empty-
  output guard skips them too. Diagnosing a no-output plain-stream
  agent is a separate problem that needs different signals.

* chore: retrigger CI on top of green main (post #697 i18n backfill)
2026-05-07 02:00:19 +08:00
Jheison Martinez BolivarandGitHub 4368b8f163 feat(linux): add headless mode for install/start/stop operations (#686)
* feat(linux): add headless mode for install/start/stop operations

* docs(linux): document headless mode commands and usage

* refactor(linux-headless): write web-root.json instead of polling IPC for URL

* fix(linux-headless): fail start when web identity never appears instead of returning success

* docs(linux-headless): add use-case context and clarify launcher path dependency

* fix(linux-headless): ensure launcher, identity and shutdown align with tools-pack

- Bake OD_DATA_DIR into launcher so manual runs use the same paths as tools-pack
- Validate web-root.json fields before accepting to reject stale identity
- Remove web-root.json on successful stop
- Add IPC server for graceful STATUS/SHUTDOWN handling

* fix(linux-headless): create IPC server before writing web-root.json
2026-05-07 01:52:03 +08:00
a5aaeb7905 Fix: Remove element selector tooltip in Tweaks mode (#697)
* Fix: Remove element selector tooltip in Tweaks mode

* fix: cover localized content fallbacks

* fix: keep comment target labels testable

---------

Co-authored-by: bojiehuang <bojiehuang@bojiehuangdeMacBook-Pro.local>
Co-authored-by: mrcfps <mrc@powerformer.com>
2026-05-07 00:02:38 +08:00
576dfed9e1 feat: add accent color control and launcher for Open Design (#683)
* feat: add accent color control and launcher for Open Design

* fix: remove launcher binary from PR

* test: cover accent appearance edge cases

---------

Co-authored-by: ferasbusiness666 <ferasbusiness666@users.noreply.github.com>
2026-05-06 23:14:21 +08:00
42e4d080bd feat(skills): add social-media-dashboard skill + Totality Festival design system (#678)
* feat(skills): add social-media-dashboard skill + Totality Festival design system

- New skill 'social-media-dashboard': single-screen creator analytics
  dashboard with platform switcher (X / GitHub / LinkedIn / YouTube /
  Instagram), KPI row, growth chart with annotations, top-post / top-PR
  preview, trending topics, and top comments. Includes a self-contained
  example.html (Totality Festival styled, X + GitHub tabs, live KPI
  ticker, GitHub contributors grid, world-map audience geography).
- New design system 'totality-festival': cosmic-premium dark glassmorphic
  system with amber corona highlights and cyan atmospheric accents.
  Mirrors Google Labs' design.md spec example so skills can be
  validated against an upstream reference.
- Fix swatches parser in apps/daemon/src/design-systems.ts so it
  recognises the '- **Name:**' bold-with-inner-colon form used by
  several existing systems (ant, totality-festival, ...). Previously
  only the '**Name** (`#hex`)' form was matched, which left their
  picker thumbnails empty.

* feat(skills): polish social-media-dashboard example + add PR preview

- Top Post media block: replace empty gold frame with an inline SVG
  thumbnail (radial glow + ascending data curve + amber/cyan pulse dots
  + play icon + 'LIVE · 0:22' caption). Visually echoes the live-artifact
  story the post copy is selling.
- Hoist the brand-mark linearGradient into a global SVG defs block at
  the top of <body> so all three avatars (header, user, top-post) can
  reference url(#brandRing) and render the teal arrow consistently.
  Previously only the header SVG carried the gradient definition, so
  the user and post avatars rendered as empty rings under headless
  capture.
- Add hero.png preview to .preview/ for the PR description.

---------

Co-authored-by: Tuola Ge <gexingli@refly.ai>
2026-05-06 21:50:23 +08:00
98e40c1cfc feat(daemon): export project transcript to disk for downstream consumption (prereq for #450) (#493)
* feat(daemon): export project transcript to disk for downstream consumption

Adds exportProjectTranscript(db, projectsRoot, projectId, options?) — a
pure function that walks SQLite-backed conversation history and writes a
structured, lossless JSONL transcript to <projectDir>/.transcript.jsonl.

This is the input primitive that #450's "Finalize design package"
synthesis step needs. Landed ahead of the synthesis endpoint as a small,
reviewable, well-tested unit — no HTTP route, no LLM call, no web UI in
this diff. PR 2 will wire POST /api/projects/:id/finalize on top of it.

Format: JSONL with header line + per-conversation marker lines +
per-message lines. Compact encoding saves ~20–30% on synth-call tokens
vs pretty-printed JSON. schemaVersion field reserved on the header for
incompatible changes later.

Coalescing: events_json carries streaming text_delta / thinking_delta
chunks plus tool_use / tool_result / thinking_start markers and
telemetry. The export collapses runs of same-type deltas into terminal
text / thinking blocks via arrival-order with type-change flush,
preserving any interleaving with tool blocks. Telemetry (status, usage,
raw) is dropped. thinking_start is treated as an explicit flush trigger
so multiple thinking blocks in one message survive intact.

Content fallback: user-typed messages persist as plain text in
messages.content with events_json = NULL because user input does not
flow through the streaming pipeline. When event-derived blocks come
back empty, fall back to a single text block from content so user
prompts are not silently dropped.

Atomic write: tmp filename includes pid + crypto-random suffix so
concurrent exports for the same project cannot collide. fsync before
rename so a crash between rename and power loss cannot lose bytes.

Hidden file: leading dot keeps .transcript.jsonl out of listFiles
(projects.ts:54), the archive zip, and the gallery.

Tests: 14 unit cases — empty project, text/streaming coalescing, tool
ordering, telemetry filtering, type-change flush, text↔thinking↔text
interleaving, thinking_start flush, multi-conversation chronological
order, atomic-write hygiene, content fallback (both directions),
malformed events_json, and unsafe project id rejection.

Refs: nexu-io/open-design#450

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(daemon): address PR #493 review feedback for transcript export

Addresses every blocker, P2, and P3 raised on
https://github.com/nexu-io/open-design/pull/493:

- Blocker (event-shape mismatch): coalescer now switches on the
  PersistedAgentEvent kind discriminator (text/thinking/tool_use/
  tool_result), reading the shared `text` field for content kinds,
  matching what apps/web/src/providers/daemon.ts:347-394 actually
  persists into messages.events_json. Empirical confirmation: live
  SQLite contains zero `type` keys.
- Removed @ts-nocheck from the source file; added inline types for
  Db (Database.Database), ConversationRow, MessageRow, AttachmentRef,
  CommentAttachmentRef, and Block. Tests retain @ts-nocheck per
  codebase convention. Note: db.ts still uses @ts-nocheck, so the
  new types catch drift inside transcript-export.ts itself, not at
  the SQLite-helper boundary.
- parseEvents now distinguishes null / malformed / not_array / ok
  cases; non-null-but-unparseable rows emit a console.warn with
  project+message id before falling back to content.
- Switched temp-write from writeSync (which can return short) to
  writeFileSync({flag:'wx'}); explicit fsync via reopen before
  rename, per reviewer concern about partial-write durability.
- Added per-project lockfile (.transcript.lock) acquired with
  openSync(..., 'wx') and released in finally; concurrent exports
  throw the new TranscriptExportLockedError. Stale-lock recovery
  is documented as a known limitation in the file header.
- Header gains attachmentCount, commentAttachmentCount, and explicit
  attachmentsInlined: false. Per-message lines gain attachments /
  commentAttachments references (paths only, not bytes; synthesis
  reads files from disk by path). schemaVersion bumped 1 -> 2 so
  the change is explicit; v1 was never consumed.
- mkdirSync(dir, { recursive: true }) at entry covers projects
  with DB rows but no on-disk directory yet (codex bot finding).
- Refactored node:fs imports from named to default
  (import fs from 'node:fs') so vitest spies in tests #15-#17 can
  redefine properties on the underlying CJS exports object. ESM
  namespace imports of node:fs produce a frozen Module Namespace
  Object that vi.spyOn cannot mutate; default-import returns the
  CJS module.exports which is mutable.
- Inline PersistedAgentEvent union: the daemon tsconfig does not
  resolve the `@open-design/contracts/api/chat` subpath export, so
  the union is restated in the source. Schema-mismatch tests cover
  the case where the contract would diverge.
- Test count 14 -> 24: failure injection for writeFileSync /
  fsyncSync / renameSync, existing-file replacement, lockfile
  contention (lockfile-pre-create design — synchronous API can't
  race via Promise.allSettled), parse-warning cases (malformed +
  not-array), attachments header + per-message coverage, missing-
  project-dir case.

Refs nexu-io/open-design#450 (does not close).

* fix(daemon): preserve thinking-segment boundaries on status thinking-start

Codex flagged this as a P2 on PR #493 a39d430:
https://github.com/nexu-io/open-design/pull/493#discussion_r3188524878

The web translator emits `{ kind: 'status', label: 'thinking' }` at every
thinking_start (apps/web/src/providers/daemon.ts:367-369). The previous
default branch dropped all status events without flushing the active
accumulator, so two thinking segments separated only by that marker
merged into one block — losing the original boundary and making the
transcript non-lossless for downstream synthesis.

coalesceBlocks now matches `status` explicitly: when `label === 'thinking'`
the prior accumulator flushes; other status labels and usage / raw drop
without flushing as before. Behavior fix within schemaVersion 2; no
shape change.

Test #25 verifies the boundary preservation:
  [thinking 'a', thinking 'b', status 'thinking', thinking 'c', thinking 'd']
  → blocks: [thinking 'ab', thinking 'cd'] (two blocks, not one)

Existing test #5 still passes because it uses status with label 'streaming'
which remains pure telemetry and does not flush.

Suite: 506 -> 507 daemon tests, all green.

Refs nexu-io/open-design#493

---------

Co-authored-by: DevForgeAI CI/CD Engineer <devforge-ai@development.ai>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 21:48:39 +08:00
shangxinyu1andGitHub 8301bcd46e test: add desktop settings and project flow e2e coverage (#306)
* test: add desktop settings regression coverage

* test: stabilize desktop smoke interactions on latest main

* fixer: address PR #306 follow-up items

Generated-By: looper 0.2.7 (runner=fixer, agent=codex)

* test: expand ui e2e automation suite

* fix: add missing Ukrainian prompt template labels

* chore: align desktop e2e helpers with layout guard

* chore: move settings protocol e2e into ui suite

* fix: preserve api provider settings across protocol switches

* fix: avoid leaking api keys across protocol drafts

* test: fold desktop smoke coverage into mac spec

* fix: dedupe Ukrainian prompt template labels
2026-05-06 21:48:12 +08:00
EliandGitHub 3298cb3756 feat(skills): add 5 Orbit briefing templates (#671)
* feat(skills): add 5 Orbit briefing templates

Introduces a new "orbit" scenario family in the Examples gallery for
morning-briefing surfaces. Each template lives at the top of "我的设计"
and aggregates yesterday's connector activity into a single page.

- orbit-general: adaptive bento dashboard that fans across 12-16
  connectors, where each module picks its own UI form by data type
  (list / avatar stack / status ring / heatmap / file grid / alert
  card / kanban / etc.)
- orbit-github: GitHub-flavored single-connector digest mirroring the
  Notifications + PR-diff visual language
- orbit-gmail: Gmail-flavored digest rendered as a Daily Digest email
  inside the three-pane inbox
- orbit-linear: Linear-flavored digest in the dark Inbox + cycle-
  progress layout
- orbit-notion: Notion-flavored digest authored as a native Notion
  page (callout / toggle / database table)

The new scenario value 'orbit' surfaces as a filter pill in
ExamplesTab automatically; no UI code change required.

* fix(skills): reframe Orbit skill descriptions as pipeline-triggered

The original descriptions framed each skill as a standalone "X-flavored
briefing template" the user picks. They are actually skills the Orbit
daily-digest pipeline selects automatically based on which connectors
the user has authenticated, then runs against live connector data.

Rewrites both `description` and `example_prompt` for all 5 templates:
- orbit-general: invoked when 2+ connectors are connected; aggregates
  the past 24h across every authenticated source
- orbit-github / orbit-gmail / orbit-linear / orbit-notion: invoked
  when the named connector is the user's only connection (or scope is
  explicitly limited to it); pulls the past 24h from that connection
  alone

All 5 now state explicitly that they are not user-triggered — the
Orbit scheduler invokes them.

* feat(examples): add Orbit pill to the mode filter row

Surfaces the Orbit briefing skills as a top-level "type" filter in the
Examples gallery, alongside Prototype · Desktop / Mobile / Slide deck /
Docs & templates. Filter matches skills with scenario === 'orbit'.

- ExamplesTab: extend ModeFilter and MODE_PILLS with 'orbit'; teach
  matchesMode and modeCounts about it
- i18n: add 'examples.modeOrbit' to Dict and to all 16 locale files
  ('Orbit' is left untranslated as a brand name)

* polish(orbit-general): real Figma preview image + revised comment

Replaces the empty gray placeholder in the Figma module with an
Unsplash UI-design photo, and rewrites the mock comment to read like
a substantive design-review note rather than a nit about button
placement.

* feat(examples): eager-load card previews via IntersectionObserver

Card previews previously only loaded on hover, leaving the example
gallery showing 'Hover to preview' placeholders for everything below
the fold. Now each card observes the viewport and prefetches its HTML
800px before scrolling into view, so the iframe is ready by the time
the user reaches it.

Hover remains as a fallback path (and for browsers without
IntersectionObserver, the card loads immediately on mount).

Also reverts the Unsplash photo on the orbit-general Figma module
back to the gray placeholder — the stock image semantically misread
as a Photoshop screenshot rather than a Figma artboard.

* feat(orbit-general): drop Figma connector module

Removes the Figma bento card and its scoped CSS, plus the orphaned
Top-3 entry that referenced a Figma comment. Reassigns Top-3 #2 to
a Notion document review so the priority list stays aligned with
the connectors actually rendered.

* i18n(skills): translate Orbit example prompts to English

The example_prompt is what gets injected into the chat input when a
user clicks 'Use this prompt', and is read by the agent verbatim. It
should match the SKILL.md description language (English), not the UI
locale. Replaces the Chinese drafts with English equivalents across
all 5 Orbit skills, and drops the Figma reference from orbit-general
since that connector module was removed earlier.

* fix(skills): rewrite Orbit SKILL.md bodies with reproducible specs

Earlier the bodies were too abstract (only a connector→UI mapping
table and a one-line style note), so agents running the skill could
not reproduce the shipped example.html and got stuck in long retries.

Each SKILL.md body now contains:
- exact color tokens lifted from the example.html
- type stack and font sizes
- a section-by-section page spec (top-to-bottom)
- chip / pill / icon rendering rules
- forbidden list

The example_prompt is collapsed back to a one-line user intent so the
skill body is the source of design truth.

Covers all 5 templates: orbit-general, orbit-github, orbit-gmail,
orbit-linear, orbit-notion.

* feat(orbit): make every connector item clickable

Each Orbit briefing template now links its rows / cards to the matching
source URL so users can jump straight from the morning digest to the
underlying connector.

- orbit-general: each bento card gains an 'Open in {connector} ↗' CTA
  built from a connector→URL map; each Top 3 card becomes an anchor
- orbit-github: every event row opens the corresponding github.com
  pull/issue URL parsed from the row identifier; the header logo links
  to the repo
- orbit-linear: each issue row gains a small ↗ button that opens
  linear.app/{team}/issue/{ID}
- orbit-gmail: action and reply buttons jump to a Gmail search URL
  scoped to the sender
- orbit-notion: page-link spans wrap as anchors and database rows are
  click-to-open against notion.so

All links use target="_blank" rel="noopener noreferrer".

* fix(skills): force agents to mirror example.html 1:1

Earlier skills told the agent the example was 'source of truth' but
left phrasing soft, so agents felt free to add extra UI elements
(snoozed-mail row, extra yellow stars on inbox rows, etc.) that
were not in example.html.

Each Orbit SKILL.md now opens with a 'Source-of-truth protocol' that
forces the agent to:
  1. read example.html before writing any output
  2. mirror its DOM structure / class names / module count / element
     order 1:1
  3. only refresh mock values; never invent additional UI elements,
     rail entries, sections, badges, or chrome ornaments

The reference sections that follow stay informative for tokens and
visual language but are explicitly demoted from spec to commentary.

* fix(orbit-gmail): remove three-pane / left-rail / inbox-list claims

The example.html is a single-column page: Gmail top header + the
opened Orbit Daily Digest email (toolbar / subject / sender / digest
body / reply bar). Earlier copy described a Gmail three-pane app with
Compose button, label list, Categories tabs, and an inbox listing —
none of which exist in the actual asset.

- example_prompt: drops 'three-pane inbox' phrasing
- description: same
- body: rewrites Page sections to mirror the real header → email-chrome
  layout, top to bottom; explicitly forbids left rail, inbox list, and
  Categories tab strip

* feat(orbit): forbid external design systems in all 5 skills

Each Orbit briefing template ships its own complete visual language
baked into example.html (Gmail / GitHub / Linear / Notion / Open
Orbit's editorial bento). Adds an explicit 'Design system policy'
block telling the agent to:

- ignore any DESIGN.md attached to the active project
- ignore brand tokens or Figma files supplied via chat
- use exclusively the colors / fonts / radii from example.html

This is a hard constraint: an Orbit briefing must look like the
connector it represents, not like the user's brand.

* feat(newproj): hide design-system picker for skills that opt out

Skills can declare 'od.design_system.requires: false' in SKILL.md to
opt out of DESIGN.md injection (the Orbit briefing skills do this —
their example.html ships with a complete connector-native visual
language). When the active default skill for a tab opts out, hide the
design-system picker so we don't ask the user to attach a brand we'll
then ignore.

Existing tabs that don't host a default skill (template, other) keep
the picker. The check only fires for prototype / live-artifact / deck.

* review: address P2 reviewer feedback

P2 — Connector family coverage gaps (orbit-general):
  Adds Finance, CRM/Sales, Support, Analytics, Infrastructure, Security
  rows to the connector→UI mapping table (now 16 families). Adds a
  'Fallback heuristics' subsection so unknown connectors are routed by
  data shape (numbers + time series → Alerts, rows + status field →
  Task mgmt, etc.).

P2 — 'Forbidden' rules too vague (all 5 skills):
  Rewrites every Forbidden section as a paired 'Don't / Do' constraints
  table so each negative is paired with a concrete positive. Replaces
  obvious bans (lorem ipsum) with substantive ones (real-shaped mock
  copy, plausible identifiers, dev-team label hues, etc.).

* ci: register orbit skills in de/ru/fr en-fallback lists

The localized-content coverage test asserts that every skill in
skills/ is either translated or explicitly declared as falling back
to English in the LOCALIZED_CONTENT_IDS bundle. The 5 new orbit
skills weren't in any bundle, so the workspace validation job failed
on the de/ru/fr coverage assertions.

Adds the 5 orbit-* ids to DE/FR/RU_SKILL_IDS_WITH_EN_FALLBACK so
those locales explicitly fall back to the SKILL.md English copy
(matching the minimal-change posture chosen earlier in this PR).
2026-05-06 21:39:52 +08:00
Xinmin ZengandGitHub 2455c70d51 fix(daemon, packaged): unbreak GUI-launched agent detection on minimal PATHs (#442) (#614)
* fix(daemon, packaged): unbreak GUI-launched agent detection on minimal PATHs (#442)

GUI-launched daemons (Finder/Dock on macOS, .desktop on Linux) inherit a
stripped PATH from launchd / the desktop session and don't read the
user's interactive shell rc files, so any CLI installed via `npm i -g`
under a sudo-free prefix like ~/.npm-global was silently undetected.

Two layers maintained their own copies of the user-toolchain bin list
(`apps/daemon/src/agents.ts:userToolchainDirs` for the resolver,
`apps/packaged/src/sidecars.ts:resolvePackagedPathEnv` for the packaged
sidecar PATH builder) and had already drifted on `~/.asdf/shims` and
`~/Library/pnpm`. Adding ~/.npm-global to one side would have
preserved the same anti-pattern.

Extracts `wellKnownUserToolchainBins` into @open-design/platform as the
single source of truth, has both layers consume it, and extends the
list to cover ~/.npm-global/bin, ~/.npm-packages/bin, plus
$NPM_CONFIG_PREFIX/bin / $npm_config_prefix/bin for users with a
non-standard prefix. New vitest coverage in the platform package and
a regression test in apps/daemon/tests/agents.test.ts modelled on the
existing mise case.

Verified end-to-end: under PATH=/usr/bin:/bin:/usr/sbin:/sbin (the
launchd default a `.app` actually inherits), `resolveAgentExecutable`
now returns ~/.npm-global/bin/gemini instead of null.

* fix(daemon): isolate OD_AGENT_HOME resolution from $NPM_CONFIG_PREFIX leakage

Address review feedback on PR #614:

- mrcfps spotted that the daemon wrapper called wellKnownUserToolchainBins
  without passing `env`, so the helper read its default process.env. A
  developer or CI runner with NPM_CONFIG_PREFIX / npm_config_prefix
  exported would inject that real <prefix>/bin into resolveOnPath() even
  while the OD_AGENT_HOME hook pointed home at a temp fixture, making
  agent-detection tests environment-dependent. Reproduced locally: with
  OD_AGENT_HOME=<tmp> + NPM_CONFIG_PREFIX=/Users/me/.npm-global,
  resolveAgentExecutable({ bin: 'codex' }) returned the real machine's
  binary instead of null. Wrapper now passes `env: {}` whenever
  homeOverride is set, alongside the existing includeSystemBins gate.

- lefarcen suggested also handling whitespace-only NPM_CONFIG_PREFIX
  values (e.g. NPM_CONFIG_PREFIX=" ") so the helper does not emit a
  bogus "<whitespace>/bin" entry. Added a .trim() check before
  appending.

- lefarcen also suggested a comment pointer from the daemon wrapper to
  the platform helper so readers don't have to grep. Added the
  reference inline.

Coverage:
- packages/platform/tests/index.test.ts: new whitespace-prefix case.
- apps/daemon/tests/agents.test.ts: new env-isolation regression
  asserting that OD_AGENT_HOME + NPM_CONFIG_PREFIX cannot leak the
  real prefix bin into the sandbox.

* test(daemon): preserve $NPM_CONFIG_PREFIX across the env-isolation case (#614)

Address mrcfps's second-round review on PR #614: the env-isolation
regression sets `process.env.NPM_CONFIG_PREFIX = realPrefix` in its
body and then unconditionally `delete`s it in `finally`. On a developer
machine or CI runner that already exported `NPM_CONFIG_PREFIX`, that
mutates the worker-wide env for every later test, making downstream
env-sensitive assertions order-dependent.

Move the save/restore into the file's existing afterEach hook (mirroring
the OD_AGENT_HOME / OD_DAEMON_URL / OD_TOOL_TOKEN pattern) and drop the
in-test `delete`. Same coverage, no worker-state mutation.

* fix(platform): prioritise $NPM_CONFIG_PREFIX over the conventional npm guesses (#614)

Address mrcfps's third-round review on PR #614: when the user has
explicitly configured a prefix via $NPM_CONFIG_PREFIX (or
$npm_config_prefix), that's where `npm i -g` puts the *current*
binaries. The conventional guesses ~/.npm-global / ~/.npm-packages
often hold *stale* installs from an older prefix the user has since
rewritten — searching the env-driven prefix first matches npm's own
resolution order (env > .npmrc > default) and gives "explicit beats
convention" semantics.

Move the env-driven push above the conventional `dirs.push(.npm-global,
.npm-packages)`. Add a vitest case in the platform package that asserts
$NPM_CONFIG_PREFIX/bin's index in the result is strictly less than
~/.npm-global/bin's and ~/.npm-packages/bin's.

`resolveOnPath()` and the packaged PATH builder both preserve insertion
order, so first hit wins and the new ordering propagates to both
layers.

* fix(platform): lift $NPM_CONFIG_PREFIX above every conventional bin (#614)

Address mrcfps's fourth-round review on PR #614: the previous fix only
moved $NPM_CONFIG_PREFIX/bin ahead of ~/.npm-global / ~/.npm-packages,
but ~/.local/bin still appeared earlier in the array. Under a minimal
GUI-launch PATH a stale agent in ~/.local/bin (also a shared dumping
ground for pip --user / cargo install / hand-built binaries) could
outrank the user's *current* explicit npm prefix.

Move the env-driven push to the head of `dirs` so the explicit prefix
wins over every conventional location below — ~/.local/bin included.
Matches npm's own resolution order (env > .npmrc > default) across the
whole list, not just the npm-prefix block.

Tightened the existing order test to assert `explicitIdx === 0` and
that ~/.local/bin's index is strictly greater than the explicit
prefix's index, so a future drift would fail loudly.
2026-05-06 20:35:19 +08:00
zztdanandGitHub f3024fdc22 feat(media): add Nano Banana image provider (#631)
* feat(media): add Nano Banana image provider

* fix(media): support Gemini API key headers for Nano Banana

* refactor(media): move Nano Banana model override flag into provider metadata
2026-05-06 20:26:31 +08:00
CaprikaandGitHub 8dc0875147 fix(i18n): remove duplicate Ukrainian prompt template keys (#680) 2026-05-06 20:25:00 +08:00
iulianandGitHub f880fd8c1d docs(tools-pack): fix Linux namespace env var (#670)
* docs(tools-pack): fix linux namespace env var

* fix(web): remove duplicate Ukrainian prompt labels
2026-05-06 20:11:40 +08:00
Jie ZhuandGitHub dbd28b79c0 fix(web): improve settings dialog scroll behavior (#667)
* fix(web): improve settings dialog scroll behavior

- Remove double scroll containers by changing modal-body overflow from auto to hidden, letting only settings-content handle scrolling

- Add min-height: 0 to settings-content and settings-sidebar to allow proper shrinking in grid layout

- Add overscroll-behavior: contain to prevent scroll chaining (scroll bleed-through to parent page)

- Add overflow-y: auto to settings-sidebar for cases where navigation items exceed viewport height

These changes fix the nested scroll issue that caused confusing scroll behavior and prevent content overflow on smaller viewports.

* fix(i18n): add missing Ukrainian translations for promptTemplates

Add promptTemplates.allSources and promptTemplates.sourceFilterAria translations to fix TypeScript error.
2026-05-06 20:09:17 +08:00
Siri-RayandGitHub 26636384a8 Fix desktop entry chrome consistency (#655)
* Fix desktop entry chrome consistency

* Fix mobile entry chrome overflow

* Fix Ukrainian prompt template source labels
2026-05-06 19:55:27 +08:00
mambaandGitHub 570d06419c feat[qoder cli] add Qoder CLI agent support (#626)
* chore(agent): 增加对 Qoder CLI 的支持和识别

- 在 QUICKSTART 文档中添加 Qoder CLI 为可选本地 agent CLI
- 更新代码中 agents.ts 注释包含 Qoder CLI 扫描支持
- 修改首次加载时检测的可用 CLI 列表,加入 Qoder CLI
- 在多个语言版本的 README 中增加 Qoder CLI 支持及相关徽章统计
- 更新 agent 适配器与事件解析相关的代码注释和文档,包含 qoder-stream-json 解析器
- 调整 Windows 下 spawn 行为以支持 Qoder CLI 的 stdin 提供 prompt
- 修复多语言文档对支持的 CLI 数量描述错误,确保数据保持同步

Change-Id: I388f2f61c60ce8faa7cef5d84eb407950f8bdbfb
Co-developed-by: Qoder <noreply@qoder.com>

* chore(agent): 增加对 Qoder CLI 的支持和识别

- 在 QUICKSTART 文档中添加 Qoder CLI 为可选本地 agent CLI
- 更新代码中 agents.ts 注释包含 Qoder CLI 扫描支持
- 修改首次加载时检测的可用 CLI 列表,加入 Qoder CLI
- 在多个语言版本的 README 中增加 Qoder CLI 支持及相关徽章统计
- 更新 agent 适配器与事件解析相关的代码注释和文档,包含 qoder-stream-json 解析器
- 调整 Windows 下 spawn 行为以支持 Qoder CLI 的 stdin 提供 prompt
- 修复多语言文档对支持的 CLI 数量描述错误,确保数据保持同步

Change-Id: Id33f125b7c0b1a1c0b0274073da74d1578c324f7
Co-developed-by: Qoder <noreply@qoder.com>

* feat(agent-icon): 添加新的Qoder徽标SVG图形组件

- 新增qoderGlyph函数,返回指定大小的SVG格式图形
- 图形包含多路径定义,颜色使用深灰和绿色填充
- 该组件可用于替代或补充现有AgentIcon图标功能
- 提升应用程序的品牌标识和视觉表现力

Change-Id: I4eca18166b5e33bc6229b40b2531d5a54607a560
Co-developed-by: Qoder <noreply@qoder.com>

* Translate to English:

---

**docs(readme): update to expand CLI agents to 16**

- Increased the number of coding agent CLIs from 11 to 16
- New agents included: Devin for Terminal, Kiro CLI, Kilo, Mistral Vibe CLI, DeepSeek TUI

**docs(readme): update to expand supported coding agents to 16**

- Increased the number of supported code agent CLIs from 11 to 16
- Added support for new CLI tools: Devin for Terminal, Kiro CLI, Kilo, Mistral Vibe CLI, DeepSeek CLI
- Added automatic CLI detection and switching while maintaining support for more agents
- Added BYOK proxy TUI
- Expanded compatibility and support coverage in the README’s multiple language versions
- Reflected changes across all README translations (Arabic, German, French, Japanese, Korean)
- Updated badges and descriptions to reflect CLI count and feature changes
- Added event parsers and protocols for the new CLIs in the agent transport implementation
- Updated the BYOK proxy and tool exploration features to be compatible with the expanded CLIs

Change-Id: I89786b4a0b09bd279fb23265c2177076206fc5af
Co-developed-by: Qoder <noreply@qoder.com>

* feat(daemon): 支持 imagePaths 参数作为附件路径传递给 Qoder

- 修改 buildArgs 函数,添加 --attachment 参数处理 imagePaths 中的绝对路径
- 过滤并忽略空字符串、非字符串及相对路径的 imagePaths 项
- 在单元测试中覆盖 imagePaths 参数支持及无效项过滤逻辑
- 在文档中补充 Qoder 运行时适配器对 --attachment 参数的说明

Change-Id: Ibfc3583ba86c6d258d524912559e97b77bf1dc87
Co-developed-by: Qoder <noreply@qoder.com>

* docs(runtime): 说明Qoder适配器继承用户令牌的环境变量

- 添加文档说明检测代理仅为可用性探针,不进行身份验证
- 说明Qoder CLI账号状态独立,认证通过运行时错误路径反馈
- 详细描述子进程环境继承机制及静态环境变量与用户私密令牌区分
- 明确QODER_PERSONAL_ACCESS_TOKEN通过守护进程环境传递,不写入静态环境
- 解释Qoder验证由Qoder CLI负责,支持持久登录和自动化环境变量注入

test(agent): 添加QODER_PERSONAL_ACCESS_TOKEN环境变量继承测试

- 验证qoder适配器环境继承守护进程中的QODER_PERSONAL_ACCESS_TOKEN
- 确认qoder适配器未在静态环境变量中定义用户令牌
- 保证用户私密令牌不会被写入静态适配器环境配置

Change-Id: Ie61869afbe497df1b16879b4e47b35123f758ed8
Co-developed-by: Qoder <noreply@qoder.com>

* fix(daemon): 改进Qoder模式支持及错误处理机制

- 更新Qoder CLI参数,使用`--yolo`替代`--permission-mode bypass_permissions`
- 将工作目录参数从`--cwd`改为`-w`以符合Qoder文档
- 在agent流事件处理中新增错误捕获并通过SSE错误事件发送
- 运行结束时若检测到agent流错误,则标记运行失败
- 测试中fix(daemon): 优化Qoder代理参数与错误处理

- 调整Qoder启动参数,改用`--yolo`和`-w`替代旧参数,避开argv长度限制
- 增强代理流事件处理,捕获并通过SSE错误通同步更新Qoder参数使用及相应断言
- 新增端到端测试,覆盖Qoder助手错误通过SSE错误通道反馈及运行状态失败处理
- 补充工具函数辅助测试事件流读取与运行状态轮询

Change-Id: I5d933745c3659e093b0d2d807f22726e7f83eb48
Co-developed-by: Qoder <noreply@qoder.com>

* feat(qoder-stream): 识别并报告Qoder运行错误事件

- 新增messageFromResult函数以从结果对象提取错误信息
- 在处理result事件时根据is_error字段触发error事件
- error事件携带具体错误消息和原始数据
- 添加测试验证Qoder运行返回is_error且退出码为0时正确触发错误事件
- 更新qoder流解析测试以校验错误事件映射
- 在聊天路由测试中增加针对Qoder错误运行的端到端场景验证

Change-Id: Ie98ac518135dbec3181c52de5a49afdea993e279
Co-developed-by: Qoder <noreply@qoder.com>
2026-05-06 19:54:03 +08:00
CaprikaandGitHub c317c889fd Fix Ukrainian prompt template translations (#674) 2026-05-06 19:49:21 +08:00
Deheng HuangandGitHub 09b78c2f9b feat(daemon): let Codex image projects use built-in imagegen (#622)
* feat(daemon): let Codex image projects avoid API-key setup

Codex has a built-in image generation path available inside the agent runtime, while the generic media dispatcher still routes gpt-image models through the daemon OpenAI provider. Pass the active agent id into prompt composition so Codex-only gpt-image projects can use built-in imagegen first without changing non-Codex media behavior.

Constraint: Existing media contract remains the default path for non-Codex agents and explicit provider fallback
Rejected: Add a nested daemon Codex media provider | heavier auth, streaming, timeout, cancellation, and output parsing surface for this parity fix
Confidence: high
Scope-risk: narrow
Directive: Keep this override after the media contract so it can intentionally supersede dispatcher-only wording for Codex gpt-image projects
Tested: pnpm --dir apps/daemon exec vitest run -c vitest.config.ts tests/system-prompt-template.test.ts
Tested: pnpm --filter @open-design/daemon typecheck
Tested: pnpm guard
Tested: pnpm typecheck
Not-tested: Live Codex image generation inside the Open Design UI

* fix(daemon): harden Codex imagegen prompt routing

PR review found the Codex override could be superseded by the web-supplied media contract, trusted unvalidated image model metadata, and assumed generated image paths outside the workspace were readable.

This keeps the override daemon-owned, appends it last in the live prompt, validates against registered gpt-image model IDs, allowlists only Codex's generated_images folder, and tightens copy-failure instructions.

Constraint: The web contracts composer still emits the generic media contract without agent identity.

Rejected: Mirror Codex-specific prompt logic into contracts/web | duplicates daemon model registry and still leaves final ordering fragile.

Confidence: high

Scope-risk: narrow

Directive: Keep Codex imagegen override appended after client systemPrompt so it remains the final media instruction for Codex gpt-image projects.

Tested: pnpm --dir apps/daemon exec vitest run -c vitest.config.ts tests/system-prompt-template.test.ts tests/agents.test.ts tests/chat-route.test.ts

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

Tested: pnpm guard

Tested: pnpm typecheck

Not-tested: Live Codex image generation inside the Open Design UI

* fix(daemon): keep Codex add-dir writable scope narrow

PR review found Codex --add-dir grants writable workspace access, so passing skill, design-system, and linked reference directories through the same chat allowlist broke their documented read-only boundary.

This routes chat extra directories by active agent: Codex receives only the validated generated_images output directory needed for built-in imagegen, while non-Codex adapters keep the existing resource and linked-directory read access behavior.

Constraint: Codex CLI treats --add-dir as writable sandbox expansion.

Constraint: The daemon still stages active skill files into the project cwd as Codex's read-safe path.

Rejected: Keep one shared extraAllowedDirs list for all agents | grants Codex write access to read-only resources.

Confidence: high

Scope-risk: narrow

Directive: Do not add read-only resource/reference directories to Codex --add-dir unless Codex gains a read-only allowlist flag.

Tested: git diff --check -- apps/daemon/src/server.ts apps/daemon/tests/chat-route.test.ts

Tested: pnpm --filter @open-design/daemon exec vitest run tests/chat-route.test.ts

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

Tested: pnpm guard

Tested: pnpm typecheck

Not-tested: Live Codex image generation inside the Open Design UI

* fix(daemon): validate Codex imagegen add-dir grants

PR review found the generated_images grant still trusted symlinked paths and rendered the Codex override before proving the sandbox grant would be present.

This validates the generated_images directory before prompt assembly, rejects final-component symlinks and protected-root canonical escapes, passes Codex the canonical grant path, and only appends the Codex imagegen override when that same path is in extraAllowedDirs.

Constraint: Codex --add-dir grants writable workspace access, so path aliases into read-only resource roots must be rejected.

Rejected: Keep returning the nominal CODEX_HOME path after validation | leaves Codex operating through a symlink alias instead of the audited grant target.

Confidence: high

Scope-risk: narrow

Directive: Keep Codex imagegen prompt rendering downstream of generated_images validation and grant resolution.

Tested: git diff --check -- apps/daemon/src/server.ts apps/daemon/tests/chat-route.test.ts

Tested: pnpm --filter @open-design/daemon exec vitest run -c vitest.config.ts tests/chat-route.test.ts

Tested: pnpm --filter @open-design/daemon exec vitest run -c vitest.config.ts tests/agents.test.ts tests/chat-route.test.ts

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

Tested: pnpm guard

Tested: pnpm typecheck

Not-tested: Live Codex image generation inside the Open Design UI
2026-05-06 18:28:16 +08:00
Tom HuangandGitHub 9d176ef12e feat(prompt-templates): HyperFrames video previews + provider badge + source filter (#293)
* feat(prompt-templates): add 11 HyperFrames video prompts and surface media generation in README

Adds eleven `hyperframes-*` prompt templates under `prompt-templates/video/`,
each one a concrete brief with scene-by-scene timing, GSAP eases, palette,
and the HyperFrames non-negotiables (deterministic, paused timelines,
entrance-only motion, lint/inspect commands). Archetypes covered:

- minimal product reveal (5s, 16:9)
- SaaS product promo (30s, 16:9, Linear/ClickUp-style)
- TikTok karaoke talking-head (9:16, TTS + word-synced captions)
- brand sizzle reel (30s, beat-synced kinetic typography)
- animated bar-chart race (NYT-style data infographic)
- Apple-style flight map route (origin → destination)
- 4s cinematic logo outro
- $0 → $10K money counter hype (9:16)
- 3-phone app showcase
- 9:16 social overlay stack (X · Reddit · Spotify · Instagram)
- 15s website-to-video pipeline

Each template uses `model: "hyperframes-html"`, real catalog-block thumbnails
from HeyGen's CDN as previewImageUrl, and source attribution to
`heygen-com/hyperframes` (Apache-2.0).

README also gets a new **Media generation** section between *Visual directions*
and *Beyond chat*, plus a new row in the *At a glance* table. The section
documents the three model families currently surfaced as templates
(gpt-image-2, Seedance 2.0, HyperFrames) with example galleries — gpt-image-2
thumbnails, Seedance MP4-linked thumbnails, and the 11 HyperFrames tiles —
and notes the wider model coverage (Kling, Veo, Sora, MiniMax, Suno, Udio,
Lyria, TTS) already wired in `VIDEO_MODELS` / `AUDIO_MODELS_BY_KIND` and
open for community templates.

* i18n(de): register new HyperFrames templates, categories, tags

Adds German titles/summaries for the 11 new hyperframes-* video templates
plus the Product/Marketing/Data/Travel/Branding/Short Form categories and
hyperframes/title-card/sizzle/etc. tags they introduce, so the German sync
guarantees enforced by apps/web/src/i18n/content.test.ts hold.

* docs(readme): sync Media generation section to de / ja / ko / zh-CN; bump counts to 93 (43 + 39 + 11)

Mirrors the English Media generation row + section into the four locale READMEs
(README.de.md, README.ja-JP.md, README.ko.md, README.zh-CN.md), translating
prose / table headers / captions while keeping the gpt-image-2, Seedance MP4,
and HyperFrames catalog-block thumbnails identical across all five locales so
the galleries render the same images.

Counts updated to reflect current main (after rebase): 43 gpt-image-2 + 39
Seedance + 11 HyperFrames = 93 prompts total. The English README's At-a-glance
row, intro paragraph, and gallery sub-headers now read "sample of 43" /
"sample of 39" / "11 ready-to-replicate templates" — locales follow.

Resolves the Codex review's German-i18n flag end-to-end: README copy is in
sync, and the German content map (DE_PROMPT_TEMPLATE_*) was already extended
in the prior commit on this branch.

* feat(prompt-templates): video previews + provider badge + source filter for HyperFrames

- Add `previewVideoUrl` to all 11 HyperFrames video templates so the preview
  modal plays the real catalog clip instead of falling back to a static image.
- Add a per-card provider badge (top-left thumbnail chip) keyed off
  `source.repo`. HyperFrames cards get a HeyGen-accent gradient so they are
  identifiable at a glance; other repos get a neutral pill.
- Add a Source filter dropdown next to Category in PromptTemplatesTab,
  populated from the small enumerated repo set (HyperFrames, Seedance 2,
  GPT Image 2, Open Design). Auto-hides when only one source is present.
  The text search now also matches the provider name.
- Wire i18n keys `promptTemplates.allSources` and
  `promptTemplates.sourceFilterAria` across all 9 locales.
2026-05-06 18:09:30 +08:00
99d443c512 fix(daemon): ignore .venv and other large dirs in project file watcher (#531)
* fix(daemon): ignore .venv and other large dirs in project file watcher

A project containing a Python virtual environment (.venv) could exhaust
the daemon's file descriptor table — chokidar recursively watched every
file in the tree, opening ~18 000 fds. With the fd table full, macOS
posix_spawn returned EBADF when the daemon tried to create stdio pipes
for a child process (codex exec, or any other agent), surfacing as
"spawn failed: spawn EBADF" on every chat request.

Adds .venv, venv, __pycache__, .mypy_cache, .pytest_cache, .tox,
.ruff_cache, target, vendor, and .cargo to the per-segment IGNORE_NAMES
set so the watcher skips these trees in any project.

* fix(daemon): narrow project-watcher ignores to safe Python dirs only

Remove target, vendor, and .cargo from IGNORE_NAMES — they match any
path segment, so src/vendor/… or .cargo/config.toml (a valid Rust
project file) would be silently dropped from file-change events.

Keep only the Python-specific names (.venv, venv, __pycache__, and the
mypy/pytest/tox/ruff caches) which are never legitimate authored source
at any depth and were the root cause of the fd-exhaustion bug.

Add a real-chokidar test covering all seven newly added ignore dirs.

---------

Co-authored-by: hbrown <hbrown@mitre.org>
2026-05-06 18:07:08 +08:00
lefarcenandGitHub ae4a08773a chore(release): prepare 0.4.1 (#659)
- bump remaining monorepo package.json files to 0.4.1 after apps/packaged was already bumped in #637
- add CHANGELOG.md [0.4.1] - 2026-05-06 entry covering the startup hotfix and 19 merged PRs since 0.4.0:
  - Added: manual edit mode (#620), Cmd/Ctrl+P quick file switcher (#556), resizable chat panel (#563), PI status/cancel updates (#618), accessibility and RTL/Bidi craft modules (#587, #595), i18n structure checks (#608)
  - Changed: first-PR README links now surface help-wanted issues (#605)
  - Fixed: packaged contracts runtime exports (#577), packaged runtime beta gating (#637), ACP/MCP/agent fixes (#604, #612, #627), conversation error recovery (#623), native mac quit (#637)
  - Documentation/Internal: OD_DATA_DIR migration docs (#570), Simplified Chinese QUICKSTART (#578), zh-TW/ko README syncs (#586, #619), generated metrics (#592)

Release workflow validation runs after merge via release-stable.
2026-05-06 18:05:56 +08:00
f0185f4d2c fix: constrain chat panel width overflow (#644)
- Add min-width: 0 on .split-chat-slot, .pane, .chat-log-wrap, .chat-log to break flex content width propagation
- Add min-width: 0 and max-width: 100% to .op-card for tool cards
- Use responsive max-width: min(220px, 100%) for file paths in tool cards

Co-authored-by: bojiehuang <bojiehuang@bojiehuangdeMacBook-Pro.local>
2026-05-06 17:45:03 +08:00
PerishFireandGitHub f1cdb2844a test(e2e): gate beta packaged runtime (#637)
* test(e2e): gate beta mac packaged runtime

* test(e2e): separate ui automation layout

* test(e2e): move localized content coverage

* chore(release): prepare packaged 0.4.1 beta validation

* test(e2e): keep ui lane playwright-only

* fix(web): keep chat recoverable after conversation load failure

* fix(desktop): honor native mac quit
2026-05-06 17:44:29 +08:00
GoemonandGitHub 95bd7e5373 fix(daemon): add required env field to McpServerStdio + recover from -32602 on set_model (#627)
* fix(daemon): add required env field to McpServerStdio in live-artifacts MCP descriptor

The ACP schema's McpServerStdio marks env as a required field
(List[EnvVariable] with no default). Omitting it causes Pydantic V2
Union validation to fail across all three variants (HttpMcpServer,
SseMcpServer, McpServerStdio), returning -32602 Invalid params on
session/new for agents with mcpDiscovery: 'mature-acp' (Hermes,
Devin, Kimi).

This bug is invisible when mcpServers resolves to an empty array
(no live-artifacts token), so it only manifests when the MCP
live-artifacts integration is enabled.

* fix(daemon): recover from -32602 Invalid params on session/set_model

Extend the existing -32603 Internal error recovery logic to also
handle -32602 (Invalid params) when the set_model request fails.
This allows the prompt to proceed with the default model instead of
hanging or timing out.

Some ACP agents may not support session/set_model or may reject the
model ID — treating this as a non-fatal condition and falling back to
the default model is more resilient than failing the entire run.

* fix(daemon): narrow -32602 handling and update test fixtures for env field

Address PR #627 review feedback:

1. Narrow -32602 Invalid params suppression to setModelRequestId only.
   Unexpected-id -32602 errors are now treated as real protocol failures
   and propagated via fail(), matching the reviewer's suggestion. Only
   -32603 Internal errors from unexpected IDs are still suppressed as
   cleanup noise.

2. Update all buildLiveArtifactsMcpServersForAgent test fixtures to
   include the new required env: [] field.
2026-05-06 16:28:43 +08:00
CaprikaandGitHub 8eb9b1b506 Implement manual edit mode (#620) 2026-05-06 16:13:52 +08:00
SidandGitHub 33255a8fdf Fix agent CLI config and workspace focus mode (#604)
* fix agent CLI config and workspace focus mode

* address CLI env review follow-ups
2026-05-06 16:06:56 +08:00
nhancdt2602andGitHub d1d63f9dae Fix/error message persistence (#623)
* test(ProjectView): persists daemon errors on assistant messages

Signed-off-by: nhancdt2602 <nhancu2602@gmail.com>

* fix(ProjectView): persists daemon errors on assistant message

Signed-off-by: nhancdt2602 <nhancu2602@gmail.com>

* test(e2e): cover agent switch and persistence

Signed-off-by: nhancdt2602 <nhancu2602@gmail.com>

* chore(chat-event): handle falsy empty error detail

Signed-off-by: nhancdt2602 <nhancu2602@gmail.com>

---------

Signed-off-by: nhancdt2602 <nhancu2602@gmail.com>
2026-05-06 15:28:30 +08:00
zzandGitHub dd702c7254 fix(acp): normalize mcpServers to stdio shape for Kimi/Hermes ACP (#612)
Kimi CLI 1.35.0 expects MCP stdio servers to include 'type', 'name',
'command', 'args', and 'env' fields. Open Design was passing only
'name', 'command', and 'args', which caused session/new to return
JSON-RPC -32602 Invalid params when MCP discovery was enabled.

This change normalizes every MCP server descriptor to the full ACP
stdio shape before sending it over the wire.
2026-05-06 15:03:51 +08:00
TomandGitHub 5df04c29a3 feat(daemon): add model name to pi initial status and RPC abort on cancel (#618)
* feat(daemon): add model name to pi initial status and RPC abort on cancel

- Emit status:initializing with model name before pi responds so the UI
  shows 'pi · claude-sonnet-4-5' — matching Claude Code, Copilot, Gemini,
  and Cursor Agent model-name parity
- Replace raw SIGTERM with RPC abort command on cancel, giving pi a
  chance to clean up gracefully before SIGTERM fallback
- Wire run.acpSession onto the run object so cancel() can dispatch to
  session.abort() for pi and ACP adapters
- Add stdinOpen guard so sendCommand is a no-op after stdin closes
- Add 4 tests covering initializing status, abort wire format, and
  stdin-closed guard

* fix(daemon): gate stdout parser after abort to prevent post-cancel events

Once abort() sets finished=true, the stdout listener kept feeding
chunks into mapPiRpcEvent, so text_delta/tool/status events could
still be emitted during the PI_ABORT_GRACE_MS window. Add a finished
guard at the top of the parser callback so no agent events are
forwarded after abort, while still draining stdout cleanly.

Adds a test that aborts mid-session, then feeds message_update and
tool events, proving zero post-abort agent events are emitted.

* refactor(daemon): own SIGTERM fallback in cancel, rewrite abort tests as integration

- Move SIGTERM fallback from pi-rpc abort() to runs cancel() so the
  termination guarantee is centralized — a misbehaving session can't
  leave the child alive indefinitely (address lefarcen P3 on L130)
- Remove the setTimeout/SIGTERM from abort(); it now only sends the
  RPC abort command, termination is the caller's responsibility
- Rewrite initial-status and abort tests as integration tests that
  exercise attachPiRpcSession against mock child processes instead
  of duplicating private sendCommand/send helpers inline (address
  lefarcen P3 on L453 and L491)
- All 28 tests pass
2026-05-06 12:20:40 +08:00
netteeandGitHub 8762f06297 Add i18n structure checks (#608) 2026-05-06 11:55:59 +08:00
2036ce0a8e feat(web): add Cmd/Ctrl+P quick file switcher (#556)
* feat(web): add Cmd/Ctrl+P quick file switcher

A keyboard-driven file palette overlaid on the workspace. Press Cmd/Ctrl+P
anywhere in the project view; type to fuzzy-filter the file list, ↑/↓ to
navigate, Enter to open in a tab, Esc to dismiss. With an empty query the
palette surfaces recents (per-project, localStorage) followed by the rest
of the file list sorted by mtime.

Adds:
- apps/web/src/components/QuickSwitcher.tsx: palette UI and matcher
- apps/web/src/quickSwitcherRecents.ts: per-project recents store
- index.css: scoped .qs-* styles using existing design tokens
- i18n: 6 new keys translated across all 16 locale files

Wires into FileWorkspace's existing openFile() so recents and tab state
behave identically to opening from DesignFilesPanel. Capture-phase keydown
beats the browser's print dialog. No backend changes; uses the files prop
already passed to FileWorkspace.

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

* fix(web): address QuickSwitcher review feedback

Three fixes from the PR review:

- z-index: bump .qs-overlay from 200 to 1500 so the palette renders in
  the modal tier (alongside prompt-template-modal-overlay) instead of
  behind context menus and popovers (which sit at 200).

- Arrow-key guard: skip setCursor when matches is empty. Without this,
  pressing ↓ on a no-results query set the cursor to -1, making the
  highlight selector miss every row on the next render.

- Tests: add 19 unit tests covering scoreMatch ranking tiers, render
  output (empty state / row count / kbd hints / placeholder), and the
  full recents lifecycle (cap at 6, dedupe-on-push, corrupt-JSON
  recovery, per-project scoping, quota-exceeded no-op). Vitest stays
  on the node env via a small in-memory localStorage stub.

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

* fix(web): QuickSwitcher review — wrap, IME, platform gate

Three follow-ups from @mrcfps's review on #556:

- ArrowUp/ArrowDown now wrap at list bounds (last → first, first → last)
  via modulo arithmetic in a new pure helper `nextCursor(current, total,
  direction)`. Previously they clamped, which contradicted the wrap
  behavior the PR test plan promised. Pulled into a pure function so
  boundary cases are unit-testable without simulating keyboard events.

- Palette's onKeyDown now early-returns on `e.nativeEvent.isComposing`,
  so users typing CJK file names through an IME keep ↑/↓/Enter for
  candidate navigation instead of having them steered by the palette.
  The global Cmd/Ctrl+P opener already had the equivalent guard.

- Global keydown is now platform-gated: macOS responds only to metaKey,
  win/linux only to ctrlKey. Previously both fired everywhere, which
  meant Ctrl+P on macOS was stealing native readline "previous line" in
  text fields (and the chat composer).

Tests: +6 unit tests for `nextCursor` covering forward/backward wrap,
mid-list moves, empty list (no division-by-zero), and single-item
no-op. Suite now 258 passing (up from 252).

Verified live: ↓ from last row → first row; ↑ from first row → last
row, in a mocked-project Playwright harness.

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

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 10:31:50 +08:00
ferasbusiness666andGitHub e8b63ecec1 feat: add resizable chat panel (#563) 2026-05-06 10:12:45 +08:00
bojie.hbjandGitHub 29e1ed5f28 fix: 修复设计文件列表行菜单被裁剪的问题 (#561) 2026-05-06 00:46:18 +08:00
TomandGitHub 6b2792b03a fix(daemon): remove --no-session from pi adapter to persist session files (#557)
* fix(daemon): remove --no-session from pi adapter to persist session files

The pi agent was the only adapter explicitly passing `--no-session`
in its `buildArgs`, preventing pi from writing session files.
All other adapters either run in single-shot mode by design or use
the ACP JSON-RPC session lifecycle without suppressing persistence.

Removing `--no-session` lets `--mode rpc` retain its default behavior
of writing session state, which is needed for multi-prompt continuity
and matches the rest of the harness ecosystem.

* test(daemon): add pi buildArgs regression tests; fix docs for --no-session removal

- Adds test for pi.buildArgs base shape: returns ["--mode", "rpc"]
  and does not include --no-session (prevents regression).
- Adds test for --model and --thinking option passthrough.
- Updates pi-rpc.ts lifecycle comment to remove [--no-session].
- Updates README.md and all localized READMEs to reflect the
  corrected pi CLI invocation.
2026-05-06 00:26:09 +08:00
Marc ChanandGitHub 0953ab58a2 fix(web): todo card dark mode styling (#526)
* fix(web): improve todo card dark mode

* fix(web): address todo card contrast feedback

Generated-By: looper 0.5.5 (runner=fixer, agent=opencode)
2026-05-05 23:57:02 +08:00
lefarcenandGitHub 963bbf2500 release: Open Design 0.4.0 (#454) 2026-05-05 23:39:40 +08:00
ChildhoodAndyandGitHub 009d7a5478 refactor(daemon): eliminate duplicate dist tree from two-tsconfig build (#553)
Move sidecar source under src/ so a single tsconfig produces all daemon
output. Removes the parallel dist/src/ tree that was emitted by
tsconfig.sidecar.json (it included src/**/*.ts to type-check the
`../src/server.js` cross-tree import).

Build now emits:
- dist/<flat>            (cli.js, server.js, app-version.js, ...)
- dist/sidecar/{index,server}.js

`dist/sidecar/server.js` reaches the main daemon via `../server.js`
instead of `../src/server.js`, so there is no second copy of the source
tree in the published tarball.

Background — issue #534 (already fixed by #537):
The packaged Settings → About panel showed 0.0.0 because the sidecar
chain loaded the duplicated `dist/src/app-version.js`, where the fixed
`new URL('../package.json', import.meta.url)` resolved to a non-existent
`dist/package.json`. #537 patched the symptom by walking parents until a
real `package.json` is found and by writing `appVersion` into the Linux
packaged config. Both stay in place — they're sound defenses — but the
underlying duplicate-emit was never addressed; any future relative
resource lookup (templates, schemas, prompts) anchored on
`import.meta.url` would have hit the same trap.

This change removes the trap.
2026-05-05 23:31:14 +08:00
soulmeandGitHub 6b7a40e5c3 Fix file tab wheel scrolling (#549) 2026-05-05 23:23:48 +08:00
Justin GaoandGitHub cbe2baf596 feat(web): add skills & design systems management page in settings (#535)
* feat(web): add skills & design systems management page in settings

Add a new "Library" section in Settings that lets users browse, search,
preview, and enable/disable skills and design systems. Disabled items are
excluded from the create-project picker. Phase 1 — browse/toggle only.

Closes #497

* fix(web): persist empty disabled lists and deduplicate DS preview

Use empty array instead of undefined when all items are re-enabled so
the daemon merge clears the key. Move DS preview panel outside the
category group loop so it renders once, not per group.

* fix(web): address review feedback on library settings

Clear disabled lists on invalid daemon writes, memoize enabled item
filters in App.tsx, and guard preview fetch against rapid-click race
conditions.

* fix(web): hydrate disabled lists from daemon and keep full lists in ProjectView

Merge daemonConfig.disabledSkills/disabledDesignSystems during bootstrap
so the values survive localStorage resets. Pass unfiltered skills and
design systems to ProjectView so existing project metadata resolves
correctly.
2026-05-05 22:50:25 +08:00