@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/README.md CHANGED
@@ -1,129 +1,70 @@
1
1
  # @morlay/dsh-session-mode
2
2
 
3
- **模式 = agent preset 的会话级扩展**:每个模式声明它挂在哪份 preset 上(`preset`,行清单由那份 preset 提供),
4
- 再给这个会话加四样东西——一段 persona、一组工具白名单(收口)、instruction / 动态快照开关、可选默认模型。
5
- 本部署的 preset 是**我们自己注册的那一份**(`mode-switch`,见
6
- [`@morlay/session-mode-profile`](../../bundles/session-mode-profile/README.md) 与
7
- [ADR-自己注册preset](../../bundles/session-mode-profile/.agents/adrs/20260929-自己注册preset.md)),
8
- 两个模式共享它——**差异全由会话级收口表达**。选择面由我们提供(composer 里的模式 chip 与
9
- `GET/POST /session-mode`);模式的选择落成**会话事实**(`session-mode/selected` 事件 + `sessionMode` 投影)。
10
- **模式不是 Cordis 子树**。
3
+ **模式 = agent preset 的会话级扩展**:每个模式给这个会话加六样东西——一段 persona、一组工具名单(允许哪些、
4
+ 另外禁掉哪些)、一份 policy 名单(哪些上游裁决规则生效)、instruction / 技能目录 / 动态快照三个开关,外加可选的
5
+ 默认模型。模式**不绑 preset**:行清单(工具 / 命令 / 压缩 / 委派)由会话挂着
6
+ 的那份 preset 提供,本部署用官方 shipped `standard`(registry 的默认由官方 web-app 给,我们不覆盖);
7
+ `config.modes.<id>.preset` 是可选声明,写了才会在切模式时把 preset 切过去。模式清单与默认值就是本行的 `config.modes`:真源在
8
+ [`src/mode-sources.ts`](./src/mode-sources.ts),行 config 由 [`src/rows.ts`](./src/rows.ts) 渲染;**模式不是 Cordis
9
+ 子树**,选择落成会话事实(`session-mode/selected` 事件 + `sessionMode` 投影)。会话里选模式走本包 client 半的 chip
10
+ 与 `GET/POST /session-mode`,且只在**空白会话**成立。
11
11
 
12
- 本包不装配任何行:行 config 由 `src/rows.ts` 渲染,装配入口在
13
- [`@morlay/session-mode-profile`](../../bundles/session-mode-profile/README.md)(那里插两行)。
12
+ **按会话收口也在这个包里**([`src/scope.ts`](./src/scope.ts),模式行内部持有、不发布服务):工具目录与
13
+ `tool:<工具名>` section 按同一份合成结果过滤、执行层 guard 挂在该 agent 的 ctx 上、三个开关
14
+ (`instructions` / `skills` / `runtimeContext`)按会话生效——其中 `instructions: false` 与 `skills: false` 是在
15
+ `agent/pre-step` 上**丢掉官方两条注入面**(工作区指令、技能目录)。取舍见
16
+ [设计 抑制官方注入面](./.agents/designs/20260929-抑制官方注入面.md)。
14
17
 
15
- | 行 | 是什么 |
16
- | ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
17
- | `session-mode` | 各模式的扩展定义(`config.modes.<id>.preset` + persona / `allowTools` / 开关 / `defaultModel`)与默认模式,按会话应用 + preset→模式反查 |
18
- | `context-assembler-scope` | [`@morlay/dsh-context-assembler/scope`](../../context/dsh-context-assembler/README.md):按会话收口 |
18
+ 本包不装配任何行:装配入口在 [`@morlay/session-mode-profile`](../../bundles/session-mode-profile/README.md)
19
+ (它插 `session-mode`、通道、工具说明与 subagent 那几行)。
19
20
 
20
- 工具行、注入通道与压缩都不在这里:行清单由 preset 的 `config.plugins` 提供(本部署那份由
21
- [`@morlay/session-mode-profile`](../../bundles/session-mode-profile/README.md) 声明,官方四个 shipped preset
22
- 照旧可选),注入通道与工具说明由各自的 bundle 在 host 平面装一次。
21
+ ## 用法
23
22
 
24
- ## 一个模式是什么
23
+ 自定义就是改这份 config——profile 的用户 patch 层可以整体改写 `config.modes`,也可以只给某个模式换提示词、
24
+ 工具名单、policy 名单或它挂的 preset,不需要任何插件行:
25
25
 
26
26
  ```yaml
27
27
  - id: session-mode
28
28
  name: "@morlay/dsh-session-mode"
29
29
  config:
30
- default: coding
30
+ default: coding # 新会话的起始模式:必须在 modes 里且是 main 角色
31
31
  modes:
32
+ coding:
33
+ name: 编码模式
34
+ description: 功能完整的编码 Agent。
35
+ role: [main, subagent]
36
+ # 上游 `fs-observation-policy` 的两条规则里禁掉改那条(免"先读后改");写那条照旧生效(CAS 安全网)。
37
+ denyPolicies: [fs/edit-intent]
32
38
  chat:
33
- # 挂哪份 preset:行清单由它的 config.plugins 提供(本部署这份两个模式共享)。
34
- preset: mode-switch
35
39
  name: 对话模式
36
- description: 只做对话:提问与联网(搜索、抓取)三件工具,不注入系统提示词、工作区指令与技能目录。
37
- role: [main]
40
+ description: 只做对话:提问与联网(搜索、抓取)三件工具。
41
+ role: [main] # main = 用户选择器;subagent = 可作子代理 mode 的候选
38
42
  persona:
39
43
  prefix: 你是一个助手。……
40
- allowTools: [ask_user_question, web_search, web_fetch]
41
- instructions: false
42
- runtimeContext: false
43
- # 这个模式的默认模型(可选;省略就跟全局 agent-default-model):
44
+ allowTools: [ask_user_question, web_search, web_fetch] # 留空 = 不设收窄(用 preset 的全部工具)
45
+ denyTools: [] # 黑名单:从上面那份里减掉;两份同时命中时以这里为准(deny 优先)
46
+ instructions: false # 丢掉官方工作区指令的注入,并关掉通道自己的降级注入
47
+ skills: false # 显式丢掉官方技能目录的注入;**本部署不写这一项**——推导已给出同一个值(见下)
48
+ runtimeContext: false # 动态快照(沙箱策略、审批策略)
44
49
  defaultModel: { provider: ollama, model: deepseek-v4.1-flash, reasoningEffort: high }
50
+ # preset: standard # 可选:写了才会在切模式时把 preset 切过去(不写 = 保持会话当前那份)
45
51
  ```
46
52
 
47
- | 字段 | 落到哪 |
48
- | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
49
- | `preset` | 挂哪份 agent preset(官方或本部署自建的 `id`):**可以共享**(本部署两个模式共享同一份,差异由会话级收口表达);空串表示不挂 |
50
- | `name` / `description` | 模式的事实文案(选择面上显示的是官方 roster 的文案) |
51
- | `role` | 归谁用:`main`(用户选择器)/ `subagent`(可作子代理 mode 的候选);缺省 `["main"]` |
52
- | `persona` | 装配前注册到**该 agent 的 scope**(`deployment:persona-prefix` / `-suffix`,遮蔽部署级那层) |
53
- | `allowTools` | `context-assembler-scope` 收口:模型目录、`tool:<名字>` 说明、执行层 guard、常驻用法正文;**preset 没有的工具自动跳过** |
54
- | `instructions` | 同上:`false` 表示这个会话不要任何 instruction 类注入(工作区指令、技能目录、用法正文) |
55
- | `runtimeContext` | 同上:`false` 表示不要动态快照(文件沙箱策略、审批策略) |
56
- | `defaultModel` | 这个模式的默认模型(可选):会话还没有模型事实时接管请求路由,不写会话事件 |
57
-
58
- 顶层还有 `default`(不在模式里):它是新会话的起始模式;各模式的默认模型在模式自己的 `defaultModel` 里
59
- (`provider` / `model` / `reasoningEffort?`,不写就跟全局 `agent-default-model`)。这些字段都是 config 的
60
- **volatile** 字段,`@morlay/dsh-client-ui-primitives` 为这一行(`session-mode`)生成的行配置页编辑的就是它们。
61
-
62
- 两类字段的生效方式不同:
63
-
64
- | 字段 | 改了之后 |
65
- | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
66
- | `default` / `modes` | 等 Loader 重挂这一行(settings 写完会重装被改的行);**已运行会话不自动换定义**,重挂后新建的会话或重新应用模式的会话才用新定义(各模式的 `defaultModel` 在 `modes` 里,同一条) |
67
-
68
- 模式名与说明是数据、不做语言翻译(`src/mode-sources.ts` 里只有中文)——取舍如此,不是漂移。
69
-
70
- 默认模型为什么能住在模式里:`modes` 整段 volatile,整棵子树都在设置面的投影里,`defaultModel` 作为它下面的普通
71
- 字段跟着上页面(自己不必、也不许再标一层 volatile)。判据见
72
- [ADR 默认模型住在模式定义里](./.agents/adrs/20260925-默认模型住在模式定义里.md)。
73
-
74
- **自定义就是改这份 config**:profile 的用户 patch 层可以整体改写 `config.modes`,也可以只给某个模式换提示词、
75
- 白名单或它挂的 preset——不需要任何插件行。装配期的判据(默认模式必须在清单里、每个模式至少给一个工具、每个
76
- 模式的 `defaultModel` 要给全)由 `modes.ts` 的 `configProblem` 兜住,写错直接拒绝装载;`preset` 只要求"非空即
77
- 声明了挂载",它是否存在由 preset 注册表自己回答。
78
-
79
- ## 会话与模式
80
-
81
- - 新会话用 `config.default`;`agent/created` 时把模式应用到该 agent(persona + 收口),早于它的第一次装配。
82
- - **选模式就换 preset**(`select`):目标 preset 与当前挂着的不同时,先把它换掉(行清单与模式一起换),再写一条
83
- `session-mode/selected` 并立刻按新模式重新应用;**相同时不换**——本部署两个模式共享同一份 preset,切 chip 因此
84
- 不再重挂行清单。官方 preset 的选择(空白窗口里直接选一个 shipped preset)在这条路上只于**映射唯一**时才反查
85
- 到模式,其余时候模式由会话事实决定。换 preset 只在**空白窗口**成立——跑过 turn 的那段历史是在旧工具集与提示词
86
- 下产生的,换了 preset,日志与实际装配就对不上。取舍见
87
- [ADR 选模式就换 preset](./.agents/adrs/20260928-选模式就换preset.md)。
88
- - 当前模式读的是 session 投影 `sessionMode`:恢复与 fork 都据此重建(官方的 `agentPreset` 说的是"挂了哪套行",
89
- 两者不是同一个事实)。
90
- - **子代理继承父的模式**:子代理是新会话,创建时(`agent/created`)取父当前模式并写进子会话日志——继承也是
91
- 一条会话事实,恢复与 fork 照样能重建。父不在场时回落部署默认。继承**不看 `role`**:`subagent` 角色声明的
92
- 是"可被指定"的候选,不是继承白名单。
93
-
94
- ## 角色与默认模型
95
-
96
- - **角色**:`main` = 用户侧可选(官方 roster 的清单按它列);`subagent` = 可作为子代理 mode 的候选。
97
- 「按角色指派 mode」还没做——子代理现在只有"继承父"与预留的服务接缝
98
- (`ctx.sessionModes.applyTo(agent, mode)` / `modesFor("subagent")`)。
99
- - **默认模型**:`config.modes[<模式 id>].defaultModel` 只在会话**尚无模型事实**时接管请求路由(投影
100
- `modelSelection` 没有 `pending`、`requestHeader()` 还没落);一旦用户选过模型或会话跑过请求,就不再插手。
101
- 它是**配置事实**,不写会话事件——重启后仍由 config 决定,与用户在设置里做的那条会话级选择
102
- (`model/selection`)是两件事。读的是构造时那份模式清单快照:设置页保存会让这一行重挂,新定义随重挂生效。
103
-
104
- ## 页面上的选择面
105
-
106
- 选择面是**本包的 client 半**:一个模式 chip 挂在 composer 工具行左侧(`conversation.input.left`,list + session
107
- scope),点开就是清单,清单与切换走 HTTP 路由 `GET/POST /session-mode`。它有两个形态:**空白期是选择器**;
108
- 会话开过 turn 之后**只读**(只写当前模式,点不动、没有下拉面,悬停说明"换模式请新开一个会话")——判据是 host 的
109
- 投影 `sessionModeEditable`,与服务端拒绝切换读的是同一份事实,所以不会出现"看起来能选、点了报错"。
110
-
111
- 挂 composer 而不是会话头部:一个会话的模式只在它跑第一轮**之前**能改(host 会拒绝给已在跑的会话换模式),而
112
- 新会话屏也是一个 blank session 的 composer——头部槽位在那里根本不存在。头部的标签**已去掉**:chip 本来就把
113
- 当前模式写在脸上,右上角再写一遍是同一句话的复读。
114
-
115
- 官方 `ui-agent-preset` 那套**保留**(它的 roster 座位在 `conversation.hero.agentPreset`,是单注册槽位)——两套入口
116
- 并存:官方管"挂哪套行"的选择面,我们管"会话级扩展"的选择面。`modeForPreset` 只在 preset → 模式的映射**唯一**
117
- 时回答(本部署两个模式共享一份 preset,所以它对本部署的 preset 返回 `undefined`:模式由会话事实决定,preset
118
- 选了什么不改变这个会话是哪个模式)。
119
-
120
- ## 文档
121
-
122
- - 设计与取舍:[设计 会话模式](./.agents/designs/20260924-会话模式.md)、
123
- [ADR 模式是 preset 的会话级扩展](./.agents/adrs/20260928-模式是preset的会话级扩展.md)、
124
- [ADR 默认模型住在模式定义里](./.agents/adrs/20260925-默认模型住在模式定义里.md)、
125
- [ADR 模式的角色与默认模型](./.agents/adrs/20260923-模式角色与默认模型.md)
126
- - 验证判据:[本包规范 how-to-verify](./.agents/standards/how-to-verify.md)
127
- - 形态沿革(已作废):[ADR preset 改用上游声明式行](./.agents/adrs/20260922-preset改用上游声明式行.md)、
128
- [ADR 通道与注入行按模式isolate装配](./.agents/adrs/20260922-通道与注入行按模式isolate装配.md)、
129
- [设计 预设生成与装配](./.agents/designs/20260917-预设生成与装配.md)
53
+ `skills` **不写就由这个模式自己的工具名单推导**:`(allowTools 留空 ? 全部 : allowTools) − denyTools` 里含 `skill`
54
+ 就要技能目录(`allowTools` 留空即"全部",所以只有 `denyTools` 能把它推成 `false`)。本部署两个模式都不写它:
55
+ `coding` 留空名单 → `true`,`chat` 的三件里没有 `skill` → `false`。要"工具收窄但目录照旧列"就显式写 `skills: true`。
56
+
57
+ 写错在装载时就拒绝:`default` 必须在清单里且声明 `main`、`role` 不能是空数组、`defaultModel` 要给全 `provider` 与
58
+ `model`、`allowPolicies` / `denyPolicies` 里的名字必须是上游那两条 waterfall(`fs/write-intent` /
59
+ `fs/edit-intent`)。改动等 Loader 重挂这一行生效,已运行会话不自动换定义。
60
+
61
+ 四处别当成全能开关:`allowTools` **留空就是不设收窄**(用会话挂着的 preset 的全部工具),列了名单时那份 preset
62
+ 没有的工具会**自动跳过**(会话挂着官方 `minimal` 时 `chat` 一件都不剩);`instructions: false` 丢的是官方
63
+ `agent-instructions` 的每步注入**与**通道自己的降级注入,代价是官方那一行每步仍会重读 `AGENTS.md` 重算一次
64
+ (结果被丢掉);开关为 `true`(或 `instructions` 缺省)时这条抑制**零干预**——官方那两行的注入节奏(首次、以及内容
65
+ 有变才注入的增量)照旧;`skills: false` 只丢官方目录的注入,`skill` 工具本身的可见性归 `allowTools`;`denyPolicies` 只改
66
+ **上游那两条 waterfall** 上的裁决(`tool-fs` 的写 / 改与 `tool-str-replace-editor`),`bash` 之类自己写文件的通路
67
+ 不经过它们。取舍见 [ADR 模式不绑定 preset](./.agents/adrs/20260929-模式不绑定preset.md)、
68
+ [ADR allowTools 留空即不设收窄](./.agents/adrs/20260929-allowTools留空即不设收窄.md)、
69
+ [设计 按模式的 policy 拦截](./.agents/designs/20260929-按模式的policy拦截.md) 与
70
+ [设计 抑制官方注入面](./.agents/designs/20260929-抑制官方注入面.md)。
package/dist/client.cjs CHANGED
@@ -8,26 +8,10 @@ window.__ModuleLoader__.load({
8
8
  let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
9
9
  let react_jsx_runtime = require("react/jsx-runtime");
10
10
  //#region src/shared.ts
11
- /**
12
- * host 半与 client 半共用的接口面:一条 HTTP 路径、模式行的对外形状、请求与响应体。
13
- *
14
- * 为什么不是 Typert Remote:客户端的 remote 清单(`@deepseek-ai/dsh-api-remotes/client`)由上游硬编码,
15
- * 我们的服务不在里面,`ctx.remote.sessionModes` 解析不到。仓库既有的跨半通路是 HTTP 路由
16
- * (见 `@morlay/ui-conversation-message-actions` 的 `/session-editor`),这里沿用同一种。
17
- *
18
- * 会话当前模式不走这条通路:它是一条 session 投影(`sessionMode`),随会话列表一起到页面。
19
- */
20
- /** 模式清单与切换的路由路径:宿主(web 与桌面)在同一张路由表上服务它。 */
21
11
  const SESSION_MODE_PATH = "/session-mode";
12
+ const POLICY_NAMES = ["fs/write-intent", "fs/edit-intent"];
22
13
  //#endregion
23
14
  //#region src/client/api.ts
24
- /**
25
- * client 半与 host 的**唯一**通路:模式清单(`GET`)与切换(`POST`)。
26
- *
27
- * 当前模式不走这里——它是一条会话投影(`sessionMode`),随会话列表一起到页面,组件用 `useSessions`
28
- * 读它。于是这里只剩两件事:拉清单、发一次切换。
29
- */
30
- /** 读一次模式清单;清单在页面生命周期里是静态的(装配事实),调用方自己缓存。 */
31
15
  async function fetchRoster() {
32
16
  const response = await fetch(SESSION_MODE_PATH, {
33
17
  method: "GET",
@@ -36,13 +20,6 @@ window.__ModuleLoader__.load({
36
20
  if (!response.ok) throw new Error(`HTTP ${String(response.status)}`);
37
21
  return await response.json();
38
22
  }
39
- /**
40
- * 把某个空白会话切到某个模式。
41
- * @param sessionId - 目标会话。
42
- * @param mode - 目标模式 id。
43
- * @returns 提交后的模式 id。
44
- * @throws 会话已开始、模式不存在、会话不存在——host 给的中文原因。
45
- */
46
23
  async function selectMode(sessionId, mode) {
47
24
  const response = await fetch(SESSION_MODE_PATH, {
48
25
  method: "POST",
@@ -64,12 +41,7 @@ window.__ModuleLoader__.load({
64
41
  }
65
42
  //#endregion
66
43
  //#region src/client/use-roster.ts
67
- /**
68
- * 模块级缓存:清单是装配事实(`session-mode` 行的 config),页面生命周期里不会变,两次挂载之间不必重拉。
69
- * 失败不缓存——下次挂载就是一次重试。
70
- */
71
44
  let pending;
72
- /** 读模式清单;还没读到(或读失败)时返回 undefined,组件据此不渲染。 */
73
45
  function useRoster() {
74
46
  const [roster, setRoster] = (0, react.useState)(void 0);
75
47
  (0, react.useEffect)(() => {
@@ -99,37 +71,16 @@ window.__ModuleLoader__.load({
99
71
  }
100
72
  var SessionModeSeat_module_css_default = {
101
73
  "seatLabel": "dDMF8q_seatLabel",
102
- "seatIcon": "dDMF8q_seatIcon",
103
- "item": "dDMF8q_item",
104
- "itemName": "dDMF8q_itemName",
105
74
  "seat": "dDMF8q_seat",
106
75
  "chevron": "dDMF8q_chevron",
107
76
  "itemDesc": "dDMF8q_itemDesc",
108
- "menuAnchor": "dDMF8q_menuAnchor"
77
+ "menuAnchor": "dDMF8q_menuAnchor",
78
+ "seatIcon": "dDMF8q_seatIcon",
79
+ "itemName": "dDMF8q_itemName",
80
+ "item": "dDMF8q_item"
109
81
  };
110
82
  //#endregion
111
83
  //#region src/client/SessionModeSeat.tsx
112
- /**
113
- * composer 里的模式 chip:**本包在会话里唯一的面**(点开是 coding / chat 两项)。
114
- *
115
- * 选择只在会话开始**之前**有效——一旦跑过 turn,那段历史是在某个模式的工具与提示词下产生的。所以 chip 在
116
- * 会话里有两个形态:空白期是选择器,开过 turn 之后**只读**(只写当前模式,点不动)。判据是 host 的投影
117
- * `sessionModeEditable`(`turn/start` 一落库就为 `false`),与服务端拒绝切换读的是同一份事实。
118
- *
119
- * 它挂在 composer 上:新会话屏也是一个 blank session 的 composer,选择正好发生在那里;会话头部则没有它的
120
- * 位置——头部只在"跑起来之后"值得看,而那时的 chip 已经把当前模式写在脸上,再挂一个只读标签就是同一句话的
121
- * 复读。
122
- */
123
- /**
124
- * 渲染 composer 里的模式切换 chip。
125
- *
126
- * 为什么在 composer(`conversation.input.left`):**新会话屏也是一个 blank session 的 composer**,选择正好
127
- * 发生在那里;会话头部槽位在新会话屏根本不存在,而会话跑起来之后头部也不该重复说一遍 chip 已经写着的事。
128
- * 官方 roster 那个座位(`conversation.hero.agentPreset`)是单注册槽位、已被官方占了,所以用自己的 list 槽位
129
- * 并存。
130
- * @param props - 槽位合成的 props。
131
- * @returns chip;清单没读到、或会话未知时返回 null。
132
- */
133
84
  function SessionModeSeat({ sessionId, useSessions, t }) {
134
85
  const roster = useRoster();
135
86
  const selected = useSessions((state) => {
@@ -212,12 +163,6 @@ window.__ModuleLoader__.load({
212
163
  }
213
164
  //#endregion
214
165
  //#region src/client/locales.ts
215
- /**
216
- * client 半的文案:命名空间 `session-mode`(与 locale 注册时用的 key 一致)。
217
- *
218
- * 只有会话里那个模式 chip 用这份字典:模式的名字与说明由 host 的清单给(那是数据,不是文案)。本行的配置页
219
- * (各模式的默认模型)由通用 schema 表单渲染,文案在它自己的字典里。
220
- */
221
166
  const zh = {
222
167
  seatHint: "选择这个会话的模式",
223
168
  lockedHint: "这个会话已经开始:换模式请新开一个会话",
@@ -242,21 +187,16 @@ window.__ModuleLoader__.load({
242
187
  };
243
188
  //#endregion
244
189
  //#region src/client/index.ts
245
- /** 浏览器半插件的字典命名空间。 */
246
190
  const NS = "session-mode";
247
- /** 这一行里需要中文文案的字段(都在 `modes.<模式>.defaultModel` 里,所以按模板路径注册一次)。 */
248
191
  const FIELDS = [
249
192
  "provider",
250
193
  "model",
251
194
  "reasoningEffort"
252
195
  ];
253
- /** 动态键的占位段(与通用表单的字段树同一约定)。 */
196
+ const POLICY_FIELDS = ["allowPolicies", "denyPolicies"];
254
197
  const DYNAMIC = "*";
255
- /** 需要的服务:槽位与字典(会话列表经槽位的标准 props 到达组件,不必自己 inject)。 */
256
198
  const inject = ["slots", "locale"];
257
- /** 本包 host 行 id:行配置页读的就是这个命名空间。 */
258
199
  const SESSION_MODE_NS = "session-mode";
259
- /** 按路径读一段配置里的值(本包只读 provider 档案里的模型清单)。 */
260
200
  function readAt(root, path) {
261
201
  return path.reduce((node, segment) => {
262
202
  if (Array.isArray(node)) return node[Number(segment)];
@@ -264,11 +204,6 @@ window.__ModuleLoader__.load({
264
204
  return Reflect.get(node, segment);
265
205
  }, root);
266
206
  }
267
- /**
268
- * 装上会话里的两个面,以及本行配置页的字段文案。
269
- *
270
- * @param ctx - 浏览器插件上下文。
271
- */
272
207
  function apply(ctx) {
273
208
  const t = ctx.locale.bind(NS);
274
209
  ctx.effect(() => ctx.locale.register(NS, {
@@ -290,6 +225,17 @@ window.__ModuleLoader__.load({
290
225
  for (const off of offs) off();
291
226
  };
292
227
  }, "session-mode: field wording"));
228
+ ctx.inject(["schemaFormHints"], (scope) => scope.effect(() => {
229
+ const offs = POLICY_FIELDS.map((key) => scope.schemaFormHints.select(SESSION_MODE_NS, [
230
+ "modes",
231
+ DYNAMIC,
232
+ key,
233
+ DYNAMIC
234
+ ], { options: () => POLICY_NAMES.map((value) => ({ value })) }));
235
+ return () => {
236
+ for (const off of offs) off();
237
+ };
238
+ }, "session-mode: policy candidates"));
293
239
  ctx.inject(["schemaFormHints"], (scope) => scope.effect(() => {
294
240
  const hints = scope.schemaFormHints;
295
241
  const remote = scope.get("remote");
package/dist/client.d.cts CHANGED
@@ -1,12 +1,6 @@
1
1
  import { Context } from "@deepseek-ai/cordis";
2
2
  import { PropsLocale, PropsRuntime } from "@deepseek-ai/dsh-client-ui-slots";
3
3
  //#region src/client/locales.d.ts
4
- /**
5
- * client 半的文案:命名空间 `session-mode`(与 locale 注册时用的 key 一致)。
6
- *
7
- * 只有会话里那个模式 chip 用这份字典:模式的名字与说明由 host 的清单给(那是数据,不是文案)。本行的配置页
8
- * (各模式的默认模型)由通用 schema 表单渲染,文案在它自己的字典里。
9
- */
10
4
  declare const zh: {
11
5
  seatHint: string;
12
6
  lockedHint: string;
@@ -21,25 +15,16 @@ declare const zh: {
21
15
  type SessionModeLocaleKey = keyof typeof zh;
22
16
  //#endregion
23
17
  //#region src/client/SessionModeSeat.d.ts
24
- /** 完整 props:composer 工具行左侧槽位的运行时 props + 本包的字典。 */
25
18
  type SessionModeSeatProps = PropsRuntime<"conversation.input.left"> & PropsLocale<"session-mode">;
26
19
  //#endregion
27
20
  //#region src/client/index.d.ts
28
21
  declare module "@deepseek-ai/dsh-client-ui-slots" {
29
22
  interface LocaleNamespaceMap {
30
- /** 会话里两个面的文案(配置页文案在通用 schema 表单的字典里)。 */
31
23
  "session-mode": SessionModeLocaleKey;
32
24
  }
33
25
  }
34
- /** 需要的服务:槽位与字典(会话列表经槽位的标准 props 到达组件,不必自己 inject)。 */
35
- declare const inject: string[];
36
- /** 本包 host 行 id:行配置页读的就是这个命名空间。 */
37
- declare const SESSION_MODE_NS = "session-mode";
38
- /**
39
- * 装上会话里的两个面,以及本行配置页的字段文案。
40
- *
41
- * @param ctx - 浏览器插件上下文。
42
- */
43
- declare function apply(ctx: Context): void;
26
+ export declare const inject: string[];
27
+ export declare const SESSION_MODE_NS = "session-mode";
28
+ export declare function apply(ctx: Context): void;
44
29
  //#endregion
45
- export { SESSION_MODE_NS, type SessionModeLocaleKey, type SessionModeSeatProps, apply, inject };
30
+ export type { SessionModeLocaleKey, SessionModeSeatProps };
package/dist/client.d.mts CHANGED
@@ -1,12 +1,6 @@
1
1
  import { Context } from "@deepseek-ai/cordis";
2
2
  import { PropsLocale, PropsRuntime } from "@deepseek-ai/dsh-client-ui-slots";
3
3
  //#region src/client/locales.d.ts
4
- /**
5
- * client 半的文案:命名空间 `session-mode`(与 locale 注册时用的 key 一致)。
6
- *
7
- * 只有会话里那个模式 chip 用这份字典:模式的名字与说明由 host 的清单给(那是数据,不是文案)。本行的配置页
8
- * (各模式的默认模型)由通用 schema 表单渲染,文案在它自己的字典里。
9
- */
10
4
  declare const zh: {
11
5
  seatHint: string;
12
6
  lockedHint: string;
@@ -21,25 +15,16 @@ declare const zh: {
21
15
  type SessionModeLocaleKey = keyof typeof zh;
22
16
  //#endregion
23
17
  //#region src/client/SessionModeSeat.d.ts
24
- /** 完整 props:composer 工具行左侧槽位的运行时 props + 本包的字典。 */
25
18
  type SessionModeSeatProps = PropsRuntime<"conversation.input.left"> & PropsLocale<"session-mode">;
26
19
  //#endregion
27
20
  //#region src/client/index.d.ts
28
21
  declare module "@deepseek-ai/dsh-client-ui-slots" {
29
22
  interface LocaleNamespaceMap {
30
- /** 会话里两个面的文案(配置页文案在通用 schema 表单的字典里)。 */
31
23
  "session-mode": SessionModeLocaleKey;
32
24
  }
33
25
  }
34
- /** 需要的服务:槽位与字典(会话列表经槽位的标准 props 到达组件,不必自己 inject)。 */
35
- declare const inject: string[];
36
- /** 本包 host 行 id:行配置页读的就是这个命名空间。 */
37
- declare const SESSION_MODE_NS = "session-mode";
38
- /**
39
- * 装上会话里的两个面,以及本行配置页的字段文案。
40
- *
41
- * @param ctx - 浏览器插件上下文。
42
- */
43
- declare function apply(ctx: Context): void;
26
+ export declare const inject: string[];
27
+ export declare const SESSION_MODE_NS = "session-mode";
28
+ export declare function apply(ctx: Context): void;
44
29
  //#endregion
45
- export { SESSION_MODE_NS, type SessionModeLocaleKey, type SessionModeSeatProps, apply, inject };
30
+ export type { SessionModeLocaleKey, SessionModeSeatProps };