@mars-sea/dsh-commandcode-provider 0.11.16 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,62 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [0.12.0] - 2026-09-29
8
+
9
+ ### Added
10
+
11
+ - **The model list now shows how far each model stretches your plan: its monthly allowance.** The pricing page publishes a **per-model allowance** (its own term) alongside every model's rates — "$47 of monthly usage on GOAT, $57 on Pro", to quote its MiniMax M3 example, which is the exact pair the new snapshot carries. The settings page's model dropdowns (Visible models, and the per-account routing pickers, which share one component) now render it beside the model name as `$20/mo`, with a tooltip spelling out what it is. **It is not extra credit**: every allowance draws on the same monthly pool, so `$20/mo` means "this model may consume $20 of that pool", not "$20 added to it". Two facts shape the implementation, both from the page rather than from guesswork: the figure exists for **GOAT and Pro only** ("the boost is a per-model allowance, so it lives on the plans that have them"), and deals are **already baked in** — MiniMax M3's `$47 / $57` IS its 2× promotion, folded in upstream. So `allowanceTierForWeight()` maps a subscription tier weight onto those two brackets and answers `undefined` for Go / Provider / Max / Ultra, and the Host resolves the bracket from the POOL's highest plan (`adapter.allowanceTier()`), matching the question `modelVisibleForAnyAccount` already answers for a pool. A Go or Max account therefore sees no allowance at all rather than a GOAT number — the honest outcome, and the reason the badge is absent rather than zero. The data rides the price rows (`allowance: { goat, pro }`, same page record, same slug resolution as the rates) and reaches the browser through the existing `commandcode/models` Remote as a single Host-resolved number; the wire stays a plain `allowance?: number` so the browser never re-derives the bracket. `tests/model-prices.test.ts` checks the snapshot against the page's own prose (MiniMax M3 at `$47 / $57`), asserts every priced catalog model carries both brackets, and pins that only weights 1 and 2 map; `tests/adapter.test.ts` covers the pool's highest-plan rule and its fail-open shape when billing is unreadable.
12
+
13
+ - **One accessibility fix on the way past: badges are no longer below the 12 px floor.** The shared `.cc-badge` / `.cc-badgeMuted` rule rendered at `font-size: 11px`. It is now 12px, the minimum the repository's visual rules allow for labels and auxiliary text. Only that one rule changed — the remaining sub-12px declarations in `panel-styles.ts` and the usage-plan pill are untouched and still need a decision.
14
+
15
+ ### Fixed
16
+
17
+ - **A header timeout now names its real cause, and stops being retried forever (#67).** The single message "通常是网络或代理问题,请检查后重试" was wrong for both transports and sent the reporter of #67 to their proxy while their gateway was simply not answering. Measured 2026-09-28, keeping **headers** separate from **totals** (a total includes the model producing every token and says nothing about the header wait). Headers, with `max_tokens: 16` to collapse the generation, 26 250-token prompt: `/alpha/generate` 2.12 / 2.56 / 2.75 s, `/provider/v1/chat/completions` 5.52 / 2.98 / 3.70 s — a ~1.1 s median gap, with the Provider API route swinging 2.5 s where the CLI route moved 0.6 s. Totals at `max_tokens: 1024` were 11.1 s / 17.9 s for the CLI route and 116.7 s / 30.5 s for the Provider API route at 200 k and 1.2 M chars — the 200 k prompt finishing 10× slower than the 1.2 M one is why a size-based rule was wrong, and it is also why the 116.7 s figure is not header evidence. So the message branches on the transport: the CLI route names provider queueing, Go-plan capacity or a network/proxy path and says a longer budget will not help; the Provider API route names the upstream first token and suggests a concrete `requestTimeoutMs` (doubled, floored at 5 minutes, capped at the retry backoff's own 15). Both say outright that prompt size is not the cause. Separately, three consecutive header timeouts on one unchanged payload now leave the retryable whitelist as `PROVIDER_HTTP_ERROR` with an explanation, because dsh-llm exposes a single scalar `maxRetries` and no per-code limit, and `maxRetries: 1000` with a 500 ms → 15 min backoff meant the identical multi-megabyte body was re-sent for hours instead of producing a diagnosis. `RATE_LIMIT` / `SERVER` / `TRANSPORT` keep retrying — each carries a real "come back later" signal — and any answer from the gateway ends the streak. The "Request timeout" hint in the Advanced card now says when the Provider API route needs a bigger budget, and **the default moved from 60 s to 300 s to match the official CLI**. That number is the CLI's, not a round choice: decompiling `command-code@1.66.0` shows its `createNodeTransport` fetch passes only the caller's `signal` and sets no timeout at all, so a model request inherits Node/undici's own default — measured at 301.0 s against a local server that accepts the connection and never sends headers. Being stricter than the CLI only ever costs legitimate requests, since the Provider API route charges its upstream's time-to-first-token to the header wait. The trade is that a genuinely dead gateway now takes longer to name itself, which is what the streak is for.
18
+
19
+ - **The output budget was raised from 64 000 to 131 072.** It was never a provider requirement: probing `/alpha/generate` directly, 64 000 → 200, 131 072 → 200, 196 608 → 200, **200 000 → 200, 200 704 → 400**, 1 000 000 → 400. The server caps `max_tokens` at 200 000, and identically so for a 262 144-window model and a 1 000 000-window one — the cap is global, not per model — so 64 000 was using 32 % of what the provider allows, and the 384 000 figure quoted elsewhere would be refused. 131 072 keeps a 35 % margin under the measured ceiling while covering the real output limits of the models that declare one (Claude 64 K, Gemini 64 K, several at 128 K). It cost nothing in latency to leave at 64 000 (a 64 000 request answered headers in 1.28 s against 1.57 s for 131 072) but it did cap what a model could say in one turn. `DEFAULT_MAX_OUTPUT_TOKENS` moved with it: `/provider/v1/models` publishes only `context_length` and no output ceiling, so the catalog derives a stand-in from the window, and leaving that fallback lower would have re-capped every request before the ceiling was ever consulted. Per request, `requestContextBudget` still lowers the budget from the prompt actually being sent, so raising the ceiling cannot push a large session over its window.
20
+
21
+ - **Both timeout fields are edited in seconds, not milliseconds.** The settings page asked users to do arithmetic to answer "how long is a reasonable wait" — the defaults were `300000` and the ceiling `2147483647`. The stored unit is unchanged (milliseconds, the Host schema, and switching it would break every existing profile); this is presentation only, formatted and parsed through a new `secondsField` spec. Inputs are whole seconds from 1 to 3600 — past an hour a header wait is a hung process, not a budget, and the input is the place to refuse it. Fractions are refused rather than rounded, so the box and the saved profile cannot disagree, and a profile hand-edited beyond the range still displays its true number instead of being silently clamped. The hints now state the default in seconds, and a test reads `DEFAULT_REQUEST_TIMEOUT_MS` to keep the copy from drifting away from the constant.
22
+
23
+ - **CLI image requests now match the official final wire shape (#64).** `/alpha/generate` sends image data URLs with `mimeType`, and both chat transports label each request image with the media type returned by `readImageRequest()` after any JPEG/WebP transcode. Tests cover tool-result images, stored-PNG/request-JPEG variants and stable historical image messages when another image is appended. Live calls showed that this correction alone does not prevent the provider's cache retreat when another image arrives.
24
+
25
+ ### Changed
26
+
27
+ - **Updated the exact DeepSeek Harness pairing from `0.1.7-rc.2` to `0.2.0-rc.1`.** Every Harness peer and development range, `dsh.compatibility.dsh`, `engines.dsh`, and the single `dshReleases` record now name 0.2.0-rc.1 together, and no business code changed: the release audit over the full public source delta (1111 changed paths, 322 package manifests) found 13 of the 16 Harness packages with **zero source changes** — `dsh-llm`, `dsh-settings`, `dsh-typert-protocol`, `dsh-credentials`, `dsh-commands`, `dsh-web`, `dsh-timeout`, `dsh-agent`, `dsh-attachment`, `dsh-launch-environment`, `dsh-invariants`, `cordis` and the two client-only rows — so `GenerateOptions`/`RequestMessage`/`ContentBlock`/`LlmError` and the settings `volatile` contract are byte-identical. The three that moved are additive: `dsh-api-remotes/client` adds one `productAnalyticsRemote` contribution to its assembly list, and `dsh-client-ui-primitives` adds one `pointerModality` export. All five slot ids this plugin registers (`settings.section`, `settings.models.provider-card`, `settings.models.footer`, `sidebar.footer.action`, `conversation.composer.dock`) survive in the new slot catalog with an unchanged `slot-contract.ts`, `apps/web` has no source change so the platform seed table is untouched, and the nine `staticModules` seeds were re-read out of the 0.2.0-rc.1 frontend and are identical to 0.1.7-rc.2's — the argument for keeping `react`, `dsh-client-ui-primitives` and `dsh-client-ui-slots` optional still holds. The `data-composer-stats` outlet the cost readout attaches to is still present at its original site. `npm run test:engine` passes against a real `@deepseek-ai/dsh@0.2.0-rc.1` (ESM link, every static named import, the client `require()` seed audit, image pricing, the volatile Config, tool history on both transports, and the durable offload contract). Deliberately not adopted: the optional `dsh-experimental-schedule-bundle` that automation tasks moved into, the desktop-only product-analytics telemetry, and `resolveAccountToken` in the official `web-search-deepseek` — this plugin ships its own `web_search` provider over the same credential chain and gains nothing from it.
28
+
29
+ - **Opt-in cache-aware image history for the CLI transport (#64).** `offloadSeenImagesForCache` asks dsh's durable image-offload surface to replace older images only after the same model has answered with them. A four-request live A/B showed that the next image then retained the growing text prefix instead of reverting to the first image boundary. It is off by default because old pixels are unavailable until reread or reattached; this is a mitigation, not a provider-side cache fix.
30
+
31
+ - **Internal cleanup with no behavior change: dead-code removal, comment trimming, and small refactors.** Removed the unreferenced `isPeakHour()` helper (the readout prices through `cost-facts.ts` and `capabilities.ts`; its test is replaced by one that pins billing-vs-picker peak agreement across every weekday boundary), inlined the single-use `StatTile` wrapper, dropped the never-used `AccountReport summaryOnly` mode, narrowed the mirror's internal status union to the states it actually writes, and registered the login endpoints through `LOGIN_HOST_CONTRIBUTION` instead of spreading the raw descriptors. Trimmed duplicated design narratives in `transport-retry.ts`, `accounts.ts`, and the slot-declaration files (one canonical copy each). Small performance refactors: `costGroupKey()` now resolves prices through a per-table index instead of scanning the table per call, and the cost projection builds its group lookup in one pass. Typecheck clean, full suite green (679 tests).
32
+
33
+ - **Synced with the official command-code@1.67.0 and 1.68.0 CLIs.** `COMMAND_CODE_CLI_VERSION` and the `x-command-code-version` header now report `1.68.0`. Both releases landed within hours of each other (npm `1.67.0` at 2026-09-28T21:39Z, `1.68.0` at 2026-09-28T23:13Z) and **the official changelog page still documents neither** — its newest entry is 1.66.0 (2026-09-26) — so the integrity-verified tarballs are the evidence, and the two releases were separated by diffing the pinned 1.66.0 artifact, the 1.67.0 artifact and `latest` side by side. 1.67.0 adds **`deepseek/deepseek-v4.1-flash-fast`** (DeepSeek V4.1 Flash Fast): `/chat/completions` + `/responses`, 1M context, text+image, `low`/`high`/`max` efforts, **Go**-tier, hourly peak/off-peak pricing at its own $0.16/$0.58/$0.016 off-peak and $0.32/$1.16/$0.032 peak — deliberately **not** 2× its `deepseek-v4.1-flash` sibling's rates, so only that row's own `timeOfDay` block is the evidence — and it is **not** ZDR-covered. 1.68.0 adds **`claude-sonnet-5-5`** (Claude Sonnet 5.5): `/messages`, 1M context, text+image, the five-level effort set, **GOAT and above**, at $2.00/$10.00/$0.20/$2.50 — one tier *above* its `claude-sonnet-5` predecessor, which keeps its Pro slot. 1.68.0 also adds one non-model feature, the TUI setting **"Show Taste learning in feed"** (`showTasteLearningInFeed`, boolean, user scope, default on), which hides Taste notices from the feed without disabling learning; it lives in the CLI's own config and has no counterpart in this plugin. Everything else is unchanged and was re-verified rather than assumed: the subscription plan maps, the ZDR anchor sets beyond the new row, the API route table, the `x-command-code-version` / `x-cmd-zdr` header table, the account-rejection vocabulary and the stream event names are byte-identical across 1.66.0 → 1.68.0. The remaining artifact diff is minified identifier rotation (e.g. `kR` → `CR`), which carries no behavior.
34
+
35
+ - **The capability snapshot grew by two models, and the vendored price table with it.** `KNOWN_EFFORTS` 53 → 55, `KNOWN_IMAGE_MODELS` 62 → 64, `KNOWN_PLANS` 82 → 84 (Go 52 → 53, GOAT 8 → 9 more; the cumulative chain is now **53/62/76/84** against a catalog that serves 84 models at 66 × chat/completions + responses, 10 Claude ids × messages, 8 × chat/completions), `KNOWN_PEAK_PRICING` 4 → 5, and `KNOWN_NON_ZDR_MODELS` 22 → 23. `scripts/sync-model-prices.mjs` rewrote `MODEL_PRICE_ROWS` with exactly two new rows and no other change, which is also what keeps `tests/model-prices.test.ts`'s "every catalog model is either priced or explicitly free" invariant green. `tests/zdr.test.ts` pins the new membership deliberately, `tests/adapter.test.ts` gained the two models' effort/plan/Vision/peak assertions and its version constant moved with the header.
36
+
37
+ - **One provenance note was corrected, not carried forward.** The `stealth/pixel-canary` comment claimed it entered the ZDR exclusion set "through `modelSupportsZdr` alone", and the `KNOWN_NON_ZDR_MODELS` docstring repeated that both stealth previews were missing from the sibling route table. Re-reading all three artifacts (1.66.0 / 1.67.0 / 1.68.0) on 2026-09-29 shows the row is listed in **both** anchor sets in every one of them; `meituan/LongCat-2.0` is the only genuine divergence. Membership never depended on this — the snapshot is the union — but the explanation was wrong and is now the evidence it claims to be.
38
+
39
+ - **A lapsed discount no longer prices a session at the promotional rate.** The picker has always hidden an expired deal, but the vendored price table kept charging its discount: Grok 4.7's 40%-off launch promotion ended `2026-09-27T23:59:59.999Z`, yet the composer still quoted **$1.20 / $3.60 / $0.30** against a row the page reverts to **$2.00 / $6.00 / $0.50** — a 40% under-report on every session, with the >200K band off by the same factor. The page publishes both sets (the promotional figures as the row's own rates, the pre-discount `listRates` alongside), so `scripts/sync-model-prices.mjs` now copies both and `modelPriceTable(now)` picks between them on **`dealLabel()`'s own clock**. Badge and dollars therefore flip on the same instant, and both context bands flip together so a >200K request is never priced off a mixed pair. The table is rebuilt per request, so the switch needs no redeploy; a deal with no `expiresAt` (the two MiMo rows, MiniMax M3) never lapses and keeps its promotional rates. `tests/model-prices.test.ts` pins the revert, the shared boundary (inclusive on the expiry side) and the no-expiry case.
40
+
41
+ - **The page's `discountPercent` is not the revert factor, and the script no longer pretends it is.** Dividing the promotional rates by `1 - pct/100` looks obvious and is wrong: MiMo V2.5 is advertised at "98% off" while its published list is `0.8 / 4 / 0.16` against charged `0.14 / 0.28 / 0.0028` — ratios of 5.7×, 14.3× and 57×, not one percentage. The generator therefore copies the page's literal `listRates` verbatim, scales a context tier only by that row's own per-bucket ratios, and prints a warning instead of guessing when those disagree. A deal with **no** literal `listRates` is left alone deliberately: the page is saying the unit price does not change (Qwen 3.7 Max's `2x-usage` doubles the allowance, it does not discount a token). Rows whose list equals their charged rates (typesafe/jev) get no fallback data, so nothing dormant ships.
42
+
43
+ - **Fixed, same day: the price script's HTML cross-check had been blind since the page redesign.** `renderedRates()` split on `<tr>`/`<td>`, but the model table is now a `<div>` grid — a live run extracted **6 rows out of 88 models**, and those six were the plan cards (Go/GOAT/Pro/Max 10×/Max 20×/Team Pro). Every model row had therefore been rewritten from the embedded JSON alone with no second opinion, which is the exact safety net that once caught `deepseek-v4-flash-vision-exp` shipping ~47% high. The parser now anchors on the row's name cell (`flex min-w-0 items-center gap-2 px-4 py-3`, whose display name is marked `text-[13px] font-medium` — a deal badge next to it uses `text-[10px]`, so they never collide) and harvests **88 of 88** rows. Re-running the generator on the fixed parser produced a **byte-identical table**, which is what makes the previous write trustworthy in hindsight rather than merely unchecked. Two guards came out of it: `main()` now **exits 2 instead of rewriting** when the harvest falls below half the record count, so the next layout change fails loudly rather than comparing nothing; and a discounted row's struck-through `<s>` figures are compared against the table JSON's `listRates`, giving the pre-discount copy an independent rendering to agree with. The `STRUCK_RATE` regex is deliberately `<s(?:\s[^>]*)?>`: the obvious `<s[^>]*>` also matches `<span …>`, which read the DeepSeek rows' tooltip spans as struck-through prices during development. Rows showing a word (`Free`) or an em-dash publish no figure and are skipped — an unparseable row is never counted as agreement.
44
+
45
+ ## [0.11.17] - 2026-09-27
46
+
47
+ ### Changed
48
+
49
+ - **Synced with the official command-code@1.66.0 CLI.** `COMMAND_CODE_CLI_VERSION` and the `x-command-code-version` header now report `1.66.0`. The release is additive with exactly one change against `1.65.5` (the newest version the official changelog page still documents, so the bundle diff is the evidence): the CLI registry gains `stealth/pixel-canary` (Pixel Canary) — Vercel AI Gateway-served, text+image, 262,144-token context, reasoning with the selectable `low`/`medium`/`high` set, available on every plan from Go up, and **free while the stealth preview lasts**. The public catalog serves it too (82 models), on `/chat/completions` + `/responses`. It is the **second change to the ZDR exception set since the 1.62.0 check**: it enters through `modelSupportsZdr` alone (the 1.66.0 bundle's `knownModelSupportsZdr` set does not repeat it), the pricing page says it plainly ("Free while the preview lasts. Not routed under ZDR."), so `KNOWN_NON_ZDR_MODELS` grows 21 → 22 and `tests/zdr.test.ts` pins the new membership deliberately. It is also the second free stealth model to carry no vendored price row, exactly like Space Bunny Alpha — the price table's free-model branch prices it at $0. **The wire protocol is untouched**: the 26 `/alpha/*` and `/provider/v1/*` routes, the `x-command-code-version` / `x-cmd-zdr` header table, the account-rejection code vocabulary, the stream event names and the subscription-plan maps are the same set as in 1.65.5. Everything else was re-verified against the live catalog and the pricing/docs pages on 2026-09-27 and is unchanged: the catalog's 82 models and its 65 × chat/completions + responses / 9 Claude ids × messages / 8 × chat/completions route mix, the plan map at 52/60/74/82, the full effort map, the Vision set, every other deal, and the DeepSeek peak schedule.
50
+
51
+ - **Two vendored price rows moved.** The official pricing page lowers Step 3.5 Flash's input rate from $0.10 to **$0.09** per 1M tokens, and publishes a `typesafe/jev` row the page did not carry before. Neither changes what the picker shows: Step 3.5 Flash's badge and plan tier are unchanged, and `typesafe/jev` is not in the public catalog and is not served by either chat transport — the System One decision client that used to price it is removed in this same release, so the row simply keeps the table in step with the page. One promotion is on its own clock rather than in this table: Grok 4.7's 40%-off deal lapses at `2026-09-27T23:59:59Z`, at which point the picker badge hides itself and the row returns to the $2.00 / $6.00 / $0.50 list rates on the next sync. Re-run `node scripts/sync-model-prices.mjs` to pick up either move.
52
+
53
+ ### Fixed
54
+
55
+ - **The opt-in stream trace now fingerprints the request, not just the response.** `DSH_COMMANDCODE_TRACE` recorded the raw response chunks and how the stream ended, but nothing about what was asked, so a provider cache hit or miss could not be tied to a request shape. Every attempt now appends a `request` record before it is sent — transport, model, thread id, the message and tool counts, and a SHA-256 of the body, config, system prompt, tool list and message list. It is the same privacy contract as the rest of the trace (counts and hashes only, never the text, and a record that cannot be written never fails the request it observes), and it is skipped entirely when the trace is off, so the default path stays one environment read. `tests/stream-trace.test.ts` pins the record on both a finished request and a pre-stream failure, and asserts the prompt text is absent from the file.
56
+
57
+ - CLI requests now derive a stable UUID thread ID from DSH's prefixed session IDs, so successive requests in an ordinary session keep the same provider thread ID. This preserves request correlation; a cache-hit improvement still requires live evidence.
58
+
59
+ ### Removed
60
+
61
+ - **The AI command guard is gone; dsh's own approval tooling does this job now.** The opt-in `commandGuard` / `commandGuardLevel` settings, the `approval/request` answerer and the `tools/pre-execute` observer it registered, the `typesafe/jev` decision client for `POST /provider/v1/systemone`, their offline explainer script, and the settings-page / dsh-TUI controls and copy are all removed. The plugin no longer touches the `approval` seam at all and no longer calls the System One decision endpoint — Command Code chat, web search, cost readouts, accounts and ZDR are unaffected. A stored `commandGuard` / `commandGuardLevel` value in an existing profile is simply ignored (schemastery keeps unknown keys without validating them), so nothing needs migrating.
62
+
7
63
  ## [0.11.16] - 2026-09-25
8
64
 
9
65
  ### Changed
package/README.md CHANGED
@@ -35,21 +35,22 @@ See [Screenshots](#screenshots) below for what the UI looks like.
35
35
 
36
36
  ## Install
37
37
 
38
- This release supports **dsh 0.1.7-rc.2 and nothing else** — the plugin's peer
38
+ This release supports **dsh 0.2.0-rc.1 and nothing else** — the plugin's peer
39
39
  range is that one version, and its compatibility record names it alone:
40
40
 
41
41
  ```sh
42
42
  dsh plugin --profile web add @mars-sea/dsh-commandcode-provider@latest
43
43
  ```
44
44
 
45
- - **Older dsh releases.** The 0.1.2–0.1.6 line is no longer supported: those
45
+ - **Older dsh releases.** The 0.1.2–0.1.7 line is no longer supported: those
46
46
  engines predate the 0.1.7 settings rewrite, the `RequestMessage` envelope, and
47
47
  the durable image-offload contract, and the compatibility code that bridged
48
- the two was removed. The last plugin release covering them is 0.11.11; the
49
- 0.5.0-era Harness line's last release is 0.9.1. Both are installed by exact
50
- version and neither is maintained:
48
+ the two was removed. The last plugin release covering 0.1.7 is 0.11.17; the
49
+ 0.1.2–0.1.6 line's last release is 0.11.11; the 0.5.0-era Harness line's last
50
+ release is 0.9.1. All are installed by exact version and none is maintained:
51
51
 
52
52
  ```sh
53
+ dsh plugin --profile web add @mars-sea/dsh-commandcode-provider@0.11.17 # dsh 0.1.7
53
54
  dsh plugin --profile web add @mars-sea/dsh-commandcode-provider@0.11.11 # dsh 0.1.2–0.1.6
54
55
  dsh plugin --profile web add @mars-sea/dsh-commandcode-provider@0.9.1 # dsh 0.5.0 line
55
56
  ```
@@ -69,7 +70,7 @@ Fresh pnpm 10 marketplace generations are supported directly. Do not add a separ
69
70
  Update with the same tag you installed with:
70
71
 
71
72
  ```sh
72
- dsh plugin --profile web update @mars-sea/dsh-commandcode-provider@latest # dsh 0.1.7-rc.2
73
+ dsh plugin --profile web update @mars-sea/dsh-commandcode-provider@latest # dsh 0.2.0-rc.1
73
74
  dsh plugin --profile web update @mars-sea/dsh-commandcode-provider@0.9.1 # older dsh (0.5.0 line, unmaintained)
74
75
  ```
75
76
 
@@ -143,7 +144,7 @@ cmd login # writes ~/.commandcode/auth.json
143
144
  cwd: !!js process.cwd()
144
145
  ```
145
146
 
146
- **Engine version.** The plugin is maintained against exactly one engine: **dsh 0.1.7-rc.2**. Its `@deepseek-ai/dsh-*` peer range is `^0.1.7-rc.2` (semver resolves that to 0.1.7-rc.2 alone), and `dsh.compatibility.dshReleases` records that single release. On an older engine the settings page, the message envelope, or the request-image budget will not line up — install the last release that supported your engine (see [Install](#install)) instead of forcing this one.
147
+ **Engine version.** The plugin is maintained against exactly one engine: **dsh 0.2.0-rc.1**. Its `@deepseek-ai/dsh-*` peer range is `^0.2.0-rc.1` (semver resolves that to 0.2.0-rc.1 alone), and `dsh.compatibility.dshReleases` records that single release. On an older engine the settings page, the message envelope, or the request-image budget will not line up — install the last release that supported your engine (see [Install](#install)) instead of forcing this one.
147
148
 
148
149
  ## Usage dashboard
149
150
 
@@ -191,7 +192,7 @@ llm-commandcode:
191
192
 
192
193
  ## Configure
193
194
 
194
- **Settings → Command Code** is organized as **Accounts** (keys, sign-in, live quota, pinning, dedicated models), **Models** (hide out-of-plan models, visible models), **Privacy & security** (zero data retention and the AI command guard, both off by default, see below), **Integrations & display** (serve web search with Command Code, show the quota card in the sidebar, off by default), and a collapsed **Advanced** section (API base URL, request/stream timeouts, transport retries). The working directory is no longer on the page; `workingDir` in config still works.
195
+ **Settings → Command Code** is organized as **Accounts** (keys, sign-in, live quota, pinning, dedicated models), **Models** (hide out-of-plan models, visible models), **Privacy & security** (zero data retention, off by default, see below), **Integrations & display** (serve web search with Command Code, show the quota card in the sidebar, off by default), and a collapsed **Advanced** section (API base URL, request/stream timeouts, transport retries, cache-aware image offload). The working directory is no longer on the page; `workingDir` in config still works.
195
196
 
196
197
  The same options live in `$DSH_HOME/settings.yaml` (changes apply immediately, no restart):
197
198
 
@@ -201,14 +202,17 @@ llm-commandcode:
201
202
  apiBase: https://api.commandcode.ai
202
203
  workingDir: /path/to/project # optional
203
204
  modelsCachePath: ~/.commandcode/models-cache.json
204
- requestTimeoutMs: 60000 # default 60s
205
+ requestTimeoutMs: 300000 # default 300s (the official CLI's own budget)
205
206
  streamIdleTimeoutMs: 300000 # default 300s
207
+ # offloadSeenImagesForCache: true # opt in to CLI cache mitigation; later requests omit old pixels
206
208
  showSidebarQuota: true # optional: show the plans & quota card in the sidebar (default off)
207
- commandGuard: true # optional: let the decision model auto-approve safe shell commands (default off)
208
- commandGuardLevel: medium # optional: auto-approve threshold, high (0.95) | medium (0.9, default) | low (0.8)
209
209
  zdr: true # optional: route requests only through zero-data-retention upstreams (default off)
210
210
  ```
211
211
 
212
+ ### Repeated image cache misses
213
+
214
+ On the CLI transport (`/alpha/generate`), a new image can make Command Code's reported prompt cache retreat to the first historical image even when every previous request message is byte-identical. The opt-in **Stop replaying images already seen** setting (`offloadSeenImagesForCache`) asks dsh's durable `image/offload` surface to replace an older image with a stable text placeholder after this same model has answered with it. The next request keeps its new image; later text can be cached without replaying old pixels. The default is off because the model cannot inspect an offloaded image again unless it rereads the source file or the user reattaches it. This mitigates the observed cost spike; it does not repair Command Code's underlying multimodal cache behavior. [Live evidence and limits](docs/issue-64-cache-review.md).
215
+
212
216
  ## Web search
213
217
 
214
218
  When your deployment's dsh shell mounts the web capability (`@deepseek-ai/dsh-web` + `@deepseek-ai/dsh-tool-web`), the model's `web_search` tool is served by this plugin's `commandcode` search provider — it calls the Command Code Provider API's `/alpha/web-search` endpoint with the **same API key and base URL** as chat. You do not configure a separate search key, endpoint, or model.
@@ -221,31 +225,15 @@ When your deployment's dsh shell mounts the web capability (`@deepseek-ai/dsh-we
221
225
 
222
226
  > This reuses the Command Code Provider API directly (like the official CLI's built-in `web_search`), so it is distinct from a DeepSeek-native search backend.
223
227
 
224
- ## AI command guard
225
-
226
- When dsh is about to ask you to approve a shell command (`bash`/`pwsh`) — a permission preset, a `PreToolUse` hook, or a sandbox escalation — this plugin can ask the Command Code decision model `typesafe/jev` whether the command is safe to run without asking. A confident *yes* turns into a one-shot `allowed-once` grant; everything else asks you exactly as before.
227
-
228
- **Off by default.** Turn it on with the *"Let AI judge shell commands"* toggle (`commandGuard`) in the Privacy & security card, or in your profile config. What the guard does and does not do:
229
-
230
- - It only ever sees commands dsh had **already decided to ask about** — it never widens a permission policy, and it never answers a session whose policy is "never ask".
231
- - A normal approval asks one `safe` question. A call carrying `sandboxPermissions` also asks `escalation_scope` and `escalation_necessity`; the primary safety probability and both escalation probabilities must reach the threshold of the chosen `commandGuardLevel` — **High** 0.95, **Medium** 0.9 (default) or **Low** 0.8; a higher level prompts more often. Thus a narrow case such as `go test ./...` needing an outside build cache can be approved by JEV without prompting the user every time.
232
- - A low-confidence answer, a broad or unnecessary escalation, a timeout (fixed at 3 seconds), a rate limit, a missing key, an unreadable answer, a command longer than 6000 characters, or a match against the built-in denylist (`sudo`, `rm -rf /`, `git push`, publish/upload commands, piping a download into a shell, …) all fall back to the normal prompt.
233
- - The **command text** (plus the agent's own one-line description, the working directory, the requested sandbox mode and the asker's reason) is sent to Command Code to be judged — with the same API key and base URL as chat.
234
- - The decision model has **no zero-data-retention upstream**, so this feature is incompatible with a ZDR-only policy. Leave it off if that matters to you.
235
- - The endpoint is free through 2026-09-24; after that it bills $0.042 per 1M input tokens (output free) — a single decision is a few hundred tokens, and an identical command with the same approval context in the same agent reuses the previous verdict for 10 minutes.
236
-
237
- Every AI decision is logged on the Host — an ordinary grant is `llm-commandcode: command guard auto-approved a bash call (safe probability 0.97, model): <the command>`; a sandbox grant also records its `sandbox scope` and `necessity` probabilities; everything that fell through is `… delegated a bash call: <reason> — <the command>` — and the outcome lands in the session's own approval audit (`approval/asked` / `approval/decided`), so a grant can always be traced back to the verdict that produced it. The log goes to the Host's console (the terminal running `dsh web`, or the desktop app's log), not to the browser.
238
-
239
228
  ## Zero data retention (ZDR)
240
229
 
241
230
  Command Code can serve a request only through upstreams that retain no prompts or completions and never train on them — the same opt-in the official CLI exposes as `CMD_ZDR=1`, and the `x-cmd-zdr: 1` header on the Provider API ([official docs](https://commandcode.ai/docs/resources/zdr)).
242
231
 
243
232
  **Off by default.** Turn it on with the *"Zero data retention (ZDR)"* toggle (`zdr`) in the Privacy & security card, or in your profile config. How this plugin implements it:
244
233
 
245
- - The header is sent on **every chat request** while ZDR is on. The plugin maintains an informational exception list (`KNOWN_NON_ZDR_MODELS`, synced from the official CLI — about 20 models, e.g. `xai/grok-4.5`, `stepfun/Step-3.7-Flash`, `meta/muse-spark-1.3`). A model without an available ZDR upstream fails with `422 cmd_zdr_no_providers`; the request is never retried without the header.
234
+ - The header is sent on **every chat request** while ZDR is on. The plugin maintains an informational exception list (`KNOWN_NON_ZDR_MODELS`, synced from the official CLI — 23 models as of 2026-09-29, e.g. `xai/grok-4.5`, `stepfun/Step-3.7-Flash`, `meta/muse-spark-1.3`). A model without an available ZDR upstream fails with `422 cmd_zdr_no_providers`; the request is never retried without the header.
246
235
  - If a refusal still happens (coverage churn, or no ZDR upstream with spare capacity at that moment), the error names the cause and how to turn ZDR off, instead of surfacing as a bare HTTP 422.
247
236
  - **ZDR usually costs more**: capacity is limited and billed at each upstream's pass-through rates, and which upstream serves a request can change per request. The session-cost readout keeps quoting the ordinary catalog rates; the real per-request price shows in Command Code's Studio usage page.
248
- - The decision model behind the **AI command guard** has no ZDR upstream, so its requests never carry the header even with `zdr` on — the two features stay independent.
249
237
  - Works on every plan; plan credits meter ZDR requests at the plan's default allowance.
250
238
 
251
239
  ## Notes & limitations
@@ -260,7 +248,7 @@ Command Code can serve a request only through upstreams that retain no prompts o
260
248
 
261
249
  ## Permissions & privacy
262
250
 
263
- The plugin only communicates between your local dsh profile and your Command Code account: locally it touches only the credential store and the models cache (plus `~/.commandcode/auth.json` as a last-resort fallback); on the network it calls only the Command Code API. No telemetry. The one optional exception is the **AI command guard** (off by default), which sends the shell command it is asked to judge — see above. With **zero data retention** on (also off by default), chat requests require a ZDR upstream; models without one fail instead of running without the protection.
251
+ The plugin only communicates between your local dsh profile and your Command Code account: locally it touches only the credential store and the models cache (plus `~/.commandcode/auth.json` as a last-resort fallback); on the network it calls only the Command Code API. No telemetry. With **zero data retention** on (also off by default), chat requests require a ZDR upstream; models without one fail instead of running without the protection.
264
252
 
265
253
  ## Disabling / uninstalling
266
254
 
package/README.zh-CN.md CHANGED
@@ -33,15 +33,16 @@
33
33
 
34
34
  ## 安装
35
35
 
36
- 本版本**只支持 dsh 0.1.7-rc.2**:插件的 peer 范围就是这一个版本,兼容性记录里也只列它。
36
+ 本版本**只支持 dsh 0.2.0-rc.1**:插件的 peer 范围就是这一个版本,兼容性记录里也只列它。
37
37
 
38
38
  ```sh
39
39
  dsh plugin --profile web add @mars-sea/dsh-commandcode-provider@latest
40
40
  ```
41
41
 
42
- - **更早的 dsh 版本。** 0.1.2–0.1.6 线已不再支持:那些引擎早于 0.1.7 的设置重写、`RequestMessage` 消息封装和持久化图片卸载契约,用来桥接两代引擎的兼容代码已经删除。覆盖它们的最后一个插件版本是 0.11.11;0.5.0 时代 Harness 线的最后一个版本是 0.9.1。两者都按精确版本安装,且都不再维护:
42
+ - **更早的 dsh 版本。** 0.1.2–0.1.7 线已不再支持:那些引擎早于 0.1.7 的设置重写、`RequestMessage` 消息封装和持久化图片卸载契约,用来桥接两代引擎的兼容代码已经删除。覆盖 0.1.7 的最后一个插件版本是 0.11.17;0.1.2–0.1.6 线的最后一个版本是 0.11.11;0.5.0 时代 Harness 线的最后一个版本是 0.9.1。三者都按精确版本安装,且都不再维护:
43
43
 
44
44
  ```sh
45
+ dsh plugin --profile web add @mars-sea/dsh-commandcode-provider@0.11.17 # dsh 0.1.7
45
46
  dsh plugin --profile web add @mars-sea/dsh-commandcode-provider@0.11.11 # dsh 0.1.2–0.1.6
46
47
  dsh plugin --profile web add @mars-sea/dsh-commandcode-provider@0.9.1 # dsh 0.5.0 线
47
48
  ```
@@ -61,7 +62,7 @@ dsh plugin --profile web add @mars-sea/dsh-commandcode-provider@0.11.15
61
62
  用与安装时相同的 tag 更新:
62
63
 
63
64
  ```sh
64
- dsh plugin --profile web update @mars-sea/dsh-commandcode-provider@latest # dsh 0.1.7-rc.2
65
+ dsh plugin --profile web update @mars-sea/dsh-commandcode-provider@latest # dsh 0.2.0-rc.1
65
66
  dsh plugin --profile web update @mars-sea/dsh-commandcode-provider@0.9.1 # 更早的 dsh(0.5.0 线,不再维护)
66
67
  ```
67
68
 
@@ -135,7 +136,7 @@ cmd login # 写入 ~/.commandcode/auth.json
135
136
  cwd: !!js process.cwd()
136
137
  ```
137
138
 
138
- **引擎版本要求。** 插件只针对一个引擎维护:**dsh 0.1.7-rc.2**。它的 `@deepseek-ai/dsh-*` peer 范围是 `^0.1.7-rc.2`(按 semver,这只会解析到 0.1.7-rc.2),`dsh.compatibility.dshReleases` 也只记录这一个版本。在更早的引擎上,设置页、消息封装或请求图片预算总有一处对不上——请改装支持你所用引擎的最后一个版本,而不是硬装这一个。
139
+ **引擎版本要求。** 插件只针对一个引擎维护:**dsh 0.2.0-rc.1**。它的 `@deepseek-ai/dsh-*` peer 范围是 `^0.2.0-rc.1`(按 semver,这只会解析到 0.2.0-rc.1),`dsh.compatibility.dshReleases` 也只记录这一个版本。在更早的引擎上,设置页、消息封装或请求图片预算总有一处对不上——请改装支持你所用引擎的最后一个版本,而不是硬装这一个。
139
140
 
140
141
  ## 用量面板
141
142
 
@@ -183,7 +184,7 @@ llm-commandcode:
183
184
 
184
185
  ## 配置
185
186
 
186
- **设置 → Command Code** 分为:**账户**(密钥、网页登录、实时额度、固定账户、专用模型)、**模型**(隐藏套餐外模型、可见模型)、**隐私与安全**(零数据保留 ZDR 与 AI 命令安全预判,均默认关闭,见下)、**集成与显示**(用 Command Code 承载联网搜索、在侧边栏显示额度卡片,默认关闭),以及默认折叠的**高级设置**(API 地址、请求/流超时、传输重试次数)。工作目录已不在页面上显示,配置中的 `workingDir` 仍然有效。
187
+ **设置 → Command Code** 分为:**账户**(密钥、网页登录、实时额度、固定账户、专用模型)、**模型**(隐藏套餐外模型、可见模型)、**隐私与安全**(零数据保留 ZDR,默认关闭,见下)、**集成与显示**(用 Command Code 承载联网搜索、在侧边栏显示额度卡片,默认关闭),以及默认折叠的**高级设置**(API 地址、请求/流超时、传输重试次数、历史图片缓存优化)。工作目录已不在页面上显示,配置中的 `workingDir` 仍然有效。
187
188
 
188
189
  同一组选项也位于 `$DSH_HOME/settings.yaml`(修改即刻生效,无需重启):
189
190
 
@@ -193,14 +194,17 @@ llm-commandcode:
193
194
  apiBase: https://api.commandcode.ai
194
195
  workingDir: /path/to/project # 可选
195
196
  modelsCachePath: ~/.commandcode/models-cache.json
196
- requestTimeoutMs: 60000 # 默认 60s
197
+ requestTimeoutMs: 300000 # 默认 300s(与官方 CLI 一致)
197
198
  streamIdleTimeoutMs: 300000 # 默认 300s
199
+ # offloadSeenImagesForCache: true # 可选:模型回复后不再重发旧图片,默认关闭
198
200
  showSidebarQuota: true # 可选:在侧边栏显示套餐与配额卡片(默认关闭)
199
- commandGuard: true # 可选:让决策模型自动放行安全的 shell 命令(默认关闭)
200
- commandGuardLevel: medium # 可选:自动放行阈值,high(0.95)| medium(0.9,默认)| low(0.8)
201
201
  zdr: true # 可选:请求只经由零数据保留上游(默认关闭)
202
202
  ```
203
203
 
204
+ ### 连续图片导致的缓存回退
205
+
206
+ 在 CLI 传输(`/alpha/generate`)上,即使此前所有请求消息逐字节一致,新图片也可能让服务端报告的缓存命中量退回第一张历史图片处。可在「高级设置」打开「已看过的图片不再重复发送」(`offloadSeenImagesForCache`):同一模型看过图片并回复后,插件通过 dsh 持久的 `image/offload` 事件把旧图替换成稳定文字占位;当前新图仍会送给模型,后续文本更容易保持缓存。**默认关闭**,因为模型如需重新查看已 offload 的图片,必须重新读取原文件或由用户再次附加。这是费用缓解方案,未修复 Command Code 服务端的多模态缓存行为。[实测证据与限制](docs/issue-64-cache-review.md)。
207
+
204
208
  ## 联网搜索
205
209
 
206
210
  当你的 dsh 部署加载了 web 能力(`@deepseek-ai/dsh-web` + `@deepseek-ai/dsh-tool-web`)时,模型所用的 `web_search` 工具会由本插件的 `commandcode` 搜索 provider 承载——它用**与聊天相同的 API key 与 base URL** 调用 Command Code Provider API 的 `/alpha/web-search` 端点。你无需另外配置搜索 key、端点或模型。
@@ -213,31 +217,15 @@ llm-commandcode:
213
217
 
214
218
  > 这里直接使用 Command Code Provider API(与官方 CLI 内置的 `web_search` 相同),因此与 DeepSeek 原生搜索后端不同。
215
219
 
216
- ## AI 命令安全预判
217
-
218
- 当 dsh 准备就某条 shell 命令(`bash`/`pwsh`)征求你同意时——无论是权限预设、`PreToolUse` 钩子还是沙箱提权——本插件可以先让 Command Code 的决策模型 `typesafe/jev` 判断这条命令是否安全到无需询问。判断为「安全」且把握足够高时,直接放行这一次调用;其余情况一律照旧询问你。
219
-
220
- **默认关闭。** 在「隐私与安全」卡片里打开「用 AI 预判命令是否安全」(`commandGuard`),或写进 profile 配置。它的行为边界:
221
-
222
- - 它只会看到 dsh **本来就要询问你**的命令——不会放宽任何权限策略,也不会替「从不询问」(policy 为 `never`)的会话作答。
223
- - 普通审批只问一个 `safe` 问题;带有 `sandboxPermissions` 的沙箱提权会额外问 `escalation_scope` 与 `escalation_necessity`。只有主安全概率以及这两个提权概率都不低于所选「自动放行阈值」(`commandGuardLevel`:高 0.95、中 0.9(默认)、低 0.8;阈值越高弹窗越多)时才跳过弹窗;因此像 `go test ./...` 需要访问工作区外构建缓存的场景,只要 JEV 确认提权范围狭窄且必要,就能由 AI 放行,不必每次打扰用户。
224
- - 任一概率不足、提权范围过宽或没有必要、超时(固定 3 秒)、限流、缺 key、返回体读不懂、命令超过 6000 字符,或命中内置危险名单(`sudo`、`rm -rf /`、`git push`、发布/上传类命令、把下载内容管进 shell 等),全部回到普通弹窗。
225
- - 送往 Command Code 判断的内容包括**命令原文**、模型自己写的一行描述、工作目录、请求的 sandbox 模式与询问原因;使用与聊天相同的 API key 与 base URL。
226
- - 该决策模型**没有零数据保留(ZDR)上游**,因此本功能与「只走 ZDR」的合规要求不兼容;如果你在意这一点,请保持关闭。
227
- - 该端点 2026-09-24 前免费,之后按输入 $0.042/1M tokens 计费(输出免费)——单次判断只有几百 tokens;同一 agent 中命令和审批场景都相同时,10 分钟内复用上一次结论。
228
-
229
- 每次 AI 决策都会在 Host 侧写日志——普通放行是 `llm-commandcode: command guard auto-approved a bash call (safe probability 0.97, model): <命令>`;沙箱提权还会附带 `sandbox scope` 与 `necessity` 概率;其余是 `… delegated a bash call: <原因> — <命令>`——并进入会话自身的审批审计(`approval/asked` / `approval/decided`),所以任何一次放行都能追溯到产生它的判断。日志打到 Host 控制台(运行 `dsh web` 的终端,或桌面端日志),不进浏览器。
230
-
231
220
  ## 零数据保留(ZDR)
232
221
 
233
222
  Command Code 可以让请求只经由「不留存提示词与回复、也不用于训练」的上游——官方 CLI 的开关是 `CMD_ZDR=1`,Provider API 上则是在请求头发 `x-cmd-zdr: 1`([官方文档](https://commandcode.ai/docs/resources/zdr))。
234
223
 
235
224
  **默认关闭。** 在「隐私与安全」卡片里打开「零数据保留(ZDR)」(`zdr`),或写进 profile 配置。本插件的实现方式:
236
225
 
237
- - 开启后,**每次聊天请求**都会带上 ZDR 请求头。插件仍维护官方 CLI 的例外名单(`KNOWN_NON_ZDR_MODELS`,约 20 个模型,例如 `xai/grok-4.5`、`stepfun/Step-3.7-Flash`、`meta/muse-spark-1.3`)供查询。没有可用 ZDR 上游时,服务端返回 `422 cmd_zdr_no_providers`;插件不会去掉请求头重试。
226
+ - 开启后,**每次聊天请求**都会带上 ZDR 请求头。插件仍维护官方 CLI 的例外名单(`KNOWN_NON_ZDR_MODELS`,截至 2026-09-29 共 23 个模型,例如 `xai/grok-4.5`、`stepfun/Step-3.7-Flash`、`meta/muse-spark-1.3`)供查询。没有可用 ZDR 上游时,服务端返回 `422 cmd_zdr_no_providers`;插件不会去掉请求头重试。
238
227
  - 万一仍被拒绝(名单过期,或那一刻没有空闲的 ZDR 上游容量),错误信息会说明原因并给出关闭 ZDR 的办法,而不是抛出一个光秃秃的 HTTP 422。
239
228
  - **ZDR 通常更贵**:容量有限,按各上游实价透传计费,且每次请求落在哪个上游可能不同。会话费用读数仍按常规目录价估算;真实单价见 Command Code Studio 的用量页。
240
- - **AI 命令安全预判**背后的决策模型没有 ZDR 上游,因此即使打开 `zdr`,它的请求也永远不带该请求头——两个功能互不影响。
241
229
  - 各套餐均可使用;ZDR 请求按套餐的默认额度(而非提升额度)计量。
242
230
 
243
231
  ## 注意事项与限制
@@ -251,7 +239,7 @@ Command Code 可以让请求只经由「不留存提示词与回复、也不用
251
239
 
252
240
  ## 权限与隐私
253
241
 
254
- 本插件只在本地与你的 Command Code 账号之间通信:本地仅读写凭据存储与模型缓存文件(兜底读取 `~/.commandcode/auth.json`);网络仅访问 Command Code API。无遥测。唯一的可选例外是 **AI 命令安全预判**(默认关闭),它会把待判断的命令文本发送出去——见上文。打开**零数据保留**(同样默认关闭)后,聊天请求必须经由 ZDR 上游;没有可用 ZDR 上游的模型会报错,不会按常规方式继续发送。
242
+ 本插件只在本地与你的 Command Code 账号之间通信:本地仅读写凭据存储与模型缓存文件(兜底读取 `~/.commandcode/auth.json`);网络仅访问 Command Code API。无遥测。打开**零数据保留**(同样默认关闭)后,聊天请求必须经由 ZDR 上游;没有可用 ZDR 上游的模型会报错,不会按常规方式继续发送。
255
243
 
256
244
  ## 关闭 / 卸载
257
245