@deepseek-ai/dsh-client-ui-commands 0.1.2-rc.1 → 0.1.5-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: b27c795eaf9a7954925961bde81872307b9f3d97
6
- README.zh.md: 65d31d0f390fce0ea341bf3391903539d3e0d0f7
5
+ README.md: e1264d10bd8ff807d8f348339e0c055607579407
6
+ README.zh.md: c5323149aea1e38ff09262db9bd2aacad7b284ea
package/README.md CHANGED
@@ -31,9 +31,9 @@ Mount this plugin alongside `ui-input-trigger` and `ui-conversation`; the `/` so
31
31
 
32
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.
33
33
 
34
- ### Image-carrying submissions
34
+ ### Attachment-carrying submissions
35
35
 
36
- When the composer submits with image attachments, only a host command declaring `input.images` proceeds; every other command route throws the localized `imagesUnsupported` refusal, which renders as a transient toast while the draft and images stay in place — a command can never consume the text and strand the images.
36
+ When the composer submits with images or generic files, only a host command declaring `input.attachments` proceeds. Every other command route throws the localized `attachmentsUnsupported` refusal, rendered as a transient toast while the draft and attachment cards stay in place. Handler errors preserve the same draft state for retry.
37
37
 
38
38
  -----
39
39
 
@@ -43,7 +43,7 @@ When the composer submits with image attachments, only a host command declaring
43
43
  <details>
44
44
  <summary>Implementation internals — click to expand</summary>
45
45
 
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. Decision record: the [web command surfaces note](../../../.agents/notes/implemented/architecture/2026-07-25-web-command-surfaces-and-assembly.md); the [fuzzy discovery note](../../../.agents/notes/implemented/feature/2026-08-04-web-slash-command-fuzzy-discovery.md) covers menu ranking.
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.
47
47
 
48
48
  </details>
49
49
 
@@ -56,8 +56,6 @@ Read these pages when the command surface is not enough. They move from the comm
56
56
 
57
57
  - [ui-input-trigger](../ui-input-trigger/README.md) — the pipeline the `/` source registers into.
58
58
  - [ui-conversation](../ui-conversation/README.md) — declares the input overlay slot and owns the composer.
59
- - [Web command surfaces and assembly](../../../.agents/notes/implemented/architecture/2026-07-25-web-command-surfaces-and-assembly.md) — the design decision behind the command surfaces.
60
- - [Web slash-command fuzzy discovery](../../../.agents/notes/implemented/feature/2026-08-04-web-slash-command-fuzzy-discovery.md) — the menu ranking rationale.
61
59
  - [Client package map](../README.md) — adjacent browser UI packages.
62
60
 
63
61
  -----
package/README.zh.md CHANGED
@@ -31,9 +31,9 @@ kind: "package-reference"
31
31
 
32
32
  贡献项是客户端自有命令——与宿主命令同名会明确报错。装饰为**已存在的**宿主命令添加裸调用弹窗:宿主命令保留其目录行、参数声明与生命周期记账,被装饰的名字在会话目录中无宿主行时永不触发。菜单查询按顺序且不区分大小写地模糊匹配命令名的子序列;前缀排名最高。
33
33
 
34
- ### 带图提交
34
+ ### 带附件提交
35
35
 
36
- composer 携带图片附件提交时,只有声明了 `input.images` 的宿主命令继续;其余每条命令路径都会抛出本地化的 `imagesUnsupported` 拒绝,以瞬态 toast 呈现,草稿与图片保持原位——命令绝不消费文本却抛下图片。
36
+ composer 携带图片或通用文件提交时,只有声明了 `input.attachments` 的宿主命令继续。其余命令路径都会抛出本地化的 `attachmentsUnsupported` 拒绝,以瞬态 toast 呈现,草稿与附件卡保持原位。处理器返回错误时保留相同草稿状态供用户重试。
37
37
 
38
38
  -----
39
39
 
@@ -43,7 +43,7 @@ composer 携带图片附件提交时,只有声明了 `input.images` 的宿主
43
43
  <details>
44
44
  <summary>实现细节——点击展开</summary>
45
45
 
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`,按会话解析。决策记录:[Web 命令表面笔记](../../../.agents/notes/implemented/architecture/2026-07-25-web-command-surfaces-and-assembly.zh.md);[模糊发现笔记](../../../.agents/notes/implemented/feature/2026-08-04-web-slash-command-fuzzy-discovery.zh.md) 说明菜单排名。
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`,按会话解析。
47
47
 
48
48
  </details>
49
49
 
@@ -56,8 +56,6 @@ composer 携带图片附件提交时,只有声明了 `input.images` 的宿主
56
56
 
57
57
  - [ui-input-trigger](../ui-input-trigger/README.zh.md)——`/` source 注册进的流水线。
58
58
  - [ui-conversation](../ui-conversation/README.zh.md)——声明输入浮层槽位并拥有 composer。
59
- - [Web 命令表面与组装](../../../.agents/notes/implemented/architecture/2026-07-25-web-command-surfaces-and-assembly.zh.md)——命令表面背后的设计决策。
60
- - [Web 斜杠命令模糊发现](../../../.agents/notes/implemented/feature/2026-08-04-web-slash-command-fuzzy-discovery.zh.md)——菜单排名的原理。
61
59
  - [客户端包映射](../README.zh.md)——相邻的浏览器 UI 包。
62
60
 
63
61
  -----
package/lib/client.js CHANGED
@@ -5,10 +5,10 @@ window.__ModuleLoader__.load({
5
5
  var exports = module.exports;
6
6
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
7
  let _deepseek_ai_cordis = require("@deepseek-ai/cordis");
8
+ let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
8
9
  let _deepseek_ai_dsh_client_store = require("@deepseek-ai/dsh-client-store");
9
10
  let react_jsx_runtime = require("react/jsx-runtime");
10
11
  let react = require("react");
11
- let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
12
12
  //#region lib/types/client/directory.js
13
13
  /** One session key's cache cell. */
14
14
  var Entry = class {
@@ -159,6 +159,43 @@ window.__ModuleLoader__.load({
159
159
  return signal.reason instanceof Error ? signal.reason : /* @__PURE__ */ new Error("command directory wait aborted");
160
160
  }
161
161
  //#endregion
162
+ //#region lib/types/client/locales.js
163
+ /** `command` namespace dictionaries (the popupSelect shell's copy). */
164
+ /** Simplified Chinese dictionary (the key-set source of truth). */
165
+ const zh = {
166
+ "description.compact": "压缩以上对话内容",
167
+ "description.export": "将当前会话内容导出为 ZIP",
168
+ "description.feedback": "发送关于当前会话的反馈",
169
+ "description.goal": "设置或查看长期任务目标",
170
+ "description.permission": "切换权限预设(沙箱模式与审批策略)",
171
+ "description.plan": "进入或退出计划模式",
172
+ "search.placeholder": "搜索…",
173
+ "search.aria": "筛选选项",
174
+ "status.loading": "正在加载选项…",
175
+ "status.applying": "正在应用…",
176
+ "status.empty": "无选项",
177
+ "overlay.aria": "/{command} 选项",
178
+ "listbox.aria": "/{command} 匹配项",
179
+ "notice.attachmentsUnsupported": "/{command} 不接受附件,请先移除附件"
180
+ };
181
+ /** English dictionary, checked complete against the zh key set. */
182
+ const en = {
183
+ "description.compact": "Compact older conversation history",
184
+ "description.export": "Download this Session log as a ZIP archive",
185
+ "description.feedback": "record feedback about this session",
186
+ "description.goal": "set or view the goal for a long-running task",
187
+ "description.permission": "Switch the permission preset (sandbox mode + approval policy)",
188
+ "description.plan": "Enter or leave plan mode",
189
+ "search.placeholder": "Search…",
190
+ "search.aria": "Filter options",
191
+ "status.loading": "Loading options…",
192
+ "status.applying": "Applying…",
193
+ "status.empty": "No options",
194
+ "overlay.aria": "/{command} options",
195
+ "listbox.aria": "/{command} matches",
196
+ "notice.attachmentsUnsupported": "/{command} does not accept attachments; remove them first"
197
+ };
198
+ //#endregion
162
199
  //#region lib/types/client/popup.js
163
200
  /**
164
201
  * Headless popupSelect shell state: one controller per client
@@ -432,8 +469,9 @@ window.__ModuleLoader__.load({
432
469
  * CommandUiRuntime (`ctx.commandUi`): the '/' command source over the
433
470
  * session-keyed directory, the client-contribution registry, and the
434
471
  * per-session popupSelect controllers. Candidate synthesis merges the host
435
- * catalog with contributions by availability, then fuzzy query/position
436
- * filtering; a host/contribution name collision fails loud. Every execute
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
437
475
  * addresses the session's agent by sessionId — sessions are always
438
476
  * agent-backed.
439
477
  */
@@ -443,60 +481,15 @@ window.__ModuleLoader__.load({
443
481
  const separator = trimmed.search(/\s/u);
444
482
  return (separator === -1 ? trimmed : trimmed.slice(0, separator)).slice(1);
445
483
  }
446
- /** Extra weight for command-name starts and separator boundaries. */
447
- function boundaryBonus(name, index) {
448
- return index === 0 || name.charAt(index - 1) === "-" || name.charAt(index - 1) === "_" ? 8 : 0;
449
- }
450
- /**
451
- * Score the strongest ordered-subsequence alignment in O(name × query).
452
- * Boundary and adjacent matches earn weight; skipped and leading characters
453
- * cost weight.
454
- */
455
- function fuzzyScore(name, query) {
456
- if (query === "") return 0;
457
- if (query.length > name.length) return void 0;
458
- const noMatch = Number.NEGATIVE_INFINITY;
459
- let previous = Array(name.length).fill(noMatch);
460
- for (let index = 0; index < name.length; index++) if (name.charAt(index) === query.charAt(0)) previous[index] = 1 + boundaryBonus(name, index) - index;
461
- for (let queryIndex = 1; queryIndex < query.length; queryIndex++) {
462
- const current = Array(name.length).fill(noMatch);
463
- let bestGapped = noMatch;
464
- for (let index = 0; index < name.length; index++) {
465
- const gappedIndex = index - 2;
466
- if (gappedIndex >= 0) {
467
- const prior = previous[gappedIndex] ?? noMatch;
468
- if (prior !== noMatch) bestGapped = Math.max(bestGapped, prior + gappedIndex);
469
- }
470
- if (name.charAt(index) !== query.charAt(queryIndex)) continue;
471
- const bonus = 1 + boundaryBonus(name, index);
472
- const adjacent = index > 0 ? previous[index - 1] ?? noMatch : noMatch;
473
- if (adjacent !== noMatch) current[index] = adjacent + bonus + 4;
474
- if (bestGapped !== noMatch) current[index] = Math.max(current[index] ?? noMatch, bestGapped + bonus + 1 - index);
475
- }
476
- previous = current;
477
- }
478
- let best = noMatch;
479
- for (const score of previous) best = Math.max(best, score);
480
- return best === noMatch ? void 0 : best;
481
- }
482
- /** Case-insensitive fuzzy filtering with stable ordering for equal matches. */
483
- function fuzzyCandidates(candidates, rawQuery) {
484
- const query = rawQuery.toLowerCase();
485
- if (query === "") return candidates;
486
- const ranked = [];
487
- candidates.forEach((candidate, index) => {
488
- const name = candidate.name.toLowerCase();
489
- const score = fuzzyScore(name, query);
490
- if (score !== void 0) ranked.push({
491
- candidate,
492
- index,
493
- prefix: name.startsWith(query),
494
- score
495
- });
496
- });
497
- ranked.sort((left, right) => Number(right.prefix) - Number(left.prefix) || right.score - left.score || left.index - right.index);
498
- return ranked.map((match) => match.candidate);
499
- }
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
+ ]);
500
493
  /** Command surface: session-keyed directory + '/' source + contribution registry + per-session popups. */
501
494
  var CommandUiRuntime = class extends _deepseek_ai_cordis.Service {
502
495
  static inject = [
@@ -637,7 +630,7 @@ window.__ModuleLoader__.load({
637
630
  if (this.focusHooks.get(id) === focus) this.focusHooks.delete(id);
638
631
  };
639
632
  }
640
- /** Menu candidates: host catalog + contribution availability, then position filtering and fuzzy name ranking. */
633
+ /** Menu candidates: host catalog + contribution availability, then position filtering and the shared name ranking. */
641
634
  async candidates(session, req) {
642
635
  const list = await this.directory.ensureReady(session.sessionId, req.signal);
643
636
  const rows = [];
@@ -646,7 +639,7 @@ window.__ModuleLoader__.load({
646
639
  seen.add(c.name);
647
640
  rows.push({
648
641
  name: c.name,
649
- description: c.description,
642
+ description: this.hostDescription(c),
650
643
  ...c.input !== void 0 ? { hint: c.input.hint } : {}
651
644
  });
652
645
  }
@@ -655,10 +648,15 @@ window.__ModuleLoader__.load({
655
648
  if (seen.has(contribution.name)) throw new Error(`ui-commands: contribution /${contribution.name} collides with a host command`);
656
649
  rows.push({
657
650
  name: contribution.name,
658
- description: contribution.description
651
+ description: contribution.description()
659
652
  });
660
653
  }
661
- return fuzzyCandidates(rows.filter((c) => req.position === "leading" || c.hint === void 0), req.query);
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;
662
660
  }
663
661
  /** Decision table, menu column: contribution/decorated-host → popup; host input → claim; host bare → detached execute. */
664
662
  dispatch(pick) {
@@ -704,10 +702,10 @@ window.__ModuleLoader__.load({
704
702
  * bare host commands act on the bare token only; leadingInput claims
705
703
  * args-tolerant.
706
704
  *
707
- * Envelope policy: an enter submission carrying images resolves only
708
- * through a command declaring image acceptance. Every other command route —
705
+ * Envelope policy: an enter submission carrying attachments resolves only
706
+ * through a command declaring attachment acceptance. Every other command route —
709
707
  * popup, non-accepting claim, bare detached execute — throws the refusal
710
- * so the machine surfaces one composer notice and the draft and images
708
+ * so the machine surfaces one composer notice and the draft and attachments
711
709
  * stay in place; nothing executes and nothing is dropped.
712
710
  */
713
711
  async matchEnter(session, line, signal, envelope) {
@@ -718,13 +716,13 @@ window.__ModuleLoader__.load({
718
716
  const bare = ws === -1;
719
717
  const name = token.slice(1);
720
718
  if (name === "") return void 0;
721
- const refuseImages = () => {
722
- throw new Error(this.t("notice.imagesUnsupported", { command: name }));
719
+ const refuseAttachments = () => {
720
+ throw new Error(this.t("notice.attachmentsUnsupported", { command: name }));
723
721
  };
724
722
  const contribution = this.live.contributions.get(name);
725
723
  if (contribution !== void 0 && contribution.available(session)) {
726
724
  if (!bare) return void 0;
727
- if (envelope.images > 0) refuseImages();
725
+ if (envelope.attachments > 0) refuseAttachments();
728
726
  this.openPopup(name, contribution.ui, session, {
729
727
  via: "enter",
730
728
  token
@@ -737,7 +735,7 @@ window.__ModuleLoader__.load({
737
735
  if (bare) {
738
736
  const decoration = this.live.decorations.get(name);
739
737
  if (decoration !== void 0 && decoration.available(session)) {
740
- if (envelope.images > 0) refuseImages();
738
+ if (envelope.attachments > 0) refuseAttachments();
741
739
  this.openPopup(name, decoration.ui, session, {
742
740
  via: "enter",
743
741
  token
@@ -746,11 +744,11 @@ window.__ModuleLoader__.load({
746
744
  }
747
745
  }
748
746
  if (desc.input !== void 0) {
749
- if (envelope.images > 0 && desc.input.images !== true) refuseImages();
747
+ if (envelope.attachments > 0 && desc.input.attachments !== true) refuseAttachments();
750
748
  return { claim: this.leadingClaim(desc, session) };
751
749
  }
752
750
  if (!bare) return void 0;
753
- if (envelope.images > 0) refuseImages();
751
+ if (envelope.attachments > 0) refuseAttachments();
754
752
  this.consumeVia(session.sessionId, {
755
753
  via: "enter",
756
754
  token
@@ -770,8 +768,8 @@ window.__ModuleLoader__.load({
770
768
  return {
771
769
  token,
772
770
  ...desc.input !== void 0 ? { hint: desc.input.hint } : {},
773
- ...desc.input?.images === true ? { images: true } : {},
774
- submit: (args, _actx, images) => this.execute(session, token + args, images)
771
+ ...desc.input?.attachments === true ? { attachments: true } : {},
772
+ submit: (args, _actx, attachments) => this.execute(session, token + args, attachments)
775
773
  };
776
774
  }
777
775
  /**
@@ -782,18 +780,18 @@ window.__ModuleLoader__.load({
782
780
  * executor durably logged the lifecycle (`command/run`/`command/done`) and
783
781
  * the outcome renders as a persistent flow node — the composer never
784
782
  * echoes it. A handler error result reports an error outcome so the
785
- * composer keeps the submission (draft and images) for correction.
783
+ * composer keeps the draft and attachments for correction.
786
784
  * A refused call throws.
787
785
  */
788
- async execute(session, line, images = []) {
789
- const result = await this.ctx.remote.commands.execute(session.sessionId, line, images);
786
+ async execute(session, line, attachments = []) {
787
+ const result = await this.ctx.remote.commands.execute(session.sessionId, line, attachments);
790
788
  if (!result.ok) throw new Error(`command.execute failed: ${result.error.code}: ${result.error.message}`);
791
789
  if (result.value === void 0) return {
792
790
  kind: "error",
793
791
  text: `unknown or malformed command: ${line}`
794
792
  };
795
793
  this.notifyExecuted(session.sessionId, submittedCommandName(line), result.value.result);
796
- if (images.length > 0 && result.value.result.kind === "error") return {
794
+ if (attachments.length > 0 && result.value.result.kind === "error") return {
797
795
  kind: "error",
798
796
  text: result.value.result.text
799
797
  };
@@ -1076,31 +1074,6 @@ window.__ModuleLoader__.load({
1076
1074
  })] });
1077
1075
  }
1078
1076
  //#endregion
1079
- //#region lib/types/client/locales.js
1080
- /** `command` namespace dictionaries (the popupSelect shell's copy). */
1081
- /** Simplified Chinese dictionary (the key-set source of truth). */
1082
- const zh = {
1083
- "search.placeholder": "搜索…",
1084
- "search.aria": "筛选选项",
1085
- "status.loading": "正在加载选项…",
1086
- "status.applying": "正在应用…",
1087
- "status.empty": "无选项",
1088
- "overlay.aria": "/{command} 选项",
1089
- "listbox.aria": "/{command} 匹配项",
1090
- "notice.imagesUnsupported": "/{command} 不接受图片附件,请先移除图片"
1091
- };
1092
- /** English dictionary, checked complete against the zh key set. */
1093
- const en = {
1094
- "search.placeholder": "Search…",
1095
- "search.aria": "Filter options",
1096
- "status.loading": "Loading options…",
1097
- "status.applying": "Applying…",
1098
- "status.empty": "No options",
1099
- "overlay.aria": "/{command} options",
1100
- "listbox.aria": "/{command} matches",
1101
- "notice.imagesUnsupported": "/{command} does not accept image attachments; remove them first"
1102
- };
1103
- //#endregion
1104
1077
  //#region lib/types/client/index.js
1105
1078
  /** Dictionary namespace owned by this plugin. */
1106
1079
  const NS = "command";
@@ -42,8 +42,8 @@ export type CommandUiSpec = {
42
42
  export interface CommandContribution {
43
43
  /** Command name without the leading slash (unique across contributions). */
44
44
  readonly name: string;
45
- /** Menu row description. */
46
- readonly description: string;
45
+ /** Resolve the localized menu row description when candidates are requested. */
46
+ readonly description: () => string;
47
47
  /** Capability filter, called with a fresh projection per candidate pass. */
48
48
  available(session: ClientSessionContext): boolean;
49
49
  /** The command's UI behavior (this phase: popupSelect only). */
@@ -1,6 +1,12 @@
1
1
  /** `command` namespace dictionaries (the popupSelect shell's copy). */
2
2
  /** Simplified Chinese dictionary (the key-set source of truth). */
3
3
  export declare const zh: {
4
+ 'description.compact': string;
5
+ 'description.export': string;
6
+ 'description.feedback': string;
7
+ 'description.goal': string;
8
+ 'description.permission': string;
9
+ 'description.plan': string;
4
10
  'search.placeholder': string;
5
11
  'search.aria': string;
6
12
  'status.loading': string;
@@ -8,12 +14,18 @@ export declare const zh: {
8
14
  'status.empty': string;
9
15
  'overlay.aria': string;
10
16
  'listbox.aria': string;
11
- 'notice.imagesUnsupported': string;
17
+ 'notice.attachmentsUnsupported': string;
12
18
  };
13
19
  /** The command namespace key union. */
14
20
  export type CommandKey = keyof typeof zh;
15
21
  /** English dictionary, checked complete against the zh key set. */
16
22
  export declare const en: {
23
+ 'description.compact': string;
24
+ 'description.export': string;
25
+ 'description.feedback': string;
26
+ 'description.goal': string;
27
+ 'description.permission': string;
28
+ 'description.plan': string;
17
29
  'search.placeholder': string;
18
30
  'search.aria': string;
19
31
  'status.loading': string;
@@ -21,6 +33,6 @@ export declare const en: {
21
33
  'status.empty': string;
22
34
  'overlay.aria': string;
23
35
  'listbox.aria': string;
24
- 'notice.imagesUnsupported': string;
36
+ 'notice.attachmentsUnsupported': string;
25
37
  };
26
38
  //# sourceMappingURL=locales.d.ts.map
@@ -2,8 +2,9 @@
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 fuzzy query/position
6
- * filtering; a host/contribution name collision fails loud. Every execute
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
7
8
  * addresses the session's agent by sessionId — sessions are always
8
9
  * agent-backed.
9
10
  */
@@ -73,8 +74,10 @@ export declare class CommandUiRuntime extends Service implements CommandUiContra
73
74
  * @returns the unbind disposer.
74
75
  */
75
76
  bindComposerFocus(id: SessionId, focus: () => void): () => void;
76
- /** Menu candidates: host catalog + contribution availability, then position filtering and fuzzy name ranking. */
77
+ /** Menu candidates: host catalog + contribution availability, then position filtering and the shared name ranking. */
77
78
  private candidates;
79
+ /** Translate exact built-in Host copy while preserving scoped or third-party descriptors verbatim. */
80
+ private hostDescription;
78
81
  /** Decision table, menu column: contribution/decorated-host → popup; host input → claim; host bare → detached execute. */
79
82
  private dispatch;
80
83
  /** Decision table, space column: hot-key sync check; only host leadingInput claims. */
@@ -85,10 +88,10 @@ export declare class CommandUiRuntime extends Service implements CommandUiContra
85
88
  * bare host commands act on the bare token only; leadingInput claims
86
89
  * args-tolerant.
87
90
  *
88
- * Envelope policy: an enter submission carrying images resolves only
89
- * through a command declaring image acceptance. Every other command route —
91
+ * Envelope policy: an enter submission carrying attachments resolves only
92
+ * through a command declaring attachment acceptance. Every other command route —
90
93
  * popup, non-accepting claim, bare detached execute — throws the refusal
91
- * so the machine surfaces one composer notice and the draft and images
94
+ * so the machine surfaces one composer notice and the draft and attachments
92
95
  * stay in place; nothing executes and nothing is dropped.
93
96
  */
94
97
  private matchEnter;
@@ -104,7 +107,7 @@ export declare class CommandUiRuntime extends Service implements CommandUiContra
104
107
  * executor durably logged the lifecycle (`command/run`/`command/done`) and
105
108
  * the outcome renders as a persistent flow node — the composer never
106
109
  * echoes it. A handler error result reports an error outcome so the
107
- * composer keeps the submission (draft and images) for correction.
110
+ * composer keeps the draft and attachments for correction.
108
111
  * A refused call throws.
109
112
  */
110
113
  private execute;
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.2-rc.1",
4
+ "version": "0.1.5-alpha.1",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -46,21 +46,21 @@
46
46
  "devDependencies": {
47
47
  "@types/react": "~18.3.1",
48
48
  "react": "^18.2.0",
49
- "@deepseek-ai/dsh-api-remotes": "^0.1.2-rc.1",
50
- "@deepseek-ai/dsh-client-connection": "^0.1.2-rc.1",
51
- "@deepseek-ai/dsh-client-locale": "^0.1.2-rc.1",
52
- "@deepseek-ai/dsh-client-test-runtime": "^0.1.2-rc.1",
53
- "@deepseek-ai/dsh-client-ui-primitives": "^0.1.2-rc.1",
54
- "@deepseek-ai/dsh-client-ui-conversation": "^0.1.2-rc.1",
55
- "@deepseek-ai/dsh-client-ui-input-trigger": "^0.1.2-rc.1",
56
- "@deepseek-ai/dsh-client-ui-slots": "^0.1.2-rc.1",
57
- "@deepseek-ai/dsh-commands": "^0.1.2-rc.1",
49
+ "@deepseek-ai/dsh-api-remotes": "^0.1.5-alpha.1",
50
+ "@deepseek-ai/dsh-client-connection": "^0.1.5-alpha.1",
51
+ "@deepseek-ai/dsh-client-locale": "^0.1.5-alpha.1",
52
+ "@deepseek-ai/dsh-client-test-runtime": "^0.1.5-alpha.1",
53
+ "@deepseek-ai/dsh-client-ui-conversation": "^0.1.5-alpha.1",
54
+ "@deepseek-ai/dsh-client-ui-primitives": "^0.1.5-alpha.1",
55
+ "@deepseek-ai/dsh-client-ui-input-trigger": "^0.1.5-alpha.1",
56
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.5-alpha.1",
57
+ "@deepseek-ai/dsh-commands": "^0.1.5-alpha.1",
58
58
  "@deepseek-ai/cordis": "^4.0.2",
59
- "@deepseek-ai/dsh-api-session-controller": "^0.1.2-rc.1",
60
- "@deepseek-ai/dsh-client-store": "^0.1.2-rc.1",
61
- "@deepseek-ai/dsh-session": "^0.1.2-rc.1",
62
- "@deepseek-ai/dsh-client-ui-renderer": "^0.1.2-rc.1",
63
- "@deepseek-ai/dsh-client-ui-session": "^0.1.2-rc.1"
59
+ "@deepseek-ai/dsh-api-session-controller": "^0.1.5-alpha.1",
60
+ "@deepseek-ai/dsh-client-store": "^0.1.5-alpha.1",
61
+ "@deepseek-ai/dsh-session": "^0.1.5-alpha.1",
62
+ "@deepseek-ai/dsh-client-ui-renderer": "^0.1.5-alpha.1",
63
+ "@deepseek-ai/dsh-client-ui-session": "^0.1.5-alpha.1"
64
64
  },
65
65
  "files": [
66
66
  "lib/index.js",