@deepseek-ai/dsh-client-ui-commands 0.1.6-alpha.1 → 0.1.7-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 -1
- package/README.zh.md +3 -1
- package/lib/client.js +127 -46
- package/lib/types/client/contract.d.ts +6 -0
- package/lib/types/client/popup.d.ts +8 -2
- package/lib/types/client/service.d.ts +10 -10
- package/package.json +18 -17
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: 7fada747562ff15a96997d8bffe9aeb6f9a18fb5
|
|
6
|
+
README.zh.md: 2f532cbd0f3d33e304074644cc6cb02b7151d3e1
|
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ Typing a `/` command opens a registered popup, a client action, a host command's
|
|
|
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. 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.
|
|
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
|
|
|
@@ -49,6 +49,8 @@ When the composer submits with images or generic files, only a host command decl
|
|
|
49
49
|
|
|
50
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.
|
|
51
51
|
|
|
52
|
+
Each command-directory fetch requires an existing retained Client Session and waits for its initial history open to succeed before sending `commands.list`. A temporary `commandCatalog` reference holds that Session until the fetch settles. An unretained Session or a failed open rejects without sending the RPC, so background catalog refreshes do not reopen closed Sessions.
|
|
53
|
+
|
|
52
54
|
</details>
|
|
53
55
|
|
|
54
56
|
-----
|
package/README.zh.md
CHANGED
|
@@ -25,7 +25,7 @@ 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
|
|
|
@@ -49,6 +49,8 @@ composer 携带图片或通用文件提交时,只有声明了 `input.attachmen
|
|
|
49
49
|
|
|
50
50
|
`src/client/contract.ts` 定义贡献项和装饰的注册接口,以及 `dismiss(name)`:它关闭该命令已打开的弹窗与确认对话框,中止待完成的选项加载,阻止晚到结果重新打开弹窗,并保留 composer 草稿。`CommandDirectory` 负责会话级协议缓存,并通过 `resolution.ts` 解析输入命令;该模块负责内置命令标识匹配和本地化输入写法。`matchSpace` 同步读取就绪缓存,`matchEnter` 等待缓存就绪,预热失败或取消时拒绝。转发的目录和连接事件使缓存失效。宿主执行匹配的命令后,本浏览器发布 `command/executed`,其他客户端只观察持久命令事件。`PopupSelectController` 负责弹窗状态,`PopupSelectView` 占据输入浮层。`presentation.ts` 负责行标题、图标和分节,展示与解析辅助函数均留在插件内部。
|
|
51
51
|
|
|
52
|
+
每次命令目录拉取都要求客户端已持有该会话,并等待首次历史打开成功后才发送 `commands.list`。临时的 `commandCatalog` 引用持续持有会话,直到拉取结束。未被持有的会话或打开失败会直接拒绝,不发送 RPC,因此后台目录刷新不会重新打开已关闭的会话。
|
|
53
|
+
|
|
52
54
|
</details>
|
|
53
55
|
|
|
54
56
|
-----
|
package/lib/client.js
CHANGED
|
@@ -9,6 +9,69 @@ 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
|
|
12
75
|
//#region lib/types/client/locales.js
|
|
13
76
|
/**
|
|
14
77
|
* `command` namespace dictionaries: the composer menu's section headings,
|
|
@@ -307,6 +370,19 @@ window.__ModuleLoader__.load({
|
|
|
307
370
|
if (query === "") return options;
|
|
308
371
|
return options.filter((o) => o.label.toLowerCase().includes(query) || (o.detail?.toLowerCase().includes(query) ?? false));
|
|
309
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
|
+
}
|
|
310
386
|
/** The shell's error-strip line for a settlement failure. */
|
|
311
387
|
function errorText(error) {
|
|
312
388
|
return error instanceof Error ? error.message : String(error);
|
|
@@ -358,11 +434,12 @@ window.__ModuleLoader__.load({
|
|
|
358
434
|
load(binding) {
|
|
359
435
|
binding.spec.options(binding.context, binding.abort.signal).then((options) => {
|
|
360
436
|
if (this.binding !== binding) return;
|
|
437
|
+
const current = this.state.getSnapshot();
|
|
361
438
|
this.state.set({
|
|
362
|
-
...
|
|
439
|
+
...current,
|
|
363
440
|
status: "ready",
|
|
364
441
|
options,
|
|
365
|
-
active:
|
|
442
|
+
active: currentIndex(options, current.search),
|
|
366
443
|
error: null
|
|
367
444
|
});
|
|
368
445
|
}, (error) => {
|
|
@@ -391,7 +468,8 @@ window.__ModuleLoader__.load({
|
|
|
391
468
|
}
|
|
392
469
|
/**
|
|
393
470
|
* Replace the local search text (pure local filter — the provider is never
|
|
394
|
-
* re-queried) and rebase the highlight
|
|
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.
|
|
395
473
|
* @param search - the shell search input's text.
|
|
396
474
|
*/
|
|
397
475
|
setSearch(search) {
|
|
@@ -565,12 +643,12 @@ window.__ModuleLoader__.load({
|
|
|
565
643
|
}
|
|
566
644
|
/** Built-in Host commands whose client face this package owns. */
|
|
567
645
|
const HOST_FACES = new Map([
|
|
568
|
-
hostFace("goal", _deepseek_ai_dsh_client_ui_primitives.
|
|
569
|
-
hostFace("plan", _deepseek_ai_dsh_client_ui_primitives.
|
|
570
|
-
hostFace("feedback", _deepseek_ai_dsh_client_ui_primitives.
|
|
571
|
-
hostFace("compact", _deepseek_ai_dsh_client_ui_primitives.
|
|
572
|
-
hostFace("permission", _deepseek_ai_dsh_client_ui_primitives.
|
|
573
|
-
hostFace("export", _deepseek_ai_dsh_client_ui_primitives.
|
|
646
|
+
hostFace("goal", _deepseek_ai_dsh_client_ui_primitives.IconGoalOutlineRegular),
|
|
647
|
+
hostFace("plan", _deepseek_ai_dsh_client_ui_primitives.IconPlanOutlineRegular),
|
|
648
|
+
hostFace("feedback", _deepseek_ai_dsh_client_ui_primitives.IconPaperPlaneOutlineRegular),
|
|
649
|
+
hostFace("compact", _deepseek_ai_dsh_client_ui_primitives.IconCompactOutlineRegular),
|
|
650
|
+
hostFace("permission", _deepseek_ai_dsh_client_ui_primitives.PermissionIconFullAccessRegular),
|
|
651
|
+
hostFace("export", _deepseek_ai_dsh_client_ui_primitives.IconDownloadOutlineRegular)
|
|
574
652
|
]);
|
|
575
653
|
/**
|
|
576
654
|
* The localized menu face of a catalog row.
|
|
@@ -626,6 +704,8 @@ window.__ModuleLoader__.load({
|
|
|
626
704
|
* `rankByName`). A host/contribution name collision fails loud. Every
|
|
627
705
|
* execute addresses the session's agent by sessionId — sessions are always
|
|
628
706
|
* agent-backed.
|
|
707
|
+
* Catalog RPCs retain an existing Client Session through completion and
|
|
708
|
+
* wait for its initial history open to succeed before contacting the Host.
|
|
629
709
|
*/
|
|
630
710
|
/** Recover the command name from a line the Host confirmed as executed. */
|
|
631
711
|
function submittedCommandName(line) {
|
|
@@ -645,7 +725,7 @@ window.__ModuleLoader__.load({
|
|
|
645
725
|
live = {
|
|
646
726
|
contributions: /* @__PURE__ */ new Map(),
|
|
647
727
|
decorations: /* @__PURE__ */ new Map(),
|
|
648
|
-
popups:
|
|
728
|
+
popups: new WeakMapWithValues()
|
|
649
729
|
};
|
|
650
730
|
/** `command`-namespace translator (composer refusal notices). */
|
|
651
731
|
t;
|
|
@@ -659,10 +739,16 @@ window.__ModuleLoader__.load({
|
|
|
659
739
|
if (locale === void 0) throw new Error("ui-commands: locale service unavailable");
|
|
660
740
|
this.t = locale.bind("command");
|
|
661
741
|
this.directory = new CommandDirectory(async (sessionId) => {
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
if (
|
|
665
|
-
return
|
|
742
|
+
const sessions = this.sessions();
|
|
743
|
+
if (sessions.subagentAddress(sessionId) !== void 0) return [];
|
|
744
|
+
if (sessions.binding(sessionId) === void 0) throw new Error(`command catalog requires a retained session "${sessionId}"`);
|
|
745
|
+
return sessions.using(sessionId, { source: "commandCatalog" }, async (reference) => {
|
|
746
|
+
const state = reference.binding.session.getSnapshot();
|
|
747
|
+
if (state.openState !== "open") throw state.openError ?? /* @__PURE__ */ new Error(`session "${sessionId}" is not open`);
|
|
748
|
+
const result = await ctx.remote.commands.list(sessionId);
|
|
749
|
+
if (!result.ok) throw new Error(`command.list failed: ${result.error.code}: ${result.error.message}`);
|
|
750
|
+
return result.value;
|
|
751
|
+
});
|
|
666
752
|
});
|
|
667
753
|
const inputTriggers = ctx.get("inputTriggers");
|
|
668
754
|
if (inputTriggers === void 0) throw new Error("ui-commands: slash service unavailable");
|
|
@@ -731,24 +817,27 @@ window.__ModuleLoader__.load({
|
|
|
731
817
|
* @param name - command name without the leading slash.
|
|
732
818
|
*/
|
|
733
819
|
dismiss(name) {
|
|
734
|
-
for (const popup of this.live.popups.values
|
|
820
|
+
for (const popup of this.live.popups.values) if (popup.state.getSnapshot().command === name) popup.dismiss({ focusComposer: true });
|
|
735
821
|
}
|
|
736
822
|
/**
|
|
737
823
|
* Resolve the per-session popup controller (lazy; dies with the session
|
|
738
824
|
* scope). The controller's consume callback dispatches the scoped
|
|
739
825
|
* consume-token event back to this session; focusComposer reaches the
|
|
740
|
-
* composer through the
|
|
826
|
+
* session's composer through the conversation input face.
|
|
741
827
|
* @param actx - session-scope ctx.
|
|
742
828
|
* @returns the resident controller.
|
|
829
|
+
* @throws when the Context no longer belongs to a retained Session generation.
|
|
743
830
|
*/
|
|
744
831
|
popupFor(actx) {
|
|
745
|
-
const
|
|
746
|
-
|
|
832
|
+
const sessions = this.sessions();
|
|
833
|
+
const session = sessions.sessionOf(actx);
|
|
834
|
+
const binding = session === void 0 ? void 0 : sessions.binding(session.sessionId);
|
|
835
|
+
if (binding === void 0 || binding.session !== session) throw new Error("command.popupFor requires a retained Session scope");
|
|
747
836
|
const { popups } = this.live;
|
|
748
|
-
const existing = popups.get(
|
|
837
|
+
const existing = popups.get(binding);
|
|
749
838
|
if (existing !== void 0) return existing;
|
|
750
839
|
const controller = new PopupSelectController({
|
|
751
|
-
consume: (segment) =>
|
|
840
|
+
consume: (segment) => binding.ctx.bail(binding.ctx, "slash/input-consume-token", { guard: segment.via === "menu" ? {
|
|
752
841
|
kind: "span",
|
|
753
842
|
span: segment.span
|
|
754
843
|
} : {
|
|
@@ -756,31 +845,16 @@ window.__ModuleLoader__.load({
|
|
|
756
845
|
token: segment.token
|
|
757
846
|
} }) === true,
|
|
758
847
|
focusComposer: () => {
|
|
759
|
-
|
|
848
|
+
binding.ctx.get("conversation")?.input.for(binding.ctx).focus();
|
|
760
849
|
}
|
|
761
850
|
});
|
|
762
|
-
popups.set(
|
|
763
|
-
|
|
851
|
+
popups.set(binding, controller);
|
|
852
|
+
binding.ctx.effect(() => () => {
|
|
764
853
|
controller.dispose();
|
|
765
|
-
popups.delete(
|
|
766
|
-
this.focusHooks.delete(id);
|
|
854
|
+
popups.delete(binding);
|
|
767
855
|
}, "command: session popup");
|
|
768
856
|
return controller;
|
|
769
857
|
}
|
|
770
|
-
/** Composer focus hooks by session (the overlay wiring binds the textarea focus here). */
|
|
771
|
-
focusHooks = /* @__PURE__ */ new Map();
|
|
772
|
-
/**
|
|
773
|
-
* Bind one session's composer-focus hook (overlay slot wiring; unbind on unmount).
|
|
774
|
-
* @param id - session id.
|
|
775
|
-
* @param focus - textarea focus callback.
|
|
776
|
-
* @returns the unbind disposer.
|
|
777
|
-
*/
|
|
778
|
-
bindComposerFocus(id, focus) {
|
|
779
|
-
this.focusHooks.set(id, focus);
|
|
780
|
-
return () => {
|
|
781
|
-
if (this.focusHooks.get(id) === focus) this.focusHooks.delete(id);
|
|
782
|
-
};
|
|
783
|
-
}
|
|
784
858
|
/**
|
|
785
859
|
* Menu candidates: host catalog + contribution availability, built-in rows
|
|
786
860
|
* localized, then position filtering; sections for an empty query, the
|
|
@@ -1055,7 +1129,7 @@ window.__ModuleLoader__.load({
|
|
|
1055
1129
|
}
|
|
1056
1130
|
//#endregion
|
|
1057
1131
|
//#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-commands/src/client/PopupSelectView.module.css.mjs
|
|
1058
|
-
const css = ".mufS8W_card{z-index:100;--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);background:var(--dsw-specific-menu)
|
|
1132
|
+
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);min-width:min(220px,100%);max-width:100%;max-height:320px;backdrop-filter:var(--dsw-menu-backdrop-filter);--dsw-elevation-stroke-color:var(--dsw-alias-border-l1);box-shadow:var(--dsw-elevation-prominent);border:0;border-radius:16px;outline:none;flex-direction:column;padding:3px;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:7px;align-items:center;gap:6px;padding:5px 7px;font-size:12px;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:11px;overflow:hidden}.mufS8W_check{color:var(--dsw-alias-label-primary);flex:none;margin-left:auto;display:inline-flex}.mufS8W_check svg{width:14px;height:14px}.mufS8W_status{color:var(--dsw-alias-label-tertiary);padding:7px 8px;font-size:12px}.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 3px;padding:5px 7px;font-size:12px}.mufS8W_error{color:var(--dsw-alias-state-error-primary);align-items:center;gap:6px;padding:5px 7px;font-size:11px;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 7px;font-size:11px}";
|
|
1059
1133
|
const tagId = "@deepseek-ai/dsh-client-ui-commands/PopupSelectView.module.css";
|
|
1060
1134
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
1061
1135
|
const tag = document.createElement("style");
|
|
@@ -1087,11 +1161,12 @@ window.__ModuleLoader__.load({
|
|
|
1087
1161
|
* store into the conversation.input.overlay anchor. Unlike the slash menu
|
|
1088
1162
|
* (combobox — textarea keeps focus), this shell HOLDS focus while open: the
|
|
1089
1163
|
* inner search input takes focus, plain typing filters the loaded options
|
|
1090
|
-
* locally, Enter
|
|
1091
|
-
*
|
|
1092
|
-
* caret. Any pointer interaction outside the
|
|
1093
|
-
* target takes focus). Closed state renders
|
|
1094
|
-
* mounted. The card height clamps to the space
|
|
1164
|
+
* locally, Enter and Tab accept the filtered highlight, ↑↓ walk it (wrapping,
|
|
1165
|
+
* scrolled into view), and Escape and Shift+Tab dismiss back to the composer.
|
|
1166
|
+
* ←→ keep the search input's native caret. Any pointer interaction outside the
|
|
1167
|
+
* box dismisses (the click's own target takes focus). Closed state renders
|
|
1168
|
+
* null; the overlay slot stays mounted. The card height clamps to the space
|
|
1169
|
+
* above the composer.
|
|
1095
1170
|
*/
|
|
1096
1171
|
/** Design cap on the card height (same MenuDropdown family as the slash menu). */
|
|
1097
1172
|
const MAX_HEIGHT = 320;
|
|
@@ -1145,6 +1220,12 @@ window.__ModuleLoader__.load({
|
|
|
1145
1220
|
ev.preventDefault();
|
|
1146
1221
|
popup.select(state.active);
|
|
1147
1222
|
return;
|
|
1223
|
+
case "Tab":
|
|
1224
|
+
if (!ev.shiftKey && (state.status !== "ready" || rows.length === 0)) return;
|
|
1225
|
+
ev.preventDefault();
|
|
1226
|
+
if (ev.shiftKey) popup.dismiss({ focusComposer: true });
|
|
1227
|
+
else popup.select(state.active);
|
|
1228
|
+
return;
|
|
1148
1229
|
case "Escape":
|
|
1149
1230
|
ev.preventDefault();
|
|
1150
1231
|
popup.dismiss({ focusComposer: true });
|
|
@@ -1230,7 +1311,7 @@ window.__ModuleLoader__.load({
|
|
|
1230
1311
|
}),
|
|
1231
1312
|
option.active === true && (0, react_jsx_runtime.jsx)("span", {
|
|
1232
1313
|
className: PopupSelectView_module_css_default.check,
|
|
1233
|
-
children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.
|
|
1314
|
+
children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutlineRegular, {})
|
|
1234
1315
|
})
|
|
1235
1316
|
]
|
|
1236
1317
|
}, option.id))
|
|
@@ -22,6 +22,12 @@ export interface SelectOption {
|
|
|
22
22
|
/** Optional short marker rendered as a superscript beside the label. */
|
|
23
23
|
readonly badge?: string;
|
|
24
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
|
+
*/
|
|
25
31
|
readonly active?: boolean;
|
|
26
32
|
/** Optional in-page risk gate owned by the shared popup shell. */
|
|
27
33
|
readonly confirmation?: SelectConfirmation;
|
|
@@ -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
|
-
/**
|
|
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
|
|
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;
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
* `rankByName`). A host/contribution name collision fails loud. Every
|
|
10
10
|
* execute addresses the session's agent by sessionId — sessions are always
|
|
11
11
|
* agent-backed.
|
|
12
|
+
* Catalog RPCs retain an existing Client Session through completion and
|
|
13
|
+
* wait for its initial history open to succeed before contacting the Host.
|
|
12
14
|
*/
|
|
13
15
|
import { Service } from '@deepseek-ai/cordis';
|
|
14
16
|
import type { Context } from '@deepseek-ai/cordis';
|
|
@@ -18,6 +20,12 @@ import type { SessionId } from '@deepseek-ai/dsh-session/types';
|
|
|
18
20
|
import type { ClientSessionContext } from '@deepseek-ai/dsh-client-ui-input-trigger/client';
|
|
19
21
|
import type { CommandContribution, CommandDecoration, CommandUiContract } from './contract.ts';
|
|
20
22
|
import { PopupSelectController } from './popup.ts';
|
|
23
|
+
declare module '@deepseek-ai/dsh-api-session-controller/client' {
|
|
24
|
+
interface SessionReferenceSourceMap {
|
|
25
|
+
/** A command-catalog fetch waiting for initial history and its RPC result. */
|
|
26
|
+
commandCatalog: unknown;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
21
29
|
declare module '@deepseek-ai/cordis' {
|
|
22
30
|
interface Events {
|
|
23
31
|
/**
|
|
@@ -68,20 +76,12 @@ export declare class CommandUiRuntime extends Service implements CommandUiContra
|
|
|
68
76
|
* Resolve the per-session popup controller (lazy; dies with the session
|
|
69
77
|
* scope). The controller's consume callback dispatches the scoped
|
|
70
78
|
* consume-token event back to this session; focusComposer reaches the
|
|
71
|
-
* composer through the
|
|
79
|
+
* session's composer through the conversation input face.
|
|
72
80
|
* @param actx - session-scope ctx.
|
|
73
81
|
* @returns the resident controller.
|
|
82
|
+
* @throws when the Context no longer belongs to a retained Session generation.
|
|
74
83
|
*/
|
|
75
84
|
popupFor(actx: ClientContext): PopupSelectController<ClientSessionContext>;
|
|
76
|
-
/** Composer focus hooks by session (the overlay wiring binds the textarea focus here). */
|
|
77
|
-
private readonly focusHooks;
|
|
78
|
-
/**
|
|
79
|
-
* Bind one session's composer-focus hook (overlay slot wiring; unbind on unmount).
|
|
80
|
-
* @param id - session id.
|
|
81
|
-
* @param focus - textarea focus callback.
|
|
82
|
-
* @returns the unbind disposer.
|
|
83
|
-
*/
|
|
84
|
-
bindComposerFocus(id: SessionId, focus: () => void): () => void;
|
|
85
85
|
/**
|
|
86
86
|
* Menu candidates: host catalog + contribution availability, built-in rows
|
|
87
87
|
* localized, then position filtering; sections for an empty query, the
|
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.7-alpha.1",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -38,27 +38,28 @@
|
|
|
38
38
|
},
|
|
39
39
|
"license": "MIT",
|
|
40
40
|
"peerDependencies": {
|
|
41
|
-
"@deepseek-ai/cordis": "^4.0.
|
|
41
|
+
"@deepseek-ai/cordis": "^4.0.3"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@types/react": "~18.3.1",
|
|
45
45
|
"react": "^18.2.0",
|
|
46
46
|
"clsx": "^2.0.0",
|
|
47
|
-
"@deepseek-ai/dsh-
|
|
48
|
-
"@deepseek-ai/dsh-client-
|
|
49
|
-
"@deepseek-ai/dsh-client-
|
|
50
|
-
"@deepseek-ai/dsh-client-ui-
|
|
51
|
-
"@deepseek-ai/dsh-
|
|
52
|
-
"@deepseek-ai/dsh-client-
|
|
53
|
-
"@deepseek-ai/dsh-
|
|
54
|
-
"@deepseek-ai/dsh-
|
|
55
|
-
"@deepseek-ai/cordis": "^4.0.
|
|
56
|
-
"@deepseek-ai/dsh-api-session-controller": "^0.1.
|
|
57
|
-
"@deepseek-ai/dsh-client-store": "^0.1.
|
|
58
|
-
"@deepseek-ai/dsh-session": "^0.1.
|
|
59
|
-
"@deepseek-ai/dsh-client-ui-
|
|
60
|
-
"@deepseek-ai/dsh-client-ui-session": "^0.1.
|
|
61
|
-
"@deepseek-ai/dsh-client-ui-
|
|
47
|
+
"@deepseek-ai/dsh-client-locale": "^0.1.7-alpha.1",
|
|
48
|
+
"@deepseek-ai/dsh-client-test-runtime": "^0.1.7-alpha.1",
|
|
49
|
+
"@deepseek-ai/dsh-client-ui-conversation": "^0.1.7-alpha.1",
|
|
50
|
+
"@deepseek-ai/dsh-client-ui-primitives": "^0.1.7-alpha.1",
|
|
51
|
+
"@deepseek-ai/dsh-api-remotes": "^0.1.7-alpha.1",
|
|
52
|
+
"@deepseek-ai/dsh-client-connection": "^0.1.7-alpha.1",
|
|
53
|
+
"@deepseek-ai/dsh-commands": "^0.1.7-alpha.1",
|
|
54
|
+
"@deepseek-ai/dsh-client-ui-slots": "^0.1.7-alpha.1",
|
|
55
|
+
"@deepseek-ai/cordis": "^4.0.3",
|
|
56
|
+
"@deepseek-ai/dsh-api-session-controller": "^0.1.7-alpha.1",
|
|
57
|
+
"@deepseek-ai/dsh-client-store": "^0.1.7-alpha.1",
|
|
58
|
+
"@deepseek-ai/dsh-session": "^0.1.7-alpha.1",
|
|
59
|
+
"@deepseek-ai/dsh-client-ui-renderer": "^0.1.7-alpha.1",
|
|
60
|
+
"@deepseek-ai/dsh-client-ui-session": "^0.1.7-alpha.1",
|
|
61
|
+
"@deepseek-ai/dsh-client-ui-input-trigger": "^0.1.7-alpha.1",
|
|
62
|
+
"@deepseek-ai/dsh-util-values": "^0.1.7-alpha.1"
|
|
62
63
|
},
|
|
63
64
|
"files": [
|
|
64
65
|
"lib/index.js",
|