mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-08-08 16:25:10 +00:00
* fix(serve): prevent repeated workspace skill rescans Make workspace skill status reads use committed snapshots and move refresh work to explicit mutation paths. Add generation-safe daemon caching, conditional HTTP responses, SDK revalidation, and multi-session extension refresh safeguards. Refs #8079 Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(serve): narrow the workspace-skills read model and close its regressions Follow-up to the previous commit on this branch, from reviewing it. Subtractions — these were separable from the fix and carried more surface than value, so they move out of this change: - Revert the ETag / If-None-Match layer (CORS allow+expose headers, the SDK conditional JSON cache, the browser bundle budget bump). Express already emits an ETag and answers 304 for these routes, so the only new behavior was the SDK cache. It saves transfer bytes but no daemon work — the ETag is a hash of the already-serialized body — and it shipped without a paired `Cache-Control`, which is what actually keeps an intermediary from serving a stale snapshot of an authenticated, mutable resource. The SDK cache was also unbounded, with no eviction or clear entry point. - Revert moving `extensions_final` ahead of skill initialization in `Config.initialize`. In non-safe, non-bare mode `extensions_initial` is already the same argument-less `refreshCache()`, and it runs `applyStoreActivation`, so `getActiveExtensions()` is fully populated before skills are enumerated either way. The move changed only startup event order (and pushed permissionManager past the extension refresh) for every surface including the interactive CLI. Regression fixes — the read went pure, but two of its inputs lost their only path back to disk: - Extension sources have no watcher, unlike skills. With the per-read `extensionManager.refreshCache()` gone, an extension installed, removed, enabled, or disabled outside the daemon would never reach the snapshot until the child restarted — and because extension-level skills are derived from the extension set, a skill-watcher tick could not recover it either. Adds `ExtensionManager.refreshCacheIfSourcesChanged()`: a stat-based fingerprint over the extension directory entries, each manifest, the enablement file, and the store state, which refreshes only when they moved. A status read pays one readdir plus one stat per entry instead of a directory scan and a full parse, and stays self-healing. The baseline is the pre-load fingerprint, so a change landing during a refresh stays visible to the next check instead of being masked by a post-load stat. The directory and store halves are captured at different points because a refresh writes the store itself but never the manifests. - Revalidation is skipped in safe and bare mode, and the whole of it — including that mode check — sits inside its error boundary. Those modes never populate the extension cache by design, while the snapshot derives extension skills from `getExtensions()`, so revalidating there would have loaded the extensions the mode exists to exclude. Keeping the mode check outside the boundary would also have let a config missing those accessors fail a read. - `initialized: true` with an empty list when the config has no `SkillManager` is now `initialized: false`. The daemon latches any initialized answer into `lastWorkspaceSkillsStatus` and then prefers it over its own local enumeration, so the old value could suppress the fallback permanently. Also: - The retained-snapshot path bumped the freshness timestamp without checking its generation, so a read that started before an invalidation could push out the TTL of a snapshot a later read had committed — letting a post-mutation snapshot go unrevalidated for longer than the window. - `setWorkspaceSkillEnabled` folded `configsFailed` into `sessionsFailed`, but it sends `reason: 'settings'`, which never refreshes a skill cache, so the term was structurally zero. Report `configsFailed` from the `content` path instead, where it can actually be non-zero. - Documents the settings-freshness gap this read model accepts: enablement now comes from the child's in-memory `LoadedSettings`, which `SettingsWatcher` keeps current for the User and Workspace scopes but not for System / SystemDefaults (locked-skill policy) or an untrusted workspace. Tests: adds a real-filesystem guard that drives 50 consecutive cached reads and asserts zero additional readdir/readFile calls — the mocked suites could only prove `refreshCache` was not *called*, which is not the invariant that broke. Adds coverage for the fingerprint gate (steady state, install, removal, in-place manifest edit, concurrent callers, and the mid-refresh race), for the null-manager, moved-sources, and safe/bare-mode read paths, and for the generation guard. The generation-guard and safe/bare-mode tests were each verified to fail with their fix reverted. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| assets | ||
| design | ||
| developers | ||
| e2e-tests | ||
| images | ||
| plans | ||
| superpowers | ||
| users | ||
| verification/abort-controller-refactor | ||
| _meta.ts | ||
| index.md | ||