@morlay/dsh-context-assembler 0.0.1 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,2 +0,0 @@
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
- export { Config, ContextAssembler, DEFAULT_KEEP, DEFAULT_REPLACE, DEFAULT_SUPPRESS, type InjectionMode, type PromptEntry, type PromptRuleDeclaration, type PromptSkillDeclaration, apply, inject, name, renderVirtualSkill };
@@ -1,2 +0,0 @@
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
- export { Config, ContextAssembler, DEFAULT_KEEP, DEFAULT_REPLACE, DEFAULT_SUPPRESS, apply, inject, name, renderVirtualSkill };
@@ -1,213 +0,0 @@
1
- import z from "@deepseek-ai/schemastery";
2
- import { Context, Service } from "@deepseek-ai/cordis";
3
- import { MessageSource } from "@deepseek-ai/dsh-llm";
4
- import { Agent } from "@deepseek-ai/dsh-agent";
5
- import "@deepseek-ai/dsh-session";
6
- //#region src/assembler/defaults.d.ts
7
- /**
8
- * 留在系统提示词里的 section:部署级 persona(子 agent 在 agent 作用域注册的同名 persona 也命中)
9
- * 与覆盖规则声明([`rules.ts`](./rules.ts))。
10
- */
11
- declare const DEFAULT_KEEP: readonly string[];
12
- /**
13
- * 不进提示词的 section。三类:
14
- *
15
- * 1. **harness 自带的开场白与平台运维说明**:`harness:identity`("You are an AI agent powered by
16
- * DeepSeek Harness."——身份由各模式的 persona 给,这句话在本部署里是噪音,且是英文)、
17
- * `harness:source`(讲 dev server / HMR / 本地 checkout 位置);
18
- * 2. 本部署不装配的工具的用法说明:`tool:lsp` / `tool:pty` / `tool:session-query` / `tool:cordis` /
19
- * `tool:ralph`;
20
- * 3. `app:web-surface`(平台外壳说明)。
21
- *
22
- * 其余工具说明由各组的 `drops` 摘掉、要点由 toolkit 的 `guidance` 出口写进组正文,不在这一份里。
23
- */
24
- declare const DEFAULT_SUPPRESS: readonly string[];
25
- /**
26
- * 改写成中文的两段上游说明(内容忠实原意):`@` 引用路径的语义、输出里的文件链接规范。
27
- *
28
- * 这两段注册在 agent 层(`context:file-reference`)与 host 层(`ui:deliverable-file-references`),
29
- * 注册层不归我们——所以在装配结果上换文本(不移除:内容要保留)。
30
- */
31
- declare const DEFAULT_REPLACE: Readonly<Record<string, string>>;
32
- //#endregion
33
- //#region src/assembler/channel.d.ts
34
- /** 正文到达模型的方式:自动注入,或等模型按需加载。 */
35
- type InjectionMode = "auto" | "on-demand";
36
- /**
37
- * 一条按 skill 形态声明的注入:正文是本仓库维护的文案。
38
- *
39
- * 正文与 agent 无关(工具集在装配期就定了),所以 skill 在装配期注册一次、全局可见。
40
- */
41
- interface PromptSkillDeclaration {
42
- /** kebab-case:skill 名,也是注入条目的 id。 */
43
- readonly name: string;
44
- readonly title: string;
45
- /** 目录行摘要:讲清什么时候该加载它。 */
46
- readonly description: string;
47
- /**
48
- * 正文按**会话**生成:模式可能只给一部分工具,正文要跟着修剪(不该讲用不到的工具)。
49
- * 不传 agent(装配期注册 skill 时)返回不过滤的完整版。
50
- */
51
- readonly content: (agent?: Agent) => string;
52
- /**
53
- * 这个 skill 赖以成立的**入口工具**(任一存在即可,不是全部)。都不可见时它不该出现在该会话的技能
54
- * 目录里——"不能用 skill 工具就没有技能目录"是同一个道理:技能也跟着工具走。
55
- *
56
- * 缺省语义就是"一个都不在就藏起来";声明方要自己挑入口,别把组内所有可能的工具都倒进来
57
- * (子代理控制行与团队插件会提供同名工具,全集当判据会把不该出现的 skill 留下)。
58
- */
59
- readonly requires?: readonly string[];
60
- /** 缺省 `on-demand`:只有正文确有必要常驻时才写 `auto`。 */
61
- readonly injection?: InjectionMode;
62
- }
63
- /**
64
- * 一条规则块声明:系统级信息(工作区指令、skill 目录),文本按 id 幂等注入、同 id 覆盖。
65
- * 文本可以是异步的(skill 目录要读 skill 注册表)。
66
- */
67
- interface PromptRuleDeclaration {
68
- readonly id: string;
69
- readonly text: (agent: Agent) => string | Promise<string>;
70
- /**
71
- * 这条规则的对外身份。工作区指令那一面按上游 kind 发消息(skill 面相反,用我们自己的 kind),好让按 kind 认领的
72
- * 消费方(客户端标签、上游的实验性约束收集)认得出来;不声明就是通道自己的 `context-assembler`。
73
- * 幂等键仍是 `id`(`reminderMessage` 会把它塞进 source)。
74
- */
75
- readonly source?: (agent: Agent) => MessageSource;
76
- }
77
- /** 一条待注入的条目:正文已渲染好,`source` 是它声明给外部的身份。 */
78
- interface PromptEntry {
79
- readonly text: string;
80
- readonly source?: MessageSource;
81
- }
82
- /**
83
- * 注入通道:调用方声明「什么内容、怎么到达模型」,这里负责到达方式。
84
- *
85
- * - `on-demand`:注册成模型可用 skill(进 skill 目录,正文按需加载);
86
- * - `auto`:正文自动注入 reminder(注册成 skill 但标 `modelInvocable: false`,因为已经常驻);
87
- * - `replaceSection` / `suppressSection`:装配结果上的文本改写与丢弃。
88
- *
89
- * 覆盖语义(同 id 最新取代更早)由系统提示词里的声明说明一次,见 [`rules.ts`](./rules.ts)。
90
- */
91
- declare class ContextAssembler extends Service {
92
- private readonly host;
93
- private readonly declarations;
94
- private readonly rules;
95
- private readonly overrides;
96
- private readonly suppressed;
97
- private readonly skills;
98
- private readonly states;
99
- /**
100
- * 不要 instruction 类注入的会话(模式说了 `instructions: false`):常驻正文、规则块与降级 section 都不进。
101
- * 按需 skill 不受它管(那是模型自己加载),引用材料也不经通道。
102
- */
103
- private readonly withoutInstructions;
104
- /** 本会话的模式收窄(白名单):投影层的过滤不碰注册表,注册表上看不出"谁能用"。 */
105
- private readonly toolScopes;
106
- /** `host` 是构造期那个根 ctx:`skills` 的访问权限挂在插件的 inject 声明上,不能依赖访问者 ctx。 */
107
- constructor(host: Context);
108
- registerSkill(declaration: PromptSkillDeclaration): () => void;
109
- registerRule(declaration: PromptRuleDeclaration): () => void;
110
- replaceSection(name: string, text: (agent: Agent) => string | undefined): () => void;
111
- suppressSection(name: string): () => void;
112
- isSuppressed(name: string): boolean;
113
- /** 装配结果上该 section 要换成的文本;undefined 表示原样保留。 */
114
- replacement(name: string, agent: Agent | undefined): string | undefined;
115
- /**
116
- * 本步要注入的条目:键 → 已渲染好的正文(规则块或内容块)。
117
- * `sections` 来自本步装配,`rules` 与 `auto` skill 正文在这里现算。
118
- */
119
- /** 这个会话要不要 instruction 类注入(模式说了 `instructions: false` 就是不要,见 {@link withoutInstructions})。 */
120
- setInstructions(agent: Agent, on: boolean): void;
121
- /**
122
- * 本会话的**工具收窄**(模式白名单):投影层只过滤装配结果、不碰工具注册表,所以"谁能用"这件事
123
- * 必须在通道上登记,技能目录与组正文才收得住。与 {@link setInstructions} 同形——谓词由模式那一行给
124
- * (`context-scope`),通道自己不碰 `tools` 服务。
125
- */
126
- restrictTools(agent: Agent, allowed: (tool: string) => boolean): void;
127
- /**
128
- * 本会话的工具可见性:调用方给出的注册表可见性 × 已登记的模式收窄。技能目录与组正文共用它,
129
- * 免得同一套判据在几处各写一遍。
130
- */
131
- visibleTools(agent: Agent, registered: (tool: string) => boolean): (tool: string) => boolean;
132
- /**
133
- * 该会话看不到的 skill 名:声明了 `requires` 而依赖的工具一个都不可见。
134
- *
135
- * 目录由 `context-skill-catalog` 渲染,它把工具可见性作为谓词传进来(通道不碰 tools 服务)。
136
- */
137
- hiddenSkills(visible: (tool: string) => boolean): ReadonlySet<string>;
138
- /**
139
- * 该 skill 按会话修剪后的正文:注册表只能存一份(技能注册是装配期一次、全局可见),所以按需加载
140
- * 路径(接管的 `skill` 工具)要用这里重新算一次,才和 `auto` 正文一样跟着工具走。
141
- */
142
- contentFor(name: string, agent: Agent | undefined): string | undefined;
143
- /** 一个 skill 在这个会话能不能被用到:声明了 `requires` 时至少一个工具在模式收窄内。 */
144
- private reachable;
145
- collect(agent: Agent): Promise<Map<string, PromptEntry>>;
146
- private sectionsOf;
147
- /**
148
- * 每步装配调一次:记下本步降级出来的 section 文本,供下一步注入。
149
- */
150
- sync(agent: Agent, input: {
151
- readonly sections: ReadonlyMap<string, string>;
152
- }): void;
153
- /** skill 注册(装配期一次,全局可见):正文没变的不重注册。 */
154
- private refreshSkills;
155
- private stateOf;
156
- }
157
- declare module "@deepseek-ai/cordis" {
158
- interface Context {
159
- contextAssembler: ContextAssembler;
160
- }
161
- }
162
- //#endregion
163
- //#region src/assembler/reminder.d.ts
164
- /** 通道自己的条目形态(注入方没声明 source 时的默认):规则块,幂等键是 id。 */
165
- interface PromptReminderSource {
166
- kind: "context-assembler";
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
- }[];
180
- /** 条目 id:同 id 的最新一条取代更早的同 id 条目。本插件早先落库的消息没有它。 */
181
- id?: string;
182
- }
183
- declare module "@deepseek-ai/dsh-llm" {
184
- interface MessageSourceMap {
185
- "context-assembler": PromptReminderSource;
186
- }
187
- }
188
- /**
189
- * 虚拟 skill(运行时注册、没有资源目录)的正文形态:只有 `<skill_instructions>`,
190
- * 不渲染上游 `renderSkillContent` 的 `<skill_resources>`(那段对虚拟 skill 是噪音)。
191
- */
192
- declare function renderVirtualSkill(name: string, content: string): string;
193
- declare namespace index_d_exports {
194
- export { Config, ContextAssembler, DEFAULT_KEEP, DEFAULT_REPLACE, DEFAULT_SUPPRESS, InjectionMode, PromptEntry, PromptRuleDeclaration, PromptSkillDeclaration, apply, inject, name, renderVirtualSkill };
195
- }
196
- declare const name = "context-assembler";
197
- declare const inject: string[];
198
- interface Config {
199
- /** 留在系统提示词里的 section 名;其余非空 section 降级为 reminder。 */
200
- keep?: string[];
201
- /** 不进提示词的 section 名(部署级噪音)。 */
202
- suppress?: string[];
203
- /** 装配结果上改写的 section 文本(section 名 → 中文文案)。 */
204
- replace?: Record<string, string>;
205
- }
206
- declare const Config: z<Config>;
207
- /**
208
- * 提示词注入的唯一通道:system prompt 里只留 `keep`,其余内容按声明的方式到达模型——
209
- * 降级为紧随用户消息的 reminder、或直接丢弃(写进按需 skill 正文是调用方自己的事)。
210
- */
211
- declare function apply(ctx: Context, config: Config): void;
212
- //#endregion
213
- export { name as a, InjectionMode as c, PromptSkillDeclaration as d, DEFAULT_KEEP as f, inject as i, PromptEntry as l, DEFAULT_SUPPRESS as m, apply as n, renderVirtualSkill as o, DEFAULT_REPLACE as p, index_d_exports as r, ContextAssembler as s, Config as t, PromptRuleDeclaration as u };
@@ -1,13 +0,0 @@
1
- //#region \0rolldown/runtime.js
2
- var __defProp = Object.defineProperty;
3
- var __exportAll = (all, no_symbols) => {
4
- let target = {};
5
- for (var name in all) __defProp(target, name, {
6
- get: all[name],
7
- enumerable: true
8
- });
9
- if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
10
- return target;
11
- };
12
- //#endregion
13
- export { __exportAll as t };
package/dist/scope.d.mts DELETED
@@ -1,41 +0,0 @@
1
- import z from "@deepseek-ai/schemastery";
2
- import { Context, Service } from "@deepseek-ai/cordis";
3
- import { Agent } from "@deepseek-ai/dsh-agent";
4
- declare namespace index_d_exports {
5
- export { Config, SessionToolScope, SessionToolScopeMode, apply, inject, name };
6
- }
7
- declare const name = "context-assembler-scope";
8
- /** 收口要的服务;`sessionToolScope` 由本行自己发布,`tools` 的 guard 挂在每个 agent 的 ctx 上。 */
9
- declare const inject: string[];
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);
32
- /**
33
- * 把某个会话收口到这份定义上(同一个 agent 再调就是换一份)。
34
- * @param agent - 目标会话的 agent。
35
- * @param mode - 收口的全部输入。
36
- */
37
- apply(agent: Agent, mode: SessionToolScopeMode): void;
38
- }
39
- declare function apply(ctx: Context): void;
40
- //#endregion
41
- export { Config, SessionToolScope, SessionToolScopeMode, apply, inject, name, index_d_exports as t };
package/dist/scope.mjs DELETED
@@ -1,102 +0,0 @@
1
- import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
- import z from "@deepseek-ai/schemastery";
3
- import { Service } from "@deepseek-ai/cordis";
4
- //#region src/scope/index.ts
5
- /**
6
- * 按**会话**收口:工具白名单、instruction 总开关、动态快照。
7
- *
8
- * 这是本包的 `scope` 出口(行 id `context-assembler-scope`)。它不再吃 config,也不认识"模式"这个概念——
9
- * 收口要什么由消费方在会话建立时**推**进来:`ctx.sessionToolScope.apply(agent, mode)`(
10
- * [`@morlay/dsh-session-mode`](../../../profile/dsh-session-mode/README.md) 的模式定义里正好是这几项)。
11
- * 方向因此是单向的:装配层读这个服务,这个包不认识装配层。
12
- *
13
- * `apply` 落到该 agent 身上的三件事,各走一条与"谁注册了这个工具"无关的路:
14
- *
15
- * 1. **装配期投影**(每次装配按已注册的那份):模型目录与工具说明的 `tool:<工具名>` section 同源过滤;
16
- * 2. **执行层 guard**:落在 `agent.ctx` 上,调用被拒时给出该模式的文案;
17
- * 3. **通道开关**:`instructions` 关掉该会话的规则块(工作区指令、技能目录、用法正文)、
18
- * `runtimeContext` 抑制该会话的动态快照(沙箱与审批策略那两条)。
19
- *
20
- * 为什么不用 `tools.restrict()`:它会改动可见工具集,从而触发 `tools/change`;上游 `tool-subagent` 正是用
21
- * 该事件做 composition reconcile——两边互相触发会变成装配风暴。这里用投影过滤加执行层 guard:既不碰注册
22
- * 表、也不发事件。
23
- *
24
- * 为什么按 `agent.ctx` 注册而不是先建一棵 preset 子树:会话的 persona / 工具收口本来就是"这个 agent 的
25
- * 事",`agent.ctx` 的 scope 已经够了(上游给子 agent 装 persona 用的也是这条路);一棵子树要连带上游的
26
- * `agent-presets` 注册表、每 revision 一份 Loader 与 `isolate` realm,模式本身用不上。
27
- *
28
- * 模式在空白窗口里可以切换,所以状态是**可替换**的:同一个 agent 再 `apply` 一次就是换一份——旧的
29
- * disposer 全部收回,再按新的装一遍。
30
- */
31
- var scope_exports = /* @__PURE__ */ __exportAll({
32
- Config: () => Config,
33
- SessionToolScope: () => SessionToolScope,
34
- apply: () => apply,
35
- inject: () => inject,
36
- name: () => name
37
- });
38
- const name = "context-assembler-scope";
39
- /** 收口要的服务;`sessionToolScope` 由本行自己发布,`tools` 的 guard 挂在每个 agent 的 ctx 上。 */
40
- const inject = ["tools", "systemPrompt"];
41
- /** 本行不吃 config:要收成什么样由消费方在 `apply` 时给。 */
42
- const Config = z.object({});
43
- const OUT_OF_SCOPE = (toolName, modeName) => `${toolName} 不在「${modeName}」的工具范围内,用它不会有结果;按当前模式提供的工具完成任务,或让用户切到别的模式。`;
44
- /** 单个工具的说明 section 名前缀:`tool:<工具名>`(`tools:` 那类是聚合块,不归白名单管)。 */
45
- const TOOL_SECTION_PREFIX = "tool:";
46
- /** 通道是可选的搭档:没有它就没有 instruction 可关、也没人收窄技能目录,工具目录与执行 guard 照常生效。 */
47
- function channelOf(ctx) {
48
- try {
49
- return ctx.get("contextAssembler");
50
- } catch {
51
- return;
52
- }
53
- }
54
- /** 按会话收口工具与注入的服务。 */
55
- var SessionToolScope = class extends Service {
56
- applied = /* @__PURE__ */ new WeakMap();
57
- constructor(ctx) {
58
- super(ctx, "sessionToolScope");
59
- ctx.on("system-prompt/assemble", async (_assembly, context, next) => {
60
- const agent = context.agent;
61
- const applied = agent === void 0 ? void 0 : this.applied.get(agent);
62
- if (applied === void 0) return next();
63
- const allow = new Set(applied.mode.allowTools);
64
- const result = await next();
65
- return {
66
- ...result,
67
- sections: result.sections.filter((section) => section.name.startsWith(TOOL_SECTION_PREFIX) ? allow.has(section.name.slice(5)) : true),
68
- tools: result.tools.filter((tool) => allow.has(tool.name))
69
- };
70
- });
71
- }
72
- /**
73
- * 把某个会话收口到这份定义上(同一个 agent 再调就是换一份)。
74
- * @param agent - 目标会话的 agent。
75
- * @param mode - 收口的全部输入。
76
- */
77
- apply(agent, mode) {
78
- const previous = this.applied.get(agent);
79
- if (previous !== void 0) previous.dispose();
80
- const allow = new Set(mode.allowTools);
81
- const dispose = agent.ctx.effect(() => {
82
- const stoppers = [];
83
- if (mode.runtimeContext === false) stoppers.push(agent.ctx.systemPrompt.suppressRuntimeContext());
84
- const fiber = agent.ctx.inject(["tools"], (scope) => {
85
- scope.tools.guard((exec) => allow.has(exec.name) ? void 0 : OUT_OF_SCOPE(exec.name, mode.name));
86
- });
87
- return [...stoppers, () => fiber.dispose()];
88
- }, "sessionToolScope: per-session gate");
89
- const channel = channelOf(this.ctx);
90
- channel?.setInstructions(agent, mode.instructions !== false);
91
- channel?.restrictTools(agent, (tool) => allow.has(tool));
92
- this.applied.set(agent, {
93
- mode,
94
- dispose
95
- });
96
- }
97
- };
98
- function apply(ctx) {
99
- new SessionToolScope(ctx);
100
- }
101
- //#endregion
102
- export { Config, SessionToolScope, apply, inject, name, scope_exports as t };
@@ -1,23 +0,0 @@
1
- import { Context } from "@deepseek-ai/cordis";
2
- declare namespace index_d_exports {
3
- export { CATALOG_ID, apply, inject, name };
4
- }
5
- declare const name = "context-skill-catalog";
6
- declare const inject: string[];
7
- declare const CATALOG_ID = "skill-catalog";
8
- /**
9
- * skill 目录 + `skill` 工具:目录是规则块(一行名字 + 摘要),正文按需加载。
10
- *
11
- * 目录只列模型可调用的 skill;`auto` 的 skill 标 `modelInvocable: false`,所以它不出现在目录里
12
- * (它的正文已经随提示送达)。工具的渲染用通道的虚拟 skill 形态,不带 `<skill_resources>`。
13
- *
14
- * **这一面归通道**(工作区指令那一面相反,是让位给 preset):官方 preset 自己装了上游 `tool-skill`,那份
15
- * 注册在 preset 的 scope 层;我们把 `skill` 工具按会话注册进 **agent 自己那一层**——它最靠里,同名注册遮蔽
16
- * 继承来的那一份([`core/tools/src/index.ts:1185-1207`](../../../vendor/deepseek-harness/packages/core/tools/src/index.ts)),
17
- * 于是模型看到的是我们的工具(中文描述 + 按会话修剪的正文)。上游的目录发布判据是"它自己注册的那个工具是
18
- * 本会话可见的那个"([`skill/tool-skill/src/index.ts:213-236`](../../../vendor/deepseek-harness/packages/skill/tool-skill/src/index.ts)),
19
- * 被遮蔽之后它闭嘴,目录由我们发布;`minimal`(`chat`)那种没有上游行的会话里也只有我们这一份。
20
- */
21
- declare function apply(ctx: Context): void;
22
- //#endregion
23
- export { CATALOG_ID, apply, inject, name, index_d_exports as t };
@@ -1,206 +0,0 @@
1
- import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
- import { u as renderVirtualSkill } from "./assembler-CDMIBAi8.mjs";
3
- import { isModelInvocable, isSkillName } from "@deepseek-ai/dsh-skill";
4
- import { defineTool } from "@deepseek-ai/dsh-tools";
5
- //#region src/skill-catalog/index.ts
6
- var skill_catalog_exports = /* @__PURE__ */ __exportAll({
7
- CATALOG_ID: () => CATALOG_ID,
8
- apply: () => apply,
9
- inject: () => inject,
10
- name: () => name
11
- });
12
- const name = "context-skill-catalog";
13
- const inject = [
14
- "agents",
15
- "skills",
16
- "tools",
17
- "contextAssembler"
18
- ];
19
- const CATALOG_ID = "skill-catalog";
20
- /** 目录行里的描述长度上限(上游默认同值)。 */
21
- const DESCRIPTION_MAX_LENGTH = 500;
22
- /**
23
- * skill 目录 + `skill` 工具:目录是规则块(一行名字 + 摘要),正文按需加载。
24
- *
25
- * 目录只列模型可调用的 skill;`auto` 的 skill 标 `modelInvocable: false`,所以它不出现在目录里
26
- * (它的正文已经随提示送达)。工具的渲染用通道的虚拟 skill 形态,不带 `<skill_resources>`。
27
- *
28
- * **这一面归通道**(工作区指令那一面相反,是让位给 preset):官方 preset 自己装了上游 `tool-skill`,那份
29
- * 注册在 preset 的 scope 层;我们把 `skill` 工具按会话注册进 **agent 自己那一层**——它最靠里,同名注册遮蔽
30
- * 继承来的那一份([`core/tools/src/index.ts:1185-1207`](../../../vendor/deepseek-harness/packages/core/tools/src/index.ts)),
31
- * 于是模型看到的是我们的工具(中文描述 + 按会话修剪的正文)。上游的目录发布判据是"它自己注册的那个工具是
32
- * 本会话可见的那个"([`skill/tool-skill/src/index.ts:213-236`](../../../vendor/deepseek-harness/packages/skill/tool-skill/src/index.ts)),
33
- * 被遮蔽之后它闭嘴,目录由我们发布;`minimal`(`chat`)那种没有上游行的会话里也只有我们这一份。
34
- */
35
- function apply(ctx) {
36
- /**
37
- * 本会话上次算出的目录条目:`source` 是同步的、正文是异步的,两者共用这一次计算的结果
38
- * (`collect` 里先算正文再取 source)。
39
- */
40
- const catalogEntries = /* @__PURE__ */ new WeakMap();
41
- const skillTool = ctx.tools.get("skill") === void 0 ? defineSkillTool(ctx) : void 0;
42
- if (skillTool === void 0) ctx.logger.warn("context-skill-catalog: a `skill` tool is already registered; keeping the existing one and only publishing the catalog");
43
- else {
44
- const install = (agent) => {
45
- if (ctx.tools.get("skill", agent) === skillTool) return;
46
- try {
47
- agent.ctx.tools.register(skillTool);
48
- } catch (error) {
49
- ctx.logger.warn(`context-skill-catalog: ${String(error)}`);
50
- }
51
- };
52
- ctx.on("agent/created", ({ agent }) => {
53
- install(agent);
54
- });
55
- for (const agent of ctx.agents.list()) install(agent);
56
- }
57
- ctx.contextAssembler.registerRule({
58
- id: CATALOG_ID,
59
- source: (agent) => ({
60
- kind: "context-assembler",
61
- form: "catalog",
62
- entries: catalogEntries.get(agent) ?? []
63
- }),
64
- text: async (agent) => {
65
- const noCatalog = () => {
66
- catalogEntries.delete(agent);
67
- return "";
68
- };
69
- const visible = ctx.contextAssembler.visibleTools(agent, (tool) => ctx.tools.get(tool, agent) !== void 0);
70
- if (!visible("skill")) return noCatalog();
71
- const snapshot = await ctx.skills.snapshot({
72
- cwd: agent.session.header.cwd,
73
- scope: agent
74
- });
75
- if (!snapshot.complete) return noCatalog();
76
- const hidden = ctx.contextAssembler.hiddenSkills(visible);
77
- const skills = snapshot.skills.filter(isModelInvocable).filter((skill) => !hidden.has(skill.name));
78
- if (skills.length === 0) return noCatalog();
79
- const entries = skills.map((skill) => ({
80
- name: skill.name,
81
- description: clamp(skill.description, DESCRIPTION_MAX_LENGTH)
82
- }));
83
- catalogEntries.set(agent, entries);
84
- return [
85
- "<available_skills>",
86
- ...entries.map((entry) => `- ${entry.name}: ${entry.description}`),
87
- "</available_skills>",
88
- "",
89
- "任务与某个 skill 说明匹配时,先加载它再行事。"
90
- ].join("\n");
91
- }
92
- });
93
- }
94
- /** 一份工具定义,按会话注册进每个 agent 的自己那一层(同一个定义对象,判据里的同一性靠它)。 */
95
- function defineSkillTool(ctx) {
96
- return defineTool({
97
- name: "skill",
98
- description: "按需加载 skill 的完整说明。",
99
- parameters: { name: {
100
- type: "string",
101
- required: true,
102
- description: "技能目录里的 skill 名。"
103
- } },
104
- output: {
105
- schema: {
106
- type: "object",
107
- additionalProperties: false,
108
- properties: {
109
- name: {
110
- type: "string",
111
- required: true
112
- },
113
- provider: {
114
- type: "string",
115
- required: true
116
- },
117
- resourceBase: { oneOf: [
118
- {
119
- type: "object",
120
- additionalProperties: false,
121
- properties: {
122
- kind: {
123
- type: "string",
124
- required: true,
125
- const: "directory"
126
- },
127
- path: {
128
- type: "string",
129
- required: true
130
- }
131
- }
132
- },
133
- {
134
- type: "object",
135
- additionalProperties: false,
136
- properties: {
137
- kind: {
138
- type: "string",
139
- required: true,
140
- const: "url"
141
- },
142
- url: {
143
- type: "string",
144
- required: true
145
- }
146
- }
147
- },
148
- {
149
- type: "object",
150
- additionalProperties: false,
151
- properties: {
152
- kind: {
153
- type: "string",
154
- required: true,
155
- const: "opaque"
156
- },
157
- description: {
158
- type: "string",
159
- required: true
160
- }
161
- }
162
- }
163
- ] },
164
- content: {
165
- type: "string",
166
- required: true
167
- }
168
- }
169
- },
170
- render: (_args, value) => [{
171
- type: "text",
172
- text: renderVirtualSkill(value.name, value.content)
173
- }]
174
- },
175
- async execute(args, exec) {
176
- if (!isSkillName(args.name)) throw new Error(`invalid skill name "${args.name}"`);
177
- const lookup = {
178
- cwd: exec.agent?.session.header.cwd,
179
- signal: exec.signal,
180
- scope: exec.agent
181
- };
182
- const skill = await ctx.skills.get(args.name, lookup);
183
- if (skill === void 0) throw new Error(`skill "${args.name}" is unknown or no longer available`);
184
- if (!isModelInvocable(skill)) throw new Error(`skill "${args.name}" is not available for model invocation`);
185
- const content = ctx.contextAssembler.contentFor(skill.name, exec.agent) ?? skill.content;
186
- return {
187
- name: skill.name,
188
- provider: skill.provider,
189
- content
190
- };
191
- },
192
- presentCall(args) {
193
- return {
194
- card: "generic",
195
- title: `Load skill ${args.name}`,
196
- kind: "read",
197
- rawInput: args.name
198
- };
199
- }
200
- });
201
- }
202
- function clamp(text, max) {
203
- return text.length <= max ? text : `${text.slice(0, max - 1)}…`;
204
- }
205
- //#endregion
206
- export { CATALOG_ID, apply, inject, name, skill_catalog_exports as t };