Replies: 7 comments
|
Confirming this one and adding a second symptom from the same root. The provider does not only lose every skill: it also switches off the model-facing skill catalog entirely, so a The presets differ only in that rowBoth presets mount # presets/standard.patch.yml
- id: skill-filesystem
name: '@deepseek-ai/dsh-skill-filesystem'
- id: tool-skill
name: '@deepseek-ai/dsh-tool-skill'
# presets/cordis.patch.yml
- id: skill-filesystem
name: '@deepseek-ai/dsh-skill-filesystem'
config:
customSkillDirs:
- !!js ...resolve('@deepseek-ai/dsh-agent-preset/package.json')... + '/skills'Why the catalog disappearsThe watchdog, not the skill read, is what marks the observation incomplete, and its first step cannot see inside the archive:
A watcher exists to invalidate a cache. As written it decides whether the discovery result may be published at all, so a directory that cannot be watched is indistinguishable from a directory that cannot be read. The measurementEvery session log on this install carries at most one catalog message, and its presence tracks the preset exactly. Logs were decoded from
The version and preset are the same for all of them, so the only variable left is the row. A fresh Completeness is merged across the scope chain
Proposed fix
Not verified
|
|
补一条可以从包内直接核到的机制,帮你定位 YAML 那一侧: 所以这个失败不是「app.asar 里读不到文件」这一条造成的,而是「先隔离到只剩一个根,再把那个根指向 app.asar」。两个推论:
第二条(目录整份不进模型视图)我们没复现,不猜;但如果它是「发现阶段就抛错 → 目录为空」,把根修好可能会一起好转。 |
|
One correction to the YAML side, because it changes what the symptom can tell you: I pulled the shipped bundle out of // lib/index.js:33
includeDefaultRoots: z.boolean().default(true),
// lib/index.js:76
this.includeDefaultRoots = config.includeDefaultRoots ?? true;The - id: skill-filesystem
name: '@deepseek-ai/dsh-skill-filesystem'
config:
customSkillDirs:
- !!js ...resolve('@deepseek-ai/dsh-agent-preset/package.json')... + '/skills'
That row therefore ran with Two of your other points hold as written:
On "rank 300 sits after project roots and before user roots": that ordering holds with One thing to be careful about for whoever reads this next: with Agreed on the second symptom being worth keeping separate: a repaired root does not clear it, because the watcher's first For reproducing either side, the extraction detail worth checking: in this asar the file bodies begin at |
|
你说的对,我两处都要收回,其中第二处是我自己给的兜底也不成立。 1. 2. 更该收回的是我给的兜底:在这个配置下,把技能软链到
所以它不是「少几个技能」,是 provider 整份不贡献 —— 这也是为什么 你第二条(目录整份不进模型视图、且修好第一条后仍在)我没有复现,也不打算猜 —— provider 被 skip 只能解释「目录为空」这一个方向,解释不了它在你绕过第一条之后仍然存在。 |
|
Both retractions are right, and the second one holds for a reason other than the one you give. I checked both against the shipped bundle while checking my own claim, and my previous comment turns out to be unproven on exactly this point. The absence list swallows
|
| Where | Code | Effect |
|---|---|---|
| Watch | :97 await this.watchManager.observeRoots(roots) inside try/catch |
catch sets complete = false; list() returns { candidates, complete: false } and the registry never sees a throw |
| Read | :103 for (const root of roots) for (const skill of await discoverRoot(...)), no per-root guard |
rejection ends list() entirely and the provider is skipped |
Only the second makes SkillRegistry log skill provider "filesystem" skipped: <error>, because the registry's catch sits around provider.list() (dsh-skill lib/index.js:349-356). A watcher failure marks the layer non-cacheable and produces no such line. observeRoots awaits Promise.all(pending) over every root (:236), so a single watcher rejection on the archive root would take the whole call down and land in the first row.
That matters for the fix path, and it is where my own comment was wrong. I wrote that the watcher's stat rejects on the archive path and that is what marks the observation incomplete. The code cannot tell those apart on its own, and the shipped read-path measurement - the host file service returning a TypeError, not an absence - points at the read row instead: rank 300 throws, the loop dies, the provider is skipped, complete is irrelevant because nothing is returned at all. I am not going to assert it either way now, because both paths lead to a provider that contributes nothing.
The line that discriminates, and one experiment
The report lists skill provider "filesystem" skipped: <error> under "not verified" because Desktop shows no host logger output. If you can see the devtools console, that line is decisive and its error text names the path: present, and the throw is the read loop; absent while the catalog is still missing, and the watcher is the one reporting incomplete.
You can also separate them without logs: mount skill-filesystem a second time with watch: false (schema default is true, :37; consumed at :494) and the same customSkillDirs. If the throw persists with watching off, the read path owns it. If it disappears, the watcher does. No capture needed, and it settles a question that neither of us can settle by reading.
On the second symptom
I agree with leaving it alone. One provider being skipped explains an empty directory and nothing more, and it cannot explain a catalog that stays missing after the first cause is bypassed. That is still open in the report, and it is the part a maintainer needs to answer rather than either of us.
|
Answering my own open question from the last comment. The missing catalog is not a second, unrelated defect: one skipped provider explains it exactly. I got there by changing a single field and watching what happened. The gate that blocks is the completeness check
The middle one is the gate, and it is the only one the intervention touches. I kept the preset, kept Before the change: zero catalogs in every recorded After it: four One timing note, because it surprised me. The profile edit alone did not reach the running app: a What throws// dsh-fs-local/lib/index.js:238
async function probe(absolutePath) {
const info = await probeStats(absolutePath, (path) => stat(path, { bigint: true }));
// ...
mode: Number(info.mode & 511n), // :243Under Electron, ELECTRON_RUN_AS_NODE=1 "/Applications/DeepSeek Harness.app/Contents/MacOS/DeepSeek Harness" -e '
const fs = require("node:fs")
console.log(fs.statSync("/Applications/DeepSeek Harness.app/Contents/Resources/app.asar/dsh/node_modules/@deepseek-ai/dsh-agent-preset/package.json", { bigint: true }).mode & 511n)'That prints the TypeError. Why the watcher is not the cause
The chain, in order
One skipped provider therefore costs both the skills and the catalog, and the catalog is the part that hurts. A skill the model has never been told about may as well not exist; it stays callable only for someone who already knows its name. Two things that would have shortened thisThe per-root guard the report already asks for, and a skipped provider that says so somewhere a user can see. Today the only trace is a My local repair is a profile patch, not a fix for the shipped preset. |
|
给你那条
|
Uh oh!
There was an error while loading. Please reload this page.
The
cordispreset loses every filesystem skill in Desktop, because its only skill provider points insideapp.asarComponent:
skill/skill-filesystem,bundle/web-app/cordis.patch.yml, host file serviceVersion:
0.2.0-rc.2· Platform: macOS, Desktop appSummary
With the
cordisagent preset selected,skillcannot load the four skills the preset ships, and itcannot load skills the user wrote either. Skills served by
@deepseek-ai/dsh-skill-officestillload, so the registry and the tool work. What fails is the only filesystem provider an agent has.
The provider configuration is the problem.
bundle/web-app/cordis.patch.ymldisables the hostskill-filesystemrow on purpose, because presets own local discovery. Thecordispreset thenmounts its own copy with
customSkillDirspointing at@deepseek-ai/dsh-agent-preset/skills, whichin the Desktop app lives inside
app.asar. The host file service fails on that path withTypeError: Cannot mix BigInt and other types, use explicit conversions, anddiscoverRoottreats afailing root as a failing provider:
app.asar.TypeErrorrather than anabsence, so it is not classified as one.
discoverRootawaits each root without a guard, so this one rejection ends the provider's wholelist(). The custom directory is scanned before the default roots, so the project's.dsh/skills,~/.dsh/skillsand~/.agents/skillsare lost with it.SkillRegistryanswers a throwing provider by skipping it and keeping the others. That is theright call for one bad provider, but nothing reaches the user, and the catalog merely looks small.
cordis-plugin-development/SKILL.mdalready describes the constraint this path runs into:Host file reads are what the skill provider uses, and they fail here.
Reproduction
cordisin General settings, then agent preset. Start a new session: a preset is fixed atsession start.
Confirm the preset mounted. The session's
request/headertool list holds exactly three toolsmore than a
standardsession: 70 against 67, withcordis_inspect_list,cordis_inspect_queryandplugin_manageradded, and nothing lost. Both introspection toolsreturn live data.
Confirm the host row is off and the preset's row is the only one. This comes from the plugin
manager's plugin list, which reads the live Loader tree:
{"entryId":"include:skill-filesystem","moduleName":"@deepseek-ai/dsh-skill-filesystem", "enabled":false,"fiberPhase":null,"patchId":"skill-filesystem"} {"entryId":"include:tool-skill","moduleName":"@deepseek-ai/dsh-tool-skill", "enabled":false,"fiberPhase":null,"patchId":"tool-skill"}include:skillitself isactive. The preset mountstool-skillandskill-filesystemin its owntree, which is why the
skilltool still works.Evidence
The file service cannot read inside the archive. Each row was run from the affected session through
the harness's own read path.
.../app.asar/dsh/node_modules/pako/package.jsonError: Cannot mix BigInt and other types, use explicit conversions.../app.asar.unpacked/dsh/node_modules/pako/package.json, the same file.../app.asar/dsh/node_modules/@deepseek-ai/dsh-agent-preset/skills/agent-experience/SKILL.mdTypeError.../app.asar/dsh/node_modules/@deepseek-ai/dsh-agent-preset/skills, the configured rootTypeError~/.dsh/profiles/desktop/package.json~/.dsh/skills/definitely-missing/SKILL.mdnot foundThree of those rows are controls. A real path outside the workspace reads, a missing real path is
reported as absent instead of throwing, and the identical file under the unpacked mirror reads
normally. The failure follows the archive, not the file.
Walking the entries the archive's directory declares puts the skills at
dsh/node_modules/@deepseek-ai/dsh-agent-preset/skills: four skills, 17 files, 75,371 bytes. A walkof the app bundle,
~/.dshand the project trees found no unpacked mirror of that package, and oneunrelated copy in this workspace's own
node_modules, installed as a dependency of@deepseek-ai/dsh.The shipped preset sets the path, and the web app layer disables the host row:
Neither end of the failure path has a guard. In
skill-filesystem,roots()pushescustomSkillDirsahead of the user and bundled roots, anddiscoverRootawaits each root without atry, so one rejection endslist(). InSkillRegistry.listLayerCandidates, a provider thatthrows is dropped for that read:
An observation marked incomplete still contributes its candidates, so the existing incomplete
channel is enough to keep the readable roots. Only a thrown provider contributes nothing.
That warning is the only trace, and it does not reach the user. This is the same shape as #8633
("Host plugin failures are invisible in
dsh web"). Theskilltool's description tells the modelthat a session skill catalog exists and that names come from it, so a shrunken catalog produces
nothing but "unknown or no longer available".
Impact
Any Desktop install on this version that selects the preset. The preset and the path both ship with
the app, and the Desktop archive layout is what the bundled skill's own text describes. The loss
reaches past the row that causes it: user skills in the standard roots stop resolving too.
Proposed fix
discoverRoot, skip a root that cannot be read, markthe observation incomplete and keep the readable roots. A provider observation already carries a
completeflag, and today only a watcher failure sets it. As written, one bad entry incustomSkillDirscosts a user every local skill.archive read that the shipped skill documentation assumes, or resolve the preset's bundled skills
directory to something readable, such as the unpacked mirror or a directory the installer writes.
the session or on the plugin status surface, rather than in one host log line.
Verification
Verified in the affected session on macOS, Desktop
0.2.0-rc.2, presetcordis: everyskillcallabove; every file read above with its controls; the archive walk; the live plugin listing that shows
the host row disabled; the preset declaration, the disabling patch and the code paths quoted. All
were read from the installed bundle or run in that session.
Three things were not verified.
The
skill provider "filesystem" skipped:line was never observed. The Desktop app does not showhost logger output to the user or the agent. That this provider throws on its single configured root
follows from the file service measurements and the unguarded
discoverRoot; the skip itself is readfrom
SkillRegistry.No source checkout was tested, so whether this reproduces where the preset resolves to a real
directory is unknown. The bundled skill's text implies it does not.
Whether the previous preset was affected is also unknown. A preset is fixed at session start, and no
session log records the catalog, so the failure cannot be re-queried after the fact.
All reactions