Repository navigation
cua_driver_native__* tools are registered but never exposed to agents (DSH 2.0.20-next, Windows) #11207
Replies: 17 comments 6 replies
|
One additional case to check is the agent's tool presentation mode, separately from its allow/deny groups. In the public rc.3 source, PTC presentation exposes only With the actual live const name = 'cua_driver_native__get_desktop_state'
console.log({
global: ctx.tools.get(name) !== undefined,
agent: ctx.tools.get(name, agent) !== undefined,
})If both are true and the outgoing model request advertises only For the late-registration observation, rc.3 already emits I haven't reproduced your installed build, so this doesn't establish its root cause. The two lookup results plus the outgoing tool names would distinguish the visibility and PTC cases; no prompt contents or tool arguments are needed. |
|
Hi Caldera, Thank you — the two lookups were exactly what we could run, so here is the raw data. We are not Environment: DSH 2.0.20-next, Windows 11, desktop profile, single machine. Agent preset in use: 1. The two lookups (run inside the live agent)So registration and agent-scope resolution both succeed, while 2. Counts, from the same processWe can dump the full 168/135 name lists if that helps. 3. What we ruled out on our side (so it is not our plugin)Our plugin masks tool groups by calling So the 11 tools we released did appear (+11), which shows our masking really works — and with zero 4. One configuration observation, offered as a fact, not a theoryThe Config directory for this profile lists 236 entries. 5. Three experiments we ran before sending this (so you get experiments, not guesses)Experiment A — a brand-new agent (rules out late registration / caching). Experiment B — switched the agent preset to the deployment default. Experiment C — removed our masking entirely (same as section 3, listed here for completeness). Also worth flagging: the whole family is 56 tools, not 19. We used to describe it as "19", because 6. Questions (we would rather ask than assume)
We are two people-and-a-model without much engineering background; we would rather send you too much Thanks for looking at this. — owner of dsh-tool-drawer (a plugin that masks tool groups via agent-scoped |
|
Corrected probe — run synchronously in the live agent, with our deny mask empty (lastHidden = { hidden: 0, applied: false }): {
"name": "cua_driver_native__list_apps",
"global": true,
"scoped": false,
"inScopedSchemas": false,
"scopedRunCode": false,
"runCodeInSchemas": false,
"defaultPreset": "cordis",
"composedPreset": "cordis"
}(inScopedSchemas / Same shape for the other family members we probed at the same moment — cua_driver_native__launch_app, launch_app, start_audio_recording — all global: true / scoped: false. schemas(agent) = 135 names; So by your rule: global true, scoped false ⇒ the leading possibility is another restriction in the inherited scope chain. Our own mask was empty at that instant, so it is not ours. The presentation row is also not mounted in this profile (Config directory lists 236 entries; @deepseek-ai/dsh-agent-tool-presentation is not among them). Two questions:
On the preset: defaultPreset = cordis and composedPreset(agent.ctx) = cordis, so our earlier selectedDefault: standard switch indeed had not taken effect for this agent — consistent with your note. We restored our usual mask (48 names, none of them cua_driver_native__*) right after the check. |
|
Ran the diagnostic, with our own mask empty at that moment (lastHidden = { hidden: 0, applied: false }): {
"layerCount": 2,
"filters": [
{ "layerIndex": 1, "filterIndex": 0, "allowCount": null, "denyCount": 56, "excludesName": true },
{ "layerIndex": 1, "filterIndex": 1, "allowCount": null, "denyCount": 48, "excludesName": false }
]
}Same snapshot, the lookups: { "global": true, "scoped": false, "inScopedSchemas": false,
"scopedRunCode": false, "runCodeInSchemas": false,
"defaultPreset": "cordis", "composedPreset": "cordis" }Facts we can state without interpreting:
One observation that makes us cautious about the first filter:
Searches on our side (to save you time): in this profile only two plugins call ctx.tools.restrict(...) — our drawer, and dsh-ops (whose deny lists are its own published names only). We found no oolFilter in profile plugins. So a filter that denies exactly this family most likely comes from a bundle/composition inside �pp.asar, not from a user plugin. Questions:
We restored our usual 48-name mask afterwards. |
|
Environment details, now verified on the machine rather than copied from this thread's title (my first comment said "DSH 2.0.20-next" because the title said so — that was not a measurement, so here is the real one): Two more facts that may matter for reproducing:
If any of these values look wrong for a 2.0.20-next desktop build, please say which one and we will re-check it. |
|
One refinement that may narrow this down, based on when the filters can exist: Restriction filters live in memory, so they cannot survive a process restart. Our plugin's deny list at boot is 48 names (the groups we keep collapsed: 37 + 9 + 2); cua-screen (19) is ON at boot, so it is never in our deny. We have never registered a 56-name deny in this process — we only ever produced 48, and later 0 (empty) during experiment C. But the 56-name deny was already present in the first diagnostic taken after that boot, and it excludes the name. So it must have been created during that boot by whatever owns those tools — not left over from an earlier state. Also worth noting the arithmetic: 56 is exactly the size of the family (cua_driver_native__* = 19 + 37), i.e. this filter denies the whole family and nothing else. That reads like a deliberate "don't expose this family natively" decision by some owner, rather than an allow/deny accident. Our own 48-name entry staying visible after we lifted it is consistent with this view retaining lifted entries — but that explanation cannot apply to the 56 one, because this process never produced a 56 in the first place. So the question narrows to: in this build, which component registers 56 cua_driver_native__* tools and restricts exactly those 56 for the agent scope at mount time? |
|
Two things from the shipped README of @deepseek-ai/dsh-experimental-computer-use-cua-driver-native (reading the copy inside our installed �pp.asar), which may matter:
That last phrase is the one that caught our eye, because our profile has a third-party computer-use plugin mounted (deepseek-computer-use 0.2.3, which registers the bare-name GUI tools list_windows / get_window_state / click / ype_text / …). It is plausible that it occupies the computer-use registration in this profile. We are not claiming that is the cause — only that it is the one failure mode your README names that our environment could plausibly hit. If you want, we can run one clean experiment: disable that third-party computer-use bundle, restart, and report schemas(agent) / scoped / the layer diagnostic again. That would separate "cua driver activation was rejected because the slot was occupied" from "something denies the family afterwards". Say the word and we will do it (it is fully reversible on our side). We can also disable the cua driver entry itself and report whether the 56-name deny disappears with it — that would tell us whether the deny belongs to that entry or to something else. To avoid burning your attention: tell us which of the two you find more informative, or if both are worth doing. |
|
Result of the occupied-slot experiment: refuted. The third-party computer-use plugin is not the cause. We disabled deepseek-computer-use (its 11 bare-name tools really did disappear: schemas(agent) went 135 → 130, and our own groups that covered them matched 0) and restarted, so the CUA driver booted into a profile without that competitor. The first diagnostic after that boot: {
"scoped": false,
"inScopedSchemas": false,
"scopedRunCode": false,
"filterReport": {
"layerCount": 2,
"filters": [
{ "layerIndex": 1, "filterIndex": 0, "allowCount": null, "denyCount": 37, "excludesName": false },
{ "layerIndex": 1, "filterIndex": 1, "allowCount": null, "denyCount": 56, "excludesName": true }
]
}
}So with the competitor gone the picture is unchanged: the 56-name deny is still there and still excludes the name. (The 37 entry is ours — only one of our groups was collapsed on that boot. Note our own deny shrank from 48 to 37 exactly because the tools from that plugin no longer exist, which is a useful cross-check that the numbers track reality.) That rules out "occupied computer-use registration" for our case. We have re-enabled the third-party plugin afterwards. Remaining candidate on our side: the deny belongs to the CUA driver entry itself (self-restriction at mount time). We can test that next — set - { id: computer-use-cua-driver-native, disabled: true } in our profile patch, restart, and report whether the 56-name deny disappears with the entry. That is one more restart on our side; we will only spend it if you think it is informative (we can also stop here and wait for your analysis). |
|
Ran your suggested comparison: drawer disabled, native driver and everything else untouched, one restart. Evidence that the drawer was really gone in that process:
Result: cua_driver_native__ were still not in the agent's tool list.* Nothing became callable. Limitation I want to state rather than paper over: our probe (lazy_tools_health) lives inside the drawer, so during that boot we could not re-run the numeric lookups (global / scoped / the 56-filter count). This result is a visibility-level observation: with our plugin entirely absent, the family is still not exposed to the model. It does not re-measure the filter table. If the numeric comparison matters to you, the honest way is one more restart with the drawer disabled and a copy of the probe living in a separate throwaway plugin, so the measurement exists while the drawer does not. We can build that (it is ~40 lines and we would remove it afterwards) — say whether that is useful, or whether the visibility result plus the earlier filter snapshot is enough for your analysis. We have re-enabled the drawer and verified it: healthy: true, 48 names masked again, and the same denyCount: 56 / excludesName: true filter is back in the layer view. Summary of what our experiments have eliminated so far: not registration; not our mask (empty deny, and now the whole plugin absent); not late registration or a stale per-agent cache (fresh agent identical); not PTC/ |
|
Before you spend more time on our data, three places where our own claims are stronger than the evidence. We would rather narrow them now than have you build on them.
One more, about our own instrument: our 48-name filter was still listed in layers after we lifted it. We told you that this "view appears to retain lifted entries". A second reading fits equally well — our lift did not actually take effect (a bug on our side). We cannot distinguish the two yet. If the second reading is right, some of our earlier "no filter of ours excludes it" reasoning needs re-checking against a build where we never lifted anything. None of this changes the two measurements you asked for (the scoped lookup and the filter counts) — it changes how much weight the surrounding sentences deserve. |
|
We upgraded two of the three caveats from "inference" to measurement, using your diagnostic with a list of names instead of one. We asked the filters about all 56 family names at once: {
"layerCount": 2,
"filters": [
{ "filterIndex": 0, "denyCount": 56, "checked": 56, "excludedCount": 56, "excludedAll": true },
{ "filterIndex": 1, "denyCount": 48, "checked": 56, "excludedCount": 37, "excludedAll": false },
{ "filterIndex": 2, "denyCount": 48, "checked": 56, "excludedCount": 37, "excludedAll": false }
]
}What that settles:
What remains genuinely unknown is only the owner of the 56-name deny: at layerIndex 1, filtered by exactly this family, registered during boot, and not ours. Our own two leftover entries show what an entry we did create looks like — same layer, same shape — so whatever creates this one has the same kind of access to this agent's scope. |
|
You were right, and the fix is in: 8859fda / 38ddb09. We reproduced it in our own mock before touching the fix, exactly the way you described it. Our mock had to be taught one thing first: that a lift fires ools/change synchronously (we had not modelled that, which is why our earlier tests were green). With that added, the regression test fails on the old code with:
and passes after the fix (50 assertions now; the new one is [41], and it opens two initially hidden groups in succession, then asserts both names are visible and zero restrictions remain). Fix: Live check on our machine (no restart): opening one group used to add an entry to the filter list; now the count stays at 3 (the 56, one orphan 48 left over from before the fix, and the current restriction, which went 48 → 11 when we opened that group → back to 48). The pre-existing orphan cannot be lifted any more — it predates the fix and disappears with the process, which is consistent with your note. Two corrections on our side, since they were our claims:
We have not restarted, per your advice, and we are holding on the remaining question (who owns the 56) until you have anything else worth testing. |
|
Thank you — genuinely. You did the hard part here: our test suite was green, and the reason it was green was a missing property in our own fake host (it did not emit ools/change synchronously on restriction removal). We had been testing against our assumption about the registry, not against the registry. That is a class of mistake we would not have caught from inside our own tests, and you caught it by running our code against a model of yours. It is fixed, tested both ways, and shipped:
ebuild is serialized per agent (pendingRebuild + bounded re-run), so a nested pass can no longer have its lift handle overwritten.
We also added the incident to our own failure archive (#54), with the lesson stated plainly: when testing "we leave no orphan behind", the fake host must model the real event ordering, or the test proves only your own assumption. Two things still open on our side, stated so they don't get lost:
Thanks again for the precision — the mock reproduction, the exact sequence, and the note about synchronous notification are what made this fixable. (Correction to my previous version of this comment: it contained a commit hash I had typed by hand and never verified — 480449d… — which does not exist. Removed. The two hashes above are the real ones, read from git rev-parse.) |
|
Found and fixed, and thank you again — that is the second real bug you have found in our code tonight by running it against a model of yours, and this one was the more dangerous of the two: a tool staying visible means silent token burn, and nothing in our own tests noticed it. Reproduced exactly as you described (our mock, cooldown compressed to 60 ms): after late_one is hidden and the cooldown starts, late_two registered inside the window stays visible, and 250 ms later it is still visible. Test failed on the old code with:
Fix (0.15.1, https://github.com/kmyy350234-dz/dsh-tool-drawer/releases/tag/v0.15.1 · source c9f6f8d · mirror �714c59):
Noted and agreed: this was pre-existing, not a regression from the serialization fix, and it says nothing about the 56-name restriction. We are still not guessing at that one. One more thing we owe you: our earlier claim in this thread that the duplicate 48-name entries proved "the view retains lifted entries, so our lift was fine" was wrong, and we retracted it — the actual cause was the orphan from the reentrancy bug. Both corrections are in our own failure archive (#54 reentrancy, #55 a hash I typed by hand and never verified, corrected in the comment above). If you have a third case, send it the same way — the pattern of "run their code, report the minimal sequence" is worth far more to us than a written review. |
|
Update after DSH 2.0.21-next (this machine, Windows): the behavior described in the opening post is unchanged. Measured on 2.0.21-next:
So the fix does not appear to be in 2.0.21. Nothing needed from the maintainers here — recording the version so the report stays accurate. If there is a preset/composition switch that is expected to expose these tools, please point me at it and I will retest. |
|
Hi Caldera — follow-up from the same machine (DSH 2.0.21-next desktop, Windows 11, profile What is happeningThe 56 names are denied at agent scope by the desktop shell's Computer Use (Next) permission gate, using the same public mechanism any plugin uses: // desktop app bundle, lib/extensions-*.js
gate.disposeTools = agent.ctx.tools.restrict({ deny: cuaToolNames })The gate is active when
So by default every session has the whole family denied at agent scope. Registration and agent-scope resolution are fine ( Reproduced from our side
What we would ask for (the part that cost us the most time)Neither of these is a bug in the gate; both are visibility/diagnosability gaps:
We are not asking the gate to change — hiding desktop-control tools by default is the right default. We are asking the API to say who is hiding them. For completeness: we have not run |
|
Follow-up after a host restart (DSH 2.0.21-next desktop, profile Earlier in this thread I attributed the missing On this host the gate defaults to enabled when Caveats: statements above are a local probe; no on/off differential was performed (no |
Uh oh!
There was an error while loading. Please reload this page.
On DSH 2.0.20-next (desktop 0.2.1-rc.3, Electron 44.7.0, win32/x64) the native computer-use tools are registered but never reach an agent's tool table.
Symptom
cua_driver_native__get_desktop_stateand the othercua_driver_native__*tools return:What I measured
ctx.toolsnames)cua_driver_native__*namesagent.ctx.tools.restrict)if (definition?.[VISION_ROUTER_TOOL_OWNER] === true) active.push(name)~/.dsh/profiles/desktop/cordis.patch.ymlhas- { id: computer-use-cua-driver-native, disabled: false }, and boot log has nopatch: entry … not foundrun.startlogs"elevated":trueExtra evidence
computer-use-cua-driver-nativeat startup, whiletheme-endfield,dsh-cost-meter,vision-routeranddsh-ops-fileeach log one.resources/app.asar.unpacked/node_modules/@trycua/cua-driver-win32-x64-msvc/{cua_driver_node_runtime.node, cua_driver_sdk.dll}.Expected
Enabling the
computer-use-cua-driver-nativerow should mountcua_driver_native__*into agent tool tables, as it did before this build.Unrelated observation (same build)
A plugin's
apply()runs before other plugins register their tools, soctx.toolslookups at apply time can miss them. A "all plugins registered" lifecycle hook would help third-party plugins avoid having to recompute atagent/created.All reactions