@morlay/dsh-context-assembler 0.0.1-alpha.2 → 0.0.1-alpha.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,13 +5,13 @@
5
5
  见 [`src/rows.ts` 的 `contextChannel()`](./src/rows.ts)。引用展开原先也在这个包里,
6
6
  现在独立成 [`@morlay/dsh-reference`](../dsh-reference/README.md)(它只挂 `agent/pre-step`、不依赖通道)。
7
7
 
8
- | 出口 | 行 id / 插件 name | 做什么 |
9
- | ---------------------- | --------------------------------------------------------- | ---------------------------------------------------------------------- |
10
- | `.` | `context-assembler`(插件 name `context-assembler-tree`) | **组装出口**:按 config 决定装哪些能力、各带什么参数(缺省即四套能力) |
11
- | `./assembler` | `context-assembler` | 注入通道:唯一渲染者与唯一覆盖判定处,发布 `ctx.contextAssembler` |
12
- | `./agent-instructions` | `context-agent-instructions` | 工作区指令链(`$DSH_HOME/AGENTS.md` + 项目根到 cwd 逐级) |
13
- | `./skill-catalog` | `context-skill-catalog` | skill 目录规则块 + 模型侧 `skill` 工具 |
14
- | `./scope` | `context-scope` | 模式收口:工具白名单、instruction 总开关、动态快照开关 |
8
+ | 出口 | 行 id / 插件 name | 做什么 |
9
+ | ---------------------- | --------------------------------------------------------- | ---------------------------------------------------------------------------- |
10
+ | `.` | `context-assembler`(插件 name `context-assembler-tree`) | **组装出口**:按 config 决定装哪些能力、各带什么参数(缺省即四套能力) |
11
+ | `./assembler` | `context-assembler` | 注入通道:唯一渲染者与唯一覆盖判定处,发布 `ctx.contextAssembler` |
12
+ | `./agent-instructions` | `context-agent-instructions` | 工作区指令链(`$DSH_HOME/AGENTS.md` + 项目根到 cwd 逐级) |
13
+ | `./skill-catalog` | `context-skill-catalog` | skill 目录规则块 + 模型侧 `skill` 工具 |
14
+ | `./scope` | `context-assembler-scope` | 按会话收口:工具白名单、instruction 总开关、动态快照开关(模式把定义推给它) |
15
15
 
16
16
  规则、id 表与分层的 home 在 [上下文注入规则](./.agents/designs/20260921-上下文注入规则.md);术语见
17
17
  [本包 CONTEXT](./.agents/CONTEXT.md)。
@@ -23,17 +23,21 @@
23
23
  ([`@morlay/dsh-agent-toolkit`](../../profile/dsh-agent-toolkit/README.md) 的 `guidance`)这类消费者住在别的包里,
24
24
  隔离会把它们挡在组外(行停在 waiting,不报错);理由见
25
25
  [ADR 通道作为全局服务装配不隔离](./.agents/adrs/20260923-通道作为全局服务装配不隔离.md)。`scope` **不在这份 patch 里**
26
- ——它是模式的开关,由 [`@morlay/dsh-agent-preset`](../../profile/dsh-agent-preset/README.md) 用 `scopeRow()` 按模式装
27
- (它 `inject` 通道,`ctx.get("contextAssembler")` 在 agent 子树里可达)。
26
+ ——它是模式那一层的收口行,由 [`@morlay/dsh-session-mode`](../../profile/dsh-session-mode/README.md) 的 patch 与模式定义
27
+ 一起装(行 id 与 name 归本包的 `scopeRow()`,单一 home)。
28
+
29
+ 装一次不等于每次都注入:这两条面各自按**会话**判归谁——`agent-instructions` 在预设自带那一行时**让位**,
30
+ `skill-catalog` 反过来把同名工具注册进 agent 自己那一层**抢面**(见下面两节)。所以 `capabilities` 不在装配期
31
+ 按模式裁。
28
32
 
29
33
  工具说明(汉化 / 精简 / 用法分组)也不在这里:它归
30
34
  [`@morlay/dsh-agent-toolkit`](../../profile/dsh-agent-toolkit/README.md),同样在 profile 平面装一次。
31
35
 
32
36
  ## 装配形态
33
37
 
34
- 本包是 bundle:`cordis.patch.yml` 由 [`tool/patch.ts`](./tool/patch.ts) 从 [`src/rows.ts`](./src/rows.ts) 渲染,
35
- `rows` 出口导出同一份清单。`dsh.profile.bundles` 列出本包即装一次,**服务全局共享、不隔离**;preset 只用
36
- `scopeRow()` 按模式收口(工具白名单 / instruction / 动态快照),别的包的行(工具说明、skill 目录)直接 `inject`
38
+ 本包是 bundle:`cordis.patch.yml` 由 [`src/rows.ts`](./src/rows.ts) 从 [`src/rows.ts`](./src/rows.ts) 渲染,
39
+ `rows` 出口导出同一份清单。`dsh.profile.bundles` 列出本包即装一次,**服务全局共享、不隔离**;模式的收口是
40
+ 另一行(`scopeRow()`:工具白名单 / instruction / 动态快照),别的包的行(工具说明、skill 目录)直接 `inject`
37
41
  同一份通道。
38
42
 
39
43
  ## 为什么合成一个包
@@ -47,7 +51,7 @@
47
51
  全局通道)。本包只做上下文重排。
48
52
 
49
53
  引用展开不住在这里:它不依赖 `ctx.contextAssembler`(`inject` 只有 `skills`),也没有「读文件 + 字节预算」
50
- 以外与组装出口共享的东西,所以独立成包、由 [`@morlay/better-session`](../../session/better-session/cordis.patch.yml)
54
+ 以外与组装出口共享的东西,所以独立成包、由 [`@morlay/better-session`](../../bundles/better-session/cordis.patch.yml)
51
55
  装配(见 [ADR-引用展开拆成独立包](../../../.agents/adrs/20260923-引用展开拆成独立包并按profile装配.md))。
52
56
 
53
57
  **每个出口仍是独立的 cordis 插件**(各自的 `apply` 与 `inject`)——这一点是硬要求:合成单入口会让 `inject`
@@ -70,7 +74,7 @@ user 消息注入(每条按文本幂等,只有变化的那条重发)。
70
74
  | `hiddenSkills(visible)` | 声明了 `requires` 而入口工具一个都不可见的 skill 名 |
71
75
 
72
76
  配置(`keep` / `suppress` / `replace`)的默认值在 [`src/assembler/defaults.ts`](./src/assembler/defaults.ts)。
73
- **全局一份、不隔离**:模式差异由 `context-scope` 登记(`setInstructions` / `restrictTools`)收口,跨包的
77
+ **全局一份、不隔离**:模式差异由 `context-assembler-scope` 登记(`setInstructions` / `restrictTools`)收口,跨包的
74
78
  消费者(工具说明、技能目录)直接 `inject` 同一份通道(见
75
79
  [ADR 通道作为全局服务装配不隔离](./.agents/adrs/20260923-通道作为全局服务装配不隔离.md))。
76
80
 
@@ -79,23 +83,42 @@ user 消息注入(每条按文本幂等,只有变化的那条重发)。
79
83
  `$DSH_HOME/AGENTS.md` 加项目根到 cwd 的逐级 `AGENTS.md` / `AGENTS.local.md`,**一条文件一条 id**
80
84
  (`agent-instructions:<根标识>:<文件>`,根标识是根目录的 8 位摘要——同进程两个项目根的同名文件因此不会互相
81
85
  顶掉),文件变化时只重发变了的那一份。取代上游 `@deepseek-ai/dsh-agent-instructions`(host 那行由上游
82
- web-app bundle 自己设在 preset 平面)。
86
+ web-app bundle 自己设在装配层;我们那份由本包的组装出口在 profile 平面装)——**在该会话的 preset 行清单没有
87
+ 上游那一行时**。
83
88
 
89
+ - **preset 自带那一行时让位**:官方 `standard` / `ptc` / `cordis` 的 preset 行清单里有
90
+ `@deepseek-ai/dsh-agent-instructions`(`minimal` 没有),装上的会话由 preset 侧注入,这一条一条都不注入;
91
+ `chat` 挂的 `minimal` 会话仍由我们提供。判据与理由见
92
+ [ADR-工作区指令让位skill面由通道抢面](./.agents/adrs/20260929-工作区指令让位skill面由通道抢面.md)。
84
93
  - **不跟踪 `read`/`write`/`edit`**:上游靠 touch 上浮触发刷新;本部署的 `AGENTS.md` 几乎不变,按
85
94
  `mtime:size` 对账足够。
86
95
  - **超预算可见**:单文件超过 `maxBytes` 时截断并留一行提示,不静默丢内容。
87
96
  - **与上游的 baseline 认领对齐**:`source` 带 `kind: "agent-instructions"` + `baseline: true` +
88
- `baselineIdentity`(身份与上游 `workspaceBaselineIdentity` 逐字相等)。少了后两样会出一个只在真会话里
89
- 看得见的毛病:**同会话切到官方 preset 后出现两条 AGENTS.md**——上游认为基线不存在,再注入一条自己的模板
90
- ("Use them as guidance…"),两条口径矛盾且模型无法理解为覆盖。
97
+ `baselineIdentity`(身份与上游 `workspaceBaselineIdentity` 逐字相等)。上游的认领判据只看本步 claimed 的
98
+ 消息与会话 surface,所以它救不了首步两侧同时注入(那由让位管),但管得住另一件事:**我们注入过基线的历史
99
+ 会话在 preset 会话里恢复时**,上游认得出那条、不再补一份自己的模板("Use them as guidance…")。
91
100
  - 配置:`instructionFileCandidates` / `localInstructionFileCandidates`(覆盖成只读 `AGENTS*`,不含 CLAUDE
92
101
  系列)、`maxBytes`(65536)、`dshHome`。
93
102
 
94
103
  ## skill-catalog
95
104
 
96
105
  `skill-catalog` 规则块(`<available_skills>`,一行 `名字: 摘要`,只列模型可调用的 skill)加模型侧 `skill`
97
- 工具。取代上游 `@deepseek-ai/dsh-tool-skill`(`skill-filesystem` 保留——它提供 skill 发现)。目录变更走
98
- 同 id 覆盖;`auto` 的 skill 不进目录(正文已随提示送达)。
106
+ 工具。取代上游 `@deepseek-ai/dsh-tool-skill`(`skill-filesystem` 保留——它提供 skill 发现)。目录变更走同 id
107
+ 覆盖;`auto` 的 skill 不进目录(正文已随提示送达)。
108
+
109
+ **这一面归通道**(工作区指令那一面相反,是让位):`skill` 工具按**会话**注册进 agent 自己那一层,最近的一层
110
+ 赢,遮蔽 preset 那份;上游的目录发布判据是"它自己注册的那个工具是本会话可见的那个",被遮蔽之后它不发目录,
111
+ 目录由我们发布。要点:
112
+
113
+ - 目录那条注入消息的 **kind 是我们自己的**(`context-assembler` + `form: "catalog"` + `entries`)。上游把任何
114
+ `kind: "skill-catalog"` 且条目可读的消息当成它自己的账本:kind 用它的,它会在首步删掉我们这条、在有可见
115
+ 目录之后补一条"没有可用 skill"把目录顶掉(它文档里的 visibility-loss 语义)。`form` 与 `entries` 照旧,
116
+ 客户端那张条目表不受影响;注入行的标签变成 `context-assembler`。
117
+ - 按会话注册随 agent 收回;这一行重挂(HMR / 设置面)时不再重复注册同一份定义。
118
+ - `chat`(挂 `minimal`,没有上游那一行):工具与目录同样是我们提供的,但它的模式定义不收这两样
119
+ (`instructions: false`、白名单里没有 `skill`)——白名单把它挡在模型目录外,执行层 guard 拒绝调用。
120
+
121
+ 取舍与上游判据见 [ADR-工作区指令让位skill面由通道抢面](./.agents/adrs/20260929-工作区指令让位skill面由通道抢面.md)。
99
122
 
100
123
  ## 工具说明(不在这个包里)
101
124
 
@@ -105,10 +128,16 @@ web-app bundle 自己设在 preset 平面)。
105
128
 
106
129
  ## scope
107
130
 
108
- 预设模式的收口行:工具白名单(装配期投影 + 执行层 guard + 与工具同源的 `tool:<工具名>` 说明 section,三侧
109
- 同判据)、`instructions`(关掉规则块与降级 section)、`runtimeContext`(关掉沙箱 / 审批那两条动态快照,按
110
- scope 抑制)。它解决的是"preset 只能决定加什么、管不了 host 层"——模式要表达"我只有这几个工具、一条提示词
111
- 都不要"时,唯一与来源无关的做法是在会话语义上收口。`allowTools` 为空即装配失败(该省掉整行)。
131
+ 按会话收口的出口(行 id `context-assembler-scope`):工具白名单(装配期投影 + 执行层 guard + 与工具同源的
132
+ `tool:<工具名>` 说明 section,三侧同判据)、`instructions`(关掉一切 instruction 类送达:常驻组正文、规则块
133
+ 与降级 section;按需 skill 与 `@` 引用材料不受它管)、`runtimeContext`
134
+ (关掉沙箱 / 审批那两条动态快照,按 scope 抑制)。
135
+
136
+ **定义由消费方推给它**,它自己不认识"模式":`ctx.sessionToolScope.apply(agent, mode)` 是唯一入口,落在该
137
+ agent 的 `ctx` 上(抑制器、guard 都在那里),同一个 agent 再 apply 一次就是换一份——旧的 effect 树收回、按新
138
+ 的装一遍。它解决的是"模式只能决定加什么、管不了 host 平面":`dsh.profile.bundles` 打开的包(工具行、
139
+ 实验性的 Agent Teams)在 host 平面插行,对所有会话一视同仁;模式要表达"我只有这几个工具、一条提示词都不要"
140
+ 时,唯一与来源无关的做法是在会话语义上收口。
112
141
 
113
142
  白名单同时登记给通道(`restrictTools`):技能目录与用法组正文按 `visibleTools` 收口,所以被收窄的工具既不在
114
143
  目录、也不会被讲——注册表里"装着"不等于这个会话"能用"。
@@ -125,15 +154,12 @@ scope 抑制)。它解决的是"preset 只能决定加什么、管不了 host
125
154
  capabilities: [assembler, agent-instructions, skill-catalog]
126
155
  ```
127
156
 
128
- **preset 平面**:模式只装一行开关(工具白名单 / instruction / 动态快照),通道由 profile 那份共享:
157
+ **模式平面**:收口那一行(不带 config——模式定义在 `session-mode` 行的 `config.modes` 里)由
158
+ [`@morlay/dsh-session-mode`](../../profile/dsh-session-mode/README.md) 的 patch 装,通道由 profile 那份共享:
129
159
 
130
160
  ```yaml
131
- - id: context-scope
161
+ - id: context-assembler-scope
132
162
  name: "@morlay/dsh-context-assembler/scope"
133
- config:
134
- allowTools: [ask_user_question, web_search, web_fetch]
135
- instructions: false
136
- runtimeContext: false
137
163
  ```
138
164
 
139
165
  几点硬要求:
@@ -141,9 +167,10 @@ scope 抑制)。它解决的是"preset 只能决定加什么、管不了 host
141
167
  - **每个能力仍是独立的 cordis 插件**(组装出口只是 `ctx.plugin()` 装它们),各自带自己的 `inject`;合成单入口会让
142
168
  `inject` 变并集,一个可选搭档缺席就拖垮整包。
143
169
  - **通道不做隔离**:消费者跨包(`@morlay/dsh-agent-toolkit` 的工具说明、本包的技能目录),隔离会把它们挡在组外
144
- (行停在 waiting);模式差异改由 `context-scope` 登记给通道,见
170
+ (行停在 waiting);模式差异改由 `context-assembler-scope` 登记给通道,见
145
171
  [ADR 通道作为全局服务装配不隔离](./.agents/adrs/20260923-通道作为全局服务装配不隔离.md)。真装配的判据是
146
- `pnpm exec tsx packages/desktop/dsh-desktop-host/tool/verify-preset-isolation.mts`(装配层可见一份、模式里没有第二份)。
172
+ `pnpm exec tsx packages/desktop/dsh-desktop-host/tool/verify-session-mode.mts`(装配层可见一份、收口行装上、
173
+ 官方 agent preset 不在)。
147
174
  - **子出口仍可单独装配**(`@morlay/dsh-context-assembler/assembler` 等):同一些插件,只是默认走组装出口。
148
175
 
149
176
  ## 维护注意
@@ -121,6 +121,40 @@ async function baselineIdentity(cwd) {
121
121
  });
122
122
  }
123
123
  //#endregion
124
+ //#region src/agent-instructions/preset-owner.ts
125
+ /** 上游那一行的模块名:与 `presets/*.patch.yml` 里写的一字不差。 */
126
+ const UPSTREAM_MODULE = "@deepseek-ai/dsh-agent-instructions";
127
+ /** 会话 → 它当前的 preset。preset 变了(空白窗口里换 preset)就整份重算。 */
128
+ const decided = /* @__PURE__ */ new WeakMap();
129
+ /**
130
+ * 该会话的 preset 自己提供了工作区指令这一面吗。
131
+ * @param ctx - 能解析到注册表的上下文(能力自己的 apply ctx)。
132
+ * @param agent - 要判的会话。
133
+ * @returns 行清单里有上游那一行且它是启用的。
134
+ */
135
+ function presetOwnsInstructions(ctx, agent) {
136
+ const registry = registryOf(ctx);
137
+ if (registry === void 0) return Promise.resolve(false);
138
+ const preset = registry.composedPreset(agent.ctx);
139
+ if (preset === void 0) return Promise.resolve(false);
140
+ const cached = decided.get(agent);
141
+ if (cached?.preset === preset) return cached.owns;
142
+ const owns = registry.compositionInventory().then((inventory) => inventory.some((composition) => composition.id === preset && composition.rows.some((row) => row.moduleName === UPSTREAM_MODULE && row.enabled === true))).catch(() => false);
143
+ decided.set(agent, {
144
+ preset,
145
+ owns
146
+ });
147
+ return owns;
148
+ }
149
+ /** 注册表可能压根没装(没有 preset 的部署形态);`ctx.get` 在不提供时读到 undefined。 */
150
+ function registryOf(ctx) {
151
+ try {
152
+ return ctx.get("agentPresets");
153
+ } catch {
154
+ return;
155
+ }
156
+ }
157
+ //#endregion
124
158
  //#region src/agent-instructions/index.ts
125
159
  var agent_instructions_exports = /* @__PURE__ */ __exportAll({
126
160
  Config: () => Config$1,
@@ -170,7 +204,8 @@ function apply(ctx, config) {
170
204
  baselineIdentity: identity,
171
205
  changes: []
172
206
  }),
173
- text: (target) => {
207
+ text: async (target) => {
208
+ if (await presetOwnsInstructions(ctx, target)) return "";
174
209
  const current = chains.get(target);
175
210
  if (current === void 0 || !current.files.some((entry) => entry.path === file.path)) return "";
176
211
  return readInstruction(file, cache, maxBytes);
@@ -1,2 +1,2 @@
1
- import { a as name, i as inject, r as apply, t as Config } from "./agent-instructions-BNEiZl0P.mjs";
1
+ import { a as name, i as inject, r as apply, t as Config } from "./agent-instructions-CIgUVIeo.mjs";
2
2
  export { Config, apply, inject, name };
@@ -13,7 +13,7 @@ const REMINDER_CLOSE = "</system-reminder>";
13
13
  const RULES_SECTION = "assembler:rules";
14
14
  /**
15
15
  * 这条消息是不是通道注入的条目——判据是 source 里的幂等键,而不是 kind:接管上游那两面的条目
16
- * 用上游 kind(`agent-instructions` / `skill-catalog`),幂等必须照样认。
16
+ * 接管工作区指令那一面的条目用上游 kind(`agent-instructions`),幂等必须照样认。
17
17
  */
18
18
  function promptEntryIdOf(source) {
19
19
  const id = source.id;
@@ -53,7 +53,7 @@ function latestReminderText(agent, id) {
53
53
  }
54
54
  /**
55
55
  * 注入一条提醒:`text` 是已渲染好的完整正文(规则块或内容块),`key` 是它的幂等键。
56
- * `source` 声明这条消息对外的身份——接管上游那两面用上游 kind,我们自己的条目留默认。
56
+ * `source` 声明这条消息对外的身份——工作区指令那一面用上游 kind,我们自己的条目留默认。
57
57
  */
58
58
  function reminderMessage(key, text, source) {
59
59
  return createUserMessage({
@@ -89,7 +89,10 @@ var ContextAssembler = class extends Service {
89
89
  suppressed = /* @__PURE__ */ new Set();
90
90
  skills = /* @__PURE__ */ new Map();
91
91
  states = /* @__PURE__ */ new WeakMap();
92
- /** 不要 instruction 类注入(规则块)的会话;内容块(技能正文、引用材料)不受它管。 */
92
+ /**
93
+ * 不要 instruction 类注入的会话(模式说了 `instructions: false`):常驻正文、规则块与降级 section 都不进。
94
+ * 按需 skill 不受它管(那是模型自己加载),引用材料也不经通道。
95
+ */
93
96
  withoutInstructions = /* @__PURE__ */ new WeakSet();
94
97
  /** 本会话的模式收窄(白名单):投影层的过滤不碰注册表,注册表上看不出"谁能用"。 */
95
98
  toolScopes = /* @__PURE__ */ new WeakMap();
@@ -137,7 +140,7 @@ var ContextAssembler = class extends Service {
137
140
  * 本步要注入的条目:键 → 已渲染好的正文(规则块或内容块)。
138
141
  * `sections` 来自本步装配,`rules` 与 `auto` skill 正文在这里现算。
139
142
  */
140
- /** 这个会话不要 instruction 类的规则块(模式说了 `instructions: false`)。 */
143
+ /** 这个会话要不要 instruction 类注入(模式说了 `instructions: false` 就是不要,见 {@link withoutInstructions})。 */
141
144
  setInstructions(agent, on) {
142
145
  if (on) this.withoutInstructions.delete(agent);
143
146
  else this.withoutInstructions.add(agent);
@@ -179,14 +182,23 @@ var ContextAssembler = class extends Service {
179
182
  contentFor(name, agent) {
180
183
  return this.declarations.get(name)?.content(agent);
181
184
  }
185
+ /** 一个 skill 在这个会话能不能被用到:声明了 `requires` 时至少一个工具在模式收窄内。 */
186
+ reachable(declaration, scope) {
187
+ const requires = declaration.requires;
188
+ if (requires === void 0 || requires.length === 0) return true;
189
+ if (scope === void 0) return true;
190
+ return requires.some((tool) => scope(tool));
191
+ }
182
192
  async collect(agent) {
183
193
  const entries = /* @__PURE__ */ new Map();
184
- for (const declaration of this.declarations.values()) {
194
+ const scope = this.toolScopes.get(agent);
195
+ const instructionsOff = this.withoutInstructions.has(agent);
196
+ if (!instructionsOff) for (const declaration of this.declarations.values()) {
185
197
  if ((declaration.injection ?? "on-demand") !== "auto") continue;
198
+ if (!this.reachable(declaration, scope)) continue;
186
199
  const body = declaration.content(agent);
187
200
  if (body.length > 0) entries.set(declaration.name, { text: renderVirtualSkill(declaration.name, body) });
188
201
  }
189
- const instructionsOff = this.withoutInstructions.has(agent);
190
202
  for (const declaration of this.rules.values()) {
191
203
  if (instructionsOff) break;
192
204
  const text = await withTimeout(declaration.text(agent), CONTENT_TIMEOUT_MS);
@@ -1,2 +1,2 @@
1
- import { a as name, c as InjectionMode, d as PromptSkillDeclaration, f as DEFAULT_KEEP, i as inject, l as PromptEntry, m as DEFAULT_SUPPRESS, n as apply, o as renderVirtualSkill, p as DEFAULT_REPLACE, s as ContextAssembler, t as Config, u as PromptRuleDeclaration } from "./index-BGkeEaQA.mjs";
1
+ import { a as name, c as InjectionMode, d as PromptSkillDeclaration, f as DEFAULT_KEEP, i as inject, l as PromptEntry, m as DEFAULT_SUPPRESS, n as apply, o as renderVirtualSkill, p as DEFAULT_REPLACE, s as ContextAssembler, t as Config, u as PromptRuleDeclaration } from "./index-zhmD45A9.mjs";
2
2
  export { Config, ContextAssembler, DEFAULT_KEEP, DEFAULT_REPLACE, DEFAULT_SUPPRESS, type InjectionMode, type PromptEntry, type PromptRuleDeclaration, type PromptSkillDeclaration, apply, inject, name, renderVirtualSkill };
@@ -1,2 +1,2 @@
1
- import { a as name, c as DEFAULT_SUPPRESS, i as inject, l as ContextAssembler, n as apply, o as DEFAULT_KEEP, s as DEFAULT_REPLACE, t as Config, u as renderVirtualSkill } from "./assembler-_F9urbPj.mjs";
1
+ import { a as name, c as DEFAULT_SUPPRESS, i as inject, l as ContextAssembler, n as apply, o as DEFAULT_KEEP, s as DEFAULT_REPLACE, t as Config, u as renderVirtualSkill } from "./assembler-CDMIBAi8.mjs";
2
2
  export { Config, ContextAssembler, DEFAULT_KEEP, DEFAULT_REPLACE, DEFAULT_SUPPRESS, apply, inject, name, renderVirtualSkill };
@@ -68,7 +68,7 @@ interface PromptRuleDeclaration {
68
68
  readonly id: string;
69
69
  readonly text: (agent: Agent) => string | Promise<string>;
70
70
  /**
71
- * 这条规则的对外身份。接管上游那两面(工作区指令 / skill 目录)按上游 kind 发消息,好让按 kind 认领的
71
+ * 这条规则的对外身份。工作区指令那一面按上游 kind 发消息(skill 面相反,用我们自己的 kind),好让按 kind 认领的
72
72
  * 消费方(客户端标签、上游的实验性约束收集)认得出来;不声明就是通道自己的 `context-assembler`。
73
73
  * 幂等键仍是 `id`(`reminderMessage` 会把它塞进 source)。
74
74
  */
@@ -96,7 +96,10 @@ declare class ContextAssembler extends Service {
96
96
  private readonly suppressed;
97
97
  private readonly skills;
98
98
  private readonly states;
99
- /** 不要 instruction 类注入(规则块)的会话;内容块(技能正文、引用材料)不受它管。 */
99
+ /**
100
+ * 不要 instruction 类注入的会话(模式说了 `instructions: false`):常驻正文、规则块与降级 section 都不进。
101
+ * 按需 skill 不受它管(那是模型自己加载),引用材料也不经通道。
102
+ */
100
103
  private readonly withoutInstructions;
101
104
  /** 本会话的模式收窄(白名单):投影层的过滤不碰注册表,注册表上看不出"谁能用"。 */
102
105
  private readonly toolScopes;
@@ -113,7 +116,7 @@ declare class ContextAssembler extends Service {
113
116
  * 本步要注入的条目:键 → 已渲染好的正文(规则块或内容块)。
114
117
  * `sections` 来自本步装配,`rules` 与 `auto` skill 正文在这里现算。
115
118
  */
116
- /** 这个会话不要 instruction 类的规则块(模式说了 `instructions: false`)。 */
119
+ /** 这个会话要不要 instruction 类注入(模式说了 `instructions: false` 就是不要,见 {@link withoutInstructions})。 */
117
120
  setInstructions(agent: Agent, on: boolean): void;
118
121
  /**
119
122
  * 本会话的**工具收窄**(模式白名单):投影层只过滤装配结果、不碰工具注册表,所以"谁能用"这件事
@@ -137,6 +140,8 @@ declare class ContextAssembler extends Service {
137
140
  * 路径(接管的 `skill` 工具)要用这里重新算一次,才和 `auto` 正文一样跟着工具走。
138
141
  */
139
142
  contentFor(name: string, agent: Agent | undefined): string | undefined;
143
+ /** 一个 skill 在这个会话能不能被用到:声明了 `requires` 时至少一个工具在模式收窄内。 */
144
+ private reachable;
140
145
  collect(agent: Agent): Promise<Map<string, PromptEntry>>;
141
146
  private sectionsOf;
142
147
  /**
@@ -159,7 +164,19 @@ declare module "@deepseek-ai/cordis" {
159
164
  /** 通道自己的条目形态(注入方没声明 source 时的默认):规则块,幂等键是 id。 */
160
165
  interface PromptReminderSource {
161
166
  kind: "context-assembler";
162
- form: "instructions";
167
+ /**
168
+ * 条目形态:`instructions` 是规则块;`catalog` 是"带清单的目录"(客户端按 `entries` 列条目,
169
+ * 而不是读正文)。
170
+ */
171
+ form: "instructions" | "catalog";
172
+ /**
173
+ * `catalog` 形态发布的那份清单。**只有我们自己发布的目录**用它:上游 `tool-skill` 的账本扫法
174
+ * 只认它自己的 `kind`,所以这里的 kind 必须是我们自己的(见 [`skill-catalog`](../skill-catalog/index.ts))。
175
+ */
176
+ entries?: readonly {
177
+ readonly name: string;
178
+ readonly description: string;
179
+ }[];
163
180
  /** 条目 id:同 id 的最新一条取代更早的同 id 条目。本插件早先落库的消息没有它。 */
164
181
  id?: string;
165
182
  }
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { t as index_d_exports } from "./agent-instructions.mjs";
2
- import { r as index_d_exports$1 } from "./index-BGkeEaQA.mjs";
2
+ import { r as index_d_exports$1 } from "./index-zhmD45A9.mjs";
3
3
  import { t as index_d_exports$2 } from "./scope.mjs";
4
4
  import { t as index_d_exports$3 } from "./skill-catalog.mjs";
5
5
  import z from "@deepseek-ai/schemastery";
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
- import { n as agent_instructions_exports } from "./agent-instructions-BNEiZl0P.mjs";
2
- import { r as assembler_exports } from "./assembler-_F9urbPj.mjs";
3
- import { a as scope_exports } from "./scope-CI-fNq5A.mjs";
1
+ import { n as agent_instructions_exports } from "./agent-instructions-CIgUVIeo.mjs";
2
+ import { r as assembler_exports } from "./assembler-CDMIBAi8.mjs";
3
+ import { t as scope_exports } from "./scope.mjs";
4
4
  import { t as skill_catalog_exports } from "./skill-catalog.mjs";
5
5
  import z from "@deepseek-ai/schemastery";
6
6
  //#region src/index.ts
package/dist/rows.d.mts CHANGED
@@ -6,8 +6,9 @@
6
6
  * (`@morlay/dsh-agent-toolkit/guidance`,在别的包里)与 skill 目录这类消费者都在别的行上
7
7
  * `inject` 它,隔离会把它们挡在组外(行停在 waiting,不报错)。
8
8
  *
9
- * preset 只装 `scopeRow()`(模式的开关):它 `inject` 通道,`ctx.get("contextAssembler")` 在 agent
10
- * 子树里可达,不需要另装一份通道。
9
+ * 本包自己的 bundle 只装通道(`PATCH_ROWS`)。模式的收口是另一行(`scopeRow()`),它由
10
+ * [`@morlay/dsh-session-mode`](../../../profile/dsh-session-mode/README.md) 的 patch 与模式定义一起装——
11
+ * 那一行的 config 就是模式清单。
11
12
  */
12
13
  /** 组装出口(包根)就是那个"按 config 装能力"的插件。 */
13
14
  declare const CONTEXT_PACKAGE = "@morlay/dsh-context-assembler";
@@ -23,18 +24,19 @@ interface ContextRow {
23
24
  */
24
25
  declare function contextChannel(config?: Readonly<Record<string, unknown>>): ContextRow;
25
26
  /**
26
- * 模式开关那一行:`scope` 出口按 preset 配(工具白名单、instruction 总开关、动态快照开关)。
27
+ * 模式收口那一行:`scope` 出口按 `ctx.sessionModes` 的模式定义收口(工具白名单、instruction 总开关、
28
+ * 动态快照开关)。
27
29
  *
28
- * 通道在 profile 平面装一次,这一行只按模式收口(它 `inject` `contextAssembler`,
29
- * `ctx.get("contextAssembler")` 在 agent 子树里可达)。
30
+ * 行 id 用全名 `context-assembler-scope`:它的搭档是同一份装配里的 `context-assembler`(通道),两个 id 放在
31
+ * 一起才看得出这一行属于哪个包、做什么——`context-scope` 那种短名会被误当成 `ctx.contextScope` 之类的服务。
32
+ *
33
+ * 行本身**不带 config**:装哪些模式、每个模式收什么,都由 `session-mode` 行的 `config.modes` 决定,
34
+ * 这一行只把它落到会话上(`inject` `sessionModes`,由
35
+ * [`@morlay/dsh-session-mode`](../../../profile/dsh-session-mode/README.md) 的 patch 插一行)。
30
36
  */
31
- declare function scopeRow(config?: Readonly<Record<string, unknown>>): {
32
- readonly id: string;
33
- readonly name: string;
34
- readonly config?: Readonly<Record<string, unknown>>;
35
- };
37
+ declare function scopeRow(): ContextRow;
36
38
  /**
37
- * bundle patch 的行清单:通道一行(`scope` 是模式的开关,由 preset 装 `scopeRow()`)。
39
+ * bundle patch 的行清单:只有通道一行(模式收口那一行归 `session-mode` 的 patch 装)。
38
40
  *
39
41
  * 形态是 `insert`:这些行由本包提供给 host 平面。写成 `- id: x` 那种"改已有行"的形态时,装配期找不到
40
42
  * 目标行(只 warn 后跳过),通道根本没装上,引用它的行停在 waiting。
package/dist/rows.mjs CHANGED
@@ -6,8 +6,9 @@
6
6
  * (`@morlay/dsh-agent-toolkit/guidance`,在别的包里)与 skill 目录这类消费者都在别的行上
7
7
  * `inject` 它,隔离会把它们挡在组外(行停在 waiting,不报错)。
8
8
  *
9
- * preset 只装 `scopeRow()`(模式的开关):它 `inject` 通道,`ctx.get("contextAssembler")` 在 agent
10
- * 子树里可达,不需要另装一份通道。
9
+ * 本包自己的 bundle 只装通道(`PATCH_ROWS`)。模式的收口是另一行(`scopeRow()`),它由
10
+ * [`@morlay/dsh-session-mode`](../../../profile/dsh-session-mode/README.md) 的 patch 与模式定义一起装——
11
+ * 那一行的 config 就是模式清单。
11
12
  */
12
13
  /** 组装出口(包根)就是那个"按 config 装能力"的插件。 */
13
14
  const CONTEXT_PACKAGE = "@morlay/dsh-context-assembler";
@@ -23,20 +24,24 @@ function contextChannel(config) {
23
24
  };
24
25
  }
25
26
  /**
26
- * 模式开关那一行:`scope` 出口按 preset 配(工具白名单、instruction 总开关、动态快照开关)。
27
+ * 模式收口那一行:`scope` 出口按 `ctx.sessionModes` 的模式定义收口(工具白名单、instruction 总开关、
28
+ * 动态快照开关)。
27
29
  *
28
- * 通道在 profile 平面装一次,这一行只按模式收口(它 `inject` `contextAssembler`,
29
- * `ctx.get("contextAssembler")` 在 agent 子树里可达)。
30
+ * 行 id 用全名 `context-assembler-scope`:它的搭档是同一份装配里的 `context-assembler`(通道),两个 id 放在
31
+ * 一起才看得出这一行属于哪个包、做什么——`context-scope` 那种短名会被误当成 `ctx.contextScope` 之类的服务。
32
+ *
33
+ * 行本身**不带 config**:装哪些模式、每个模式收什么,都由 `session-mode` 行的 `config.modes` 决定,
34
+ * 这一行只把它落到会话上(`inject` `sessionModes`,由
35
+ * [`@morlay/dsh-session-mode`](../../../profile/dsh-session-mode/README.md) 的 patch 插一行)。
30
36
  */
31
- function scopeRow(config) {
37
+ function scopeRow() {
32
38
  return {
33
- id: "context-scope",
34
- name: "@morlay/dsh-context-assembler/scope",
35
- ...config === void 0 ? {} : { config }
39
+ id: "context-assembler-scope",
40
+ name: "@morlay/dsh-context-assembler/scope"
36
41
  };
37
42
  }
38
43
  /**
39
- * bundle patch 的行清单:通道一行(`scope` 是模式的开关,由 preset 装 `scopeRow()`)。
44
+ * bundle patch 的行清单:只有通道一行(模式收口那一行归 `session-mode` 的 patch 装)。
40
45
  *
41
46
  * 形态是 `insert`:这些行由本包提供给 host 平面。写成 `- id: x` 那种"改已有行"的形态时,装配期找不到
42
47
  * 目标行(只 warn 后跳过),通道根本没装上,引用它的行停在 waiting。
package/dist/scope.d.mts CHANGED
@@ -1,47 +1,41 @@
1
1
  import z from "@deepseek-ai/schemastery";
2
- import { Context } from "@deepseek-ai/cordis";
2
+ import { Context, Service } from "@deepseek-ai/cordis";
3
+ import { Agent } from "@deepseek-ai/dsh-agent";
3
4
  declare namespace index_d_exports {
4
- export { Config, apply, inject, name };
5
+ export { Config, SessionToolScope, SessionToolScopeMode, apply, inject, name };
5
6
  }
6
- declare const name = "context-scope";
7
+ declare const name = "context-assembler-scope";
8
+ /** 收口要的服务;`sessionToolScope` 由本行自己发布,`tools` 的 guard 挂在每个 agent 的 ctx 上。 */
7
9
  declare const inject: string[];
8
- interface Config {
9
- /**
10
- * 只保留这些工具;其余(含 host 层 bundle 加进来的)既不进模型目录,调用不了,它们自己注册的说明
11
- * section(`tool:<工具名>`)也不留在提示词里。
12
- */
13
- allowTools?: string[];
14
- /**
15
- * 是否要 instruction 类的注入(工作区指令、技能目录、用法正文那类规则块)。缺省要;
16
- * `false` 表示这个模式不要任何 instructions——对话模式就是它。
17
- *
18
- * 关的是**规则块与降级 section**(通道的 `setInstructions`);内容块(`auto` 组正文、引用材料)
19
- * 不受这个开关管,要靠对应的注入方自己不注册(chat 同时给了 `groups: false`)。
20
- * 注意这与工具白名单是两件事:工具没给,跟着它的注入本来就该自己关(见各注入包对 requires 的判断);
21
- * 这里是"连与工具无关的 instruction 也不要"的总开关。
22
- */
23
- instructions?: boolean;
10
+ /** 本行不吃 config:要收成什么样由消费方在 `apply` 时给。 */
11
+ declare const Config: z<Schemastery.ObjectS<{}>, Schemastery.ObjectT<{}>, "plain">;
12
+ /** 一次收口的全部输入:模式定义里本出口用到的那几项。 */
13
+ interface SessionToolScopeMode {
14
+ /** 拒绝对话时用的模式名。 */
15
+ readonly name: string;
16
+ /** 这个会话能用的工具;其余既不进目录,调用也被拒。 */
17
+ readonly allowTools: readonly string[];
18
+ /** 缺省为要;`false` 表示这个会话不要任何 instruction 类注入。 */
19
+ readonly instructions?: boolean | undefined;
20
+ /** 缺省为要;`false` 表示这个会话不要动态快照。 */
21
+ readonly runtimeContext?: boolean | undefined;
22
+ }
23
+ declare module "@deepseek-ai/cordis" {
24
+ interface Context {
25
+ sessionToolScope: SessionToolScope;
26
+ }
27
+ }
28
+ /** 按会话收口工具与注入的服务。 */
29
+ declare class SessionToolScope extends Service {
30
+ private readonly applied;
31
+ constructor(ctx: Context);
24
32
  /**
25
- * 是否要 runtime context(动态快照:文件沙箱策略、审批策略)。缺省要;`false` 表示这个模式不要它们
26
- * ——对话模式没有文件与 shell 工具,"能改工作区哪些文件、要不要走审批"对它全是噪音。
27
- *
28
- * 抑制是**按 scope** 的(上游按装配的 scope 链查抑制器),所以本行装在 preset 子树里就只作用于这个
29
- * 模式的会话;它只挡注入,不改任何提供方的行为(沙箱该怎么判还怎么判)。
33
+ * 把某个会话收口到这份定义上(同一个 agent 再调就是换一份)。
34
+ * @param agent - 目标会话的 agent。
35
+ * @param mode - 收口的全部输入。
30
36
  */
31
- runtimeContext?: boolean;
37
+ apply(agent: Agent, mode: SessionToolScopeMode): void;
32
38
  }
33
- declare const Config: z<Config>;
34
- /**
35
- * 把某个 preset 的工具收成"只有这些"。
36
- *
37
- * 为什么需要它:preset composition 只能决定"加什么",管不了 host 层——`dsh.profile.bundles` 打开的
38
- * bundle(例如实验性的 Agent Teams)会在 host 层插工具行与 section,对所有 preset 一视同仁。模式想
39
- * 表达"我只有这几个工具、一条提示词都不要"时,唯一与来源无关的做法是收口"有什么"。
40
- *
41
- * 为什么不是 `tools.restrict()`:它会改动可见工具集,从而触发 `tools/change`;上游 `tool-subagent`
42
- * 正是用该事件做 composition reconcile——两边互相触发会变成装配风暴。这里用投影过滤加执行层 guard:
43
- * 既不碰注册表、也不发事件。
44
- */
45
- declare function apply(ctx: Context, config: Config): void;
39
+ declare function apply(ctx: Context): void;
46
40
  //#endregion
47
- export { Config, apply, inject, name, index_d_exports as t };
41
+ export { Config, SessionToolScope, SessionToolScopeMode, apply, inject, name, index_d_exports as t };