dsh-llm-fallbacks 0.3.0 → 0.3.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/README.md +12 -12
- package/README.zh-CN.md +1 -1
- package/dist/client/FallbacksCard.d.ts +1 -1
- package/dist/client/GeneralFallbacksRow.d.ts +1 -1
- package/dist/client/index.js +162 -3
- package/dist/client/use-snapshot.d.ts +11 -0
- package/package.json +26 -23
- package/scripts/build-client.ts +9 -9
package/README.md
CHANGED
|
@@ -5,17 +5,17 @@
|
|
|
5
5
|
[](LICENSE)
|
|
6
6
|

|
|
7
7
|

|
|
8
|
-

|
|
9
8
|

|
|
9
|
+

|
|
10
10
|
[](https://dshfind.com/zh/plugins/omdsh-dev/dsh-llm-fallbacks?ref=badge)
|
|
11
11
|
|
|
12
12
|
Automatic provider/model fallback chains for dsh (DeepSeek Harness): when an agent's LLM requests keep failing — retries exhausted, auth errors, quota exceeded, rate limiting (429) — the plugin switches provider/model along the fallback chain for the current role, and the current step/turn continues on the target model: tasks are not interrupted by model problems.
|
|
13
13
|
|
|
14
|
-
Works in both dsh front ends: the **web** profile (Settings →
|
|
14
|
+
Works in both dsh front ends: the **web** profile (Settings → Plugins → Fallbacks card) and the **dsh-tui** terminal profile (`/fallbacks` + `/fallbacks config`).
|
|
15
15
|
|
|
16
16
|
## Time slots
|
|
17
17
|
|
|
18
|
-
Time slots
|
|
18
|
+
Time slots rotate the **effective root chain** by wall-clock windows: each slot row carries its own fallback chain, and the first row whose window contains the current moment replaces the all-day chain for the next root request — the all-day chain stays as the last resort when no slot matches. Peak and valley windows can therefore use different chains while the failure walk (fallback switch) remains untouched.
|
|
19
19
|
|
|
20
20
|

|
|
21
21
|
|
|
@@ -30,7 +30,7 @@ Four frozen UTC+8 presets (windows are code constants; preset rows lock `tz` to
|
|
|
30
30
|
|
|
31
31
|
GLM Peak and GLM Valley are offered in the card picker only when `zai-coding-cn` is configured.
|
|
32
32
|
|
|
33
|
-
The first extra row whose window contains the current moment (in `fallbacks.tz`, default Asia/Shanghai) wins; no match → the all-day `rootChain`, whose tail (
|
|
33
|
+
The first extra row whose window contains the current moment (in `fallbacks.tz`, default Asia/Shanghai) wins; no match → the all-day `rootChain`, whose tail (Default model) must be exactly one official V4 model — `deepseek-official/deepseek-v4-flash` XOR `deepseek-official/deepseek-v4-pro`. Slot rotation is a routing seed, not a failure decision: it applies on the next root request, consumes no cooldown, and is logged as a time-slot switch — failure walks keep fallback switch. Full semantics → [Time-slot presets](#time-slot-presets) and [docs/configuration.md](docs/configuration.md).
|
|
34
34
|
|
|
35
35
|
## Quick start
|
|
36
36
|
|
|
@@ -64,8 +64,8 @@ Add a `fallbacks:` section to the dsh settings document (default `$DSH_HOME/sett
|
|
|
64
64
|
```yaml
|
|
65
65
|
fallbacks:
|
|
66
66
|
enabled: true # feature switch; defaults to false — set explicitly to enable
|
|
67
|
-
rootChain: # all-day chain: LAST entry is
|
|
68
|
-
- anthropic/claude-3-5-sonnet # leading entries =
|
|
67
|
+
rootChain: # all-day chain: LAST entry is Default model (official V4);
|
|
68
|
+
- anthropic/claude-3-5-sonnet # leading entries = Default fallback chain (walked first)
|
|
69
69
|
- deepseek-official/deepseek-v4-flash # last = Flash XOR Pro last-resort fallback
|
|
70
70
|
timeSlots: # optional: rotate the effective root chain by wall-clock windows
|
|
71
71
|
- kind: preset # frozen UTC+8 windows; only the model chain is editable (locks tz to Asia/Shanghai)
|
|
@@ -103,7 +103,7 @@ Save and restart the session, then type `/fallbacks` — the read-only in-sessio
|
|
|
103
103
|
- **Automatic fallback for root and subagents**: any agent switches down the chain to the next available provider/model on model failure — no manual model switching.
|
|
104
104
|
- **Two-block config**: `rootChain` for the root agent; declared role entities (`roles.list`) referenced by `roles.rules` (or the built-in `inherit`).
|
|
105
105
|
- **Chain as root primary from the picker**: when `enabled` is on, the host model picker (web and TUI alike) shows a virtual `FallbacksChain` / `Auto` row — selecting it uses the configured chain as the root primary (a conforming all-day head is required for the override to succeed); selecting a real model keeps fallback-only (see [FallbacksChain in the model picker](#fallbackschain-in-the-model-picker)).
|
|
106
|
-
- **Time slots
|
|
106
|
+
- **Time slots**: optional `fallbacks.timeSlots` rows rotate the effective root chain by wall-clock windows in the config-level `tz` timezone (default `Asia/Shanghai`) — four frozen UTC+8 presets (`liang-peak` / `liang-valley` / `glm-peak` / `glm-valley`, windows are code constants, models-only edits) or custom `start`/`end`/`days` windows. The first matching row wins; the all-day row is always last. A slot change applies on the **next** root request and is logged as a **time-slot switch** — a routing seed, never a failure decision: it consumes no cooldown and does not count against `maxSwitchesPerStep`. Failure walks keep the **fallback switch** copy (see [Time-slot presets](#time-slot-presets)).
|
|
107
107
|
- **Dispatch-time role resolution**: on a subagent's first request its role is resolved in three stages — explicit (`agentPreset` matches a declared role id) → deterministic rules (unchanged) → LLM auto-match from the declared role taxonomy (`fallbacks.roleAutoMatch`, default `true`). The resolved role's chain-head model is injected into the first request and recorded via an explicit `role → model` log line (no durable `fallbacks/switch` event is written — issue #52 stop-write); set `roleAutoMatch: false` to disable the LLM auto-match stage (the explicit `agentPreset` stage still applies — with no explicit role this reproduces the previous rules-only behavior). The settings card always renders an **Enable role auto-match** switch (default `true`) to toggle it — the schema default applies even to legacy configs that never declared the key.
|
|
108
108
|
- **Cooldown and revert**: failed / switched-away models are not re-selected during cooldown; `revertPolicy: cooldown-expiry` returns to the primary model automatically.
|
|
109
109
|
- **Visible behavior**: every switch is recorded in an info-level log line (from/to/role/reason) — no silent model switching. The plugin deliberately writes **no** durable `fallbacks/switch` session events (issue #52: the apply()-time event-type registration was proven ineffective, and a session containing the event refused to load after a dsh restart). Sessions written by older plugin versions that contain such events are repaired by `scripts/repair-fallbacks-switch-logs.ts`, which marks legacy events ignorable so affected sessions load again.
|
|
@@ -122,19 +122,19 @@ Notes:
|
|
|
122
122
|
|
|
123
123
|
- **Picker label**: the row's catalog `name` (what the composer trigger shows) is live — `Auto: DeepSeek V4 Flash[Liang Peak]` / `Auto: DeepSeek V4 Flash[all-day]` (catalog display name, not the model id); the id stays `Auto`. Bare `Auto` if the all-day tail is not conforming. Refresh by reopening the picker.
|
|
124
124
|
- **Root only**: the row is about the root agent. Subagent role resolution and injection are unchanged; a subagent session that inherits the selection still routes through the chain head — the virtual row is a thin delegate, never a second routing engine.
|
|
125
|
-
- **Conformance gate on the tail**: a successful override/delegate requires the all-day chain to be **tail-conforming** — its last entry must be exactly one official V4 model (`deepseek-official/deepseek-v4-flash` or `deepseek-official/deepseek-v4-pro`, the card's
|
|
125
|
+
- **Conformance gate on the tail**: a successful override/delegate requires the all-day chain to be **tail-conforming** — its last entry must be exactly one official V4 model (`deepseek-official/deepseek-v4-flash` or `deepseek-official/deepseek-v4-pro`, the card's Default model panel); leading entries (Default fallback chain) are walked first. Disabling the plugin hides the row again (slot-row/chain edits never churn registration).
|
|
126
126
|
- **Stale selection**: if the row disappears (plugin disabled) while `FallbacksChain / Auto` is selected, the session keeps showing it as the current model with `routable: false` — pick a real model from the catalog to continue (host-native catalog semantics).
|
|
127
127
|
- **Capabilities follow the head**: the row's model metadata (context window, modalities, reasoning) mirrors the current effective head; retry attribution follows the permissive default — retries/failures are accounted to the real head pair, not to the `FallbacksChain` provider. Full semantics → [docs/configuration.md](docs/configuration.md).
|
|
128
128
|
|
|
129
|
-
## Time-slot presets
|
|
129
|
+
## Time-slot presets
|
|
130
130
|
|
|
131
|
-
Time slots
|
|
131
|
+
Time slots are introduced in the [featured overview](#time-slots) above; this section is the reference. Time-slot rows rotate the **effective root chain** by wall-clock windows — useful for peak/valley pricing without confusing wall-clock rotation with failure fallback. The copy split is strict: slot rotation logs and UI say **time-slot switch**; the failure walk keeps **fallback switch**; the conversation notice Model downgraded stays on the failure path only.
|
|
132
132
|
|
|
133
|
-
- **Match order**: at every root request, the first extra row whose window contains the current moment (in `fallbacks.tz`, default `Asia/Shanghai` / UTC+8) wins — that row's chain **replaces** the all-day chain. No row matches → the all-day `rootChain` is used. The all-day row is always last and **required**: its last entry must be exactly one official V4 model (Flash XOR Pro; leading
|
|
133
|
+
- **Match order**: at every root request, the first extra row whose window contains the current moment (in `fallbacks.tz`, default `Asia/Shanghai` / UTC+8) wins — that row's chain **replaces** the all-day chain. No row matches → the all-day `rootChain` is used. The all-day row is always last and **required**: its last entry must be exactly one official V4 model (Flash XOR Pro; leading Default fallback chain entries are walked first).
|
|
134
134
|
- **Presets** (frozen, not user-editable): `liang-peak` = 09:00–12:00 **and** 14:00–18:00 every day; `liang-valley` = every other UTC+8 time; `glm-peak` = Monday–Friday 14:00–18:00; `glm-valley` = every other time. One preset id = one row; the card picker never offers a duplicate.
|
|
135
135
|
- **Custom rows**: `start` / `end` (`HH:mm`, may wrap midnight) + optional `days` (0=Sunday…6=Saturday; omitted/empty = every day) + models.
|
|
136
136
|
- **Next-request apply**: a slot boundary crossing never preempts an in-flight step — the new row takes effect on the next root request. Rotation is mount-only: info log + card/`/fallbacks` status line, no durable switch event.
|
|
137
|
-
- **Settings card**: the
|
|
137
|
+
- **Settings card**: the Main agent section groups Time slots (extra rows — add preset / add custom / remove / reorder by buttons or **drag**; preset rows show a read-only window summary and edit models only; custom rows carry an editable name; the **timezone picker** lives here and **locks to Asia/Shanghai while any preset row exists**, since preset windows are frozen UTC+8 constants), Default fallback chain (walked first when no slot matches) and Default model (the official V4 Flash | Pro last-resort fallback). Rows are collapsible to name + first model. There is no `timeSlots.enabled` master switch (adding a row is the opt-in) and no `rootMode` control.
|
|
138
138
|
|
|
139
139
|
## Preset roles
|
|
140
140
|
|
package/README.zh-CN.md
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
[](LICENSE)
|
|
6
6
|

|
|
7
7
|

|
|
8
|
-

|
|
9
8
|

|
|
9
|
+

|
|
10
10
|
[](https://dshfind.com/zh/plugins/omdsh-dev/dsh-llm-fallbacks?ref=badge)
|
|
11
11
|
|
|
12
12
|
dsh(DeepSeek Harness)的自动模型降级插件:当 root agent 或 subagent 的模型请求持续失败(重试耗尽、权限、配额超限、限流 429)时,按角色/模型 fallback 链自动切换 provider/model,当前 step/turn 在目标模型上继续完成——任务不因模型问题中断。
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
*/
|
|
81
81
|
import type { ReactNode } from 'react';
|
|
82
82
|
import type { PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
|
|
83
|
-
import type { SnapshotSelectorHook } from '@deepseek-ai/dsh-client-
|
|
83
|
+
import type { SnapshotSelectorHook } from '@deepseek-ai/dsh-client-ui-slots';
|
|
84
84
|
import { FallbacksSettingsController, type FallbacksSettingsState } from './fallbacks-store.ts';
|
|
85
85
|
/** Injected dependencies of {@link FallbacksCard} (slot `inject`). */
|
|
86
86
|
export interface FallbacksCardInjected {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
2
|
import type { PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
|
|
3
|
-
import type { SnapshotSelectorHook } from '@deepseek-ai/dsh-client-
|
|
3
|
+
import type { SnapshotSelectorHook } from '@deepseek-ai/dsh-client-ui-slots';
|
|
4
4
|
import type { FallbacksSettingsController, FallbacksSettingsState } from './fallbacks-store.ts';
|
|
5
5
|
/** Injected dependencies of {@link GeneralFallbacksRow} (slot `inject`). */
|
|
6
6
|
export interface GeneralFallbacksRowInjected {
|
package/dist/client/index.js
CHANGED
|
@@ -4,11 +4,170 @@ window.__ModuleLoader__.load({
|
|
|
4
4
|
var module = { exports: {} };
|
|
5
5
|
var exports = module.exports;
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
-
|
|
7
|
+
//#region \0rolldown/runtime.js
|
|
8
|
+
var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
|
|
9
|
+
//#endregion
|
|
8
10
|
let react = require("react");
|
|
9
11
|
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
10
12
|
let _deepseek_ai_dsh_client_runtime_client = require("@deepseek-ai/dsh-client-runtime/client");
|
|
11
13
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
14
|
+
//#region node_modules/use-sync-external-store/cjs/use-sync-external-store-shim.production.js
|
|
15
|
+
/**
|
|
16
|
+
* @license React
|
|
17
|
+
* use-sync-external-store-shim.production.js
|
|
18
|
+
*
|
|
19
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
20
|
+
*
|
|
21
|
+
* This source code is licensed under the MIT license found in the
|
|
22
|
+
* LICENSE file in the root directory of this source tree.
|
|
23
|
+
*/
|
|
24
|
+
var require_use_sync_external_store_shim_production = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
25
|
+
var React$1 = require("react");
|
|
26
|
+
function is(x, y) {
|
|
27
|
+
return x === y && (0 !== x || 1 / x === 1 / y) || x !== x && y !== y;
|
|
28
|
+
}
|
|
29
|
+
var objectIs = "function" === typeof Object.is ? Object.is : is;
|
|
30
|
+
var useState = React$1.useState;
|
|
31
|
+
var useEffect = React$1.useEffect;
|
|
32
|
+
var useLayoutEffect = React$1.useLayoutEffect;
|
|
33
|
+
var useDebugValue = React$1.useDebugValue;
|
|
34
|
+
function useSyncExternalStore$2(subscribe, getSnapshot) {
|
|
35
|
+
var value = getSnapshot(), _useState = useState({ inst: {
|
|
36
|
+
value,
|
|
37
|
+
getSnapshot
|
|
38
|
+
} }), inst = _useState[0].inst, forceUpdate = _useState[1];
|
|
39
|
+
useLayoutEffect(function() {
|
|
40
|
+
inst.value = value;
|
|
41
|
+
inst.getSnapshot = getSnapshot;
|
|
42
|
+
checkIfSnapshotChanged(inst) && forceUpdate({ inst });
|
|
43
|
+
}, [
|
|
44
|
+
subscribe,
|
|
45
|
+
value,
|
|
46
|
+
getSnapshot
|
|
47
|
+
]);
|
|
48
|
+
useEffect(function() {
|
|
49
|
+
checkIfSnapshotChanged(inst) && forceUpdate({ inst });
|
|
50
|
+
return subscribe(function() {
|
|
51
|
+
checkIfSnapshotChanged(inst) && forceUpdate({ inst });
|
|
52
|
+
});
|
|
53
|
+
}, [subscribe]);
|
|
54
|
+
useDebugValue(value);
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
function checkIfSnapshotChanged(inst) {
|
|
58
|
+
var latestGetSnapshot = inst.getSnapshot;
|
|
59
|
+
inst = inst.value;
|
|
60
|
+
try {
|
|
61
|
+
var nextValue = latestGetSnapshot();
|
|
62
|
+
return !objectIs(inst, nextValue);
|
|
63
|
+
} catch (error) {
|
|
64
|
+
return !0;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function useSyncExternalStore$1(subscribe, getSnapshot) {
|
|
68
|
+
return getSnapshot();
|
|
69
|
+
}
|
|
70
|
+
var shim = "undefined" === typeof window || "undefined" === typeof window.document || "undefined" === typeof window.document.createElement ? useSyncExternalStore$1 : useSyncExternalStore$2;
|
|
71
|
+
exports.useSyncExternalStore = void 0 !== React$1.useSyncExternalStore ? React$1.useSyncExternalStore : shim;
|
|
72
|
+
}));
|
|
73
|
+
//#endregion
|
|
74
|
+
//#region node_modules/use-sync-external-store/shim/index.js
|
|
75
|
+
var require_shim = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
76
|
+
module.exports = require_use_sync_external_store_shim_production();
|
|
77
|
+
}));
|
|
78
|
+
//#endregion
|
|
79
|
+
//#region node_modules/use-sync-external-store/cjs/use-sync-external-store-shim/with-selector.production.js
|
|
80
|
+
/**
|
|
81
|
+
* @license React
|
|
82
|
+
* use-sync-external-store-shim/with-selector.production.js
|
|
83
|
+
*
|
|
84
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
85
|
+
*
|
|
86
|
+
* This source code is licensed under the MIT license found in the
|
|
87
|
+
* LICENSE file in the root directory of this source tree.
|
|
88
|
+
*/
|
|
89
|
+
var require_with_selector_production = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
90
|
+
var React = require("react");
|
|
91
|
+
var shim = require_shim();
|
|
92
|
+
function is(x, y) {
|
|
93
|
+
return x === y && (0 !== x || 1 / x === 1 / y) || x !== x && y !== y;
|
|
94
|
+
}
|
|
95
|
+
var objectIs = "function" === typeof Object.is ? Object.is : is;
|
|
96
|
+
var useSyncExternalStore = shim.useSyncExternalStore;
|
|
97
|
+
var useRef = React.useRef;
|
|
98
|
+
var useEffect = React.useEffect;
|
|
99
|
+
var useMemo = React.useMemo;
|
|
100
|
+
var useDebugValue = React.useDebugValue;
|
|
101
|
+
exports.useSyncExternalStoreWithSelector = function(subscribe, getSnapshot, getServerSnapshot, selector, isEqual) {
|
|
102
|
+
var instRef = useRef(null);
|
|
103
|
+
if (null === instRef.current) {
|
|
104
|
+
var inst = {
|
|
105
|
+
hasValue: !1,
|
|
106
|
+
value: null
|
|
107
|
+
};
|
|
108
|
+
instRef.current = inst;
|
|
109
|
+
} else inst = instRef.current;
|
|
110
|
+
instRef = useMemo(function() {
|
|
111
|
+
function memoizedSelector(nextSnapshot) {
|
|
112
|
+
if (!hasMemo) {
|
|
113
|
+
hasMemo = !0;
|
|
114
|
+
memoizedSnapshot = nextSnapshot;
|
|
115
|
+
nextSnapshot = selector(nextSnapshot);
|
|
116
|
+
if (void 0 !== isEqual && inst.hasValue) {
|
|
117
|
+
var currentSelection = inst.value;
|
|
118
|
+
if (isEqual(currentSelection, nextSnapshot)) return memoizedSelection = currentSelection;
|
|
119
|
+
}
|
|
120
|
+
return memoizedSelection = nextSnapshot;
|
|
121
|
+
}
|
|
122
|
+
currentSelection = memoizedSelection;
|
|
123
|
+
if (objectIs(memoizedSnapshot, nextSnapshot)) return currentSelection;
|
|
124
|
+
var nextSelection = selector(nextSnapshot);
|
|
125
|
+
if (void 0 !== isEqual && isEqual(currentSelection, nextSelection)) return memoizedSnapshot = nextSnapshot, currentSelection;
|
|
126
|
+
memoizedSnapshot = nextSnapshot;
|
|
127
|
+
return memoizedSelection = nextSelection;
|
|
128
|
+
}
|
|
129
|
+
var hasMemo = !1, memoizedSnapshot, memoizedSelection, maybeGetServerSnapshot = void 0 === getServerSnapshot ? null : getServerSnapshot;
|
|
130
|
+
return [function() {
|
|
131
|
+
return memoizedSelector(getSnapshot());
|
|
132
|
+
}, null === maybeGetServerSnapshot ? void 0 : function() {
|
|
133
|
+
return memoizedSelector(maybeGetServerSnapshot());
|
|
134
|
+
}];
|
|
135
|
+
}, [
|
|
136
|
+
getSnapshot,
|
|
137
|
+
getServerSnapshot,
|
|
138
|
+
selector,
|
|
139
|
+
isEqual
|
|
140
|
+
]);
|
|
141
|
+
var value = useSyncExternalStore(subscribe, instRef[0], instRef[1]);
|
|
142
|
+
useEffect(function() {
|
|
143
|
+
inst.hasValue = !0;
|
|
144
|
+
inst.value = value;
|
|
145
|
+
}, [value]);
|
|
146
|
+
useDebugValue(value);
|
|
147
|
+
return value;
|
|
148
|
+
};
|
|
149
|
+
}));
|
|
150
|
+
//#endregion
|
|
151
|
+
//#region src/client/use-snapshot.ts
|
|
152
|
+
var import_with_selector = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
153
|
+
module.exports = require_with_selector_production();
|
|
154
|
+
})))();
|
|
155
|
+
/**
|
|
156
|
+
* Bind a bare observable source to a typed uSES selector hook.
|
|
157
|
+
* subscribe/getSnapshot are captured once per source into stable closures
|
|
158
|
+
* (also re-binds `this` for method-based sources), so components never
|
|
159
|
+
* resubscribe across renders. Equality defaults to Object.is.
|
|
160
|
+
* @param w - snapshot source (engine store, Session object, store instance).
|
|
161
|
+
* @returns the selector hook.
|
|
162
|
+
*/
|
|
163
|
+
function bindSnapshotSelector(w) {
|
|
164
|
+
const subscribe = (fn) => w.subscribe(fn);
|
|
165
|
+
const getSnapshot = () => w.getSnapshot();
|
|
166
|
+
return function useSelector(sel, eq) {
|
|
167
|
+
return (0, import_with_selector.useSyncExternalStoreWithSelector)(subscribe, getSnapshot, void 0, sel, eq);
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
//#endregion
|
|
12
171
|
//#region src/selectors.ts
|
|
13
172
|
/** Catchable error for illegal/unknown selectors (config-warning path). */
|
|
14
173
|
var SelectorError = class extends Error {
|
|
@@ -318,7 +477,7 @@ window.__ModuleLoader__.load({
|
|
|
318
477
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
319
478
|
}
|
|
320
479
|
/**
|
|
321
|
-
* Read a nested value by path — the
|
|
480
|
+
* Read a nested value by path — the upstream `dsh-client-schema-form`
|
|
322
481
|
* `getPath` semantics, copied locally so the provider-configured join needs no
|
|
323
482
|
* new dependency (array indexes as numeric keys, `undefined` along a missing
|
|
324
483
|
* branch).
|
|
@@ -4187,7 +4346,7 @@ window.__ModuleLoader__.load({
|
|
|
4187
4346
|
const connection = ctx.get("connection");
|
|
4188
4347
|
const sessions = ctx.get("sessions");
|
|
4189
4348
|
const controller = new FallbacksSettingsController(connection.api, connection.rpc);
|
|
4190
|
-
const useSnapshot =
|
|
4349
|
+
const useSnapshot = bindSnapshotSelector(controller.store);
|
|
4191
4350
|
ctx.effect(() => {
|
|
4192
4351
|
const syncSession = () => {
|
|
4193
4352
|
controller.setCurrentSession(sessions?.list.getSnapshot().current);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { HostObservable, SnapshotSelectorHook } from '@deepseek-ai/dsh-client-ui-slots';
|
|
2
|
+
export type { SnapshotSelectorHook } from '@deepseek-ai/dsh-client-ui-slots';
|
|
3
|
+
/**
|
|
4
|
+
* Bind a bare observable source to a typed uSES selector hook.
|
|
5
|
+
* subscribe/getSnapshot are captured once per source into stable closures
|
|
6
|
+
* (also re-binds `this` for method-based sources), so components never
|
|
7
|
+
* resubscribe across renders. Equality defaults to Object.is.
|
|
8
|
+
* @param w - snapshot source (engine store, Session object, store instance).
|
|
9
|
+
* @returns the selector hook.
|
|
10
|
+
*/
|
|
11
|
+
export declare function bindSnapshotSelector<T>(w: HostObservable<T>): SnapshotSelectorHook<T>;
|
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"dsh",
|
|
6
6
|
"dsh-plugin"
|
|
7
7
|
],
|
|
8
|
-
"version": "0.3.
|
|
8
|
+
"version": "0.3.1",
|
|
9
9
|
"type": "module",
|
|
10
10
|
"main": "./dist/index.js",
|
|
11
11
|
"types": "./dist/index.d.ts",
|
|
@@ -56,28 +56,30 @@
|
|
|
56
56
|
"engines": {
|
|
57
57
|
"node": ">=22"
|
|
58
58
|
},
|
|
59
|
+
"dependencies": {
|
|
60
|
+
"use-sync-external-store": "^1.6.0"
|
|
61
|
+
},
|
|
59
62
|
"peerDependencies": {
|
|
60
63
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
61
|
-
"@deepseek-ai/dsh-agent": "^0.1.0-rc.
|
|
62
|
-
"@deepseek-ai/dsh-api-gateway": "^0.1.0-rc.
|
|
63
|
-
"@deepseek-ai/dsh-api-remotes": "^0.1.0-rc.
|
|
64
|
-
"@deepseek-ai/dsh-client-connection": "^0.1.0-rc.
|
|
65
|
-
"@deepseek-ai/dsh-client-locale": "^0.1.0-rc.
|
|
66
|
-
"@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.
|
|
67
|
-
"@deepseek-ai/dsh-client-ui-
|
|
68
|
-
"@deepseek-ai/dsh-client-ui-
|
|
69
|
-
"@deepseek-ai/dsh-client-ui-
|
|
70
|
-
"@deepseek-ai/dsh-client-ui-
|
|
71
|
-
"@deepseek-ai/dsh-client-
|
|
72
|
-
"@deepseek-ai/dsh-
|
|
73
|
-
"@deepseek-ai/dsh-
|
|
74
|
-
"@deepseek-ai/dsh-
|
|
75
|
-
"@deepseek-ai/dsh-
|
|
76
|
-
"@deepseek-ai/dsh-
|
|
77
|
-
"@deepseek-ai/dsh-typert-
|
|
78
|
-
"@deepseek-ai/
|
|
79
|
-
"react": "^18.2.0"
|
|
80
|
-
"@deepseek-ai/schemastery": "^3.18.1"
|
|
64
|
+
"@deepseek-ai/dsh-agent": "^0.1.0-rc.8",
|
|
65
|
+
"@deepseek-ai/dsh-api-gateway": "^0.1.0-rc.8",
|
|
66
|
+
"@deepseek-ai/dsh-api-remotes": "^0.1.0-rc.8",
|
|
67
|
+
"@deepseek-ai/dsh-client-connection": "^0.1.0-rc.8",
|
|
68
|
+
"@deepseek-ai/dsh-client-locale": "^0.1.0-rc.8",
|
|
69
|
+
"@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.8",
|
|
70
|
+
"@deepseek-ai/dsh-client-ui-conversation": "^0.1.0-rc.8",
|
|
71
|
+
"@deepseek-ai/dsh-client-ui-primitives": "^0.1.0-rc.8",
|
|
72
|
+
"@deepseek-ai/dsh-client-ui-settings": "^0.1.0-rc.8",
|
|
73
|
+
"@deepseek-ai/dsh-client-ui-settings-plugins": "^0.1.0-rc.8",
|
|
74
|
+
"@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.8",
|
|
75
|
+
"@deepseek-ai/dsh-commands": "^0.1.0-rc.8",
|
|
76
|
+
"@deepseek-ai/dsh-llm": "^0.1.0-rc.8",
|
|
77
|
+
"@deepseek-ai/dsh-session": "^0.1.0-rc.8",
|
|
78
|
+
"@deepseek-ai/dsh-settings": "^0.1.0-rc.8",
|
|
79
|
+
"@deepseek-ai/dsh-typert-protocol": "^0.1.0-rc.8",
|
|
80
|
+
"@deepseek-ai/dsh-typert-registry": "^0.1.0-rc.8",
|
|
81
|
+
"@deepseek-ai/schemastery": "^3.18.1",
|
|
82
|
+
"react": "^18.2.0"
|
|
81
83
|
},
|
|
82
84
|
"devDependencies": {
|
|
83
85
|
"@testing-library/dom": "^10.4.1",
|
|
@@ -85,13 +87,14 @@
|
|
|
85
87
|
"@types/node": "^22.20.0",
|
|
86
88
|
"@types/react": "~18.3.31",
|
|
87
89
|
"@types/react-dom": "~18.3.7",
|
|
90
|
+
"@types/use-sync-external-store": "^1.5.0",
|
|
88
91
|
"jsdom": "29.1.1",
|
|
89
92
|
"react": "^18.3.1",
|
|
90
93
|
"react-dom": "^18.3.1",
|
|
91
|
-
"vitest": "^4.1.8",
|
|
92
94
|
"tsdown": "^0.22.2",
|
|
93
95
|
"tsx": "^4.22.4",
|
|
94
|
-
"typescript": "^5.9.3"
|
|
96
|
+
"typescript": "^5.9.3",
|
|
97
|
+
"vitest": "^4.1.8"
|
|
95
98
|
},
|
|
96
99
|
"repository": {
|
|
97
100
|
"type": "git",
|
package/scripts/build-client.ts
CHANGED
|
@@ -47,12 +47,15 @@ const OUT_FILE = 'index.js'
|
|
|
47
47
|
|
|
48
48
|
/**
|
|
49
49
|
* Frozen loader module table (dsh mechanism-guide): the plan-documented
|
|
50
|
-
* composition = PLATFORM_MODULES (
|
|
51
|
-
* + the
|
|
52
|
-
* `
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
50
|
+
* composition = PLATFORM_MODULES (7 entries, `web/src/platform.ts:8-12`)
|
|
51
|
+
* + the PRELOADED_CLIENT_EXTERNALS (`@deepseek-ai/dsh-client-runtime/client`,
|
|
52
|
+
* `web/src/platform.ts:15-17`). rc.8 dropped `dsh-client-web-react` (deleted
|
|
53
|
+
* package — the uSES bridge is vendored in `src/client/use-snapshot.ts`),
|
|
54
|
+
* `dsh-client-ui-attachment` and `dsh-client-schema-form` from the platform
|
|
55
|
+
* table. `@deepseek-ai/cordis` is the in-box cordis framework (dsh-advisor
|
|
56
|
+
* alignment): the client graph resolves it through the generated shim, so it
|
|
57
|
+
* stays on the table as a real loader-table specifier for the frozen-
|
|
58
|
+
* composition claim.
|
|
56
59
|
*/
|
|
57
60
|
export const CLIENT_EXTERNALS: readonly string[] = [
|
|
58
61
|
'react',
|
|
@@ -61,10 +64,7 @@ export const CLIENT_EXTERNALS: readonly string[] = [
|
|
|
61
64
|
'react-dom/client',
|
|
62
65
|
'@deepseek-ai/cordis',
|
|
63
66
|
'@deepseek-ai/dsh-client-ui-slots',
|
|
64
|
-
'@deepseek-ai/dsh-client-web-react',
|
|
65
67
|
'@deepseek-ai/dsh-client-ui-primitives',
|
|
66
|
-
'@deepseek-ai/dsh-client-ui-attachment',
|
|
67
|
-
'@deepseek-ai/dsh-client-schema-form',
|
|
68
68
|
'@deepseek-ai/dsh-client-runtime/client',
|
|
69
69
|
]
|
|
70
70
|
|