From bf88a29656ce9255592855ea821c574f23cdad3d Mon Sep 17 00:00:00 2001 From: yiliang114 <1204183885@qq.com> Date: Fri, 14 Aug 2026 08:12:33 +0000 Subject: [PATCH] feat(desktop): remove packages/desktop after OpenWork fork; keep the Tauri upgrade bridge MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit OpenWork (modelstudioai/openwork) has forked the Electron desktop code and is self-contained now, so retire the Electron package and its release/sync machinery from this repo: - Delete packages/desktop (Electron app, live-host app, bun workspace). - Retire scripts/desktop-openwork-sync.ts and the desktop-openwork-sync root script; the OpenWork sync is no longer needed. - Retire .github/workflows/live-host.yml, live-host-release.yml and sync-live-host-to-oss.yml; live-host releases now live in OpenWork. The CLI-side packages/cli/src/serve/live code stays for now (separate cleanup). - Retire scripts/check-voice-guard-sync.js (cli<->desktop parity only) and its CI step. - Clean up remaining references: root package.json workspaces negation and package-lock.json, eslint/prettier/yamllint ignores, architecture docs, web-shell skill descriptions, and review-lib workspace fixtures/comments (renamed to point at packages/desktop-shell, the remaining negation). Deliberately kept: the Electron->Tauri upgrade bridge — desktop-release.yml (incl. the electron_bridge input), create-electron-bridge-manifest.mjs, sync-desktop-to-oss.yml (mirrors Tauri desktop-shell artifacts only) and everything under packages/desktop-shell. --- .github/workflows/.size-baseline | 3 - .github/workflows/ci.yml | 4 - .github/workflows/live-host-release.yml | 363 - .github/workflows/live-host.yml | 148 - .github/workflows/sync-live-host-to-oss.yml | 222 - .prettierignore | 1 - .../desktop-pet/scripts/gen_spritesheet.py | 2 +- .qwen/skills/openwork-desktop-sync/SKILL.md | 102 - .yamllint.yml | 2 - docs/developers/architecture.md | 34 +- eslint.config.js | 1 - integration-tests/tsconfig.json | 2 +- package-lock.json | 1 - package.json | 3 - .../src/commands/review/build-test.test.ts | 14 +- .../src/commands/review/lib/diff-plan.test.ts | 2 +- .../review/lib/workspace-scope.test.ts | 8 +- .../commands/review/lib/workspace-scope.ts | 2 +- .../commands/review/lib/workspaces.test.ts | 83 +- .../cli/src/commands/review/lib/workspaces.ts | 20 +- .../src/commands/review/test-efficacy.test.ts | 32 +- .../cli/src/services/voice-transcriber.ts | 3 - packages/core/src/tools/agent/agent.ts | 6 +- .../skills/desktop-brand-builder/SKILL.md | 151 - .../scripts/brand-create.ts | 381 - .../.agents/skills/desktop-develop/SKILL.md | 121 - .../references/harness-principles.md | 42 - .../.agents/skills/desktop-pet/SKILL.md | 235 - .../desktop-pet/scripts/gen_spritesheet.py | 558 - packages/desktop/.dockerignore | 50 - .../.github/ISSUE_TEMPLATE/bug_report.yml | 123 - .../ISSUE_TEMPLATE/feature_request.yml | 36 - .../.github/workflows/desktop-release.yml | 592 - packages/desktop/.gitignore | 75 - packages/desktop/.nvmrc | 1 - packages/desktop/CODE_OF_CONDUCT.md | 28 - packages/desktop/CONTRIBUTING.md | 103 - packages/desktop/LICENSE | 191 - packages/desktop/NOTICE | 23 - packages/desktop/README.md | 189 - packages/desktop/SECURITY.md | 58 - packages/desktop/TRADEMARK.md | 103 - packages/desktop/apps/cli/package.json | 25 - packages/desktop/apps/cli/src/client.test.ts | 409 - packages/desktop/apps/cli/src/client.ts | 239 - .../desktop/apps/cli/src/commands.test.ts | 391 - packages/desktop/apps/cli/src/index.ts | 1922 -- packages/desktop/apps/cli/src/run.test.ts | 427 - .../desktop/apps/cli/src/server-spawner.ts | 146 - packages/desktop/apps/cli/tsconfig.json | 30 - packages/desktop/apps/electron/.gitignore | 11 - packages/desktop/apps/electron/README.md | 28 - .../electron/build/entitlements.mac.plist | 17 - .../desktop/apps/electron/components.json | 19 - .../apps/electron/electron-builder.yml | 192 - .../__tests__/no-hardcoded-z-index.test.ts | 44 - .../eslint-rules/no-direct-file-open.cjs | 65 - .../no-direct-navigation-state.cjs | 103 - .../eslint-rules/no-direct-platform-check.cjs | 59 - .../no-hardcoded-path-separator.cjs | 65 - .../eslint-rules/no-hardcoded-z-index.cjs | 133 - .../no-inline-source-auth-check.cjs | 85 - .../electron/eslint-rules/no-localstorage.cjs | 101 - .../eslint-rules/no-nonstandard-shadows.cjs | 156 - .../desktop/apps/electron/eslint.config.mjs | 186 - packages/desktop/apps/electron/package.json | 82 - .../desktop/apps/electron/resources/AGENTS.md | 50 - .../apps/electron/resources/bin/craft-agent | 4 - .../electron/resources/bin/craft-agent.cmd | 7 - .../apps/electron/resources/bin/doc-diff | 2 - .../apps/electron/resources/bin/doc-diff.cmd | 2 - .../apps/electron/resources/bin/docx-tool | 2 - .../apps/electron/resources/bin/docx-tool.cmd | 2 - .../apps/electron/resources/bin/ical-tool | 2 - .../apps/electron/resources/bin/ical-tool.cmd | 2 - .../apps/electron/resources/bin/img-tool | 2 - .../apps/electron/resources/bin/img-tool.cmd | 2 - .../apps/electron/resources/bin/markitdown | 2 - .../electron/resources/bin/markitdown.cmd | 2 - .../apps/electron/resources/bin/pdf-tool | 2 - .../apps/electron/resources/bin/pdf-tool.cmd | 2 - .../apps/electron/resources/bin/pptx-tool | 2 - .../apps/electron/resources/bin/pptx-tool.cmd | 2 - .../apps/electron/resources/bin/xlsx-tool | 2 - .../apps/electron/resources/bin/xlsx-tool.cmd | 2 - .../resources/brands/openwork/dock.png | Bin 24740 -> 0 bytes .../resources/brands/openwork/icon.icns | Bin 145824 -> 0 bytes .../openwork/icon.iconset/icon_128x128.png | Bin 3173 -> 0 bytes .../openwork/icon.iconset/icon_128x128@2x.png | Bin 6800 -> 0 bytes .../openwork/icon.iconset/icon_16x16.png | Bin 439 -> 0 bytes .../openwork/icon.iconset/icon_16x16@2x.png | Bin 805 -> 0 bytes .../openwork/icon.iconset/icon_256x256.png | Bin 6800 -> 0 bytes .../openwork/icon.iconset/icon_256x256@2x.png | Bin 14921 -> 0 bytes .../openwork/icon.iconset/icon_32x32.png | Bin 805 -> 0 bytes .../openwork/icon.iconset/icon_32x32@2x.png | Bin 1566 -> 0 bytes .../openwork/icon.iconset/icon_512x512.png | Bin 14921 -> 0 bytes .../openwork/icon.iconset/icon_512x512@2x.png | Bin 39932 -> 0 bytes .../openwork/icon.iconset/icon_64x64.png | Bin 1566 -> 0 bytes .../resources/brands/openwork/icon.png | Bin 14921 -> 0 bytes .../resources/brands/openwork/symbol.png | Bin 19015 -> 0 bytes .../resources/brands/qwen-code/dock.png | Bin 23761 -> 0 bytes .../resources/brands/qwen-code/icon.icns | Bin 177828 -> 0 bytes .../resources/brands/qwen-code/icon.ico | Bin 63049 -> 0 bytes .../qwen-code/icon.icon/Assets/icon.svg | 6 - .../brands/qwen-code/icon.icon/icon.json | 37 - .../resources/brands/qwen-code/icon.png | Bin 76589 -> 0 bytes .../resources/brands/qwen-code/icon.svg | 6 - .../resources/bridge-mcp-server/index.js | 18276 ---------------- .../electron/resources/config-defaults.json | 23 - .../resources/craft-logos/craft_app_icon.png | Bin 104597 -> 0 bytes .../craft-logos/craft_app_icon_dark.png | Bin 110122 -> 0 bytes .../craft-logos/craft_logo_black.png | Bin 4746 -> 0 bytes .../craft-logos/craft_logo_white.png | Bin 4008 -> 0 bytes .../electron/resources/dmg-background.png | Bin 408341 -> 0 bytes .../electron/resources/dmg-background.tiff | Bin 12319640 -> 0 bytes .../electron/resources/dmg-background@2x.png | Bin 693633 -> 0 bytes .../electron/resources/docs/automations.md | 857 - .../electron/resources/docs/browser-tools.md | 289 - .../apps/electron/resources/docs/craft-cli.md | 395 - .../electron/resources/docs/data-tables.md | 401 - .../electron/resources/docs/html-preview.md | 406 - .../electron/resources/docs/image-preview.md | 109 - .../apps/electron/resources/docs/labels.md | 350 - .../apps/electron/resources/docs/llm-tool.md | 34 - .../apps/electron/resources/docs/mermaid.md | 474 - .../electron/resources/docs/pdf-preview.md | 223 - .../electron/resources/docs/permissions.md | 338 - .../apps/electron/resources/docs/skills.md | 335 - .../apps/electron/resources/docs/sources.md | 920 - .../apps/electron/resources/docs/statuses.md | 203 - .../apps/electron/resources/docs/themes.md | 274 - .../electron/resources/docs/tool-icons.md | 93 - .../apps/electron/resources/generate-icons.sh | 80 - .../resources/permissions/default.json | 949 - .../electron/resources/release-notes/0.0.1.md | 3 - .../electron/resources/scripts/doc_diff.py | 251 - .../electron/resources/scripts/docx_tool.py | 390 - .../electron/resources/scripts/ical_tool.py | 372 - .../electron/resources/scripts/img_tool.py | 370 - .../resources/scripts/markitdown_cli.py | 137 - .../electron/resources/scripts/pdf_tool.py | 1321 -- .../electron/resources/scripts/pptx_tool.py | 364 - .../resources/scripts/tests/README.md | 42 - .../scripts/tests/_tool_test_harness.py | 82 - .../scripts/tests/test_doc_diff_smoke.py | 46 - .../scripts/tests/test_docx_tool_smoke.py | 90 - .../scripts/tests/test_ical_tool_smoke.py | 70 - .../scripts/tests/test_img_tool_smoke.py | 56 - .../scripts/tests/test_markitdown_smoke.py | 46 - .../scripts/tests/test_pdf_tool_smoke.py | 126 - .../scripts/tests/test_pptx_tool_smoke.py | 60 - .../scripts/tests/test_xlsx_tool_smoke.py | 83 - .../electron/resources/scripts/xlsx_tool.py | 375 - .../apps/electron/resources/source.png | Bin 833451 -> 0 bytes .../electron/resources/themes/catppuccin.json | 26 - .../electron/resources/themes/default.json | 25 - .../electron/resources/themes/dracula.json | 26 - .../electron/resources/themes/ghostty.json | 26 - .../electron/resources/themes/github.json | 26 - .../electron/resources/themes/gruvbox.json | 26 - .../apps/electron/resources/themes/haze.json | 21 - .../electron/resources/themes/night-owl.json | 26 - .../apps/electron/resources/themes/nord.json | 26 - .../resources/themes/one-dark-pro.json | 26 - .../electron/resources/themes/pierre.json | 26 - .../electron/resources/themes/rose-pine.json | 26 - .../electron/resources/themes/solarized.json | 26 - .../resources/themes/tokyo-night.json | 26 - .../electron/resources/themes/vitesse.json | 26 - .../resources/tool-icons/1password.png | Bin 1205 -> 0 bytes .../resources/tool-icons/amazonaws.png | Bin 3061 -> 0 bytes .../electron/resources/tool-icons/bun.png | Bin 4994 -> 0 bytes .../electron/resources/tool-icons/chrome.svg | 33 - .../resources/tool-icons/cloudflare.png | Bin 2485 -> 0 bytes .../electron/resources/tool-icons/cmake.ico | Bin 15086 -> 0 bytes .../resources/tool-icons/cocoapods.png | Bin 10244 -> 0 bytes .../resources/tool-icons/craft-agent.svg | 10 - .../electron/resources/tool-icons/curl.svg | 14 - .../electron/resources/tool-icons/deno.png | Bin 4559 -> 0 bytes .../electron/resources/tool-icons/docker.png | Bin 2900 -> 0 bytes .../electron/resources/tool-icons/esbuild.svg | 4 - .../electron/resources/tool-icons/eslint.png | Bin 1673 -> 0 bytes .../resources/tool-icons/firebase.png | Bin 2763 -> 0 bytes .../resources/tool-icons/flydotio.png | Bin 1361 -> 0 bytes .../electron/resources/tool-icons/git.ico | Bin 6518 -> 0 bytes .../electron/resources/tool-icons/github.png | Bin 33270 -> 0 bytes .../electron/resources/tool-icons/gnumake.png | Bin 1708 -> 0 bytes .../apps/electron/resources/tool-icons/go.png | Bin 12840 -> 0 bytes .../resources/tool-icons/googlecloud.png | Bin 4166 -> 0 bytes .../electron/resources/tool-icons/heroku.png | Bin 5655 -> 0 bytes .../resources/tool-icons/homebrew.png | Bin 2075 -> 0 bytes .../electron/resources/tool-icons/jest.png | Bin 22916 -> 0 bytes .../apps/electron/resources/tool-icons/jq.png | Bin 2163 -> 0 bytes .../resources/tool-icons/kubernetes.png | Bin 21559 -> 0 bytes .../resources/tool-icons/microsoftazure.ico | Bin 17174 -> 0 bytes .../electron/resources/tool-icons/mongodb.ico | Bin 32988 -> 0 bytes .../electron/resources/tool-icons/mysql.png | Bin 3753 -> 0 bytes .../electron/resources/tool-icons/netlify.png | Bin 1391 -> 0 bytes .../electron/resources/tool-icons/ngrok.svg | 1 - .../resources/tool-icons/nodedotjs.png | Bin 1130 -> 0 bytes .../electron/resources/tool-icons/npm.png | Bin 730 -> 0 bytes .../apps/electron/resources/tool-icons/nx.png | Bin 3959 -> 0 bytes .../electron/resources/tool-icons/openssh.png | Bin 62740 -> 0 bytes .../electron/resources/tool-icons/pnpm.png | Bin 241 -> 0 bytes .../resources/tool-icons/postgresql.png | Bin 6990 -> 0 bytes .../resources/tool-icons/prettier.png | Bin 1419 -> 0 bytes .../electron/resources/tool-icons/pypi.png | Bin 3726 -> 0 bytes .../electron/resources/tool-icons/pytest.png | Bin 723 -> 0 bytes .../electron/resources/tool-icons/python.ico | Bin 15086 -> 0 bytes .../electron/resources/tool-icons/redis.png | Bin 3659 -> 0 bytes .../electron/resources/tool-icons/ruby.ico | Bin 5430 -> 0 bytes .../electron/resources/tool-icons/rust.png | Bin 6126 -> 0 bytes .../electron/resources/tool-icons/sentry.png | Bin 3902 -> 0 bytes .../electron/resources/tool-icons/sqlite.jpg | Bin 4655 -> 0 bytes .../electron/resources/tool-icons/stripe.png | Bin 1487 -> 0 bytes .../resources/tool-icons/supabase.png | Bin 1966 -> 0 bytes .../electron/resources/tool-icons/swift.ico | Bin 16958 -> 0 bytes .../resources/tool-icons/terraform.png | Bin 595 -> 0 bytes .../resources/tool-icons/tool-icons.json | 59 - .../resources/tool-icons/turborepo.png | Bin 1353 -> 0 bytes .../electron/resources/tool-icons/vercel.png | Bin 534 -> 0 bytes .../electron/resources/tool-icons/vite.svg | 130 - .../electron/resources/tool-icons/vitest.png | Bin 1754 -> 0 bytes .../electron/resources/tool-icons/webpack.png | Bin 11341 -> 0 bytes .../electron/resources/tool-icons/xcode.png | Bin 5700 -> 0 bytes .../electron/resources/tool-icons/yarn.svg | 7 - .../apps/electron/scripts/afterPack.cjs | 57 - .../apps/electron/scripts/build-dmg.sh | 219 - .../apps/electron/scripts/build-linux.sh | 200 - .../apps/electron/scripts/build-win.ps1 | 387 - .../apps/electron/scripts/copy-assets.ts | 33 - .../scripts/generate-pet-spritesheets.mjs | 136 - .../electron/src/__tests__/transport.test.ts | 868 - .../__tests__/auto-update-signature.test.ts | 44 - .../src/main/__tests__/browser-cdp.test.ts | 487 - .../__tests__/browser-pane-manager.test.ts | 1332 -- .../__tests__/browser-tool-detection.test.ts | 69 - .../main/__tests__/deep-link-routing.test.ts | 104 - .../src/main/__tests__/git-bash.test.ts | 54 - .../src/main/__tests__/init-gate.test.ts | 32 - .../src/main/__tests__/network-proxy.test.ts | 128 - .../notifications-routing.isolated.ts | 110 - .../__tests__/session-branch-cleanup.test.ts | 101 - .../session-event-message-parity.test.ts | 560 - .../__tests__/session-lazy-load-race.test.ts | 192 - .../__tests__/session-message-parity.test.ts | 367 - .../session-turn-grouping-parity.test.ts | 287 - .../sessions-annotations.isolated.ts | 113 - .../sessions-browser-release.test.ts | 26 - .../main/__tests__/sessions-sanitize.test.ts | 233 - .../src/main/auto-update-signature.ts | 75 - .../apps/electron/src/main/auto-update.ts | 575 - .../apps/electron/src/main/browser-cdp.ts | 1061 - .../electron/src/main/browser-pane-manager.ts | 3468 --- .../apps/electron/src/main/chunked-rpc.ts | 144 - .../apps/electron/src/main/deep-link.ts | 366 - .../__tests__/registration-profiles.test.ts | 182 - .../handlers/__tests__/registration.test.ts | 216 - .../__tests__/session-watcher.test.ts | 222 - .../__tests__/sessions-watchers.test.ts | 141 - .../settings-default-thinking.test.ts | 327 - .../electron/src/main/handlers/browser.ts | 210 - .../src/main/handlers/handler-deps.ts | 18 - .../apps/electron/src/main/handlers/index.ts | 36 - .../electron/src/main/handlers/pet-window.ts | 53 - .../electron/src/main/handlers/settings.ts | 30 - .../apps/electron/src/main/handlers/system.ts | 440 - .../electron/src/main/handlers/window-drag.ts | 71 - .../electron/src/main/handlers/workspace.ts | 136 - .../desktop/apps/electron/src/main/index.ts | 1348 -- .../desktop/apps/electron/src/main/logger.ts | 251 - .../desktop/apps/electron/src/main/menu.ts | 256 - .../electron/src/main/network-proxy-utils.ts | 125 - .../apps/electron/src/main/network-proxy.ts | 194 - .../apps/electron/src/main/notifications.ts | 303 - .../apps/electron/src/main/onboarding.ts | 95 - .../apps/electron/src/main/platform.ts | 72 - .../apps/electron/src/main/power-manager.ts | 108 - .../apps/electron/src/main/shell-env.ts | 109 - .../src/main/shims/abort-controller.cjs | 12 - .../electron/src/main/shims/node-fetch.cjs | 17 - .../electron/src/main/thumbnail-protocol.ts | 199 - .../main/voice/__tests__/frame-trust.test.ts | 89 - .../electron/src/main/voice/frame-trust.ts | 46 - .../apps/electron/src/main/window-manager.ts | 788 - .../apps/electron/src/main/window-state.ts | 90 - .../apps/electron/src/preload/bootstrap.ts | 366 - .../electron/src/preload/browser-toolbar.ts | 55 - .../apps/electron/src/renderer/App.tsx | 2678 --- .../__tests__/keybinding-context.test.ts | 65 - .../src/renderer/actions/definitions.ts | 224 - .../electron/src/renderer/actions/index.ts | 6 - .../renderer/actions/keybinding-context.ts | 144 - .../src/renderer/actions/registry.tsx | 196 - .../electron/src/renderer/actions/types.ts | 25 - .../src/renderer/actions/useAction.ts | 41 - .../src/renderer/actions/useHotkeyLabel.ts | 34 - .../assets/messaging-icons/telegram.svg | 8 - .../assets/messaging-icons/whatsapp.svg | 8 - .../assets/pets/qwen-spritesheet.webp | Bin 115462 -> 0 bytes .../assets/samples/sample-invoice.pdf | Bin 4826 -> 0 bytes .../assets/samples/sample-landscape.jpg | Bin 64487 -> 0 bytes .../renderer/assets/skill-market-hero.webp | Bin 15066 -> 0 bytes .../atoms/__tests__/browser-pane.test.ts | 71 - .../atoms/__tests__/panel-stack-lanes.test.ts | 111 - .../renderer/atoms/__tests__/sessions.test.ts | 707 - .../src/renderer/atoms/automations.ts | 16 - .../src/renderer/atoms/browser-pane.ts | 90 - .../electron/src/renderer/atoms/messaging.ts | 66 - .../src/renderer/atoms/new-session-draft.ts | 17 - .../electron/src/renderer/atoms/overlay.ts | 7 - .../src/renderer/atoms/panel-stack.ts | 361 - .../electron/src/renderer/atoms/sessions.ts | 1228 -- .../electron/src/renderer/atoms/skills.ts | 16 - .../electron/src/renderer/atoms/sources.ts | 16 - .../src/renderer/browser-empty-state.html | 28 - .../src/renderer/browser-empty-state.tsx | 48 - .../src/renderer/browser-toolbar.html | 28 - .../electron/src/renderer/browser-toolbar.tsx | 265 - .../src/renderer/components/AboutDialog.tsx | 74 - .../src/renderer/components/AppMenu.tsx | 372 - .../renderer/components/KeyboardShortcuts.tsx | 38 - .../components/KeyboardShortcutsDialog.tsx | 177 - .../components/ResetConfirmationDialog.tsx | 121 - .../components/ServerDirectoryBrowser.tsx | 317 - .../src/renderer/components/SplashScreen.tsx | 83 - .../components/apisetup/ApiKeyInput.tsx | 53 - .../components/apisetup/OAuthConnect.tsx | 103 - .../apisetup/ProviderConnectDialog.tsx | 29 - .../apisetup/ProviderConnectForm.tsx | 547 - .../src/renderer/components/apisetup/index.ts | 13 - .../app-shell/ActiveOptionBadges.tsx | 522 - .../components/app-shell/ActiveTasksBar.tsx | 85 - .../components/app-shell/AppShell.tsx | 5560 ----- .../app-shell/AttachmentPreview.tsx | 132 - .../components/app-shell/BatchSessionMenu.tsx | 305 - .../components/app-shell/BrowserDockPanel.tsx | 330 - .../app-shell/ChatDisplay.follow-ups.ts | 119 - .../components/app-shell/ChatDisplay.tsx | 2506 --- .../components/app-shell/FilePreviewPanel.tsx | 91 - .../components/app-shell/LeftSidebar.tsx | 591 - .../components/app-shell/MainContentPanel.tsx | 497 - .../app-shell/MarketplaceSkillIcon.tsx | 204 - .../components/app-shell/MultiSelectPanel.tsx | 211 - .../components/app-shell/NavigatorPanel.tsx | 52 - .../renderer/components/app-shell/Panel.tsx | 67 - .../components/app-shell/PanelHeader.tsx | 210 - .../components/app-shell/PanelResizeSash.tsx | 150 - .../components/app-shell/PanelSlot.tsx | 169 - .../app-shell/PanelStackContainer.tsx | 191 - .../SendResourceToWorkspaceDialog.tsx | 348 - .../app-shell/SendToWorkspaceDialog.tsx | 298 - .../components/app-shell/SessionBadges.tsx | 48 - .../app-shell/SessionInfoPopover.tsx | 160 - .../components/app-shell/SessionItem.tsx | 311 - .../components/app-shell/SessionList.tsx | 760 - .../components/app-shell/SessionMenu.tsx | 414 - .../components/app-shell/SessionMenuParts.tsx | 209 - .../app-shell/SessionSearchHeader.tsx | 107 - .../app-shell/SessionStatusIcon.tsx | 67 - .../components/app-shell/SetupAuthBanner.tsx | 117 - .../components/app-shell/SidebarMenu.tsx | 241 - .../app-shell/SkillMarketplaceDetailPanel.tsx | 317 - .../app-shell/SkillMarketplacePanel.tsx | 274 - .../components/app-shell/SkillMenu.tsx | 99 - .../components/app-shell/SkillsListPanel.tsx | 273 - .../components/app-shell/SourceMenu.tsx | 89 - .../components/app-shell/SourcesListPanel.tsx | 163 - .../components/app-shell/TaskActionMenu.tsx | 179 - .../renderer/components/app-shell/TopBar.tsx | 452 - .../app-shell/TransportConnectionBanner.tsx | 113 - .../app-shell/WorkspaceProjectTree.tsx | 979 - .../app-shell/WorkspaceSwitcher.tsx | 332 - .../__tests__/ChatDisplay.follow-ups.test.ts | 122 - .../__tests__/chat-display-branching.test.ts | 16 - .../__tests__/error-message-actions.test.ts | 68 - .../open-url-in-built-in-browser.test.ts | 259 - .../transport-connection-banner.test.ts | 67 - .../components/app-shell/branching.ts | 3 - .../app-shell/error-message-actions.ts | 51 - .../app-shell/input/ChatInputZone.tsx | 145 - .../input/CompactPermissionModeSelector.tsx | 146 - .../app-shell/input/FreeFormInput.tsx | 3127 --- .../input/FreeFormInputContextBadge.tsx | 141 - .../app-shell/input/InputContainer.tsx | 243 - .../app-shell/input/InputErrorBoundary.tsx | 109 - .../app-shell/input/StructuredInput.tsx | 61 - .../app-shell/input/ToolbarStatusSlot.tsx | 194 - .../file-attachment-metadata.test.ts | 39 - .../__tests__/file-attachment-order.test.ts | 30 - .../__tests__/focus-input-events.test.ts | 42 - .../__tests__/input-event-guards.test.ts | 30 - .../working-directory-history.test.ts | 63 - .../input/file-attachment-metadata.ts | 58 - .../app-shell/input/file-attachment-order.ts | 12 - .../app-shell/input/focus-input-events.ts | 45 - .../components/app-shell/input/index.ts | 23 - .../app-shell/input/input-event-guards.ts | 20 - .../input/structured/AdminApprovalRequest.tsx | 116 - .../structured/AskUserQuestionRequest.tsx | 214 - .../input/structured/CredentialRequest.tsx | 312 - .../input/structured/PermissionRequest.tsx | 113 - .../app-shell/input/structured/types.ts | 61 - .../components/app-shell/input/useAutoGrow.ts | 49 - .../app-shell/input/voice/VoiceMicControl.tsx | 84 - .../input/voice/VoiceRecordingBar.tsx | 45 - .../voice/__tests__/useVoiceDictation.test.ts | 38 - .../input/voice/useVoiceCapture.test.ts | 17 - .../app-shell/input/voice/useVoiceCapture.ts | 487 - .../input/voice/useVoiceDictation.ts | 192 - .../input/voice/voice-frame-sender.test.ts | 45 - .../input/voice/voice-frame-sender.ts | 27 - .../app-shell/input/voice/voiceModels.ts | 45 - .../input/working-directory-history.ts | 79 - .../app-shell/open-url-in-built-in-browser.ts | 93 - .../components/app-shell/panel-constants.ts | 38 - .../app-shell/plan-approval-message.ts | 24 - .../components/app-shell/sidebar-types.ts | 107 - .../components/automations/ActionTypeIcon.tsx | 7 - .../automations/AutomationActionPreview.tsx | 36 - .../automations/AutomationActionRow.tsx | 76 - .../automations/AutomationAvatar.tsx | 84 - .../components/automations/AutomationCard.tsx | 116 - .../automations/AutomationEventTimeline.tsx | 229 - .../automations/AutomationInfoPage.tsx | 305 - .../components/automations/AutomationMenu.tsx | 108 - .../automations/AutomationTestPanel.tsx | 64 - .../automations/AutomationsListPanel.tsx | 356 - .../automations/BatchAutomationMenu.tsx | 118 - .../components/automations/CronBuilder.tsx | 286 - .../components/automations/PhaseBadge.tsx | 38 - .../automations/__tests__/types.test.ts | 213 - .../components/automations/constants.ts | 2 - .../renderer/components/automations/types.ts | 508 - .../renderer/components/automations/utils.ts | 63 - .../components/browser/BrowserTabBadge.tsx | 97 - .../components/browser/BrowserTabStrip.tsx | 306 - .../components/browser/BrowserToolbar.tsx | 50 - .../browser/__tests__/utils.test.ts | 28 - .../components/browser/empty-state-prompts.ts | 44 - .../src/renderer/components/browser/utils.ts | 71 - .../components/chat/AuthRequestCard.tsx | 675 - .../components/chat/EmptyStateHint.tsx | 229 - .../renderer/components/files/FileViewer.tsx | 85 - .../components/icons/ConnectionIcon.tsx | 58 - .../components/icons/CraftAgentsLogo.tsx | 24 - .../components/icons/CraftAgentsSymbol.tsx | 24 - .../src/renderer/components/icons/McpIcon.tsx | 25 - .../components/icons/PanelLeftRounded.tsx | 24 - .../components/icons/PanelRightRounded.tsx | 23 - .../components/icons/SettingsIcons.tsx | 236 - .../components/icons/SquarePenRounded.tsx | 27 - .../components/icons/TodoStateIcons.tsx | 194 - .../components/info/AutoRulesDataTable.tsx | 249 - .../renderer/components/info/Info_Alert.tsx | 68 - .../renderer/components/info/Info_Badge.tsx | 87 - .../components/info/Info_DataTable.tsx | 160 - .../components/info/Info_GroupedList.tsx | 134 - .../components/info/Info_Markdown.tsx | 85 - .../renderer/components/info/Info_Page.tsx | 165 - .../renderer/components/info/Info_Section.tsx | 48 - .../components/info/Info_StatusBadge.tsx | 47 - .../renderer/components/info/Info_Table.tsx | 68 - .../components/info/LabelsDataTable.tsx | 197 - .../components/info/PermissionsDataTable.tsx | 242 - .../components/info/ToolsDataTable.tsx | 95 - .../src/renderer/components/info/index.ts | 46 - .../components/markdown/StreamingMarkdown.tsx | 185 - .../src/renderer/components/markdown/index.ts | 5 - .../messaging/MessagingDialogHost.tsx | 85 - .../messaging/MessagingPlatformIcon.tsx | 63 - .../messaging/MessagingSessionMenuItem.tsx | 140 - .../messaging/PairingCodeDialog.tsx | 143 - .../messaging/TelegramConnectDialog.tsx | 170 - .../messaging/WhatsAppConnectDialog.tsx | 157 - .../components/onboarding/APISetupStep.tsx | 83 - .../components/onboarding/CompletionStep.tsx | 56 - .../components/onboarding/CredentialsStep.tsx | 51 - .../components/onboarding/GitBashWarning.tsx | 149 - .../onboarding/OnboardingWizard.tsx | 168 - .../onboarding/ProviderSelectStep.tsx | 108 - .../components/onboarding/ReauthScreen.tsx | 102 - .../components/onboarding/WelcomeStep.tsx | 47 - .../renderer/components/onboarding/index.ts | 36 - .../components/onboarding/primitives.tsx | 282 - .../renderer/components/pet/DesktopPet.tsx | 271 - .../components/pet/PetNotifications.tsx | 77 - .../components/pet/PetWindowController.tsx | 18 - .../src/renderer/components/pet/QwenPet.tsx | 92 - .../components/preview/TableOfContents.tsx | 136 - .../right-sidebar/SessionFilesSection.tsx | 621 - .../__tests__/session-files-watch.test.ts | 58 - .../right-sidebar/session-files-watch.ts | 16 - .../settings/SearchableModelInput.tsx | 170 - .../components/settings/SettingsCard.tsx | 86 - .../components/settings/SettingsEditRow.tsx | 63 - .../components/settings/SettingsInput.tsx | 309 - .../settings/SettingsMenuSelect.tsx | 273 - .../settings/SettingsRadioGroup.tsx | 300 - .../components/settings/SettingsRow.tsx | 104 - .../components/settings/SettingsSection.tsx | 117 - .../settings/SettingsSegmentedControl.tsx | 181 - .../components/settings/SettingsSelect.tsx | 184 - .../components/settings/SettingsTextarea.tsx | 117 - .../components/settings/SettingsToggle.tsx | 80 - .../settings/SettingsUIConstants.ts | 22 - .../src/renderer/components/settings/index.ts | 94 - .../components/shiki/ShikiCodeEditor.tsx | 202 - .../components/shiki/ShikiCodeViewer.tsx | 22 - .../components/shiki/ShikiDiffViewer.tsx | 25 - .../src/renderer/components/shiki/index.ts | 3 - .../renderer/components/ui/EditPopover.tsx | 1120 - .../components/ui/HeaderIconButton.tsx | 53 - .../src/renderer/components/ui/HeaderMenu.tsx | 73 - .../components/ui/PanelHeaderCenterButton.tsx | 47 - .../components/ui/SourceSelectorPopover.tsx | 74 - .../renderer/components/ui/TopBarButton.tsx | 42 - .../ui/__tests__/label-menu.test.ts | 72 - .../ui/__tests__/mention-menu.test.ts | 125 - .../ui/__tests__/rich-text-input.test.ts | 64 - .../ui/__tests__/slash-command-menu.test.ts | 105 - .../ui/__tests__/status-icon.test.ts | 35 - .../components/ui/action-menu-item.tsx | 22 - .../renderer/components/ui/action-tooltip.tsx | 22 - .../renderer/components/ui/avatar-group.tsx | 35 - .../src/renderer/components/ui/avatar.tsx | 183 - .../src/renderer/components/ui/badge.tsx | 39 - .../src/renderer/components/ui/button.tsx | 57 - .../src/renderer/components/ui/calendar.tsx | 165 - .../renderer/components/ui/collapsible.tsx | 58 - .../src/renderer/components/ui/command.tsx | 155 - .../renderer/components/ui/context-menu.tsx | 331 - .../src/renderer/components/ui/data-table.tsx | 319 - .../src/renderer/components/ui/dialog.tsx | 143 - .../src/renderer/components/ui/drawer.tsx | 136 - .../renderer/components/ui/dropdown-menu.tsx | 311 - .../src/renderer/components/ui/empty.tsx | 104 - .../renderer/components/ui/entity-icon.tsx | 185 - .../components/ui/entity-list-badge.tsx | 59 - .../components/ui/entity-list-empty.tsx | 59 - .../components/ui/entity-list-label-badge.tsx | 74 - .../renderer/components/ui/entity-list.tsx | 227 - .../renderer/components/ui/entity-panel.tsx | 117 - .../src/renderer/components/ui/entity-row.tsx | 359 - .../renderer/components/ui/fading-text.tsx | 47 - .../components/ui/gradient-resize-handle.tsx | 62 - .../ui/horizontal-resize-handle.tsx | 91 - .../src/renderer/components/ui/input.tsx | 22 - .../src/renderer/components/ui/kbd.tsx | 28 - .../components/ui/label-badge-row.tsx | 122 - .../src/renderer/components/ui/label-icon.tsx | 115 - .../components/ui/label-menu-utils.ts | 138 - .../src/renderer/components/ui/label-menu.tsx | 444 - .../components/ui/label-value-popover.tsx | 310 - .../src/renderer/components/ui/label.tsx | 22 - .../renderer/components/ui/mention-badge.tsx | 136 - .../renderer/components/ui/mention-menu.tsx | 755 - .../renderer/components/ui/menu-context.tsx | 101 - .../renderer/components/ui/metadata-badge.tsx | 87 - .../src/renderer/components/ui/popover.tsx | 48 - .../renderer/components/ui/rename-dialog.tsx | 86 - .../src/renderer/components/ui/resizable.tsx | 49 - .../components/ui/rich-text-input.tsx | 1060 - .../renderer/components/ui/scroll-area.tsx | 58 - .../src/renderer/components/ui/select.tsx | 167 - .../src/renderer/components/ui/separator.tsx | 26 - .../renderer/components/ui/service-logo.tsx | 33 - .../components/ui/session-status-menu.tsx | 145 - .../renderer/components/ui/skill-avatar.tsx | 46 - .../components/ui/skill-mention-menu.tsx | 274 - .../components/ui/slash-command-menu.tsx | 910 - .../src/renderer/components/ui/sonner.tsx | 40 - .../renderer/components/ui/sortable-list.tsx | 261 - .../renderer/components/ui/source-avatar.tsx | 196 - .../components/ui/source-status-indicator.tsx | 181 - .../renderer/components/ui/status-icon.tsx | 84 - .../components/ui/styled-context-menu.tsx | 131 - .../components/ui/styled-dropdown.tsx | 17 - .../src/renderer/components/ui/switch.tsx | 31 - .../src/renderer/components/ui/table.tsx | 113 - .../src/renderer/components/ui/tabs.tsx | 55 - .../src/renderer/components/ui/textarea.tsx | 18 - .../components/ui/window-header-badge.tsx | 15 - .../workspace/AddWorkspaceStep_Choice.tsx | 95 - .../AddWorkspaceStep_ConnectRemote.tsx | 367 - .../workspace/AddWorkspaceStep_CreateNew.tsx | 199 - .../workspace/AddWorkspaceStep_OpenFolder.tsx | 128 - .../workspace/AddWorkspace_RadioOption.tsx | 71 - .../workspace/WorkspaceCreationScreen.tsx | 259 - .../components/workspace/WorkspacePicker.tsx | 129 - .../renderer/components/workspace/index.ts | 7 - .../components/workspace/primitives.tsx | 147 - .../__tests__/session-status-config.test.ts | 60 - .../electron/src/renderer/config/layout.ts | 9 - .../renderer/config/session-status-config.tsx | 193 - .../src/renderer/context/AppShellContext.tsx | 369 - .../context/DismissibleLayerContext.tsx | 144 - .../context/EscapeInterruptContext.tsx | 96 - .../src/renderer/context/FocusContext.tsx | 164 - .../src/renderer/context/ModalContext.tsx | 111 - .../renderer/context/SessionListContext.tsx | 50 - .../src/renderer/context/StoplightContext.tsx | 25 - .../src/renderer/context/ThemeContext.tsx | 535 - .../dismissible-layer-context.test.ts | 58 - .../renderer/contexts/NavigationContext.tsx | 1654 -- .../__tests__/navigation-history-key.test.ts | 65 - .../__tests__/navigation-reconcile.test.ts | 107 - .../renderer/contexts/navigation-history.ts | 45 - .../renderer/contexts/navigation-reconcile.ts | 29 - .../message-annotations-updated.test.ts | 102 - .../__tests__/text-message-id-sync.test.ts | 83 - .../user-message-confirmation.test.ts | 156 - .../handlers/permission-mode-changed.test.ts | 36 - .../event-processor/handlers/session.ts | 1110 - .../renderer/event-processor/handlers/text.ts | 146 - .../renderer/event-processor/handlers/tool.ts | 280 - .../src/renderer/event-processor/helpers.ts | 171 - .../src/renderer/event-processor/index.ts | 39 - .../src/renderer/event-processor/processor.ts | 237 - .../src/renderer/event-processor/types.ts | 563 - .../event-processor/useEventProcessor.ts | 130 - .../hooks/__tests__/useMultiSelect.test.ts | 335 - .../hooks/__tests__/useSessionSearch.test.ts | 69 - .../src/renderer/hooks/keyboard/index.ts | 2 - .../renderer/hooks/keyboard/useFocusZone.ts | 96 - .../hooks/keyboard/useRovingTabIndex.ts | 295 - .../src/renderer/hooks/useAutomations.ts | 218 - .../src/renderer/hooks/useBackgroundTasks.ts | 89 - .../src/renderer/hooks/useContainerWidth.ts | 27 - .../src/renderer/hooks/useDirectoryPicker.ts | 73 - .../src/renderer/hooks/useDynamicStack.ts | 185 - .../hooks/useEntityListInteractions.ts | 338 - .../src/renderer/hooks/useEntitySelection.ts | 106 - .../hooks/useHorizontalResizeGradient.ts | 87 - .../electron/src/renderer/hooks/useLabels.ts | 84 - .../src/renderer/hooks/useLinkInterceptor.ts | 244 - .../src/renderer/hooks/useMultiSelect.ts | 231 - .../src/renderer/hooks/useNotifications.ts | 244 - .../src/renderer/hooks/useOnboarding.ts | 279 - .../src/renderer/hooks/useReducedMotion.ts | 29 - .../src/renderer/hooks/useResizablePanels.ts | 19 - .../src/renderer/hooks/useResizeGradient.ts | 120 - .../electron/src/renderer/hooks/useSession.ts | 44 - .../src/renderer/hooks/useSessionActions.ts | 87 - .../src/renderer/hooks/useSessionOptions.ts | 48 - .../src/renderer/hooks/useSessionSearch.ts | 539 - .../renderer/hooks/useStaleSessionRecovery.ts | 98 - .../src/renderer/hooks/useStatuses.ts | 78 - .../electron/src/renderer/hooks/useTheme.ts | 76 - .../hooks/useTransportConnectionState.ts | 66 - .../renderer/hooks/useTurnCardExpansion.ts | 143 - .../src/renderer/hooks/useUpdateChecker.ts | 114 - .../electron/src/renderer/hooks/useViews.ts | 118 - .../renderer/hooks/useWindowCloseHandler.ts | 66 - .../src/renderer/hooks/useWorkspaceIcon.ts | 215 - .../apps/electron/src/renderer/index.css | 1379 -- .../apps/electron/src/renderer/index.html | 36 - .../src/renderer/lib/__tests__/drafts.test.ts | 132 - .../lib/__tests__/file-changes.test.ts | 92 - .../renderer/lib/__tests__/icon-cache.test.ts | 354 - .../renderer/lib/__tests__/input-text.test.ts | 27 - .../renderer/lib/__tests__/mentions.test.ts | 332 - .../lib/__tests__/overlay-detection.test.ts | 50 - .../__tests__/permission-mode-cycle.test.ts | 21 - .../project-session-snapshots.test.ts | 82 - .../__tests__/qwen-settings-snapshot.test.ts | 144 - .../lib/__tests__/reconnect-recovery.test.ts | 35 - .../session-delete-navigation.test.ts | 35 - .../lib/__tests__/session-load.test.ts | 239 - .../lib/__tests__/skills-loading.test.ts | 32 - .../__tests__/slash-command-badges.test.ts | 46 - .../lib/__tests__/transport-wait.test.ts | 75 - .../electron/src/renderer/lib/animations.ts | 29 - .../renderer/lib/dismissible-layer-bridge.ts | 43 - .../apps/electron/src/renderer/lib/drafts.ts | 85 - .../electron/src/renderer/lib/file-changes.ts | 91 - .../electron/src/renderer/lib/file-utils.ts | 66 - .../electron/src/renderer/lib/icon-cache.ts | 745 - .../electron/src/renderer/lib/input-text.ts | 23 - .../src/renderer/lib/local-storage.ts | 130 - .../apps/electron/src/renderer/lib/logger.ts | 7 - .../electron/src/renderer/lib/mentions.ts | 275 - .../electron/src/renderer/lib/merge-refs.ts | 20 - .../electron/src/renderer/lib/navigate.ts | 50 - .../src/renderer/lib/navigation-registry.ts | 200 - .../src/renderer/lib/overlay-detection.ts | 61 - .../apps/electron/src/renderer/lib/perf.ts | 187 - .../src/renderer/lib/permission-mode-cycle.ts | 17 - .../electron/src/renderer/lib/platform.ts | 51 - .../renderer/lib/project-session-snapshots.ts | 25 - .../src/renderer/lib/provider-icons.ts | 41 - .../src/renderer/lib/qwen-capability-cache.ts | 118 - .../renderer/lib/qwen-settings-snapshot.ts | 66 - .../src/renderer/lib/reconnect-recovery.ts | 20 - .../renderer/lib/session-delete-navigation.ts | 19 - .../electron/src/renderer/lib/session-load.ts | 131 - .../src/renderer/lib/skill-mentions.ts | 56 - .../src/renderer/lib/skills-loading.ts | 17 - .../src/renderer/lib/slash-command-badges.ts | 77 - .../apps/electron/src/renderer/lib/slugify.ts | 35 - .../src/renderer/lib/smart-typography.ts | 113 - .../src/renderer/lib/transport-wait.ts | 52 - .../apps/electron/src/renderer/lib/utils.ts | 6 - .../apps/electron/src/renderer/main.tsx | 124 - .../electron/src/renderer/pages/ChatPage.tsx | 1039 - .../src/renderer/pages/DraftChatPage.tsx | 569 - .../src/renderer/pages/PreferencesPage.tsx | 289 - .../src/renderer/pages/ShortcutsPage.tsx | 155 - .../src/renderer/pages/SkillInfoPage.tsx | 362 - .../src/renderer/pages/SourceInfoPage.tsx | 533 - .../apps/electron/src/renderer/pages/index.ts | 22 - .../pages/settings/AiSettingsPage.tsx | 196 - .../pages/settings/AppSettingsPage.tsx | 404 - .../pages/settings/AppearanceSettingsPage.tsx | 495 - .../pages/settings/ExtensionsSettingsPage.tsx | 7 - .../pages/settings/GeneralSettingsPage.tsx | 7 - .../pages/settings/HooksSettingsPage.tsx | 7 - .../pages/settings/InputSettingsPage.tsx | 196 - .../pages/settings/LabelsSettingsPage.tsx | 166 - .../pages/settings/McpServersSettingsPage.tsx | 7 - .../pages/settings/MemorySettingsPage.tsx | 235 - .../pages/settings/MessagingSettingsPage.tsx | 367 - .../settings/PermissionsSettingsPage.tsx | 413 - .../pages/settings/PreferencesPage.tsx | 278 - .../pages/settings/QwenSettingsPage.tsx | 1529 -- .../pages/settings/ServerSettingsPage.tsx | 298 - .../pages/settings/SettingsNavigator.tsx | 179 - .../renderer/pages/settings/ShortcutsPage.tsx | 168 - .../pages/settings/WorkspaceSettingsPage.tsx | 575 - .../src/renderer/pages/settings/index.ts | 62 - .../renderer/pages/settings/settings-pages.ts | 69 - .../apps/electron/src/renderer/pet-window.tsx | 19 - .../apps/electron/src/renderer/pet.html | 17 - .../src/renderer/pets/pet-animation.test.ts | 86 - .../src/renderer/pets/pet-animation.ts | 129 - .../electron/src/renderer/pets/pet-atoms.ts | 11 - .../electron/src/renderer/pets/pet-size.ts | 7 - .../electron/src/renderer/pets/registry.ts | 59 - .../src/renderer/pets/usePetActivityState.ts | 101 - .../src/renderer/pets/usePetCompanion.ts | 126 - .../src/renderer/pets/usePetNotifications.ts | 103 - .../electron/src/renderer/playground.html | 24 - .../apps/electron/src/renderer/playground.tsx | 37 - .../renderer/playground/ComponentPreview.tsx | 185 - .../src/renderer/playground/PlaygroundApp.tsx | 266 - .../playground/PlaygroundAppShellProvider.tsx | 77 - .../src/renderer/playground/Sidebar.tsx | 100 - .../src/renderer/playground/ThemeToggle.tsx | 36 - .../renderer/playground/VariantsSidebar.tsx | 179 - .../__tests__/recent-working-dirs.test.ts | 20 - .../playground/adapters/input-adapters.tsx | 98 - .../MessagingSettingsPagePreview.tsx | 151 - .../messaging/MessagingSubmenuPreview.tsx | 76 - .../messaging/PairingCodeDialogPreview.tsx | 71 - .../WhatsAppConnectDialogPreview.tsx | 91 - .../src/renderer/playground/mock-utils.ts | 552 - .../playground/recent-working-dirs.ts | 26 - .../playground/registry/automations.tsx | 682 - .../playground/registry/browser-ui.tsx | 1082 - .../src/renderer/playground/registry/chat.tsx | 1645 -- .../registry/container-transitions.tsx | 455 - .../playground/registry/custom-shadows.tsx | 298 - .../playground/registry/edit-popover.tsx | 506 - .../playground/registry/entity-lists.tsx | 1034 - .../playground/registry/generate-icons.ts | 150 - .../renderer/playground/registry/icons.tsx | 88 - .../src/renderer/playground/registry/index.ts | 68 - .../renderer/playground/registry/input.tsx | 5 - .../playground/registry/label-badges.tsx | 144 - .../renderer/playground/registry/markdown.tsx | 684 - .../renderer/playground/registry/messages.tsx | 883 - .../playground/registry/messaging.tsx | 237 - .../renderer/playground/registry/oauth.tsx | 124 - .../renderer/playground/registry/planner.css | 23 - .../renderer/playground/registry/planner.tsx | 1147 - .../playground/registry/sample-icons.ts | 47 - .../registry/samples/customer-support.ts | 286 - .../playground/registry/samples/deployment.ts | 362 - .../registry/samples/incident-response.ts | 267 - .../playground/registry/session-list.tsx | 499 - .../playground/registry/slash-command.tsx | 167 - .../renderer/playground/registry/toasts.tsx | 143 - .../playground/registry/transport-banner.tsx | 138 - .../playground/registry/turn-card-modes.tsx | 412 - .../playground/registry/turn-card.tsx | 2373 -- .../src/renderer/playground/registry/types.ts | 47 - .../utils/__tests__/auth-validation.test.ts | 345 - .../__tests__/session-list-collapse.test.ts | 60 - .../renderer/utils/__tests__/session.test.ts | 32 - .../src/renderer/utils/auth-validation.ts | 70 - .../renderer/utils/session-list-collapse.ts | 36 - .../electron/src/renderer/utils/session.ts | 211 - .../apps/electron/src/renderer/utils/text.ts | 25 - .../electron/src/renderer/utils/workspace.ts | 25 - .../apps/electron/src/renderer/vite-env.d.ts | 33 - .../electron/src/runtime/platform-headless.ts | 1 - .../apps/electron/src/runtime/platform.ts | 7 - .../shared/__tests__/browser-live-fx.test.ts | 39 - .../src/shared/__tests__/ipc-channels.test.ts | 390 - .../route-parser-automations.test.ts | 78 - .../route-parser-skill-marketplace.test.ts | 52 - .../electron/src/shared/browser-live-fx.ts | 39 - .../apps/electron/src/shared/feature-flags.ts | 5 - .../apps/electron/src/shared/menu-schema.ts | 285 - .../apps/electron/src/shared/route-parser.ts | 970 - .../apps/electron/src/shared/routes.ts | 223 - .../electron/src/shared/settings-registry.ts | 174 - .../desktop/apps/electron/src/shared/types.ts | 1504 -- .../__tests__/channel-map-parity.test.ts | 65 - .../src/transport/__tests__/codec.test.ts | 115 - .../transport/__tests__/routed-client.test.ts | 266 - .../apps/electron/src/transport/build-api.ts | 65 - .../electron/src/transport/channel-map.ts | 483 - .../apps/electron/src/transport/client.ts | 17 - .../apps/electron/src/transport/codec.ts | 5 - .../apps/electron/src/transport/index.ts | 5 - .../electron/src/transport/routed-client.ts | 251 - .../apps/electron/src/transport/server.ts | 1 - packages/desktop/apps/electron/tsconfig.json | 32 - packages/desktop/apps/electron/vite.config.ts | 85 - packages/desktop/apps/live-host/.gitignore | 3 - packages/desktop/apps/live-host/README.md | 126 - .../live-host/build/entitlements.mac.plist | 12 - .../apps/live-host/electron-builder.yml | 72 - packages/desktop/apps/live-host/package.json | 31 - .../resources/audio-input-worklet.js | 60 - .../apps/live-host/scripts/after-pack.cjs | 31 - .../desktop/apps/live-host/scripts/build.mjs | 103 - .../scripts/create-release-manifest.mjs | 47 - .../__tests__/appshot-architecture.test.ts | 75 - .../main/__tests__/appshot-capture.test.ts | 104 - .../main/__tests__/appshot-readiness.test.ts | 134 - .../main/__tests__/audio-architecture.test.ts | 44 - .../main/__tests__/audio-input-policy.test.ts | 56 - .../main/__tests__/audio-lifecycle.test.ts | 56 - .../main/__tests__/audio-output-queue.test.ts | 25 - .../main/__tests__/daemon-connection.test.ts | 380 - .../src/main/__tests__/discovery.test.ts | 146 - .../main/__tests__/global-shortcut.test.ts | 136 - .../main/__tests__/live-state-policy.test.ts | 68 - .../__tests__/native-service-policy.test.ts | 21 - .../main/__tests__/overlay-position.test.ts | 23 - .../main/__tests__/overlay-recovery.test.ts | 86 - .../src/main/__tests__/protocol.test.ts | 464 - .../main/__tests__/reconnect-policy.test.ts | 38 - .../__tests__/release-architecture.test.ts | 28 - .../live-host/src/main/appshot-capture.ts | 154 - .../live-host/src/main/appshot-readiness.ts | 68 - .../live-host/src/main/daemon-connection.ts | 552 - .../apps/live-host/src/main/discovery.ts | 211 - .../live-host/src/main/global-shortcut.ts | 81 - .../desktop/apps/live-host/src/main/index.ts | 1018 - .../live-host/src/main/live-state-policy.ts | 62 - .../apps/live-host/src/main/native-appshot.ts | 39 - .../src/main/native-service-policy.ts | 17 - .../live-host/src/main/overlay-position.ts | 18 - .../live-host/src/main/overlay-recovery.ts | 64 - .../live-host/src/main/reconnect-policy.ts | 27 - .../apps/live-host/src/native/appshot.mm | 575 - .../live-host/src/preload/audio-engine.ts | 565 - .../src/preload/audio-input-policy.ts | 28 - .../live-host/src/preload/audio-lifecycle.ts | 36 - .../src/preload/audio-output-queue.ts | 19 - .../apps/live-host/src/preload/index.ts | 120 - .../apps/live-host/src/renderer/index.html | 17 - .../apps/live-host/src/renderer/main.ts | 327 - .../apps/live-host/src/renderer/style.css | 340 - .../apps/live-host/src/shared/host-api.ts | 39 - .../apps/live-host/src/shared/protocol.ts | 388 - packages/desktop/apps/live-host/tsconfig.json | 16 - .../desktop/apps/live-host/vite.config.ts | 13 - packages/desktop/apps/viewer/index.html | 25 - packages/desktop/apps/viewer/package.json | 38 - .../desktop/apps/viewer/public/_redirects | 7 - packages/desktop/apps/viewer/src/App.tsx | 331 - .../apps/viewer/src/components/Header.tsx | 82 - .../viewer/src/components/SessionUpload.tsx | 161 - .../apps/viewer/src/components/index.ts | 6 - packages/desktop/apps/viewer/src/index.css | 39 - packages/desktop/apps/viewer/src/main.tsx | 22 - .../desktop/apps/viewer/src/vite-env.d.ts | 11 - packages/desktop/apps/viewer/tsconfig.json | 24 - .../desktop/apps/viewer/tsconfig.node.json | 18 - packages/desktop/apps/viewer/vite.config.ts | 48 - packages/desktop/apps/webui/package.json | 17 - packages/desktop/apps/webui/src/App.tsx | 149 - .../desktop/apps/webui/src/adapter/web-api.ts | 277 - packages/desktop/apps/webui/src/index.css | 52 - packages/desktop/apps/webui/src/index.html | 30 - packages/desktop/apps/webui/src/login.html | 286 - packages/desktop/apps/webui/src/main.tsx | 64 - .../webui/src/public/apple-touch-icon.png | Bin 20624 -> 0 bytes .../desktop/apps/webui/src/public/favicon.ico | Bin 7769 -> 0 bytes .../desktop/apps/webui/src/public/favicon.svg | 6 - .../apps/webui/src/public/icon-192.png | Bin 23174 -> 0 bytes .../apps/webui/src/public/icon-512.png | Bin 76589 -> 0 bytes .../apps/webui/src/public/manifest.json | 13 - packages/desktop/apps/webui/src/responsive.ts | 35 - .../apps/webui/src/shims/electron-log.ts | 17 - .../apps/webui/src/shims/fs-promises.ts | 12 - .../apps/webui/src/shims/node-builtins.ts | 130 - packages/desktop/apps/webui/src/shims/open.ts | 5 - .../apps/webui/src/shims/sentry-electron.ts | 5 - packages/desktop/apps/webui/src/shims/ws.ts | 20 - packages/desktop/apps/webui/tsconfig.json | 35 - packages/desktop/apps/webui/vite.config.ts | 98 - packages/desktop/bun.lock | 4106 ---- .../2026-06-08-desktop-auto-update-design.md | 68 - packages/desktop/package.json | 233 - packages/desktop/packages/core/README.md | 21 - packages/desktop/packages/core/package.json | 20 - packages/desktop/packages/core/src/index.ts | 15 - .../desktop/packages/core/src/types/index.ts | 73 - .../packages/core/src/types/message-mapper.ts | 29 - .../packages/core/src/types/message.ts | 731 - .../desktop/packages/core/src/types/server.ts | 67 - .../packages/core/src/types/session.ts | 60 - .../packages/core/src/types/workspace.ts | 76 - .../src/utils/__tests__/text-elements.test.ts | 64 - .../desktop/packages/core/src/utils/debug.ts | 12 - .../desktop/packages/core/src/utils/index.ts | 14 - .../desktop/packages/core/src/utils/paths.ts | 50 - .../packages/core/src/utils/text-elements.ts | 131 - packages/desktop/packages/core/tsconfig.json | 36 - .../packages/messaging-gateway/package.json | 26 - .../src/__tests__/binding-store.test.ts | 197 - .../src/__tests__/commands.test.ts | 125 - .../src/__tests__/pairing.test.ts | 91 - .../src/__tests__/plan-tokens.test.ts | 100 - .../src/__tests__/renderer-plan.test.ts | 214 - .../src/__tests__/renderer.test.ts | 475 - .../src/__tests__/router.test.ts | 198 - .../src/adapters/telegram/dm-only.test.ts | 39 - .../src/adapters/telegram/format.ts | 22 - .../src/adapters/telegram/index.ts | 570 - .../src/adapters/whatsapp/index.ts | 490 - .../src/adapters/whatsapp/lifecycle.test.ts | 122 - .../src/binding-store.test.ts | 68 - .../messaging-gateway/src/binding-store.ts | 247 - .../messaging-gateway/src/bootstrap.ts | 118 - .../messaging-gateway/src/commands.ts | 383 - .../messaging-gateway/src/config-store.ts | 111 - .../messaging-gateway/src/event-fanout.ts | 36 - .../packages/messaging-gateway/src/gateway.ts | 481 - .../packages/messaging-gateway/src/index.ts | 58 - .../messaging-gateway/src/pairing.test.ts | 78 - .../packages/messaging-gateway/src/pairing.ts | 161 - .../messaging-gateway/src/plan-tokens.ts | 83 - .../messaging-gateway/src/registry.ts | 829 - .../messaging-gateway/src/renderer.ts | 719 - .../packages/messaging-gateway/src/router.ts | 103 - .../packages/messaging-gateway/src/types.ts | 280 - .../packages/messaging-gateway/tsconfig.json | 9 - .../messaging-whatsapp-worker/package.json | 22 - .../src/__tests__/filter.test.ts | 270 - .../messaging-whatsapp-worker/src/filter.ts | 153 - .../messaging-whatsapp-worker/src/protocol.ts | 197 - .../messaging-whatsapp-worker/src/worker.ts | 552 - .../messaging-whatsapp-worker/tsconfig.json | 16 - .../desktop/packages/server-core/README.md | 18 - .../desktop/packages/server-core/package.json | 40 - .../bootstrap/__tests__/server-port.test.ts | 32 - .../bootstrap/__tests__/token-entropy.test.ts | 29 - .../src/bootstrap/headless-start.ts | 465 - .../server-core/src/bootstrap/index.ts | 1 - .../src/domain/browser-tool-detection.ts | 43 - .../src/domain/connection-setup-logic.ts | 182 - .../packages/server-core/src/domain/index.ts | 6 - .../server-core/src/domain/init-gate.ts | 33 - .../src/domain/session-branch-cleanup.ts | 44 - .../src/domain/session-browser-release.ts | 13 - .../server-core/src/domain/title-sanitizer.ts | 22 - .../__tests__/validate-file-path.test.ts | 87 - .../browser-pane-manager-interface.ts | 228 - .../server-core/src/handlers/handler-deps.ts | 30 - .../server-core/src/handlers/index.ts | 7 - .../handlers/messaging-registry-interface.ts | 77 - .../handlers/oauth-flow-store-interface.ts | 14 - .../server-core/src/handlers/rpc/auth.ts | 81 - .../src/handlers/rpc/automations.ts | 313 - .../server-core/src/handlers/rpc/files.ts | 596 - .../server-core/src/handlers/rpc/index.ts | 50 - .../server-core/src/handlers/rpc/labels.ts | 43 - .../src/handlers/rpc/llm-connections.ts | 463 - .../server-core/src/handlers/rpc/messaging.ts | 78 - .../server-core/src/handlers/rpc/oauth.ts | 204 - .../src/handlers/rpc/onboarding.ts | 204 - .../server-core/src/handlers/rpc/resources.ts | 95 - .../server-core/src/handlers/rpc/server.ts | 166 - .../rpc/sessions.get-for-workspace.test.ts | 210 - .../server-core/src/handlers/rpc/sessions.ts | 872 - .../server-core/src/handlers/rpc/settings.ts | 984 - .../handlers/rpc/skills.marketplace.test.ts | 208 - .../server-core/src/handlers/rpc/skills.ts | 668 - .../src/handlers/rpc/sources.test.ts | 153 - .../server-core/src/handlers/rpc/sources.ts | 258 - .../server-core/src/handlers/rpc/statuses.ts | 30 - .../handlers/rpc/system.git-branch.test.ts | 110 - .../src/handlers/rpc/system.open-url.test.ts | 128 - .../server-core/src/handlers/rpc/system.ts | 433 - .../src/handlers/rpc/transfer.test.ts | 225 - .../server-core/src/handlers/rpc/transfer.ts | 262 - .../handlers/rpc/workspace.image-path.test.ts | 163 - .../server-core/src/handlers/rpc/workspace.ts | 534 - .../src/handlers/session-manager-interface.ts | 428 - .../server-core/src/handlers/utils.ts | 136 - .../src/handlers/window-manager-interface.ts | 23 - .../desktop/packages/server-core/src/index.ts | 4 - .../server-core/src/model-fetchers/index.ts | 241 - .../server-core/src/model-fetchers/qwen.ts | 27 - .../src/model-fetchers/registry.ts | 22 - .../server-core/src/model-fetchers/runtime.ts | 26 - .../packages/server-core/src/runtime/index.ts | 3 - .../src/runtime/null-browser-pane-manager.ts | 91 - .../src/runtime/platform-headless.ts | 120 - .../server-core/src/runtime/platform.ts | 128 - .../server-core/src/services/git-bash.ts | 39 - .../src/services/image-utils.test.ts | 46 - .../server-core/src/services/image-utils.ts | 180 - .../server-core/src/services/index.ts | 5 - .../services/privileged-execution-broker.ts | 186 - .../server-core/src/services/search.ts | 388 - .../server-core/src/services/vcredist.test.ts | 99 - .../server-core/src/services/vcredist.ts | 58 - .../src/sessions/SessionManager.test.ts | 126 - .../src/sessions/SessionManager.ts | 12299 ----------- .../sessions/create-managed-session.test.ts | 119 - .../server-core/src/sessions/index.ts | 1 - .../permission-mode-persistence.test.ts | 68 - .../src/sessions/qwen-native-history.test.ts | 3108 --- .../__tests__/server-lifecycle.test.ts | 244 - .../server-core/src/transport/capabilities.ts | 130 - .../server-core/src/transport/client.ts | 982 - .../server-core/src/transport/codec.ts | 156 - .../server-core/src/transport/index.ts | 6 - .../server-core/src/transport/push.ts | 15 - .../server-core/src/transport/server.ts | 868 - .../server-core/src/transport/types.ts | 28 - .../src/utils/path-validation.test.ts | 144 - .../server-core/src/utils/path-validation.ts | 124 - .../server-core/src/voice/ansi.test.ts | 17 - .../packages/server-core/src/voice/ansi.ts | 20 - .../packages/server-core/src/voice/index.ts | 4 - .../server-core/src/voice/net-guard.ts | 298 - .../voice/qwen-asr-realtime-session.test.ts | 350 - .../src/voice/qwen-asr-realtime-session.ts | 345 - .../src/voice/resolve-voice-config.test.ts | 1966 -- .../src/voice/resolve-voice-config.ts | 861 - .../server-core/src/voice/transcribe.test.ts | 109 - .../server-core/src/voice/transcribe.ts | 157 - .../server-core/src/voice/voice-model.ts | 28 - .../src/voice/voice-server.test.ts | 437 - .../server-core/src/voice/voice-server.ts | 362 - .../src/voice/voice-stream-retry.test.ts | 116 - .../src/voice/voice-stream-retry.ts | 36 - .../src/voice/voice-stream-session.test.ts | 386 - .../src/voice/voice-stream-session.ts | 394 - .../src/voice/voice-ws-handler.isolated.ts | 590 - .../server-core/src/voice/voice-ws-handler.ts | 463 - .../server-core/src/voice/voice.test.ts | 534 - .../packages/server-core/src/voice/wav.ts | 29 - .../src/webui/__tests__/http-server.test.ts | 191 - .../webui/__tests__/oauth-callback.test.ts | 135 - .../packages/server-core/src/webui/auth.ts | 187 - .../server-core/src/webui/http-server.ts | 444 - .../packages/server-core/src/webui/index.ts | 3 - .../server-core/src/webui/node-adapter.ts | 90 - .../packages/server-core/tsconfig.json | 28 - packages/desktop/packages/server/package.json | 38 - .../server/src/__tests__/smoke.test.ts | 260 - packages/desktop/packages/server/src/index.ts | 361 - .../desktop/packages/server/tsconfig.json | 32 - .../packages/session-mcp-server/package.json | 24 - .../src/credential-cache.test.ts | 32 - .../src/credential-cache.ts | 45 - .../packages/session-mcp-server/src/index.ts | 534 - .../packages/session-mcp-server/tsconfig.json | 12 - .../packages/session-tools-core/package.json | 24 - .../session-tools-core/src/context.ts | 532 - .../src/handlers/config-validate.test.ts | 105 - .../src/handlers/config-validate.ts | 219 - .../src/handlers/credential-prompt.ts | 82 - .../src/handlers/get-session-info.ts | 27 - .../session-tools-core/src/handlers/index.ts | 74 - .../src/handlers/list-sessions.ts | 36 - .../src/handlers/mermaid-validate.ts | 57 - .../src/handlers/messaging.ts | 77 - .../src/handlers/render-template.ts | 95 - .../src/handlers/script-sandbox.test.ts | 176 - .../src/handlers/script-sandbox.ts | 260 - .../src/handlers/send-agent-message.ts | 53 - .../src/handlers/send-developer-feedback.ts | 52 - .../src/handlers/set-session-labels.ts | 49 - .../src/handlers/set-session-status.ts | 39 - .../src/handlers/skill-validate.ts | 131 - .../src/handlers/source-oauth.ts | 371 - .../src/handlers/source-test.test.ts | 315 - .../src/handlers/source-test.ts | 915 - .../src/handlers/submit-plan.test.ts | 113 - .../src/handlers/submit-plan.ts | 57 - .../src/handlers/transform-data.test.ts | 273 - .../src/handlers/transform-data.ts | 290 - .../src/handlers/update-preferences.ts | 91 - .../packages/session-tools-core/src/index.ts | 248 - .../session-tools-core/src/response.ts | 60 - .../src/runtime/filesystem-isolation.test.ts | 75 - .../src/runtime/filesystem-isolation.ts | 152 - .../src/runtime/network-isolation.ts | 88 - .../src/runtime/path-security.test.ts | 94 - .../src/runtime/path-security.ts | 103 - .../runtime/resolve-script-runtime.test.ts | 92 - .../src/runtime/resolve-script-runtime.ts | 255 - .../src/runtime/sandbox-env.test.ts | 75 - .../src/runtime/sandbox-env.ts | 78 - .../src/source-helpers.test.ts | 62 - .../session-tools-core/src/source-helpers.ts | 249 - .../src/templates/loader.ts | 207 - .../src/templates/mustache.ts | 234 - .../src/tool-defs-filtering.test.ts | 76 - .../session-tools-core/src/tool-defs.ts | 709 - .../packages/session-tools-core/src/types.ts | 327 - .../session-tools-core/src/validation.ts | 374 - .../packages/session-tools-core/tsconfig.json | 13 - .../eslint-rules/no-direct-open-import.cjs | 85 - .../no-inline-source-auth-check.cjs | 85 - .../desktop/packages/shared/eslint.config.mjs | 51 - packages/desktop/packages/shared/package.json | 91 - .../src/__tests__/feature-flags.test.ts | 102 - .../src/__tests__/interceptor-common.test.ts | 74 - .../interceptor-packaging-contract.test.ts | 17 - .../interceptor-request-utils.test.ts | 30 - .../__tests__/__tmp_build_call_llm__/empty.ts | 0 .../__tmp_build_call_llm__/image.png | 1 - .../__tests__/__tmp_build_call_llm__/test.ts | 2 - .../src/agent/__tests__/base-agent.test.ts | 262 - .../__tests__/base-event-adapter.test.ts | 273 - .../__tests__/browser-tool-names.test.ts | 45 - .../browser-tools-permissions.test.ts | 44 - .../src/agent/__tests__/browser-tools.test.ts | 1064 - .../__tests__/build-call-llm-request.test.ts | 226 - .../__tests__/call-llm-permissions.test.ts | 37 - .../__tests__/conversation-summary.test.ts | 57 - .../credential-prompt-detection.test.ts | 232 - .../shared/src/agent/__tests__/errors.test.ts | 37 - .../src/agent/__tests__/event-queue.test.ts | 192 - .../__tests__/mode-manager-bash-hints.test.ts | 85 - .../mode-manager-path-boundary.test.ts | 121 - .../__tests__/mode-session-state.test.ts | 119 - .../pending-permissions-lifecycle.test.ts | 163 - .../permissions-config-craft-cli-flag.test.ts | 87 - .../permissions-config-migration.test.ts | 87 - .../permissions-config-source-slug.test.ts | 20 - .../agent/__tests__/qwen-agent-models.test.ts | 359 - .../qwen-agent-slash-history.test.ts | 2791 --- .../__tests__/qwen-agent-tool-updates.test.ts | 106 - ...end-developer-feedback-permissions.test.ts | 60 - .../session-scoped-tools-merge.test.ts | 62 - ...session-scoped-tools-path-boundary.test.ts | 31 - .../session-self-management-bindings.test.ts | 292 - ...session-tool-safe-mode-permissions.test.ts | 63 - .../src/agent/__tests__/source-state.test.ts | 347 - .../spawn-session-thinking-level.test.ts | 77 - .../spawn-session-tilde-expansion.test.ts | 85 - .../shared/src/agent/__tests__/test-utils.ts | 201 - .../tool-matching-sdk-fixtures.test.ts | 384 - .../src/agent/__tests__/tool-matching.test.ts | 1012 - .../src/agent/__tests__/with-timeout.test.ts | 47 - .../backend/__tests__/factory-model.test.ts | 41 - .../backend/__tests__/read-patterns.test.ts | 150 - .../src/agent/backend/base-event-adapter.ts | 248 - .../shared/src/agent/backend/event-queue.ts | 104 - .../shared/src/agent/backend/factory.ts | 285 - .../shared/src/agent/backend/index.ts | 86 - .../__tests__/electron-run-as-node.test.ts | 57 - .../__tests__/runtime-resolver.test.ts | 231 - .../agent/backend/internal/driver-types.ts | 106 - .../agent/backend/internal/drivers/qwen.ts | 22 - .../backend/internal/electron-run-as-node.ts | 22 - .../backend/internal/runtime-resolver.ts | 300 - .../shared/src/agent/backend/read-patterns.ts | 343 - .../shared/src/agent/backend/types.ts | 816 - .../packages/shared/src/agent/base-agent.ts | 1273 -- .../shared/src/agent/bash-parser.d.ts | 29 - .../shared/src/agent/bash-validator.ts | 627 - .../shared/src/agent/browser-tool-names.ts | 80 - .../shared/src/agent/browser-tool-runtime.ts | 1730 -- .../shared/src/agent/browser-tools.ts | 281 - .../shared/src/agent/conversation-summary.ts | 48 - .../core/__tests__/permission-manager.test.ts | 206 - .../__tests__/pre-tool-use-checks.isolated.ts | 1270 -- .../prerequisite-manager.isolated.ts | 392 - .../core/__tests__/session-lifecycle.test.ts | 197 - .../core/__tests__/source-manager.test.ts | 271 - .../core/__tests__/usage-tracker.test.ts | 249 - .../shared/src/agent/core/config-validator.ts | 300 - .../src/agent/core/config-watcher-manager.ts | 266 - .../packages/shared/src/agent/core/index.ts | 148 - .../shared/src/agent/core/path-processor.ts | 224 - .../src/agent/core/permission-manager.ts | 390 - .../shared/src/agent/core/pre-tool-use.ts | 1115 - .../src/agent/core/prerequisite-manager.ts | 272 - .../shared/src/agent/core/prompt-builder.ts | 243 - .../src/agent/core/session-lifecycle.ts | 253 - .../shared/src/agent/core/source-manager.ts | 387 - .../packages/shared/src/agent/core/types.ts | 148 - .../shared/src/agent/core/usage-tracker.ts | 297 - .../packages/shared/src/agent/diagnostics.ts | 122 - .../packages/shared/src/agent/errors.ts | 520 - .../packages/shared/src/agent/index.ts | 192 - .../packages/shared/src/agent/llm-tool.ts | 725 - .../packages/shared/src/agent/mode-manager.ts | 2182 -- .../packages/shared/src/agent/mode-types.ts | 391 - .../shared/src/agent/permissions-config.ts | 939 - .../packages/shared/src/agent/plan-types.ts | 232 - .../shared/src/agent/powershell-parser.ps1 | 251 - .../shared/src/agent/powershell-validator.ts | 1095 - .../packages/shared/src/agent/qwen-agent.ts | 5363 ----- .../session-scoped-tool-callback-registry.ts | 124 - .../shared/src/agent/session-scoped-tools.ts | 317 - .../agent/session-self-management-bindings.ts | 125 - .../shared/src/agent/session-tool-context.ts | 300 - .../shared/src/agent/spawn-session-tool.ts | 102 - .../shared/src/agent/thinking-levels.ts | 140 - .../shared/src/agent/tool-matching.ts | 494 - .../exports-and-session-context.test.ts | 138 - .../src/auth/__tests__/generic-oauth.test.ts | 160 - .../auth/__tests__/oauth-callback-url.test.ts | 47 - .../src/auth/__tests__/oauth-relay.test.ts | 64 - .../src/auth/__tests__/oauth.e2e.test.ts | 125 - .../shared/src/auth/__tests__/oauth.test.ts | 1227 -- .../shared/src/auth/__tests__/types.test.ts | 92 - .../packages/shared/src/auth/callback-page.ts | 183 - .../shared/src/auth/callback-server.ts | 189 - .../packages/shared/src/auth/generic-oauth.ts | 242 - .../packages/shared/src/auth/google-oauth.ts | 485 - .../desktop/packages/shared/src/auth/index.ts | 12 - .../shared/src/auth/microsoft-oauth.ts | 446 - .../shared/src/auth/oauth-flow-store.ts | 106 - .../shared/src/auth/oauth-flow-types.ts | 57 - .../packages/shared/src/auth/oauth-relay.ts | 89 - .../desktop/packages/shared/src/auth/oauth.ts | 986 - .../desktop/packages/shared/src/auth/pkce.ts | 38 - .../packages/shared/src/auth/slack-oauth.ts | 420 - .../desktop/packages/shared/src/auth/state.ts | 62 - .../desktop/packages/shared/src/auth/types.ts | 72 - .../src/automations/automation-system.test.ts | 537 - .../src/automations/automation-system.ts | 562 - .../src/automations/conditions-constants.ts | 10 - .../shared/src/automations/conditions.test.ts | 548 - .../shared/src/automations/conditions.ts | 242 - .../shared/src/automations/constants.ts | 20 - .../src/automations/cron-matcher.test.ts | 97 - .../shared/src/automations/cron-matcher.ts | 57 - .../shared/src/automations/event-bus.test.ts | 325 - .../shared/src/automations/event-bus.ts | 320 - .../shared/src/automations/event-logger.ts | 172 - .../handlers/event-log-handler.test.ts | 197 - .../automations/handlers/event-log-handler.ts | 86 - .../shared/src/automations/handlers/index.ts | 15 - .../handlers/prompt-handler.test.ts | 588 - .../automations/handlers/prompt-handler.ts | 131 - .../shared/src/automations/handlers/types.ts | 83 - .../automations/handlers/webhook-handler.ts | 274 - .../src/automations/history-store.test.ts | 320 - .../shared/src/automations/history-store.ts | 290 - .../packages/shared/src/automations/index.ts | 139 - .../shared/src/automations/name-utils.test.ts | 64 - .../shared/src/automations/name-utils.ts | 39 - .../automations/resolve-config-path.test.ts | 11 - .../src/automations/resolve-config-path.ts | 22 - .../shared/src/automations/retry-scheduler.ts | 247 - .../shared/src/automations/schemas.ts | 206 - .../shared/src/automations/sdk-bridge.test.ts | 144 - .../shared/src/automations/sdk-bridge.ts | 65 - .../shared/src/automations/security.test.ts | 107 - .../shared/src/automations/security.ts | 30 - .../packages/shared/src/automations/types.ts | 322 - .../packages/shared/src/automations/utils.ts | 312 - .../shared/src/automations/validation.test.ts | 363 - .../shared/src/automations/validation.ts | 546 - .../src/automations/webhook-utils.test.ts | 90 - .../shared/src/automations/webhook-utils.ts | 365 - .../desktop/packages/shared/src/branding.ts | 201 - .../packages/shared/src/colors/defaults.ts | 39 - .../packages/shared/src/colors/index.ts | 40 - .../packages/shared/src/colors/migrate.ts | 114 - .../packages/shared/src/colors/resolve.ts | 163 - .../packages/shared/src/colors/types.ts | 64 - .../packages/shared/src/colors/validate.ts | 163 - .../default-conversation-workspace.test.ts | 108 - .../__tests__/default-thinking-level.test.ts | 126 - .../config/__tests__/qwen-settings.test.ts | 35 - .../config/__tests__/session-drafts.test.ts | 192 - .../__tests__/source-slug-validation.test.ts | 77 - .../config/__tests__/voice-settings.test.ts | 65 - .../__tests__/watcher-duplicate-guard.test.ts | 44 - .../__tests__/workspace-icon-url.test.ts | 82 - .../workspace-project-root-migration.test.ts | 160 - .../__tests__/workspace-reorder.test.ts | 100 - .../packages/shared/src/config/cli-domains.ts | 165 - .../src/config/config-defaults-schema.ts | 33 - .../packages/shared/src/config/index.ts | 16 - .../shared/src/config/llm-connections.ts | 132 - .../shared/src/config/model-fetcher.ts | 64 - .../packages/shared/src/config/models.ts | 87 - .../packages/shared/src/config/paths.ts | 19 - .../packages/shared/src/config/pets.ts | 115 - .../packages/shared/src/config/preferences.ts | 189 - .../shared/src/config/provider-metadata.ts | 44 - .../packages/shared/src/config/proxy-env.ts | 25 - .../shared/src/config/qwen-settings.ts | 63 - .../shared/src/config/server-config.ts | 44 - .../packages/shared/src/config/storage.ts | 2490 --- .../config/sync-craft-agent-bash-patterns.ts | 56 - .../packages/shared/src/config/theme.ts | 317 - .../packages/shared/src/config/types.ts | 22 - .../packages/shared/src/config/validators.ts | 2170 -- .../packages/shared/src/config/watcher.ts | 1128 - .../shared/src/credentials/backends/env.ts | 35 - .../credentials/backends/secure-storage.ts | 358 - .../shared/src/credentials/backends/types.ts | 31 - .../packages/shared/src/credentials/index.ts | 28 - .../shared/src/credentials/manager.ts | 576 - .../packages/shared/src/credentials/types.ts | 272 - .../packages/shared/src/docs/doc-links.ts | 125 - .../desktop/packages/shared/src/docs/index.ts | 192 - .../packages/shared/src/docs/source-guides.ts | 227 - .../packages/shared/src/feature-flags.ts | 92 - .../src/i18n/__tests__/locale-parity.test.ts | 131 - .../i18n/__tests__/locale-registry.test.ts | 122 - .../packages/shared/src/i18n/date-locale.ts | 8 - .../desktop/packages/shared/src/i18n/index.ts | 5 - .../packages/shared/src/i18n/languages.ts | 21 - .../packages/shared/src/i18n/locales/de.json | 1550 -- .../packages/shared/src/i18n/locales/en.json | 1550 -- .../packages/shared/src/i18n/locales/es.json | 1550 -- .../packages/shared/src/i18n/locales/hu.json | 1550 -- .../packages/shared/src/i18n/locales/ja.json | 1550 -- .../packages/shared/src/i18n/locales/pl.json | 1550 -- .../shared/src/i18n/locales/zh-Hans.json | 1550 -- .../packages/shared/src/i18n/registry.ts | 59 - .../packages/shared/src/i18n/setupI18n.ts | 57 - .../packages/shared/src/icons/index.ts | 12 - .../packages/shared/src/icons/types.ts | 76 - packages/desktop/packages/shared/src/index.ts | 32 - .../packages/shared/src/interceptor-common.ts | 405 - .../shared/src/interceptor-request-utils.ts | 29 - .../shared/src/labels/__tests__/crud.test.ts | 105 - .../src/labels/__tests__/resolve.test.ts | 174 - .../shared/src/labels/__tests__/tree.test.ts | 70 - .../shared/src/labels/auto/evaluator.ts | 148 - .../packages/shared/src/labels/auto/index.ts | 4 - .../shared/src/labels/auto/normalize.ts | 67 - .../packages/shared/src/labels/auto/types.ts | 19 - .../shared/src/labels/auto/validation.ts | 76 - .../packages/shared/src/labels/crud.ts | 300 - .../packages/shared/src/labels/index.ts | 25 - .../packages/shared/src/labels/resolve.ts | 81 - .../packages/shared/src/labels/storage.ts | 206 - .../packages/shared/src/labels/tree.ts | 204 - .../packages/shared/src/labels/types.ts | 127 - .../packages/shared/src/labels/validation.ts | 33 - .../packages/shared/src/labels/values.ts | 184 - .../shared/src/mcp/api-source-pool-client.ts | 52 - .../desktop/packages/shared/src/mcp/client.ts | 152 - .../desktop/packages/shared/src/mcp/index.ts | 4 - .../packages/shared/src/mcp/local-tools.ts | 56 - .../packages/shared/src/mcp/mcp-pool.ts | 459 - .../packages/shared/src/mcp/pool-server.ts | 178 - .../packages/shared/src/mcp/validation.ts | 429 - .../__tests__/resolve-file-mentions.test.ts | 122 - .../resolve-skill-source-mentions.test.ts | 160 - .../packages/shared/src/mentions/index.ts | 225 - .../src/prompts/__tests__/system.test.ts | 109 - .../packages/shared/src/prompts/index.ts | 1 - .../shared/src/prompts/print-system-prompt.ts | 225 - .../packages/shared/src/prompts/system.ts | 1097 - .../src/protocol/__tests__/routing.test.ts | 71 - .../packages/shared/src/protocol/channels.ts | 451 - .../packages/shared/src/protocol/dto.ts | 1176 - .../packages/shared/src/protocol/events.ts | 76 - .../packages/shared/src/protocol/index.ts | 5 - .../packages/shared/src/protocol/routing.ts | 470 - .../packages/shared/src/protocol/types.ts | 128 - .../shared/src/release-notes/index.ts | 154 - .../__tests__/resource-bundle.test.ts | 1197 - .../packages/shared/src/resources/index.ts | 22 - .../shared/src/resources/resource-bundle.ts | 1012 - .../packages/shared/src/resources/types.ts | 148 - .../packages/shared/src/scheduler/index.ts | 1 - .../shared/src/scheduler/scheduler-service.ts | 88 - .../packages/shared/src/search/fuzzy.ts | 106 - .../packages/shared/src/search/index.ts | 1 - .../src/sessions/__tests__/bundle.test.ts | 252 - ...sonl-permission-mode-normalization.test.ts | 84 - .../__tests__/model-persistence.test.ts | 139 - .../__tests__/pending-plan-execution.test.ts | 71 - .../__tests__/persistence-queue.test.ts | 98 - .../previous-permission-mode.test.ts | 28 - .../sessions/__tests__/slug-generator.test.ts | 53 - .../packages/shared/src/sessions/bundle.ts | 162 - .../packages/shared/src/sessions/index.ts | 127 - .../packages/shared/src/sessions/jsonl.ts | 383 - .../shared/src/sessions/persistence-queue.ts | 295 - .../shared/src/sessions/slug-generator.ts | 161 - .../packages/shared/src/sessions/storage.ts | 1104 - .../packages/shared/src/sessions/types.ts | 366 - .../packages/shared/src/sessions/utils.ts | 25 - .../shared/src/sessions/validation.ts | 70 - .../shared/src/sessions/word-lists.ts | 80 - .../src/skills/__tests__/marketplace.test.ts | 42 - .../src/skills/__tests__/storage.test.ts | 645 - .../packages/shared/src/skills/index.ts | 24 - .../packages/shared/src/skills/marketplace.ts | 122 - .../packages/shared/src/skills/storage.ts | 450 - .../packages/shared/src/skills/types.ts | 63 - .../__tests__/authScheme-empty.test.ts | 35 - .../src/sources/__tests__/basic-auth.test.ts | 243 - .../credential-manager-expiry.test.ts | 157 - .../credential-manager-integration.test.ts | 253 - .../credential-manager-renew.test.ts | 244 - .../__tests__/multi-header-auth.test.ts | 367 - .../__tests__/multi-header-e2e.test.ts | 288 - .../src/sources/__tests__/oauth-relay.test.ts | 132 - .../server-builder-authScheme.test.ts | 91 - .../__tests__/server-builder-flow.test.ts | 250 - .../__tests__/server-builder-url.test.ts | 41 - .../source-config-validation.test.ts | 242 - .../__tests__/storage-source-slug.test.ts | 114 - .../__tests__/token-refresh-manager.test.ts | 668 - .../packages/shared/src/sources/api-tools.ts | 342 - .../shared/src/sources/builtin-sources.ts | 77 - .../shared/src/sources/credential-manager.ts | 1355 -- .../packages/shared/src/sources/index.ts | 111 - .../shared/src/sources/server-builder.ts | 363 - .../packages/shared/src/sources/storage.ts | 592 - .../src/sources/token-refresh-manager.ts | 257 - .../packages/shared/src/sources/types.ts | 566 - .../packages/shared/src/statuses/crud.ts | 196 - .../shared/src/statuses/default-icons.ts | 70 - .../packages/shared/src/statuses/index.ts | 20 - .../packages/shared/src/statuses/storage.ts | 321 - .../packages/shared/src/statuses/types.ts | 96 - .../shared/src/statuses/validation.ts | 39 - .../packages/shared/src/tools/index.ts | 9 - .../shared/src/types/incr-regex-package.d.ts | 85 - .../shared/src/types/marked-terminal.d.ts | 30 - .../__tests__/binary-detection-base64.test.ts | 144 - .../utils/__tests__/binary-detection.test.ts | 471 - .../src/utils/__tests__/bundle-files.test.ts | 366 - .../utils/__tests__/cli-icon-resolver.test.ts | 418 - .../shared/src/utils/__tests__/files.test.ts | 31 - .../utils/__tests__/icon-constants.test.ts | 19 - .../shared/src/utils/__tests__/logo.test.ts | 44 - .../shared/src/utils/__tests__/paths.test.ts | 33 - .../utils/__tests__/title-generator.test.ts | 407 - .../src/utils/__tests__/url-safety.test.ts | 116 - .../shared/src/utils/binary-detection.ts | 398 - .../packages/shared/src/utils/bundle-files.ts | 209 - .../shared/src/utils/cli-icon-resolver.ts | 357 - .../packages/shared/src/utils/debug.ts | 217 - .../packages/shared/src/utils/files.ts | 893 - .../shared/src/utils/icon-constants.ts | 58 - .../packages/shared/src/utils/icon-encoder.ts | 234 - .../desktop/packages/shared/src/utils/icon.ts | 240 - .../packages/shared/src/utils/index.ts | 13 - .../shared/src/utils/large-response.ts | 574 - .../desktop/packages/shared/src/utils/logo.ts | 408 - .../desktop/packages/shared/src/utils/mask.ts | 56 - .../packages/shared/src/utils/open-url.ts | 29 - .../packages/shared/src/utils/paths.ts | 223 - .../desktop/packages/shared/src/utils/perf.ts | 417 - .../packages/shared/src/utils/service-url.ts | 30 - .../packages/shared/src/utils/summarize.ts | 18 - .../shared/src/utils/title-generator.ts | 244 - .../packages/shared/src/utils/toolNames.ts | 121 - .../packages/shared/src/utils/url-safety.ts | 53 - .../shared/src/utils/workspace-slug.ts | 16 - .../packages/shared/src/utils/workspace.ts | 13 - .../packages/shared/src/validation/index.ts | 1 - .../shared/src/validation/url-validator.ts | 38 - .../packages/shared/src/version/index.ts | 13 - .../packages/shared/src/version/install.ts | 152 - .../packages/shared/src/version/manifest.ts | 57 - .../packages/shared/src/version/version.ts | 33 - .../packages/shared/src/views/defaults.ts | 45 - .../packages/shared/src/views/evaluator.ts | 157 - .../packages/shared/src/views/functions.ts | 100 - .../packages/shared/src/views/index.ts | 12 - .../packages/shared/src/views/storage.ts | 132 - .../packages/shared/src/views/types.ts | 108 - .../packages/shared/src/views/validation.ts | 99 - ...rage-permission-mode-normalization.test.ts | 112 - .../packages/shared/src/workspaces/index.ts | 42 - .../packages/shared/src/workspaces/storage.ts | 482 - .../packages/shared/src/workspaces/types.ts | 95 - .../shared/tests/content-validators.test.ts | 494 - .../packages/shared/tests/mcp-pool.test.ts | 186 - .../shared/tests/mode-manager.test.ts | 2403 -- .../packages/shared/tests/models.test.ts | 38 - .../permissions-craft-agent-sync.test.ts | 26 - .../shared/tests/persistence-queue.test.ts | 123 - .../shared/tests/session-validation.test.ts | 282 - .../shared/tests/shellguard-corpus.test.ts | 900 - .../desktop/packages/shared/tsconfig.json | 34 - .../no-floating-z-tokens-in-island.test.ts | 69 - .../__tests__/no-hardcoded-z-index.test.ts | 44 - .../no-floating-z-tokens-in-island.cjs | 68 - .../ui/eslint-rules/no-hardcoded-z-index.cjs | 116 - .../eslint-rules/no-nonstandard-shadows.cjs | 156 - .../desktop/packages/ui/eslint.config.mjs | 100 - packages/desktop/packages/ui/package.json | 71 - .../annotations/AnnotationIslandMenu.tsx | 155 - .../annotations/AnnotationOverlayLayer.tsx | 123 - .../__tests__/annotation-core.test.ts | 62 - .../__tests__/annotation-host-config.test.ts | 16 - .../__tests__/interaction-policy.test.ts | 75 - .../interaction-state-machine.test.ts | 92 - .../__tests__/island-dismiss-policy.test.ts | 36 - .../__tests__/island-motion.test.ts | 34 - .../__tests__/selection-restore.test.ts | 25 - .../use-annotation-island-events.test.ts | 37 - ...use-annotation-island-presentation.test.ts | 80 - .../components/annotations/annotation-core.ts | 274 - .../annotations/annotation-host-config.ts | 23 - .../annotation-overlay-geometry.ts | 101 - .../annotations/annotation-style-tokens.ts | 58 - .../components/annotations/block-markers.ts | 31 - .../components/annotations/follow-up-state.ts | 57 - .../annotations/interaction-policy.ts | 44 - .../annotations/interaction-selectors.ts | 23 - .../annotations/interaction-state-machine.ts | 136 - .../annotations/island-dismiss-policy.ts | 18 - .../components/annotations/island-motion.ts | 70 - .../annotations/selection-restore.ts | 50 - .../use-annotation-cancel-restore.ts | 18 - .../use-annotation-interaction-controller.ts | 111 - .../use-annotation-island-events.ts | 114 - .../use-annotation-island-presentation.ts | 102 - .../components/chat/AcceptPlanDropdown.tsx | 227 - .../src/components/chat/InlineExecution.tsx | 236 - .../ui/src/components/chat/SessionViewer.tsx | 243 - .../ui/src/components/chat/SystemMessage.tsx | 84 - .../ui/src/components/chat/TurnCard.tsx | 3568 --- .../components/chat/TurnCardActionsMenu.tsx | 84 - .../src/components/chat/UserMessageBubble.tsx | 666 - .../chat/__tests__/assistant-ui-key.test.ts | 42 - .../chat/__tests__/turn-lifecycle.test.ts | 404 - .../chat/__tests__/turn-phase.test.ts | 422 - .../chat/__tests__/turn-timeline.test.ts | 161 - .../__tests__/turn-utils-grouping.test.ts | 725 - .../__tests__/turn-utils-incremental.test.ts | 95 - .../turn-utils-plan-annotations.test.ts | 59 - ...user-message-bubble-text-elements.test.tsx | 46 - .../components/chat/attachment-helpers.tsx | 209 - .../src/components/chat/follow-up-helpers.ts | 30 - .../packages/ui/src/components/chat/index.ts | 21 - .../ui/src/components/chat/turn-timeline.ts | 195 - .../ui/src/components/chat/turn-utils.ts | 1323 -- .../src/components/code-viewer/DiffIcons.tsx | 94 - .../code-viewer/DiffViewerControls.tsx | 89 - .../code-viewer/ShikiCodeViewer.tsx | 187 - .../code-viewer/ShikiDiffViewer.tsx | 221 - .../code-viewer/UnifiedDiffViewer.tsx | 246 - .../ui/src/components/code-viewer/index.ts | 10 - .../components/code-viewer/language-map.ts | 119 - .../code-viewer/registerShikiThemes.ts | 24 - .../ui/src/components/icons/Folder.tsx | 28 - .../packages/ui/src/components/icons/Home.tsx | 29 - .../ui/src/components/icons/Inbox.tsx | 28 - .../packages/ui/src/components/icons/index.ts | 4 - .../packages/ui/src/components/icons/types.ts | 9 - .../ui/src/components/markdown/CodeBlock.tsx | 234 - .../markdown/CollapsibleMarkdownContext.tsx | 61 - .../markdown/CollapsibleSection.tsx | 103 - .../components/markdown/ImageCardStack.tsx | 219 - .../ui/src/components/markdown/Markdown.tsx | 739 - .../markdown/MarkdownDatatableBlock.tsx | 720 - .../components/markdown/MarkdownDiffBlock.tsx | 128 - .../components/markdown/MarkdownHtmlBlock.tsx | 265 - .../markdown/MarkdownImageBlock.tsx | 283 - .../components/markdown/MarkdownJsonBlock.tsx | 205 - .../markdown/MarkdownLatexBlock.tsx | 43 - .../markdown/MarkdownMermaidBlock.tsx | 262 - .../components/markdown/MarkdownPdfBlock.tsx | 252 - .../markdown/MarkdownSpreadsheetBlock.tsx | 319 - .../components/markdown/RichBlockShell.tsx | 40 - .../markdown/TableExportDropdown.tsx | 77 - .../components/markdown/TiptapBubbleMenus.tsx | 533 - .../markdown/TiptapCodeBlockView.tsx | 325 - .../markdown/TiptapHoverActions.tsx | 52 - .../markdown/TiptapMarkdownEditor.tsx | 426 - .../components/markdown/TiptapSlashMenu.ts | 545 - .../__tests__/annotation-resolver.test.ts | 82 - .../markdown/__tests__/diff-normalize.test.ts | 183 - .../markdown/__tests__/linkify.test.ts | 296 - .../__tests__/markdown-link-routing.test.ts | 107 - .../markdown/__tests__/math-options.test.ts | 42 - .../official-markdown-math-foundation.test.ts | 192 - .../__tests__/safe-components.test.ts | 382 - .../tiptap-mermaid-input-helpers.test.ts | 31 - .../__tests__/tiptap-slash-menu.test.ts | 151 - .../markdown/annotation-resolver.ts | 172 - .../src/components/markdown/diff-normalize.ts | 128 - .../markdown/extensions/AnimatedTaskItem.ts | 152 - .../markdown/extensions/LatexBlock.tsx | 80 - .../markdown/extensions/MermaidBlock.tsx | 78 - .../extensions/RichBlockInteractions.ts | 224 - .../markdown/extensions/TiptapImageBlock.tsx | 160 - .../extensions/animated-task-item.css | 163 - .../extensions/animated-task-item.tokens.ts | 7 - .../ui/src/components/markdown/index.ts | 14 - .../ui/src/components/markdown/link-target.ts | 59 - .../ui/src/components/markdown/linkify.ts | 303 - .../src/components/markdown/math-options.ts | 9 - .../markdown/remarkCollapsibleSections.ts | 129 - .../components/markdown/rich-block-events.ts | 1 - .../components/markdown/safe-components.tsx | 111 - .../src/components/markdown/table-export.ts | 261 - .../src/components/markdown/tiptap-editor.css | 610 - .../src/components/markdown/useScrollFade.ts | 50 - .../overlay/ActivityCardsOverlay.tsx | 199 - .../overlay/AnnotatableMarkdownDocument.tsx | 643 - .../components/overlay/CodePreviewOverlay.tsx | 113 - .../src/components/overlay/ContentFrame.tsx | 115 - .../ui/src/components/overlay/CopyButton.tsx | 56 - .../components/overlay/DataTableOverlay.tsx | 63 - .../DocumentFormattedMarkdownOverlay.tsx | 143 - .../overlay/FullscreenOverlayBase.tsx | 246 - .../overlay/FullscreenOverlayBaseHeader.tsx | 256 - .../src/components/overlay/GenericOverlay.tsx | 185 - .../components/overlay/HTMLPreviewOverlay.tsx | 216 - .../overlay/ImagePreviewOverlay.tsx | 200 - .../src/components/overlay/ItemNavigator.tsx | 109 - .../components/overlay/JSONPreviewOverlay.tsx | 177 - .../overlay/MermaidPreviewOverlay.tsx | 113 - .../overlay/MultiDiffPreviewOverlay.tsx | 395 - .../components/overlay/OverlayErrorBanner.tsx | 33 - .../components/overlay/PDFPreviewOverlay.tsx | 168 - .../src/components/overlay/PreviewOverlay.tsx | 210 - .../overlay/TerminalPreviewOverlay.tsx | 96 - .../src/components/overlay/ZoomControls.tsx | 180 - .../fullscreen-overlay-escape-stack.test.ts | 29 - .../__tests__/rich-block-parity.test.ts | 15 - .../useRichBlockInteractions.test.ts | 36 - .../ui/src/components/overlay/index.ts | 24 - .../overlay/rich-block-interaction-spec.ts | 42 - .../overlay/useRichBlockInteractions.ts | 208 - .../components/terminal/TerminalOutput.tsx | 222 - .../ui/src/components/terminal/ansi-parser.ts | 153 - .../ui/src/components/terminal/index.ts | 14 - .../packages/ui/src/components/tooltip.tsx | 44 - .../ui/src/components/ui/BrowserControls.tsx | 354 - .../components/ui/BrowserEmptyStateCard.tsx | 69 - .../ui/src/components/ui/BrowserShader.tsx | 96 - .../components/ui/FilterableSelectPopover.tsx | 225 - .../ui/src/components/ui/InlineMenuSurface.ts | 121 - .../packages/ui/src/components/ui/Island.tsx | 813 - .../ui/IslandFollowUpContentView.tsx | 292 - .../ui/src/components/ui/LoadingIndicator.tsx | 139 - .../ui/src/components/ui/PreviewHeader.tsx | 169 - .../ui/src/components/ui/SimpleDropdown.tsx | 334 - .../ui/src/components/ui/StyledDropdown.tsx | 243 - .../ui/__tests__/Island.escape.test.ts | 64 - .../ui/__tests__/styled-dropdown.test.ts | 36 - .../packages/ui/src/components/ui/index.ts | 63 - .../src/components/ui/useIslandNavigation.ts | 62 - .../ui/src/context/PlatformContext.tsx | 190 - .../ui/src/context/ShikiThemeContext.tsx | 68 - .../desktop/packages/ui/src/context/index.ts | 16 - packages/desktop/packages/ui/src/index.ts | 287 - .../lib/__tests__/tool-parsers-cards.test.ts | 93 - .../ui/src/lib/dismissible-layer-bridge.ts | 43 - .../ui/src/lib/file-classification.ts | 127 - .../desktop/packages/ui/src/lib/layout.ts | 97 - .../packages/ui/src/lib/open-external-url.ts | 60 - .../packages/ui/src/lib/tool-parsers.ts | 569 - packages/desktop/packages/ui/src/lib/utils.ts | 13 - .../desktop/packages/ui/src/pdfjs-worker.d.ts | 4 - .../desktop/packages/ui/src/styles/index.css | 549 - packages/desktop/packages/ui/tsconfig.json | 18 - packages/desktop/scripts/browser-tool.ts | 167 - packages/desktop/scripts/build-server.ts | 893 - packages/desktop/scripts/build-wa-worker.ts | 131 - packages/desktop/scripts/build/common.ts | 509 - packages/desktop/scripts/build/darwin.ts | 81 - packages/desktop/scripts/build/linux.ts | 50 - packages/desktop/scripts/build/win32.ts | 255 - .../desktop/scripts/bump-desktop-version.ts | 115 - .../desktop/scripts/bump-live-host-version.ts | 40 - packages/desktop/scripts/check-i18n-parity.ts | 91 - .../desktop/scripts/check-release-version.ts | 144 - .../scripts/desktop-release-version.ts | 87 - packages/desktop/scripts/dev-instance.ts | 171 - packages/desktop/scripts/dev-server.ts | 91 - packages/desktop/scripts/dev-vite.ts | 161 - packages/desktop/scripts/docker-smoke-test.sh | 114 - .../desktop/scripts/electron-build-main.ts | 302 - .../desktop/scripts/electron-build-preload.ts | 145 - .../scripts/electron-build-renderer.ts | 27 - .../scripts/electron-build-resources.ts | 19 - .../scripts/electron-builder-config.ts | 73 - packages/desktop/scripts/electron-clean.ts | 23 - packages/desktop/scripts/electron-dev.ts | 755 - packages/desktop/scripts/generate-dev-cert.sh | 29 - packages/desktop/scripts/install-app.ps1 | 266 - packages/desktop/scripts/install-app.sh | 416 - packages/desktop/scripts/install-server.sh | 106 - .../desktop/scripts/test-workflow-local.sh | 16 - packages/desktop/scripts/vendor-qwen-code.ts | 252 - packages/desktop/tsconfig.base.json | 17 - packages/desktop/tsconfig.json | 30 - .../client/adapters/toolClassification.ts | 13 +- .../client/constants/localCommands.ts | 1 - packages/web-shell/client/i18n.tsx | 3 - scripts/check-desktop-isolation.js | 2 +- scripts/check-voice-guard-sync.js | 232 - scripts/desktop-openwork-sync.ts | 839 - scripts/tests/check-voice-guard-sync.test.js | 164 - scripts/tests/live-host-oss-workflow.test.js | 96 - scripts/tests/qwen-autofix-workflow.test.js | 8 +- scripts/tests/release-workflow.test.js | 75 +- scripts/tests/workspaces.test.js | 8 +- 1723 files changed, 136 insertions(+), 383635 deletions(-) delete mode 100644 .github/workflows/live-host-release.yml delete mode 100644 .github/workflows/live-host.yml delete mode 100644 .github/workflows/sync-live-host-to-oss.yml delete mode 100644 .qwen/skills/openwork-desktop-sync/SKILL.md delete mode 100644 packages/desktop/.agents/skills/desktop-brand-builder/SKILL.md delete mode 100644 packages/desktop/.agents/skills/desktop-brand-builder/scripts/brand-create.ts delete mode 100644 packages/desktop/.agents/skills/desktop-develop/SKILL.md delete mode 100644 packages/desktop/.agents/skills/desktop-develop/references/harness-principles.md delete mode 100644 packages/desktop/.agents/skills/desktop-pet/SKILL.md delete mode 100644 packages/desktop/.agents/skills/desktop-pet/scripts/gen_spritesheet.py delete mode 100644 packages/desktop/.dockerignore delete mode 100644 packages/desktop/.github/ISSUE_TEMPLATE/bug_report.yml delete mode 100644 packages/desktop/.github/ISSUE_TEMPLATE/feature_request.yml delete mode 100644 packages/desktop/.github/workflows/desktop-release.yml delete mode 100644 packages/desktop/.gitignore delete mode 100644 packages/desktop/.nvmrc delete mode 100644 packages/desktop/CODE_OF_CONDUCT.md delete mode 100644 packages/desktop/CONTRIBUTING.md delete mode 100644 packages/desktop/LICENSE delete mode 100644 packages/desktop/NOTICE delete mode 100644 packages/desktop/README.md delete mode 100644 packages/desktop/SECURITY.md delete mode 100644 packages/desktop/TRADEMARK.md delete mode 100644 packages/desktop/apps/cli/package.json delete mode 100644 packages/desktop/apps/cli/src/client.test.ts delete mode 100644 packages/desktop/apps/cli/src/client.ts delete mode 100644 packages/desktop/apps/cli/src/commands.test.ts delete mode 100755 packages/desktop/apps/cli/src/index.ts delete mode 100644 packages/desktop/apps/cli/src/run.test.ts delete mode 100644 packages/desktop/apps/cli/src/server-spawner.ts delete mode 100644 packages/desktop/apps/cli/tsconfig.json delete mode 100644 packages/desktop/apps/electron/.gitignore delete mode 100644 packages/desktop/apps/electron/README.md delete mode 100644 packages/desktop/apps/electron/build/entitlements.mac.plist delete mode 100644 packages/desktop/apps/electron/components.json delete mode 100644 packages/desktop/apps/electron/electron-builder.yml delete mode 100644 packages/desktop/apps/electron/eslint-rules/__tests__/no-hardcoded-z-index.test.ts delete mode 100644 packages/desktop/apps/electron/eslint-rules/no-direct-file-open.cjs delete mode 100644 packages/desktop/apps/electron/eslint-rules/no-direct-navigation-state.cjs delete mode 100644 packages/desktop/apps/electron/eslint-rules/no-direct-platform-check.cjs delete mode 100644 packages/desktop/apps/electron/eslint-rules/no-hardcoded-path-separator.cjs delete mode 100644 packages/desktop/apps/electron/eslint-rules/no-hardcoded-z-index.cjs delete mode 100644 packages/desktop/apps/electron/eslint-rules/no-inline-source-auth-check.cjs delete mode 100644 packages/desktop/apps/electron/eslint-rules/no-localstorage.cjs delete mode 100644 packages/desktop/apps/electron/eslint-rules/no-nonstandard-shadows.cjs delete mode 100644 packages/desktop/apps/electron/eslint.config.mjs delete mode 100644 packages/desktop/apps/electron/package.json delete mode 100644 packages/desktop/apps/electron/resources/AGENTS.md delete mode 100755 packages/desktop/apps/electron/resources/bin/craft-agent delete mode 100644 packages/desktop/apps/electron/resources/bin/craft-agent.cmd delete mode 100755 packages/desktop/apps/electron/resources/bin/doc-diff delete mode 100644 packages/desktop/apps/electron/resources/bin/doc-diff.cmd delete mode 100755 packages/desktop/apps/electron/resources/bin/docx-tool delete mode 100644 packages/desktop/apps/electron/resources/bin/docx-tool.cmd delete mode 100755 packages/desktop/apps/electron/resources/bin/ical-tool delete mode 100644 packages/desktop/apps/electron/resources/bin/ical-tool.cmd delete mode 100755 packages/desktop/apps/electron/resources/bin/img-tool delete mode 100644 packages/desktop/apps/electron/resources/bin/img-tool.cmd delete mode 100755 packages/desktop/apps/electron/resources/bin/markitdown delete mode 100644 packages/desktop/apps/electron/resources/bin/markitdown.cmd delete mode 100755 packages/desktop/apps/electron/resources/bin/pdf-tool delete mode 100644 packages/desktop/apps/electron/resources/bin/pdf-tool.cmd delete mode 100755 packages/desktop/apps/electron/resources/bin/pptx-tool delete mode 100644 packages/desktop/apps/electron/resources/bin/pptx-tool.cmd delete mode 100755 packages/desktop/apps/electron/resources/bin/xlsx-tool delete mode 100644 packages/desktop/apps/electron/resources/bin/xlsx-tool.cmd delete mode 100644 packages/desktop/apps/electron/resources/brands/openwork/dock.png delete mode 100644 packages/desktop/apps/electron/resources/brands/openwork/icon.icns delete mode 100644 packages/desktop/apps/electron/resources/brands/openwork/icon.iconset/icon_128x128.png delete mode 100644 packages/desktop/apps/electron/resources/brands/openwork/icon.iconset/icon_128x128@2x.png delete mode 100644 packages/desktop/apps/electron/resources/brands/openwork/icon.iconset/icon_16x16.png delete mode 100644 packages/desktop/apps/electron/resources/brands/openwork/icon.iconset/icon_16x16@2x.png delete mode 100644 packages/desktop/apps/electron/resources/brands/openwork/icon.iconset/icon_256x256.png delete mode 100644 packages/desktop/apps/electron/resources/brands/openwork/icon.iconset/icon_256x256@2x.png delete mode 100644 packages/desktop/apps/electron/resources/brands/openwork/icon.iconset/icon_32x32.png delete mode 100644 packages/desktop/apps/electron/resources/brands/openwork/icon.iconset/icon_32x32@2x.png delete mode 100644 packages/desktop/apps/electron/resources/brands/openwork/icon.iconset/icon_512x512.png delete mode 100644 packages/desktop/apps/electron/resources/brands/openwork/icon.iconset/icon_512x512@2x.png delete mode 100644 packages/desktop/apps/electron/resources/brands/openwork/icon.iconset/icon_64x64.png delete mode 100644 packages/desktop/apps/electron/resources/brands/openwork/icon.png delete mode 100644 packages/desktop/apps/electron/resources/brands/openwork/symbol.png delete mode 100644 packages/desktop/apps/electron/resources/brands/qwen-code/dock.png delete mode 100644 packages/desktop/apps/electron/resources/brands/qwen-code/icon.icns delete mode 100644 packages/desktop/apps/electron/resources/brands/qwen-code/icon.ico delete mode 100644 packages/desktop/apps/electron/resources/brands/qwen-code/icon.icon/Assets/icon.svg delete mode 100644 packages/desktop/apps/electron/resources/brands/qwen-code/icon.icon/icon.json delete mode 100644 packages/desktop/apps/electron/resources/brands/qwen-code/icon.png delete mode 100644 packages/desktop/apps/electron/resources/brands/qwen-code/icon.svg delete mode 100755 packages/desktop/apps/electron/resources/bridge-mcp-server/index.js delete mode 100644 packages/desktop/apps/electron/resources/config-defaults.json delete mode 100644 packages/desktop/apps/electron/resources/craft-logos/craft_app_icon.png delete mode 100644 packages/desktop/apps/electron/resources/craft-logos/craft_app_icon_dark.png delete mode 100644 packages/desktop/apps/electron/resources/craft-logos/craft_logo_black.png delete mode 100644 packages/desktop/apps/electron/resources/craft-logos/craft_logo_white.png delete mode 100644 packages/desktop/apps/electron/resources/dmg-background.png delete mode 100644 packages/desktop/apps/electron/resources/dmg-background.tiff delete mode 100644 packages/desktop/apps/electron/resources/dmg-background@2x.png delete mode 100644 packages/desktop/apps/electron/resources/docs/automations.md delete mode 100644 packages/desktop/apps/electron/resources/docs/browser-tools.md delete mode 100644 packages/desktop/apps/electron/resources/docs/craft-cli.md delete mode 100644 packages/desktop/apps/electron/resources/docs/data-tables.md delete mode 100644 packages/desktop/apps/electron/resources/docs/html-preview.md delete mode 100644 packages/desktop/apps/electron/resources/docs/image-preview.md delete mode 100644 packages/desktop/apps/electron/resources/docs/labels.md delete mode 100644 packages/desktop/apps/electron/resources/docs/llm-tool.md delete mode 100644 packages/desktop/apps/electron/resources/docs/mermaid.md delete mode 100644 packages/desktop/apps/electron/resources/docs/pdf-preview.md delete mode 100644 packages/desktop/apps/electron/resources/docs/permissions.md delete mode 100644 packages/desktop/apps/electron/resources/docs/skills.md delete mode 100644 packages/desktop/apps/electron/resources/docs/sources.md delete mode 100644 packages/desktop/apps/electron/resources/docs/statuses.md delete mode 100644 packages/desktop/apps/electron/resources/docs/themes.md delete mode 100644 packages/desktop/apps/electron/resources/docs/tool-icons.md delete mode 100755 packages/desktop/apps/electron/resources/generate-icons.sh delete mode 100644 packages/desktop/apps/electron/resources/permissions/default.json delete mode 100644 packages/desktop/apps/electron/resources/release-notes/0.0.1.md delete mode 100644 packages/desktop/apps/electron/resources/scripts/doc_diff.py delete mode 100644 packages/desktop/apps/electron/resources/scripts/docx_tool.py delete mode 100644 packages/desktop/apps/electron/resources/scripts/ical_tool.py delete mode 100644 packages/desktop/apps/electron/resources/scripts/img_tool.py delete mode 100644 packages/desktop/apps/electron/resources/scripts/markitdown_cli.py delete mode 100644 packages/desktop/apps/electron/resources/scripts/pdf_tool.py delete mode 100644 packages/desktop/apps/electron/resources/scripts/pptx_tool.py delete mode 100644 packages/desktop/apps/electron/resources/scripts/tests/README.md delete mode 100644 packages/desktop/apps/electron/resources/scripts/tests/_tool_test_harness.py delete mode 100644 packages/desktop/apps/electron/resources/scripts/tests/test_doc_diff_smoke.py delete mode 100644 packages/desktop/apps/electron/resources/scripts/tests/test_docx_tool_smoke.py delete mode 100644 packages/desktop/apps/electron/resources/scripts/tests/test_ical_tool_smoke.py delete mode 100644 packages/desktop/apps/electron/resources/scripts/tests/test_img_tool_smoke.py delete mode 100644 packages/desktop/apps/electron/resources/scripts/tests/test_markitdown_smoke.py delete mode 100644 packages/desktop/apps/electron/resources/scripts/tests/test_pdf_tool_smoke.py delete mode 100644 packages/desktop/apps/electron/resources/scripts/tests/test_pptx_tool_smoke.py delete mode 100644 packages/desktop/apps/electron/resources/scripts/tests/test_xlsx_tool_smoke.py delete mode 100644 packages/desktop/apps/electron/resources/scripts/xlsx_tool.py delete mode 100644 packages/desktop/apps/electron/resources/source.png delete mode 100644 packages/desktop/apps/electron/resources/themes/catppuccin.json delete mode 100644 packages/desktop/apps/electron/resources/themes/default.json delete mode 100644 packages/desktop/apps/electron/resources/themes/dracula.json delete mode 100644 packages/desktop/apps/electron/resources/themes/ghostty.json delete mode 100644 packages/desktop/apps/electron/resources/themes/github.json delete mode 100644 packages/desktop/apps/electron/resources/themes/gruvbox.json delete mode 100644 packages/desktop/apps/electron/resources/themes/haze.json delete mode 100644 packages/desktop/apps/electron/resources/themes/night-owl.json delete mode 100644 packages/desktop/apps/electron/resources/themes/nord.json delete mode 100644 packages/desktop/apps/electron/resources/themes/one-dark-pro.json delete mode 100644 packages/desktop/apps/electron/resources/themes/pierre.json delete mode 100644 packages/desktop/apps/electron/resources/themes/rose-pine.json delete mode 100644 packages/desktop/apps/electron/resources/themes/solarized.json delete mode 100644 packages/desktop/apps/electron/resources/themes/tokyo-night.json delete mode 100644 packages/desktop/apps/electron/resources/themes/vitesse.json delete mode 100644 packages/desktop/apps/electron/resources/tool-icons/1password.png delete mode 100644 packages/desktop/apps/electron/resources/tool-icons/amazonaws.png delete mode 100644 packages/desktop/apps/electron/resources/tool-icons/bun.png delete mode 100644 packages/desktop/apps/electron/resources/tool-icons/chrome.svg delete mode 100644 packages/desktop/apps/electron/resources/tool-icons/cloudflare.png delete mode 100644 packages/desktop/apps/electron/resources/tool-icons/cmake.ico delete mode 100644 packages/desktop/apps/electron/resources/tool-icons/cocoapods.png delete mode 100644 packages/desktop/apps/electron/resources/tool-icons/craft-agent.svg delete mode 100644 packages/desktop/apps/electron/resources/tool-icons/curl.svg delete mode 100644 packages/desktop/apps/electron/resources/tool-icons/deno.png delete mode 100644 packages/desktop/apps/electron/resources/tool-icons/docker.png delete mode 100644 packages/desktop/apps/electron/resources/tool-icons/esbuild.svg delete mode 100644 packages/desktop/apps/electron/resources/tool-icons/eslint.png delete mode 100644 packages/desktop/apps/electron/resources/tool-icons/firebase.png delete mode 100644 packages/desktop/apps/electron/resources/tool-icons/flydotio.png delete mode 100644 packages/desktop/apps/electron/resources/tool-icons/git.ico delete mode 100644 packages/desktop/apps/electron/resources/tool-icons/github.png delete mode 100644 packages/desktop/apps/electron/resources/tool-icons/gnumake.png delete mode 100644 packages/desktop/apps/electron/resources/tool-icons/go.png delete mode 100644 packages/desktop/apps/electron/resources/tool-icons/googlecloud.png delete mode 100644 packages/desktop/apps/electron/resources/tool-icons/heroku.png delete mode 100644 packages/desktop/apps/electron/resources/tool-icons/homebrew.png delete mode 100644 packages/desktop/apps/electron/resources/tool-icons/jest.png delete mode 100644 packages/desktop/apps/electron/resources/tool-icons/jq.png delete mode 100644 packages/desktop/apps/electron/resources/tool-icons/kubernetes.png delete mode 100644 packages/desktop/apps/electron/resources/tool-icons/microsoftazure.ico delete mode 100644 packages/desktop/apps/electron/resources/tool-icons/mongodb.ico delete mode 100644 packages/desktop/apps/electron/resources/tool-icons/mysql.png delete mode 100644 packages/desktop/apps/electron/resources/tool-icons/netlify.png delete mode 100644 packages/desktop/apps/electron/resources/tool-icons/ngrok.svg delete mode 100644 packages/desktop/apps/electron/resources/tool-icons/nodedotjs.png delete mode 100644 packages/desktop/apps/electron/resources/tool-icons/npm.png delete mode 100644 packages/desktop/apps/electron/resources/tool-icons/nx.png delete mode 100644 packages/desktop/apps/electron/resources/tool-icons/openssh.png delete mode 100644 packages/desktop/apps/electron/resources/tool-icons/pnpm.png delete mode 100644 packages/desktop/apps/electron/resources/tool-icons/postgresql.png delete mode 100644 packages/desktop/apps/electron/resources/tool-icons/prettier.png delete mode 100644 packages/desktop/apps/electron/resources/tool-icons/pypi.png delete mode 100644 packages/desktop/apps/electron/resources/tool-icons/pytest.png delete mode 100644 packages/desktop/apps/electron/resources/tool-icons/python.ico delete mode 100644 packages/desktop/apps/electron/resources/tool-icons/redis.png delete mode 100644 packages/desktop/apps/electron/resources/tool-icons/ruby.ico delete mode 100644 packages/desktop/apps/electron/resources/tool-icons/rust.png delete mode 100644 packages/desktop/apps/electron/resources/tool-icons/sentry.png delete mode 100644 packages/desktop/apps/electron/resources/tool-icons/sqlite.jpg delete mode 100644 packages/desktop/apps/electron/resources/tool-icons/stripe.png delete mode 100644 packages/desktop/apps/electron/resources/tool-icons/supabase.png delete mode 100644 packages/desktop/apps/electron/resources/tool-icons/swift.ico delete mode 100644 packages/desktop/apps/electron/resources/tool-icons/terraform.png delete mode 100644 packages/desktop/apps/electron/resources/tool-icons/tool-icons.json delete mode 100644 packages/desktop/apps/electron/resources/tool-icons/turborepo.png delete mode 100644 packages/desktop/apps/electron/resources/tool-icons/vercel.png delete mode 100644 packages/desktop/apps/electron/resources/tool-icons/vite.svg delete mode 100644 packages/desktop/apps/electron/resources/tool-icons/vitest.png delete mode 100644 packages/desktop/apps/electron/resources/tool-icons/webpack.png delete mode 100644 packages/desktop/apps/electron/resources/tool-icons/xcode.png delete mode 100644 packages/desktop/apps/electron/resources/tool-icons/yarn.svg delete mode 100644 packages/desktop/apps/electron/scripts/afterPack.cjs delete mode 100755 packages/desktop/apps/electron/scripts/build-dmg.sh delete mode 100755 packages/desktop/apps/electron/scripts/build-linux.sh delete mode 100644 packages/desktop/apps/electron/scripts/build-win.ps1 delete mode 100644 packages/desktop/apps/electron/scripts/copy-assets.ts delete mode 100644 packages/desktop/apps/electron/scripts/generate-pet-spritesheets.mjs delete mode 100644 packages/desktop/apps/electron/src/__tests__/transport.test.ts delete mode 100644 packages/desktop/apps/electron/src/main/__tests__/auto-update-signature.test.ts delete mode 100644 packages/desktop/apps/electron/src/main/__tests__/browser-cdp.test.ts delete mode 100644 packages/desktop/apps/electron/src/main/__tests__/browser-pane-manager.test.ts delete mode 100644 packages/desktop/apps/electron/src/main/__tests__/browser-tool-detection.test.ts delete mode 100644 packages/desktop/apps/electron/src/main/__tests__/deep-link-routing.test.ts delete mode 100644 packages/desktop/apps/electron/src/main/__tests__/git-bash.test.ts delete mode 100644 packages/desktop/apps/electron/src/main/__tests__/init-gate.test.ts delete mode 100644 packages/desktop/apps/electron/src/main/__tests__/network-proxy.test.ts delete mode 100644 packages/desktop/apps/electron/src/main/__tests__/notifications-routing.isolated.ts delete mode 100644 packages/desktop/apps/electron/src/main/__tests__/session-branch-cleanup.test.ts delete mode 100644 packages/desktop/apps/electron/src/main/__tests__/session-event-message-parity.test.ts delete mode 100644 packages/desktop/apps/electron/src/main/__tests__/session-lazy-load-race.test.ts delete mode 100644 packages/desktop/apps/electron/src/main/__tests__/session-message-parity.test.ts delete mode 100644 packages/desktop/apps/electron/src/main/__tests__/session-turn-grouping-parity.test.ts delete mode 100644 packages/desktop/apps/electron/src/main/__tests__/sessions-annotations.isolated.ts delete mode 100644 packages/desktop/apps/electron/src/main/__tests__/sessions-browser-release.test.ts delete mode 100644 packages/desktop/apps/electron/src/main/__tests__/sessions-sanitize.test.ts delete mode 100644 packages/desktop/apps/electron/src/main/auto-update-signature.ts delete mode 100644 packages/desktop/apps/electron/src/main/auto-update.ts delete mode 100644 packages/desktop/apps/electron/src/main/browser-cdp.ts delete mode 100644 packages/desktop/apps/electron/src/main/browser-pane-manager.ts delete mode 100644 packages/desktop/apps/electron/src/main/chunked-rpc.ts delete mode 100644 packages/desktop/apps/electron/src/main/deep-link.ts delete mode 100644 packages/desktop/apps/electron/src/main/handlers/__tests__/registration-profiles.test.ts delete mode 100644 packages/desktop/apps/electron/src/main/handlers/__tests__/registration.test.ts delete mode 100644 packages/desktop/apps/electron/src/main/handlers/__tests__/session-watcher.test.ts delete mode 100644 packages/desktop/apps/electron/src/main/handlers/__tests__/sessions-watchers.test.ts delete mode 100644 packages/desktop/apps/electron/src/main/handlers/__tests__/settings-default-thinking.test.ts delete mode 100644 packages/desktop/apps/electron/src/main/handlers/browser.ts delete mode 100644 packages/desktop/apps/electron/src/main/handlers/handler-deps.ts delete mode 100644 packages/desktop/apps/electron/src/main/handlers/index.ts delete mode 100644 packages/desktop/apps/electron/src/main/handlers/pet-window.ts delete mode 100644 packages/desktop/apps/electron/src/main/handlers/settings.ts delete mode 100644 packages/desktop/apps/electron/src/main/handlers/system.ts delete mode 100644 packages/desktop/apps/electron/src/main/handlers/window-drag.ts delete mode 100644 packages/desktop/apps/electron/src/main/handlers/workspace.ts delete mode 100644 packages/desktop/apps/electron/src/main/index.ts delete mode 100644 packages/desktop/apps/electron/src/main/logger.ts delete mode 100644 packages/desktop/apps/electron/src/main/menu.ts delete mode 100644 packages/desktop/apps/electron/src/main/network-proxy-utils.ts delete mode 100644 packages/desktop/apps/electron/src/main/network-proxy.ts delete mode 100644 packages/desktop/apps/electron/src/main/notifications.ts delete mode 100644 packages/desktop/apps/electron/src/main/onboarding.ts delete mode 100644 packages/desktop/apps/electron/src/main/platform.ts delete mode 100644 packages/desktop/apps/electron/src/main/power-manager.ts delete mode 100644 packages/desktop/apps/electron/src/main/shell-env.ts delete mode 100644 packages/desktop/apps/electron/src/main/shims/abort-controller.cjs delete mode 100644 packages/desktop/apps/electron/src/main/shims/node-fetch.cjs delete mode 100644 packages/desktop/apps/electron/src/main/thumbnail-protocol.ts delete mode 100644 packages/desktop/apps/electron/src/main/voice/__tests__/frame-trust.test.ts delete mode 100644 packages/desktop/apps/electron/src/main/voice/frame-trust.ts delete mode 100644 packages/desktop/apps/electron/src/main/window-manager.ts delete mode 100644 packages/desktop/apps/electron/src/main/window-state.ts delete mode 100644 packages/desktop/apps/electron/src/preload/bootstrap.ts delete mode 100644 packages/desktop/apps/electron/src/preload/browser-toolbar.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/App.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/actions/__tests__/keybinding-context.test.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/actions/definitions.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/actions/index.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/actions/keybinding-context.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/actions/registry.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/actions/types.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/actions/useAction.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/actions/useHotkeyLabel.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/assets/messaging-icons/telegram.svg delete mode 100644 packages/desktop/apps/electron/src/renderer/assets/messaging-icons/whatsapp.svg delete mode 100644 packages/desktop/apps/electron/src/renderer/assets/pets/qwen-spritesheet.webp delete mode 100644 packages/desktop/apps/electron/src/renderer/assets/samples/sample-invoice.pdf delete mode 100644 packages/desktop/apps/electron/src/renderer/assets/samples/sample-landscape.jpg delete mode 100644 packages/desktop/apps/electron/src/renderer/assets/skill-market-hero.webp delete mode 100644 packages/desktop/apps/electron/src/renderer/atoms/__tests__/browser-pane.test.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/atoms/__tests__/panel-stack-lanes.test.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/atoms/__tests__/sessions.test.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/atoms/automations.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/atoms/browser-pane.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/atoms/messaging.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/atoms/new-session-draft.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/atoms/overlay.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/atoms/panel-stack.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/atoms/sessions.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/atoms/skills.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/atoms/sources.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/browser-empty-state.html delete mode 100644 packages/desktop/apps/electron/src/renderer/browser-empty-state.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/browser-toolbar.html delete mode 100644 packages/desktop/apps/electron/src/renderer/browser-toolbar.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/AboutDialog.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/AppMenu.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/KeyboardShortcuts.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/KeyboardShortcutsDialog.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ResetConfirmationDialog.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ServerDirectoryBrowser.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/SplashScreen.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/apisetup/ApiKeyInput.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/apisetup/OAuthConnect.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/apisetup/ProviderConnectDialog.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/apisetup/ProviderConnectForm.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/apisetup/index.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/ActiveOptionBadges.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/ActiveTasksBar.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/AppShell.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/AttachmentPreview.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/BatchSessionMenu.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/BrowserDockPanel.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/ChatDisplay.follow-ups.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/ChatDisplay.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/FilePreviewPanel.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/LeftSidebar.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/MainContentPanel.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/MarketplaceSkillIcon.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/MultiSelectPanel.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/NavigatorPanel.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/Panel.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/PanelHeader.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/PanelResizeSash.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/PanelSlot.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/PanelStackContainer.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/SendResourceToWorkspaceDialog.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/SendToWorkspaceDialog.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/SessionBadges.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/SessionInfoPopover.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/SessionItem.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/SessionList.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/SessionMenu.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/SessionMenuParts.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/SessionSearchHeader.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/SessionStatusIcon.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/SetupAuthBanner.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/SidebarMenu.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/SkillMarketplaceDetailPanel.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/SkillMarketplacePanel.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/SkillMenu.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/SkillsListPanel.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/SourceMenu.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/SourcesListPanel.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/TaskActionMenu.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/TopBar.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/TransportConnectionBanner.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/WorkspaceProjectTree.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/WorkspaceSwitcher.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/__tests__/ChatDisplay.follow-ups.test.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/__tests__/chat-display-branching.test.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/__tests__/error-message-actions.test.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/__tests__/open-url-in-built-in-browser.test.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/__tests__/transport-connection-banner.test.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/branching.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/error-message-actions.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/input/ChatInputZone.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/input/CompactPermissionModeSelector.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/input/FreeFormInput.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/input/FreeFormInputContextBadge.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/input/InputContainer.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/input/InputErrorBoundary.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/input/StructuredInput.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/input/ToolbarStatusSlot.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/input/__tests__/file-attachment-metadata.test.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/input/__tests__/file-attachment-order.test.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/input/__tests__/focus-input-events.test.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/input/__tests__/input-event-guards.test.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/input/__tests__/working-directory-history.test.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/input/file-attachment-metadata.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/input/file-attachment-order.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/input/focus-input-events.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/input/index.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/input/input-event-guards.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/input/structured/AdminApprovalRequest.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/input/structured/AskUserQuestionRequest.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/input/structured/CredentialRequest.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/input/structured/PermissionRequest.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/input/structured/types.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/input/useAutoGrow.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/input/voice/VoiceMicControl.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/input/voice/VoiceRecordingBar.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/input/voice/__tests__/useVoiceDictation.test.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/input/voice/useVoiceCapture.test.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/input/voice/useVoiceCapture.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/input/voice/useVoiceDictation.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/input/voice/voice-frame-sender.test.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/input/voice/voice-frame-sender.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/input/voice/voiceModels.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/input/working-directory-history.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/open-url-in-built-in-browser.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/panel-constants.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/plan-approval-message.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/components/app-shell/sidebar-types.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/components/automations/ActionTypeIcon.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/automations/AutomationActionPreview.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/automations/AutomationActionRow.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/automations/AutomationAvatar.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/automations/AutomationCard.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/automations/AutomationEventTimeline.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/automations/AutomationInfoPage.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/automations/AutomationMenu.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/automations/AutomationTestPanel.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/automations/AutomationsListPanel.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/automations/BatchAutomationMenu.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/automations/CronBuilder.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/automations/PhaseBadge.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/automations/__tests__/types.test.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/components/automations/constants.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/components/automations/types.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/components/automations/utils.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/components/browser/BrowserTabBadge.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/browser/BrowserTabStrip.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/browser/BrowserToolbar.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/browser/__tests__/utils.test.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/components/browser/empty-state-prompts.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/components/browser/utils.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/components/chat/AuthRequestCard.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/chat/EmptyStateHint.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/files/FileViewer.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/icons/ConnectionIcon.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/icons/CraftAgentsLogo.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/icons/CraftAgentsSymbol.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/icons/McpIcon.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/icons/PanelLeftRounded.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/icons/PanelRightRounded.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/icons/SettingsIcons.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/icons/SquarePenRounded.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/icons/TodoStateIcons.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/info/AutoRulesDataTable.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/info/Info_Alert.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/info/Info_Badge.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/info/Info_DataTable.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/info/Info_GroupedList.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/info/Info_Markdown.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/info/Info_Page.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/info/Info_Section.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/info/Info_StatusBadge.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/info/Info_Table.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/info/LabelsDataTable.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/info/PermissionsDataTable.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/info/ToolsDataTable.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/info/index.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/components/markdown/StreamingMarkdown.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/markdown/index.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/components/messaging/MessagingDialogHost.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/messaging/MessagingPlatformIcon.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/messaging/MessagingSessionMenuItem.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/messaging/PairingCodeDialog.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/messaging/TelegramConnectDialog.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/messaging/WhatsAppConnectDialog.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/onboarding/APISetupStep.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/onboarding/CompletionStep.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/onboarding/CredentialsStep.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/onboarding/GitBashWarning.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/onboarding/OnboardingWizard.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/onboarding/ProviderSelectStep.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/onboarding/ReauthScreen.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/onboarding/WelcomeStep.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/onboarding/index.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/components/onboarding/primitives.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/pet/DesktopPet.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/pet/PetNotifications.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/pet/PetWindowController.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/pet/QwenPet.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/preview/TableOfContents.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/right-sidebar/SessionFilesSection.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/right-sidebar/__tests__/session-files-watch.test.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/components/right-sidebar/session-files-watch.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/components/settings/SearchableModelInput.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/settings/SettingsCard.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/settings/SettingsEditRow.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/settings/SettingsInput.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/settings/SettingsMenuSelect.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/settings/SettingsRadioGroup.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/settings/SettingsRow.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/settings/SettingsSection.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/settings/SettingsSegmentedControl.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/settings/SettingsSelect.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/settings/SettingsTextarea.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/settings/SettingsToggle.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/settings/SettingsUIConstants.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/components/settings/index.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/components/shiki/ShikiCodeEditor.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/shiki/ShikiCodeViewer.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/shiki/ShikiDiffViewer.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/shiki/index.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/EditPopover.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/HeaderIconButton.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/HeaderMenu.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/PanelHeaderCenterButton.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/SourceSelectorPopover.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/TopBarButton.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/__tests__/label-menu.test.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/__tests__/mention-menu.test.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/__tests__/rich-text-input.test.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/__tests__/slash-command-menu.test.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/__tests__/status-icon.test.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/action-menu-item.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/action-tooltip.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/avatar-group.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/avatar.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/badge.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/button.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/calendar.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/collapsible.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/command.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/context-menu.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/data-table.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/dialog.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/drawer.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/dropdown-menu.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/empty.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/entity-icon.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/entity-list-badge.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/entity-list-empty.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/entity-list-label-badge.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/entity-list.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/entity-panel.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/entity-row.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/fading-text.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/gradient-resize-handle.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/horizontal-resize-handle.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/input.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/kbd.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/label-badge-row.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/label-icon.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/label-menu-utils.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/label-menu.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/label-value-popover.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/label.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/mention-badge.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/mention-menu.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/menu-context.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/metadata-badge.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/popover.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/rename-dialog.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/resizable.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/rich-text-input.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/scroll-area.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/select.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/separator.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/service-logo.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/session-status-menu.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/skill-avatar.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/skill-mention-menu.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/slash-command-menu.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/sonner.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/sortable-list.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/source-avatar.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/source-status-indicator.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/status-icon.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/styled-context-menu.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/styled-dropdown.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/switch.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/table.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/tabs.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/textarea.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/ui/window-header-badge.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/workspace/AddWorkspaceStep_Choice.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/workspace/AddWorkspaceStep_ConnectRemote.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/workspace/AddWorkspaceStep_CreateNew.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/workspace/AddWorkspaceStep_OpenFolder.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/workspace/AddWorkspace_RadioOption.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/workspace/WorkspaceCreationScreen.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/workspace/WorkspacePicker.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/components/workspace/index.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/components/workspace/primitives.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/config/__tests__/session-status-config.test.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/config/layout.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/config/session-status-config.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/context/AppShellContext.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/context/DismissibleLayerContext.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/context/EscapeInterruptContext.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/context/FocusContext.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/context/ModalContext.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/context/SessionListContext.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/context/StoplightContext.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/context/ThemeContext.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/context/__tests__/dismissible-layer-context.test.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/contexts/NavigationContext.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/contexts/__tests__/navigation-history-key.test.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/contexts/__tests__/navigation-reconcile.test.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/contexts/navigation-history.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/contexts/navigation-reconcile.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/event-processor/handlers/__tests__/message-annotations-updated.test.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/event-processor/handlers/__tests__/text-message-id-sync.test.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/event-processor/handlers/__tests__/user-message-confirmation.test.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/event-processor/handlers/permission-mode-changed.test.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/event-processor/handlers/session.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/event-processor/handlers/text.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/event-processor/handlers/tool.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/event-processor/helpers.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/event-processor/index.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/event-processor/processor.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/event-processor/types.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/event-processor/useEventProcessor.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/hooks/__tests__/useMultiSelect.test.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/hooks/__tests__/useSessionSearch.test.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/hooks/keyboard/index.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/hooks/keyboard/useFocusZone.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/hooks/keyboard/useRovingTabIndex.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/hooks/useAutomations.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/hooks/useBackgroundTasks.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/hooks/useContainerWidth.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/hooks/useDirectoryPicker.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/hooks/useDynamicStack.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/hooks/useEntityListInteractions.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/hooks/useEntitySelection.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/hooks/useHorizontalResizeGradient.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/hooks/useLabels.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/hooks/useLinkInterceptor.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/hooks/useMultiSelect.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/hooks/useNotifications.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/hooks/useOnboarding.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/hooks/useReducedMotion.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/hooks/useResizablePanels.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/hooks/useResizeGradient.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/hooks/useSession.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/hooks/useSessionActions.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/hooks/useSessionOptions.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/hooks/useSessionSearch.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/hooks/useStaleSessionRecovery.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/hooks/useStatuses.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/hooks/useTheme.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/hooks/useTransportConnectionState.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/hooks/useTurnCardExpansion.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/hooks/useUpdateChecker.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/hooks/useViews.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/hooks/useWindowCloseHandler.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/hooks/useWorkspaceIcon.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/index.css delete mode 100644 packages/desktop/apps/electron/src/renderer/index.html delete mode 100644 packages/desktop/apps/electron/src/renderer/lib/__tests__/drafts.test.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/lib/__tests__/file-changes.test.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/lib/__tests__/icon-cache.test.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/lib/__tests__/input-text.test.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/lib/__tests__/mentions.test.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/lib/__tests__/overlay-detection.test.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/lib/__tests__/permission-mode-cycle.test.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/lib/__tests__/project-session-snapshots.test.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/lib/__tests__/qwen-settings-snapshot.test.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/lib/__tests__/reconnect-recovery.test.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/lib/__tests__/session-delete-navigation.test.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/lib/__tests__/session-load.test.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/lib/__tests__/skills-loading.test.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/lib/__tests__/slash-command-badges.test.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/lib/__tests__/transport-wait.test.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/lib/animations.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/lib/dismissible-layer-bridge.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/lib/drafts.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/lib/file-changes.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/lib/file-utils.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/lib/icon-cache.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/lib/input-text.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/lib/local-storage.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/lib/logger.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/lib/mentions.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/lib/merge-refs.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/lib/navigate.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/lib/navigation-registry.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/lib/overlay-detection.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/lib/perf.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/lib/permission-mode-cycle.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/lib/platform.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/lib/project-session-snapshots.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/lib/provider-icons.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/lib/qwen-capability-cache.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/lib/qwen-settings-snapshot.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/lib/reconnect-recovery.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/lib/session-delete-navigation.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/lib/session-load.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/lib/skill-mentions.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/lib/skills-loading.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/lib/slash-command-badges.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/lib/slugify.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/lib/smart-typography.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/lib/transport-wait.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/lib/utils.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/main.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/pages/ChatPage.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/pages/DraftChatPage.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/pages/PreferencesPage.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/pages/ShortcutsPage.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/pages/SkillInfoPage.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/pages/SourceInfoPage.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/pages/index.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/pages/settings/AiSettingsPage.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/pages/settings/AppSettingsPage.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/pages/settings/AppearanceSettingsPage.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/pages/settings/ExtensionsSettingsPage.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/pages/settings/GeneralSettingsPage.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/pages/settings/HooksSettingsPage.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/pages/settings/InputSettingsPage.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/pages/settings/LabelsSettingsPage.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/pages/settings/McpServersSettingsPage.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/pages/settings/MemorySettingsPage.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/pages/settings/MessagingSettingsPage.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/pages/settings/PermissionsSettingsPage.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/pages/settings/PreferencesPage.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/pages/settings/QwenSettingsPage.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/pages/settings/ServerSettingsPage.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/pages/settings/SettingsNavigator.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/pages/settings/ShortcutsPage.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/pages/settings/WorkspaceSettingsPage.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/pages/settings/index.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/pages/settings/settings-pages.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/pet-window.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/pet.html delete mode 100644 packages/desktop/apps/electron/src/renderer/pets/pet-animation.test.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/pets/pet-animation.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/pets/pet-atoms.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/pets/pet-size.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/pets/registry.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/pets/usePetActivityState.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/pets/usePetCompanion.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/pets/usePetNotifications.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/playground.html delete mode 100644 packages/desktop/apps/electron/src/renderer/playground.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/playground/ComponentPreview.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/playground/PlaygroundApp.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/playground/PlaygroundAppShellProvider.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/playground/Sidebar.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/playground/ThemeToggle.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/playground/VariantsSidebar.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/playground/__tests__/recent-working-dirs.test.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/playground/adapters/input-adapters.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/playground/demos/messaging/MessagingSettingsPagePreview.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/playground/demos/messaging/MessagingSubmenuPreview.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/playground/demos/messaging/PairingCodeDialogPreview.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/playground/demos/messaging/WhatsAppConnectDialogPreview.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/playground/mock-utils.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/playground/recent-working-dirs.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/playground/registry/automations.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/playground/registry/browser-ui.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/playground/registry/chat.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/playground/registry/container-transitions.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/playground/registry/custom-shadows.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/playground/registry/edit-popover.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/playground/registry/entity-lists.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/playground/registry/generate-icons.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/playground/registry/icons.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/playground/registry/index.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/playground/registry/input.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/playground/registry/label-badges.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/playground/registry/markdown.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/playground/registry/messages.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/playground/registry/messaging.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/playground/registry/oauth.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/playground/registry/planner.css delete mode 100644 packages/desktop/apps/electron/src/renderer/playground/registry/planner.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/playground/registry/sample-icons.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/playground/registry/samples/customer-support.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/playground/registry/samples/deployment.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/playground/registry/samples/incident-response.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/playground/registry/session-list.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/playground/registry/slash-command.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/playground/registry/toasts.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/playground/registry/transport-banner.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/playground/registry/turn-card-modes.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/playground/registry/turn-card.tsx delete mode 100644 packages/desktop/apps/electron/src/renderer/playground/registry/types.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/utils/__tests__/auth-validation.test.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/utils/__tests__/session-list-collapse.test.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/utils/__tests__/session.test.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/utils/auth-validation.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/utils/session-list-collapse.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/utils/session.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/utils/text.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/utils/workspace.ts delete mode 100644 packages/desktop/apps/electron/src/renderer/vite-env.d.ts delete mode 100644 packages/desktop/apps/electron/src/runtime/platform-headless.ts delete mode 100644 packages/desktop/apps/electron/src/runtime/platform.ts delete mode 100644 packages/desktop/apps/electron/src/shared/__tests__/browser-live-fx.test.ts delete mode 100644 packages/desktop/apps/electron/src/shared/__tests__/ipc-channels.test.ts delete mode 100644 packages/desktop/apps/electron/src/shared/__tests__/route-parser-automations.test.ts delete mode 100644 packages/desktop/apps/electron/src/shared/__tests__/route-parser-skill-marketplace.test.ts delete mode 100644 packages/desktop/apps/electron/src/shared/browser-live-fx.ts delete mode 100644 packages/desktop/apps/electron/src/shared/feature-flags.ts delete mode 100644 packages/desktop/apps/electron/src/shared/menu-schema.ts delete mode 100644 packages/desktop/apps/electron/src/shared/route-parser.ts delete mode 100644 packages/desktop/apps/electron/src/shared/routes.ts delete mode 100644 packages/desktop/apps/electron/src/shared/settings-registry.ts delete mode 100644 packages/desktop/apps/electron/src/shared/types.ts delete mode 100644 packages/desktop/apps/electron/src/transport/__tests__/channel-map-parity.test.ts delete mode 100644 packages/desktop/apps/electron/src/transport/__tests__/codec.test.ts delete mode 100644 packages/desktop/apps/electron/src/transport/__tests__/routed-client.test.ts delete mode 100644 packages/desktop/apps/electron/src/transport/build-api.ts delete mode 100644 packages/desktop/apps/electron/src/transport/channel-map.ts delete mode 100644 packages/desktop/apps/electron/src/transport/client.ts delete mode 100644 packages/desktop/apps/electron/src/transport/codec.ts delete mode 100644 packages/desktop/apps/electron/src/transport/index.ts delete mode 100644 packages/desktop/apps/electron/src/transport/routed-client.ts delete mode 100644 packages/desktop/apps/electron/src/transport/server.ts delete mode 100644 packages/desktop/apps/electron/tsconfig.json delete mode 100644 packages/desktop/apps/electron/vite.config.ts delete mode 100644 packages/desktop/apps/live-host/.gitignore delete mode 100644 packages/desktop/apps/live-host/README.md delete mode 100644 packages/desktop/apps/live-host/build/entitlements.mac.plist delete mode 100644 packages/desktop/apps/live-host/electron-builder.yml delete mode 100644 packages/desktop/apps/live-host/package.json delete mode 100644 packages/desktop/apps/live-host/resources/audio-input-worklet.js delete mode 100644 packages/desktop/apps/live-host/scripts/after-pack.cjs delete mode 100644 packages/desktop/apps/live-host/scripts/build.mjs delete mode 100644 packages/desktop/apps/live-host/scripts/create-release-manifest.mjs delete mode 100644 packages/desktop/apps/live-host/src/main/__tests__/appshot-architecture.test.ts delete mode 100644 packages/desktop/apps/live-host/src/main/__tests__/appshot-capture.test.ts delete mode 100644 packages/desktop/apps/live-host/src/main/__tests__/appshot-readiness.test.ts delete mode 100644 packages/desktop/apps/live-host/src/main/__tests__/audio-architecture.test.ts delete mode 100644 packages/desktop/apps/live-host/src/main/__tests__/audio-input-policy.test.ts delete mode 100644 packages/desktop/apps/live-host/src/main/__tests__/audio-lifecycle.test.ts delete mode 100644 packages/desktop/apps/live-host/src/main/__tests__/audio-output-queue.test.ts delete mode 100644 packages/desktop/apps/live-host/src/main/__tests__/daemon-connection.test.ts delete mode 100644 packages/desktop/apps/live-host/src/main/__tests__/discovery.test.ts delete mode 100644 packages/desktop/apps/live-host/src/main/__tests__/global-shortcut.test.ts delete mode 100644 packages/desktop/apps/live-host/src/main/__tests__/live-state-policy.test.ts delete mode 100644 packages/desktop/apps/live-host/src/main/__tests__/native-service-policy.test.ts delete mode 100644 packages/desktop/apps/live-host/src/main/__tests__/overlay-position.test.ts delete mode 100644 packages/desktop/apps/live-host/src/main/__tests__/overlay-recovery.test.ts delete mode 100644 packages/desktop/apps/live-host/src/main/__tests__/protocol.test.ts delete mode 100644 packages/desktop/apps/live-host/src/main/__tests__/reconnect-policy.test.ts delete mode 100644 packages/desktop/apps/live-host/src/main/__tests__/release-architecture.test.ts delete mode 100644 packages/desktop/apps/live-host/src/main/appshot-capture.ts delete mode 100644 packages/desktop/apps/live-host/src/main/appshot-readiness.ts delete mode 100644 packages/desktop/apps/live-host/src/main/daemon-connection.ts delete mode 100644 packages/desktop/apps/live-host/src/main/discovery.ts delete mode 100644 packages/desktop/apps/live-host/src/main/global-shortcut.ts delete mode 100644 packages/desktop/apps/live-host/src/main/index.ts delete mode 100644 packages/desktop/apps/live-host/src/main/live-state-policy.ts delete mode 100644 packages/desktop/apps/live-host/src/main/native-appshot.ts delete mode 100644 packages/desktop/apps/live-host/src/main/native-service-policy.ts delete mode 100644 packages/desktop/apps/live-host/src/main/overlay-position.ts delete mode 100644 packages/desktop/apps/live-host/src/main/overlay-recovery.ts delete mode 100644 packages/desktop/apps/live-host/src/main/reconnect-policy.ts delete mode 100644 packages/desktop/apps/live-host/src/native/appshot.mm delete mode 100644 packages/desktop/apps/live-host/src/preload/audio-engine.ts delete mode 100644 packages/desktop/apps/live-host/src/preload/audio-input-policy.ts delete mode 100644 packages/desktop/apps/live-host/src/preload/audio-lifecycle.ts delete mode 100644 packages/desktop/apps/live-host/src/preload/audio-output-queue.ts delete mode 100644 packages/desktop/apps/live-host/src/preload/index.ts delete mode 100644 packages/desktop/apps/live-host/src/renderer/index.html delete mode 100644 packages/desktop/apps/live-host/src/renderer/main.ts delete mode 100644 packages/desktop/apps/live-host/src/renderer/style.css delete mode 100644 packages/desktop/apps/live-host/src/shared/host-api.ts delete mode 100644 packages/desktop/apps/live-host/src/shared/protocol.ts delete mode 100644 packages/desktop/apps/live-host/tsconfig.json delete mode 100644 packages/desktop/apps/live-host/vite.config.ts delete mode 100644 packages/desktop/apps/viewer/index.html delete mode 100644 packages/desktop/apps/viewer/package.json delete mode 100644 packages/desktop/apps/viewer/public/_redirects delete mode 100644 packages/desktop/apps/viewer/src/App.tsx delete mode 100644 packages/desktop/apps/viewer/src/components/Header.tsx delete mode 100644 packages/desktop/apps/viewer/src/components/SessionUpload.tsx delete mode 100644 packages/desktop/apps/viewer/src/components/index.ts delete mode 100644 packages/desktop/apps/viewer/src/index.css delete mode 100644 packages/desktop/apps/viewer/src/main.tsx delete mode 100644 packages/desktop/apps/viewer/src/vite-env.d.ts delete mode 100644 packages/desktop/apps/viewer/tsconfig.json delete mode 100644 packages/desktop/apps/viewer/tsconfig.node.json delete mode 100644 packages/desktop/apps/viewer/vite.config.ts delete mode 100644 packages/desktop/apps/webui/package.json delete mode 100644 packages/desktop/apps/webui/src/App.tsx delete mode 100644 packages/desktop/apps/webui/src/adapter/web-api.ts delete mode 100644 packages/desktop/apps/webui/src/index.css delete mode 100644 packages/desktop/apps/webui/src/index.html delete mode 100644 packages/desktop/apps/webui/src/login.html delete mode 100644 packages/desktop/apps/webui/src/main.tsx delete mode 100644 packages/desktop/apps/webui/src/public/apple-touch-icon.png delete mode 100644 packages/desktop/apps/webui/src/public/favicon.ico delete mode 100644 packages/desktop/apps/webui/src/public/favicon.svg delete mode 100644 packages/desktop/apps/webui/src/public/icon-192.png delete mode 100644 packages/desktop/apps/webui/src/public/icon-512.png delete mode 100644 packages/desktop/apps/webui/src/public/manifest.json delete mode 100644 packages/desktop/apps/webui/src/responsive.ts delete mode 100644 packages/desktop/apps/webui/src/shims/electron-log.ts delete mode 100644 packages/desktop/apps/webui/src/shims/fs-promises.ts delete mode 100644 packages/desktop/apps/webui/src/shims/node-builtins.ts delete mode 100644 packages/desktop/apps/webui/src/shims/open.ts delete mode 100644 packages/desktop/apps/webui/src/shims/sentry-electron.ts delete mode 100644 packages/desktop/apps/webui/src/shims/ws.ts delete mode 100644 packages/desktop/apps/webui/tsconfig.json delete mode 100644 packages/desktop/apps/webui/vite.config.ts delete mode 100644 packages/desktop/bun.lock delete mode 100644 packages/desktop/docs/plans/2026-06-08-desktop-auto-update-design.md delete mode 100644 packages/desktop/package.json delete mode 100644 packages/desktop/packages/core/README.md delete mode 100644 packages/desktop/packages/core/package.json delete mode 100644 packages/desktop/packages/core/src/index.ts delete mode 100644 packages/desktop/packages/core/src/types/index.ts delete mode 100644 packages/desktop/packages/core/src/types/message-mapper.ts delete mode 100644 packages/desktop/packages/core/src/types/message.ts delete mode 100644 packages/desktop/packages/core/src/types/server.ts delete mode 100644 packages/desktop/packages/core/src/types/session.ts delete mode 100644 packages/desktop/packages/core/src/types/workspace.ts delete mode 100644 packages/desktop/packages/core/src/utils/__tests__/text-elements.test.ts delete mode 100644 packages/desktop/packages/core/src/utils/debug.ts delete mode 100644 packages/desktop/packages/core/src/utils/index.ts delete mode 100644 packages/desktop/packages/core/src/utils/paths.ts delete mode 100644 packages/desktop/packages/core/src/utils/text-elements.ts delete mode 100644 packages/desktop/packages/core/tsconfig.json delete mode 100644 packages/desktop/packages/messaging-gateway/package.json delete mode 100644 packages/desktop/packages/messaging-gateway/src/__tests__/binding-store.test.ts delete mode 100644 packages/desktop/packages/messaging-gateway/src/__tests__/commands.test.ts delete mode 100644 packages/desktop/packages/messaging-gateway/src/__tests__/pairing.test.ts delete mode 100644 packages/desktop/packages/messaging-gateway/src/__tests__/plan-tokens.test.ts delete mode 100644 packages/desktop/packages/messaging-gateway/src/__tests__/renderer-plan.test.ts delete mode 100644 packages/desktop/packages/messaging-gateway/src/__tests__/renderer.test.ts delete mode 100644 packages/desktop/packages/messaging-gateway/src/__tests__/router.test.ts delete mode 100644 packages/desktop/packages/messaging-gateway/src/adapters/telegram/dm-only.test.ts delete mode 100644 packages/desktop/packages/messaging-gateway/src/adapters/telegram/format.ts delete mode 100644 packages/desktop/packages/messaging-gateway/src/adapters/telegram/index.ts delete mode 100644 packages/desktop/packages/messaging-gateway/src/adapters/whatsapp/index.ts delete mode 100644 packages/desktop/packages/messaging-gateway/src/adapters/whatsapp/lifecycle.test.ts delete mode 100644 packages/desktop/packages/messaging-gateway/src/binding-store.test.ts delete mode 100644 packages/desktop/packages/messaging-gateway/src/binding-store.ts delete mode 100644 packages/desktop/packages/messaging-gateway/src/bootstrap.ts delete mode 100644 packages/desktop/packages/messaging-gateway/src/commands.ts delete mode 100644 packages/desktop/packages/messaging-gateway/src/config-store.ts delete mode 100644 packages/desktop/packages/messaging-gateway/src/event-fanout.ts delete mode 100644 packages/desktop/packages/messaging-gateway/src/gateway.ts delete mode 100644 packages/desktop/packages/messaging-gateway/src/index.ts delete mode 100644 packages/desktop/packages/messaging-gateway/src/pairing.test.ts delete mode 100644 packages/desktop/packages/messaging-gateway/src/pairing.ts delete mode 100644 packages/desktop/packages/messaging-gateway/src/plan-tokens.ts delete mode 100644 packages/desktop/packages/messaging-gateway/src/registry.ts delete mode 100644 packages/desktop/packages/messaging-gateway/src/renderer.ts delete mode 100644 packages/desktop/packages/messaging-gateway/src/router.ts delete mode 100644 packages/desktop/packages/messaging-gateway/src/types.ts delete mode 100644 packages/desktop/packages/messaging-gateway/tsconfig.json delete mode 100644 packages/desktop/packages/messaging-whatsapp-worker/package.json delete mode 100644 packages/desktop/packages/messaging-whatsapp-worker/src/__tests__/filter.test.ts delete mode 100644 packages/desktop/packages/messaging-whatsapp-worker/src/filter.ts delete mode 100644 packages/desktop/packages/messaging-whatsapp-worker/src/protocol.ts delete mode 100644 packages/desktop/packages/messaging-whatsapp-worker/src/worker.ts delete mode 100644 packages/desktop/packages/messaging-whatsapp-worker/tsconfig.json delete mode 100644 packages/desktop/packages/server-core/README.md delete mode 100644 packages/desktop/packages/server-core/package.json delete mode 100644 packages/desktop/packages/server-core/src/bootstrap/__tests__/server-port.test.ts delete mode 100644 packages/desktop/packages/server-core/src/bootstrap/__tests__/token-entropy.test.ts delete mode 100644 packages/desktop/packages/server-core/src/bootstrap/headless-start.ts delete mode 100644 packages/desktop/packages/server-core/src/bootstrap/index.ts delete mode 100644 packages/desktop/packages/server-core/src/domain/browser-tool-detection.ts delete mode 100644 packages/desktop/packages/server-core/src/domain/connection-setup-logic.ts delete mode 100644 packages/desktop/packages/server-core/src/domain/index.ts delete mode 100644 packages/desktop/packages/server-core/src/domain/init-gate.ts delete mode 100644 packages/desktop/packages/server-core/src/domain/session-branch-cleanup.ts delete mode 100644 packages/desktop/packages/server-core/src/domain/session-browser-release.ts delete mode 100644 packages/desktop/packages/server-core/src/domain/title-sanitizer.ts delete mode 100644 packages/desktop/packages/server-core/src/handlers/__tests__/validate-file-path.test.ts delete mode 100644 packages/desktop/packages/server-core/src/handlers/browser-pane-manager-interface.ts delete mode 100644 packages/desktop/packages/server-core/src/handlers/handler-deps.ts delete mode 100644 packages/desktop/packages/server-core/src/handlers/index.ts delete mode 100644 packages/desktop/packages/server-core/src/handlers/messaging-registry-interface.ts delete mode 100644 packages/desktop/packages/server-core/src/handlers/oauth-flow-store-interface.ts delete mode 100644 packages/desktop/packages/server-core/src/handlers/rpc/auth.ts delete mode 100644 packages/desktop/packages/server-core/src/handlers/rpc/automations.ts delete mode 100644 packages/desktop/packages/server-core/src/handlers/rpc/files.ts delete mode 100644 packages/desktop/packages/server-core/src/handlers/rpc/index.ts delete mode 100644 packages/desktop/packages/server-core/src/handlers/rpc/labels.ts delete mode 100644 packages/desktop/packages/server-core/src/handlers/rpc/llm-connections.ts delete mode 100644 packages/desktop/packages/server-core/src/handlers/rpc/messaging.ts delete mode 100644 packages/desktop/packages/server-core/src/handlers/rpc/oauth.ts delete mode 100644 packages/desktop/packages/server-core/src/handlers/rpc/onboarding.ts delete mode 100644 packages/desktop/packages/server-core/src/handlers/rpc/resources.ts delete mode 100644 packages/desktop/packages/server-core/src/handlers/rpc/server.ts delete mode 100644 packages/desktop/packages/server-core/src/handlers/rpc/sessions.get-for-workspace.test.ts delete mode 100644 packages/desktop/packages/server-core/src/handlers/rpc/sessions.ts delete mode 100644 packages/desktop/packages/server-core/src/handlers/rpc/settings.ts delete mode 100644 packages/desktop/packages/server-core/src/handlers/rpc/skills.marketplace.test.ts delete mode 100644 packages/desktop/packages/server-core/src/handlers/rpc/skills.ts delete mode 100644 packages/desktop/packages/server-core/src/handlers/rpc/sources.test.ts delete mode 100644 packages/desktop/packages/server-core/src/handlers/rpc/sources.ts delete mode 100644 packages/desktop/packages/server-core/src/handlers/rpc/statuses.ts delete mode 100644 packages/desktop/packages/server-core/src/handlers/rpc/system.git-branch.test.ts delete mode 100644 packages/desktop/packages/server-core/src/handlers/rpc/system.open-url.test.ts delete mode 100644 packages/desktop/packages/server-core/src/handlers/rpc/system.ts delete mode 100644 packages/desktop/packages/server-core/src/handlers/rpc/transfer.test.ts delete mode 100644 packages/desktop/packages/server-core/src/handlers/rpc/transfer.ts delete mode 100644 packages/desktop/packages/server-core/src/handlers/rpc/workspace.image-path.test.ts delete mode 100644 packages/desktop/packages/server-core/src/handlers/rpc/workspace.ts delete mode 100644 packages/desktop/packages/server-core/src/handlers/session-manager-interface.ts delete mode 100644 packages/desktop/packages/server-core/src/handlers/utils.ts delete mode 100644 packages/desktop/packages/server-core/src/handlers/window-manager-interface.ts delete mode 100644 packages/desktop/packages/server-core/src/index.ts delete mode 100644 packages/desktop/packages/server-core/src/model-fetchers/index.ts delete mode 100644 packages/desktop/packages/server-core/src/model-fetchers/qwen.ts delete mode 100644 packages/desktop/packages/server-core/src/model-fetchers/registry.ts delete mode 100644 packages/desktop/packages/server-core/src/model-fetchers/runtime.ts delete mode 100644 packages/desktop/packages/server-core/src/runtime/index.ts delete mode 100644 packages/desktop/packages/server-core/src/runtime/null-browser-pane-manager.ts delete mode 100644 packages/desktop/packages/server-core/src/runtime/platform-headless.ts delete mode 100644 packages/desktop/packages/server-core/src/runtime/platform.ts delete mode 100644 packages/desktop/packages/server-core/src/services/git-bash.ts delete mode 100644 packages/desktop/packages/server-core/src/services/image-utils.test.ts delete mode 100644 packages/desktop/packages/server-core/src/services/image-utils.ts delete mode 100644 packages/desktop/packages/server-core/src/services/index.ts delete mode 100644 packages/desktop/packages/server-core/src/services/privileged-execution-broker.ts delete mode 100644 packages/desktop/packages/server-core/src/services/search.ts delete mode 100644 packages/desktop/packages/server-core/src/services/vcredist.test.ts delete mode 100644 packages/desktop/packages/server-core/src/services/vcredist.ts delete mode 100644 packages/desktop/packages/server-core/src/sessions/SessionManager.test.ts delete mode 100644 packages/desktop/packages/server-core/src/sessions/SessionManager.ts delete mode 100644 packages/desktop/packages/server-core/src/sessions/create-managed-session.test.ts delete mode 100644 packages/desktop/packages/server-core/src/sessions/index.ts delete mode 100644 packages/desktop/packages/server-core/src/sessions/permission-mode-persistence.test.ts delete mode 100644 packages/desktop/packages/server-core/src/sessions/qwen-native-history.test.ts delete mode 100644 packages/desktop/packages/server-core/src/transport/__tests__/server-lifecycle.test.ts delete mode 100644 packages/desktop/packages/server-core/src/transport/capabilities.ts delete mode 100644 packages/desktop/packages/server-core/src/transport/client.ts delete mode 100644 packages/desktop/packages/server-core/src/transport/codec.ts delete mode 100644 packages/desktop/packages/server-core/src/transport/index.ts delete mode 100644 packages/desktop/packages/server-core/src/transport/push.ts delete mode 100644 packages/desktop/packages/server-core/src/transport/server.ts delete mode 100644 packages/desktop/packages/server-core/src/transport/types.ts delete mode 100644 packages/desktop/packages/server-core/src/utils/path-validation.test.ts delete mode 100644 packages/desktop/packages/server-core/src/utils/path-validation.ts delete mode 100644 packages/desktop/packages/server-core/src/voice/ansi.test.ts delete mode 100644 packages/desktop/packages/server-core/src/voice/ansi.ts delete mode 100644 packages/desktop/packages/server-core/src/voice/index.ts delete mode 100644 packages/desktop/packages/server-core/src/voice/net-guard.ts delete mode 100644 packages/desktop/packages/server-core/src/voice/qwen-asr-realtime-session.test.ts delete mode 100644 packages/desktop/packages/server-core/src/voice/qwen-asr-realtime-session.ts delete mode 100644 packages/desktop/packages/server-core/src/voice/resolve-voice-config.test.ts delete mode 100644 packages/desktop/packages/server-core/src/voice/resolve-voice-config.ts delete mode 100644 packages/desktop/packages/server-core/src/voice/transcribe.test.ts delete mode 100644 packages/desktop/packages/server-core/src/voice/transcribe.ts delete mode 100644 packages/desktop/packages/server-core/src/voice/voice-model.ts delete mode 100644 packages/desktop/packages/server-core/src/voice/voice-server.test.ts delete mode 100644 packages/desktop/packages/server-core/src/voice/voice-server.ts delete mode 100644 packages/desktop/packages/server-core/src/voice/voice-stream-retry.test.ts delete mode 100644 packages/desktop/packages/server-core/src/voice/voice-stream-retry.ts delete mode 100644 packages/desktop/packages/server-core/src/voice/voice-stream-session.test.ts delete mode 100644 packages/desktop/packages/server-core/src/voice/voice-stream-session.ts delete mode 100644 packages/desktop/packages/server-core/src/voice/voice-ws-handler.isolated.ts delete mode 100644 packages/desktop/packages/server-core/src/voice/voice-ws-handler.ts delete mode 100644 packages/desktop/packages/server-core/src/voice/voice.test.ts delete mode 100644 packages/desktop/packages/server-core/src/voice/wav.ts delete mode 100644 packages/desktop/packages/server-core/src/webui/__tests__/http-server.test.ts delete mode 100644 packages/desktop/packages/server-core/src/webui/__tests__/oauth-callback.test.ts delete mode 100644 packages/desktop/packages/server-core/src/webui/auth.ts delete mode 100644 packages/desktop/packages/server-core/src/webui/http-server.ts delete mode 100644 packages/desktop/packages/server-core/src/webui/index.ts delete mode 100644 packages/desktop/packages/server-core/src/webui/node-adapter.ts delete mode 100644 packages/desktop/packages/server-core/tsconfig.json delete mode 100644 packages/desktop/packages/server/package.json delete mode 100644 packages/desktop/packages/server/src/__tests__/smoke.test.ts delete mode 100755 packages/desktop/packages/server/src/index.ts delete mode 100644 packages/desktop/packages/server/tsconfig.json delete mode 100644 packages/desktop/packages/session-mcp-server/package.json delete mode 100644 packages/desktop/packages/session-mcp-server/src/credential-cache.test.ts delete mode 100644 packages/desktop/packages/session-mcp-server/src/credential-cache.ts delete mode 100644 packages/desktop/packages/session-mcp-server/src/index.ts delete mode 100644 packages/desktop/packages/session-mcp-server/tsconfig.json delete mode 100644 packages/desktop/packages/session-tools-core/package.json delete mode 100644 packages/desktop/packages/session-tools-core/src/context.ts delete mode 100644 packages/desktop/packages/session-tools-core/src/handlers/config-validate.test.ts delete mode 100644 packages/desktop/packages/session-tools-core/src/handlers/config-validate.ts delete mode 100644 packages/desktop/packages/session-tools-core/src/handlers/credential-prompt.ts delete mode 100644 packages/desktop/packages/session-tools-core/src/handlers/get-session-info.ts delete mode 100644 packages/desktop/packages/session-tools-core/src/handlers/index.ts delete mode 100644 packages/desktop/packages/session-tools-core/src/handlers/list-sessions.ts delete mode 100644 packages/desktop/packages/session-tools-core/src/handlers/mermaid-validate.ts delete mode 100644 packages/desktop/packages/session-tools-core/src/handlers/messaging.ts delete mode 100644 packages/desktop/packages/session-tools-core/src/handlers/render-template.ts delete mode 100644 packages/desktop/packages/session-tools-core/src/handlers/script-sandbox.test.ts delete mode 100644 packages/desktop/packages/session-tools-core/src/handlers/script-sandbox.ts delete mode 100644 packages/desktop/packages/session-tools-core/src/handlers/send-agent-message.ts delete mode 100644 packages/desktop/packages/session-tools-core/src/handlers/send-developer-feedback.ts delete mode 100644 packages/desktop/packages/session-tools-core/src/handlers/set-session-labels.ts delete mode 100644 packages/desktop/packages/session-tools-core/src/handlers/set-session-status.ts delete mode 100644 packages/desktop/packages/session-tools-core/src/handlers/skill-validate.ts delete mode 100644 packages/desktop/packages/session-tools-core/src/handlers/source-oauth.ts delete mode 100644 packages/desktop/packages/session-tools-core/src/handlers/source-test.test.ts delete mode 100644 packages/desktop/packages/session-tools-core/src/handlers/source-test.ts delete mode 100644 packages/desktop/packages/session-tools-core/src/handlers/submit-plan.test.ts delete mode 100644 packages/desktop/packages/session-tools-core/src/handlers/submit-plan.ts delete mode 100644 packages/desktop/packages/session-tools-core/src/handlers/transform-data.test.ts delete mode 100644 packages/desktop/packages/session-tools-core/src/handlers/transform-data.ts delete mode 100644 packages/desktop/packages/session-tools-core/src/handlers/update-preferences.ts delete mode 100644 packages/desktop/packages/session-tools-core/src/index.ts delete mode 100644 packages/desktop/packages/session-tools-core/src/response.ts delete mode 100644 packages/desktop/packages/session-tools-core/src/runtime/filesystem-isolation.test.ts delete mode 100644 packages/desktop/packages/session-tools-core/src/runtime/filesystem-isolation.ts delete mode 100644 packages/desktop/packages/session-tools-core/src/runtime/network-isolation.ts delete mode 100644 packages/desktop/packages/session-tools-core/src/runtime/path-security.test.ts delete mode 100644 packages/desktop/packages/session-tools-core/src/runtime/path-security.ts delete mode 100644 packages/desktop/packages/session-tools-core/src/runtime/resolve-script-runtime.test.ts delete mode 100644 packages/desktop/packages/session-tools-core/src/runtime/resolve-script-runtime.ts delete mode 100644 packages/desktop/packages/session-tools-core/src/runtime/sandbox-env.test.ts delete mode 100644 packages/desktop/packages/session-tools-core/src/runtime/sandbox-env.ts delete mode 100644 packages/desktop/packages/session-tools-core/src/source-helpers.test.ts delete mode 100644 packages/desktop/packages/session-tools-core/src/source-helpers.ts delete mode 100644 packages/desktop/packages/session-tools-core/src/templates/loader.ts delete mode 100644 packages/desktop/packages/session-tools-core/src/templates/mustache.ts delete mode 100644 packages/desktop/packages/session-tools-core/src/tool-defs-filtering.test.ts delete mode 100644 packages/desktop/packages/session-tools-core/src/tool-defs.ts delete mode 100644 packages/desktop/packages/session-tools-core/src/types.ts delete mode 100644 packages/desktop/packages/session-tools-core/src/validation.ts delete mode 100644 packages/desktop/packages/session-tools-core/tsconfig.json delete mode 100644 packages/desktop/packages/shared/eslint-rules/no-direct-open-import.cjs delete mode 100644 packages/desktop/packages/shared/eslint-rules/no-inline-source-auth-check.cjs delete mode 100644 packages/desktop/packages/shared/eslint.config.mjs delete mode 100644 packages/desktop/packages/shared/package.json delete mode 100644 packages/desktop/packages/shared/src/__tests__/feature-flags.test.ts delete mode 100644 packages/desktop/packages/shared/src/__tests__/interceptor-common.test.ts delete mode 100644 packages/desktop/packages/shared/src/__tests__/interceptor-packaging-contract.test.ts delete mode 100644 packages/desktop/packages/shared/src/__tests__/interceptor-request-utils.test.ts delete mode 100644 packages/desktop/packages/shared/src/agent/__tests__/__tmp_build_call_llm__/empty.ts delete mode 100644 packages/desktop/packages/shared/src/agent/__tests__/__tmp_build_call_llm__/image.png delete mode 100644 packages/desktop/packages/shared/src/agent/__tests__/__tmp_build_call_llm__/test.ts delete mode 100644 packages/desktop/packages/shared/src/agent/__tests__/base-agent.test.ts delete mode 100644 packages/desktop/packages/shared/src/agent/__tests__/base-event-adapter.test.ts delete mode 100644 packages/desktop/packages/shared/src/agent/__tests__/browser-tool-names.test.ts delete mode 100644 packages/desktop/packages/shared/src/agent/__tests__/browser-tools-permissions.test.ts delete mode 100644 packages/desktop/packages/shared/src/agent/__tests__/browser-tools.test.ts delete mode 100644 packages/desktop/packages/shared/src/agent/__tests__/build-call-llm-request.test.ts delete mode 100644 packages/desktop/packages/shared/src/agent/__tests__/call-llm-permissions.test.ts delete mode 100644 packages/desktop/packages/shared/src/agent/__tests__/conversation-summary.test.ts delete mode 100644 packages/desktop/packages/shared/src/agent/__tests__/credential-prompt-detection.test.ts delete mode 100644 packages/desktop/packages/shared/src/agent/__tests__/errors.test.ts delete mode 100644 packages/desktop/packages/shared/src/agent/__tests__/event-queue.test.ts delete mode 100644 packages/desktop/packages/shared/src/agent/__tests__/mode-manager-bash-hints.test.ts delete mode 100644 packages/desktop/packages/shared/src/agent/__tests__/mode-manager-path-boundary.test.ts delete mode 100644 packages/desktop/packages/shared/src/agent/__tests__/mode-session-state.test.ts delete mode 100644 packages/desktop/packages/shared/src/agent/__tests__/pending-permissions-lifecycle.test.ts delete mode 100644 packages/desktop/packages/shared/src/agent/__tests__/permissions-config-craft-cli-flag.test.ts delete mode 100644 packages/desktop/packages/shared/src/agent/__tests__/permissions-config-migration.test.ts delete mode 100644 packages/desktop/packages/shared/src/agent/__tests__/permissions-config-source-slug.test.ts delete mode 100644 packages/desktop/packages/shared/src/agent/__tests__/qwen-agent-models.test.ts delete mode 100644 packages/desktop/packages/shared/src/agent/__tests__/qwen-agent-slash-history.test.ts delete mode 100644 packages/desktop/packages/shared/src/agent/__tests__/qwen-agent-tool-updates.test.ts delete mode 100644 packages/desktop/packages/shared/src/agent/__tests__/send-developer-feedback-permissions.test.ts delete mode 100644 packages/desktop/packages/shared/src/agent/__tests__/session-scoped-tools-merge.test.ts delete mode 100644 packages/desktop/packages/shared/src/agent/__tests__/session-scoped-tools-path-boundary.test.ts delete mode 100644 packages/desktop/packages/shared/src/agent/__tests__/session-self-management-bindings.test.ts delete mode 100644 packages/desktop/packages/shared/src/agent/__tests__/session-tool-safe-mode-permissions.test.ts delete mode 100644 packages/desktop/packages/shared/src/agent/__tests__/source-state.test.ts delete mode 100644 packages/desktop/packages/shared/src/agent/__tests__/spawn-session-thinking-level.test.ts delete mode 100644 packages/desktop/packages/shared/src/agent/__tests__/spawn-session-tilde-expansion.test.ts delete mode 100644 packages/desktop/packages/shared/src/agent/__tests__/test-utils.ts delete mode 100644 packages/desktop/packages/shared/src/agent/__tests__/tool-matching-sdk-fixtures.test.ts delete mode 100644 packages/desktop/packages/shared/src/agent/__tests__/tool-matching.test.ts delete mode 100644 packages/desktop/packages/shared/src/agent/__tests__/with-timeout.test.ts delete mode 100644 packages/desktop/packages/shared/src/agent/backend/__tests__/factory-model.test.ts delete mode 100644 packages/desktop/packages/shared/src/agent/backend/__tests__/read-patterns.test.ts delete mode 100644 packages/desktop/packages/shared/src/agent/backend/base-event-adapter.ts delete mode 100644 packages/desktop/packages/shared/src/agent/backend/event-queue.ts delete mode 100644 packages/desktop/packages/shared/src/agent/backend/factory.ts delete mode 100644 packages/desktop/packages/shared/src/agent/backend/index.ts delete mode 100644 packages/desktop/packages/shared/src/agent/backend/internal/__tests__/electron-run-as-node.test.ts delete mode 100644 packages/desktop/packages/shared/src/agent/backend/internal/__tests__/runtime-resolver.test.ts delete mode 100644 packages/desktop/packages/shared/src/agent/backend/internal/driver-types.ts delete mode 100644 packages/desktop/packages/shared/src/agent/backend/internal/drivers/qwen.ts delete mode 100644 packages/desktop/packages/shared/src/agent/backend/internal/electron-run-as-node.ts delete mode 100644 packages/desktop/packages/shared/src/agent/backend/internal/runtime-resolver.ts delete mode 100644 packages/desktop/packages/shared/src/agent/backend/read-patterns.ts delete mode 100644 packages/desktop/packages/shared/src/agent/backend/types.ts delete mode 100644 packages/desktop/packages/shared/src/agent/base-agent.ts delete mode 100644 packages/desktop/packages/shared/src/agent/bash-parser.d.ts delete mode 100644 packages/desktop/packages/shared/src/agent/bash-validator.ts delete mode 100644 packages/desktop/packages/shared/src/agent/browser-tool-names.ts delete mode 100644 packages/desktop/packages/shared/src/agent/browser-tool-runtime.ts delete mode 100644 packages/desktop/packages/shared/src/agent/browser-tools.ts delete mode 100644 packages/desktop/packages/shared/src/agent/conversation-summary.ts delete mode 100644 packages/desktop/packages/shared/src/agent/core/__tests__/permission-manager.test.ts delete mode 100644 packages/desktop/packages/shared/src/agent/core/__tests__/pre-tool-use-checks.isolated.ts delete mode 100644 packages/desktop/packages/shared/src/agent/core/__tests__/prerequisite-manager.isolated.ts delete mode 100644 packages/desktop/packages/shared/src/agent/core/__tests__/session-lifecycle.test.ts delete mode 100644 packages/desktop/packages/shared/src/agent/core/__tests__/source-manager.test.ts delete mode 100644 packages/desktop/packages/shared/src/agent/core/__tests__/usage-tracker.test.ts delete mode 100644 packages/desktop/packages/shared/src/agent/core/config-validator.ts delete mode 100644 packages/desktop/packages/shared/src/agent/core/config-watcher-manager.ts delete mode 100644 packages/desktop/packages/shared/src/agent/core/index.ts delete mode 100644 packages/desktop/packages/shared/src/agent/core/path-processor.ts delete mode 100644 packages/desktop/packages/shared/src/agent/core/permission-manager.ts delete mode 100644 packages/desktop/packages/shared/src/agent/core/pre-tool-use.ts delete mode 100644 packages/desktop/packages/shared/src/agent/core/prerequisite-manager.ts delete mode 100644 packages/desktop/packages/shared/src/agent/core/prompt-builder.ts delete mode 100644 packages/desktop/packages/shared/src/agent/core/session-lifecycle.ts delete mode 100644 packages/desktop/packages/shared/src/agent/core/source-manager.ts delete mode 100644 packages/desktop/packages/shared/src/agent/core/types.ts delete mode 100644 packages/desktop/packages/shared/src/agent/core/usage-tracker.ts delete mode 100644 packages/desktop/packages/shared/src/agent/diagnostics.ts delete mode 100644 packages/desktop/packages/shared/src/agent/errors.ts delete mode 100644 packages/desktop/packages/shared/src/agent/index.ts delete mode 100644 packages/desktop/packages/shared/src/agent/llm-tool.ts delete mode 100644 packages/desktop/packages/shared/src/agent/mode-manager.ts delete mode 100644 packages/desktop/packages/shared/src/agent/mode-types.ts delete mode 100644 packages/desktop/packages/shared/src/agent/permissions-config.ts delete mode 100644 packages/desktop/packages/shared/src/agent/plan-types.ts delete mode 100644 packages/desktop/packages/shared/src/agent/powershell-parser.ps1 delete mode 100644 packages/desktop/packages/shared/src/agent/powershell-validator.ts delete mode 100644 packages/desktop/packages/shared/src/agent/qwen-agent.ts delete mode 100644 packages/desktop/packages/shared/src/agent/session-scoped-tool-callback-registry.ts delete mode 100644 packages/desktop/packages/shared/src/agent/session-scoped-tools.ts delete mode 100644 packages/desktop/packages/shared/src/agent/session-self-management-bindings.ts delete mode 100644 packages/desktop/packages/shared/src/agent/session-tool-context.ts delete mode 100644 packages/desktop/packages/shared/src/agent/spawn-session-tool.ts delete mode 100644 packages/desktop/packages/shared/src/agent/thinking-levels.ts delete mode 100644 packages/desktop/packages/shared/src/agent/tool-matching.ts delete mode 100644 packages/desktop/packages/shared/src/auth/__tests__/exports-and-session-context.test.ts delete mode 100644 packages/desktop/packages/shared/src/auth/__tests__/generic-oauth.test.ts delete mode 100644 packages/desktop/packages/shared/src/auth/__tests__/oauth-callback-url.test.ts delete mode 100644 packages/desktop/packages/shared/src/auth/__tests__/oauth-relay.test.ts delete mode 100644 packages/desktop/packages/shared/src/auth/__tests__/oauth.e2e.test.ts delete mode 100644 packages/desktop/packages/shared/src/auth/__tests__/oauth.test.ts delete mode 100644 packages/desktop/packages/shared/src/auth/__tests__/types.test.ts delete mode 100644 packages/desktop/packages/shared/src/auth/callback-page.ts delete mode 100644 packages/desktop/packages/shared/src/auth/callback-server.ts delete mode 100644 packages/desktop/packages/shared/src/auth/generic-oauth.ts delete mode 100644 packages/desktop/packages/shared/src/auth/google-oauth.ts delete mode 100644 packages/desktop/packages/shared/src/auth/index.ts delete mode 100644 packages/desktop/packages/shared/src/auth/microsoft-oauth.ts delete mode 100644 packages/desktop/packages/shared/src/auth/oauth-flow-store.ts delete mode 100644 packages/desktop/packages/shared/src/auth/oauth-flow-types.ts delete mode 100644 packages/desktop/packages/shared/src/auth/oauth-relay.ts delete mode 100644 packages/desktop/packages/shared/src/auth/oauth.ts delete mode 100644 packages/desktop/packages/shared/src/auth/pkce.ts delete mode 100644 packages/desktop/packages/shared/src/auth/slack-oauth.ts delete mode 100644 packages/desktop/packages/shared/src/auth/state.ts delete mode 100644 packages/desktop/packages/shared/src/auth/types.ts delete mode 100644 packages/desktop/packages/shared/src/automations/automation-system.test.ts delete mode 100644 packages/desktop/packages/shared/src/automations/automation-system.ts delete mode 100644 packages/desktop/packages/shared/src/automations/conditions-constants.ts delete mode 100644 packages/desktop/packages/shared/src/automations/conditions.test.ts delete mode 100644 packages/desktop/packages/shared/src/automations/conditions.ts delete mode 100644 packages/desktop/packages/shared/src/automations/constants.ts delete mode 100644 packages/desktop/packages/shared/src/automations/cron-matcher.test.ts delete mode 100644 packages/desktop/packages/shared/src/automations/cron-matcher.ts delete mode 100644 packages/desktop/packages/shared/src/automations/event-bus.test.ts delete mode 100644 packages/desktop/packages/shared/src/automations/event-bus.ts delete mode 100644 packages/desktop/packages/shared/src/automations/event-logger.ts delete mode 100644 packages/desktop/packages/shared/src/automations/handlers/event-log-handler.test.ts delete mode 100644 packages/desktop/packages/shared/src/automations/handlers/event-log-handler.ts delete mode 100644 packages/desktop/packages/shared/src/automations/handlers/index.ts delete mode 100644 packages/desktop/packages/shared/src/automations/handlers/prompt-handler.test.ts delete mode 100644 packages/desktop/packages/shared/src/automations/handlers/prompt-handler.ts delete mode 100644 packages/desktop/packages/shared/src/automations/handlers/types.ts delete mode 100644 packages/desktop/packages/shared/src/automations/handlers/webhook-handler.ts delete mode 100644 packages/desktop/packages/shared/src/automations/history-store.test.ts delete mode 100644 packages/desktop/packages/shared/src/automations/history-store.ts delete mode 100644 packages/desktop/packages/shared/src/automations/index.ts delete mode 100644 packages/desktop/packages/shared/src/automations/name-utils.test.ts delete mode 100644 packages/desktop/packages/shared/src/automations/name-utils.ts delete mode 100644 packages/desktop/packages/shared/src/automations/resolve-config-path.test.ts delete mode 100644 packages/desktop/packages/shared/src/automations/resolve-config-path.ts delete mode 100644 packages/desktop/packages/shared/src/automations/retry-scheduler.ts delete mode 100644 packages/desktop/packages/shared/src/automations/schemas.ts delete mode 100644 packages/desktop/packages/shared/src/automations/sdk-bridge.test.ts delete mode 100644 packages/desktop/packages/shared/src/automations/sdk-bridge.ts delete mode 100644 packages/desktop/packages/shared/src/automations/security.test.ts delete mode 100644 packages/desktop/packages/shared/src/automations/security.ts delete mode 100644 packages/desktop/packages/shared/src/automations/types.ts delete mode 100644 packages/desktop/packages/shared/src/automations/utils.ts delete mode 100644 packages/desktop/packages/shared/src/automations/validation.test.ts delete mode 100644 packages/desktop/packages/shared/src/automations/validation.ts delete mode 100644 packages/desktop/packages/shared/src/automations/webhook-utils.test.ts delete mode 100644 packages/desktop/packages/shared/src/automations/webhook-utils.ts delete mode 100644 packages/desktop/packages/shared/src/branding.ts delete mode 100644 packages/desktop/packages/shared/src/colors/defaults.ts delete mode 100644 packages/desktop/packages/shared/src/colors/index.ts delete mode 100644 packages/desktop/packages/shared/src/colors/migrate.ts delete mode 100644 packages/desktop/packages/shared/src/colors/resolve.ts delete mode 100644 packages/desktop/packages/shared/src/colors/types.ts delete mode 100644 packages/desktop/packages/shared/src/colors/validate.ts delete mode 100644 packages/desktop/packages/shared/src/config/__tests__/default-conversation-workspace.test.ts delete mode 100644 packages/desktop/packages/shared/src/config/__tests__/default-thinking-level.test.ts delete mode 100644 packages/desktop/packages/shared/src/config/__tests__/qwen-settings.test.ts delete mode 100644 packages/desktop/packages/shared/src/config/__tests__/session-drafts.test.ts delete mode 100644 packages/desktop/packages/shared/src/config/__tests__/source-slug-validation.test.ts delete mode 100644 packages/desktop/packages/shared/src/config/__tests__/voice-settings.test.ts delete mode 100644 packages/desktop/packages/shared/src/config/__tests__/watcher-duplicate-guard.test.ts delete mode 100644 packages/desktop/packages/shared/src/config/__tests__/workspace-icon-url.test.ts delete mode 100644 packages/desktop/packages/shared/src/config/__tests__/workspace-project-root-migration.test.ts delete mode 100644 packages/desktop/packages/shared/src/config/__tests__/workspace-reorder.test.ts delete mode 100644 packages/desktop/packages/shared/src/config/cli-domains.ts delete mode 100644 packages/desktop/packages/shared/src/config/config-defaults-schema.ts delete mode 100644 packages/desktop/packages/shared/src/config/index.ts delete mode 100644 packages/desktop/packages/shared/src/config/llm-connections.ts delete mode 100644 packages/desktop/packages/shared/src/config/model-fetcher.ts delete mode 100644 packages/desktop/packages/shared/src/config/models.ts delete mode 100644 packages/desktop/packages/shared/src/config/paths.ts delete mode 100644 packages/desktop/packages/shared/src/config/pets.ts delete mode 100644 packages/desktop/packages/shared/src/config/preferences.ts delete mode 100644 packages/desktop/packages/shared/src/config/provider-metadata.ts delete mode 100644 packages/desktop/packages/shared/src/config/proxy-env.ts delete mode 100644 packages/desktop/packages/shared/src/config/qwen-settings.ts delete mode 100644 packages/desktop/packages/shared/src/config/server-config.ts delete mode 100644 packages/desktop/packages/shared/src/config/storage.ts delete mode 100644 packages/desktop/packages/shared/src/config/sync-craft-agent-bash-patterns.ts delete mode 100644 packages/desktop/packages/shared/src/config/theme.ts delete mode 100644 packages/desktop/packages/shared/src/config/types.ts delete mode 100644 packages/desktop/packages/shared/src/config/validators.ts delete mode 100644 packages/desktop/packages/shared/src/config/watcher.ts delete mode 100644 packages/desktop/packages/shared/src/credentials/backends/env.ts delete mode 100644 packages/desktop/packages/shared/src/credentials/backends/secure-storage.ts delete mode 100644 packages/desktop/packages/shared/src/credentials/backends/types.ts delete mode 100644 packages/desktop/packages/shared/src/credentials/index.ts delete mode 100644 packages/desktop/packages/shared/src/credentials/manager.ts delete mode 100644 packages/desktop/packages/shared/src/credentials/types.ts delete mode 100644 packages/desktop/packages/shared/src/docs/doc-links.ts delete mode 100644 packages/desktop/packages/shared/src/docs/index.ts delete mode 100644 packages/desktop/packages/shared/src/docs/source-guides.ts delete mode 100644 packages/desktop/packages/shared/src/feature-flags.ts delete mode 100644 packages/desktop/packages/shared/src/i18n/__tests__/locale-parity.test.ts delete mode 100644 packages/desktop/packages/shared/src/i18n/__tests__/locale-registry.test.ts delete mode 100644 packages/desktop/packages/shared/src/i18n/date-locale.ts delete mode 100644 packages/desktop/packages/shared/src/i18n/index.ts delete mode 100644 packages/desktop/packages/shared/src/i18n/languages.ts delete mode 100644 packages/desktop/packages/shared/src/i18n/locales/de.json delete mode 100644 packages/desktop/packages/shared/src/i18n/locales/en.json delete mode 100644 packages/desktop/packages/shared/src/i18n/locales/es.json delete mode 100644 packages/desktop/packages/shared/src/i18n/locales/hu.json delete mode 100644 packages/desktop/packages/shared/src/i18n/locales/ja.json delete mode 100644 packages/desktop/packages/shared/src/i18n/locales/pl.json delete mode 100644 packages/desktop/packages/shared/src/i18n/locales/zh-Hans.json delete mode 100644 packages/desktop/packages/shared/src/i18n/registry.ts delete mode 100644 packages/desktop/packages/shared/src/i18n/setupI18n.ts delete mode 100644 packages/desktop/packages/shared/src/icons/index.ts delete mode 100644 packages/desktop/packages/shared/src/icons/types.ts delete mode 100644 packages/desktop/packages/shared/src/index.ts delete mode 100644 packages/desktop/packages/shared/src/interceptor-common.ts delete mode 100644 packages/desktop/packages/shared/src/interceptor-request-utils.ts delete mode 100644 packages/desktop/packages/shared/src/labels/__tests__/crud.test.ts delete mode 100644 packages/desktop/packages/shared/src/labels/__tests__/resolve.test.ts delete mode 100644 packages/desktop/packages/shared/src/labels/__tests__/tree.test.ts delete mode 100644 packages/desktop/packages/shared/src/labels/auto/evaluator.ts delete mode 100644 packages/desktop/packages/shared/src/labels/auto/index.ts delete mode 100644 packages/desktop/packages/shared/src/labels/auto/normalize.ts delete mode 100644 packages/desktop/packages/shared/src/labels/auto/types.ts delete mode 100644 packages/desktop/packages/shared/src/labels/auto/validation.ts delete mode 100644 packages/desktop/packages/shared/src/labels/crud.ts delete mode 100644 packages/desktop/packages/shared/src/labels/index.ts delete mode 100644 packages/desktop/packages/shared/src/labels/resolve.ts delete mode 100644 packages/desktop/packages/shared/src/labels/storage.ts delete mode 100644 packages/desktop/packages/shared/src/labels/tree.ts delete mode 100644 packages/desktop/packages/shared/src/labels/types.ts delete mode 100644 packages/desktop/packages/shared/src/labels/validation.ts delete mode 100644 packages/desktop/packages/shared/src/labels/values.ts delete mode 100644 packages/desktop/packages/shared/src/mcp/api-source-pool-client.ts delete mode 100644 packages/desktop/packages/shared/src/mcp/client.ts delete mode 100644 packages/desktop/packages/shared/src/mcp/index.ts delete mode 100644 packages/desktop/packages/shared/src/mcp/local-tools.ts delete mode 100644 packages/desktop/packages/shared/src/mcp/mcp-pool.ts delete mode 100644 packages/desktop/packages/shared/src/mcp/pool-server.ts delete mode 100644 packages/desktop/packages/shared/src/mcp/validation.ts delete mode 100644 packages/desktop/packages/shared/src/mentions/__tests__/resolve-file-mentions.test.ts delete mode 100644 packages/desktop/packages/shared/src/mentions/__tests__/resolve-skill-source-mentions.test.ts delete mode 100644 packages/desktop/packages/shared/src/mentions/index.ts delete mode 100644 packages/desktop/packages/shared/src/prompts/__tests__/system.test.ts delete mode 100644 packages/desktop/packages/shared/src/prompts/index.ts delete mode 100644 packages/desktop/packages/shared/src/prompts/print-system-prompt.ts delete mode 100644 packages/desktop/packages/shared/src/prompts/system.ts delete mode 100644 packages/desktop/packages/shared/src/protocol/__tests__/routing.test.ts delete mode 100644 packages/desktop/packages/shared/src/protocol/channels.ts delete mode 100644 packages/desktop/packages/shared/src/protocol/dto.ts delete mode 100644 packages/desktop/packages/shared/src/protocol/events.ts delete mode 100644 packages/desktop/packages/shared/src/protocol/index.ts delete mode 100644 packages/desktop/packages/shared/src/protocol/routing.ts delete mode 100644 packages/desktop/packages/shared/src/protocol/types.ts delete mode 100644 packages/desktop/packages/shared/src/release-notes/index.ts delete mode 100644 packages/desktop/packages/shared/src/resources/__tests__/resource-bundle.test.ts delete mode 100644 packages/desktop/packages/shared/src/resources/index.ts delete mode 100644 packages/desktop/packages/shared/src/resources/resource-bundle.ts delete mode 100644 packages/desktop/packages/shared/src/resources/types.ts delete mode 100644 packages/desktop/packages/shared/src/scheduler/index.ts delete mode 100644 packages/desktop/packages/shared/src/scheduler/scheduler-service.ts delete mode 100644 packages/desktop/packages/shared/src/search/fuzzy.ts delete mode 100644 packages/desktop/packages/shared/src/search/index.ts delete mode 100644 packages/desktop/packages/shared/src/sessions/__tests__/bundle.test.ts delete mode 100644 packages/desktop/packages/shared/src/sessions/__tests__/jsonl-permission-mode-normalization.test.ts delete mode 100644 packages/desktop/packages/shared/src/sessions/__tests__/model-persistence.test.ts delete mode 100644 packages/desktop/packages/shared/src/sessions/__tests__/pending-plan-execution.test.ts delete mode 100644 packages/desktop/packages/shared/src/sessions/__tests__/persistence-queue.test.ts delete mode 100644 packages/desktop/packages/shared/src/sessions/__tests__/previous-permission-mode.test.ts delete mode 100644 packages/desktop/packages/shared/src/sessions/__tests__/slug-generator.test.ts delete mode 100644 packages/desktop/packages/shared/src/sessions/bundle.ts delete mode 100644 packages/desktop/packages/shared/src/sessions/index.ts delete mode 100644 packages/desktop/packages/shared/src/sessions/jsonl.ts delete mode 100644 packages/desktop/packages/shared/src/sessions/persistence-queue.ts delete mode 100644 packages/desktop/packages/shared/src/sessions/slug-generator.ts delete mode 100644 packages/desktop/packages/shared/src/sessions/storage.ts delete mode 100644 packages/desktop/packages/shared/src/sessions/types.ts delete mode 100644 packages/desktop/packages/shared/src/sessions/utils.ts delete mode 100644 packages/desktop/packages/shared/src/sessions/validation.ts delete mode 100644 packages/desktop/packages/shared/src/sessions/word-lists.ts delete mode 100644 packages/desktop/packages/shared/src/skills/__tests__/marketplace.test.ts delete mode 100644 packages/desktop/packages/shared/src/skills/__tests__/storage.test.ts delete mode 100644 packages/desktop/packages/shared/src/skills/index.ts delete mode 100644 packages/desktop/packages/shared/src/skills/marketplace.ts delete mode 100644 packages/desktop/packages/shared/src/skills/storage.ts delete mode 100644 packages/desktop/packages/shared/src/skills/types.ts delete mode 100644 packages/desktop/packages/shared/src/sources/__tests__/authScheme-empty.test.ts delete mode 100644 packages/desktop/packages/shared/src/sources/__tests__/basic-auth.test.ts delete mode 100644 packages/desktop/packages/shared/src/sources/__tests__/credential-manager-expiry.test.ts delete mode 100644 packages/desktop/packages/shared/src/sources/__tests__/credential-manager-integration.test.ts delete mode 100644 packages/desktop/packages/shared/src/sources/__tests__/credential-manager-renew.test.ts delete mode 100644 packages/desktop/packages/shared/src/sources/__tests__/multi-header-auth.test.ts delete mode 100644 packages/desktop/packages/shared/src/sources/__tests__/multi-header-e2e.test.ts delete mode 100644 packages/desktop/packages/shared/src/sources/__tests__/oauth-relay.test.ts delete mode 100644 packages/desktop/packages/shared/src/sources/__tests__/server-builder-authScheme.test.ts delete mode 100644 packages/desktop/packages/shared/src/sources/__tests__/server-builder-flow.test.ts delete mode 100644 packages/desktop/packages/shared/src/sources/__tests__/server-builder-url.test.ts delete mode 100644 packages/desktop/packages/shared/src/sources/__tests__/source-config-validation.test.ts delete mode 100644 packages/desktop/packages/shared/src/sources/__tests__/storage-source-slug.test.ts delete mode 100644 packages/desktop/packages/shared/src/sources/__tests__/token-refresh-manager.test.ts delete mode 100644 packages/desktop/packages/shared/src/sources/api-tools.ts delete mode 100644 packages/desktop/packages/shared/src/sources/builtin-sources.ts delete mode 100644 packages/desktop/packages/shared/src/sources/credential-manager.ts delete mode 100644 packages/desktop/packages/shared/src/sources/index.ts delete mode 100644 packages/desktop/packages/shared/src/sources/server-builder.ts delete mode 100644 packages/desktop/packages/shared/src/sources/storage.ts delete mode 100644 packages/desktop/packages/shared/src/sources/token-refresh-manager.ts delete mode 100644 packages/desktop/packages/shared/src/sources/types.ts delete mode 100644 packages/desktop/packages/shared/src/statuses/crud.ts delete mode 100644 packages/desktop/packages/shared/src/statuses/default-icons.ts delete mode 100644 packages/desktop/packages/shared/src/statuses/index.ts delete mode 100644 packages/desktop/packages/shared/src/statuses/storage.ts delete mode 100644 packages/desktop/packages/shared/src/statuses/types.ts delete mode 100644 packages/desktop/packages/shared/src/statuses/validation.ts delete mode 100644 packages/desktop/packages/shared/src/tools/index.ts delete mode 100644 packages/desktop/packages/shared/src/types/incr-regex-package.d.ts delete mode 100644 packages/desktop/packages/shared/src/types/marked-terminal.d.ts delete mode 100644 packages/desktop/packages/shared/src/utils/__tests__/binary-detection-base64.test.ts delete mode 100644 packages/desktop/packages/shared/src/utils/__tests__/binary-detection.test.ts delete mode 100644 packages/desktop/packages/shared/src/utils/__tests__/bundle-files.test.ts delete mode 100644 packages/desktop/packages/shared/src/utils/__tests__/cli-icon-resolver.test.ts delete mode 100644 packages/desktop/packages/shared/src/utils/__tests__/files.test.ts delete mode 100644 packages/desktop/packages/shared/src/utils/__tests__/icon-constants.test.ts delete mode 100644 packages/desktop/packages/shared/src/utils/__tests__/logo.test.ts delete mode 100644 packages/desktop/packages/shared/src/utils/__tests__/paths.test.ts delete mode 100644 packages/desktop/packages/shared/src/utils/__tests__/title-generator.test.ts delete mode 100644 packages/desktop/packages/shared/src/utils/__tests__/url-safety.test.ts delete mode 100644 packages/desktop/packages/shared/src/utils/binary-detection.ts delete mode 100644 packages/desktop/packages/shared/src/utils/bundle-files.ts delete mode 100644 packages/desktop/packages/shared/src/utils/cli-icon-resolver.ts delete mode 100644 packages/desktop/packages/shared/src/utils/debug.ts delete mode 100644 packages/desktop/packages/shared/src/utils/files.ts delete mode 100644 packages/desktop/packages/shared/src/utils/icon-constants.ts delete mode 100644 packages/desktop/packages/shared/src/utils/icon-encoder.ts delete mode 100644 packages/desktop/packages/shared/src/utils/icon.ts delete mode 100644 packages/desktop/packages/shared/src/utils/index.ts delete mode 100644 packages/desktop/packages/shared/src/utils/large-response.ts delete mode 100644 packages/desktop/packages/shared/src/utils/logo.ts delete mode 100644 packages/desktop/packages/shared/src/utils/mask.ts delete mode 100644 packages/desktop/packages/shared/src/utils/open-url.ts delete mode 100644 packages/desktop/packages/shared/src/utils/paths.ts delete mode 100644 packages/desktop/packages/shared/src/utils/perf.ts delete mode 100644 packages/desktop/packages/shared/src/utils/service-url.ts delete mode 100644 packages/desktop/packages/shared/src/utils/summarize.ts delete mode 100644 packages/desktop/packages/shared/src/utils/title-generator.ts delete mode 100644 packages/desktop/packages/shared/src/utils/toolNames.ts delete mode 100644 packages/desktop/packages/shared/src/utils/url-safety.ts delete mode 100644 packages/desktop/packages/shared/src/utils/workspace-slug.ts delete mode 100644 packages/desktop/packages/shared/src/utils/workspace.ts delete mode 100644 packages/desktop/packages/shared/src/validation/index.ts delete mode 100644 packages/desktop/packages/shared/src/validation/url-validator.ts delete mode 100644 packages/desktop/packages/shared/src/version/index.ts delete mode 100644 packages/desktop/packages/shared/src/version/install.ts delete mode 100644 packages/desktop/packages/shared/src/version/manifest.ts delete mode 100644 packages/desktop/packages/shared/src/version/version.ts delete mode 100644 packages/desktop/packages/shared/src/views/defaults.ts delete mode 100644 packages/desktop/packages/shared/src/views/evaluator.ts delete mode 100644 packages/desktop/packages/shared/src/views/functions.ts delete mode 100644 packages/desktop/packages/shared/src/views/index.ts delete mode 100644 packages/desktop/packages/shared/src/views/storage.ts delete mode 100644 packages/desktop/packages/shared/src/views/types.ts delete mode 100644 packages/desktop/packages/shared/src/views/validation.ts delete mode 100644 packages/desktop/packages/shared/src/workspaces/__tests__/storage-permission-mode-normalization.test.ts delete mode 100644 packages/desktop/packages/shared/src/workspaces/index.ts delete mode 100644 packages/desktop/packages/shared/src/workspaces/storage.ts delete mode 100644 packages/desktop/packages/shared/src/workspaces/types.ts delete mode 100644 packages/desktop/packages/shared/tests/content-validators.test.ts delete mode 100644 packages/desktop/packages/shared/tests/mcp-pool.test.ts delete mode 100644 packages/desktop/packages/shared/tests/mode-manager.test.ts delete mode 100644 packages/desktop/packages/shared/tests/models.test.ts delete mode 100644 packages/desktop/packages/shared/tests/permissions-craft-agent-sync.test.ts delete mode 100644 packages/desktop/packages/shared/tests/persistence-queue.test.ts delete mode 100644 packages/desktop/packages/shared/tests/session-validation.test.ts delete mode 100644 packages/desktop/packages/shared/tests/shellguard-corpus.test.ts delete mode 100644 packages/desktop/packages/shared/tsconfig.json delete mode 100644 packages/desktop/packages/ui/eslint-rules/__tests__/no-floating-z-tokens-in-island.test.ts delete mode 100644 packages/desktop/packages/ui/eslint-rules/__tests__/no-hardcoded-z-index.test.ts delete mode 100644 packages/desktop/packages/ui/eslint-rules/no-floating-z-tokens-in-island.cjs delete mode 100644 packages/desktop/packages/ui/eslint-rules/no-hardcoded-z-index.cjs delete mode 100644 packages/desktop/packages/ui/eslint-rules/no-nonstandard-shadows.cjs delete mode 100644 packages/desktop/packages/ui/eslint.config.mjs delete mode 100644 packages/desktop/packages/ui/package.json delete mode 100644 packages/desktop/packages/ui/src/components/annotations/AnnotationIslandMenu.tsx delete mode 100644 packages/desktop/packages/ui/src/components/annotations/AnnotationOverlayLayer.tsx delete mode 100644 packages/desktop/packages/ui/src/components/annotations/__tests__/annotation-core.test.ts delete mode 100644 packages/desktop/packages/ui/src/components/annotations/__tests__/annotation-host-config.test.ts delete mode 100644 packages/desktop/packages/ui/src/components/annotations/__tests__/interaction-policy.test.ts delete mode 100644 packages/desktop/packages/ui/src/components/annotations/__tests__/interaction-state-machine.test.ts delete mode 100644 packages/desktop/packages/ui/src/components/annotations/__tests__/island-dismiss-policy.test.ts delete mode 100644 packages/desktop/packages/ui/src/components/annotations/__tests__/island-motion.test.ts delete mode 100644 packages/desktop/packages/ui/src/components/annotations/__tests__/selection-restore.test.ts delete mode 100644 packages/desktop/packages/ui/src/components/annotations/__tests__/use-annotation-island-events.test.ts delete mode 100644 packages/desktop/packages/ui/src/components/annotations/__tests__/use-annotation-island-presentation.test.ts delete mode 100644 packages/desktop/packages/ui/src/components/annotations/annotation-core.ts delete mode 100644 packages/desktop/packages/ui/src/components/annotations/annotation-host-config.ts delete mode 100644 packages/desktop/packages/ui/src/components/annotations/annotation-overlay-geometry.ts delete mode 100644 packages/desktop/packages/ui/src/components/annotations/annotation-style-tokens.ts delete mode 100644 packages/desktop/packages/ui/src/components/annotations/block-markers.ts delete mode 100644 packages/desktop/packages/ui/src/components/annotations/follow-up-state.ts delete mode 100644 packages/desktop/packages/ui/src/components/annotations/interaction-policy.ts delete mode 100644 packages/desktop/packages/ui/src/components/annotations/interaction-selectors.ts delete mode 100644 packages/desktop/packages/ui/src/components/annotations/interaction-state-machine.ts delete mode 100644 packages/desktop/packages/ui/src/components/annotations/island-dismiss-policy.ts delete mode 100644 packages/desktop/packages/ui/src/components/annotations/island-motion.ts delete mode 100644 packages/desktop/packages/ui/src/components/annotations/selection-restore.ts delete mode 100644 packages/desktop/packages/ui/src/components/annotations/use-annotation-cancel-restore.ts delete mode 100644 packages/desktop/packages/ui/src/components/annotations/use-annotation-interaction-controller.ts delete mode 100644 packages/desktop/packages/ui/src/components/annotations/use-annotation-island-events.ts delete mode 100644 packages/desktop/packages/ui/src/components/annotations/use-annotation-island-presentation.ts delete mode 100644 packages/desktop/packages/ui/src/components/chat/AcceptPlanDropdown.tsx delete mode 100644 packages/desktop/packages/ui/src/components/chat/InlineExecution.tsx delete mode 100644 packages/desktop/packages/ui/src/components/chat/SessionViewer.tsx delete mode 100644 packages/desktop/packages/ui/src/components/chat/SystemMessage.tsx delete mode 100644 packages/desktop/packages/ui/src/components/chat/TurnCard.tsx delete mode 100644 packages/desktop/packages/ui/src/components/chat/TurnCardActionsMenu.tsx delete mode 100644 packages/desktop/packages/ui/src/components/chat/UserMessageBubble.tsx delete mode 100644 packages/desktop/packages/ui/src/components/chat/__tests__/assistant-ui-key.test.ts delete mode 100644 packages/desktop/packages/ui/src/components/chat/__tests__/turn-lifecycle.test.ts delete mode 100644 packages/desktop/packages/ui/src/components/chat/__tests__/turn-phase.test.ts delete mode 100644 packages/desktop/packages/ui/src/components/chat/__tests__/turn-timeline.test.ts delete mode 100644 packages/desktop/packages/ui/src/components/chat/__tests__/turn-utils-grouping.test.ts delete mode 100644 packages/desktop/packages/ui/src/components/chat/__tests__/turn-utils-incremental.test.ts delete mode 100644 packages/desktop/packages/ui/src/components/chat/__tests__/turn-utils-plan-annotations.test.ts delete mode 100644 packages/desktop/packages/ui/src/components/chat/__tests__/user-message-bubble-text-elements.test.tsx delete mode 100644 packages/desktop/packages/ui/src/components/chat/attachment-helpers.tsx delete mode 100644 packages/desktop/packages/ui/src/components/chat/follow-up-helpers.ts delete mode 100644 packages/desktop/packages/ui/src/components/chat/index.ts delete mode 100644 packages/desktop/packages/ui/src/components/chat/turn-timeline.ts delete mode 100644 packages/desktop/packages/ui/src/components/chat/turn-utils.ts delete mode 100644 packages/desktop/packages/ui/src/components/code-viewer/DiffIcons.tsx delete mode 100644 packages/desktop/packages/ui/src/components/code-viewer/DiffViewerControls.tsx delete mode 100644 packages/desktop/packages/ui/src/components/code-viewer/ShikiCodeViewer.tsx delete mode 100644 packages/desktop/packages/ui/src/components/code-viewer/ShikiDiffViewer.tsx delete mode 100644 packages/desktop/packages/ui/src/components/code-viewer/UnifiedDiffViewer.tsx delete mode 100644 packages/desktop/packages/ui/src/components/code-viewer/index.ts delete mode 100644 packages/desktop/packages/ui/src/components/code-viewer/language-map.ts delete mode 100644 packages/desktop/packages/ui/src/components/code-viewer/registerShikiThemes.ts delete mode 100644 packages/desktop/packages/ui/src/components/icons/Folder.tsx delete mode 100644 packages/desktop/packages/ui/src/components/icons/Home.tsx delete mode 100644 packages/desktop/packages/ui/src/components/icons/Inbox.tsx delete mode 100644 packages/desktop/packages/ui/src/components/icons/index.ts delete mode 100644 packages/desktop/packages/ui/src/components/icons/types.ts delete mode 100644 packages/desktop/packages/ui/src/components/markdown/CodeBlock.tsx delete mode 100644 packages/desktop/packages/ui/src/components/markdown/CollapsibleMarkdownContext.tsx delete mode 100644 packages/desktop/packages/ui/src/components/markdown/CollapsibleSection.tsx delete mode 100644 packages/desktop/packages/ui/src/components/markdown/ImageCardStack.tsx delete mode 100644 packages/desktop/packages/ui/src/components/markdown/Markdown.tsx delete mode 100644 packages/desktop/packages/ui/src/components/markdown/MarkdownDatatableBlock.tsx delete mode 100644 packages/desktop/packages/ui/src/components/markdown/MarkdownDiffBlock.tsx delete mode 100644 packages/desktop/packages/ui/src/components/markdown/MarkdownHtmlBlock.tsx delete mode 100644 packages/desktop/packages/ui/src/components/markdown/MarkdownImageBlock.tsx delete mode 100644 packages/desktop/packages/ui/src/components/markdown/MarkdownJsonBlock.tsx delete mode 100644 packages/desktop/packages/ui/src/components/markdown/MarkdownLatexBlock.tsx delete mode 100644 packages/desktop/packages/ui/src/components/markdown/MarkdownMermaidBlock.tsx delete mode 100644 packages/desktop/packages/ui/src/components/markdown/MarkdownPdfBlock.tsx delete mode 100644 packages/desktop/packages/ui/src/components/markdown/MarkdownSpreadsheetBlock.tsx delete mode 100644 packages/desktop/packages/ui/src/components/markdown/RichBlockShell.tsx delete mode 100644 packages/desktop/packages/ui/src/components/markdown/TableExportDropdown.tsx delete mode 100644 packages/desktop/packages/ui/src/components/markdown/TiptapBubbleMenus.tsx delete mode 100644 packages/desktop/packages/ui/src/components/markdown/TiptapCodeBlockView.tsx delete mode 100644 packages/desktop/packages/ui/src/components/markdown/TiptapHoverActions.tsx delete mode 100644 packages/desktop/packages/ui/src/components/markdown/TiptapMarkdownEditor.tsx delete mode 100644 packages/desktop/packages/ui/src/components/markdown/TiptapSlashMenu.ts delete mode 100644 packages/desktop/packages/ui/src/components/markdown/__tests__/annotation-resolver.test.ts delete mode 100644 packages/desktop/packages/ui/src/components/markdown/__tests__/diff-normalize.test.ts delete mode 100644 packages/desktop/packages/ui/src/components/markdown/__tests__/linkify.test.ts delete mode 100644 packages/desktop/packages/ui/src/components/markdown/__tests__/markdown-link-routing.test.ts delete mode 100644 packages/desktop/packages/ui/src/components/markdown/__tests__/math-options.test.ts delete mode 100644 packages/desktop/packages/ui/src/components/markdown/__tests__/official-markdown-math-foundation.test.ts delete mode 100644 packages/desktop/packages/ui/src/components/markdown/__tests__/safe-components.test.ts delete mode 100644 packages/desktop/packages/ui/src/components/markdown/__tests__/tiptap-mermaid-input-helpers.test.ts delete mode 100644 packages/desktop/packages/ui/src/components/markdown/__tests__/tiptap-slash-menu.test.ts delete mode 100644 packages/desktop/packages/ui/src/components/markdown/annotation-resolver.ts delete mode 100644 packages/desktop/packages/ui/src/components/markdown/diff-normalize.ts delete mode 100644 packages/desktop/packages/ui/src/components/markdown/extensions/AnimatedTaskItem.ts delete mode 100644 packages/desktop/packages/ui/src/components/markdown/extensions/LatexBlock.tsx delete mode 100644 packages/desktop/packages/ui/src/components/markdown/extensions/MermaidBlock.tsx delete mode 100644 packages/desktop/packages/ui/src/components/markdown/extensions/RichBlockInteractions.ts delete mode 100644 packages/desktop/packages/ui/src/components/markdown/extensions/TiptapImageBlock.tsx delete mode 100644 packages/desktop/packages/ui/src/components/markdown/extensions/animated-task-item.css delete mode 100644 packages/desktop/packages/ui/src/components/markdown/extensions/animated-task-item.tokens.ts delete mode 100644 packages/desktop/packages/ui/src/components/markdown/index.ts delete mode 100644 packages/desktop/packages/ui/src/components/markdown/link-target.ts delete mode 100644 packages/desktop/packages/ui/src/components/markdown/linkify.ts delete mode 100644 packages/desktop/packages/ui/src/components/markdown/math-options.ts delete mode 100644 packages/desktop/packages/ui/src/components/markdown/remarkCollapsibleSections.ts delete mode 100644 packages/desktop/packages/ui/src/components/markdown/rich-block-events.ts delete mode 100644 packages/desktop/packages/ui/src/components/markdown/safe-components.tsx delete mode 100644 packages/desktop/packages/ui/src/components/markdown/table-export.ts delete mode 100644 packages/desktop/packages/ui/src/components/markdown/tiptap-editor.css delete mode 100644 packages/desktop/packages/ui/src/components/markdown/useScrollFade.ts delete mode 100644 packages/desktop/packages/ui/src/components/overlay/ActivityCardsOverlay.tsx delete mode 100644 packages/desktop/packages/ui/src/components/overlay/AnnotatableMarkdownDocument.tsx delete mode 100644 packages/desktop/packages/ui/src/components/overlay/CodePreviewOverlay.tsx delete mode 100644 packages/desktop/packages/ui/src/components/overlay/ContentFrame.tsx delete mode 100644 packages/desktop/packages/ui/src/components/overlay/CopyButton.tsx delete mode 100644 packages/desktop/packages/ui/src/components/overlay/DataTableOverlay.tsx delete mode 100644 packages/desktop/packages/ui/src/components/overlay/DocumentFormattedMarkdownOverlay.tsx delete mode 100644 packages/desktop/packages/ui/src/components/overlay/FullscreenOverlayBase.tsx delete mode 100644 packages/desktop/packages/ui/src/components/overlay/FullscreenOverlayBaseHeader.tsx delete mode 100644 packages/desktop/packages/ui/src/components/overlay/GenericOverlay.tsx delete mode 100644 packages/desktop/packages/ui/src/components/overlay/HTMLPreviewOverlay.tsx delete mode 100644 packages/desktop/packages/ui/src/components/overlay/ImagePreviewOverlay.tsx delete mode 100644 packages/desktop/packages/ui/src/components/overlay/ItemNavigator.tsx delete mode 100644 packages/desktop/packages/ui/src/components/overlay/JSONPreviewOverlay.tsx delete mode 100644 packages/desktop/packages/ui/src/components/overlay/MermaidPreviewOverlay.tsx delete mode 100644 packages/desktop/packages/ui/src/components/overlay/MultiDiffPreviewOverlay.tsx delete mode 100644 packages/desktop/packages/ui/src/components/overlay/OverlayErrorBanner.tsx delete mode 100644 packages/desktop/packages/ui/src/components/overlay/PDFPreviewOverlay.tsx delete mode 100644 packages/desktop/packages/ui/src/components/overlay/PreviewOverlay.tsx delete mode 100644 packages/desktop/packages/ui/src/components/overlay/TerminalPreviewOverlay.tsx delete mode 100644 packages/desktop/packages/ui/src/components/overlay/ZoomControls.tsx delete mode 100644 packages/desktop/packages/ui/src/components/overlay/__tests__/fullscreen-overlay-escape-stack.test.ts delete mode 100644 packages/desktop/packages/ui/src/components/overlay/__tests__/rich-block-parity.test.ts delete mode 100644 packages/desktop/packages/ui/src/components/overlay/__tests__/useRichBlockInteractions.test.ts delete mode 100644 packages/desktop/packages/ui/src/components/overlay/index.ts delete mode 100644 packages/desktop/packages/ui/src/components/overlay/rich-block-interaction-spec.ts delete mode 100644 packages/desktop/packages/ui/src/components/overlay/useRichBlockInteractions.ts delete mode 100644 packages/desktop/packages/ui/src/components/terminal/TerminalOutput.tsx delete mode 100644 packages/desktop/packages/ui/src/components/terminal/ansi-parser.ts delete mode 100644 packages/desktop/packages/ui/src/components/terminal/index.ts delete mode 100644 packages/desktop/packages/ui/src/components/tooltip.tsx delete mode 100644 packages/desktop/packages/ui/src/components/ui/BrowserControls.tsx delete mode 100644 packages/desktop/packages/ui/src/components/ui/BrowserEmptyStateCard.tsx delete mode 100644 packages/desktop/packages/ui/src/components/ui/BrowserShader.tsx delete mode 100644 packages/desktop/packages/ui/src/components/ui/FilterableSelectPopover.tsx delete mode 100644 packages/desktop/packages/ui/src/components/ui/InlineMenuSurface.ts delete mode 100644 packages/desktop/packages/ui/src/components/ui/Island.tsx delete mode 100644 packages/desktop/packages/ui/src/components/ui/IslandFollowUpContentView.tsx delete mode 100644 packages/desktop/packages/ui/src/components/ui/LoadingIndicator.tsx delete mode 100644 packages/desktop/packages/ui/src/components/ui/PreviewHeader.tsx delete mode 100644 packages/desktop/packages/ui/src/components/ui/SimpleDropdown.tsx delete mode 100644 packages/desktop/packages/ui/src/components/ui/StyledDropdown.tsx delete mode 100644 packages/desktop/packages/ui/src/components/ui/__tests__/Island.escape.test.ts delete mode 100644 packages/desktop/packages/ui/src/components/ui/__tests__/styled-dropdown.test.ts delete mode 100644 packages/desktop/packages/ui/src/components/ui/index.ts delete mode 100644 packages/desktop/packages/ui/src/components/ui/useIslandNavigation.ts delete mode 100644 packages/desktop/packages/ui/src/context/PlatformContext.tsx delete mode 100644 packages/desktop/packages/ui/src/context/ShikiThemeContext.tsx delete mode 100644 packages/desktop/packages/ui/src/context/index.ts delete mode 100644 packages/desktop/packages/ui/src/index.ts delete mode 100644 packages/desktop/packages/ui/src/lib/__tests__/tool-parsers-cards.test.ts delete mode 100644 packages/desktop/packages/ui/src/lib/dismissible-layer-bridge.ts delete mode 100644 packages/desktop/packages/ui/src/lib/file-classification.ts delete mode 100644 packages/desktop/packages/ui/src/lib/layout.ts delete mode 100644 packages/desktop/packages/ui/src/lib/open-external-url.ts delete mode 100644 packages/desktop/packages/ui/src/lib/tool-parsers.ts delete mode 100644 packages/desktop/packages/ui/src/lib/utils.ts delete mode 100644 packages/desktop/packages/ui/src/pdfjs-worker.d.ts delete mode 100644 packages/desktop/packages/ui/src/styles/index.css delete mode 100644 packages/desktop/packages/ui/tsconfig.json delete mode 100644 packages/desktop/scripts/browser-tool.ts delete mode 100644 packages/desktop/scripts/build-server.ts delete mode 100644 packages/desktop/scripts/build-wa-worker.ts delete mode 100644 packages/desktop/scripts/build/common.ts delete mode 100644 packages/desktop/scripts/build/darwin.ts delete mode 100644 packages/desktop/scripts/build/linux.ts delete mode 100644 packages/desktop/scripts/build/win32.ts delete mode 100644 packages/desktop/scripts/bump-desktop-version.ts delete mode 100644 packages/desktop/scripts/bump-live-host-version.ts delete mode 100644 packages/desktop/scripts/check-i18n-parity.ts delete mode 100644 packages/desktop/scripts/check-release-version.ts delete mode 100644 packages/desktop/scripts/desktop-release-version.ts delete mode 100644 packages/desktop/scripts/dev-instance.ts delete mode 100644 packages/desktop/scripts/dev-server.ts delete mode 100644 packages/desktop/scripts/dev-vite.ts delete mode 100755 packages/desktop/scripts/docker-smoke-test.sh delete mode 100644 packages/desktop/scripts/electron-build-main.ts delete mode 100644 packages/desktop/scripts/electron-build-preload.ts delete mode 100644 packages/desktop/scripts/electron-build-renderer.ts delete mode 100644 packages/desktop/scripts/electron-build-resources.ts delete mode 100644 packages/desktop/scripts/electron-builder-config.ts delete mode 100644 packages/desktop/scripts/electron-clean.ts delete mode 100644 packages/desktop/scripts/electron-dev.ts delete mode 100755 packages/desktop/scripts/generate-dev-cert.sh delete mode 100644 packages/desktop/scripts/install-app.ps1 delete mode 100755 packages/desktop/scripts/install-app.sh delete mode 100755 packages/desktop/scripts/install-server.sh delete mode 100755 packages/desktop/scripts/test-workflow-local.sh delete mode 100644 packages/desktop/scripts/vendor-qwen-code.ts delete mode 100644 packages/desktop/tsconfig.base.json delete mode 100644 packages/desktop/tsconfig.json delete mode 100644 scripts/check-voice-guard-sync.js delete mode 100644 scripts/desktop-openwork-sync.ts delete mode 100644 scripts/tests/check-voice-guard-sync.test.js delete mode 100644 scripts/tests/live-host-oss-workflow.test.js diff --git a/.github/workflows/.size-baseline b/.github/workflows/.size-baseline index f920373a836..af915611f12 100644 --- a/.github/workflows/.size-baseline +++ b/.github/workflows/.size-baseline @@ -26,8 +26,6 @@ 10005 dsw-swe-verified-release.yml 12340 e2e.yml 11394 finalize-release.yml -15871 live-host-release.yml -6384 live-host.yml 7642 main-ci-failure-issue.yml 1686 npm-cache.yml 7299 pr-force-push-reminder.yml @@ -56,7 +54,6 @@ 17013 serve-ab.yml 2641 stale.yml 10920 sync-desktop-to-oss.yml -10018 sync-live-host-to-oss.yml 10138 sync-release-to-oss.yml 3303 update-ecs-runner-qwen.yml 2307 web-shell-visuals-cleanup.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2a7e4026e9d..a4d83733d3c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -389,10 +389,6 @@ jobs: if: "${{ needs.classify_pr.outputs.skip_ci != 'true' && steps.ci_profile.outputs.ci_profile == 'full' }}" run: 'npm run check:desktop-isolation' - - name: 'Check voice guard mirror sync' - if: "${{ needs.classify_pr.outputs.skip_ci != 'true' && steps.ci_profile.outputs.ci_profile == 'full' }}" - run: 'npm run check:voice-guard-sync' - - name: 'Install linters' if: "${{ needs.classify_pr.outputs.skip_ci != 'true' && steps.ci_profile.outputs.ci_profile == 'full' }}" run: 'node scripts/lint.js --setup' diff --git a/.github/workflows/live-host-release.yml b/.github/workflows/live-host-release.yml deleted file mode 100644 index 290d2c59b31..00000000000 --- a/.github/workflows/live-host-release.yml +++ /dev/null @@ -1,363 +0,0 @@ -name: 'Qwen Live Host Release' - -run-name: "Qwen Live Host release ${{ inputs.version || format('PR #{0} dry run', github.event.pull_request.number) }}" - -on: - pull_request: - paths: - - '.github/workflows/live-host-release.yml' - - 'packages/desktop/apps/live-host/**' - - 'packages/desktop/bun.lock' - - 'packages/desktop/package.json' - - 'packages/desktop/scripts/bump-live-host-version.ts' - workflow_dispatch: - inputs: - version: - description: 'Live Host version, for example 0.1.0 or v0.1.0.' - required: true - type: 'string' - dry_run: - description: 'Build unsigned packages without publishing.' - required: true - default: true - type: 'boolean' - draft: - description: 'Create a draft GitHub release.' - required: true - default: true - type: 'boolean' - prerelease: - description: 'Mark the release as a prerelease.' - required: true - default: false - type: 'boolean' - clobber: - description: 'Replace same-named assets in an existing release.' - required: true - default: false - type: 'boolean' - -permissions: - actions: 'read' - contents: 'read' - -concurrency: - group: 'live-host-release-${{ github.event.pull_request.number || inputs.version }}' - cancel-in-progress: false - -env: - BUN_VERSION: '1.3.9' - NODE_VERSION: '22.20.0' - LIVE_HOST_FEED_TAG: 'live-host-latest' - -jobs: - prepare: - name: 'Prepare Live Host release' - runs-on: 'ubuntu-latest' - timeout-minutes: 10 - outputs: - tag: '${{ steps.version.outputs.tag }}' - version: '${{ steps.version.outputs.version }}' - steps: - - name: 'Resolve version' - id: 'version' - shell: 'bash' - env: - INPUT_VERSION: '${{ inputs.version }}' - PR_NUMBER: '${{ github.event.pull_request.number }}' - run: | - set -euo pipefail - if [ "$GITHUB_EVENT_NAME" = 'pull_request' ]; then - version="0.0.$PR_NUMBER" - else - version="${INPUT_VERSION#v}" - fi - if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+([+-][0-9A-Za-z.-]+)?$ ]]; then - echo "::error::Live Host version must be valid SemVer: $INPUT_VERSION" - exit 1 - fi - if [[ "$version" == *+* ]]; then - echo '::error::Live Host releases do not support SemVer build metadata.' - exit 1 - fi - if [ "$GITHUB_EVENT_NAME" = 'workflow_dispatch' ] && [ "${{ inputs.dry_run }}" = 'false' ] && [ "$GITHUB_REF_NAME" != 'main' ]; then - echo '::error::Published Live Host releases must run from main.' - exit 1 - fi - echo "version=$version" >> "$GITHUB_OUTPUT" - echo "tag=live-host-v$version" >> "$GITHUB_OUTPUT" - - build: - name: 'Build Qwen Live Host' - needs: 'prepare' - runs-on: 'macos-latest' - timeout-minutes: 60 - steps: - - uses: 'actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10' # v6.0.3 - - - uses: 'actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e' # v6.4.0 - with: - node-version: '${{ env.NODE_VERSION }}' - - - uses: 'oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6' # v2 - with: - bun-version: '${{ env.BUN_VERSION }}' - - - name: 'Install dependencies' - working-directory: 'packages/desktop' - run: 'bun install --frozen-lockfile' - - - name: 'Set Live Host version' - working-directory: 'packages/desktop' - run: 'bun run bump-live-host-version "${{ needs.prepare.outputs.version }}"' - - - name: 'Test Live Host' - working-directory: 'packages/desktop' - run: 'bun run live-host:typecheck && bun run live-host:test' - - - name: 'Import macOS certificate' - if: "${{ github.event_name == 'workflow_dispatch' && inputs.dry_run == false }}" - shell: 'bash' - env: - APPLE_CERTIFICATE: '${{ secrets.APPLE_CERTIFICATE }}' - APPLE_CERTIFICATE_PASSWORD: '${{ secrets.APPLE_CERTIFICATE_PASSWORD }}' - LEGACY_APPLE_CERTIFICATE: '${{ secrets.MAC_CSC_LINK }}' - LEGACY_APPLE_CERTIFICATE_PASSWORD: '${{ secrets.MAC_CSC_KEY_PASSWORD }}' - KEYCHAIN_PASSWORD: '${{ secrets.APPLE_KEYCHAIN_PASSWORD }}' - run: | - set -euo pipefail - if [ -n "$APPLE_CERTIFICATE" ] && [ -n "$APPLE_CERTIFICATE_PASSWORD" ]; then - certificate_data="$APPLE_CERTIFICATE" - certificate_password="$APPLE_CERTIFICATE_PASSWORD" - elif [ -n "$LEGACY_APPLE_CERTIFICATE" ] && [ -n "$LEGACY_APPLE_CERTIFICATE_PASSWORD" ]; then - certificate_data="$LEGACY_APPLE_CERTIFICATE" - certificate_password="$LEGACY_APPLE_CERTIFICATE_PASSWORD" - else - echo '::error::A complete APPLE_CERTIFICATE pair or MAC_CSC pair is required for published Qwen Live Host releases.' - exit 1 - fi - keychain_password="${KEYCHAIN_PASSWORD:-$(openssl rand -hex 32)}" - certificate="$RUNNER_TEMP/qwen-live-host.p12" - keychain="$RUNNER_TEMP/qwen-live-host.keychain-db" - certificate_data="${certificate_data#*base64,}" - printf '%s' "$certificate_data" | base64 --decode > "$certificate" - security create-keychain -p "$keychain_password" "$keychain" - security set-keychain-settings -lut 21600 "$keychain" - security unlock-keychain -p "$keychain_password" "$keychain" - security import "$certificate" -P "$certificate_password" -A -t cert -f pkcs12 -k "$keychain" - security list-keychains -d user -s "$keychain" login.keychain-db - security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$keychain_password" "$keychain" - identity="$(security find-identity -v -p codesigning "$keychain" | sed -n 's/.*"\(Developer ID Application:.*\)"/\1/p' | head -n 1)" - if [ -z "$identity" ]; then echo '::error::Developer ID Application identity was not found.'; exit 1; fi - identity_name="${identity#Developer ID Application: }" - { - echo "CSC_NAME=$identity_name" - echo 'CSC_IDENTITY_AUTO_DISCOVERY=true' - } >> "$GITHUB_ENV" - - - name: 'Configure notarization' - if: "${{ github.event_name == 'workflow_dispatch' && inputs.dry_run == false }}" - shell: 'bash' - env: - APPLE_API_ISSUER: '${{ secrets.APPLE_API_ISSUER }}' - APPLE_API_KEY_ID: '${{ secrets.APPLE_API_KEY }}' - APPLE_API_KEY_P8: '${{ secrets.APPLE_API_KEY_P8 }}' - LEGACY_APPLE_API_ISSUER: '${{ secrets.APPLE_NOTARY_ISSUER_ID }}' - LEGACY_APPLE_API_KEY_ID: '${{ secrets.APPLE_NOTARY_KEY_ID }}' - LEGACY_APPLE_API_KEY_P8: '${{ secrets.APPLE_NOTARY_API_KEY_P8_BASE64 }}' - APPLE_TEAM_ID: '${{ secrets.APPLE_TEAM_ID }}' - run: | - set -euo pipefail - if [ -n "$APPLE_API_ISSUER" ] && [ -n "$APPLE_API_KEY_ID" ] && [ -n "$APPLE_API_KEY_P8" ]; then - api_issuer="$APPLE_API_ISSUER" - api_key_id="$APPLE_API_KEY_ID" - key_path="$RUNNER_TEMP/AuthKey_${api_key_id}.p8" - printf '%s' "$APPLE_API_KEY_P8" > "$key_path" - elif [ -n "$LEGACY_APPLE_API_ISSUER" ] && [ -n "$LEGACY_APPLE_API_KEY_ID" ] && [ -n "$LEGACY_APPLE_API_KEY_P8" ]; then - api_issuer="$LEGACY_APPLE_API_ISSUER" - api_key_id="$LEGACY_APPLE_API_KEY_ID" - key_path="$RUNNER_TEMP/AuthKey_${api_key_id}.p8" - printf '%s' "$LEGACY_APPLE_API_KEY_P8" | base64 --decode > "$key_path" - else - echo '::error::A complete APPLE_API notarization set or APPLE_NOTARY set is required for Qwen Live Host notarization.' - exit 1 - fi - if [ -z "$APPLE_TEAM_ID" ]; then echo '::error::APPLE_TEAM_ID is required for Qwen Live Host notarization.'; exit 1; fi - { - echo "APPLE_API_KEY=$key_path" - echo "APPLE_API_KEY_ID=$api_key_id" - echo "APPLE_API_ISSUER=$api_issuer" - echo "APPLE_TEAM_ID=$APPLE_TEAM_ID" - } >> "$GITHUB_ENV" - - - name: 'Build packages' - working-directory: 'packages/desktop' - env: - CSC_IDENTITY_AUTO_DISCOVERY: "${{ github.event_name == 'workflow_dispatch' && inputs.dry_run == false }}" - run: 'bun run live-host:dist:mac:no-publish' - - - name: 'Verify release assets' - working-directory: 'packages/desktop/apps/live-host' - env: - RELEASE_VERSION: '${{ needs.prepare.outputs.version }}' - shell: 'bash' - run: | - set -euo pipefail - for asset in \ - Qwen-Live-Host-arm64.dmg \ - Qwen-Live-Host-x64.dmg \ - Qwen-Live-Host-arm64.zip \ - Qwen-Live-Host-x64.zip \ - Qwen-Live-Host-manifest.json; do - test -f "release/$asset" - done - node --input-type=module -e ' - import { createHash } from "node:crypto"; - import { readFileSync, statSync } from "node:fs"; - const manifest = JSON.parse(readFileSync("release/Qwen-Live-Host-manifest.json", "utf8")); - if (manifest.version !== process.env.RELEASE_VERSION) { - throw new Error("Manifest version " + manifest.version + " does not match " + process.env.RELEASE_VERSION + "."); - } - for (const architecture of ["arm64", "x64"]) { - const name = "Qwen-Live-Host-" + architecture + ".zip"; - const path = "release/" + name; - const bytes = readFileSync(path); - const asset = manifest.assets?.[architecture]; - if (asset?.name !== name || asset.size !== statSync(path).size || asset.sha256 !== createHash("sha256").update(bytes).digest("hex")) { - throw new Error("Manifest asset verification failed for " + architecture + "."); - } - } - ' - - - name: 'Verify signing and notarization' - if: "${{ github.event_name == 'workflow_dispatch' && inputs.dry_run == false }}" - working-directory: 'packages/desktop' - shell: 'bash' - run: | - set -euo pipefail - app_count=0 - while IFS= read -r -d '' app; do - app_count=$((app_count + 1)) - codesign --verify --deep --strict --verbose=2 "$app" - signature="$(codesign -dv --verbose=4 "$app" 2>&1)" - if ! grep -q '^Authority=Developer ID Application:' <<<"$signature" || ! grep -qx 'TeamIdentifier=NF4574S59H' <<<"$signature"; then - echo '::error::Qwen Live Host was not signed by the expected Developer ID team.' - exit 1 - fi - spctl -a -vv -t exec "$app" - xcrun stapler validate "$app" - done < <(find apps/live-host/release -mindepth 2 -maxdepth 2 -type d -name '*.app' -print0) - if [ "$app_count" -eq 0 ]; then echo '::error::No packaged Qwen Live Host application was found.'; exit 1; fi - dmg_count=0 - while IFS= read -r -d '' dmg; do - dmg_count=$((dmg_count + 1)) - hdiutil verify "$dmg" - done < <(find apps/live-host/release -type f -name '*.dmg' -print0) - if [ "$dmg_count" -eq 0 ]; then echo '::error::No Qwen Live Host disk image was found.'; exit 1; fi - - - uses: 'actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a' # v7.0.1 - with: - name: 'qwen-live-host-macos' - path: | - packages/desktop/apps/live-host/release/*.dmg - packages/desktop/apps/live-host/release/*-manifest.json - packages/desktop/apps/live-host/release/*.zip - if-no-files-found: 'error' - retention-days: 14 - - publish: - name: 'Publish Qwen Live Host release' - if: "${{ github.event_name == 'workflow_dispatch' && inputs.dry_run == false && github.repository == 'QwenLM/qwen-code' }}" - needs: - - 'prepare' - - 'build' - runs-on: 'ubuntu-latest' - timeout-minutes: 20 - permissions: - contents: 'write' - env: - GH_REPO: '${{ github.repository }}' - steps: - - uses: 'actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c' # v8.0.1 - with: - path: 'release-assets' - merge-multiple: true - - - name: 'Generate checksums' - working-directory: 'release-assets' - run: 'sha256sum -- * > SHA256SUMS.txt' - - - name: 'Create GitHub release' - id: 'release' - env: - GH_TOKEN: '${{ github.token }}' - RELEASE_TAG: '${{ needs.prepare.outputs.tag }}' - RELEASE_VERSION: '${{ needs.prepare.outputs.version }}' - RELEASE_DRAFT: '${{ inputs.draft }}' - RELEASE_PRERELEASE: '${{ inputs.prerelease }}' - RELEASE_CLOBBER: '${{ inputs.clobber }}' - run: | - set -euo pipefail - args=("$RELEASE_TAG" release-assets/* --target "$GITHUB_SHA" --title "Qwen Live Host v$RELEASE_VERSION" --generate-notes --latest=false) - if [ "$RELEASE_DRAFT" = 'true' ]; then args+=(--draft); fi - if [ "$RELEASE_PRERELEASE" = 'true' ]; then args+=(--prerelease); fi - if gh release view "$RELEASE_TAG" >/dev/null 2>&1; then - if [ "$RELEASE_CLOBBER" != 'true' ]; then - echo "::error::Release $RELEASE_TAG already exists. Re-run with clobber=true to replace its assets." - exit 1 - fi - gh release upload "$RELEASE_TAG" release-assets/* --clobber - edit_args=() - if [ "$RELEASE_DRAFT" = 'true' ]; then edit_args+=(--draft); else edit_args+=(--draft=false); fi - if [ "$RELEASE_PRERELEASE" = 'true' ]; then edit_args+=(--prerelease); else edit_args+=(--prerelease=false); fi - gh release edit "$RELEASE_TAG" "${edit_args[@]}" - release_url="$(gh release view "$RELEASE_TAG" --json url --jq '.url')" - else - release_url="$(gh release create "${args[@]}")" - fi - echo "url=$release_url" >> "$GITHUB_OUTPUT" - - - name: 'Update stable Live Host feed' - if: '${{ inputs.draft == false && inputs.prerelease == false }}' - env: - GH_TOKEN: '${{ github.token }}' - FEED_TAG: '${{ env.LIVE_HOST_FEED_TAG }}' - run: | - set -euo pipefail - stable_assets=( - release-assets/Qwen-Live-Host-manifest.json - release-assets/Qwen-Live-Host-arm64.zip - release-assets/Qwen-Live-Host-x64.zip - ) - if gh release view "$FEED_TAG" >/dev/null 2>&1; then - gh release upload "$FEED_TAG" "${stable_assets[@]}" --clobber - else - gh release create "$FEED_TAG" "${stable_assets[@]}" --title 'Qwen Live Host latest' --notes 'Stable Qwen Live Host installer feed.' --latest=false - fi - - - name: 'Publish release summary' - env: - RELEASE_URL: '${{ steps.release.outputs.url }}' - RELEASE_VERSION: '${{ needs.prepare.outputs.version }}' - run: | - { - echo '## Qwen Live Host release' - echo - echo "Version: $RELEASE_VERSION" - echo "Release: $RELEASE_URL" - } >> "$GITHUB_STEP_SUMMARY" - - sync-oss: - name: 'Mirror stable Qwen Live Host release to Aliyun OSS' - if: "${{ github.event_name == 'workflow_dispatch' && inputs.dry_run == false && inputs.draft == false && inputs.prerelease == false && github.repository == 'QwenLM/qwen-code' }}" - needs: - - 'prepare' - - 'build' - - 'publish' - uses: './.github/workflows/sync-live-host-to-oss.yml' - with: - version: '${{ needs.prepare.outputs.version }}' - source: 'artifact' - secrets: - ALIYUN_OSS_ACCESS_KEY_ID: '${{ secrets.ALIYUN_OSS_ACCESS_KEY_ID }}' - ALIYUN_OSS_ACCESS_KEY_SECRET: '${{ secrets.ALIYUN_OSS_ACCESS_KEY_SECRET }}' diff --git a/.github/workflows/live-host.yml b/.github/workflows/live-host.yml deleted file mode 100644 index deb5db5a4ce..00000000000 --- a/.github/workflows/live-host.yml +++ /dev/null @@ -1,148 +0,0 @@ -name: 'Qwen Live Host CI' - -on: - pull_request: - paths: - - '.github/workflows/live-host.yml' - - '.github/workflows/live-host-release.yml' - - 'packages/cli/src/serve/conversations/**' - - 'packages/cli/src/serve/live/**' - - 'packages/desktop/apps/live-host/**' - - 'packages/desktop/bun.lock' - - 'packages/desktop/package.json' - - 'packages/desktop/scripts/bump-live-host-version.ts' - - 'packages/sdk-typescript/src/daemon/types.ts' - merge_group: - workflow_dispatch: - -permissions: - contents: 'read' - -concurrency: - group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}' - cancel-in-progress: true - -jobs: - test: - name: 'Live Host (macos-latest)' - runs-on: 'macos-latest' - timeout-minutes: 30 - steps: - - name: 'Check out source' - uses: 'actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10' # v6.0.3 - - - name: 'Set up Node' - uses: 'actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e' # v6.4.0 - with: - node-version-file: '.nvmrc' - - - name: 'Set up Bun' - uses: 'oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6' # v2 - with: - bun-version: '1.3.9' - - - name: 'Install desktop dependencies' - working-directory: 'packages/desktop' - run: 'bun install --frozen-lockfile' - - - name: 'Typecheck Live Host' - working-directory: 'packages/desktop' - run: 'bun run live-host:typecheck' - - - name: 'Test Live Host' - working-directory: 'packages/desktop' - run: 'bun run live-host:test' - - - name: 'Build Live Host' - working-directory: 'packages/desktop' - run: 'bun run live-host:build' - - - name: 'Package unsigned Live Host app' - working-directory: 'packages/desktop/apps/live-host' - run: | - set -euo pipefail - - case "$(uname -m)" in - arm64) builder_arch='--arm64' ;; - x86_64) builder_arch='--x64' ;; - *) echo "::error::Unsupported runner architecture: $(uname -m)"; exit 1 ;; - esac - - CSC_IDENTITY_AUTO_DISCOVERY=false bunx electron-builder --config electron-builder.yml --mac dir "$builder_arch" - - app="$(find release -type d -name 'Qwen Live Host.app' -print -quit)" - if [ -z "$app" ]; then - echo '::error::Packaged Qwen Live Host.app was not found.' - exit 1 - fi - - test -x "$app/Contents/MacOS/Qwen Live Host" - test -f "$app/Contents/Resources/app.asar" - - asar_entries="$(node ../../node_modules/@electron/asar/bin/asar.mjs list "$app/Contents/Resources/app.asar")" - if printf '%s\n' "$asar_entries" | grep -Eiq '(^|/)(native/|.*command[-_]?monitor|.*commandtap)'; then - echo '::error::Packaged app.asar contains a removed keyboard monitor or native helper.' - exit 1 - fi - native_files="$(find "$app/Contents/Resources/native" -type f -print 2>/dev/null || true)" - expected_native="$app/Contents/Resources/native/qwen-live-appshot.node" - if [ "$native_files" != "$expected_native" ] || find "$app/Contents/Resources" -type f -print | grep -Eiq 'command[-_]?monitor|commandtap'; then - echo '::error::Packaged resources do not contain exactly the built-in Appshot module.' - exit 1 - fi - - extracted_asar="$(mktemp -d)" - packaged_entitlements="$(mktemp)" - trap 'rm -rf "$extracted_asar"; rm -f "$packaged_entitlements"' EXIT - node ../../node_modules/@electron/asar/bin/asar.mjs extract "$app/Contents/Resources/app.asar" "$extracted_asar" - test -d "$extracted_asar/dist" - if grep -RIEq 'openWebShellWindow|web-shell-security|host\.open_session|inputMonitoring|installUrl|loadURL\(|@modelcontextprotocol|node:child_process' "$extracted_asar/dist"; then - echo '::error::Packaged app contains a forbidden external backend, process launcher, WebShell window, or Input Monitoring path.' - exit 1 - fi - - info_plist="$app/Contents/Info.plist" - if /usr/libexec/PlistBuddy -c 'Print :NSInputMonitoringUsageDescription' "$info_plist" >/dev/null 2>&1; then - echo '::error::Packaged app declares an Input Monitoring usage description.' - exit 1 - fi - for unused_permission in NSBluetoothAlwaysUsageDescription NSBluetoothPeripheralUsageDescription NSCameraUsageDescription; do - if /usr/libexec/PlistBuddy -c "Print :$unused_permission" "$info_plist" >/dev/null 2>&1; then - echo "::error::Packaged app declares unused permission $unused_permission." - exit 1 - fi - done - /usr/libexec/PlistBuddy -c 'Print :NSMicrophoneUsageDescription' "$info_plist" >/dev/null - - assert_live_host_entitlements() { - local entitlements_file="$1" - /usr/bin/plutil -convert json -o - "$entitlements_file" | node -e ' - const chunks = []; - process.stdin.on("data", (chunk) => chunks.push(chunk)); - process.stdin.on("end", () => { - const value = JSON.parse(Buffer.concat(chunks).toString("utf8")); - const expected = [ - "com.apple.security.cs.allow-jit", - "com.apple.security.cs.allow-unsigned-executable-memory", - "com.apple.security.device.audio-input", - ]; - const actual = Object.keys(value).sort(); - if ( - actual.length !== expected.length || - actual.some((key, index) => key !== expected[index]) || - expected.some((key) => value[key] !== true) - ) { - console.error( - "Unexpected Live Host entitlements: " + JSON.stringify(value), - ); - process.exit(1); - } - }); - ' - } - - assert_live_host_entitlements build/entitlements.mac.plist - /usr/bin/codesign --force --deep --sign - --entitlements build/entitlements.mac.plist "$app" - /usr/bin/codesign --verify --deep --strict "$app" - /usr/bin/codesign -d --entitlements :- "$app" >"$packaged_entitlements" 2>/dev/null - assert_live_host_entitlements "$packaged_entitlements" diff --git a/.github/workflows/sync-live-host-to-oss.yml b/.github/workflows/sync-live-host-to-oss.yml deleted file mode 100644 index ef2d38105f8..00000000000 --- a/.github/workflows/sync-live-host-to-oss.yml +++ /dev/null @@ -1,222 +0,0 @@ -name: 'Sync Qwen Live Host to Aliyun OSS' - -on: - workflow_call: - inputs: - version: - required: true - type: 'string' - source: - required: true - type: 'string' - secrets: - ALIYUN_OSS_ACCESS_KEY_ID: - required: true - ALIYUN_OSS_ACCESS_KEY_SECRET: - required: true - workflow_dispatch: - inputs: - version: - description: 'Stable Live Host version to mirror, for example 0.1.0 or v0.1.0.' - required: true - type: 'string' - source: - description: 'Download the assets from the matching GitHub release.' - required: true - default: 'release' - type: 'choice' - options: - - 'release' - -concurrency: - group: 'sync-live-host-to-oss' - cancel-in-progress: false - -jobs: - sync: - name: 'Mirror Qwen Live Host to Aliyun OSS' - if: "${{ github.repository == 'QwenLM/qwen-code' }}" - runs-on: 'ubuntu-latest' - timeout-minutes: 30 - environment: - name: 'production-release' - permissions: - actions: 'read' - contents: 'read' - steps: - - name: 'Checkout' - uses: 'actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10' # v6.0.3 - - - name: 'Resolve release' - id: 'release' - env: - INPUT_VERSION: '${{ inputs.version }}' - INPUT_SOURCE: '${{ inputs.source }}' - run: | - set -euo pipefail - version="${INPUT_VERSION#v}" - if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - echo "::error::Live Host OSS mirrors require a stable X.Y.Z version (got '$INPUT_VERSION')." - exit 1 - fi - if [[ "$INPUT_SOURCE" != 'artifact' && "$INPUT_SOURCE" != 'release' ]]; then - echo "::error::Live Host mirror source must be artifact or release (got '$INPUT_SOURCE')." - exit 1 - fi - echo "version=$version" >> "$GITHUB_OUTPUT" - echo "source=$INPUT_SOURCE" >> "$GITHUB_OUTPUT" - - - name: 'Download release workflow artifact' - if: "${{ steps.release.outputs.source == 'artifact' }}" - uses: 'actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c' # v8.0.1 - with: - name: 'qwen-live-host-macos' - path: 'dist/live-host' - - - name: 'Download GitHub release assets' - if: "${{ steps.release.outputs.source == 'release' }}" - env: - GH_TOKEN: '${{ github.token }}' - VERSION: '${{ steps.release.outputs.version }}' - run: | - set -euo pipefail - metadata="$(gh release view "live-host-v${VERSION}" --json isDraft,isPrerelease)" - if ! jq -e '.isDraft == false and .isPrerelease == false' <<<"$metadata" >/dev/null; then - echo "::error::live-host-v${VERSION} is not a published stable release." - exit 1 - fi - mkdir -p dist/live-host - gh release download "live-host-v${VERSION}" \ - --dir dist/live-host \ - --pattern 'Qwen-Live-Host-manifest.json' \ - --pattern 'Qwen-Live-Host-arm64.zip' \ - --pattern 'Qwen-Live-Host-x64.zip' - - - name: 'Verify release assets' - env: - VERSION: '${{ steps.release.outputs.version }}' - run: | - set -euo pipefail - # shellcheck disable=SC2016 - node --input-type=module -e ' - import { createHash } from "node:crypto"; - import { readFileSync, statSync } from "node:fs"; - const directory = "dist/live-host"; - const manifest = JSON.parse(readFileSync(`${directory}/Qwen-Live-Host-manifest.json`, "utf8")); - if (manifest.version !== process.env.VERSION) throw new Error(`Manifest version ${manifest.version} does not match ${process.env.VERSION}.`); - for (const architecture of ["arm64", "x64"]) { - const name = `Qwen-Live-Host-${architecture}.zip`; - const file = `${directory}/${name}`; - const asset = manifest.assets?.[architecture]; - const checksum = createHash("sha256").update(readFileSync(file)).digest("hex"); - if (asset?.name !== name || asset.size !== statSync(file).size || asset.sha256 !== checksum) throw new Error(`Manifest asset verification failed for ${architecture}.`); - } - ' - - - name: 'Install ossutil' - env: - OSSUTIL_URL: "${{ vars.OSSUTIL_URL || 'https://gosspublic.alicdn.com/ossutil/1.7.19/ossutil-v1.7.19-linux-amd64.zip' }}" - OSSUTIL_SHA256: "${{ vars.OSSUTIL_SHA256 || 'dcc512e4a893e16bbee63bc769339d8e56b21744fd83c8212a9d8baf28767343' }}" - run: | - set -euo pipefail - tmp_dir="$(mktemp -d)" - curl -fsSL --connect-timeout 15 --max-time 300 "$OSSUTIL_URL" -o "$tmp_dir/ossutil.zip" - echo "$OSSUTIL_SHA256 $tmp_dir/ossutil.zip" | sha256sum -c - - unzip -q "$tmp_dir/ossutil.zip" -d "$tmp_dir" - ossutil_path="$(find "$tmp_dir" -type f \( -name 'ossutil' -o -name 'ossutil64' \) -print -quit)" - if [[ -z "$ossutil_path" ]]; then echo '::error::ossutil binary not found'; exit 1; fi - chmod +x "$ossutil_path" - mkdir -p "$HOME/.local/bin" - install -m 0755 "$ossutil_path" "$HOME/.local/bin/ossutil" - echo "$HOME/.local/bin" >> "$GITHUB_PATH" - rm -rf "$tmp_dir" - "$HOME/.local/bin/ossutil" >/dev/null - - - name: 'Configure Aliyun OSS credentials' - env: - ALIYUN_OSS_ACCESS_KEY_ID: '${{ secrets.ALIYUN_OSS_ACCESS_KEY_ID }}' - ALIYUN_OSS_ACCESS_KEY_SECRET: '${{ secrets.ALIYUN_OSS_ACCESS_KEY_SECRET }}' - ALIYUN_OSS_ENDPOINT: "${{ vars.ALIYUN_OSS_ENDPOINT || 'https://oss-cn-hangzhou.aliyuncs.com' }}" - run: | - set -euo pipefail - if [[ -z "$ALIYUN_OSS_ACCESS_KEY_ID" || -z "$ALIYUN_OSS_ACCESS_KEY_SECRET" ]]; then - echo '::error::Missing Aliyun OSS credentials in the production-release environment.' - exit 1 - fi - ossutil config -e "$ALIYUN_OSS_ENDPOINT" -i "$ALIYUN_OSS_ACCESS_KEY_ID" -k "$ALIYUN_OSS_ACCESS_KEY_SECRET" -L EN -c "$RUNNER_TEMP/.ossutilconfig" - - - name: 'Upload versioned assets to Aliyun OSS' - env: - ALIYUN_OSS_BUCKET: "${{ vars.ALIYUN_OSS_BUCKET || 'qwen-code-assets' }}" - VERSION: '${{ steps.release.outputs.version }}' - run: | - set -euo pipefail - node scripts/upload-aliyun-oss-assets.js \ - --bucket "$ALIYUN_OSS_BUCKET" \ - --config "$RUNNER_TEMP/.ossutilconfig" \ - --prefix "live-host/v${VERSION}" \ - dist/live-host/Qwen-Live-Host-arm64.zip \ - dist/live-host/Qwen-Live-Host-x64.zip \ - dist/live-host/Qwen-Live-Host-manifest.json - - - name: 'Verify versioned assets on Aliyun OSS' - env: - ALIYUN_OSS_PUBLIC_BASE_URL: "${{ vars.ALIYUN_OSS_PUBLIC_BASE_URL || 'https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com' }}" - VERSION: '${{ steps.release.outputs.version }}' - run: | - set -euo pipefail - base="$ALIYUN_OSS_PUBLIC_BASE_URL/live-host/v${VERSION}" - directory="$(mktemp -d)" - trap 'rm -rf "$directory"' EXIT - for asset in Qwen-Live-Host-manifest.json Qwen-Live-Host-arm64.zip Qwen-Live-Host-x64.zip; do - curl -fsSL --connect-timeout 15 --max-time 3600 "$base/$asset" -o "$directory/$asset" - done - # shellcheck disable=SC2016 - VERSION="$VERSION" DIRECTORY="$directory" node --input-type=module -e ' - import { createHash } from "node:crypto"; - import { readFileSync, statSync } from "node:fs"; - const manifest = JSON.parse(readFileSync(`${process.env.DIRECTORY}/Qwen-Live-Host-manifest.json`, "utf8")); - if (manifest.version !== process.env.VERSION) throw new Error("Mirrored manifest version mismatch."); - for (const architecture of ["arm64", "x64"]) { - const name = `Qwen-Live-Host-${architecture}.zip`; - const file = `${process.env.DIRECTORY}/${name}`; - const asset = manifest.assets?.[architecture]; - const checksum = createHash("sha256").update(readFileSync(file)).digest("hex"); - if (asset?.name !== name || asset.size !== statSync(file).size || asset.sha256 !== checksum) throw new Error(`Mirrored asset verification failed for ${architecture}.`); - } - ' - - - name: 'Confirm latest manifest matches GitHub stable feed' - env: - GH_TOKEN: '${{ github.token }}' - run: | - set -euo pipefail - directory="$(mktemp -d)" - trap 'rm -rf "$directory"' EXIT - gh release download 'live-host-latest' \ - --dir "$directory" \ - --pattern 'Qwen-Live-Host-manifest.json' - cmp dist/live-host/Qwen-Live-Host-manifest.json \ - "$directory/Qwen-Live-Host-manifest.json" - - - name: 'Publish latest manifest to Aliyun OSS' - env: - ALIYUN_OSS_BUCKET: "${{ vars.ALIYUN_OSS_BUCKET || 'qwen-code-assets' }}" - run: | - node scripts/upload-aliyun-oss-assets.js \ - --bucket "$ALIYUN_OSS_BUCKET" \ - --config "$RUNNER_TEMP/.ossutilconfig" \ - --prefix 'live-host/latest' \ - dist/live-host/Qwen-Live-Host-manifest.json - - - name: 'Verify latest manifest on Aliyun OSS' - env: - ALIYUN_OSS_PUBLIC_BASE_URL: "${{ vars.ALIYUN_OSS_PUBLIC_BASE_URL || 'https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com' }}" - run: | - set -euo pipefail - curl -fsSL --connect-timeout 15 --max-time 300 "$ALIYUN_OSS_PUBLIC_BASE_URL/live-host/latest/Qwen-Live-Host-manifest.json" -o "$RUNNER_TEMP/Qwen-Live-Host-manifest.json" - cmp dist/live-host/Qwen-Live-Host-manifest.json "$RUNNER_TEMP/Qwen-Live-Host-manifest.json" - - - name: 'Cleanup Aliyun OSS credentials' - if: '${{ always() }}' - run: 'rm -f "$RUNNER_TEMP/.ossutilconfig"' diff --git a/.prettierignore b/.prettierignore index 03f5711300b..bb330c55794 100644 --- a/.prettierignore +++ b/.prettierignore @@ -23,4 +23,3 @@ Thumbs.db packages/vscode-ide-companion/schemas/settings.schema.json packages/cli/src/services/insight/templates/insightTemplate.ts packages/cua-driver/ -packages/desktop/ diff --git a/.qwen/skills/desktop-pet/scripts/gen_spritesheet.py b/.qwen/skills/desktop-pet/scripts/gen_spritesheet.py index ede60f07d37..e9efb7602f8 100644 --- a/.qwen/skills/desktop-pet/scripts/gen_spritesheet.py +++ b/.qwen/skills/desktop-pet/scripts/gen_spritesheet.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """ -Desktop Pet Spritesheet Generator for OpenWork. +Desktop Pet Spritesheet Generator for Qwen Code. Generates a 1536×1872 pixel-art chibi spritesheet (8 cols × 9 rows, 192×208 px cells) customized via a JSON config describing colors, headgear, and features. diff --git a/.qwen/skills/openwork-desktop-sync/SKILL.md b/.qwen/skills/openwork-desktop-sync/SKILL.md deleted file mode 100644 index 51ae9dbe4a3..00000000000 --- a/.qwen/skills/openwork-desktop-sync/SKILL.md +++ /dev/null @@ -1,102 +0,0 @@ ---- -name: openwork-desktop-sync -description: Sync qwen-code packages/desktop with modelstudioai/openwork using commit-by-commit path migration, not subtree split or tree overwrite. Use when exporting qwen-code desktop changes to OpenWork, importing OpenWork desktop changes into qwen-code, preserving target-owned overlay files such as README.md, resolving sync conflicts, or preparing sync PR branches between the two repositories. ---- - -# OpenWork Desktop Sync - -Use this skill to sync desktop changes between this qwen-code repo and an -OpenWork checkout. The repository script owns the Git mechanics: - -```bash -OPENWORK_DIR=/path/to/openwork bun run desktop-openwork-sync --mode export -``` - -Default overlay is `README.md`. Overlay paths are excluded from migrated -commits and stay target-owned. - -```bash -OPENWORK_OVERLAY_PATHS='README.md' -``` - -## Contract - -This is commit-by-commit path migration, not snapshot replacement. The script -walks source commits from `source-base..source-head`, rewrites paths between -qwen-code `packages/desktop` and the OpenWork repository root, then applies each -commit with `git apply -3`. - -Commits that already came from the receiving repository are skipped by their -sync trailers. During import, qwen-code-origin export commits are skipped; -during export, OpenWork-origin import commits are skipped. - -Merge commits are not migrated as merge commits. The script migrates the regular -commits inside the merged branch; when it later sees the merge wrapper, it -checks that the regular commits were already handled and that the merge tree -matches Git's automatic merge result. If the merge wrapper contains manual -resolution changes, the sync stops so the agent can convert that resolution into -a normal follow-up commit. - -Target-side changes are preserved unless a migrated source commit touches the -same hunk. If that happens, Git leaves a normal conflict for the agent to -resolve. Do not use `git subtree split` or full tree replacement for normal -sync. - -Successful sync commits include trailers such as `Qwen-Code-Commit` or -`OpenWork-Commit`. Later syncs can use the latest trailer as the next source -base. The first sync needs an explicit source base when no previous sync trailer -exists: - -```bash -bun run desktop-openwork-sync --mode export --source-base -bun run desktop-openwork-sync --mode import --source-base -``` - -## Modes - -- `--mode export`: qwen-code `packages/desktop` commits -> OpenWork. -- `--mode import`: OpenWork commits -> qwen-code `packages/desktop`. -- `--mode auto`: guardrail only; use explicit directions for real sync. - -## Workflow - -1. Confirm repo paths and clean worktrees: - - ```bash - git rev-parse --show-toplevel - git -C /path/to/openwork rev-parse --show-toplevel - git status --short - git -C /path/to/openwork status --short - ``` - -2. Run the requested direction: - - ```bash - OPENWORK_DIR=/path/to/openwork \ - OPENWORK_OVERLAY_PATHS='README.md' \ - bun run desktop-openwork-sync --mode export --source-base - ``` - -3. If Git reports conflicts, resolve only the conflicted hunks, preserving - target-owned repository metadata unless the source change intentionally - updates that same behavior. - -4. After sync, verify: - - ```bash - git status --short - git diff --check HEAD - git diff --name-status ..HEAD - ``` - -5. If the user asked to publish, push the branch and create a PR after the - branch is clean. - -## Rules - -- Keep only `README.md` as the default overlay unless the user adds paths to - `OPENWORK_OVERLAY_PATHS`. -- OpenWork-specific files not touched by source commits must remain unchanged. -- Prefer PR branches. The script prints the push command for export branches. -- Do not manually import PR merge commits. Let the script migrate regular - commits and treat merge commits as wrappers. diff --git a/.yamllint.yml b/.yamllint.yml index b01f2c813b2..a98b6dbba8f 100644 --- a/.yamllint.yml +++ b/.yamllint.yml @@ -88,5 +88,3 @@ ignore: - 'vendor/' - 'node_modules/' - 'integration-tests/terminal-bench/' - - 'packages/desktop/.github/' - - 'packages/desktop/apps/electron/electron-builder.yml' diff --git a/docs/developers/architecture.md b/docs/developers/architecture.md index 36c565a5a8d..f3ba6ee0be6 100644 --- a/docs/developers/architecture.md +++ b/docs/developers/architecture.md @@ -77,23 +77,23 @@ an HTTP daemon. See the ## Repository layout -| Path | Responsibility | -| ---------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `packages/cli` | The `qwen` executable, argument parsing, configuration assembly, Ink TUI, headless output, ACP entry point, `qwen serve`, and command-specific adapters. | -| `packages/core` | UI-independent agent orchestration, model-provider integration, prompt and context construction, tool registration and execution, permissions, sessions, memory, telemetry, and shared services. | -| `packages/acp-bridge` | ACP channel lifecycle, session multiplexing, event delivery, permission mediation, process spawning, and the filesystem seam shared by daemon and adapter hosts. | -| `packages/sdk-typescript` | Programmatic process execution through `query()` plus HTTP/SSE clients and transcript projection for `qwen serve`. | -| `packages/webui` | Shared React components and the daemon React adapter built on the TypeScript SDK. | -| `packages/web-shell` | The terminal-style browser UI built on `packages/webui` and the daemon SDK. | -| `packages/web-templates` | Web templates packaged as embeddable JavaScript and CSS strings. | -| `packages/audio-capture` | Native microphone capture for voice input. | -| `packages/channels` | The shared channel runtime and platform adapters for messaging services. | -| `packages/desktop`, `packages/vscode-ide-companion`, `packages/chrome-extension`, `packages/zed-extension` | Product and editor surfaces that adapt Qwen Code to their host environments. | -| `packages/sdk-java`, `packages/sdk-python` | Language-specific programmatic clients. | -| `packages/cua-driver`, `packages/mobile-mcp` | Computer-use and mobile-device integrations exposed through MCP-compatible boundaries. | -| `integration-tests` | End-to-end coverage for CLI, interactive, SDK, sandbox, hook, and terminal behavior. | -| `docs` and `docs-site` | User, developer, protocol, and design documentation plus the documentation site. | -| `scripts` | Build, packaging, release, validation, and repository-maintenance automation. | +| Path | Responsibility | +| ---------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `packages/cli` | The `qwen` executable, argument parsing, configuration assembly, Ink TUI, headless output, ACP entry point, `qwen serve`, and command-specific adapters. | +| `packages/core` | UI-independent agent orchestration, model-provider integration, prompt and context construction, tool registration and execution, permissions, sessions, memory, telemetry, and shared services. | +| `packages/acp-bridge` | ACP channel lifecycle, session multiplexing, event delivery, permission mediation, process spawning, and the filesystem seam shared by daemon and adapter hosts. | +| `packages/sdk-typescript` | Programmatic process execution through `query()` plus HTTP/SSE clients and transcript projection for `qwen serve`. | +| `packages/webui` | Shared React components and the daemon React adapter built on the TypeScript SDK. | +| `packages/web-shell` | The terminal-style browser UI built on `packages/webui` and the daemon SDK. | +| `packages/web-templates` | Web templates packaged as embeddable JavaScript and CSS strings. | +| `packages/audio-capture` | Native microphone capture for voice input. | +| `packages/channels` | The shared channel runtime and platform adapters for messaging services. | +| `packages/desktop-shell`, `packages/vscode-ide-companion`, `packages/chrome-extension`, `packages/zed-extension` | Product and editor surfaces that adapt Qwen Code to their host environments. | +| `packages/sdk-java`, `packages/sdk-python` | Language-specific programmatic clients. | +| `packages/cua-driver`, `packages/mobile-mcp` | Computer-use and mobile-device integrations exposed through MCP-compatible boundaries. | +| `integration-tests` | End-to-end coverage for CLI, interactive, SDK, sandbox, hook, and terminal behavior. | +| `docs` and `docs-site` | User, developer, protocol, and design documentation plus the documentation site. | +| `scripts` | Build, packaging, release, validation, and repository-maintenance automation. | Most code lives in npm workspaces under `packages/`. A package should depend on another package through its declared public exports rather than through a diff --git a/eslint.config.js b/eslint.config.js index 3d45b97157d..c4643163260 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -51,7 +51,6 @@ export default tseslint.config( 'docs-site/.next/**', 'docs-site/out/**', '.qwen/**', - 'packages/desktop/**', 'packages/desktop-shell/runtime/**', 'packages/desktop-shell/src-tauri/target/**', 'packages/cua-driver/**', // vendored trycua/cua driver (Rust + scripts); not qwen-code TS diff --git a/integration-tests/tsconfig.json b/integration-tests/tsconfig.json index b0c6caded8e..514c02518a7 100644 --- a/integration-tests/tsconfig.json +++ b/integration-tests/tsconfig.json @@ -11,7 +11,7 @@ "composite": false, // The root turns `noPropertyAccessFromIndexSignature` on, which forced // bracket-access rewrites in production SDK sources just to satisfy this - // test program (packages/desktop already sets it false). Relax it here so + // test program. Relax it here so // packages keep their own compiler regime and the tests keep dot access. "noPropertyAccessFromIndexSignature": false, // Matches packages/cli. The suite drives browser-side code in diff --git a/package-lock.json b/package-lock.json index ea8b7f61cef..b34a4bf984f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,6 @@ "packages/channels/gitlab", "packages/channels/plugin-example", "integrations/external-context", - "!packages/desktop", "!packages/desktop-shell" ], "dependencies": { diff --git a/package.json b/package.json index 3798dff83c4..ddfded0d8c8 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,6 @@ "packages/channels/gitlab", "packages/channels/plugin-example", "integrations/external-context", - "!packages/desktop", "!packages/desktop-shell" ], "repository": { @@ -90,9 +89,7 @@ "telemetry": "node scripts/telemetry.js", "check:lockfile": "node scripts/check-lockfile.js", "check:desktop-isolation": "node scripts/check-desktop-isolation.js", - "check:voice-guard-sync": "node scripts/check-voice-guard-sync.js", "check:serve-fast-path-bundle": "node scripts/clean-package-build-artifacts.js && npm run build -- --cli-only && cross-env DEV=true npm run bundle && node scripts/check-serve-fast-path-bundle.js", - "desktop-openwork-sync": "bun run scripts/desktop-openwork-sync.ts", "clean": "node scripts/clean.js", "pre-commit": "node scripts/pre-commit.js" }, diff --git a/packages/cli/src/commands/review/build-test.test.ts b/packages/cli/src/commands/review/build-test.test.ts index 8299a633a34..b29224d139d 100644 --- a/packages/cli/src/commands/review/build-test.test.ts +++ b/packages/cli/src/commands/review/build-test.test.ts @@ -2045,7 +2045,7 @@ describe('runBuildTest', () => { }); it('discloses a diff inside a negated member — softly, never as an incomplete scope', () => { - // packages/desktop is a separate toolchain (its own lockfile); a diff + // packages/desktop-shell is a separate toolchain (its own lockfile); a diff // inside it cannot fail any npm workspace's suite, so "nothing to run" // stays the answer — disclosed softly (its own suite did not run), never // as an incomplete scope. @@ -2053,7 +2053,7 @@ describe('runBuildTest', () => { join(root, 'package.json'), JSON.stringify({ name: 'r', - workspaces: ['packages/*', '!packages/desktop'], + workspaces: ['packages/*', '!packages/desktop-shell'], scripts: { test: 'exit 0' }, }), ); @@ -2061,11 +2061,11 @@ describe('runBuildTest', () => { name: '@x/core', scripts: { build: 'exit 0', test: 'exit 0' }, }); - pkg('packages/desktop', { + pkg('packages/desktop-shell', { name: '@x/desktop', scripts: { build: 'exit 0', test: 'exit 0' }, }); - writePlan(['packages/desktop/src/main.rs']); + writePlan(['packages/desktop-shell/src/main.rs']); const rep = runBuildTest({ plan: planPath, @@ -2077,7 +2077,9 @@ describe('runBuildTest', () => { expect(rep.build).toEqual([]); expect(rep.test).toEqual([]); expect(rep.testScope?.workspaces).toEqual([]); - expect(rep.testScope?.caveat).toContain('packages/desktop/src/main.rs'); + expect(rep.testScope?.caveat).toContain( + 'packages/desktop-shell/src/main.rs', + ); expect(rep.testScope?.caveat).toContain('were not run'); expect(rep.note).toContain('were not run'); }); @@ -2321,7 +2323,7 @@ describe('runBuildTest', () => { it('excludes a negated workspace from the build set (integration)', () => { // `!packages/excluded` must keep that package out — building it could fail on a - // repo where it is a separate toolchain (e.g. packages/desktop, its own lockfile). + // repo where it is a separate toolchain (e.g. packages/desktop-shell, its own lockfile). writeFileSync( join(root, 'package.json'), JSON.stringify({ diff --git a/packages/cli/src/commands/review/lib/diff-plan.test.ts b/packages/cli/src/commands/review/lib/diff-plan.test.ts index d9ac06ba7b4..2ed0e27a9ea 100644 --- a/packages/cli/src/commands/review/lib/diff-plan.test.ts +++ b/packages/cli/src/commands/review/lib/diff-plan.test.ts @@ -56,7 +56,7 @@ describe('classifyPath', () => { it('recognises generated and vendored files', () => { const paths = [ 'package-lock.json', - 'packages/desktop/bun.lock', + 'packages/desktop-shell/bun.lock', 'packages/vscode-ide-companion/NOTICES.txt', 'dist/bundle.min.js', 'vendor/lib.go', diff --git a/packages/cli/src/commands/review/lib/workspace-scope.test.ts b/packages/cli/src/commands/review/lib/workspace-scope.test.ts index 3db9c278f36..f0c06a17ad0 100644 --- a/packages/cli/src/commands/review/lib/workspace-scope.test.ts +++ b/packages/cli/src/commands/review/lib/workspace-scope.test.ts @@ -136,18 +136,18 @@ describe('resolveTestScope', () => { }); it('discloses — softly, not as incompleteness — a member a negation excludes', () => { - // !packages/desktop is a separate toolchain with its own lockfile; a diff + // !packages/desktop-shell is a separate toolchain with its own lockfile; a diff // inside it cannot fail any included workspace's suite, so it earns no // incomplete-scope caveat. But its own suite was not run either, and // "nothing is silent" covers that: a softer line says what did not run. const scope = resolveTestScope({ - changed: ['packages/desktop/src/main.rs'], - globs: ['packages/*', '!packages/desktop'], + changed: ['packages/desktop-shell/src/main.rs'], + globs: ['packages/*', '!packages/desktop-shell'], packages: PKGS, skipped: [], }); expect(scope.workspaces).toEqual([]); - expect(scope.caveat).toContain('packages/desktop/src/main.rs'); + expect(scope.caveat).toContain('packages/desktop-shell/src/main.rs'); expect(scope.caveat).toContain('were not run'); expect(scope.caveat).not.toContain('outside every workspace'); }); diff --git a/packages/cli/src/commands/review/lib/workspace-scope.ts b/packages/cli/src/commands/review/lib/workspace-scope.ts index d277ef88536..40bc5e8e651 100644 --- a/packages/cli/src/commands/review/lib/workspace-scope.ts +++ b/packages/cli/src/commands/review/lib/workspace-scope.ts @@ -196,7 +196,7 @@ export function resolveTestScope(input: { const unmapped = affected.filter( (d) => d !== '.' && !scriptsOf.has(d) && !skipped.includes(d), ); - // Files a negation excludes (!packages/desktop — a separate toolchain with + // Files a negation excludes (!packages/desktop-shell — a separate toolchain with // its own lockfile) cannot affect any included workspace's tests, so they // earn no incomplete-scope caveat — but their own suites were not run // either, and "nothing is silent" covers that too: disclose it as the diff --git a/packages/cli/src/commands/review/lib/workspaces.test.ts b/packages/cli/src/commands/review/lib/workspaces.test.ts index e07a7963cce..f60624b834e 100644 --- a/packages/cli/src/commands/review/lib/workspaces.test.ts +++ b/packages/cli/src/commands/review/lib/workspaces.test.ts @@ -35,7 +35,7 @@ const GLOBS = [ 'packages/channels/base', 'packages/channels/telegram', 'packages/channels/qqbot', - '!packages/desktop', + '!packages/desktop-shell', ]; describe('workspaceDirFor', () => { @@ -54,37 +54,48 @@ describe('workspaceDirFor', () => { ); }); - it('honours a negation, so a separate bun workspace is not a member', () => { - // packages/desktop has its own lockfile and is not part of this npm workspace. + it('honours a negation, so a separate toolchain is not a member', () => { + // packages/desktop-shell has its own lockfile and is not part of this npm workspace. // Building it from the root fails. expect( - workspaceDirFor('packages/desktop/apps/electron/src/main.ts', GLOBS), + workspaceDirFor( + 'packages/desktop-shell/apps/electron/src/main.ts', + GLOBS, + ), ).toBeNull(); - expect(isWorkspaceMember('packages/desktop/src/a.test.ts', GLOBS)).toBe( - false, - ); + expect( + isWorkspaceMember('packages/desktop-shell/src/a.test.ts', GLOBS), + ).toBe(false); }); it('re-includes what a negation excluded when a later glob matches again', () => { // npm's own rule: last match wins, whichever direction it points. - const globs = ['packages/*', '!packages/desktop', 'packages/desktop']; - expect(workspaceDirFor('packages/desktop/src/a.ts', globs)).toBe( - 'packages/desktop', + const globs = [ + 'packages/*', + '!packages/desktop-shell', + 'packages/desktop-shell', + ]; + expect(workspaceDirFor('packages/desktop-shell/src/a.ts', globs)).toBe( + 'packages/desktop-shell', ); }); it('falls back to the surviving OUTER member when a negation excludes a nested one', () => { - // npm keeps packages/desktop in the graph — only src is excluded — and + // npm keeps packages/desktop-shell in the graph — only src is excluded — and // desktop's test runner collects src/**, so the file is felt by the outer // member's suite. Declaring it felt by NOTHING would certify "a complete // answer" over a suite that can fail. - const globs = ['packages/*', 'packages/desktop/*', '!packages/desktop/src']; - expect(workspaceDirFor('packages/desktop/src/x.test.ts', globs)).toBe( - 'packages/desktop', - ); - expect(isNegationExcluded('packages/desktop/src/x.test.ts', globs)).toBe( - false, + const globs = [ + 'packages/*', + 'packages/desktop-shell/*', + '!packages/desktop-shell/src', + ]; + expect(workspaceDirFor('packages/desktop-shell/src/x.test.ts', globs)).toBe( + 'packages/desktop-shell', ); + expect( + isNegationExcluded('packages/desktop-shell/src/x.test.ts', globs), + ).toBe(false); }); it('treats a ./-prefixed glob like its bare form', () => { @@ -201,9 +212,9 @@ describe('readRootPackage', () => { describe('isNegationExcluded', () => { it('is true when a positive glob claims the file but a negation excludes it', () => { - expect(isNegationExcluded('packages/desktop/src/main.rs', GLOBS)).toBe( - true, - ); + expect( + isNegationExcluded('packages/desktop-shell/src/main.rs', GLOBS), + ).toBe(true); }); it('is false for a file inside an included workspace', () => { @@ -216,21 +227,27 @@ describe('isNegationExcluded', () => { }); it('is false when a later glob re-includes what the negation excluded', () => { - const globs = ['packages/*', '!packages/desktop', 'packages/desktop']; - expect(isNegationExcluded('packages/desktop/src/a.ts', globs)).toBe(false); + const globs = [ + 'packages/*', + '!packages/desktop-shell', + 'packages/desktop-shell', + ]; + expect(isNegationExcluded('packages/desktop-shell/src/a.ts', globs)).toBe( + false, + ); }); - it('keeps a member owned under a partial negation (`!packages/desktop/*`)', () => { - // npm keeps packages/desktop itself a member — a glob with a subpath + it('keeps a member owned under a partial negation (`!packages/desktop-shell/*`)', () => { + // npm keeps packages/desktop-shell itself a member — a glob with a subpath // cannot match the dir itself — so a file under it is still owned and its // suite can feel a change there; it is NOT negation-excluded. - const globs = ['packages/*', '!packages/desktop/*']; - expect(workspaceDirFor('packages/desktop/src/main.ts', globs)).toBe( - 'packages/desktop', - ); - expect(isNegationExcluded('packages/desktop/src/main.ts', globs)).toBe( - false, + const globs = ['packages/*', '!packages/desktop-shell/*']; + expect(workspaceDirFor('packages/desktop-shell/src/main.ts', globs)).toBe( + 'packages/desktop-shell', ); + expect( + isNegationExcluded('packages/desktop-shell/src/main.ts', globs), + ).toBe(false); }); }); @@ -334,9 +351,9 @@ describe('readWorkspacePackages', () => { }); it('ignores a broken manifest in a NEGATED dir — not a workspace, not our graph', () => { - setup(['packages/*', '!packages/desktop']); + setup(['packages/*', '!packages/desktop-shell']); write('packages/good', { name: '@x/good' }); - write('packages/desktop', '{ not json'); + write('packages/desktop-shell', '{ not json'); const { packages, skipped } = readWorkspacePackages(root); expect(packages.map((p) => p.dir)).toEqual(['packages/good']); expect(skipped).toEqual([]); @@ -442,7 +459,7 @@ describe('hasUnmodeledWorkspaceGlob', () => { it('is false for the shapes the walker models — literals and a trailing /*', () => { expect(hasUnmodeledWorkspaceGlob(GLOBS)).toBe(false); expect(hasUnmodeledWorkspaceGlob(['packages/*', 'apps/web'])).toBe(false); - expect(hasUnmodeledWorkspaceGlob(['!packages/desktop'])).toBe(false); + expect(hasUnmodeledWorkspaceGlob(['!packages/desktop-shell'])).toBe(false); }); it('is true for `**`, an inner `*`, or a `foo-*` prefix the walker cannot model', () => { diff --git a/packages/cli/src/commands/review/lib/workspaces.ts b/packages/cli/src/commands/review/lib/workspaces.ts index 6975d1fe89a..49b2c9224bf 100644 --- a/packages/cli/src/commands/review/lib/workspaces.ts +++ b/packages/cli/src/commands/review/lib/workspaces.ts @@ -79,10 +79,10 @@ export function workspaceDirFor( const norm = filePath.replace(/^\.\//, ''); let owner: string | null = null; // The owners that came before the current one — where a negation falls back - // TO. When a negation excludes a NESTED member (`packages/desktop/*` claimed - // `packages/desktop/src`, then `!packages/desktop/src` excluded it), the + // TO. When a negation excludes a NESTED member (`packages/desktop-shell/*` claimed + // `packages/desktop-shell/src`, then `!packages/desktop-shell/src` excluded it), the // still-included outer member keeps owning the file: npm keeps - // `packages/desktop` in the graph, and its test runner collects `src/**`. + // `packages/desktop-shell` in the graph, and its test runner collects `src/**`. // Falling back to the previous owner is what lets that suite feel the // change instead of the file being declared felt by nothing. const previous: Array = []; @@ -111,15 +111,15 @@ export function workspaceDirFor( } } else if (dir === owner) { // A negation only excludes the file when it excludes the member that - // currently owns it. `!packages/desktop/*` matches a deeper pseudo-dir + // currently owns it. `!packages/desktop-shell/*` matches a deeper pseudo-dir // than `packages/*` does, and npm keeps the member itself in the graph // (a glob with a subpath cannot match a dir with no subpath), so the // member's suite can still feel a change there. When the negation DOES // exclude the owner, ownership falls back to the previous, outer member // — only a negation of THAT one leaves the file owned by nothing. (The // pop does not re-check the popped owner against negations already - // walked past: a contrived ordering like `!packages/desktop` BEFORE - // `!packages/desktop/src` can resurrect an excluded owner. Realistic + // walked past: a contrived ordering like `!packages/desktop-shell` BEFORE + // `!packages/desktop-shell/src` can resurrect an excluded owner. Realistic // orderings — the outer negation written last — are exact.) owner = previous.pop() ?? null; } @@ -132,11 +132,11 @@ export function workspaceDirFor( * every member. * * Such a file belongs to a workspace the npm graph does not contain — this - * repo's `!packages/desktop` is a separate bun workspace with its own + * repo's `!packages/desktop-shell` is a separate toolchain with its own * lockfile — so no included workspace's tests can feel a change to it, and it * must not earn the incomplete-scope caveat a genuinely outside file does. A * file whose nested member is negated while an OUTER member survives - * (`!packages/desktop/src` under `packages/desktop`) is NOT excluded here: + * (`!packages/desktop-shell/src` under `packages/desktop-shell`) is NOT excluded here: * `workspaceDirFor` falls back to the outer member, whose suite collects it. */ export function isNegationExcluded( @@ -351,8 +351,8 @@ export function readWorkspacePackages(root: string): WorkspaceGraph { if (!existsSync(manifest)) continue; if (workspaceDirFor(`${dir}/package.json`, globs) !== dir) { // A directory a negation excludes is not a workspace, and its own - // `package.json` says nothing about that — `packages/desktop` is a - // separate bun workspace with its own lockfile, and building it from + // `package.json` says nothing about that — `packages/desktop-shell` is a + // separate toolchain with its own lockfile, and building it from // here fails. The tell: the POSITIVE globs alone still make the dir its // own owner, so a negation is what took the ownership away. const positives = globs.filter((g) => !g.startsWith('!')); diff --git a/packages/cli/src/commands/review/test-efficacy.test.ts b/packages/cli/src/commands/review/test-efficacy.test.ts index 67d85373be2..b7f1772b01e 100644 --- a/packages/cli/src/commands/review/test-efficacy.test.ts +++ b/packages/cli/src/commands/review/test-efficacy.test.ts @@ -51,7 +51,7 @@ const GLOBS = [ 'packages/*', 'packages/channels/base', 'packages/channels/telegram', - '!packages/desktop', + '!packages/desktop-shell', ]; describe('isWorkspaceMember', () => { @@ -77,22 +77,30 @@ describe('isWorkspaceMember', () => { }); it('honours a negated glob', () => { - expect(isWorkspaceMember('packages/desktop/src/a.test.ts', GLOBS)).toBe( - false, - ); + expect( + isWorkspaceMember('packages/desktop-shell/src/a.test.ts', GLOBS), + ).toBe(false); }); it('honours workspace-glob ORDER — a positive after a negation re-includes', () => { // npm evaluates the list in order. Filtering all negations first let a // negation win wherever it sat, which would file a false `unreachable`. - const globs = ['packages/*', '!packages/desktop', 'packages/desktop']; - expect(isWorkspaceMember('packages/desktop/src/a.test.ts', globs)).toBe( - true, - ); - const reordered = ['packages/*', 'packages/desktop', '!packages/desktop']; - expect(isWorkspaceMember('packages/desktop/src/a.test.ts', reordered)).toBe( - false, - ); + const globs = [ + 'packages/*', + '!packages/desktop-shell', + 'packages/desktop-shell', + ]; + expect( + isWorkspaceMember('packages/desktop-shell/src/a.test.ts', globs), + ).toBe(true); + const reordered = [ + 'packages/*', + 'packages/desktop-shell', + '!packages/desktop-shell', + ]; + expect( + isWorkspaceMember('packages/desktop-shell/src/a.test.ts', reordered), + ).toBe(false); }); it('does not match a sibling directory by prefix', () => { diff --git a/packages/cli/src/services/voice-transcriber.ts b/packages/cli/src/services/voice-transcriber.ts index 6996397f29e..74ba38713b6 100644 --- a/packages/cli/src/services/voice-transcriber.ts +++ b/packages/cli/src/services/voice-transcriber.ts @@ -24,9 +24,6 @@ const MIN_KEYTERM_ECHO_TOKENS = 8; const MIN_ABSOLUTE_KEYTERM_ECHO_TOKENS = 10; const MIN_KEYTERM_SET_ECHO_RATIO = 0.3; const debugLogger = createDebugLogger('VOICE_TRANSCRIBER'); -// The address classification in this file is mirrored in -// packages/desktop/packages/server-core/src/voice/net-guard.ts. The bun -// workspace boundary prevents sharing a module; keep the two in sync. const BLOCKED_TRANSITION_IPV6_ADDRESSES = new BlockList(); for (const [address, prefix] of [ ['64:ff9b:1::', 48], diff --git a/packages/core/src/tools/agent/agent.ts b/packages/core/src/tools/agent/agent.ts index d00a4bdaf2e..1fa436f2c3f 100644 --- a/packages/core/src/tools/agent/agent.ts +++ b/packages/core/src/tools/agent/agent.ts @@ -2695,13 +2695,11 @@ class AgentToolInvocation extends BaseToolInvocation { // agent-level background flag retains its existing meaning, and safe // ordinary one-shot launches default to background. // - // This is the source of truth for the background-classification rule. Two - // UI classifiers replicate it from tool-call args (they cannot see + // This is the source of truth for the background-classification rule. The + // web-shell classifier replicates it from tool-call args (it cannot see // subagentConfig.background) and must be kept in sync when it changes: // - packages/web-shell/client/adapters/toolClassification.ts // (isBackgroundSubAgentToolCall) - // - packages/desktop/packages/shared/src/agent/tool-matching.ts - // (detectBackgroundEvents) // // Background delegation is top-level-only in v1. A nested launcher would // be handed a completion contract it cannot honor — the success guidance diff --git a/packages/desktop/.agents/skills/desktop-brand-builder/SKILL.md b/packages/desktop/.agents/skills/desktop-brand-builder/SKILL.md deleted file mode 100644 index c22d0230506..00000000000 --- a/packages/desktop/.agents/skills/desktop-brand-builder/SKILL.md +++ /dev/null @@ -1,151 +0,0 @@ ---- -name: desktop-brand-builder -description: Generate a branded qwen-code desktop package from a minimal brandId and logo. Use when the user wants a custom, white-label, rebranded, ModelStudio/OpenWork/Qwen Code desktop client, installer, DMG/EXE/AppImage, or one-click brand build. ---- - -# Desktop Brand Builder - -## Goal - -Create a branded desktop package with the least user input possible. The user -should usually provide only: - -```text -brandId: acme-ai -logo: /absolute/path/to/logo.png -website: https://acme.ai -``` - -`website` is optional. Do not ask for app name, app id, artifact name, -copyright, dock icon, renderer symbol, signing, or local installation unless -the user explicitly asks to override them. - -## Input Rules - -Required fields: - -- `brandId`: must match `^[a-z][a-z0-9-]*$` -- `logo`: local file path; must exist - -Optional overrides: - -- `website` -- `appName` -- `appId` -- `artifactPrefix` -- `target`: `mac`, `win`, `linux`, or `all` - -If required input is missing, ask once: - -```text -请提供: -brandId: 例如 acme-ai,只能小写字母、数字、短横线 -logo: 本地 logo 文件路径 -website: 可选 -``` - -Once the required fields are present, proceed without a confirmation step. - -## Derived Defaults - -Infer missing values deterministically: - -- `appName`: title-case the hyphen-separated `brandId`; `acme-ai` becomes - `Acme AI` -- `artifactPrefix`: title-case the hyphen-separated `brandId` and join with - hyphens; `acme-ai` becomes `Acme-AI` -- `appId`: if `website` has a valid host, reverse the host labels and append - `.desktop`; `https://acme.ai` becomes `ai.acme.desktop` -- fallback `appId`: `app..desktop` -- `copyright`: `Copyright © ` -- all brand images: generate icon, dock icon, and renderer symbol from `logo` - -Use explicit user-provided override values as-is after basic validation. - -## Build Workflow - -Use an isolated build directory under the current working directory so user -changes in the current worktree are not mutated. Default to the qwen-code main -branch; do not clone from `craft-agents-oss`, OpenWork, or another local -checkout unless the user explicitly asks for that source: - -```bash -BUILD_ROOT="$PWD/brand-builds/-" -mkdir -p "$BUILD_ROOT" -git clone --branch main --single-branch \ - https://github.com/QwenLM/qwen-code.git \ - "$BUILD_ROOT/qwen-code" -cd "$BUILD_ROOT/qwen-code" -git checkout -B brand- origin/main -``` - -If the branch fetch or checkout fails, stop and report the failure. Do not -continue as if `brand-` was created. - -Create a temporary `brand.json` in the build directory: - -```json -{ - "brandId": "acme-ai", - "logo": "/absolute/path/to/logo.png", - "website": "https://acme.ai", - "appName": "Acme AI", - "appId": "ai.acme.desktop", - "artifactPrefix": "Acme-AI", - "copyright": "Copyright © 2026 Acme AI" -} -``` - -Install desktop dependencies if `packages/desktop/node_modules` is missing: - -```bash -cd packages/desktop -bun install -``` - -Then run this skill's bundled brand creation script: - -```bash -cd /absolute/path/to/qwen-code -bun run packages/desktop/.agents/skills/desktop-brand-builder/scripts/brand-create.ts \ - --desktop-root /absolute/path/to/qwen-code/packages/desktop \ - --config /absolute/path/to/brand.json -``` - -The agent should not hand-edit `branding.ts` or brand asset files when this -bundled script is available. The bundled script is the source of truth for -patching code and generating resources. - -Package with the current host target unless the user requested a target: - -```bash -CRAFT_BRAND= bun run electron:dist:mac -CRAFT_BRAND= bun run electron:dist:win -CRAFT_BRAND= bun run electron:dist:linux -``` - -For `target: all`, run only targets supported by the current machine or CI -environment. Do not claim cross-platform artifacts were produced unless the -files exist. - -## Validation - -After packaging: - -1. Confirm the expected artifact exists under - `packages/desktop/apps/electron/release/`. -2. Compute `sha256sum` or `shasum -a 256` for each artifact. -3. On macOS, run `hdiutil verify` for generated DMG files. -4. Report the artifact path, SHA-256, app name, app id, and build directory. - -## Failure Handling - -- Invalid `brandId`: show the regex and ask for a corrected value. -- Missing `logo`: ask for a valid local path. -- Missing bundled script: report that - `packages/desktop/.agents/skills/desktop-brand-builder/scripts/brand-create.ts` - is missing, and include the expected command. -- Build failure: preserve the build directory, return the last useful error - lines, and include the full log path or command that produced the failure. - -Do not delete the build directory on failure. diff --git a/packages/desktop/.agents/skills/desktop-brand-builder/scripts/brand-create.ts b/packages/desktop/.agents/skills/desktop-brand-builder/scripts/brand-create.ts deleted file mode 100644 index 8112a423ee4..00000000000 --- a/packages/desktop/.agents/skills/desktop-brand-builder/scripts/brand-create.ts +++ /dev/null @@ -1,381 +0,0 @@ -import { createRequire } from 'node:module'; -import { - copyFileSync, - existsSync, - mkdirSync, - mkdtempSync, - readFileSync, - rmSync, - writeFileSync, -} from 'node:fs'; -import { tmpdir } from 'node:os'; -import { extname, join, resolve } from 'node:path'; - -interface BrandInput { - brandId?: string; - logo?: string; - website?: string; - appName?: string; - appId?: string; - artifactPrefix?: string; - copyright?: string; -} - -interface BrandConfig { - brandId: string; - logo: string; - website?: string; - appName: string; - appId: string; - artifactPrefix: string; - copyright: string; -} - -const BRAND_ID_RE = /^[a-z][a-z0-9-]*$/; - -function argValue(name: string): string | undefined { - const index = process.argv.indexOf(name); - return index >= 0 ? process.argv[index + 1] : undefined; -} - -function configPathFromArgs(): string { - const value = argValue('--config'); - if (!value) { - throw new Error( - 'Usage: bun run scripts/brand-create.ts --desktop-root /path/to/packages/desktop --config /path/to/brand.json', - ); - } - return resolve(value); -} - -function desktopRootFromArgs(): string { - const value = argValue('--desktop-root'); - if (!value) { - throw new Error( - 'Usage: bun run scripts/brand-create.ts --desktop-root /path/to/packages/desktop --config /path/to/brand.json', - ); - } - - const desktopRoot = resolve(value); - if (!existsSync(join(desktopRoot, 'package.json'))) { - throw new Error(`Desktop package not found: ${desktopRoot}`); - } - return desktopRoot; -} - -function titleWords(brandId: string): string[] { - return brandId - .split('-') - .filter(Boolean) - .map((part) => part[0]!.toUpperCase() + part.slice(1)); -} - -function deriveAppId(website: string | undefined, brandId: string): string { - if (!website) return `app.${brandId}.desktop`; - - try { - const withProtocol = website.includes('://') - ? website - : `https://${website}`; - const host = new URL(withProtocol).hostname.replace(/^www\./, ''); - const parts = host.split('.').filter(Boolean); - if (parts.length >= 2) { - return `${parts.reverse().join('.')}.desktop`; - } - } catch { - // Fall through to the deterministic fallback. - } - - return `app.${brandId}.desktop`; -} - -function loadConfig(path: string): BrandConfig { - const input = JSON.parse(readFileSync(path, 'utf8')) as BrandInput; - const brandId = input.brandId?.trim(); - const logo = input.logo ? resolve(input.logo) : undefined; - - if (!brandId || !BRAND_ID_RE.test(brandId)) { - throw new Error(`brandId must match ${BRAND_ID_RE}`); - } - if (!logo || !existsSync(logo)) { - throw new Error(`Logo file not found: ${logo ?? '(missing)'}`); - } - - const words = titleWords(brandId); - const appName = input.appName?.trim() || words.join(' '); - const artifactPrefix = input.artifactPrefix?.trim() || words.join('-'); - - return { - brandId, - logo, - website: input.website?.trim() || undefined, - appName, - appId: input.appId?.trim() || deriveAppId(input.website, brandId), - artifactPrefix, - copyright: - input.copyright?.trim() || - `Copyright \u00a9 ${new Date().getFullYear()} ${appName}`, - }; -} - -async function run(cmd: string[], cwd: string): Promise { - const proc = Bun.spawn({ - cmd, - cwd, - stdout: 'inherit', - stderr: 'inherit', - stdin: 'inherit', - }); - const exitCode = await proc.exited; - if (exitCode !== 0) { - throw new Error(`${cmd.join(' ')} failed with exit code ${exitCode}`); - } -} - -interface BrandAssetsResult { - macIcon: string; - hasAssetsCar: boolean; -} - -async function writeBrandAssets( - config: BrandConfig, - desktopRoot: string, -): Promise { - const requireFromDesktop = createRequire(join(desktopRoot, 'package.json')); - const sharp = requireFromDesktop('sharp') as typeof import('sharp'); - const electronDir = join(desktopRoot, 'apps', 'electron'); - const brandDir = join(electronDir, 'resources', 'brands', config.brandId); - mkdirSync(brandDir, { recursive: true }); - - async function writePng(output: string, size: number) { - await sharp(config.logo) - .resize(size, size, { - fit: 'contain', - background: { r: 0, g: 0, b: 0, alpha: 0 }, - }) - .png() - .toFile(output); - } - - const sourceExt = extname(config.logo) || '.logo'; - copyFileSync(config.logo, join(brandDir, `source${sourceExt}`)); - - await writePng(join(brandDir, 'icon.png'), 512); - await writePng(join(brandDir, 'dock.png'), 512); - await writePng(join(brandDir, 'symbol.png'), 512); - - if (process.platform !== 'darwin') return { macIcon: 'icon.png', hasAssetsCar: false }; - - const iconset = join(brandDir, 'icon.iconset'); - rmSync(iconset, { recursive: true, force: true }); - mkdirSync(iconset, { recursive: true }); - - const sizes = [ - ['icon_16x16.png', 16], - ['icon_16x16@2x.png', 32], - ['icon_32x32.png', 32], - ['icon_32x32@2x.png', 64], - ['icon_128x128.png', 128], - ['icon_128x128@2x.png', 256], - ['icon_256x256.png', 256], - ['icon_256x256@2x.png', 512], - ['icon_512x512.png', 512], - ['icon_512x512@2x.png', 1024], - ] as const; - - for (const [file, size] of sizes) { - await writePng(join(iconset, file), size); - } - - await run( - ['iconutil', '-c', 'icns', iconset, '-o', join(brandDir, 'icon.icns')], - brandDir, - ); - - const hasAssetsCar = await compileAssetsCar(config, brandDir, writePng); - return { macIcon: 'icon.icns', hasAssetsCar }; -} - -async function compileAssetsCar( - config: BrandConfig, - brandDir: string, - writePng: (output: string, size: number) => Promise, -): Promise { - const xcassets = join(brandDir, 'Assets.xcassets'); - const appiconset = join(xcassets, 'AppIcon.appiconset'); - rmSync(xcassets, { recursive: true, force: true }); - mkdirSync(appiconset, { recursive: true }); - - writeFileSync( - join(xcassets, 'Contents.json'), - JSON.stringify({ info: { author: 'xcode', version: 1 } }), - ); - - const entries = [ - { file: 'icon_16.png', size: 16, scale: '1x', dims: '16x16' }, - { file: 'icon_32.png', size: 32, scale: '2x', dims: '16x16' }, - { file: 'icon_32.png', size: 32, scale: '1x', dims: '32x32' }, - { file: 'icon_64.png', size: 64, scale: '2x', dims: '32x32' }, - { file: 'icon_128.png', size: 128, scale: '1x', dims: '128x128' }, - { file: 'icon_256.png', size: 256, scale: '2x', dims: '128x128' }, - { file: 'icon_256.png', size: 256, scale: '1x', dims: '256x256' }, - { file: 'icon_512.png', size: 512, scale: '2x', dims: '256x256' }, - { file: 'icon_512.png', size: 512, scale: '1x', dims: '512x512' }, - { file: 'icon_1024.png', size: 1024, scale: '2x', dims: '512x512' }, - ]; - - const uniqueSizes = new Set(entries.map((e) => e.size)); - for (const size of uniqueSizes) { - await writePng(join(appiconset, `icon_${size}.png`), size); - } - - writeFileSync( - join(appiconset, 'Contents.json'), - JSON.stringify({ - images: entries.map((e) => ({ - filename: e.file, - idiom: 'mac', - scale: e.scale, - size: e.dims, - })), - info: { author: 'xcode', version: 1 }, - }), - ); - - const outDir = mkdtempSync(join(tmpdir(), 'assets-car-')); - const partialPlist = join(outDir, 'partial-info.plist'); - const proc = Bun.spawn({ - cmd: [ - 'xcrun', 'actool', xcassets, - '--compile', outDir, - '--app-icon', 'AppIcon', - '--platform', 'macosx', - '--minimum-deployment-target', '14.0', - '--output-partial-info-plist', partialPlist, - ], - cwd: brandDir, - stdout: 'pipe', - stderr: 'pipe', - }); - const exitCode = await proc.exited; - if (exitCode !== 0) { - console.log('Warning: actool compilation failed, skipping Assets.car'); - rmSync(xcassets, { recursive: true, force: true }); - return false; - } - - const compiledCar = join(outDir, 'Assets.car'); - if (!existsSync(compiledCar)) { - console.log('Warning: actool produced no Assets.car, skipping'); - rmSync(xcassets, { recursive: true, force: true }); - return false; - } - - copyFileSync(compiledCar, join(brandDir, 'Assets.car')); - rmSync(xcassets, { recursive: true, force: true }); - rmSync(outDir, { recursive: true, force: true }); - console.log('Assets.car compiled successfully'); - return true; -} - -function tsString(value: string): string { - return `'${value.replaceAll('\\', '\\\\').replaceAll("'", "\\'")}'`; -} - -function helpMenuLinks(config: BrandConfig): string { - if (!config.website) return '[]'; - - return `[ - { - labelKey: 'menu.homepage', - url: ${tsString(config.website)}, - icon: 'House', - }, - ]`; -} - -function brandBlock(config: BrandConfig, macIcon: string, hasAssetsCar: boolean): string { - const resourceDir = `resources/brands/${config.brandId}`; - const liquidGlassLine = hasAssetsCar - ? `\n liquidGlassAssetsCar: ${tsString(`${resourceDir}/Assets.car`)},` - : ''; - - return ` ${tsString(config.brandId)}: { - id: ${tsString(config.brandId)}, - appName: ${tsString(config.appName)}, - appId: ${tsString(config.appId)}, - productName: ${tsString(config.appName)}, - artifactPrefix: ${tsString(config.artifactPrefix)}, - copyright: ${tsString(config.copyright)}, - coAuthorLine: ${tsString(`Co-Authored-By: ${config.appName} `)}, - selfReferName: ${tsString(config.appName)}, - viewerUrl: 'https://agents.craft.do', - helpMenuLinks: ${helpMenuLinks(config)}, - assets: { - resourceDir: ${tsString(resourceDir)}, - rendererSymbol: ${tsString(`${resourceDir}/symbol.png`)}, - macIcon: ${tsString(`${resourceDir}/${macIcon}`)}, - winIcon: ${tsString(`${resourceDir}/icon.png`)}, - linuxIcon: ${tsString(`${resourceDir}/icon.png`)}, - devDockIcon: ${tsString(`${resourceDir}/dock.png`)},${liquidGlassLine} - }, - credits: '', - creditsShort: '', - creditsEntries: [], - }, -`; -} - -function registerBrand( - config: BrandConfig, - desktopRoot: string, - macIcon: string, - hasAssetsCar: boolean, -): void { - const brandingPath = join( - desktopRoot, - 'packages', - 'shared', - 'src', - 'branding.ts', - ); - const source = readFileSync(brandingPath, 'utf8'); - if ( - source.includes(`${tsString(config.brandId)}:`) || - source.includes(`id: ${tsString(config.brandId)}`) - ) { - throw new Error(`Brand already exists in branding.ts: ${config.brandId}`); - } - - const marker = '\n};\n\n/** Active brand'; - if (!source.includes(marker)) { - throw new Error(`Could not find BRANDS insertion point in ${brandingPath}`); - } - - writeFileSync( - brandingPath, - source.replace(marker, `\n${brandBlock(config, macIcon, hasAssetsCar)}${marker}`), - ); -} - -async function main(): Promise { - const desktopRoot = desktopRootFromArgs(); - const config = loadConfig(configPathFromArgs()); - const { macIcon, hasAssetsCar } = await writeBrandAssets(config, desktopRoot); - registerBrand(config, desktopRoot, macIcon, hasAssetsCar); - - console.log(`Created brand ${config.brandId}`); - console.log(`App name: ${config.appName}`); - console.log(`App ID: ${config.appId}`); - console.log( - `Assets: ${join(desktopRoot, 'apps', 'electron', 'resources', 'brands', config.brandId)}`, - ); - if (hasAssetsCar) { - console.log('Assets.car: generated (macOS 26+ Liquid Glass icon)'); - } -} - -main().catch((error: unknown) => { - console.error(error instanceof Error ? error.message : error); - process.exit(1); -}); diff --git a/packages/desktop/.agents/skills/desktop-develop/SKILL.md b/packages/desktop/.agents/skills/desktop-develop/SKILL.md deleted file mode 100644 index ee91353da8e..00000000000 --- a/packages/desktop/.agents/skills/desktop-develop/SKILL.md +++ /dev/null @@ -1,121 +0,0 @@ ---- -name: desktop-develop -description: Develop, debug, and verify the OpenWork desktop/Electron app with an agent-readable harness. Use when working on packages/desktop, Electron renderer/main/preload code, desktop UI bugs, local desktop runtime failures, Chrome DevTools MCP investigation, desktop logs, messaging gateway issues, or when improving the development feedback loop for desktop features. ---- - -# Desktop Development Harness - -## Overview - -Use this skill to turn desktop work into a tight harness loop: gather runtime -context, reproduce with the UI and logs visible to the agent, make the smallest -fix, verify through the same path, and encode any missing affordance back into -the repo. - -Read `references/harness-principles.md` when the task changes the development -workflow, observability, docs, tests, or agent-facing harness itself. - -## Quick Context - -For bug reports, UI failures, hangs, startup problems, messaging issues, or -anything involving the running desktop app, inspect the runtime logs directly. -Important paths: - -- `~/Library/Logs/@craft-agent/electron/main.log` -- `~/Library/Logs/@craft-agent/electron/main.old.log` -- `~/.craft-agent/logs/messaging-gateway.log` - -Search logs before guessing: - -```bash -rg -n "error|warn|failed|exception|crash|Unhandled|rejection|browser-cdp|messaging-gateway" \ - "$HOME/Library/Logs/@craft-agent/electron/main.log" \ - "$HOME/.craft-agent/logs/messaging-gateway.log" -``` - -## Harness Loop - -1. **Map the surface.** Identify whether the task touches Electron main, - preload, renderer, shared desktop packages, server, messaging, or browser - CDP. Read nearby code and tests before editing. -2. **Collect live evidence.** Read and tail the relevant log while reproducing. - Treat missing or ambiguous logs as part of the bug. -3. **Drive the UI.** Use Chrome DevTools MCP when a browser/renderer page is - involved: `list_pages`, `select_page`, `take_snapshot`, then console/network - inspection. Prefer accessibility snapshots over screenshots for reasoning. -4. **Reproduce first.** For bugs, capture the exact observed behavior and the - evidence that proves it. If reproduction differs from the user's report, - compare environment, app state, build artifact, account, timing, and logs. -5. **Patch narrowly.** Keep changes scoped to the proven cause. Add structure - only when it removes real repeated work or makes the app more readable to - future agents. -6. **Verify through the same path.** Re-run the reproduction, inspect logs and - DevTools again, then run focused tests/typechecks for touched packages. -7. **Improve the harness when needed.** If the fix required hidden knowledge, - add a small doc, test, log field, or skill update so the next agent - can see it directly. - -## Running Desktop - -Use desktop-specific commands from `packages/desktop`: - -```bash -cd packages/desktop -bun run electron:dev -bun run electron:dev:terminal -bun run electron:dev:logs -``` - -Use `electron:dev:terminal` when the bug involves process output, startup, or -shutdown. Use `electron:dev:logs` when the app is already running and you need a -live log tail. - -## Chrome DevTools MCP - -If DevTools tools are not loaded, search for `chrome-devtools` tools first. -Then: - -1. Call `mcp__chrome_devtools.list_pages`. -2. Select the relevant page with `mcp__chrome_devtools.select_page`. -3. Capture an accessibility snapshot with - `mcp__chrome_devtools.take_snapshot`. -4. Inspect runtime failures with - `mcp__chrome_devtools.list_console_messages`, then - `mcp__chrome_devtools.get_console_message` for important entries. -5. Inspect selected network requests with - `mcp__chrome_devtools.get_network_request` when network state is involved. -6. For memory issues, save a heap snapshot with - `mcp__chrome_devtools.take_heapsnapshot` and keep it under `.qwen/` or - `/tmp`, not in source directories. - -Always take a fresh snapshot after each UI-changing action. Do not rely on stale -element ids or old console state. - -## Focused Verification - -Choose the narrowest checks that cover the touched surface: - -```bash -cd packages/desktop && bun run typecheck:electron -cd packages/desktop && bun run typecheck:all -cd packages/desktop && bun run validate:dev -cd packages/desktop/apps/electron && bun run lint -cd packages/desktop/packages/shared && bun test path/to/file.test.ts -``` - -For root CLI/core changes, use the root repository commands from `AGENTS.md` -instead. For desktop-only changes, prefer desktop package commands first. - -## Agent-Readable Changes - -Favor changes that future agents can inspect and verify: - -- Add structured log fields near failure boundaries instead of vague messages. -- Add accessible names or stable UI affordances when DevTools snapshots are - hard to interpret. -- Keep docs as maps with links to deeper sources. Do not create giant manuals. -- Convert repeated manual debugging steps into docs, tests, or structured logs. -- Record non-trivial investigation notes in `.qwen/investigations/`. - -Stop and ask the user only when the missing input cannot be discovered locally -and a reasonable assumption would risk changing the wrong behavior. diff --git a/packages/desktop/.agents/skills/desktop-develop/references/harness-principles.md b/packages/desktop/.agents/skills/desktop-develop/references/harness-principles.md deleted file mode 100644 index 9f9c18c61ff..00000000000 --- a/packages/desktop/.agents/skills/desktop-develop/references/harness-principles.md +++ /dev/null @@ -1,42 +0,0 @@ -# Desktop Harness Principles - -Use this reference when designing desktop development workflows, debugging -loops, observability, or agent-facing docs. - -## Source - -OpenAI, "Engineering: Harnessing Codex in an agent-first world" -https://openai.com/zh-Hans-CN/index/harness-engineering/ - -## Principles - -- Give the agent a map, not a giant manual. Keep `AGENTS.md` and skills concise - entry points that route to focused docs, tests, and logs. -- Make the application readable to agents. UI snapshots, logs, metrics, traces, - and runtime state should be directly inspectable without asking a human to - copy/paste observations. -- Treat the repo as the system of record. If a fix depends on tribal knowledge, - encode that knowledge as versioned docs, tests, lint rules, or structured - logging. -- Build feedback loops, not one-off heroics. Reproduce, observe, patch, - restart, and verify through the same harness until the evidence changes. -- Prefer enforceable constraints over vague preference. If a pattern matters - repeatedly, turn it into a test, linter, helper, or review checklist. -- Use human attention for judgment. Let agents collect evidence, run tools, - draft fixes, and verify; ask humans when product intent or risk cannot be - inferred locally. - -## Desktop Application Pattern - -For qwen-code desktop work, the harness is: - -1. Desktop runtime logs under `~/Library/Logs/@craft-agent/electron/`. -2. Domain-specific logs such as `~/.craft-agent/logs/messaging-gateway.log`. -3. Chrome DevTools MCP snapshots, console messages, network details, and heap - snapshots. -4. Focused package tests and typechecks under `packages/desktop`. -5. Small repo artifacts under `.qwen/` for investigations, E2E notes, and skill - improvements. - -When one of these is missing or hard to read, consider improving the harness as -part of the development task. diff --git a/packages/desktop/.agents/skills/desktop-pet/SKILL.md b/packages/desktop/.agents/skills/desktop-pet/SKILL.md deleted file mode 100644 index 1f7b7324f4f..00000000000 --- a/packages/desktop/.agents/skills/desktop-pet/SKILL.md +++ /dev/null @@ -1,235 +0,0 @@ ---- -name: desktop-pet -description: Create pixel-art desktop pet companions for OpenWork/Qwen Code. Generates a customized chibi spritesheet (1536×1872, 8×9 grid) for any character the user names — F1 drivers, anime characters, celebrities, fictional characters, animals, etc. Use when the user says "桌面宠物", "desktop pet", "想要XXX当桌宠", "换个宠物" or similar. -version: 1.0.0 ---- - -# Desktop Pet Creator - -Create pixel-art chibi desktop pet companions for OpenWork's floating pet window. -Given any character name, generate a complete pet package with animated spritesheet -and place it in `~/.qwen/pets/` where OpenWork auto-discovers it. - -## Workflow - -### Step 1: Identify the Character - -Ask the user who they want as their desktop pet if not already specified. -Then research the character's visual appearance: - -- **Team/organization colors** (e.g., McLaren papaya orange, Ferrari red) -- **Outfit/uniform** (racing suit, school uniform, armor, etc.) -- **Distinguishing features** (hair color/style, accessories, number, helmet) -- **Personality traits** (for animation style — energetic, calm, goofy, serious) -- **Iconic items** (steering wheel, lightsaber, guitar, etc.) - -Use web search if needed to gather visual reference. For well-known characters -(F1 drivers, popular anime, etc.), rely on training knowledge. - -### Step 2: Design the Color Palette - -Define 8-12 colors for the character: - -| Color Role | Example (F1 Driver) | Example (Anime Character) | -| -------------- | -------------------- | ------------------------- | -| Primary outfit | Team color (papaya) | Uniform color (navy) | -| Outfit dark | Darker shade | Darker shade | -| Outfit light | Lighter shade | Lighter shade | -| Skin | Warm skin tone | Skin tone | -| Skin dark | Shadow skin | Shadow skin | -| Hair | Character hair color | Character hair color | -| Accent | Number/logo color | Eye color / accessory | -| Shoe | Dark grey/black | Character shoe color | - -**Important:** All colors must be distinct and work at small pixel scale (3x = 9px details). - -### Step 3: Generate the Spritesheet - -Use the template script at `scripts/gen_spritesheet.py` as a starting point. -The script generates a **1536×1872 pixel RGBA spritesheet** (8 columns × 9 rows, -192×208 px cells) — the exact format OpenWork expects. - -**Run it like this:** - -```bash -python3 /scripts/gen_spritesheet.py \ - --output ~/.qwen/pets//spritesheet.webp \ - --config '{"name":"...","colors":{...},"features":{...}}' -``` - -Or copy and customize the script for characters that need unique visual elements -not covered by the parameterized version. - -**The 9 animation rows are:** - -| Row | State | Description | -| --- | ------------- | --------------------------------- | -| 0 | idle | Breathing + blinking (8 frames) | -| 1 | running-right | Running to the right (8 frames) | -| 2 | running-left | Running to the left (8 frames) | -| 3 | waving | Waving at user (8 frames) | -| 4 | jumping | Jumping celebration (8 frames) | -| 5 | failed | Sad/collapsed on error (8 frames) | -| 6 | waiting | Idle tapping (8 frames) | -| 7 | running | Generic running (8 frames) | -| 8 | review | Thinking/examining (8 frames) | - -### Step 4: Create pet.json - -Write the manifest to `~/.qwen/pets//pet.json`: - -```json -{ - "id": "", - "displayName": "", - "description": "", - "spritesheetPath": "spritesheet.webp" -} -``` - -Rules: - -- `id`: lowercase, no spaces, URL-safe (e.g., `piastri`, `satoru`, `goku`) -- `displayName`: The name shown in the UI (e.g., "Piastri", "五条悟", "悟空") -- `description`: One short sentence describing the character - -### Step 5: Verify and Activate - -1. Confirm the files exist: - - ```bash - ls -lh ~/.qwen/pets// - ``` - -2. Open the spritesheet in Preview for the user to check: - - ```bash - open ~/.qwen/pets//spritesheet.webp - ``` - -3. Tell the user to activate: - > Open **OpenWork → Settings → Appearance → Pet Companion**, - > click **Refresh**, then select ****. - -## Character Design Guidelines - -### Chibi Proportions - -- **Head**: ~40% of total height (big head = cute) -- **Body**: ~30% of total height -- **Legs**: ~25% of total height -- **Scale**: Each "pixel" in the art = 3×3 actual pixels (scale=3) -- **Character center**: approximately (96, 124) within the 192×208 cell - -### Drawing Order (back to front) - -1. Legs (behind body) -2. Body / outfit -3. Arms -4. Head shape -5. Hair (back layer) -6. Hair (front/top layer) -7. Face features (eyes, mouth, expression) -8. Accessories (hat, helmet, glasses, etc.) -9. Foreground details (number, logo, badge) - -### Animation Tips - -- **Idle**: subtle Y bob (0 to -2px) + blink every 3rd-4th frame -- **Running**: alternating leg offset (±4px), body tilt (±2px), arm swing -- **Waving**: one arm raised high, alternating frames -- **Jumping**: Y offset curve (0 → -30 → 0), arms up -- **Failed**: body tilt increases, then collapse to sitting pose -- **Happy expression**: curved eyes (∧ shape), blush marks on cheeks -- **Sad expression**: straight eyebrows, downturned mouth - -### Headgear Variants - -The template supports several headgear types. Set via `features.headgear`: - -- `cap` — baseball cap with brim (default for F1 drivers) -- `helmet` — full racing helmet with visor -- `none` — no headgear (just hair) -- `hat` — generic hat -- `hood` — hooded outfit -- `crown` — royal crown -- `horns` — devil/dragon horns -- `ears` — animal ears (cat, dog, etc.) -- `halo` — angel halo -- `headband` — ninja/sports headband - -### Special Features - -Set via `features.extras` (list): - -- `glasses` — round or rectangular glasses -- `scarf` — neck scarf -- `tail` — animal tail -- `wings` — small wings on back -- `number` — chest number (set `features.number` to the number string) -- `logo` — chest badge/logo area -- `sweat_drop` — anime sweat drop (in waiting/failed states) - -## Example Characters - -### F1 Driver (e.g., Piastri, Norris, Verstappen) - -```json -{ - "colors": { - "outfit": [255, 135, 32], - "outfit_dark": [220, 110, 20], - "outfit_light": [255, 170, 80], - "hair": [120, 80, 40], - "number": [30, 30, 30] - }, - "features": { - "headgear": "cap", - "number": "81", - "extras": ["logo"] - } -} -``` - -### Anime Character (e.g., Gojo Satoru) - -```json -{ - "colors": { - "outfit": [30, 30, 50], - "outfit_dark": [20, 20, 35], - "outfit_light": [60, 60, 80], - "hair": [230, 230, 250], - "accent": [100, 180, 255] - }, - "features": { - "headgear": "none", - "extras": ["glasses"] - } -} -``` - -### Animal (e.g., Shiba Inu) - -```json -{ - "colors": { - "outfit": [220, 170, 100], - "outfit_dark": [180, 130, 70], - "outfit_light": [240, 200, 140], - "hair": [220, 170, 100], - "accent": [255, 255, 255] - }, - "features": { - "headgear": "ears", - "extras": ["tail"] - } -} -``` - -## Troubleshooting - -- **Pet not showing**: Click Refresh in Settings → Appearance → Pet Companion -- **Colors look wrong**: Check that RGB values are tuples, not hex strings -- **Spritesheet too large**: Must be under 5MB (webp lossless usually ~8-50KB) -- **Animation jittery**: Ensure all 8 frames per row are visually distinct but not jarring diff --git a/packages/desktop/.agents/skills/desktop-pet/scripts/gen_spritesheet.py b/packages/desktop/.agents/skills/desktop-pet/scripts/gen_spritesheet.py deleted file mode 100644 index ede60f07d37..00000000000 --- a/packages/desktop/.agents/skills/desktop-pet/scripts/gen_spritesheet.py +++ /dev/null @@ -1,558 +0,0 @@ -#!/usr/bin/env python3 -""" -Desktop Pet Spritesheet Generator for OpenWork. - -Generates a 1536×1872 pixel-art chibi spritesheet (8 cols × 9 rows, 192×208 px cells) -customized via a JSON config describing colors, headgear, and features. - -Usage: - python3 gen_spritesheet.py --output ~/.qwen/pets/mychar/spritesheet.webp --config '{...}' - python3 gen_spritesheet.py --output out.webp --config-file config.json -""" - -import argparse -import json -import sys -import math - -try: - from PIL import Image, ImageDraw, ImageFont -except ImportError: - print("ERROR: Pillow is required. Install with: pip3 install Pillow") - sys.exit(1) - -# --- Atlas layout --- -COLS, ROWS = 8, 9 -CW, CH = 192, 208 -W, H = COLS * CW, ROWS * CH - -# --- Default color palette (Qwen capybara-like neutral) --- -DEFAULT_COLORS = { - "outfit": [100, 120, 180], - "outfit_dark": [70, 85, 140], - "outfit_light": [140, 160, 210], - "skin": [255, 218, 185], - "skin_dark": [230, 190, 155], - "hair": [80, 55, 35], - "hair_light": [110, 80, 55], - "accent": [30, 30, 30], - "shoe": [50, 50, 50], - "eye": [50, 70, 90], - "eye_white": [245, 245, 245], - "blush": [255, 160, 140], - "mouth": [180, 80, 80], -} - -DEFAULT_FEATURES = { - "headgear": "none", - "extras": [], - "number": "", - "hair_style": "short", - "helmet_color": None, -} - -def tuple_color(c): - if isinstance(c, list): - return tuple(c) - if isinstance(c, str) and c.startswith("#"): - h = c.lstrip("#") - return tuple(int(h[i:i+2], 16) for i in (0, 2, 4)) - return c - -def darken(color, amount=40): - return tuple(max(0, c - amount) for c in color[:3]) - -def lighten(color, amount=40): - return tuple(min(255, c + amount) for c in color[:3]) - -def fill_rect(draw, x, y, w, h, color): - draw.rectangle([x, y, x + w - 1, y + h - 1], fill=color) - - -def draw_headgear(draw, hx, hy, s, colors, features, flip=False): - """Draw headgear on top of the head.""" - hg = features.get("headgear", "none") - outfit = colors["outfit"] - outfit_dark = colors["outfit_dark"] - helmet_c = tuple_color(features.get("helmet_color") or outfit) - - if hg == "cap": - cap_y = hy - 7 * s - fill_rect(draw, hx - 2*s, cap_y, 24*s, 5*s, outfit) - fill_rect(draw, hx + 2*s, cap_y - 2*s, 16*s, 3*s, outfit) - fill_rect(draw, hx + 4*s, cap_y - 3*s, 12*s, 2*s, outfit_dark) - if not flip: - fill_rect(draw, hx - 4*s, cap_y + 4*s, 26*s, 2*s, outfit_dark) - else: - fill_rect(draw, hx - 2*s, cap_y + 4*s, 26*s, 2*s, outfit_dark) - fill_rect(draw, hx + 7*s, cap_y + s, 6*s, 2*s, colors["accent"]) - - elif hg == "helmet": - cap_y = hy - 8 * s - fill_rect(draw, hx - 3*s, cap_y, 26*s, 8*s, helmet_c) - fill_rect(draw, hx + 2*s, cap_y - 2*s, 16*s, 3*s, helmet_c) - fill_rect(draw, hx + 4*s, cap_y - 3*s, 12*s, 2*s, darken(helmet_c, 20)) - fill_rect(draw, hx, cap_y + 5*s, 20*s, 3*s, darken(helmet_c, 60)) - fill_rect(draw, hx + 2*s, cap_y + 6*s, 16*s, s, lighten(helmet_c, 60)) - - elif hg == "hat": - cap_y = hy - 6 * s - fill_rect(draw, hx - 4*s, cap_y + 3*s, 28*s, 3*s, outfit_dark) - fill_rect(draw, hx + 2*s, cap_y - 2*s, 16*s, 6*s, outfit) - fill_rect(draw, hx + 4*s, cap_y - 3*s, 12*s, 2*s, outfit_dark) - - elif hg == "hood": - cap_y = hy - 5 * s - fill_rect(draw, hx - 3*s, cap_y, 26*s, 4*s, outfit) - fill_rect(draw, hx - 4*s, cap_y + 2*s, 4*s, 8*s, outfit) - fill_rect(draw, hx + 20*s, cap_y + 2*s, 4*s, 8*s, outfit) - fill_rect(draw, hx + 4*s, cap_y - 2*s, 12*s, 3*s, outfit_dark) - - elif hg == "crown": - cap_y = hy - 8 * s - gold = (255, 215, 0) - gold_dark = (200, 170, 0) - fill_rect(draw, hx + 2*s, cap_y + 2*s, 16*s, 4*s, gold) - fill_rect(draw, hx + 2*s, cap_y, 3*s, 3*s, gold) - fill_rect(draw, hx + 8*s, cap_y - s, 4*s, 3*s, gold) - fill_rect(draw, hx + 15*s, cap_y, 3*s, 3*s, gold) - fill_rect(draw, hx + 3*s, cap_y + s, s, s, (200, 50, 50)) - fill_rect(draw, hx + 9*s, cap_y, 2*s, s, (50, 150, 200)) - fill_rect(draw, hx + 16*s, cap_y + s, s, s, (50, 200, 50)) - fill_rect(draw, hx + 2*s, cap_y + 5*s, 16*s, s, gold_dark) - - elif hg == "horns": - horn_c = (80, 60, 50) - fill_rect(draw, hx - 2*s, hy - 6*s, 3*s, 8*s, horn_c) - fill_rect(draw, hx - 3*s, hy - 8*s, 2*s, 3*s, horn_c) - fill_rect(draw, hx + 19*s, hy - 6*s, 3*s, 8*s, horn_c) - fill_rect(draw, hx + 21*s, hy - 8*s, 2*s, 3*s, horn_c) - - elif hg == "ears": - hair_c = colors["hair"] - inner = colors["skin"] - fill_rect(draw, hx - 3*s, hy - 8*s, 5*s, 8*s, hair_c) - fill_rect(draw, hx - 2*s, hy - 6*s, 3*s, 5*s, inner) - fill_rect(draw, hx + 18*s, hy - 8*s, 5*s, 8*s, hair_c) - fill_rect(draw, hx + 19*s, hy - 6*s, 3*s, 5*s, inner) - - elif hg == "halo": - halo_c = (255, 255, 200) - cap_y = hy - 10 * s - fill_rect(draw, hx + 3*s, cap_y, 14*s, 2*s, halo_c) - fill_rect(draw, hx + 2*s, cap_y + s, s, s, halo_c) - fill_rect(draw, hx + 17*s, cap_y + s, s, s, halo_c) - fill_rect(draw, hx + 3*s, cap_y + 2*s, 14*s, s, darken(halo_c, 30)) - - elif hg == "headband": - fill_rect(draw, hx - 2*s, hy - 2*s, 24*s, 2*s, colors["accent"]) - fill_rect(draw, hx + 18*s, hy - 2*s, 4*s, 6*s, colors["accent"]) - - -def draw_extras(draw, cx, cy, s, colors, features, bx, by, expression): - """Draw extra features like glasses, scarf, tail, wings.""" - extras = features.get("extras", []) - hx = cx - 10 * s - hy = cy - 16 * s - - if "glasses" in extras: - ey = hy + 8 * s - glass_c = (60, 60, 80) - fill_rect(draw, hx + 3*s, ey - s, 6*s, 5*s, glass_c) - fill_rect(draw, hx + 4*s, ey, 4*s, 3*s, (200, 220, 240)) - fill_rect(draw, hx + 11*s, ey - s, 6*s, 5*s, glass_c) - fill_rect(draw, hx + 12*s, ey, 4*s, 3*s, (200, 220, 240)) - fill_rect(draw, hx + 9*s, ey + s, 2*s, s, glass_c) - - if "scarf" in extras: - scarf_c = lighten(colors["outfit"], 30) - fill_rect(draw, bx + 3*s, by - 2*s, 10*s, 3*s, scarf_c) - fill_rect(draw, bx + 4*s, by + s, 3*s, 6*s, scarf_c) - - if "tail" in extras: - tail_c = colors["hair"] - fill_rect(draw, bx + 16*s, by + 10*s, 3*s, 3*s, tail_c) - fill_rect(draw, bx + 18*s, by + 8*s, 3*s, 3*s, tail_c) - fill_rect(draw, bx + 20*s, by + 6*s, 3*s, 3*s, tail_c) - fill_rect(draw, bx + 21*s, by + 4*s, 2*s, 3*s, tail_c) - - if "wings" in extras: - wing_c = (240, 240, 255) - wing_dark = (200, 200, 220) - fill_rect(draw, bx - 6*s, by + 2*s, 5*s, 8*s, wing_c) - fill_rect(draw, bx - 8*s, by + 4*s, 3*s, 5*s, wing_c) - fill_rect(draw, bx - 5*s, by + 3*s, 3*s, 5*s, wing_dark) - fill_rect(draw, bx + 17*s, by + 2*s, 5*s, 8*s, wing_c) - fill_rect(draw, bx + 21*s, by + 4*s, 3*s, 5*s, wing_c) - fill_rect(draw, bx + 18*s, by + 3*s, 3*s, 5*s, wing_dark) - - if "sweat_drop" in extras and expression in ("waiting", "failed"): - fill_rect(draw, hx + 18*s, hy + 2*s, 2*s, 3*s, (150, 200, 255)) - fill_rect(draw, hx + 18*s, hy + s, s, s, (150, 200, 255)) - - -def draw_character(draw, cx, cy, colors, features, scale=3, flip=False, - arm_angle=0, leg_offset=0, body_tilt=0, head_tilt=0, - expression="normal", arm_wave=False, jump_y=0, collapsed=False): - """Draw a chibi character centered at (cx, cy).""" - s = scale - cy += jump_y - - skin = colors["skin"] - skin_dark = colors["skin_dark"] - hair_c = colors["hair"] - hair_light = colors.get("hair_light", lighten(hair_c, 30)) - outfit = colors["outfit"] - outfit_dark = colors["outfit_dark"] - outfit_light = colors["outfit_light"] - eye_c = colors["eye"] - eye_w = colors["eye_white"] - blush_c = colors["blush"] - mouth_c = colors.get("mouth", darken(skin, 80)) - accent_c = colors["accent"] - shoe_c = colors["shoe"] - - # --- LEGS --- - leg_spread = 4 * s - leg_left_x = cx - leg_spread - 2*s + body_tilt - leg_right_x = cx + leg_spread - 2*s + body_tilt - - if collapsed: - fill_rect(draw, leg_left_x, cy + 18*s, 5*s, 6*s, outfit_dark) - fill_rect(draw, leg_right_x, cy + 18*s, 5*s, 6*s, outfit_dark) - fill_rect(draw, leg_left_x, cy + 24*s, 5*s, 2*s, shoe_c) - fill_rect(draw, leg_right_x, cy + 24*s, 5*s, 2*s, shoe_c) - else: - fill_rect(draw, leg_left_x + leg_offset, cy + 16*s, 5*s, 10*s, outfit_dark) - fill_rect(draw, leg_right_x - leg_offset, cy + 16*s, 5*s, 10*s, outfit_dark) - fill_rect(draw, leg_left_x + leg_offset - s, cy + 26*s, 7*s, 3*s, shoe_c) - fill_rect(draw, leg_right_x - leg_offset - s, cy + 26*s, 7*s, 3*s, shoe_c) - - # --- BODY --- - bx = cx - 8*s + body_tilt - by = cy + 2*s - fill_rect(draw, bx, by, 16*s, 16*s, outfit) - fill_rect(draw, bx + s, by + s, 14*s, 2*s, outfit_light) - fill_rect(draw, bx + 5*s, by - s, 6*s, 2*s, (255, 255, 255)) - - # Number on chest - num = features.get("number", "") - if num: - try: - font_size = max(5 * s, 8) - font = ImageFont.truetype("/System/Library/Fonts/Helvetica.ttc", font_size) - except Exception: - font = ImageFont.load_default() - bbox = draw.textbbox((0, 0), num, font=font) - tw = bbox[2] - bbox[0] - th = bbox[3] - bbox[1] - tx = bx + (16*s - tw) // 2 - ty = by + (14*s - th) // 2 + s - draw.text((tx, ty), num, fill=accent_c, font=font) - - # Logo area - if "logo" in features.get("extras", []) and not num: - fill_rect(draw, bx + 5*s, by + 5*s, 6*s, 4*s, accent_c) - fill_rect(draw, bx + 6*s, by + 6*s, 4*s, 2*s, outfit_light) - - # --- ARMS --- - arm_y = by + 3*s - left_arm_x = bx - 5*s - right_arm_x = bx + 16*s - - if arm_wave: - fill_rect(draw, left_arm_x, arm_y + 2*s, 5*s, 8*s, outfit) - fill_rect(draw, left_arm_x - s, arm_y + 10*s, 5*s, 4*s, skin) - fill_rect(draw, right_arm_x, arm_y - 8*s, 5*s, 10*s, outfit) - fill_rect(draw, right_arm_x, arm_y - 10*s, 6*s, 4*s, skin) - elif arm_angle > 0: - fill_rect(draw, left_arm_x - arm_angle, arm_y, 5*s, 10*s, outfit) - fill_rect(draw, left_arm_x - arm_angle - s, arm_y + 10*s, 5*s, 4*s, skin) - fill_rect(draw, right_arm_x + arm_angle, arm_y, 5*s, 10*s, outfit) - fill_rect(draw, right_arm_x + arm_angle + s, arm_y + 10*s, 5*s, 4*s, skin) - else: - fill_rect(draw, left_arm_x, arm_y, 5*s, 10*s, outfit) - fill_rect(draw, left_arm_x - s, arm_y + 10*s, 5*s, 4*s, skin) - fill_rect(draw, right_arm_x, arm_y, 5*s, 10*s, outfit) - fill_rect(draw, right_arm_x + s, arm_y + 10*s, 5*s, 4*s, skin) - - # --- Scarf (drawn between body and head) --- - draw_extras(draw, cx, cy, s, colors, features, bx, by, expression) - - # --- HEAD --- - hx = cx - 10*s + head_tilt - hy = cy - 16*s - - # Hair back - fill_rect(draw, hx - s, hy - 2*s, 22*s, 6*s, hair_c) - # Head shape - fill_rect(draw, hx, hy, 20*s, 18*s, skin) - - # Hair style - hair_style = features.get("hair_style", "short") - if hair_style == "long": - fill_rect(draw, hx - s, hy - 4*s, 22*s, 6*s, hair_c) - fill_rect(draw, hx + 2*s, hy - 5*s, 16*s, 3*s, hair_c) - fill_rect(draw, hx + 4*s, hy - 6*s, 12*s, 2*s, hair_light) - fill_rect(draw, hx - 2*s, hy, 3*s, 16*s, hair_c) - fill_rect(draw, hx + 19*s, hy, 3*s, 16*s, hair_c) - fill_rect(draw, hx - 3*s, hy + 14*s, 4*s, 4*s, hair_c) - fill_rect(draw, hx + 19*s, hy + 14*s, 4*s, 4*s, hair_c) - elif hair_style == "spiky": - fill_rect(draw, hx - s, hy - 4*s, 22*s, 6*s, hair_c) - fill_rect(draw, hx + s, hy - 7*s, 4*s, 4*s, hair_c) - fill_rect(draw, hx + 6*s, hy - 8*s, 4*s, 5*s, hair_c) - fill_rect(draw, hx + 11*s, hy - 7*s, 4*s, 4*s, hair_c) - fill_rect(draw, hx + 16*s, hy - 6*s, 3*s, 3*s, hair_c) - fill_rect(draw, hx - 2*s, hy, 3*s, 10*s, hair_c) - fill_rect(draw, hx + 19*s, hy, 3*s, 10*s, hair_c) - elif hair_style == "ponytail": - fill_rect(draw, hx - s, hy - 4*s, 22*s, 6*s, hair_c) - fill_rect(draw, hx + 2*s, hy - 5*s, 16*s, 3*s, hair_c) - fill_rect(draw, hx - 2*s, hy, 3*s, 10*s, hair_c) - fill_rect(draw, hx + 19*s, hy, 3*s, 10*s, hair_c) - fill_rect(draw, hx + 18*s, hy + 8*s, 3*s, 3*s, hair_c) - fill_rect(draw, hx + 19*s, hy + 10*s, 3*s, 8*s, hair_c) - fill_rect(draw, hx + 20*s, hy + 16*s, 2*s, 4*s, hair_light) - elif hair_style == "bald": - fill_rect(draw, hx + 2*s, hy - 2*s, 16*s, 2*s, skin_dark) - else: # short (default) - fill_rect(draw, hx - s, hy - 4*s, 22*s, 6*s, hair_c) - fill_rect(draw, hx + 2*s, hy - 5*s, 16*s, 3*s, hair_c) - fill_rect(draw, hx + 4*s, hy - 6*s, 12*s, 2*s, hair_light) - fill_rect(draw, hx - 2*s, hy, 3*s, 10*s, hair_c) - fill_rect(draw, hx + 19*s, hy, 3*s, 10*s, hair_c) - - # --- FACE --- - ey = hy + 8*s - - if expression == "blink": - fill_rect(draw, hx + 4*s, ey + s, 4*s, s, eye_c) - fill_rect(draw, hx + 12*s, ey + s, 4*s, s, eye_c) - elif expression == "happy": - fill_rect(draw, hx + 4*s, ey, 4*s, s, eye_c) - fill_rect(draw, hx + 3*s, ey + s, s, s, eye_c) - fill_rect(draw, hx + 8*s, ey + s, s, s, eye_c) - fill_rect(draw, hx + 12*s, ey, 4*s, s, eye_c) - fill_rect(draw, hx + 11*s, ey + s, s, s, eye_c) - fill_rect(draw, hx + 16*s, ey + s, s, s, eye_c) - fill_rect(draw, hx + 7*s, ey + 5*s, 6*s, s, mouth_c) - fill_rect(draw, hx + 6*s, ey + 4*s, s, s, mouth_c) - fill_rect(draw, hx + 13*s, ey + 4*s, s, s, mouth_c) - fill_rect(draw, hx + 2*s, ey + 3*s, 3*s, 2*s, blush_c) - fill_rect(draw, hx + 15*s, ey + 3*s, 3*s, 2*s, blush_c) - elif expression == "sad": - fill_rect(draw, hx + 4*s, ey, 4*s, 3*s, eye_w) - fill_rect(draw, hx + 5*s, ey + s, 2*s, 2*s, eye_c) - fill_rect(draw, hx + 12*s, ey, 4*s, 3*s, eye_w) - fill_rect(draw, hx + 13*s, ey + s, 2*s, 2*s, eye_c) - fill_rect(draw, hx + 3*s, ey - 2*s, 5*s, s, hair_c) - fill_rect(draw, hx + 12*s, ey - 2*s, 5*s, s, hair_c) - fill_rect(draw, hx + 8*s, ey + 5*s, 4*s, s, mouth_c) - fill_rect(draw, hx + 7*s, ey + 6*s, s, s, mouth_c) - fill_rect(draw, hx + 12*s, ey + 6*s, s, s, mouth_c) - elif expression == "surprised": - fill_rect(draw, hx + 3*s, ey - s, 5*s, 4*s, eye_w) - fill_rect(draw, hx + 4*s, ey, 3*s, 3*s, eye_c) - fill_rect(draw, hx + 5*s, ey + s, s, s, (255, 255, 255)) - fill_rect(draw, hx + 12*s, ey - s, 5*s, 4*s, eye_w) - fill_rect(draw, hx + 13*s, ey, 3*s, 3*s, eye_c) - fill_rect(draw, hx + 14*s, ey + s, s, s, (255, 255, 255)) - fill_rect(draw, hx + 8*s, ey + 4*s, 4*s, 3*s, mouth_c) - fill_rect(draw, hx + 9*s, ey + 5*s, 2*s, s, (180, 80, 80)) - elif expression == "determined": - fill_rect(draw, hx + 4*s, ey, 4*s, 3*s, eye_w) - fill_rect(draw, hx + 6*s, ey + s, 2*s, 2*s, eye_c) - fill_rect(draw, hx + 12*s, ey, 4*s, 3*s, eye_w) - fill_rect(draw, hx + 14*s, ey + s, 2*s, 2*s, eye_c) - fill_rect(draw, hx + 3*s, ey - 2*s, 6*s, s, hair_c) - fill_rect(draw, hx + 11*s, ey - 2*s, 6*s, s, hair_c) - fill_rect(draw, hx + 8*s, ey + 5*s, 4*s, s, mouth_c) - else: # normal - fill_rect(draw, hx + 4*s, ey, 4*s, 3*s, eye_w) - fill_rect(draw, hx + 5*s, ey + s, 2*s, 2*s, eye_c) - fill_rect(draw, hx + 5*s, ey + s, s, s, (255, 255, 255)) - fill_rect(draw, hx + 12*s, ey, 4*s, 3*s, eye_w) - fill_rect(draw, hx + 13*s, ey + s, 2*s, 2*s, eye_c) - fill_rect(draw, hx + 13*s, ey + s, s, s, (255, 255, 255)) - fill_rect(draw, hx + 8*s, ey + 5*s, 4*s, s, mouth_c) - fill_rect(draw, hx + 7*s, ey + 4*s, s, s, mouth_c) - fill_rect(draw, hx + 12*s, ey + 4*s, s, s, mouth_c) - - # --- HEADGEAR --- - draw_headgear(draw, hx, hy, s, colors, features, flip) - - -def gen_row(sheet, row, colors, features, frame_configs): - """Generate one animation row from frame configs.""" - for col, fc in enumerate(frame_configs): - img = Image.new("RGBA", (CW, CH), (0, 0, 0, 0)) - d = ImageDraw.Draw(img) - draw_character( - d, CW // 2 + fc.get("dx", 0), CH // 2 + 20, - colors, features, scale=3, - flip=fc.get("flip", False), - arm_angle=fc.get("arm_angle", 0), - leg_offset=fc.get("leg_offset", 0), - body_tilt=fc.get("body_tilt", 0), - head_tilt=fc.get("head_tilt", 0), - expression=fc.get("expression", "normal"), - arm_wave=fc.get("arm_wave", False), - jump_y=fc.get("jump_y", 0), - collapsed=fc.get("collapsed", False), - ) - sheet.paste(img, (col * CW, row * CH), img) - - -def gen_all_animations(sheet, colors, features): - """Generate all 9 animation rows.""" - - # Row 0: idle - gen_row(sheet, 0, colors, features, [ - {"jump_y": 0, "expression": "normal"}, - {"jump_y": -1, "expression": "normal"}, - {"jump_y": -2, "expression": "blink"}, - {"jump_y": -1, "expression": "normal"}, - {"jump_y": 0, "expression": "normal"}, - {"jump_y": -1, "expression": "normal"}, - {"jump_y": 0, "expression": "normal"}, - {"jump_y": 0, "expression": "blink"}, - ]) - - # Row 1: running right - gen_row(sheet, 1, colors, features, [ - {"leg_offset": 0, "body_tilt": 0, "head_tilt": 0, "arm_angle": 0, "jump_y": 0, "dx": 8}, - {"leg_offset": 4, "body_tilt": 1, "head_tilt": 1, "arm_angle": 2, "jump_y": -2, "dx": 8}, - {"leg_offset": 0, "body_tilt": 2, "head_tilt": 2, "arm_angle": 3, "jump_y": -3, "dx": 8}, - {"leg_offset": -4, "body_tilt": 1, "head_tilt": 1, "arm_angle": 2, "jump_y": -2, "dx": 8}, - {"leg_offset": 0, "body_tilt": 0, "head_tilt": 0, "arm_angle": 0, "jump_y": 0, "dx": 8}, - {"leg_offset": 4, "body_tilt": -1, "head_tilt": -1, "arm_angle": -2, "jump_y": -2, "dx": 8}, - {"leg_offset": 0, "body_tilt": -2, "head_tilt": -2, "arm_angle": -3, "jump_y": -3, "dx": 8}, - {"leg_offset": -4, "body_tilt": -1, "head_tilt": -1, "arm_angle": -2, "jump_y": -2, "dx": 8}, - ]) - - # Row 2: running left - gen_row(sheet, 2, colors, features, [ - {"leg_offset": 0, "body_tilt": 0, "head_tilt": 0, "arm_angle": 0, "jump_y": 0, "flip": True, "dx": -8}, - {"leg_offset": 4, "body_tilt": -1, "head_tilt": -1, "arm_angle": 2, "jump_y": -2, "flip": True, "dx": -8}, - {"leg_offset": 0, "body_tilt": -2, "head_tilt": -2, "arm_angle": 3, "jump_y": -3, "flip": True, "dx": -8}, - {"leg_offset": -4, "body_tilt": -1, "head_tilt": -1, "arm_angle": 2, "jump_y": -2, "flip": True, "dx": -8}, - {"leg_offset": 0, "body_tilt": 0, "head_tilt": 0, "arm_angle": 0, "jump_y": 0, "flip": True, "dx": -8}, - {"leg_offset": 4, "body_tilt": 1, "head_tilt": 1, "arm_angle": -2, "jump_y": -2, "flip": True, "dx": -8}, - {"leg_offset": 0, "body_tilt": 2, "head_tilt": 2, "arm_angle": -3, "jump_y": -3, "flip": True, "dx": -8}, - {"leg_offset": -4, "body_tilt": 1, "head_tilt": 1, "arm_angle": -2, "jump_y": -2, "flip": True, "dx": -8}, - ]) - - # Row 3: waving - gen_row(sheet, 3, colors, features, [ - {"arm_wave": True, "jump_y": 0, "expression": "happy"}, - {"arm_wave": False, "jump_y": -1, "expression": "happy"}, - {"arm_wave": True, "jump_y": 0, "expression": "happy"}, - {"arm_wave": False, "jump_y": -1, "expression": "happy"}, - {"arm_wave": True, "jump_y": 0, "expression": "happy"}, - {"arm_wave": False, "jump_y": 0, "expression": "happy"}, - {"arm_wave": True, "jump_y": 0, "expression": "normal"}, - {"arm_wave": False, "jump_y": 0, "expression": "normal"}, - ]) - - # Row 4: jumping - gen_row(sheet, 4, colors, features, [ - {"jump_y": 0, "arm_angle": 0, "expression": "normal"}, - {"jump_y": -8, "arm_angle": 3, "expression": "happy"}, - {"jump_y": -20, "arm_angle": 5, "expression": "happy"}, - {"jump_y": -30, "arm_angle": 5, "expression": "happy"}, - {"jump_y": -35, "arm_angle": 5, "expression": "happy"}, - {"jump_y": -25, "arm_angle": 3, "expression": "happy"}, - {"jump_y": -10, "arm_angle": 0, "expression": "happy"}, - {"jump_y": 0, "arm_angle": 0, "expression": "normal"}, - ]) - - # Row 5: failed - gen_row(sheet, 5, colors, features, [ - {"body_tilt": 0, "head_tilt": 0, "expression": "sad"}, - {"body_tilt": -1, "head_tilt": -2, "expression": "sad"}, - {"body_tilt": -2, "head_tilt": -4, "expression": "sad"}, - {"body_tilt": -3, "head_tilt": -6, "expression": "sad"}, - {"body_tilt": -3, "head_tilt": -6, "expression": "sad", "collapsed": True}, - {"body_tilt": -2, "head_tilt": -4, "expression": "sad", "collapsed": True}, - {"body_tilt": -1, "head_tilt": -2, "expression": "sad"}, - {"body_tilt": 0, "head_tilt": 0, "expression": "sad"}, - ]) - - # Row 6: waiting - gen_row(sheet, 6, colors, features, [ - {"jump_y": 0, "head_tilt": 0, "expression": "normal"}, - {"jump_y": -1, "head_tilt": 0, "expression": "normal"}, - {"jump_y": 0, "head_tilt": 2, "expression": "normal"}, - {"jump_y": -1, "head_tilt": 0, "expression": "normal"}, - {"jump_y": 0, "head_tilt": -2, "expression": "blink"}, - {"jump_y": 0, "head_tilt": 0, "expression": "blink"}, - {"jump_y": 0, "head_tilt": 0, "expression": "normal"}, - {"jump_y": 0, "head_tilt": 0, "expression": "normal"}, - ]) - - # Row 7: running (generic, same as row 1) - gen_row(sheet, 7, colors, features, [ - {"leg_offset": 0, "body_tilt": 0, "head_tilt": 0, "arm_angle": 0, "jump_y": 0, "dx": 8}, - {"leg_offset": 4, "body_tilt": 1, "head_tilt": 1, "arm_angle": 2, "jump_y": -2, "dx": 8}, - {"leg_offset": 0, "body_tilt": 2, "head_tilt": 2, "arm_angle": 3, "jump_y": -3, "dx": 8}, - {"leg_offset": -4, "body_tilt": 1, "head_tilt": 1, "arm_angle": 2, "jump_y": -2, "dx": 8}, - {"leg_offset": 0, "body_tilt": 0, "head_tilt": 0, "arm_angle": 0, "jump_y": 0, "dx": 8}, - {"leg_offset": 4, "body_tilt": -1, "head_tilt": -1, "arm_angle": -2, "jump_y": -2, "dx": 8}, - {"leg_offset": 0, "body_tilt": -2, "head_tilt": -2, "arm_angle": -3, "jump_y": -3, "dx": 8}, - {"leg_offset": -4, "body_tilt": -1, "head_tilt": -1, "arm_angle": -2, "jump_y": -2, "dx": 8}, - ]) - - # Row 8: review/thinking - gen_row(sheet, 8, colors, features, [ - {"head_tilt": 0, "arm_angle": 2, "expression": "surprised"}, - {"head_tilt": 2, "arm_angle": 2, "expression": "surprised", "jump_y": -1}, - {"head_tilt": 4, "arm_angle": 2, "expression": "surprised", "jump_y": -1}, - {"head_tilt": 4, "arm_angle": 0, "expression": "normal"}, - {"head_tilt": 2, "arm_angle": 0, "expression": "happy"}, - {"head_tilt": 0, "arm_angle": 0, "expression": "happy"}, - {"head_tilt": 0, "arm_angle": 0, "expression": "normal"}, - {"head_tilt": 0, "arm_angle": 0, "expression": "normal"}, - ]) - - -def main(): - parser = argparse.ArgumentParser(description="Generate desktop pet spritesheet") - parser.add_argument("--output", "-o", required=True, help="Output .webp file path") - parser.add_argument("--config", "-c", type=str, help="JSON config string") - parser.add_argument("--config-file", "-f", help="Path to JSON config file") - args = parser.parse_args() - - config = {} - if args.config_file: - with open(args.config_file) as f: - config = json.load(f) - elif args.config: - config = json.loads(args.config) - - # Merge colors with defaults (convert all to tuples) - colors = {k: tuple_color(v) for k, v in DEFAULT_COLORS.items()} - for k, v in config.get("colors", {}).items(): - colors[k] = tuple_color(v) - - # Auto-derive missing colors - if "hair_light" not in config.get("colors", {}): - colors["hair_light"] = lighten(colors["hair"], 30) - if "mouth" not in config.get("colors", {}): - colors["mouth"] = darken(colors["skin"], 80) - - # Merge features with defaults - features = dict(DEFAULT_FEATURES) - features.update(config.get("features", {})) - - sheet = Image.new("RGBA", (W, H), (0, 0, 0, 0)) - gen_all_animations(sheet, colors, features) - - import os - os.makedirs(os.path.dirname(os.path.abspath(args.output)), exist_ok=True) - sheet.save(args.output, "WEBP", quality=95, lossless=True) - print(f"Saved spritesheet to {args.output} ({sheet.size[0]}x{sheet.size[1]})") - - -if __name__ == "__main__": - main() diff --git a/packages/desktop/.dockerignore b/packages/desktop/.dockerignore deleted file mode 100644 index 1079fcd9da1..00000000000 --- a/packages/desktop/.dockerignore +++ /dev/null @@ -1,50 +0,0 @@ -# Dependencies (rebuilt inside container) -node_modules/ - -# Build artifacts -dist/ -*.tgz - -# Electron app output (not needed for server) -apps/electron/release/ -apps/electron/vendor/ - -# Git -.git/ -.gitignore - -# IDE -.vscode/ -.idea/ -*.swp -*.swo - -# OS -.DS_Store -Thumbs.db - -# CI/CD -.github/ - -# Tests -**/*.test.ts -**/*.spec.ts -**/tests/ -**/test/ -**/__tests__/ - -# Top-level docs (not resource docs used by server) -/docs/ -apps/online-docs/ -README*.md -CONTRIBUTING*.md -CHANGELOG*.md -LICENSE* - -# Docker files (avoid recursive context) -Dockerfile* -docker-compose*.yml -.dockerignore - -# Smoke test script (not needed inside image) -scripts/docker-smoke-test.sh diff --git a/packages/desktop/.github/ISSUE_TEMPLATE/bug_report.yml b/packages/desktop/.github/ISSUE_TEMPLATE/bug_report.yml deleted file mode 100644 index 6b832b8b74a..00000000000 --- a/packages/desktop/.github/ISSUE_TEMPLATE/bug_report.yml +++ /dev/null @@ -1,123 +0,0 @@ -name: Bug Report -description: Report a bug or unexpected behavior in OpenWork -labels: ["bug"] -body: - - type: markdown - attributes: - value: | - Thanks for taking the time to report a bug! Please fill out the sections below so we can reproduce and fix the issue. - - - type: input - id: version - attributes: - label: OpenWork Version - description: "Found in Settings or the title bar (e.g. 0.4.6)" - placeholder: "0.4.6" - validations: - required: true - - - type: dropdown - id: os - attributes: - label: Operating System - options: - - macOS (Apple Silicon) - - macOS (Intel) - - Windows 11 - - Windows 10 - - Linux (Ubuntu/Debian) - - Linux (Fedora/RHEL) - - Linux (Arch) - - Linux (Other) - validations: - required: true - - - type: input - id: os_version - attributes: - label: OS Version - description: "e.g. macOS 15.3, Windows 11 24H2, Ubuntu 24.04" - placeholder: "macOS 15.3" - validations: - required: true - - - type: dropdown - id: ai_provider - attributes: - label: AI Provider - description: Which AI provider/connection are you using? - options: - - Anthropic API (direct) - - Anthropic API (custom endpoint) - - OpenAI / Codex - - Copilot (GitHub) - - Other - validations: - required: true - - - type: input - id: model - attributes: - label: Model - description: "Which model are you using? (e.g. Claude Opus 4.7, GPT-4.1)" - placeholder: "Claude Opus 4.7" - - - type: textarea - id: description - attributes: - label: Description - description: A clear description of what the bug is. - validations: - required: true - - - type: textarea - id: steps - attributes: - label: Steps to Reproduce - description: Step-by-step instructions to reproduce the behavior. - value: | - 1. - 2. - 3. - validations: - required: true - - - type: textarea - id: expected - attributes: - label: Expected Behavior - description: What did you expect to happen? - validations: - required: true - - - type: textarea - id: actual - attributes: - label: Actual Behavior - description: What actually happened? - validations: - required: true - - - type: textarea - id: screenshots - attributes: - label: Screenshots / Screen Recordings - description: | - For **UI issues**, please attach screenshots or screen recordings showing the problem. - You can drag and drop images/videos directly into this field. - - - type: textarea - id: logs - attributes: - label: Debug Logs - description: | - For **non-UI issues** (crashes, errors, connection problems), please attach relevant logs from a debug session. - - Launch the app with `-- --debug` and reproduce the issue. - render: shell - - - type: textarea - id: additional - attributes: - label: Additional Context - description: Any other context about the problem (MCP sources used, workspace config, etc.) diff --git a/packages/desktop/.github/ISSUE_TEMPLATE/feature_request.yml b/packages/desktop/.github/ISSUE_TEMPLATE/feature_request.yml deleted file mode 100644 index e275776d94e..00000000000 --- a/packages/desktop/.github/ISSUE_TEMPLATE/feature_request.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: Feature Request -description: Suggest a new feature or enhancement -labels: ["enhancement"] -body: - - type: markdown - attributes: - value: | - Have an idea for OpenWork? We'd love to hear it! - - - type: textarea - id: problem - attributes: - label: Problem or Motivation - description: What problem does this feature solve, or what workflow does it improve? - validations: - required: true - - - type: textarea - id: solution - attributes: - label: Proposed Solution - description: Describe the feature or change you'd like to see. - validations: - required: true - - - type: textarea - id: alternatives - attributes: - label: Alternatives Considered - description: Any workarounds or alternative approaches you've tried. - - - type: textarea - id: additional - attributes: - label: Additional Context - description: Screenshots, mockups, links, or any other context. diff --git a/packages/desktop/.github/workflows/desktop-release.yml b/packages/desktop/.github/workflows/desktop-release.yml deleted file mode 100644 index accb7ac1c21..00000000000 --- a/packages/desktop/.github/workflows/desktop-release.yml +++ /dev/null @@ -1,592 +0,0 @@ -name: Desktop Release - -run-name: Desktop release ${{ inputs.version }} - -on: - workflow_dispatch: - inputs: - version: - description: "Desktop app version to release, for example 0.0.2 or v0.0.2" - required: true - type: string - release_name: - description: "Release title. Defaults to the tag." - required: false - type: string - qwen_code_source: - description: "Qwen Code runtime source to vendor into the desktop app." - required: true - default: pinned_package_version - type: choice - options: - - npm_latest - - source_branch - - pinned_package_version - qwen_code_ref: - description: "QwenLM/qwen-code branch, tag, or commit when qwen_code_source is source_branch." - required: false - default: main - type: string - qwen_code_version: - description: "Optional exact @qwen-code/qwen-code npm version for pinned_package_version. Defaults to package.json qwenCodeRuntime.version." - required: false - type: string - dry_run: - description: "Build installers only. Do not create or update a GitHub Release." - required: true - default: true - type: boolean - draft: - description: "Create a draft release." - required: true - default: true - type: boolean - prerelease: - description: "Mark the release as a prerelease." - required: true - default: false - type: boolean - clobber: - description: "Replace same-named assets when uploading to an existing release." - required: true - default: false - type: boolean - -permissions: - contents: read - -concurrency: - group: desktop-release-${{ inputs.version }} - cancel-in-progress: false - -env: - BUN_VERSION: 1.3.9 - CRAFT_BRAND: openwork - -jobs: - release_metadata: - name: Prepare Release Source - runs-on: ubuntu-latest - timeout-minutes: 10 - permissions: - contents: write - outputs: - release_branch: ${{ steps.release-branch.outputs.branch }} - release_ref: ${{ steps.release-branch.outputs.ref }} - tag: ${{ steps.release-version.outputs.tag }} - version: ${{ steps.release-version.outputs.version }} - - steps: - - name: Check out source - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Set up Node - uses: actions/setup-node@v6 - with: - node-version-file: '.nvmrc' - - - name: Set up Bun - uses: oven-sh/setup-bun@v2 - with: - bun-version: ${{ env.BUN_VERSION }} - - - name: Install dependencies - run: bun install --frozen-lockfile - - - name: Configure Git user - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - - - name: Require main for publishing - if: ${{ inputs.dry_run == false }} - env: - SOURCE_REF: ${{ github.ref_name }} - run: | - set -euo pipefail - - if [ "$SOURCE_REF" != "main" ]; then - echo "::error::Desktop releases with dry_run=false must be run from main. Current ref: $SOURCE_REF" - exit 1 - fi - - - name: Bump desktop version - env: - INPUT_VERSION: ${{ inputs.version }} - run: bun run bump-desktop-version "$INPUT_VERSION" - - - name: Validate release version - id: release-version - env: - INPUT_VERSION: ${{ inputs.version }} - run: bun run check-release-version --version "$INPUT_VERSION" - - - name: Create release branch - id: release-branch - env: - IS_DRY_RUN: ${{ inputs.dry_run }} - RELEASE_TAG: ${{ steps.release-version.outputs.tag }} - run: | - set -euo pipefail - - branch="release/desktop-${RELEASE_TAG}" - git switch -C "$branch" - git add package.json apps/electron/package.json packages/shared/package.json - - if git diff --staged --quiet; then - echo "No desktop version changes to commit." - else - git commit -m "chore(release): desktop ${RELEASE_TAG}" - fi - - echo "branch=$branch" >> "$GITHUB_OUTPUT" - - if [ "$IS_DRY_RUN" = "false" ]; then - remote_sha="$(git ls-remote --heads origin "$branch" | awk '{print $1}')" - if [ -n "$remote_sha" ]; then - git push --force-with-lease="refs/heads/$branch:$remote_sha" origin "HEAD:refs/heads/$branch" - else - git push origin "HEAD:refs/heads/$branch" - fi - echo "ref=$branch" >> "$GITHUB_OUTPUT" - else - echo "Dry run enabled. Skipping release branch push." - echo "ref=$GITHUB_SHA" >> "$GITHUB_OUTPUT" - fi - - build: - name: Build ${{ matrix.name }} - runs-on: ${{ matrix.os }} - timeout-minutes: 90 - needs: release_metadata - env: - RELEASE_TAG: ${{ needs.release_metadata.outputs.tag }} - RELEASE_VERSION: ${{ needs.release_metadata.outputs.version }} - strategy: - fail-fast: false - matrix: - include: - - name: macOS - os: macos-latest - command: bun run dist:mac:no-publish - - name: Windows - os: windows-latest - command: bun run dist:win:no-publish - - name: Linux - os: ubuntu-22.04 - command: bun run dist:linux:no-publish - - steps: - - name: Check out source - uses: actions/checkout@v4 - with: - ref: ${{ needs.release_metadata.outputs.release_ref }} - - - name: Set up Node - uses: actions/setup-node@v6 - with: - node-version-file: '.nvmrc' - - - name: Check out Qwen Code source - if: ${{ inputs.qwen_code_source == 'source_branch' }} - shell: bash - env: - QWEN_CODE_REF_INPUT: ${{ inputs.qwen_code_ref }} - QWEN_CODE_SOURCE_ROOT: ${{ runner.temp }}/qwen-code-source - run: | - set -euo pipefail - - if [ -z "$QWEN_CODE_REF_INPUT" ]; then - echo "::error::qwen_code_ref is required when qwen_code_source is source_branch." - exit 1 - fi - - rm -rf "$QWEN_CODE_SOURCE_ROOT" - git init "$QWEN_CODE_SOURCE_ROOT" - git -C "$QWEN_CODE_SOURCE_ROOT" remote add origin https://github.com/QwenLM/qwen-code.git - - if ! git -C "$QWEN_CODE_SOURCE_ROOT" fetch --depth=1 origin "$QWEN_CODE_REF_INPUT"; then - if ! git -C "$QWEN_CODE_SOURCE_ROOT" fetch --depth=1 origin "refs/heads/$QWEN_CODE_REF_INPUT"; then - git -C "$QWEN_CODE_SOURCE_ROOT" fetch --depth=1 origin "refs/tags/$QWEN_CODE_REF_INPUT" - fi - fi - - git -C "$QWEN_CODE_SOURCE_ROOT" checkout --detach FETCH_HEAD - git config --global --add safe.directory "$QWEN_CODE_SOURCE_ROOT" - - - name: Set up Bun - uses: oven-sh/setup-bun@v2 - with: - bun-version: ${{ env.BUN_VERSION }} - - - name: Install Linux packaging dependencies - if: runner.os == 'Linux' - run: | - sudo apt-get update - sudo apt-get install -y libfuse2 - - - name: Install dependencies - run: bun install --frozen-lockfile - - - name: Install Qwen Code source dependencies - if: ${{ inputs.qwen_code_source == 'source_branch' }} - working-directory: ${{ runner.temp }}/qwen-code-source - run: npm ci - - - name: Bump desktop version - run: bun run bump-desktop-version "${{ needs.release_metadata.outputs.version }}" - - - name: Confirm release version - run: bun run check-release-version --version "${{ needs.release_metadata.outputs.version }}" - - - name: Configure Qwen Code runtime source - shell: bash - env: - QWEN_CODE_REF_INPUT: ${{ inputs.qwen_code_ref }} - QWEN_CODE_SOURCE_INPUT: ${{ inputs.qwen_code_source }} - QWEN_CODE_SOURCE_ROOT: ${{ runner.temp }}/qwen-code-source - QWEN_CODE_VERSION_INPUT: ${{ inputs.qwen_code_version }} - run: | - set -euo pipefail - - case "$QWEN_CODE_SOURCE_INPUT" in - npm_latest) - echo "QWEN_CODE_VERSION=latest" >> "$GITHUB_ENV" - echo "Using Qwen Code runtime from npm dist-tag: latest" - ;; - source_branch) - if [ -z "$QWEN_CODE_REF_INPUT" ]; then - echo "::error::qwen_code_ref is required when qwen_code_source is source_branch." - exit 1 - fi - echo "QWEN_CODE_ROOT=$QWEN_CODE_SOURCE_ROOT" >> "$GITHUB_ENV" - echo "Using Qwen Code runtime from QwenLM/qwen-code ref: $QWEN_CODE_REF_INPUT" - ;; - pinned_package_version) - if [ -n "$QWEN_CODE_VERSION_INPUT" ]; then - echo "QWEN_CODE_VERSION=$QWEN_CODE_VERSION_INPUT" >> "$GITHUB_ENV" - echo "Using exact Qwen Code npm version: $QWEN_CODE_VERSION_INPUT" - else - echo "Using Qwen Code runtime from package.json qwenCodeRuntime.version" - fi - ;; - *) - echo "::error::Unknown qwen_code_source: $QWEN_CODE_SOURCE_INPUT" - exit 1 - ;; - esac - - - name: Configure optional signing secrets - shell: bash - env: - IS_DRY_RUN: ${{ inputs.dry_run }} - APPLE_APP_SPECIFIC_PASSWORD_SECRET: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} - APPLE_ID_SECRET: ${{ secrets.APPLE_ID }} - APPLE_TEAM_ID_SECRET: ${{ secrets.APPLE_TEAM_ID }} - IS_DRAFT: ${{ inputs.draft }} - MAC_CSC_KEY_PASSWORD_SECRET: ${{ secrets.MAC_CSC_KEY_PASSWORD }} - MAC_CSC_LINK_SECRET: ${{ secrets.MAC_CSC_LINK }} - CSC_KEY_PASSWORD_SECRET: ${{ secrets.CSC_KEY_PASSWORD }} - CSC_LINK_SECRET: ${{ secrets.CSC_LINK }} - WIN_CSC_KEY_PASSWORD_SECRET: ${{ secrets.WIN_CSC_KEY_PASSWORD }} - WIN_CSC_LINK_SECRET: ${{ secrets.WIN_CSC_LINK }} - SENTRY_ELECTRON_INGEST_URL_SECRET: ${{ secrets.SENTRY_ELECTRON_INGEST_URL }} - run: | - set -euo pipefail - - append_env() { - local name="$1" - local value="$2" - - if [ -z "$value" ]; then - return - fi - - { - echo "$name<<__${name}__" - printf '%s\n' "$value" - echo "__${name}__" - } >> "$GITHUB_ENV" - } - - mac_csc_link="${MAC_CSC_LINK_SECRET:-$CSC_LINK_SECRET}" - mac_csc_key_password="${MAC_CSC_KEY_PASSWORD_SECRET:-$CSC_KEY_PASSWORD_SECRET}" - - allow_unsigned_artifacts() { - if [ "$IS_DRY_RUN" = "true" ]; then - return 0 - fi - - if [ "$IS_DRAFT" = "true" ]; then - return 0 - fi - - return 1 - } - - if [ "$RUNNER_OS" = "macOS" ]; then - if [ -n "$mac_csc_link" ]; then - if [ -z "$mac_csc_key_password" ]; then - echo "::error::MAC_CSC_LINK/CSC_LINK is configured, but MAC_CSC_KEY_PASSWORD/CSC_KEY_PASSWORD is missing." - exit 1 - fi - - append_env "CSC_LINK" "$mac_csc_link" - append_env "CSC_KEY_PASSWORD" "$mac_csc_key_password" - append_env "APPLE_ID" "$APPLE_ID_SECRET" - append_env "APPLE_APP_SPECIFIC_PASSWORD" "$APPLE_APP_SPECIFIC_PASSWORD_SECRET" - append_env "APPLE_TEAM_ID" "$APPLE_TEAM_ID_SECRET" - echo "CSC_IDENTITY_AUTO_DISCOVERY=true" >> "$GITHUB_ENV" - else - if ! allow_unsigned_artifacts; then - echo "::error::Published macOS desktop releases require MAC_CSC_LINK/CSC_LINK and MAC_CSC_KEY_PASSWORD/CSC_KEY_PASSWORD so auto-update signature validation can pass." - exit 1 - fi - - if [ "$IS_DRY_RUN" = "false" ]; then - echo "::warning::Publishing an unsigned macOS draft release for maintainer testing. Auto-update validation is not supported for this artifact." - fi - - echo "CSC_IDENTITY_AUTO_DISCOVERY=false" >> "$GITHUB_ENV" - fi - elif [ "$RUNNER_OS" = "Windows" ]; then - if [ -n "$WIN_CSC_LINK_SECRET" ]; then - if [ -z "$WIN_CSC_KEY_PASSWORD_SECRET" ]; then - echo "::error::WIN_CSC_LINK is configured, but WIN_CSC_KEY_PASSWORD is missing." - exit 1 - fi - - append_env "WIN_CSC_LINK" "$WIN_CSC_LINK_SECRET" - append_env "WIN_CSC_KEY_PASSWORD" "$WIN_CSC_KEY_PASSWORD_SECRET" - else - if ! allow_unsigned_artifacts; then - echo "::error::Published Windows desktop releases require WIN_CSC_LINK and WIN_CSC_KEY_PASSWORD." - exit 1 - fi - - if [ "$IS_DRY_RUN" = "false" ]; then - echo "::warning::Publishing an unsigned Windows draft release for maintainer testing. Windows may show Unknown Publisher / SmartScreen warnings." - else - echo "Windows signing certificate is not configured; Windows dry-run artifacts will be unsigned." - fi - fi - else - if [ "$RUNNER_OS" != "Linux" ] && [ -n "$CSC_LINK_SECRET" ]; then - echo "::warning::CSC_LINK is configured but not used on $RUNNER_OS." - fi - fi - - append_env "SENTRY_ELECTRON_INGEST_URL" "$SENTRY_ELECTRON_INGEST_URL_SECRET" - - - name: Build desktop installer - # Build jobs only produce artifacts. The publish job below owns GitHub - # Release creation/upload so dry-run, draft, prerelease, and replace - # behavior stays centralized. - run: ${{ matrix.command }} - - - name: Upload installer artifacts - uses: actions/upload-artifact@v4 - with: - name: desktop-${{ matrix.name }} - if-no-files-found: error - retention-days: 14 - path: | - apps/electron/release/*.AppImage - apps/electron/release/*.blockmap - apps/electron/release/*.dmg - apps/electron/release/*.exe - apps/electron/release/*.yml - apps/electron/release/*.zip - - publish: - name: Publish GitHub Release - runs-on: ubuntu-latest - timeout-minutes: 20 - needs: - - build - - release_metadata - if: ${{ inputs.dry_run == false }} - permissions: - contents: write - env: - RELEASE_TAG: ${{ needs.release_metadata.outputs.tag }} - RELEASE_VERSION: ${{ needs.release_metadata.outputs.version }} - - steps: - - name: Download installer artifacts - uses: actions/download-artifact@v4 - with: - path: release-assets - merge-multiple: true - - - name: Publish release assets - env: - GH_REPO: ${{ github.repository }} - GH_TOKEN: ${{ github.token }} - RELEASE_DRAFT: ${{ inputs.draft }} - RELEASE_NAME: ${{ inputs.release_name }} - RELEASE_PRERELEASE: ${{ inputs.prerelease }} - RELEASE_TARGET: ${{ needs.release_metadata.outputs.release_ref }} - UPLOAD_CLOBBER: ${{ inputs.clobber }} - run: | - set -euo pipefail - - assets=() - while IFS= read -r -d '' file; do - assets+=("$file") - done < <(find release-assets -type f -print0 | sort -z) - - if [ "${#assets[@]}" -eq 0 ]; then - echo "No release assets were downloaded." - exit 1 - fi - - printf 'Release assets:\n' - printf ' %s\n' "${assets[@]}" - - title="${RELEASE_NAME:-$RELEASE_TAG}" - - if gh release view "$RELEASE_TAG" >/dev/null 2>&1; then - upload_args=("$RELEASE_TAG" "${assets[@]}") - if [ "$UPLOAD_CLOBBER" = "true" ]; then - upload_args+=(--clobber) - fi - gh release upload "${upload_args[@]}" - else - previous_tag="$( - gh release list \ - --repo "$GH_REPO" \ - --limit 100 \ - --json tagName,isDraft,isPrerelease \ - --jq '.[] | select(.isDraft == false and .isPrerelease == false) | .tagName' \ - | grep -vxF "$RELEASE_TAG" \ - | head -n 1 \ - || true - )" - - create_args=( - "$RELEASE_TAG" - "${assets[@]}" - --generate-notes - --target "$RELEASE_TARGET" - --title "$title" - ) - if [ -n "$previous_tag" ]; then - echo "Using $previous_tag as the release notes start tag." - create_args+=(--notes-start-tag "$previous_tag") - else - echo "No previous published stable release found for release notes." - fi - if [ "$RELEASE_DRAFT" = "true" ]; then - create_args+=(--draft) - fi - if [ "$RELEASE_PRERELEASE" = "true" ]; then - create_args+=(--prerelease) - fi - gh release create "${create_args[@]}" - fi - - sync-version: - name: Sync Release Version to Main - runs-on: ubuntu-latest - timeout-minutes: 10 - needs: - - publish - - release_metadata - if: ${{ inputs.dry_run == false && inputs.draft == false && inputs.prerelease == false }} - permissions: - contents: write - pull-requests: write - - steps: - - name: Create version sync PR - id: version-pr - env: - GH_TOKEN: ${{ secrets.CI_BOT_PAT || github.token }} - RELEASE_BRANCH: ${{ needs.release_metadata.outputs.release_branch }} - RELEASE_TAG: ${{ needs.release_metadata.outputs.tag }} - run: | - set -euo pipefail - - pr_url="$(gh pr list \ - --repo "$GITHUB_REPOSITORY" \ - --head "$RELEASE_BRANCH" \ - --base main \ - --json url \ - --jq '.[0].url')" - - if [ -z "$pr_url" ]; then - pr_url="$(gh pr create \ - --repo "$GITHUB_REPOSITORY" \ - --base main \ - --head "$RELEASE_BRANCH" \ - --title "chore(release): desktop ${RELEASE_TAG}" \ - --body "Automated desktop release PR for ${RELEASE_TAG}. Syncs desktop package versions on main.")" - fi - - echo "url=$pr_url" >> "$GITHUB_OUTPUT" - - - name: Enable auto-merge - env: - GH_TOKEN: ${{ secrets.CI_BOT_PAT || github.token }} - PR_URL: ${{ steps.version-pr.outputs.url }} - RELEASE_TAG: ${{ needs.release_metadata.outputs.tag }} - run: | - set -euo pipefail - - gh pr merge "$PR_URL" \ - --squash \ - --auto \ - --delete-branch \ - --subject "chore(release): desktop ${RELEASE_TAG} [skip ci]" - - dry-run-summary: - name: Dry Run Summary - runs-on: ubuntu-latest - timeout-minutes: 10 - needs: - - build - - release_metadata - if: ${{ inputs.dry_run }} - env: - RELEASE_TAG: ${{ needs.release_metadata.outputs.tag }} - RELEASE_VERSION: ${{ needs.release_metadata.outputs.version }} - - steps: - - name: Download installer artifacts - uses: actions/download-artifact@v4 - with: - path: release-assets - merge-multiple: true - - - name: List release assets - run: | - set -euo pipefail - - assets=() - while IFS= read -r -d '' file; do - assets+=("$file") - done < <(find release-assets -type f -print0 | sort -z) - - if [ "${#assets[@]}" -eq 0 ]; then - echo "No release assets were downloaded." - exit 1 - fi - - { - echo "## Desktop release dry run" - echo - echo "Version: $RELEASE_VERSION" - echo "Release tag: $RELEASE_TAG" - echo - echo "Built ${#assets[@]} asset(s). No GitHub Release was created or updated." - echo - echo "| Asset | Size |" - echo "| --- | ---: |" - for file in "${assets[@]}"; do - size=$(du -h "$file" | cut -f1) - echo "| $(basename "$file") | $size |" - done - } >> "$GITHUB_STEP_SUMMARY" diff --git a/packages/desktop/.gitignore b/packages/desktop/.gitignore deleted file mode 100644 index 8c0b6f7574a..00000000000 --- a/packages/desktop/.gitignore +++ /dev/null @@ -1,75 +0,0 @@ -# dependencies (bun install) -node_modules - -# output -out -dist -*.tgz -.build -apps/electron/release -apps/electron/electron-builder.generated.yml -apps/electron/resources/session-mcp-server/ -apps/electron/resources/pi-agent-server/ - -# code coverage -coverage -*.lcov - -# logs -logs -_.log -report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json - -# dotenv environment variable files -.env -.env.development.local -.env.test.local -.env.production.local -.env.local - -# caches -.eslintcache -.cache -*.tsbuildinfo -.wrangler -.mintlify - -# OSS sync temp directory -.oss-sync-temp/ - -# IntelliJ based IDEs -.idea - -# Python bytecode -__pycache__/ -*.pyc - -# Finder (MacOS) folder config -.DS_Store - -# Claude Code local settings (user-specific) -.claude/settings.local.json - -# Accidental literal tilde directories (Node.js doesn't expand ~) -~/ - -# Lock files from other package managers -pnpm-lock.yaml - -# Craft Agent local data (sessions, credentials, config) -.craft-agent/ - -# SSH keys and secrets -*.pem -*.key -sshkey -id_rsa* -id_ed25519* - -# Credentials files -credentials.enc -*.credentials - -# act (local GitHub Actions runner) -.secrets -.actrc diff --git a/packages/desktop/.nvmrc b/packages/desktop/.nvmrc deleted file mode 100644 index 2bd5a0a98a3..00000000000 --- a/packages/desktop/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -22 diff --git a/packages/desktop/CODE_OF_CONDUCT.md b/packages/desktop/CODE_OF_CONDUCT.md deleted file mode 100644 index 81156dcc986..00000000000 --- a/packages/desktop/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,28 +0,0 @@ -# Code of Conduct - -## Our Pledge - -We are committed to providing a welcoming and inclusive environment for everyone who participates in OpenWork. - -## Our Standards - -We expect all participants to: - -- Be respectful and considerate -- Welcome newcomers and help them learn -- Accept constructive feedback gracefully -- Focus on what is best for the community - -## Enforcement - -OpenWork does not currently maintain a dedicated private conduct-reporting inbox. - -If you need to report unacceptable behavior, please contact a repository maintainer through the current GitHub repository channels. If the report includes sensitive personal information, avoid posting those details publicly and ask a maintainer for a private contact path first. - -Reports will be reviewed in good faith by the maintainers who are currently responsible for the project. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org/), version 2.1. - -For the full text, see: https://www.contributor-covenant.org/version/2/1/code_of_conduct/ diff --git a/packages/desktop/CONTRIBUTING.md b/packages/desktop/CONTRIBUTING.md deleted file mode 100644 index aa5faa3d9aa..00000000000 --- a/packages/desktop/CONTRIBUTING.md +++ /dev/null @@ -1,103 +0,0 @@ -# Contributing to OpenWork - -Thank you for your interest in contributing to OpenWork. This guide covers the -local development workflow for the desktop app and shared packages. - -## Prerequisites - -- [Bun](https://bun.sh/) 1.3 or newer -- Node.js 22 or newer for the Qwen Code runtime and related tooling -- macOS, Linux, or Windows - -## Development Setup - -1. Clone the repository: - - ```bash - git clone https://github.com/modelstudioai/openwork.git - cd openwork - ``` - -2. Install dependencies: - - ```bash - bun install - ``` - -3. Start the Electron app in development mode: - - ```bash - CRAFT_BRAND=modelstudio bun run electron:dev - ``` - - To run against a local Qwen Code checkout instead of a vendored or npm - runtime, pass the source root: - - ```bash - CRAFT_BRAND=modelstudio \ - QWEN_CODE_ROOT=/path/to/qwen-code \ - bun run electron:dev - ``` - -## Useful Commands - -Run focused checks whenever possible: - -```bash -bun run typecheck:shared -bun run typecheck:electron -bun run typecheck:all -``` - -Build the desktop app resources: - -```bash -bun run electron:build -``` - -Package a dev build: - -```bash -CRAFT_DEV_RUNTIME=1 bun run electron:dist:dev:mac -``` - -## Project Structure - -```text -openwork/ -├── apps/ -│ ├── electron/ # Electron desktop app -│ ├── cli/ # Command-line entry points -│ ├── viewer/ # Shared session viewer -│ └── webui/ # Web UI build -├── packages/ -│ ├── shared/ # Shared app logic and protocol types -│ ├── server-core/ # Server/session orchestration -│ ├── server/ # Server entry point -│ ├── ui/ # React UI components -│ └── core/ # Shared lower-level utilities -└── scripts/ # Build, dev, and packaging scripts -``` - -## Contribution Guidelines - -- Keep changes focused and minimal. -- Follow existing TypeScript and React patterns. -- Prefer existing shared helpers over introducing new abstractions. -- Include screenshots or a short screen recording for visible UI changes. -- Mention the commands you ran in the PR description. -- Do not include generated build artifacts unless the project explicitly tracks - them. - -## Pull Requests - -1. Create a branch from the target branch. -2. Make the smallest change that solves the problem. -3. Run the relevant focused checks. -4. Open a pull request with a clear summary, testing notes, and screenshots for - UI changes. - -## License - -By contributing, you agree that your contributions are licensed under the same -license as this repository. diff --git a/packages/desktop/LICENSE b/packages/desktop/LICENSE deleted file mode 100644 index 557a2f9750f..00000000000 --- a/packages/desktop/LICENSE +++ /dev/null @@ -1,191 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to the Licensor for inclusion in the Work by the copyright - owner or by an individual or Legal Entity authorized to submit on - behalf of the copyright owner. For the purposes of this definition, - "submitted" means any form of electronic, verbal, or written - communication sent to the Licensor or its representatives, including - but not limited to communication on electronic mailing lists, source - code control systems, and issue tracking systems that are managed by, - or on behalf of, the Licensor for the purpose of discussing and - improving the Work, but excluding communication that is conspicuously - marked or otherwise designated in writing by the copyright owner as - "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - Copyright 2026 Craft Docs Ltd. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/packages/desktop/NOTICE b/packages/desktop/NOTICE deleted file mode 100644 index ac4a5f93d70..00000000000 --- a/packages/desktop/NOTICE +++ /dev/null @@ -1,23 +0,0 @@ -Craft Agents -Copyright 2026 Craft Docs Ltd. - -This product includes software developed by Craft Docs Ltd. -https://craft.do - ---- - -This project uses the Claude Agent SDK, which is subject to Anthropic's -Commercial Terms of Service: https://www.anthropic.com/legal/commercial-terms - ---- - -OpenWork -Copyright 2026 Model Studio AI - -OpenWork includes modifications and extensions developed by Model Studio AI. -https://github.com/modelstudioai/openwork - ---- - -Third-party dependencies are listed in package.json files and are subject -to their respective licenses. diff --git a/packages/desktop/README.md b/packages/desktop/README.md deleted file mode 100644 index a4745ae03e2..00000000000 --- a/packages/desktop/README.md +++ /dev/null @@ -1,189 +0,0 @@ -# Qwen Code - -Qwen Code is a desktop and headless agent workspace. It provides multi-session chat, source connections, skills, file previews, automations, and permission modes in a local-first application. - -## Backend - -This fork is Qwen-only: - -- Agent sessions run through Qwen Code over ACP. -- The app does not store third-party LLM API keys. -- The built-in LLM connection is `qwen-code`. -- Legacy multi-provider backends and package/runtime wiring have been removed. - -## Qwen Code CLI Runtime - -The desktop app talks to the Qwen Code CLI over ACP. Treat the CLI as a -runtime artifact, not as desktop source code. A packaged app must bundle a -known CLI build so users can launch it without installing `qwen` separately. - -Use one of these workflows depending on what you are developing: - -| Workflow | Use it when | Commands | -| --------------------------- | ------------------------------------------------------------------------- | -------------------------------------------------------------------- | -| Default desktop development | You are developing desktop only. | `bun run dev` | -| Published npm package | You want a specific published CLI version for dev, CI, or release builds. | `QWEN_CODE_VERSION=0.15.12-acp.0 bun run dev` | -| Local npm tarball | You need to verify the exact package contents before publishing. | `QWEN_CODE_TARBALL=/path/to/qwen-code-0.15.12-acp.0.tgz bun run dev` | -| Local qwen-code checkout | You are changing ACP or other CLI behavior while testing desktop. | `QWEN_CODE_ROOT=/path/to/qwen-code bun run dev` | -| Explicit CLI entry | You need to point at a specific CLI file. | `QWEN_CODE_CLI=/path/to/qwen-code/scripts/dev.js bun run dev` | - -`electron:dev` uses local overrides first. If no override is set and this -repository is not inside the qwen-code monorepo, it vendors the default version -from `qwenCodeRuntime.version` in `package.json` and points Electron at the -vendored CLI automatically. - -If you are preparing a package without publishing it, create the tarball from -the Qwen Code repository and point desktop at it: - -```bash -cd /path/to/qwen-code -npm run build -npm run bundle -npm run prepare:package -npm pack - -cd /path/to/desktop -QWEN_CODE_TARBALL=/path/to/qwen-code/qwen-code-0.15.12-acp.0.tgz bun run dist:mac -``` - -Distribution builds run `electron:vendor:qwen` automatically. Set -`QWEN_CODE_VERSION` or `QWEN_CODE_TARBALL` when you want the packaged app to use -a published or packed CLI artifact. If neither is set, this monorepo builds -from the local checkout; a standalone desktop checkout uses -`qwenCodeRuntime.version` from `package.json`. - -Development runtime resolution checks sources in this order: - -```text -QWEN_CODE_CLI / QWEN_CODE_ROOT / QWEN_CODE_PATH -QWEN_CODE_TARBALL -QWEN_CODE_VERSION -local monorepo checkout -existing vendored CLI -qwenCodeRuntime.version from package.json -``` - -Distribution vendoring checks sources in this order: - -```text -QWEN_CODE_TARBALL -QWEN_CODE_VERSION -QWEN_CODE_ROOT / QWEN_CODE_PATH -local monorepo checkout -qwenCodeRuntime.version from package.json -``` - -## Installation - -```bash -bun install -bun run dev -``` - -## Common Commands - -```bash -bun run typecheck:all -bun run test:shared -bun run dev -bun run server:start -``` - -## Building for Distribution - -All build commands run from `packages/desktop/`. - -### Prerequisites - -- [Bun](https://bun.sh) (see `.bun-version` for exact version) -- `bun install` — install all workspace dependencies - -### Developer Build (no code signing) - -Use this for local testing. Produces an ad-hoc signed app. - -```bash -# macOS (arm64 + x64) -bun run electron:dist:dev:mac - -# Windows -bun run electron:dist:dev:win - -# Linux -bun run electron:dist:dev:linux -``` - -### Release Build (with code signing) - -```bash -bun run electron:dist:mac -bun run electron:dist:win -bun run electron:dist:linux -``` - -Release builds require signing credentials via environment variables: - -| Variable | Purpose | -| ----------------------------- | --------------------------- | -| `CSC_LINK` | Path to signing certificate | -| `APPLE_ID` | Apple ID for notarization | -| `APPLE_APP_SPECIFIC_PASSWORD` | App-specific password | -| `APPLE_TEAM_ID` | Team ID for notarization | - -### Build Output - -All artifacts are written to `apps/electron/release/`: - -| Platform | Artifact | -| -------- | ------------------------------------------------------------------------ | -| macOS | `Qwen-Code-Desktop-{arm64,x64}.dmg`, `Qwen-Code-Desktop-{arm64,x64}.zip` | -| Windows | `Qwen-Code-Desktop-x64.exe` | -| Linux | `Qwen-Code-Desktop-x64.AppImage` | - -### What the Build Does - -Each `electron:dist:*` command runs three stages: - -1. **`electron:vendor:qwen`** — vendors a Qwen Code CLI runtime into `vendor/qwen-code/`. Set `QWEN_CODE_VERSION` to download a published npm version, or `QWEN_CODE_TARBALL` to use a local `npm pack` tarball. If neither is set in this monorepo, it builds from the local checkout. -2. **`electron:build`** — compiles the app via esbuild (main + preload), Vite (renderer), and copies resources/assets. -3. **`electron-builder`** — downloads the Electron runtime, packages the app, signs it, and produces distributable installers (DMG, NSIS, AppImage). - -## CLI - -```bash -bun run apps/cli/src/index.ts run "Hello from Qwen" -bun run apps/cli/src/index.ts run --workspace-dir ./project "Summarize this repo" -``` - -The `run` command spawns a headless server, creates a temporary session, streams the response, and exits. Provider flags are accepted only for compatibility; the backend remains Qwen Code. - -## Repository Layout - -```text -apps/ - electron/ Desktop app - cli/ Terminal client - webui/ Web adapter -packages/ - shared/ Agent, config, prompts, sessions, sources - server-core/ RPC handlers and session manager - core/ Shared types - ui/ Shared UI components - session-tools-core/ - session-mcp-server/ -scripts/ Build and packaging helpers -``` - -## Capabilities - -- Multi-session inbox with streaming responses and tool visualization -- Qwen Code model discovery through ACP -- MCP, REST API, and local filesystem sources -- Skills stored per workspace -- Permission modes for planning, asking before edits, and autonomous execution -- File attachments and in-app previews for images, PDFs, Office files, and diffs -- Event-driven automations and messaging integrations - -## License - -Apache 2.0. Third-party dependencies are listed in package manifests and are subject to their respective licenses. diff --git a/packages/desktop/SECURITY.md b/packages/desktop/SECURITY.md deleted file mode 100644 index 65d144b77bb..00000000000 --- a/packages/desktop/SECURITY.md +++ /dev/null @@ -1,58 +0,0 @@ -# Security Policy - -## Reporting a Vulnerability - -We take security seriously. If you discover a security vulnerability in OpenWork, please report it responsibly. - -### How to Report - -Please do not publish exploit details in public GitHub issues. - -OpenWork does not currently maintain a dedicated security email address. If GitHub private vulnerability reporting is available for this repository, use that channel. Otherwise, open a minimal public issue that requests maintainer contact without including exploit details, secrets, or proof-of-concept code. - -Include the following information: - -- Description of the vulnerability -- Steps to reproduce the issue -- Potential impact -- Any suggested fixes (optional) - -### What to Expect - -Maintainers will review reports on a best-effort basis. Response and resolution timelines depend on maintainer availability and the severity of the issue. - -### Scope - -This policy applies to: - -- The OpenWork desktop application -- OpenWork server and shared packages -- Official OpenWork repositories - -### Out of Scope - -- Third-party dependencies (report to their maintainers) -- Social engineering attacks -- Denial of service attacks - -## Supported Versions - -| Version | Supported | -| -------- | ------------------ | -| Latest | :white_check_mark: | -| < Latest | :x: | - -We currently provide security updates for the latest version only. Please keep your installation up to date. - -## Security Best Practices - -When using OpenWork: - -1. **Keep credentials secure**: Never commit `.env` files or credentials -2. **Use environment variables**: Store secrets in environment variables -3. **Review permissions**: Be cautious with "Execute" permission mode -4. **Update regularly**: Keep the application updated - -## Acknowledgments - -We appreciate responsible disclosure and will acknowledge security researchers who report valid vulnerabilities (with their permission). diff --git a/packages/desktop/TRADEMARK.md b/packages/desktop/TRADEMARK.md deleted file mode 100644 index 4911ce43927..00000000000 --- a/packages/desktop/TRADEMARK.md +++ /dev/null @@ -1,103 +0,0 @@ -# Trademark Policy - -This trademark policy describes how the OpenWork name, Model Studio AI branding, and related project identifiers may be used. - -## Trademarks - -The following names and branding are associated with this project: - -- **OpenWork** -- **Model Studio AI** -- OpenWork logos, icons, and visual branding - -## What You Can Do - -### Use the Code Freely - -The OpenWork source code is licensed under the Apache License 2.0. You are free to: - -- Use, modify, and distribute the code -- Create derivative works -- Use the software for any purpose, including commercial use - -The license for the code does not grant permission to use OpenWork or Model Studio AI branding in a way that suggests endorsement or official status. - -### Make Factual Statements - -You may make accurate, factual statements about your relationship to the project: - -- "Based on OpenWork" -- "Built with OpenWork technology" -- "Compatible with OpenWork" -- "Fork of OpenWork" - -You may also make accurate attribution statements about upstream projects, such as Craft Agents OSS and Qwen Code, when describing the technical foundations of OpenWork. - -### Contribute to the Project - -Contributors may use the project name when discussing their contributions to OpenWork. - -## What You Cannot Do - -### Use OpenWork or Model Studio AI Branding for Forks - -If you create a fork or derivative work, you **must**: - -- Choose a different name that does not imply it is the official OpenWork project -- Remove or replace OpenWork and Model Studio AI logos and icons -- Update bundle identifiers, package metadata, and product names to your own -- Avoid using Model Studio AI branding unless you have explicit permission - -### Imply Official Endorsement - -You may not: - -- Use "OpenWork" or "Model Studio AI" as your product name in a confusing way -- Use OpenWork or Model Studio AI logos as your application icon -- Suggest that your fork is the official version -- Imply that Model Studio AI endorses your product - -### Create Confusion - -You may not use the trademarks in any way that: - -- Suggests your product is created by or affiliated with Model Studio AI -- Could cause confusion between your product and the official OpenWork project -- Misrepresents the relationship between your product, OpenWork, Model Studio AI, Craft Agents OSS, or Qwen Code - -## Branding Locations - -For those creating forks, the following files contain branding that should be updated: - -| File | Contains | -| ------------------------------------ | ---------------------------------- | -| `apps/electron/electron-builder.yml` | Product name, bundle ID, copyright | -| `apps/electron/resources/` | Application icons | -| `packages/shared/src/branding.ts` | Product name, bundle ID, URLs | - -## Examples - -### Acceptable - -- "MyAgent - based on OpenWork" -- "This project is a fork of OpenWork" -- "Compatible with OpenWork" - -### Not Acceptable - -- "OpenWork Pro" -- "OpenWork for Linux" -- "Better OpenWork" -- Using OpenWork or Model Studio AI logos for your fork without permission - -## Questions - -OpenWork does not currently maintain a dedicated trademark or legal contact email. If you have questions about this policy or want to request permission for a specific use, please open an issue in the repository. - -## Changes - -This policy may be updated from time to time. The current version will always be available in this repository. - ---- - -_This trademark policy is inspired by similar policies from Mozilla, WordPress, and the Apache Software Foundation._ diff --git a/packages/desktop/apps/cli/package.json b/packages/desktop/apps/cli/package.json deleted file mode 100644 index ce6649fc404..00000000000 --- a/packages/desktop/apps/cli/package.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "@craft-agent/cli", - "version": "0.0.1", - "license": "Apache-2.0", - "description": "Terminal client for Qwen Code server", - "type": "module", - "main": "src/index.ts", - "bin": { - "craft-cli": "src/index.ts" - }, - "scripts": { - "start": "bun run src/index.ts", - "typecheck": "tsc --noEmit", - "test": "bun test src/" - }, - "dependencies": { - "@craft-agent/shared": "workspace:*", - "@craft-agent/server-core": "workspace:*" - }, - "devDependencies": { - "typescript": "^5.8.2", - "@types/node": "^22.0.0", - "@types/bun": "latest" - } -} diff --git a/packages/desktop/apps/cli/src/client.test.ts b/packages/desktop/apps/cli/src/client.test.ts deleted file mode 100644 index 4ebef9a2f58..00000000000 --- a/packages/desktop/apps/cli/src/client.test.ts +++ /dev/null @@ -1,409 +0,0 @@ -import { describe, it, expect, afterEach } from 'bun:test' -import { CliRpcClient } from './client.ts' -import { - serializeEnvelope, - deserializeEnvelope, -} from '@craft-agent/server-core/transport' -import type { MessageEnvelope } from '@craft-agent/shared/protocol' - -// --------------------------------------------------------------------------- -// Mock WS server helpers -// --------------------------------------------------------------------------- - -interface MockServer { - url: string - port: number - close: () => void - lastMessage: () => MessageEnvelope | null - sendToAll: (envelope: MessageEnvelope) => void -} - -function createMockServer(opts?: { - rejectAuth?: boolean - noAck?: boolean - tls?: { cert: string; key: string } -}): MockServer { - let lastMsg: MessageEnvelope | null = null - const clients = new Set() - - const server = Bun.serve({ - port: 0, - tls: opts?.tls, - fetch(req, server) { - if (server.upgrade(req)) return undefined - return new Response('Not found', { status: 404 }) - }, - websocket: { - message(ws, message) { - const raw = typeof message === 'string' ? message : new TextDecoder().decode(message) - const envelope = deserializeEnvelope(raw) - lastMsg = envelope - - if (envelope.type === 'handshake') { - if (opts?.rejectAuth) { - const error: MessageEnvelope = { - id: envelope.id, - type: 'error', - error: { code: 'AUTH_FAILED', message: 'Invalid token' }, - } - ws.send(serializeEnvelope(error)) - ws.close() - return - } - - if (opts?.noAck) return // Simulate timeout - - const ack: MessageEnvelope = { - id: crypto.randomUUID(), - type: 'handshake_ack', - clientId: 'test-client-001', - protocolVersion: '1.0', - } - ws.send(serializeEnvelope(ack)) - return - } - - if (envelope.type === 'request') { - // Default: echo args back as result - const response: MessageEnvelope = { - id: envelope.id, - type: 'response', - channel: envelope.channel, - result: envelope.args, - } - ws.send(serializeEnvelope(response)) - } - }, - open(ws) { - clients.add(ws) - }, - close(ws) { - clients.delete(ws) - }, - }, - }) - - const protocol = opts?.tls ? 'wss' : 'ws' - const port = server.port! - return { - url: `${protocol}://127.0.0.1:${port}`, - port, - close: () => server.stop(true), - lastMessage: () => lastMsg, - sendToAll: (envelope: MessageEnvelope) => { - const data = serializeEnvelope(envelope) - for (const ws of clients) ws.send(data) - }, - } -} - -function createErrorServer(): MockServer { - let lastMsg: MessageEnvelope | null = null - const clients = new Set() - - const server = Bun.serve({ - port: 0, - fetch(req, server) { - if (server.upgrade(req)) return undefined - return new Response('Not found', { status: 404 }) - }, - websocket: { - message(ws, message) { - const raw = typeof message === 'string' ? message : new TextDecoder().decode(message) - const envelope = deserializeEnvelope(raw) - lastMsg = envelope - - if (envelope.type === 'handshake') { - const ack: MessageEnvelope = { - id: crypto.randomUUID(), - type: 'handshake_ack', - clientId: 'test-client-err', - protocolVersion: '1.0', - } - ws.send(serializeEnvelope(ack)) - return - } - - if (envelope.type === 'request') { - // Respond with error - const response: MessageEnvelope = { - id: envelope.id, - type: 'response', - channel: envelope.channel, - error: { code: 'HANDLER_ERROR', message: 'test error' }, - } - ws.send(serializeEnvelope(response)) - } - }, - open(ws) { - clients.add(ws) - }, - close(ws) { - clients.delete(ws) - }, - }, - }) - - const port = server.port! - return { - url: `ws://127.0.0.1:${port}`, - port, - close: () => server.stop(true), - lastMessage: () => lastMsg, - sendToAll: (envelope: MessageEnvelope) => { - const data = serializeEnvelope(envelope) - for (const ws of clients) ws.send(data) - }, - } -} - -// --------------------------------------------------------------------------- -// Tests -// --------------------------------------------------------------------------- - -let server: MockServer | null = null - -afterEach(() => { - server?.close() - server = null -}) - -describe('CliRpcClient', () => { - it('connects and completes handshake', async () => { - server = createMockServer() - const client = new CliRpcClient(server.url, { token: 'test-token' }) - const clientId = await client.connect() - expect(clientId).toBe('test-client-001') - expect(client.isConnected).toBe(true) - expect(client.clientId).toBe('test-client-001') - client.destroy() - }) - - it('sends token in handshake', async () => { - server = createMockServer() - const client = new CliRpcClient(server.url, { token: 'my-secret' }) - await client.connect() - const hs = server.lastMessage() - expect(hs?.type).toBe('handshake') - expect(hs?.token).toBe('my-secret') - client.destroy() - }) - - it('rejects on auth failure', async () => { - server = createMockServer({ rejectAuth: true }) - const client = new CliRpcClient(server.url, { token: 'bad-token' }) - await expect(client.connect()).rejects.toThrow('Invalid token') - client.destroy() - }) - - it('rejects on connect timeout', async () => { - server = createMockServer({ noAck: true }) - const client = new CliRpcClient(server.url, { connectTimeout: 200 }) - await expect(client.connect()).rejects.toThrow('Connection timeout') - client.destroy() - }) - - it('invoke sends request and receives response', async () => { - server = createMockServer() - const client = new CliRpcClient(server.url) - await client.connect() - const result = await client.invoke('system:homeDir') - // Mock server echoes args — no args means empty array - expect(result).toEqual([]) - client.destroy() - }) - - it('invoke passes args correctly', async () => { - server = createMockServer() - const client = new CliRpcClient(server.url) - await client.connect() - const result = await client.invoke('sessions:get', 'workspace-1') - expect(result).toEqual(['workspace-1']) - client.destroy() - }) - - it('invoke rejects on server error', async () => { - server = createErrorServer() - const client = new CliRpcClient(server.url) - await client.connect() - await expect(client.invoke('system:versions')).rejects.toThrow('test error') - client.destroy() - }) - - it('invoke rejects on timeout', async () => { - server = createMockServer({ noAck: false }) - // Create a server that acks handshake but never responds to requests - server.close() - - const silentServer = Bun.serve({ - port: 0, - fetch(req, svr) { - if (svr.upgrade(req)) return undefined - return new Response('Not found', { status: 404 }) - }, - websocket: { - message(ws, message) { - const raw = typeof message === 'string' ? message : new TextDecoder().decode(message) - const envelope = deserializeEnvelope(raw) - if (envelope.type === 'handshake') { - const ack: MessageEnvelope = { - id: crypto.randomUUID(), - type: 'handshake_ack', - clientId: 'silent-client', - protocolVersion: '1.0', - } - ws.send(serializeEnvelope(ack)) - } - // Never respond to requests - }, - }, - }) - - const client = new CliRpcClient(`ws://127.0.0.1:${silentServer.port}`, { requestTimeout: 200 }) - await client.connect() - await expect(client.invoke('system:homeDir')).rejects.toThrow('Request timeout') - client.destroy() - silentServer.stop(true) - }) - - it('invoke throws when not connected', async () => { - const client = new CliRpcClient('ws://127.0.0.1:1') - await expect(client.invoke('system:homeDir')).rejects.toThrow('Not connected') - client.destroy() - }) - - it('receives push events via on()', async () => { - server = createMockServer() - const client = new CliRpcClient(server.url) - await client.connect() - - const events: unknown[][] = [] - const unsub = client.on('session:event', (...args) => { - events.push(args) - }) - - // Push an event from server - server.sendToAll({ - id: crypto.randomUUID(), - type: 'event', - channel: 'session:event', - args: [{ type: 'text_delta', sessionId: 's1', delta: 'hello' }], - }) - - // Give it a tick - await new Promise((r) => setTimeout(r, 50)) - - expect(events.length).toBe(1) - expect((events[0][0] as any).delta).toBe('hello') - - // Unsubscribe stops delivery - unsub() - server.sendToAll({ - id: crypto.randomUUID(), - type: 'event', - channel: 'session:event', - args: [{ type: 'text_delta', sessionId: 's1', delta: 'world' }], - }) - - await new Promise((r) => setTimeout(r, 50)) - expect(events.length).toBe(1) // Still 1 - client.destroy() - }) - - it('destroy closes connection and rejects pending', async () => { - server = createMockServer({ noAck: false }) - // Use a server that acks but never responds - server.close() - - const silentServer = Bun.serve({ - port: 0, - fetch(req, svr) { - if (svr.upgrade(req)) return undefined - return new Response('Not found', { status: 404 }) - }, - websocket: { - message(ws, message) { - const raw = typeof message === 'string' ? message : new TextDecoder().decode(message) - const envelope = deserializeEnvelope(raw) - if (envelope.type === 'handshake') { - ws.send(serializeEnvelope({ - id: crypto.randomUUID(), - type: 'handshake_ack', - clientId: 'destroy-test', - protocolVersion: '1.0', - })) - } - }, - }, - }) - - const client = new CliRpcClient(`ws://127.0.0.1:${silentServer.port}`, { requestTimeout: 5000 }) - await client.connect() - - const pending = client.invoke('system:homeDir') - client.destroy() - - await expect(pending).rejects.toThrow('Client destroyed') - expect(client.isConnected).toBe(false) - silentServer.stop(true) - }) - - it('throws on invoke after destroy', async () => { - server = createMockServer() - const client = new CliRpcClient(server.url) - await client.connect() - client.destroy() - await expect(client.invoke('system:homeDir')).rejects.toThrow('Not connected') - }) - - it('connects over wss:// with TLS', async () => { - const tls = generateSelfSignedCert() - if (!tls) { - // openssl not available — skip TLS test - console.log(' (skipped: openssl not available)') - return - } - server = createMockServer({ tls }) - - const prev = process.env.NODE_TLS_REJECT_UNAUTHORIZED - process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0' - try { - const client = new CliRpcClient(server.url) - const clientId = await client.connect() - expect(clientId).toBe('test-client-001') - expect(server.url.startsWith('wss://')).toBe(true) - client.destroy() - } finally { - if (prev === undefined) { - delete process.env.NODE_TLS_REJECT_UNAUTHORIZED - } else { - process.env.NODE_TLS_REJECT_UNAUTHORIZED = prev - } - } - }) -}) - -// --------------------------------------------------------------------------- -// TLS cert helper — generates a real self-signed cert via openssl -// --------------------------------------------------------------------------- - -function generateSelfSignedCert(): { cert: string; key: string } | null { - try { - const keyResult = Bun.spawnSync({ - cmd: ['openssl', 'req', '-x509', '-newkey', 'ec', '-pkeyopt', 'ec_paramgen_curve:prime256v1', - '-keyout', '/dev/stdout', '-out', '/dev/stdout', - '-days', '1', '-nodes', '-subj', '/CN=localhost', '-batch'], - stderr: 'pipe', - }) - if (keyResult.exitCode !== 0) return null - - const pem = keyResult.stdout.toString() - const certMatch = pem.match(/(-----BEGIN CERTIFICATE-----[\s\S]+?-----END CERTIFICATE-----)/) - const keyMatch = pem.match(/(-----BEGIN (?:EC )?PRIVATE KEY-----[\s\S]+?-----END (?:EC )?PRIVATE KEY-----)/) - if (!certMatch || !keyMatch) return null - - return { cert: certMatch[1], key: keyMatch[1] } - } catch { - return null - } -} diff --git a/packages/desktop/apps/cli/src/client.ts b/packages/desktop/apps/cli/src/client.ts deleted file mode 100644 index 2a28267b2e2..00000000000 --- a/packages/desktop/apps/cli/src/client.ts +++ /dev/null @@ -1,239 +0,0 @@ -/** - * CliRpcClient — Minimal WebSocket RPC client for CLI usage. - * - * Stripped-down version of WsRpcClient: no auto-reconnect, no capabilities, - * no connection state listeners. Connect, work, exit. - */ - -import { - PROTOCOL_VERSION, - type MessageEnvelope, -} from '@craft-agent/shared/protocol' -import { - serializeEnvelope, - deserializeEnvelope, -} from '@craft-agent/server-core/transport' - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -interface PendingRequest { - resolve: (value: unknown) => void - reject: (error: Error) => void - timeout: ReturnType -} - -export interface CliClientOptions { - token?: string - workspaceId?: string - requestTimeout?: number - connectTimeout?: number -} - -// --------------------------------------------------------------------------- -// Client -// --------------------------------------------------------------------------- - -export class CliRpcClient { - private ws: WebSocket | null = null - private pending = new Map() - private listeners = new Map void>>() - private _clientId: string | null = null - private _connected = false - private _destroyed = false - - private readonly url: string - private readonly token: string | undefined - private readonly workspaceId: string | undefined - private readonly requestTimeout: number - private readonly connectTimeout: number - - constructor(url: string, opts?: CliClientOptions) { - this.url = url - this.token = opts?.token - this.workspaceId = opts?.workspaceId - this.requestTimeout = opts?.requestTimeout ?? 10_000 - this.connectTimeout = opts?.connectTimeout ?? 10_000 - } - - /** Connect to the server and complete the handshake. Returns the assigned clientId. */ - async connect(): Promise { - if (this._destroyed) throw new Error('Client destroyed') - - return new Promise((resolve, reject) => { - const timer = setTimeout(() => { - reject(new Error(`Connection timeout (${this.connectTimeout}ms)`)) - this.ws?.close() - }, this.connectTimeout) - - this.ws = new WebSocket(this.url) - - this.ws.onopen = () => { - const handshake: MessageEnvelope = { - id: crypto.randomUUID(), - type: 'handshake', - protocolVersion: PROTOCOL_VERSION, - workspaceId: this.workspaceId, - token: this.token, - } - this.ws!.send(serializeEnvelope(handshake)) - } - - this.ws.onmessage = (event) => { - const raw = typeof event.data === 'string' ? event.data : String(event.data) - let envelope: MessageEnvelope - try { - envelope = deserializeEnvelope(raw) - } catch { - return - } - - if (envelope.type === 'handshake_ack') { - clearTimeout(timer) - this._clientId = envelope.clientId ?? null - this._connected = true - // Switch to normal message handler - this.ws!.onmessage = (e) => { - this.onMessage(typeof e.data === 'string' ? e.data : String(e.data)) - } - resolve(this._clientId!) - } else if (envelope.type === 'error') { - clearTimeout(timer) - const err = new Error(envelope.error?.message ?? 'Connection rejected') - ;(err as any).code = envelope.error?.code - reject(err) - } - } - - this.ws.onerror = () => { - if (!this._connected) { - clearTimeout(timer) - reject(new Error('WebSocket connection error')) - } - } - - this.ws.onclose = () => { - if (!this._connected) { - clearTimeout(timer) - reject(new Error('WebSocket closed before handshake')) - } - this._connected = false - for (const [, req] of this.pending) { - clearTimeout(req.timeout) - req.reject(new Error('Disconnected')) - } - this.pending.clear() - } - }) - } - - /** Send an RPC request and await the response. */ - async invoke(channel: string, ...args: unknown[]): Promise { - if (!this._connected || !this.ws) { - throw new Error(`Not connected (channel: ${channel})`) - } - - return new Promise((resolve, reject) => { - const id = crypto.randomUUID() - const timeout = setTimeout(() => { - this.pending.delete(id) - reject(new Error(`Request timeout: ${channel} (${this.requestTimeout}ms)`)) - }, this.requestTimeout) - - this.pending.set(id, { resolve, reject, timeout }) - - const envelope: MessageEnvelope = { - id, - type: 'request', - channel, - args, - } - this.ws!.send(serializeEnvelope(envelope)) - }) - } - - /** Subscribe to push events on a channel. Returns an unsubscribe function. */ - on(channel: string, callback: (...args: unknown[]) => void): () => void { - let set = this.listeners.get(channel) - if (!set) { - set = new Set() - this.listeners.set(channel, set) - } - set.add(callback) - - return () => { - set!.delete(callback) - if (set!.size === 0) this.listeners.delete(channel) - } - } - - /** Close the connection and reject all pending requests. */ - destroy(): void { - this._destroyed = true - for (const [, req] of this.pending) { - clearTimeout(req.timeout) - req.reject(new Error('Client destroyed')) - } - this.pending.clear() - this.ws?.close() - this.ws = null - this._connected = false - } - - get isConnected(): boolean { - return this._connected - } - - get clientId(): string | null { - return this._clientId - } - - // ------------------------------------------------------------------------- - // Internal message routing - // ------------------------------------------------------------------------- - - private onMessage(raw: string): void { - let envelope: MessageEnvelope - try { - envelope = deserializeEnvelope(raw) - } catch { - return - } - - switch (envelope.type) { - case 'response': { - const req = this.pending.get(envelope.id) - if (req) { - this.pending.delete(envelope.id) - clearTimeout(req.timeout) - if (envelope.error) { - const err = new Error(envelope.error.message) - ;(err as any).code = envelope.error.code - ;(err as any).data = envelope.error.data - req.reject(err) - } else { - req.resolve(envelope.result) - } - } - break - } - - case 'event': { - if (envelope.channel) { - const set = this.listeners.get(envelope.channel) - if (set) { - for (const cb of set) { - try { - cb(...(envelope.args ?? [])) - } catch { - // Listener errors shouldn't break the client - } - } - } - } - break - } - } - } -} diff --git a/packages/desktop/apps/cli/src/commands.test.ts b/packages/desktop/apps/cli/src/commands.test.ts deleted file mode 100644 index 86034c02238..00000000000 --- a/packages/desktop/apps/cli/src/commands.test.ts +++ /dev/null @@ -1,391 +0,0 @@ -import { describe, it, expect } from 'bun:test' -import { parseArgs, resolveApiKey, shouldSetupLlmConnection } from './index.ts' - -// --------------------------------------------------------------------------- -// Arg parsing tests -// --------------------------------------------------------------------------- - -describe('parseArgs', () => { - it('parses --url, --token, --workspace', () => { - const args = parseArgs([ - 'bun', 'index.ts', - '--url', 'ws://localhost:3000', - '--token', 'secret123', - '--workspace', 'ws-1', - 'ping', - ]) - expect(args.url).toBe('ws://localhost:3000') - expect(args.token).toBe('secret123') - expect(args.workspace).toBe('ws-1') - expect(args.command).toBe('ping') - }) - - it('parses --timeout and --json', () => { - const args = parseArgs([ - 'bun', 'index.ts', - '--timeout', '5000', - '--json', - 'workspaces', - ]) - expect(args.timeout).toBe(5000) - expect(args.json).toBe(true) - expect(args.command).toBe('workspaces') - }) - - it('parses --tls-ca', () => { - const args = parseArgs([ - 'bun', 'index.ts', - '--tls-ca', '/path/to/ca.pem', - 'ping', - ]) - expect(args.tlsCa).toBe('/path/to/ca.pem') - }) - - it('parses --send-timeout', () => { - const args = parseArgs([ - 'bun', 'index.ts', - '--send-timeout', '60000', - 'send', 'session-1', 'hello', - ]) - expect(args.sendTimeout).toBe(60000) - expect(args.command).toBe('send') - expect(args.rest).toEqual(['session-1', 'hello']) - }) - - it('falls back to env vars for url and token', () => { - const prevUrl = process.env.CRAFT_SERVER_URL - const prevToken = process.env.CRAFT_SERVER_TOKEN - const prevCa = process.env.CRAFT_TLS_CA - - process.env.CRAFT_SERVER_URL = 'ws://env-server:8080' - process.env.CRAFT_SERVER_TOKEN = 'env-token' - process.env.CRAFT_TLS_CA = '/env/ca.pem' - - try { - const args = parseArgs(['bun', 'index.ts', 'ping']) - expect(args.url).toBe('ws://env-server:8080') - expect(args.token).toBe('env-token') - expect(args.tlsCa).toBe('/env/ca.pem') - } finally { - if (prevUrl === undefined) delete process.env.CRAFT_SERVER_URL - else process.env.CRAFT_SERVER_URL = prevUrl - if (prevToken === undefined) delete process.env.CRAFT_SERVER_TOKEN - else process.env.CRAFT_SERVER_TOKEN = prevToken - if (prevCa === undefined) delete process.env.CRAFT_TLS_CA - else process.env.CRAFT_TLS_CA = prevCa - } - }) - - it('explicit flags override env vars', () => { - const prevUrl = process.env.CRAFT_SERVER_URL - process.env.CRAFT_SERVER_URL = 'ws://env-server:8080' - - try { - const args = parseArgs(['bun', 'index.ts', '--url', 'ws://flag-server:9090', 'ping']) - expect(args.url).toBe('ws://flag-server:9090') - } finally { - if (prevUrl === undefined) delete process.env.CRAFT_SERVER_URL - else process.env.CRAFT_SERVER_URL = prevUrl - } - }) - - it('parses --help as command', () => { - const args = parseArgs(['bun', 'index.ts', '--help']) - expect(args.command).toBe('help') - }) - - it('parses --version as command', () => { - const args = parseArgs(['bun', 'index.ts', '--version']) - expect(args.command).toBe('version') - }) - - it('parses --validate-server as command', () => { - const args = parseArgs(['bun', 'index.ts', '--validate-server']) - expect(args.command).toBe('validate') - }) - - it('parses session subcommand with args', () => { - const args = parseArgs([ - 'bun', 'index.ts', - 'session', 'create', '--name', 'test', '--mode', 'safe', - ]) - expect(args.command).toBe('session') - // --mode is now a global flag, consumed at top level - expect(args.rest).toEqual(['create', '--name', 'test']) - expect(args.mode).toBe('safe') - }) - - it('parses send with message text', () => { - const args = parseArgs([ - 'bun', 'index.ts', - 'send', 'sess-123', 'What', 'files', 'are', 'here?', - ]) - expect(args.command).toBe('send') - expect(args.rest).toEqual(['sess-123', 'What', 'files', 'are', 'here?']) - }) - - it('parses invoke with channel and JSON args', () => { - const args = parseArgs([ - 'bun', 'index.ts', - 'invoke', 'sessions:get', '["workspace-1"]', - ]) - expect(args.command).toBe('invoke') - expect(args.rest).toEqual(['sessions:get', '["workspace-1"]']) - }) - - it('defaults to empty command (shows help)', () => { - const args = parseArgs(['bun', 'index.ts']) - expect(args.command).toBe('') - }) - - it('defaults timeout to 10000', () => { - const args = parseArgs(['bun', 'index.ts', 'ping']) - expect(args.timeout).toBe(10000) - }) - - it('defaults sendTimeout to 300000', () => { - const args = parseArgs(['bun', 'index.ts', 'send', 's1', 'hi']) - expect(args.sendTimeout).toBe(300000) - }) - - it('defaults json to false', () => { - const args = parseArgs(['bun', 'index.ts', 'ping']) - expect(args.json).toBe(false) - }) - - // --- run-specific flags --- - - it('parses run command with positional args', () => { - const args = parseArgs(['bun', 'index.ts', 'run', 'hello', 'world']) - expect(args.command).toBe('run') - expect(args.rest).toEqual(['hello', 'world']) - }) - - it('--source accumulates into array', () => { - const args = parseArgs([ - 'bun', 'index.ts', - '--source', 'craft-kb', - '--source', 'github', - 'run', 'do stuff', - ]) - expect(args.sources).toEqual(['craft-kb', 'github']) - }) - - it('defaults sources to empty array', () => { - const args = parseArgs(['bun', 'index.ts', 'run', 'hello']) - expect(args.sources).toEqual([]) - }) - - it('--mode sets mode', () => { - const args = parseArgs(['bun', 'index.ts', '--mode', 'safe', 'run', 'hello']) - expect(args.mode).toBe('safe') - }) - - it('defaults mode to empty (run defaults to allow-all)', () => { - const args = parseArgs(['bun', 'index.ts', 'run', 'hello']) - expect(args.mode).toBe('') - }) - - it('--output-format sets outputFormat', () => { - const args = parseArgs(['bun', 'index.ts', '--output-format', 'stream-json', 'run', 'hello']) - expect(args.outputFormat).toBe('stream-json') - }) - - it('defaults outputFormat to text', () => { - const args = parseArgs(['bun', 'index.ts', 'run', 'hello']) - expect(args.outputFormat).toBe('text') - }) - - it('--no-cleanup sets noCleanup', () => { - const args = parseArgs(['bun', 'index.ts', '--no-cleanup', 'run', 'hello']) - expect(args.noCleanup).toBe(true) - }) - - it('defaults noCleanup to false', () => { - const args = parseArgs(['bun', 'index.ts', 'run', 'hello']) - expect(args.noCleanup).toBe(false) - }) - - it('--server-entry sets serverEntry', () => { - const args = parseArgs(['bun', 'index.ts', '--server-entry', '/path/to/server.ts', 'run', 'hello']) - expect(args.serverEntry).toBe('/path/to/server.ts') - }) - - it('defaults serverEntry to undefined', () => { - const args = parseArgs(['bun', 'index.ts', 'run', 'hello']) - expect(args.serverEntry).toBeUndefined() - }) - - it('--workspace-dir sets workspaceDir', () => { - const args = parseArgs(['bun', 'index.ts', '--workspace-dir', '/tmp/ws', 'run', 'hello']) - expect(args.workspaceDir).toBe('/tmp/ws') - }) - - it('defaults workspaceDir to undefined', () => { - const args = parseArgs(['bun', 'index.ts', 'run', 'hello']) - expect(args.workspaceDir).toBeUndefined() - }) - - it('parses --provider deepseek for run', () => { - const args = parseArgs(['bun', 'index.ts', '--provider', 'deepseek', 'run', 'hello']) - expect(args.provider).toBe('deepseek') - }) -}) - -// --------------------------------------------------------------------------- -// Provider credential resolution tests -// --------------------------------------------------------------------------- - -describe('resolveApiKey', () => { - it('keeps explicit key passthrough for script compatibility', () => { - expect(resolveApiKey('qwen', 'unused-key')).toBe('unused-key') - }) -}) - -describe('shouldSetupLlmConnection', () => { - it('sets up Qwen when no connection exists', () => { - expect(shouldSetupLlmConnection(0, { provider: 'qwen', baseUrl: '' })).toBe(true) - }) - - it('skips setup when a connection already exists', () => { - expect(shouldSetupLlmConnection(2, { provider: 'qwen', baseUrl: '' })).toBe(false) - }) -}) - -// --------------------------------------------------------------------------- -// Validate steps structure tests -// --------------------------------------------------------------------------- - -import { getValidateSteps } from './index.ts' - -describe('getValidateSteps', () => { - it('returns a non-empty array of steps', () => { - const steps = getValidateSteps() - expect(steps.length).toBeGreaterThan(0) - // Sanity: at least the known lifecycle groups exist - expect(steps.length).toBeGreaterThanOrEqual(20) - }) - - it('first step is handshake', () => { - const steps = getValidateSteps() - expect(steps[0].name).toBe('Connect + handshake') - }) - - it('last step is disconnect', () => { - const steps = getValidateSteps() - expect(steps[steps.length - 1].name).toBe('Disconnect') - }) - - it('has no duplicate step names', () => { - const names = getValidateSteps().map((s) => s.name) - expect(new Set(names).size).toBe(names.length) - }) - - it('includes session lifecycle steps (create, read, delete)', () => { - const steps = getValidateSteps() - const names = steps.map((s) => s.name) - expect(names).toContain('sessions:create') - expect(names).toContain('sessions:getMessages') - expect(names).toContain('sessions:delete') - }) - - it('includes send message + stream step', () => { - const steps = getValidateSteps() - const names = steps.map((s) => s.name) - expect(names).toContain('send message + stream') - }) - - it('includes send message + tool use step', () => { - const steps = getValidateSteps() - const names = steps.map((s) => s.name) - expect(names).toContain('send message + tool use') - }) - - it('includes source lifecycle steps (create, mention, delete)', () => { - const steps = getValidateSteps() - const names = steps.map((s) => s.name) - expect(names).toContain('sources:create') - expect(names).toContain('send + source mention') - expect(names).toContain('sources:delete') - }) - - it('includes skill lifecycle steps (create, mention, delete)', () => { - const steps = getValidateSteps() - const names = steps.map((s) => s.name) - expect(names).toContain('send + skill create') - expect(names).toContain('send + skill mention') - expect(names).toContain('skills:delete') - }) - - it('includes automation lifecycle steps', () => { - const names = getValidateSteps().map((s) => s.name) - expect(names).toContain('automation:create') - expect(names).toContain('automation:trigger (status change)') - expect(names).toContain('automation:verify session') - expect(names).toContain('automation:verify labels') - expect(names).toContain('automations:getLastExecuted') - expect(names).toContain('automation:cleanup') - }) - - it('includes session tool validation steps', () => { - const names = getValidateSteps().map((s) => s.name) - expect(names).toContain('session-tools:set_session_labels') - expect(names).toContain('session-tools:get_session_info') - expect(names).toContain('session-tools:list_sessions') - }) - - it('session tool steps come after tool use and before branching', () => { - const names = getValidateSteps().map((s) => s.name) - const toolUse = names.indexOf('send message + tool use') - const labels = names.indexOf('session-tools:set_session_labels') - const branch = names.indexOf('sessions:branch') - expect(labels).toBeGreaterThan(toolUse) - expect(branch).toBeGreaterThan(labels) - }) - - it('includes session branching steps', () => { - const names = getValidateSteps().map((s) => s.name) - expect(names).toContain('sessions:branch') - expect(names).toContain('sessions:branch verify') - expect(names).toContain('sessions:branch send') - }) - - it('includes webhook validation steps', () => { - const names = getValidateSteps().map((s) => s.name) - expect(names).toContain('webhook:test (RPC)') - expect(names).toContain('webhook:verify failure') - }) - - it('creates session with allow-all permission mode', () => { - const steps = getValidateSteps() - const createStep = steps.find((s) => s.name === 'sessions:create') - expect(createStep).toBeDefined() - }) - - it('cleanup steps come after send steps', () => { - const steps = getValidateSteps() - const names = steps.map((s) => s.name) - const skillDelete = names.indexOf('skills:delete') - const sourceDelete = names.indexOf('sources:delete') - const sessionDelete = names.indexOf('sessions:delete') - const skillMention = names.indexOf('send + skill mention') - expect(skillDelete).toBeGreaterThan(skillMention) - expect(sourceDelete).toBeGreaterThan(skillDelete) - expect(sessionDelete).toBeGreaterThan(sourceDelete) - }) - - it('branching steps come after send message + tool use', () => { - const names = getValidateSteps().map((s) => s.name) - const toolUse = names.indexOf('send message + tool use') - const branch = names.indexOf('sessions:branch') - expect(branch).toBeGreaterThan(toolUse) - }) - - it('automation cleanup comes before sources:delete', () => { - const names = getValidateSteps().map((s) => s.name) - const cleanup = names.indexOf('automation:cleanup') - const srcDelete = names.indexOf('sources:delete') - expect(cleanup).toBeGreaterThan(-1) - expect(cleanup).toBeLessThan(srcDelete) - }) -}) diff --git a/packages/desktop/apps/cli/src/index.ts b/packages/desktop/apps/cli/src/index.ts deleted file mode 100755 index bd8fdcd5753..00000000000 --- a/packages/desktop/apps/cli/src/index.ts +++ /dev/null @@ -1,1922 +0,0 @@ -#!/usr/bin/env bun -/** - * craft-cli — Terminal client for Qwen Code server. - * - * Connects over WebSocket (ws:// or wss://) to a running Qwen Code server - * and provides commands for listing resources, managing sessions, sending - * messages with real-time streaming, and validating server health. - */ - -import { resolve } from 'path' -import { CliRpcClient } from './client.ts' - -// --------------------------------------------------------------------------- -// Arg parsing -// --------------------------------------------------------------------------- - -export interface CliArgs { - url: string - token: string - workspace?: string - timeout: number - json: boolean - tlsCa?: string - sendTimeout: number - command: string - rest: string[] - // run-specific flags - sources: string[] - mode: string - outputFormat: string - noCleanup: boolean - noSpinner: boolean - verbose: boolean - serverEntry?: string - workspaceDir?: string - // LLM configuration - provider: string - model: string - apiKey: string - baseUrl: string -} - -export function parseArgs(argv: string[]): CliArgs { - const args = argv.slice(2) // skip bun + script path - let url = '' - let token = '' - let workspace: string | undefined - let timeout = 10_000 - let json = false - let tlsCa: string | undefined - let sendTimeout = 300_000 // 5 min - const rest: string[] = [] - let command = '' - const sources: string[] = [] - let mode = '' - let outputFormat = 'text' - let noCleanup = false - let noSpinner = false - let verbose = false - let serverEntry: string | undefined - let workspaceDir: string | undefined - let provider = '' - let model = '' - let apiKey = '' - let baseUrl = '' - - for (let i = 0; i < args.length; i++) { - const arg = args[i] - switch (arg) { - case '--url': - url = args[++i] ?? '' - break - case '--token': - token = args[++i] ?? '' - break - case '--workspace': - workspace = args[++i] - break - case '--timeout': - timeout = parseInt(args[++i] ?? '10000', 10) - break - case '--json': - json = true - break - case '--tls-ca': - tlsCa = args[++i] - break - case '--send-timeout': - sendTimeout = parseInt(args[++i] ?? '300000', 10) - break - case '--source': - sources.push(args[++i] ?? '') - break - case '--mode': - mode = args[++i] ?? '' - break - case '--output-format': - outputFormat = args[++i] ?? 'text' - break - case '--no-cleanup': - noCleanup = true - break - case '--disable-spinner': - case '--no-spinner': - noSpinner = true - break - case '--verbose': - case '-v': - verbose = true - break - case '--server-entry': - serverEntry = args[++i] - break - case '--workspace-dir': - workspaceDir = args[++i] - break - case '--provider': - provider = args[++i] ?? '' - break - case '--model': - model = args[++i] ?? '' - break - case '--api-key': - apiKey = args[++i] ?? '' - break - case '--base-url': - baseUrl = args[++i] ?? '' - break - case '--help': - case '-h': - command = 'help' - break - case '--version': - command = 'version' - break - case '--validate-server': - command = 'validate' - break - default: - if (!command && !arg.startsWith('-')) { - command = arg - } else { - rest.push(arg) - } - } - } - - // Env var fallbacks - if (!url) url = process.env.CRAFT_SERVER_URL ?? '' - if (!token) token = process.env.CRAFT_SERVER_TOKEN ?? '' - if (!tlsCa) tlsCa = process.env.CRAFT_TLS_CA - if (!provider) provider = process.env.LLM_PROVIDER ?? 'qwen' - if (!model) model = process.env.LLM_MODEL ?? '' - if (!apiKey) apiKey = process.env.LLM_API_KEY ?? '' - if (!baseUrl) baseUrl = process.env.LLM_BASE_URL ?? '' - - return { url, token, workspace, timeout, json, tlsCa, sendTimeout, command, rest, sources, mode, outputFormat, noCleanup, noSpinner, verbose, serverEntry, workspaceDir, provider, model, apiKey, baseUrl } -} - -// --------------------------------------------------------------------------- -// Auto workspace resolution -// --------------------------------------------------------------------------- - -async function resolveWorkspace( - client: CliRpcClient, - explicit?: string, -): Promise { - if (explicit) { - // Bind client to the workspace so push events reach us - await client.invoke('window:switchWorkspace', explicit).catch(() => {}) - return explicit - } - try { - const workspaces = (await client.invoke('workspaces:get')) as any[] - if (workspaces?.length > 0) { - const id = workspaces[0].id - await client.invoke('window:switchWorkspace', id).catch(() => {}) - return id - } - } catch { - // Fall through — workspace may not be needed - } - return undefined -} - -// --------------------------------------------------------------------------- -// Output helpers -// --------------------------------------------------------------------------- - -function out(data: unknown, jsonMode: boolean): void { - if (jsonMode) { - process.stdout.write(JSON.stringify(data, null, 2) + '\n') - } else if (typeof data === 'string') { - process.stdout.write(data + '\n') - } else { - process.stdout.write(JSON.stringify(data, null, 2) + '\n') - } -} - -function err(msg: string): void { - process.stderr.write(`Error: ${msg}\n`) -} - -// --------------------------------------------------------------------------- -// ANSI colors (disabled when NO_COLOR is set or stdout is not a TTY) -// --------------------------------------------------------------------------- - -const _useColor = !process.env.NO_COLOR && process.stdout.isTTY !== false -const c = { - dim: (s: string) => _useColor ? `\x1b[2m${s}\x1b[22m` : s, - green: (s: string) => _useColor ? `\x1b[32m${s}\x1b[39m` : s, - red: (s: string) => _useColor ? `\x1b[31m${s}\x1b[39m` : s, - cyan: (s: string) => _useColor ? `\x1b[36m${s}\x1b[39m` : s, - bold: (s: string) => _useColor ? `\x1b[1m${s}\x1b[22m` : s, - yellow: (s: string) => _useColor ? `\x1b[33m${s}\x1b[39m` : s, - blue: (s: string) => _useColor ? `\x1b[34m${s}\x1b[39m` : s, -} - -// --------------------------------------------------------------------------- -// Spinner (TTY only — skipped when piped or NO_COLOR) -// --------------------------------------------------------------------------- - -const _spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'] - -function createSpinner(text: string): { stop(): void } { - let i = 0 - let stopped = false - // Render first frame immediately — setInterval alone misses fast steps - process.stdout.write(`${text} ${c.dim(_spinnerFrames[i++ % _spinnerFrames.length])}`) - const timer = setInterval(() => { - process.stdout.write(`\r\x1b[2K${text} ${c.dim(_spinnerFrames[i++ % _spinnerFrames.length])}`) - }, 80) - return { - stop() { - if (stopped) return - stopped = true - clearInterval(timer) - process.stdout.write('\r\x1b[2K') - }, - } -} - -// --------------------------------------------------------------------------- -// Commands -// --------------------------------------------------------------------------- - -async function cmdPing(client: CliRpcClient, args: CliArgs): Promise { - const start = performance.now() - const clientId = await client.connect() - const latency = Math.round(performance.now() - start) - out( - args.json - ? { clientId, latencyMs: latency } - : `Connected: clientId=${clientId} latency=${latency}ms`, - args.json, - ) -} - -async function cmdHealth(client: CliRpcClient, args: CliArgs): Promise { - await client.connect() - const result = await client.invoke('credentials:healthCheck') - out(result, args.json) -} - -async function cmdVersions(client: CliRpcClient, args: CliArgs): Promise { - await client.connect() - const result = await client.invoke('system:versions') - out(result, args.json) -} - -async function cmdWorkspaces(client: CliRpcClient, args: CliArgs): Promise { - await client.connect() - const result = (await client.invoke('workspaces:get')) as any[] - if (args.json) { - out(result, true) - } else { - if (!result?.length) { - out('No workspaces found', false) - return - } - for (const ws of result) { - out(`${ws.id} ${ws.name ?? '(unnamed)'} ${ws.path ?? ''}`, false) - } - } -} - -async function cmdSessions(client: CliRpcClient, args: CliArgs): Promise { - await client.connect() - const workspaceId = await resolveWorkspace(client, args.workspace) - if (!workspaceId) { - err('No workspace available. Use --workspace ') - process.exit(1) - } - const result = (await client.invoke('sessions:get', workspaceId)) as any[] - if (args.json) { - out(result, true) - } else { - if (!result?.length) { - out('No sessions found', false) - return - } - for (const s of result) { - const name = s.name ?? '(unnamed)' - const preview = s.preview ? ` ${s.preview.slice(0, 60)}` : '' - const status = s.isProcessing ? ' [processing]' : '' - out(`${s.id} ${name}${preview}${status}`, false) - } - } -} - -async function cmdConnections(client: CliRpcClient, args: CliArgs): Promise { - await client.connect() - const result = await client.invoke('LLM_Connection:list') - out(result, args.json) -} - -async function cmdSources(client: CliRpcClient, args: CliArgs): Promise { - await client.connect() - const workspaceId = await resolveWorkspace(client, args.workspace) - if (!workspaceId) { - err('No workspace available. Use --workspace ') - process.exit(1) - } - const result = await client.invoke('sources:get', workspaceId) - out(result, args.json) -} - -async function cmdSessionCreate(client: CliRpcClient, args: CliArgs): Promise { - await client.connect() - const workspaceId = await resolveWorkspace(client, args.workspace) - if (!workspaceId) { - err('No workspace available. Use --workspace ') - process.exit(1) - } - - // Parse sub-args: --name - let name: string | undefined - for (let i = 0; i < args.rest.length; i++) { - if (args.rest[i] === '--name') name = args.rest[++i] - } - - const opts: Record = {} - if (name) opts.name = name - if (args.mode) opts.permissionMode = args.mode - - const result = await client.invoke('sessions:create', workspaceId, opts) - out(result, args.json) -} - -async function cmdSessionMessages(client: CliRpcClient, args: CliArgs): Promise { - const sessionId = args.rest[0] - if (!sessionId) { - err('Usage: session messages ') - process.exit(1) - } - await client.connect() - const result = await client.invoke('sessions:getMessages', sessionId) - out(result, args.json) -} - -async function cmdSessionDelete(client: CliRpcClient, args: CliArgs): Promise { - const sessionId = args.rest[0] - if (!sessionId) { - err('Usage: session delete ') - process.exit(1) - } - await client.connect() - await client.invoke('sessions:delete', sessionId) - out(args.json ? { deleted: sessionId } : `Deleted session: ${sessionId}`, args.json) -} - -/** - * Read prompt text from positional args + stdin. - * If there are positional words, they become the base message. - * Reads stdin when: --stdin flag is present, or no message and stdin is piped (not a TTY). - */ -async function readPrompt(words: string[], restArgs?: string[]): Promise { - let message = words.join(' ') - - const wantsStdin = restArgs?.includes('--stdin') - const isTTY = typeof process.stdin.isTTY === 'boolean' ? process.stdin.isTTY : false - if (wantsStdin || (!message && !isTTY)) { - const chunks: string[] = [] - const reader = Bun.stdin.stream().getReader() - const decoder = new TextDecoder() - while (true) { - const { done, value } = await reader.read() - if (done) break - chunks.push(decoder.decode(value, { stream: true })) - } - const stdinText = chunks.join('') - message = message ? `${message}\n${stdinText}` : stdinText - } - - return message -} - -/** - * Subscribe to session events, send the message, stream output, wait for completion. - * Returns the exit code (0 = success, 1 = error, 130 = interrupted). - */ -async function sendAndStream( - client: CliRpcClient, - sessionId: string, - message: string, - args: CliArgs, -): Promise { - let exitCode = 0 - let finished = false - const streamJson = args.outputFormat === 'stream-json' - - const unsub = client.on('session:event', (event: unknown) => { - const ev = event as { type: string; sessionId: string; [key: string]: unknown } - if (ev.sessionId !== sessionId) return - - if (streamJson) { - process.stdout.write(JSON.stringify(ev) + '\n') - } - - switch (ev.type) { - case 'text_delta': - if (!streamJson) process.stdout.write(ev.delta as string) - break - case 'tool_start': - if (!streamJson) process.stdout.write(`\n[tool: ${ev.toolName}${ev.toolIntent ? ` — ${ev.toolIntent}` : ''}]\n`) - break - case 'tool_result': { - if (!streamJson) { - const result = String(ev.result ?? '') - if (result.length > 200) { - process.stdout.write(`${result.slice(0, 200)}...\n`) - } else if (result) { - process.stdout.write(`${result}\n`) - } - } - break - } - case 'error': - if (!streamJson) err(String(ev.error)) - exitCode = 1 - finished = true - break - case 'complete': - if (!streamJson) process.stdout.write('\n') - finished = true - break - case 'interrupted': - if (!streamJson) process.stdout.write('\n[interrupted]\n') - exitCode = 130 - finished = true - break - } - }) - - await client.invoke('sessions:sendMessage', sessionId, message) - - const deadline = Date.now() + args.sendTimeout - while (!finished && Date.now() < deadline) { - await new Promise((r) => setTimeout(r, 100)) - } - - unsub() - - if (!finished) { - err('Send timeout — no completion event received') - exitCode = 1 - } - - return exitCode -} - -async function cmdSend(client: CliRpcClient, args: CliArgs): Promise { - const sessionId = args.rest[0] - if (!sessionId) { - err('Usage: send ') - process.exit(1) - } - - const message = await readPrompt(args.rest.slice(1), args.rest) - if (!message.trim()) { - err('No message provided') - process.exit(1) - } - - await client.connect() - const exitCode = await sendAndStream(client, sessionId, message, args) - client.destroy() - process.exit(exitCode) -} - -interface LocalServer { - client: CliRpcClient - stop: () => Promise -} - -async function spawnLocalServer(args: CliArgs, opts?: { quiet?: boolean }): Promise { - const { spawnServer } = await import('./server-spawner.ts') - process.stderr.write('Starting server...\n') - const server = await spawnServer({ - serverEntry: args.serverEntry, - startupTimeout: args.timeout > 30_000 ? args.timeout : 30_000, - quiet: opts?.quiet, - }) - process.stderr.write(`Server ready: ${server.url}\n`) - const client = new CliRpcClient(server.url, { - token: server.token, - requestTimeout: args.timeout, - }) - return { client, stop: server.stop } -} - -// --------------------------------------------------------------------------- -// LLM connection helpers -// --------------------------------------------------------------------------- - -function getProviderDisplayName(provider: string): string { - return provider === 'qwen' ? 'Qwen Code' : provider.charAt(0).toUpperCase() + provider.slice(1) -} - -export function resolveApiKey(_provider: string, explicit: string): string { - return explicit -} - -export function shouldSetupLlmConnection(existingConnectionCount: number, _args: Pick): boolean { - return existingConnectionCount === 0 -} - -async function setupLlmConnection( - client: CliRpcClient, - _args: CliArgs, -): Promise<{ connectionSlug: string }> { - const provider = 'qwen' - const connectionSlug = 'qwen-code' - - await client.invoke('LLM_Connection:save', { - slug: connectionSlug, - name: getProviderDisplayName(provider), - providerType: 'qwen', - authType: 'none', - createdAt: Date.now(), - }) - const setupResult = await client.invoke('settings:setupLlmConnection', { slug: connectionSlug }) as { success: boolean; error?: string } - if (!setupResult?.success) { - throw new Error(`LLM connection setup failed: ${setupResult?.error ?? 'unknown error'}`) - } - await client.invoke('LLM_Connection:setDefault', connectionSlug) - process.stderr.write('LLM connection configured: Qwen Code\n') - - return { connectionSlug } -} - -async function cmdRun(args: CliArgs): Promise { - // Prompt = all positional args (no session ID needed, unlike send) - const message = await readPrompt(args.rest, args.rest) - if (!message.trim()) { - err('No prompt provided. Usage: run ') - process.exit(1) - } - - const server = await spawnLocalServer(args) - - let client: CliRpcClient | undefined = server.client - let sessionId: string | undefined - - const cleanup = async () => { - if (sessionId && client?.isConnected && !args.noCleanup) { - await client.invoke('sessions:delete', sessionId).catch(() => {}) - } - client?.destroy() - await server.stop() - } - - // Signal handling — cancel + clean up on SIGINT/SIGTERM - const onSignal = async () => { - if (sessionId && client?.isConnected) { - await client.invoke('sessions:cancel', sessionId).catch(() => {}) - } - await cleanup() - process.exit(130) - } - process.on('SIGINT', onSignal) - process.on('SIGTERM', onSignal) - - try { - await client.connect() - - // Bootstrap workspace from directory if specified - let bootstrappedWorkspaceId: string | undefined - if (args.workspaceDir) { - const absPath = resolve(args.workspaceDir) - const ws = (await client.invoke('workspaces:create', absPath, 'ci-workspace')) as { id: string } - bootstrappedWorkspaceId = ws.id - process.stderr.write(`Workspace registered: ${absPath}\n`) - } - - // Auto-setup the Qwen Code connection when no connection exists yet. - const connections = (await client.invoke('LLM_Connection:list')) as any[] - let connectionSlug: string | undefined - if (shouldSetupLlmConnection(connections?.length ?? 0, args)) { - const result = await setupLlmConnection(client, args) - connectionSlug = result.connectionSlug - } - - const workspaceId = bootstrappedWorkspaceId - ?? await resolveWorkspace(client, args.workspace) - if (bootstrappedWorkspaceId) { - await client.invoke('window:switchWorkspace', bootstrappedWorkspaceId).catch(() => {}) - } - if (!workspaceId) { - err('No workspace found on server') - process.exit(1) - } - - const session = (await client.invoke('sessions:create', workspaceId, { - permissionMode: args.mode || 'allow-all', - enabledSourceSlugs: args.sources.length > 0 ? args.sources : undefined, - slugHint: message, - })) as { id: string } - sessionId = session.id - - if (args.model) { - await client.invoke('session:setModel', sessionId, workspaceId, args.model, connectionSlug) - } - - const exitCode = await sendAndStream(client, sessionId, message, args) - await cleanup() - process.exit(exitCode) - } catch (e) { - const msg = e instanceof Error ? e.message : String(e) - err(msg) - await cleanup() - process.exit(1) - } finally { - process.off('SIGINT', onSignal) - process.off('SIGTERM', onSignal) - } -} - -async function cmdValidate(args: CliArgs): Promise { - let server: LocalServer | undefined - let client: CliRpcClient - - // Use a generous timeout for validation steps — source creation and MCP - // server startup can be slow on Windows. - const validateArgs = { ...args, timeout: Math.max(args.timeout, 30_000) } - - if (args.url) { - client = new CliRpcClient(args.url, { - token: args.token || undefined, - requestTimeout: validateArgs.timeout, - connectTimeout: validateArgs.timeout, - }) - } else { - server = await spawnLocalServer(validateArgs, { quiet: !args.verbose }) - client = server.client - } - - try { - const exitCode = await runValidation(client, args.json, args.noSpinner, args.workspaceDir, { - baseUrl: args.baseUrl, - apiKey: args.apiKey, - provider: args.provider, - }) - client.destroy() - if (server) await server.stop() - process.exit(exitCode) - } catch (e) { - const msg = e instanceof Error ? e.message : String(e) - err(msg) - client.destroy() - if (server) await server.stop() - process.exit(1) - } -} - -async function cmdCancel(client: CliRpcClient, args: CliArgs): Promise { - const sessionId = args.rest[0] - if (!sessionId) { - err('Usage: cancel ') - process.exit(1) - } - await client.connect() - await client.invoke('sessions:cancel', sessionId) - out(args.json ? { cancelled: sessionId } : `Cancelled: ${sessionId}`, args.json) -} - -async function cmdInvoke(client: CliRpcClient, args: CliArgs): Promise { - const channel = args.rest[0] - if (!channel) { - err('Usage: invoke [json-args...]') - process.exit(1) - } - await client.connect() - - // Parse remaining args as JSON - const invokeArgs: unknown[] = [] - for (let i = 1; i < args.rest.length; i++) { - try { - invokeArgs.push(JSON.parse(args.rest[i])) - } catch { - invokeArgs.push(args.rest[i]) - } - } - - const result = await client.invoke(channel, ...invokeArgs) - out(result, args.json) -} - -async function cmdListen(client: CliRpcClient, args: CliArgs): Promise { - const channel = args.rest[0] - if (!channel) { - err('Usage: listen ') - process.exit(1) - } - await client.connect() - - client.on(channel, (...eventArgs: unknown[]) => { - out({ channel, args: eventArgs, timestamp: new Date().toISOString() }, true) - }) - - process.stdout.write(`Listening on ${channel} (Ctrl+C to stop)\n`) - - // Keep alive - await new Promise(() => { - // Never resolves — Ctrl+C exits - }) -} - -// --------------------------------------------------------------------------- -// Validate server -// --------------------------------------------------------------------------- - -export interface ValidateStep { - name: string - fn: (client: CliRpcClient, ctx: ValidateContext) => Promise -} - -export interface ValidateContext { - /** Pre-existing workspace directory (from --workspace-dir) */ - workspaceDir?: string - /** Custom endpoint URL (from --base-url) */ - baseUrl?: string - /** API key override (from --api-key) */ - apiKey?: string - /** Provider hint (from --provider, default 'qwen') */ - provider?: string - workspaceId?: string - workspaceRootPath?: string - createdWorkspace?: boolean - createdSessionId?: string - createdSourceSlug?: string - createdSkillSlug?: string - createdAutomation?: boolean - automationTestSessionId?: string - /** Session created by automation that should be blocked by failing condition (if bug occurs) */ - automationBlockedSessionId?: string - automationName?: string - automationBlockedName?: string - createdLabelId?: string - /** Backup of existing automations.json before overwrite (undefined = didn't exist) */ - automationsJsonBackup?: string | null - /** Backup of existing automations-history.jsonl before overwrite (undefined = didn't exist) */ - automationsHistoryBackup?: string | null - branchedSessionId?: string - /** Label ID for e2e-test label created for session tool validation */ - e2eTestLabelId?: string - onEvent?: (ev: { type: string; [key: string]: unknown }) => void -} - -/** Minimal shapes for RPC responses used in validation steps. */ -interface ValidateStatus { - id?: string - label?: string -} - -interface ValidateSession { - id: string - name?: string - labels?: string[] -} - -interface ValidateLabel { - id?: string - name?: string -} - -interface ValidateMessageBlock { - type: string - text?: string -} - -interface ValidateMessage { - role: string - content: string | ValidateMessageBlock[] -} - -interface ValidateMessagesResponse { - messages?: ValidateMessage[] - conversation?: ValidateMessage[] -} - -/** - * Send a message and wait for streaming events. - * Returns a summary of received event types. - * If expectTool is true, validates that tool_start + tool_result events arrived. - */ -async function waitForSendEvents( - client: CliRpcClient, - sessionId: string, - message: string, - timeoutMs: number, - expectTool: boolean, - sendOptions?: Record, - onEvent?: (ev: { type: string; [key: string]: unknown }) => void, - expectToolName?: string, -): Promise { - const seen = new Set() - let textChunks = 0 - let toolName = '' - let finished = false - - const unsub = client.on('session:event', (event: unknown) => { - const ev = event as { type: string; sessionId: string; [key: string]: unknown } - if (ev.sessionId !== sessionId) return - - seen.add(ev.type) - if (ev.type === 'text_delta') textChunks++ - if (ev.type === 'tool_start') toolName = String(ev.toolName ?? '') - if (ev.type === 'complete' || ev.type === 'error' || ev.type === 'interrupted') { - finished = true - } - onEvent?.(ev) - }) - - try { - await client.invoke('sessions:sendMessage', sessionId, message, - undefined, undefined, sendOptions) - - const deadline = Date.now() + timeoutMs - while (!finished && Date.now() < deadline) { - await new Promise((r) => setTimeout(r, 100)) - } - - if (!finished) throw new Error('Timed out waiting for completion') - - // Only treat as failure if error was the terminal event (no complete followed) - if (seen.has('error') && !seen.has('complete')) throw new Error('Session returned an error event') - - if (expectTool) { - if (!seen.has('tool_start')) throw new Error('No tool_start event received') - if (!seen.has('tool_result')) throw new Error('No tool_result event received') - if (expectToolName && !toolName.includes(expectToolName)) { - throw new Error(`Expected tool containing "${expectToolName}", got "${toolName}"`) - } - return `tool=${toolName}, ${textChunks} text deltas, events: ${[...seen].join(', ')}` - } - - if (!seen.has('text_delta')) throw new Error('No text_delta events received') - return `${textChunks} text deltas, events: ${[...seen].join(', ')}` - } finally { - unsub() - } -} - -/** - * Clean up automation test artifacts (config files, session, label). - * Shared between the automation:cleanup test step and runValidation error recovery. - */ -async function cleanupAutomationArtifacts( - client: CliRpcClient, - ctx: ValidateContext, -): Promise { - const cleaned: string[] = [] - - // Restore or remove automation config files - if (ctx.workspaceRootPath && ctx.createdAutomation) { - try { - const { writeFile, unlink } = await import('fs/promises') - const configPath = `${ctx.workspaceRootPath}/automations.json` - const historyPath = `${ctx.workspaceRootPath}/automations-history.jsonl` - if (ctx.automationsJsonBackup != null) { - await writeFile(configPath, ctx.automationsJsonBackup).catch(() => {}) - cleaned.push('automations.json (restored)') - } else { - await unlink(configPath).catch(() => {}) - cleaned.push('automations.json (removed)') - } - if (ctx.automationsHistoryBackup != null) { - await writeFile(historyPath, ctx.automationsHistoryBackup).catch(() => {}) - } else { - await unlink(historyPath).catch(() => {}) - } - ctx.createdAutomation = false - } catch { /* best effort */ } - } - - // Delete automation-triggered sessions - for (const key of ['automationTestSessionId', 'automationBlockedSessionId'] as const) { - const id = ctx[key] - if (!id || !client.isConnected) continue - try { - await client.invoke('sessions:delete', id) - cleaned.push(`session ${id}`) - ctx[key] = undefined - } catch { /* best effort */ } - } - - // Delete test label - if (ctx.workspaceId && ctx.createdLabelId && client.isConnected) { - try { - await client.invoke('labels:delete', ctx.workspaceId, ctx.createdLabelId) - cleaned.push(`label ${ctx.createdLabelId}`) - ctx.createdLabelId = undefined - } catch { /* best effort */ } - } - - return cleaned -} - -export function getValidateSteps(): ValidateStep[] { - return [ - { - name: 'Connect + handshake', - fn: async (client) => { - const start = performance.now() - const clientId = await client.connect() - const ms = Math.round(performance.now() - start) - return `clientId: ${clientId}, ${ms}ms` - }, - }, - { - name: 'credentials:healthCheck', - fn: async (client) => { - const r = (await client.invoke('credentials:healthCheck')) as any - return JSON.stringify(r) - }, - }, - { - name: 'system:versions', - fn: async (client) => { - const r = (await client.invoke('system:versions')) as any - return r?.node ? `node=${r.node}` : JSON.stringify(r) - }, - }, - { - name: 'system:homeDir', - fn: async (client) => { - const r = await client.invoke('system:homeDir') - return String(r) - }, - }, - { - name: 'workspaces:get', - fn: async (client, ctx) => { - // Register workspace from --workspace-dir if provided - if (ctx.workspaceDir) { - const { resolve } = await import('path') - const absPath = resolve(ctx.workspaceDir) - const ws = (await client.invoke('workspaces:create', absPath, 'ci-workspace')) as { id: string } - ctx.workspaceId = ws.id - ctx.workspaceRootPath = absPath - await client.invoke('window:switchWorkspace', ws.id) - return `registered: ${absPath}` - } - const r = (await client.invoke('workspaces:get')) as any[] - if (r?.length > 0) { - ctx.workspaceId = r[0].id - ctx.workspaceRootPath = r[0].rootPath ?? r[0].path - // Bind this client to the workspace so push events (e.g. session:event) - // routed { to: 'workspace' } reach us. - await client.invoke('window:switchWorkspace', r[0].id) - return `${r.length} workspaces` - } - // Auto-bootstrap a temp workspace for CI environments - const { mkdtemp } = await import('fs/promises') - const { tmpdir } = await import('os') - const tmpDir = await mkdtemp(`${tmpdir()}/craft-validate-`) - const ws = (await client.invoke('workspaces:create', tmpDir, 'validate-workspace')) as { id: string } - ctx.workspaceId = ws.id - ctx.workspaceRootPath = tmpDir - ctx.createdWorkspace = true - await client.invoke('window:switchWorkspace', ws.id) - return `0 found → created temp workspace` - }, - }, - { - name: 'sessions:get', - fn: async (client, ctx) => { - if (!ctx.workspaceId) return 'skipped (no workspace)' - const r = (await client.invoke('sessions:get', ctx.workspaceId)) as any[] - return `${r?.length ?? 0} sessions` - }, - }, - { - name: 'LLM_Connection:list', - fn: async (client, ctx) => { - const r = (await client.invoke('LLM_Connection:list')) as any[] - const provider = ctx.provider || 'qwen' - if (!shouldSetupLlmConnection(r?.length ?? 0, { provider, baseUrl: ctx.baseUrl ?? '' })) { - return `${r.length} connections` - } - const slug = 'qwen-code' - await client.invoke('LLM_Connection:save', { - slug, - name: 'Qwen Code', - providerType: 'qwen', - authType: 'none', - createdAt: Date.now(), - }) - const result = await client.invoke('settings:setupLlmConnection', { slug }) as { success: boolean; error?: string } - if (!result?.success) return `setup failed: ${result?.error ?? 'unknown'}` - await client.invoke('LLM_Connection:setDefault', slug) - return '0 found -> created Qwen Code connection' - }, - }, - { - name: 'sources:get', - fn: async (client, ctx) => { - if (!ctx.workspaceId) return 'skipped (no workspace)' - const r = (await client.invoke('sources:get', ctx.workspaceId)) as any[] - return `${r?.length ?? 0} sources` - }, - }, - { - name: 'sessions:create', - fn: async (client, ctx) => { - if (!ctx.workspaceId) return 'skipped (no workspace)' - const name = `__cli-validate-${Date.now()}` - const r = (await client.invoke('sessions:create', ctx.workspaceId, { - name, - permissionMode: 'allow-all', - })) as any - ctx.createdSessionId = r?.id - return ctx.createdSessionId ?? 'created' - }, - }, - { - name: 'sessions:getMessages', - fn: async (client, ctx) => { - if (!ctx.createdSessionId) return 'skipped (no session)' - await client.invoke('sessions:getMessages', ctx.createdSessionId) - return 'session readable' - }, - }, - { - name: 'send message + stream', - fn: async (client, ctx) => { - if (!ctx.createdSessionId) return 'skipped (no session)' - return await waitForSendEvents(client, ctx.createdSessionId, - 'Reply with exactly: VALIDATION_OK', 60_000, false, undefined, ctx.onEvent) - }, - }, - { - name: 'send message + tool use', - fn: async (client, ctx) => { - if (!ctx.createdSessionId) return 'skipped (no session)' - return await waitForSendEvents(client, ctx.createdSessionId, - 'Use the Bash tool to run: echo TOOL_VALIDATION_OK', 90_000, true, undefined, ctx.onEvent) - }, - }, - // ----- Session tool validation (guards against #511 regression) ----- - { - name: 'labels:create (e2e-test)', - fn: async (client, ctx) => { - if (!ctx.workspaceId) return 'skipped (no workspace)' - const r = (await client.invoke('labels:create', ctx.workspaceId, { - name: 'e2e-test', - color: 'gray', - })) as any - ctx.e2eTestLabelId = r?.id - return `label created: ${r?.id}` - }, - }, - { - name: 'session-tools:set_session_labels', - fn: async (client, ctx) => { - if (!ctx.createdSessionId) return 'skipped (no session)' - if (!ctx.e2eTestLabelId) return 'skipped (no e2e-test label)' - const result = await waitForSendEvents(client, ctx.createdSessionId, - 'Use the set_session_labels tool to set labels: ["e2e-test"] on the current session. Do NOT use any other tool.', - 90_000, true, undefined, ctx.onEvent, 'set_session_labels') - // Verify labels were actually applied - const sessions = (await client.invoke('sessions:get', ctx.workspaceId)) as any[] - const session = sessions?.find((s: any) => s.id === ctx.createdSessionId) - const labels = session?.labels ?? session?.labelIds ?? [] - if (!labels.length) throw new Error('Labels not applied to session') - return `${result} — labels verified: ${JSON.stringify(labels)}` - }, - }, - { - name: 'session-tools:get_session_info', - fn: async (client, ctx) => { - if (!ctx.createdSessionId) return 'skipped (no session)' - return await waitForSendEvents(client, ctx.createdSessionId, - 'Use the get_session_info tool to get info about the current session. Do NOT use any other tool.', - 90_000, true, undefined, ctx.onEvent, 'get_session_info') - }, - }, - { - name: 'session-tools:list_sessions', - fn: async (client, ctx) => { - if (!ctx.createdSessionId) return 'skipped (no session)' - return await waitForSendEvents(client, ctx.createdSessionId, - 'Use the list_sessions tool to list all sessions. Do NOT use any other tool.', - 90_000, true, undefined, ctx.onEvent, 'list_sessions') - }, - }, - // ----- Session branching ----- - { - name: 'sessions:branch', - fn: async (client, ctx) => { - if (!ctx.createdSessionId || !ctx.workspaceId) return 'skipped (no session)' - const r = (await client.invoke('sessions:getMessages', ctx.createdSessionId)) as ValidateMessagesResponse - const messages = r?.messages ?? r?.conversation ?? [] - const firstAssistant = messages.find((m) => m.role === 'assistant') as any - if (!firstAssistant?.id) throw new Error('No assistant message found to branch from') - const branch = (await client.invoke('sessions:create', ctx.workspaceId, { - name: `__cli-validate-branch-${Date.now()}`, - permissionMode: 'allow-all', - branchFromSessionId: ctx.createdSessionId, - branchFromMessageId: firstAssistant.id, - })) as any - ctx.branchedSessionId = branch?.id - return `branched at message ${firstAssistant.id} → session ${branch?.id}` - }, - }, - { - name: 'sessions:branch verify', - fn: async (client, ctx) => { - if (!ctx.branchedSessionId) return 'skipped (no branch)' - const r = (await client.invoke('sessions:getMessages', ctx.branchedSessionId)) as ValidateMessagesResponse - const messages = r?.messages ?? r?.conversation ?? [] - const hasAssistant = messages.some((m) => m.role === 'assistant') - if (!hasAssistant) throw new Error('Branch missing assistant message') - const origR = (await client.invoke('sessions:getMessages', ctx.createdSessionId!)) as ValidateMessagesResponse - const origMessages = origR?.messages ?? origR?.conversation ?? [] - if (messages.length >= origMessages.length) { - throw new Error(`Branch has ${messages.length} messages, expected fewer than original (${origMessages.length})`) - } - return `branch has ${messages.length} messages (original has ${origMessages.length})` - }, - }, - { - name: 'sessions:branch send', - fn: async (client, ctx) => { - if (!ctx.branchedSessionId) return 'skipped (no branch)' - return await waitForSendEvents(client, ctx.branchedSessionId, - 'Reply with exactly: BRANCH_OK', 60_000, false, undefined, ctx.onEvent) - }, - }, - // ----- Source lifecycle ----- - { - name: 'sources:create', - fn: async (client, ctx) => { - if (!ctx.workspaceId) return 'skipped (no workspace)' - const r = (await client.invoke('sources:create', ctx.workspaceId, { - name: 'Cat Facts', - provider: 'catfact', - type: 'api', - api: { baseUrl: 'https://catfact.ninja', authType: 'none' }, - icon: '🐱', - })) as any - ctx.createdSourceSlug = r?.slug - return ctx.createdSourceSlug ? `slug=${ctx.createdSourceSlug}` : JSON.stringify(r) - }, - }, - { - name: 'send + source mention', - fn: async (client, ctx) => { - if (!ctx.createdSessionId || !ctx.createdSourceSlug) return 'skipped (no session or source)' - // Enable the source on the session - await client.invoke('sessions:command', ctx.createdSessionId, { - type: 'setSources', - sourceSlugs: [ctx.createdSourceSlug], - }) - return await waitForSendEvents(client, ctx.createdSessionId, - `[source:${ctx.createdSourceSlug}] Get me a cat fact`, 90_000, false, undefined, ctx.onEvent) - }, - }, - // ----- MCP source validation (pre-committed in .github/agents/sources/) ----- - { - name: 'mcp:craft-public (auth:none)', - fn: async (client, ctx) => { - if (!ctx.createdSessionId) return 'skipped (no session)' - // Enable the pre-committed craft-public MCP source on the session - const enableSlugs = [ctx.createdSourceSlug, 'craft-public'].filter(Boolean) as string[] - await client.invoke('sessions:command', ctx.createdSessionId, { - type: 'setSources', - sourceSlugs: enableSlugs, - }) - return await waitForSendEvents(client, ctx.createdSessionId, - `[source:craft-public] List the documents under the "CraftAgents E2E Test" folder inside the "CraftAgents" folder. Just list their names.`, - 180_000, false, undefined, ctx.onEvent) - }, - }, - { - name: 'mcp:stitch-mcp (header-auth)', - fn: async (client, ctx) => { - if (!ctx.createdSessionId) return 'skipped (no session)' - const apiKey = process.env.STITCH_API_KEY - if (!apiKey) return 'skipped (no STITCH_API_KEY)' - // Inject credential into store (multi-header JSON format, same as API headerNames) - await client.invoke('sources:saveCredentials', ctx.workspaceId, 'stitch-mcp', JSON.stringify({ 'X-Goog-Api-Key': apiKey })) - // Enable stitch-mcp + existing sources on session - const enableSlugs = [ctx.createdSourceSlug, 'craft-public', 'stitch-mcp'].filter(Boolean) as string[] - await client.invoke('sessions:command', ctx.createdSessionId, { - type: 'setSources', - sourceSlugs: enableSlugs, - }) - return await waitForSendEvents(client, ctx.createdSessionId, - `Use the source_test tool to test the stitch-mcp source. Report the result.`, - 90_000, false, undefined, ctx.onEvent) - }, - }, - // ----- Skill lifecycle ----- - { - name: 'send + skill create', - fn: async (client, ctx) => { - if (!ctx.createdSessionId || !ctx.workspaceRootPath) return 'skipped (no session or workspace)' - ctx.createdSkillSlug = '__cli-validate-skill' - const sourceSlug = ctx.createdSourceSlug ?? 'cat-facts' - const skillDir = `${ctx.workspaceRootPath}/skills/${ctx.createdSkillSlug}` - // Use bash to create the skill file deterministically - return await waitForSendEvents(client, ctx.createdSessionId, - `Use the Bash tool to run this exact command: -mkdir -p "${skillDir}" && cat > "${skillDir}/SKILL.md" << 'SKILLEOF' ---- -name: "CLI Validate Skill" -description: "Validation skill created by craft-cli" -requiredSources: - - "${sourceSlug}" ---- - -This skill does two things: -1. Check the current water temperature of Lake Balaton (search the web or estimate based on the season) -2. Use the Cat Facts source to get a random cat fact - -Always perform both steps when this skill is invoked. -SKILLEOF`, 90_000, true, undefined, ctx.onEvent) - }, - }, - { - name: 'skills:get (verify)', - fn: async (client, ctx) => { - if (!ctx.workspaceId || !ctx.createdSkillSlug) return 'skipped (no skill)' - const r = (await client.invoke('skills:get', ctx.workspaceId)) as any[] - const found = r?.find((s: any) => s.slug === ctx.createdSkillSlug) - if (!found) throw new Error(`Skill '${ctx.createdSkillSlug}' not found in skills list`) - return `found: ${found.name ?? found.slug}` - }, - }, - { - name: 'send + skill mention', - fn: async (client, ctx) => { - if (!ctx.createdSessionId || !ctx.createdSkillSlug) return 'skipped (no session or skill)' - return await waitForSendEvents(client, ctx.createdSessionId, - `[skill:${ctx.createdSkillSlug}] Run the skill`, 120_000, false, - { skillSlugs: [ctx.createdSkillSlug] }, ctx.onEvent) - }, - }, - { - name: 'skills:delete', - fn: async (client, ctx) => { - if (!ctx.workspaceId || !ctx.createdSkillSlug) return 'skipped (no skill)' - await client.invoke('skills:delete', ctx.workspaceId, ctx.createdSkillSlug) - return `deleted skill: ${ctx.createdSkillSlug}` - }, - }, - // ----- Automation lifecycle ----- - { - name: 'automation:create', - fn: async (client, ctx) => { - if (!ctx.createdSessionId || !ctx.workspaceRootPath) return 'skipped (no session or workspace)' - const configPath = `${ctx.workspaceRootPath}/automations.json` - const historyPath = `${ctx.workspaceRootPath}/automations-history.jsonl` - const { readFile, writeFile } = await import('fs/promises') - - // Always backup + overwrite with deterministic validation config, - // then restore during cleanup. - const existingConfig = await readFile(configPath, 'utf-8').catch(() => null) - ctx.automationsJsonBackup = existingConfig - ctx.automationsHistoryBackup = await readFile(historyPath, 'utf-8').catch(() => null) - - const templatePath = `${process.cwd()}/.github/agents/automations.json` - const templateConfig = await readFile(templatePath, 'utf-8').catch(() => null) - if (!templateConfig) { - throw new Error(`Missing automation template at ${templatePath}`) - } - - const parsed = JSON.parse(templateConfig) as { - automations?: { SessionStatusChange?: Array<{ name?: string }> } - } - const entries = parsed?.automations?.SessionStatusChange - if (!Array.isArray(entries) || entries.length === 0) { - throw new Error('Automation template missing automations.SessionStatusChange entries') - } - - const blocked = entries.find((e) => e.name === 'CLI Validate Condition Blocked') - const pass = entries.find((e) => e.name === 'CLI Validate Condition Pass') - if (!blocked?.name || !pass?.name) { - throw new Error('Automation template must define both "CLI Validate Condition Blocked" and "CLI Validate Condition Pass"') - } - - ctx.automationBlockedName = blocked.name - ctx.automationName = pass.name - - await writeFile(configPath, templateConfig) - ctx.createdAutomation = true - // ConfigWatcher auto-detects automations.json changes (debounced) - await new Promise((r) => setTimeout(r, 2000)) - return `wrote config from template (blocked=${ctx.automationBlockedName}, pass=${ctx.automationName})` - }, - }, - { - name: 'automation:trigger (status change)', - fn: async (client, ctx) => { - if (!ctx.createdSessionId || !ctx.workspaceId) return 'skipped (no session or workspace)' - // Get available statuses to find one containing "in-progress" - const statuses = (await client.invoke('statuses:list', ctx.workspaceId)) as ValidateStatus[] - const inProgress = statuses?.find((s) => - (s.id ?? '').toLowerCase().includes('in-progress') || - (s.label ?? '').toLowerCase().includes('in progress') - ) - const statusValue = inProgress?.id ?? 'in-progress' - - // Change session status to trigger the automations - await client.invoke('sessions:command', ctx.createdSessionId, { - type: 'setSessionStatus', - state: statusValue, - }) - - // Poll for expected automation behavior: - // - pass automation MUST create a session - // - blocked automation MUST NOT create a session - let delay = 1000 - const deadline = Date.now() + 60_000 - while (Date.now() < deadline) { - await new Promise((r) => setTimeout(r, delay)) - delay = Math.min(delay * 1.5, 10_000) - const sessions = (await client.invoke('sessions:get', ctx.workspaceId)) as ValidateSession[] - - const blockedSession = sessions?.find((s) => - s.name === ctx.automationBlockedName && s.id !== ctx.createdSessionId - ) - if (blockedSession) { - ctx.automationBlockedSessionId = blockedSession.id - throw new Error(`Blocked automation unexpectedly triggered (session=${blockedSession.id})`) - } - - const passSession = sessions?.find((s) => - s.name === ctx.automationName && s.id !== ctx.createdSessionId - ) - if (passSession) { - ctx.automationTestSessionId = passSession.id - - // Guard against delayed blocked-automation session creation. - await new Promise((r) => setTimeout(r, 2000)) - const sessionsAfter = (await client.invoke('sessions:get', ctx.workspaceId)) as ValidateSession[] - const blockedAfter = sessionsAfter?.find((s) => - s.name === ctx.automationBlockedName && s.id !== ctx.createdSessionId - ) - if (blockedAfter) { - ctx.automationBlockedSessionId = blockedAfter.id - throw new Error(`Blocked automation unexpectedly triggered after delay (session=${blockedAfter.id})`) - } - - return `pass triggered → session ${passSession.id}; blocked automation did not trigger (status=${statusValue})` - } - } - throw new Error('Passing automation-created session not found within 60s') - }, - }, - { - name: 'automation:verify session', - fn: async (client, ctx) => { - if (!ctx.automationTestSessionId) return 'skipped (no automation session)' - // Wait for the automation session to complete - let delay = 1000 - const deadline = Date.now() + 90_000 - while (Date.now() < deadline) { - const session = (await client.invoke('sessions:getMessages', ctx.automationTestSessionId)) as ValidateMessagesResponse - const messages = session?.messages ?? session?.conversation ?? [] - const hasAssistant = messages.some((m) => m.role === 'assistant') - if (hasAssistant) { - const lastAssistant = [...messages].reverse().find((m) => m.role === 'assistant') - const text = typeof lastAssistant?.content === 'string' - ? lastAssistant.content - : Array.isArray(lastAssistant?.content) - ? lastAssistant.content.filter((b) => b.type === 'text').map((b) => b.text ?? '').join(' ') - : '' - return `session has assistant response (${text.slice(0, 80).trim()})` - } - await new Promise((r) => setTimeout(r, delay)) - delay = Math.min(delay * 1.5, 10_000) - } - throw new Error('Automation session did not complete within 90s') - }, - }, - { - name: 'automation:verify labels', - fn: async (client, ctx) => { - if (!ctx.automationTestSessionId || !ctx.workspaceId) return 'skipped (no automation session)' - // Verify label was auto-created - const labels = (await client.invoke('labels:list', ctx.workspaceId)) as ValidateLabel[] - const found = labels?.find((l) => (l.id ?? l.name ?? '') === 'cli-validate-label') - if (!found) throw new Error('Label cli-validate-label was not auto-created') - ctx.createdLabelId = found.id ?? 'cli-validate-label' - - // Verify the automation session has the label - const sessions = (await client.invoke('sessions:get', ctx.workspaceId)) as ValidateSession[] - const automationSession = sessions?.find((s) => s.id === ctx.automationTestSessionId) - const sessionLabels: string[] = automationSession?.labels ?? [] - const hasLabel = sessionLabels.some((l: string) => l.includes('cli-validate-label')) - if (!hasLabel) throw new Error(`Automation session missing label (has: ${sessionLabels.join(', ')})`) - return `label created and assigned: ${ctx.createdLabelId}` - }, - }, - { - name: 'automations:getLastExecuted', - fn: async (client, ctx) => { - if (!ctx.workspaceId) return 'skipped (no workspace)' - const history = (await client.invoke('automations:getLastExecuted', ctx.workspaceId)) as Record - const entries = Object.entries(history) - if (entries.length === 0) throw new Error('No automation execution history found') - // Verify at least one automation ran recently (within last 2 minutes) - const recentThreshold = Date.now() - 120_000 - const recent = entries.find(([, ts]) => ts > recentThreshold) - if (!recent) throw new Error(`No recent automation execution (latest: ${Math.max(...entries.map(([, ts]) => ts))})`) - return `${entries.length} automation(s), latest ran ${Math.round((Date.now() - recent[1]) / 1000)}s ago` - }, - }, - // ----- Webhook validation ----- - { - name: 'webhook:test (RPC)', - fn: async (client, ctx) => { - if (!ctx.workspaceId) return 'skipped (no workspace)' - const r = (await client.invoke('automations:test', { - workspaceId: ctx.workspaceId, - actions: [{ - type: 'webhook', - url: 'http://127.0.0.1:19999/validate-test', - method: 'GET', - }], - })) as any - const result = r?.actions?.[0] - if (result?.success) throw new Error('Expected webhook to fail (nothing listening)') - if (!result?.error && result?.statusCode !== 0) throw new Error('Expected error or statusCode 0 in result') - return `correctly failed: ${(result.error ?? `statusCode=${result.statusCode}`).slice(0, 80)}` - }, - }, - { - name: 'webhook:verify failure', - fn: async (client, ctx) => { - if (!ctx.workspaceRootPath) return 'skipped (no workspace root)' - const { readFile } = await import('fs/promises') - const historyPath = `${ctx.workspaceRootPath}/automations-history.jsonl` - - const start = Date.now() - const deadline = start + 15_000 - let delay = 200 - - let lastLineCount = 0 - let lastWebhookCount = 0 - let lastSummary = 'no entries' - - while (Date.now() < deadline) { - const content = await readFile(historyPath, 'utf-8').catch(() => '') - const lines = content.trim().split('\n').filter(Boolean) - lastLineCount = lines.length - - const entries = lines - .map((l) => { - try { - return JSON.parse(l) - } catch { - return null - } - }) - .filter(Boolean) as Array> - - const webhookEntries = entries.filter((e) => !!e.webhook) - lastWebhookCount = webhookEntries.length - - if (webhookEntries.length > 0) { - const recentThreshold = Date.now() - 120_000 - const recentFailed = webhookEntries.find((e: any) => - !e.ok && e.ts > recentThreshold && e.webhook?.method === 'POST' - ) as any - if (recentFailed) { - return `webhook failure recorded: method=${recentFailed.webhook.method}, url=${recentFailed.webhook.url?.slice(0, 50)}` - } - - const latest = webhookEntries[webhookEntries.length - 1] as any - lastSummary = `latest: ok=${String(latest?.ok)} method=${String(latest?.webhook?.method ?? 'n/a')} ts=${String(latest?.ts ?? 'n/a')}` - } - - await new Promise((r) => setTimeout(r, delay)) - delay = Math.min(Math.round(delay * 1.8), 1500) - } - - const waitedMs = Date.now() - start - throw new Error( - `No recent failed POST webhook history entry after ${waitedMs}ms (lines=${lastLineCount}, webhookEntries=${lastWebhookCount}, ${lastSummary})`, - ) - }, - }, - { - name: 'automation:cleanup', - fn: async (client, ctx) => { - const cleaned = await cleanupAutomationArtifacts(client, ctx) - return cleaned.length > 0 ? `cleaned: ${cleaned.join(', ')}` : 'nothing to clean' - }, - }, - { - name: 'sessions:branch delete', - fn: async (client, ctx) => { - if (!ctx.branchedSessionId) return 'skipped (no branch)' - await client.invoke('sessions:delete', ctx.branchedSessionId) - const id = ctx.branchedSessionId - ctx.branchedSessionId = undefined - return `deleted branch session: ${id}` - }, - }, - { - name: 'sources:delete', - fn: async (client, ctx) => { - if (!ctx.workspaceId || !ctx.createdSourceSlug) return 'skipped (no source)' - await client.invoke('sources:delete', ctx.workspaceId, ctx.createdSourceSlug) - return `deleted source: ${ctx.createdSourceSlug}` - }, - }, - { - name: 'labels:delete (e2e-test)', - fn: async (client, ctx) => { - if (!ctx.workspaceId || !ctx.e2eTestLabelId) return 'skipped (no e2e-test label)' - await client.invoke('labels:delete', ctx.workspaceId, ctx.e2eTestLabelId) - return `deleted label: ${ctx.e2eTestLabelId}` - }, - }, - { - name: 'sessions:delete', - fn: async (client, ctx) => { - if (!ctx.createdSessionId) return 'skipped (no session)' - await client.invoke('sessions:delete', ctx.createdSessionId) - return `deleted session: ${ctx.createdSessionId}` - }, - }, - { - name: 'Disconnect', - fn: async (client) => { - client.destroy() - return 'OK' - }, - }, - ] -} - -export async function runValidation( - client: CliRpcClient, - jsonMode: boolean, - noSpinner?: boolean, - workspaceDir?: string, - validateOptions?: { baseUrl?: string; apiKey?: string; provider?: string }, -): Promise { - const steps = getValidateSteps() - const total = steps.length - const ctx: ValidateContext = { - workspaceDir, - baseUrl: validateOptions?.baseUrl, - apiKey: validateOptions?.apiKey, - provider: validateOptions?.provider, - } - let passed = 0 - let failed = 0 - const results: Array<{ step: string; status: string; detail: string; elapsed: number }> = [] - const totalStart = performance.now() - - for (let i = 0; i < steps.length; i++) { - const step = steps[i] - const num = `[${i + 1}/${total}]` - const plainLen = num.length + 1 + step.name.length - - // Spinner + live event printer - // Spinner keeps running until the agent produces real output (text_delta/tool_start). - // Early events (user_message, connection_changed, usage_update) are buffered or ignored - // so the spinner stays visible while the agent is thinking. - let spinner: { stop(): void } | undefined - if (!jsonMode) { - let headerPrinted = false - let accText = '' - let textFlushed = false - let bufferedPrompt = '' - - if (_useColor && !noSpinner) { - spinner = createSpinner(`${c.cyan(num)} ${step.name}`) - } - - const flushText = () => { - if (textFlushed || !accText) return - const clean = accText.replace(/\n/g, ' ').trim() - if (!clean) return - const display = clean.length > 120 ? clean.slice(0, 120) + '…' : clean - process.stdout.write(` ${c.dim('↳')} ${c.yellow(display)}\n`) - textFlushed = true - } - - const ensureHeader = () => { - if (headerPrinted) return - spinner?.stop() - process.stdout.write(`${c.cyan(num)} ${step.name}\n`) - if (bufferedPrompt) { - process.stdout.write(` ${c.dim('→')} ${c.blue(`"${bufferedPrompt}"`)}\n`) - } - headerPrinted = true - } - - ctx.onEvent = (ev) => { - switch (ev.type) { - // Buffer prompt — shown when agent starts responding - case 'user_message': { - const msg = ev.message as any - let text = '' - if (typeof msg?.content === 'string') { - text = msg.content - } else if (Array.isArray(msg?.content)) { - text = msg.content.filter((b: any) => b.type === 'text').map((b: any) => b.text).join(' ') - } - const clean = text.replace(/\n/g, ' ').trim() - bufferedPrompt = clean.length > 100 ? clean.slice(0, 100) + '…' : clean - break - } - // Agent text — stop spinner, show header + prompt + text - case 'text_delta': - ensureHeader() - accText += String(ev.delta ?? '') - if (!textFlushed && accText.length > 40) flushText() - break - case 'text_complete': - ensureHeader() - flushText() - break - // Tool use — stop spinner, show header + prompt + tool - case 'tool_start': { - ensureHeader() - flushText() - const name = String(ev.toolName ?? '?') - const intent = ev.toolIntent ? ` — "${ev.toolIntent}"` : '' - process.stdout.write(` ${c.dim('↳')} ${c.dim(`tool: ${name}${intent}`)}\n`) - accText = '' - textFlushed = false - break - } - // Ignore internal events (connection_changed, usage_update, etc.) - } - } - } else { - ctx.onEvent = undefined - } - - const stepStart = performance.now() - try { - const detail = await step.fn(client, ctx) - const elapsed = (performance.now() - stepStart) / 1000 - passed++ - results.push({ step: step.name, status: 'OK', detail, elapsed }) - spinner?.stop() - if (!jsonMode) { - const dots = c.dim('.'.repeat(Math.max(1, 50 - plainLen))) - const time = c.dim(elapsed < 1 ? `(${Math.round(elapsed * 1000)}ms)` : `(${elapsed.toFixed(1)}s)`) - process.stdout.write(`${c.cyan(num)} ${step.name} ${dots} ${c.green('✓')} ${detail} ${time}\n`) - } - } catch (e) { - const elapsed = (performance.now() - stepStart) / 1000 - failed++ - const msg = e instanceof Error ? e.message : String(e) - results.push({ step: step.name, status: 'FAIL', detail: msg, elapsed }) - spinner?.stop() - if (!jsonMode) { - const dots = c.dim('.'.repeat(Math.max(1, 50 - plainLen))) - const time = c.dim(elapsed < 1 ? `(${Math.round(elapsed * 1000)}ms)` : `(${elapsed.toFixed(1)}s)`) - process.stderr.write(`${c.cyan(num)} ${step.name} ${dots} ${c.red('✗')} ${msg} ${time}\n`) - } - } - } - - // Cleanup: branched session - if (ctx.branchedSessionId && client.isConnected) { - try { - await client.invoke('sessions:delete', ctx.branchedSessionId) - } catch { - // best effort - } - } - - // Cleanup: if a session was created but delete step hasn't run or failed - if (ctx.createdSessionId && client.isConnected) { - try { - await client.invoke('sessions:delete', ctx.createdSessionId) - } catch { - // best effort - } - } - - // Cleanup: automation artifacts - await cleanupAutomationArtifacts(client, ctx) - - // Cleanup: if we auto-created a temp workspace, remove it - if (ctx.createdWorkspace && ctx.workspaceId && client.isConnected) { - try { - await client.invoke('workspaces:delete', ctx.workspaceId) - } catch { - // best effort - } - if (ctx.workspaceRootPath) { - try { - const { rm } = await import('fs/promises') - await rm(ctx.workspaceRootPath, { recursive: true, force: true }) - } catch { - // best effort - } - } - } - - const totalSec = ((performance.now() - totalStart) / 1000).toFixed(1) - - if (jsonMode) { - out({ total, passed, failed, results, elapsedSeconds: parseFloat(totalSec) }, true) - } else { - if (failed === 0) { - process.stdout.write(`\n${c.green(`✓ ${passed}/${total} passed`)} ${c.dim(`in ${totalSec}s`)}\n`) - } else { - process.stdout.write(`\n${c.red(`✗ ${passed}/${total} passed, ${failed} failed`)} ${c.dim(`in ${totalSec}s`)}\n`) - } - } - - return failed > 0 ? 1 : 0 -} - -// --------------------------------------------------------------------------- -// Help -// --------------------------------------------------------------------------- - -function printHelp(): void { - process.stdout.write(`craft-cli — Terminal client for Qwen Code server - -Usage: craft-cli [options] [args...] - -Connection: - --url Server URL (default: $CRAFT_SERVER_URL) - --token Auth token (default: $CRAFT_SERVER_TOKEN) - --workspace Workspace ID (auto-detected if omitted) - --timeout Request timeout (default: 10000) - --tls-ca Custom CA cert for self-signed TLS - --json Raw JSON output for scripting - -LLM Configuration (for 'run' command): - --provider LLM provider (default: qwen, or $LLM_PROVIDER) - Supported: qwen - --model Model to use (or $LLM_MODEL) - --api-key Ignored for Qwen Code; kept for script compatibility - --base-url Ignored for Qwen Code; kept for script compatibility - -Commands: - run Spawn server, send message, stream response, exit - --workspace-dir Use directory as workspace (creates if needed) - --source Enable source (repeatable) - --mode Permission mode (default: allow-all) - --output-format text or stream-json (default: text) - --no-cleanup Keep session after completion - --server-entry Path to server/index.ts - ping Verify connectivity (clientId + latency) - health Check credential store health - versions Show server runtime versions - workspaces List workspaces - sessions List sessions in workspace - connections List LLM connections - sources List configured sources - session create Create a session (--name, --mode) - session messages Print session message history - session delete Delete a session - send Send message and stream AI response - cancel Cancel in-progress processing - invoke [...] Raw RPC call with JSON args - listen Subscribe to push events (Ctrl+C to stop) - --validate-server Multi-step server integration test - --verbose, -v Show server stderr output - -Examples: - craft-cli run "What files are in the current directory?" - craft-cli run --source craft-kb "Summarize today's daily note" - craft-cli run --workspace-dir .github/agents --source craft-public "Read the doc" - craft-cli run --provider qwen --model qwen3-coder "Summarize this repo" - echo "Analyze this code" | craft-cli run - craft-cli ping - craft-cli sessions - craft-cli send abc-123 "What files are in the current directory?" - echo "Summarize this" | craft-cli send abc-123 - craft-cli --validate-server - craft-cli invoke system:homeDir - craft-cli --json workspaces | jq '.[].name' -`) -} - -// --------------------------------------------------------------------------- -// Main -// --------------------------------------------------------------------------- - -export async function main(argv: string[] = process.argv): Promise { - const args = parseArgs(argv) - - // Set custom CA before any WS connections - if (args.tlsCa) { - process.env.NODE_EXTRA_CA_CERTS = args.tlsCa - } - - if (args.command === 'help' || args.command === '') { - printHelp() - return - } - - if (args.command === 'version') { - const pkg = await import('../package.json') - out(pkg.version ?? pkg.default?.version ?? 'unknown', false) - return - } - - // run is self-contained — spawns its own server - if (args.command === 'run') { - await cmdRun(args) - return - } - - // validate can spawn its own server or use --url - if (args.command === 'validate') { - await cmdValidate(args) - return - } - - // All other commands need a server URL - if (!args.url) { - err('No server URL. Use --url or set $CRAFT_SERVER_URL') - process.exit(1) - } - - const client = new CliRpcClient(args.url, { - token: args.token || undefined, - workspaceId: args.workspace, - requestTimeout: args.timeout, - connectTimeout: args.timeout, - }) - - try { - switch (args.command) { - case 'ping': - await cmdPing(client, args) - break - case 'health': - await cmdHealth(client, args) - break - case 'versions': - await cmdVersions(client, args) - break - case 'workspaces': - await cmdWorkspaces(client, args) - break - case 'sessions': - await cmdSessions(client, args) - break - case 'connections': - await cmdConnections(client, args) - break - case 'sources': - await cmdSources(client, args) - break - case 'session': { - const subCmd = args.rest.shift() - switch (subCmd) { - case 'create': - await cmdSessionCreate(client, args) - break - case 'messages': - await cmdSessionMessages(client, args) - break - case 'delete': - await cmdSessionDelete(client, args) - break - default: - err(`Unknown session subcommand: ${subCmd}`) - process.exit(1) - } - break - } - case 'send': - await cmdSend(client, args) - break // cmdSend calls process.exit - case 'cancel': - await cmdCancel(client, args) - break - case 'invoke': - await cmdInvoke(client, args) - break - case 'listen': - await cmdListen(client, args) - break // never returns - default: - err(`Unknown command: ${args.command}`) - printHelp() - process.exit(1) - } - } catch (e) { - const msg = e instanceof Error ? e.message : String(e) - err(msg) - process.exit(1) - } finally { - client.destroy() - } -} - -// Run if executed directly (not when imported by tests) -if (import.meta.main) { - main() -} diff --git a/packages/desktop/apps/cli/src/run.test.ts b/packages/desktop/apps/cli/src/run.test.ts deleted file mode 100644 index e54e4dc11b9..00000000000 --- a/packages/desktop/apps/cli/src/run.test.ts +++ /dev/null @@ -1,427 +0,0 @@ -import { describe, it, expect, afterEach, mock, beforeEach } from 'bun:test' -import { - serializeEnvelope, - deserializeEnvelope, -} from '@craft-agent/server-core/transport' -import type { SpawnedServer } from './server-spawner.ts' - -// --------------------------------------------------------------------------- -// Mock WS server for run command tests -// --------------------------------------------------------------------------- - -interface MockServerOptions { - /** What LLM_Connection:list returns */ - connections?: unknown[] -} - -interface MockServer { - url: string - token: string - close: () => void - /** Channels invoked by the client, in order */ - invokedChannels: string[] - /** Arguments passed to sessions:create */ - createSessionArgs?: unknown[] - /** All invocation args, keyed by channel */ - invokeArgs: Record -} - -function pushSessionEvents( - ws: any, - sessionId: string, - events: Array>, -): void { - setTimeout(() => { - for (const ev of events) { - ws.send(serializeEnvelope({ - id: crypto.randomUUID(), - type: 'event', - channel: 'session:event', - args: [{ sessionId, ...ev }], - })) - } - }, 10) -} - -function createMockServer(opts?: MockServerOptions): MockServer { - const token = 'test-token' - const invokedChannels: string[] = [] - const invokeArgs: Record = {} - let createSessionArgs: unknown[] | undefined - const connections = opts?.connections ?? [] - - const server = Bun.serve({ - port: 0, - fetch(req, svr) { - if (svr.upgrade(req)) return undefined - return new Response('Not found', { status: 404 }) - }, - websocket: { - message(ws, message) { - const raw = typeof message === 'string' ? message : new TextDecoder().decode(message) - const envelope = deserializeEnvelope(raw) - - if (envelope.type === 'handshake') { - ws.send(serializeEnvelope({ - id: crypto.randomUUID(), - type: 'handshake_ack', - clientId: 'run-test-client', - protocolVersion: '1.0', - })) - return - } - - if (envelope.type === 'request') { - const ch = envelope.channel! - invokedChannels.push(ch) - if (!invokeArgs[ch]) invokeArgs[ch] = [] - invokeArgs[ch].push(envelope.args ?? []) - - let result: unknown - switch (ch) { - case 'workspaces:get': - result = [{ id: 'ws-1', name: 'Test Workspace' }] - break - case 'workspaces:create': - result = { id: 'ws-1', name: 'ci-workspace' } - break - case 'window:switchWorkspace': - result = { ok: true } - break - case 'LLM_Connection:list': - result = connections - break - case 'LLM_Connection:save': - result = { ok: true } - break - case 'settings:setupLlmConnection': - result = { ok: true } - break - case 'LLM_Connection:setDefault': - result = { ok: true } - break - case 'sessions:create': - createSessionArgs = envelope.args - result = { id: 'run-session-1', name: 'run-test' } - break - case 'sessions:sendMessage': { - ws.send(serializeEnvelope({ - id: envelope.id, - type: 'response', - channel: ch, - result: { started: true }, - })) - pushSessionEvents(ws, 'run-session-1', [ - { type: 'text_delta', delta: 'Hello ' }, - { type: 'text_delta', delta: 'World' }, - { type: 'complete' }, - ]) - return // already sent response - } - case 'sessions:delete': - result = { deleted: true } - break - case 'sessions:cancel': - result = { cancelled: true } - break - default: - result = null - } - - ws.send(serializeEnvelope({ - id: envelope.id, - type: 'response', - channel: ch, - result, - })) - } - }, - }, - }) - - return { - url: `ws://localhost:${server.port}`, - token, - close: () => server.stop(), - invokedChannels, - invokeArgs, - get createSessionArgs() { return createSessionArgs }, - } -} - -// --------------------------------------------------------------------------- -// Mock spawnServer so cmdRun doesn't actually launch a child process -// --------------------------------------------------------------------------- - -let mockWsServer: MockServer | null = null - -mock.module('./server-spawner.ts', () => ({ - spawnServer: async (): Promise => { - if (!mockWsServer) throw new Error('mockWsServer not initialized') - return { - url: mockWsServer.url, - token: mockWsServer.token, - stop: async () => {}, - } - }, -})) - -// Import main AFTER mocking -const { parseArgs } = await import('./index.ts') - -// --------------------------------------------------------------------------- -// Tests -// --------------------------------------------------------------------------- - -describe('run command', () => { - beforeEach(() => { - mockWsServer = createMockServer() - }) - - afterEach(() => { - mockWsServer?.close() - mockWsServer = null - }) - - it('parseArgs: run with --source accumulates sources', () => { - const args = parseArgs([ - 'bun', 'index.ts', - '--source', 'craft-kb', - '--source', 'github', - 'run', 'do', 'stuff', - ]) - expect(args.command).toBe('run') - expect(args.sources).toEqual(['craft-kb', 'github']) - expect(args.rest).toEqual(['do', 'stuff']) - }) - - it('parseArgs: --output-format stream-json', () => { - const args = parseArgs([ - 'bun', 'index.ts', - '--output-format', 'stream-json', - 'run', 'test', - ]) - expect(args.outputFormat).toBe('stream-json') - }) - - it('parseArgs: --no-cleanup flag', () => { - const args = parseArgs([ - 'bun', 'index.ts', - '--no-cleanup', - 'run', 'test', - ]) - expect(args.noCleanup).toBe(true) - }) - - it('creates session with correct workspace and options', async () => { - // We can't easily call cmdRun directly since it calls process.exit. - // Instead, test the mock server interaction via CliRpcClient to verify - // the channels and args that cmdRun would invoke. - const { CliRpcClient } = await import('./client.ts') - - const client = new CliRpcClient(mockWsServer!.url, { - token: mockWsServer!.token, - requestTimeout: 5_000, - }) - await client.connect() - - // Simulate what cmdRun does: resolve workspace, create session - const workspaces = await client.invoke('workspaces:get') as any[] - expect(workspaces).toHaveLength(1) - - await client.invoke('window:switchWorkspace', workspaces[0].id) - - const session = await client.invoke('sessions:create', 'ws-1', { - permissionMode: 'allow-all', - enabledSourceSlugs: ['craft-kb'], - }) as { id: string } - expect(session.id).toBe('run-session-1') - - // Verify the create args - expect(mockWsServer!.createSessionArgs).toEqual([ - 'ws-1', - { permissionMode: 'allow-all', enabledSourceSlugs: ['craft-kb'] }, - ]) - - // Verify channel order - expect(mockWsServer!.invokedChannels).toEqual([ - 'workspaces:get', - 'window:switchWorkspace', - 'sessions:create', - ]) - - client.destroy() - }) - - it('streams text events from session', async () => { - const { CliRpcClient } = await import('./client.ts') - - const client = new CliRpcClient(mockWsServer!.url, { - token: mockWsServer!.token, - requestTimeout: 5_000, - }) - await client.connect() - - // Subscribe and collect text deltas - const deltas: string[] = [] - let completed = false - - const unsub = client.on('session:event', (event: unknown) => { - const ev = event as { type: string; sessionId: string; delta?: string } - if (ev.sessionId !== 'run-session-1') return - if (ev.type === 'text_delta') deltas.push(ev.delta!) - if (ev.type === 'complete') completed = true - }) - - await client.invoke('sessions:sendMessage', 'run-session-1', 'test') - - // Wait for events - const deadline = Date.now() + 5_000 - while (!completed && Date.now() < deadline) { - await new Promise((r) => setTimeout(r, 50)) - } - - unsub() - expect(completed).toBe(true) - expect(deltas).toEqual(['Hello ', 'World']) - - client.destroy() - }) - - it('session delete is called in lifecycle', async () => { - const { CliRpcClient } = await import('./client.ts') - - const client = new CliRpcClient(mockWsServer!.url, { - token: mockWsServer!.token, - requestTimeout: 5_000, - }) - await client.connect() - - await client.invoke('sessions:create', 'ws-1', { permissionMode: 'allow-all' }) - await client.invoke('sessions:delete', 'run-session-1') - - expect(mockWsServer!.invokedChannels).toContain('sessions:create') - expect(mockWsServer!.invokedChannels).toContain('sessions:delete') - - client.destroy() - }) - - it('spawnServer mock returns expected url and token', async () => { - const { spawnServer } = await import('./server-spawner.ts') - const server = await spawnServer() - - expect(server.url).toBe(mockWsServer!.url) - expect(server.token).toBe(mockWsServer!.token) - expect(typeof server.stop).toBe('function') - }) - - it('parseArgs: --workspace-dir sets workspaceDir', () => { - const args = parseArgs([ - 'bun', 'index.ts', - '--workspace-dir', '/tmp/my-workspace', - 'run', 'hello', - ]) - expect(args.workspaceDir).toBe('/tmp/my-workspace') - expect(args.command).toBe('run') - }) - - it('parseArgs: workspaceDir defaults to undefined', () => { - const args = parseArgs(['bun', 'index.ts', 'run', 'hello']) - expect(args.workspaceDir).toBeUndefined() - }) - - it('workspace:create returns ID used directly (no workspaces:get needed)', async () => { - const { CliRpcClient } = await import('./client.ts') - - const client = new CliRpcClient(mockWsServer!.url, { - token: mockWsServer!.token, - requestTimeout: 5_000, - }) - await client.connect() - - // Simulate the workspace bootstrap path from cmdRun: - // workspaces:create returns { id }, which is used directly - const ws = (await client.invoke('workspaces:create', '/tmp/ws', 'ci-workspace')) as { id: string } - expect(ws.id).toBe('ws-1') - - // Then switchWorkspace is called with the returned ID - await client.invoke('window:switchWorkspace', ws.id) - - // Session is created with the bootstrapped workspace ID - await client.invoke('sessions:create', ws.id, { - permissionMode: 'allow-all', - enabledSourceSlugs: ['craft-public'], - }) - - expect(mockWsServer!.invokedChannels).toEqual([ - 'workspaces:create', - 'window:switchWorkspace', - 'sessions:create', - ]) - expect(mockWsServer!.invokeArgs['workspaces:create']![0]).toEqual(['/tmp/ws', 'ci-workspace']) - - client.destroy() - }) - - it('LLM bootstrap calls save, setup, and setDefault when no connections exist', async () => { - // Server returns empty connections list - mockWsServer?.close() - mockWsServer = createMockServer({ connections: [] }) - - const { CliRpcClient } = await import('./client.ts') - const client = new CliRpcClient(mockWsServer!.url, { - token: mockWsServer!.token, - requestTimeout: 5_000, - }) - await client.connect() - - // Simulate the LLM bootstrap path from cmdRun - const connections = (await client.invoke('LLM_Connection:list')) as any[] - expect(connections).toEqual([]) - - await client.invoke('LLM_Connection:save', { - slug: 'qwen-code', - name: 'Qwen Code', - providerType: 'qwen', - authType: 'none', - createdAt: 123, - }) - await client.invoke('settings:setupLlmConnection', { - slug: 'qwen-code', - }) - await client.invoke('LLM_Connection:setDefault', 'qwen-code') - - expect(mockWsServer!.invokedChannels).toEqual([ - 'LLM_Connection:list', - 'LLM_Connection:save', - 'settings:setupLlmConnection', - 'LLM_Connection:setDefault', - ]) - - client.destroy() - }) - - it('LLM bootstrap is skipped when connections already exist', async () => { - // Server returns existing connection - mockWsServer?.close() - mockWsServer = createMockServer({ - connections: [{ slug: 'existing', name: 'Existing' }], - }) - - const { CliRpcClient } = await import('./client.ts') - const client = new CliRpcClient(mockWsServer!.url, { - token: mockWsServer!.token, - requestTimeout: 5_000, - }) - await client.connect() - - // Simulate: check connections — they exist, so skip bootstrap - const connections = (await client.invoke('LLM_Connection:list')) as any[] - expect(connections).toHaveLength(1) - - // No further LLM calls should be needed - expect(mockWsServer!.invokedChannels).toEqual(['LLM_Connection:list']) - - client.destroy() - }) -}) diff --git a/packages/desktop/apps/cli/src/server-spawner.ts b/packages/desktop/apps/cli/src/server-spawner.ts deleted file mode 100644 index 941e21be200..00000000000 --- a/packages/desktop/apps/cli/src/server-spawner.ts +++ /dev/null @@ -1,146 +0,0 @@ -/** - * Server spawner — start a headless Qwen Code server as a child process. - * - * Spawns `bun run `, reads stdout for the `CRAFT_SERVER_URL=` - * and `CRAFT_SERVER_TOKEN=` lines, and returns a handle to stop the server. - */ - -import { resolve, join } from 'node:path' -import type { Subprocess } from 'bun' - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- - -export interface SpawnedServer { - url: string - token: string - stop: () => Promise -} - -export interface SpawnServerOptions { - /** Path to the server entry file. Auto-detected from monorepo root if omitted. */ - serverEntry?: string - /** Extra env vars to pass to the server process. */ - env?: Record - /** How long to wait for the server to print its URL (ms). Default: 30000. */ - startupTimeout?: number - /** Suppress server stderr output (useful for validation where only test output matters). */ - quiet?: boolean -} - -// --------------------------------------------------------------------------- -// Auto-detect server entry -// --------------------------------------------------------------------------- - -function findServerEntry(): string { - // Walk up from this file's directory to find the monorepo root. - // Expected layout: apps/cli/src/server-spawner.ts → root/packages/server/src/index.ts - let dir = import.meta.dir - for (let i = 0; i < 10; i++) { - const candidate = join(dir, 'packages', 'server', 'src', 'index.ts') - if (Bun.file(candidate).size > 0) return candidate - dir = resolve(dir, '..') - } - throw new Error( - 'Could not auto-detect server entry. ' + - 'Pass --server-entry or ensure the monorepo layout includes packages/server/src/index.ts', - ) -} - -// --------------------------------------------------------------------------- -// Spawn -// --------------------------------------------------------------------------- - -export async function spawnServer(opts?: SpawnServerOptions): Promise { - const serverEntry = opts?.serverEntry ?? findServerEntry() - const startupTimeout = opts?.startupTimeout ?? 30_000 - const token = crypto.randomUUID() - - const proc: Subprocess = Bun.spawn(['bun', 'run', serverEntry], { - env: { - ...process.env, - ...opts?.env, - CRAFT_SERVER_TOKEN: token, - CRAFT_RPC_PORT: '0', - CRAFT_RPC_HOST: '127.0.0.1', - }, - stdout: 'pipe', - stderr: 'pipe', - }) - - // Pipe server stderr to our stderr so --debug logs are visible (unless quiet) - if (proc.stderr && !opts?.quiet) { - ;(async () => { - // @ts-expect-error — Bun Subprocess types don't narrow stderr to ReadableStream when stderr: 'pipe' - const reader = proc.stderr.getReader() - try { - while (true) { - const { done, value } = await reader.read() - if (done) break - process.stderr.write(value) - } - } catch { - // Server exited — normal - } - })() - } - - // Read stdout line by line looking for CRAFT_SERVER_URL= - return new Promise((resolve, reject) => { - const timer = setTimeout(() => { - proc.kill() - reject(new Error(`Server did not start within ${startupTimeout}ms`)) - }, startupTimeout) - - let url = '' - let buffer = '' - - const processLines = () => { - const lines = buffer.split('\n') - buffer = lines.pop() ?? '' // keep incomplete last line in buffer - for (const line of lines) { - if (line.startsWith('CRAFT_SERVER_URL=')) { - url = line.slice('CRAFT_SERVER_URL='.length).trim() - } - if (line.startsWith('CRAFT_SERVER_TOKEN=')) { - // Server echoes the token — we already have it but this confirms ready - } - // Once we have the URL, the server is ready - if (url) { - clearTimeout(timer) - resolve({ - url, - token, - stop: async () => { - proc.kill('SIGTERM') - await proc.exited - }, - }) - return - } - } - } - - ;(async () => { - // @ts-expect-error — Bun Subprocess types don't narrow stdout to ReadableStream when stdout: 'pipe' - const reader = proc.stdout.getReader() - const decoder = new TextDecoder() - try { - while (true) { - const { done, value } = await reader.read() - if (done) break - buffer += decoder.decode(value, { stream: true }) - processLines() - } - } catch { - // Stream closed - } - // If we get here without resolving, the process exited before printing the URL - clearTimeout(timer) - if (!url) { - reject(new Error('Server process exited before printing CRAFT_SERVER_URL')) - } - })() - }) -} diff --git a/packages/desktop/apps/cli/tsconfig.json b/packages/desktop/apps/cli/tsconfig.json deleted file mode 100644 index 1c0952eea2a..00000000000 --- a/packages/desktop/apps/cli/tsconfig.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "compilerOptions": { - "lib": ["ESNext"], - "target": "ESNext", - "module": "ESNext", - "moduleDetection": "force", - "allowJs": true, - "esModuleInterop": true, - "resolveJsonModule": true, - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "verbatimModuleSyntax": true, - "noEmit": true, - "strict": true, - "skipLibCheck": true, - "types": ["node", "bun"], - "noFallthroughCasesInSwitch": true, - "noUnusedLocals": false, - "noUnusedParameters": false, - "baseUrl": ".", - "paths": { - "@craft-agent/shared": ["../../packages/shared/src/index.ts"], - "@craft-agent/shared/*": ["../../packages/shared/src/*"], - "@craft-agent/server-core": ["../../packages/server-core/src/index.ts"], - "@craft-agent/server-core/*": ["../../packages/server-core/src/*"] - } - }, - "include": ["src/**/*", "../../packages/shared/src/**/*.d.ts"], - "exclude": ["node_modules", "dist"] -} diff --git a/packages/desktop/apps/electron/.gitignore b/packages/desktop/apps/electron/.gitignore deleted file mode 100644 index 4f50e570fb9..00000000000 --- a/packages/desktop/apps/electron/.gitignore +++ /dev/null @@ -1,11 +0,0 @@ -# Build artifacts for DMG packaging -vendor/ -packages/ -release/ -node_modules/@anthropic-ai/ - -# Platform-specific uv binaries (downloaded during build) -resources/bin/darwin-arm64/ -resources/bin/darwin-x64/ -resources/bin/win32-x64/ -resources/bin/linux-x64/ diff --git a/packages/desktop/apps/electron/README.md b/packages/desktop/apps/electron/README.md deleted file mode 100644 index 3aa57a0a7c2..00000000000 --- a/packages/desktop/apps/electron/README.md +++ /dev/null @@ -1,28 +0,0 @@ -# Qwen Code Electron App - -Electron + React desktop interface for Qwen Code. - -The desktop app provides: - -- Qwen-backed multi-session chat -- Workspace and source management -- Onboarding for local Qwen Code setup -- Permission modes and plan approval flow -- File previews, diffs, browser panes, and automations - -## Development - -```bash -bun install -bun run electron:start -``` - -## Structure - -```text -src/main/ Electron main process -src/preload/ Context bridge -src/renderer/ React UI -src/transport/ RPC client/server transport -resources/ Built-in docs and release assets -``` diff --git a/packages/desktop/apps/electron/build/entitlements.mac.plist b/packages/desktop/apps/electron/build/entitlements.mac.plist deleted file mode 100644 index 369ba01ad37..00000000000 --- a/packages/desktop/apps/electron/build/entitlements.mac.plist +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - com.apple.security.cs.allow-jit - - com.apple.security.cs.allow-unsigned-executable-memory - - - com.apple.security.cs.disable-library-validation - - - com.apple.security.device.audio-input - - - diff --git a/packages/desktop/apps/electron/components.json b/packages/desktop/apps/electron/components.json deleted file mode 100644 index b69345d8fc8..00000000000 --- a/packages/desktop/apps/electron/components.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "$schema": "https://ui.shadcn.com/schema.json", - "style": "new-york", - "rsc": false, - "tsx": true, - "tailwind": { - "config": "", - "css": "src/renderer/index.css", - "baseColor": "neutral", - "cssVariables": true - }, - "aliases": { - "components": "@/components", - "utils": "@/lib/utils", - "ui": "@/components/ui", - "lib": "@/lib", - "hooks": "@/hooks" - } -} diff --git a/packages/desktop/apps/electron/electron-builder.yml b/packages/desktop/apps/electron/electron-builder.yml deleted file mode 100644 index 7a2b8ba3655..00000000000 --- a/packages/desktop/apps/electron/electron-builder.yml +++ /dev/null @@ -1,192 +0,0 @@ -appId: com.alibaba.qwen-code -productName: Qwen Code Desktop -copyright: Copyright © 2026 Alibaba Group. - -electronVersion: "39.2.7" - -# Hook to compile macOS 26+ Liquid Glass icon after packaging -afterPack: scripts/afterPack.cjs - -directories: - output: release - buildResources: resources - -files: - - dist/**/* - - "!dist/renderer/src/**" - - "!**/*.map" - - package.json - # Include bundled MCP servers (bridge + session) for agent sessions - - resources/bridge-mcp-server/**/* - - resources/session-mcp-server/**/* - # Note: Bundled assets (docs, themes, permissions, tool-icons) are in resources/ - # and copied to dist/resources/ by build:copy. They're included via dist/**/* above. - - packages/shared/src/interceptor-common.ts - - packages/shared/src/feature-flags.ts - - packages/shared/src/interceptor-request-utils.ts - # Include CLI tool Python scripts (platform-independent) - - resources/scripts/**/* - # Include CLI tool shell wrappers (platform-independent, both Unix and Windows) - - resources/bin/markitdown - - resources/bin/markitdown.cmd - - resources/bin/pdf-tool - - resources/bin/pdf-tool.cmd - - resources/bin/xlsx-tool - - resources/bin/xlsx-tool.cmd - - resources/bin/doc-diff - - resources/bin/doc-diff.cmd - - resources/bin/img-tool - - resources/bin/img-tool.cmd - - resources/bin/docx-tool - - resources/bin/docx-tool.cmd - - resources/bin/pptx-tool - - resources/bin/pptx-tool.cmd - - resources/bin/ical-tool - - resources/bin/ical-tool.cmd - # Include bundled uv binary (platform-specific, only the relevant one ships per build) - - resources/bin/darwin-arm64/**/* - - resources/bin/darwin-x64/**/* - - resources/bin/win32-x64/**/* - - resources/bin/linux-x64/**/* - # Include bundled Bun runtime (platform-specific, set by build script) - - vendor/bun/**/* - # Include bundled Codex binary (platform-specific, downloaded by build script) - - vendor/codex/**/* - # Include bundled Qwen Code CLI package (downloaded by build script) - - vendor/qwen-code/**/* - # Exclude everything from node_modules. - - "!node_modules/**/*" - -extraMetadata: - main: dist/main.cjs - -# Auto-update publish metadata is generated from the active brand config. - -# Disable ASAR to avoid decompression overhead and click delays -asar: false - -mac: - category: public.app-category.productivity - icon: resources/brands/qwen-code/icon.icns - # macOS 26+ Liquid Glass: Tell macOS to look for icon in Assets.car - # The value must match --app-icon used in actool (see afterPack.js) - extendInfo: - CFBundleIconName: AppIcon - # Voice dictation: shown in the macOS microphone permission prompt. - NSMicrophoneUsageDescription: Qwen Code uses the microphone for voice dictation in the prompt composer. - target: - - target: dmg - arch: - - arm64 - - x64 - - target: zip - arch: - - arm64 - - x64 - hardenedRuntime: true - gatekeeperAssess: false - entitlements: build/entitlements.mac.plist - entitlementsInherit: build/entitlements.mac.plist - extraResources: - # WhatsApp worker subprocess (self-contained; Baileys bundled in). - - from: ../../packages/messaging-whatsapp-worker/dist/worker.cjs - to: messaging-whatsapp-worker/worker.cjs - # Exclude binaries for other platforms - files: - - "!**/vendor/codex/linux-*/**" - - "!**/vendor/codex/win32-*/**" - - "!**/resources/bin/win32-*/**" - - "!**/resources/bin/linux-*/**" - # Use predictable naming for macOS packages (applies to zip; dmg uses its own config below) - artifactName: "Qwen-Code-Desktop-${arch}.${ext}" - # Code signing & notarization (disabled by default for local builds) - # To enable: set CSC_LINK, APPLE_ID, APPLE_APP_SPECIFIC_PASSWORD, APPLE_TEAM_ID - # notarize: - # teamId: ${APPLE_TEAM_ID} - -dmg: - # Use predictable naming: Qwen-Code-Desktop-arm64.dmg, Qwen-Code-Desktop-x64.dmg - artifactName: "Qwen-Code-Desktop-${arch}.dmg" - # Custom background (multi-resolution TIFF with 1x+2x for retina support) - background: resources/dmg-background.tiff - # Use app icon as the mounted volume icon in Finder - icon: resources/brands/qwen-code/icon.icns - iconSize: 80 - title: "Qwen Code Desktop" - contents: - - x: 130 - y: 200 - - x: 410 - y: 200 - type: link - path: /Applications - window: - width: 540 - height: 380 - -win: - icon: resources/brands/qwen-code/icon.ico - # Windows releases are currently unsigned, so keep this update channel unsigned - # from the first release. Re-enable only with a planned signed-channel migration. - verifyUpdateCodeSignature: false - target: - - target: nsis - arch: - - x64 - # Use predictable naming: Qwen-Code-Desktop-x64.exe - artifactName: "Qwen-Code-Desktop-${arch}.${ext}" - files: - # Exclude binaries for other platforms - - "!**/vendor/codex/darwin-*/**" - - "!**/vendor/codex/linux-*/**" - - "!**/resources/bin/darwin-*/**" - - "!**/resources/bin/linux-*/**" - # WORKAROUND: Exclude bun, codex, uv, and Qwen Code from regular files on Windows. - # electron-builder's npm node module collector causes EBUSY errors when copying - # files because it scans/locks files while simultaneously trying to copy them. - # Moving them to extraResources avoids this by copying before the collector runs. - # See: https://github.com/electron-userland/electron-builder/issues/8250 - - "!vendor/bun/**/*" - - "!vendor/codex/**/*" - - "!vendor/qwen-code/**/*" - - "!**/resources/bin/win32-x64/**" - # Copy executables as extraResources to avoid EBUSY file locking (see comment above) - extraResources: - - from: vendor/bun/bun.exe - to: vendor/bun/bun.exe - - from: vendor/codex/win32-x64 - to: app/vendor/codex/win32-x64 - - from: vendor/qwen-code - to: app/vendor/qwen-code - - from: resources/bin/win32-x64 - to: app/resources/bin/win32-x64 - # WhatsApp worker subprocess (self-contained; Baileys bundled in). - - from: ../../packages/messaging-whatsapp-worker/dist/worker.cjs - to: messaging-whatsapp-worker/worker.cjs - -nsis: - oneClick: true - # Per-user install to %LOCALAPPDATA%\Programs\ (not Program Files). - # Bun subprocess cannot read/write files in Program Files due to Windows permissions. - perMachine: false - deleteAppDataOnUninstall: true - -linux: - icon: resources/brands/qwen-code/icon.png - category: Utility - maintainer: "Alibaba Group" - target: - - target: AppImage - arch: - - x64 - artifactName: "Qwen-Code-Desktop-${arch}.${ext}" - extraResources: - # WhatsApp worker subprocess (self-contained; Baileys bundled in). - - from: ../../packages/messaging-whatsapp-worker/dist/worker.cjs - to: messaging-whatsapp-worker/worker.cjs - # Exclude binaries for other platforms - files: - - "!**/vendor/codex/darwin-*/**" - - "!**/vendor/codex/win32-*/**" - - "!**/resources/bin/darwin-*/**" - - "!**/resources/bin/win32-*/**" diff --git a/packages/desktop/apps/electron/eslint-rules/__tests__/no-hardcoded-z-index.test.ts b/packages/desktop/apps/electron/eslint-rules/__tests__/no-hardcoded-z-index.test.ts deleted file mode 100644 index b5288159067..00000000000 --- a/packages/desktop/apps/electron/eslint-rules/__tests__/no-hardcoded-z-index.test.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { describe, expect, it } from 'bun:test' -import { Linter } from 'eslint' -import { createRequire } from 'node:module' - -const require = createRequire(import.meta.url) -const rule = require('../no-hardcoded-z-index.cjs') - -function runRule(code: string) { - const linter = new Linter({ configType: 'eslintrc' }) - linter.defineRule('craft-styles/no-hardcoded-z-index', rule) - - return linter.verify(code, { - parserOptions: { - ecmaVersion: 'latest', - sourceType: 'module', - }, - rules: { - 'craft-styles/no-hardcoded-z-index': 'error', - }, - }) -} - -describe('no-hardcoded-z-index (electron)', () => { - it('flags hardcoded default in destructured props', () => { - const messages = runRule('function Comp({ zIndex = 50 }) { return zIndex }') - expect(messages.length).toBe(1) - expect(messages[0]?.message).toContain('Avoid hardcoded zIndex values') - }) - - it('flags hardcoded default in object destructuring', () => { - const messages = runRule('const { zIndex = 50 } = props') - expect(messages.length).toBe(1) - }) - - it('allows token-based zIndex default values', () => { - const messages = runRule("const { zIndex = 'var(--z-floating-menu, 400)' } = props") - expect(messages.length).toBe(0) - }) - - it('continues to flag hardcoded style zIndex literals', () => { - const messages = runRule('const style = { zIndex: 400 }') - expect(messages.length).toBe(1) - }) -}) diff --git a/packages/desktop/apps/electron/eslint-rules/no-direct-file-open.cjs b/packages/desktop/apps/electron/eslint-rules/no-direct-file-open.cjs deleted file mode 100644 index f820e54d0de..00000000000 --- a/packages/desktop/apps/electron/eslint-rules/no-direct-file-open.cjs +++ /dev/null @@ -1,65 +0,0 @@ -/** - * ESLint Rule: no-direct-file-open - * - * Prevents calling window.electronAPI.openFile() directly in renderer code. - * All file-open calls should go through the link interceptor (via AppShellContext - * or PlatformContext's onOpenFile) so the app can show in-app previews for - * supported file types instead of always opening in the default external app. - * - * Allowed in: - * - App.tsx (link interceptor implementation) - * - useLinkInterceptor.ts (link interceptor fallback) - * - * Bad: - * window.electronAPI.openFile(path) - * - * Good: - * const { onOpenFile } = useAppShellContext() - * onOpenFile(path) - */ - -/** @type {import('eslint').Rule.RuleModule} */ -module.exports = { - meta: { - type: 'problem', - docs: { - description: 'Disallow direct window.electronAPI.openFile() calls. Use onOpenFile from context instead.', - category: 'Best Practices', - recommended: true, - }, - messages: { - noDirectFileOpen: - 'Use onOpenFile from AppShellContext or PlatformContext instead of calling window.electronAPI.openFile() directly. This ensures the link interceptor can show in-app previews for supported file types.', - }, - schema: [], - }, - - create(context) { - // Allow direct calls in the interceptor implementation files - const filename = context.filename || context.getFilename() - const basename = filename.split('/').pop() || '' - if (basename === 'App.tsx' || basename === 'useLinkInterceptor.ts') { - return {} - } - - return { - // Match: window.electronAPI.openFile(...) - CallExpression(node) { - const callee = node.callee - if ( - callee.type === 'MemberExpression' && - callee.property.type === 'Identifier' && - callee.property.name === 'openFile' && - callee.object.type === 'MemberExpression' && - callee.object.property.type === 'Identifier' && - callee.object.property.name === 'electronAPI' - ) { - context.report({ - node, - messageId: 'noDirectFileOpen', - }) - } - }, - } - }, -} diff --git a/packages/desktop/apps/electron/eslint-rules/no-direct-navigation-state.cjs b/packages/desktop/apps/electron/eslint-rules/no-direct-navigation-state.cjs deleted file mode 100644 index f125914847d..00000000000 --- a/packages/desktop/apps/electron/eslint-rules/no-direct-navigation-state.cjs +++ /dev/null @@ -1,103 +0,0 @@ -/** - * ESLint Rule: no-direct-navigation-state - * - * Prevents direct calls to navigation state setters outside of the - * SIDEBAR_NAVIGATE_EVENT listener in AppShell.tsx. All navigation should - * go through navigate(routes.xxx()) to ensure: - * - * 1. URL/deep link consistency - * 2. History tracking for back/forward - * 3. Auto-selection of first item in new views - * - * Bad (in click handlers): - * setSidebarMode({ type: 'sources' }) - * - * Good: - * navigate(routes.view.sources()) - * navigate(routes.view.agent(agentId)) - * - * Note: This rule only checks AppShell.tsx where the navigation state is defined. - * The setSidebarMode function is not exported, so other files can't use it anyway. - */ - -/** - * Get ancestor nodes for a given node in ESLint 9+ - * ESLint 9 removed context.getAncestors(), so we use sourceCode.getAncestors() - */ -function getAncestors(context, node) { - const sourceCode = context.sourceCode || context.getSourceCode() - if (sourceCode.getAncestors) { - return sourceCode.getAncestors(node) - } - // Fallback for older versions - if (context.getAncestors) { - return context.getAncestors() - } - return [] -} - -/** @type {import('eslint').Rule.RuleModule} */ -module.exports = { - meta: { - type: 'problem', - docs: { - description: 'Disallow direct calls to setSidebarMode outside the navigation event handler.', - category: 'Best Practices', - recommended: true, - }, - messages: { - noDirectCall: - "Do not call 'setSidebarMode()' directly. Use navigate(routes.xxx()) instead to ensure URL consistency, history tracking, and auto-selection. Only the handleSidebarNavigate event listener should call setSidebarMode. See the comment at line ~595 in AppShell.tsx.", - }, - schema: [], - }, - - create(context) { - // Get the filename - only check AppShell.tsx - const filename = context.filename || context.getFilename() - const isAppShell = filename.includes('AppShell.tsx') - - // Only apply this rule to AppShell.tsx - if (!isAppShell) { - return {} - } - - return { - CallExpression(node) { - // Only check setSidebarMode calls - if ( - node.callee.type === 'Identifier' && - node.callee.name === 'setSidebarMode' - ) { - const ancestors = getAncestors(context, node) - - // Allow if inside handleSidebarNavigate - for (const ancestor of ancestors) { - // Check for: const handleSidebarNavigate = useCallback(...) - if ( - ancestor.type === 'VariableDeclarator' && - ancestor.id.type === 'Identifier' && - ancestor.id.name === 'handleSidebarNavigate' - ) { - return // Allowed - inside the event handler - } - // Check for: function handleSidebarNavigate(...) - if ( - ancestor.type === 'FunctionDeclaration' && - ancestor.id && - ancestor.id.name === 'handleSidebarNavigate' - ) { - return // Allowed - } - } - - // Not inside allowed context - report error - context.report({ - node, - messageId: 'noDirectCall', - }) - } - }, - } - }, -} diff --git a/packages/desktop/apps/electron/eslint-rules/no-direct-platform-check.cjs b/packages/desktop/apps/electron/eslint-rules/no-direct-platform-check.cjs deleted file mode 100644 index e7434aef2a2..00000000000 --- a/packages/desktop/apps/electron/eslint-rules/no-direct-platform-check.cjs +++ /dev/null @@ -1,59 +0,0 @@ -/** - * ESLint Rule: no-direct-platform-check - * - * Prevents direct access to navigator.platform. Use the platform utilities instead: - * - * import { isMac, isWindows, isLinux, PATH_SEP } from '@/lib/platform' - * - * This ensures consistent platform detection across the codebase and proper - * handling of path separators on different operating systems. - * - * Bad: - * navigator.platform.toLowerCase().includes('mac') - * navigator.platform.toUpperCase().indexOf('MAC') >= 0 - * - * Good: - * import { isMac } from '@/lib/platform' - * if (isMac) { ... } - */ - -/** @type {import('eslint').Rule.RuleModule} */ -module.exports = { - meta: { - type: 'problem', - docs: { - description: 'Disallow direct access to navigator.platform', - category: 'Best Practices', - recommended: true, - }, - messages: { - noDirectAccess: - "Don't access 'navigator.platform' directly. Import from '@/lib/platform' instead: { isMac, isWindows, isLinux, PATH_SEP }", - }, - schema: [], - }, - - create(context) { - return { - MemberExpression(node) { - if ( - node.object.type === 'Identifier' && - node.object.name === 'navigator' && - node.property.type === 'Identifier' && - node.property.name === 'platform' - ) { - // Allow in platform.ts itself (the source of truth) - const filename = context.filename || context.getFilename() - if (filename.includes('platform.ts')) { - return - } - - context.report({ - node, - messageId: 'noDirectAccess', - }) - } - }, - } - }, -} diff --git a/packages/desktop/apps/electron/eslint-rules/no-hardcoded-path-separator.cjs b/packages/desktop/apps/electron/eslint-rules/no-hardcoded-path-separator.cjs deleted file mode 100644 index 43a17c39b77..00000000000 --- a/packages/desktop/apps/electron/eslint-rules/no-hardcoded-path-separator.cjs +++ /dev/null @@ -1,65 +0,0 @@ -/** - * ESLint Rule: no-hardcoded-path-separator - * - * Prevents hardcoded path separators in path comparison operations. - * This catches patterns like `path.startsWith(dir + '/')` which fail on Windows. - * - * Bad: - * filePath.startsWith(dir + '/') - * path.startsWith(prefix + '/') - * - * Good: - * import { pathStartsWith } from '@craft-agent/core/utils' - * pathStartsWith(filePath, dir) - * - * Or in Node.js main process: - * import { sep } from 'path' - * filePath.startsWith(dir + sep) - */ - -/** @type {import('eslint').Rule.RuleModule} */ -module.exports = { - meta: { - type: 'problem', - docs: { - description: 'Disallow hardcoded path separators in path operations', - category: 'Cross-Platform', - recommended: true, - }, - messages: { - hardcodedSeparator: - "Avoid hardcoded '/' or '\\\\' in path operations - this breaks on Windows/Unix. " + - "Use pathStartsWith() from @craft-agent/core/utils, or path.sep in Node.js code.", - }, - schema: [], - }, - - create(context) { - return { - BinaryExpression(node) { - // Detect: someVar + '/' or someVar + '\\' - if ( - node.operator === '+' && - node.right.type === 'Literal' && - (node.right.value === '/' || node.right.value === '\\') - ) { - // Check if parent is a path operation (startsWith, endsWith, includes) - const parent = node.parent - if (parent?.type === 'CallExpression') { - const callee = parent.callee - if ( - callee?.type === 'MemberExpression' && - callee.property?.type === 'Identifier' && - ['startsWith', 'endsWith', 'includes'].includes(callee.property.name) - ) { - context.report({ - node, - messageId: 'hardcodedSeparator', - }) - } - } - } - }, - } - }, -} diff --git a/packages/desktop/apps/electron/eslint-rules/no-hardcoded-z-index.cjs b/packages/desktop/apps/electron/eslint-rules/no-hardcoded-z-index.cjs deleted file mode 100644 index eaeb813dd27..00000000000 --- a/packages/desktop/apps/electron/eslint-rules/no-hardcoded-z-index.cjs +++ /dev/null @@ -1,133 +0,0 @@ -/** - * ESLint Rule: no-hardcoded-z-index - * - * Enforces the centralized z-index token system by blocking hardcoded - * literal values in JS/TS style objects and style assignments. - * - * Allowed examples: - * style={{ zIndex: 'var(--z-floating-menu, 400)' }} - * style={{ zIndex: 'calc(var(--z-floating-menu, 400) + 1)' }} - * style={{ zIndex: Z_FULLSCREEN }} - * style={{ zIndex: index + 1 }} - * - * Disallowed examples: - * style={{ zIndex: 400 }} - * style={{ zIndex: '400' }} - * el.style.zIndex = 9999 - */ - -/** @type {import('eslint').Rule.RuleModule} */ -module.exports = { - meta: { - type: 'suggestion', - docs: { - description: 'Disallow hardcoded zIndex literals. Use centralized z-index tokens/constants.', - category: 'Best Practices', - recommended: true, - }, - schema: [], - messages: { - noHardcodedZIndex: - 'Avoid hardcoded zIndex values. Use z-index tokens (for example var(--z-floating-menu, 400)), Tailwind z-* utilities, or a named constant.', - }, - }, - - create(context) { - function isZIndexPropertyName(node) { - if (!node) return false - if (node.type === 'Identifier') return node.name === 'zIndex' - if (node.type === 'Literal') return node.value === 'zIndex' - return false - } - - function getStaticTemplateValue(node) { - if (node.type !== 'TemplateLiteral') return null - if (node.expressions.length > 0) return null - return node.quasis.map((q) => q.value.cooked ?? '').join('') - } - - function isAllowedZIndexString(value) { - const normalized = value.trim().toLowerCase() - - // CSS variable/token usage from centralized z-scale - if (normalized.includes('var(--z-')) return true - - // Common CSS keywords - if ( - normalized === 'auto' || - normalized === 'inherit' || - normalized === 'initial' || - normalized === 'unset' || - normalized === 'revert' || - normalized === 'revert-layer' - ) { - return true - } - - return false - } - - function isHardcodedLiteralValue(node) { - if (!node) return false - - if (node.type === 'Literal') { - if (typeof node.value === 'number') return true - if (typeof node.value === 'string') return !isAllowedZIndexString(node.value) - return false - } - - if (node.type === 'TemplateLiteral') { - const staticValue = getStaticTemplateValue(node) - if (staticValue == null) return false - return !isAllowedZIndexString(staticValue) - } - - return false - } - - function isStyleZIndexMemberExpression(node) { - // Matches: something.style.zIndex - return ( - node && - node.type === 'MemberExpression' && - !node.computed && - node.property && - node.property.type === 'Identifier' && - node.property.name === 'zIndex' && - node.object && - node.object.type === 'MemberExpression' && - !node.object.computed && - node.object.property && - node.object.property.type === 'Identifier' && - node.object.property.name === 'style' - ) - } - - function isZIndexIdentifier(node) { - return node && node.type === 'Identifier' && node.name === 'zIndex' - } - - return { - Property(node) { - if (!isZIndexPropertyName(node.key)) return - if (isHardcodedLiteralValue(node.value)) { - context.report({ node: node.value, messageId: 'noHardcodedZIndex' }) - } - }, - - AssignmentPattern(node) { - if (!isZIndexIdentifier(node.left)) return - if (isHardcodedLiteralValue(node.right)) { - context.report({ node: node.right, messageId: 'noHardcodedZIndex' }) - } - }, - - AssignmentExpression(node) { - if (!isStyleZIndexMemberExpression(node.left)) return - if (isHardcodedLiteralValue(node.right)) { - context.report({ node: node.right, messageId: 'noHardcodedZIndex' }) - } - }, - } - }, -} diff --git a/packages/desktop/apps/electron/eslint-rules/no-inline-source-auth-check.cjs b/packages/desktop/apps/electron/eslint-rules/no-inline-source-auth-check.cjs deleted file mode 100644 index a8b80474a84..00000000000 --- a/packages/desktop/apps/electron/eslint-rules/no-inline-source-auth-check.cjs +++ /dev/null @@ -1,85 +0,0 @@ -/** - * ESLint Rule: no-inline-source-auth-check - * - * Prevents inline checks of source.config.isAuthenticated. - * Use the centralized isSourceUsable() helper instead. - * - * The isSourceUsable() helper correctly handles: - * - Sources with authType: 'none' (no auth required) - * - Sources with undefined authType (no auth required) - * - Sources with OAuth/Bearer auth (requires isAuthenticated) - * - * Inline checks often miss the authType: 'none' case, causing bugs where - * no-auth sources are incorrectly filtered out. - * - * Allowed in: - * - storage.ts (where isSourceUsable is defined) - * - credential-manager.ts (state-setting operations, inverse check) - * - server-builder.ts (documented exceptions for OAuth providers) - * - * Bad: - * source.config.isAuthenticated - * s.config.enabled && s.config.isAuthenticated - * - * Good: - * isSourceUsable(source) - */ - -/** @type {import('eslint').Rule.RuleModule} */ -module.exports = { - meta: { - type: 'suggestion', - docs: { - description: - 'Disallow inline source.config.isAuthenticated checks. Use isSourceUsable() from storage.ts instead.', - category: 'Best Practices', - recommended: true, - }, - messages: { - useIsSourceUsable: - 'Do not check source.config.isAuthenticated directly. Use isSourceUsable() from sources/storage.ts instead. ' + - 'Direct checks often miss sources with authType: "none" which should be considered authenticated.', - }, - schema: [], - }, - - create(context) { - // Files where direct isAuthenticated access is allowed - const allowedFiles = [ - 'storage.ts', // isSourceUsable is defined here - 'credential-manager.ts', // State-setting and inverse check - 'server-builder.ts', // OAuth provider checks (documented) - ] - - const filename = context.filename || context.getFilename() - const basename = filename.split('/').pop() || '' - - // Allow in specific files - if (allowedFiles.includes(basename)) { - return {} - } - - return { - // Match: .config.isAuthenticated access - MemberExpression(node) { - // Check if property is 'isAuthenticated' - if ( - node.property.type === 'Identifier' && - node.property.name === 'isAuthenticated' - ) { - // Check if accessed via .config.isAuthenticated pattern - if ( - node.object.type === 'MemberExpression' && - node.object.property.type === 'Identifier' && - node.object.property.name === 'config' - ) { - context.report({ - node, - messageId: 'useIsSourceUsable', - }) - } - } - }, - } - }, -} diff --git a/packages/desktop/apps/electron/eslint-rules/no-localstorage.cjs b/packages/desktop/apps/electron/eslint-rules/no-localstorage.cjs deleted file mode 100644 index c975c49119f..00000000000 --- a/packages/desktop/apps/electron/eslint-rules/no-localstorage.cjs +++ /dev/null @@ -1,101 +0,0 @@ -/** - * ESLint Rule: no-localstorage - * - * Warns against using localStorage in Qwen Code codebase. - * All persistent user settings should be stored in file-based configs - * (preferences.json, workspace configs) for consistency with Qwen Code - * architecture principles. - * - * Bad: - * localStorage.getItem('key') - * localStorage.setItem('key', 'value') - * window.localStorage.getItem('key') - * - * Good: - * // Use IPC to read/write preferences - * window.electronAPI.readPreferences() - * window.electronAPI.writePreferences(content) - * - * Why: File-based configs are: - * - Portable (sync via cloud services) - * - Editable (users can manually modify) - * - Consistent (all settings in one place) - * - Inspectable (easy debugging) - */ - -/** @type {import('eslint').Rule.RuleModule} */ -module.exports = { - meta: { - type: 'suggestion', - docs: { - description: 'Disallow localStorage usage. Use file-based preferences instead.', - category: 'Best Practices', - recommended: true, - }, - messages: { - noLocalStorage: - "Avoid localStorage in Qwen Code. Store settings in ~/.craft-agent/preferences.json using window.electronAPI.readPreferences/writePreferences. See packages/shared/src/config/preferences.ts for the preferences API.", - }, - schema: [], - }, - - create(context) { - /** - * Check if a node is a localStorage access (localStorage or window.localStorage) - */ - function isLocalStorageAccess(node) { - // Direct: localStorage.getItem - if (node.type === 'Identifier' && node.name === 'localStorage') { - return true - } - - // window.localStorage - if ( - node.type === 'MemberExpression' && - node.object.type === 'Identifier' && - node.object.name === 'window' && - node.property.type === 'Identifier' && - node.property.name === 'localStorage' - ) { - return true - } - - return false - } - - return { - // Catch localStorage.getItem(), localStorage.setItem(), etc. - MemberExpression(node) { - if (isLocalStorageAccess(node.object)) { - context.report({ - node, - messageId: 'noLocalStorage', - }) - } - }, - - // Catch direct localStorage references (e.g., passing it as argument) - Identifier(node) { - if (node.name === 'localStorage') { - // Only report if it's being used (not just referenced in a type) - const parent = node.parent - if ( - parent && - parent.type === 'MemberExpression' && - parent.object === node - ) { - // Already handled by MemberExpression rule - return - } - // Report standalone localStorage references - if (parent && parent.type !== 'TSTypeReference') { - context.report({ - node, - messageId: 'noLocalStorage', - }) - } - } - }, - } - }, -} diff --git a/packages/desktop/apps/electron/eslint-rules/no-nonstandard-shadows.cjs b/packages/desktop/apps/electron/eslint-rules/no-nonstandard-shadows.cjs deleted file mode 100644 index 59fa5677aae..00000000000 --- a/packages/desktop/apps/electron/eslint-rules/no-nonstandard-shadows.cjs +++ /dev/null @@ -1,156 +0,0 @@ -/** - * ESLint Rule: no-nonstandard-shadows - * - * Enforces approved shadow usage: - * - Allows only specific shadow-* utility classes - * - Disallows arbitrary shadow classes (shadow-[...]) unless explicitly allowlisted - * - Disallows inline style boxShadow values - * - Disallows direct style assignments (el.style.boxShadow = ...) - */ - -/** @type {import('eslint').Rule.RuleModule} */ -module.exports = { - meta: { - type: 'suggestion', - docs: { - description: 'Allow only approved shadow utilities and block inline boxShadow usage.', - category: 'Best Practices', - recommended: true, - }, - schema: [ - { - type: 'object', - properties: { - allowedClasses: { - type: 'array', - items: { type: 'string' }, - }, - allowInlineNone: { - type: 'boolean', - }, - }, - additionalProperties: false, - }, - ], - messages: { - disallowedClass: - 'Disallowed shadow class "{{className}}". Use approved shadow classes only: {{allowed}}.', - disallowedInline: - 'Avoid inline boxShadow usage. Use approved shadow utility classes (for example shadow-minimal/shadow-modal-small).', - }, - }, - - create(context) { - const options = context.options[0] || {} - const allowedClasses = new Set( - options.allowedClasses || [ - 'shadow-none', - 'shadow-minimal', - 'shadow-tinted', - 'shadow-thin', - 'shadow-middle', - 'shadow-strong', - 'shadow-panel-focused', - 'shadow-modal-small', - 'shadow-bottom-border', - 'shadow-bottom-border-thin', - ] - ) - - const allowInlineNone = options.allowInlineNone !== false - const allowedSummary = Array.from(allowedClasses).sort().join(', ') - - function reportDisallowedClass(node, className) { - context.report({ - node, - messageId: 'disallowedClass', - data: { - className, - allowed: allowedSummary, - }, - }) - } - - function checkStringForShadowTokens(node, text) { - if (!text || !text.includes('shadow-')) return - - const regex = /shadow-[^\s'"`]+/g - let match - - while ((match = regex.exec(text)) !== null) { - const token = match[0] - const start = match.index - const prev = start > 0 ? text[start - 1] : '' - - // Ignore CSS custom property names like --shadow-color, --shadow-minimal-flat - if (prev === '-') continue - - if (token.startsWith('shadow-[') && !allowedClasses.has(token)) { - reportDisallowedClass(node, token) - continue - } - if (!allowedClasses.has(token)) { - reportDisallowedClass(node, token) - } - } - } - - function isBoxShadowPropertyKey(node) { - if (!node) return false - if (node.type === 'Identifier') return node.name === 'boxShadow' - if (node.type === 'Literal') return node.value === 'boxShadow' - return false - } - - function isStyleBoxShadowAssignment(node) { - return ( - node && - node.type === 'MemberExpression' && - !node.computed && - node.property && - node.property.type === 'Identifier' && - node.property.name === 'boxShadow' && - node.object && - node.object.type === 'MemberExpression' && - !node.object.computed && - node.object.property && - node.object.property.type === 'Identifier' && - node.object.property.name === 'style' - ) - } - - function isNoneLiteral(node) { - return ( - node && - node.type === 'Literal' && - typeof node.value === 'string' && - node.value.trim().toLowerCase() === 'none' - ) - } - - return { - Literal(node) { - if (typeof node.value !== 'string') return - checkStringForShadowTokens(node, node.value) - }, - - TemplateLiteral(node) { - if (node.expressions.length > 0) return - const text = node.quasis.map((q) => q.value.cooked ?? '').join('') - checkStringForShadowTokens(node, text) - }, - - Property(node) { - if (!isBoxShadowPropertyKey(node.key)) return - if (allowInlineNone && isNoneLiteral(node.value)) return - context.report({ node: node.value, messageId: 'disallowedInline' }) - }, - - AssignmentExpression(node) { - if (!isStyleBoxShadowAssignment(node.left)) return - if (allowInlineNone && isNoneLiteral(node.right)) return - context.report({ node: node.right, messageId: 'disallowedInline' }) - }, - } - }, -} diff --git a/packages/desktop/apps/electron/eslint.config.mjs b/packages/desktop/apps/electron/eslint.config.mjs deleted file mode 100644 index c9a3819bff3..00000000000 --- a/packages/desktop/apps/electron/eslint.config.mjs +++ /dev/null @@ -1,186 +0,0 @@ -/** - * ESLint Configuration for Electron App - * - * Uses flat config format (ESLint 9+). - * Includes custom navigation rule to enforce navigate() usage. - */ - -import tsParser from '@typescript-eslint/parser' -import tsPlugin from '@typescript-eslint/eslint-plugin' -import reactPlugin from 'eslint-plugin-react' -import reactHooksPlugin from 'eslint-plugin-react-hooks' -import noDirectNavigationState from './eslint-rules/no-direct-navigation-state.cjs' -import noLocalStorage from './eslint-rules/no-localstorage.cjs' -import noDirectPlatformCheck from './eslint-rules/no-direct-platform-check.cjs' -import noHardcodedPathSeparator from './eslint-rules/no-hardcoded-path-separator.cjs' -import noDirectFileOpen from './eslint-rules/no-direct-file-open.cjs' -import noInlineSourceAuthCheck from './eslint-rules/no-inline-source-auth-check.cjs' -import noHardcodedZIndex from './eslint-rules/no-hardcoded-z-index.cjs' -import noNonstandardShadows from './eslint-rules/no-nonstandard-shadows.cjs' - -export default [ - // Ignore patterns - { - ignores: [ - 'dist/**', - 'node_modules/**', - 'release/**', - '*.cjs', - 'eslint-rules/**', - ], - }, - - // TypeScript/React files - { - files: ['src/**/*.{ts,tsx}'], - languageOptions: { - parser: tsParser, - parserOptions: { - ecmaVersion: 'latest', - sourceType: 'module', - ecmaFeatures: { - jsx: true, - }, - }, - }, - plugins: { - '@typescript-eslint': tsPlugin, - react: reactPlugin, - 'react-hooks': reactHooksPlugin, - // Custom plugin for Qwen Code rules - 'craft-agent': { - rules: { - 'no-direct-navigation-state': noDirectNavigationState, - 'no-localstorage': noLocalStorage, - }, - }, - // Custom plugin for platform detection rules - 'craft-platform': { - rules: { - 'no-direct-platform-check': noDirectPlatformCheck, - }, - }, - // Custom plugin for cross-platform path rules - 'craft-paths': { - rules: { - 'no-hardcoded-path-separator': noHardcodedPathSeparator, - }, - }, - // Custom plugin for link interceptor enforcement - 'craft-links': { - rules: { - 'no-direct-file-open': noDirectFileOpen, - }, - }, - // Custom plugin for source auth checks (shared with packages/shared) - 'craft-sources': { - rules: { - 'no-inline-source-auth-check': noInlineSourceAuthCheck, - }, - }, - // Custom style rules - 'craft-styles': { - rules: { - 'no-hardcoded-z-index': noHardcodedZIndex, - 'no-nonstandard-shadows': noNonstandardShadows, - }, - }, - }, - settings: { - react: { - version: 'detect', - }, - }, - rules: { - // React Hooks rules - 'react-hooks/rules-of-hooks': 'error', - 'react-hooks/exhaustive-deps': 'warn', - - // Custom Qwen Code rules - 'craft-agent/no-direct-navigation-state': 'error', - 'craft-agent/no-localstorage': 'warn', - - // Custom platform detection rule - 'craft-platform/no-direct-platform-check': 'error', - - // Custom cross-platform path rule - 'craft-paths/no-hardcoded-path-separator': 'warn', - - // Custom link interceptor rule — prevents bypassing in-app file preview - 'craft-links/no-direct-file-open': 'error', - - // Custom source auth check rule — use isSourceUsable() instead of inline checks - 'craft-sources/no-inline-source-auth-check': 'error', - - // Custom style rule — use z-index token scale instead of hardcoded literals - 'craft-styles/no-hardcoded-z-index': 'error', - - // Custom style rule — enforce approved shadow classes/tokens only - 'craft-styles/no-nonstandard-shadows': ['error', { - allowedClasses: [ - 'shadow-none', - 'shadow-xs', - 'shadow-minimal', - 'shadow-tinted', - 'shadow-thin', - 'shadow-middle', - 'shadow-strong', - 'shadow-panel-focused', - 'shadow-modal-small', - 'shadow-bottom-border', - 'shadow-bottom-border-thin', - ], - allowInlineNone: true, - }], - - // Enforce centralized action registry for keyboard shortcuts - 'no-restricted-imports': ['error', { - paths: [ - { - name: 'react-hotkeys-hook', - message: 'Use useAction from @/actions instead. See actions/index.ts' - } - ], - }], - }, - }, - - // Temporary exceptions for unresolved shadow migrations. - { - files: [ - 'src/renderer/components/ui/sortable-list.tsx', - 'src/main/browser-pane-manager.ts', - 'src/shared/browser-live-fx.ts', - 'src/renderer/components/KeyboardShortcutsDialog.tsx', - 'src/renderer/playground/**/*.{ts,tsx}', - ], - rules: { - 'craft-styles/no-nonstandard-shadows': 'off', - }, - }, - - // Enforce backend abstraction boundary in Electron main process. - { - files: ['src/main/**/*.{ts,tsx}'], - rules: { - 'no-restricted-imports': ['error', { - paths: [ - { name: '@craft-agent/shared/agent/qwen-agent', message: 'Use backend factory APIs from @craft-agent/shared/agent/backend instead.' }, - ], - }], - }, - }, - - // Keep main model fetchers provider-agnostic (delegate to shared backend APIs only). - { - files: ['src/main/model-fetchers/**/*.{ts,tsx}'], - rules: { - 'no-restricted-syntax': ['error', - { - selector: "CallExpression[callee.name='fetch']", - message: 'Do not call provider APIs directly in Electron model fetchers. Delegate to fetchBackendModels() from @craft-agent/shared/agent/backend.', - }, - ], - }, - }, -] diff --git a/packages/desktop/apps/electron/package.json b/packages/desktop/apps/electron/package.json deleted file mode 100644 index e15aeced017..00000000000 --- a/packages/desktop/apps/electron/package.json +++ /dev/null @@ -1,82 +0,0 @@ -{ - "name": "@craft-agent/electron", - "version": "0.0.5", - "description": "Electron desktop app for Qwen Code", - "main": "dist/main.cjs", - "private": true, - "author": { - "name": "Craft Docs Ltd.", - "email": "support@craft.do" - }, - "homepage": "https://agents.craft.do", - "license": "Apache-2.0", - "type": "module", - "engines": { - "node": ">=18.0.0" - }, - "scripts": { - "build:main": "source ../../.env 2>/dev/null || true; esbuild src/main/index.ts --bundle --platform=node --format=cjs --outfile=dist/main.cjs --external:electron --alias:node-fetch=./src/main/shims/node-fetch.cjs --alias:abort-controller=./src/main/shims/abort-controller.cjs --define:process.env.GOOGLE_OAUTH_CLIENT_ID=\\\"${GOOGLE_OAUTH_CLIENT_ID:-}\\\" --define:process.env.GOOGLE_OAUTH_CLIENT_SECRET=\\\"${GOOGLE_OAUTH_CLIENT_SECRET:-}\\\" --define:process.env.SLACK_OAUTH_CLIENT_ID=\\\"${SLACK_OAUTH_CLIENT_ID:-}\\\" --define:process.env.SLACK_OAUTH_CLIENT_SECRET=\\\"${SLACK_OAUTH_CLIENT_SECRET:-}\\\" --define:process.env.MICROSOFT_OAUTH_CLIENT_ID=\\\"${MICROSOFT_OAUTH_CLIENT_ID:-}\\\"", - "build:main:win": "esbuild src/main/index.ts --bundle --platform=node --format=cjs --outfile=dist/main.cjs --external:electron --alias:node-fetch=./src/main/shims/node-fetch.cjs --alias:abort-controller=./src/main/shims/abort-controller.cjs", - "build:preload": "esbuild src/preload/bootstrap.ts --bundle --platform=node --format=cjs --outfile=dist/bootstrap-preload.cjs --external:electron", - "build:preload-toolbar": "esbuild src/preload/browser-toolbar.ts --bundle --platform=node --format=cjs --outfile=dist/browser-toolbar-preload.cjs --external:electron", - "build:renderer": "vite build", - "build:copy": "bun scripts/copy-assets.ts", - "build:validate": "bun scripts/validate-assets.ts", - "build": "bun run lint && bun run build:main && bun run build:preload && bun run build:preload-toolbar && bun run build:renderer && bun run build:copy && bun run build:validate", - "build:win": "bun run build:main:win && bun run build:preload && bun run build:preload-toolbar && bun run build:renderer && bun run build:copy && bun run build:validate", - "start": "bun run build && electron .", - "start:win": "bun run build:win && electron .", - "dev": "vite dev", - "typecheck": "tsc --noEmit", - "dist:mac": "bash scripts/build-dmg.sh arm64", - "dist:mac:x64": "bash scripts/build-dmg.sh x64", - "dist:win": "powershell -ExecutionPolicy Bypass -File scripts/build-win.ps1", - "lint": "eslint src/", - "lint:fix": "eslint src/ --fix" - }, - "dependencies": { - "@craft-agent/core": "workspace:*", - "@craft-agent/messaging-gateway": "workspace:*", - "@craft-agent/server-core": "workspace:*", - "@craft-agent/shared": "workspace:*", - "@craft-agent/ui": "workspace:*", - "@dnd-kit/core": "^6.3.1", - "@dnd-kit/sortable": "^10.0.0", - "@dnd-kit/utilities": "^3.2.2", - "@paper-design/shaders-react": "^0.0.69", - "@pierre/diffs": "^1.0.4", - "@radix-ui/react-context-menu": "^2.2.16", - "@radix-ui/react-dialog": "^1.1.15", - "@radix-ui/react-dropdown-menu": "^2.1.16", - "@radix-ui/react-label": "^2.1.8", - "@radix-ui/react-popover": "^1.1.15", - "@radix-ui/react-switch": "^1.2.6", - "@tanstack/react-table": "^8.21.3", - "chrono-node": "^2.9.0", - "cmdk": "^1.1.1", - "electron-log": "^5.4.3", - "electron-updater": "^6.8.0", - "i18next-browser-languagedetector": "^8.2.1", - "jotai-family": "^1.0.1", - "motion": "^12.23.26", - "next-themes": "^0.4.6", - "qrcode.react": "^4.2.0", - "react": "^18.3.1", - "react-day-picker": "^9.13.0", - "react-dom": "^18.3.1", - "react-i18next": "^17.0.2", - "react-pdf": "^10.3.0", - "react-simple-code-editor": "^0.14.1", - "remark": "^15.0.1", - "sharp": "0.34.5", - "sonner": "^2.0.7", - "strip-markdown": "^6.0.0", - "undici": "^6.22.0", - "unist-util-visit": "^5.0.0", - "vaul": "^1.1.2", - "ws": "^8.19.0" - }, - "devDependencies": { - "@types/ws": "^8.18.1" - } -} diff --git a/packages/desktop/apps/electron/resources/AGENTS.md b/packages/desktop/apps/electron/resources/AGENTS.md deleted file mode 100644 index 9ca2ebb2651..00000000000 --- a/packages/desktop/apps/electron/resources/AGENTS.md +++ /dev/null @@ -1,50 +0,0 @@ -# Bundled Resources - -This folder contains assets that are bundled with the Electron app and synced to the user's `~/.craft-agent/` directory on every launch. - -## How It Works - -1. **Build time**: `scripts/copy-assets.ts` copies this folder to `dist/resources/` -2. **Package time**: electron-builder includes `dist/resources/` in the app bundle -3. **Runtime**: `getBundledAssetsDir()` resolves paths to these bundled assets -4. **Launch**: Each asset type syncs to the user's home directory - -## Asset Types - -| Folder/File | Synced To | Sync Behavior | -|-------------|-----------|---------------| -| `docs/` | `~/.craft-agent/docs/` | Always overwrite on launch | -| `themes/` | `~/.craft-agent/themes/` | Always overwrite on launch | -| `permissions/` | `~/.craft-agent/permissions/` | Always overwrite on launch | -| `tool-icons/` | `~/.craft-agent/tool-icons/` | Always overwrite on launch | -| `config-defaults.json` | `~/.craft-agent/config-defaults.json` | Always overwrite on launch | - -## Why Sync on Every Launch? - -- Ensures users always have the latest defaults/docs when the app updates -- Consistent behavior between debug and release builds -- No stale configuration causing confusion - -## Other Files (Not Synced) - -These files are used by electron-builder or the app directly, not synced to user home: - -| File | Purpose | -|------|---------| -| `icon.*` | App icons (icns, ico, png, svg) | -| `Assets.car` | Optional macOS compiled asset catalog | -| `dmg-background.*` | DMG installer background | -| `craft-logos/` | Branding assets | -| `source.png` | Default source icon | -| `generate-icons.sh` | Icon generation script | -| `bridge-mcp-server/` | Bundled MCP server for API source bridge | -| `session-mcp-server/` | Bundled MCP server for session tools | - -## Single Source of Truth - -The files in this folder are the **source of truth** for bundled defaults: -- Edit `config-defaults.json` here to change default settings -- Edit files in `docs/` to update documentation -- Edit files in `themes/` to update bundled themes - -There is no TypeScript fallback - if the bundled JSON file is missing, the app will fail with a clear error. diff --git a/packages/desktop/apps/electron/resources/bin/craft-agent b/packages/desktop/apps/electron/resources/bin/craft-agent deleted file mode 100755 index 245cf8144c0..00000000000 --- a/packages/desktop/apps/electron/resources/bin/craft-agent +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh -ENTRY="${CRAFT_COMMANDS_ENTRY:-$CRAFT_CLI_ENTRY}" -export CRAFT_CLI_JSON_ONLY="${CRAFT_CLI_JSON_ONLY:-1}" -exec "${CRAFT_BUN:-bun}" run "$ENTRY" "$@" diff --git a/packages/desktop/apps/electron/resources/bin/craft-agent.cmd b/packages/desktop/apps/electron/resources/bin/craft-agent.cmd deleted file mode 100644 index 16ee6853ce7..00000000000 --- a/packages/desktop/apps/electron/resources/bin/craft-agent.cmd +++ /dev/null @@ -1,7 +0,0 @@ -@echo off -set "CRAFT_BUN_BIN=%CRAFT_BUN%" -if "%CRAFT_BUN_BIN%"=="" set "CRAFT_BUN_BIN=bun" -set "CRAFT_COMMANDS_BIN=%CRAFT_COMMANDS_ENTRY%" -if "%CRAFT_COMMANDS_BIN%"=="" set "CRAFT_COMMANDS_BIN=%CRAFT_CLI_ENTRY%" -if "%CRAFT_CLI_JSON_ONLY%"=="" set "CRAFT_CLI_JSON_ONLY=1" -"%CRAFT_BUN_BIN%" run "%CRAFT_COMMANDS_BIN%" %* diff --git a/packages/desktop/apps/electron/resources/bin/doc-diff b/packages/desktop/apps/electron/resources/bin/doc-diff deleted file mode 100755 index b1859fd0d07..00000000000 --- a/packages/desktop/apps/electron/resources/bin/doc-diff +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/sh -exec "$CRAFT_UV" run --python 3.12 "$CRAFT_SCRIPTS/doc_diff.py" "$@" diff --git a/packages/desktop/apps/electron/resources/bin/doc-diff.cmd b/packages/desktop/apps/electron/resources/bin/doc-diff.cmd deleted file mode 100644 index be7527f5ad3..00000000000 --- a/packages/desktop/apps/electron/resources/bin/doc-diff.cmd +++ /dev/null @@ -1,2 +0,0 @@ -@echo off -"%CRAFT_UV%" run --python 3.12 "%CRAFT_SCRIPTS%\doc_diff.py" %* diff --git a/packages/desktop/apps/electron/resources/bin/docx-tool b/packages/desktop/apps/electron/resources/bin/docx-tool deleted file mode 100755 index 24f66291e92..00000000000 --- a/packages/desktop/apps/electron/resources/bin/docx-tool +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/sh -exec "$CRAFT_UV" run --python 3.12 "$CRAFT_SCRIPTS/docx_tool.py" "$@" diff --git a/packages/desktop/apps/electron/resources/bin/docx-tool.cmd b/packages/desktop/apps/electron/resources/bin/docx-tool.cmd deleted file mode 100644 index 2e52d00384f..00000000000 --- a/packages/desktop/apps/electron/resources/bin/docx-tool.cmd +++ /dev/null @@ -1,2 +0,0 @@ -@echo off -"%CRAFT_UV%" run --python 3.12 "%CRAFT_SCRIPTS%\docx_tool.py" %* diff --git a/packages/desktop/apps/electron/resources/bin/ical-tool b/packages/desktop/apps/electron/resources/bin/ical-tool deleted file mode 100755 index c654c26c401..00000000000 --- a/packages/desktop/apps/electron/resources/bin/ical-tool +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/sh -exec "$CRAFT_UV" run --python 3.12 "$CRAFT_SCRIPTS/ical_tool.py" "$@" diff --git a/packages/desktop/apps/electron/resources/bin/ical-tool.cmd b/packages/desktop/apps/electron/resources/bin/ical-tool.cmd deleted file mode 100644 index 65c9a8f86cc..00000000000 --- a/packages/desktop/apps/electron/resources/bin/ical-tool.cmd +++ /dev/null @@ -1,2 +0,0 @@ -@echo off -"%CRAFT_UV%" run --python 3.12 "%CRAFT_SCRIPTS%\ical_tool.py" %* diff --git a/packages/desktop/apps/electron/resources/bin/img-tool b/packages/desktop/apps/electron/resources/bin/img-tool deleted file mode 100755 index 25e78ca5c74..00000000000 --- a/packages/desktop/apps/electron/resources/bin/img-tool +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/sh -exec "$CRAFT_UV" run --python 3.12 "$CRAFT_SCRIPTS/img_tool.py" "$@" diff --git a/packages/desktop/apps/electron/resources/bin/img-tool.cmd b/packages/desktop/apps/electron/resources/bin/img-tool.cmd deleted file mode 100644 index 07043287165..00000000000 --- a/packages/desktop/apps/electron/resources/bin/img-tool.cmd +++ /dev/null @@ -1,2 +0,0 @@ -@echo off -"%CRAFT_UV%" run --python 3.12 "%CRAFT_SCRIPTS%\img_tool.py" %* diff --git a/packages/desktop/apps/electron/resources/bin/markitdown b/packages/desktop/apps/electron/resources/bin/markitdown deleted file mode 100755 index 77921af795d..00000000000 --- a/packages/desktop/apps/electron/resources/bin/markitdown +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/sh -exec "$CRAFT_UV" run --python 3.12 "$CRAFT_SCRIPTS/markitdown_cli.py" "$@" diff --git a/packages/desktop/apps/electron/resources/bin/markitdown.cmd b/packages/desktop/apps/electron/resources/bin/markitdown.cmd deleted file mode 100644 index 1ff4b1ac79d..00000000000 --- a/packages/desktop/apps/electron/resources/bin/markitdown.cmd +++ /dev/null @@ -1,2 +0,0 @@ -@echo off -"%CRAFT_UV%" run --python 3.12 "%CRAFT_SCRIPTS%\markitdown_cli.py" %* diff --git a/packages/desktop/apps/electron/resources/bin/pdf-tool b/packages/desktop/apps/electron/resources/bin/pdf-tool deleted file mode 100755 index e4c9612a465..00000000000 --- a/packages/desktop/apps/electron/resources/bin/pdf-tool +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/sh -exec "$CRAFT_UV" run --python 3.12 "$CRAFT_SCRIPTS/pdf_tool.py" "$@" diff --git a/packages/desktop/apps/electron/resources/bin/pdf-tool.cmd b/packages/desktop/apps/electron/resources/bin/pdf-tool.cmd deleted file mode 100644 index 709fb7cf537..00000000000 --- a/packages/desktop/apps/electron/resources/bin/pdf-tool.cmd +++ /dev/null @@ -1,2 +0,0 @@ -@echo off -"%CRAFT_UV%" run --python 3.12 "%CRAFT_SCRIPTS%\pdf_tool.py" %* diff --git a/packages/desktop/apps/electron/resources/bin/pptx-tool b/packages/desktop/apps/electron/resources/bin/pptx-tool deleted file mode 100755 index 5abbf94c2b3..00000000000 --- a/packages/desktop/apps/electron/resources/bin/pptx-tool +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/sh -exec "$CRAFT_UV" run --python 3.12 "$CRAFT_SCRIPTS/pptx_tool.py" "$@" diff --git a/packages/desktop/apps/electron/resources/bin/pptx-tool.cmd b/packages/desktop/apps/electron/resources/bin/pptx-tool.cmd deleted file mode 100644 index f2321142370..00000000000 --- a/packages/desktop/apps/electron/resources/bin/pptx-tool.cmd +++ /dev/null @@ -1,2 +0,0 @@ -@echo off -"%CRAFT_UV%" run --python 3.12 "%CRAFT_SCRIPTS%\pptx_tool.py" %* diff --git a/packages/desktop/apps/electron/resources/bin/xlsx-tool b/packages/desktop/apps/electron/resources/bin/xlsx-tool deleted file mode 100755 index d71beb50c54..00000000000 --- a/packages/desktop/apps/electron/resources/bin/xlsx-tool +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/sh -exec "$CRAFT_UV" run --python 3.12 "$CRAFT_SCRIPTS/xlsx_tool.py" "$@" diff --git a/packages/desktop/apps/electron/resources/bin/xlsx-tool.cmd b/packages/desktop/apps/electron/resources/bin/xlsx-tool.cmd deleted file mode 100644 index be7347ef47d..00000000000 --- a/packages/desktop/apps/electron/resources/bin/xlsx-tool.cmd +++ /dev/null @@ -1,2 +0,0 @@ -@echo off -"%CRAFT_UV%" run --python 3.12 "%CRAFT_SCRIPTS%\xlsx_tool.py" %* diff --git a/packages/desktop/apps/electron/resources/brands/openwork/dock.png b/packages/desktop/apps/electron/resources/brands/openwork/dock.png deleted file mode 100644 index 787bb52b0c1d96edb6ca6d8bf6c507cbeafbfb18..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24740 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 diff --git a/packages/desktop/apps/electron/resources/brands/openwork/icon.icns b/packages/desktop/apps/electron/resources/brands/openwork/icon.icns deleted file mode 100644 index 9c9808c7811b22663fe34abf7d5d388ae05f6bbc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 145824 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