@linxin666/dsh-remote-web-ui 0.3.3 → 0.3.4

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.
@@ -22,6 +22,8 @@ export interface PluginSettingsCardProps<TKey extends string = string> {
22
22
  titleKey: TKey;
23
23
  /** Locale key of the line describing what this plugin's settings govern. */
24
24
  descriptionKey: TKey;
25
+ /** Optional rich header description rendered instead of the plain t(descriptionKey) text (the plain text stays as the tooltip). */
26
+ descriptionNode?: ReactNode;
25
27
  /** The card's form state: availability, writability, and what a save would do. */
26
28
  state: CardShell;
27
29
  /** Write every staged edit. */
@@ -1 +1 @@
1
- {"version":3,"file":"PluginSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/client/PluginSettingsCard.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"PluginSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/client/PluginSettingsCard.tsx"],"names":[],"mappings":"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"}
@@ -40,8 +40,8 @@ export function PluginSettingsCard(props) {
40
40
  // With alwaysOpen the nav entry already provides the selection, so the
41
41
  // header is a static title row instead of a disclosure button.
42
42
  const header = alwaysOpen === true
43
- ? (_jsxs("div", { className: css.headerStatic, children: [_jsxs("span", { className: css.headText, children: [_jsx("span", { className: css.name, title: title, children: title }), _jsx("span", { className: css.description, title: description, children: description })] }), state.dirty ? _jsx("span", { className: css.pending, title: props.t('settings.unsaved'), children: props.t('settings.unsaved') }) : null] }))
44
- : (_jsxs("button", { type: "button", className: css.header, "aria-expanded": open, "aria-label": `${props.t(open ? 'settings.collapse' : 'settings.expand')}: ${title}`, onClick: () => { setOpen(!open); }, children: [_jsxs("span", { className: css.headText, children: [_jsx("span", { className: css.name, title: title, children: title }), _jsx("span", { className: css.description, title: description, children: description })] }), state.dirty ? _jsx("span", { className: css.pending, title: props.t('settings.unsaved'), children: props.t('settings.unsaved') }) : null, _jsx("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", xmlns: "http://www.w3.org/2000/svg", className: open ? `${css.chevron} ${css.chevronOpen}` : css.chevron, children: _jsx("path", { d: "M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z", fill: "currentColor" }) })] }));
43
+ ? (_jsxs("div", { className: css.headerStatic, children: [_jsxs("span", { className: css.headText, children: [_jsx("span", { className: css.name, title: title, children: title }), _jsx("span", { className: css.description, title: description, children: props.descriptionNode ?? description })] }), state.dirty ? _jsx("span", { className: css.pending, title: props.t('settings.unsaved'), children: props.t('settings.unsaved') }) : null] }))
44
+ : (_jsxs("button", { type: "button", className: css.header, "aria-expanded": open, "aria-label": `${props.t(open ? 'settings.collapse' : 'settings.expand')}: ${title}`, onClick: () => { setOpen(!open); }, children: [_jsxs("span", { className: css.headText, children: [_jsx("span", { className: css.name, title: title, children: title }), _jsx("span", { className: css.description, title: description, children: props.descriptionNode ?? description })] }), state.dirty ? _jsx("span", { className: css.pending, title: props.t('settings.unsaved'), children: props.t('settings.unsaved') }) : null, _jsx("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", xmlns: "http://www.w3.org/2000/svg", className: open ? `${css.chevron} ${css.chevronOpen}` : css.chevron, children: _jsx("path", { d: "M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z", fill: "currentColor" }) })] }));
45
45
  // The namespace exists but the Host does not serve it to this client (the
46
46
  // official settings allowlist omits third-party namespaces): show a card
47
47
  // that explains the gap instead of vanishing, so a missing card never
@@ -1 +1 @@
1
- {"version":3,"file":"dsh-home.d.ts","sourceRoot":"","sources":["../../src/dsh-home.ts"],"names":[],"mappings":"AACA;;;;GAIG;AAKH,+DAA+D;AAC/D,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,MAAkB,GAAG,MAAM,CAIzE;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,EAAE,IAAI,GAAE,MAAkB,GAAG,MAAM,CAOrG;AAED,gEAAgE;AAChE,wBAAgB,OAAO,IAAI,MAAM,CAEhC"}
1
+ {"version":3,"file":"dsh-home.d.ts","sourceRoot":"","sources":["../../src/dsh-home.ts"],"names":[],"mappings":"AACA;;;;GAIG;AAMH,+DAA+D;AAC/D,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,MAAkB,GAAG,MAAM,CAMzE;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,EAAE,IAAI,GAAE,MAAkB,GAAG,MAAM,CAUrG;AAED,gEAAgE;AAChE,wBAAgB,OAAO,IAAI,MAAM,CAEhC"}
@@ -6,12 +6,15 @@
6
6
  */
7
7
  import { homedir } from 'node:os';
8
8
  import { isAbsolute, join } from 'node:path';
9
+ import { isAbsolute as posixIsAbsolute, join as posixJoin } from 'node:path/posix';
9
10
  /** Expand a leading ~ (or ~user) in a path, platform-style. */
10
11
  export function expandHome(path, home = homedir()) {
12
+ const isPosix = home.startsWith('/');
13
+ const j = isPosix ? posixJoin : join;
11
14
  if (path === '~')
12
15
  return home;
13
16
  if (path.startsWith('~/') || path.startsWith('~\\'))
14
- return join(home, path.slice(2));
17
+ return j(home, path.slice(2));
15
18
  return path;
16
19
  }
17
20
  /**
@@ -21,12 +24,15 @@ export function expandHome(path, home = homedir()) {
21
24
  * @returns the absolute DSH home path.
22
25
  */
23
26
  export function resolveDshHome(env = process.env, home = homedir()) {
27
+ const isPosix = home.startsWith('/');
28
+ const j = isPosix ? posixJoin : join;
29
+ const isAbs = isPosix ? posixIsAbsolute : isAbsolute;
24
30
  const raw = env.DSH_HOME;
25
31
  if (raw !== undefined && raw.trim() !== '') {
26
32
  const expanded = expandHome(raw.trim(), home);
27
- return isAbsolute(expanded) ? expanded : join(process.cwd(), expanded);
33
+ return isAbs(expanded) ? expanded : j(process.cwd(), expanded);
28
34
  }
29
- return join(home, '.dsh');
35
+ return j(home, '.dsh');
30
36
  }
31
37
  /** Resolve the DSH home directory from the live environment. */
32
38
  export function dshHome() {
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAChD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAElD,OAAO,CAAC,MAAM,aAAa,CAAA;AAE3B,OAAO,EAA0C,KAAK,aAAa,EAAE,MAAM,cAAc,CAAA;AA6BzF,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,MAAM;QACd;;;;;WAKG;QACH,UAAU,CACR,IAAI,EAAE,OAAO,EACb,OAAO,EAAE,eAAe,EACxB,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,IAAI,EAAE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,GACrC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;KAC9B;CACF;AAED,iCAAiC;AACjC,eAAO,MAAM,IAAI,kBAAkB,CAAA;AAEnC,+DAA+D;AAC/D,eAAO,MAAM,MAAM,UAA4B,CAAA;AAE/C;;;;GAIG;AACH,eAAO,MAAM,gCAAgC,uDAAqC,CAAA;AAElF,qEAAqE;AACrE,MAAM,WAAW,MAAM;IACrB,yDAAyD;IACzD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,yEAAyE;IACzE,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,iDAAiD;IACjD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;;;;OAMG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAA;IAC9B;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B,2EAA2E;IAC3E,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,eAAO,MAAM,MAAM,EAAE,CAAC,CAAC,MAAM,CAY3B,CAAA;AAKF;;;;GAIG;AACH,KAAK,cAAc,GAAG,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,eAAe,GAAG,aAAa,CAAC,CAAC,GAAG;IAC9E,aAAa,EAAE,MAAM,GAAG,SAAS,CAAA;IACjC,WAAW,EAAE,MAAM,CAAA;CACpB,CAAA;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,IAAI,CAC5C,cAAc,EACd,YAAY,GAAG,gBAAgB,GAAG,YAAY,GAAG,cAAc,GAAG,YAAY,GAAG,aAAa,CAC/F,GAAG,aAAa,CAShB;AAED,4EAA4E;AAC5E,wBAAgB,kBAAkB,CAAC,IAAI,GAAE,MAAkB,GAAG,MAAM,CAEnE;AAiBD;;;;GAIG;AACH,eAAO,MAAM,KAAK,kBAAuD,CAAA;AAEzE,iBAAS,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAkUtD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAChD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAElD,OAAO,CAAC,MAAM,aAAa,CAAA;AAI3B,OAAO,EAA0C,KAAK,aAAa,EAAE,MAAM,cAAc,CAAA;AA6BzF,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,MAAM;QACd;;;;;WAKG;QACH,UAAU,CACR,IAAI,EAAE,OAAO,EACb,OAAO,EAAE,eAAe,EACxB,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,IAAI,EAAE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,GACrC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;KAC9B;CACF;AAED,iCAAiC;AACjC,eAAO,MAAM,IAAI,kBAAkB,CAAA;AAEnC,+DAA+D;AAC/D,eAAO,MAAM,MAAM,UAAkD,CAAA;AAErE;;;;GAIG;AACH,eAAO,MAAM,gCAAgC,uDAAqC,CAAA;AAElF,qEAAqE;AACrE,MAAM,WAAW,MAAM;IACrB,yDAAyD;IACzD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,yEAAyE;IACzE,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,iDAAiD;IACjD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;;;;OAMG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAA;IAC9B;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B,2EAA2E;IAC3E,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,eAAO,MAAM,MAAM,EAAE,CAAC,CAAC,MAAM,CAY3B,CAAA;AAKF;;;;GAIG;AACH,KAAK,cAAc,GAAG,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,eAAe,GAAG,aAAa,CAAC,CAAC,GAAG;IAC9E,aAAa,EAAE,MAAM,GAAG,SAAS,CAAA;IACjC,WAAW,EAAE,MAAM,CAAA;CACpB,CAAA;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,IAAI,CAC5C,cAAc,EACd,YAAY,GAAG,gBAAgB,GAAG,YAAY,GAAG,cAAc,GAAG,YAAY,GAAG,aAAa,CAC/F,GAAG,aAAa,CAShB;AAED,4EAA4E;AAC5E,wBAAgB,kBAAkB,CAAC,IAAI,GAAE,MAAkB,GAAG,MAAM,CAEnE;AAiBD;;;;GAIG;AACH,eAAO,MAAM,KAAK,kBAAuD,CAAA;AAEzE,iBAAS,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAkVtD"}
@@ -33,7 +33,7 @@ import { UUID_POLYFILL_SCRIPT } from "./uuid-polyfill.js";
33
33
  /** Stable cordis plugin name. */
34
34
  export const name = 'remote-web-ui';
35
35
  /** Services required before the pairing surfaces can mount. */
36
- export const inject = ['webServer', 'apiProxy'];
36
+ export const inject = ['webServer', 'apiProxy', 'commands', 'agents'];
37
37
  /**
38
38
  * Settings namespace of the remote-control capability — the section the web
39
39
  * settings surface edits. Spelled here rather than imported: the browser
@@ -266,7 +266,25 @@ function applyImpl(ctx, config) {
266
266
  ...makeRoutes({ service, lanAddresses, requirePairingForLan: () => resolve().requirePairingForLan }),
267
267
  ...makeMobileRoutes(),
268
268
  ...(apiProxy !== undefined
269
- ? makeMobileApiRoutes({ service, apiProxy, pendingTracker: new PendingTracker(), mobileEnterToSend: () => resolve().mobileEnterToSend })
269
+ ? makeMobileApiRoutes({
270
+ service,
271
+ apiProxy,
272
+ pendingTracker: new PendingTracker(),
273
+ mobileEnterToSend: () => resolve().mobileEnterToSend,
274
+ commandDispatcher: ctx.commands !== undefined && ctx.agents !== undefined
275
+ ? {
276
+ async execute(sessionId, line, signal) {
277
+ const agent = ctx.agents.get(sessionId);
278
+ if (agent === undefined)
279
+ return { error: 'session-not-found' };
280
+ const execution = await ctx.commands.execute(agent, line, [], signal);
281
+ if (execution === undefined)
282
+ return { error: 'unknown-command' };
283
+ return { ok: true, result: execution.result };
284
+ },
285
+ }
286
+ : undefined,
287
+ })
270
288
  : []),
271
289
  ...(apiProxy !== undefined ? makePairedModelCatalogRoutes({ service, apiProxy, lanAddresses }) : []),
272
290
  // The remote desktop channel: policy-gated `/remote` prefix that
@@ -1 +1 @@
1
- {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../../src/mobile/api.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../../src/mobile/api.ts"],"names":[],"mappings":"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"}
@@ -47,10 +47,9 @@ export async function prompt(sessionId, text) {
47
47
  }
48
48
  /** Send one slash command line (e.g. `/permission workspace-write`). */
49
49
  export async function sendCommand(sessionId, line) {
50
- return await callUnary('session.prompt', {
50
+ return await callUnary('mobile.command', {
51
51
  sessionId,
52
- mode: 'queue',
53
- content: [{ type: 'text', text: line }],
52
+ line,
54
53
  });
55
54
  }
56
55
  /**
@@ -37,6 +37,20 @@ export interface MobileApiDeps {
37
37
  mobileEnterToSend: () => boolean;
38
38
  /** SSE keep-alive ping cadence for the mux stream (default 15000 ms; test seam). */
39
39
  eventsHeartbeatMs?: number;
40
+ /**
41
+ * Optional command dispatcher for executing slash commands on the mobile
42
+ * surface. When absent, `mobile.command` returns an error; the mobile UI
43
+ * can still fall back to `session.prompt` (which sends the line to the
44
+ * model, not the command registry).
45
+ */
46
+ commandDispatcher?: {
47
+ execute(sessionId: string, line: string, signal: AbortSignal): Promise<{
48
+ ok: true;
49
+ result: unknown;
50
+ } | {
51
+ error: string;
52
+ }>;
53
+ };
40
54
  }
41
55
  /** Mobile API route paths. */
42
56
  export declare const MOBILE_API_PATHS: {
@@ -1 +1 @@
1
- {"version":3,"file":"mobile-api.d.ts","sourceRoot":"","sources":["../../src/mobile-api.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAA;AAC/D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gCAAgC,CAAA;AAG9D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AACzD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AA8DlD,iCAAiC;AACjC,MAAM,WAAW,aAAa;IAC5B,uDAAuD;IACvD,OAAO,EAAE,cAAc,CAAA;IACvB,0DAA0D;IAC1D,QAAQ,EAAE,QAAQ,CAAA;IAClB,2BAA2B;IAC3B,cAAc,EAAE,cAAc,CAAA;IAC9B,kEAAkE;IAClE,iBAAiB,EAAE,MAAM,OAAO,CAAA;IAChC,oFAAoF;IACpF,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAC3B;AAED,8BAA8B;AAC9B,eAAO,MAAM,gBAAgB;;CAEnB,CAAA;AAOV;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,aAAa,GAAG,QAAQ,EAAE,CA+KnE"}
1
+ {"version":3,"file":"mobile-api.d.ts","sourceRoot":"","sources":["../../src/mobile-api.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAA;AAC/D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gCAAgC,CAAA;AAG9D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AACzD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAgElD,iCAAiC;AACjC,MAAM,WAAW,aAAa;IAC5B,uDAAuD;IACvD,OAAO,EAAE,cAAc,CAAA;IACvB,0DAA0D;IAC1D,QAAQ,EAAE,QAAQ,CAAA;IAClB,2BAA2B;IAC3B,cAAc,EAAE,cAAc,CAAA;IAC9B,kEAAkE;IAClE,iBAAiB,EAAE,MAAM,OAAO,CAAA;IAChC,oFAAoF;IACpF,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE;QAClB,OAAO,CACL,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC;YAAE,EAAE,EAAE,IAAI,CAAC;YAAC,MAAM,EAAE,OAAO,CAAA;SAAE,GAAG;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KAC9D,CAAA;CACF;AAED,8BAA8B;AAC9B,eAAO,MAAM,gBAAgB;;CAEnB,CAAA;AAOV;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,aAAa,GAAG,QAAQ,EAAE,CA2NnE"}
@@ -55,6 +55,8 @@ const MOBILE_ALLOWLIST = new Set([
55
55
  const MOBILE_PREFERENCES_METHOD = 'mobile.preferences';
56
56
  const MOBILE_PENDING_METHOD = 'mobile.pending';
57
57
  const MOBILE_RESPOND_METHOD = 'mobile.respond';
58
+ /** Execute a slash command through the host command registry (#1125). */
59
+ const MOBILE_COMMAND_METHOD = 'mobile.command';
58
60
  /** One session.list page (thin phones load incrementally). */
59
61
  const SESSION_PAGE_SIZE = 20;
60
62
  /** SSE keep-alive ping cadence for the live mux stream (single connection). */
@@ -130,7 +132,8 @@ export function makeMobileApiRoutes(deps) {
130
132
  const method = pathname.slice(MOBILE_API_METHOD_PREFIX.length);
131
133
  const local = method === MOBILE_PREFERENCES_METHOD
132
134
  || method === MOBILE_PENDING_METHOD
133
- || method === MOBILE_RESPOND_METHOD;
135
+ || method === MOBILE_RESPOND_METHOD
136
+ || method === MOBILE_COMMAND_METHOD;
134
137
  if (!MOBILE_ALLOWLIST.has(method) && !local) {
135
138
  writeJson(res, 403, { ok: false, error: { code: 'forbidden', message: `method ${method} is not exposed to the mobile surface` } });
136
139
  return;
@@ -188,6 +191,55 @@ export function makeMobileApiRoutes(deps) {
188
191
  });
189
192
  }
190
193
  }
194
+ else if (method === MOBILE_COMMAND_METHOD) {
195
+ const payload = parsed.payload;
196
+ const sessionId = payload?.sessionId;
197
+ const line = payload?.line;
198
+ if (typeof sessionId !== 'string' || typeof line !== 'string') {
199
+ writeJson(res, 200, {
200
+ type: 'server-response',
201
+ rpcId,
202
+ result: { ok: false, error: { code: 'bad-request', message: 'missing sessionId or line' } },
203
+ });
204
+ }
205
+ else if (deps.commandDispatcher === undefined) {
206
+ writeJson(res, 200, {
207
+ type: 'server-response',
208
+ rpcId,
209
+ result: { ok: false, error: { code: 'unavailable', message: 'command dispatcher is not available' } },
210
+ });
211
+ }
212
+ else {
213
+ try {
214
+ const abort = new AbortController();
215
+ res.on('close', () => { if (!res.writableEnded)
216
+ abort.abort(); });
217
+ const outcome = await deps.commandDispatcher.execute(sessionId, line, abort.signal);
218
+ if ('error' in outcome) {
219
+ writeJson(res, 200, {
220
+ type: 'server-response',
221
+ rpcId,
222
+ result: { ok: false, error: { code: outcome.error, message: outcome.error } },
223
+ });
224
+ }
225
+ else {
226
+ writeJson(res, 200, {
227
+ type: 'server-response',
228
+ rpcId,
229
+ result: { ok: true, value: { matched: true, result: outcome.result } },
230
+ });
231
+ }
232
+ }
233
+ catch (error) {
234
+ const message = error instanceof Error ? error.message : String(error);
235
+ writeJson(res, 200, {
236
+ type: 'server-response',
237
+ rpcId,
238
+ result: { ok: false, error: { code: 'internal', message } },
239
+ });
240
+ }
241
+ }
242
+ }
191
243
  return;
192
244
  }
193
245
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"mobile-routes.d.ts","sourceRoot":"","sources":["../../src/mobile-routes.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAA;AAwH/D;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,QAAQ,EAAE,CAyE7C"}
1
+ {"version":3,"file":"mobile-routes.d.ts","sourceRoot":"","sources":["../../src/mobile-routes.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAOH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAA;AAwH/D;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,QAAQ,EAAE,CA8F7C"}
@@ -7,6 +7,7 @@
7
7
  import { readFile } from 'node:fs/promises';
8
8
  import { existsSync } from 'node:fs';
9
9
  import { fileURLToPath } from 'node:url';
10
+ import { gzipSync } from 'node:zlib';
10
11
  const MOBILE_ROOT = '/m/';
11
12
  const MOBILE_BUNDLE_URL = '/m/mobile.js';
12
13
  const MOBILE_MANIFEST_URL = '/m/manifest.webmanifest';
@@ -132,7 +133,8 @@ export function makeMobileRoutes() {
132
133
  // The bundle is immutable for the process lifetime (a rebuild requires a
133
134
  // host restart), so the body is read from disk once, not per phone.
134
135
  let bundleBody;
135
- const handleBundle = async (_req, res) => {
136
+ let bundleGzip;
137
+ const handleBundle = async (req, res) => {
136
138
  if (bundleBody === undefined) {
137
139
  const path = mobileBundlePath();
138
140
  if (!existsSync(path)) {
@@ -141,13 +143,34 @@ export function makeMobileRoutes() {
141
143
  }
142
144
  try {
143
145
  bundleBody = await readFile(path, 'utf8');
146
+ bundleGzip = gzipSync(Buffer.from(bundleBody, 'utf8'));
144
147
  }
145
148
  catch {
146
149
  writeStatic(res, 500, 'text/plain', 'failed to read the mobile bundle');
147
150
  return;
148
151
  }
149
152
  }
150
- writeStatic(res, 200, 'text/javascript', bundleBody);
153
+ const acceptEncoding = req.headers['accept-encoding'] ?? '';
154
+ const acceptsGzip = typeof acceptEncoding === 'string' && acceptEncoding.includes('gzip') && bundleGzip !== undefined;
155
+ if (acceptsGzip) {
156
+ res.writeHead(200, {
157
+ 'content-type': 'text/javascript; charset=utf-8',
158
+ 'content-encoding': 'gzip',
159
+ 'cache-control': 'no-cache',
160
+ vary: 'Accept-Encoding',
161
+ 'referrer-policy': 'no-referrer',
162
+ });
163
+ res.end(bundleGzip);
164
+ }
165
+ else {
166
+ res.writeHead(200, {
167
+ 'content-type': 'text/javascript; charset=utf-8',
168
+ 'cache-control': 'no-cache',
169
+ vary: 'Accept-Encoding',
170
+ 'referrer-policy': 'no-referrer',
171
+ });
172
+ res.end(bundleBody);
173
+ }
151
174
  };
152
175
  let workerBody;
153
176
  const handleWorker = async (_req, res) => {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@linxin666/dsh-remote-web-ui",
3
3
  "description": "Scan-to-pair mobile and PC remote control for the dsh web GUI: QR entry beside the settings button, one-time pairing tokens, a gated /remote/api desktop channel, live device status, and revocable sessions",
4
- "version": "0.3.3",
4
+ "version": "0.3.4",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "node": "^22.19.0 || >=24.0.0"
@@ -75,6 +75,8 @@
75
75
  "@deepseek-ai/dsh-host-webserver": "^0.1.1-rc.2",
76
76
  "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
77
77
  "@deepseek-ai/dsh-settings": "^0.1.1-rc.2",
78
+ "@deepseek-ai/dsh-agent": "^0.1.1-rc.2",
79
+ "@deepseek-ai/dsh-commands": "^0.1.1-rc.2",
78
80
  "@testing-library/dom": "^10.4.1",
79
81
  "@testing-library/react": "^16.3.2",
80
82
  "@types/node": "^22.20.0",
@@ -39,6 +39,8 @@ export interface PluginSettingsCardProps<TKey extends string = string> {
39
39
  titleKey: TKey
40
40
  /** Locale key of the line describing what this plugin's settings govern. */
41
41
  descriptionKey: TKey
42
+ /** Optional rich header description rendered instead of the plain t(descriptionKey) text (the plain text stays as the tooltip). */
43
+ descriptionNode?: ReactNode
42
44
  /** The card's form state: availability, writability, and what a save would do. */
43
45
  state: CardShell
44
46
  /** Write every staged edit. */
@@ -87,7 +89,7 @@ export function PluginSettingsCard<TKey extends string = string>(props: PluginSe
87
89
  <div className={css.headerStatic}>
88
90
  <span className={css.headText}>
89
91
  <span className={css.name} title={title}>{title}</span>
90
- <span className={css.description} title={description}>{description}</span>
92
+ <span className={css.description} title={description}>{props.descriptionNode ?? description}</span>
91
93
  </span>
92
94
  {state.dirty ? <span className={css.pending} title={props.t('settings.unsaved')}>{props.t('settings.unsaved')}</span> : null}
93
95
  </div>
@@ -102,7 +104,7 @@ export function PluginSettingsCard<TKey extends string = string>(props: PluginSe
102
104
  >
103
105
  <span className={css.headText}>
104
106
  <span className={css.name} title={title}>{title}</span>
105
- <span className={css.description} title={description}>{description}</span>
107
+ <span className={css.description} title={description}>{props.descriptionNode ?? description}</span>
106
108
  </span>
107
109
  {state.dirty ? <span className={css.pending} title={props.t('settings.unsaved')}>{props.t('settings.unsaved')}</span> : null}
108
110
  <svg
@@ -24,6 +24,10 @@
24
24
  .header {
25
25
  appearance: none;
26
26
  width: 100%;
27
+ /* The suite does not force a global border-box, so width:100% + padding
28
+ would stretch the row 32px past the card and push the trailing pending
29
+ badge (last flex item) outside the card border. */
30
+ box-sizing: border-box;
27
31
  font: inherit;
28
32
  color: inherit;
29
33
  text-align: left;
@@ -44,6 +48,7 @@
44
48
 
45
49
  .headerStatic {
46
50
  width: 100%;
51
+ box-sizing: border-box;
47
52
  border-radius: 12px;
48
53
  align-items: center;
49
54
  gap: 12px;
package/src/dsh-home.ts CHANGED
@@ -7,11 +7,14 @@
7
7
 
8
8
  import { homedir } from 'node:os'
9
9
  import { isAbsolute, join } from 'node:path'
10
+ import { isAbsolute as posixIsAbsolute, join as posixJoin } from 'node:path/posix'
10
11
 
11
12
  /** Expand a leading ~ (or ~user) in a path, platform-style. */
12
13
  export function expandHome(path: string, home: string = homedir()): string {
14
+ const isPosix = home.startsWith('/')
15
+ const j = isPosix ? posixJoin : join
13
16
  if (path === '~') return home
14
- if (path.startsWith('~/') || path.startsWith('~\\')) return join(home, path.slice(2))
17
+ if (path.startsWith('~/') || path.startsWith('~\\')) return j(home, path.slice(2))
15
18
  return path
16
19
  }
17
20
 
@@ -22,12 +25,15 @@ export function expandHome(path: string, home: string = homedir()): string {
22
25
  * @returns the absolute DSH home path.
23
26
  */
24
27
  export function resolveDshHome(env: NodeJS.ProcessEnv = process.env, home: string = homedir()): string {
28
+ const isPosix = home.startsWith('/')
29
+ const j = isPosix ? posixJoin : join
30
+ const isAbs = isPosix ? posixIsAbsolute : isAbsolute
25
31
  const raw = env.DSH_HOME
26
32
  if (raw !== undefined && raw.trim() !== '') {
27
33
  const expanded = expandHome(raw.trim(), home)
28
- return isAbsolute(expanded) ? expanded : join(process.cwd(), expanded)
34
+ return isAbs(expanded) ? expanded : j(process.cwd(), expanded)
29
35
  }
30
- return join(home, '.dsh')
36
+ return j(home, '.dsh')
31
37
  }
32
38
 
33
39
  /** Resolve the DSH home directory from the live environment. */
package/src/index.ts CHANGED
@@ -16,6 +16,8 @@ import type { Context } from '@deepseek-ai/cordis'
16
16
  import { installSettingsSection, settingsNamespace } from '@deepseek-ai/dsh-settings'
17
17
  import z from 'schemastery'
18
18
  import type {} from '@deepseek-ai/dsh-host-webserver'
19
+ import type {} from '@deepseek-ai/dsh-agent'
20
+ import type {} from '@deepseek-ai/dsh-commands'
19
21
  import { DEFAULT_IDLE_EXPIRE_MS, PairingService, type PairingConfig } from './pairing.ts'
20
22
  import { dshHome } from './dsh-home.ts'
21
23
  import { isPairedDeviceRequest, makeGateListener } from './gate.ts'
@@ -66,7 +68,7 @@ declare module '@deepseek-ai/cordis' {
66
68
  export const name = 'remote-web-ui'
67
69
 
68
70
  /** Services required before the pairing surfaces can mount. */
69
- export const inject = ['webServer', 'apiProxy']
71
+ export const inject = ['webServer', 'apiProxy', 'commands', 'agents']
70
72
 
71
73
  /**
72
74
  * Settings namespace of the remote-control capability — the section the web
@@ -374,7 +376,23 @@ function applyImpl(ctx: Context, config?: Config): void {
374
376
  ...makeRoutes({ service, lanAddresses, requirePairingForLan: () => resolve().requirePairingForLan }),
375
377
  ...makeMobileRoutes(),
376
378
  ...(apiProxy !== undefined
377
- ? makeMobileApiRoutes({ service, apiProxy, pendingTracker: new PendingTracker(), mobileEnterToSend: () => resolve().mobileEnterToSend })
379
+ ? makeMobileApiRoutes({
380
+ service,
381
+ apiProxy,
382
+ pendingTracker: new PendingTracker(),
383
+ mobileEnterToSend: () => resolve().mobileEnterToSend,
384
+ commandDispatcher: ctx.commands !== undefined && ctx.agents !== undefined
385
+ ? {
386
+ async execute(sessionId, line, signal) {
387
+ const agent = ctx.agents.get(sessionId as never)
388
+ if (agent === undefined) return { error: 'session-not-found' as const }
389
+ const execution = await ctx.commands.execute(agent, line, [], signal)
390
+ if (execution === undefined) return { error: 'unknown-command' as const }
391
+ return { ok: true as const, result: execution.result }
392
+ },
393
+ }
394
+ : undefined,
395
+ })
378
396
  : []),
379
397
  ...(apiProxy !== undefined ? makePairedModelCatalogRoutes({ service, apiProxy, lanAddresses }) : []),
380
398
  // The remote desktop channel: policy-gated `/remote` prefix that
package/src/mobile/api.ts CHANGED
@@ -105,10 +105,9 @@ export async function prompt(sessionId: string, text: string): Promise<void> {
105
105
 
106
106
  /** Send one slash command line (e.g. `/permission workspace-write`). */
107
107
  export async function sendCommand(sessionId: string, line: string): Promise<unknown> {
108
- return await callUnary<unknown>('session.prompt', {
108
+ return await callUnary<unknown>('mobile.command', {
109
109
  sessionId,
110
- mode: 'queue',
111
- content: [{ type: 'text', text: line }],
110
+ line,
112
111
  })
113
112
  }
114
113
 
package/src/mobile-api.ts CHANGED
@@ -64,6 +64,8 @@ const MOBILE_ALLOWLIST = new Set([
64
64
  const MOBILE_PREFERENCES_METHOD = 'mobile.preferences'
65
65
  const MOBILE_PENDING_METHOD = 'mobile.pending'
66
66
  const MOBILE_RESPOND_METHOD = 'mobile.respond'
67
+ /** Execute a slash command through the host command registry (#1125). */
68
+ const MOBILE_COMMAND_METHOD = 'mobile.command'
67
69
 
68
70
  /** One session.list page (thin phones load incrementally). */
69
71
  const SESSION_PAGE_SIZE = 20
@@ -102,6 +104,19 @@ export interface MobileApiDeps {
102
104
  mobileEnterToSend: () => boolean
103
105
  /** SSE keep-alive ping cadence for the mux stream (default 15000 ms; test seam). */
104
106
  eventsHeartbeatMs?: number
107
+ /**
108
+ * Optional command dispatcher for executing slash commands on the mobile
109
+ * surface. When absent, `mobile.command` returns an error; the mobile UI
110
+ * can still fall back to `session.prompt` (which sends the line to the
111
+ * model, not the command registry).
112
+ */
113
+ commandDispatcher?: {
114
+ execute(
115
+ sessionId: string,
116
+ line: string,
117
+ signal: AbortSignal,
118
+ ): Promise<{ ok: true; result: unknown } | { error: string }>
119
+ }
105
120
  }
106
121
 
107
122
  /** Mobile API route paths. */
@@ -161,6 +176,7 @@ export function makeMobileApiRoutes(deps: MobileApiDeps): WebRoute[] {
161
176
  const local = method === MOBILE_PREFERENCES_METHOD
162
177
  || method === MOBILE_PENDING_METHOD
163
178
  || method === MOBILE_RESPOND_METHOD
179
+ || method === MOBILE_COMMAND_METHOD
164
180
  if (!MOBILE_ALLOWLIST.has(method) && !local) {
165
181
  writeJson(res, 403, { ok: false, error: { code: 'forbidden', message: `method ${method} is not exposed to the mobile surface` } })
166
182
  return
@@ -213,6 +229,49 @@ export function makeMobileApiRoutes(deps: MobileApiDeps): WebRoute[] {
213
229
  result: { ok: false, error: { code: 'internal', message } },
214
230
  })
215
231
  }
232
+ } else if (method === MOBILE_COMMAND_METHOD) {
233
+ const payload = parsed.payload as { sessionId?: string; line?: string } | undefined
234
+ const sessionId = payload?.sessionId
235
+ const line = payload?.line
236
+ if (typeof sessionId !== 'string' || typeof line !== 'string') {
237
+ writeJson(res, 200, {
238
+ type: 'server-response',
239
+ rpcId,
240
+ result: { ok: false, error: { code: 'bad-request', message: 'missing sessionId or line' } },
241
+ })
242
+ } else if (deps.commandDispatcher === undefined) {
243
+ writeJson(res, 200, {
244
+ type: 'server-response',
245
+ rpcId,
246
+ result: { ok: false, error: { code: 'unavailable', message: 'command dispatcher is not available' } },
247
+ })
248
+ } else {
249
+ try {
250
+ const abort = new AbortController()
251
+ res.on('close', () => { if (!res.writableEnded) abort.abort() })
252
+ const outcome = await deps.commandDispatcher.execute(sessionId, line, abort.signal)
253
+ if ('error' in outcome) {
254
+ writeJson(res, 200, {
255
+ type: 'server-response',
256
+ rpcId,
257
+ result: { ok: false, error: { code: outcome.error, message: outcome.error } },
258
+ })
259
+ } else {
260
+ writeJson(res, 200, {
261
+ type: 'server-response',
262
+ rpcId,
263
+ result: { ok: true, value: { matched: true, result: outcome.result } },
264
+ })
265
+ }
266
+ } catch (error) {
267
+ const message = error instanceof Error ? error.message : String(error)
268
+ writeJson(res, 200, {
269
+ type: 'server-response',
270
+ rpcId,
271
+ result: { ok: false, error: { code: 'internal', message } },
272
+ })
273
+ }
274
+ }
216
275
  }
217
276
  return
218
277
  }
@@ -8,6 +8,7 @@
8
8
  import { readFile } from 'node:fs/promises'
9
9
  import { existsSync } from 'node:fs'
10
10
  import { fileURLToPath } from 'node:url'
11
+ import { gzipSync } from 'node:zlib'
11
12
  import type { IncomingMessage, ServerResponse } from 'node:http'
12
13
  import type { WebRoute } from '@deepseek-ai/dsh-host-webserver'
13
14
 
@@ -147,7 +148,8 @@ export function makeMobileRoutes(): WebRoute[] {
147
148
  // The bundle is immutable for the process lifetime (a rebuild requires a
148
149
  // host restart), so the body is read from disk once, not per phone.
149
150
  let bundleBody: string | undefined
150
- const handleBundle = async (_req: IncomingMessage, res: ServerResponse): Promise<void> => {
151
+ let bundleGzip: Buffer | undefined
152
+ const handleBundle = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {
151
153
  if (bundleBody === undefined) {
152
154
  const path = mobileBundlePath()
153
155
  if (!existsSync(path)) {
@@ -156,12 +158,32 @@ export function makeMobileRoutes(): WebRoute[] {
156
158
  }
157
159
  try {
158
160
  bundleBody = await readFile(path, 'utf8')
161
+ bundleGzip = gzipSync(Buffer.from(bundleBody, 'utf8'))
159
162
  } catch {
160
163
  writeStatic(res, 500, 'text/plain', 'failed to read the mobile bundle')
161
164
  return
162
165
  }
163
166
  }
164
- writeStatic(res, 200, 'text/javascript', bundleBody)
167
+ const acceptEncoding = req.headers['accept-encoding'] ?? ''
168
+ const acceptsGzip = typeof acceptEncoding === 'string' && acceptEncoding.includes('gzip') && bundleGzip !== undefined
169
+ if (acceptsGzip) {
170
+ res.writeHead(200, {
171
+ 'content-type': 'text/javascript; charset=utf-8',
172
+ 'content-encoding': 'gzip',
173
+ 'cache-control': 'no-cache',
174
+ vary: 'Accept-Encoding',
175
+ 'referrer-policy': 'no-referrer',
176
+ })
177
+ res.end(bundleGzip)
178
+ } else {
179
+ res.writeHead(200, {
180
+ 'content-type': 'text/javascript; charset=utf-8',
181
+ 'cache-control': 'no-cache',
182
+ vary: 'Accept-Encoding',
183
+ 'referrer-policy': 'no-referrer',
184
+ })
185
+ res.end(bundleBody)
186
+ }
165
187
  }
166
188
 
167
189
  let workerBody: string | undefined