Bundle
@morlay/dsh-agent-instructions-as-prompt
Fork of @deepseek-ai/dsh-agent-instructions that delivers the workspace instruction baseline as system-prompt sections, keeping the upstream mid-session reminder path.
- weekly downloads
- 24k weekly downloads
- License
- MIT
- Updated
- Updated 15 hours ago
Readme
# @morlay/dsh-agent-instructions-as-prompt
> **当前状态**:本包**暂不接入默认打包**——`@morlay/dsh-preset` 的生成器保持
> `agent-instructions` 行为上游官方插件(baseline 走 user 消息),源码与测试保留,
> 供后续调整后重新启用(恢复步骤见「preset 里也要替换」)。
上游 `@deepseek-ai/dsh-agent-instructions` 的 fork:源码复制(`src/` 下 6 个文件
与上游一一对应),**baseline 的落点从 user 消息改成 system-prompt section**,
其余行为(发现、去重、字节预算、工具触达后的增量提醒、pre-step 折叠)与上游一致。
## 行为
| 项 | 上游 | 本包 |
| ------------- | ---------------------------------------- | -------------------------------------------------------- |
| baseline 落点 | user 消息(`<system-reminder>` 信封) | system prompt section,追加在**全部 section 之后** |
| 注入范围 | 全局 + 项目链 + 子目录,一次渲染 | **只注入用户全局与项目根的指令文件**,每文件一个 section |
| 正文 | intro 文案 + `Instructions from:` 标题 | **文件内容原样**(标记行之外不加任何包裹) |
| 首次注入 | 每轮 pre-step 检查并按需注入 | 首次装配注入,之后从 system prompt 标记重建(内容冻结) |
| 中途变更提醒 | 工具触达 / 文件变更 → user 消息 reminder | **相同**(未改动,嵌套目录的指令由此送达) |
| 发现与预算 | — | 相同(复用上游 `files.ts` / `render.ts`) |
因此上游那一行必须禁用(见「装配」),否则同一份指令会在 user 消息里再出现一次。
## 装配
本包自带 `cordis.patch.yml`(`dsh.bundle.patch` 声明),作为独立 bundle 安装时
自动禁用上游行并挂载本包:
```jsonc
// profile 的 package.json
"dsh": {
"profile": {
"bundles": ["@deepseek-ai/dsh-base", "@deepseek-ai/dsh-web-app", "@morlay/dsh-agent-instructions-as-prompt"]
}
}
```
在别的 bundle 里按 id 装配(等价写法):
```yaml
- id: agent-instructions
disabled: true
- insert:
- id: agent-instructions-as-prompt
name: "@morlay/dsh-agent-instructions-as-prompt"
config:
maxBytes: 65536
```
配置字段与上游同名同义(`maxBytes` 必填;`dshHome`、`projectRootMarkers`、
`maxSourceBytes`、`instructionFileCandidates`、`localInstructionFileCandidates`
缺省即上游默认,包括候选里的 `CLAUDE.md`)。**当前部署在 preset 侧把候选收紧为
`AGENTS` 系列**(不读 CLAUDE,见 `@morlay/dsh-preset` README);本包源码保持与上游
一致,接入时由 preset 传入收紧后的 config。
**preset 里也要替换(当前未启用)**:preset 的 `agent.cordis.yml` 是**会话级**
composition,profile 级的 `disabled` 管不到它——只要 preset 里还写着上游包名,每个
会话仍会由上游插件把 baseline 作为 user 消息注入一次。因此启用本包时,
`@morlay/dsh-preset` 的生成器(`tool/generate-presets.ts`)要在生成产物时把
`agent-instructions` 行的 `name` 换成 `@morlay/dsh-agent-instructions-as-prompt`
(含 profile 侧的 `disabled: true` 行)。
当前生成器不改这一行,preset 跟随官方行为;要重新启用,就把替换逻辑加回生成器,
并在 `@morlay/dsh-preset` 的 `dependencies` 里加回本包。
`fs` provider 是必需的(发现/读取经 `ctx.fs`,无 provider 时插件是 no-op);
base bundle 已带 `fs-local`。
## 正文与快照
section 正文就是文件内容:没有 `<system-reminder>` 信封、intro 文案或
`Instructions from:` 标题,**也没有任何元数据行**——那些都是 user 消息的呈现约定。
正文里的 `{{` 会被插入一个零宽字符:`renderPrompt` 对 `{{name}}` 做严格插值,未注册
的名字会直接抛错,指令正文里的模板片段不该让整轮请求失败。
注入过什么由**两份快照**记住,都不进模型可见文本:
| 快照 | 位置 | 作用 |
| ------ | ---------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| 进程内 | `promptBaselines`(WeakMap,按 session) | 本轮装配复用,文件改了也不动 system prompt |
| 持久 | `ctx.storageDomain` 的 `agent_instructions_as_prompt` 域(按 session id 键) | 重启 / resume 后重建同样的 section,并让增量协调继续看得见 baseline 的 scope |
于是:**渲染结果与上次逐字相同** → `SystemPromptProjection` 判定无变化 → 不替换
surface node 0、不打断 KV cache 前缀;文件在会话关闭期间被改过也不影响,变更照旧由
user 消息提醒送达。
**为什么不用会话事件**:上游 `Session.append()` 不写 `ignorable` 标记,而持久化读路径
会拒绝未识别的事件类型——外部插件写不了(也无法注册)这种类型,一条自定义事件会让
整条会话在重启后读不回来。
**为什么不用会话投影**:投影状态必须由 `apply(state, event)` 从会话事件 fold 出来,
且上游明确「缓存行永不权威,只是 fold 快捷方式」;没有承载数据的事件,投影恢复不出
快照。`storage-domain` 正是为这类 host 侧状态准备的(其文档把 session sidecar
metadata 列为首选用途),且写入在 resolve 前已落盘。
## 实现要点
- **挂载点是 `system-prompt/assemble` waterfall**:section 的 `text` provider 是同步
的,而文件加载是异步的;`assemble` 是唯一的异步装配点,且其返回值权威。
- **`inject = ["sessionProjections"]`**:增量协调所需的 `turnBoundary` 投影。
`systemPrompt` 刻意不声明——注入是事件监听器,而本插件也会挂在 preset 的会话级
composition 里,对 host 级服务做严格注入会让整个插件(含增量协调)加载不上。
- **注入幂等**:assembly 里已有本插件的 section 就让位,profile 级与 preset 级各挂
一个实例时不会重复注入。
- **存储域可选**:首次装配时经 `ctx.get("storageDomain")` 打开一次;未挂载或路由不到
支持 kv 的后端时只降级为「每进程重读文件」(重启后若文件已改,system prompt 会更新
一次),不影响注入本身。
- **本地改动集中在两处**(其余为上游源码原样):
- `src/index.ts`:`visibleBaselineSource` 改读会话快照;compose 不再
把 baseline 放进 user 消息(只保留 excludedScopes / 版本记账,且只把「进
system prompt 的那两个 scope」算作已提供);末尾挂 `applyPromptSections`。
- `src/state.ts`:`visibleInstructionChanges` 增加快照来源;插件名改为
`agent-instructions-as-prompt`。
- 新增 `src/prompt.ts`(注入范围过滤 + section 注入 + 快照)与
`src/baseline-domain.ts`(存储域声明与打开)。
## 上游同步
升级 `DEEPSEEK_HARNESS_VERSION` 后:
1. 用上游 `packages/context/agent-instructions/src/` 覆盖本包 `src/` 的同名 6 个
文件(`config/digest/files/render/state/index`),保留 `prompt.ts` 与
`section-marker.ts`。
2. 重新应用上面的本地改动(文件头注释里有逐项说明)。
3. `pnpm exec vitest run packages/preset/dsh-agent-instructions-as-prompt`:测试覆盖
section 注入范围与位置、正文即文件内容、跨进程快照重建、baseline 不重复注入、
变更仍产出提醒。
## 已知限制
- **section 内容不随后续变更更新**:变更会替换 surface node 0、打断 KV cache;
最新指令由 user 消息 reminder 送达对话尾部。
- **只注入全局与项目根**:嵌套目录(含 cwd 位于子目录时的中间层)的指令不进
system prompt,只在工具触达后以 user 消息提醒——这是刻意的范围收窄。
- **文件名沿用候选配置**:位置过滤之后,项目根/全局的每个命中候选(默认含
`CLAUDE.md`、`AGENTS.local.md`)各成一个 section;要只认 `AGENTS.md`,在 config
里设 `instructionFileCandidates: ["AGENTS.md"]`、`localInstructionFileCandidates: []`。
- **`complete` section 冲突**:若某 preset 注册了 `complete: true` 的 section
(如上游 `minimal` preset 的 persona),装配器只保留那一个 section,本插件的注入
会被丢弃——这是上游语义,插件不做对抗。
- **baseline identity 变化**(cwd / 项目根标记改变)会重新加载并替换 section 文本,
与上游的 replacement baseline 语义一致,但代价是替换 node 0。
Install
dsh plugin --profile web add @morlay/dsh-agent-instructions-as-prompt@0.0.2
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install morlay-dsh-agent-instructions-as-prompt from the hub