@deepseek-ai/dsh-client-ui-commands 0.1.5-rc.2 → 0.1.6-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: c5f3c55c08a6df8e6c3f122d0c5e341721c2b41a
6
- README.zh.md: f728c03e067f74ba5142464adc17531225034aad
5
+ README.md: f8cba29179f9960042a4a9e67e86496591a76d08
6
+ README.zh.md: 1cea3ae7aea857f2b2d219abf0a7192c2f222792
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 popup holds composer focus: typing filters the loaded rows locally, `↑`/`↓` walk them, Enter and `Tab` accept the highlighted row, and Escape and `Shift+Tab` return to the composer. The highlight opens on the row the options mark as the session's current value, so accepting on a freshly opened panel confirms it. 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 焦点:键入即在已加载的行上本地筛选,`↑`/`↓` 在行间移动,回车与 `Tab` 接受高亮行,Escape 与 `Shift+Tab` 把焦点还给 composer。高亮落在选项标记为会话当前值的行上,因此在刚打开的弹窗上接受即确认当前值。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,181 @@ 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 ../../util/values/src/index.ts
13
+ /**
14
+ * Weak-key lookup with a strongly retained iterable set of associated values.
15
+ *
16
+ * Each value must belong to only one key. The container performs no automatic
17
+ * cleanup; owners delete associations or clear the container at lifecycle end.
18
+ */
19
+ var WeakMapWithValues = class {
20
+ keys = /* @__PURE__ */ new WeakMap();
21
+ valueSet = /* @__PURE__ */ new Set();
22
+ /** Live strongly retained values in insertion order. */
23
+ values = this.valueSet;
24
+ /**
25
+ * Read the value associated with a key.
26
+ * @param key - weakly held lookup key.
27
+ * @returns the associated value, or absence.
28
+ */
29
+ get(key) {
30
+ return this.keys.get(key);
31
+ }
32
+ /**
33
+ * Test whether a key has an association.
34
+ * @param key - weakly held lookup key.
35
+ * @returns whether the key is present.
36
+ */
37
+ has(key) {
38
+ return this.keys.has(key);
39
+ }
40
+ /**
41
+ * Associate one key with one caller-unique value.
42
+ * @param key - weakly held lookup key.
43
+ * @param value - strongly retained value that belongs to no other key.
44
+ * @returns this container.
45
+ */
46
+ set(key, value) {
47
+ if (this.keys.has(key)) {
48
+ const previous = this.keys.get(key);
49
+ if (previous === value) return this;
50
+ this.valueSet.delete(previous);
51
+ }
52
+ this.keys.set(key, value);
53
+ this.valueSet.add(value);
54
+ return this;
55
+ }
56
+ /**
57
+ * Remove one association and its strongly retained value.
58
+ * @param key - weakly held lookup key.
59
+ * @returns whether an association was removed.
60
+ */
61
+ delete(key) {
62
+ if (!this.keys.has(key)) return false;
63
+ const value = this.keys.get(key);
64
+ const deleted = this.keys.delete(key);
65
+ this.valueSet.delete(value);
66
+ return deleted;
67
+ }
68
+ /** Remove every association and strongly retained value. */
69
+ clear() {
70
+ this.keys = /* @__PURE__ */ new WeakMap();
71
+ this.valueSet.clear();
72
+ }
73
+ };
74
+ //#endregion
75
+ //#region lib/types/client/locales.js
76
+ /**
77
+ * `command` namespace dictionaries: the composer menu's section headings,
78
+ * the client face (title, description, claim token) of the built-in Host
79
+ * commands whose catalog descriptors carry English text only, and the
80
+ * popupSelect shell's copy.
81
+ */
82
+ /** Simplified Chinese dictionary (the key-set source of truth). */
83
+ const zh = {
84
+ "section.add": "添加",
85
+ "section.commands": "指令",
86
+ "label.goal": "目标",
87
+ "label.plan": "计划",
88
+ "label.feedback": "反馈",
89
+ "label.compact": "压缩",
90
+ "label.permission": "权限",
91
+ "label.export": "下载日志",
92
+ "description.goal": "设置或查看长期任务目标",
93
+ "description.plan": "进入或退出计划模式",
94
+ "description.feedback": "发送关于当前会话的反馈",
95
+ "description.compact": "压缩以上对话内容",
96
+ "description.permission": "切换权限预设(沙箱模式与审批策略)",
97
+ "description.export": "将当前会话内容导出为 ZIP",
98
+ "token.goal": "目标",
99
+ "token.plan": "计划",
100
+ "token.feedback": "反馈",
101
+ "token.compact": "压缩",
102
+ "token.permission": "权限",
103
+ "token.export": "导出",
104
+ "search.placeholder": "搜索…",
105
+ "search.aria": "筛选选项",
106
+ "status.loading": "正在加载选项…",
107
+ "status.applying": "正在应用…",
108
+ "status.empty": "无选项",
109
+ "overlay.aria": "/{command} 选项",
110
+ "listbox.aria": "/{command} 匹配项",
111
+ "notice.attachmentsUnsupported": "/{command} 不接受附件,请先移除附件"
112
+ };
113
+ /** English dictionary, checked complete against the zh key set. */
114
+ const en = {
115
+ "section.add": "Add",
116
+ "section.commands": "Commands",
117
+ "label.goal": "Goal",
118
+ "label.plan": "Plan",
119
+ "label.feedback": "Feedback",
120
+ "label.compact": "Compact",
121
+ "label.permission": "Permission",
122
+ "label.export": "Export",
123
+ "description.goal": "Set or view the goal for a long-running task",
124
+ "description.plan": "Enter or leave plan mode",
125
+ "description.feedback": "Record feedback about this session",
126
+ "description.compact": "Compact older conversation history",
127
+ "description.permission": "Switch the permission preset (sandbox mode + approval policy)",
128
+ "description.export": "Download this Session log as a ZIP archive",
129
+ "token.goal": "goal",
130
+ "token.plan": "plan",
131
+ "token.feedback": "feedback",
132
+ "token.compact": "compact",
133
+ "token.permission": "permission",
134
+ "token.export": "export",
135
+ "search.placeholder": "Search…",
136
+ "search.aria": "Filter options",
137
+ "status.loading": "Loading options…",
138
+ "status.applying": "Applying…",
139
+ "status.empty": "No options",
140
+ "overlay.aria": "/{command} options",
141
+ "listbox.aria": "/{command} matches",
142
+ "notice.attachmentsUnsupported": "/{command} does not accept attachments; remove them first"
143
+ };
144
+ //#endregion
145
+ //#region lib/types/client/resolution.js
146
+ const BUILTINS = {
147
+ goal: "@deepseek-ai/dsh-command-goal",
148
+ plan: "@deepseek-ai/dsh-plan-mode",
149
+ feedback: "@deepseek-ai/dsh-command-feedback",
150
+ compact: "@deepseek-ai/dsh-command-compact",
151
+ permission: "@deepseek-ai/dsh-permission-presets",
152
+ export: "@deepseek-ai/dsh-session-log-export"
153
+ };
154
+ /**
155
+ * Identify a first-party definition without interpreting its display copy.
156
+ * @param descriptor - effective Host descriptor after scoped shadowing.
157
+ * @returns its first-party name, or undefined for another definition.
158
+ */
159
+ function builtinCommandName(descriptor) {
160
+ return Object.keys(BUILTINS).find((name) => descriptor.definitionId === BUILTINS[name]);
161
+ }
162
+ /**
163
+ * Select the input spelling for a menu-picked command.
164
+ * @param descriptor - effective Host descriptor.
165
+ * @param t - command-namespace translator.
166
+ * @returns localized spelling for a known definition, otherwise its registered name.
167
+ */
168
+ function claimToken(descriptor, t) {
169
+ const name = builtinCommandName(descriptor);
170
+ return name === void 0 ? descriptor.name : t(`token.${name}`);
171
+ }
172
+ const TOKEN_ALIASES = new Map(Object.keys(BUILTINS).flatMap((name) => [zh[`token.${name}`], en[`token.${name}`]].map((token) => [token, name])));
173
+ /**
174
+ * Resolve typed spelling against the current Session's effective definitions.
175
+ * @param token - typed name without its leading slash.
176
+ * @param descriptors - effective descriptors in the Session's ready catalog.
177
+ * @returns the matching descriptor; aliases never select an unrelated scoped override.
178
+ */
179
+ function resolveCommand(token, descriptors) {
180
+ const exact = descriptors.find((descriptor) => descriptor.name === token);
181
+ if (exact !== void 0) return exact;
182
+ const name = TOKEN_ALIASES.get(token);
183
+ if (name === void 0) return void 0;
184
+ return descriptors.find((descriptor) => descriptor.definitionId === BUILTINS[name]);
185
+ }
186
+ //#endregion
12
187
  //#region lib/types/client/directory.js
13
188
  /** One session key's cache cell. */
14
189
  var Entry = class {
@@ -35,15 +210,15 @@ window.__ModuleLoader__.load({
35
210
  return this.entries.get(sessionId)?.state ?? "cold";
36
211
  }
37
212
  /**
38
- * Synchronous exact-name lookup over one session's hot snapshot.
213
+ * Synchronous command lookup over one Session's ready catalog; exact names precede localized aliases.
39
214
  * @param sessionId - session key.
40
- * @param name - command name without the leading slash.
215
+ * @param name - typed command spelling without the leading slash.
41
216
  * @returns the descriptor, or undefined when absent or the entry is not ready.
42
217
  */
43
218
  resolve(sessionId, name) {
44
219
  const entry = this.entries.get(sessionId);
45
220
  if (entry === void 0 || entry.state !== "ready") return void 0;
46
- return entry.commands.find((c) => c.name === name);
221
+ return resolveCommand(name, entry.commands);
47
222
  }
48
223
  /** Soft invalidation (commands-changed): background repull on every touched key; ready snapshots keep serving. */
49
224
  invalidateAll() {
@@ -159,43 +334,6 @@ window.__ModuleLoader__.load({
159
334
  return signal.reason instanceof Error ? signal.reason : /* @__PURE__ */ new Error("command directory wait aborted");
160
335
  }
161
336
  //#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
337
  //#region lib/types/client/popup.js
200
338
  /**
201
339
  * Headless popupSelect shell state: one controller per client
@@ -232,6 +370,19 @@ window.__ModuleLoader__.load({
232
370
  if (query === "") return options;
233
371
  return options.filter((o) => o.label.toLowerCase().includes(query) || (o.detail?.toLowerCase().includes(query) ?? false));
234
372
  }
373
+ /**
374
+ * Highlight index for a freshly loaded row list: the row marked as the current
375
+ * value when the live search still shows it, else the top row. Opening parks
376
+ * the highlight on the value the session already uses, so an accept gesture
377
+ * made without looking confirms that value instead of the topmost row.
378
+ * @param options - the loaded rows.
379
+ * @param search - the shell's live filter text (non-empty after a retry).
380
+ * @returns index into the filtered rows.
381
+ */
382
+ function currentIndex(options, search) {
383
+ const at = filterOptions(options, search).findIndex((option) => option.active === true);
384
+ return at === -1 ? 0 : at;
385
+ }
235
386
  /** The shell's error-strip line for a settlement failure. */
236
387
  function errorText(error) {
237
388
  return error instanceof Error ? error.message : String(error);
@@ -283,11 +434,12 @@ window.__ModuleLoader__.load({
283
434
  load(binding) {
284
435
  binding.spec.options(binding.context, binding.abort.signal).then((options) => {
285
436
  if (this.binding !== binding) return;
437
+ const current = this.state.getSnapshot();
286
438
  this.state.set({
287
- ...this.state.getSnapshot(),
439
+ ...current,
288
440
  status: "ready",
289
441
  options,
290
- active: 0,
442
+ active: currentIndex(options, current.search),
291
443
  error: null
292
444
  });
293
445
  }, (error) => {
@@ -316,7 +468,8 @@ window.__ModuleLoader__.load({
316
468
  }
317
469
  /**
318
470
  * Replace the local search text (pure local filter — the provider is never
319
- * re-queried) and rebase the highlight onto the new filtered list.
471
+ * re-queried) and rebase the highlight to the top of the new filtered list:
472
+ * typing searches for something other than the current value.
320
473
  * @param search - the shell search input's text.
321
474
  */
322
475
  setSearch(search) {
@@ -464,15 +617,92 @@ window.__ModuleLoader__.load({
464
617
  }
465
618
  };
466
619
  //#endregion
620
+ //#region lib/types/client/presentation.js
621
+ /** Row names per section, highest usage first; rows outside both lists close the Commands section in catalog order. */
622
+ const SECTION_ROWS = {
623
+ add: [
624
+ "file",
625
+ "goal",
626
+ "plan",
627
+ "feedback"
628
+ ],
629
+ commands: [
630
+ "compact",
631
+ "permission",
632
+ "model",
633
+ "export"
634
+ ]
635
+ };
636
+ /** One built-in Host command's face, keyed by its dictionary entries. */
637
+ function hostFace(name, icon) {
638
+ return [name, {
639
+ label: `label.${name}`,
640
+ description: `description.${name}`,
641
+ icon
642
+ }];
643
+ }
644
+ /** Built-in Host commands whose client face this package owns. */
645
+ const HOST_FACES = new Map([
646
+ hostFace("goal", _deepseek_ai_dsh_client_ui_primitives.IconGoalOutline16),
647
+ hostFace("plan", _deepseek_ai_dsh_client_ui_primitives.IconPlanOutline14),
648
+ hostFace("feedback", _deepseek_ai_dsh_client_ui_primitives.IconPaperPlaneOutline14),
649
+ hostFace("compact", _deepseek_ai_dsh_client_ui_primitives.IconCompactOutline16),
650
+ hostFace("permission", _deepseek_ai_dsh_client_ui_primitives.IconShieldOutline16),
651
+ hostFace("export", _deepseek_ai_dsh_client_ui_primitives.IconDownloadOutline16)
652
+ ]);
653
+ /**
654
+ * The localized menu face of a catalog row.
655
+ * @param descriptor - effective Host command descriptor.
656
+ * @param t - the `command` namespace translator.
657
+ * @returns title, description, and glyph for a built-in command; undefined
658
+ * for any other row, which keeps its catalog description.
659
+ */
660
+ function builtinRowFace(descriptor, t) {
661
+ const name = builtinCommandName(descriptor);
662
+ const face = name === void 0 ? void 0 : HOST_FACES.get(name);
663
+ return face === void 0 ? void 0 : {
664
+ label: t(face.label),
665
+ description: t(face.description),
666
+ icon: face.icon
667
+ };
668
+ }
669
+ /**
670
+ * Arrange the empty-query menu: the Add section, then the Commands section,
671
+ * each in usage order, with unlisted rows closing Commands in their input
672
+ * order; each row carries its section heading.
673
+ * @param rows - the visible candidates in catalog-then-contribution order.
674
+ * @param t - the `command` namespace translator.
675
+ * @returns the sectioned rows.
676
+ */
677
+ function sectionRows(rows, t) {
678
+ const listed = new Set([...SECTION_ROWS.add, ...SECTION_ROWS.commands]);
679
+ const byName = new Map(rows.map((row) => [row.name, row]));
680
+ const pick = (names) => names.flatMap((name) => {
681
+ const row = byName.get(name);
682
+ return row === void 0 ? [] : [row];
683
+ });
684
+ const add = pick(SECTION_ROWS.add).map((row) => ({
685
+ ...row,
686
+ section: t("section.add")
687
+ }));
688
+ const commands = [...pick(SECTION_ROWS.commands), ...rows.filter((row) => !listed.has(row.name))].map((row) => ({
689
+ ...row,
690
+ section: t("section.commands")
691
+ }));
692
+ return [...add, ...commands];
693
+ }
694
+ //#endregion
467
695
  //#region lib/types/client/service.js
468
696
  /**
469
697
  * CommandUiRuntime (`ctx.commandUi`): the '/' command source over the
470
698
  * session-keyed directory, the client-contribution registry, and the
471
699
  * 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
700
+ * catalog with contributions by availability, gives built-in Host rows their
701
+ * localized face (presentation.ts), then position-filters; an empty query
702
+ * lists the Add and Commands sections in usage order, a typed query ranks
703
+ * every row by the `/` menu's shared name-and-label ranking (ui-primitives
704
+ * `rankByName`). A host/contribution name collision fails loud. Every
705
+ * execute addresses the session's agent by sessionId — sessions are always
476
706
  * agent-backed.
477
707
  */
478
708
  /** Recover the command name from a line the Host confirmed as executed. */
@@ -481,15 +711,6 @@ window.__ModuleLoader__.load({
481
711
  const separator = trimmed.search(/\s/u);
482
712
  return (separator === -1 ? trimmed : trimmed.slice(0, separator)).slice(1);
483
713
  }
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
714
  /** Command surface: session-keyed directory + '/' source + contribution registry + per-session popups. */
494
715
  var CommandUiRuntime = class extends _deepseek_ai_cordis.Service {
495
716
  static inject = [
@@ -502,7 +723,7 @@ window.__ModuleLoader__.load({
502
723
  live = {
503
724
  contributions: /* @__PURE__ */ new Map(),
504
725
  decorations: /* @__PURE__ */ new Map(),
505
- popups: /* @__PURE__ */ new Map()
726
+ popups: new WeakMapWithValues()
506
727
  };
507
728
  /** `command`-namespace translator (composer refusal notices). */
508
729
  t;
@@ -583,21 +804,32 @@ window.__ModuleLoader__.load({
583
804
  };
584
805
  }
585
806
  /**
807
+ * Close every open popup for a command whose options have become stale.
808
+ * Pending loads and confirmations lose their binding; drafts stay intact.
809
+ * @param name - command name without the leading slash.
810
+ */
811
+ dismiss(name) {
812
+ for (const popup of this.live.popups.values) if (popup.state.getSnapshot().command === name) popup.dismiss({ focusComposer: true });
813
+ }
814
+ /**
586
815
  * Resolve the per-session popup controller (lazy; dies with the session
587
816
  * scope). The controller's consume callback dispatches the scoped
588
817
  * consume-token event back to this session; focusComposer reaches the
589
- * composer through the overlay slot currency.
818
+ * session's composer through the conversation input face.
590
819
  * @param actx - session-scope ctx.
591
820
  * @returns the resident controller.
821
+ * @throws when the Context no longer belongs to a retained Session generation.
592
822
  */
593
823
  popupFor(actx) {
594
- const id = this.sessions().scopeOf(actx);
595
- if (id === void 0) throw new Error("command.popupFor requires a session scope");
824
+ const sessions = this.sessions();
825
+ const session = sessions.sessionOf(actx);
826
+ const binding = session === void 0 ? void 0 : sessions.binding(session.sessionId);
827
+ if (binding === void 0 || binding.session !== session) throw new Error("command.popupFor requires a retained Session scope");
596
828
  const { popups } = this.live;
597
- const existing = popups.get(id);
829
+ const existing = popups.get(binding);
598
830
  if (existing !== void 0) return existing;
599
831
  const controller = new PopupSelectController({
600
- consume: (segment) => actx.bail(actx, "slash/input-consume-token", { guard: segment.via === "menu" ? {
832
+ consume: (segment) => binding.ctx.bail(binding.ctx, "slash/input-consume-token", { guard: segment.via === "menu" ? {
601
833
  kind: "span",
602
834
  span: segment.span
603
835
  } : {
@@ -605,32 +837,21 @@ window.__ModuleLoader__.load({
605
837
  token: segment.token
606
838
  } }) === true,
607
839
  focusComposer: () => {
608
- this.focusHooks.get(id)?.();
840
+ binding.ctx.get("conversation")?.input.for(binding.ctx).focus();
609
841
  }
610
842
  });
611
- popups.set(id, controller);
612
- actx.effect(() => () => {
843
+ popups.set(binding, controller);
844
+ binding.ctx.effect(() => () => {
613
845
  controller.dispose();
614
- popups.delete(id);
615
- this.focusHooks.delete(id);
846
+ popups.delete(binding);
616
847
  }, "command: session popup");
617
848
  return controller;
618
849
  }
619
- /** Composer focus hooks by session (the overlay wiring binds the textarea focus here). */
620
- focusHooks = /* @__PURE__ */ new Map();
621
850
  /**
622
- * Bind one session's composer-focus hook (overlay slot wiring; unbind on unmount).
623
- * @param id - session id.
624
- * @param focus - textarea focus callback.
625
- * @returns the unbind disposer.
851
+ * Menu candidates: host catalog + contribution availability, built-in rows
852
+ * localized, then position filtering; sections for an empty query, the
853
+ * shared name-and-label ranking for a typed one.
626
854
  */
627
- bindComposerFocus(id, focus) {
628
- this.focusHooks.set(id, focus);
629
- return () => {
630
- if (this.focusHooks.get(id) === focus) this.focusHooks.delete(id);
631
- };
632
- }
633
- /** Menu candidates: host catalog + contribution availability, then position filtering and the shared name ranking. */
634
855
  async candidates(session, req) {
635
856
  const list = await this.directory.ensureReady(session.sessionId, req.signal);
636
857
  const rows = [];
@@ -639,7 +860,7 @@ window.__ModuleLoader__.load({
639
860
  seen.add(c.name);
640
861
  rows.push({
641
862
  name: c.name,
642
- description: this.hostDescription(c),
863
+ ...builtinRowFace(c, this.t) ?? { description: c.description },
643
864
  ...c.input !== void 0 ? { hint: c.input.hint } : {}
644
865
  });
645
866
  }
@@ -648,15 +869,13 @@ window.__ModuleLoader__.load({
648
869
  if (seen.has(contribution.name)) throw new Error(`ui-commands: contribution /${contribution.name} collides with a host command`);
649
870
  rows.push({
650
871
  name: contribution.name,
651
- description: contribution.description()
872
+ ...contribution.label === void 0 ? {} : { label: contribution.label() },
873
+ ...contribution.description === void 0 ? {} : { description: contribution.description() },
874
+ ...contribution.icon === void 0 ? {} : { icon: contribution.icon }
652
875
  });
653
876
  }
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;
877
+ const visible = rows.filter((c) => req.position === "leading" || c.hint === void 0);
878
+ return req.query === "" ? sectionRows(visible, this.t) : (0, _deepseek_ai_dsh_client_ui_primitives.rankByName)(visible, req.query);
660
879
  }
661
880
  /** Decision table, menu column: contribution/decorated-host → popup or action; host input → claim; host bare → detached execute. */
662
881
  dispatch(pick) {
@@ -679,7 +898,7 @@ window.__ModuleLoader__.load({
679
898
  });
680
899
  return "handled";
681
900
  }
682
- if (desc.input !== void 0) return { claim: this.leadingClaim(desc, pick.session) };
901
+ if (desc.input !== void 0) return { claim: this.leadingClaim(desc, pick.session, claimToken(desc, this.t)) };
683
902
  this.consumeVia(pick.session.sessionId, {
684
903
  via: "menu",
685
904
  span: pick.span
@@ -690,11 +909,10 @@ window.__ModuleLoader__.load({
690
909
  /** Decision table, space column: hot-key sync check; only host leadingInput claims. */
691
910
  matchSpace(session, token) {
692
911
  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);
912
+ if (this.live.contributions.has(token.slice(1))) return void 0;
913
+ const desc = this.directory.resolve(session.sessionId, token.slice(1));
696
914
  if (desc === void 0 || desc.input === void 0) return void 0;
697
- return { claim: this.leadingClaim(desc, session) };
915
+ return { claim: this.leadingClaim(desc, session, token.slice(1)) };
698
916
  }
699
917
  /**
700
918
  * Decision table, enter column. Strong-waits the session's catalog (a
@@ -708,6 +926,9 @@ window.__ModuleLoader__.load({
708
926
  * refusal so the machine surfaces one composer notice and the draft and
709
927
  * attachments stay in place; nothing executes and nothing is dropped. An
710
928
  * action submits nothing and runs regardless.
929
+ *
930
+ * A typed token is resolved through the localized claim tokens, so a line
931
+ * written as `/计划` reaches the `plan` descriptor and executes as `/plan`.
711
932
  */
712
933
  async matchEnter(session, line, signal, envelope) {
713
934
  const trimmed = line.trim();
@@ -715,24 +936,26 @@ window.__ModuleLoader__.load({
715
936
  const ws = trimmed.search(/\s/);
716
937
  const token = ws === -1 ? trimmed : trimmed.slice(0, ws);
717
938
  const bare = ws === -1;
718
- const name = token.slice(1);
719
- if (name === "") return void 0;
939
+ const typedName = token.slice(1);
940
+ if (typedName === "") return void 0;
720
941
  const refuseAttachments = () => {
721
- throw new Error(this.t("notice.attachmentsUnsupported", { command: name }));
942
+ throw new Error(this.t("notice.attachmentsUnsupported", { command: typedName }));
722
943
  };
723
- const contribution = this.live.contributions.get(name);
944
+ const contribution = this.live.contributions.get(typedName);
724
945
  if (contribution !== void 0 && contribution.available(session)) {
725
946
  if (!bare) return void 0;
726
947
  if (envelope.attachments > 0 && contribution.ui.kind !== "action") refuseAttachments();
727
- this.invoke(name, contribution.ui, session, {
948
+ this.invoke(typedName, contribution.ui, session, {
728
949
  via: "enter",
729
950
  token
730
951
  });
731
952
  return "handled";
732
953
  }
733
954
  await this.directory.ensureReady(session.sessionId, signal);
734
- const desc = this.directory.resolve(session.sessionId, name);
955
+ const desc = this.directory.resolve(session.sessionId, typedName);
735
956
  if (desc === void 0) return void 0;
957
+ const name = desc.name;
958
+ const canonical = `/${name}${trimmed.slice(token.length)}`;
736
959
  if (bare) {
737
960
  const decoration = this.live.decorations.get(name);
738
961
  if (decoration !== void 0 && decoration.available(session)) {
@@ -746,7 +969,7 @@ window.__ModuleLoader__.load({
746
969
  }
747
970
  if (desc.input !== void 0) {
748
971
  if (envelope.attachments > 0 && desc.input.attachments !== true) refuseAttachments();
749
- return { claim: this.leadingClaim(desc, session) };
972
+ return { claim: this.leadingClaim(desc, session, token.slice(1)) };
750
973
  }
751
974
  if (!bare) return void 0;
752
975
  if (envelope.attachments > 0) refuseAttachments();
@@ -754,7 +977,7 @@ window.__ModuleLoader__.load({
754
977
  via: "enter",
755
978
  token
756
979
  });
757
- this.runDetached(desc, session, trimmed);
980
+ this.runDetached(desc, session, canonical);
758
981
  return "handled";
759
982
  }
760
983
  /**
@@ -771,14 +994,22 @@ window.__ModuleLoader__.load({
771
994
  if (actx === void 0) return;
772
995
  this.popupFor(actx).open(name, ui, session, segment);
773
996
  }
774
- /** Build the leadingInput claim: token `/name ` + the command.execute submit transaction. */
775
- leadingClaim(desc, session) {
776
- const token = `/${desc.name} `;
997
+ /**
998
+ * Build the leadingInput claim. The composer keeps the claimed token in
999
+ * the draft and reads the arguments after it, so the token is the spelling
1000
+ * the draft will carry: the locale's token for a menu pick, the typed
1001
+ * spelling for Space and Enter. The command.execute submit transaction
1002
+ * always sends the catalog name.
1003
+ */
1004
+ leadingClaim(desc, session, shown) {
1005
+ const token = `/${shown} `;
1006
+ const line = `/${desc.name} `;
777
1007
  return {
1008
+ name: desc.name,
778
1009
  token,
779
1010
  ...desc.input !== void 0 ? { hint: desc.input.hint } : {},
780
1011
  ...desc.input?.attachments === true ? { attachments: true } : {},
781
- submit: (args, _actx, attachments) => this.execute(session, token + args, attachments)
1012
+ submit: (args, _actx, attachments) => this.execute(session, line + args, attachments)
782
1013
  };
783
1014
  }
784
1015
  /**
@@ -890,7 +1121,7 @@ window.__ModuleLoader__.load({
890
1121
  }
891
1122
  //#endregion
892
1123
  //#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}";
1124
+ 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
1125
  const tagId = "@deepseek-ai/dsh-client-ui-commands/PopupSelectView.module.css";
895
1126
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
896
1127
  const tag = document.createElement("style");
@@ -900,12 +1131,14 @@ window.__ModuleLoader__.load({
900
1131
  document.head.appendChild(tag);
901
1132
  }
902
1133
  var PopupSelectView_module_css_default = {
1134
+ "badge": "mufS8W_badge",
903
1135
  "card": "mufS8W_card",
904
1136
  "check": "mufS8W_check",
905
1137
  "detail": "mufS8W_detail",
906
1138
  "error": "mufS8W_error",
907
1139
  "errorText": "mufS8W_errorText",
908
1140
  "label": "mufS8W_label",
1141
+ "labelText": "mufS8W_labelText",
909
1142
  "retry": "mufS8W_retry",
910
1143
  "row": "mufS8W_row",
911
1144
  "rowActive": "mufS8W_rowActive",
@@ -920,11 +1153,12 @@ window.__ModuleLoader__.load({
920
1153
  * store into the conversation.input.overlay anchor. Unlike the slash menu
921
1154
  * (combobox — textarea keeps focus), this shell HOLDS focus while open: the
922
1155
  * inner search input takes focus, plain typing filters the loaded options
923
- * locally, Enter/↑↓ drive the filtered highlight (scrolled into view), Escape
924
- * dismisses back to the composer, and ←→ keep the search input's native
925
- * caret. Any pointer interaction outside the box dismisses (the click's own
926
- * target takes focus). Closed state renders null; the overlay slot stays
927
- * mounted. The card height clamps to the space above the composer.
1156
+ * locally, Enter and Tab accept the filtered highlight, ↑↓ walk it (wrapping,
1157
+ * scrolled into view), and Escape and Shift+Tab dismiss back to the composer.
1158
+ * ←→ keep the search input's native caret. Any pointer interaction outside the
1159
+ * box dismisses (the click's own target takes focus). Closed state renders
1160
+ * null; the overlay slot stays mounted. The card height clamps to the space
1161
+ * above the composer.
928
1162
  */
929
1163
  /** Design cap on the card height (same MenuDropdown family as the slash menu). */
930
1164
  const MAX_HEIGHT = 320;
@@ -978,6 +1212,12 @@ window.__ModuleLoader__.load({
978
1212
  ev.preventDefault();
979
1213
  popup.select(state.active);
980
1214
  return;
1215
+ case "Tab":
1216
+ if (!ev.shiftKey && (state.status !== "ready" || rows.length === 0)) return;
1217
+ ev.preventDefault();
1218
+ if (ev.shiftKey) popup.dismiss({ focusComposer: true });
1219
+ else popup.select(state.active);
1220
+ return;
981
1221
  case "Escape":
982
1222
  ev.preventDefault();
983
1223
  popup.dismiss({ focusComposer: true });
@@ -1038,6 +1278,7 @@ window.__ModuleLoader__.load({
1038
1278
  children: rows.map((option, index) => (0, react_jsx_runtime.jsxs)("div", {
1039
1279
  role: "option",
1040
1280
  "aria-selected": index === state.active,
1281
+ "aria-label": option.badge === void 0 ? void 0 : `${option.label} ${option.badge}`,
1041
1282
  className: clsx(PopupSelectView_module_css_default.row, index === state.active && PopupSelectView_module_css_default.rowActive),
1042
1283
  onClick: () => {
1043
1284
  popup.select(index);
@@ -1046,9 +1287,15 @@ window.__ModuleLoader__.load({
1046
1287
  popup.highlight(index);
1047
1288
  },
1048
1289
  children: [
1049
- (0, react_jsx_runtime.jsx)("span", {
1290
+ (0, react_jsx_runtime.jsxs)("span", {
1050
1291
  className: PopupSelectView_module_css_default.label,
1051
- children: option.label
1292
+ children: [(0, react_jsx_runtime.jsx)("span", {
1293
+ className: PopupSelectView_module_css_default.labelText,
1294
+ children: option.label
1295
+ }), option.badge !== void 0 && (0, react_jsx_runtime.jsx)("sup", {
1296
+ className: PopupSelectView_module_css_default.badge,
1297
+ children: option.badge
1298
+ })]
1052
1299
  }),
1053
1300
  option.detail !== void 0 && (0, react_jsx_runtime.jsx)("span", {
1054
1301
  className: PopupSelectView_module_css_default.detail,
@@ -1095,8 +1342,7 @@ window.__ModuleLoader__.load({
1095
1342
  "locale"
1096
1343
  ];
1097
1344
  /**
1098
- * Client plugin body: mount the service, then register the popupSelect shell
1099
- * into the input overlay once its declarer is up.
1345
+ * Mount the command service and its per-session popupSelect overlay.
1100
1346
  * @param ctx - client root context.
1101
1347
  */
1102
1348
  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,7 +19,15 @@ 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;
25
+ /**
26
+ * The row the shell's highlight parks on when the panel opens, so an accept
27
+ * gesture made without looking confirms the value in use. A business package
28
+ * that marks a row `active` for presentation alone would make that row the
29
+ * default pick.
30
+ */
21
31
  readonly active?: boolean;
22
32
  /** Optional in-page risk gate owned by the shared popup shell. */
23
33
  readonly confirmation?: SelectConfirmation;
@@ -35,8 +45,7 @@ export interface PopupSelectSpec {
35
45
  }
36
46
  /**
37
47
  * 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
48
+ * consumes the trigger token and runs one client-side callback. It submits nothing, so an
40
49
  * attachment-carrying draft never refuses it.
41
50
  */
42
51
  export interface ActionSpec {
@@ -53,13 +62,18 @@ export type CommandUiSpec = PopupSelectSpec | ActionSpec;
53
62
  * One client-owned command contribution: a slash-menu entry whose behavior
54
63
  * lives entirely on the client (no host descriptor). Merged with the host
55
64
  * catalog by name — a collision with a host command fails loud at candidate
56
- * synthesis, never shadows.
65
+ * synthesis, never shadows. Row copy is read on every candidate pass, so a
66
+ * locale change reaches the next menu open without re-registration.
57
67
  */
58
68
  export interface CommandContribution {
59
69
  /** Command name without the leading slash (unique across contributions). */
60
70
  readonly name: string;
61
- /** Resolve the localized menu row description when candidates are requested. */
62
- readonly description: () => string;
71
+ /** Localized menu row title; the name itself when absent. */
72
+ label?(): string;
73
+ /** Localized menu row description; the row shows none when absent. */
74
+ description?(): string;
75
+ /** Menu row glyph from the shared icon set. */
76
+ readonly icon?: ComponentType<IconProps>;
63
77
  /** Capability filter, called with a fresh projection per candidate pass. */
64
78
  available(session: ClientSessionContext): boolean;
65
79
  /** The command's UI behavior. */
@@ -94,6 +108,8 @@ export interface CommandUiContract {
94
108
  * Duplicate names throw at registration.
95
109
  */
96
110
  decorate(decoration: CommandDecoration): () => void;
111
+ /** Close this command's open popups and confirmations without consuming composer drafts. */
112
+ dismiss(name: string): void;
97
113
  /** Resolve the per-session popup controller for one session scope (wiring/overlay layer). */
98
114
  popupFor(actx: ClientContext): unknown;
99
115
  }
@@ -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;
@@ -51,7 +51,12 @@ export interface PopupState {
51
51
  readonly options: readonly SelectOption[];
52
52
  /** Local filter text over the loaded options. */
53
53
  readonly search: string;
54
- /** Highlight index into the filtered row list (0 when empty/pending). */
54
+ /**
55
+ * Highlight index into the filtered row list: 0 until options land; afterwards
56
+ * the row the loaded list marks as the current value
57
+ * ({@link SelectOption.active}), else 0. A search rebases it to the top of the
58
+ * filtered rows.
59
+ */
55
60
  readonly active: number;
56
61
  /** A select() settlement is in flight: further select/search/highlight no-op until it settles. */
57
62
  readonly submitting: boolean;
@@ -101,7 +106,8 @@ export declare class PopupSelectController<TCtx = unknown> {
101
106
  retry(): void;
102
107
  /**
103
108
  * Replace the local search text (pure local filter — the provider is never
104
- * re-queried) and rebase the highlight onto the new filtered list.
109
+ * re-queried) and rebase the highlight to the top of the new filtered list:
110
+ * typing searches for something other than the current value.
105
111
  * @param search - the shell search input's text.
106
112
  */
107
113
  setSearch(search: string): void;
@@ -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,28 +58,28 @@ 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
62
70
  * consume-token event back to this session; focusComposer reaches the
63
- * composer through the overlay slot currency.
71
+ * session's composer through the conversation input face.
64
72
  * @param actx - session-scope ctx.
65
73
  * @returns the resident controller.
74
+ * @throws when the Context no longer belongs to a retained Session generation.
66
75
  */
67
76
  popupFor(actx: ClientContext): PopupSelectController<ClientSessionContext>;
68
- /** Composer focus hooks by session (the overlay wiring binds the textarea focus here). */
69
- private readonly focusHooks;
70
77
  /**
71
- * Bind one session's composer-focus hook (overlay slot wiring; unbind on unmount).
72
- * @param id - session id.
73
- * @param focus - textarea focus callback.
74
- * @returns the unbind disposer.
78
+ * Menu candidates: host catalog + contribution availability, built-in rows
79
+ * localized, then position filtering; sections for an empty query, the
80
+ * shared name-and-label ranking for a typed one.
75
81
  */
76
- bindComposerFocus(id: SessionId, focus: () => void): () => void;
77
- /** Menu candidates: host catalog + contribution availability, then position filtering and the shared name ranking. */
78
82
  private candidates;
79
- /** Translate exact built-in Host copy while preserving scoped or third-party descriptors verbatim. */
80
- private hostDescription;
81
83
  /** Decision table, menu column: contribution/decorated-host → popup or action; host input → claim; host bare → detached execute. */
82
84
  private dispatch;
83
85
  /** Decision table, space column: hot-key sync check; only host leadingInput claims. */
@@ -94,6 +96,9 @@ export declare class CommandUiRuntime extends Service implements CommandUiContra
94
96
  * refusal so the machine surfaces one composer notice and the draft and
95
97
  * attachments stay in place; nothing executes and nothing is dropped. An
96
98
  * action submits nothing and runs regardless.
99
+ *
100
+ * A typed token is resolved through the localized claim tokens, so a line
101
+ * written as `/计划` reaches the `plan` descriptor and executes as `/plan`.
97
102
  */
98
103
  private matchEnter;
99
104
  /**
@@ -101,7 +106,13 @@ export declare class CommandUiRuntime extends Service implements CommandUiContra
101
106
  * session's popup, or consume the token and run the action.
102
107
  */
103
108
  private invoke;
104
- /** Build the leadingInput claim: token `/name ` + the command.execute submit transaction. */
109
+ /**
110
+ * Build the leadingInput claim. The composer keeps the claimed token in
111
+ * the draft and reads the arguments after it, so the token is the spelling
112
+ * the draft will carry: the locale's token for a menu pick, the typed
113
+ * spelling for Space and Enter. The command.execute submit transaction
114
+ * always sends the catalog name.
115
+ */
105
116
  private leadingClaim;
106
117
  /**
107
118
  * 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.2",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -44,21 +44,22 @@
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.2",
48
+ "@deepseek-ai/dsh-client-connection": "^0.1.6-alpha.2",
49
+ "@deepseek-ai/dsh-client-locale": "^0.1.6-alpha.2",
50
+ "@deepseek-ai/dsh-client-test-runtime": "^0.1.6-alpha.2",
51
+ "@deepseek-ai/dsh-client-ui-conversation": "^0.1.6-alpha.2",
52
+ "@deepseek-ai/dsh-client-ui-primitives": "^0.1.6-alpha.2",
53
+ "@deepseek-ai/dsh-client-ui-input-trigger": "^0.1.6-alpha.2",
54
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.6-alpha.2",
55
+ "@deepseek-ai/dsh-commands": "^0.1.6-alpha.2",
55
56
  "@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"
57
+ "@deepseek-ai/dsh-api-session-controller": "^0.1.6-alpha.2",
58
+ "@deepseek-ai/dsh-client-store": "^0.1.6-alpha.2",
59
+ "@deepseek-ai/dsh-session": "^0.1.6-alpha.2",
60
+ "@deepseek-ai/dsh-util-values": "^0.1.6-alpha.2",
61
+ "@deepseek-ai/dsh-client-ui-renderer": "^0.1.6-alpha.2",
62
+ "@deepseek-ai/dsh-client-ui-session": "^0.1.6-alpha.2"
62
63
  },
63
64
  "files": [
64
65
  "lib/index.js",