Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .changes/unreleased/upgrade-dsh-012-alpha4-peers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
category: Changed
---
- Upgrade every `@deepseek-ai/dsh-*` peer dependency to `^0.1.2-alpha.4` (dsh 0.1.2-alpha.4, 2026-09-01). Migrate `Session.events` reads to `Session.snapshotEvents()` (Session no longer exposes the events array; session logs are read on demand via `snapshotEvents()`/`eventAt()`).
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
![node](https://img.shields.io/badge/node-%3E%3D22-339933.svg)
![pnpm](https://img.shields.io/badge/pnpm-%3E%3D10-f69220.svg)
![dsh tui](https://img.shields.io/badge/dsh%20tui-compatible-4B32C3.svg)
![dsh](https://img.shields.io/badge/DSH-0.1.2--alpha.3-4B32C3.svg)
![dsh](https://img.shields.io/badge/DSH-0.1.2--alpha.4-4B32C3.svg)
[![dshfind](https://dshfind.com/api/badge/omdsh-dev/dsh-llm-fallbacks?lang=en)](https://dshfind.com/zh/plugins/omdsh-dev/dsh-llm-fallbacks?ref=badge)

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.
Expand Down
2 changes: 1 addition & 1 deletion README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
![node](https://img.shields.io/badge/node-%3E%3D22-339933.svg)
![pnpm](https://img.shields.io/badge/pnpm-%3E%3D10-f69220.svg)
![dsh tui](https://img.shields.io/badge/dsh%20tui-compatible-4B32C3.svg)
![dsh](https://img.shields.io/badge/DSH-0.1.2--alpha.3-4B32C3.svg)
![dsh](https://img.shields.io/badge/DSH-0.1.2--alpha.4-4B32C3.svg)
[![dshfind](https://dshfind.com/api/badge/omdsh-dev/dsh-llm-fallbacks?lang=zh)](https://dshfind.com/zh/plugins/omdsh-dev/dsh-llm-fallbacks?ref=badge)

dsh(DeepSeek Harness)的自动模型降级插件:当 root agent 或 subagent 的模型请求持续失败(重试耗尽、权限、配额超限、限流 429)时,按角色/模型 fallback 链自动切换 provider/model,当前 step/turn 在目标模型上继续完成——任务不因模型问题中断。
Expand Down
10 changes: 5 additions & 5 deletions docs/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,19 @@ This document explains how to load `dsh-llm-fallbacks` into a dsh profile, verif

## Prerequisites

- A working dsh runtime environment (`$DSH_HOME`, defaults to `~/.dsh`); development-time type checking and tests need the `@deepseek-ai/*@0.1.2-alpha.3` peer dependencies resolved (see "Development-time `@deepseek-ai/*` peer resolution" below).
- A working dsh runtime environment (`$DSH_HOME`, defaults to `~/.dsh`); development-time type checking and tests need the `@deepseek-ai/*@0.1.2-alpha.4` peer dependencies resolved (see "Development-time `@deepseek-ai/*` peer resolution" below).
- Building requires **node** (`>= 22`) and **pnpm** (`>= 10`) — needed **only for local directory installs** (development): the plugin's `prepare` script self-builds (`pnpm run build`, tsdown + tsc, pnpm stack without bun); registry installs deliver built artifacts, so the target machine does not build anything.
- The target profile (e.g. `web`) is readable/writable, and a dsh session restart is required after installation.

## Development-time `@deepseek-ai/*` peer resolution

`@deepseek-ai/*` is a private package family: at runtime it is provided by the host dsh box as a bundle (`peerDependencies` contract; `@deepseek-ai/*` is externalized at tsdown build time). During development the real packages (`0.1.2-alpha.3`) resolve in one of two supported ways:
`@deepseek-ai/*` is a private package family: at runtime it is provided by the host dsh box as a bundle (`peerDependencies` contract; `@deepseek-ai/*` is externalized at tsdown build time). During development the real packages (`0.1.2-alpha.4`) resolve in one of two supported ways:

- **npm registry (current)**: 0.1.2-alpha.3 is on the npm registry — `autoInstallPeers: true` in `pnpm-workspace.yaml` + an auth token in the user-level `~/.npmrc` — `pnpm install` pulls in the peer dependencies automatically, so type checking, tests, and navigation all run against the real published code (no local link farm).
- **npm registry (current)**: 0.1.2-alpha.4 is on the npm registry — `autoInstallPeers: true` in `pnpm-workspace.yaml` + an auth token in the user-level `~/.npmrc` — `pnpm install` pulls in the peer dependencies automatically, so type checking, tests, and navigation all run against the real published code (no local link farm).
- **Local link (alternative)**: when the target dsh line is not yet on npm, development links the sibling dsh source checkout's `@deepseek-ai/*` packages into `node_modules` (type checking, tests, and navigation run against that linked tree; it is tsc-built into `lib/types/` only — its exports-mapped `lib/index.js` bundles do not exist — so the vitest config aliases the packages the test graph VALUE-imports, see the "Client store seam" bullet below).
- **Authentication (pnpm 11)**: applies to the npm-registry mode. As of pnpm 11, credentials in a project-level `.npmrc` **no longer expand environment variables** (`${NPM_TOKEN}` is invalid and warns). The token must live in the **user-level** `~/.npmrc`: `@deepseek-ai:registry=https://registry.npmjs.org/` + `//registry.npmjs.org/:_authToken=<token>` (or `pnpm config set "//registry.npmjs.org/:_authToken" <token>`); `NPM_TOKEN` remains the read-only token source for the restricted scope.
- **Version**: `peerDependencies` is pinned to `^0.1.2-alpha.3` (aligned with the dlx host's 0.1.2-alpha.3); bump the peer version in sync after a dsh upgrade.
- **pnpm version**: pnpm 11.21+ (this project's stack). Since 11.21 the minimum-release-age supply-chain gate is enabled by default; the `minimumReleaseAgeExclude` table in `pnpm-workspace.yaml` is a **pnpm-maintained** exemption table covering the published dsh package line (rc versions and cordis 4.0.2), with the `0.1.2-alpha.3` entries covering that line's first registry publish; **do not delete the block manually**: a resolved lockfile hard-fails without the exemptions (`ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION`; re-resolve with `pnpm clean --lockfile`).
- **Version**: `peerDependencies` is pinned to `^0.1.2-alpha.4` (aligned with the dlx host's 0.1.2-alpha.4); bump the peer version in sync after a dsh upgrade.
- **pnpm version**: pnpm 11.21+ (this project's stack). Since 11.21 the minimum-release-age supply-chain gate is enabled by default; the `minimumReleaseAgeExclude` table in `pnpm-workspace.yaml` is a **pnpm-maintained** exemption table covering the published dsh package line (rc versions and cordis 4.0.2), with the `0.1.2-alpha.4` entries covering that line's first registry publish; **do not delete the block manually**: a resolved lockfile hard-fails without the exemptions (`ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION`; re-resolve with `pnpm clean --lockfile`).
- **Client store seam**: the client half's one runtime VALUE import is `@deepseek-ai/dsh-client-store` (the snapshot-store engine — a frozen loader-table module in the 0.1.2 host, so the client bundle stays external on it); tests run the real linked implementation through a vitest alias (`vitest.config.ts`), because the linked 0.1.2 tree is tsc-built into `lib/types/` and its exports-mapped `lib/index.js` bundles do not exist yet.

## 1. Local directory install (recommended)
Expand Down
6 changes: 3 additions & 3 deletions docs/verification.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ This document records the installation / runtime-contract verification already c
| client (T5) | `fallbacks-store.spec.ts` / `fallbacks-card.spec.tsx` / `general-row.spec.tsx` / `conversation-switch.spec.tsx` | 95 / 36 / 9 / 15 | card read/write via the **gateway channel** (rpc mock of `/api/fallbacks/get|set|reset`: `load` fetches config from `get`, `save` goes through `set`, `resetToDefaults` goes through `reset`), `present` flag and unreachable-channel skeleton, describe only reads writable + other namespaces (the fallbacks namespace no longer appears in describe), KD-G3 new error path (errors surface truthfully after the revision guard was removed), draft seeded only from a real `get` result (I-1 invariant), chain/rule row-edit round trips, status-block recent-switch extraction (sessions.history event surface), card chrome (plugin-config page listing, collapse/expand, dirty/save/discard), controller lifecycle; General page status row (`settings.general.item` registration shape id `fallbacks` order 100, enabled badge + recent-switch summary, KD-G5 unreachable does not masquerade as disabled, lazy first read and no re-read once read); conversation switch row (`conversation.chat.node` keyed registration key `fallbacks-switch`, D1-defined state machine match/start/update/buildViewNode, renders `from → to (role · reason)` with unknown reasons passed through verbatim, role=status, malformed payloads degrade to the title row without throwing, zh rendering parity smoke) |
| command (AC-5) | `command.spec.ts` | 30 | `/fallbacks` registration shape (name/description/empty hint/handler, disposer passthrough), conditional `commands` child injection (registers only when a registry exists; silent without a service), snapshot building (role/chain resolution with the default fallback, recent switches newest-first capped, cooldown read-only snapshot), output states (configured chain / no chain / switches present + absent / cooldown present + absent / `never` does not revert), zh/en rendering smoke, real runtime-state integration (switch events + cooldown read from real state; read-only, never adds state) |
| release/consumer tooling | `service.spec.ts` / `export-surface.spec.ts` / `release-scripts.spec.ts` | 7 / 27 / 17 | the named cordis service surface (static provide metadata, `ctx.get` availability while applied, unregister on dispose, multi-fiber dedupe, same functions as the package-root re-exports); the package export surface (runtime values + callable smokes + type exports matching the docs-inventory keys); release-script gates (autoBumpPatch / insertSection / parseArgs / validateReleaseVersion / tagExists) |
| regression | `skeleton.spec.ts` / `host-native.spec.ts` / `peer-deps.test.ts` | 3 / 3 / 5 | bundle contract (row id, empty schema accepted, host+client apply entry points); host-native behavior baseline (real `@deepseek-ai/dsh-agent` module: trigger-code switches route to the chain target, always-cap second return point, no-op invariant); registry peer contract (`@deepseek-ai/*` as peerDependencies only, dsh-* pinned to `^0.1.2-alpha.3`, autoInstallPeers, no link farm) |
| regression | `skeleton.spec.ts` / `host-native.spec.ts` / `peer-deps.test.ts` | 3 / 3 / 5 | bundle contract (row id, empty schema accepted, host+client apply entry points); host-native behavior baseline (real `@deepseek-ai/dsh-agent` module: trigger-code switches route to the chain target, always-cap second return point, no-op invariant); registry peer contract (`@deepseek-ai/*` as peerDependencies only, dsh-* pinned to `^0.1.2-alpha.4`, autoInstallPeers, no link farm) |

Result: **23 files / 475 tests all green** (`pnpm test`, vitest run); `pnpm build` (`tsc -p tsconfig.build.json` emits JS first (standard decorator downgrade `__esDecorate`) → tsdown host bundle →
`pnpm run build-client` → `tsc` declarations → `node scripts/verify-dist.mjs` artifact-parsing guard) all green — `tsc` is
Expand Down Expand Up @@ -58,7 +58,7 @@ order section of [docs/install.md](docs/install.md); the real web profile's laye
- **No residue on unload**: `agent/disposed` removes state, `agent/status` idle is defensively cleaned, `ctx.effect`
dispose clears everything (T3 assertions).
- **Real-type contract**: the type layer does not use hand-written `peer-stubs/` — the real
`@deepseek-ai/*@0.1.2-alpha.3` packages drive `tsc` and the integration tests (`tests/support/harness.ts` +
`@deepseek-ai/*@0.1.2-alpha.4` packages drive `tsc` and the integration tests (`tests/support/harness.ts` +
llm-retry-stub + model-selection-stub): in registry mode `autoInstallPeers` resolves them from npm (user-level
`~/.npmrc` auth, no local link farm); until 0.1.2 publishes, development links a sibling dsh checkout into
`node_modules`. Runtime seams run the real implementations: `installSettingsSection` mounts the real
Expand Down Expand Up @@ -131,7 +131,7 @@ Then restart the dsh web session so the host half and the client half load.
#### 4.1 Environment preparation (new snapshot baseline)

1. **Preflight check**: record `dsh --version` (snapshot); the plugin-side peer dependencies resolve from the npm registry
(`@deepseek-ai/*@0.1.2-alpha.3`, no source tree needed).
(`@deepseek-ai/*@0.1.2-alpha.4`, no source tree needed).
2. **Plugin build**: `cd <plugin-repo> && pnpm build` (host bundle + client bundle + tsc
declarations) green — pure-mount semantics: no dsh source-tree modification, no patch step; settings read/write go through the plugin
gateway channel (`/api/fallbacks/get|set|reset`), usable right after installation.
Expand Down
44 changes: 22 additions & 22 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,28 +60,28 @@
},
"peerDependencies": {
"@deepseek-ai/cordis": "^4.0.2",
"@deepseek-ai/dsh-agent": "^0.1.2-alpha.3",
"@deepseek-ai/dsh-api-gateway": "^0.1.2-alpha.3",
"@deepseek-ai/dsh-api-remotes": "^0.1.2-alpha.3",
"@deepseek-ai/dsh-api-session-controller": "^0.1.2-alpha.3",
"@deepseek-ai/dsh-client-connection": "^0.1.2-alpha.3",
"@deepseek-ai/dsh-client-locale": "^0.1.2-alpha.3",
"@deepseek-ai/dsh-client-store": "^0.1.2-alpha.3",
"@deepseek-ai/dsh-client-ui-chat": "^0.1.2-alpha.3",
"@deepseek-ai/dsh-client-ui-conversation": "^0.1.2-alpha.3",
"@deepseek-ai/dsh-client-ui-primitives": "^0.1.2-alpha.3",
"@deepseek-ai/dsh-client-ui-renderer": "^0.1.2-alpha.3",
"@deepseek-ai/dsh-client-ui-settings": "^0.1.2-alpha.3",
"@deepseek-ai/dsh-client-ui-settings-plugins": "^0.1.2-alpha.3",
"@deepseek-ai/dsh-client-ui-slots": "^0.1.2-alpha.3",
"@deepseek-ai/dsh-commands": "^0.1.2-alpha.3",
"@deepseek-ai/dsh-llm": "^0.1.2-alpha.3",
"@deepseek-ai/dsh-llm-retry": "^0.1.2-alpha.3",
"@deepseek-ai/dsh-session": "^0.1.2-alpha.3",
"@deepseek-ai/dsh-settings": "^0.1.2-alpha.3",
"@deepseek-ai/dsh-tool-subagent": "^0.1.2-alpha.3",
"@deepseek-ai/dsh-typert-protocol": "^0.1.2-alpha.3",
"@deepseek-ai/dsh-typert-registry": "^0.1.2-alpha.3",
"@deepseek-ai/dsh-agent": "^0.1.2-alpha.4",
"@deepseek-ai/dsh-api-gateway": "^0.1.2-alpha.4",
"@deepseek-ai/dsh-api-remotes": "^0.1.2-alpha.4",
"@deepseek-ai/dsh-api-session-controller": "^0.1.2-alpha.4",
"@deepseek-ai/dsh-client-connection": "^0.1.2-alpha.4",
"@deepseek-ai/dsh-client-locale": "^0.1.2-alpha.4",
"@deepseek-ai/dsh-client-store": "^0.1.2-alpha.4",
"@deepseek-ai/dsh-client-ui-chat": "^0.1.2-alpha.4",
"@deepseek-ai/dsh-client-ui-conversation": "^0.1.2-alpha.4",
"@deepseek-ai/dsh-client-ui-primitives": "^0.1.2-alpha.4",
"@deepseek-ai/dsh-client-ui-renderer": "^0.1.2-alpha.4",
"@deepseek-ai/dsh-client-ui-settings": "^0.1.2-alpha.4",
"@deepseek-ai/dsh-client-ui-settings-plugins": "^0.1.2-alpha.4",
"@deepseek-ai/dsh-client-ui-slots": "^0.1.2-alpha.4",
"@deepseek-ai/dsh-commands": "^0.1.2-alpha.4",
"@deepseek-ai/dsh-llm": "^0.1.2-alpha.4",
"@deepseek-ai/dsh-llm-retry": "^0.1.2-alpha.4",
"@deepseek-ai/dsh-session": "^0.1.2-alpha.4",
"@deepseek-ai/dsh-settings": "^0.1.2-alpha.4",
"@deepseek-ai/dsh-tool-subagent": "^0.1.2-alpha.4",
"@deepseek-ai/dsh-typert-protocol": "^0.1.2-alpha.4",
"@deepseek-ai/dsh-typert-registry": "^0.1.2-alpha.4",
"@deepseek-ai/schemastery": "^3.18.2",
"react": "^18.2.0"
},
Expand Down
Loading
Loading