fix: add loader await timeout diagnostics and refine model unavailable copy - #485
Merged
yaojin3616 merged 3 commits intoSep 20, 2026
Merged
Conversation
- Add timeout handling to cordis-plugin-loader EntryTree.prototype.await (configurable via DSH_LOADER_TIMEOUT_MS, default 100s) with 5s progress heartbeat - Accurately identify stuck plugins on timeout and emit structured dshPluginFailure errors - Add unit test coverage in cordis-plugin-loader-patch.test.ts
- Refine warning dialog message and detail copy when default model is unconfigured or unavailable - Update unit tests in test/repair-agent.test.ts
yaojin3616
deleted the
fix/repair-agent-model-check-and-loader-timeout
branch
September 20, 2026 13:44
yaojin3616
added a commit
that referenced
this pull request
Sep 21, 2026
* feat(release): configure initial 5% rollout on crash service (#401) - Add configure-rollout.mjs script to manage platform rollout rules on crash service - Integrate initial 5% rollout step into release workflow - Add unit tests covering creation, update, and race condition conflict recovery * fix(llm): send Harness session headers through pi-ai (#329) Co-authored-by: yaojin3616 <yaoj.alex@gmail.com> * fix(llm): recover missing terminal content (#400) Co-authored-by: Debug Agent <debug-agent@noreply.local> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: import the local web Harness home on first launch (#397) * feat: import the local web Harness home on first launch Copy ~/.dsh into an unused desktop home so same-machine web users are not treated as a fresh install. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(market-baseline): treat pnpm isolated-store symlinks as valid installs In dev mode (dsh-desktop-dev), profiles have no nodeLinker:hoisted config so pnpm installs dshmarket as a symlink into .pnpm/…. The previous isSymbolicLink() check treated any symlink as a stray generation link, deleted it, then called pnpm to reinstall — which recreated the same symlink. The post-install guard rejected it and the app entered Safe Mode on every dev launch. Fix: read the symlink target and only treat paths containing `.generations` as generation links. pnpm isolated-store symlinks (.pnpm/…) are left alone in demoteMarketGeneration and ensureMarketBaseline, and are accepted by upgradeMarketInSharedTree's post-install verification. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: fairy <3054158379@qq.com> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: yaojin3616 <yaoj.alex@gmail.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(test): correct patch indentation in provider-error-patch.test.ts (#433) * fix(test): correct patch indentation in provider-error-patch.test.ts * fix(types): add type declarations for configure-rollout.mjs and resolve typecheck errors * fix(diagnostics): 减少误报故障反馈、优化插件自愈免报与修复 Windows 启动缺陷 (#418) * fix(diagnostics): reduce false-positive crash reports and fix Windows startup issues - Suppress false-positive unclean-exit reports when previous session log was healthy - Mark clean exit synchronously on before-quit and handle Windows session-end - Defer plugin failure reports during startup and discard them when recovery succeeds - Fix Windows EPERM on junction removal in ensureSymlink (@deepseek-ai/dsh-app-boot) - Use pathToFileURL in loadDesktopResource to avoid ERR_FAILED (-2) on non-C drives - Enrich render-process-gone logs in harness.log with URL and GPU feature status - Enable V8 compile cache (NODE_COMPILE_CACHE) for faster Node.js restarts * fix(windows): declare asInvoker, enable long paths, and defer profile inspection * feat(runtime): prefix harness.log lines with ISO date timestamp * feat(release): sign inner Windows binaries and repackage NSIS installer with SafeNet UKey * fix(release): harden Windows inner signing with version sync and fallback * fix(runtime): use platform-specific path join for NODE_COMPILE_CACHE * fix(types): add type declarations for configure-rollout.mjs and resolve typecheck errors * test: serialize suites on Windows CI and set global timeout to prevent hangs * fix(release): verify PE header for .node files to skip non-Windows prebuilds in Jsign * perf(windows): exclude build-tool deps and add Defender exclusions on install - Exclude ~2,000 build-tool-only transitive files from the Windows package: caniuse-lite (838 JS files, browserslist/babel only) lodash (1,048 JS files, only used by @malept/flatpak-bundler linux tool) browserslist / electron-to-chromium / update-browserslist-db (15 files) @malept/* (flatpak bundler, Linux-only build tool) Excluded packages are verified to have no runtime dependency chain: - @mixmark-io/domino kept (dsh-tool-web → turndown → domino) - jsdom kept (dsh-ppt runtime dep) - dsh-ppt's nested typescript kept (only top-level typescript is absent) - Add Windows Defender exclusions in NSIS installer (customInstall macro): Add-MpPreference for $INSTDIR and $APPDATA\dsh-desktop via PowerShell. Uses -ErrorAction SilentlyContinue so non-admin installs fail silently. Without this exclusion Defender scans 44k files on first launch (observed: 121-123 s until Harness ready on a fresh install). With the exclusion, first launch should complete in under 20 s. * fix(release): align package.json version with package-lock.json (0.1.1) * fix(test): correct patch indentation in provider-error-patch.test.ts * fix(windows): optimize titlebar layout, integrate more actions into app menu, and fix sidebar overlap (#409) * perf(windows): cut 20k non-runtime files, elevate defender exclusion, and relax startup timeout (#435) * fix(diagnostics): suppress unclean-exit after upgrade or overwrite in… (#437) * fix(diagnostics): suppress unclean-exit after upgrade or overwrite install An in-app update can be force-killed before will-quit, and a leftover session marker from another version is a reinstall, not a crash. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(diagnostics): suppress startup failure report when frontend identifies incompatible plugins * fix(build): preserve runtime skill markdown files in build output and update ppt artifacts --------- Co-authored-by: fairy <3054158379@qq.com> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: yaojin3616 <yaoj.alex@gmail.com> * fix(windows): clear session marker on overwrite install (#440) NSIS force-kills the running app, so will-quit never clears session.json and a same-version overwrite looks like an unclean exit. Co-authored-by: fairy <3054158379@qq.com> Co-authored-by: Cursor <cursoragent@cursor.com> * Fix/suppress upgrade unclean exit (#441) * fix(diagnostics): suppress unclean-exit after upgrade or overwrite install An in-app update can be force-killed before will-quit, and a leftover session marker from another version is a reinstall, not a crash. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(diagnostics): suppress startup failure report when frontend identifies incompatible plugins * fix(build): preserve runtime skill markdown files in build output and update ppt artifacts * update * update * update * update --------- Co-authored-by: fairy <3054158379@qq.com> Co-authored-by: Cursor <cursoragent@cursor.com> * docs: stop tracking plugin-management and keep it local only (#443) Co-authored-by: fairy <3054158379@qq.com> Co-authored-by: Cursor <cursoragent@cursor.com> * docs: 建立仓库级 AGENTS.md 开发约束与治理规范 (#428) * docs: establish repository-wide AGENTS development standards and governance contracts - Add root AGENTS.md defining project boundaries, TypeScript code style, dependency/data constraints, and verification runbooks. - Add directory-scoped AGENTS.md rules for src/main, src/preload, packages, patches, and build. - Add supporting design specifications: - docs/code-standards.md (design rationale and phased tool enforcement) - docs/patch-plugin-contract.md (patch vs plugin selection and slot compatibility) - docs/source-build-contract.md (single authoritative source and build artifact boundaries) * docs: 补齐 AGENTS 前端硬约束与企业/编译型包覆盖 把主题 token、locale、preload 与 slot 边界写成可执行规则,并标出 enterprise 存量与 ppt-runtime 的维护入口。 --------- Co-authored-by: fairy <3054158379@qq.com> * fix(security): allow notifications permission from the harness window (#461) The allowlist only covered clipboard-sanitized-write, so notification permission requests from the harness page could not be granted. The loopback main-frame checks are unchanged, so the trust boundary is the same. * fix(release): honor platform target and add signed-only dispatch mode (#468) A filled prerelease tag previously forced a Windows build even when macos was selected. Dispatch now separates development, signed, and prerelease so production-identity packages can be signed without publishing. Co-authored-by: fairy <3054158379@qq.com> Co-authored-by: Cursor <cursoragent@cursor.com> * fix(settings): keep long navigation menus scrollable (#456) * fix(settings): keep long navigation menus scrollable * fix(settings): add navigation overflow patch * feat(safe-mode): add floating repair agent widget in recovery and safe mode (#431) * feat(safe-mode): add floating repair agent widget in recovery and safe mode - Add floating repair agent FAB widget on plugin recovery and safe mode overlay - Provide pre-configured quick action prompts with error diagnostics and logs - Support switching models and uploading/pasting screenshots for vision models - Connect with isolated safe mode Harness session and stream responses via IPC * feat(safe-mode): temporarily disable floating repair agent chat widget * feat(safe-mode): enable repair agent with 0.9.0 incident knowledge and offline diagnostics * feat(release): expand release notes generator with performance section and full changelog * feat(window): persist window bounds, maximized state, and zoom level * fix(repair-agent): handle unconfigured models with api key drawer and prevent silent stream failures * fix(dev): strip inherited ELECTRON_RUN_AS_NODE to prevent electron launch crash * feat(dev): forward runtime logs to console in development and log single-instance conflicts * fix(repair-agent): inherit working default model route and add dual-track history polling * refactor(repair-agent): rely purely on websocket stream without redundant history polling * feat(repair-agent): replace floating widget with seamless native Harness UI diagnostic cards * fix(windows): stop GPU fallback ladder from degrading on TDR device-loss recoveries, recover the menu view from a lost renderer gcp3 crash data for v0.9.0 showed 23/30 Windows gpu-crash reports at exitCode=34 — Chromium's own exit code when it detects a lost D3D11 device (typically a driver TDR reset) and exits the GPU process on purpose so it can restart it. The fallback ladder was counting this self-recovery as evidence the sandbox is broken and would degrade hardware acceleration after 3 of these on an otherwise healthy machine. isGpuLossFatal now takes the exit code and treats exitCode 34 as non-fatal, only logging a breadcrumb. Separately, ~6 renderer-crash reports were on windows-menu.html: that view runs in its own WebContentsView outside installMainWindowRendererRecovery, which only hooks the main window's webContents, so a lost renderer there left a dead menu until the whole app restarted. It now reloads itself on render-process-gone. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(session): skip unreadable session logs instead of failing the whole listing One corrupt JSONL/zstd session log made sessionPersistence.list() throw, which failed dsh-workspace and stopped the Harness entirely (0.9.0 startup-failure reports: "corrupt Zstandard session log", corrupt header id). The log is now skipped with a warning naming its path and left on disk untouched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(startup): stop reporting attributed plugin failures, clear stale writer locks, fix repair diagnosis - Harness startup failures that plugin recovery attributes to a user plugin are handed to the user and their pending crash report is discarded; only the frontend recovery path did this before, so they were still uploaded. - Remove dsh-atomic-write locks in DSH_HOME and DSH_HOME/profiles whose owner pid is gone before launching; a killed Harness left .credentials.yaml.lock behind and the next launch timed out on it. - Repair Agent diagnosis uses loader provenance and the recovery-resolved plugins, and only reports a startup timeout when the runtime says so, since "waiting for Harness" + SIGTERM matched ordinary failure cleanup. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(safe-mode): ensure safe mode uses hoisted node-linker and hide diagnostics when plugins are identified * feat(runtime): add preset YAML validation step before launch * refactor(runtime): remove redundant preset check in favor of native Cordis loader logging * feat(repair-agent): unify diagnostic cards into a single entry with verification and reporting instructions * fix(repair-agent): restore the 3 fine-grained diagnostic cards in safe mode * fix(safe-mode): unify safe mode diagnostic cards into a single AI agent entry * fix(web-import): never replace a desktop home that already holds plugins or credentials A 0.9.0-rc1 home with plugins or .credentials.yaml but no settings.yaml or sessions was treated as unused, so the web import deleted it wholesale. It now counts as used. An unused home is also moved aside before deletion so delete-pending entries on Windows cannot block links created at the same path. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(recovery): don't remove plugins a deferred migration has not installed yet When the generation migration failed (e.g. pnpm EPERM right after install), imported plugins stayed manifest-only, failed to prepare, and plugin recovery removed them as incompatible. Deferred migrations now report the pending plugins and recovery excludes them. Transient EPERM/EBUSY/EACCES failures no longer freeze the same profile for six hours, so the next launch retries. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(windows): stop the titlebar drag region from swallowing clicks and churning The drag region now ignores pointer events, and only semantic dialog markers hide it: class-name guesses like [class*="modal"] matched permanent elements and hid it for good. Visibility checks run at most once a frame, since streaming output mutates the DOM continuously. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(diagnostics): send crashes without an event id right away Only a plugin-attributed startup failure should wait for recovery to decide whether to discard it. A crash with no event id compared equal to an unset pending id and was held back too. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(market): pin the exact version a declared dshmarket range names The installer pins and verifies an exact version, so a declared range such as ^1.47.0 is reduced to the version it names instead of being passed through. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore: ignore the local pnpm content store Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(safe-mode): resolve Safe Mode plugins from the installation, not the module fallback Safe Mode loads only installation-owned bundles, yet it still went through the shared $DSH_HOME/profiles/node_modules fallback, whose junctions Windows can refuse to recreate (EPERM) for minutes — the top startup failure of v0.9.0. The desktop now sets DSH_DESKTOP_HOST_RESOLVED for the Safe Mode profile only; the patched Harness then skips healing the fallback and resolves bare plugin names, including entries created at runtime through ctx.loader.create, from its own installation. A flag leaked from the parent environment never reaches a normal profile. This replaces the hoisted node-linker workaround for Safe Mode. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(runtime): mirror Harness logs to the console only in development A packaged app's stdout may be a closed pipe; writing every Harness log line to it there buys nothing and can fail. harness.log still receives every line. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(recovery): still blame a migration-pending plugin whose legacy copy is installed f475ce5 exempted every plugin with a pending deferred migration from recovery. Only a plugin the migration left manifest-only is not broken; one whose legacy copy is installed still loads and can be the real culprit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * feat(logging): record Harness runtime warnings and errors in harness.log Cordis's ctx.logger keeps messages only in an in-memory ring, and its default level filters out warnings; nothing in the shipped composition exported them. Session activation failures (a preset that cannot be found or mounted) never reach the logger at all — they are only pushed to the client. So errors after startup left no trace for a person or the Repair Agent once the process was gone. A new desktop plugin, dsh-desktop-log-bridge, is composed first into both the normal and the Safe Mode profile. It writes warn and error messages, the errors already in the ring, and api-session/error events to stderr, which the desktop records into harness.log. It rate-limits floods and writes one ready line per launch, so a log without it is known to predate the bridge. Every bridged line carries a [harness-log] prefix, and latestHarnessAttemptLogs drops such lines: runtime warnings never become recovery's failure cause or the plugin it blames. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * feat(safe-mode): disable plugins instead of removing them Safe Mode now switches a plugin off the way the plugin market's own toggle does, so it can come back without a reinstall: `disabled: true` rows for the loader entries the package inserts in the user patch layer, which the loader re-applies on every boot, plus the market's .dsh-market/state.json list, which is also the only switch for client-only packages. Nothing is deleted. Disabled plugins show as disabled with a Re-enable action instead of a selection. Compatibility issues that call for disabling a plugin use the same path; the old approach of dropping it from dsh.profile.bundles was silently undone, because the generation projection rewrites that list on every launch. Compatibility inspection skips disabled plugins, since they never load. A disable-carrier (a bundle whose patch disables another plugin) cannot be switched off on its own without stranding the plugin it replaces, so it keeps the backed-up removal. Startup recovery keeps removal too: a package that is itself broken fails before the patch layer's disable applies. When recovery removes a plugin it also clears its market disable entry, as the market's own uninstall does. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * feat(repair-agent): start a fresh, well-briefed repair session from one card Recovery and Safe Mode now offer a single "start the repair agent" card. Each click opens a new session in the Safe Mode Harness and makes it the session the Harness UI shows: the UI restores its current session from desktop storage when the page loads, so the Recovery path opens the session before its page loads and the Safe Mode path reloads the already open page. The floating repair widget is gone. The session's workspace is the Harness home (profiles, plugins, patch layer) instead of the empty launch root, and the first turn of each session carries the diagnosis. The system prompt is rebuilt around real incidents: - a directory map that separates the normal profile it repairs from the Safe Mode profile it runs in; - the path to harness.log with how to find the failed launch in it, plus a short excerpt, instead of pasting logs; Safe Mode's own logs are no longer used when no failed launch was captured; - one bilingual playbook source, so the Chinese and English prompts can no longer drift: plugin load failures, broken packages (which disabling cannot fix), the Windows module-fallback EPERM (no Developer Mode advice), a corrupt patch layer, startup timeouts, the code -> ptc preset rename, and schema errors in copied user presets; - actions named after the real UI, and a confirm-and-back-up rule before any file change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(market): stop shipping dshmarket so market upgrades take effect Harness resolves every profile bundle from its own installation before the profile (`resolveBundleDir` in dsh-app-boot). Since #352 pinned dshmarket 1.45.1 as a runtime dependency, the packaged copy won that lookup: upgrading the market wrote 1.48.0 into the profile, the market and the baseline check both read 1.48.0 back, and Harness kept loading 1.45.1 after every restart. Nothing in the app imports dshmarket; the profile copy is installed from npm by the market installer and `ensureMarketBaseline`. It stays a devDependency for the tests that exercise its real profile reader, which keeps it out of the packaged app. `ensureMarketBaseline` now also notes when the copy Harness would load is not the profile's, so a shadowed market shows up in harness.log instead of as a version the UI reports but never runs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * feat(recovery): let startup recovery repair a plugin market that breaks startup The market is a core bundle, so startup-failure attribution never named it and the Recovery page, finding no culprit, could only offer Safe Mode — which does not load the market and has nothing to act on. A market release that stopped Harness from booting therefore failed every launch the same way. Until now the packaged dshmarket shadowed the profile copy and hid this; with it gone the profile copy really loads, so the loop is live. When the failed launch's log (or loader provenance) names dshmarket, the Recovery page shows it as its own row, apart from the third-party plugin list whose removal path refuses core bundles: - Upgrade to the newest release the market check finds compatible with this Harness, for a market too old for it; a release already tried here is not offered again. - Install the verified version (VERIFIED_MARKET_BASELINE). That moves down from a broken release, up from a stale one, or reinstalls a damaged copy. It is the primary action when the market is the only culprit. - Remove the market; installed community plugins are kept. Versions come from the main process's own check, never from the page. Each change stops Harness first, as the shared-tree installer requires, pins the exact version so the next baseline check does not reinstall the broken release, then relaunches and returns to recovery if startup still fails. The market removal behind the settings page is shared as `removeMarket`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(recovery): render the plugin market row like any other plugin row The market row added its own touches: a version after the name, a green button for restoring the verified version (which can be a downgrade), and a confirmation dialog plus its own wording for removal. Third-party rows have none of these, so the market read as a different kind of item. Show the name only, keep green for the upgrade alone with every other action in the neutral style, and remove it the way the page removes any plugin — "Remove this plugin", no extra confirmation. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(recovery): offer the plugin market only upgrade and removal Restoring the verified market version was an action no other plugin row has. Drop it, so the market row is a plugin row like the rest: upgrade to a compatible release when the market check finds one, and removal. When the market alone blocks startup, the page now uses the same buttons as a lone third-party plugin: "Upgrade plugin and restart" with "Uninstall plugin" beside it when an upgrade exists, otherwise "Remove this plugin and continue". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore: stop tracking the local pnpm content store 0d083d5 committed the whole `.pnpm-store/` (13,357 files, ~235 MB) along with its four intended changes, before 7a2cb73 added it to .gitignore. An ignore rule does not untrack files already committed, so they stayed in the branch and buried the PR's real diff. Remove them from the index only; the local store is untouched and stays ignored. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test: drop tests that only grep our own source code About 40 tests read src/main/index.ts, the preload, build/*.html, the NSIS script or our own packages' client.js as text and asserted that particular lines were present — `toContain("ipcMain.handle('safe-mode:action'")`, `indexOf(a) < indexOf(b)` and the like. They execute nothing, so they cannot catch a behavior regression, yet they fail on any rename or reformat, and on Windows, where the checkout has CRLF line endings, any expected string that spans a newline never matches. Two of them failed CI that way on PR #431. Removed: every test block whose assertions were such text matches, and the source-text lines inside otherwise behavioral tests (windows-titlebar allowlist, runtime Node mode, release dev channel, branding postinstall, harness-node-entry). The dev-channel check now reads electron-builder.dev.cjs as a config object instead of matching its text. Kept: tests that execute code, parse package.json/lockfile/YAML/JSON config, or check the installed third-party bundles in node_modules (patch and upstream-contract checks), since those inspect what actually ships. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(repair-agent): drop tests that only check the prompt's wording Two repair-prompt tests asserted fixed sentences of the generated prompt: exact Chinese phrases with hard-coded POSIX paths, and a fixed count of seven playbooks plus two error strings. Any edit to the copy broke them without saying anything about how the agent behaves, and the path one failed on Windows, where path.join correctly yields backslashes. Keep the test of real prompt logic: only the tail of the log is included, and none at all when no failed launch was captured. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(repair-agent): drop the remaining repair-prompt test It asserted which log lines and which sentence end up in the generated prompt text. Like the other prompt tests, that pins the copy rather than anything the agent does with it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(safe-mode): offer the repair agent as one line under the summary The repair agent sat in its own section below the actions — a heading, a divider and a card, about 140px. In a 1280x800 window that pushed the plugin list into a scrollbar, which the native Windows recovery UI check rejects: every plugin has to be visible at once. Its text was also Chinese-only. Show it as a single link under the summary instead, in both languages. It sends the same diagnostic request as the card did. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(recovery): offer the repair agent as a button beside Safe Mode The recovery page showed the repair agent in its own section under the technical details — a heading and a card, in Chinese only. Make it a secondary "Repair agent" button next to "Enter Safe Mode", in both languages. As before, it appears only when no plugin or market was identified; a named culprit has its own repair above. The native Windows UI check expected Safe Mode to be the only action when nothing is identified; it now expects the agent beside it, and nowhere else. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(runtime): keep a clean launch off stderr The packaged Windows smoke fails any launch that writes to stderr once a workspace and session exist. Two lines did on every launch, and neither reported a problem: - The log bridge's "recording from here on" marker. It is a notice, so it now goes to stdout; stderr carries only bridged warnings and errors. - `patch: entry dsh-market not found`. The desktop patch configured the market's entry (restart off, wait for desktopProfiles) unconditionally, but the market is optional, and a patch row whose entry is absent makes the loader warn. It was always there; the bridge only made it visible. The row now lives in dsh-desktop-market.patch.yml, passed as a second --patch only when the profile boots dshmarket, so a market profile behaves as before. Checked by booting a fresh web profile (no market overlay, no bridged warning) and one that boots dshmarket (overlay applied, no warning). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> * fix(market): remove dshmarket devDependency to avoid shadowing profile updates (#478) * fix(market): remove dshmarket devDependency to avoid shadowing profile updates Harness resolves every profile bundle from its installation anchor before the user profile (resolveBundleDir in dsh-app-boot). Previously, dshmarket was retained as a devDependency in package.json to support test/generation-boundary.test.js. However, electron-builder's files: ["node_modules/**/*"] glob unconditionally includes all modules in node_modules unless explicitly excluded, causing dshmarket to be bundled into the packaged application and to exist in dev checkouts. When the user upgrades dshmarket in their profile to 1.48.0, Harness continues loading the packaged copy instead of the profile's copy. This removes dshmarket from devDependencies and package-lock.json: - test/generation-boundary.test.js implements a local readInstalledVersion helper - test/market-baseline.test.ts verifies dshmarket is absent from both dependencies and devDependencies, and simulates installation shadowing with a mock directory * style(safe-mode): style repair agent session link with blue accent color * fix(safe-mode): make the repair agent's failures visible and its guidance true (#479) * fix(safe-mode): report a repair agent session that could not open Clicking "let the repair agent read the log" did nothing at all when the session failed to start: startRepairAgentPrompt returned a bare boolean, the caller skipped the reload, openHarness found the window already on the Harness origin and only raised it, and the reason reached the log alone. A user whose default agent preset was broken saw three clicks produce no visible change. The call now carries the reason. The Safe Mode manager stays open and shows it instead of closing on the way out, so the click can be retried, and plugin recovery surfaces the same reason through the manager it hands the window to. The notice keeps the first two lines of Harness's mount error; the rest is a stack that would push the plugin list off the page. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(plugin-disable): refuse a bundle whose package yields no loader row Disabling a broken plugin reported success and changed nothing that matters. pluginPatchRows reads the loader rows from the package's own patch; when that file is missing or unparsable the list comes back empty, so the patch layer was skipped, only the market state was written, and the result was ok. The bundle stayed in dsh.profile.bundles and the next launch composed — and failed on — the same package. A package listed as a bundle is one the loader prepares on every launch, so no readable row means the package on disk is broken, not that the plugin has no rows. That case is now refused as broken-package, and Safe Mode falls back to the backed-up removal it already uses for carriers. A client-only plugin is untouched: it is not a listed bundle, and the market state remains its only switch. The compatibility fixture listed a bundle whose manifest declared no dsh.bundle — a profile that cannot start — so it now carries the patch the loader would read. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * refactor(repair-agent): tighten the prompt and drop the unused repair module The prompt repeated absolute Windows paths in every playbook entry and walked through the Safe Mode profile the agent must not touch. Paths are given once in the directory section and referred to by name afterwards; the Safe Mode profile is one line. 5290 to 4403 characters, with more in it than before. What changed beyond wording: - The agent fixes what it can fix itself. Disabling a plugin is spelled out (patch layer plus market state, never dsh.profile.bundles), and only upgrading and reinstalling still go through the UI. - A broken package is triaged by layer first: node_modules/<package> is usually a link the next launch reprojects, so a merely missing link is a restart, not a removal. Only a missing manifest or patch in the real package is an interrupted install. - Windows EPERM tries to self-heal once by deleting the entry the error names — Safe Mode does not use that folder — before asking the user to hunt for the process holding it. - A clean log is a finding: say nothing is wrong, name the log sections checked, and ask what the user actually saw. The "a quiet log proves nothing" line that contradicted this is gone, and an empty offline diagnosis is no longer printed as if it were a result. - Failures outside the list are reasoned from the log, with the source repository named for confirming Desktop's own behaviour. - The preset-not-found entry is dropped: an old session naming a deleted preset simply cannot open, and recreating the preset to revive it is not a repair worth prescribing. profile-repair.ts goes with it. Startup stopped performing destructive package repair, which left the module with no caller in src/ and only its own test — a documented, tested path to nothing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore: point the community link at the current Discord invite The old invite is replaced everywhere it appears: the six READMEs, the Discord entry on the plugin recovery and Safe Mode pages, and the recovery smoke test that asserts the URL the click opens. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * fix(workspace): start a new session on the default preset, not a stale blank one (#481) * fix(workspace): start a new session on the default preset, not a stale blank one Opening a workspace does not create a session when a blank one is already there: connectWorkspace returns the first blank session whose cwd matches and only calls sessions.create when it finds none. A blank session is never run, so it stays blank forever — every later visit to that project is handed the same one as its "new session", carrying whatever preset was default when it was made. Changing the default in settings reaches only sessions created afterwards, which a session made weeks ago is not. Switching that session's preset instead is not available: the host resumes a session before recomposing it, and resuming loads the preset it already names. A blank session naming a preset that has since been deleted cannot be resumed at all, so it cannot be moved either — the select call comes back with "resume failed … preset not found". So the stale session is left alone and simply not reused. A blank session now qualifies only when the preset it names matches the current default, and a workspace whose blank session does not is given a fresh one. The stale session stays in the list to open or delete. The default is read through ctx.get rather than an inject: reading an un-injected Remote namespace throws rather than answering undefined, and this plugin has to keep working where no preset roster is composed. When it cannot be read, reuse behaves exactly as before. Verified in a running Harness: opening the project created session-67b5595c on `standard` and left session-95d736d0 on the deleted `repair` untouched, where before the stale one was handed back every time. The patch applies cleanly to the pristine 0.1.5-rc.2 package. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(repair-agent): check model and key availability before starting repair - Add checkModelAvailability in RepairAgentService to verify model keys and default session model status - Show informative warning dialog in Safe Mode and Recovery views when models are unavailable or unroutable - Add unit tests covering missing keys, unroutable providers, provider failures, and valid configurations --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * fix: add loader await timeout diagnostics and refine model unavailable copy (#485) * fix(loader): add timeout and stuck plugin diagnostics to loader await - Add timeout handling to cordis-plugin-loader EntryTree.prototype.await (configurable via DSH_LOADER_TIMEOUT_MS, default 100s) with 5s progress heartbeat - Accurately identify stuck plugins on timeout and emit structured dshPluginFailure errors - Add unit test coverage in cordis-plugin-loader-patch.test.ts * fix(repair-agent): update model unavailable dialog copy text - Refine warning dialog message and detail copy when default model is unconfigured or unavailable - Update unit tests in test/repair-agent.test.ts * update * test(repair-agent): sync model unavailable detail assertion wording (#486) * feat: adapt desktop to Harness 0.1.6-alpha.2 --------- Co-authored-by: fzfz <fzfzqp@gmail.com> Co-authored-by: Debug Agent <debug-agent@noreply.local> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Fairy-happy <243122010@qq.com> Co-authored-by: fairy <3054158379@qq.com> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Keep-Silence <keep137950@163.com> Co-authored-by: Wanming08 <152006802+Wanming08@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR addresses two key issues identified in recent Safe Mode and Loader startup diagnostics:
Loader await timeout and stuck plugin diagnostics:
@deepseek-ai/cordis-plugin-loader, if a plugin hangs or is blocked indefinitely during initialization, the loader'sawait()method previously lacked a proactive timeout, causing startup to freeze until the outer desktop watchdog forcefully terminated it.EntryTree.prototype.await(defaulting to 100s, overridable viaDSH_LOADER_TIMEOUT_MS) along with a 5-second progress heartbeat warning.entry._initTaskor non-active fiber) are captured and reported toconsole.errorwith package name and entry ID, throwing a structureddshPluginFailureerror for upstream Safe Mode and offline diagnostics recovery.Refine model unavailable dialog copy:
Changes
patches/@deepseek-ai+cordis-plugin-loader+1.0.3.patch: Added timeout, progress heartbeat, and stuck plugin failure attribution toEntryTree.prototype.await.test/cordis-plugin-loader-patch.test.ts: Added assertions for patch contents and unit test verifying hanging entry timeout behavior and error structure.src/main/repair-agent.ts: Updated dialog copy for unconfigured or unavailable models.test/repair-agent.test.ts: Updated corresponding unit test assertions.Verification
npm test -- test/cordis-plugin-loader-patch.test.ts test/repair-agent.test.ts(All 11 tests passed)npm run typecheck(Passed with zero errors)npm test(Full suite: 111 test files, 955 tests passed)git diff --check origin/main(No whitespace/merge conflicts)