{"_id":"@argszero/cordis-plugin-preset-tool-filter","_rev":"2-b43cd416838ef97724d4e8c1a7e12961","name":"@argszero/cordis-plugin-preset-tool-filter","dist-tags":{"latest":"0.2.0"},"versions":{"0.1.0":{"name":"@argszero/cordis-plugin-preset-tool-filter","version":"0.1.0","keywords":["deepseek","dsh","cordis","plugin","preset","tool-filter","minimal"],"license":"MIT","_id":"@argszero/cordis-plugin-preset-tool-filter@0.1.0","maintainers":[{"name":"argszero","email":"argszero.reg@gmail.com"}],"homepage":"https://github.com/argszero/cordis-plugin-preset-tool-filter#readme","bugs":{"url":"https://github.com/argszero/cordis-plugin-preset-tool-filter/issues"},"dist":{"shasum":"db136228d6fd4c42bce6197638d4382ded73f66c","tarball":"https://registry.npmjs.org/@argszero/cordis-plugin-preset-tool-filter/-/cordis-plugin-preset-tool-filter-0.1.0.tgz","fileCount":5,"integrity":"sha512-IoMVjlBfNAaCWO4s1veUtN+/kluqyFAW4XYeTsxg+4juHBozEpcE3+YZI4mGMz4pqqlMttuRlfeaDKU4MOjrsA==","signatures":[{"sig":"MEQCIGK/ueM4Q9Re/vUFb2JK2aWsUG3VUbSf2Itn2IzyKtx0AiAEu42uvQhPBsCLBOWxiqzfvcBl+cX0qBD4wq16i+BQug==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":14468},"main":"lib/index.js","type":"module","types":"lib/index.d.ts","engines":{"node":"^22.19 || >=24"},"exports":{".":{"types":"./lib/index.d.ts","default":"./lib/index.js"}},"gitHead":"b8e1600d21cfc543c86bec40cceb4d5572c250dc","scripts":{"build":"tsc -p .","prepack":"tsc -p ."},"_npmUser":{"name":"argszero","email":"argszero.reg@gmail.com"},"repository":{"url":"git+https://github.com/argszero/cordis-plugin-preset-tool-filter.git","type":"git"},"_npmVersion":"11.17.0","description":"Per-preset tool allowlist for the dsh harness: apply a ToolRestriction to an agent's scope so a preset (e.g. minimal) exposes only its intended tools and hides plugin-registered global tools (#5786).","directories":{},"_nodeVersion":"26.5.0","dependencies":{"@types/node":"^22.20.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^7.0.2","@deepseek-ai/cordis":">=4.0.0 <5"},"peerDependencies":{"@deepseek-ai/cordis":">=4.0.0 <5"},"peerDependenciesMeta":{"@deepseek-ai/dsh-tools":{"optional":true},"@deepseek-ai/dsh-preset":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/cordis-plugin-preset-tool-filter_0.1.0_1788687382156_0.6468273176484858","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"_id":"@argszero/cordis-plugin-preset-tool-filter@0.2.0","bugs":{"url":"https://github.com/argszero/cordis-plugin-preset-tool-filter/issues"},"dist":{"shasum":"b1072236810525a3da33afde0df06521c09a02b5","tarball":"https://registry.npmjs.org/@argszero/cordis-plugin-preset-tool-filter/-/cordis-plugin-preset-tool-filter-0.2.0.tgz","fileCount":5,"integrity":"sha512-+y5eu1zDgLuUiVTMaLloFvAljd/udzmJWMVnLzHVOAeiWGKcl7jWoQL1yNAOdeRYrY66o2T/hyGuHvaAe4mEtg==","signatures":[{"sig":"MEYCIQCytVPzuSMO2zByFsm6GRI5b+pbgfxNHLOPf5oRDfuIpwIhAOZ6ba19ulq1BDr6wA8ojNuIFyC+Ba8gWNil8q0UYiEG","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCBuzE6Pxsw0RBE7dwYTphQxuGFNgt2uXSi1o6E5x3EVwIgKhsJN380gEp4/2HJtDRZZmgW40IE8PBWnmdI/eeuCUs="}],"unpackedSize":26548},"main":"lib/index.js","name":"@argszero/cordis-plugin-preset-tool-filter","type":"module","types":"lib/types/index.d.ts","engines":{"node":"^22.19 || >=24"},"exports":{".":{"types":"./lib/types/index.d.ts","default":"./lib/index.js"},"./src/*":"./src/*","./package.json":"./package.json"},"gitHead":"7e18eff41505c6c70ad362decd3d92a0be9df75d","license":"MIT","scripts":{"test":"node --test \"test/*.spec.mjs\"","build":"tsc","pretest":"tsc","prepublishOnly":"tsc"},"version":"0.2.0","_npmUser":{"name":"argszero","email":"argszero.reg@gmail.com"},"homepage":"https://github.com/argszero/cordis-plugin-preset-tool-filter#readme","keywords":["cordis","deepseek-harness","dsh","plugin","preset","tool-filter","tool-restriction","computer-use","minimal"],"repository":{"url":"git+https://github.com/argszero/cordis-plugin-preset-tool-filter.git","type":"git"},"_npmVersion":"11.17.0","description":"Two public tools.restrict() use cases for the dsh harness, applied per agent scope: a per-preset tool ALLOWLIST (#5786: a preset agent inherits the global layer and every ancestor scope layer, so plugin-registered commands leak into a minimal preset) and ","directories":{},"maintainers":[{"name":"argszero","email":"argszero.reg@gmail.com"}],"_nodeVersion":"26.5.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"semver":"^7.6.3","typescript":"^5.5.0","@types/node":"^22.10.2","@deepseek-ai/cordis":"^4.0.2","@deepseek-ai/dsh-scope":"0.1.6-alpha.2","@deepseek-ai/dsh-tools":"0.1.6-alpha.2","@deepseek-ai/dsh-system-prompt":"0.1.6-alpha.2"},"peerDependencies":{"@deepseek-ai/cordis":"^4.0.2","@deepseek-ai/dsh-tools":">=0.1.2-rc.1 <0.2.0 || >=0.1.3-alpha.2 <0.2.0 || >=0.1.5-alpha.1 <0.2.0 || >=0.1.6-alpha.1 <0.2.0"},"peerDependenciesMeta":{"@deepseek-ai/dsh-tools":{"optional":true}},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/cordis-plugin-preset-tool-filter_0.2.0_1789735198785_0.2574649914279543"}}},"time":{"created":"2026-09-06T09:36:21.962Z","modified":"2026-09-18T12:39:59.051Z","0.1.0":"2026-09-06T09:36:22.276Z","0.2.0":"2026-09-18T12:39:58.872Z"},"bugs":{"url":"https://github.com/argszero/cordis-plugin-preset-tool-filter/issues"},"license":"MIT","homepage":"https://github.com/argszero/cordis-plugin-preset-tool-filter#readme","keywords":["cordis","deepseek-harness","dsh","plugin","preset","tool-filter","tool-restriction","computer-use","minimal"],"repository":{"url":"git+https://github.com/argszero/cordis-plugin-preset-tool-filter.git","type":"git"},"description":"Two public tools.restrict() use cases for the dsh harness, applied per agent scope: a per-preset tool ALLOWLIST (#5786: a preset agent inherits the global layer and every ancestor scope layer, so plugin-registered commands leak into a minimal preset) and ","maintainers":[{"name":"argszero","email":"argszero.reg@gmail.com"}],"readme":"# cordis-plugin-preset-tool-filter\n\nTwo uses of one public `tools.restrict()` seam for the [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (`dsh`), applied per agent scope at `agent/created`:\n\n1. **Per-preset tool allowlist** — [#5786](https://github.com/deepseek-ai/deepseek-harness/discussions/5786)\n2. **Per-deployment tool-group opt-out** — [#7080](https://github.com/deepseek-ai/deepseek-harness/discussions/7080)\n\nBoth are **plugin-side**, not harness patches: the harness already exposes the mechanism, nothing in the preset\nor provider composition calls it.\n\n## Why\n\nEvery agent **inherits** the tool registry's global layer and every ancestor layer on its scope chain. Two\nconsequences:\n\n- **#5786.** Preset composition mounts its rows into a standing ancestor scope and never applies a\n  `ToolRestriction`, so a `minimal` agent still inherits every plugin-registered global tool — the model sees a\n  catalog far larger than the preset intended.\n- **#7080.** A provider that registers a large catalog unconditionally (the computer-use driver: 55 tools,\n  ~94 KB / ~23.5k tokens of request prefix in the reporter's environment) has no registration-time knob.\n  Disabling the provider removes the capabilities the deployment *does* use; there is no way to drop one group.\n\n`tools.restrict({ allow, deny })` answers both: it masks what the scope **inherits** (global layer + every\nancestor layer) and never the scope's own registrations, so a config-mounted provider's tools can be taken out\nof the request prefix — and out of dispatch — while the provider stays mounted.\n\n## Install\n\n```sh\nnpm install @argszero/cordis-plugin-preset-tool-filter\n```\n\nMount the plugin into your `dsh` profile:\n\n```yaml\n# cordis.yml\nplugins:\n  \"@argszero/cordis-plugin-preset-tool-filter\":\n    groups:\n      browser: { names: [cua_driver_native__browser_prepare, cua_driver_native__get_browser_state] }\n      recording: { prefix: cua_driver_native__recording }\n    disableGroups: [browser, recording]\n```\n\n> **Peer dependencies.** The plugin reads `tools` and `agentPresets` at runtime via `ctx.get()`, so\n> `@deepseek-ai/dsh-tools` is an optional peer (present in any real harness workspace, absent from a bare\n> `@deepseek-ai/cordis` one). The built artifact imports **nothing** at runtime — the `cordis` import is\n> type-only — so the peer range is only a compatibility claim, checked in `test/packaging.spec.mjs`.\n\n> **Version pin.** The `@deepseek-ai/dsh-*` `latest` dist-tag is frozen at an old `0.1.2-rc.1`; the current\n> harness line is published under `next`. If you install by tag, prefer `@next`.\n\n## Usage: cut a tool group out of the request prefix (#7080)\n\nDeclare each group once — by exact names, by namespace prefix, or both — then name the ones this deployment\ncannot use:\n\n```yaml\nplugins:\n  \"@argszero/cordis-plugin-preset-tool-filter\":\n    groups:\n      browser: { names: [cua_driver_native__browser_prepare, cua_driver_native__get_browser_state] }\n      recording: { prefix: cua_driver_native__recording }\n      session: { names: [cua_driver_native__page] }\n    disableGroups: [browser, recording, session]\n    # or, without naming a group:\n    # deny: [cua_driver_native__get_window_state]\n    # denyPrefixes: [cua_driver_native__recording]\n```\n\nEvery agent created afterwards logs one line:\n\n```\npreset-tool-filter: hid 4/9 tools (1214 of 2625 schema bytes, 46.2%, ~304 tokens) on \"reporter\" [browser=2, recording=2]\n```\n\nThat is the same measurement #7080 reports by hand, taken from the live agent's own schema surface. Set\n`dryRun: true` to rehearse the config and print `would hide …` without changing anything, and `report: false`\nto silence it.\n\n**Group membership is an assertion about your provider's catalog, not a runtime invariant.** A name that the\nagent cannot see is skipped (never thrown), and a group that matches nothing warns instead of failing the\nagent's creation — so a driver upgrade that renames a tool degrades to a warning plus a smaller saving, not to\nan unstartable deployment.\n\n### What the restriction does and does not change\n\n| | effect |\n|---|---|\n| model-facing tool schemas (the per-request prefix) | the group is gone |\n| the PTC SDK surface | the group is gone (same view drives both projections) |\n| dispatch | `UNKNOWN_TOOL` — the mask is real, not presentational |\n| other agents | unaffected (a restriction is per scope) |\n| the provider itself | still mounted; its catalog fetch and every capability you kept still work |\n\nThe provider still *registers* its catalog at startup (that is a one-time cost, not a per-request one); what the\nplugin removes is the per-request prefix and the model's ability to call the group.\n\n## Usage: per-preset allowlist (#5786)\n\n```yaml\nplugins:\n  \"@argszero/cordis-plugin-preset-tool-filter\":\n    allowlists:\n      minimal: [bash, str_replace_editor]\n```\n\nFor any agent whose composed preset is a key in `allowlists` (default\n`{ minimal: ['bash', 'pwsh', 'str_replace_editor'] }`), the plugin calls `tools.restrict({ allow })` on that\nagent's scope, masking every other inherited tool. On POSIX the `pwsh` row is disabled by the `minimal` preset,\nso each candidate is probed on the agent's scope first and dropped when absent — `restrict` never throws on a\nname the platform never mounted.\n\n`allow` and `deny` may both apply; restrictions **intersect**.\n\n### Configuration\n\n| option | type | default | description |\n|--------|------|---------|-------------|\n| `allowlists` | `Record<string, string[]>` | `{ minimal: ['bash', 'pwsh', 'str_replace_editor'] }` | Per-preset allowlist. Any agent whose composed preset is a key here gets `tools.restrict({ allow })`. |\n| `skipUncomposed` | `boolean` | `true` | Skip agents with no composed preset, so the **allowlist** half never affects a bare agent. The deny/group half is never gated by this. |\n| `groups` | `Record<string, { names?: string[]; prefix?: string }>` | `{}` | Named tool groups, matched by the union of exact `names` and the `prefix` namespace. |\n| `disableGroups` | `string[]` | `[]` | Groups removed from every agent's model-facing surface. |\n| `deny` | `string[]` | `[]` | Extra exact tool names removed from every agent. Names the agent cannot see are skipped. |\n| `denyPrefixes` | `string[]` | `[]` | Extra namespace prefixes removed from every agent. |\n| `report` | `boolean` | `true` | One stderr line per agent describing what was hidden and what it saves. |\n| `dryRun` | `boolean` | `false` | Measure and report, apply nothing. |\n\n## How it works\n\n- `restrict` is `ToolRuntime.restrict(filter)` and **requires a scoped context**. It is invoked on `agent.ctx`\n  (the agent's own scope), never the plugin root. An unscoped context produces one diagnostic line and no\n  throw.\n- A restriction masks what the scope **inherits** — the global layer and every ancestor scope layer — and never\n  the scope's own registrations (delegation reporting tools, structured-output tools).\n- Every configured candidate is resolved against `tools.schemas(agent)` **first**: `restrict` throws for a name\n  outside the inheritable set, and an anonymous throw during agent creation is exactly the failure mode this\n  plugin exists to avoid.\n- The reserved PTC presentation transport (`run_code`) may not be named in a restriction at all; a config that\n  names it is dropped silently.\n- An agent handle **is** its scope key (`scopeTarget(agent, agent)` in `packages/core/agent/src/index.ts`),\n  which is why the plugin passes the `Agent` itself to `tools.schemas()`/`get()`.\n\n### Verified seam\n\nMeasured against the published `@deepseek-ai/dsh-tools`, with a global-layer provider, an ancestor scope layer\nand the agent's own layer mounted together:\n\n| probe | result |\n|-------|--------|\n| `restrict({ deny: [<global-layer name>] })` from the agent scope | accepted; the name leaves `schemas()` and `get()`, and dispatches as `UNKNOWN_TOOL` |\n| `restrict({ deny: [<ancestor-scope name>] })` | accepted, same effect |\n| `restrict({ deny: [<agent-own-layer name>] })` | throws — own registrations are outside the filter |\n| `restrict({ deny: [<unknown name>] })` | throws, listing the known names |\n\nThe same suite runs green on `0.1.2-rc.1`, `0.1.3-alpha.2`, `0.1.5-rc.2` and `0.1.6-alpha.2` (30/30 each, with\nthe requested `dsh-tools` version installed for each run) — the seam is identical on all four lines.\n\n## Development\n\n```sh\nnpm install\nnpm test        # pretest builds lib/, then node --test runs the suite\n```\n\n`test/packaging.spec.mjs` guards two defect classes this series has shipped before: an undeclared runtime\nimport in the built artifact, and a peer range that silently excludes a line the code actually supports.\n\n## License\n\nMIT\n","readmeFilename":"README.md"}