dsh-edge 0.1.0
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/LICENSE +21 -0
- package/README.i18n.yaml +6 -0
- package/README.md +183 -0
- package/README.zh.md +183 -0
- package/THIRD_PARTY_NOTICES.md +199 -0
- package/dist/_headers +3 -0
- package/dist/assets/fonts/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/dist/assets/fonts/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/dist/assets/fonts/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/dist/assets/fonts/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/dist/assets/fonts/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/dist/assets/fonts/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/dist/assets/fonts/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/dist/assets/fonts/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/dist/assets/fonts/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/dist/assets/fonts/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/dist/assets/fonts/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/dist/assets/fonts/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/dist/assets/fonts/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/dist/assets/fonts/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/dist/assets/fonts/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/dist/assets/fonts/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/dist/assets/fonts/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/dist/assets/fonts/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/dist/assets/fonts/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/dist/assets/fonts/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/dist/assets/fonts/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/dist/assets/fonts/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/dist/assets/fonts/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/dist/assets/fonts/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/dist/assets/fonts/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/dist/assets/fonts/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/dist/assets/fonts/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/dist/assets/fonts/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/dist/assets/fonts/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/dist/assets/fonts/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/dist/assets/fonts/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/dist/assets/fonts/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/dist/assets/fonts/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/dist/assets/fonts/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/dist/assets/fonts/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/dist/assets/fonts/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/dist/assets/fonts/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/dist/assets/fonts/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/dist/assets/fonts/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/dist/assets/fonts/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/dist/assets/fonts/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/dist/assets/fonts/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/dist/assets/fonts/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/dist/assets/fonts/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/dist/assets/fonts/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/dist/assets/fonts/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/dist/assets/fonts/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/dist/assets/fonts/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/dist/assets/fonts/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/dist/assets/fonts/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/dist/assets/fonts/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/dist/assets/fonts/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/dist/assets/fonts/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/dist/assets/fonts/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/dist/assets/fonts/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/dist/assets/fonts/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/dist/assets/fonts/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/dist/assets/fonts/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/dist/assets/fonts/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/dist/assets/index-C-1AiF3k.js +112 -0
- package/dist/assets/index-CSGf6Qzd.css +1 -0
- package/dist/assets/langs/c-BIGW1oBm.js +2 -0
- package/dist/assets/langs/cpp-DIPi6g--.js +2 -0
- package/dist/assets/langs/csharp-DSvCPggb.js +2 -0
- package/dist/assets/langs/css-CLj8gQPS.js +2 -0
- package/dist/assets/langs/go-C27-OAKa.js +2 -0
- package/dist/assets/langs/html-CfGypltT.js +2 -0
- package/dist/assets/langs/ini-BEwlwnbL.js +2 -0
- package/dist/assets/langs/java-CylS5w8V.js +2 -0
- package/dist/assets/langs/kotlin-BdnUsdx6.js +2 -0
- package/dist/assets/langs/less-B1dDrJ26.js +2 -0
- package/dist/assets/langs/lua-BaeVxFsk.js +2 -0
- package/dist/assets/langs/markdown-Cvjx9yec.js +2 -0
- package/dist/assets/langs/mdx-Cmh6b_Ma.js +2 -0
- package/dist/assets/langs/php-D-uCvoST.js +2 -0
- package/dist/assets/langs/python-B6aJPvgy.js +2 -0
- package/dist/assets/langs/ruby-DEItuUFs.js +2 -0
- package/dist/assets/langs/rust-B1yitclQ.js +2 -0
- package/dist/assets/langs/scss-D5BDwBP9.js +2 -0
- package/dist/assets/langs/sql-CRqJ_cUM.js +2 -0
- package/dist/assets/langs/swift-D82vCrfD.js +2 -0
- package/dist/assets/langs/toml-vGWfd6FD.js +2 -0
- package/dist/assets/langs/xml-sdJ4AIDG.js +2 -0
- package/dist/assets/langs/yaml-Buea-lGh.js +2 -0
- package/dist/assets/vendor-Cjbwl5VI.js +411 -0
- package/dist/assets/vendor-CjyC-hUb.css +1 -0
- package/dist/favicon.svg +8 -0
- package/dist/index.html +35 -0
- package/dist/manifest.webmanifest +16 -0
- package/dist/plugins/@deepseek-ai/dsh-api-gateway/client.js +420 -0
- package/dist/plugins/@deepseek-ai/dsh-api-remotes/client.js +5937 -0
- package/dist/plugins/@deepseek-ai/dsh-client-connection/client.js +10207 -0
- package/dist/plugins/@deepseek-ai/dsh-client-locale/client.js +1231 -0
- package/dist/plugins/@deepseek-ai/dsh-client-modules/client.js +259 -0
- package/dist/plugins/@deepseek-ai/dsh-client-runtime/client.js +10539 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-agent-preset/client.js +1725 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-commands/client.js +1116 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-conversation/client.js +9832 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-deliverables/client.js +373 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-edge/client.js +549 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-input-trigger/client.js +859 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-jobs/client.js +280 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-layout/client.js +456 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-model-selection/client.js +804 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-permission-presets/client.js +461 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-settings/client.js +254 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-settings-general/client.js +609 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-sidebar/client.js +292 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-skill/client.js +327 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-subagent/client.js +710 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-theme/client.js +1312 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-tool/client.js +1630 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-trajectory/client.js +7370 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-user-questions/client.js +700 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-workflow-run/client.js +474 -0
- package/dist/plugins/@deepseek-ai/dsh-client-ui-workspace/client.js +2426 -0
- package/dist/plugins/@deepseek-ai/dsh-typert-registry/client.js +1372 -0
- package/package.json +97 -0
- package/scripts/assemble-web.mjs +230 -0
- package/scripts/bundle-size.d.mts +2 -0
- package/scripts/bundle-size.mjs +26 -0
- package/scripts/bundle.mjs +53 -0
- package/scripts/cli.d.mts +39 -0
- package/scripts/cli.mjs +496 -0
- package/scripts/install.d.mts +149 -0
- package/scripts/install.mjs +989 -0
- package/scripts/legal-files.mjs +24 -0
- package/scripts/verify-packed.mjs +32 -0
- package/scripts/wrangler-config.d.mts +18 -0
- package/scripts/wrangler-config.mjs +84 -0
- package/src/agent.ts +119 -0
- package/src/auth.ts +352 -0
- package/src/deepseek.ts +231 -0
- package/src/deployment.ts +147 -0
- package/src/direct-shell-core-empty.ts +10 -0
- package/src/direct-shell-protocol.ts +5 -0
- package/src/direct-shell.ts +341 -0
- package/src/do-session-persistence.ts +1064 -0
- package/src/edge-api.ts +796 -0
- package/src/edge-credentials.ts +56 -0
- package/src/edge-remotes.ts +41 -0
- package/src/edge-workspace-store.ts +339 -0
- package/src/http.ts +212 -0
- package/src/index.ts +196 -0
- package/src/instance.ts +1007 -0
- package/src/isolated-direct-shell-unavailable.ts +11 -0
- package/src/protocol.ts +39 -0
- package/src/release.ts +9 -0
- package/src/session-store.ts +1110 -0
- package/src/sse.ts +86 -0
- package/src/web-search.ts +38 -0
- package/src/workspace.ts +270 -0
- package/wrangler.jsonc +56 -0
|
@@ -0,0 +1,1116 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: "@deepseek-ai/dsh-client-ui-commands",
|
|
3
|
+
factory: (require) => {
|
|
4
|
+
var module = { exports: {} };
|
|
5
|
+
var exports = module.exports;
|
|
6
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
+
let _deepseek_ai_cordis = require("@deepseek-ai/cordis");
|
|
8
|
+
let _deepseek_ai_dsh_client_runtime_client = require("@deepseek-ai/dsh-client-runtime/client");
|
|
9
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
10
|
+
let react = require("react");
|
|
11
|
+
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
12
|
+
//#region lib/types/client/directory.js
|
|
13
|
+
/** One session key's cache cell. */
|
|
14
|
+
var Entry = class {
|
|
15
|
+
state = "cold";
|
|
16
|
+
commands = [];
|
|
17
|
+
/** Bumped at each pull start; only the latest pull may publish its outcome. */
|
|
18
|
+
epoch = 0;
|
|
19
|
+
lastError;
|
|
20
|
+
waiters = [];
|
|
21
|
+
};
|
|
22
|
+
/** The session-keyed directory cache. Plain class — the owning service wires events and RPC. */
|
|
23
|
+
var CommandDirectory = class {
|
|
24
|
+
fetchCommands;
|
|
25
|
+
entries = /* @__PURE__ */ new Map();
|
|
26
|
+
constructor(fetchCommands) {
|
|
27
|
+
this.fetchCommands = fetchCommands;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Current cache status for one session.
|
|
31
|
+
* @param sessionId - session key.
|
|
32
|
+
* @returns the entry status (cold when never touched).
|
|
33
|
+
*/
|
|
34
|
+
status(sessionId) {
|
|
35
|
+
return this.entries.get(sessionId)?.state ?? "cold";
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Synchronous exact-name lookup over one session's hot snapshot.
|
|
39
|
+
* @param sessionId - session key.
|
|
40
|
+
* @param name - command name without the leading slash.
|
|
41
|
+
* @returns the descriptor, or undefined when absent or the entry is not ready.
|
|
42
|
+
*/
|
|
43
|
+
resolve(sessionId, name) {
|
|
44
|
+
const entry = this.entries.get(sessionId);
|
|
45
|
+
if (entry === void 0 || entry.state !== "ready") return void 0;
|
|
46
|
+
return entry.commands.find((c) => c.name === name);
|
|
47
|
+
}
|
|
48
|
+
/** Soft invalidation (commands-changed): background repull on every touched key; ready snapshots keep serving. */
|
|
49
|
+
invalidateAll() {
|
|
50
|
+
for (const key of this.entries.keys()) this.refresh(key);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Hard reset on reconnect: every entry drops its snapshot (the agent world
|
|
54
|
+
* may have changed shape across the generation) and prewarms.
|
|
55
|
+
*/
|
|
56
|
+
resetConnected() {
|
|
57
|
+
for (const [key, entry] of this.entries) {
|
|
58
|
+
entry.state = "cold";
|
|
59
|
+
entry.commands = [];
|
|
60
|
+
this.refresh(key);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Fire-and-forget prewarm of one session (the command source's scope-birth
|
|
65
|
+
* warm hook lands here).
|
|
66
|
+
* @param sessionId - session key.
|
|
67
|
+
*/
|
|
68
|
+
warm(sessionId) {
|
|
69
|
+
const entry = this.entry(sessionId);
|
|
70
|
+
if (entry.state === "cold" || entry.state === "failed") this.refresh(sessionId);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Start one pull for one session. Publishes ready/failed only while it is
|
|
74
|
+
* still the key's latest pull (epoch guard); a ready snapshot is not
|
|
75
|
+
* demoted while the pull flies.
|
|
76
|
+
* @param sessionId - session key.
|
|
77
|
+
* @returns settled when this pull's outcome is published or discarded.
|
|
78
|
+
*/
|
|
79
|
+
async refresh(sessionId) {
|
|
80
|
+
const entry = this.entry(sessionId);
|
|
81
|
+
const epoch = ++entry.epoch;
|
|
82
|
+
if (entry.state !== "ready") entry.state = "pending";
|
|
83
|
+
try {
|
|
84
|
+
const commands = await this.fetchCommands(sessionId);
|
|
85
|
+
if (epoch !== entry.epoch) return;
|
|
86
|
+
entry.commands = commands;
|
|
87
|
+
entry.state = "ready";
|
|
88
|
+
entry.lastError = void 0;
|
|
89
|
+
} catch (error) {
|
|
90
|
+
if (epoch !== entry.epoch) return;
|
|
91
|
+
entry.commands = [];
|
|
92
|
+
entry.state = "failed";
|
|
93
|
+
entry.lastError = error;
|
|
94
|
+
} finally {
|
|
95
|
+
if (epoch === entry.epoch) notifyWaiters(entry);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Strong-wait until one session's catalog is servable (the enter-
|
|
100
|
+
* adjudication "directory must be reached" rule): ready returns at once;
|
|
101
|
+
* cold/failed launch a fresh pull; pending joins the flying one. Rejects
|
|
102
|
+
* when the awaited pull fails or the signal aborts.
|
|
103
|
+
* @param sessionId - session key.
|
|
104
|
+
* @param signal - attempt-scoped abort (the SubmitAttempt signal).
|
|
105
|
+
* @returns the hot command snapshot.
|
|
106
|
+
*/
|
|
107
|
+
async ensureReady(sessionId, signal) {
|
|
108
|
+
const entry = this.entry(sessionId);
|
|
109
|
+
while (true) {
|
|
110
|
+
if (entry.state === "ready") return entry.commands;
|
|
111
|
+
if (entry.state !== "pending") this.refresh(sessionId);
|
|
112
|
+
await settled(entry, signal);
|
|
113
|
+
if (entry.state === "failed") throw new Error(`command directory warmup failed: ${entry.lastError instanceof Error ? entry.lastError.message : String(entry.lastError)}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
entry(sessionId) {
|
|
117
|
+
let entry = this.entries.get(sessionId);
|
|
118
|
+
if (entry === void 0) {
|
|
119
|
+
entry = new Entry();
|
|
120
|
+
this.entries.set(sessionId, entry);
|
|
121
|
+
}
|
|
122
|
+
return entry;
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
/** One settlement tick for one entry: resolves at the next winning publish, rejects on abort. */
|
|
126
|
+
function settled(entry, signal) {
|
|
127
|
+
if (signal.aborted) return Promise.reject(abortReason(signal));
|
|
128
|
+
return new Promise((resolve, reject) => {
|
|
129
|
+
const waiter = () => {
|
|
130
|
+
signal.removeEventListener("abort", onAbort);
|
|
131
|
+
resolve();
|
|
132
|
+
};
|
|
133
|
+
const onAbort = () => {
|
|
134
|
+
entry.waiters = entry.waiters.filter((w) => w !== waiter);
|
|
135
|
+
reject(abortReason(signal));
|
|
136
|
+
};
|
|
137
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
138
|
+
entry.waiters.push(waiter);
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
function notifyWaiters(entry) {
|
|
142
|
+
const woken = entry.waiters;
|
|
143
|
+
entry.waiters = [];
|
|
144
|
+
for (const wake of woken) wake();
|
|
145
|
+
}
|
|
146
|
+
/** Normalize an abort into an Error rejection. */
|
|
147
|
+
function abortReason(signal) {
|
|
148
|
+
return signal.reason instanceof Error ? signal.reason : /* @__PURE__ */ new Error("command directory wait aborted");
|
|
149
|
+
}
|
|
150
|
+
//#endregion
|
|
151
|
+
//#region lib/types/client/popup.js
|
|
152
|
+
/**
|
|
153
|
+
* Headless popupSelect shell state: one controller per client
|
|
154
|
+
* session, owned by CommandUiRuntime's per-session map and torn down by the
|
|
155
|
+
* session scope disposer. The shell is a transient layer (never in the input
|
|
156
|
+
* state machine): it loads options once, filters them locally against the
|
|
157
|
+
* shell's own search text, and settles a selection through the context
|
|
158
|
+
* captured at open time. Draft consumption and composer focus are injected
|
|
159
|
+
* callbacks — the session wiring dispatches the consume-token event (the
|
|
160
|
+
* Input side owns the span/bare-token CAS guard) and focuses the composer;
|
|
161
|
+
* the controller never touches the input machine.
|
|
162
|
+
*/
|
|
163
|
+
const CLOSED = {
|
|
164
|
+
open: false,
|
|
165
|
+
command: null,
|
|
166
|
+
status: "pending",
|
|
167
|
+
options: [],
|
|
168
|
+
search: "",
|
|
169
|
+
active: 0,
|
|
170
|
+
submitting: false,
|
|
171
|
+
confirming: null,
|
|
172
|
+
acknowledged: false,
|
|
173
|
+
error: null
|
|
174
|
+
};
|
|
175
|
+
/**
|
|
176
|
+
* Filter option rows against the shell's local search text (case-insensitive
|
|
177
|
+
* substring over label and detail; blank search keeps every row).
|
|
178
|
+
* @param options - the loaded rows.
|
|
179
|
+
* @param search - the shell's search text.
|
|
180
|
+
* @returns the rows the shell shows and highlights over.
|
|
181
|
+
*/
|
|
182
|
+
function filterOptions(options, search) {
|
|
183
|
+
const query = search.trim().toLowerCase();
|
|
184
|
+
if (query === "") return options;
|
|
185
|
+
return options.filter((o) => o.label.toLowerCase().includes(query) || (o.detail?.toLowerCase().includes(query) ?? false));
|
|
186
|
+
}
|
|
187
|
+
/** The shell's error-strip line for a settlement failure. */
|
|
188
|
+
function errorText(error) {
|
|
189
|
+
return error instanceof Error ? error.message : String(error);
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Headless controller of one session's popupSelect shell. Late settlements
|
|
193
|
+
* lose their write rights through binding identity: dismiss/dispose/reopen
|
|
194
|
+
* swap the binding, so a settling options fetch or onSelect that no longer
|
|
195
|
+
* matches writes nothing and consumes nothing.
|
|
196
|
+
*/
|
|
197
|
+
var PopupSelectController = class {
|
|
198
|
+
deps;
|
|
199
|
+
/** Shell state store (the overlay component subscribes here). */
|
|
200
|
+
state = (0, _deepseek_ai_dsh_client_runtime_client.createSnapshotStore)(CLOSED);
|
|
201
|
+
binding = null;
|
|
202
|
+
/**
|
|
203
|
+
* @param deps - session-wiring callbacks (token consumption + composer focus).
|
|
204
|
+
*/
|
|
205
|
+
constructor(deps) {
|
|
206
|
+
this.deps = deps;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Open the shell for one command: publish pending state and fetch options
|
|
210
|
+
* once through the business spec. A reopen supersedes the previous shell
|
|
211
|
+
* (its options fetch is aborted, its late settlements are dropped).
|
|
212
|
+
* @param command - command name the shell serves.
|
|
213
|
+
* @param spec - the registered popupSelect spec.
|
|
214
|
+
* @param context - open-time context snapshot, handed verbatim to options/onSelect.
|
|
215
|
+
* @param segment - open-time token segment snapshot for post-select consumption.
|
|
216
|
+
*/
|
|
217
|
+
open(command, spec, context, segment) {
|
|
218
|
+
this.binding?.abort.abort();
|
|
219
|
+
const binding = {
|
|
220
|
+
command,
|
|
221
|
+
spec,
|
|
222
|
+
context,
|
|
223
|
+
segment,
|
|
224
|
+
abort: new AbortController()
|
|
225
|
+
};
|
|
226
|
+
this.binding = binding;
|
|
227
|
+
this.state.set({
|
|
228
|
+
...CLOSED,
|
|
229
|
+
open: true,
|
|
230
|
+
command
|
|
231
|
+
});
|
|
232
|
+
this.load(binding);
|
|
233
|
+
}
|
|
234
|
+
/** Run the one options fetch of a binding; settlement rights die with the binding. */
|
|
235
|
+
load(binding) {
|
|
236
|
+
binding.spec.options(binding.context, binding.abort.signal).then((options) => {
|
|
237
|
+
if (this.binding !== binding) return;
|
|
238
|
+
this.state.set({
|
|
239
|
+
...this.state.getSnapshot(),
|
|
240
|
+
status: "ready",
|
|
241
|
+
options,
|
|
242
|
+
active: 0,
|
|
243
|
+
error: null
|
|
244
|
+
});
|
|
245
|
+
}, (error) => {
|
|
246
|
+
if (this.binding !== binding) return;
|
|
247
|
+
console.error(`[ui-commands] popupSelect options failed for /${binding.command}:`, error);
|
|
248
|
+
this.state.set({
|
|
249
|
+
...this.state.getSnapshot(),
|
|
250
|
+
status: "failed",
|
|
251
|
+
options: [],
|
|
252
|
+
active: 0,
|
|
253
|
+
error: errorText(error)
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
/** Re-run a failed options fetch (search survives; no-op unless status is 'failed'). */
|
|
258
|
+
retry() {
|
|
259
|
+
const binding = this.binding;
|
|
260
|
+
const s = this.state.getSnapshot();
|
|
261
|
+
if (binding === null || !s.open || s.status !== "failed") return;
|
|
262
|
+
this.state.set({
|
|
263
|
+
...s,
|
|
264
|
+
status: "pending",
|
|
265
|
+
error: null
|
|
266
|
+
});
|
|
267
|
+
this.load(binding);
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Replace the local search text (pure local filter — the provider is never
|
|
271
|
+
* re-queried) and rebase the highlight onto the new filtered list.
|
|
272
|
+
* @param search - the shell search input's text.
|
|
273
|
+
*/
|
|
274
|
+
setSearch(search) {
|
|
275
|
+
const s = this.state.getSnapshot();
|
|
276
|
+
if (!s.open || s.submitting || s.confirming !== null || search === s.search) return;
|
|
277
|
+
this.state.set({
|
|
278
|
+
...s,
|
|
279
|
+
search,
|
|
280
|
+
active: 0
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Move the highlight across the filtered rows (wraps around; no-op unless
|
|
285
|
+
* options are ready and no selection is in flight).
|
|
286
|
+
* @param dir - +1 down, -1 up.
|
|
287
|
+
*/
|
|
288
|
+
move(dir) {
|
|
289
|
+
const s = this.state.getSnapshot();
|
|
290
|
+
if (!s.open || s.status !== "ready" || s.submitting || s.confirming !== null) return;
|
|
291
|
+
const rows = filterOptions(s.options, s.search);
|
|
292
|
+
if (rows.length === 0) return;
|
|
293
|
+
const active = (s.active + dir + rows.length) % rows.length;
|
|
294
|
+
this.state.set({
|
|
295
|
+
...s,
|
|
296
|
+
active
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Set the highlight directly (pointer hover; no-op unless ready, idle, and
|
|
301
|
+
* in filtered range).
|
|
302
|
+
* @param index - filtered-row index.
|
|
303
|
+
*/
|
|
304
|
+
highlight(index) {
|
|
305
|
+
const s = this.state.getSnapshot();
|
|
306
|
+
if (!s.open || s.status !== "ready" || s.submitting || s.confirming !== null) return;
|
|
307
|
+
if (index < 0 || index >= filterOptions(s.options, s.search).length || index === s.active) return;
|
|
308
|
+
this.state.set({
|
|
309
|
+
...s,
|
|
310
|
+
active: index
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Select one filtered row: single-flight — the first call enters
|
|
315
|
+
* `submitting` and later calls no-op until it settles. Success consumes the
|
|
316
|
+
* open-time token segment (a false CAS answer is benign), closes, and
|
|
317
|
+
* returns focus to the composer. Failure keeps the shell open with search,
|
|
318
|
+
* highlight, and token intact, surfaces the error, and re-arms select as
|
|
319
|
+
* the retry.
|
|
320
|
+
* @param index - filtered-row index (callers pass the highlight or the clicked row).
|
|
321
|
+
* @returns settled when the attempt has closed the shell or surfaced its failure.
|
|
322
|
+
*/
|
|
323
|
+
async select(index) {
|
|
324
|
+
const binding = this.binding;
|
|
325
|
+
const s = this.state.getSnapshot();
|
|
326
|
+
if (binding === null || !s.open || s.status !== "ready" || s.submitting || s.confirming !== null) return;
|
|
327
|
+
const option = filterOptions(s.options, s.search)[index];
|
|
328
|
+
if (option === void 0) return;
|
|
329
|
+
if (option.confirmation !== void 0) {
|
|
330
|
+
this.state.set({
|
|
331
|
+
...s,
|
|
332
|
+
confirming: option,
|
|
333
|
+
acknowledged: false,
|
|
334
|
+
error: null
|
|
335
|
+
});
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
await this.settle(binding, option);
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Update the explicit checkbox for the currently pending risk gate.
|
|
342
|
+
* @param acknowledged - whether the user has acknowledged the displayed risk.
|
|
343
|
+
*/
|
|
344
|
+
acknowledge(acknowledged) {
|
|
345
|
+
const s = this.state.getSnapshot();
|
|
346
|
+
if (!s.open || s.submitting || s.confirming === null || s.acknowledged === acknowledged) return;
|
|
347
|
+
this.state.set({
|
|
348
|
+
...s,
|
|
349
|
+
acknowledged
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
/** Cancel only the risk gate and return to the still-open option picker. */
|
|
353
|
+
cancelConfirmation() {
|
|
354
|
+
const s = this.state.getSnapshot();
|
|
355
|
+
if (!s.open || s.submitting || s.confirming === null) return;
|
|
356
|
+
this.state.set({
|
|
357
|
+
...s,
|
|
358
|
+
confirming: null,
|
|
359
|
+
acknowledged: false
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
/** Settle the gated option only after the checkbox is acknowledged. */
|
|
363
|
+
async confirm() {
|
|
364
|
+
const binding = this.binding;
|
|
365
|
+
const s = this.state.getSnapshot();
|
|
366
|
+
if (binding === null || !s.open || s.submitting || s.confirming === null || !s.acknowledged) return;
|
|
367
|
+
await this.settle(binding, s.confirming);
|
|
368
|
+
}
|
|
369
|
+
/** Run the business settlement for an already admitted option. */
|
|
370
|
+
async settle(binding, option) {
|
|
371
|
+
const s = this.state.getSnapshot();
|
|
372
|
+
if (this.binding !== binding || !s.open || s.submitting) return;
|
|
373
|
+
this.state.set({
|
|
374
|
+
...s,
|
|
375
|
+
submitting: true,
|
|
376
|
+
confirming: null,
|
|
377
|
+
acknowledged: false,
|
|
378
|
+
error: null
|
|
379
|
+
});
|
|
380
|
+
try {
|
|
381
|
+
await binding.spec.onSelect(option, binding.context);
|
|
382
|
+
} catch (error) {
|
|
383
|
+
console.error(`[ui-commands] popupSelect onSelect failed for /${binding.command}:`, error);
|
|
384
|
+
if (this.binding !== binding) return;
|
|
385
|
+
this.state.set({
|
|
386
|
+
...this.state.getSnapshot(),
|
|
387
|
+
submitting: false,
|
|
388
|
+
error: errorText(error)
|
|
389
|
+
});
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
if (this.binding !== binding) return;
|
|
393
|
+
this.deps.consume(binding.segment);
|
|
394
|
+
this.binding = null;
|
|
395
|
+
this.state.set(CLOSED);
|
|
396
|
+
this.deps.focusComposer();
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Close the shell; aborts a flying options fetch and revokes settlement
|
|
400
|
+
* rights. An outside pointer interaction dismisses plainly (the click's own
|
|
401
|
+
* target takes focus); Escape passes focusComposer to return focus explicitly.
|
|
402
|
+
* @param opts - focusComposer: also restore composer focus (Escape path).
|
|
403
|
+
*/
|
|
404
|
+
dismiss(opts) {
|
|
405
|
+
if (this.binding === null) return;
|
|
406
|
+
this.binding.abort.abort();
|
|
407
|
+
this.binding = null;
|
|
408
|
+
this.state.set(CLOSED);
|
|
409
|
+
if (opts?.focusComposer === true) this.deps.focusComposer();
|
|
410
|
+
}
|
|
411
|
+
/** Scope-teardown disposer: abort in-flight work and clear state (no focus side effect). */
|
|
412
|
+
dispose() {
|
|
413
|
+
this.binding?.abort.abort();
|
|
414
|
+
this.binding = null;
|
|
415
|
+
this.state.set(CLOSED);
|
|
416
|
+
}
|
|
417
|
+
};
|
|
418
|
+
//#endregion
|
|
419
|
+
//#region lib/types/client/service.js
|
|
420
|
+
/**
|
|
421
|
+
* CommandUiRuntime (`ctx.commandUi`): the '/' command source over the
|
|
422
|
+
* session-keyed directory, the client-contribution registry, and the
|
|
423
|
+
* per-session popupSelect controllers. Candidate synthesis merges the host
|
|
424
|
+
* catalog with contributions by availability, then fuzzy query/position
|
|
425
|
+
* filtering; a host/contribution name collision fails loud. Every execute
|
|
426
|
+
* addresses the session's agent by sessionId — sessions are always
|
|
427
|
+
* agent-backed.
|
|
428
|
+
*/
|
|
429
|
+
/** Recover the command name from a line the Host confirmed as executed. */
|
|
430
|
+
function submittedCommandName(line) {
|
|
431
|
+
const trimmed = line.trim();
|
|
432
|
+
const separator = trimmed.search(/\s/u);
|
|
433
|
+
return (separator === -1 ? trimmed : trimmed.slice(0, separator)).slice(1);
|
|
434
|
+
}
|
|
435
|
+
/** Extra weight for command-name starts and separator boundaries. */
|
|
436
|
+
function boundaryBonus(name, index) {
|
|
437
|
+
return index === 0 || name.charAt(index - 1) === "-" || name.charAt(index - 1) === "_" ? 8 : 0;
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Score the strongest ordered-subsequence alignment in O(name × query).
|
|
441
|
+
* Boundary and adjacent matches earn weight; skipped and leading characters
|
|
442
|
+
* cost weight.
|
|
443
|
+
*/
|
|
444
|
+
function fuzzyScore(name, query) {
|
|
445
|
+
if (query === "") return 0;
|
|
446
|
+
if (query.length > name.length) return void 0;
|
|
447
|
+
const noMatch = Number.NEGATIVE_INFINITY;
|
|
448
|
+
let previous = Array(name.length).fill(noMatch);
|
|
449
|
+
for (let index = 0; index < name.length; index++) if (name.charAt(index) === query.charAt(0)) previous[index] = 1 + boundaryBonus(name, index) - index;
|
|
450
|
+
for (let queryIndex = 1; queryIndex < query.length; queryIndex++) {
|
|
451
|
+
const current = Array(name.length).fill(noMatch);
|
|
452
|
+
let bestGapped = noMatch;
|
|
453
|
+
for (let index = 0; index < name.length; index++) {
|
|
454
|
+
const gappedIndex = index - 2;
|
|
455
|
+
if (gappedIndex >= 0) {
|
|
456
|
+
const prior = previous[gappedIndex] ?? noMatch;
|
|
457
|
+
if (prior !== noMatch) bestGapped = Math.max(bestGapped, prior + gappedIndex);
|
|
458
|
+
}
|
|
459
|
+
if (name.charAt(index) !== query.charAt(queryIndex)) continue;
|
|
460
|
+
const bonus = 1 + boundaryBonus(name, index);
|
|
461
|
+
const adjacent = index > 0 ? previous[index - 1] ?? noMatch : noMatch;
|
|
462
|
+
if (adjacent !== noMatch) current[index] = adjacent + bonus + 4;
|
|
463
|
+
if (bestGapped !== noMatch) current[index] = Math.max(current[index] ?? noMatch, bestGapped + bonus + 1 - index);
|
|
464
|
+
}
|
|
465
|
+
previous = current;
|
|
466
|
+
}
|
|
467
|
+
let best = noMatch;
|
|
468
|
+
for (const score of previous) best = Math.max(best, score);
|
|
469
|
+
return best === noMatch ? void 0 : best;
|
|
470
|
+
}
|
|
471
|
+
/** Case-insensitive fuzzy filtering with stable ordering for equal matches. */
|
|
472
|
+
function fuzzyCandidates(candidates, rawQuery) {
|
|
473
|
+
const query = rawQuery.toLowerCase();
|
|
474
|
+
if (query === "") return candidates;
|
|
475
|
+
const ranked = [];
|
|
476
|
+
candidates.forEach((candidate, index) => {
|
|
477
|
+
const name = candidate.name.toLowerCase();
|
|
478
|
+
const score = fuzzyScore(name, query);
|
|
479
|
+
if (score !== void 0) ranked.push({
|
|
480
|
+
candidate,
|
|
481
|
+
index,
|
|
482
|
+
prefix: name.startsWith(query),
|
|
483
|
+
score
|
|
484
|
+
});
|
|
485
|
+
});
|
|
486
|
+
ranked.sort((left, right) => Number(right.prefix) - Number(left.prefix) || right.score - left.score || left.index - right.index);
|
|
487
|
+
return ranked.map((match) => match.candidate);
|
|
488
|
+
}
|
|
489
|
+
/** Command surface: session-keyed directory + '/' source + contribution registry + per-session popups. */
|
|
490
|
+
var CommandUiRuntime = class extends _deepseek_ai_cordis.Service {
|
|
491
|
+
static inject = [
|
|
492
|
+
"inputTriggers",
|
|
493
|
+
"sessions",
|
|
494
|
+
"remote",
|
|
495
|
+
"remote.commands"
|
|
496
|
+
];
|
|
497
|
+
directory;
|
|
498
|
+
live = {
|
|
499
|
+
contributions: /* @__PURE__ */ new Map(),
|
|
500
|
+
decorations: /* @__PURE__ */ new Map(),
|
|
501
|
+
popups: /* @__PURE__ */ new Map()
|
|
502
|
+
};
|
|
503
|
+
/**
|
|
504
|
+
* @param ctx - owning root context (plugin fiber; the service registers
|
|
505
|
+
* itself as `command` and follows that fiber's lifetime).
|
|
506
|
+
*/
|
|
507
|
+
constructor(ctx) {
|
|
508
|
+
super(ctx, "commandUi");
|
|
509
|
+
this.directory = new CommandDirectory(async (sessionId) => {
|
|
510
|
+
if (this.sessions().subagentAddress(sessionId) !== void 0) return [];
|
|
511
|
+
const result = await ctx.remote.commands.list(sessionId);
|
|
512
|
+
if (!result.ok) throw new Error(`command.list failed: ${result.error.code}: ${result.error.message}`);
|
|
513
|
+
return result.value;
|
|
514
|
+
});
|
|
515
|
+
const inputTriggers = ctx.get("inputTriggers");
|
|
516
|
+
if (inputTriggers === void 0) throw new Error("ui-commands: slash service unavailable");
|
|
517
|
+
ctx.effect(() => inputTriggers.registerSource({
|
|
518
|
+
trigger: "/",
|
|
519
|
+
name: "command",
|
|
520
|
+
candidates: (session, req) => this.candidates(session, req),
|
|
521
|
+
onPick: (pick) => this.dispatch(pick),
|
|
522
|
+
matchSpace: (session, token) => this.matchSpace(session, token),
|
|
523
|
+
matchEnter: (session, line, signal) => this.matchEnter(session, line, signal),
|
|
524
|
+
warm: (session) => {
|
|
525
|
+
this.directory.warm(session.sessionId);
|
|
526
|
+
}
|
|
527
|
+
}), "command: slash source");
|
|
528
|
+
ctx.remote.$on("commands/change", () => {
|
|
529
|
+
this.directory.invalidateAll();
|
|
530
|
+
});
|
|
531
|
+
ctx.remote.$on("agent-preset/selected", (sessionId) => {
|
|
532
|
+
this.directory.refresh(sessionId);
|
|
533
|
+
});
|
|
534
|
+
ctx.on("connection/reset", () => {
|
|
535
|
+
this.directory.resetConnected();
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Register one client command contribution; effect disposer (rides the
|
|
540
|
+
* caller's fiber). Duplicate names throw.
|
|
541
|
+
* @param contribution - the contribution (descriptor + availability + popup spec).
|
|
542
|
+
* @returns the disposer removing the registration.
|
|
543
|
+
*/
|
|
544
|
+
register(contribution) {
|
|
545
|
+
const dispose = this.ctx.effect(() => {
|
|
546
|
+
const { contributions } = this.live;
|
|
547
|
+
if (contributions.has(contribution.name)) throw new Error(`ui-commands: duplicate contribution for /${contribution.name}`);
|
|
548
|
+
contributions.set(contribution.name, contribution);
|
|
549
|
+
return () => {
|
|
550
|
+
contributions.delete(contribution.name);
|
|
551
|
+
};
|
|
552
|
+
}, "command.register()");
|
|
553
|
+
return () => {
|
|
554
|
+
dispose();
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
/**
|
|
558
|
+
* Hang a bare-invocation decoration on one host command; effect disposer
|
|
559
|
+
* (rides the caller's fiber). Duplicate names throw.
|
|
560
|
+
* @param decoration - host command name + availability + popup spec.
|
|
561
|
+
* @returns the disposer removing the registration.
|
|
562
|
+
*/
|
|
563
|
+
decorate(decoration) {
|
|
564
|
+
const dispose = this.ctx.effect(() => {
|
|
565
|
+
const { decorations } = this.live;
|
|
566
|
+
if (decorations.has(decoration.name)) throw new Error(`ui-commands: duplicate decoration for /${decoration.name}`);
|
|
567
|
+
decorations.set(decoration.name, decoration);
|
|
568
|
+
return () => {
|
|
569
|
+
decorations.delete(decoration.name);
|
|
570
|
+
};
|
|
571
|
+
}, "command.decorate()");
|
|
572
|
+
return () => {
|
|
573
|
+
dispose();
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* Resolve the per-session popup controller (lazy; dies with the session
|
|
578
|
+
* scope). The controller's consume callback dispatches the scoped
|
|
579
|
+
* consume-token event back to this session; focusComposer reaches the
|
|
580
|
+
* composer through the overlay slot currency.
|
|
581
|
+
* @param actx - session-scope ctx.
|
|
582
|
+
* @returns the resident controller.
|
|
583
|
+
*/
|
|
584
|
+
popupFor(actx) {
|
|
585
|
+
const id = this.sessions().scopeOf(actx);
|
|
586
|
+
if (id === void 0) throw new Error("command.popupFor requires a session scope");
|
|
587
|
+
const { popups } = this.live;
|
|
588
|
+
const existing = popups.get(id);
|
|
589
|
+
if (existing !== void 0) return existing;
|
|
590
|
+
const controller = new PopupSelectController({
|
|
591
|
+
consume: (segment) => actx.bail(actx, "slash/input-consume-token", { guard: segment.via === "menu" ? {
|
|
592
|
+
kind: "span",
|
|
593
|
+
span: segment.span
|
|
594
|
+
} : {
|
|
595
|
+
kind: "bare-token",
|
|
596
|
+
token: segment.token
|
|
597
|
+
} }) === true,
|
|
598
|
+
focusComposer: () => {
|
|
599
|
+
this.focusHooks.get(id)?.();
|
|
600
|
+
}
|
|
601
|
+
});
|
|
602
|
+
popups.set(id, controller);
|
|
603
|
+
actx.effect(() => () => {
|
|
604
|
+
controller.dispose();
|
|
605
|
+
popups.delete(id);
|
|
606
|
+
this.focusHooks.delete(id);
|
|
607
|
+
}, "command: session popup");
|
|
608
|
+
return controller;
|
|
609
|
+
}
|
|
610
|
+
/** Composer focus hooks by session (the overlay wiring binds the textarea focus here). */
|
|
611
|
+
focusHooks = /* @__PURE__ */ new Map();
|
|
612
|
+
/**
|
|
613
|
+
* Bind one session's composer-focus hook (overlay slot wiring; unbind on unmount).
|
|
614
|
+
* @param id - session id.
|
|
615
|
+
* @param focus - textarea focus callback.
|
|
616
|
+
* @returns the unbind disposer.
|
|
617
|
+
*/
|
|
618
|
+
bindComposerFocus(id, focus) {
|
|
619
|
+
this.focusHooks.set(id, focus);
|
|
620
|
+
return () => {
|
|
621
|
+
if (this.focusHooks.get(id) === focus) this.focusHooks.delete(id);
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
/** Menu candidates: host catalog + contribution availability, then position filtering and fuzzy name ranking. */
|
|
625
|
+
async candidates(session, req) {
|
|
626
|
+
const list = await this.directory.ensureReady(session.sessionId, req.signal);
|
|
627
|
+
const rows = [];
|
|
628
|
+
const seen = /* @__PURE__ */ new Set();
|
|
629
|
+
for (const c of list) {
|
|
630
|
+
seen.add(c.name);
|
|
631
|
+
rows.push({
|
|
632
|
+
name: c.name,
|
|
633
|
+
description: c.description,
|
|
634
|
+
...c.input !== void 0 ? { hint: c.input.hint } : {}
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
for (const contribution of this.live.contributions.values()) {
|
|
638
|
+
if (!contribution.available(session)) continue;
|
|
639
|
+
if (seen.has(contribution.name)) throw new Error(`ui-commands: contribution /${contribution.name} collides with a host command`);
|
|
640
|
+
rows.push({
|
|
641
|
+
name: contribution.name,
|
|
642
|
+
description: contribution.description
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
return fuzzyCandidates(rows.filter((c) => req.position === "leading" || c.hint === void 0), req.query);
|
|
646
|
+
}
|
|
647
|
+
/** Decision table, menu column: contribution/decorated-host → popup; host input → claim; host bare → detached execute. */
|
|
648
|
+
dispatch(pick) {
|
|
649
|
+
const name = pick.candidate.name;
|
|
650
|
+
const contribution = this.live.contributions.get(name);
|
|
651
|
+
if (contribution !== void 0 && contribution.available(pick.session)) {
|
|
652
|
+
this.openPopup(name, contribution.ui, pick.session, {
|
|
653
|
+
via: "menu",
|
|
654
|
+
span: pick.span
|
|
655
|
+
});
|
|
656
|
+
return "handled";
|
|
657
|
+
}
|
|
658
|
+
const desc = this.directory.resolve(pick.session.sessionId, name);
|
|
659
|
+
if (desc === void 0) return void 0;
|
|
660
|
+
const decoration = this.live.decorations.get(name);
|
|
661
|
+
if (decoration !== void 0 && decoration.available(pick.session)) {
|
|
662
|
+
this.openPopup(name, decoration.ui, pick.session, {
|
|
663
|
+
via: "menu",
|
|
664
|
+
span: pick.span
|
|
665
|
+
});
|
|
666
|
+
return "handled";
|
|
667
|
+
}
|
|
668
|
+
if (desc.input !== void 0) return { claim: this.leadingClaim(desc, pick.session) };
|
|
669
|
+
this.consumeVia(pick.session.sessionId, {
|
|
670
|
+
via: "menu",
|
|
671
|
+
span: pick.span
|
|
672
|
+
});
|
|
673
|
+
this.runDetached(desc, pick.session, `/${name}`);
|
|
674
|
+
return "handled";
|
|
675
|
+
}
|
|
676
|
+
/** Decision table, space column: hot-key sync check; only host leadingInput claims. */
|
|
677
|
+
matchSpace(session, token) {
|
|
678
|
+
if (!token.startsWith("/")) return void 0;
|
|
679
|
+
const name = token.slice(1);
|
|
680
|
+
if (this.live.contributions.has(name)) return void 0;
|
|
681
|
+
const desc = this.directory.resolve(session.sessionId, name);
|
|
682
|
+
if (desc === void 0 || desc.input === void 0) return void 0;
|
|
683
|
+
return { claim: this.leadingClaim(desc, session) };
|
|
684
|
+
}
|
|
685
|
+
/**
|
|
686
|
+
* Decision table, enter column. Strong-waits the session's catalog (a
|
|
687
|
+
* warmup failure rejects — never a silent downgrade). Contributions and
|
|
688
|
+
* bare host commands act on the bare token only; leadingInput claims
|
|
689
|
+
* args-tolerant.
|
|
690
|
+
*/
|
|
691
|
+
async matchEnter(session, line, signal) {
|
|
692
|
+
const trimmed = line.trim();
|
|
693
|
+
if (!trimmed.startsWith("/")) return void 0;
|
|
694
|
+
const ws = trimmed.search(/\s/);
|
|
695
|
+
const token = ws === -1 ? trimmed : trimmed.slice(0, ws);
|
|
696
|
+
const bare = ws === -1;
|
|
697
|
+
const name = token.slice(1);
|
|
698
|
+
if (name === "") return void 0;
|
|
699
|
+
const contribution = this.live.contributions.get(name);
|
|
700
|
+
if (contribution !== void 0 && contribution.available(session)) {
|
|
701
|
+
if (!bare) return void 0;
|
|
702
|
+
this.openPopup(name, contribution.ui, session, {
|
|
703
|
+
via: "enter",
|
|
704
|
+
token
|
|
705
|
+
});
|
|
706
|
+
return "handled";
|
|
707
|
+
}
|
|
708
|
+
await this.directory.ensureReady(session.sessionId, signal);
|
|
709
|
+
const desc = this.directory.resolve(session.sessionId, name);
|
|
710
|
+
if (desc === void 0) return void 0;
|
|
711
|
+
if (bare) {
|
|
712
|
+
const decoration = this.live.decorations.get(name);
|
|
713
|
+
if (decoration !== void 0 && decoration.available(session)) {
|
|
714
|
+
this.openPopup(name, decoration.ui, session, {
|
|
715
|
+
via: "enter",
|
|
716
|
+
token
|
|
717
|
+
});
|
|
718
|
+
return "handled";
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
if (desc.input !== void 0) return { claim: this.leadingClaim(desc, session) };
|
|
722
|
+
if (!bare) return void 0;
|
|
723
|
+
this.consumeVia(session.sessionId, {
|
|
724
|
+
via: "enter",
|
|
725
|
+
token
|
|
726
|
+
});
|
|
727
|
+
this.runDetached(desc, session, trimmed);
|
|
728
|
+
return "handled";
|
|
729
|
+
}
|
|
730
|
+
/** Open the session's popup for one contribution or decoration (menu pick / bare enter). */
|
|
731
|
+
openPopup(name, ui, session, segment) {
|
|
732
|
+
const actx = this.scopeFor(session.sessionId);
|
|
733
|
+
if (actx === void 0) return;
|
|
734
|
+
this.popupFor(actx).open(name, ui, session, segment);
|
|
735
|
+
}
|
|
736
|
+
/** Build the leadingInput claim: token `/name ` + the command.execute submit transaction. */
|
|
737
|
+
leadingClaim(desc, session) {
|
|
738
|
+
const token = `/${desc.name} `;
|
|
739
|
+
return {
|
|
740
|
+
token,
|
|
741
|
+
...desc.input !== void 0 ? { hint: desc.input.hint } : {},
|
|
742
|
+
submit: (args, _actx) => this.execute(session, token + args)
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
/**
|
|
746
|
+
* The command.execute transaction, addressed to the session's agent — pure
|
|
747
|
+
* admission semantics. An unmatched line reports an error outcome (the
|
|
748
|
+
* composer's immediate admission feedback); an admitted command reports
|
|
749
|
+
* plain success regardless of its handler outcome, because the host
|
|
750
|
+
* executor durably logged the lifecycle (`command/run`/`command/done`) and
|
|
751
|
+
* the outcome renders as a persistent flow node — the composer never
|
|
752
|
+
* echoes it. Transport failures throw.
|
|
753
|
+
*/
|
|
754
|
+
async execute(session, line) {
|
|
755
|
+
const result = await this.ctx.remote.commands.execute(session.sessionId, line);
|
|
756
|
+
if (!result.ok) throw new Error(`command.execute failed: ${result.error.code}: ${result.error.message}`);
|
|
757
|
+
if (result.value === void 0) return {
|
|
758
|
+
kind: "error",
|
|
759
|
+
text: `unknown or malformed command: ${line}`
|
|
760
|
+
};
|
|
761
|
+
this.notifyExecuted(session.sessionId, submittedCommandName(line), result.value.result);
|
|
762
|
+
return { kind: "success" };
|
|
763
|
+
}
|
|
764
|
+
/** Publish the local acknowledgment without letting an observer change command admission. */
|
|
765
|
+
notifyExecuted(sessionId, name, result) {
|
|
766
|
+
const args = [
|
|
767
|
+
"command/executed",
|
|
768
|
+
sessionId,
|
|
769
|
+
name,
|
|
770
|
+
result
|
|
771
|
+
];
|
|
772
|
+
for (const listener of this.ctx.events.dispatch("emit", args)) try {
|
|
773
|
+
const returned = listener(sessionId, name, result);
|
|
774
|
+
if (returned != null && typeof returned.then === "function") Promise.resolve(returned).then(void 0, (error) => {
|
|
775
|
+
this.warnExecutedListenerFailure(name, error);
|
|
776
|
+
});
|
|
777
|
+
} catch (error) {
|
|
778
|
+
this.warnExecutedListenerFailure(name, error);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
/** Log one contained `command/executed` observer failure. */
|
|
782
|
+
warnExecutedListenerFailure(name, error) {
|
|
783
|
+
this.ctx.logger.warn("client command: a command/executed listener for \"%s\" failed", name);
|
|
784
|
+
this.ctx.logger.warn(error);
|
|
785
|
+
}
|
|
786
|
+
/**
|
|
787
|
+
* Fire-and-forget execute for the internal ('handled') paths. Outcomes are
|
|
788
|
+
* NOT surfaced here: the host executor durably logs the command lifecycle
|
|
789
|
+
* (`command/run`/`command/done`), and the mux-broadcast events render as a
|
|
790
|
+
* persistent flow node on every tab. Only a transport/admission failure —
|
|
791
|
+
* which never entered a handler and therefore never logged — falls back to
|
|
792
|
+
* the composer notice as immediate feedback.
|
|
793
|
+
*/
|
|
794
|
+
runDetached(desc, session, line) {
|
|
795
|
+
this.execute(session, line).then((outcome) => {
|
|
796
|
+
if (outcome.kind === "error") this.noticeFor(session.sessionId, "error", outcome.text ?? `/${desc.name} failed`);
|
|
797
|
+
}, (error) => {
|
|
798
|
+
this.noticeFor(session.sessionId, "error", error instanceof Error ? error.message : String(error));
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
/** Dispatch a consume-token event to one session (menu-pick / bare-enter execute paths). */
|
|
802
|
+
consumeVia(id, segment) {
|
|
803
|
+
const actx = this.scopeFor(id);
|
|
804
|
+
if (actx === void 0) return;
|
|
805
|
+
actx.bail(actx, "slash/input-consume-token", { guard: segment.via === "menu" ? {
|
|
806
|
+
kind: "span",
|
|
807
|
+
span: segment.span
|
|
808
|
+
} : {
|
|
809
|
+
kind: "bare-token",
|
|
810
|
+
token: segment.token
|
|
811
|
+
} });
|
|
812
|
+
}
|
|
813
|
+
/** Route an admission/transport failure to the session's composer notice channel (scope gone = attempt died with it). */
|
|
814
|
+
noticeFor(id, level, text) {
|
|
815
|
+
const actx = this.scopeFor(id);
|
|
816
|
+
if (actx === void 0) return;
|
|
817
|
+
const conversation = actx.get("conversation");
|
|
818
|
+
if (conversation === void 0) return;
|
|
819
|
+
conversation.input.for(actx).notify(level, text);
|
|
820
|
+
}
|
|
821
|
+
/** id → actx interchange (registered exchange point: this service coordinates for projection-only sources). */
|
|
822
|
+
scopeFor(id) {
|
|
823
|
+
return this.sessions().scope(id);
|
|
824
|
+
}
|
|
825
|
+
sessions() {
|
|
826
|
+
const sessions = this.ctx.get("sessions");
|
|
827
|
+
if (sessions === void 0) throw new Error("ui-commands: sessions service unavailable");
|
|
828
|
+
return sessions;
|
|
829
|
+
}
|
|
830
|
+
};
|
|
831
|
+
//#endregion
|
|
832
|
+
//#region ../../../node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs
|
|
833
|
+
function r(e) {
|
|
834
|
+
var t, f, n = "";
|
|
835
|
+
if ("string" == typeof e || "number" == typeof e) n += e;
|
|
836
|
+
else if ("object" == typeof e) if (Array.isArray(e)) {
|
|
837
|
+
var o = e.length;
|
|
838
|
+
for (t = 0; t < o; t++) e[t] && (f = r(e[t])) && (n && (n += " "), n += f);
|
|
839
|
+
} else for (f in e) e[f] && (n && (n += " "), n += f);
|
|
840
|
+
return n;
|
|
841
|
+
}
|
|
842
|
+
function clsx() {
|
|
843
|
+
for (var e, t, f = 0, n = "", o = arguments.length; f < o; f++) (e = arguments[f]) && (t = r(e)) && (n && (n += " "), n += t);
|
|
844
|
+
return n;
|
|
845
|
+
}
|
|
846
|
+
//#endregion
|
|
847
|
+
//#region \0dsh-css:/private/var/folders/nm/n5smty9962gf5rhjc5855kt80000gn/T/dsh-edge-release-worktree.XXXXXX.RcJjogaGt4/repo/packages/client/ui-commands/src/client/PopupSelectView.module.css.mjs
|
|
848
|
+
const css = "._4QTx4a_card{z-index:100;--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);border:1px solid var(--dsw-alias-border-inverted);background:var(--dsw-specific-menu);min-width:min(220px,100%);max-width:100%;max-height:320px;box-shadow:var(--dsw-shadow-lv3);border-radius:12px;outline:none;flex-direction:column;padding:4px;display:flex;position:absolute;bottom:calc(100% + 4px);left:0;overflow:hidden}._4QTx4a_viewport{flex-direction:column;min-height:0;display:flex;overflow-y:auto}._4QTx4a_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}._4QTx4a_rowActive{background:var(--dsw-alias-interactive-bg-hover)}._4QTx4a_label{white-space:nowrap;text-overflow:ellipsis;flex:auto;min-width:0;overflow:hidden}._4QTx4a_detail{color:var(--dsw-alias-label-tertiary);white-space:nowrap;text-overflow:ellipsis;font-size:12px;overflow:hidden}._4QTx4a_check{color:var(--dsw-alias-label-primary);flex:none;display:inline-flex}._4QTx4a_status{color:var(--dsw-alias-label-tertiary);padding:8px 10px;font-size:13px}._4QTx4a_search{border:1px 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}._4QTx4a_error{color:var(--dsw-alias-state-error-primary);align-items:center;gap:8px;padding:6px 8px;font-size:12px;display:flex}._4QTx4a_errorText{text-overflow:ellipsis;flex:1;overflow:hidden}._4QTx4a_retry{border:1px 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}";
|
|
849
|
+
const tagId = "@deepseek-ai/dsh-client-ui-commands/PopupSelectView.module.css";
|
|
850
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
851
|
+
const tag = document.createElement("style");
|
|
852
|
+
tag.dataset.plugin = "@deepseek-ai/dsh-client-ui-commands";
|
|
853
|
+
tag.dataset.pluginCss = tagId;
|
|
854
|
+
tag.textContent = css;
|
|
855
|
+
document.head.appendChild(tag);
|
|
856
|
+
}
|
|
857
|
+
var PopupSelectView_module_css_default = {
|
|
858
|
+
"row": "_4QTx4a_row",
|
|
859
|
+
"card": "_4QTx4a_card",
|
|
860
|
+
"rowActive": "_4QTx4a_rowActive",
|
|
861
|
+
"status": "_4QTx4a_status",
|
|
862
|
+
"viewport": "_4QTx4a_viewport",
|
|
863
|
+
"detail": "_4QTx4a_detail",
|
|
864
|
+
"errorText": "_4QTx4a_errorText",
|
|
865
|
+
"label": "_4QTx4a_label",
|
|
866
|
+
"check": "_4QTx4a_check",
|
|
867
|
+
"error": "_4QTx4a_error",
|
|
868
|
+
"search": "_4QTx4a_search",
|
|
869
|
+
"retry": "_4QTx4a_retry"
|
|
870
|
+
};
|
|
871
|
+
//#endregion
|
|
872
|
+
//#region lib/types/client/PopupSelectView.js
|
|
873
|
+
/**
|
|
874
|
+
* Official popupSelect shell: renders one session's PopupSelectController
|
|
875
|
+
* store into the conversation.input.overlay anchor. Unlike the slash menu
|
|
876
|
+
* (combobox — textarea keeps focus), this shell HOLDS focus while open: the
|
|
877
|
+
* inner search input takes focus, plain typing filters the loaded options
|
|
878
|
+
* locally, Enter/↑↓ drive the filtered highlight (scrolled into view), Escape
|
|
879
|
+
* dismisses back to the composer, and ←→ keep the search input's native
|
|
880
|
+
* caret. Any pointer interaction outside the box dismisses (the click's own
|
|
881
|
+
* target takes focus). Closed state renders null; the overlay slot stays
|
|
882
|
+
* mounted. The card height clamps to the space above the composer.
|
|
883
|
+
*/
|
|
884
|
+
/** Design cap on the card height (same MenuDropdown family as the slash menu). */
|
|
885
|
+
const MAX_HEIGHT = 320;
|
|
886
|
+
/**
|
|
887
|
+
* Render the popupSelect shell overlay entry.
|
|
888
|
+
* @param props - injected face: the session's shell controller; `t` rides the standard locale seat.
|
|
889
|
+
* @returns the select card while open; null while closed.
|
|
890
|
+
*/
|
|
891
|
+
function PopupSelectView({ popup, t }) {
|
|
892
|
+
const state = (0, react.useSyncExternalStore)((fn) => popup.state.subscribe(fn), () => popup.state.getSnapshot());
|
|
893
|
+
const cardRef = (0, react.useRef)(null);
|
|
894
|
+
const searchRef = (0, react.useRef)(null);
|
|
895
|
+
const maxHeight = (0, _deepseek_ai_dsh_client_ui_primitives.useAnchoredMaxHeight)(cardRef, MAX_HEIGHT, state);
|
|
896
|
+
const active = state.open ? state.active : null;
|
|
897
|
+
(0, react.useEffect)(() => {
|
|
898
|
+
if (active === null) return;
|
|
899
|
+
cardRef.current?.querySelector("[aria-selected=\"true\"]")?.scrollIntoView({ block: "nearest" });
|
|
900
|
+
}, [active]);
|
|
901
|
+
(0, react.useEffect)(() => {
|
|
902
|
+
if (!state.open || state.confirming !== null) return;
|
|
903
|
+
const onPointerDown = (ev) => {
|
|
904
|
+
if (cardRef.current !== null && ev.target instanceof Node && cardRef.current.contains(ev.target)) return;
|
|
905
|
+
popup.dismiss();
|
|
906
|
+
};
|
|
907
|
+
document.addEventListener("pointerdown", onPointerDown, true);
|
|
908
|
+
return () => {
|
|
909
|
+
document.removeEventListener("pointerdown", onPointerDown, true);
|
|
910
|
+
};
|
|
911
|
+
}, [
|
|
912
|
+
state.open,
|
|
913
|
+
state.confirming,
|
|
914
|
+
popup
|
|
915
|
+
]);
|
|
916
|
+
(0, react.useEffect)(() => {
|
|
917
|
+
if (state.open && state.confirming === null) searchRef.current?.focus();
|
|
918
|
+
}, [state.open, state.confirming]);
|
|
919
|
+
if (!state.open) return null;
|
|
920
|
+
const rows = filterOptions(state.options, state.search);
|
|
921
|
+
const confirmation = state.confirming?.confirmation;
|
|
922
|
+
const onKeyDown = (ev) => {
|
|
923
|
+
switch (ev.key) {
|
|
924
|
+
case "ArrowDown":
|
|
925
|
+
ev.preventDefault();
|
|
926
|
+
popup.move(1);
|
|
927
|
+
return;
|
|
928
|
+
case "ArrowUp":
|
|
929
|
+
ev.preventDefault();
|
|
930
|
+
popup.move(-1);
|
|
931
|
+
return;
|
|
932
|
+
case "Enter":
|
|
933
|
+
ev.preventDefault();
|
|
934
|
+
popup.select(state.active);
|
|
935
|
+
return;
|
|
936
|
+
case "Escape":
|
|
937
|
+
ev.preventDefault();
|
|
938
|
+
popup.dismiss({ focusComposer: true });
|
|
939
|
+
return;
|
|
940
|
+
default:
|
|
941
|
+
}
|
|
942
|
+
};
|
|
943
|
+
return (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [state.confirming === null && (0, react_jsx_runtime.jsxs)("div", {
|
|
944
|
+
ref: cardRef,
|
|
945
|
+
className: PopupSelectView_module_css_default.card,
|
|
946
|
+
style: { maxHeight },
|
|
947
|
+
"aria-label": t("overlay.aria", { command: String(state.command) }),
|
|
948
|
+
onKeyDown,
|
|
949
|
+
children: [
|
|
950
|
+
(0, react_jsx_runtime.jsx)("input", {
|
|
951
|
+
ref: searchRef,
|
|
952
|
+
className: PopupSelectView_module_css_default.search,
|
|
953
|
+
type: "text",
|
|
954
|
+
placeholder: t("search.placeholder"),
|
|
955
|
+
"aria-label": t("search.aria"),
|
|
956
|
+
value: state.search,
|
|
957
|
+
readOnly: state.submitting,
|
|
958
|
+
onChange: (ev) => {
|
|
959
|
+
popup.setSearch(ev.currentTarget.value);
|
|
960
|
+
}
|
|
961
|
+
}),
|
|
962
|
+
state.error !== null && (0, react_jsx_runtime.jsxs)("div", {
|
|
963
|
+
className: PopupSelectView_module_css_default.error,
|
|
964
|
+
role: "alert",
|
|
965
|
+
children: [(0, react_jsx_runtime.jsx)("span", {
|
|
966
|
+
className: PopupSelectView_module_css_default.errorText,
|
|
967
|
+
children: state.error
|
|
968
|
+
}), state.status === "failed" && (0, react_jsx_runtime.jsx)("button", {
|
|
969
|
+
type: "button",
|
|
970
|
+
className: PopupSelectView_module_css_default.retry,
|
|
971
|
+
onClick: () => {
|
|
972
|
+
popup.retry();
|
|
973
|
+
},
|
|
974
|
+
children: t("retry")
|
|
975
|
+
})]
|
|
976
|
+
}),
|
|
977
|
+
state.status === "pending" && (0, react_jsx_runtime.jsx)("div", {
|
|
978
|
+
className: PopupSelectView_module_css_default.status,
|
|
979
|
+
children: t("status.loading")
|
|
980
|
+
}),
|
|
981
|
+
state.submitting && (0, react_jsx_runtime.jsx)("div", {
|
|
982
|
+
className: PopupSelectView_module_css_default.status,
|
|
983
|
+
children: t("status.applying")
|
|
984
|
+
}),
|
|
985
|
+
state.status === "ready" && rows.length === 0 && (0, react_jsx_runtime.jsx)("div", {
|
|
986
|
+
className: PopupSelectView_module_css_default.status,
|
|
987
|
+
children: t("status.empty")
|
|
988
|
+
}),
|
|
989
|
+
state.status === "ready" && (0, react_jsx_runtime.jsx)("div", {
|
|
990
|
+
role: "listbox",
|
|
991
|
+
"aria-label": t("listbox.aria", { command: String(state.command) }),
|
|
992
|
+
className: PopupSelectView_module_css_default.viewport,
|
|
993
|
+
children: rows.map((option, index) => (0, react_jsx_runtime.jsxs)("div", {
|
|
994
|
+
role: "option",
|
|
995
|
+
"aria-selected": index === state.active,
|
|
996
|
+
className: clsx(PopupSelectView_module_css_default.row, index === state.active && PopupSelectView_module_css_default.rowActive),
|
|
997
|
+
onClick: () => {
|
|
998
|
+
popup.select(index);
|
|
999
|
+
},
|
|
1000
|
+
onMouseEnter: () => {
|
|
1001
|
+
popup.highlight(index);
|
|
1002
|
+
},
|
|
1003
|
+
children: [
|
|
1004
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
1005
|
+
className: PopupSelectView_module_css_default.label,
|
|
1006
|
+
children: option.label
|
|
1007
|
+
}),
|
|
1008
|
+
option.detail !== void 0 && (0, react_jsx_runtime.jsx)("span", {
|
|
1009
|
+
className: PopupSelectView_module_css_default.detail,
|
|
1010
|
+
children: option.detail
|
|
1011
|
+
}),
|
|
1012
|
+
option.active === true && (0, react_jsx_runtime.jsx)("span", {
|
|
1013
|
+
className: PopupSelectView_module_css_default.check,
|
|
1014
|
+
children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutline16, {})
|
|
1015
|
+
})
|
|
1016
|
+
]
|
|
1017
|
+
}, option.id))
|
|
1018
|
+
})
|
|
1019
|
+
]
|
|
1020
|
+
}), confirmation !== void 0 && (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.RiskConfirmation, {
|
|
1021
|
+
open: true,
|
|
1022
|
+
title: confirmation.title,
|
|
1023
|
+
description: confirmation.description,
|
|
1024
|
+
acknowledgeLabel: confirmation.acknowledgeLabel,
|
|
1025
|
+
cancelLabel: confirmation.cancelLabel,
|
|
1026
|
+
confirmLabel: confirmation.confirmLabel,
|
|
1027
|
+
acknowledged: state.acknowledged,
|
|
1028
|
+
onAcknowledgedChange: (value) => {
|
|
1029
|
+
popup.acknowledge(value);
|
|
1030
|
+
},
|
|
1031
|
+
onCancel: () => {
|
|
1032
|
+
popup.cancelConfirmation();
|
|
1033
|
+
},
|
|
1034
|
+
onConfirm: () => {
|
|
1035
|
+
popup.confirm();
|
|
1036
|
+
}
|
|
1037
|
+
})] });
|
|
1038
|
+
}
|
|
1039
|
+
//#endregion
|
|
1040
|
+
//#region lib/types/client/locales.js
|
|
1041
|
+
/** `command` namespace dictionaries (the popupSelect shell's copy). */
|
|
1042
|
+
/** Simplified Chinese dictionary (the key-set source of truth). */
|
|
1043
|
+
const zh = {
|
|
1044
|
+
"search.placeholder": "搜索…",
|
|
1045
|
+
"search.aria": "筛选选项",
|
|
1046
|
+
"status.loading": "正在加载选项…",
|
|
1047
|
+
"status.applying": "正在应用…",
|
|
1048
|
+
"status.empty": "无选项",
|
|
1049
|
+
"overlay.aria": "/{command} 选项",
|
|
1050
|
+
"listbox.aria": "/{command} 匹配项"
|
|
1051
|
+
};
|
|
1052
|
+
/** English dictionary, checked complete against the zh key set. */
|
|
1053
|
+
const en = {
|
|
1054
|
+
"search.placeholder": "Search…",
|
|
1055
|
+
"search.aria": "Filter options",
|
|
1056
|
+
"status.loading": "Loading options…",
|
|
1057
|
+
"status.applying": "Applying…",
|
|
1058
|
+
"status.empty": "No options",
|
|
1059
|
+
"overlay.aria": "/{command} options",
|
|
1060
|
+
"listbox.aria": "/{command} matches"
|
|
1061
|
+
};
|
|
1062
|
+
//#endregion
|
|
1063
|
+
//#region lib/types/client/index.js
|
|
1064
|
+
/** Dictionary namespace owned by this plugin. */
|
|
1065
|
+
const NS = "command";
|
|
1066
|
+
/** Required services: the '/' source registry, session scopes, commands Remote, and locale registry. */
|
|
1067
|
+
const inject = [
|
|
1068
|
+
"inputTriggers",
|
|
1069
|
+
"sessions",
|
|
1070
|
+
"remote",
|
|
1071
|
+
"remote.commands",
|
|
1072
|
+
"locale"
|
|
1073
|
+
];
|
|
1074
|
+
/**
|
|
1075
|
+
* Client plugin body: mount the service, then register the popupSelect shell
|
|
1076
|
+
* into the input overlay once its declarer is up.
|
|
1077
|
+
* @param ctx - client root context.
|
|
1078
|
+
*/
|
|
1079
|
+
function apply(ctx) {
|
|
1080
|
+
ctx.effect(() => ctx.locale.register(NS, {
|
|
1081
|
+
zh,
|
|
1082
|
+
en
|
|
1083
|
+
}), "ui-commands: dictionaries");
|
|
1084
|
+
ctx.plugin(CommandUiRuntime);
|
|
1085
|
+
ctx.inject([
|
|
1086
|
+
"slots",
|
|
1087
|
+
"commandUi",
|
|
1088
|
+
"sessions"
|
|
1089
|
+
], (scope) => {
|
|
1090
|
+
const command = scope.commandUi;
|
|
1091
|
+
const sessions = scope.sessions;
|
|
1092
|
+
scope.slots.inject("conversation.input.overlay", () => scope.slots.register({
|
|
1093
|
+
name: "conversation.input.overlay",
|
|
1094
|
+
id: "command-popup",
|
|
1095
|
+
order: 1,
|
|
1096
|
+
locale: NS,
|
|
1097
|
+
inject: (sessionId) => {
|
|
1098
|
+
const actx = sessions.scope(sessionId);
|
|
1099
|
+
if (actx === void 0) throw new Error(`ui-commands: session "${String(sessionId)}" resolved no scope`);
|
|
1100
|
+
return { popup: command.popupFor(actx) };
|
|
1101
|
+
}
|
|
1102
|
+
}, PopupSelectView));
|
|
1103
|
+
});
|
|
1104
|
+
}
|
|
1105
|
+
//#endregion
|
|
1106
|
+
exports.CommandDirectory = CommandDirectory;
|
|
1107
|
+
exports.CommandUiRuntime = CommandUiRuntime;
|
|
1108
|
+
exports.PopupSelectController = PopupSelectController;
|
|
1109
|
+
exports.apply = apply;
|
|
1110
|
+
exports.filterOptions = filterOptions;
|
|
1111
|
+
exports.inject = inject;
|
|
1112
|
+
return module.exports;
|
|
1113
|
+
}
|
|
1114
|
+
});
|
|
1115
|
+
|
|
1116
|
+
//# sourceMappingURL=client.js.map
|