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,859 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: "@deepseek-ai/dsh-client-ui-input-trigger",
|
|
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/core/detect.js
|
|
13
|
+
const WORD_CHAR = /[\p{L}\p{N}_]/u;
|
|
14
|
+
const WHITESPACE = /\s/u;
|
|
15
|
+
/**
|
|
16
|
+
* Word-boundary rule: a trigger char opens only at start-of-draft, after
|
|
17
|
+
* whitespace (newlines included), or after punctuation. Two URL carve-outs
|
|
18
|
+
* keep '/' dead inside URLs (both pinned by tests): '/' after a ':' that
|
|
19
|
+
* itself follows a non-whitespace char (scheme separator, `https:/…`), and
|
|
20
|
+
* '/' directly after another '/' (second slash of `//`).
|
|
21
|
+
*/
|
|
22
|
+
function boundaryOk(draft, index, char) {
|
|
23
|
+
if (index === 0) return true;
|
|
24
|
+
const prev = draft.charAt(index - 1);
|
|
25
|
+
if (WHITESPACE.test(prev)) return true;
|
|
26
|
+
if (WORD_CHAR.test(prev)) return false;
|
|
27
|
+
if (char === "/") {
|
|
28
|
+
if (prev === "/") return false;
|
|
29
|
+
if (prev === ":" && index >= 2 && !WHITESPACE.test(draft.charAt(index - 2))) return false;
|
|
30
|
+
}
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Detect a trigger token at the caret. Scans left from the caret and stops
|
|
35
|
+
* at the first whitespace (the token under edit never spans whitespace);
|
|
36
|
+
* trigger chars failing the guard tier or the word boundary are treated as
|
|
37
|
+
* ordinary token chars and the scan continues (`user@host`, URL slashes).
|
|
38
|
+
* Guard tiers: plain = both chars live; claimed = '/' fully suppressed,
|
|
39
|
+
* '@' live; frozen = none.
|
|
40
|
+
*
|
|
41
|
+
* @param draft - Full draft text.
|
|
42
|
+
* @param caret - Caret offset into `draft`.
|
|
43
|
+
* @param guard - Availability tier derived from the input phase.
|
|
44
|
+
* @returns The hit with `query` = trigger-to-caret slice and `span` =
|
|
45
|
+
* `{start: triggerIndex, end: caret}`; `span.draftRev` is a placeholder `0`
|
|
46
|
+
* — the calling shell stamps the real revision. Null when no trigger is
|
|
47
|
+
* live at the caret.
|
|
48
|
+
*/
|
|
49
|
+
const detectTrigger = (draft, caret, guard) => {
|
|
50
|
+
if (guard.tier === "frozen") return null;
|
|
51
|
+
for (let i = caret - 1; i >= 0; i--) {
|
|
52
|
+
const ch = draft.charAt(i);
|
|
53
|
+
if (WHITESPACE.test(ch)) return null;
|
|
54
|
+
if (ch !== "/" && ch !== "@") continue;
|
|
55
|
+
if (guard.tier === "claimed" && ch === "/") continue;
|
|
56
|
+
if (!boundaryOk(draft, i, ch)) continue;
|
|
57
|
+
return {
|
|
58
|
+
trigger: ch,
|
|
59
|
+
query: draft.slice(i + 1, caret),
|
|
60
|
+
position: draft.search(/\S/) === i ? "leading" : "inline",
|
|
61
|
+
span: {
|
|
62
|
+
start: i,
|
|
63
|
+
end: caret,
|
|
64
|
+
draftRev: 0
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
};
|
|
70
|
+
//#endregion
|
|
71
|
+
//#region lib/types/core/menu.js
|
|
72
|
+
/** Closed rest state with generation 0; store initializer and test seed. */
|
|
73
|
+
const MENU_CLOSED = {
|
|
74
|
+
open: false,
|
|
75
|
+
hit: null,
|
|
76
|
+
generation: 0,
|
|
77
|
+
groups: [],
|
|
78
|
+
highlight: null
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* Replace the group roster with pending groups for `sources`, in order.
|
|
82
|
+
* Shell-side step before dispatching `hit` on a fresh menu open.
|
|
83
|
+
*
|
|
84
|
+
* @param state - Current menu state.
|
|
85
|
+
* @param sources - Source names registered for the hit trigger, menu order.
|
|
86
|
+
* @returns State carrying the new pending roster; highlight cleared.
|
|
87
|
+
*/
|
|
88
|
+
function seedGroups(state, sources) {
|
|
89
|
+
return {
|
|
90
|
+
...state,
|
|
91
|
+
groups: sources.map((source) => ({
|
|
92
|
+
source,
|
|
93
|
+
status: "pending",
|
|
94
|
+
items: []
|
|
95
|
+
})),
|
|
96
|
+
highlight: null
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
/** Close, preserving the generation so in-flight settlements stay droppable. */
|
|
100
|
+
const closed = (state) => state.open || state.hit !== null || state.groups.length > 0 || state.highlight !== null ? {
|
|
101
|
+
open: false,
|
|
102
|
+
hit: null,
|
|
103
|
+
generation: state.generation,
|
|
104
|
+
groups: [],
|
|
105
|
+
highlight: null
|
|
106
|
+
} : state;
|
|
107
|
+
/** First item of the first non-empty ready group, or null. */
|
|
108
|
+
function firstHighlight(groups) {
|
|
109
|
+
for (const g of groups) if (g.status === "ready" && g.items.length > 0) return {
|
|
110
|
+
source: g.source,
|
|
111
|
+
index: 0
|
|
112
|
+
};
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
/** The highlight itself when it still points at a ready item, else null. */
|
|
116
|
+
function validHighlight(highlight, groups) {
|
|
117
|
+
if (!highlight) return null;
|
|
118
|
+
const g = groups.find((x) => x.source === highlight.source);
|
|
119
|
+
return g && g.status === "ready" && highlight.index < g.items.length ? highlight : null;
|
|
120
|
+
}
|
|
121
|
+
/** Flatten ready items into (source, index) positions in group order. */
|
|
122
|
+
function positions(groups) {
|
|
123
|
+
const out = [];
|
|
124
|
+
for (const g of groups) {
|
|
125
|
+
if (g.status !== "ready") continue;
|
|
126
|
+
for (let i = 0; i < g.items.length; i++) out.push({
|
|
127
|
+
source: g.source,
|
|
128
|
+
index: i
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
return out;
|
|
132
|
+
}
|
|
133
|
+
/** True when every group is ready with zero items (the auto-close condition). */
|
|
134
|
+
const allReadyEmpty = (groups) => groups.every((g) => g.status === "ready" && g.items.length === 0);
|
|
135
|
+
/**
|
|
136
|
+
* Pure menu reducer. `hit` opens a new generation over the seeded roster
|
|
137
|
+
* (null hit closes); `source-settled` outside the current generation, the
|
|
138
|
+
* open menu, or the roster is dropped; a settlement or failure leaving every
|
|
139
|
+
* group ready-and-empty (or no groups) auto-closes; `source-failed` silently
|
|
140
|
+
* removes the group (the shell logs); `move` cycles the highlight across
|
|
141
|
+
* ready items.
|
|
142
|
+
*
|
|
143
|
+
* @param state - Current menu state.
|
|
144
|
+
* @param ev - Menu event.
|
|
145
|
+
* @returns Next state; the same reference when stale or a no-op.
|
|
146
|
+
*/
|
|
147
|
+
const menuReduce = (state, ev) => {
|
|
148
|
+
switch (ev.type) {
|
|
149
|
+
case "hit":
|
|
150
|
+
if (ev.hit === null) return closed(state);
|
|
151
|
+
return {
|
|
152
|
+
open: true,
|
|
153
|
+
hit: ev.hit,
|
|
154
|
+
generation: state.generation + 1,
|
|
155
|
+
groups: state.groups.map((g) => ({
|
|
156
|
+
source: g.source,
|
|
157
|
+
status: "pending",
|
|
158
|
+
items: []
|
|
159
|
+
})),
|
|
160
|
+
highlight: null
|
|
161
|
+
};
|
|
162
|
+
case "source-settled": {
|
|
163
|
+
if (!state.open || ev.generation !== state.generation) return state;
|
|
164
|
+
const idx = state.groups.findIndex((g) => g.source === ev.source);
|
|
165
|
+
if (idx < 0) return state;
|
|
166
|
+
const items = ev.items ?? [];
|
|
167
|
+
const groups = state.groups.map((g, i) => i === idx ? {
|
|
168
|
+
source: g.source,
|
|
169
|
+
status: "ready",
|
|
170
|
+
items
|
|
171
|
+
} : g);
|
|
172
|
+
if (allReadyEmpty(groups)) return closed(state);
|
|
173
|
+
const highlight = validHighlight(state.highlight, groups) ?? firstHighlight(groups);
|
|
174
|
+
return {
|
|
175
|
+
...state,
|
|
176
|
+
groups,
|
|
177
|
+
highlight
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
case "source-failed": {
|
|
181
|
+
if (!state.open || ev.generation !== state.generation) return state;
|
|
182
|
+
if (!state.groups.some((g) => g.source === ev.source)) return state;
|
|
183
|
+
const groups = state.groups.filter((g) => g.source !== ev.source);
|
|
184
|
+
if (groups.length === 0 || allReadyEmpty(groups)) return closed(state);
|
|
185
|
+
const highlight = validHighlight(state.highlight, groups) ?? firstHighlight(groups);
|
|
186
|
+
return {
|
|
187
|
+
...state,
|
|
188
|
+
groups,
|
|
189
|
+
highlight
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
case "move": {
|
|
193
|
+
if (!state.open) return state;
|
|
194
|
+
const pos = positions(state.groups);
|
|
195
|
+
if (pos.length === 0) return state;
|
|
196
|
+
const hl = state.highlight;
|
|
197
|
+
const at = hl ? pos.findIndex((p) => p.source === hl.source && p.index === hl.index) : -1;
|
|
198
|
+
const next = pos[at < 0 ? ev.dir === 1 ? 0 : pos.length - 1 : (at + ev.dir + pos.length) % pos.length];
|
|
199
|
+
if (next === void 0) return state;
|
|
200
|
+
if (hl && next.source === hl.source && next.index === hl.index) return state;
|
|
201
|
+
return {
|
|
202
|
+
...state,
|
|
203
|
+
highlight: next
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
case "close": return closed(state);
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
//#endregion
|
|
210
|
+
//#region lib/types/client/controller.js
|
|
211
|
+
/**
|
|
212
|
+
* Per-session trigger pipeline state and orchestration. All mutation stays
|
|
213
|
+
* inside; MenuView renders from {@link InputTriggerController.menu} and routes
|
|
214
|
+
* pointer picks back through {@link InputTriggerController.pick}.
|
|
215
|
+
*/
|
|
216
|
+
var InputTriggerController = class {
|
|
217
|
+
deps;
|
|
218
|
+
/** Menu state store (per-session; survives session switches, dies with the scope). */
|
|
219
|
+
menu = (0, _deepseek_ai_dsh_client_runtime_client.createSnapshotStore)(MENU_CLOSED);
|
|
220
|
+
/**
|
|
221
|
+
* Name of the source opened through the programmatic launcher, or null for
|
|
222
|
+
* trigger-detected/closed menus. Composer chrome subscribes to this store
|
|
223
|
+
* for the launcher's expanded state without owning a second menu model.
|
|
224
|
+
*/
|
|
225
|
+
launcher = (0, _deepseek_ai_dsh_client_runtime_client.createSnapshotStore)(null);
|
|
226
|
+
/**
|
|
227
|
+
* Aggregated hot reference lexicon, grouped by trigger (plain-text-reference decision;
|
|
228
|
+
* see .agents/notes/implemented/architecture/2026-07-25-web-input-machine-and-slash-pipeline.md):
|
|
229
|
+
* sources implementing the lexicon hook are polled with the session
|
|
230
|
+
* projection; undefined answers (roll not hot yet) are skipped; multiple
|
|
231
|
+
* sources on one trigger concatenate in registration order. A snapshot
|
|
232
|
+
* store because rolls change asynchronously (catalog settles, children
|
|
233
|
+
* spawn/exit) — render-side consumers subscribe instead of re-reading a
|
|
234
|
+
* mutable answer.
|
|
235
|
+
*/
|
|
236
|
+
lexicon = (0, _deepseek_ai_dsh_client_runtime_client.createSnapshotStore)(/* @__PURE__ */ new Map());
|
|
237
|
+
/** The authoritative hit: single truth for span CAS material (menu snapshot never carries it alone). */
|
|
238
|
+
hit = null;
|
|
239
|
+
fetch = null;
|
|
240
|
+
disposed = false;
|
|
241
|
+
/** Per-source lexicon unsubscribers (sources without the hook never enter). */
|
|
242
|
+
lexiconOffs = /* @__PURE__ */ new Map();
|
|
243
|
+
constructor(deps) {
|
|
244
|
+
this.deps = deps;
|
|
245
|
+
const projection = this.project();
|
|
246
|
+
for (const src of deps.roster.all()) {
|
|
247
|
+
src.warm?.(projection);
|
|
248
|
+
this.watchLexicon(src, projection);
|
|
249
|
+
}
|
|
250
|
+
this.refreshLexicon();
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Feed a draft/caret change through trigger detection and drive the menu.
|
|
254
|
+
* @param draft - full draft text.
|
|
255
|
+
* @param caret - caret offset into `draft`.
|
|
256
|
+
* @param guard - availability tier derived from the input phase.
|
|
257
|
+
* @param draftRev - the input machine's current draft revision, stamped
|
|
258
|
+
* into the hit span for pick-time CAS.
|
|
259
|
+
*/
|
|
260
|
+
track(draft, caret, guard, draftRev) {
|
|
261
|
+
if (this.disposed) return;
|
|
262
|
+
const launched = this.launcher.getSnapshot() !== null;
|
|
263
|
+
this.clearLauncher();
|
|
264
|
+
const raw = detectTrigger(draft, caret, guard);
|
|
265
|
+
if (raw === null) {
|
|
266
|
+
this.hit = null;
|
|
267
|
+
this.stopFetch();
|
|
268
|
+
this.reduce({ type: "close" });
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
const hit = {
|
|
272
|
+
...raw,
|
|
273
|
+
span: {
|
|
274
|
+
...raw.span,
|
|
275
|
+
draftRev
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
const prev = this.menu.getSnapshot();
|
|
279
|
+
const same = !launched && prev.open && prev.hit !== null && prev.hit.trigger === hit.trigger && prev.hit.query === hit.query && prev.hit.span.start === hit.span.start && prev.hit.span.end === hit.span.end;
|
|
280
|
+
this.hit = hit;
|
|
281
|
+
if (same) return;
|
|
282
|
+
const roster = this.deps.roster.sources(hit.trigger);
|
|
283
|
+
if (roster.length === 0) {
|
|
284
|
+
this.stopFetch();
|
|
285
|
+
this.reduce({ type: "close" });
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
if (launched || !prev.open || prev.hit === null || prev.hit.trigger !== hit.trigger) this.menu.set(seedGroups(this.menu.getSnapshot(), roster.map((s) => s.name)));
|
|
289
|
+
this.reduce({
|
|
290
|
+
type: "hit",
|
|
291
|
+
hit
|
|
292
|
+
});
|
|
293
|
+
this.fetchCandidates(hit, roster);
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Toggle a menu containing exactly one registered source. The supplied hit
|
|
297
|
+
* is a synthetic selection span rather than a typed trigger token, but
|
|
298
|
+
* picks deliberately reuse the ordinary source callback and scoped input
|
|
299
|
+
* mutation pipeline.
|
|
300
|
+
* @param source - registered source name under `hit.trigger`.
|
|
301
|
+
* @param hit - synthetic hit carrying position and pick-time draft CAS.
|
|
302
|
+
*/
|
|
303
|
+
toggleSource(source, hit) {
|
|
304
|
+
if (this.disposed) return;
|
|
305
|
+
if (this.launcher.getSnapshot() === source && this.menu.getSnapshot().open) {
|
|
306
|
+
this.dismiss();
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
const match = this.deps.roster.sources(hit.trigger).find((item) => item.name === source);
|
|
310
|
+
if (match === void 0) {
|
|
311
|
+
this.dismiss();
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
this.stopFetch();
|
|
315
|
+
this.hit = hit;
|
|
316
|
+
this.launcher.set(source);
|
|
317
|
+
this.menu.set(seedGroups(this.menu.getSnapshot(), [source]));
|
|
318
|
+
this.reduce({
|
|
319
|
+
type: "hit",
|
|
320
|
+
hit
|
|
321
|
+
});
|
|
322
|
+
this.fetchCandidates(hit, [match]);
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Pointer pick from MenuView: route the clicked candidate through onPick
|
|
326
|
+
* and execute claim/insert outcomes via the scoped input events.
|
|
327
|
+
* @param source - source (group) name.
|
|
328
|
+
* @param index - candidate index within the group.
|
|
329
|
+
*/
|
|
330
|
+
pick(source, index) {
|
|
331
|
+
const state = this.menu.getSnapshot();
|
|
332
|
+
const hit = this.hit;
|
|
333
|
+
if (this.disposed || !state.open || hit === null) return;
|
|
334
|
+
const group = state.groups.find((g) => g.source === source);
|
|
335
|
+
const candidate = group !== void 0 && group.status === "ready" ? group.items[index] : void 0;
|
|
336
|
+
if (candidate === void 0) return;
|
|
337
|
+
const src = this.deps.roster.sources(hit.trigger).find((s) => s.name === source);
|
|
338
|
+
if (src === void 0) return;
|
|
339
|
+
const outcome = src.onPick({
|
|
340
|
+
candidate,
|
|
341
|
+
session: this.project(),
|
|
342
|
+
position: hit.position,
|
|
343
|
+
via: "menu",
|
|
344
|
+
span: hit.span
|
|
345
|
+
});
|
|
346
|
+
this.stopFetch();
|
|
347
|
+
this.reduce({ type: "close" });
|
|
348
|
+
this.execute(outcome, hit.span);
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Keyboard arbitration while the menu is open.
|
|
352
|
+
* @param key - intercepted key.
|
|
353
|
+
* @param composing - inside IME composition: everything passes.
|
|
354
|
+
* @returns consumed / pick-highlighted / pass.
|
|
355
|
+
*/
|
|
356
|
+
arbitrate(key, composing) {
|
|
357
|
+
if (composing || this.disposed) return "pass";
|
|
358
|
+
const state = this.menu.getSnapshot();
|
|
359
|
+
if (!state.open) return "pass";
|
|
360
|
+
switch (key) {
|
|
361
|
+
case "up":
|
|
362
|
+
this.reduce({
|
|
363
|
+
type: "move",
|
|
364
|
+
dir: -1
|
|
365
|
+
});
|
|
366
|
+
return "consumed";
|
|
367
|
+
case "down":
|
|
368
|
+
this.reduce({
|
|
369
|
+
type: "move",
|
|
370
|
+
dir: 1
|
|
371
|
+
});
|
|
372
|
+
return "consumed";
|
|
373
|
+
case "escape":
|
|
374
|
+
this.stopFetch();
|
|
375
|
+
this.reduce({ type: "close" });
|
|
376
|
+
return "consumed";
|
|
377
|
+
case "enter":
|
|
378
|
+
if (state.highlight === null) return "pass";
|
|
379
|
+
this.pick(state.highlight.source, state.highlight.index);
|
|
380
|
+
return "pick-highlighted";
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Space adjudication over the just-completed leading token: polls sources'
|
|
385
|
+
* matchSpace (hot state, synchronous) and dispatches the outcome itself.
|
|
386
|
+
* @returns true when a claim/insert was actually applied by the input —
|
|
387
|
+
* the caller preventDefaults exactly then.
|
|
388
|
+
*/
|
|
389
|
+
onSpace() {
|
|
390
|
+
const hit = this.hit;
|
|
391
|
+
if (this.disposed || hit === null || hit.position !== "leading") return false;
|
|
392
|
+
const token = hit.trigger + hit.query;
|
|
393
|
+
const projection = this.project();
|
|
394
|
+
for (const src of this.deps.roster.sources(hit.trigger)) {
|
|
395
|
+
if (src.matchSpace === void 0) continue;
|
|
396
|
+
const outcome = src.matchSpace(projection, token);
|
|
397
|
+
if (outcome === void 0) continue;
|
|
398
|
+
if (outcome === "handled") return true;
|
|
399
|
+
return this.execute(outcome, hit.span);
|
|
400
|
+
}
|
|
401
|
+
return false;
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Serialize one reference occurrence to its model form via the owning
|
|
405
|
+
* source's codec (prompt serialization: registry → explicit
|
|
406
|
+
* call → await). Owner missing or codec-less rejects — the submit attempt
|
|
407
|
+
* blocks instead of silently downgrading to the clipboard text.
|
|
408
|
+
* @param source - owning source name.
|
|
409
|
+
* @param ref - owner-scoped reference id.
|
|
410
|
+
* @param signal - the submit attempt's abort signal.
|
|
411
|
+
* @returns the model representation (e.g. `<skill>name</skill>`).
|
|
412
|
+
*/
|
|
413
|
+
serializeReference(source, ref, signal) {
|
|
414
|
+
const owner = this.deps.roster.all().find((s) => s.name === source);
|
|
415
|
+
if (owner?.codec === void 0) return Promise.reject(/* @__PURE__ */ new Error(`slash: no serializer for reference source "${source}"`));
|
|
416
|
+
return owner.codec.serialize(ref, signal);
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Enter last adjudication: polls sources' matchEnter in registration
|
|
420
|
+
* order, first non-undefined wins. The outcome returns to the caller (the
|
|
421
|
+
* input machine applies it inside the same submit attempt — no event).
|
|
422
|
+
* @param line - trimmed draft; the leading char selects the trigger roster.
|
|
423
|
+
* @param signal - attempt-scoped abort from the input machine.
|
|
424
|
+
* @returns the winning outcome or undefined (default sink). Rejects when a
|
|
425
|
+
* polled source's warmup fails — the caller must not silently downgrade.
|
|
426
|
+
*/
|
|
427
|
+
async adjudicate(line, signal) {
|
|
428
|
+
const projection = this.project();
|
|
429
|
+
for (const src of this.deps.roster.all()) {
|
|
430
|
+
if (signal.aborted) throw signal.reason instanceof Error ? signal.reason : /* @__PURE__ */ new Error("slash adjudication aborted");
|
|
431
|
+
if (src.matchEnter === void 0 || !line.startsWith(src.trigger)) continue;
|
|
432
|
+
const outcome = await src.matchEnter(projection, line, signal);
|
|
433
|
+
if (outcome !== void 0) return outcome;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Drop the menu group of a disposed source (root registry change notification).
|
|
438
|
+
* @param source - the source whose registration was disposed.
|
|
439
|
+
*/
|
|
440
|
+
sourceRemoved(source) {
|
|
441
|
+
const state = this.menu.getSnapshot();
|
|
442
|
+
if (state.open && state.hit !== null && state.hit.trigger === source.trigger) this.reduce({
|
|
443
|
+
type: "source-failed",
|
|
444
|
+
generation: state.generation,
|
|
445
|
+
source: source.name
|
|
446
|
+
});
|
|
447
|
+
this.lexiconOffs.get(source)?.();
|
|
448
|
+
this.lexiconOffs.delete(source);
|
|
449
|
+
this.refreshLexicon();
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Admit a source registered after this controller's birth (root registry
|
|
453
|
+
* change notification): warm it and fold its roll into the live lexicon —
|
|
454
|
+
* the constructor-time prewarm covers only the roster present at scope
|
|
455
|
+
* birth.
|
|
456
|
+
* @param source - the newly registered source.
|
|
457
|
+
*/
|
|
458
|
+
sourceAdded(source) {
|
|
459
|
+
const projection = this.project();
|
|
460
|
+
source.warm?.(projection);
|
|
461
|
+
this.watchLexicon(source, projection);
|
|
462
|
+
this.refreshLexicon();
|
|
463
|
+
}
|
|
464
|
+
/** External dismiss (e.g. pointer outside the composer area). */
|
|
465
|
+
dismiss() {
|
|
466
|
+
if (this.disposed) return;
|
|
467
|
+
this.stopFetch();
|
|
468
|
+
this.reduce({ type: "close" });
|
|
469
|
+
}
|
|
470
|
+
/** Scope teardown: close and abort (the service deletes the map entry). */
|
|
471
|
+
dispose() {
|
|
472
|
+
this.disposed = true;
|
|
473
|
+
this.stopFetch();
|
|
474
|
+
this.reduce({ type: "close" });
|
|
475
|
+
this.hit = null;
|
|
476
|
+
for (const off of this.lexiconOffs.values()) off();
|
|
477
|
+
this.lexiconOffs.clear();
|
|
478
|
+
}
|
|
479
|
+
/** The session projection handed to sources (agent-backed identity; constant per scope). */
|
|
480
|
+
project() {
|
|
481
|
+
return { sessionId: this.deps.sessionId };
|
|
482
|
+
}
|
|
483
|
+
/** Execute a claim/insert/text outcome via the scoped input events (actx as dispatch subject); true = the input applied it. */
|
|
484
|
+
execute(outcome, span) {
|
|
485
|
+
const { actx } = this.deps;
|
|
486
|
+
if (outcome === void 0 || outcome === "handled") return false;
|
|
487
|
+
if ("claim" in outcome) return actx.bail(actx, "slash/input-begin-command", {
|
|
488
|
+
claim: outcome.claim,
|
|
489
|
+
span
|
|
490
|
+
}) === true;
|
|
491
|
+
if ("text" in outcome) return actx.bail(actx, "slash/input-insert-text", {
|
|
492
|
+
text: outcome.text,
|
|
493
|
+
span
|
|
494
|
+
}) === true;
|
|
495
|
+
return actx.bail(actx, "slash/input-insert-reference", {
|
|
496
|
+
reference: outcome.insert,
|
|
497
|
+
span
|
|
498
|
+
}) === true;
|
|
499
|
+
}
|
|
500
|
+
/** Re-poll every lexicon-bearing source and publish the aggregated rolls (see the store doc). */
|
|
501
|
+
refreshLexicon() {
|
|
502
|
+
const projection = this.project();
|
|
503
|
+
const rolls = /* @__PURE__ */ new Map();
|
|
504
|
+
for (const src of this.deps.roster.all()) {
|
|
505
|
+
if (src.lexicon === void 0) continue;
|
|
506
|
+
let names;
|
|
507
|
+
try {
|
|
508
|
+
names = src.lexicon(projection);
|
|
509
|
+
} catch (error) {
|
|
510
|
+
console.error(`[ui-input-trigger] source "${src.name}" lexicon failed:`, error);
|
|
511
|
+
continue;
|
|
512
|
+
}
|
|
513
|
+
if (names === void 0) continue;
|
|
514
|
+
const prev = rolls.get(src.trigger);
|
|
515
|
+
rolls.set(src.trigger, prev === void 0 ? names : [...prev, ...names]);
|
|
516
|
+
}
|
|
517
|
+
this.lexicon.set(rolls);
|
|
518
|
+
}
|
|
519
|
+
/** Wire one source's lexicon invalidation channel into refresh (hookless or roll-less sources never notify). */
|
|
520
|
+
watchLexicon(source, projection) {
|
|
521
|
+
if (source.lexicon === void 0 || source.subscribeLexicon === void 0) return;
|
|
522
|
+
this.lexiconOffs.set(source, source.subscribeLexicon(projection, () => {
|
|
523
|
+
this.refreshLexicon();
|
|
524
|
+
}));
|
|
525
|
+
}
|
|
526
|
+
/** Launch the candidate fetch for one hit generation, superseding the previous one. */
|
|
527
|
+
fetchCandidates(hit, roster) {
|
|
528
|
+
this.stopFetch();
|
|
529
|
+
const controller = new AbortController();
|
|
530
|
+
this.fetch = controller;
|
|
531
|
+
const generation = this.menu.getSnapshot().generation;
|
|
532
|
+
const projection = this.project();
|
|
533
|
+
for (const source of roster) source.candidates(projection, {
|
|
534
|
+
query: hit.query,
|
|
535
|
+
position: hit.position,
|
|
536
|
+
signal: controller.signal
|
|
537
|
+
}).then((items) => {
|
|
538
|
+
if (controller.signal.aborted) return;
|
|
539
|
+
this.reduce({
|
|
540
|
+
type: "source-settled",
|
|
541
|
+
generation,
|
|
542
|
+
source: source.name,
|
|
543
|
+
items
|
|
544
|
+
});
|
|
545
|
+
}, (error) => {
|
|
546
|
+
if (controller.signal.aborted) return;
|
|
547
|
+
console.error(`[ui-input-trigger] source "${source.name}" candidates failed:`, error);
|
|
548
|
+
this.reduce({
|
|
549
|
+
type: "source-failed",
|
|
550
|
+
generation,
|
|
551
|
+
source: source.name
|
|
552
|
+
});
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
stopFetch() {
|
|
556
|
+
this.fetch?.abort();
|
|
557
|
+
this.fetch = null;
|
|
558
|
+
}
|
|
559
|
+
clearLauncher() {
|
|
560
|
+
if (this.launcher.getSnapshot() !== null) this.launcher.set(null);
|
|
561
|
+
}
|
|
562
|
+
reduce(ev) {
|
|
563
|
+
const cur = this.menu.getSnapshot();
|
|
564
|
+
const next = menuReduce(cur, ev);
|
|
565
|
+
if (next !== cur) this.menu.set(next);
|
|
566
|
+
if (!next.open) this.clearLauncher();
|
|
567
|
+
}
|
|
568
|
+
};
|
|
569
|
+
//#endregion
|
|
570
|
+
//#region lib/types/client/service.js
|
|
571
|
+
/**
|
|
572
|
+
* InputTriggerService (`ctx.inputTriggers`): the root half of the trigger pipeline — the
|
|
573
|
+
* stateless source registry plus the per-session controller map. Every piece
|
|
574
|
+
* of mutable interaction state (hit, menu, fetch) lives on the
|
|
575
|
+
* {@link InputTriggerController}; the service only registers sources, resolves
|
|
576
|
+
* controllers by session scope, and relays roster changes.
|
|
577
|
+
*/
|
|
578
|
+
/** The `ctx.inputTriggers` trigger pipeline service (root registry + controller resolution). */
|
|
579
|
+
var InputTriggerService = class extends _deepseek_ai_cordis.Service {
|
|
580
|
+
static inject = ["sessions"];
|
|
581
|
+
live = {
|
|
582
|
+
sources: [],
|
|
583
|
+
controllers: /* @__PURE__ */ new Map()
|
|
584
|
+
};
|
|
585
|
+
/**
|
|
586
|
+
* @param ctx - owning root context (the service registers itself as `slash`).
|
|
587
|
+
*/
|
|
588
|
+
constructor(ctx) {
|
|
589
|
+
super(ctx, "inputTriggers");
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* Register one trigger source. Live session controllers are notified so a
|
|
593
|
+
* source arriving after scope birth still warms and joins the lexicon.
|
|
594
|
+
* @param src - the source; (trigger, name) must be unique — duplicates throw.
|
|
595
|
+
* @returns the disposer (callers wrap registration in ctx.effect). Disposal
|
|
596
|
+
* while a controller shows the source's menu group drops that group.
|
|
597
|
+
*/
|
|
598
|
+
registerSource(src) {
|
|
599
|
+
const { live } = this;
|
|
600
|
+
if (live.sources.some((s) => s.trigger === src.trigger && s.name === src.name)) throw new Error(`slash source "${src.trigger}${src.name}" is already registered`);
|
|
601
|
+
live.sources.push(src);
|
|
602
|
+
for (const controller of live.controllers.values()) try {
|
|
603
|
+
controller.sourceAdded(src);
|
|
604
|
+
} catch (error) {
|
|
605
|
+
console.error(`[ui-input-trigger] source "${src.trigger}${src.name}" late-registration setup failed:`, error);
|
|
606
|
+
}
|
|
607
|
+
return () => {
|
|
608
|
+
const at = live.sources.indexOf(src);
|
|
609
|
+
if (at < 0) return;
|
|
610
|
+
live.sources.splice(at, 1);
|
|
611
|
+
for (const controller of live.controllers.values()) controller.sourceRemoved(src);
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Resolve the per-session controller for one session scope (lazy; the
|
|
616
|
+
* scope disposer removes and disposes it). Construction warms the source
|
|
617
|
+
* roster once — sessions are always agent-backed, so scope birth is the
|
|
618
|
+
* single prewarm moment.
|
|
619
|
+
* @param actx - session-scope ctx.
|
|
620
|
+
* @returns the resident controller.
|
|
621
|
+
*/
|
|
622
|
+
sessionOf(actx) {
|
|
623
|
+
const id = this.sessions().scopeOf(actx);
|
|
624
|
+
if (id === void 0) throw new Error("slash.sessionOf requires a session scope");
|
|
625
|
+
const { live } = this;
|
|
626
|
+
const existing = live.controllers.get(id);
|
|
627
|
+
if (existing !== void 0) return existing;
|
|
628
|
+
const controller = new InputTriggerController({
|
|
629
|
+
actx,
|
|
630
|
+
sessionId: id,
|
|
631
|
+
roster: {
|
|
632
|
+
sources: (trigger) => live.sources.filter((s) => s.trigger === trigger).sort((a, b) => (a.order ?? 0) - (b.order ?? 0)),
|
|
633
|
+
all: () => live.sources
|
|
634
|
+
}
|
|
635
|
+
});
|
|
636
|
+
live.controllers.set(id, controller);
|
|
637
|
+
actx.effect(() => () => {
|
|
638
|
+
controller.dispose();
|
|
639
|
+
live.controllers.delete(id);
|
|
640
|
+
}, "slash: session controller");
|
|
641
|
+
return controller;
|
|
642
|
+
}
|
|
643
|
+
sessions() {
|
|
644
|
+
const sessions = this.ctx.get("sessions");
|
|
645
|
+
if (sessions === void 0) throw new Error("ui-input-trigger: sessions service unavailable");
|
|
646
|
+
return sessions;
|
|
647
|
+
}
|
|
648
|
+
};
|
|
649
|
+
//#endregion
|
|
650
|
+
//#region ../../../node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs
|
|
651
|
+
function r(e) {
|
|
652
|
+
var t, f, n = "";
|
|
653
|
+
if ("string" == typeof e || "number" == typeof e) n += e;
|
|
654
|
+
else if ("object" == typeof e) if (Array.isArray(e)) {
|
|
655
|
+
var o = e.length;
|
|
656
|
+
for (t = 0; t < o; t++) e[t] && (f = r(e[t])) && (n && (n += " "), n += f);
|
|
657
|
+
} else for (f in e) e[f] && (n && (n += " "), n += f);
|
|
658
|
+
return n;
|
|
659
|
+
}
|
|
660
|
+
function clsx() {
|
|
661
|
+
for (var e, t, f = 0, n = "", o = arguments.length; f < o; f++) (e = arguments[f]) && (t = r(e)) && (n && (n += " "), n += t);
|
|
662
|
+
return n;
|
|
663
|
+
}
|
|
664
|
+
//#endregion
|
|
665
|
+
//#region \0dsh-css:/private/var/folders/nm/n5smty9962gf5rhjc5855kt80000gn/T/dsh-edge-release-worktree.XXXXXX.RcJjogaGt4/repo/packages/client/ui-input-trigger/src/client/MenuView.module.css.mjs
|
|
666
|
+
const css = ".s1ZeLW_menu{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(260px,100%);max-width:min(537px,100%);max-height:320px;box-shadow:var(--dsw-shadow-lv3);border-radius:12px;flex-direction:column;padding:4px;display:flex;position:absolute;bottom:calc(100% + 4px);left:0;overflow:hidden}.s1ZeLW_viewport{flex-direction:column;min-height:0;display:flex;overflow-y:auto}.s1ZeLW_item{cursor:pointer;width:100%;min-height:40px;color:var(--dsw-alias-label-primary);text-align:left;background:0 0;border:none;border-radius:10px;align-items:center;gap:8px;padding:8px 10px;font-size:14px;line-height:22px;display:flex}.s1ZeLW_item:hover,.s1ZeLW_item.s1ZeLW_active{background:var(--dsw-alias-interactive-bg-hover)}.s1ZeLW_itemIcon{width:16px;height:16px;color:var(--dsw-alias-label-tertiary);flex:none;justify-content:center;align-items:center;display:inline-flex}.s1ZeLW_itemName{text-overflow:ellipsis;white-space:nowrap;flex:none;max-width:40%;overflow:hidden}.s1ZeLW_itemDescription{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--dsw-alias-label-tertiary);flex:1;overflow:hidden}.s1ZeLW_groupTitle{color:var(--dsw-alias-label-tertiary);padding:8px 10px;font-size:12px;line-height:16px}.s1ZeLW_loading{min-height:40px;color:var(--dsw-alias-label-dimmed);align-items:center;padding:8px 10px;font-size:14px;line-height:22px;display:flex}";
|
|
667
|
+
const tagId = "@deepseek-ai/dsh-client-ui-input-trigger/MenuView.module.css";
|
|
668
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
669
|
+
const tag = document.createElement("style");
|
|
670
|
+
tag.dataset.plugin = "@deepseek-ai/dsh-client-ui-input-trigger";
|
|
671
|
+
tag.dataset.pluginCss = tagId;
|
|
672
|
+
tag.textContent = css;
|
|
673
|
+
document.head.appendChild(tag);
|
|
674
|
+
}
|
|
675
|
+
var MenuView_module_css_default = {
|
|
676
|
+
"item": "s1ZeLW_item",
|
|
677
|
+
"menu": "s1ZeLW_menu",
|
|
678
|
+
"loading": "s1ZeLW_loading",
|
|
679
|
+
"itemDescription": "s1ZeLW_itemDescription",
|
|
680
|
+
"itemName": "s1ZeLW_itemName",
|
|
681
|
+
"active": "s1ZeLW_active",
|
|
682
|
+
"groupTitle": "s1ZeLW_groupTitle",
|
|
683
|
+
"viewport": "s1ZeLW_viewport",
|
|
684
|
+
"itemIcon": "s1ZeLW_itemIcon"
|
|
685
|
+
};
|
|
686
|
+
//#endregion
|
|
687
|
+
//#region lib/types/client/MenuView.js
|
|
688
|
+
/**
|
|
689
|
+
* Trigger candidate menu: renders the InputTriggerService menu store into the
|
|
690
|
+
* conversation.input.overlay anchor. Closed state renders null (the overlay
|
|
691
|
+
* slot stays mounted); groups render in roster order under localized title
|
|
692
|
+
* rows, pending groups as a loading row; pointer picks route back through
|
|
693
|
+
* the service (combobox pattern — focus never leaves the textarea, so rows
|
|
694
|
+
* are mousedown-handled and the highlight is exposed via
|
|
695
|
+
* aria-activedescendant on the listbox).
|
|
696
|
+
*/
|
|
697
|
+
/** Design cap on the list height (figma SLASH 39:26572 MenuDropdown). */
|
|
698
|
+
const MAX_HEIGHT = 320;
|
|
699
|
+
/** DOM id of one option row (the aria-activedescendant target). */
|
|
700
|
+
function optionId(source, index) {
|
|
701
|
+
return `dsh-slash-option-${source}-${index}`;
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Render the candidate menu overlay entry.
|
|
705
|
+
* @param props - injected face (the menu store and the pick route); `t` rides the standard locale seat.
|
|
706
|
+
* @returns the dropdown while open; null while closed.
|
|
707
|
+
*/
|
|
708
|
+
function MenuView({ menu, onPick, onDismiss, t }) {
|
|
709
|
+
const state = (0, react.useSyncExternalStore)((fn) => menu.subscribe(fn), () => menu.getSnapshot());
|
|
710
|
+
const listRef = (0, react.useRef)(null);
|
|
711
|
+
const maxHeight = (0, _deepseek_ai_dsh_client_ui_primitives.useAnchoredMaxHeight)(listRef, MAX_HEIGHT, state);
|
|
712
|
+
const highlight = state.open ? state.highlight : null;
|
|
713
|
+
(0, react.useEffect)(() => {
|
|
714
|
+
if (highlight === null) return;
|
|
715
|
+
document.getElementById(optionId(highlight.source, highlight.index))?.scrollIntoView({ block: "nearest" });
|
|
716
|
+
}, [highlight]);
|
|
717
|
+
(0, react.useEffect)(() => {
|
|
718
|
+
if (!state.open) return;
|
|
719
|
+
const onPointerDown = (ev) => {
|
|
720
|
+
if (!(ev.target instanceof Node)) return;
|
|
721
|
+
if (listRef.current?.contains(ev.target)) return;
|
|
722
|
+
if ((listRef.current?.closest("[data-composer-card]"))?.contains(ev.target)) return;
|
|
723
|
+
onDismiss();
|
|
724
|
+
};
|
|
725
|
+
document.addEventListener("pointerdown", onPointerDown, true);
|
|
726
|
+
return () => {
|
|
727
|
+
document.removeEventListener("pointerdown", onPointerDown, true);
|
|
728
|
+
};
|
|
729
|
+
}, [state.open, onDismiss]);
|
|
730
|
+
if (!state.open) return null;
|
|
731
|
+
return (0, react_jsx_runtime.jsx)("div", {
|
|
732
|
+
ref: listRef,
|
|
733
|
+
className: MenuView_module_css_default.menu,
|
|
734
|
+
style: { maxHeight },
|
|
735
|
+
role: "listbox",
|
|
736
|
+
"aria-label": t("suggestions.aria"),
|
|
737
|
+
"aria-activedescendant": highlight !== null ? optionId(highlight.source, highlight.index) : void 0,
|
|
738
|
+
children: (0, react_jsx_runtime.jsx)("div", {
|
|
739
|
+
className: MenuView_module_css_default.viewport,
|
|
740
|
+
children: state.groups.map((group) => group.status === "ready" && group.items.length === 0 ? null : (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [(0, react_jsx_runtime.jsx)("div", {
|
|
741
|
+
className: MenuView_module_css_default.groupTitle,
|
|
742
|
+
role: "presentation",
|
|
743
|
+
"data-source": group.source,
|
|
744
|
+
children: t(group.source)
|
|
745
|
+
}), group.status === "pending" ? (0, react_jsx_runtime.jsx)("div", {
|
|
746
|
+
className: MenuView_module_css_default.loading,
|
|
747
|
+
"data-source": group.source,
|
|
748
|
+
children: t("loading")
|
|
749
|
+
}) : group.items.map((item, index) => {
|
|
750
|
+
const active = highlight !== null && highlight.source === group.source && highlight.index === index;
|
|
751
|
+
return (0, react_jsx_runtime.jsxs)("button", {
|
|
752
|
+
id: optionId(group.source, index),
|
|
753
|
+
type: "button",
|
|
754
|
+
role: "option",
|
|
755
|
+
"aria-selected": active,
|
|
756
|
+
className: clsx(MenuView_module_css_default.item, active && MenuView_module_css_default.active),
|
|
757
|
+
onMouseDown: (ev) => {
|
|
758
|
+
ev.preventDefault();
|
|
759
|
+
onPick(group.source, index);
|
|
760
|
+
},
|
|
761
|
+
children: [
|
|
762
|
+
item.icon !== void 0 && (0, react_jsx_runtime.jsx)("span", {
|
|
763
|
+
className: MenuView_module_css_default.itemIcon,
|
|
764
|
+
"aria-hidden": true,
|
|
765
|
+
children: item.icon
|
|
766
|
+
}),
|
|
767
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
768
|
+
className: MenuView_module_css_default.itemName,
|
|
769
|
+
children: item.name
|
|
770
|
+
}),
|
|
771
|
+
item.description !== void 0 && (0, react_jsx_runtime.jsx)("span", {
|
|
772
|
+
className: MenuView_module_css_default.itemDescription,
|
|
773
|
+
children: item.description
|
|
774
|
+
})
|
|
775
|
+
]
|
|
776
|
+
}, `${group.source}:${item.name}`);
|
|
777
|
+
})] }, group.source))
|
|
778
|
+
})
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
//#endregion
|
|
782
|
+
//#region lib/types/client/locales.js
|
|
783
|
+
/**
|
|
784
|
+
* `slash.menu` namespace dictionaries: group titles keyed by source name
|
|
785
|
+
* (the lookup chain returns the key itself, so an unknown source shows its
|
|
786
|
+
* raw name), the pending row, and the listbox aria label.
|
|
787
|
+
*/
|
|
788
|
+
/** Simplified Chinese dictionary (the key-set source of truth). */
|
|
789
|
+
const zh = {
|
|
790
|
+
"command": "命令",
|
|
791
|
+
"skill": "技能",
|
|
792
|
+
"subagent": "子智能体",
|
|
793
|
+
"loading": "正在加载…",
|
|
794
|
+
"suggestions.aria": "触发候选建议"
|
|
795
|
+
};
|
|
796
|
+
/** English dictionary, checked complete against the zh key set. */
|
|
797
|
+
const en = {
|
|
798
|
+
"command": "Commands",
|
|
799
|
+
"skill": "Skills",
|
|
800
|
+
"subagent": "Subagents",
|
|
801
|
+
"loading": "Loading…",
|
|
802
|
+
"suggestions.aria": "Trigger suggestions"
|
|
803
|
+
};
|
|
804
|
+
//#endregion
|
|
805
|
+
//#region lib/types/client/index.js
|
|
806
|
+
/** Namespace owning the candidate-menu copy. */
|
|
807
|
+
const MENU_NS = "slash.menu";
|
|
808
|
+
/** Required services: controller resolution reads the session scope tree; the menu copy is localized. */
|
|
809
|
+
const inject = ["sessions", "locale"];
|
|
810
|
+
/**
|
|
811
|
+
* Client plugin body: mount the service, then register MenuView into the
|
|
812
|
+
* input overlay once its declarer is up.
|
|
813
|
+
* @param ctx - client root context.
|
|
814
|
+
*/
|
|
815
|
+
function apply(ctx) {
|
|
816
|
+
ctx.plugin(InputTriggerService);
|
|
817
|
+
ctx.effect(() => ctx.locale.register(MENU_NS, {
|
|
818
|
+
zh,
|
|
819
|
+
en
|
|
820
|
+
}), "ui-input-trigger: menu dictionaries");
|
|
821
|
+
ctx.inject([
|
|
822
|
+
"slots",
|
|
823
|
+
"inputTriggers",
|
|
824
|
+
"sessions"
|
|
825
|
+
], (scope) => {
|
|
826
|
+
const inputTriggers = scope.inputTriggers;
|
|
827
|
+
const sessions = scope.sessions;
|
|
828
|
+
scope.slots.inject("conversation.input.overlay", () => scope.slots.register({
|
|
829
|
+
name: "conversation.input.overlay",
|
|
830
|
+
id: "slash-menu",
|
|
831
|
+
order: 0,
|
|
832
|
+
locale: MENU_NS,
|
|
833
|
+
inject: (sessionId) => {
|
|
834
|
+
const actx = sessions.scope(sessionId);
|
|
835
|
+
if (actx === void 0) throw new Error(`ui-input-trigger: session "${String(sessionId)}" resolved no scope`);
|
|
836
|
+
const controller = inputTriggers.sessionOf(actx);
|
|
837
|
+
return {
|
|
838
|
+
menu: controller.menu,
|
|
839
|
+
onPick: (source, index) => {
|
|
840
|
+
controller.pick(source, index);
|
|
841
|
+
},
|
|
842
|
+
onDismiss: () => {
|
|
843
|
+
controller.dismiss();
|
|
844
|
+
}
|
|
845
|
+
};
|
|
846
|
+
}
|
|
847
|
+
}, MenuView));
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
//#endregion
|
|
851
|
+
exports.InputTriggerController = InputTriggerController;
|
|
852
|
+
exports.InputTriggerService = InputTriggerService;
|
|
853
|
+
exports.apply = apply;
|
|
854
|
+
exports.inject = inject;
|
|
855
|
+
return module.exports;
|
|
856
|
+
}
|
|
857
|
+
});
|
|
858
|
+
|
|
859
|
+
//# sourceMappingURL=client.js.map
|