@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 +2 -2
- package/README.md +3 -5
- package/README.zh.md +3 -5
- package/lib/client.js +74 -101
- package/lib/types/client/contract.d.ts +2 -2
- package/lib/types/client/locales.d.ts +14 -2
- package/lib/types/client/service.d.ts +10 -7
- package/package.json +15 -15
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:
|
|
6
|
-
README.zh.md:
|
|
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
|
-
###
|
|
34
|
+
### Attachment-carrying submissions
|
|
35
35
|
|
|
36
|
-
When the composer submits with
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
436
|
-
*
|
|
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
|
-
/**
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
708
|
-
* through a command declaring
|
|
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
|
|
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
|
|
722
|
-
throw new Error(this.t("notice.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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?.
|
|
774
|
-
submit: (args, _actx,
|
|
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
|
|
783
|
+
* composer keeps the draft and attachments for correction.
|
|
786
784
|
* A refused call throws.
|
|
787
785
|
*/
|
|
788
|
-
async execute(session, line,
|
|
789
|
-
const result = await this.ctx.remote.commands.execute(session.sessionId, line,
|
|
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 (
|
|
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
|
-
/**
|
|
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.
|
|
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.
|
|
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
|
|
6
|
-
*
|
|
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
|
|
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
|
|
89
|
-
* through a command declaring
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
50
|
-
"@deepseek-ai/dsh-client-connection": "^0.1.
|
|
51
|
-
"@deepseek-ai/dsh-client-locale": "^0.1.
|
|
52
|
-
"@deepseek-ai/dsh-client-test-runtime": "^0.1.
|
|
53
|
-
"@deepseek-ai/dsh-client-ui-
|
|
54
|
-
"@deepseek-ai/dsh-client-ui-
|
|
55
|
-
"@deepseek-ai/dsh-client-ui-input-trigger": "^0.1.
|
|
56
|
-
"@deepseek-ai/dsh-client-ui-slots": "^0.1.
|
|
57
|
-
"@deepseek-ai/dsh-commands": "^0.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.
|
|
60
|
-
"@deepseek-ai/dsh-client-store": "^0.1.
|
|
61
|
-
"@deepseek-ai/dsh-session": "^0.1.
|
|
62
|
-
"@deepseek-ai/dsh-client-ui-renderer": "^0.1.
|
|
63
|
-
"@deepseek-ai/dsh-client-ui-session": "^0.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",
|