@morlay/dsh-session-mode 0.0.8 → 0.0.9
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 +7 -11
- package/dist/catalog-DWxIg3qw.mjs +131 -0
- package/dist/catalog-K5cESbqA.cjs +136 -0
- package/dist/catalog-remote-B7ZgYqun.cjs +28 -0
- package/dist/{catalog-remote-B-NMi_vS.mjs → catalog-remote-KXJjDaN6.mjs} +1 -5
- package/dist/client.cjs +61 -16357
- package/dist/client.d.cts +3 -326
- package/dist/client.d.mts +3 -326
- package/dist/client.mjs +1612 -0
- package/dist/index.cjs +635 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +45 -169
- package/dist/rows.cjs +85 -0
- package/dist/rows.mjs +2 -5
- package/dist/shared-DX2Giyh3.mjs +5 -0
- package/dist/shared-sy45ixVR.cjs +16 -0
- package/dist/testing.cjs +6 -0
- package/dist/testing.d.mts +35 -0
- package/dist/testing.mjs +3 -0
- package/package.json +72 -60
- package/src/client/BundleConfigPage.tsx +12 -11
- package/src/client/SessionModeSeat.tsx +1 -1
- package/src/client/bundle-config.ts +1 -1
- package/src/client/bundle-locales.ts +4 -2
- package/src/client/index.ts +8 -12
- package/src/client/llm-directory.ts +7 -4
- package/src/index.ts +4 -1
- package/src/mode-sources.ts +11 -8
- package/src/modes.ts +6 -5
- package/src/scope.ts +74 -65
- package/src/testing.ts +3 -0
- package/dist/index.d.cts +0 -146
- package/dist/rows.d.cts +0 -11
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
SeatButton,
|
|
11
11
|
Stack,
|
|
12
12
|
Text,
|
|
13
|
-
} from "@morlay/dsh-client-ui-primitives
|
|
13
|
+
} from "@morlay/dsh-client-ui-primitives";
|
|
14
14
|
import type { PropsLocale, PropsRuntime } from "@deepseek-ai/dsh-client-ui-slots";
|
|
15
15
|
// Type-only:拉入本包 host 半的 Context / 投影声明(`sessionMode`),让 `projectionValues` 有类型。
|
|
16
16
|
import type {} from "../index.ts";
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
type SelectOption,
|
|
20
20
|
type SettingsFormShell,
|
|
21
21
|
type ValidationFailure,
|
|
22
|
-
} from "@morlay/dsh-client-ui-primitives
|
|
22
|
+
} from "@morlay/dsh-client-ui-primitives";
|
|
23
23
|
import type { BundleLocaleKey } from "./bundle-locales.ts";
|
|
24
24
|
import { loadLlmProviders, routesOf, type ModelRoute } from "./llm-directory.ts";
|
|
25
25
|
|
|
@@ -64,7 +64,8 @@ export const bundleZh = {
|
|
|
64
64
|
"hint.instructions": "每步都把工作区指令(AGENTS.md 之类)发给模型;关掉就不发。",
|
|
65
65
|
"hint.skills": "每步都把技能目录发给模型;默认按这个模式的工具名单自动判断。",
|
|
66
66
|
"hint.runtimeContext": "每步都把沙箱与审批策略发给模型。",
|
|
67
|
-
"hint.defaultModel":
|
|
67
|
+
"hint.defaultModel":
|
|
68
|
+
"开 = 这个模式自带一个默认模型(会话还没选过模型时用它);关 = 跟随全局模型。",
|
|
68
69
|
"hint.modelRoute": "从部署里已设置的模型里挑一条:服务商与模型是一对,选它等于两个一起定。",
|
|
69
70
|
"hint.reasoningEffort": "思考档位;不填用服务商默认。",
|
|
70
71
|
"model.loading": "正在读部署里已设置的模型…",
|
|
@@ -115,7 +116,8 @@ export const bundleEn: Record<keyof typeof bundleZh, string> = {
|
|
|
115
116
|
"add.confirm": "Add",
|
|
116
117
|
"add.cancel": "Cancel",
|
|
117
118
|
"add.close": "Close",
|
|
118
|
-
"add.hint":
|
|
119
|
+
"add.hint":
|
|
120
|
+
"Give the new mode an id (it also shows in pickers and lists; rename it after adding).",
|
|
119
121
|
removeNamed: "Delete mode {name}",
|
|
120
122
|
protected: "Built-in mode: no delete",
|
|
121
123
|
untitled: "(untitled)",
|
package/src/client/index.ts
CHANGED
|
@@ -11,8 +11,11 @@ import type {} from "@deepseek-ai/dsh-client-ui-slots";
|
|
|
11
11
|
import type {} from "@deepseek-ai/dsh-client-ui-conversation/client";
|
|
12
12
|
// Type-only:`plugins.bundle.config` 的槽位声明(bundle 详情页的配置座位)。
|
|
13
13
|
import type {} from "@deepseek-ai/dsh-client-ui-plugin-manager/client";
|
|
14
|
-
import {
|
|
15
|
-
|
|
14
|
+
import {
|
|
15
|
+
CATALOG_NS,
|
|
16
|
+
SESSION_MODE_CATALOG_REMOTE,
|
|
17
|
+
type SessionModeCatalogRemote,
|
|
18
|
+
} from "../catalog-remote.ts";
|
|
16
19
|
import { POLICY_NAMES } from "../shared.ts";
|
|
17
20
|
import {
|
|
18
21
|
BUNDLE_CONFIG_KEY,
|
|
@@ -24,11 +27,7 @@ import {
|
|
|
24
27
|
} from "./bundle-config.ts";
|
|
25
28
|
import { BundleConfigPage } from "./BundleConfigPage.tsx";
|
|
26
29
|
import { BUNDLE_NS, bundleEn, bundleZh, type BundleLocaleKey } from "./bundle-locales.ts";
|
|
27
|
-
import {
|
|
28
|
-
loadLlmProviders,
|
|
29
|
-
remoteNamespace,
|
|
30
|
-
type ProviderModels,
|
|
31
|
-
} from "./llm-directory.ts";
|
|
30
|
+
import { loadLlmProviders, remoteNamespace, type ProviderModels } from "./llm-directory.ts";
|
|
32
31
|
import { SessionModeSeat } from "./SessionModeSeat.tsx";
|
|
33
32
|
import { en, zh, type SessionModeLocaleKey } from "./locales.ts";
|
|
34
33
|
|
|
@@ -58,8 +57,7 @@ const DYNAMIC = "*";
|
|
|
58
57
|
// 部署里的 agent preset 清单(`remote.agentPresets.list()`):本包只用它的 id 与显示名做候选。
|
|
59
58
|
interface PresetRosterReader {
|
|
60
59
|
list: () => Promise<
|
|
61
|
-
|
|
62
|
-
| { ok: false }
|
|
60
|
+
{ ok: true; value: { presets: readonly { id: string; name?: string }[] } } | { ok: false }
|
|
63
61
|
>;
|
|
64
62
|
}
|
|
65
63
|
|
|
@@ -83,7 +81,6 @@ function unwrapHintService(scope: Context): HintsService {
|
|
|
83
81
|
// 装上会话里的那一个面、本行配置页的字段文案,以及装本行的 bundle 那一页的表单。
|
|
84
82
|
export function apply(ctx: Context): void {
|
|
85
83
|
// 基础面随本包 inline(不再是装配行):装上它提供的字典、字段槽与按行配置页;多份副本只装一次。
|
|
86
|
-
installUiPrimitives(ctx);
|
|
87
84
|
const t = ctx.locale.bind(NS);
|
|
88
85
|
ctx.effect(() => ctx.locale.register(NS, { zh, en }), "session-mode: dictionaries");
|
|
89
86
|
|
|
@@ -162,7 +159,6 @@ export function apply(ctx: Context): void {
|
|
|
162
159
|
}, "session-mode: model candidates"),
|
|
163
160
|
);
|
|
164
161
|
|
|
165
|
-
|
|
166
162
|
// 模式"允许挂哪些 preset"的候选:部署里注册的 agent preset(`remote.agentPresets.list()`),登记成具名源
|
|
167
163
|
// `agent-presets`——schema 上 `presetsOnly` 声明 `role('select', { source })` 认领它,页面不必知道这些 preset
|
|
168
164
|
// 从哪来。装配组合变了(设置文档一次更新)就重取一次。
|
|
@@ -220,7 +216,7 @@ export function apply(ctx: Context): void {
|
|
|
220
216
|
}
|
|
221
217
|
void scope.remote
|
|
222
218
|
.$mount(SESSION_MODE_CATALOG_REMOTE)
|
|
223
|
-
.then(async (dispose) => {
|
|
219
|
+
.then(async (dispose: () => Promise<void>) => {
|
|
224
220
|
disposeMount = dispose;
|
|
225
221
|
const listed = await remoteNamespace<SessionModeCatalogRemote>(scope, CATALOG_NS)?.list();
|
|
226
222
|
if (listed?.ok !== true) return;
|
|
@@ -97,16 +97,19 @@ function modelsOf(
|
|
|
97
97
|
// 部署里"已设置"的 provider 与它们的模型:活着的路由与可配置声明合并(按 provider 去重,声明的显示名优先)。
|
|
98
98
|
export async function loadLlmProviders(scope: Context): Promise<readonly ProviderModels[]> {
|
|
99
99
|
const llm = remoteNamespace<LlmReader>(scope, "llm");
|
|
100
|
-
const forms = (scope as unknown as { get(key: string): unknown }).get(
|
|
101
|
-
|
|
102
|
-
|
|
100
|
+
const forms = (scope as unknown as { get(key: string): unknown }).get("configForms") as
|
|
101
|
+
| ConfigFormsReader
|
|
102
|
+
| undefined;
|
|
103
103
|
if (llm === undefined || forms === undefined) return [];
|
|
104
104
|
const [routes, directory] = await Promise.all([
|
|
105
105
|
llm.listProviders(),
|
|
106
106
|
llm.listConfigurableProviders(),
|
|
107
107
|
]);
|
|
108
108
|
if (!routes.ok || !directory.ok) return [];
|
|
109
|
-
const merged = new Map<
|
|
109
|
+
const merged = new Map<
|
|
110
|
+
string,
|
|
111
|
+
{ value: string; label: string; settingsNs: string; settingsPath: readonly string[] }
|
|
112
|
+
>();
|
|
110
113
|
for (const entry of directory.value) {
|
|
111
114
|
merged.set(entry.provider, {
|
|
112
115
|
value: entry.provider,
|
package/src/index.ts
CHANGED
|
@@ -297,7 +297,10 @@ export class SessionModes extends Service {
|
|
|
297
297
|
|
|
298
298
|
// 把当前挂着的 preset 落进模式的白名单:已经在名单里就返回 `undefined`(不换);不在里面(或读不到当前那份)
|
|
299
299
|
// 就返回名单的**第一个**(`undefined` = 名单为空 = 不限制,什么都不换)。
|
|
300
|
-
private presetTarget(
|
|
300
|
+
private presetTarget(
|
|
301
|
+
allowed: readonly string[],
|
|
302
|
+
current: string | undefined,
|
|
303
|
+
): string | undefined {
|
|
301
304
|
if (allowed.length === 0) return undefined;
|
|
302
305
|
// 当前那份在名单里:保持不动(用户自己选的 preset 落在许可范围内,模式不去覆盖它)。
|
|
303
306
|
if (current !== undefined && allowed.includes(current)) return undefined;
|
package/src/mode-sources.ts
CHANGED
|
@@ -44,13 +44,16 @@ export interface ModeModelSource {
|
|
|
44
44
|
readonly reasoningEffort?: string;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
// 编码模式:编程专家 +
|
|
47
|
+
// 编码模式:编程专家 + 语言与思考纪律。
|
|
48
|
+
// 这里**不再有**工作目录那半句(原为 `你的工作目录在 \`{{cwd}}\``):上游 0.2.1-alpha.2 起工作目录由
|
|
49
|
+
// `working-directory:current` 这条运行时上下文注入(文本是 `Current working directory: "<绝对路径>".`),
|
|
50
|
+
// 而 persona 用的提示词插值是**严格**的——`cwd` 变量已不再注册,残留 `{{cwd}}` 会在渲染时直接抛
|
|
51
|
+
// `unknown prompt variable`(注册变量只剩 provider / model)。
|
|
48
52
|
const CODING_PERSONA = {
|
|
49
53
|
prefix: [
|
|
50
54
|
"你是一个经验丰富的编程专家,YAGNI 是你的编程哲学,PDCA 是你的行为规范。",
|
|
51
55
|
"全程用中文(专有名词除外),包括但不限于思考,回答,工具描述,subagent 提示词;思考不要陷入重复循环,一旦循环立即退出;思考聚焦需求理解与方案设计,不预演具体代码实现,正确性由验证环节确认。",
|
|
52
56
|
].join("\n"),
|
|
53
|
-
suffix: "你的工作目录在 `{{cwd}}`",
|
|
54
57
|
};
|
|
55
58
|
|
|
56
59
|
// 对话模式:一个助手,保留语言与思考纪律,没有 suffix。
|
|
@@ -83,10 +86,7 @@ export const MODE_SOURCES: readonly ModeSource[] = [
|
|
|
83
86
|
// 官方 Office 技能的三个名字同样按会话排除:技能目录里不列它们,`skill` 工具加载它们也被拒。
|
|
84
87
|
// 名单是技能名这一层的模型可见契约(官方 `skill-office` 的 `SKILL_NAMES`),跟着上游改名走。
|
|
85
88
|
denySkills: ["office-docx", "office-pptx", "office-xlsx"],
|
|
86
|
-
|
|
87
|
-
// `denyPolicies` 只禁 `fs/edit-intent`(上游那条"先读后改"):改文件不再要求先读过——写路径上的
|
|
88
|
-
// `fs/write-intent`(陈旧版本 CAS 那层安全网)照旧生效,那正是这条配置不写成"两条都禁"的理由。
|
|
89
|
-
denyPolicies: ["fs/edit-intent"],
|
|
89
|
+
denyPolicies: ["fs/edit-intent", "fs/write-intent"],
|
|
90
90
|
},
|
|
91
91
|
{
|
|
92
92
|
id: "chat",
|
|
@@ -103,9 +103,12 @@ export const MODE_SOURCES: readonly ModeSource[] = [
|
|
|
103
103
|
// 配了只是噪音,所以留空。
|
|
104
104
|
// 没有文件与 shell 工具,"能改工作区哪些文件、要不要走审批"对它全是噪音。
|
|
105
105
|
//
|
|
106
|
-
//
|
|
106
|
+
// 注入面全关:`instructions: false` 丢掉官方 `agent-instructions`(工作区指令)的注入;这个模式不写
|
|
107
107
|
// `skills`,而白名单三件里没有 `skill`、`denyTools` 也留空 → 推导成 `false`,官方 `skill-catalog` 的注入
|
|
108
|
-
//
|
|
108
|
+
// 同样丢掉;`runtimeContext: false` 连动态快照与时钟(`standard` 那类 preset 声明的 `time-context`)一起收。
|
|
109
|
+
// **收不掉的**:上游 `working-directory:current` 是 required 的目录上下文(0.2.1-alpha.2 起工作目录只走这条),
|
|
110
|
+
// 抑制可选运行时上下文不影响它——chat 会话的模型仍会看到工作目录那条快照。
|
|
111
|
+
// 取舍见 `.agents/designs/20260929-抑制官方注入面.md`。
|
|
109
112
|
instructions: false,
|
|
110
113
|
runtimeContext: false,
|
|
111
114
|
},
|
package/src/modes.ts
CHANGED
|
@@ -72,8 +72,9 @@ export interface SessionMode {
|
|
|
72
72
|
// denyTools` 里含 `skill` 就要(`allowTools` 留空即"全部",所以只有 `denyTools` 能把它推成 `false`)。
|
|
73
73
|
// `false` 表示这个模式不要技能目录:丢掉官方 `skill-catalog` 的注入(`skill` 工具的收窄仍归 `allowTools`)。
|
|
74
74
|
readonly skills?: boolean;
|
|
75
|
-
//
|
|
76
|
-
//
|
|
75
|
+
// 是否要动态快照(文件沙箱策略、审批策略那两条 snapshot section,以及 `standard` 那类 preset 声明的时钟
|
|
76
|
+
// `time-context` 注入)。缺省要;`false` 表示这个模式不要它们——对话模式没有文件与 shell 工具,"能改工作区
|
|
77
|
+
// 哪些文件、要不要走审批"对它全是噪音,它也不谈时间。
|
|
77
78
|
readonly runtimeContext: boolean;
|
|
78
79
|
// 这个模式的默认模型;省略就跟全局 `agent-default-model`。**可选**:没配的模式在页面上不出现在这一行
|
|
79
80
|
// (`defaultModel` 是它所在模式的一个可加字段)。
|
|
@@ -277,13 +278,13 @@ const modeSchema: z<SessionMode> = z.object({
|
|
|
277
278
|
.default(true)
|
|
278
279
|
.description(
|
|
279
280
|
localized({
|
|
280
|
-
zh: "
|
|
281
|
-
en: "Whether the runtime snapshot
|
|
281
|
+
zh: "是否要动态快照(文件沙箱策略、审批策略)与时钟注入(`standard` 那类 preset 声明的 `time-context`)。",
|
|
282
|
+
en: "Whether the runtime snapshot (sandbox and approval policy) and the clock injection (`time-context`, declared by presets such as `standard`) apply.",
|
|
282
283
|
}),
|
|
283
284
|
),
|
|
284
285
|
// 这个模式的默认模型。不标 `volatile`:`modes` 本身就是 volatile,整棵子树都在页面上——再标一层会被
|
|
285
286
|
// schemastery 拒(`validateVolatileSchema` 不许 volatile 套 volatile)。
|
|
286
|
-
defaultModel: modelSchema
|
|
287
|
+
defaultModel: modelSchema // `default(null)` 是"没配就没有这个键":schemastery 对缺省的对象字段会造一个空对象,那样每个模式都会
|
|
287
288
|
// 凭空多出一行;给了 null 反而让它保持缺失(页面按非必填处理,从候选加成)。
|
|
288
289
|
// 类型上放行一次:`null` 在这里只是"没有这个键"的写法,schema 的输入形状不接受它。
|
|
289
290
|
.default(null as unknown as SessionModeModel)
|
package/src/scope.ts
CHANGED
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
// - **装配期投影**:模型目录与 `tool:<工具名>` section 按同一份合成结果过滤;
|
|
7
7
|
// - **执行层 guard**(在 `agent.ctx` 的 `tools` inject 回调里):拒绝时给该模式的文案,并说得出是哪一类(白名单外 /
|
|
8
8
|
// 黑名单内);
|
|
9
|
-
// -
|
|
10
|
-
//
|
|
9
|
+
// - **三个抑制面**(`agent/pre-step`):官方 `agent-instructions`、`skill-catalog` 与 `time-context` 的注入按开关
|
|
10
|
+
// 丢掉——官方那几行在装配投影之外自己往瀑布里 append,只能在瀑布上丢(不动注册表、也不动它们的行);
|
|
11
11
|
// - **另两个开关**(注入面那个 `skills` 在上面那条里):`runtimeContext: false` 抑制动态快照
|
|
12
|
-
// (`systemPrompt.suppressRuntimeContext
|
|
13
|
-
//
|
|
12
|
+
// (`systemPrompt.suppressRuntimeContext`)与时钟那条注入(`agent/pre-step` 上的 `time-context`),
|
|
13
|
+
// `instructions: false` 关掉通道自己的降级注入(`ctx.contextAssembler.setInstructions`)。
|
|
14
14
|
//
|
|
15
15
|
// 同一个 agent 再 `apply` 就是换一份(旧 disposer 全部收回);不走 `tools.restrict()`(会触发 `tools/change`),
|
|
16
16
|
// 也不建 preset 子树。
|
|
@@ -41,7 +41,7 @@ export interface SessionScopeDefinition {
|
|
|
41
41
|
readonly instructions: boolean;
|
|
42
42
|
// 这个会话要不要技能目录(`false` → 丢掉官方 `skill-catalog` 的注入)。
|
|
43
43
|
readonly skills: boolean;
|
|
44
|
-
// 这个会话要不要动态快照(`false` → 按 scope
|
|
44
|
+
// 这个会话要不要动态快照(`false` → 按 scope 抑制,并在瀑布上丢掉时钟那条注入)。
|
|
45
45
|
readonly runtimeContext: boolean;
|
|
46
46
|
}
|
|
47
47
|
|
|
@@ -164,19 +164,15 @@ function skillNameOf(arguments_: unknown): string | undefined {
|
|
|
164
164
|
return typeof name === "string" && name.length > 0 ? name : undefined;
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
-
// 一个 agent
|
|
167
|
+
// 一个 agent 当前装着的那一份:装配期过滤读它(`apply` 时解析好的名单)。
|
|
168
168
|
interface Applied {
|
|
169
169
|
// 两条名单都空 = 不过滤(装配期与执行层都放行)。
|
|
170
170
|
readonly gate: NameGate | undefined;
|
|
171
|
-
// 技能面的名单:与工具面同形,`undefined` = 不设收窄(目录照旧、`skill` 工具不按名判)。
|
|
172
|
-
readonly skillGate: NameGate | undefined;
|
|
173
|
-
readonly instructions: boolean;
|
|
174
|
-
readonly skills: boolean;
|
|
175
171
|
// 收回这一份在 `agent.ctx` 上的注册(抑制器与执行层 guard)。
|
|
176
172
|
readonly dispose: () => void;
|
|
177
173
|
}
|
|
178
174
|
|
|
179
|
-
// 按会话收口工具面与注入面的状态机:一份 per-agent
|
|
175
|
+
// 按会话收口工具面与注入面的状态机:一份 per-agent 的装配期名单 + 挂在各 agent 上的注册。
|
|
180
176
|
export class SessionScope {
|
|
181
177
|
private readonly applied = new WeakMap<Agent, Applied>();
|
|
182
178
|
private readonly ctx: Context;
|
|
@@ -201,54 +197,6 @@ export class SessionScope {
|
|
|
201
197
|
tools: result.tools.filter((tool) => denialOf(gate, tool.name) === undefined),
|
|
202
198
|
};
|
|
203
199
|
});
|
|
204
|
-
|
|
205
|
-
// 两个抑制面:官方 `agent-instructions`(工作区指令)与官方 `skill-catalog`(技能目录)都在装配投影之外自己
|
|
206
|
-
// 往这条瀑布里 append,所以只有在这里丢掉它们的条目(技能名单则在这里把目录按名收窄)。
|
|
207
|
-
//
|
|
208
|
-
// `prepend` 是必需的:官方那两行比本行早注册(会话挂着的 preset 先于 host 平面),瀑布里先注册的是**外层**,
|
|
209
|
-
// 站在它们后面就看不到、也丢不掉它们 append 的条目——只有抢在最外层(`await next()` 之后再收)才拿得到最终
|
|
210
|
-
// 消息表。
|
|
211
|
-
ctx.on(
|
|
212
|
-
"agent/pre-step",
|
|
213
|
-
async (payload, next): Promise<PreStepDecision> => {
|
|
214
|
-
const decision = await next();
|
|
215
|
-
if (decision.kind !== "enter") return decision;
|
|
216
|
-
const applied = this.applied.get(payload.agent);
|
|
217
|
-
// 没收过口的会话一律不动。
|
|
218
|
-
if (applied === undefined) return decision;
|
|
219
|
-
let changed = false;
|
|
220
|
-
const kept: UserMessage[] = [];
|
|
221
|
-
for (const message of decision.messages) {
|
|
222
|
-
const kind = kindOf(message);
|
|
223
|
-
if (kind === "agent-instructions") {
|
|
224
|
-
if (applied.instructions) kept.push(message);
|
|
225
|
-
else changed = true;
|
|
226
|
-
continue;
|
|
227
|
-
}
|
|
228
|
-
if (kind === "skill-catalog") {
|
|
229
|
-
if (!applied.skills) {
|
|
230
|
-
changed = true;
|
|
231
|
-
continue;
|
|
232
|
-
}
|
|
233
|
-
const narrowed =
|
|
234
|
-
applied.skillGate === undefined
|
|
235
|
-
? undefined
|
|
236
|
-
: narrowCatalog(message, applied.skillGate, (line) =>
|
|
237
|
-
this.ctx.logger.warn(`session-mode: ${line}`),
|
|
238
|
-
);
|
|
239
|
-
if (narrowed === undefined) kept.push(message);
|
|
240
|
-
else {
|
|
241
|
-
changed = true;
|
|
242
|
-
kept.push(narrowed);
|
|
243
|
-
}
|
|
244
|
-
continue;
|
|
245
|
-
}
|
|
246
|
-
kept.push(message);
|
|
247
|
-
}
|
|
248
|
-
return changed ? { ...decision, messages: kept } : decision;
|
|
249
|
-
},
|
|
250
|
-
{ prepend: true },
|
|
251
|
-
);
|
|
252
200
|
}
|
|
253
201
|
|
|
254
202
|
// 把某个会话收口到这份定义上(同一个 agent 再调就是换一份)。
|
|
@@ -259,10 +207,29 @@ export class SessionScope {
|
|
|
259
207
|
const gate = gateOfNames(definition.allowTools, definition.denyTools);
|
|
260
208
|
const skillGate = gateOfNames(definition.allowSkills, definition.denySkills);
|
|
261
209
|
|
|
262
|
-
// 落在 `agent.ctx`
|
|
263
|
-
// `tools` 激活才装得上(`inject` 的回调),它挂在那个 inject fiber 下,所以收回时连 fiber 一起收。
|
|
210
|
+
// 落在 `agent.ctx` 上的那几件用一个 effect 装:注入面的抑制与动态快照抑制是 scope 层的注册;执行层 guard
|
|
211
|
+
// 要等 `tools` 激活才装得上(`inject` 的回调),它挂在那个 inject fiber 下,所以收回时连 fiber 一起收。
|
|
264
212
|
const dispose = agent.ctx.effect(() => {
|
|
265
213
|
const stoppers: (() => void)[] = [];
|
|
214
|
+
// 三个抑制面:官方 `agent-instructions`(工作区指令)、官方 `skill-catalog`(技能目录)与官方 `time-context`
|
|
215
|
+
// (时钟)都在装配投影之外自己往这条瀑布里 append,所以只能在这里丢它们的条目(技能目录那条还要按名收窄)。
|
|
216
|
+
//
|
|
217
|
+
// 两件事让这条注册必须落在**会话**上而不是行上:
|
|
218
|
+
// - `prepend` 只是把自己排到表头,不保证一直在那儿:表头归**最后**注册的那个 prepend,而时钟那条也 prepend;
|
|
219
|
+
// - 官方那几行随 preset 挂载(早于 `agent/created`,本注册就在那儿),所以按会话注册天然排在它们之后。
|
|
220
|
+
// 换模式 / 换 preset 时 `apply` 会重来一遍(旧的收回、新的 prepend),最外层由此回到我们手上。
|
|
221
|
+
stoppers.push(
|
|
222
|
+
agent.ctx.on(
|
|
223
|
+
"agent/pre-step",
|
|
224
|
+
async (_payload, next): Promise<PreStepDecision> => {
|
|
225
|
+
const decision = await next();
|
|
226
|
+
if (decision.kind !== "enter") return decision;
|
|
227
|
+
const kept = this.suppress(decision.messages, definition, skillGate);
|
|
228
|
+
return kept === undefined ? decision : { ...decision, messages: kept };
|
|
229
|
+
},
|
|
230
|
+
{ prepend: true },
|
|
231
|
+
),
|
|
232
|
+
);
|
|
266
233
|
if (definition.runtimeContext === false) {
|
|
267
234
|
// service 在 `agent.ctx` 上是 shadow:抑制落在该 agent 的 scope 层,只覆盖这个会话。
|
|
268
235
|
stoppers.push(agent.ctx.systemPrompt.suppressRuntimeContext());
|
|
@@ -292,14 +259,56 @@ export class SessionScope {
|
|
|
292
259
|
|
|
293
260
|
this.applied.set(agent, {
|
|
294
261
|
gate,
|
|
295
|
-
skillGate,
|
|
296
|
-
instructions: definition.instructions,
|
|
297
|
-
skills: definition.skills,
|
|
298
262
|
dispose,
|
|
299
263
|
});
|
|
300
264
|
}
|
|
301
265
|
|
|
302
|
-
//
|
|
266
|
+
// 三个抑制面按这份定义过一遍本步的消息表:`undefined` = 一条都不动(开关都是"要"、技能名单也没排除谁),
|
|
267
|
+
// 其余 = 过滤 / 按名收窄之后的那份表。
|
|
268
|
+
private suppress(
|
|
269
|
+
messages: readonly UserMessage[],
|
|
270
|
+
definition: SessionScopeDefinition,
|
|
271
|
+
skillGate: NameGate | undefined,
|
|
272
|
+
): UserMessage[] | undefined {
|
|
273
|
+
let changed = false;
|
|
274
|
+
const kept: UserMessage[] = [];
|
|
275
|
+
for (const message of messages) {
|
|
276
|
+
const kind = kindOf(message);
|
|
277
|
+
if (kind === "agent-instructions") {
|
|
278
|
+
if (definition.instructions) kept.push(message);
|
|
279
|
+
else changed = true;
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
if (kind === "skill-catalog") {
|
|
283
|
+
if (!definition.skills) {
|
|
284
|
+
changed = true;
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
const narrowed =
|
|
288
|
+
skillGate === undefined
|
|
289
|
+
? undefined
|
|
290
|
+
: narrowCatalog(message, skillGate, (line) =>
|
|
291
|
+
this.ctx.logger.warn(`session-mode: ${line}`),
|
|
292
|
+
);
|
|
293
|
+
if (narrowed === undefined) kept.push(message);
|
|
294
|
+
else {
|
|
295
|
+
changed = true;
|
|
296
|
+
kept.push(narrowed);
|
|
297
|
+
}
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
if (kind === "time-context") {
|
|
301
|
+
// 时钟也归"动态快照":它不经 `suppressRuntimeContext`(那是 snapshot section 那条路),只在瀑布上丢。
|
|
302
|
+
if (definition.runtimeContext) kept.push(message);
|
|
303
|
+
else changed = true;
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
kept.push(message);
|
|
307
|
+
}
|
|
308
|
+
return changed ? kept : undefined;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// 通道是可选的搭档:没有它就没有降级注入可关,工具面与三个抑制面照常生效。
|
|
303
312
|
private channel(): { setInstructions(agent: Agent, on: boolean): void } | undefined {
|
|
304
313
|
try {
|
|
305
314
|
return this.ctx.get("contextAssembler");
|
package/src/testing.ts
ADDED
package/dist/index.d.cts
DELETED
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
import { Context, Service, Volatile } from "@deepseek-ai/cordis";
|
|
2
|
-
import { Agent } from "@deepseek-ai/dsh-agent";
|
|
3
|
-
import { Session, SessionEvent, SessionId } from "@deepseek-ai/dsh-session";
|
|
4
|
-
import { z } from "zod";
|
|
5
|
-
import z$1 from "@deepseek-ai/schemastery";
|
|
6
|
-
//#region src/modes.d.ts
|
|
7
|
-
interface SessionModePersona {
|
|
8
|
-
readonly prefix: string;
|
|
9
|
-
readonly suffix: string;
|
|
10
|
-
}
|
|
11
|
-
type SessionModeRole = "main" | "subagent";
|
|
12
|
-
interface SessionModeModel {
|
|
13
|
-
readonly provider: string;
|
|
14
|
-
readonly model: string;
|
|
15
|
-
readonly reasoningEffort?: string;
|
|
16
|
-
}
|
|
17
|
-
type SessionModeModels = Readonly<Record<string, SessionModeModel>>;
|
|
18
|
-
interface SessionMode {
|
|
19
|
-
readonly presetsOnly: string[];
|
|
20
|
-
readonly name: string;
|
|
21
|
-
readonly description: string;
|
|
22
|
-
readonly role: SessionModeRole[];
|
|
23
|
-
readonly persona: SessionModePersona;
|
|
24
|
-
readonly allowTools: string[];
|
|
25
|
-
readonly denyTools: string[];
|
|
26
|
-
readonly allowSkills: string[];
|
|
27
|
-
readonly denySkills: string[];
|
|
28
|
-
readonly allowPolicies: string[];
|
|
29
|
-
readonly denyPolicies: string[];
|
|
30
|
-
readonly instructions: boolean;
|
|
31
|
-
readonly skills?: boolean;
|
|
32
|
-
readonly runtimeContext: boolean;
|
|
33
|
-
readonly defaultModel?: SessionModeModel;
|
|
34
|
-
readonly preset?: string;
|
|
35
|
-
}
|
|
36
|
-
export interface Config {
|
|
37
|
-
readonly default: string;
|
|
38
|
-
readonly modes: Record<string, SessionMode>;
|
|
39
|
-
readonly models?: SessionModeModels;
|
|
40
|
-
}
|
|
41
|
-
interface ResolvedConfig {
|
|
42
|
-
readonly default: Volatile<string>;
|
|
43
|
-
readonly modes: Volatile<Record<string, SessionMode>>;
|
|
44
|
-
readonly models: SessionModeModels;
|
|
45
|
-
}
|
|
46
|
-
export declare const Config: z$1<Config, ResolvedConfig>;
|
|
47
|
-
//#endregion
|
|
48
|
-
//#region src/shared.d.ts
|
|
49
|
-
export declare const SESSION_MODE_PATH = "/session-mode";
|
|
50
|
-
export declare const POLICY_NAMES: readonly ["fs/write-intent", "fs/edit-intent"];
|
|
51
|
-
type PolicyName = (typeof POLICY_NAMES)[number];
|
|
52
|
-
interface SessionModeRow {
|
|
53
|
-
readonly id: string;
|
|
54
|
-
readonly name: string;
|
|
55
|
-
readonly description?: string;
|
|
56
|
-
}
|
|
57
|
-
interface SessionModeRoster {
|
|
58
|
-
readonly default: string;
|
|
59
|
-
readonly modes: readonly SessionModeRow[];
|
|
60
|
-
}
|
|
61
|
-
//#endregion
|
|
62
|
-
//#region src/index.d.ts
|
|
63
|
-
export declare const name = "session-mode";
|
|
64
|
-
export declare const inject: string[];
|
|
65
|
-
declare module "@deepseek-ai/cordis" {
|
|
66
|
-
interface Context {
|
|
67
|
-
sessionModes: SessionModes;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
declare module "@deepseek-ai/dsh-session/types" {
|
|
71
|
-
interface SessionEventMap {
|
|
72
|
-
"session-mode/selected": {
|
|
73
|
-
sessionMode: string;
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
declare module "@deepseek-ai/dsh-session-projection/types" {
|
|
78
|
-
interface SessionProjectionStateMap {
|
|
79
|
-
sessionMode: string | null;
|
|
80
|
-
sessionModeEditable: boolean;
|
|
81
|
-
}
|
|
82
|
-
interface SessionProjectionMap {
|
|
83
|
-
sessionMode: string | null;
|
|
84
|
-
sessionModeEditable: boolean;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
export declare const sessionModeProjection: {
|
|
88
|
-
key: "sessionMode";
|
|
89
|
-
stateSchema: z.ZodType<string | null, unknown, z.core.$ZodTypeInternals<string | null, unknown>>;
|
|
90
|
-
init: () => null;
|
|
91
|
-
apply: (state: string | null, event: SessionEvent) => string | null;
|
|
92
|
-
wire: {
|
|
93
|
-
viewSchema: z.ZodType<string | null, unknown, z.core.$ZodTypeInternals<string | null, unknown>>;
|
|
94
|
-
view: (state: string | null) => string | null;
|
|
95
|
-
};
|
|
96
|
-
stateVersion: number;
|
|
97
|
-
};
|
|
98
|
-
export declare const sessionModeEditableProjection: {
|
|
99
|
-
key: "sessionModeEditable";
|
|
100
|
-
stateSchema: z.ZodType<boolean, unknown, z.core.$ZodTypeInternals<boolean, unknown>>;
|
|
101
|
-
init: () => true;
|
|
102
|
-
apply: (state: boolean, event: SessionEvent) => boolean;
|
|
103
|
-
wire: {
|
|
104
|
-
viewSchema: z.ZodType<boolean, unknown, z.core.$ZodTypeInternals<boolean, unknown>>;
|
|
105
|
-
view: (state: boolean) => boolean;
|
|
106
|
-
};
|
|
107
|
-
stateVersion: number;
|
|
108
|
-
};
|
|
109
|
-
export declare class SessionModes extends Service {
|
|
110
|
-
private readonly installs;
|
|
111
|
-
private readonly scope;
|
|
112
|
-
readonly config: {
|
|
113
|
-
default: string;
|
|
114
|
-
modes: Record<string, SessionMode>;
|
|
115
|
-
};
|
|
116
|
-
constructor(ctx: Context, config: ResolvedConfig);
|
|
117
|
-
get defaultId(): string;
|
|
118
|
-
list(): SessionModeRow[];
|
|
119
|
-
idsFor(role: SessionModeRole): string[];
|
|
120
|
-
modesFor(role: SessionModeRole): {
|
|
121
|
-
id: string;
|
|
122
|
-
mode: SessionMode;
|
|
123
|
-
}[];
|
|
124
|
-
roster(): SessionModeRoster;
|
|
125
|
-
definition(id?: string): SessionMode;
|
|
126
|
-
modeOf(session: Session): string;
|
|
127
|
-
modeOfSession(session: Session): SessionMode;
|
|
128
|
-
private policyInForce;
|
|
129
|
-
private decidePolicy;
|
|
130
|
-
select(sessionId: SessionId, mode: string): Promise<string>;
|
|
131
|
-
private presetTarget;
|
|
132
|
-
private presetOfAgent;
|
|
133
|
-
private presetRegistry;
|
|
134
|
-
applyTo(agent: Agent, mode: string, options?: {
|
|
135
|
-
record?: boolean;
|
|
136
|
-
}): void;
|
|
137
|
-
private resolveModeId;
|
|
138
|
-
private presetOf;
|
|
139
|
-
modeForPreset(preset: string | undefined): string | undefined;
|
|
140
|
-
private inheritedModeId;
|
|
141
|
-
private installFor;
|
|
142
|
-
private installDefaultModel;
|
|
143
|
-
}
|
|
144
|
-
export declare function apply(ctx: Context, config: ResolvedConfig): void;
|
|
145
|
-
//#endregion
|
|
146
|
-
export type { PolicyName, ResolvedConfig, SessionMode, SessionModeModel, SessionModePersona, SessionModeRole, SessionModeRoster, SessionModeRow };
|
package/dist/rows.d.cts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
//#region src/rows.d.ts
|
|
2
|
-
export interface PatchRow {
|
|
3
|
-
readonly insert?: readonly RowEntry[];
|
|
4
|
-
}
|
|
5
|
-
export interface RowEntry {
|
|
6
|
-
readonly id: string;
|
|
7
|
-
readonly name: string;
|
|
8
|
-
readonly config?: Readonly<Record<string, unknown>>;
|
|
9
|
-
}
|
|
10
|
-
export declare function sessionModeRows(): readonly PatchRow[];
|
|
11
|
-
//#endregion
|