@joekytc/dsh-swarm 0.5.0 → 0.5.1
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/client/ConfigSection.tsx +127 -18
- package/client/ConfigSelect.tsx +1 -0
- package/client/config-store.ts +6 -3
- package/client/config.css +89 -1
- package/cordis.patch.yml +10 -52
- package/lib/client.js +193 -35
- package/lib/config.d.ts +7 -0
- package/lib/config.js +7 -1
- package/lib/dispatcher/agent-runner.d.ts +4 -0
- package/lib/dispatcher/agent-runner.js +15 -4
- package/lib/dispatcher/model-candidates.d.ts +9 -3
- package/lib/dispatcher/model-candidates.js +27 -6
- package/lib/dispatcher/model-chain.d.ts +19 -0
- package/lib/dispatcher/model-chain.js +110 -0
- package/lib/dispatcher/session-events.d.ts +2 -0
- package/lib/dispatcher/session-events.js +13 -0
- package/lib/dispatcher/v-orchestrator.js +5 -2
- package/lib/domain/config-override.d.ts +20 -0
- package/lib/domain/config-override.js +46 -2
- package/lib/index.js +7 -0
- package/lib/routes/kanban-http.js +24 -3
- package/lib/services/config-provider.js +5 -0
- package/package.json +2 -2
package/lib/client.js
CHANGED
|
@@ -1643,7 +1643,7 @@ window.__ModuleLoader__.load({
|
|
|
1643
1643
|
// client/config-store.ts
|
|
1644
1644
|
var SWARM_CONFIG_NS = "swarm-config";
|
|
1645
1645
|
function createConfigStore(fetchImpl) {
|
|
1646
|
-
let state = { effective: { wikiVault: { baseUrl: "", pagePrefix: "" }, roles: { models: {} }, reviewEngine: { mode: "delegate", managed: { provider: "", model: "" } }, imDelivery: { fallbackBotId: "" } }, sources: {}, catalog: { providers: [], models: {} }, ocrStatus: null, install: { phase: "idle", log: "" }, saving: false, error: null };
|
|
1646
|
+
let state = { effective: { wikiVault: { baseUrl: "", pagePrefix: "" }, roles: { models: {} }, chainFallback: { provider: "", model: "", reasoningEffort: "high" }, reviewEngine: { mode: "delegate", managed: { provider: "", model: "" } }, imDelivery: { fallbackBotId: "" } }, sources: {}, catalog: { providers: [], models: {} }, ocrStatus: null, install: { phase: "idle", log: "" }, saving: false, error: null };
|
|
1647
1647
|
const listeners = /* @__PURE__ */ new Set();
|
|
1648
1648
|
const setState = (patch) => {
|
|
1649
1649
|
state = { ...state, ...patch };
|
|
@@ -1763,6 +1763,7 @@ window.__ModuleLoader__.load({
|
|
|
1763
1763
|
"aria-haspopup": "listbox",
|
|
1764
1764
|
"aria-expanded": open,
|
|
1765
1765
|
disabled,
|
|
1766
|
+
title: current?.label ?? placeholder,
|
|
1766
1767
|
onClick: () => setOpen((v) => !v),
|
|
1767
1768
|
children: [
|
|
1768
1769
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: current ? void 0 : "dsh-kb-config__select-placeholder", children: current?.label ?? placeholder }),
|
|
@@ -1815,6 +1816,15 @@ window.__ModuleLoader__.load({
|
|
|
1815
1816
|
// client/ConfigSection.tsx
|
|
1816
1817
|
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
1817
1818
|
var ROLES = ["v", "p", "w", "d", "pt", "dt"];
|
|
1819
|
+
var MAX_FALLBACKS = 2;
|
|
1820
|
+
var ROLE_NAMES = {
|
|
1821
|
+
v: "\u770B\u677F\u7F16\u6392\u5B98\uFF08V\uFF09",
|
|
1822
|
+
p: "\u89C4\u5212\u5B98\uFF08P\uFF09",
|
|
1823
|
+
w: "\u77E5\u8BC6\u5B98\uFF08W\uFF09",
|
|
1824
|
+
d: "\u5168\u6808\u5F00\u53D1\u5B98\uFF08D\uFF09",
|
|
1825
|
+
pt: "\u8BA1\u5212\u5BA1\u67E5\u5B98\uFF08PT\uFF09",
|
|
1826
|
+
dt: "\u4EA4\u4ED8\u8BC4\u5BA1\u5B98\uFF08DT\uFF09"
|
|
1827
|
+
};
|
|
1818
1828
|
function ocrBanner(ocr, installing) {
|
|
1819
1829
|
if (installing) return null;
|
|
1820
1830
|
if (ocr === null) return "ocr \u72B6\u6001\u672A\u77E5\u2014\u2014\u59D4\u6258/\u6258\u7BA1\u8BC4\u5BA1\u53EF\u80FD\u4E0D\u53EF\u7528";
|
|
@@ -1828,6 +1838,7 @@ window.__ModuleLoader__.load({
|
|
|
1828
1838
|
const state = (0, import_react9.useSyncExternalStore)(store.subscribe, store.get);
|
|
1829
1839
|
const [draft, setDraft] = (0, import_react9.useState)(null);
|
|
1830
1840
|
const [wireResult, setWireResult] = (0, import_react9.useState)(null);
|
|
1841
|
+
const [openRole, setOpenRole] = (0, import_react9.useState)(void 0);
|
|
1831
1842
|
(0, import_react9.useEffect)(() => {
|
|
1832
1843
|
void store.load().then(() => setDraft(store.get().effective));
|
|
1833
1844
|
void store.loadOcrStatus();
|
|
@@ -1847,9 +1858,33 @@ window.__ModuleLoader__.load({
|
|
|
1847
1858
|
setDraft(next);
|
|
1848
1859
|
void store.save(next);
|
|
1849
1860
|
};
|
|
1861
|
+
const roleEntry = (r) => draft.roles.models[r] ?? { provider: "", model: "", reasoningEffort: "", fallbacks: [] };
|
|
1850
1862
|
const setModel = (r, patch) => {
|
|
1851
|
-
|
|
1852
|
-
|
|
1863
|
+
commit({ ...draft, roles: { models: { ...draft.roles.models, [r]: { ...roleEntry(r), ...patch } } } });
|
|
1864
|
+
};
|
|
1865
|
+
const setFallback = (r, idx, patch) => {
|
|
1866
|
+
const fbs = [...roleEntry(r).fallbacks];
|
|
1867
|
+
const cur = fbs[idx] ?? { provider: "", model: "", reasoningEffort: "" };
|
|
1868
|
+
fbs[idx] = { ...cur, ...patch };
|
|
1869
|
+
setModel(r, { fallbacks: fbs });
|
|
1870
|
+
};
|
|
1871
|
+
const addFallback = (r) => {
|
|
1872
|
+
const fbs = roleEntry(r).fallbacks;
|
|
1873
|
+
if (fbs.length >= MAX_FALLBACKS) return;
|
|
1874
|
+
setModel(r, { fallbacks: [...fbs, { provider: "", model: "", reasoningEffort: "" }] });
|
|
1875
|
+
};
|
|
1876
|
+
const removeFallback = (r, idx) => {
|
|
1877
|
+
setModel(r, { fallbacks: roleEntry(r).fallbacks.filter((_, i) => i !== idx) });
|
|
1878
|
+
};
|
|
1879
|
+
const copyChainToAll = (r) => {
|
|
1880
|
+
const src = roleEntry(r);
|
|
1881
|
+
if (!(src.provider && src.model)) return;
|
|
1882
|
+
const models = { ...draft.roles.models };
|
|
1883
|
+
for (const o of ROLES) {
|
|
1884
|
+
if (o === r) continue;
|
|
1885
|
+
models[o] = { provider: src.provider, model: src.model, reasoningEffort: src.reasoningEffort, fallbacks: src.fallbacks.map((f) => ({ ...f })) };
|
|
1886
|
+
}
|
|
1887
|
+
commit({ ...draft, roles: { models } });
|
|
1853
1888
|
};
|
|
1854
1889
|
const commitReviewEngine = (mode, managed) => {
|
|
1855
1890
|
commit({ ...draft, reviewEngine: { mode, managed } });
|
|
@@ -1857,11 +1892,26 @@ window.__ModuleLoader__.load({
|
|
|
1857
1892
|
const onBlur = () => {
|
|
1858
1893
|
void store.save(draft);
|
|
1859
1894
|
};
|
|
1895
|
+
const chainField = (label, value, placeholder, options, onPick) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { className: "dsh-kb-config__field", children: [
|
|
1896
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "dsh-kb-config__field-label", children: label }),
|
|
1897
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ConfigSelect, { value, placeholder, options, onChange: onPick })
|
|
1898
|
+
] });
|
|
1899
|
+
const chainSummary = (m) => {
|
|
1900
|
+
if (!(m.provider && m.model)) return "\u672A\u914D\u7F6E";
|
|
1901
|
+
const n = (m.fallbacks ?? []).filter((f) => f.provider && f.model).length;
|
|
1902
|
+
return `${m.provider} / ${m.model}` + (n ? ` \xB7 ${n} \u964D\u7EA7` : "");
|
|
1903
|
+
};
|
|
1904
|
+
const providerOptions = state.catalog.providers.map((p) => ({ value: p.id, label: p.name }));
|
|
1905
|
+
const expandedRole = openRole === void 0 ? ROLES.find((x) => {
|
|
1906
|
+
const e = draft.roles.models[x];
|
|
1907
|
+
return Boolean(e?.provider && e?.model);
|
|
1908
|
+
}) ?? null : openRole;
|
|
1860
1909
|
const ocr = state.ocrStatus;
|
|
1861
1910
|
const banner = ocrBanner(ocr, installing);
|
|
1862
1911
|
const ocrBlocked = installing || ocr === null || !ocr.installed;
|
|
1863
1912
|
const re = draft.reviewEngine;
|
|
1864
1913
|
const managedFieldsEnabled = !ocrBlocked && re.mode === "managed";
|
|
1914
|
+
const cf = draft.chainFallback ?? { provider: "", model: "", reasoningEffort: "high" };
|
|
1865
1915
|
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "dsh-kb-config", children: [
|
|
1866
1916
|
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("section", { className: "dsh-kb-config__card", children: [
|
|
1867
1917
|
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("h3", { children: "\u77E5\u8BC6\u5E93" }),
|
|
@@ -1889,47 +1939,155 @@ window.__ModuleLoader__.load({
|
|
|
1889
1939
|
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("section", { className: "dsh-kb-config__card", children: [
|
|
1890
1940
|
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("h3", { children: "\u6A21\u578B\u94FE" }),
|
|
1891
1941
|
ROLES.map((r) => {
|
|
1892
|
-
const m =
|
|
1942
|
+
const m = roleEntry(r);
|
|
1943
|
+
const fbs = m.fallbacks ?? [];
|
|
1944
|
+
const configured = Boolean(m.provider && m.model);
|
|
1945
|
+
const open = expandedRole === r;
|
|
1893
1946
|
const providerModels = state.catalog.models[m.provider] ?? [];
|
|
1894
1947
|
const efforts = providerModels.find((x) => x.id === m.model)?.efforts ?? [];
|
|
1895
|
-
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "dsh-kb-config__role", children: [
|
|
1896
|
-
/* @__PURE__ */ (0, import_jsx_runtime9.
|
|
1897
|
-
|
|
1898
|
-
ConfigSelect,
|
|
1948
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "dsh-kb-config__role-block", children: [
|
|
1949
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
|
|
1950
|
+
"button",
|
|
1899
1951
|
{
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1952
|
+
type: "button",
|
|
1953
|
+
className: "dsh-kb-config__role-head",
|
|
1954
|
+
"aria-expanded": open,
|
|
1955
|
+
onClick: () => setOpenRole(open ? null : r),
|
|
1956
|
+
children: [
|
|
1957
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "dsh-kb-config__chevron" + (open ? " dsh-kb-config__chevron--open" : ""), "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("svg", { width: "14", height: "14", viewBox: "0 0 16 16", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
1958
|
+
"path",
|
|
1959
|
+
{
|
|
1960
|
+
d: "M6 3.5 10.5 8 6 12.5",
|
|
1961
|
+
fill: "none",
|
|
1962
|
+
stroke: "currentColor",
|
|
1963
|
+
strokeWidth: "1.5",
|
|
1964
|
+
strokeLinecap: "round",
|
|
1965
|
+
strokeLinejoin: "round"
|
|
1966
|
+
}
|
|
1967
|
+
) }) }),
|
|
1968
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("strong", { children: ROLE_NAMES[r] }),
|
|
1969
|
+
!open && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "dsh-kb-config__role-summary", children: chainSummary(m) })
|
|
1970
|
+
]
|
|
1906
1971
|
}
|
|
1907
1972
|
),
|
|
1908
|
-
/* @__PURE__ */ (0, import_jsx_runtime9.
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1973
|
+
open && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "dsh-kb-config__role-body", children: [
|
|
1974
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "dsh-kb-config__group-label", children: "\u4E3B\u6A21\u578B" }),
|
|
1975
|
+
chainField(
|
|
1976
|
+
"\u4F9B\u5E94\u5546",
|
|
1977
|
+
m.provider,
|
|
1978
|
+
"\u9009\u62E9 provider",
|
|
1979
|
+
providerOptions,
|
|
1980
|
+
(v) => {
|
|
1981
|
+
if (v !== m.provider) setModel(r, { provider: v, model: "", reasoningEffort: "" });
|
|
1982
|
+
}
|
|
1983
|
+
),
|
|
1984
|
+
chainField(
|
|
1985
|
+
"\u6A21\u578B",
|
|
1986
|
+
m.model,
|
|
1987
|
+
"\u9009\u62E9\u6A21\u578B",
|
|
1988
|
+
providerModels.map((x) => ({ value: x.id, label: x.name })),
|
|
1989
|
+
(v) => {
|
|
1915
1990
|
if (v !== m.model) setModel(r, { model: v, reasoningEffort: "" });
|
|
1916
1991
|
}
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
value:
|
|
1923
|
-
|
|
1924
|
-
options: efforts.map((x) => ({ value: x.id, label: x.name })),
|
|
1925
|
-
onChange: (v) => {
|
|
1992
|
+
),
|
|
1993
|
+
chainField(
|
|
1994
|
+
"\u601D\u8003\u7B49\u7EA7",
|
|
1995
|
+
m.reasoningEffort,
|
|
1996
|
+
"\u8DDF\u968F\u9ED8\u8BA4",
|
|
1997
|
+
efforts.map((x) => ({ value: x.id, label: x.name })),
|
|
1998
|
+
(v) => {
|
|
1926
1999
|
if (v !== m.reasoningEffort) setModel(r, { reasoningEffort: v });
|
|
1927
2000
|
}
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
2001
|
+
),
|
|
2002
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "dsh-kb-config__group-label", children: "\u964D\u7EA7\uFF08\u6700\u591A 2 \u4E2A\uFF0C\u987A\u5E8F = \u5207\u6362\u4F18\u5148\u5E8F\uFF09" }),
|
|
2003
|
+
fbs.map((f, i) => {
|
|
2004
|
+
const fProviderModels = state.catalog.models[f.provider] ?? [];
|
|
2005
|
+
const fEfforts = fProviderModels.find((x) => x.id === f.model)?.efforts ?? [];
|
|
2006
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "dsh-kb-config__fb-group", children: [
|
|
2007
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "dsh-kb-config__fb-head", children: [
|
|
2008
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "dsh-kb-config__fb-title", children: [
|
|
2009
|
+
"\u964D\u7EA7 ",
|
|
2010
|
+
i + 1
|
|
2011
|
+
] }),
|
|
2012
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
2013
|
+
"button",
|
|
2014
|
+
{
|
|
2015
|
+
type: "button",
|
|
2016
|
+
className: "dsh-kb-config__row-btn",
|
|
2017
|
+
title: "\u5220\u9664\u8BE5\u964D\u7EA7\u5019\u9009",
|
|
2018
|
+
onClick: () => removeFallback(r, i),
|
|
2019
|
+
children: "\u5220\u9664"
|
|
2020
|
+
}
|
|
2021
|
+
)
|
|
2022
|
+
] }),
|
|
2023
|
+
chainField(
|
|
2024
|
+
"\u4F9B\u5E94\u5546",
|
|
2025
|
+
f.provider,
|
|
2026
|
+
"\u9009\u62E9 provider",
|
|
2027
|
+
providerOptions,
|
|
2028
|
+
(v) => {
|
|
2029
|
+
if (v !== f.provider) setFallback(r, i, { provider: v, model: "", reasoningEffort: "" });
|
|
2030
|
+
}
|
|
2031
|
+
),
|
|
2032
|
+
chainField(
|
|
2033
|
+
"\u6A21\u578B",
|
|
2034
|
+
f.model,
|
|
2035
|
+
"\u9009\u62E9\u6A21\u578B",
|
|
2036
|
+
fProviderModels.map((x) => ({ value: x.id, label: x.name })),
|
|
2037
|
+
(v) => {
|
|
2038
|
+
if (v !== f.model) setFallback(r, i, { model: v, reasoningEffort: "" });
|
|
2039
|
+
}
|
|
2040
|
+
),
|
|
2041
|
+
chainField(
|
|
2042
|
+
"\u601D\u8003\u7B49\u7EA7",
|
|
2043
|
+
f.reasoningEffort,
|
|
2044
|
+
"\u8DDF\u968F\u9ED8\u8BA4",
|
|
2045
|
+
fEfforts.map((x) => ({ value: x.id, label: x.name })),
|
|
2046
|
+
(v) => {
|
|
2047
|
+
if (v !== f.reasoningEffort) setFallback(r, i, { reasoningEffort: v });
|
|
2048
|
+
}
|
|
2049
|
+
)
|
|
2050
|
+
] }, i);
|
|
2051
|
+
}),
|
|
2052
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "dsh-kb-config__role-actions", children: [
|
|
2053
|
+
fbs.length < MAX_FALLBACKS && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
2054
|
+
"button",
|
|
2055
|
+
{
|
|
2056
|
+
type: "button",
|
|
2057
|
+
className: "dsh-kb-config__row-btn",
|
|
2058
|
+
disabled: !configured,
|
|
2059
|
+
title: configured ? "\u8FFD\u52A0\u4E00\u4E2A\u964D\u7EA7\u5019\u9009" : "\u5148\u9009\u597D\u4E3B\u6A21\u578B\uFF08provider+\u6A21\u578B\uFF09\u518D\u8FFD\u52A0\u964D\u7EA7",
|
|
2060
|
+
onClick: () => addFallback(r),
|
|
2061
|
+
children: "+ \u964D\u7EA7"
|
|
2062
|
+
}
|
|
2063
|
+
),
|
|
2064
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
2065
|
+
"button",
|
|
2066
|
+
{
|
|
2067
|
+
type: "button",
|
|
2068
|
+
className: "dsh-kb-config__row-btn",
|
|
2069
|
+
disabled: !configured,
|
|
2070
|
+
title: "\u628A\u8BE5\u89D2\u8272\u7684\u4E3B\u6A21\u578B+\u964D\u7EA7\u94FE\u6574\u4F53\u590D\u5236\u5230\u5176\u4F59\u89D2\u8272",
|
|
2071
|
+
onClick: () => copyChainToAll(r),
|
|
2072
|
+
children: "\u590D\u5236\u672C\u94FE\u5230\u5168\u90E8\u89D2\u8272"
|
|
2073
|
+
}
|
|
2074
|
+
)
|
|
2075
|
+
] })
|
|
2076
|
+
] })
|
|
1931
2077
|
] }, r);
|
|
1932
|
-
})
|
|
2078
|
+
}),
|
|
2079
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "dsh-kb-config__role dsh-kb-config__role--official", children: [
|
|
2080
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "dsh-kb-config__badge", children: "\u5B98\u65B9\u515C\u5E95" }),
|
|
2081
|
+
cf.provider && cf.model ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { children: [
|
|
2082
|
+
cf.provider,
|
|
2083
|
+
" / ",
|
|
2084
|
+
cf.model,
|
|
2085
|
+
"\uFF08reasoningEffort=",
|
|
2086
|
+
cf.reasoningEffort,
|
|
2087
|
+
"\uFF09"
|
|
2088
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "dsh-kb-config__muted", children: "\u672A\u914D\u7F6E\u2014\u2014\u89D2\u8272\u94FE\u5168\u7A7A\u65F6\u8D70\u5BBF\u4E3B\u9ED8\u8BA4\u6A21\u578B" })
|
|
2089
|
+
] }),
|
|
2090
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "dsh-kb-config__help", children: "\u6D3E\u53D1\u987A\u5E8F\uFF1A\u4E3B\u6A21\u578B \u2192 \u964D\u7EA7\uFF08\u987A\u5E8F=\u4F18\u5148\u5E8F\uFF0C\u6700\u591A 2 \u4E2A\uFF09\u2192 \u5B98\u65B9\u515C\u5E95\uFF08\u6052\u57AB\u94FE\u5C3E\uFF09\u2192 \u5BBF\u4E3B\u9ED8\u8BA4\u6A21\u578B\uFF1B\u5019\u9009\u4E0D\u53EF\u7528\uFF08\u542B\u5B98\u65B9\u8D26\u53F7\u672A\u767B\u5F55\uFF09\u9759\u9ED8\u5207\u6362\u5E76\u7559 [model-fallback] \u8BC4\u8BBA" })
|
|
1933
2091
|
] }),
|
|
1934
2092
|
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("section", { className: "dsh-kb-config__card", children: [
|
|
1935
2093
|
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("h3", { children: "\u8BC4\u5BA1\u5F15\u64CE\uFF08ocr\uFF09" }),
|
|
@@ -2911,7 +3069,7 @@ window.__ModuleLoader__.load({
|
|
|
2911
3069
|
`;
|
|
2912
3070
|
|
|
2913
3071
|
// client/config.css
|
|
2914
|
-
var config_default = ".dsh-kb-config {\n /* \u4E0E kanban.css \u5927\u5706\u89D2\u5951\u7EA6\u5BF9\u9F50\uFF08DSH \u5361\u7247 16px\uFF09 */\n --dsh-kb-config-border: var(--dsw-alias-border-l2, #333);\n --dsh-kb-config-radius-lg: 16px;\n --dsh-kb-config-radius-md: 10px;\n --dsh-kb-config-input: var(--dsw-specific-input-major, rgba(127, 127, 127, 0.12));\n padding: 16px;\n font-size: 13px;\n}\n.dsh-kb-config__card { border: 1px solid var(--dsh-kb-config-border); border-radius: var(--dsh-kb-config-radius-lg); padding: 20px; margin-bottom: 16px; }\n.dsh-kb-config__card h3 { margin: 0 0 14px; }\n\n.dsh-kb-config input,\n.dsh-kb-config select {\n box-sizing: border-box;\n width: 100%;\n padding: 8px 12px;\n border: 1px solid var(--dsh-kb-config-border);\n border-radius: var(--dsh-kb-config-radius-md);\n background: var(--dsh-kb-config-input);\n color: var(--dsw-alias-label-primary, inherit);\n font: inherit;\n transition: border-color 150ms ease, background-color 150ms ease;\n}\n.dsh-kb-config input:focus,\n.dsh-kb-config select:focus {\n outline: none;\n /* \u7126\u70B9/\u9009\u4E2D\u5F3A\u8C03\u8272\u8DDF\u968F\u4E3B\u9898\u524D\u666F\uFF08\u6D45\u8272=\u6697\u3001\u6697\u8272=\u767D\uFF09\uFF0C\u4E0E dsh settings \u8272\u7CFB\u4E00\u81F4 */\n border-color: var(--dsw-alias-label-primary, currentColor);\n}\n\n/* DSH \u98CE\u683C\u4E0B\u62C9\uFF1Atrigger \u884C\uFF08\u503C + \u203A chevron\uFF09+ \u5F39\u51FA\u83DC\u5355\uFF08\u9009\u4E2D\u6253\u52FE\u3001hover \u9AD8\u4EAE\uFF09 */\n.dsh-kb-config__select { position: relative; min-width: 0; }\n.dsh-kb-config__select-trigger {\n box-sizing: border-box;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n width: 100%;\n padding: 8px 12px;\n border: 1px solid var(--dsh-kb-config-border);\n border-radius: var(--dsh-kb-config-radius-md);\n background: var(--dsh-kb-config-input);\n color: var(--dsw-alias-label-primary, inherit);\n font: inherit;\n text-align: left;\n cursor: pointer;\n transition: border-color 150ms ease, background-color 150ms ease;\n}\n.dsh-kb-config__select-trigger:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, 0.12)); }\n.dsh-kb-config__select-trigger[aria-expanded='true'] { border-color: var(--dsw-alias-label-primary, currentColor); }\n.dsh-kb-config__select-placeholder { color: var(--dsw-alias-label-tertiary, #999); }\n.dsh-kb-config__select-trigger > span:first-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.dsh-kb-config__select-chevron { flex: 0 0 auto; display: inline-flex; color: var(--dsw-alias-label-tertiary, #999); }\n.dsh-kb-config__menu {\n position: absolute;\n z-index: 30;\n top: calc(100% + 6px);\n left: 0;\n right: 0;\n margin: 0;\n padding: 6px;\n list-style: none;\n border: 1px solid var(--dsh-kb-config-border);\n border-radius: var(--dsh-kb-config-radius-lg);\n background: var(--dsw-alias-bg-layer-3, #222);\n color: var(--dsw-alias-label-primary, inherit);\n box-shadow: 0 8px 24px rgba(0, 0, 0, 0.24);\n max-height: 260px;\n overflow: auto;\n}\n.dsh-kb-config__option {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n width: 100%;\n padding: 8px 10px;\n border: none;\n border-radius: var(--dsh-kb-config-radius-md);\n background: transparent;\n color: inherit;\n font: inherit;\n text-align: left;\n cursor: pointer;\n transition: background-color 150ms ease;\n}\n.dsh-kb-config__option:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, 0.12)); }\n.dsh-kb-config__option[data-selected='true'] { color: var(--dsw-alias-state-business-primary, #4b7bff); }\n.dsh-kb-config__option > span:first-child { overflow: hidden; text-overflow: ellipsis; white-space:
|
|
3072
|
+
var config_default = ".dsh-kb-config {\n /* \u4E0E kanban.css \u5927\u5706\u89D2\u5951\u7EA6\u5BF9\u9F50\uFF08DSH \u5361\u7247 16px\uFF09 */\n --dsh-kb-config-border: var(--dsw-alias-border-l2, #333);\n --dsh-kb-config-radius-lg: 16px;\n --dsh-kb-config-radius-md: 10px;\n --dsh-kb-config-input: var(--dsw-specific-input-major, rgba(127, 127, 127, 0.12));\n padding: 16px;\n font-size: 13px;\n}\n.dsh-kb-config__card { border: 1px solid var(--dsh-kb-config-border); border-radius: var(--dsh-kb-config-radius-lg); padding: 20px; margin-bottom: 16px; }\n.dsh-kb-config__card h3 { margin: 0 0 14px; }\n\n.dsh-kb-config input,\n.dsh-kb-config select {\n box-sizing: border-box;\n width: 100%;\n padding: 8px 12px;\n border: 1px solid var(--dsh-kb-config-border);\n border-radius: var(--dsh-kb-config-radius-md);\n background: var(--dsh-kb-config-input);\n color: var(--dsw-alias-label-primary, inherit);\n font: inherit;\n transition: border-color 150ms ease, background-color 150ms ease;\n}\n.dsh-kb-config input:focus,\n.dsh-kb-config select:focus {\n outline: none;\n /* \u7126\u70B9/\u9009\u4E2D\u5F3A\u8C03\u8272\u8DDF\u968F\u4E3B\u9898\u524D\u666F\uFF08\u6D45\u8272=\u6697\u3001\u6697\u8272=\u767D\uFF09\uFF0C\u4E0E dsh settings \u8272\u7CFB\u4E00\u81F4 */\n border-color: var(--dsw-alias-label-primary, currentColor);\n}\n\n/* DSH \u98CE\u683C\u4E0B\u62C9\uFF1Atrigger \u884C\uFF08\u503C + \u203A chevron\uFF09+ \u5F39\u51FA\u83DC\u5355\uFF08\u9009\u4E2D\u6253\u52FE\u3001hover \u9AD8\u4EAE\uFF09 */\n.dsh-kb-config__select { position: relative; min-width: 0; }\n.dsh-kb-config__select-trigger {\n box-sizing: border-box;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n width: 100%;\n padding: 8px 12px;\n border: 1px solid var(--dsh-kb-config-border);\n border-radius: var(--dsh-kb-config-radius-md);\n background: var(--dsh-kb-config-input);\n color: var(--dsw-alias-label-primary, inherit);\n font: inherit;\n text-align: left;\n cursor: pointer;\n transition: border-color 150ms ease, background-color 150ms ease;\n}\n.dsh-kb-config__select-trigger:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, 0.12)); }\n.dsh-kb-config__select-trigger[aria-expanded='true'] { border-color: var(--dsw-alias-label-primary, currentColor); }\n.dsh-kb-config__select-placeholder { color: var(--dsw-alias-label-tertiary, #999); }\n.dsh-kb-config__select-trigger > span:first-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.dsh-kb-config__select-chevron { flex: 0 0 auto; display: inline-flex; color: var(--dsw-alias-label-tertiary, #999); }\n.dsh-kb-config__menu {\n position: absolute;\n z-index: 30;\n top: calc(100% + 6px);\n left: 0;\n right: 0;\n margin: 0;\n padding: 6px;\n list-style: none;\n border: 1px solid var(--dsh-kb-config-border);\n border-radius: var(--dsh-kb-config-radius-lg);\n background: var(--dsw-alias-bg-layer-3, #222);\n color: var(--dsw-alias-label-primary, inherit);\n box-shadow: 0 8px 24px rgba(0, 0, 0, 0.24);\n max-height: 260px;\n overflow: auto;\n}\n.dsh-kb-config__option {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n width: 100%;\n padding: 8px 10px;\n border: none;\n border-radius: var(--dsh-kb-config-radius-md);\n background: transparent;\n color: inherit;\n font: inherit;\n text-align: left;\n cursor: pointer;\n transition: background-color 150ms ease;\n}\n.dsh-kb-config__option:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, 0.12)); }\n.dsh-kb-config__option[data-selected='true'] { color: var(--dsw-alias-state-business-primary, #4b7bff); }\n.dsh-kb-config__option > span:first-child { overflow: hidden; text-overflow: ellipsis; white-space: normal; word-break: break-word; }\n.dsh-kb-config__option-check { flex: 0 0 auto; display: inline-flex; }\n\n.dsh-kb-config__label { display: block; margin: 12px 0 6px; font-weight: 600; }\n.dsh-kb-config__help { color: var(--dsw-alias-label-secondary, #888); font-size: 12px; display: block; margin: 6px 0 0; }\n.dsh-kb-config__role { display: grid; grid-template-columns: 32px 1fr 1fr 1fr auto; gap: 10px; margin-bottom: 12px; align-items: center; }\n.dsh-kb-config__src { color: var(--dsw-alias-label-secondary, #888); font-size: 12px; white-space: nowrap; padding-left: 4px; }\n.dsh-kb-config__footer { display: flex; justify-content: flex-end; margin-top: 4px; }\n.dsh-kb-config__footer button {\n padding: 6px 16px;\n border: 1px solid var(--dsh-kb-config-border);\n border-radius: var(--dsh-kb-config-radius-md);\n background: transparent;\n color: inherit;\n font: inherit;\n cursor: pointer;\n transition: background-color 150ms ease;\n}\n.dsh-kb-config__footer button:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, 0.12)); }\n.dsh-kb-config__error { color: var(--dsw-alias-state-error-primary, #e5484d); }\n\n/* \u6A21\u578B\u94FE\u5361\uFF1A\u5BBF\u4E3B provider \u5361\u5C55\u5F00\u98CE\u2014\u2014\u89D2\u8272\u5934\u53EF\u6298\u53E0\u3001\u5B57\u6BB5 label \u5728\u4E0A + \u63A7\u4EF6\u5168\u5BBD\u3001\u964D\u7EA7\u4E3A\u6761\u76EE\u7EC4 */\n.dsh-kb-config__role-block { margin-bottom: 4px; }\n.dsh-kb-config__role-head {\n display: flex;\n align-items: center;\n gap: 8px;\n width: 100%;\n padding: 10px 10px;\n border: none;\n border-radius: var(--dsh-kb-config-radius-md);\n background: transparent;\n color: inherit;\n font: inherit;\n text-align: left;\n cursor: pointer;\n}\n.dsh-kb-config__role-head:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, 0.12)); }\n.dsh-kb-config__chevron { flex: 0 0 auto; display: inline-flex; color: var(--dsw-alias-label-tertiary, #999); transition: transform 150ms ease; }\n.dsh-kb-config__chevron--open { transform: rotate(90deg); }\n.dsh-kb-config__role-summary {\n margin-left: auto;\n color: var(--dsw-alias-label-secondary, #888);\n font-size: 12px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n/* \u5C55\u5F00\u4F53\u80CC\u666F\u8272\uFF1A\u4E0E\u6298\u53E0\u89D2\u8272\u5934\u5F62\u6210\u5757\u7EA7\u533A\u5206\uFF08\u540C\u5BBF\u4E3B\u8BBE\u7F6E\u5185\u5D4C\u9762\u677F\u5C42\u6B21\uFF09 */\n.dsh-kb-config__role-body {\n padding: 12px 14px 16px;\n margin: 8px 0 4px;\n background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, 0.08));\n border-radius: var(--dsh-kb-config-radius-md);\n}\n.dsh-kb-config__group-label { display: block; margin: 10px 0 8px; font-weight: 600; }\n.dsh-kb-config__group-label:first-child { margin-top: 0; }\n.dsh-kb-config__field { display: block; margin: 0 0 10px; }\n.dsh-kb-config__field-label { display: block; margin: 0 0 6px; color: var(--dsw-alias-label-secondary, #888); font-size: 12px; }\n.dsh-kb-config__fb-group {\n margin: 0 0 10px;\n padding: 10px 12px;\n border: 1px solid var(--dsh-kb-config-border);\n border-radius: var(--dsh-kb-config-radius-md);\n}\n.dsh-kb-config__fb-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin: 0 0 8px;\n}\n.dsh-kb-config__fb-title { color: var(--dsw-alias-label-secondary, #888); font-size: 12px; }\n.dsh-kb-config__row-btn {\n padding: 6px 10px;\n border: 1px solid var(--dsh-kb-config-border);\n border-radius: var(--dsh-kb-config-radius-md);\n background: transparent;\n color: inherit;\n font: inherit;\n font-size: 12px;\n white-space: nowrap;\n cursor: pointer;\n transition: background-color 150ms ease;\n}\n.dsh-kb-config__row-btn:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, 0.12)); }\n.dsh-kb-config__row-btn:disabled { opacity: 0.5; cursor: not-allowed; }\n.dsh-kb-config__role-actions { display: flex; gap: 8px; margin-top: 12px; }\n.dsh-kb-config__role--official {\n display: flex;\n gap: 10px;\n align-items: center;\n margin: 12px 0 0;\n padding: 8px 10px;\n border: 1px dashed var(--dsh-kb-config-border);\n border-radius: var(--dsh-kb-config-radius-md);\n color: var(--dsw-alias-label-secondary, #888);\n font-size: 12px;\n}\n.dsh-kb-config__badge {\n flex: 0 0 auto;\n padding: 2px 8px;\n border-radius: 999px;\n background: var(--dsw-alias-interactive-bg-hover, rgba(127, 127, 127, 0.12));\n color: var(--dsw-alias-label-primary, inherit);\n font-size: 12px;\n white-space: nowrap;\n}\n.dsh-kb-config__muted { color: var(--dsw-alias-label-tertiary, #999); }\n";
|
|
2915
3073
|
|
|
2916
3074
|
// client/index.ts
|
|
2917
3075
|
var name = "kanban-board";
|
package/lib/config.d.ts
CHANGED
|
@@ -19,12 +19,19 @@ export interface KanbanConfig {
|
|
|
19
19
|
provider: string;
|
|
20
20
|
model: string;
|
|
21
21
|
reasoningEffort?: string;
|
|
22
|
+
/** 降级候选(顺序=优先序,最多 2 个);仅 primary 配齐时生效。 */
|
|
22
23
|
fallbacks?: Array<{
|
|
23
24
|
provider: string;
|
|
24
25
|
model: string;
|
|
25
26
|
reasoningEffort?: string;
|
|
26
27
|
}>;
|
|
27
28
|
}>>;
|
|
29
|
+
/** 全局官方兜底(所有角色共用):GUI 未配角色链时单候选直用;有配置时垫主+降级链尾。 */
|
|
30
|
+
chainFallback: {
|
|
31
|
+
provider: string;
|
|
32
|
+
model: string;
|
|
33
|
+
reasoningEffort?: string;
|
|
34
|
+
};
|
|
28
35
|
};
|
|
29
36
|
dispatcher: {
|
|
30
37
|
staleTimeoutSeconds: number;
|
package/lib/config.js
CHANGED
|
@@ -13,7 +13,7 @@ const modelItemSchema = () => Schema.object({
|
|
|
13
13
|
provider: Schema.string().required(),
|
|
14
14
|
model: Schema.string().required(),
|
|
15
15
|
reasoningEffort: Schema.string().default('high'),
|
|
16
|
-
})).default([]),
|
|
16
|
+
})).max(2).default([]),
|
|
17
17
|
});
|
|
18
18
|
export const Config = Schema.object({
|
|
19
19
|
storageDir: Schema.string().default('$DSH_HOME/storages/kanban'),
|
|
@@ -25,6 +25,12 @@ export const Config = Schema.object({
|
|
|
25
25
|
// 角色系统提示词经 personas/kanban-{v,p,w,d}/agent.cordis.yml 组合装配(agentPresets.mount),
|
|
26
26
|
// 随包安装到 $DSH_HOME/.agent-presets/(preset-installer),不再经 config 引用 md 文本。
|
|
27
27
|
models: Schema.dict(modelItemSchema()).default({}),
|
|
28
|
+
// 官方兜底默认空(不启用);部署经 bundle patch 配值(如官方账号路由 deepseek-account/deepseek-flash)。
|
|
29
|
+
chainFallback: Schema.object({
|
|
30
|
+
provider: Schema.string().default(''),
|
|
31
|
+
model: Schema.string().default(''),
|
|
32
|
+
reasoningEffort: Schema.string().default('high'),
|
|
33
|
+
}).default({ provider: '', model: '', reasoningEffort: 'high' }),
|
|
28
34
|
}),
|
|
29
35
|
dispatcher: Schema.object({
|
|
30
36
|
staleTimeoutSeconds: Schema.number().default(14400),
|
|
@@ -5,6 +5,10 @@ import type { Role } from '../domain/types.js';
|
|
|
5
5
|
import type { WikiVaultClient } from '../wiki/wiki-vault-client.js';
|
|
6
6
|
import { probeOcr } from '../services/ocr-cli.js';
|
|
7
7
|
import type { AgentModelOptions } from './dispatcher.js';
|
|
8
|
+
/** 关键事件追加落盘 storageDir/dispatcher.log(与 dispatcher.ts 同一文件、同一行格式)——
|
|
9
|
+
* agent-runner 未注入 logFile,按 dispatcher 同款方式经 configProvider 派生 storageDir;
|
|
10
|
+
* 写失败静默忽略(日志只是观测面,事件日志才是事实源)。 */
|
|
11
|
+
export declare function logToDispatcherLog(configProvider: ConfigProvider, msg: string): void;
|
|
8
12
|
/** 角色组合标记(会话10事故根因A):setup 成功组合角色工具面后写入,live 复用前校验。 */
|
|
9
13
|
interface RoleCompositionMarker {
|
|
10
14
|
role: Role;
|
|
@@ -8,6 +8,7 @@ import { ensureLocalKbRoot } from '../wiki/local-kb.js';
|
|
|
8
8
|
import { installCleanFsTools } from '../roles/clean-fs-tools.js';
|
|
9
9
|
import { mountOrRecompose } from '../roles/preset-installer.js';
|
|
10
10
|
import { buildModelCandidates, isModelUnavailableError } from './model-candidates.js';
|
|
11
|
+
import { registerModelChain } from './model-chain.js';
|
|
11
12
|
import { toolName, isGuardSynthesizedReply, lastTurnEnd } from './session-events.js';
|
|
12
13
|
import { filterCandidatesByCatalog } from '../services/llm-catalog.js';
|
|
13
14
|
import { attachSessionToWorkspace, resolveOrCreateWorkspace } from './workspace-attach.js';
|
|
@@ -31,14 +32,14 @@ function isInfraError(err) {
|
|
|
31
32
|
function classifyTurnError(code, message) {
|
|
32
33
|
if (code === 'UNKNOWN_MODEL' || code === 'UNSUPPORTED_REASONING_EFFORT' || code === 'INVALID_REQUEST')
|
|
33
34
|
return 'config';
|
|
34
|
-
if (code
|
|
35
|
+
if ((code ?? '').includes('QUOTA') || /quota|rate.?limit|\b429\b|insufficient balance/i.test(message) || isInfraError(message))
|
|
35
36
|
return 'transient';
|
|
36
37
|
return 'unknown';
|
|
37
38
|
}
|
|
38
39
|
/** 关键事件追加落盘 storageDir/dispatcher.log(与 dispatcher.ts 同一文件、同一行格式)——
|
|
39
40
|
* agent-runner 未注入 logFile,按 dispatcher 同款方式经 configProvider 派生 storageDir;
|
|
40
41
|
* 写失败静默忽略(日志只是观测面,事件日志才是事实源)。 */
|
|
41
|
-
function logToDispatcherLog(configProvider, msg) {
|
|
42
|
+
export function logToDispatcherLog(configProvider, msg) {
|
|
42
43
|
try {
|
|
43
44
|
const storageDir = configProvider.getEffective().storageDir.replace('$DSH_HOME', process.env.DSH_HOME ?? process.cwd());
|
|
44
45
|
writeFileSync(join(storageDir, 'dispatcher.log'), new Date().toISOString() + ' ' + msg + '\n', { flag: 'a' });
|
|
@@ -395,7 +396,7 @@ ${task.body}`);
|
|
|
395
396
|
const builtCandidates = buildModelCandidates(this.configProvider.getEffective(), task.assignee, this.defaultModel);
|
|
396
397
|
// 派发前预校验(LLM 目录):不存在的 provider/model 组合、模型未声明的 reasoningEffort
|
|
397
398
|
// 直接剔除,不再打进网络白烧会话(UNKNOWN_MODEL / UNSUPPORTED_REASONING_EFFORT 秒退实证形态——
|
|
398
|
-
//
|
|
399
|
+
// 必须前置拦截)。
|
|
399
400
|
// fail-open:llm 服务未就绪/目录探测失败 → 全放行,不阻断派发。
|
|
400
401
|
// 全部候选被拒 → block(model-unavailable) 终态(与 spawn 全候选失败同语义,等人工修配置)。
|
|
401
402
|
let candidates = builtCandidates;
|
|
@@ -421,6 +422,7 @@ ${task.body}`);
|
|
|
421
422
|
}
|
|
422
423
|
candidates = precheck.ok;
|
|
423
424
|
}
|
|
425
|
+
let chainStartIndex = 0;
|
|
424
426
|
if (candidates.length === 0) {
|
|
425
427
|
// 无任何候选配置:不传 agentOptions(用部署默认),单次尝试
|
|
426
428
|
agent = hasRunHistory
|
|
@@ -434,7 +436,7 @@ ${task.body}`);
|
|
|
434
436
|
else {
|
|
435
437
|
const agents = this.ctx.get('agents');
|
|
436
438
|
let spawnError = null;
|
|
437
|
-
for (const candidate of candidates) {
|
|
439
|
+
for (const [candidateIndex, candidate] of candidates.entries()) {
|
|
438
440
|
try {
|
|
439
441
|
// hasRunHistory → resumeOrReuse 直接返回 AgentLike(内部已解包 .agent);create 返回 { agent } 需解包。
|
|
440
442
|
// 统一归一化为 AgentLike,避免二次解包(h.agent=undefined → if(!agent) 误标 failed)。
|
|
@@ -442,6 +444,7 @@ ${task.body}`);
|
|
|
442
444
|
? await this.resumeOrReuse(agents, task.resumeSessionId ?? `kbn-${taskId}`, { agentOptions: candidate, setup, role: task.assignee, taskId: task.id })
|
|
443
445
|
: (await agents.create({ sessionId: SessionId(`kbn-${taskId}`), meta: { cwd: sessionCwd }, agentOptions: candidate, setup })).agent;
|
|
444
446
|
agent = h;
|
|
447
|
+
chainStartIndex = candidateIndex;
|
|
445
448
|
// 切换成功且非首选 → 发可审计 model/fallback 评论(记录证据,不弹用户)
|
|
446
449
|
if (candidate !== candidates[0]) {
|
|
447
450
|
try {
|
|
@@ -467,6 +470,14 @@ ${task.body}`);
|
|
|
467
470
|
throw spawnError;
|
|
468
471
|
}
|
|
469
472
|
}
|
|
473
|
+
if (agent && candidates.length > 0) {
|
|
474
|
+
registerModelChain(agent, {
|
|
475
|
+
candidates,
|
|
476
|
+
index: chainStartIndex,
|
|
477
|
+
taskId,
|
|
478
|
+
comment: async (body) => { await this.kanban.comment(taskId, body, 'system'); },
|
|
479
|
+
});
|
|
480
|
+
}
|
|
470
481
|
}
|
|
471
482
|
catch (err) {
|
|
472
483
|
// claim/buildContext/spawn 失败也走 failed(attempts 递增)→ 调度器重派/看门狗熔断;不再让任务永久 claimed
|
|
@@ -2,12 +2,18 @@ import type { KanbanConfig } from '../config.js';
|
|
|
2
2
|
import type { Role } from '../domain/types.js';
|
|
3
3
|
import type { AgentModelOptions } from './dispatcher.js';
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* 模型候选链(四层):
|
|
6
|
+
* ① 角色 primary(config.roles.models[role])→ ② 降级候选(fallbacks,顺序=优先序,≤2)
|
|
7
|
+
* → ③ 全局官方兜底(config.roles.chainFallback,恒垫链尾)→ ④ 宿主默认模型(defaultModel)。
|
|
8
|
+
* 角色链全空 → 官方兜底单候选直用;官方兜底也未配 → 宿主默认模型;都无 → 空链(调用方不传 agentOptions)。
|
|
7
9
|
* reasoningEffort 未指定一律 'high'。
|
|
8
10
|
*
|
|
9
11
|
* @returns 有序候选链(不含不可用者;可能为空 = 无任何配置)
|
|
10
12
|
*/
|
|
11
13
|
export declare function buildModelCandidates(config: KanbanConfig, role: Role, defaultModel?: AgentModelOptions): AgentModelOptions[];
|
|
12
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* 判定 create/resume 错误是否属于候选不可用(可静默切换下一候选)。
|
|
16
|
+
* 覆盖三类:provider/model 不可用(无适配器/不存在)、凭据缺失(官方账号未登录
|
|
17
|
+
* MISSING_CREDENTIAL / 登录提示——不切换会把登录态问题误判成任务失败)。
|
|
18
|
+
*/
|
|
13
19
|
export declare function isModelUnavailableError(err: unknown): boolean;
|
|
@@ -1,30 +1,51 @@
|
|
|
1
|
+
/** 单角色降级候选上限(与 config schema .max(2)、config-override 校验同口径)。 */
|
|
2
|
+
const MAX_FALLBACKS = 2;
|
|
1
3
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
+
* 模型候选链(四层):
|
|
5
|
+
* ① 角色 primary(config.roles.models[role])→ ② 降级候选(fallbacks,顺序=优先序,≤2)
|
|
6
|
+
* → ③ 全局官方兜底(config.roles.chainFallback,恒垫链尾)→ ④ 宿主默认模型(defaultModel)。
|
|
7
|
+
* 角色链全空 → 官方兜底单候选直用;官方兜底也未配 → 宿主默认模型;都无 → 空链(调用方不传 agentOptions)。
|
|
4
8
|
* reasoningEffort 未指定一律 'high'。
|
|
5
9
|
*
|
|
6
10
|
* @returns 有序候选链(不含不可用者;可能为空 = 无任何配置)
|
|
7
11
|
*/
|
|
8
12
|
export function buildModelCandidates(config, role, defaultModel) {
|
|
9
13
|
const m = config.roles?.models?.[role];
|
|
14
|
+
const cf = config.roles?.chainFallback;
|
|
15
|
+
const officialTail = [];
|
|
16
|
+
if (cf?.provider && cf?.model) {
|
|
17
|
+
officialTail.push({ provider: cf.provider, model: cf.model, reasoningEffort: cf.reasoningEffort ?? 'high' });
|
|
18
|
+
}
|
|
10
19
|
const chain = [];
|
|
11
20
|
if (m?.provider && m?.model) {
|
|
12
21
|
chain.push({ provider: m.provider, model: m.model, reasoningEffort: m.reasoningEffort ?? 'high' });
|
|
13
|
-
for (const f of m.fallbacks ?? []) {
|
|
22
|
+
for (const f of (m.fallbacks ?? []).slice(0, MAX_FALLBACKS)) {
|
|
14
23
|
if (f?.provider && f?.model)
|
|
15
24
|
chain.push({ provider: f.provider, model: f.model, reasoningEffort: f.reasoningEffort ?? 'high' });
|
|
16
25
|
}
|
|
26
|
+
chain.push(...officialTail);
|
|
17
27
|
}
|
|
18
|
-
else
|
|
19
|
-
|
|
28
|
+
else {
|
|
29
|
+
// 角色链全空:官方兜底直用;未配官方兜底 → 宿主默认模型(第四层安全网)。
|
|
30
|
+
if (officialTail.length)
|
|
31
|
+
chain.push(...officialTail);
|
|
32
|
+
else if (defaultModel?.provider && defaultModel?.model) {
|
|
33
|
+
chain.push({ provider: defaultModel.provider, model: defaultModel.model, reasoningEffort: defaultModel.reasoningEffort ?? 'high' });
|
|
34
|
+
}
|
|
20
35
|
}
|
|
21
36
|
return chain;
|
|
22
37
|
}
|
|
23
|
-
/**
|
|
38
|
+
/**
|
|
39
|
+
* 判定 create/resume 错误是否属于候选不可用(可静默切换下一候选)。
|
|
40
|
+
* 覆盖三类:provider/model 不可用(无适配器/不存在)、凭据缺失(官方账号未登录
|
|
41
|
+
* MISSING_CREDENTIAL / 登录提示——不切换会把登录态问题误判成任务失败)。
|
|
42
|
+
*/
|
|
24
43
|
export function isModelUnavailableError(err) {
|
|
25
44
|
const msg = String(err instanceof Error ? err.message : err).toLowerCase();
|
|
26
45
|
if (msg.includes('adapter'))
|
|
27
46
|
return true; // no adapter registered for provider …
|
|
47
|
+
if (msg.includes('missing_credential') || msg.includes('credentials') || msg.includes('login') || msg.includes('登录'))
|
|
48
|
+
return true;
|
|
28
49
|
if (msg.includes('provider') && (msg.includes('unavailable') || msg.includes('not') || msg.includes('fail')))
|
|
29
50
|
return true;
|
|
30
51
|
return msg.includes('model') && (msg.includes('unavailable') || msg.includes('not found') || msg.includes('no adapter'));
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { KanbanConfig } from '../config.js';
|
|
2
|
+
import type { AgentModelOptions } from './dispatcher.js';
|
|
3
|
+
export interface ModelChainState {
|
|
4
|
+
candidates: AgentModelOptions[];
|
|
5
|
+
index: number;
|
|
6
|
+
taskId?: string;
|
|
7
|
+
comment?: (body: string) => Promise<void>;
|
|
8
|
+
}
|
|
9
|
+
export declare function registerModelChain(agent: object, state: ModelChainState): void;
|
|
10
|
+
export declare function isSwitchableModelFailure(failure: {
|
|
11
|
+
code?: string;
|
|
12
|
+
message?: string;
|
|
13
|
+
status?: number;
|
|
14
|
+
}): boolean;
|
|
15
|
+
export interface ModelChainHookDeps {
|
|
16
|
+
getConfig: () => KanbanConfig;
|
|
17
|
+
log?: (line: string) => void;
|
|
18
|
+
}
|
|
19
|
+
export declare function installModelChainHooks(scope: unknown, deps: ModelChainHookDeps): () => void;
|