@jayyuen66/dsh-plugin-shared 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -8
- package/{README.zh.md → README.zh-CN.md} +10 -8
- package/dist/card-apply.js +1 -1
- package/dist/http.js +1 -1
- package/dist/tool-events.js +1 -1
- package/dist/types/jsonl.d.ts +20 -1
- package/dist/types/record.d.ts +20 -1
- package/dist/types/tool-events.d.ts +14 -9
- package/package.json +39 -16
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @jayyuen66/dsh-plugin-shared
|
|
2
2
|
|
|
3
|
-
[English](./README.md) · [简体中文](./README.zh.md)
|
|
3
|
+
[English](./README.md) · [简体中文](./README.zh-CN.md)
|
|
4
4
|
|
|
5
5
|
## What this package is
|
|
6
6
|
|
|
@@ -33,7 +33,7 @@ End users do not install or enable it separately: any consumer package pulls it
|
|
|
33
33
|
| | |
|
|
34
34
|
| --- | --- |
|
|
35
35
|
| Node.js | `^22.19.0 \|\| >=24.0.0`, the same clause as the harness root |
|
|
36
|
-
| dsh | `>=0.2.
|
|
36
|
+
| dsh | `>= 0.2.1-alpha.1`, declared as an **optional** peer (this package needs no host at import time) |
|
|
37
37
|
| Module format | ESM only. `require()` works only where Node supports `require(esm)` |
|
|
38
38
|
| Tree shaking | `"sideEffects": false` |
|
|
39
39
|
|
|
@@ -97,11 +97,12 @@ The sonarjs entry point is resolved lazily, inside `definePluginConfig()`. Impor
|
|
|
97
97
|
|
|
98
98
|
## Dependencies
|
|
99
99
|
|
|
100
|
-
|
|
100
|
+
`dependencies` is empty; the seven `@deepseek-ai/*` packages appear only in `peerDependencies` and `devDependencies`. `test/publish-manifest.test.ts` enforces this — it is not a style preference:
|
|
101
101
|
|
|
102
|
-
- `@deepseek-ai/dsh-
|
|
103
|
-
-
|
|
104
|
-
-
|
|
102
|
+
- **Putting them in `dependencies` disables the consumer's host.** Consumers are dsh plugins installed into a profile (`nodeLinker: hoisted`, `autoInstallPeers: false`). Any host package listed under `dependencies` is written into the profile as a **real directory**, coexisting with the copy the host process already loaded. `@deepseek-ai/dsh-tools` keys its `TOOL_RUNTIME_SCHEDULER` with a plain `Symbol` (not `Symbol.for`), so symbol identity is per module instance: the host's `dsh-agent-loop` reads a `ToolRuntime` mounted from the duplicate using its own copy's symbol, gets `undefined`, and **every native tool call throws** `Cannot read properties of undefined (reading 'prepare')` — the whole profile loses its tools. This package's build, typecheck and tests all stay green right up to that point, so the invariant has to live in the manifest.
|
|
103
|
+
- `@deepseek-ai/dsh-output-retention` is a **value** import (`truncateEnd`) and is provided by the host at runtime, so it is a required peer.
|
|
104
|
+
- The other six are referenced only by the published `.d.ts`, so they are optional peers plus devDependencies: the peer keeps the type surface honest — `lib/job-outcome` used to take `ShellProcess` and `JobOutcome` from packages no consumer tree was required to contain, so with `skipLibCheck: true` a wrong `status` or `exitCode` produced no error at all and `jobOutcomeOf`'s return type collapsed to `any` — while devDependencies let this repository compile and test. Profiles never install peers, so no duplicate appears.
|
|
105
|
+
- Versions follow the host: exact pins throughout the `dsh-*` family and for `cordis`, matching what the host bundle itself declares so the same copy is reused rather than duplicated.
|
|
105
106
|
|
|
106
107
|
Known upstream limitation: `@deepseek-ai/dsh-llm`'s own declarations import `@deepseek-ai/dsh-attachment`, which it does not declare. It is reached transitively through `dsh-session`, so a consumer running with `skipLibCheck: false` sees `TS2307` from *that* package, not from this one.
|
|
107
108
|
|
|
@@ -110,7 +111,7 @@ Known upstream limitation: `@deepseek-ai/dsh-llm`'s own declarations import `@de
|
|
|
110
111
|
`main` and `exports` point at `dist/` only. There is no second, source-shaped manifest.
|
|
111
112
|
|
|
112
113
|
- `publishConfig` carries `access` and `registry` and nothing else. Putting `main`/`exports` there was wrong: pnpm merges those onto the top level when publishing and npm does not, so publishing with npm shipped a package whose fourteen entry points named files that were not in the tarball.
|
|
113
|
-
- `files` is `icon.svg`, `dist`, `config/*.json`, `README.zh.md`. npm always adds `package.json`, `LICENSE` and `README.md`; `README.zh.md` is listed explicitly so the Chinese copy ships too.
|
|
114
|
+
- `files` is `icon.svg`, `dist`, `config/*.json`, `README.zh-CN.md`. npm always adds `package.json`, `LICENSE` and `README.md`; `README.zh-CN.md` is listed explicitly so the Chinese copy ships too.
|
|
114
115
|
- `build` starts by deleting `dist/`, because `files` takes the whole directory and a renamed facet would otherwise keep publishing its orphan artifact.
|
|
115
116
|
- `prepare` runs `build`. It does not run when a consumer installs from the registry; it exists so packing, publishing and workspace links always ship a fresh `dist/`.
|
|
116
117
|
- Node throws `ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING` for `.ts` inside `node_modules`, which is exactly where a published package is loaded from, so an artifact is mandatory. The barrel's declaration file is generated by `build-host.mjs` because source-form `export * from "./http.ts"` specifiers are not resolvable by consumers.
|
|
@@ -130,8 +131,9 @@ Known upstream limitation: `@deepseek-ai/dsh-llm`'s own declarations import `@de
|
|
|
130
131
|
## Quality gates
|
|
131
132
|
|
|
132
133
|
- `npm run check` = typecheck → lint → build → test (coverage thresholds 100 for lines, statements, functions and branches) → format check.
|
|
133
|
-
- `test/publish-manifest.test.ts` is the release-shape gate: every `exports` target must be inside what `files` actually ships, entries must point at build output rather than source, and `publishConfig` must not carry entry fields. One set of "internal reference" patterns scans **both planes**: **artifacts** must contain no absolute paths, home-directory references, dates, or sibling-package identifiers; **source and docs** must contain no dates and no "this round / next round" wording that only made sense during collaboration. The artifact pass cannot see comments, so the source pass is what keeps that cleanup from being a one-off.
|
|
134
|
+
- `test/publish-manifest.test.ts` is the release-shape gate: every `exports` target must be inside what `files` actually ships, entries must point at build output rather than source, and `publishConfig` must not carry entry fields. It also pins **facet/`exports` parity in both directions**: every `lib/*.ts` needs a matching subpath (a missing one means that facet is not importable by any of the nine consumer packages, while the build — which follows the builder's own entries array — stays green), and no dangling subpath may outlive its facet. `./lib/canonicalize-region-paths` is the one facet that lives in `exports` but not in the barrel, and that subpath is load-bearing: nine sibling packages' `build-*.mjs` import it, so removing it breaks all of their builds at once. One set of "internal reference" patterns scans **both planes**: **artifacts** must contain no absolute paths, home-directory references, dates, or sibling-package identifiers; **source and docs** must contain no dates and no "this round / next round" wording that only made sense during collaboration. The artifact pass cannot see comments, so the source pass is what keeps that cleanup from being a one-off.
|
|
134
135
|
- `test/build-host.test.ts` compares the bytes on disk against an in-memory build, so editing `lib/*.ts` without rebuilding goes red.
|
|
136
|
+
- `test/setup-logs.ts` (a vitest setup file) is the ledger for runtime logs: it takes over `console.*`, so the two `console.error` calls in `lib/` neither leak into the test report (that stack trace is pure noise) nor go unclaimed — every `[shared/*]` line must match a template fragment in `test/log-templates.ts` or the run goes red. Do not substitute vitest's `silent`: it only hides, and a newly added log would still go unnoticed.
|
|
135
137
|
- `.github/workflows/ci.yml` runs the gate on push and pull request across a Node matrix and on Windows; the install matrix there additionally packs, installs into a clean directory with no flags, imports every subpath and type-checks a consumer probe.
|
|
136
138
|
|
|
137
139
|
## FAQ
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @jayyuen66/dsh-plugin-shared
|
|
2
2
|
|
|
3
|
-
[English](./README.md) · [简体中文](./README.zh.md)
|
|
3
|
+
[English](./README.md) · [简体中文](./README.zh-CN.md)
|
|
4
4
|
|
|
5
5
|
## 这个包是什么
|
|
6
6
|
|
|
@@ -33,7 +33,7 @@ import { readBody, sendJson } from "@jayyuen66/dsh-plugin-shared/lib/http";
|
|
|
33
33
|
| | |
|
|
34
34
|
| --- | --- |
|
|
35
35
|
| Node.js | `^22.19.0 \|\| >=24.0.0`,与 harness 根同款 |
|
|
36
|
-
| dsh | `>=0.2.
|
|
36
|
+
| dsh | `>= 0.2.1-alpha.1`,声明为 **optional** peer(本包 import 时不需要宿主在场) |
|
|
37
37
|
| 模块形态 | 仅 ESM。`require()` 只在支持 `require(esm)` 的 Node 上可用 |
|
|
38
38
|
| Tree shaking | `"sideEffects": false` |
|
|
39
39
|
|
|
@@ -97,11 +97,12 @@ sonarjs 的入口是**惰性解析**的,发生在 `definePluginConfig()` 内
|
|
|
97
97
|
|
|
98
98
|
## 依赖
|
|
99
99
|
|
|
100
|
-
|
|
100
|
+
`dependencies` 是空的;七枚 `@deepseek-ai/*` 只出现在 `peerDependencies` 与 `devDependencies` 里。这条由 `test/publish-manifest.test.ts` 钉住,不是风格偏好:
|
|
101
101
|
|
|
102
|
-
- `@deepseek-ai/dsh-
|
|
103
|
-
-
|
|
104
|
-
-
|
|
102
|
+
- **写进 `dependencies` 会让消费方的宿主瘫掉。** 消费方是 dsh 插件,被装进 profile(`nodeLinker: hoisted`、`autoInstallPeers: false`)。宿主包只要出现在 `dependencies`,pnpm 就在 profile 里把它们落成**真实目录**,与宿主进程里已有的那份并存。而 `@deepseek-ai/dsh-tools` 的 `TOOL_RUNTIME_SCHEDULER` 是普通 `Symbol`(不是 `Symbol.for`),符号身份按模块实例计算——宿主 `dsh-agent-loop` 拿自己那份的 Symbol 去读由副本挂载出来的 `ToolRuntime`,得到 `undefined`,于是**每一次原生工具调用都抛** `Cannot read properties of undefined (reading 'prepare')`,整个 profile 的工具一起失效。本包的构建、类型检查与测试在这之前全部照绿,所以判据只能落在 manifest 面。
|
|
103
|
+
- `@deepseek-ai/dsh-output-retention` 是**值导入**(`truncateEnd` 用它),运行期由宿主提供,因此是非 optional 的 peer。
|
|
104
|
+
- 其余六枚只被发布出去的 `.d.ts` 引用,声明成 optional peer 加 dev:peer 保住类型面不静默降级——`lib/job-outcome` 的 `ShellProcess`/`JobOutcome` 曾经来自消费方根本不保证存在的包,于是在 `skipLibCheck: true` 下写错 `status` 或 `exitCode` 一条都不报,返回值类型直接塌成 `any`;dev 让本包自己的编译与测试解析得到。profile 不装 peer,所以不产生副本。
|
|
105
|
+
- 版本口径跟着宿主走:`dsh-*` 家族内一律精确钉,`cordis` 也精确钉,与宿主本体自己声明的一致,这样是复用同一份而不是装出第二份。
|
|
105
106
|
|
|
106
107
|
已知的上游限制:`@deepseek-ai/dsh-llm` 的声明文件引用了它自己没声明的 `@deepseek-ai/dsh-attachment`。它经 `dsh-session` 传递进来,所以开 `skipLibCheck: false` 的消费方会看到来自**那个包**的 `TS2307`,不是来自本包。
|
|
107
108
|
|
|
@@ -110,7 +111,7 @@ sonarjs 的入口是**惰性解析**的,发生在 `definePluginConfig()` 内
|
|
|
110
111
|
`main` 与 `exports` 只指 `dist/`,不存在第二套源码形态的 manifest。
|
|
111
112
|
|
|
112
113
|
- `publishConfig` 只带 `access` 和 `registry`。把 `main`/`exports` 放在那里是错的:pnpm 发布时会把它们合并到顶层,npm 不会——用 npm 发出去的包,十四个入口指向的都是 tarball 里不存在的文件。
|
|
113
|
-
- `files` 是 `icon.svg`、`dist`、`config/*.json`、`README.zh.md`。npm 永远附带 `package.json`、`LICENSE` 和 `README.md`;`README.zh.md` 显式列出才会一起发。
|
|
114
|
+
- `files` 是 `icon.svg`、`dist`、`config/*.json`、`README.zh-CN.md`。npm 永远附带 `package.json`、`LICENSE` 和 `README.md`;`README.zh-CN.md` 显式列出才会一起发。
|
|
114
115
|
- `build` 第一步删除 `dist/`:`files` 整目录收,切面一旦改名,上一代孤儿产物会继续被发出去。
|
|
115
116
|
- `prepare` 挂 `build`。消费方从 registry 安装时它不执行;它存在是为了让打包、发布与 workspace link 拿到的都是新鲜 `dist/`。
|
|
116
117
|
- 必须有编译产物:Node 对 `node_modules` 里的 `.ts` 直接抛 `ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING`,而发出去的包正是躺在那里被载入的。barrel 的声明文件由 `build-host.mjs` 生成,因为源码形态的 `export * from "./http.ts"` 消费方解析不到。
|
|
@@ -130,8 +131,9 @@ sonarjs 的入口是**惰性解析**的,发生在 `definePluginConfig()` 内
|
|
|
130
131
|
## 质量门
|
|
131
132
|
|
|
132
133
|
- `npm run check` = typecheck → lint → build → test(覆盖率 lines/statements/functions/branches 四阈值 100)→ fmt:check。
|
|
133
|
-
- `test/publish-manifest.test.ts` 是发布形态门:`exports` 的每个目标都必须在 `files` 真会发出去的范围内;入口必须指编译产物而不是源码;`publishConfig`
|
|
134
|
+
- `test/publish-manifest.test.ts` 是发布形态门:`exports` 的每个目标都必须在 `files` 真会发出去的范围内;入口必须指编译产物而不是源码;`publishConfig` 不得携带入口字段。它还钉着**切面与 `exports` 的双向 parity**:每个 `lib/*.ts` 都要有对应子路径(少一条 = 该切面对 9 个消费包不可 import,而构建走的是 builder 自己的 entries 数组、不会报错),反过来也不许留悬空子路径。`./lib/canonicalize-region-paths` 正是"在 `exports` 里、不在 barrel 里"的那一枚,且这条子路径是承重的(9 个兄弟包的 `build-*.mjs` 靠它),删它会一次性打断全部构建。同一组"内部指涉"判据扫两个平面:**产物**不得出现绝对路径、家目录指涉、日期或兄弟包标识符;**源码与文档**不得出现日期与"这一轮/下一轮"这类只在协作当时成立的措辞——产物面那条看不见注释,所以必须单独扫源码,否则清理只是一次性动作、没有回归保护。
|
|
134
135
|
- `test/build-host.test.ts` 里的指纹用例比较磁盘产物与内存构建的字节,所以改了 `lib/*.ts` 忘了重建会直接红。
|
|
136
|
+
- `test/setup-logs.ts`(vitest setupFile)是算子日志的账本:它接管 `console.*`,于是 `lib/` 里那两行 `console.error` 既不会漏进测试报告(那条栈是纯噪点),又必须被用例认领——每条 `[shared/*]` 日志都要对得上 `test/log-templates.ts` 里的模板片段,否则当场红。别拿 vitest 的 `silent` 顶替:那只是不显示,新飘出来的日志照样没人看。
|
|
135
137
|
- `.github/workflows/ci.yml` 在 push 与 PR 上跨 Node 版本矩阵、并在 Windows 上跑同一道门;那边的安装矩阵还会打包、在干净目录里不带任何 flag 安装、逐子路径 import、并对消费方探针文件做类型检查。
|
|
136
138
|
|
|
137
139
|
## 常见问题
|
package/dist/card-apply.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
function e(e,t,n){let r=globalThis;
|
|
1
|
+
function e(e,t,n){let r=globalThis;if(r[t]===!0)return!1;r[t]=!0;try{e.effect(()=>()=>{r[t]=void 0},n)}catch(e){throw r[t]=void 0,e}return!0}export{e as claimApply};
|
package/dist/http.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
function e(e,t,n){if(e.headersSent||e.writableEnded)return;let r=JSON.stringify(n);e.writeHead(t,{"content-type":`application/json; charset=utf-8`,"cache-control":`no-store`}),e.end(r)}function t(e,t){let n=e.headers?.[t],r=Array.isArray(n)?n[0]:n;return typeof r==`string`?r:``}function n(e){let n=t(e,`sec-fetch-site`);return n!==``&&n!==`same-origin`&&n!==`none`}function r(e,t){try{return new URL(e.url??``,`http://localhost`).searchParams.get(t)}catch{return null}}function i(e,n,r){let i=t(e,r);return i!==``&&i===n}const a={"too-large":{status:413,error:`request body too large`},aborted:{status:400,error:`request body unreadable`},"bad-budget":{status:500,error:`request body limit misconfigured`}};function o(e){return Buffer.isBuffer(e)?e:Buffer.from(String(e),`utf8`)}async function s(e,n){if(!Number.isFinite(n)||n<0)return{ok:!1,reason:`bad-budget`};let r=Number(t(e,`content-length`));if(Number.isFinite(r)&&r>n)return{ok:!1,reason:`too-large`};let i=[],a=0;try{for await(let t of e){let e=o(t);if(a+=e.length,a>n)return{ok:!1,reason:`too-large`};i.push(e)}}catch{return{ok:!1,reason:`aborted`}}return{ok:!0,text:Buffer.concat(i).toString(`utf8`)}}const c=`cross-origin request rejected`;async function l(t,r,o){if(n(t))return e(r,403,{ok:!1,error:c}),null;if(o.csrf!==void 0&&!i(t,o.csrf.token,o.csrf.headerName))return e(r,403,{ok:!1,error:`invalid csrf token`}),null;let l=await s(t,o.maxBytes);if(l.ok)return l.text;let u=a[l.reason];return e(r,u.status,{ok:!1,error:u.error}),null}export{c as CROSS_ORIGIN_TEXT,i as checkCsrf,l as guardBody,n as isCrossOrigin,r as queryParam,s as readBody,e as sendJson};
|
|
1
|
+
function e(e,t,n){if(e.headersSent||e.writableEnded)return;let r=JSON.stringify(n);e.writeHead(t,{"content-type":`application/json; charset=utf-8`,"cache-control":`no-store`}),e.end(r)}function t(e,t){let n=e.headers?.[t.toLowerCase()],r=Array.isArray(n)?n[0]:n;return typeof r==`string`?r:``}function n(e){let n=t(e,`sec-fetch-site`);return n!==``&&n!==`same-origin`&&n!==`none`}function r(e,t){try{return new URL(e.url??``,`http://localhost`).searchParams.get(t)}catch{return null}}function i(e,n,r){let i=t(e,r);return i!==``&&i===n}const a={"too-large":{status:413,error:`request body too large`},aborted:{status:400,error:`request body unreadable`},"bad-budget":{status:500,error:`request body limit misconfigured`}};function o(e){return Buffer.isBuffer(e)?e:Buffer.from(String(e),`utf8`)}async function s(e,n){if(!Number.isFinite(n)||n<0)return{ok:!1,reason:`bad-budget`};let r=Number(t(e,`content-length`));if(Number.isFinite(r)&&r>n)return{ok:!1,reason:`too-large`};let i=[],a=0;try{for await(let t of e){let e=o(t);if(a+=e.length,a>n)return{ok:!1,reason:`too-large`};i.push(e)}}catch{return{ok:!1,reason:`aborted`}}return{ok:!0,text:Buffer.concat(i).toString(`utf8`)}}const c=`cross-origin request rejected`;async function l(t,r,o){if(n(t))return e(r,403,{ok:!1,error:c}),null;if(o.csrf!==void 0&&!i(t,o.csrf.token,o.csrf.headerName))return e(r,403,{ok:!1,error:`invalid csrf token`}),null;let l=await s(t,o.maxBytes);if(l.ok)return l.text;let u=a[l.reason];return e(r,u.status,{ok:!1,error:u.error}),null}export{c as CROSS_ORIGIN_TEXT,i as checkCsrf,l as guardBody,n as isCrossOrigin,r as queryParam,s as readBody,e as sendJson};
|
package/dist/tool-events.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{isRecord as e}from"./record.js";function t(t){return e(t)&&t.isError===!0}function n(e){return e===!0}function r(t){return e(t.data)}function i(e){return typeof e==`string`&&e.length>0?e:void 0}function a(t){if(typeof t==`string`)try{let n=JSON.parse(t);return e(n)?n:{}}catch{return{}}return e(t)?t:{}}function
|
|
1
|
+
import{isRecord as e}from"./record.js";function t(t){return e(t)&&t.isError===!0}function n(e){return e===!0}function r(t){return e(t.data)}function i(e){return typeof e==`string`&&e.length>0?e:void 0}function a(e){return typeof e==`string`&&e.length>0?e:void 0}function o(t){if(typeof t==`string`)try{let n=JSON.parse(t);return e(n)?n:{}}catch{return{}}return e(t)?t:{}}function s(e){if(typeof e==`string`)try{return JSON.parse(e),!1}catch{return!0}return!1}function c(e,t){let n;if(!r(e))return n;if(e.type===`tool/call`){let r=a(e.data.name);r!==void 0&&(n={name:r,callId:i(e.data.callId),arguments:o(e.data.arguments),badArguments:s(e.data.arguments),seq:t})}else if(e.type===`tool/ptc-dispatch-start`){let r=i(e.data.subCallId),c=a(e.data.name);r!==void 0&&c!==void 0&&(n={name:c,callId:r,arguments:o(e.data.arguments),badArguments:s(e.data.arguments),seq:t})}return n}function l(a,o){let s;if(!r(a))return s;if(a.type===`tool/result`){let n=a.data.message,r=e(n)?n.source:void 0;s={callId:e(r)?i(r.callId):void 0,isError:t(n),seq:o}}else if(a.type===`tool/ptc-dispatch`){let e=i(a.data.subCallId);e!==void 0&&(s={callId:e,isError:n(a.data.isError),seq:o})}return s}function u(e,t){return{call:c(e,t),result:l(e,t)}}function d(t){let n=[],r=[];for(let[i,a]of t.entries())if(e(a)){let{call:e,result:t}=u(a,i);e!==void 0&&n.push(e),t!==void 0&&r.push(t)}return{calls:n,results:r}}function f(e,t){let n=e[t];return typeof n==`string`?n:void 0}function p(e){let t=e.arguments;return e.name===`str_replace_editor`?t.command===`view`?{kind:`read-view`,path:f(t,`path`)}:{kind:`write`,path:f(t,`path`)}:{kind:`write`,path:f(t,`file_path`)}}export{p as editPathOf,o as parseToolArguments,d as scanToolEvents,s as toolArgumentsBad,u as toolEventRowsOf};
|
package/dist/types/jsonl.d.ts
CHANGED
|
@@ -1,8 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 反复保留后半行段直到 UTF-8 字节数 ≤ `maxBytes`;行数 ≤2 时停手(单条超限行原样保留,
|
|
3
3
|
* 防死循环也防清空文件)。未超限直接返回入参**同一引用**,调用方可据此跳过写盘。
|
|
4
|
+
*
|
|
5
|
+
* **配额必须是有限数**(这一档由调用方保证,本函数不代为归一):
|
|
6
|
+
* - `0` 与负数**照样收缩**到 ≤2 行为止——「0 = 永不截断」是某消费方在**调用前**
|
|
7
|
+
* `maxBytes > 0` 那道触发策略的承诺,不是本函数的语义(见它自己的 jsonl-fuse 头注)。
|
|
8
|
+
* 把 0 当成「不裁」会把这个承诺写成一个假象。
|
|
9
|
+
* - `NaN` 与 `+Infinity` 让 `字节数 > maxBytes` 恒假 ⇒ 循环一次都不进,整份原样返回,
|
|
10
|
+
* 即**静默关掉收缩**。现存两个消费方都不会把这种值传进来:一个用固定常量作默认实参,
|
|
11
|
+
另一个在调用前就把非有限值与负数归一成 0。但那道归一在**消费方**,本包并不强制——
|
|
12
|
+
新增消费方时没有任何东西会提醒它。
|
|
13
|
+
*
|
|
14
|
+
* 为什么不改成 http.readBody 那样的 bad-budget 拒读:那边拒读是因为不设限等于放开内存
|
|
15
|
+
* 上限(请求体是攻击面),这边不设限只是「这次不裁」。抛错会被两个消费方各自的 catch
|
|
16
|
+
* 接走(一个上抛、一个内部吞),等于把一条清晰的预算错误变成一次静默跳过,排查方向
|
|
17
|
+
* 反而更差。形状不同的同一种错,各按各的失败模型处理。
|
|
18
|
+
*
|
|
19
|
+
* 两个消费方的具体调用点与配额归一位置记在 test/jsonl.test.ts 的同名用例里——本文件头
|
|
20
|
+
* 不能出现兄弟包标识符:那些名字会随 JSDoc 一起被 tsc 抄进 dist/types/*.d.ts 发出去,
|
|
21
|
+
* 让装了这个包的消费方以为存在一个并不存在的依赖(test/publish-manifest.test.ts 会红)。
|
|
22
|
+
*
|
|
4
23
|
* @param text 整份 JSONL 文本
|
|
5
|
-
* @param maxBytes
|
|
24
|
+
* @param maxBytes 字节上限;调用方须保证是有限数
|
|
6
25
|
* @returns 收缩后的文本
|
|
7
26
|
*/
|
|
8
27
|
export declare function shrinkJsonlTail(text: string, maxBytes: number): string;
|
package/dist/types/record.d.ts
CHANGED
|
@@ -1,4 +1,23 @@
|
|
|
1
1
|
/** 对象类型守卫:非 `null` 的 `object` 且非数组(`Record` 索引签名投影的基础)。 */
|
|
2
2
|
export declare function isRecord(value: unknown): value is Record<string, unknown>;
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* 从 `unknown` 投影单个字段;非 `Record` → `undefined`。
|
|
5
|
+
*
|
|
6
|
+
* ⚠ **取值走原型链,这是承重的,不是疏忽**:`value[key]` 不做 `hasOwn` 判定,所以原型上的
|
|
7
|
+
* 方法照样取得到。消费方正靠这一条做**能力探测**——cordis 服务对象的方法挂在原型上、不是
|
|
8
|
+
* 自有键,形如 `typeof fieldOf(settings, "describe") === "function"`、
|
|
9
|
+
* `hasMethods(fieldOf(value, "timer"), ["timeout"])`、`typeof fieldOf(session,
|
|
10
|
+
* "snapshotEvents") === "function"`(三处消费包的调用点与行号记在 test/record.test.ts)。
|
|
11
|
+
* 改成 `Object.hasOwn` 会让上面几处**一律判否**,那些包随即认不出合法的 cordis 服务——
|
|
12
|
+
* 而这类误判的表现是「能力探测静默返回 false」,不是报错,极难往这里归因。
|
|
13
|
+
*
|
|
14
|
+
* 代价是明写在这里:键名取到的是原型成员时(`fieldOf({}, "__proto__")` 交回
|
|
15
|
+
* `Object.prototype`、`fieldOf({}, "constructor")` 交回 `Object`)。这是本函数的取舍,
|
|
16
|
+
* 不是让消费方猜的属性——现存调用点的键名全是字面量,没有一处来自数据。
|
|
17
|
+
* test/record.test.ts 两侧都钉住了:原型方法取得到(承重面),自有键优先于原型键。
|
|
18
|
+
*
|
|
19
|
+
* ⚠ 本段刻意不点名兄弟包:**声明上的 JSDoc 会被 tsc 抄进 dist/types/record.d.ts 一起
|
|
20
|
+
* 发出去**,包名会让装了这个包的消费方以为存在一个并不存在的依赖
|
|
21
|
+
*(test/publish-manifest.test.ts 的产物面判据会红)。要写具体调用点就写在 test/ 里。
|
|
22
|
+
*/
|
|
4
23
|
export declare function fieldOf(value: unknown, key: string): unknown;
|
|
@@ -8,17 +8,22 @@ export type { PtcDispatchEventData, PtcDispatchStartEventData } from "@deepseek-
|
|
|
8
8
|
*/
|
|
9
9
|
export interface ToolCallRecord {
|
|
10
10
|
/**
|
|
11
|
-
* 调用名(edit/write/str_replace_editor/read/grep
|
|
11
|
+
* 调用名(edit/write/str_replace_editor/read/grep/…)——本模块的**分桶键**,必选。
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
* packages/core/
|
|
15
|
-
*
|
|
16
|
-
*
|
|
13
|
+
* 官方把它声明成必选 `string`(`tool/call` 见 packages/core/session/src/types.ts:361,
|
|
14
|
+
* PtcDispatchStartEventData.name 见 packages/core/tools/src/types.ts:15),但运行期拦不住
|
|
15
|
+
* (依据见 usableName)。所以可空性在**扫描层**就处理掉了,不带到类型面:callRecordOf 遇到
|
|
16
|
+
* 缺可用 name 的调用类事件整条跳过,与 hasObjectData 同一口径。
|
|
17
17
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
18
|
+
* 为什么不把过滤留给消费方:下游分类全建立在 name 上——editPathOf 的 str_replace_editor
|
|
19
|
+
* 只读判定、门禁侧的 EDIT_TOOLS 与 refSearchTools 名单——而 editPathOf 的兜底分支会把任何
|
|
20
|
+
* 非 str_replace_editor 的调用一律判成 write。无名调用一旦进了台账,只要它的 arguments
|
|
21
|
+
* 里有 file_path 就算成写操作。现在是靠三个消费方各自记得先判一次才没出事——那是同一条
|
|
22
|
+
* 不变式的三份手抄,漏一份就出事。
|
|
23
|
+
*
|
|
24
|
+
* 与 callId 的差别是**为什么一个必选、一个可空**:callId 由 usableCallId 兜底,且它有
|
|
25
|
+
* nc:<seq> 兜底席位(消费方的 noCallId 桶专门收那种行),缺了不影响归类;name 一旦缺了
|
|
26
|
+
* 就没法分桶、没有兜底席位,只能在入口滤掉。
|
|
22
27
|
*/
|
|
23
28
|
readonly name: string;
|
|
24
29
|
/** callId:tool/result 串联键;缺失时由调用方按 seq 造 `nc:` 键。 */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jayyuen66/dsh-plugin-shared",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "dsh 自研插件的共享工具库:tool 事件流记账扫描 + webServer HTTP 样板(sendJson/CORS/CSRF/body 限制)+ 卡片 apply 骨架 + 项目键派生 + 代理对安全截断。作为库包被 import,不由用户启用",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cordis",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"icon.svg",
|
|
25
25
|
"dist",
|
|
26
26
|
"config/*.json",
|
|
27
|
-
"README.zh.md"
|
|
27
|
+
"README.zh-CN.md"
|
|
28
28
|
],
|
|
29
29
|
"type": "module",
|
|
30
30
|
"sideEffects": false,
|
|
@@ -103,19 +103,17 @@
|
|
|
103
103
|
"access": "public",
|
|
104
104
|
"registry": "https://registry.npmjs.org"
|
|
105
105
|
},
|
|
106
|
-
"dependencies": {
|
|
107
|
-
"@deepseek-ai/cordis": "~4.0.4",
|
|
108
|
-
"@deepseek-ai/dsh-client-locale": "0.2.0-rc.2",
|
|
109
|
-
"@deepseek-ai/dsh-jobs": "0.2.0-rc.2",
|
|
110
|
-
"@deepseek-ai/dsh-output-retention": "0.2.0-rc.2",
|
|
111
|
-
"@deepseek-ai/dsh-session": "0.2.0-rc.2",
|
|
112
|
-
"@deepseek-ai/dsh-shell": "0.2.0-rc.2",
|
|
113
|
-
"@deepseek-ai/dsh-tools": "0.2.0-rc.2"
|
|
114
|
-
},
|
|
115
106
|
"devDependencies": {
|
|
116
|
-
"@deepseek-ai/
|
|
117
|
-
"@deepseek-ai/dsh-
|
|
118
|
-
"@deepseek-ai/dsh-
|
|
107
|
+
"@deepseek-ai/cordis": "4.0.5-alpha.1",
|
|
108
|
+
"@deepseek-ai/dsh-brand": "0.2.1-alpha.1",
|
|
109
|
+
"@deepseek-ai/dsh-client-locale": "0.2.1-alpha.1",
|
|
110
|
+
"@deepseek-ai/dsh-jobs": "0.2.1-alpha.1",
|
|
111
|
+
"@deepseek-ai/dsh-llm": "0.2.1-alpha.1",
|
|
112
|
+
"@deepseek-ai/dsh-output-retention": "0.2.1-alpha.1",
|
|
113
|
+
"@deepseek-ai/dsh-scope": "0.2.1-alpha.1",
|
|
114
|
+
"@deepseek-ai/dsh-session": "0.2.1-alpha.1",
|
|
115
|
+
"@deepseek-ai/dsh-shell": "0.2.1-alpha.1",
|
|
116
|
+
"@deepseek-ai/dsh-tools": "0.2.1-alpha.1",
|
|
119
117
|
"@types/node": "26.6.3",
|
|
120
118
|
"@vitest/coverage-v8": "5.0.2",
|
|
121
119
|
"eslint-plugin-sonarjs": "4.2.2",
|
|
@@ -128,16 +126,41 @@
|
|
|
128
126
|
"vitest": "5.0.2"
|
|
129
127
|
},
|
|
130
128
|
"peerDependencies": {
|
|
131
|
-
"@deepseek-ai/
|
|
129
|
+
"@deepseek-ai/cordis": "4.0.5-alpha.1",
|
|
130
|
+
"@deepseek-ai/dsh": "^0.2.1-alpha.1",
|
|
131
|
+
"@deepseek-ai/dsh-client-locale": "0.2.1-alpha.1",
|
|
132
|
+
"@deepseek-ai/dsh-jobs": "0.2.1-alpha.1",
|
|
133
|
+
"@deepseek-ai/dsh-output-retention": "0.2.1-alpha.1",
|
|
134
|
+
"@deepseek-ai/dsh-session": "0.2.1-alpha.1",
|
|
135
|
+
"@deepseek-ai/dsh-shell": "0.2.1-alpha.1",
|
|
136
|
+
"@deepseek-ai/dsh-tools": "0.2.1-alpha.1"
|
|
132
137
|
},
|
|
133
138
|
"peerDependenciesMeta": {
|
|
139
|
+
"@deepseek-ai/cordis": {
|
|
140
|
+
"optional": true
|
|
141
|
+
},
|
|
134
142
|
"@deepseek-ai/dsh": {
|
|
135
143
|
"optional": true
|
|
144
|
+
},
|
|
145
|
+
"@deepseek-ai/dsh-client-locale": {
|
|
146
|
+
"optional": true
|
|
147
|
+
},
|
|
148
|
+
"@deepseek-ai/dsh-jobs": {
|
|
149
|
+
"optional": true
|
|
150
|
+
},
|
|
151
|
+
"@deepseek-ai/dsh-session": {
|
|
152
|
+
"optional": true
|
|
153
|
+
},
|
|
154
|
+
"@deepseek-ai/dsh-shell": {
|
|
155
|
+
"optional": true
|
|
156
|
+
},
|
|
157
|
+
"@deepseek-ai/dsh-tools": {
|
|
158
|
+
"optional": true
|
|
136
159
|
}
|
|
137
160
|
},
|
|
138
161
|
"icon": "icon.svg",
|
|
139
162
|
"engines": {
|
|
140
|
-
"dsh": ">=0.2.
|
|
163
|
+
"dsh": ">=0.2.1-alpha.1",
|
|
141
164
|
"node": "^22.19.0 || >=24.0.0"
|
|
142
165
|
},
|
|
143
166
|
"scripts": {
|