Merge branch 'master' into worktree/fail-web-wildcard-host

This commit is contained in:
Tianyi Cui
2026-08-13 16:22:21 +08:00
committed by GitHub
184 changed files with 3116 additions and 515 deletions
@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-08-12-collapsed-sidebar-shared-entry-motion.md
2026-08-12-collapsed-sidebar-shared-entry-motion.md: c5bc18973db693cf9ba60800fbcf7720593dbad5
2026-08-12-collapsed-sidebar-shared-entry-motion.zh.md: b87ef6322d548061cc2e29c0e8f9896098c09041
@@ -0,0 +1,33 @@
# Agent Note: Collapsed sidebar upper controls share one entry motion
Status: implemented
Archived: 2026-08-12
English | [中文](2026-08-12-collapsed-sidebar-shared-entry-motion.zh.md)
## Problem
The collapsed sidebar rail renders four upper controls owned by two packages: the shell owns the toggle and New Session, while the workspace region owns add and search. Their opacity timing matched, but their geometry did not. Right-aligned controls moved with the narrowing column while left-aligned controls stayed fixed, so add appeared slower than search even under the same fade.
The bottom settings control has a different role. It is pinned to the rail foot and must not join the upper controls' horizontal entry.
## Decision
At the rail settle point, the four upper 36px controls start from one left-anchored layout and share one `150ms` animation from `translateX(49px)` to their final 10px inset. The shell applies the translation to its toggle and New Session seats and once to the workspace region, so add and search inherit the same path without nested transforms. Opacity uses the same animation timeline.
The settings seat uses a separate opacity-only keyframe with the same duration and easing. A page that starts collapsed renders the rail without an entry animation, and reduced-motion mode disables both keyframes.
## Alternatives considered
**Keep every rail control fixed at its final inset.** This removes the mismatch, but it also removes the requested horizontal entry from the four upper controls.
**Animate each workspace button independently.** This would duplicate shell timing inside `ui-workspace` and could apply both a region and child transform. Translating the registered region once keeps animation ownership in the sidebar shell.
**Translate the settings control with the upper controls.** Rejected because settings is a bottom-pinned foot action, not part of the upper control sequence.
## Consequences
- Toggle, New Session, add, and search follow the same horizontal coordinates throughout collapse.
- Settings fades at its final horizontal coordinate.
- Static collapsed renders retain their final geometry without startup motion.
- Style tests pin the shared animation assignments, translation distance, base anchors, and settings exception.
@@ -0,0 +1,33 @@
# Agent Note: 收起侧栏的上方控件共用同一进入动画
Status: implemented
Archived: 2026-08-12
[English](2026-08-12-collapsed-sidebar-shared-entry-motion.md) | 中文
## Problem
收起侧栏轨道的四个上方控件由两个包渲染:外壳持有侧栏切换与新建会话,Workspace 区域持有添加和搜索。它们的透明度时序相同,但几何行为不同。右对齐控件会随栏变窄而移动,左对齐控件则保持不动,因此添加即使使用相同淡入,视觉上仍比搜索慢。
底部设置控件承担不同角色。它固定在轨道页脚,不能参与上方控件的横向进入。
## Decision
轨道落位时,四个 36px 上方控件从同一个左对齐布局开始,共用一段 `150ms` 动画,从 `translateX(49px)` 移动到最终 10px 内边距。外壳把位移分别应用于侧栏切换、新建会话,并只对 Workspace 区域应用一次,因此添加与搜索会继承同一路径,不产生嵌套变换。透明度使用同一条动画时间线。
设置控件使用时长与缓动相同、但只改变透明度的独立关键帧。页面初始即为收起状态时不会播放进入动画;减少动态效果模式会禁用两段关键帧。
## Alternatives considered
**把每个轨道控件固定在最终内边距。** 这能消除不一致,但也会移除四个上方控件所需的横向进入效果。
**分别为每个 Workspace 按钮添加动画。** 这会在 `ui-workspace` 中重复外壳时序,还可能同时应用区域与子控件变换。只移动一次已注册区域,可以让动画继续由侧栏外壳持有。
**让设置控件随上方控件一起移动。** 不予采纳,因为设置是固定在底部的页脚操作,不属于上方控件序列。
## Consequences
- 侧栏切换、新建会话、添加与搜索在整个收起过程中使用相同横坐标。
- 设置在最终横坐标上淡入。
- 静态收起渲染保持最终几何,不播放启动动画。
- 样式测试固定共用动画分配、位移距离、基础锚点与设置例外。
+3
View File
@@ -97,6 +97,9 @@
"bug-fix/2026-08-10-web-favicon-dark-mode.i18n.yaml": "sha256:859c4399f9a017a68ba89552fdafa05e73c0599d94cee9551c84ea5b749a14f3", "bug-fix/2026-08-10-web-favicon-dark-mode.i18n.yaml": "sha256:859c4399f9a017a68ba89552fdafa05e73c0599d94cee9551c84ea5b749a14f3",
"bug-fix/2026-08-10-web-favicon-dark-mode.md": "sha256:4d17e247abd76ae3aed5fb4e075fd66a2838292f89f7021c82a79fe37ed905e6", "bug-fix/2026-08-10-web-favicon-dark-mode.md": "sha256:4d17e247abd76ae3aed5fb4e075fd66a2838292f89f7021c82a79fe37ed905e6",
"bug-fix/2026-08-10-web-favicon-dark-mode.zh.md": "sha256:7bbff8a3b7061c127afcc75cd2a8043b02a999b78c0180edd8f7e4807fcfe71d", "bug-fix/2026-08-10-web-favicon-dark-mode.zh.md": "sha256:7bbff8a3b7061c127afcc75cd2a8043b02a999b78c0180edd8f7e4807fcfe71d",
"bug-fix/2026-08-12-collapsed-sidebar-shared-entry-motion.i18n.yaml": "sha256:3ce4f6e39e173fc304bf64deca9c95bcddc1dbb492e065ca8c267a7a40788588",
"bug-fix/2026-08-12-collapsed-sidebar-shared-entry-motion.md": "sha256:7b169aa4543edfc965de5a8b7b9e60aa9d9d5218693cd0b57908e2d482280723",
"bug-fix/2026-08-12-collapsed-sidebar-shared-entry-motion.zh.md": "sha256:88db36c698800bf55c3c7531d6f92665576d978c29c15ff7d74215fb93376cb1",
"feature/2026-06-14-acp-agent-client-protocol.i18n.yaml": "sha256:006795baa43ae962a8d125cc0f1e9f134bc2ee9fb758b6e7669e3fa0126e1918", "feature/2026-06-14-acp-agent-client-protocol.i18n.yaml": "sha256:006795baa43ae962a8d125cc0f1e9f134bc2ee9fb758b6e7669e3fa0126e1918",
"feature/2026-06-14-acp-agent-client-protocol.md": "sha256:6828c0af74bb3fb96206ca6b21c0e56a000b50e4744aad4bc2c05092f3a5a31b", "feature/2026-06-14-acp-agent-client-protocol.md": "sha256:6828c0af74bb3fb96206ca6b21c0e56a000b50e4744aad4bc2c05092f3a5a31b",
"feature/2026-06-14-acp-agent-client-protocol.zh.md": "sha256:ba104e841a1fb84edbd3b6c8119d50445b7785255a7a8d13bb9ac8a2cb4d2e69", "feature/2026-06-14-acp-agent-client-protocol.zh.md": "sha256:ba104e841a1fb84edbd3b6c8119d50445b7785255a7a8d13bb9ac8a2cb4d2e69",
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority; # side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with: # after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-08-04-composer-tab-gutter-reservation.md # pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-08-04-composer-tab-gutter-reservation.md
2026-08-04-composer-tab-gutter-reservation.md: 3b28c35c1f11676e41cabde76d1b0d16c688f034 2026-08-04-composer-tab-gutter-reservation.md: 8bd9fb2d86982d82b44a82c55b7303fcd9a5bf4d
2026-08-04-composer-tab-gutter-reservation.zh.md: c357dd06c52a834c18d2e8a25246d4ad003db548 2026-08-04-composer-tab-gutter-reservation.zh.md: 4b70aeb1d3777384907c345968971fcf75b3e74d
@@ -14,13 +14,11 @@ So for as long as the transcript overflowed — the ordinary state of any sessio
## Decision ## Decision
`.scrollBody` declares `scrollbar-gutter: stable` unconditionally, and the overlay branch declares the same box a scroll container on both axes — `overflow-x: hidden; overflow-y: auto` — instead of `overflow: hidden`. `.scrollBody` declares `scrollbar-gutter: stable` for the Chat state, and the overlay branch overrides it with `scrollbar-gutter: auto` while staying a scroll container on both axes — `overflow-x: hidden; overflow-y: auto`. The reservation is Chat's alone: it holds the seat's content box at the same width whether or not the transcript overflows, so the card never jumps as a growing transcript starts to scroll, nor between the hero phase and the first scrolling turn. The overlay branch reserves nothing — the view owns its own scrollers, so a gutter there would only narrow the view's content — and its seat compensates for the bar instead ([the seat-width compensation](2026-08-12-composer-overlay-seat-width-compensation.md)).
The two halves are one change. The reservation is what makes both states measure against the same width; declaring the overlay branch a scroll container is what makes the reservation reach it. `stable` rather than `auto` because `auto` reserves only while the box actually overflows, and the difference between overflowing and not is precisely the difference between the two tabs — an `auto` gutter would state the bug rather than fix it. `stable` rather than `auto` because `auto` reserves only while the box actually overflows, and the difference between overflowing and not is precisely the difference between Chat's two phases — an `auto` gutter would state the bug rather than fix it.
The overlay state is a scroll container that nothing scrolls: the view fills it (`flex: 1 1 0` with its own clip) and the seat is out of flow, so no gesture and no clipping behavior changes. What changes is which declarations the engine honours. WebKit applies `scrollbar-gutter` to an `overflow-y: auto` box and ignores it on a hidden one — measured on this app's own composer layers and recorded in [the composer scrollport note](2026-07-31-composer-text-layers-share-one-scrollport.md) — so a reservation left on a hidden box would hold in Chromium and silently not in Safari. The reservation lives on an `overflow-y: auto` box, and that form is load-bearing: WebKit applies `scrollbar-gutter` to an `overflow-y: auto` box and ignores it on a hidden one — measured on this app's own composer layers and recorded in [the composer scrollport note](2026-07-31-composer-text-layers-share-one-scrollport.md) — so a reservation on a hidden box would hold in Chromium and silently not in Safari. The overlay branch keeps its `overflow-y: auto` form too, as a clipping box nothing scrolls out of: a single-axis scroller computes the other axis to `auto`, so the horizontal axis is declared `hidden` rather than left to compute, and would otherwise grow a horizontal scrollbar of its own the first time a view's content reached past the column.
The horizontal axis is declared rather than left to compute: a box that scrolls on one axis computes `visible` on the other to `auto`, and would grow a horizontal scrollbar of its own the first time a view's content reached past the column.
The reservation is worth what it costs only because the bar takes layout space here at all, which is not the browser's default behavior but this client's: `::-webkit-scrollbar` carries a width in ui-theme's sheet ([themed scrollbars](2026-07-28-themed-scrollbars-and-reserved-gutter.md)), and the sidebar's session list already reserves its own gutter for the same reason. The reservation is worth what it costs only because the bar takes layout space here at all, which is not the browser's default behavior but this client's: `::-webkit-scrollbar` carries a width in ui-theme's sheet ([themed scrollbars](2026-07-28-themed-scrollbars-and-reserved-gutter.md)), and the sidebar's session list already reserves its own gutter for the same reason.
@@ -37,7 +35,7 @@ The reservation is worth what it costs only because the bar takes layout space h
## Consequences ## Consequences
- Chat's content column is permanently 8px narrower — in the hero phase and while the transcript is short as well, where no bar is drawn. That is the trade: one card position at every content height, instead of the widest possible column. - Chat's content column is permanently 8px narrower — in the hero phase and while the transcript is short as well, where no bar is drawn. That is the trade: one card position at every content height, instead of the widest possible column.
- The fix covers three transitions with one declaration, because all three are the same difference: Chat ↔ Trajectory, short ↔ scrolling transcript within Chat, and hero ↔ first scrolling turn. - The card holds one position across three transitions, by two mechanisms: the reservation keeps Chat's seat at one width across its own phases (short ↔ scrolling transcript, hero ↔ first scrolling turn), and the overlay seat's compensation matches it on the Chat ↔ Trajectory transition ([the seat-width compensation](2026-08-12-composer-overlay-seat-width-compensation.md)).
- The overlay state is now a scroll container. Nothing in it can overflow today; a future view that let its content exceed the column would scroll this box instead of clipping, and would need its own clip the way the Trajectory view already has one. - The overlay state is now a scroll container. Nothing in it can overflow today; a future view that let its content exceed the column would scroll this box instead of clipping, and would need its own clip the way the Trajectory view already has one.
- The committed golden records the reserved band, so a change to the sheet's `::-webkit-scrollbar` width — the value that decides how wide the reservation is — arrives as a reviewable diff in this scenario as well as in the sidebar's. - The committed golden records the reserved band, so a change to the sheet's `::-webkit-scrollbar` width — the value that decides how wide the reservation is — arrives as a reviewable diff in this scenario as well as in the sidebar's.
@@ -45,6 +43,6 @@ The reservation is worth what it costs only because the bar takes layout space h
`apps/web/tests/composer-tab-geometry.e2e.ts` measures the input card's rectangle in both tabs, at a viewport where the card sits at its width cap and one where it shrinks with the column, and asserts the two rectangles are the same rectangle. Only a real engine reports this: jsdom gives every element a zero-sized box and no scrollbar, so a unit spec could assert the declarations exist but not that the two states land in the same place. For the same reason no CSS-text spec accompanies it — it would restate the declarations without adding a fact the browser lane does not already establish. `apps/web/tests/composer-tab-geometry.e2e.ts` measures the input card's rectangle in both tabs, at a viewport where the card sits at its width cap and one where it shrinks with the column, and asserts the two rectangles are the same rectangle. Only a real engine reports this: jsdom gives every element a zero-sized box and no scrollbar, so a unit spec could assert the declarations exist but not that the two states land in the same place. For the same reason no CSS-text spec accompanies it — it would restate the declarations without adding a fact the browser lane does not already establish.
The scenario launches chromium without Playwright's default `--hide-scrollbars`, which is load-bearing: under that argument a bar consumes no layout width, both tabs agree before this change as much as after it, and every comparison in the file holds vacuously. Measured, the pre-fix cascade leaves both bands at 0 under the argument, and at 8 and 0 with it dropped. The scenario launches chromium without Playwright's default `--hide-scrollbars`, which is load-bearing: under that argument a bar consumes no layout width, so the tabs agree with and without the compensation and every comparison in the file holds vacuously. Measured, both bands sit at 0 under the argument and at 8 and 0 with it dropped.
The pre-fix cascade is then applied in the page — `scrollbar-gutter: auto` on the scroller, `overflow: hidden` on the overlay branch — and the same two tabs measured through it, which is what separates a card that does not move from a tab switch that never reached the layout. It reproduces the reported symptom as a number: 4px on each edge, half the 8px band. The golden records that control beside the fixed state, so the fixture carries the difference the change removes rather than only its absence. The uncompensated cascade is then applied in the page — the overlay seat's `right` compensation dropped to 0 via `!important`, Chat's reservation untouched — and the same two tabs measured through it, which is what separates a card that does not move from a tab switch that never reached the layout. It reproduces the reported symptom as a number: 4px on each edge, half the 8px band. The golden records that control beside the fixed state, so the fixture carries the difference the change removes rather than only its absence.
@@ -14,13 +14,11 @@ composer 座位在组件树中只有一个节点、一个位置,但它究竟
## 决策 ## 决策
`.scrollBody` 无条件声明 `scrollbar-gutter: stable`overlay 分支则把同一个盒子在两个轴向上都声明为滚动容器——`overflow-x: hidden; overflow-y: auto`——而不再是 `overflow: hidden` `.scrollBody` 为 Chat 状态声明 `scrollbar-gutter: stable`覆盖分支则将其覆盖为 `scrollbar-gutter: auto`,同时保持为双轴滚动容器——`overflow-x: hidden; overflow-y: auto`。这条预留只属于 Chat:它让座位的内容盒在 transcript 是否溢出时都保持同一宽度,因此卡片不会在 transcript 增长到开始滚动的那一刻跳动,也不会在 hero 态与第一个可滚动轮次之间跳动。覆盖分支不预留任何槽位——视图自己滚动,槽位只会白白收窄视图内容——它的座位改为补偿滚动条宽度([座位宽度补偿](2026-08-12-composer-overlay-seat-width-compensation.md)
这两半是同一处改动。预留使两种状态依附于同一个宽度;把 overlay 分支声明为滚动容器,才使这条预留真正抵达它。`stable` 而非 `auto`,是因为 `auto` 只在盒子确实溢出时才预留,而「溢出与否」恰恰就是两个标签页之间的那点差别——`auto` 的写法只是把缺陷重述一遍,并不能修掉它。 `stable` 而非 `auto`,是因为 `auto` 只在盒子确实溢出时才预留,而「溢出与否」恰恰就是 Chat 两种相位之间的那点差别——`auto` 的写法只是把缺陷重述一遍,并不能修掉它。
overlay 状态是一个没有任何东西会去滚动它的滚动容器:视图把它填满(`flex: 1 1 0`,且自带裁剪),座位不在常规流中,因此没有任何手势与裁剪行为发生变化。变化的是引擎会认哪些声明。WebKit 对 `overflow-y: auto` 的盒子应用 `scrollbar-gutter`,对 hidden 的盒子则忽略它——这是在本应用 composer 自身的图层上实测所得,并记录于 [composer 滚动视口记录](2026-07-31-composer-text-layers-share-one-scrollport.md)——所以把预留留在一个 hidden 盒子上,会在 Chromium 上成立,在 Safari 上悄无声息地不成立。 这条预留位于 `overflow-y: auto` 的盒子上,而这个形式是承重的:WebKit 对 `overflow-y: auto` 的盒子应用 `scrollbar-gutter`,对 hidden 的盒子则忽略它——这是在本应用 composer 自身的图层上实测所得,并记录于 [composer 滚动视口记录](2026-07-31-composer-text-layers-share-one-scrollport.md)——所以把预留放在 hidden 盒子上,会在 Chromium 上成立,在 Safari 上悄无声息地不成立。覆盖分支同样保留 `overflow-y: auto` 的形式,作为没有任何内容会滚出去的裁剪盒:单轴滚动的盒子会把另一轴的 `visible` 计算为 `auto`,因此横向轴显式声明为 `hidden` 而不是交给推导,否则某个视图的内容第一次伸出列外时,它就会长出自己的横向滚动条。
横向轴是显式声明的,而不是交给推导:单轴滚动的盒子会把另一轴的 `visible` 计算为 `auto`,于是只要某个视图的内容第一次伸出列外,它就会长出自己的横向滚动条。
这条预留之所以值回它的代价,前提是滚动条在这里确实占布局空间——这并非浏览器的默认行为,而是本客户端的选择:ui-theme 的样式表给 `::-webkit-scrollbar` 声明了宽度([滚动条主题化](2026-07-28-themed-scrollbars-and-reserved-gutter.md)),侧边栏的会话列表也正是出于同一原因预留了自己的滚动条槽。 这条预留之所以值回它的代价,前提是滚动条在这里确实占布局空间——这并非浏览器的默认行为,而是本客户端的选择:ui-theme 的样式表给 `::-webkit-scrollbar` 声明了宽度([滚动条主题化](2026-07-28-themed-scrollbars-and-reserved-gutter.md)),侧边栏的会话列表也正是出于同一原因预留了自己的滚动条槽。
@@ -37,7 +35,7 @@ overlay 状态是一个没有任何东西会去滚动它的滚动容器:视图
## 后果 ## 后果
- Chat 的内容列永久变窄 8px——hero 态与 transcript 尚短、根本不绘制滚动条时同样如此。这就是这笔交易:以最宽的列换取卡片在任何内容高度下都只有一个位置。 - Chat 的内容列永久变窄 8px——hero 态与 transcript 尚短、根本不绘制滚动条时同样如此。这就是这笔交易:以最宽的列换取卡片在任何内容高度下都只有一个位置。
- 一条声明覆盖三种切换,因为这三者本就是同一个差异:Chat ↔ Trajectory、Chat 内部 transcript 较短 ↔ transcript 可滚动,以及 hero ↔ 第一个可滚动轮次 - 卡片在三种切换下保持同一位置,由两种机制达成:预留让 Chat 的座位在自身各相位间保持同一宽度(transcript 较短 ↔ 可滚动、hero ↔ 第一个可滚动轮次),Chat ↔ Trajectory 的切换则由覆盖座位的补偿来对齐([座位宽度补偿](2026-08-12-composer-overlay-seat-width-compensation.md)
- overlay 状态现在是一个滚动容器。今天其中没有任何内容会溢出;将来若有视图允许自身内容超出会话列,这个盒子会滚动而不是裁剪,那个视图就需要像 Trajectory 视图那样自带裁剪。 - overlay 状态现在是一个滚动容器。今天其中没有任何内容会溢出;将来若有视图允许自身内容超出会话列,这个盒子会滚动而不是裁剪,那个视图就需要像 Trajectory 视图那样自带裁剪。
- 提交的 golden 记录了预留条带,因此样式表中 `::-webkit-scrollbar` 宽度的变化——决定这条预留有多宽的那个值——会在本场景中与在侧边栏场景中一样,以可评审的 diff 形式出现。 - 提交的 golden 记录了预留条带,因此样式表中 `::-webkit-scrollbar` 宽度的变化——决定这条预留有多宽的那个值——会在本场景中与在侧边栏场景中一样,以可评审的 diff 形式出现。
@@ -45,6 +43,6 @@ overlay 状态是一个没有任何东西会去滚动它的滚动容器:视图
`apps/web/tests/composer-tab-geometry.e2e.ts` 在两个标签页下测量输入卡片的矩形,分别取卡片处于宽度上限的视口与卡片随列收缩的视口,并断言这两个矩形是同一个矩形。只有真实引擎能报告这件事:jsdom 给每个元素的盒子尺寸都是零,也没有滚动条,因此单元测试只能断言那些声明存在,无法断言两种状态落在同一位置。出于同一原因,本次没有附带读取 CSS 文本的单元测试——它只会把声明复述一遍,并不会补上浏览器车道尚未确立的事实。 `apps/web/tests/composer-tab-geometry.e2e.ts` 在两个标签页下测量输入卡片的矩形,分别取卡片处于宽度上限的视口与卡片随列收缩的视口,并断言这两个矩形是同一个矩形。只有真实引擎能报告这件事:jsdom 给每个元素的盒子尺寸都是零,也没有滚动条,因此单元测试只能断言那些声明存在,无法断言两种状态落在同一位置。出于同一原因,本次没有附带读取 CSS 文本的单元测试——它只会把声明复述一遍,并不会补上浏览器车道尚未确立的事实。
该场景启动 chromium 时去掉了 Playwright 默认的 `--hide-scrollbars`,这一点是承重的:带上该参数时滚动条不占任何布局宽度,两个标签页在改动前后同样一致,文件中的每一处比较都会空洞地通过。实测:带上该参数时,改动前的层叠让两条预留带的宽度都是 0;去掉它则分别是 8 与 0。 该场景启动 chromium 时去掉了 Playwright 默认的 `--hide-scrollbars`,这一点是承重的:带上该参数时滚动条不占任何布局宽度,因此两个标签页在有补偿与无补偿时同样一致,文件中的每一处比较都会空洞地通过。实测:带上该参数时两条预留带的宽度都是 0;去掉它则分别是 8 与 0。
随后,改动前的层叠会被注入页面——滚动容器上 `scrollbar-gutter: auto`overlay 分支上 `overflow: hidden`——并在其下测量同样的两个标签页,这正是把「卡片确实没动」与「标签页切换根本没到达布局」区分开的那一步。它把上报的症状复现为一个数字:每条边 4px,恰是 8px 带宽的一半。golden 把这份对照与修复后的状态并排记录,因此 fixture(测试前置数据)承载的是这次改动所消除的那个差值,而不仅仅是它的缺席。 随后,未补偿的级联会被注入页面——通过 `!important` 把覆盖座位的 `right` 补偿降为 0,Chat 的预留保持不变——并在其下测量同样的两个标签页,这正是把「卡片确实没动」与「标签页切换根本没到达布局」区分开的那一步。它把上报的症状复现为一个数字:每条边 4px,恰是 8px 带宽的一半。golden 把这份对照与修复后的状态并排记录,因此 fixture(测试前置数据)承载的是这次改动所消除的那个差值,而不仅仅是它的缺席。
@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-08-12-composer-overlay-seat-width-compensation.md
2026-08-12-composer-overlay-seat-width-compensation.md: 0ec4d1272ac1adab5b724dccf44f567e15cc3368
2026-08-12-composer-overlay-seat-width-compensation.zh.md: 2f66771240fe62e15c481342d55a82dd09cfb864
@@ -0,0 +1,39 @@
# Agent Note: The overlay composer seat compensates for the bar instead of reserving a gutter
Status: implemented
English | [中文](2026-08-12-composer-overlay-seat-width-compensation.zh.md)
## Problem
The [composer-tab gutter reservation](2026-08-04-composer-tab-gutter-reservation.md) made the column's scroller reserve a scrollbar gutter unconditionally, so the composer seat measured the same width in Chat and in a view with a composer overlay. The cost was paid by every overlay view: the view's content column ended 8px short of the column's right edge, because the scroller reserved a gutter for a bar it never draws — the trajectory ledger owns its own scrollers and the outer box never scrolls.
The trajectory table made that cost visible: its full-width row divider lines stopped 8px short of the pane edge, leaving a strip of whitespace at the right of every line and of the whole content column.
## Decision
The reservation now belongs to Chat alone. The overlay branch declares `scrollbar-gutter: auto`, so the view's content spans the full column; the overlay composer seat (absolutely positioned against the padding box) gives back the bar's width with `right: var(--dsh-scrollbar-width)`, so the input card still measures the same width as Chat's seat and does not move between tabs.
The compensation value is not a literal: ui-theme's scrollbar.css defines `--dsh-scrollbar-width` (8px on the WebKit path) beside the `::-webkit-scrollbar` rule it mirrors, and the seat reads that variable. The scrollbar-styles spec pairs the variable with the mirrored rule and with the compensation consumer, so a change to the sheet's bar width without the variable — or to the variable without the consumer — fails the gate, not just review.
## Alternatives considered
**Keep the unconditional reservation and shrink every overlay view.** The pre-fix behavior. It keeps one declaration for both tabs but taxes every overlay view with an 8px content column, which the trajectory ledger surfaced as visible whitespace. Rejected because the overlay views own their scrolling; they should not pay for Chat's bar.
**Reserve on the overlay branch too and let the view bleed into the gutter.** More moving parts for the same result: the gutter would still exist on a box that never scrolls, and the view would have to break out of the content box to reclaim its width.
**Accept the 4px card shift.** Dropping the reservation without compensating the seat would move the input card on every tab switch, which is exactly the symptom the earlier note fixed. Rejected: the card position is a deliberate cross-tab invariant.
**Inset the overlay seat by the bar's width.** The [gutter-reservation note](2026-08-04-composer-tab-gutter-reservation.md) rejected exactly this, and this note adopts it; what changed is the rejection's premise. The number was the engine's, not ours — the WebKit path draws the sheet's 8px bar while the Firefox path draws whatever `scrollbar-width: thin` resolves to — so a hardcoded inset would line the two states up in Chromium and drift elsewhere. The overlay branch reserved an engine-resolved gutter of its own back then, so an inset had to match that width exactly. Today the overlay branch reserves nothing, so the compensation is the overlay side's only mechanism, and the literal half of the rejection is answered by making the 8px a variable that mirrors the `::-webkit-scrollbar` rule in the same diff. The Firefox half remains: Chat reserves the engine-resolved width while the compensation stays fixed, and the residual drift where the two differ is recorded as an accepted cost in Consequences.
## Consequences
- Chat keeps its reserved gutter and its stable card position; nothing changes on that tab.
- Overlay views (trajectory) span the full column; the trajectory ledger's divider lines reach the pane edge.
- The input card still holds one horizontal position across the Chat and Trajectory tabs, now by two mechanisms instead of one: Chat reserves, the overlay seat compensates.
- Chat reserves the engine-resolved width while the overlay seat compensates a fixed 8px. Where the two differ — the Firefox path resolves `scrollbar-width: thin` per platform, and the e2e runs only on Chromium — the card drifts by half the difference on tab switch. Accepted residual cost, recorded here rather than asserted away: no measurement of the Firefox thin width on the target platforms exists in this change.
- `--dsh-scrollbar-width` becomes a public ui-theme variable read outside ui-theme; the scrollbar-styles spec pairs it with the mirrored `::-webkit-scrollbar` width rule and with the compensation consumer, closing the indirection-gate gap the variable would otherwise leave.
## Testing
`apps/web/tests/composer-tab-geometry.e2e.ts` still asserts the card holds its position across tabs and now also asserts the split: Chat's scroller keeps `scrollbar-gutter: stable` and a nonzero band, while the overlay branch resolves `auto` with a zero band. The control cascade changed with the mechanism: it now drops the seat's `right` compensation (instead of dropping a gutter Chat never had on that branch) and measures the same 4px shift, proving the equal rectangles are not a tab switch that never reached layout. The committed golden records both states.
@@ -0,0 +1,39 @@
# Agent Note: 覆盖视图的 composer 座位改为补偿滚动条宽度,不再预留滚动条槽
Status: implemented
[English](2026-08-12-composer-overlay-seat-width-compensation.md) | 中文
## 问题
[composer 标签页滚动条槽预留](2026-08-04-composer-tab-gutter-reservation.md) 让会话列滚动容器无条件预留一条滚动条槽,使 composer 座位在 Chat 与带 composer 覆盖的视图中测得相同宽度。代价由每个覆盖视图承担:视图内容列比列右边缘窄 8px,因为滚动容器为一条它从不绘制的滚动条预留了槽——trajectory 台账由视图内部自己的滚动容器滚动,外层盒子从不滚动。
trajectory 表格让这个代价显形:整行分隔线在面板右边缘前 8px 处停止,每条线右侧以及整个内容列右侧都留下一条空白带。
## 决策
预留现在只属于 Chat。覆盖分支声明 `scrollbar-gutter: auto`,视图内容占满整列;覆盖分支的 composer 座位(相对 padding box 绝对定位)用 `right: var(--dsh-scrollbar-width)` 让出滚动条宽度,使输入卡仍与 Chat 座位测得相同宽度,切换标签页时不移动。
补偿值不是字面量:ui-theme 的 scrollbar.css 在它镜像的 `::-webkit-scrollbar` 规则旁定义 `--dsh-scrollbar-width`(WebKit 路径 8px),座位读取该变量。scrollbar-styles 规格把该变量与其镜像规则、以及补偿消费者配对检查,因此样式表滚动条宽度一变却不同步变量——或变量一变却不同步消费者——都会让门禁失败,而不只是评审时发现。
## 备选方案
**保留无条件预留,压缩每个覆盖视图。** 修复前行为。两个标签页一条声明,但每个覆盖视图都要付出 8px 内容列,trajectory 台账将其显现为可见空白。已拒绝:覆盖视图自己滚动,不应为 Chat 的滚动条买单。
**覆盖分支也预留,并让视图渗入滚动条槽。** 同样结果下更多活动部件:从不滚动的盒子上仍存在滚动条槽,视图还得突破内容盒才能取回宽度。
**接受 4px 卡片位移。** 去掉预留却不补偿座位,会在每次切换标签页时移动输入卡——正是前一份 note 修复的症状。已拒绝:卡片位置是刻意保持的跨标签页不变量。
**把 overlay 座位按滚动条宽度内缩。** [滚动条槽预留 note](2026-08-04-composer-tab-gutter-reservation.md) 当初否决的正是这个方案,本 note 采纳了它;变的是否决的前提。这个数字属于引擎而不属于我们——WebKit 路径绘制样式表里的 8px 滚动条,Firefox 路径绘制 `scrollbar-width: thin` 解析出的宽度——因此硬编码的内缩会让两种状态在 Chromium 上对齐、在别处继续漂移。当初 overlay 分支自己预留的是引擎解析出的槽宽,内缩必须精确匹配那个宽度。如今 overlay 分支不预留任何槽位,补偿成为覆盖侧唯一的机制;否决的字面量那一半,通过把 8px 变成与 `::-webkit-scrollbar` 规则同处一个 diff 的变量来回应。Firefox 那一半仍然存在:Chat 预留引擎解析宽度,补偿保持固定 8px,两者不等之处的残余漂移作为接受的代价记录在后果中。
## 后果
- Chat 保留滚动条槽与稳定的卡片位置;该标签页无任何变化。
- 覆盖视图(trajectory)占满整列;trajectory 台账的分隔线到达面板右边缘。
- 输入卡在 Chat 与 Trajectory 标签页间仍保持同一水平位置,现在由两种机制而非一种达成:Chat 预留,覆盖座位补偿。
- Chat 预留引擎解析宽度,覆盖座位补偿固定的 8px。两者不等之处——Firefox 路径按平台解析 `scrollbar-width: thin`,而 e2e 只在 Chromium 上运行——卡片在切换标签页时会漂移半个差值。这是接受的残余代价,如实记录于此而不断言消除:本次改动并未提供目标平台 Firefox thin 宽度的实测。
- `--dsh-scrollbar-width` 成为 ui-theme 对外、且被 ui-theme 之外读取的变量;scrollbar-styles 规格把它与镜像的 `::-webkit-scrollbar` 宽度规则、以及补偿消费者配对检查,补上了该变量本会留下的间接层门禁缺口。
## 测试
`apps/web/tests/composer-tab-geometry.e2e.ts` 仍断言输入卡在标签页间保持位置,并新增断言拆分:Chat 滚动容器保持 `scrollbar-gutter: stable` 与非零槽宽,覆盖分支解析为 `auto` 且槽宽为零。控制级联随机制改变:现在移除座位的 `right` 补偿(而非移除该分支上 Chat 从未有过的槽),测得同样的 4px 位移,证明相等的矩形并非从未到达布局的标签页切换。提交的 golden 记录两种状态。
@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-08-12-resolve-store-pwsh-aliases.md
2026-08-12-resolve-store-pwsh-aliases.md: 20fe58e15e75462dc0a9ba76c7a1a94939f8a004
2026-08-12-resolve-store-pwsh-aliases.zh.md: bbfa4616127a9dbdb6609fe2973283663de55b31
@@ -0,0 +1,23 @@
# Agent Note: Resolve Microsoft Store pwsh aliases
Status: implemented
English | [中文](2026-08-12-resolve-store-pwsh-aliases.zh.md)
## Problem
`resolvePwshPath` documented that Microsoft Store installs resolve through PATH, but its existence probe was `existsSync`, which stats a candidate and therefore follows reparse points. The Store's `%LOCALAPPDATA%\Microsoft\WindowsApps\pwsh.exe` is an app execution alias whose target directory ACL refuses stat (EACCES), so `existsSync` missed it and resolution silently fell through to Windows PowerShell 5.1 on hosts whose only PowerShell 7 is a Store install.
## Decision
`candidateExists` accepts a candidate that stats as a file or that lstat sees as a link-shaped reparse point, and `resolvePwshPath` uses it. Spawning the alias path works because CreateProcess resolves app execution aliases. A dangling link-shaped candidate is accepted so a broken pwsh fails loudly at spawn instead of silently downgrading to 5.1.
## Alternatives considered
**Probe the WindowsApps package directory directly.** The Store package path is versioned and ACL-hidden; hard-coding it duplicates packaging knowledge that PATH plus the alias already owns.
**Keep the 5.1 fallback for stat failures.** Rejected: it silently runs a different shell than the one installed, which is the defect this note fixes.
## Consequences
Store-installed PowerShell 7 now resolves ahead of the 5.1 fallback on Windows; real-file candidates and non-Windows behavior are unchanged. The dangling-symlink unit test pins the stat/lstat split on every platform.
@@ -0,0 +1,23 @@
# Agent Note: 解析 Microsoft Store 的 pwsh 别名
Status: implemented
[English](2026-08-12-resolve-store-pwsh-aliases.md) | 中文
## 问题
`resolvePwshPath` 声称 Store 安装经 PATH 解析,但它的存在性探测用的是 `existsSync`,会对候选做 stat、从而跟随重解析点。Store 的 `%LOCALAPPDATA%\Microsoft\WindowsApps\pwsh.exe` 是 app execution alias,其目标目录的 ACL 拒绝 stat(EACCES),于是 `existsSync` 看不到它,解析静默落到 Windows PowerShell 5.1——在这类「唯一的 PowerShell 7 是 Store 安装」的机器上就用了错误的 shell。
## 决策
`candidateExists` 接受「stat 为文件」或「lstat 为链接形态重解析点」的候选,`resolvePwshPath` 改用它。spawn 别名路径可以工作,因为 CreateProcess 会解析 app execution alias。悬空的链接形态候选同样被接受,让损坏的 pwsh 在 spawn 时响亮失败,而不是静默降级到 5.1。
## 考虑过的替代方案
**直接探测 WindowsApps 包目录。** Store 包路径带版本且被 ACL 隐藏;硬编码它只是重复了 PATH 加别名已经拥有的打包知识。
**对 stat 失败继续走 5.1 回退。** 否决:它静默运行了一个并非所装的 shell,这正是本 note 修复的缺陷。
## 后果
Windows 上 Store 安装的 PowerShell 7 现在先于 5.1 回退被解析;普通文件候选和非 Windows 平台行为不变。悬空 symlink 单元测试在全部平台上钉住 stat/lstat 的分裂行为。
@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-08-12-unlink-fixture-junctions-before-delete.md
2026-08-12-unlink-fixture-junctions-before-delete.md: 4514a33d728866b817f4e9c1393f16c07976ed45
2026-08-12-unlink-fixture-junctions-before-delete.zh.md: 3c212c052ab0303ccb8d31d2b310a365a1d8cc99
@@ -0,0 +1,23 @@
# Agent Note: Unlink fixture junctions before recursive deletion
Status: implemented
English | [中文](2026-08-12-unlink-fixture-junctions-before-delete.zh.md)
## Problem
The install-lefthook and translation-pairing fixtures junction the repository's real `scripts/`, `node_modules`, and tsx package directories into fixture trees so installer probes resolve through them. Windows recursive deletion can treat a junction (a MOUNT_POINT reparse point) as a directory and follow it into its target; Git's `worktree remove` did exactly that and deleted the repository's tracked `scripts/` and tsx package (the incident's instrumentation pinned the deletion to that step). A fixture cleanup that trusts its deleter therefore deletes the repository's own sources instead of the fixture.
## Decision
`scripts/test-fixture-cleanup.ts` owns junction-safe fixture teardown: `unlinkFixtureLinks` walks a tree and unlinks every reparse point before `removeFixtureSafely` removes the now link-free tree (with Windows async-handle retries). Every affected `afterEach` and the pre-`worktree remove` hook call it. The general rule lives in `docs/defensive-patterns.md`: remove link-shaped paths with unlink, reserve recursive `rmSync` for known real directories.
## Alternatives considered
**Trust recursive deletion alone.** Rejected: whether a given deleter follows junctions is tool- and version-dependent, and one path through `git worktree remove` already destroyed tracked files; no cleanup may bet the repository on that behavior.
**Copy instead of junctioning the real directories.** Rejected: the fixtures exist to probe the real installer paths through their real contents, so copies would stop exercising the boundary under test.
## Consequences
Fixture teardown can no longer reach repository sources through junctions. The extra walk is one lstat/unlink pass over small fixture trees. The data-destroying defect now has its durable why beside the defensive-patterns rule, and the helper is the shared teardown path for future junction fixtures.
@@ -0,0 +1,23 @@
# Agent Note: 递归删除前先解链 fixture junction
Status: implemented
[English](2026-08-12-unlink-fixture-junctions-before-delete.md) | 中文
## 问题
install-lefthook 与 translation-pairing 的 fixture 把仓库真实的 `scripts/``node_modules` 和 tsx 包目录用 junction 链进 fixture 树,让 installer 探测能穿透解析。Windows 的递归删除可能把 junctionMOUNT_POINT 重解析点)当作目录并跟随进其目标;Git 的 `worktree remove` 正是这样删掉了仓库被跟踪的 `scripts/` 和 tsx 包(事故的插桩把删除定位到这一步)。因此,信任删除器的 fixture 清理删掉的是仓库自己的源码,而不是 fixture。
## 决策
`scripts/test-fixture-cleanup.ts` 拥有 junction 安全的 fixture 拆除:`unlinkFixtureLinks` 先遍历并解链所有重解析点,`removeFixtureSafely` 再删除已无链接的树(带 Windows 异步句柄重试)。所有受影响的 `afterEach``worktree remove` 前的钩子都调用它。通用规则记录在 `docs/defensive-patterns.md`:链接形态的路径用 unlink 删除,递归 `rmSync` 只留给确知为真实目录的路径。
## 考虑过的替代方案
**只信任递归删除。** 否决:特定删除器是否跟随 junction 随工具和版本而异,而 `git worktree remove` 这一条路径已经摧毁过被跟踪文件;任何清理都不该拿仓库去赌这个行为。
**复制而不是 junction 真实目录。** 否决:fixture 的意义就是用真实内容探测真实 installer 路径,复制品会失去被测边界。
## 后果
fixture 拆除不再能穿过 junction 触及仓库源码。额外开销只是对小型 fixture 树的一趟 lstat/unlink。这个摧毁数据的缺陷现在在 defensive-patterns 规则旁有了持久化的原因,helper 也是未来所有 junction fixture 共享的拆除路径。
@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-08-12-unlink-stale-profile-fallback-links.md
2026-08-12-unlink-stale-profile-fallback-links.md: 32959eb1b83bcc290d1daa4e4a020a2721be489b
2026-08-12-unlink-stale-profile-fallback-links.zh.md: 1f4da12748c9b57c12bf41a740001d5df770beb6
@@ -0,0 +1,25 @@
# Agent Note: Unlink stale profile fallback links instead of rmSync
Status: implemented
English | [中文](2026-08-12-unlink-stale-profile-fallback-links.zh.md)
## Problem
`healProfilesModuleFallback` re-points `$DSH_HOME/profiles/node_modules` entries when an installation moves, and Windows hosts keep those entries as junctions. `ensureSymlink` deleted a stale entry with `rmSync(link)`, but Node treats a junction as a directory for removal: without `recursive`, `rmSync` throws `ERR_FS_EISDIR`, so every launch from a moved installation or a second worktree crashed before booting. The `replaces a wrong symlink` unit test reproduces that crash on Windows at the exact removal call.
## Decision
`ensureSymlink` removes a stale link with `unlinkSync(link)`. `unlink` deletes the reparse point or symlink itself on every platform and never descends into the target, which preserves the function's fail-loud guarantee that a real directory is never deleted. The [profile-plugin-bundles decision](../architecture/2026-08-05-profile-plugin-bundles.md) keeps owning the fallback's two-anchor resolution; this note owns only the removal primitive.
## Alternatives considered
**`rmSync(link, { recursive: true })`.** On Node 24 this deletes the junction without following its target, but `recursive` would silently delete a real directory that replaced the link between the `lstat` guard and the removal, weakening the fail-loud contract that motivates the guard.
**`rmdirSync(link)`.** Removes a junction on Windows as well, but it reads as directory removal for a link, and `unlinkSync` is the repository's existing junction-cleanup idiom.
**Delete and recreate every entry unconditionally.** Correct but churns unchanged links on every launch and widens the concurrent-heal race window.
## Consequences
Windows launches heal moved or second-checkout installations instead of crashing with `ERR_FS_EISDIR`; POSIX behavior is unchanged because `unlinkSync` also unlinks plain symlinks. The existing `replaces a wrong symlink` test now passes on Windows where it previously reproduced the crash. Two concurrent healers deleting the same stale link still surface the second deletion as `ENOENT`, unchanged from the previous `rmSync` implementation.
@@ -0,0 +1,25 @@
# Agent Note: 用 unlink 删除过期的 profile 回退链接而非 rmSync
Status: implemented
[English](2026-08-12-unlink-stale-profile-fallback-links.md) | 中文
## 问题
`healProfilesModuleFallback` 在安装位置迁移时会把 `$DSH_HOME/profiles/node_modules` 中的条目重新指向新目标,而 Windows 主机上这些条目是 junction。`ensureSymlink` 原先用 `rmSync(link)` 删除过期条目,但 Node 在删除时把 junction 当作目录处理:不带 `recursive``rmSync` 会抛 `ERR_FS_EISDIR`,于是从迁移后的安装或第二个 worktree 启动时,每次都会在应用引导前崩溃。`replaces a wrong symlink` 单元测试在 Windows 上正好在该删除调用处复现了这一崩溃。
## 决策
`ensureSymlink` 改用 `unlinkSync(link)` 删除过期链接。`unlink` 在所有平台上都只删除重解析点或符号链接本身、绝不进入目标目录,从而保住该函数“真实目录永远不会被删除”的大声失败保证。[profile-plugin-bundles 决策](../architecture/2026-08-05-profile-plugin-bundles.md)继续拥有回退目录的双锚点解析;本 note 只拥有“用哪个删除原语”这一决定。
## 考虑过的替代方案
**`rmSync(link, { recursive: true })`。** Node 24 上它只删 junction、不跟随目标,但 `recursive` 会在 `lstat` 守卫与删除之间链接被替换成真实目录时静默删除该目录,削弱守卫存在所依据的大声失败契约。
**`rmdirSync(link)`。** Windows 上同样能删 junction,但它读起来像“删目录”,而 `unlinkSync` 才是仓库现有的 junction 清理惯例。
**无条件删除并重建所有条目。** 正确,但每次启动都翻动未变化的链接,并扩大并发修复的竞态窗口。
## 后果
Windows 启动现在可以修复迁移后的安装或第二个 checkout,而不是以 `ERR_FS_EISDIR` 崩溃;POSIX 行为不变,因为 `unlinkSync` 同样能 unlink 普通符号链接。现有的 `replaces a wrong symlink` 测试在 Windows 上从复现崩溃变为通过。两个并发 healer 删除同一过期链接时,第二次删除仍会以 `ENOENT` 浮现,与原先的 `rmSync` 实现一致。
@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-08-13-bounded-cold-blank-verification.md
2026-08-13-bounded-cold-blank-verification.md: bf8d167d742001ce65b3e96713a9603adb19603e
2026-08-13-bounded-cold-blank-verification.zh.md: 7cfef77a02308a8e75281877f8a774b41bb9559d
@@ -0,0 +1,37 @@
# Agent Note: Bound cold blank-session verification
Status: implemented
English | [中文](2026-08-13-bounded-cold-blank-verification.zh.md)
## Problem
The Web session tree hides blank Sessions and reuses the selected blank entry as New Session. Attached Sessions can derive blankness from their in-memory event log, but `session.list` normally avoids loading every cold log. Treating every materialized cold Session as non-blank exposes empty Sessions left by older versions. Treating a projection-cache `blank: true` as current can instead hide a real conversation after the log advances and the fail-soft cache remains stale.
The same cold list used the JSONL artifact mtime for `updatedAt`. Opening a Session appends `session/end-seed`, so a pickup with no human prompt refreshed mtime and promoted that Session above recently used conversations.
## Decision
`dsh-host-apiproxy` registers `sessionListMetadata`, a projection containing `blank` and `lastPromptAt`. The attached summary folds the same functions directly over the live log. `blank` changes only from true to false on `turn/start`; `lastPromptAt` changes only on a `user/message` whose source kind is `user`.
A cold summary trusts cached `blank: false`, because a checkpoint prefix containing `turn/start` remains non-blank. Cached `blank: true` and a cache miss do not prove the current log is blank. When persistence exposes a physical artifact through `locate()` and its observed size is at most the `coldBlankProbeMaxBytes` eligibility threshold (default 1 KiB per Session), the gateway calls `readFrom(id, 0)` and folds exact list metadata from the stored prefix. Files above the threshold, backends without a location, vanished artifacts, and failed reads all produce `blank: false`, keeping the Session visible.
`updatedAt` is the later of `createdAt` and `lastPromptAt`. An eligible artifact read supplies exact `lastPromptAt` at no additional I/O cost; other cache misses or stale checkpoints order the Session too old rather than promoting it from an unrelated file write. After each asynchronous cold read, the gateway checks the live store again and replaces the cold result with an attached summary when another request resumed that Session meanwhile.
## Alternatives considered
**Trust cached `blank: true`.** Rejected because the projection cache deliberately permits a persisted log to advance beyond its checkpoint. A crash or fail-soft write failure after the first `turn/start` would hide a real conversation and could make the client reuse it as New Session.
**Read every cold log.** Rejected because list latency and I/O would scale with total stored conversation bytes. The physical-size eligibility check targets small historical artifacts that can be checked cheaply and degrades larger unknowns toward visibility. It intentionally does not add a persistence operation solely to make the threshold atomic with the read: concurrent growth may increase one probe's read cost, but the additional events can only preserve visibility or change a blank result to non-blank.
**Store blankness and recency in an authoritative persistence index.** Deferred because JSONL has an immutable first line and would require a second durable artifact with ordered updates, while SQLite would require a schema field. The broader exact-index design remains in the [last-activity proposal](../../proposed/architecture/2026-07-29-durable-last-activity-index.md).
**Continue ordering JSONL by mtime.** Rejected because mtime records every artifact write, including pickup boundaries, rather than the latest human prompt. Its error direction promotes untouched Sessions to the front.
## Consequences
Existing small blank JSONL artifacts are hidden without depending on projection-cache availability, and a stale cache cannot hide a stored `turn/start`. A cold list may read each artifact whose observed physical size is within the configured threshold when its cache does not already prove non-blank. The default threshold compares compressed bytes for the shipped Zstandard JSONL backend.
Blank artifacts above the threshold and blank Sessions on location-less backends remain visible. Missing or delayed recency cache entries for artifacts that are not read fall back to `createdAt`. These are conservative degradations: the UI may show an extra empty row or order a Session too low, but it does not hide a conversation or promote one because it was merely opened.
The gateway-owned projection is an effect of the gateway fiber; unloading the gateway removes the key. Unit coverage pins exact-threshold eligibility, stale-true rejection, monotonic false reuse, exact small-log recency, live-attachment races, fallback direction, human-prompt recency, and fiber disposal. A keyless Web snapshot boots the shipped compressed JSONL composition, seeds a small cold blank artifact without a cache row, and verifies that the sidebar omits it.
@@ -0,0 +1,37 @@
# Agent Note: 有界验证冷空白会话
Status: implemented
[English](2026-08-13-bounded-cold-blank-verification.md) | 中文
## Problem
Web 会话树会隐藏空白 Session,并把当前选中的空白项复用为 New Session。已附加 Session 可以从内存事件日志派生空白状态,但 `session.list` 通常不会加载每一份冷日志。把所有已物化的冷 Session 都视为非空,会暴露旧版本留下的空 Session;反过来,把 projection cache 中的 `blank: true` 当成当前事实,则可能在日志已经前进而 fail-soft cache 仍然陈旧时隐藏真实对话。
同一份冷列表还曾用 JSONL 工件的 mtime 作为 `updatedAt`。打开 Session 会追加 `session/end-seed`,因此即使没有真人 prompt,单纯拾起也会刷新 mtime,并把该 Session 提升到最近使用的对话之前。
## Decision
`dsh-host-apiproxy` 注册 `sessionListMetadata` 投影,其中包含 `blank``lastPromptAt`。已附加摘要直接用同一组函数折叠实时日志。`blank` 只在 `turn/start` 时从 true 单调变为 false`lastPromptAt` 只在来源 kind 为 `user``user/message` 上更新。
冷摘要信任缓存的 `blank: false`,因为已包含 `turn/start` 的 checkpoint 前缀会始终保持非空。缓存的 `blank: true` 和 cache miss 都无法证明当前日志为空。当 persistence 通过 `locate()` 暴露物理工件,且其观测大小不超过 `coldBlankProbeMaxBytes` 资格阈值(默认每个 Session 1 KiB)时,网关调用 `readFrom(id, 0)`,从已存前缀折叠精确列表元数据。超过阈值的文件、不提供位置的后端、已消失的工件和读取失败都产生 `blank: false`,让 Session 保持可见。
`updatedAt``createdAt``lastPromptAt` 中较晚者。符合资格的工件读取无需额外 I/O 即可提供精确 `lastPromptAt`;其他 cache miss 或陈旧 checkpoint 只会让 Session 排得偏旧,而不会因无关的文件写入被提升。每次异步冷读取后,网关都会再次检查实时 store;若另一请求期间已恢复该 Session,则用已附加摘要替换冷结果。
## Alternatives considered
**信任缓存的 `blank: true`。** 拒绝,因为 projection cache 有意允许持久日志前进到 checkpoint 之后。首个 `turn/start` 之后若发生崩溃或 fail-soft 写入失败,真实对话就会被隐藏,客户端还可能把它复用为 New Session。
**读取每一份冷日志。** 拒绝,因为列表延迟与 I/O 会随所有已存对话的总字节数增长。物理大小资格检查只针对能够低成本核验的小型历史工件,更大的未知项则向保持可见降级。该检查有意不为“让阈值与读取原子化”单独新增 persistence 操作:并发增长可能增加一次探测的读取成本,但新增事件只会保持可见,或把空白结果改为非空。
**把空白状态与最近时间存入权威 persistence index。** 暂缓,因为 JSONL 的首行不可变,需要增加带有顺序写入要求的第二份持久工件;SQLite 则需要 schema 字段。更广泛的精确索引设计仍由[最后活动提案](../../proposed/architecture/2026-07-29-durable-last-activity-index.md)负责。
**继续按 mtime 排序 JSONL。** 拒绝,因为 mtime 记录包括拾起边界在内的每一次工件写入,而非最近真人 prompt;其错误方向会把未经操作的 Session 提升到列表开头。
## Consequences
既有的小型空白 JSONL 工件无需依赖 projection cache 是否存在即可被隐藏,陈旧 cache 也无法隐藏已存的 `turn/start`。对于 cache 尚不能证明非空,且观测物理大小在配置阈值内的每个 Session,冷列表可能读取其工件。对默认交付的 Zstandard JSONL 后端,该阈值比较压缩后的字节数。
超过阈值的空白工件,以及来自不提供位置的后端的空白 Session 会保持可见。对于未被读取的工件,缺失或延迟的最近时间 cache 会回退到 `createdAt`。这些都是保守降级:UI 可能多显示一条空记录,或把 Session 排得偏低,但不会隐藏真实对话,也不会因为单纯打开而把会话提升到前面。
网关自有投影是网关 fiber 的 effect;卸载网关会移除该 key。单元覆盖固定了临界大小资格、拒绝陈旧 true、复用单调 false、小日志精确最近时间、实时附加竞态、回退方向、真人 prompt 最近时间和 fiber 销毁。无密钥 Web snapshot 会启动发行版的压缩 JSONL 组合,在没有 cache row 的情况下播种一份小型冷空白工件,并验证侧栏不展示它。
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority; # side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with: # after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-27-trajectory-inspection-ledger.md # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-27-trajectory-inspection-ledger.md
2026-07-27-trajectory-inspection-ledger.md: 74ed1f8ec6f6efcbf77e9caec7e254cb114efbd9 2026-07-27-trajectory-inspection-ledger.md: 7f97bc395ed694a0a750d3b744cc9887834ef32f
2026-07-27-trajectory-inspection-ledger.zh.md: be648cb73478037979fd941753129b7c68be5085 2026-07-27-trajectory-inspection-ledger.zh.md: 34bb8741124c410fe06ec18bdde801caa987f4fb
@@ -21,7 +21,7 @@ Trajectory has to make prose, machine payloads, token usage, timing, and nested
- Call schemas come from the active recorded Request header. Keyless snapshot fixtures deliberately replace that catalog with the non-array `{{tools}}` token, which the durable inspection boundary treats as unavailable instead of attempting to project or fabricate schemas. - Call schemas come from the active recorded Request header. Keyless snapshot fixtures deliberately replace that catalog with the non-array `{{tools}}` token, which the durable inspection boundary treats as unavailable instead of attempting to project or fabricate schemas.
- Selecting a record or Request opens an inspector inside Trajectory. Tabs and Summary sections follow the selected entity: Markdown messages expose rendered content, source fields, provider/model fields, and hierarchy views; tools add JSON payload/result and schema views; Requests add options, usage, timing, and result navigation. Scrollable Summary regions keep their scrollbar thumbs transparent until hover or `focus-within`, while retaining the scrollbar reservation and scroll behavior. Images render as media rather than serialized data. - Selecting a record or Request opens an inspector inside Trajectory. Tabs and Summary sections follow the selected entity: Markdown messages expose rendered content, source fields, provider/model fields, and hierarchy views; tools add JSON payload/result and schema views; Requests add options, usage, timing, and result navigation. Scrollable Summary regions keep their scrollbar thumbs transparent until hover or `focus-within`, while retaining the scrollbar reservation and scroll behavior. Images render as media rather than serialized data.
- Turn folding removes all rows after its first record and replaces them with a compact step/tool-call count; Assistant folding applies the same interaction to its tool-call descendants. Global controls fold or expand both levels. - Turn folding removes all rows after its first record and replaces them with a compact step/tool-call count; Assistant folding applies the same interaction to its tool-call descendants. Global controls fold or expand both levels.
- A long ledger initially positions the loaded tail at the bottom and mounts only the viewport's row window plus bounded overscan. Request-only separators join the next measurable virtual item, with a terminal separator retaining its own fixed clearance, so the virtualizer never owns a zero-height item. Semantic DOM-safe row keys and ARIA indexes expose identity independently from mount position. A tail with known older history virtualizes immediately even when its loaded projection is below the ordinary row threshold. Stable-key virtualizer anchoring preserves the visible item across prepends and appends; the manual scroll-height fallback applies only when completing pagination disables virtualization. Selection, timeline focus, folding, search, and bottom following address records by stable event or tool-call identity rather than requiring their DOM rows to exist. An explicit loading row covers records until initial positioning finishes and while an older Session page is pending. - A long ledger initially positions the loaded tail at the bottom and mounts only the viewport's row window plus bounded overscan. Request-only separators join the next measurable virtual item, with a terminal separator retaining its own fixed clearance, so the virtualizer never owns a zero-height item. Semantic DOM-safe row keys and ARIA indexes expose identity independently from mount position. A tail with known older history virtualizes immediately even when its loaded projection is below the ordinary row threshold. Stable-key virtualizer anchoring preserves the visible item across prepends and appends; the manual scroll-height fallback applies only when completing pagination disables virtualization. Selection, timeline focus, folding, search, and bottom following address records by stable event or tool-call identity rather than requiring their DOM rows to exist. An explicit loading row covers records until initial positioning finishes. While an older Session prefix remains unloaded, an interactive first row precedes the loaded records and requests one older page; the same row becomes a disabled loading status for a pending page and disappears only when paging completes.
- The separate Waterfall tab is removed. A fixed Overview above the ledger projects every loaded record with known `startedAt` onto three semantic timing lanes using its own duration. While an older prefix remains unloaded and the viewport includes the loaded domain's start, a neutral ellipsis control covers the truncated edge and loads one earlier page without assigning unknown history a fabricated duration; hovering that control suppresses the ordinary timeline cursor. Finalized Assistant spans divide the recorded interval at the first non-empty token delta, so distinct TTFT and decoding colors retain their actual ratio; incomplete timing falls back to one Assistant color. Hovering for 500 ms exposes exact start/end, total duration, TTFT, and decoding time without relying on the browser's native tooltip delay. Dragging left or right commits an inclusive interval filter: any record whose active interval overlaps either boundary remains visible, records without known timing leave the focused ledger, and clearing the selection restores the full loaded ledger. Wheel gestures zoom the time domain. A right-button click clears the interval selection; dragging instead pans an already zoomed viewport without mutating it. The Overview keeps the full time domain while focused so the selection can be resized or cleared without losing orientation. - The separate Waterfall tab is removed. A fixed Overview above the ledger projects every loaded record with known `startedAt` onto three semantic timing lanes using its own duration. While an older prefix remains unloaded and the viewport includes the loaded domain's start, a neutral ellipsis control covers the truncated edge and loads one earlier page without assigning unknown history a fabricated duration; hovering that control suppresses the ordinary timeline cursor. Finalized Assistant spans divide the recorded interval at the first non-empty token delta, so distinct TTFT and decoding colors retain their actual ratio; incomplete timing falls back to one Assistant color. Hovering for 500 ms exposes exact start/end, total duration, TTFT, and decoding time without relying on the browser's native tooltip delay. Dragging left or right commits an inclusive interval filter: any record whose active interval overlaps either boundary remains visible, records without known timing leave the focused ledger, and clearing the selection restores the full loaded ledger. Wheel gestures zoom the time domain. A right-button click clears the interval selection; dragging instead pans an already zoomed viewport without mutating it. The Overview keeps the full time domain while focused so the selection can be resized or cleared without losing orientation.
- Live history updates retain the ledger's bottom position only while the user is already following its tail. Scrolling upward clears that follow state, so streamed chunks and newly appended records do not interrupt inspection of earlier rows. Tail following and virtualizer measurement react to row keys and heights rather than content identity, so text-only stream frames neither discard the measurement cache nor repeat a DOM scroll write. - Live history updates retain the ledger's bottom position only while the user is already following its tail. Scrolling upward clears that follow state, so streamed chunks and newly appended records do not interrupt inspection of earlier rows. Tail following and virtualizer measurement react to row keys and heights rather than content identity, so text-only stream frames neither discard the measurement cache nor repeat a DOM scroll write.
- Token streaming updates only the matching Trajectory Assistant Context, while publication is coalesced to at most once per animation frame. The target snapshot preserves the existing stage, layout, Request numbering, Overview, and search inputs; completed Assistant State retains assembled blocks, timing, and usage rather than every raw chunk payload, while Session keeps the raw Event window. - Token streaming updates only the matching Trajectory Assistant Context, while publication is coalesced to at most once per animation frame. The target snapshot preserves the existing stage, layout, Request numbering, Overview, and search inputs; completed Assistant State retains assembled blocks, timing, and usage rather than every raw chunk payload, while Session keeps the raw Event window.
@@ -21,7 +21,7 @@ Status: implemented
- 调用 schema 来自当前生效且已记录的请求头。无密钥快照 fixture(测试前置数据)有意将该目录替换为非数组 token `{{tools}}`,持久化检查边界会将其视为不可用,而不是尝试投影或虚构 schema。 - 调用 schema 来自当前生效且已记录的请求头。无密钥快照 fixture(测试前置数据)有意将该目录替换为非数组 token `{{tools}}`,持久化检查边界会将其视为不可用,而不是尝试投影或虚构 schema。
- 选择记录或请求后,Trajectory 内部会打开检查器,其标签页和概述区域随实体类型变化:Markdown 消息提供渲染内容、来源字段、提供方/模型字段和层级视图;工具提供 JSON 载荷/结果和 schema 视图;请求提供选项、用量、计时和结果跳转。可滚动的概述区域默认保持滚动条滑块透明,直到悬停或 `focus-within` 时才显示,同时保留滚动条预留空间和滚动行为。图片以媒体形式渲染,而不是显示为序列化数据。 - 选择记录或请求后,Trajectory 内部会打开检查器,其标签页和概述区域随实体类型变化:Markdown 消息提供渲染内容、来源字段、提供方/模型字段和层级视图;工具提供 JSON 载荷/结果和 schema 视图;请求提供选项、用量、计时和结果跳转。可滚动的概述区域默认保持滚动条滑块透明,直到悬停或 `focus-within` 时才显示,同时保留滚动条预留空间和滚动行为。图片以媒体形式渲染,而不是显示为序列化数据。
- 折叠轮次时保留其第一条记录,并用紧凑的步骤数和工具调用数替换后续所有行;折叠助手时对其工具调用后代应用相同操作。全局控件会折叠或展开这两个层级。 - 折叠轮次时保留其第一条记录,并用紧凑的步骤数和工具调用数替换后续所有行;折叠助手时对其工具调用后代应用相同操作。全局控件会折叠或展开这两个层级。
- 长记录表初始时将已加载尾部置于底部,只挂载视口对应的行窗口及有界的额外缓冲行。仅含请求的分隔行并入下一个具备可测高度的虚拟项,末尾分隔行则保留固定留白,因此虚拟化器不会管理零高度项。可安全用于 DOM 的语义行键与 ARIA 索引使标识不依赖挂载位置。只要已知尾部之前仍有更早历史,即使当前已加载投影低于常规行数阈值,也会立即启用虚拟化。基于稳定键的虚拟化器锚定会在向前补页和尾部追加时保留当前可见项;只有分页完成导致虚拟化停用时,才使用手动滚动高度兜底。选择、时间线聚焦、折叠、搜索和末尾跟随均按稳定的事件或工具调用标识定位,不要求对应 DOM 行已存在。初始定位完成前以及更早 Session 页面仍在等待时,明确的加载行会遮住真实记录 - 长记录表初始时将已加载尾部置于底部,只挂载视口对应的行窗口及有界的额外缓冲行。仅含请求的分隔行并入下一个具备可测高度的虚拟项,末尾分隔行则保留固定留白,因此虚拟化器不会管理零高度项。可安全用于 DOM 的语义行键与 ARIA 索引使标识不依赖挂载位置。只要已知尾部之前仍有更早历史,即使当前已加载投影低于常规行数阈值,也会立即启用虚拟化。基于稳定键的虚拟化器锚定会在向前补页和尾部追加时保留当前可见项;只有分页完成导致虚拟化停用时,才使用手动滚动高度兜底。选择、时间线聚焦、折叠、搜索和末尾跟随均按稳定的事件或工具调用标识定位,不要求对应 DOM 行已存在。初始定位完成前,明确的加载行会遮住真实记录。更早 Session 前缀仍未加载时,交互式首行位于已加载记录之前,可请求一页更早历史;页面加载期间,同一行会变为禁用的加载状态,仅在分页完成时消失
- 移除独立的 waterfall(瀑布式事件)标签页。固定在记录表上方的 Overview 区域将所有 `startedAt` 已知的已加载记录按各自耗时投影到三条语义计时轨道。仍有更早前缀尚未加载且 viewport 包含已加载时间域起点时,中性的省略号控件会遮住截断边缘并加载一页更早历史,而不会为未知历史虚构耗时;悬停在该控件上会隐藏普通的时间线光标。已完成的助手时间条以首个非空 token 增量为分界,用不同颜色按真实比例表示 TTFT 与解码时间;计时不完整时退化为单一助手色。悬停 500 ms 后会显示精确起止时刻、总耗时、TTFT 和解码时间,而不依赖浏览器原生 tooltip 的延迟。向左或向右拖动会提交包含边界的区间筛选:任何活动区间与所选区间任一边界重叠的记录都会保留,计时未知的记录会从聚焦后的记录表中移除,清除选择则恢复完整的已加载记录表。滚轮手势用于缩放时间域。右键单击会清除区间选择;右键拖动则只会平移已放大的 viewport,不会改变该选区。聚焦后,Overview 区域仍保留完整时间范围,以便在不失去方位的情况下调整或清除选择。 - 移除独立的 waterfall(瀑布式事件)标签页。固定在记录表上方的 Overview 区域将所有 `startedAt` 已知的已加载记录按各自耗时投影到三条语义计时轨道。仍有更早前缀尚未加载且 viewport 包含已加载时间域起点时,中性的省略号控件会遮住截断边缘并加载一页更早历史,而不会为未知历史虚构耗时;悬停在该控件上会隐藏普通的时间线光标。已完成的助手时间条以首个非空 token 增量为分界,用不同颜色按真实比例表示 TTFT 与解码时间;计时不完整时退化为单一助手色。悬停 500 ms 后会显示精确起止时刻、总耗时、TTFT 和解码时间,而不依赖浏览器原生 tooltip 的延迟。向左或向右拖动会提交包含边界的区间筛选:任何活动区间与所选区间任一边界重叠的记录都会保留,计时未知的记录会从聚焦后的记录表中移除,清除选择则恢复完整的已加载记录表。滚轮手势用于缩放时间域。右键单击会清除区间选择;右键拖动则只会平移已放大的 viewport,不会改变该选区。聚焦后,Overview 区域仍保留完整时间范围,以便在不失去方位的情况下调整或清除选择。
- 实时历史更新仅在用户已经跟随记录表末尾时保留底部位置。向上滚动会清除跟随状态,因此流式分块和新追加的记录不会打断对旧记录的检查。末尾跟随与虚拟化器测量仅响应行键和高度,而非内容标识,因此仅含文本的流式帧既不会丢弃测量缓存,也不会重复执行 DOM 滚动写入。 - 实时历史更新仅在用户已经跟随记录表末尾时保留底部位置。向上滚动会清除跟随状态,因此流式分块和新追加的记录不会打断对旧记录的检查。末尾跟随与虚拟化器测量仅响应行键和高度,而非内容标识,因此仅含文本的流式帧既不会丢弃测量缓存,也不会重复执行 DOM 滚动写入。
- token 流式输出只更新命中的 Trajectory Assistant Context,发布则合并为每个 animation frame 最多一次。target snapshot 继续提供既有 stage、layout、请求编号、Overview 与搜索输入;已完成的 Assistant State 只保留组装后的 blocks、计时与 usage,不保留每条原始 chunk payload,而 Session 继续保存原始 Event 窗口。 - token 流式输出只更新命中的 Trajectory Assistant Context,发布则合并为每个 animation frame 最多一次。target snapshot 继续提供既有 stage、layout、请求编号、Overview 与搜索输入;已完成的 Assistant State 只保留组装后的 blocks、计时与 usage,不保留每条原始 chunk payload,而 Session 继续保存原始 Event 窗口。
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority; # side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with: # after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-08-10-npm-release-sequences.md # pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-08-10-npm-release-sequences.md
2026-08-10-npm-release-sequences.md: e8138aef923e201cc0883232f48ce6921452ca00 2026-08-10-npm-release-sequences.md: e74a4ac8f2aadd8665ec0db198c6a317a0c201bc
2026-08-10-npm-release-sequences.zh.md: 7ce9fa01dbe10cbdae5585f64392d0ca6a9fb868 2026-08-10-npm-release-sequences.zh.md: e152163976f945224f2524fccd7f831ba98e8161
@@ -70,6 +70,10 @@ Publication runs only from GitHub Actions; there is no local publication path. P
The third state catches code that changed without a version bump. The first two provide idempotence — re-running publish over one artifact republishes nothing and needs no manual selection of packages. The same rule resolves the tension between one vendor release carrying several tags and a workflow that can only run from one ref: the workflow never infers which packages to publish from the tag it ran from. The third state catches code that changed without a version bump. The first two provide idempotence — re-running publish over one artifact republishes nothing and needs no manual selection of packages. The same rule resolves the tension between one vendor release carrying several tags and a workflow that can only run from one ref: the workflow never infers which packages to publish from the tag it ran from.
All three sequences decide this way, including the native one: it publishes through its own script rather than a shell loop, because a loop of bare `npm publish` calls cannot be retried — the registry answers a repeat of an existing version permanently, so one failure partway through left no way forward.
Two registry behaviours shape how a publish is attempted. Writes are spaced by at least two seconds and retried with a backoff, because publishing several packages back to back outruns the registry's own processing and earns `E409 Failed to save packument`. And every retry re-reads the registry first: a reported failure can answer a write that landed anyway, so a version that now exists with this tarball's integrity counts as published rather than as a version to place again.
### Workspace-internal references use the `workspace:` protocol ### Workspace-internal references use the `workspace:` protocol
Every reference to a workspace member uses `workspace:^`, so `pnpm pack` substitutes a range matching the target version: sibling `peerDependencies` follow the family version, and a reference to a vendored package follows that package's own line. The Landlock platform packages keep `workspace:*`, which publishes the exact version, because a platform package and its entry must agree exactly. Every reference to a workspace member uses `workspace:^`, so `pnpm pack` substitutes a range matching the target version: sibling `peerDependencies` follow the family version, and a reference to a vendored package follows that package's own line. The Landlock platform packages keep `workspace:*`, which publishes the exact version, because a platform package and its entry must agree exactly.
@@ -70,6 +70,10 @@ tag 只是 commit 指针,不是发布成功的证明。bump 会向 registry
第三态拦住「改了代码却没 bump 版本」。前两态给出幂等——同一个 artifact 重跑 publish 不会重复发布,也不需要人工挑拣包。同一条规则还解决了「一次 vendor 发布携带多个 tag,而 workflow 只能从一个 ref 触发」的矛盾:workflow 从不从触发它的 tag 去推断该发哪些包。 第三态拦住「改了代码却没 bump 版本」。前两态给出幂等——同一个 artifact 重跑 publish 不会重复发布,也不需要人工挑拣包。同一条规则还解决了「一次 vendor 发布携带多个 tag,而 workflow 只能从一个 ref 触发」的矛盾:workflow 从不从触发它的 tag 去推断该发哪些包。
三条序列都按这套判定,native 也在内:它通过自己的脚本发布,而不是 shell 循环——一串裸 `npm publish` 无法重试,registry 对「重发已存在的版本」的回答是永久失败,因此中途失败一次就没有前路了。
registry 的两个行为决定了「怎么尝试一次发布」。写入之间至少间隔两秒并带退避重试,因为连续背靠背发多个包会超出 registry 自身的处理速度,换来 `E409 Failed to save packument`。而每次重试都先重查 registry:报出来的失败可能对应一次其实已经落地的写入,所以「该版本现在存在且 integrity 与本 tarball 相同」算作已发布,而不是又一个待放置的版本。
### workspace 内部引用走 `workspace:` 协议 ### workspace 内部引用走 `workspace:` 协议
所有指向 workspace 成员的引用都用 `workspace:^`,由 `pnpm pack` 替换成匹配目标版本的范围:兄弟包的 `peerDependencies` 跟随族版本,指向 vendored 包的引用跟随那个包自己的版本线。Landlock 平台包保留 `workspace:*`(发布成精确版本),因为平台包与它的入口必须版本完全一致。 所有指向 workspace 成员的引用都用 `workspace:^`,由 `pnpm pack` 替换成匹配目标版本的范围:兄弟包的 `peerDependencies` 跟随族版本,指向 vendored 包的引用跟随那个包自己的版本线。Landlock 平台包保留 `workspace:*`(发布成精确版本),因为平台包与它的入口必须版本完全一致。
@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-08-13-published-document-fragments.md
2026-08-13-published-document-fragments.md: 4146a592a97b5d8b4e1d3fbabd0f035074cc8001
2026-08-13-published-document-fragments.zh.md: 6cf76dfa349bbd61d1dd51cb683b696e4c405435
@@ -0,0 +1,27 @@
# Agent Note: Validate published document fragments
Status: implemented
English | [中文](2026-08-13-published-document-fragments.zh.md)
## Problem
`verify-md-links` validates fragments with GitHub's Markdown heading ids, while the documentation website renders headings with VitePress. Punctuation-heavy headings and translated headings can therefore pass source validation but produce links to ids absent from the published HTML. A successful VitePress build validates target pages, not fragment ids.
## Decision
`docs:build` and its MPA variant run `verify-doc-site-fragments` after VitePress emits `website/.dist`. The verifier parses every emitted HTML page, resolves each internal fragment link against VitePress clean URLs, and fails when the output is absent, routes are ambiguous, an href is malformed, or either the target page or requested id is missing. Unit tests cover those failures plus clean URLs, `.html` aliases, same-page links, encoded and literal ids, and external-link exclusion.
Any fragment target heading whose GitHub id differs from its VitePress id carries an explicit GitHub-compatible alias. Authored English and translated pages place the alias before the heading; translated pages use the English id shared by the bilingual pair. Generated config, tool, and persistence catalogs emit the alias from their owning generator. Source Markdown validation remains independent and continues to reject links that do not resolve under repository rendering.
## Alternatives considered
**Use locale-specific fragments.** Bilingual pairs intentionally preserve identical link targets. Locale-specific fragments would make the two sources disagree and would require every link producer to know the target locale's translated heading.
**Rely on VitePress heading ids.** Those ids depend on rendered punctuation and localized heading text. They do not preserve the GitHub ids already used by repository links and generated references.
**Check source Markdown only.** This leaves the published artifact unverified and cannot detect differences between the GitHub and VitePress slug algorithms.
## Consequences
Every production documentation build reads its emitted HTML once, adding a bounded post-build check to the existing site build. Cross-page fragment links now require an id that survives publication. Explicit aliases become part of the published reference and let headings change language or punctuation without invalidating established fragments.
@@ -0,0 +1,27 @@
# Agent Note: 校验已发布文档的 fragment
Status: implemented
[English](2026-08-13-published-document-fragments.md) | 中文
## Problem
`verify-md-links` 使用 GitHub 的 Markdown 标题 id 校验 fragment,而文档网站使用 VitePress 渲染标题。包含较多标点的标题与翻译后的标题可能通过源码校验,却在已发布 HTML 中没有对应 id。VitePress 构建成功只会校验目标页面,不会校验 fragment id。
## Decision
`docs:build` 及其 MPA 变体会在 VitePress 生成 `website/.dist` 后运行 `verify-doc-site-fragments`。该校验器解析每个生成的 HTML 页面,按照 VitePress clean URL 解析每个内部 fragment 链接,并在构建产物不存在、路由有歧义、href 格式错误、目标页面不存在或请求的 id 缺失时失败。单元测试覆盖这些失败,以及 clean URL、`.html` 别名、同页链接、编码和字面 id 与外部链接排除。
任何 GitHub id 与 VitePress id 不同的 fragment 目标标题都会带有与 GitHub 兼容的显式别名。英文手写页面和翻译页面会在标题前添加别名;翻译页面使用双语对侧文件共享的英文 id。生成的配置、工具和持久化目录由所属生成器输出别名。源码 Markdown 校验保持独立,仍会拒绝在仓库渲染规则下无法解析的链接。
## Alternatives considered
**使用各语言专属的 fragment。** 双语对侧文件会刻意保留相同的链接目标。语言专属 fragment 会使两侧源码不一致,还会要求每个链接生成方都了解目标语言翻译后的标题。
**依赖 VitePress 标题 id。** 这些 id 取决于渲染后的标点与本地化标题文本,无法保留仓库链接和生成引用已经使用的 GitHub id。
**只检查 Markdown 源码。** 这种做法不会校验发布产物,也无法发现 GitHub 与 VitePress slug 算法之间的差异。
## Consequences
每次生产文档构建都会读取一次生成的 HTML,在现有网站构建后增加一个有界检查。跨页面 fragment 链接必须指向发布后仍存在的 id。显式别名成为已发布参考的一部分,使标题更换语言或标点后仍能保留既有 fragment。
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority; # side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with: # after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/proposed/architecture/2026-07-29-durable-last-activity-index.md # pnpm run verify-translation-pairing --write .agents/notes/proposed/architecture/2026-07-29-durable-last-activity-index.md
2026-07-29-durable-last-activity-index.md: 0e441f54a719b29a1a450c133e08cdf7d2c82e9e 2026-07-29-durable-last-activity-index.md: 99e50dd40b789db5d896cb7f9e25fa8893b02ae2
2026-07-29-durable-last-activity-index.zh.md: ebc2e2167d7743eafc5a4600fe9a0f687349e1a3 2026-07-29-durable-last-activity-index.zh.md: e317fb192d53353295e6b52f631707ba6b400b66
@@ -6,17 +6,17 @@ English | [中文](2026-07-29-durable-last-activity-index.zh.md)
## Problem ## Problem
A cold (persisted, unattached) session has no stored answer to "when was this last worked in". `dsh-host-apiproxy`'s `summarizeCold()` therefore approximates it with the log file's mtime where one exists — `locate()` resolves a per-session artifact for JSONL and `undefined` for SQLite, whose cold sessions fall back to `createdAt` and the web client sorts its session tree by the resulting `updatedAt`. The two backends are wrong in opposite directions: JSONL reads too new, SQLite too old. A cold (persisted, unattached) session has no authoritative stored answer to "when did the user last prompt here". `dsh-host-apiproxy` serves `updatedAt` from the optional projection cache's `lastPromptAt`, falling back to `createdAt`, and the Web client sorts its Session tree by that value. The cache is fail-soft and checkpointed asynchronously, so a missing or delayed row makes a recently prompted Session sort too old.
mtime answers a different question: when the artifact was last written. Every durable write refreshes it, including writes that are not activity — a truncate-repair of a torn tail, the synthetic closers that balance an interrupted turn, and the [`session/end-seed` boundary](../../implemented/architecture/2026-07-30-session-end-seed-log-boundary.md) a seeded session appends. (A `flush` with nothing pending is not among them: the coordinator returns without reaching the backend.) The visible consequence is stable and wrong in one direction: a session touched without being worked in promotes itself above sessions the user actually worked in afterwards, and each touch re-promotes it. `dsh-host-apiproxy` keeps `session.history` inspection-only, but any Agent-bound ordinary-session control resumes through `agentFor()` and is enough to promote the cold artifact. The gateway previously used JSONL artifact mtime when available. mtime answers a different question: when the artifact was last written. Every durable write refreshes it, including a truncate-repair of a torn tail, synthetic closers that balance an interrupted turn, and the [`session/end-seed` boundary](../../implemented/architecture/2026-07-30-session-end-seed-log-boundary.md) appended during pickup. That approximation promoted a Session merely because it was opened. The [bounded cold blank verification](../../implemented/bug-fix/2026-08-13-bounded-cold-blank-verification.md) removed mtime ordering and accepted the cache's conservative "too old" failure direction as an interim tradeoff.
The attached projection has a real fix — `lastActivityTime()` skips boundaries — but it needs the event log, and the cold path deliberately does not read one. Reading the log to compute `updatedAt` would defeat the header-only listing that keeps `list()` scaling with session count rather than log size. An attached summary can fold the live event log and select the latest human-authored `user/message`, but the cold path deliberately does not read large logs. Reading every log to compute `updatedAt` would make `list()` scale with total conversation bytes rather than Session count. The 1 KiB cold read used for metadata verification makes eligible small-artifact recency exact, but it does not make large-log ordering exact.
The [boundary change](../../implemented/architecture/2026-07-30-session-end-seed-log-boundary.md) raised the frequency of this defect, because a pickup now writes where nothing was written before; `dsh-host-apiproxy`'s README records it under Known Limitations. It did not introduce the approximation, and removing the approximation is a durable-format decision, which is why it is scoped here rather than there. Making cold ordering exact remains a durable-format decision, which is why it is scoped here rather than in the gateway workaround.
## Proposal ## Proposal
Store last-activity time where a listing already reads — the session index — so `summarizeCold()` can serve it without opening the log. The coordinator computes the value, because it sees every append and already owns per-id state; backends persist it. That makes it a new `PersistenceBackend` contract element rather than backend-local bookkeeping, and keeps one definition of "activity" shared with the in-log `lastActivityTime()`. Store the latest human-prompt time where a listing already reads — the Session index — so `summarizeCold()` can serve it without opening the log or depending on a cache checkpoint. The coordinator computes the value because it sees every append and already owns per-id state; backends persist it. That makes it a new `PersistenceBackend` contract element rather than backend-local bookkeeping, with the same event predicate as the attached projection: `user/message` whose `source.kind` is `user`.
The two shipped backends have opposite constraints, and the proposal is deliberately asymmetric about them: The two shipped backends have opposite constraints, and the proposal is deliberately asymmetric about them:
@@ -25,7 +25,7 @@ The two shipped backends have opposite constraints, and the proposal is delibera
Three questions must be answered before implementation, and none of them is settled here: Three questions must be answered before implementation, and none of them is settled here:
**Which events count as activity?** `lastActivityTime()` answers this for the log by excluding `session/end-seed`. A stored field encodes the rule at write time, where the writer sees one batch rather than the whole log. The two must not drift, or the attached and cold surfaces will disagree about the same session. **How is the shared predicate owned?** A stored field encodes the rule at write time, where the writer sees one batch, while the attached summary folds a whole log. Both must use one exported event predicate or reducer so new message-source variants cannot make attached and cold ordering disagree.
**How do pre-field logs behave?** Existing artifacts have no value. Falling back to mtime keeps them at today's accuracy; falling back to `createdAt` is honest but reorders every existing session in the picker and the tree. **How do pre-field logs behave?** Existing artifacts have no value. Falling back to mtime keeps them at today's accuracy; falling back to `createdAt` is honest but reorders every existing session in the picker and the tree.
@@ -39,28 +39,29 @@ Three questions must be answered before implementation, and none of them is sett
**Write the boundary only when repair occurred.** Would reduce the frequency, and the [boundary note](../../implemented/architecture/2026-07-30-session-end-seed-log-boundary.md) already rejected it: the predicate must hold for an orderly restart too. Trading a correctness invariant for timestamp accuracy is the wrong direction. **Write the boundary only when repair occurred.** Would reduce the frequency, and the [boundary note](../../implemented/architecture/2026-07-30-session-end-seed-log-boundary.md) already rejected it: the predicate must hold for an orderly restart too. Trading a correctness invariant for timestamp accuracy is the wrong direction.
**Derive activity from a projection cache.** `session-projection-cache` already folds tails past a watermark, so a last-activity unit would ride existing machinery. Rejected as the primary shape because the cache is an optional composition entry; a listing served only when a cache plugin is mounted makes ordering depend on composition. **Derive activity from a projection cache.** This is the current interim implementation. `session-projection-cache` folds tails past a watermark without changing the persistence format, but it is optional and fail-soft. Its absence or checkpoint delay makes ordering depend on cache availability and freshness, so it cannot provide the authoritative value proposed here.
## Acceptance criteria ## Acceptance criteria
- `SessionSummary.updatedAt` for a cold session equals the same value the attached projection reports for that session, verified by resuming, quitting without a turn, and asserting the order is unchanged across both paths. - `SessionSummary.updatedAt` for a cold session equals the same value the attached projection reports for that session, verified by resuming, quitting without a turn, and asserting the order is unchanged across both paths.
- A resumed-then-abandoned session does not sort above a session worked in afterwards, in the web session tree and the TUI resume picker, pinned by an assembled snapshot rather than unit tests alone. - A resumed-then-abandoned session does not sort above a session worked in afterwards, in the web session tree and the TUI resume picker, pinned by an assembled snapshot rather than unit tests alone.
- The activity rule has one definition: a test proves the stored field and `lastActivityTime()` agree over a log containing boundaries, closers, and a plain turn. - The prompt-time rule has one definition: a test proves the stored field and attached fold agree over a log containing human prompts, injected user messages, boundaries, and closers.
- Pre-field artifacts load and list without error under the chosen fallback, with the fallback's ordering consequence asserted. - Pre-field artifacts load and list without error under the chosen fallback, with the fallback's ordering consequence asserted.
- SQLite's `SCHEMA_VERSION` bump rejects the old on-disk version per the repo's no-migration stance. - SQLite's `SCHEMA_VERSION` bump rejects the old on-disk version per the repo's no-migration stance.
## Risks ## Risks
**Two definitions of activity drift.** The stored field is computed per batch, the projection over a whole log. A new event type classified one way at write time and the other at read time yields a session whose cold and attached orderings disagree — a bug that only appears after a restart, which is where it is hardest to notice. **Two definitions of prompt time drift.** The stored field is computed per batch, the projection over a whole log. A new message source classified one way at write time and the other at read time yields a Session whose cold and attached orderings disagree — a bug that only appears after restart.
**A JSONL sidecar can disagree with its log.** A crash between the log append and the sidecar write leaves a stale value with no torn-tail marker to repair it. Every consumer would need to treat the sidecar as a hint, which is close to what mtime already is. **A JSONL sidecar can disagree with its log.** A crash between the log append and the sidecar write leaves a stale value with no torn-tail marker to repair it. Every consumer would need to treat the sidecar as a hint, which is close to what mtime already is.
**The fallback reorders existing sessions.** Whichever fallback is chosen, users with existing logs see their picker and tree reorder once on upgrade. `createdAt` makes that reordering large. **The fallback reorders existing sessions.** Whichever fallback is chosen, users with existing logs see their picker and tree reorder once on upgrade. `createdAt` makes that reordering large.
**Cost may exceed the defect.** The defect is a misordering of abandoned sessions. If the honest answer for JSONL is "keep the approximation", this note's outcome may be documenting that decision rather than implementing a field — and that is an acceptable outcome. **Cost may exceed the defect.** The remaining defect is conservative misordering when projection metadata is missing or delayed. If the honest answer for JSONL is "keep the cache fallback", this note's outcome may be documenting that decision rather than implementing a field.
## Related ## Related
- [The end-seed log boundary](../../implemented/architecture/2026-07-30-session-end-seed-log-boundary.md) — one of the non-activity writes mtime counts; `dsh-session` owns `lastActivityTime()`, the in-log projection a stored field must agree with. - [Bounded cold blank verification](../../implemented/bug-fix/2026-08-13-bounded-cold-blank-verification.md) — removes mtime ordering, defines the interim projection-cache fallback, and limits direct cold reads to small-artifact metadata verification.
- [The end-seed log boundary](../../implemented/architecture/2026-07-30-session-end-seed-log-boundary.md) — one of the non-prompt writes that made mtime unsuitable.
- [Session persistence](../../implemented/architecture/2026-06-14-session-persistence.md) — the append-only and never-rewrite invariants that rule out a mutable JSONL header field. - [Session persistence](../../implemented/architecture/2026-06-14-session-persistence.md) — the append-only and never-rewrite invariants that rule out a mutable JSONL header field.
- [Shared persistence write coordinator](../../implemented/architecture/2026-06-18-shared-persistence-write-coordinator.md) — the append path a stored field would hook into. - [Shared persistence write coordinator](../../implemented/architecture/2026-06-18-shared-persistence-write-coordinator.md) — the append path a stored field would hook into.
@@ -6,17 +6,17 @@ Status: proposed
## 问题 ## 问题
一个冷会话(已持久化、未附加)对「上次是什么时候在这里面工作过」没有任何已存储答案。因此 `dsh-host-apiproxy` `summarizeCold()` 在存在日志文件时用它的 mtime 来近似它——`locate()` 为 JSONL 解析出一个逐会话产物,为 SQLite 解析出 `undefined`,而 SQLite 的冷会话会回退到 `createdAt`——而 web 客户端就按由此得到的 `updatedAt` 为自己的会话树排序。这两个后端错的方向正好相反:JSONL 读出来偏新,SQLite 偏旧。 一个冷会话(已持久化、未附加)对「用户上次是什么时候在这里发出 prompt」没有权威的已存储答案。`dsh-host-apiproxy` 从可选 projection cache 的 `lastPromptAt` 提供 `updatedAt`,缺失时回退到 `createdAt`Web 客户端按该值为 Session 树排序。cache 采用 fail-soft 并异步写入 checkpoint,因此缺失或延迟的记录会让最近收到 prompt 的 Session 排得过旧。
mtime 回答的是另一个问题:这份产物上次是什么时候被写入。每一次持久写入都会刷新它,包括那些并不是活动的写入:一次对撕裂尾部的截断修复、用来平衡中断轮次的那些合成 closer,以及带种子的会话会追加的 [`session/end-seed` 边界](../../implemented/architecture/2026-07-30-session-end-seed-log-boundary.md)。(没有待处理内容的 `flush` 不在其中:协调器在到达后端之前就返回了。)用户可见的后果是稳定的,而且只朝一个方向错:一个被触碰过却没有在里面工作过的会话,会把自己排到用户此后真正工作过的那些会话之前,而且每次触碰都会重新把它排上去一次。`dsh-host-apiproxy``session.history` 保持只执行检查,但任何绑定到 Agent 的普通会话控件都会通过 `agentFor()` 恢复会话,足以把冷态产物排到前面 网关以前会在可用时采用 JSONL 产物的 mtime。mtime 回答的是另一件事:这份产物上次是什么时候被写入。每一次持久写入都会刷新它,包括对撕裂尾部的截断修复、平衡中断轮次的合成 closer,以及拾起时追加的 [`session/end-seed` 边界](../../implemented/architecture/2026-07-30-session-end-seed-log-boundary.md)。这套近似会让 Session 仅仅因为被打开就提升排序。[有界冷空白验证](../../implemented/bug-fix/2026-08-13-bounded-cold-blank-verification.md)移除了 mtime 排序,并把 cache 保守的「过旧」错误方向作为现阶段取舍
已附加会话的那个投影有真正的修复办法(`lastActivityTime()` 会跳过边界),但它需要事件日志,而冷路径有意不读日志。为计算 `updatedAt` 而读取日志,会让只读 header 的列举失去意义,而正是它让 `list()` 的开销随会话数量而非日志体量增长 已附加摘要可以折叠实时事件日志并选择最新的真人 `user/message`,但冷路径有意不读取大日志。为计算 `updatedAt` 而读取每一份日志,会让 `list()` 的开销随对话总字节数而非 Session 数量增长。用于 metadata 验证的 1 KiB 冷读取可以让符合条件的小产物得到精确的最近时间,但不能让大日志的排序精确
[边界那次变更](../../implemented/architecture/2026-07-30-session-end-seed-log-boundary.md)提高了这个缺陷的出现频率,因为一次拾起如今会在此前完全无写入的路径上产生写入;`dsh-host-apiproxy` 的 README 已在 Known Limitations 中记录该项。它并没有引入这套近似做法,而移除这套近似是一项持久格式决策,因此它的范围在本文,而不是那里 让冷排序变得精确仍是一项持久格式决策,因此范围在本文,而不是网关 workaround 中
## 提案 ## 提案
把最后活动时间存到列举本就会读取的地方,也就是会话索引,这样 `summarizeCold()` 无需打开日志就能给出答案。该值由协调器计算,因为它看得到每一次追加,而且本就拥有每 id 状态;由后端负责持久化。这样它就成为 `PersistenceBackend` 约定中新增的一个要素,而不是各后端本地账目,同时让「活动」只保留一个定义,与日志内的 `lastActivityTime()` 共用 把最新真人 prompt 时间存到列举本就会读取的 Session 索引,这样 `summarizeCold()` 无需打开日志或依赖 cache checkpoint 就能给出答案。该值由协调器计算,因为它看得到每一次追加,而且本就拥有每 id 状态;由后端负责持久化。这样它就成为 `PersistenceBackend` 约定中新增的一个要素,而不是各后端本地账目,并与已附加投影使用同一个事件谓词:`source.kind``user``user/message`
两个已交付的后端受到的约束正好相反,本提案对它们有意采取不对称的处理: 两个已交付的后端受到的约束正好相反,本提案对它们有意采取不对称的处理:
@@ -25,7 +25,7 @@ mtime 回答的是另一个问题:这份产物上次是什么时候被写入
实现之前必须回答三个问题,本文对它们都没有定论: 实现之前必须回答三个问题,本文对它们都没有定论:
**哪些事件算作活动?** 对日志而言,`lastActivityTime()` 通过排除 `session/end-seed` 回答了这个问题。一个已存储字段在写入时编码这条规则的,而写入方在那里只看到一个批次,不是整份日志。两者不得发生漂移,否则已附加表层与冷表层会对同一个会话给出彼此矛盾的答案 **共享谓词由谁拥有?** 已存储字段在写入时编码规则,写入方只看到一个批次,而已附加摘要折叠整份日志。两者必须使用同一个导出的事件谓词或 reducer,避免新的消息来源变体让已附加排序与冷排序发生分歧
**该字段引入之前的日志表现如何?** 既有产物里没有这个值。回退到 mtime 能让它们保持今天的准确度;回退到 `createdAt` 是诚实的,但会把选择器和会话树里每一个既有会话都重新排一次序。 **该字段引入之前的日志表现如何?** 既有产物里没有这个值。回退到 mtime 能让它们保持今天的准确度;回退到 `createdAt` 是诚实的,但会把选择器和会话树里每一个既有会话都重新排一次序。
@@ -39,28 +39,29 @@ mtime 回答的是另一个问题:这份产物上次是什么时候被写入
**仅在确实发生了修复时才写入边界。** 这能降低出现频率,而[边界 Agent Note](../../implemented/architecture/2026-07-30-session-end-seed-log-boundary.md)已经否决过它:谓词对有序重启同样必须成立。用一条正确性不变式去换时间戳的准确度,方向是错的。 **仅在确实发生了修复时才写入边界。** 这能降低出现频率,而[边界 Agent Note](../../implemented/architecture/2026-07-30-session-end-seed-log-boundary.md)已经否决过它:谓词对有序重启同样必须成立。用一条正确性不变式去换时间戳的准确度,方向是错的。
**从投影缓存派生活动时间。** `session-projection-cache` 本就会折叠水位线之后的尾部,因此一个最后活动单元可以搭乘既有机制。它作为主形态被否决,因为该缓存是一个可选的组合项;只有挂载了缓存插件才提供的列举,会让排序取决于如何组合 **从投影缓存派生活动时间。** 这是当前的过渡实现。`session-projection-cache` 会折叠水位线之后的尾部,无需改变持久格式,但它是可选且 fail-soft 的。缺失或 checkpoint 延迟会让排序取决于 cache 是否存在以及是否新鲜,因此无法提供本文所提议的权威值
## 验收标准 ## 验收标准
- 冷会话的 `SessionSummary.updatedAt` 等于已附加会话的投影为同一个会话报告的那个值;验证方式是恢复、不跑轮次就退出,并断言两条路径上的顺序都没有变化。 - 冷会话的 `SessionSummary.updatedAt` 等于已附加会话的投影为同一个会话报告的那个值;验证方式是恢复、不跑轮次就退出,并断言两条路径上的顺序都没有变化。
- 在 web 会话树和 TUI 恢复选择器中,一个恢复后即被弃置的会话不会排到此后工作过的会话之前;由一份组装后的快照钉住,而不是只靠单元测试。 - 在 web 会话树和 TUI 恢复选择器中,一个恢复后即被弃置的会话不会排到此后工作过的会话之前;由一份组装后的快照钉住,而不是只靠单元测试。
- 活动规则只有一个定义:一个测试证明,在一份同时包含边界、closer 和一个普通轮次的日志上,已存储字段与 `lastActivityTime()`结果一致。 - prompt 时间规则只有一个定义:一个测试证明,在包含真人 prompt、注入式 user message、边界和 closer 的日志上,已存储字段与已附加折叠结果一致。
- 在选定的回退方案下,该字段引入之前的产物能够无错误地加载和列举,并且该回退在排序上的后果有断言覆盖。 - 在选定的回退方案下,该字段引入之前的产物能够无错误地加载和列举,并且该回退在排序上的后果有断言覆盖。
- 按本仓库不做迁移的立场,SQLite 的 `SCHEMA_VERSION` 递增会拒绝旧的磁盘版本。 - 按本仓库不做迁移的立场,SQLite 的 `SCHEMA_VERSION` 递增会拒绝旧的磁盘版本。
## 风险 ## 风险
**「活动」的两个定义发生漂移。** 已存储字段按批次计算,而投影在整份日志上计算。一种新事件类型若在写入时按一种方式归类、在读取时按另一种方式归类,就会产生一个冷排序与已附加排序彼此矛盾的会话;这个缺陷只在重启之后才显现,而那正是最难被注意到的地方 **prompt 时间的两个定义发生漂移。** 已存储字段按批次计算,而投影在整份日志上计算。一种新消息来源若在写入时按一种方式归类、在读取时按另一种方式归类,就会产生冷排序与已附加排序彼此矛盾的 Session;该缺陷只在重启后显现
**JSONL 的伴随文件可能与它的日志不一致。** 在日志追加与伴随文件写入之间发生崩溃,会留下一个陈旧的值,而且没有撕裂尾部标记可用来修复它。每个消费方都得把伴随文件当作一条提示来对待,而这与 mtime 今天的地位已经很接近了。 **JSONL 的伴随文件可能与它的日志不一致。** 在日志追加与伴随文件写入之间发生崩溃,会留下一个陈旧的值,而且没有撕裂尾部标记可用来修复它。每个消费方都得把伴随文件当作一条提示来对待,而这与 mtime 今天的地位已经很接近了。
**回退方案会让既有会话重新排序。** 无论选定哪种回退,持有既有日志的用户都会在升级时看到自己的选择器和会话树重新排一次序。选 `createdAt` 会让这次重排的幅度很大。 **回退方案会让既有会话重新排序。** 无论选定哪种回退,持有既有日志的用户都会在升级时看到自己的选择器和会话树重新排一次序。选 `createdAt` 会让这次重排的幅度很大。
**代价可能超过这个缺陷本身。** 缺陷是被弃置会话的排序出错。如果对 JSONL 来说诚实的答案是「保留这套近似」,那么本文的结局可能是记录下这个决定,而不是实现一个字段,而这也是一个可以接受的结局 **代价可能超过这个缺陷本身。** 剩余缺陷是 projection metadata 缺失或延迟时的保守错序。如果对 JSONL 来说诚实的答案是「保留 cache 回退」,那么本文的结局可能是记录决定,而不是实现一个字段。
## 相关 ## 相关
- [种子结束日志边界](../../implemented/architecture/2026-07-30-session-end-seed-log-boundary.md)——mtime 会计入的非活动写入之一;`dsh-session` 拥有 `lastActivityTime()`,也就是一个已存储字段必须与之保持一致的那个日志内投影 - [有界冷空白验证](../../implemented/bug-fix/2026-08-13-bounded-cold-blank-verification.md)——移除 mtime 排序,定义 projection cache 的过渡回退,并把直接冷读取限制为小产物 metadata 验证
- [种子结束日志边界](../../implemented/architecture/2026-07-30-session-end-seed-log-boundary.md)——让 mtime 不适用的非 prompt 写入之一。
- [会话持久化](../../implemented/architecture/2026-06-14-session-persistence.md)——仅追加与绝不重写这两条不变式,正是它们排除了可变的 JSONL header 字段。 - [会话持久化](../../implemented/architecture/2026-06-14-session-persistence.md)——仅追加与绝不重写这两条不变式,正是它们排除了可变的 JSONL header 字段。
- [共享持久化写入协调器](../../implemented/architecture/2026-06-18-shared-persistence-write-coordinator.md)——一个已存储字段将挂入的那条追加路径。 - [共享持久化写入协调器](../../implemented/architecture/2026-06-18-shared-persistence-write-coordinator.md)——一个已存储字段将挂入的那条追加路径。
@@ -49,6 +49,7 @@ Write normal repository-relative Markdown links in canonical docs. The projector
- An image is the exception: its file is copied into the generated tree and referenced from there, so the site serves it regardless of repository visibility. It must be a regular file inside the repository. - An image is the exception: its file is copied into the generated tree and referenced from there, so the site serves it regardless of repository visibility. It must be a regular file inside the repository.
- External URLs, site-absolute URLs, email links, and fragment-only links remain unchanged. - External URLs, site-absolute URLs, email links, and fragment-only links remain unchanged.
- A missing repository-relative target fails projection instead of silently producing a broken link. - A missing repository-relative target fails projection instead of silently producing a broken link.
- Cross-page fragments use the English GitHub heading id as their canonical id. If an authored heading emits a different VitePress id, place an explicit `<a id="..."></a>` immediately before it; add generated aliases in the owning generator.
Do not write website-specific routes into canonical Markdown just to satisfy VitePress. Use `sourceAliases` for directory-style repository links that should resolve to a mapped index page. Do not write website-specific routes into canonical Markdown just to satisfy VitePress. Use `sourceAliases` for directory-style repository links that should resolve to a mapped index page.
@@ -68,6 +69,8 @@ Run the focused website gate before treating the mapping as valid:
pnpm docs:check pnpm docs:check
``` ```
If Markdown link checks pass but the site build reports a missing fragment, follow the `verify-doc-site-fragments` source and target paths. Preserve the English GitHub id with an explicit alias in authored Markdown or in the owning generator.
Before committing a documentation-site change, run: Before committing a documentation-site change, run:
```sh ```sh
+11 -9
View File
@@ -147,6 +147,12 @@ jobs:
contents: read contents: read
id-token: write id-token: write
steps: steps:
# The publish script is the only repository file this job needs, and it
# imports nothing outside Node's builtins, so there is no install step.
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: 24 node-version: 24
@@ -167,12 +173,8 @@ jobs:
fi fi
- name: Publish tarballs - name: Publish tarballs
run: | # Publication is decided per package against the registry, so re-running
version="${GITHUB_REF#refs/tags/landlock-run-v}" # this job over the same artifact skips what already landed instead of
tag_args=() # failing on it. A bare `npm publish` loop could not be retried: the
case "$version" in *-*) tag_args=(--tag next);; esac # registry answers a repeat of an existing version permanently.
while IFS= read -r tarball; do run: node ./scripts/publish-release.mjs dist/npm
# No --access: publishConfig.access in each manifest decides, and a
# command-line flag would override it.
npm publish "dist/npm/${tarball}" "${tag_args[@]}"
done < dist/npm/publish-order.txt
+2 -2
View File
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority; # side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with: # after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write README.md # pnpm run verify-translation-pairing --write README.md
README.md: 646262f0b8317a48cab9aa84bab87b7a2b8e85ec README.md: 098b514f8311de092bab18bdd560722b1e485913
README.zh.md: 892da6f48cd8b60dd61332ec412f604a8062b27d README.zh.md: 639d519b6202ffe740d46e462feec2657d7e13ad
+1
View File
@@ -38,6 +38,7 @@ pnpm dsh web
- Feel free to submit feedback or bug reports through [GitHub Discussions](https://github.com/deepseek-ai/deepseek-harness/discussions). - Feel free to submit feedback or bug reports through [GitHub Discussions](https://github.com/deepseek-ai/deepseek-harness/discussions).
- Add the [`dsh-plugin`](https://github.com/topics/dsh-plugin) topic to your plugin repository for discoverability. - Add the [`dsh-plugin`](https://github.com/topics/dsh-plugin) topic to your plugin repository for discoverability.
- Join <a href="https://discord.gg/Ycq5dCaS4">DeepSeek Harness Discord community</a>.
## Contributing ## Contributing
+19 -1
View File
@@ -37,7 +37,25 @@ pnpm dsh web
## 社区与支持 ## 社区与支持
- 欢迎通过 [GitHub Discussions](https://github.com/deepseek-ai/deepseek-harness/discussions) 提交反馈或 bug 报告。 - 欢迎通过 [GitHub Discussions](https://github.com/deepseek-ai/deepseek-harness/discussions) 提交反馈或 bug 报告。
- 为你的插件仓库添加 [`dsh-plugin`](https://github.com/topics/dsh-plugin) 题,便于被发现。 - 为你的插件仓库添加 [`dsh-plugin`](https://github.com/topics/dsh-plugin) 题,便于被发现。
- 欢迎加入 DeepSeek Harness 企微群:扫码添加企微小助手并填写入群问卷,完成后小助手会邀请你入群。
<table>
<thead>
<tr>
<th align="center">企微小助手</th>
<th align="center">入群问卷</th>
<th align="center">微信公众号</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center"><img src="assets/community-wecom-assistant.png" alt="DeepSeek Harness 企微小助手二维码" width="180" height="180"></td>
<td align="center"><a href="https://trtgsjkv6r.feishu.cn/share/base/form/shrcnIt5twSVdLGD52KJBckGCgg"><img src="assets/community-wecom-survey.png" alt="DeepSeek Harness 入群问卷二维码" width="180" height="180"></a></td>
<td align="center"><img src="assets/community-wechat-official-account.png" alt="DeepSeek Harness 团队微信公众号二维码" width="180" height="180"></td>
</tr>
</tbody>
</table>
## 参与贡献 ## 参与贡献
+60
View File
@@ -0,0 +1,60 @@
/** Cold Session list visibility through the shipped compressed JSONL backend. */
import { mkdir, stat } from 'node:fs/promises'
import { fileURLToPath } from 'node:url'
import { join } from 'node:path'
import type { Browser, Page } from 'playwright'
import { chromium } from 'playwright'
import { afterAll, beforeAll, describe, expect, it, onTestFailed } from 'vitest'
import {
captureStableAria, compareOrRefreshGolden, launchWebScaffold, seedBlankSession,
watchConsole, webSnapshotMode, type WebScaffold,
} from './scaffold.ts'
import { newEnglishPage, saveFailureShot } from './support.ts'
const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/cold-blank-session', import.meta.url))
const SIDEBAR_EXPECTED = join(SNAPSHOT_DIR, 'sidebar.expected.md')
const MODE = webSnapshotMode()
const SESSION_ID = 'cold-blank-session-web-e2e'
const WORKSPACE_NAME = 'cold-blank-workspace'
describe('web e2e: cold blank Session visibility', () => {
let scaffold: WebScaffold
let browser: Browser
let page: Page
let tripwire: ReturnType<typeof watchConsole>
beforeAll(async () => {
scaffold = await launchWebScaffold({})
const cwd = join(scaffold.workspaceCwd, WORKSPACE_NAME)
await mkdir(cwd, { recursive: true })
await seedBlankSession(scaffold, SESSION_ID, cwd)
const header = (await scaffold.ctx.sessionPersistence.list())
.find(candidate => candidate.id === SESSION_ID)
if (header === undefined) throw new Error('blank Session fixture did not materialize')
const location = scaffold.ctx.sessionPersistence.locate(header)
if (location === undefined) throw new Error('JSONL fixture has no physical artifact')
expect((await stat(location.path)).size).toBeLessThanOrEqual(1024)
browser = await chromium.launch()
page = await newEnglishPage(browser)
tripwire = watchConsole(page)
await page.goto(scaffold.baseUrl, { waitUntil: 'load' })
await page.waitForSelector('[class*="frame"]', { timeout: 30_000 })
}, 120_000)
afterAll(async () => {
await browser?.close()
await scaffold?.close()
})
it('keeps the verified cold blank Session out of the sidebar', async () => {
onTestFailed(() => saveFailureShot(page, 'web-e2e-cold-blank-session'))
const tree = page.getByRole('tree', { name: 'Sessions' })
await tree.waitFor({ timeout: 30_000 })
expect(await tree.getByText(WORKSPACE_NAME, { exact: true }).count()).toBe(0)
const sidebar = await captureStableAria(page, '[role="tree"][aria-label="Sessions"]', scaffold.workspaceCwd)
await compareOrRefreshGolden(SIDEBAR_EXPECTED, sidebar, MODE)
expect(tripwire.pageErrors).toEqual([])
})
})
+54 -44
View File
@@ -11,12 +11,13 @@
// gets an absolutely positioned seat instead, laid out against the padding box, // gets an absolutely positioned seat instead, laid out against the padding box,
// which the scrollbar never reduces. // which the scrollbar never reduces.
// //
// Without a shared reservation the two tabs disagree by exactly the bar's // The column handles the two edges without reserving the gutter on both: Chat
// width for as long as the transcript overflows: the card jumps sideways on // keeps `scrollbar-gutter: stable` so its seat's content box never jumps as the
// every tab switch, and inside Chat alone at the moment a growing transcript // transcript starts to scroll; the overlay branch does NOT reserve (the view
// starts to scroll. The column reserves the gutter unconditionally // owns its own scrollers, so a reserved gutter would only narrow the view's
// (`scrollbar-gutter: stable`) and states the overlay branch as a scroll // content by the bar's width), and the overlay seat instead gives back the
// container on the same axes, so both edges are the same edge. // bar's width (`right: var(--dsh-scrollbar-width)`) so both seats measure the
// same width and the card does not move.
// //
// Only a real engine can show this. The seat's geometry is layout: jsdom gives // Only a real engine can show this. The seat's geometry is layout: jsdom gives
// every element a zero-sized box and reports no scrollbar at all, so a unit spec // every element a zero-sized box and reports no scrollbar at all, so a unit spec
@@ -27,18 +28,17 @@
// The browser is launched WITHOUT Playwright's default `--hide-scrollbars`, // The browser is launched WITHOUT Playwright's default `--hide-scrollbars`,
// which is load-bearing rather than incidental. Under that argument a scroll // which is load-bearing rather than incidental. Under that argument a scroll
// container's bar consumes no layout width at all, so the two tabs agree with // container's bar consumes no layout width at all, so the two tabs agree with
// and without the reservation and every comparison below holds vacuously — // and without the compensation and every comparison below holds vacuously —
// measured: the unreserved cascade leaves both tabs' bands at 0 there, against // measured: the uncompensated cascade leaves both tabs' bands at 0 there,
// 8 and 0 with the argument dropped. Dropping it is also the faithful // against 8 and 0 with the argument dropped. Dropping it is also the faithful
// configuration: ui-theme's scrollbar.css gives `::-webkit-scrollbar` a width, // configuration: ui-theme's scrollbar.css gives `::-webkit-scrollbar` a width,
// and a bar that occupies layout space is what the product actually draws. // and a bar that occupies layout space is what the product actually draws.
// //
// The scenario runs that unreserved cascade in the page — `scrollbar-gutter: auto` // The scenario runs that uncompensated cascade in the page — the overlay seat's
// on the scroller, `overflow: hidden` on the overlay branch — and measures the // `right` compensation dropped to 0 — and measures the same two tabs through
// same two tabs through it, which is what keeps the equal rectangles above from // it, which is what keeps the equal rectangles above from being explained by a
// being explained by a tab switch that never reached the layout. It is the // tab switch that never reached the layout. It is the reported symptom as a
// reported symptom as a number: the card moves 4px, half the 8px band, on each // number: the card moves 4px, half the 8px band, on each edge.
// edge.
// //
// Zero model calls: a seeded cold session renders from its log, and switching // Zero model calls: a seeded cold session renders from its log, and switching
// tabs asks the host for nothing. A stray stream would fail loud with NO_ADAPTER. // tabs asks the host for nothing. A stray stream would fail loud with NO_ADAPTER.
@@ -62,9 +62,9 @@ const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/composer-tab-geometry',
* Absolute coordinates are deliberately absent: they depend on the sidebar's * Absolute coordinates are deliberately absent: they depend on the sidebar's
* laid-out width and on font metrics, so committing them would produce a fixture * laid-out width and on font metrics, so committing them would produce a fixture
* that has to be re-recorded per platform. What is recorded is the distance * that has to be re-recorded per platform. What is recorded is the distance
* between the two tabs' rectangles, which is zero when the reservation holds and * between the two tabs' rectangles, which is zero when the compensation holds and
* the bar's width when it does not — including under the control, so the golden * the bar's width when it does not — including under the control, so the golden
* carries the shift the unreserved cascade produces rather than only its absence. * carries the shift the uncompensated cascade produces rather than only its absence.
*/ */
const GEOMETRY_EXPECTED = join(SNAPSHOT_DIR, 'geometry.expected.md') const GEOMETRY_EXPECTED = join(SNAPSHOT_DIR, 'geometry.expected.md')
const MODE = webSnapshotMode() const MODE = webSnapshotMode()
@@ -114,15 +114,15 @@ async function setMeasuredViewport(
} }
/** /**
* The unreserved cascade, injected into the page: the reservation dropped and * The uncompensated cascade, injected into the page: the overlay seat's `right`
* the overlay branch forced to a hidden box. `!important` beats the module * compensation dropped to 0, so it measures the full padding box while Chat's
* seat still rides the reserved content box. `!important` beats the module
* rules without a rebuild, and the id lets the control be lifted again in the * rules without a rebuild, and the id lets the control be lifted again in the
* same session. * same session.
*/ */
const CONTROL_STYLE_ID = 'composer-tab-geometry-control' const CONTROL_STYLE_ID = 'composer-tab-geometry-control'
const CONTROL_CSS = ` const CONTROL_CSS = `
[data-conversation-scroll] { scrollbar-gutter: auto !important; } [data-conversation-scroll]:has([data-conversation-composer-overlay]) > [data-composer-seat] { right: 0 !important; }
[data-conversation-scroll]:has([data-conversation-composer-overlay]) { overflow: hidden !important; }
` `
/** The column scroller and the input card as the browser lays them out, in one tab. */ /** The column scroller and the input card as the browser lays them out, in one tab. */
@@ -221,11 +221,13 @@ async function compareTabs(page: Page): Promise<TabComparison> {
} }
/** /**
* Run the unreserved cascade in the page for one measurement, then lift it. * Run the uncompensated cascade in the page for one measurement, then lift it:
* the overlay seat's `right` compensation dropped to 0, so it measures the
* full padding box while Chat's seat still rides the reserved content box.
* @param page - the page under test. * @param page - the page under test.
* @returns the comparison as the column lays out without the reservation. * @returns the comparison as the column lays out without the compensation.
*/ */
async function compareTabsWithoutReservation(page: Page): Promise<TabComparison> { async function compareTabsWithoutCompensation(page: Page): Promise<TabComparison> {
await page.evaluate(({ id, css }) => { await page.evaluate(({ id, css }) => {
const style = document.createElement('style') const style = document.createElement('style')
style.id = id style.id = id
@@ -268,7 +270,7 @@ async function openSeededSession(page: Page): Promise<void> {
* Render the golden body. * Render the golden body.
* @param wide - comparison at the viewport where the card sits at its width cap. * @param wide - comparison at the viewport where the card sits at its width cap.
* @param narrow - comparison at the viewport where the card shrinks with the column. * @param narrow - comparison at the viewport where the card shrinks with the column.
* @param control - comparison at the wide viewport with the reservation removed. * @param control - comparison at the wide viewport with the compensation removed.
* @returns the golden body, without a trailing newline. * @returns the golden body, without a trailing newline.
*/ */
function renderGeometry(wide: TabComparison, narrow: TabComparison, control: TabComparison): string { function renderGeometry(wide: TabComparison, narrow: TabComparison, control: TabComparison): string {
@@ -291,7 +293,7 @@ function renderGeometry(wide: TabComparison, narrow: TabComparison, control: Tab
'', '',
...section(`Wide viewport (${String(WIDE_VIEWPORT.width)}px, card at its cap)`, wide), ...section(`Wide viewport (${String(WIDE_VIEWPORT.width)}px, card at its cap)`, wide),
...section(`Narrow viewport (${String(NARROW_VIEWPORT.width)}px, card shrinking with the column)`, narrow), ...section(`Narrow viewport (${String(NARROW_VIEWPORT.width)}px, card shrinking with the column)`, narrow),
...section('Wide viewport, reservation removed in the page (control)', control), ...section('Wide viewport, seat compensation removed in the page (control)', control),
].join('\n').trimEnd() ].join('\n').trimEnd()
} }
@@ -322,22 +324,29 @@ describe('web e2e: input card position across view tabs', () => {
await scaffold?.close() await scaffold?.close()
}) })
it('reserves the same gutter in both tabs while the transcript scrolls', async () => { it('reserves the gutter in Chat and lets Trajectory own its width', async () => {
onTestFailed(() => saveFailureShot(page, 'web-e2e-composer-tab-geometry-band')) onTestFailed(() => saveFailureShot(page, 'web-e2e-composer-tab-geometry-band'))
await setMeasuredViewport(page, WIDE_VIEWPORT, false) await setMeasuredViewport(page, WIDE_VIEWPORT, false)
// Vacuity guard, in two parts. A transcript that does not overflow gives // Vacuity guard. The scenario must be able to fail: on an engine that
// Chat no scrollbar, and a hidden or overlaid bar gives it no width; either // does not implement `scrollbar-gutter`, Chat reserves nothing and the
// would make the tabs agree without the reservation doing anything. // overlay seat's fixed compensation stands alone, manufacturing an 8px
// deviation the equal-rectangle assertions would catch. `stable` reserves
// even without overflow, so a short transcript is not a vacuous case; the
// poll still pins the measurement to the overflowing state the product
// ships.
await expect.poll(async () => (await measureTab(page)).scrolls, { timeout: 10_000 }).toBe(true) await expect.poll(async () => (await measureTab(page)).scrolls, { timeout: 10_000 }).toBe(true)
const comparison = await compareTabs(page) const comparison = await compareTabs(page)
expect(comparison.chat.band).toBeGreaterThan(0) expect(comparison.chat.band).toBeGreaterThan(0)
// The reservation reaches both states, which is the whole point: the same // Chat keeps the unconditional reservation so its seat's content box never
// band, on a box that scrolls and on one that only holds a view. // jumps as the transcript starts to scroll.
expect(comparison.chat.gutter).toBe('stable') expect(comparison.chat.gutter).toBe('stable')
expect(comparison.trajectory.gutter).toBe('stable') // The overlay branch does NOT reserve: the view owns its own scrollers, so
expect(comparison.trajectory.band).toBe(comparison.chat.band) // a reserved gutter would only narrow the view's content by the bar's
// width. The seat compensates instead, which the next test asserts.
expect(comparison.trajectory.gutter).toBe('auto')
expect(comparison.trajectory.band).toBe(0)
// Declared as a scroll container on both axes rather than left to compute: // Declared as a scroll container on both axes rather than left to compute:
// `overflow: hidden` would drop the reservation in WebKit, and a `visible` // `overflow: hidden` would drop any reservation in WebKit, and a `visible`
// horizontal axis computes to `auto` beside a scrolling one. // horizontal axis computes to `auto` beside a scrolling one.
expect(comparison.trajectory.overflowY).toBe('auto') expect(comparison.trajectory.overflowY).toBe('auto')
expect(comparison.trajectory.overflowX).toBe('hidden') expect(comparison.trajectory.overflowX).toBe('hidden')
@@ -351,7 +360,7 @@ describe('web e2e: input card position across view tabs', () => {
await setMeasuredViewport(page, WIDE_VIEWPORT, false) await setMeasuredViewport(page, WIDE_VIEWPORT, false)
const comparison = await compareTabs(page) const comparison = await compareTabs(page)
// The reported symptom as a number. At this viewport the card sits at its // The reported symptom as a number. At this viewport the card sits at its
// width cap, so the unreserved cascade's shift shows up as a centring // width cap, so the uncompensated cascade's shift shows up as a centring
// difference — half the band on each edge — rather than as a width change. // difference — half the band on each edge — rather than as a width change.
expect(comparison.leftShift).toBe(0) expect(comparison.leftShift).toBe(0)
expect(comparison.rightShift).toBe(0) expect(comparison.rightShift).toBe(0)
@@ -378,20 +387,21 @@ describe('web e2e: input card position across view tabs', () => {
expect(tripwire.pageErrors).toEqual([]) expect(tripwire.pageErrors).toEqual([])
}, 60_000) }, 60_000)
it('moves the card again once the reservation is removed in the page', async () => { it('moves the card again once the seat compensation is removed in the page', async () => {
onTestFailed(() => saveFailureShot(page, 'web-e2e-composer-tab-geometry-control')) onTestFailed(() => saveFailureShot(page, 'web-e2e-composer-tab-geometry-control'))
await setMeasuredViewport(page, WIDE_VIEWPORT, false) await setMeasuredViewport(page, WIDE_VIEWPORT, false)
// The control: without it, equal rectangles could also mean the tab switch // The control: without it, equal rectangles could also mean the tab switch
// never reached the layout. Under the unreserved cascade the Chat scroller // never reached the layout. Under the uncompensated cascade the overlay seat
// keeps its bar and the Trajectory branch becomes a hidden box with none, // loses its `right` compensation and measures the full padding box, so the
// and the card moves by half the band on each edge. // card moves by half the band on each edge. Chat's own reservation is
const comparison = await compareTabsWithoutReservation(page) // untouched — that is the side that must not change.
expect(comparison.chat.gutter).toBe('auto') const comparison = await compareTabsWithoutCompensation(page)
expect(comparison.chat.gutter).toBe('stable')
expect(comparison.chat.band).toBeGreaterThan(0) expect(comparison.chat.band).toBeGreaterThan(0)
expect(comparison.trajectory.band).toBe(0) expect(comparison.trajectory.band).toBe(0)
expect(comparison.leftShift).toBe(comparison.chat.band / 2) expect(comparison.leftShift).toBe(comparison.chat.band / 2)
expect(comparison.rightShift).toBe(comparison.chat.band / 2) expect(comparison.rightShift).toBe(comparison.chat.band / 2)
// Restoring the sheet restores the reservation, so the control cannot leak // Restoring the sheet restores the compensation, so the control cannot leak
// into the remaining measurements. // into the remaining measurements.
const restored = await compareTabs(page) const restored = await compareTabs(page)
expect(restored.leftShift).toBe(0) expect(restored.leftShift).toBe(0)
@@ -405,7 +415,7 @@ describe('web e2e: input card position across view tabs', () => {
await setMeasuredViewport(page, NARROW_VIEWPORT, true) await setMeasuredViewport(page, NARROW_VIEWPORT, true)
const narrow = await compareTabs(page) const narrow = await compareTabs(page)
await setMeasuredViewport(page, WIDE_VIEWPORT, false) await setMeasuredViewport(page, WIDE_VIEWPORT, false)
const control = await compareTabsWithoutReservation(page) const control = await compareTabsWithoutCompensation(page)
await compareOrRefreshGolden(GEOMETRY_EXPECTED, renderGeometry(wide, narrow, control), MODE) await compareOrRefreshGolden(GEOMETRY_EXPECTED, renderGeometry(wide, narrow, control), MODE)
expect(tripwire.pageErrors).toEqual([]) expect(tripwire.pageErrors).toEqual([])
}, 60_000) }, 60_000)
+33 -8
View File
@@ -23,7 +23,7 @@
// (the plugin-row path discards the ReplayHandle; the direct install keeps // (the plugin-row path discards the ReplayHandle; the direct install keeps
// assertConsumed for the teardown fixture-consumption check). // assertConsumed for the teardown fixture-consumption check).
import { existsSync } from 'node:fs' import { existsSync } from 'node:fs'
import { mkdir, mkdtemp, readFile, readdir, realpath, rm, utimes, writeFile } from 'node:fs/promises' import { mkdir, mkdtemp, readFile, readdir, realpath, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os' import { tmpdir } from 'node:os'
import { join } from 'node:path' import { join } from 'node:path'
import { pathToFileURL } from 'node:url' import { pathToFileURL } from 'node:url'
@@ -703,6 +703,38 @@ export async function seedSession(
delegationDepth: 0, delegationDepth: 0,
...agentPreset === undefined ? {} : { agentPreset }, ...agentPreset === undefined ? {} : { agentPreset },
} }
await persistSeedSession(scaffold, meta, events)
return meta.id
}
/** Seed one materialized cold Session whose log has no turn/start event. */
export async function seedBlankSession(
scaffold: WebScaffold,
id: string,
cwd: string,
): Promise<SessionId> {
const meta: SessionHeader = {
version: SESSION_FORMAT_VERSION,
id: SessionId(id),
createdAt: Date.now() - 60_000,
cwd,
delegationDepth: 0,
}
await persistSeedSession(scaffold, meta, [{
type: 'session/end-seed',
seq: 0,
time: meta.createdAt,
data: {},
}])
return meta.id
}
/** Materialize one detached Session fixture through the shipped JSONL provider. */
async function persistSeedSession(
scaffold: WebScaffold,
meta: SessionHeader,
events: readonly SessionEvent[],
): Promise<void> {
const seeder = new Context() const seeder = new Context()
try { try {
await seeder.plugin(SessionStore) await seeder.plugin(SessionStore)
@@ -711,16 +743,9 @@ export async function seedSession(
await seeder.plugin(JsonlSessionPersistence, { root: scaffold.persistenceRoot }) await seeder.plugin(JsonlSessionPersistence, { root: scaffold.persistenceRoot })
await seeder.sessionPersistence.create(meta) await seeder.sessionPersistence.create(meta)
await seeder.sessionPersistence.append(meta.id, events) await seeder.sessionPersistence.append(meta.id, events)
// Deterministic sidebar order: cold summaries take updatedAt from mtime.
const located = seeder.sessionPersistence.locate(meta)
if (located !== undefined) {
const backdated = new Date(meta.createdAt)
await utimes(located.path, backdated, backdated)
}
} finally { } finally {
await seeder.fiber.dispose() await seeder.fiber.dispose()
} }
return meta.id
} }
/** /**
@@ -0,0 +1 @@
- tree "Sessions": No sessions yet
@@ -5,9 +5,9 @@
- Chat: scrollbar-gutter stable, overflow hidden/auto - Chat: scrollbar-gutter stable, overflow hidden/auto
- Chat scroller scrolls: true - Chat scroller scrolls: true
- Chat reserved band: 8px - Chat reserved band: 8px
- Trajectory: scrollbar-gutter stable, overflow hidden/auto - Trajectory: scrollbar-gutter auto, overflow hidden/auto
- Trajectory scroller scrolls: false - Trajectory scroller scrolls: false
- Trajectory reserved band: 8px - Trajectory reserved band: 0px
- input card left edge moves between tabs: 0px - input card left edge moves between tabs: 0px
- input card right edge moves between tabs: 0px - input card right edge moves between tabs: 0px
- input card width changes between tabs: 0px - input card width changes between tabs: 0px
@@ -17,19 +17,19 @@
- Chat: scrollbar-gutter stable, overflow hidden/auto - Chat: scrollbar-gutter stable, overflow hidden/auto
- Chat scroller scrolls: true - Chat scroller scrolls: true
- Chat reserved band: 8px - Chat reserved band: 8px
- Trajectory: scrollbar-gutter stable, overflow hidden/auto - Trajectory: scrollbar-gutter auto, overflow hidden/auto
- Trajectory scroller scrolls: false - Trajectory scroller scrolls: false
- Trajectory reserved band: 8px - Trajectory reserved band: 0px
- input card left edge moves between tabs: 0px - input card left edge moves between tabs: 0px
- input card right edge moves between tabs: 0px - input card right edge moves between tabs: 0px
- input card width changes between tabs: 0px - input card width changes between tabs: 0px
## Wide viewport, reservation removed in the page (control) ## Wide viewport, seat compensation removed in the page (control)
- Chat: scrollbar-gutter auto, overflow hidden/auto - Chat: scrollbar-gutter stable, overflow hidden/auto
- Chat scroller scrolls: true - Chat scroller scrolls: true
- Chat reserved band: 8px - Chat reserved band: 8px
- Trajectory: scrollbar-gutter auto, overflow hidden/hidden - Trajectory: scrollbar-gutter auto, overflow hidden/auto
- Trajectory scroller scrolls: false - Trajectory scroller scrolls: false
- Trajectory reserved band: 0px - Trajectory reserved band: 0px
- input card left edge moves between tabs: 4px - input card left edge moves between tabs: 4px
@@ -0,0 +1,4 @@
- row "Load earlier history":
- cell "Load earlier history":
- button "Load earlier history":
- status
@@ -3,6 +3,7 @@
// DOM mounting stays bounded, and every scroll range remains reachable. // DOM mounting stays bounded, and every scroll range remains reachable.
import { mkdtemp, rm, writeFile } from 'node:fs/promises' import { mkdtemp, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os' import { tmpdir } from 'node:os'
import { fileURLToPath } from 'node:url'
import { join } from 'node:path' import { join } from 'node:path'
import type { Browser, Page } from 'playwright' import type { Browser, Page } from 'playwright'
import { chromium } from 'playwright' import { chromium } from 'playwright'
@@ -11,6 +12,8 @@ import type { StreamChunk } from '@deepseek-ai/dsh-llm'
import type { ReplayEntry } from '@deepseek-ai/dsh-llm-replay' import type { ReplayEntry } from '@deepseek-ai/dsh-llm-replay'
import { createChatScrollFixture } from './chat-scroll-fixture.ts' import { createChatScrollFixture } from './chat-scroll-fixture.ts'
import { import {
captureStableAria,
compareOrRefreshGolden,
launchWebScaffold, launchWebScaffold,
seedSession, seedSession,
watchConsole, watchConsole,
@@ -20,6 +23,10 @@ import {
import { newEnglishPage, saveFailureShot } from './support.ts' import { newEnglishPage, saveFailureShot } from './support.ts'
const MODE = webSnapshotMode() const MODE = webSnapshotMode()
const LOAD_MORE_EXPECTED = fileURLToPath(new URL(
'./snapshots/trajectory-virtualization/load-more.expected.md',
import.meta.url,
))
const SESSION_ID = 'trajectory-virtualization-e2e' const SESSION_ID = 'trajectory-virtualization-e2e'
const FIXTURE = createChatScrollFixture({ const FIXTURE = createChatScrollFixture({
markerPrefix: 'TRAJECTORY_VIRTUAL', markerPrefix: 'TRAJECTORY_VIRTUAL',
@@ -150,10 +157,16 @@ async function loadToFirstTurn(page: Page): Promise<void> {
await scrollToRatio(page, 0) await scrollToRatio(page, 0)
if (await page.getByText(marker, { exact: false }).count() > 0) return if (await page.getByText(marker, { exact: false }).count() > 0) return
const before = await logicalRows(page) const before = await logicalRows(page)
const anchor = await firstVisibleRow(page)
await expect.poll(async () => ({ await expect.poll(async () => ({
marker: await page.getByText(marker, { exact: false }).count() > 0, marker: await page.getByText(marker, { exact: false }).count() > 0,
rows: await logicalRows(page), rows: await logicalRows(page),
}), { timeout: 30_000 }).not.toEqual({ marker: false, rows: before }) }), { timeout: 30_000 }).not.toEqual({ marker: false, rows: before })
await nextPaint(page)
await expect.poll(async () => {
const top = await rowTop(page, anchor.key)
return top === null ? Number.POSITIVE_INFINITY : Math.abs(top - anchor.top)
}, { timeout: 15_000 }).toBeLessThanOrEqual(GEOMETRY_TOLERANCE)
} }
throw new Error('trajectory did not reach the first turn after twelve older-page requests') throw new Error('trajectory did not reach the first turn after twelve older-page requests')
} }
@@ -230,8 +243,27 @@ describe('web e2e: Trajectory virtualization over tail-paged history', () => {
expect(await page.getByText('Initial System Prompt', { exact: true }).count()).toBe(0) expect(await page.getByText('Initial System Prompt', { exact: true }).count()).toBe(0)
expect(await mountedRows(page)).toBeLessThanOrEqual(MAX_MOUNTED_ROWS) expect(await mountedRows(page)).toBeLessThanOrEqual(MAX_MOUNTED_ROWS)
await scrollToRatio(page, 0) const loadMore = page.locator('[data-history-load] button')
await loadMore.waitFor({ timeout: 15_000 })
expect(await loadMore.textContent()).toBe('Load earlier history')
const loadMoreSnapshot = await captureStableAria(
page,
'[data-history-load]',
scaffold.workspaceCwd,
)
await compareOrRefreshGolden(LOAD_MORE_EXPECTED, loadMoreSnapshot, MODE)
// Avoid Playwright scrolling the offscreen first row into the automatic-load threshold.
await loadMore.evaluate((button: HTMLButtonElement) => { button.click() })
await expect.poll(() => held, { timeout: 15_000 }).toBe(true) await expect.poll(() => held, { timeout: 15_000 }).toBe(true)
await expect.poll(async () => ({
disabled: await loadMore.isDisabled(),
label: await loadMore.getAttribute('aria-label'),
}), { timeout: 15_000 }).toEqual({
disabled: true,
label: 'Loading earlier history…',
})
await scrollToRatio(page, 0)
const anchor = await firstVisibleRow(page) const anchor = await firstVisibleRow(page)
const selectedRow = page.locator( const selectedRow = page.locator(
`[data-trajectory-scroll] tr[data-trajectory-row-key=${JSON.stringify(anchor.key)}]`, `[data-trajectory-scroll] tr[data-trajectory-row-key=${JSON.stringify(anchor.key)}]`,
+1
View File
@@ -48,6 +48,7 @@
"tests/replay-round-trip.e2e.ts", "tests/replay-round-trip.e2e.ts",
"tests/hmr-live.e2e.ts", "tests/hmr-live.e2e.ts",
"tests/seeded-history.e2e.ts", "tests/seeded-history.e2e.ts",
"tests/cold-blank-session.e2e.ts",
"tests/stats-paged-history.e2e.ts", "tests/stats-paged-history.e2e.ts",
"tests/sidebar-scrollbar.e2e.ts", "tests/sidebar-scrollbar.e2e.ts",
"tests/conversation-column-overflow.e2e.ts", "tests/conversation-column-overflow.e2e.ts",
Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 30 KiB

+1 -1
View File
@@ -3,4 +3,4 @@
# after editing either side, bring the other along and re-record with: # after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/architecture.md # pnpm run verify-translation-pairing --write docs/architecture.md
architecture.md: 77000ce9d4608d440e1d903eb80a42f2ed6435ef architecture.md: 77000ce9d4608d440e1d903eb80a42f2ed6435ef
architecture.zh.md: 268724d52c82e31c4fc2b51db59823f3720f9b91 architecture.zh.md: f2f5310f665b86b86587307e7ce31c5841b96317
+4
View File
@@ -50,6 +50,8 @@ dsh --profile web --dump-config
| [`core/scope`](subsystems/scope.md) | 按 agent 划分作用域的注册原语 | 库,无 ctx 键 | | [`core/scope`](subsystems/scope.md) | 按 agent 划分作用域的注册原语 | 库,无 ctx 键 |
| [`llm/llm`](subsystems/llm-streaming.md) | 消息与流式词汇表,以及适配器 seam | `ctx.llm` | | [`llm/llm`](subsystems/llm-streaming.md) | 消息与流式词汇表,以及适配器 seam | `ctx.llm` |
<a id="events"></a>
## 事件 ## 事件
事件就是扩展点,而选对事件域是大多数改动的第一个决定。 事件就是扩展点,而选对事件域是大多数改动的第一个决定。
@@ -60,6 +62,8 @@ dsh --profile web --dump-config
[事件映射](event-producer-consumer.md)列出每个事件的生产方与消费方。 [事件映射](event-producer-consumer.md)列出每个事件的生产方与消费方。
<a id="turn-flow"></a>
## 轮次流程 ## 轮次流程
一个**步骤**是一次模型请求加上它调用的工具。一个**轮次**包含零个或多个步骤:它在领取首条输入之前打开,并在不再欠下任何工作时关闭。 一个**步骤**是一次模型请求加上它调用的工具。一个**轮次**包含零个或多个步骤:它在领取首条输入之前打开,并在不再欠下任何工作时关闭。
+2 -2
View File
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority; # side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with: # after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/config-catalog.md # pnpm run verify-translation-pairing --write docs/config-catalog.md
config-catalog.md: 19bfa6d1fb847de4a7207f42dabd67d43f288361 config-catalog.md: 20919b3fdc5ab26255465949d72bdce8d356a529
config-catalog.zh.md: fda208e8fcd2ff6dd697efed84a4073ecbd5a912 config-catalog.zh.md: 8dfb49df5e3f5906af859a4de83ebc44f315a0bd
+216
View File
@@ -9,6 +9,8 @@ This file is GENERATED from source (`scripts/gen-config-catalog.ts`) and verifie
A `Requires:` line lists the service keys the plugin `inject`s: its `cordis.yml` tree must also load providers for those services. Scope is the harness tier (`packages/`); the vendored cordis plugins a config tree may also load (`hmr`, the console logger, …) are pinned upstream source ([vendoring policy](../vendor/README.md)) and not catalogued here. A `Requires:` line lists the service keys the plugin `inject`s: its `cordis.yml` tree must also load providers for those services. Scope is the harness tier (`packages/`); the vendored cordis plugins a config tree may also load (`hmr`, the console logger, …) are pinned upstream source ([vendoring policy](../vendor/README.md)) and not catalogued here.
<a id="deepseek-aidsh-acp"></a>
## `@deepseek-ai/dsh-acp` ## `@deepseek-ai/dsh-acp`
Requires: `agents` Requires: `agents`
@@ -29,6 +31,8 @@ Depends on: `Stream` (`@agentclientprotocol/sdk`)
Source: [`packages/acp/acp/src/index.ts:70`](../packages/acp/acp/src/index.ts) Source: [`packages/acp/acp/src/index.ts:70`](../packages/acp/acp/src/index.ts)
<a id="deepseek-aidsh-acp-demo"></a>
## `@deepseek-ai/dsh-acp-demo` ## `@deepseek-ai/dsh-acp-demo`
```ts config-catalog ```ts config-catalog
@@ -82,6 +86,8 @@ Depends on: [`agentCore`](../packages/examples/agent-spine-demo/src/index.ts) ·
Source: [`packages/examples/acp-demo/src/index.ts:39`](../packages/examples/acp-demo/src/index.ts) Source: [`packages/examples/acp-demo/src/index.ts:39`](../packages/examples/acp-demo/src/index.ts)
<a id="deepseek-aidsh-agent-default-model"></a>
## `@deepseek-ai/dsh-agent-default-model` ## `@deepseek-ai/dsh-agent-default-model`
```ts config-catalog ```ts config-catalog
@@ -96,6 +102,8 @@ export interface Config {
Source: [`packages/core/agent-default-model/src/index.ts:41`](../packages/core/agent-default-model/src/index.ts) Source: [`packages/core/agent-default-model/src/index.ts:41`](../packages/core/agent-default-model/src/index.ts)
<a id="deepseek-aidsh-agent-instructions"></a>
## `@deepseek-ai/dsh-agent-instructions` ## `@deepseek-ai/dsh-agent-instructions`
```ts config-catalog ```ts config-catalog
@@ -124,6 +132,8 @@ export interface Config {
Source: [`packages/context/agent-instructions/src/config.ts:18`](../packages/context/agent-instructions/src/config.ts) Source: [`packages/context/agent-instructions/src/config.ts:18`](../packages/context/agent-instructions/src/config.ts)
<a id="deepseek-aidsh-agent-loop"></a>
## `@deepseek-ai/dsh-agent-loop` ## `@deepseek-ai/dsh-agent-loop`
Requires: `agents` · `sessions` · `llm` · `tools` · `systemPrompt` Requires: `agents` · `sessions` · `llm` · `tools` · `systemPrompt`
@@ -154,6 +164,8 @@ Depends on: [`AgentOptions`](subsystems/core.md) · [`SessionId`](subsystems/cor
Source: [`packages/core/agent-loop/src/index.ts:255`](../packages/core/agent-loop/src/index.ts) Source: [`packages/core/agent-loop/src/index.ts:255`](../packages/core/agent-loop/src/index.ts)
<a id="deepseek-aidsh-agent-presets"></a>
## `@deepseek-ai/dsh-agent-presets` ## `@deepseek-ai/dsh-agent-presets`
Requires: `loader` Requires: `loader`
@@ -190,6 +202,8 @@ export type PresetTrust = 'system' | 'user'
Source: [`packages/preset/agent-presets/src/preset.ts:52`](../packages/preset/agent-presets/src/preset.ts) Source: [`packages/preset/agent-presets/src/preset.ts:52`](../packages/preset/agent-presets/src/preset.ts)
<a id="deepseek-aidsh-agent-spine-demo"></a>
## `@deepseek-ai/dsh-agent-spine-demo` ## `@deepseek-ai/dsh-agent-spine-demo`
```ts config-catalog ```ts config-catalog
@@ -280,6 +294,8 @@ Depends on: [`AgentLoopConfig`](#deepseek-aidsh-agent-loop) · [`GoalDomainConfi
Source: [`packages/examples/agent-spine-demo/src/index.ts:92`](../packages/examples/agent-spine-demo/src/index.ts) Source: [`packages/examples/agent-spine-demo/src/index.ts:92`](../packages/examples/agent-spine-demo/src/index.ts)
<a id="deepseek-aidsh-agent-tool-presentation"></a>
## `@deepseek-ai/dsh-agent-tool-presentation` ## `@deepseek-ai/dsh-agent-tool-presentation`
Requires: `tools` Requires: `tools`
@@ -302,6 +318,8 @@ Depends on: [`ToolPresentationMode`](subsystems/tools.md)
Source: [`packages/core/agent-tool-presentation/src/index.ts:38`](../packages/core/agent-tool-presentation/src/index.ts) Source: [`packages/core/agent-tool-presentation/src/index.ts:38`](../packages/core/agent-tool-presentation/src/index.ts)
<a id="deepseek-aidsh-attachment-local"></a>
## `@deepseek-ai/dsh-attachment-local` ## `@deepseek-ai/dsh-attachment-local`
```ts config-catalog ```ts config-catalog
@@ -322,6 +340,8 @@ export interface Config {
Source: [`packages/attachment/attachment-local/src/index.ts:24`](../packages/attachment/attachment-local/src/index.ts) Source: [`packages/attachment/attachment-local/src/index.ts:24`](../packages/attachment/attachment-local/src/index.ts)
<a id="deepseek-aidsh-bash-local"></a>
## `@deepseek-ai/dsh-bash-local` ## `@deepseek-ai/dsh-bash-local`
Requires: `subprocess` Requires: `subprocess`
@@ -346,6 +366,8 @@ export interface Config {
Source: [`packages/shell/bash-local/src/index.ts:41`](../packages/shell/bash-local/src/index.ts) Source: [`packages/shell/bash-local/src/index.ts:41`](../packages/shell/bash-local/src/index.ts)
<a id="deepseek-aidsh-bash-sandbox"></a>
## `@deepseek-ai/dsh-bash-sandbox` ## `@deepseek-ai/dsh-bash-sandbox`
Requires: `subprocess` · `sandbox` · `sandboxPolicy` Requires: `subprocess` · `sandbox` · `sandboxPolicy`
@@ -365,6 +387,8 @@ Depends on: [`LocalConfig`](#deepseek-aidsh-bash-local)
Source: [`packages/shell/bash-sandbox/src/index.ts:35`](../packages/shell/bash-sandbox/src/index.ts) Source: [`packages/shell/bash-sandbox/src/index.ts:35`](../packages/shell/bash-sandbox/src/index.ts)
<a id="deepseek-aidsh-client-connection"></a>
## `@deepseek-ai/dsh-client-connection` ## `@deepseek-ai/dsh-client-connection`
Requires: `webServer` Requires: `webServer`
@@ -388,6 +412,8 @@ export interface ConnectionConfig {
Source: [`packages/client/connection/src/index.ts:50`](../packages/client/connection/src/index.ts) Source: [`packages/client/connection/src/index.ts:50`](../packages/client/connection/src/index.ts)
<a id="deepseek-aidsh-client-hmr"></a>
## `@deepseek-ai/dsh-client-hmr` ## `@deepseek-ai/dsh-client-hmr`
Requires: `clientModules` · `webServer` Requires: `clientModules` · `webServer`
@@ -402,6 +428,8 @@ export interface Config {
Source: [`packages/client/hmr/src/index.ts:31`](../packages/client/hmr/src/index.ts) Source: [`packages/client/hmr/src/index.ts:31`](../packages/client/hmr/src/index.ts)
<a id="deepseek-aidsh-code-runtime-worker-thread"></a>
## `@deepseek-ai/dsh-code-runtime-worker-thread` ## `@deepseek-ai/dsh-code-runtime-worker-thread`
```ts config-catalog ```ts config-catalog
@@ -437,6 +465,8 @@ export interface Config {
Source: [`packages/code-runtime/code-runtime-worker-thread/src/index.ts:25`](../packages/code-runtime/code-runtime-worker-thread/src/index.ts) Source: [`packages/code-runtime/code-runtime-worker-thread/src/index.ts:25`](../packages/code-runtime/code-runtime-worker-thread/src/index.ts)
<a id="deepseek-aidsh-compaction-basic"></a>
## `@deepseek-ai/dsh-compaction-basic` ## `@deepseek-ai/dsh-compaction-basic`
Requires: `llm` · `tokenMeter` · `sessions` Requires: `llm` · `tokenMeter` · `sessions`
@@ -481,6 +511,8 @@ export interface ModelCompactPolicyConfig extends CompactionPolicyConfig {
Source: [`packages/compaction/compaction-basic/src/types.ts:38`](../packages/compaction/compaction-basic/src/types.ts) Source: [`packages/compaction/compaction-basic/src/types.ts:38`](../packages/compaction/compaction-basic/src/types.ts)
<a id="deepseek-aidsh-compaction-tool-result-pruner"></a>
## `@deepseek-ai/dsh-compaction-tool-result-pruner` ## `@deepseek-ai/dsh-compaction-tool-result-pruner`
Requires: `tokenMeter` Requires: `tokenMeter`
@@ -499,6 +531,8 @@ export interface ToolResultPruneConfig {
Source: [`packages/compaction/compaction-tool-result-pruner/src/types.ts:4`](../packages/compaction/compaction-tool-result-pruner/src/types.ts) Source: [`packages/compaction/compaction-tool-result-pruner/src/types.ts:4`](../packages/compaction/compaction-tool-result-pruner/src/types.ts)
<a id="deepseek-aidsh-cordis-host-runner"></a>
## `@deepseek-ai/dsh-cordis-host-runner` ## `@deepseek-ai/dsh-cordis-host-runner`
Requires: `tools` Requires: `tools`
@@ -513,6 +547,8 @@ export interface Config {
Source: [`packages/extensions/cordis-host-runner/src/index.ts:88`](../packages/extensions/cordis-host-runner/src/index.ts) Source: [`packages/extensions/cordis-host-runner/src/index.ts:88`](../packages/extensions/cordis-host-runner/src/index.ts)
<a id="deepseek-aidsh-credentials-local"></a>
## `@deepseek-ai/dsh-credentials-local` ## `@deepseek-ai/dsh-credentials-local`
```ts config-catalog ```ts config-catalog
@@ -531,6 +567,8 @@ export interface Config {
Source: [`packages/credentials/credentials-local/src/index.ts:55`](../packages/credentials/credentials-local/src/index.ts) Source: [`packages/credentials/credentials-local/src/index.ts:55`](../packages/credentials/credentials-local/src/index.ts)
<a id="deepseek-aidsh-e2b"></a>
## `@deepseek-ai/dsh-e2b` ## `@deepseek-ai/dsh-e2b`
```ts config-catalog ```ts config-catalog
@@ -547,6 +585,8 @@ export interface Config {
Source: [`packages/e2b/e2b/src/index.ts:43`](../packages/e2b/e2b/src/index.ts) Source: [`packages/e2b/e2b/src/index.ts:43`](../packages/e2b/e2b/src/index.ts)
<a id="deepseek-aidsh-fs-local"></a>
## `@deepseek-ai/dsh-fs-local` ## `@deepseek-ai/dsh-fs-local`
```ts config-catalog ```ts config-catalog
@@ -564,6 +604,8 @@ export interface Config {
Source: [`packages/fs/fs-local/src/index.ts:41`](../packages/fs/fs-local/src/index.ts) Source: [`packages/fs/fs-local/src/index.ts:41`](../packages/fs/fs-local/src/index.ts)
<a id="deepseek-aidsh-fs-sandbox"></a>
## `@deepseek-ai/dsh-fs-sandbox` ## `@deepseek-ai/dsh-fs-sandbox`
Requires: `sandboxPolicy` Requires: `sandboxPolicy`
@@ -582,6 +624,8 @@ Depends on: [`LocalConfig`](#deepseek-aidsh-fs-local)
Source: [`packages/fs/fs-sandbox/src/index.ts:49`](../packages/fs/fs-sandbox/src/index.ts) Source: [`packages/fs/fs-sandbox/src/index.ts:49`](../packages/fs/fs-sandbox/src/index.ts)
<a id="deepseek-aidsh-goal"></a>
## `@deepseek-ai/dsh-goal` ## `@deepseek-ai/dsh-goal`
Requires: `agents` Requires: `agents`
@@ -596,6 +640,8 @@ export interface Config {
Source: [`packages/goal/goal/src/index.ts:116`](../packages/goal/goal/src/index.ts) Source: [`packages/goal/goal/src/index.ts:116`](../packages/goal/goal/src/index.ts)
<a id="deepseek-aidsh-headless"></a>
## `@deepseek-ai/dsh-headless` ## `@deepseek-ai/dsh-headless`
Requires: `agentDefaultModel` · `agents` · `sessions` Requires: `agentDefaultModel` · `agents` · `sessions`
@@ -610,6 +656,8 @@ export interface Config {
Source: [`packages/bundle/headless/src/index.ts:31`](../packages/bundle/headless/src/index.ts) Source: [`packages/bundle/headless/src/index.ts:31`](../packages/bundle/headless/src/index.ts)
<a id="deepseek-aidsh-hooks-claude-code"></a>
## `@deepseek-ai/dsh-hooks-claude-code` ## `@deepseek-ai/dsh-hooks-claude-code`
Requires: `shell` Requires: `shell`
@@ -646,6 +694,8 @@ export interface Config {
Source: [`packages/hooks/hooks-claude-code/src/index.ts:45`](../packages/hooks/hooks-claude-code/src/index.ts) Source: [`packages/hooks/hooks-claude-code/src/index.ts:45`](../packages/hooks/hooks-claude-code/src/index.ts)
<a id="deepseek-aidsh-hooks-codex"></a>
## `@deepseek-ai/dsh-hooks-codex` ## `@deepseek-ai/dsh-hooks-codex`
Requires: `shell` Requires: `shell`
@@ -671,6 +721,8 @@ export interface Config {
Source: [`packages/hooks/hooks-codex/src/index.ts:44`](../packages/hooks/hooks-codex/src/index.ts) Source: [`packages/hooks/hooks-codex/src/index.ts:44`](../packages/hooks/hooks-codex/src/index.ts)
<a id="deepseek-aidsh-host-apiproxy"></a>
## `@deepseek-ai/dsh-host-apiproxy` ## `@deepseek-ai/dsh-host-apiproxy`
Requires: `agentDefaultModel` · `agents` · `attachments` · `directoryPicker` · `llm` · `sessions` · `subagents` · `sessionQuery` · `tools` · `userQuestions` · `workspaceRegistry` Requires: `agentDefaultModel` · `agents` · `attachments` · `directoryPicker` · `llm` · `sessions` · `subagents` · `sessionQuery` · `tools` · `userQuestions` · `workspaceRegistry`
@@ -692,11 +744,19 @@ export interface Config {
* @default 6 * @default 6
*/ */
sessionExportCompressionLevel?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 sessionExportCompressionLevel?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
/**
* Maximum physical size of a cold Session artifact eligible for blankness
* verification. Zero disables probes.
* @default 1024
*/
coldBlankProbeMaxBytes?: number
} }
``` ```
Source: [`packages/host/apiproxy/src/index.ts:41`](../packages/host/apiproxy/src/index.ts) Source: [`packages/host/apiproxy/src/index.ts:41`](../packages/host/apiproxy/src/index.ts)
<a id="deepseek-aidsh-host-directory-picker-browse"></a>
## `@deepseek-ai/dsh-host-directory-picker-browse` ## `@deepseek-ai/dsh-host-directory-picker-browse`
```ts config-catalog ```ts config-catalog
@@ -709,6 +769,8 @@ export interface Config {
Source: [`packages/host/directory-picker-browse/src/index.ts:181`](../packages/host/directory-picker-browse/src/index.ts) Source: [`packages/host/directory-picker-browse/src/index.ts:181`](../packages/host/directory-picker-browse/src/index.ts)
<a id="deepseek-aidsh-host-frontend-static"></a>
## `@deepseek-ai/dsh-host-frontend-static` ## `@deepseek-ai/dsh-host-frontend-static`
Requires: `webServer` Requires: `webServer`
@@ -723,6 +785,8 @@ export interface Config {
Source: [`packages/host/frontend-static/src/index.ts:28`](../packages/host/frontend-static/src/index.ts) Source: [`packages/host/frontend-static/src/index.ts:28`](../packages/host/frontend-static/src/index.ts)
<a id="deepseek-aidsh-host-webserver"></a>
## `@deepseek-ai/dsh-host-webserver` ## `@deepseek-ai/dsh-host-webserver`
```ts config-catalog ```ts config-catalog
@@ -737,6 +801,8 @@ export interface Config {
Source: [`packages/host/webserver/src/index.ts:45`](../packages/host/webserver/src/index.ts) Source: [`packages/host/webserver/src/index.ts:45`](../packages/host/webserver/src/index.ts)
<a id="deepseek-aidsh-invariants"></a>
## `@deepseek-ai/dsh-invariants` ## `@deepseek-ai/dsh-invariants`
```ts config-catalog ```ts config-catalog
@@ -753,6 +819,8 @@ export interface Config {
Source: [`packages/runtime-diagnostics/invariants/src/index.ts:15`](../packages/runtime-diagnostics/invariants/src/index.ts) Source: [`packages/runtime-diagnostics/invariants/src/index.ts:15`](../packages/runtime-diagnostics/invariants/src/index.ts)
<a id="deepseek-aidsh-jobs-local"></a>
## `@deepseek-ai/dsh-jobs-local` ## `@deepseek-ai/dsh-jobs-local`
```ts config-catalog ```ts config-catalog
@@ -768,6 +836,8 @@ export interface Config {
Source: [`packages/jobs/jobs-local/src/index.ts:31`](../packages/jobs/jobs-local/src/index.ts) Source: [`packages/jobs/jobs-local/src/index.ts:31`](../packages/jobs/jobs-local/src/index.ts)
<a id="deepseek-aidsh-llm-deepseek"></a>
## `@deepseek-ai/dsh-llm-deepseek` ## `@deepseek-ai/dsh-llm-deepseek`
Requires: `llm` Requires: `llm`
@@ -821,6 +891,8 @@ Depends on: [`RetryPolicyConfig`](../packages/llm/llm/src/index.ts)
Source: [`packages/llm/llm-deepseek/src/index.ts:62`](../packages/llm/llm-deepseek/src/index.ts) Source: [`packages/llm/llm-deepseek/src/index.ts:62`](../packages/llm/llm-deepseek/src/index.ts)
<a id="deepseek-aidsh-llm-pi-ai"></a>
## `@deepseek-ai/dsh-llm-pi-ai` ## `@deepseek-ai/dsh-llm-pi-ai`
Requires: `llm` Requires: `llm`
@@ -1009,6 +1081,8 @@ Depends on: `Api` (`@earendil-works/pi-ai`) · `CacheRetention` (`@earendil-work
Source: [`packages/llm/llm-pi-ai/src/config.ts:172`](../packages/llm/llm-pi-ai/src/config.ts) Source: [`packages/llm/llm-pi-ai/src/config.ts:172`](../packages/llm/llm-pi-ai/src/config.ts)
<a id="deepseek-aidsh-llm-replay"></a>
## `@deepseek-ai/dsh-llm-replay` ## `@deepseek-ai/dsh-llm-replay`
Requires: `llm` Requires: `llm`
@@ -1075,6 +1149,8 @@ Depends on: [`ModelModality`](../packages/llm/llm/src/index.ts) · [`RetryPolicy
Source: [`packages/test-support/llm-replay/src/index.ts:776`](../packages/test-support/llm-replay/src/index.ts) Source: [`packages/test-support/llm-replay/src/index.ts:776`](../packages/test-support/llm-replay/src/index.ts)
<a id="deepseek-aidsh-llm-retry"></a>
## `@deepseek-ai/dsh-llm-retry` ## `@deepseek-ai/dsh-llm-retry`
Requires: `agents` Requires: `agents`
@@ -1086,6 +1162,8 @@ export type Config = Readonly<Record<string, never>>
Source: [`packages/llm/llm-retry/src/index.ts:24`](../packages/llm/llm-retry/src/index.ts) Source: [`packages/llm/llm-retry/src/index.ts:24`](../packages/llm/llm-retry/src/index.ts)
<a id="deepseek-aidsh-lsp-stdio"></a>
## `@deepseek-ai/dsh-lsp-stdio` ## `@deepseek-ai/dsh-lsp-stdio`
Requires: `fs` · `lsp` · `subprocess` Requires: `fs` · `lsp` · `subprocess`
@@ -1126,6 +1204,8 @@ export interface LspLocalServerConfig {
Source: [`packages/lsp/lsp-stdio/src/index.ts:82`](../packages/lsp/lsp-stdio/src/index.ts) Source: [`packages/lsp/lsp-stdio/src/index.ts:82`](../packages/lsp/lsp-stdio/src/index.ts)
<a id="deepseek-aidsh-mcp-client"></a>
## `@deepseek-ai/dsh-mcp-client` ## `@deepseek-ai/dsh-mcp-client`
Requires: `tools` Requires: `tools`
@@ -1197,6 +1277,8 @@ export interface ReconnectConfig {
Source: [`packages/mcp/mcp-client/src/index.ts:98`](../packages/mcp/mcp-client/src/index.ts) Source: [`packages/mcp/mcp-client/src/index.ts:98`](../packages/mcp/mcp-client/src/index.ts)
<a id="deepseek-aidsh-message-feedback"></a>
## `@deepseek-ai/dsh-message-feedback` ## `@deepseek-ai/dsh-message-feedback`
Requires: `storageDomain` · `sessionPersistence` · `sessions` Requires: `storageDomain` · `sessionPersistence` · `sessions`
@@ -1211,6 +1293,8 @@ export interface Config {
Source: [`packages/feedback/message-feedback/src/index.ts:49`](../packages/feedback/message-feedback/src/index.ts) Source: [`packages/feedback/message-feedback/src/index.ts:49`](../packages/feedback/message-feedback/src/index.ts)
<a id="deepseek-aidsh-permission-presets"></a>
## `@deepseek-ai/dsh-permission-presets` ## `@deepseek-ai/dsh-permission-presets`
Requires: `shell` · `approval` · `sessions` Requires: `shell` · `approval` · `sessions`
@@ -1248,6 +1332,8 @@ Depends on: [`ApprovalPolicy`](subsystems/approval.md) · [`SandboxMode`](subsys
Source: [`packages/interaction/permission-presets/src/index.ts:140`](../packages/interaction/permission-presets/src/index.ts) Source: [`packages/interaction/permission-presets/src/index.ts:140`](../packages/interaction/permission-presets/src/index.ts)
<a id="deepseek-aidsh-persona"></a>
## `@deepseek-ai/dsh-persona` ## `@deepseek-ai/dsh-persona`
Requires: `systemPrompt` Requires: `systemPrompt`
@@ -1270,6 +1356,8 @@ export interface Config {
Source: [`packages/preset/persona/src/index.ts:34`](../packages/preset/persona/src/index.ts) Source: [`packages/preset/persona/src/index.ts:34`](../packages/preset/persona/src/index.ts)
<a id="deepseek-aidsh-plan-mode"></a>
## `@deepseek-ai/dsh-plan-mode` ## `@deepseek-ai/dsh-plan-mode`
Requires: `tools` · `systemPrompt` Requires: `tools` · `systemPrompt`
@@ -1284,6 +1372,8 @@ export interface PlanModeConfig {
Source: [`packages/plan/plan-mode/src/index.ts:70`](../packages/plan/plan-mode/src/index.ts) Source: [`packages/plan/plan-mode/src/index.ts:70`](../packages/plan/plan-mode/src/index.ts)
<a id="deepseek-aidsh-pwsh-local"></a>
## `@deepseek-ai/dsh-pwsh-local` ## `@deepseek-ai/dsh-pwsh-local`
Requires: `subprocess` Requires: `subprocess`
@@ -1315,6 +1405,8 @@ export interface Config {
Source: [`packages/shell/pwsh-local/src/index.ts:58`](../packages/shell/pwsh-local/src/index.ts) Source: [`packages/shell/pwsh-local/src/index.ts:58`](../packages/shell/pwsh-local/src/index.ts)
<a id="deepseek-aidsh-pwsh-sandbox"></a>
## `@deepseek-ai/dsh-pwsh-sandbox` ## `@deepseek-ai/dsh-pwsh-sandbox`
Requires: `subprocess` · `sandbox` · `sandboxPolicy` Requires: `subprocess` · `sandbox` · `sandboxPolicy`
@@ -1335,6 +1427,8 @@ Depends on: [`LocalConfig`](#deepseek-aidsh-pwsh-local)
Source: [`packages/shell/pwsh-sandbox/src/index.ts:40`](../packages/shell/pwsh-sandbox/src/index.ts) Source: [`packages/shell/pwsh-sandbox/src/index.ts:40`](../packages/shell/pwsh-sandbox/src/index.ts)
<a id="deepseek-aidsh-repeat-tool-reminder"></a>
## `@deepseek-ai/dsh-repeat-tool-reminder` ## `@deepseek-ai/dsh-repeat-tool-reminder`
```ts config-catalog ```ts config-catalog
@@ -1367,6 +1461,8 @@ export interface Config {
Source: [`packages/guard/repeat-tool-reminder/src/index.ts:28`](../packages/guard/repeat-tool-reminder/src/index.ts) Source: [`packages/guard/repeat-tool-reminder/src/index.ts:28`](../packages/guard/repeat-tool-reminder/src/index.ts)
<a id="deepseek-aidsh-sandbox-local"></a>
## `@deepseek-ai/dsh-sandbox-local` ## `@deepseek-ai/dsh-sandbox-local`
```ts config-catalog ```ts config-catalog
@@ -1397,6 +1493,8 @@ export interface Config {
Source: [`packages/sandbox/sandbox-local/src/index.ts:44`](../packages/sandbox/sandbox-local/src/index.ts) Source: [`packages/sandbox/sandbox-local/src/index.ts:44`](../packages/sandbox/sandbox-local/src/index.ts)
<a id="deepseek-aidsh-sandbox-policy"></a>
## `@deepseek-ai/dsh-sandbox-policy` ## `@deepseek-ai/dsh-sandbox-policy`
```ts config-catalog ```ts config-catalog
@@ -1422,6 +1520,8 @@ Depends on: [`SandboxMode`](subsystems/sandbox.md)
Source: [`packages/sandbox/sandbox-policy/src/index.ts:67`](../packages/sandbox/sandbox-policy/src/index.ts) Source: [`packages/sandbox/sandbox-policy/src/index.ts:67`](../packages/sandbox/sandbox-policy/src/index.ts)
<a id="deepseek-aidsh-sdk-jsonrpc-server"></a>
## `@deepseek-ai/dsh-sdk-jsonrpc-server` ## `@deepseek-ai/dsh-sdk-jsonrpc-server`
Requires: `agents` Requires: `agents`
@@ -1444,6 +1544,8 @@ Depends on: `Readable` (`node:stream`) · `Writable` (`node:stream`)
Source: [`packages/sdk/server/src/index.ts:25`](../packages/sdk/server/src/index.ts) Source: [`packages/sdk/server/src/index.ts:25`](../packages/sdk/server/src/index.ts)
<a id="deepseek-aidsh-session-persistence-jsonl"></a>
## `@deepseek-ai/dsh-session-persistence-jsonl` ## `@deepseek-ai/dsh-session-persistence-jsonl`
Requires: `sessions` Requires: `sessions`
@@ -1481,6 +1583,8 @@ export type JsonlCompression = 'zstd' | 'none'
Source: [`packages/session/session-persistence-jsonl/src/index.ts:60`](../packages/session/session-persistence-jsonl/src/index.ts) Source: [`packages/session/session-persistence-jsonl/src/index.ts:60`](../packages/session/session-persistence-jsonl/src/index.ts)
<a id="deepseek-aidsh-session-persistence-sqlite"></a>
## `@deepseek-ai/dsh-session-persistence-sqlite` ## `@deepseek-ai/dsh-session-persistence-sqlite`
Requires: `sessions` Requires: `sessions`
@@ -1524,6 +1628,8 @@ export type JournalMode = 'wal' | 'delete' | 'truncate' | 'persist'
Source: [`packages/session/session-persistence-sqlite/src/index.ts:70`](../packages/session/session-persistence-sqlite/src/index.ts) Source: [`packages/session/session-persistence-sqlite/src/index.ts:70`](../packages/session/session-persistence-sqlite/src/index.ts)
<a id="deepseek-aidsh-session-projection-cache"></a>
## `@deepseek-ai/dsh-session-projection-cache` ## `@deepseek-ai/dsh-session-projection-cache`
Requires: `storageDomain` · `sessionProjections` · `sessionPersistence` · `sessions` Requires: `storageDomain` · `sessionProjections` · `sessionPersistence` · `sessions`
@@ -1545,6 +1651,8 @@ export interface Config {
Source: [`packages/session/session-projection-cache/src/index.ts:42`](../packages/session/session-projection-cache/src/index.ts) Source: [`packages/session/session-projection-cache/src/index.ts:42`](../packages/session/session-projection-cache/src/index.ts)
<a id="deepseek-aidsh-session-query-sqlite"></a>
## `@deepseek-ai/dsh-session-query-sqlite` ## `@deepseek-ai/dsh-session-query-sqlite`
Requires: `sessions` Requires: `sessions`
@@ -1589,6 +1697,8 @@ Depends on: [`SessionQueryConfig`](../packages/session-query/session-query/src/i
Source: [`packages/session-query/session-query-sqlite/src/index.ts:89`](../packages/session-query/session-query-sqlite/src/index.ts) Source: [`packages/session-query/session-query-sqlite/src/index.ts:89`](../packages/session-query/session-query-sqlite/src/index.ts)
<a id="deepseek-aidsh-session-reference"></a>
## `@deepseek-ai/dsh-session-reference` ## `@deepseek-ai/dsh-session-reference`
Requires: `sessionQuery` Requires: `sessionQuery`
@@ -1607,6 +1717,8 @@ export interface Config {
Source: [`packages/context/session-reference/src/config.ts:11`](../packages/context/session-reference/src/config.ts) Source: [`packages/context/session-reference/src/config.ts:11`](../packages/context/session-reference/src/config.ts)
<a id="deepseek-aidsh-session-telemetry-otel"></a>
## `@deepseek-ai/dsh-session-telemetry-otel` ## `@deepseek-ai/dsh-session-telemetry-otel`
Requires: `sessions` Requires: `sessions`
@@ -1651,6 +1763,8 @@ Depends on: `BatchLogRecordProcessorOptions` (`@opentelemetry/sdk-logs`) · `OTL
Source: [`packages/session/session-telemetry-otel/src/index.ts:91`](../packages/session/session-telemetry-otel/src/index.ts) Source: [`packages/session/session-telemetry-otel/src/index.ts:91`](../packages/session/session-telemetry-otel/src/index.ts)
<a id="deepseek-aidsh-session-title"></a>
## `@deepseek-ai/dsh-session-title` ## `@deepseek-ai/dsh-session-title`
Requires: `sessions` Requires: `sessions`
@@ -1669,6 +1783,8 @@ export interface Config {
Source: [`packages/session/session-title/src/index.ts:79`](../packages/session/session-title/src/index.ts) Source: [`packages/session/session-title/src/index.ts:79`](../packages/session/session-title/src/index.ts)
<a id="deepseek-aidsh-session-title-all-prompts-llm"></a>
## `@deepseek-ai/dsh-session-title-all-prompts-llm` ## `@deepseek-ai/dsh-session-title-all-prompts-llm`
Requires: `sessionTitle` · `llm` · `sessions` Requires: `sessionTitle` · `llm` · `sessions`
@@ -1682,6 +1798,8 @@ Depends on: [`SessionTitleLlmConfig`](../packages/session/session-title-llm/src/
Source: [`packages/session/session-title-all-prompts-llm/src/index.ts:15`](../packages/session/session-title-all-prompts-llm/src/index.ts) Source: [`packages/session/session-title-all-prompts-llm/src/index.ts:15`](../packages/session/session-title-all-prompts-llm/src/index.ts)
<a id="deepseek-aidsh-session-title-first-prompt-llm"></a>
## `@deepseek-ai/dsh-session-title-first-prompt-llm` ## `@deepseek-ai/dsh-session-title-first-prompt-llm`
Requires: `sessionTitle` · `llm` · `sessions` Requires: `sessionTitle` · `llm` · `sessions`
@@ -1695,6 +1813,8 @@ Depends on: [`SessionTitleLlmConfig`](../packages/session/session-title-llm/src/
Source: [`packages/session/session-title-first-prompt-llm/src/index.ts:15`](../packages/session/session-title-first-prompt-llm/src/index.ts) Source: [`packages/session/session-title-first-prompt-llm/src/index.ts:15`](../packages/session/session-title-first-prompt-llm/src/index.ts)
<a id="deepseek-aidsh-settings-file"></a>
## `@deepseek-ai/dsh-settings-file` ## `@deepseek-ai/dsh-settings-file`
```ts config-catalog ```ts config-catalog
@@ -1713,6 +1833,8 @@ export interface Config {
Source: [`packages/settings/settings-file/src/index.ts:21`](../packages/settings/settings-file/src/index.ts) Source: [`packages/settings/settings-file/src/index.ts:21`](../packages/settings/settings-file/src/index.ts)
<a id="deepseek-aidsh-shell-env"></a>
## `@deepseek-ai/dsh-shell-env` ## `@deepseek-ai/dsh-shell-env`
```ts config-catalog ```ts config-catalog
@@ -1725,6 +1847,8 @@ export interface Config {
Source: [`packages/shell/shell-env/src/index.ts:29`](../packages/shell/shell-env/src/index.ts) Source: [`packages/shell/shell-env/src/index.ts:29`](../packages/shell/shell-env/src/index.ts)
<a id="deepseek-aidsh-skill"></a>
## `@deepseek-ai/dsh-skill` ## `@deepseek-ai/dsh-skill`
```ts config-catalog ```ts config-catalog
@@ -1737,6 +1861,8 @@ export interface Config {
Source: [`packages/skill/skill/src/index.ts:279`](../packages/skill/skill/src/index.ts) Source: [`packages/skill/skill/src/index.ts:279`](../packages/skill/skill/src/index.ts)
<a id="deepseek-aidsh-skill-filesystem"></a>
## `@deepseek-ai/dsh-skill-filesystem` ## `@deepseek-ai/dsh-skill-filesystem`
Requires: `skills` Requires: `skills`
@@ -1773,6 +1899,8 @@ export interface Config {
Source: [`packages/skill/skill-filesystem/src/index.ts:49`](../packages/skill/skill-filesystem/src/index.ts) Source: [`packages/skill/skill-filesystem/src/index.ts:49`](../packages/skill/skill-filesystem/src/index.ts)
<a id="deepseek-aidsh-spill-local"></a>
## `@deepseek-ai/dsh-spill-local` ## `@deepseek-ai/dsh-spill-local`
```ts config-catalog ```ts config-catalog
@@ -1789,6 +1917,8 @@ export interface Config {
Source: [`packages/spill/spill-local/src/index.ts:22`](../packages/spill/spill-local/src/index.ts) Source: [`packages/spill/spill-local/src/index.ts:22`](../packages/spill/spill-local/src/index.ts)
<a id="deepseek-aidsh-spill-policy"></a>
## `@deepseek-ai/dsh-spill-policy` ## `@deepseek-ai/dsh-spill-policy`
Requires: `tools` Requires: `tools`
@@ -1807,6 +1937,8 @@ export interface Config {
Source: [`packages/spill/spill-policy/src/index.ts:60`](../packages/spill/spill-policy/src/index.ts) Source: [`packages/spill/spill-policy/src/index.ts:60`](../packages/spill/spill-policy/src/index.ts)
<a id="deepseek-aidsh-storage-domain"></a>
## `@deepseek-ai/dsh-storage-domain` ## `@deepseek-ai/dsh-storage-domain`
Requires: `storage` Requires: `storage`
@@ -1828,6 +1960,8 @@ export interface Config {
Source: [`packages/storage/storage-domain/src/index.ts:52`](../packages/storage/storage-domain/src/index.ts) Source: [`packages/storage/storage-domain/src/index.ts:52`](../packages/storage/storage-domain/src/index.ts)
<a id="deepseek-aidsh-storage-json"></a>
## `@deepseek-ai/dsh-storage-json` ## `@deepseek-ai/dsh-storage-json`
Requires: `storage` Requires: `storage`
@@ -1847,6 +1981,8 @@ export interface Config {
Source: [`packages/storage/storage-json/src/index.ts:27`](../packages/storage/storage-json/src/index.ts) Source: [`packages/storage/storage-json/src/index.ts:27`](../packages/storage/storage-json/src/index.ts)
<a id="deepseek-aidsh-storage-sqlite"></a>
## `@deepseek-ai/dsh-storage-sqlite` ## `@deepseek-ai/dsh-storage-sqlite`
Requires: `storage` Requires: `storage`
@@ -1885,6 +2021,8 @@ export type JournalMode = 'wal' | 'delete' | 'truncate' | 'persist'
Source: [`packages/storage/storage-sqlite/src/index.ts:24`](../packages/storage/storage-sqlite/src/index.ts) Source: [`packages/storage/storage-sqlite/src/index.ts:24`](../packages/storage/storage-sqlite/src/index.ts)
<a id="deepseek-aidsh-subagent-acp"></a>
## `@deepseek-ai/dsh-subagent-acp` ## `@deepseek-ai/dsh-subagent-acp`
Requires: `subagents` · `subprocess` Requires: `subagents` · `subprocess`
@@ -1936,6 +2074,8 @@ export type PermissionPolicy = 'allow' | 'reject'
Source: [`packages/subagent/subagent-acp/src/index.ts:27`](../packages/subagent/subagent-acp/src/index.ts) Source: [`packages/subagent/subagent-acp/src/index.ts:27`](../packages/subagent/subagent-acp/src/index.ts)
<a id="deepseek-aidsh-subagent-claude-code"></a>
## `@deepseek-ai/dsh-subagent-claude-code` ## `@deepseek-ai/dsh-subagent-claude-code`
Requires: `subagents` · `subprocess` Requires: `subagents` · `subprocess`
@@ -1955,6 +2095,8 @@ export interface Config {
Source: [`packages/subagent/subagent-claude-code/src/index.ts:32`](../packages/subagent/subagent-claude-code/src/index.ts) Source: [`packages/subagent/subagent-claude-code/src/index.ts:32`](../packages/subagent/subagent-claude-code/src/index.ts)
<a id="deepseek-aidsh-subagent-codex"></a>
## `@deepseek-ai/dsh-subagent-codex` ## `@deepseek-ai/dsh-subagent-codex`
Requires: `subagents` · `subprocess` Requires: `subagents` · `subprocess`
@@ -1974,6 +2116,8 @@ export interface Config {
Source: [`packages/subagent/subagent-codex/src/index.ts:30`](../packages/subagent/subagent-codex/src/index.ts) Source: [`packages/subagent/subagent-codex/src/index.ts:30`](../packages/subagent/subagent-codex/src/index.ts)
<a id="deepseek-aidsh-subagent-dsh-sdk"></a>
## `@deepseek-ai/dsh-subagent-dsh-sdk` ## `@deepseek-ai/dsh-subagent-dsh-sdk`
Requires: `subagents` Requires: `subagents`
@@ -2025,6 +2169,8 @@ export interface Config {
Source: [`packages/subagent/subagent-dsh-sdk/src/index.ts:29`](../packages/subagent/subagent-dsh-sdk/src/index.ts) Source: [`packages/subagent/subagent-dsh-sdk/src/index.ts:29`](../packages/subagent/subagent-dsh-sdk/src/index.ts)
<a id="deepseek-aidsh-subagent-fork-in-process"></a>
## `@deepseek-ai/dsh-subagent-fork-in-process` ## `@deepseek-ai/dsh-subagent-fork-in-process`
Requires: `subagents` Requires: `subagents`
@@ -2039,6 +2185,8 @@ export interface Config {
Source: [`packages/subagent/subagent-fork-in-process/src/index.ts:31`](../packages/subagent/subagent-fork-in-process/src/index.ts) Source: [`packages/subagent/subagent-fork-in-process/src/index.ts:31`](../packages/subagent/subagent-fork-in-process/src/index.ts)
<a id="deepseek-aidsh-subagent-spawn-in-process"></a>
## `@deepseek-ai/dsh-subagent-spawn-in-process` ## `@deepseek-ai/dsh-subagent-spawn-in-process`
Requires: `subagents` Requires: `subagents`
@@ -2053,6 +2201,8 @@ export interface Config {
Source: [`packages/subagent/subagent-spawn-in-process/src/index.ts:25`](../packages/subagent/subagent-spawn-in-process/src/index.ts) Source: [`packages/subagent/subagent-spawn-in-process/src/index.ts:25`](../packages/subagent/subagent-spawn-in-process/src/index.ts)
<a id="deepseek-aidsh-subprocess-e2b"></a>
## `@deepseek-ai/dsh-subprocess-e2b` ## `@deepseek-ai/dsh-subprocess-e2b`
Requires: `e2b` Requires: `e2b`
@@ -2067,6 +2217,8 @@ export interface Config {
Source: [`packages/e2b/subprocess-e2b/src/index.ts:25`](../packages/e2b/subprocess-e2b/src/index.ts) Source: [`packages/e2b/subprocess-e2b/src/index.ts:25`](../packages/e2b/subprocess-e2b/src/index.ts)
<a id="deepseek-aidsh-system-prompt"></a>
## `@deepseek-ai/dsh-system-prompt` ## `@deepseek-ai/dsh-system-prompt`
```ts config-catalog ```ts config-catalog
@@ -2092,6 +2244,8 @@ export interface Config {
Source: [`packages/core/system-prompt/src/index.ts:186`](../packages/core/system-prompt/src/index.ts) Source: [`packages/core/system-prompt/src/index.ts:186`](../packages/core/system-prompt/src/index.ts)
<a id="deepseek-aidsh-terminal-bash"></a>
## `@deepseek-ai/dsh-terminal-bash` ## `@deepseek-ai/dsh-terminal-bash`
Requires: `terminals` · `sandboxPolicy` · `subprocess` Requires: `terminals` · `sandboxPolicy` · `subprocess`
@@ -2135,6 +2289,8 @@ export interface Config {
Source: [`packages/terminal/terminal-bash/src/config.ts:6`](../packages/terminal/terminal-bash/src/config.ts) Source: [`packages/terminal/terminal-bash/src/config.ts:6`](../packages/terminal/terminal-bash/src/config.ts)
<a id="deepseek-aidsh-time-context"></a>
## `@deepseek-ai/dsh-time-context` ## `@deepseek-ai/dsh-time-context`
Requires: `agents` Requires: `agents`
@@ -2151,6 +2307,8 @@ export interface Config {
Source: [`packages/context/time-context/src/index.ts:27`](../packages/context/time-context/src/index.ts) Source: [`packages/context/time-context/src/index.ts:27`](../packages/context/time-context/src/index.ts)
<a id="deepseek-aidsh-tmux-context"></a>
## `@deepseek-ai/dsh-tmux-context` ## `@deepseek-ai/dsh-tmux-context`
Requires: `agents` Requires: `agents`
@@ -2165,6 +2323,8 @@ export interface Config {
Source: [`packages/context/tmux-context/src/index.ts:34`](../packages/context/tmux-context/src/index.ts) Source: [`packages/context/tmux-context/src/index.ts:34`](../packages/context/tmux-context/src/index.ts)
<a id="deepseek-aidsh-token-meter"></a>
## `@deepseek-ai/dsh-token-meter` ## `@deepseek-ai/dsh-token-meter`
```ts config-catalog ```ts config-catalog
@@ -2174,6 +2334,8 @@ export type TokenMeterConfig = Record<string, never>
Source: [`packages/llm/token-meter/src/types.ts:12`](../packages/llm/token-meter/src/types.ts) Source: [`packages/llm/token-meter/src/types.ts:12`](../packages/llm/token-meter/src/types.ts)
<a id="deepseek-aidsh-tool-bash"></a>
## `@deepseek-ai/dsh-tool-bash` ## `@deepseek-ai/dsh-tool-bash`
Requires: `tools` · `shell` · `systemPrompt` · `shellEnv` Requires: `tools` · `shell` · `systemPrompt` · `shellEnv`
@@ -2188,6 +2350,8 @@ export interface Config {
Source: [`packages/shell/tool-bash/src/index.ts:34`](../packages/shell/tool-bash/src/index.ts) Source: [`packages/shell/tool-bash/src/index.ts:34`](../packages/shell/tool-bash/src/index.ts)
<a id="deepseek-aidsh-tool-bash-persistent"></a>
## `@deepseek-ai/dsh-tool-bash-persistent` ## `@deepseek-ai/dsh-tool-bash-persistent`
Requires: `tools` · `terminals` Requires: `tools` · `terminals`
@@ -2208,6 +2372,8 @@ export interface Config {
Source: [`packages/shell/tool-bash-persistent/src/index.ts:405`](../packages/shell/tool-bash-persistent/src/index.ts) Source: [`packages/shell/tool-bash-persistent/src/index.ts:405`](../packages/shell/tool-bash-persistent/src/index.ts)
<a id="deepseek-aidsh-tool-fs"></a>
## `@deepseek-ai/dsh-tool-fs` ## `@deepseek-ai/dsh-tool-fs`
Requires: `tools` · `fs` · `systemPrompt` Requires: `tools` · `fs` · `systemPrompt`
@@ -2228,6 +2394,8 @@ export interface Config {
Source: [`packages/fs/tool-fs/src/index.ts:25`](../packages/fs/tool-fs/src/index.ts) Source: [`packages/fs/tool-fs/src/index.ts:25`](../packages/fs/tool-fs/src/index.ts)
<a id="deepseek-aidsh-tool-fs-search"></a>
## `@deepseek-ai/dsh-tool-fs-search` ## `@deepseek-ai/dsh-tool-fs-search`
Requires: `tools` · `systemPrompt` · `subprocess` Requires: `tools` · `systemPrompt` · `subprocess`
@@ -2261,6 +2429,8 @@ export interface Config {
Source: [`packages/fs/tool-fs-search/src/index.ts:73`](../packages/fs/tool-fs-search/src/index.ts) Source: [`packages/fs/tool-fs-search/src/index.ts:73`](../packages/fs/tool-fs-search/src/index.ts)
<a id="deepseek-aidsh-tool-goal"></a>
## `@deepseek-ai/dsh-tool-goal` ## `@deepseek-ai/dsh-tool-goal`
Requires: `agents` · `goals` · `tools` · `systemPrompt` Requires: `agents` · `goals` · `tools` · `systemPrompt`
@@ -2275,6 +2445,8 @@ export interface Config {
Source: [`packages/goal/tool-goal/src/index.ts:26`](../packages/goal/tool-goal/src/index.ts) Source: [`packages/goal/tool-goal/src/index.ts:26`](../packages/goal/tool-goal/src/index.ts)
<a id="deepseek-aidsh-tool-jobs"></a>
## `@deepseek-ai/dsh-tool-jobs` ## `@deepseek-ai/dsh-tool-jobs`
Requires: `tools` · `jobs` · `systemPrompt` Requires: `tools` · `jobs` · `systemPrompt`
@@ -2307,6 +2479,8 @@ export type CompletionDelivery = 'quiet' | 'wakeup'
Source: [`packages/jobs/tool-jobs/src/index.ts:32`](../packages/jobs/tool-jobs/src/index.ts) Source: [`packages/jobs/tool-jobs/src/index.ts:32`](../packages/jobs/tool-jobs/src/index.ts)
<a id="deepseek-aidsh-tool-lsp"></a>
## `@deepseek-ai/dsh-tool-lsp` ## `@deepseek-ai/dsh-tool-lsp`
Requires: `tools` · `lsp` · `systemPrompt` Requires: `tools` · `lsp` · `systemPrompt`
@@ -2325,6 +2499,8 @@ export interface Config {
Source: [`packages/lsp/tool-lsp/src/index.ts:58`](../packages/lsp/tool-lsp/src/index.ts) Source: [`packages/lsp/tool-lsp/src/index.ts:58`](../packages/lsp/tool-lsp/src/index.ts)
<a id="deepseek-aidsh-tool-pwsh"></a>
## `@deepseek-ai/dsh-tool-pwsh` ## `@deepseek-ai/dsh-tool-pwsh`
Requires: `tools` · `shell` · `systemPrompt` · `shellEnv` Requires: `tools` · `shell` · `systemPrompt` · `shellEnv`
@@ -2339,6 +2515,8 @@ export interface Config {
Source: [`packages/shell/tool-pwsh/src/index.ts:52`](../packages/shell/tool-pwsh/src/index.ts) Source: [`packages/shell/tool-pwsh/src/index.ts:52`](../packages/shell/tool-pwsh/src/index.ts)
<a id="deepseek-aidsh-tool-ralph"></a>
## `@deepseek-ai/dsh-tool-ralph` ## `@deepseek-ai/dsh-tool-ralph`
Requires: `tools` · `workflowEngine` · `subagents` · `systemPrompt` Requires: `tools` · `workflowEngine` · `subagents` · `systemPrompt`
@@ -2359,6 +2537,8 @@ export interface Config {
Source: [`packages/workflow/tool-ralph/src/index.ts:23`](../packages/workflow/tool-ralph/src/index.ts) Source: [`packages/workflow/tool-ralph/src/index.ts:23`](../packages/workflow/tool-ralph/src/index.ts)
<a id="deepseek-aidsh-tool-session-query"></a>
## `@deepseek-ai/dsh-tool-session-query` ## `@deepseek-ai/dsh-tool-session-query`
Requires: `tools` · `systemPrompt` · `sessionQuery` Requires: `tools` · `systemPrompt` · `sessionQuery`
@@ -2375,6 +2555,8 @@ export interface Config {
Source: [`packages/session-query/tool-session-query/src/index.ts:29`](../packages/session-query/tool-session-query/src/index.ts) Source: [`packages/session-query/tool-session-query/src/index.ts:29`](../packages/session-query/tool-session-query/src/index.ts)
<a id="deepseek-aidsh-tool-skill"></a>
## `@deepseek-ai/dsh-tool-skill` ## `@deepseek-ai/dsh-tool-skill`
Requires: `agents` · `tools` · `skills` Requires: `agents` · `tools` · `skills`
@@ -2389,6 +2571,8 @@ export interface Config {
Source: [`packages/skill/tool-skill/src/index.ts:61`](../packages/skill/tool-skill/src/index.ts) Source: [`packages/skill/tool-skill/src/index.ts:61`](../packages/skill/tool-skill/src/index.ts)
<a id="deepseek-aidsh-tool-str-replace-editor"></a>
## `@deepseek-ai/dsh-tool-str-replace-editor` ## `@deepseek-ai/dsh-tool-str-replace-editor`
Requires: `tools` · `fs` Requires: `tools` · `fs`
@@ -2405,6 +2589,8 @@ export interface Config {
Source: [`packages/fs/tool-str-replace-editor/src/index.ts:497`](../packages/fs/tool-str-replace-editor/src/index.ts) Source: [`packages/fs/tool-str-replace-editor/src/index.ts:497`](../packages/fs/tool-str-replace-editor/src/index.ts)
<a id="deepseek-aidsh-tool-subagent"></a>
## `@deepseek-ai/dsh-tool-subagent` ## `@deepseek-ai/dsh-tool-subagent`
Requires: `tools` · `subagents` · `systemPrompt` Requires: `tools` · `subagents` · `systemPrompt`
@@ -2468,6 +2654,8 @@ Depends on: [`AgentOptions`](subsystems/core.md)
Source: [`packages/subagent/tool-subagent/src/index.ts:29`](../packages/subagent/tool-subagent/src/index.ts) Source: [`packages/subagent/tool-subagent/src/index.ts:29`](../packages/subagent/tool-subagent/src/index.ts)
<a id="deepseek-aidsh-tool-subagent-report"></a>
## `@deepseek-ai/dsh-tool-subagent-report` ## `@deepseek-ai/dsh-tool-subagent-report`
Requires: `subagents` · `tools` · `systemPrompt` Requires: `subagents` · `tools` · `systemPrompt`
@@ -2488,6 +2676,8 @@ Depends on: [`SubagentReportDelivery`](subsystems/subagent.md)
Source: [`packages/subagent/tool-subagent-report/src/index.ts:27`](../packages/subagent/tool-subagent-report/src/index.ts) Source: [`packages/subagent/tool-subagent-report/src/index.ts:27`](../packages/subagent/tool-subagent-report/src/index.ts)
<a id="deepseek-aidsh-tool-terminal"></a>
## `@deepseek-ai/dsh-tool-terminal` ## `@deepseek-ai/dsh-tool-terminal`
Requires: `terminals` · `tools` · `systemPrompt` Requires: `terminals` · `tools` · `systemPrompt`
@@ -2504,6 +2694,8 @@ export interface Config {
Source: [`packages/terminal/tool-terminal/src/index.ts:35`](../packages/terminal/tool-terminal/src/index.ts) Source: [`packages/terminal/tool-terminal/src/index.ts:35`](../packages/terminal/tool-terminal/src/index.ts)
<a id="deepseek-aidsh-tool-todo"></a>
## `@deepseek-ai/dsh-tool-todo` ## `@deepseek-ai/dsh-tool-todo`
Requires: `tools` Requires: `tools`
@@ -2524,6 +2716,8 @@ export interface Config {
Source: [`packages/todo/tool-todo/src/index.ts:29`](../packages/todo/tool-todo/src/index.ts) Source: [`packages/todo/tool-todo/src/index.ts:29`](../packages/todo/tool-todo/src/index.ts)
<a id="deepseek-aidsh-tool-web"></a>
## `@deepseek-ai/dsh-tool-web` ## `@deepseek-ai/dsh-tool-web`
Requires: `tools` · `web` · `systemPrompt` Requires: `tools` · `web` · `systemPrompt`
@@ -2548,6 +2742,8 @@ export interface Config {
Source: [`packages/web/tool-web/src/index.ts:37`](../packages/web/tool-web/src/index.ts) Source: [`packages/web/tool-web/src/index.ts:37`](../packages/web/tool-web/src/index.ts)
<a id="deepseek-aidsh-tool-workflow"></a>
## `@deepseek-ai/dsh-tool-workflow` ## `@deepseek-ai/dsh-tool-workflow`
Requires: `tools` · `workflowEngine` · `systemPrompt` Requires: `tools` · `workflowEngine` · `systemPrompt`
@@ -2564,6 +2760,8 @@ export interface Config {
Source: [`packages/workflow/tool-workflow/src/index.ts:33`](../packages/workflow/tool-workflow/src/index.ts) Source: [`packages/workflow/tool-workflow/src/index.ts:33`](../packages/workflow/tool-workflow/src/index.ts)
<a id="deepseek-aidsh-tools"></a>
## `@deepseek-ai/dsh-tools` ## `@deepseek-ai/dsh-tools`
Requires: `systemPrompt` Requires: `systemPrompt`
@@ -2598,6 +2796,8 @@ export type ToolPresentationMode = 'native' | 'code' | 'both'
Source: [`packages/core/tools/src/index.ts:654`](../packages/core/tools/src/index.ts) Source: [`packages/core/tools/src/index.ts:654`](../packages/core/tools/src/index.ts)
<a id="deepseek-aidsh-typert-loader"></a>
## `@deepseek-ai/dsh-typert-loader` ## `@deepseek-ai/dsh-typert-loader`
Requires: `typert` · `loader` Requires: `typert` · `loader`
@@ -2612,6 +2812,8 @@ export interface Config {
Source: [`packages/typert/loader/src/index.ts:47`](../packages/typert/loader/src/index.ts) Source: [`packages/typert/loader/src/index.ts:47`](../packages/typert/loader/src/index.ts)
<a id="deepseek-aidsh-user-approval"></a>
## `@deepseek-ai/dsh-user-approval` ## `@deepseek-ai/dsh-user-approval`
```ts config-catalog ```ts config-catalog
@@ -2641,6 +2843,8 @@ export type ApprovalPolicy = 'ask' | 'never'
Source: [`packages/interaction/user-approval/src/index.ts:177`](../packages/interaction/user-approval/src/index.ts) Source: [`packages/interaction/user-approval/src/index.ts:177`](../packages/interaction/user-approval/src/index.ts)
<a id="deepseek-aidsh-web"></a>
## `@deepseek-ai/dsh-web` ## `@deepseek-ai/dsh-web`
```ts config-catalog ```ts config-catalog
@@ -2660,6 +2864,8 @@ export interface WebRuntimeConfig {
Source: [`packages/web/web/src/index.ts:55`](../packages/web/web/src/index.ts) Source: [`packages/web/web/src/index.ts:55`](../packages/web/web/src/index.ts)
<a id="deepseek-aidsh-web-app"></a>
## `@deepseek-ai/dsh-web-app` ## `@deepseek-ai/dsh-web-app`
Requires: `webServer` Requires: `webServer`
@@ -2683,6 +2889,8 @@ export interface Config {
Source: [`packages/bundle/web-app/src/index.ts:38`](../packages/bundle/web-app/src/index.ts) Source: [`packages/bundle/web-app/src/index.ts:38`](../packages/bundle/web-app/src/index.ts)
<a id="deepseek-aidsh-web-fetch-http"></a>
## `@deepseek-ai/dsh-web-fetch-http` ## `@deepseek-ai/dsh-web-fetch-http`
Requires: `web` Requires: `web`
@@ -2707,6 +2915,8 @@ export interface Config {
Source: [`packages/web/web-fetch-http/src/index.ts:34`](../packages/web/web-fetch-http/src/index.ts) Source: [`packages/web/web-fetch-http/src/index.ts:34`](../packages/web/web-fetch-http/src/index.ts)
<a id="deepseek-aidsh-web-search-deepseek"></a>
## `@deepseek-ai/dsh-web-search-deepseek` ## `@deepseek-ai/dsh-web-search-deepseek`
Requires: `web` Requires: `web`
@@ -2733,6 +2943,8 @@ export interface Config {
Source: [`packages/web/web-search-deepseek/src/index.ts:46`](../packages/web/web-search-deepseek/src/index.ts) Source: [`packages/web/web-search-deepseek/src/index.ts:46`](../packages/web/web-search-deepseek/src/index.ts)
<a id="deepseek-aidsh-web-search-exa"></a>
## `@deepseek-ai/dsh-web-search-exa` ## `@deepseek-ai/dsh-web-search-exa`
Requires: `web` Requires: `web`
@@ -2755,6 +2967,8 @@ export interface Config {
Source: [`packages/web/web-search-exa/src/index.ts:38`](../packages/web/web-search-exa/src/index.ts) Source: [`packages/web/web-search-exa/src/index.ts:38`](../packages/web/web-search-exa/src/index.ts)
<a id="deepseek-aidsh-web-search-perplexity"></a>
## `@deepseek-ai/dsh-web-search-perplexity` ## `@deepseek-ai/dsh-web-search-perplexity`
Requires: `web` Requires: `web`
@@ -2777,6 +2991,8 @@ export interface Config {
Source: [`packages/web/web-search-perplexity/src/index.ts:32`](../packages/web/web-search-perplexity/src/index.ts) Source: [`packages/web/web-search-perplexity/src/index.ts:32`](../packages/web/web-search-perplexity/src/index.ts)
<a id="deepseek-aidsh-workflow-worker-thread"></a>
## `@deepseek-ai/dsh-workflow-worker-thread` ## `@deepseek-ai/dsh-workflow-worker-thread`
Requires: `subagents` Requires: `subagents`
+216
View File
@@ -11,6 +11,8 @@
`Requires:` 行列出插件通过 `inject` 注入的服务键:其 `cordis.yml` 树还必须加载这些服务的提供者。范围限定为 harness 层级(`packages/`);配置树还可能加载的 vendored cordis 插件(`hmr`、控制台日志记录器等)固定为上游源代码(参见 [vendoring policy](../vendor/README.md)),未收录于此目录。 `Requires:` 行列出插件通过 `inject` 注入的服务键:其 `cordis.yml` 树还必须加载这些服务的提供者。范围限定为 harness 层级(`packages/`);配置树还可能加载的 vendored cordis 插件(`hmr`、控制台日志记录器等)固定为上游源代码(参见 [vendoring policy](../vendor/README.md)),未收录于此目录。
<a id="deepseek-aidsh-acp"></a>
## `@deepseek-ai/dsh-acp` ## `@deepseek-ai/dsh-acp`
需要:`agents` 需要:`agents`
@@ -31,6 +33,8 @@ export interface AcpConfig {
来源:[`packages/acp/acp/src/index.ts:70`](../packages/acp/acp/src/index.ts) 来源:[`packages/acp/acp/src/index.ts:70`](../packages/acp/acp/src/index.ts)
<a id="deepseek-aidsh-acp-demo"></a>
## `@deepseek-ai/dsh-acp-demo` ## `@deepseek-ai/dsh-acp-demo`
```ts config-catalog ```ts config-catalog
@@ -84,6 +88,8 @@ export interface Config {
来源:[`packages/examples/acp-demo/src/index.ts:39`](../packages/examples/acp-demo/src/index.ts) 来源:[`packages/examples/acp-demo/src/index.ts:39`](../packages/examples/acp-demo/src/index.ts)
<a id="deepseek-aidsh-agent-default-model"></a>
## `@deepseek-ai/dsh-agent-default-model` ## `@deepseek-ai/dsh-agent-default-model`
```ts config-catalog ```ts config-catalog
@@ -98,6 +104,8 @@ export interface Config {
来源:[`packages/core/agent-default-model/src/index.ts:41`](../packages/core/agent-default-model/src/index.ts) 来源:[`packages/core/agent-default-model/src/index.ts:41`](../packages/core/agent-default-model/src/index.ts)
<a id="deepseek-aidsh-agent-instructions"></a>
## `@deepseek-ai/dsh-agent-instructions` ## `@deepseek-ai/dsh-agent-instructions`
```ts config-catalog ```ts config-catalog
@@ -126,6 +134,8 @@ export interface Config {
来源:[`packages/context/agent-instructions/src/config.ts:18`](../packages/context/agent-instructions/src/config.ts) 来源:[`packages/context/agent-instructions/src/config.ts:18`](../packages/context/agent-instructions/src/config.ts)
<a id="deepseek-aidsh-agent-loop"></a>
## `@deepseek-ai/dsh-agent-loop` ## `@deepseek-ai/dsh-agent-loop`
需要:`agents` · `sessions` · `llm` · `tools` · `systemPrompt` 需要:`agents` · `sessions` · `llm` · `tools` · `systemPrompt`
@@ -156,6 +166,8 @@ export interface Config {
来源:[`packages/core/agent-loop/src/index.ts:255`](../packages/core/agent-loop/src/index.ts) 来源:[`packages/core/agent-loop/src/index.ts:255`](../packages/core/agent-loop/src/index.ts)
<a id="deepseek-aidsh-agent-presets"></a>
## `@deepseek-ai/dsh-agent-presets` ## `@deepseek-ai/dsh-agent-presets`
需要:`loader` 需要:`loader`
@@ -192,6 +204,8 @@ export type PresetTrust = 'system' | 'user'
来源:[`packages/preset/agent-presets/src/preset.ts:52`](../packages/preset/agent-presets/src/preset.ts) 来源:[`packages/preset/agent-presets/src/preset.ts:52`](../packages/preset/agent-presets/src/preset.ts)
<a id="deepseek-aidsh-agent-spine-demo"></a>
## `@deepseek-ai/dsh-agent-spine-demo` ## `@deepseek-ai/dsh-agent-spine-demo`
```ts config-catalog ```ts config-catalog
@@ -282,6 +296,8 @@ export interface GoalConfig {
来源:[`packages/examples/agent-spine-demo/src/index.ts:92`](../packages/examples/agent-spine-demo/src/index.ts) 来源:[`packages/examples/agent-spine-demo/src/index.ts:92`](../packages/examples/agent-spine-demo/src/index.ts)
<a id="deepseek-aidsh-agent-tool-presentation"></a>
## `@deepseek-ai/dsh-agent-tool-presentation` ## `@deepseek-ai/dsh-agent-tool-presentation`
需要:`tools` 需要:`tools`
@@ -304,6 +320,8 @@ export interface Config {
来源:[`packages/core/agent-tool-presentation/src/index.ts:38`](../packages/core/agent-tool-presentation/src/index.ts) 来源:[`packages/core/agent-tool-presentation/src/index.ts:38`](../packages/core/agent-tool-presentation/src/index.ts)
<a id="deepseek-aidsh-attachment-local"></a>
## `@deepseek-ai/dsh-attachment-local` ## `@deepseek-ai/dsh-attachment-local`
```ts config-catalog ```ts config-catalog
@@ -324,6 +342,8 @@ export interface Config {
来源:[`packages/attachment/attachment-local/src/index.ts:24`](../packages/attachment/attachment-local/src/index.ts) 来源:[`packages/attachment/attachment-local/src/index.ts:24`](../packages/attachment/attachment-local/src/index.ts)
<a id="deepseek-aidsh-bash-local"></a>
## `@deepseek-ai/dsh-bash-local` ## `@deepseek-ai/dsh-bash-local`
需要:`subprocess` 需要:`subprocess`
@@ -348,6 +368,8 @@ export interface Config {
来源:[`packages/shell/bash-local/src/index.ts:41`](../packages/shell/bash-local/src/index.ts) 来源:[`packages/shell/bash-local/src/index.ts:41`](../packages/shell/bash-local/src/index.ts)
<a id="deepseek-aidsh-bash-sandbox"></a>
## `@deepseek-ai/dsh-bash-sandbox` ## `@deepseek-ai/dsh-bash-sandbox`
需要:`subprocess` · `sandbox` · `sandboxPolicy` 需要:`subprocess` · `sandbox` · `sandboxPolicy`
@@ -367,6 +389,8 @@ export type Config = LocalConfig
来源:[`packages/shell/bash-sandbox/src/index.ts:35`](../packages/shell/bash-sandbox/src/index.ts) 来源:[`packages/shell/bash-sandbox/src/index.ts:35`](../packages/shell/bash-sandbox/src/index.ts)
<a id="deepseek-aidsh-client-connection"></a>
## `@deepseek-ai/dsh-client-connection` ## `@deepseek-ai/dsh-client-connection`
需要:`webServer` 需要:`webServer`
@@ -390,6 +414,8 @@ export interface ConnectionConfig {
来源:[`packages/client/connection/src/index.ts:50`](../packages/client/connection/src/index.ts) 来源:[`packages/client/connection/src/index.ts:50`](../packages/client/connection/src/index.ts)
<a id="deepseek-aidsh-client-hmr"></a>
## `@deepseek-ai/dsh-client-hmr` ## `@deepseek-ai/dsh-client-hmr`
需要:`clientModuleHost` · `webServer` 需要:`clientModuleHost` · `webServer`
@@ -404,6 +430,8 @@ export interface Config {
来源:[`packages/client/hmr/src/index.ts:31`](../packages/client/hmr/src/index.ts) 来源:[`packages/client/hmr/src/index.ts:31`](../packages/client/hmr/src/index.ts)
<a id="deepseek-aidsh-code-runtime-worker-thread"></a>
## `@deepseek-ai/dsh-code-runtime-worker-thread` ## `@deepseek-ai/dsh-code-runtime-worker-thread`
```ts config-catalog ```ts config-catalog
@@ -439,6 +467,8 @@ export interface Config {
来源:[`packages/code-runtime/code-runtime-worker-thread/src/index.ts:25`](../packages/code-runtime/code-runtime-worker-thread/src/index.ts) 来源:[`packages/code-runtime/code-runtime-worker-thread/src/index.ts:25`](../packages/code-runtime/code-runtime-worker-thread/src/index.ts)
<a id="deepseek-aidsh-compaction-basic"></a>
## `@deepseek-ai/dsh-compaction-basic` ## `@deepseek-ai/dsh-compaction-basic`
需要:`llm` · `tokenMeter` · `sessions` 需要:`llm` · `tokenMeter` · `sessions`
@@ -483,6 +513,8 @@ export interface ModelCompactPolicyConfig extends CompactionPolicyConfig {
来源:[`packages/compaction/compaction-basic/src/types.ts:38`](../packages/compaction/compaction-basic/src/types.ts) 来源:[`packages/compaction/compaction-basic/src/types.ts:38`](../packages/compaction/compaction-basic/src/types.ts)
<a id="deepseek-aidsh-compaction-tool-result-pruner"></a>
## `@deepseek-ai/dsh-compaction-tool-result-pruner` ## `@deepseek-ai/dsh-compaction-tool-result-pruner`
需要:`tokenMeter` 需要:`tokenMeter`
@@ -501,6 +533,8 @@ export interface ToolResultPruneConfig {
来源:[`packages/compaction/compaction-tool-result-pruner/src/types.ts:4`](../packages/compaction/compaction-tool-result-pruner/src/types.ts) 来源:[`packages/compaction/compaction-tool-result-pruner/src/types.ts:4`](../packages/compaction/compaction-tool-result-pruner/src/types.ts)
<a id="deepseek-aidsh-cordis-host-runner"></a>
## `@deepseek-ai/dsh-cordis-host-runner` ## `@deepseek-ai/dsh-cordis-host-runner`
需要:`tools` 需要:`tools`
@@ -515,6 +549,8 @@ export interface Config {
来源:[`packages/extensions/cordis-host-runner/src/index.ts:88`](../packages/extensions/cordis-host-runner/src/index.ts) 来源:[`packages/extensions/cordis-host-runner/src/index.ts:88`](../packages/extensions/cordis-host-runner/src/index.ts)
<a id="deepseek-aidsh-credentials-local"></a>
## `@deepseek-ai/dsh-credentials-local` ## `@deepseek-ai/dsh-credentials-local`
```ts config-catalog ```ts config-catalog
@@ -533,6 +569,8 @@ export interface Config {
来源:[`packages/credentials/credentials-local/src/index.ts:55`](../packages/credentials/credentials-local/src/index.ts) 来源:[`packages/credentials/credentials-local/src/index.ts:55`](../packages/credentials/credentials-local/src/index.ts)
<a id="deepseek-aidsh-e2b"></a>
## `@deepseek-ai/dsh-e2b` ## `@deepseek-ai/dsh-e2b`
```ts config-catalog ```ts config-catalog
@@ -549,6 +587,8 @@ export interface Config {
来源:[`packages/e2b/e2b/src/index.ts:43`](../packages/e2b/e2b/src/index.ts) 来源:[`packages/e2b/e2b/src/index.ts:43`](../packages/e2b/e2b/src/index.ts)
<a id="deepseek-aidsh-fs-local"></a>
## `@deepseek-ai/dsh-fs-local` ## `@deepseek-ai/dsh-fs-local`
```ts config-catalog ```ts config-catalog
@@ -566,6 +606,8 @@ export interface Config {
来源:[`packages/fs/fs-local/src/index.ts:41`](../packages/fs/fs-local/src/index.ts) 来源:[`packages/fs/fs-local/src/index.ts:41`](../packages/fs/fs-local/src/index.ts)
<a id="deepseek-aidsh-fs-sandbox"></a>
## `@deepseek-ai/dsh-fs-sandbox` ## `@deepseek-ai/dsh-fs-sandbox`
需要:`sandboxPolicy` 需要:`sandboxPolicy`
@@ -584,6 +626,8 @@ export type Config = LocalConfig
来源:[`packages/fs/fs-sandbox/src/index.ts:49`](../packages/fs/fs-sandbox/src/index.ts) 来源:[`packages/fs/fs-sandbox/src/index.ts:49`](../packages/fs/fs-sandbox/src/index.ts)
<a id="deepseek-aidsh-goal"></a>
## `@deepseek-ai/dsh-goal` ## `@deepseek-ai/dsh-goal`
需要:`agents` 需要:`agents`
@@ -598,6 +642,8 @@ export interface Config {
来源:[`packages/goal/goal/src/index.ts:116`](../packages/goal/goal/src/index.ts) 来源:[`packages/goal/goal/src/index.ts:116`](../packages/goal/goal/src/index.ts)
<a id="deepseek-aidsh-headless"></a>
## `@deepseek-ai/dsh-headless` ## `@deepseek-ai/dsh-headless`
需要:`agentDefaultModel` · `agents` · `sessions` 需要:`agentDefaultModel` · `agents` · `sessions`
@@ -612,6 +658,8 @@ export interface Config {
来源:[`packages/bundle/headless/src/index.ts:31`](../packages/bundle/headless/src/index.ts) 来源:[`packages/bundle/headless/src/index.ts:31`](../packages/bundle/headless/src/index.ts)
<a id="deepseek-aidsh-hooks-claude-code"></a>
## `@deepseek-ai/dsh-hooks-claude-code` ## `@deepseek-ai/dsh-hooks-claude-code`
需要:`bash` 需要:`bash`
@@ -648,6 +696,8 @@ export interface Config {
来源:[`packages/hooks/hooks-claude-code/src/index.ts:45`](../packages/hooks/hooks-claude-code/src/index.ts) 来源:[`packages/hooks/hooks-claude-code/src/index.ts:45`](../packages/hooks/hooks-claude-code/src/index.ts)
<a id="deepseek-aidsh-hooks-codex"></a>
## `@deepseek-ai/dsh-hooks-codex` ## `@deepseek-ai/dsh-hooks-codex`
需要:`bash` 需要:`bash`
@@ -673,6 +723,8 @@ export interface Config {
来源:[`packages/hooks/hooks-codex/src/index.ts:44`](../packages/hooks/hooks-codex/src/index.ts) 来源:[`packages/hooks/hooks-codex/src/index.ts:44`](../packages/hooks/hooks-codex/src/index.ts)
<a id="deepseek-aidsh-host-apiproxy"></a>
## `@deepseek-ai/dsh-host-apiproxy` ## `@deepseek-ai/dsh-host-apiproxy`
需要:`agentDefaultModel` · `agents` · `attachments` · `directoryPicker` · `llm` · `sessions` · `subagents` · `sessionQuery` · `tools` · `userInteraction` · `workspace` 需要:`agentDefaultModel` · `agents` · `attachments` · `directoryPicker` · `llm` · `sessions` · `subagents` · `sessionQuery` · `tools` · `userInteraction` · `workspace`
@@ -694,11 +746,19 @@ export interface Config {
* @default 6 * @default 6
*/ */
sessionExportCompressionLevel?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 sessionExportCompressionLevel?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
/**
* Maximum physical size of a cold Session artifact eligible for blankness
* verification. Zero disables probes.
* @default 1024
*/
coldBlankProbeMaxBytes?: number
} }
``` ```
来源:[`packages/host/apiproxy/src/index.ts:41`](../packages/host/apiproxy/src/index.ts) 来源:[`packages/host/apiproxy/src/index.ts:41`](../packages/host/apiproxy/src/index.ts)
<a id="deepseek-aidsh-host-directory-picker-browse"></a>
## `@deepseek-ai/dsh-host-directory-picker-browse` ## `@deepseek-ai/dsh-host-directory-picker-browse`
```ts config-catalog ```ts config-catalog
@@ -711,6 +771,8 @@ export interface Config {
来源:[`packages/host/directory-picker-browse/src/index.ts:181`](../packages/host/directory-picker-browse/src/index.ts) 来源:[`packages/host/directory-picker-browse/src/index.ts:181`](../packages/host/directory-picker-browse/src/index.ts)
<a id="deepseek-aidsh-host-frontend-static"></a>
## `@deepseek-ai/dsh-host-frontend-static` ## `@deepseek-ai/dsh-host-frontend-static`
需要:`webServer` 需要:`webServer`
@@ -725,6 +787,8 @@ export interface Config {
来源:[`packages/host/frontend-static/src/index.ts:28`](../packages/host/frontend-static/src/index.ts) 来源:[`packages/host/frontend-static/src/index.ts:28`](../packages/host/frontend-static/src/index.ts)
<a id="deepseek-aidsh-host-webserver"></a>
## `@deepseek-ai/dsh-host-webserver` ## `@deepseek-ai/dsh-host-webserver`
```ts config-catalog ```ts config-catalog
@@ -739,6 +803,8 @@ export interface Config {
来源:[`packages/host/webserver/src/index.ts:45`](../packages/host/webserver/src/index.ts) 来源:[`packages/host/webserver/src/index.ts:45`](../packages/host/webserver/src/index.ts)
<a id="deepseek-aidsh-invariants"></a>
## `@deepseek-ai/dsh-invariants` ## `@deepseek-ai/dsh-invariants`
```ts config-catalog ```ts config-catalog
@@ -755,6 +821,8 @@ export interface Config {
来源:[`packages/runtime-diagnostics/invariants/src/index.ts:15`](../packages/runtime-diagnostics/invariants/src/index.ts) 来源:[`packages/runtime-diagnostics/invariants/src/index.ts:15`](../packages/runtime-diagnostics/invariants/src/index.ts)
<a id="deepseek-aidsh-jobs-local"></a>
## `@deepseek-ai/dsh-jobs-local` ## `@deepseek-ai/dsh-jobs-local`
```ts config-catalog ```ts config-catalog
@@ -770,6 +838,8 @@ export interface Config {
来源:[`packages/jobs/jobs-local/src/index.ts:31`](../packages/jobs/jobs-local/src/index.ts) 来源:[`packages/jobs/jobs-local/src/index.ts:31`](../packages/jobs/jobs-local/src/index.ts)
<a id="deepseek-aidsh-llm-deepseek"></a>
## `@deepseek-ai/dsh-llm-deepseek` ## `@deepseek-ai/dsh-llm-deepseek`
需要:`llm` 需要:`llm`
@@ -823,6 +893,8 @@ export interface DeepSeekCatalogModel {
来源:[`packages/llm/llm-deepseek/src/index.ts:62`](../packages/llm/llm-deepseek/src/index.ts) 来源:[`packages/llm/llm-deepseek/src/index.ts:62`](../packages/llm/llm-deepseek/src/index.ts)
<a id="deepseek-aidsh-llm-pi-ai"></a>
## `@deepseek-ai/dsh-llm-pi-ai` ## `@deepseek-ai/dsh-llm-pi-ai`
需要:`llm` 需要:`llm`
@@ -1011,6 +1083,8 @@ type WithheldThinkingFormat = 'chat-template' | 'qwen-chat-template'
来源:[`packages/llm/llm-pi-ai/src/config.ts:172`](../packages/llm/llm-pi-ai/src/config.ts) 来源:[`packages/llm/llm-pi-ai/src/config.ts:172`](../packages/llm/llm-pi-ai/src/config.ts)
<a id="deepseek-aidsh-llm-replay"></a>
## `@deepseek-ai/dsh-llm-replay` ## `@deepseek-ai/dsh-llm-replay`
需要:`llm` 需要:`llm`
@@ -1077,6 +1151,8 @@ export interface ReplayModelConfig {
来源:[`packages/test-support/llm-replay/src/index.ts:776`](../packages/test-support/llm-replay/src/index.ts) 来源:[`packages/test-support/llm-replay/src/index.ts:776`](../packages/test-support/llm-replay/src/index.ts)
<a id="deepseek-aidsh-llm-retry"></a>
## `@deepseek-ai/dsh-llm-retry` ## `@deepseek-ai/dsh-llm-retry`
需要:`agents` 需要:`agents`
@@ -1088,6 +1164,8 @@ export type Config = Readonly<Record<string, never>>
来源:[`packages/llm/llm-retry/src/index.ts:24`](../packages/llm/llm-retry/src/index.ts) 来源:[`packages/llm/llm-retry/src/index.ts:24`](../packages/llm/llm-retry/src/index.ts)
<a id="deepseek-aidsh-lsp-stdio"></a>
## `@deepseek-ai/dsh-lsp-stdio` ## `@deepseek-ai/dsh-lsp-stdio`
需要:`fs` · `lsp` · `subprocess` 需要:`fs` · `lsp` · `subprocess`
@@ -1128,6 +1206,8 @@ export interface LspLocalServerConfig {
来源:[`packages/lsp/lsp-stdio/src/index.ts:82`](../packages/lsp/lsp-stdio/src/index.ts) 来源:[`packages/lsp/lsp-stdio/src/index.ts:82`](../packages/lsp/lsp-stdio/src/index.ts)
<a id="deepseek-aidsh-mcp-client"></a>
## `@deepseek-ai/dsh-mcp-client` ## `@deepseek-ai/dsh-mcp-client`
需要:`tools` 需要:`tools`
@@ -1199,6 +1279,8 @@ export interface ReconnectConfig {
来源:[`packages/mcp/mcp-client/src/index.ts:98`](../packages/mcp/mcp-client/src/index.ts) 来源:[`packages/mcp/mcp-client/src/index.ts:98`](../packages/mcp/mcp-client/src/index.ts)
<a id="deepseek-aidsh-message-feedback"></a>
## `@deepseek-ai/dsh-message-feedback` ## `@deepseek-ai/dsh-message-feedback`
需要:`storageDomain` · `sessionPersistence` · `sessions` 需要:`storageDomain` · `sessionPersistence` · `sessions`
@@ -1213,6 +1295,8 @@ export interface Config {
来源:[`packages/feedback/message-feedback/src/index.ts:49`](../packages/feedback/message-feedback/src/index.ts) 来源:[`packages/feedback/message-feedback/src/index.ts:49`](../packages/feedback/message-feedback/src/index.ts)
<a id="deepseek-aidsh-permission-presets"></a>
## `@deepseek-ai/dsh-permission-presets` ## `@deepseek-ai/dsh-permission-presets`
需要:`bash` · `approval` · `sessions` 需要:`bash` · `approval` · `sessions`
@@ -1250,6 +1334,8 @@ export interface PresetSpec {
来源:[`packages/interaction/permission-presets/src/index.ts:140`](../packages/interaction/permission-presets/src/index.ts) 来源:[`packages/interaction/permission-presets/src/index.ts:140`](../packages/interaction/permission-presets/src/index.ts)
<a id="deepseek-aidsh-persona"></a>
## `@deepseek-ai/dsh-persona` ## `@deepseek-ai/dsh-persona`
需要:`systemPrompt` 需要:`systemPrompt`
@@ -1272,6 +1358,8 @@ export interface Config {
来源:[`packages/preset/persona/src/index.ts:34`](../packages/preset/persona/src/index.ts) 来源:[`packages/preset/persona/src/index.ts:34`](../packages/preset/persona/src/index.ts)
<a id="deepseek-aidsh-plan-mode"></a>
## `@deepseek-ai/dsh-plan-mode` ## `@deepseek-ai/dsh-plan-mode`
需要:`tools` · `systemPrompt` 需要:`tools` · `systemPrompt`
@@ -1286,6 +1374,8 @@ export interface PlanModeConfig {
来源:[`packages/plan/plan-mode/src/index.ts:70`](../packages/plan/plan-mode/src/index.ts) 来源:[`packages/plan/plan-mode/src/index.ts:70`](../packages/plan/plan-mode/src/index.ts)
<a id="deepseek-aidsh-pwsh-local"></a>
## `@deepseek-ai/dsh-pwsh-local` ## `@deepseek-ai/dsh-pwsh-local`
需要:`subprocess` 需要:`subprocess`
@@ -1317,6 +1407,8 @@ export interface Config {
来源:[`packages/shell/pwsh-local/src/index.ts:58`](../packages/shell/pwsh-local/src/index.ts) 来源:[`packages/shell/pwsh-local/src/index.ts:58`](../packages/shell/pwsh-local/src/index.ts)
<a id="deepseek-aidsh-pwsh-sandbox"></a>
## `@deepseek-ai/dsh-pwsh-sandbox` ## `@deepseek-ai/dsh-pwsh-sandbox`
需要:`subprocess` · `sandbox` · `sandboxPolicy` 需要:`subprocess` · `sandbox` · `sandboxPolicy`
@@ -1337,6 +1429,8 @@ export type Config = LocalConfig
来源:[`packages/shell/pwsh-sandbox/src/index.ts:40`](../packages/shell/pwsh-sandbox/src/index.ts) 来源:[`packages/shell/pwsh-sandbox/src/index.ts:40`](../packages/shell/pwsh-sandbox/src/index.ts)
<a id="deepseek-aidsh-repeat-tool-reminder"></a>
## `@deepseek-ai/dsh-repeat-tool-reminder` ## `@deepseek-ai/dsh-repeat-tool-reminder`
```ts config-catalog ```ts config-catalog
@@ -1369,6 +1463,8 @@ export interface Config {
来源:[`packages/guard/repeat-tool-reminder/src/index.ts:28`](../packages/guard/repeat-tool-reminder/src/index.ts) 来源:[`packages/guard/repeat-tool-reminder/src/index.ts:28`](../packages/guard/repeat-tool-reminder/src/index.ts)
<a id="deepseek-aidsh-sandbox-local"></a>
## `@deepseek-ai/dsh-sandbox-local` ## `@deepseek-ai/dsh-sandbox-local`
```ts config-catalog ```ts config-catalog
@@ -1399,6 +1495,8 @@ export interface Config {
来源:[`packages/sandbox/sandbox-local/src/index.ts:44`](../packages/sandbox/sandbox-local/src/index.ts) 来源:[`packages/sandbox/sandbox-local/src/index.ts:44`](../packages/sandbox/sandbox-local/src/index.ts)
<a id="deepseek-aidsh-sandbox-policy"></a>
## `@deepseek-ai/dsh-sandbox-policy` ## `@deepseek-ai/dsh-sandbox-policy`
```ts config-catalog ```ts config-catalog
@@ -1424,6 +1522,8 @@ export interface Config {
来源:[`packages/sandbox/sandbox-policy/src/index.ts:67`](../packages/sandbox/sandbox-policy/src/index.ts) 来源:[`packages/sandbox/sandbox-policy/src/index.ts:67`](../packages/sandbox/sandbox-policy/src/index.ts)
<a id="deepseek-aidsh-sdk-jsonrpc-server"></a>
## `@deepseek-ai/dsh-sdk-jsonrpc-server` ## `@deepseek-ai/dsh-sdk-jsonrpc-server`
需要:`agents` 需要:`agents`
@@ -1446,6 +1546,8 @@ export interface JsonRpcConfig {
来源:[`packages/sdk/server/src/index.ts:29`](../packages/sdk/server/src/index.ts) 来源:[`packages/sdk/server/src/index.ts:29`](../packages/sdk/server/src/index.ts)
<a id="deepseek-aidsh-session-persistence-jsonl"></a>
## `@deepseek-ai/dsh-session-persistence-jsonl` ## `@deepseek-ai/dsh-session-persistence-jsonl`
需要:`sessions` 需要:`sessions`
@@ -1483,6 +1585,8 @@ export type JsonlCompression = 'zstd' | 'none'
来源:[`packages/session/session-persistence-jsonl/src/index.ts:60`](../packages/session/session-persistence-jsonl/src/index.ts) 来源:[`packages/session/session-persistence-jsonl/src/index.ts:60`](../packages/session/session-persistence-jsonl/src/index.ts)
<a id="deepseek-aidsh-session-persistence-sqlite"></a>
## `@deepseek-ai/dsh-session-persistence-sqlite` ## `@deepseek-ai/dsh-session-persistence-sqlite`
需要:`sessions` 需要:`sessions`
@@ -1526,6 +1630,8 @@ export type JournalMode = 'wal' | 'delete' | 'truncate' | 'persist'
来源:[`packages/session/session-persistence-sqlite/src/index.ts:70`](../packages/session/session-persistence-sqlite/src/index.ts) 来源:[`packages/session/session-persistence-sqlite/src/index.ts:70`](../packages/session/session-persistence-sqlite/src/index.ts)
<a id="deepseek-aidsh-session-projection-cache"></a>
## `@deepseek-ai/dsh-session-projection-cache` ## `@deepseek-ai/dsh-session-projection-cache`
需要:`storageDomain` · `sessionProjections` · `sessionPersistence` · `sessions` 需要:`storageDomain` · `sessionProjections` · `sessionPersistence` · `sessions`
@@ -1547,6 +1653,8 @@ export interface Config {
来源:[`packages/session/session-projection-cache/src/index.ts:42`](../packages/session/session-projection-cache/src/index.ts) 来源:[`packages/session/session-projection-cache/src/index.ts:42`](../packages/session/session-projection-cache/src/index.ts)
<a id="deepseek-aidsh-session-query-sqlite"></a>
## `@deepseek-ai/dsh-session-query-sqlite` ## `@deepseek-ai/dsh-session-query-sqlite`
需要:`sessions` 需要:`sessions`
@@ -1591,6 +1699,8 @@ export type JournalMode = 'wal' | 'delete' | 'truncate' | 'persist'
来源:[`packages/session-query/session-query-sqlite/src/index.ts:89`](../packages/session-query/session-query-sqlite/src/index.ts) 来源:[`packages/session-query/session-query-sqlite/src/index.ts:89`](../packages/session-query/session-query-sqlite/src/index.ts)
<a id="deepseek-aidsh-session-reference"></a>
## `@deepseek-ai/dsh-session-reference` ## `@deepseek-ai/dsh-session-reference`
需要:`sessionQuery` 需要:`sessionQuery`
@@ -1609,6 +1719,8 @@ export interface Config {
来源:[`packages/context/session-reference/src/config.ts:11`](../packages/context/session-reference/src/config.ts) 来源:[`packages/context/session-reference/src/config.ts:11`](../packages/context/session-reference/src/config.ts)
<a id="deepseek-aidsh-session-telemetry-otel"></a>
## `@deepseek-ai/dsh-session-telemetry-otel` ## `@deepseek-ai/dsh-session-telemetry-otel`
需要:`sessions` 需要:`sessions`
@@ -1653,6 +1765,8 @@ export enum SessionTelemetryMode {
来源:[`packages/session/session-telemetry-otel/src/index.ts:91`](../packages/session/session-telemetry-otel/src/index.ts) 来源:[`packages/session/session-telemetry-otel/src/index.ts:91`](../packages/session/session-telemetry-otel/src/index.ts)
<a id="deepseek-aidsh-session-title"></a>
## `@deepseek-ai/dsh-session-title` ## `@deepseek-ai/dsh-session-title`
需要:`sessions` 需要:`sessions`
@@ -1671,6 +1785,8 @@ export interface Config {
来源:[`packages/session/session-title/src/index.ts:79`](../packages/session/session-title/src/index.ts) 来源:[`packages/session/session-title/src/index.ts:79`](../packages/session/session-title/src/index.ts)
<a id="deepseek-aidsh-session-title-all-prompts-llm"></a>
## `@deepseek-ai/dsh-session-title-all-prompts-llm` ## `@deepseek-ai/dsh-session-title-all-prompts-llm`
需要:`sessionTitle` · `llm` · `sessions` 需要:`sessionTitle` · `llm` · `sessions`
@@ -1684,6 +1800,8 @@ export type Config = SessionTitleLlmConfig
来源:[`packages/session/session-title-all-prompts-llm/src/index.ts:15`](../packages/session/session-title-all-prompts-llm/src/index.ts) 来源:[`packages/session/session-title-all-prompts-llm/src/index.ts:15`](../packages/session/session-title-all-prompts-llm/src/index.ts)
<a id="deepseek-aidsh-session-title-first-prompt-llm"></a>
## `@deepseek-ai/dsh-session-title-first-prompt-llm` ## `@deepseek-ai/dsh-session-title-first-prompt-llm`
需要:`sessionTitle` · `llm` · `sessions` 需要:`sessionTitle` · `llm` · `sessions`
@@ -1697,6 +1815,8 @@ export type Config = SessionTitleLlmConfig
来源:[`packages/session/session-title-first-prompt-llm/src/index.ts:15`](../packages/session/session-title-first-prompt-llm/src/index.ts) 来源:[`packages/session/session-title-first-prompt-llm/src/index.ts:15`](../packages/session/session-title-first-prompt-llm/src/index.ts)
<a id="deepseek-aidsh-settings-file"></a>
## `@deepseek-ai/dsh-settings-file` ## `@deepseek-ai/dsh-settings-file`
```ts config-catalog ```ts config-catalog
@@ -1715,6 +1835,8 @@ export interface Config {
来源:[`packages/settings/settings-file/src/index.ts:21`](../packages/settings/settings-file/src/index.ts) 来源:[`packages/settings/settings-file/src/index.ts:21`](../packages/settings/settings-file/src/index.ts)
<a id="deepseek-aidsh-shell-env"></a>
## `@deepseek-ai/dsh-shell-env` ## `@deepseek-ai/dsh-shell-env`
```ts config-catalog ```ts config-catalog
@@ -1727,6 +1849,8 @@ export interface Config {
来源:[`packages/shell/shell-env/src/index.ts:29`](../packages/shell/shell-env/src/index.ts) 来源:[`packages/shell/shell-env/src/index.ts:29`](../packages/shell/shell-env/src/index.ts)
<a id="deepseek-aidsh-skill"></a>
## `@deepseek-ai/dsh-skill` ## `@deepseek-ai/dsh-skill`
```ts config-catalog ```ts config-catalog
@@ -1739,6 +1863,8 @@ export interface Config {
来源:[`packages/skill/skill/src/index.ts:279`](../packages/skill/skill/src/index.ts) 来源:[`packages/skill/skill/src/index.ts:279`](../packages/skill/skill/src/index.ts)
<a id="deepseek-aidsh-skill-filesystem"></a>
## `@deepseek-ai/dsh-skill-filesystem` ## `@deepseek-ai/dsh-skill-filesystem`
需要:`skills` 需要:`skills`
@@ -1775,6 +1901,8 @@ export interface Config {
来源:[`packages/skill/skill-filesystem/src/index.ts:49`](../packages/skill/skill-filesystem/src/index.ts) 来源:[`packages/skill/skill-filesystem/src/index.ts:49`](../packages/skill/skill-filesystem/src/index.ts)
<a id="deepseek-aidsh-spill-local"></a>
## `@deepseek-ai/dsh-spill-local` ## `@deepseek-ai/dsh-spill-local`
```ts config-catalog ```ts config-catalog
@@ -1791,6 +1919,8 @@ export interface Config {
来源:[`packages/spill/spill-local/src/index.ts:22`](../packages/spill/spill-local/src/index.ts) 来源:[`packages/spill/spill-local/src/index.ts:22`](../packages/spill/spill-local/src/index.ts)
<a id="deepseek-aidsh-spill-policy"></a>
## `@deepseek-ai/dsh-spill-policy` ## `@deepseek-ai/dsh-spill-policy`
需要:`tools` 需要:`tools`
@@ -1809,6 +1939,8 @@ export interface Config {
来源:[`packages/spill/spill-policy/src/index.ts:60`](../packages/spill/spill-policy/src/index.ts) 来源:[`packages/spill/spill-policy/src/index.ts:60`](../packages/spill/spill-policy/src/index.ts)
<a id="deepseek-aidsh-storage-domain"></a>
## `@deepseek-ai/dsh-storage-domain` ## `@deepseek-ai/dsh-storage-domain`
需要:`storage` 需要:`storage`
@@ -1830,6 +1962,8 @@ export interface Config {
来源:[`packages/storage/storage-domain/src/index.ts:52`](../packages/storage/storage-domain/src/index.ts) 来源:[`packages/storage/storage-domain/src/index.ts:52`](../packages/storage/storage-domain/src/index.ts)
<a id="deepseek-aidsh-storage-json"></a>
## `@deepseek-ai/dsh-storage-json` ## `@deepseek-ai/dsh-storage-json`
需要:`storage` 需要:`storage`
@@ -1849,6 +1983,8 @@ export interface Config {
来源:[`packages/storage/storage-json/src/index.ts:27`](../packages/storage/storage-json/src/index.ts) 来源:[`packages/storage/storage-json/src/index.ts:27`](../packages/storage/storage-json/src/index.ts)
<a id="deepseek-aidsh-storage-sqlite"></a>
## `@deepseek-ai/dsh-storage-sqlite` ## `@deepseek-ai/dsh-storage-sqlite`
需要:`storage` 需要:`storage`
@@ -1887,6 +2023,8 @@ export type JournalMode = 'wal' | 'delete' | 'truncate' | 'persist'
来源:[`packages/storage/storage-sqlite/src/index.ts:24`](../packages/storage/storage-sqlite/src/index.ts) 来源:[`packages/storage/storage-sqlite/src/index.ts:24`](../packages/storage/storage-sqlite/src/index.ts)
<a id="deepseek-aidsh-subagent-acp"></a>
## `@deepseek-ai/dsh-subagent-acp` ## `@deepseek-ai/dsh-subagent-acp`
需要:`subagents` · `subprocess` 需要:`subagents` · `subprocess`
@@ -1938,6 +2076,8 @@ export type PermissionPolicy = 'allow' | 'reject'
来源:[`packages/subagent/subagent-acp/src/index.ts:27`](../packages/subagent/subagent-acp/src/index.ts) 来源:[`packages/subagent/subagent-acp/src/index.ts:27`](../packages/subagent/subagent-acp/src/index.ts)
<a id="deepseek-aidsh-subagent-claude-code"></a>
## `@deepseek-ai/dsh-subagent-claude-code` ## `@deepseek-ai/dsh-subagent-claude-code`
需要:`subagents` · `subprocess` 需要:`subagents` · `subprocess`
@@ -1957,6 +2097,8 @@ export interface Config {
来源:[`packages/subagent/subagent-claude-code/src/index.ts:32`](../packages/subagent/subagent-claude-code/src/index.ts) 来源:[`packages/subagent/subagent-claude-code/src/index.ts:32`](../packages/subagent/subagent-claude-code/src/index.ts)
<a id="deepseek-aidsh-subagent-codex"></a>
## `@deepseek-ai/dsh-subagent-codex` ## `@deepseek-ai/dsh-subagent-codex`
需要:`subagents` · `subprocess` 需要:`subagents` · `subprocess`
@@ -1976,6 +2118,8 @@ export interface Config {
来源:[`packages/subagent/subagent-codex/src/index.ts:30`](../packages/subagent/subagent-codex/src/index.ts) 来源:[`packages/subagent/subagent-codex/src/index.ts:30`](../packages/subagent/subagent-codex/src/index.ts)
<a id="deepseek-aidsh-subagent-dsh-sdk"></a>
## `@deepseek-ai/dsh-subagent-dsh-sdk` ## `@deepseek-ai/dsh-subagent-dsh-sdk`
需要:`subagents` 需要:`subagents`
@@ -2027,6 +2171,8 @@ export interface Config {
来源:[`packages/subagent/subagent-dsh-sdk/src/index.ts:29`](../packages/subagent/subagent-dsh-sdk/src/index.ts) 来源:[`packages/subagent/subagent-dsh-sdk/src/index.ts:29`](../packages/subagent/subagent-dsh-sdk/src/index.ts)
<a id="deepseek-aidsh-subagent-fork-in-process"></a>
## `@deepseek-ai/dsh-subagent-fork-in-process` ## `@deepseek-ai/dsh-subagent-fork-in-process`
需要:`subagents` 需要:`subagents`
@@ -2041,6 +2187,8 @@ export interface Config {
来源:[`packages/subagent/subagent-fork-in-process/src/index.ts:31`](../packages/subagent/subagent-fork-in-process/src/index.ts) 来源:[`packages/subagent/subagent-fork-in-process/src/index.ts:31`](../packages/subagent/subagent-fork-in-process/src/index.ts)
<a id="deepseek-aidsh-subagent-spawn-in-process"></a>
## `@deepseek-ai/dsh-subagent-spawn-in-process` ## `@deepseek-ai/dsh-subagent-spawn-in-process`
需要:`subagents` 需要:`subagents`
@@ -2055,6 +2203,8 @@ export interface Config {
来源:[`packages/subagent/subagent-spawn-in-process/src/index.ts:25`](../packages/subagent/subagent-spawn-in-process/src/index.ts) 来源:[`packages/subagent/subagent-spawn-in-process/src/index.ts:25`](../packages/subagent/subagent-spawn-in-process/src/index.ts)
<a id="deepseek-aidsh-subprocess-e2b"></a>
## `@deepseek-ai/dsh-subprocess-e2b` ## `@deepseek-ai/dsh-subprocess-e2b`
需要:`e2b` 需要:`e2b`
@@ -2069,6 +2219,8 @@ export interface Config {
来源:[`packages/e2b/subprocess-e2b/src/index.ts:25`](../packages/e2b/subprocess-e2b/src/index.ts) 来源:[`packages/e2b/subprocess-e2b/src/index.ts:25`](../packages/e2b/subprocess-e2b/src/index.ts)
<a id="deepseek-aidsh-system-prompt"></a>
## `@deepseek-ai/dsh-system-prompt` ## `@deepseek-ai/dsh-system-prompt`
```ts config-catalog ```ts config-catalog
@@ -2094,6 +2246,8 @@ export interface Config {
来源:[`packages/core/system-prompt/src/index.ts:186`](../packages/core/system-prompt/src/index.ts) 来源:[`packages/core/system-prompt/src/index.ts:186`](../packages/core/system-prompt/src/index.ts)
<a id="deepseek-aidsh-terminal-bash"></a>
## `@deepseek-ai/dsh-terminal-bash` ## `@deepseek-ai/dsh-terminal-bash`
需要:`pty` · `sandboxPolicy` · `subprocess` 需要:`pty` · `sandboxPolicy` · `subprocess`
@@ -2137,6 +2291,8 @@ export interface Config {
来源:[`packages/terminal/terminal-bash/src/config.ts:6`](../packages/terminal/terminal-bash/src/config.ts) 来源:[`packages/terminal/terminal-bash/src/config.ts:6`](../packages/terminal/terminal-bash/src/config.ts)
<a id="deepseek-aidsh-time-context"></a>
## `@deepseek-ai/dsh-time-context` ## `@deepseek-ai/dsh-time-context`
需要:`agents` 需要:`agents`
@@ -2153,6 +2309,8 @@ export interface Config {
来源:[`packages/context/time-context/src/index.ts:27`](../packages/context/time-context/src/index.ts) 来源:[`packages/context/time-context/src/index.ts:27`](../packages/context/time-context/src/index.ts)
<a id="deepseek-aidsh-tmux-context"></a>
## `@deepseek-ai/dsh-tmux-context` ## `@deepseek-ai/dsh-tmux-context`
需要:`agents` 需要:`agents`
@@ -2167,6 +2325,8 @@ export interface Config {
来源:[`packages/context/tmux-context/src/index.ts:34`](../packages/context/tmux-context/src/index.ts) 来源:[`packages/context/tmux-context/src/index.ts:34`](../packages/context/tmux-context/src/index.ts)
<a id="deepseek-aidsh-token-meter"></a>
## `@deepseek-ai/dsh-token-meter` ## `@deepseek-ai/dsh-token-meter`
```ts config-catalog ```ts config-catalog
@@ -2176,6 +2336,8 @@ export type TokenMeterConfig = Record<string, never>
来源:[`packages/llm/token-meter/src/types.ts:12`](../packages/llm/token-meter/src/types.ts) 来源:[`packages/llm/token-meter/src/types.ts:12`](../packages/llm/token-meter/src/types.ts)
<a id="deepseek-aidsh-tool-bash"></a>
## `@deepseek-ai/dsh-tool-bash` ## `@deepseek-ai/dsh-tool-bash`
需要:`tools` · `bash` · `systemPrompt` · `bashEnv` 需要:`tools` · `bash` · `systemPrompt` · `bashEnv`
@@ -2190,6 +2352,8 @@ export interface Config {
来源:[`packages/shell/tool-bash/src/index.ts:34`](../packages/shell/tool-bash/src/index.ts) 来源:[`packages/shell/tool-bash/src/index.ts:34`](../packages/shell/tool-bash/src/index.ts)
<a id="deepseek-aidsh-tool-bash-persistent"></a>
## `@deepseek-ai/dsh-tool-bash-persistent` ## `@deepseek-ai/dsh-tool-bash-persistent`
需要:`tools` · `pty` 需要:`tools` · `pty`
@@ -2210,6 +2374,8 @@ export interface Config {
来源:[`packages/shell/tool-bash-persistent/src/index.ts:405`](../packages/shell/tool-bash-persistent/src/index.ts) 来源:[`packages/shell/tool-bash-persistent/src/index.ts:405`](../packages/shell/tool-bash-persistent/src/index.ts)
<a id="deepseek-aidsh-tool-fs"></a>
## `@deepseek-ai/dsh-tool-fs` ## `@deepseek-ai/dsh-tool-fs`
需要:`tools` · `fs` · `systemPrompt` 需要:`tools` · `fs` · `systemPrompt`
@@ -2230,6 +2396,8 @@ export interface Config {
来源:[`packages/fs/tool-fs/src/index.ts:25`](../packages/fs/tool-fs/src/index.ts) 来源:[`packages/fs/tool-fs/src/index.ts:25`](../packages/fs/tool-fs/src/index.ts)
<a id="deepseek-aidsh-tool-fs-search"></a>
## `@deepseek-ai/dsh-tool-fs-search` ## `@deepseek-ai/dsh-tool-fs-search`
需要:`tools` · `systemPrompt` · `subprocess` 需要:`tools` · `systemPrompt` · `subprocess`
@@ -2263,6 +2431,8 @@ export interface Config {
来源:[`packages/fs/tool-fs-search/src/index.ts:73`](../packages/fs/tool-fs-search/src/index.ts) 来源:[`packages/fs/tool-fs-search/src/index.ts:73`](../packages/fs/tool-fs-search/src/index.ts)
<a id="deepseek-aidsh-tool-goal"></a>
## `@deepseek-ai/dsh-tool-goal` ## `@deepseek-ai/dsh-tool-goal`
需要:`agents` · `goals` · `tools` · `systemPrompt` 需要:`agents` · `goals` · `tools` · `systemPrompt`
@@ -2277,6 +2447,8 @@ export interface Config {
来源:[`packages/goal/tool-goal/src/index.ts:26`](../packages/goal/tool-goal/src/index.ts) 来源:[`packages/goal/tool-goal/src/index.ts:26`](../packages/goal/tool-goal/src/index.ts)
<a id="deepseek-aidsh-tool-jobs"></a>
## `@deepseek-ai/dsh-tool-jobs` ## `@deepseek-ai/dsh-tool-jobs`
需要:`tools` · `tasks` · `systemPrompt` 需要:`tools` · `tasks` · `systemPrompt`
@@ -2309,6 +2481,8 @@ export type CompletionDelivery = 'quiet' | 'wakeup'
来源:[`packages/jobs/tool-jobs/src/index.ts:32`](../packages/jobs/tool-jobs/src/index.ts) 来源:[`packages/jobs/tool-jobs/src/index.ts:32`](../packages/jobs/tool-jobs/src/index.ts)
<a id="deepseek-aidsh-tool-lsp"></a>
## `@deepseek-ai/dsh-tool-lsp` ## `@deepseek-ai/dsh-tool-lsp`
需要:`tools` · `lsp` · `systemPrompt` 需要:`tools` · `lsp` · `systemPrompt`
@@ -2327,6 +2501,8 @@ export interface Config {
来源:[`packages/lsp/tool-lsp/src/index.ts:58`](../packages/lsp/tool-lsp/src/index.ts) 来源:[`packages/lsp/tool-lsp/src/index.ts:58`](../packages/lsp/tool-lsp/src/index.ts)
<a id="deepseek-aidsh-tool-pwsh"></a>
## `@deepseek-ai/dsh-tool-pwsh` ## `@deepseek-ai/dsh-tool-pwsh`
需要:`tools` · `bash` · `systemPrompt` · `bashEnv` 需要:`tools` · `bash` · `systemPrompt` · `bashEnv`
@@ -2341,6 +2517,8 @@ export interface Config {
来源:[`packages/shell/tool-pwsh/src/index.ts:52`](../packages/shell/tool-pwsh/src/index.ts) 来源:[`packages/shell/tool-pwsh/src/index.ts:52`](../packages/shell/tool-pwsh/src/index.ts)
<a id="deepseek-aidsh-tool-ralph"></a>
## `@deepseek-ai/dsh-tool-ralph` ## `@deepseek-ai/dsh-tool-ralph`
需要:`tools` · `workflows` · `subagents` · `systemPrompt` 需要:`tools` · `workflows` · `subagents` · `systemPrompt`
@@ -2361,6 +2539,8 @@ export interface Config {
来源:[`packages/workflow/tool-ralph/src/index.ts:23`](../packages/workflow/tool-ralph/src/index.ts) 来源:[`packages/workflow/tool-ralph/src/index.ts:23`](../packages/workflow/tool-ralph/src/index.ts)
<a id="deepseek-aidsh-tool-session-query"></a>
## `@deepseek-ai/dsh-tool-session-query` ## `@deepseek-ai/dsh-tool-session-query`
需要:`tools` · `systemPrompt` · `sessionQuery` 需要:`tools` · `systemPrompt` · `sessionQuery`
@@ -2377,6 +2557,8 @@ export interface Config {
来源:[`packages/session-query/tool-session-query/src/index.ts:29`](../packages/session-query/tool-session-query/src/index.ts) 来源:[`packages/session-query/tool-session-query/src/index.ts:29`](../packages/session-query/tool-session-query/src/index.ts)
<a id="deepseek-aidsh-tool-skill"></a>
## `@deepseek-ai/dsh-tool-skill` ## `@deepseek-ai/dsh-tool-skill`
需要:`agents` · `tools` · `skills` 需要:`agents` · `tools` · `skills`
@@ -2391,6 +2573,8 @@ export interface Config {
来源:[`packages/skill/tool-skill/src/index.ts:61`](../packages/skill/tool-skill/src/index.ts) 来源:[`packages/skill/tool-skill/src/index.ts:61`](../packages/skill/tool-skill/src/index.ts)
<a id="deepseek-aidsh-tool-str-replace-editor"></a>
## `@deepseek-ai/dsh-tool-str-replace-editor` ## `@deepseek-ai/dsh-tool-str-replace-editor`
需要:`tools` · `fs` 需要:`tools` · `fs`
@@ -2407,6 +2591,8 @@ export interface Config {
来源:[`packages/fs/tool-str-replace-editor/src/index.ts:497`](../packages/fs/tool-str-replace-editor/src/index.ts) 来源:[`packages/fs/tool-str-replace-editor/src/index.ts:497`](../packages/fs/tool-str-replace-editor/src/index.ts)
<a id="deepseek-aidsh-tool-subagent"></a>
## `@deepseek-ai/dsh-tool-subagent` ## `@deepseek-ai/dsh-tool-subagent`
需要:`tools` · `subagents` · `systemPrompt` 需要:`tools` · `subagents` · `systemPrompt`
@@ -2470,6 +2656,8 @@ export interface Config {
来源:[`packages/subagent/tool-subagent/src/index.ts:29`](../packages/subagent/tool-subagent/src/index.ts) 来源:[`packages/subagent/tool-subagent/src/index.ts:29`](../packages/subagent/tool-subagent/src/index.ts)
<a id="deepseek-aidsh-tool-subagent-report"></a>
## `@deepseek-ai/dsh-tool-subagent-report` ## `@deepseek-ai/dsh-tool-subagent-report`
需要:`subagents` · `tools` · `systemPrompt` 需要:`subagents` · `tools` · `systemPrompt`
@@ -2490,6 +2678,8 @@ export interface Config {
来源:[`packages/subagent/tool-subagent-report/src/index.ts:27`](../packages/subagent/tool-subagent-report/src/index.ts) 来源:[`packages/subagent/tool-subagent-report/src/index.ts:27`](../packages/subagent/tool-subagent-report/src/index.ts)
<a id="deepseek-aidsh-tool-terminal"></a>
## `@deepseek-ai/dsh-tool-terminal` ## `@deepseek-ai/dsh-tool-terminal`
需要:`pty` · `tools` · `systemPrompt` 需要:`pty` · `tools` · `systemPrompt`
@@ -2506,6 +2696,8 @@ export interface Config {
来源:[`packages/terminal/tool-terminal/src/index.ts:35`](../packages/terminal/tool-terminal/src/index.ts) 来源:[`packages/terminal/tool-terminal/src/index.ts:35`](../packages/terminal/tool-terminal/src/index.ts)
<a id="deepseek-aidsh-tool-todo"></a>
## `@deepseek-ai/dsh-tool-todo` ## `@deepseek-ai/dsh-tool-todo`
需要:`tools` 需要:`tools`
@@ -2526,6 +2718,8 @@ export interface Config {
来源:[`packages/todo/tool-todo/src/index.ts:29`](../packages/todo/tool-todo/src/index.ts) 来源:[`packages/todo/tool-todo/src/index.ts:29`](../packages/todo/tool-todo/src/index.ts)
<a id="deepseek-aidsh-tool-web"></a>
## `@deepseek-ai/dsh-tool-web` ## `@deepseek-ai/dsh-tool-web`
需要:`tools` · `web` · `systemPrompt` 需要:`tools` · `web` · `systemPrompt`
@@ -2550,6 +2744,8 @@ export interface Config {
来源:[`packages/web/tool-web/src/index.ts:37`](../packages/web/tool-web/src/index.ts) 来源:[`packages/web/tool-web/src/index.ts:37`](../packages/web/tool-web/src/index.ts)
<a id="deepseek-aidsh-tool-workflow"></a>
## `@deepseek-ai/dsh-tool-workflow` ## `@deepseek-ai/dsh-tool-workflow`
需要:`tools` · `workflows` · `systemPrompt` 需要:`tools` · `workflows` · `systemPrompt`
@@ -2566,6 +2762,8 @@ export interface Config {
来源:[`packages/workflow/tool-workflow/src/index.ts:33`](../packages/workflow/tool-workflow/src/index.ts) 来源:[`packages/workflow/tool-workflow/src/index.ts:33`](../packages/workflow/tool-workflow/src/index.ts)
<a id="deepseek-aidsh-tools"></a>
## `@deepseek-ai/dsh-tools` ## `@deepseek-ai/dsh-tools`
需要:`systemPrompt` 需要:`systemPrompt`
@@ -2600,6 +2798,8 @@ export type ToolPresentationMode = 'native' | 'code' | 'both'
来源:[`packages/core/tools/src/index.ts:654`](../packages/core/tools/src/index.ts) 来源:[`packages/core/tools/src/index.ts:654`](../packages/core/tools/src/index.ts)
<a id="deepseek-aidsh-typert-loader"></a>
## `@deepseek-ai/dsh-typert-loader` ## `@deepseek-ai/dsh-typert-loader`
需要:`typert` · `loader` 需要:`typert` · `loader`
@@ -2614,6 +2814,8 @@ export interface Config {
来源:[`packages/typert/loader/src/index.ts:47`](../packages/typert/loader/src/index.ts) 来源:[`packages/typert/loader/src/index.ts:47`](../packages/typert/loader/src/index.ts)
<a id="deepseek-aidsh-user-approval"></a>
## `@deepseek-ai/dsh-user-approval` ## `@deepseek-ai/dsh-user-approval`
```ts config-catalog ```ts config-catalog
@@ -2643,6 +2845,8 @@ export type ApprovalPolicy = 'ask' | 'never'
来源:[`packages/interaction/user-approval/src/index.ts:177`](../packages/interaction/user-approval/src/index.ts) 来源:[`packages/interaction/user-approval/src/index.ts:177`](../packages/interaction/user-approval/src/index.ts)
<a id="deepseek-aidsh-web"></a>
## `@deepseek-ai/dsh-web` ## `@deepseek-ai/dsh-web`
```ts config-catalog ```ts config-catalog
@@ -2662,6 +2866,8 @@ export interface WebRuntimeConfig {
来源:[`packages/web/web/src/index.ts:55`](../packages/web/web/src/index.ts) 来源:[`packages/web/web/src/index.ts:55`](../packages/web/web/src/index.ts)
<a id="deepseek-aidsh-web-app"></a>
## `@deepseek-ai/dsh-web-app` ## `@deepseek-ai/dsh-web-app`
需要:`webServer` 需要:`webServer`
@@ -2685,6 +2891,8 @@ export interface Config {
来源:[`packages/bundle/web-app/src/index.ts:38`](../packages/bundle/web-app/src/index.ts) 来源:[`packages/bundle/web-app/src/index.ts:38`](../packages/bundle/web-app/src/index.ts)
<a id="deepseek-aidsh-web-fetch-http"></a>
## `@deepseek-ai/dsh-web-fetch-http` ## `@deepseek-ai/dsh-web-fetch-http`
需要:`web` 需要:`web`
@@ -2709,6 +2917,8 @@ export interface Config {
来源:[`packages/web/web-fetch-http/src/index.ts:34`](../packages/web/web-fetch-http/src/index.ts) 来源:[`packages/web/web-fetch-http/src/index.ts:34`](../packages/web/web-fetch-http/src/index.ts)
<a id="deepseek-aidsh-web-search-deepseek"></a>
## `@deepseek-ai/dsh-web-search-deepseek` ## `@deepseek-ai/dsh-web-search-deepseek`
需要:`web` 需要:`web`
@@ -2735,6 +2945,8 @@ export interface Config {
来源:[`packages/web/web-search-deepseek/src/index.ts:46`](../packages/web/web-search-deepseek/src/index.ts) 来源:[`packages/web/web-search-deepseek/src/index.ts:46`](../packages/web/web-search-deepseek/src/index.ts)
<a id="deepseek-aidsh-web-search-exa"></a>
## `@deepseek-ai/dsh-web-search-exa` ## `@deepseek-ai/dsh-web-search-exa`
需要:`web` 需要:`web`
@@ -2757,6 +2969,8 @@ export interface Config {
来源:[`packages/web/web-search-exa/src/index.ts:38`](../packages/web/web-search-exa/src/index.ts) 来源:[`packages/web/web-search-exa/src/index.ts:38`](../packages/web/web-search-exa/src/index.ts)
<a id="deepseek-aidsh-web-search-perplexity"></a>
## `@deepseek-ai/dsh-web-search-perplexity` ## `@deepseek-ai/dsh-web-search-perplexity`
需要:`web` 需要:`web`
@@ -2779,6 +2993,8 @@ export interface Config {
来源:[`packages/web/web-search-perplexity/src/index.ts:32`](../packages/web/web-search-perplexity/src/index.ts) 来源:[`packages/web/web-search-perplexity/src/index.ts:32`](../packages/web/web-search-perplexity/src/index.ts)
<a id="deepseek-aidsh-workflow-worker-thread"></a>
## `@deepseek-ai/dsh-workflow-worker-thread` ## `@deepseek-ai/dsh-workflow-worker-thread`
需要:`subagents` 需要:`subagents`
+1 -1
View File
@@ -3,4 +3,4 @@
# after editing either side, bring the other along and re-record with: # after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/cookbook/adding-a-tool.md # pnpm run verify-translation-pairing --write docs/cookbook/adding-a-tool.md
adding-a-tool.md: 37516521de4d00de964003fd6f877831774fdcd3 adding-a-tool.md: 37516521de4d00de964003fd6f877831774fdcd3
adding-a-tool.zh.md: 9455fa3b8d8b87632724ad4b035f184cb0c17993 adding-a-tool.zh.md: 27a90ce19653333a0a6afd989115e614a1c8f9f4
+2
View File
@@ -54,6 +54,8 @@ export function apply(ctx: Context) {
producer 提供同步的 `cancel`、在资源清理后 settle 且不 reject 的 `done`,以及可选的消费式 `readOutput`(负责有界输出的格式化)。预先中止的调用属于失败,因为此时没有任务,其 id 无法满足成功输出 schema。`ctx.jobs.start()` 发布 id 后,应使用任务自有的取消信号,而不是 `exec.signal`:之后取消外层调用只会停止等待本次调用,不会终止已经发布的工作;该生命周期归 `job_kill`、owner dispose 和服务 teardown 所有。前台工作仍与 `exec.signal` 耦合。流式 producer 的示例和完整约定见[后台任务运行时 Agent Note](../../.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.md)与 `dsh-tool-bash` producer 提供同步的 `cancel`、在资源清理后 settle 且不 reject 的 `done`,以及可选的消费式 `readOutput`(负责有界输出的格式化)。预先中止的调用属于失败,因为此时没有任务,其 id 无法满足成功输出 schema。`ctx.jobs.start()` 发布 id 后,应使用任务自有的取消信号,而不是 `exec.signal`:之后取消外层调用只会停止等待本次调用,不会终止已经发布的工作;该生命周期归 `job_kill`、owner dispose 和服务 teardown 所有。前台工作仍与 `exec.signal` 耦合。流式 producer 的示例和完整约定见[后台任务运行时 Agent Note](../../.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.md)与 `dsh-tool-bash`
<a id="execution-policy-and-observation"></a>
## 执行策略与观测 ## 执行策略与观测
尽量不要把部署策略内建到工具中。使用 `tools/pre-execute` 实现可扩展的允许/拒绝/询问策略(见[权限门禁示例](extension-cookbook.md#a-hook-plugin-permission-gate-example));使用 `ctx.tools.guard()` 设置最终的单调拒绝,后续监听器无法撤销;使用 `tools/execute` 为分发添加截止时间、重试或指标收集;使用 `tools/post-execute` 替换展示内容或返回值、阻止结果,或附加模型可见上下文;使用 `tools/result` 观测不可变的归一化结果而不改变它。替换内容不会阻止程序化访问 `value`;保密策略会屏蔽或替换该值。沙箱实现也可以在工具的执行器实现中运行;[`dsh-tools` README](../../packages/core/tools/README.md#extension-points) 定义每个扩展点的输入、顺序、返回值和失败行为。 尽量不要把部署策略内建到工具中。使用 `tools/pre-execute` 实现可扩展的允许/拒绝/询问策略(见[权限门禁示例](extension-cookbook.md#a-hook-plugin-permission-gate-example));使用 `ctx.tools.guard()` 设置最终的单调拒绝,后续监听器无法撤销;使用 `tools/execute` 为分发添加截止时间、重试或指标收集;使用 `tools/post-execute` 替换展示内容或返回值、阻止结果,或附加模型可见上下文;使用 `tools/result` 观测不可变的归一化结果而不改变它。替换内容不会阻止程序化访问 `value`;保密策略会屏蔽或替换该值。沙箱实现也可以在工具的执行器实现中运行;[`dsh-tools` README](../../packages/core/tools/README.md#extension-points) 定义每个扩展点的输入、顺序、返回值和失败行为。
+1 -1
View File
@@ -3,4 +3,4 @@
# after editing either side, bring the other along and re-record with: # after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/cookbook/extension-cookbook.md # pnpm run verify-translation-pairing --write docs/cookbook/extension-cookbook.md
extension-cookbook.md: 9618a3522c5566636fe3e49f7eca93d1e113d51a extension-cookbook.md: 9618a3522c5566636fe3e49f7eca93d1e113d51a
extension-cookbook.zh.md: 7b82d7d1ff239cc2139ee424613e818669f3e2e8 extension-cookbook.zh.md: 540bc6867016d095ccae0fb0fdc79bc1b1c26290
+2
View File
@@ -8,6 +8,8 @@ harness 扩展的参考模式。代码片段省略了 import 和辅助实现,
工具在 `ctx.tools` 上注册。带注解的 `defineTool` 示例(类型化的 `execute` 参数、结果构造、`run_in_background` 模式)见 [adding-a-tool.md](adding-a-tool.md)——该指南是工具定义的真源。`ctx.tools.register()` 也直接接受原始 JSON Schema `ToolDefinition`MCP 来源的工具就是这样到达的);`defineTool` 是第一方工具使用的类型化辅助函数。 工具在 `ctx.tools` 上注册。带注解的 `defineTool` 示例(类型化的 `execute` 参数、结果构造、`run_in_background` 模式)见 [adding-a-tool.md](adding-a-tool.md)——该指南是工具定义的真源。`ctx.tools.register()` 也直接接受原始 JSON Schema `ToolDefinition`MCP 来源的工具就是这样到达的);`defineTool` 是第一方工具使用的类型化辅助函数。
<a id="a-hook-plugin-permission-gate-example"></a>
## 钩子插件(以权限门禁为例) ## 钩子插件(以权限门禁为例)
这个权限门禁是钩子插件的一个示例。它从 `tools/pre-execute` 门禁返回一个类型化的决策,用于允许或拒绝一次调用;沙箱、权限和 plan-mode 插件都可以使用该扩展点。钩子插件也可以拦截其他扩展点,本身并不等同于权限门禁。「原生钩子」是在拦截点上运行的普通 Cordis 插件,不需要外部协议。 这个权限门禁是钩子插件的一个示例。它从 `tools/pre-execute` 门禁返回一个类型化的决策,用于允许或拒绝一次调用;沙箱、权限和 plan-mode 插件都可以使用该扩展点。钩子插件也可以拦截其他扩展点,本身并不等同于权限门禁。「原生钩子」是在拦截点上运行的普通 Cordis 插件,不需要外部协议。
+1 -1
View File
@@ -3,4 +3,4 @@
# after editing either side, bring the other along and re-record with: # after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/cordis-primer.md # pnpm run verify-translation-pairing --write docs/cordis-primer.md
cordis-primer.md: 2a3afe180623d89b006dfa3e73aba5567c15bbe9 cordis-primer.md: 2a3afe180623d89b006dfa3e73aba5567c15bbe9
cordis-primer.zh.md: bdce14cf9f157959d6419f88c9c69570102b9c0c cordis-primer.zh.md: d4d60f60717ffdc01499fdffadba2808557b285f
+2
View File
@@ -12,6 +12,8 @@ Cordis 是 DeepSeek Harness 底层以 vendor 方式引入的插件框架。本
- **类型化事件用于通信。** 服务通过 TypeScript 声明合并注册事件名,然后以 `emit``waterfall`(瀑布式事件)、`parallel``serial` 方式分发,分别对应监听者观察、包装、并行扇出或按序执行。 - **类型化事件用于通信。** 服务通过 TypeScript 声明合并注册事件名,然后以 `emit``waterfall`(瀑布式事件)、`parallel``serial` 方式分发,分别对应监听者观察、包装、并行扇出或按序执行。
- **注册是可逆的副作用。** 提示词片段、工具 schema、适配器、提供方和监听器通过 `ctx.effect()``ctx.on()` 安装,reload 和 teardown 时会按预期撤销。 - **注册是可逆的副作用。** 提示词片段、工具 schema、适配器、提供方和监听器通过 `ctx.effect()``ctx.on()` 安装,reload 和 teardown 时会按预期撤销。
<a id="dispatch-modes"></a>
## 分发模式 ## 分发模式
每个事件具有以下分发模式之一,且只能通过对应方法分发。 每个事件具有以下分发模式之一,且只能通过对应方法分发。
+2 -2
View File
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority; # side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with: # after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/cordis-tutorial/03-services.md # pnpm run verify-translation-pairing --write docs/cordis-tutorial/03-services.md
03-services.md: 32007284be99ef46b4621089c9b3a80317e77189 03-services.md: ebfc400dbbc701a3c164c7d30c371dec879d7d73
03-services.zh.md: d82be29aa69686b8dc10cc6a45a658683c017cbd 03-services.zh.md: fcfd8be7f7fe654a4f4943cf591b5ab7bfc27fc6
+1 -1
View File
@@ -75,7 +75,7 @@ Swap the two lines in `cordis.yml` and rerun: same output. Try removing `./greet
`inject` is not a one-shot boot check. If a required service disappears while the app runs — its provider was unloaded or hot-replaced — every dependent plugin is unloaded too, and loads again when the service returns. Combined with effects ([chapter 2](02-lifecycle-and-effects.md)), this prevents a running consumer from retaining a reference to an unavailable service: its own registrations are unwound when the dependency disappears. `inject` is not a one-shot boot check. If a required service disappears while the app runs — its provider was unloaded or hot-replaced — every dependent plugin is unloaded too, and loads again when the service returns. Combined with effects ([chapter 2](02-lifecycle-and-effects.md)), this prevents a running consumer from retaining a reference to an unavailable service: its own registrations are unwound when the dependency disappears.
This is also why service replacement works in config: unload the `dsh-bash-local` entry, mount a different `bash` provider, and every plugin injecting `'bash'` cleanly restarts against the new implementation. This is also why service replacement works in config: unload the `dsh-bash-local` entry, mount a different `shell` provider, and every plugin injecting `'shell'` cleanly restarts against the new implementation.
## Optional dependencies ## Optional dependencies
+1 -1
View File
@@ -75,7 +75,7 @@ Hello, world!
`inject` 并非一次性的启动检查。如果应用运行期间所需服务消失,例如提供方被卸载或热替换,每个依赖插件也会随之卸载,并在服务恢复后再次加载。结合 effect([第 2 章](02-lifecycle-and-effects.md)),这能防止运行中的消费方保留对不可用服务的引用:依赖消失时,它自己的注册也会撤销。 `inject` 并非一次性的启动检查。如果应用运行期间所需服务消失,例如提供方被卸载或热替换,每个依赖插件也会随之卸载,并在服务恢复后再次加载。结合 effect([第 2 章](02-lifecycle-and-effects.md)),这能防止运行中的消费方保留对不可用服务的引用:依赖消失时,它自己的注册也会撤销。
这也是配置中可以替换服务的原因:卸载 Cordis 配置项 `dsh-bash-local`,挂载另一个 `bash` 提供方,所有注入 `'bash'` 的插件都会重新启动并使用新实现。 这也是配置中可以替换服务的原因:卸载 Cordis 配置项 `dsh-bash-local`,挂载另一个 `shell` 提供方,所有注入 `'shell'` 的插件都会重新启动并使用新实现。
## 可选依赖 ## 可选依赖
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority; # side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with: # after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/cordis-tutorial/06-composition-and-hmr.md # pnpm run verify-translation-pairing --write docs/cordis-tutorial/06-composition-and-hmr.md
06-composition-and-hmr.md: 87ea26014657ae8c8199e1ebb486556c827d96ca 06-composition-and-hmr.md: 2b53aa28be99851e77a71de76337f2beb4003e8d
06-composition-and-hmr.zh.md: 830f55de7c1be351fe701cb068197543602619a7 06-composition-and-hmr.zh.md: cd4afa1d5465a5442bfd6b771ebcd61c46fe2983
@@ -18,7 +18,7 @@ A config entry accepts metadata beyond `name` and `config`:
`id` gives the entry a stable identity so the loader can tell an edit to an existing entry apart from a removal plus an addition. `disabled: true` unmounts a plugin without deleting its entry — flip it back and the plugin (and everything PENDING on its services) loads again. `id` gives the entry a stable identity so the loader can tell an edit to an existing entry apart from a removal plus an addition. `disabled: true` unmounts a plugin without deleting its entry — flip it back and the plugin (and everything PENDING on its services) loads again.
Groups nest a sub-list of entries that load and unload as one unit, and `isolate` gives a group its own instance of a service name — two groups can each see a differently-configured `bash` without affecting each other. The [Cordis primer](../cordis-primer.md) and the [service isolation example](../user/develop/framework/service.md#service-isolation) cover the details. Groups nest a sub-list of entries that load and unload as one unit, and `isolate` gives a group its own instance of a service name — two groups can each see a differently configured `shell` provider without affecting each other. The [Cordis primer](../cordis-primer.md) and the [service isolation example](../user/develop/framework/service.md#service-isolation) cover the details.
## Hot module replacement ## Hot module replacement
@@ -18,7 +18,7 @@ Cordis 配置项除了 `name` 和 `config`,还接受其他元数据:
`id` 为 Cordis 配置项提供稳定标识,使 loader 能区分修改现有 Cordis 配置项与先删除再添加。`disabled: true` 会卸载插件而不删除其 Cordis 配置项;改回原值后,插件以及所有因依赖其服务而处于 PENDING 的插件都会再次加载。 `id` 为 Cordis 配置项提供稳定标识,使 loader 能区分修改现有 Cordis 配置项与先删除再添加。`disabled: true` 会卸载插件而不删除其 Cordis 配置项;改回原值后,插件以及所有因依赖其服务而处于 PENDING 的插件都会再次加载。
组可以嵌套一份 Cordis 配置项子列表,并将其作为一个单元加载和卸载;`isolate` 则为一个组提供某项服务名称的独立实例,因此两个组可以各自看到配置不同的 `bash`,互不影响。[Cordis 入门](../cordis-primer.md)和[服务隔离示例](../user/develop/framework/service.md#service-isolation)介绍了详细内容。 组可以嵌套一份 Cordis 配置项子列表,并将其作为一个单元加载和卸载;`isolate` 则为一个组提供某项服务名称的独立实例,因此两个组可以各自看到配置不同的 `shell` 提供方,互不影响。[Cordis 入门](../cordis-primer.md)和[服务隔离示例](../user/develop/framework/service.md#service-isolation)介绍了详细内容。
## 热模块替换 ## 热模块替换
+1 -1
View File
@@ -3,4 +3,4 @@
# after editing either side, bring the other along and re-record with: # after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/cordis-tutorial/index.md # pnpm run verify-translation-pairing --write docs/cordis-tutorial/index.md
index.md: c51965e186ce8d78b577c1005c1c3e1831b91be1 index.md: c51965e186ce8d78b577c1005c1c3e1831b91be1
index.zh.md: 8811930eba0f3e24ecbc47521e65582502257473 index.zh.md: 22e1918672a34219ca0cf5efaa43a8eb693e6996
+2
View File
@@ -10,6 +10,8 @@ Cordis 是 DeepSeek Harness 底层的插件框架:它是一个小型运行时
如果你要为 harness 本身编写插件——由 `cordis.yml` 加载、在 Web UI 中驱动,而不是下面这个启动器——请从[第一个 Harness 插件](../user/develop/basic/index.md)开始。 如果你要为 harness 本身编写插件——由 `cordis.yml` 加载、在 Web UI 中驱动,而不是下面这个启动器——请从[第一个 Harness 插件](../user/develop/basic/index.md)开始。
<a id="setup"></a>
## 准备工作 ## 准备工作
你需要克隆本仓库并安装依赖;[开发指南](../development.md#setup-tutorial)列出了前置条件。本教程不需要 API 密钥;所有示例均可在无密钥环境中运行。 你需要克隆本仓库并安装依赖;[开发指南](../development.md#setup-tutorial)列出了前置条件。本教程不需要 API 密钥;所有示例均可在无密钥环境中运行。
+2 -2
View File
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority; # side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with: # after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/defensive-patterns.md # pnpm run verify-translation-pairing --write docs/defensive-patterns.md
defensive-patterns.md: b6e643cad6180ea35a363f2c4131b2c24f5f70af defensive-patterns.md: 9db582354628a13abd43c5bd052dbbfd6e52f79f
defensive-patterns.zh.md: e1a6abdfd2138a51a13ceaf418bf6df1d0c65579 defensive-patterns.zh.md: 7bebbe3c1964f2b826afc523eaa4af7be180e36e
+4
View File
@@ -27,3 +27,7 @@ A user-supplied listener that throws must not reject the promise it runs inside
## Never hand untrusted output the ambient environment or predictable paths ## Never hand untrusted output the ambient environment or predictable paths
Spawned commands get a scrubbed env (drop `*KEY*`/`*SECRET*`/`*TOKEN*`/`*PASSWORD*`) so harness credentials cannot leak into output, `env`, or spill files. Temp/spill files use a private (0700) dir, random names, and exclusive owner-only opens (`'wx'`, `0o600`) — predictable world-readable paths invite symlink races and disclosure. Spawned commands get a scrubbed env (drop `*KEY*`/`*SECRET*`/`*TOKEN*`/`*PASSWORD*`) so harness credentials cannot leak into output, `env`, or spill files. Temp/spill files use a private (0700) dir, random names, and exclusive owner-only opens (`'wx'`, `0o600`) — predictable world-readable paths invite symlink races and disclosure.
## Unlink link-shaped paths
A path that may be a symlink or Windows junction is removed with `lstatSync().isSymbolicLink()` then `unlinkSync`: unlink deletes only the link and refuses a real directory, so it never follows the link into its target. Windows `rmSync(link)` throws `ERR_FS_EISDIR` on a junction; recursive deletion may descend through one into its target. Reserve recursive `rmSync` for known real directories.
+5 -1
View File
@@ -26,4 +26,8 @@
## 绝不将环境变量或可预测路径暴露给不可信输出 ## 绝不将环境变量或可预测路径暴露给不可信输出
启动的命令应使用经过清理的环境变量,移除名称匹配 `*KEY*``*SECRET*``*TOKEN*``*PASSWORD*` 的项,防止 harness 凭证通过命令输出、`env` 或 spill 文件泄漏。临时文件和 spill 文件应放在权限为 0700 的私有目录中,使用随机文件名,并以独占且仅所有者可访问的方式打开(`'wx'``0o600`);可预测且所有用户均可读的路径会引发符号链接竞态和信息泄露。 启动的命令应使用经过清理的环境变量,移除名称匹配 `*KEY*``*SECRET*``*TOKEN*``*PASSWORD*` 的项,防止 harness 凭证通过命令输出、`env` 或 spill 文件泄漏。临时文件和 spill 文件应放在权限为 0700 的私有目录中,使用随机文件名,并以独占且仅所有者可访问的方式打开(`'wx'``0o600`);可预测且全局可读的路径会引发符号链接竞态和信息泄露。
## 用 unlink 删除链接形态的路径
可能是符号链接或 Windows junction 的路径,应先用 `lstatSync().isSymbolicLink()` 判断,再用 `unlinkSync` 删除:unlink 只删除链接本身并拒绝真实目录,因此绝不会跟随链接进入其目标。Windows 上对 junction 调用 `rmSync(link)` 会抛 `ERR_FS_EISDIR`;递归删除可能穿过 junction 进入其目标。真实目录才使用带 `recursive``rmSync`
+2 -2
View File
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority; # side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with: # after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/persistence-catalog.md # pnpm run verify-translation-pairing --write docs/persistence-catalog.md
persistence-catalog.md: 032f7ae45d05b688fd317e9aa46363f658f19b76 persistence-catalog.md: c400298f7d37c590918820bcbda10e6550f197e8
persistence-catalog.zh.md: 4f3ace46f7abb2d87be1cd66cbead0ac6b0cccda persistence-catalog.zh.md: 65ec0e3fbdd226c51a371dc9a90f10db5c929c7a
+88
View File
@@ -96,6 +96,8 @@ Sources: [`packages/core/session/src/types.ts:336`](../packages/core/session/src
### `agent/*` ### `agent/*`
<a id="agentinboxspliced--log-only"></a>
#### `agent/inbox/spliced` — log-only #### `agent/inbox/spliced` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -117,6 +119,8 @@ Source: [`packages/core/agent/src/types.ts:19`](../packages/core/agent/src/types
### `agent-preset/*` ### `agent-preset/*`
<a id="agent-presetselected--log-only"></a>
#### `agent-preset/selected` — log-only #### `agent-preset/selected` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -133,6 +137,8 @@ Source: [`packages/preset/agent-presets/src/session.ts:26`](../packages/preset/a
### `approval/*` ### `approval/*`
<a id="approvalasked--log-only"></a>
#### `approval/asked` — log-only #### `approval/asked` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -156,6 +162,8 @@ Types: [CallId](subsystems/core.md)
Source: [`packages/interaction/user-approval/src/index.ts:44`](../packages/interaction/user-approval/src/index.ts) Source: [`packages/interaction/user-approval/src/index.ts:44`](../packages/interaction/user-approval/src/index.ts)
<a id="approvaldecided--log-only"></a>
#### `approval/decided` — log-only #### `approval/decided` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -172,6 +180,8 @@ Source: [`packages/interaction/user-approval/src/index.ts:44`](../packages/inter
Source: [`packages/interaction/user-approval/src/index.ts:55`](../packages/interaction/user-approval/src/index.ts) Source: [`packages/interaction/user-approval/src/index.ts:55`](../packages/interaction/user-approval/src/index.ts)
<a id="approvalpolicy--log-only"></a>
#### `approval/policy` — log-only #### `approval/policy` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -194,6 +204,8 @@ Source: [`packages/interaction/user-approval/src/index.ts:67`](../packages/inter
### `assistant/*` ### `assistant/*`
<a id="assistantchunk--log-only"></a>
#### `assistant/chunk` — log-only #### `assistant/chunk` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -205,6 +217,8 @@ Types: [StreamChunk](subsystems/llm-streaming.md)
Source: [`packages/core/session/src/types.ts:266`](../packages/core/session/src/types.ts) Source: [`packages/core/session/src/types.ts:266`](../packages/core/session/src/types.ts)
<a id="assistantmessage--surface"></a>
#### `assistant/message` — surface #### `assistant/message` — surface
```ts persistence-catalog ```ts persistence-catalog
@@ -223,6 +237,8 @@ Source: [`packages/core/session/src/types.ts:273`](../packages/core/session/src/
### `command/*` ### `command/*`
<a id="commanddone--log-only"></a>
#### `command/done` — log-only #### `command/done` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -242,6 +258,8 @@ Source: [`packages/core/session/src/types.ts:273`](../packages/core/session/src/
Source: [`packages/interaction/commands/src/types.ts:95`](../packages/interaction/commands/src/types.ts) Source: [`packages/interaction/commands/src/types.ts:95`](../packages/interaction/commands/src/types.ts)
<a id="commandrun--log-only"></a>
#### `command/run` — log-only #### `command/run` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -262,6 +280,8 @@ Source: [`packages/interaction/commands/src/types.ts:88`](../packages/interactio
### `compaction/*` ### `compaction/*`
<a id="compactionend--log-only"></a>
#### `compaction/end` — log-only #### `compaction/end` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -274,6 +294,8 @@ Source: [`packages/interaction/commands/src/types.ts:88`](../packages/interactio
Source: [`packages/compaction/compaction/src/types.ts:71`](../packages/compaction/compaction/src/types.ts) Source: [`packages/compaction/compaction/src/types.ts:71`](../packages/compaction/compaction/src/types.ts)
<a id="compactionprune--log-only"></a>
#### `compaction/prune` — log-only #### `compaction/prune` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -298,6 +320,8 @@ Source: [`packages/compaction/compaction/src/types.ts:71`](../packages/compactio
Source: [`packages/compaction/compaction/src/types.ts:81`](../packages/compaction/compaction/src/types.ts) Source: [`packages/compaction/compaction/src/types.ts:81`](../packages/compaction/compaction/src/types.ts)
<a id="compactionstart--log-only"></a>
#### `compaction/start` — log-only #### `compaction/start` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -311,6 +335,8 @@ Source: [`packages/compaction/compaction/src/types.ts:81`](../packages/compactio
Source: [`packages/compaction/compaction/src/types.ts:23`](../packages/compaction/compaction/src/types.ts) Source: [`packages/compaction/compaction/src/types.ts:23`](../packages/compaction/compaction/src/types.ts)
<a id="compactionsummary--log-only"></a>
#### `compaction/summary` — log-only #### `compaction/summary` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -365,6 +391,8 @@ Source: [`packages/compaction/compaction/src/types.ts:33`](../packages/compactio
### `feedback/*` ### `feedback/*`
<a id="feedbackrecord--log-only"></a>
#### `feedback/record` — log-only #### `feedback/record` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -379,6 +407,8 @@ Source: [`packages/feedback/command-feedback/src/index.ts:62`](../packages/feedb
### `goal/*` ### `goal/*`
<a id="goalchange--log-only"></a>
#### `goal/change` — log-only #### `goal/change` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -392,6 +422,8 @@ Source: [`packages/goal/goal/src/domain.ts:66`](../packages/goal/goal/src/domain
### `hook/*` ### `hook/*`
<a id="hookinvoked--log-only"></a>
#### `hook/invoked` — log-only #### `hook/invoked` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -415,6 +447,8 @@ Source: [`packages/goal/goal/src/domain.ts:66`](../packages/goal/goal/src/domain
Source: [`packages/hooks/hook-protocol/src/types.ts:19`](../packages/hooks/hook-protocol/src/types.ts) Source: [`packages/hooks/hook-protocol/src/types.ts:19`](../packages/hooks/hook-protocol/src/types.ts)
<a id="hookresult--log-only"></a>
#### `hook/result` — log-only #### `hook/result` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -438,6 +472,8 @@ Source: [`packages/hooks/hook-protocol/src/types.ts:31`](../packages/hooks/hook-
### `llm/*` ### `llm/*`
<a id="llmretry--log-only"></a>
#### `llm/retry` — log-only #### `llm/retry` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -447,6 +483,8 @@ Source: [`packages/hooks/hook-protocol/src/types.ts:31`](../packages/hooks/hook-
Source: [`packages/llm/llm-retry/src/types.ts:9`](../packages/llm/llm-retry/src/types.ts) Source: [`packages/llm/llm-retry/src/types.ts:9`](../packages/llm/llm-retry/src/types.ts)
<a id="llmretry-started--log-only"></a>
#### `llm/retry-started` — log-only #### `llm/retry-started` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -458,6 +496,8 @@ Source: [`packages/llm/llm-retry/src/types.ts:11`](../packages/llm/llm-retry/src
### `permission/*` ### `permission/*`
<a id="permissionpreset--log-only"></a>
#### `permission/preset` — log-only #### `permission/preset` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -474,6 +514,8 @@ Source: [`packages/interaction/permission-presets/src/index.ts:50`](../packages/
### `plan/*` ### `plan/*`
<a id="planmode--log-only"></a>
#### `plan/mode` — log-only #### `plan/mode` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -489,6 +531,8 @@ Source: [`packages/plan/plan-mode/src/index.ts:53`](../packages/plan/plan-mode/s
### `request/*` ### `request/*`
<a id="requestcontext--log-only"></a>
#### `request/context` — log-only #### `request/context` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -501,6 +545,8 @@ Source: [`packages/plan/plan-mode/src/index.ts:53`](../packages/plan/plan-mode/s
Source: [`packages/core/session/src/types.ts:309`](../packages/core/session/src/types.ts) Source: [`packages/core/session/src/types.ts:309`](../packages/core/session/src/types.ts)
<a id="requestheader--log-only"></a>
#### `request/header` — log-only #### `request/header` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -515,6 +561,8 @@ Source: [`packages/core/session/src/types.ts:304`](../packages/core/session/src/
### `sandbox/*` ### `sandbox/*`
<a id="sandboxmode--log-only"></a>
#### `sandbox/mode` — log-only #### `sandbox/mode` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -536,6 +584,8 @@ Source: [`packages/sandbox/sandbox-policy/src/session-mode.ts:33`](../packages/s
### `schedule/*` ### `schedule/*`
<a id="schedulechange--log-only"></a>
#### `schedule/change` — log-only #### `schedule/change` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -552,6 +602,8 @@ Source: [`packages/schedule/schedule/src/types.ts:219`](../packages/schedule/sch
### `session/*` ### `session/*`
<a id="sessionend-seed--log-only"></a>
#### `session/end-seed` — log-only #### `session/end-seed` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -582,6 +634,8 @@ Source: [`packages/schedule/schedule/src/types.ts:219`](../packages/schedule/sch
Source: [`packages/core/session/src/types.ts:332`](../packages/core/session/src/types.ts) Source: [`packages/core/session/src/types.ts:332`](../packages/core/session/src/types.ts)
<a id="sessiontitle--log-only"></a>
#### `session/title` — log-only #### `session/title` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -596,6 +650,8 @@ Types: [SessionTitleEventData](subsystems/session-title.md)
Source: [`packages/session/session-title/src/index.ts:100`](../packages/session/session-title/src/index.ts) Source: [`packages/session/session-title/src/index.ts:100`](../packages/session/session-title/src/index.ts)
<a id="sessiontitle-llm-request--log-only"></a>
#### `session/title-llm-request` — log-only #### `session/title-llm-request` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -609,6 +665,8 @@ Source: [`packages/session/session-title-llm/src/index.ts:43`](../packages/sessi
### `step/*` ### `step/*`
<a id="stepend--log-only"></a>
#### `step/end` — log-only #### `step/end` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -618,6 +676,8 @@ Source: [`packages/session/session-title-llm/src/index.ts:43`](../packages/sessi
Source: [`packages/core/session/src/types.ts:256`](../packages/core/session/src/types.ts) Source: [`packages/core/session/src/types.ts:256`](../packages/core/session/src/types.ts)
<a id="stepstart--log-only"></a>
#### `step/start` — log-only #### `step/start` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -629,6 +689,8 @@ Source: [`packages/core/session/src/types.ts:254`](../packages/core/session/src/
### `subagent/*` ### `subagent/*`
<a id="subagentdescriptor--log-only"></a>
#### `subagent/descriptor` — log-only #### `subagent/descriptor` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -646,6 +708,8 @@ Source: [`packages/subagent/subagent/src/descriptor.ts:37`](../packages/subagent
### `todo/*` ### `todo/*`
<a id="todowrite--log-only"></a>
#### `todo/write` — log-only #### `todo/write` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -659,6 +723,8 @@ Source: [`packages/core/session/src/types.ts:299`](../packages/core/session/src/
### `tool/*` ### `tool/*`
<a id="toolcall--log-only"></a>
#### `tool/call` — log-only #### `tool/call` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -674,6 +740,8 @@ Types: [CallId](subsystems/core.md)
Source: [`packages/core/session/src/types.ts:279`](../packages/core/session/src/types.ts) Source: [`packages/core/session/src/types.ts:279`](../packages/core/session/src/types.ts)
<a id="toolcode-dispatch--log-only"></a>
#### `tool/code-dispatch` — log-only #### `tool/code-dispatch` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -697,6 +765,8 @@ Source: [`packages/core/session/src/types.ts:279`](../packages/core/session/src/
Source: [`packages/core/tools/src/types.ts:56`](../packages/core/tools/src/types.ts) Source: [`packages/core/tools/src/types.ts:56`](../packages/core/tools/src/types.ts)
<a id="toolcode-dispatch-start--log-only"></a>
#### `tool/code-dispatch-start` — log-only #### `tool/code-dispatch-start` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -718,6 +788,8 @@ Source: [`packages/core/tools/src/types.ts:56`](../packages/core/tools/src/types
Source: [`packages/core/tools/src/types.ts:40`](../packages/core/tools/src/types.ts) Source: [`packages/core/tools/src/types.ts:40`](../packages/core/tools/src/types.ts)
<a id="toolresult--surface"></a>
#### `tool/result` — surface #### `tool/result` — surface
```ts persistence-catalog ```ts persistence-catalog
@@ -745,6 +817,8 @@ Source: [`packages/core/session/src/types.ts:291`](../packages/core/session/src/
### `tool-workflow/*` ### `tool-workflow/*`
<a id="tool-workflowagent-end--log-only"></a>
#### `tool-workflow/agent-end` — log-only #### `tool-workflow/agent-end` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -757,6 +831,8 @@ Source: [`packages/core/session/src/types.ts:291`](../packages/core/session/src/
Source: [`packages/workflow/tool-workflow/src/types.ts:57`](../packages/workflow/tool-workflow/src/types.ts) Source: [`packages/workflow/tool-workflow/src/types.ts:57`](../packages/workflow/tool-workflow/src/types.ts)
<a id="tool-workflowagent-start--log-only"></a>
#### `tool-workflow/agent-start` — log-only #### `tool-workflow/agent-start` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -769,6 +845,8 @@ Source: [`packages/workflow/tool-workflow/src/types.ts:57`](../packages/workflow
Source: [`packages/workflow/tool-workflow/src/types.ts:52`](../packages/workflow/tool-workflow/src/types.ts) Source: [`packages/workflow/tool-workflow/src/types.ts:52`](../packages/workflow/tool-workflow/src/types.ts)
<a id="tool-workflowrun-end--log-only"></a>
#### `tool-workflow/run-end` — log-only #### `tool-workflow/run-end` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -781,6 +859,8 @@ Source: [`packages/workflow/tool-workflow/src/types.ts:52`](../packages/workflow
Source: [`packages/workflow/tool-workflow/src/types.ts:62`](../packages/workflow/tool-workflow/src/types.ts) Source: [`packages/workflow/tool-workflow/src/types.ts:62`](../packages/workflow/tool-workflow/src/types.ts)
<a id="tool-workflowrun-start--log-only"></a>
#### `tool-workflow/run-start` — log-only #### `tool-workflow/run-start` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -795,6 +875,8 @@ Source: [`packages/workflow/tool-workflow/src/types.ts:47`](../packages/workflow
### `turn/*` ### `turn/*`
<a id="turnend--log-only"></a>
#### `turn/end` — log-only #### `turn/end` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -813,6 +895,8 @@ Types: [TurnEndReason](subsystems/session.md)
Source: [`packages/core/session/src/types.ts:252`](../packages/core/session/src/types.ts) Source: [`packages/core/session/src/types.ts:252`](../packages/core/session/src/types.ts)
<a id="turnstart--log-only"></a>
#### `turn/start` — log-only #### `turn/start` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -829,6 +913,8 @@ Source: [`packages/core/session/src/types.ts:243`](../packages/core/session/src/
### `user/*` ### `user/*`
<a id="usermessage--surface"></a>
#### `user/message` — surface #### `user/message` — surface
```ts persistence-catalog ```ts persistence-catalog
@@ -846,6 +932,8 @@ Source: [`packages/core/session/src/types.ts:264`](../packages/core/session/src/
### `web/*` ### `web/*`
<a id="webdeepseek-search-llm-request--log-only"></a>
#### `web/deepseek-search-llm-request` — log-only #### `web/deepseek-search-llm-request` — log-only
```ts persistence-catalog ```ts persistence-catalog
+88
View File
@@ -98,6 +98,8 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
### `agent/*` ### `agent/*`
<a id="agentinboxspliced--log-only"></a>
#### `agent/inbox/spliced` — log-only #### `agent/inbox/spliced` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -119,6 +121,8 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
### `agent-preset/*` ### `agent-preset/*`
<a id="agent-presetselected--log-only"></a>
#### `agent-preset/selected` — log-only #### `agent-preset/selected` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -135,6 +139,8 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
### `approval/*` ### `approval/*`
<a id="approvalasked--log-only"></a>
#### `approval/asked` — log-only #### `approval/asked` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -158,6 +164,8 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
来源:[`packages/interaction/user-approval/src/index.ts:44`](../packages/interaction/user-approval/src/index.ts) 来源:[`packages/interaction/user-approval/src/index.ts:44`](../packages/interaction/user-approval/src/index.ts)
<a id="approvaldecided--log-only"></a>
#### `approval/decided` — log-only #### `approval/decided` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -174,6 +182,8 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
来源:[`packages/interaction/user-approval/src/index.ts:55`](../packages/interaction/user-approval/src/index.ts) 来源:[`packages/interaction/user-approval/src/index.ts:55`](../packages/interaction/user-approval/src/index.ts)
<a id="approvalpolicy--log-only"></a>
#### `approval/policy` — log-only #### `approval/policy` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -196,6 +206,8 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
### `assistant/*` ### `assistant/*`
<a id="assistantchunk--log-only"></a>
#### `assistant/chunk` — log-only #### `assistant/chunk` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -207,6 +219,8 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
来源:[`packages/core/session/src/types.ts:266`](../packages/core/session/src/types.ts) 来源:[`packages/core/session/src/types.ts:266`](../packages/core/session/src/types.ts)
<a id="assistantmessage--surface"></a>
#### `assistant/message` — surface #### `assistant/message` — surface
```ts persistence-catalog ```ts persistence-catalog
@@ -225,6 +239,8 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
### `command/*` ### `command/*`
<a id="commanddone--log-only"></a>
#### `command/done` — log-only #### `command/done` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -244,6 +260,8 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
来源:[`packages/interaction/commands/src/types.ts:95`](../packages/interaction/commands/src/types.ts) 来源:[`packages/interaction/commands/src/types.ts:95`](../packages/interaction/commands/src/types.ts)
<a id="commandrun--log-only"></a>
#### `command/run` — log-only #### `command/run` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -264,6 +282,8 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
### `compaction/*` ### `compaction/*`
<a id="compactionend--log-only"></a>
#### `compaction/end` — log-only #### `compaction/end` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -276,6 +296,8 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
来源:[`packages/compaction/compaction/src/types.ts:71`](../packages/compaction/compaction/src/types.ts) 来源:[`packages/compaction/compaction/src/types.ts:71`](../packages/compaction/compaction/src/types.ts)
<a id="compactionprune--log-only"></a>
#### `compaction/prune` — log-only #### `compaction/prune` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -300,6 +322,8 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
来源:[`packages/compaction/compaction/src/types.ts:81`](../packages/compaction/compaction/src/types.ts) 来源:[`packages/compaction/compaction/src/types.ts:81`](../packages/compaction/compaction/src/types.ts)
<a id="compactionstart--log-only"></a>
#### `compaction/start` — log-only #### `compaction/start` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -313,6 +337,8 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
来源:[`packages/compaction/compaction/src/types.ts:23`](../packages/compaction/compaction/src/types.ts) 来源:[`packages/compaction/compaction/src/types.ts:23`](../packages/compaction/compaction/src/types.ts)
<a id="compactionsummary--log-only"></a>
#### `compaction/summary` — log-only #### `compaction/summary` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -367,6 +393,8 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
### `feedback/*` ### `feedback/*`
<a id="feedbackrecord--log-only"></a>
#### `feedback/record` — log-only #### `feedback/record` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -381,6 +409,8 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
### `goal/*` ### `goal/*`
<a id="goalchange--log-only"></a>
#### `goal/change` — log-only #### `goal/change` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -394,6 +424,8 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
### `hook/*` ### `hook/*`
<a id="hookinvoked--log-only"></a>
#### `hook/invoked` — log-only #### `hook/invoked` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -417,6 +449,8 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
来源:[`packages/hooks/hook-protocol/src/types.ts:19`](../packages/hooks/hook-protocol/src/types.ts) 来源:[`packages/hooks/hook-protocol/src/types.ts:19`](../packages/hooks/hook-protocol/src/types.ts)
<a id="hookresult--log-only"></a>
#### `hook/result` — log-only #### `hook/result` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -440,6 +474,8 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
### `llm/*` ### `llm/*`
<a id="llmretry--log-only"></a>
#### `llm/retry` — log-only #### `llm/retry` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -449,6 +485,8 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
来源:[`packages/llm/llm-retry/src/types.ts:9`](../packages/llm/llm-retry/src/types.ts) 来源:[`packages/llm/llm-retry/src/types.ts:9`](../packages/llm/llm-retry/src/types.ts)
<a id="llmretry-started--log-only"></a>
#### `llm/retry-started` — log-only #### `llm/retry-started` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -460,6 +498,8 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
### `permission/*` ### `permission/*`
<a id="permissionpreset--log-only"></a>
#### `permission/preset` — log-only #### `permission/preset` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -476,6 +516,8 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
### `plan/*` ### `plan/*`
<a id="planmode--log-only"></a>
#### `plan/mode` — log-only #### `plan/mode` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -491,6 +533,8 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
### `request/*` ### `request/*`
<a id="requestcontext--log-only"></a>
#### `request/context` — log-only #### `request/context` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -503,6 +547,8 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
来源:[`packages/core/session/src/types.ts:309`](../packages/core/session/src/types.ts) 来源:[`packages/core/session/src/types.ts:309`](../packages/core/session/src/types.ts)
<a id="requestheader--log-only"></a>
#### `request/header` — log-only #### `request/header` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -517,6 +563,8 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
### `sandbox/*` ### `sandbox/*`
<a id="sandboxmode--log-only"></a>
#### `sandbox/mode` — log-only #### `sandbox/mode` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -538,6 +586,8 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
### `schedule/*` ### `schedule/*`
<a id="schedulechange--log-only"></a>
#### `schedule/change` — log-only #### `schedule/change` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -554,6 +604,8 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
### `session/*` ### `session/*`
<a id="sessionend-seed--log-only"></a>
#### `session/end-seed` — log-only #### `session/end-seed` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -584,6 +636,8 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
来源:[`packages/core/session/src/types.ts:332`](../packages/core/session/src/types.ts) 来源:[`packages/core/session/src/types.ts:332`](../packages/core/session/src/types.ts)
<a id="sessiontitle--log-only"></a>
#### `session/title` — log-only #### `session/title` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -598,6 +652,8 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
来源:[`packages/session/session-title/src/index.ts:100`](../packages/session/session-title/src/index.ts) 来源:[`packages/session/session-title/src/index.ts:100`](../packages/session/session-title/src/index.ts)
<a id="sessiontitle-llm-request--log-only"></a>
#### `session/title-llm-request` — log-only #### `session/title-llm-request` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -611,6 +667,8 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
### `step/*` ### `step/*`
<a id="stepend--log-only"></a>
#### `step/end` — log-only #### `step/end` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -620,6 +678,8 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
来源:[`packages/core/session/src/types.ts:256`](../packages/core/session/src/types.ts) 来源:[`packages/core/session/src/types.ts:256`](../packages/core/session/src/types.ts)
<a id="stepstart--log-only"></a>
#### `step/start` — log-only #### `step/start` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -631,6 +691,8 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
### `subagent/*` ### `subagent/*`
<a id="subagentdescriptor--log-only"></a>
#### `subagent/descriptor` — log-only #### `subagent/descriptor` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -648,6 +710,8 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
### `todo/*` ### `todo/*`
<a id="todowrite--log-only"></a>
#### `todo/write` — log-only #### `todo/write` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -661,6 +725,8 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
### `tool/*` ### `tool/*`
<a id="toolcall--log-only"></a>
#### `tool/call` — log-only #### `tool/call` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -676,6 +742,8 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
来源:[`packages/core/session/src/types.ts:279`](../packages/core/session/src/types.ts) 来源:[`packages/core/session/src/types.ts:279`](../packages/core/session/src/types.ts)
<a id="toolcode-dispatch--log-only"></a>
#### `tool/code-dispatch` — log-only #### `tool/code-dispatch` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -699,6 +767,8 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
来源:[`packages/core/tools/src/types.ts:56`](../packages/core/tools/src/types.ts) 来源:[`packages/core/tools/src/types.ts:56`](../packages/core/tools/src/types.ts)
<a id="toolcode-dispatch-start--log-only"></a>
#### `tool/code-dispatch-start` — log-only #### `tool/code-dispatch-start` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -720,6 +790,8 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
来源:[`packages/core/tools/src/types.ts:40`](../packages/core/tools/src/types.ts) 来源:[`packages/core/tools/src/types.ts:40`](../packages/core/tools/src/types.ts)
<a id="toolresult--surface"></a>
#### `tool/result` — surface #### `tool/result` — surface
```ts persistence-catalog ```ts persistence-catalog
@@ -747,6 +819,8 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
### `tool-workflow/*` ### `tool-workflow/*`
<a id="tool-workflowagent-end--log-only"></a>
#### `tool-workflow/agent-end` — log-only #### `tool-workflow/agent-end` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -759,6 +833,8 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
来源:[`packages/workflow/tool-workflow/src/types.ts:57`](../packages/workflow/tool-workflow/src/types.ts) 来源:[`packages/workflow/tool-workflow/src/types.ts:57`](../packages/workflow/tool-workflow/src/types.ts)
<a id="tool-workflowagent-start--log-only"></a>
#### `tool-workflow/agent-start` — log-only #### `tool-workflow/agent-start` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -771,6 +847,8 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
来源:[`packages/workflow/tool-workflow/src/types.ts:52`](../packages/workflow/tool-workflow/src/types.ts) 来源:[`packages/workflow/tool-workflow/src/types.ts:52`](../packages/workflow/tool-workflow/src/types.ts)
<a id="tool-workflowrun-end--log-only"></a>
#### `tool-workflow/run-end` — log-only #### `tool-workflow/run-end` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -783,6 +861,8 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
来源:[`packages/workflow/tool-workflow/src/types.ts:62`](../packages/workflow/tool-workflow/src/types.ts) 来源:[`packages/workflow/tool-workflow/src/types.ts:62`](../packages/workflow/tool-workflow/src/types.ts)
<a id="tool-workflowrun-start--log-only"></a>
#### `tool-workflow/run-start` — log-only #### `tool-workflow/run-start` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -797,6 +877,8 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
### `turn/*` ### `turn/*`
<a id="turnend--log-only"></a>
#### `turn/end` — log-only #### `turn/end` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -815,6 +897,8 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
来源:[`packages/core/session/src/types.ts:252`](../packages/core/session/src/types.ts) 来源:[`packages/core/session/src/types.ts:252`](../packages/core/session/src/types.ts)
<a id="turnstart--log-only"></a>
#### `turn/start` — log-only #### `turn/start` — log-only
```ts persistence-catalog ```ts persistence-catalog
@@ -831,6 +915,8 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
### `user/*` ### `user/*`
<a id="usermessage--surface"></a>
#### `user/message` — surface #### `user/message` — surface
```ts persistence-catalog ```ts persistence-catalog
@@ -848,6 +934,8 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
### `web/*` ### `web/*`
<a id="webdeepseek-search-llm-request--log-only"></a>
#### `web/deepseek-search-llm-request` — log-only #### `web/deepseek-search-llm-request` — log-only
```ts persistence-catalog ```ts persistence-catalog
+2 -2
View File
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority; # side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with: # after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/subsystems/core.md # pnpm run verify-translation-pairing --write docs/subsystems/core.md
core.md: 6eda80cbd164168c3c4a846c9bc50a8fc12b0c92 core.md: d14ad52e57572d5b0b110a0b16ff734e3499bdf5
core.zh.md: 14c2e78bc3fff959fb0c0040eae0f382985ecdf4 core.zh.md: 9ace28731f2532f571b66f2e7f6a3ea4a2c4e345
+2
View File
@@ -257,6 +257,8 @@ Its full fields, the `defineTool`/`ValueSchemaSpec`/`ParameterSchemaSpec` typed
Two patterns recur across every subsystem and are documented once, here. Two patterns recur across every subsystem and are documented once, here.
<a id="the-map--derived-union-pattern"></a>
### The `…Map → derived-union` pattern ### The `…Map → derived-union` pattern
Almost every extensible sum type in the harness follows one pattern: an interface keyed by a discriminant tag (the `…Map`), from which the union is derived with `keyof`. Plugins add variants by **declaration merging** — no edit to the owning package. Almost every extensible sum type in the harness follows one pattern: an interface keyed by a discriminant tag (the `…Map`), from which the union is derived with `keyof`. Plugins add variants by **declaration merging** — no edit to the owning package.
+2
View File
@@ -263,6 +263,8 @@ type SessionStartSource = 'startup' | 'resume' | 'clear' | 'compact'
两个模式在每个子系统中反复出现,只在此处记录一次。 两个模式在每个子系统中反复出现,只在此处记录一次。
<a id="the-map--derived-union-pattern"></a>
### `…Map → derived-union` 模式 ### `…Map → derived-union` 模式
harness 中几乎所有可扩展的和类型都遵循同一模式:一个以判别标签为键的接口(`…Map`),联合类型由 `keyof` 派生。插件通过**声明合并**添加变体——无需修改拥有该类型的包。 harness 中几乎所有可扩展的和类型都遵循同一模式:一个以判别标签为键的接口(`…Map`),联合类型由 `keyof` 派生。插件通过**声明合并**添加变体——无需修改拥有该类型的包。
+1 -1
View File
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority; # side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with: # after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/subsystems/llm-streaming.md # pnpm run verify-translation-pairing --write docs/subsystems/llm-streaming.md
llm-streaming.md: 0f395245332e735c04997bd1ca82f66fa9286104 llm-streaming.md: 0d3a0d53c875c9d943146ba44b775d81fc9cae01
llm-streaming.zh.md: fbaa47d14d57e7377be4db6ecaa04f11997572a6 llm-streaming.zh.md: fbaa47d14d57e7377be4db6ecaa04f11997572a6
+2
View File
@@ -151,6 +151,8 @@ type ContextFormed =
| { readonly form: 'recall' } | { readonly form: 'recall' }
``` ```
<a id="streamchunk--the-raw-protocol"></a>
## `StreamChunk` — the raw protocol ## `StreamChunk` — the raw protocol
A streaming response interleaves several typed blocks (text, reasoning, multiple tool calls). `index` ties each delta to its block; `block-end` carries the fully-assembled `ContentBlock` so consumers don't have to re-assemble deltas themselves. It is a **closed** discriminated union — a `switch` over `type` ends with `assertNever`, so adding a variant breaks compilation at every consumer that must handle it. A streaming response interleaves several typed blocks (text, reasoning, multiple tool calls). `index` ties each delta to its block; `block-end` carries the fully-assembled `ContentBlock` so consumers don't have to re-assemble deltas themselves. It is a **closed** discriminated union — a `switch` over `type` ends with `assertNever`, so adding a variant breaks compilation at every consumer that must handle it.
+2 -2
View File
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority; # side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with: # after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/subsystems/persistence.md # pnpm run verify-translation-pairing --write docs/subsystems/persistence.md
persistence.md: fde8348d64a200eda5133abf66deedee6be09857 persistence.md: 5b1b224e419aca205baba69894ed64467b8fb4e1
persistence.zh.md: a2a836d81d153a2697c1d739345501e7e6770eef persistence.zh.md: a91e7d66b92270c82287d054e619b665e96ea206
+2
View File
@@ -36,6 +36,8 @@ interface SessionLocation {
} }
``` ```
<a id="sessionheader--metadata-beside-the-log"></a>
## `SessionHeader` — metadata beside the log ## `SessionHeader` — metadata beside the log
Per-session metadata travels **separately** from the event log: format version, cwd, lineage, and the seed boundary are storage concerns, not conversation events, so they stay out of `SessionEventMap` and never reach `deriveMessages()`. The header is attached to a `Session` via `session.header`. Per-session metadata travels **separately** from the event log: format version, cwd, lineage, and the seed boundary are storage concerns, not conversation events, so they stay out of `SessionEventMap` and never reach `deriveMessages()`. The header is attached to a `Session` via `session.header`.
+2
View File
@@ -36,6 +36,8 @@ interface SessionLocation {
} }
``` ```
<a id="sessionheader--metadata-beside-the-log"></a>
## `SessionHeader`:日志旁的元数据 ## `SessionHeader`:日志旁的元数据
每个会话的元数据与事件日志**分开**存储:格式版本、cwd、血统与 seed 边界是存储层关注点而非对话事件,因此不进入 `SessionEventMap`,也不会到达 `deriveMessages()`。header 通过 `session.header` 附加到 `Session` 上。 每个会话的元数据与事件日志**分开**存储:格式版本、cwd、血统与 seed 边界是存储层关注点而非对话事件,因此不进入 `SessionEventMap`,也不会到达 `deriveMessages()`。header 通过 `session.header` 附加到 `Session` 上。
+2 -2
View File
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority; # side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with: # after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/subsystems/session.md # pnpm run verify-translation-pairing --write docs/subsystems/session.md
session.md: 4c40971fe58952b32635aa5eb767a42f73108b00 session.md: aea9d00b38e384e7a973ce168c3a75a62e70a8bb
session.zh.md: d958b03fcdad91b58cc277636e599cce46a2c7fb session.zh.md: 8c56029af5144569f1ab6df73a8fe2278f9ef5b4
+3 -1
View File
@@ -149,6 +149,8 @@ interface TodoItem {
} }
``` ```
<a id="the-request-header-event-requestheader"></a>
### The request header event: `request/header` ### The request header event: `request/header`
The request envelope — the `EpochHeader` (call config + markers for adapter-supplied defaults + rendered system prompt + assembled tool schemas) — is logged session state, so every conversation request is a pure function of the log (the reconstructability Agent Note). A full `request/header` snapshot with reason `'initial'` or `'resume'` records each loop-instance boundary; a later changed request records another full snapshot with reason `'change'`. `foldRequestHeader(events)` reconstructs the header by selecting the latest snapshot. The event is not a `SurfaceEventType`: it produces no LLM message. The request envelope — the `EpochHeader` (call config + markers for adapter-supplied defaults + rendered system prompt + assembled tool schemas) — is logged session state, so every conversation request is a pure function of the log (the reconstructability Agent Note). A full `request/header` snapshot with reason `'initial'` or `'resume'` records each loop-instance boundary; a later changed request records another full snapshot with reason `'change'`. `foldRequestHeader(events)` reconstructs the header by selecting the latest snapshot. The event is not a `SurfaceEventType`: it produces no LLM message.
@@ -586,7 +588,7 @@ An explicitly supplied empty seed writes `session/end-seed` at seq 0, which dist
It exists because seed history and live work are otherwise byte-identical, which defeats any plugin owning a standalone open/close bracket: an unmatched `compaction/start` reads the same whether the writer crashed mid-compaction or is compacting right now. An opening marker before `session/end-seed` came from the constructor seed and belongs to an ended lifecycle, whatever ended it (a crash, a succeeding process, or a fork out of a still-running parent), so its owner may treat it as dead. That covers only brackets *this* session inherited: a concurrently live session holding an open bracket over the same history has its own boundary elsewhere, so tolerating concurrent writers needs a liveness signal beyond the log. Core writes the boundary and reads nothing from it — a bracket's vocabulary stays with its owning plugin, which is why crash repair closes turn/step/tool boundaries and never `compaction/*`. It exists because seed history and live work are otherwise byte-identical, which defeats any plugin owning a standalone open/close bracket: an unmatched `compaction/start` reads the same whether the writer crashed mid-compaction or is compacting right now. An opening marker before `session/end-seed` came from the constructor seed and belongs to an ended lifecycle, whatever ended it (a crash, a succeeding process, or a fork out of a still-running parent), so its owner may treat it as dead. That covers only brackets *this* session inherited: a concurrently live session holding an open bracket over the same history has its own boundary elsewhere, so tolerating concurrent writers needs a liveness signal beyond the log. Core writes the boundary and reads nothing from it — a bracket's vocabulary stays with its owning plugin, which is why crash repair closes turn/step/tool boundaries and never `compaction/*`.
Activity ordering excludes the boundary through `lastActivityTime(events)`: picking a session up is not work, and lazy resume means browsing writes one, so a resume picker or session list ordering by log tail would float every opened session to the top. Consumers that order Sessions by human activity exclude this boundary: picking a Session up is not work, so ordering by the log tail would float every opened Session to the top.
## Plugin-contributed log-only events ## Plugin-contributed log-only events
+1 -1
View File
@@ -590,7 +590,7 @@ interface TurnEndReasonMap {
它之所以必要,是因为种子历史与实时工作在字节层面完全相同,这会让任何拥有独立开/闭括号的插件失效:一个未配对的 `compaction/start`,无论写入方是在压缩中途崩溃、还是此刻正在压缩,读起来都一样。在 `session/end-seed` 之前的开启标记来自构造种子,并且属于一个已结束的生命周期,无论结束原因为何(崩溃、进程接替,或从仍在运行的父会话 fork 出来),因此其所有方可以视之为已死。这只覆盖*本*会话继承的括号:另一个并发存活的会话可能在同一段历史上持有开放括号,而它自己的边界在别处,因此容忍并发写入方还需要日志之外的存活信号。核心写入该边界但不从中读取任何内容——括号的词汇表仍归其所属插件,这也正是崩溃修复只关闭轮次/步骤/工具边界而从不处理 `compaction/*` 的原因。 它之所以必要,是因为种子历史与实时工作在字节层面完全相同,这会让任何拥有独立开/闭括号的插件失效:一个未配对的 `compaction/start`,无论写入方是在压缩中途崩溃、还是此刻正在压缩,读起来都一样。在 `session/end-seed` 之前的开启标记来自构造种子,并且属于一个已结束的生命周期,无论结束原因为何(崩溃、进程接替,或从仍在运行的父会话 fork 出来),因此其所有方可以视之为已死。这只覆盖*本*会话继承的括号:另一个并发存活的会话可能在同一段历史上持有开放括号,而它自己的边界在别处,因此容忍并发写入方还需要日志之外的存活信号。核心写入该边界但不从中读取任何内容——括号的词汇表仍归其所属插件,这也正是崩溃修复只关闭轮次/步骤/工具边界而从不处理 `compaction/*` 的原因。
活动排序通过 `lastActivityTime(events)` 排除该边界:接手会话不算工作,而惰性恢复意味着浏览就会写入一个,因此按日志尾部排序的恢复选择器或会话列表会把每个打开过的会话顶到最前。 按真人活动排序 Session 的消费方会排除该边界:接手 Session 不算工作,因此按日志尾部排序会把每个打开过的 Session 顶到最前。
## 插件贡献的仅日志事件 ## 插件贡献的仅日志事件
+2 -2
View File
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority; # side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with: # after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/tool-catalog.md # pnpm run verify-translation-pairing --write docs/tool-catalog.md
tool-catalog.md: 3d73ed1ef5346f620808e9d6b04299291ba1ed4c tool-catalog.md: 50563c97c6cd5496871ea7fa52c4823a56b088fd
tool-catalog.zh.md: 90573928a24630e802d98441bc0e340baf582b23 tool-catalog.zh.md: ed0c7e3f70cffbecd3d20a1556bcb0cd4204df00
+48
View File
@@ -40,6 +40,8 @@ This table connects model-visible tool names to the plugin package and service s
| `@deepseek-ai/dsh-tool-workflow` | `workflow` | `ctx.tools`, `ctx.workflowEngine`, `ctx.systemPrompt`, `a calling Agent (exec.agent parents the script children)` | `tool/call`, `tool/result` | - | - | | `@deepseek-ai/dsh-tool-workflow` | `workflow` | `ctx.tools`, `ctx.workflowEngine`, `ctx.systemPrompt`, `a calling Agent (exec.agent parents the script children)` | `tool/call`, `tool/result` | - | - |
| `@deepseek-ai/dsh-tool-web` | `web_fetch`, `web_search` | `ctx.tools`, `ctx.web`, `ctx.systemPrompt` | `tool/call`, `tool/result` | - | web_search and web_fetch keep provider selection behind ctx.web so model-visible schemas stay stable across backend swaps. | | `@deepseek-ai/dsh-tool-web` | `web_fetch`, `web_search` | `ctx.tools`, `ctx.web`, `ctx.systemPrompt` | `tool/call`, `tool/result` | - | web_search and web_fetch keep provider selection behind ctx.web so model-visible schemas stay stable across backend swaps. |
<a id="deepseek-aidsh-tool-ask-user"></a>
## `@deepseek-ai/dsh-tool-ask-user` ## `@deepseek-ai/dsh-tool-ask-user`
### `ask_user_question` ### `ask_user_question`
@@ -112,6 +114,8 @@ Source: [`packages/interaction/tool-ask-user/src/index.ts`](../packages/interact
ask_user_question pauses the tool call until the active UI provider returns a human answer. ask_user_question pauses the tool call until the active UI provider returns a human answer.
<a id="deepseek-aidsh-tools"></a>
## `@deepseek-ai/dsh-tools` ## `@deepseek-ai/dsh-tools`
### `run_code` ### `run_code`
@@ -142,6 +146,8 @@ Source: [`packages/core/tools/src/code-mode.ts`](../packages/core/tools/src/code
Owned by the tool registry as a reserved transport outside filterable capability layers under `mode: code` / `mode: both` (see the Code Mode Agent Note). Under `code` it is the registry's only wire contribution; the other visible capabilities are declared in a generated SDK section in the loaded runtime's language, and a program calls them through bindings scheduled under the native concurrency contract (submission-ordered starts and policy; concurrency-safe bodies overlap up to `maxParallelSubCalls`) that re-enter the complete guarded tool pipeline and link each nested execution to this outer result. Owned by the tool registry as a reserved transport outside filterable capability layers under `mode: code` / `mode: both` (see the Code Mode Agent Note). Under `code` it is the registry's only wire contribution; the other visible capabilities are declared in a generated SDK section in the loaded runtime's language, and a program calls them through bindings scheduled under the native concurrency contract (submission-ordered starts and policy; concurrency-safe bodies overlap up to `maxParallelSubCalls`) that re-enter the complete guarded tool pipeline and link each nested execution to this outer result.
<a id="deepseek-aidsh-plan-mode"></a>
## `@deepseek-ai/dsh-plan-mode` ## `@deepseek-ai/dsh-plan-mode`
### `exit_plan_mode` ### `exit_plan_mode`
@@ -167,6 +173,8 @@ Source: [`packages/plan/plan-mode/src/index.ts`](../packages/plan/plan-mode/src/
exit_plan_mode stays in the model-facing schema while planning is inactive so transitions add no tool-catalog churn on top of the plan-policy change. Its execute path rejects calls outside plan mode; in plan mode it presents the plan over the user-questions seam (approve / keep planning with feedback), and approval logs plan mode inactive at the step boundary. exit_plan_mode stays in the model-facing schema while planning is inactive so transitions add no tool-catalog churn on top of the plan-policy change. Its execute path rejects calls outside plan mode; in plan mode it presents the plan over the user-questions seam (approve / keep planning with feedback), and approval logs plan mode inactive at the step boundary.
<a id="deepseek-aidsh-tool-bash"></a>
## `@deepseek-ai/dsh-tool-bash` ## `@deepseek-ai/dsh-tool-bash`
### `bash` ### `bash`
@@ -209,6 +217,8 @@ Source: [`packages/shell/tool-bash/src/index.ts`](../packages/shell/tool-bash/sr
The bash tool is the model-facing consumer of the bash executor seam. A `run_in_background` run registers with the generic `ctx.jobs` runtime and is collected/stopped through the `job_*` tools from `@deepseek-ai/dsh-tool-jobs`; the `enableRunInBackground` config (default true) removes the parameter entirely when disabled. The bash tool is the model-facing consumer of the bash executor seam. A `run_in_background` run registers with the generic `ctx.jobs` runtime and is collected/stopped through the `job_*` tools from `@deepseek-ai/dsh-tool-jobs`; the `enableRunInBackground` config (default true) removes the parameter entirely when disabled.
<a id="deepseek-aidsh-tool-pwsh"></a>
## `@deepseek-ai/dsh-tool-pwsh` ## `@deepseek-ai/dsh-tool-pwsh`
### `pwsh` ### `pwsh`
@@ -251,6 +261,8 @@ Source: [`packages/shell/tool-pwsh/src/index.ts`](../packages/shell/tool-pwsh/sr
The pwsh tool is the PowerShell-dialect consumer of the bash executor seam for Windows compositions (a PowerShell executor such as `@deepseek-ai/dsh-pwsh-local` backs `ctx.shell`); it mirrors the bash tool call-for-call minus sandbox controls — `run_in_background` runs register with the generic `ctx.jobs` runtime and are collected/stopped through the `job_*` tools, and the managed `DSH_*` environment comes from `@deepseek-ai/dsh-shell-env`. Each call runs in a fresh process (no persistent PTY session), with native `C:\...` paths and `$env:NAME` variables. The pwsh tool is the PowerShell-dialect consumer of the bash executor seam for Windows compositions (a PowerShell executor such as `@deepseek-ai/dsh-pwsh-local` backs `ctx.shell`); it mirrors the bash tool call-for-call minus sandbox controls — `run_in_background` runs register with the generic `ctx.jobs` runtime and are collected/stopped through the `job_*` tools, and the managed `DSH_*` environment comes from `@deepseek-ai/dsh-shell-env`. Each call runs in a fresh process (no persistent PTY session), with native `C:\...` paths and `$env:NAME` variables.
<a id="deepseek-aidsh-tool-cordis"></a>
## `@deepseek-ai/dsh-tool-cordis` ## `@deepseek-ai/dsh-tool-cordis`
### `cordis_define` ### `cordis_define`
@@ -487,6 +499,8 @@ Source: [`packages/extensions/tool-cordis/src/index.ts`](../packages/extensions/
Not in any shipped tree (a deliberate opt-in — dynamic package code reaches the real runtime, see .agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md). The toolset injects `ctx.dynamicCordisRunner` from `@deepseek-ai/dsh-cordis-host-runner`, which owns the definition registry and the vm sandbox; a composition missing it never activates the tools. A running package may register ADDITIONAL model-visible tools until it is stopped, undefined, or DSH restarts; a full changed request header logs those tool-set changes. Not in any shipped tree (a deliberate opt-in — dynamic package code reaches the real runtime, see .agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md). The toolset injects `ctx.dynamicCordisRunner` from `@deepseek-ai/dsh-cordis-host-runner`, which owns the definition registry and the vm sandbox; a composition missing it never activates the tools. A running package may register ADDITIONAL model-visible tools until it is stopped, undefined, or DSH restarts; a full changed request header logs those tool-set changes.
<a id="deepseek-aidsh-tool-bash-persistent"></a>
## `@deepseek-ai/dsh-tool-bash-persistent` ## `@deepseek-ai/dsh-tool-bash-persistent`
### `bash` ### `bash`
@@ -512,6 +526,8 @@ Source: [`packages/shell/tool-bash-persistent/src/index.ts`](../packages/shell/t
One owner-isolated persistent bash tool; deployment composition supplies the PTY backend and may override the model-facing environment description. One owner-isolated persistent bash tool; deployment composition supplies the PTY backend and may override the model-facing environment description.
<a id="deepseek-aidsh-tool-str-replace-editor"></a>
## `@deepseek-ai/dsh-tool-str-replace-editor` ## `@deepseek-ai/dsh-tool-str-replace-editor`
### `str_replace_editor` ### `str_replace_editor`
@@ -580,6 +596,8 @@ Source: [`packages/fs/tool-str-replace-editor/src/index.ts`](../packages/fs/tool
Standalone view/create/unique literal replace/line insert tool over the filesystem seam; it composes with any shell or terminal API. Standalone view/create/unique literal replace/line insert tool over the filesystem seam; it composes with any shell or terminal API.
<a id="deepseek-aidsh-tool-fs"></a>
## `@deepseek-ai/dsh-tool-fs` ## `@deepseek-ai/dsh-tool-fs`
### `edit` ### `edit`
@@ -695,6 +713,8 @@ Source: [`packages/fs/tool-fs/src/index.ts`](../packages/fs/tool-fs/src/index.ts
The read-before-write/edit policy is added by `@deepseek-ai/dsh-fs-observation-policy` (an `fs/*` event-gate plugin, no schema change); a deployment that loads these tools is expected to also load it. `read_image` is not registered without `ctx.attachments`; its schema is route-independent, and execution refuses unless the exact routed model declares image input. The read-before-write/edit policy is added by `@deepseek-ai/dsh-fs-observation-policy` (an `fs/*` event-gate plugin, no schema change); a deployment that loads these tools is expected to also load it. `read_image` is not registered without `ctx.attachments`; its schema is route-independent, and execution refuses unless the exact routed model declares image input.
<a id="deepseek-aidsh-tool-fs-search"></a>
## `@deepseek-ai/dsh-tool-fs-search` ## `@deepseek-ai/dsh-tool-fs-search`
### `glob` ### `glob`
@@ -753,6 +773,8 @@ Source: [`packages/fs/tool-fs-search/src/index.ts`](../packages/fs/tool-fs-searc
glob and grep are unconditional discovery tools that spawn the packaged ripgrep binary (`@vscode/ripgrep`) through ctx.subprocess as ordinary foreground calls (never background jobs) — no host `rg` install and no shell layer. The catalog uses `sampleOverCapGlobResults: true`; deployments must choose that behavior explicitly. Capped results save the complete formatted list through the optional ctx.spillStore backend; returned locators are follow-up-readable/searchable when the backend exposes local paths in co-located deployments. glob and grep are unconditional discovery tools that spawn the packaged ripgrep binary (`@vscode/ripgrep`) through ctx.subprocess as ordinary foreground calls (never background jobs) — no host `rg` install and no shell layer. The catalog uses `sampleOverCapGlobResults: true`; deployments must choose that behavior explicitly. Capped results save the complete formatted list through the optional ctx.spillStore backend; returned locators are follow-up-readable/searchable when the backend exposes local paths in co-located deployments.
<a id="deepseek-aidsh-tool-terminal"></a>
## `@deepseek-ai/dsh-tool-terminal` ## `@deepseek-ai/dsh-tool-terminal`
### `terminal_close` ### `terminal_close`
@@ -916,6 +938,8 @@ Source: [`packages/terminal/tool-terminal/src/index.ts`](../packages/terminal/to
The six terminal tools are opt-in and complement one-shot shell/filesystem tools. `terminal_send(run_in_background: true)` registers with `ctx.jobs`; TUI, named key sequences, BEL, resize, auto-start, and cross-agent sharing are absent from the schema. The six terminal tools are opt-in and complement one-shot shell/filesystem tools. `terminal_send(run_in_background: true)` registers with `ctx.jobs`; TUI, named key sequences, BEL, resize, auto-start, and cross-agent sharing are absent from the schema.
<a id="deepseek-aidsh-tool-goal"></a>
## `@deepseek-ai/dsh-tool-goal` ## `@deepseek-ai/dsh-tool-goal`
### `create_goal` ### `create_goal`
@@ -1008,6 +1032,8 @@ Source: [`packages/goal/tool-goal/src/index.ts`](../packages/goal/tool-goal/src/
create, edit, pause, and resume require direct-human root authority; complete and blocked also accept the exact current goal round. The default blocked lower bound is three admitted rounds. create, edit, pause, and resume require direct-human root authority; complete and blocked also accept the exact current goal round. The default blocked lower bound is three admitted rounds.
<a id="deepseek-aidsh-schedule"></a>
## `@deepseek-ai/dsh-schedule` ## `@deepseek-ai/dsh-schedule`
### `schedule_create` ### `schedule_create`
@@ -1103,6 +1129,8 @@ Source: [`packages/schedule/schedule/src/tools.ts`](../packages/schedule/schedul
Registered only inside live root Agent scopes created after the opt-in Schedule plugin loads. Version 1 accepts after_seconds, explicit absolute at, and bounded fixed-rate every_seconds, and discloses session-local delivery; management reads and mutations require the shared Session persistence barrier. Registered only inside live root Agent scopes created after the opt-in Schedule plugin loads. Version 1 accepts after_seconds, explicit absolute at, and bounded fixed-rate every_seconds, and discloses session-local delivery; management reads and mutations require the shared Session persistence barrier.
<a id="deepseek-aidsh-tool-lsp"></a>
## `@deepseek-ai/dsh-tool-lsp` ## `@deepseek-ai/dsh-tool-lsp`
### `lsp` ### `lsp`
@@ -1149,6 +1177,8 @@ Source: [`packages/lsp/tool-lsp/src/index.ts`](../packages/lsp/tool-lsp/src/inde
The lsp tool keeps provider selection and language-server subprocesses behind ctx.lsp, so its model-visible schema stays stable across providers. Requires a registered provider (e.g. `@deepseek-ai/dsh-lsp-stdio`) at runtime; without one, a query returns the structured `LSP_UNAVAILABLE` error rather than changing the schema. The lsp tool keeps provider selection and language-server subprocesses behind ctx.lsp, so its model-visible schema stays stable across providers. Requires a registered provider (e.g. `@deepseek-ai/dsh-lsp-stdio`) at runtime; without one, a query returns the structured `LSP_UNAVAILABLE` error rather than changing the schema.
<a id="deepseek-aidsh-tool-ralph"></a>
## `@deepseek-ai/dsh-tool-ralph` ## `@deepseek-ai/dsh-tool-ralph`
### `ralph` ### `ralph`
@@ -1178,6 +1208,8 @@ Source: [`packages/workflow/tool-ralph/src/index.ts`](../packages/workflow/tool-
A fixed foreground workflow starts one fresh structured child per round; the model selects only the immutable objective and an optional round cap. A fixed foreground workflow starts one fresh structured child per round; the model selects only the immutable objective and an optional round cap.
<a id="deepseek-aidsh-tool-skill"></a>
## `@deepseek-ai/dsh-tool-skill` ## `@deepseek-ai/dsh-tool-skill`
### `skill` ### `skill`
@@ -1201,6 +1233,8 @@ Load the full instructions for an available skill. Call this with the exact skil
Source: [`packages/skill/tool-skill/src/index.ts`](../packages/skill/tool-skill/src/index.ts) Source: [`packages/skill/tool-skill/src/index.ts`](../packages/skill/tool-skill/src/index.ts)
<a id="deepseek-aidsh-tool-session-query"></a>
## `@deepseek-ai/dsh-tool-session-query` ## `@deepseek-ai/dsh-tool-session-query`
### `session_event_read` ### `session_event_read`
@@ -1434,6 +1468,8 @@ Source: [`packages/session-query/tool-session-query/src/index.ts`](../packages/s
The five read-only tools hide provider cursors and authorize every result from the immutable calling agent session. The package is opt-in; compositions that need enforced deadlines or bounded inline output also mount the generic timeout or spill policies. The five read-only tools hide provider cursors and authorize every result from the immutable calling agent session. The package is opt-in; compositions that need enforced deadlines or bounded inline output also mount the generic timeout or spill policies.
<a id="deepseek-aidsh-tool-subagent"></a>
## `@deepseek-ai/dsh-tool-subagent` ## `@deepseek-ai/dsh-tool-subagent`
### `subagent` ### `subagent`
@@ -1468,6 +1504,8 @@ Source: [`packages/subagent/tool-subagent/src/index.ts`](../packages/subagent/to
The registered tool name is the load-time `toolName` config (default `subagent`); the schema above is that default. The shipped compositions load this package once per subagent backend, so the model additionally sees `subagent_fork` bound to the fork backend. Each instance's description, `run_in_background` parameter, and system-prompt policy follow its own `backgroundMode` and `enableRunInBackground`, so the two shipped schemas are not identical: `subagent` is `continuable` and defaults omitted calls to background with automatic settlement delivery, while `subagent_fork` stays `one-shot` and defaults them to foreground — see `packages/bundle/base/cordis.patch.yml` and `examples/acp-agent/cordis.yml`. The registered tool name is the load-time `toolName` config (default `subagent`); the schema above is that default. The shipped compositions load this package once per subagent backend, so the model additionally sees `subagent_fork` bound to the fork backend. Each instance's description, `run_in_background` parameter, and system-prompt policy follow its own `backgroundMode` and `enableRunInBackground`, so the two shipped schemas are not identical: `subagent` is `continuable` and defaults omitted calls to background with automatic settlement delivery, while `subagent_fork` stays `one-shot` and defaults them to foreground — see `packages/bundle/base/cordis.patch.yml` and `examples/acp-agent/cordis.yml`.
<a id="deepseek-aidsh-tool-subagent-control"></a>
## `@deepseek-ai/dsh-tool-subagent-control` ## `@deepseek-ai/dsh-tool-subagent-control`
### `interrupt_agent` ### `interrupt_agent`
@@ -1541,6 +1579,8 @@ Source: [`packages/subagent/tool-subagent-control/src/index.ts`](../packages/sub
The globally named control tools over continuable background subagents: provider-bound `tool-subagent` instances register distinct delegation tools, while this package registers `send_message` and `interrupt_agent` once, plus `list_agents` from its separately loaded `/list-agents` plugin (whose catalog rows use the sessionProjections and live Agent registries). The globally named control tools over continuable background subagents: provider-bound `tool-subagent` instances register distinct delegation tools, while this package registers `send_message` and `interrupt_agent` once, plus `list_agents` from its separately loaded `/list-agents` plugin (whose catalog rows use the sessionProjections and live Agent registries).
<a id="deepseek-aidsh-tool-subagent-report"></a>
## `@deepseek-ai/dsh-tool-subagent-report` ## `@deepseek-ai/dsh-tool-subagent-report`
### `report` ### `report`
@@ -1566,6 +1606,8 @@ Source: [`packages/subagent/tool-subagent-report/src/index.ts`](../packages/suba
Registered per continuable in-process child rather than globally, so this schema is visible only inside such a child and survives its global `toolFilter`. The same contribution installs the child-scoped `tool:report` prompt section, which this catalog does not render. The parent-facing `send_message` tool is installed independently. Registered per continuable in-process child rather than globally, so this schema is visible only inside such a child and survives its global `toolFilter`. The same contribution installs the child-scoped `tool:report` prompt section, which this catalog does not render. The parent-facing `send_message` tool is installed independently.
<a id="deepseek-aidsh-tool-jobs"></a>
## `@deepseek-ai/dsh-tool-jobs` ## `@deepseek-ai/dsh-tool-jobs`
### `job_kill` ### `job_kill`
@@ -1637,6 +1679,8 @@ Source: [`packages/jobs/tool-jobs/src/index.ts`](../packages/jobs/tool-jobs/src/
The kind-agnostic background-job controller: background bash commands, PTY sends, and subagents are read, listed, and killed through the same three tools. Loading the plugin attaches the controller that arms producers' `ctx.jobs.start()`. The kind-agnostic background-job controller: background bash commands, PTY sends, and subagents are read, listed, and killed through the same three tools. Loading the plugin attaches the controller that arms producers' `ctx.jobs.start()`.
<a id="deepseek-aidsh-tool-todo"></a>
## `@deepseek-ai/dsh-tool-todo` ## `@deepseek-ai/dsh-tool-todo`
### `todo_write` ### `todo_write`
@@ -1685,6 +1729,8 @@ Source: [`packages/todo/tool-todo/src/index.ts`](../packages/todo/tool-todo/src/
todo_write is session-owned state; UIs render the latest todo/write event as a checklist. `allowParallelInProgress` is required with no default, so the catalog states its choice: `true`, whose description invites several `in_progress` items. A deployment choosing `false` receives the same tool with a description asking for exactly one active task. todo_write is session-owned state; UIs render the latest todo/write event as a checklist. `allowParallelInProgress` is required with no default, so the catalog states its choice: `true`, whose description invites several `in_progress` items. A deployment choosing `false` receives the same tool with a description asking for exactly one active task.
<a id="deepseek-aidsh-tool-workflow"></a>
## `@deepseek-ai/dsh-tool-workflow` ## `@deepseek-ai/dsh-tool-workflow`
### `workflow` ### `workflow`
@@ -1778,6 +1824,8 @@ Constraints: concurrency and total-agent caps apply; no filesystem, network, tim
Source: [`packages/workflow/tool-workflow/src/index.ts`](../packages/workflow/tool-workflow/src/index.ts) Source: [`packages/workflow/tool-workflow/src/index.ts`](../packages/workflow/tool-workflow/src/index.ts)
<a id="deepseek-aidsh-tool-web"></a>
## `@deepseek-ai/dsh-tool-web` ## `@deepseek-ai/dsh-tool-web`
### `web_fetch` ### `web_fetch`
+48
View File
@@ -42,6 +42,8 @@
| `@deepseek-ai/dsh-tool-workflow` | `workflow` | `ctx.tools``ctx.workflowEngine``ctx.systemPrompt``a calling Agent (exec.agent parents the script children)` | `tool/call``tool/result` | - | - | | `@deepseek-ai/dsh-tool-workflow` | `workflow` | `ctx.tools``ctx.workflowEngine``ctx.systemPrompt``a calling Agent (exec.agent parents the script children)` | `tool/call``tool/result` | - | - |
| `@deepseek-ai/dsh-tool-web` | `web_fetch``web_search` | `ctx.tools``ctx.web``ctx.systemPrompt` | `tool/call``tool/result` | - | web_search 和 web_fetch 将提供方选择置于 ctx.web 之后,使模型可见 schema 在更换后端时保持稳定。 | | `@deepseek-ai/dsh-tool-web` | `web_fetch``web_search` | `ctx.tools``ctx.web``ctx.systemPrompt` | `tool/call``tool/result` | - | web_search 和 web_fetch 将提供方选择置于 ctx.web 之后,使模型可见 schema 在更换后端时保持稳定。 |
<a id="deepseek-aidsh-tool-ask-user"></a>
## `@deepseek-ai/dsh-tool-ask-user` ## `@deepseek-ai/dsh-tool-ask-user`
### `ask_user_question` ### `ask_user_question`
@@ -114,6 +116,8 @@
ask_user_question 会暂停工具调用,直到当前 UI 提供方返回人类答案。 ask_user_question 会暂停工具调用,直到当前 UI 提供方返回人类答案。
<a id="deepseek-aidsh-tools"></a>
## `@deepseek-ai/dsh-tools` ## `@deepseek-ai/dsh-tools`
### `run_code` ### `run_code`
@@ -144,6 +148,8 @@ ask_user_question 会暂停工具调用,直到当前 UI 提供方返回人类
`mode: code``mode: both` 下,它由工具注册表所有,作为可过滤能力层之外的保留传输机制(参见 Code Mode Agent Note)。在 `code` 下,它是注册表对协议格式的唯一贡献;其他可见能力在使用已加载运行时语言生成的 SDK 章节中声明。程序通过 binding 调用这些能力,调用按照原生并发约定调度:启动顺序和策略遵循提交顺序,并发安全的函数体最多重叠执行 `maxParallelSubCalls` 个。调用会重新进入完整且受守卫保护的工具流水线,并将每个嵌套执行关联到此外层结果。 `mode: code``mode: both` 下,它由工具注册表所有,作为可过滤能力层之外的保留传输机制(参见 Code Mode Agent Note)。在 `code` 下,它是注册表对协议格式的唯一贡献;其他可见能力在使用已加载运行时语言生成的 SDK 章节中声明。程序通过 binding 调用这些能力,调用按照原生并发约定调度:启动顺序和策略遵循提交顺序,并发安全的函数体最多重叠执行 `maxParallelSubCalls` 个。调用会重新进入完整且受守卫保护的工具流水线,并将每个嵌套执行关联到此外层结果。
<a id="deepseek-aidsh-plan-mode"></a>
## `@deepseek-ai/dsh-plan-mode` ## `@deepseek-ai/dsh-plan-mode`
### `exit_plan_mode` ### `exit_plan_mode`
@@ -169,6 +175,8 @@ ask_user_question 会暂停工具调用,直到当前 UI 提供方返回人类
规划未激活时,exit_plan_mode 仍保留在面向模型的 schema 中,这样状态转换不会在规划策略变更之外额外造成工具目录变动。其执行路径会拒绝规划模式之外的调用;在规划模式下,它通过用户交互 seam 提交计划(批准/根据反馈继续规划),批准后会在步骤边界记录规划模式已停用。 规划未激活时,exit_plan_mode 仍保留在面向模型的 schema 中,这样状态转换不会在规划策略变更之外额外造成工具目录变动。其执行路径会拒绝规划模式之外的调用;在规划模式下,它通过用户交互 seam 提交计划(批准/根据反馈继续规划),批准后会在步骤边界记录规划模式已停用。
<a id="deepseek-aidsh-tool-bash"></a>
## `@deepseek-ai/dsh-tool-bash` ## `@deepseek-ai/dsh-tool-bash`
### `bash` ### `bash`
@@ -211,6 +219,8 @@ ask_user_question 会暂停工具调用,直到当前 UI 提供方返回人类
bash 工具是 bash 执行器 seam 面向模型的消费方。使用 `run_in_background` 的运行会注册到通用 `ctx.jobs` 运行时,并通过 `job_*` 工具(来自 `@deepseek-ai/dsh-tool-jobs`)收集/停止;禁用 `enableRunInBackground` 配置(默认为 true)后,该参数会被完全移除。 bash 工具是 bash 执行器 seam 面向模型的消费方。使用 `run_in_background` 的运行会注册到通用 `ctx.jobs` 运行时,并通过 `job_*` 工具(来自 `@deepseek-ai/dsh-tool-jobs`)收集/停止;禁用 `enableRunInBackground` 配置(默认为 true)后,该参数会被完全移除。
<a id="deepseek-aidsh-tool-pwsh"></a>
## `@deepseek-ai/dsh-tool-pwsh` ## `@deepseek-ai/dsh-tool-pwsh`
### `pwsh` ### `pwsh`
@@ -253,6 +263,8 @@ bash 工具是 bash 执行器 seam 面向模型的消费方。使用 `run_in_bac
pwsh 工具是 Windows 组合中 bash 执行器 seam 的 PowerShell 方言消费方(由 `@deepseek-ai/dsh-pwsh-local` 等 PowerShell 执行器为 `ctx.shell` 提供后端);除沙箱接口外,它逐项对应 bash 工具调用。使用 `run_in_background` 的运行会注册到通用 `ctx.jobs` 运行时,并通过 `job_*` 工具收集/停止;托管的 `DSH_*` 环境来自 `@deepseek-ai/dsh-shell-env`。每次调用都在新进程中运行,不使用持久 PTY 会话。路径采用原生 `C:\...` 形式,变量采用 `$env:NAME` pwsh 工具是 Windows 组合中 bash 执行器 seam 的 PowerShell 方言消费方(由 `@deepseek-ai/dsh-pwsh-local` 等 PowerShell 执行器为 `ctx.shell` 提供后端);除沙箱接口外,它逐项对应 bash 工具调用。使用 `run_in_background` 的运行会注册到通用 `ctx.jobs` 运行时,并通过 `job_*` 工具收集/停止;托管的 `DSH_*` 环境来自 `@deepseek-ai/dsh-shell-env`。每次调用都在新进程中运行,不使用持久 PTY 会话。路径采用原生 `C:\...` 形式,变量采用 `$env:NAME`
<a id="deepseek-aidsh-tool-cordis"></a>
## `@deepseek-ai/dsh-tool-cordis` ## `@deepseek-ai/dsh-tool-cordis`
### `cordis_define` ### `cordis_define`
@@ -489,6 +501,8 @@ pwsh 工具是 Windows 组合中 bash 执行器 seam 的 PowerShell 方言消费
不在任何随产品发布的树中,需要显式选择启用;动态 Package 代码可以访问真实运行时,见 .agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md。该工具集注入 `@deepseek-ai/dsh-cordis-host-runner` 提供的 `ctx.dynamicCordisRunner`,后者拥有定义注册表和 vm 沙箱;组合缺少它时这些工具不会激活。运行中的 Package 在停止、undefine 或 DSH 重启前可以注册**额外的**模型可见工具;发生这类工具集变化时,系统会记录完整且有变动的请求头。 不在任何随产品发布的树中,需要显式选择启用;动态 Package 代码可以访问真实运行时,见 .agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md。该工具集注入 `@deepseek-ai/dsh-cordis-host-runner` 提供的 `ctx.dynamicCordisRunner`,后者拥有定义注册表和 vm 沙箱;组合缺少它时这些工具不会激活。运行中的 Package 在停止、undefine 或 DSH 重启前可以注册**额外的**模型可见工具;发生这类工具集变化时,系统会记录完整且有变动的请求头。
<a id="deepseek-aidsh-tool-bash-persistent"></a>
## `@deepseek-ai/dsh-tool-bash-persistent` ## `@deepseek-ai/dsh-tool-bash-persistent`
### `bash` ### `bash`
@@ -514,6 +528,8 @@ pwsh 工具是 Windows 组合中 bash 执行器 seam 的 PowerShell 方言消费
一个按所有者隔离的持久 bash 工具;部署组合提供 PTY 后端,并可覆盖面向模型的环境描述。 一个按所有者隔离的持久 bash 工具;部署组合提供 PTY 后端,并可覆盖面向模型的环境描述。
<a id="deepseek-aidsh-tool-str-replace-editor"></a>
## `@deepseek-ai/dsh-tool-str-replace-editor` ## `@deepseek-ai/dsh-tool-str-replace-editor`
### `str_replace_editor` ### `str_replace_editor`
@@ -584,6 +600,8 @@ pwsh 工具是 Windows 组合中 bash 执行器 seam 的 PowerShell 方言消费
基于文件系统 seam 的独立查看/创建/唯一字面量替换/按行插入工具;可与任何 shell 或终端接口组合。 基于文件系统 seam 的独立查看/创建/唯一字面量替换/按行插入工具;可与任何 shell 或终端接口组合。
<a id="deepseek-aidsh-tool-fs"></a>
## `@deepseek-ai/dsh-tool-fs` ## `@deepseek-ai/dsh-tool-fs`
### `edit` ### `edit`
@@ -699,6 +717,8 @@ pwsh 工具是 Windows 组合中 bash 执行器 seam 的 PowerShell 方言消费
先读后写/编辑策略由 `@deepseek-ai/dsh-fs-observation-policy` 添加;它是一个 `fs/*` 事件门禁插件,不会改变 schema。加载这些工具的部署按预期也应加载该插件。没有 `ctx.attachments``read_image` 不会注册;其 schema 与路由无关,执行时除非确切路由的模型声明图像输入,否则拒绝。 先读后写/编辑策略由 `@deepseek-ai/dsh-fs-observation-policy` 添加;它是一个 `fs/*` 事件门禁插件,不会改变 schema。加载这些工具的部署按预期也应加载该插件。没有 `ctx.attachments``read_image` 不会注册;其 schema 与路由无关,执行时除非确切路由的模型声明图像输入,否则拒绝。
<a id="deepseek-aidsh-tool-fs-search"></a>
## `@deepseek-ai/dsh-tool-fs-search` ## `@deepseek-ai/dsh-tool-fs-search`
### `glob` ### `glob`
@@ -757,6 +777,8 @@ pwsh 工具是 Windows 组合中 bash 执行器 seam 的 PowerShell 方言消费
glob 和 grep 是无条件可用的发现工具,通过 ctx.subprocess spawn 随包提供的 ripgrep 二进制文件(`@vscode/ripgrep`),并作为普通前台调用运行,绝不作为后台任务;无需在宿主机安装 `rg`,也不经过 shell 层。本目录使用 `sampleOverCapGlobResults: true`;部署必须显式选择该行为。结果超过上限时,会通过可选的 ctx.spillStore 后端保存完整的格式化列表;在共置部署中,如果后端公开本地路径,返回的定位信息可供后续读取/搜索。 glob 和 grep 是无条件可用的发现工具,通过 ctx.subprocess spawn 随包提供的 ripgrep 二进制文件(`@vscode/ripgrep`),并作为普通前台调用运行,绝不作为后台任务;无需在宿主机安装 `rg`,也不经过 shell 层。本目录使用 `sampleOverCapGlobResults: true`;部署必须显式选择该行为。结果超过上限时,会通过可选的 ctx.spillStore 后端保存完整的格式化列表;在共置部署中,如果后端公开本地路径,返回的定位信息可供后续读取/搜索。
<a id="deepseek-aidsh-tool-terminal"></a>
## `@deepseek-ai/dsh-tool-terminal` ## `@deepseek-ai/dsh-tool-terminal`
### `terminal_close` ### `terminal_close`
@@ -920,6 +942,8 @@ glob 和 grep 是无条件可用的发现工具,通过 ctx.subprocess spawn
这 6 个终端工具需要选择启用,用于补充一次性 bash/文件系统工具。`terminal_send(run_in_background: true)` 会注册到 `ctx.jobs`;schema 不包含 TUI、具名按键序列、BEL、调整尺寸、自动启动和跨 agent 共享。 这 6 个终端工具需要选择启用,用于补充一次性 bash/文件系统工具。`terminal_send(run_in_background: true)` 会注册到 `ctx.jobs`;schema 不包含 TUI、具名按键序列、BEL、调整尺寸、自动启动和跨 agent 共享。
<a id="deepseek-aidsh-tool-goal"></a>
## `@deepseek-ai/dsh-tool-goal` ## `@deepseek-ai/dsh-tool-goal`
### `create_goal` ### `create_goal`
@@ -1012,6 +1036,8 @@ glob 和 grep 是无条件可用的发现工具,通过 ctx.subprocess spawn
create、edit、pause 和 resume 要求直接来自人类的根权限;complete 和 blocked 也接受确切的当前 Goal Round。blocked 的默认下限是 3 个获准的 Round。 create、edit、pause 和 resume 要求直接来自人类的根权限;complete 和 blocked 也接受确切的当前 Goal Round。blocked 的默认下限是 3 个获准的 Round。
<a id="deepseek-aidsh-schedule"></a>
## `@deepseek-ai/dsh-schedule` ## `@deepseek-ai/dsh-schedule`
### `schedule_create` ### `schedule_create`
@@ -1107,6 +1133,8 @@ create、edit、pause 和 resume 要求直接来自人类的根权限;complete
仅在选择启用的 Schedule 插件加载后创建的 live 根 Agent scope 内注册。版本 1 接受 after_seconds、显式绝对 at 和有界固定速率 every_seconds,并披露 session-local 交付;管理读取与变更必须通过共享的 Session 持久化 barrier。 仅在选择启用的 Schedule 插件加载后创建的 live 根 Agent scope 内注册。版本 1 接受 after_seconds、显式绝对 at 和有界固定速率 every_seconds,并披露 session-local 交付;管理读取与变更必须通过共享的 Session 持久化 barrier。
<a id="deepseek-aidsh-tool-lsp"></a>
## `@deepseek-ai/dsh-tool-lsp` ## `@deepseek-ai/dsh-tool-lsp`
### `lsp` ### `lsp`
@@ -1153,6 +1181,8 @@ create、edit、pause 和 resume 要求直接来自人类的根权限;complete
lsp 工具将提供方选择和语言服务器子进程置于 ctx.lsp 之后,因此其模型可见 schema 在更换提供方时保持稳定。运行时要求已注册提供方,例如 `@deepseek-ai/dsh-lsp-stdio`;如果没有提供方,查询会返回结构化 `LSP_UNAVAILABLE` 错误,而不会改变 schema。 lsp 工具将提供方选择和语言服务器子进程置于 ctx.lsp 之后,因此其模型可见 schema 在更换提供方时保持稳定。运行时要求已注册提供方,例如 `@deepseek-ai/dsh-lsp-stdio`;如果没有提供方,查询会返回结构化 `LSP_UNAVAILABLE` 错误,而不会改变 schema。
<a id="deepseek-aidsh-tool-ralph"></a>
## `@deepseek-ai/dsh-tool-ralph` ## `@deepseek-ai/dsh-tool-ralph`
### `ralph` ### `ralph`
@@ -1182,6 +1212,8 @@ lsp 工具将提供方选择和语言服务器子进程置于 ctx.lsp 之后,
固定的前台工作流会在每个 Round 启动一个全新的结构化子级;模型只能选择不可变目标和可选的 Round 上限。 固定的前台工作流会在每个 Round 启动一个全新的结构化子级;模型只能选择不可变目标和可选的 Round 上限。
<a id="deepseek-aidsh-tool-skill"></a>
## `@deepseek-ai/dsh-tool-skill` ## `@deepseek-ai/dsh-tool-skill`
### `skill` ### `skill`
@@ -1205,6 +1237,8 @@ lsp 工具将提供方选择和语言服务器子进程置于 ctx.lsp 之后,
来源:[`packages/skill/tool-skill/src/index.ts`](../packages/skill/tool-skill/src/index.ts) 来源:[`packages/skill/tool-skill/src/index.ts`](../packages/skill/tool-skill/src/index.ts)
<a id="deepseek-aidsh-tool-session-query"></a>
## `@deepseek-ai/dsh-tool-session-query` ## `@deepseek-ai/dsh-tool-session-query`
### `session_event_read` ### `session_event_read`
@@ -1438,6 +1472,8 @@ lsp 工具将提供方选择和语言服务器子进程置于 ctx.lsp 之后,
这 5 个只读工具会隐藏提供方游标,并根据不可变的调用 agent 会话为每个结果授权。该包需要选择启用;需要强制截止时间或限制行内输出的组合还会挂载通用超时或 spill 策略。 这 5 个只读工具会隐藏提供方游标,并根据不可变的调用 agent 会话为每个结果授权。该包需要选择启用;需要强制截止时间或限制行内输出的组合还会挂载通用超时或 spill 策略。
<a id="deepseek-aidsh-tool-subagent"></a>
## `@deepseek-ai/dsh-tool-subagent` ## `@deepseek-ai/dsh-tool-subagent`
### `subagent` ### `subagent`
@@ -1472,6 +1508,8 @@ lsp 工具将提供方选择和语言服务器子进程置于 ctx.lsp 之后,
注册的工具名称取决于加载时 `toolName` 配置(默认为 `subagent`);上述 schema 对应默认值。随产品发布的组合会为每个 subagent 后端加载一次该包,因此模型还会看到绑定到 fork 后端的 `subagent_fork`。每个实例的描述、`run_in_background` 参数与 system prompt 策略取决于它自己的 `backgroundMode``enableRunInBackground`,因此两个随附 schema 并不相同:`subagent``continuable`,省略参数时默认后台运行,并由 runtime 自动投递结束结果;`subagent_fork` 保持 `one-shot`,省略参数时默认前台运行。详见 `packages/bundle/base/cordis.patch.yml``examples/acp-agent/cordis.yml` 注册的工具名称取决于加载时 `toolName` 配置(默认为 `subagent`);上述 schema 对应默认值。随产品发布的组合会为每个 subagent 后端加载一次该包,因此模型还会看到绑定到 fork 后端的 `subagent_fork`。每个实例的描述、`run_in_background` 参数与 system prompt 策略取决于它自己的 `backgroundMode``enableRunInBackground`,因此两个随附 schema 并不相同:`subagent``continuable`,省略参数时默认后台运行,并由 runtime 自动投递结束结果;`subagent_fork` 保持 `one-shot`,省略参数时默认前台运行。详见 `packages/bundle/base/cordis.patch.yml``examples/acp-agent/cordis.yml`
<a id="deepseek-aidsh-tool-subagent-control"></a>
## `@deepseek-ai/dsh-tool-subagent-control` ## `@deepseek-ai/dsh-tool-subagent-control`
### `interrupt_agent` ### `interrupt_agent`
@@ -1545,6 +1583,8 @@ lsp 工具将提供方选择和语言服务器子进程置于 ctx.lsp 之后,
这些是控制可继续后台 subagent 的全局命名工具:绑定提供方的 `tool-subagent` 实例注册不同的委派工具;本包注册一次 `send_message``interrupt_agent`,另由 `list_agents` 通过单独加载的 `/list-agents` 插件提供,其目录行使用 sessionProjections 和实时 Agent 注册表。 这些是控制可继续后台 subagent 的全局命名工具:绑定提供方的 `tool-subagent` 实例注册不同的委派工具;本包注册一次 `send_message``interrupt_agent`,另由 `list_agents` 通过单独加载的 `/list-agents` 插件提供,其目录行使用 sessionProjections 和实时 Agent 注册表。
<a id="deepseek-aidsh-tool-subagent-report"></a>
## `@deepseek-ai/dsh-tool-subagent-report` ## `@deepseek-ai/dsh-tool-subagent-report`
### `report` ### `report`
@@ -1570,6 +1610,8 @@ lsp 工具将提供方选择和语言服务器子进程置于 ctx.lsp 之后,
按可继续的进程内子级注册,而非全局注册,因此该 schema 仅在这种子级内部可见,并且不受其全局 `toolFilter` 影响。同一份贡献还会安装子级作用域的 `tool:report` 系统提示词 section,本目录不渲染该 section。面向父级的 `send_message` 工具单独安装。 按可继续的进程内子级注册,而非全局注册,因此该 schema 仅在这种子级内部可见,并且不受其全局 `toolFilter` 影响。同一份贡献还会安装子级作用域的 `tool:report` 系统提示词 section,本目录不渲染该 section。面向父级的 `send_message` 工具单独安装。
<a id="deepseek-aidsh-tool-jobs"></a>
## `@deepseek-ai/dsh-tool-jobs` ## `@deepseek-ai/dsh-tool-jobs`
### `job_kill` ### `job_kill`
@@ -1641,6 +1683,8 @@ lsp 工具将提供方选择和语言服务器子进程置于 ctx.lsp 之后,
与任务种类无关的后台任务控制器:后台 bash 命令、PTY 发送和 subagent 都通过相同的 3 个工具读取、列出和终止。加载该插件会挂接控制器,从而启用生产方的 `ctx.jobs.start()` 与任务种类无关的后台任务控制器:后台 bash 命令、PTY 发送和 subagent 都通过相同的 3 个工具读取、列出和终止。加载该插件会挂接控制器,从而启用生产方的 `ctx.jobs.start()`
<a id="deepseek-aidsh-tool-todo"></a>
## `@deepseek-ai/dsh-tool-todo` ## `@deepseek-ai/dsh-tool-todo`
### `todo_write` ### `todo_write`
@@ -1689,6 +1733,8 @@ lsp 工具将提供方选择和语言服务器子进程置于 ctx.lsp 之后,
todo_write 是会话所有的状态;UI 将最新的 todo/write 事件渲染为检查清单。`allowParallelInProgress` 是没有默认值的必填项,因此本目录明确选择 `true`,对应描述允许同时存在多个 `in_progress` 项。选择 `false` 的部署会获得同一工具,但描述会要求只能有 1 个活动任务。 todo_write 是会话所有的状态;UI 将最新的 todo/write 事件渲染为检查清单。`allowParallelInProgress` 是没有默认值的必填项,因此本目录明确选择 `true`,对应描述允许同时存在多个 `in_progress` 项。选择 `false` 的部署会获得同一工具,但描述会要求只能有 1 个活动任务。
<a id="deepseek-aidsh-tool-workflow"></a>
## `@deepseek-ai/dsh-tool-workflow` ## `@deepseek-ai/dsh-tool-workflow`
### `workflow` ### `workflow`
@@ -1783,6 +1829,8 @@ todo_write 是会话所有的状态;UI 将最新的 todo/write 事件渲染为
来源:[`packages/workflow/tool-workflow/src/index.ts`](../packages/workflow/tool-workflow/src/index.ts) 来源:[`packages/workflow/tool-workflow/src/index.ts`](../packages/workflow/tool-workflow/src/index.ts)
<a id="deepseek-aidsh-tool-web"></a>
## `@deepseek-ai/dsh-tool-web` ## `@deepseek-ai/dsh-tool-web`
### `web_fetch` ### `web_fetch`
+2 -2
View File
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority; # side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with: # after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/user/develop/basic/index.md # pnpm run verify-translation-pairing --write docs/user/develop/basic/index.md
index.md: 494b7869be6ffdf5767fac260b36b2585305b516 index.md: 08199624e638aaf4a36b04446c39c228b2af6025
index.zh.md: a55b8e31151c5cfa5445069974be9fa022fea1eb index.zh.md: c45a30d0bfffaf4a6c78303f9ca043c3397c8a08
+4 -2
View File
@@ -45,14 +45,16 @@ export function apply(ctx: Context) {
## Register it in cordis.yml ## Register it in cordis.yml
Create `scratch-plugin/cordis.yml` as a Web overlay that inserts the local plugin: Run `pwd` from the repository root, then create `scratch-plugin/cordis.yml` as a Web overlay that inserts the local plugin. Replace `/absolute/path/to/deepseek-harness` below with the printed path:
```yaml ```yaml
- insert: - insert:
- id: hello - id: hello
name: './src/my-plugin.ts' name: '/absolute/path/to/deepseek-harness/scratch-plugin/src/my-plugin.ts'
``` ```
The plugin path must be absolute. A patch file contributes configuration but does not change the profile directory from which the loader resolves module paths.
Start the Web UI with that overlay: Start the Web UI with that overlay:
```sh ```sh
+4 -2
View File
@@ -45,14 +45,16 @@ export function apply(ctx: Context) {
## 注册到 cordis.yml ## 注册到 cordis.yml
创建 `scratch-plugin/cordis.yml`,作为插入本地插件的 Web 覆盖层: 在仓库根目录运行 `pwd`,然后创建 `scratch-plugin/cordis.yml`,作为插入本地插件的 Web 覆盖层。请将下文的 `/absolute/path/to/deepseek-harness` 替换为命令打印的路径
```yaml ```yaml
- insert: - insert:
- id: hello - id: hello
name: './src/my-plugin.ts' name: '/absolute/path/to/deepseek-harness/scratch-plugin/src/my-plugin.ts'
``` ```
插件路径必须是绝对路径。patch 文件只贡献配置,不会改变 loader 解析模块路径时使用的 profile 目录。
使用该覆盖层启动 Web UI 使用该覆盖层启动 Web UI
```sh ```sh
+2 -2
View File
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority; # side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with: # after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/user/develop/basic/publish.md # pnpm run verify-translation-pairing --write docs/user/develop/basic/publish.md
publish.md: 588531a28020ebe620643cd1aaaa43de000e658a publish.md: 8283f9e7ff0c28580343975d67c5715d17c53074
publish.zh.md: b86bd43369c027972705394fded43ae053248c0f publish.zh.md: 5a87901fe39aa00e94db81dc840e39a52e0cc88c
+22 -5
View File
@@ -2,7 +2,7 @@
English | [中文](publish.zh.md) English | [中文](publish.zh.md)
The previous tutorials loaded a local plugin through a `--patch` overlay. This tutorial packages it as an installable **bundle**, installs it into a **profile** with `dsh plugin add`, and explains the layer order that determines the composed configuration. Complete [plugin configuration](./config.md) first. The previous tutorials loaded a local plugin through a `--patch` overlay. This tutorial packages it as an installable **bundle**, installs it into a **profile** with `dsh plugin add`, and explains the layer order that determines the composed configuration. It assumes the `dsh` CLI is installed. Complete [plugin configuration](./config.md) first.
## Two concepts, two manifests ## Two concepts, two manifests
@@ -15,6 +15,12 @@ A bundle is what you author and distribute; a profile is what a user boots with
### The bundle manifest ### The bundle manifest
Create the package directory:
```sh
mkdir -p hello-plugin
```
``` ```
hello-plugin/ hello-plugin/
├── package.json # declares dsh.bundle ├── package.json # declares dsh.bundle
@@ -22,6 +28,8 @@ hello-plugin/
└── index.js # plugin modules the patch rows reference └── index.js # plugin modules the patch rows reference
``` ```
Create `hello-plugin/package.json`:
```json ```json
{ {
"name": "dsh-hello-plugin", "name": "dsh-hello-plugin",
@@ -33,7 +41,17 @@ hello-plugin/
} }
``` ```
The patch file is a YAML array of patch entries, like the `--patch` overlays you have been writing, except plugin rows reference the package by name instead of a relative source path so Node resolution finds the installed code: Create `hello-plugin/index.js` with the plugin entry point:
```js
export const name = 'hello-plugin'
export function apply() {
console.log('[hello-plugin] plugin loaded!')
}
```
Create `hello-plugin/cordis.patch.yml`. The patch is a YAML array like the `--patch` overlays you have been writing, except plugin rows reference the package by name instead of a relative source path so Node resolution finds the installed code:
```yaml ```yaml
- insert: - insert:
@@ -54,11 +72,10 @@ You never write a profile manifest by hand: `dsh plugin` creates and maintains i
## Install into a profile ## Install into a profile
`dsh plugin --profile <name> <args...>` forwards to pnpm in the profile directory, so every pnpm verb works. Install your package from its checkout: `dsh plugin --profile <name> <args...>` forwards to pnpm in the profile directory, so every pnpm verb works. From the directory that contains `hello-plugin`, install the package checkout:
```sh ```sh
cd hello-plugin dsh plugin --profile demo add ./hello-plugin
dsh plugin --profile demo add .
``` ```
The first use initializes the profile (with `@deepseek-ai/dsh-base` as its first bundle), pnpm links the checkout, and `dsh` appends the bundle to `dsh.profile.bundles` because the package declares `dsh.bundle`: The first use initializes the profile (with `@deepseek-ai/dsh-base` as its first bundle), pnpm links the checkout, and `dsh` appends the bundle to `dsh.profile.bundles` because the package declares `dsh.bundle`:
+22 -5
View File
@@ -2,7 +2,7 @@
[English](publish.md) | 中文 [English](publish.md) | 中文
前几篇教程通过 `--patch` overlay 加载本地插件。本教程把它打包成可安装的**组合包**(bundle),用 `dsh plugin add` 安装进一个 **profile**,并解释决定组合后配置的层顺序。请先完成[插件配置](./config.md)。 前几篇教程通过 `--patch` overlay 加载本地插件。本教程把它打包成可安装的**组合包**(bundle),用 `dsh plugin add` 安装进一个 **profile**,并解释决定组合后配置的层顺序。本文假设 `dsh` CLI 已安装。请先完成[插件配置](./config.md)。
## 两个概念,两种 manifest ## 两个概念,两种 manifest
@@ -15,6 +15,12 @@
### 组合包 manifest ### 组合包 manifest
创建包目录:
```sh
mkdir -p hello-plugin
```
``` ```
hello-plugin/ hello-plugin/
├── package.json # declares dsh.bundle ├── package.json # declares dsh.bundle
@@ -22,6 +28,8 @@ hello-plugin/
└── index.js # plugin modules the patch rows reference └── index.js # plugin modules the patch rows reference
``` ```
创建 `hello-plugin/package.json`
```json ```json
{ {
"name": "dsh-hello-plugin", "name": "dsh-hello-plugin",
@@ -33,7 +41,17 @@ hello-plugin/
} }
``` ```
patch 文件与一直在写的 `--patch` overlay 一样,是一个 patch 条目的 YAML 数组;区别是插件行按包名而不是相对源码路径引用这个包,这样 Node 的模块解析才能找到已安装的代码 创建 `hello-plugin/index.js`,写入插件入口
```js
export const name = 'hello-plugin'
export function apply() {
console.log('[hello-plugin] plugin loaded!')
}
```
创建 `hello-plugin/cordis.patch.yml`。这个 patch 与一直在写的 `--patch` overlay 一样,是一个 patch 条目的 YAML 数组;区别是插件行按包名而不是相对源码路径引用这个包,这样 Node 的模块解析才能找到已安装的代码:
```yaml ```yaml
- insert: - insert:
@@ -54,11 +72,10 @@ profile manifest 从不需要手写:`dsh plugin` 负责创建和维护它。
## 安装进 profile ## 安装进 profile
`dsh plugin --profile <name> <args...>` 在 profile 目录内转发给 pnpm,因此所有 pnpm 子命令都可用。 checkout 安装你的包 `dsh plugin --profile <name> <args...>` 在 profile 目录内转发给 pnpm,因此所有 pnpm 子命令都可用。在包含 `hello-plugin` 的目录中安装该包的 checkout
```sh ```sh
cd hello-plugin dsh plugin --profile demo add ./hello-plugin
dsh plugin --profile demo add .
``` ```
首次使用会初始化 profile`@deepseek-ai/dsh-base` 作为它的第一个组合包),pnpm 链接该 checkout,而 `dsh` 因为这个包声明了 `dsh.bundle`,把它追加进 `dsh.profile.bundles` 首次使用会初始化 profile`@deepseek-ai/dsh-base` 作为它的第一个组合包),pnpm 链接该 checkout,而 `dsh` 因为这个包声明了 `dsh.bundle`,把它追加进 `dsh.profile.bundles`
+2 -2
View File
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority; # side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with: # after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/user/develop/framework/events.md # pnpm run verify-translation-pairing --write docs/user/develop/framework/events.md
events.md: 4a6ecbad614cf2debccaee9ace1086273d8fb95b events.md: 1d9fe5c8f5068de6ad8b2abaa85cf67be35c8459
events.zh.md: c77747bf6ac767c69e4f2d9cfe7375b71bd388e6 events.zh.md: 8bb9447a270cc2db966b1e01298a831a60a1b9c1
+3 -3
View File
@@ -40,7 +40,7 @@ ctx.on('my-plugin/ready', ({ id }) => {
### bail — short circuit ### bail — short circuit
Listeners run in order; the first non-`undefined` result becomes the final result: Listeners run in order; the first result other than `null`, `false`, or `undefined` becomes the final result:
```ts ignore-check ```ts ignore-check
// Dispatch // Dispatch
@@ -49,13 +49,13 @@ const result = ctx.bail('some-check', input)
// Listen: a returned value stops later listeners. // Listen: a returned value stops later listeners.
ctx.on('some-check', (input) => { ctx.on('some-check', (input) => {
if (shouldBlock(input)) return 'blocked' if (shouldBlock(input)) return 'blocked'
// Return undefined to continue to the next listener. // Return null, false, or undefined to continue to the next listener.
}) })
``` ```
### serial — ordered execution ### serial — ordered execution
Listeners run in registration order and asynchronous results are awaited. The first listener to return a non-empty value stops further execution: Listeners run in registration order and asynchronous results are awaited. The first result other than `null`, `false`, or `undefined` stops further execution:
```ts ignore-check ```ts ignore-check
await ctx.serial('setup-phase', context) await ctx.serial('setup-phase', context)
+3 -3
View File
@@ -40,7 +40,7 @@ ctx.on('my-plugin/ready', ({ id }) => {
### bail — 短路 ### bail — 短路
依次调用监听器,第一个非 `undefined` 的返回值将作为最终结果: 监听器按顺序运行,第一个不是 `null`、`false` 或 `undefined` 的返回值会成为最终结果:
```ts ignore-check ```ts ignore-check
// Dispatch // Dispatch
@@ -49,13 +49,13 @@ const result = ctx.bail('some-check', input)
// Listen: a returned value stops later listeners. // Listen: a returned value stops later listeners.
ctx.on('some-check', (input) => { ctx.on('some-check', (input) => {
if (shouldBlock(input)) return 'blocked' if (shouldBlock(input)) return 'blocked'
// Return undefined to continue to the next listener. // Return null, false, or undefined to continue to the next listener.
}) })
``` ```
### serial — 顺序执行 ### serial — 顺序执行
监听器按注册顺序依次执行,并等待异步结果;第一个返回非空值的监听器会终止后续执行: 监听器按注册顺序依次执行,并等待异步结果;第一个不是 `null`、`false` 或 `undefined` 的返回值会终止后续执行:
```ts ignore-check ```ts ignore-check
await ctx.serial('setup-phase', context) await ctx.serial('setup-phase', context)
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority; # side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with: # after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/user/develop/framework/service.md # pnpm run verify-translation-pairing --write docs/user/develop/framework/service.md
service.md: a9e873f1ba969b1f2d22266f8ec03207f84eeddc service.md: 03f4e7dc4df934495a4b203066183753b621339e
service.zh.md: a201280bc38176f57f5354ab205ae700c7b04a89 service.zh.md: 2f4c01e0ea6a87a87ca694b265b07e07098f3e59
+2 -2
View File
@@ -117,7 +117,7 @@ This prevents a plugin from calling a service that no longer exists.
name: '@deepseek-ai/cordis-plugin-group' name: '@deepseek-ai/cordis-plugin-group'
group: true group: true
isolate: isolate:
bash: true shell: true
config: config:
- name: '@deepseek-ai/dsh-bash-local' - name: '@deepseek-ai/dsh-bash-local'
config: config:
@@ -128,7 +128,7 @@ This prevents a plugin from calling a service that no longer exists.
name: '@deepseek-ai/cordis-plugin-group' name: '@deepseek-ai/cordis-plugin-group'
group: true group: true
isolate: isolate:
bash: true shell: true
config: config:
- name: '@deepseek-ai/dsh-bash-local' - name: '@deepseek-ai/dsh-bash-local'
config: config:

Some files were not shown because too many files have changed in this diff Show More