@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 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.3";
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
- /** The shipped composer stats row (dsh-client-ui-chat `StatsPills`). */
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 in the whole of
5210
- * `dsh-client-ui-chat`: the per-message turn-usage panel has its own dialog with
5211
- * different markup, so the composer's is unambiguous.
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(`[dsh-commandcode-provider] no ${STATS_ROOT} row to append the session cost to; the figure stays in the usage dialog only`);
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
- /** The shipped token pill, scoped to this entry's own composer. */
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 root = (this.scope() ?? this.doc).querySelector(STATS_ROOT);
5339
- if (root === null) return null;
5340
- const triggers = root.querySelectorAll(DIALOG_TRIGGER);
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; DSH renders one composer, and the attribute is unique in
5467
- * the chat client, so a document-level lookup is exact. Should a future build
5468
- * mount two composers at once, both dialogs would describe whichever session
5469
- * this entry belongs to — noted rather than defended against.
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
- return this.doc.querySelector(USAGE_DIALOG);
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
- /** The dock outlet this entry renders inside, i.e. what it can scope itself from. */
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)?.parentElement ?? null
5633
+ scope: () => markerRef.current?.closest(DOCK_ANCHOR) ?? null
5584
5634
  });
5585
5635
  displayRef.current = display;
5586
5636
  display.start();