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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +13 -10
  3. package/README.zh.md +13 -10
  4. package/icon.svg +3 -0
  5. package/lib/client.js +396 -132
  6. package/lib/client.js.map +1 -1
  7. package/lib/index.js +604 -262
  8. package/lib/types/client/FenceNotice.d.ts.map +1 -1
  9. package/lib/types/client/FenceNotice.js +0 -3
  10. package/lib/types/client/PluginSettingsCard.d.ts +4 -0
  11. package/lib/types/client/PluginSettingsCard.d.ts.map +1 -1
  12. package/lib/types/client/PluginSettingsCard.js +3 -2
  13. package/lib/types/client/RemoteEntry.d.ts.map +1 -1
  14. package/lib/types/client/RemoteEntry.js +14 -9
  15. package/lib/types/client/RemotePanel.d.ts.map +1 -1
  16. package/lib/types/client/RemotePanel.js +13 -4
  17. package/lib/types/client/RemoteSettingsCard.d.ts +6 -6
  18. package/lib/types/client/RemoteSettingsCard.d.ts.map +1 -1
  19. package/lib/types/client/RemoteSettingsCard.js +14 -8
  20. package/lib/types/client/UpdateEntry.js +2 -2
  21. package/lib/types/client/UpdatePanel.js +2 -2
  22. package/lib/types/client/deep-link.js +2 -2
  23. package/lib/types/client/index.d.ts +16 -8
  24. package/lib/types/client/index.d.ts.map +1 -1
  25. package/lib/types/client/index.js +56 -32
  26. package/lib/types/client/locales.d.ts +0 -2
  27. package/lib/types/client/locales.d.ts.map +1 -1
  28. package/lib/types/client/locales.js +6 -8
  29. package/lib/types/client/mobile-adapt.d.ts.map +1 -1
  30. package/lib/types/client/mobile-adapt.js +143 -28
  31. package/lib/types/client/pair-api.d.ts +32 -3
  32. package/lib/types/client/pair-api.d.ts.map +1 -1
  33. package/lib/types/client/pair-api.js +44 -6
  34. package/lib/types/client/plugin-card-seat.d.ts +104 -0
  35. package/lib/types/client/plugin-card-seat.d.ts.map +1 -0
  36. package/lib/types/client/plugin-card-seat.js +143 -0
  37. package/lib/types/client/remote-channel.d.ts.map +1 -1
  38. package/lib/types/client/remote-channel.js +18 -5
  39. package/lib/types/client/settings-form.d.ts +17 -17
  40. package/lib/types/client/settings-form.d.ts.map +1 -1
  41. package/lib/types/client/settings-form.js +30 -27
  42. package/lib/types/console-output.d.ts +41 -0
  43. package/lib/types/console-output.d.ts.map +1 -0
  44. package/lib/types/console-output.js +89 -0
  45. package/lib/types/http.d.ts +18 -1
  46. package/lib/types/http.d.ts.map +1 -1
  47. package/lib/types/http.js +22 -1
  48. package/lib/types/index.d.ts +19 -11
  49. package/lib/types/index.d.ts.map +1 -1
  50. package/lib/types/index.js +91 -76
  51. package/lib/types/inner-auth.d.ts +3 -2
  52. package/lib/types/inner-auth.d.ts.map +1 -1
  53. package/lib/types/inner-auth.js +3 -2
  54. package/lib/types/pair-grant.d.ts +66 -0
  55. package/lib/types/pair-grant.d.ts.map +1 -0
  56. package/lib/types/pair-grant.js +95 -0
  57. package/lib/types/pairing.d.ts +44 -12
  58. package/lib/types/pairing.d.ts.map +1 -1
  59. package/lib/types/pairing.js +69 -20
  60. package/lib/types/pairing.persist.test.js +26 -0
  61. package/lib/types/posture.d.ts +11 -3
  62. package/lib/types/posture.d.ts.map +1 -1
  63. package/lib/types/posture.js +8 -3
  64. package/lib/types/remote-api.d.ts +10 -13
  65. package/lib/types/remote-api.d.ts.map +1 -1
  66. package/lib/types/remote-api.js +34 -34
  67. package/lib/types/remote-channel-boot.d.ts +6 -1
  68. package/lib/types/remote-channel-boot.d.ts.map +1 -1
  69. package/lib/types/remote-channel-boot.js +16 -8
  70. package/lib/types/remote-channel-rules.d.ts +14 -0
  71. package/lib/types/remote-channel-rules.d.ts.map +1 -1
  72. package/lib/types/remote-channel-rules.js +8 -0
  73. package/lib/types/remote-methods.d.ts +1 -1
  74. package/lib/types/remote-methods.d.ts.map +1 -1
  75. package/lib/types/remote-methods.js +1 -0
  76. package/lib/types/routes.d.ts +47 -5
  77. package/lib/types/routes.d.ts.map +1 -1
  78. package/lib/types/routes.js +160 -58
  79. package/lib/types/tunnel.d.ts +21 -6
  80. package/lib/types/tunnel.d.ts.map +1 -1
  81. package/lib/types/tunnel.js +58 -11
  82. package/lib/types/update.d.ts +4 -0
  83. package/lib/types/update.d.ts.map +1 -1
  84. package/lib/types/update.js +74 -33
  85. package/package.json +23 -20
  86. package/src/client/FenceNotice.tsx +0 -2
  87. package/src/client/PluginSettingsCard.tsx +7 -2
  88. package/src/client/RemoteEntry.tsx +14 -9
  89. package/src/client/RemotePanel.tsx +17 -7
  90. package/src/client/RemoteSettingsCard.tsx +16 -9
  91. package/src/client/UpdateEntry.tsx +2 -2
  92. package/src/client/UpdatePanel.tsx +3 -3
  93. package/src/client/deep-link.ts +2 -2
  94. package/src/client/index.ts +70 -40
  95. package/src/client/locales.ts +6 -8
  96. package/src/client/mobile-adapt.ts +136 -25
  97. package/src/client/pair-api.ts +47 -7
  98. package/src/client/plugin-card-seat.ts +192 -0
  99. package/src/client/remote-channel.ts +19 -4
  100. package/src/client/remote.module.css +38 -0
  101. package/src/client/settings-form.ts +34 -31
  102. package/src/console-output.ts +97 -0
  103. package/src/http.ts +23 -1
  104. package/src/index.ts +102 -79
  105. package/src/inner-auth.ts +3 -2
  106. package/src/pair-grant.ts +131 -0
  107. package/src/pairing.persist.test.ts +36 -0
  108. package/src/pairing.ts +75 -24
  109. package/src/posture.ts +12 -4
  110. package/src/remote-api.ts +34 -41
  111. package/src/remote-channel-boot.ts +16 -8
  112. package/src/remote-channel-rules.ts +17 -0
  113. package/src/remote-methods.ts +1 -0
  114. package/src/routes.ts +165 -57
  115. package/src/tunnel.ts +56 -11
  116. package/src/update.ts +74 -29
@@ -0,0 +1,104 @@
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
+ * - `plugins.bundle.config` — the official keyed seat of the harness's plugin
10
+ * manager page, keyed by the bundle's package name and rendered on that
11
+ * bundle's page. alpha.2 removed the `settings.plugin.item` keyed seat of the
12
+ * `ui-settings-plugins` tab that this helper used before, so a card keyed by
13
+ * its settings namespace has no seat to land in any more.
14
+ *
15
+ * SEAT SELECTION IS NOT A DECLARATION PROBE. The official plugin surface
16
+ * belongs to the harness bundle and declares its seats before any external
17
+ * plugin's `apply()` runs, so "is the official seat declared?" answers yes even
18
+ * in the deployment whose whole point is the family group. Choosing on that
19
+ * probe sends every family card to the official page and leaves the group's own
20
+ * section permanently empty — the family of reports where the section renders
21
+ * its heading and zero cards.
22
+ *
23
+ * The signal that actually distinguishes the two deployments is whether
24
+ * dsh-web-settings is loaded: it is the package that owns the group section and
25
+ * it publishes the `webUiSettings` service during `apply()`, which every
26
+ * family plugin already reads for its settings form. Group loaded -> the family
27
+ * seat; group absent -> the official seat.
28
+ *
29
+ * The decision is re-evaluated on every `slots/changed` because the group may
30
+ * apply after this plugin (the family aggregate orders it first, a profile that
31
+ * installs the group separately need not): the initial contribution goes to the
32
+ * official seat, then moves to the family seat the moment the group's section
33
+ * registers. The entry is disposed before the replacement is registered, so a
34
+ * card is never in two seats at once.
35
+ *
36
+ * The shared tree has no client-SDK dependency, so this module reads its
37
+ * context through the structural shape below; callers pass the plugin's own
38
+ * `ctx`.
39
+ */
40
+ /** The family list seat key. */
41
+ export declare const FAMILY_PLUGIN_CARD_SEAT = "web-ui.plugin.item";
42
+ /** The official keyed plugin-card seat key (the alpha.2 bundle-configuration seat). */
43
+ export declare const OFFICIAL_PLUGIN_CARD_SEAT = "plugins.bundle.config";
44
+ /** The service dsh-web-settings publishes while it is loaded. */
45
+ export declare const FAMILY_GROUP_SERVICE = "webUiSettings";
46
+ /** The slot-registry member this helper uses (structurally satisfied by ctx.slots). */
47
+ export interface PluginCardSlots {
48
+ /** Contribution of one card entry. */
49
+ register(options: never, component: never): unknown;
50
+ }
51
+ /** The slice of the client context a card contribution needs. */
52
+ export interface PluginCardContext {
53
+ slots: PluginCardSlots;
54
+ /** Service lookup; absent on a context double that only models the slots. */
55
+ get?(name: string): unknown;
56
+ /** Event subscription seat; absent on an event-less test double. */
57
+ on?(event: string, listener: (...args: never[]) => void): unknown;
58
+ }
59
+ /** Owner share of a family list-seat card (that seat supplies nothing). */
60
+ export interface SettingsPluginItemOwnerProps {
61
+ /** Marker field: card owner props are intentionally empty. */
62
+ children?: never;
63
+ }
64
+ /** One family plugin's card contribution. */
65
+ export interface PluginCardSeat {
66
+ /**
67
+ * Npm package name of the bundle this card configures; the official keyed
68
+ * seat dispatches on it. Every family package ships its own bundle patch, so
69
+ * a standalone install lists that package as the bundle whose page renders
70
+ * this card.
71
+ */
72
+ bundle: string;
73
+ /** Family list-seat entry id. */
74
+ id: string;
75
+ /** Family list-seat sort order. */
76
+ order?: number;
77
+ /** Family list-seat display label; the official keyed seat carries none. */
78
+ label?: () => string;
79
+ /** Locale namespace the card renders with. */
80
+ locale: string;
81
+ /** Business-face factory of the registration. */
82
+ inject?: () => object;
83
+ /**
84
+ * The card component. Its props type is the seat's composed shape, which is
85
+ * chosen at runtime, so the helper takes it erased; the registrant keeps its
86
+ * own precise typing at the call site.
87
+ */
88
+ component: unknown;
89
+ }
90
+ /**
91
+ * Whether the family group (dsh-web-settings) is loaded in this page. The
92
+ * service is the group package's own contract, so the probe cannot be fooled
93
+ * by a harness release that starts declaring the official seat differently.
94
+ */
95
+ export declare function familyGroupLoaded(ctx: PluginCardContext): boolean;
96
+ /**
97
+ * Contribute one family plugin card to the seat this host renders, following
98
+ * the group if it loads later. The entry is disposed and re-registered on a
99
+ * seat change, never duplicated.
100
+ * @param ctx - client context (its slot registry decides the seat).
101
+ * @param seat - the card contribution.
102
+ */
103
+ export declare function installPluginCard(ctx: PluginCardContext, seat: PluginCardSeat): void;
104
+ //# 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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH,gCAAgC;AAChC,eAAO,MAAM,uBAAuB,uBAAuB,CAAA;AAE3D,uFAAuF;AACvF,eAAO,MAAM,yBAAyB,0BAA0B,CAAA;AAEhE,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,2EAA2E;AAC3E,MAAM,WAAW,4BAA4B;IAC3C,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,KAAK,CAAA;CACjB;AAED,6CAA6C;AAC7C,MAAM,WAAW,cAAc;IAC7B;;;;;OAKG;IACH,MAAM,EAAE,MAAM,CAAA;IACd,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,143 @@
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
+ * - `plugins.bundle.config` — the official keyed seat of the harness's plugin
11
+ * manager page, keyed by the bundle's package name and rendered on that
12
+ * bundle's page. alpha.2 removed the `settings.plugin.item` keyed seat of the
13
+ * `ui-settings-plugins` tab that this helper used before, so a card keyed by
14
+ * its settings namespace has no seat to land in any more.
15
+ *
16
+ * SEAT SELECTION IS NOT A DECLARATION PROBE. The official plugin surface
17
+ * belongs to the harness bundle and declares its seats before any external
18
+ * plugin's `apply()` runs, so "is the official seat declared?" answers yes even
19
+ * in the deployment whose whole point is the family group. Choosing on that
20
+ * probe sends every family card to the official page and leaves the group's own
21
+ * section permanently empty — the family of reports where the section renders
22
+ * its heading and zero cards.
23
+ *
24
+ * The signal that actually distinguishes the two deployments is whether
25
+ * dsh-web-settings is loaded: it is the package that owns the group section and
26
+ * it publishes the `webUiSettings` service during `apply()`, which every
27
+ * family plugin already reads for its settings form. Group loaded -> the family
28
+ * seat; group absent -> the official seat.
29
+ *
30
+ * The decision is re-evaluated on every `slots/changed` because the group may
31
+ * apply after this plugin (the family aggregate orders it first, a profile that
32
+ * installs the group separately need not): the initial contribution goes to the
33
+ * official seat, then moves to the family seat the moment the group's section
34
+ * registers. The entry is disposed before the replacement is registered, so a
35
+ * card is never in two seats at once.
36
+ *
37
+ * The shared tree has no client-SDK dependency, so this module reads its
38
+ * context through the structural shape below; callers pass the plugin's own
39
+ * `ctx`.
40
+ */
41
+ /** The family list seat key. */
42
+ export const FAMILY_PLUGIN_CARD_SEAT = 'web-ui.plugin.item';
43
+ /** The official keyed plugin-card seat key (the alpha.2 bundle-configuration seat). */
44
+ export const OFFICIAL_PLUGIN_CARD_SEAT = 'plugins.bundle.config';
45
+ /** The service dsh-web-settings publishes while it is loaded. */
46
+ export const FAMILY_GROUP_SERVICE = 'webUiSettings';
47
+ /**
48
+ * Whether the family group (dsh-web-settings) is loaded in this page. The
49
+ * service is the group package's own contract, so the probe cannot be fooled
50
+ * by a harness release that starts declaring the official seat differently.
51
+ */
52
+ export function familyGroupLoaded(ctx) {
53
+ const get = ctx.get;
54
+ if (typeof get !== 'function')
55
+ return false;
56
+ try {
57
+ return get.call(ctx, FAMILY_GROUP_SERVICE) !== undefined;
58
+ }
59
+ catch {
60
+ // A context that refuses the lookup: treat the group as absent, which
61
+ // falls back to the official seat the harness declares.
62
+ return false;
63
+ }
64
+ }
65
+ /** Report a refused registration instead of leaving the user with no card. */
66
+ function warnRefusedSeat(seat, error) {
67
+ try {
68
+ console.warn(`[dsh-web] plugin card registration into "${seat}" was refused; the card will not render`, error);
69
+ }
70
+ catch {
71
+ // Best-effort console write; a failed warning must not break the plugin.
72
+ }
73
+ }
74
+ /**
75
+ * Contribute one family plugin card to the seat this host renders, following
76
+ * the group if it loads later. The entry is disposed and re-registered on a
77
+ * seat change, never duplicated.
78
+ * @param ctx - client context (its slot registry decides the seat).
79
+ * @param seat - the card contribution.
80
+ */
81
+ export function installPluginCard(ctx, seat) {
82
+ const slots = ctx.slots;
83
+ const component = seat.component;
84
+ const inject = seat.inject;
85
+ let dispose;
86
+ let current;
87
+ /**
88
+ * Re-entrancy latch. The registry emits a change event synchronously from
89
+ * inside both `register` and the previous entry's disposer, so an unguarded
90
+ * reconcile would re-enter itself mid-move and register the card twice into
91
+ * the seat it is leaving ("already has an entry for key ...").
92
+ */
93
+ let reconciling = false;
94
+ /** Reconcile the contribution with the currently live seat (no-op when unchanged). */
95
+ const reconcile = () => {
96
+ if (reconciling)
97
+ return;
98
+ const target = familyGroupLoaded(ctx) ? FAMILY_PLUGIN_CARD_SEAT : OFFICIAL_PLUGIN_CARD_SEAT;
99
+ if (current === target)
100
+ return;
101
+ reconciling = true;
102
+ const previous = dispose;
103
+ dispose = undefined;
104
+ current = undefined;
105
+ previous?.();
106
+ try {
107
+ dispose = slots.register((target === FAMILY_PLUGIN_CARD_SEAT
108
+ ? {
109
+ name: FAMILY_PLUGIN_CARD_SEAT,
110
+ id: seat.id,
111
+ ...(seat.order === undefined ? {} : { order: seat.order }),
112
+ ...(seat.label === undefined ? {} : { label: seat.label }),
113
+ locale: seat.locale,
114
+ ...(seat.inject === undefined ? {} : { inject }),
115
+ }
116
+ : {
117
+ name: OFFICIAL_PLUGIN_CARD_SEAT,
118
+ key: seat.bundle,
119
+ locale: seat.locale,
120
+ ...(seat.inject === undefined ? {} : { inject }),
121
+ }), component);
122
+ current = target;
123
+ }
124
+ catch (error) {
125
+ warnRefusedSeat(target, error);
126
+ }
127
+ finally {
128
+ reconciling = false;
129
+ }
130
+ };
131
+ if (typeof ctx.on === 'function') {
132
+ try {
133
+ ctx.on('slots/changed', () => { reconcile(); });
134
+ }
135
+ catch {
136
+ // An event seat that refuses the subscription leaves the initial
137
+ // decision in place; the card still renders in the seat chosen below.
138
+ }
139
+ }
140
+ // The initial decision: the group, when it is already loaded (the aggregate
141
+ // order), otherwise the official seat the harness declares.
142
+ reconcile();
143
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"remote-channel.d.ts","sourceRoot":"","sources":["../../../src/client/remote-channel.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"remote-channel.d.ts","sourceRoot":"","sources":["../../../src/client/remote-channel.ts"],"names":[],"mappings":"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"}
@@ -43,7 +43,11 @@ export function remoteChannelRequired(hostname, snapshot, hostPairingPolicy) {
43
43
  * @returns true for localhost, IPv6 loopback, or any 127/8 literal.
44
44
  */
45
45
  export function isLoopbackHostname(hostname) {
46
- if (hostname === 'localhost' || hostname === '::1')
46
+ // WHATWG location.hostname keeps IPv6 literals bracketed ("[::1]"); the
47
+ // host-side shared predicate accepts that spelling, so this browser copy must
48
+ // too, or an IPv6-loopback origin is judged remote and rewrites every call
49
+ // onto the gated channel where it can never pair.
50
+ if (hostname === 'localhost' || hostname === '::1' || hostname === '[::1]')
47
51
  return true;
48
52
  const parts = hostname.split('.');
49
53
  return parts.length === 4 && parts[0] === '127' && parts.every(part => /^\d{1,3}$/.test(part) && Number(part) <= 255);
@@ -193,11 +197,17 @@ export function installRemoteChannel(window, options = {}) {
193
197
  return init;
194
198
  const headers = init?.headers;
195
199
  if (typeof Headers !== 'undefined' && headers instanceof Headers) {
200
+ // Copy instead of mutating: the instance belongs to the caller, which may
201
+ // reuse it for a request this channel does not rewrite (the device
202
+ // credential must not ride along there).
196
203
  try {
197
- headers.set(RULES.deviceHeader, device);
204
+ const copy = new Headers(headers);
205
+ copy.set(RULES.deviceHeader, device);
206
+ return { ...init, headers: copy };
207
+ }
208
+ catch {
209
+ return init;
198
210
  }
199
- catch { /* ignore */ }
200
- return init;
201
211
  }
202
212
  if (typeof headers === 'object' && headers !== null) {
203
213
  return { ...init, headers: { ...headers, [RULES.deviceHeader]: device } };
@@ -218,7 +228,10 @@ export function installRemoteChannel(window, options = {}) {
218
228
  ? rewritten.toString()
219
229
  : new Request(rewritten, input);
220
230
  return Promise.resolve(originalFetch.call(window, next, attach(init))).then(async (response) => {
221
- if (await isUnpairedDenied(response.clone()))
231
+ // The clone tees the response body; only the 403 branch inspects it, so
232
+ // every other status skips the copy (a large upload or session dump
233
+ // would otherwise be duplicated per gated request).
234
+ if (response.status === 403 && await isUnpairedDenied(response.clone()))
222
235
  options.onUnpaired?.();
223
236
  else
224
237
  options.onPaired?.();
@@ -6,7 +6,7 @@
6
6
  * each plugin's client bundle; mirrors the official ui-plugin-config
7
7
  * card-store pattern.
8
8
  */
9
- import type { SettingsScope } from '@deepseek-ai/dsh-client-ui-settings/client';
9
+ import type { ConfigForm } from '@deepseek-ai/dsh-client-ui-settings/client';
10
10
  import type { SnapshotStore } from '@deepseek-ai/dsh-client-store';
11
11
  /** The write one field's staged text performs when the card is saved. */
12
12
  export type FieldWrite = {
@@ -86,7 +86,7 @@ export interface CardActions {
86
86
  /** Drop every staged edit. */
87
87
  discard: () => void;
88
88
  }
89
- /** One durable write inside the save's atomic scope mutation. */
89
+ /** One durable write inside the save's atomic form mutation. */
90
90
  export interface BatchedWrite {
91
91
  /** Field this entry writes. */
92
92
  field: string;
@@ -130,20 +130,20 @@ export declare class CardForm<T> {
130
130
  private readonly specs;
131
131
  private readonly staged;
132
132
  private readonly listeners;
133
- /** The scope subscription installed in the constructor; released by dispose(). */
134
- private readonly disposeScope;
133
+ /** The form subscription installed in the constructor; released by dispose(). */
134
+ private readonly disposeForm;
135
135
  private disposed;
136
136
  private saving;
137
137
  private failed;
138
138
  private failedReason;
139
- /** @param scope - the bound settings scope for this card's namespace. */
140
- constructor(scope: SettingsScope<T>, specs: FieldSpec[]);
139
+ /** @param scope - the bound configuration form for this card's namespace. */
140
+ constructor(scope: ConfigForm<T>, specs: FieldSpec[]);
141
141
  /**
142
- * Release the scope subscription and every bound store listener. The card
142
+ * Release the form subscription and every bound store listener. The card
143
143
  * must call this on teardown; later calls are no-ops.
144
144
  */
145
145
  dispose(): void;
146
- /** Publish a projection of this form, rebuilt whenever the scope or a draft changes. */
146
+ /** Publish a projection of this form, rebuilt whenever the form or a draft changes. */
147
147
  bind<S>(project: () => S): SnapshotStore<S>;
148
148
  /** Read the card-level state: what the Host serves, and what a save would do. */
149
149
  shell(): CardShell;
@@ -152,19 +152,19 @@ export declare class CardForm<T> {
152
152
  /** The actions the card's slot registration injects. */
153
153
  actions(): CardActions;
154
154
  /**
155
- * Write every staged edit in one atomic scope mutation, then re-seed from
155
+ * Write every staged edit in one atomic form mutation, then re-seed from
156
156
  * what the Host accepted.
157
157
  *
158
158
  * The whole batch rides one mutate, so cross-field validate hooks
159
159
  * (baseURL+model) judge it as a unit: the Host either applies every write
160
- * or refuses the batch. The 0.1.2 scope contract never rejects a refused
161
- * mutation — the scope recovers with a fresh Host view and resolves — so
162
- * resolution alone proves nothing: the outcome is judged by reading the
163
- * settled snapshot back, one planned write at a time, and one missed write
164
- * fails the whole save. A scope that still rejects on refusal (the dsh-web
165
- * bridge scope) reports through the same failure path with its rejection
166
- * message. A save that did not land keeps its drafts, so the user can
167
- * correct them instead of retyping.
160
+ * or refuses the batch. The form contract answers a refusal or a skipped
161
+ * write with `false` (it recovers with a fresh Host view instead of
162
+ * throwing), so the outcome is judged twice: the answer itself, and then the
163
+ * settled snapshot read back one planned write at a time. One missed write
164
+ * fails the whole save. A transport that rejects instead (the dsh-web bridge
165
+ * controller on a dead connection) reports through the same failure path
166
+ * with its rejection message. A save that did not land keeps its drafts, so
167
+ * the user can correct them instead of retyping.
168
168
  * @returns settlement after the mutation and the read-back.
169
169
  */
170
170
  save(): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"settings-form.d.ts","sourceRoot":"","sources":["../../../src/client/settings-form.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"settings-form.d.ts","sourceRoot":"","sources":["../../../src/client/settings-form.ts"],"names":[],"mappings":"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"}
@@ -91,30 +91,30 @@ export class CardForm {
91
91
  specs;
92
92
  staged = new Map();
93
93
  listeners = new Set();
94
- /** The scope subscription installed in the constructor; released by dispose(). */
95
- disposeScope;
94
+ /** The form subscription installed in the constructor; released by dispose(). */
95
+ disposeForm;
96
96
  disposed = false;
97
97
  saving = false;
98
98
  failed = false;
99
99
  failedReason;
100
- /** @param scope - the bound settings scope for this card's namespace. */
100
+ /** @param scope - the bound configuration form for this card's namespace. */
101
101
  constructor(scope, specs) {
102
102
  this.scope = scope;
103
103
  this.specs = new Map(specs.map(spec => [spec.field, spec]));
104
- this.disposeScope = scope.subscribe(() => { this.publish(); });
104
+ this.disposeForm = scope.subscribe(() => { this.publish(); });
105
105
  }
106
106
  /**
107
- * Release the scope subscription and every bound store listener. The card
107
+ * Release the form subscription and every bound store listener. The card
108
108
  * must call this on teardown; later calls are no-ops.
109
109
  */
110
110
  dispose() {
111
111
  if (this.disposed)
112
112
  return;
113
113
  this.disposed = true;
114
- this.disposeScope();
114
+ this.disposeForm();
115
115
  this.listeners.clear();
116
116
  }
117
- /** Publish a projection of this form, rebuilt whenever the scope or a draft changes. */
117
+ /** Publish a projection of this form, rebuilt whenever the form or a draft changes. */
118
118
  bind(project) {
119
119
  const store = createSnapshotStore(project());
120
120
  this.listeners.add(() => { store.set(project()); });
@@ -168,19 +168,19 @@ export class CardForm {
168
168
  };
169
169
  }
170
170
  /**
171
- * Write every staged edit in one atomic scope mutation, then re-seed from
171
+ * Write every staged edit in one atomic form mutation, then re-seed from
172
172
  * what the Host accepted.
173
173
  *
174
174
  * The whole batch rides one mutate, so cross-field validate hooks
175
175
  * (baseURL+model) judge it as a unit: the Host either applies every write
176
- * or refuses the batch. The 0.1.2 scope contract never rejects a refused
177
- * mutation — the scope recovers with a fresh Host view and resolves — so
178
- * resolution alone proves nothing: the outcome is judged by reading the
179
- * settled snapshot back, one planned write at a time, and one missed write
180
- * fails the whole save. A scope that still rejects on refusal (the dsh-web
181
- * bridge scope) reports through the same failure path with its rejection
182
- * message. A save that did not land keeps its drafts, so the user can
183
- * correct them instead of retyping.
176
+ * or refuses the batch. The form contract answers a refusal or a skipped
177
+ * write with `false` (it recovers with a fresh Host view instead of
178
+ * throwing), so the outcome is judged twice: the answer itself, and then the
179
+ * settled snapshot read back one planned write at a time. One missed write
180
+ * fails the whole save. A transport that rejects instead (the dsh-web bridge
181
+ * controller on a dead connection) reports through the same failure path
182
+ * with its rejection message. A save that did not land keeps its drafts, so
183
+ * the user can correct them instead of retyping.
184
184
  * @returns settlement after the mutation and the read-back.
185
185
  */
186
186
  async save() {
@@ -198,32 +198,35 @@ export class CardForm {
198
198
  this.failed = false;
199
199
  this.failedReason = undefined;
200
200
  this.publish();
201
- // One atomic namespace mutation: the 0.1.2 scope contract takes ordered
202
- // path operations, so the whole staged batch is validated, persisted, and
201
+ // One atomic namespace mutation: the form contract takes ordered path
202
+ // operations, so the whole staged batch is validated, persisted, and
203
203
  // recovered together — either every write lands or none does.
204
204
  const ops = valid.map(item => item.op.op === 'set'
205
205
  ? { op: 'set', path: [item.field], value: item.op.value }
206
206
  : { op: 'unset', path: [item.field] });
207
207
  let failedReason;
208
+ let accepted = false;
208
209
  try {
209
- await this.scope.mutate(ops);
210
+ accepted = await this.scope.mutate(ops);
210
211
  }
211
212
  catch (error) {
212
213
  failedReason = error instanceof Error ? error.message : String(error);
213
214
  }
214
- // The 0.1.2 scope resolves even a refused mutation (it recovers with a
215
- // fresh view instead of throwing), so resolution alone proves nothing:
216
- // judge every planned write against the settled snapshot. The mutation is
217
- // atomic, so one missed write fails the whole save and keeps the drafts.
218
- const landed = failedReason === undefined && valid.every(item => item.judge());
215
+ // `false` is the contract's refusal/skip answer, and the form still
216
+ // resolves after a refusal (it recovers with a fresh view), so the answer
217
+ // alone is not enough: judge every planned write against the settled
218
+ // snapshot as well. The mutation is atomic, so one missed write fails the
219
+ // whole save and keeps the drafts.
220
+ const landed = accepted && failedReason === undefined && valid.every(item => item.judge());
219
221
  for (const [field, before] of pending) {
220
222
  if (landed && this.staged.get(field) === before)
221
223
  this.staged.delete(field);
222
224
  }
223
225
  this.saving = false;
224
226
  this.failed = !landed;
225
- // A read-back failure carries no server reason: the card surfaces its
226
- // generic failure copy; a rejecting scope (the bridge) adds its message.
227
+ // A refused or unlanded batch carries no server reason: the card surfaces
228
+ // its generic failure copy; a rejecting transport (the bridge) adds its
229
+ // message.
227
230
  this.failedReason = failedReason;
228
231
  this.publish();
229
232
  }
@@ -261,7 +264,7 @@ export class CardForm {
261
264
  */
262
265
  landedSet(field, value) {
263
266
  // A redacted secret never appears in any wire view layer: the Host strips
264
- // role('secret') fields and reports them through a sidecar the scope
267
+ // role('secret') fields and reports them through a sidecar the form
265
268
  // snapshot does not expose, so there is nothing to compare the draft
266
269
  // against. Settling is the only signal the form has; the rest of the
267
270
  // batch, when one exists, still carries the atomic verdict by read-back.
@@ -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"}