@morlay/dsh-session-mode 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/dist/rows.mjs CHANGED
@@ -1,461 +1,19 @@
1
- //#endregion
2
- //#region ../dsh-agent-toolkit/src/guidance/packs/index.ts
3
- /** 全部工具族(顺序只影响报错信息里的排列)。 */
4
- const TOOL_PACKS = [
5
- {
6
- family: "ask",
7
- tools: [{
8
- tool: "ask_user_question",
9
- short: "向用户提问并等待回答"
10
- }]
11
- },
12
- {
13
- family: "delegation",
14
- tools: [
15
- {
16
- tool: "subagent",
17
- short: "派发子代理执行任务"
18
- },
19
- {
20
- tool: "subagent_fork",
21
- short: "派发继承当前上下文的子代理"
22
- },
23
- {
24
- tool: "list_subagent_models",
25
- short: "查看子代理可用的模型"
26
- },
27
- {
28
- tool: "workflow",
29
- short: "用脚本编排多个子代理"
30
- }
31
- ]
32
- },
33
- {
34
- family: "flow",
35
- tools: [
36
- {
37
- tool: "todo_write",
38
- short: "维护多步任务的待办清单"
39
- },
40
- {
41
- tool: "exit_plan_mode",
42
- short: "提交计划并请求批准"
43
- },
44
- {
45
- tool: "get_goal",
46
- short: "查看当前会话目标"
47
- },
48
- {
49
- tool: "create_goal",
50
- short: "创建会话目标"
51
- },
52
- {
53
- tool: "update_goal",
54
- short: "更新会话目标状态"
55
- },
56
- {
57
- tool: "present",
58
- short: "声明交付给用户的产物"
59
- }
60
- ]
61
- },
62
- {
63
- family: "fs",
64
- tools: [
65
- {
66
- tool: "read",
67
- short: "读文件内容(可指定行范围)"
68
- },
69
- {
70
- tool: "write",
71
- short: "写入文件或整文件覆盖"
72
- },
73
- {
74
- tool: "edit",
75
- short: "按精确匹配替换文件片段"
76
- },
77
- {
78
- tool: "glob",
79
- short: "按模式查找文件路径"
80
- },
81
- {
82
- tool: "grep",
83
- short: "按正则搜索文件内容"
84
- },
85
- {
86
- tool: "read_image",
87
- short: "读取图片内容"
88
- }
89
- ]
90
- },
91
- {
92
- family: "shell",
93
- tools: [
94
- {
95
- tool: "bash",
96
- short: "执行 shell 命令"
97
- },
98
- {
99
- tool: "pwsh",
100
- short: "执行 PowerShell 命令"
101
- },
102
- {
103
- tool: "job_output",
104
- short: "读取后台任务的输出"
105
- },
106
- {
107
- tool: "job_list",
108
- short: "列出后台任务"
109
- },
110
- {
111
- tool: "job_kill",
112
- short: "终止后台任务"
113
- }
114
- ]
115
- },
116
- {
117
- family: "skill",
118
- tools: [{
119
- tool: "skill",
120
- short: "按需加载技能说明"
121
- }]
122
- },
123
- {
124
- family: "team",
125
- tools: [
126
- {
127
- tool: "spawn_teammate",
128
- short: "招募一个队友"
129
- },
130
- {
131
- tool: "send_message",
132
- short: "给队友发消息"
133
- },
134
- {
135
- tool: "list_agents",
136
- short: "列出可协作的代理"
137
- },
138
- {
139
- tool: "wait_agent",
140
- short: "等待队友回复"
141
- },
142
- {
143
- tool: "interrupt_agent",
144
- short: "打断某个队友"
145
- },
146
- {
147
- tool: "team_task_create",
148
- short: "创建共享任务"
149
- },
150
- {
151
- tool: "team_task_list",
152
- short: "列出共享任务"
153
- },
154
- {
155
- tool: "team_task_get",
156
- short: "读取共享任务"
157
- },
158
- {
159
- tool: "team_task_update",
160
- short: "更新共享任务状态"
161
- }
162
- ]
163
- },
164
- {
165
- family: "web",
166
- tools: [{
167
- tool: "web_fetch",
168
- short: "抓取指定 URL 的内容"
169
- }, {
170
- tool: "web_search",
171
- short: "联网搜索"
172
- }]
173
- }
174
- ];
175
- /**
176
- * 工具名的并集:**工具集与汉化同源**——需要"这套工具有哪些"的地方(例如模式的 `allowTools` 白名单)读它,
177
- * 而不是另写一份名单。
178
- * @param packs - 待合并的族,缺省全部。
179
- * @returns 去重后的工具名(按族出现顺序)。
180
- */
181
- function toolNamesOf(packs = TOOL_PACKS) {
182
- const names = [];
183
- const seen = /* @__PURE__ */ new Set();
184
- for (const pack of packs) for (const guidance of pack.tools) {
185
- if (seen.has(guidance.tool)) continue;
186
- seen.add(guidance.tool);
187
- names.push(guidance.tool);
188
- }
189
- return names;
190
- }
191
- //#endregion
192
- //#region ../dsh-agent-toolkit/src/js-expr.ts
193
- /** 词字符:它的两侧不能不留空格地粘起来(`typeof x` 不是 `typeofx`)。 */
194
- const WORD = /[\w$]/u;
195
- /**
196
- * 压掉字符串字面量之外的空白,只在**两侧都是词字符**时留一个(`typeof x` 不能粘成 `typeofx`,
197
- * 而 `a === b` 可以压成 `a===b`)。
198
- *
199
- * 为什么非要压:函数体文本取自 `String(body)`,而它由**加载这段代码的那台转换器**给出——`tsdown` 读
200
- * `tsdown.config.ts` 的 loader 与 vitest 的 vite 转换器对同一个表达式给出的空白不同(`a === "1"` 对
201
- * `a==="1"`)。产物是要入库、又要与 `render()` 逐字符比对的文件,所以这里收口到一种形态。
202
- *
203
- * 只动空白、不补空白:无论输入是松是紧,结果一样(幂等),也就不必赌转换器站哪边。
204
- */
205
- function compact(expression) {
206
- let out = "";
207
- let quote;
208
- let pendingSpace = false;
209
- for (const char of expression) {
210
- if (quote !== void 0) {
211
- out += char;
212
- if (char === quote) quote = void 0;
213
- continue;
214
- }
215
- if (char === "\"" || char === "'" || char === "`") quote = char;
216
- else if (/\s/u.test(char)) {
217
- pendingSpace = out.length > 0;
218
- continue;
219
- }
220
- if (pendingSpace && WORD.test(out.at(-1) ?? "") && WORD.test(char)) out += " ";
221
- pendingSpace = false;
222
- out += char;
223
- }
224
- return out;
225
- }
226
- /**
227
- * 把一段 TS 表达式内联进产物:传一个**无参函数**,取它的函数体,于是表达式是编译器检查过的代码,
228
- * 而不是手拼的字符串(`process.platform` 这类运行期判断必须留给运行期——产物在构建机上生成)。
229
- *
230
- * 函数体写**单行表达式**:多行的那份原样留着(那时压缩会动到换行与注释的位置)。
231
- */
232
- function jsExpr(body) {
233
- const text = String(body).replace(/^\(\)\s*=>\s*/, "").replace(/;\s*$/, "");
234
- return { __jsExpr: text.includes("\n") ? text : compact(text) };
235
- }
236
- //#endregion
237
- //#region ../dsh-agent-toolkit/src/rows.ts
238
- const UPSTREAM_SCOPE = "@deepseek-ai/dsh-";
239
- /** 行 id 默认取短名(可被 extra.id 覆盖)。 */
240
- function named(short, name, extra) {
241
- const { id, ...rest } = extra;
242
- return {
243
- id: id ?? short,
244
- name,
245
- ...rest
246
- };
247
- }
248
- /**
249
- * 上游的一行:`row("tool-fs")` → 包 `@deepseek-ai/dsh-tool-fs`、行 id `tool-fs`。
250
- *
251
- * 包名按「scope + 短名」拼(目录名不可靠:`packages/compaction/tool-result-pruner` 的包名是
252
- * `@deepseek-ai/dsh-compaction-tool-result-pruner`);id 由我们自己定,默认与短名一致。
253
- * 带 subpath 的行(`tool-subagent-control/list-agents`)与需要短 id 的行显式给 `id`。
254
- */
255
- function row(short, extra = {}) {
256
- return named(short, `${UPSTREAM_SCOPE}${short}`, extra);
257
- }
258
- /** 一个工具族:`group("toolkit-"+族名, 行)`——组名与 `guidance/packs/` 的族文件同名(前缀见 {@link TOOLKIT_ROWS})。 */
259
- function group(id, config, extra = {}) {
260
- return {
261
- id,
262
- name: "cordis:group",
263
- group: true,
264
- config,
265
- ...extra
266
- };
267
- }
268
- /**
269
- * 这套工具集的工具名(从汉化数据派生:**工具集与汉化同源**)。
270
- *
271
- * 谁需要它:模式的 `allowTools` 白名单——模式只声明"我要哪些",工具行本身在 profile 平面装一次。
272
- */
273
- const TOOLKIT_TOOL_NAMES = toolNamesOf();
274
- /** agent-team 那几行默认关闭:不是 `DSH_AGENT_TEAM=1` 就不装。 */
275
- function agentTeamDisabled() {
276
- return jsExpr(() => process.env.DSH_AGENT_TEAM !== "1");
277
- }
278
- /** 跨平台的一对 shell 工具:装哪一半由运行期平台决定。 */
279
- const SHELL_ROWS = [row("tool-bash", { disabled: jsExpr(() => process.platform === "win32") }), row("tool-pwsh", { disabled: jsExpr(() => process.platform !== "win32") })];
280
- row("experimental-agent-team", {
281
- id: "agent-team",
282
- disabled: agentTeamDisabled(),
283
- config: {
284
- maxMembers: 8,
285
- maxTasks: 256,
286
- maxPendingMessagesPerMember: 64,
287
- maxMessageBytes: 65536,
288
- disposalTimeoutMs: 5e3
289
- }
290
- }), row("experimental-tool-agent-team", {
291
- id: "tool-agent-team",
292
- disabled: agentTeamDisabled(),
293
- config: {
294
- freshProvider: "spawn",
295
- forkProvider: "fork"
296
- }
297
- }), row("experimental-client-ui-agent-team", {
298
- id: "ui-agent-team",
299
- disabled: agentTeamDisabled()
300
- });
301
- /**
302
- * 完整一套功能行(coding 用),**按工具族分组**:族顺序与 `guidance/packs/index.ts` 的族索引一致。
303
- * 它们引用的都是上游包,行 id 由本仓库定(见 {@link row})。
304
- *
305
- * 组 id 是 `toolkit-<族名>`(不是裸族名):装配按 id 全局对应,上游已有行占用 `web` / `skill` 这类短名,
306
- * 撞上会让两行被当作同一行(后者覆盖前者的 config,组行拿到非数组 config 直接装配失败)。
307
- *
308
- * 不含 Agent Teams 那一族({@link TEAM_ROWS}):团队归上游 `agent-team-profile`,我们不单独配。
309
- */
310
- const TOOLKIT_ROWS = [
311
- group("toolkit-ask", [row("tool-ask-user")]),
312
- group("toolkit-delegation", [
313
- row("tool-subagent-control"),
314
- row("tool-subagent-control/list-agents", { id: "tool-subagent-list-agents" }),
315
- row("tool-subagent", { config: {
316
- provider: "spawn",
317
- toolName: "subagent",
318
- backgroundMode: "continuable"
319
- } }),
320
- row("tool-subagent", {
321
- id: "tool-subagent-fork",
322
- config: {
323
- provider: "fork",
324
- toolName: "subagent_fork",
325
- backgroundMode: "continuable"
326
- }
327
- }),
328
- row("workflow-ptc", { config: { provider: "spawn" } }),
329
- row("tool-workflow")
330
- ], { isolate: { workflowEngine: true } }),
331
- group("toolkit-flow", [
332
- row("tool-todo", { config: { allowParallelInProgress: true } }),
333
- row("command-goal"),
334
- row("tool-goal"),
335
- row("tool-present", { id: "present" })
336
- ]),
337
- group("toolkit-fs", [row("tool-fs"), row("tool-fs-search", { config: { sampleOverCapGlobResults: false } })]),
338
- group("toolkit-shell", [...SHELL_ROWS, row("tool-jobs")]),
339
- group("toolkit-skill", [row("skill-filesystem")]),
340
- group("toolkit-web", [row("tool-web", { config: {
341
- fetch: true,
342
- searchTimeoutMs: 6e4
343
- } })])
344
- ];
345
- /**
346
- * 不属于任何工具族、但归 preset 平面的行:上下文压缩(引擎,不是模型侧工具)与计划模式。
347
- */
348
- const TOOLKIT_COMPACTION_ROWS = [group("compaction", [
349
- row("compaction-basic"),
350
- row("command-compact"),
351
- row("compaction-tool-result-pruner", {
352
- id: "tool-result-pruner",
353
- config: {
354
- thresholdChars: 8192,
355
- headChars: 4096,
356
- tailChars: 1024
357
- }
358
- })
359
- ], { isolate: {
360
- compaction: true,
361
- toolResultPruner: true
362
- } })];
363
- /**
364
- * 计划模式(`plan-mode` 行 + 它的 `exit_plan_mode` 工具):组形照上游
365
- * (`bundle/web-app/presets/standard.patch.yml` 的 `planning` 组:`isolate: { planMode: true }`——preset realm
366
- * 里的服务必须隔离,否则 mount 直接失败)。
367
- *
368
- * **规则正文由我们自己维护**({@link PLAN_MODE_SECTION},中文):`@deepseek-ai/dsh-plan-mode` 的 `section` 是
369
- * 部署自有的必填项(`vendor/deepseek-harness/packages/plan/plan-mode/src/index.ts` 的 `resolveConfig`:缺 / 空 /
370
- * 多键都抛),**没有默认文案**,所以这一行必须带上它。代价是上游改 `plan-mode` 的契约(`section` 的形状、
371
- * `exit_plan_mode` 的行为)时要人工核对这段中文的语义。
372
- */
373
- const TOOLKIT_PLAN_ROWS = [group("planning", [row("plan-mode", { config: { section: [
374
- "你处于计划模式。在 exit_plan_mode 成功、或用户切换会话模式之前,一直留在计划模式。",
375
- "",
376
- "先探索:用非破坏性的读、搜索、静态分析与检查,把计划落到真实仓库上。不要做任何写操作——不改文件、不改配置、不跑会重写被跟踪文件的格式化或代码生成、不提交,也不要把计划执行掉。优先用仓库里已有的函数与写法,不要新造机制。",
377
- "",
378
- "用户的对话式同意(包括对你自己提问的肯定回答)不构成批准,也不结束计划模式;把确认到的决定并进计划里。",
379
- "",
380
- "工具目录在模式之间保持一致(请求缓存稳定),所以这些规则压过任何后续工具说明或用法正文里“去改文件”的建议;那些工具仍列在目录里,只是这一步不能用。不要用 todo_write 跟踪规划阶段:它跟的是批准之后的实施,计划本身归 exit_plan_mode。",
381
- "",
382
- "能自己查清的事实自己查。只有用户拥有的选择、或检查无法回答的实质歧义才用 ask_user_question;不要问代码在哪、现在怎么跑这类自己能查明的问题。",
383
- "",
384
- "计划要决策完备:目标与成功判据;按子系统分组的改动;公共 API、schema 与数据流的变化;边界情况与失败模式;测试与验收判据;显式假设。可审即可,不必长到别人照它实现还要自己做设计决定。",
385
- "",
386
- "就绪时调 exit_plan_mode,正文是完整的计划 markdown(以 # 标题开头),并让它成为那一条回复里唯一且最后的工具调用:它把计划交出去等批准,实施只在批准之后的步骤里开始。不要把最终计划当普通回复贴出来,也不要用散文或 ask_user_question 问“要不要继续”。评审驳回就吸收反馈重新提交;评审通道不可用或被中断时留在计划模式、让用户手动切模式,不要自己往下做。"
387
- ].join("\n") } })], { isolate: { planMode: true } })];
388
- [...TOOLKIT_COMPACTION_ROWS, toolGuidanceRow()];
389
- /**
390
- * 策略行(不属于工具族):放宽 fs 的写 / 改 intent——**挂本 preset 的会话不吃上游「先读后改」**。
391
- *
392
- * 为什么是"抢 waterfall 抵消"而不是"禁用上游那一行":`fs-observation-policy` 住在 host 平面、对所有 preset
393
- * 生效;preset realm 里禁用同 id 的行禁不掉它,而在 host 平面禁用又会连官方 shipped preset 的会话一起失去
394
- * 策略。判据与实现见 [`./relax-intent.ts`](./relax-intent.ts)。
395
- */
396
- const TOOLKIT_POLICY_ROWS = [{
397
- id: "fs-intent-relax",
398
- name: "@morlay/dsh-agent-toolkit/relax-intent"
399
- }];
400
- [
401
- ...TOOLKIT_ROWS,
402
- ...TOOLKIT_COMPACTION_ROWS,
403
- ...TOOLKIT_PLAN_ROWS,
404
- ...TOOLKIT_POLICY_ROWS
405
- ];
406
- /** 工具说明那一行(汉化精简 + 用法分组):实现与数据在 `./guidance` 出口,行本身也归本包。 */
407
- function toolGuidanceRow(config) {
408
- return {
409
- id: "tool-guidance",
410
- name: "@morlay/dsh-agent-toolkit/guidance",
411
- ...config === void 0 ? {} : { config }
412
- };
413
- }
414
- row("tool-ask-user"), row("tool-web");
415
- //#endregion
416
1
  //#region src/mode-sources.ts
417
- /**
418
- * 模式定义的真源:`session-mode` 行的 `config` 由 `./rows.ts` 渲染(装配入口在 `packages/bundles/session-mode-profile`)。
419
- *
420
- * "自定义"就落在这份数据上——装配层(profile 的用户 patch 层)可以整体改写 `config.modes`,也可以只给
421
- * 某个模式换提示词或工具白名单,不需要任何插件行。
422
- *
423
- * `allowTools` 从 [`@morlay/dsh-agent-toolkit/rows`](../../../dsh-agent-toolkit/src/rows.ts) 的
424
- * `TOOLKIT_TOOL_NAMES` 派生(工具名与汉化同源):**行清单由本部署自己注册的那份 preset 提供**
425
- * ({@link MODE_PRESET_ID},两个模式共享它),白名单里那份 preset 没有的工具自动跳过。想改某一个模式的名单,
426
- * 直接在这条源数据里加/减。
427
- */
428
- /**
429
- * 本部署自己注册的那份 agent preset 的 id:两个模式共享它(行清单见
430
- * `packages/bundles/session-mode-profile`,`config.plugins` 引用 `@morlay/dsh-agent-toolkit/rows` 的
431
- * `TOOLKIT_PRESET_ROWS`)。
432
- *
433
- * 为什么自己注册而不是复用官方 preset:官方 `minimal` 没有 `tool-web`,`chat` 的白名单(提问 + 联网三件)
434
- * 收口后一件都不剩;而且官方 preset 自带的上游注入要在我们的开关之外做"让位",抢 `skill` 面还得把目录 kind
435
- * 换掉。行清单归我们之后,两个模式的**差异全在会话级收口**(persona / `allowTools` / 两个开关)。
436
- */
437
- const MODE_PRESET_ID = "mode-switch";
438
- /** 编码模式:编程专家 + 语言与思考纪律 + 工作目录提醒。 */
439
2
  const CODING_PERSONA = {
440
3
  prefix: ["你是一个经验丰富的编程专家,YAGNI 是你的编程哲学,PDCA 是你的行为规范。", "全程用中文(专有名词除外),包括但不限于思考,回答,工具描述,subagent 提示词;思考不要陷入重复循环,一旦循环立即退出;思考聚焦需求理解与方案设计,不预演具体代码实现,正确性由验证环节确认。"].join("\n"),
441
4
  suffix: "你的工作目录在 `{{cwd}}`"
442
5
  };
443
- /** 对话模式:一个助手,保留语言与思考纪律,没有 suffix。 */
444
6
  const CHAT_PERSONA = { prefix: "你是一个助手。全程用中文(专有名词除外),包括但不限于思考,回答,工具描述;思考不要陷入重复循环,一旦循环立即退出。" };
445
- /** 新会话用哪个模式(`session-mode` 行的 `config.default`)。 */
446
7
  const DEFAULT_MODE = "coding";
447
- /** 两个模式:编码与对话。**同一个 preset,差异全在会话级收口**。 */
448
8
  const MODE_SOURCES = [{
449
9
  id: "coding",
450
- preset: MODE_PRESET_ID,
451
10
  name: "编码模式",
452
11
  description: "功能完整的编码 Agent:文件、Shell、检索、联网等工具常驻,其余用法说明按需加载。",
453
12
  persona: CODING_PERSONA,
454
13
  role: ["main", "subagent"],
455
- allowTools: [...TOOLKIT_TOOL_NAMES]
14
+ denyPolicies: ["fs/edit-intent"]
456
15
  }, {
457
16
  id: "chat",
458
- preset: MODE_PRESET_ID,
459
17
  name: "对话模式",
460
18
  description: "只做对话:提问与联网(搜索、抓取)三件工具,不注入系统提示词、工作区指令与技能目录。",
461
19
  persona: CHAT_PERSONA,
@@ -470,25 +28,30 @@ const MODE_SOURCES = [{
470
28
  }];
471
29
  //#endregion
472
30
  //#region src/rows.ts
473
- /**
474
- * 本包作为能力包发布的**装配数据**:`session-mode` 行的 config(各模式的会话级扩展)由这里渲染,
475
- * 装配入口在 `packages/bundles/session-mode-profile`(它同时装注入通道、工具说明与 subagent 那几行)。
476
- */
477
- /** 一个模式的源定义 → 行 config(schema 的输入形状:可省的字段就省)。 */
31
+ function list(values) {
32
+ return values === void 0 || values.length === 0 ? void 0 : [...values];
33
+ }
478
34
  function modeConfig(source) {
35
+ const allowTools = list(source.allowTools);
36
+ const denyTools = list(source.denyTools);
37
+ const allowPolicies = list(source.allowPolicies);
38
+ const denyPolicies = list(source.denyPolicies);
479
39
  return {
480
- preset: source.preset,
40
+ ...source.preset === void 0 ? {} : { preset: source.preset },
481
41
  name: source.name,
482
42
  description: source.description,
483
43
  ...source.role === void 0 ? {} : { role: [...source.role] },
484
44
  ...source.persona === void 0 ? {} : { persona: { ...source.persona } },
485
- allowTools: [...source.allowTools],
45
+ ...allowTools === void 0 ? {} : { allowTools },
46
+ ...denyTools === void 0 ? {} : { denyTools },
47
+ ...allowPolicies === void 0 ? {} : { allowPolicies },
48
+ ...denyPolicies === void 0 ? {} : { denyPolicies },
486
49
  ...source.instructions === void 0 ? {} : { instructions: source.instructions },
50
+ ...source.skills === void 0 ? {} : { skills: source.skills },
487
51
  ...source.runtimeContext === void 0 ? {} : { runtimeContext: source.runtimeContext },
488
52
  ...source.defaultModel === void 0 ? {} : { defaultModel: { ...source.defaultModel } }
489
53
  };
490
54
  }
491
- /** `session-mode` 行的装配:默认模式与各模式的扩展定义。 */
492
55
  function sessionModeRows() {
493
56
  return [{ insert: [{
494
57
  id: "session-mode",
@@ -500,4 +63,4 @@ function sessionModeRows() {
500
63
  }] }];
501
64
  }
502
65
  //#endregion
503
- export { MODE_PRESET_ID, sessionModeRows };
66
+ export { sessionModeRows };
@@ -0,0 +1,5 @@
1
+ //#region src/shared.ts
2
+ const SESSION_MODE_PATH = "/session-mode";
3
+ const POLICY_NAMES = ["fs/write-intent", "fs/edit-intent"];
4
+ //#endregion
5
+ export { SESSION_MODE_PATH as n, POLICY_NAMES as t };
package/locale/en.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "meta": {
3
- "title": "Session modes",
4
- "description": "The coding / chat modes as one row of data (persona plus the allowed tools); the host applies the current one per session, and the chip in the composer replaces the official agent-preset surface."
3
+ "title": "Session Modes",
4
+ "description": "The coding / chat modes as one row of data (persona, tool lists, upstream policy lists, instruction / skill-catalog / runtime-context switches and an optional default model); the host applies the current one per session, and the chip in the composer sits beside the official agent-preset entry, which stays."
5
5
  }
6
6
  }
package/locale/zh.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "meta": {
3
3
  "title": "会话模式",
4
- "description": "coding / chat 两个模式:一行数据(提示词 + 工具白名单),host 按会话应用;composer 里的模式 chip 替代官方的 agent preset 面。"
4
+ "description": "coding / chat 两个模式:一行数据(提示词、工具名单、上游 policy 名单、instruction / 技能目录 / 动态快照开关与可选默认模型),host 按会话应用;composer 里的模式 chip 与官方 agent preset 入口并存。"
5
5
  }
6
6
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@morlay/dsh-session-mode",
3
- "version": "0.0.2",
4
- "description": "Personal dsh bundle: session modes (coding / chat) as data — a mode is a persona plus the tools it may use. The host applies the current mode to each session, and the chip in the composer replaces the official agent-preset surface.",
3
+ "version": "0.0.4",
4
+ "description": "Personal dsh bundle: session modes (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",
7
7
  "dsh-bundle",
@@ -50,17 +50,18 @@
50
50
  "@deepseek-ai/dsh-client-ui-primitives": "0.2.0-rc.1",
51
51
  "@deepseek-ai/dsh-client-ui-renderer": "0.2.0-rc.1",
52
52
  "@deepseek-ai/dsh-client-ui-slots": "0.2.0-rc.1",
53
+ "@deepseek-ai/dsh-fs": "0.2.0-rc.1",
53
54
  "@deepseek-ai/dsh-fs-local": "0.2.0-rc.1",
55
+ "@deepseek-ai/dsh-fs-observation-policy": "0.2.0-rc.1",
54
56
  "@deepseek-ai/dsh-llm": "0.2.0-rc.1",
55
57
  "@deepseek-ai/dsh-session": "0.2.0-rc.1",
56
58
  "@deepseek-ai/dsh-session-projection": "0.2.0-rc.1",
57
59
  "@deepseek-ai/dsh-skill": "0.2.0-rc.1",
58
60
  "@deepseek-ai/dsh-system-prompt": "0.2.0-rc.1",
59
61
  "@deepseek-ai/dsh-tools": "0.2.0-rc.1",
60
- "@morlay/dsh-agent-toolkit": "0.0.2",
61
- "@morlay/dsh-client-ui-conversation": "0.1.1",
62
- "@morlay/dsh-client-ui-primitives": "0.0.4",
63
- "@morlay/dsh-context-assembler": "0.0.2",
62
+ "@morlay/dsh-client-ui-conversation": "0.1.3",
63
+ "@morlay/dsh-client-ui-primitives": "0.0.6",
64
+ "@morlay/dsh-context-assembler": "0.0.4",
64
65
  "@testing-library/react": "^16.3.0",
65
66
  "@types/js-yaml": "^4.0.9",
66
67
  "@types/react": "~18.3.1",
@@ -77,13 +78,14 @@
77
78
  "@deepseek-ai/dsh-client-locale": "^0.2.0-rc.1",
78
79
  "@deepseek-ai/dsh-client-ui-primitives": "^0.2.0-rc.1",
79
80
  "@deepseek-ai/dsh-client-ui-slots": "^0.2.0-rc.1",
81
+ "@deepseek-ai/dsh-fs": "^0.2.0-rc.1",
80
82
  "@deepseek-ai/dsh-llm": "^0.2.0-rc.1",
81
83
  "@deepseek-ai/dsh-session": "^0.2.0-rc.1",
82
84
  "@deepseek-ai/dsh-session-projection": "^0.2.0-rc.1",
83
85
  "@deepseek-ai/dsh-system-prompt": "^0.2.0-rc.1",
84
86
  "@deepseek-ai/schemastery": "^3.18.4",
85
- "@morlay/dsh-client-ui-conversation": "0.1.1",
86
- "@morlay/dsh-client-ui-primitives": "0.0.4"
87
+ "@morlay/dsh-client-ui-conversation": "0.1.3",
88
+ "@morlay/dsh-client-ui-primitives": "0.0.6"
87
89
  },
88
90
  "dsh": {
89
91
  "client": {
@@ -1,14 +1,6 @@
1
- /**
2
- * composer 里的模式 chip:**本包在会话里唯一的面**(点开是 coding / chat 两项)。
3
- *
4
- * 选择只在会话开始**之前**有效——一旦跑过 turn,那段历史是在某个模式的工具与提示词下产生的。所以 chip 在
5
- * 会话里有两个形态:空白期是选择器,开过 turn 之后**只读**(只写当前模式,点不动)。判据是 host 的投影
6
- * `sessionModeEditable`(`turn/start` 一落库就为 `false`),与服务端拒绝切换读的是同一份事实。
7
- *
8
- * 它挂在 composer 上:新会话屏也是一个 blank session 的 composer,选择正好发生在那里;会话头部则没有它的
9
- * 位置——头部只在"跑起来之后"值得看,而那时的 chip 已经把当前模式写在脸上,再挂一个只读标签就是同一句话的
10
- * 复读。
11
- */
1
+ // composer 里的模式 chip:本包在会话里唯一的面(点开是 coding / chat 两项)。
2
+ // 选择只在会话开始**之前**有效(那段历史属于某个模式的工具与提示词),所以 chip 有两个形态:空白期是选择器,
3
+ // 开过 turn 之后只读。判据是 host 的投影 `sessionModeEditable`,与服务端拒绝切换读的是同一份事实。
12
4
 
13
5
  import { useState } from "react";
14
6
  import {
@@ -25,20 +17,12 @@ import { selectMode } from "./api.ts";
25
17
  import { useRoster } from "./use-roster.ts";
26
18
  import css from "./SessionModeSeat.module.css";
27
19
 
28
- /** 完整 props:composer 工具行左侧槽位的运行时 props + 本包的字典。 */
20
+ // 完整 props:composer 工具行左侧槽位的运行时 props + 本包的字典。
29
21
  export type SessionModeSeatProps = PropsRuntime<"conversation.input.left"> &
30
22
  PropsLocale<"session-mode">;
31
23
 
32
- /**
33
- * 渲染 composer 里的模式切换 chip。
34
- *
35
- * 为什么在 composer(`conversation.input.left`):**新会话屏也是一个 blank session 的 composer**,选择正好
36
- * 发生在那里;会话头部槽位在新会话屏根本不存在,而会话跑起来之后头部也不该重复说一遍 chip 已经写着的事。
37
- * 官方 roster 那个座位(`conversation.hero.agentPreset`)是单注册槽位、已被官方占了,所以用自己的 list 槽位
38
- * 并存。
39
- * @param props - 槽位合成的 props。
40
- * @returns chip;清单没读到、或会话未知时返回 null。
41
- */
24
+ // 渲染 composer 里的模式切换 chip:新会话屏也是 blank session 的 composer,选择正好发生在那里;官方 roster 占了
25
+ // 单注册槽位 `conversation.hero.agentPreset`,所以这里用自己的 list 槽位并存。清单没读到时会话未知时返回 null。
42
26
  export function SessionModeSeat({ sessionId, useSessions, t }: SessionModeSeatProps) {
43
27
  const roster = useRoster();
44
28
  const selected = useSessions((state) => {
@@ -63,8 +47,7 @@ export function SessionModeSeat({ sessionId, useSessions, t }: SessionModeSeatPr
63
47
  const chosen = roster.modes.find((mode) => mode.id === current);
64
48
  const label = chosen?.name ?? current;
65
49
 
66
- // 开过 turn 的会话**只读**:chip 只写当前模式,点不动也不给清单。判据与 host 拒绝切换读的是同一个投影
67
- // (`sessionModeEditable`),所以不会出现"看起来能选、点了报错"。
50
+ // 开过 turn 的会话只读:chip 只写当前模式、点不动(判据与 host 拒绝切换读的是同一个投影)。
68
51
  if (editable === false) {
69
52
  return (
70
53
  <button type="button" className={css.seat} disabled title={t("lockedHint")}>
package/src/client/api.ts CHANGED
@@ -1,9 +1,5 @@
1
- /**
2
- * client 半与 host 的**唯一**通路:模式清单(`GET`)与切换(`POST`)。
3
- *
4
- * 当前模式不走这里——它是一条会话投影(`sessionMode`),随会话列表一起到页面,组件用 `useSessions`
5
- * 读它。于是这里只剩两件事:拉清单、发一次切换。
6
- */
1
+ // client 半与 host 的**唯一**通路:模式清单(`GET`)与切换(`POST`)。当前模式是会话投影(`sessionMode`),
2
+ // 随会话列表一起到页面,不走这里。
7
3
 
8
4
  import {
9
5
  SESSION_MODE_PATH,
@@ -11,7 +7,7 @@ import {
11
7
  type SessionModeSelectResult,
12
8
  } from "../shared.ts";
13
9
 
14
- /** 读一次模式清单;清单在页面生命周期里是静态的(装配事实),调用方自己缓存。 */
10
+ // 读一次模式清单;清单在页面生命周期里是静态的(装配事实),调用方自己缓存。
15
11
  export async function fetchRoster(): Promise<SessionModeRoster> {
16
12
  const response = await fetch(SESSION_MODE_PATH, {
17
13
  method: "GET",
@@ -21,13 +17,7 @@ export async function fetchRoster(): Promise<SessionModeRoster> {
21
17
  return (await response.json()) as SessionModeRoster;
22
18
  }
23
19
 
24
- /**
25
- * 把某个空白会话切到某个模式。
26
- * @param sessionId - 目标会话。
27
- * @param mode - 目标模式 id。
28
- * @returns 提交后的模式 id。
29
- * @throws 会话已开始、模式不存在、会话不存在——host 给的中文原因。
30
- */
20
+ // 把某个空白会话切到某个模式;失败抛 host 给的中文原因(会话已开始 / 模式不存在 / 会话不存在)。
31
21
  export async function selectMode(sessionId: string, mode: string): Promise<string> {
32
22
  const response = await fetch(SESSION_MODE_PATH, {
33
23
  method: "POST",