@khorsheed/dsh-local-agent-dsh 0.1.0-rc.6

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.
Files changed (45) hide show
  1. package/README.en.md +150 -0
  2. package/README.i18n.yaml +6 -0
  3. package/README.md +151 -0
  4. package/cordis.patch.yml +24 -0
  5. package/lib/client.js +1115 -0
  6. package/lib/dsh-cli-provider-UhWvivte.js +1701 -0
  7. package/lib/dsh-cli-provider.js +3 -0
  8. package/lib/index.js +624 -0
  9. package/lib/invariant.js +33 -0
  10. package/lib/provision.js +602 -0
  11. package/lib/records.js +109 -0
  12. package/lib/session-log-DOsSz56-.js +88 -0
  13. package/lib/types/client/SettingsCard.d.ts +78 -0
  14. package/lib/types/client/SettingsCard.js +200 -0
  15. package/lib/types/client/icons.d.ts +18 -0
  16. package/lib/types/client/icons.js +2 -0
  17. package/lib/types/client/index.d.ts +45 -0
  18. package/lib/types/client/index.js +88 -0
  19. package/lib/types/client/locales.d.ts +76 -0
  20. package/lib/types/client/locales.js +67 -0
  21. package/lib/types/dsh-cli-provider.d.ts +213 -0
  22. package/lib/types/dsh-cli-provider.js +733 -0
  23. package/lib/types/index.d.ts +124 -0
  24. package/lib/types/index.js +347 -0
  25. package/lib/types/invariant.d.ts +16 -0
  26. package/lib/types/invariant.js +37 -0
  27. package/lib/types/live-driver.d.ts +180 -0
  28. package/lib/types/live-driver.js +887 -0
  29. package/lib/types/live-switch.d.ts +76 -0
  30. package/lib/types/live-switch.js +100 -0
  31. package/lib/types/model-broker.d.ts +86 -0
  32. package/lib/types/model-broker.js +149 -0
  33. package/lib/types/model-catalog.d.ts +27 -0
  34. package/lib/types/model-catalog.js +71 -0
  35. package/lib/types/provision.d.ts +382 -0
  36. package/lib/types/provision.js +656 -0
  37. package/lib/types/records.d.ts +41 -0
  38. package/lib/types/records.js +125 -0
  39. package/lib/types/session-log.d.ts +58 -0
  40. package/lib/types/session-log.js +83 -0
  41. package/lib/types/session-mirror.d.ts +155 -0
  42. package/lib/types/session-mirror.js +451 -0
  43. package/locale/en.json +6 -0
  44. package/locale/zh.json +6 -0
  45. package/package.json +121 -0
package/lib/client.js ADDED
@@ -0,0 +1,1115 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "@khorsheed/dsh-local-agent-dsh",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ let react = require("react");
8
+ let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
9
+ let react_jsx_runtime = require("react/jsx-runtime");
10
+ //#region ../local-agent/src/client/settings-scope.ts
11
+ /**
12
+ * Observable channel behind the card's settings face: delegates to the armed
13
+ * line scope and republishes on arming, reporting the degraded face until
14
+ * either host line's deferred probe arms it. The channel exists because the
15
+ * line probe cannot run as a direct property read — cordis 4.0.4's
16
+ * service-access guard throws `cannot get property "<name>" without inject`
17
+ * on any undeclared `ctx.<service>` touch, and neither service name exists on
18
+ * the other host line, so a static inject would pend there; `ctx.inject` +
19
+ * `ctx.get` is the only probe that is safe on both.
20
+ */
21
+ var SettingsScopeChannel = class {
22
+ constructor() {
23
+ this.inner = unavailableSettingsScope();
24
+ this.listeners = /* @__PURE__ */ new Set();
25
+ this.getSnapshot = () => this.inner.getSnapshot();
26
+ this.subscribe = (listener) => {
27
+ this.listeners.add(listener);
28
+ return () => {
29
+ this.listeners.delete(listener);
30
+ };
31
+ };
32
+ this.set = (field, value) => this.inner.set(field, value);
33
+ this.unset = (field) => this.inner.unset(field);
34
+ }
35
+ /** Bind the line-served scope; the first arm wins (the two lines never coexist). */
36
+ arm(scope) {
37
+ this.inner = scope;
38
+ scope.subscribe(() => {
39
+ this.emit();
40
+ });
41
+ this.emit();
42
+ }
43
+ emit() {
44
+ for (const listener of [...this.listeners]) listener();
45
+ }
46
+ };
47
+ /**
48
+ * An inert scope for a composition whose settings client never came up:
49
+ * reads report `unavailable` (the cards disable their controls, exactly like
50
+ * an unregistered namespace) and writes never settle a value change.
51
+ * @returns the degraded scope.
52
+ */
53
+ function unavailableSettingsScope() {
54
+ const snapshot = {
55
+ status: "unavailable",
56
+ value: void 0,
57
+ base: void 0,
58
+ user: void 0,
59
+ revision: void 0,
60
+ writable: false,
61
+ mode: "memory"
62
+ };
63
+ return {
64
+ getSnapshot: () => snapshot,
65
+ subscribe: () => () => {},
66
+ set: () => Promise.resolve(false),
67
+ unset: () => Promise.resolve(false)
68
+ };
69
+ }
70
+ /**
71
+ * Bind the provider row's settings scope on the host line actually serving.
72
+ * Returns the channel synchronously; both line probes ride deferred injects,
73
+ * so the serving line's arm fires as soon as its settings service is up (the
74
+ * settings client package is a declared client inject of every family
75
+ * provider, so in a normal composition arming lands immediately) and a
76
+ * provider mounting first still gets armed later. Absent both — a broken
77
+ * composition — the channel keeps the card on the degraded face, alive and
78
+ * disabled.
79
+ * @param ctx - the provider's client context.
80
+ * @param entryId - the plugin row id (`local-agent-dsh` and friends), the
81
+ * same string the 0.1.5 namespace used.
82
+ * @returns the bound scope channel.
83
+ */
84
+ function bindSettingsScope(ctx, entryId) {
85
+ const channel = new SettingsScopeChannel();
86
+ ctx.inject(["configForms"], (formsCtx) => {
87
+ const forms = formsCtx.get("configForms");
88
+ if (typeof forms?.get === "function") channel.arm(forms.get(entryId));
89
+ });
90
+ ctx.inject(["settingsScope"], (legacyCtx) => {
91
+ const legacy = legacyCtx.get("settingsScope");
92
+ if (typeof legacy?.bind === "function") channel.arm(legacy.bind({ namespace: entryId }));
93
+ });
94
+ return channel;
95
+ }
96
+ //#endregion
97
+ //#region src/client/locales.ts
98
+ /** `local-agent-dsh` namespace dictionaries. */
99
+ /** Dictionary namespace owned by this plugin (matches the settings namespace). */
100
+ const NS = "local-agent-dsh";
101
+ /** Simplified Chinese dictionary (the key-set source of truth). */
102
+ const zh = {
103
+ "card.title": "Local Agent · dsh",
104
+ "card.description": "dsh 自委派:开关、认证状态与常驻模式(live)",
105
+ "card.expand": "展开",
106
+ "card.collapse": "收起",
107
+ "auth.title": "认证",
108
+ "model.title": "默认模型",
109
+ "model.info.aria": "默认模型说明",
110
+ "model.info": "留空 = 跟随默认(输入框里显示的就是当前跟随的模型)。从列表选择或手动输入一个模型后保存,之后的每轮委派都以它起子 dsh(`--model <provider/model>`;只写模型名则沿用宿主实例的 provider)。单次委派自带的模型优先级更高;成员会话里还可用作曲器按会话切换,不影响这里的默认。改动在下一轮委派生效,进行中的轮次不受影响;评测 run 的条件在建立时冻结,中途换模型会被下一轮的模型回读判为 misattributed 而拦下。",
111
+ "model.placeholder": "留空 = 不传,用宿主实例的默认模型",
112
+ "model.menu": "选择模型",
113
+ "model.menuDefault": "默认(跟随宿主实例默认)",
114
+ "model.menuDefault.cli-config": "默认(跟随宿主默认模型:{model})",
115
+ "model.menuDefault.cli-builtin": "默认(跟随宿主默认:{model})",
116
+ "model.menuDefault.last-observed": "默认(跟随宿主实例默认(最近:{model}))",
117
+ "model.save": "保存",
118
+ "model.applied": "已保存,下一轮委派生效;进行中的轮次不受影响",
119
+ "model.error": "保存失败,请重试",
120
+ "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
+ "live.title": "常驻模式(live)",
127
+ "live.info.aria": "常驻模式说明",
128
+ "live.info": "常驻模式 = 成员进程常驻:输出实时流入成员会话、取消不杀进程、崩溃后自动续上原会话;关闭则每轮独立进程,跑完一次性出结果。设置为全局偏好,对所有后续委派生效。",
129
+ "live.applied": "已保存,下一轮委派生效;进行中的轮次不受影响",
130
+ "live.error": "保存失败,请重试",
131
+ "live.unavailable": "设置服务不可用(宿主未注册 local-agent-dsh 命名空间)"
132
+ };
133
+ /** English dictionary, checked complete against the zh key set. */
134
+ const en = {
135
+ "card.title": "Local Agent · dsh",
136
+ "card.description": "dsh self-delegation: toggle, credential status, and resident (live) mode",
137
+ "card.expand": "Expand",
138
+ "card.collapse": "Collapse",
139
+ "auth.title": "Authentication",
140
+ "model.title": "Default model",
141
+ "model.info.aria": "About the default model",
142
+ "model.info": "Blank = follow the default (the input shows the model it currently follows). Pick from the list or type a model by hand and save, and every later delegation round starts the sub-dsh with it (`--model <provider/model>`; a bare id keeps the host instance provider). A model named by one delegation outranks this key; inside a member session the composer can also switch the model per session, without touching this default. A change applies to the next round and never disturbs a round in flight; an evaluation run freezes its condition at setup, so switching mid-run is caught by the model read-back on the next round and fails the run as misattributed.",
143
+ "model.placeholder": "Blank = pass none, use the host instance default",
144
+ "model.menu": "Pick a model",
145
+ "model.menuDefault": "Default (follow the host instance default)",
146
+ "model.menuDefault.cli-config": "Default (follow the host default model: {model})",
147
+ "model.menuDefault.cli-builtin": "Default (follow the host default: {model})",
148
+ "model.menuDefault.last-observed": "Default (follow the host instance default (last: {model}))",
149
+ "model.save": "Save",
150
+ "model.applied": "Saved — takes effect on the next delegation round; rounds in flight are unaffected",
151
+ "model.error": "Save failed, please retry",
152
+ "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
+ "live.title": "Resident mode (live)",
159
+ "live.info.aria": "About resident mode",
160
+ "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.",
161
+ "live.applied": "Saved — takes effect on the next delegation round; rounds in flight are unaffected",
162
+ "live.error": "Save failed, please retry",
163
+ "live.unavailable": "Settings service unavailable (the host did not register the local-agent-dsh namespace)"
164
+ };
165
+ //#endregion
166
+ //#region src/client/icons.tsx
167
+ const IconChevronDownOutlineMedium = ({ size = 14, className }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
168
+ width: size,
169
+ height: size,
170
+ className,
171
+ viewBox: "0 0 16 16",
172
+ fill: "none",
173
+ xmlns: "http://www.w3.org/2000/svg",
174
+ "aria-hidden": "true",
175
+ strokeWidth: 1.3,
176
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
177
+ d: "M4 6L7.29289 9.29289C7.68342 9.68342 8.31658 9.68342 8.70711 9.29289L12 6",
178
+ stroke: "currentColor"
179
+ })
180
+ });
181
+ //#endregion
182
+ //#region ../local-agent/src/client/icons.tsx
183
+ const IconCheckOutlineMedium = ({ size = 16, className }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
184
+ width: size,
185
+ height: size,
186
+ className,
187
+ viewBox: "0 0 16 16",
188
+ fill: "none",
189
+ xmlns: "http://www.w3.org/2000/svg",
190
+ "aria-hidden": "true",
191
+ strokeWidth: 1.3,
192
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
193
+ d: "M2.25 8.5L5.49732 11.7473C5.90519 12.1552 6.57263 12.1344 6.95426 11.7018L13.75 4",
194
+ stroke: "currentColor"
195
+ })
196
+ });
197
+ //#endregion
198
+ //#region ../local-agent/src/client/auth-status.ts
199
+ /**
200
+ * Harness auth-status bus for the settings cards: the ProviderAuthBlock
201
+ * publishes every probe result, and card headers subscribe for the at-a-glance
202
+ * credential dot — one probe feeds both the block and the header, a login or
203
+ * logout inside any card flips every dot immediately, and every header mount
204
+ * re-probes so a mid-session credential loss cannot leave a stale green dot.
205
+ * @module @khorsheed/dsh-local-agent/client — auth status bus
206
+ */
207
+ const statuses = /* @__PURE__ */ new Map();
208
+ const listeners = /* @__PURE__ */ new Set();
209
+ /** Record one harness's latest probe result and notify subscribers. */
210
+ function publishAuthStatus(harnessId, status) {
211
+ if (statuses.get(harnessId) === status) return;
212
+ statuses.set(harnessId, status);
213
+ for (const listener of listeners) listener();
214
+ }
215
+ function subscribe(listener) {
216
+ listeners.add(listener);
217
+ return () => {
218
+ listeners.delete(listener);
219
+ };
220
+ }
221
+ /**
222
+ * Read one harness's latest known auth status. Every mount probes once and
223
+ * publishes — a published value only snapshots the past (a credential expiring
224
+ * mid-session must flip the dot at the next view, not stay green forever);
225
+ * the bus dedupes identical values, so repeat probes never re-render.
226
+ * @param harnessId - the harness to read.
227
+ * @param probe - the card's injected status face (read-only Remote channel).
228
+ * @returns the latest known status kind.
229
+ */
230
+ function useHarnessAuthStatus(harnessId, probe) {
231
+ const status = (0, react.useSyncExternalStore)(subscribe, () => statuses.get(harnessId) ?? "checking");
232
+ (0, react.useEffect)(() => {
233
+ let cancelled = false;
234
+ probe(harnessId).then((result) => {
235
+ if (cancelled) return;
236
+ publishAuthStatus(harnessId, result === void 0 ? "unavailable" : result.authenticated ? "authenticated" : "anonymous");
237
+ }).catch(() => {
238
+ if (!cancelled) publishAuthStatus(harnessId, "unavailable");
239
+ });
240
+ return () => {
241
+ cancelled = true;
242
+ };
243
+ }, [harnessId, probe]);
244
+ return status;
245
+ }
246
+ //#endregion
247
+ //#region \0dsh-css:../local-agent/src/client/ProviderAuthBlock.module.css.mjs
248
+ const css$2 = ".On5p7W_section{max-width:720px;color:var(--dsw-alias-label-primary);flex-direction:column;gap:4px;display:flex}.On5p7W_title{color:var(--dsw-alias-label-primary);margin:0;font-size:16px;font-weight:500;line-height:24px}.On5p7W_intro{color:var(--dsw-alias-label-tertiary);margin:0;font-size:14px;line-height:22px}.On5p7W_rows{flex-direction:column;gap:8px;margin:12px 0 0;padding:0;list-style:none;display:flex}.On5p7W_rowCard{border:1px solid var(--dsw-alias-border-l2);border-radius:12px;flex-direction:column;gap:10px;padding:12px 14px;display:flex}.On5p7W_rowPending{opacity:.6}.On5p7W_rowHead{align-items:center;gap:10px;display:flex}.On5p7W_rowIdentity{align-items:center;gap:6px;min-width:0;display:inline-flex}.On5p7W_rowName{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:500;line-height:22px}.On5p7W_rowTag{border:1px solid var(--dsw-alias-border-l3);color:var(--dsw-alias-label-secondary);border-radius:4px;flex:none;padding:1px 6px;font-size:11px;line-height:16px}.On5p7W_credentialDot{box-sizing:border-box;border-radius:50%;flex:none;width:8px;height:8px;display:inline-block}.On5p7W_dotOn{background:var(--dsw-alias-state-success-primary)}.On5p7W_dotOff{background:var(--dsw-alias-state-error-primary)}.On5p7W_dotPending{background:var(--dsw-alias-fill-l3)}.On5p7W_status{color:var(--dsw-alias-label-tertiary);flex:none;margin-left:auto;font-size:12px;line-height:18px}.On5p7W_rowActions{align-items:center;gap:4px;display:inline-flex}.On5p7W_loginButton{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);height:28px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border-radius:14px;justify-content:center;align-items:center;padding:0 12px;font-size:12px;line-height:18px;display:inline-flex}.On5p7W_loginButton:hover{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-border-l3)}.On5p7W_loginButton:disabled{color:var(--dsw-alias-label-tertiary);border-color:var(--dsw-alias-border-l2);cursor:not-allowed}.On5p7W_logoutButton{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);height:28px;color:var(--dsw-alias-state-error-primary);cursor:pointer;background:0 0;border-radius:14px;justify-content:center;align-items:center;padding:0 12px;font-size:12px;line-height:18px;display:inline-flex}.On5p7W_logoutButton:hover{border-color:var(--dsw-alias-state-error-primary)}.On5p7W_rosterError{color:var(--dsw-alias-state-error-primary);align-items:center;gap:10px;margin:8px 0 0;font-size:12px;line-height:18px;display:flex}.On5p7W_retryButton{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);height:24px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border-radius:12px;justify-content:center;align-items:center;padding:0 10px;font-size:12px;line-height:18px;display:inline-flex}.On5p7W_retryButton:hover{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-border-l3)}.On5p7W_loginPrompt{align-items:flex-start;gap:10px;display:flex}.On5p7W_promptText{min-width:0;color:var(--dsw-alias-label-tertiary);overflow-wrap:anywhere;flex:1;font-size:12px;line-height:18px}.On5p7W_codeInput{border:1px solid var(--dsw-alias-line-normal,currentColor);min-width:0;color:inherit;background:0 0;border-radius:6px;flex:1;padding:4px 8px;font-size:12px;line-height:18px}.On5p7W_openPage{color:var(--dsw-alias-accent-link);flex:none;font-size:12px;line-height:18px;text-decoration:none}.On5p7W_openPage:hover{text-decoration:underline}";
249
+ const tagId$2 = "@khorsheed/dsh-local-agent-dsh/ProviderAuthBlock.module.css";
250
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
251
+ const tag = document.createElement("style");
252
+ tag.dataset.plugin = "@khorsheed/dsh-local-agent-dsh";
253
+ tag.dataset.pluginCss = tagId$2;
254
+ tag.textContent = css$2;
255
+ document.head.appendChild(tag);
256
+ }
257
+ var ProviderAuthBlock_module_css_default = {
258
+ "promptText": "On5p7W_promptText",
259
+ "retryButton": "On5p7W_retryButton",
260
+ "intro": "On5p7W_intro",
261
+ "rowName": "On5p7W_rowName",
262
+ "dotPending": "On5p7W_dotPending",
263
+ "codeInput": "On5p7W_codeInput",
264
+ "section": "On5p7W_section",
265
+ "rowIdentity": "On5p7W_rowIdentity",
266
+ "openPage": "On5p7W_openPage",
267
+ "status": "On5p7W_status",
268
+ "title": "On5p7W_title",
269
+ "logoutButton": "On5p7W_logoutButton",
270
+ "dotOff": "On5p7W_dotOff",
271
+ "rowPending": "On5p7W_rowPending",
272
+ "dotOn": "On5p7W_dotOn",
273
+ "loginPrompt": "On5p7W_loginPrompt",
274
+ "rowCard": "On5p7W_rowCard",
275
+ "credentialDot": "On5p7W_credentialDot",
276
+ "rowTag": "On5p7W_rowTag",
277
+ "rowActions": "On5p7W_rowActions",
278
+ "rosterError": "On5p7W_rosterError",
279
+ "rows": "On5p7W_rows",
280
+ "rowHead": "On5p7W_rowHead",
281
+ "loginButton": "On5p7W_loginButton"
282
+ };
283
+ //#endregion
284
+ //#region ../local-agent/src/client/ProviderAuthBlock.tsx
285
+ /** Known harnesses the family plans to support; unregistered ones render as pending. */
286
+ const KNOWN_HARNESSES = [
287
+ {
288
+ id: "kimi",
289
+ label: "Kimi Code"
290
+ },
291
+ {
292
+ id: "codex",
293
+ label: "Codex"
294
+ },
295
+ {
296
+ id: "claude-code",
297
+ label: "Claude Code"
298
+ }
299
+ ];
300
+ /** How often a pending login's status is re-probed after the prompt surfaces. */
301
+ const LOGIN_POLL_MS = 4e3;
302
+ /** The first http(s) URL in a login prompt, if any. */
303
+ function parseLoginUrl(text) {
304
+ return /https?:\/\/\S+/.exec(text)?.[0];
305
+ }
306
+ /** Resolve a harness id to its display label for toast text. */
307
+ function harnessLabel(id) {
308
+ return KNOWN_HARNESSES.find((harness) => harness.id === id)?.label ?? id;
309
+ }
310
+ /**
311
+ * The main-view session: host 0.1.6-alpha.2 dropped SessionListState.current
312
+ * for the retainedBy.mainView count on each summary (the label is ui-session's
313
+ * declaration merge, read here as a plain duck-typed count so this package
314
+ * needs no ui-session edge), with the legacy field as the older-host fallback.
315
+ */
316
+ const mainSessionId = (list) => Object.values(list.byId ?? {}).find((session) => (session.retainedBy?.mainView ?? 0) > 0)?.id ?? list.current;
317
+ /**
318
+ * One harness's auth interactions, shared between the per-provider settings
319
+ * cards (the alpha.2 `plugins.bundle.config` pages and the 0.1.5
320
+ * `settings.plugin.item` tab): auth status through
321
+ * the read-only Remote channel and a web-login action that runs
322
+ * `/<harness> login`, surfaces the device-code prompt, and offers a link to
323
+ * the authorization page. The login command returns at prompt time while the
324
+ * CLI polls in the background, so a pending login's status keeps being
325
+ * re-probed until credentials land or the poll window expires. Status polls
326
+ * ride the Remote channel and emit no session events; only the user-initiated
327
+ * login/logout commands leave command nodes in the log.
328
+ * @param props - the harness, the injected query/command faces, and copy.
329
+ * @returns the block content (head row, prompts, and the login toast).
330
+ */
331
+ function ProviderAuthBlock({ harness, useSessions, status, runCommand, actions, t }) {
332
+ const sessionId = useSessions((state) => mainSessionId(state));
333
+ const [view, setView] = (0, react.useState)({ status: "checking" });
334
+ /** The login start time while the device-code login is still pending. */
335
+ const [pendingSince, setPendingSince] = (0, react.useState)(void 0);
336
+ /** A login-completion toast awaiting mount; seq forces a re-show for repeats. */
337
+ const [loginToast, setLoginToast] = (0, react.useState)(null);
338
+ const toastSeq = (0, react.useRef)(0);
339
+ /** OAuth code draft for a pty login awaiting a paste. */
340
+ const [codeDraft, setCodeDraft] = (0, react.useState)("");
341
+ const refresh = () => {
342
+ const id = harness.id;
343
+ setView((prev) => ({
344
+ ...prev,
345
+ status: "checking"
346
+ }));
347
+ status(id).then((probe) => {
348
+ const statusKind = probe === void 0 ? "unavailable" : probe.authenticated ? "authenticated" : "anonymous";
349
+ publishAuthStatus(id, statusKind);
350
+ const capabilities = probe === void 0 ? {} : {
351
+ loginable: probe.loginable ?? true,
352
+ logoutable: probe.logoutable ?? true,
353
+ ...probe.loginAwaitingCode === true ? { loginAwaitingCode: true } : {}
354
+ };
355
+ setView((prev) => statusKind === "authenticated" ? {
356
+ status: statusKind,
357
+ ...capabilities
358
+ } : {
359
+ ...prev,
360
+ status: statusKind,
361
+ ...capabilities
362
+ });
363
+ if (statusKind === "authenticated") setPendingSince((prev) => {
364
+ if (prev !== void 0) {
365
+ toastSeq.current += 1;
366
+ setLoginToast({ seq: toastSeq.current });
367
+ }
368
+ });
369
+ }).catch(() => {
370
+ publishAuthStatus(id, "unavailable");
371
+ setView((prev) => ({
372
+ ...prev,
373
+ status: "unavailable"
374
+ }));
375
+ });
376
+ };
377
+ (0, react.useEffect)(() => {
378
+ refresh();
379
+ }, [
380
+ harness.id,
381
+ status,
382
+ sessionId
383
+ ]);
384
+ (0, react.useEffect)(() => {
385
+ if (sessionId === void 0 || pendingSince === void 0) return;
386
+ const timer = window.setInterval(() => {
387
+ if (Date.now() - pendingSince > 3e5) {
388
+ setPendingSince(void 0);
389
+ return;
390
+ }
391
+ refresh();
392
+ }, LOGIN_POLL_MS);
393
+ return () => {
394
+ window.clearInterval(timer);
395
+ };
396
+ }, [
397
+ pendingSince,
398
+ status,
399
+ sessionId,
400
+ harness.id
401
+ ]);
402
+ const startLogin = () => {
403
+ if (sessionId === void 0) return;
404
+ runCommand(sessionId, `/${harness.id} login`).then((text) => {
405
+ const text2 = text ?? "";
406
+ const loginUrl = parseLoginUrl(text2);
407
+ setView({
408
+ status: "checking",
409
+ loginText: text2,
410
+ ...loginUrl !== void 0 && { loginUrl }
411
+ });
412
+ setPendingSince(Date.now());
413
+ }).catch(() => {
414
+ setView({ status: "unavailable" });
415
+ });
416
+ };
417
+ const signOut = () => {
418
+ if (sessionId === void 0) return;
419
+ runCommand(sessionId, `/${harness.id} logout`).then((text) => {
420
+ setView({
421
+ status: "checking",
422
+ loginText: text !== void 0 && text.trim() !== "" ? text : t("settings.loggedOut")
423
+ });
424
+ refresh();
425
+ }).catch(() => {
426
+ setView({ status: "unavailable" });
427
+ });
428
+ };
429
+ const statusKind = view.status;
430
+ const authenticated = statusKind === "authenticated";
431
+ const loginPending = pendingSince !== void 0;
432
+ const loginable = view.loginable ?? true;
433
+ const logoutable = view.logoutable ?? true;
434
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
435
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
436
+ className: ProviderAuthBlock_module_css_default.rowHead,
437
+ children: [
438
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
439
+ className: ProviderAuthBlock_module_css_default.rowIdentity,
440
+ children: [
441
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
442
+ className: ProviderAuthBlock_module_css_default.rowName,
443
+ children: harness.label
444
+ }),
445
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
446
+ className: `${ProviderAuthBlock_module_css_default.credentialDot} ${authenticated ? ProviderAuthBlock_module_css_default.dotOn : ProviderAuthBlock_module_css_default.dotOff}`,
447
+ role: "img",
448
+ "aria-label": t(authenticated ? "settings.authenticated" : "settings.notAuthenticated")
449
+ }),
450
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
451
+ className: ProviderAuthBlock_module_css_default.rowTag,
452
+ children: harness.id
453
+ })
454
+ ]
455
+ }),
456
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
457
+ className: ProviderAuthBlock_module_css_default.status,
458
+ children: [
459
+ statusKind === "checking" && t("loading"),
460
+ statusKind === "authenticated" && t("settings.authenticated"),
461
+ statusKind === "anonymous" && t("settings.notAuthenticated"),
462
+ statusKind === "unavailable" && t("error")
463
+ ]
464
+ }),
465
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
466
+ className: ProviderAuthBlock_module_css_default.rowActions,
467
+ children: [
468
+ authenticated && logoutable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
469
+ type: "button",
470
+ className: ProviderAuthBlock_module_css_default.logoutButton,
471
+ onClick: () => {
472
+ signOut();
473
+ },
474
+ children: t("settings.logout")
475
+ }),
476
+ loginable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
477
+ type: "button",
478
+ className: ProviderAuthBlock_module_css_default.loginButton,
479
+ disabled: loginPending || sessionId === void 0,
480
+ onClick: () => {
481
+ startLogin();
482
+ },
483
+ children: authenticated ? t("settings.reauthorize") : t("settings.login")
484
+ }),
485
+ actions
486
+ ]
487
+ })
488
+ ]
489
+ }),
490
+ view.loginText !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
491
+ className: ProviderAuthBlock_module_css_default.loginPrompt,
492
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
493
+ className: ProviderAuthBlock_module_css_default.promptText,
494
+ children: view.loginText
495
+ }), view.loginUrl !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
496
+ className: ProviderAuthBlock_module_css_default.openPage,
497
+ href: view.loginUrl,
498
+ target: "_blank",
499
+ rel: "noreferrer",
500
+ children: t("settings.openPage")
501
+ })]
502
+ }),
503
+ view.loginAwaitingCode === true && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
504
+ className: ProviderAuthBlock_module_css_default.loginPrompt,
505
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
506
+ className: ProviderAuthBlock_module_css_default.codeInput,
507
+ value: codeDraft,
508
+ placeholder: t("settings.pasteCode"),
509
+ onChange: (event) => {
510
+ setCodeDraft(event.currentTarget.value);
511
+ }
512
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
513
+ type: "button",
514
+ className: ProviderAuthBlock_module_css_default.loginButton,
515
+ disabled: codeDraft.trim() === "",
516
+ onClick: () => {
517
+ const code = codeDraft.trim();
518
+ if (code === "" || sessionId === void 0) return;
519
+ runCommand(sessionId, `/${harness.id} code ${code}`).then((text) => {
520
+ setView((prev) => {
521
+ const { loginAwaitingCode: _cleared, ...rest } = prev;
522
+ return {
523
+ ...rest,
524
+ loginText: text ?? ""
525
+ };
526
+ });
527
+ setCodeDraft("");
528
+ });
529
+ },
530
+ children: t("settings.submitCode")
531
+ })]
532
+ }),
533
+ loginToast !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
534
+ text: t("settings.loginSuccess", { harness: harnessLabel(harness.id) }),
535
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconCheckOutlineMedium, {}),
536
+ onDone: () => {
537
+ setLoginToast(null);
538
+ }
539
+ }, loginToast.seq)
540
+ ] });
541
+ }
542
+ //#endregion
543
+ //#region \0dsh-css:../local-agent/src/client/AuthStatusDot.module.css.mjs
544
+ const css$1 = ".OFjNUG_credentialDot{box-sizing:border-box;border-radius:50%;flex:none;width:8px;height:8px;display:inline-block}.OFjNUG_dotOn{background:var(--dsw-alias-state-success-primary)}.OFjNUG_dotOff{background:var(--dsw-alias-state-error-primary)}.OFjNUG_dotPending{background:var(--dsw-alias-fill-l3)}";
545
+ const tagId$1 = "@khorsheed/dsh-local-agent-dsh/AuthStatusDot.module.css";
546
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
547
+ const tag = document.createElement("style");
548
+ tag.dataset.plugin = "@khorsheed/dsh-local-agent-dsh";
549
+ tag.dataset.pluginCss = tagId$1;
550
+ tag.textContent = css$1;
551
+ document.head.appendChild(tag);
552
+ }
553
+ var AuthStatusDot_module_css_default = {
554
+ "dotOn": "OFjNUG_dotOn",
555
+ "dotOff": "OFjNUG_dotOff",
556
+ "credentialDot": "OFjNUG_credentialDot",
557
+ "dotPending": "OFjNUG_dotPending"
558
+ };
559
+ //#endregion
560
+ //#region ../local-agent/src/client/AuthStatusDot.tsx
561
+ /** One 8px status dot with an accessible label. */
562
+ function AuthStatusDot({ status, label }) {
563
+ const modifier = status === "authenticated" ? AuthStatusDot_module_css_default.dotOn : status === "anonymous" ? AuthStatusDot_module_css_default.dotOff : AuthStatusDot_module_css_default.dotPending;
564
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
565
+ className: `${AuthStatusDot_module_css_default.credentialDot} ${modifier}`,
566
+ role: "img",
567
+ "aria-label": label,
568
+ "data-auth-status": status
569
+ });
570
+ }
571
+ //#endregion
572
+ //#region \0dsh-css:src/client/SettingsCard.module.css.mjs
573
+ const css = "._3LBPpW_card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;transition:border-color .16s,background .16s}._3LBPpW_card:hover{border-color:var(--dsw-alias-label-dimmed)}._3LBPpW_cardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}._3LBPpW_header{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}._3LBPpW_header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}._3LBPpW_headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}._3LBPpW_nameRow{align-items:center;gap:6px;display:flex}._3LBPpW_name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}._3LBPpW_description{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}._3LBPpW_chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}._3LBPpW_chevronOpen{transform:rotate(180deg)}._3LBPpW_body{border-top:1px solid var(--dsw-alias-border-l2);flex-direction:column;gap:14px;margin:0 16px;padding:12px 0 8px;display:flex}._3LBPpW_block{flex-direction:column;gap:10px;display:flex}._3LBPpW_blockTitle{color:var(--dsw-alias-label-primary);align-items:center;gap:6px;margin:0;font-size:13px;font-weight:500;line-height:1.4;display:flex}._3LBPpW_info{appearance:none;color:var(--dsw-alias-label-tertiary);cursor:help;background:0 0;border:0;border-radius:50%;padding:0 2px;font-size:12px;line-height:1}._3LBPpW_info:hover,._3LBPpW_info:focus-visible{color:var(--dsw-alias-label-primary)}._3LBPpW_row{align-items:center;gap:12px;display:flex}._3LBPpW_rowLabel{color:var(--dsw-alias-label-primary);align-items:center;gap:4px;font-size:13px;line-height:1.4;display:inline-flex}._3LBPpW_switch{border:1px solid color-mix(in srgb, currentColor 30%, transparent);background:color-mix(in srgb, currentColor 12%, transparent);cursor:pointer;width:2.25rem;height:1.25rem;color:var(--dsw-alias-label-primary);border-radius:999px;flex:none;align-self:center;padding:.125rem;transition:background .12s}._3LBPpW_switch:disabled{opacity:.5;cursor:default}._3LBPpW_switchOn{background:color-mix(in srgb, var(--dsh-accent,#4f8cff) 70%, transparent)}._3LBPpW_knob{background:var(--dsh-surface,#fff);border-radius:999px;width:.9375rem;height:.9375rem;transition:transform .12s;display:block}._3LBPpW_switchOn ._3LBPpW_knob{transform:translate(.9375rem)}._3LBPpW_saved{color:var(--dsw-alias-state-success-primary);font-size:12px}._3LBPpW_errorText{color:var(--dsw-alias-state-error-primary);font-size:12px}._3LBPpW_hint{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5}._3LBPpW_modelInput{min-width:0;font:inherit;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);border-radius:8px;flex:1;padding:5px 8px;font-size:13px;line-height:1.4}._3LBPpW_modelInput::placeholder{color:var(--dsw-alias-label-caption);opacity:1}._3LBPpW_modelInput:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}._3LBPpW_modelInput:disabled{opacity:.5}._3LBPpW_modelField{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:8px;flex:1;align-items:center;min-width:0;display:flex;position:relative}._3LBPpW_modelField:focus-within{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}._3LBPpW_modelField ._3LBPpW_modelInput{background:0 0;border:0;border-radius:0}._3LBPpW_modelField ._3LBPpW_modelInput:focus-visible{outline:none}._3LBPpW_modelMenuButton{width:24px;height:24px;color:var(--dsw-alias-label-caption);cursor:pointer;background:0 0;border:0;border-radius:6px;flex:none;justify-content:center;align-items:center;margin-right:4px;display:flex}._3LBPpW_modelMenuButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}._3LBPpW_modelMenuButton:disabled{opacity:.5;cursor:default}._3LBPpW_modelMenuChevron{transition:transform .16s}._3LBPpW_modelMenuChevronOpen{transform:rotate(180deg)}._3LBPpW_modelMenu{z-index:10;border:1px solid var(--dsw-alias-border-l2-darkmode-thin,var(--dsw-alias-border-l2));background:var(--dsw-specific-menu,var(--dsw-alias-bg-layer-2,var(--dsw-alias-bg-layer-1,#fff)));min-width:160px;max-height:240px;backdrop-filter:var(--dsw-menu-backdrop-filter,blur(20px));box-shadow:var(--dsw-elevation-prominent,var(--dsw-shadow-lv3,0 0 1px 0 #0003, 0 0 4px 0 #00000005, 0 12px 32px 0 #00000014));border-radius:12px;flex-direction:column;padding:4px;display:flex;position:absolute;top:calc(100% + 6px);left:0;right:0;overflow-y:auto}._3LBPpW_modelMenuUp{top:auto;bottom:calc(100% + 6px)}._3LBPpW_modelItem,._3LBPpW_modelItemCurrent,._3LBPpW_modelItemDefault{color:var(--dsw-alias-label-primary);text-align:left;white-space:nowrap;cursor:pointer;background:0 0;border:none;border-radius:8px;align-items:center;gap:8px;padding:6px 10px;font-size:12px;line-height:18px;display:flex;overflow:hidden}._3LBPpW_modelItem:hover,._3LBPpW_modelItemCurrent:hover,._3LBPpW_modelItemDefault:hover{background:var(--dsw-alias-interactive-bg-hover)}._3LBPpW_modelItemCurrent{font-weight:560}._3LBPpW_modelItemLabel{text-overflow:ellipsis;flex:1;min-width:0;overflow:hidden}._3LBPpW_modelItemCheck{text-align:center;width:14px;color:var(--dsw-alias-label-secondary);flex:none}._3LBPpW_modelItemDefault{color:var(--dsw-alias-label-secondary);box-shadow:0 1px 0 var(--dsw-alias-border-l2);border-radius:8px 8px 0 0;margin-bottom:2px;padding-bottom:7px}._3LBPpW_modelSave{appearance:none;font:inherit;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:8px;flex:none;padding:5px 10px;font-size:13px;line-height:1.4}._3LBPpW_modelSave:hover:not(:disabled){border-color:var(--dsw-alias-label-dimmed)}._3LBPpW_modelSave:disabled{opacity:.5;cursor:default}";
574
+ const tagId = "@khorsheed/dsh-local-agent-dsh/SettingsCard.module.css";
575
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
576
+ const tag = document.createElement("style");
577
+ tag.dataset.plugin = "@khorsheed/dsh-local-agent-dsh";
578
+ tag.dataset.pluginCss = tagId;
579
+ tag.textContent = css;
580
+ document.head.appendChild(tag);
581
+ }
582
+ var SettingsCard_module_css_default = {
583
+ "cardOpen": "_3LBPpW_cardOpen",
584
+ "name": "_3LBPpW_name",
585
+ "chevronOpen": "_3LBPpW_chevronOpen",
586
+ "rowLabel": "_3LBPpW_rowLabel",
587
+ "modelMenuChevronOpen": "_3LBPpW_modelMenuChevronOpen",
588
+ "row": "_3LBPpW_row",
589
+ "body": "_3LBPpW_body",
590
+ "nameRow": "_3LBPpW_nameRow",
591
+ "modelField": "_3LBPpW_modelField",
592
+ "info": "_3LBPpW_info",
593
+ "blockTitle": "_3LBPpW_blockTitle",
594
+ "headText": "_3LBPpW_headText",
595
+ "modelItem": "_3LBPpW_modelItem",
596
+ "modelMenuChevron": "_3LBPpW_modelMenuChevron",
597
+ "description": "_3LBPpW_description",
598
+ "chevron": "_3LBPpW_chevron",
599
+ "modelInput": "_3LBPpW_modelInput",
600
+ "modelMenuUp": "_3LBPpW_modelMenuUp",
601
+ "modelItemCurrent": "_3LBPpW_modelItemCurrent",
602
+ "modelMenu": "_3LBPpW_modelMenu",
603
+ "modelItemDefault": "_3LBPpW_modelItemDefault",
604
+ "card": "_3LBPpW_card",
605
+ "header": "_3LBPpW_header",
606
+ "knob": "_3LBPpW_knob",
607
+ "switchOn": "_3LBPpW_switchOn",
608
+ "modelSave": "_3LBPpW_modelSave",
609
+ "hint": "_3LBPpW_hint",
610
+ "modelItemLabel": "_3LBPpW_modelItemLabel",
611
+ "modelItemCheck": "_3LBPpW_modelItemCheck",
612
+ "modelMenuButton": "_3LBPpW_modelMenuButton",
613
+ "block": "_3LBPpW_block",
614
+ "saved": "_3LBPpW_saved",
615
+ "switch": "_3LBPpW_switch",
616
+ "errorText": "_3LBPpW_errorText"
617
+ };
618
+ //#endregion
619
+ //#region src/client/SettingsCard.tsx
620
+ /** How many previously-saved model identifiers the input suggests. */
621
+ const RECENT_MODEL_LIMIT = 5;
622
+ /**
623
+ * The staged card state both settings surfaces share: the delegation,
624
+ * resident-mode, and model edits write through the bound settingsScope
625
+ * immediately (revision-fenced), so a flip takes effect on the next
626
+ * delegation round without a reload; the harness's model surface is fetched
627
+ * while the surface is active (the 0.1.5 card opened, the alpha.2 page
628
+ * mounted) and re-fetched after every save.
629
+ * @param active - whether the surface currently shows the body (gates the model-surface fetch).
630
+ * @param useSettings - the bound settings hook from the inject hooks compartment.
631
+ * @param scope - the bound settings scope the writes go through.
632
+ * @param harnessModel - the harness's memberless model surface read.
633
+ * @param t - copy.
634
+ */
635
+ function useCardState(active, useSettings, scope, harnessModel, t) {
636
+ const snapshot = useSettings((value) => value);
637
+ const [saved, setSaved] = (0, react.useState)(false);
638
+ const [error, setError] = (0, react.useState)(false);
639
+ const ready = snapshot.status === "ready" && snapshot.writable;
640
+ const enabled = snapshot.value?.enabled ?? false;
641
+ const live = snapshot.value?.live ?? false;
642
+ const storedModel = snapshot.value?.model ?? "";
643
+ const recentModels = snapshot.value?.recentModels ?? [];
644
+ const [modelDraft, setModelDraft] = (0, react.useState)(null);
645
+ const [modelSaved, setModelSaved] = (0, react.useState)(false);
646
+ const [modelError, setModelError] = (0, react.useState)(false);
647
+ const modelValue = modelDraft ?? storedModel;
648
+ const [modelInfo, setModelInfo] = (0, react.useState)(void 0);
649
+ (0, react.useEffect)(() => {
650
+ if (!active) return;
651
+ let stale = false;
652
+ harnessModel().then((info) => {
653
+ if (!stale) setModelInfo(info ?? null);
654
+ });
655
+ return () => {
656
+ stale = true;
657
+ };
658
+ }, [
659
+ active,
660
+ harnessModel,
661
+ modelSaved
662
+ ]);
663
+ /**
664
+ * Commit the model field. A blank value UNSETS the key rather than storing
665
+ * an empty string, so the field re-inherits the YAML composition base — and
666
+ * with no base, the harness is back to passing no model at all.
667
+ */
668
+ const saveModel = () => {
669
+ const next = modelValue.trim();
670
+ setModelSaved(false);
671
+ setModelError(false);
672
+ (next === "" ? scope.unset("model") : scope.set("model", next)).then(async () => {
673
+ if (next !== "") await scope.set("recentModels", [next, ...recentModels.filter((value) => value !== next)].slice(0, RECENT_MODEL_LIMIT));
674
+ setModelDraft(null);
675
+ setModelSaved(true);
676
+ }, () => {
677
+ setModelError(true);
678
+ });
679
+ };
680
+ const write = (field, value) => {
681
+ setSaved(false);
682
+ setError(false);
683
+ scope.set(field, value).then(() => {
684
+ setSaved(true);
685
+ }, () => {
686
+ setError(true);
687
+ });
688
+ };
689
+ const choices = modelInfo?.choices ?? recentModels;
690
+ const [modelMenuOpen, setModelMenuOpen] = (0, react.useState)(false);
691
+ const [modelMenuUp, setModelMenuUp] = (0, react.useState)(false);
692
+ const modelFieldRef = (0, react.useRef)(null);
693
+ (0, react.useEffect)(() => {
694
+ if (!modelMenuOpen) return;
695
+ const onPointerDown = (event) => {
696
+ if (modelFieldRef.current?.contains(event.target) === true) return;
697
+ setModelMenuOpen(false);
698
+ };
699
+ const onKeyDown = (event) => {
700
+ if (event.key === "Escape") setModelMenuOpen(false);
701
+ };
702
+ document.addEventListener("mousedown", onPointerDown);
703
+ document.addEventListener("keydown", onKeyDown);
704
+ return () => {
705
+ document.removeEventListener("mousedown", onPointerDown);
706
+ document.removeEventListener("keydown", onKeyDown);
707
+ };
708
+ }, [modelMenuOpen]);
709
+ const toggleModelMenu = () => {
710
+ if (!modelMenuOpen) {
711
+ const rect = modelFieldRef.current?.getBoundingClientRect();
712
+ if (rect !== void 0) {
713
+ const spaceBelow = window.innerHeight - rect.bottom;
714
+ setModelMenuUp(spaceBelow < 260 && rect.top > spaceBelow);
715
+ }
716
+ }
717
+ setModelMenuOpen((open) => !open);
718
+ };
719
+ return {
720
+ snapshot,
721
+ saved,
722
+ error,
723
+ ready,
724
+ enabled,
725
+ live,
726
+ storedModel,
727
+ modelValue,
728
+ modelSaved,
729
+ modelError,
730
+ choices,
731
+ modelMenuOpen,
732
+ modelMenuUp,
733
+ modelFieldRef,
734
+ modelPlaceholder: (modelInfo != null && modelInfo.cliDefault !== void 0 && modelInfo.cliDefault !== "" ? modelInfo.cliDefault : modelInfo != null && modelInfo.source === "cli-builtin" && modelInfo.effective !== void 0 && modelInfo.effective !== "" ? modelInfo.effective : modelInfo != null && modelInfo.lastObserved !== void 0 && modelInfo.lastObserved !== "" ? modelInfo.lastObserved : void 0) ?? t("model.placeholder"),
735
+ modelDefaultItem: modelInfo != null && modelInfo.cliDefault !== void 0 && modelInfo.cliDefault !== "" ? t("model.menuDefault.cli-config", { model: modelInfo.cliDefault }) : modelInfo != null && modelInfo.source === "cli-builtin" && modelInfo.effective !== void 0 && modelInfo.effective !== "" ? t("model.menuDefault.cli-builtin", { model: modelInfo.effective }) : modelInfo != null && modelInfo.lastObserved !== void 0 && modelInfo.lastObserved !== "" ? t("model.menuDefault.last-observed", { model: modelInfo.lastObserved }) : t("model.menuDefault"),
736
+ saveModel,
737
+ write,
738
+ toggleModelMenu,
739
+ setModelDraft,
740
+ setModelMenuOpen
741
+ };
742
+ }
743
+ /** The card body both settings surfaces share: the auth block, the delegation switch, the default-model block, and the resident-mode block. */
744
+ function CardBody({ state, auth, authT, renderModelPicker, useSessions, t }) {
745
+ const { snapshot, saved, error, ready, enabled, live, storedModel, modelValue, modelSaved, modelError, choices, modelMenuOpen, modelMenuUp, modelFieldRef, modelPlaceholder, modelDefaultItem, saveModel, write, toggleModelMenu, setModelDraft, setModelMenuOpen } = state;
746
+ const modelInputElement = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
747
+ type: "text",
748
+ className: SettingsCard_module_css_default.modelInput,
749
+ value: modelValue,
750
+ placeholder: modelPlaceholder,
751
+ "aria-label": t("model.title"),
752
+ disabled: !ready,
753
+ onChange: (event) => {
754
+ setModelDraft(event.target.value);
755
+ }
756
+ });
757
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
758
+ className: SettingsCard_module_css_default.body,
759
+ children: [
760
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
761
+ className: SettingsCard_module_css_default.block,
762
+ children: [
763
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
764
+ className: SettingsCard_module_css_default.blockTitle,
765
+ children: t("auth.title")
766
+ }),
767
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProviderAuthBlock, {
768
+ harness: {
769
+ id: "dsh",
770
+ label: "dsh"
771
+ },
772
+ useSessions,
773
+ status: auth.status,
774
+ runCommand: auth.runCommand,
775
+ t: authT
776
+ }),
777
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
778
+ className: SettingsCard_module_css_default.hint,
779
+ children: t("auth.host")
780
+ })
781
+ ]
782
+ }),
783
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
784
+ className: SettingsCard_module_css_default.block,
785
+ children: [
786
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
787
+ className: SettingsCard_module_css_default.blockTitle,
788
+ children: t("enable.title")
789
+ }),
790
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
791
+ className: SettingsCard_module_css_default.row,
792
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
793
+ className: SettingsCard_module_css_default.rowLabel,
794
+ children: t("enable.switch")
795
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
796
+ type: "button",
797
+ role: "switch",
798
+ "aria-checked": enabled,
799
+ "aria-label": t("enable.switch.aria"),
800
+ className: enabled ? `${SettingsCard_module_css_default.switch} ${SettingsCard_module_css_default.switchOn}` : SettingsCard_module_css_default.switch,
801
+ disabled: !ready,
802
+ onClick: () => {
803
+ write("enabled", !enabled);
804
+ },
805
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: SettingsCard_module_css_default.knob })
806
+ })]
807
+ }),
808
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
809
+ className: SettingsCard_module_css_default.hint,
810
+ children: t(enabled ? "enable.on" : "enable.off")
811
+ })
812
+ ]
813
+ }),
814
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
815
+ className: SettingsCard_module_css_default.block,
816
+ children: [
817
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("h3", {
818
+ className: SettingsCard_module_css_default.blockTitle,
819
+ children: [t("model.title"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
820
+ label: t("model.info"),
821
+ side: "bottom",
822
+ maxWidth: 360,
823
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
824
+ type: "button",
825
+ className: SettingsCard_module_css_default.info,
826
+ "aria-label": t("model.info.aria"),
827
+ children: "ⓘ"
828
+ })
829
+ })]
830
+ }),
831
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
832
+ className: SettingsCard_module_css_default.row,
833
+ children: [renderModelPicker?.({
834
+ value: modelValue,
835
+ onChange: setModelDraft,
836
+ disabled: !ready,
837
+ defaultLabel: modelDefaultItem
838
+ }) ?? (choices.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
839
+ className: SettingsCard_module_css_default.modelField,
840
+ ref: modelFieldRef,
841
+ children: [
842
+ modelInputElement,
843
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
844
+ type: "button",
845
+ className: SettingsCard_module_css_default.modelMenuButton,
846
+ "aria-label": t("model.menu"),
847
+ "aria-haspopup": "menu",
848
+ "aria-expanded": modelMenuOpen,
849
+ disabled: !ready,
850
+ onClick: () => {
851
+ toggleModelMenu();
852
+ },
853
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconChevronDownOutlineMedium, { className: modelMenuOpen ? `${SettingsCard_module_css_default.modelMenuChevron} ${SettingsCard_module_css_default.modelMenuChevronOpen}` : SettingsCard_module_css_default.modelMenuChevron })
854
+ }),
855
+ modelMenuOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
856
+ className: modelMenuUp ? `${SettingsCard_module_css_default.modelMenu} ${SettingsCard_module_css_default.modelMenuUp}` : SettingsCard_module_css_default.modelMenu,
857
+ role: "menu",
858
+ "aria-label": t("model.menu"),
859
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
860
+ type: "button",
861
+ role: "menuitemradio",
862
+ "aria-checked": modelValue.trim() === "",
863
+ className: modelValue.trim() === "" ? `${SettingsCard_module_css_default.modelItemDefault} ${SettingsCard_module_css_default.modelItemCurrent}` : SettingsCard_module_css_default.modelItemDefault,
864
+ onClick: () => {
865
+ setModelDraft("");
866
+ setModelMenuOpen(false);
867
+ },
868
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
869
+ className: SettingsCard_module_css_default.modelItemLabel,
870
+ children: modelDefaultItem
871
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
872
+ className: SettingsCard_module_css_default.modelItemCheck,
873
+ "aria-hidden": true,
874
+ children: modelValue.trim() === "" ? "✓" : ""
875
+ })]
876
+ }), choices.map((choice) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
877
+ type: "button",
878
+ role: "menuitemradio",
879
+ "aria-checked": choice === modelValue,
880
+ className: choice === modelValue ? SettingsCard_module_css_default.modelItemCurrent : SettingsCard_module_css_default.modelItem,
881
+ onClick: () => {
882
+ setModelDraft(choice);
883
+ setModelMenuOpen(false);
884
+ },
885
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
886
+ className: SettingsCard_module_css_default.modelItemLabel,
887
+ children: choice
888
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
889
+ className: SettingsCard_module_css_default.modelItemCheck,
890
+ "aria-hidden": true,
891
+ children: choice === modelValue ? "✓" : ""
892
+ })]
893
+ }, choice))]
894
+ })
895
+ ]
896
+ }) : modelInputElement), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
897
+ type: "button",
898
+ className: SettingsCard_module_css_default.modelSave,
899
+ disabled: !ready || modelValue.trim() === storedModel,
900
+ onClick: () => {
901
+ saveModel();
902
+ },
903
+ children: t("model.save")
904
+ })]
905
+ }),
906
+ modelSaved && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
907
+ className: SettingsCard_module_css_default.saved,
908
+ children: t("model.applied")
909
+ }),
910
+ modelError && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
911
+ className: SettingsCard_module_css_default.errorText,
912
+ children: t("model.error")
913
+ })
914
+ ]
915
+ }),
916
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
917
+ className: SettingsCard_module_css_default.block,
918
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
919
+ className: SettingsCard_module_css_default.row,
920
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
921
+ className: SettingsCard_module_css_default.rowLabel,
922
+ children: [t("live.title"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
923
+ label: t("live.info"),
924
+ side: "bottom",
925
+ maxWidth: 360,
926
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
927
+ type: "button",
928
+ className: SettingsCard_module_css_default.info,
929
+ "aria-label": t("live.info.aria"),
930
+ children: "ⓘ"
931
+ })
932
+ })]
933
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
934
+ type: "button",
935
+ role: "switch",
936
+ "aria-checked": live,
937
+ "aria-label": t("live.title"),
938
+ className: live ? `${SettingsCard_module_css_default.switch} ${SettingsCard_module_css_default.switchOn}` : SettingsCard_module_css_default.switch,
939
+ disabled: !ready,
940
+ onClick: () => {
941
+ write("live", !live);
942
+ },
943
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: SettingsCard_module_css_default.knob })
944
+ })]
945
+ }), snapshot.status === "unavailable" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
946
+ className: SettingsCard_module_css_default.hint,
947
+ children: t("live.unavailable")
948
+ })]
949
+ }),
950
+ saved && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
951
+ className: SettingsCard_module_css_default.saved,
952
+ children: t("live.applied")
953
+ }),
954
+ error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
955
+ className: SettingsCard_module_css_default.errorText,
956
+ children: t("live.error")
957
+ })
958
+ ]
959
+ });
960
+ }
961
+ /**
962
+ * The dsh harness settings card in the 0.1.5 plugin configuration tab: the
963
+ * same collapsible chrome the official plugin cards use, re-implemented
964
+ * locally (the bundle-purity gate forbids value-importing the official chrome
965
+ * — the context-guard pattern), with the at-a-glance credential dot in the
966
+ * header. Explanation copy lives in hover/focus bubbles behind ⓘ anchors —
967
+ * the card's rows stay one line each.
968
+ * @param props - the injected scope/auth faces and copy.
969
+ * @returns the card.
970
+ */
971
+ function DshSettingsCard(props) {
972
+ const { useSettings, scope, auth, authT, renderModelPicker, harnessModel, useSessions, t } = props;
973
+ const [open, setOpen] = (0, react.useState)(false);
974
+ const state = useCardState(open, useSettings, scope, harnessModel, t);
975
+ const authStatus = useHarnessAuthStatus("dsh", auth.status);
976
+ const dotLabel = authT(authStatus === "authenticated" ? "settings.authenticated" : authStatus === "anonymous" ? "settings.notAuthenticated" : authStatus === "checking" ? "loading" : "error");
977
+ const title = t("card.title");
978
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
979
+ className: open ? `${SettingsCard_module_css_default.card} ${SettingsCard_module_css_default.cardOpen}` : SettingsCard_module_css_default.card,
980
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
981
+ type: "button",
982
+ className: SettingsCard_module_css_default.header,
983
+ "aria-expanded": open,
984
+ "aria-label": `${t(open ? "card.collapse" : "card.expand")}: ${title}`,
985
+ onClick: () => {
986
+ setOpen(!open);
987
+ },
988
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
989
+ className: SettingsCard_module_css_default.headText,
990
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
991
+ className: SettingsCard_module_css_default.nameRow,
992
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
993
+ className: SettingsCard_module_css_default.name,
994
+ children: title
995
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AuthStatusDot, {
996
+ status: authStatus,
997
+ label: dotLabel
998
+ })]
999
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1000
+ className: SettingsCard_module_css_default.description,
1001
+ children: t("card.description")
1002
+ })]
1003
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconChevronDownOutlineMedium, { className: open ? `${SettingsCard_module_css_default.chevron} ${SettingsCard_module_css_default.chevronOpen}` : SettingsCard_module_css_default.chevron })]
1004
+ }), open && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CardBody, {
1005
+ state,
1006
+ auth,
1007
+ authT,
1008
+ renderModelPicker,
1009
+ useSessions,
1010
+ t
1011
+ })]
1012
+ });
1013
+ }
1014
+ /**
1015
+ * The bundle's configuration on the alpha.2 Plugins page
1016
+ * (`plugins.bundle.config`, keyed by package name): the page draws the title,
1017
+ * the icon, and the crumb itself, so the entry renders the one-liner for the
1018
+ * `summary` view and the bare form — with its own save control — for `page`.
1019
+ * @param props - the owner view, the injected scope/auth faces, and copy.
1020
+ * @returns the entry.
1021
+ */
1022
+ function DshBundleConfig({ view, useSettings, scope, auth, authT, renderModelPicker, harnessModel, useSessions, t }) {
1023
+ const state = useCardState(view === "page", useSettings, scope, harnessModel, t);
1024
+ if (view === "summary") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1025
+ className: SettingsCard_module_css_default.description,
1026
+ children: t("card.description")
1027
+ });
1028
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CardBody, {
1029
+ state,
1030
+ auth,
1031
+ authT,
1032
+ renderModelPicker,
1033
+ useSessions,
1034
+ t
1035
+ });
1036
+ }
1037
+ //#endregion
1038
+ //#region src/client/index.ts
1039
+ /**
1040
+ * This bundle's package name — the key the Plugins page dispatches
1041
+ * `plugins.bundle.config` entries on (identity triangle: cordis.patch.yml,
1042
+ * tsdown.config.ts, invariant.ts).
1043
+ */
1044
+ const PACKAGE_NAME = "@khorsheed/dsh-local-agent-dsh";
1045
+ /**
1046
+ * Required services: slot registry, command Remote, and locale registry. The
1047
+ * settings scope is NOT a static inject — its service name differs per host
1048
+ * line (`settingsScope` on 0.1.5, `configForms` on rc.1), so a static entry
1049
+ * would pend the whole client plugin on the other line; bindSettingsScope
1050
+ * probes the serving one instead (the settings client package is a declared
1051
+ * client inject, so one of them is always already up).
1052
+ */
1053
+ const inject = [
1054
+ "slots",
1055
+ "remote",
1056
+ "remote.commands",
1057
+ "locale"
1058
+ ];
1059
+ /**
1060
+ * Client plugin body: register the dictionaries and the dsh settings surfaces
1061
+ * bound to the local-agent-dsh namespace.
1062
+ * @param ctx - client root context.
1063
+ */
1064
+ function apply(ctx) {
1065
+ ctx.effect(() => ctx.locale.register(NS, {
1066
+ zh,
1067
+ en
1068
+ }), "local-agent-dsh: dictionaries");
1069
+ const scope = bindSettingsScope(ctx, "local-agent-dsh");
1070
+ const authT = ctx.locale.bind("local-agent");
1071
+ const gateway = () => ctx.get("remote.localAgentGateway");
1072
+ const cardInject = () => ({
1073
+ scope,
1074
+ hooks: { settings: scope },
1075
+ authT,
1076
+ renderModelPicker: (props) => ctx.get("localAgentUi")?.renderHarnessModelPicker("dsh", props),
1077
+ harnessModel: () => gateway()?.harnessModel("dsh").then((result) => result.ok ? result.value : void 0) ?? Promise.resolve(void 0),
1078
+ auth: {
1079
+ status: (name) => gateway()?.status(name, void 0).then((result) => result.ok ? result.value : void 0) ?? Promise.resolve(void 0),
1080
+ runCommand: (sessionId, line) => ctx.remote.commands.execute(sessionId, line, []).then((result) => result.ok ? result.value?.result.text : void 0)
1081
+ }
1082
+ });
1083
+ ctx.slots.inject("plugins.bundle.config", () => ctx.slots.register({
1084
+ name: "plugins.bundle.config",
1085
+ key: PACKAGE_NAME,
1086
+ locale: NS,
1087
+ inject: cardInject
1088
+ }, DshBundleConfig));
1089
+ ctx.slots.inject("plugins.row.config", () => ctx.slots.register({
1090
+ name: "plugins.row.config",
1091
+ key: "@khorsheed/dsh-bundle-local-agent#local-agent-dsh",
1092
+ locale: NS,
1093
+ inject: cardInject
1094
+ }, DshBundleConfig));
1095
+ const legacy = ctx.slots;
1096
+ legacy.inject("settings.plugin.item", () => legacy.register({
1097
+ name: "settings.plugin.item",
1098
+ key: "local-agent-dsh",
1099
+ locale: NS,
1100
+ inject: cardInject
1101
+ }, DshSettingsCard));
1102
+ }
1103
+ //#endregion
1104
+ exports.DshBundleConfig = DshBundleConfig;
1105
+ exports.DshSettingsCard = DshSettingsCard;
1106
+ exports.NS = NS;
1107
+ exports.apply = apply;
1108
+ exports.en = en;
1109
+ exports.inject = inject;
1110
+ exports.zh = zh;
1111
+ return module.exports;
1112
+ }
1113
+ });
1114
+
1115
+ //# sourceMappingURL=client.js.map