{"_id":"@buckeyestudio/toh-output-retention","name":"@buckeyestudio/toh-output-retention","dist-tags":{"next":"0.1.1-rc.2","latest":"0.1.1-rc.2"},"versions":{"0.1.1-rc.2":{"name":"@buckeyestudio/toh-output-retention","description":"Zero-dependency bounded-retention primitive: ItemRetainer/TextRetainer + neutral notice helpers (what did we keep, what did we omit)","version":"0.1.1-rc.2","publishConfig":{"access":"public"},"repository":{"type":"git","url":"git+https://github.com/dustinwloring1988/theopen-harness.git","directory":"packages/util/output-retention"},"type":"module","main":"lib/index.js","types":"lib/types/index.d.ts","exports":{".":{"types":"./lib/types/index.d.ts","default":"./lib/index.js"},"./invariant":{"types":"./lib/types/invariant.d.ts","default":"./lib/invariant.js"},"./src/*":"./src/*","./package.json":"./package.json"},"license":"MIT","author":{"name":"buckeyestudio"},"peerDependencies":{"@buckeyestudio/toh-invariants":"^0.1.1-rc.2","@buckeyestudio/cordis":"^4.0.1"},"devDependencies":{"@buckeyestudio/toh-invariants":"^0.1.1-rc.2","@buckeyestudio/cordis":"^4.0.1"},"_id":"@buckeyestudio/toh-output-retention@0.1.1-rc.2","bugs":{"url":"https://github.com/dustinwloring1988/theopen-harness/issues"},"homepage":"https://github.com/dustinwloring1988/theopen-harness#readme","_integrity":"sha512-3S7fOOKDxlwvE0AbuJrZoktDKkOpo/xILOEJ4yjHUxP0OyNVawF/N7R9eYzVhSFDSTjXxFECcn8gyNs6E8SSQQ==","_resolved":"/home/runner/work/theopen-harness/theopen-harness/dist/npm/buckeyestudio-toh-output-retention-0.1.1-rc.2.tgz","_from":"file:/home/runner/work/theopen-harness/theopen-harness/dist/npm/buckeyestudio-toh-output-retention-0.1.1-rc.2.tgz","_nodeVersion":"24.19.0","_npmVersion":"11.17.0","dist":{"integrity":"sha512-3S7fOOKDxlwvE0AbuJrZoktDKkOpo/xILOEJ4yjHUxP0OyNVawF/N7R9eYzVhSFDSTjXxFECcn8gyNs6E8SSQQ==","shasum":"3cfe57fae868281f9f261e84e9d7b1e9dfd44af8","tarball":"https://registry.npmjs.org/@buckeyestudio/toh-output-retention/-/toh-output-retention-0.1.1-rc.2.tgz","fileCount":9,"unpackedSize":38155,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIDgngG6YDDNf6fs0mXfDIWPdtm30IChmKoRmfk5Auzk6AiAcQUe452taxJAl9Vm4JlnL0J5ly4ZD8HAzSaNAGA3BCQ=="}]},"_npmUser":{"name":"buckeyestudio","email":"dustinwloring1988@gmail.com"},"directories":{},"maintainers":[{"name":"buckeyestudio","email":"dustinwloring1988@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/toh-output-retention_0.1.1-rc.2_1787489072785_0.5120808131567693"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-23T12:44:32.615Z","0.1.1-rc.2":"2026-08-23T12:44:32.921Z","modified":"2026-08-23T12:44:33.116Z"},"maintainers":[{"name":"buckeyestudio","email":"dustinwloring1988@gmail.com"}],"description":"Zero-dependency bounded-retention primitive: ItemRetainer/TextRetainer + neutral notice helpers (what did we keep, what did we omit)","homepage":"https://github.com/dustinwloring1988/theopen-harness#readme","repository":{"type":"git","url":"git+https://github.com/dustinwloring1988/theopen-harness.git","directory":"packages/util/output-retention"},"author":{"name":"buckeyestudio"},"bugs":{"url":"https://github.com/dustinwloring1988/theopen-harness/issues"},"license":"MIT","readme":"# toh-output-retention\n\n[English](README.md) | 中文\n\n一个轻依赖的**保留**库：为必须限制返回上下文量的工具提供有界的面向模型输出。调用方将项或文本分片送入有界对象，然后取回保留的内容和精确的省略元数据。\n\n该库**只**负责这个机制问题：*「我们保留了什么，又省略了什么？」*。工具专用代码保留其业务语义：文件分组、行号、退出码、提供方错误状态、每行预览截断、spill 文件以及面向模型的文案。这就是 [Agent Note](../../../.agents/notes/implemented/architecture/2026-07-06-tool-result-retention-library.zh.md) 划定的边界。\n\n它是**库，而非服务或插件**：没有 `ctx`，不注册任何内容，不发出任何事件。状态只存在于每个 retainer（一次累积）中，绝不跨调用。工具包直接导入它。\n\n## 对外接口\n\n```ts\nimport {\n  ItemRetainer, TextRetainer,\n  describeOmitted, formatRetentionNotice,\n} from '@buckeyestudio/toh-output-retention'\nimport type {\n  Omitted, PushDecision, RetainedItems, RetainedText,\n  ItemRetentionStrategy, TextRetentionStrategy, RetentionNotice,\n} from '@buckeyestudio/toh-output-retention'\n```\n\n| 导出项 | 职责 |\n|---|---|\n| `ItemRetainer<T>` | 限制有序逻辑单元（路径、grep 匹配项、来源）。只支持 `head`。`push()` → `PushDecision`；`finish()` → `RetainedItems<T>`。 |\n| `TextRetainer` | 限制面向字节的文本流。`head` / `tail` / `headTail`，并在 `finish()` 时保留 UTF-8 边界。`push()` → `PushDecision`；`finish()` → `RetainedText`。 |\n| `describeOmitted(omitted, unit)` | 标准化的省略子句（`exact` 输出数量；`unknown` 不输出）。 |\n| `formatRetentionNotice(notice, recovery)` | 将标准化的省略子句与工具自有的恢复指引连接起来。 |\n| `Omitted` | `none` / `exact` / `unknown`：省略了多少内容。 |\n| `PushDecision` | `{ kept, truncated }`：每次 push 的保留结果。 |\n\n## 资源模式\n\n两个 retainer 使用独立名称，而不是同一个通用收集器，因为它们的**资源模型**不同。\n\n- **`ItemRetainer` 限制有序逻辑单元**。搜索工具可收集完整结果集用于 spill 文件恢复，同时只为面向模型的预览保留前 `maxItems` 项。因为调用方会继续送入每个已观察到的项，所以省略数量是精确的。\n- **`TextRetainer` 限制面向字节的文本**。`head`、`tail` 和 `headTail` 在 `finish()` 时保留 UTF-8 边界；`headTail` 是 `toh-spill-policy` 用于围绕 spill 文件通知构建有界预览的形态。\n\n## `truncated` 是预算事实，绝不表示「不完整」\n\n`truncated` 表示*因为预算限制，retainer 省略了本可获得的内容*。它**不**表示上游不完整。权限失败、跳过二进制文件、提供方部分失败、不可读候选项和无效 UTF-8 保留在工具领域字段中，绝不合并到 `truncated`。将两者混为一谈是该库命名最容易诱发的缺陷；务必保持分离。\n\n## 字节，而非字符\n\n文本上限和 `omittedBytes` 按**字节**计数，以保证进程/正文安全（子进程管道和 HTTP 正文都是字节流）。跨越码点的分片会被正确处理：`finish()` 会修剪每个切割位置的不完整码点，使返回文本绝不在边界引入替换字符；首尾两侧会分开解码，因此绝不会跨越被省略的中间部分重建码点。按字符或行限制的预览预算属于独立的工具职责。\n\n## 工具映射\n\n当前的保留机制消费方采用以下映射：\n\n| 工具 | Retainer 与策略 | 说明 |\n|---|---|---|\n| `glob` | `ItemRetainer<FsGlobEntry>`，`head` | 收集完整的已排序路径列表用于 spill 文件，同时在内联位置保留第一页。路径映射、已跳过候选项和 `incomplete` 保留在外部。 |\n| `grep` | `ItemRetainer<FlatGrepMatch>`，`head` | 收集匹配项用于 spill 文件，同时在内联位置保留第一页。每个匹配项的预览截断、分组、排序和 `incomplete` 保留在外部。 |\n| `bash` | `TextRetainer`，`tail` 或 `headTail` | 执行器仍负责 spill 文件、退出状态、信号、超时和后台任务。 |\n| `web_fetch` | `TextRetainer`，`head` 或 `headTail` | 提供方/资源上限保留为提供方事实；retainer 只提供保留文本和省略元数据。 |\n| `web_search` | `ItemRetainer<WebSearchSource>`，`head` | 当提供方返回的来源超过面向模型的结果应包含的数量时，标准化「来源已达上限」通知。 |\n\n`read` 仍不属于这个通用库。其 `read-render` 辅助工具负责文件专用的分页约定：`offset`/`limit`、行号、`totalLines`、偏移越界错误、每行预览截断，以及所选窗口的字节上限。该辅助工具是一个行窗口渲染器。单个 `Omitted` 数量无法表示该窗口两侧。\n\n## 使用形态\n\n```ts ignore-check\n// glob: keep the first page inline while still collecting the full list for spill.\nconst retainer = new ItemRetainer<FsGlobEntry>({ kind: 'head', maxItems: globMaxResults })\nconst allEntries: FsGlobEntry[] = []\nfor await (const entry of candidates) {\n  allEntries.push(entry)\n  retainer.push(entry)\n}\nconst { items, truncated, omitted } = retainer.finish()\n\n// bash: keep a head + tail, read to process exit.\nconst out = new TextRetainer({ kind: 'headTail', headBytes: headCap, tailBytes: tailCap })\nchild.stdout.on('data', (chunk: Buffer) => { out.push(chunk) })\nconst { text, omittedBytes } = out.finish()\n\n// A footer: the library standardizes the omission clause; the tool owns recovery words.\nconst footer = formatRetentionNotice(\n  { scope: 'grep', strategy: 'head', unit: 'items', limit: grepMaxMatches, kept: items.length, omitted },\n  ({ kept }) => `Results capped at ${kept}. Narrow the pattern, path, or include to see more.`,\n)\n```\n\n## 模型体验\n\n通过渲染保留内容和省略元数据的工具消费方间接影响模型。\n\n#### KV Cache 影响\n\n不会直接导致 KV Cache 失效；请求前缀变更由上述消费方负责。\n\n## 已知限制与暂缓事项\n\n- **项保留只支持 `head`**：tail、head/tail、分页、分组和提供方完整性语义仍由工具负责。\n- **文本保留面向字节**：`read` 分页等行窗口和字符窗口需要单独的渲染器；切割可能会丢弃部分 UTF-8 边界字节，以保持返回文本有效。\n","readmeFilename":"README.zh.md","_rev":"1-6855b3a503754d85b616c6fc74bdaf2f"}