dsh-advisor 0.1.0
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/LICENSE +21 -0
- package/README.i18n.yaml +7 -0
- package/README.md +303 -0
- package/README.zh.md +166 -0
- package/cordis.patch.yml +6 -0
- package/lib/advisor-runtime.d.ts +242 -0
- package/lib/advisor-runtime.js +662 -0
- package/lib/advisor-runtime.js.map +1 -0
- package/lib/client/advisor-card.d.ts +90 -0
- package/lib/client/advisor-store.d.ts +310 -0
- package/lib/client/index.d.ts +39 -0
- package/lib/client/locales.d.ts +40 -0
- package/lib/client.d.ts +1 -0
- package/lib/client.js +840 -0
- package/lib/commands.d.ts +136 -0
- package/lib/commands.js +185 -0
- package/lib/commands.js.map +1 -0
- package/lib/config.d.ts +74 -0
- package/lib/config.js +93 -0
- package/lib/config.js.map +1 -0
- package/lib/delivery.d.ts +129 -0
- package/lib/delivery.js +169 -0
- package/lib/delivery.js.map +1 -0
- package/lib/emission-guard.d.ts +99 -0
- package/lib/emission-guard.js +155 -0
- package/lib/emission-guard.js.map +1 -0
- package/lib/gateway.d.ts +116 -0
- package/lib/gateway.js +214 -0
- package/lib/gateway.js.map +1 -0
- package/lib/index.d.ts +48 -0
- package/lib/index.js +485 -0
- package/lib/index.js.map +1 -0
- package/lib/kinds.d.ts +38 -0
- package/lib/kinds.js +24 -0
- package/lib/kinds.js.map +1 -0
- package/lib/prompts.d.ts +22 -0
- package/lib/prompts.js +38 -0
- package/lib/prompts.js.map +1 -0
- package/lib/settings.d.ts +96 -0
- package/lib/settings.js +141 -0
- package/lib/settings.js.map +1 -0
- package/lib/transcript.d.ts +257 -0
- package/lib/transcript.js +530 -0
- package/lib/transcript.js.map +1 -0
- package/package.json +90 -0
- package/scripts/build-client.mjs +268 -0
package/lib/client.js
ADDED
|
@@ -0,0 +1,840 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({ id: "dsh-advisor", factory: (require) => {
|
|
2
|
+
var module = { exports: {} }; var exports = module.exports;
|
|
3
|
+
"use strict";
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name in all)
|
|
11
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
+
};
|
|
13
|
+
var __copyProps = (to, from, except, desc) => {
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
+
for (let key of __getOwnPropNames(from))
|
|
16
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
17
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
18
|
+
}
|
|
19
|
+
return to;
|
|
20
|
+
};
|
|
21
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
22
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
23
|
+
|
|
24
|
+
// src/client/index.ts
|
|
25
|
+
var index_exports = {};
|
|
26
|
+
__export(index_exports, {
|
|
27
|
+
apply: () => apply,
|
|
28
|
+
inject: () => inject,
|
|
29
|
+
refreshIfLoaded: () => refreshIfLoaded
|
|
30
|
+
});
|
|
31
|
+
module.exports = __toCommonJS(index_exports);
|
|
32
|
+
var import_dsh_client_web_react = require("@deepseek-ai/dsh-client-web-react");
|
|
33
|
+
|
|
34
|
+
// src/client/advisor-card.tsx
|
|
35
|
+
var import_react = require("react");
|
|
36
|
+
var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
37
|
+
|
|
38
|
+
var css = `.fgpdlq_card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;transition:border-color .16s,background .16s}.fgpdlq_card:hover{border-color:var(--dsw-alias-label-dimmed)}.fgpdlq_cardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}.fgpdlq_header{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}.fgpdlq_header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}.fgpdlq_headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.fgpdlq_name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.fgpdlq_description{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}.fgpdlq_chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.fgpdlq_chevronOpen{transform:rotate(180deg)}.fgpdlq_body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}.fgpdlq_readOnly{color:var(--dsw-alias-label-tertiary);margin:12px 0 0;font-size:12px;line-height:1.5}.fgpdlq_pending{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.fgpdlq_footer{border-top:1px solid var(--dsw-alias-border-l2);justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px;display:flex}.fgpdlq_failed{min-width:0;color:var(--dsw-alias-label-error);flex:1;margin:0;font-size:12px;line-height:1.5}.fgpdlq_discard,.fgpdlq_save{appearance:none;font:inherit;cursor:pointer;border:1px solid #0000;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5}.fgpdlq_discard{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0}.fgpdlq_discard:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed)}.fgpdlq_save{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}.fgpdlq_discard:disabled,.fgpdlq_save:disabled{opacity:.4;cursor:default}.fgpdlq_discard:focus-visible,.fgpdlq_save:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.fgpdlq_notice{color:var(--dsw-alias-state-warn-label);margin:12px 0 0;font-size:12px;line-height:18px}.fgpdlq_savedNotice{color:var(--dsw-alias-state-success-primary);margin:12px 0 0;font-size:12px;line-height:18px}.fgpdlq_error{color:var(--dsw-alias-state-error-primary);margin:12px 0 0;font-size:12px;line-height:18px}.fgpdlq_form{flex-direction:column;gap:12px;padding:12px 0 0;display:flex}.fgpdlq_checkboxRow{align-items:center;gap:8px;display:flex}.fgpdlq_checkLabel{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:500;line-height:22px}.fgpdlq_checkbox{width:16px;height:16px;accent-color:var(--dsw-alias-brand-primary);cursor:pointer;margin:0}.fgpdlq_checkbox:disabled{opacity:.4;cursor:default}.fgpdlq_checkbox:focus-visible{outline:2px solid var(--dsw-alias-border-l3);outline-offset:2px}.fgpdlq_fieldset{border:none;flex-direction:column;gap:12px;margin:0;padding:0;display:flex}.fgpdlq_field{flex-direction:column;gap:6px;display:flex}.fgpdlq_fieldLabel{color:var(--dsw-alias-label-secondary);align-items:center;gap:10px;font-size:12px;font-weight:500;line-height:18px;display:inline-flex}.fgpdlq_input{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);width:100%;height:32px;font:inherit;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 10px;font-size:14px;line-height:22px}select.fgpdlq_input{cursor:pointer;max-width:240px}.fgpdlq_input:focus{border-color:var(--dsw-alias-brand-primary);outline:none}.fgpdlq_input::placeholder{color:var(--dsw-alias-label-dimmed)}.fgpdlq_input:disabled{opacity:.6;cursor:default}.fgpdlq_selectInput{appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12' fill='none'%3E%3Cpath d='M3 4.5L6 7.5L9 4.5' stroke='%2381858C' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");background-position:right 12px center;background-repeat:no-repeat;background-size:12px 12px;padding-right:32px}.fgpdlq_textarea{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);width:100%;min-height:96px;font:inherit;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);resize:vertical;border-radius:8px;padding:8px 10px;font-size:14px;line-height:22px}.fgpdlq_textarea:focus{border-color:var(--dsw-alias-brand-primary);outline:none}.fgpdlq_textarea:disabled{opacity:.6;cursor:default}.fgpdlq_numberFields{grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:12px;display:grid}.fgpdlq_hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:18px}.fgpdlq_warnHint{color:var(--dsw-alias-state-warn-label);margin:0;font-size:12px;line-height:18px}`;
|
|
39
|
+
var tagId = "dsh-advisor/advisor-card.module.css";
|
|
40
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
41
|
+
const tag = document.createElement("style");
|
|
42
|
+
tag.dataset.plugin = "dsh-advisor";
|
|
43
|
+
tag.dataset.pluginCss = tagId;
|
|
44
|
+
tag.textContent = css;
|
|
45
|
+
document.head.appendChild(tag);
|
|
46
|
+
}
|
|
47
|
+
var advisor_card_module_css_default = { "body": "fgpdlq_body", "card": "fgpdlq_card", "cardOpen": "fgpdlq_cardOpen", "checkLabel": "fgpdlq_checkLabel", "checkbox": "fgpdlq_checkbox", "checkboxRow": "fgpdlq_checkboxRow", "chevron": "fgpdlq_chevron", "chevronOpen": "fgpdlq_chevronOpen", "description": "fgpdlq_description", "discard": "fgpdlq_discard", "error": "fgpdlq_error", "failed": "fgpdlq_failed", "field": "fgpdlq_field", "fieldLabel": "fgpdlq_fieldLabel", "fieldset": "fgpdlq_fieldset", "footer": "fgpdlq_footer", "form": "fgpdlq_form", "headText": "fgpdlq_headText", "header": "fgpdlq_header", "hint": "fgpdlq_hint", "input": "fgpdlq_input", "name": "fgpdlq_name", "notice": "fgpdlq_notice", "numberFields": "fgpdlq_numberFields", "pending": "fgpdlq_pending", "readOnly": "fgpdlq_readOnly", "save": "fgpdlq_save", "savedNotice": "fgpdlq_savedNotice", "selectInput": "fgpdlq_selectInput", "textarea": "fgpdlq_textarea", "warnHint": "fgpdlq_warnHint" };
|
|
48
|
+
|
|
49
|
+
// src/client/advisor-card.tsx
|
|
50
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
51
|
+
function failureCopy(failure, t) {
|
|
52
|
+
switch (failure.kind) {
|
|
53
|
+
case "gate":
|
|
54
|
+
return void 0;
|
|
55
|
+
case "message":
|
|
56
|
+
return failure.message;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function AdvisorCard(props) {
|
|
60
|
+
const { controller, useSnapshot, t } = props;
|
|
61
|
+
const state = useSnapshot((snapshot) => snapshot);
|
|
62
|
+
const [userOpen, setUserOpen] = (0, import_react.useState)(false);
|
|
63
|
+
const degraded = state.status === "ready" ? !state.advisorPresent : state.degraded;
|
|
64
|
+
const open = userOpen || state.status === "error" || degraded;
|
|
65
|
+
if (state.status === "idle") void controller.load();
|
|
66
|
+
const title = t("title");
|
|
67
|
+
const header = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
68
|
+
"button",
|
|
69
|
+
{
|
|
70
|
+
type: "button",
|
|
71
|
+
className: advisor_card_module_css_default["header"],
|
|
72
|
+
"aria-expanded": open,
|
|
73
|
+
"aria-label": `${t(open ? "collapse" : "expand")}: ${title}`,
|
|
74
|
+
onClick: () => {
|
|
75
|
+
if (!degraded && state.status !== "error") setUserOpen(!userOpen);
|
|
76
|
+
},
|
|
77
|
+
children: [
|
|
78
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: advisor_card_module_css_default["headText"], children: [
|
|
79
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: advisor_card_module_css_default["name"], children: title }),
|
|
80
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: advisor_card_module_css_default["description"], children: t("intro") })
|
|
81
|
+
] }),
|
|
82
|
+
state.dirty ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: advisor_card_module_css_default["pending"], children: t("unsaved") }) : null,
|
|
83
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
84
|
+
import_dsh_client_ui_primitives.IconChevronDownOutline14,
|
|
85
|
+
{
|
|
86
|
+
className: open ? `${advisor_card_module_css_default["chevron"]} ${advisor_card_module_css_default["chevronOpen"]}` : advisor_card_module_css_default["chevron"]
|
|
87
|
+
}
|
|
88
|
+
)
|
|
89
|
+
]
|
|
90
|
+
}
|
|
91
|
+
);
|
|
92
|
+
let body;
|
|
93
|
+
if (state.status === "error") {
|
|
94
|
+
body = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: advisor_card_module_css_default["body"], children: [
|
|
95
|
+
state.applyState.kind === "saved" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: advisor_card_module_css_default["savedNotice"], role: "status", children: t("saved") }) : null,
|
|
96
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: advisor_card_module_css_default["error"], children: `${t("loadFailed")}: ${state.error ?? ""}` }),
|
|
97
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: advisor_card_module_css_default["footer"], children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: advisor_card_module_css_default["discard"], onClick: () => {
|
|
98
|
+
void controller.load();
|
|
99
|
+
}, children: t("retry") }) })
|
|
100
|
+
] });
|
|
101
|
+
} else if (degraded) {
|
|
102
|
+
body = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: advisor_card_module_css_default["body"], children: [
|
|
103
|
+
state.applyState.kind === "saved" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: advisor_card_module_css_default["savedNotice"], role: "status", children: t("saved") }) : null,
|
|
104
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: advisor_card_module_css_default["notice"], role: "status", children: t("namespaceUnavailable") }),
|
|
105
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: advisor_card_module_css_default["footer"], children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: advisor_card_module_css_default["discard"], onClick: () => {
|
|
106
|
+
void controller.load();
|
|
107
|
+
}, children: t("retry") }) })
|
|
108
|
+
] });
|
|
109
|
+
} else if (state.status === "ready") {
|
|
110
|
+
const { draft, providers, writable, applyState } = state;
|
|
111
|
+
const providerEmpty = draft.provider === void 0;
|
|
112
|
+
const modelEmpty = draft.model === void 0;
|
|
113
|
+
const gateFailed = draft.enabled && (providerEmpty || modelEmpty);
|
|
114
|
+
const saving = applyState.kind === "saving";
|
|
115
|
+
const busy = !writable || saving;
|
|
116
|
+
const selectedModels = draft.provider === void 0 ? [] : state.modelsByProvider[draft.provider] ?? [];
|
|
117
|
+
const modelsEmpty = draft.provider !== void 0 && Object.hasOwn(state.modelsEmptyReason, draft.provider);
|
|
118
|
+
const providerStale = draft.provider !== void 0 && !providers.some((option) => option.provider === draft.provider);
|
|
119
|
+
const modelStale = !providerStale && draft.model !== void 0 && selectedModels.length > 0 && !selectedModels.some((option) => option.id === draft.model);
|
|
120
|
+
const errorText = applyState.kind === "error" ? failureCopy(applyState.failure, t) : void 0;
|
|
121
|
+
const saveDisabled = !state.dirty || gateFailed || saving || !writable;
|
|
122
|
+
const discardDisabled = !state.dirty || saving;
|
|
123
|
+
body = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: advisor_card_module_css_default["body"], children: [
|
|
124
|
+
!writable ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: advisor_card_module_css_default["readOnly"], role: "status", children: t("readOnly") }) : null,
|
|
125
|
+
applyState.kind === "saved" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: advisor_card_module_css_default["savedNotice"], role: "status", children: t("saved") }) : null,
|
|
126
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: advisor_card_module_css_default["form"], children: [
|
|
127
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: advisor_card_module_css_default["checkboxRow"], children: [
|
|
128
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { htmlFor: "advisor-enabled", className: advisor_card_module_css_default["checkLabel"], children: t("enabled") }),
|
|
129
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
130
|
+
"input",
|
|
131
|
+
{
|
|
132
|
+
id: "advisor-enabled",
|
|
133
|
+
type: "checkbox",
|
|
134
|
+
className: advisor_card_module_css_default["checkbox"],
|
|
135
|
+
checked: draft.enabled,
|
|
136
|
+
disabled: busy,
|
|
137
|
+
onChange: (event) => {
|
|
138
|
+
controller.setEnabled(event.target.checked);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
)
|
|
142
|
+
] }),
|
|
143
|
+
draft.enabled ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("fieldset", { className: advisor_card_module_css_default["fieldset"], children: [
|
|
144
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: advisor_card_module_css_default["field"], children: [
|
|
145
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { htmlFor: "advisor-provider", className: advisor_card_module_css_default["fieldLabel"], children: t("provider") }),
|
|
146
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
147
|
+
"select",
|
|
148
|
+
{
|
|
149
|
+
id: "advisor-provider",
|
|
150
|
+
"aria-label": t("provider"),
|
|
151
|
+
className: `${advisor_card_module_css_default["input"]} ${advisor_card_module_css_default["selectInput"]}`,
|
|
152
|
+
value: draft.provider ?? "",
|
|
153
|
+
disabled: busy,
|
|
154
|
+
onChange: (event) => {
|
|
155
|
+
controller.setProvider(event.target.value);
|
|
156
|
+
},
|
|
157
|
+
children: [
|
|
158
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "", children: t("providerPlaceholder") }),
|
|
159
|
+
providers.map((option) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: option.provider, children: option.displayName }, option.provider))
|
|
160
|
+
]
|
|
161
|
+
}
|
|
162
|
+
),
|
|
163
|
+
providers.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: advisor_card_module_css_default["hint"], children: t("noProviders") }) : null,
|
|
164
|
+
providerEmpty ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: advisor_card_module_css_default["warnHint"], children: t("providerRequired") }) : null,
|
|
165
|
+
providerStale ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: advisor_card_module_css_default["hint"], children: t("staleProvider") }) : null
|
|
166
|
+
] }),
|
|
167
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: advisor_card_module_css_default["field"], children: [
|
|
168
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { htmlFor: "advisor-model", className: advisor_card_module_css_default["fieldLabel"], children: t("model") }),
|
|
169
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
170
|
+
"select",
|
|
171
|
+
{
|
|
172
|
+
id: "advisor-model",
|
|
173
|
+
"aria-label": t("model"),
|
|
174
|
+
className: `${advisor_card_module_css_default["input"]} ${advisor_card_module_css_default["selectInput"]}`,
|
|
175
|
+
value: draft.model ?? "",
|
|
176
|
+
disabled: busy || draft.provider === void 0 || selectedModels.length === 0,
|
|
177
|
+
onChange: (event) => {
|
|
178
|
+
controller.setModel(event.target.value);
|
|
179
|
+
},
|
|
180
|
+
children: [
|
|
181
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "", children: t("modelPlaceholder") }),
|
|
182
|
+
selectedModels.map((option) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: option.id, children: option.name }, option.id))
|
|
183
|
+
]
|
|
184
|
+
}
|
|
185
|
+
),
|
|
186
|
+
modelsEmpty ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: advisor_card_module_css_default["hint"], children: t("noModels") }) : null,
|
|
187
|
+
modelStale ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: advisor_card_module_css_default["hint"], children: t("staleModel") }) : null,
|
|
188
|
+
!providerEmpty && modelEmpty ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: advisor_card_module_css_default["warnHint"], children: t("modelRequired") }) : null
|
|
189
|
+
] })
|
|
190
|
+
] }) : null,
|
|
191
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: advisor_card_module_css_default["field"], children: [
|
|
192
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { htmlFor: "advisor-system-prompt", className: advisor_card_module_css_default["fieldLabel"], children: t("systemPrompt") }),
|
|
193
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
194
|
+
"textarea",
|
|
195
|
+
{
|
|
196
|
+
id: "advisor-system-prompt",
|
|
197
|
+
"aria-label": t("systemPrompt"),
|
|
198
|
+
placeholder: t("systemPromptPlaceholder"),
|
|
199
|
+
className: advisor_card_module_css_default["textarea"],
|
|
200
|
+
value: draft.systemPrompt,
|
|
201
|
+
disabled: busy,
|
|
202
|
+
onChange: (event) => {
|
|
203
|
+
controller.setSystemPrompt(event.target.value);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
)
|
|
207
|
+
] }),
|
|
208
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: advisor_card_module_css_default["numberFields"], children: [
|
|
209
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: advisor_card_module_css_default["field"], children: [
|
|
210
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { htmlFor: "advisor-immune-turns", className: advisor_card_module_css_default["fieldLabel"], children: t("immuneTurns") }),
|
|
211
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
212
|
+
"input",
|
|
213
|
+
{
|
|
214
|
+
id: "advisor-immune-turns",
|
|
215
|
+
"aria-label": t("immuneTurns"),
|
|
216
|
+
className: advisor_card_module_css_default["input"],
|
|
217
|
+
type: "number",
|
|
218
|
+
min: 0,
|
|
219
|
+
step: 1,
|
|
220
|
+
value: draft.immuneTurns ?? "",
|
|
221
|
+
disabled: busy,
|
|
222
|
+
onChange: (event) => {
|
|
223
|
+
controller.setImmuneTurns(event.target.value === "" ? void 0 : Number(event.target.value));
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
)
|
|
227
|
+
] }),
|
|
228
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: advisor_card_module_css_default["field"], children: [
|
|
229
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { htmlFor: "advisor-max-delta-messages", className: advisor_card_module_css_default["fieldLabel"], children: t("maxDeltaMessages") }),
|
|
230
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
231
|
+
"input",
|
|
232
|
+
{
|
|
233
|
+
id: "advisor-max-delta-messages",
|
|
234
|
+
"aria-label": t("maxDeltaMessages"),
|
|
235
|
+
className: advisor_card_module_css_default["input"],
|
|
236
|
+
type: "number",
|
|
237
|
+
min: 0,
|
|
238
|
+
step: 1,
|
|
239
|
+
value: draft.maxDeltaMessages ?? "",
|
|
240
|
+
disabled: busy,
|
|
241
|
+
onChange: (event) => {
|
|
242
|
+
controller.setMaxDeltaMessages(event.target.value === "" ? void 0 : Number(event.target.value));
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
)
|
|
246
|
+
] })
|
|
247
|
+
] })
|
|
248
|
+
] }),
|
|
249
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: advisor_card_module_css_default["footer"], children: [
|
|
250
|
+
errorText === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: advisor_card_module_css_default["failed"], role: "status", children: errorText }),
|
|
251
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
252
|
+
"button",
|
|
253
|
+
{
|
|
254
|
+
type: "button",
|
|
255
|
+
className: advisor_card_module_css_default["discard"],
|
|
256
|
+
disabled: discardDisabled,
|
|
257
|
+
onClick: () => {
|
|
258
|
+
controller.discard();
|
|
259
|
+
},
|
|
260
|
+
children: t("discard")
|
|
261
|
+
}
|
|
262
|
+
),
|
|
263
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
264
|
+
"button",
|
|
265
|
+
{
|
|
266
|
+
type: "button",
|
|
267
|
+
className: advisor_card_module_css_default["save"],
|
|
268
|
+
disabled: saveDisabled,
|
|
269
|
+
onClick: () => {
|
|
270
|
+
void controller.apply();
|
|
271
|
+
},
|
|
272
|
+
children: t(saving ? "saving" : "save")
|
|
273
|
+
}
|
|
274
|
+
)
|
|
275
|
+
] })
|
|
276
|
+
] });
|
|
277
|
+
} else {
|
|
278
|
+
body = /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: advisor_card_module_css_default["body"] });
|
|
279
|
+
}
|
|
280
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { className: open ? `${advisor_card_module_css_default["card"]} ${advisor_card_module_css_default["cardOpen"]}` : advisor_card_module_css_default["card"], children: [
|
|
281
|
+
header,
|
|
282
|
+
open ? body : null
|
|
283
|
+
] });
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// src/client/advisor-store.ts
|
|
287
|
+
var import_client = require("@deepseek-ai/dsh-client-runtime/client");
|
|
288
|
+
var import_dsh_client_schema_form = require("@deepseek-ai/dsh-client-schema-form");
|
|
289
|
+
function defaultDraft() {
|
|
290
|
+
return { enabled: false, systemPrompt: "", immuneTurns: 3, maxDeltaMessages: 60 };
|
|
291
|
+
}
|
|
292
|
+
function stringField(value) {
|
|
293
|
+
return typeof value === "string" && value.trim().length > 0 ? value : void 0;
|
|
294
|
+
}
|
|
295
|
+
function numberField(value, fallback) {
|
|
296
|
+
return typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.floor(value)) : fallback;
|
|
297
|
+
}
|
|
298
|
+
function draftOfConfig(config) {
|
|
299
|
+
return {
|
|
300
|
+
enabled: config?.enabled === true,
|
|
301
|
+
provider: stringField(config?.provider),
|
|
302
|
+
model: stringField(config?.model),
|
|
303
|
+
systemPrompt: typeof config?.systemPrompt === "string" ? config.systemPrompt : "",
|
|
304
|
+
immuneTurns: numberField(config?.immuneTurns, 3),
|
|
305
|
+
maxDeltaMessages: numberField(config?.maxDeltaMessages, 60)
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
function gateFailure(draft) {
|
|
309
|
+
if (!draft.enabled) return void 0;
|
|
310
|
+
if (draft.provider === void 0) return "provider";
|
|
311
|
+
if (draft.model === void 0) return "model";
|
|
312
|
+
return void 0;
|
|
313
|
+
}
|
|
314
|
+
function profileModels(profile) {
|
|
315
|
+
const models = (0, import_dsh_client_schema_form.getPath)(profile, ["models"]);
|
|
316
|
+
if (!Array.isArray(models)) return void 0;
|
|
317
|
+
return models.flatMap((entry) => {
|
|
318
|
+
if (typeof entry !== "object" || entry === null) return [];
|
|
319
|
+
const model = entry;
|
|
320
|
+
if (typeof model.id !== "string" || model.id.length === 0) return [];
|
|
321
|
+
return [{
|
|
322
|
+
id: model.id,
|
|
323
|
+
name: typeof model.name === "string" ? model.name : model.id,
|
|
324
|
+
...typeof model.description === "string" ? { description: model.description } : {}
|
|
325
|
+
}];
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
var AdvisorSettingsStore = class {
|
|
329
|
+
/**
|
|
330
|
+
* @param api - the wire face (settings/llm domains) for the provider/model
|
|
331
|
+
* directory (the advisor namespace is NOT on that wire).
|
|
332
|
+
* @param rpc - the connection's generic RPC caller for the host gateway
|
|
333
|
+
* channel (`/api`), injected from the connection handle.
|
|
334
|
+
*/
|
|
335
|
+
constructor(api, rpc) {
|
|
336
|
+
__publicField(this, "api", api);
|
|
337
|
+
__publicField(this, "rpc", rpc);
|
|
338
|
+
/** The snapshot the card renders from (uSES-safe store). */
|
|
339
|
+
__publicField(this, "store", (0, import_client.createSnapshotStore)({
|
|
340
|
+
status: "idle",
|
|
341
|
+
error: null,
|
|
342
|
+
writable: false,
|
|
343
|
+
providers: [],
|
|
344
|
+
modelsByProvider: {},
|
|
345
|
+
modelsEmptyReason: {},
|
|
346
|
+
namespaces: {},
|
|
347
|
+
advisorPresent: false,
|
|
348
|
+
// qc1 S-2: the degraded latch defaults false — a first mount / healthy
|
|
349
|
+
// card is never degraded, so the healthy card stays collapsed through its
|
|
350
|
+
// first load (the latch only flips on a settled degraded ready state).
|
|
351
|
+
degraded: false,
|
|
352
|
+
// KD-U2: dirty derives from the patch diff against the seed
|
|
353
|
+
// (recomputeDirty below — patchFor non-empty), recomputed on
|
|
354
|
+
// load/mutation/discard/apply; the store default is clean.
|
|
355
|
+
dirty: false,
|
|
356
|
+
draft: defaultDraft(),
|
|
357
|
+
applyState: { kind: "idle" }
|
|
358
|
+
}));
|
|
359
|
+
/** Latest load wins; an older response never overwrites a newer one. */
|
|
360
|
+
__publicField(this, "generation", 0);
|
|
361
|
+
/** Host-scoped model catalog; cached only on success (a transient failure stays uncached and is refetched). */
|
|
362
|
+
__publicField(this, "catalog");
|
|
363
|
+
/** In-flight host-scoped catalog fetch; one promise shared across providers. */
|
|
364
|
+
__publicField(this, "catalogPromise");
|
|
365
|
+
/**
|
|
366
|
+
* Bumped by every successful load(): an in-flight catalog fetch started
|
|
367
|
+
* before a bump is stale (the models/changed invalidation that triggered
|
|
368
|
+
* the reload happened mid-fetch) and caches nothing (qc1 W-1 / qc3 S-1).
|
|
369
|
+
*/
|
|
370
|
+
__publicField(this, "catalogGeneration", 0);
|
|
371
|
+
/** In-flight model resolutions (one per provider). */
|
|
372
|
+
__publicField(this, "inflightModels", /* @__PURE__ */ new Set());
|
|
373
|
+
/** The draft is seeded once (first load); refreshes never clobber edits. */
|
|
374
|
+
__publicField(this, "draftSeeded", false);
|
|
375
|
+
/** The resolved advisor config at the last get/apply (patch diff baseline). */
|
|
376
|
+
__publicField(this, "seed", defaultDraft());
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Refresh the whole page snapshot: the provider directory and the settings
|
|
380
|
+
* namespaces in parallel, then the advisor config over the gateway channel,
|
|
381
|
+
* then the provider join and the draft seed. A provider-directory failure
|
|
382
|
+
* keeps the last good rows and surfaces the error; an `advisor.get` failure
|
|
383
|
+
* is NOT a page error (KD-G5 — the card shows the config-channel notice,
|
|
384
|
+
* the provider directory stays usable). The draft is seeded only on the
|
|
385
|
+
* first load — pushed refreshes never discard in-progress edits (mirror the
|
|
386
|
+
* ProviderEditor draft lifetime).
|
|
387
|
+
* @returns nothing; the snapshot carries the outcome.
|
|
388
|
+
*/
|
|
389
|
+
async load() {
|
|
390
|
+
const generation = ++this.generation;
|
|
391
|
+
this.store.update((s) => {
|
|
392
|
+
s.status = "loading";
|
|
393
|
+
s.error = null;
|
|
394
|
+
});
|
|
395
|
+
let providers;
|
|
396
|
+
let writable;
|
|
397
|
+
let views;
|
|
398
|
+
try {
|
|
399
|
+
const [providersResponse, settingsResponse] = await Promise.all([
|
|
400
|
+
this.api.llm.providers({}),
|
|
401
|
+
this.api.settings.describe({})
|
|
402
|
+
]);
|
|
403
|
+
if (!providersResponse.result.ok) throw new Error(providersResponse.result.error.message);
|
|
404
|
+
if (!settingsResponse.result.ok) throw new Error(settingsResponse.result.error.message);
|
|
405
|
+
providers = providersResponse.result.value.providers;
|
|
406
|
+
writable = settingsResponse.result.value.writable;
|
|
407
|
+
views = settingsResponse.result.value.namespaces;
|
|
408
|
+
} catch (error) {
|
|
409
|
+
if (generation !== this.generation) return;
|
|
410
|
+
this.store.update((s) => {
|
|
411
|
+
s.status = "error";
|
|
412
|
+
s.error = error instanceof Error ? error.message : String(error);
|
|
413
|
+
});
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
if (generation !== this.generation) return;
|
|
417
|
+
let config;
|
|
418
|
+
try {
|
|
419
|
+
const getResult = await this.rpc.call("/api", "advisor/get", { args: {} });
|
|
420
|
+
if (getResult.ok) {
|
|
421
|
+
config = getResult.value.config;
|
|
422
|
+
}
|
|
423
|
+
} catch {
|
|
424
|
+
}
|
|
425
|
+
if (generation !== this.generation) return;
|
|
426
|
+
const namespaces = Object.fromEntries(
|
|
427
|
+
views.map((view) => [view.ns, view])
|
|
428
|
+
);
|
|
429
|
+
const options = [];
|
|
430
|
+
for (const entry of providers) {
|
|
431
|
+
const namespace = namespaces[entry.settingsNs];
|
|
432
|
+
const configured = namespace !== void 0 && (entry.settingsPath.length === 0 || (0, import_dsh_client_schema_form.getPath)(namespace.value, entry.settingsPath) !== void 0);
|
|
433
|
+
if (!configured) continue;
|
|
434
|
+
options.push({
|
|
435
|
+
provider: entry.provider,
|
|
436
|
+
displayName: entry.displayName,
|
|
437
|
+
settingsNs: entry.settingsNs,
|
|
438
|
+
settingsPath: entry.settingsPath,
|
|
439
|
+
configured
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
this.seed = draftOfConfig(config);
|
|
443
|
+
if (!this.draftSeeded && config !== void 0) {
|
|
444
|
+
this.store.update((s) => {
|
|
445
|
+
s.draft = this.seed;
|
|
446
|
+
});
|
|
447
|
+
this.draftSeeded = true;
|
|
448
|
+
}
|
|
449
|
+
this.catalogGeneration += 1;
|
|
450
|
+
this.catalog = void 0;
|
|
451
|
+
this.catalogPromise = void 0;
|
|
452
|
+
this.inflightModels.clear();
|
|
453
|
+
this.store.update((s) => {
|
|
454
|
+
s.status = "ready";
|
|
455
|
+
s.error = null;
|
|
456
|
+
s.writable = writable;
|
|
457
|
+
s.providers = options;
|
|
458
|
+
s.namespaces = namespaces;
|
|
459
|
+
s.advisorPresent = config !== void 0;
|
|
460
|
+
s.degraded = config === void 0;
|
|
461
|
+
s.modelsByProvider = {};
|
|
462
|
+
s.modelsEmptyReason = {};
|
|
463
|
+
if (config !== void 0) s.dirty = this.recomputeDirty(s.draft);
|
|
464
|
+
});
|
|
465
|
+
const selected = this.seed.provider;
|
|
466
|
+
if (selected !== void 0 && options.some((option) => option.provider === selected)) {
|
|
467
|
+
void this.ensureModels(selected);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Resolve the model options for one provider (KD-S2): profile-declared
|
|
472
|
+
* `models` win; otherwise the `llm.models` catalog group for that provider;
|
|
473
|
+
* neither → empty options + a reason. The host-scoped catalog is fetched at
|
|
474
|
+
* most once (concurrent first resolutions share one in-flight fetch) and
|
|
475
|
+
* cached only on success. No-op for unconfigured providers.
|
|
476
|
+
* @param provider - provider route id.
|
|
477
|
+
* @returns nothing; the snapshot carries the outcome.
|
|
478
|
+
*/
|
|
479
|
+
async ensureModels(provider) {
|
|
480
|
+
if (this.inflightModels.has(provider)) return;
|
|
481
|
+
const state = this.store.getSnapshot();
|
|
482
|
+
if (Object.hasOwn(state.modelsByProvider, provider) || Object.hasOwn(state.modelsEmptyReason, provider)) return;
|
|
483
|
+
const option = state.providers.find((candidate) => candidate.provider === provider);
|
|
484
|
+
if (option === void 0) return;
|
|
485
|
+
const namespace = state.namespaces[option.settingsNs];
|
|
486
|
+
const profile = namespace !== void 0 ? (0, import_dsh_client_schema_form.getPath)(namespace.value, option.settingsPath) : void 0;
|
|
487
|
+
const declared = profileModels(profile);
|
|
488
|
+
if (declared !== void 0) {
|
|
489
|
+
this.store.update((s) => {
|
|
490
|
+
if (declared.length > 0) {
|
|
491
|
+
s.modelsByProvider = { ...s.modelsByProvider, [provider]: declared };
|
|
492
|
+
} else {
|
|
493
|
+
s.modelsEmptyReason = { ...s.modelsEmptyReason, [provider]: "profile-empty" };
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
this.inflightModels.add(provider);
|
|
499
|
+
try {
|
|
500
|
+
let group;
|
|
501
|
+
for (; ; ) {
|
|
502
|
+
if (this.catalog === void 0) {
|
|
503
|
+
if (this.catalogPromise === void 0) {
|
|
504
|
+
const fetch = this.fetchCatalog();
|
|
505
|
+
const tracked = fetch.finally(() => {
|
|
506
|
+
if (this.catalogPromise === tracked) this.catalogPromise = void 0;
|
|
507
|
+
});
|
|
508
|
+
this.catalogPromise = tracked;
|
|
509
|
+
}
|
|
510
|
+
const generation = this.catalogGeneration;
|
|
511
|
+
await this.catalogPromise;
|
|
512
|
+
if (generation !== this.catalogGeneration) continue;
|
|
513
|
+
}
|
|
514
|
+
group = this.catalog?.find((candidate) => candidate.id === provider);
|
|
515
|
+
break;
|
|
516
|
+
}
|
|
517
|
+
const options = group?.models ?? [];
|
|
518
|
+
this.store.update((s) => {
|
|
519
|
+
if (options.length > 0) {
|
|
520
|
+
s.modelsByProvider = { ...s.modelsByProvider, [provider]: options };
|
|
521
|
+
} else {
|
|
522
|
+
const reason = group === void 0 ? "unavailable" : "catalog-empty";
|
|
523
|
+
s.modelsEmptyReason = { ...s.modelsEmptyReason, [provider]: reason };
|
|
524
|
+
}
|
|
525
|
+
});
|
|
526
|
+
} finally {
|
|
527
|
+
this.inflightModels.delete(provider);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
/**
|
|
531
|
+
* Fetch the host-scoped model catalog. A failure (wire rejection or throw)
|
|
532
|
+
* leaves the cache empty so the next resolution refetches — a transient
|
|
533
|
+
* failure is never sticky for the store lifetime. A fetch started before a
|
|
534
|
+
* load() bumped `catalogGeneration` is stale (the invalidation that caused
|
|
535
|
+
* the reload happened mid-fetch) and caches nothing.
|
|
536
|
+
*/
|
|
537
|
+
async fetchCatalog() {
|
|
538
|
+
const generation = this.catalogGeneration;
|
|
539
|
+
try {
|
|
540
|
+
const response = await this.api.llm.models({});
|
|
541
|
+
if (generation !== this.catalogGeneration) return;
|
|
542
|
+
this.catalog = response.result.ok ? response.result.value.groups : void 0;
|
|
543
|
+
} catch {
|
|
544
|
+
if (generation === this.catalogGeneration) this.catalog = void 0;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
/** Set the enabled switch (gate fields become required while on). */
|
|
548
|
+
setEnabled(enabled) {
|
|
549
|
+
this.setField("enabled", enabled);
|
|
550
|
+
}
|
|
551
|
+
/** Set or clear the provider ('' clears); switches invalidate the chosen model. */
|
|
552
|
+
setProvider(provider) {
|
|
553
|
+
const trimmed = provider.trim();
|
|
554
|
+
this.store.update((s) => {
|
|
555
|
+
const draft = s.draft;
|
|
556
|
+
const withoutProvider = trimmed.length === 0 ? (0, import_dsh_client_schema_form.deletePath)(draft, ["provider"]) : (0, import_dsh_client_schema_form.setPath)(draft, ["provider"], trimmed);
|
|
557
|
+
s.draft = (0, import_dsh_client_schema_form.deletePath)(withoutProvider, ["model"]);
|
|
558
|
+
s.applyState = { kind: "idle" };
|
|
559
|
+
s.dirty = this.recomputeDirty(s.draft);
|
|
560
|
+
});
|
|
561
|
+
if (trimmed.length > 0) void this.ensureModels(trimmed);
|
|
562
|
+
}
|
|
563
|
+
/** Set or clear the model ('' clears). */
|
|
564
|
+
setModel(model) {
|
|
565
|
+
const trimmed = model.trim();
|
|
566
|
+
if (trimmed.length === 0) this.clearField("model");
|
|
567
|
+
else this.setField("model", trimmed);
|
|
568
|
+
}
|
|
569
|
+
/** Set the system prompt override. */
|
|
570
|
+
setSystemPrompt(prompt) {
|
|
571
|
+
this.setField("systemPrompt", prompt);
|
|
572
|
+
}
|
|
573
|
+
/** Set the immune-turns cooldown (int ≥ 0; non-numeric input keeps the current value; undefined clears → omitted from patch). */
|
|
574
|
+
setImmuneTurns(value) {
|
|
575
|
+
if (value === void 0) {
|
|
576
|
+
this.clearField("immuneTurns");
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
this.setField("immuneTurns", this.clampInt(value, this.store.getSnapshot().draft.immuneTurns ?? 0));
|
|
580
|
+
}
|
|
581
|
+
/** Set the delta-message window (int ≥ 0; non-numeric input keeps the current value; undefined clears → omitted from patch). */
|
|
582
|
+
setMaxDeltaMessages(value) {
|
|
583
|
+
if (value === void 0) {
|
|
584
|
+
this.clearField("maxDeltaMessages");
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
this.setField("maxDeltaMessages", this.clampInt(value, this.store.getSnapshot().draft.maxDeltaMessages ?? 0));
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* Refuse in a read-only environment (store-side defense-in-depth for the
|
|
591
|
+
* W-1 invariant — a write must never be issued when the UI declares the
|
|
592
|
+
* settings service read-only), then validate the draft (KD-S4 gate) and
|
|
593
|
+
* write the changed keys as a config patch through the gateway channel
|
|
594
|
+
* (`/api/advisor/set`). Any failure (business rejection or transport)
|
|
595
|
+
* surfaces the message failure and keeps the form for retry — the gateway
|
|
596
|
+
* merge has no revision guard, so the old settings-conflict branch is
|
|
597
|
+
* replaced by plain error handling (KD-G3). Nothing to write → the apply
|
|
598
|
+
* reports saved without a call.
|
|
599
|
+
* @returns nothing; the apply state carries the outcome.
|
|
600
|
+
*/
|
|
601
|
+
async apply() {
|
|
602
|
+
const state = this.store.getSnapshot();
|
|
603
|
+
if (!state.writable) {
|
|
604
|
+
this.store.update((s) => {
|
|
605
|
+
s.applyState = {
|
|
606
|
+
kind: "error",
|
|
607
|
+
failure: { kind: "message", message: "advisor: settings service is read-only \u2014 configuration cannot be written" }
|
|
608
|
+
};
|
|
609
|
+
});
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
const gate = gateFailure(state.draft);
|
|
613
|
+
if (gate !== void 0) {
|
|
614
|
+
this.store.update((s) => {
|
|
615
|
+
s.applyState = { kind: "error", failure: { kind: "gate", reason: gate } };
|
|
616
|
+
});
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
const patch = this.patchFor(state.draft);
|
|
620
|
+
if (Object.keys(patch).length === 0) {
|
|
621
|
+
this.store.update((s) => {
|
|
622
|
+
s.applyState = { kind: "saved" };
|
|
623
|
+
s.dirty = this.recomputeDirty(s.draft);
|
|
624
|
+
});
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
this.store.update((s) => {
|
|
628
|
+
s.applyState = { kind: "saving" };
|
|
629
|
+
});
|
|
630
|
+
try {
|
|
631
|
+
const result = await this.rpc.call("/api", "advisor/set", { args: { patch } });
|
|
632
|
+
if (!result.ok) {
|
|
633
|
+
this.store.update((s) => {
|
|
634
|
+
s.applyState = { kind: "error", failure: { kind: "message", message: result.error.message } };
|
|
635
|
+
});
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
this.seed = draftOfConfig(result.value.config);
|
|
639
|
+
this.store.update((s) => {
|
|
640
|
+
s.applyState = { kind: "saved" };
|
|
641
|
+
s.dirty = this.recomputeDirty(s.draft);
|
|
642
|
+
});
|
|
643
|
+
await this.load();
|
|
644
|
+
} catch (error) {
|
|
645
|
+
this.store.update((s) => {
|
|
646
|
+
s.applyState = {
|
|
647
|
+
kind: "error",
|
|
648
|
+
failure: { kind: "message", message: error instanceof Error ? error.message : String(error) }
|
|
649
|
+
};
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Drop the current draft edits and rewind the form to the last-known host
|
|
655
|
+
* config (the seed from the last get/apply). The card chrome mirrors the
|
|
656
|
+
* upstream plugin cards' Save/Discard pair (KD-4): unlike the upstream
|
|
657
|
+
* staged CardForm, this store's edits mutate the draft in place, so discard
|
|
658
|
+
* rewinds the draft instead of dropping a staging layer. Pure client-side
|
|
659
|
+
* revert — no gateway write (the host stays the single fact source; a
|
|
660
|
+
* follow-up apply diffs against the restored seed and sends nothing).
|
|
661
|
+
*
|
|
662
|
+
* Invariant (qc3 N-2): a mid-apply discard is UNGUARDED at the store level
|
|
663
|
+
* BY DESIGN — the UI disables Discard while a save is in flight
|
|
664
|
+
* (`disabled={busy}` on the card, `busy = !writable || saving`), so the
|
|
665
|
+
* interleaving cannot occur from the UI and no behavior guard is added
|
|
666
|
+
* here. A programmatic mid-apply discard would only rewind the draft before
|
|
667
|
+
* the in-flight apply adopts the returned config as the new seed — the
|
|
668
|
+
* host write is unaffected.
|
|
669
|
+
*/
|
|
670
|
+
discard() {
|
|
671
|
+
this.store.update((s) => {
|
|
672
|
+
s.draft = this.seed;
|
|
673
|
+
s.applyState = { kind: "idle" };
|
|
674
|
+
s.dirty = this.recomputeDirty(s.draft);
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
/**
|
|
678
|
+
* The minimal config patch making the effective config match the draft —
|
|
679
|
+
* only keys whose value differs from the last-read seed are sent, so a
|
|
680
|
+
* stale read never clobbers concurrent changes to untouched keys and base
|
|
681
|
+
* (plugin-row) values stay in place. A cleared provider/model the seed pins
|
|
682
|
+
* becomes an explicit `''` override (the gateway merge cannot express an
|
|
683
|
+
* unset, and the resolver treats '' as absent); a cleared number field is
|
|
684
|
+
* omitted (the stored value stays unchanged).
|
|
685
|
+
*/
|
|
686
|
+
patchFor(draft) {
|
|
687
|
+
const patch = {};
|
|
688
|
+
const always = ["enabled", "systemPrompt", "immuneTurns", "maxDeltaMessages"];
|
|
689
|
+
for (const key of always) {
|
|
690
|
+
const next = draft[key];
|
|
691
|
+
if (next === void 0) continue;
|
|
692
|
+
if (JSON.stringify(this.seed[key]) !== JSON.stringify(next)) {
|
|
693
|
+
patch[key] = next;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
for (const key of ["provider", "model"]) {
|
|
697
|
+
const next = draft[key];
|
|
698
|
+
if (next !== void 0) {
|
|
699
|
+
if (JSON.stringify(this.seed[key]) !== JSON.stringify(next)) {
|
|
700
|
+
patch[key] = next;
|
|
701
|
+
}
|
|
702
|
+
} else if (this.seed[key] !== void 0) {
|
|
703
|
+
patch[key] = "";
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
return patch;
|
|
707
|
+
}
|
|
708
|
+
/**
|
|
709
|
+
* The dirty derivation (KD-U2, plan dsh-advisor-plugin-config-card-ux task
|
|
710
|
+
* 2): the draft holds edits a save would write iff the minimal patch
|
|
711
|
+
* against the current seed is non-empty. Takes the DRAFT being mutated and
|
|
712
|
+
* reads `this.seed` — it must be called INSIDE the same `store.update`
|
|
713
|
+
* callback that mutates the draft/seed, never via a snapshot read + second
|
|
714
|
+
* write (the frozen-snapshot write would be dropped by the snapshot store).
|
|
715
|
+
*/
|
|
716
|
+
recomputeDirty(draft) {
|
|
717
|
+
return Object.keys(this.patchFor(draft)).length > 0;
|
|
718
|
+
}
|
|
719
|
+
/** Edit one always-present draft field via the schema-form path writer. */
|
|
720
|
+
setField(key, value) {
|
|
721
|
+
this.store.update((s) => {
|
|
722
|
+
s.draft = (0, import_dsh_client_schema_form.setPath)(s.draft, [key], value);
|
|
723
|
+
s.applyState = { kind: "idle" };
|
|
724
|
+
s.dirty = this.recomputeDirty(s.draft);
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
/** Remove one optional draft field via the schema-form path writer. */
|
|
728
|
+
clearField(key) {
|
|
729
|
+
this.store.update((s) => {
|
|
730
|
+
s.draft = (0, import_dsh_client_schema_form.deletePath)(s.draft, [key]);
|
|
731
|
+
s.applyState = { kind: "idle" };
|
|
732
|
+
s.dirty = this.recomputeDirty(s.draft);
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
clampInt(value, fallback) {
|
|
736
|
+
return Number.isFinite(value) ? Math.max(0, Math.floor(value)) : fallback;
|
|
737
|
+
}
|
|
738
|
+
};
|
|
739
|
+
function refreshIfLoaded(controller) {
|
|
740
|
+
if (controller.store.getSnapshot().status === "idle") return;
|
|
741
|
+
void controller.load();
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// src/client/locales.ts
|
|
745
|
+
var en = {
|
|
746
|
+
title: "Advisor",
|
|
747
|
+
intro: "Configure the advisor reviewer model that observes the primary transcript and injects severity-ranked advice.",
|
|
748
|
+
collapse: "Hide settings",
|
|
749
|
+
expand: "Show settings",
|
|
750
|
+
unsaved: "Unsaved",
|
|
751
|
+
loadFailed: "Loading advisor settings failed",
|
|
752
|
+
retry: "Retry",
|
|
753
|
+
enabled: "Enable the advisor",
|
|
754
|
+
provider: "Provider",
|
|
755
|
+
providerPlaceholder: "Select a provider",
|
|
756
|
+
providerRequired: "Provider is required when the advisor is enabled.",
|
|
757
|
+
model: "Model",
|
|
758
|
+
modelPlaceholder: "Select a model",
|
|
759
|
+
modelRequired: "Model is required when the advisor is enabled.",
|
|
760
|
+
noProviders: "No configured providers. Configure one on the Models page first.",
|
|
761
|
+
noModels: "This provider has no available models. Configure models for it on the Models page.",
|
|
762
|
+
staleProvider: "The stored provider is no longer configured. Reselect one or keep the stored value.",
|
|
763
|
+
staleModel: "The stored model is no longer available for this provider. Reselect one or keep the stored value.",
|
|
764
|
+
systemPrompt: "System prompt",
|
|
765
|
+
systemPromptPlaceholder: "Leave empty to use the default system prompt",
|
|
766
|
+
immuneTurns: "Immune turns",
|
|
767
|
+
maxDeltaMessages: "Max delta messages",
|
|
768
|
+
save: "Save",
|
|
769
|
+
saving: "Saving\u2026",
|
|
770
|
+
discard: "Discard",
|
|
771
|
+
saved: "Advisor settings saved. New sessions pick them up immediately.",
|
|
772
|
+
readOnly: "Settings are read-only in this environment.",
|
|
773
|
+
namespaceUnavailable: "The advisor configuration channel is not available yet \u2014 the settings gateway is not ready on this host. Configure the advisor via the plugin config row \u2014 `- id: advisor` whose `config:` map sets `enabled`/`provider`/`model` (e.g. `config: { enabled: true, provider: \u2026, model: \u2026 }`) in `$DSH_HOME/profiles/<name>/cordis.patch.yml`. The advisor plugin runs fully from its plugin config row; the web settings form becomes available again on the next load once the gateway is reachable. Note: `/advisor` only toggles the advisor per session; it cannot supply provider/model."
|
|
774
|
+
};
|
|
775
|
+
var zh = {
|
|
776
|
+
title: "\u987E\u95EE",
|
|
777
|
+
intro: "\u914D\u7F6E\u987E\u95EE\u5BA1\u9605\u6A21\u578B\uFF1A\u5B83\u89C2\u5BDF\u4E3B\u4F1A\u8BDD\u8BB0\u5F55\uFF0C\u5E76\u6CE8\u5165\u6309\u4E25\u91CD\u7A0B\u5EA6\u6392\u5E8F\u7684\u5EFA\u8BAE\u3002",
|
|
778
|
+
collapse: "\u6536\u8D77\u8BBE\u7F6E",
|
|
779
|
+
expand: "\u5C55\u5F00\u8BBE\u7F6E",
|
|
780
|
+
unsaved: "\u672A\u4FDD\u5B58",
|
|
781
|
+
loadFailed: "\u52A0\u8F7D\u987E\u95EE\u8BBE\u7F6E\u5931\u8D25",
|
|
782
|
+
retry: "\u91CD\u8BD5",
|
|
783
|
+
enabled: "\u542F\u7528\u987E\u95EE",
|
|
784
|
+
provider: "\u63D0\u4F9B\u5546",
|
|
785
|
+
providerPlaceholder: "\u9009\u62E9\u63D0\u4F9B\u5546",
|
|
786
|
+
providerRequired: "\u542F\u7528\u987E\u95EE\u65F6\u5FC5\u987B\u9009\u62E9\u63D0\u4F9B\u5546\u3002",
|
|
787
|
+
model: "\u6A21\u578B",
|
|
788
|
+
modelPlaceholder: "\u9009\u62E9\u6A21\u578B",
|
|
789
|
+
modelRequired: "\u542F\u7528\u987E\u95EE\u65F6\u5FC5\u987B\u9009\u62E9\u6A21\u578B\u3002",
|
|
790
|
+
noProviders: "\u6CA1\u6709\u5DF2\u914D\u7F6E\u7684\u63D0\u4F9B\u5546\u3002\u8BF7\u5148\u5728 Models \u9875\u9762\u914D\u7F6E\u3002",
|
|
791
|
+
noModels: "\u8BE5\u63D0\u4F9B\u5546\u6CA1\u6709\u53EF\u7528\u6A21\u578B\u3002\u8BF7\u5230 Models \u9875\u9762\u4E3A\u5176\u914D\u7F6E\u6A21\u578B\u3002",
|
|
792
|
+
staleProvider: "\u5B58\u50A8\u7684\u63D0\u4F9B\u5546\u5DF2\u4E0D\u5728\u914D\u7F6E\u4E2D\u3002\u8BF7\u91CD\u65B0\u9009\u62E9\uFF0C\u6216\u4FDD\u7559\u5B58\u50A8\u7684\u503C\u3002",
|
|
793
|
+
staleModel: "\u5B58\u50A8\u7684\u6A21\u578B\u5728\u8BE5\u63D0\u4F9B\u5546\u4E0B\u5DF2\u4E0D\u53EF\u7528\u3002\u8BF7\u91CD\u65B0\u9009\u62E9\uFF0C\u6216\u4FDD\u7559\u5B58\u50A8\u7684\u503C\u3002",
|
|
794
|
+
systemPrompt: "\u7CFB\u7EDF\u63D0\u793A\u8BCD",
|
|
795
|
+
systemPromptPlaceholder: "\u7559\u7A7A\u5219\u4F7F\u7528\u9ED8\u8BA4\u7CFB\u7EDF\u63D0\u793A\u8BCD",
|
|
796
|
+
immuneTurns: "\u514D\u75AB\u8F6E\u6B21",
|
|
797
|
+
maxDeltaMessages: "\u6700\u5927\u589E\u91CF\u6D88\u606F\u6570",
|
|
798
|
+
save: "\u4FDD\u5B58",
|
|
799
|
+
saving: "\u4FDD\u5B58\u4E2D\u2026",
|
|
800
|
+
discard: "\u653E\u5F03\u4FEE\u6539",
|
|
801
|
+
saved: "\u987E\u95EE\u8BBE\u7F6E\u5DF2\u4FDD\u5B58\u3002\u65B0\u4F1A\u8BDD\u7ACB\u5373\u751F\u6548\u3002",
|
|
802
|
+
readOnly: "\u5F53\u524D\u73AF\u5883\u4E2D\u7684\u8BBE\u7F6E\u4E3A\u53EA\u8BFB\u3002",
|
|
803
|
+
namespaceUnavailable: "\u987E\u95EE\u914D\u7F6E\u901A\u9053\u6682\u4E0D\u53EF\u7528\u2014\u2014\u672C\u5BBF\u4E3B\u4E0A\u7684\u8BBE\u7F6E\u7F51\u5173\u5C1A\u672A\u5C31\u7EEA\u3002\u8BF7\u901A\u8FC7\u63D2\u4EF6\u914D\u7F6E\u884C\u914D\u7F6E\u987E\u95EE\u2014\u2014\u5728 `$DSH_HOME/profiles/<name>/cordis.patch.yml` \u4E2D\u5199 `- id: advisor`\uFF0C\u5176 `config:` \u6620\u5C04\u4E2D\u8BBE\u7F6E `enabled`/`provider`/`model`\uFF08\u4F8B\u5982 `config: { enabled: true, provider: \u2026, model: \u2026 }`\uFF09\u3002\u987E\u95EE\u63D2\u4EF6\u5B8C\u5168\u901A\u8FC7\u63D2\u4EF6\u914D\u7F6E\u884C\u8FD0\u884C\uFF1B\u7F51\u5173\u53EF\u7528\u540E\uFF0Cweb \u8BBE\u7F6E\u8868\u5355\u5C06\u5728\u4E0B\u6B21\u52A0\u8F7D\u65F6\u91CD\u65B0\u663E\u793A\u3002\u6CE8\u610F\uFF1A`/advisor` \u4EC5\u5207\u6362\u5F53\u524D\u4F1A\u8BDD\u7684\u987E\u95EE\u5F00\u5173\uFF0C\u65E0\u6CD5\u63D0\u4F9B provider/model\u3002"
|
|
804
|
+
};
|
|
805
|
+
|
|
806
|
+
// src/client/index.ts
|
|
807
|
+
var NS = "settings.advisor";
|
|
808
|
+
var inject = ["slots", "locale", "connection"];
|
|
809
|
+
function apply(ctx) {
|
|
810
|
+
ctx.effect(() => ctx.locale.register(NS, { zh, en }), "advisor: copy dictionaries");
|
|
811
|
+
const connection = ctx.get("connection");
|
|
812
|
+
const controller = new AdvisorSettingsStore(connection.api, connection.rpc);
|
|
813
|
+
const useSnapshot = (0, import_dsh_client_web_react.bindSnapshotSelector)(controller.store);
|
|
814
|
+
ctx.effect(() => {
|
|
815
|
+
let pending = false;
|
|
816
|
+
const refresh = () => {
|
|
817
|
+
if (pending) return;
|
|
818
|
+
pending = true;
|
|
819
|
+
queueMicrotask(() => {
|
|
820
|
+
pending = false;
|
|
821
|
+
refreshIfLoaded(controller);
|
|
822
|
+
});
|
|
823
|
+
};
|
|
824
|
+
const disposers = [ctx.on("connection/reset", refresh)];
|
|
825
|
+
return () => {
|
|
826
|
+
for (const dispose of disposers) dispose();
|
|
827
|
+
};
|
|
828
|
+
}, "advisor: pushed invalidations");
|
|
829
|
+
ctx.slots.inject("settings.plugin.item", function* () {
|
|
830
|
+
yield ctx.slots.register({
|
|
831
|
+
name: "settings.plugin.item",
|
|
832
|
+
id: "advisor",
|
|
833
|
+
order: 30,
|
|
834
|
+
// bash 0 / agent-loop 10 / web-search 20 / advisor 30
|
|
835
|
+
locale: NS,
|
|
836
|
+
inject: () => ({ controller, useSnapshot })
|
|
837
|
+
}, AdvisorCard);
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
return module.exports; } });
|