@linxin666/dsh-remote-web-ui 0.3.23 → 0.3.24

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/lib/index.js CHANGED
@@ -3266,6 +3266,45 @@ var PublicBaseKeeper = class {
3266
3266
  this.onChange(this.current());
3267
3267
  }
3268
3268
  };
3269
+ //#endregion
3270
+ //#region src/console-output.ts
3271
+ /** Count the U+FFFD replacement characters one candidate decode produced. */
3272
+ function replacementCount(text) {
3273
+ let count = 0;
3274
+ for (const character of text) if (character === "�") count += 1;
3275
+ return count;
3276
+ }
3277
+ /**
3278
+ * Decode one console byte buffer. Strict UTF-8 wins whenever it is valid;
3279
+ * otherwise the bytes are not UTF-8 at all and the console wrote its own code
3280
+ * page, so the candidate that loses the least text is taken.
3281
+ * @param bytes - the captured bytes.
3282
+ * @returns the decoded text.
3283
+ */
3284
+ function decodeConsoleBytes(bytes) {
3285
+ try {
3286
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
3287
+ } catch {
3288
+ let best;
3289
+ let bestLoss = Number.POSITIVE_INFINITY;
3290
+ for (const label of ["gbk", "utf-8"]) {
3291
+ let text;
3292
+ try {
3293
+ text = new TextDecoder(label).decode(bytes);
3294
+ } catch {
3295
+ continue;
3296
+ }
3297
+ const loss = replacementCount(text);
3298
+ if (loss < bestLoss) {
3299
+ best = text;
3300
+ bestLoss = loss;
3301
+ }
3302
+ }
3303
+ /* v8 ignore next 3 -- reachable only without the legacy code page AND without the lenient pass */
3304
+ if (best === void 0) return Buffer.from(bytes).toString("utf8");
3305
+ return best;
3306
+ }
3307
+ }
3269
3308
  /** The aggregate package that is the canonical update entry point. */
3270
3309
  const AGGREGATE_PACKAGE = "@linxin666/dsh-web-all";
3271
3310
  /** Fallback anchor: this plugin's own package when the aggregate is absent. */
@@ -3620,18 +3659,12 @@ const WIN_CMD_MISSING_RE = /not recognized as an internal or external command|
3620
3659
  /**
3621
3660
  * Decode process output with tolerant decoding:
3622
3661
  * Defaults to UTF-8; if invalid byte sequences are encountered (common on
3623
- * Windows consoles using code page 936/GBK), falls back to GBK via TextDecoder.
3662
+ * Windows consoles using code page 936/GBK), falls back to the code page that
3663
+ * loses the least text. Bytes are decoded once per whole chunk by the caller's
3664
+ * accumulator — decoding per \`data\` event would split multi-byte characters.
3624
3665
  */
3625
3666
  function decodeProcessChunk(chunk) {
3626
- try {
3627
- return new TextDecoder("utf-8", { fatal: true }).decode(chunk);
3628
- } catch {
3629
- try {
3630
- return new TextDecoder("gbk").decode(chunk);
3631
- } catch {
3632
- return chunk.toString("utf8");
3633
- }
3634
- }
3667
+ return decodeConsoleBytes(chunk);
3635
3668
  }
3636
3669
  /**
3637
3670
  * Bypass for pnpm 11's supply-chain gate: `minimumReleaseAge` (default 24 h)
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACnE,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,4CAA4C,CAAA;AAelG,OAAO,EAAU,KAAK,SAAS,EAAE,MAAM,cAAc,CAAA;AAmBrD,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACzD,YAAY,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACrE,YAAY,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAA;AACnE,YAAY,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAC7C,YAAY,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAA;AAC/F,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACzD,YAAY,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAErE,OAAO,QAAQ,kCAAkC,CAAC;IAChD,UAAU,kBAAkB;QAC1B,0CAA0C;QAC1C,MAAM,EAAE,SAAS,CAAA;KAClB;IAED,UAAU,OAAO;QACf;;;;WAIG;QACH;;;;;WAKG;QACH,oBAAoB,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,4BAA4B,CAAA;SAAE,CAAA;KAC3F;CACF;AAED,4GAA4G;AAC5G,MAAM,WAAW,uBAAuB;IACtC,oEAAoE;IACpE,IAAI,EAAE,OAAO,CAAA;CACd;AAED,mEAAmE;AACnE,MAAM,WAAW,4BAA4B;IAC3C,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,KAAK,CAAA;CACjB;AAED,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,OAAO;QACf;;;;WAIG;QACH,aAAa,CAAC,EAAE;YAAE,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,CAAA;SAAE,CAAA;KAC1E;CACF;AAYD,wCAAwC;AACxC,eAAO,MAAM,MAAM,UAA+D,CAAA;AAElF;;;GAGG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAiR9C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACnE,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,4CAA4C,CAAA;AAelG,OAAO,EAAU,KAAK,SAAS,EAAE,MAAM,cAAc,CAAA;AAoBrD,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACzD,YAAY,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACrE,YAAY,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAA;AACnE,YAAY,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAC7C,YAAY,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAA;AAC/F,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACzD,YAAY,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAErE,OAAO,QAAQ,kCAAkC,CAAC;IAChD,UAAU,kBAAkB;QAC1B,0CAA0C;QAC1C,MAAM,EAAE,SAAS,CAAA;KAClB;IAED,UAAU,OAAO;QACf;;;;WAIG;QACH;;;;;WAKG;QACH,oBAAoB,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,4BAA4B,CAAA;SAAE,CAAA;KAC3F;CACF;AAED,4GAA4G;AAC5G,MAAM,WAAW,uBAAuB;IACtC,oEAAoE;IACpE,IAAI,EAAE,OAAO,CAAA;CACd;AAED,mEAAmE;AACnE,MAAM,WAAW,4BAA4B;IAC3C,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,KAAK,CAAA;CACjB;AAED,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,OAAO;QACf;;;;WAIG;QACH,aAAa,CAAC,EAAE;YAAE,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,CAAA;SAAE,CAAA;KAC1E;CACF;AAYD,wCAAwC;AACxC,eAAO,MAAM,MAAM,UAA+D,CAAA;AAElF;;;GAGG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CA4Q9C"}
@@ -19,6 +19,7 @@ import { channelTransition, installRemoteChannel, isLoopbackHostname, remoteChan
19
19
  import { FenceNotice } from "./FenceNotice.js";
20
20
  import { reportDailyHeartbeat } from "./telemetry.js";
21
21
  import { startMobileAdapt } from "./mobile-adapt.js";
22
+ import { installPluginCard } from "./plugin-card-seat.js";
22
23
  /** Dictionary namespace owned by this plugin. */
23
24
  const NS = 'remote';
24
25
  /** Settings namespace the remote-control card edits (the Host plugin registers it). */
@@ -150,26 +151,20 @@ export function apply(ctx) {
150
151
  };
151
152
  });
152
153
  // Plugin configuration card: one staged form over the `remote-web-ui`
153
- // settings namespace, contributed to the Web UI plugin group.
154
+ // settings namespace, contributed to whichever plugin-card seat the running
155
+ // host declares (the family group's list seat, or the official keyed seat of
156
+ // the plugin-configuration tab when dsh-web-settings is not installed —
157
+ // issue #1589).
154
158
  const remoteSettings = new RemoteSettingsCardController(settingsScope);
155
- ctx.slots.inject('web-ui.plugin.item', () => {
156
- try {
157
- const unregister = ctx.slots.register({
158
- name: 'web-ui.plugin.item',
159
- id: 'remote-web-ui',
160
- order: 90,
161
- locale: NS,
162
- inject: () => remoteSettings.inject(),
163
- }, RemoteSettingsCard);
164
- return () => {
165
- remoteSettings.dispose();
166
- unregister();
167
- };
168
- }
169
- catch {
170
- return () => { };
171
- }
159
+ installPluginCard(ctx, {
160
+ namespace: REMOTE_WEB_UI_NS,
161
+ id: 'remote-web-ui',
162
+ order: 90,
163
+ locale: NS,
164
+ inject: () => remoteSettings.inject(),
165
+ component: RemoteSettingsCard,
172
166
  });
167
+ ctx.effect(() => () => { remoteSettings.dispose(); }, 'remote-web-ui: settings card');
173
168
  // Phone-side boot flow + heartbeats. Loopback pages (the desktop) never
174
169
  // heartbeat; the server ignores unpaired heartbeats anyway. Both run only
175
170
  // while the plugin is enabled.
@@ -1 +1 @@
1
- {"version":3,"file":"mobile-adapt.d.ts","sourceRoot":"","sources":["../../../src/client/mobile-adapt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAE7C,0EAA0E;AAC1E,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,IAAI,CAAA;IACpB,yDAAyD;IACzD,aAAa,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAA;IAClC,yDAAyD;IACzD,YAAY,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAA;IACjC;;;;;;OAMG;IACH,SAAS,EAAE,CAAC,CAAC,GAAG,EAAE,SAAS,KAAK,MAAM,CAAC,GAAG,IAAI,CAAA;IAC9C,wEAAwE;IACxE,UAAU,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,CAAA;IAC7B,4GAA4G;IAC5G,iBAAiB,IAAI,IAAI,CAAA;CAC1B;AA+PD;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CA0zBvC"}
1
+ {"version":3,"file":"mobile-adapt.d.ts","sourceRoot":"","sources":["../../../src/client/mobile-adapt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAE7C,0EAA0E;AAC1E,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,IAAI,CAAA;IACpB,yDAAyD;IACzD,aAAa,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAA;IAClC,yDAAyD;IACzD,YAAY,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAA;IACjC;;;;;;OAMG;IACH,SAAS,EAAE,CAAC,CAAC,GAAG,EAAE,SAAS,KAAK,MAAM,CAAC,GAAG,IAAI,CAAA;IAC9C,wEAAwE;IACxE,UAAU,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,CAAA;IAC7B,4GAA4G;IAC5G,iBAAiB,IAAI,IAAI,CAAA;CAC1B;AAmRD;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CAw0BvC"}
@@ -40,6 +40,12 @@ const EFFORT_BTN_ID = 'dshRemoteEffortPick';
40
40
  const COMPACT_CLASS = 'dsh-remote-compact-picker';
41
41
  /** Body class while the header actions are seated in the tabs row. */
42
42
  const HEADER_SEATED_CLASS = 'dsh-remote-header-seated';
43
+ /**
44
+ * Width the seated header actions paint over the tabs row, as a CSS variable
45
+ * on <html>. The row caps its own width with it (see the v80 rules) instead of
46
+ * reserving the space with padding.
47
+ */
48
+ const HEADER_RESERVE_VAR = '--dsh-remote-header-actions-reserve';
43
49
  /** Locale-dependent fast path for the official picker cells (zh/en). */
44
50
  const PICKER_CELL_PATTERN = {
45
51
  model: /模型|Model/, // i18n-allow: matches official picker cell text, not plugin copy
@@ -228,6 +234,20 @@ const ADAPT_CSS = [
228
234
  // [class$="_tab"] misses the active tab (its class ends in "_tabActive")
229
235
  // — containment so BOTH tabs match.
230
236
  '[class$="_header"] [class$="_tabs"] [class*="_tab"]{font-size:12px;white-space:nowrap}',
237
+ // v80: the tabs row must give the painted actions their width up on its own.
238
+ // The v67 padding reservation could not do it: the row is a nowrap flex
239
+ // container, and once the tab labels plus their gaps overflow the reduced
240
+ // content box the flex items spill over the padding box, so the trailing
241
+ // tabs render underneath the actions (reported on a phone: a long agent
242
+ // preset label covered the last tabs). Cap the row against the reserve and
243
+ // let the labels scroll instead. The value rides a CSS variable on <html>
244
+ // because the update lands before React re-creates the row node, and an
245
+ // inline reservation on that node is dropped until the next sync tick.
246
+ // The 2px bottom padding keeps the active-tab underline inside the clipping
247
+ // box; the matching negative margin keeps the header height official.
248
+ `body.${HEADER_SEATED_CLASS} [class$="_header"] [class$="_tabs"]{max-width:max(0px,calc(100% - var(${HEADER_RESERVE_VAR},0px)));overflow-x:auto;overflow-y:hidden;scrollbar-width:none;-webkit-overflow-scrolling:touch;padding-bottom:2px;margin-bottom:-2px}`,
249
+ `body.${HEADER_SEATED_CLASS} [class$="_header"] [class$="_tabs"]::-webkit-scrollbar{display:none}`,
250
+ `body.${HEADER_SEATED_CLASS} [class$="_header"] [class$="_tabs"] > [class*="_tab"]{flex:0 0 auto}`,
231
251
  // Mobile scope: hide the plugin surfaces that do not fit a phone — the
232
252
  // right-hand details column and every desktop-oriented tool surface. The
233
253
  // list keys on the L2 semantic roots (data-dsh-plugin, ownership stays
@@ -775,9 +795,14 @@ export function startMobileAdapt() {
775
795
  let dx = translate !== null ? parseFloat(translate[1] ?? '0') : 0;
776
796
  let dy = translate !== null ? parseFloat(translate[2] ?? '0') : 0;
777
797
  const actionsRect = actions.getBoundingClientRect();
778
- const tabsRect = tabs.getBoundingClientRect();
779
- // Horizontal: the actions' right edge lands on the tabs row's right edge.
780
- const rightDiff = tabsRect.right - actionsRect.right;
798
+ // Horizontal: the actions' right edge lands on the header's content edge,
799
+ // NOT on the tabs row's right edge. The row caps its own width against the
800
+ // reserve below (v80), so anchoring to the row would let the pair walk
801
+ // leftwards on every measurement.
802
+ const headerRect = header.getBoundingClientRect();
803
+ const headerStyle = getComputedStyle(header);
804
+ const contentRight = headerRect.right - (parseFloat(headerStyle.paddingRight) || 0) - (parseFloat(headerStyle.borderRightWidth) || 0);
805
+ const rightDiff = contentRight - actionsRect.right;
781
806
  if (Math.abs(rightDiff) >= 0.5)
782
807
  dx = Math.round((dx + rightDiff) * 10) / 10;
783
808
  // Vertical: the actions' text bottom lands on the tab text bottom.
@@ -798,18 +823,27 @@ export function startMobileAdapt() {
798
823
  const next = `translate(${dx}px, ${dy}px)`;
799
824
  if (actions.style.transform !== next)
800
825
  actions.style.transform = next;
801
- // Reserve the painted width so the tab labels never slide under it.
826
+ // Reserve the painted width on the row itself, so the tab labels never
827
+ // slide under it. The value rides a CSS variable on <html>: React
828
+ // recreates the row node on re-render, which would drop an inline
829
+ // reservation until the next sync tick, and until then the labels render
830
+ // underneath the painted actions.
802
831
  const reserve = `${Math.ceil(actionsRect.width) + 8}px`;
803
- if (tabs.style.paddingRight !== reserve)
804
- tabs.style.paddingRight = reserve;
832
+ const rootStyle = document.documentElement.style;
833
+ if (rootStyle.getPropertyValue(HEADER_RESERVE_VAR) !== reserve)
834
+ rootStyle.setProperty(HEADER_RESERVE_VAR, reserve);
805
835
  }
806
836
  function unseatHeaderActions() {
807
837
  document.body.classList.remove(HEADER_SEATED_CLASS);
838
+ document.documentElement.style.removeProperty(HEADER_RESERVE_VAR);
808
839
  const header = document.querySelector('[class$="_header"]');
809
840
  const tabs = header !== null ? header.querySelector('[class$="_tabs"]') : null;
810
841
  const actions = header !== null ? header.querySelector('[class$="_titleCluster"] [class$="_headerActions"]') : null;
811
842
  if (actions instanceof HTMLElement)
812
843
  actions.style.transform = '';
844
+ // The v67 padding reservation is gone with the v80 cap; clearing it keeps a
845
+ // profile that hot-updated from an older build from keeping a stale inline
846
+ // width on the row.
813
847
  if (tabs instanceof HTMLElement)
814
848
  tabs.style.paddingRight = '';
815
849
  if (headerObserver !== null) {
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Family plugin-card seat.
3
+ *
4
+ * A family plugin contributes its settings card to whichever plugin-card seat
5
+ * the running host actually renders:
6
+ *
7
+ * - `web-ui.plugin.item` — the list seat declared by the dsh-web-settings
8
+ * group section (this family's own first-level "Web UI plugins" section);
9
+ * - `settings.plugin.item` — the official keyed seat of the harness's
10
+ * `ui-settings-plugins` tab, keyed by the settings namespace the card edits.
11
+ *
12
+ * SEAT SELECTION IS NOT A DECLARATION PROBE. The official `ui-settings-plugins`
13
+ * row belongs to the harness bundle and its `configurable` tab always declares
14
+ * `settings.plugin.item` before any external plugin's `apply()` runs, so
15
+ * "is the official seat declared?" answers yes even in the deployment whose
16
+ * whole point is the family group. Choosing on that probe sends every family
17
+ * card to the official Plugins tab and leaves the group's own section
18
+ * permanently empty — the family of reports where the section renders its
19
+ * heading and zero cards.
20
+ *
21
+ * The signal that actually distinguishes the two deployments is whether
22
+ * dsh-web-settings is loaded: it is the package that owns the group section and
23
+ * it publishes the `webUiSettings` service during `apply()`, which every
24
+ * family plugin already reads for its settings scope. Group loaded -> the family
25
+ * seat; group absent -> the official seat.
26
+ *
27
+ * The decision is re-evaluated on every `slots/changed` because the group may
28
+ * apply after this plugin (the family aggregate orders it first, a profile that
29
+ * installs the group separately need not): the initial contribution goes to the
30
+ * official seat, then moves to the family seat the moment the group's section
31
+ * registers. The entry is disposed before the replacement is registered, so a
32
+ * card is never in two seats at once.
33
+ *
34
+ * The shared tree has no client-SDK dependency, so this module reads its
35
+ * context through the structural shape below; callers pass the plugin's own
36
+ * `ctx`.
37
+ */
38
+ /** The family list seat key. */
39
+ export declare const FAMILY_PLUGIN_CARD_SEAT = "web-ui.plugin.item";
40
+ /** The official keyed plugin-card seat key. */
41
+ export declare const OFFICIAL_PLUGIN_CARD_SEAT = "settings.plugin.item";
42
+ /** The service dsh-web-settings publishes while it is loaded. */
43
+ export declare const FAMILY_GROUP_SERVICE = "webUiSettings";
44
+ /** The slot-registry member this helper uses (structurally satisfied by ctx.slots). */
45
+ export interface PluginCardSlots {
46
+ /** Contribution of one card entry. */
47
+ register(options: never, component: never): unknown;
48
+ }
49
+ /** The slice of the client context a card contribution needs. */
50
+ export interface PluginCardContext {
51
+ slots: PluginCardSlots;
52
+ /** Service lookup; absent on a context double that only models the slots. */
53
+ get?(name: string): unknown;
54
+ /** Event subscription seat; absent on an event-less test double. */
55
+ on?(event: string, listener: (...args: never[]) => void): unknown;
56
+ }
57
+ /** Owner share of a plugin card (both seats supply nothing). */
58
+ export interface SettingsPluginItemOwnerProps {
59
+ /** Marker field: card owner props are intentionally empty. */
60
+ children?: never;
61
+ }
62
+ /** One family plugin's card contribution. */
63
+ export interface PluginCardSeat {
64
+ /** Settings namespace the card edits (the official seat's dispatch key). */
65
+ namespace: string;
66
+ /** Family list-seat entry id. */
67
+ id: string;
68
+ /** Family list-seat sort order. */
69
+ order?: number;
70
+ /** Family list-seat display label; the official keyed seat carries none. */
71
+ label?: () => string;
72
+ /** Locale namespace the card renders with. */
73
+ locale: string;
74
+ /** Business-face factory of the registration. */
75
+ inject?: () => object;
76
+ /**
77
+ * The card component. Its props type is the seat's composed shape, which is
78
+ * chosen at runtime, so the helper takes it erased; the registrant keeps its
79
+ * own precise typing at the call site.
80
+ */
81
+ component: unknown;
82
+ }
83
+ /**
84
+ * Whether the family group (dsh-web-settings) is loaded in this page. The
85
+ * service is the group package's own contract, so the probe cannot be fooled
86
+ * by a harness release that starts declaring the official seat differently.
87
+ */
88
+ export declare function familyGroupLoaded(ctx: PluginCardContext): boolean;
89
+ /**
90
+ * Contribute one family plugin card to the seat this host renders, following
91
+ * the group if it loads later. The entry is disposed and re-registered on a
92
+ * seat change, never duplicated.
93
+ * @param ctx - client context (its slot registry decides the seat).
94
+ * @param seat - the card contribution.
95
+ */
96
+ export declare function installPluginCard(ctx: PluginCardContext, seat: PluginCardSeat): void;
97
+ //# sourceMappingURL=plugin-card-seat.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-card-seat.d.ts","sourceRoot":"","sources":["../../../src/client/plugin-card-seat.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH,gCAAgC;AAChC,eAAO,MAAM,uBAAuB,uBAAuB,CAAA;AAE3D,+CAA+C;AAC/C,eAAO,MAAM,yBAAyB,yBAAyB,CAAA;AAE/D,iEAAiE;AACjE,eAAO,MAAM,oBAAoB,kBAAkB,CAAA;AAEnD,uFAAuF;AACvF,MAAM,WAAW,eAAe;IAC9B,sCAAsC;IACtC,QAAQ,CAAC,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,GAAG,OAAO,CAAA;CACpD;AAED,iEAAiE;AACjE,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,eAAe,CAAA;IACtB,6EAA6E;IAC7E,GAAG,CAAC,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;IAC3B,oEAAoE;IACpE,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,IAAI,GAAG,OAAO,CAAA;CAClE;AAED,gEAAgE;AAChE,MAAM,WAAW,4BAA4B;IAC3C,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,KAAK,CAAA;CACjB;AAED,6CAA6C;AAC7C,MAAM,WAAW,cAAc;IAC7B,4EAA4E;IAC5E,SAAS,EAAE,MAAM,CAAA;IACjB,iCAAiC;IACjC,EAAE,EAAE,MAAM,CAAA;IACV,mCAAmC;IACnC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,4EAA4E;IAC5E,KAAK,CAAC,EAAE,MAAM,MAAM,CAAA;IACpB,8CAA8C;IAC9C,MAAM,EAAE,MAAM,CAAA;IACd,iDAAiD;IACjD,MAAM,CAAC,EAAE,MAAM,MAAM,CAAA;IACrB;;;;OAIG;IACH,SAAS,EAAE,OAAO,CAAA;CACnB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,iBAAiB,GAAG,OAAO,CAUjE;AAWD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,iBAAiB,EAAE,IAAI,EAAE,cAAc,GAAG,IAAI,CA4DpF"}
@@ -0,0 +1,141 @@
1
+ // Generated by scripts/sync-shared.mjs from shared/client/settings/plugin-card-seat.ts. Do not edit this copy; edit the shared source and run "node scripts/sync-shared.mjs".
2
+ /**
3
+ * Family plugin-card seat.
4
+ *
5
+ * A family plugin contributes its settings card to whichever plugin-card seat
6
+ * the running host actually renders:
7
+ *
8
+ * - `web-ui.plugin.item` — the list seat declared by the dsh-web-settings
9
+ * group section (this family's own first-level "Web UI plugins" section);
10
+ * - `settings.plugin.item` — the official keyed seat of the harness's
11
+ * `ui-settings-plugins` tab, keyed by the settings namespace the card edits.
12
+ *
13
+ * SEAT SELECTION IS NOT A DECLARATION PROBE. The official `ui-settings-plugins`
14
+ * row belongs to the harness bundle and its `configurable` tab always declares
15
+ * `settings.plugin.item` before any external plugin's `apply()` runs, so
16
+ * "is the official seat declared?" answers yes even in the deployment whose
17
+ * whole point is the family group. Choosing on that probe sends every family
18
+ * card to the official Plugins tab and leaves the group's own section
19
+ * permanently empty — the family of reports where the section renders its
20
+ * heading and zero cards.
21
+ *
22
+ * The signal that actually distinguishes the two deployments is whether
23
+ * dsh-web-settings is loaded: it is the package that owns the group section and
24
+ * it publishes the `webUiSettings` service during `apply()`, which every
25
+ * family plugin already reads for its settings scope. Group loaded -> the family
26
+ * seat; group absent -> the official seat.
27
+ *
28
+ * The decision is re-evaluated on every `slots/changed` because the group may
29
+ * apply after this plugin (the family aggregate orders it first, a profile that
30
+ * installs the group separately need not): the initial contribution goes to the
31
+ * official seat, then moves to the family seat the moment the group's section
32
+ * registers. The entry is disposed before the replacement is registered, so a
33
+ * card is never in two seats at once.
34
+ *
35
+ * The shared tree has no client-SDK dependency, so this module reads its
36
+ * context through the structural shape below; callers pass the plugin's own
37
+ * `ctx`.
38
+ */
39
+ /** The family list seat key. */
40
+ export const FAMILY_PLUGIN_CARD_SEAT = 'web-ui.plugin.item';
41
+ /** The official keyed plugin-card seat key. */
42
+ export const OFFICIAL_PLUGIN_CARD_SEAT = 'settings.plugin.item';
43
+ /** The service dsh-web-settings publishes while it is loaded. */
44
+ export const FAMILY_GROUP_SERVICE = 'webUiSettings';
45
+ /**
46
+ * Whether the family group (dsh-web-settings) is loaded in this page. The
47
+ * service is the group package's own contract, so the probe cannot be fooled
48
+ * by a harness release that starts declaring the official seat differently.
49
+ */
50
+ export function familyGroupLoaded(ctx) {
51
+ const get = ctx.get;
52
+ if (typeof get !== 'function')
53
+ return false;
54
+ try {
55
+ return get.call(ctx, FAMILY_GROUP_SERVICE) !== undefined;
56
+ }
57
+ catch {
58
+ // A context that refuses the lookup: treat the group as absent, which
59
+ // falls back to the official seat the harness declares.
60
+ return false;
61
+ }
62
+ }
63
+ /** Report a refused registration instead of leaving the user with no card. */
64
+ function warnRefusedSeat(seat, error) {
65
+ try {
66
+ console.warn(`[dsh-web] plugin card registration into "${seat}" was refused; the card will not render`, error);
67
+ }
68
+ catch {
69
+ // Best-effort console write; a failed warning must not break the plugin.
70
+ }
71
+ }
72
+ /**
73
+ * Contribute one family plugin card to the seat this host renders, following
74
+ * the group if it loads later. The entry is disposed and re-registered on a
75
+ * seat change, never duplicated.
76
+ * @param ctx - client context (its slot registry decides the seat).
77
+ * @param seat - the card contribution.
78
+ */
79
+ export function installPluginCard(ctx, seat) {
80
+ const slots = ctx.slots;
81
+ const component = seat.component;
82
+ const inject = seat.inject;
83
+ let dispose;
84
+ let current;
85
+ /**
86
+ * Re-entrancy latch. The registry emits a change event synchronously from
87
+ * inside both `register` and the previous entry's disposer, so an unguarded
88
+ * reconcile would re-enter itself mid-move and register the card twice into
89
+ * the seat it is leaving ("already has an entry for key ...").
90
+ */
91
+ let reconciling = false;
92
+ /** Reconcile the contribution with the currently live seat (no-op when unchanged). */
93
+ const reconcile = () => {
94
+ if (reconciling)
95
+ return;
96
+ const target = familyGroupLoaded(ctx) ? FAMILY_PLUGIN_CARD_SEAT : OFFICIAL_PLUGIN_CARD_SEAT;
97
+ if (current === target)
98
+ return;
99
+ reconciling = true;
100
+ const previous = dispose;
101
+ dispose = undefined;
102
+ current = undefined;
103
+ previous?.();
104
+ try {
105
+ dispose = slots.register((target === FAMILY_PLUGIN_CARD_SEAT
106
+ ? {
107
+ name: FAMILY_PLUGIN_CARD_SEAT,
108
+ id: seat.id,
109
+ ...(seat.order === undefined ? {} : { order: seat.order }),
110
+ ...(seat.label === undefined ? {} : { label: seat.label }),
111
+ locale: seat.locale,
112
+ ...(seat.inject === undefined ? {} : { inject }),
113
+ }
114
+ : {
115
+ name: OFFICIAL_PLUGIN_CARD_SEAT,
116
+ key: seat.namespace,
117
+ locale: seat.locale,
118
+ ...(seat.inject === undefined ? {} : { inject }),
119
+ }), component);
120
+ current = target;
121
+ }
122
+ catch (error) {
123
+ warnRefusedSeat(target, error);
124
+ }
125
+ finally {
126
+ reconciling = false;
127
+ }
128
+ };
129
+ if (typeof ctx.on === 'function') {
130
+ try {
131
+ ctx.on('slots/changed', () => { reconcile(); });
132
+ }
133
+ catch {
134
+ // An event seat that refuses the subscription leaves the initial
135
+ // decision in place; the card still renders in the seat chosen below.
136
+ }
137
+ }
138
+ // The initial decision: the group, when it is already loaded (the aggregate
139
+ // order), otherwise the official seat the harness declares.
140
+ reconcile();
141
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Console-output capture shared by the family's Host halves.
3
+ *
4
+ * A child process's stdout/stderr are raw bytes, and on Windows a console
5
+ * program writes the OEM code page (CP936/GBK on a zh-CN system), not UTF-8.
6
+ * Decoding each chunk on its own corrupts the text twice over: the
7
+ * encoding is guessed wrong, and a multi-byte character split across two reads
8
+ * decodes into two replacement characters. This capture accumulates raw bytes
9
+ * under a byte budget and decodes exactly once, at read time.
10
+ */
11
+ /** Bounded byte accumulator for one child process's interleaved stdout/stderr. */
12
+ export interface OutputCapture {
13
+ /** Append one raw chunk (the bytes are copied into the budget). */
14
+ push(chunk: Uint8Array): void;
15
+ /**
16
+ * Decode everything captured so far. Idempotent, so a caller may read the
17
+ * tail both after a failure branch and at settlement.
18
+ */
19
+ read(): string;
20
+ }
21
+ /**
22
+ * Decode one console byte buffer. Strict UTF-8 wins whenever it is valid;
23
+ * otherwise the bytes are not UTF-8 at all and the console wrote its own code
24
+ * page, so the candidate that loses the least text is taken.
25
+ * @param bytes - the captured bytes.
26
+ * @returns the decoded text.
27
+ */
28
+ export declare function decodeConsoleBytes(bytes: Uint8Array): string;
29
+ /**
30
+ * Create a capture bounded to a byte budget. Overflow drops from the FRONT
31
+ * (the tail is what carries a failure reason), and the cut never lands inside
32
+ * a character: leading UTF-8 continuation bytes are dropped with it, and a
33
+ * legacy code page's dangling lead byte — indistinguishable from a real byte
34
+ * without knowing the encoding until the whole buffer is decoded — is removed
35
+ * with the one replacement character it produced, so an over-long stream still
36
+ * reads cleanly at the seam.
37
+ * @param maxBytes - maximum retained bytes.
38
+ * @returns the capture.
39
+ */
40
+ export declare function createOutputCapture(maxBytes: number): OutputCapture;
41
+ //# sourceMappingURL=console-output.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"console-output.d.ts","sourceRoot":"","sources":["../../src/console-output.ts"],"names":[],"mappings":"AACA;;;;;;;;;GASG;AAEH,kFAAkF;AAClF,MAAM,WAAW,aAAa;IAC5B,mEAAmE;IACnE,IAAI,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAAA;IAC7B;;;OAGG;IACH,IAAI,IAAI,MAAM,CAAA;CACf;AAWD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CA2B5D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,aAAa,CAiBnE"}
@@ -0,0 +1,89 @@
1
+ // Generated by scripts/sync-shared.mjs from shared/host/console-output.ts. Do not edit this copy; edit the shared source and run "node scripts/sync-shared.mjs".
2
+ /**
3
+ * Console-output capture shared by the family's Host halves.
4
+ *
5
+ * A child process's stdout/stderr are raw bytes, and on Windows a console
6
+ * program writes the OEM code page (CP936/GBK on a zh-CN system), not UTF-8.
7
+ * Decoding each chunk on its own corrupts the text twice over: the
8
+ * encoding is guessed wrong, and a multi-byte character split across two reads
9
+ * decodes into two replacement characters. This capture accumulates raw bytes
10
+ * under a byte budget and decodes exactly once, at read time.
11
+ */
12
+ /** Count the U+FFFD replacement characters one candidate decode produced. */
13
+ function replacementCount(text) {
14
+ let count = 0;
15
+ for (const character of text) {
16
+ if (character === '\uFFFD')
17
+ count += 1;
18
+ }
19
+ return count;
20
+ }
21
+ /**
22
+ * Decode one console byte buffer. Strict UTF-8 wins whenever it is valid;
23
+ * otherwise the bytes are not UTF-8 at all and the console wrote its own code
24
+ * page, so the candidate that loses the least text is taken.
25
+ * @param bytes - the captured bytes.
26
+ * @returns the decoded text.
27
+ */
28
+ export function decodeConsoleBytes(bytes) {
29
+ try {
30
+ return new TextDecoder('utf-8', { fatal: true }).decode(bytes);
31
+ }
32
+ catch {
33
+ // Not valid UTF-8. Prefer whichever candidate leaves the fewest
34
+ // replacement characters so real UTF-8 text carrying one bad byte is not
35
+ // rewritten wholesale as GBK gibberish; without the legacy code page
36
+ // (small-ICU builds) the lenient UTF-8 pass is all that is left.
37
+ let best;
38
+ let bestLoss = Number.POSITIVE_INFINITY;
39
+ for (const label of ['gbk', 'utf-8']) {
40
+ let text;
41
+ try {
42
+ text = new TextDecoder(label).decode(bytes);
43
+ }
44
+ catch {
45
+ continue;
46
+ }
47
+ const loss = replacementCount(text);
48
+ if (loss < bestLoss) {
49
+ best = text;
50
+ bestLoss = loss;
51
+ }
52
+ }
53
+ /* v8 ignore next 3 -- reachable only without the legacy code page AND without the lenient pass */
54
+ if (best === undefined)
55
+ return Buffer.from(bytes).toString('utf8');
56
+ return best;
57
+ }
58
+ }
59
+ /**
60
+ * Create a capture bounded to a byte budget. Overflow drops from the FRONT
61
+ * (the tail is what carries a failure reason), and the cut never lands inside
62
+ * a character: leading UTF-8 continuation bytes are dropped with it, and a
63
+ * legacy code page's dangling lead byte — indistinguishable from a real byte
64
+ * without knowing the encoding until the whole buffer is decoded — is removed
65
+ * with the one replacement character it produced, so an over-long stream still
66
+ * reads cleanly at the seam.
67
+ * @param maxBytes - maximum retained bytes.
68
+ * @returns the capture.
69
+ */
70
+ export function createOutputCapture(maxBytes) {
71
+ let buffer = Buffer.alloc(0);
72
+ let trimmed = false;
73
+ return {
74
+ push(chunk) {
75
+ buffer = buffer.length === 0 ? Buffer.from(chunk) : Buffer.concat([buffer, Buffer.from(chunk)]);
76
+ if (buffer.length <= maxBytes)
77
+ return;
78
+ let start = buffer.length - maxBytes;
79
+ while (start < buffer.length && (buffer[start] & 0xc0) === 0x80)
80
+ start += 1;
81
+ buffer = Buffer.from(buffer.subarray(start));
82
+ trimmed = true;
83
+ },
84
+ read() {
85
+ const text = decodeConsoleBytes(buffer);
86
+ return trimmed && text.startsWith('\uFFFD') ? text.slice(1) : text;
87
+ },
88
+ };
89
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../src/update.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../src/update.ts"],"names":[],"mappings":"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"}
@@ -11,6 +11,7 @@
11
11
  import { readFileSync } from 'node:fs';
12
12
  import { dirname, join } from 'node:path';
13
13
  import { spawn } from 'node:child_process';
14
+ import { decodeConsoleBytes } from "./console-output.js";
14
15
  /** npm registry base used for version probes. */
15
16
  export const NPM_REGISTRY = 'https://registry.npmjs.org';
16
17
  /** The family scope every dsh-web package is published under. */
@@ -434,20 +435,12 @@ const WIN_CMD_MISSING_RE = /not recognized as an internal or external command|
434
435
  /**
435
436
  * Decode process output with tolerant decoding:
436
437
  * Defaults to UTF-8; if invalid byte sequences are encountered (common on
437
- * Windows consoles using code page 936/GBK), falls back to GBK via TextDecoder.
438
+ * Windows consoles using code page 936/GBK), falls back to the code page that
439
+ * loses the least text. Bytes are decoded once per whole chunk by the caller's
440
+ * accumulator — decoding per \`data\` event would split multi-byte characters.
438
441
  */
439
442
  function decodeProcessChunk(chunk) {
440
- try {
441
- return new TextDecoder('utf-8', { fatal: true }).decode(chunk);
442
- }
443
- catch {
444
- try {
445
- return new TextDecoder('gbk').decode(chunk);
446
- }
447
- catch {
448
- return chunk.toString('utf8');
449
- }
450
- }
443
+ return decodeConsoleBytes(chunk);
451
444
  }
452
445
  /**
453
446
  * Bypass for pnpm 11's supply-chain gate: `minimumReleaseAge` (default 24 h)