@morlay/dsh-session-mode 0.0.7 → 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 +27 -15
- 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-KXJjDaN6.mjs +17 -0
- package/dist/client.cjs +1364 -16751
- package/dist/client.d.cts +149 -3
- package/dist/client.d.mts +149 -3
- package/dist/client.mjs +1612 -0
- package/dist/index.cjs +635 -0
- package/dist/index.d.mts +5 -3
- package/dist/index.mjs +73 -57
- package/dist/rows.cjs +85 -0
- package/dist/rows.mjs +40 -32
- 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 +74 -50
- package/src/catalog-remote.ts +45 -0
- package/src/catalog.ts +75 -0
- package/src/client/BundleConfigPage.tsx +631 -0
- package/src/client/SessionModeSeat.tsx +21 -19
- package/src/client/bundle-config.ts +701 -0
- package/src/client/bundle-locales.ts +216 -0
- package/src/client/index.ts +178 -74
- package/src/client/llm-directory.ts +149 -0
- package/src/client/locales.ts +2 -2
- package/src/index.ts +28 -11
- package/src/mode-sources.ts +31 -14
- package/src/modes.ts +51 -25
- package/src/rows.ts +3 -2
- package/src/scope.ts +74 -65
- package/src/testing.ts +3 -0
- package/dist/index.d.cts +0 -144
- package/dist/rows.d.cts +0 -11
- package/src/client/SessionModeSeat.module.css +0 -74
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
// bundle 配置页的文案:命名空间 `session-mode-bundle`(与行内那半的 `session-mode` 分开——两者的面与生命周期都不同)。
|
|
2
|
+
//
|
|
3
|
+
// 模式自己的名字与说明是**数据**(行 config 里的 `name` / `description`),不在这里;这里只有页面的结构、控件,以及
|
|
4
|
+
// 每个字段"到底管什么"的那句说明。
|
|
5
|
+
|
|
6
|
+
// 本页字典的命名空间。
|
|
7
|
+
export const BUNDLE_NS = "session-mode-bundle";
|
|
8
|
+
|
|
9
|
+
export const bundleZh = {
|
|
10
|
+
"default.label": "新会话的默认模式",
|
|
11
|
+
"default.hint": "还没选过模式的会话从这里开始。",
|
|
12
|
+
"modes.label": "模式",
|
|
13
|
+
"modes.hint": "展开一个模式,改它的提示词、工具与技能、上游规则和注入内容。",
|
|
14
|
+
"add.label": "添加模式",
|
|
15
|
+
"add.placeholder": "模式 id",
|
|
16
|
+
"add.confirm": "添加",
|
|
17
|
+
"add.cancel": "取消",
|
|
18
|
+
"add.close": "关闭",
|
|
19
|
+
"add.hint": "给新模式一个 id(选择器与列表里也显示它,加好后可以再改名称)。",
|
|
20
|
+
removeNamed: "删除模式 {name}",
|
|
21
|
+
protected: "内置模式,不提供删除",
|
|
22
|
+
untitled: "(未命名)",
|
|
23
|
+
overridden: "已覆盖",
|
|
24
|
+
"group.identity": "基本信息",
|
|
25
|
+
"group.persona": "提示词",
|
|
26
|
+
"group.tools": "工具与技能",
|
|
27
|
+
"group.rules": "上游规则",
|
|
28
|
+
"group.injections": "请求里注入什么",
|
|
29
|
+
"group.model": "默认模型",
|
|
30
|
+
"role.main": "会话可选",
|
|
31
|
+
"role.subagent": "子代理可用",
|
|
32
|
+
"field.name": "名称",
|
|
33
|
+
"field.description": "一句话说明",
|
|
34
|
+
"field.role": "角色",
|
|
35
|
+
"field.presetsOnly": "允许挂的 preset",
|
|
36
|
+
"field.prefix": "提示词前缀",
|
|
37
|
+
"field.suffix": "提示词后缀",
|
|
38
|
+
"field.allowTools": "允许的工具",
|
|
39
|
+
"field.denyTools": "禁用的工具",
|
|
40
|
+
"field.allowSkills": "允许的技能",
|
|
41
|
+
"field.denySkills": "禁用的技能",
|
|
42
|
+
"field.allowPolicies": "生效的规则",
|
|
43
|
+
"field.denyPolicies": "禁用的规则",
|
|
44
|
+
"field.instructions": "工作区指令",
|
|
45
|
+
"field.skills": "技能目录",
|
|
46
|
+
"field.runtimeContext": "动态快照",
|
|
47
|
+
"field.defaultModel": "设默认模型",
|
|
48
|
+
"field.modelRoute": "用哪个模型",
|
|
49
|
+
"field.reasoningEffort": "思考档位",
|
|
50
|
+
"field.reset": "恢复默认",
|
|
51
|
+
"hint.name": "选择器和列表里显示的名字。",
|
|
52
|
+
"hint.description": "一句话说明这个模式做什么。",
|
|
53
|
+
"hint.role": "「会话可选」= 用户能在会话里选它;「子代理可用」= 可以把子代理跑在这个模式下。",
|
|
54
|
+
"hint.presetsOnly":
|
|
55
|
+
"只允许这个模式挂这些 preset(行清单由挂着的那份提供);不填 = 允许全部(哪份 preset 都行,切模式也不动它)。有值时切到这个模式,会话当前那份不在名单里就换成名单第一个。",
|
|
56
|
+
"hint.prefix": "加在系统提示词开头的一段话。",
|
|
57
|
+
"hint.suffix": "加在系统提示词结尾的一段话。",
|
|
58
|
+
"hint.allowTools": "只允许这些工具;不填 = 不限制(用会话当前 preset 的全部工具)。",
|
|
59
|
+
"hint.denyTools": "从上面那份里去掉这些工具。",
|
|
60
|
+
"hint.allowSkills": "只允许这些技能;不填 = 不限制。",
|
|
61
|
+
"hint.denySkills": "这些技能不列进技能目录,`skill` 工具也加载不了。",
|
|
62
|
+
"hint.allowPolicies": "只让这些上游规则生效;不填 = 全部生效。",
|
|
63
|
+
"hint.denyPolicies": "禁用这些上游规则,它的判断连拒绝一起丢掉。",
|
|
64
|
+
"hint.instructions": "每步都把工作区指令(AGENTS.md 之类)发给模型;关掉就不发。",
|
|
65
|
+
"hint.skills": "每步都把技能目录发给模型;默认按这个模式的工具名单自动判断。",
|
|
66
|
+
"hint.runtimeContext": "每步都把沙箱与审批策略发给模型。",
|
|
67
|
+
"hint.defaultModel":
|
|
68
|
+
"开 = 这个模式自带一个默认模型(会话还没选过模型时用它);关 = 跟随全局模型。",
|
|
69
|
+
"hint.modelRoute": "从部署里已设置的模型里挑一条:服务商与模型是一对,选它等于两个一起定。",
|
|
70
|
+
"hint.reasoningEffort": "思考档位;不填用服务商默认。",
|
|
71
|
+
"model.loading": "正在读部署里已设置的模型…",
|
|
72
|
+
"model.error": "读不出部署里已设置的模型。",
|
|
73
|
+
"model.empty": "部署里还没有已设置的模型。",
|
|
74
|
+
"tags.placeholder": "加一个后回车,或粘贴一串",
|
|
75
|
+
"tags.add": "添加",
|
|
76
|
+
"tags.remove": "移除 {name}",
|
|
77
|
+
"select.search": "搜索候选",
|
|
78
|
+
"select.noMatch": "没有匹配的候选",
|
|
79
|
+
"tri.unset": "默认",
|
|
80
|
+
"tri.on": "开",
|
|
81
|
+
"tri.off": "关",
|
|
82
|
+
"remove.title": "删除模式",
|
|
83
|
+
"remove.description": "删除 {name}?删除只在保存之后生效,保存之前还可以丢弃。",
|
|
84
|
+
"remove.confirm": "删除",
|
|
85
|
+
"remove.cancel": "取消",
|
|
86
|
+
"remove.close": "关闭",
|
|
87
|
+
"model.set": "设置默认模型",
|
|
88
|
+
"model.follow": "改为跟随全局",
|
|
89
|
+
save: "保存",
|
|
90
|
+
saving: "保存中…",
|
|
91
|
+
saveFailed: "保存没有生效,请重试。",
|
|
92
|
+
readOnly: "这份部署的配置是只读的。",
|
|
93
|
+
unavailable: "这一行当前不可写。",
|
|
94
|
+
notSaved: "没有保存:{message}",
|
|
95
|
+
"configured.loading": "正在读取这一行的配置…",
|
|
96
|
+
"configured.missing":
|
|
97
|
+
"设置面里没有 `session-mode` 这一行:没装、被禁用,或者还没装配成功(依赖没到齐时会一直 PENDING)。",
|
|
98
|
+
"configured.unreadable": "`session-mode` 这一行在跑,但这份配置读不出来({problem})。",
|
|
99
|
+
"violation.modesEmpty": "至少要有一种模式。",
|
|
100
|
+
"violation.defaultMissing": "默认模式不在模式清单里。",
|
|
101
|
+
"violation.modelPair": "默认模型要同时给出服务商与模型。",
|
|
102
|
+
"summary.allTools": "全部工具",
|
|
103
|
+
"summary.tools": "{n} 件工具",
|
|
104
|
+
"summary.denyTools": "禁 {n} 件",
|
|
105
|
+
"summary.globalModel": "跟随全局模型",
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export const bundleEn: Record<keyof typeof bundleZh, string> = {
|
|
109
|
+
"default.label": "Mode for new sessions",
|
|
110
|
+
"default.hint": "Sessions that never picked a mode start here.",
|
|
111
|
+
"modes.label": "Modes",
|
|
112
|
+
"modes.hint":
|
|
113
|
+
"Expand a mode to edit its prompt, tools and skills, upstream rules, and injected content.",
|
|
114
|
+
"add.label": "Add a mode",
|
|
115
|
+
"add.placeholder": "mode id",
|
|
116
|
+
"add.confirm": "Add",
|
|
117
|
+
"add.cancel": "Cancel",
|
|
118
|
+
"add.close": "Close",
|
|
119
|
+
"add.hint":
|
|
120
|
+
"Give the new mode an id (it also shows in pickers and lists; rename it after adding).",
|
|
121
|
+
removeNamed: "Delete mode {name}",
|
|
122
|
+
protected: "Built-in mode: no delete",
|
|
123
|
+
untitled: "(untitled)",
|
|
124
|
+
overridden: "Overridden",
|
|
125
|
+
"group.identity": "Basics",
|
|
126
|
+
"group.persona": "Prompt",
|
|
127
|
+
"group.tools": "Tools and skills",
|
|
128
|
+
"group.rules": "Upstream rules",
|
|
129
|
+
"group.injections": "Injected into requests",
|
|
130
|
+
"group.model": "Default model",
|
|
131
|
+
"role.main": "Selectable in sessions",
|
|
132
|
+
"role.subagent": "Usable for subagents",
|
|
133
|
+
"field.name": "Name",
|
|
134
|
+
"field.description": "One-line description",
|
|
135
|
+
"field.role": "Roles",
|
|
136
|
+
"field.presetsOnly": "Presets it may ride on",
|
|
137
|
+
"field.prefix": "Prompt prefix",
|
|
138
|
+
"field.suffix": "Prompt suffix",
|
|
139
|
+
"field.allowTools": "Allowed tools",
|
|
140
|
+
"field.denyTools": "Denied tools",
|
|
141
|
+
"field.allowSkills": "Allowed skills",
|
|
142
|
+
"field.denySkills": "Denied skills",
|
|
143
|
+
"field.allowPolicies": "Rules in force",
|
|
144
|
+
"field.denyPolicies": "Rules disabled",
|
|
145
|
+
"field.instructions": "Workspace instructions",
|
|
146
|
+
"field.skills": "Skill catalog",
|
|
147
|
+
"field.runtimeContext": "Runtime snapshot",
|
|
148
|
+
"field.defaultModel": "Default model",
|
|
149
|
+
"field.modelRoute": "Which model",
|
|
150
|
+
"field.reasoningEffort": "Reasoning effort",
|
|
151
|
+
"field.reset": "Reset",
|
|
152
|
+
"hint.name": "Shown in the picker and in lists.",
|
|
153
|
+
"hint.description": "One line saying what this mode is for.",
|
|
154
|
+
"hint.role":
|
|
155
|
+
"Selectable in sessions = the user can pick it; usable for subagents = subagents may run in it.",
|
|
156
|
+
"hint.presetsOnly":
|
|
157
|
+
"Only these presets may back this mode (the one it rides on supplies the row list); empty = every preset is allowed (and selecting the mode switches nothing). When set, selecting the mode switches to the first entry if the session's current preset is not listed.",
|
|
158
|
+
"hint.prefix": "Text prepended to the system prompt.",
|
|
159
|
+
"hint.suffix": "Text appended to the system prompt.",
|
|
160
|
+
"hint.allowTools":
|
|
161
|
+
"Only these tools; empty = no restriction (every tool of the session's preset).",
|
|
162
|
+
"hint.denyTools": "Those tools are removed from the list above.",
|
|
163
|
+
"hint.allowSkills": "Only these skills; empty = no restriction.",
|
|
164
|
+
"hint.denySkills": "Those skills leave the catalog, and the `skill` tool cannot load them.",
|
|
165
|
+
"hint.allowPolicies": "Only these upstream rules stay in force; empty = all of them.",
|
|
166
|
+
"hint.denyPolicies": "Those upstream rules are disabled, rejection included.",
|
|
167
|
+
"hint.instructions":
|
|
168
|
+
"Sends the workspace instructions (AGENTS.md and friends) with every step; off means it is not sent.",
|
|
169
|
+
"hint.skills":
|
|
170
|
+
"Sends the skill catalog with every step; default decides from this mode's tool lists.",
|
|
171
|
+
"hint.runtimeContext": "Sends the sandbox and approval policies with every step.",
|
|
172
|
+
"hint.defaultModel":
|
|
173
|
+
"On = this mode carries a default model (used while a session has picked none); off = follow the global model.",
|
|
174
|
+
"hint.modelRoute":
|
|
175
|
+
"Pick one model already set up in this deployment: a provider and a model come as one route, so choosing it sets both.",
|
|
176
|
+
"hint.reasoningEffort": "Reasoning effort; empty uses the provider default.",
|
|
177
|
+
"model.loading": "Reading the models set up in this deployment…",
|
|
178
|
+
"model.error": "Could not read the models set up in this deployment.",
|
|
179
|
+
"model.empty": "No model is set up in this deployment yet.",
|
|
180
|
+
"tags.placeholder": "Add one and press Enter, or paste a list",
|
|
181
|
+
"tags.add": "Add",
|
|
182
|
+
"tags.remove": "Remove {name}",
|
|
183
|
+
"select.search": "Search candidates",
|
|
184
|
+
"select.noMatch": "No matching candidate",
|
|
185
|
+
"tri.unset": "Default",
|
|
186
|
+
"tri.on": "On",
|
|
187
|
+
"tri.off": "Off",
|
|
188
|
+
"remove.title": "Delete mode",
|
|
189
|
+
"remove.description":
|
|
190
|
+
"Delete {name}? The deletion applies on save; until then you can discard it.",
|
|
191
|
+
"remove.confirm": "Delete",
|
|
192
|
+
"remove.cancel": "Cancel",
|
|
193
|
+
"remove.close": "Close",
|
|
194
|
+
"model.set": "Set a default model",
|
|
195
|
+
"model.follow": "Follow the global model",
|
|
196
|
+
save: "Save",
|
|
197
|
+
saving: "Saving…",
|
|
198
|
+
saveFailed: "The save did not take effect; try again.",
|
|
199
|
+
readOnly: "This deployment stores settings read-only.",
|
|
200
|
+
unavailable: "This row is not writable right now.",
|
|
201
|
+
notSaved: "Not saved: {message}",
|
|
202
|
+
"configured.loading": "Reading this row's configuration…",
|
|
203
|
+
"configured.missing":
|
|
204
|
+
"The settings surface has no `session-mode` row: not installed, disabled, or not composed yet (it stays PENDING while a dependency is missing).",
|
|
205
|
+
"configured.unreadable":
|
|
206
|
+
"The `session-mode` row is running, but its configuration cannot be read ({problem}).",
|
|
207
|
+
"violation.modesEmpty": "At least one mode is required.",
|
|
208
|
+
"violation.defaultMissing": "The default mode is not in the mode list.",
|
|
209
|
+
"violation.modelPair": "A default model needs both a provider and a model.",
|
|
210
|
+
"summary.allTools": "every tool",
|
|
211
|
+
"summary.tools": "{n} tools",
|
|
212
|
+
"summary.denyTools": "{n} denied",
|
|
213
|
+
"summary.globalModel": "global model",
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
export type BundleLocaleKey = keyof typeof bundleZh;
|
package/src/client/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
// client 半:会话里那一个面(模式 chip,槽位 `conversation.input.left`,list + session scope)+
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// client 半:会话里那一个面(模式 chip,槽位 `conversation.input.left`,list + session scope)+ 配置面两处——
|
|
2
|
+
// 本行自己那页(由 `@morlay/dsh-client-ui-primitives` 按 schema 自动生成,key = `<bundle 包名>#<行 id>`)与
|
|
3
|
+
// 装本行的 bundle 那一页(`plugins.bundle.config`,key = bundle 包名,见 `./BundleConfigPage.tsx`)。
|
|
4
4
|
|
|
5
5
|
import type { Context } from "@deepseek-ai/cordis";
|
|
6
6
|
// Type-only:`ctx.remote` 的合并面(选模型的候选来自 LLM 目录)。
|
|
@@ -9,15 +9,34 @@ import type {} from "@deepseek-ai/dsh-client-locale/client";
|
|
|
9
9
|
import type {} from "@deepseek-ai/dsh-client-ui-slots";
|
|
10
10
|
// Type-only:槽位声明与 standard props(session / session-maybe / global)。
|
|
11
11
|
import type {} from "@deepseek-ai/dsh-client-ui-conversation/client";
|
|
12
|
-
|
|
12
|
+
// Type-only:`plugins.bundle.config` 的槽位声明(bundle 详情页的配置座位)。
|
|
13
|
+
import type {} from "@deepseek-ai/dsh-client-ui-plugin-manager/client";
|
|
14
|
+
import {
|
|
15
|
+
CATALOG_NS,
|
|
16
|
+
SESSION_MODE_CATALOG_REMOTE,
|
|
17
|
+
type SessionModeCatalogRemote,
|
|
18
|
+
} from "../catalog-remote.ts";
|
|
13
19
|
import { POLICY_NAMES } from "../shared.ts";
|
|
20
|
+
import {
|
|
21
|
+
BUNDLE_CONFIG_KEY,
|
|
22
|
+
createBundleConfigFace,
|
|
23
|
+
SESSION_MODE_NS,
|
|
24
|
+
unwrapService,
|
|
25
|
+
type HintsLike,
|
|
26
|
+
type HintsService,
|
|
27
|
+
} from "./bundle-config.ts";
|
|
28
|
+
import { BundleConfigPage } from "./BundleConfigPage.tsx";
|
|
29
|
+
import { BUNDLE_NS, bundleEn, bundleZh, type BundleLocaleKey } from "./bundle-locales.ts";
|
|
30
|
+
import { loadLlmProviders, remoteNamespace, type ProviderModels } from "./llm-directory.ts";
|
|
14
31
|
import { SessionModeSeat } from "./SessionModeSeat.tsx";
|
|
15
32
|
import { en, zh, type SessionModeLocaleKey } from "./locales.ts";
|
|
16
33
|
|
|
17
34
|
declare module "@deepseek-ai/dsh-client-ui-slots" {
|
|
18
35
|
interface LocaleNamespaceMap {
|
|
19
|
-
//
|
|
36
|
+
// 会话里两个面的文案(本行配置页的文案在通用 schema 表单的字典里)。
|
|
20
37
|
"session-mode": SessionModeLocaleKey;
|
|
38
|
+
// bundle 配置页(`plugins.bundle.config`)的文案。
|
|
39
|
+
"session-mode-bundle": BundleLocaleKey;
|
|
21
40
|
}
|
|
22
41
|
}
|
|
23
42
|
|
|
@@ -35,36 +54,33 @@ const POLICY_FIELDS = ["allowPolicies", "denyPolicies"] as const;
|
|
|
35
54
|
// 动态键的占位段(与通用表单的字段树同一约定)。
|
|
36
55
|
const DYNAMIC = "*";
|
|
37
56
|
|
|
38
|
-
//
|
|
39
|
-
interface
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
settingsPath: readonly string[];
|
|
57
|
+
// 部署里的 agent preset 清单(`remote.agentPresets.list()`):本包只用它的 id 与显示名做候选。
|
|
58
|
+
interface PresetRosterReader {
|
|
59
|
+
list: () => Promise<
|
|
60
|
+
{ ok: true; value: { presets: readonly { id: string; name?: string }[] } } | { ok: false }
|
|
61
|
+
>;
|
|
44
62
|
}
|
|
45
63
|
|
|
46
64
|
export type { SessionModeSeatProps } from "./SessionModeSeat.tsx";
|
|
47
65
|
export type { SessionModeLocaleKey } from "./locales.ts";
|
|
66
|
+
export type { BundleConfigPageProps } from "./BundleConfigPage.tsx";
|
|
48
67
|
|
|
49
|
-
// 需要的服务:槽位与字典(会话列表经槽位的标准 props 到达组件,不必自己 inject
|
|
68
|
+
// 需要的服务:槽位与字典(会话列表经槽位的标准 props 到达组件,不必自己 inject);bundle 那页另要配置表单与
|
|
69
|
+
// schema 服务,见下面那段注册自己的 `ctx.inject`。
|
|
50
70
|
export const inject = ["slots", "locale"];
|
|
51
71
|
|
|
52
|
-
// 本包 host 行 id
|
|
53
|
-
export
|
|
72
|
+
// 本包 host 行 id:行配置页与 bundle 配置页读的都是这个命名空间。
|
|
73
|
+
export { SESSION_MODE_NS };
|
|
54
74
|
|
|
55
|
-
//
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
if (typeof node !== "object" || node === null) return undefined;
|
|
60
|
-
return Reflect.get(node, segment);
|
|
61
|
-
}, root);
|
|
75
|
+
// 从 inject 的 scope 上取提示面服务并解包:cordis 把它包成追踪代理,而它的实现用的是 JS 私有字段,
|
|
76
|
+
// 代理上的方法调用会以代理为 `this` 而抛(`Cannot read private member #texts …`)。
|
|
77
|
+
function unwrapHintService(scope: Context): HintsService {
|
|
78
|
+
return unwrapService(scope.schemaFormHints as unknown as HintsService) as HintsService;
|
|
62
79
|
}
|
|
63
80
|
|
|
64
|
-
//
|
|
81
|
+
// 装上会话里的那一个面、本行配置页的字段文案,以及装本行的 bundle 那一页的表单。
|
|
65
82
|
export function apply(ctx: Context): void {
|
|
66
83
|
// 基础面随本包 inline(不再是装配行):装上它提供的字典、字段槽与按行配置页;多份副本只装一次。
|
|
67
|
-
installUiPrimitives(ctx);
|
|
68
84
|
const t = ctx.locale.bind(NS);
|
|
69
85
|
ctx.effect(() => ctx.locale.register(NS, { zh, en }), "session-mode: dictionaries");
|
|
70
86
|
|
|
@@ -72,7 +88,8 @@ export function apply(ctx: Context): void {
|
|
|
72
88
|
// 中文标签与说明。
|
|
73
89
|
ctx.inject(["schemaFormHints"], (scope) =>
|
|
74
90
|
scope.effect(() => {
|
|
75
|
-
|
|
91
|
+
// 提示面服务走 cordis 的追踪代理,而它的实现用 JS 私有字段:先解包成原实例再调(见 `./bundle-config.ts`)。
|
|
92
|
+
const hints = unwrapHintService(scope);
|
|
76
93
|
const offs = FIELDS.map((key) =>
|
|
77
94
|
hints.describe(SESSION_MODE_NS, ["modes", DYNAMIC, "defaultModel", key], () => ({
|
|
78
95
|
label: t(key),
|
|
@@ -91,7 +108,7 @@ export function apply(ctx: Context): void {
|
|
|
91
108
|
ctx.inject(["schemaFormHints"], (scope) =>
|
|
92
109
|
scope.effect(() => {
|
|
93
110
|
const offs = POLICY_FIELDS.map((key) =>
|
|
94
|
-
scope.
|
|
111
|
+
unwrapHintService(scope).select(SESSION_MODE_NS, ["modes", DYNAMIC, key, DYNAMIC], {
|
|
95
112
|
options: () => POLICY_NAMES.map((value) => ({ value })),
|
|
96
113
|
}),
|
|
97
114
|
);
|
|
@@ -101,59 +118,18 @@ export function apply(ctx: Context): void {
|
|
|
101
118
|
}, "session-mode: policy candidates"),
|
|
102
119
|
);
|
|
103
120
|
|
|
104
|
-
// 选模型的候选不在本行的 schema
|
|
105
|
-
//
|
|
121
|
+
// 选模型的候选不在本行的 schema 里:读的是部署里的 LLM 目录(活着的路由 + 可配置声明,模型清单在声明指向的
|
|
122
|
+
// 配置里,见 `./llm-directory.ts`)。目录异步取到后注册——注册本身就是一次变更通知。
|
|
106
123
|
ctx.inject(["schemaFormHints"], (scope) =>
|
|
107
124
|
scope.effect(() => {
|
|
108
|
-
const hints = scope
|
|
125
|
+
const hints = unwrapHintService(scope);
|
|
109
126
|
const remote = scope.get("remote");
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
let providers: ProviderEntry[] = [];
|
|
127
|
+
if (remote === undefined) return () => {};
|
|
128
|
+
let providers: readonly ProviderModels[] = [];
|
|
113
129
|
const load = async (): Promise<void> => {
|
|
114
|
-
|
|
115
|
-
remote.llm.listProviders(),
|
|
116
|
-
remote.llm.listConfigurableProviders(),
|
|
117
|
-
]);
|
|
118
|
-
if (!routes.ok || !directory.ok) return;
|
|
119
|
-
const merged = new Map<string, ProviderEntry>();
|
|
120
|
-
for (const entry of directory.value) {
|
|
121
|
-
merged.set(entry.provider, {
|
|
122
|
-
value: entry.provider,
|
|
123
|
-
label: entry.displayName,
|
|
124
|
-
settingsNs: entry.settingsNs,
|
|
125
|
-
settingsPath: [...entry.settingsPath],
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
for (const route of routes.value) {
|
|
129
|
-
if (!merged.has(route.id)) {
|
|
130
|
-
merged.set(route.id, {
|
|
131
|
-
value: route.id,
|
|
132
|
-
label: route.name,
|
|
133
|
-
settingsNs: "",
|
|
134
|
-
settingsPath: [],
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
providers = [...merged.values()];
|
|
130
|
+
providers = await loadLlmProviders(scope);
|
|
139
131
|
hints.refresh();
|
|
140
132
|
};
|
|
141
|
-
const modelsOf = (provider: unknown): readonly { value: string }[] => {
|
|
142
|
-
if (typeof provider !== "string") return [];
|
|
143
|
-
const entry = providers.find((candidate) => candidate.value === provider);
|
|
144
|
-
if (entry === undefined || entry.settingsNs === "") return [];
|
|
145
|
-
const profile = readAt(forms.get(entry.settingsNs).getSnapshot().value, entry.settingsPath);
|
|
146
|
-
const models =
|
|
147
|
-
typeof profile === "object" && profile !== null
|
|
148
|
-
? Reflect.get(profile, "models")
|
|
149
|
-
: undefined;
|
|
150
|
-
if (!Array.isArray(models)) return [];
|
|
151
|
-
return models.flatMap((model) => {
|
|
152
|
-
const id =
|
|
153
|
-
typeof model === "object" && model !== null ? Reflect.get(model, "id") : undefined;
|
|
154
|
-
return typeof id === "string" ? [{ value: id }] : [];
|
|
155
|
-
});
|
|
156
|
-
};
|
|
157
133
|
const offs = [
|
|
158
134
|
// 两个具名源:schema 上 `role('select', { source })` 认领它们,本包因此不必知道行 id 与字段路径。
|
|
159
135
|
hints.source("llm-providers", {
|
|
@@ -162,13 +138,18 @@ export function apply(ctx: Context): void {
|
|
|
162
138
|
// 换服务商就换模型清单:依赖声明让表单在投影时按当前 provider 重算候选。
|
|
163
139
|
hints.source("llm-models", {
|
|
164
140
|
dependsOn: [["provider"]],
|
|
165
|
-
options: (read) =>
|
|
141
|
+
options: (read) => {
|
|
142
|
+
const provider = read(["provider"]);
|
|
143
|
+
return typeof provider === "string"
|
|
144
|
+
? (providers.find((entry) => entry.value === provider)?.models ?? [])
|
|
145
|
+
: [];
|
|
146
|
+
},
|
|
166
147
|
}),
|
|
167
148
|
remote.$on("llm/adapters-updated", () => {
|
|
168
149
|
void load();
|
|
169
150
|
}),
|
|
170
151
|
remote.$on("settings/document-updated", () => {
|
|
171
|
-
|
|
152
|
+
void load();
|
|
172
153
|
}),
|
|
173
154
|
];
|
|
174
155
|
void load();
|
|
@@ -178,6 +159,81 @@ export function apply(ctx: Context): void {
|
|
|
178
159
|
}, "session-mode: model candidates"),
|
|
179
160
|
);
|
|
180
161
|
|
|
162
|
+
// 模式"允许挂哪些 preset"的候选:部署里注册的 agent preset(`remote.agentPresets.list()`),登记成具名源
|
|
163
|
+
// `agent-presets`——schema 上 `presetsOnly` 声明 `role('select', { source })` 认领它,页面不必知道这些 preset
|
|
164
|
+
// 从哪来。装配组合变了(设置文档一次更新)就重取一次。
|
|
165
|
+
ctx.inject(["schemaFormHints"], (scope) =>
|
|
166
|
+
scope.effect(() => {
|
|
167
|
+
const hints = unwrapHintService(scope);
|
|
168
|
+
const remote = scope.get("remote");
|
|
169
|
+
if (remote === undefined) return () => {};
|
|
170
|
+
const roster = remoteNamespace<PresetRosterReader>(scope, "agentPresets");
|
|
171
|
+
if (roster === undefined) return () => {};
|
|
172
|
+
let presets: readonly { value: string; label: string }[] = [];
|
|
173
|
+
const load = async (): Promise<void> => {
|
|
174
|
+
const listed = await roster.list();
|
|
175
|
+
if (!listed.ok) return;
|
|
176
|
+
presets = listed.value.presets.map((preset) => ({
|
|
177
|
+
value: preset.id,
|
|
178
|
+
label: preset.name ?? preset.id,
|
|
179
|
+
}));
|
|
180
|
+
// 候选变了:注册本身就是一次变更通知(页面据此重投影)。
|
|
181
|
+
hints.refresh();
|
|
182
|
+
};
|
|
183
|
+
const offs = [
|
|
184
|
+
hints.source("agent-presets", { options: () => presets }),
|
|
185
|
+
remote.$on("settings/document-updated", () => {
|
|
186
|
+
void load();
|
|
187
|
+
}),
|
|
188
|
+
];
|
|
189
|
+
void load();
|
|
190
|
+
return () => {
|
|
191
|
+
for (const off of offs) off();
|
|
192
|
+
};
|
|
193
|
+
}, "session-mode: preset candidates"),
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
// 配置页的名单候选(工具名 / 技能名 / 上游 policy 名):前两个来自本包自己开的那条 Remote 面
|
|
197
|
+
// (`../catalog-remote.ts`——工具清单只在 host,技能面按会话,配置页都没有别的路可走),mount 由这里做;
|
|
198
|
+
// policy 名是一份封闭名单(`shared.ts`)。三个都是**具名源**,schema 上名单字段声明 `role('select', { source })`
|
|
199
|
+
// 认领——标签输入因此聚焦就有候选,而不是只能手输。
|
|
200
|
+
ctx.inject(["schemaFormHints", "remote"], (scope) =>
|
|
201
|
+
scope.effect(() => {
|
|
202
|
+
const hints = unwrapHintService(scope);
|
|
203
|
+
let tools: readonly string[] = [];
|
|
204
|
+
let skills: readonly string[] = [];
|
|
205
|
+
const offs = [
|
|
206
|
+
hints.source("catalog-tools", { options: () => tools.map((value) => ({ value })) }),
|
|
207
|
+
hints.source("catalog-skills", { options: () => skills.map((value) => ({ value })) }),
|
|
208
|
+
hints.source("policies", { options: () => POLICY_NAMES.map((value) => ({ value })) }),
|
|
209
|
+
];
|
|
210
|
+
let disposeMount: (() => Promise<void>) | undefined;
|
|
211
|
+
// `$mount` 不是每个环境都有(测试替身、极简装配):没有就只留 policy 那份候选。
|
|
212
|
+
if (typeof scope.remote.$mount !== "function") {
|
|
213
|
+
return () => {
|
|
214
|
+
for (const off of offs) off();
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
void scope.remote
|
|
218
|
+
.$mount(SESSION_MODE_CATALOG_REMOTE)
|
|
219
|
+
.then(async (dispose: () => Promise<void>) => {
|
|
220
|
+
disposeMount = dispose;
|
|
221
|
+
const listed = await remoteNamespace<SessionModeCatalogRemote>(scope, CATALOG_NS)?.list();
|
|
222
|
+
if (listed?.ok !== true) return;
|
|
223
|
+
tools = listed.value.tools;
|
|
224
|
+
skills = listed.value.skills;
|
|
225
|
+
hints.refresh();
|
|
226
|
+
})
|
|
227
|
+
.catch(() => {
|
|
228
|
+
// 这条面 mount 不上(旧 host)时不给候选:字段退回自由输入,不炸页面。
|
|
229
|
+
});
|
|
230
|
+
return () => {
|
|
231
|
+
for (const off of offs) off();
|
|
232
|
+
void disposeMount?.();
|
|
233
|
+
};
|
|
234
|
+
}, "session-mode: catalog candidates"),
|
|
235
|
+
);
|
|
236
|
+
|
|
181
237
|
// chip 挂 **composer 工具行左侧**(`conversation.input.left`,list + session scope):新会话屏也是一个
|
|
182
238
|
// blank session 的 composer,所以那里也能选模式——头部槽位在新会话屏不存在。
|
|
183
239
|
ctx.inject(["slots", "conversation"], (scope) => {
|
|
@@ -196,4 +252,52 @@ export function apply(ctx: Context): void {
|
|
|
196
252
|
};
|
|
197
253
|
}, "session-mode: composer chip");
|
|
198
254
|
});
|
|
255
|
+
|
|
256
|
+
// 装本行的 bundle 那一页(`plugins.bundle.config`,key = bundle 包名):本行是那份配置的 owner,页面摆的就是
|
|
257
|
+
// `modes` 与 `default` 这几项——按模式折叠分组而不是按 schema 平铺,所以它自带表单,不用自动生成那页。
|
|
258
|
+
// 提示面的候选(选服务商 / 选模型的两个具名源)可能比本页晚到,所以这一页自己重投影的入口留在外面:
|
|
259
|
+
// 提示面一到(或它的候选变了)就刷一次,模型那两个字段不会静默退回手输。
|
|
260
|
+
let refreshPage: (() => void) | undefined;
|
|
261
|
+
// 提示面按**属性访问**取(`ctx.get(name)` 给的是追踪代理,调带 JS 私有字段的方法会抛),存下来给下面那一页用。
|
|
262
|
+
let hints: HintsLike | undefined;
|
|
263
|
+
ctx.inject(["schemaFormHints"], (scope) =>
|
|
264
|
+
scope.effect(() => {
|
|
265
|
+
// 订阅也要用解包后的实例(服务实现用 JS 私有字段,代理上的调用会抛)。
|
|
266
|
+
const service = unwrapHintService(scope);
|
|
267
|
+
hints = service;
|
|
268
|
+
const off = service.subscribe(() => {
|
|
269
|
+
refreshPage?.();
|
|
270
|
+
});
|
|
271
|
+
return () => {
|
|
272
|
+
off();
|
|
273
|
+
hints = undefined;
|
|
274
|
+
};
|
|
275
|
+
}, "session-mode: bundle page candidates"),
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
ctx.inject(["slots", "locale", "configForms", "settingsSchema"], (scope) => {
|
|
279
|
+
scope.effect(() => {
|
|
280
|
+
const bundleT = scope.locale.bind(BUNDLE_NS);
|
|
281
|
+
const { face, refresh, dispose } = createBundleConfigFace(scope, bundleT, () => hints);
|
|
282
|
+
refreshPage = refresh;
|
|
283
|
+
const bundleDictionary = scope.locale.register(BUNDLE_NS, { zh: bundleZh, en: bundleEn });
|
|
284
|
+
const off = scope.slots.inject("plugins.bundle.config", () =>
|
|
285
|
+
scope.slots.register(
|
|
286
|
+
{
|
|
287
|
+
name: "plugins.bundle.config",
|
|
288
|
+
key: BUNDLE_CONFIG_KEY,
|
|
289
|
+
locale: BUNDLE_NS,
|
|
290
|
+
inject: () => face,
|
|
291
|
+
},
|
|
292
|
+
BundleConfigPage,
|
|
293
|
+
),
|
|
294
|
+
);
|
|
295
|
+
return () => {
|
|
296
|
+
off();
|
|
297
|
+
bundleDictionary();
|
|
298
|
+
refreshPage = undefined;
|
|
299
|
+
dispose();
|
|
300
|
+
};
|
|
301
|
+
}, "session-mode: bundle configuration page");
|
|
302
|
+
});
|
|
199
303
|
}
|