mirror of
https://github.com/nexu-io/open-design.git
synced 2026-10-27 09:51:12 +01:00
codex/design-system-github-handoff
53
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
1523defad1 |
feat(web): add plugin registry detail drawer (#2087)
Co-authored-by: multica-agent <github@multica.ai> |
||
|
|
0026dfa344 |
fix: support CODEX_API_KEY for Codex CLI (#1880)
* fix(daemon): allow Codex API key env * fix(web): expose Codex API key setting * fix(web): clarify Codex key labels |
||
|
|
bac56415a2 |
fix(web): surface daemon error messages for invalid folder imports (#1923)
* fix(web): surface daemon error messages for invalid folder imports importFolderProject() was swallowing non-2xx responses by returning null, so the UI could only show a generic "Open folder failed: <path>" message even though the daemon already returns specific errors like "cannot import the filesystem root" or "folder not found". Parse the daemon error body and throw so the panel displays the actual reason. Also show feedback for empty path input instead of silently returning. Fixes #1186 * test(web): update folder import test to match new error propagation The existing test expected a generic "Open folder failed: <path>" message from a boolean return. Update to match the new behavior where the daemon's error message is thrown and displayed directly. |
||
|
|
76defffb93 |
Garnet hemisphere (#1702)
* feat(chat-composer): enhance mention handling and input overlay - Introduced a new overlay for inline mentions in the chat composer, improving user experience by visually indicating mentions as users type. - Updated the `ChatComposer` component to manage mention entities and integrate them into the input field, allowing for better context and interaction. - Enhanced the `AssistantMessage` component to support the display of plugin action panels based on the current project context, facilitating easier plugin management. - Refactored related components to ensure consistent handling of project files and mentions across the application. This update significantly improves the chat interaction model, making it more intuitive for users to engage with mentions and plugins. * feat(plugin-management): enhance plugin action panels and UI components - Updated the `AssistantMessage` component to include plugin action panels based on the latest project context, improving user interaction with generated plugins. - Refactored the `PluginsView` to support detailed views for available marketplace entries, allowing users to access more information and actions for each plugin. - Introduced new CSS styles for improved visual representation of plugin-related UI elements, enhancing overall user experience. - Enhanced the `listPlugins` function to include an option for fetching hidden plugins, providing more flexibility in plugin management. This update significantly improves the usability and functionality of the plugin management system, making it easier for users to interact with and manage their plugins. * fix(assistant-message): refine plugin folder candidate selection logic - Updated the `pluginFoldersTouchedThisTurn` function to improve the logic for selecting plugin folder candidates based on touched paths and message content. - Introduced a new helper function, `pathMatchesFolderFileBasename`, to enhance the matching criteria for folder candidates. - Added a check for explicit folder matches before falling back to a single candidate, improving accuracy in folder selection. - Modified the `shouldRenderSlotAsText` function in `HomeHero` to include the name parameter, refining the rendering logic for slot text. These changes enhance the functionality and reliability of the assistant message component in managing plugin folder candidates. * feat(plugin-folder-actions): implement agent-routed CLI actions for plugin management - Introduced a new `PluginFolderAgentAction` type to streamline actions related to plugin folders, including install, publish, and contribute. - Updated the `DesignFilesPanel`, `FileWorkspace`, and `AssistantMessage` components to utilize the new agent action handling, improving user interaction with generated plugins. - Refactored the action handling logic to send commands to the agent, enhancing the workflow for managing plugin folders. - Added corresponding tests to ensure the new functionality works as expected and integrates seamlessly with existing components. This update significantly enhances the plugin management experience by routing actions through the agent, allowing for a more cohesive and interactive user experience. * Fix PR 1702 CI blockers * Fix PR 1702 remaining CI checks * Prebuild AGUI adapter after install * Restore plugin project snapshot wiring * feat(marketplace): refactor marketplace URL handling and enhance fetching logic - Introduced new functions to normalize marketplace URLs and manage fetching of marketplace manifests, improving the reliability of marketplace integrations. - Updated the server and plugin logic to utilize the new fetching mechanisms, ensuring consistent handling of marketplace data. - Enhanced tests to cover new URL normalization and fetching scenarios, ensuring robustness in marketplace management. This update significantly improves the marketplace experience by streamlining URL handling and enhancing data fetching capabilities. * Fix project auto-send cleanup spec |
||
|
|
b268bbe169 |
Merge origin/garnet-hemisphere (post-9e196d34) — Use Plugin handoff fix
Brings in 11 new garnet commits, most importantly: - |
||
|
|
56c264c9bd |
feat(plugins): add login and whoami commands for GitHub CLI authentication
- Introduced `login` and `whoami` commands to the plugin CLI, enabling users to authenticate with the Open Design registry via GitHub CLI. - The `login` command wraps GitHub CLI authentication, allowing users to specify a host, defaulting to GitHub. - The `whoami` command retrieves and displays the authenticated GitHub account information, with an option for JSON output. - Updated the CLI help documentation to include usage instructions for the new commands. - Enhanced error handling for GitHub CLI dependencies and authentication status. This update improves the user experience by simplifying the authentication process for plugin publishing. |
||
|
|
d83b228c81 | Merge remote-tracking branch 'origin/garnet-hemisphere' into reconcile/garnet-main-merge | ||
|
|
7c48fbd902 |
feat(plugins): enhance PluginsView with new marketplace management features
- Updated the PluginsView component to include new tabs for 'Installed', 'Available', and 'Sources', improving the organization of plugin management. - Introduced functions for adding, refreshing, removing, and setting trust for plugin marketplaces, enhancing the marketplace interaction capabilities. - Enhanced the UI to reflect the new structure, including updated CSS styles for better visual consistency and usability. - Added tests to ensure the functionality of the new marketplace features and verify the correct rendering of available plugins. This update significantly improves the user experience in managing plugins and marketplaces, providing a more intuitive interface for users. |
||
|
|
53997990b7 |
Merge origin/main (post-0.7.0) into reconciled garnet branch
Second-pass merge layering 41+ new commits from origin/main on top of the first reconcile commit. Headline upstream additions absorbed: - 0.7.0 release: redesigned chat bubble user-text styling, neutralised palette, lucide icons, ElevenLabs audio voice option discovery in the prompt composer, analytics tracking (PostHog) wired across home / studio / create surfaces, Prometheus `/api/metrics` endpoint, critique-theater drop-in mount with a settings toggle. - Misc upstream fixes (titlebar padding, release header layout, deck preview chrome, feedback form auto-scroll, conversation-created SSE on routine runs, etc.) Conflict resolutions (12 files, ~22 hunks): - contracts barrel + prompts/system: union of both sides; new analytics exports (`./analytics/events`, `./analytics/public-params`) added alongside garnet's plugin/atom/genui exports. Both ElevenLabs voice fields (audioVoiceOptions/audioVoiceOptionsError, main) and pluginBlock/activeStageBlocks (garnet) preserved on ComposeInput. - daemon/server.ts: Prometheus `/api/metrics` route inserted after garnet's `/api/daemon/shutdown`. main's `createAnalyticsService` call added before the chat-run service init alongside the prior reconcile note about the dropped legacy POST /api/projects body. - App.tsx: handleCreateProject now consumes both garnet's plugin fields (pluginId / appliedPluginSnapshotId / pluginInputs / autoSendFirstMessage) and main's analytics requestId. Tracking fires success + failure paths; PluginLoopHome auto-send sessionStorage flag is preserved. - ProjectView.tsx: the garnet auto-send useEffect coexists with main's `useCritiqueTheaterEnabled()` hook. - ChatComposer.tsx: imports merged (drop now-unused fetchSkills, add analytics provider + tracking + buildVisualAnnotationAttachment). - index.css: main's redesigned `.msg.user .user-text` chat bubble styling wins over garnet's plain text rule; garnet's `.msg-plugin-chip*` rules preserved alongside. - EntryView.tsx: accepted HEAD (garnet wrapper) — consistent with reconcile decision #2. main's added PetRail / TopTab / analytics view tracking is intentionally NOT brought into the wrapper; the follow-up to re-integrate PetRail / image-templates / video-templates into EntryShell still stands and now also covers analytics view-tracking hooks. - daemon/package.json + pnpm-lock: merged dep set (tar + posthog-node + prom-client coexist). - Test fixtures (FileWorkspace.test): kept garnet's plugin-folders describe block intact; main's projectKind="prototype" addition is dropped where it conflicted with garnet's plugin-folder fixture files. Verification: `pnpm install` (after lockfile reconciled), `pnpm typecheck` exits 0 across all workspace packages. Follow-up not done in this commit: - PetRail / image-templates / video-templates / 0.7.0 analytics view-tracking hooks need to be added to EntryShell. - Critique-theater settings toggle UX (added on main) lives in the SettingsDialog hierarchy; the reconcile state preserves the SettingsDialog so this should work without changes, but no end-to-end verification yet. |
||
|
|
d3602be666 |
Merge origin/main into garnet-hemisphere (reconcile)
Merge of `origin/main` (`03ed3960`, 2026-05-13 pre-0.7.0) into the 161-commit garnet-hemisphere line, reconciling the product-vibe-coded plugin/marketplace/EntryShell surfaces from garnet with the routines / skills / live-artifacts feature work landed on main since the fork point. Headline decisions (full rationale + side-by-side screenshots in `specs/change/20260513-garnet-skills-automations/reconcile-result-vs-garnet.md`): - #1 SettingsDialog: keep main's Memory / Skills / External MCP / Connectors / Routines / MCP server nav items even though the top-level /integrations + /automations routes also cover them. Two entries coexist for now; revisit once Track A/B fill in the placeholder content. - #2 EntryView: accept garnet's thin wrapper delegating to EntryShell. Main's PetRail sidebar + image-templates/video-templates tabs are intentionally deferred to a follow-up that re-integrates them into the new EntryShell layout. - #3 /integrations + /automations top-level routes: kept (garnet's product intent). Skills tab is still a "Coming soon" placeholder awaiting Track A; Routines/Schedules/Live-artifacts cards on /automations are still mock awaiting Track B. - #5 DesignFilesPanel: hybrid — main's pagination as primary list, garnet's Plugin folders section preserved between the live-artifacts block and the pagination block. (by-kind sections drop in favour of pagination; plugin-folders rendering stays because it is a garnet-specific product addition.) - #7 server.ts (10 hunks, ~5400 conflict lines): manual hunk-by-hunk merge. Both daemon admin routes + plugin/genui routes (garnet) and routines/memory/skills upgrades (main) preserved. Garnet's inline project route block kept alongside main's `registerProjectRoutes` / `registerProjectUploadRoutes` modular wiring — duplicate route audit is a follow-up. Garnet's POST /api/projects plugin-snapshot resolution + default-scenario fallback is intentionally dropped from the inline body (now handled by registerProjectRoutes) and listed for follow-up re-integration into `project-routes.ts`. Verification (worktree at /Users/elian/Documents/open-design-garnet): - `pnpm typecheck` exits 0 across all workspace packages - daemon (`pnpm tools-dev run web --namespace reconcile-shots`) boots, serves `/api/daemon/status` healthy, and survives a Playwright walkthrough of /integrations / /automations / home / projects / design-systems / plugins / settings dialog - `@open-design/plugin-runtime` package built (was missing dist/ on garnet); without it the daemon's plugins/* imports fail at boot Track A (Skills tab → real SkillsSection) and Track B (Automations cards → real routines / live-artifacts backend) are the two remaining follow-ups blocking the placeholder/mock content from going live. See `spec.md` and `track-skills.md` in the same directory. |
||
|
|
5172e37217 |
Merge origin/main into release/v0.7.0 to prepare merge-back PR
Resolves 7 conflicts via hybrid strategy: - apps/web/src/components/EntryView.tsx: take main (Discord+X pills are forward feature) - apps/web/src/components/Icon.tsx: take main (switch-case refactor) - apps/web/src/components/NewProjectPanel.tsx: take release (preserve #1514 dropdown UX validated in 0.7.0 acceptance) - apps/web/src/index.css: take main (project-target-platforms / instructions chip styles) - apps/web/tests/components/FileViewer.inspect-empty-hint.test.tsx: accept main's deletion - nix/package-daemon.nix, nix/package-web.nix: take main pnpmDepsHash Non-conflicting hunks from #1519 (AppChromeHeader), #1428 (PostHog analytics call sites), and #1540 (release light background) are preserved via auto-merge. |
||
|
|
e2952acd05 |
Revert "fix(web): restore consistent app header layout (#1432)"
This reverts commit
|
||
|
|
c36609c47d |
feat(daemon, web): implement plugin sharing project creation and enhance CLI functionality
- Added new flags for conversation, message, agent, and model in the CLI to support enhanced plugin sharing features. - Introduced a new API endpoint for creating share projects for plugins, allowing users to publish to GitHub or contribute to Open Design. - Updated the UI components to facilitate the new sharing functionalities, including prompts for user input during the sharing process. - Enhanced the project management system to handle new plugin share actions, improving user interaction and experience. - Added tests to ensure the reliability of the new sharing features and their integration within the existing plugin management system. This update significantly enhances the plugin ecosystem by enabling users to share their creations more effectively and streamline collaboration. |
||
|
|
b167991d7c |
feat: add project-level and user-level custom instructions (#1304)
* feat: add project-level and user-level custom instructions Implements #510 — editable custom instructions that get injected into every model message, at both user level (Settings → Memory) and project level (pencil icon in project header). - Add customInstructions to Project, AppConfigPrefs contracts - Add custom_instructions column migration to projects table - Inject user + project instructions into system prompt (after memory, before design system; project-level wins on conflict) - Add Settings textarea for user-level instructions - Add inline editor bar in ProjectView for project-level instructions - Sync user-level instructions through daemon app-config round-trip * fix: address PR review — validation, draft reset, length limit - Reset instructionsDraft on Cancel and toggle close (stale draft bug) - Thread customInstructions through POST /api/projects create handler - Add type + length validation (5000 chars) in PATCH handler - Enforce length cap in app-config applyConfigValue - Add maxLength={5000} to both UI textareas - Resync draft via useEffect when editor is closed - Remove stray run.sh from commit * fix: address maintainer review — save race condition, precedence wording - Make handleSaveInstructions async with await + revert on failure - Add instructionsSaving state to disable Save/Cancel/textarea during save - Clarify precedence wording with concrete example in both prompt composers - UpdateProjectRequest already has customInstructions (verified) * fix: use server-returned project in save handler, drop optimistic update The previous optimistic-update + revert approach captured a stale project snapshot in the useCallback closure. On failure, reverting with the captured object could clobber unrelated project fields that changed during the async request. Switch to pessimistic update: wait for patchProject to succeed, then call onProjectChange(result) with the server-returned project object. The instructionsSaving flag disables the editor UI during the round-trip. * fix: align create/PATCH validation for customInstructions Create endpoint now rejects invalid types and >5000 char values with 400 instead of silently truncating, matching the PATCH handler behavior. |
||
|
+14 |
3d3119333c |
fix(web): restore consistent app header layout (#1432)
* docs: add NotebookLM GitHub export script (#1062) * docs: add NotebookLM GitHub export script * fix: make NotebookLM export TOC anchors work * fix: escape TOC link text markdown chars * fix: include merged PRs when exporting --prs all * fix: allow --prs merged mode * fix: treat --limit as total export budget * fix: avoid starving buckets under global --limit * fix: support --issues none and handle repos w/ issues disabled * fix: avoid underfilling export when buckets empty * fix: keep disabled-issues fallback quiet * fix: silence disabled issues fallback * fix: satisfy script typecheck * prevent duplicate saves and add template deletion (#1294) * prevent duplicate template entries on repeated save * add delete button to saved template list Templates can now be removed from the template picker via a hover x button, calling the existing DELETE /api/templates/:id endpoint. * add missing onDeleteTemplate prop in test fixtures * add template deletion flow test for NewProjectPanel * reject template names longer than 100 characters * preserve original createdAt on template update * feat: add FAQ page skill (#1162) * fix: set writable OD_DATA_DIR default for nix run Fixes #1157 When running via 'nix run github:nexu-io/open-design', the daemon attempted to create runtime state under the Nix store package path: /nix/store/.../lib/open-design/.od/projects The Nix store is read-only at runtime, causing startup to fail with ENOENT when mkdir() tried to create the projects directory. This commit updates the nix run wrapper to export OD_DATA_DIR with a writable default ($HOME/.od) when the variable is unset. Users can still override it by setting OD_DATA_DIR before running. The Home Manager and NixOS modules already set OD_DATA_DIR, so they are unaffected by this change. * feat: add FAQ page skill Add a new skill for generating Frequently Asked Questions pages with: - Collapsible accordion sections for Q&A pairs - Real-time search functionality - Category filtering (Billing, Account, Technical, General) - Smooth animations and transitions - Keyboard navigation support - Mobile-friendly responsive design - Semantic HTML with proper ARIA attributes The skill includes: - SKILL.md with triggers, workflow, and output contract - example.html demonstrating a complete FAQ page with 12 questions Use cases: help centers, support pages, product documentation * fix: address PR review feedback for FAQ page skill - Fix craft slugs: use accessibility-baseline and state-coverage instead of non-existent slugs - Remove overly broad 'questions and answers' trigger - Add edge case handling for insufficient/excessive FAQs - Remove search highlighting requirement (XSS risk) - Update self-check to reflect filtering instead of highlighting Addresses review comments from @lefarcen and @chatgpt-codex-connector * feat: add localized copy for faq-page skill Add German, French, and Russian translations for the FAQ page skill example prompt to fix validation test failure. - DE: FAQ-Seite mit Akkordeon-Abschnitten, Suchfunktion und Kategoriefilterung - FR: Page FAQ avec sections accordéon, recherche et filtrage par catégorie - RU: Страница FAQ со складными секциями-аккордеонами, поиском и фильтрацией * fix: escape apostrophe in French translation Use double quotes to avoid syntax error with d'auth * fix(platform): add legacy ~/.fnm path to wellKnownUserToolchainBins (#1110) * fix(platform): add legacy ~/.fnm path to wellKnownUserToolchainBins fnm legacy installations use ~/.fnm/node-versions. Closes #1102 * fix: remove stray .fnm token from type declaration * docs: add Windows troubleshooting guide (#478) (#1170) * docs: add Windows troubleshooting guide (#478) Add docs/windows-troubleshooting.md with step-by-step fixes for the most common native-Windows setup errors: - Node 24 / nvm-windows gotchas (fake nvm file in System32) - pnpm not found after installation - Build scripts blocked by pnpm 10 (better-sqlite3, sharp) - Visual Studio / gyp build errors - Starting the dev server - Optional OpenCode CLI setup Also update CONTRIBUTING.md and QUICKSTART.md to link to the new guide instead of the vague "file an issue if it doesn't" note. * docs: fix Windows guide command accuracy (#1170) Address all 6 inline review comments from lefarcen: - Pin npm-global pnpm install to @10.33.2 (matches packageManager field) - Use where.exe instead of bare where (PowerShell alias conflict) - Fix OpenCode package: opencode-ai (not opencode), binary is opencode - Add EPERM fallback note for corepack enable on protected installs - Add Python check for gyp ERR! find Python - Expand diagnostic checklist with corepack, python, execution policy Also remove redundant corepack pnpm --version from checklist. * feat(daemon): inject compiled design-system tokens + fixture into prompts (#1385) * feat(daemon): inject compiled design-system tokens + fixture into prompts Follow-up to #1231. The prior PR landed the structured form of two brands (`default` + `kami`) and codified the schema; this PR teaches the daemon to actually consume those files when assembling the system prompt, so agents stop having to re-derive token names from DESIGN.md prose every turn. Gated behind `OD_DESIGN_TOKEN_CHANNEL=1` for the smoke-test phase — flag-off keeps the daemon byte-equivalent to today's behavior, flag-on appends two new prompt blocks (the brand's `tokens.css` :root contract and its `components.html` reference fixture) right after the existing DESIGN.md block. Brands without those sibling files (every brand except `default` and `kami` today) skip silently in either mode. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(daemon): only swallow ENOENT/ENOTDIR in readFileOptional, rethrow rest Reviewer feedback (nettee, #1385). The prior catch-all hid permission errors, EISDIR, and broken packaged-resource paths behind the same "undefined = absent" branch the legacy ~138-brand fallback uses, which would let `OD_DESIGN_TOKEN_CHANNEL=1` silently degrade to the DESIGN.md-only prompt while reporting success. That corrupts the exact signal the smoke-test rollout depends on. Now `readFileOptional` only returns undefined for ENOENT / ENOTDIR (real "file does not exist" cases) and rethrows everything else. Added a focused test that plants a directory at the tokens.css path to exercise the EISDIR branch, plus a partial-presence regression test to confirm the stricter contract preserves the legacy fallback. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: chaoxiaoche <chaoxiaoche@192.168.10.16> Co-authored-by: Cursor <cursoragent@cursor.com> * feat(daemon): make connection-test timeouts configurable (#1222) * feat(daemon): make connection-test timeouts configurable Provider and agent connection tests had hardcoded 12s / 45s budgets, which are too tight for slow networks or distant providers (the user sees "timeout" in Settings with no way to extend the budget). - Add OD_CONNECTION_TEST_PROVIDER_TIMEOUT_MS (default 12_000) - Add OD_CONNECTION_TEST_AGENT_TIMEOUT_MS (default 45_000) - Invalid values (non-numeric, zero, negative, fractional) emit a console.warn and fall back to the default, so a typo in the env never silently disables the safety timeout. - Export resolveConnectionTestTimeoutMs for unit testing; cover the three resolution paths (fallback / honored override / invalid). 41 connection-test tests pass (+3 new), full daemon suite 1170/1170. * fix(daemon): reject connection-test timeout overrides above Node's setTimeout maximum Node's `setTimeout` silently clamps any delay above `2^31-1` ms (2_147_483_647) to ~1 ms with a TimeoutOverflowWarning. The previous `Number.isInteger(n) && n >= 1` check accepted oversized values unchanged and passed them straight to `setTimeout`, so an override that *intended* to raise the budget — e.g. `OD_CONNECTION_TEST_AGENT_TIMEOUT_MS=3000000000` — instead caused every connection test to fail almost immediately. The safety timeout was effectively disarmed. Add `MAX_CONNECTION_TEST_TIMEOUT_MS = 2_147_483_647` and switch the guard to `Number.isSafeInteger(n) && n >= 1 && n <= MAX...`. The boundary value is still accepted; one millisecond past it falls back with a warn. Regression test exercises `3_000_000_000`, `2_147_483_647`, and `2_147_483_648`. Addresses #1222 review feedback from @chatgpt-codex-connector, @mrcfps, and @lefarcen. * fix(security): strip trailing dot in normalizeBracketedIpv6 (FQDN SSRF bypass) (#1122) * fix(security): strip trailing dot in normalizeBracketedIpv6 (FQDN bypass) new URL('http://192.168.1.5./').hostname returns '192.168.1.5.' — the trailing dot is the RFC 1034 absolute-FQDN form and resolves identically to '192.168.1.5'. parseIpv4 fails on the dotted form, so 169.254.169.254. slips past the metadata-service block, 192.168.1.5. slips past the LAN block, and localhost. slips past the loopback identification. Strip trailing dots in normalizeBracketedIpv6 so all downstream checks (isLoopbackApiHost, isBlockedExternalApiHostname, isBlockedIpv4, IPv6 range tests) see the canonical form. Adds 6 vitest cases covering loopback FQDN forms (localhost., foo.localhost., 127.0.0.1.) and SSRF FQDN bypasses (169.254.169.254., 192.168.1.5., 10.0.0.5.). Refs nexu-io/open-design#1119 review feedback (P2 from @lefarcen). * test(connectionTest): tighten trailing-dot coverage per #1122 review Two issues from #1122 review: 1. (P2 from @mrcfps + codex bot) The original `foo.localhost.` case asserted error===undefined on validateBaseUrl, which only proves the URL passed validation — not that the host is identified as loopback. Replaced with direct isLoopbackApiHost(...) assertions on the actual loopback FQDN forms (localhost., 127.0.0.1., 127.0.0.5.) so the test exercises the loopback path the comment claims. 2. (P3 from @lefarcen) Original blocked-FQDN tests covered only 3 of 7 ranges that isBlockedIpv4 handles. Added a dedicated case per range (0.0.0.0/8, 10/8, 100.64/10, 169.254/16, 172.16/12, 192.168/16, multicast >=224) so future regressions in normalizeBracketedIpv6 surface against the full coverage. * docs: drop misleading foo.localhost./endsWith claim in normalizer comment @lefarcen review feedback: isLoopbackApiHost only accepts exact 'localhost', '::1', loopback IPv4, and mapped loopback IPv4 — there's no subdomain or endsWith handling, so referencing 'foo.localhost.' overstates what the trailing-dot strip enables. Rewrite the comment to match actual call sites (isLoopbackApiHost equality + isBlockedIpv4 numeric parse). * feat(daemon): export self-contained HTML via /export/*?inline=1 endpoint (#1312) * test(daemon): add Red unit tests for inlineRelativeAssets helper 14 cases pinning the behavior contract for the upcoming apps/daemon/src/inline-assets.ts helper: - link/script inlining with verbatim body preservation - non-src script attrs preserved (type=module, defer, crossorigin) - relative path resolution (root + nested + deep-nested owners) - self-closing and single-quoted attr forms - negative cases: missing rel, rel=preload, absolute/data/blob/leading-slash - escaping: </style and </script inside body - null-fileReader graceful degradation - duplicate identical tags fully replaced (diverges from apps/web/src/components/FileViewer.tsx:5313's first-match-only; locked decision per plan §3.3) - HTML-escaped data-od-inline-asset attr Tests intentionally Red — module ../src/inline-assets.js does not yet exist. Phase B-G of plan declarative-roaming-gosling.md will turn them green by porting FileViewer.tsx:5248-5354 server-side. Refs nexu-io/open-design#368. * feat(daemon): port inlineRelativeAssets server-side for export endpoint Adds apps/daemon/src/inline-assets.ts — a pure helper that takes (html, ownerFileName, fileReader closure) and returns the HTML with every relative <link rel=stylesheet> and <script src> contents inlined into <style data-od-inline-asset="…">/<script>…</script> blocks. The fileReader closure keeps the helper free of fs/Express coupling so the route handler owns the filesystem boundary. Port source: apps/web/src/components/FileViewer.tsx:5248-5354 — five functions (inlineRelativeAssets, resolveProjectRelativePath, baseDirFor, readHtmlAttr, escapeHtmlAttr). The fetch hop becomes the fileReader closure; replace-all replaces first-match-only per locked design decision §3.3 (inline comment in inline-assets.ts cites the divergence from FileViewer.tsx:5313 and notes the web inline path is on a deprecation track since PR #384 made URL-load the default). Phase B-G of plan declarative-roaming-gosling.md. All 14 unit cases from the Red commit ( |
||
|
|
5f71968f61 |
feat(daemon, web): implement plugin sharing features for GitHub and Open Design contributions
- Added new API endpoints for publishing plugins to GitHub and contributing to Open Design, enhancing the plugin sharing capabilities. - Introduced functions for handling plugin sharing actions, including `publishGeneratedPluginToGitHub` and `contributeGeneratedPluginToOpenDesign`. - Updated the `DesignFilesPanel` and `FileWorkspace` components to support new sharing functionalities, allowing users to publish or contribute plugins directly from the interface. - Enhanced the UI with new buttons for publishing and contributing plugins, improving user interaction and experience. - Added tests to ensure the reliability of the new sharing features and their integration within the existing plugin management system. This update significantly improves the plugin ecosystem by enabling users to share their creations with the community and streamline collaboration. |
||
|
|
26d21a942e |
feat(web): enhance plugin input handling and categorization
- Added support for plugin inputs in the EntryShell and HomeView components, allowing for more dynamic plugin interactions. - Updated the PluginsHomeSection to include subcategory filtering, improving the user experience when navigating plugins. - Enhanced the PluginsView and related components to reflect the new categorization model, transitioning to a workflow-based approach. - Refactored tests to ensure coverage for new input handling and categorization features, maintaining reliability across the application. This update significantly improves the plugin management experience by providing clearer categorization and enhanced input handling for plugins. |
||
|
|
6f818d971d |
feat(daemon, web): implement plugin folder installation and enhance atom worker registry
- Added a new API endpoint for installing plugins from specified folder paths, improving the plugin management experience. - Introduced functions for normalizing and validating project plugin folder paths, ensuring robust error handling. - Implemented a registry for built-in atom workers, allowing for dynamic signal aggregation during pipeline execution. - Enhanced the `runStageWithRegistry` function to support multiple atom workers, merging their outputs with pessimistic logic. - Updated the UI components to display plugin folder candidates and facilitate user interactions for plugin installation. - Added tests for the new atom worker registry and plugin folder installation features, ensuring reliability and correctness. This update significantly enhances the plugin installation process and the overall functionality of the atom worker system, providing users with better tools for managing plugins and their interactions. |
||
|
|
13d5598b0c |
feat(web, daemon): enhance plugin import functionality and UI components
- Added support for uploading plugins via zip files and folders, improving the plugin import process. - Introduced a new `PluginImportModal` for a streamlined user experience when importing plugins. - Updated the `PluginsView` to include disabled states for unfinished plugin areas, enhancing clarity for users. - Refactored various components to utilize the new `resolvePluginQueryFallback` function for improved localization handling. - Enhanced CSS styles for better visual feedback and responsiveness in the plugin import interface. This update significantly improves the plugin management experience, making it easier for users to import and manage plugins effectively. |
||
|
|
443aea72c5 |
feat(daemon, web): enhance plugin handling and UI integration
- Introduced a new plugin upload mechanism with file size limits and memory storage, allowing users to upload plugins directly. - Implemented fallback logic for plugin application, ensuring projects can be created without explicit plugin requests. - Enhanced the UI to support plugin selection and integration, including a new `PluginsView` component for managing plugins. - Updated various components to utilize localized text for plugin queries, improving user experience across different languages. - Added tests for new plugin functionalities and local skill loading, ensuring reliability and correctness. This update significantly improves the plugin management experience, providing users with better tools for plugin integration and interaction. |
||
|
|
e6c5560884 | Fix appearance accent color persistence (#1439) | ||
|
|
2f51f3c1ae |
feature: refine assistant artifact feedback (#1379)
* feature: refine assistant artifact feedback * fix: clear hidden custom feedback reason * test: update assistant feedback expectations |
||
|
|
140a4e1ff6 |
Improve responsive preview and design handoff outputs (#1224)
* feat: improve responsive design handoff * feat: refine cross-platform design outputs Changelog:\n- Add auto-fit responsive preview behavior for tablet/mobile frames.\n- Add landing page and OS widgets metadata options with project header chips.\n- Strengthen prompt contracts for modern breakpoints, app-specific modules, CJX-ready UX, and final product surfaces.\n- Require cross-platform outputs to use separate platform files instead of tabbed demo selectors.\n- Add DESIGN-MANIFEST.json plus richer handoff guidance to daemon/client exports.\n- Update archive/export tests for manifest and responsive viewport matrix. * feat: enforce screen-file design outputs Changelog:\n- Enforce screen-file-first generation for landing pages, app screens, platform surfaces, and OS widgets.\n- Update design handoff and manifest exports so coding tools map each screen file to separate routes/surfaces.\n- Strengthen minimal-brief visual guidance to avoid monochrome or unstyled design outputs. * fix: address responsive handoff review feedback * fix: address handoff review blockers * fix: preserve proxy auth and normalized export entry * fix: narrow frame wrapper filter to directory paths only * fix: make artifact save failure banner generic --------- Co-authored-by: Huy Hoàng <macos@MacBook-Pro-Hoang.local> |
||
|
|
dbc94b83ed |
feat(web): add thumbs-up/down feedback widget under completed assistant turns (#1288) (#1308)
* feat(web): add thumbs-up/down feedback widget under completed assistant turns (#1288) Adds a lightweight feedback widget that surfaces under each assistant turn whose run succeeded. Users can submit positive or negative feedback in one click; the negative path opens an optional free-text comment area. The widget never blocks the message composer and only mounts after the run has produced its final artifact, matching the acceptance criteria. What ships - `<MessageFeedback>` (apps/web/src/components/MessageFeedback.tsx) renders the three states: idle (prompt + thumbs), submitted positive (confirmation + Change), submitted negative (confirmation + optional comment textarea + Send + Change). - AssistantMessage.tsx slots the widget under AssistantFooter, gated on `runSucceeded && !hasEmptyResponse`, so failed and empty-response turns don't ask the user to rate something that never finished. - The full record shape leaves room for the future analytics metadata the issue calls out (rating, comment, submittedAt; artifactRef / runId derivable from the surrounding message whenever the analytics pipeline lands). Persistence (v1 = localStorage) Lefarcen's clarifying comment on the issue asked whether v1 should be daemon-persisted or in-memory while the analytics pipeline is defined. The daemon's messages table is column-strict, so daemon persistence would require a SQLite migration plus a contract bump on `ChatMessage`; locking that shape in before the analytics pipeline is designed risks reworking it twice. localStorage is the middle ground: feedback survives reload (so the "feedback state is visually clear after submission" criterion holds across tabs and sessions) without committing the wire shape. The hook surface is just `(value, setter)`, so a future PR can swap the storage layer for a daemon mirror or an analytics shipper without touching the React surface. The store handles corrupted JSON, unknown future rating values, disabled storage (private-mode browsers), and broadcasts changes across listeners in the same tab via a CustomEvent so two mounts of the hook for the same messageId stay in sync. i18n 11 new keys under `feedback.*` (prompt, thumbsUp/Down, two confirmation chips, comment label/placeholder/submit/saved, change). English source values authored alongside the keys; zh-CN translations added in the same pass so the locale alignment test stays green and Chinese users see Chinese strings from day one. The other 16 locales pick up English fallbacks via their existing `...en` spread. Test coverage - `tests/state/message-feedback.test.ts` (8 jsdom cases) — round-trip, null-clear, corrupted JSON, missing rating, unknown rating, key collision across messages. - `tests/components/MessageFeedback.test.tsx` (7 jsdom cases) — idle state, positive submit, negative submit, comment save, blank-comment Send disabled, Change unsticks the rating, rehydration from pre-populated storage. The locale alignment test continues to enforce that every locale declares the new keys (5/5 across 18 locales). Validated - pnpm guard clean - pnpm --filter @open-design/web typecheck clean - tests/i18n/locales.test.ts 5/5 - tests/state/message-feedback.test.ts 8/8 - tests/components/MessageFeedback.test.tsx 7/7 - Full web suite: 98 files, 903 tests * fix(web): tighten feedback widget gate + storage sync + textarea, add styles (PR #1308 review) Addresses every P2/P3 from the codex + Siri-Ray + lefarcen reviews on PR #1308, plus a couple of polish items the review surfaced indirectly. Visibility gate (lefarcen P2) The gate was `runSucceeded && !hasEmptyResponse`, which also matched text-only acknowledgements and question-form replies. The issue scopes feedback to turns that produced a final artifact, so the gate now also requires `produced.length > 0`. New AssistantMessage suite (5 jsdom cases) pins: artifact -> shown, no-artifact -> hidden, streaming -> hidden, failed run -> hidden, empty_response -> hidden. Storage sync (codex P2 + lefarcen P2) The previous broadcast contract was: write storage, dispatch a bare CustomEvent, listeners re-read storage. That had two failure modes: - setItem throwing (private mode / quota / disabled storage) left the listener seeing null and clobbering the in-memory state the user just confirmed. - The clear path early-returned after removeItem and never dispatched, so a second mount of the same messageId stayed in the submitted state when the user clicked Change. New contract: every successful OR failed write dispatches a CustomEvent whose `detail.value` carries the new feedback record (or null). Listeners apply the value directly without re-reading. Same- tab sync survives storage failures and the clear path no longer early-returns. Cross-tab still re-reads on the platform `storage` event since that event has no detail. Two new storage tests pin the new broadcast contract (positive + null) and the failed-setItem path; two new component tests pin in-session confirmation under setItem failure and two-mount Submit + Change synchrony. Textarea draft fix (lefarcen P3) The textarea used `draftComment || feedback.comment || ''` as its controlled value, so erasing a saved comment snapped it back. The draft is now exclusively the source of truth; a ref-backed effect re-seeds the draft from feedback.comment whenever the rating transitions (mount, idle -> negative, cross-mount sync). Send is now enabled when `draftComment !== savedComment`, which lets the user both edit and clear a saved comment. New component test pins erase+ Send actually removing a previously-saved comment. Accessibility The confirmation chip and "Comment saved" tag both gain `role="status"` + `aria-live="polite"` so screen readers announce the state transition. The thumb buttons keep their `aria-label`. CSS (lefarcen P3) The widget's `.message-feedback*` class set had no rules in index.css, so it rendered with default browser controls. Added a ~130-line block that mirrors the surrounding chat pill/chip vocabulary: bg-subtle background, border-pill confirmation chip, accent-tinted positive state and amber-tinted negative state to match the assistant-footer's data-unfinished pattern. Comment area sits below the chip and wraps on narrow widths so the composer isn't pushed off-screen on small panes. Validated - pnpm guard clean - pnpm --filter @open-design/web typecheck clean - tests/state/message-feedback.test.ts 10/10 (was 8, +2 broadcast) - tests/components/MessageFeedback.test.tsx 10/10 (was 7, +3 sync / storage-failure / clear-saved-comment) - tests/components/AssistantMessage.test.tsx 5/5 (new file) - tests/i18n/locales.test.ts 5/5 - Full web suite: 866 tests --------- Co-authored-by: Nagendhra <nagendhra405@gmail.com> |
||
|
|
b3dc3c3e0c |
feat(web): integrate applied plugin snapshot for enhanced user experience
- Added support for displaying an active plugin as a context chip in user messages when a project is created with a pinned plugin. This replaces the in-composer plugin rail to avoid re-prompting users for plugin selection. - Introduced `applied_plugin_snapshot_id` in the database schema and updated relevant components (ChatComposer, ChatPane, ProjectView) to handle the new functionality. - Implemented fetching of the applied plugin snapshot in ProjectView to ensure the active plugin is rendered correctly. - Enhanced CSS for the plugin chip to improve visual presentation. This change streamlines the user experience by providing context on previously selected plugins directly within the chat interface. |
||
|
|
175629193f |
feat(web): PluginLoopHome minimum-closed-loop entry on Home
Replace the tabbed NewProjectPanel on Home with PluginLoopHome — a single prompt textarea + plugin grid. Picking a plugin calls /api/plugins/:id/apply, binds the snapshot, and lets the user submit the prompt straight into a new project; createProject forwards pluginId / appliedPluginSnapshotId so daemon pins the plugin to the project + conversation, and ProjectView auto-sends the first message once via a sessionStorage flag so the user lands inside a running pipeline. Also: InlinePluginsRail / PluginsSection gain a `kinds` filter so the ChatComposer strip only surfaces user-facing skill/scenario/bundle plugins (atoms stay pipeline-side). |
||
|
|
e254d1280b |
feat(memory): auto-memory store with chat-protocol-aware extraction (#999)
* feat(memory): auto-memory store with chat-protocol-aware extraction Markdown memory store at <dataDir>/memory/ with two extractors — heuristic regex for explicit "remember:" / "我是 X" markers, and a small-model LLM pass after each turn — folded into the system prompt so cross-chat preferences, role, and ongoing-work context survive restarts. Settings UI: - Memory tab lists entries, exposes a hand-edited MEMORY.md index, and shows an extraction history with per-attempt phase/skip/failure rows. - Memory model picker is inline next to the chat model picker (CLI and BYOK) so the choice "which fast model mines facts each turn?" sits next to the chat-model decision instead of a separate panel. The picker reuses the same SUGGESTED_MODELS table and "Custom..." pattern the chat picker uses. LLM extractor supports all four protocols (anthropic / openai / azure / google); pickProvider takes the chat agent id from the chat handler and constrains its auto-pick to the chat's protocol family — Claude Code chats no longer surprise users by silently extracting on whatever OpenAI key happens to be in media-config. When no matching key is configured the attempt records as 'skipped: no-provider' instead of quietly switching vendors. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(memory): keep hint outside <label> and disambiguate Model selectors The inline Memory model picker wrapped its hint paragraph inside the <label>, which made the hint's "API key" / "model" wording bleed into the <select>'s accessible name and broke Playwright's getByLabel('API key') / getByLabel('Model') strict-mode matching in the existing settings-api-protocol e2e suite. - Move the hint <p> out of the <label> in MemoryModelInline so the select's accessible name is just "Memory model". - Switch the chat-Model selectors in settings-api-protocol.test.ts from getByLabel('Model') to getByRole('combobox', { name: 'Model', exact: true }) so they no longer collide with the new "Memory model" select that sits next to the chat Model picker. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(memory): address review changes — BYOK wiring, MEMORY.md index, /v1, label wrapper Addresses the four blocking review threads on PR #999. 1. MemoryModelInline accessibility (mrcfps) The inline picker still wrapped its select + custom input + flash + hint inside a single <label>, which made the select's accessible name absorb every text descendant — including the "API key" / "model" hint copy. The previous fix moved only the hint outside; the reviewer asked for a non-label wrapper. Switch to <div className="field"> and associate just the short title with the controls via `aria-labelledby` / `aria-label`. The select's accessible name is now exactly "Memory model" so `getByLabel` strict-mode locators on the surrounding chat form stop cross-matching the memory copy. 2. Respect the hand-edited MEMORY.md index (mrcfps + codex) `composeMemoryBody()` was reading every *.md file in the memory dir, ignoring the index. Removing a `- [Name](id.md)` line had no effect on future prompts. Parse the index's `INDEX_LINK_RE` bullets and filter `listMemoryEntries()` to the linked id set, so the editor's "delete this line to disable injection" promise actually holds. 3. Versioned OpenAI-compatible base URLs (codex) `callOpenAI` and `callAnthropic` hard-coded `/v1` onto `provider.baseUrl`, breaking custom endpoints whose saved URL already includes `/v1` (`/v1/v1/chat/completions`). Apply the same conditional `appendVersionedApiPath` helper the chat proxy and connection-test routes already use. 4. Wire memory into BYOK / API-mode chats (mrcfps + codex) The previous PR's daemon-only memory hook never fired for BYOK, leaving the Memory tab + model picker as a no-op for that mode. Add the missing surface and wire it through ProjectView: - contracts: extend `composeSystemPrompt` with `memoryBody`, mirroring the daemon's local composer; add `MemorySystemPromptResponse` and the `attemptedLLM` flag on `ExtractMemoryResponse`. - daemon: expose `GET /api/memory/system-prompt` (returns the composed body) and turn `POST /api/memory/extract` into a two-phase endpoint — heuristic-only when only userMessage is supplied (pre-turn), LLM-only when assistantMessage is also supplied (post-turn), so the extraction-history doesn't double up. - web: ProjectView's BYOK branch now fetches the memory body before composing the system prompt, runs the heuristic extractor before the run (so "remember:" markers in this turn reach this turn's prompt), accumulates assistant text during streaming, and queues the LLM extractor on `onDone` — fire-and- forget so it never blocks the chat round-trip. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(memory): re-sync BYOK memory override when chat config drifts The inline memory-model picker captured `apiProtocol` / `chatApiKey` / `chatBaseUrl` / `chatApiVersion` into the saved override only at the moment the user clicked a model. If they later swapped the BYOK protocol tab, rotated the API key, or edited the base URL in the same settings flow, the daemon's background extractor kept calling the *old* vendor / credential — directly contradicting the picker's "borrows the surrounding chat picker's protocol, key, base URL, and api-version automatically" promise. Add a debounced effect that compares the persisted (masked) shape against the live chat props and re-PATCHes /api/memory/config when they drift. The masked config exposes `apiKeyTail` (last 4 chars), so key rotation is detectable without ever round-tripping the secret back to the browser. The 300 ms debounce coalesces the keystroke- granularity prop updates the parent settings dialog streams during its autosave loop, so a user editing the base URL doesn't trigger one PATCH per character. Background re-syncs are silent — the "Saved!" flash only fires for explicit user clicks, so the picker doesn't feel like it's fighting them as they edit unrelated chat fields. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(memory): thread BYOK chat config through /api/memory/extract default path Leaving the BYOK memory picker on "Same as chat" still broke the default LLM extraction path: `MemoryModelInline` clears the override for that option, both `/api/memory/extract` calls in `ProjectView` only sent the messages, and the daemon never persists BYOK creds, so `extractWithLLM(..., { chatAgentId: null })` always reached `pickProvider()` with no chat context and fell through to env / media-config — the wrong vendor for a BYOK chat that works for inference. Thread the live BYOK chat config through the extract endpoint as a per-call snapshot: - contracts: extend `ExtractMemoryRequest` with an optional `chatProvider` (provider/apiKey/baseUrl/apiVersion/model) and add `'chat-byok'` to the credentialSource enum. - daemon: parse + validate `chatProvider` on `/api/memory/extract` (provider must be one of the five known shapes) and forward to `extractWithLLM` as a new option. `pickProvider()` gets a new path 2 that uses the snapshot directly with the per-protocol fast-model default — so a memory pass on `gpt-4o` / `claude-sonnet-4-5` silently turns into a cheap `gpt-4o-mini` / `claude-haiku-4-5` call instead of paying chat-tier rates for sediment work. Override and CLI-agent-constrained paths still win when they apply. - web: `ProjectView` snapshots `apiProtocol` / `apiKey` / `baseUrl` / `apiVersion` from the live `AppConfig` on each BYOK extract call (both pre-turn heuristic-only and post-turn LLM phases). The picker's existing drift-resync effect already covers explicit overrides; this snapshot covers the implicit "Same as chat" default that the override flow can't reach. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(memory): treat empty apiKey on PATCH as a real clear MemoryModelInline silently re-PATCHes /api/memory/config whenever the surrounding BYOK chat creds drift. The previous reuse branch lumped `apiKey === ''` together with `apiKey === undefined`, so clearing the chat API key from the picker quietly preserved the old daemon-side secret and kept calling the provider on a stale credential. Distinguish four states for the apiKey field: - absent -> preserve stored secret (form re-save without re-typing) - '' -> clear stored secret (user removed it from the picker) - 'sk-...' -> replace - new provider -> ignore stored secret entirely Add tests/memory-config-route.test.ts covering all four cases. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
06e677cb72 | Fix pending prompt clearing for templates (#1148) | ||
|
|
adc2afd769 |
feat(web): plugin composer surface — applyPlugin + Rail + Inputs + GenUI renderer
Plan §3.C1–§3.C4.
Web composer integration for the plugin system:
- apps/web/src/state/projects.ts gains:
* applyPlugin(pluginId, { inputs?, projectId?, grantCaps? }) — wraps
POST /api/plugins/:id/apply and returns the typed ApplyResult.
* listPlugins() — wraps GET /api/plugins.
* renderPluginBriefTemplate(template, inputs) — substitutes
{{var}} placeholders inside useCase.query as the user types so the
composer's brief textarea re-renders live.
- New components:
* InlinePluginsRail — the card strip that lives below the input box
on Home and inside ChatComposer. Supports 'wide' / 'strip' layouts
+ taskKind / mode filters.
* ContextChipStrip — typed ContextItem chips above the brief input.
Optional onRemove for clearing the applied plugin.
* PluginInputsForm — JSON-Schema-light form rendered between the
input and Send. Required fields gate Send via onValidityChange;
string/text/select/number/boolean field types are supported.
* GenUISurfaceRenderer — first-class confirmation + oauth-prompt
surfaces (form + choice fall back to a JSON Schema preview +
free-form textarea until Phase 2A.5).
* GenUIInbox — drawer that lists every persisted surface answer for
a project; revoke calls POST /api/projects/:id/genui/:sid/revoke.
- jsdom tests under apps/web/tests/components/:
* InlinePluginsRail (mount fetch, click → applyPlugin → onApplied,
taskKind filter)
* PluginInputsForm (validity gating, default hydration, select
options)
* GenUISurfaceRenderer (confirmation true/false branches; oauth
surface forwards { authorized, connectorId } per spec §10.3.1)
Web test suite: 567 → 575 (added 8 plugin component cases). The
NewProjectPanel / ChatComposer / ProjectView mounts will land in the
follow-up commit so this PR's diff stays reviewable.
Co-authored-by: Tom Huang <1043269994@qq.com>
|
||
|
|
fe879036fb |
fix(web): restore media config from daemon on startup (#687)
* fix(web): restore media config from daemon on startup * fix(media): preserve stored keys on settings save * fix(web): harden daemon media restore flow * fix(web): unify media provider empty-state rules * fix(desktop): retry loading discovered web url * fix(web): preserve local media providers on partial daemon reload * fix(web): preserve media providers on daemon reload * fix(web): skip media migration for masked-only local state * fix(web): preserve daemon media state across reloads |
||
|
|
66f84972cf |
feat(byok): Added Ollama CLoud as BYOK provider (#923)
* feat: add Ollama Cloud to KNOWN_PROVIDERS as OpenAI-compatible BYOK provider * feat: add ollama.com to isOpenAICompatible base URL detection * feat: add Ollama Cloud models to SUGGESTED_MODELS_BY_PROTOCOL fallback list * fix: use full Ollama Cloud model list from /api/tags, drop -cloud suffix * feat: add Ollama Cloud as native protocol with NDJSON streaming and connection test support * fix: remove ollama.com from OpenAI compatibility check * feat: add token overrides for Ollama Cloud models to prevent truncation * fix: extend inferApiProtocol and legacy migration to recognize ollama.com base URLs * fix: normalize Ollama Cloud base URL by stripping /api suffix during migration and in daemon --------- Co-authored-by: herediaron <aronheredi346@gmail.com> |
||
|
|
afb331a288 |
feat: add opt-in Langfuse telemetry (#800)
* docs(specs): add langfuse telemetry change spec
Captures the design for forwarding completed agent runs to Langfuse,
including data-model mapping, field-budget caps, privacy gates,
build-secret injection, GDPR right-to-deletion approach, and the
resolved decisions on default consent, identifier shape, region, and
ownership.
* feat(daemon): add langfuse-trace module and telemetry prefs
Adds the dependency-free building blocks for forwarding completed
agent runs to Langfuse. Two layers:
- AppConfigPrefs gains installationId and a TelemetryPrefs object with
metrics / content / artifactManifest gates. The daemon validator
treats telemetry like agentModels — replace-on-write, drop-when-empty,
reject non-boolean inner values.
- New langfuse-trace.ts builds a {trace-create, generation-create}
pair from a ReportContext, capping prompt at 8 KB, output at 16 KB,
artifacts at 50 entries, and dropping any batch larger than 1 MB
before send. reportRunCompleted is no-op when LANGFUSE_PUBLIC_KEY /
LANGFUSE_SECRET_KEY are unset (so dev runs and forks never emit) and
short-circuits on prefs.metrics === false.
Server-side wiring into the run-close path lands in a follow-up.
* fix(langfuse): default to US Langfuse region
End-to-end smoke against the project's actual dev key on 2026-05-07
returned 401 from cloud.langfuse.com (EU) and 207 from
us.cloud.langfuse.com (US), confirming the org lives in US. Update the
default base URL, the matching test, and the spec's Q3 decision row to
match. Self-hosted or EU-region operators can still override via the
LANGFUSE_BASE_URL env var.
* feat(daemon): wire langfuse trace forwarding into run-close
Adds the daemon-side glue to forward completed agent runs:
- runs.ts gains an optional onTerminate hook fired once per run after it
reaches a terminal state. Errors thrown from the hook are caught and
logged, never propagated, so telemetry can never break the run path.
- New langfuse-bridge.ts assembles a ReportContext from the in-memory
run record, the conversation's persisted assistant message, and the
user's app-config preferences. It tolerates a missing message (e.g.
when web has not yet PUT the final delta) and a missing app-config.
- server.ts stashes the original user prompt on the run object inside
startChatRun so the bridge can include it without crossing the
createChatRunService boundary, and registers the hook callback when
building the run service.
Behavior remains a no-op unless LANGFUSE_PUBLIC_KEY / LANGFUSE_SECRET_KEY
are set in the daemon env AND telemetry.metrics is true in app-config.
A live smoke against us.cloud.langfuse.com on 2026-05-07 confirmed the
matching trace + generation schema is accepted (HTTP 207, both events
201 created).
* fix(langfuse): address PR #800 review feedback
P1 — Move trace forwarding off the daemon-internal run-close hook and
onto the message-persistence path. The original onTerminate hook ran
inside finish() the moment the SSE 'end' event was emitted, which is
*before* the web client's onDone handler refreshes project files and
PUTs producedFiles + final assistant content back to SQLite. Reading
SQLite at that moment routinely missed both. The fix: drop the runs.ts
hook entirely and trigger from PUT /api/projects/:id/conversations/:cid/
messages/:mid when the saved row carries a terminal runStatus. A
reportedRuns Set guards against the multiple PUT calls web makes per
turn (each retry / state update). Set entries auto-evict after the same
30 min TTL the runs map uses. Web persists a terminal-status message in
all three completion paths — onDone (succeeded), onError (failed), and
cancel (canceled) — so this catches every run shape.
P2 — postLangfuseBatch now parses the 207 Multi-Status response body.
Langfuse legacy ingestion always returns 207, and response.ok is true
for 207, so per-event validation errors used to slip through silently.
We now warn when body.errors is non-empty. Two new unit tests.
P2 — truncate() and the HARD_BATCH cap now compare UTF-8 byte length,
not String.length (which counts UTF-16 code units). A 4096-character
CJK prompt occupies 12 KB, well over the 8 KB input cap. truncate also
walks backwards to a UTF-8 leading byte so the cut never lands inside a
multi-byte codepoint. New unit test covers '设'.repeat(4096).
P2 — Spec R7 now lists the actual Langfuse trace deletion endpoint
(DELETE /api/public/traces/{traceId} for single, DELETE /api/public/traces
with body for batch). Verified by curl on us.cloud.langfuse.com:
DELETE /api/public/traces/X → 200; the path the original spec named
(POST /api/public/trace/X) returns 404. Reference link points at
langfuse.com/docs/administration/data-deletion.
P3 — Q4 (legacy ingestion vs OTel) moved from Open Questions to
Resolved Decisions. The implementation already commits to legacy and
the trade-off was discussed during design; the open-question status was
stale.
* feat(web): privacy consent surface + Settings → Privacy tab
Adds the user-facing half of the telemetry feature so the daemon-side
hook from PR #800 has something to talk to.
- AppConfig gains optional `installationId` (anonymous v4 uuid generated
on first opt-in; null after explicit decline; undefined when the user
has never seen the consent surface) and `telemetry: TelemetryConfig`
({metrics, content, artifactManifest}). syncConfigToDaemon round-trips
both fields so the bridge module sees the same prefs.
- SettingsDialog grows a Privacy section with two states. When the user
has never made a consent decision (typical first-run path), the
section renders the GDPR-aligned consent card: a kicker, the disclosure
body listing both metrics and conversation content as separate bullets,
and two equally-prominent buttons ("Share usage data" / "Don't share").
The Don't-share path keeps the app fully usable (core app must work
with all tracking declined). After a decision the same panel switches
to three independent toggles + the anonymous ID + a "Delete my data"
button that rotates the ID and turns everything off.
- App.tsx points the welcome modal at the new Privacy section so the
consent decision is the first thing a fresh installation sees.
- 17 i18n keys land in en + zh-CN + zh-TW with hand-translated copy,
and as English placeholders in the remaining 14 locales — enough for
the parity check to pass while leaving room for proper localisation
in a follow-up. Dict type updated.
- Minimal index.css for the consent card + toggle rows so the panel is
legible without depending on follow-up design polish.
Telemetry remains a no-op end-to-end until the user clicks Share usage
data: the daemon gate (prefs.metrics === true) keeps every code path
short-circuited otherwise.
* refactor(web): rebuild Privacy panel using project-native settings primitives
The first cut used custom .settings-privacy-* classes + raw HTML
checkboxes that didn't match any other Settings tab. Replace with the
shell other sections already use:
- settings-subsection containers with section-head + h4 + .hint
- seg-control / seg-btn pill toggles ("active" / "offline") for each of
the three telemetry preferences, mirroring NotificationsSection
- a 2-cell seg-control for the consent card so Share usage data and
Don't share carry identical visual weight (the GDPR equal-prominence
requirement that the previous accent / outline split missed)
- ghost button + readonly text input for the installation id row,
mirroring the API-key field pattern elsewhere
Drop the bespoke CSS block in favor of inheriting the existing
settings-section / seg-control / ghost styling. The only privacy-
specific style left is a tight definition list inside the consent card
for the metrics + content disclosure rows.
* refactor(web): use .toggle-row iOS switch for Privacy preferences
Active/offline pills (the seg-control single-cell pattern that
NotificationsSection uses) read awkwardly for a flat preference list.
Switch the three telemetry toggles to .toggle-row — the same control
NewProjectPanel uses for "speaker notes" / "animations": label + hint
on the left, iOS-style sliding switch on the right, full-row click
target. The consent card's two-button seg-control stays as-is — there
the equal-weight pill pair is exactly what GDPR equal-prominence wants.
* feat(web): standalone first-run privacy consent banner
Replaces the Settings-dialog-as-onboarding hack with a dedicated
bottom-right banner card that mounts whenever the user has never made
a privacy decision (cfg.installationId === undefined). The banner is
prominent (anchored to the corner with a soft shadow) but
non-blocking, mirrors cookie-consent UX, and shares the project's
panel styling — same .modal-elevated background, --radius-lg corners,
--shadow-lg lift.
Wiring:
- App.tsx imports PrivacyConsentModal and renders it at the root,
gated on installationId === undefined && !settingsOpen so it doesn't
double up with the Privacy tab's own consent card when Settings is
already showing.
- Share / Don't share both go through handleConfigPersist, so the
resulting installationId + telemetry prefs land in localStorage and
the daemon at the same time, reusing the existing autosave plumbing.
- The previous attempt that pinned the welcome SettingsDialog to the
Privacy section is reverted; onboarding now stays focused on agent
configuration, and the consent decision lives in its own surface.
* fix(web): keep privacy banner visible while Settings welcome modal is open
The banner gated itself on `!settingsOpen` to avoid double-rendering
with the Privacy tab's consent card. But the first-run path opens the
Settings welcome modal automatically when `onboardingCompleted=false`,
which fired immediately after bootstrap — so the banner flashed for a
moment and then vanished behind the modal backdrop.
Drop the `!settingsOpen` clause so the banner stays mounted whenever
the user has not yet made a privacy decision, and bump its z-index
above the modal backdrop (200 vs 100) so first-run users can actually
reach the consent buttons. The minor visual overlap with the Privacy
tab's own card is fine: clicking either copy resolves both surfaces.
* copy(privacy): soften consent button labels
Banner action buttons now read "Help improve Open Design" / "Not now"
(en, with hand translations in zh-CN / zh-TW and English placeholders
in the other 13 locales) instead of "Share usage data" / "Don't share".
The new wording aligns the affirmative action with the kicker copy
("Help us improve Open Design") and reads less alarming, while the
disclosure list above still names both data categories explicitly so
the consent stays informed under GDPR. The decline button stays as a
soft "Not now" rather than an aggressive "Don't share" so the reject
path doesn't read as hostile to the user.
No structural change — the two-cell seg-control still gives the buttons
identical visual weight, and the underlying side-effects are unchanged
(installationId is generated on Help / nulled on Not now, and the
telemetry prefs flip the same way).
* feat(telemetry): expand trace fields for evals & dataset construction
Each Langfuse trace now ships the full per-turn + per-install fact
sheet that the eval/dataset workflow needs, instead of only the bare
turn id + token count from before. Everything below is gated by
`prefs.metrics === true`; nothing here is content (those gates remain
separate).
Per-turn:
- model — first-class generation.model field, drives Langfuse cost
lookup and model-grouping in the UI; also mirrored in trace.metadata
and trace.tags so list-view filters work.
- reasoning — generation.modelParameters.{ reasoning } so the Model
Parameters card lights up; mirrored in metadata.
- skillId / designSystemId — metadata + tags, so dataset slices can
group by which skill/DS produced which output.
Per-process / build (constant within one daemon run, cached at start):
- appVersion / appChannel / packaged from app-version.ts
- nodeVersion (process.version), os (platform()), osRelease,
arch (os.arch())
- clientType — desktop vs web, derived from a new X-OD-Client header
the web layer sets in providers/daemon.ts (with a User-Agent sniff
fallback for third-party callers).
Plumbing:
- startChatRun stashes model / reasoning / skillId / designSystemId
on the run object alongside the existing userPrompt stash.
- POST /api/runs reads X-OD-Client and stores run.clientType.
- langfuse-bridge collects RuntimeInfo once per process and merges
per-run client carrier; ReportContext gains optional `turn` +
`runtime` blocks; existing fields stay backward compatible.
Spec gains a "Telemetry Fields Catalog" section enumerating every
field, its source, and the gate it lives under, so the eval team has a
single place to look up what's available without reading the trace
schema by example.
Tests:
- new langfuse-trace tests cover turn tags, runtime tags, generation
model/modelParameters promotion, modelParameters omission when
reasoning is unset, and metadata mirroring.
- langfuse-bridge gains an end-to-end "turn-level config" test that
threads model/reasoning/skill/DS/clientType + appVersion through
the bridge and asserts the Langfuse payload shape.
- existing tests adjusted to tolerate host-dependent os tag.
* copy(privacy): trim Share button to verb phrase only
"Help improve Open Design" overflowed the equal-width 2-cell
seg-control on the consent banner — the product name is already in
the kicker + headline above the buttons, so the button itself only
needs the verb phrase. Drop the product name from all locales:
- en: Help improve Open Design → Help improve
- zh-CN: 帮助改进 Open Design → 帮助改进
- zh-TW: 協助改進 Open Design → 協助改進
The decline button ("Not now" / "暂不" / "暫不") was already short, so
the two buttons now have comparable length and the equal-prominence
seg-control fits cleanly. Standalone Settings → Privacy panel uses
the same labels for consistency.
* fix(web): defer Settings welcome modal until privacy decision is made
Previously bootstrap raced two surfaces against each other on first
launch: the privacy consent banner (gated on installationId ===
undefined) and the Settings welcome modal (gated on
onboardingCompleted === false). The banner's higher z-index kept it
above the backdrop visually, but having two foreground surfaces at
once is still confusing UX.
Sequence them instead: bootstrap only opens the welcome modal when
the user has *already* resolved consent (installationId !== undefined).
Until then the banner owns the foreground alone. Once the user clicks
Help improve / Not now, the corresponding handler hands off to the
welcome modal if onboarding is still pending. End state matches what
it was before — just without the simultaneous-render flash.
* debug(privacy): log banner gate state to track sudden disappearance
Two console.log points to find which setCfg call (or stale bundle) is
flipping cfg.installationId from undefined to a value while the banner
is visible. To remove once the regression is reproduced.
* fix(privacy): keep installationId + telemetry out of localStorage
Daemon is now the single source of truth for the privacy decision.
Why this matters: the consent banner gates on
\`config.installationId === undefined\`, but loadConfig() merges
localStorage on top of the daemon's reply, so a stale uuid in
\`open-design:config\` (left over from a previous opt-in) was
re-hydrating the React state and immediately syncing back to the
daemon — defeating "Delete my data" and re-suppressing the banner
within milliseconds of every page load.
The deeper reason to fix it here, not just patch the gate: a privacy
identifier persisted in browser storage that the user can't see or
clear without DevTools is a compliance liability. Anything users can
revoke needs one canonical place to store it. Daemon \`app-config.json\`
already serves that role for everything else gated through
syncConfigToDaemon, so installationId + telemetry now ride that path
exclusively:
- saveConfig() strips both keys before writing localStorage.
- loadConfig() strips both keys when reading older stale payloads,
so existing installs migrate transparently on next launch.
- syncConfigToDaemon() / mergeDaemonConfig still round-trip them, so
the React state stays in sync with the daemon as before.
Net effect: clearing app-config.json (or hitting "Delete my data") now
fully resets the install identity, with no residual cohort key in
browser storage.
* feat(privacy): scrub secrets + PII from prompt/output before send
When prefs.content is on, daemon now runs the prompt and assistant
text through a regex scrubber (apps/daemon/src/redact.ts) before
posting to Langfuse. The scrubber is the simplest thing that gives
the user-facing copy a truthful claim — pure regex, zero new
dependencies, fully auditable in this Apache-2.0 repo (vs. pulling a
single-maintainer 5-month-old npm package into a core process).
Categories covered (each replaced with [REDACTED:<kind>]):
- Anthropic / OpenAI sk- keys (incl. proj/live/test/ant variants)
- Langfuse pk-lf- / sk-lf- (specific rule wins over generic sk-)
- GitHub gh[opsur]_ tokens
- AWS access key ids (AKIA + 16 uppercase)
- Google API keys (AIza + 35)
- Slack xox[abprs]- tokens
- Stripe live/test keys
- JWT header.payload.signature triples
- Bearer-header values (scheme word stays readable)
- Emails, IPv4, US-style phone numbers
- Credit cards — 13–19 digit runs that pass a Luhn check, so order ids
and unix-nanos timestamps that fail Luhn pass through unchanged
Not covered, stated openly in spec + i18n: names, postal addresses,
business-secret semantics, raw 40-hex tokens (too high a false-positive
cost for artifact slugs). Those would require an ML layer.
Wired in:
- apps/daemon/src/redact.ts — exports redactSecrets() +
redactSecretsWithCounts() helper for future audit-summary metadata.
- apps/daemon/src/langfuse-bridge.ts — runs both prompt and output
through redactSecrets() before they reach the trace builder.
- 18 unit tests cover every pattern plus negative cases (Luhn-failing
digit runs, out-of-range IPv4 octets, idempotence on re-redacted
text, ordinary prose passthrough).
- i18n privacyContentHint on en + zh-CN + zh-TW (plus 14 locale
placeholders) enumerates the categories so the consent disclosure
matches the implementation — the GDPR informed-consent requirement.
- spec gains a Pre-send Redaction subsection with the regex shape
table + intentional non-coverage list.
Drive-by: dropped the [privacy] debug logs that traced the now-fixed
bootstrap regression.
* fix(telemetry): make Langfuse reporting resilient
* feat(telemetry): nest Langfuse turn observations
* feat(telemetry): emit Langfuse tool spans
* fix(telemetry): report after finalized message writes
* fix(telemetry): honor persisted terminal status
* fix(web): let consent banner yield page clicks
* fix(telemetry): report current turn prompt only
|
||
|
|
d592f6087f |
feat(mcp): external MCP client with daemon-managed OAuth and 39 design-focused templates (#898)
* feat(mcp): add external MCP client with daemon-managed OAuth and 17 design-focused templates
Open Design now acts as an MCP CLIENT and surfaces tools from third-party
MCP servers to the underlying agent (Claude Code, Hermes, Kimi).
Daemon
- New mcp-config / mcp-oauth / mcp-tokens modules: persist server entries
to .od/mcp-config.json, run the OAuth dance for HTTP/SSE servers
end-to-end on the daemon (so cloud deployments work and tokens
survive across turns), and inject Authorization: Bearer headers into
the per-spawn .mcp.json the daemon writes for Claude Code (or the
ACP mcpServers map for Hermes/Kimi).
- /api/mcp/servers and /api/mcp/oauth/{start,status,disconnect}
endpoints, plus spawn-time wiring in agents that hands the configured
servers to the active agent CLI.
- System-prompt directive for connected external MCPs so the model
does not chase Claude Code's synthetic *_authenticate /
*_complete_authentication tools when the Bearer is already pinned.
Web
- Settings -> External MCP servers panel with per-row OAuth Connect /
Disconnect / Refresh affordances and per-row template hints.
- New "Add server" picker categorized into 7 groups
(image-generation, image-editing, web-capture, ui-components,
data-viz, publishing, utilities) with a search box, sticky close
button, collapsible <details> sections (auto-expand on search),
60vh capped scroll region, and a pinned Custom-server footer.
- ChatComposer /mcp slash and MCP picker button forward to the new
Settings tab; AssistantMessage renders MCP tool calls inline;
markdown autolinker handles bare http(s) URLs (incl. OAuth links)
before italic markers so OAuth callback URLs do not get
italic-fragmented mid-token.
Contracts
- packages/contracts/src/api/mcp.ts owns the wire shapes
(McpServerConfig, McpTemplate with stable McpTemplateCategory
enum, McpServersResponse, OAuth start/status/disconnect bodies, the
postMessage payload from the OAuth callback).
Templates (17 built-in)
- image-generation: Higgsfield (OpenClaw, OAuth HTTP), Pollinations,
Allyson (animated SVG), AWS Bedrock Image (uvx).
- image-editing: Imagician, ImageSorcery.
- web-capture: just-every screenshot-website-fast, ScreenshotOne.
- ui-components: 21st.dev Magic, shadcn/ui, FlyonUI.
- data-viz: AntV Chart, Mermaid.
- publishing: EdgeOne Pages.
- utilities: Filesystem, GitHub, Fetch.
Tests
- apps/daemon/tests/mcp-{config,oauth,tokens,spawn}.test.ts cover
storage round-trip, OAuth helpers, token persistence, spawn-time
wiring, every template's transport / command / args / env-field
invariants, and the canonical category enum.
- apps/web/tests/runtime/markdown.test.tsx covers the new autolinker
ordering rules.
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(mcp): add 21 more design-focused templates and a `design-systems` category
Expands the built-in MCP picker from 17 to 38 templates so users can compose
the full Open Design craft loop (design-system intake → generate → edit →
audit → publish) without leaving the Settings dialog. Every install spec is
verified live against the upstream README; templates that needed Go binaries,
multi-step `init` ceremonies, or massive runtime stacks (PostgreSQL + Redis
+ Ollama) are intentionally deferred so picking a template still resolves to
a working server in one click.
New `design-systems` category between `web-capture` and `ui-components`
(reflects the upstream-of-components position in the workflow). Mirrored in
`McpTemplateCategory` on both contracts and daemon, and `CATEGORY_ORDER` on
the web side.
New templates by category:
- image-generation (+4): prompt-to-asset (icons / favicons / OG / logos with
free-tier routing across Cloudflare AI / NVIDIA NIM / HF / Stable Horde),
Nano Banana (hosted streamable HTTP, virtual try-on + product placement),
Seedream (hosted streamable HTTP, ByteDance Seedream v3-v5 + SeedEdit),
fal.ai (uvx, 600+ models incl. FLUX / Kling / Hunyuan / MusicGen).
- image-editing (+3): Photopea (34 layered-editor tools — closes the PSD
gap), Topaz Labs (AI upscale / denoise / sharpen), Transloadit (86+ media
pipeline robots).
- web-capture (+1): Pagecast (browser → demo GIF / MP4 with auto-zoom).
- design-systems (+4, NEW category): Figma-Context (Framelink, designs →
code), Design Token Bridge (Tailwind ⇄ CSS ⇄ Figma ⇄ M3 / SwiftUI / W3C
DTCG + WCAG contrast), Design System Extractor (Storybook scrape),
Aesthetics Wiki (cottagecore / dark-academia / y2k / … moodboards).
- data-viz (+2): MCP Dashboards (45+ chart types + KPI dashboards),
Excalidraw Architect (hand-drawn architecture diagrams).
- publishing (+6): PageDrop, PDFSpark, OGForge, QRMint, Slideshot
(HTML → PDF / PPTX / PNG with 7 themes), Deckrun (Markdown → PDF / video,
hosted free tier with no key required).
- utilities (+1): A11y axe-core (WCAG 2.0/2.1/2.2 + color-contrast + ARIA).
Tests cover every new template's wiring (command, args, env / header
required-vs-optional, secret flag), the category enum invariant, and
in-category declaration order for image-generation, design-systems and
publishing buckets where the order is what users see in the picker. 21 new
test cases pass; full mcp-config suite is green.
Templates intentionally deferred (documented in PR body): figma-use
(needs Figma desktop with --remote-debugging-port=9222), m-moire (multi-step
`memi suite init` + daemon ceremony), gemini-media-mcp + trident-mcp (Go
binaries — no npx / uvx path), Pixelle-MCP (full app with web UI + ComfyUI
backend), storybook-addon-mcp (lives inside user's Storybook, not standalone),
primitiv (multi-step init / build / serve), ReftrixMCP (PostgreSQL + Redis +
Ollama + DINOv2), narasimhaponnada/mermaid (overlap with peng-shawn).
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(mcp): add figma-use template (write designs from chat) under design-systems
figma-use is the natural counterpart to Figma-Context already in this PR:
where Framelink reads Figma designs into the model, figma-use writes back
into the canvas (90+ tools — create frames / text / components / variants,
render JSX into Figma, export PNG/SVG, query nodes via XPath, lint for
WCAG / auto-layout / hardcoded colors, analyze design systems).
Wired as an HTTP MCP template (`http://localhost:38451/mcp`) because
`figma-use mcp serve` only exposes HTTP — there's no stdio mode in the
upstream `serve.ts`. No API key. Two prerequisites the user owns are
spelled out in the description so picking the template still resolves to
a working server: (1) start Figma with `--remote-debugging-port=9222`
(or `figma-use daemon start --pipe` on Figma 126+), and (2) leave
`npx figma-use mcp serve` running in a terminal.
Inserted between `design-system-extractor` and `aesthetics-wiki` so the
design-systems category reads as a workflow: read existing design (Figma
Context) → translate tokens (Token Bridge) → extract from Storybook
(Extractor) → write back to Figma (figma-use) → break creative block
(Aesthetics Wiki).
Tests cover the new template's transport (`http`), endpoint URL, the
empty header-fields invariant (no auth required), and bump the
design-systems group order to include it.
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(settings): i18n the External MCP / MCP server / Connectors sidebar entries and make the dialog header track the active section
The External MCP sidebar entry this PR introduces was hardcoded English
("External MCP / Add MCP tools (Higgsfield, GitHub…)"). Same for the
adjacent Connectors and MCP server entries. The dialog header was also
pinned to "Execution & model" copy, so opening Settings → External MCP
showed a header that lied about which section the user was on.
Adds six translation keys — `settings.connectorsTitle/Hint`,
`settings.mcpServerTitle/Hint`, `settings.externalMcpTitle/Hint` — and
translates them across all 17 locales (ar, de, en, es-ES, fa, fr, hu, id,
ja, ko, pl, pt-BR, ru, tr, uk, zh-CN, zh-TW).
`SettingsDialog` now derives the header title/subtitle from the active
section (11 sections total) instead of a single hardcoded pair, so each
section renders an honest header.
Co-authored-by: Cursor <cursoragent@cursor.com>
* test(e2e): pin level: 3 on dialog heading lookups for Pets and Connectors
CI's Validate workspace job (#1479) failed two Playwright cases with the
strict-mode violation:
getByRole('dialog').getByRole('heading', { name: 'Pets' })
resolved to 2 elements:
1) <h2>Pets</h2>
2) <h3>Pets</h3>
Same root cause as the unit-test fix already in this PR: the dynamic
dialog `<h2>` now echoes the section's own `<h3>` because the dialog
header tracks the active section. Disambiguate to `level: 3` so each
assertion still pins the section heading specifically (which is what
the test intends to verify).
Audit of the rest of e2e/ for `dialog.getByRole('heading', ...)` —
settings-api-protocol.test.ts looks for "OpenAI API" / "Anthropic API"
section h3s which never appear in the dialog `<h2>` (always
"Execution & model"), so those stay safe.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(mcp): bind OAuth refresh to the issuing client and skip stale tokens
Persist the OAuth client context (token endpoint, client_id, client_secret,
issuer, redirect_uri, resource) alongside the bearer token so refresh hits
the same client the refresh_token was bound to (RFC 6749 §6). The previous
refresh path re-ran beginAuth with a dummy OOB redirect URI, which kept
getOrRegisterClient from finding the original DCR client and made
providers reject the refresh on the next chat turn. Refreshes now reuse
the persisted endpoint/client pair directly.
Also stop injecting expired access tokens at spawn time when refresh is
unavailable or fails. Pinning a stale Bearer made every Claude MCP call
401 while the prompt still treated the server as connected; on that path
we now skip the entry and let the UI surface a reconnect.
Generated-By: looper 0.6.1 (runner=fixer, agent=claude-code)
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
|
||
|
|
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 |
||
|
|
e14b8092ea |
feat: add Orbit activity summaries (#681)
* feat: add Orbit activity summaries * fix(orbit): make runs navigable while agent continues * fix(web): widen minimum chat panel * feat: support Orbit template selection * fix(daemon): avoid bogus skill side-file preflight * fix(web): collapse orbit artifact project cards * fix(web): preserve orbit project card titles * fix: improve Orbit run daily briefing * fix: handle Orbit digest data failures * fix: load Orbit templates and connector tools reliably * fix: keep Orbit summary counts consistent Generated-By: looper 0.6.1 (runner=fixer, agent=opencode) * fix: apply Orbit template skill context * fix: cache and curate connector tools for Orbit * fix: align Orbit defaults and connector discovery * fix: simplify Orbit template settings * fix: move connectors into settings * fix: compact connector settings catalog * fix: address Orbit PR feedback Generated-By: looper 0.6.1 (runner=fixer, agent=opencode) * fix: address Orbit PR feedback Generated-By: looper 0.6.1 (runner=fixer, agent=opencode) * fix: address Orbit PR feedback Generated-By: looper 0.6.1 (runner=fixer, agent=opencode) * fix: address Orbit PR feedback Generated-By: looper 0.6.1 (runner=fixer, agent=opencode) * fix: address Orbit PR feedback Generated-By: looper 0.6.1 (runner=fixer, agent=opencode) * fix: address Orbit PR feedback Generated-By: looper 0.6.1 (runner=fixer, agent=opencode) * fix: address Orbit PR feedback Generated-By: looper 0.6.1 (runner=fixer, agent=opencode) * fix: address Orbit PR feedback Generated-By: looper 0.6.1 (runner=fixer, agent=opencode) * fix: prevent connector action button from stretching into pill The icon-only connect/disconnect buttons in the embedded connectors catalog inherited min-width: 92px / 106px from the non-embedded pill rules, overriding the 24px square sizing and causing the buttons to overlap the card head text. Reset min-width to 0 in the embedded icon-only rule so the compact square layout holds. * fix(web): align live artifact file rows * fix: clean up Orbit connector settings lifecycle Generated-By: looper 0.6.2 (runner=fixer, agent=opencode) * fix: address Orbit review regressions Generated-By: looper 0.6.2 (runner=fixer, agent=opencode) * feat(web): localize Orbit and connector settings * feat(web): gate Orbit runs without connectors * feat(web): refine connector settings UX * feat(web): safeguard Composio key clearing * fix(web): refresh Composio tool badges * feat(web): show connector logos * feat(daemon): localize Orbit prompt window * fix(daemon): clarify blocked connector callback closes * test(daemon): harden flaky async probes * fix(web): align Indonesian connector locale keys * test(web): align connector browser props * fix(web): preserve explicit credential clears Generated-By: looper 0.6.2 (runner=fixer, agent=opencode) * fix(daemon): time out Composio logo proxy fetches Generated-By: looper 0.6.2 (runner=fixer, agent=opencode) * fix(web): localize Indonesian connector settings copy Translate the new connector settings strings in the Indonesian locale and lock them with a regression test so this surface no longer silently falls back to English. Generated-By: looper 0.6.2 (runner=fixer, agent=opencode) * fix(web): preserve discovered connector tools Generated-By: looper 0.6.2 (runner=fixer, agent=opencode) * fix(web): preserve onboarding autosave completion Keep settings autosave from clearing onboarding completion after the close gesture, and expose the desktop main types from source so workspace validation can typecheck packaged imports without a prior desktop build. Generated-By: looper 0.6.2 (runner=fixer, agent=opencode) * fix(daemon): defer Composio catalog cache hydration Load persisted Composio catalog data only after the runtime data directory is configured so startup cannot read another namespace's cache. Add a regression test that exercises the module-load singleton path. Generated-By: looper 0.6.2 (runner=fixer, agent=opencode) * fix(web): treat discovery completion independently Generated-By: looper 0.6.2 (runner=fixer, agent=opencode) * fix(web): preserve latest settings draft on close Use the latest persisted settings draft when the dialog closes so onboarding completion does not race a stale daemon sync and overwrite newer Orbit/template selections. Generated-By: looper 0.6.2 (runner=fixer, agent=opencode) * fix(web): avoid syncing draft Composio key on Orbit run Generated-By: looper 0.6.2 (runner=fixer, agent=opencode) * fix(web): localize Orbit settings copy Translate the new Indonesian Orbit and autosave strings so the settings UI no longer falls back to English and the locale regression stays covered. Generated-By: looper 0.6.2 (runner=fixer, agent=opencode) * fix(web): prefer fresh connector catalog state Keep refetched connector status/auth data authoritative while retaining discovery-only tool metadata so the connectors UI stays consistent after refreshes. Generated-By: looper 0.6.2 (runner=fixer, agent=opencode) * fix(web): declare Indonesian locale fallback keys explicitly Generated-By: looper 0.6.2 (runner=fixer, agent=opencode) * fix(web): inline Indonesian fallback strings for CI Replace the Indonesian locale's per-key English lookups with explicit strings so workspace typecheck no longer depends on brittle build-mode resolution in CI. Add a regression test that blocks those per-key English lookups from reappearing in the CI-sensitive fallback sections. Generated-By: looper 0.6.2 (runner=fixer, agent=opencode) * fix(daemon): restrict proxied connector logos to image MIME types Reject non-image upstream logo responses so the daemon never serves third-party HTML from its localhost origin. Generated-By: looper 0.6.2 (runner=fixer, agent=opencode) * test(e2e): align settings dialog regressions Generated-By: looper 0.6.2 (runner=fixer, agent=opencode) * fix(web): decouple Orbit runs from media sync failures Generated-By: looper 0.6.2 (runner=fixer, agent=opencode) * fix(web): keep SPA catch-all export-compatible Disable dynamic catch-all params for the exported SPA shell so Next.js static builds can emit the root route again. Add a regression test covering the route config against the web export mode. Generated-By: looper 0.6.2 (runner=fixer, agent=opencode) * fix(web): preserve Orbit config and workspace routes Generated-By: looper 0.6.2 (runner=fixer, agent=opencode) * fix(daemon): block SVG in connector logo proxy Reject SVG and other unsafe proxied logo responses so third-party logo content cannot execute under the daemon origin, while keeping raster logo fetches working and making rejected responses non-cacheable. Generated-By: looper 0.6.2 (runner=fixer, agent=opencode) * fix(daemon): fall back to static catalog for empty cache Generated-By: looper 0.6.2 (runner=fixer, agent=opencode) * fix(web): disable Orbit run before connector gate resolves Generated-By: looper 0.6.2 (runner=fixer, agent=opencode) * fix(desktop): export shipped desktop types Point the desktop ./main type export at the generated declaration so installed consumers resolve the published file set. Generated-By: looper 0.6.2 (runner=fixer, agent=opencode) * fix(web): restore persisted question form selections Render historical submitted answers directly so reloaded question forms keep their locked selections visible. Generated-By: looper 0.6.2 (runner=fixer, agent=opencode) * fix(web): retry forced media sync autosave Generated-By: looper 0.6.2 (runner=fixer, agent=opencode) * fix(daemon): keep Composio logo timeout through body read Keep the Composio logo fetch timeout active until the response body is fully consumed so stalled body reads abort and clear the inflight cache entry. Add a regression test that proves a delayed body read times out and the next request can recover.\n\nGenerated-By: looper 0.6.2 (runner=fixer, agent=opencode) * fix(web): refresh Orbit gate after connector auth Re-check connector availability when the settings window regains focus so Orbit unlocks as soon as a connector finishes authenticating in the same settings session. Generated-By: looper 0.6.2 (runner=fixer, agent=opencode) * fix(daemon): keep connector detail tool lists intact Generated-By: looper 0.6.2 (runner=fixer, agent=opencode) * fix(daemon): ignore malformed Orbit summaries Generated-By: looper 0.6.2 (runner=fixer, agent=opencode) * fix(e2e): stabilize design-system multi-select flow Generated-By: looper 0.6.2 (runner=fixer, agent=opencode) * fix(daemon): cap Composio logo cache growth Bound the Composio logo cache with LRU eviction and expired-entry pruning so repeated untrusted logo requests cannot grow daemon memory without limit. Generated-By: looper 0.6.2 (runner=fixer, agent=opencode) * fix(daemon): bound proxied Composio logo payloads Generated-By: looper 0.6.2 (runner=fixer, agent=opencode) * fix(web): align autosave settings tests Generated-By: looper 0.6.2 (runner=fixer, agent=opencode) * fix(web): remove stray CSS conflict marker Generated-By: looper 0.6.2 (runner=fixer, agent=opencode) * fixer: address PR #681 follow-up items Generated-By: looper 0.6.2 (runner=fixer, agent=opencode) * fix(web): restore restart routes and connector flows * fix(web): keep SPA export route static * fix(web): stabilize chat scroll tests --------- Co-authored-by: lefarcen <935902669@qq.com> |
||
|
|
988fd6db5e |
feat: import existing local folder as project (#597) (#624)
* feat(contracts): types for folder-import endpoint
Add ImportFolderRequest, ImportFolderResponse to the public contract
surface. Extend ProjectMetadata with a baseDir field — when set, the
project's files live at this absolute path instead of .od/projects/<id>/.
Stored as the realpath() result so symlinks cannot redirect later writes.
Refs nexu-io/open-design#597
* feat(daemon): support metadata.baseDir for folder-rooted projects
Add resolveProjectDir() and metadata-aware variants of listFiles,
readProjectFile, writeProjectFile, ensureProject so a project's files
can live under metadata.baseDir (the user's chosen folder) instead of
.od/projects/<id>/. metadata.baseDir is opt-in — projects without it
keep the existing .od/projects/<id>/ behavior unchanged.
When listFiles walks a baseDir-rooted project, it skips conventional
build / install dirs (node_modules, .git, dist, build, .next, .nuxt,
.turbo, .cache, .output, out, coverage, __pycache__, .venv, vendor,
target, .od, .tmp) so the file panel stays focused on design content
instead of being dominated by lockfiles and node_modules.
Add detectEntryFile() — best-effort lookup for index.html or any
.html at the folder root, used by the import endpoint to seed the
initial active tab.
Refs nexu-io/open-design#597
* feat(daemon): add POST /api/import/folder endpoint
Creates a project rooted at the submitted local folder. metadata.baseDir
points at that folder and OD reads / writes there directly — no copy,
no shadow tree, mirroring how Cursor / Claude Code / Aider behave. The
user owns the workspace and is responsible for their own version
control.
Safety:
- baseDir is canonicalized via fs.promises.realpath() at import time so
user-controlled symlinks can't redirect later writes. resolveSafe
enforces the bounds check against the literal stored path; without
realpath, a symlink (e.g. ~/sneaky → /etc) would let writeProjectFile
escape the project tree at every later call because the OS follows
the symlink at open() time.
- Post-realpath lstat ensures the canonical target is itself a real
directory (defense-in-depth).
- The data directory (RUNTIME_DATA_DIR) and its descendants are
refused after symlink resolution so a redirect into the daemon's
own state can't masquerade as a project import.
The web client wires this through state/projects.ts → App.tsx,
landing the user on the auto-detected entry file when present.
Refs nexu-io/open-design#597
* feat(desktop): expose native folder picker to renderer
Adds an Electron preload script that exposes window.electronAPI.pickFolder
via contextBridge. Wires dialog.showOpenDialog through ipcMain so the
web UI can open a native folder selector for project import. Browser-only
users fall back to a text input for the absolute path (handled in the
web layer); the picker stays an optional convenience on the desktop
binary.
ipcMain.handle() registers handlers in an internal map that is not
exposed via eventNames(), so the natural-looking guard
if (!ipcMain.eventNames().includes('dialog:pick-folder')) ipcMain.handle(...)
is always true. On a second createDesktopRuntime() call (dev hot-reload,
packaged-vs-electron mode swap) the body re-runs and ipcMain.handle()
throws 'Attempted to register a second handler'. Use removeHandler()
+ handle() unconditionally — removeHandler() is a documented no-op
when nothing is registered, making the pair idempotent.
Includes *.cts in the apps/desktop tsconfig so the preload script is
typechecked.
Refs nexu-io/open-design#597
* feat(web): add 'From existing folder' option to New Project
UI surface for the import flow:
- A new 'Open folder' affordance in NewProjectPanel that uses the
native picker on Electron (window.electronAPI.pickFolder) and falls
back to an absolute-path text input in the browser.
- importFolderProject() in state/projects.ts: typed wrapper around
POST /api/import/folder using @open-design/contracts types.
- App.tsx wires the response: prepend the new project to the list,
navigate to it, and select the auto-detected entry file as the
active tab.
Skill / design-system pickers from the existing prototype tab are
reused — folder import is a project-creation flow, not a separate
project type.
Refs nexu-io/open-design#597
* docs(architecture): document folder-import endpoint
Adds POST /api/import/folder to the daemon API table and a 'Folder
import' section explaining the single-mode design (direct read/write
in metadata.baseDir, mirroring Cursor / Claude Code / Aider), the
realpath() canonicalization, the RUNTIME_DATA_DIR refusal, and the
SKIP_DIRS list applied to listFiles for baseDir-rooted projects.
Refs nexu-io/open-design#597
* test(daemon): unit + integration tests for folder import
Two new files:
apps/daemon/tests/folder-import-projects.test.ts (13 unit tests):
- resolveProjectDir behavior under all metadata combinations,
including the fallback when baseDir is relative and the
isSafeId-bypass when baseDir is set
- detectEntryFile: index.html priority, .html fallback, null when
no html, no descent into subdirs
- listFiles with metadata.baseDir: walk, SKIP_DIRS hides node_modules
/ .git / dist, back-compat for projects without baseDir
apps/daemon/tests/folder-import-route.test.ts (10 integration tests):
- Happy path: baseDir stored in metadata, importedFrom='folder',
conversation created, entry file detected
- Error paths: missing baseDir, empty, relative, non-existent,
pointing at a file
- Security: realpath canonicalization (the symlink test was the one
that surfaced the original /var vs /private/var mismatch in
RUNTIME_DATA_DIR comparison on macOS)
- Security: a symlink that resolves into RUNTIME_DATA_DIR is rejected
after realpath, not before
Refs nexu-io/open-design#597
* fix(daemon): wire baseDir metadata into chat + deploy reads
Two bugs caught in Codex automated review of #624:
1. chat-route was passing the metadata object directly as the listFiles
opts argument: `listFiles(PROJECTS_DIR, projectId, chatMeta)`. The
listFiles contract reads opts.metadata, not opts itself, so this
silently fell back to .od/projects/<id>/ instead of the imported
folder. existingProjectFiles was empty for baseDir-rooted projects.
Wrap as `{ metadata: chatMeta }`.
2. deploy.ts read project files via readProjectFile without the
metadata third argument, so for baseDir-rooted projects the deploy
and preflight endpoints would look in .od/projects/<id>/ and fail
with file-not-found instead of reading the imported folder. Thread
options.metadata through buildDeployFilePlan → readProjectFile and
pass project?.metadata at the two server.ts callsites
(`POST /api/projects/:id/deploy` and the preflight endpoint).
Add a regression test that locks the listFiles contract: passing a
bare metadata object as opts must NOT scan baseDir — it must fall back
to the standard project dir, otherwise callers can leak the wrong
folder by mistake.
Refs nexu-io/open-design#597, #624 (Codex review)
* fix(daemon): ensure correct metadata handling in folder import
Addressed issues with metadata handling in folder import functionality. Updated the listFiles and readProjectFile methods to correctly utilize the metadata.baseDir, ensuring that project files are read from the intended directory. Added regression tests to verify that passing a bare metadata object does not inadvertently scan the baseDir, maintaining the integrity of project file access.
Refs nexu-io/open-design#597
* fix(daemon): security hardening from Codex review of #624
P1 findings from automated review:
1. POST /api/projects + PATCH /api/projects/:id rejected
client-supplied metadata.baseDir. baseDir is privileged: it lets a
project root inside the user's filesystem, and the realpath() +
RUNTIME_DATA_DIR reentry checks live only on /api/import/folder.
Allowing it on the generic create/patch path lets an attacker
smuggle e.g. /etc through and bypass every import-time guard.
Both endpoints now refuse a baseDir field with 400.
2. resolveSafeReal() helper: realpath()s each candidate path (or its
longest existing prefix for write paths) and re-validates against
realpath(projectRoot). The original resolveSafe() only did a
string-prefix check, which was fooled by symlinks *inside* a
baseDir-rooted project. A repo containing 'assets -> /Users/me/.ssh'
passed the literal prefix check but readFile() followed the link
at open() time. resolveSafeReal() is now used by readProjectFile,
writeProjectFile, and deleteProjectFile.
3. Multer chat-upload destination now resolves to metadata.baseDir for
imported folder projects via a module-level lookup wired to db at
startServer() boot. Previously attachments landed in
.od/projects/<id>/ even for baseDir projects, so the agent (which
runs with cwd=baseDir) couldn't open them.
P2 findings:
4. searchProjectFiles threads metadata through listFiles +
resolveProjectDir so /api/projects/:id/search hits the right tree.
5. buildProjectArchive + buildBatchArchive now accept metadata so
'Download .zip' works for imported folder projects.
6. Watcher subscribe() resolves to baseDir for imported projects so
live-reload SSE actually fires when the user edits files in their
own folder. Registry stays keyed by the canonical directory.
7. Template snapshotting reads source-project files with metadata
so a template can be saved from a baseDir-rooted source.
Tests:
- Regression: POST /api/projects with metadata.baseDir → 400.
- Regression: descendant symlink (assets/leak.txt -> /etc/hosts) is
refused on the raw read endpoint.
Refs nexu-io/open-design#597, #624 (Codex P1+P2 review)
* fix(daemon): close two regressions found in #624 review round 2
@mrcfps caught two more correctness gaps:
1. Archive root symlink escape — buildProjectArchive accepts an optional
?root=<subdir> param to scope the zip to a subdirectory. The path was
resolved with the string-only resolveSafe(), so a directory symlink
inside an imported folder (docs -> /Users/me/.ssh) passed the prefix
check and collectArchiveEntries() then walked outside the project
tree. Switch to the symlink-aware resolveSafeReal() — the same one
that already protects raw read/write/delete paths. The walker itself
already skips dirent symlinks via !isDirectory && !isFile, so
canonicalizing the root is the only missing piece.
2. PATCH metadata wiped baseDir — updateProject() replaces metadata
wholesale. The previous guard only blocked an explicit baseDir
change, but a normal patch that *omits* baseDir (a UI editing
linkedDirs only sends { metadata: { kind, linkedDirs } }) silently
detached imported projects from their folder root. Subsequent
reads/writes/watch/deploy fell back to .od/projects/<id>.
Re-stamp the immutable folder-import fields (baseDir, importedFrom='folder')
from the existing project record onto the incoming patch when the
project is imported. A patch that supplies a *different* baseDir
still gets rejected as before; a patch that supplies the *same*
baseDir is accepted as a no-op. A patch on a non-imported project
that tries to set baseDir is also still rejected (preserves the
POST /api/projects guard from the previous round).
Tests:
- archive endpoint: ?root=<symlink-to-/etc> → 400.
- patch endpoint: PATCH that omits baseDir on an imported project keeps
baseDir intact (project still resolves to the user's folder after).
Refs nexu-io/open-design#597, #624 (Codex P1 round 2)
* fix(web): add Indonesian deploy provider copy
---------
Co-authored-by: INFINITY <valentyn.sotov@trendarena.app>
Co-authored-by: Siri-Ray <2667192167@qq.com>
|
||
|
|
555dbebfe2 |
fix(web): add alert when pdf export popup is blocked (#664)
Fixes PDF export feedback when popup blockers prevent opening the export preview. |
||
|
|
984797b3cd | fix: add DeepSeek v4 models to catalog (#722) | ||
|
|
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> |
||
|
|
33255a8fdf |
Fix agent CLI config and workspace focus mode (#604)
* fix agent CLI config and workspace focus mode * address CLI env review follow-ups |
||
|
|
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. |
||
|
|
c3d9136a0c |
Add live artifacts and Composio connector catalog (#381)
* docs: add live artifacts implementation spec * docs: align live artifacts implementation plan * Ralph iteration 1: work in progress * Ralph iteration 2: work in progress * Ralph iteration 3: work in progress * Ralph iteration 4: work in progress * Ralph iteration 5: work in progress * Ralph iteration 6: work in progress * Ralph iteration 7: work in progress * Ralph iteration 8: work in progress * Ralph iteration 9: work in progress * Ralph iteration 10: work in progress * Ralph iteration 11: work in progress * Ralph iteration 12: work in progress * Ralph iteration 13: work in progress * Ralph iteration 14: work in progress * Ralph iteration 15: work in progress * Ralph iteration 16: work in progress * Ralph iteration 17: work in progress * Ralph iteration 18: work in progress * Ralph iteration 19: work in progress * Ralph iteration 20: work in progress * Ralph iteration 21: work in progress * Ralph iteration 22: work in progress * Ralph iteration 23: work in progress * Ralph iteration 24: work in progress * Ralph iteration 25: work in progress * Ralph iteration 26: work in progress * Ralph iteration 27: work in progress * Ralph iteration 28: work in progress * Ralph iteration 29: work in progress * Ralph iteration 30: work in progress * Ralph iteration 31: work in progress * Ralph iteration 32: work in progress * Ralph iteration 33: work in progress * Ralph iteration 34: work in progress * Ralph iteration 35: work in progress * Ralph iteration 36: work in progress * Ralph iteration 37: work in progress * Ralph iteration 38: work in progress * Ralph iteration 39: work in progress * Ralph iteration 40: work in progress * Ralph iteration 41: work in progress * Ralph iteration 42: work in progress * Ralph iteration 43: work in progress * Ralph iteration 44: work in progress * Ralph iteration 45: work in progress * Ralph iteration 46: work in progress * Ralph iteration 47: work in progress * Ralph iteration 48: work in progress * Ralph iteration 49: work in progress * Ralph iteration 50: work in progress * Ralph iteration 51: work in progress * Ralph iteration 52: work in progress * Ralph iteration 53: work in progress * Ralph iteration 54: work in progress * Ralph iteration 55: work in progress * Ralph iteration 56: work in progress * Ralph iteration 57: work in progress * Ralph iteration 58: work in progress * Ralph iteration 59: work in progress * Ralph iteration 60: work in progress * Ralph iteration 61: work in progress * Ralph iteration 62: work in progress * Ralph iteration 63: work in progress * Ralph iteration 64: work in progress * Ralph iteration 65: work in progress * Ralph iteration 1: work in progress * Ralph iteration 2: work in progress * Ralph iteration 3: work in progress * Ralph iteration 4: work in progress * Ralph iteration 5: work in progress * Ralph iteration 6: work in progress * Ralph iteration 8: work in progress * Ralph iteration 9: work in progress * Ralph iteration 17: work in progress * Add Composio-backed connectors * Add Composio-backed connector catalog * Fix connector callback flow * Update live artifact connector refresh * Fix live artifact refresh updates * Improve live artifact viewer toolbar * Refine live artifact source tabs * Expand Composio connector catalog * Improve Composio connector browsing * Fix artifact refresh source safety checks Generated-By: looper 0.4.1 (runner=fixer, agent=opencode) * Fix live artifacts PR feedback Generated-By: looper 0.5.0 (runner=fixer, agent=opencode) * Fix live artifact preview CORS validation Generated-By: looper 0.0.0-dev (runner=fixer, agent=opencode) * Fix connector OAuth IPv6 loopback hosts Allow bracketed IPv6 loopback Host headers when deriving connector OAuth callback URLs so IPv6-bound daemons can complete connection flow. Generated-By: looper 0.0.0-dev (runner=fixer, agent=opencode) * Preserve live artifact refresh permissions Respect explicit refresh permission choices during live artifact create and update flows so revoked connector sources remain gated. Generated-By: looper 0.0.0-dev (runner=fixer, agent=opencode) * Fix live artifact preview cache freshness Generated-By: looper 0.0.0-dev (runner=fixer, agent=opencode) * Fix live artifact refresh validation Guard manual refreshes with local daemon checks and reject daemon_tool sources without a toolName before refresh execution. Generated-By: looper 0.0.0-dev (runner=fixer, agent=opencode) * Fix Composio credential invalidation Generated-By: looper 0.0.0-dev (runner=fixer, agent=opencode) * Fix live artifact CORS methods Generated-By: looper 0.0.0-dev (runner=fixer, agent=opencode) * Fix workspace validation Restore media config test isolation under Vitest setup data-dir overrides and add the missing French live artifact display copy so the workspace test suite stays aligned.\n\nGenerated-By: looper 0.5.2 (runner=fixer, agent=opencode) * Fix connector safety filtering Keep agent-preview connector listings aligned with execution safety policy and prune stale Composio OAuth state records before they accumulate. Generated-By: looper 0.5.2 (runner=fixer, agent=opencode) * Fix agent runtime cleanup Generated-By: looper 0.5.2 (runner=fixer, agent=opencode) * Fix live artifact daemon access Validate local-only live artifact routes against the peer socket address and pass daemon-resolved CLI paths to ACP MCP descriptors.\n\nGenerated-By: looper 0.5.2 (runner=fixer, agent=opencode) * Fix connector run limit pruning Evict stale connector rate-limit buckets so long-lived daemon processes do not retain per-run entries indefinitely.\n\nGenerated-By: looper 0.5.2 (runner=fixer, agent=opencode) * Fix connector compact schemas Generated-By: looper 0.5.2 (runner=fixer, agent=opencode) * Improve connector connection feedback * Adjust connector gate positioning * Fix live artifact refresh commits Avoid marking refresh candidates failed after snapshot or state persistence errors by deferring live artifact mutations until the durable refresh metadata is written. Also align connector OAuth callback host validation with daemon loopback handling.\n\nGenerated-By: looper 0.5.4 (runner=fixer, agent=opencode) * Improve connector search relevance * fix(daemon): harden connector connection state Require loopback daemon validation before connector connect side effects and only clear provider-owned connector statuses during credential reset. Generated-By: looper 0.5.4 (runner=fixer, agent=opencode) * fix(daemon): guard connector disconnect route Require local daemon request validation before connector disconnect side effects. Generated-By: looper 0.5.4 (runner=fixer, agent=opencode) * fix(daemon): guard composio config updates Generated-By: looper 0.5.4 (runner=fixer, agent=opencode) * fix(daemon): dispatch live artifacts mcp first Route the live-artifacts MCP server before the generic MCP CLI so od mcp live-artifacts starts the dedicated server instead of failing generic argument parsing.\n\nGenerated-By: looper 0.5.4 (runner=fixer, agent=opencode) * fix(daemon): handle integer connector schemas Allow JSON Schema integer connector inputs while preserving fractional-value validation so generated connector tool schemas accept valid page sizes and limits. Generated-By: looper 0.5.4 (runner=fixer, agent=opencode) * fix: align live artifact refresh error codes Generated-By: looper 0.5.4 (runner=fixer, agent=opencode) * Fix live artifact connector refresh flow * Update live artifact design cards * Add beta badge to live artifact form * Remove live artifact tile model * Fix live artifact refresh sync * Fix live artifact MCP refresh durability Generated-By: looper 0.5.4 (runner=fixer, agent=opencode) * Fix live artifact refresh safety Enforce persisted refresh opt-out and connector auto-read gating before refresh sources execute. Generated-By: looper 0.5.5 (runner=fixer, agent=opencode) |
||
|
|
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. |
||
|
|
bf533c3d72 |
fix(web): split execution mode tabs and align active chip visuals (#418)
* Refine settings API configuration UX * Fix PR validation issues * Address settings review edge cases * Address BYOK settings review feedback * Fix BYOK provider labels and model display |
||
|
|
179b23d7e7 |
Modernize multi-provider API proxy routing
Adds provider-specific API proxy routes for Anthropic, OpenAI-compatible, Azure OpenAI, and Google Gemini; normalizes provider SSE streams; updates web provider clients/settings/docs/tests; and forwards Gemini token limits. |
||
|
|
68493b7b72 |
feat(web): task completion sound + browser notification (#359)
Merged per maintainer approval. |
||
|
|
9d700ec74f |
feat(daemon): persist code agent startup (#255)
* feat(daemon): persist code agent startup * fix: complete all suggestions * fix: types for app config * chore: revert local origin * chore: format to single quotes * fix: duplicate headers * fix: isLocalSameOrigin rewriting issue --------- Co-authored-by: mrcfps <mrc@powerformer.com> |
||
|
|
816cc1d135 |
feat(settings): split API provider protocols (#250)
* feat(settings): split API provider protocols * fix(settings): preserve API protocol config * fix(settings): harden API protocol migration * fix(settings): set apiProviderBaseUrl during v1 migration for known providers |
||
|
|
6fa2077651 |
feat(web): add pet companion with Codex hatch-pet integration (#296)
* feat(web): add pet companion with Codex hatch-pet integration Introduces a customizable floating pet companion (overlay + entry-view rail + composer menu + dedicated Settings → Pets section) that supports built-in pets, user customization (glyph/image/spritesheet), and one-click adoption of pets packaged by the upstream Codex `hatch-pet` skill via a new `/api/codex-pets` daemon endpoint. Vendors the unmodified `hatch-pet` skill under `skills/hatch-pet/` and adds i18n strings across all locales. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(scripts): sync community Codex pets from public catalogs Adds `pnpm sync:community-pets` which fetches all pets from codex-pet-share.pages.dev (paginated Supabase Functions API) and j20.nz/hatchery (single-shot JSON), then writes each one as `<id>/pet.json` + `<id>/spritesheet.webp` under `\${CODEX_HOME:-\$HOME/.codex}/pets/`. The existing daemon `codex-pets` registry already scans that folder, so synced pets appear under Settings → Pets → Recently hatched and adopt with one click — no manual upload. Supports --source/--out/--force/--limit flags and validates magic bytes so HTML error pages never end up masquerading as `.webp` files. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(daemon): tighten codex-pets validation and document vendoring - sanitizeId now rejects ids that still contain `..` after collapsing, closing a defensive gap on the path-traversal guard for the `/api/codex-pets/:id/spritesheet` route. - listCodexPets emits the sanitised folder name as the public id so the download route resolves directly against the on-disk folder, even when `manifest.id` differs (manual drops, sanitiser-touched manifests). - Drop `@ts-nocheck` from `codex-pets.ts`; module is now strict-typed with explicit interfaces, an unknown-narrowed JSON.parse path, and a `pickString` helper guarding manifest fields one by one. - Restrict the spritesheet response CORS header to sandboxed-iframe callers (Origin: null) instead of unconditional `*`, matching the existing raw-file route pattern. Same-origin web traffic does not need the header (web proxies `/api/*` through the daemon). - Add `skills/hatch-pet/README.md` explaining the vendoring trade-off, provenance, and re-sync procedure. - Add `docs/codex-pets.md` covering where pets live, how to populate the registry without Codex installed, and the manifest contract. Generated-By: looper 0.4.0 (runner=fixer, agent=claude-code) * fix(i18n): add pet.* keys to Hungarian locale Hungarian locale was added on main after this branch diverged, so the new pet.* dictionary keys never landed there and tsc -b reports hu's Dict as incomplete once main is merged in. Generated-By: looper 0.4.0 (runner=fixer, agent=claude-code) * feat(web): atlas-driven pet animations + bundled community pets Builds on the existing pet companion (#296) with a richer animation loop, a curated set of community pets that ship with the repo, and a one-click sync into ~/.codex/pets/. - Atlas-mode rendering: PetSpriteFace can now play the full Codex 8x9 sprite atlas and swap rows from a JS-driven frame index. PetOverlay classifies pointer interactions (idle / hover / drag-direction / long-idle waiting) and maps them to the matching atlas row, so the pet waves on hover, runs on drag, and falls into a waiting pose after 6s of stillness. Single-strip pets keep their existing CSS steps() animation, with the steps timing fixed to jump-none so frame cells line up on cell boundaries. - Atlas adoption: PetSettings exposes both "Use full atlas (animated)" and "Freeze to this row" — full mode keeps every row for the interaction state machine, single-row mode crops one strip via the existing canvas helper. New prepareCodexAtlas downscales the atlas to a localStorage-friendly PNG while preserving the grid layout. - Settings tabs: pet sources are now split into Built-in / Custom / Community tabs so each origin gets its own dedicated surface. - Bundled pets: scripts/bake-community-pets.ts seeds a curated set (clippit, dario, nyako-shigure, slavik, trump, tux, yelling-dario, yorha-sit-2b) into assets/community-pets/. The daemon scans this alongside the user's ~/.codex/pets/ root, with user pets winning when ids collide. CodexPetSummary gains a `bundled` flag so the UI can tag those cards with a "Bundled" pill. - One-click community sync: daemon-side port of sync-community-pets exposed via POST /api/codex-pets/sync. Returns the same wrote/skipped/failed/total summary the CLI prints. Web Pet settings surface this as a "Download community pets" button under the Community tab. - Avatar dropdown + hide rail: EntryView's avatar button is now a small menu (mirrors the project-view AvatarMenu) with toggles for hiding/showing the pet rail and opening Settings. PetRail gets a matching × button for the same hide flow. - Locales: 7 new pet.* keys for tabs, sync, hide/show, atlas full mode, and the Bundled pill — translated into all 13 supported locales. Typechecks pass across all workspace packages; daemon + web vitest suites stay green. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(web): bundled-pets built-in tab, ambient atlas animations, and community sync button The Built-in tab now sources its catalog from the bundled spritesheets at `assets/community-pets/` instead of the eight emoji placeholders that felt boring next to the Codex hatch-pet atlases. - Daemon: `listCodexPets` flags `bundled: true` by curated-set membership in `assets/community-pets/`, not by which folder the sprite happened to be read from. Previously a fully-synced user inbox preempted every bundled id and left the tab empty. - Settings → Pets → Built-in renders the same sprite-card grid as Community, filtered by `bundled: true`, and reuses the existing `adoptCodexPet` flow. Community tab filters to non-bundled so the curated set never appears twice. - Community tab gains the long-promised "Download community pets" trigger that calls `/api/codex-pets/sync` and shows an inline status line for the run summary. Strings already existed in every locale; we just plumbed the button. - `PetOverlay` gets ambient atlas-row choreography — while idle, the overlay occasionally swaps `idle` for a random non-idle row (wave / hop / look) so the pet doesn't feel frozen. User gestures cancel the beat and take over instantly. `pickAmbientRow` lives next to `pickAtlasRow` so both row pickers share the fallback discipline. - One-shot `migrateCustomPetAtlas` heals configs adopted before the overlay learned row switching by re-downloading the full spritesheet so hover / drag / ambient variety light up on next launch. - `BUILT_IN_PETS` is now an empty array (the type stays for backwards compat); legacy configs whose `petId` still points at an emoji id (`mochi`, `pixel`, …) fall back to the user's custom slot in `resolveActivePet` so the overlay never renders blank. - i18n: refresh `pet.tabBuiltInHint` (drop "emoji companions") and add `pet.builtInEmpty` across all locales. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
1592beb961 |
feat(web): add dark mode support with system/light/dark toggle (#259)
* feat(web): add dark mode support with system/light/dark toggle Closes #93. Adds a three-way theme toggle (System / Light / Dark) to Settings > Appearance. The chosen preference is persisted in localStorage alongside the existing config and applied immediately via a data-theme attribute on <html>. Implementation - AppTheme type ('system' | 'light' | 'dark') added to AppConfig - CSS custom properties for the dark palette defined under [data-theme="dark"] and @media (prefers-color-scheme: dark) so the System option follows the OS without JavaScript - Appearance section added to SettingsDialog with a sun-moon icon - Several hardcoded light-only colour values replaced with CSS variable equivalents so they adapt correctly in dark mode - i18n: appearance/theme strings added to all 11 supported locales Note: theme variables are currently inlined in index.css. A follow-up could extract them into per-theme files to support community themes (e.g. Catppuccin) -- happy to explore that direction if it fits the project roadmap. Also fixes (pre-existing) - Syntax error in locales/tr.ts that was masking missing-key errors - Missing translation keys in de, es-ES, ja, tr locales - locales.test.ts EXPECTED_LOCALES not updated after Korean was added * fix(web): apply theme before first paint to prevent FOUC Add a synchronous inline script in layout.tsx that reads the saved theme from localStorage and sets data-theme on <html> before React hydrates, eliminating the flash of unstyled content that occurred when the theme was only applied inside a useEffect. * fix(web): use useLayoutEffect for theme sync to avoid 1-frame flash useEffect fires after the browser paints, so switching themes in Settings could show a single-frame flicker of the old theme. useLayoutEffect fires synchronously before the paint, making theme transitions atomic. Safe because the component tree is rendered with ssr:false. * fix(web): fix 3-column theme selector layout and add seg-title overflow guard .seg-control used a hardcoded 2-column grid; the theme toggle has 3 buttons so Dark was falling onto a second row. Switch to a CSS custom property (--seg-cols, default 2) so each usage can declare its own column count. Also add white-space/overflow/text-overflow to .seg-title so long translations never overflow their cell. * fix(i18n): add missing maxTokens keys to Dict and Korean locale settings.maxTokens and settings.maxTokensHint were used in SettingsDialog and present in all locale files but absent from the Dict interface, causing typecheck to fail. Also adds the two keys to the Korean locale to restore full locale parity. Fixes the blocking typecheck failure flagged in PR review. * fix(i18n): remove duplicate maxTokens keys in ko.ts and Dict Rebase introduced duplicate settings.maxTokens and settings.maxTokensHint in both ko.ts and types.ts (Dict interface), causing TS1117/TS2300 errors. Remove the second occurrence of each to restore clean typecheck. * test(i18n): add pl to EXPECTED_LOCALES in locales.test.ts Polish locale was added upstream in #273; update the hard-coded list so the locale parity test passes on CI. * fix(web): live-preview theme in Settings before Save AppearanceSection now applies data-theme to <html> immediately on toggle via useLayoutEffect, so the page re-themes in real time while the dialog is open. SettingsDialogs useLayoutEffect cleanup reverts to the original saved theme when the dialog is dismissed without saving. |