@deepseek-ai/dsh-client-ui-commands 0.1.3-alpha.2 → 0.1.5-alpha.2

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/README.i18n.yaml CHANGED
@@ -2,5 +2,5 @@
2
2
  # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
3
  # after editing either side, bring the other along and re-record with:
4
4
  # pnpm run verify-translation-pairing --write packages/client/ui-commands/README.md
5
- README.md: e1264d10bd8ff807d8f348339e0c055607579407
6
- README.zh.md: c5323149aea1e38ff09262db9bd2aacad7b284ea
5
+ README.md: c5f3c55c08a6df8e6c3f122d0c5e341721c2b41a
6
+ README.zh.md: f728c03e067f74ba5142464adc17531225034aad
package/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  ---
2
- description: "Client command API for the Web GUI: the / command source, three dispatch kinds, the per-session command directory, and popupSelect registration for business packages; for users and maintainers of slash commands."
2
+ description: "Client command API for the Web GUI: the / command source, three dispatch kinds, the per-session command directory, and popupSelect and action registration for business packages; for users and maintainers of slash commands."
3
3
  kind: "package-reference"
4
4
  ---
5
5
 
@@ -9,7 +9,7 @@ English | [中文](README.zh.md)
9
9
 
10
10
  ## Summary
11
11
 
12
- Typing a `/` command in the composer opens the matching surface — a registered popup, a host command's input, or a direct execution — and a command line is never silently downgraded to a plain prompt. Business packages contribute command surfaces through `ctx.commandUi`, registering a popupSelect spec (`/model`, `/permission`) or decorating an existing host command with a picker while the host keeps its catalog row and argument claim. Space and Enter resolve the line against the session's directory: a host descriptor with `input` is `leadingInput`, a registered `CommandUiSpec` is `popupSelect`, and everything else is `execute`.
12
+ Typing a `/` command in the composer opens the matching surface — a registered popup, a host command's input, or a direct execution — and a command line is never silently downgraded to a plain prompt. Business packages contribute command surfaces through `ctx.commandUi`: a popupSelect spec (`/model`, `/permission`) or an action (`/feedback`), registered as a command or decorating an existing host command while the host keeps its catalog row and argument claim. Space and Enter resolve the line against the session's directory: a host descriptor with `input` is `leadingInput`, a registered `CommandUiSpec` is its kind, and everything else is `execute`.
13
13
 
14
14
  ## Table of Contents
15
15
 
package/README.zh.md CHANGED
@@ -1,5 +1,5 @@
1
1
  ---
2
- description: "Web GUI 的客户端命令 API:/ 命令 source、三类派发、会话级命令目录,以及面向业务包的 popupSelect 注册;供斜杠命令的用户与维护者阅读。"
2
+ description: "Web GUI 的客户端命令 API:/ 命令 source、三类派发、会话级命令目录,以及面向业务包的 popupSelect 与 action 注册;供斜杠命令的用户与维护者阅读。"
3
3
  kind: "package-reference"
4
4
  ---
5
5
 
@@ -9,7 +9,7 @@ kind: "package-reference"
9
9
 
10
10
  ## 概述
11
11
 
12
- 在 composer 中键入 `/` 命令会打开匹配的表面——已注册的弹窗、宿主命令的输入或直接执行——命令行绝不会被静默降级为普通提示词。业务包经 `ctx.commandUi` 贡献命令表面:注册 popupSelect 贡献项(`/model`、`/permission`),或用选择器装饰既有宿主命令,宿主保留其目录行与参数声明。空格与回车对照会话目录解析命令行:带 `input` 的宿主描述符是 `leadingInput`,注册了 `CommandUiSpec` 的是 `popupSelect`,其余全部是 `execute`。
12
+ 在 composer 中键入 `/` 命令会打开匹配的表面——已注册的弹窗、宿主命令的输入或直接执行——命令行绝不会被静默降级为普通提示词。业务包经 `ctx.commandUi` 贡献命令表面:popupSelect 贡献项(`/model`、`/permission`)或 action(`/feedback`),既可注册为命令,也可装饰既有宿主命令,宿主保留其目录行与参数声明。空格与回车对照会话目录解析命令行:带 `input` 的宿主描述符是 `leadingInput`,注册了 `CommandUiSpec` 的按其种类派发,其余全部是 `execute`。
13
13
 
14
14
  ## 目录
15
15
 
package/lib/client.js CHANGED
@@ -159,6 +159,43 @@ window.__ModuleLoader__.load({
159
159
  return signal.reason instanceof Error ? signal.reason : /* @__PURE__ */ new Error("command directory wait aborted");
160
160
  }
161
161
  //#endregion
162
+ //#region lib/types/client/locales.js
163
+ /** `command` namespace dictionaries (the popupSelect shell's copy). */
164
+ /** Simplified Chinese dictionary (the key-set source of truth). */
165
+ const zh = {
166
+ "description.compact": "压缩以上对话内容",
167
+ "description.export": "将当前会话内容导出为 ZIP",
168
+ "description.feedback": "发送关于当前会话的反馈",
169
+ "description.goal": "设置或查看长期任务目标",
170
+ "description.permission": "切换权限预设(沙箱模式与审批策略)",
171
+ "description.plan": "进入或退出计划模式",
172
+ "search.placeholder": "搜索…",
173
+ "search.aria": "筛选选项",
174
+ "status.loading": "正在加载选项…",
175
+ "status.applying": "正在应用…",
176
+ "status.empty": "无选项",
177
+ "overlay.aria": "/{command} 选项",
178
+ "listbox.aria": "/{command} 匹配项",
179
+ "notice.attachmentsUnsupported": "/{command} 不接受附件,请先移除附件"
180
+ };
181
+ /** English dictionary, checked complete against the zh key set. */
182
+ const en = {
183
+ "description.compact": "Compact older conversation history",
184
+ "description.export": "Download this Session log as a ZIP archive",
185
+ "description.feedback": "record feedback about this session",
186
+ "description.goal": "set or view the goal for a long-running task",
187
+ "description.permission": "Switch the permission preset (sandbox mode + approval policy)",
188
+ "description.plan": "Enter or leave plan mode",
189
+ "search.placeholder": "Search…",
190
+ "search.aria": "Filter options",
191
+ "status.loading": "Loading options…",
192
+ "status.applying": "Applying…",
193
+ "status.empty": "No options",
194
+ "overlay.aria": "/{command} options",
195
+ "listbox.aria": "/{command} matches",
196
+ "notice.attachmentsUnsupported": "/{command} does not accept attachments; remove them first"
197
+ };
198
+ //#endregion
162
199
  //#region lib/types/client/popup.js
163
200
  /**
164
201
  * Headless popupSelect shell state: one controller per client
@@ -444,6 +481,15 @@ window.__ModuleLoader__.load({
444
481
  const separator = trimmed.search(/\s/u);
445
482
  return (separator === -1 ? trimmed : trimmed.slice(0, separator)).slice(1);
446
483
  }
484
+ /** Locale keys for the canonical first-party Host command descriptions. */
485
+ const HOST_DESCRIPTION_KEYS = new Map([
486
+ ["compact", "description.compact"],
487
+ ["export", "description.export"],
488
+ ["feedback", "description.feedback"],
489
+ ["goal", "description.goal"],
490
+ ["permission", "description.permission"],
491
+ ["plan", "description.plan"]
492
+ ]);
447
493
  /** Command surface: session-keyed directory + '/' source + contribution registry + per-session popups. */
448
494
  var CommandUiRuntime = class extends _deepseek_ai_cordis.Service {
449
495
  static inject = [
@@ -593,7 +639,7 @@ window.__ModuleLoader__.load({
593
639
  seen.add(c.name);
594
640
  rows.push({
595
641
  name: c.name,
596
- description: c.description,
642
+ description: this.hostDescription(c),
597
643
  ...c.input !== void 0 ? { hint: c.input.hint } : {}
598
644
  });
599
645
  }
@@ -602,17 +648,22 @@ window.__ModuleLoader__.load({
602
648
  if (seen.has(contribution.name)) throw new Error(`ui-commands: contribution /${contribution.name} collides with a host command`);
603
649
  rows.push({
604
650
  name: contribution.name,
605
- description: contribution.description
651
+ description: contribution.description()
606
652
  });
607
653
  }
608
654
  return (0, _deepseek_ai_dsh_client_ui_primitives.rankByName)(rows.filter((c) => req.position === "leading" || c.hint === void 0), req.query);
609
655
  }
610
- /** Decision table, menu column: contribution/decorated-host popup; host input claim; host bare → detached execute. */
656
+ /** Translate exact built-in Host copy while preserving scoped or third-party descriptors verbatim. */
657
+ hostDescription(command) {
658
+ const key = HOST_DESCRIPTION_KEYS.get(command.name);
659
+ return key !== void 0 && command.description === en[key] ? this.t(key) : command.description;
660
+ }
661
+ /** Decision table, menu column: contribution/decorated-host → popup or action; host input → claim; host bare → detached execute. */
611
662
  dispatch(pick) {
612
663
  const name = pick.candidate.name;
613
664
  const contribution = this.live.contributions.get(name);
614
665
  if (contribution !== void 0 && contribution.available(pick.session)) {
615
- this.openPopup(name, contribution.ui, pick.session, {
666
+ this.invoke(name, contribution.ui, pick.session, {
616
667
  via: "menu",
617
668
  span: pick.span
618
669
  });
@@ -622,7 +673,7 @@ window.__ModuleLoader__.load({
622
673
  if (desc === void 0) return void 0;
623
674
  const decoration = this.live.decorations.get(name);
624
675
  if (decoration !== void 0 && decoration.available(pick.session)) {
625
- this.openPopup(name, decoration.ui, pick.session, {
676
+ this.invoke(name, decoration.ui, pick.session, {
626
677
  via: "menu",
627
678
  span: pick.span
628
679
  });
@@ -652,10 +703,11 @@ window.__ModuleLoader__.load({
652
703
  * args-tolerant.
653
704
  *
654
705
  * Envelope policy: an enter submission carrying attachments resolves only
655
- * through a command declaring attachment acceptance. Every other command route —
656
- * popup, non-accepting claim, bare detached execute — throws the refusal
657
- * so the machine surfaces one composer notice and the draft and attachments
658
- * stay in place; nothing executes and nothing is dropped.
706
+ * through a command declaring attachment acceptance. Every other submitting
707
+ * route — popup, non-accepting claim, bare detached execute — throws the
708
+ * refusal so the machine surfaces one composer notice and the draft and
709
+ * attachments stay in place; nothing executes and nothing is dropped. An
710
+ * action submits nothing and runs regardless.
659
711
  */
660
712
  async matchEnter(session, line, signal, envelope) {
661
713
  const trimmed = line.trim();
@@ -671,8 +723,8 @@ window.__ModuleLoader__.load({
671
723
  const contribution = this.live.contributions.get(name);
672
724
  if (contribution !== void 0 && contribution.available(session)) {
673
725
  if (!bare) return void 0;
674
- if (envelope.attachments > 0) refuseAttachments();
675
- this.openPopup(name, contribution.ui, session, {
726
+ if (envelope.attachments > 0 && contribution.ui.kind !== "action") refuseAttachments();
727
+ this.invoke(name, contribution.ui, session, {
676
728
  via: "enter",
677
729
  token
678
730
  });
@@ -684,8 +736,8 @@ window.__ModuleLoader__.load({
684
736
  if (bare) {
685
737
  const decoration = this.live.decorations.get(name);
686
738
  if (decoration !== void 0 && decoration.available(session)) {
687
- if (envelope.attachments > 0) refuseAttachments();
688
- this.openPopup(name, decoration.ui, session, {
739
+ if (envelope.attachments > 0 && decoration.ui.kind !== "action") refuseAttachments();
740
+ this.invoke(name, decoration.ui, session, {
689
741
  via: "enter",
690
742
  token
691
743
  });
@@ -705,8 +757,16 @@ window.__ModuleLoader__.load({
705
757
  this.runDetached(desc, session, trimmed);
706
758
  return "handled";
707
759
  }
708
- /** Open the session's popup for one contribution or decoration (menu pick / bare enter). */
709
- openPopup(name, ui, session, segment) {
760
+ /**
761
+ * Invoke one contribution or decoration (menu pick / bare enter): open the
762
+ * session's popup, or consume the token and run the action.
763
+ */
764
+ invoke(name, ui, session, segment) {
765
+ if (ui.kind === "action") {
766
+ this.consumeVia(session.sessionId, segment);
767
+ ui.run(session);
768
+ return;
769
+ }
710
770
  const actx = this.scopeFor(session.sessionId);
711
771
  if (actx === void 0) return;
712
772
  this.popupFor(actx).open(name, ui, session, segment);
@@ -1023,31 +1083,6 @@ window.__ModuleLoader__.load({
1023
1083
  })] });
1024
1084
  }
1025
1085
  //#endregion
1026
- //#region lib/types/client/locales.js
1027
- /** `command` namespace dictionaries (the popupSelect shell's copy). */
1028
- /** Simplified Chinese dictionary (the key-set source of truth). */
1029
- const zh = {
1030
- "search.placeholder": "搜索…",
1031
- "search.aria": "筛选选项",
1032
- "status.loading": "正在加载选项…",
1033
- "status.applying": "正在应用…",
1034
- "status.empty": "无选项",
1035
- "overlay.aria": "/{command} 选项",
1036
- "listbox.aria": "/{command} 匹配项",
1037
- "notice.attachmentsUnsupported": "/{command} 不接受附件,请先移除附件"
1038
- };
1039
- /** English dictionary, checked complete against the zh key set. */
1040
- const en = {
1041
- "search.placeholder": "Search…",
1042
- "search.aria": "Filter options",
1043
- "status.loading": "Loading options…",
1044
- "status.applying": "Applying…",
1045
- "status.empty": "No options",
1046
- "overlay.aria": "/{command} options",
1047
- "listbox.aria": "/{command} matches",
1048
- "notice.attachmentsUnsupported": "/{command} does not accept attachments; remove them first"
1049
- };
1050
- //#endregion
1051
1086
  //#region lib/types/client/index.js
1052
1087
  /** Dictionary namespace owned by this plugin. */
1053
1088
  const NS = "command";
@@ -28,11 +28,27 @@ export interface SelectOption {
28
28
  * The shell component is owned by ui-commands; business never sees it. Both
29
29
  * callbacks receive the ClientSessionContext captured at popup open.
30
30
  */
31
- export type CommandUiSpec = {
31
+ export interface PopupSelectSpec {
32
32
  readonly kind: 'popupSelect';
33
33
  options(session: ClientSessionContext, signal: AbortSignal): Promise<readonly SelectOption[]>;
34
34
  onSelect(option: SelectOption, session: ClientSessionContext): void | Promise<void>;
35
- };
35
+ }
36
+ /**
37
+ * Business registration for the action command kind: a bare invocation
38
+ * consumes the trigger token and runs one client-side callback (the Feedback
39
+ * row opens the feedback dialog). It submits nothing, so an
40
+ * attachment-carrying draft never refuses it.
41
+ */
42
+ export interface ActionSpec {
43
+ readonly kind: 'action';
44
+ /**
45
+ * Run the action for one session.
46
+ * @param session - the ClientSessionContext captured at invocation.
47
+ */
48
+ run(session: ClientSessionContext): void;
49
+ }
50
+ /** The UI behavior of a contribution or decoration. */
51
+ export type CommandUiSpec = PopupSelectSpec | ActionSpec;
36
52
  /**
37
53
  * One client-owned command contribution: a slash-menu entry whose behavior
38
54
  * lives entirely on the client (no host descriptor). Merged with the host
@@ -42,11 +58,11 @@ export type CommandUiSpec = {
42
58
  export interface CommandContribution {
43
59
  /** Command name without the leading slash (unique across contributions). */
44
60
  readonly name: string;
45
- /** Menu row description. */
46
- readonly description: string;
61
+ /** Resolve the localized menu row description when candidates are requested. */
62
+ readonly description: () => string;
47
63
  /** Capability filter, called with a fresh projection per candidate pass. */
48
64
  available(session: ClientSessionContext): boolean;
49
- /** The command's UI behavior (this phase: popupSelect only). */
65
+ /** The command's UI behavior. */
50
66
  readonly ui: CommandUiSpec;
51
67
  }
52
68
  /**
@@ -63,7 +79,7 @@ export interface CommandDecoration {
63
79
  readonly name: string;
64
80
  /** Capability filter, called with a fresh projection per bare invocation. */
65
81
  available(session: ClientSessionContext): boolean;
66
- /** The bare-invocation UI (this phase: popupSelect only). */
82
+ /** The bare-invocation UI. */
67
83
  readonly ui: CommandUiSpec;
68
84
  }
69
85
  /** The `ctx.commandUi` service face visible to business packages. */
@@ -14,7 +14,7 @@ export type { CommandDescriptor, DirectoryStatus } from './directory.ts';
14
14
  export { filterOptions, PopupSelectController } from './popup.ts';
15
15
  export type { PopupSelectDeps, PopupSpec, PopupState, TokenSegment } from './popup.ts';
16
16
  export type { PopupSelectInjected, PopupSelectViewProps } from './PopupSelectView.tsx';
17
- export type { CommandContribution, CommandDecoration, CommandUiContract, CommandUiSpec, SelectConfirmation, SelectOption, } from './contract.ts';
17
+ export type { ActionSpec, CommandContribution, CommandDecoration, CommandUiContract, CommandUiSpec, PopupSelectSpec, SelectConfirmation, SelectOption, } from './contract.ts';
18
18
  export type { CommandKey } from './locales.ts';
19
19
  declare module '@deepseek-ai/cordis' {
20
20
  interface Context {
@@ -1,6 +1,12 @@
1
1
  /** `command` namespace dictionaries (the popupSelect shell's copy). */
2
2
  /** Simplified Chinese dictionary (the key-set source of truth). */
3
3
  export declare const zh: {
4
+ 'description.compact': string;
5
+ 'description.export': string;
6
+ 'description.feedback': string;
7
+ 'description.goal': string;
8
+ 'description.permission': string;
9
+ 'description.plan': string;
4
10
  'search.placeholder': string;
5
11
  'search.aria': string;
6
12
  'status.loading': string;
@@ -14,6 +20,12 @@ export declare const zh: {
14
20
  export type CommandKey = keyof typeof zh;
15
21
  /** English dictionary, checked complete against the zh key set. */
16
22
  export declare const en: {
23
+ 'description.compact': string;
24
+ 'description.export': string;
25
+ 'description.feedback': string;
26
+ 'description.goal': string;
27
+ 'description.permission': string;
28
+ 'description.plan': string;
17
29
  'search.placeholder': string;
18
30
  'search.aria': string;
19
31
  'status.loading': string;
@@ -76,7 +76,9 @@ export declare class CommandUiRuntime extends Service implements CommandUiContra
76
76
  bindComposerFocus(id: SessionId, focus: () => void): () => void;
77
77
  /** Menu candidates: host catalog + contribution availability, then position filtering and the shared name ranking. */
78
78
  private candidates;
79
- /** Decision table, menu column: contribution/decorated-host popup; host input claim; host bare → detached execute. */
79
+ /** Translate exact built-in Host copy while preserving scoped or third-party descriptors verbatim. */
80
+ private hostDescription;
81
+ /** Decision table, menu column: contribution/decorated-host → popup or action; host input → claim; host bare → detached execute. */
80
82
  private dispatch;
81
83
  /** Decision table, space column: hot-key sync check; only host leadingInput claims. */
82
84
  private matchSpace;
@@ -87,14 +89,18 @@ export declare class CommandUiRuntime extends Service implements CommandUiContra
87
89
  * args-tolerant.
88
90
  *
89
91
  * Envelope policy: an enter submission carrying attachments resolves only
90
- * through a command declaring attachment acceptance. Every other command route —
91
- * popup, non-accepting claim, bare detached execute — throws the refusal
92
- * so the machine surfaces one composer notice and the draft and attachments
93
- * stay in place; nothing executes and nothing is dropped.
92
+ * through a command declaring attachment acceptance. Every other submitting
93
+ * route — popup, non-accepting claim, bare detached execute — throws the
94
+ * refusal so the machine surfaces one composer notice and the draft and
95
+ * attachments stay in place; nothing executes and nothing is dropped. An
96
+ * action submits nothing and runs regardless.
94
97
  */
95
98
  private matchEnter;
96
- /** Open the session's popup for one contribution or decoration (menu pick / bare enter). */
97
- private openPopup;
99
+ /**
100
+ * Invoke one contribution or decoration (menu pick / bare enter): open the
101
+ * session's popup, or consume the token and run the action.
102
+ */
103
+ private invoke;
98
104
  /** Build the leadingInput claim: token `/name ` + the command.execute submit transaction. */
99
105
  private leadingClaim;
100
106
  /**
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-client-ui-commands",
3
3
  "description": "Client command surface: global directory cache, '/' source, three command UI kinds, popupSelect registry",
4
- "version": "0.1.3-alpha.2",
4
+ "version": "0.1.5-alpha.2",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -37,30 +37,28 @@
37
37
  }
38
38
  },
39
39
  "license": "MIT",
40
- "dependencies": {
41
- "clsx": "^2.0.0"
42
- },
43
40
  "peerDependencies": {
44
41
  "@deepseek-ai/cordis": "^4.0.2"
45
42
  },
46
43
  "devDependencies": {
47
44
  "@types/react": "~18.3.1",
48
45
  "react": "^18.2.0",
49
- "@deepseek-ai/dsh-api-remotes": "^0.1.3-alpha.2",
50
- "@deepseek-ai/dsh-client-connection": "^0.1.3-alpha.2",
51
- "@deepseek-ai/dsh-client-locale": "^0.1.3-alpha.2",
52
- "@deepseek-ai/dsh-client-test-runtime": "^0.1.3-alpha.2",
53
- "@deepseek-ai/dsh-client-ui-conversation": "^0.1.3-alpha.2",
54
- "@deepseek-ai/dsh-client-ui-primitives": "^0.1.3-alpha.2",
55
- "@deepseek-ai/dsh-client-ui-input-trigger": "^0.1.3-alpha.2",
56
- "@deepseek-ai/dsh-client-ui-slots": "^0.1.3-alpha.2",
46
+ "clsx": "^2.0.0",
47
+ "@deepseek-ai/dsh-client-connection": "^0.1.5-alpha.2",
48
+ "@deepseek-ai/dsh-client-locale": "^0.1.5-alpha.2",
49
+ "@deepseek-ai/dsh-client-test-runtime": "^0.1.5-alpha.2",
50
+ "@deepseek-ai/dsh-client-ui-primitives": "^0.1.5-alpha.2",
51
+ "@deepseek-ai/dsh-commands": "^0.1.5-alpha.2",
52
+ "@deepseek-ai/dsh-client-ui-conversation": "^0.1.5-alpha.2",
53
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.5-alpha.2",
57
54
  "@deepseek-ai/cordis": "^4.0.2",
58
- "@deepseek-ai/dsh-api-session-controller": "^0.1.3-alpha.2",
59
- "@deepseek-ai/dsh-client-store": "^0.1.3-alpha.2",
60
- "@deepseek-ai/dsh-session": "^0.1.3-alpha.2",
61
- "@deepseek-ai/dsh-client-ui-renderer": "^0.1.3-alpha.2",
62
- "@deepseek-ai/dsh-client-ui-session": "^0.1.3-alpha.2",
63
- "@deepseek-ai/dsh-commands": "^0.1.3-alpha.2"
55
+ "@deepseek-ai/dsh-client-ui-input-trigger": "^0.1.5-alpha.2",
56
+ "@deepseek-ai/dsh-api-session-controller": "^0.1.5-alpha.2",
57
+ "@deepseek-ai/dsh-client-store": "^0.1.5-alpha.2",
58
+ "@deepseek-ai/dsh-session": "^0.1.5-alpha.2",
59
+ "@deepseek-ai/dsh-client-ui-renderer": "^0.1.5-alpha.2",
60
+ "@deepseek-ai/dsh-client-ui-session": "^0.1.5-alpha.2",
61
+ "@deepseek-ai/dsh-api-remotes": "^0.1.5-alpha.2"
64
62
  },
65
63
  "files": [
66
64
  "lib/index.js",