@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 +59 -32
- package/dist/{agent-instructions-BNEiZl0P.mjs → agent-instructions-CIgUVIeo.mjs} +36 -1
- package/dist/agent-instructions.mjs +1 -1
- package/dist/{assembler-_F9urbPj.mjs → assembler-CDMIBAi8.mjs} +18 -6
- package/dist/assembler.d.mts +1 -1
- package/dist/assembler.mjs +1 -1
- package/dist/{index-BGkeEaQA.d.mts → index-zhmD45A9.d.mts} +21 -4
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +3 -3
- package/dist/rows.d.mts +13 -11
- package/dist/rows.mjs +15 -10
- package/dist/scope.d.mts +33 -39
- package/dist/scope.mjs +102 -2
- package/dist/skill-catalog.d.mts +7 -0
- package/dist/skill-catalog.mjs +29 -7
- package/locale/en.json +1 -1
- package/locale/zh.json +1 -1
- package/package.json +37 -39
- package/src/agent-instructions/index.ts +6 -2
- package/src/agent-instructions/preset-owner.ts +70 -0
- package/src/assembler/channel.ts +34 -10
- package/src/assembler/reminder.ts +12 -3
- package/src/index.ts +5 -5
- package/src/rows.ts +14 -13
- package/src/scope/index.ts +131 -99
- package/src/skill-catalog/index.ts +44 -18
- package/cordis.patch.yml +0 -9
- package/dist/scope-CI-fNq5A.mjs +0 -104
- package/tool/patch.ts +0 -43
package/dist/scope.mjs
CHANGED
|
@@ -1,2 +1,102 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
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 };
|
package/dist/skill-catalog.d.mts
CHANGED
|
@@ -10,6 +10,13 @@ declare const CATALOG_ID = "skill-catalog";
|
|
|
10
10
|
*
|
|
11
11
|
* 目录只列模型可调用的 skill;`auto` 的 skill 标 `modelInvocable: false`,所以它不出现在目录里
|
|
12
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`)那种没有上游行的会话里也只有我们这一份。
|
|
13
20
|
*/
|
|
14
21
|
declare function apply(ctx: Context): void;
|
|
15
22
|
//#endregion
|
package/dist/skill-catalog.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
2
|
-
import { u as renderVirtualSkill } from "./assembler-
|
|
2
|
+
import { u as renderVirtualSkill } from "./assembler-CDMIBAi8.mjs";
|
|
3
3
|
import { isModelInvocable, isSkillName } from "@deepseek-ai/dsh-skill";
|
|
4
4
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
5
5
|
//#region src/skill-catalog/index.ts
|
|
@@ -11,6 +11,7 @@ var skill_catalog_exports = /* @__PURE__ */ __exportAll({
|
|
|
11
11
|
});
|
|
12
12
|
const name = "context-skill-catalog";
|
|
13
13
|
const inject = [
|
|
14
|
+
"agents",
|
|
14
15
|
"skills",
|
|
15
16
|
"tools",
|
|
16
17
|
"contextAssembler"
|
|
@@ -23,6 +24,13 @@ const DESCRIPTION_MAX_LENGTH = 500;
|
|
|
23
24
|
*
|
|
24
25
|
* 目录只列模型可调用的 skill;`auto` 的 skill 标 `modelInvocable: false`,所以它不出现在目录里
|
|
25
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`)那种没有上游行的会话里也只有我们这一份。
|
|
26
34
|
*/
|
|
27
35
|
function apply(ctx) {
|
|
28
36
|
/**
|
|
@@ -30,10 +38,26 @@ function apply(ctx) {
|
|
|
30
38
|
* (`collect` 里先算正文再取 source)。
|
|
31
39
|
*/
|
|
32
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
|
+
}
|
|
33
57
|
ctx.contextAssembler.registerRule({
|
|
34
58
|
id: CATALOG_ID,
|
|
35
59
|
source: (agent) => ({
|
|
36
|
-
kind: "
|
|
60
|
+
kind: "context-assembler",
|
|
37
61
|
form: "catalog",
|
|
38
62
|
entries: catalogEntries.get(agent) ?? []
|
|
39
63
|
}),
|
|
@@ -66,11 +90,10 @@ function apply(ctx) {
|
|
|
66
90
|
].join("\n");
|
|
67
91
|
}
|
|
68
92
|
});
|
|
69
|
-
if (ctx.tools.get("skill") !== void 0) ctx.logger.warn("context-skill-catalog: a `skill` tool is already registered; keeping the existing one and only publishing the catalog");
|
|
70
|
-
else registerSkillTool(ctx);
|
|
71
93
|
}
|
|
72
|
-
|
|
73
|
-
|
|
94
|
+
/** 一份工具定义,按会话注册进每个 agent 的自己那一层(同一个定义对象,判据里的同一性靠它)。 */
|
|
95
|
+
function defineSkillTool(ctx) {
|
|
96
|
+
return defineTool({
|
|
74
97
|
name: "skill",
|
|
75
98
|
description: "按需加载 skill 的完整说明。",
|
|
76
99
|
parameters: { name: {
|
|
@@ -175,7 +198,6 @@ function registerSkillTool(ctx) {
|
|
|
175
198
|
};
|
|
176
199
|
}
|
|
177
200
|
});
|
|
178
|
-
ctx.tools.register(skillTool);
|
|
179
201
|
}
|
|
180
202
|
function clamp(text, max) {
|
|
181
203
|
return text.length <= max ? text : `${text.slice(0, max - 1)}…`;
|
package/locale/en.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"meta": {
|
|
3
3
|
"title": "Context Assembler",
|
|
4
|
-
"description": "Prompt-injection capability set: the single injection channel plus workspace instructions, skill catalog
|
|
4
|
+
"description": "Prompt-injection capability set: the single injection channel plus workspace instructions, skill catalog and the scope gate that narrows tools per session mode."
|
|
5
5
|
}
|
|
6
6
|
}
|
package/locale/zh.json
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@morlay/dsh-context-assembler",
|
|
3
|
-
"version": "0.0.1-alpha.
|
|
4
|
-
"description": "Prompt-injection capability set for dsh: the single channel (context assembler) plus the injectors it carries — workspace instructions, skill catalog
|
|
3
|
+
"version": "0.0.1-alpha.4",
|
|
4
|
+
"description": "Prompt-injection capability set for dsh: the single channel (context assembler) plus the injectors it carries — workspace instructions, skill catalog — plus the scope gate that narrows tools per session mode. Reference expansion is its own package (@morlay/dsh-reference).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"context",
|
|
7
7
|
"dsh",
|
|
@@ -19,65 +19,63 @@
|
|
|
19
19
|
"dist",
|
|
20
20
|
"src",
|
|
21
21
|
"tool",
|
|
22
|
-
"cordis.patch.yml",
|
|
23
22
|
"!**/__tests__"
|
|
24
23
|
],
|
|
25
24
|
"type": "module",
|
|
26
25
|
"exports": {
|
|
27
26
|
".": "./dist/index.mjs",
|
|
27
|
+
"./rows": "./dist/rows.mjs",
|
|
28
28
|
"./assembler": "./dist/assembler.mjs",
|
|
29
29
|
"./agent-instructions": "./dist/agent-instructions.mjs",
|
|
30
30
|
"./skill-catalog": "./dist/skill-catalog.mjs",
|
|
31
31
|
"./scope": "./dist/scope.mjs",
|
|
32
32
|
"./package.json": "./package.json",
|
|
33
|
-
"./rows": "./dist/rows.mjs",
|
|
34
|
-
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
35
33
|
"./locale/*.json": "./locale/*.json"
|
|
36
34
|
},
|
|
37
35
|
"dependencies": {
|
|
38
|
-
"@deepseek-ai/schemastery": "3.18.4"
|
|
36
|
+
"@deepseek-ai/schemastery": "^3.18.4"
|
|
39
37
|
},
|
|
40
38
|
"devDependencies": {
|
|
41
39
|
"@deepseek-ai/cordis": "4.0.4",
|
|
40
|
+
"@deepseek-ai/cordis-plugin-group": "1.0.4",
|
|
42
41
|
"@deepseek-ai/cordis-plugin-include": "1.0.9",
|
|
43
|
-
"@deepseek-ai/
|
|
44
|
-
"@deepseek-ai/dsh-agent
|
|
45
|
-
"@deepseek-ai/dsh-agent-
|
|
46
|
-
"@deepseek-ai/dsh-agent-loop
|
|
47
|
-
"@deepseek-ai/dsh-
|
|
48
|
-
"@deepseek-ai/dsh-
|
|
49
|
-
"@deepseek-ai/dsh-
|
|
50
|
-
"@deepseek-ai/dsh-
|
|
51
|
-
"@deepseek-ai/dsh-
|
|
52
|
-
"@deepseek-ai/dsh-
|
|
53
|
-
"@deepseek-ai/dsh-
|
|
54
|
-
"@deepseek-ai/dsh-
|
|
55
|
-
"@deepseek-ai/dsh-
|
|
56
|
-
"@deepseek-ai/dsh-
|
|
57
|
-
"@deepseek-ai/dsh-
|
|
58
|
-
"@deepseek-ai/dsh-
|
|
59
|
-
"@deepseek-ai/dsh-
|
|
60
|
-
"@
|
|
42
|
+
"@deepseek-ai/cordis-plugin-loader": "1.0.5",
|
|
43
|
+
"@deepseek-ai/dsh-agent": "0.1.7-rc.2",
|
|
44
|
+
"@deepseek-ai/dsh-agent-instructions": "0.1.7-rc.2",
|
|
45
|
+
"@deepseek-ai/dsh-agent-loop": "0.1.7-rc.2",
|
|
46
|
+
"@deepseek-ai/dsh-agent-loop-testkit": "0.1.7-rc.2",
|
|
47
|
+
"@deepseek-ai/dsh-agent-preset-registry": "0.1.7-rc.2",
|
|
48
|
+
"@deepseek-ai/dsh-commands": "0.1.7-rc.2",
|
|
49
|
+
"@deepseek-ai/dsh-compaction-basic": "0.1.7-rc.2",
|
|
50
|
+
"@deepseek-ai/dsh-fs": "0.1.7-rc.2",
|
|
51
|
+
"@deepseek-ai/dsh-fs-local": "0.1.7-rc.2",
|
|
52
|
+
"@deepseek-ai/dsh-llm": "0.1.7-rc.2",
|
|
53
|
+
"@deepseek-ai/dsh-scope": "0.1.7-rc.2",
|
|
54
|
+
"@deepseek-ai/dsh-session": "0.1.7-rc.2",
|
|
55
|
+
"@deepseek-ai/dsh-session-persistence-jsonl": "0.1.7-rc.2",
|
|
56
|
+
"@deepseek-ai/dsh-skill": "0.1.7-rc.2",
|
|
57
|
+
"@deepseek-ai/dsh-skill-filesystem": "0.1.7-rc.2",
|
|
58
|
+
"@deepseek-ai/dsh-system-prompt": "0.1.7-rc.2",
|
|
59
|
+
"@deepseek-ai/dsh-token-meter": "0.1.7-rc.2",
|
|
60
|
+
"@deepseek-ai/dsh-tool-skill": "0.1.7-rc.2",
|
|
61
|
+
"@deepseek-ai/dsh-tools": "0.1.7-rc.2",
|
|
62
|
+
"@morlay/session-rdb": "0.0.21-alpha.9",
|
|
61
63
|
"@types/js-yaml": "^4.0.9",
|
|
62
64
|
"@types/mdast": "^4.0.4",
|
|
63
65
|
"js-yaml": "^4.1.0"
|
|
64
66
|
},
|
|
65
67
|
"peerDependencies": {
|
|
66
|
-
"@deepseek-ai/cordis": "4.0.4",
|
|
67
|
-
"@deepseek-ai/dsh-agent": "0.1.7-
|
|
68
|
-
"@deepseek-ai/dsh-agent-instructions": "0.1.7-
|
|
69
|
-
"@deepseek-ai/dsh-
|
|
70
|
-
"@deepseek-ai/dsh-
|
|
71
|
-
"@deepseek-ai/dsh-
|
|
72
|
-
"@deepseek-ai/dsh-
|
|
73
|
-
"@deepseek-ai/dsh-
|
|
74
|
-
"@deepseek-ai/dsh-
|
|
75
|
-
"@deepseek-ai/dsh-
|
|
76
|
-
|
|
77
|
-
"dsh": {
|
|
78
|
-
"bundle": {
|
|
79
|
-
"patch": "./cordis.patch.yml"
|
|
80
|
-
}
|
|
68
|
+
"@deepseek-ai/cordis": "^4.0.4",
|
|
69
|
+
"@deepseek-ai/dsh-agent": "^0.1.7-rc.2",
|
|
70
|
+
"@deepseek-ai/dsh-agent-instructions": "^0.1.7-rc.2",
|
|
71
|
+
"@deepseek-ai/dsh-agent-preset-registry": "^0.1.7-rc.2",
|
|
72
|
+
"@deepseek-ai/dsh-commands": "^0.1.7-rc.2",
|
|
73
|
+
"@deepseek-ai/dsh-fs": "^0.1.7-rc.2",
|
|
74
|
+
"@deepseek-ai/dsh-llm": "^0.1.7-rc.2",
|
|
75
|
+
"@deepseek-ai/dsh-session": "^0.1.7-rc.2",
|
|
76
|
+
"@deepseek-ai/dsh-skill": "^0.1.7-rc.2",
|
|
77
|
+
"@deepseek-ai/dsh-system-prompt": "^0.1.7-rc.2",
|
|
78
|
+
"@deepseek-ai/dsh-tools": "^0.1.7-rc.2"
|
|
81
79
|
},
|
|
82
80
|
"scripts": {
|
|
83
81
|
"build": "pnpm exec tsdown"
|
|
@@ -7,6 +7,7 @@ import z from "@deepseek-ai/schemastery";
|
|
|
7
7
|
import type {} from "../assembler/index.ts";
|
|
8
8
|
import { instructionChain, readInstruction, type InstructionFile } from "./files.ts";
|
|
9
9
|
import { baselineIdentity } from "./baseline.ts";
|
|
10
|
+
import { presetOwnsInstructions } from "./preset-owner.ts";
|
|
10
11
|
|
|
11
12
|
export const name = "context-agent-instructions";
|
|
12
13
|
|
|
@@ -59,7 +60,7 @@ export function apply(ctx: Context, config: Config): void {
|
|
|
59
60
|
for (const file of chain.files) {
|
|
60
61
|
ctx.contextAssembler.registerRule({
|
|
61
62
|
id: `agent-instructions:${rootTag(file.root)}:${file.display}`,
|
|
62
|
-
//
|
|
63
|
+
// 对外身份沿用上游那两样(skill 面相反,用我们自己的 kind):kind 让客户端标签与按 kind 认领的消费方认得这是
|
|
63
64
|
// 工作区指令;`baseline` 与 `baselineIdentity` 让上游的认领判据(kind + baseline===true + 身份相等)
|
|
64
65
|
// 认这份条目就是基线,于是它不再注入自己那条模板。`changes` 留空——上游那套按文件做 reconciliation
|
|
65
66
|
// 的记录我们不做(一条文件一条 id)。
|
|
@@ -70,7 +71,10 @@ export function apply(ctx: Context, config: Config): void {
|
|
|
70
71
|
baselineIdentity: identity,
|
|
71
72
|
changes: [],
|
|
72
73
|
}),
|
|
73
|
-
text: (target) => {
|
|
74
|
+
text: async (target) => {
|
|
75
|
+
// 这个会话的 preset 自己装了这一面(官方 `standard` / `ptc` / `cordis` 都装了上游那行)就让位:
|
|
76
|
+
// 同一份工作区指令只从一处到模型(见 [`preset-owner.ts`](./preset-owner.ts))。
|
|
77
|
+
if (await presetOwnsInstructions(ctx, target)) return "";
|
|
74
78
|
const current = chains.get(target);
|
|
75
79
|
if (current === undefined || !current.files.some((entry) => entry.path === file.path))
|
|
76
80
|
return "";
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 「这个会话的 preset 行清单里装了上游 `agent-instructions` 吗」——装了就让位,这一面归 preset。
|
|
3
|
+
*
|
|
4
|
+
* 为什么要有这条判据:官方 web-app 把 `agent-instructions` 从 host 平面设成 `disabled`、交给 preset 的行清单
|
|
5
|
+
* ([`web-app/cordis.patch.yml:546`](../../../vendor/deepseek-harness/packages/bundle/web-app/cordis.patch.yml) 与
|
|
6
|
+
* `presets/*.patch.yml`)。本部署保留官方 preset 整套,于是**同一个会话**里 preset 平面的上游行与 host 平面的
|
|
7
|
+
* 我们都会注入:一步里两条 AGENTS.md,而模型无法把后一条理解成"作废前一条"(两条的幂等键不同)。
|
|
8
|
+
*
|
|
9
|
+
* 方向:**我们让位**,不是上游认领我们。上游 `agent-instructions` 的认领判据只看它**看得见**的东西
|
|
10
|
+
* (本步 claimed 的消息 + 会话 surface:
|
|
11
|
+
* [`agent-instructions/src/index.ts:46-63`](../../../vendor/deepseek-harness/packages/context/agent-instructions/src/index.ts)),
|
|
12
|
+
* 第 1 步两侧都在各自算注入,谁也算不出对方那一条,所以"由上游认领"救不了首步的重复
|
|
13
|
+
* (`baselineIdentity` 那份身份管的是另一件事,见 [`baseline.ts`](./baseline.ts))。
|
|
14
|
+
*
|
|
15
|
+
* 判据取**行清单里的模块名**而不是行 id:行 id 在 preset 的 `config.plugins` 里是自由的,模块名才是"哪一面的
|
|
16
|
+
* 提供者"。`enabled === true` 表示这一行在这个代际真的装着;`'conditional'`(`!!js` 读不出)按"没装"处理——
|
|
17
|
+
* 读不出就不敢让位,宁可我们提供。
|
|
18
|
+
*/
|
|
19
|
+
import type { Context } from "@deepseek-ai/cordis";
|
|
20
|
+
import type { Agent } from "@deepseek-ai/dsh-agent";
|
|
21
|
+
// 只用它的类型:`ctx.agentPresets` 的形状来自上游注册表的声明合并,装不上时这里读到 undefined。
|
|
22
|
+
import type {} from "@deepseek-ai/dsh-agent-preset-registry";
|
|
23
|
+
|
|
24
|
+
/** 上游那一行的模块名:与 `presets/*.patch.yml` 里写的一字不差。 */
|
|
25
|
+
const UPSTREAM_MODULE = "@deepseek-ai/dsh-agent-instructions";
|
|
26
|
+
|
|
27
|
+
/** 会话 → 它当前的 preset。preset 变了(空白窗口里换 preset)就整份重算。 */
|
|
28
|
+
const decided = new WeakMap<Agent, { preset: string; owns: Promise<boolean> }>();
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 该会话的 preset 自己提供了工作区指令这一面吗。
|
|
32
|
+
* @param ctx - 能解析到注册表的上下文(能力自己的 apply ctx)。
|
|
33
|
+
* @param agent - 要判的会话。
|
|
34
|
+
* @returns 行清单里有上游那一行且它是启用的。
|
|
35
|
+
*/
|
|
36
|
+
export function presetOwnsInstructions(ctx: Context, agent: Agent): Promise<boolean> {
|
|
37
|
+
const registry = registryOf(ctx);
|
|
38
|
+
if (registry === undefined) return Promise.resolve(false);
|
|
39
|
+
const preset = registry.composedPreset(agent.ctx);
|
|
40
|
+
// 没挂 preset(子代理还没 join、没装注册表的部署、测试里的假 standing scope):这一面归我们。
|
|
41
|
+
if (preset === undefined) return Promise.resolve(false);
|
|
42
|
+
|
|
43
|
+
const cached = decided.get(agent);
|
|
44
|
+
if (cached?.preset === preset) return cached.owns;
|
|
45
|
+
|
|
46
|
+
const owns = registry
|
|
47
|
+
.compositionInventory()
|
|
48
|
+
.then((inventory) =>
|
|
49
|
+
inventory.some(
|
|
50
|
+
(composition) =>
|
|
51
|
+
composition.id === preset &&
|
|
52
|
+
composition.rows.some(
|
|
53
|
+
(row) => row.moduleName === UPSTREAM_MODULE && row.enabled === true,
|
|
54
|
+
),
|
|
55
|
+
),
|
|
56
|
+
)
|
|
57
|
+
// 读不出来就当我们提供:缺内容比重复更糟,而"读不出来"只可能来自坏掉的注册表。
|
|
58
|
+
.catch(() => false);
|
|
59
|
+
decided.set(agent, { preset, owns });
|
|
60
|
+
return owns;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** 注册表可能压根没装(没有 preset 的部署形态);`ctx.get` 在不提供时读到 undefined。 */
|
|
64
|
+
function registryOf(ctx: Context): Context["agentPresets"] | undefined {
|
|
65
|
+
try {
|
|
66
|
+
return ctx.get("agentPresets") as Context["agentPresets"] | undefined;
|
|
67
|
+
} catch {
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
}
|
package/src/assembler/channel.ts
CHANGED
|
@@ -52,7 +52,7 @@ export interface PromptRuleDeclaration {
|
|
|
52
52
|
readonly id: string;
|
|
53
53
|
readonly text: (agent: Agent) => string | Promise<string>;
|
|
54
54
|
/**
|
|
55
|
-
*
|
|
55
|
+
* 这条规则的对外身份。工作区指令那一面按上游 kind 发消息(skill 面相反,用我们自己的 kind),好让按 kind 认领的
|
|
56
56
|
* 消费方(客户端标签、上游的实验性约束收集)认得出来;不声明就是通道自己的 `context-assembler`。
|
|
57
57
|
* 幂等键仍是 `id`(`reminderMessage` 会把它塞进 source)。
|
|
58
58
|
*/
|
|
@@ -81,7 +81,10 @@ export class ContextAssembler extends Service {
|
|
|
81
81
|
private readonly suppressed = new Set<string>();
|
|
82
82
|
private readonly skills = new Map<string, RegisteredSkill>();
|
|
83
83
|
private readonly states = new WeakMap<Agent, AgentState>();
|
|
84
|
-
/**
|
|
84
|
+
/**
|
|
85
|
+
* 不要 instruction 类注入的会话(模式说了 `instructions: false`):常驻正文、规则块与降级 section 都不进。
|
|
86
|
+
* 按需 skill 不受它管(那是模型自己加载),引用材料也不经通道。
|
|
87
|
+
*/
|
|
85
88
|
private readonly withoutInstructions = new WeakSet<Agent>();
|
|
86
89
|
|
|
87
90
|
/** 本会话的模式收窄(白名单):投影层的过滤不碰注册表,注册表上看不出"谁能用"。 */
|
|
@@ -137,7 +140,7 @@ export class ContextAssembler extends Service {
|
|
|
137
140
|
* 本步要注入的条目:键 → 已渲染好的正文(规则块或内容块)。
|
|
138
141
|
* `sections` 来自本步装配,`rules` 与 `auto` skill 正文在这里现算。
|
|
139
142
|
*/
|
|
140
|
-
/**
|
|
143
|
+
/** 这个会话要不要 instruction 类注入(模式说了 `instructions: false` 就是不要,见 {@link withoutInstructions})。 */
|
|
141
144
|
setInstructions(agent: Agent, on: boolean): void {
|
|
142
145
|
if (on) this.withoutInstructions.delete(agent);
|
|
143
146
|
else this.withoutInstructions.add(agent);
|
|
@@ -184,16 +187,37 @@ export class ContextAssembler extends Service {
|
|
|
184
187
|
return this.declarations.get(name)?.content(agent);
|
|
185
188
|
}
|
|
186
189
|
|
|
190
|
+
/** 一个 skill 在这个会话能不能被用到:声明了 `requires` 时至少一个工具在模式收窄内。 */
|
|
191
|
+
private reachable(
|
|
192
|
+
declaration: PromptSkillDeclaration,
|
|
193
|
+
scope: ((tool: string) => boolean) | undefined,
|
|
194
|
+
): boolean {
|
|
195
|
+
const requires = declaration.requires;
|
|
196
|
+
if (requires === undefined || requires.length === 0) return true;
|
|
197
|
+
// 没有收窄(没装模式的部署)就是全可见。
|
|
198
|
+
if (scope === undefined) return true;
|
|
199
|
+
return requires.some((tool) => scope(tool));
|
|
200
|
+
}
|
|
201
|
+
|
|
187
202
|
async collect(agent: Agent): Promise<Map<string, PromptEntry>> {
|
|
188
203
|
const entries = new Map<string, PromptEntry>();
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
if (body.length > 0)
|
|
194
|
-
entries.set(declaration.name, { text: renderVirtualSkill(declaration.name, body) });
|
|
195
|
-
}
|
|
204
|
+
const scope = this.toolScopes.get(agent);
|
|
205
|
+
// 这一档开关管**一切 instruction 类送达**:常驻正文(内容块)、规则块、降级 section。按需 skill 照常注册
|
|
206
|
+
// (模型自己调 `skill` 才拿到正文,那已不是"注入");用户手打 `@` 引用带进来的材料走
|
|
207
|
+
// `@morlay/dsh-reference` 的 pre-step,不经这里。
|
|
196
208
|
const instructionsOff = this.withoutInstructions.has(agent);
|
|
209
|
+
if (!instructionsOff) {
|
|
210
|
+
for (const declaration of this.declarations.values()) {
|
|
211
|
+
if ((declaration.injection ?? "on-demand") !== "auto") continue;
|
|
212
|
+
// 常驻正文也跟随工具可见性:它声明的 `requires` 工具在这个会话一个都不可用,正文就不该注入
|
|
213
|
+
// (与 skill 目录那侧的 `hiddenSkills` 同一判据;通道不碰 tools 注册表,只用模式收窄那一份)。
|
|
214
|
+
if (!this.reachable(declaration, scope)) continue;
|
|
215
|
+
const body = declaration.content(agent);
|
|
216
|
+
// 常驻送达的 skill 正文与按需加载同一形态:内容块,不是规则块。
|
|
217
|
+
if (body.length > 0)
|
|
218
|
+
entries.set(declaration.name, { text: renderVirtualSkill(declaration.name, body) });
|
|
219
|
+
}
|
|
220
|
+
}
|
|
197
221
|
for (const declaration of this.rules.values()) {
|
|
198
222
|
if (instructionsOff) break;
|
|
199
223
|
// 注入路径不能被任何一个内容提供者拖死:文本是异步算的(技能目录要读注册表、工作区指令要读文件),
|
|
@@ -14,7 +14,16 @@ export const RULES_SECTION = "assembler:rules";
|
|
|
14
14
|
/** 通道自己的条目形态(注入方没声明 source 时的默认):规则块,幂等键是 id。 */
|
|
15
15
|
export interface PromptReminderSource {
|
|
16
16
|
kind: "context-assembler";
|
|
17
|
-
|
|
17
|
+
/**
|
|
18
|
+
* 条目形态:`instructions` 是规则块;`catalog` 是"带清单的目录"(客户端按 `entries` 列条目,
|
|
19
|
+
* 而不是读正文)。
|
|
20
|
+
*/
|
|
21
|
+
form: "instructions" | "catalog";
|
|
22
|
+
/**
|
|
23
|
+
* `catalog` 形态发布的那份清单。**只有我们自己发布的目录**用它:上游 `tool-skill` 的账本扫法
|
|
24
|
+
* 只认它自己的 `kind`,所以这里的 kind 必须是我们自己的(见 [`skill-catalog`](../skill-catalog/index.ts))。
|
|
25
|
+
*/
|
|
26
|
+
entries?: readonly { readonly name: string; readonly description: string }[];
|
|
18
27
|
/** 条目 id:同 id 的最新一条取代更早的同 id 条目。本插件早先落库的消息没有它。 */
|
|
19
28
|
id?: string;
|
|
20
29
|
}
|
|
@@ -27,7 +36,7 @@ declare module "@deepseek-ai/dsh-llm" {
|
|
|
27
36
|
|
|
28
37
|
/**
|
|
29
38
|
* 这条消息是不是通道注入的条目——判据是 source 里的幂等键,而不是 kind:接管上游那两面的条目
|
|
30
|
-
*
|
|
39
|
+
* 接管工作区指令那一面的条目用上游 kind(`agent-instructions`),幂等必须照样认。
|
|
31
40
|
*/
|
|
32
41
|
export function promptEntryIdOf(source: MessageSource): string | undefined {
|
|
33
42
|
const id = (source as { readonly id?: unknown }).id;
|
|
@@ -79,7 +88,7 @@ export function latestReminderText(agent: Agent, id: string): string | undefined
|
|
|
79
88
|
|
|
80
89
|
/**
|
|
81
90
|
* 注入一条提醒:`text` 是已渲染好的完整正文(规则块或内容块),`key` 是它的幂等键。
|
|
82
|
-
* `source`
|
|
91
|
+
* `source` 声明这条消息对外的身份——工作区指令那一面用上游 kind,我们自己的条目留默认。
|
|
83
92
|
*/
|
|
84
93
|
export function reminderMessage(key: string, text: string, source?: MessageSource): UserMessage {
|
|
85
94
|
return createUserMessage({
|
package/src/index.ts
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
* 组装出口:按 config 把各能力装成一个 cordis 插件树。
|
|
3
3
|
*
|
|
4
4
|
* 装配面因此只有**一行**(`@morlay/dsh-context-assembler`):`isolate` 是行级选项,一行声明就覆盖它整棵子树,
|
|
5
|
-
* 而这里 `ctx.plugin()` 出来的子插件都继承行 ctx 的 isolate map 与 scope
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* 而这里 `ctx.plugin()` 出来的子插件都继承行 ctx 的 isolate map 与 scope——**通道服务由这一行装一次**。
|
|
6
|
+
* **每个子插件各有自己的 `inject`**(合成单入口会让 inject 变并集,一个可选搭档缺席就拖垮整包),这是这层
|
|
7
|
+
* 组装唯一必须守住的东西。
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
9
|
+
* 模式之间的差异(哪个模式要哪些能力、工具收到什么程度)不在这里用 config 裁:由
|
|
10
|
+
* `context-assembler-scope` 那一行把模式定义登记给通道(`options` 只用来给留下的能力传参,能力名就是子出口名)。
|
|
11
11
|
*/
|
|
12
12
|
import type { Context } from "@deepseek-ai/cordis";
|
|
13
13
|
import z from "@deepseek-ai/schemastery";
|
package/src/rows.ts
CHANGED
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
* (`@morlay/dsh-agent-toolkit/guidance`,在别的包里)与 skill 目录这类消费者都在别的行上
|
|
6
6
|
* `inject` 它,隔离会把它们挡在组外(行停在 waiting,不报错)。
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* 本包自己的 bundle 只装通道(`PATCH_ROWS`)。模式的收口是另一行(`scopeRow()`),它由
|
|
9
|
+
* [`@morlay/dsh-session-mode`](../../../profile/dsh-session-mode/README.md) 的 patch 与模式定义一起装——
|
|
10
|
+
* 那一行的 config 就是模式清单。
|
|
10
11
|
*/
|
|
11
12
|
|
|
12
13
|
/** 组装出口(包根)就是那个"按 config 装能力"的插件。 */
|
|
@@ -32,25 +33,25 @@ export function contextChannel(config?: Readonly<Record<string, unknown>>): Cont
|
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
/**
|
|
35
|
-
*
|
|
36
|
+
* 模式收口那一行:`scope` 出口按 `ctx.sessionModes` 的模式定义收口(工具白名单、instruction 总开关、
|
|
37
|
+
* 动态快照开关)。
|
|
36
38
|
*
|
|
37
|
-
*
|
|
38
|
-
* `ctx.
|
|
39
|
+
* 行 id 用全名 `context-assembler-scope`:它的搭档是同一份装配里的 `context-assembler`(通道),两个 id 放在
|
|
40
|
+
* 一起才看得出这一行属于哪个包、做什么——`context-scope` 那种短名会被误当成 `ctx.contextScope` 之类的服务。
|
|
41
|
+
*
|
|
42
|
+
* 行本身**不带 config**:装哪些模式、每个模式收什么,都由 `session-mode` 行的 `config.modes` 决定,
|
|
43
|
+
* 这一行只把它落到会话上(`inject` `sessionModes`,由
|
|
44
|
+
* [`@morlay/dsh-session-mode`](../../../profile/dsh-session-mode/README.md) 的 patch 插一行)。
|
|
39
45
|
*/
|
|
40
|
-
export function scopeRow(
|
|
41
|
-
readonly id: string;
|
|
42
|
-
readonly name: string;
|
|
43
|
-
readonly config?: Readonly<Record<string, unknown>>;
|
|
44
|
-
} {
|
|
46
|
+
export function scopeRow(): ContextRow {
|
|
45
47
|
return {
|
|
46
|
-
id: "context-scope",
|
|
48
|
+
id: "context-assembler-scope",
|
|
47
49
|
name: "@morlay/dsh-context-assembler/scope",
|
|
48
|
-
...(config === undefined ? {} : { config }),
|
|
49
50
|
};
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
/**
|
|
53
|
-
* bundle patch
|
|
54
|
+
* bundle patch 的行清单:只有通道一行(模式收口那一行归 `session-mode` 的 patch 装)。
|
|
54
55
|
*
|
|
55
56
|
* 形态是 `insert`:这些行由本包提供给 host 平面。写成 `- id: x` 那种"改已有行"的形态时,装配期找不到
|
|
56
57
|
* 目标行(只 warn 后跳过),通道根本没装上,引用它的行停在 waiting。
|