@huanlin/dsh-plugin-yet-another-subagent 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client.js +1578 -1578
- package/lib/index.js +5487 -5487
- package/lib/types/client/SettingsPage.d.ts +49 -0
- package/lib/types/client/SubagentCard.d.ts +56 -0
- package/lib/types/client/SubagentTreeView.d.ts +63 -0
- package/lib/types/client/index.d.ts +29 -0
- package/lib/types/client/locales.d.ts +13 -0
- package/lib/types/index.d.ts +42 -0
- package/lib/types/invariant.d.ts +16 -0
- package/lib/types/profile-store.d.ts +59 -0
- package/lib/types/projection.d.ts +103 -0
- package/lib/types/repair.d.ts +49 -0
- package/lib/types/rpc.d.ts +59 -0
- package/lib/types/tool-factory.d.ts +45 -0
- package/lib/types/types.d.ts +116 -0
- package/package.json +4 -3
package/lib/client.js
CHANGED
|
@@ -1,1579 +1,1579 @@
|
|
|
1
|
-
window.__ModuleLoader__.load({
|
|
2
|
-
id: "@huanlin/dsh-plugin-yet-another-subagent",
|
|
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 react_jsx_runtime = require("react/jsx-runtime");
|
|
9
|
-
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
10
|
-
//#region \0dsh-css:D:\Projects\deepseek-harness\yet-another-subagent\src\client\SubagentCard.module.css.mjs
|
|
11
|
-
const css$2 = "/* SubagentCard toolview: a proper card with border, padding, and rounded\n * corners. Three display branches: running (spinner), continuable (live\n * child stats), foreground completed (output preview).\n *\n * Every color resolves through a --dsw-alias-* token (no literal colors). */\n\n.d8sln8w_card {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 10px 12px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n background: var(--dsw-alias-bg-layer-1);\n transition: border-color 0.15s ease;\n}\n\n.d8sln8w_card[data-clickable] {\n cursor: pointer;\n}\n\n.d8sln8w_card[data-clickable]:hover {\n border-color: var(--dsw-alias-brand-primary);\n}\n\n/* ---- Header: state dot + title + state badge ---- */\n\n.d8sln8w_header {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n}\n\n.d8sln8w_stateDot {\n flex: none;\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: var(--dsw-alias-label-caption);\n transition: background 0.15s ease;\n}\n\n/* Running: pulsing brand-color dot */\n.d8sln8w_card[data-state='running'] .d8sln8w_stateDot,\n.d8sln8w_card[data-state='child-running'] .d8sln8w_stateDot {\n background: var(--dsw-alias-brand-primary);\n animation: dsh-ya-subagent-pulse 1.4s ease-in-out infinite;\n}\n\n/* Completed: success green */\n.d8sln8w_card[data-state='completed'] .d8sln8w_stateDot {\n background: var(--dsw-alias-state-success-primary);\n}\n\n/* Child idle: success green (turn done, conversation alive) */\n.d8sln8w_card[data-state='child-idle'] .d8sln8w_stateDot {\n background: var(--dsw-alias-state-success-primary);\n}\n\n@keyframes dsh-ya-subagent-pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.4; }\n}\n\n.d8sln8w_title {\n flex: 1 1 auto;\n font-size: 13px;\n font-weight: 500;\n line-height: 20px;\n color: var(--dsw-alias-label-primary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.d8sln8w_stateBadge {\n flex: none;\n font-size: 11px;\n line-height: 18px;\n padding: 0 6px;\n border-radius: 4px;\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-tertiary);\n}\n\n/* Running / child-running: shimmer sweep over the state badge text,\n * mirroring DSH's \"Deep diving...\" turn-status effect. The pill background\n * is dropped so background-clip: text can show the gradient through the glyphs. */\n.d8sln8w_card[data-state='running'] .d8sln8w_stateBadge,\n.d8sln8w_card[data-state='child-running'] .d8sln8w_stateBadge {\n padding: 0;\n background: linear-gradient(\n 90deg,\n var(--dsw-static-deepseek-500) 0%,\n var(--dsw-static-deepseek-500) 40%,\n var(--dsw-static-deepseek-200) 50%,\n var(--dsw-static-deepseek-500) 60%,\n var(--dsw-static-deepseek-500) 100%\n );\n background-position: 100% 0;\n background-size: 250% 100%;\n background-clip: text;\n color: transparent;\n -webkit-background-clip: text;\n -webkit-text-fill-color: transparent;\n animation: dsh-ya-subagent-shimmer 1.8s linear infinite;\n}\n\n@keyframes dsh-ya-subagent-shimmer {\n to {\n background-position: 0 0;\n }\n}\n\n.d8sln8w_card[data-state='completed'] .d8sln8w_stateBadge,\n.d8sln8w_card[data-state='child-idle'] .d8sln8w_stateBadge {\n color: var(--dsw-alias-state-success-primary);\n}\n\n/* ---- Body: stats or output preview ---- */\n\n.d8sln8w_body {\n min-height: 18px;\n}\n\n.d8sln8w_stats {\n display: flex;\n align-items: center;\n gap: 12px;\n font-size: 11px;\n line-height: 16px;\n font-variant-numeric: tabular-nums;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.d8sln8w_stat {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n}\n\n.d8sln8w_activity {\n margin-top: 4px;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-secondary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.d8sln8w_output {\n display: block;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-secondary);\n overflow: hidden;\n display: -webkit-box;\n -webkit-line-clamp: 3;\n -webkit-box-orient: vertical;\n}\n\n/* ---- Footer: click hint ---- */\n\n.d8sln8w_footer {\n font-size: 11px;\n line-height: 16px;\n color: var(--dsw-alias-label-dimmed);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .d8sln8w_card[data-state='running'] .d8sln8w_stateDot,\n .d8sln8w_card[data-state='child-running'] .d8sln8w_stateDot {\n animation: none;\n }\n .d8sln8w_card[data-state='running'] .d8sln8w_stateBadge,\n .d8sln8w_card[data-state='child-running'] .d8sln8w_stateBadge {\n background-position: 0 0;\n background-size: 100% 100%;\n animation: none;\n }\n}\n";
|
|
12
|
-
const classMap$2 = {
|
|
13
|
-
"card": "d8sln8w_card",
|
|
14
|
-
"header": "d8sln8w_header",
|
|
15
|
-
"stateDot": "d8sln8w_stateDot",
|
|
16
|
-
"title": "d8sln8w_title",
|
|
17
|
-
"stateBadge": "d8sln8w_stateBadge",
|
|
18
|
-
"body": "d8sln8w_body",
|
|
19
|
-
"stats": "d8sln8w_stats",
|
|
20
|
-
"stat": "d8sln8w_stat",
|
|
21
|
-
"activity": "d8sln8w_activity",
|
|
22
|
-
"output": "d8sln8w_output",
|
|
23
|
-
"footer": "d8sln8w_footer"
|
|
24
|
-
};
|
|
25
|
-
const tagId$2 = "@huanlin/dsh-plugin-yet-another-subagent/SubagentCard.module.css";
|
|
26
|
-
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
|
|
27
|
-
const tag = document.createElement("style");
|
|
28
|
-
tag.dataset.plugin = "@huanlin/dsh-plugin-yet-another-subagent";
|
|
29
|
-
tag.dataset.pluginCss = tagId$2;
|
|
30
|
-
tag.textContent = css$2;
|
|
31
|
-
document.head.appendChild(tag);
|
|
32
|
-
}
|
|
33
|
-
//#endregion
|
|
34
|
-
//#region src/client/SubagentCard.tsx
|
|
35
|
-
/**
|
|
36
|
-
* SubagentCard — the model-facing toolcall card for the `subagent` tool.
|
|
37
|
-
*
|
|
38
|
-
* Three display branches:
|
|
39
|
-
* 1. **Running** (block is `RunningToolCall`): the tool call is in flight.
|
|
40
|
-
* Show "running" with a spinner dot; no child session to subscribe to.
|
|
41
|
-
* 2. **Continuable settled** (result text matches `started <label>
|
|
42
|
-
* subagent <id>`): subscribe to the child's `yaSubagentProgress`
|
|
43
|
-
* projection for live toolcall/token counts; clickable to open.
|
|
44
|
-
* 3. **Foreground settled** (result text is the child's output): the
|
|
45
|
-
* one-shot child has completed; show "completed" with an output
|
|
46
|
-
* preview. No child session survives.
|
|
47
|
-
*
|
|
48
|
-
* @module @huanlin/dsh-plugin-yet-another-subagent/client/SubagentCard
|
|
49
|
-
*/
|
|
50
|
-
/** Parse `block.content` for:
|
|
51
|
-
* - background: `started background subagent task <taskId>`
|
|
52
|
-
* - continuable: `started <profileLabel> subagent <subagentId>`
|
|
53
|
-
* - foreground: `completed <profileLabel> subagent <runId>\n<output>`
|
|
54
|
-
* - foreground (legacy): plain output text (no embedded id, not clickable)
|
|
55
|
-
*/
|
|
56
|
-
function parseResult(block) {
|
|
57
|
-
if (!("kind" in block)) return {};
|
|
58
|
-
const texts = [];
|
|
59
|
-
for (const item of block.content) {
|
|
60
|
-
if (item.type !== "text") continue;
|
|
61
|
-
if (item.text.match(/^started background subagent task \S+$/) !== null) return {};
|
|
62
|
-
const continuableMatch = item.text.match(/^started (\S+) subagent (\S+)$/);
|
|
63
|
-
if (continuableMatch !== null && continuableMatch[1] !== void 0 && continuableMatch[2] !== void 0) return {
|
|
64
|
-
profileLabel: continuableMatch[1],
|
|
65
|
-
subagentId: continuableMatch[2],
|
|
66
|
-
continuable: true
|
|
67
|
-
};
|
|
68
|
-
const foregroundMatch = item.text.match(/^completed (\S+) subagent (\S+)\n([\s\S]*)$/);
|
|
69
|
-
if (foregroundMatch !== null && foregroundMatch[1] !== void 0 && foregroundMatch[2] !== void 0 && foregroundMatch[3] !== void 0) return {
|
|
70
|
-
profileLabel: foregroundMatch[1],
|
|
71
|
-
subagentId: foregroundMatch[2],
|
|
72
|
-
continuable: false,
|
|
73
|
-
output: foregroundMatch[3]
|
|
74
|
-
};
|
|
75
|
-
texts.push(item.text);
|
|
76
|
-
}
|
|
77
|
-
const output = texts.join("").trim();
|
|
78
|
-
return output !== "" ? { output } : {};
|
|
79
|
-
}
|
|
80
|
-
/** Extract the `profile` parameter from a running or settled block's raw arguments. */
|
|
81
|
-
function readProfileArg(block) {
|
|
82
|
-
const argsRaw = "argsRaw" in block ? block.argsRaw : block.call?.argsRaw;
|
|
83
|
-
if (argsRaw === void 0) return void 0;
|
|
84
|
-
try {
|
|
85
|
-
const parsed = JSON.parse(argsRaw);
|
|
86
|
-
return typeof parsed.profile === "string" ? parsed.profile : void 0;
|
|
87
|
-
} catch {
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
/** Truncate a foreground output preview to a reasonable card line. */
|
|
92
|
-
function truncate(text, max) {
|
|
93
|
-
const single = text.replace(/\s+/g, " ").trim();
|
|
94
|
-
return single.length > max ? single.slice(0, max) + "…" : single;
|
|
95
|
-
}
|
|
96
|
-
/** Format token totals compactly: >=1000 uses "k", <1000 uses plain count. */
|
|
97
|
-
function formatTokens$1(tokens, t) {
|
|
98
|
-
if (tokens === void 0) return `0 ${t("card.tokens")}`;
|
|
99
|
-
const total = tokens.input + tokens.output + tokens.cacheRead + tokens.cacheWrite + tokens.reasoning;
|
|
100
|
-
return `${total >= 1e3 ? `${(total / 1e3).toFixed(1)}k` : String(total)} ${t("card.tokens")}`;
|
|
101
|
-
}
|
|
102
|
-
/**
|
|
103
|
-
* Subscribe to a child session's `yaSubagentProgress` projection through the
|
|
104
|
-
* sessions binding face. Returns `undefined` while the binding is absent or
|
|
105
|
-
* the projection has not pushed yet.
|
|
106
|
-
*/
|
|
107
|
-
function useChildProgress$1(sessions, childId) {
|
|
108
|
-
const [progress, setProgress] = (0, react.useState)(void 0);
|
|
109
|
-
(0, react.useEffect)(() => {
|
|
110
|
-
if (childId === void 0) return;
|
|
111
|
-
const binding = sessions.binding(childId);
|
|
112
|
-
if (binding === void 0) return;
|
|
113
|
-
const face = binding.session.projections.faceOf("yaSubagentProgress");
|
|
114
|
-
if (face === void 0) return;
|
|
115
|
-
const snapshot = face.getSnapshot();
|
|
116
|
-
setProgress(snapshot ?? void 0);
|
|
117
|
-
return face.subscribe(() => {
|
|
118
|
-
const next = face.getSnapshot();
|
|
119
|
-
setProgress(next ?? void 0);
|
|
120
|
-
});
|
|
121
|
-
}, [sessions, childId]);
|
|
122
|
-
return progress;
|
|
123
|
-
}
|
|
124
|
-
/**
|
|
125
|
-
* Subscribe to the parent session's `subagentProfile` projection to resolve
|
|
126
|
-
* a running call's childId (before the result text embeds it). Returns the
|
|
127
|
-
* childId for this callId, or undefined.
|
|
128
|
-
*/
|
|
129
|
-
function useChildIdFromProjection(sessions, sessionId, callId) {
|
|
130
|
-
const [childId, setChildId] = (0, react.useState)(void 0);
|
|
131
|
-
(0, react.useEffect)(() => {
|
|
132
|
-
const binding = sessions.binding(sessionId);
|
|
133
|
-
if (binding === void 0) return;
|
|
134
|
-
const face = binding.session.projections.faceOf("subagentProfile");
|
|
135
|
-
if (face === void 0) return;
|
|
136
|
-
const read = () => {
|
|
137
|
-
return face.getSnapshot()?.calls?.[callId];
|
|
138
|
-
};
|
|
139
|
-
setChildId(read());
|
|
140
|
-
return face.subscribe(() => setChildId(read()));
|
|
141
|
-
}, [
|
|
142
|
-
sessions,
|
|
143
|
-
sessionId,
|
|
144
|
-
callId
|
|
145
|
-
]);
|
|
146
|
-
return childId;
|
|
147
|
-
}
|
|
148
|
-
/**
|
|
149
|
-
* Render one `subagent` tool call as a compact live card.
|
|
150
|
-
* @param props - keyed toolview payload + locale seat + sessions inject.
|
|
151
|
-
* @returns the dedicated subagent card.
|
|
152
|
-
*/
|
|
153
|
-
function SubagentCard({ block, callId, toolName, sessionId, sessions, profileLabelOf, t }) {
|
|
154
|
-
const result = parseResult(block);
|
|
155
|
-
const projectedChildId = useChildIdFromProjection(sessions, sessionId, callId);
|
|
156
|
-
const childId = result.subagentId ?? projectedChildId;
|
|
157
|
-
const profileId = readProfileArg(block);
|
|
158
|
-
const profileLabel = result.profileLabel ?? (profileId !== void 0 ? profileLabelOf(profileId) : void 0) ?? toolName;
|
|
159
|
-
const progress = useChildProgress$1(sessions, childId);
|
|
160
|
-
const isRunning = !("kind" in block);
|
|
161
|
-
const isContinuable = childId !== void 0;
|
|
162
|
-
const liveState = progress?.state;
|
|
163
|
-
const cardState = isRunning ? "running" : isContinuable ? liveState === "running" ? "child-running" : "child-idle" : "completed";
|
|
164
|
-
const toolCallCount = progress?.toolCallCount ?? 0;
|
|
165
|
-
const clickable = childId !== void 0;
|
|
166
|
-
const openChild = () => {
|
|
167
|
-
if (childId === void 0) return;
|
|
168
|
-
const parentId = sessionId;
|
|
169
|
-
const retained = sessions.subagentAddress?.(childId);
|
|
170
|
-
if (retained !== void 0) try {
|
|
171
|
-
sessions.openSubagent(retained);
|
|
172
|
-
return;
|
|
173
|
-
} catch {}
|
|
174
|
-
for (const mode of ["continuable", "one-shot"]) try {
|
|
175
|
-
sessions.openSubagent({
|
|
176
|
-
parentSessionId: parentId,
|
|
177
|
-
childSessionId: childId,
|
|
178
|
-
mode
|
|
179
|
-
});
|
|
180
|
-
return;
|
|
181
|
-
} catch {}
|
|
182
|
-
sessions.refreshSubagents(parentId).then(() => {
|
|
183
|
-
const r = sessions.subagentAddress?.(childId);
|
|
184
|
-
if (r !== void 0) try {
|
|
185
|
-
sessions.openSubagent(r);
|
|
186
|
-
} catch (e) {
|
|
187
|
-
console.error("[ya-subagent] openSubagent failed after refresh:", e);
|
|
188
|
-
}
|
|
189
|
-
else console.error("[ya-subagent] child not found in catalog after refresh:", childId);
|
|
190
|
-
}).catch((e) => {
|
|
191
|
-
console.error("[ya-subagent] refreshSubagents failed:", e);
|
|
192
|
-
});
|
|
193
|
-
};
|
|
194
|
-
const stateLabel = t(`card.${cardState}`);
|
|
195
|
-
const showOutput = cardState === "completed" && result.output !== void 0;
|
|
196
|
-
const activity = progress?.activity;
|
|
197
|
-
const activityText = activity === void 0 ? void 0 : activity.kind === "tool" ? activity.args !== void 0 ? `${activity.name} ${activity.args}` : `${t("card.calling")} ${activity.name}` : activity.text;
|
|
198
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
199
|
-
className: classMap$2.card,
|
|
200
|
-
"data-tool": toolName,
|
|
201
|
-
"data-state": cardState,
|
|
202
|
-
"data-clickable": clickable || void 0,
|
|
203
|
-
onClick: clickable ? openChild : void 0,
|
|
204
|
-
role: clickable ? "button" : void 0,
|
|
205
|
-
tabIndex: clickable ? 0 : void 0,
|
|
206
|
-
children: [
|
|
207
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
208
|
-
className: classMap$2.header,
|
|
209
|
-
children: [
|
|
210
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
211
|
-
className: classMap$2.stateDot,
|
|
212
|
-
"aria-hidden": true
|
|
213
|
-
}),
|
|
214
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
215
|
-
className: classMap$2.title,
|
|
216
|
-
children: profileLabel
|
|
217
|
-
}),
|
|
218
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
219
|
-
className: classMap$2.stateBadge,
|
|
220
|
-
children: stateLabel
|
|
221
|
-
})
|
|
222
|
-
]
|
|
223
|
-
}),
|
|
224
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
225
|
-
className: classMap$2.body,
|
|
226
|
-
children: showOutput ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
227
|
-
className: classMap$2.output,
|
|
228
|
-
children: truncate(result.output, 200)
|
|
229
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
230
|
-
className: classMap$2.stats,
|
|
231
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
232
|
-
className: classMap$2.stat,
|
|
233
|
-
children: [
|
|
234
|
-
toolCallCount,
|
|
235
|
-
" ",
|
|
236
|
-
t("card.toolcalls")
|
|
237
|
-
]
|
|
238
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
239
|
-
className: classMap$2.stat,
|
|
240
|
-
children: formatTokens$1(progress?.tokens, t)
|
|
241
|
-
})]
|
|
242
|
-
}), activityText !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
243
|
-
className: classMap$2.activity,
|
|
244
|
-
title: activityText,
|
|
245
|
-
children: activityText
|
|
246
|
-
})] })
|
|
247
|
-
}),
|
|
248
|
-
clickable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
249
|
-
className: classMap$2.footer,
|
|
250
|
-
children: t("card.open")
|
|
251
|
-
})
|
|
252
|
-
]
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
//#endregion
|
|
256
|
-
//#region \0dsh-css:D:\Projects\deepseek-harness\yet-another-subagent\src\client\SubagentTreeView.module.css.mjs
|
|
257
|
-
const css$1 = "/* SubagentTreeView: a recursive tree of subagent children with live progress. */\n\n.dkph7p9_tree {\n display: flex;\n flex-direction: column;\n gap: 4px;\n padding: 16px;\n max-width: 720px;\n margin: 0 auto;\n}\n\n.dkph7p9_rootHint {\n font-size: 11px;\n line-height: 16px;\n color: var(--dsw-alias-label-tertiary);\n margin-bottom: 4px;\n}\n\n.dkph7p9_empty {\n padding: 32px 16px;\n text-align: center;\n font-size: 13px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.dkph7p9_childList {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.dkph7p9_node {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.dkph7p9_row {\n display: flex;\n flex-direction: column;\n gap: 4px;\n padding: 10px 12px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n background: var(--dsw-alias-bg-layer-1);\n cursor: pointer;\n transition: border-color 0.15s ease, background 0.15s ease;\n}\n\n.dkph7p9_row:hover {\n border-color: var(--dsw-alias-brand-primary);\n}\n\n.dkph7p9_row:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 2px;\n}\n\n/* Highlight the currently open session. */\n.dkph7p9_row[data-current] {\n border-color: var(--dsw-alias-brand-primary);\n background: var(--dsw-alias-bg-layer-2);\n}\n\n.dkph7p9_rowHeader {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n}\n\n.dkph7p9_dot {\n flex: none;\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: var(--dsw-alias-label-caption);\n transition: background 0.15s ease;\n}\n\n.dkph7p9_row[data-state='running'] .dkph7p9_dot {\n background: var(--dsw-alias-brand-primary);\n animation: dsh-ya-tree-pulse 1.4s ease-in-out infinite;\n}\n\n.dkph7p9_row[data-state='idle'] .dkph7p9_dot,\n.dkph7p9_row[data-state='settled'] .dkph7p9_dot {\n background: var(--dsw-alias-state-success-primary);\n}\n\n@keyframes dsh-ya-tree-pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.4; }\n}\n\n.dkph7p9_label {\n flex: 1 1 auto;\n font-size: 13px;\n font-weight: 500;\n line-height: 20px;\n color: var(--dsw-alias-label-primary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.dkph7p9_badge {\n flex: none;\n font-size: 11px;\n line-height: 18px;\n padding: 0 6px;\n border-radius: 4px;\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-tertiary);\n}\n\n.dkph7p9_row[data-state='running'] .dkph7p9_badge {\n padding: 0;\n background: linear-gradient(\n 90deg,\n var(--dsw-static-deepseek-500) 0%,\n var(--dsw-static-deepseek-500) 40%,\n var(--dsw-static-deepseek-200) 50%,\n var(--dsw-static-deepseek-500) 60%,\n var(--dsw-static-deepseek-500) 100%\n );\n background-position: 100% 0;\n background-size: 250% 100%;\n background-clip: text;\n color: transparent;\n -webkit-background-clip: text;\n -webkit-text-fill-color: transparent;\n animation: dsh-ya-tree-shimmer 1.8s linear infinite;\n}\n\n@keyframes dsh-ya-tree-shimmer {\n to { background-position: 0 0; }\n}\n\n.dkph7p9_rowStats {\n display: flex;\n align-items: center;\n gap: 12px;\n font-size: 11px;\n line-height: 16px;\n font-variant-numeric: tabular-nums;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.dkph7p9_stat {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n}\n\n.dkph7p9_activity {\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-secondary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .dkph7p9_row[data-state='running'] .dkph7p9_dot {\n animation: none;\n }\n .dkph7p9_row[data-state='running'] .dkph7p9_badge {\n background-position: 0 0;\n background-size: 100% 100%;\n animation: none;\n }\n}\n";
|
|
258
|
-
const classMap$1 = {
|
|
259
|
-
"tree": "dkph7p9_tree",
|
|
260
|
-
"rootHint": "dkph7p9_rootHint",
|
|
261
|
-
"empty": "dkph7p9_empty",
|
|
262
|
-
"childList": "dkph7p9_childList",
|
|
263
|
-
"node": "dkph7p9_node",
|
|
264
|
-
"row": "dkph7p9_row",
|
|
265
|
-
"rowHeader": "dkph7p9_rowHeader",
|
|
266
|
-
"dot": "dkph7p9_dot",
|
|
267
|
-
"label": "dkph7p9_label",
|
|
268
|
-
"badge": "dkph7p9_badge",
|
|
269
|
-
"rowStats": "dkph7p9_rowStats",
|
|
270
|
-
"stat": "dkph7p9_stat",
|
|
271
|
-
"activity": "dkph7p9_activity"
|
|
272
|
-
};
|
|
273
|
-
const tagId$1 = "@huanlin/dsh-plugin-yet-another-subagent/SubagentTreeView.module.css";
|
|
274
|
-
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
|
|
275
|
-
const tag = document.createElement("style");
|
|
276
|
-
tag.dataset.plugin = "@huanlin/dsh-plugin-yet-another-subagent";
|
|
277
|
-
tag.dataset.pluginCss = tagId$1;
|
|
278
|
-
tag.textContent = css$1;
|
|
279
|
-
document.head.appendChild(tag);
|
|
280
|
-
}
|
|
281
|
-
//#endregion
|
|
282
|
-
//#region src/client/SubagentTreeView.tsx
|
|
283
|
-
/**
|
|
284
|
-
* SubagentTreeView — a `conversation.view` entry showing the root session's
|
|
285
|
-
* full subagent tree (all depths) with live progress.
|
|
286
|
-
*
|
|
287
|
-
* Uses `sessions.subagentsByParent` (the catalog) as the primary tree
|
|
288
|
-
* structure source — this works for ALL depths without needing per-session
|
|
289
|
-
* bindings. `setSubagentCatalogOpen` keeps catalogs auto-refreshing.
|
|
290
|
-
* Projections (`yaSubagentProgress`) are used additionally when a session
|
|
291
|
-
* binding is available (current session + opened children) for richer data.
|
|
292
|
-
*
|
|
293
|
-
* @module @huanlin/dsh-plugin-yet-another-subagent/client/SubagentTreeView
|
|
294
|
-
*/
|
|
295
|
-
/** Walk up `subagentAddress` to find the root session id. */
|
|
296
|
-
function findRootSession(sessions, sessionId) {
|
|
297
|
-
let current = sessionId;
|
|
298
|
-
for (let i = 0; i < 32; i++) {
|
|
299
|
-
const addr = sessions.subagentAddress?.(current);
|
|
300
|
-
if (addr === void 0) return current;
|
|
301
|
-
current = addr.parentSessionId;
|
|
302
|
-
}
|
|
303
|
-
return sessionId;
|
|
304
|
-
}
|
|
305
|
-
/** Subscribe to a session's `subagentProfile` projection → children map. */
|
|
306
|
-
function useChildren(sessions, sessionId) {
|
|
307
|
-
const [children, setChildren] = (0, react.useState)({});
|
|
308
|
-
(0, react.useEffect)(() => {
|
|
309
|
-
const binding = sessions.binding(sessionId);
|
|
310
|
-
if (binding === void 0) return;
|
|
311
|
-
const face = binding.session.projections.faceOf("subagentProfile");
|
|
312
|
-
if (face === void 0) return;
|
|
313
|
-
const read = () => {
|
|
314
|
-
const snap = face.getSnapshot();
|
|
315
|
-
setChildren(snap?.children ?? {});
|
|
316
|
-
};
|
|
317
|
-
read();
|
|
318
|
-
return face.subscribe(read);
|
|
319
|
-
}, [sessions, sessionId]);
|
|
320
|
-
return children;
|
|
321
|
-
}
|
|
322
|
-
/** Subscribe to a child session's `yaSubagentProgress` projection (if binding exists). */
|
|
323
|
-
function useChildProgress(sessions, childId) {
|
|
324
|
-
const [progress, setProgress] = (0, react.useState)(void 0);
|
|
325
|
-
(0, react.useEffect)(() => {
|
|
326
|
-
const binding = sessions.binding(childId);
|
|
327
|
-
if (binding === void 0) return;
|
|
328
|
-
const face = binding.session.projections.faceOf("yaSubagentProgress");
|
|
329
|
-
if (face === void 0) return;
|
|
330
|
-
const snapshot = face.getSnapshot();
|
|
331
|
-
setProgress(snapshot ?? void 0);
|
|
332
|
-
return face.subscribe(() => {
|
|
333
|
-
const next = face.getSnapshot();
|
|
334
|
-
setProgress(next ?? void 0);
|
|
335
|
-
});
|
|
336
|
-
}, [sessions, childId]);
|
|
337
|
-
return progress;
|
|
338
|
-
}
|
|
339
|
-
/**
|
|
340
|
-
* Recursively refresh catalogs for a session and all its descendants.
|
|
341
|
-
* Returns immediately — `subagentsByParent` updates reactively.
|
|
342
|
-
*/
|
|
343
|
-
function refreshTree(sessions, parentId) {
|
|
344
|
-
sessions.refreshSubagents(parentId).then(() => {
|
|
345
|
-
const catalog = sessions.subagentsByParent?.[parentId];
|
|
346
|
-
if (catalog === void 0) return;
|
|
347
|
-
for (const entry of catalog.entries) if (entry.kind === "child" && entry.hasChildren === true) refreshTree(sessions, entry.id);
|
|
348
|
-
}).catch(() => {});
|
|
349
|
-
}
|
|
350
|
-
/** Format token totals: >=1000 uses "k", <1000 uses plain count. */
|
|
351
|
-
function formatTokens(tokens, t) {
|
|
352
|
-
if (tokens === void 0) return `0 ${t("tree.tokens")}`;
|
|
353
|
-
const total = tokens.input + tokens.output + tokens.cacheRead + tokens.cacheWrite + tokens.reasoning;
|
|
354
|
-
return `${total >= 1e3 ? `${(total / 1e3).toFixed(1)}k` : String(total)} ${t("tree.tokens")}`;
|
|
355
|
-
}
|
|
356
|
-
function buildTree(sessions, parentId, projectionChildren, profileLabelOf, visited) {
|
|
357
|
-
const catalog = sessions.subagentsByParent?.[parentId];
|
|
358
|
-
if (catalog === void 0) return [];
|
|
359
|
-
const nodes = [];
|
|
360
|
-
for (const entry of catalog.entries) {
|
|
361
|
-
if (entry.kind !== "child") continue;
|
|
362
|
-
if (visited.has(entry.id)) continue;
|
|
363
|
-
visited.add(entry.id);
|
|
364
|
-
const profileId = projectionChildren[entry.id];
|
|
365
|
-
const label = (profileId !== void 0 ? profileLabelOf(profileId) : void 0) ?? entry.label ?? entry.id;
|
|
366
|
-
const childProjectionChildren = {};
|
|
367
|
-
const binding = sessions.binding(entry.id);
|
|
368
|
-
if (binding !== void 0) {
|
|
369
|
-
const face = binding.session.projections.faceOf("subagentProfile");
|
|
370
|
-
if (face !== void 0) {
|
|
371
|
-
const snap = face.getSnapshot();
|
|
372
|
-
if (snap?.children !== void 0) for (const [k, v] of Object.entries(snap.children)) childProjectionChildren[k] = v;
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
const children = entry.hasChildren === true ? buildTree(sessions, entry.id, childProjectionChildren, profileLabelOf, visited) : [];
|
|
376
|
-
nodes.push({
|
|
377
|
-
id: entry.id,
|
|
378
|
-
label,
|
|
379
|
-
mode: entry.mode,
|
|
380
|
-
catalogActivity: entry.activity,
|
|
381
|
-
hasChildren: children.length > 0,
|
|
382
|
-
children
|
|
383
|
-
});
|
|
384
|
-
}
|
|
385
|
-
return nodes;
|
|
386
|
-
}
|
|
387
|
-
/**
|
|
388
|
-
* Render the subagent tree view. Always shows the ROOT session's full tree;
|
|
389
|
-
* highlights the current session if it is a subagent.
|
|
390
|
-
*/
|
|
391
|
-
function SubagentTreeView({ sessionId, sessions, profileLabelOf, t }) {
|
|
392
|
-
const rootId = findRootSession(sessions, sessionId);
|
|
393
|
-
const isSubagent = rootId !== sessionId;
|
|
394
|
-
(0, react.useEffect)(() => {
|
|
395
|
-
sessions.setSubagentCatalogOpen(rootId, true);
|
|
396
|
-
refreshTree(sessions, rootId);
|
|
397
|
-
return () => {
|
|
398
|
-
sessions.setSubagentCatalogOpen(rootId, false);
|
|
399
|
-
};
|
|
400
|
-
}, [sessions, rootId]);
|
|
401
|
-
const projectionChildren = useChildren(sessions, rootId);
|
|
402
|
-
const [, setTick] = (0, react.useState)(0);
|
|
403
|
-
(0, react.useEffect)(() => {
|
|
404
|
-
const interval = setInterval(() => setTick((t) => t + 1), 2e3);
|
|
405
|
-
return () => clearInterval(interval);
|
|
406
|
-
}, []);
|
|
407
|
-
const tree = (0, react.useMemo)(() => buildTree(sessions, rootId, projectionChildren, profileLabelOf, /* @__PURE__ */ new Set()), [
|
|
408
|
-
sessions,
|
|
409
|
-
rootId,
|
|
410
|
-
projectionChildren,
|
|
411
|
-
profileLabelOf,
|
|
412
|
-
sessions.subagentsByParent
|
|
413
|
-
]);
|
|
414
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
415
|
-
className: classMap$1.tree,
|
|
416
|
-
children: [isSubagent && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
417
|
-
className: classMap$1.rootHint,
|
|
418
|
-
children: t("tree.rootHint")
|
|
419
|
-
}), tree.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
420
|
-
className: classMap$1.empty,
|
|
421
|
-
children: t("tree.empty")
|
|
422
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TreeNodes, {
|
|
423
|
-
nodes: tree,
|
|
424
|
-
currentSessionId: sessionId,
|
|
425
|
-
sessions,
|
|
426
|
-
t,
|
|
427
|
-
depth: 0
|
|
428
|
-
})]
|
|
429
|
-
});
|
|
430
|
-
}
|
|
431
|
-
/** Render a list of tree nodes. */
|
|
432
|
-
function TreeNodes({ nodes, currentSessionId, sessions, t, depth }) {
|
|
433
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
434
|
-
className: classMap$1.childList,
|
|
435
|
-
children: nodes.map((node) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TreeNodeView, {
|
|
436
|
-
node,
|
|
437
|
-
currentSessionId,
|
|
438
|
-
sessions,
|
|
439
|
-
t,
|
|
440
|
-
depth
|
|
441
|
-
}, node.id))
|
|
442
|
-
});
|
|
443
|
-
}
|
|
444
|
-
/** One tree node row + its children (recursively). */
|
|
445
|
-
function TreeNodeView({ node, currentSessionId, sessions, t, depth }) {
|
|
446
|
-
const progress = useChildProgress(sessions, node.id);
|
|
447
|
-
const isCurrent = node.id === currentSessionId;
|
|
448
|
-
const state = progress?.state ?? (node.catalogActivity === "running" ? "running" : "idle");
|
|
449
|
-
const stateLabel = t(`tree.state.${state}`);
|
|
450
|
-
const toolCallCount = progress?.toolCallCount ?? 0;
|
|
451
|
-
const tokens = formatTokens(progress?.tokens, t);
|
|
452
|
-
const activity = progress?.activity;
|
|
453
|
-
const activityText = activity === void 0 ? void 0 : activity.kind === "tool" ? activity.args !== void 0 ? `${activity.name} ${activity.args}` : `${t("tree.calling")} ${activity.name}` : activity.text;
|
|
454
|
-
const openChild = () => {
|
|
455
|
-
const retained = sessions.subagentAddress?.(node.id);
|
|
456
|
-
if (retained !== void 0) try {
|
|
457
|
-
sessions.openSubagent(retained);
|
|
458
|
-
return;
|
|
459
|
-
} catch {}
|
|
460
|
-
const addr = sessions.subagentAddress?.(node.id);
|
|
461
|
-
if (addr !== void 0) try {
|
|
462
|
-
sessions.openSubagent(addr);
|
|
463
|
-
return;
|
|
464
|
-
} catch {}
|
|
465
|
-
};
|
|
466
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
467
|
-
className: classMap$1.node,
|
|
468
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
469
|
-
className: classMap$1.row,
|
|
470
|
-
"data-state": state,
|
|
471
|
-
"data-current": isCurrent || void 0,
|
|
472
|
-
style: { paddingLeft: `${12 + depth * 20}px` },
|
|
473
|
-
onClick: openChild,
|
|
474
|
-
role: "button",
|
|
475
|
-
tabIndex: 0,
|
|
476
|
-
children: [
|
|
477
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
478
|
-
className: classMap$1.rowHeader,
|
|
479
|
-
children: [
|
|
480
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
481
|
-
className: classMap$1.dot,
|
|
482
|
-
"aria-hidden": true
|
|
483
|
-
}),
|
|
484
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
485
|
-
className: classMap$1.label,
|
|
486
|
-
children: node.label
|
|
487
|
-
}),
|
|
488
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
489
|
-
className: classMap$1.badge,
|
|
490
|
-
children: stateLabel
|
|
491
|
-
})
|
|
492
|
-
]
|
|
493
|
-
}),
|
|
494
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
495
|
-
className: classMap$1.rowStats,
|
|
496
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
497
|
-
className: classMap$1.stat,
|
|
498
|
-
children: [
|
|
499
|
-
toolCallCount,
|
|
500
|
-
" ",
|
|
501
|
-
t("tree.toolcalls")
|
|
502
|
-
]
|
|
503
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
504
|
-
className: classMap$1.stat,
|
|
505
|
-
children: tokens
|
|
506
|
-
})]
|
|
507
|
-
}),
|
|
508
|
-
activityText !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
509
|
-
className: classMap$1.activity,
|
|
510
|
-
title: activityText,
|
|
511
|
-
children: activityText
|
|
512
|
-
})
|
|
513
|
-
]
|
|
514
|
-
}), node.children.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TreeNodes, {
|
|
515
|
-
nodes: node.children,
|
|
516
|
-
currentSessionId,
|
|
517
|
-
sessions,
|
|
518
|
-
t,
|
|
519
|
-
depth: depth + 1
|
|
520
|
-
})]
|
|
521
|
-
});
|
|
522
|
-
}
|
|
523
|
-
//#endregion
|
|
524
|
-
//#region \0dsh-css:D:\Projects\deepseek-harness\yet-another-subagent\src\client\SettingsPage.module.css.mjs
|
|
525
|
-
const css = "/* Subagent settings section, in the settings-panel design language shared\r\n * with ModelsSection / GeneralSection: 14/22 body, 12/18 caption, 16/24 title,\r\n * capsule controls (h36 r18 primary, h28 r14 secondary), 32px fields,\r\n * border-l2 hairlines, and the editor as a filled module on the panel fill.\r\n *\r\n * Every color resolves through a --dsw-alias-* token (no literal colors). */\r\n\r\n.dun7765_section {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 12px;\r\n max-width: 720px;\r\n color: var(--dsw-alias-label-primary);\r\n}\r\n\r\n.dun7765_title {\r\n margin: 0;\r\n font-size: 16px;\r\n line-height: 24px;\r\n font-weight: 500;\r\n color: var(--dsw-alias-label-primary);\r\n}\r\n\r\n.dun7765_intro {\r\n margin: 0;\r\n font-size: 14px;\r\n line-height: 22px;\r\n color: var(--dsw-alias-label-tertiary);\r\n}\r\n\r\n.dun7765_error {\r\n margin: 0;\r\n padding: 8px 12px;\r\n border: 1px solid var(--dsw-alias-state-error-primary);\r\n border-radius: 8px;\r\n background: var(--dsw-alias-interactive-bg-hover-danger);\r\n font-size: 12px;\r\n line-height: 18px;\r\n color: var(--dsw-alias-state-error-primary);\r\n display: flex;\r\n align-items: center;\r\n justify-content: space-between;\r\n gap: 8px;\r\n}\r\n\r\n.dun7765_errorDismiss {\r\n flex: none;\r\n border: none;\r\n background: transparent;\r\n color: inherit;\r\n font-size: 16px;\r\n line-height: 1;\r\n cursor: pointer;\r\n padding: 0 4px;\r\n}\r\n\r\n.dun7765_rows {\r\n list-style: none;\r\n margin: 12px 0 0;\r\n padding: 0;\r\n display: flex;\r\n flex-direction: column;\r\n gap: 8px;\r\n}\r\n\r\n/* A configured profile: outlined on the panel fill, matching the rowCard\r\n * chrome in ModelsSection. */\r\n.dun7765_rowCard {\r\n border: 1px solid var(--dsw-alias-border-l2);\r\n border-radius: 12px;\r\n padding: 12px 14px;\r\n display: flex;\r\n flex-direction: column;\r\n gap: 12px;\r\n}\r\n\r\n.dun7765_rowHead {\r\n display: flex;\r\n align-items: center;\r\n gap: 10px;\r\n}\r\n\r\n/* Chevron toggle: a small square with two borders, rotated to point right\r\n * (collapsed) or down (expanded). Pure CSS, no icon font. */\r\n.dun7765_chevronButton {\r\n flex: none;\r\n width: 24px;\r\n height: 24px;\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n border: none;\r\n background: transparent;\r\n color: var(--dsw-alias-label-secondary);\r\n cursor: pointer;\r\n padding: 0;\r\n border-radius: 4px;\r\n}\r\n\r\n.dun7765_chevronButton:hover {\r\n background: var(--dsw-alias-interactive-bg-hover-solid);\r\n color: var(--dsw-alias-label-primary);\r\n}\r\n\r\n/* Spacer that occupies the chevron slot on cards without a toggle (e.dun7765_g. the\r\n * new-draft card, which is always expanded). Keeps row-head alignment. */\r\n.dun7765_chevronSpacer {\r\n flex: none;\r\n width: 24px;\r\n height: 24px;\r\n}\r\n\r\n.dun7765_chevron {\r\n width: 7px;\r\n height: 7px;\r\n border-right: 1.5px solid currentColor;\r\n border-bottom: 1.5px solid currentColor;\r\n transform: rotate(-45deg);\r\n transition: transform 0.15s ease;\r\n}\r\n\r\n.dun7765_chevronExpanded {\r\n transform: rotate(45deg);\r\n}\r\n\r\n.dun7765_rowIdentity {\r\n display: inline-flex;\r\n align-items: center;\r\n gap: 6px;\r\n min-width: 0;\r\n flex: 1 1 auto;\r\n}\r\n\r\n.dun7765_rowName {\r\n font-size: 14px;\r\n line-height: 22px;\r\n font-weight: 500;\r\n color: var(--dsw-alias-label-primary);\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n}\r\n\r\n.dun7765_rowNamePlaceholder {\r\n font-size: 14px;\r\n line-height: 22px;\r\n font-weight: 500;\r\n color: var(--dsw-alias-label-tertiary);\r\n font-style: italic;\r\n}\r\n\r\n/* Builtin badge: uses DSH <Pill> with a brand-colored override to mark\r\n * seed profiles. The Pill's default chrome is bg-layer-2/label-secondary;\r\n * this override shifts it to the brand fill for the \"builtin\" emphasis. */\r\n.dun7765_builtinBadge {\r\n height: 18px;\r\n padding: 0 6px;\r\n border-radius: 4px;\r\n font-size: 11px;\r\n line-height: 16px;\r\n font-weight: 500;\r\n background: var(--dsw-alias-brand-primary);\r\n color: var(--dsw-alias-label-primary-foreground);\r\n letter-spacing: 0.02em;\r\n}\r\n\r\n.dun7765_rowId {\r\n flex: none;\r\n padding: 1px 6px;\r\n border: 1px solid var(--dsw-alias-border-l3);\r\n border-radius: 4px;\r\n font-size: 11px;\r\n line-height: 16px;\r\n font-family: var(--dsw-font-markdown-code-block-small, monospace);\r\n color: var(--dsw-alias-label-secondary);\r\n}\r\n\r\n.dun7765_rowActions {\r\n display: inline-flex;\r\n align-items: center;\r\n gap: 4px;\r\n margin-left: auto;\r\n flex: none;\r\n}\r\n\r\n/* Editor surface: a filled module on the panel, matching ModelsSection's\r\n * editor chrome (bg-module-platform, r12, p14/16). */\r\n.dun7765_editor {\r\n border-radius: 12px;\r\n background: var(--dsw-alias-bg-module-platform);\r\n padding: 14px 16px;\r\n display: flex;\r\n flex-direction: column;\r\n gap: 14px;\r\n}\r\n\r\n.dun7765_field {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 6px;\r\n}\r\n\r\n.dun7765_fieldLabel {\r\n display: inline-flex;\r\n align-items: center;\r\n gap: 10px;\r\n font-size: 12px;\r\n line-height: 18px;\r\n font-weight: 500;\r\n color: var(--dsw-alias-label-secondary);\r\n}\r\n\r\n.dun7765_fieldControl {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 6px;\r\n}\r\n\r\n/* Input: matching ModelsSection .dun7765_input — h32, r8, border-l2, bg-layer-1. */\r\n.dun7765_input {\r\n box-sizing: border-box;\r\n width: 100%;\r\n height: 32px;\r\n padding: 0 10px;\r\n border: 1px solid var(--dsw-alias-border-l2);\r\n border-radius: 8px;\r\n font: inherit;\r\n font-size: 14px;\r\n line-height: 22px;\r\n background: var(--dsw-alias-bg-layer-1);\r\n color: var(--dsw-alias-label-primary);\r\n}\r\n\r\n.dun7765_input:focus {\r\n outline: none;\r\n border-color: var(--dsw-alias-brand-primary);\r\n}\r\n\r\n.dun7765_input::placeholder {\r\n color: var(--dsw-alias-label-dimmed);\r\n}\r\n\r\n.dun7765_input:disabled {\r\n opacity: 0.6;\r\n cursor: default;\r\n}\r\n\r\n/* DropdownPicker button: same chrome as .dun7765_input (h32, r8, border-l2,\r\n * bg-layer-1) but a button element with a trailing chevron icon. The\r\n * popup list is rendered by DSH's <Menu> component (portal mode). */\r\n.dun7765_dropdownButton {\r\n box-sizing: border-box;\r\n width: 100%;\r\n height: 32px;\r\n padding: 0 10px;\r\n border: 1px solid var(--dsw-alias-border-l2);\r\n border-radius: 8px;\r\n font: inherit;\r\n font-size: 14px;\r\n line-height: 22px;\r\n background: var(--dsw-alias-bg-layer-1);\r\n color: var(--dsw-alias-label-primary);\r\n cursor: pointer;\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: space-between;\r\n gap: 8px;\r\n}\r\n\r\n.dun7765_dropdownButton:hover:not(:disabled) {\r\n border-color: var(--dsw-alias-brand-primary);\r\n}\r\n\r\n.dun7765_dropdownButton:focus-visible {\r\n outline: none;\r\n border-color: var(--dsw-alias-brand-primary);\r\n box-shadow: 0 0 0 2px var(--dsw-alias-border-l3);\r\n}\r\n\r\n.dun7765_dropdownButton:disabled {\r\n opacity: 0.6;\r\n cursor: default;\r\n}\r\n\r\n.dun7765_dropdownButtonLabel {\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n flex: 1 1 auto;\r\n text-align: left;\r\n}\r\n\r\n.dun7765_dropdownCaret {\r\n flex: none;\r\n opacity: 0.6;\r\n}\r\n\r\n.dun7765_textarea {\r\n box-sizing: border-box;\r\n width: 100%;\r\n min-height: 64px;\r\n padding: 6px 10px;\r\n border: 1px solid var(--dsw-alias-border-l2);\r\n border-radius: 8px;\r\n font: inherit;\r\n font-size: 14px;\r\n line-height: 22px;\r\n background: var(--dsw-alias-bg-layer-1);\r\n color: var(--dsw-alias-label-primary);\r\n resize: vertical;\r\n}\r\n\r\n.dun7765_textarea:focus {\r\n outline: none;\r\n border-color: var(--dsw-alias-brand-primary);\r\n}\r\n\r\n.dun7765_textarea::placeholder {\r\n color: var(--dsw-alias-label-dimmed);\r\n}\r\n\r\n/* Radio group: inline, matching the model.dun7765_kind auto/manual picker. */\r\n.dun7765_radioGroup {\r\n display: flex;\r\n gap: 16px;\r\n flex-wrap: wrap;\r\n}\r\n\r\n.dun7765_radioOption {\r\n display: inline-flex;\r\n align-items: center;\r\n gap: 6px;\r\n font-size: 14px;\r\n line-height: 22px;\r\n color: var(--dsw-alias-label-secondary);\r\n cursor: pointer;\r\n}\r\n\r\n.dun7765_radioOption input[type='radio'] {\r\n margin: 0;\r\n cursor: pointer;\r\n}\r\n\r\n/* Buttons: capsule controls matching ModelsSection (h36 r18 primary,\r\n * h28 r14 secondary in row context). */\r\n.dun7765_primaryButton,\r\n.dun7765_secondaryButton,\r\n.dun7765_dangerButton,\r\n.dun7765_addBlockButton {\r\n box-sizing: border-box;\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n gap: 4px;\r\n height: 36px;\r\n padding: 0 14px;\r\n border: none;\r\n border-radius: 18px;\r\n font: inherit;\r\n font-size: 14px;\r\n line-height: 22px;\r\n cursor: pointer;\r\n}\r\n\r\n.dun7765_primaryButton {\r\n background: var(--dsw-alias-button-primary-fill);\r\n color: var(--dsw-alias-label-primary-foreground);\r\n}\r\n\r\n.dun7765_primaryButton:hover:not(:disabled) {\r\n background: var(--dsw-alias-button-primary-hover);\r\n}\r\n\r\n.dun7765_secondaryButton,\r\n.dun7765_addBlockButton {\r\n border: 1px solid var(--dsw-alias-border-l2);\r\n background: transparent;\r\n color: var(--dsw-alias-label-primary);\r\n}\r\n\r\n.dun7765_secondaryButton:hover:not(:disabled),\r\n.dun7765_addBlockButton:hover:not(:disabled) {\r\n background: var(--dsw-alias-interactive-bg-hover-solid);\r\n}\r\n\r\n.dun7765_dangerButton {\r\n background: transparent;\r\n color: var(--dsw-alias-state-error-primary);\r\n}\r\n\r\n.dun7765_dangerButton:hover:not(:disabled) {\r\n background: var(--dsw-alias-interactive-bg-hover-danger);\r\n}\r\n\r\n/* Row-context buttons go dense (h28 r14, 12/18). */\r\n.dun7765_rowActions .dun7765_secondaryButton,\r\n.dun7765_rowActions .dun7765_dangerButton {\r\n height: 28px;\r\n padding: 0 10px;\r\n border-radius: 14px;\r\n font-size: 12px;\r\n line-height: 18px;\r\n}\r\n\r\n.dun7765_rowActions .dun7765_primaryButton {\r\n height: 28px;\r\n padding: 0 10px;\r\n border-radius: 14px;\r\n font-size: 12px;\r\n line-height: 18px;\r\n}\r\n\r\n.dun7765_primaryButton:disabled,\r\n.dun7765_secondaryButton:disabled,\r\n.dun7765_dangerButton:disabled,\r\n.dun7765_addBlockButton:disabled {\r\n opacity: 0.4;\r\n cursor: default;\r\n}\r\n\r\n.dun7765_primaryButton:focus-visible,\r\n.dun7765_secondaryButton:focus-visible,\r\n.dun7765_dangerButton:focus-visible,\r\n.dun7765_addBlockButton:focus-visible {\r\n outline: none;\r\n box-shadow: 0 0 0 2px var(--dsw-alias-border-l3);\r\n}\r\n\r\n/* Add-profile action: a full-width dashed-outline place card matching\r\n * ModelsSection's addBlock — not a command pill. Clicking it reveals the\r\n * inline draft card (handled in JS); the button itself is the affordance. */\r\n.dun7765_addBlockButton {\r\n width: 100%;\r\n margin-top: 12px;\r\n border-style: dashed;\r\n border-radius: 12px;\r\n height: 40px;\r\n color: var(--dsw-alias-label-secondary);\r\n}\r\n\r\n.dun7765_addBlockButton:hover:not(:disabled) {\r\n color: var(--dsw-alias-label-primary);\r\n border-color: var(--dsw-alias-brand-primary);\r\n}\r\n\r\n/* Empty state. */\r\n.dun7765_empty {\r\n margin: 0;\r\n padding: 24px 12px;\r\n text-align: center;\r\n font-size: 14px;\r\n line-height: 22px;\r\n color: var(--dsw-alias-label-tertiary);\r\n}\r\n\r\n/* Modal confirm body text. */\r\n.dun7765_confirmText {\r\n margin: 0;\r\n font-size: 14px;\r\n line-height: 22px;\r\n color: var(--dsw-alias-label-secondary);\r\n}\r\n\r\n/* Repair history block: a discrete action below the add-profile button. */\r\n.dun7765_repairBlock {\r\n margin-top: 8px;\r\n}\r\n\r\n.dun7765_repairBlock button {\r\n width: 100%;\r\n}\r\n\r\n/* Repair result stats container. */\r\n.dun7765_repairStats {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 4px;\r\n}\r\n\r\n/* Repair error list inside the result modal. */\r\n.dun7765_repairErrorList {\r\n margin: 8px 0 0;\r\n padding: 0 0 0 16px;\r\n display: flex;\r\n flex-direction: column;\r\n gap: 4px;\r\n max-height: 200px;\r\n overflow-y: auto;\r\n}\r\n\r\n.dun7765_loading {\r\n padding: 12px;\r\n font-size: 14px;\r\n line-height: 22px;\r\n color: var(--dsw-alias-label-tertiary);\r\n}\r\n\r\n/* ─── Multi-select dropdown ─────────────────────────────────────────────── */\r\n\r\n.dun7765_multiSelect {\r\n position: relative;\r\n width: 100%;\r\n}\r\n\r\n.dun7765_msButton {\r\n box-sizing: border-box;\r\n width: 100%;\r\n height: 32px;\r\n padding: 0 10px;\r\n border: 1px solid var(--dsw-alias-border-l2);\r\n border-radius: 8px;\r\n font: inherit;\r\n font-size: 14px;\r\n line-height: 22px;\r\n background: var(--dsw-alias-bg-layer-1);\r\n color: var(--dsw-alias-label-primary);\r\n cursor: pointer;\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: space-between;\r\n gap: 8px;\r\n}\r\n\r\n.dun7765_msButton:hover {\r\n border-color: var(--dsw-alias-brand-primary);\r\n}\r\n\r\n.dun7765_msButton:focus-visible {\r\n outline: none;\r\n border-color: var(--dsw-alias-brand-primary);\r\n box-shadow: 0 0 0 2px var(--dsw-alias-border-l3);\r\n}\r\n\r\n.dun7765_msButtonLabel {\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n flex: 1 1 auto;\r\n text-align: left;\r\n}\r\n\r\n/* Caret: DSH IconChevronDownOutline14 icon. */\r\n.dun7765_msCaret {\r\n flex: none;\r\n opacity: 0.6;\r\n}\r\n\r\n.dun7765_msPopup {\r\n position: absolute;\r\n top: calc(100% + 4px);\r\n left: 0;\r\n right: 0;\r\n z-index: 10;\r\n border: 1px solid var(--dsw-alias-border-l2);\r\n border-radius: 8px;\r\n background: var(--dsw-alias-bg-layer-1);\r\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);\r\n display: flex;\r\n flex-direction: column;\r\n max-height: 280px;\r\n overflow: hidden;\r\n}\r\n\r\n.dun7765_msSearch {\r\n box-sizing: border-box;\r\n width: 100%;\r\n padding: 8px 10px;\r\n border: none;\r\n border-bottom: 1px solid var(--dsw-alias-border-l2);\r\n font: inherit;\r\n font-size: 14px;\r\n line-height: 22px;\r\n background: transparent;\r\n color: var(--dsw-alias-label-primary);\r\n}\r\n\r\n.dun7765_msSearch:focus {\r\n outline: none;\r\n}\r\n\r\n.dun7765_msSearch::placeholder {\r\n color: var(--dsw-alias-label-dimmed);\r\n}\r\n\r\n.dun7765_msActions {\r\n display: flex;\r\n justify-content: flex-end;\r\n padding: 6px 10px;\r\n border-bottom: 1px solid var(--dsw-alias-border-l2);\r\n}\r\n\r\n.dun7765_msActionLink {\r\n border: none;\r\n background: transparent;\r\n color: var(--dsw-alias-brand-primary);\r\n font: inherit;\r\n font-size: 12px;\r\n line-height: 18px;\r\n cursor: pointer;\r\n padding: 0;\r\n}\r\n\r\n.dun7765_msActionLink:hover {\r\n text-decoration: underline;\r\n}\r\n\r\n.dun7765_msList {\r\n list-style: none;\r\n margin: 0;\r\n padding: 4px 0;\r\n overflow-y: auto;\r\n flex: 1 1 auto;\r\n}\r\n\r\n.dun7765_msItem {\r\n margin: 0;\r\n}\r\n\r\n.dun7765_msItemLabel {\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n padding: 6px 10px;\r\n cursor: pointer;\r\n font-size: 13px;\r\n line-height: 18px;\r\n color: var(--dsw-alias-label-primary);\r\n}\r\n\r\n.dun7765_msItemLabel:hover {\r\n background: var(--dsw-alias-interactive-bg-hover-solid);\r\n}\r\n\r\n.dun7765_msItemLabel input[type='checkbox'] {\r\n margin: 0;\r\n cursor: pointer;\r\n flex: none;\r\n}\r\n\r\n.dun7765_msItemName {\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n font-family: var(--dsw-font-markdown-code-block-small, monospace);\r\n}\r\n\r\n.dun7765_msEmpty {\r\n padding: 12px 10px;\r\n font-size: 13px;\r\n line-height: 18px;\r\n color: var(--dsw-alias-label-tertiary);\r\n text-align: center;\r\n}\r\n";
|
|
526
|
-
const classMap = {
|
|
527
|
-
"section": "dun7765_section",
|
|
528
|
-
"title": "dun7765_title",
|
|
529
|
-
"intro": "dun7765_intro",
|
|
530
|
-
"error": "dun7765_error",
|
|
531
|
-
"errorDismiss": "dun7765_errorDismiss",
|
|
532
|
-
"rows": "dun7765_rows",
|
|
533
|
-
"rowCard": "dun7765_rowCard",
|
|
534
|
-
"rowHead": "dun7765_rowHead",
|
|
535
|
-
"chevronButton": "dun7765_chevronButton",
|
|
536
|
-
"g": "dun7765_g",
|
|
537
|
-
"chevronSpacer": "dun7765_chevronSpacer",
|
|
538
|
-
"chevron": "dun7765_chevron",
|
|
539
|
-
"chevronExpanded": "dun7765_chevronExpanded",
|
|
540
|
-
"rowIdentity": "dun7765_rowIdentity",
|
|
541
|
-
"rowName": "dun7765_rowName",
|
|
542
|
-
"rowNamePlaceholder": "dun7765_rowNamePlaceholder",
|
|
543
|
-
"builtinBadge": "dun7765_builtinBadge",
|
|
544
|
-
"rowId": "dun7765_rowId",
|
|
545
|
-
"rowActions": "dun7765_rowActions",
|
|
546
|
-
"editor": "dun7765_editor",
|
|
547
|
-
"field": "dun7765_field",
|
|
548
|
-
"fieldLabel": "dun7765_fieldLabel",
|
|
549
|
-
"fieldControl": "dun7765_fieldControl",
|
|
550
|
-
"input": "dun7765_input",
|
|
551
|
-
"dropdownButton": "dun7765_dropdownButton",
|
|
552
|
-
"dropdownButtonLabel": "dun7765_dropdownButtonLabel",
|
|
553
|
-
"dropdownCaret": "dun7765_dropdownCaret",
|
|
554
|
-
"textarea": "dun7765_textarea",
|
|
555
|
-
"kind": "dun7765_kind",
|
|
556
|
-
"radioGroup": "dun7765_radioGroup",
|
|
557
|
-
"radioOption": "dun7765_radioOption",
|
|
558
|
-
"primaryButton": "dun7765_primaryButton",
|
|
559
|
-
"secondaryButton": "dun7765_secondaryButton",
|
|
560
|
-
"dangerButton": "dun7765_dangerButton",
|
|
561
|
-
"addBlockButton": "dun7765_addBlockButton",
|
|
562
|
-
"empty": "dun7765_empty",
|
|
563
|
-
"confirmText": "dun7765_confirmText",
|
|
564
|
-
"repairBlock": "dun7765_repairBlock",
|
|
565
|
-
"repairStats": "dun7765_repairStats",
|
|
566
|
-
"repairErrorList": "dun7765_repairErrorList",
|
|
567
|
-
"loading": "dun7765_loading",
|
|
568
|
-
"multiSelect": "dun7765_multiSelect",
|
|
569
|
-
"msButton": "dun7765_msButton",
|
|
570
|
-
"msButtonLabel": "dun7765_msButtonLabel",
|
|
571
|
-
"msCaret": "dun7765_msCaret",
|
|
572
|
-
"msPopup": "dun7765_msPopup",
|
|
573
|
-
"msSearch": "dun7765_msSearch",
|
|
574
|
-
"msActions": "dun7765_msActions",
|
|
575
|
-
"msActionLink": "dun7765_msActionLink",
|
|
576
|
-
"msList": "dun7765_msList",
|
|
577
|
-
"msItem": "dun7765_msItem",
|
|
578
|
-
"msItemLabel": "dun7765_msItemLabel",
|
|
579
|
-
"msItemName": "dun7765_msItemName",
|
|
580
|
-
"msEmpty": "dun7765_msEmpty"
|
|
581
|
-
};
|
|
582
|
-
const tagId = "@huanlin/dsh-plugin-yet-another-subagent/SettingsPage.module.css";
|
|
583
|
-
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
584
|
-
const tag = document.createElement("style");
|
|
585
|
-
tag.dataset.plugin = "@huanlin/dsh-plugin-yet-another-subagent";
|
|
586
|
-
tag.dataset.pluginCss = tagId;
|
|
587
|
-
tag.textContent = css;
|
|
588
|
-
document.head.appendChild(tag);
|
|
589
|
-
}
|
|
590
|
-
//#endregion
|
|
591
|
-
//#region src/client/SettingsPage.tsx
|
|
592
|
-
/**
|
|
593
|
-
* SettingsPage — the `ya-subagent` settings section: profile list CRUD.
|
|
594
|
-
*
|
|
595
|
-
* Visual language: matches ModelsSection / GeneralSection — outlined rowCard
|
|
596
|
-
* per profile (border-l2, r12, p12/14), filled editor surface
|
|
597
|
-
* (bg-module-platform, r12, p14/16), capsule controls (h36 r18 primary,
|
|
598
|
-
* h28 r14 secondary), 32px fields with border-l2 / bg-layer-1, 12/18 caption
|
|
599
|
-
* labels. Every color resolves through --dsw-alias-* tokens.
|
|
600
|
-
*
|
|
601
|
-
* Each profile card is collapsible (chevron in the row head); the editor
|
|
602
|
-
* surface is hidden when collapsed. Builtin profiles (cordis.yml seed) carry
|
|
603
|
-
* a `builtin`/`内置` badge next to the title. The "+ Add subagent" button at
|
|
604
|
-
* the bottom reveals an inline draft card with all fields editable (including
|
|
605
|
-
* id) and Create / Cancel actions.
|
|
606
|
-
*
|
|
607
|
-
* The persona field is a radio (inherit deployment persona vs custom text);
|
|
608
|
-
* the textarea is shown only when custom. The tool filter is a select
|
|
609
|
-
* (none / allow / deny); a multi-select dropdown is shown only when allow or
|
|
610
|
-
* deny is picked, populated from `tools.list` (the host's current
|
|
611
|
-
* `ctx.tools.schemas()`).
|
|
612
|
-
*
|
|
613
|
-
* Pulls the profile list once on mount via `connection.rpc.call('/ya-subagent',
|
|
614
|
-
* 'profiles.list')`, dispatches add/update/remove through the
|
|
615
|
-
* same RPC. The toolview slot is keyed by `subagent` and registered once at
|
|
616
|
-
* plugin load, so profile mutations do not need to re-register slots.
|
|
617
|
-
*
|
|
618
|
-
* @module @huanlin/dsh-plugin-yet-another-subagent/client/SettingsPage
|
|
619
|
-
*/
|
|
620
|
-
/** Default shape for a brand-new draft (before the user fills in id/label). */
|
|
621
|
-
function emptyDraft() {
|
|
622
|
-
return {
|
|
623
|
-
id: "",
|
|
624
|
-
label: "",
|
|
625
|
-
model: {
|
|
626
|
-
kind: "auto",
|
|
627
|
-
provider: "",
|
|
628
|
-
model: ""
|
|
629
|
-
},
|
|
630
|
-
persona: { kind: "inherit" },
|
|
631
|
-
toolFilter: { kind: "none" },
|
|
632
|
-
maxDepth: 3,
|
|
633
|
-
backgroundMode: "continuable",
|
|
634
|
-
builtin: false
|
|
635
|
-
};
|
|
636
|
-
}
|
|
637
|
-
async function callRpc(rpc, endpoint, payload) {
|
|
638
|
-
return rpc.call("/ya-subagent", endpoint, payload);
|
|
639
|
-
}
|
|
640
|
-
/**
|
|
641
|
-
* Render the subagent profiles settings page.
|
|
642
|
-
* @param props - settings.section runtime share + locale + inject.
|
|
643
|
-
* @returns the page element.
|
|
644
|
-
*/
|
|
645
|
-
function SettingsPage({ rpc, fetchProfiles, t }) {
|
|
646
|
-
const [profiles, setProfiles] = (0, react.useState)([]);
|
|
647
|
-
const [drafts, setDrafts] = (0, react.useState)([]);
|
|
648
|
-
const [expanded, setExpanded] = (0, react.useState)(/* @__PURE__ */ new Set());
|
|
649
|
-
const [addingNew, setAddingNew] = (0, react.useState)(false);
|
|
650
|
-
const [newDraft, setNewDraft] = (0, react.useState)(emptyDraft());
|
|
651
|
-
const [toolList, setToolList] = (0, react.useState)([]);
|
|
652
|
-
const [modelGroups, setModelGroups] = (0, react.useState)([]);
|
|
653
|
-
const [loading, setLoading] = (0, react.useState)(true);
|
|
654
|
-
const [error, setError] = (0, react.useState)(void 0);
|
|
655
|
-
const [confirmDelete, setConfirmDelete] = (0, react.useState)(void 0);
|
|
656
|
-
const [repairConfirmOpen, setRepairConfirmOpen] = (0, react.useState)(false);
|
|
657
|
-
const [repairRunning, setRepairRunning] = (0, react.useState)(false);
|
|
658
|
-
const [repairStats, setRepairStats] = (0, react.useState)(void 0);
|
|
659
|
-
const [repairError, setRepairError] = (0, react.useState)(void 0);
|
|
660
|
-
const refresh = (0, react.useCallback)(async () => {
|
|
661
|
-
setLoading(true);
|
|
662
|
-
setError(void 0);
|
|
663
|
-
try {
|
|
664
|
-
const [list, toolsResult, modelsResult] = await Promise.all([
|
|
665
|
-
fetchProfiles(),
|
|
666
|
-
callRpc(rpc, "tools.list", {}),
|
|
667
|
-
rpc.call("/api", "llm.models", {})
|
|
668
|
-
]);
|
|
669
|
-
setProfiles(list);
|
|
670
|
-
setDrafts(list.map((p) => ({ ...p })));
|
|
671
|
-
if (toolsResult.ok) setToolList(toolsResult.value.tools);
|
|
672
|
-
else setToolList([]);
|
|
673
|
-
if (modelsResult.ok) setModelGroups(modelsResult.value.groups);
|
|
674
|
-
else setModelGroups([]);
|
|
675
|
-
} catch (err) {
|
|
676
|
-
setError(err instanceof Error ? err.message : String(err));
|
|
677
|
-
} finally {
|
|
678
|
-
setLoading(false);
|
|
679
|
-
}
|
|
680
|
-
}, [fetchProfiles, rpc]);
|
|
681
|
-
(0, react.useEffect)(() => {
|
|
682
|
-
refresh();
|
|
683
|
-
}, [refresh]);
|
|
684
|
-
const addProfile = (0, react.useCallback)(async () => {
|
|
685
|
-
if (newDraft.id === "" || newDraft.label === "") return;
|
|
686
|
-
const result = await callRpc(rpc, "profiles.add", { profile: newDraft });
|
|
687
|
-
if (result.ok) {
|
|
688
|
-
setProfiles(result.value.profiles);
|
|
689
|
-
setDrafts(result.value.profiles.map((p) => ({ ...p })));
|
|
690
|
-
setExpanded(/* @__PURE__ */ new Set([...expanded, newDraft.id]));
|
|
691
|
-
setAddingNew(false);
|
|
692
|
-
setNewDraft(emptyDraft());
|
|
693
|
-
} else setError(result.error.message);
|
|
694
|
-
}, [
|
|
695
|
-
expanded,
|
|
696
|
-
newDraft,
|
|
697
|
-
rpc
|
|
698
|
-
]);
|
|
699
|
-
const updateProfile = (0, react.useCallback)(async (draft) => {
|
|
700
|
-
const result = await callRpc(rpc, "profiles.update", { profile: draft });
|
|
701
|
-
if (result.ok) {
|
|
702
|
-
setProfiles(result.value.profiles);
|
|
703
|
-
setDrafts(result.value.profiles.map((p) => ({ ...p })));
|
|
704
|
-
} else setError(result.error.message);
|
|
705
|
-
}, [rpc]);
|
|
706
|
-
const removeProfile = (0, react.useCallback)(async (id) => {
|
|
707
|
-
const result = await callRpc(rpc, "profiles.remove", { id });
|
|
708
|
-
if (result.ok) {
|
|
709
|
-
setProfiles(result.value.profiles);
|
|
710
|
-
setDrafts(result.value.profiles.map((p) => ({ ...p })));
|
|
711
|
-
const next = new Set(expanded);
|
|
712
|
-
next.delete(id);
|
|
713
|
-
setExpanded(next);
|
|
714
|
-
} else setError(result.error.message);
|
|
715
|
-
}, [expanded, rpc]);
|
|
716
|
-
const runRepair = (0, react.useCallback)(async () => {
|
|
717
|
-
setRepairConfirmOpen(false);
|
|
718
|
-
setRepairRunning(true);
|
|
719
|
-
setRepairError(void 0);
|
|
720
|
-
try {
|
|
721
|
-
const result = await callRpc(rpc, "sessions.repair", {});
|
|
722
|
-
if (result.ok) setRepairStats(result.value);
|
|
723
|
-
else setRepairError(result.error.message);
|
|
724
|
-
} catch (err) {
|
|
725
|
-
setRepairError(err instanceof Error ? err.message : String(err));
|
|
726
|
-
} finally {
|
|
727
|
-
setRepairRunning(false);
|
|
728
|
-
}
|
|
729
|
-
}, [rpc]);
|
|
730
|
-
const patchDraft = (id, patch) => {
|
|
731
|
-
setDrafts((prev) => prev.map((d) => d.id === id ? {
|
|
732
|
-
...d,
|
|
733
|
-
...patch
|
|
734
|
-
} : d));
|
|
735
|
-
};
|
|
736
|
-
const toggleExpand = (id) => {
|
|
737
|
-
const next = new Set(expanded);
|
|
738
|
-
if (next.has(id)) next.delete(id);
|
|
739
|
-
else next.add(id);
|
|
740
|
-
setExpanded(next);
|
|
741
|
-
};
|
|
742
|
-
const cancelNew = () => {
|
|
743
|
-
setAddingNew(false);
|
|
744
|
-
setNewDraft(emptyDraft());
|
|
745
|
-
};
|
|
746
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
747
|
-
className: classMap.section,
|
|
748
|
-
children: [
|
|
749
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
750
|
-
className: classMap.title,
|
|
751
|
-
children: t("page.title")
|
|
752
|
-
}),
|
|
753
|
-
error !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
754
|
-
className: classMap.error,
|
|
755
|
-
children: [error, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
756
|
-
type: "button",
|
|
757
|
-
className: classMap.errorDismiss,
|
|
758
|
-
onClick: () => setError(void 0),
|
|
759
|
-
children: "×"
|
|
760
|
-
})]
|
|
761
|
-
}),
|
|
762
|
-
loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
763
|
-
className: classMap.loading,
|
|
764
|
-
children: "…"
|
|
765
|
-
}) : profiles.length === 0 && !addingNew ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
766
|
-
className: classMap.empty,
|
|
767
|
-
children: t("page.empty")
|
|
768
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("ul", {
|
|
769
|
-
className: classMap.rows,
|
|
770
|
-
children: [drafts.map((draft) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProfileCard, {
|
|
771
|
-
draft,
|
|
772
|
-
expanded: expanded.has(draft.id),
|
|
773
|
-
toolList,
|
|
774
|
-
modelGroups,
|
|
775
|
-
t,
|
|
776
|
-
onToggle: () => toggleExpand(draft.id),
|
|
777
|
-
onPatch: (patch) => patchDraft(draft.id, patch),
|
|
778
|
-
onSave: () => void updateProfile(draft),
|
|
779
|
-
onDelete: () => setConfirmDelete(draft.id)
|
|
780
|
-
}, draft.id)), addingNew && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProfileCard, {
|
|
781
|
-
draft: newDraft,
|
|
782
|
-
expanded: true,
|
|
783
|
-
isNew: true,
|
|
784
|
-
toolList,
|
|
785
|
-
modelGroups,
|
|
786
|
-
t,
|
|
787
|
-
onPatch: (patch) => setNewDraft((prev) => ({
|
|
788
|
-
...prev,
|
|
789
|
-
...patch
|
|
790
|
-
})),
|
|
791
|
-
onCreate: () => void addProfile(),
|
|
792
|
-
onCancel: cancelNew
|
|
793
|
-
}, "__new__")]
|
|
794
|
-
}),
|
|
795
|
-
!loading && !addingNew && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
796
|
-
type: "button",
|
|
797
|
-
className: classMap.addBlockButton,
|
|
798
|
-
onClick: () => setAddingNew(true),
|
|
799
|
-
children: t("page.add")
|
|
800
|
-
}),
|
|
801
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
802
|
-
className: classMap.repairBlock,
|
|
803
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
804
|
-
type: "button",
|
|
805
|
-
className: classMap.secondaryButton,
|
|
806
|
-
disabled: repairRunning,
|
|
807
|
-
onClick: () => setRepairConfirmOpen(true),
|
|
808
|
-
children: repairRunning ? t("repair.running") : t("repair.button")
|
|
809
|
-
})
|
|
810
|
-
}),
|
|
811
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
|
|
812
|
-
open: repairConfirmOpen,
|
|
813
|
-
onClose: () => {
|
|
814
|
-
setRepairConfirmOpen(false);
|
|
815
|
-
},
|
|
816
|
-
title: t("repair.confirm.title"),
|
|
817
|
-
footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
818
|
-
type: "button",
|
|
819
|
-
className: classMap.secondaryButton,
|
|
820
|
-
onClick: () => {
|
|
821
|
-
setRepairConfirmOpen(false);
|
|
822
|
-
},
|
|
823
|
-
children: t("repair.confirm.cancel")
|
|
824
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
825
|
-
type: "button",
|
|
826
|
-
className: classMap.primaryButton,
|
|
827
|
-
onClick: () => {
|
|
828
|
-
runRepair();
|
|
829
|
-
},
|
|
830
|
-
children: t("repair.confirm.proceed")
|
|
831
|
-
})] }),
|
|
832
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
833
|
-
className: classMap.confirmText,
|
|
834
|
-
children: t("repair.confirm.body")
|
|
835
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
836
|
-
className: classMap.confirmText,
|
|
837
|
-
children: t("repair.confirm.warning")
|
|
838
|
-
})]
|
|
839
|
-
}),
|
|
840
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
|
|
841
|
-
open: repairStats !== void 0 || repairError !== void 0,
|
|
842
|
-
onClose: () => {
|
|
843
|
-
setRepairStats(void 0);
|
|
844
|
-
setRepairError(void 0);
|
|
845
|
-
},
|
|
846
|
-
title: t("repair.result.title"),
|
|
847
|
-
footer: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
848
|
-
type: "button",
|
|
849
|
-
className: classMap.secondaryButton,
|
|
850
|
-
onClick: () => {
|
|
851
|
-
setRepairStats(void 0);
|
|
852
|
-
setRepairError(void 0);
|
|
853
|
-
},
|
|
854
|
-
children: t("repair.result.close")
|
|
855
|
-
}),
|
|
856
|
-
children: repairError !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
857
|
-
className: classMap.error,
|
|
858
|
-
children: [
|
|
859
|
-
t("repair.error"),
|
|
860
|
-
": ",
|
|
861
|
-
repairError
|
|
862
|
-
]
|
|
863
|
-
}) : repairStats !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
864
|
-
className: classMap.repairStats,
|
|
865
|
-
children: [
|
|
866
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
867
|
-
className: classMap.confirmText,
|
|
868
|
-
children: t("repair.result.scanned").replace("{n}", String(repairStats.scanned))
|
|
869
|
-
}),
|
|
870
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
871
|
-
className: classMap.confirmText,
|
|
872
|
-
children: t("repair.result.repaired").replace("{n}", String(repairStats.repaired))
|
|
873
|
-
}),
|
|
874
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
875
|
-
className: classMap.confirmText,
|
|
876
|
-
children: t("repair.result.skipped").replace("{n}", String(repairStats.skipped))
|
|
877
|
-
}),
|
|
878
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
879
|
-
className: classMap.confirmText,
|
|
880
|
-
children: t("repair.result.errors").replace("{n}", String(repairStats.errors.length))
|
|
881
|
-
}),
|
|
882
|
-
repairStats.errors.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
883
|
-
className: classMap.repairErrorList,
|
|
884
|
-
children: repairStats.errors.map((e, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
|
|
885
|
-
className: classMap.confirmText,
|
|
886
|
-
children: t("repair.result.errorEntry").replace("{path}", e.path).replace("{message}", e.message)
|
|
887
|
-
}, i))
|
|
888
|
-
})
|
|
889
|
-
]
|
|
890
|
-
}) : null
|
|
891
|
-
}),
|
|
892
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
|
|
893
|
-
open: confirmDelete !== void 0,
|
|
894
|
-
onClose: () => {
|
|
895
|
-
setConfirmDelete(void 0);
|
|
896
|
-
},
|
|
897
|
-
title: t("row.delete.confirm"),
|
|
898
|
-
footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
899
|
-
type: "button",
|
|
900
|
-
className: classMap.secondaryButton,
|
|
901
|
-
onClick: () => {
|
|
902
|
-
setConfirmDelete(void 0);
|
|
903
|
-
},
|
|
904
|
-
children: t("page.add.cancel")
|
|
905
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
906
|
-
type: "button",
|
|
907
|
-
className: classMap.dangerButton,
|
|
908
|
-
onClick: () => {
|
|
909
|
-
if (confirmDelete !== void 0) removeProfile(confirmDelete);
|
|
910
|
-
setConfirmDelete(void 0);
|
|
911
|
-
},
|
|
912
|
-
children: t("row.delete")
|
|
913
|
-
})] }),
|
|
914
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
915
|
-
className: classMap.confirmText,
|
|
916
|
-
children: t("row.delete.confirm")
|
|
917
|
-
})
|
|
918
|
-
})
|
|
919
|
-
]
|
|
920
|
-
});
|
|
921
|
-
}
|
|
922
|
-
function ProfileCard({ draft, expanded, isNew, toolList, modelGroups, t, onToggle, onPatch, onSave, onDelete, onCreate, onCancel }) {
|
|
923
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
924
|
-
className: classMap.rowCard,
|
|
925
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
926
|
-
className: classMap.rowHead,
|
|
927
|
-
children: [
|
|
928
|
-
!isNew && onToggle !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
929
|
-
type: "button",
|
|
930
|
-
className: classMap.chevronButton,
|
|
931
|
-
onClick: onToggle,
|
|
932
|
-
"aria-label": expanded ? t("row.collapse") : t("row.expand"),
|
|
933
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
934
|
-
className: expanded ? `${classMap.chevron} ${classMap.chevronExpanded}` : classMap.chevron,
|
|
935
|
-
"aria-hidden": "true"
|
|
936
|
-
})
|
|
937
|
-
}),
|
|
938
|
-
isNew && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
939
|
-
className: classMap.chevronSpacer,
|
|
940
|
-
"aria-hidden": "true"
|
|
941
|
-
}),
|
|
942
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
943
|
-
className: classMap.rowIdentity,
|
|
944
|
-
children: [
|
|
945
|
-
isNew ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
946
|
-
className: classMap.rowNamePlaceholder,
|
|
947
|
-
children: t("page.add")
|
|
948
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
949
|
-
className: classMap.rowName,
|
|
950
|
-
children: draft.label === "" ? draft.id : draft.label
|
|
951
|
-
}),
|
|
952
|
-
draft.builtin && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Pill, {
|
|
953
|
-
className: classMap.builtinBadge,
|
|
954
|
-
children: t("badge.builtin")
|
|
955
|
-
}),
|
|
956
|
-
!isNew && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
957
|
-
className: classMap.rowId,
|
|
958
|
-
children: draft.id
|
|
959
|
-
})
|
|
960
|
-
]
|
|
961
|
-
}),
|
|
962
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
963
|
-
className: classMap.rowActions,
|
|
964
|
-
children: isNew ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
965
|
-
type: "button",
|
|
966
|
-
className: classMap.primaryButton,
|
|
967
|
-
onClick: onCreate,
|
|
968
|
-
disabled: draft.id === "" || draft.label === "",
|
|
969
|
-
children: t("page.add.submit")
|
|
970
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
971
|
-
type: "button",
|
|
972
|
-
className: classMap.secondaryButton,
|
|
973
|
-
onClick: onCancel,
|
|
974
|
-
children: t("page.add.cancel")
|
|
975
|
-
})] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
976
|
-
type: "button",
|
|
977
|
-
className: classMap.secondaryButton,
|
|
978
|
-
onClick: onSave,
|
|
979
|
-
children: t("row.save")
|
|
980
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
981
|
-
type: "button",
|
|
982
|
-
className: classMap.dangerButton,
|
|
983
|
-
onClick: onDelete,
|
|
984
|
-
children: t("row.delete")
|
|
985
|
-
})] })
|
|
986
|
-
})
|
|
987
|
-
]
|
|
988
|
-
}), expanded && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
989
|
-
className: classMap.editor,
|
|
990
|
-
children: [
|
|
991
|
-
isNew && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
992
|
-
className: classMap.field,
|
|
993
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
994
|
-
className: classMap.fieldLabel,
|
|
995
|
-
children: t("row.id")
|
|
996
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
997
|
-
className: classMap.input,
|
|
998
|
-
value: draft.id,
|
|
999
|
-
placeholder: t("page.add.placeholder.id"),
|
|
1000
|
-
onChange: (e) => onPatch({ id: e.target.value })
|
|
1001
|
-
})]
|
|
1002
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1003
|
-
className: classMap.field,
|
|
1004
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1005
|
-
className: classMap.fieldLabel,
|
|
1006
|
-
children: t("row.label")
|
|
1007
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1008
|
-
className: classMap.input,
|
|
1009
|
-
value: draft.label,
|
|
1010
|
-
placeholder: t("page.add.placeholder.label"),
|
|
1011
|
-
onChange: (e) => onPatch({ label: e.target.value })
|
|
1012
|
-
})]
|
|
1013
|
-
})] }),
|
|
1014
|
-
!isNew && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1015
|
-
className: classMap.field,
|
|
1016
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1017
|
-
className: classMap.fieldLabel,
|
|
1018
|
-
children: t("row.label")
|
|
1019
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1020
|
-
className: classMap.input,
|
|
1021
|
-
value: draft.label,
|
|
1022
|
-
onChange: (e) => onPatch({ label: e.target.value })
|
|
1023
|
-
})]
|
|
1024
|
-
}),
|
|
1025
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1026
|
-
className: classMap.field,
|
|
1027
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1028
|
-
className: classMap.fieldLabel,
|
|
1029
|
-
children: [
|
|
1030
|
-
t("row.model.kind.auto"),
|
|
1031
|
-
" / ",
|
|
1032
|
-
t("row.model.kind.manual")
|
|
1033
|
-
]
|
|
1034
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1035
|
-
className: classMap.radioGroup,
|
|
1036
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1037
|
-
className: classMap.radioOption,
|
|
1038
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1039
|
-
type: "radio",
|
|
1040
|
-
name: `model-${draft.id}-${isNew ? "new" : ""}`,
|
|
1041
|
-
checked: draft.model.kind === "auto",
|
|
1042
|
-
onChange: () => onPatch({ model: {
|
|
1043
|
-
kind: "auto",
|
|
1044
|
-
provider: "",
|
|
1045
|
-
model: ""
|
|
1046
|
-
} })
|
|
1047
|
-
}), t("row.model.kind.auto")]
|
|
1048
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1049
|
-
className: classMap.radioOption,
|
|
1050
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1051
|
-
type: "radio",
|
|
1052
|
-
name: `model-${draft.id}-${isNew ? "new" : ""}`,
|
|
1053
|
-
checked: draft.model.kind === "manual",
|
|
1054
|
-
onChange: () => onPatch({ model: {
|
|
1055
|
-
kind: "manual",
|
|
1056
|
-
provider: draft.model.provider,
|
|
1057
|
-
model: draft.model.model
|
|
1058
|
-
} })
|
|
1059
|
-
}), t("row.model.kind.manual")]
|
|
1060
|
-
})]
|
|
1061
|
-
})]
|
|
1062
|
-
}),
|
|
1063
|
-
draft.model.kind === "manual" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1064
|
-
className: classMap.field,
|
|
1065
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1066
|
-
className: classMap.fieldLabel,
|
|
1067
|
-
children: t("row.model.provider")
|
|
1068
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DropdownPicker, {
|
|
1069
|
-
value: draft.model.provider,
|
|
1070
|
-
placeholder: t("row.model.provider.placeholder"),
|
|
1071
|
-
items: modelGroups.map((g) => ({
|
|
1072
|
-
id: g.id,
|
|
1073
|
-
label: g.name
|
|
1074
|
-
})),
|
|
1075
|
-
onSelect: (id) => onPatch({ model: {
|
|
1076
|
-
kind: "manual",
|
|
1077
|
-
provider: id,
|
|
1078
|
-
model: ""
|
|
1079
|
-
} })
|
|
1080
|
-
})]
|
|
1081
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1082
|
-
className: classMap.field,
|
|
1083
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1084
|
-
className: classMap.fieldLabel,
|
|
1085
|
-
children: t("row.model.model")
|
|
1086
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DropdownPicker, {
|
|
1087
|
-
value: draft.model.model,
|
|
1088
|
-
placeholder: t("row.model.model.placeholder"),
|
|
1089
|
-
disabled: draft.model.provider === "",
|
|
1090
|
-
items: draft.model.provider !== "" ? modelGroups.filter((g) => g.id === draft.model.provider).flatMap((g) => g.models).map((m) => ({
|
|
1091
|
-
id: m.id,
|
|
1092
|
-
label: m.name
|
|
1093
|
-
})) : [],
|
|
1094
|
-
onSelect: (id) => onPatch({ model: {
|
|
1095
|
-
kind: "manual",
|
|
1096
|
-
provider: draft.model.provider,
|
|
1097
|
-
model: id
|
|
1098
|
-
} })
|
|
1099
|
-
})]
|
|
1100
|
-
})] }),
|
|
1101
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1102
|
-
className: classMap.field,
|
|
1103
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1104
|
-
className: classMap.fieldLabel,
|
|
1105
|
-
children: t("row.persona")
|
|
1106
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1107
|
-
className: classMap.radioGroup,
|
|
1108
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1109
|
-
className: classMap.radioOption,
|
|
1110
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1111
|
-
type: "radio",
|
|
1112
|
-
name: `persona-${draft.id}-${isNew ? "new" : ""}`,
|
|
1113
|
-
checked: draft.persona.kind === "inherit",
|
|
1114
|
-
onChange: () => onPatch({ persona: { kind: "inherit" } })
|
|
1115
|
-
}), t("row.persona.kind.inherit")]
|
|
1116
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1117
|
-
className: classMap.radioOption,
|
|
1118
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1119
|
-
type: "radio",
|
|
1120
|
-
name: `persona-${draft.id}-${isNew ? "new" : ""}`,
|
|
1121
|
-
checked: draft.persona.kind === "custom",
|
|
1122
|
-
onChange: () => onPatch({ persona: {
|
|
1123
|
-
kind: "custom",
|
|
1124
|
-
text: draft.persona.kind === "custom" ? draft.persona.text : ""
|
|
1125
|
-
} })
|
|
1126
|
-
}), t("row.persona.kind.custom")]
|
|
1127
|
-
})]
|
|
1128
|
-
})]
|
|
1129
|
-
}),
|
|
1130
|
-
draft.persona.kind === "custom" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1131
|
-
className: classMap.field,
|
|
1132
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1133
|
-
className: classMap.fieldLabel,
|
|
1134
|
-
children: t("row.persona.text")
|
|
1135
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
1136
|
-
className: classMap.textarea,
|
|
1137
|
-
value: draft.persona.kind === "custom" ? draft.persona.text : "",
|
|
1138
|
-
onChange: (e) => onPatch({ persona: {
|
|
1139
|
-
kind: "custom",
|
|
1140
|
-
text: e.target.value
|
|
1141
|
-
} })
|
|
1142
|
-
})]
|
|
1143
|
-
}),
|
|
1144
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1145
|
-
className: classMap.field,
|
|
1146
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1147
|
-
className: classMap.fieldLabel,
|
|
1148
|
-
children: t("row.toolFilter")
|
|
1149
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1150
|
-
className: classMap.radioGroup,
|
|
1151
|
-
children: [
|
|
1152
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1153
|
-
className: classMap.radioOption,
|
|
1154
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1155
|
-
type: "radio",
|
|
1156
|
-
name: `toolFilter-${draft.id}-${isNew ? "new" : ""}`,
|
|
1157
|
-
checked: draft.toolFilter.kind === "none",
|
|
1158
|
-
onChange: () => onPatch({ toolFilter: { kind: "none" } })
|
|
1159
|
-
}), t("row.toolFilter.kind.none")]
|
|
1160
|
-
}),
|
|
1161
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1162
|
-
className: classMap.radioOption,
|
|
1163
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1164
|
-
type: "radio",
|
|
1165
|
-
name: `toolFilter-${draft.id}-${isNew ? "new" : ""}`,
|
|
1166
|
-
checked: draft.toolFilter.kind === "allow",
|
|
1167
|
-
onChange: () => onPatch({ toolFilter: {
|
|
1168
|
-
kind: "allow",
|
|
1169
|
-
tools: draft.toolFilter.kind === "allow" ? draft.toolFilter.tools : []
|
|
1170
|
-
} })
|
|
1171
|
-
}), t("row.toolFilter.kind.allow")]
|
|
1172
|
-
}),
|
|
1173
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1174
|
-
className: classMap.radioOption,
|
|
1175
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1176
|
-
type: "radio",
|
|
1177
|
-
name: `toolFilter-${draft.id}-${isNew ? "new" : ""}`,
|
|
1178
|
-
checked: draft.toolFilter.kind === "deny",
|
|
1179
|
-
onChange: () => onPatch({ toolFilter: {
|
|
1180
|
-
kind: "deny",
|
|
1181
|
-
tools: draft.toolFilter.kind === "deny" ? draft.toolFilter.tools : []
|
|
1182
|
-
} })
|
|
1183
|
-
}), t("row.toolFilter.kind.deny")]
|
|
1184
|
-
})
|
|
1185
|
-
]
|
|
1186
|
-
})]
|
|
1187
|
-
}),
|
|
1188
|
-
(draft.toolFilter.kind === "allow" || draft.toolFilter.kind === "deny") && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1189
|
-
className: classMap.field,
|
|
1190
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1191
|
-
className: classMap.fieldLabel,
|
|
1192
|
-
children: t("row.toolFilter.tools")
|
|
1193
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MultiSelect, {
|
|
1194
|
-
options: toolList,
|
|
1195
|
-
value: draft.toolFilter.kind === "allow" || draft.toolFilter.kind === "deny" ? [...draft.toolFilter.tools] : [],
|
|
1196
|
-
onChange: (next) => onPatch({ toolFilter: {
|
|
1197
|
-
kind: draft.toolFilter.kind === "deny" ? "deny" : "allow",
|
|
1198
|
-
tools: next
|
|
1199
|
-
} }),
|
|
1200
|
-
t
|
|
1201
|
-
})]
|
|
1202
|
-
}),
|
|
1203
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1204
|
-
className: classMap.field,
|
|
1205
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1206
|
-
className: classMap.fieldLabel,
|
|
1207
|
-
children: t("row.maxDepth")
|
|
1208
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1209
|
-
type: "number",
|
|
1210
|
-
min: 0,
|
|
1211
|
-
className: classMap.input,
|
|
1212
|
-
value: draft.maxDepth,
|
|
1213
|
-
onChange: (e) => onPatch({ maxDepth: Number(e.target.value) })
|
|
1214
|
-
})]
|
|
1215
|
-
})
|
|
1216
|
-
]
|
|
1217
|
-
})]
|
|
1218
|
-
});
|
|
1219
|
-
}
|
|
1220
|
-
function MultiSelect({ options, value, onChange, t }) {
|
|
1221
|
-
const [open, setOpen] = (0, react.useState)(false);
|
|
1222
|
-
const [query, setQuery] = (0, react.useState)("");
|
|
1223
|
-
const containerRef = (0, react.useRef)(null);
|
|
1224
|
-
(0, react.useEffect)(() => {
|
|
1225
|
-
if (!open) return;
|
|
1226
|
-
const handler = (e) => {
|
|
1227
|
-
if (containerRef.current !== null && !containerRef.current.contains(e.target)) setOpen(false);
|
|
1228
|
-
};
|
|
1229
|
-
document.addEventListener("mousedown", handler);
|
|
1230
|
-
return () => document.removeEventListener("mousedown", handler);
|
|
1231
|
-
}, [open]);
|
|
1232
|
-
const q = query.trim().toLowerCase();
|
|
1233
|
-
const filtered = q === "" ? options : options.filter((o) => o.name.toLowerCase().includes(q));
|
|
1234
|
-
const valueSet = new Set(value);
|
|
1235
|
-
const filteredAllSelected = filtered.length > 0 && filtered.every((o) => valueSet.has(o.name));
|
|
1236
|
-
const selectAllFiltered = () => {
|
|
1237
|
-
if (filteredAllSelected) onChange(value.filter((n) => !filtered.some((o) => o.name === n)));
|
|
1238
|
-
else {
|
|
1239
|
-
const next = new Set(value);
|
|
1240
|
-
for (const o of filtered) next.add(o.name);
|
|
1241
|
-
onChange([...next]);
|
|
1242
|
-
}
|
|
1243
|
-
};
|
|
1244
|
-
const toggleOne = (name) => {
|
|
1245
|
-
if (valueSet.has(name)) onChange(value.filter((n) => n !== name));
|
|
1246
|
-
else onChange([...value, name]);
|
|
1247
|
-
};
|
|
1248
|
-
const label = value.length === 0 ? t("row.toolFilter.tools") : t("row.toolFilter.tools.selected").replace("{n}", String(value.length));
|
|
1249
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1250
|
-
className: classMap.multiSelect,
|
|
1251
|
-
ref: containerRef,
|
|
1252
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1253
|
-
type: "button",
|
|
1254
|
-
className: classMap.msButton,
|
|
1255
|
-
onClick: () => setOpen((prev) => !prev),
|
|
1256
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1257
|
-
className: classMap.msButtonLabel,
|
|
1258
|
-
children: label
|
|
1259
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { className: classMap.msCaret })]
|
|
1260
|
-
}), open && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1261
|
-
className: classMap.msPopup,
|
|
1262
|
-
children: [
|
|
1263
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1264
|
-
className: classMap.msSearch,
|
|
1265
|
-
placeholder: t("row.toolFilter.tools.search"),
|
|
1266
|
-
value: query,
|
|
1267
|
-
onChange: (e) => setQuery(e.target.value)
|
|
1268
|
-
}),
|
|
1269
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1270
|
-
className: classMap.msActions,
|
|
1271
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1272
|
-
type: "button",
|
|
1273
|
-
className: classMap.msActionLink,
|
|
1274
|
-
onClick: selectAllFiltered,
|
|
1275
|
-
children: filteredAllSelected ? t("row.toolFilter.tools.clear") : t("row.toolFilter.tools.selectAll")
|
|
1276
|
-
})
|
|
1277
|
-
}),
|
|
1278
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
1279
|
-
className: classMap.msList,
|
|
1280
|
-
children: filtered.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
|
|
1281
|
-
className: classMap.msEmpty,
|
|
1282
|
-
children: t("row.toolFilter.tools.empty")
|
|
1283
|
-
}) : filtered.map((o) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
|
|
1284
|
-
className: classMap.msItem,
|
|
1285
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1286
|
-
className: classMap.msItemLabel,
|
|
1287
|
-
title: o.description,
|
|
1288
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1289
|
-
type: "checkbox",
|
|
1290
|
-
checked: valueSet.has(o.name),
|
|
1291
|
-
onChange: () => toggleOne(o.name)
|
|
1292
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1293
|
-
className: classMap.msItemName,
|
|
1294
|
-
children: o.name
|
|
1295
|
-
})]
|
|
1296
|
-
})
|
|
1297
|
-
}, o.name))
|
|
1298
|
-
})
|
|
1299
|
-
]
|
|
1300
|
-
})]
|
|
1301
|
-
});
|
|
1302
|
-
}
|
|
1303
|
-
function DropdownPicker({ value, placeholder, items, disabled, onSelect }) {
|
|
1304
|
-
const [open, setOpen] = (0, react.useState)(false);
|
|
1305
|
-
const selectedLabel = items.find((i) => i.id === value)?.label;
|
|
1306
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
|
|
1307
|
-
open: open && disabled !== true,
|
|
1308
|
-
onClose: () => {
|
|
1309
|
-
setOpen(false);
|
|
1310
|
-
},
|
|
1311
|
-
items,
|
|
1312
|
-
selectedId: value === "" ? void 0 : value,
|
|
1313
|
-
onSelect: (id) => {
|
|
1314
|
-
onSelect(id);
|
|
1315
|
-
setOpen(false);
|
|
1316
|
-
},
|
|
1317
|
-
align: "start",
|
|
1318
|
-
portal: true,
|
|
1319
|
-
anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1320
|
-
type: "button",
|
|
1321
|
-
className: classMap.dropdownButton,
|
|
1322
|
-
disabled,
|
|
1323
|
-
"aria-haspopup": "menu",
|
|
1324
|
-
"aria-expanded": open,
|
|
1325
|
-
onClick: () => {
|
|
1326
|
-
setOpen((v) => !v);
|
|
1327
|
-
},
|
|
1328
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1329
|
-
className: classMap.dropdownButtonLabel,
|
|
1330
|
-
children: selectedLabel ?? placeholder
|
|
1331
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { className: classMap.dropdownCaret })]
|
|
1332
|
-
})
|
|
1333
|
-
});
|
|
1334
|
-
}
|
|
1335
|
-
//#endregion
|
|
1336
|
-
//#region src/client/locales.ts
|
|
1337
|
-
/** Locale namespace id. */
|
|
1338
|
-
const NS = "ya-subagent";
|
|
1339
|
-
/** English dictionary. */
|
|
1340
|
-
const en = {
|
|
1341
|
-
"nav": "Subagents",
|
|
1342
|
-
"page.title": "Subagent Profiles",
|
|
1343
|
-
"page.empty": "No profiles configured. Add one below.",
|
|
1344
|
-
"page.add": "+ Add subagent",
|
|
1345
|
-
"page.add.placeholder.id": "profile-id",
|
|
1346
|
-
"page.add.placeholder.label": "Display name",
|
|
1347
|
-
"page.add.submit": "Create",
|
|
1348
|
-
"page.add.error": "Failed to add profile",
|
|
1349
|
-
"page.add.cancel": "Cancel",
|
|
1350
|
-
"row.label": "Label",
|
|
1351
|
-
"row.id": "ID",
|
|
1352
|
-
"row.model.kind.auto": "Auto (inherit parent model)",
|
|
1353
|
-
"row.model.kind.manual": "Manual",
|
|
1354
|
-
"row.model.provider": "Provider",
|
|
1355
|
-
"row.model.model": "Model",
|
|
1356
|
-
"row.model.provider.placeholder": "Select provider…",
|
|
1357
|
-
"row.model.model.placeholder": "Select model…",
|
|
1358
|
-
"row.model.noModels": "No models available",
|
|
1359
|
-
"row.persona": "Persona",
|
|
1360
|
-
"row.persona.kind.inherit": "Inherit (default persona)",
|
|
1361
|
-
"row.persona.kind.custom": "Custom",
|
|
1362
|
-
"row.persona.text": "Custom persona text",
|
|
1363
|
-
"row.toolFilter": "Tools",
|
|
1364
|
-
"row.toolFilter.kind.none": "No restriction",
|
|
1365
|
-
"row.toolFilter.kind.allow": "Whitelist",
|
|
1366
|
-
"row.toolFilter.kind.deny": "Blacklist",
|
|
1367
|
-
"row.toolFilter.tools": "Tools",
|
|
1368
|
-
"row.toolFilter.tools.search": "Search tools…",
|
|
1369
|
-
"row.toolFilter.tools.empty": "No tools found",
|
|
1370
|
-
"row.toolFilter.tools.selected": "{n} selected",
|
|
1371
|
-
"row.toolFilter.tools.selectAll": "Select all",
|
|
1372
|
-
"row.toolFilter.tools.clear": "Clear",
|
|
1373
|
-
"row.maxDepth": "Max depth",
|
|
1374
|
-
"row.delete": "Delete",
|
|
1375
|
-
"row.delete.confirm": "Delete this profile?",
|
|
1376
|
-
"row.save": "Save",
|
|
1377
|
-
"row.saved": "Saved",
|
|
1378
|
-
"row.error": "Failed to update profile",
|
|
1379
|
-
"row.expand": "Expand",
|
|
1380
|
-
"row.collapse": "Collapse",
|
|
1381
|
-
"badge.builtin": "builtin",
|
|
1382
|
-
"card.starting": "Starting…",
|
|
1383
|
-
"card.waiting": "Waiting for child session…",
|
|
1384
|
-
"card.idle": "idle",
|
|
1385
|
-
"card.running": "running",
|
|
1386
|
-
"card.completed": "completed",
|
|
1387
|
-
"card.child-running": "running",
|
|
1388
|
-
"card.child-idle": "idle",
|
|
1389
|
-
"card.toolcalls": "tool calls",
|
|
1390
|
-
"card.tokens": "Tokens",
|
|
1391
|
-
"card.calling": "calling",
|
|
1392
|
-
"card.open": "Click to open subagent session →",
|
|
1393
|
-
"card.unavailable": "session unavailable",
|
|
1394
|
-
"tree.tab": "Subagent Tree",
|
|
1395
|
-
"tree.empty": "No subagent children in this session.",
|
|
1396
|
-
"tree.rootHint": "Showing the root session’s subagent tree.",
|
|
1397
|
-
"tree.toolcalls": "tool calls",
|
|
1398
|
-
"tree.tokens": "Tokens",
|
|
1399
|
-
"tree.calling": "calling",
|
|
1400
|
-
"tree.state.running": "running",
|
|
1401
|
-
"tree.state.idle": "idle",
|
|
1402
|
-
"tree.state.settled": "settled",
|
|
1403
|
-
"repair.button": "Repair session history",
|
|
1404
|
-
"repair.confirm.title": "Repair session history?",
|
|
1405
|
-
"repair.confirm.body": "Scans every session log under $DSH_HOME/sessions and stamps \"ignorable\" on legacy ya-subagent/started events so the harness can load them again. Each modified file is backed up to .bak first.",
|
|
1406
|
-
"repair.confirm.warning": "If dsh is running, a session being written right now may conflict; prefer running this when no agent is active.",
|
|
1407
|
-
"repair.confirm.cancel": "Cancel",
|
|
1408
|
-
"repair.confirm.proceed": "Repair",
|
|
1409
|
-
"repair.running": "Repairing…",
|
|
1410
|
-
"repair.result.title": "Repair complete",
|
|
1411
|
-
"repair.result.scanned": "Scanned: {n}",
|
|
1412
|
-
"repair.result.repaired": "Repaired: {n}",
|
|
1413
|
-
"repair.result.skipped": "Skipped (already clean): {n}",
|
|
1414
|
-
"repair.result.errors": "Errors: {n}",
|
|
1415
|
-
"repair.result.errorEntry": "{path}: {message}",
|
|
1416
|
-
"repair.result.close": "Close",
|
|
1417
|
-
"repair.error": "Repair failed"
|
|
1418
|
-
};
|
|
1419
|
-
/** Chinese dictionary. */
|
|
1420
|
-
const zh = {
|
|
1421
|
-
"nav": "子代理",
|
|
1422
|
-
"page.title": "子代理配置",
|
|
1423
|
-
"page.empty": "暂无配置,请在下方添加。",
|
|
1424
|
-
"page.add": "+ 添加子代理",
|
|
1425
|
-
"page.add.placeholder.id": "profile-id",
|
|
1426
|
-
"page.add.placeholder.label": "显示名",
|
|
1427
|
-
"page.add.submit": "创建",
|
|
1428
|
-
"page.add.error": "添加失败",
|
|
1429
|
-
"page.add.cancel": "取消",
|
|
1430
|
-
"row.label": "名称",
|
|
1431
|
-
"row.id": "ID",
|
|
1432
|
-
"row.model.kind.auto": "自动(继承父代理模型)",
|
|
1433
|
-
"row.model.kind.manual": "手动指定",
|
|
1434
|
-
"row.model.provider": "Provider",
|
|
1435
|
-
"row.model.model": "模型",
|
|
1436
|
-
"row.model.provider.placeholder": "选择 Provider…",
|
|
1437
|
-
"row.model.model.placeholder": "选择模型…",
|
|
1438
|
-
"row.model.noModels": "无可用模型",
|
|
1439
|
-
"row.persona": "人设",
|
|
1440
|
-
"row.persona.kind.inherit": "跟随默认人设",
|
|
1441
|
-
"row.persona.kind.custom": "自定义",
|
|
1442
|
-
"row.persona.text": "自定义人设文本",
|
|
1443
|
-
"row.toolFilter": "工具",
|
|
1444
|
-
"row.toolFilter.kind.none": "不管",
|
|
1445
|
-
"row.toolFilter.kind.allow": "白名单",
|
|
1446
|
-
"row.toolFilter.kind.deny": "黑名单",
|
|
1447
|
-
"row.toolFilter.tools": "工具列表",
|
|
1448
|
-
"row.toolFilter.tools.search": "搜索工具…",
|
|
1449
|
-
"row.toolFilter.tools.empty": "未找到工具",
|
|
1450
|
-
"row.toolFilter.tools.selected": "已选 {n} 个",
|
|
1451
|
-
"row.toolFilter.tools.selectAll": "全选",
|
|
1452
|
-
"row.toolFilter.tools.clear": "清空",
|
|
1453
|
-
"row.maxDepth": "最大递归深度",
|
|
1454
|
-
"row.delete": "删除",
|
|
1455
|
-
"row.delete.confirm": "确定删除此配置?",
|
|
1456
|
-
"row.save": "保存",
|
|
1457
|
-
"row.saved": "已保存",
|
|
1458
|
-
"row.error": "保存失败",
|
|
1459
|
-
"row.expand": "展开",
|
|
1460
|
-
"row.collapse": "收起",
|
|
1461
|
-
"badge.builtin": "内置",
|
|
1462
|
-
"card.starting": "启动中…",
|
|
1463
|
-
"card.waiting": "等待子会话…",
|
|
1464
|
-
"card.idle": "空闲",
|
|
1465
|
-
"card.running": "运行中",
|
|
1466
|
-
"card.completed": "已完成",
|
|
1467
|
-
"card.child-running": "运行中",
|
|
1468
|
-
"card.child-idle": "空闲",
|
|
1469
|
-
"card.toolcalls": "次工具调用",
|
|
1470
|
-
"card.tokens": "Tokens",
|
|
1471
|
-
"card.calling": "调用中",
|
|
1472
|
-
"card.open": "点击打开子代理会话 →",
|
|
1473
|
-
"card.unavailable": "会话不可用",
|
|
1474
|
-
"tree.tab": "子代理树",
|
|
1475
|
-
"tree.empty": "当前会话暂无子代理。",
|
|
1476
|
-
"tree.rootHint": "显示根会话的子代理树。",
|
|
1477
|
-
"tree.toolcalls": "次工具调用",
|
|
1478
|
-
"tree.tokens": "Tokens",
|
|
1479
|
-
"tree.calling": "调用中",
|
|
1480
|
-
"tree.state.running": "运行中",
|
|
1481
|
-
"tree.state.idle": "空闲",
|
|
1482
|
-
"tree.state.settled": "已结束",
|
|
1483
|
-
"repair.button": "修复历史会话",
|
|
1484
|
-
"repair.confirm.title": "修复历史会话?",
|
|
1485
|
-
"repair.confirm.body": "将扫描 $DSH_HOME/sessions 下的所有会话日志,为遗留的 ya-subagent/started 事件补上 \"ignorable\" 标记,使 harness 能重新加载。每个被修改的文件会先备份为 .bak。",
|
|
1486
|
-
"repair.confirm.warning": "若 dsh 正在运行,正在写入的会话可能冲突;建议在无活跃代理时执行。",
|
|
1487
|
-
"repair.confirm.cancel": "取消",
|
|
1488
|
-
"repair.confirm.proceed": "修复",
|
|
1489
|
-
"repair.running": "修复中…",
|
|
1490
|
-
"repair.result.title": "修复完成",
|
|
1491
|
-
"repair.result.scanned": "扫描:{n}",
|
|
1492
|
-
"repair.result.repaired": "已修复:{n}",
|
|
1493
|
-
"repair.result.skipped": "跳过(已干净):{n}",
|
|
1494
|
-
"repair.result.errors": "错误:{n}",
|
|
1495
|
-
"repair.result.errorEntry": "{path}:{message}",
|
|
1496
|
-
"repair.result.close": "关闭",
|
|
1497
|
-
"repair.error": "修复失败"
|
|
1498
|
-
};
|
|
1499
|
-
//#endregion
|
|
1500
|
-
//#region src/client/index.ts
|
|
1501
|
-
/** Required services: settings/tool slots, locale, sessions, connection. */
|
|
1502
|
-
const inject = [
|
|
1503
|
-
"slots",
|
|
1504
|
-
"locale",
|
|
1505
|
-
"sessions",
|
|
1506
|
-
"connection"
|
|
1507
|
-
];
|
|
1508
|
-
/**
|
|
1509
|
-
* Client plugin body: register settings page + single `subagent` toolview slot.
|
|
1510
|
-
* @param ctx - client root context.
|
|
1511
|
-
*/
|
|
1512
|
-
function apply(ctx) {
|
|
1513
|
-
ctx.effect(() => ctx.locale.register(NS, {
|
|
1514
|
-
zh,
|
|
1515
|
-
en
|
|
1516
|
-
}), "ya-subagent: dictionaries");
|
|
1517
|
-
const connection = ctx.connection;
|
|
1518
|
-
const t = ctx.locale.bind(NS);
|
|
1519
|
-
const profileLabels = /* @__PURE__ */ new Map();
|
|
1520
|
-
const refreshProfileLabels = (profiles) => {
|
|
1521
|
-
profileLabels.clear();
|
|
1522
|
-
for (const p of profiles) profileLabels.set(p.id, p.label);
|
|
1523
|
-
};
|
|
1524
|
-
const fetchProfilesInternal = async () => {
|
|
1525
|
-
const result = await connection.rpc.call("/ya-subagent", "profiles.list", {});
|
|
1526
|
-
return result.ok ? result.value.profiles : [];
|
|
1527
|
-
};
|
|
1528
|
-
fetchProfilesInternal().then(refreshProfileLabels).catch((error) => {
|
|
1529
|
-
ctx.logger.error("ya-subagent: initial profile label fetch failed", error);
|
|
1530
|
-
});
|
|
1531
|
-
ctx.on("connection/reset", () => {
|
|
1532
|
-
fetchProfilesInternal().then(refreshProfileLabels).catch((error) => {
|
|
1533
|
-
ctx.logger.error("ya-subagent: post-reset profile label fetch failed", error);
|
|
1534
|
-
});
|
|
1535
|
-
});
|
|
1536
|
-
const cardInjected = () => ({
|
|
1537
|
-
sessions: ctx.sessions,
|
|
1538
|
-
profileLabelOf: (id) => profileLabels.get(id)
|
|
1539
|
-
});
|
|
1540
|
-
ctx.effect(() => ctx.slots.register({
|
|
1541
|
-
name: "tool.call.toolview",
|
|
1542
|
-
key: "subagent",
|
|
1543
|
-
locale: NS,
|
|
1544
|
-
inject: cardInjected
|
|
1545
|
-
}, SubagentCard), "ya-subagent: subagent toolview");
|
|
1546
|
-
const fetchProfiles = fetchProfilesInternal;
|
|
1547
|
-
const settingsInjected = () => ({
|
|
1548
|
-
rpc: connection.rpc,
|
|
1549
|
-
fetchProfiles,
|
|
1550
|
-
t
|
|
1551
|
-
});
|
|
1552
|
-
ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
1553
|
-
name: "settings.section",
|
|
1554
|
-
id: "ya-subagent",
|
|
1555
|
-
order: 30,
|
|
1556
|
-
label: () => t("nav"),
|
|
1557
|
-
inject: settingsInjected
|
|
1558
|
-
}, SettingsPage));
|
|
1559
|
-
const treeInjected = () => ({
|
|
1560
|
-
sessions: ctx.sessions,
|
|
1561
|
-
profileLabelOf: (id) => profileLabels.get(id)
|
|
1562
|
-
});
|
|
1563
|
-
ctx.slots.inject("conversation.view", () => ctx.slots.register({
|
|
1564
|
-
name: "conversation.view",
|
|
1565
|
-
id: "subagent-tree",
|
|
1566
|
-
order: 20,
|
|
1567
|
-
label: () => ctx.locale.bind(NS)("tree.tab"),
|
|
1568
|
-
locale: NS,
|
|
1569
|
-
inject: treeInjected
|
|
1570
|
-
}, SubagentTreeView));
|
|
1571
|
-
}
|
|
1572
|
-
//#endregion
|
|
1573
|
-
exports.apply = apply;
|
|
1574
|
-
exports.inject = inject;
|
|
1575
|
-
return module.exports;
|
|
1576
|
-
}
|
|
1577
|
-
});
|
|
1578
|
-
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: "@huanlin/dsh-plugin-yet-another-subagent",
|
|
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 react_jsx_runtime = require("react/jsx-runtime");
|
|
9
|
+
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
10
|
+
//#region \0dsh-css:D:\Projects\deepseek-harness\yet-another-subagent\src\client\SubagentCard.module.css.mjs
|
|
11
|
+
const css$2 = "/* SubagentCard toolview: a proper card with border, padding, and rounded\n * corners. Three display branches: running (spinner), continuable (live\n * child stats), foreground completed (output preview).\n *\n * Every color resolves through a --dsw-alias-* token (no literal colors). */\n\n.d8sln8w_card {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 10px 12px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n background: var(--dsw-alias-bg-layer-1);\n transition: border-color 0.15s ease;\n}\n\n.d8sln8w_card[data-clickable] {\n cursor: pointer;\n}\n\n.d8sln8w_card[data-clickable]:hover {\n border-color: var(--dsw-alias-brand-primary);\n}\n\n/* ---- Header: state dot + title + state badge ---- */\n\n.d8sln8w_header {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n}\n\n.d8sln8w_stateDot {\n flex: none;\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: var(--dsw-alias-label-caption);\n transition: background 0.15s ease;\n}\n\n/* Running: pulsing brand-color dot */\n.d8sln8w_card[data-state='running'] .d8sln8w_stateDot,\n.d8sln8w_card[data-state='child-running'] .d8sln8w_stateDot {\n background: var(--dsw-alias-brand-primary);\n animation: dsh-ya-subagent-pulse 1.4s ease-in-out infinite;\n}\n\n/* Completed: success green */\n.d8sln8w_card[data-state='completed'] .d8sln8w_stateDot {\n background: var(--dsw-alias-state-success-primary);\n}\n\n/* Child idle: success green (turn done, conversation alive) */\n.d8sln8w_card[data-state='child-idle'] .d8sln8w_stateDot {\n background: var(--dsw-alias-state-success-primary);\n}\n\n@keyframes dsh-ya-subagent-pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.4; }\n}\n\n.d8sln8w_title {\n flex: 1 1 auto;\n font-size: 13px;\n font-weight: 500;\n line-height: 20px;\n color: var(--dsw-alias-label-primary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.d8sln8w_stateBadge {\n flex: none;\n font-size: 11px;\n line-height: 18px;\n padding: 0 6px;\n border-radius: 4px;\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-tertiary);\n}\n\n/* Running / child-running: shimmer sweep over the state badge text,\n * mirroring DSH's \"Deep diving...\" turn-status effect. The pill background\n * is dropped so background-clip: text can show the gradient through the glyphs. */\n.d8sln8w_card[data-state='running'] .d8sln8w_stateBadge,\n.d8sln8w_card[data-state='child-running'] .d8sln8w_stateBadge {\n padding: 0;\n background: linear-gradient(\n 90deg,\n var(--dsw-static-deepseek-500) 0%,\n var(--dsw-static-deepseek-500) 40%,\n var(--dsw-static-deepseek-200) 50%,\n var(--dsw-static-deepseek-500) 60%,\n var(--dsw-static-deepseek-500) 100%\n );\n background-position: 100% 0;\n background-size: 250% 100%;\n background-clip: text;\n color: transparent;\n -webkit-background-clip: text;\n -webkit-text-fill-color: transparent;\n animation: dsh-ya-subagent-shimmer 1.8s linear infinite;\n}\n\n@keyframes dsh-ya-subagent-shimmer {\n to {\n background-position: 0 0;\n }\n}\n\n.d8sln8w_card[data-state='completed'] .d8sln8w_stateBadge,\n.d8sln8w_card[data-state='child-idle'] .d8sln8w_stateBadge {\n color: var(--dsw-alias-state-success-primary);\n}\n\n/* ---- Body: stats or output preview ---- */\n\n.d8sln8w_body {\n min-height: 18px;\n}\n\n.d8sln8w_stats {\n display: flex;\n align-items: center;\n gap: 12px;\n font-size: 11px;\n line-height: 16px;\n font-variant-numeric: tabular-nums;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.d8sln8w_stat {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n}\n\n.d8sln8w_activity {\n margin-top: 4px;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-secondary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.d8sln8w_output {\n display: block;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-secondary);\n overflow: hidden;\n display: -webkit-box;\n -webkit-line-clamp: 3;\n -webkit-box-orient: vertical;\n}\n\n/* ---- Footer: click hint ---- */\n\n.d8sln8w_footer {\n font-size: 11px;\n line-height: 16px;\n color: var(--dsw-alias-label-dimmed);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .d8sln8w_card[data-state='running'] .d8sln8w_stateDot,\n .d8sln8w_card[data-state='child-running'] .d8sln8w_stateDot {\n animation: none;\n }\n .d8sln8w_card[data-state='running'] .d8sln8w_stateBadge,\n .d8sln8w_card[data-state='child-running'] .d8sln8w_stateBadge {\n background-position: 0 0;\n background-size: 100% 100%;\n animation: none;\n }\n}\n";
|
|
12
|
+
const classMap$2 = {
|
|
13
|
+
"card": "d8sln8w_card",
|
|
14
|
+
"header": "d8sln8w_header",
|
|
15
|
+
"stateDot": "d8sln8w_stateDot",
|
|
16
|
+
"title": "d8sln8w_title",
|
|
17
|
+
"stateBadge": "d8sln8w_stateBadge",
|
|
18
|
+
"body": "d8sln8w_body",
|
|
19
|
+
"stats": "d8sln8w_stats",
|
|
20
|
+
"stat": "d8sln8w_stat",
|
|
21
|
+
"activity": "d8sln8w_activity",
|
|
22
|
+
"output": "d8sln8w_output",
|
|
23
|
+
"footer": "d8sln8w_footer"
|
|
24
|
+
};
|
|
25
|
+
const tagId$2 = "@huanlin/dsh-plugin-yet-another-subagent/SubagentCard.module.css";
|
|
26
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
|
|
27
|
+
const tag = document.createElement("style");
|
|
28
|
+
tag.dataset.plugin = "@huanlin/dsh-plugin-yet-another-subagent";
|
|
29
|
+
tag.dataset.pluginCss = tagId$2;
|
|
30
|
+
tag.textContent = css$2;
|
|
31
|
+
document.head.appendChild(tag);
|
|
32
|
+
}
|
|
33
|
+
//#endregion
|
|
34
|
+
//#region src/client/SubagentCard.tsx
|
|
35
|
+
/**
|
|
36
|
+
* SubagentCard — the model-facing toolcall card for the `subagent` tool.
|
|
37
|
+
*
|
|
38
|
+
* Three display branches:
|
|
39
|
+
* 1. **Running** (block is `RunningToolCall`): the tool call is in flight.
|
|
40
|
+
* Show "running" with a spinner dot; no child session to subscribe to.
|
|
41
|
+
* 2. **Continuable settled** (result text matches `started <label>
|
|
42
|
+
* subagent <id>`): subscribe to the child's `yaSubagentProgress`
|
|
43
|
+
* projection for live toolcall/token counts; clickable to open.
|
|
44
|
+
* 3. **Foreground settled** (result text is the child's output): the
|
|
45
|
+
* one-shot child has completed; show "completed" with an output
|
|
46
|
+
* preview. No child session survives.
|
|
47
|
+
*
|
|
48
|
+
* @module @huanlin/dsh-plugin-yet-another-subagent/client/SubagentCard
|
|
49
|
+
*/
|
|
50
|
+
/** Parse `block.content` for:
|
|
51
|
+
* - background: `started background subagent task <taskId>`
|
|
52
|
+
* - continuable: `started <profileLabel> subagent <subagentId>`
|
|
53
|
+
* - foreground: `completed <profileLabel> subagent <runId>\n<output>`
|
|
54
|
+
* - foreground (legacy): plain output text (no embedded id, not clickable)
|
|
55
|
+
*/
|
|
56
|
+
function parseResult(block) {
|
|
57
|
+
if (!("kind" in block)) return {};
|
|
58
|
+
const texts = [];
|
|
59
|
+
for (const item of block.content) {
|
|
60
|
+
if (item.type !== "text") continue;
|
|
61
|
+
if (item.text.match(/^started background subagent task \S+$/) !== null) return {};
|
|
62
|
+
const continuableMatch = item.text.match(/^started (\S+) subagent (\S+)$/);
|
|
63
|
+
if (continuableMatch !== null && continuableMatch[1] !== void 0 && continuableMatch[2] !== void 0) return {
|
|
64
|
+
profileLabel: continuableMatch[1],
|
|
65
|
+
subagentId: continuableMatch[2],
|
|
66
|
+
continuable: true
|
|
67
|
+
};
|
|
68
|
+
const foregroundMatch = item.text.match(/^completed (\S+) subagent (\S+)\n([\s\S]*)$/);
|
|
69
|
+
if (foregroundMatch !== null && foregroundMatch[1] !== void 0 && foregroundMatch[2] !== void 0 && foregroundMatch[3] !== void 0) return {
|
|
70
|
+
profileLabel: foregroundMatch[1],
|
|
71
|
+
subagentId: foregroundMatch[2],
|
|
72
|
+
continuable: false,
|
|
73
|
+
output: foregroundMatch[3]
|
|
74
|
+
};
|
|
75
|
+
texts.push(item.text);
|
|
76
|
+
}
|
|
77
|
+
const output = texts.join("").trim();
|
|
78
|
+
return output !== "" ? { output } : {};
|
|
79
|
+
}
|
|
80
|
+
/** Extract the `profile` parameter from a running or settled block's raw arguments. */
|
|
81
|
+
function readProfileArg(block) {
|
|
82
|
+
const argsRaw = "argsRaw" in block ? block.argsRaw : block.call?.argsRaw;
|
|
83
|
+
if (argsRaw === void 0) return void 0;
|
|
84
|
+
try {
|
|
85
|
+
const parsed = JSON.parse(argsRaw);
|
|
86
|
+
return typeof parsed.profile === "string" ? parsed.profile : void 0;
|
|
87
|
+
} catch {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/** Truncate a foreground output preview to a reasonable card line. */
|
|
92
|
+
function truncate(text, max) {
|
|
93
|
+
const single = text.replace(/\s+/g, " ").trim();
|
|
94
|
+
return single.length > max ? single.slice(0, max) + "…" : single;
|
|
95
|
+
}
|
|
96
|
+
/** Format token totals compactly: >=1000 uses "k", <1000 uses plain count. */
|
|
97
|
+
function formatTokens$1(tokens, t) {
|
|
98
|
+
if (tokens === void 0) return `0 ${t("card.tokens")}`;
|
|
99
|
+
const total = tokens.input + tokens.output + tokens.cacheRead + tokens.cacheWrite + tokens.reasoning;
|
|
100
|
+
return `${total >= 1e3 ? `${(total / 1e3).toFixed(1)}k` : String(total)} ${t("card.tokens")}`;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Subscribe to a child session's `yaSubagentProgress` projection through the
|
|
104
|
+
* sessions binding face. Returns `undefined` while the binding is absent or
|
|
105
|
+
* the projection has not pushed yet.
|
|
106
|
+
*/
|
|
107
|
+
function useChildProgress$1(sessions, childId) {
|
|
108
|
+
const [progress, setProgress] = (0, react.useState)(void 0);
|
|
109
|
+
(0, react.useEffect)(() => {
|
|
110
|
+
if (childId === void 0) return;
|
|
111
|
+
const binding = sessions.binding(childId);
|
|
112
|
+
if (binding === void 0) return;
|
|
113
|
+
const face = binding.session.projections.faceOf("yaSubagentProgress");
|
|
114
|
+
if (face === void 0) return;
|
|
115
|
+
const snapshot = face.getSnapshot();
|
|
116
|
+
setProgress(snapshot ?? void 0);
|
|
117
|
+
return face.subscribe(() => {
|
|
118
|
+
const next = face.getSnapshot();
|
|
119
|
+
setProgress(next ?? void 0);
|
|
120
|
+
});
|
|
121
|
+
}, [sessions, childId]);
|
|
122
|
+
return progress;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Subscribe to the parent session's `subagentProfile` projection to resolve
|
|
126
|
+
* a running call's childId (before the result text embeds it). Returns the
|
|
127
|
+
* childId for this callId, or undefined.
|
|
128
|
+
*/
|
|
129
|
+
function useChildIdFromProjection(sessions, sessionId, callId) {
|
|
130
|
+
const [childId, setChildId] = (0, react.useState)(void 0);
|
|
131
|
+
(0, react.useEffect)(() => {
|
|
132
|
+
const binding = sessions.binding(sessionId);
|
|
133
|
+
if (binding === void 0) return;
|
|
134
|
+
const face = binding.session.projections.faceOf("subagentProfile");
|
|
135
|
+
if (face === void 0) return;
|
|
136
|
+
const read = () => {
|
|
137
|
+
return face.getSnapshot()?.calls?.[callId];
|
|
138
|
+
};
|
|
139
|
+
setChildId(read());
|
|
140
|
+
return face.subscribe(() => setChildId(read()));
|
|
141
|
+
}, [
|
|
142
|
+
sessions,
|
|
143
|
+
sessionId,
|
|
144
|
+
callId
|
|
145
|
+
]);
|
|
146
|
+
return childId;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Render one `subagent` tool call as a compact live card.
|
|
150
|
+
* @param props - keyed toolview payload + locale seat + sessions inject.
|
|
151
|
+
* @returns the dedicated subagent card.
|
|
152
|
+
*/
|
|
153
|
+
function SubagentCard({ block, callId, toolName, sessionId, sessions, profileLabelOf, t }) {
|
|
154
|
+
const result = parseResult(block);
|
|
155
|
+
const projectedChildId = useChildIdFromProjection(sessions, sessionId, callId);
|
|
156
|
+
const childId = result.subagentId ?? projectedChildId;
|
|
157
|
+
const profileId = readProfileArg(block);
|
|
158
|
+
const profileLabel = result.profileLabel ?? (profileId !== void 0 ? profileLabelOf(profileId) : void 0) ?? toolName;
|
|
159
|
+
const progress = useChildProgress$1(sessions, childId);
|
|
160
|
+
const isRunning = !("kind" in block);
|
|
161
|
+
const isContinuable = childId !== void 0;
|
|
162
|
+
const liveState = progress?.state;
|
|
163
|
+
const cardState = isRunning ? "running" : isContinuable ? liveState === "running" ? "child-running" : "child-idle" : "completed";
|
|
164
|
+
const toolCallCount = progress?.toolCallCount ?? 0;
|
|
165
|
+
const clickable = childId !== void 0;
|
|
166
|
+
const openChild = () => {
|
|
167
|
+
if (childId === void 0) return;
|
|
168
|
+
const parentId = sessionId;
|
|
169
|
+
const retained = sessions.subagentAddress?.(childId);
|
|
170
|
+
if (retained !== void 0) try {
|
|
171
|
+
sessions.openSubagent(retained);
|
|
172
|
+
return;
|
|
173
|
+
} catch {}
|
|
174
|
+
for (const mode of ["continuable", "one-shot"]) try {
|
|
175
|
+
sessions.openSubagent({
|
|
176
|
+
parentSessionId: parentId,
|
|
177
|
+
childSessionId: childId,
|
|
178
|
+
mode
|
|
179
|
+
});
|
|
180
|
+
return;
|
|
181
|
+
} catch {}
|
|
182
|
+
sessions.refreshSubagents(parentId).then(() => {
|
|
183
|
+
const r = sessions.subagentAddress?.(childId);
|
|
184
|
+
if (r !== void 0) try {
|
|
185
|
+
sessions.openSubagent(r);
|
|
186
|
+
} catch (e) {
|
|
187
|
+
console.error("[ya-subagent] openSubagent failed after refresh:", e);
|
|
188
|
+
}
|
|
189
|
+
else console.error("[ya-subagent] child not found in catalog after refresh:", childId);
|
|
190
|
+
}).catch((e) => {
|
|
191
|
+
console.error("[ya-subagent] refreshSubagents failed:", e);
|
|
192
|
+
});
|
|
193
|
+
};
|
|
194
|
+
const stateLabel = t(`card.${cardState}`);
|
|
195
|
+
const showOutput = cardState === "completed" && result.output !== void 0;
|
|
196
|
+
const activity = progress?.activity;
|
|
197
|
+
const activityText = activity === void 0 ? void 0 : activity.kind === "tool" ? activity.args !== void 0 ? `${activity.name} ${activity.args}` : `${t("card.calling")} ${activity.name}` : activity.text;
|
|
198
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
199
|
+
className: classMap$2.card,
|
|
200
|
+
"data-tool": toolName,
|
|
201
|
+
"data-state": cardState,
|
|
202
|
+
"data-clickable": clickable || void 0,
|
|
203
|
+
onClick: clickable ? openChild : void 0,
|
|
204
|
+
role: clickable ? "button" : void 0,
|
|
205
|
+
tabIndex: clickable ? 0 : void 0,
|
|
206
|
+
children: [
|
|
207
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
208
|
+
className: classMap$2.header,
|
|
209
|
+
children: [
|
|
210
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
211
|
+
className: classMap$2.stateDot,
|
|
212
|
+
"aria-hidden": true
|
|
213
|
+
}),
|
|
214
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
215
|
+
className: classMap$2.title,
|
|
216
|
+
children: profileLabel
|
|
217
|
+
}),
|
|
218
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
219
|
+
className: classMap$2.stateBadge,
|
|
220
|
+
children: stateLabel
|
|
221
|
+
})
|
|
222
|
+
]
|
|
223
|
+
}),
|
|
224
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
225
|
+
className: classMap$2.body,
|
|
226
|
+
children: showOutput ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
227
|
+
className: classMap$2.output,
|
|
228
|
+
children: truncate(result.output, 200)
|
|
229
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
230
|
+
className: classMap$2.stats,
|
|
231
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
232
|
+
className: classMap$2.stat,
|
|
233
|
+
children: [
|
|
234
|
+
toolCallCount,
|
|
235
|
+
" ",
|
|
236
|
+
t("card.toolcalls")
|
|
237
|
+
]
|
|
238
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
239
|
+
className: classMap$2.stat,
|
|
240
|
+
children: formatTokens$1(progress?.tokens, t)
|
|
241
|
+
})]
|
|
242
|
+
}), activityText !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
243
|
+
className: classMap$2.activity,
|
|
244
|
+
title: activityText,
|
|
245
|
+
children: activityText
|
|
246
|
+
})] })
|
|
247
|
+
}),
|
|
248
|
+
clickable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
249
|
+
className: classMap$2.footer,
|
|
250
|
+
children: t("card.open")
|
|
251
|
+
})
|
|
252
|
+
]
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
//#endregion
|
|
256
|
+
//#region \0dsh-css:D:\Projects\deepseek-harness\yet-another-subagent\src\client\SubagentTreeView.module.css.mjs
|
|
257
|
+
const css$1 = "/* SubagentTreeView: a recursive tree of subagent children with live progress. */\n\n.dkph7p9_tree {\n display: flex;\n flex-direction: column;\n gap: 4px;\n padding: 16px;\n max-width: 720px;\n margin: 0 auto;\n}\n\n.dkph7p9_rootHint {\n font-size: 11px;\n line-height: 16px;\n color: var(--dsw-alias-label-tertiary);\n margin-bottom: 4px;\n}\n\n.dkph7p9_empty {\n padding: 32px 16px;\n text-align: center;\n font-size: 13px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.dkph7p9_childList {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.dkph7p9_node {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.dkph7p9_row {\n display: flex;\n flex-direction: column;\n gap: 4px;\n padding: 10px 12px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n background: var(--dsw-alias-bg-layer-1);\n cursor: pointer;\n transition: border-color 0.15s ease, background 0.15s ease;\n}\n\n.dkph7p9_row:hover {\n border-color: var(--dsw-alias-brand-primary);\n}\n\n.dkph7p9_row:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 2px;\n}\n\n/* Highlight the currently open session. */\n.dkph7p9_row[data-current] {\n border-color: var(--dsw-alias-brand-primary);\n background: var(--dsw-alias-bg-layer-2);\n}\n\n.dkph7p9_rowHeader {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n}\n\n.dkph7p9_dot {\n flex: none;\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: var(--dsw-alias-label-caption);\n transition: background 0.15s ease;\n}\n\n.dkph7p9_row[data-state='running'] .dkph7p9_dot {\n background: var(--dsw-alias-brand-primary);\n animation: dsh-ya-tree-pulse 1.4s ease-in-out infinite;\n}\n\n.dkph7p9_row[data-state='idle'] .dkph7p9_dot,\n.dkph7p9_row[data-state='settled'] .dkph7p9_dot {\n background: var(--dsw-alias-state-success-primary);\n}\n\n@keyframes dsh-ya-tree-pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.4; }\n}\n\n.dkph7p9_label {\n flex: 1 1 auto;\n font-size: 13px;\n font-weight: 500;\n line-height: 20px;\n color: var(--dsw-alias-label-primary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.dkph7p9_badge {\n flex: none;\n font-size: 11px;\n line-height: 18px;\n padding: 0 6px;\n border-radius: 4px;\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-tertiary);\n}\n\n.dkph7p9_row[data-state='running'] .dkph7p9_badge {\n padding: 0;\n background: linear-gradient(\n 90deg,\n var(--dsw-static-deepseek-500) 0%,\n var(--dsw-static-deepseek-500) 40%,\n var(--dsw-static-deepseek-200) 50%,\n var(--dsw-static-deepseek-500) 60%,\n var(--dsw-static-deepseek-500) 100%\n );\n background-position: 100% 0;\n background-size: 250% 100%;\n background-clip: text;\n color: transparent;\n -webkit-background-clip: text;\n -webkit-text-fill-color: transparent;\n animation: dsh-ya-tree-shimmer 1.8s linear infinite;\n}\n\n@keyframes dsh-ya-tree-shimmer {\n to { background-position: 0 0; }\n}\n\n.dkph7p9_rowStats {\n display: flex;\n align-items: center;\n gap: 12px;\n font-size: 11px;\n line-height: 16px;\n font-variant-numeric: tabular-nums;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.dkph7p9_stat {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n}\n\n.dkph7p9_activity {\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-secondary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .dkph7p9_row[data-state='running'] .dkph7p9_dot {\n animation: none;\n }\n .dkph7p9_row[data-state='running'] .dkph7p9_badge {\n background-position: 0 0;\n background-size: 100% 100%;\n animation: none;\n }\n}\n";
|
|
258
|
+
const classMap$1 = {
|
|
259
|
+
"tree": "dkph7p9_tree",
|
|
260
|
+
"rootHint": "dkph7p9_rootHint",
|
|
261
|
+
"empty": "dkph7p9_empty",
|
|
262
|
+
"childList": "dkph7p9_childList",
|
|
263
|
+
"node": "dkph7p9_node",
|
|
264
|
+
"row": "dkph7p9_row",
|
|
265
|
+
"rowHeader": "dkph7p9_rowHeader",
|
|
266
|
+
"dot": "dkph7p9_dot",
|
|
267
|
+
"label": "dkph7p9_label",
|
|
268
|
+
"badge": "dkph7p9_badge",
|
|
269
|
+
"rowStats": "dkph7p9_rowStats",
|
|
270
|
+
"stat": "dkph7p9_stat",
|
|
271
|
+
"activity": "dkph7p9_activity"
|
|
272
|
+
};
|
|
273
|
+
const tagId$1 = "@huanlin/dsh-plugin-yet-another-subagent/SubagentTreeView.module.css";
|
|
274
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
|
|
275
|
+
const tag = document.createElement("style");
|
|
276
|
+
tag.dataset.plugin = "@huanlin/dsh-plugin-yet-another-subagent";
|
|
277
|
+
tag.dataset.pluginCss = tagId$1;
|
|
278
|
+
tag.textContent = css$1;
|
|
279
|
+
document.head.appendChild(tag);
|
|
280
|
+
}
|
|
281
|
+
//#endregion
|
|
282
|
+
//#region src/client/SubagentTreeView.tsx
|
|
283
|
+
/**
|
|
284
|
+
* SubagentTreeView — a `conversation.view` entry showing the root session's
|
|
285
|
+
* full subagent tree (all depths) with live progress.
|
|
286
|
+
*
|
|
287
|
+
* Uses `sessions.subagentsByParent` (the catalog) as the primary tree
|
|
288
|
+
* structure source — this works for ALL depths without needing per-session
|
|
289
|
+
* bindings. `setSubagentCatalogOpen` keeps catalogs auto-refreshing.
|
|
290
|
+
* Projections (`yaSubagentProgress`) are used additionally when a session
|
|
291
|
+
* binding is available (current session + opened children) for richer data.
|
|
292
|
+
*
|
|
293
|
+
* @module @huanlin/dsh-plugin-yet-another-subagent/client/SubagentTreeView
|
|
294
|
+
*/
|
|
295
|
+
/** Walk up `subagentAddress` to find the root session id. */
|
|
296
|
+
function findRootSession(sessions, sessionId) {
|
|
297
|
+
let current = sessionId;
|
|
298
|
+
for (let i = 0; i < 32; i++) {
|
|
299
|
+
const addr = sessions.subagentAddress?.(current);
|
|
300
|
+
if (addr === void 0) return current;
|
|
301
|
+
current = addr.parentSessionId;
|
|
302
|
+
}
|
|
303
|
+
return sessionId;
|
|
304
|
+
}
|
|
305
|
+
/** Subscribe to a session's `subagentProfile` projection → children map. */
|
|
306
|
+
function useChildren(sessions, sessionId) {
|
|
307
|
+
const [children, setChildren] = (0, react.useState)({});
|
|
308
|
+
(0, react.useEffect)(() => {
|
|
309
|
+
const binding = sessions.binding(sessionId);
|
|
310
|
+
if (binding === void 0) return;
|
|
311
|
+
const face = binding.session.projections.faceOf("subagentProfile");
|
|
312
|
+
if (face === void 0) return;
|
|
313
|
+
const read = () => {
|
|
314
|
+
const snap = face.getSnapshot();
|
|
315
|
+
setChildren(snap?.children ?? {});
|
|
316
|
+
};
|
|
317
|
+
read();
|
|
318
|
+
return face.subscribe(read);
|
|
319
|
+
}, [sessions, sessionId]);
|
|
320
|
+
return children;
|
|
321
|
+
}
|
|
322
|
+
/** Subscribe to a child session's `yaSubagentProgress` projection (if binding exists). */
|
|
323
|
+
function useChildProgress(sessions, childId) {
|
|
324
|
+
const [progress, setProgress] = (0, react.useState)(void 0);
|
|
325
|
+
(0, react.useEffect)(() => {
|
|
326
|
+
const binding = sessions.binding(childId);
|
|
327
|
+
if (binding === void 0) return;
|
|
328
|
+
const face = binding.session.projections.faceOf("yaSubagentProgress");
|
|
329
|
+
if (face === void 0) return;
|
|
330
|
+
const snapshot = face.getSnapshot();
|
|
331
|
+
setProgress(snapshot ?? void 0);
|
|
332
|
+
return face.subscribe(() => {
|
|
333
|
+
const next = face.getSnapshot();
|
|
334
|
+
setProgress(next ?? void 0);
|
|
335
|
+
});
|
|
336
|
+
}, [sessions, childId]);
|
|
337
|
+
return progress;
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Recursively refresh catalogs for a session and all its descendants.
|
|
341
|
+
* Returns immediately — `subagentsByParent` updates reactively.
|
|
342
|
+
*/
|
|
343
|
+
function refreshTree(sessions, parentId) {
|
|
344
|
+
sessions.refreshSubagents(parentId).then(() => {
|
|
345
|
+
const catalog = sessions.subagentsByParent?.[parentId];
|
|
346
|
+
if (catalog === void 0) return;
|
|
347
|
+
for (const entry of catalog.entries) if (entry.kind === "child" && entry.hasChildren === true) refreshTree(sessions, entry.id);
|
|
348
|
+
}).catch(() => {});
|
|
349
|
+
}
|
|
350
|
+
/** Format token totals: >=1000 uses "k", <1000 uses plain count. */
|
|
351
|
+
function formatTokens(tokens, t) {
|
|
352
|
+
if (tokens === void 0) return `0 ${t("tree.tokens")}`;
|
|
353
|
+
const total = tokens.input + tokens.output + tokens.cacheRead + tokens.cacheWrite + tokens.reasoning;
|
|
354
|
+
return `${total >= 1e3 ? `${(total / 1e3).toFixed(1)}k` : String(total)} ${t("tree.tokens")}`;
|
|
355
|
+
}
|
|
356
|
+
function buildTree(sessions, parentId, projectionChildren, profileLabelOf, visited) {
|
|
357
|
+
const catalog = sessions.subagentsByParent?.[parentId];
|
|
358
|
+
if (catalog === void 0) return [];
|
|
359
|
+
const nodes = [];
|
|
360
|
+
for (const entry of catalog.entries) {
|
|
361
|
+
if (entry.kind !== "child") continue;
|
|
362
|
+
if (visited.has(entry.id)) continue;
|
|
363
|
+
visited.add(entry.id);
|
|
364
|
+
const profileId = projectionChildren[entry.id];
|
|
365
|
+
const label = (profileId !== void 0 ? profileLabelOf(profileId) : void 0) ?? entry.label ?? entry.id;
|
|
366
|
+
const childProjectionChildren = {};
|
|
367
|
+
const binding = sessions.binding(entry.id);
|
|
368
|
+
if (binding !== void 0) {
|
|
369
|
+
const face = binding.session.projections.faceOf("subagentProfile");
|
|
370
|
+
if (face !== void 0) {
|
|
371
|
+
const snap = face.getSnapshot();
|
|
372
|
+
if (snap?.children !== void 0) for (const [k, v] of Object.entries(snap.children)) childProjectionChildren[k] = v;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
const children = entry.hasChildren === true ? buildTree(sessions, entry.id, childProjectionChildren, profileLabelOf, visited) : [];
|
|
376
|
+
nodes.push({
|
|
377
|
+
id: entry.id,
|
|
378
|
+
label,
|
|
379
|
+
mode: entry.mode,
|
|
380
|
+
catalogActivity: entry.activity,
|
|
381
|
+
hasChildren: children.length > 0,
|
|
382
|
+
children
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
return nodes;
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Render the subagent tree view. Always shows the ROOT session's full tree;
|
|
389
|
+
* highlights the current session if it is a subagent.
|
|
390
|
+
*/
|
|
391
|
+
function SubagentTreeView({ sessionId, sessions, profileLabelOf, t }) {
|
|
392
|
+
const rootId = findRootSession(sessions, sessionId);
|
|
393
|
+
const isSubagent = rootId !== sessionId;
|
|
394
|
+
(0, react.useEffect)(() => {
|
|
395
|
+
sessions.setSubagentCatalogOpen(rootId, true);
|
|
396
|
+
refreshTree(sessions, rootId);
|
|
397
|
+
return () => {
|
|
398
|
+
sessions.setSubagentCatalogOpen(rootId, false);
|
|
399
|
+
};
|
|
400
|
+
}, [sessions, rootId]);
|
|
401
|
+
const projectionChildren = useChildren(sessions, rootId);
|
|
402
|
+
const [, setTick] = (0, react.useState)(0);
|
|
403
|
+
(0, react.useEffect)(() => {
|
|
404
|
+
const interval = setInterval(() => setTick((t) => t + 1), 2e3);
|
|
405
|
+
return () => clearInterval(interval);
|
|
406
|
+
}, []);
|
|
407
|
+
const tree = (0, react.useMemo)(() => buildTree(sessions, rootId, projectionChildren, profileLabelOf, /* @__PURE__ */ new Set()), [
|
|
408
|
+
sessions,
|
|
409
|
+
rootId,
|
|
410
|
+
projectionChildren,
|
|
411
|
+
profileLabelOf,
|
|
412
|
+
sessions.subagentsByParent
|
|
413
|
+
]);
|
|
414
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
415
|
+
className: classMap$1.tree,
|
|
416
|
+
children: [isSubagent && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
417
|
+
className: classMap$1.rootHint,
|
|
418
|
+
children: t("tree.rootHint")
|
|
419
|
+
}), tree.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
420
|
+
className: classMap$1.empty,
|
|
421
|
+
children: t("tree.empty")
|
|
422
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TreeNodes, {
|
|
423
|
+
nodes: tree,
|
|
424
|
+
currentSessionId: sessionId,
|
|
425
|
+
sessions,
|
|
426
|
+
t,
|
|
427
|
+
depth: 0
|
|
428
|
+
})]
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
/** Render a list of tree nodes. */
|
|
432
|
+
function TreeNodes({ nodes, currentSessionId, sessions, t, depth }) {
|
|
433
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
434
|
+
className: classMap$1.childList,
|
|
435
|
+
children: nodes.map((node) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TreeNodeView, {
|
|
436
|
+
node,
|
|
437
|
+
currentSessionId,
|
|
438
|
+
sessions,
|
|
439
|
+
t,
|
|
440
|
+
depth
|
|
441
|
+
}, node.id))
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
/** One tree node row + its children (recursively). */
|
|
445
|
+
function TreeNodeView({ node, currentSessionId, sessions, t, depth }) {
|
|
446
|
+
const progress = useChildProgress(sessions, node.id);
|
|
447
|
+
const isCurrent = node.id === currentSessionId;
|
|
448
|
+
const state = progress?.state ?? (node.catalogActivity === "running" ? "running" : "idle");
|
|
449
|
+
const stateLabel = t(`tree.state.${state}`);
|
|
450
|
+
const toolCallCount = progress?.toolCallCount ?? 0;
|
|
451
|
+
const tokens = formatTokens(progress?.tokens, t);
|
|
452
|
+
const activity = progress?.activity;
|
|
453
|
+
const activityText = activity === void 0 ? void 0 : activity.kind === "tool" ? activity.args !== void 0 ? `${activity.name} ${activity.args}` : `${t("tree.calling")} ${activity.name}` : activity.text;
|
|
454
|
+
const openChild = () => {
|
|
455
|
+
const retained = sessions.subagentAddress?.(node.id);
|
|
456
|
+
if (retained !== void 0) try {
|
|
457
|
+
sessions.openSubagent(retained);
|
|
458
|
+
return;
|
|
459
|
+
} catch {}
|
|
460
|
+
const addr = sessions.subagentAddress?.(node.id);
|
|
461
|
+
if (addr !== void 0) try {
|
|
462
|
+
sessions.openSubagent(addr);
|
|
463
|
+
return;
|
|
464
|
+
} catch {}
|
|
465
|
+
};
|
|
466
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
467
|
+
className: classMap$1.node,
|
|
468
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
469
|
+
className: classMap$1.row,
|
|
470
|
+
"data-state": state,
|
|
471
|
+
"data-current": isCurrent || void 0,
|
|
472
|
+
style: { paddingLeft: `${12 + depth * 20}px` },
|
|
473
|
+
onClick: openChild,
|
|
474
|
+
role: "button",
|
|
475
|
+
tabIndex: 0,
|
|
476
|
+
children: [
|
|
477
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
478
|
+
className: classMap$1.rowHeader,
|
|
479
|
+
children: [
|
|
480
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
481
|
+
className: classMap$1.dot,
|
|
482
|
+
"aria-hidden": true
|
|
483
|
+
}),
|
|
484
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
485
|
+
className: classMap$1.label,
|
|
486
|
+
children: node.label
|
|
487
|
+
}),
|
|
488
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
489
|
+
className: classMap$1.badge,
|
|
490
|
+
children: stateLabel
|
|
491
|
+
})
|
|
492
|
+
]
|
|
493
|
+
}),
|
|
494
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
495
|
+
className: classMap$1.rowStats,
|
|
496
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
497
|
+
className: classMap$1.stat,
|
|
498
|
+
children: [
|
|
499
|
+
toolCallCount,
|
|
500
|
+
" ",
|
|
501
|
+
t("tree.toolcalls")
|
|
502
|
+
]
|
|
503
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
504
|
+
className: classMap$1.stat,
|
|
505
|
+
children: tokens
|
|
506
|
+
})]
|
|
507
|
+
}),
|
|
508
|
+
activityText !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
509
|
+
className: classMap$1.activity,
|
|
510
|
+
title: activityText,
|
|
511
|
+
children: activityText
|
|
512
|
+
})
|
|
513
|
+
]
|
|
514
|
+
}), node.children.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TreeNodes, {
|
|
515
|
+
nodes: node.children,
|
|
516
|
+
currentSessionId,
|
|
517
|
+
sessions,
|
|
518
|
+
t,
|
|
519
|
+
depth: depth + 1
|
|
520
|
+
})]
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
//#endregion
|
|
524
|
+
//#region \0dsh-css:D:\Projects\deepseek-harness\yet-another-subagent\src\client\SettingsPage.module.css.mjs
|
|
525
|
+
const css = "/* Subagent settings section, in the settings-panel design language shared\r\n * with ModelsSection / GeneralSection: 14/22 body, 12/18 caption, 16/24 title,\r\n * capsule controls (h36 r18 primary, h28 r14 secondary), 32px fields,\r\n * border-l2 hairlines, and the editor as a filled module on the panel fill.\r\n *\r\n * Every color resolves through a --dsw-alias-* token (no literal colors). */\r\n\r\n.dun7765_section {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 12px;\r\n max-width: 720px;\r\n color: var(--dsw-alias-label-primary);\r\n}\r\n\r\n.dun7765_title {\r\n margin: 0;\r\n font-size: 16px;\r\n line-height: 24px;\r\n font-weight: 500;\r\n color: var(--dsw-alias-label-primary);\r\n}\r\n\r\n.dun7765_intro {\r\n margin: 0;\r\n font-size: 14px;\r\n line-height: 22px;\r\n color: var(--dsw-alias-label-tertiary);\r\n}\r\n\r\n.dun7765_error {\r\n margin: 0;\r\n padding: 8px 12px;\r\n border: 1px solid var(--dsw-alias-state-error-primary);\r\n border-radius: 8px;\r\n background: var(--dsw-alias-interactive-bg-hover-danger);\r\n font-size: 12px;\r\n line-height: 18px;\r\n color: var(--dsw-alias-state-error-primary);\r\n display: flex;\r\n align-items: center;\r\n justify-content: space-between;\r\n gap: 8px;\r\n}\r\n\r\n.dun7765_errorDismiss {\r\n flex: none;\r\n border: none;\r\n background: transparent;\r\n color: inherit;\r\n font-size: 16px;\r\n line-height: 1;\r\n cursor: pointer;\r\n padding: 0 4px;\r\n}\r\n\r\n.dun7765_rows {\r\n list-style: none;\r\n margin: 12px 0 0;\r\n padding: 0;\r\n display: flex;\r\n flex-direction: column;\r\n gap: 8px;\r\n}\r\n\r\n/* A configured profile: outlined on the panel fill, matching the rowCard\r\n * chrome in ModelsSection. */\r\n.dun7765_rowCard {\r\n border: 1px solid var(--dsw-alias-border-l2);\r\n border-radius: 12px;\r\n padding: 12px 14px;\r\n display: flex;\r\n flex-direction: column;\r\n gap: 12px;\r\n}\r\n\r\n.dun7765_rowHead {\r\n display: flex;\r\n align-items: center;\r\n gap: 10px;\r\n}\r\n\r\n/* Chevron toggle: a small square with two borders, rotated to point right\r\n * (collapsed) or down (expanded). Pure CSS, no icon font. */\r\n.dun7765_chevronButton {\r\n flex: none;\r\n width: 24px;\r\n height: 24px;\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n border: none;\r\n background: transparent;\r\n color: var(--dsw-alias-label-secondary);\r\n cursor: pointer;\r\n padding: 0;\r\n border-radius: 4px;\r\n}\r\n\r\n.dun7765_chevronButton:hover {\r\n background: var(--dsw-alias-interactive-bg-hover-solid);\r\n color: var(--dsw-alias-label-primary);\r\n}\r\n\r\n/* Spacer that occupies the chevron slot on cards without a toggle (e.dun7765_g. the\r\n * new-draft card, which is always expanded). Keeps row-head alignment. */\r\n.dun7765_chevronSpacer {\r\n flex: none;\r\n width: 24px;\r\n height: 24px;\r\n}\r\n\r\n.dun7765_chevron {\r\n width: 7px;\r\n height: 7px;\r\n border-right: 1.5px solid currentColor;\r\n border-bottom: 1.5px solid currentColor;\r\n transform: rotate(-45deg);\r\n transition: transform 0.15s ease;\r\n}\r\n\r\n.dun7765_chevronExpanded {\r\n transform: rotate(45deg);\r\n}\r\n\r\n.dun7765_rowIdentity {\r\n display: inline-flex;\r\n align-items: center;\r\n gap: 6px;\r\n min-width: 0;\r\n flex: 1 1 auto;\r\n}\r\n\r\n.dun7765_rowName {\r\n font-size: 14px;\r\n line-height: 22px;\r\n font-weight: 500;\r\n color: var(--dsw-alias-label-primary);\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n}\r\n\r\n.dun7765_rowNamePlaceholder {\r\n font-size: 14px;\r\n line-height: 22px;\r\n font-weight: 500;\r\n color: var(--dsw-alias-label-tertiary);\r\n font-style: italic;\r\n}\r\n\r\n/* Builtin badge: uses DSH <Pill> with a brand-colored override to mark\r\n * seed profiles. The Pill's default chrome is bg-layer-2/label-secondary;\r\n * this override shifts it to the brand fill for the \"builtin\" emphasis. */\r\n.dun7765_builtinBadge {\r\n height: 18px;\r\n padding: 0 6px;\r\n border-radius: 4px;\r\n font-size: 11px;\r\n line-height: 16px;\r\n font-weight: 500;\r\n background: var(--dsw-alias-brand-primary);\r\n color: var(--dsw-alias-label-primary-foreground);\r\n letter-spacing: 0.02em;\r\n}\r\n\r\n.dun7765_rowId {\r\n flex: none;\r\n padding: 1px 6px;\r\n border: 1px solid var(--dsw-alias-border-l3);\r\n border-radius: 4px;\r\n font-size: 11px;\r\n line-height: 16px;\r\n font-family: var(--dsw-font-markdown-code-block-small, monospace);\r\n color: var(--dsw-alias-label-secondary);\r\n}\r\n\r\n.dun7765_rowActions {\r\n display: inline-flex;\r\n align-items: center;\r\n gap: 4px;\r\n margin-left: auto;\r\n flex: none;\r\n}\r\n\r\n/* Editor surface: a filled module on the panel, matching ModelsSection's\r\n * editor chrome (bg-module-platform, r12, p14/16). */\r\n.dun7765_editor {\r\n border-radius: 12px;\r\n background: var(--dsw-alias-bg-module-platform);\r\n padding: 14px 16px;\r\n display: flex;\r\n flex-direction: column;\r\n gap: 14px;\r\n}\r\n\r\n.dun7765_field {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 6px;\r\n}\r\n\r\n.dun7765_fieldLabel {\r\n display: inline-flex;\r\n align-items: center;\r\n gap: 10px;\r\n font-size: 12px;\r\n line-height: 18px;\r\n font-weight: 500;\r\n color: var(--dsw-alias-label-secondary);\r\n}\r\n\r\n.dun7765_fieldControl {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 6px;\r\n}\r\n\r\n/* Input: matching ModelsSection .dun7765_input — h32, r8, border-l2, bg-layer-1. */\r\n.dun7765_input {\r\n box-sizing: border-box;\r\n width: 100%;\r\n height: 32px;\r\n padding: 0 10px;\r\n border: 1px solid var(--dsw-alias-border-l2);\r\n border-radius: 8px;\r\n font: inherit;\r\n font-size: 14px;\r\n line-height: 22px;\r\n background: var(--dsw-alias-bg-layer-1);\r\n color: var(--dsw-alias-label-primary);\r\n}\r\n\r\n.dun7765_input:focus {\r\n outline: none;\r\n border-color: var(--dsw-alias-brand-primary);\r\n}\r\n\r\n.dun7765_input::placeholder {\r\n color: var(--dsw-alias-label-dimmed);\r\n}\r\n\r\n.dun7765_input:disabled {\r\n opacity: 0.6;\r\n cursor: default;\r\n}\r\n\r\n/* DropdownPicker button: same chrome as .dun7765_input (h32, r8, border-l2,\r\n * bg-layer-1) but a button element with a trailing chevron icon. The\r\n * popup list is rendered by DSH's <Menu> component (portal mode). */\r\n.dun7765_dropdownButton {\r\n box-sizing: border-box;\r\n width: 100%;\r\n height: 32px;\r\n padding: 0 10px;\r\n border: 1px solid var(--dsw-alias-border-l2);\r\n border-radius: 8px;\r\n font: inherit;\r\n font-size: 14px;\r\n line-height: 22px;\r\n background: var(--dsw-alias-bg-layer-1);\r\n color: var(--dsw-alias-label-primary);\r\n cursor: pointer;\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: space-between;\r\n gap: 8px;\r\n}\r\n\r\n.dun7765_dropdownButton:hover:not(:disabled) {\r\n border-color: var(--dsw-alias-brand-primary);\r\n}\r\n\r\n.dun7765_dropdownButton:focus-visible {\r\n outline: none;\r\n border-color: var(--dsw-alias-brand-primary);\r\n box-shadow: 0 0 0 2px var(--dsw-alias-border-l3);\r\n}\r\n\r\n.dun7765_dropdownButton:disabled {\r\n opacity: 0.6;\r\n cursor: default;\r\n}\r\n\r\n.dun7765_dropdownButtonLabel {\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n flex: 1 1 auto;\r\n text-align: left;\r\n}\r\n\r\n.dun7765_dropdownCaret {\r\n flex: none;\r\n opacity: 0.6;\r\n}\r\n\r\n.dun7765_textarea {\r\n box-sizing: border-box;\r\n width: 100%;\r\n min-height: 64px;\r\n padding: 6px 10px;\r\n border: 1px solid var(--dsw-alias-border-l2);\r\n border-radius: 8px;\r\n font: inherit;\r\n font-size: 14px;\r\n line-height: 22px;\r\n background: var(--dsw-alias-bg-layer-1);\r\n color: var(--dsw-alias-label-primary);\r\n resize: vertical;\r\n}\r\n\r\n.dun7765_textarea:focus {\r\n outline: none;\r\n border-color: var(--dsw-alias-brand-primary);\r\n}\r\n\r\n.dun7765_textarea::placeholder {\r\n color: var(--dsw-alias-label-dimmed);\r\n}\r\n\r\n/* Radio group: inline, matching the model.dun7765_kind auto/manual picker. */\r\n.dun7765_radioGroup {\r\n display: flex;\r\n gap: 16px;\r\n flex-wrap: wrap;\r\n}\r\n\r\n.dun7765_radioOption {\r\n display: inline-flex;\r\n align-items: center;\r\n gap: 6px;\r\n font-size: 14px;\r\n line-height: 22px;\r\n color: var(--dsw-alias-label-secondary);\r\n cursor: pointer;\r\n}\r\n\r\n.dun7765_radioOption input[type='radio'] {\r\n margin: 0;\r\n cursor: pointer;\r\n}\r\n\r\n/* Buttons: capsule controls matching ModelsSection (h36 r18 primary,\r\n * h28 r14 secondary in row context). */\r\n.dun7765_primaryButton,\r\n.dun7765_secondaryButton,\r\n.dun7765_dangerButton,\r\n.dun7765_addBlockButton {\r\n box-sizing: border-box;\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n gap: 4px;\r\n height: 36px;\r\n padding: 0 14px;\r\n border: none;\r\n border-radius: 18px;\r\n font: inherit;\r\n font-size: 14px;\r\n line-height: 22px;\r\n cursor: pointer;\r\n}\r\n\r\n.dun7765_primaryButton {\r\n background: var(--dsw-alias-button-primary-fill);\r\n color: var(--dsw-alias-label-primary-foreground);\r\n}\r\n\r\n.dun7765_primaryButton:hover:not(:disabled) {\r\n background: var(--dsw-alias-button-primary-hover);\r\n}\r\n\r\n.dun7765_secondaryButton,\r\n.dun7765_addBlockButton {\r\n border: 1px solid var(--dsw-alias-border-l2);\r\n background: transparent;\r\n color: var(--dsw-alias-label-primary);\r\n}\r\n\r\n.dun7765_secondaryButton:hover:not(:disabled),\r\n.dun7765_addBlockButton:hover:not(:disabled) {\r\n background: var(--dsw-alias-interactive-bg-hover-solid);\r\n}\r\n\r\n.dun7765_dangerButton {\r\n background: transparent;\r\n color: var(--dsw-alias-state-error-primary);\r\n}\r\n\r\n.dun7765_dangerButton:hover:not(:disabled) {\r\n background: var(--dsw-alias-interactive-bg-hover-danger);\r\n}\r\n\r\n/* Row-context buttons go dense (h28 r14, 12/18). */\r\n.dun7765_rowActions .dun7765_secondaryButton,\r\n.dun7765_rowActions .dun7765_dangerButton {\r\n height: 28px;\r\n padding: 0 10px;\r\n border-radius: 14px;\r\n font-size: 12px;\r\n line-height: 18px;\r\n}\r\n\r\n.dun7765_rowActions .dun7765_primaryButton {\r\n height: 28px;\r\n padding: 0 10px;\r\n border-radius: 14px;\r\n font-size: 12px;\r\n line-height: 18px;\r\n}\r\n\r\n.dun7765_primaryButton:disabled,\r\n.dun7765_secondaryButton:disabled,\r\n.dun7765_dangerButton:disabled,\r\n.dun7765_addBlockButton:disabled {\r\n opacity: 0.4;\r\n cursor: default;\r\n}\r\n\r\n.dun7765_primaryButton:focus-visible,\r\n.dun7765_secondaryButton:focus-visible,\r\n.dun7765_dangerButton:focus-visible,\r\n.dun7765_addBlockButton:focus-visible {\r\n outline: none;\r\n box-shadow: 0 0 0 2px var(--dsw-alias-border-l3);\r\n}\r\n\r\n/* Add-profile action: a full-width dashed-outline place card matching\r\n * ModelsSection's addBlock — not a command pill. Clicking it reveals the\r\n * inline draft card (handled in JS); the button itself is the affordance. */\r\n.dun7765_addBlockButton {\r\n width: 100%;\r\n margin-top: 12px;\r\n border-style: dashed;\r\n border-radius: 12px;\r\n height: 40px;\r\n color: var(--dsw-alias-label-secondary);\r\n}\r\n\r\n.dun7765_addBlockButton:hover:not(:disabled) {\r\n color: var(--dsw-alias-label-primary);\r\n border-color: var(--dsw-alias-brand-primary);\r\n}\r\n\r\n/* Empty state. */\r\n.dun7765_empty {\r\n margin: 0;\r\n padding: 24px 12px;\r\n text-align: center;\r\n font-size: 14px;\r\n line-height: 22px;\r\n color: var(--dsw-alias-label-tertiary);\r\n}\r\n\r\n/* Modal confirm body text. */\r\n.dun7765_confirmText {\r\n margin: 0;\r\n font-size: 14px;\r\n line-height: 22px;\r\n color: var(--dsw-alias-label-secondary);\r\n}\r\n\r\n/* Repair history block: a discrete action below the add-profile button. */\r\n.dun7765_repairBlock {\r\n margin-top: 8px;\r\n}\r\n\r\n.dun7765_repairBlock button {\r\n width: 100%;\r\n}\r\n\r\n/* Repair result stats container. */\r\n.dun7765_repairStats {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 4px;\r\n}\r\n\r\n/* Repair error list inside the result modal. */\r\n.dun7765_repairErrorList {\r\n margin: 8px 0 0;\r\n padding: 0 0 0 16px;\r\n display: flex;\r\n flex-direction: column;\r\n gap: 4px;\r\n max-height: 200px;\r\n overflow-y: auto;\r\n}\r\n\r\n.dun7765_loading {\r\n padding: 12px;\r\n font-size: 14px;\r\n line-height: 22px;\r\n color: var(--dsw-alias-label-tertiary);\r\n}\r\n\r\n/* ─── Multi-select dropdown ─────────────────────────────────────────────── */\r\n\r\n.dun7765_multiSelect {\r\n position: relative;\r\n width: 100%;\r\n}\r\n\r\n.dun7765_msButton {\r\n box-sizing: border-box;\r\n width: 100%;\r\n height: 32px;\r\n padding: 0 10px;\r\n border: 1px solid var(--dsw-alias-border-l2);\r\n border-radius: 8px;\r\n font: inherit;\r\n font-size: 14px;\r\n line-height: 22px;\r\n background: var(--dsw-alias-bg-layer-1);\r\n color: var(--dsw-alias-label-primary);\r\n cursor: pointer;\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: space-between;\r\n gap: 8px;\r\n}\r\n\r\n.dun7765_msButton:hover {\r\n border-color: var(--dsw-alias-brand-primary);\r\n}\r\n\r\n.dun7765_msButton:focus-visible {\r\n outline: none;\r\n border-color: var(--dsw-alias-brand-primary);\r\n box-shadow: 0 0 0 2px var(--dsw-alias-border-l3);\r\n}\r\n\r\n.dun7765_msButtonLabel {\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n flex: 1 1 auto;\r\n text-align: left;\r\n}\r\n\r\n/* Caret: DSH IconChevronDownOutline14 icon. */\r\n.dun7765_msCaret {\r\n flex: none;\r\n opacity: 0.6;\r\n}\r\n\r\n.dun7765_msPopup {\r\n position: absolute;\r\n top: calc(100% + 4px);\r\n left: 0;\r\n right: 0;\r\n z-index: 10;\r\n border: 1px solid var(--dsw-alias-border-l2);\r\n border-radius: 8px;\r\n background: var(--dsw-alias-bg-layer-1);\r\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);\r\n display: flex;\r\n flex-direction: column;\r\n max-height: 280px;\r\n overflow: hidden;\r\n}\r\n\r\n.dun7765_msSearch {\r\n box-sizing: border-box;\r\n width: 100%;\r\n padding: 8px 10px;\r\n border: none;\r\n border-bottom: 1px solid var(--dsw-alias-border-l2);\r\n font: inherit;\r\n font-size: 14px;\r\n line-height: 22px;\r\n background: transparent;\r\n color: var(--dsw-alias-label-primary);\r\n}\r\n\r\n.dun7765_msSearch:focus {\r\n outline: none;\r\n}\r\n\r\n.dun7765_msSearch::placeholder {\r\n color: var(--dsw-alias-label-dimmed);\r\n}\r\n\r\n.dun7765_msActions {\r\n display: flex;\r\n justify-content: flex-end;\r\n padding: 6px 10px;\r\n border-bottom: 1px solid var(--dsw-alias-border-l2);\r\n}\r\n\r\n.dun7765_msActionLink {\r\n border: none;\r\n background: transparent;\r\n color: var(--dsw-alias-brand-primary);\r\n font: inherit;\r\n font-size: 12px;\r\n line-height: 18px;\r\n cursor: pointer;\r\n padding: 0;\r\n}\r\n\r\n.dun7765_msActionLink:hover {\r\n text-decoration: underline;\r\n}\r\n\r\n.dun7765_msList {\r\n list-style: none;\r\n margin: 0;\r\n padding: 4px 0;\r\n overflow-y: auto;\r\n flex: 1 1 auto;\r\n}\r\n\r\n.dun7765_msItem {\r\n margin: 0;\r\n}\r\n\r\n.dun7765_msItemLabel {\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n padding: 6px 10px;\r\n cursor: pointer;\r\n font-size: 13px;\r\n line-height: 18px;\r\n color: var(--dsw-alias-label-primary);\r\n}\r\n\r\n.dun7765_msItemLabel:hover {\r\n background: var(--dsw-alias-interactive-bg-hover-solid);\r\n}\r\n\r\n.dun7765_msItemLabel input[type='checkbox'] {\r\n margin: 0;\r\n cursor: pointer;\r\n flex: none;\r\n}\r\n\r\n.dun7765_msItemName {\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n font-family: var(--dsw-font-markdown-code-block-small, monospace);\r\n}\r\n\r\n.dun7765_msEmpty {\r\n padding: 12px 10px;\r\n font-size: 13px;\r\n line-height: 18px;\r\n color: var(--dsw-alias-label-tertiary);\r\n text-align: center;\r\n}\r\n";
|
|
526
|
+
const classMap = {
|
|
527
|
+
"section": "dun7765_section",
|
|
528
|
+
"title": "dun7765_title",
|
|
529
|
+
"intro": "dun7765_intro",
|
|
530
|
+
"error": "dun7765_error",
|
|
531
|
+
"errorDismiss": "dun7765_errorDismiss",
|
|
532
|
+
"rows": "dun7765_rows",
|
|
533
|
+
"rowCard": "dun7765_rowCard",
|
|
534
|
+
"rowHead": "dun7765_rowHead",
|
|
535
|
+
"chevronButton": "dun7765_chevronButton",
|
|
536
|
+
"g": "dun7765_g",
|
|
537
|
+
"chevronSpacer": "dun7765_chevronSpacer",
|
|
538
|
+
"chevron": "dun7765_chevron",
|
|
539
|
+
"chevronExpanded": "dun7765_chevronExpanded",
|
|
540
|
+
"rowIdentity": "dun7765_rowIdentity",
|
|
541
|
+
"rowName": "dun7765_rowName",
|
|
542
|
+
"rowNamePlaceholder": "dun7765_rowNamePlaceholder",
|
|
543
|
+
"builtinBadge": "dun7765_builtinBadge",
|
|
544
|
+
"rowId": "dun7765_rowId",
|
|
545
|
+
"rowActions": "dun7765_rowActions",
|
|
546
|
+
"editor": "dun7765_editor",
|
|
547
|
+
"field": "dun7765_field",
|
|
548
|
+
"fieldLabel": "dun7765_fieldLabel",
|
|
549
|
+
"fieldControl": "dun7765_fieldControl",
|
|
550
|
+
"input": "dun7765_input",
|
|
551
|
+
"dropdownButton": "dun7765_dropdownButton",
|
|
552
|
+
"dropdownButtonLabel": "dun7765_dropdownButtonLabel",
|
|
553
|
+
"dropdownCaret": "dun7765_dropdownCaret",
|
|
554
|
+
"textarea": "dun7765_textarea",
|
|
555
|
+
"kind": "dun7765_kind",
|
|
556
|
+
"radioGroup": "dun7765_radioGroup",
|
|
557
|
+
"radioOption": "dun7765_radioOption",
|
|
558
|
+
"primaryButton": "dun7765_primaryButton",
|
|
559
|
+
"secondaryButton": "dun7765_secondaryButton",
|
|
560
|
+
"dangerButton": "dun7765_dangerButton",
|
|
561
|
+
"addBlockButton": "dun7765_addBlockButton",
|
|
562
|
+
"empty": "dun7765_empty",
|
|
563
|
+
"confirmText": "dun7765_confirmText",
|
|
564
|
+
"repairBlock": "dun7765_repairBlock",
|
|
565
|
+
"repairStats": "dun7765_repairStats",
|
|
566
|
+
"repairErrorList": "dun7765_repairErrorList",
|
|
567
|
+
"loading": "dun7765_loading",
|
|
568
|
+
"multiSelect": "dun7765_multiSelect",
|
|
569
|
+
"msButton": "dun7765_msButton",
|
|
570
|
+
"msButtonLabel": "dun7765_msButtonLabel",
|
|
571
|
+
"msCaret": "dun7765_msCaret",
|
|
572
|
+
"msPopup": "dun7765_msPopup",
|
|
573
|
+
"msSearch": "dun7765_msSearch",
|
|
574
|
+
"msActions": "dun7765_msActions",
|
|
575
|
+
"msActionLink": "dun7765_msActionLink",
|
|
576
|
+
"msList": "dun7765_msList",
|
|
577
|
+
"msItem": "dun7765_msItem",
|
|
578
|
+
"msItemLabel": "dun7765_msItemLabel",
|
|
579
|
+
"msItemName": "dun7765_msItemName",
|
|
580
|
+
"msEmpty": "dun7765_msEmpty"
|
|
581
|
+
};
|
|
582
|
+
const tagId = "@huanlin/dsh-plugin-yet-another-subagent/SettingsPage.module.css";
|
|
583
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
584
|
+
const tag = document.createElement("style");
|
|
585
|
+
tag.dataset.plugin = "@huanlin/dsh-plugin-yet-another-subagent";
|
|
586
|
+
tag.dataset.pluginCss = tagId;
|
|
587
|
+
tag.textContent = css;
|
|
588
|
+
document.head.appendChild(tag);
|
|
589
|
+
}
|
|
590
|
+
//#endregion
|
|
591
|
+
//#region src/client/SettingsPage.tsx
|
|
592
|
+
/**
|
|
593
|
+
* SettingsPage — the `ya-subagent` settings section: profile list CRUD.
|
|
594
|
+
*
|
|
595
|
+
* Visual language: matches ModelsSection / GeneralSection — outlined rowCard
|
|
596
|
+
* per profile (border-l2, r12, p12/14), filled editor surface
|
|
597
|
+
* (bg-module-platform, r12, p14/16), capsule controls (h36 r18 primary,
|
|
598
|
+
* h28 r14 secondary), 32px fields with border-l2 / bg-layer-1, 12/18 caption
|
|
599
|
+
* labels. Every color resolves through --dsw-alias-* tokens.
|
|
600
|
+
*
|
|
601
|
+
* Each profile card is collapsible (chevron in the row head); the editor
|
|
602
|
+
* surface is hidden when collapsed. Builtin profiles (cordis.yml seed) carry
|
|
603
|
+
* a `builtin`/`内置` badge next to the title. The "+ Add subagent" button at
|
|
604
|
+
* the bottom reveals an inline draft card with all fields editable (including
|
|
605
|
+
* id) and Create / Cancel actions.
|
|
606
|
+
*
|
|
607
|
+
* The persona field is a radio (inherit deployment persona vs custom text);
|
|
608
|
+
* the textarea is shown only when custom. The tool filter is a select
|
|
609
|
+
* (none / allow / deny); a multi-select dropdown is shown only when allow or
|
|
610
|
+
* deny is picked, populated from `tools.list` (the host's current
|
|
611
|
+
* `ctx.tools.schemas()`).
|
|
612
|
+
*
|
|
613
|
+
* Pulls the profile list once on mount via `connection.rpc.call('/ya-subagent',
|
|
614
|
+
* 'profiles.list')`, dispatches add/update/remove through the
|
|
615
|
+
* same RPC. The toolview slot is keyed by `subagent` and registered once at
|
|
616
|
+
* plugin load, so profile mutations do not need to re-register slots.
|
|
617
|
+
*
|
|
618
|
+
* @module @huanlin/dsh-plugin-yet-another-subagent/client/SettingsPage
|
|
619
|
+
*/
|
|
620
|
+
/** Default shape for a brand-new draft (before the user fills in id/label). */
|
|
621
|
+
function emptyDraft() {
|
|
622
|
+
return {
|
|
623
|
+
id: "",
|
|
624
|
+
label: "",
|
|
625
|
+
model: {
|
|
626
|
+
kind: "auto",
|
|
627
|
+
provider: "",
|
|
628
|
+
model: ""
|
|
629
|
+
},
|
|
630
|
+
persona: { kind: "inherit" },
|
|
631
|
+
toolFilter: { kind: "none" },
|
|
632
|
+
maxDepth: 3,
|
|
633
|
+
backgroundMode: "continuable",
|
|
634
|
+
builtin: false
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
async function callRpc(rpc, endpoint, payload) {
|
|
638
|
+
return rpc.call("/ya-subagent", endpoint, payload);
|
|
639
|
+
}
|
|
640
|
+
/**
|
|
641
|
+
* Render the subagent profiles settings page.
|
|
642
|
+
* @param props - settings.section runtime share + locale + inject.
|
|
643
|
+
* @returns the page element.
|
|
644
|
+
*/
|
|
645
|
+
function SettingsPage({ rpc, fetchProfiles, t }) {
|
|
646
|
+
const [profiles, setProfiles] = (0, react.useState)([]);
|
|
647
|
+
const [drafts, setDrafts] = (0, react.useState)([]);
|
|
648
|
+
const [expanded, setExpanded] = (0, react.useState)(/* @__PURE__ */ new Set());
|
|
649
|
+
const [addingNew, setAddingNew] = (0, react.useState)(false);
|
|
650
|
+
const [newDraft, setNewDraft] = (0, react.useState)(emptyDraft());
|
|
651
|
+
const [toolList, setToolList] = (0, react.useState)([]);
|
|
652
|
+
const [modelGroups, setModelGroups] = (0, react.useState)([]);
|
|
653
|
+
const [loading, setLoading] = (0, react.useState)(true);
|
|
654
|
+
const [error, setError] = (0, react.useState)(void 0);
|
|
655
|
+
const [confirmDelete, setConfirmDelete] = (0, react.useState)(void 0);
|
|
656
|
+
const [repairConfirmOpen, setRepairConfirmOpen] = (0, react.useState)(false);
|
|
657
|
+
const [repairRunning, setRepairRunning] = (0, react.useState)(false);
|
|
658
|
+
const [repairStats, setRepairStats] = (0, react.useState)(void 0);
|
|
659
|
+
const [repairError, setRepairError] = (0, react.useState)(void 0);
|
|
660
|
+
const refresh = (0, react.useCallback)(async () => {
|
|
661
|
+
setLoading(true);
|
|
662
|
+
setError(void 0);
|
|
663
|
+
try {
|
|
664
|
+
const [list, toolsResult, modelsResult] = await Promise.all([
|
|
665
|
+
fetchProfiles(),
|
|
666
|
+
callRpc(rpc, "tools.list", {}),
|
|
667
|
+
rpc.call("/api", "llm.models", {})
|
|
668
|
+
]);
|
|
669
|
+
setProfiles(list);
|
|
670
|
+
setDrafts(list.map((p) => ({ ...p })));
|
|
671
|
+
if (toolsResult.ok) setToolList(toolsResult.value.tools);
|
|
672
|
+
else setToolList([]);
|
|
673
|
+
if (modelsResult.ok) setModelGroups(modelsResult.value.groups);
|
|
674
|
+
else setModelGroups([]);
|
|
675
|
+
} catch (err) {
|
|
676
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
677
|
+
} finally {
|
|
678
|
+
setLoading(false);
|
|
679
|
+
}
|
|
680
|
+
}, [fetchProfiles, rpc]);
|
|
681
|
+
(0, react.useEffect)(() => {
|
|
682
|
+
refresh();
|
|
683
|
+
}, [refresh]);
|
|
684
|
+
const addProfile = (0, react.useCallback)(async () => {
|
|
685
|
+
if (newDraft.id === "" || newDraft.label === "") return;
|
|
686
|
+
const result = await callRpc(rpc, "profiles.add", { profile: newDraft });
|
|
687
|
+
if (result.ok) {
|
|
688
|
+
setProfiles(result.value.profiles);
|
|
689
|
+
setDrafts(result.value.profiles.map((p) => ({ ...p })));
|
|
690
|
+
setExpanded(/* @__PURE__ */ new Set([...expanded, newDraft.id]));
|
|
691
|
+
setAddingNew(false);
|
|
692
|
+
setNewDraft(emptyDraft());
|
|
693
|
+
} else setError(result.error.message);
|
|
694
|
+
}, [
|
|
695
|
+
expanded,
|
|
696
|
+
newDraft,
|
|
697
|
+
rpc
|
|
698
|
+
]);
|
|
699
|
+
const updateProfile = (0, react.useCallback)(async (draft) => {
|
|
700
|
+
const result = await callRpc(rpc, "profiles.update", { profile: draft });
|
|
701
|
+
if (result.ok) {
|
|
702
|
+
setProfiles(result.value.profiles);
|
|
703
|
+
setDrafts(result.value.profiles.map((p) => ({ ...p })));
|
|
704
|
+
} else setError(result.error.message);
|
|
705
|
+
}, [rpc]);
|
|
706
|
+
const removeProfile = (0, react.useCallback)(async (id) => {
|
|
707
|
+
const result = await callRpc(rpc, "profiles.remove", { id });
|
|
708
|
+
if (result.ok) {
|
|
709
|
+
setProfiles(result.value.profiles);
|
|
710
|
+
setDrafts(result.value.profiles.map((p) => ({ ...p })));
|
|
711
|
+
const next = new Set(expanded);
|
|
712
|
+
next.delete(id);
|
|
713
|
+
setExpanded(next);
|
|
714
|
+
} else setError(result.error.message);
|
|
715
|
+
}, [expanded, rpc]);
|
|
716
|
+
const runRepair = (0, react.useCallback)(async () => {
|
|
717
|
+
setRepairConfirmOpen(false);
|
|
718
|
+
setRepairRunning(true);
|
|
719
|
+
setRepairError(void 0);
|
|
720
|
+
try {
|
|
721
|
+
const result = await callRpc(rpc, "sessions.repair", {});
|
|
722
|
+
if (result.ok) setRepairStats(result.value);
|
|
723
|
+
else setRepairError(result.error.message);
|
|
724
|
+
} catch (err) {
|
|
725
|
+
setRepairError(err instanceof Error ? err.message : String(err));
|
|
726
|
+
} finally {
|
|
727
|
+
setRepairRunning(false);
|
|
728
|
+
}
|
|
729
|
+
}, [rpc]);
|
|
730
|
+
const patchDraft = (id, patch) => {
|
|
731
|
+
setDrafts((prev) => prev.map((d) => d.id === id ? {
|
|
732
|
+
...d,
|
|
733
|
+
...patch
|
|
734
|
+
} : d));
|
|
735
|
+
};
|
|
736
|
+
const toggleExpand = (id) => {
|
|
737
|
+
const next = new Set(expanded);
|
|
738
|
+
if (next.has(id)) next.delete(id);
|
|
739
|
+
else next.add(id);
|
|
740
|
+
setExpanded(next);
|
|
741
|
+
};
|
|
742
|
+
const cancelNew = () => {
|
|
743
|
+
setAddingNew(false);
|
|
744
|
+
setNewDraft(emptyDraft());
|
|
745
|
+
};
|
|
746
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
747
|
+
className: classMap.section,
|
|
748
|
+
children: [
|
|
749
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
750
|
+
className: classMap.title,
|
|
751
|
+
children: t("page.title")
|
|
752
|
+
}),
|
|
753
|
+
error !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
754
|
+
className: classMap.error,
|
|
755
|
+
children: [error, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
756
|
+
type: "button",
|
|
757
|
+
className: classMap.errorDismiss,
|
|
758
|
+
onClick: () => setError(void 0),
|
|
759
|
+
children: "×"
|
|
760
|
+
})]
|
|
761
|
+
}),
|
|
762
|
+
loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
763
|
+
className: classMap.loading,
|
|
764
|
+
children: "…"
|
|
765
|
+
}) : profiles.length === 0 && !addingNew ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
766
|
+
className: classMap.empty,
|
|
767
|
+
children: t("page.empty")
|
|
768
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("ul", {
|
|
769
|
+
className: classMap.rows,
|
|
770
|
+
children: [drafts.map((draft) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProfileCard, {
|
|
771
|
+
draft,
|
|
772
|
+
expanded: expanded.has(draft.id),
|
|
773
|
+
toolList,
|
|
774
|
+
modelGroups,
|
|
775
|
+
t,
|
|
776
|
+
onToggle: () => toggleExpand(draft.id),
|
|
777
|
+
onPatch: (patch) => patchDraft(draft.id, patch),
|
|
778
|
+
onSave: () => void updateProfile(draft),
|
|
779
|
+
onDelete: () => setConfirmDelete(draft.id)
|
|
780
|
+
}, draft.id)), addingNew && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProfileCard, {
|
|
781
|
+
draft: newDraft,
|
|
782
|
+
expanded: true,
|
|
783
|
+
isNew: true,
|
|
784
|
+
toolList,
|
|
785
|
+
modelGroups,
|
|
786
|
+
t,
|
|
787
|
+
onPatch: (patch) => setNewDraft((prev) => ({
|
|
788
|
+
...prev,
|
|
789
|
+
...patch
|
|
790
|
+
})),
|
|
791
|
+
onCreate: () => void addProfile(),
|
|
792
|
+
onCancel: cancelNew
|
|
793
|
+
}, "__new__")]
|
|
794
|
+
}),
|
|
795
|
+
!loading && !addingNew && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
796
|
+
type: "button",
|
|
797
|
+
className: classMap.addBlockButton,
|
|
798
|
+
onClick: () => setAddingNew(true),
|
|
799
|
+
children: t("page.add")
|
|
800
|
+
}),
|
|
801
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
802
|
+
className: classMap.repairBlock,
|
|
803
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
804
|
+
type: "button",
|
|
805
|
+
className: classMap.secondaryButton,
|
|
806
|
+
disabled: repairRunning,
|
|
807
|
+
onClick: () => setRepairConfirmOpen(true),
|
|
808
|
+
children: repairRunning ? t("repair.running") : t("repair.button")
|
|
809
|
+
})
|
|
810
|
+
}),
|
|
811
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
|
|
812
|
+
open: repairConfirmOpen,
|
|
813
|
+
onClose: () => {
|
|
814
|
+
setRepairConfirmOpen(false);
|
|
815
|
+
},
|
|
816
|
+
title: t("repair.confirm.title"),
|
|
817
|
+
footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
818
|
+
type: "button",
|
|
819
|
+
className: classMap.secondaryButton,
|
|
820
|
+
onClick: () => {
|
|
821
|
+
setRepairConfirmOpen(false);
|
|
822
|
+
},
|
|
823
|
+
children: t("repair.confirm.cancel")
|
|
824
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
825
|
+
type: "button",
|
|
826
|
+
className: classMap.primaryButton,
|
|
827
|
+
onClick: () => {
|
|
828
|
+
runRepair();
|
|
829
|
+
},
|
|
830
|
+
children: t("repair.confirm.proceed")
|
|
831
|
+
})] }),
|
|
832
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
833
|
+
className: classMap.confirmText,
|
|
834
|
+
children: t("repair.confirm.body")
|
|
835
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
836
|
+
className: classMap.confirmText,
|
|
837
|
+
children: t("repair.confirm.warning")
|
|
838
|
+
})]
|
|
839
|
+
}),
|
|
840
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
|
|
841
|
+
open: repairStats !== void 0 || repairError !== void 0,
|
|
842
|
+
onClose: () => {
|
|
843
|
+
setRepairStats(void 0);
|
|
844
|
+
setRepairError(void 0);
|
|
845
|
+
},
|
|
846
|
+
title: t("repair.result.title"),
|
|
847
|
+
footer: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
848
|
+
type: "button",
|
|
849
|
+
className: classMap.secondaryButton,
|
|
850
|
+
onClick: () => {
|
|
851
|
+
setRepairStats(void 0);
|
|
852
|
+
setRepairError(void 0);
|
|
853
|
+
},
|
|
854
|
+
children: t("repair.result.close")
|
|
855
|
+
}),
|
|
856
|
+
children: repairError !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
857
|
+
className: classMap.error,
|
|
858
|
+
children: [
|
|
859
|
+
t("repair.error"),
|
|
860
|
+
": ",
|
|
861
|
+
repairError
|
|
862
|
+
]
|
|
863
|
+
}) : repairStats !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
864
|
+
className: classMap.repairStats,
|
|
865
|
+
children: [
|
|
866
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
867
|
+
className: classMap.confirmText,
|
|
868
|
+
children: t("repair.result.scanned").replace("{n}", String(repairStats.scanned))
|
|
869
|
+
}),
|
|
870
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
871
|
+
className: classMap.confirmText,
|
|
872
|
+
children: t("repair.result.repaired").replace("{n}", String(repairStats.repaired))
|
|
873
|
+
}),
|
|
874
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
875
|
+
className: classMap.confirmText,
|
|
876
|
+
children: t("repair.result.skipped").replace("{n}", String(repairStats.skipped))
|
|
877
|
+
}),
|
|
878
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
879
|
+
className: classMap.confirmText,
|
|
880
|
+
children: t("repair.result.errors").replace("{n}", String(repairStats.errors.length))
|
|
881
|
+
}),
|
|
882
|
+
repairStats.errors.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
883
|
+
className: classMap.repairErrorList,
|
|
884
|
+
children: repairStats.errors.map((e, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
|
|
885
|
+
className: classMap.confirmText,
|
|
886
|
+
children: t("repair.result.errorEntry").replace("{path}", e.path).replace("{message}", e.message)
|
|
887
|
+
}, i))
|
|
888
|
+
})
|
|
889
|
+
]
|
|
890
|
+
}) : null
|
|
891
|
+
}),
|
|
892
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
|
|
893
|
+
open: confirmDelete !== void 0,
|
|
894
|
+
onClose: () => {
|
|
895
|
+
setConfirmDelete(void 0);
|
|
896
|
+
},
|
|
897
|
+
title: t("row.delete.confirm"),
|
|
898
|
+
footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
899
|
+
type: "button",
|
|
900
|
+
className: classMap.secondaryButton,
|
|
901
|
+
onClick: () => {
|
|
902
|
+
setConfirmDelete(void 0);
|
|
903
|
+
},
|
|
904
|
+
children: t("page.add.cancel")
|
|
905
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
906
|
+
type: "button",
|
|
907
|
+
className: classMap.dangerButton,
|
|
908
|
+
onClick: () => {
|
|
909
|
+
if (confirmDelete !== void 0) removeProfile(confirmDelete);
|
|
910
|
+
setConfirmDelete(void 0);
|
|
911
|
+
},
|
|
912
|
+
children: t("row.delete")
|
|
913
|
+
})] }),
|
|
914
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
915
|
+
className: classMap.confirmText,
|
|
916
|
+
children: t("row.delete.confirm")
|
|
917
|
+
})
|
|
918
|
+
})
|
|
919
|
+
]
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
function ProfileCard({ draft, expanded, isNew, toolList, modelGroups, t, onToggle, onPatch, onSave, onDelete, onCreate, onCancel }) {
|
|
923
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
924
|
+
className: classMap.rowCard,
|
|
925
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
926
|
+
className: classMap.rowHead,
|
|
927
|
+
children: [
|
|
928
|
+
!isNew && onToggle !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
929
|
+
type: "button",
|
|
930
|
+
className: classMap.chevronButton,
|
|
931
|
+
onClick: onToggle,
|
|
932
|
+
"aria-label": expanded ? t("row.collapse") : t("row.expand"),
|
|
933
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
934
|
+
className: expanded ? `${classMap.chevron} ${classMap.chevronExpanded}` : classMap.chevron,
|
|
935
|
+
"aria-hidden": "true"
|
|
936
|
+
})
|
|
937
|
+
}),
|
|
938
|
+
isNew && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
939
|
+
className: classMap.chevronSpacer,
|
|
940
|
+
"aria-hidden": "true"
|
|
941
|
+
}),
|
|
942
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
943
|
+
className: classMap.rowIdentity,
|
|
944
|
+
children: [
|
|
945
|
+
isNew ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
946
|
+
className: classMap.rowNamePlaceholder,
|
|
947
|
+
children: t("page.add")
|
|
948
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
949
|
+
className: classMap.rowName,
|
|
950
|
+
children: draft.label === "" ? draft.id : draft.label
|
|
951
|
+
}),
|
|
952
|
+
draft.builtin && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Pill, {
|
|
953
|
+
className: classMap.builtinBadge,
|
|
954
|
+
children: t("badge.builtin")
|
|
955
|
+
}),
|
|
956
|
+
!isNew && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
957
|
+
className: classMap.rowId,
|
|
958
|
+
children: draft.id
|
|
959
|
+
})
|
|
960
|
+
]
|
|
961
|
+
}),
|
|
962
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
963
|
+
className: classMap.rowActions,
|
|
964
|
+
children: isNew ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
965
|
+
type: "button",
|
|
966
|
+
className: classMap.primaryButton,
|
|
967
|
+
onClick: onCreate,
|
|
968
|
+
disabled: draft.id === "" || draft.label === "",
|
|
969
|
+
children: t("page.add.submit")
|
|
970
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
971
|
+
type: "button",
|
|
972
|
+
className: classMap.secondaryButton,
|
|
973
|
+
onClick: onCancel,
|
|
974
|
+
children: t("page.add.cancel")
|
|
975
|
+
})] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
976
|
+
type: "button",
|
|
977
|
+
className: classMap.secondaryButton,
|
|
978
|
+
onClick: onSave,
|
|
979
|
+
children: t("row.save")
|
|
980
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
981
|
+
type: "button",
|
|
982
|
+
className: classMap.dangerButton,
|
|
983
|
+
onClick: onDelete,
|
|
984
|
+
children: t("row.delete")
|
|
985
|
+
})] })
|
|
986
|
+
})
|
|
987
|
+
]
|
|
988
|
+
}), expanded && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
989
|
+
className: classMap.editor,
|
|
990
|
+
children: [
|
|
991
|
+
isNew && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
992
|
+
className: classMap.field,
|
|
993
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
994
|
+
className: classMap.fieldLabel,
|
|
995
|
+
children: t("row.id")
|
|
996
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
997
|
+
className: classMap.input,
|
|
998
|
+
value: draft.id,
|
|
999
|
+
placeholder: t("page.add.placeholder.id"),
|
|
1000
|
+
onChange: (e) => onPatch({ id: e.target.value })
|
|
1001
|
+
})]
|
|
1002
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1003
|
+
className: classMap.field,
|
|
1004
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1005
|
+
className: classMap.fieldLabel,
|
|
1006
|
+
children: t("row.label")
|
|
1007
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1008
|
+
className: classMap.input,
|
|
1009
|
+
value: draft.label,
|
|
1010
|
+
placeholder: t("page.add.placeholder.label"),
|
|
1011
|
+
onChange: (e) => onPatch({ label: e.target.value })
|
|
1012
|
+
})]
|
|
1013
|
+
})] }),
|
|
1014
|
+
!isNew && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1015
|
+
className: classMap.field,
|
|
1016
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1017
|
+
className: classMap.fieldLabel,
|
|
1018
|
+
children: t("row.label")
|
|
1019
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1020
|
+
className: classMap.input,
|
|
1021
|
+
value: draft.label,
|
|
1022
|
+
onChange: (e) => onPatch({ label: e.target.value })
|
|
1023
|
+
})]
|
|
1024
|
+
}),
|
|
1025
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1026
|
+
className: classMap.field,
|
|
1027
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1028
|
+
className: classMap.fieldLabel,
|
|
1029
|
+
children: [
|
|
1030
|
+
t("row.model.kind.auto"),
|
|
1031
|
+
" / ",
|
|
1032
|
+
t("row.model.kind.manual")
|
|
1033
|
+
]
|
|
1034
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1035
|
+
className: classMap.radioGroup,
|
|
1036
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1037
|
+
className: classMap.radioOption,
|
|
1038
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1039
|
+
type: "radio",
|
|
1040
|
+
name: `model-${draft.id}-${isNew ? "new" : ""}`,
|
|
1041
|
+
checked: draft.model.kind === "auto",
|
|
1042
|
+
onChange: () => onPatch({ model: {
|
|
1043
|
+
kind: "auto",
|
|
1044
|
+
provider: "",
|
|
1045
|
+
model: ""
|
|
1046
|
+
} })
|
|
1047
|
+
}), t("row.model.kind.auto")]
|
|
1048
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1049
|
+
className: classMap.radioOption,
|
|
1050
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1051
|
+
type: "radio",
|
|
1052
|
+
name: `model-${draft.id}-${isNew ? "new" : ""}`,
|
|
1053
|
+
checked: draft.model.kind === "manual",
|
|
1054
|
+
onChange: () => onPatch({ model: {
|
|
1055
|
+
kind: "manual",
|
|
1056
|
+
provider: draft.model.provider,
|
|
1057
|
+
model: draft.model.model
|
|
1058
|
+
} })
|
|
1059
|
+
}), t("row.model.kind.manual")]
|
|
1060
|
+
})]
|
|
1061
|
+
})]
|
|
1062
|
+
}),
|
|
1063
|
+
draft.model.kind === "manual" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1064
|
+
className: classMap.field,
|
|
1065
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1066
|
+
className: classMap.fieldLabel,
|
|
1067
|
+
children: t("row.model.provider")
|
|
1068
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DropdownPicker, {
|
|
1069
|
+
value: draft.model.provider,
|
|
1070
|
+
placeholder: t("row.model.provider.placeholder"),
|
|
1071
|
+
items: modelGroups.map((g) => ({
|
|
1072
|
+
id: g.id,
|
|
1073
|
+
label: g.name
|
|
1074
|
+
})),
|
|
1075
|
+
onSelect: (id) => onPatch({ model: {
|
|
1076
|
+
kind: "manual",
|
|
1077
|
+
provider: id,
|
|
1078
|
+
model: ""
|
|
1079
|
+
} })
|
|
1080
|
+
})]
|
|
1081
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1082
|
+
className: classMap.field,
|
|
1083
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1084
|
+
className: classMap.fieldLabel,
|
|
1085
|
+
children: t("row.model.model")
|
|
1086
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DropdownPicker, {
|
|
1087
|
+
value: draft.model.model,
|
|
1088
|
+
placeholder: t("row.model.model.placeholder"),
|
|
1089
|
+
disabled: draft.model.provider === "",
|
|
1090
|
+
items: draft.model.provider !== "" ? modelGroups.filter((g) => g.id === draft.model.provider).flatMap((g) => g.models).map((m) => ({
|
|
1091
|
+
id: m.id,
|
|
1092
|
+
label: m.name
|
|
1093
|
+
})) : [],
|
|
1094
|
+
onSelect: (id) => onPatch({ model: {
|
|
1095
|
+
kind: "manual",
|
|
1096
|
+
provider: draft.model.provider,
|
|
1097
|
+
model: id
|
|
1098
|
+
} })
|
|
1099
|
+
})]
|
|
1100
|
+
})] }),
|
|
1101
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1102
|
+
className: classMap.field,
|
|
1103
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1104
|
+
className: classMap.fieldLabel,
|
|
1105
|
+
children: t("row.persona")
|
|
1106
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1107
|
+
className: classMap.radioGroup,
|
|
1108
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1109
|
+
className: classMap.radioOption,
|
|
1110
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1111
|
+
type: "radio",
|
|
1112
|
+
name: `persona-${draft.id}-${isNew ? "new" : ""}`,
|
|
1113
|
+
checked: draft.persona.kind === "inherit",
|
|
1114
|
+
onChange: () => onPatch({ persona: { kind: "inherit" } })
|
|
1115
|
+
}), t("row.persona.kind.inherit")]
|
|
1116
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1117
|
+
className: classMap.radioOption,
|
|
1118
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1119
|
+
type: "radio",
|
|
1120
|
+
name: `persona-${draft.id}-${isNew ? "new" : ""}`,
|
|
1121
|
+
checked: draft.persona.kind === "custom",
|
|
1122
|
+
onChange: () => onPatch({ persona: {
|
|
1123
|
+
kind: "custom",
|
|
1124
|
+
text: draft.persona.kind === "custom" ? draft.persona.text : ""
|
|
1125
|
+
} })
|
|
1126
|
+
}), t("row.persona.kind.custom")]
|
|
1127
|
+
})]
|
|
1128
|
+
})]
|
|
1129
|
+
}),
|
|
1130
|
+
draft.persona.kind === "custom" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1131
|
+
className: classMap.field,
|
|
1132
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1133
|
+
className: classMap.fieldLabel,
|
|
1134
|
+
children: t("row.persona.text")
|
|
1135
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
1136
|
+
className: classMap.textarea,
|
|
1137
|
+
value: draft.persona.kind === "custom" ? draft.persona.text : "",
|
|
1138
|
+
onChange: (e) => onPatch({ persona: {
|
|
1139
|
+
kind: "custom",
|
|
1140
|
+
text: e.target.value
|
|
1141
|
+
} })
|
|
1142
|
+
})]
|
|
1143
|
+
}),
|
|
1144
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1145
|
+
className: classMap.field,
|
|
1146
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1147
|
+
className: classMap.fieldLabel,
|
|
1148
|
+
children: t("row.toolFilter")
|
|
1149
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1150
|
+
className: classMap.radioGroup,
|
|
1151
|
+
children: [
|
|
1152
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1153
|
+
className: classMap.radioOption,
|
|
1154
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1155
|
+
type: "radio",
|
|
1156
|
+
name: `toolFilter-${draft.id}-${isNew ? "new" : ""}`,
|
|
1157
|
+
checked: draft.toolFilter.kind === "none",
|
|
1158
|
+
onChange: () => onPatch({ toolFilter: { kind: "none" } })
|
|
1159
|
+
}), t("row.toolFilter.kind.none")]
|
|
1160
|
+
}),
|
|
1161
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1162
|
+
className: classMap.radioOption,
|
|
1163
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1164
|
+
type: "radio",
|
|
1165
|
+
name: `toolFilter-${draft.id}-${isNew ? "new" : ""}`,
|
|
1166
|
+
checked: draft.toolFilter.kind === "allow",
|
|
1167
|
+
onChange: () => onPatch({ toolFilter: {
|
|
1168
|
+
kind: "allow",
|
|
1169
|
+
tools: draft.toolFilter.kind === "allow" ? draft.toolFilter.tools : []
|
|
1170
|
+
} })
|
|
1171
|
+
}), t("row.toolFilter.kind.allow")]
|
|
1172
|
+
}),
|
|
1173
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1174
|
+
className: classMap.radioOption,
|
|
1175
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1176
|
+
type: "radio",
|
|
1177
|
+
name: `toolFilter-${draft.id}-${isNew ? "new" : ""}`,
|
|
1178
|
+
checked: draft.toolFilter.kind === "deny",
|
|
1179
|
+
onChange: () => onPatch({ toolFilter: {
|
|
1180
|
+
kind: "deny",
|
|
1181
|
+
tools: draft.toolFilter.kind === "deny" ? draft.toolFilter.tools : []
|
|
1182
|
+
} })
|
|
1183
|
+
}), t("row.toolFilter.kind.deny")]
|
|
1184
|
+
})
|
|
1185
|
+
]
|
|
1186
|
+
})]
|
|
1187
|
+
}),
|
|
1188
|
+
(draft.toolFilter.kind === "allow" || draft.toolFilter.kind === "deny") && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1189
|
+
className: classMap.field,
|
|
1190
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1191
|
+
className: classMap.fieldLabel,
|
|
1192
|
+
children: t("row.toolFilter.tools")
|
|
1193
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MultiSelect, {
|
|
1194
|
+
options: toolList,
|
|
1195
|
+
value: draft.toolFilter.kind === "allow" || draft.toolFilter.kind === "deny" ? [...draft.toolFilter.tools] : [],
|
|
1196
|
+
onChange: (next) => onPatch({ toolFilter: {
|
|
1197
|
+
kind: draft.toolFilter.kind === "deny" ? "deny" : "allow",
|
|
1198
|
+
tools: next
|
|
1199
|
+
} }),
|
|
1200
|
+
t
|
|
1201
|
+
})]
|
|
1202
|
+
}),
|
|
1203
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1204
|
+
className: classMap.field,
|
|
1205
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1206
|
+
className: classMap.fieldLabel,
|
|
1207
|
+
children: t("row.maxDepth")
|
|
1208
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1209
|
+
type: "number",
|
|
1210
|
+
min: 0,
|
|
1211
|
+
className: classMap.input,
|
|
1212
|
+
value: draft.maxDepth,
|
|
1213
|
+
onChange: (e) => onPatch({ maxDepth: Number(e.target.value) })
|
|
1214
|
+
})]
|
|
1215
|
+
})
|
|
1216
|
+
]
|
|
1217
|
+
})]
|
|
1218
|
+
});
|
|
1219
|
+
}
|
|
1220
|
+
function MultiSelect({ options, value, onChange, t }) {
|
|
1221
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
1222
|
+
const [query, setQuery] = (0, react.useState)("");
|
|
1223
|
+
const containerRef = (0, react.useRef)(null);
|
|
1224
|
+
(0, react.useEffect)(() => {
|
|
1225
|
+
if (!open) return;
|
|
1226
|
+
const handler = (e) => {
|
|
1227
|
+
if (containerRef.current !== null && !containerRef.current.contains(e.target)) setOpen(false);
|
|
1228
|
+
};
|
|
1229
|
+
document.addEventListener("mousedown", handler);
|
|
1230
|
+
return () => document.removeEventListener("mousedown", handler);
|
|
1231
|
+
}, [open]);
|
|
1232
|
+
const q = query.trim().toLowerCase();
|
|
1233
|
+
const filtered = q === "" ? options : options.filter((o) => o.name.toLowerCase().includes(q));
|
|
1234
|
+
const valueSet = new Set(value);
|
|
1235
|
+
const filteredAllSelected = filtered.length > 0 && filtered.every((o) => valueSet.has(o.name));
|
|
1236
|
+
const selectAllFiltered = () => {
|
|
1237
|
+
if (filteredAllSelected) onChange(value.filter((n) => !filtered.some((o) => o.name === n)));
|
|
1238
|
+
else {
|
|
1239
|
+
const next = new Set(value);
|
|
1240
|
+
for (const o of filtered) next.add(o.name);
|
|
1241
|
+
onChange([...next]);
|
|
1242
|
+
}
|
|
1243
|
+
};
|
|
1244
|
+
const toggleOne = (name) => {
|
|
1245
|
+
if (valueSet.has(name)) onChange(value.filter((n) => n !== name));
|
|
1246
|
+
else onChange([...value, name]);
|
|
1247
|
+
};
|
|
1248
|
+
const label = value.length === 0 ? t("row.toolFilter.tools") : t("row.toolFilter.tools.selected").replace("{n}", String(value.length));
|
|
1249
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1250
|
+
className: classMap.multiSelect,
|
|
1251
|
+
ref: containerRef,
|
|
1252
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1253
|
+
type: "button",
|
|
1254
|
+
className: classMap.msButton,
|
|
1255
|
+
onClick: () => setOpen((prev) => !prev),
|
|
1256
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1257
|
+
className: classMap.msButtonLabel,
|
|
1258
|
+
children: label
|
|
1259
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { className: classMap.msCaret })]
|
|
1260
|
+
}), open && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1261
|
+
className: classMap.msPopup,
|
|
1262
|
+
children: [
|
|
1263
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1264
|
+
className: classMap.msSearch,
|
|
1265
|
+
placeholder: t("row.toolFilter.tools.search"),
|
|
1266
|
+
value: query,
|
|
1267
|
+
onChange: (e) => setQuery(e.target.value)
|
|
1268
|
+
}),
|
|
1269
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1270
|
+
className: classMap.msActions,
|
|
1271
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1272
|
+
type: "button",
|
|
1273
|
+
className: classMap.msActionLink,
|
|
1274
|
+
onClick: selectAllFiltered,
|
|
1275
|
+
children: filteredAllSelected ? t("row.toolFilter.tools.clear") : t("row.toolFilter.tools.selectAll")
|
|
1276
|
+
})
|
|
1277
|
+
}),
|
|
1278
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
1279
|
+
className: classMap.msList,
|
|
1280
|
+
children: filtered.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
|
|
1281
|
+
className: classMap.msEmpty,
|
|
1282
|
+
children: t("row.toolFilter.tools.empty")
|
|
1283
|
+
}) : filtered.map((o) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
|
|
1284
|
+
className: classMap.msItem,
|
|
1285
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1286
|
+
className: classMap.msItemLabel,
|
|
1287
|
+
title: o.description,
|
|
1288
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1289
|
+
type: "checkbox",
|
|
1290
|
+
checked: valueSet.has(o.name),
|
|
1291
|
+
onChange: () => toggleOne(o.name)
|
|
1292
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1293
|
+
className: classMap.msItemName,
|
|
1294
|
+
children: o.name
|
|
1295
|
+
})]
|
|
1296
|
+
})
|
|
1297
|
+
}, o.name))
|
|
1298
|
+
})
|
|
1299
|
+
]
|
|
1300
|
+
})]
|
|
1301
|
+
});
|
|
1302
|
+
}
|
|
1303
|
+
function DropdownPicker({ value, placeholder, items, disabled, onSelect }) {
|
|
1304
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
1305
|
+
const selectedLabel = items.find((i) => i.id === value)?.label;
|
|
1306
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
|
|
1307
|
+
open: open && disabled !== true,
|
|
1308
|
+
onClose: () => {
|
|
1309
|
+
setOpen(false);
|
|
1310
|
+
},
|
|
1311
|
+
items,
|
|
1312
|
+
selectedId: value === "" ? void 0 : value,
|
|
1313
|
+
onSelect: (id) => {
|
|
1314
|
+
onSelect(id);
|
|
1315
|
+
setOpen(false);
|
|
1316
|
+
},
|
|
1317
|
+
align: "start",
|
|
1318
|
+
portal: true,
|
|
1319
|
+
anchor: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1320
|
+
type: "button",
|
|
1321
|
+
className: classMap.dropdownButton,
|
|
1322
|
+
disabled,
|
|
1323
|
+
"aria-haspopup": "menu",
|
|
1324
|
+
"aria-expanded": open,
|
|
1325
|
+
onClick: () => {
|
|
1326
|
+
setOpen((v) => !v);
|
|
1327
|
+
},
|
|
1328
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1329
|
+
className: classMap.dropdownButtonLabel,
|
|
1330
|
+
children: selectedLabel ?? placeholder
|
|
1331
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { className: classMap.dropdownCaret })]
|
|
1332
|
+
})
|
|
1333
|
+
});
|
|
1334
|
+
}
|
|
1335
|
+
//#endregion
|
|
1336
|
+
//#region src/client/locales.ts
|
|
1337
|
+
/** Locale namespace id. */
|
|
1338
|
+
const NS = "ya-subagent";
|
|
1339
|
+
/** English dictionary. */
|
|
1340
|
+
const en = {
|
|
1341
|
+
"nav": "Subagents",
|
|
1342
|
+
"page.title": "Subagent Profiles",
|
|
1343
|
+
"page.empty": "No profiles configured. Add one below.",
|
|
1344
|
+
"page.add": "+ Add subagent",
|
|
1345
|
+
"page.add.placeholder.id": "profile-id",
|
|
1346
|
+
"page.add.placeholder.label": "Display name",
|
|
1347
|
+
"page.add.submit": "Create",
|
|
1348
|
+
"page.add.error": "Failed to add profile",
|
|
1349
|
+
"page.add.cancel": "Cancel",
|
|
1350
|
+
"row.label": "Label",
|
|
1351
|
+
"row.id": "ID",
|
|
1352
|
+
"row.model.kind.auto": "Auto (inherit parent model)",
|
|
1353
|
+
"row.model.kind.manual": "Manual",
|
|
1354
|
+
"row.model.provider": "Provider",
|
|
1355
|
+
"row.model.model": "Model",
|
|
1356
|
+
"row.model.provider.placeholder": "Select provider…",
|
|
1357
|
+
"row.model.model.placeholder": "Select model…",
|
|
1358
|
+
"row.model.noModels": "No models available",
|
|
1359
|
+
"row.persona": "Persona",
|
|
1360
|
+
"row.persona.kind.inherit": "Inherit (default persona)",
|
|
1361
|
+
"row.persona.kind.custom": "Custom",
|
|
1362
|
+
"row.persona.text": "Custom persona text",
|
|
1363
|
+
"row.toolFilter": "Tools",
|
|
1364
|
+
"row.toolFilter.kind.none": "No restriction",
|
|
1365
|
+
"row.toolFilter.kind.allow": "Whitelist",
|
|
1366
|
+
"row.toolFilter.kind.deny": "Blacklist",
|
|
1367
|
+
"row.toolFilter.tools": "Tools",
|
|
1368
|
+
"row.toolFilter.tools.search": "Search tools…",
|
|
1369
|
+
"row.toolFilter.tools.empty": "No tools found",
|
|
1370
|
+
"row.toolFilter.tools.selected": "{n} selected",
|
|
1371
|
+
"row.toolFilter.tools.selectAll": "Select all",
|
|
1372
|
+
"row.toolFilter.tools.clear": "Clear",
|
|
1373
|
+
"row.maxDepth": "Max depth",
|
|
1374
|
+
"row.delete": "Delete",
|
|
1375
|
+
"row.delete.confirm": "Delete this profile?",
|
|
1376
|
+
"row.save": "Save",
|
|
1377
|
+
"row.saved": "Saved",
|
|
1378
|
+
"row.error": "Failed to update profile",
|
|
1379
|
+
"row.expand": "Expand",
|
|
1380
|
+
"row.collapse": "Collapse",
|
|
1381
|
+
"badge.builtin": "builtin",
|
|
1382
|
+
"card.starting": "Starting…",
|
|
1383
|
+
"card.waiting": "Waiting for child session…",
|
|
1384
|
+
"card.idle": "idle",
|
|
1385
|
+
"card.running": "running",
|
|
1386
|
+
"card.completed": "completed",
|
|
1387
|
+
"card.child-running": "running",
|
|
1388
|
+
"card.child-idle": "idle",
|
|
1389
|
+
"card.toolcalls": "tool calls",
|
|
1390
|
+
"card.tokens": "Tokens",
|
|
1391
|
+
"card.calling": "calling",
|
|
1392
|
+
"card.open": "Click to open subagent session →",
|
|
1393
|
+
"card.unavailable": "session unavailable",
|
|
1394
|
+
"tree.tab": "Subagent Tree",
|
|
1395
|
+
"tree.empty": "No subagent children in this session.",
|
|
1396
|
+
"tree.rootHint": "Showing the root session’s subagent tree.",
|
|
1397
|
+
"tree.toolcalls": "tool calls",
|
|
1398
|
+
"tree.tokens": "Tokens",
|
|
1399
|
+
"tree.calling": "calling",
|
|
1400
|
+
"tree.state.running": "running",
|
|
1401
|
+
"tree.state.idle": "idle",
|
|
1402
|
+
"tree.state.settled": "settled",
|
|
1403
|
+
"repair.button": "Repair session history",
|
|
1404
|
+
"repair.confirm.title": "Repair session history?",
|
|
1405
|
+
"repair.confirm.body": "Scans every session log under $DSH_HOME/sessions and stamps \"ignorable\" on legacy ya-subagent/started events so the harness can load them again. Each modified file is backed up to .bak first.",
|
|
1406
|
+
"repair.confirm.warning": "If dsh is running, a session being written right now may conflict; prefer running this when no agent is active.",
|
|
1407
|
+
"repair.confirm.cancel": "Cancel",
|
|
1408
|
+
"repair.confirm.proceed": "Repair",
|
|
1409
|
+
"repair.running": "Repairing…",
|
|
1410
|
+
"repair.result.title": "Repair complete",
|
|
1411
|
+
"repair.result.scanned": "Scanned: {n}",
|
|
1412
|
+
"repair.result.repaired": "Repaired: {n}",
|
|
1413
|
+
"repair.result.skipped": "Skipped (already clean): {n}",
|
|
1414
|
+
"repair.result.errors": "Errors: {n}",
|
|
1415
|
+
"repair.result.errorEntry": "{path}: {message}",
|
|
1416
|
+
"repair.result.close": "Close",
|
|
1417
|
+
"repair.error": "Repair failed"
|
|
1418
|
+
};
|
|
1419
|
+
/** Chinese dictionary. */
|
|
1420
|
+
const zh = {
|
|
1421
|
+
"nav": "子代理",
|
|
1422
|
+
"page.title": "子代理配置",
|
|
1423
|
+
"page.empty": "暂无配置,请在下方添加。",
|
|
1424
|
+
"page.add": "+ 添加子代理",
|
|
1425
|
+
"page.add.placeholder.id": "profile-id",
|
|
1426
|
+
"page.add.placeholder.label": "显示名",
|
|
1427
|
+
"page.add.submit": "创建",
|
|
1428
|
+
"page.add.error": "添加失败",
|
|
1429
|
+
"page.add.cancel": "取消",
|
|
1430
|
+
"row.label": "名称",
|
|
1431
|
+
"row.id": "ID",
|
|
1432
|
+
"row.model.kind.auto": "自动(继承父代理模型)",
|
|
1433
|
+
"row.model.kind.manual": "手动指定",
|
|
1434
|
+
"row.model.provider": "Provider",
|
|
1435
|
+
"row.model.model": "模型",
|
|
1436
|
+
"row.model.provider.placeholder": "选择 Provider…",
|
|
1437
|
+
"row.model.model.placeholder": "选择模型…",
|
|
1438
|
+
"row.model.noModels": "无可用模型",
|
|
1439
|
+
"row.persona": "人设",
|
|
1440
|
+
"row.persona.kind.inherit": "跟随默认人设",
|
|
1441
|
+
"row.persona.kind.custom": "自定义",
|
|
1442
|
+
"row.persona.text": "自定义人设文本",
|
|
1443
|
+
"row.toolFilter": "工具",
|
|
1444
|
+
"row.toolFilter.kind.none": "不管",
|
|
1445
|
+
"row.toolFilter.kind.allow": "白名单",
|
|
1446
|
+
"row.toolFilter.kind.deny": "黑名单",
|
|
1447
|
+
"row.toolFilter.tools": "工具列表",
|
|
1448
|
+
"row.toolFilter.tools.search": "搜索工具…",
|
|
1449
|
+
"row.toolFilter.tools.empty": "未找到工具",
|
|
1450
|
+
"row.toolFilter.tools.selected": "已选 {n} 个",
|
|
1451
|
+
"row.toolFilter.tools.selectAll": "全选",
|
|
1452
|
+
"row.toolFilter.tools.clear": "清空",
|
|
1453
|
+
"row.maxDepth": "最大递归深度",
|
|
1454
|
+
"row.delete": "删除",
|
|
1455
|
+
"row.delete.confirm": "确定删除此配置?",
|
|
1456
|
+
"row.save": "保存",
|
|
1457
|
+
"row.saved": "已保存",
|
|
1458
|
+
"row.error": "保存失败",
|
|
1459
|
+
"row.expand": "展开",
|
|
1460
|
+
"row.collapse": "收起",
|
|
1461
|
+
"badge.builtin": "内置",
|
|
1462
|
+
"card.starting": "启动中…",
|
|
1463
|
+
"card.waiting": "等待子会话…",
|
|
1464
|
+
"card.idle": "空闲",
|
|
1465
|
+
"card.running": "运行中",
|
|
1466
|
+
"card.completed": "已完成",
|
|
1467
|
+
"card.child-running": "运行中",
|
|
1468
|
+
"card.child-idle": "空闲",
|
|
1469
|
+
"card.toolcalls": "次工具调用",
|
|
1470
|
+
"card.tokens": "Tokens",
|
|
1471
|
+
"card.calling": "调用中",
|
|
1472
|
+
"card.open": "点击打开子代理会话 →",
|
|
1473
|
+
"card.unavailable": "会话不可用",
|
|
1474
|
+
"tree.tab": "子代理树",
|
|
1475
|
+
"tree.empty": "当前会话暂无子代理。",
|
|
1476
|
+
"tree.rootHint": "显示根会话的子代理树。",
|
|
1477
|
+
"tree.toolcalls": "次工具调用",
|
|
1478
|
+
"tree.tokens": "Tokens",
|
|
1479
|
+
"tree.calling": "调用中",
|
|
1480
|
+
"tree.state.running": "运行中",
|
|
1481
|
+
"tree.state.idle": "空闲",
|
|
1482
|
+
"tree.state.settled": "已结束",
|
|
1483
|
+
"repair.button": "修复历史会话",
|
|
1484
|
+
"repair.confirm.title": "修复历史会话?",
|
|
1485
|
+
"repair.confirm.body": "将扫描 $DSH_HOME/sessions 下的所有会话日志,为遗留的 ya-subagent/started 事件补上 \"ignorable\" 标记,使 harness 能重新加载。每个被修改的文件会先备份为 .bak。",
|
|
1486
|
+
"repair.confirm.warning": "若 dsh 正在运行,正在写入的会话可能冲突;建议在无活跃代理时执行。",
|
|
1487
|
+
"repair.confirm.cancel": "取消",
|
|
1488
|
+
"repair.confirm.proceed": "修复",
|
|
1489
|
+
"repair.running": "修复中…",
|
|
1490
|
+
"repair.result.title": "修复完成",
|
|
1491
|
+
"repair.result.scanned": "扫描:{n}",
|
|
1492
|
+
"repair.result.repaired": "已修复:{n}",
|
|
1493
|
+
"repair.result.skipped": "跳过(已干净):{n}",
|
|
1494
|
+
"repair.result.errors": "错误:{n}",
|
|
1495
|
+
"repair.result.errorEntry": "{path}:{message}",
|
|
1496
|
+
"repair.result.close": "关闭",
|
|
1497
|
+
"repair.error": "修复失败"
|
|
1498
|
+
};
|
|
1499
|
+
//#endregion
|
|
1500
|
+
//#region src/client/index.ts
|
|
1501
|
+
/** Required services: settings/tool slots, locale, sessions, connection. */
|
|
1502
|
+
const inject = [
|
|
1503
|
+
"slots",
|
|
1504
|
+
"locale",
|
|
1505
|
+
"sessions",
|
|
1506
|
+
"connection"
|
|
1507
|
+
];
|
|
1508
|
+
/**
|
|
1509
|
+
* Client plugin body: register settings page + single `subagent` toolview slot.
|
|
1510
|
+
* @param ctx - client root context.
|
|
1511
|
+
*/
|
|
1512
|
+
function apply(ctx) {
|
|
1513
|
+
ctx.effect(() => ctx.locale.register(NS, {
|
|
1514
|
+
zh,
|
|
1515
|
+
en
|
|
1516
|
+
}), "ya-subagent: dictionaries");
|
|
1517
|
+
const connection = ctx.connection;
|
|
1518
|
+
const t = ctx.locale.bind(NS);
|
|
1519
|
+
const profileLabels = /* @__PURE__ */ new Map();
|
|
1520
|
+
const refreshProfileLabels = (profiles) => {
|
|
1521
|
+
profileLabels.clear();
|
|
1522
|
+
for (const p of profiles) profileLabels.set(p.id, p.label);
|
|
1523
|
+
};
|
|
1524
|
+
const fetchProfilesInternal = async () => {
|
|
1525
|
+
const result = await connection.rpc.call("/ya-subagent", "profiles.list", {});
|
|
1526
|
+
return result.ok ? result.value.profiles : [];
|
|
1527
|
+
};
|
|
1528
|
+
fetchProfilesInternal().then(refreshProfileLabels).catch((error) => {
|
|
1529
|
+
ctx.logger.error("ya-subagent: initial profile label fetch failed", error);
|
|
1530
|
+
});
|
|
1531
|
+
ctx.on("connection/reset", () => {
|
|
1532
|
+
fetchProfilesInternal().then(refreshProfileLabels).catch((error) => {
|
|
1533
|
+
ctx.logger.error("ya-subagent: post-reset profile label fetch failed", error);
|
|
1534
|
+
});
|
|
1535
|
+
});
|
|
1536
|
+
const cardInjected = () => ({
|
|
1537
|
+
sessions: ctx.sessions,
|
|
1538
|
+
profileLabelOf: (id) => profileLabels.get(id)
|
|
1539
|
+
});
|
|
1540
|
+
ctx.effect(() => ctx.slots.register({
|
|
1541
|
+
name: "tool.call.toolview",
|
|
1542
|
+
key: "subagent",
|
|
1543
|
+
locale: NS,
|
|
1544
|
+
inject: cardInjected
|
|
1545
|
+
}, SubagentCard), "ya-subagent: subagent toolview");
|
|
1546
|
+
const fetchProfiles = fetchProfilesInternal;
|
|
1547
|
+
const settingsInjected = () => ({
|
|
1548
|
+
rpc: connection.rpc,
|
|
1549
|
+
fetchProfiles,
|
|
1550
|
+
t
|
|
1551
|
+
});
|
|
1552
|
+
ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
1553
|
+
name: "settings.section",
|
|
1554
|
+
id: "ya-subagent",
|
|
1555
|
+
order: 30,
|
|
1556
|
+
label: () => t("nav"),
|
|
1557
|
+
inject: settingsInjected
|
|
1558
|
+
}, SettingsPage));
|
|
1559
|
+
const treeInjected = () => ({
|
|
1560
|
+
sessions: ctx.sessions,
|
|
1561
|
+
profileLabelOf: (id) => profileLabels.get(id)
|
|
1562
|
+
});
|
|
1563
|
+
ctx.slots.inject("conversation.view", () => ctx.slots.register({
|
|
1564
|
+
name: "conversation.view",
|
|
1565
|
+
id: "subagent-tree",
|
|
1566
|
+
order: 20,
|
|
1567
|
+
label: () => ctx.locale.bind(NS)("tree.tab"),
|
|
1568
|
+
locale: NS,
|
|
1569
|
+
inject: treeInjected
|
|
1570
|
+
}, SubagentTreeView));
|
|
1571
|
+
}
|
|
1572
|
+
//#endregion
|
|
1573
|
+
exports.apply = apply;
|
|
1574
|
+
exports.inject = inject;
|
|
1575
|
+
return module.exports;
|
|
1576
|
+
}
|
|
1577
|
+
});
|
|
1578
|
+
|
|
1579
1579
|
//# sourceMappingURL=client.js.map
|