@morlay/dsh-session-mode 0.0.6 → 0.0.8

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/dist/index.mjs CHANGED
@@ -1,8 +1,9 @@
1
- import { n as SESSION_MODE_PATH, t as POLICY_NAMES } from "./shared-DX2Giyh3.mjs";
1
+ import { i as SESSION_MODE_PATH, r as POLICY_NAMES, t as CATALOG_NS } from "./catalog-remote-B-NMi_vS.mjs";
2
2
  import { Service } from "@deepseek-ai/cordis";
3
3
  import { ReasoningEffortId } from "@deepseek-ai/dsh-llm";
4
4
  import { z } from "zod";
5
5
  import z$1 from "@deepseek-ai/schemastery";
6
+ import { Remote, TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
6
7
  import { PERSONA_PREFIX_SECTION, PERSONA_SUFFIX_SECTION } from "@deepseek-ai/dsh-system-prompt";
7
8
  //#region src/modes.ts
8
9
  function derivedSkills(mode) {
@@ -24,14 +25,15 @@ const modelSchema = z$1.object({
24
25
  en: "Model id."
25
26
  })),
26
27
  reasoningEffort: z$1.string().description(localized({
27
- zh: "思考档位;省略就跟服务商自己的默认。",
28
- en: "Reasoning effort; unset keeps the provider's own default."
28
+ zh: "思考档位;默认跟服务商自己的默认。",
29
+ en: "Reasoning effort; the default is the provider's own."
29
30
  }))
30
31
  });
31
32
  const modeSchema = z$1.object({
32
- preset: z$1.string().default("").description(localized({
33
- zh: "这个模式挂哪个 agent preset(它的 id,官方或本部署自建的):行清单由那份 preset 提供,几个模式可以共享同一个。留空就是不绑——选这个模式不换 preset,会话保持当前挂着的那份,模式自己的提示词、工具收口与开关照常生效。",
34
- en: "Which agent preset this mode rides on (its id, shipped or deployment-owned): that preset supplies the row list, and several modes may share it. Leave it empty to bind none — selecting the mode then keeps whatever preset the session already has, while the mode's persona, tool narrowing, and switches still apply."
33
+ preset: z$1.string().hidden(),
34
+ presetsOnly: z$1.array(z$1.string()).role("select", { source: "agent-presets" }).default([]).description(localized({
35
+ zh: "这个模式只允许挂哪些 agent preset(它们的 id,官方或本部署自建的):行清单由挂着的那份提供,几个模式可以共享同一份名单。留空表示**允许全部**——哪份 preset 都行,选这个模式也不动会话当前挂着的那份;有值时当前的已经在名单里就不动,否则换成名单里的第一个。模式自己的提示词、工具收口与开关照常生效。",
36
+ en: "Which agent presets this mode may ride on (their ids, shipped or deployment-owned): that preset supplies the row list, and several modes may share one list. Empty means **every preset is allowed** — selecting the mode then leaves the session's current preset alone; when set, the current one stays if it is listed, otherwise the session switches to the first entry. The mode's persona, tool narrowing, and switches apply either way."
35
37
  })),
36
38
  name: z$1.string().required().description(localized({
37
39
  zh: "模式名(事实文案;选择面归官方 roster)。",
@@ -46,27 +48,27 @@ const modeSchema = z$1.object({
46
48
  en: "Who may use this mode: `main` (selectable in sessions) or `subagent` (inherited by subagents)."
47
49
  })),
48
50
  persona: personaSchema.default({}),
49
- allowTools: z$1.array(z$1.string()).default([]).description(localized({
51
+ allowTools: z$1.array(z$1.string()).role("select", { source: "catalog-tools" }).default([]).description(localized({
50
52
  zh: "这个会话能用的工具;其余既不进目录,调用也被拒。**留空就是不设收窄**:用这个会话挂着的 preset 提供的全部工具。",
51
53
  en: "Tools this session may use; everything else leaves the catalog and calls are refused. Leave it empty to narrow nothing: the session then uses every tool its preset provides."
52
54
  })),
53
- denyTools: z$1.array(z$1.string()).default([]).description(localized({
55
+ denyTools: z$1.array(z$1.string()).role("select", { source: "catalog-tools" }).default([]).description(localized({
54
56
  zh: "这个模式不用的工具(黑名单):从 `allowTools` 定下的那份里减掉(`allowTools` 留空就是从全部里减)。留空 = 一条都不禁;同时命中两份名单时以这里为准。",
55
57
  en: "Tools this mode must not use (deny list): subtracted from whatever `allowTools` settled on (with an empty `allowTools`, from everything). Empty denies nothing; a name in both lists is denied."
56
58
  })),
57
- allowSkills: z$1.array(z$1.string()).default([]).description(localized({
59
+ allowSkills: z$1.array(z$1.string()).role("select", { source: "catalog-skills" }).default([]).description(localized({
58
60
  zh: "这个模式能用的技能;其余既不进技能目录,`skill` 工具加载它也被拒。**留空就是不设收窄**:技能注册表里有什么就用什么。",
59
61
  en: "Skills this mode may use; everything else leaves the skill catalog and loading it through the `skill` tool is refused. Leave it empty to narrow nothing: the session then sees every skill the registry carries."
60
62
  })),
61
- denySkills: z$1.array(z$1.string()).default([]).description(localized({
63
+ denySkills: z$1.array(z$1.string()).role("select", { source: "catalog-skills" }).default([]).description(localized({
62
64
  zh: "这个模式不用的技能(黑名单):从 `allowSkills` 定下的那份里减掉(`allowSkills` 留空就是从全部里减)。留空 = 一条都不禁;同时命中两份名单时以这里为准。",
63
65
  en: "Skills this mode must not use (deny list): subtracted from whatever `allowSkills` settled on (with an empty `allowSkills`, from everything). Empty denies nothing; a name in both lists is denied."
64
66
  })),
65
- allowPolicies: z$1.array(z$1.string()).default([]).description(localized({
67
+ allowPolicies: z$1.array(z$1.string()).role("select", { source: "policies" }).default([]).description(localized({
66
68
  zh: "上游 policy 规则的生效白名单(`fs/write-intent` / `fs/edit-intent`):留空 = 全部规则照旧生效;有值 = 只有列出的生效,其余的被绕过。",
67
69
  en: "Allow list of upstream policy rules that stay in force (`fs/write-intent` / `fs/edit-intent`): empty keeps every rule; when set, only the listed ones stay, and the others are bypassed."
68
70
  })),
69
- denyPolicies: z$1.array(z$1.string()).default([]).description(localized({
71
+ denyPolicies: z$1.array(z$1.string()).role("select", { source: "policies" }).default([]).description(localized({
70
72
  zh: "上游 policy 规则的黑名单:列出的规则禁用(它的裁决连拒绝一起丢掉,调用按没有这条规则继续)。留空 = 一条都不禁;同时命中两份名单时以这里为准。",
71
73
  en: "Deny list of upstream policy rules: the listed ones are disabled — their verdict (rejection included) is dropped and the call proceeds as if the rule were absent. Empty denies nothing; a name in both lists is denied."
72
74
  })),
@@ -75,16 +77,16 @@ const modeSchema = z$1.object({
75
77
  en: "Whether instruction-class injections apply (workspace instructions, guidance): `false` drops the official `agent-instructions` injection and turns off the channel's own demoted delivery."
76
78
  })),
77
79
  skills: z$1.boolean().description(localized({
78
- zh: "是否要技能目录(官方 `skill-catalog` 的注入)。**不写就按这个模式自己的工具名单推导**:`(allowTools 留空 ? 全部 : allowTools) − denyTools` 里含 `skill` 就要;写 `false` 就丢掉官方 `skill-catalog` 的注入(`skill` 工具的可见性仍归 `allowTools`)。",
79
- en: "Whether the skill catalog applies (the official `skill-catalog` injection). Unset derives it from this mode's own tool lists: it applies when `(empty allowTools ? every tool : allowTools) − denyTools` contains `skill`; `false` drops the official `skill-catalog` injection (`skill` tool visibility still belongs to `allowTools`)."
80
+ zh: "是否要技能目录(官方 `skill-catalog` 的注入)。**默认就按这个模式自己的工具名单推导**:`(allowTools 留空 ? 全部 : allowTools) − denyTools` 里含 `skill` 就要;写 `false` 就丢掉官方 `skill-catalog` 的注入(`skill` 工具的可见性仍归 `allowTools`)。",
81
+ en: "Whether the skill catalog applies (the official `skill-catalog` injection). The default derives it from this mode's own tool lists: it applies when `(empty allowTools ? every tool : allowTools) − denyTools` contains `skill`; `false` drops the official `skill-catalog` injection (`skill` tool visibility still belongs to `allowTools`)."
80
82
  })),
81
83
  runtimeContext: z$1.boolean().default(true).description(localized({
82
84
  zh: "是否要动态快照(文件沙箱策略、审批策略)。",
83
85
  en: "Whether the runtime snapshot applies (sandbox and approval policy)."
84
86
  })),
85
87
  defaultModel: modelSchema.default(null).description(localized({
86
- zh: "这个模式的默认模型;省略就跟全局默认模型。只在会话还没有模型事实时接管。",
87
- en: "Default model for this mode; unset follows the global default. Applies only while a session has no model fact yet."
88
+ zh: "这个模式的默认模型;不设就跟全局默认模型。只在会话还没有模型事实时接管。",
89
+ en: "Default model for this mode; empty follows the global default. Applies only while a session has no model fact yet."
88
90
  }))
89
91
  });
90
92
  const Config = z$1.object({
@@ -110,6 +112,8 @@ function configProblem(config) {
110
112
  const unknownPolicies = Object.entries(config.modes).flatMap(([id, mode]) => [...mode.allowPolicies ?? [], ...mode.denyPolicies ?? []].filter((policy) => !POLICY_NAME_SET.has(policy)).map((policy) => `${id}: ${policy}`));
111
113
  if (unknownPolicies.length > 0) return `session-mode: mode(s) ${unknownPolicies.join(", ")} name unknown policies; the known ones are ${POLICY_NAMES.join(", ")}`;
112
114
  if (Object.keys(config.models ?? {}).length > 0) return "session-mode: `models` has moved into each mode's `defaultModel`; move the entries there and drop the top-level `models`";
115
+ const retiredPresets = Object.entries(config.modes).filter(([, mode]) => mode.preset !== void 0).map(([id]) => id);
116
+ if (retiredPresets.length > 0) return `session-mode: mode(s) ${retiredPresets.join(", ")} still declare \`preset\`; it has moved to \`presetsOnly\` (the presets that mode may ride on)`;
113
117
  const partial = Object.entries(config.modes).filter(([, mode]) => {
114
118
  const model = mode.defaultModel;
115
119
  if (model === void 0) return false;
@@ -118,6 +122,134 @@ function configProblem(config) {
118
122
  if (partial.length > 0) return `session-mode: mode(s) ${partial.join(", ")} declare \`defaultModel\` without both \`provider\` and \`model\``;
119
123
  }
120
124
  //#endregion
125
+ //#region src/catalog.ts
126
+ var __create = Object.create;
127
+ var __defProp = Object.defineProperty;
128
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
129
+ var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name);
130
+ var __typeError = (msg) => {
131
+ throw TypeError(msg);
132
+ };
133
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, {
134
+ enumerable: true,
135
+ configurable: true,
136
+ writable: true,
137
+ value
138
+ }) : obj[key] = value;
139
+ var __name = (target, value) => __defProp(target, "name", {
140
+ value,
141
+ configurable: true
142
+ });
143
+ var __decoratorStart = (base) => [
144
+ ,
145
+ ,
146
+ ,
147
+ __create(base?.[__knownSymbol("metadata")] ?? null)
148
+ ];
149
+ var __decoratorStrings = [
150
+ "class",
151
+ "method",
152
+ "getter",
153
+ "setter",
154
+ "accessor",
155
+ "field",
156
+ "value",
157
+ "get",
158
+ "set"
159
+ ];
160
+ var __expectFn = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError("Function expected") : fn;
161
+ var __decoratorContext = (kind, name, done, metadata, fns) => ({
162
+ kind: __decoratorStrings[kind],
163
+ name,
164
+ metadata,
165
+ addInitializer: (fn) => done._ ? __typeError("Already initialized") : fns.push(__expectFn(fn || null))
166
+ });
167
+ var __decoratorMetadata = (array, target) => __defNormalProp(target, __knownSymbol("metadata"), array[3]);
168
+ var __runInitializers = (array, flags, self, value) => {
169
+ for (var i = 0, fns = array[flags >> 1], n = fns && fns.length; i < n; i++) flags & 1 ? fns[i].call(self) : value = fns[i].call(self, value);
170
+ return value;
171
+ };
172
+ var __decorateElement = (array, flags, name, decorators, target, extra) => {
173
+ var fn, it, done, ctx, access, k = flags & 7, s = !!(flags & 8), p = !!(flags & 16);
174
+ var j = k > 3 ? array.length + 1 : k ? s ? 1 : 2 : 0, key = __decoratorStrings[k + 5];
175
+ var initializers = k > 3 && (array[j - 1] = []), extraInitializers = array[j] || (array[j] = []);
176
+ var desc = k && (!p && !s && (target = target.prototype), k < 5 && (k > 3 || !p) && __getOwnPropDesc(k < 4 ? target : {
177
+ get [name]() {
178
+ return __privateGet(this, extra);
179
+ },
180
+ set [name](x) {
181
+ return __privateSet(this, extra, x);
182
+ }
183
+ }, name));
184
+ k ? p && k < 4 && __name(extra, (k > 2 ? "set " : k > 1 ? "get " : "") + name) : __name(target, name);
185
+ for (var i = decorators.length - 1; i >= 0; i--) {
186
+ ctx = __decoratorContext(k, name, done = {}, array[3], extraInitializers);
187
+ if (k) {
188
+ ctx.static = s, ctx.private = p, access = ctx.access = { has: p ? (x) => __privateIn(target, x) : (x) => name in x };
189
+ if (k ^ 3) access.get = p ? (x) => (k ^ 1 ? __privateGet : __privateMethod)(x, target, k ^ 4 ? extra : desc.get) : (x) => x[name];
190
+ if (k > 2) access.set = p ? (x, y) => __privateSet(x, target, y, k ^ 4 ? extra : desc.set) : (x, y) => x[name] = y;
191
+ }
192
+ it = (0, decorators[i])(k ? k < 4 ? p ? extra : desc[key] : k > 4 ? void 0 : {
193
+ get: desc.get,
194
+ set: desc.set
195
+ } : target, ctx), done._ = 1;
196
+ if (k ^ 4 || it === void 0) __expectFn(it) && (k > 4 ? initializers.unshift(it) : k ? p ? extra = it : desc[key] = it : target = it);
197
+ else if (typeof it !== "object" || it === null) __typeError("Object expected");
198
+ else __expectFn(fn = it.get) && (desc.get = fn), __expectFn(fn = it.set) && (desc.set = fn), __expectFn(fn = it.init) && initializers.unshift(fn);
199
+ }
200
+ return k || __decoratorMetadata(array, target), desc && __defProp(target, name, desc), p ? k ^ 4 ? extra : desc : target;
201
+ };
202
+ var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
203
+ var __privateIn = (member, obj) => Object(obj) !== obj ? __typeError("Cannot use the \"in\" operator on this value") : member.has(obj);
204
+ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
205
+ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
206
+ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
207
+ var _remoteExportList_dec;
208
+ var _a;
209
+ var _init;
210
+ function cwdOf(agent) {
211
+ const cwd = agent.session?.header?.cwd;
212
+ return typeof cwd === "string" ? cwd : void 0;
213
+ }
214
+ var SessionModeCatalog = class extends (_a = TypertRemoteService, _remoteExportList_dec = [Remote("list")], _a) {
215
+ /**
216
+ * @param ctx - owning context (the `session-mode` row).
217
+ */
218
+ constructor(ctx) {
219
+ super(ctx, CATALOG_NS);
220
+ __runInitializers(_init, 5, this);
221
+ }
222
+ async remoteExportList() {
223
+ return {
224
+ tools: this.toolNames(),
225
+ skills: await this.skillNames()
226
+ };
227
+ }
228
+ toolNames() {
229
+ const tools = this.ctx.get("tools");
230
+ if (tools === void 0) return [];
231
+ const names = new Set(tools.schemas().map((schema) => String(schema.name)));
232
+ for (const agent of this.agents()) for (const schema of tools.schemas(agent)) names.add(String(schema.name));
233
+ return [...names];
234
+ }
235
+ async skillNames() {
236
+ const skills = this.ctx.get("skills");
237
+ if (skills === void 0) return [];
238
+ const names = new Set((await skills.list()).map((row) => row.name));
239
+ for (const agent of this.agents()) for (const row of await skills.list({
240
+ scope: agent,
241
+ cwd: cwdOf(agent)
242
+ })) names.add(row.name);
243
+ return [...names];
244
+ }
245
+ agents() {
246
+ return this.ctx.get("agents")?.list() ?? [];
247
+ }
248
+ };
249
+ _init = __decoratorStart(_a);
250
+ __decorateElement(_init, 1, "remoteExportList", _remoteExportList_dec, SessionModeCatalog);
251
+ __decoratorMetadata(_init, SessionModeCatalog);
252
+ //#endregion
121
253
  //#region src/persona.ts
122
254
  const PREFIX_ORDER = "DEPLOYMENT_PERSONA_PREFIX";
123
255
  const SUFFIX_ORDER = "DEPLOYMENT_PERSONA_SUFFIX";
@@ -198,11 +330,7 @@ function narrowCatalog(message, gate, warn) {
198
330
  content: [{
199
331
  ...block,
200
332
  text: lines.join("\n")
201
- }],
202
- source: {
203
- ...source,
204
- entries: entries.filter((entry) => !denied.has(entry.name))
205
- }
333
+ }]
206
334
  };
207
335
  }
208
336
  function skillNameOf(arguments_) {
@@ -427,13 +555,20 @@ var SessionModes = class extends Service {
427
555
  if (this.ctx.sessionProjections.stateOf(session, "sessionModeEditable") === false) throw new Error("这个会话已经开始,模式不能再改;要换模式请新开一个会话。");
428
556
  const agent = this.ctx.agents.get(sessionId);
429
557
  const registry = this.presetRegistry();
430
- if (agent !== void 0 && registry !== void 0 && definition.preset.length > 0) {
431
- if (this.presetOfAgent(registry, agent) !== definition.preset) await registry.select(agent, definition.preset);
558
+ if (agent !== void 0 && registry !== void 0) {
559
+ const target = this.presetTarget(definition.presetsOnly, this.presetOfAgent(registry, agent));
560
+ if (target !== void 0) await registry.select(agent, target);
432
561
  }
433
562
  if (this.ctx.sessionProjections.stateOf(session, "sessionMode") !== mode) session.append("session-mode/selected", { sessionMode: mode });
434
563
  if (agent !== void 0) this.installFor(agent);
435
564
  return mode;
436
565
  }
566
+ presetTarget(allowed, current) {
567
+ if (allowed.length === 0) return void 0;
568
+ if (current !== void 0 && allowed.includes(current)) return void 0;
569
+ const target = allowed[0];
570
+ return target === current ? void 0 : target;
571
+ }
437
572
  presetOfAgent(registry, agent) {
438
573
  const read = registry.composedPreset;
439
574
  if (typeof read !== "function") return void 0;
@@ -478,7 +613,7 @@ var SessionModes = class extends Service {
478
613
  }
479
614
  modeForPreset(preset) {
480
615
  if (preset === void 0) return void 0;
481
- const owners = Object.keys(this.config.modes).filter((id) => this.config.modes[id]?.preset === preset);
616
+ const owners = Object.keys(this.config.modes).filter((id) => this.config.modes[id]?.presetsOnly.includes(preset));
482
617
  return owners.length === 1 ? owners[0] : void 0;
483
618
  }
484
619
  inheritedModeId(agent) {
@@ -529,6 +664,7 @@ var SessionModes = class extends Service {
529
664
  };
530
665
  function apply(ctx, config) {
531
666
  registerHttpRoutes(ctx, new SessionModes(ctx, config));
667
+ ctx.plugin(SessionModeCatalog);
532
668
  }
533
669
  function respondJson(response, status, body) {
534
670
  response.writeHead(status, { "content-type": "application/json; charset=utf-8" });
package/dist/rows.mjs CHANGED
@@ -4,40 +4,51 @@ const CODING_PERSONA = {
4
4
  suffix: "你的工作目录在 `{{cwd}}`"
5
5
  };
6
6
  const CHAT_PERSONA = { prefix: "你是一个助手。全程用中文(专有名词除外),包括但不限于思考,回答,工具描述;思考不要陷入重复循环,一旦循环立即退出。" };
7
+ const NOOP_MODE_ID = "noop";
7
8
  const DEFAULT_MODE = "coding";
8
- const MODE_SOURCES = [{
9
- id: "coding",
10
- name: "编码模式",
11
- description: "功能完整的编码 Agent:文件、Shell、检索、联网等工具常驻,其余用法说明按需加载。",
12
- persona: CODING_PERSONA,
13
- role: ["main", "subagent"],
14
- denyTools: ["load_workspace_dependencies"],
15
- denySkills: [
16
- "office-docx",
17
- "office-pptx",
18
- "office-xlsx"
19
- ],
20
- denyPolicies: ["fs/edit-intent"]
21
- }, {
22
- id: "chat",
23
- name: "对话模式",
24
- description: "只做对话:提问与联网(搜索、抓取)三件工具,不注入系统提示词、工作区指令与技能目录。",
25
- persona: CHAT_PERSONA,
26
- role: ["main"],
27
- allowTools: [
28
- "ask_user_question",
29
- "web_search",
30
- "web_fetch"
31
- ],
32
- instructions: false,
33
- runtimeContext: false
34
- }];
9
+ const MODE_SOURCES = [
10
+ {
11
+ id: "coding",
12
+ name: "编码模式",
13
+ description: "功能完整的编码 Agent:文件、Shell、检索、联网等工具常驻,其余用法说明按需加载。",
14
+ persona: CODING_PERSONA,
15
+ role: ["main", "subagent"],
16
+ denyTools: ["load_workspace_dependencies"],
17
+ denySkills: [
18
+ "office-docx",
19
+ "office-pptx",
20
+ "office-xlsx"
21
+ ],
22
+ denyPolicies: ["fs/edit-intent"]
23
+ },
24
+ {
25
+ id: "chat",
26
+ name: "对话模式",
27
+ description: "只做对话:提问与联网(搜索、抓取)三件工具,不注入系统提示词、工作区指令与技能目录。",
28
+ persona: CHAT_PERSONA,
29
+ role: ["main"],
30
+ allowTools: [
31
+ "ask_user_question",
32
+ "web_search",
33
+ "web_fetch"
34
+ ],
35
+ instructions: false,
36
+ runtimeContext: false
37
+ },
38
+ {
39
+ id: NOOP_MODE_ID,
40
+ name: "原样模式",
41
+ description: "与上游一致:不加人格提示词、不收窄工具与技能、policy 规则全开、官方注入面照旧。",
42
+ role: ["main", "subagent"]
43
+ }
44
+ ];
35
45
  //#endregion
36
46
  //#region src/rows.ts
37
47
  function list(values) {
38
48
  return values === void 0 || values.length === 0 ? void 0 : [...values];
39
49
  }
40
50
  function modeConfig(source) {
51
+ const presetsOnly = list(source.presetsOnly);
41
52
  const allowTools = list(source.allowTools);
42
53
  const denyTools = list(source.denyTools);
43
54
  const allowSkills = list(source.allowSkills);
@@ -45,7 +56,7 @@ function modeConfig(source) {
45
56
  const allowPolicies = list(source.allowPolicies);
46
57
  const denyPolicies = list(source.denyPolicies);
47
58
  return {
48
- ...source.preset === void 0 ? {} : { preset: source.preset },
59
+ ...presetsOnly === void 0 ? {} : { presetsOnly },
49
60
  name: source.name,
50
61
  description: source.description,
51
62
  ...source.role === void 0 ? {} : { role: [...source.role] },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@morlay/dsh-session-mode",
3
- "version": "0.0.6",
3
+ "version": "0.0.8",
4
4
  "description": "Session modes for dsh: coding / chat as data — a mode is a persona plus its tool lists, its upstream policy lists, its instruction / skill-catalog / runtime-context switches and an optional default model. The host applies the current mode to each session, and the chip in the composer sits beside the official agent-preset entry, which stays.",
5
5
  "keywords": [
6
6
  "dsh",
@@ -47,9 +47,12 @@
47
47
  "@deepseek-ai/dsh-api-remotes": "0.2.0-rc.2",
48
48
  "@deepseek-ai/dsh-api-session-controller": "0.2.0-rc.2",
49
49
  "@deepseek-ai/dsh-client-locale": "0.2.0-rc.2",
50
+ "@deepseek-ai/dsh-client-store": "0.2.0-rc.2",
50
51
  "@deepseek-ai/dsh-client-ui-conversation": "0.2.0-rc.2",
52
+ "@deepseek-ai/dsh-client-ui-plugin-manager": "0.2.0-rc.2",
51
53
  "@deepseek-ai/dsh-client-ui-primitives": "0.2.0-rc.2",
52
54
  "@deepseek-ai/dsh-client-ui-renderer": "0.2.0-rc.2",
55
+ "@deepseek-ai/dsh-client-ui-settings": "0.2.0-rc.2",
53
56
  "@deepseek-ai/dsh-client-ui-slots": "0.2.0-rc.2",
54
57
  "@deepseek-ai/dsh-fs": "0.2.0-rc.2",
55
58
  "@deepseek-ai/dsh-fs-local": "0.2.0-rc.2",
@@ -61,8 +64,9 @@
61
64
  "@deepseek-ai/dsh-system-prompt": "0.2.0-rc.2",
62
65
  "@deepseek-ai/dsh-tool-skill": "0.2.0-rc.2",
63
66
  "@deepseek-ai/dsh-tools": "0.2.0-rc.2",
64
- "@morlay/dsh-client-ui-primitives": "0.0.7",
65
- "@morlay/dsh-context-assembler": "0.0.5",
67
+ "@deepseek-ai/dsh-typert-protocol": "0.2.0-rc.2",
68
+ "@morlay/dsh-client-ui-primitives": "0.0.8",
69
+ "@morlay/dsh-context-assembler": "0.0.6",
66
70
  "@testing-library/react": "^16.3.0",
67
71
  "@types/js-yaml": "^4.0.9",
68
72
  "@types/react": "~18.3.1",
@@ -77,14 +81,20 @@
77
81
  "@deepseek-ai/dsh-agent-preset-registry": "^0.2.0-rc.2",
78
82
  "@deepseek-ai/dsh-api-session-controller": "^0.2.0-rc.2",
79
83
  "@deepseek-ai/dsh-client-locale": "^0.2.0-rc.2",
84
+ "@deepseek-ai/dsh-client-store": "^0.2.0-rc.2",
80
85
  "@deepseek-ai/dsh-client-ui-conversation": "^0.2.0-rc.2",
86
+ "@deepseek-ai/dsh-client-ui-plugin-manager": "^0.2.0-rc.2",
81
87
  "@deepseek-ai/dsh-client-ui-primitives": "^0.2.0-rc.2",
88
+ "@deepseek-ai/dsh-client-ui-settings": "^0.2.0-rc.2",
82
89
  "@deepseek-ai/dsh-client-ui-slots": "^0.2.0-rc.2",
83
90
  "@deepseek-ai/dsh-fs": "^0.2.0-rc.2",
84
91
  "@deepseek-ai/dsh-llm": "^0.2.0-rc.2",
85
92
  "@deepseek-ai/dsh-session": "^0.2.0-rc.2",
86
93
  "@deepseek-ai/dsh-session-projection": "^0.2.0-rc.2",
94
+ "@deepseek-ai/dsh-skill": "^0.2.0-rc.2",
87
95
  "@deepseek-ai/dsh-system-prompt": "^0.2.0-rc.2",
96
+ "@deepseek-ai/dsh-tools": "^0.2.0-rc.2",
97
+ "@deepseek-ai/dsh-typert-protocol": "^0.2.0-rc.2",
88
98
  "@deepseek-ai/schemastery": "^3.18.4"
89
99
  },
90
100
  "dsh": {
@@ -92,6 +102,8 @@
92
102
  "platform": "web",
93
103
  "inject": [
94
104
  "@deepseek-ai/dsh-client-locale",
105
+ "@deepseek-ai/dsh-client-ui-plugin-manager",
106
+ "@deepseek-ai/dsh-client-ui-settings",
95
107
  "@deepseek-ai/dsh-client-ui-slots"
96
108
  ]
97
109
  }
@@ -0,0 +1,45 @@
1
+ // 配置页要的"全局目录"(工具名 / 技能名)走一条**本包自己开的 Remote 面**:工具清单只在 host(`ctx.tools`),技能面
2
+ // 在客户端只有按会话的 `remote.skills.list({ sessionId })`——而这个配置页是全局设置面,没有会话。官方没有现成的 api,
3
+ // 所以这条面由本包自己注册:host 半提供服务([`./catalog.ts`](./catalog.ts) 的 `@Remote('list')`),client 半把下面这份
4
+ // 贡献 mount 上去([`./client/index.ts`](./client/index.ts))。
5
+ //
6
+ // 本文件只有类型与那份贡献常量(不带 host 依赖),两半都能 import。
7
+
8
+ import type { RemoteResult, TypertRemoteContribution } from "@deepseek-ai/dsh-typert-protocol";
9
+
10
+ // 线上命名空间与它的 Cordis 服务键:客户端 mount 之后按 `remote.<这个名字>` 取服务。
11
+ export const CATALOG_NS = "sessionModeCatalog";
12
+
13
+ // 部署里可用的工具名与技能名。
14
+ export interface SessionModeCatalogValue {
15
+ readonly tools: readonly string[];
16
+ readonly skills: readonly string[];
17
+ }
18
+
19
+ // 这条 Remote 面的读侧。
20
+ export interface SessionModeCatalogRemote {
21
+ list: () => Promise<RemoteResult<SessionModeCatalogValue>>;
22
+ }
23
+
24
+ declare module "@deepseek-ai/dsh-typert-protocol" {
25
+ interface TypertRemoteNamespaceMap {
26
+ sessionModeCatalog: SessionModeCatalogRemote;
27
+ }
28
+ }
29
+
30
+ // 客户端挂载用的贡献:`result` 用 `src-json`(结果就是普通 JSON,不走严格解码)。
31
+ export const SESSION_MODE_CATALOG_REMOTE: TypertRemoteContribution = {
32
+ package: "@morlay/dsh-session-mode",
33
+ descriptors: [
34
+ {
35
+ id: "@morlay/dsh-session-mode#sessionModeCatalog/list",
36
+ service: CATALOG_NS,
37
+ namespace: CATALOG_NS,
38
+ method: "list",
39
+ implementation: "remoteExportList",
40
+ invocation: { kind: "direct" },
41
+ parameters: [],
42
+ result: { mode: "src-json" },
43
+ },
44
+ ],
45
+ };
package/src/catalog.ts ADDED
@@ -0,0 +1,75 @@
1
+ // 全局目录的 host 半:把部署里**当前可见**的工具名与技能名列出来,供配置页当候选(工具 / 技能名单字段)。
2
+ //
3
+ // 为什么要有这一层:这两份清单只有 host 侧读得到(工具面在 `ctx.tools`、技能面在 `ctx.skills`),而客户端只有按会话的
4
+ // `remote.skills.list`——bundle 配置页却是全局设置面。所以本包自己开一条 Remote 命名空间(贡献常量在
5
+ // [`./catalog-remote.ts`](./catalog-remote.ts),由 client 半自己 mount)。
6
+ //
7
+ // 清单的范围是"**部署里当前可见的那些**",不是某一份静态名单:
8
+ // - 工具按 agent 收口——实测本部署的工具注册都发生在 agent 层,全局视图(`schemas()` 不传 scope)是空的;
9
+ // - 技能按 agent 的收口与工作目录扫——工作区技能要 `cwd` 才看得到,而本行没有自己的工作区上下文。
10
+ // 所以两份都按当前活跃 agent 逐个读、去重合并;没有活跃 agent(刚启动、会话一个都没开)时只剩全局层那份。
11
+ //
12
+ // 不按 preset 分别取:preset 到工具名的映射没有任何服务暴露(工具名由各自的行注册进 agent 层,行清单里只有包名),
13
+ // 要"按 preset 分别取"就得为每个 preset 各起一个探针 agent;而候选在页面上是一维标签输入,分组也没有落点。
14
+
15
+ import type { Context } from "@deepseek-ai/cordis";
16
+ import type { Agent } from "@deepseek-ai/dsh-agent";
17
+ import type {} from "@deepseek-ai/dsh-skill";
18
+ import type {} from "@deepseek-ai/dsh-tools";
19
+ import { Remote, TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
20
+ import { CATALOG_NS, type SessionModeCatalogValue } from "./catalog-remote.ts";
21
+
22
+ // 一个 agent 的工作目录:技能面按 `cwd` 才会扫工作区技能。
23
+ function cwdOf(agent: Agent): string | undefined {
24
+ const session = (agent as unknown as { session?: { header?: { cwd?: unknown } } }).session;
25
+ const cwd = session?.header?.cwd;
26
+ return typeof cwd === "string" ? cwd : undefined;
27
+ }
28
+
29
+ // 部署当前可见的工具与技能。
30
+ export class SessionModeCatalog extends TypertRemoteService {
31
+ /**
32
+ * @param ctx - owning context (the `session-mode` row).
33
+ */
34
+ constructor(ctx: Context) {
35
+ super(ctx, CATALOG_NS);
36
+ }
37
+
38
+ /**
39
+ * List the tool and skill names this deployment currently shows.
40
+ * @returns tool names from the tool registry, and skill names from the skill registry.
41
+ */
42
+ @Remote("list")
43
+ async remoteExportList(): Promise<SessionModeCatalogValue> {
44
+ return { tools: this.toolNames(), skills: await this.skillNames() };
45
+ }
46
+
47
+ // 工具名:全局层 + 每个活跃 agent 的可见面(同一名字只留一次)。
48
+ private toolNames(): readonly string[] {
49
+ const tools = this.ctx.get("tools");
50
+ if (tools === undefined) return [];
51
+ const names = new Set(tools.schemas().map((schema) => String(schema.name)));
52
+ for (const agent of this.agents()) {
53
+ for (const schema of tools.schemas(agent)) names.add(String(schema.name));
54
+ }
55
+ return [...names];
56
+ }
57
+
58
+ // 技能名:全局层 + 每个活跃 agent 按其收口与工作目录看到的那份。
59
+ private async skillNames(): Promise<readonly string[]> {
60
+ const skills = this.ctx.get("skills");
61
+ if (skills === undefined) return [];
62
+ const names = new Set((await skills.list()).map((row) => row.name));
63
+ for (const agent of this.agents()) {
64
+ for (const row of await skills.list({ scope: agent, cwd: cwdOf(agent) })) {
65
+ names.add(row.name);
66
+ }
67
+ }
68
+ return [...names];
69
+ }
70
+
71
+ // 当前活跃的 agent:清单按它们逐个读。服务缺席(极简装配)时按没有处理。
72
+ private agents(): readonly Agent[] {
73
+ return this.ctx.get("agents")?.list() ?? [];
74
+ }
75
+ }