@morlay/dsh-context-assembler 0.0.2 → 0.0.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@morlay/dsh-context-assembler",
3
- "version": "0.0.2",
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).",
3
+ "version": "0.0.4",
4
+ "description": "Prompt channel for dsh: assembly-result text conversion (replace / suppress) plus per-step delivery of demoted sections. Workspace instructions and the skill catalog use the official rows (@deepseek-ai/dsh-agent-instructions / @deepseek-ai/dsh-tool-skill); the per-session scope gate lives in @morlay/dsh-session-mode. Reference expansion is its own package (@morlay/dsh-reference).",
5
5
  "keywords": [
6
6
  "context",
7
7
  "dsh",
@@ -25,10 +25,6 @@
25
25
  "exports": {
26
26
  ".": "./dist/index.mjs",
27
27
  "./rows": "./dist/rows.mjs",
28
- "./assembler": "./dist/assembler.mjs",
29
- "./agent-instructions": "./dist/agent-instructions.mjs",
30
- "./skill-catalog": "./dist/skill-catalog.mjs",
31
- "./scope": "./dist/scope.mjs",
32
28
  "./package.json": "./package.json",
33
29
  "./locale/*.json": "./locale/*.json"
34
30
  },
@@ -37,45 +33,24 @@
37
33
  },
38
34
  "devDependencies": {
39
35
  "@deepseek-ai/cordis": "4.0.4",
40
- "@deepseek-ai/cordis-plugin-group": "1.0.4",
41
- "@deepseek-ai/cordis-plugin-include": "1.0.9",
42
- "@deepseek-ai/cordis-plugin-loader": "1.0.5",
43
36
  "@deepseek-ai/dsh-agent": "0.2.0-rc.1",
44
- "@deepseek-ai/dsh-agent-instructions": "0.2.0-rc.1",
45
37
  "@deepseek-ai/dsh-agent-loop": "0.2.0-rc.1",
46
38
  "@deepseek-ai/dsh-agent-loop-testkit": "0.2.0-rc.1",
47
- "@deepseek-ai/dsh-agent-preset-registry": "0.2.0-rc.1",
48
- "@deepseek-ai/dsh-commands": "0.2.0-rc.1",
49
39
  "@deepseek-ai/dsh-compaction-basic": "0.2.0-rc.1",
50
- "@deepseek-ai/dsh-fs": "0.2.0-rc.1",
51
- "@deepseek-ai/dsh-fs-local": "0.2.0-rc.1",
52
40
  "@deepseek-ai/dsh-llm": "0.2.0-rc.1",
53
41
  "@deepseek-ai/dsh-scope": "0.2.0-rc.1",
54
42
  "@deepseek-ai/dsh-session": "0.2.0-rc.1",
55
43
  "@deepseek-ai/dsh-session-persistence-jsonl": "0.2.0-rc.1",
56
- "@deepseek-ai/dsh-skill": "0.2.0-rc.1",
57
- "@deepseek-ai/dsh-skill-filesystem": "0.2.0-rc.1",
58
44
  "@deepseek-ai/dsh-system-prompt": "0.2.0-rc.1",
59
45
  "@deepseek-ai/dsh-token-meter": "0.2.0-rc.1",
60
- "@deepseek-ai/dsh-tool-skill": "0.2.0-rc.1",
61
- "@deepseek-ai/dsh-tools": "0.2.0-rc.1",
62
- "@morlay/session-rdb": "0.1.1",
63
- "@types/js-yaml": "^4.0.9",
64
- "@types/mdast": "^4.0.4",
65
- "js-yaml": "^4.1.0"
46
+ "@morlay/session-rdb": "0.1.3"
66
47
  },
67
48
  "peerDependencies": {
68
49
  "@deepseek-ai/cordis": "^4.0.4",
69
50
  "@deepseek-ai/dsh-agent": "^0.2.0-rc.1",
70
- "@deepseek-ai/dsh-agent-instructions": "^0.2.0-rc.1",
71
- "@deepseek-ai/dsh-agent-preset-registry": "^0.2.0-rc.1",
72
- "@deepseek-ai/dsh-commands": "^0.2.0-rc.1",
73
- "@deepseek-ai/dsh-fs": "^0.2.0-rc.1",
74
51
  "@deepseek-ai/dsh-llm": "^0.2.0-rc.1",
75
52
  "@deepseek-ai/dsh-session": "^0.2.0-rc.1",
76
- "@deepseek-ai/dsh-skill": "^0.2.0-rc.1",
77
- "@deepseek-ai/dsh-system-prompt": "^0.2.0-rc.1",
78
- "@deepseek-ai/dsh-tools": "^0.2.0-rc.1"
53
+ "@deepseek-ai/dsh-system-prompt": "^0.2.0-rc.1"
79
54
  },
80
55
  "scripts": {
81
56
  "build": "pnpm exec tsdown"
package/src/channel.ts ADDED
@@ -0,0 +1,86 @@
1
+ import type { Agent } from "@deepseek-ai/dsh-agent";
2
+ import { Service, type Context } from "@deepseek-ai/cordis";
3
+ import { renderReminder } from "./reminder.ts";
4
+
5
+ interface AgentState {
6
+ // 本步装配降级出来的 section 条目:id → 正文(未渲染信封)。
7
+ sections: ReadonlyMap<string, string>;
8
+ }
9
+
10
+ // 注入通道:装配结果上的**文本改写**(`replaceSection` / `suppressSection`)与降级 section 的按步注入。
11
+ //
12
+ // 工作区指令与技能目录**不在这里**:那两面用官方行自己的注入方式,本包只做内容转换(把不要的 section 丢掉、
13
+ // 把要的换成我们的文案)。覆盖语义(同 id 最新取代更早)由系统提示词里的声明说明一次,见
14
+ // [`rules.ts`](./rules.ts)。
15
+ export class ContextAssembler extends Service {
16
+ private readonly replacements = new Map<string, string>();
17
+ private readonly suppressed = new Set<string>();
18
+ private readonly states = new WeakMap<Agent, AgentState>();
19
+ // 不要 instruction 类注入的会话(模式说了 `instructions: false`):降级 section 不进提示词。
20
+ private readonly withoutInstructions = new WeakSet<Agent>();
21
+
22
+ constructor(ctx: Context) {
23
+ super(ctx, "contextAssembler");
24
+ }
25
+
26
+ // 装配结果上把这个 section 的文本换成 `text`。
27
+ replaceSection(name: string, text: string): () => void {
28
+ this.replacements.set(name, text);
29
+ return () => {
30
+ if (this.replacements.get(name) === text) this.replacements.delete(name);
31
+ };
32
+ }
33
+
34
+ suppressSection(name: string): () => void {
35
+ this.suppressed.add(name);
36
+ return () => {
37
+ this.suppressed.delete(name);
38
+ };
39
+ }
40
+
41
+ isSuppressed(name: string): boolean {
42
+ return this.suppressed.has(name);
43
+ }
44
+
45
+ // 装配结果上该 section 要换成的文本;undefined 表示原样保留。
46
+ replacement(name: string): string | undefined {
47
+ return this.replacements.get(name);
48
+ }
49
+
50
+ // 这个会话要不要 instruction 类注入(模式说了 `instructions: false` 就是不要)。
51
+ setInstructions(agent: Agent, on: boolean): void {
52
+ if (on) this.withoutInstructions.delete(agent);
53
+ else this.withoutInstructions.add(agent);
54
+ }
55
+
56
+ // 本步要注入的条目:降级出来的 section(id → 正文)。
57
+ collect(agent: Agent): Map<string, string> {
58
+ const entries = new Map<string, string>();
59
+ if (this.withoutInstructions.has(agent)) return entries;
60
+ for (const [id, text] of this.sectionsOf(agent)) entries.set(id, renderReminder(id, text));
61
+ return entries;
62
+ }
63
+
64
+ private sectionsOf(agent: Agent): ReadonlyMap<string, string> {
65
+ return this.states.get(agent)?.sections ?? new Map();
66
+ }
67
+
68
+ // 每步装配调一次:记下本步降级出来的 section 文本,供下一步注入。
69
+ sync(agent: Agent, input: { readonly sections: ReadonlyMap<string, string> }): void {
70
+ this.stateOf(agent).sections = input.sections;
71
+ }
72
+
73
+ private stateOf(agent: Agent): AgentState {
74
+ const existing = this.states.get(agent);
75
+ if (existing !== undefined) return existing;
76
+ const state: AgentState = { sections: new Map() };
77
+ this.states.set(agent, state);
78
+ return state;
79
+ }
80
+ }
81
+
82
+ declare module "@deepseek-ai/cordis" {
83
+ interface Context {
84
+ contextAssembler: ContextAssembler;
85
+ }
86
+ }
@@ -1,28 +1,17 @@
1
1
  import { PERSONA_PREFIX_SECTION, PERSONA_SUFFIX_SECTION } from "@deepseek-ai/dsh-system-prompt";
2
2
  import { RULES_SECTION } from "./reminder.ts";
3
3
 
4
- /**
5
- * 留在系统提示词里的 section:部署级 persona(子 agent 在 agent 作用域注册的同名 persona 也命中)
6
- * 与覆盖规则声明([`rules.ts`](./rules.ts))。
7
- */
4
+ // 留在系统提示词里的 section:部署级 persona(子 agent 在 agent 作用域注册的同名 persona 也命中)
5
+ // 与覆盖规则声明([`rules.ts`](./rules.ts))。
8
6
  export const DEFAULT_KEEP: readonly string[] = [
9
7
  PERSONA_PREFIX_SECTION,
10
8
  PERSONA_SUFFIX_SECTION,
11
9
  RULES_SECTION,
12
10
  ];
13
11
 
14
- /**
15
- * 不进提示词的 section。三类:
16
- *
17
- * 1. **harness 自带的开场白与平台运维说明**:`harness:identity`("You are an AI agent powered by
18
- * DeepSeek Harness."——身份由各模式的 persona 给,这句话在本部署里是噪音,且是英文)、
19
- * `harness:source`(讲 dev server / HMR / 本地 checkout 位置);
20
- * 2. 本部署不装配的工具的用法说明:`tool:lsp` / `tool:pty` / `tool:session-query` / `tool:cordis` /
21
- * `tool:ralph`;
22
- * 3. `app:web-surface`(平台外壳说明)。
23
- *
24
- * 其余工具说明由各组的 `drops` 摘掉、要点由 toolkit 的 `guidance` 出口写进组正文,不在这一份里。
25
- */
12
+ // 不进提示词的 section:harness 自带的开场白与平台运维说明(`harness:identity`、`harness:source`)、本部署
13
+ // 不装配的工具的用法说明(`tool:lsp` / `tool:pty` / `tool:session-query` / `tool:cordis` / `tool:ralph`)、
14
+ // 平台外壳说明(`app:web-surface`)。其余工具说明由各组的 `drops` 摘掉、要点由 `tool-guidance` 的包根写进组正文。
26
15
  export const DEFAULT_SUPPRESS: readonly string[] = [
27
16
  "harness:identity",
28
17
  "harness:source",
@@ -34,12 +23,8 @@ export const DEFAULT_SUPPRESS: readonly string[] = [
34
23
  "tool:ralph",
35
24
  ];
36
25
 
37
- /**
38
- * 改写成中文的两段上游说明(内容忠实原意):`@` 引用路径的语义、输出里的文件链接规范。
39
- *
40
- * 这两段注册在 agent 层(`context:file-reference`)与 host 层(`ui:deliverable-file-references`),
41
- * 注册层不归我们——所以在装配结果上换文本(不移除:内容要保留)。
42
- */
26
+ // 改写成中文的两段上游说明(内容忠实原意):`@` 引用路径的语义、输出里的文件链接规范。注册层不归我们
27
+ // (agent 层的 `context:file-reference` 与 host 层的 `ui:deliverable-file-references`),所以在装配结果上换文本。
43
28
  export const DEFAULT_REPLACE: Readonly<Record<string, string>> = {
44
29
  "context:file-reference":
45
30
  "以 @ 开头的词是用户显式引用的工作区路径,相对于工作区根。结尾带斜杠表示目录:需要时列目录内容。" +
package/src/index.ts CHANGED
@@ -1,56 +1,126 @@
1
- /**
2
- * 组装出口:按 config 把各能力装成一个 cordis 插件树。
3
- *
4
- * 装配面因此只有**一行**(`@morlay/dsh-context-assembler`):`isolate` 是行级选项,一行声明就覆盖它整棵子树,
5
- * 而这里 `ctx.plugin()` 出来的子插件都继承行 ctx 的 isolate map 与 scope——**通道服务由这一行装一次**。
6
- * **每个子插件各有自己的 `inject`**(合成单入口会让 inject 变并集,一个可选搭档缺席就拖垮整包),这是这层
7
- * 组装唯一必须守住的东西。
8
- *
9
- * 模式之间的差异(哪个模式要哪些能力、工具收到什么程度)不在这里用 config 裁:由
10
- * `context-assembler-scope` 那一行把模式定义登记给通道(`options` 只用来给留下的能力传参,能力名就是子出口名)。
11
- */
1
+ // 包根就是通道本体:装配行写包名(`@morlay/dsh-context-assembler`)装到的就是这一份。
2
+ //
3
+ // 工作区指令与技能目录**不在这里**:那两面用官方行(`@deepseek-ai/dsh-agent-instructions` /
4
+ // `@deepseek-ai/dsh-tool-skill`)自己的注入方式,本包只做装配结果上的文本转换(`replace` / `suppress`)与降级
5
+ // section 的按步送达。按会话收口(工具名单、instruction / 技能目录 / 动态快照三个开关)归 `@morlay/dsh-session-mode`
6
+ // ——收口的输入是模式定义、唯一消费者也是模式。
12
7
  import type { Context } from "@deepseek-ai/cordis";
13
8
  import z from "@deepseek-ai/schemastery";
14
- import * as agentInstructions from "./agent-instructions/index.ts";
15
- import * as assembler from "./assembler/index.ts";
16
- import * as scope from "./scope/index.ts";
17
- import * as skillCatalog from "./skill-catalog/index.ts";
9
+ import type { AssembledSection } from "@deepseek-ai/dsh-system-prompt";
10
+ import { ContextAssembler } from "./channel.ts";
11
+ import { DEFAULT_KEEP, DEFAULT_REPLACE, DEFAULT_SUPPRESS } from "./defaults.ts";
12
+ import { RULES_SECTION, latestReminderText, reminderMessage } from "./reminder.ts";
13
+ import { RULES_TEXT } from "./rules.ts";
18
14
 
19
- export const name = "context-assembler-tree";
15
+ export { DEFAULT_KEEP, DEFAULT_REPLACE, DEFAULT_SUPPRESS } from "./defaults.ts";
16
+ export { ContextAssembler } from "./channel.ts";
20
17
 
21
- const CAPABILITIES = {
22
- assembler,
23
- "agent-instructions": agentInstructions,
24
- "skill-catalog": skillCatalog,
25
- scope,
26
- } as const;
18
+ export const name = "context-assembler";
27
19
 
28
- export type Capability = keyof typeof CAPABILITIES;
20
+ // 规则声明的位置:紧跟部署 persona(order 0),在任何降级内容之前。
21
+ const RULES_ORDER = 1;
29
22
 
30
- const CAPABILITY_NAMES = Object.keys(CAPABILITIES) as Capability[];
23
+ export const inject = ["systemPrompt"];
31
24
 
32
25
  export interface Config {
33
- /** 装哪些能力(名字即子出口名);缺省全部——标准模式要完整的一套。未知名字在装载时报错。 */
34
- capabilities?: string[];
35
- /** 各能力自己的 config,按能力名给(例如 `{ scope: { allowTools: [...] } }`)。 */
36
- options?: Record<string, Record<string, unknown>>;
26
+ // 留在系统提示词里的 section 名;其余非空 section 降级为 reminder。
27
+ keep?: string[];
28
+ // 不进提示词的 section 名(部署级噪音)。
29
+ suppress?: string[];
30
+ // 装配结果上改写的 section 文本(section 名 → 中文文案)。
31
+ replace?: Record<string, string>;
37
32
  }
38
33
 
39
34
  export const Config: z<Config> = z.object({
40
- capabilities: z.array(z.string()).default([...CAPABILITY_NAMES]),
41
- options: z.any().default({}),
35
+ keep: z.array(z.string()).default([...DEFAULT_KEEP]),
36
+ suppress: z.array(z.string()).default([...DEFAULT_SUPPRESS]),
37
+ replace: z.dict(z.string()).default({ ...DEFAULT_REPLACE }),
42
38
  });
43
39
 
40
+ // 提示词注入的通道:system prompt 里只留 `keep`,其余 section 降级为紧随用户消息的 reminder,或直接丢弃
41
+ // (`suppress`);`replace` 在装配结果上把这些 section 的文本换成我们的文案。工作区指令与技能目录走官方行,
42
+ // 不经这里。
44
43
  export function apply(ctx: Context, config: Config): void {
45
- const wanted = config.capabilities ?? CAPABILITY_NAMES;
46
- const options = config.options ?? {};
47
- for (const name of wanted) {
48
- const capability = CAPABILITIES[name as Capability];
49
- if (capability === undefined) {
50
- throw new Error(
51
- `context: unknown capability "${name}"(可选:${CAPABILITY_NAMES.join(" / ")})`,
52
- );
53
- }
54
- ctx.plugin(capability, options[name] ?? {});
44
+ const channel = new ContextAssembler(ctx);
45
+ const keep = new Set(config.keep ?? DEFAULT_KEEP);
46
+ for (const name of config.suppress ?? DEFAULT_SUPPRESS) channel.suppressSection(name);
47
+ for (const [name, text] of Object.entries(config.replace ?? DEFAULT_REPLACE)) {
48
+ channel.replaceSection(name, text);
55
49
  }
50
+
51
+ ctx.systemPrompt.section({ name: RULES_SECTION, order: RULES_ORDER, text: RULES_TEXT });
52
+
53
+ ctx.on("system-prompt/assemble", async (_assembly, context, next) => {
54
+ const result = await next();
55
+ const agent = context.agent;
56
+ const demoted = new Map<string, string>();
57
+ const sections: AssembledSection[] = [];
58
+ let changed = false;
59
+
60
+ for (const section of result.sections) {
61
+ if (channel.isSuppressed(section.name)) {
62
+ changed = true;
63
+ continue;
64
+ }
65
+ const text = channel.replacement(section.name) ?? section.text;
66
+ if (text !== section.text) changed = true;
67
+ if (keep.has(section.name) || text.length === 0) {
68
+ sections.push(text === section.text ? section : { ...section, text });
69
+ continue;
70
+ }
71
+ changed = true;
72
+ demoted.set(sectionId(section.name), text);
73
+ }
74
+
75
+ if (agent !== undefined) channel.sync(agent, { sections: demoted });
76
+ return changed ? { ...result, sections } : result;
77
+ });
78
+
79
+ ctx.on("agent/pre-step", async ({ agent, messages }, next) => {
80
+ const decision = await next();
81
+
82
+ if (decision.kind === "reject" || decision.messages.length === 0) return decision;
83
+ const pending = pendingEntries(agent, channel);
84
+ if (pending.length === 0) return decision;
85
+
86
+ const claimedEnd = decision.messages.findLastIndex((message) => messages.includes(message));
87
+ return {
88
+ ...decision,
89
+ messages: decision.messages.toSpliced(
90
+ claimedEnd + 1,
91
+ 0,
92
+ ...pending.map(([key, text]) => reminderMessage(key, text)),
93
+ ),
94
+ };
95
+ });
96
+
97
+ ctx.on(
98
+ "agent/request-error",
99
+ async ({ agent, signal }, next) => {
100
+ const action = await next();
101
+ if (action?.kind !== "retry" || signal.aborted) return action;
102
+ for (const [key, text] of pendingEntries(agent, channel)) {
103
+ agent.session.append("user/message", reminderMessage(key, text), {
104
+ surfaceOp: "append",
105
+ });
106
+ }
107
+ return action;
108
+ },
109
+ { prepend: true },
110
+ );
111
+ }
112
+
113
+ // 降级 section 的条目 id:一条 section 一个 id,于是同一次变化只重发那一条。
114
+ function sectionId(name: string): string {
115
+ return `section:${name}`;
116
+ }
117
+
118
+ // 本步要注入的条目(id → 已渲染正文):同 id 文本未变就不注入,按 id 字典序(顺序不随注册顺序抖动)。
119
+ function pendingEntries(
120
+ agent: Parameters<typeof latestReminderText>[0],
121
+ channel: ContextAssembler,
122
+ ): [string, string][] {
123
+ return [...channel.collect(agent)]
124
+ .filter(([key, text]) => latestReminderText(agent, key) !== text)
125
+ .toSorted(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0));
56
126
  }
@@ -0,0 +1,62 @@
1
+ import type { Agent } from "@deepseek-ai/dsh-agent";
2
+ import { createUserMessage, type MessageSource } from "@deepseek-ai/dsh-llm";
3
+ import type { UserMessage } from "@deepseek-ai/dsh-session";
4
+
5
+ const REMINDER_OPEN = "<system-reminder";
6
+ const REMINDER_CLOSE = "</system-reminder>";
7
+
8
+ // 覆盖规则在系统提示词里声明一次(见 [`rules.ts`](./rules.ts)),每条 reminder 只带 id 与正文。
9
+ export const RULES_SECTION = "assembler:rules";
10
+
11
+ // 通道自己的条目形态:降级出来的规则块,幂等键是 id。
12
+ export interface PromptReminderSource {
13
+ kind: "context-assembler";
14
+ form: "instructions";
15
+ // 条目 id:同 id 的最新一条取代更早的同 id 条目;可选——既有消息里可能没有它。
16
+ id?: string;
17
+ }
18
+
19
+ declare module "@deepseek-ai/dsh-llm" {
20
+ interface MessageSourceMap {
21
+ "context-assembler": PromptReminderSource;
22
+ }
23
+ }
24
+
25
+ // 这条消息是不是通道注入的条目——判据是 source 里的幂等键(`id`),不是 kind:覆盖按 id 判定,kind 只描述
26
+ // 这条消息对外的身份。
27
+ function promptEntryIdOf(source: MessageSource): string | undefined {
28
+ const id = (source as { readonly id?: unknown }).id;
29
+ return typeof id === "string" ? id : undefined;
30
+ }
31
+
32
+ function escapeFrameBody(body: string): string {
33
+ return body.replaceAll(REMINDER_CLOSE, "<\\/system-reminder>");
34
+ }
35
+
36
+ function escapeAttr(value: string): string {
37
+ return value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("<", "&lt;");
38
+ }
39
+
40
+ export function renderReminder(id: string, body: string): string {
41
+ return `${REMINDER_OPEN} id="${escapeAttr(id)}">\n${escapeFrameBody(body)}\n${REMINDER_CLOSE}`;
42
+ }
43
+
44
+ // surface 上最近一条该 id 的 reminder 原文(含信封);没有则 undefined。
45
+ export function latestReminderText(agent: Agent, id: string): string | undefined {
46
+ for (const seq of agent.session.surface.nodes.toReversed()) {
47
+ const event = agent.session.eventAt(seq);
48
+ if (event?.type !== "user/message") continue;
49
+ if (promptEntryIdOf(event.data.source) !== id) continue;
50
+ const [block] = event.data.content;
51
+ return event.data.content.length === 1 && block?.type === "text" ? block.text : "";
52
+ }
53
+ return undefined;
54
+ }
55
+
56
+ // 注入一条提醒:`text` 是已渲染好的正文(规则块),`key` 是它的幂等键。
57
+ export function reminderMessage(key: string, text: string): UserMessage {
58
+ return createUserMessage({
59
+ content: [{ type: "text", text }],
60
+ source: { kind: "context-assembler", form: "instructions", id: key },
61
+ });
62
+ }
package/src/rows.ts CHANGED
@@ -1,65 +1,25 @@
1
- /**
2
- * 本包作为 **bundle** 的行清单:`dsh.profile.bundles` 列出本包时由它渲染成 `cordis.patch.yml`。
3
- *
4
- * 通道装在 host 平面,整份部署共享一套注入。**不做隔离**——工具说明
5
- * (`@morlay/dsh-agent-toolkit/guidance`,在别的包里)与 skill 目录这类消费者都在别的行上
6
- * `inject` 它,隔离会把它们挡在组外(行停在 waiting,不报错)。
7
- *
8
- * 本包自己的 bundle 只装通道(`PATCH_ROWS`)。模式的收口是另一行(`scopeRow()`),它由
9
- * [`@morlay/dsh-session-mode`](../../../profile/dsh-session-mode/README.md) 的 patch 与模式定义一起装——
10
- * 那一行的 config 就是模式清单。
11
- */
1
+ // 本包那一行的真源(`contextChannel()`):`dsh.profile.bundles` 里列的是 bundle(本部署是
2
+ // `@morlay/session-mode-profile`),它 import 本模块拼装,构建期由 devkit 的 `bundlePatch` 写出包根那份
3
+ // `cordis.patch.yml`。消费方把它包成 `insert` 层:写成"改已有行"的形态时,装配期找不到目标行(只 warn 后跳过),
4
+ // 引用它的行停在 waiting。
5
+ //
6
+ // 通道装在 host 平面、整份部署共享一套注入,**不做隔离**:消费者(`@morlay/dsh-tool-guidance` 的包根)住在
7
+ // 别的包里、在别的行上 `inject` 它,隔离会把它们挡在组外(行停在 waiting,不报错)。
12
8
 
13
- /** 组装出口(包根)就是那个"按 config 装能力"的插件。 */
9
+ // 通道那一行的插件 name:包根(`@morlay/dsh-context-assembler`)就是通道本体。
14
10
  export const CONTEXT_PACKAGE = "@morlay/dsh-context-assembler";
15
11
 
16
- /** 一行 plugin entry(本模块只描述形状,类型由消费方自己那份行类型决定)。 */
12
+ // 一行 plugin entry(本模块只描述形状,类型由消费方自己那份行类型决定)。
17
13
  export interface ContextRow {
18
14
  readonly id: string;
19
15
  readonly name: string;
20
16
  readonly config?: Readonly<Record<string, unknown>>;
21
17
  }
22
18
 
23
- /**
24
- * 通道那一行:包的主出口是**组装插件**——按 config 决定装哪些能力、各带什么参数,所以装配面
25
- * 只有这一行,组成与各能力的 config 都归包内(缺省即完整的一套;`chat` 用它裁剪)。
26
- */
27
- export function contextChannel(config?: Readonly<Record<string, unknown>>): ContextRow {
19
+ // 通道那一行:**不带 config**——包根即通道本体,`keep` / `suppress` / `replace` 的缺省就是本部署要的那一份。
20
+ export function contextChannel(): ContextRow {
28
21
  return {
29
22
  id: "context-assembler",
30
23
  name: CONTEXT_PACKAGE,
31
- ...(config === undefined ? {} : { config }),
32
24
  };
33
25
  }
34
-
35
- /**
36
- * 模式收口那一行:`scope` 出口按 `ctx.sessionModes` 的模式定义收口(工具白名单、instruction 总开关、
37
- * 动态快照开关)。
38
- *
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 插一行)。
45
- */
46
- export function scopeRow(): ContextRow {
47
- return {
48
- id: "context-assembler-scope",
49
- name: "@morlay/dsh-context-assembler/scope",
50
- };
51
- }
52
-
53
- /**
54
- * bundle patch 的行清单:只有通道一行(模式收口那一行归 `session-mode` 的 patch 装)。
55
- *
56
- * 形态是 `insert`:这些行由本包提供给 host 平面。写成 `- id: x` 那种"改已有行"的形态时,装配期找不到
57
- * 目标行(只 warn 后跳过),通道根本没装上,引用它的行停在 waiting。
58
- */
59
- export const PATCH_ROWS: readonly { readonly insert: readonly ContextRow[] }[] = [
60
- {
61
- insert: [
62
- contextChannel({ capabilities: ["assembler", "agent-instructions", "skill-catalog"] }),
63
- ],
64
- },
65
- ];
package/src/rules.ts ADDED
@@ -0,0 +1,11 @@
1
+ import { RULES_SECTION } from "./reminder.ts";
2
+
3
+ // 覆盖规则的声明文本:写进**系统提示词**(不在 reminder 里重复),所以每条 reminder 只带 id 与正文。
4
+ // 放系统提示词是因为它是固定文本(不随会话变、不付缓存代价),且 reminder 会被压缩与覆盖、权威性更低。
5
+ // 官方那两行(工作区指令、技能目录)写的是**不带 id** 的块,所以声明按"带 id 看 id、其余按来源"说覆盖。
6
+ export const RULES_TEXT = [
7
+ "system-reminder 与 skill_content 块是本系统提示词的一部分,持续有效:同一条内容的最新一条取代更早的(带 id 的块以 id 为准,其余按来源),不同来源互不影响。",
8
+ "skill_content 是要遵循的技能说明(随请求送来的与你后来加载的一样),file_content 是本步引用的文件内容。",
9
+ ].join("");
10
+
11
+ export { RULES_SECTION };