@mars-sea/dsh-commandcode-provider 0.11.8 → 0.11.9
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 +39 -0
- package/icon.svg +5 -0
- package/lib/client.js +470 -22
- package/lib/client.js.map +1 -1
- package/lib/index.d.ts +93 -5
- package/lib/index.js +702 -182
- package/lib/index.js.map +1 -1
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +43 -38
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,45 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.11.9] - 2026-09-22
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- **dsh 0.1.7-alpha.1 is a declared-compatible engine, and the release that rewrote settings needs no user action to keep working.** The engine replaced its settings.yaml document with schema-derived Config forms (`SettingsProvider` → `SettingsForms`, and `installSection` plus the browser's `settingsScope` service are both gone), so this release carries the two-generation adaptation: every Config field except the top-level `apiKey` secret is marked volatile through a feature-detected bridge (`src/config-volatile.ts`) — an unmarked field would be invisible to and unwritable from 0.1.7's settings page — while the ≤0.1.6 registration keeps an unmarked schema and an unwrapped config, exactly as that generation validates and clones it; volatile writes commit in place and re-run the two facts not re-derived per use (the web-search selection and the dsh-TUI section re-registration); and the browser side speaks the `remote.settings` describe/mutate wire directly through the plugin's own scope, so one implementation serves 0.1.2-rc.1 through 0.1.7-alpha.1. The one-time settings.yaml import needs no cooperation from the plugin: the engine looks the section up by profile entry id, which is the same string as the section id (`llm-commandcode`) and must stay that way. `npm run test:engine` passes against 0.1.7-alpha.1 (the bundle links and evaluates there — the request-image policy is the durable one — and the adapter is driven with each engine's own message constructors), so a release installed beside the newest engine is proven to load before it ships.
|
|
14
|
+
|
|
15
|
+
- **The plugin manager now shows a real identity for this plugin on dsh 0.1.7+.** The package declares `icon` (`icon.svg`) and ships `locale/en.json` + `locale/zh.json` under the `locale/*.json` export, so the plugin-management page renders "Command Code" / "Command Code 提供方" with a description instead of a bare package name and a placeholder icon. Both are pure metadata: older engines ignore them, and every other install path is unaffected.
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- **The account card's "无法连接 Command Code 服务 / 请检查网络连接或 API 地址设置" is no longer a guess, and three of its false causes are gone.** `classifyTotalFailure()` answers `network` whenever all four account endpoints throw without a status, so one verdict covered a real outage, a per-request timeout, a key no HTTP header can carry and an unparseable `apiBase` — while the card hid the only evidence, the four per-endpoint messages. Now: (a) the account path validates the key through the harness's own `assertUsableApiKey`, as chat already did, and answers `blocked: 'invalid-key'` with the credential message instead of four phantom transport failures (a key pasted with an embedded newline or a full-width character makes `fetch` throw a `TypeError` **before any I/O**, once per endpoint — the connection was fine); (b) the four endpoints use the connection's own `requestTimeoutMs` (the SAME 60 s budget as a chat call) instead of the catalog's hidden 10 s cap, which on a slow link made every account query time out while chat kept working; (c) both surfaces now render `report.failures`, so the next occurrence names its actual cause. Refresh keeps working in every case — it was the diagnosis, not the fetch lifecycle, that was wrong.
|
|
20
|
+
|
|
21
|
+
- **Turning the web-search toggle off no longer destroys the user's own `commandcode` pin.** `applyCommandCodeSearchSelection()` remembered "the field already read `commandcode` before we touched it" as `displaced: undefined`, and its disable branch wrote that value back — which is not the no-op its contract promises. The field was cleared, dsh-web reads a cleared `searchProviderId` as auto-select, and with a second usable provider registered (the shipped `deepseek-official` is usable whenever a DeepSeek key resolves) EVERY later search threw `WEB_PROVIDER_AMBIGUOUS`; the same write ran on plugin unload. The tracked selection now carries `preexisting`, so a field we did not take over is left alone, while a field that was genuinely unset at takeover is still cleared (or the toggle would leave Command Code serving). `tests/web-search.test.ts` pins both directions, including an end-to-end boot with `searchProvider: commandcode`.
|
|
22
|
+
|
|
23
|
+
- **A 5xx that proxies a credits/plan rejection rotates the account instead of being retried as a transient outage.** `classifyAccountRejection()` hoisted only `RATE_LIMITED` above its `status >= 500` guard, so `503 {"code":"INSUFFICIENT_CREDITS"}`, `502 MODEL_NOT_IN_PLAN`, `USAGE_EXCEEDED` and `PREMIUM_CREDITS_EXHAUSTED` were classified as "the provider is unavailable": no rotation, and `SERVER` put the byte-identical request on dsh-llm-retry's 1000-attempt cadence while the configured sibling accounts were never consulted. All four structured codes (`ACCOUNT_UNAVAILABLE_CODES`) are now status-independent, exactly like `RATE_LIMITED`; only the prose scan stays confined to 4xx.
|
|
24
|
+
|
|
25
|
+
- **A provider-published rate-limit reset of an implausible magnitude no longer breaks the failure it belongs to.** `error.rateLimit.reset` is seconds multiplied by 1000 and was trusted verbatim: `1e300` reached `new Date(1e303).toISOString()` and threw `RangeError: Invalid time value` out of `stream()` in place of the intended retryable `RATE_LIMIT`, and a mark pinned to it became a `cooldown` that no probe ever revisits (the account left rotation for the life of the process). Resets are now accepted only within `MAX_TRUSTED_RESET_MS` (30 days — the longest window this provider meters, so the bound rejects nonsense rather than second-guessing a schedule) at BOTH seams: the rejection body and the `/alpha/billing/credits` probe. A dropped reset degrades the mark to `unknown`, which the probe pass re-checks.
|
|
26
|
+
|
|
27
|
+
- **An in-band stream error's `statusCode` no longer outvotes a terminal marker or `isRetryable: false`.** `{statusCode: 500, message: 'insufficient credits'}` was reported as retryable `SERVER`, so a permanent quota/plan refusal was replayed on the 1000-attempt cadence. The terminal markers now also match the spaced spelling (the separator is normalized, as the pre-stream classifier already did), and an explicit refusal or a terminal marker outranks a retryable status; an explicit `isRetryable: true` still wins.
|
|
28
|
+
|
|
29
|
+
- **The first usage report no longer sticks as a false "remote is not mounted" error.** The plugin's `remote.commandcode` namespace lands one wire round-trip after the plugin asks for it, and a surface that asked first (the sidebar quota card does, on every boot) stored the synthetic not-mounted failure as `status: 'error'` — and the card's `shouldRefresh` only fires from `idle`, so nothing retried it until the panel's two-minute tick. The mount callback now re-reads the usage report through a seam declared before the effect (a `const` read in its temporal dead zone would throw). `tests/client-boot.test.ts` boots with the namespace deferred by a macrotask and asserts that re-read.
|
|
30
|
+
|
|
31
|
+
- **A settings directory whose FIRST read fails retries instead of leaving the page permanently disabled.** With nothing ever held, the mirror keeps its initial `status: 'loading'`/`writable: false` snapshot, which the settings page renders as a fully disabled form behind a "read-only" banner — and the forwarded invalidations need a live Host to fire, so a Host that was still starting up left the page dead until a reload. `SettingsDescribeMirror` now retries a first-read failure on a bounded 1/2/4 s ladder (`SETTINGS_DESCRIBE_RETRY_MS`, injectable timer, cancelled on disposal); a failure with a HELD view schedules nothing, because the page is already showing the last good document.
|
|
32
|
+
|
|
33
|
+
- **A concurrent `loginBegin` no longer leaks a loopback login server.** `begin()` rejoin read a status that only becomes `waiting` after two awaits, so two GUI tabs (or a Sign-in following a cancel that arrived too early to retire anything) each bound their own server — and only the last one was reachable by `teardown()`, so the orphan kept listening and answering `/callback` for the process's lifetime; ten of those exhausted the login port window. `begin()` is now single-flight across the bind, a `cancel()` in that window sets `cancelPending` (observed after the bind, which closes the fresh server and publishes `failed/cancelled`), and `bindServer` closes a server it does not track.
|
|
34
|
+
|
|
35
|
+
- **`DSH_COMMANDCODE_TRACE=false` (or `0`/`no`/`off`) now disables the trace instead of enabling it.** Only `1`/`true`/`yes`/`on` were recognized as flags and everything else was taken for a path, so an explicit disable switched the trace ON and wrote the conversation to a file named `false` in the process's working directory.
|
|
36
|
+
|
|
37
|
+
- **DSH 0.1.7 tool results survive into the next model request.** That engine uses `role: 'tool'` with message-level `toolCallId` and raw content blocks; the adapter only recognized the older user-role `tool-result` wrapper and silently discarded both results and their paired calls on both transports. The model consequently lost completed work, could repeat tools, and could end with only a statement of intent. Both envelopes now share pairing and serialization, including errors, images and long call ids. Engine smoke tests capture both request bodies using each engine's actual message constructors.
|
|
38
|
+
|
|
39
|
+
- **Reasoning-only replies no longer silently complete a turn.** Both transports now validate that a terminal response contains answer text or a tool call before emitting a success finish. Empty completions use the existing `EMPTY_RESPONSE` retry policy; output-limit completions without an answer report non-retryable `OUTPUT_TOKEN_LIMIT` with the actual budget and available usage, without assuming all tokens went to reasoning. Received usage is retained on failure, and trace terminal diagnostics survive the payload cap.
|
|
40
|
+
|
|
41
|
+
- **A reply cut off mid-generation is a reported failure again, not a turn that quietly finished** (report: 《调用会莫名其妙的自动停止,并没有任何报错信息》). The mechanism was in the stream tail: when the response body ended without its transport's terminal event — the gateway or an intermediary closing the socket mid-generation — the adapter closed the open blocks and yielded a clean `finish: { kind: 'stop' }`. Both transports DO terminate explicitly (the CLI transport with a `finish` event, the Provider API with a final `finish_reason` chunk), so an EOF there is not a normal ending, and reporting it as one is exactly why a truncated answer arrived with no error in the UI and nothing in the session log: the harness saw a completed turn. The tail now classifies. `!sawContent` (no visible text AND no tool call) keeps the pre-existing retryable `EMPTY_RESPONSE`, because such a request is free to repeat and the retry policy's whitelist absorbs it — a cut during a long thinking phase lands there, unchanged. A cut that HAD produced content throws `STREAM_CLOSED`, the code `dsh-llm-deepseek`'s own `translate()` raises for the same condition ("… stream ended before message_stop"); a tool call still in flight when the socket closed is dropped rather than executed with truncated JSON arguments. The code is deliberately OUTSIDE `providerRetryPolicy()`'s whitelist for now: every retry of a content-bearing cut re-generates the whole answer, so auto-retrying is a cost decision rather than an obvious win (adding it to `retryableCodes`, ideally bounded by `src/transport-retry.ts`'s per-agent budget, is the one-line opt-in). `tests/adapter.test.ts` pins both endings on both transports, the in-flight-tool-call refusal, and the untouched `EMPTY_RESPONSE` path.
|
|
42
|
+
- **The Command Code settings page is interactive again on dsh 0.1.7** (report: 《Command Code 设置页面所有的按钮和开关都无法点击,下拉选择模型也无法使用》). The page rendered — with its read-only banner, empty text fields and every switch, button and select disabled — because the plugin's own settings scope read `ctx.remote.settings` directly, and `remote.settings` is a Cordis service NESTED under the `remote` service (api-gateway registers one `Service` per contribution namespace as `remote.<ns>`): a read from a fiber that does not declare that name in its inject list is answered with `Error: cannot get property "remote.settings" without inject`. The throw landed inside the scope's own `try`/`catch`, so nothing was logged and no `settings/describe` ever left the browser; the mirror stayed `idle`, the scope kept its INITIAL snapshot (`status: 'loading'`, `writable: false`, `value: undefined`), and since the page disables its controls on `!state.writable`, everything was inert. The boolean toggles were the confusing part of the report: they fall back to their hardcoded `defaultChecked` while the scope has no value, so they looked "set" over empty text inputs. `createSettingsScope()` now takes the namespace as a resolver, and the client entry fills it from the object `ctx.inject(['remote.settings'], …)` resolves — the same shape the `remote.credentials` and `remote.commandcode` reads have always used. `$host` / `$on` / `$mount` are plain members of the declared `remote` service, so those stay direct reads, and no namespace was added to the exported client `inject` list: gating the whole client half on a service a profile may not serve would silence every surface, which is the failure this scope is built to avoid. Verified end to end against a real dsh 0.1.7-alpha.1 web app (the page converges on the stored row, a toggle marks it dirty, the model multi-select opens its 76-model list, and a save lands in the profile document and survives a reload); `tests/settings-scope.test.ts` now builds its fake context with a `settings` getter that throws the engine's exact error and covers the unmounted → mount + `refresh()` handshake, and `tests/client-boot.test.ts` mounts its namespaces as real `remote.<ns>` Cordis services — with the old direct read restored, the boot suite fails.
|
|
43
|
+
|
|
44
|
+
### Added
|
|
45
|
+
|
|
46
|
+
- **`DSH_COMMANDCODE_TRACE=<path>`: an opt-in raw-stream trace, so the next "it stopped in the middle" report arrives with evidence** (`src/stream-trace.ts`). A cut stream is diagnosed from the wire or not at all — which events actually arrived, how long the socket then sat silent, and how it ended are the facts that separate "the provider closed it" from "our parser missed the terminal event", and until now the adapter kept none of them. With the variable set (a bare `1`/`true`/`yes`/`on` writes to `$TMPDIR/dsh-commandcode-stream.jsonl`), every request appends JSONL: one record per raw chunk carrying the provider's own payload verbatim, plus `response` / `eof` / `idle-timeout` / `read-error` / `end` / `stream-close`, each stamped with milliseconds since that request's first byte. Records never include the request body, an API key, or a request header; one request is capped at 4 MiB (`trace-capped` names the cap); a path that cannot be written turns the trace off rather than failing the request it is observing; and unset, the whole facility is one environment read and some inert calls. `scripts/probe-stream.mjs` is the companion isolation test — it drives the adapter alone, with no harness and no session, so a cut seen there is the provider's while a clean `finished` puts the cut above the adapter (`PROBE_MODEL` / `PROBE_MAX_TOKENS` / `PROBE_PROMPT` / `PROBE_TRACE` override its defaults). `tests/stream-trace.test.ts` pins switch resolution, the JSONL shape, the cap, the degradation paths, and the adapter wiring on both a finished and a cut stream. Both live probes so far (`deepseek/deepseek-v4.1-flash`, 13 and 244 chunks) reached `finished` on `/provider/v1/chat/completions`, so the gateway does not cut unconditionally.
|
|
47
|
+
|
|
9
48
|
## [0.11.8] - 2026-09-22
|
|
10
49
|
|
|
11
50
|
### Changed
|
package/icon.svg
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="Command Code">
|
|
2
|
+
<rect width="64" height="64" rx="14" fill="#0F172A"/>
|
|
3
|
+
<path d="M18 20 L30 32 L18 44" fill="none" stroke="#4ADE80" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
|
|
4
|
+
<path d="M34 44 L48 44" fill="none" stroke="#94A3B8" stroke-width="6" stroke-linecap="round"/>
|
|
5
|
+
</svg>
|
package/lib/client.js
CHANGED
|
@@ -36,6 +36,424 @@ window.__ModuleLoader__.load({
|
|
|
36
36
|
};
|
|
37
37
|
}
|
|
38
38
|
//#endregion
|
|
39
|
+
//#region src/client/settings-scope.ts
|
|
40
|
+
const MEMORY_STATUS = "unavailable";
|
|
41
|
+
/**
|
|
42
|
+
* Backoff before re-reading a directory whose FIRST read failed, in millis.
|
|
43
|
+
*
|
|
44
|
+
* A first read can lose to a Host that is still coming up (the gateway's WS
|
|
45
|
+
* handshake, a settings service mid-start): the failure is transient, but the
|
|
46
|
+
* scope has nothing held, so it keeps its initial `{ status: 'loading',
|
|
47
|
+
* writable: false }` snapshot — and every control on the settings page renders
|
|
48
|
+
* disabled behind a "read-only" banner. Nothing else would ever re-read it:
|
|
49
|
+
* the forwarded `settings/document-updated` and `connection/reset` signals need
|
|
50
|
+
* a live Host to fire, and the namespace only re-mounts on a page reload. So a
|
|
51
|
+
* failure with NOTHING held retries on this bounded ladder; a failure with a
|
|
52
|
+
* held view keeps it (no retry — the page is already showing the last good
|
|
53
|
+
* document, and `tests/settings-scope.test.ts` pins that).
|
|
54
|
+
*/
|
|
55
|
+
const SETTINGS_DESCRIBE_RETRY_MS = [
|
|
56
|
+
1e3,
|
|
57
|
+
2e3,
|
|
58
|
+
4e3
|
|
59
|
+
];
|
|
60
|
+
/** The production timer: a browser/node timeout, no polling. */
|
|
61
|
+
const REAL_SETTINGS_TIMER = {
|
|
62
|
+
set(callback, ms) {
|
|
63
|
+
const handle = setTimeout(callback, ms);
|
|
64
|
+
handle.unref?.();
|
|
65
|
+
return handle;
|
|
66
|
+
},
|
|
67
|
+
clear(handle) {
|
|
68
|
+
clearTimeout(handle);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
/** Shallow equality over the snapshot fields, so an unchanged derive never churns subscribers. */
|
|
72
|
+
function sameSnapshot(a, b) {
|
|
73
|
+
return a.status === b.status && Object.is(a.value, b.value) && Object.is(a.base, b.base) && Object.is(a.user, b.user) && Object.is(a.revision, b.revision) && a.writable === b.writable && a.mode === b.mode;
|
|
74
|
+
}
|
|
75
|
+
/** Accept a decoded row value only when it is the object a settings section is. */
|
|
76
|
+
function decodeRow(value) {
|
|
77
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
|
|
78
|
+
return value;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* One shared mirror of the Host `settings.describe()` view.
|
|
82
|
+
*
|
|
83
|
+
* The run loop is the harness's own: serialize behind `inFlight`, fold a
|
|
84
|
+
* concurrent invalidation into exactly one rerun, and hold the previous view
|
|
85
|
+
* across a failed read (status stays `ready` with an `error`, or returns to
|
|
86
|
+
* `idle` when nothing was ever held).
|
|
87
|
+
*/
|
|
88
|
+
var SettingsDescribeMirror = class {
|
|
89
|
+
resolveRemote;
|
|
90
|
+
timer;
|
|
91
|
+
store;
|
|
92
|
+
persistence;
|
|
93
|
+
inFlight;
|
|
94
|
+
rerun = false;
|
|
95
|
+
generation = 0;
|
|
96
|
+
/** Pending retry handle and how many of {@link SETTINGS_DESCRIBE_RETRY_MS} are spent. */
|
|
97
|
+
retryHandle;
|
|
98
|
+
retries = 0;
|
|
99
|
+
disposed = false;
|
|
100
|
+
/**
|
|
101
|
+
* @param resolveRemote - Reads the inject-captured `remote.settings` namespace.
|
|
102
|
+
* @param persistence - Client-selected Host persistence; non-loopback pages may stay process-local.
|
|
103
|
+
* @param timer - Retry timer seam (see {@link SETTINGS_DESCRIBE_RETRY_MS}).
|
|
104
|
+
*/
|
|
105
|
+
constructor(resolveRemote, persistence, timer = REAL_SETTINGS_TIMER) {
|
|
106
|
+
this.resolveRemote = resolveRemote;
|
|
107
|
+
this.timer = timer;
|
|
108
|
+
this.persistence = persistence;
|
|
109
|
+
this.store = createSnapshotStore({
|
|
110
|
+
status: persistence === "host" ? "idle" : MEMORY_STATUS,
|
|
111
|
+
view: null,
|
|
112
|
+
error: null
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
getSnapshot() {
|
|
116
|
+
return this.store.getSnapshot();
|
|
117
|
+
}
|
|
118
|
+
subscribe(listener) {
|
|
119
|
+
return this.store.subscribe(listener);
|
|
120
|
+
}
|
|
121
|
+
/** The cheap idempotent entry: kick a first read (a no-op on memory persistence). */
|
|
122
|
+
ensure() {
|
|
123
|
+
this.load();
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Refresh from the Host. A call during an in-flight read marks one rerun
|
|
127
|
+
* after it settles instead of racing a second wire read.
|
|
128
|
+
* @returns settlement after this call's freshness is reflected.
|
|
129
|
+
*/
|
|
130
|
+
load() {
|
|
131
|
+
if (this.disposed || this.persistence === "memory") return Promise.resolve();
|
|
132
|
+
this.clearRetry();
|
|
133
|
+
if (this.inFlight !== void 0) {
|
|
134
|
+
this.rerun = true;
|
|
135
|
+
return this.inFlight;
|
|
136
|
+
}
|
|
137
|
+
const run = Promise.resolve().then(() => this.run());
|
|
138
|
+
this.inFlight = run;
|
|
139
|
+
return run;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Fold one write answer's namespace ROW into the held view without a wire
|
|
143
|
+
* read, and invalidate any read still in flight: a read begun before the
|
|
144
|
+
* write committed must not publish the pre-write document over it. With no
|
|
145
|
+
* held document the answer is not published as a partial document — the
|
|
146
|
+
* next read (or the failed-write recovery) picks it up.
|
|
147
|
+
* @param row - The namespace view a settings write answered with.
|
|
148
|
+
*/
|
|
149
|
+
acceptView(row) {
|
|
150
|
+
const before = this.store.getSnapshot();
|
|
151
|
+
this.generation += 1;
|
|
152
|
+
if (this.inFlight !== void 0) this.rerun = true;
|
|
153
|
+
if (before.view === null) return;
|
|
154
|
+
const held = before.view;
|
|
155
|
+
const namespaces = held.namespaces?.some((candidate) => candidate.ns === row.ns) ? held.namespaces.map((candidate) => candidate.ns === row.ns ? row : candidate) : [...held.namespaces ?? [], row];
|
|
156
|
+
this.store.set({
|
|
157
|
+
...before,
|
|
158
|
+
view: {
|
|
159
|
+
...held,
|
|
160
|
+
namespaces
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
async run() {
|
|
165
|
+
try {
|
|
166
|
+
for (;;) {
|
|
167
|
+
const before = this.store.getSnapshot();
|
|
168
|
+
if (before.status === "idle") this.store.set({
|
|
169
|
+
...before,
|
|
170
|
+
status: "loading"
|
|
171
|
+
});
|
|
172
|
+
this.rerun = false;
|
|
173
|
+
const generation = ++this.generation;
|
|
174
|
+
let outcome;
|
|
175
|
+
try {
|
|
176
|
+
const namespace = this.resolveRemote();
|
|
177
|
+
if (namespace === void 0) throw new Error("the settings remote namespace is not mounted");
|
|
178
|
+
const response = await namespace.describe();
|
|
179
|
+
if (response.ok) {
|
|
180
|
+
const value = response.value;
|
|
181
|
+
outcome = typeof value === "object" && value !== null ? { view: value } : { failure: "settings describe answered no view" };
|
|
182
|
+
} else outcome = { failure: response.error?.message ?? "settings describe failed" };
|
|
183
|
+
} catch (error) {
|
|
184
|
+
outcome = { failure: error instanceof Error ? error.message : String(error) };
|
|
185
|
+
}
|
|
186
|
+
if (generation !== this.generation) continue;
|
|
187
|
+
if ("view" in outcome) {
|
|
188
|
+
this.retries = 0;
|
|
189
|
+
this.store.set({
|
|
190
|
+
status: "ready",
|
|
191
|
+
view: outcome.view,
|
|
192
|
+
error: null
|
|
193
|
+
});
|
|
194
|
+
} else {
|
|
195
|
+
const held = this.store.getSnapshot();
|
|
196
|
+
this.store.set({
|
|
197
|
+
status: held.view === null ? "idle" : "ready",
|
|
198
|
+
view: held.view,
|
|
199
|
+
error: outcome.failure
|
|
200
|
+
});
|
|
201
|
+
if (held.view === null) this.scheduleRetry();
|
|
202
|
+
}
|
|
203
|
+
if (!this.rerun) break;
|
|
204
|
+
}
|
|
205
|
+
} finally {
|
|
206
|
+
this.inFlight = void 0;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Schedule the next retry of a failed FIRST read ({@link SETTINGS_DESCRIBE_RETRY_MS}).
|
|
211
|
+
* Bounded: once the ladder is spent the scope keeps its degraded snapshot,
|
|
212
|
+
* exactly as before, until an invalidation or a namespace re-mount arrives.
|
|
213
|
+
*/
|
|
214
|
+
scheduleRetry() {
|
|
215
|
+
if (this.disposed || this.retries >= SETTINGS_DESCRIBE_RETRY_MS.length) return;
|
|
216
|
+
const delay = SETTINGS_DESCRIBE_RETRY_MS[this.retries];
|
|
217
|
+
this.retries += 1;
|
|
218
|
+
this.retryHandle = this.timer.set(() => {
|
|
219
|
+
this.retryHandle = void 0;
|
|
220
|
+
this.load();
|
|
221
|
+
}, delay);
|
|
222
|
+
}
|
|
223
|
+
/** Cancel a pending retry (a live read is starting, or the scope is going away). */
|
|
224
|
+
clearRetry() {
|
|
225
|
+
if (this.retryHandle !== void 0) this.timer.clear(this.retryHandle);
|
|
226
|
+
this.retryHandle = void 0;
|
|
227
|
+
}
|
|
228
|
+
/** Stop the mirror: no further reads, and no retry may fire after disposal. */
|
|
229
|
+
dispose() {
|
|
230
|
+
this.disposed = true;
|
|
231
|
+
this.clearRetry();
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
/**
|
|
235
|
+
* One namespace's derived scope over the shared mirror, plus its serialized
|
|
236
|
+
* Host writes. This is the face `CommandCodeSettingsController` consumes
|
|
237
|
+
* (`getSnapshot` / `subscribe` / `set` / `unset`), so the controller itself
|
|
238
|
+
* needs no changes for either generation.
|
|
239
|
+
*/
|
|
240
|
+
var RemoteSettingsScope = class {
|
|
241
|
+
resolveRemote;
|
|
242
|
+
store;
|
|
243
|
+
mirror;
|
|
244
|
+
namespace;
|
|
245
|
+
persistence;
|
|
246
|
+
tail = Promise.resolve();
|
|
247
|
+
writeGeneration = 0;
|
|
248
|
+
disposed = false;
|
|
249
|
+
unsubscribe;
|
|
250
|
+
/** Revision answered by a superseded write still ahead of the mirror. */
|
|
251
|
+
pendingRevision;
|
|
252
|
+
constructor(resolveRemote, mirror, namespace, persistence) {
|
|
253
|
+
this.resolveRemote = resolveRemote;
|
|
254
|
+
this.mirror = mirror;
|
|
255
|
+
this.namespace = namespace;
|
|
256
|
+
this.persistence = persistence;
|
|
257
|
+
this.store = createSnapshotStore({
|
|
258
|
+
status: persistence === "host" ? "loading" : "unavailable",
|
|
259
|
+
value: void 0,
|
|
260
|
+
base: void 0,
|
|
261
|
+
user: void 0,
|
|
262
|
+
revision: void 0,
|
|
263
|
+
writable: false,
|
|
264
|
+
mode: persistence
|
|
265
|
+
});
|
|
266
|
+
if (persistence === "host") {
|
|
267
|
+
this.unsubscribe = mirror.subscribe(() => {
|
|
268
|
+
this.derive();
|
|
269
|
+
});
|
|
270
|
+
this.derive();
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
getSnapshot() {
|
|
274
|
+
return this.store.getSnapshot();
|
|
275
|
+
}
|
|
276
|
+
subscribe(listener) {
|
|
277
|
+
return this.store.subscribe(listener);
|
|
278
|
+
}
|
|
279
|
+
/** Queue one field write (the single-path `set` op form). */
|
|
280
|
+
set(field, value) {
|
|
281
|
+
return this.mutate([{
|
|
282
|
+
op: "set",
|
|
283
|
+
path: [field],
|
|
284
|
+
value
|
|
285
|
+
}]);
|
|
286
|
+
}
|
|
287
|
+
/** Queue one field clear (the single-path `unset` op form). */
|
|
288
|
+
unset(field) {
|
|
289
|
+
return this.mutate([{
|
|
290
|
+
op: "unset",
|
|
291
|
+
path: [field]
|
|
292
|
+
}]);
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Queue one atomic namespace mutation behind the single-flight fence.
|
|
296
|
+
* A rejected write (e.g. `SETTINGS_CONFLICT`) re-reads the Host so the
|
|
297
|
+
* snapshot reports what actually landed; an accepted one folds its answer
|
|
298
|
+
* in directly — unless a newer write already superseded it, in which case
|
|
299
|
+
* that answer's revision becomes the next write's fence.
|
|
300
|
+
*/
|
|
301
|
+
mutate(ops, expectedRevision) {
|
|
302
|
+
const ownedOps = structuredClone(ops);
|
|
303
|
+
const generation = ++this.writeGeneration;
|
|
304
|
+
return this.enqueue(async () => {
|
|
305
|
+
const revision = expectedRevision ?? this.pendingRevision ?? this.getSnapshot().revision;
|
|
306
|
+
const response = await this.mutateRemote(ownedOps, revision);
|
|
307
|
+
if (!response.ok) {
|
|
308
|
+
await this.recover(generation);
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
if (this.disposed) return;
|
|
312
|
+
const row = response.value;
|
|
313
|
+
if (generation === this.writeGeneration) {
|
|
314
|
+
this.pendingRevision = void 0;
|
|
315
|
+
this.mirror.acceptView(row);
|
|
316
|
+
} else this.pendingRevision = typeof row.revision === "number" ? row.revision : void 0;
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
async mutateRemote(ops, revision) {
|
|
320
|
+
try {
|
|
321
|
+
const namespace = this.resolveRemote();
|
|
322
|
+
if (namespace === void 0) return {
|
|
323
|
+
ok: false,
|
|
324
|
+
error: { message: "the settings remote namespace is not mounted" }
|
|
325
|
+
};
|
|
326
|
+
return await namespace.mutate(this.namespace, ops, revision);
|
|
327
|
+
} catch (error) {
|
|
328
|
+
return {
|
|
329
|
+
ok: false,
|
|
330
|
+
error: { message: error instanceof Error ? error.message : String(error) }
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
/** Reload Host state for the latest failed write; superseded failures leave recovery to it. */
|
|
335
|
+
async recover(generation) {
|
|
336
|
+
if (this.disposed || generation !== this.writeGeneration) return;
|
|
337
|
+
this.pendingRevision = void 0;
|
|
338
|
+
await this.mirror.load();
|
|
339
|
+
}
|
|
340
|
+
/** Derive this namespace's snapshot from the mirror's held view. */
|
|
341
|
+
derive() {
|
|
342
|
+
if (this.disposed) return;
|
|
343
|
+
const mirrored = this.mirror.getSnapshot();
|
|
344
|
+
if (mirrored.view === null || mirrored.view === void 0) return;
|
|
345
|
+
const writable = mirrored.view.writable === true;
|
|
346
|
+
const row = mirrored.view.namespaces?.find((candidate) => candidate.ns === this.namespace);
|
|
347
|
+
if (row === void 0) {
|
|
348
|
+
this.publish({
|
|
349
|
+
status: "unavailable",
|
|
350
|
+
writable
|
|
351
|
+
});
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
const decoded = decodeRow(row.value);
|
|
355
|
+
if (decoded === void 0) return;
|
|
356
|
+
this.publish({
|
|
357
|
+
status: "ready",
|
|
358
|
+
writable,
|
|
359
|
+
value: decoded,
|
|
360
|
+
base: row.base,
|
|
361
|
+
user: row.user,
|
|
362
|
+
revision: typeof row.revision === "number" ? row.revision : void 0
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
/** Publish `next` over the current snapshot, skipping the write when nothing changed. */
|
|
366
|
+
publish(next) {
|
|
367
|
+
const current = this.store.getSnapshot();
|
|
368
|
+
const candidate = {
|
|
369
|
+
status: next.status,
|
|
370
|
+
value: "value" in next ? next.value : current.value,
|
|
371
|
+
base: "base" in next ? next.base : current.base,
|
|
372
|
+
user: "user" in next ? next.user : current.user,
|
|
373
|
+
revision: "revision" in next ? next.revision : current.revision,
|
|
374
|
+
writable: next.writable,
|
|
375
|
+
mode: current.mode
|
|
376
|
+
};
|
|
377
|
+
if (sameSnapshot(candidate, current)) return;
|
|
378
|
+
this.store.set(candidate);
|
|
379
|
+
}
|
|
380
|
+
/** Queue operations one at a time; memory persistence and disposal are no-ops. */
|
|
381
|
+
enqueue(operation) {
|
|
382
|
+
if (this.persistence === "memory" || this.disposed) return Promise.resolve();
|
|
383
|
+
const task = this.tail.then(async () => {
|
|
384
|
+
if (this.disposed) return;
|
|
385
|
+
await operation();
|
|
386
|
+
});
|
|
387
|
+
this.tail = task.catch(() => {});
|
|
388
|
+
return task;
|
|
389
|
+
}
|
|
390
|
+
/** Stop queued operations, stop deriving, and wait for the current wire call to settle. */
|
|
391
|
+
async dispose() {
|
|
392
|
+
this.disposed = true;
|
|
393
|
+
this.writeGeneration += 1;
|
|
394
|
+
this.unsubscribe?.();
|
|
395
|
+
await this.tail;
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
/**
|
|
399
|
+
* Build the plugin's settings scope for one namespace.
|
|
400
|
+
*
|
|
401
|
+
* Subscribes the two signals that can move the Host view — the forwarded
|
|
402
|
+
* `settings/document-updated` invalidation and (when the client exposes it)
|
|
403
|
+
* `connection/reset` — kicks the first describe read, and derives the
|
|
404
|
+
* namespace row on every mirror change.
|
|
405
|
+
*
|
|
406
|
+
* `resolveRemote` is the INJECT-CAPTURED namespace (see
|
|
407
|
+
* {@link SettingsRemoteResolver}): `remote.settings` is a Cordis service nested
|
|
408
|
+
* under `remote`, so a read through our own context throws and has to be
|
|
409
|
+
* replaced by an object captured inside `ctx.inject(['remote.settings'], …)`.
|
|
410
|
+
* The caller that owns that inject calls {@link ManagedSettingsScope.refresh}
|
|
411
|
+
* once the namespace lands, which is what turns the first, namespace-less read
|
|
412
|
+
* into a served one; a profile that never mounts the namespace leaves the page
|
|
413
|
+
* in its degraded (`unavailable`, read-only) state instead of throwing.
|
|
414
|
+
*
|
|
415
|
+
* @param context - The client context slice (only the declared `remote` service is read).
|
|
416
|
+
* @param namespace - Settings namespace (profile entry id) this scope binds.
|
|
417
|
+
* @param resolveRemote - Reads the inject-captured `remote.settings` namespace.
|
|
418
|
+
* @returns The scope plus the refresh/dispose handles the entry wires into its fiber.
|
|
419
|
+
*/
|
|
420
|
+
function createSettingsScope(context, namespace, resolveRemote, timer = REAL_SETTINGS_TIMER) {
|
|
421
|
+
const loopback = context.remote.$host?.isLoopback === true;
|
|
422
|
+
const persistence = loopback ? "host" : "memory";
|
|
423
|
+
const mirror = new SettingsDescribeMirror(resolveRemote, persistence, timer);
|
|
424
|
+
const scope = new RemoteSettingsScope(resolveRemote, mirror, namespace, persistence);
|
|
425
|
+
const disposers = [];
|
|
426
|
+
if (loopback) {
|
|
427
|
+
if (typeof context.remote.$on === "function") {
|
|
428
|
+
const off = context.remote.$on("settings/document-updated", () => {
|
|
429
|
+
mirror.ensure();
|
|
430
|
+
});
|
|
431
|
+
if (typeof off === "function") disposers.push(off);
|
|
432
|
+
}
|
|
433
|
+
if (typeof context.on === "function") {
|
|
434
|
+
const off = context.on("connection/reset", () => {
|
|
435
|
+
mirror.load();
|
|
436
|
+
});
|
|
437
|
+
if (typeof off === "function") disposers.push(off);
|
|
438
|
+
}
|
|
439
|
+
mirror.ensure();
|
|
440
|
+
}
|
|
441
|
+
return {
|
|
442
|
+
getSnapshot: () => scope.getSnapshot(),
|
|
443
|
+
subscribe: (listener) => scope.subscribe(listener),
|
|
444
|
+
set: (field, value) => scope.set(field, value),
|
|
445
|
+
unset: (field) => scope.unset(field),
|
|
446
|
+
refresh: () => {
|
|
447
|
+
mirror.load();
|
|
448
|
+
},
|
|
449
|
+
dispose: async () => {
|
|
450
|
+
for (const dispose of disposers.splice(0)) dispose();
|
|
451
|
+
mirror.dispose();
|
|
452
|
+
await scope.dispose();
|
|
453
|
+
}
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
//#endregion
|
|
39
457
|
//#region src/client/settings.ts
|
|
40
458
|
/**
|
|
41
459
|
* Browser controller for the "Command Code" settings page.
|
|
@@ -56,10 +474,14 @@ window.__ModuleLoader__.load({
|
|
|
56
474
|
* - Working dir, request/stream timeouts -> the same namespace.
|
|
57
475
|
*
|
|
58
476
|
* The controller mirrors the plugin-card pattern from the harness's own
|
|
59
|
-
* settings UI: it binds the `llm-commandcode` namespace through
|
|
60
|
-
* `
|
|
61
|
-
*
|
|
62
|
-
*
|
|
477
|
+
* settings UI: it binds the `llm-commandcode` namespace through a
|
|
478
|
+
* `SettingsScope` — this plugin's own binding of `remote.settings` (see
|
|
479
|
+
* `./settings-scope.ts`; the harness's `settingsScope` wrapper existed only
|
|
480
|
+
* through 0.1.6 and was removed with the 0.1.7 settings rewrite, while the
|
|
481
|
+
* wire underneath spans every supported release) — keeps a staged draft of
|
|
482
|
+
* edits, and writes them on save through `scope.set` / the credentials
|
|
483
|
+
* domain. The Host stays the single fact source; the snapshot is republished
|
|
484
|
+
* after each accepted write.
|
|
63
485
|
*
|
|
64
486
|
* This module is deliberately free of JSX — it only produces the state face
|
|
65
487
|
* the React component renders.
|
|
@@ -2157,7 +2579,7 @@ window.__ModuleLoader__.load({
|
|
|
2157
2579
|
}
|
|
2158
2580
|
//#endregion
|
|
2159
2581
|
//#region package.json
|
|
2160
|
-
var version = "0.11.
|
|
2582
|
+
var version = "0.11.9";
|
|
2161
2583
|
var repository = {
|
|
2162
2584
|
"type": "git",
|
|
2163
2585
|
"url": "git+https://github.com/Mars-Sea/dsh-commandcode-provider.git"
|
|
@@ -2812,13 +3234,21 @@ window.__ModuleLoader__.load({
|
|
|
2812
3234
|
report.blocked !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2813
3235
|
className: "cc-usageBlocked",
|
|
2814
3236
|
role: "alert",
|
|
2815
|
-
children: [
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
3237
|
+
children: [
|
|
3238
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3239
|
+
className: "cc-usageBlockedTitle",
|
|
3240
|
+
children: blockedTitle(report.blocked, t)
|
|
3241
|
+
}),
|
|
3242
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3243
|
+
className: "cc-usageBlockedHint",
|
|
3244
|
+
children: blockedHint(report.blocked, t)
|
|
3245
|
+
}),
|
|
3246
|
+
report.failures.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3247
|
+
className: "cc-usageBlockedDetail",
|
|
3248
|
+
title: report.failures.join("; "),
|
|
3249
|
+
children: report.failures.join(" · ")
|
|
3250
|
+
}) : null
|
|
3251
|
+
]
|
|
2822
3252
|
}) : null,
|
|
2823
3253
|
report.usage !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2824
3254
|
className: "cc-usageStats",
|
|
@@ -4122,21 +4552,23 @@ window.__ModuleLoader__.load({
|
|
|
4122
4552
|
}
|
|
4123
4553
|
/** The report-level failure box, or undefined when the report is usable. */
|
|
4124
4554
|
function failureView(state) {
|
|
4125
|
-
const
|
|
4555
|
+
const blockedEntry = state.report?.accounts.find((entry) => entry.report.blocked !== void 0);
|
|
4556
|
+
const blocked = blockedEntry?.report.blocked;
|
|
4557
|
+
const detail = (blockedEntry?.report.failures ?? []).join(" · ");
|
|
4126
4558
|
if (blocked === "invalid-key") return {
|
|
4127
4559
|
title: "errorInvalidKey",
|
|
4128
4560
|
hint: "errorInvalidKeyHint",
|
|
4129
|
-
detail
|
|
4561
|
+
detail
|
|
4130
4562
|
};
|
|
4131
4563
|
if (blocked === "service-unavailable") return {
|
|
4132
4564
|
title: "errorServiceUnavailable",
|
|
4133
4565
|
hint: "errorServiceUnavailableHint",
|
|
4134
|
-
detail
|
|
4566
|
+
detail
|
|
4135
4567
|
};
|
|
4136
4568
|
if (blocked === "network") return {
|
|
4137
4569
|
title: "errorNetwork",
|
|
4138
4570
|
hint: "errorNetworkHint",
|
|
4139
|
-
detail
|
|
4571
|
+
detail
|
|
4140
4572
|
};
|
|
4141
4573
|
if (state.status === "error" && state.report === void 0) return {
|
|
4142
4574
|
title: "errorGeneric",
|
|
@@ -5791,6 +6223,7 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
|
|
|
5791
6223
|
.cc-usageBlocked{border:1px solid var(--dsw-alias-label-error);border-radius:10px;padding:10px 12px;display:flex;flex-direction:column;gap:4px}
|
|
5792
6224
|
.cc-usageBlockedTitle{color:var(--dsw-alias-label-error);margin:0;font-size:13px;font-weight:600;line-height:1.5}
|
|
5793
6225
|
.cc-usageBlockedHint{color:var(--dsw-alias-label-secondary);margin:0;font-size:12px;line-height:1.5}
|
|
6226
|
+
.cc-usageBlockedDetail{color:var(--dsw-alias-label-secondary);margin:0;font-size:11px;line-height:1.5;word-break:break-word;opacity:.85}
|
|
5794
6227
|
.cc-version{margin:4px 0 0;text-align:center;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5}
|
|
5795
6228
|
/* The Models-page provider panel root: unstyled by design — the row card the
|
|
5796
6229
|
* Models page owns supplies the surface, the panel only stacks its controls. */
|
|
@@ -6314,9 +6747,11 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
|
|
|
6314
6747
|
const SESSION_COST_ID = "commandcode-session-cost";
|
|
6315
6748
|
/**
|
|
6316
6749
|
* Client plugin body. Gates on the services shared by both client generations
|
|
6317
|
-
* (`slots`, `locale`, `connection`, `remote
|
|
6318
|
-
*
|
|
6319
|
-
*
|
|
6750
|
+
* (`slots`, `locale`, `connection`, `remote`) — never on `settingsScope`,
|
|
6751
|
+
* which 0.1.7 removed and which the settings scope here replaces for every
|
|
6752
|
+
* generation. Current clients mount the page after `remote.credentials`
|
|
6753
|
+
* appears; legacy clients mount it from the connection ApiProxy credential
|
|
6754
|
+
* face.
|
|
6320
6755
|
*/
|
|
6321
6756
|
function apply(ctx) {
|
|
6322
6757
|
injectPageCss();
|
|
@@ -6341,7 +6776,18 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
|
|
|
6341
6776
|
}
|
|
6342
6777
|
/** Mount the one shared UI implementation over either credential transport. */
|
|
6343
6778
|
function applyClientSurfaces(ctx, api, hostDescription) {
|
|
6344
|
-
|
|
6779
|
+
let settingsNamespace;
|
|
6780
|
+
const scope = createSettingsScope(ctx, COMMANDCODE_NS, () => settingsNamespace);
|
|
6781
|
+
ctx.effect(() => () => {
|
|
6782
|
+
scope.dispose();
|
|
6783
|
+
}, "dsh-commandcode-provider: settings scope");
|
|
6784
|
+
ctx.inject(["remote.settings"], (settingsCtx) => {
|
|
6785
|
+
settingsNamespace = settingsCtx.remote.settings;
|
|
6786
|
+
scope.refresh();
|
|
6787
|
+
settingsCtx.effect(() => () => {
|
|
6788
|
+
settingsNamespace = void 0;
|
|
6789
|
+
}, "dsh-commandcode-provider: settings namespace");
|
|
6790
|
+
});
|
|
6345
6791
|
let modelsRemote;
|
|
6346
6792
|
const controller = new CommandCodeSettingsController(scope, {
|
|
6347
6793
|
...api,
|
|
@@ -6359,6 +6805,7 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
|
|
|
6359
6805
|
let usageNamespace;
|
|
6360
6806
|
let usageMountError;
|
|
6361
6807
|
let pricesController;
|
|
6808
|
+
let refreshUsage;
|
|
6362
6809
|
const contribution = {
|
|
6363
6810
|
package: USAGE_REMOTE_CONTRIBUTION.package,
|
|
6364
6811
|
descriptors: [
|
|
@@ -6381,6 +6828,7 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
|
|
|
6381
6828
|
usageNamespace = namespaceCtx.remote.commandcode;
|
|
6382
6829
|
controller.refreshCatalog();
|
|
6383
6830
|
pricesController?.reload();
|
|
6831
|
+
refreshUsage?.();
|
|
6384
6832
|
namespaceCtx.effect(() => () => {
|
|
6385
6833
|
usageNamespace = void 0;
|
|
6386
6834
|
}, "dsh-commandcode-provider: usage namespace");
|
|
@@ -6430,6 +6878,7 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
|
|
|
6430
6878
|
};
|
|
6431
6879
|
modelsRemote = () => usageRemote.models();
|
|
6432
6880
|
const usageController = new CommandCodeUsageController(usageRemote);
|
|
6881
|
+
refreshUsage = () => void usageController.refresh();
|
|
6433
6882
|
ctx.effect(() => () => usageController.dispose(), "dsh-commandcode-provider: usage controller");
|
|
6434
6883
|
const usageStore = createSnapshotStore(usageController.state());
|
|
6435
6884
|
usageController.subscribe(() => usageStore.set(usageController.state()));
|
|
@@ -6596,8 +7045,7 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
|
|
|
6596
7045
|
"slots",
|
|
6597
7046
|
"locale",
|
|
6598
7047
|
"connection",
|
|
6599
|
-
"remote"
|
|
6600
|
-
"settingsScope"
|
|
7048
|
+
"remote"
|
|
6601
7049
|
];
|
|
6602
7050
|
//#endregion
|
|
6603
7051
|
exports.apply = apply;
|