Commit Graph

19 Commits

Author SHA1 Message Date
open-design-crew[bot] f2e1bec689 test(e2e): close recent release coverage gaps (#7441)
* test(e2e): close recent release coverage gaps

* test(e2e): provide hermetic ffmpeg binaries

* test(e2e): own ACP refusal fixture

* test(e2e): satisfy resource layout guard

* test(e2e): stabilize ACP and desktop regressions

---------

Co-authored-by: Amy <1184569493@qq.com>
2026-08-27 08:18:50 +00:00
lefarcen d85e78ab7d fix(runtimes): reach a working agent CLI past a broken shim (#7153)
* fix(runtimes): reach a working agent CLI past a broken shim

Resolving an agent's name on PATH only proves a file exists there, never
that it runs. Detection stopped at the first hit, so a wrapper orphaned by
a half-finished `npm i -g` shadowed a perfectly good CLI of the same name
in a later search directory — and because the not-invocable branch dropped
the resolved path, the picker (which renders an unavailable agent only when
it carries one) hid the agent entirely. The user saw "scan found nothing"
with no way to act.

Detection now walks past candidates that cannot be executed, and reports
the path it tried when every candidate fails, so the row stays actionable.

Also on this chain:

- `@open-design/dsh-runtime` pinned its DeepSeek Harness peers to one exact
  release candidate, so it became uninstallable the moment upstream shipped
  the next one. Peers now take ranges. `scripts/guard.ts` gains a narrow,
  logged exemption for external-host plugin manifests — their peers describe
  a host this repo neither installs nor controls, which is the one case
  where an exact pin is wrong rather than reproducible.
- The DSH installers pinned only the entry package while its ~190 siblings
  used caret ranges over prerelease floors. Once a newer release candidate
  existed, the transitive tree floated onto it and its mutually exclusive
  peer sets sent npm into an ERESOLVE backtrack that never converged. Both
  installers now freeze the resolution window alongside the pin.
- The rescan notice counted agents the list never renders, so it announced
  "3 available" above two rows.

* fix(runtimes): make detection's chosen executable the one spawn sites use

The candidate walk taught detection to skip a binary it cannot execute, but
every other resolution — chat, the connection test, the memory summariser,
companion install — called `resolveAgentLaunch` with no skip list and got
back the first file that merely exists on PATH: the broken shim detection
had just rejected. Settings advertised the agent as installed while each
turn exec'd the wrapper, so the fix only ever reached the picker.

Detection is the only stage that spawns anything, so it is the only one that
learns which candidate works. It now publishes that winner, and resolution
prefers it over a fresh first-hit walk. An explicit `*_BIN` override and a
packaged built-in still outrank it, a winner whose file has since vanished
is ignored, and detection clears the entry before each pass so a rescan
after a repair never resolves against a stale one.

This restores the invariant the file already documented — detection probes
the exact path the runtime will spawn — which the walk had broken.

Reported by @mrcfps in review.

* fix(runtimes): keep the remembered executable inside the live candidate set

The winner map made resolution stop being a pure function of the current
environment. It only checked that the remembered file still existed, so a
path learned in a richer environment survived an emptied PATH, a sandboxed
OD_AGENT_HOME, and any other narrowing of the search — resurrecting a binary
the caller could no longer see.

That is not just a test artifact: `GET /api/integrations/vela/status` must
answer `amr-runtime-unavailable` when the vela binary cannot be resolved, and
it started answering "ready" instead, because an earlier detection in the
same process had remembered a path.

The memory now reorders candidates rather than introducing them: it applies
only when the live search already offers the remembered path. An emptied
PATH, a sandbox, and an uninstalled CLI all keep meaning "not found", while
a broken shim shadowing a working binary is still resolved to the winner.

This also settles the scope question raised in review — no separate cache key
is needed, because the candidate set already carries the resolution scope.

* fix(runtimes): treat a launcher whose target is gone as not invocable

A stale global npm wrapper on Windows is a `.CMD`, which OpenDesign runs
through `cmd.exe /d /s /c`. When the package behind it has been
uninstalled, cmd.exe starts, node starts, and only then does node fail to
load the script the wrapper names — a plain exit 1 with MODULE_NOT_FOUND
on stderr. None of the spawn-level signals the classifier looked for
(ENOENT / EACCES / ENOTDIR, exits 126 and 127) can occur along that
chain, so detection read the dead wrapper as a healthy CLI that merely
disliked `--version`, stopped the candidate walk on it, and never reached
the working `dsh.CMD` later on PATH.

Classify invocability from the launcher's own vocabulary as well: cmd.exe's
9009 (the Windows analogue of POSIX 127), node's missing-module report, and
cmd.exe / PowerShell's missing-command and missing-path reports. Exit status
alone still means nothing — a CLI that runs and exits non-zero is a real
answer from the right binary and keeps its place in the walk.

Drop the win32 early return from every case in the fallback suite and write
fixtures per platform, so `dsh.CMD` resolution through PATHEXT and the
cmd.exe wrapping are exercised natively. The orphaned-wrapper regression is
reproduced on every platform — the defect is a classification bug, not an OS
API one, and the daemon suite only runs on Linux in CI.

* fix(runtimes): carry forward the dead paths, not detection's winner

Detection published the candidate it settled on and resolution preferred
it over the first live PATH hit. That pinned the daemon to one binary for
its whole lifetime and let it silently outrank the caller's own PATH: a
CLI that became visible earlier afterwards — a fresh install, a version
manager swapping shims, a caller resolving under its own environment —
could no longer be reached.

The daemon suite caught it on a machine that has a real `vela` installed:
`chat-route` and `connection-test` inject a fake agent CLI ahead of PATH
per case, and the remembered winner outranked it, so 15 cases spawned the
machine's real CLI instead of the fixture. CI never saw it because its
runners have no such CLI to remember.

Carry the proven-dead paths forward instead. Skipping them keeps every
candidate still standing in plain PATH order, while chat, the connection
test, the memory summariser, and companion setup still avoid the shim
detection rejected — which is the whole point of publishing anything.

* docs(runtimes): say why detection keeps its own launcher-error list

`isCliNotInstalledText` in run-failure-classification.ts reads similar
output, and the next reader will reasonably ask why the two are not one.
They answer different questions: that one buckets a finished run for
telemetry, where over-matching costs a mislabelled event, while this one
decides whether to abandon a binary mid-resolution, where over-matching
sends a working CLI's user to a different install.

* fix(runtimes): accept the dsh release line, not one release candidate

This branch moved the installers to 0.1.0-rc.8 and left the agent def
naming 0.1.0-rc.6 as the only supported version, so following our own
one-line install produced "0.1.0-rc.8 has not been tested with this
OpenDesign build" in Settings on a clean machine. That is the same
pinning defect the installers had, one layer up.

`dsh` publishes a stream of release candidates whose sibling packages
peer-require each other, so the version our installer hands the user
moves with upstream. Version policies can now declare the release line
they accept alongside the exact versions exercised, and DeepSeek Harness
declares its own. A version off that line still warns — the point is to
stop pinning, not to stop checking.

Add a cross-boundary guard for the drift itself: the installers and the
agent def cannot import each other, so `e2e/tests` reads both and fails
when an installer pins a version the daemon would not accept.

* docs(contracts): correct what shim-broken now means

The reason's docstring said "exit 126/127", which stopped being the whole
story once detection learned to read a Windows wrapper's stderr: a `.cmd`
starts an interpreter successfully and reports the missing target there,
so no POSIX exit code appears at all.

* test(runtimes): cover the launcher signatures no fixture can produce

`launcherTargetMissing` recognises six ways a launcher says it never
reached its target, and until now two of them had any coverage — the two
a POSIX shell can print. cmd.exe's wording, PowerShell's, and exit 9009
were load-bearing for the Windows fix and untested anywhere.

A fixture cannot close that gap. POSIX masks exit statuses to eight bits,
so a shell asked for 9009 reports 49, and /bin/sh does not speak cmd.exe.
The daemon suite runs on Linux in CI, so driving the classifier directly
is the only way the merge gate can catch a typo in a signature the fix
depends on for the platform the bug came from.

Extract the catch arm verbatim as `classifyVersionProbeFailure` — same
branches, same order, no behaviour change — and feed it the rejection
shape `promisify(execFile)` actually produces. Verified by mutation:
deleting the 9009 branch reds one case, deleting the stderr branch reds
six.

The suite also pins the constraint from the review by name: exit 1 with an
ordinary argument complaint, with no stderr, or with wording that belongs
to run-failure telemetry ("not installed") all stay `spawned`. That last
one is why detection keeps a narrower list than `isCliNotInstalledText`.
2026-08-24 13:04:31 +00:00
open-design-crew[bot] 50c15417a9 fix(web): enable completion feedback by default (#7038)
Enable completion sound and desktop notifications for fresh configurations while preserving saved opt-outs. Activate browser audio and permission flows only from the first real task submission gesture.

Co-authored-by: Cheems <94773058+itscheems@users.noreply.github.com>
2026-08-18 09:42:37 +00:00
PerishFire f9fe73c836 feat: add DeepSeek Harness support (#6874)
* feat(daemon): scaffold DeepSeek Harness profile protocol

* feat(daemon): integrate DeepSeek Harness profile runtime

* fix(daemon): preserve headless prompts on native resume

* refactor(daemon): model Harness profile resume explicitly

* fix(daemon): resume Harness sessions after cancel

* test(daemon): verify DeepSeek Harness Windows carrier

* test(daemon): reap DeepSeek Harness Windows process tree

* feat(agents): surface DeepSeek Harness models and branding

* fix(daemon): surface Harness credential guidance

* feat: add bundled DeepSeek Harness setup

* fix: link DeepSeek Harness installer

* fix: link official Harness docs

* fix(daemon): distinguish Harness companion install

* test(pack): verify bundled Harness resources on Windows

* fix: harden Harness profile turn lifecycle

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

* fix(dsh-runtime): stop cancelled restore follow-ups

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

* fix(daemon): cancel Harness startup before execute

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

* fix(pack): build DeepSeek runtime on cold cache

---------

Co-authored-by: lefarcen <935902669@qq.com>
Co-authored-by: Looper <looper@noreply.github.com>
2026-08-14 02:07:55 +00:00
Nicholas-Xiong 5127ad188b fix: add Kiro to agentLabels.ts (AGENT_LABELS + AGENT_ALIASES) (#5085)
* fix: add Kiro to agentLabels.ts (AGENT_LABELS + AGENT_ALIASES)

- Add kiro to AGENT_LABELS so exactAgentDisplayName('kiro') returns 'Kiro'
- Add 'kiro cli' and 'kiro-cli' aliases to AGENT_ALIASES
- Kiro icon (kiro.svg) already exists, this was purely a missing label mapping

Closes #5081

* test: pin Kiro label and alias resolution in agentLabels utility

PerishCode's review on PR #5085: mapping change is merge-safe but the
new behavior should be pinned in the existing utility test matrix.

Added 4 tests covering:
- agentDisplayName resolves 'kiro cli' → 'Kiro' (alias)
- agentDisplayName resolves 'kiro-cli' → 'Kiro' (alias)
- exactAgentDisplayName returns 'Kiro' for exact id 'kiro' (canonical label)
- exactAgentDisplayName returns null for 'kiro cli'/'kiro-cli' (aliases are
  not themselves agents — substring aliases stay routed via agentDisplayName
  and do not pollute the exact-id table)

Locks the fix from db025410 and prevents regression.

* test: update kiro alias assertions to match exactAgentDisplayName behavior
2026-07-13 07:59:21 +00:00
PerishFire 32011bdfcc [codex] Route BYOK API providers through OpenCode (#4876)
* feat(byok): route API providers through OpenCode

* test(web): align BYOK OpenCode expectations

Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex)

* fix(byok): preserve OpenCode media defaults

Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex)

* fix(byok): forward OpenCode media defaults

Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex)

* test(e2e): align API empty stream smoke with daemon runs

Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex)

* fix(byok): guard OpenCode API reroute

Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex)

* test(e2e): expose BYOK OpenCode in API smoke

Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex)

* fix(byok): preserve Azure and Ollama provider contracts

* fix(byok): preserve media default fallbacks

* fix(byok): keep api history for opencode routing

* fix(daemon): alias byok-opencode detection env

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

* fix(daemon): isolate BYOK provider config

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

* fix(web): keep Bedrock BYOK unsupported client-side

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

* fix(byok): allow keyless local Ollama OpenCode runs

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

* fix(web): label remaining BYOK OpenCode agents

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

* fix(daemon): reject Azure BYOK without base URL

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

* fix(byok): preserve keyless vLLM OpenCode runs

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

* fix(byok): omit auth for keyless OpenCode providers

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

---------

Co-authored-by: Siri-Ray <2667192167@qq.com>
Co-authored-by: Looper <looper@noreply.github.com>
2026-07-03 09:18:08 +00:00
Ethan Guo b3e338ddb2 test(web): cover zip encoder, design-system audit, chat-events, mentions, utils (#2451)
* test(web): cover stored-mode ZIP encoder CRC and central directory

* test(web): cover design-system audit repair-actions and chat-events dedup

* test(web): cover @mention parsing and agent label normalization

* test(web): cover error flattener, prompt-template categories, design-system sentinel

* fix(web): set ZIP UTF-8 filename flag (bit 11) for non-ASCII names

buildZip wrote UTF-8 filename bytes (TextEncoder) but left the
general-purpose bit flag at 0 in both the local and central headers, so
readers fall back to CP437 and mis-decode non-ASCII names like 日本.md.

Set general-purpose bit 11 (0x0800, EFS) in both headers when the
filename contains a non-ASCII byte; pure-ASCII archives are unchanged
(ASCII == CP437 == UTF-8), matching how Python/Go zip encoders behave.

Strengthen the UTF-8 test to assert the flag + filename in BOTH the
local and central-directory headers (parsed via EOCD), and add a case
asserting pure-ASCII names do not set the flag.

Addresses review feedback on #2451.
2026-07-02 09:29:28 +00:00
Caprika f2fef8d9a5 [codex] fix prompt title summaries (#4348)
* fix prompt title summaries

* fix ui p0 title expectations

* use selected agent for title suggestions

* strip generated titles across streams
2026-06-15 15:32:29 +00:00
elihahah666 84e2e3e056 feat(web): ease the Home scroll-back instead of snapping to top (#4043)
Routing a plugin from the detail modal (Use / Use with query) returns
the user to the Home composer by snapping the scroll container to 0 in
a single frame; stacked on the closing modal it reads as a stutter.
Replace both jump sites (HomeView.scrollHomeToTop and EntryShell's
prompt-handoff effect) with a shared smoothScrollToTop helper that
tweens scrollTop with the house ease-out cubic-bezier(0.23, 1, 0.32, 1),
scales duration with distance (260-600ms), yields to user wheel/touch
input mid-flight, retargets instead of stacking on re-entry, and jumps
instantly under prefers-reduced-motion.

Co-authored-by: qiongyu1999 <2694684348@qq.com>
2026-06-10 06:28:36 +00:00
elihahah666 8920577f43 feat(web): simplify Home composer (drop plugin-inputs form & example pill, move Design Agent next to Send) (#3645)
* feat(web): simplify Home composer (drop plugin-inputs form & example pill, move Design Agent next to Send)

- Remove the inline plugin-inputs form (ARTIFACT KIND / AUDIENCE / TEMPLATE) from the Home composer; required inputs no longer gate Send (defaults still flow to the backend).
- Remove the dismissible "selected example" pill above the editor; picking an example still seeds the prompt.
- Move the Design Agent toggle from the footer-left group to sit directly left of the Send button.

* fix(web): keep client-side required-input gate on Home composer

Dropping the inline plugin-inputs form left pluginInputsAreValid() always
returning true, so a required field with no default that the user blanks out in
an editable Home scenario kept Send enabled and only failed at click-time apply
with a generic "Failed to apply …" error (the daemon's validateInputs rejects
missing required inputs).

Restore a client-side gate that mirrors the daemon's required-input rule and
names the missing field in the submit error, so these Home flows fail fast.
Extracts the pure check into utils/pluginRequiredInputs with unit coverage.

* fix(web): drop example from active-row mount condition

With the example pill removed from the Home composer active row, keeping
selectedPromptExample in showActiveContextRow mounted an empty .home-hero__active
row when an example was picked with no other active context. Remove it from the
condition so the row only renders when it has visible content.

---------

Co-authored-by: qiongyu1999 <2694684348@qq.com>
Co-authored-by: lefarcen <935902669@qq.com>
2026-06-04 14:08:40 +00:00
elihahah666 7ab1e6eff2 feat(composer): reconcile context with prompt + per-connector brand color (#3625)
* feat(composer): reconcile context with prompt + per-connector brand color

Two increments on top of the Lexical composer (#3516):

- Filter contextPlugins/contextMcpServers/contextConnectors at submit time
  by whether the entity's @mention still survives in the prompt text. When a
  user deletes a mention pill, that plugin/MCP/connector is no longer sent to
  the agent. Adds mentionTokenPresent() to inlineMentions.

- Give connector mention pills a per-connector brand color (Notion black,
  Figma purple, etc.) by setting an inline --m-hue in MentionNode.createDOM,
  reusing the existing CSS hue machinery instead of the shared connector green.
  Adds a curated brand-color map with a hashed fallback.

* fix(composer): keep mention context when punctuation trails the pill

mentionTokenPresent() reused the parser's strict isMentionRightBoundary(),
so a still-visible atomic pill serialized as `@Slack,` was treated as
deleted and dropped from contextPlugins/contextMcpServers/contextConnectors
at submit time. Add a submit-time boundary that also accepts trailing
clause/sentence punctuation, leaving the parser invariant untouched, plus a
HomeView regression for the punctuation case.

Generated-By: looper 0.8.1 (runner=fixer, agent=claude-code)

* fix(composer): make connector pill brand hue theme-aware

Near-black curated brand colors (Notion, GitHub, Slack) rendered the
connector mention pill text as dark-on-dark in the app's dark theme,
since chat.css derives the text as color-mix(--m-hue 72%, --text) and
--text is light in dark mode. connectorBrandColor now takes the live
theme and lightens hues below a luminance floor so they stay legible,
while bright brands (Figma, Linear) pass through unchanged.

Generated-By: looper 0.8.1 (runner=fixer, agent=claude-code)

* fix(composer): make connector pill hue follow live theme switches

Recompute a mounted connector pill's brand --m-hue when <html data-theme>
or the OS prefers-color-scheme changes, not only on node create/update, so
a pill inserted in light mode no longer keeps its near-black hue (and
dark-on-dark text) after a live switch to dark mode.

Generated-By: looper 0.8.1 (runner=fixer, agent=claude-code)

* feat(composer): collapse footer tools into one "+" menu; drop duplicate context badges

- Replace the separate "@" context-trigger and paperclip attach buttons with a
  single "+" button. Its popup offers Attach files plus Connectors/Plugins/MCP
  right-side flyout submenus (Plugins/MCP searchable + scrollable). Picking an
  entry inserts the inline @mention pill via the existing pickX handlers; "Add …"
  rows route to the plugins / integrations views.
- Stop rendering the top plugin/connector/MCP context-badge row — those now live
  as inline mention pills in the composer, so the badges were duplicates. Staged
  file chips still render there. showActiveContextRow no longer keys off the
  selected*Contexts arrays.
- New i18n keys (addMenu/addPlugin/addConnectors/addMcp/noPlugins/noMcp/
  noConnectors) in types.ts + en + zh-CN; other locales inherit via ...en.

* fix(composer): repair collapsed-menu test selector and complete addMenu i18n

Update the HomeView context-picker test to open the plus menu before
asserting the attach affordance, since the collapsed footer now renders
home-hero-plus-attach only after home-hero-plus-trigger is clicked.

Add the seven home-hero add-menu keys (addMenu, addPlugin, addConnectors,
addMcp, noPlugins, noMcp, noConnectors) to every locale beyond en/zh-CN so
the i18n contract holds across all 18 locale files.

Generated-By: looper 0.8.1 (runner=fixer, agent=claude-code)

* test(web): drop assertions on removed context-badge row

The PR removed the duplicate plugin/connector/MCP context-badge row
(context now renders as inline @mention pills), but four HomeView tests
still asserted the dropped `home-hero-context-plugin-*` testids and went
red. Re-anchor them to behavior that survives: the submit payload's
`contextPlugins`/prompt-pill text for picker flows, and the active
context row's resolved-count label for empty-prompt "Use" handoffs.

Generated-By: looper 0.8.1 (runner=fixer, agent=claude-code)

* test(web): align entry-chrome e2e with collapsed footer menu

The footer-collapse change removed the always-visible home-hero-attach
control and the plugin/MCP/connector context-badge row, but the entry
chrome Playwright suite still asserted those removed surfaces, failing
Browser tests (and the Validate workspace aggregate).

- Assert home-hero-plus-trigger instead of the removed home-hero-attach.
- Drop the home-hero-context-plugin-* badge assertions; plugin context
  no longer renders a visible badge. Coverage is preserved by the
  existing prompt-text and submit-payload (pluginId) assertions.

* fix(composer): exclude inline-mentioned contexts from active row; reset plus-menu query per submenu

Gate the Home active context row on contexts that have no inline @mention
representation, so selecting only an inline-mentioned connector no longer mounts
an empty row above the editor. Reset the shared plus-menu search query when the
active submenu changes so a stale plugin search no longer filters the MCP list
into a false "No MCP servers" empty state.

Generated-By: looper 0.8.1 (runner=fixer, agent=claude-code)

* fix(composer): keep context-only Use selections in submit payload

The submit-time reconciliation dropped every selected context that lacked an
inline @mention token, which silently discarded contexts staged through the
plain Use action (it never writes a token). The agent then received no
contextPlugins even though the user explicitly chose the plugin as context.

Tag each selection with inlineBacked: contexts inserted as @mention pills are
only sent while their token survives in the prompt; context-only Use selections
stay in the payload until explicitly cleared. The active context row likewise
counts only context-only selections, so an inline-mentioned-only pick no longer
mounts an empty row. Adds a regression covering the direct Use submit path.

* fix(composer): render removable chips for context-only Use selections

Context-only selections (staged via the plain Use action, no inline @mention
pill) are kept in the submit payload, but the home composer no longer rendered
any chip for them, so they were invisible and unremovable — a kept context
would silently drift into later freeform prompts.

Render a removable chip in the active context row for each context-only
plugin/MCP/connector selection, wired to the existing remove handlers. Adds a
regression that stages a plain Use context, clears it via the chip, and asserts
the submit payload no longer includes it.

---------

Co-authored-by: qiongyu1999 <2694684348@qq.com>
Co-authored-by: lefarcen <935902669@qq.com>
2026-06-04 12:55:30 +00:00
luden 3a21392c46 Fix Electron composer lag and stale generation failures (#3593)
* Keep chat typing and web CSS resolution responsive

Cache the normalized mention index by entity array and scan only concrete @ positions so the composer does not rescan every file/plugin/skill token on each draft update. Link the shared components package at the root importer so Turbopack/PostCSS can resolve the exported component stylesheet from the web CSS entrypoint.

Constraint: Chat composer typing updates run on every keystroke in Electron, and tools-dev/Next evaluates global CSS from the workspace root.
Rejected: Debouncing textarea state updates | would make text entry feel stale and would not remove the expensive synchronous parser path.
Rejected: Relative-importing package source CSS from apps/web | would bypass the package export contract and hardcode package layout into the app stylesheet.
Confidence: high
Scope-risk: narrow
Directive: Keep inline mention parsing proportional to draft mention markers, and keep package CSS imports resolvable through workspace package links.
Tested: pnpm install
Tested: pnpm --filter @open-design/web test -- tests/utils/inlineMentions.test.ts
Tested: pnpm --filter @open-design/web typecheck
Tested: pnpm --filter @open-design/web build
Tested: pnpm guard
Not-tested: Full web Vitest remains blocked by unrelated CSS selector tests observed before this commit.

* Keep composer typing off the render path

Move keystroke-time draft updates into the textarea/ref path while deferring React draft rendering, mention overlay work, picker filtering, and localStorage persistence so Electron input echo stays within frame budget.

Constraint: Electron chat input was visibly lagging for plain text and mention text while the right iframe stayed responsive.

Rejected: Keep the textarea fully controlled | every keystroke still synchronizes the largest ChatComposer render path.

Confidence: high

Scope-risk: moderate

Directive: Keep prompt submission, slash commands, and mention insertion reading draftRef when draft rendering is deferred.

Tested: pnpm guard; pnpm --filter @open-design/web typecheck; pnpm --filter @open-design/web build; pnpm exec vitest run -c vitest.config.ts tests/components/ChatComposer.infinite-render.test.tsx; pnpm exec vitest run -c vitest.config.ts tests/components/ChatComposer.context-pickers.test.tsx; pnpm exec vitest run -c vitest.config.ts tests/utils/inlineMentions.test.ts; Electron inspect eval plain text avg 0.03ms max 0.5ms; Electron inspect eval @ path avg 0.03ms max 0.1ms; HTTP smoke status 200 with no components CSS resolve error.

Not-tested: Full web Vitest suite and Playwright UI suite.

* Keep Electron chat input responsive under heavy projects

Constraint: Electron project views can carry large chat/file DOM and Chromium can throttle or defer visible renderer work under tab/window visibility edge cases.
Rejected: Re-rendering the composer draft on every plain keystroke | it couples textarea input to mention overlay, resize, storage, and project layout work.
Confidence: medium
Scope-risk: moderate
Directive: Keep plain text input on the textarea/ref path; only commit React draft state immediately for mention, slash, chip, or imperative draft mutations.
Tested: pnpm guard; pnpm --filter @open-design/web typecheck; pnpm exec vitest run -c vitest.config.ts tests/components/ChatComposer.infinite-render.test.tsx; pnpm --filter @open-design/desktop test; pnpm --filter @open-design/desktop typecheck; desktop inspect actual project composer with OS SendKeys.
Not-tested: Push/PR remains blocked by GitHub 403 for mimicryluden on nexu-io/open-design.

* Keep heavy project typing isolated from preview layout

Constraint: Electron project panes can carry large DOM histories and preview frames that make textarea paint sensitive to parent layout.

Rejected: Further debouncing ChatComposer input state | app-side handler costs were already low while native textarea updates still lagged.

Confidence: high

Scope-risk: moderate

Directive: Keep the composer mounted through the fixed layer/slot pair so future layout changes do not pull typing back into the split-pane flex reflow path.

Tested: pnpm --filter @open-design/web typecheck; pnpm exec vitest run -c vitest.config.ts tests/components/ChatComposer.infinite-render.test.tsx tests/components/ChatComposer.context-pickers.test.tsx; pnpm guard; pnpm --filter @open-design/web build; desktop inspect fixed-layer DOM and SendKeys probe avg beforeinput->input 1.4ms max 2.5ms

Not-tested: PR publish blocked by GitHub repository permissions if remote still rejects mimicryluden

* Keep stale run recovery from hiding generated previews

Constraint: Completed daemon runs can outlive the in-memory run registry after a restart.

Rejected: Replaying succeeded rows with empty producedFiles | It turns completed messages into failed messages when the registry is gone.

Confidence: high

Scope-risk: narrow

Directive: Only active run statuses should enter daemon reattach recovery.

Tested: vitest generation-preview; vitest ProjectView reattach restore; web typecheck; pnpm guard; web build; desktop DOM eval failureText=false iframeCount=2

Not-tested: Fresh provider generation against an external model

* Keep composer contracts aligned with latest main

Rebase conflict resolution restored the current composer contract while leaving the lower-risk latency fixes in separate layers.

Constraint: The branch was rebased over newer workspace/session context composer contracts.

Rejected: Keeping the older composer conflict side because it removed current ChatComposer props and broke web typecheck.

Confidence: high

Scope-risk: narrow

Directive: Preserve upstream ChatComposer contracts when rebasing input-latency work.

Tested: web typecheck; targeted ChatComposer, inlineMentions, ProjectView generation-preview tests; desktop window-chrome test

Not-tested: Full browser interaction after fork push

* Fix daemon Nix workspace symlink pruning

---------

Co-authored-by: Siri-Ray <2667192167@qq.com>
2026-06-04 10:47:44 +00:00
Sid 1d6e8b93b0 Reduce chat composer work during plain typing (#3466)
Mac desktop users reported severe input lag once a conversation had history. The composer was still doing synchronous resize work and full mention-entity normalization on every ordinary keystroke, so the cost scaled with DOM/layout complexity even when the user was just typing plain text.

Constraint: Keep the chat composer controlled and preserve existing @-mention overlay behavior
Rejected: Virtualize chat history in this PR | broader rendering change than needed for the immediate input-lag path
Confidence: medium
Scope-risk: narrow
Directive: Keep plain-text keystrokes off synchronous layout and full mention matching paths
Tested: pnpm exec vitest run -c vitest.config.ts tests/components/ChatComposer.infinite-render.test.tsx tests/utils/inlineMentions.test.ts
Tested: pnpm --filter @open-design/web typecheck
Tested: git diff --check
Not-tested: lint unavailable because @open-design/web has no lint script
Related: #3461
2026-06-02 07:54:01 +00:00
estelledc 1a6face04c fix(web): prune draft tokens when the plugin chip strip clears (#2881) (#3356)
ChatComposer tracks the `@…` tokens this surface authored via the
@-mention popover plugin-pick path. When PluginsSection's chip strip
clears, we wire its `onCleared` and prune *only* those tracked
insertions from the draft so the textarea no longer holds orphaned
styled mentions whose chips just unmounted.

Architecture summary (rounds 1–9 collapsed; round 10 detailed below):

  - `Array<{token, start, pluginId, insertionId?}>` tracking with
    start offsets reconciled across each keystroke via an LCS+LCP
    edit-range diff in
    `apps/web/src/utils/pluginInsertionTracking.ts` (round 3-4).
    `insertionId` is forwarded by `reconcileInsertions` so the
    producer can locate its own entry across reconciles
    (round 10).
  - All draft mutations route through a single `updateDraft`
    chokepoint that runs `reconcileInsertions` outside the
    `setDraft` updater so React StrictMode's double-invoke is
    harmless (round 4-5).
  - Boundaries delegate to the shared
    `inlineMentions.isMentionBoundary` /
    `inlineMentions.isMentionRightBoundary` helpers so the
    tracker can never diverge from the parser (round 5).
  - `setActivePlugin` is a chokepoint for every applyById path,
    filtering tracked entries to those matching the new active
    plugin so a replace-plugin flow can never let stale entries
    survive (round 6).
  - Picker rollback double-snapshots draft + tracker so apply-
    failure restores the tracker but only rewrites the draft
    when no user keystrokes arrived during the await
    (round 7-8).
  - `stripPluginInsertedTokens` collapses whitespace seam-local
    so user-authored multi-space spans elsewhere are preserved
    (round 8).
  - `setActivePlugin` is deferred past `await applyById` on
    every path, and `onCleared` filters by
    `pluginsSectionRef.current?.getActiveRecord()?.id` so a
    pending-window clear scopes to the actually-mounted
    plugin's tokens (round 9).

race in the picker rollback:

  Round 9 made `onCleared` mutate the tracker and the draft when
  it ran during a pending replace, and added the `getActiveRecord`
  filter so the strip targets the still-mounted plugin's entries
  only. The picker's failure-path rollback, however, still
  restored `prevEntries` / `prevActiveId` wholesale — assuming
  nothing else had touched the tracker during the await. If the
  user clicked the still-mounted original chip's × during the
  pending replace AND the deferred `applyById` then resolved
  with a 500, the wholesale restore (a) resurrected entries that
  `onCleared` had legitimately stripped (now stale offsets) and
  (b) left the optimistic `@<target>` orphaned in the draft with
  no chip ever having mounted — the original #2881 symptom
  recurring inside the failure window.

  Fix splits the failure rollback into two paths:

  1. **Detect "intervening clear" via `activePluginIdRef.current
     === null && prevActiveId !== null`.** `onCleared` always
     nulls the active id as its last action; our deferred
     `setActivePlugin` never ran in the failure branch. So the
     null-while-prev-not-null state is the smoking gun for an
     intervening clear during the await.

  2. **On detection, surgically remove only our optimistic
     entry and only its `@<target>`.** Locate the entry by
     `insertionId` (added to `TrackedInsertion` as an optional
     field, forwarded by `reconcileInsertions` so the id
     survives offset shifts) — this disambiguates the case
     where the user picked the same plugin from the @-popover
     more than once during the await window. Splice that entry
     out and run `updateDraft((d) => stripPluginInsertedTokens(
     d, [ourEntry]))` so the draft loses `@<target>` and any
     remaining tracked entries (the in-flight target would have
     no others, but a co-pending second pick could) get their
     offsets reconciled. `activePluginIdRef` stays at `null` —
     `onCleared`'s truth, since no chip is mounted.

  The "no intervening clear" branch is the round 7/8 path:
  restore `prevEntries`/`prevActiveId` wholesale and rewrite
  the draft only if `draftRef.current === postInsertDraft`
  (no user keystrokes during the await).

Regression coverage (additions):

  - `apps/web/tests/components/ChatComposer.plugin-clear-prunes-draft.test.tsx`
    — 18 integration specs total (17 prior + 1 new round-10):
    * `@-popover pick A → @-popover pick B (apply pending) →
      clear A's chip → resolve B with 500 → assert no orphan
      @<target>, no orphan @A, no chip mounted, no stale
      tracker entries`. Uses a deferred `Promise<Response>` so
      the apply stays in flight while the chip-clear is fired,
      then resolves with a 500 to drive the failure path. Pre-
      fix this would resurrect Airbnb's stale entry AND leave
      `@SecondPlugin` orphaned in the draft.

PluginsSection.tsx is unchanged. The host-local tracking +
draft-update chokepoint + parser-aligned boundaries + deferred
active-plugin scoping + transactional applyById + intervening-
clear-aware rollback + filtered `onCleared` keep the cross-
component contract identical to main — only ChatComposer touches
behavior, plus the utils module and two `inlineMentions` exports.

Validation:
  - pnpm exec vitest run tests/utils/pluginInsertionTracking.test.ts → 36/36 passed
  - pnpm exec vitest run tests/components/ChatComposer.plugin-clear-prunes-draft.test.tsx → 18/18 passed
  - pnpm exec vitest run -c vitest.config.ts (full apps/web suite, 228 files) → 2202/2202 passed
  - pnpm --filter @open-design/web typecheck → green
  - pnpm guard → green
2026-05-30 17:16:24 +00:00
lefarcen 77b865e4d1 fix(web): handle dropped file read failures (#2858) 2026-05-25 06:05:17 +00:00
Eli 4376d8a8ec [codex] Add pet task center and desktop pet (#1833)
* feat: add pet task center and desktop pet

* Fix pet task center review regressions
2026-05-19 15:38:39 +08:00
Sid 8b0625aa6f fix(web): unbreak Create button on plain HTTP / LAN-IP deployments (#849) (#900)
`crypto.randomUUID()` is restricted to secure contexts (HTTPS or
`localhost`), so when Open Design is served over plain HTTP on a
LAN IP — the standard Docker / unRAID / NAS self-hosted setup,
e.g. `http://192.168.1.10:17573` — Chromium silently makes the
function undefined. Calls then throw
`TypeError: crypto.randomUUID is not a function`, which the
`try/catch` around `createProject()` swallows by returning `null`,
which the click handler reads as "no project, do nothing". The
Create button effectively becomes a silent no-op for every LAN-IP
user (issue #849, also reported as #394).

Centralize the call into a new `apps/web/src/utils/uuid.ts` helper
with a three-tier fallback per @lefarcen's review:

  1. `crypto.randomUUID()` — secure-context happy path, native and
     cryptographically random.
  2. `crypto.getRandomValues()` + RFC 4122 §4.4 byte layout — still
     available in non-secure contexts since the Web Crypto API is
     not gated by `isSecureContext`. Yields a real v4 UUID with
     crypto-quality entropy.
  3. `Math.random()` — last-resort polyfill for environments
     missing both, kept because the IDs we generate (project ids,
     message ids, client request ids) are scoped to a single
     user's local browser session — cryptographic uniqueness
     isn't required, just enough entropy to avoid collisions.

Replace all four `crypto.randomUUID()` callsites confirmed in
@lefarcen's audit:

- `apps/web/src/state/projects.ts:48` (createProject id)
- `apps/web/src/components/ProjectView.tsx:986` (user message id)
- `apps/web/src/components/ProjectView.tsx:1013` (assistant message id)
- `apps/web/src/components/ProjectView.tsx:1263` (daemon stream
  clientRequestId)

with calls to the new `randomUUID()` helper.

Tests: 6 new tests in `apps/web/tests/utils/uuid.test.ts` cover
each fallback tier, RFC 4122 v4 format validation (regex + explicit
version/variant nibble checks), the explicit "doesn't throw when
`crypto.randomUUID` is undefined" assertion that pins the #849
root cause, and a 1000-iteration uniqueness check on the
`getRandomValues` path.

Verified locally:
- web vitest: 522/522 (was 516, +6)
- web `tsc -b --noEmit` clean
- `tsx scripts/i18n-check.ts` passes
2026-05-08 16:50:59 +08:00
mamba 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
PerishFire bbdd4e84b5 chore: enforce test directory conventions (#496)
* chore: enforce test directory conventions

Move package, app, and tool tests out of src and add guard enforcement so source directories stay source-only.

* ci: use guard and package-scoped tests

Run the new repository guard in CI and keep test execution aligned with package-scoped commands after removing root aliases.

* ci: align stable release guard check

Use the new repository guard in stable release verification after replacing the residual-JS-only script.

* chore: tighten test layout enforcement

Enforce sibling tests directories, typecheck moved test suites with dedicated configs, and refresh remaining guidance that pointed at src-based tests.

* chore: clarify no-emit test tsconfigs

Explicitly disable declaration-only emit in test tsconfigs so review tooling sees they are no-emit typecheck configs.
2026-05-05 15:34:22 +08:00