@morlay/dsh-context-assembler 0.0.1-alpha.2
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/LICENSE +21 -0
- package/README.md +154 -0
- package/cordis.patch.yml +9 -0
- package/dist/agent-instructions-BNEiZl0P.mjs +202 -0
- package/dist/agent-instructions.d.mts +27 -0
- package/dist/agent-instructions.mjs +2 -0
- package/dist/assembler-_F9urbPj.mjs +409 -0
- package/dist/assembler.d.mts +2 -0
- package/dist/assembler.mjs +2 -0
- package/dist/index-BGkeEaQA.d.mts +196 -0
- package/dist/index.d.mts +25 -0
- package/dist/index.mjs +29 -0
- package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
- package/dist/rows.d.mts +46 -0
- package/dist/rows.mjs +50 -0
- package/dist/scope-CI-fNq5A.mjs +104 -0
- package/dist/scope.d.mts +47 -0
- package/dist/scope.mjs +2 -0
- package/dist/skill-catalog.d.mts +16 -0
- package/dist/skill-catalog.mjs +184 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +85 -0
- package/src/agent-instructions/baseline.ts +75 -0
- package/src/agent-instructions/files.ts +117 -0
- package/src/agent-instructions/index.ts +107 -0
- package/src/assembler/channel.ts +285 -0
- package/src/assembler/defaults.ts +53 -0
- package/src/assembler/index.ts +129 -0
- package/src/assembler/reminder.ts +92 -0
- package/src/assembler/rules.ts +14 -0
- package/src/index.ts +56 -0
- package/src/rows.ts +64 -0
- package/src/scope/index.ts +118 -0
- package/src/skill-catalog/index.ts +177 -0
- package/tool/patch.ts +43 -0
|
@@ -0,0 +1,13 @@
|
|
|
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/rows.d.mts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
//#region src/rows.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* 本包作为 **bundle** 的行清单:`dsh.profile.bundles` 列出本包时由它渲染成 `cordis.patch.yml`。
|
|
4
|
+
*
|
|
5
|
+
* 通道装在 host 平面,整份部署共享一套注入。**不做隔离**——工具说明
|
|
6
|
+
* (`@morlay/dsh-agent-toolkit/guidance`,在别的包里)与 skill 目录这类消费者都在别的行上
|
|
7
|
+
* `inject` 它,隔离会把它们挡在组外(行停在 waiting,不报错)。
|
|
8
|
+
*
|
|
9
|
+
* preset 只装 `scopeRow()`(模式的开关):它 `inject` 通道,`ctx.get("contextAssembler")` 在 agent
|
|
10
|
+
* 子树里可达,不需要另装一份通道。
|
|
11
|
+
*/
|
|
12
|
+
/** 组装出口(包根)就是那个"按 config 装能力"的插件。 */
|
|
13
|
+
declare const CONTEXT_PACKAGE = "@morlay/dsh-context-assembler";
|
|
14
|
+
/** 一行 plugin entry(本模块只描述形状,类型由消费方自己那份行类型决定)。 */
|
|
15
|
+
interface ContextRow {
|
|
16
|
+
readonly id: string;
|
|
17
|
+
readonly name: string;
|
|
18
|
+
readonly config?: Readonly<Record<string, unknown>>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* 通道那一行:包的主出口是**组装插件**——按 config 决定装哪些能力、各带什么参数,所以装配面
|
|
22
|
+
* 只有这一行,组成与各能力的 config 都归包内(缺省即完整的一套;`chat` 用它裁剪)。
|
|
23
|
+
*/
|
|
24
|
+
declare function contextChannel(config?: Readonly<Record<string, unknown>>): ContextRow;
|
|
25
|
+
/**
|
|
26
|
+
* 模式开关那一行:`scope` 出口按 preset 配(工具白名单、instruction 总开关、动态快照开关)。
|
|
27
|
+
*
|
|
28
|
+
* 通道在 profile 平面装一次,这一行只按模式收口(它 `inject` `contextAssembler`,
|
|
29
|
+
* `ctx.get("contextAssembler")` 在 agent 子树里可达)。
|
|
30
|
+
*/
|
|
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
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* bundle patch 的行清单:通道一行(`scope` 是模式的开关,由 preset 装 `scopeRow()`)。
|
|
38
|
+
*
|
|
39
|
+
* 形态是 `insert`:这些行由本包提供给 host 平面。写成 `- id: x` 那种"改已有行"的形态时,装配期找不到
|
|
40
|
+
* 目标行(只 warn 后跳过),通道根本没装上,引用它的行停在 waiting。
|
|
41
|
+
*/
|
|
42
|
+
declare const PATCH_ROWS: readonly {
|
|
43
|
+
readonly insert: readonly ContextRow[];
|
|
44
|
+
}[];
|
|
45
|
+
//#endregion
|
|
46
|
+
export { CONTEXT_PACKAGE, ContextRow, PATCH_ROWS, contextChannel, scopeRow };
|
package/dist/rows.mjs
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
//#region src/rows.ts
|
|
2
|
+
/**
|
|
3
|
+
* 本包作为 **bundle** 的行清单:`dsh.profile.bundles` 列出本包时由它渲染成 `cordis.patch.yml`。
|
|
4
|
+
*
|
|
5
|
+
* 通道装在 host 平面,整份部署共享一套注入。**不做隔离**——工具说明
|
|
6
|
+
* (`@morlay/dsh-agent-toolkit/guidance`,在别的包里)与 skill 目录这类消费者都在别的行上
|
|
7
|
+
* `inject` 它,隔离会把它们挡在组外(行停在 waiting,不报错)。
|
|
8
|
+
*
|
|
9
|
+
* preset 只装 `scopeRow()`(模式的开关):它 `inject` 通道,`ctx.get("contextAssembler")` 在 agent
|
|
10
|
+
* 子树里可达,不需要另装一份通道。
|
|
11
|
+
*/
|
|
12
|
+
/** 组装出口(包根)就是那个"按 config 装能力"的插件。 */
|
|
13
|
+
const CONTEXT_PACKAGE = "@morlay/dsh-context-assembler";
|
|
14
|
+
/**
|
|
15
|
+
* 通道那一行:包的主出口是**组装插件**——按 config 决定装哪些能力、各带什么参数,所以装配面
|
|
16
|
+
* 只有这一行,组成与各能力的 config 都归包内(缺省即完整的一套;`chat` 用它裁剪)。
|
|
17
|
+
*/
|
|
18
|
+
function contextChannel(config) {
|
|
19
|
+
return {
|
|
20
|
+
id: "context-assembler",
|
|
21
|
+
name: CONTEXT_PACKAGE,
|
|
22
|
+
...config === void 0 ? {} : { config }
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* 模式开关那一行:`scope` 出口按 preset 配(工具白名单、instruction 总开关、动态快照开关)。
|
|
27
|
+
*
|
|
28
|
+
* 通道在 profile 平面装一次,这一行只按模式收口(它 `inject` `contextAssembler`,
|
|
29
|
+
* `ctx.get("contextAssembler")` 在 agent 子树里可达)。
|
|
30
|
+
*/
|
|
31
|
+
function scopeRow(config) {
|
|
32
|
+
return {
|
|
33
|
+
id: "context-scope",
|
|
34
|
+
name: "@morlay/dsh-context-assembler/scope",
|
|
35
|
+
...config === void 0 ? {} : { config }
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* bundle patch 的行清单:通道一行(`scope` 是模式的开关,由 preset 装 `scopeRow()`)。
|
|
40
|
+
*
|
|
41
|
+
* 形态是 `insert`:这些行由本包提供给 host 平面。写成 `- id: x` 那种"改已有行"的形态时,装配期找不到
|
|
42
|
+
* 目标行(只 warn 后跳过),通道根本没装上,引用它的行停在 waiting。
|
|
43
|
+
*/
|
|
44
|
+
const PATCH_ROWS = [{ insert: [contextChannel({ capabilities: [
|
|
45
|
+
"assembler",
|
|
46
|
+
"agent-instructions",
|
|
47
|
+
"skill-catalog"
|
|
48
|
+
] })] }];
|
|
49
|
+
//#endregion
|
|
50
|
+
export { CONTEXT_PACKAGE, PATCH_ROWS, contextChannel, scopeRow };
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
2
|
+
import z from "@deepseek-ai/schemastery";
|
|
3
|
+
import "@deepseek-ai/cordis";
|
|
4
|
+
//#region ../../../vendor/deepseek-harness/packages/core/scope/lib/index.js
|
|
5
|
+
/**
|
|
6
|
+
* Scoped-context primitive: mint a Cordis context that tags registrations with
|
|
7
|
+
* an opaque identity and build routing-only event carriers for that identity.
|
|
8
|
+
*
|
|
9
|
+
* @module @deepseek-ai/dsh-scope
|
|
10
|
+
*/
|
|
11
|
+
/** Context tag written by {@link createScope}. */
|
|
12
|
+
const kScope = Symbol("dsh.scope");
|
|
13
|
+
/**
|
|
14
|
+
* The enclosing scope of each key. One relation powers both directions of
|
|
15
|
+
* scope nesting: registration views inherit DOWN the chain (a child scope
|
|
16
|
+
* sees its ancestors' layers — {@link ScopedLayers}), and event admission
|
|
17
|
+
* extends UP it (a listener tagged with an ancestor receives events dispatched
|
|
18
|
+
* to a descendant key — {@link scopeTarget}).
|
|
19
|
+
*/
|
|
20
|
+
const scopeParents = /* @__PURE__ */ new WeakMap();
|
|
21
|
+
/**
|
|
22
|
+
* The chain from a key to its root ancestor.
|
|
23
|
+
* @param key - the starting key, or `undefined` for the empty chain.
|
|
24
|
+
* @returns keys nearest-first: `[key, parent, grandparent, …]`.
|
|
25
|
+
*/
|
|
26
|
+
function scopeChainOf(key) {
|
|
27
|
+
const chain = [];
|
|
28
|
+
for (let cursor = key; cursor !== void 0; cursor = scopeParents.get(cursor)) chain.push(cursor);
|
|
29
|
+
return chain;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Read the nearest scope tag inherited by a context.
|
|
33
|
+
* @param ctx - context to inspect.
|
|
34
|
+
* @returns its scope key, or `undefined` for an unscoped context.
|
|
35
|
+
*/
|
|
36
|
+
function scopeOf(ctx) {
|
|
37
|
+
return ctx[kScope];
|
|
38
|
+
}
|
|
39
|
+
//#endregion
|
|
40
|
+
//#region src/scope/index.ts
|
|
41
|
+
var scope_exports = /* @__PURE__ */ __exportAll({
|
|
42
|
+
Config: () => Config,
|
|
43
|
+
apply: () => apply,
|
|
44
|
+
inject: () => inject,
|
|
45
|
+
name: () => name
|
|
46
|
+
});
|
|
47
|
+
const name = "context-scope";
|
|
48
|
+
const inject = ["tools", "systemPrompt"];
|
|
49
|
+
const Config = z.object({
|
|
50
|
+
allowTools: z.array(z.string()).default([]),
|
|
51
|
+
instructions: z.boolean().default(true),
|
|
52
|
+
runtimeContext: z.boolean().default(true)
|
|
53
|
+
});
|
|
54
|
+
const OUT_OF_SCOPE = (toolName) => `${toolName} 不在本模式的工具范围内,用它不会有结果;按当前模式提供的工具完成任务,或让用户切到别的模式。`;
|
|
55
|
+
/** 单个工具的说明 section 名前缀:`tool:<工具名>`(`tools:` 那类是聚合块,不归白名单管)。 */
|
|
56
|
+
const TOOL_SECTION_PREFIX = "tool:";
|
|
57
|
+
/**
|
|
58
|
+
* 把某个 preset 的工具收成"只有这些"。
|
|
59
|
+
*
|
|
60
|
+
* 为什么需要它:preset composition 只能决定"加什么",管不了 host 层——`dsh.profile.bundles` 打开的
|
|
61
|
+
* bundle(例如实验性的 Agent Teams)会在 host 层插工具行与 section,对所有 preset 一视同仁。模式想
|
|
62
|
+
* 表达"我只有这几个工具、一条提示词都不要"时,唯一与来源无关的做法是收口"有什么"。
|
|
63
|
+
*
|
|
64
|
+
* 为什么不是 `tools.restrict()`:它会改动可见工具集,从而触发 `tools/change`;上游 `tool-subagent`
|
|
65
|
+
* 正是用该事件做 composition reconcile——两边互相触发会变成装配风暴。这里用投影过滤加执行层 guard:
|
|
66
|
+
* 既不碰注册表、也不发事件。
|
|
67
|
+
*/
|
|
68
|
+
function apply(ctx, config) {
|
|
69
|
+
const allow = new Set(config.allowTools ?? []);
|
|
70
|
+
if (allow.size === 0) throw new Error("context-scope: `allowTools` must name at least one tool; omit the row instead of mounting an empty scope");
|
|
71
|
+
const presetKey = scopeOf(ctx);
|
|
72
|
+
const scoped = /* @__PURE__ */ new WeakSet();
|
|
73
|
+
if (config.runtimeContext === false) ctx.systemPrompt.suppressRuntimeContext();
|
|
74
|
+
const belongs = (agent) => {
|
|
75
|
+
if (presetKey === void 0) return true;
|
|
76
|
+
const key = scopeOf(agent.ctx);
|
|
77
|
+
return key !== void 0 && scopeChainOf(key).includes(presetKey);
|
|
78
|
+
};
|
|
79
|
+
/** 每个会话只做一次:拨提示词开关,并挂上执行层白名单。 */
|
|
80
|
+
const scopeOnce = (agent) => {
|
|
81
|
+
if (scoped.has(agent) || !belongs(agent)) return;
|
|
82
|
+
scoped.add(agent);
|
|
83
|
+
try {
|
|
84
|
+
const channel = ctx.get("contextAssembler");
|
|
85
|
+
if (config.instructions === false) channel?.setInstructions(agent, false);
|
|
86
|
+
channel?.restrictTools(agent, (tool) => allow.has(tool));
|
|
87
|
+
} catch {}
|
|
88
|
+
agent.ctx.inject(["tools"], () => {
|
|
89
|
+
agent.ctx.tools.guard((exec) => allow.has(exec.name) ? void 0 : OUT_OF_SCOPE(exec.name));
|
|
90
|
+
});
|
|
91
|
+
};
|
|
92
|
+
ctx.on("system-prompt/assemble", async (_assembly, context, next) => {
|
|
93
|
+
const agent = context.agent;
|
|
94
|
+
if (agent !== void 0) scopeOnce(agent);
|
|
95
|
+
const result = await next();
|
|
96
|
+
return {
|
|
97
|
+
...result,
|
|
98
|
+
sections: result.sections.filter((section) => section.name.startsWith(TOOL_SECTION_PREFIX) ? allow.has(section.name.slice(5)) : true),
|
|
99
|
+
tools: result.tools.filter((tool) => allow.has(tool.name))
|
|
100
|
+
};
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
//#endregion
|
|
104
|
+
export { scope_exports as a, name as i, apply as n, inject as r, Config as t };
|
package/dist/scope.d.mts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import z from "@deepseek-ai/schemastery";
|
|
2
|
+
import { Context } from "@deepseek-ai/cordis";
|
|
3
|
+
declare namespace index_d_exports {
|
|
4
|
+
export { Config, apply, inject, name };
|
|
5
|
+
}
|
|
6
|
+
declare const name = "context-scope";
|
|
7
|
+
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;
|
|
24
|
+
/**
|
|
25
|
+
* 是否要 runtime context(动态快照:文件沙箱策略、审批策略)。缺省要;`false` 表示这个模式不要它们
|
|
26
|
+
* ——对话模式没有文件与 shell 工具,"能改工作区哪些文件、要不要走审批"对它全是噪音。
|
|
27
|
+
*
|
|
28
|
+
* 抑制是**按 scope** 的(上游按装配的 scope 链查抑制器),所以本行装在 preset 子树里就只作用于这个
|
|
29
|
+
* 模式的会话;它只挡注入,不改任何提供方的行为(沙箱该怎么判还怎么判)。
|
|
30
|
+
*/
|
|
31
|
+
runtimeContext?: boolean;
|
|
32
|
+
}
|
|
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;
|
|
46
|
+
//#endregion
|
|
47
|
+
export { Config, apply, inject, name, index_d_exports as t };
|
package/dist/scope.mjs
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
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
|
+
declare function apply(ctx: Context): void;
|
|
15
|
+
//#endregion
|
|
16
|
+
export { CATALOG_ID, apply, inject, name, index_d_exports as t };
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
2
|
+
import { u as renderVirtualSkill } from "./assembler-_F9urbPj.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
|
+
"skills",
|
|
15
|
+
"tools",
|
|
16
|
+
"contextAssembler"
|
|
17
|
+
];
|
|
18
|
+
const CATALOG_ID = "skill-catalog";
|
|
19
|
+
/** 目录行里的描述长度上限(上游默认同值)。 */
|
|
20
|
+
const DESCRIPTION_MAX_LENGTH = 500;
|
|
21
|
+
/**
|
|
22
|
+
* skill 目录 + `skill` 工具:目录是规则块(一行名字 + 摘要),正文按需加载。
|
|
23
|
+
*
|
|
24
|
+
* 目录只列模型可调用的 skill;`auto` 的 skill 标 `modelInvocable: false`,所以它不出现在目录里
|
|
25
|
+
* (它的正文已经随提示送达)。工具的渲染用通道的虚拟 skill 形态,不带 `<skill_resources>`。
|
|
26
|
+
*/
|
|
27
|
+
function apply(ctx) {
|
|
28
|
+
/**
|
|
29
|
+
* 本会话上次算出的目录条目:`source` 是同步的、正文是异步的,两者共用这一次计算的结果
|
|
30
|
+
* (`collect` 里先算正文再取 source)。
|
|
31
|
+
*/
|
|
32
|
+
const catalogEntries = /* @__PURE__ */ new WeakMap();
|
|
33
|
+
ctx.contextAssembler.registerRule({
|
|
34
|
+
id: CATALOG_ID,
|
|
35
|
+
source: (agent) => ({
|
|
36
|
+
kind: "skill-catalog",
|
|
37
|
+
form: "catalog",
|
|
38
|
+
entries: catalogEntries.get(agent) ?? []
|
|
39
|
+
}),
|
|
40
|
+
text: async (agent) => {
|
|
41
|
+
const noCatalog = () => {
|
|
42
|
+
catalogEntries.delete(agent);
|
|
43
|
+
return "";
|
|
44
|
+
};
|
|
45
|
+
const visible = ctx.contextAssembler.visibleTools(agent, (tool) => ctx.tools.get(tool, agent) !== void 0);
|
|
46
|
+
if (!visible("skill")) return noCatalog();
|
|
47
|
+
const snapshot = await ctx.skills.snapshot({
|
|
48
|
+
cwd: agent.session.header.cwd,
|
|
49
|
+
scope: agent
|
|
50
|
+
});
|
|
51
|
+
if (!snapshot.complete) return noCatalog();
|
|
52
|
+
const hidden = ctx.contextAssembler.hiddenSkills(visible);
|
|
53
|
+
const skills = snapshot.skills.filter(isModelInvocable).filter((skill) => !hidden.has(skill.name));
|
|
54
|
+
if (skills.length === 0) return noCatalog();
|
|
55
|
+
const entries = skills.map((skill) => ({
|
|
56
|
+
name: skill.name,
|
|
57
|
+
description: clamp(skill.description, DESCRIPTION_MAX_LENGTH)
|
|
58
|
+
}));
|
|
59
|
+
catalogEntries.set(agent, entries);
|
|
60
|
+
return [
|
|
61
|
+
"<available_skills>",
|
|
62
|
+
...entries.map((entry) => `- ${entry.name}: ${entry.description}`),
|
|
63
|
+
"</available_skills>",
|
|
64
|
+
"",
|
|
65
|
+
"任务与某个 skill 说明匹配时,先加载它再行事。"
|
|
66
|
+
].join("\n");
|
|
67
|
+
}
|
|
68
|
+
});
|
|
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
|
+
}
|
|
72
|
+
function registerSkillTool(ctx) {
|
|
73
|
+
const skillTool = defineTool({
|
|
74
|
+
name: "skill",
|
|
75
|
+
description: "按需加载 skill 的完整说明。",
|
|
76
|
+
parameters: { name: {
|
|
77
|
+
type: "string",
|
|
78
|
+
required: true,
|
|
79
|
+
description: "技能目录里的 skill 名。"
|
|
80
|
+
} },
|
|
81
|
+
output: {
|
|
82
|
+
schema: {
|
|
83
|
+
type: "object",
|
|
84
|
+
additionalProperties: false,
|
|
85
|
+
properties: {
|
|
86
|
+
name: {
|
|
87
|
+
type: "string",
|
|
88
|
+
required: true
|
|
89
|
+
},
|
|
90
|
+
provider: {
|
|
91
|
+
type: "string",
|
|
92
|
+
required: true
|
|
93
|
+
},
|
|
94
|
+
resourceBase: { oneOf: [
|
|
95
|
+
{
|
|
96
|
+
type: "object",
|
|
97
|
+
additionalProperties: false,
|
|
98
|
+
properties: {
|
|
99
|
+
kind: {
|
|
100
|
+
type: "string",
|
|
101
|
+
required: true,
|
|
102
|
+
const: "directory"
|
|
103
|
+
},
|
|
104
|
+
path: {
|
|
105
|
+
type: "string",
|
|
106
|
+
required: true
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
type: "object",
|
|
112
|
+
additionalProperties: false,
|
|
113
|
+
properties: {
|
|
114
|
+
kind: {
|
|
115
|
+
type: "string",
|
|
116
|
+
required: true,
|
|
117
|
+
const: "url"
|
|
118
|
+
},
|
|
119
|
+
url: {
|
|
120
|
+
type: "string",
|
|
121
|
+
required: true
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
type: "object",
|
|
127
|
+
additionalProperties: false,
|
|
128
|
+
properties: {
|
|
129
|
+
kind: {
|
|
130
|
+
type: "string",
|
|
131
|
+
required: true,
|
|
132
|
+
const: "opaque"
|
|
133
|
+
},
|
|
134
|
+
description: {
|
|
135
|
+
type: "string",
|
|
136
|
+
required: true
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
] },
|
|
141
|
+
content: {
|
|
142
|
+
type: "string",
|
|
143
|
+
required: true
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
render: (_args, value) => [{
|
|
148
|
+
type: "text",
|
|
149
|
+
text: renderVirtualSkill(value.name, value.content)
|
|
150
|
+
}]
|
|
151
|
+
},
|
|
152
|
+
async execute(args, exec) {
|
|
153
|
+
if (!isSkillName(args.name)) throw new Error(`invalid skill name "${args.name}"`);
|
|
154
|
+
const lookup = {
|
|
155
|
+
cwd: exec.agent?.session.header.cwd,
|
|
156
|
+
signal: exec.signal,
|
|
157
|
+
scope: exec.agent
|
|
158
|
+
};
|
|
159
|
+
const skill = await ctx.skills.get(args.name, lookup);
|
|
160
|
+
if (skill === void 0) throw new Error(`skill "${args.name}" is unknown or no longer available`);
|
|
161
|
+
if (!isModelInvocable(skill)) throw new Error(`skill "${args.name}" is not available for model invocation`);
|
|
162
|
+
const content = ctx.contextAssembler.contentFor(skill.name, exec.agent) ?? skill.content;
|
|
163
|
+
return {
|
|
164
|
+
name: skill.name,
|
|
165
|
+
provider: skill.provider,
|
|
166
|
+
content
|
|
167
|
+
};
|
|
168
|
+
},
|
|
169
|
+
presentCall(args) {
|
|
170
|
+
return {
|
|
171
|
+
card: "generic",
|
|
172
|
+
title: `Load skill ${args.name}`,
|
|
173
|
+
kind: "read",
|
|
174
|
+
rawInput: args.name
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
ctx.tools.register(skillTool);
|
|
179
|
+
}
|
|
180
|
+
function clamp(text, max) {
|
|
181
|
+
return text.length <= max ? text : `${text.slice(0, max - 1)}…`;
|
|
182
|
+
}
|
|
183
|
+
//#endregion
|
|
184
|
+
export { CATALOG_ID, apply, inject, name, skill_catalog_exports as t };
|
package/locale/en.json
ADDED
package/locale/zh.json
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@morlay/dsh-context-assembler",
|
|
3
|
+
"version": "0.0.1-alpha.2",
|
|
4
|
+
"description": "Prompt-injection capability set for dsh: the single channel (context assembler) plus the injectors it carries — workspace instructions, skill catalog, tool-usage guidance, and the per-mode scope gate. Reference expansion is its own package (@morlay/dsh-reference).",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"context",
|
|
7
|
+
"dsh",
|
|
8
|
+
"dsh-plugin",
|
|
9
|
+
"injection",
|
|
10
|
+
"system-prompt"
|
|
11
|
+
],
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "https://github.com/morlay/dsh-plugin.git"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"locale/*.json",
|
|
19
|
+
"dist",
|
|
20
|
+
"src",
|
|
21
|
+
"tool",
|
|
22
|
+
"cordis.patch.yml",
|
|
23
|
+
"!**/__tests__"
|
|
24
|
+
],
|
|
25
|
+
"type": "module",
|
|
26
|
+
"exports": {
|
|
27
|
+
".": "./dist/index.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
|
+
"./package.json": "./package.json",
|
|
33
|
+
"./rows": "./dist/rows.mjs",
|
|
34
|
+
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
35
|
+
"./locale/*.json": "./locale/*.json"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@deepseek-ai/schemastery": "3.18.4"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@deepseek-ai/cordis": "4.0.4",
|
|
42
|
+
"@deepseek-ai/cordis-plugin-include": "1.0.9",
|
|
43
|
+
"@deepseek-ai/dsh-agent": "0.1.7-alpha.2",
|
|
44
|
+
"@deepseek-ai/dsh-agent-instructions": "0.1.7-alpha.2",
|
|
45
|
+
"@deepseek-ai/dsh-agent-loop": "0.1.7-alpha.2",
|
|
46
|
+
"@deepseek-ai/dsh-agent-loop-testkit": "0.1.7-alpha.2",
|
|
47
|
+
"@deepseek-ai/dsh-commands": "0.1.7-alpha.2",
|
|
48
|
+
"@deepseek-ai/dsh-compaction-basic": "0.1.7-alpha.2",
|
|
49
|
+
"@deepseek-ai/dsh-fs": "0.1.7-alpha.2",
|
|
50
|
+
"@deepseek-ai/dsh-llm": "0.1.7-alpha.2",
|
|
51
|
+
"@deepseek-ai/dsh-scope": "0.1.7-alpha.2",
|
|
52
|
+
"@deepseek-ai/dsh-session": "0.1.7-alpha.2",
|
|
53
|
+
"@deepseek-ai/dsh-session-persistence-jsonl": "0.1.7-alpha.2",
|
|
54
|
+
"@deepseek-ai/dsh-skill": "0.1.7-alpha.2",
|
|
55
|
+
"@deepseek-ai/dsh-skill-filesystem": "0.1.7-alpha.2",
|
|
56
|
+
"@deepseek-ai/dsh-system-prompt": "0.1.7-alpha.2",
|
|
57
|
+
"@deepseek-ai/dsh-token-meter": "0.1.7-alpha.2",
|
|
58
|
+
"@deepseek-ai/dsh-tool-skill": "0.1.7-alpha.2",
|
|
59
|
+
"@deepseek-ai/dsh-tools": "0.1.7-alpha.2",
|
|
60
|
+
"@morlay/session-rdb": "0.0.21-alpha.7",
|
|
61
|
+
"@types/js-yaml": "^4.0.9",
|
|
62
|
+
"@types/mdast": "^4.0.4",
|
|
63
|
+
"js-yaml": "^4.1.0"
|
|
64
|
+
},
|
|
65
|
+
"peerDependencies": {
|
|
66
|
+
"@deepseek-ai/cordis": "4.0.4",
|
|
67
|
+
"@deepseek-ai/dsh-agent": "0.1.7-alpha.2",
|
|
68
|
+
"@deepseek-ai/dsh-agent-instructions": "0.1.7-alpha.2",
|
|
69
|
+
"@deepseek-ai/dsh-commands": "0.1.7-alpha.2",
|
|
70
|
+
"@deepseek-ai/dsh-fs": "0.1.7-alpha.2",
|
|
71
|
+
"@deepseek-ai/dsh-llm": "0.1.7-alpha.2",
|
|
72
|
+
"@deepseek-ai/dsh-session": "0.1.7-alpha.2",
|
|
73
|
+
"@deepseek-ai/dsh-skill": "0.1.7-alpha.2",
|
|
74
|
+
"@deepseek-ai/dsh-system-prompt": "0.1.7-alpha.2",
|
|
75
|
+
"@deepseek-ai/dsh-tools": "0.1.7-alpha.2"
|
|
76
|
+
},
|
|
77
|
+
"dsh": {
|
|
78
|
+
"bundle": {
|
|
79
|
+
"patch": "./cordis.patch.yml"
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
"scripts": {
|
|
83
|
+
"build": "pnpm exec tsdown"
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { stat } from "node:fs/promises";
|
|
2
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
3
|
+
import { Config as UpstreamConfig } from "@deepseek-ai/dsh-agent-instructions";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 上游 baseline 身份:`@deepseek-ai/dsh-agent-instructions` 用它在会话 surface 上认领"基线已注入"。
|
|
7
|
+
*
|
|
8
|
+
* 为什么我们要算它:接管那面之后,我们注入的条目**沿用上游 kind**(客户端「上下文注入」标签与按 kind
|
|
9
|
+
* 认领的消费方才认得这是工作区指令),但只沿用一半不够——上游的认领判据是
|
|
10
|
+
* `source.kind === 'agent-instructions' && source.baseline === true` 且 `baselineIdentity` 与它自己算出的
|
|
11
|
+
* identity **逐字相等**。少这两样,上游(官方 preset 自己装的那行)就认为基线不存在,于是**再注入一条**:
|
|
12
|
+
* 同会话切到官方 preset 后会同时存在两条 AGENTS.md——一条是这份正文、一条是上游模板("Use them as
|
|
13
|
+
* guidance…"),口径互相矛盾,而模型无法把后一条理解成"作废前一条"(两条来源的幂等键不同)。
|
|
14
|
+
*
|
|
15
|
+
* 身份要与上游逐字对齐,但它的 `resolveConfig` / `workspaceBaselineIdentity` / `findProjectRoot` **不在包
|
|
16
|
+
* 出口面**(发布包 `files` 只有 `lib/index.js` 与声明),所以这里复刻它的形状:默认值取自**导出的
|
|
17
|
+
* `Config` schema**(跟随上游默认值演进),字段名与顺序照它的 `workspaceBaselineIdentity` 抄一份;
|
|
18
|
+
* `baseline.spec.ts` 用上游 `./src/*` 出口的真函数比对,防这份复刻悄悄漂移。
|
|
19
|
+
*
|
|
20
|
+
* 配置基线是 base bundle 给那行的 `maxBytes: 65536`(其余取 schema 默认)——与它同源,身份才相等。
|
|
21
|
+
*/
|
|
22
|
+
const BASE_MAX_BYTES = 65_536;
|
|
23
|
+
|
|
24
|
+
/** 上游那行的身份基线:与 `packages/bundle/base/cordis.patch.yml` 的 `agent-instructions` 行 config 同源。 */
|
|
25
|
+
export const UPSTREAM_BASELINE_CONFIG = { maxBytes: BASE_MAX_BYTES } as const;
|
|
26
|
+
|
|
27
|
+
interface BaselineConfig {
|
|
28
|
+
readonly projectRootMarkers: string[];
|
|
29
|
+
readonly maxBytes: number;
|
|
30
|
+
readonly maxSourceBytes: number;
|
|
31
|
+
readonly instructionFileCandidates: string[];
|
|
32
|
+
readonly localInstructionFileCandidates: string[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** schema 调用会把缺省项补齐,所以这里拿到的是上游 schema 的实际默认值。 */
|
|
36
|
+
function resolveBaselineConfig(): BaselineConfig {
|
|
37
|
+
return UpstreamConfig({ ...UPSTREAM_BASELINE_CONFIG }) as unknown as BaselineConfig;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** 上游 `findProjectRoot` 的语义:向上找第一个带标记的目录,找不到就用 cwd。 */
|
|
41
|
+
async function projectRootOf(cwd: string, markers: readonly string[]): Promise<string> {
|
|
42
|
+
let current = resolve(cwd);
|
|
43
|
+
for (;;) {
|
|
44
|
+
for (const marker of markers) {
|
|
45
|
+
if (await exists(join(current, marker))) return current;
|
|
46
|
+
}
|
|
47
|
+
const parent = dirname(current);
|
|
48
|
+
if (parent === current) return resolve(cwd);
|
|
49
|
+
current = parent;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function exists(path: string): Promise<boolean> {
|
|
54
|
+
try {
|
|
55
|
+
await stat(path);
|
|
56
|
+
return true;
|
|
57
|
+
} catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** 一个会话的 baseline 身份;cwd 或项目根变了就是另一份身份(上游语义)。 */
|
|
63
|
+
export async function baselineIdentity(cwd: string): Promise<string> {
|
|
64
|
+
const config = resolveBaselineConfig();
|
|
65
|
+
const projectRoot = await projectRootOf(cwd, config.projectRootMarkers);
|
|
66
|
+
// 字段顺序与上游 `workspaceBaselineIdentity` 一致:它比较的是 JSON 字符串,顺序也参与。
|
|
67
|
+
return JSON.stringify({
|
|
68
|
+
projectRoot: relative(cwd, projectRoot),
|
|
69
|
+
projectRootMarkers: config.projectRootMarkers,
|
|
70
|
+
maxBytes: config.maxBytes,
|
|
71
|
+
maxSourceBytes: config.maxSourceBytes,
|
|
72
|
+
instructionFileCandidates: config.instructionFileCandidates,
|
|
73
|
+
localInstructionFileCandidates: config.localInstructionFileCandidates,
|
|
74
|
+
});
|
|
75
|
+
}
|