@khorsheed/dsh-local-agent-dsh 0.2.0 → 0.2.2
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/CHANGELOG.md +10 -0
- package/README.en.md +5 -5
- package/README.i18n.yaml +2 -2
- package/README.md +5 -5
- package/lib/client.js +139 -97
- package/lib/index.js +4 -48
- package/lib/tool.js +40 -0
- package/lib/types/client/SettingsCard.js +7 -3
- package/lib/types/client/index.js +2 -2
- package/lib/types/client/locales.d.ts +0 -10
- package/lib/types/client/locales.js +0 -10
- package/lib/types/index.js +10 -38
- package/lib/types/tool.d.ts +52 -0
- package/lib/types/tool.js +34 -0
- package/package.json +9 -4
- package/lib/dsh-cli-provider-B7s3zH0m.js +0 -1782
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# 变更记录
|
|
2
2
|
|
|
3
|
+
## 0.2.2(2026-10-27)
|
|
4
|
+
|
|
5
|
+
- 跟随本地 Agent 家族统一版本线发布,本包没有功能改动。
|
|
6
|
+
|
|
7
|
+
## 0.2.1(2026-10-27)
|
|
8
|
+
|
|
9
|
+
- **不用再打开开关**:「DeepSeek 委派」的启用开关去掉了,装上就能用;委派工具 `subagent_dsh` 随模式授予,只在开发、游戏开发、剪辑模式的会话里出现,不再出现在每个会话里。
|
|
10
|
+
- **常驻模式(live)默认打开**:遇到问题可以在卡片上关掉,改回每次新起一个。
|
|
11
|
+
- **设置存在自己的目录里**:默认模型等设置改存在子 DSH 自己的目录里,几个模式套件一起装时也能保存;改不成时显示「保存失败」,不再假装「已保存」。
|
|
12
|
+
|
|
3
13
|
## 0.2.0(2026-10-26)
|
|
4
14
|
|
|
5
15
|
- **版本号去掉 rc**:社区包不再发 prerelease 版本(docs/publishing.md「版本号规则」),家族各包同版 0.2.0;本节原记为 0.2.0-rc.4。
|
package/README.en.md
CHANGED
|
@@ -4,19 +4,19 @@ English | [中文](README.md)
|
|
|
4
4
|
|
|
5
5
|
Hand a subtask to a second, separately running DSH on your machine: it has its own sessions and settings, the result comes back to your session, and you can keep the conversation going.
|
|
6
6
|
|
|
7
|
-
Some subtasks deserve a clean DSH: none of the current session's context, its own session history, and no way to disturb the instance you're using. With this plugin,
|
|
7
|
+
Some subtasks deserve a clean DSH: none of the current session's context, its own session history, and no way to disturb the instance you're using. With this plugin, in sessions of the Dev, Game Dev and Video modes the agent can hand such subtasks to a separate DSH started on your machine. Its output shows up live in a sub-session, the result comes back to you, and you can keep working in the same sub-session afterwards.
|
|
8
8
|
|
|
9
|
-
<img src="https://raw.githubusercontent.com/Khorsheed/dsh-basic/main/docs/screenshots/local-agent-dsh.png" width="560" alt="The
|
|
9
|
+
<img src="https://raw.githubusercontent.com/Khorsheed/dsh-basic/main/docs/screenshots/local-agent-dsh.png" width="560" alt="The “Local Agent Delegation” card with the “Local DSH Delegation” section open: signed in (0.2.1-rc.1), credentials taken from the host instance with no separate login, DeepSeek-V4.1-Flash as the default model, and resident mode (live) on">
|
|
10
10
|
|
|
11
11
|
## What it does
|
|
12
12
|
|
|
13
|
-
- **
|
|
13
|
+
- **Works once installed**: no switch to turn on; the delegation tool follows the mode — sessions of the Dev, Game Dev and Video modes get it, every other session is unchanged.
|
|
14
14
|
- **No separate sign-in**: it uses the model and key you picked in your current DSH, and the card shows whether that credential works.
|
|
15
15
|
- **Kept apart**: the second DSH's sessions, settings, and state live in their own folder; they never show up in your session list or change your current instance.
|
|
16
16
|
- **See the work as it happens**: its output appears in the sub-session as it runs.
|
|
17
17
|
- **Keep talking**: have the agent follow up, or open the sub-session and message it yourself; it's the same session on the second DSH.
|
|
18
18
|
- **Choose the model**: pick one under "Default model" on the card and every hand-off uses it; leave it blank to follow your current DSH's default.
|
|
19
|
-
- **Resident mode is faster**:
|
|
19
|
+
- **Resident mode is faster**: on by default — the second DSH stays up and stopping doesn't kill it; if it misbehaves, turn off "Resident mode (live)" on the card to start a fresh one each time.
|
|
20
20
|
|
|
21
21
|
## Install
|
|
22
22
|
|
|
@@ -26,7 +26,7 @@ In DSH, open Plugins → Add plugin, enter this package name, and press Install:
|
|
|
26
26
|
@khorsheed/dsh-bundle-local-agent
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
This plugin comes with the 「多Agent协作」 (multi-agent) suite; all the local agents share one core, so on its own it won't work. The Dev Suite `@khorsheed/dsh-dev-suite` includes it too. Restart DSH,
|
|
29
|
+
This plugin comes with the 「多Agent协作」 (multi-agent) suite; all the local agents share one core, so on its own it won't work. The Dev Suite `@khorsheed/dsh-dev-suite` includes it too. Restart DSH, then in a Dev (or Game Dev, Video) mode session tell the agent "hand this subtask to the local DSH".
|
|
30
30
|
|
|
31
31
|
## Known limitations
|
|
32
32
|
|
package/README.i18n.yaml
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
# last confirmed-consistent state. Both languages carry equal authority; after
|
|
3
3
|
# editing either side, bring the other along and re-record with:
|
|
4
4
|
# pnpm run verify-translation-pairing --write packages/local-agent-dsh/README.en.md
|
|
5
|
-
packages/local-agent-dsh/README.en.md:
|
|
6
|
-
packages/local-agent-dsh/README.md:
|
|
5
|
+
packages/local-agent-dsh/README.en.md: ae3dd795c6fe850b185d05dc2857f6c91d1aeaac
|
|
6
|
+
packages/local-agent-dsh/README.md: 91968cfebfd1e6a70454e5fdb09bb87a0881d38f
|
package/README.md
CHANGED
|
@@ -4,19 +4,19 @@
|
|
|
4
4
|
|
|
5
5
|
把子任务交给你电脑上另一个单独运行的 DSH:它有自己的会话和设置,跑完结果回到当前会话,还能接着聊。
|
|
6
6
|
|
|
7
|
-
有些子任务你想让一个干净的 DSH
|
|
7
|
+
有些子任务你想让一个干净的 DSH 来做:不带当前会话的上下文,有自己的会话记录,也不会动到你正在用的这个实例。装上之后,在开发、游戏开发、剪辑这几个模式的会话里,agent 就能把这类子任务交给本机另起的一个 DSH;它的输出实时出现在子会话里,跑完把结果带回来,之后还能在同一个子会话里接着干。
|
|
8
8
|
|
|
9
|
-
<img src="https://raw.githubusercontent.com/Khorsheed/dsh-basic/main/docs/screenshots/local-agent-dsh.png" width="560" alt="
|
|
9
|
+
<img src="https://raw.githubusercontent.com/Khorsheed/dsh-basic/main/docs/screenshots/local-agent-dsh.png" width="560" alt="「本机 Agent 委派」卡片里展开的「本机 DSH 委派」一节:已登录(0.2.1-rc.1),凭据走宿主实例、无需单独登录,默认模型选的是 DeepSeek-V4.1-Flash,常驻模式(live)开着">
|
|
10
10
|
|
|
11
11
|
## 能做什么
|
|
12
12
|
|
|
13
|
-
-
|
|
13
|
+
- **装上就能用**:不用再打开什么开关;委派工具跟着模式走,开发、游戏开发、剪辑模式的会话里才有,其他会话一切照旧。
|
|
14
14
|
- **不用另外登录**:它沿用你在当前 DSH 里选的模型和 key,卡片上直接显示凭据能不能用。
|
|
15
15
|
- **互不打扰**:子 DSH 的会话、设置和状态都放在单独的目录里,不会出现在你的会话列表里,也不会改动当前实例。
|
|
16
16
|
- **过程看得见**:子 DSH 的输出边跑边出现在子会话里。
|
|
17
17
|
- **接着聊**:让 agent 追问,或者打开子会话直接发消息,继续的是同一个子 DSH 会话。
|
|
18
18
|
- **指定模型**:卡片的「默认模型」里选一个模型,之后每次委派都用它;留空就跟随当前 DSH 的默认模型。
|
|
19
|
-
-
|
|
19
|
+
- **常驻模式更快**:默认开着,子 DSH 常驻,停下不杀进程;遇到问题可以在卡片上关掉「常驻模式(live)」,改回每次新起一个。
|
|
20
20
|
|
|
21
21
|
## 安装
|
|
22
22
|
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
@khorsheed/dsh-bundle-local-agent
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
本插件会跟着「多Agent协作」套件一起装好(各家本机 agent 共用同一个底座,单装用不起来);开发套件 `@khorsheed/dsh-dev-suite` 也包含它。装完重启 DSH
|
|
29
|
+
本插件会跟着「多Agent协作」套件一起装好(各家本机 agent 共用同一个底座,单装用不起来);开发套件 `@khorsheed/dsh-dev-suite` 也包含它。装完重启 DSH,在开发(或游戏开发、剪辑)模式的会话里对 agent 说「把这个子任务交给本机的 DSH」。
|
|
30
30
|
|
|
31
31
|
## 已知限制
|
|
32
32
|
|
package/lib/client.js
CHANGED
|
@@ -93,6 +93,83 @@ window.__ModuleLoader__.load({
|
|
|
93
93
|
});
|
|
94
94
|
return channel;
|
|
95
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* Bind a settings card to the harness's own card-settings store (card-settings.ts
|
|
98
|
+
* on the host) layered over the row's host settings form: reads are the
|
|
99
|
+
* form's value with the stored overrides on top; writes go to the store only,
|
|
100
|
+
* never the profile patch — so they cannot be refused when several suites
|
|
101
|
+
* insert the same row. A host without the store keeps the plain row scope.
|
|
102
|
+
* @param ctx - the provider's client context.
|
|
103
|
+
* @param harness - the harness name (`dsh`, `kimi`, …).
|
|
104
|
+
* @param entryId - the plugin row id the base values come from.
|
|
105
|
+
* @returns the bound scope.
|
|
106
|
+
*/
|
|
107
|
+
function bindCardSettingsScope(ctx, harness, entryId) {
|
|
108
|
+
const row = bindSettingsScope(ctx, entryId);
|
|
109
|
+
const gateway = () => {
|
|
110
|
+
const remote = ctx.get("remote.localAgentGateway");
|
|
111
|
+
return typeof remote?.cardSettings === "function" && typeof remote.setCardSettings === "function" ? remote : void 0;
|
|
112
|
+
};
|
|
113
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
114
|
+
let stored;
|
|
115
|
+
let cached;
|
|
116
|
+
const emit = () => {
|
|
117
|
+
for (const listener of [...listeners]) listener();
|
|
118
|
+
};
|
|
119
|
+
const load = () => {
|
|
120
|
+
gateway()?.cardSettings(harness).then((result) => {
|
|
121
|
+
if (!result.ok || result.value === void 0) return;
|
|
122
|
+
stored = result.value;
|
|
123
|
+
emit();
|
|
124
|
+
}).catch(() => {});
|
|
125
|
+
};
|
|
126
|
+
const write = async (patch) => {
|
|
127
|
+
const remote = gateway();
|
|
128
|
+
if (remote === void 0) return false;
|
|
129
|
+
try {
|
|
130
|
+
const result = await remote.setCardSettings(harness, patch);
|
|
131
|
+
if (!result.ok || result.value === void 0) return false;
|
|
132
|
+
stored = result.value;
|
|
133
|
+
emit();
|
|
134
|
+
return true;
|
|
135
|
+
} catch {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
return {
|
|
140
|
+
getSnapshot: () => {
|
|
141
|
+
const rowSnapshot = row.getSnapshot();
|
|
142
|
+
if (cached !== void 0 && cached.rowSnapshot === rowSnapshot && cached.stored === stored) return cached.snapshot;
|
|
143
|
+
const overrides = stored ?? {};
|
|
144
|
+
const snapshot = gateway() === void 0 ? rowSnapshot : {
|
|
145
|
+
...rowSnapshot,
|
|
146
|
+
value: rowSnapshot.value === void 0 && stored === void 0 ? void 0 : {
|
|
147
|
+
...rowSnapshot.value,
|
|
148
|
+
...overrides
|
|
149
|
+
},
|
|
150
|
+
writable: true,
|
|
151
|
+
status: rowSnapshot.status === "loading" && stored !== void 0 ? "ready" : rowSnapshot.status === "unavailable" && stored !== void 0 ? "ready" : rowSnapshot.status
|
|
152
|
+
};
|
|
153
|
+
cached = {
|
|
154
|
+
rowSnapshot,
|
|
155
|
+
stored,
|
|
156
|
+
snapshot
|
|
157
|
+
};
|
|
158
|
+
return snapshot;
|
|
159
|
+
},
|
|
160
|
+
subscribe: (listener) => {
|
|
161
|
+
if (listeners.size === 0) load();
|
|
162
|
+
listeners.add(listener);
|
|
163
|
+
const off = row.subscribe(listener);
|
|
164
|
+
return () => {
|
|
165
|
+
listeners.delete(listener);
|
|
166
|
+
off();
|
|
167
|
+
};
|
|
168
|
+
},
|
|
169
|
+
set: (field, value) => gateway() === void 0 ? row.set(field, value) : write({ [field]: value }),
|
|
170
|
+
unset: (field) => gateway() === void 0 ? row.unset(field) : write({ [field]: null })
|
|
171
|
+
};
|
|
172
|
+
}
|
|
96
173
|
//#endregion
|
|
97
174
|
//#region src/client/locales.ts
|
|
98
175
|
/** `local-agent-dsh` namespace dictionaries. */
|
|
@@ -118,11 +195,6 @@ window.__ModuleLoader__.load({
|
|
|
118
195
|
"model.applied": "已保存,下一轮委派生效;进行中的轮次不受影响",
|
|
119
196
|
"model.error": "保存失败,请重试",
|
|
120
197
|
"auth.host": "dsh 凭据走宿主实例的凭证设置,无需单独登录。",
|
|
121
|
-
"enable.title": "DeepSeek 委派",
|
|
122
|
-
"enable.switch": "启用",
|
|
123
|
-
"enable.switch.aria": "DeepSeek 委派开关",
|
|
124
|
-
"enable.on": "已开启:模型可使用 subagent_dsh 委派给本机 dsh",
|
|
125
|
-
"enable.off": "已关闭:委派只走官方内置子代理工具",
|
|
126
198
|
"live.title": "常驻模式(live)",
|
|
127
199
|
"live.info.aria": "常驻模式说明",
|
|
128
200
|
"live.info": "常驻模式 = 成员进程常驻:输出实时流入成员会话、取消不杀进程、崩溃后自动续上原会话;关闭则每轮独立进程,跑完一次性出结果。设置为全局偏好,对所有后续委派生效。",
|
|
@@ -150,11 +222,6 @@ window.__ModuleLoader__.load({
|
|
|
150
222
|
"model.applied": "Saved — takes effect on the next delegation round; rounds in flight are unaffected",
|
|
151
223
|
"model.error": "Save failed, please retry",
|
|
152
224
|
"auth.host": "dsh authenticates through the host instance’s credential settings — no separate sign-in.",
|
|
153
|
-
"enable.title": "DeepSeek delegation",
|
|
154
|
-
"enable.switch": "Enabled",
|
|
155
|
-
"enable.switch.aria": "DeepSeek delegation switch",
|
|
156
|
-
"enable.on": "ON: the model can delegate to the local dsh via subagent_dsh",
|
|
157
|
-
"enable.off": "OFF: delegation goes through the official in-process subagent tool only",
|
|
158
225
|
"live.title": "Resident mode (live)",
|
|
159
226
|
"live.info.aria": "About resident mode",
|
|
160
227
|
"live.info": "Resident mode keeps the member process alive: output streams into the member session in real time, cancelling does not kill the process, and a crash resumes the same session. Off means every round runs as an independent one-shot process. The setting is global and applies to every later delegation.",
|
|
@@ -204,8 +271,13 @@ window.__ModuleLoader__.load({
|
|
|
204
271
|
* re-probes so a mid-session credential loss cannot leave a stale green dot.
|
|
205
272
|
* @module @khorsheed/dsh-local-agent/client — auth status bus
|
|
206
273
|
*/
|
|
207
|
-
const
|
|
208
|
-
const
|
|
274
|
+
const BUS_KEY = Symbol.for("dsh.local-agent.auth-status");
|
|
275
|
+
const bus = globalThis[BUS_KEY] ??= {
|
|
276
|
+
statuses: /* @__PURE__ */ new Map(),
|
|
277
|
+
listeners: /* @__PURE__ */ new Set()
|
|
278
|
+
};
|
|
279
|
+
const statuses = bus.statuses;
|
|
280
|
+
const listeners = bus.listeners;
|
|
209
281
|
/** Record one harness's latest probe result and notify subscribers. */
|
|
210
282
|
function publishAuthStatus(harnessId, status) {
|
|
211
283
|
if (statuses.get(harnessId) === status) return;
|
|
@@ -263,30 +335,30 @@ window.__ModuleLoader__.load({
|
|
|
263
335
|
document.head.appendChild(tag);
|
|
264
336
|
}
|
|
265
337
|
var ProviderAuthBlock_module_css_default = {
|
|
266
|
-
"rowCard": "On5p7W_rowCard",
|
|
267
|
-
"rowName": "On5p7W_rowName",
|
|
268
|
-
"rowHead": "On5p7W_rowHead",
|
|
269
|
-
"section": "On5p7W_section",
|
|
270
|
-
"dotOff": "On5p7W_dotOff",
|
|
271
|
-
"logoutButton": "On5p7W_logoutButton",
|
|
272
|
-
"rosterError": "On5p7W_rosterError",
|
|
273
|
-
"dotOn": "On5p7W_dotOn",
|
|
274
|
-
"promptText": "On5p7W_promptText",
|
|
275
|
-
"title": "On5p7W_title",
|
|
276
|
-
"rowTag": "On5p7W_rowTag",
|
|
277
|
-
"intro": "On5p7W_intro",
|
|
278
338
|
"rows": "On5p7W_rows",
|
|
279
|
-
"
|
|
280
|
-
"rowActions": "On5p7W_rowActions",
|
|
281
|
-
"loginButton": "On5p7W_loginButton",
|
|
339
|
+
"logoutButton": "On5p7W_logoutButton",
|
|
282
340
|
"rowPending": "On5p7W_rowPending",
|
|
283
|
-
"
|
|
284
|
-
"
|
|
285
|
-
"status": "On5p7W_status",
|
|
341
|
+
"intro": "On5p7W_intro",
|
|
342
|
+
"title": "On5p7W_title",
|
|
286
343
|
"credentialDot": "On5p7W_credentialDot",
|
|
287
344
|
"loginPrompt": "On5p7W_loginPrompt",
|
|
345
|
+
"rowCard": "On5p7W_rowCard",
|
|
346
|
+
"dotOn": "On5p7W_dotOn",
|
|
347
|
+
"rosterError": "On5p7W_rosterError",
|
|
348
|
+
"dotPending": "On5p7W_dotPending",
|
|
349
|
+
"status": "On5p7W_status",
|
|
350
|
+
"loginButton": "On5p7W_loginButton",
|
|
351
|
+
"promptText": "On5p7W_promptText",
|
|
352
|
+
"dotOff": "On5p7W_dotOff",
|
|
288
353
|
"rowIdentity": "On5p7W_rowIdentity",
|
|
289
|
-
"
|
|
354
|
+
"openPage": "On5p7W_openPage",
|
|
355
|
+
"rowTag": "On5p7W_rowTag",
|
|
356
|
+
"rowHead": "On5p7W_rowHead",
|
|
357
|
+
"retryButton": "On5p7W_retryButton",
|
|
358
|
+
"codeInput": "On5p7W_codeInput",
|
|
359
|
+
"section": "On5p7W_section",
|
|
360
|
+
"rowActions": "On5p7W_rowActions",
|
|
361
|
+
"rowName": "On5p7W_rowName"
|
|
290
362
|
};
|
|
291
363
|
//#endregion
|
|
292
364
|
//#region ../local-agent/src/client/ProviderAuthBlock.tsx
|
|
@@ -439,8 +511,8 @@ window.__ModuleLoader__.load({
|
|
|
439
511
|
const statusKind = usePublishedAuthStatus(harness.id) ?? view.status;
|
|
440
512
|
const authenticated = statusKind === "authenticated";
|
|
441
513
|
const loginPending = pendingSince !== void 0;
|
|
442
|
-
const loginable = view.loginable ??
|
|
443
|
-
const logoutable = view.logoutable ??
|
|
514
|
+
const loginable = view.loginable ?? statusKind !== "unavailable";
|
|
515
|
+
const logoutable = view.logoutable ?? statusKind !== "unavailable";
|
|
444
516
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
445
517
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
446
518
|
className: ProviderAuthBlock_module_css_default.rowHead,
|
|
@@ -473,7 +545,7 @@ window.__ModuleLoader__.load({
|
|
|
473
545
|
statusKind === "checking" && t("loading"),
|
|
474
546
|
statusKind === "authenticated" && t("settings.authenticated"),
|
|
475
547
|
statusKind === "anonymous" && t("settings.notAuthenticated"),
|
|
476
|
-
statusKind === "unavailable" && t("
|
|
548
|
+
statusKind === "unavailable" && t("settings.statusFailed")
|
|
477
549
|
]
|
|
478
550
|
}),
|
|
479
551
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
@@ -565,10 +637,10 @@ window.__ModuleLoader__.load({
|
|
|
565
637
|
document.head.appendChild(tag);
|
|
566
638
|
}
|
|
567
639
|
var AuthStatusDot_module_css_default = {
|
|
568
|
-
"credentialDot": "OFjNUG_credentialDot",
|
|
569
640
|
"dotOff": "OFjNUG_dotOff",
|
|
570
|
-
"
|
|
571
|
-
"dotPending": "OFjNUG_dotPending"
|
|
641
|
+
"credentialDot": "OFjNUG_credentialDot",
|
|
642
|
+
"dotPending": "OFjNUG_dotPending",
|
|
643
|
+
"dotOn": "OFjNUG_dotOn"
|
|
572
644
|
};
|
|
573
645
|
//#endregion
|
|
574
646
|
//#region ../local-agent/src/client/AuthStatusDot.tsx
|
|
@@ -594,40 +666,40 @@ window.__ModuleLoader__.load({
|
|
|
594
666
|
document.head.appendChild(tag);
|
|
595
667
|
}
|
|
596
668
|
var SettingsCard_module_css_default = {
|
|
597
|
-
"
|
|
598
|
-
"
|
|
599
|
-
"rowLabel": "_3LBPpW_rowLabel",
|
|
600
|
-
"description": "_3LBPpW_description",
|
|
601
|
-
"body": "_3LBPpW_body",
|
|
602
|
-
"chevron": "_3LBPpW_chevron",
|
|
603
|
-
"modelMenuButton": "_3LBPpW_modelMenuButton",
|
|
669
|
+
"blockTitle": "_3LBPpW_blockTitle",
|
|
670
|
+
"info": "_3LBPpW_info",
|
|
604
671
|
"modelItemCheck": "_3LBPpW_modelItemCheck",
|
|
605
|
-
"headText": "_3LBPpW_headText",
|
|
606
|
-
"modelItemCurrent": "_3LBPpW_modelItemCurrent",
|
|
607
|
-
"header": "_3LBPpW_header",
|
|
608
672
|
"chevronOpen": "_3LBPpW_chevronOpen",
|
|
673
|
+
"description": "_3LBPpW_description",
|
|
674
|
+
"switchOn": "_3LBPpW_switchOn",
|
|
675
|
+
"modelMenu": "_3LBPpW_modelMenu",
|
|
676
|
+
"row": "_3LBPpW_row",
|
|
677
|
+
"card": "_3LBPpW_card",
|
|
678
|
+
"headText": "_3LBPpW_headText",
|
|
679
|
+
"rowLabel": "_3LBPpW_rowLabel",
|
|
680
|
+
"modelMenuChevron": "_3LBPpW_modelMenuChevron",
|
|
681
|
+
"modelMenuUp": "_3LBPpW_modelMenuUp",
|
|
682
|
+
"modelItemLabel": "_3LBPpW_modelItemLabel",
|
|
683
|
+
"block": "_3LBPpW_block",
|
|
609
684
|
"knob": "_3LBPpW_knob",
|
|
685
|
+
"saved": "_3LBPpW_saved",
|
|
610
686
|
"modelMenuChevronOpen": "_3LBPpW_modelMenuChevronOpen",
|
|
687
|
+
"cardOpen": "_3LBPpW_cardOpen",
|
|
611
688
|
"modelItemDefault": "_3LBPpW_modelItemDefault",
|
|
612
|
-
"modelItemLabel": "_3LBPpW_modelItemLabel",
|
|
613
689
|
"switch": "_3LBPpW_switch",
|
|
614
|
-
"blockTitle": "_3LBPpW_blockTitle",
|
|
615
|
-
"modelItem": "_3LBPpW_modelItem",
|
|
616
690
|
"modelInput": "_3LBPpW_modelInput",
|
|
617
|
-
"
|
|
618
|
-
"
|
|
619
|
-
"
|
|
691
|
+
"body": "_3LBPpW_body",
|
|
692
|
+
"chevron": "_3LBPpW_chevron",
|
|
693
|
+
"modelField": "_3LBPpW_modelField",
|
|
694
|
+
"header": "_3LBPpW_header",
|
|
695
|
+
"modelItem": "_3LBPpW_modelItem",
|
|
696
|
+
"errorText": "_3LBPpW_errorText",
|
|
697
|
+
"modelMenuButton": "_3LBPpW_modelMenuButton",
|
|
698
|
+
"modelItemCurrent": "_3LBPpW_modelItemCurrent",
|
|
699
|
+
"name": "_3LBPpW_name",
|
|
620
700
|
"modelSave": "_3LBPpW_modelSave",
|
|
621
|
-
"modelMenuUp": "_3LBPpW_modelMenuUp",
|
|
622
|
-
"saved": "_3LBPpW_saved",
|
|
623
701
|
"hint": "_3LBPpW_hint",
|
|
624
|
-
"
|
|
625
|
-
"switchOn": "_3LBPpW_switchOn",
|
|
626
|
-
"block": "_3LBPpW_block",
|
|
627
|
-
"nameRow": "_3LBPpW_nameRow",
|
|
628
|
-
"modelField": "_3LBPpW_modelField",
|
|
629
|
-
"info": "_3LBPpW_info",
|
|
630
|
-
"name": "_3LBPpW_name"
|
|
702
|
+
"nameRow": "_3LBPpW_nameRow"
|
|
631
703
|
};
|
|
632
704
|
//#endregion
|
|
633
705
|
//#region src/client/SettingsCard.tsx
|
|
@@ -694,8 +766,9 @@ window.__ModuleLoader__.load({
|
|
|
694
766
|
const write = (field, value) => {
|
|
695
767
|
setSaved(false);
|
|
696
768
|
setError(false);
|
|
697
|
-
scope.set(field, value).then(() => {
|
|
698
|
-
|
|
769
|
+
scope.set(field, value).then((accepted) => {
|
|
770
|
+
if (accepted === false) setError(true);
|
|
771
|
+
else setSaved(true);
|
|
699
772
|
}, () => {
|
|
700
773
|
setError(true);
|
|
701
774
|
});
|
|
@@ -756,7 +829,7 @@ window.__ModuleLoader__.load({
|
|
|
756
829
|
}
|
|
757
830
|
/** The card body both settings surfaces share: the auth block, the delegation switch, the default-model block, and the resident-mode block. */
|
|
758
831
|
function CardBody({ state, auth, authT, renderModelPicker, useSessions, t }) {
|
|
759
|
-
const { snapshot, saved, error, ready,
|
|
832
|
+
const { snapshot, saved, error, ready, live, storedModel, modelValue, modelSaved, modelError, choices, modelMenuOpen, modelMenuUp, modelFieldRef, modelPlaceholder, modelDefaultItem, saveModel, write, toggleModelMenu, setModelDraft, setModelMenuOpen } = state;
|
|
760
833
|
const modelInputElement = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
761
834
|
type: "text",
|
|
762
835
|
className: SettingsCard_module_css_default.modelInput,
|
|
@@ -794,37 +867,6 @@ window.__ModuleLoader__.load({
|
|
|
794
867
|
})
|
|
795
868
|
]
|
|
796
869
|
}),
|
|
797
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
798
|
-
className: SettingsCard_module_css_default.block,
|
|
799
|
-
children: [
|
|
800
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
801
|
-
className: SettingsCard_module_css_default.blockTitle,
|
|
802
|
-
children: t("enable.title")
|
|
803
|
-
}),
|
|
804
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
805
|
-
className: SettingsCard_module_css_default.row,
|
|
806
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
807
|
-
className: SettingsCard_module_css_default.rowLabel,
|
|
808
|
-
children: t("enable.switch")
|
|
809
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
810
|
-
type: "button",
|
|
811
|
-
role: "switch",
|
|
812
|
-
"aria-checked": enabled,
|
|
813
|
-
"aria-label": t("enable.switch.aria"),
|
|
814
|
-
className: enabled ? `${SettingsCard_module_css_default.switch} ${SettingsCard_module_css_default.switchOn}` : SettingsCard_module_css_default.switch,
|
|
815
|
-
disabled: !ready,
|
|
816
|
-
onClick: () => {
|
|
817
|
-
write("enabled", !enabled);
|
|
818
|
-
},
|
|
819
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: SettingsCard_module_css_default.knob })
|
|
820
|
-
})]
|
|
821
|
-
}),
|
|
822
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
823
|
-
className: SettingsCard_module_css_default.hint,
|
|
824
|
-
children: t(enabled ? "enable.on" : "enable.off")
|
|
825
|
-
})
|
|
826
|
-
]
|
|
827
|
-
}),
|
|
828
870
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
829
871
|
className: SettingsCard_module_css_default.block,
|
|
830
872
|
children: [
|
|
@@ -1122,7 +1164,7 @@ window.__ModuleLoader__.load({
|
|
|
1122
1164
|
en
|
|
1123
1165
|
}), "local-agent-dsh: dictionaries");
|
|
1124
1166
|
const t = ctx.locale.bind(NS);
|
|
1125
|
-
const scope =
|
|
1167
|
+
const scope = bindCardSettingsScope(ctx, "dsh", "local-agent-dsh");
|
|
1126
1168
|
const authT = ctx.locale.bind("local-agent");
|
|
1127
1169
|
const gateway = () => ctx.get("remote.localAgentGateway");
|
|
1128
1170
|
const cardFace = () => ({
|
package/lib/index.js
CHANGED
|
@@ -5,7 +5,6 @@ import { dirname } from "node:path";
|
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import z from "@deepseek-ai/schemastery";
|
|
7
7
|
import { ModelDirectoryCache, extendModelDirectory, settingsFace, vol } from "@khorsheed/dsh-local-agent";
|
|
8
|
-
import { mountSubagentDelegationTool } from "@khorsheed/dsh-local-agent/tool-factory";
|
|
9
8
|
//#region lib/types/live-switch.js
|
|
10
9
|
/**
|
|
11
10
|
* The settings-driven live-driver switch: owns the active driver generation
|
|
@@ -404,7 +403,7 @@ const inject = [
|
|
|
404
403
|
*/
|
|
405
404
|
const SETTINGS_FIELDS = {
|
|
406
405
|
enabled: z.boolean().default(false),
|
|
407
|
-
live: z.boolean().default(
|
|
406
|
+
live: z.boolean().default(true),
|
|
408
407
|
liveMirrorGranularity: z.union([z.const("event"), z.const("token")]).default("token"),
|
|
409
408
|
model: z.string(),
|
|
410
409
|
recentModels: z.array(z.string()).default([])
|
|
@@ -446,34 +445,6 @@ const DSH_SETTINGS_NAMESPACE = "local-agent-dsh";
|
|
|
446
445
|
* the YAML config's deployment defaults through the composition base.
|
|
447
446
|
*/
|
|
448
447
|
const DSH_SETTINGS_SCHEMA = z.object(SETTINGS_FIELDS);
|
|
449
|
-
/** The delegation tool the toggle mounts while ON. */
|
|
450
|
-
const DSH_TOOL_NAME = "subagent_dsh";
|
|
451
|
-
/**
|
|
452
|
-
* This package's own name, passed to the family tool factory as the
|
|
453
|
-
* tool-origin owner (AGENTS.md § Tool origin tagging — a shared factory never
|
|
454
|
-
* hardcodes an owner), the same pattern the worktrees/room `./tool` entries
|
|
455
|
-
* use.
|
|
456
|
-
*/
|
|
457
|
-
const PACKAGE_NAME = "@khorsheed/dsh-local-agent-dsh";
|
|
458
|
-
/**
|
|
459
|
-
* The `subagent_dsh` mount, as a plugin object over the family factory: the
|
|
460
|
-
* toggle owns its lifecycle, so it mounts dynamically through `ctx.plugin`
|
|
461
|
-
* instead of shipping a `./tool` composition row like the CLI providers.
|
|
462
|
-
*/
|
|
463
|
-
const dshSubagentTool = {
|
|
464
|
-
name: "local-agent-dsh-tool",
|
|
465
|
-
inject: [
|
|
466
|
-
"tools",
|
|
467
|
-
"subagents",
|
|
468
|
-
"localAgent"
|
|
469
|
-
],
|
|
470
|
-
apply: (toolCtx) => {
|
|
471
|
-
mountSubagentDelegationTool(toolCtx, {
|
|
472
|
-
provider: "dsh-cli",
|
|
473
|
-
toolName: DSH_TOOL_NAME
|
|
474
|
-
}, PACKAGE_NAME);
|
|
475
|
-
}
|
|
476
|
-
};
|
|
477
448
|
/**
|
|
478
449
|
* Mount the toggle controller: register the settings namespace, then mirror
|
|
479
450
|
* its value into harness/provider/tool registrations, live.
|
|
@@ -485,7 +456,7 @@ function apply(ctx, config) {
|
|
|
485
456
|
ctx.effect(() => {
|
|
486
457
|
const readSettings = () => ({
|
|
487
458
|
enabled: vol(config.enabled) ?? false,
|
|
488
|
-
live: vol(config.live) ??
|
|
459
|
+
live: vol(config.live) ?? true,
|
|
489
460
|
liveMirrorGranularity: vol(config.liveMirrorGranularity) ?? "token",
|
|
490
461
|
model: vol(config.model),
|
|
491
462
|
recentModels: vol(config.recentModels) ?? []
|
|
@@ -502,6 +473,7 @@ function apply(ctx, config) {
|
|
|
502
473
|
}
|
|
503
474
|
const face = settingsFace(ctx, {
|
|
504
475
|
namespace: DSH_SETTINGS_NAMESPACE,
|
|
476
|
+
harness: "dsh",
|
|
505
477
|
legacySchema: DSH_SETTINGS_SCHEMA,
|
|
506
478
|
legacyBase,
|
|
507
479
|
read: readSettings
|
|
@@ -620,7 +592,6 @@ function apply(ctx, config) {
|
|
|
620
592
|
if (enabled === currentEnabled) return;
|
|
621
593
|
currentEnabled = enabled;
|
|
622
594
|
generation += 1;
|
|
623
|
-
const gen = generation;
|
|
624
595
|
for (const dispose of disposers.splice(0)) dispose();
|
|
625
596
|
if (!enabled) return;
|
|
626
597
|
provisionDshScope(homeDir, config, profileDirOption());
|
|
@@ -632,25 +603,10 @@ function apply(ctx, config) {
|
|
|
632
603
|
if (currentLiveSwitch === liveSwitch) currentLiveSwitch = void 0;
|
|
633
604
|
liveSwitch.dispose();
|
|
634
605
|
});
|
|
635
|
-
ctx.plugin(dshSubagentTool).then((fiber) => {
|
|
636
|
-
if (gen !== generation) {
|
|
637
|
-
fiber.dispose();
|
|
638
|
-
return;
|
|
639
|
-
}
|
|
640
|
-
disposers.push(() => {
|
|
641
|
-
fiber.dispose();
|
|
642
|
-
});
|
|
643
|
-
}, (error) => {
|
|
644
|
-
ctx.logger.warn(`local-agent-dsh: delegation tool mount failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
645
|
-
});
|
|
646
606
|
};
|
|
647
|
-
sync(
|
|
648
|
-
const unwatch = face.watch((next) => {
|
|
649
|
-
sync(next.enabled);
|
|
650
|
-
});
|
|
607
|
+
sync(true);
|
|
651
608
|
return () => {
|
|
652
609
|
modelCatalog.dispose();
|
|
653
|
-
unwatch();
|
|
654
610
|
generation += 1;
|
|
655
611
|
for (const dispose of disposers.splice(0)) dispose();
|
|
656
612
|
};
|
package/lib/tool.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { mountSubagentDelegationTool, subagentToolConfigSchema } from "@khorsheed/dsh-local-agent/tool-factory";
|
|
2
|
+
//#region lib/types/tool.js
|
|
3
|
+
const PACKAGE_NAME = "@khorsheed/dsh-local-agent-dsh";
|
|
4
|
+
/** This entry's own defaults: a preset row needs no config. */
|
|
5
|
+
const DEFAULT_PROVIDER = "dsh-cli";
|
|
6
|
+
const DEFAULT_TOOL_NAME = "subagent_dsh";
|
|
7
|
+
/** Cordis plugin name used by loader diagnostics. */
|
|
8
|
+
const name = "local-agent-dsh-tool";
|
|
9
|
+
/**
|
|
10
|
+
* The family core's `localAgent` service is a declared inject (the
|
|
11
|
+
* owning-family companion exception to the community-service probe rule): a
|
|
12
|
+
* preset's standing scope mounts at registry-activation time, BEFORE the
|
|
13
|
+
* profile's later bundle rows provide the core, so a one-shot ctx.get probe
|
|
14
|
+
* at apply saw ABSENT there and nothing re-ran the row (rc.1 boot order; the
|
|
15
|
+
* worktrees `./tool` entry documents the incident). The declared inject pends
|
|
16
|
+
* the row until the core provides, then the body applies.
|
|
17
|
+
*/
|
|
18
|
+
const inject = [
|
|
19
|
+
"tools",
|
|
20
|
+
"subagents",
|
|
21
|
+
"localAgent"
|
|
22
|
+
];
|
|
23
|
+
const Config = subagentToolConfigSchema(DEFAULT_PROVIDER, DEFAULT_TOOL_NAME);
|
|
24
|
+
/**
|
|
25
|
+
* Plugin body: mount the delegation tool through the family factory, stamped
|
|
26
|
+
* with THIS package as the tool-origin owner (AGENTS.md § Tool origin
|
|
27
|
+
* tagging — the shared factory never hardcodes an owner).
|
|
28
|
+
* @param ctx - Cordis context (the preset's agent-plane mount).
|
|
29
|
+
* @param config - row config (schema defaults already applied; the `??`
|
|
30
|
+
* fallbacks cover a direct ctx.plugin caller that bypassed validation).
|
|
31
|
+
*/
|
|
32
|
+
function apply(ctx, config) {
|
|
33
|
+
const resolved = {
|
|
34
|
+
provider: config.provider ?? DEFAULT_PROVIDER,
|
|
35
|
+
toolName: config.toolName ?? DEFAULT_TOOL_NAME
|
|
36
|
+
};
|
|
37
|
+
mountSubagentDelegationTool(ctx, resolved, PACKAGE_NAME);
|
|
38
|
+
}
|
|
39
|
+
//#endregion
|
|
40
|
+
export { Config, apply, inject, name };
|
|
@@ -78,7 +78,11 @@ function useCardState(active, useSettings, scope, harnessModel, t) {
|
|
|
78
78
|
const write = (field, value) => {
|
|
79
79
|
setSaved(false);
|
|
80
80
|
setError(false);
|
|
81
|
-
|
|
81
|
+
// The host answers a refused write with `false` (ConfigForm), not a rejection.
|
|
82
|
+
void scope.set(field, value).then((accepted) => { if (accepted === false)
|
|
83
|
+
setError(true);
|
|
84
|
+
else
|
|
85
|
+
setSaved(true); }, () => { setError(true); });
|
|
82
86
|
};
|
|
83
87
|
// The suggestion vocabulary: the broker's deduped union (settings + host
|
|
84
88
|
// default selection + recent) when the surface answered, the card's own
|
|
@@ -152,13 +156,13 @@ function useCardState(active, useSettings, scope, harnessModel, t) {
|
|
|
152
156
|
}
|
|
153
157
|
/** The card body both settings surfaces share: the auth block, the delegation switch, the default-model block, and the resident-mode block. */
|
|
154
158
|
function CardBody({ state, auth, authT, renderModelPicker, useSessions, t }) {
|
|
155
|
-
const { snapshot, saved, error, ready,
|
|
159
|
+
const { snapshot, saved, error, ready, live, storedModel, modelValue, modelSaved, modelError, choices, modelMenuOpen, modelMenuUp, modelFieldRef, modelPlaceholder, modelDefaultItem, saveModel, write, toggleModelMenu, setModelDraft, setModelMenuOpen, } = state;
|
|
156
160
|
// The free-text input, single-sourced for both presentations: bare when no
|
|
157
161
|
// vocabulary exists (a fresh install degrades to exactly the pre-picker
|
|
158
162
|
// field), or inside the select-like field next to its chevron. No datalist:
|
|
159
163
|
// the chevron menu is the single choice list.
|
|
160
164
|
const modelInputElement = (_jsx("input", { type: "text", className: css.modelInput, value: modelValue, placeholder: modelPlaceholder, "aria-label": t('model.title'), disabled: !ready, onChange: (event) => { setModelDraft(event.target.value); } }));
|
|
161
|
-
return (_jsxs("div", { className: css.body, children: [_jsxs("section", { className: css.block, children: [_jsx("h3", { className: css.blockTitle, children: t('auth.title') }), _jsx(ProviderAuthBlock, { harness: { id: 'dsh', label: 'dsh' }, useSessions: useSessions, status: auth.status, runCommand: auth.runCommand, t: authT }), _jsx("span", { className: css.hint, children: t('auth.host') })] }), _jsxs("section", { className: css.block, children: [
|
|
165
|
+
return (_jsxs("div", { className: css.body, children: [_jsxs("section", { className: css.block, children: [_jsx("h3", { className: css.blockTitle, children: t('auth.title') }), _jsx(ProviderAuthBlock, { harness: { id: 'dsh', label: 'dsh' }, useSessions: useSessions, status: auth.status, runCommand: auth.runCommand, t: authT }), _jsx("span", { className: css.hint, children: t('auth.host') })] }), _jsxs("section", { className: css.block, children: [_jsxs("h3", { className: css.blockTitle, children: [t('model.title'), _jsx(Tooltip, { label: t('model.info'), side: "bottom", maxWidth: 360, children: _jsx("button", { type: "button", className: css.info, "aria-label": t('model.info.aria'), children: "\u24D8" }) })] }), _jsxs("div", { className: css.row, children: [renderModelPicker?.({ value: modelValue, onChange: setModelDraft, disabled: !ready, defaultLabel: modelDefaultItem }) ?? (choices.length > 0 ? (_jsxs("div", { className: css.modelField, ref: modelFieldRef, children: [modelInputElement, _jsx("button", { type: "button", className: css.modelMenuButton, "aria-label": t('model.menu'), "aria-haspopup": "menu", "aria-expanded": modelMenuOpen, disabled: !ready, onClick: () => { toggleModelMenu(); }, children: _jsx(IconChevronDownOutlineMedium, { className: modelMenuOpen ? `${css.modelMenuChevron} ${css.modelMenuChevronOpen}` : css.modelMenuChevron }) }), modelMenuOpen && (_jsxs("div", { className: modelMenuUp ? `${css.modelMenu} ${css.modelMenuUp}` : css.modelMenu, role: "menu", "aria-label": t('model.menu'), children: [_jsxs("button", { type: "button", role: "menuitemradio", "aria-checked": modelValue.trim() === '', className: modelValue.trim() === '' ? `${css.modelItemDefault} ${css.modelItemCurrent}` : css.modelItemDefault, onClick: () => { setModelDraft(''); setModelMenuOpen(false); }, children: [_jsx("span", { className: css.modelItemLabel, children: modelDefaultItem }), _jsx("span", { className: css.modelItemCheck, "aria-hidden": true, children: modelValue.trim() === '' ? '✓' : '' })] }), choices.map(choice => (_jsxs("button", { type: "button", role: "menuitemradio", "aria-checked": choice === modelValue, className: choice === modelValue ? css.modelItemCurrent : css.modelItem, onClick: () => { setModelDraft(choice); setModelMenuOpen(false); }, children: [_jsx("span", { className: css.modelItemLabel, children: choice }), _jsx("span", { className: css.modelItemCheck, "aria-hidden": true, children: choice === modelValue ? '✓' : '' })] }, choice)))] }))] })) : modelInputElement), _jsx("button", { type: "button", className: css.modelSave, disabled: !ready || modelValue.trim() === storedModel, onClick: () => { saveModel(); }, children: t('model.save') })] }), modelSaved && _jsx("span", { className: css.saved, children: t('model.applied') }), modelError && _jsx("span", { className: css.errorText, children: t('model.error') })] }), _jsxs("section", { className: css.block, children: [_jsxs("div", { className: css.row, children: [_jsxs("span", { className: css.rowLabel, children: [t('live.title'), _jsx(Tooltip, { label: t('live.info'), side: "bottom", maxWidth: 360, children: _jsx("button", { type: "button", className: css.info, "aria-label": t('live.info.aria'), children: "\u24D8" }) })] }), _jsx("button", { type: "button", role: "switch", "aria-checked": live, "aria-label": t('live.title'), className: live ? `${css.switch} ${css.switchOn}` : css.switch, disabled: !ready, onClick: () => { write('live', !live); }, children: _jsx("span", { className: css.knob }) })] }), snapshot.status === 'unavailable' && (_jsx("span", { className: css.hint, children: t('live.unavailable') }))] }), saved && _jsx("span", { className: css.saved, children: t('live.applied') }), error && _jsx("span", { className: css.errorText, children: t('live.error') })] }));
|
|
162
166
|
}
|
|
163
167
|
/**
|
|
164
168
|
* The dsh harness settings card of the retired 0.1.5 plugin configuration
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { bindCardSettingsScope } from '@khorsheed/dsh-local-agent/src/client/settings-scope.ts';
|
|
2
2
|
/**
|
|
3
3
|
* Local-agent-dsh plugin, browser half: the dsh settings card — a section of
|
|
4
4
|
* the family core's「本机 Agent 委派」family card on the Plugins page
|
|
@@ -45,7 +45,7 @@ export const inject = ['slots', 'remote', 'remote.commands', 'locale'];
|
|
|
45
45
|
export function apply(ctx) {
|
|
46
46
|
ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'local-agent-dsh: dictionaries');
|
|
47
47
|
const t = ctx.locale.bind(NS);
|
|
48
|
-
const scope =
|
|
48
|
+
const scope = bindCardSettingsScope(ctx, 'dsh', 'local-agent-dsh');
|
|
49
49
|
// The auth block's copy lives in the family core's dictionary; binding is
|
|
50
50
|
// stable per namespace and late dictionary registration still resolves.
|
|
51
51
|
const authT = ctx.locale.bind('local-agent');
|