@mars-sea/dsh-commandcode-provider 0.11.3 → 0.11.5
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 +14 -0
- package/lib/client.js +73 -23
- package/lib/client.js.map +1 -1
- package/lib/index.js +7 -5
- package/lib/index.js.map +1 -1
- package/package.json +39 -38
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,20 @@ 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.5] - 2026-09-18
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- **The composer session-cost figure survives dsh 0.1.6-alpha.2.** That release deleted the `data-composer-stats` marker from the shipped stats row (`StatsPills`) while leaving the row's markup otherwise identical, and the readout's pill lookup *required* that attribute — so there the lookup matched nothing on every sync and the amount silently stopped appearing beside the token pill (after three misses it logged a single console line), leaving only the usage dialog still carrying per-row prices. `STATS_ROOT` is now a PREFERENCE: when the attribute is absent the lookup falls back to the dock OUTLET (`[data-slot="conversation.composer.dock"]`), the `display:contents` div holding exactly that slot's entries — the shipped `stats` cell and ours — whose LAST dialog trigger is therefore still the token pill. The fallback is deliberately the outlet and never the outlet's PARENT: the same release moved the composer's context-usage ring out of the card into a new footer `div` beside the dock, and that ring's trigger is itself a `button[aria-haspopup="dialog"]` rendered AFTER the dock, so the parent-scoped lookup this previously depended on would have appended the cost to the context meter instead. `tests/session-cost-display.test.ts` now builds both the unmarked 0.1.6-alpha.2 row and a footer-sibling meter, and fails if the cost stops landing on the token pill or the meter becomes the host; the earlier suite constructed its own marked row and accepted a missing anchor as a no-op, which is exactly why the deletion could go dark with every check green.
|
|
13
|
+
- **A second composer's usage dialog can no longer be priced with this session's figures.** dsh 0.1.6-alpha.2 mounts an embedded Conversation in the sidebar, so two composers — each with its own session, dock and dialog — can be live and open at once. The dialog is portaled onto `body` and carries no id or `aria-controls` tying it back to its trigger, so the document-level lookup was exact only while a single composer was guaranteed: the per-row shape confirmation compares against THIS entry's counts, so a dialog belonging to the other session that happened to match would have been decorated with the wrong figures. `resolveDialog()` now decorates only while the document holds EXACTLY ONE `[data-session-stats-usage]` and otherwise declines — the same rule the shape check already follows one level down. Pinned by two tests: with two dialogs open neither is priced, and the survivor is priced once the other composer's dialog unmounts.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
- **dsh 0.1.6-alpha.2 is a declared-compatible engine.** The engine's other changes need no adapter work, and each was verified against the installed release rather than inferred: the Typert strict result codec's member swap (`schema` → the lazy `create()` factory) was already covered by 0.11.4's two-member codec — both the Host and Client contributions register on that release's real registry, which is worth restating because 0.1.6-alpha.2 is the first PUBLISHED engine to require `create()`; `npm run test:engine` passes against it (the bundle links and evaluates there, and the request-image policy is the durable one); every slot this plugin registers into or re-states (`conversation.composer.dock`, `main`, `sidebar.footer.action`, `settings.section`, `settings.models.provider-card`) has an unchanged declaration and the dock still supplies `sessionId`/`useProjection`/`useChat`; the platform seed table, the host packages this plugin imports and the sidebar/footer class stems are unchanged; and the release's new plugin-management page asks nothing of a plugin whose registrations are already cordis effects — which is also what its runtime unload needs. The one genuinely new surface it exposes, a sidebar-mounted embedded Conversation, is what the second fix above defends against.
|
|
17
|
+
|
|
18
|
+
## [0.11.4] - 2026-09-17
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
- **The Command Code Remotes keep registering on the next Harness generation** ([#49](https://github.com/Mars-Sea/dsh-commandcode-provider/issues/49)). The Typert protocol changed the strict result codec's shape inside the same `mode: 'strict'` tag: every released engine (`0.1.2-rc.1` … `0.1.6-alpha.1`, all 21 published versions) declares `schema: TypertSchema` and refuses registration with `strict codec has no parse() method` without it, while master after `e459e3263` replaced that member with the lazy factory `create: () => TypertSchema` and refuses with `strict codec has no create() factory`. All six Command Code Remotes (`report`, `models`, `prices`, `loginBegin`, `loginStatus`, `loginCancel`) come from one descriptor factory that carried `schema` alone, so on that newer generation none of them registers: the settings page's account card, the plans & quota panel, the price table and the browser login all lose their Host data. `makeRemoteDescriptor()` now carries both members, where `create: () => schema` names the same hand-rolled validator rather than a second copy, so the two generations can never validate against different rules. This is a version-agnostic fix instead of a runtime probe: neither generation inspects the member it does not know, and the member the running engine ignores is inert — which matters because a single-member codec is broken in either direction, and the one-line patch the issue proposed (`schema` → `create`) would have fixed unreleased master by breaking every install that exists today. `tests/wire-shared.test.ts` pins it by running both generations' real validators over every shipped descriptor, with negative controls proving that a codec carrying only one member is rejected by the other generation. The helper is inlined into both `lib/index.js` and `lib/client.js`, so a fix is dead until the bundle is rebuilt — hence the rebuilt bundles in this release.
|
|
22
|
+
|
|
9
23
|
## [0.11.3] - 2026-09-16
|
|
10
24
|
|
|
11
25
|
### Added
|
package/lib/client.js
CHANGED
|
@@ -1678,6 +1678,12 @@ window.__ModuleLoader__.load({
|
|
|
1678
1678
|
* only its own facts.
|
|
1679
1679
|
*/
|
|
1680
1680
|
function makeRemoteDescriptor(endpoint, method, typeSymbol, schema) {
|
|
1681
|
+
const result = {
|
|
1682
|
+
mode: "strict",
|
|
1683
|
+
typeSymbol,
|
|
1684
|
+
schema,
|
|
1685
|
+
create: () => schema
|
|
1686
|
+
};
|
|
1681
1687
|
return {
|
|
1682
1688
|
id: `${REMOTE_PACKAGE}#${endpoint}`,
|
|
1683
1689
|
service: REMOTE_SERVICE,
|
|
@@ -1685,11 +1691,7 @@ window.__ModuleLoader__.load({
|
|
|
1685
1691
|
method,
|
|
1686
1692
|
invocation: { kind: "direct" },
|
|
1687
1693
|
parameters: [],
|
|
1688
|
-
result
|
|
1689
|
-
mode: "strict",
|
|
1690
|
-
typeSymbol,
|
|
1691
|
-
schema
|
|
1692
|
-
}
|
|
1694
|
+
result
|
|
1693
1695
|
};
|
|
1694
1696
|
}
|
|
1695
1697
|
//#endregion
|
|
@@ -2155,7 +2157,7 @@ window.__ModuleLoader__.load({
|
|
|
2155
2157
|
}
|
|
2156
2158
|
//#endregion
|
|
2157
2159
|
//#region package.json
|
|
2158
|
-
var version = "0.11.
|
|
2160
|
+
var version = "0.11.5";
|
|
2159
2161
|
var repository = {
|
|
2160
2162
|
"type": "git",
|
|
2161
2163
|
"url": "git+https://github.com/Mars-Sea/dsh-commandcode-provider.git"
|
|
@@ -5203,12 +5205,25 @@ window.__ModuleLoader__.load({
|
|
|
5203
5205
|
}
|
|
5204
5206
|
//#endregion
|
|
5205
5207
|
//#region src/client/session-cost-display.ts
|
|
5206
|
-
/**
|
|
5208
|
+
/**
|
|
5209
|
+
* The shipped composer stats row (dsh-client-ui-chat `StatsPills`).
|
|
5210
|
+
*
|
|
5211
|
+
* A PREFERENCE, not a requirement: 0.1.6-alpha.2 dropped this attribute from the
|
|
5212
|
+
* row's root while leaving the rest of the markup identical, so a lookup that
|
|
5213
|
+
* insisted on it would silently stop finding the row on that engine. When it is
|
|
5214
|
+
* absent the search falls back to the dock OUTLET — deliberately the outlet and
|
|
5215
|
+
* never the outlet's parent, because from 0.1.6-alpha.2 the parent is the new
|
|
5216
|
+
* composer footer, which holds the `ContextMeter` as well, and that meter's
|
|
5217
|
+
* trigger is itself a `button[aria-haspopup="dialog"]` rendered AFTER the dock.
|
|
5218
|
+
* A parent-scoped "last trigger wins" would therefore append the cost to the
|
|
5219
|
+
* context ring instead of the token pill.
|
|
5220
|
+
*/
|
|
5207
5221
|
const STATS_ROOT = "[data-composer-stats]";
|
|
5208
5222
|
/**
|
|
5209
|
-
* The shipped token-usage dialog's `<dl>`. Unique
|
|
5210
|
-
*
|
|
5211
|
-
*
|
|
5223
|
+
* The shipped token-usage dialog's `<dl>`. Unique WITHIN one `StatsPills`
|
|
5224
|
+
* render: the per-message turn-usage panel has its own dialog with different
|
|
5225
|
+
* markup, so a single composer's usage dialog is unambiguous. It is not unique
|
|
5226
|
+
* across the document once two composers are live — see {@link resolveDialog}.
|
|
5212
5227
|
*/
|
|
5213
5228
|
const USAGE_DIALOG = "[data-session-stats-usage]";
|
|
5214
5229
|
/**
|
|
@@ -5309,7 +5324,7 @@ window.__ModuleLoader__.load({
|
|
|
5309
5324
|
const button = this.resolvePillButton();
|
|
5310
5325
|
if (button === null) {
|
|
5311
5326
|
this.pillMisses += 1;
|
|
5312
|
-
if (this.pillMisses === MISSES_BEFORE_WARNING) console.warn(
|
|
5327
|
+
if (this.pillMisses === MISSES_BEFORE_WARNING) console.warn("[dsh-commandcode-provider] no shipped token pill in this composer's dock outlet to append the session cost to; the figure stays in the usage dialog only");
|
|
5313
5328
|
return;
|
|
5314
5329
|
}
|
|
5315
5330
|
this.pillMisses = 0;
|
|
@@ -5333,11 +5348,24 @@ window.__ModuleLoader__.load({
|
|
|
5333
5348
|
if (this.pillA11y.textContent !== described) this.pillA11y.textContent = described;
|
|
5334
5349
|
}
|
|
5335
5350
|
}
|
|
5336
|
-
/**
|
|
5351
|
+
/**
|
|
5352
|
+
* The shipped token pill, scoped to this entry's own composer.
|
|
5353
|
+
*
|
|
5354
|
+
* The scope is the dock OUTLET (`display: contents`, one per slot), which holds
|
|
5355
|
+
* exactly that slot's entries — the shipped `stats` cell and ours, in their
|
|
5356
|
+
* registration order. {@link STATS_ROOT} narrows that to the stats row on the
|
|
5357
|
+
* engines that still mark it; on the engines that do not, the outlet is
|
|
5358
|
+
* already the narrowest correct container, because the composer footer's
|
|
5359
|
+
* `ContextMeter` sits BESIDE the outlet rather than inside it.
|
|
5360
|
+
*
|
|
5361
|
+
* A missing scope is "no pill", never a document-wide search: the outlet is
|
|
5362
|
+
* what makes this lookup per-composer, and a second composer can be live at
|
|
5363
|
+
* once (0.1.6-alpha.2 mounts an embedded Conversation in the sidebar).
|
|
5364
|
+
*/
|
|
5337
5365
|
resolvePillButton() {
|
|
5338
|
-
const
|
|
5339
|
-
if (
|
|
5340
|
-
const triggers =
|
|
5366
|
+
const scope = this.scope();
|
|
5367
|
+
if (scope === null) return null;
|
|
5368
|
+
const triggers = (scope.querySelector(STATS_ROOT) ?? scope).querySelectorAll(DIALOG_TRIGGER);
|
|
5341
5369
|
return triggers.length === 0 ? null : triggers[triggers.length - 1] ?? null;
|
|
5342
5370
|
}
|
|
5343
5371
|
/**
|
|
@@ -5460,18 +5488,30 @@ window.__ModuleLoader__.load({
|
|
|
5460
5488
|
this.dialogPrices.delete(dd);
|
|
5461
5489
|
}
|
|
5462
5490
|
/**
|
|
5463
|
-
* The shipped usage dialog, or null while it is closed.
|
|
5491
|
+
* The shipped usage dialog, or null while it is closed (or ambiguous).
|
|
5464
5492
|
*
|
|
5465
5493
|
* The dialog is portaled onto `body`, so unlike the pill it cannot be scoped
|
|
5466
|
-
* from this entry
|
|
5467
|
-
*
|
|
5468
|
-
*
|
|
5469
|
-
*
|
|
5494
|
+
* from this entry, and the attribute is unique per DIALOG rather than per
|
|
5495
|
+
* document. While one composer was guaranteed, a document-level lookup was
|
|
5496
|
+
* exact. 0.1.6-alpha.2 ends that guarantee: it mounts an embedded Conversation
|
|
5497
|
+
* in the sidebar, so a second composer — with its own session, its own dock
|
|
5498
|
+
* and its own dialog — can be live and open at the same time.
|
|
5499
|
+
*
|
|
5500
|
+
* There is no DOM link from a dialog back to its trigger (the portaled panel
|
|
5501
|
+
* carries no id and no `aria-controls`), so with two dialogs open document
|
|
5502
|
+
* order says nothing about ownership. The shape confirmation in
|
|
5503
|
+
* {@link dialogShapeMatches} would usually reject the stranger, but it compares
|
|
5504
|
+
* against THIS entry's counts, so a coincidentally matching dialog would be
|
|
5505
|
+
* priced with another session's figures. Ambiguity is therefore answered by
|
|
5506
|
+
* declining to decorate at all — the same rule the shape check already
|
|
5507
|
+
* follows, applied one level up: a dialog we cannot prove is ours is left
|
|
5508
|
+
* alone.
|
|
5470
5509
|
*/
|
|
5471
5510
|
resolveDialog() {
|
|
5472
5511
|
if (this.dialogHost?.isConnected === true) return this.dialogHost;
|
|
5473
5512
|
this.clearDialog();
|
|
5474
|
-
|
|
5513
|
+
const dialogs = this.doc.querySelectorAll(USAGE_DIALOG);
|
|
5514
|
+
return dialogs.length === 1 ? dialogs[0] ?? null : null;
|
|
5475
5515
|
}
|
|
5476
5516
|
/** Give the dialog back: every price removed, every hidden row restored. */
|
|
5477
5517
|
clearDialog() {
|
|
@@ -5542,7 +5582,17 @@ window.__ModuleLoader__.load({
|
|
|
5542
5582
|
}
|
|
5543
5583
|
//#endregion
|
|
5544
5584
|
//#region src/client/session-cost-view.tsx
|
|
5545
|
-
/**
|
|
5585
|
+
/**
|
|
5586
|
+
* The dock outlet this entry renders inside, i.e. what it scopes itself from.
|
|
5587
|
+
*
|
|
5588
|
+
* The OUTLET itself, not its parent. The outlet is one `display:contents` div
|
|
5589
|
+
* holding exactly this slot's entries (the shipped `stats` cell and ours), so it
|
|
5590
|
+
* is the narrowest container that is still per-composer. Its parent is NOT:
|
|
5591
|
+
* from 0.1.6-alpha.2 the composer stacks the dock inside a footer that also
|
|
5592
|
+
* holds the `ContextMeter`, whose trigger is another
|
|
5593
|
+
* `button[aria-haspopup="dialog"]` rendered after the dock — scoping to the
|
|
5594
|
+
* parent would let the cost land on the context ring.
|
|
5595
|
+
*/
|
|
5546
5596
|
const DOCK_ANCHOR = "[data-slot=\"conversation.composer.dock\"]";
|
|
5547
5597
|
/** Module-level constant so the marker's `style` prop never diffs. */
|
|
5548
5598
|
const HIDDEN_STYLE = { display: "none" };
|
|
@@ -5580,7 +5630,7 @@ window.__ModuleLoader__.load({
|
|
|
5580
5630
|
if (typeof document === "undefined") return;
|
|
5581
5631
|
const display = new SessionCostDisplay({
|
|
5582
5632
|
doc: document,
|
|
5583
|
-
scope: () => markerRef.current?.closest(DOCK_ANCHOR)
|
|
5633
|
+
scope: () => markerRef.current?.closest(DOCK_ANCHOR) ?? null
|
|
5584
5634
|
});
|
|
5585
5635
|
displayRef.current = display;
|
|
5586
5636
|
display.start();
|