@deepseek-ai/dsh-client-ui-commands 0.1.5-rc.2 → 0.1.6-alpha.1

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: c5f3c55c08a6df8e6c3f122d0c5e341721c2b41a
6
- README.zh.md: f728c03e067f74ba5142464adc17531225034aad
5
+ README.md: f802179e08009cd4db8a3b786e52e81b26bb21da
6
+ README.zh.md: 1dee58ab42d67a62099d57d4e631e440d3349f21
package/README.md CHANGED
@@ -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`: 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`.
12
+ Typing a `/` command opens a registered popup, a client action, a host command's input, or direct execution; a command line is never silently downgraded to a plain prompt. Business packages register popupSelect specs (`/model`, `/permission`) or actions through `ctx.commandUi`, or decorate existing host commands with either kind while preserving their catalog rows and argument claims. Space and Enter resolve the line against the session's directory: a host descriptor with `input` is `leadingInput`, a registered `CommandUiSpec` is `popupSelect` or `action`, and everything else is `execute`.
13
13
 
14
14
  ## Table of Contents
15
15
 
@@ -25,11 +25,15 @@ Typing a `/` command in the composer opens the matching surface — a registered
25
25
  <a id="use-this-package"></a>
26
26
  ## Use this package
27
27
 
28
- Mount this plugin alongside `ui-input-trigger` and `ui-conversation`; the `/` source then appears in the trigger menu, and business packages register their command surfaces through `ctx.commandUi`. Typing `/model` opens the registered popup; a host command with an argument claim opens its input or executes directly.
28
+ Mount this plugin alongside `ui-input-trigger` and `ui-conversation`; the `/` source then appears in the trigger menu, and business packages register their command surfaces through `ctx.commandUi`. Typing `/model` opens the registered popup; a host command with an argument claim opens its input or executes directly. The composer's `+` button and a typed `/` open the same menu: an Add section (File, Goal, Plan, Feedback) and a Commands section (Compact, Permission, Model, Export) in usage order, each row with a glyph, a localized title and description, and the command name as an alias where the localized title differs from it.
29
29
 
30
30
  ### Kinds and decorations
31
31
 
32
- A contribution is a client-owned command — a host-name collision fails loud. A decoration adds a bare-invocation popup to an EXISTING host command: the host command keeps its catalog row, its argument claim, and its lifecycle logging, and a decorated name with no host row in the session's directory never fires. Menu queries fuzzy-match ordered, case-insensitive subsequences of command names; prefixes rank first.
32
+ A contribution is a client-owned command; a host-name collision fails loudly. Its UI is a popupSelect spec or an action: a callback a bare invocation runs after the trigger token is consumed, without submitting a message. Business packages own their actions and availability; the composer registers File through this same API. A decoration adds a bare-invocation popup or action to an existing host command while preserving its catalog row, argument claim, and lifecycle logging; it never fires without a matching host row. Menu queries fuzzy-match ordered, case-insensitive subsequences of command names and titles, with prefixes first and no section headings.
33
+
34
+ ### Built-in row faces
35
+
36
+ First-party command definitions carry stable `definitionId` values. The client selects their localized titles, descriptions, icons, and input spellings by identity; changing a Host description cannot change that selection. Same-name overrides without the matching identity keep their own copy and receive no first-party aliases. Chinese and English spellings resolve through the same effective Session catalog in every locale, preserving the typed spelling in the draft and submitting the registered Host name. Contributions supply their own `label`, `description`, and `icon`, read on every candidate pass. Empty-query section order follows names, with unlisted rows closing Commands.
33
37
 
34
38
  ### Attachment-carrying submissions
35
39
 
@@ -43,7 +47,7 @@ When the composer submits with images or generic files, only a host command decl
43
47
  <details>
44
48
  <summary>Implementation internals — click to expand</summary>
45
49
 
46
- `src/client/contract.ts` is the fixed business contract: `CommandUiContract.register(name, spec)` and `decorate(name, spec)` are everything a business package consumes. `CommandDirectory` is the one wire-derived cache, keyed by session: ordinary sessions fetch through `command.list({sessionId})`, entries are soft-invalidated by the forwarded `commands/change` owner event and hard-invalidated by `connection/reset`, and epoch-guarded so a superseded pull can never overwrite a newer one. `matchSpace` answers synchronously from this cache only; `matchEnter` strong-waits it on the SubmitAttempt signal and rejects on warmup failure. After `command.execute` returns a matched result, the browser emits a local `command/executed` acknowledgment; other clients receive the durable command nodes through the Host event stream but never this acknowledgment. `PopupSelectController` is the headless shell state; `PopupSelectView` self-registers into `conversation.input.overlay` with per-session resolution.
50
+ `src/client/contract.ts` defines contribution and decoration registration, plus `dismiss(name)`, which closes that command's open popups and confirmations: dismissal aborts pending option loads, prevents their late results from reopening the popup, and preserves composer drafts. `CommandDirectory` owns the per-session wire cache and resolves typed commands through `resolution.ts`; that module owns first-party identity matching and localized input spellings. `matchSpace` reads the ready cache synchronously, while `matchEnter` waits for readiness and rejects on warmup failure or cancellation. Forwarded catalog and connection events invalidate the cache. After a matched Host execution, this browser emits `command/executed`; other clients observe only the durable command events. `PopupSelectController` owns popup state, and `PopupSelectView` occupies the input overlay. `presentation.ts` owns row labels, icons, and sections; its helpers and the resolution helpers stay internal to the plugin.
47
51
 
48
52
  </details>
49
53
 
@@ -63,7 +67,7 @@ Read these pages when the command surface is not enough. They move from the comm
63
67
  <a id="model-experience"></a>
64
68
  ## Model Experience
65
69
 
66
- Indirectly, through the host `command.execute` RPC the dispatch paths trigger: each command handler's host package owns any model-visible effect (the `/plan` handler flips plan mode, whose owning package injects its policy section), while the command line, the detached result, and every menu and notice rendering stay client-side and never enter the session log.
70
+ Indirectly, through the host `command.execute` RPC they trigger, each command handler's host package owns any model-visible effect (the `/plan` handler flips plan mode, whose owning package injects its policy section), while the command line, the detached result, and every menu and notice rendering stay client-side and never enter the session log.
67
71
 
68
72
  #### KV Cache effect
69
73
 
@@ -88,4 +92,4 @@ None.
88
92
 
89
93
  </details>
90
94
 
91
- **Runtime invariant:** No companion is published. A browser-side source over the wire command directory — it emits no cordis events and owns no cross-plugin mutable state; dispatch and cache behavior are asserted by this package's specs.
95
+ **Runtime invariant:** No companion is published. This browser-side source uses the wire command directory; it emits no Cordis events and owns no cross-plugin mutable state. Its dispatch and cache behavior are asserted by this package's specs.
package/README.zh.md CHANGED
@@ -9,7 +9,7 @@ kind: "package-reference"
9
9
 
10
10
  ## 概述
11
11
 
12
- 在 composer 中键入 `/` 命令会打开匹配的表面——已注册的弹窗、宿主命令的输入或直接执行——命令行绝不会被静默降级为普通提示词。业务包经 `ctx.commandUi` 贡献命令表面:popupSelect 贡献项(`/model`、`/permission`)或 action(`/feedback`),既可注册为命令,也可装饰既有宿主命令,宿主保留其目录行与参数声明。空格与回车对照会话目录解析命令行:带 `input` 的宿主描述符是 `leadingInput`,注册了 `CommandUiSpec` 的按其种类派发,其余全部是 `execute`。
12
+ 键入 `/` 命令会打开已注册的弹窗、运行客户端动作、进入宿主命令的输入或直接执行,命令行不会被静默降级为普通提示词。业务包通过 `ctx.commandUi` 注册 popupSelect(`/model`、`/permission`)或 action,也可用这两种方式装饰既有宿主命令,同时保留其目录行与参数声明。空格与回车根据会话目录解析命令行:带 `input` 的宿主描述符是 `leadingInput`,注册了 `CommandUiSpec` 的是 `popupSelect` 或 `action`,其余是 `execute`。
13
13
 
14
14
  ## 目录
15
15
 
@@ -25,15 +25,19 @@ kind: "package-reference"
25
25
  <a id="use-this-package"></a>
26
26
  ## 使用本包
27
27
 
28
- 与 `ui-input-trigger` 及 `ui-conversation` 一起挂载本插件;`/` source 随即出现在触发菜单中,业务包经 `ctx.commandUi` 注册自己的命令表面。键入 `/model` 打开已注册的弹窗;带参数声明的宿主命令打开其输入或直接执行。
28
+ 与 `ui-input-trigger` 及 `ui-conversation` 一起挂载本插件;`/` source 随即出现在触发菜单中,业务包经 `ctx.commandUi` 注册自己的命令表面。键入 `/model` 打开已注册的弹窗;带参数声明的宿主命令打开其输入或直接执行。composer 的 `+` 按钮与键入的 `/` 打开同一个菜单:「添加」小节(文件、目标、计划、反馈)与「指令」小节(压缩、权限、模型、下载日志)按使用频次排列,每行带图标、本地化的标题与说明,本地化标题与命令名不同时还显示命令名作为别名。
29
29
 
30
30
  ### 种类与装饰
31
31
 
32
- 贡献项是客户端自有命令——与宿主命令同名会明确报错。装饰为**已存在的**宿主命令添加裸调用弹窗:宿主命令保留其目录行、参数声明与生命周期记账,被装饰的名字在会话目录中无宿主行时永不触发。菜单查询按顺序且不区分大小写地模糊匹配命令名的子序列;前缀排名最高。
32
+ 贡献项是客户端自有命令,与宿主命令同名会明确报错。它的 UI 是 popupSelect 规格或动作:裸调用消费触发 token 后运行回调,不提交消息。业务包负责自己的动作及可用性,输入框通过同一 API 注册「文件」。装饰为已有宿主命令添加裸调用弹窗或动作,并保留其目录行、参数认领与生命周期记录;没有匹配的宿主行时不触发。菜单查询按顺序、不区分大小写地模糊匹配命令名与标题的子序列,前缀优先,不显示小节标题。
33
+
34
+ ### 内置行的展示面
35
+
36
+ 内置命令定义携带稳定的 `definitionId`。客户端按标识选择本地化标题、说明、图标和输入写法,修改宿主说明不会改变选择结果。没有匹配标识的同名覆盖保留自己的文案,也不获得内置别名。在任何界面语言下,中英文写法都通过同一个会话有效目录解析,草稿保留手输写法,提交使用宿主注册名。贡献项提供自己的 `label`、`description` 和 `icon`,每次生成候选项时读取。空查询按名称确定小节顺序,未列出的行排在「指令」末尾。
33
37
 
34
38
  ### 带附件提交
35
39
 
36
- composer 携带图片或通用文件提交时,只有声明了 `input.attachments` 的宿主命令继续。其余命令路径都会抛出本地化的 `attachmentsUnsupported` 拒绝,以瞬态 toast 呈现,草稿与附件卡保持原位。处理器返回错误时保留相同草稿状态供用户重试。
40
+ composer 携带图片或通用文件提交时,只有声明了 `input.attachments` 的宿主命令继续。其余命令路径都会抛出本地化的 `attachmentsUnsupported` 拒绝,以瞬态 toast 呈现,草稿与附件卡保持原位。处理器出错时保留相同草稿状态供用户重试。
37
41
 
38
42
  -----
39
43
 
@@ -43,7 +47,7 @@ composer 携带图片或通用文件提交时,只有声明了 `input.attachmen
43
47
  <details>
44
48
  <summary>实现细节——点击展开</summary>
45
49
 
46
- `src/client/contract.ts` 是固定的业务约定:`CommandUiContract.register(name, spec)` 与 `decorate(name, spec)` 是业务包消费的全部内容。`CommandDirectory` 是唯一的 wire 派生缓存,以会话为 key:普通会话经 `command.list({sessionId})` 拉取;条目由转发的 `commands/change` owner 事件软失效、由 `connection/reset` 硬失效,并以 epoch 把关,被取代的旧拉取永远无法覆盖更新的结果。`matchSpace` 只凭该缓存同步应答;`matchEnter` 在 SubmitAttempt 信号上强等缓存,预热失败即拒绝。`command.execute` 返回匹配结果后,浏览器发布本地 `command/executed` 确认;其他客户端经宿主事件流收到持久命令节点,但收不到这条确认。`PopupSelectController` 是不含界面的外壳状态;`PopupSelectView` 自注册进 `conversation.input.overlay`,按会话解析。
50
+ `src/client/contract.ts` 定义贡献项和装饰的注册接口,以及 `dismiss(name)`:它关闭该命令已打开的弹窗与确认对话框,中止待完成的选项加载,阻止晚到结果重新打开弹窗,并保留 composer 草稿。`CommandDirectory` 负责会话级协议缓存,并通过 `resolution.ts` 解析输入命令;该模块负责内置命令标识匹配和本地化输入写法。`matchSpace` 同步读取就绪缓存,`matchEnter` 等待缓存就绪,预热失败或取消时拒绝。转发的目录和连接事件使缓存失效。宿主执行匹配的命令后,本浏览器发布 `command/executed`,其他客户端只观察持久命令事件。`PopupSelectController` 负责弹窗状态,`PopupSelectView` 占据输入浮层。`presentation.ts` 负责行标题、图标和分节,展示与解析辅助函数均留在插件内部。
47
51
 
48
52
  </details>
49
53
 
@@ -52,10 +56,10 @@ composer 携带图片或通用文件提交时,只有声明了 `input.attachmen
52
56
  <a id="further-exploration"></a>
53
57
  ## 进一步探索
54
58
 
55
- 当命令面不够用时阅读以下页面。它们从命令 API 进入触发流水线与宿主命令注册表。
59
+ 如果仅了解命令交互还不够,请阅读以下页面。它们从命令 API 进入触发流水线与宿主命令注册表。
56
60
 
57
61
  - [ui-input-trigger](../ui-input-trigger/README.zh.md)——`/` source 注册进的流水线。
58
- - [ui-conversation](../ui-conversation/README.zh.md)——声明输入浮层槽位并拥有 composer。
62
+ - [ui-conversation](../ui-conversation/README.zh.md)——声明输入浮层 slot 并拥有 composer。
59
63
  - [客户端包映射](../README.zh.md)——相邻的浏览器 UI 包。
60
64
 
61
65
  -----
@@ -63,7 +67,7 @@ composer 携带图片或通用文件提交时,只有声明了 `input.attachmen
63
67
  <a id="model-experience"></a>
64
68
  ## 模型体验
65
69
 
66
- 间接影响,经由派发路径触发的宿主 `command.execute` RPC:每个命令 handler 的宿主包拥有任何模型可见效果(`/plan` 的 handler 翻转 plan 模式,其归属包注入 policy 段),而命令行、分离结果与所有菜单和 notice 渲染都留在客户端,永不进入会话日志。
70
+ 派发路径通过其触发的宿主 `command.execute` RPC 间接影响模型:每个命令 handler 的宿主包拥有任何模型可见效果(`/plan` 的 handler 翻转 plan 模式,其归属包注入 policy 段),而命令行、分离结果与所有菜单和 notice 渲染都留在客户端,永不进入会话日志。
67
71
 
68
72
  #### KV Cache 影响
69
73
 
@@ -74,7 +78,7 @@ composer 携带图片或通用文件提交时,只有声明了 `input.attachmen
74
78
  <a id="known-limitations-and-deferred-work"></a>
75
79
 
76
80
 
77
- 这些限制界定了当前命令表面。它们是当前包约束,不是通用命令行对比或任务积压。
81
+ 这些限制界定了当前命令交互方式。它们是当前包约束,不是通用命令行对比或任务积压。
78
82
 
79
83
  - **脱离会话后,分离结果 notice 回退到 console**——fire-and-forget 路径经 `SessionInput.notify` 把结果送到触发会话的 composer;会话销毁后,console 输出行是仅剩的呈现面。
80
84
 
@@ -88,4 +92,4 @@ composer 携带图片或通用文件提交时,只有声明了 `input.attachmen
88
92
 
89
93
  </details>
90
94
 
91
- **运行时不变式:** 不发布伴生入口。这是基于 wire command directory 的浏览器侧 source,不发出 Cordis 事件,也不持有跨插件可变状态;dispatch 与 cache 行为由包测试覆盖。
95
+ **运行时不变式:** 不发布伴生入口。这是基于 wire 命令目录的浏览器侧 source,不发出 Cordis 事件,也不持有跨插件可变状态;dispatch 与 cache 行为由包测试覆盖。
package/lib/client.js CHANGED
@@ -9,6 +9,118 @@ window.__ModuleLoader__.load({
9
9
  let _deepseek_ai_dsh_client_store = require("@deepseek-ai/dsh-client-store");
10
10
  let react_jsx_runtime = require("react/jsx-runtime");
11
11
  let react = require("react");
12
+ //#region lib/types/client/locales.js
13
+ /**
14
+ * `command` namespace dictionaries: the composer menu's section headings,
15
+ * the client face (title, description, claim token) of the built-in Host
16
+ * commands whose catalog descriptors carry English text only, and the
17
+ * popupSelect shell's copy.
18
+ */
19
+ /** Simplified Chinese dictionary (the key-set source of truth). */
20
+ const zh = {
21
+ "section.add": "添加",
22
+ "section.commands": "指令",
23
+ "label.goal": "目标",
24
+ "label.plan": "计划",
25
+ "label.feedback": "反馈",
26
+ "label.compact": "压缩",
27
+ "label.permission": "权限",
28
+ "label.export": "下载日志",
29
+ "description.goal": "设置或查看长期任务目标",
30
+ "description.plan": "进入或退出计划模式",
31
+ "description.feedback": "发送关于当前会话的反馈",
32
+ "description.compact": "压缩以上对话内容",
33
+ "description.permission": "切换权限预设(沙箱模式与审批策略)",
34
+ "description.export": "将当前会话内容导出为 ZIP",
35
+ "token.goal": "目标",
36
+ "token.plan": "计划",
37
+ "token.feedback": "反馈",
38
+ "token.compact": "压缩",
39
+ "token.permission": "权限",
40
+ "token.export": "导出",
41
+ "search.placeholder": "搜索…",
42
+ "search.aria": "筛选选项",
43
+ "status.loading": "正在加载选项…",
44
+ "status.applying": "正在应用…",
45
+ "status.empty": "无选项",
46
+ "overlay.aria": "/{command} 选项",
47
+ "listbox.aria": "/{command} 匹配项",
48
+ "notice.attachmentsUnsupported": "/{command} 不接受附件,请先移除附件"
49
+ };
50
+ /** English dictionary, checked complete against the zh key set. */
51
+ const en = {
52
+ "section.add": "Add",
53
+ "section.commands": "Commands",
54
+ "label.goal": "Goal",
55
+ "label.plan": "Plan",
56
+ "label.feedback": "Feedback",
57
+ "label.compact": "Compact",
58
+ "label.permission": "Permission",
59
+ "label.export": "Export",
60
+ "description.goal": "Set or view the goal for a long-running task",
61
+ "description.plan": "Enter or leave plan mode",
62
+ "description.feedback": "Record feedback about this session",
63
+ "description.compact": "Compact older conversation history",
64
+ "description.permission": "Switch the permission preset (sandbox mode + approval policy)",
65
+ "description.export": "Download this Session log as a ZIP archive",
66
+ "token.goal": "goal",
67
+ "token.plan": "plan",
68
+ "token.feedback": "feedback",
69
+ "token.compact": "compact",
70
+ "token.permission": "permission",
71
+ "token.export": "export",
72
+ "search.placeholder": "Search…",
73
+ "search.aria": "Filter options",
74
+ "status.loading": "Loading options…",
75
+ "status.applying": "Applying…",
76
+ "status.empty": "No options",
77
+ "overlay.aria": "/{command} options",
78
+ "listbox.aria": "/{command} matches",
79
+ "notice.attachmentsUnsupported": "/{command} does not accept attachments; remove them first"
80
+ };
81
+ //#endregion
82
+ //#region lib/types/client/resolution.js
83
+ const BUILTINS = {
84
+ goal: "@deepseek-ai/dsh-command-goal",
85
+ plan: "@deepseek-ai/dsh-plan-mode",
86
+ feedback: "@deepseek-ai/dsh-command-feedback",
87
+ compact: "@deepseek-ai/dsh-command-compact",
88
+ permission: "@deepseek-ai/dsh-permission-presets",
89
+ export: "@deepseek-ai/dsh-session-log-export"
90
+ };
91
+ /**
92
+ * Identify a first-party definition without interpreting its display copy.
93
+ * @param descriptor - effective Host descriptor after scoped shadowing.
94
+ * @returns its first-party name, or undefined for another definition.
95
+ */
96
+ function builtinCommandName(descriptor) {
97
+ return Object.keys(BUILTINS).find((name) => descriptor.definitionId === BUILTINS[name]);
98
+ }
99
+ /**
100
+ * Select the input spelling for a menu-picked command.
101
+ * @param descriptor - effective Host descriptor.
102
+ * @param t - command-namespace translator.
103
+ * @returns localized spelling for a known definition, otherwise its registered name.
104
+ */
105
+ function claimToken(descriptor, t) {
106
+ const name = builtinCommandName(descriptor);
107
+ return name === void 0 ? descriptor.name : t(`token.${name}`);
108
+ }
109
+ const TOKEN_ALIASES = new Map(Object.keys(BUILTINS).flatMap((name) => [zh[`token.${name}`], en[`token.${name}`]].map((token) => [token, name])));
110
+ /**
111
+ * Resolve typed spelling against the current Session's effective definitions.
112
+ * @param token - typed name without its leading slash.
113
+ * @param descriptors - effective descriptors in the Session's ready catalog.
114
+ * @returns the matching descriptor; aliases never select an unrelated scoped override.
115
+ */
116
+ function resolveCommand(token, descriptors) {
117
+ const exact = descriptors.find((descriptor) => descriptor.name === token);
118
+ if (exact !== void 0) return exact;
119
+ const name = TOKEN_ALIASES.get(token);
120
+ if (name === void 0) return void 0;
121
+ return descriptors.find((descriptor) => descriptor.definitionId === BUILTINS[name]);
122
+ }
123
+ //#endregion
12
124
  //#region lib/types/client/directory.js
13
125
  /** One session key's cache cell. */
14
126
  var Entry = class {
@@ -35,15 +147,15 @@ window.__ModuleLoader__.load({
35
147
  return this.entries.get(sessionId)?.state ?? "cold";
36
148
  }
37
149
  /**
38
- * Synchronous exact-name lookup over one session's hot snapshot.
150
+ * Synchronous command lookup over one Session's ready catalog; exact names precede localized aliases.
39
151
  * @param sessionId - session key.
40
- * @param name - command name without the leading slash.
152
+ * @param name - typed command spelling without the leading slash.
41
153
  * @returns the descriptor, or undefined when absent or the entry is not ready.
42
154
  */
43
155
  resolve(sessionId, name) {
44
156
  const entry = this.entries.get(sessionId);
45
157
  if (entry === void 0 || entry.state !== "ready") return void 0;
46
- return entry.commands.find((c) => c.name === name);
158
+ return resolveCommand(name, entry.commands);
47
159
  }
48
160
  /** Soft invalidation (commands-changed): background repull on every touched key; ready snapshots keep serving. */
49
161
  invalidateAll() {
@@ -159,43 +271,6 @@ window.__ModuleLoader__.load({
159
271
  return signal.reason instanceof Error ? signal.reason : /* @__PURE__ */ new Error("command directory wait aborted");
160
272
  }
161
273
  //#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
199
274
  //#region lib/types/client/popup.js
200
275
  /**
201
276
  * Headless popupSelect shell state: one controller per client
@@ -464,15 +539,92 @@ window.__ModuleLoader__.load({
464
539
  }
465
540
  };
466
541
  //#endregion
542
+ //#region lib/types/client/presentation.js
543
+ /** Row names per section, highest usage first; rows outside both lists close the Commands section in catalog order. */
544
+ const SECTION_ROWS = {
545
+ add: [
546
+ "file",
547
+ "goal",
548
+ "plan",
549
+ "feedback"
550
+ ],
551
+ commands: [
552
+ "compact",
553
+ "permission",
554
+ "model",
555
+ "export"
556
+ ]
557
+ };
558
+ /** One built-in Host command's face, keyed by its dictionary entries. */
559
+ function hostFace(name, icon) {
560
+ return [name, {
561
+ label: `label.${name}`,
562
+ description: `description.${name}`,
563
+ icon
564
+ }];
565
+ }
566
+ /** Built-in Host commands whose client face this package owns. */
567
+ const HOST_FACES = new Map([
568
+ hostFace("goal", _deepseek_ai_dsh_client_ui_primitives.IconGoalOutline16),
569
+ hostFace("plan", _deepseek_ai_dsh_client_ui_primitives.IconPlanOutline14),
570
+ hostFace("feedback", _deepseek_ai_dsh_client_ui_primitives.IconPaperPlaneOutline14),
571
+ hostFace("compact", _deepseek_ai_dsh_client_ui_primitives.IconCompactOutline16),
572
+ hostFace("permission", _deepseek_ai_dsh_client_ui_primitives.IconShieldOutline16),
573
+ hostFace("export", _deepseek_ai_dsh_client_ui_primitives.IconDownloadOutline16)
574
+ ]);
575
+ /**
576
+ * The localized menu face of a catalog row.
577
+ * @param descriptor - effective Host command descriptor.
578
+ * @param t - the `command` namespace translator.
579
+ * @returns title, description, and glyph for a built-in command; undefined
580
+ * for any other row, which keeps its catalog description.
581
+ */
582
+ function builtinRowFace(descriptor, t) {
583
+ const name = builtinCommandName(descriptor);
584
+ const face = name === void 0 ? void 0 : HOST_FACES.get(name);
585
+ return face === void 0 ? void 0 : {
586
+ label: t(face.label),
587
+ description: t(face.description),
588
+ icon: face.icon
589
+ };
590
+ }
591
+ /**
592
+ * Arrange the empty-query menu: the Add section, then the Commands section,
593
+ * each in usage order, with unlisted rows closing Commands in their input
594
+ * order; each row carries its section heading.
595
+ * @param rows - the visible candidates in catalog-then-contribution order.
596
+ * @param t - the `command` namespace translator.
597
+ * @returns the sectioned rows.
598
+ */
599
+ function sectionRows(rows, t) {
600
+ const listed = new Set([...SECTION_ROWS.add, ...SECTION_ROWS.commands]);
601
+ const byName = new Map(rows.map((row) => [row.name, row]));
602
+ const pick = (names) => names.flatMap((name) => {
603
+ const row = byName.get(name);
604
+ return row === void 0 ? [] : [row];
605
+ });
606
+ const add = pick(SECTION_ROWS.add).map((row) => ({
607
+ ...row,
608
+ section: t("section.add")
609
+ }));
610
+ const commands = [...pick(SECTION_ROWS.commands), ...rows.filter((row) => !listed.has(row.name))].map((row) => ({
611
+ ...row,
612
+ section: t("section.commands")
613
+ }));
614
+ return [...add, ...commands];
615
+ }
616
+ //#endregion
467
617
  //#region lib/types/client/service.js
468
618
  /**
469
619
  * CommandUiRuntime (`ctx.commandUi`): the '/' command source over the
470
620
  * session-keyed directory, the client-contribution registry, and the
471
621
  * per-session popupSelect controllers. Candidate synthesis merges the host
472
- * catalog with contributions by availability, then position filtering and
473
- * the `/` menu's shared name ranking (ui-primitives `rankByName`); a
474
- * host/contribution name collision fails loud. Every execute
475
- * addresses the session's agent by sessionId — sessions are always
622
+ * catalog with contributions by availability, gives built-in Host rows their
623
+ * localized face (presentation.ts), then position-filters; an empty query
624
+ * lists the Add and Commands sections in usage order, a typed query ranks
625
+ * every row by the `/` menu's shared name-and-label ranking (ui-primitives
626
+ * `rankByName`). A host/contribution name collision fails loud. Every
627
+ * execute addresses the session's agent by sessionId — sessions are always
476
628
  * agent-backed.
477
629
  */
478
630
  /** Recover the command name from a line the Host confirmed as executed. */
@@ -481,15 +633,6 @@ window.__ModuleLoader__.load({
481
633
  const separator = trimmed.search(/\s/u);
482
634
  return (separator === -1 ? trimmed : trimmed.slice(0, separator)).slice(1);
483
635
  }
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
- ]);
493
636
  /** Command surface: session-keyed directory + '/' source + contribution registry + per-session popups. */
494
637
  var CommandUiRuntime = class extends _deepseek_ai_cordis.Service {
495
638
  static inject = [
@@ -583,6 +726,14 @@ window.__ModuleLoader__.load({
583
726
  };
584
727
  }
585
728
  /**
729
+ * Close every open popup for a command whose options have become stale.
730
+ * Pending loads and confirmations lose their binding; drafts stay intact.
731
+ * @param name - command name without the leading slash.
732
+ */
733
+ dismiss(name) {
734
+ for (const popup of this.live.popups.values()) if (popup.state.getSnapshot().command === name) popup.dismiss();
735
+ }
736
+ /**
586
737
  * Resolve the per-session popup controller (lazy; dies with the session
587
738
  * scope). The controller's consume callback dispatches the scoped
588
739
  * consume-token event back to this session; focusComposer reaches the
@@ -630,7 +781,11 @@ window.__ModuleLoader__.load({
630
781
  if (this.focusHooks.get(id) === focus) this.focusHooks.delete(id);
631
782
  };
632
783
  }
633
- /** Menu candidates: host catalog + contribution availability, then position filtering and the shared name ranking. */
784
+ /**
785
+ * Menu candidates: host catalog + contribution availability, built-in rows
786
+ * localized, then position filtering; sections for an empty query, the
787
+ * shared name-and-label ranking for a typed one.
788
+ */
634
789
  async candidates(session, req) {
635
790
  const list = await this.directory.ensureReady(session.sessionId, req.signal);
636
791
  const rows = [];
@@ -639,7 +794,7 @@ window.__ModuleLoader__.load({
639
794
  seen.add(c.name);
640
795
  rows.push({
641
796
  name: c.name,
642
- description: this.hostDescription(c),
797
+ ...builtinRowFace(c, this.t) ?? { description: c.description },
643
798
  ...c.input !== void 0 ? { hint: c.input.hint } : {}
644
799
  });
645
800
  }
@@ -648,15 +803,13 @@ window.__ModuleLoader__.load({
648
803
  if (seen.has(contribution.name)) throw new Error(`ui-commands: contribution /${contribution.name} collides with a host command`);
649
804
  rows.push({
650
805
  name: contribution.name,
651
- description: contribution.description()
806
+ ...contribution.label === void 0 ? {} : { label: contribution.label() },
807
+ ...contribution.description === void 0 ? {} : { description: contribution.description() },
808
+ ...contribution.icon === void 0 ? {} : { icon: contribution.icon }
652
809
  });
653
810
  }
654
- return (0, _deepseek_ai_dsh_client_ui_primitives.rankByName)(rows.filter((c) => req.position === "leading" || c.hint === void 0), req.query);
655
- }
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;
811
+ const visible = rows.filter((c) => req.position === "leading" || c.hint === void 0);
812
+ return req.query === "" ? sectionRows(visible, this.t) : (0, _deepseek_ai_dsh_client_ui_primitives.rankByName)(visible, req.query);
660
813
  }
661
814
  /** Decision table, menu column: contribution/decorated-host → popup or action; host input → claim; host bare → detached execute. */
662
815
  dispatch(pick) {
@@ -679,7 +832,7 @@ window.__ModuleLoader__.load({
679
832
  });
680
833
  return "handled";
681
834
  }
682
- if (desc.input !== void 0) return { claim: this.leadingClaim(desc, pick.session) };
835
+ if (desc.input !== void 0) return { claim: this.leadingClaim(desc, pick.session, claimToken(desc, this.t)) };
683
836
  this.consumeVia(pick.session.sessionId, {
684
837
  via: "menu",
685
838
  span: pick.span
@@ -690,11 +843,10 @@ window.__ModuleLoader__.load({
690
843
  /** Decision table, space column: hot-key sync check; only host leadingInput claims. */
691
844
  matchSpace(session, token) {
692
845
  if (!token.startsWith("/")) return void 0;
693
- const name = token.slice(1);
694
- if (this.live.contributions.has(name)) return void 0;
695
- const desc = this.directory.resolve(session.sessionId, name);
846
+ if (this.live.contributions.has(token.slice(1))) return void 0;
847
+ const desc = this.directory.resolve(session.sessionId, token.slice(1));
696
848
  if (desc === void 0 || desc.input === void 0) return void 0;
697
- return { claim: this.leadingClaim(desc, session) };
849
+ return { claim: this.leadingClaim(desc, session, token.slice(1)) };
698
850
  }
699
851
  /**
700
852
  * Decision table, enter column. Strong-waits the session's catalog (a
@@ -708,6 +860,9 @@ window.__ModuleLoader__.load({
708
860
  * refusal so the machine surfaces one composer notice and the draft and
709
861
  * attachments stay in place; nothing executes and nothing is dropped. An
710
862
  * action submits nothing and runs regardless.
863
+ *
864
+ * A typed token is resolved through the localized claim tokens, so a line
865
+ * written as `/计划` reaches the `plan` descriptor and executes as `/plan`.
711
866
  */
712
867
  async matchEnter(session, line, signal, envelope) {
713
868
  const trimmed = line.trim();
@@ -715,24 +870,26 @@ window.__ModuleLoader__.load({
715
870
  const ws = trimmed.search(/\s/);
716
871
  const token = ws === -1 ? trimmed : trimmed.slice(0, ws);
717
872
  const bare = ws === -1;
718
- const name = token.slice(1);
719
- if (name === "") return void 0;
873
+ const typedName = token.slice(1);
874
+ if (typedName === "") return void 0;
720
875
  const refuseAttachments = () => {
721
- throw new Error(this.t("notice.attachmentsUnsupported", { command: name }));
876
+ throw new Error(this.t("notice.attachmentsUnsupported", { command: typedName }));
722
877
  };
723
- const contribution = this.live.contributions.get(name);
878
+ const contribution = this.live.contributions.get(typedName);
724
879
  if (contribution !== void 0 && contribution.available(session)) {
725
880
  if (!bare) return void 0;
726
881
  if (envelope.attachments > 0 && contribution.ui.kind !== "action") refuseAttachments();
727
- this.invoke(name, contribution.ui, session, {
882
+ this.invoke(typedName, contribution.ui, session, {
728
883
  via: "enter",
729
884
  token
730
885
  });
731
886
  return "handled";
732
887
  }
733
888
  await this.directory.ensureReady(session.sessionId, signal);
734
- const desc = this.directory.resolve(session.sessionId, name);
889
+ const desc = this.directory.resolve(session.sessionId, typedName);
735
890
  if (desc === void 0) return void 0;
891
+ const name = desc.name;
892
+ const canonical = `/${name}${trimmed.slice(token.length)}`;
736
893
  if (bare) {
737
894
  const decoration = this.live.decorations.get(name);
738
895
  if (decoration !== void 0 && decoration.available(session)) {
@@ -746,7 +903,7 @@ window.__ModuleLoader__.load({
746
903
  }
747
904
  if (desc.input !== void 0) {
748
905
  if (envelope.attachments > 0 && desc.input.attachments !== true) refuseAttachments();
749
- return { claim: this.leadingClaim(desc, session) };
906
+ return { claim: this.leadingClaim(desc, session, token.slice(1)) };
750
907
  }
751
908
  if (!bare) return void 0;
752
909
  if (envelope.attachments > 0) refuseAttachments();
@@ -754,7 +911,7 @@ window.__ModuleLoader__.load({
754
911
  via: "enter",
755
912
  token
756
913
  });
757
- this.runDetached(desc, session, trimmed);
914
+ this.runDetached(desc, session, canonical);
758
915
  return "handled";
759
916
  }
760
917
  /**
@@ -771,14 +928,22 @@ window.__ModuleLoader__.load({
771
928
  if (actx === void 0) return;
772
929
  this.popupFor(actx).open(name, ui, session, segment);
773
930
  }
774
- /** Build the leadingInput claim: token `/name ` + the command.execute submit transaction. */
775
- leadingClaim(desc, session) {
776
- const token = `/${desc.name} `;
931
+ /**
932
+ * Build the leadingInput claim. The composer keeps the claimed token in
933
+ * the draft and reads the arguments after it, so the token is the spelling
934
+ * the draft will carry: the locale's token for a menu pick, the typed
935
+ * spelling for Space and Enter. The command.execute submit transaction
936
+ * always sends the catalog name.
937
+ */
938
+ leadingClaim(desc, session, shown) {
939
+ const token = `/${shown} `;
940
+ const line = `/${desc.name} `;
777
941
  return {
942
+ name: desc.name,
778
943
  token,
779
944
  ...desc.input !== void 0 ? { hint: desc.input.hint } : {},
780
945
  ...desc.input?.attachments === true ? { attachments: true } : {},
781
- submit: (args, _actx, attachments) => this.execute(session, token + args, attachments)
946
+ submit: (args, _actx, attachments) => this.execute(session, line + args, attachments)
782
947
  };
783
948
  }
784
949
  /**
@@ -890,7 +1055,7 @@ window.__ModuleLoader__.load({
890
1055
  }
891
1056
  //#endregion
892
1057
  //#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-commands/src/client/PopupSelectView.module.css.mjs
893
- const css = ".mufS8W_card{z-index:100;--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);background:var(--dsw-specific-menu);--dsw-elevation-stroke-color:var(--dsw-alias-border-l1);min-width:min(220px,100%);max-width:100%;max-height:320px;box-shadow:var(--dsw-elevation-prominent);border:0;border-radius:20px;outline:none;flex-direction:column;padding:4px;display:flex;position:absolute;bottom:calc(100% + 4px);left:0;overflow:hidden}.mufS8W_viewport{flex-direction:column;min-height:0;display:flex;overflow-y:auto}.mufS8W_row{cursor:pointer;color:var(--dsw-alias-label-primary);border-radius:8px;align-items:center;gap:8px;padding:6px 8px;font-size:13px;display:flex}.mufS8W_rowActive{background:var(--dsw-alias-interactive-bg-hover)}.mufS8W_label{white-space:nowrap;text-overflow:ellipsis;flex:auto;min-width:0;overflow:hidden}.mufS8W_detail{color:var(--dsw-alias-label-tertiary);white-space:nowrap;text-overflow:ellipsis;font-size:12px;overflow:hidden}.mufS8W_check{color:var(--dsw-alias-label-primary);flex:none;display:inline-flex}.mufS8W_status{color:var(--dsw-alias-label-tertiary);padding:8px 10px;font-size:13px}.mufS8W_search{border:.5px solid var(--dsw-alias-border-inverted);color:var(--dsw-alias-label-primary);background:0 0;border-radius:8px;outline:none;margin:2px 2px 4px;padding:6px 8px;font-size:13px}.mufS8W_error{color:var(--dsw-alias-state-error-primary);align-items:center;gap:8px;padding:6px 8px;font-size:12px;display:flex}.mufS8W_errorText{text-overflow:ellipsis;flex:1;overflow:hidden}.mufS8W_retry{border:.5px solid var(--dsw-alias-border-inverted);color:var(--dsw-alias-label-primary);cursor:pointer;background:0 0;border-radius:6px;padding:2px 8px;font-size:12px}";
1058
+ const css = ".mufS8W_card{z-index:100;--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);background:var(--dsw-specific-menu);--dsw-elevation-stroke-color:var(--dsw-alias-border-l1);min-width:min(220px,100%);max-width:100%;max-height:320px;box-shadow:var(--dsw-elevation-prominent);border:0;border-radius:20px;outline:none;flex-direction:column;padding:4px;display:flex;position:absolute;bottom:calc(100% + 4px);left:0;overflow:hidden}.mufS8W_viewport{flex-direction:column;min-height:0;display:flex;overflow-y:auto}.mufS8W_row{cursor:pointer;color:var(--dsw-alias-label-primary);border-radius:8px;align-items:center;gap:8px;padding:6px 8px;font-size:13px;display:flex}.mufS8W_rowActive{background:var(--dsw-alias-interactive-bg-hover)}.mufS8W_label{flex:0 auto;align-items:baseline;gap:4px;min-width:0;display:flex}.mufS8W_labelText{white-space:nowrap;text-overflow:ellipsis;min-width:0;overflow:hidden}.mufS8W_badge{color:var(--dsw-alias-label-tertiary);letter-spacing:.2px;flex:none;align-self:flex-start;margin-top:-1px;font-size:8px;font-weight:600;line-height:10px}.mufS8W_detail{min-width:0;color:var(--dsw-alias-label-tertiary);white-space:nowrap;text-overflow:ellipsis;flex:1;font-size:12px;overflow:hidden}.mufS8W_check{color:var(--dsw-alias-label-primary);flex:none;margin-left:auto;display:inline-flex}.mufS8W_status{color:var(--dsw-alias-label-tertiary);padding:8px 10px;font-size:13px}.mufS8W_search{border:.5px solid var(--dsw-alias-border-inverted);color:var(--dsw-alias-label-primary);background:0 0;border-radius:8px;outline:none;margin:2px 2px 4px;padding:6px 8px;font-size:13px}.mufS8W_error{color:var(--dsw-alias-state-error-primary);align-items:center;gap:8px;padding:6px 8px;font-size:12px;display:flex}.mufS8W_errorText{text-overflow:ellipsis;flex:1;overflow:hidden}.mufS8W_retry{border:.5px solid var(--dsw-alias-border-inverted);color:var(--dsw-alias-label-primary);cursor:pointer;background:0 0;border-radius:6px;padding:2px 8px;font-size:12px}";
894
1059
  const tagId = "@deepseek-ai/dsh-client-ui-commands/PopupSelectView.module.css";
895
1060
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
896
1061
  const tag = document.createElement("style");
@@ -900,12 +1065,14 @@ window.__ModuleLoader__.load({
900
1065
  document.head.appendChild(tag);
901
1066
  }
902
1067
  var PopupSelectView_module_css_default = {
1068
+ "badge": "mufS8W_badge",
903
1069
  "card": "mufS8W_card",
904
1070
  "check": "mufS8W_check",
905
1071
  "detail": "mufS8W_detail",
906
1072
  "error": "mufS8W_error",
907
1073
  "errorText": "mufS8W_errorText",
908
1074
  "label": "mufS8W_label",
1075
+ "labelText": "mufS8W_labelText",
909
1076
  "retry": "mufS8W_retry",
910
1077
  "row": "mufS8W_row",
911
1078
  "rowActive": "mufS8W_rowActive",
@@ -1038,6 +1205,7 @@ window.__ModuleLoader__.load({
1038
1205
  children: rows.map((option, index) => (0, react_jsx_runtime.jsxs)("div", {
1039
1206
  role: "option",
1040
1207
  "aria-selected": index === state.active,
1208
+ "aria-label": option.badge === void 0 ? void 0 : `${option.label} ${option.badge}`,
1041
1209
  className: clsx(PopupSelectView_module_css_default.row, index === state.active && PopupSelectView_module_css_default.rowActive),
1042
1210
  onClick: () => {
1043
1211
  popup.select(index);
@@ -1046,9 +1214,15 @@ window.__ModuleLoader__.load({
1046
1214
  popup.highlight(index);
1047
1215
  },
1048
1216
  children: [
1049
- (0, react_jsx_runtime.jsx)("span", {
1217
+ (0, react_jsx_runtime.jsxs)("span", {
1050
1218
  className: PopupSelectView_module_css_default.label,
1051
- children: option.label
1219
+ children: [(0, react_jsx_runtime.jsx)("span", {
1220
+ className: PopupSelectView_module_css_default.labelText,
1221
+ children: option.label
1222
+ }), option.badge !== void 0 && (0, react_jsx_runtime.jsx)("sup", {
1223
+ className: PopupSelectView_module_css_default.badge,
1224
+ children: option.badge
1225
+ })]
1052
1226
  }),
1053
1227
  option.detail !== void 0 && (0, react_jsx_runtime.jsx)("span", {
1054
1228
  className: PopupSelectView_module_css_default.detail,
@@ -1095,8 +1269,7 @@ window.__ModuleLoader__.load({
1095
1269
  "locale"
1096
1270
  ];
1097
1271
  /**
1098
- * Client plugin body: mount the service, then register the popupSelect shell
1099
- * into the input overlay once its declarer is up.
1272
+ * Mount the command service and its per-session popupSelect overlay.
1100
1273
  * @param ctx - client root context.
1101
1274
  */
1102
1275
  function apply(ctx) {
@@ -1,10 +1,12 @@
1
1
  /**
2
2
  * Frozen contract of the client command surface. Types only. The
3
3
  * CommandUiRuntime (`ctx.commandUi`) implements this face; business packages
4
- * consume `register` alone.
4
+ * consume its registration and dismissal operations.
5
5
  */
6
+ import type { ComponentType } from 'react';
6
7
  import type { Context as ClientContext } from '@deepseek-ai/cordis';
7
8
  import type { ClientSessionContext } from '@deepseek-ai/dsh-client-ui-input-trigger/client';
9
+ import type { IconProps } from '@deepseek-ai/dsh-client-ui-primitives';
8
10
  /** Copy for an option that must be acknowledged before onSelect can run. */
9
11
  export interface SelectConfirmation {
10
12
  readonly title: string;
@@ -17,6 +19,8 @@ export interface SelectConfirmation {
17
19
  export interface SelectOption {
18
20
  readonly id: string;
19
21
  readonly label: string;
22
+ /** Optional short marker rendered as a superscript beside the label. */
23
+ readonly badge?: string;
20
24
  readonly detail?: string;
21
25
  readonly active?: boolean;
22
26
  /** Optional in-page risk gate owned by the shared popup shell. */
@@ -35,8 +39,7 @@ export interface PopupSelectSpec {
35
39
  }
36
40
  /**
37
41
  * 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
42
+ * consumes the trigger token and runs one client-side callback. It submits nothing, so an
40
43
  * attachment-carrying draft never refuses it.
41
44
  */
42
45
  export interface ActionSpec {
@@ -53,13 +56,18 @@ export type CommandUiSpec = PopupSelectSpec | ActionSpec;
53
56
  * One client-owned command contribution: a slash-menu entry whose behavior
54
57
  * lives entirely on the client (no host descriptor). Merged with the host
55
58
  * catalog by name — a collision with a host command fails loud at candidate
56
- * synthesis, never shadows.
59
+ * synthesis, never shadows. Row copy is read on every candidate pass, so a
60
+ * locale change reaches the next menu open without re-registration.
57
61
  */
58
62
  export interface CommandContribution {
59
63
  /** Command name without the leading slash (unique across contributions). */
60
64
  readonly name: string;
61
- /** Resolve the localized menu row description when candidates are requested. */
62
- readonly description: () => string;
65
+ /** Localized menu row title; the name itself when absent. */
66
+ label?(): string;
67
+ /** Localized menu row description; the row shows none when absent. */
68
+ description?(): string;
69
+ /** Menu row glyph from the shared icon set. */
70
+ readonly icon?: ComponentType<IconProps>;
63
71
  /** Capability filter, called with a fresh projection per candidate pass. */
64
72
  available(session: ClientSessionContext): boolean;
65
73
  /** The command's UI behavior. */
@@ -94,6 +102,8 @@ export interface CommandUiContract {
94
102
  * Duplicate names throw at registration.
95
103
  */
96
104
  decorate(decoration: CommandDecoration): () => void;
105
+ /** Close this command's open popups and confirmations without consuming composer drafts. */
106
+ dismiss(name: string): void;
97
107
  /** Resolve the per-session popup controller for one session scope (wiring/overlay layer). */
98
108
  popupFor(actx: ClientContext): unknown;
99
109
  }
@@ -28,9 +28,9 @@ export declare class CommandDirectory {
28
28
  */
29
29
  status(sessionId: SessionId): DirectoryStatus;
30
30
  /**
31
- * Synchronous exact-name lookup over one session's hot snapshot.
31
+ * Synchronous command lookup over one Session's ready catalog; exact names precede localized aliases.
32
32
  * @param sessionId - session key.
33
- * @param name - command name without the leading slash.
33
+ * @param name - typed command spelling without the leading slash.
34
34
  * @returns the descriptor, or undefined when absent or the entry is not ready.
35
35
  */
36
36
  resolve(sessionId: SessionId, name: string): CommandDescriptor | undefined;
@@ -23,15 +23,14 @@ declare module '@deepseek-ai/cordis' {
23
23
  }
24
24
  declare module '@deepseek-ai/dsh-client-ui-slots' {
25
25
  interface LocaleNamespaceMap {
26
- /** The popupSelect shell's copy. */
26
+ /** The menu rows' and the popupSelect shell's copy. */
27
27
  command: CommandKey;
28
28
  }
29
29
  }
30
30
  /** Required services: the '/' source registry, session scopes, commands Remote, and locale registry. */
31
31
  export declare const inject: string[];
32
32
  /**
33
- * Client plugin body: mount the service, then register the popupSelect shell
34
- * into the input overlay once its declarer is up.
33
+ * Mount the command service and its per-session popupSelect overlay.
35
34
  * @param ctx - client root context.
36
35
  */
37
36
  export declare function apply(ctx: ClientContext): void;
@@ -1,12 +1,31 @@
1
- /** `command` namespace dictionaries (the popupSelect shell's copy). */
1
+ /**
2
+ * `command` namespace dictionaries: the composer menu's section headings,
3
+ * the client face (title, description, claim token) of the built-in Host
4
+ * commands whose catalog descriptors carry English text only, and the
5
+ * popupSelect shell's copy.
6
+ */
2
7
  /** Simplified Chinese dictionary (the key-set source of truth). */
3
8
  export declare const zh: {
4
- 'description.compact': string;
5
- 'description.export': string;
6
- 'description.feedback': string;
9
+ 'section.add': string;
10
+ 'section.commands': string;
11
+ 'label.goal': string;
12
+ 'label.plan': string;
13
+ 'label.feedback': string;
14
+ 'label.compact': string;
15
+ 'label.permission': string;
16
+ 'label.export': string;
7
17
  'description.goal': string;
8
- 'description.permission': string;
9
18
  'description.plan': string;
19
+ 'description.feedback': string;
20
+ 'description.compact': string;
21
+ 'description.permission': string;
22
+ 'description.export': string;
23
+ 'token.goal': string;
24
+ 'token.plan': string;
25
+ 'token.feedback': string;
26
+ 'token.compact': string;
27
+ 'token.permission': string;
28
+ 'token.export': string;
10
29
  'search.placeholder': string;
11
30
  'search.aria': string;
12
31
  'status.loading': string;
@@ -20,12 +39,26 @@ export declare const zh: {
20
39
  export type CommandKey = keyof typeof zh;
21
40
  /** English dictionary, checked complete against the zh key set. */
22
41
  export declare const en: {
23
- 'description.compact': string;
24
- 'description.export': string;
25
- 'description.feedback': string;
42
+ 'section.add': string;
43
+ 'section.commands': string;
44
+ 'label.goal': string;
45
+ 'label.plan': string;
46
+ 'label.feedback': string;
47
+ 'label.compact': string;
48
+ 'label.permission': string;
49
+ 'label.export': string;
26
50
  'description.goal': string;
27
- 'description.permission': string;
28
51
  'description.plan': string;
52
+ 'description.feedback': string;
53
+ 'description.compact': string;
54
+ 'description.permission': string;
55
+ 'description.export': string;
56
+ 'token.goal': string;
57
+ 'token.plan': string;
58
+ 'token.feedback': string;
59
+ 'token.compact': string;
60
+ 'token.permission': string;
61
+ 'token.export': string;
29
62
  'search.placeholder': string;
30
63
  'search.aria': string;
31
64
  'status.loading': string;
@@ -0,0 +1,23 @@
1
+ import type { InputTriggerCandidate } from '@deepseek-ai/dsh-client-ui-input-trigger/client';
2
+ import type { TranslateNS } from '@deepseek-ai/dsh-client-locale/client';
3
+ import type { CommandDescriptor } from '@deepseek-ai/dsh-commands/types';
4
+ /** The menu's two sections. */
5
+ export type MenuSection = 'add' | 'commands';
6
+ /**
7
+ * The localized menu face of a catalog row.
8
+ * @param descriptor - effective Host command descriptor.
9
+ * @param t - the `command` namespace translator.
10
+ * @returns title, description, and glyph for a built-in command; undefined
11
+ * for any other row, which keeps its catalog description.
12
+ */
13
+ export declare function builtinRowFace(descriptor: CommandDescriptor, t: TranslateNS<'command'>): Pick<InputTriggerCandidate, 'label' | 'description' | 'icon'> | undefined;
14
+ /**
15
+ * Arrange the empty-query menu: the Add section, then the Commands section,
16
+ * each in usage order, with unlisted rows closing Commands in their input
17
+ * order; each row carries its section heading.
18
+ * @param rows - the visible candidates in catalog-then-contribution order.
19
+ * @param t - the `command` namespace translator.
20
+ * @returns the sectioned rows.
21
+ */
22
+ export declare function sectionRows(rows: readonly InputTriggerCandidate[], t: TranslateNS<'command'>): readonly InputTriggerCandidate[];
23
+ //# sourceMappingURL=presentation.d.ts.map
@@ -0,0 +1,35 @@
1
+ /** Command identity and localized input spelling over the effective Host catalog. */
2
+ import type { CommandDescriptor } from '@deepseek-ai/dsh-commands/types';
3
+ import type { TranslateNS } from '@deepseek-ai/dsh-client-locale/client';
4
+ declare const BUILTINS: {
5
+ readonly goal: "@deepseek-ai/dsh-command-goal";
6
+ readonly plan: "@deepseek-ai/dsh-plan-mode";
7
+ readonly feedback: "@deepseek-ai/dsh-command-feedback";
8
+ readonly compact: "@deepseek-ai/dsh-command-compact";
9
+ readonly permission: "@deepseek-ai/dsh-permission-presets";
10
+ readonly export: "@deepseek-ai/dsh-session-log-export";
11
+ };
12
+ /** Names whose first-party definitions have localized client presentation. */
13
+ export type BuiltinCommandName = keyof typeof BUILTINS;
14
+ /**
15
+ * Identify a first-party definition without interpreting its display copy.
16
+ * @param descriptor - effective Host descriptor after scoped shadowing.
17
+ * @returns its first-party name, or undefined for another definition.
18
+ */
19
+ export declare function builtinCommandName(descriptor: CommandDescriptor): BuiltinCommandName | undefined;
20
+ /**
21
+ * Select the input spelling for a menu-picked command.
22
+ * @param descriptor - effective Host descriptor.
23
+ * @param t - command-namespace translator.
24
+ * @returns localized spelling for a known definition, otherwise its registered name.
25
+ */
26
+ export declare function claimToken(descriptor: CommandDescriptor, t: TranslateNS<'command'>): string;
27
+ /**
28
+ * Resolve typed spelling against the current Session's effective definitions.
29
+ * @param token - typed name without its leading slash.
30
+ * @param descriptors - effective descriptors in the Session's ready catalog.
31
+ * @returns the matching descriptor; aliases never select an unrelated scoped override.
32
+ */
33
+ export declare function resolveCommand(token: string, descriptors: readonly CommandDescriptor[]): CommandDescriptor | undefined;
34
+ export {};
35
+ //# sourceMappingURL=resolution.d.ts.map
@@ -2,10 +2,12 @@
2
2
  * CommandUiRuntime (`ctx.commandUi`): the '/' command source over the
3
3
  * session-keyed directory, the client-contribution registry, and the
4
4
  * per-session popupSelect controllers. Candidate synthesis merges the host
5
- * catalog with contributions by availability, then position filtering and
6
- * the `/` menu's shared name ranking (ui-primitives `rankByName`); a
7
- * host/contribution name collision fails loud. Every execute
8
- * addresses the session's agent by sessionId — sessions are always
5
+ * catalog with contributions by availability, gives built-in Host rows their
6
+ * localized face (presentation.ts), then position-filters; an empty query
7
+ * lists the Add and Commands sections in usage order, a typed query ranks
8
+ * every row by the `/` menu's shared name-and-label ranking (ui-primitives
9
+ * `rankByName`). A host/contribution name collision fails loud. Every
10
+ * execute addresses the session's agent by sessionId — sessions are always
9
11
  * agent-backed.
10
12
  */
11
13
  import { Service } from '@deepseek-ai/cordis';
@@ -56,6 +58,12 @@ export declare class CommandUiRuntime extends Service implements CommandUiContra
56
58
  * @returns the disposer removing the registration.
57
59
  */
58
60
  decorate(decoration: CommandDecoration): () => void;
61
+ /**
62
+ * Close every open popup for a command whose options have become stale.
63
+ * Pending loads and confirmations lose their binding; drafts stay intact.
64
+ * @param name - command name without the leading slash.
65
+ */
66
+ dismiss(name: string): void;
59
67
  /**
60
68
  * Resolve the per-session popup controller (lazy; dies with the session
61
69
  * scope). The controller's consume callback dispatches the scoped
@@ -74,10 +82,12 @@ export declare class CommandUiRuntime extends Service implements CommandUiContra
74
82
  * @returns the unbind disposer.
75
83
  */
76
84
  bindComposerFocus(id: SessionId, focus: () => void): () => void;
77
- /** Menu candidates: host catalog + contribution availability, then position filtering and the shared name ranking. */
85
+ /**
86
+ * Menu candidates: host catalog + contribution availability, built-in rows
87
+ * localized, then position filtering; sections for an empty query, the
88
+ * shared name-and-label ranking for a typed one.
89
+ */
78
90
  private candidates;
79
- /** Translate exact built-in Host copy while preserving scoped or third-party descriptors verbatim. */
80
- private hostDescription;
81
91
  /** Decision table, menu column: contribution/decorated-host → popup or action; host input → claim; host bare → detached execute. */
82
92
  private dispatch;
83
93
  /** Decision table, space column: hot-key sync check; only host leadingInput claims. */
@@ -94,6 +104,9 @@ export declare class CommandUiRuntime extends Service implements CommandUiContra
94
104
  * refusal so the machine surfaces one composer notice and the draft and
95
105
  * attachments stay in place; nothing executes and nothing is dropped. An
96
106
  * action submits nothing and runs regardless.
107
+ *
108
+ * A typed token is resolved through the localized claim tokens, so a line
109
+ * written as `/计划` reaches the `plan` descriptor and executes as `/plan`.
97
110
  */
98
111
  private matchEnter;
99
112
  /**
@@ -101,7 +114,13 @@ export declare class CommandUiRuntime extends Service implements CommandUiContra
101
114
  * session's popup, or consume the token and run the action.
102
115
  */
103
116
  private invoke;
104
- /** Build the leadingInput claim: token `/name ` + the command.execute submit transaction. */
117
+ /**
118
+ * Build the leadingInput claim. The composer keeps the claimed token in
119
+ * the draft and reads the arguments after it, so the token is the spelling
120
+ * the draft will carry: the locale's token for a menu pick, the typed
121
+ * spelling for Space and Enter. The command.execute submit transaction
122
+ * always sends the catalog name.
123
+ */
105
124
  private leadingClaim;
106
125
  /**
107
126
  * The command.execute transaction, addressed to the session's agent — pure
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.5-rc.2",
4
+ "version": "0.1.6-alpha.1",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -44,21 +44,21 @@
44
44
  "@types/react": "~18.3.1",
45
45
  "react": "^18.2.0",
46
46
  "clsx": "^2.0.0",
47
- "@deepseek-ai/dsh-api-remotes": "^0.1.5-rc.2",
48
- "@deepseek-ai/dsh-client-connection": "^0.1.5-rc.2",
49
- "@deepseek-ai/dsh-client-locale": "^0.1.5-rc.2",
50
- "@deepseek-ai/dsh-client-test-runtime": "^0.1.5-rc.2",
51
- "@deepseek-ai/dsh-client-ui-conversation": "^0.1.5-rc.2",
52
- "@deepseek-ai/dsh-client-ui-primitives": "^0.1.5-rc.2",
53
- "@deepseek-ai/dsh-client-ui-slots": "^0.1.5-rc.2",
54
- "@deepseek-ai/dsh-commands": "^0.1.5-rc.2",
47
+ "@deepseek-ai/dsh-api-remotes": "^0.1.6-alpha.1",
48
+ "@deepseek-ai/dsh-client-locale": "^0.1.6-alpha.1",
49
+ "@deepseek-ai/dsh-client-test-runtime": "^0.1.6-alpha.1",
50
+ "@deepseek-ai/dsh-client-ui-conversation": "^0.1.6-alpha.1",
51
+ "@deepseek-ai/dsh-client-ui-primitives": "^0.1.6-alpha.1",
52
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.6-alpha.1",
53
+ "@deepseek-ai/dsh-client-connection": "^0.1.6-alpha.1",
54
+ "@deepseek-ai/dsh-commands": "^0.1.6-alpha.1",
55
55
  "@deepseek-ai/cordis": "^4.0.2",
56
- "@deepseek-ai/dsh-api-session-controller": "^0.1.5-rc.2",
57
- "@deepseek-ai/dsh-client-store": "^0.1.5-rc.2",
58
- "@deepseek-ai/dsh-session": "^0.1.5-rc.2",
59
- "@deepseek-ai/dsh-client-ui-renderer": "^0.1.5-rc.2",
60
- "@deepseek-ai/dsh-client-ui-input-trigger": "^0.1.5-rc.2",
61
- "@deepseek-ai/dsh-client-ui-session": "^0.1.5-rc.2"
56
+ "@deepseek-ai/dsh-api-session-controller": "^0.1.6-alpha.1",
57
+ "@deepseek-ai/dsh-client-store": "^0.1.6-alpha.1",
58
+ "@deepseek-ai/dsh-session": "^0.1.6-alpha.1",
59
+ "@deepseek-ai/dsh-client-ui-input-trigger": "^0.1.6-alpha.1",
60
+ "@deepseek-ai/dsh-client-ui-session": "^0.1.6-alpha.1",
61
+ "@deepseek-ai/dsh-client-ui-renderer": "^0.1.6-alpha.1"
62
62
  },
63
63
  "files": [
64
64
  "lib/index.js",