dsh-daruma 0.1.0 → 0.1.2
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/README.md +3 -1
- package/lib/client/BackupPanel.js +7 -3
- package/lib/client/BackupPanel.js.map +1 -1
- package/lib/client/StatusDock.js +2 -11
- package/lib/client/StatusDock.js.map +1 -1
- package/lib/client/api.d.ts +2 -0
- package/lib/client/api.js.map +1 -1
- package/lib/client/health.d.ts +20 -0
- package/lib/client/health.js +27 -0
- package/lib/client/health.js.map +1 -0
- package/lib/client.js +33 -10
- package/lib/client.js.map +1 -1
- package/lib/config.js +38 -8
- package/lib/config.js.map +1 -1
- package/lib/engine.d.ts +9 -3
- package/lib/engine.js +14 -5
- package/lib/engine.js.map +1 -1
- package/lib/index.js +6 -1
- package/lib/index.js.map +1 -1
- package/lib/rpc.js +7 -5
- package/lib/rpc.js.map +1 -1
- package/lib/store.js +31 -3
- package/lib/store.js.map +1 -1
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -23,7 +23,9 @@ channel stays cooled-down across restarts.
|
|
|
23
23
|
## Install
|
|
24
24
|
|
|
25
25
|
```bash
|
|
26
|
-
dsh plugin --profile web add
|
|
26
|
+
dsh plugin --profile web add dsh-daruma # from npm
|
|
27
|
+
# or from a checkout:
|
|
28
|
+
dsh plugin --profile web add link:./packages/daruma-core link:./packages/dsh-daruma
|
|
27
29
|
```
|
|
28
30
|
|
|
29
31
|
## Configure
|
|
@@ -60,8 +60,8 @@ const candidateRowStyle = {
|
|
|
60
60
|
};
|
|
61
61
|
/** One candidate row, memoized so selection changes only re-render it. */
|
|
62
62
|
const CandidateRow = memo(function CandidateRow(props) {
|
|
63
|
-
const { candidate, isBackup, t, onSetBackup } = props;
|
|
64
|
-
return (_jsxs("div", { style: candidateRowStyle, children: [_jsx("span", { style: { width: 14, flexShrink: 0 } }), _jsx("span", { style: { flex: 1, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, children: candidate.model }), isBackup && _jsx(Pill, { active: true, children: t('currentBackupMark') }), _jsx(Button, { size: "sm", variant: "ghost", disabled: isBackup, onClick: () => onSetBackup(candidate.model), children: t('setBackup') })] }));
|
|
63
|
+
const { candidate, isBackup, busy, t, onSetBackup } = props;
|
|
64
|
+
return (_jsxs("div", { style: candidateRowStyle, children: [_jsx("span", { style: { width: 14, flexShrink: 0 } }), _jsx("span", { style: { flex: 1, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, children: candidate.model }), isBackup && _jsx(Pill, { active: true, children: t('currentBackupMark') }), _jsx(Button, { size: "sm", variant: "ghost", disabled: isBackup || busy, onClick: () => onSetBackup(candidate.model), children: t('setBackup') })] }));
|
|
65
65
|
});
|
|
66
66
|
export function BackupPanel(props) {
|
|
67
67
|
const { api, t, status, onClose } = props;
|
|
@@ -116,6 +116,7 @@ export function BackupPanel(props) {
|
|
|
116
116
|
}
|
|
117
117
|
};
|
|
118
118
|
const clearBackup = async () => {
|
|
119
|
+
setBusy('__clear__');
|
|
119
120
|
setError(null);
|
|
120
121
|
try {
|
|
121
122
|
await api.clearBackup();
|
|
@@ -124,9 +125,12 @@ export function BackupPanel(props) {
|
|
|
124
125
|
catch (cause) {
|
|
125
126
|
setError(cause instanceof Error ? cause.message : String(cause));
|
|
126
127
|
}
|
|
128
|
+
finally {
|
|
129
|
+
setBusy(null);
|
|
130
|
+
}
|
|
127
131
|
};
|
|
128
132
|
// Stable callback so memoized CandidateRow props don't change every render.
|
|
129
133
|
const handleSetBackup = useCallback((model) => void setBackup(model), [provider]);
|
|
130
|
-
return (_jsx(Modal, { open: true, onClose: onClose, title: t('backupManage'), className: "daruma-wide-dialog", closeLabel: t('close'), description: t('backupManageHint'), footer: _jsx(Button, { variant: "primary", onClick: onClose, children: t('close') }), children: _jsxs("div", { style: sectionStyle, children: [status !== null && status.failoverCount > 0 && (_jsxs("div", { style: noteStyle, children: [t('failoverCount'), ": ", status.failoverCount, status.failoverHistory.length > 0 && (_jsxs(_Fragment, { children: [' · ', t('lastFailover'), ": ", status.failoverHistory.map((h) => `${h.from} → ${h.to}`).join(' · ')] }))] })), backupShown !== null && (_jsxs("div", { style: rowStyle, children: [_jsxs("span", { style: { flexShrink: 0 }, children: [t('currentBackup'), ":"] }), _jsx(Pill, { active: true, style: backupPillStyle, title: `${backupShown.provider}/${backupShown.model}`, children: _jsxs("span", { style: ellipsisStyle, children: [backupShown.provider, "/", backupShown.model] }) }), _jsx(Button, { size: "sm", variant: "ghost", style: { flexShrink: 0 }, onClick: () => void clearBackup(), children: t('clearBackup') })] })), _jsxs("div", { style: rowStyle, children: [(status?.providers ?? []).length > 0 ? (_jsx("select", { value: provider, disabled: loading, onChange: (event) => setProvider(event.currentTarget.value), style: { flex: 1, padding: '4px 8px', fontSize: 13 }, children: status?.providers.map((name) => (_jsx("option", { value: name, children: name }, name))) })) : (_jsx("input", { value: provider, placeholder: t('providerPlaceholder'), onChange: (event) => setProvider(event.currentTarget.value), style: { flex: 1, padding: '4px 8px', fontSize: 13 } })), _jsx(Button, { size: "sm", variant: "outline", onClick: () => void loadCandidates(), disabled: provider === '' || loading, children: loading ? t('loading') : t('loadCandidates') })] }), error !== null && _jsx("div", { style: { color: 'var(--dsw-text-danger, #e5484d)' }, children: error }), candidates !== null && (_jsxs(_Fragment, { children: [_jsx("div", { style: rowStyle, children: _jsxs("span", { children: [t('candidates'), " (", candidates.length, ")"] }) }), candidates.length === 0 && _jsx("div", { style: noteStyle, children: t('noCandidates') }), _jsx("div", { style: listStyle, children: candidates.map((candidate) => (_jsx(CandidateRow, { candidate: candidate, isBackup: candidate.model === backupModel, t: t, onSetBackup: handleSetBackup }, candidate.model))) })] }))] }) }));
|
|
134
|
+
return (_jsx(Modal, { open: true, onClose: onClose, title: t('backupManage'), className: "daruma-wide-dialog", closeLabel: t('close'), description: t('backupManageHint'), footer: _jsx(Button, { variant: "primary", onClick: onClose, children: t('close') }), children: _jsxs("div", { style: sectionStyle, children: [status !== null && status.failoverCount > 0 && (_jsxs("div", { style: noteStyle, children: [t('failoverCount'), ": ", status.failoverCount, status.failoverHistory.length > 0 && (_jsxs(_Fragment, { children: [' · ', t('lastFailover'), ": ", status.failoverHistory.map((h) => `${h.from} → ${h.to}`).join(' · ')] }))] })), backupShown !== null && (_jsxs("div", { style: rowStyle, children: [_jsxs("span", { style: { flexShrink: 0 }, children: [t('currentBackup'), ":"] }), _jsx(Pill, { active: true, style: backupPillStyle, title: `${backupShown.provider}/${backupShown.model}`, children: _jsxs("span", { style: ellipsisStyle, children: [backupShown.provider, "/", backupShown.model] }) }), _jsx(Button, { size: "sm", variant: "ghost", style: { flexShrink: 0 }, disabled: busy !== null, onClick: () => void clearBackup(), children: t('clearBackup') })] })), _jsxs("div", { style: rowStyle, children: [(status?.providers ?? []).length > 0 ? (_jsx("select", { value: provider, disabled: loading, onChange: (event) => setProvider(event.currentTarget.value), style: { flex: 1, padding: '4px 8px', fontSize: 13 }, children: status?.providers.map((name) => (_jsx("option", { value: name, children: name }, name))) })) : (_jsx("input", { value: provider, placeholder: t('providerPlaceholder'), onChange: (event) => setProvider(event.currentTarget.value), style: { flex: 1, padding: '4px 8px', fontSize: 13 } })), _jsx(Button, { size: "sm", variant: "outline", onClick: () => void loadCandidates(), disabled: provider === '' || loading, children: loading ? t('loading') : t('loadCandidates') })] }), error !== null && _jsx("div", { style: { color: 'var(--dsw-text-danger, #e5484d)' }, children: error }), candidates !== null && (_jsxs(_Fragment, { children: [_jsx("div", { style: rowStyle, children: _jsxs("span", { children: [t('candidates'), " (", candidates.length, ")"] }) }), candidates.length === 0 && _jsx("div", { style: noteStyle, children: t('noCandidates') }), _jsx("div", { style: listStyle, children: candidates.map((candidate) => (_jsx(CandidateRow, { candidate: candidate, isBackup: candidate.model === backupModel, busy: busy !== null, t: t, onSetBackup: handleSetBackup }, candidate.model))) })] }))] }) }));
|
|
131
135
|
}
|
|
132
136
|
//# sourceMappingURL=BackupPanel.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BackupPanel.js","sourceRoot":"","sources":["../../src/client/BackupPanel.tsx"],"names":[],"mappings":";AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AACvE,OAAO,EACL,MAAM,EACN,KAAK,EACL,IAAI,GACL,MAAM,uCAAuC,CAAA;AAK9C,SAAS,cAAc,CAAC,MAAyB;IAC/C,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,EAAE,CAAA;IAC9B,IAAI,MAAM,CAAC,OAAO,KAAK,IAAI;QAAE,OAAO,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;IACvE,IAAI,MAAM,CAAC,MAAM,KAAK,IAAI;QAAE,OAAO,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAA;IACzD,OAAO,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;AAClC,CAAC;AAED,MAAM,YAAY,GAAwB;IACxC,OAAO,EAAE,MAAM;IACf,aAAa,EAAE,QAAQ;IACvB,GAAG,EAAE,KAAK;IACV,OAAO,EAAE,QAAQ;IACjB,QAAQ,EAAE,MAAM;CACjB,CAAA;AAED,MAAM,QAAQ,GAAwB;IACpC,OAAO,EAAE,MAAM;IACf,UAAU,EAAE,QAAQ;IACpB,GAAG,EAAE,KAAK;CACX,CAAA;AAED,MAAM,SAAS,GAAwB,EAAE,KAAK,EAAE,gCAAgC,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAA;AAEpG,+DAA+D;AAC/D,MAAM,eAAe,GAAwB;IAC3C,IAAI,EAAE,UAAU;IAChB,QAAQ,EAAE,CAAC;IACX,QAAQ,EAAE,QAAQ;CACnB,CAAA;AAED,MAAM,aAAa,GAAwB;IACzC,QAAQ,EAAE,CAAC;IACX,QAAQ,EAAE,QAAQ;IAClB,YAAY,EAAE,UAAU;IACxB,UAAU,EAAE,QAAQ;CACrB,CAAA;AAED,MAAM,SAAS,GAAwB;IACrC,SAAS,EAAE,GAAG;IACd,SAAS,EAAE,MAAM;IACjB,OAAO,EAAE,MAAM;IACf,aAAa,EAAE,QAAQ;IACvB,GAAG,EAAE,CAAC;IACN,OAAO,EAAE,SAAS;IAClB,UAAU,EAAE,iBAAiB;IAC7B,kBAAkB,EAAE,SAAS;CAC9B,CAAA;AAED,gEAAgE;AAChE,MAAM,iBAAiB,GAAwB;IAC7C,GAAG,QAAQ;IACX,iBAAiB,EAAE,MAAM;IACzB,oBAAoB,EAAE,MAAM;CAC7B,CAAA;AAED,0EAA0E;AAC1E,MAAM,YAAY,GAAG,IAAI,CAAC,SAAS,YAAY,CAAC,
|
|
1
|
+
{"version":3,"file":"BackupPanel.js","sourceRoot":"","sources":["../../src/client/BackupPanel.tsx"],"names":[],"mappings":"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"}
|
package/lib/client/StatusDock.js
CHANGED
|
@@ -6,6 +6,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
6
6
|
*/
|
|
7
7
|
import { useCallback, useEffect, useState } from 'react';
|
|
8
8
|
import { Button, IconCheckOutline16, IconWarningOutline16 } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
9
|
+
import { overallState } from "./health.js";
|
|
9
10
|
import { BackupPanel } from "./BackupPanel.js";
|
|
10
11
|
const controlStyle = {
|
|
11
12
|
display: 'inline-flex',
|
|
@@ -16,16 +17,6 @@ const controlStyle = {
|
|
|
16
17
|
textOverflow: 'ellipsis',
|
|
17
18
|
whiteSpace: 'nowrap',
|
|
18
19
|
};
|
|
19
|
-
/** Overall health: any cooling channel → error, any probe → warning, else done. */
|
|
20
|
-
function overallState(status) {
|
|
21
|
-
if (status === null)
|
|
22
|
-
return 'ongoing';
|
|
23
|
-
if (status.channels.some((c) => c.state === 'COOLDOWN'))
|
|
24
|
-
return 'error';
|
|
25
|
-
if (status.channels.some((c) => c.state === 'PROBE'))
|
|
26
|
-
return 'warning';
|
|
27
|
-
return 'done';
|
|
28
|
-
}
|
|
29
20
|
const iconColor = {
|
|
30
21
|
done: 'var(--dsw-text-success, #30a46c)',
|
|
31
22
|
warning: 'var(--dsw-text-warning, #f5a524)',
|
|
@@ -59,7 +50,7 @@ export function StatusDock(props) {
|
|
|
59
50
|
const backupLabel = status?.backup !== null && status?.backup !== undefined
|
|
60
51
|
? `${status.backup.provider}/${status.backup.model}`
|
|
61
52
|
: t('backupNone');
|
|
62
|
-
return (_jsxs(_Fragment, { children: [_jsxs(Button, { size: "sm", variant: "ghost", title: t('backupManageHint'), onClick: () => setPanelOpen(true), style: controlStyle, children: [_jsx(StatusIcon, { state: overallState(status) }), _jsxs("span", { style: { minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, children: [t('backup'), ": ", backupLabel] })] }), panelOpen && (_jsx(BackupPanel, { api: props.api, t: t, status: status, onClose: () => {
|
|
53
|
+
return (_jsxs(_Fragment, { children: [_jsxs(Button, { size: "sm", variant: "ghost", title: t('backupManageHint'), onClick: () => setPanelOpen(true), style: controlStyle, children: [_jsx(StatusIcon, { state: overallState(status?.channels ?? null, Date.now()) }), _jsxs("span", { style: { minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, children: [t('backup'), ": ", backupLabel] })] }), panelOpen && (_jsx(BackupPanel, { api: props.api, t: t, status: status, onClose: () => {
|
|
63
54
|
setPanelOpen(false);
|
|
64
55
|
void load();
|
|
65
56
|
} }))] }));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StatusDock.js","sourceRoot":"","sources":["../../src/client/StatusDock.tsx"],"names":[],"mappings":";AAAA;;;;GAIG;AAEH,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AACxD,OAAO,EAAE,MAAM,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,uCAAuC,CAAA;AAExG,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAmB,CAAA;
|
|
1
|
+
{"version":3,"file":"StatusDock.js","sourceRoot":"","sources":["../../src/client/StatusDock.tsx"],"names":[],"mappings":";AAAA;;;;GAIG;AAEH,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AACxD,OAAO,EAAE,MAAM,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,uCAAuC,CAAA;AAExG,OAAO,EAAE,YAAY,EAAY,MAAM,aAAa,CAAA;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAmB,CAAA;AAI/C,MAAM,YAAY,GAAwB;IACxC,OAAO,EAAE,aAAa;IACtB,UAAU,EAAE,QAAQ;IACpB,GAAG,EAAE,CAAC;IACN,QAAQ,EAAE,GAAG;IACb,QAAQ,EAAE,QAAQ;IAClB,YAAY,EAAE,UAAU;IACxB,UAAU,EAAE,QAAQ;CACrB,CAAA;AAED,MAAM,SAAS,GAAwB;IACrC,IAAI,EAAE,kCAAkC;IACxC,OAAO,EAAE,kCAAkC;IAC3C,KAAK,EAAE,iCAAiC;IACxC,OAAO,EAAE,iCAAiC;CAC3C,CAAA;AAED,+DAA+D;AAC/D,SAAS,UAAU,CAAC,KAAqB;IACvC,OAAO,CACL,eAAM,KAAK,EAAE,EAAE,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,YAClF,KAAK,CAAC,KAAK,KAAK,MAAM;YACrB,CAAC,CAAC,KAAC,kBAAkB,IAAC,IAAI,EAAE,EAAE,GAAI;YAClC,CAAC,CAAC,KAAC,oBAAoB,IAAC,IAAI,EAAE,EAAE,GAAI,GACjC,CACR,CAAA;AACH,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,KAAuC;IAChE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAoB,IAAI,CAAC,CAAA;IAC7D,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IAEjD,MAAM,IAAI,GAAG,WAAW,CAAC,KAAK,IAAmB,EAAE;QACjD,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,CAAA;YACtC,SAAS,CAAC,KAAK,CAAC,CAAA;QAClB,CAAC;QAAC,MAAM,CAAC;YACP,kEAAkE;QACpE,CAAC;IACH,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAA;IAEf,SAAS,CAAC,GAAG,EAAE;QACb,KAAK,IAAI,EAAE,CAAA;QACX,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,EAAE,EAAE,MAAM,CAAC,CAAA;QACpD,OAAO,GAAG,EAAE,CAAC,aAAa,CAAC,KAAK,CAAC,CAAA;IACnC,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAA;IAEV,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,CAAA;IACjB,MAAM,WAAW,GAAG,MAAM,EAAE,MAAM,KAAK,IAAI,IAAI,MAAM,EAAE,MAAM,KAAK,SAAS;QACzE,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE;QACpD,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAA;IAEnB,OAAO,CACL,8BACE,MAAC,MAAM,IACL,IAAI,EAAC,IAAI,EACT,OAAO,EAAC,OAAO,EACf,KAAK,EAAE,CAAC,CAAC,kBAAkB,CAAC,EAC5B,OAAO,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,EACjC,KAAK,EAAE,YAAY,aAEnB,KAAC,UAAU,IAAC,KAAK,EAAE,YAAY,CAAC,MAAM,EAAE,QAAQ,IAAI,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,GAAI,EACzE,gBAAM,KAAK,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE,aAC7F,CAAC,CAAC,QAAQ,CAAC,QAAI,WAAW,IACtB,IACA,EACR,SAAS,IAAI,CACZ,KAAC,WAAW,IACV,GAAG,EAAE,KAAK,CAAC,GAAG,EACd,CAAC,EAAE,CAAC,EACJ,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,GAAG,EAAE;oBACZ,YAAY,CAAC,KAAK,CAAC,CAAA;oBACnB,KAAK,IAAI,EAAE,CAAA;gBACb,CAAC,GACD,CACH,IACA,CACJ,CAAA;AACH,CAAC"}
|
package/lib/client/api.d.ts
CHANGED
|
@@ -7,6 +7,8 @@ export interface ChannelHealthView {
|
|
|
7
7
|
channel: string;
|
|
8
8
|
state: ChannelState;
|
|
9
9
|
failures: number;
|
|
10
|
+
/** Epoch ms when the channel's cooldown ends; `0` when not cooling. */
|
|
11
|
+
cooldownUntilMs: number;
|
|
10
12
|
}
|
|
11
13
|
export interface StatusView {
|
|
12
14
|
current: string | null;
|
package/lib/client/api.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/client/api.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/client/api.ts"],"names":[],"mappings":"AAAA;;;GAGG;AA0CH,MAAM,UAAU,SAAS,CAAC,GAAQ;IAChC,MAAM,IAAI,GAAG,KAAK,EAAK,QAAgB,EAAE,OAAiB,EAAc,EAAE;QACxE,MAAM,MAAM,GAAG,CAAC,MAAM,GAAG,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAiB,CAAA;QAC7D,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,OAAO,IAAI,cAAc,QAAQ,SAAS,CAAC,CAAA;QAC3E,CAAC;QACD,OAAO,MAAM,CAAC,KAAU,CAAA;IAC1B,CAAC,CAAA;IAED,OAAO;QACL,MAAM,EAAE,GAAG,EAAE,CAAC,IAAI,CAAa,QAAQ,CAAC;QACxC,cAAc,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,IAAI,CAAkB,gBAAgB,EAAE,EAAE,QAAQ,EAAE,CAAC;QACnF,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE;YACnC,MAAM,IAAI,CAAkB,WAAW,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAA;QAC/D,CAAC;QACD,WAAW,EAAE,KAAK,IAAI,EAAE;YACtB,MAAM,IAAI,CAAkB,aAAa,CAAC,CAAA;QAC5C,CAAC;KACF,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Time-aware channel-health classification for the status dock.
|
|
3
|
+
*
|
|
4
|
+
* The host exposes no request-success event, so a channel whose cooldown has
|
|
5
|
+
* expired keeps its `COOLDOWN` state field forever — it becomes routable again
|
|
6
|
+
* (`canRouteNow`) without ever flipping back to `HEALTHY`. The dock therefore
|
|
7
|
+
* must judge by time, not by the raw state field: only a channel that is
|
|
8
|
+
* *still* cooling (cooldown not yet expired) counts as an error.
|
|
9
|
+
*/
|
|
10
|
+
import type { ChannelHealthView } from './api.ts';
|
|
11
|
+
export type Dot = 'done' | 'warning' | 'ongoing' | 'error';
|
|
12
|
+
/**
|
|
13
|
+
* Overall dock state:
|
|
14
|
+
* - `error` — at least one channel is still cooling (`cooldownUntilMs > now`).
|
|
15
|
+
* - `warning` — a probe is in flight (kept for parity; the adapter no longer
|
|
16
|
+
* enters PROBE, but persisted legacy states may surface it).
|
|
17
|
+
* - `done` — everything routable: healthy, or cooled down long enough.
|
|
18
|
+
* - `ongoing` — no snapshot yet.
|
|
19
|
+
*/
|
|
20
|
+
export declare function overallState(channels: readonly ChannelHealthView[] | null, nowMs: number): Dot;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Time-aware channel-health classification for the status dock.
|
|
3
|
+
*
|
|
4
|
+
* The host exposes no request-success event, so a channel whose cooldown has
|
|
5
|
+
* expired keeps its `COOLDOWN` state field forever — it becomes routable again
|
|
6
|
+
* (`canRouteNow`) without ever flipping back to `HEALTHY`. The dock therefore
|
|
7
|
+
* must judge by time, not by the raw state field: only a channel that is
|
|
8
|
+
* *still* cooling (cooldown not yet expired) counts as an error.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Overall dock state:
|
|
12
|
+
* - `error` — at least one channel is still cooling (`cooldownUntilMs > now`).
|
|
13
|
+
* - `warning` — a probe is in flight (kept for parity; the adapter no longer
|
|
14
|
+
* enters PROBE, but persisted legacy states may surface it).
|
|
15
|
+
* - `done` — everything routable: healthy, or cooled down long enough.
|
|
16
|
+
* - `ongoing` — no snapshot yet.
|
|
17
|
+
*/
|
|
18
|
+
export function overallState(channels, nowMs) {
|
|
19
|
+
if (channels === null)
|
|
20
|
+
return 'ongoing';
|
|
21
|
+
if (channels.some((c) => c.state === 'COOLDOWN' && c.cooldownUntilMs > nowMs))
|
|
22
|
+
return 'error';
|
|
23
|
+
if (channels.some((c) => c.state === 'PROBE'))
|
|
24
|
+
return 'warning';
|
|
25
|
+
return 'done';
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=health.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"health.js","sourceRoot":"","sources":["../../src/client/health.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAC1B,QAA6C,EAC7C,KAAa;IAEb,IAAI,QAAQ,KAAK,IAAI;QAAE,OAAO,SAAS,CAAA;IACvC,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,UAAU,IAAI,CAAC,CAAC,eAAe,GAAG,KAAK,CAAC;QAAE,OAAO,OAAO,CAAA;IAC7F,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,OAAO,CAAC;QAAE,OAAO,SAAS,CAAA;IAC/D,OAAO,MAAM,CAAA;AACf,CAAC"}
|
package/lib/client.js
CHANGED
|
@@ -88,6 +88,31 @@ window.__ModuleLoader__.load({
|
|
|
88
88
|
};
|
|
89
89
|
}
|
|
90
90
|
//#endregion
|
|
91
|
+
//#region lib/client/health.js
|
|
92
|
+
/**
|
|
93
|
+
* Time-aware channel-health classification for the status dock.
|
|
94
|
+
*
|
|
95
|
+
* The host exposes no request-success event, so a channel whose cooldown has
|
|
96
|
+
* expired keeps its `COOLDOWN` state field forever — it becomes routable again
|
|
97
|
+
* (`canRouteNow`) without ever flipping back to `HEALTHY`. The dock therefore
|
|
98
|
+
* must judge by time, not by the raw state field: only a channel that is
|
|
99
|
+
* *still* cooling (cooldown not yet expired) counts as an error.
|
|
100
|
+
*/
|
|
101
|
+
/**
|
|
102
|
+
* Overall dock state:
|
|
103
|
+
* - `error` — at least one channel is still cooling (`cooldownUntilMs > now`).
|
|
104
|
+
* - `warning` — a probe is in flight (kept for parity; the adapter no longer
|
|
105
|
+
* enters PROBE, but persisted legacy states may surface it).
|
|
106
|
+
* - `done` — everything routable: healthy, or cooled down long enough.
|
|
107
|
+
* - `ongoing` — no snapshot yet.
|
|
108
|
+
*/
|
|
109
|
+
function overallState(channels, nowMs) {
|
|
110
|
+
if (channels === null) return "ongoing";
|
|
111
|
+
if (channels.some((c) => c.state === "COOLDOWN" && c.cooldownUntilMs > nowMs)) return "error";
|
|
112
|
+
if (channels.some((c) => c.state === "PROBE")) return "warning";
|
|
113
|
+
return "done";
|
|
114
|
+
}
|
|
115
|
+
//#endregion
|
|
91
116
|
//#region lib/client/BackupPanel.js
|
|
92
117
|
/**
|
|
93
118
|
* BackupChannelPanel: list candidate models for a provider and pick one as
|
|
@@ -148,7 +173,7 @@ window.__ModuleLoader__.load({
|
|
|
148
173
|
};
|
|
149
174
|
/** One candidate row, memoized so selection changes only re-render it. */
|
|
150
175
|
const CandidateRow = (0, react.memo)(function CandidateRow(props) {
|
|
151
|
-
const { candidate, isBackup, t, onSetBackup } = props;
|
|
176
|
+
const { candidate, isBackup, busy, t, onSetBackup } = props;
|
|
152
177
|
return (0, react_jsx_runtime.jsxs)("div", {
|
|
153
178
|
style: candidateRowStyle,
|
|
154
179
|
children: [
|
|
@@ -173,7 +198,7 @@ window.__ModuleLoader__.load({
|
|
|
173
198
|
(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
174
199
|
size: "sm",
|
|
175
200
|
variant: "ghost",
|
|
176
|
-
disabled: isBackup,
|
|
201
|
+
disabled: isBackup || busy,
|
|
177
202
|
onClick: () => onSetBackup(candidate.model),
|
|
178
203
|
children: t("setBackup")
|
|
179
204
|
})
|
|
@@ -224,12 +249,15 @@ window.__ModuleLoader__.load({
|
|
|
224
249
|
}
|
|
225
250
|
};
|
|
226
251
|
const clearBackup = async () => {
|
|
252
|
+
setBusy("__clear__");
|
|
227
253
|
setError(null);
|
|
228
254
|
try {
|
|
229
255
|
await api.clearBackup();
|
|
230
256
|
setBackupOverride(null);
|
|
231
257
|
} catch (cause) {
|
|
232
258
|
setError(cause instanceof Error ? cause.message : String(cause));
|
|
259
|
+
} finally {
|
|
260
|
+
setBusy(null);
|
|
233
261
|
}
|
|
234
262
|
};
|
|
235
263
|
const handleSetBackup = (0, react.useCallback)((model) => void setBackup(model), [provider]);
|
|
@@ -286,6 +314,7 @@ window.__ModuleLoader__.load({
|
|
|
286
314
|
size: "sm",
|
|
287
315
|
variant: "ghost",
|
|
288
316
|
style: { flexShrink: 0 },
|
|
317
|
+
disabled: busy !== null,
|
|
289
318
|
onClick: () => void clearBackup(),
|
|
290
319
|
children: t("clearBackup")
|
|
291
320
|
})
|
|
@@ -346,6 +375,7 @@ window.__ModuleLoader__.load({
|
|
|
346
375
|
children: candidates.map((candidate) => (0, react_jsx_runtime.jsx)(CandidateRow, {
|
|
347
376
|
candidate,
|
|
348
377
|
isBackup: candidate.model === backupModel,
|
|
378
|
+
busy: busy !== null,
|
|
349
379
|
t,
|
|
350
380
|
onSetBackup: handleSetBackup
|
|
351
381
|
}, candidate.model))
|
|
@@ -371,13 +401,6 @@ window.__ModuleLoader__.load({
|
|
|
371
401
|
textOverflow: "ellipsis",
|
|
372
402
|
whiteSpace: "nowrap"
|
|
373
403
|
};
|
|
374
|
-
/** Overall health: any cooling channel → error, any probe → warning, else done. */
|
|
375
|
-
function overallState(status) {
|
|
376
|
-
if (status === null) return "ongoing";
|
|
377
|
-
if (status.channels.some((c) => c.state === "COOLDOWN")) return "error";
|
|
378
|
-
if (status.channels.some((c) => c.state === "PROBE")) return "warning";
|
|
379
|
-
return "done";
|
|
380
|
-
}
|
|
381
404
|
const iconColor = {
|
|
382
405
|
done: "var(--dsw-text-success, #30a46c)",
|
|
383
406
|
warning: "var(--dsw-text-warning, #f5a524)",
|
|
@@ -417,7 +440,7 @@ window.__ModuleLoader__.load({
|
|
|
417
440
|
title: t("backupManageHint"),
|
|
418
441
|
onClick: () => setPanelOpen(true),
|
|
419
442
|
style: controlStyle,
|
|
420
|
-
children: [(0, react_jsx_runtime.jsx)(StatusIcon, { state: overallState(status) }), (0, react_jsx_runtime.jsxs)("span", {
|
|
443
|
+
children: [(0, react_jsx_runtime.jsx)(StatusIcon, { state: overallState(status?.channels ?? null, Date.now()) }), (0, react_jsx_runtime.jsxs)("span", {
|
|
421
444
|
style: {
|
|
422
445
|
minWidth: 0,
|
|
423
446
|
overflow: "hidden",
|
package/lib/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","names":["memo","_jsxs","_jsx","Pill","Button","useState","useMemo","useCallback","Modal","_Fragment","_jsx","IconCheckOutline16","IconWarningOutline16","useState","useCallback","_jsxs","_Fragment","Button","h"],"sources":["client/locales.js","client/api.js","client/BackupPanel.js","client/StatusDock.js","client/index.js"],"sourcesContent":["/** zh/en dictionaries for the daruma client UI. */\nexport const zh = {\n status: '渠道状态',\n current: '当前',\n backup: '备用',\n backupNone: '未设置',\n backupManage: '备用渠道',\n backupManageHint: '从候选列表选择备用渠道',\n failoverCount: '已切换',\n lastFailover: '上次切换',\n healthy: '健康',\n cooldown: '冷却中',\n probe: '探测中',\n refresh: '刷新',\n close: '关闭',\n clearBackup: '清除备用',\n provider: '提供方',\n providerPlaceholder: 'provider,如 mt',\n loadCandidates: '加载候选',\n loading: '加载中…',\n candidates: '候选模型',\n setBackup: '设为备用',\n currentBackup: '当前备用',\n currentBackupMark: '备用',\n noCandidates: '未加载到候选模型',\n empty: '暂无渠道数据',\n};\nexport const en = {\n status: 'Channel status',\n current: 'Current',\n backup: 'Backup',\n backupNone: 'none',\n backupManage: 'Backup channel',\n backupManageHint: 'Pick a backup channel from the candidate list',\n failoverCount: 'failovers',\n lastFailover: 'last failover',\n healthy: 'healthy',\n cooldown: 'cooling',\n probe: 'probing',\n refresh: 'Refresh',\n close: 'Close',\n clearBackup: 'Clear backup',\n provider: 'Provider',\n providerPlaceholder: 'provider, e.g. mt',\n loadCandidates: 'Load candidates',\n loading: 'Loading…',\n candidates: 'Candidate models',\n setBackup: 'Set as backup',\n currentBackup: 'Current backup',\n currentBackupMark: 'backup',\n noCandidates: 'No candidates loaded',\n empty: 'No channel data yet',\n};\n//# sourceMappingURL=locales.js.map","/**\n * Client-side wire types and RPC wrapper for the `/dsh-daruma` channel.\n * Structural types only — no monorepo-internal imports.\n */\nexport function createApi(rpc) {\n const call = async (endpoint, payload) => {\n const result = (await rpc(endpoint, payload));\n if (!result.ok) {\n throw new Error(result.error?.message ?? `daruma rpc ${endpoint} failed`);\n }\n return result.value;\n };\n return {\n status: () => call('status'),\n listCandidates: (provider) => call('listCandidates', { provider }),\n setBackup: async (provider, model) => {\n await call('setBackup', { provider, model });\n },\n clearBackup: async () => {\n await call('clearBackup');\n },\n };\n}\n//# sourceMappingURL=api.js.map","import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from \"react/jsx-runtime\";\n/**\n * BackupChannelPanel: list candidate models for a provider and pick one as\n * the failover backup. Rendered inside a primitives Modal.\n *\n * No probing happens here anymore — the panel is a plain picker. Channel\n * health still updates from real traffic via the recovery engine.\n */\nimport { memo, useCallback, useEffect, useMemo, useState } from 'react';\nimport { Button, Modal, Pill, } from '@deepseek-ai/dsh-client-ui-primitives';\nfunction deriveProvider(status) {\n if (status === null)\n return '';\n if (status.current !== null)\n return status.current.split('::')[0] ?? '';\n if (status.backup !== null)\n return status.backup.provider;\n return status.providers[0] ?? '';\n}\nconst sectionStyle = {\n display: 'flex',\n flexDirection: 'column',\n gap: '8px',\n padding: '12px 0',\n fontSize: '13px',\n};\nconst rowStyle = {\n display: 'flex',\n alignItems: 'center',\n gap: '8px',\n};\nconst noteStyle = { color: 'var(--dsw-text-tertiary, #999)', fontSize: '12px' };\n/** Clamp a long provider/model name so the row never wraps. */\nconst backupPillStyle = {\n flex: '1 1 auto',\n minWidth: 0,\n overflow: 'hidden',\n};\nconst ellipsisStyle = {\n minWidth: 0,\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n};\nconst listStyle = {\n maxHeight: 300,\n overflowY: 'auto',\n display: 'flex',\n flexDirection: 'column',\n gap: 4,\n contain: 'content',\n willChange: 'scroll-position',\n overscrollBehavior: 'contain',\n};\n/** Skip rendering rows outside the viewport while scrolling. */\nconst candidateRowStyle = {\n ...rowStyle,\n contentVisibility: 'auto',\n containIntrinsicSize: '28px',\n};\n/** One candidate row, memoized so selection changes only re-render it. */\nconst CandidateRow = memo(function CandidateRow(props) {\n const { candidate, isBackup, t, onSetBackup } = props;\n return (_jsxs(\"div\", { style: candidateRowStyle, children: [_jsx(\"span\", { style: { width: 14, flexShrink: 0 } }), _jsx(\"span\", { style: { flex: 1, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, children: candidate.model }), isBackup && _jsx(Pill, { active: true, children: t('currentBackupMark') }), _jsx(Button, { size: \"sm\", variant: \"ghost\", disabled: isBackup, onClick: () => onSetBackup(candidate.model), children: t('setBackup') })] }));\n});\nexport function BackupPanel(props) {\n const { api, t, status, onClose } = props;\n const [provider, setProvider] = useState(deriveProvider(status));\n const [candidates, setCandidates] = useState(null);\n const [loading, setLoading] = useState(false);\n const [busy, setBusy] = useState(null);\n const [error, setError] = useState(null);\n // Local overlay on the status-prop backup: applied optimistically after\n // set/clear so the row reflects the change without waiting for the parent\n // to re-fetch status. Cleared whenever a fresh status arrives.\n const [backupOverride, setBackupOverride] = useState(undefined);\n // The effective backup shown in the panel: local override → status prop.\n const backupShown = backupOverride !== undefined ? backupOverride : status?.backup ?? null;\n // O(1) lookup for the current backup model; re-built only when the effective backup changes.\n const backupModel = useMemo(() => (backupShown !== null && backupShown.provider === provider ? backupShown.model : null), [backupShown, provider]);\n const loadCandidates = async () => {\n if (provider === '')\n return;\n setLoading(true);\n setError(null);\n try {\n setCandidates(await api.listCandidates(provider));\n }\n catch (cause) {\n setError(cause instanceof Error ? cause.message : String(cause));\n }\n finally {\n setLoading(false);\n }\n };\n useEffect(() => {\n if (provider !== '')\n void loadCandidates();\n }, [provider]);\n // A fresh status from the parent supersedes the local override.\n useEffect(() => {\n setBackupOverride(undefined);\n }, [status]);\n const setBackup = async (model) => {\n setBusy(model);\n setError(null);\n try {\n await api.setBackup(provider, model);\n setBackupOverride({ provider, model });\n }\n catch (cause) {\n setError(cause instanceof Error ? cause.message : String(cause));\n }\n finally {\n setBusy(null);\n }\n };\n const clearBackup = async () => {\n setError(null);\n try {\n await api.clearBackup();\n setBackupOverride(null);\n }\n catch (cause) {\n setError(cause instanceof Error ? cause.message : String(cause));\n }\n };\n // Stable callback so memoized CandidateRow props don't change every render.\n const handleSetBackup = useCallback((model) => void setBackup(model), [provider]);\n return (_jsx(Modal, { open: true, onClose: onClose, title: t('backupManage'), className: \"daruma-wide-dialog\", closeLabel: t('close'), description: t('backupManageHint'), footer: _jsx(Button, { variant: \"primary\", onClick: onClose, children: t('close') }), children: _jsxs(\"div\", { style: sectionStyle, children: [status !== null && status.failoverCount > 0 && (_jsxs(\"div\", { style: noteStyle, children: [t('failoverCount'), \": \", status.failoverCount, status.failoverHistory.length > 0 && (_jsxs(_Fragment, { children: [' · ', t('lastFailover'), \": \", status.failoverHistory.map((h) => `${h.from} → ${h.to}`).join(' · ')] }))] })), backupShown !== null && (_jsxs(\"div\", { style: rowStyle, children: [_jsxs(\"span\", { style: { flexShrink: 0 }, children: [t('currentBackup'), \":\"] }), _jsx(Pill, { active: true, style: backupPillStyle, title: `${backupShown.provider}/${backupShown.model}`, children: _jsxs(\"span\", { style: ellipsisStyle, children: [backupShown.provider, \"/\", backupShown.model] }) }), _jsx(Button, { size: \"sm\", variant: \"ghost\", style: { flexShrink: 0 }, onClick: () => void clearBackup(), children: t('clearBackup') })] })), _jsxs(\"div\", { style: rowStyle, children: [(status?.providers ?? []).length > 0 ? (_jsx(\"select\", { value: provider, disabled: loading, onChange: (event) => setProvider(event.currentTarget.value), style: { flex: 1, padding: '4px 8px', fontSize: 13 }, children: status?.providers.map((name) => (_jsx(\"option\", { value: name, children: name }, name))) })) : (_jsx(\"input\", { value: provider, placeholder: t('providerPlaceholder'), onChange: (event) => setProvider(event.currentTarget.value), style: { flex: 1, padding: '4px 8px', fontSize: 13 } })), _jsx(Button, { size: \"sm\", variant: \"outline\", onClick: () => void loadCandidates(), disabled: provider === '' || loading, children: loading ? t('loading') : t('loadCandidates') })] }), error !== null && _jsx(\"div\", { style: { color: 'var(--dsw-text-danger, #e5484d)' }, children: error }), candidates !== null && (_jsxs(_Fragment, { children: [_jsx(\"div\", { style: rowStyle, children: _jsxs(\"span\", { children: [t('candidates'), \" (\", candidates.length, \")\"] }) }), candidates.length === 0 && _jsx(\"div\", { style: noteStyle, children: t('noCandidates') }), _jsx(\"div\", { style: listStyle, children: candidates.map((candidate) => (_jsx(CandidateRow, { candidate: candidate, isBackup: candidate.model === backupModel, t: t, onSetBackup: handleSetBackup }, candidate.model))) })] }))] }) }));\n}\n//# sourceMappingURL=BackupPanel.js.map","import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from \"react/jsx-runtime\";\n/**\n * DarumaStatus: a compact control at the right end of the composer tool row,\n * next to the model selector. Shows overall channel health and the backup\n * selection; clicking it opens the backup-channel panel.\n */\nimport { useCallback, useEffect, useState } from 'react';\nimport { Button, IconCheckOutline16, IconWarningOutline16 } from '@deepseek-ai/dsh-client-ui-primitives';\nimport { BackupPanel } from \"./BackupPanel.js\";\nconst controlStyle = {\n display: 'inline-flex',\n alignItems: 'center',\n gap: 6,\n maxWidth: 220,\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n};\n/** Overall health: any cooling channel → error, any probe → warning, else done. */\nfunction overallState(status) {\n if (status === null)\n return 'ongoing';\n if (status.channels.some((c) => c.state === 'COOLDOWN'))\n return 'error';\n if (status.channels.some((c) => c.state === 'PROBE'))\n return 'warning';\n return 'done';\n}\nconst iconColor = {\n done: 'var(--dsw-text-success, #30a46c)',\n warning: 'var(--dsw-text-warning, #f5a524)',\n error: 'var(--dsw-text-danger, #e5484d)',\n ongoing: 'var(--dsw-text-secondary, #888)',\n};\n/** Status glyph: a check when healthy, a warning otherwise. */\nfunction StatusIcon(props) {\n return (_jsx(\"span\", { style: { display: 'inline-flex', color: iconColor[props.state], flexShrink: 0 }, children: props.state === 'done'\n ? _jsx(IconCheckOutline16, { size: 14 })\n : _jsx(IconWarningOutline16, { size: 14 }) }));\n}\nexport function StatusDock(props) {\n const [status, setStatus] = useState(null);\n const [panelOpen, setPanelOpen] = useState(false);\n const load = useCallback(async () => {\n try {\n const value = await props.api.status();\n setStatus(value);\n }\n catch {\n // keep last good snapshot; the control stays quiet on RPC failure\n }\n }, [props.api]);\n useEffect(() => {\n void load();\n const timer = setInterval(() => void load(), 30_000);\n return () => clearInterval(timer);\n }, [load]);\n const t = props.t;\n const backupLabel = status?.backup !== null && status?.backup !== undefined\n ? `${status.backup.provider}/${status.backup.model}`\n : t('backupNone');\n return (_jsxs(_Fragment, { children: [_jsxs(Button, { size: \"sm\", variant: \"ghost\", title: t('backupManageHint'), onClick: () => setPanelOpen(true), style: controlStyle, children: [_jsx(StatusIcon, { state: overallState(status) }), _jsxs(\"span\", { style: { minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, children: [t('backup'), \": \", backupLabel] })] }), panelOpen && (_jsx(BackupPanel, { api: props.api, t: t, status: status, onClose: () => {\n setPanelOpen(false);\n void load();\n } }))] }));\n}\n//# sourceMappingURL=StatusDock.js.map","/**\n * dsh-daruma client: registers a compact channel-status dock above the\n * composer (conversation.input.dock) with a \"backup channel\" panel that\n * lists candidate models and picks a backup. Built by tsdown into the\n * __ModuleLoader__ factory bundle at lib/client.js.\n */\nimport { createElement as h } from 'react';\nimport { en, zh } from \"./locales.js\";\nimport { createApi } from \"./api.js\";\nimport { StatusDock } from \"./StatusDock.js\";\nconst NS = 'daruma';\nexport const name = 'dsh-daruma';\nexport const inject = ['slots', 'locale'];\nexport function apply(ctx) {\n // Widen the backup-panel dialog: the primitives Modal card is fixed at\n // min(380px, 100%), which truncates long provider/model names. A global\n // class (not a CSS module) outranks the module-scoped `.dialog` rule.\n if (typeof document !== 'undefined') {\n const tagId = 'daruma-panel-style';\n if (document.querySelector(`style[data-daruma=\"${tagId}\"]`) === null) {\n const style = document.createElement('style');\n style.dataset.daruma = tagId;\n style.textContent = '.daruma-wide-dialog.daruma-wide-dialog { width: min(560px, 100%); }';\n document.head.appendChild(style);\n }\n }\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'dsh-daruma: dictionaries');\n const t = ctx.locale.bind(NS);\n const connection = ctx.get('connection');\n if (connection === undefined) {\n console.warn('[dsh-daruma] web transport unavailable — client UI disabled');\n return;\n }\n const rpc = (endpoint, payload) => connection.rpc.call('/dsh-daruma', endpoint, payload ?? {});\n const api = createApi(rpc);\n ctx.slots.inject('conversation.input.right', () => ctx.slots.register({\n name: 'conversation.input.right',\n id: 'daruma-status',\n order: 0,\n inject: () => ({ api, t }),\n }, () => h(StatusDock, { api, t })));\n}\n//# sourceMappingURL=index.js.map"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"client.js","names":["memo","_jsxs","_jsx","Pill","Button","useState","useMemo","useCallback","Modal","_Fragment","_jsx","IconCheckOutline16","IconWarningOutline16","useState","useCallback","_jsxs","_Fragment","Button","h"],"sources":["client/locales.js","client/api.js","client/health.js","client/BackupPanel.js","client/StatusDock.js","client/index.js"],"sourcesContent":["/** zh/en dictionaries for the daruma client UI. */\nexport const zh = {\n status: '渠道状态',\n current: '当前',\n backup: '备用',\n backupNone: '未设置',\n backupManage: '备用渠道',\n backupManageHint: '从候选列表选择备用渠道',\n failoverCount: '已切换',\n lastFailover: '上次切换',\n healthy: '健康',\n cooldown: '冷却中',\n probe: '探测中',\n refresh: '刷新',\n close: '关闭',\n clearBackup: '清除备用',\n provider: '提供方',\n providerPlaceholder: 'provider,如 mt',\n loadCandidates: '加载候选',\n loading: '加载中…',\n candidates: '候选模型',\n setBackup: '设为备用',\n currentBackup: '当前备用',\n currentBackupMark: '备用',\n noCandidates: '未加载到候选模型',\n empty: '暂无渠道数据',\n};\nexport const en = {\n status: 'Channel status',\n current: 'Current',\n backup: 'Backup',\n backupNone: 'none',\n backupManage: 'Backup channel',\n backupManageHint: 'Pick a backup channel from the candidate list',\n failoverCount: 'failovers',\n lastFailover: 'last failover',\n healthy: 'healthy',\n cooldown: 'cooling',\n probe: 'probing',\n refresh: 'Refresh',\n close: 'Close',\n clearBackup: 'Clear backup',\n provider: 'Provider',\n providerPlaceholder: 'provider, e.g. mt',\n loadCandidates: 'Load candidates',\n loading: 'Loading…',\n candidates: 'Candidate models',\n setBackup: 'Set as backup',\n currentBackup: 'Current backup',\n currentBackupMark: 'backup',\n noCandidates: 'No candidates loaded',\n empty: 'No channel data yet',\n};\n//# sourceMappingURL=locales.js.map","/**\n * Client-side wire types and RPC wrapper for the `/dsh-daruma` channel.\n * Structural types only — no monorepo-internal imports.\n */\nexport function createApi(rpc) {\n const call = async (endpoint, payload) => {\n const result = (await rpc(endpoint, payload));\n if (!result.ok) {\n throw new Error(result.error?.message ?? `daruma rpc ${endpoint} failed`);\n }\n return result.value;\n };\n return {\n status: () => call('status'),\n listCandidates: (provider) => call('listCandidates', { provider }),\n setBackup: async (provider, model) => {\n await call('setBackup', { provider, model });\n },\n clearBackup: async () => {\n await call('clearBackup');\n },\n };\n}\n//# sourceMappingURL=api.js.map","/**\n * Time-aware channel-health classification for the status dock.\n *\n * The host exposes no request-success event, so a channel whose cooldown has\n * expired keeps its `COOLDOWN` state field forever — it becomes routable again\n * (`canRouteNow`) without ever flipping back to `HEALTHY`. The dock therefore\n * must judge by time, not by the raw state field: only a channel that is\n * *still* cooling (cooldown not yet expired) counts as an error.\n */\n/**\n * Overall dock state:\n * - `error` — at least one channel is still cooling (`cooldownUntilMs > now`).\n * - `warning` — a probe is in flight (kept for parity; the adapter no longer\n * enters PROBE, but persisted legacy states may surface it).\n * - `done` — everything routable: healthy, or cooled down long enough.\n * - `ongoing` — no snapshot yet.\n */\nexport function overallState(channels, nowMs) {\n if (channels === null)\n return 'ongoing';\n if (channels.some((c) => c.state === 'COOLDOWN' && c.cooldownUntilMs > nowMs))\n return 'error';\n if (channels.some((c) => c.state === 'PROBE'))\n return 'warning';\n return 'done';\n}\n//# sourceMappingURL=health.js.map","import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from \"react/jsx-runtime\";\n/**\n * BackupChannelPanel: list candidate models for a provider and pick one as\n * the failover backup. Rendered inside a primitives Modal.\n *\n * No probing happens here anymore — the panel is a plain picker. Channel\n * health still updates from real traffic via the recovery engine.\n */\nimport { memo, useCallback, useEffect, useMemo, useState } from 'react';\nimport { Button, Modal, Pill, } from '@deepseek-ai/dsh-client-ui-primitives';\nfunction deriveProvider(status) {\n if (status === null)\n return '';\n if (status.current !== null)\n return status.current.split('::')[0] ?? '';\n if (status.backup !== null)\n return status.backup.provider;\n return status.providers[0] ?? '';\n}\nconst sectionStyle = {\n display: 'flex',\n flexDirection: 'column',\n gap: '8px',\n padding: '12px 0',\n fontSize: '13px',\n};\nconst rowStyle = {\n display: 'flex',\n alignItems: 'center',\n gap: '8px',\n};\nconst noteStyle = { color: 'var(--dsw-text-tertiary, #999)', fontSize: '12px' };\n/** Clamp a long provider/model name so the row never wraps. */\nconst backupPillStyle = {\n flex: '1 1 auto',\n minWidth: 0,\n overflow: 'hidden',\n};\nconst ellipsisStyle = {\n minWidth: 0,\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n};\nconst listStyle = {\n maxHeight: 300,\n overflowY: 'auto',\n display: 'flex',\n flexDirection: 'column',\n gap: 4,\n contain: 'content',\n willChange: 'scroll-position',\n overscrollBehavior: 'contain',\n};\n/** Skip rendering rows outside the viewport while scrolling. */\nconst candidateRowStyle = {\n ...rowStyle,\n contentVisibility: 'auto',\n containIntrinsicSize: '28px',\n};\n/** One candidate row, memoized so selection changes only re-render it. */\nconst CandidateRow = memo(function CandidateRow(props) {\n const { candidate, isBackup, busy, t, onSetBackup } = props;\n return (_jsxs(\"div\", { style: candidateRowStyle, children: [_jsx(\"span\", { style: { width: 14, flexShrink: 0 } }), _jsx(\"span\", { style: { flex: 1, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, children: candidate.model }), isBackup && _jsx(Pill, { active: true, children: t('currentBackupMark') }), _jsx(Button, { size: \"sm\", variant: \"ghost\", disabled: isBackup || busy, onClick: () => onSetBackup(candidate.model), children: t('setBackup') })] }));\n});\nexport function BackupPanel(props) {\n const { api, t, status, onClose } = props;\n const [provider, setProvider] = useState(deriveProvider(status));\n const [candidates, setCandidates] = useState(null);\n const [loading, setLoading] = useState(false);\n const [busy, setBusy] = useState(null);\n const [error, setError] = useState(null);\n // Local overlay on the status-prop backup: applied optimistically after\n // set/clear so the row reflects the change without waiting for the parent\n // to re-fetch status. Cleared whenever a fresh status arrives.\n const [backupOverride, setBackupOverride] = useState(undefined);\n // The effective backup shown in the panel: local override → status prop.\n const backupShown = backupOverride !== undefined ? backupOverride : status?.backup ?? null;\n // O(1) lookup for the current backup model; re-built only when the effective backup changes.\n const backupModel = useMemo(() => (backupShown !== null && backupShown.provider === provider ? backupShown.model : null), [backupShown, provider]);\n const loadCandidates = async () => {\n if (provider === '')\n return;\n setLoading(true);\n setError(null);\n try {\n setCandidates(await api.listCandidates(provider));\n }\n catch (cause) {\n setError(cause instanceof Error ? cause.message : String(cause));\n }\n finally {\n setLoading(false);\n }\n };\n useEffect(() => {\n if (provider !== '')\n void loadCandidates();\n }, [provider]);\n // A fresh status from the parent supersedes the local override.\n useEffect(() => {\n setBackupOverride(undefined);\n }, [status]);\n const setBackup = async (model) => {\n setBusy(model);\n setError(null);\n try {\n await api.setBackup(provider, model);\n setBackupOverride({ provider, model });\n }\n catch (cause) {\n setError(cause instanceof Error ? cause.message : String(cause));\n }\n finally {\n setBusy(null);\n }\n };\n const clearBackup = async () => {\n setBusy('__clear__');\n setError(null);\n try {\n await api.clearBackup();\n setBackupOverride(null);\n }\n catch (cause) {\n setError(cause instanceof Error ? cause.message : String(cause));\n }\n finally {\n setBusy(null);\n }\n };\n // Stable callback so memoized CandidateRow props don't change every render.\n const handleSetBackup = useCallback((model) => void setBackup(model), [provider]);\n return (_jsx(Modal, { open: true, onClose: onClose, title: t('backupManage'), className: \"daruma-wide-dialog\", closeLabel: t('close'), description: t('backupManageHint'), footer: _jsx(Button, { variant: \"primary\", onClick: onClose, children: t('close') }), children: _jsxs(\"div\", { style: sectionStyle, children: [status !== null && status.failoverCount > 0 && (_jsxs(\"div\", { style: noteStyle, children: [t('failoverCount'), \": \", status.failoverCount, status.failoverHistory.length > 0 && (_jsxs(_Fragment, { children: [' · ', t('lastFailover'), \": \", status.failoverHistory.map((h) => `${h.from} → ${h.to}`).join(' · ')] }))] })), backupShown !== null && (_jsxs(\"div\", { style: rowStyle, children: [_jsxs(\"span\", { style: { flexShrink: 0 }, children: [t('currentBackup'), \":\"] }), _jsx(Pill, { active: true, style: backupPillStyle, title: `${backupShown.provider}/${backupShown.model}`, children: _jsxs(\"span\", { style: ellipsisStyle, children: [backupShown.provider, \"/\", backupShown.model] }) }), _jsx(Button, { size: \"sm\", variant: \"ghost\", style: { flexShrink: 0 }, disabled: busy !== null, onClick: () => void clearBackup(), children: t('clearBackup') })] })), _jsxs(\"div\", { style: rowStyle, children: [(status?.providers ?? []).length > 0 ? (_jsx(\"select\", { value: provider, disabled: loading, onChange: (event) => setProvider(event.currentTarget.value), style: { flex: 1, padding: '4px 8px', fontSize: 13 }, children: status?.providers.map((name) => (_jsx(\"option\", { value: name, children: name }, name))) })) : (_jsx(\"input\", { value: provider, placeholder: t('providerPlaceholder'), onChange: (event) => setProvider(event.currentTarget.value), style: { flex: 1, padding: '4px 8px', fontSize: 13 } })), _jsx(Button, { size: \"sm\", variant: \"outline\", onClick: () => void loadCandidates(), disabled: provider === '' || loading, children: loading ? t('loading') : t('loadCandidates') })] }), error !== null && _jsx(\"div\", { style: { color: 'var(--dsw-text-danger, #e5484d)' }, children: error }), candidates !== null && (_jsxs(_Fragment, { children: [_jsx(\"div\", { style: rowStyle, children: _jsxs(\"span\", { children: [t('candidates'), \" (\", candidates.length, \")\"] }) }), candidates.length === 0 && _jsx(\"div\", { style: noteStyle, children: t('noCandidates') }), _jsx(\"div\", { style: listStyle, children: candidates.map((candidate) => (_jsx(CandidateRow, { candidate: candidate, isBackup: candidate.model === backupModel, busy: busy !== null, t: t, onSetBackup: handleSetBackup }, candidate.model))) })] }))] }) }));\n}\n//# sourceMappingURL=BackupPanel.js.map","import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from \"react/jsx-runtime\";\n/**\n * DarumaStatus: a compact control at the right end of the composer tool row,\n * next to the model selector. Shows overall channel health and the backup\n * selection; clicking it opens the backup-channel panel.\n */\nimport { useCallback, useEffect, useState } from 'react';\nimport { Button, IconCheckOutline16, IconWarningOutline16 } from '@deepseek-ai/dsh-client-ui-primitives';\nimport { overallState } from \"./health.js\";\nimport { BackupPanel } from \"./BackupPanel.js\";\nconst controlStyle = {\n display: 'inline-flex',\n alignItems: 'center',\n gap: 6,\n maxWidth: 220,\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n};\nconst iconColor = {\n done: 'var(--dsw-text-success, #30a46c)',\n warning: 'var(--dsw-text-warning, #f5a524)',\n error: 'var(--dsw-text-danger, #e5484d)',\n ongoing: 'var(--dsw-text-secondary, #888)',\n};\n/** Status glyph: a check when healthy, a warning otherwise. */\nfunction StatusIcon(props) {\n return (_jsx(\"span\", { style: { display: 'inline-flex', color: iconColor[props.state], flexShrink: 0 }, children: props.state === 'done'\n ? _jsx(IconCheckOutline16, { size: 14 })\n : _jsx(IconWarningOutline16, { size: 14 }) }));\n}\nexport function StatusDock(props) {\n const [status, setStatus] = useState(null);\n const [panelOpen, setPanelOpen] = useState(false);\n const load = useCallback(async () => {\n try {\n const value = await props.api.status();\n setStatus(value);\n }\n catch {\n // keep last good snapshot; the control stays quiet on RPC failure\n }\n }, [props.api]);\n useEffect(() => {\n void load();\n const timer = setInterval(() => void load(), 30_000);\n return () => clearInterval(timer);\n }, [load]);\n const t = props.t;\n const backupLabel = status?.backup !== null && status?.backup !== undefined\n ? `${status.backup.provider}/${status.backup.model}`\n : t('backupNone');\n return (_jsxs(_Fragment, { children: [_jsxs(Button, { size: \"sm\", variant: \"ghost\", title: t('backupManageHint'), onClick: () => setPanelOpen(true), style: controlStyle, children: [_jsx(StatusIcon, { state: overallState(status?.channels ?? null, Date.now()) }), _jsxs(\"span\", { style: { minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, children: [t('backup'), \": \", backupLabel] })] }), panelOpen && (_jsx(BackupPanel, { api: props.api, t: t, status: status, onClose: () => {\n setPanelOpen(false);\n void load();\n } }))] }));\n}\n//# sourceMappingURL=StatusDock.js.map","/**\n * dsh-daruma client: registers a compact channel-status dock above the\n * composer (conversation.input.dock) with a \"backup channel\" panel that\n * lists candidate models and picks a backup. Built by tsdown into the\n * __ModuleLoader__ factory bundle at lib/client.js.\n */\nimport { createElement as h } from 'react';\nimport { en, zh } from \"./locales.js\";\nimport { createApi } from \"./api.js\";\nimport { StatusDock } from \"./StatusDock.js\";\nconst NS = 'daruma';\nexport const name = 'dsh-daruma';\nexport const inject = ['slots', 'locale'];\nexport function apply(ctx) {\n // Widen the backup-panel dialog: the primitives Modal card is fixed at\n // min(380px, 100%), which truncates long provider/model names. A global\n // class (not a CSS module) outranks the module-scoped `.dialog` rule.\n if (typeof document !== 'undefined') {\n const tagId = 'daruma-panel-style';\n if (document.querySelector(`style[data-daruma=\"${tagId}\"]`) === null) {\n const style = document.createElement('style');\n style.dataset.daruma = tagId;\n style.textContent = '.daruma-wide-dialog.daruma-wide-dialog { width: min(560px, 100%); }';\n document.head.appendChild(style);\n }\n }\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'dsh-daruma: dictionaries');\n const t = ctx.locale.bind(NS);\n const connection = ctx.get('connection');\n if (connection === undefined) {\n console.warn('[dsh-daruma] web transport unavailable — client UI disabled');\n return;\n }\n const rpc = (endpoint, payload) => connection.rpc.call('/dsh-daruma', endpoint, payload ?? {});\n const api = createApi(rpc);\n ctx.slots.inject('conversation.input.right', () => ctx.slots.register({\n name: 'conversation.input.right',\n id: 'daruma-status',\n order: 0,\n inject: () => ({ api, t }),\n }, () => h(StatusDock, { api, t })));\n}\n//# sourceMappingURL=index.js.map"],"mappings":"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"}
|
package/lib/config.js
CHANGED
|
@@ -9,16 +9,46 @@ export function defaultStateFile() {
|
|
|
9
9
|
return join(homedir(), '.dsh', 'daruma', 'channel-health.json');
|
|
10
10
|
}
|
|
11
11
|
export function resolveConfig(raw = {}) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
12
|
+
if (raw === null || typeof raw !== 'object' || Array.isArray(raw))
|
|
13
|
+
throw new Error('invalid config: expected an object');
|
|
14
|
+
if (raw.channels !== undefined && !Array.isArray(raw.channels)) {
|
|
15
|
+
throw new Error('invalid channels: expected an array');
|
|
16
|
+
}
|
|
17
|
+
const channels = (raw.channels ?? []).map((entry, index) => {
|
|
18
|
+
if (!entry || typeof entry.provider !== 'string' || typeof entry.model !== 'string'
|
|
19
|
+
|| entry.provider.trim() === '' || entry.model.trim() === '') {
|
|
20
|
+
throw new Error(`invalid channel at index ${index}: provider/model must be non-empty strings`);
|
|
21
|
+
}
|
|
22
|
+
if (entry.provider.length > 256 || entry.model.length > 256) {
|
|
23
|
+
throw new Error(`invalid channel at index ${index}: provider/model too long`);
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
id: channelIdOf(entry.provider, entry.model),
|
|
27
|
+
provider: entry.provider,
|
|
28
|
+
model: modelId(entry.model),
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
const ids = new Set(channels.map((channel) => channel.id));
|
|
32
|
+
if (ids.size !== channels.length)
|
|
33
|
+
throw new Error('invalid channels: duplicate provider/model pair');
|
|
34
|
+
const positiveInteger = (value, fallback, name) => {
|
|
35
|
+
const resolved = value ?? fallback;
|
|
36
|
+
if (!Number.isSafeInteger(resolved) || resolved <= 0) {
|
|
37
|
+
throw new Error(`invalid ${name}: expected a positive integer`);
|
|
38
|
+
}
|
|
39
|
+
return resolved;
|
|
40
|
+
};
|
|
41
|
+
const failureBudget = positiveInteger(raw.failureBudget, 3, 'failureBudget');
|
|
42
|
+
const cooldownMs = positiveInteger(raw.cooldownMs, 30_000, 'cooldownMs');
|
|
43
|
+
const giveUpBudget = positiveInteger(raw.giveUpBudget, 8, 'giveUpBudget');
|
|
44
|
+
if (raw.stateFile !== undefined && (typeof raw.stateFile !== 'string' || raw.stateFile.trim() === '')) {
|
|
45
|
+
throw new Error('invalid stateFile: expected a non-empty string');
|
|
46
|
+
}
|
|
17
47
|
return {
|
|
18
48
|
channels,
|
|
19
|
-
failureBudget
|
|
20
|
-
cooldownMs
|
|
21
|
-
giveUpBudget
|
|
49
|
+
failureBudget,
|
|
50
|
+
cooldownMs,
|
|
51
|
+
giveUpBudget,
|
|
22
52
|
stateFile: raw.stateFile || defaultStateFile(),
|
|
23
53
|
};
|
|
24
54
|
}
|
package/lib/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,OAAO,EAA2C,MAAM,aAAa,CAAA;AAC9E,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAmB1C,MAAM,UAAU,gBAAgB;IAC9B,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,qBAAqB,CAAC,CAAA;AACjE,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,MAAoB,EAAE;IAClD,MAAM,QAAQ,GAAc,CAAC,GAAG,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
package/lib/engine.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* RecoveryEngine — the adapter-side state holder that wraps the pure
|
|
3
3
|
* `decide()` from daruma-core.
|
|
4
4
|
*
|
|
5
|
-
* It owns the mutable channel-health map
|
|
5
|
+
* It owns the mutable channel-health map and per-scope failover counters,
|
|
6
6
|
* and persistence through a `ChannelHealthStore`. All decision logic stays in
|
|
7
7
|
* daruma-core; this class only threads state and I/O.
|
|
8
8
|
*/
|
|
@@ -11,6 +11,8 @@ export interface ChannelHealthView {
|
|
|
11
11
|
readonly channel: ChannelId;
|
|
12
12
|
readonly state: ChannelState;
|
|
13
13
|
readonly failures: number;
|
|
14
|
+
/** Epoch ms when the channel's cooldown ends; `0` when not cooling. */
|
|
15
|
+
readonly cooldownUntilMs: number;
|
|
14
16
|
}
|
|
15
17
|
export interface FailoverRecord {
|
|
16
18
|
readonly from: string;
|
|
@@ -23,13 +25,17 @@ export declare class RecoveryEngine {
|
|
|
23
25
|
private readonly store;
|
|
24
26
|
private readonly healths;
|
|
25
27
|
private failoverTotal;
|
|
28
|
+
private readonly failoversByScope;
|
|
26
29
|
private readonly failoverHistory;
|
|
27
30
|
private readonly clock;
|
|
28
31
|
constructor(config: RecoveryPolicyConfig, store: ChannelHealthStore, clock?: Clock);
|
|
29
32
|
/** Load every configured channel's persisted health into memory. */
|
|
30
33
|
private preload;
|
|
31
|
-
/** Feed one failure signal and persist the resulting state.
|
|
32
|
-
|
|
34
|
+
/** Feed one failure signal and persist the resulting state. `scope` should
|
|
35
|
+
* identify an agent/session so independent tasks do not share a budget. */
|
|
36
|
+
onFailure(signal: FailureSignal, preferred?: Channel, scope?: string): RecoveryPlan;
|
|
37
|
+
/** Release per-agent/session counters when the host disposes the scope. */
|
|
38
|
+
clearScope(scope: string): void;
|
|
33
39
|
/** Snapshot of every tracked channel's health, for the status UI. */
|
|
34
40
|
listHealth(): ChannelHealthView[];
|
|
35
41
|
get failoverCount(): number;
|
package/lib/engine.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* RecoveryEngine — the adapter-side state holder that wraps the pure
|
|
3
3
|
* `decide()` from daruma-core.
|
|
4
4
|
*
|
|
5
|
-
* It owns the mutable channel-health map
|
|
5
|
+
* It owns the mutable channel-health map and per-scope failover counters,
|
|
6
6
|
* and persistence through a `ChannelHealthStore`. All decision logic stays in
|
|
7
7
|
* daruma-core; this class only threads state and I/O.
|
|
8
8
|
*/
|
|
@@ -14,6 +14,7 @@ export class RecoveryEngine {
|
|
|
14
14
|
store;
|
|
15
15
|
healths = new Map();
|
|
16
16
|
failoverTotal = 0;
|
|
17
|
+
failoversByScope = new Map();
|
|
17
18
|
failoverHistory = [];
|
|
18
19
|
clock;
|
|
19
20
|
constructor(config, store, clock) {
|
|
@@ -31,12 +32,14 @@ export class RecoveryEngine {
|
|
|
31
32
|
this.healths.set(channel.id, stored ?? freshHealth(channel.id, this.clock.nowMs()));
|
|
32
33
|
}
|
|
33
34
|
}
|
|
34
|
-
/** Feed one failure signal and persist the resulting state.
|
|
35
|
-
|
|
35
|
+
/** Feed one failure signal and persist the resulting state. `scope` should
|
|
36
|
+
* identify an agent/session so independent tasks do not share a budget. */
|
|
37
|
+
onFailure(signal, preferred, scope = 'global') {
|
|
38
|
+
const scopedCount = this.failoversByScope.get(scope) ?? 0;
|
|
36
39
|
const plan = decide({
|
|
37
40
|
signal,
|
|
38
41
|
healths: this.healths,
|
|
39
|
-
failoverCount:
|
|
42
|
+
failoverCount: scopedCount,
|
|
40
43
|
config: this.config,
|
|
41
44
|
nowMs: this.clock.nowMs(),
|
|
42
45
|
...(preferred !== undefined ? { preferred } : {}),
|
|
@@ -44,7 +47,8 @@ export class RecoveryEngine {
|
|
|
44
47
|
this.healths.set(signal.channel, plan.healthAfter);
|
|
45
48
|
this.store.save(plan.healthAfter);
|
|
46
49
|
if (plan.verdict.kind === 'FAILOVER') {
|
|
47
|
-
this.
|
|
50
|
+
this.failoversByScope.set(scope, plan.failoverCount);
|
|
51
|
+
this.failoverTotal += 1;
|
|
48
52
|
this.failoverHistory.push({
|
|
49
53
|
from: signal.channel,
|
|
50
54
|
to: plan.verdict.target.id,
|
|
@@ -57,12 +61,17 @@ export class RecoveryEngine {
|
|
|
57
61
|
}
|
|
58
62
|
return plan;
|
|
59
63
|
}
|
|
64
|
+
/** Release per-agent/session counters when the host disposes the scope. */
|
|
65
|
+
clearScope(scope) {
|
|
66
|
+
this.failoversByScope.delete(scope);
|
|
67
|
+
}
|
|
60
68
|
/** Snapshot of every tracked channel's health, for the status UI. */
|
|
61
69
|
listHealth() {
|
|
62
70
|
return [...this.healths.values()].map((health) => ({
|
|
63
71
|
channel: health.channel,
|
|
64
72
|
state: health.state,
|
|
65
73
|
failures: health.consecutiveFailures,
|
|
74
|
+
cooldownUntilMs: health.cooldownUntilMs,
|
|
66
75
|
}));
|
|
67
76
|
}
|
|
68
77
|
get failoverCount() {
|
package/lib/engine.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.js","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EACL,MAAM,EACN,WAAW,GAUZ,MAAM,aAAa,CAAA;AAEpB,MAAM,WAAW,GAAU,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,CAAA;
|
|
1
|
+
{"version":3,"file":"engine.js","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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"}
|
package/lib/index.js
CHANGED
|
@@ -57,7 +57,7 @@ export function apply(ctx, rawConfig = {}) {
|
|
|
57
57
|
ctx.on('agent/request-error', async (payload, next) => {
|
|
58
58
|
const channel = currentChannel.get(payload.agent.id) ?? channelIdOf(payload.provider, '');
|
|
59
59
|
const signal = toFailureSignal(payload.failure, channel, Date.now());
|
|
60
|
-
const plan = engine.onFailure(signal, backupChannel());
|
|
60
|
+
const plan = engine.onFailure(signal, backupChannel(), payload.agent.id);
|
|
61
61
|
if (plan.verdict.kind === 'FAILOVER') {
|
|
62
62
|
pending.set(payload.agent.id, plan.verdict.target);
|
|
63
63
|
const eventData = { from: channel, to: plan.verdict.target.id, reason: signal.code, at: Date.now() };
|
|
@@ -71,6 +71,11 @@ export function apply(ctx, rawConfig = {}) {
|
|
|
71
71
|
// RETRY_NOW / GIVE_UP: delegate downstream (retry may still own it).
|
|
72
72
|
return next();
|
|
73
73
|
});
|
|
74
|
+
ctx.on('agent/disposed', ({ agent }) => {
|
|
75
|
+
currentChannel.delete(agent.id);
|
|
76
|
+
pending.delete(agent.id);
|
|
77
|
+
engine.clearScope(agent.id);
|
|
78
|
+
});
|
|
74
79
|
mountRpc(ctx, { engine, currentChannel, status, getLlm: () => ctx.llm, getSettings: () => ctx.settings });
|
|
75
80
|
}
|
|
76
81
|
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/lib/rpc.js
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
* `/dsh-daruma` RPC channel: status snapshot, candidate discovery, and
|
|
3
3
|
* backup-channel management for the client UI.
|
|
4
4
|
*/
|
|
5
|
-
import { channelIdOf } from "./mapping.js";
|
|
6
5
|
function messageOf(error) {
|
|
7
6
|
return error instanceof Error ? error.message : String(error);
|
|
8
7
|
}
|
|
@@ -12,10 +11,13 @@ function isRecord(value) {
|
|
|
12
11
|
function isString(value) {
|
|
13
12
|
return typeof value === 'string';
|
|
14
13
|
}
|
|
14
|
+
function isIdentifier(value) {
|
|
15
|
+
return isString(value) && value.trim() !== '' && value.length <= 256;
|
|
16
|
+
}
|
|
15
17
|
/** `{ provider, model }` — a backup selection. */
|
|
16
18
|
function isBackupPayload(value) {
|
|
17
|
-
return isRecord(value) &&
|
|
18
|
-
&&
|
|
19
|
+
return isRecord(value) && isIdentifier(value.provider)
|
|
20
|
+
&& isIdentifier(value.model);
|
|
19
21
|
}
|
|
20
22
|
/** Known provider names: daruma's configured channels plus settings providers. */
|
|
21
23
|
function knownProviders(deps) {
|
|
@@ -65,7 +67,7 @@ export function mountRpc(ctx, deps) {
|
|
|
65
67
|
const connection = ctx.get('connection');
|
|
66
68
|
if (connection === undefined)
|
|
67
69
|
return; // no web transport (headless)
|
|
68
|
-
const dispatch = async (endpoint, payload,
|
|
70
|
+
const dispatch = async (endpoint, payload, _signal) => {
|
|
69
71
|
try {
|
|
70
72
|
switch (endpoint) {
|
|
71
73
|
case 'status': {
|
|
@@ -85,7 +87,7 @@ export function mountRpc(ctx, deps) {
|
|
|
85
87
|
};
|
|
86
88
|
}
|
|
87
89
|
case 'listCandidates': {
|
|
88
|
-
const provider = isRecord(payload) &&
|
|
90
|
+
const provider = isRecord(payload) && isIdentifier(payload.provider) ? payload.provider : undefined;
|
|
89
91
|
if (provider === undefined)
|
|
90
92
|
throw new Error('bad-request: listCandidates needs a provider');
|
|
91
93
|
const llm = deps.getLlm();
|
package/lib/rpc.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rpc.js","sourceRoot":"","sources":["../src/rpc.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"rpc.js","sourceRoot":"","sources":["../src/rpc.ts"],"names":[],"mappings":"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"}
|
package/lib/store.js
CHANGED
|
@@ -4,8 +4,18 @@
|
|
|
4
4
|
* Persistence is best-effort: a corrupt or unreadable state file makes the
|
|
5
5
|
* store start fresh rather than crashing the plugin.
|
|
6
6
|
*/
|
|
7
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
7
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
8
8
|
import { dirname } from 'node:path';
|
|
9
|
+
function isHealth(value, key) {
|
|
10
|
+
if (typeof value !== 'object' || value === null)
|
|
11
|
+
return false;
|
|
12
|
+
const h = value;
|
|
13
|
+
return h.channel === key
|
|
14
|
+
&& (h.state === 'HEALTHY' || h.state === 'COOLDOWN' || h.state === 'PROBE')
|
|
15
|
+
&& Number.isSafeInteger(h.consecutiveFailures) && h.consecutiveFailures >= 0
|
|
16
|
+
&& Number.isFinite(h.cooldownUntilMs) && h.cooldownUntilMs >= 0
|
|
17
|
+
&& Number.isFinite(h.updatedAtMs) && h.updatedAtMs >= 0;
|
|
18
|
+
}
|
|
9
19
|
export class JsonFileChannelHealthStore {
|
|
10
20
|
file;
|
|
11
21
|
healths = new Map();
|
|
@@ -20,9 +30,12 @@ export class JsonFileChannelHealthStore {
|
|
|
20
30
|
try {
|
|
21
31
|
if (!existsSync(this.file))
|
|
22
32
|
return;
|
|
33
|
+
if (!statSync(this.file).isFile())
|
|
34
|
+
return;
|
|
23
35
|
const raw = JSON.parse(readFileSync(this.file, 'utf8'));
|
|
24
36
|
for (const [key, health] of Object.entries(raw.healths ?? {})) {
|
|
25
|
-
|
|
37
|
+
if (isHealth(health, key))
|
|
38
|
+
this.healths.set(key, health);
|
|
26
39
|
}
|
|
27
40
|
}
|
|
28
41
|
catch {
|
|
@@ -44,7 +57,22 @@ export class JsonFileChannelHealthStore {
|
|
|
44
57
|
const healths = {};
|
|
45
58
|
for (const [key, health] of this.healths)
|
|
46
59
|
healths[key] = health;
|
|
47
|
-
|
|
60
|
+
const temporary = `${this.file}.tmp-${process.pid}`;
|
|
61
|
+
const serialized = JSON.stringify({ healths }, null, 2);
|
|
62
|
+
writeFileSync(temporary, serialized, { mode: 0o600 });
|
|
63
|
+
try {
|
|
64
|
+
renameSync(temporary, this.file);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// Keep a compatibility fallback for filesystems where replacement
|
|
68
|
+
// rename is unavailable.
|
|
69
|
+
writeFileSync(this.file, serialized, { mode: 0o600 });
|
|
70
|
+
try {
|
|
71
|
+
unlinkSync(temporary);
|
|
72
|
+
}
|
|
73
|
+
catch { /* best effort cleanup */ }
|
|
74
|
+
}
|
|
75
|
+
chmodSync(this.file, 0o600);
|
|
48
76
|
}
|
|
49
77
|
catch {
|
|
50
78
|
// Persistence failure must not take down the plugin.
|
package/lib/store.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-daruma",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Daruma resilience plugin for DeepSeek Harness — detects model-request failures and fails over to another channel to keep long tasks alive.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -38,6 +38,11 @@
|
|
|
38
38
|
"engines": {
|
|
39
39
|
"node": ">=22.0.0"
|
|
40
40
|
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"build": "tsc -p tsconfig.build.json && tsdown",
|
|
43
|
+
"typecheck": "tsc --noEmit",
|
|
44
|
+
"test": "vitest run --passWithNoTests"
|
|
45
|
+
},
|
|
41
46
|
"keywords": [
|
|
42
47
|
"deepseek-harness",
|
|
43
48
|
"dsh",
|
|
@@ -83,10 +88,5 @@
|
|
|
83
88
|
"tsdown": "^0.22.0",
|
|
84
89
|
"typescript": "^5.7.0",
|
|
85
90
|
"vitest": "^3.0.0"
|
|
86
|
-
},
|
|
87
|
-
"scripts": {
|
|
88
|
-
"build": "tsc -p tsconfig.build.json && tsdown",
|
|
89
|
-
"typecheck": "tsc --noEmit",
|
|
90
|
-
"test": "vitest run --passWithNoTests"
|
|
91
91
|
}
|
|
92
|
-
}
|
|
92
|
+
}
|