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