dsh-codinput 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.
Files changed (54) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/LICENSE +21 -0
  3. package/NOTICE +18 -0
  4. package/README.en.md +167 -0
  5. package/README.md +167 -0
  6. package/TESTING.md +95 -0
  7. package/assets/en/preview-ball-detail.png +0 -0
  8. package/assets/en/preview-float.png +0 -0
  9. package/assets/en/preview-normal.png +0 -0
  10. package/assets/en/preview-side.png +0 -0
  11. package/assets/en/preview-split.png +0 -0
  12. package/assets/icon.svg +1 -0
  13. package/assets/preview-ball-detail.png +0 -0
  14. package/assets/preview-float.png +0 -0
  15. package/assets/preview-normal.png +0 -0
  16. package/assets/preview-side.png +0 -0
  17. package/assets/preview-split.png +0 -0
  18. package/cordis.patch.yml +5 -0
  19. package/lib/client.js +544 -0
  20. package/lib/index.js +5 -0
  21. package/package.json +79 -0
  22. package/src/client/attachments.ts +43 -0
  23. package/src/client/cm.ts +207 -0
  24. package/src/client/command.ts +78 -0
  25. package/src/client/components/AttachmentRail.tsx +241 -0
  26. package/src/client/components/CodinputBar.tsx +357 -0
  27. package/src/client/components/CodinputSurface.tsx +375 -0
  28. package/src/client/components/EditorPane.tsx +214 -0
  29. package/src/client/components/ExpandBall.tsx +122 -0
  30. package/src/client/components/MenuSurface.tsx +72 -0
  31. package/src/client/components/ModelMenu.tsx +234 -0
  32. package/src/client/components/PermissionMenu.tsx +257 -0
  33. package/src/client/components/PopupSelectView.tsx +140 -0
  34. package/src/client/components/SlashMenu.tsx +255 -0
  35. package/src/client/components/StatsRow.tsx +188 -0
  36. package/src/client/components/Toolbar.tsx +239 -0
  37. package/src/client/components/popup-filter.ts +13 -0
  38. package/src/client/components/useSnapshot.ts +18 -0
  39. package/src/client/diag.ts +58 -0
  40. package/src/client/host-types.ts +328 -0
  41. package/src/client/hotkey.ts +82 -0
  42. package/src/client/i18n.ts +312 -0
  43. package/src/client/icons.tsx +296 -0
  44. package/src/client/index.tsx +178 -0
  45. package/src/client/modeldir.ts +42 -0
  46. package/src/client/permission.ts +123 -0
  47. package/src/client/prefs.ts +123 -0
  48. package/src/client/services.ts +78 -0
  49. package/src/client/settings.tsx +236 -0
  50. package/src/client/sidebar.tsx +114 -0
  51. package/src/client/sideinput.ts +171 -0
  52. package/src/client/stats.ts +166 -0
  53. package/src/client/styles.ts +489 -0
  54. package/src/client/triggers.ts +84 -0
@@ -0,0 +1,296 @@
1
+ /** 内联图标:lucide Outline 线性风格,对齐官方输入框视觉(stroke=currentColor)。 */
2
+
3
+ import type { JSX } from 'react';
4
+
5
+ interface IconProps {
6
+ size?: number;
7
+ className?: string;
8
+ }
9
+
10
+ function svg(paths: JSX.Element, size: number | undefined, className: string | undefined): JSX.Element {
11
+ return (
12
+ <svg
13
+ xmlns="http://www.w3.org/2000/svg"
14
+ width={size ?? 16}
15
+ height={size ?? 16}
16
+ viewBox="0 0 24 24"
17
+ fill="none"
18
+ stroke="currentColor"
19
+ strokeWidth={2}
20
+ strokeLinecap="round"
21
+ strokeLinejoin="round"
22
+ className={className}
23
+ aria-hidden="true"
24
+ >
25
+ {paths}
26
+ </svg>
27
+ );
28
+ }
29
+
30
+ /** 品牌:lucide file-code-corner。 */
31
+ export function IconBrand({ size, className }: IconProps): JSX.Element {
32
+ return svg(
33
+ <>
34
+ <path d="M4 12.15V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.706.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2h-3.35" />
35
+ <path d="M14 2v5a1 1 0 0 0 1 1h5" />
36
+ <path d="m5 16-3 3 3 3" />
37
+ <path d="m9 22 3-3-3-3" />
38
+ </>,
39
+ size,
40
+ className,
41
+ );
42
+ }
43
+
44
+ export function IconEdit({ size, className }: IconProps): JSX.Element {
45
+ return svg(
46
+ <>
47
+ <path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z" />
48
+ <path d="m15 5 4 4" />
49
+ </>,
50
+ size,
51
+ className,
52
+ );
53
+ }
54
+
55
+ export function IconEye({ size, className }: IconProps): JSX.Element {
56
+ return svg(
57
+ <>
58
+ <path d="M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0" />
59
+ <circle cx="12" cy="12" r="3" />
60
+ </>,
61
+ size,
62
+ className,
63
+ );
64
+ }
65
+
66
+ export function IconTerminalSquare({ size, className }: IconProps): JSX.Element {
67
+ return svg(
68
+ <>
69
+ <path d="m7 11 2-2-2-2" />
70
+ <path d="M11 13h4" />
71
+ <rect width="18" height="18" x="3" y="3" rx="2" ry="2" />
72
+ </>,
73
+ size,
74
+ className,
75
+ );
76
+ }
77
+
78
+ export function IconPaperclip({ size, className }: IconProps): JSX.Element {
79
+ return svg(
80
+ <path d="m16 6-8.414 8.586a2 2 0 0 0 2.829 2.829l8.414-8.586a4 4 0 1 0-5.657-5.657l-8.379 8.551a6 6 0 1 0 8.485 8.485l8.379-8.551" />,
81
+ size,
82
+ className,
83
+ );
84
+ }
85
+
86
+ export function IconShield({ size, className }: IconProps): JSX.Element {
87
+ return svg(
88
+ <path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" />,
89
+ size,
90
+ className,
91
+ );
92
+ }
93
+
94
+ export function IconChevronDown({ size, className }: IconProps): JSX.Element {
95
+ return svg(<path d="m6 9 6 6 6-6" />, size, className);
96
+ }
97
+
98
+ export function IconArrowUp({ size, className }: IconProps): JSX.Element {
99
+ return svg(
100
+ <path d="m5 12 7-7 7 7" />,
101
+ size,
102
+ className,
103
+ );
104
+ // 竖线由外层圆形按钮构图补足:这里仅箭头,竖线单独画在按钮内。
105
+ }
106
+
107
+ export function IconArrowUpFull({ size, className }: IconProps): JSX.Element {
108
+ return svg(
109
+ <>
110
+ <path d="m5 12 7-7 7 7" />
111
+ <path d="M12 19V5" />
112
+ </>,
113
+ size,
114
+ className,
115
+ );
116
+ }
117
+
118
+ export function IconStop({ size, className }: IconProps): JSX.Element {
119
+ return svg(<rect x="7" y="7" width="10" height="10" rx="1.5" />, size, className);
120
+ }
121
+
122
+ export function IconX({ size, className }: IconProps): JSX.Element {
123
+ return svg(<path d="M18 6 6 18M6 6l12 12" />, size, className);
124
+ }
125
+
126
+ export function IconFile({ size, className }: IconProps): JSX.Element {
127
+ return svg(
128
+ <>
129
+ <path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" />
130
+ <path d="M14 2v4a2 2 0 0 0 2 2h4" />
131
+ </>,
132
+ size,
133
+ className,
134
+ );
135
+ }
136
+
137
+ export function IconFolder({ size, className }: IconProps): JSX.Element {
138
+ return svg(
139
+ <path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z" />,
140
+ size,
141
+ className,
142
+ );
143
+ }
144
+
145
+ export function IconSearch({ size, className }: IconProps): JSX.Element {
146
+ return svg(
147
+ <>
148
+ <circle cx="11" cy="11" r="8" />
149
+ <path d="m21 21-4.3-4.3" />
150
+ </>,
151
+ size,
152
+ className,
153
+ );
154
+ }
155
+
156
+ // ---------- 官方图标(路径逐字取自宿主 primitives / ui-conversation,保证与官方输入框一致) ----------
157
+
158
+ /** 官方 IconPlusOutline16(指令按钮,fill)。 */
159
+ export function IconPlusOfficial({ size }: { size?: number }): JSX.Element {
160
+ return (
161
+ <svg width={size ?? 14} height={size ?? 14} viewBox="0 0 16 16" fill="none" aria-hidden="true">
162
+ <path
163
+ d="M8.64453 1.5V7.34961H14.5V8.65039H8.64453V14.5H7.34473V8.65039H1.5V7.34961H7.34473V1.5H8.64453Z"
164
+ fill="currentColor"
165
+ />
166
+ </svg>
167
+ );
168
+ }
169
+
170
+ /** 官方 IconPaperclipOutline16(附件按钮;官方为 fill 路径自带描边轮廓,不得再叠 stroke)。 */
171
+ export function IconPaperclipOfficial({ size }: { size?: number }): JSX.Element {
172
+ return (
173
+ <svg width={size ?? 14} height={size ?? 14} viewBox="0 0 16 16" fill="none" aria-hidden="true">
174
+ <path
175
+ d="M5.5498 9.75V5H6.9502V9.75C6.9502 10.3299 7.4201 10.7998 8 10.7998C8.5799 10.7998 9.0498 10.3299 9.0498 9.75V4.5C9.0498 2.9536 7.7964 1.7002 6.25 1.7002C4.7036 1.7002 3.4502 2.9536 3.4502 4.5V9.75C3.4502 12.2629 5.4871 14.2998 8 14.2998C10.5129 14.2998 12.5498 12.2629 12.5498 9.75V4H13.9502V9.75C13.9502 13.0361 11.2861 15.7002 8 15.7002C4.71391 15.7002 2.0498 13.0361 2.0498 9.75V4.5C2.04981 2.1804 3.9304 0.299806 6.25 0.299805C8.5696 0.299805 10.4502 2.1804 10.4502 4.5V9.75C10.4502 11.1031 9.3531 12.2002 8 12.2002C6.6469 12.2002 5.5498 11.1031 5.5498 9.75Z"
176
+ fill="currentColor"
177
+ />
178
+ </svg>
179
+ );
180
+ }
181
+
182
+ const OFFICIAL_SHIELD =
183
+ 'M8.20554 0.899994L14.7901 3.36857V7.01026C14.7901 12 11.0466 14.2103 8.20554 15.3C5.36446 14.2103 1.62012 12 1.62012 7.01026V3.36857L8.20554 0.899994Z';
184
+ const OFFICIAL_SHIELD_CHECK =
185
+ 'M12.1654 5.7552L8.9447 9.41475C8.73044 9.65816 8.53628 9.8804 8.35774 10.0423C8.1713 10.2114 7.94235 10.3717 7.64016 10.4254C7.48207 10.4535 7.32 10.4552 7.16151 10.4294C6.85843 10.3801 6.62728 10.2223 6.43836 10.0559C6.25752 9.89653 6.06037 9.67732 5.84264 9.43705L4.72925 8.20897L5.63557 7.38707L6.74897 8.61594C6.98603 8.87755 7.12974 9.03533 7.24673 9.13839C7.31033 9.19443 7.34485 9.21476 7.35823 9.22122C7.38068 9.22484 7.40352 9.22515 7.42593 9.22122C7.40522 9.22502 7.42893 9.23294 7.53583 9.136C7.65132 9.03126 7.79316 8.87139 8.02643 8.60638L11.2479 4.94763L12.1654 5.7552Z';
186
+
187
+ /** 官方权限 glyph:read-only(盾+勾)。 */
188
+ export function IconShieldCheckOfficial({ size }: { size?: number }): JSX.Element {
189
+ return (
190
+ <svg width={size ?? 16} height={size ?? 16} viewBox="0 0 16 16" fill="none" aria-hidden="true">
191
+ <path d={OFFICIAL_SHIELD} stroke="currentColor" strokeWidth="1.31831" strokeLinejoin="round" />
192
+ <path d={OFFICIAL_SHIELD_CHECK} fill="currentColor" />
193
+ </svg>
194
+ );
195
+ }
196
+
197
+ /** 官方权限 glyph:workspace-write(盾+横线+铅笔,全 fill)。 */
198
+ export function IconShieldWriteOfficial({ size }: { size?: number }): JSX.Element {
199
+ return (
200
+ <svg width={size ?? 16} height={size ?? 16} viewBox="0 0 16 16" fill="none" aria-hidden="true">
201
+ <path
202
+ d="M8.08887 0.251709C8.20479 0.23085 8.32486 0.241168 8.43652 0.282959L15.0215 2.75171C15.2787 2.84819 15.4492 3.09414 15.4492 3.3689V7.0105C15.4492 7.10986 15.4441 7.2081 15.4414 7.30542C15.0285 7.07175 14.5905 6.87695 14.1309 6.73022V3.82495L8.20508 1.60327L2.2793 3.82495V7.0105C2.27936 9.7171 3.4745 11.5379 5.02734 12.7947C5.01025 12.9942 5 13.1962 5 13.4001C5.00001 13.7617 5.02722 14.1169 5.08008 14.4636C2.91555 13.0393 0.961014 10.752 0.960938 7.0105V3.3689C0.960938 3.09417 1.13146 2.84821 1.38867 2.75171L7.97461 0.282959L8.08887 0.251709Z"
203
+ fill="currentColor"
204
+ />
205
+ <path d="M11.3525 5.64688V6.85688H5V5.64688H11.3525Z" fill="currentColor" />
206
+ <path d="M9.5824 8.29376V9.50376H5V8.29376H9.5824Z" fill="currentColor" />
207
+ <path d="M14.6647 15.6852H10.0338C10.3878 15.3751 10.7567 15.0517 11.0772 14.7706C11.2531 14.6164 11.4144 14.4746 11.5511 14.3547H14.6647V15.6852Z" fill="currentColor" />
208
+ <path d="M8.14852 14.1308L7.33925 15.4976C7.22458 15.6912 7.42245 15.9194 7.63037 15.8333L9.09785 15.2254L15.0399 10.0719L14.0905 8.97733L8.14852 14.1308Z" fill="currentColor" />
209
+ </svg>
210
+ );
211
+ }
212
+
213
+ /** 官方权限 glyph:danger-full-access(盾+两道竖杠)。 */
214
+ export function IconShieldBarsOfficial({ size }: { size?: number }): JSX.Element {
215
+ return (
216
+ <svg width={size ?? 16} height={size ?? 16} viewBox="0 0 16 16" fill="none" aria-hidden="true">
217
+ <path d={OFFICIAL_SHIELD} stroke="currentColor" strokeWidth="1.31831" strokeLinejoin="round" />
218
+ <path d="M9.10094 4.5V8.75939H7.59888V4.5H9.10094Z" fill="currentColor" />
219
+ <path d="M9.10094 9.8114V11.5H7.59888V9.8114H9.10094Z" fill="currentColor" />
220
+ </svg>
221
+ );
222
+ }
223
+
224
+ /** 官方 IconWarningOutline16(风险确认弹层的警示 glyph,viewBox 14)。 */
225
+ export function IconWarningOfficial({ size }: { size?: number }): JSX.Element {
226
+ return (
227
+ <svg width={size ?? 18} height={size ?? 18} viewBox="0 0 14 14" fill="none" aria-hidden="true">
228
+ <path d="M6.3002 3.32843L7.69986 3.32843L7.69986 7.79657H6.3002L6.3002 3.32843Z" fill="currentColor" />
229
+ <path d="M6.3002 9.01935H7.69986V10.6711H6.3002V9.01935Z" fill="currentColor" />
230
+ <path
231
+ d="M12.6328 6.99976C12.6328 3.88874 10.111 1.36694 7 1.36694C3.88899 1.36695 1.3672 3.88875 1.36719 6.99976C1.36719 10.1108 3.88899 12.6326 7 12.6326C10.111 12.6326 12.6328 10.1108 12.6328 6.99976ZM13.8582 6.99976C13.8582 10.7873 10.7876 13.8579 7 13.8579C3.21244 13.8579 0.141846 10.7873 0.141846 6.99976C0.141857 3.2122 3.21245 0.141612 7 0.141602C10.7876 0.141602 13.8581 3.21219 13.8582 6.99976Z"
232
+ fill="currentColor"
233
+ />
234
+ </svg>
235
+ );
236
+ }
237
+
238
+ /** 官方发送箭头(primary 按钮,fill,viewBox 16)。 */
239
+ export function IconSendOfficial({ size }: { size?: number }): JSX.Element {
240
+ return (
241
+ <svg width={size ?? 16} height={size ?? 16} viewBox="0 0 16 16" aria-hidden="true">
242
+ <path
243
+ d="M8.3125 0.980183C8.66767 1.0531 8.97902 1.20418 9.2627 1.43233C9.48724 1.61297 9.73029 1.85793 9.97949 2.10714L14.707 6.83468L13.293 8.24874L9 3.95577V15.0417H7V3.95577L2.70703 8.24874L1.29297 6.83468L6.02051 2.10714C6.26971 1.85793 6.51277 1.61297 6.7373 1.43233C6.97662 1.23986 7.28445 1.04402 7.6875 0.980183C7.8973 0.947006 8.1031 0.95516 8.3125 0.980183Z"
244
+ fill="currentColor"
245
+ />
246
+ </svg>
247
+ );
248
+ }
249
+
250
+ /** 官方停止方块(primary 按钮的 busy 形态,fill)。 */
251
+ export function IconStopOfficial({ size }: { size?: number }): JSX.Element {
252
+ return (
253
+ <svg width={size ?? 16} height={size ?? 16} viewBox="0 0 16 16" aria-hidden="true">
254
+ <rect x="3" y="3" width="10" height="10" rx="3" fill="currentColor" />
255
+ </svg>
256
+ );
257
+ }
258
+
259
+ /** 官方风格下拉 chevron(14,描边,随 open 旋转由外层 CSS 控制)。 */
260
+ export function IconChevronOfficial({ size }: { size?: number }): JSX.Element {
261
+ return (
262
+ <svg width={size ?? 14} height={size ?? 14} viewBox="0 0 14 14" fill="none" aria-hidden="true">
263
+ <path
264
+ d="M3.5 5.75L7 9.25L10.5 5.75"
265
+ stroke="currentColor"
266
+ strokeWidth="1.3"
267
+ strokeLinecap="round"
268
+ strokeLinejoin="round"
269
+ />
270
+ </svg>
271
+ );
272
+ }
273
+
274
+ /**
275
+ * /codinput 命令图标(宿主命令菜单 glyph:文档+代码括号,fill
276
+ * currentColor,与 primitives Icon*Outline16 同构——接受 size,命令
277
+ * 菜单里按官方语义渲染为 <icon size={16}/>)。
278
+ */
279
+ export function IconCodinputCommand16({ size = 16, className }: { size?: number; className?: string }): JSX.Element {
280
+ return (
281
+ <svg width={size} height={size} viewBox="0 0 24 24" fill="none" className={className} aria-hidden="true">
282
+ <path
283
+ fillRule="evenodd"
284
+ clipRule="evenodd"
285
+ d="M12.25 2.83422C11.7896 2.75598 11.162 2.75005 10.0298 2.75005C8.11311 2.75005 6.75075 2.75163 5.71785 2.88987C4.70596 3.0253 4.12453 3.27933 3.7019 3.70195C3.27869 4.12516 3.02502 4.70481 2.88976 5.7109C2.75159 6.73856 2.75 8.09323 2.75 10.0001V14.0001C2.75 15.9069 2.75159 17.2615 2.88976 18.2892C3.02502 19.2953 3.27869 19.8749 3.7019 20.2981C4.12511 20.7214 4.70476 20.975 5.71085 21.1103C6.73851 21.2485 8.09318 21.2501 10 21.2501H14C15.9068 21.2501 17.2615 21.2485 18.2892 21.1103C19.2952 20.975 19.8749 20.7214 20.2981 20.2981C20.7213 19.8749 20.975 19.2953 21.1102 18.2892C21.2484 17.2615 21.25 15.9069 21.25 14.0001V13.5629C21.25 12.0269 21.2392 11.2988 21.0762 10.7501H17.9463C16.8135 10.7501 15.8877 10.7501 15.1569 10.6518C14.3929 10.5491 13.7306 10.3268 13.2019 9.79815C12.6732 9.26945 12.4509 8.60712 12.3482 7.84317C12.25 7.1123 12.25 6.18657 12.25 5.05374V2.83422ZM13.75 3.6095V5.00005C13.75 6.19976 13.7516 7.0241 13.8348 7.64329C13.9152 8.24091 14.059 8.53395 14.2626 8.73749C14.4661 8.94103 14.7591 9.08486 15.3568 9.16521C15.976 9.24846 16.8003 9.25005 18 9.25005H20.0195C19.723 8.9625 19.3432 8.61797 18.85 8.17407L14.8912 4.61117C14.4058 4.17433 14.0446 3.85187 13.75 3.6095ZM10.1755 1.25002C11.5601 1.24965 12.4546 1.24942 13.2779 1.56535C14.1012 1.88129 14.7632 2.47735 15.7873 3.39955C15.8226 3.43139 15.8584 3.46361 15.8947 3.49623L19.8534 7.05912C19.8956 7.09705 19.9372 7.1345 19.9783 7.17149C21.162 8.23614 21.9274 8.92458 22.3391 9.84902C22.7508 10.7734 22.7505 11.8029 22.75 13.3949C22.75 13.4502 22.75 13.5062 22.75 13.5629V14.0565C22.75 15.8942 22.75 17.3499 22.5969 18.4891C22.4392 19.6615 22.1071 20.6104 21.3588 21.3588C20.6104 22.1072 19.6614 22.4393 18.489 22.5969C17.3498 22.7501 15.8942 22.7501 14.0564 22.7501H9.94359C8.10583 22.7501 6.65019 22.7501 5.51098 22.5969C4.33856 22.4393 3.38961 22.1072 2.64124 21.3588C1.89288 20.6104 1.56076 19.6615 1.40314 18.4891C1.24997 17.3499 1.24998 15.8942 1.25 14.0565V9.94363C1.24998 8.10587 1.24997 6.65024 1.40314 5.51103C1.56076 4.33861 1.89288 3.38966 2.64124 2.64129C3.39019 1.89235 4.34232 1.56059 5.51887 1.40313C6.66283 1.25002 8.1257 1.25003 9.97352 1.25005L10.0298 1.25005C10.0789 1.25005 10.1275 1.25004 10.1755 1.25002Z"
286
+ fill="currentColor"
287
+ />
288
+ <path
289
+ fillRule="evenodd"
290
+ clipRule="evenodd"
291
+ d="M10.2633 13.2978C10.6512 13.4432 10.8477 13.8756 10.7022 14.2634L9.20225 18.2634C9.05681 18.6512 8.6245 18.8477 8.23666 18.7023C7.84882 18.5569 7.65231 18.1245 7.79775 17.7367L9.29775 13.7367C9.44319 13.3489 9.8755 13.1524 10.2633 13.2978ZM7.53033 13.4697C7.82322 13.7626 7.82322 14.2375 7.53033 14.5304L7.06066 15L7.53033 15.4697C7.82322 15.7626 7.82322 16.2375 7.53033 16.5304C7.23744 16.8233 6.76256 16.8233 6.46967 16.5304L5.46967 15.5304C5.17678 15.2375 5.17678 14.7626 5.46967 14.4697L6.46967 13.4697C6.76256 13.1768 7.23744 13.1768 7.53033 13.4697ZM10.9697 15.4697C11.2626 15.1768 11.7374 15.1768 12.0303 15.4697L13.0303 16.4697C13.3232 16.7626 13.3232 17.2375 13.0303 17.5304L12.0303 18.5304C11.7374 18.8233 11.2626 18.8233 10.9697 18.5304C10.6768 18.2375 10.6768 17.7626 10.9697 17.4697L11.4393 17L10.9697 16.5304C10.6768 16.2375 10.6768 15.7626 10.9697 15.4697Z"
292
+ fill="currentColor"
293
+ />
294
+ </svg>
295
+ );
296
+ }
@@ -0,0 +1,178 @@
1
+ /**
2
+ * dsh-codinput 客户端模块入口。
3
+ *
4
+ * 客户端产物为 esbuild bundle + cjs,banner/footer 包成
5
+ * window.__ModuleLoader__.load({ id, factory });模块导出 name / inject /
6
+ * apply(docs/idea.md 附录工程要点)。
7
+ *
8
+ * 注册时序:第三方插件装载在宿主之后,目标槽位通常已声明——
9
+ * slots.inject 锚点只对「声明晚于本插件」的场景触发,因此这里用
10
+ * 直接注册 + 未声明短重试(槽位声明可能晚于首个 attempt 时仍兜底)。
11
+ */
12
+
13
+ import type { ClientContext } from './host-types';
14
+ import { installServices } from './services';
15
+ import { injectStyles } from './styles';
16
+ import { diagError, diagWindow, runGuarded } from './diag';
17
+ import { loadPrefs, subscribePrefs } from './prefs';
18
+ import { isAnySidebarInputCarried, subscribeSidebarInput } from './sideinput';
19
+ import { barRegistration, CodinputBar } from './components/CodinputBar';
20
+ import { sidebarBodyRegistration, sidebarTabDefinition } from './sidebar';
21
+ import { settingsRegistration } from './settings';
22
+ import { registerCodinputCommand } from './command';
23
+
24
+ export const name = 'dsh-codinput';
25
+
26
+ /** 客户端 inject:slots(槽位原语)+ conversation(输入机/会话装配)。 */
27
+ export const inject = ['slots', 'conversation'];
28
+
29
+ /** 尝试注册;槽位未声明会 throw——短重试兜底,成功即停。 */
30
+ function tryRegister(doRegister: () => void, label: string, attempts = 60): void {
31
+ try {
32
+ doRegister();
33
+ diagWindow().__dshCodinputShell[label] = 'registered';
34
+ return;
35
+ } catch (error) {
36
+ if (attempts <= 0) {
37
+ diagError(label, error);
38
+ diagWindow().__dshCodinputShell[label] = 'failed';
39
+ return;
40
+ }
41
+ }
42
+ setTimeout(() => tryRegister(doRegister, label, attempts - 1), 250);
43
+ }
44
+
45
+ export function apply(ctx: ClientContext): void {
46
+ injectStyles();
47
+ installServices(ctx);
48
+
49
+ // 诊断:插件加载期即接管 console.error(应用首渲染在插件加载之后),
50
+ // 抓取槽位边界的原始错误对象。
51
+ runGuarded('console.hook', () => {
52
+ const w = diagWindow() as unknown as Record<string, unknown> & { console: Console };
53
+ if (!(w as Record<string, unknown>).__dshConsoleHooked) {
54
+ (w as Record<string, unknown>).__dshConsoleHooked = true;
55
+ const orig = w.console.error.bind(w.console);
56
+ w.console.error = (...args: unknown[]): void => {
57
+ const first = args.find((a) => a instanceof Error);
58
+ if (first) (w.__dshCodinputErrs as unknown as Error[]).push(first as Error);
59
+ orig(...args);
60
+ };
61
+ }
62
+ });
63
+
64
+ // alpha 槽位:条目渲染崩溃会被「废黜」(本次注册周期内永久除名)。
65
+ // 尽早挂 onEntryError 监听,把崩溃堆栈留给诊断口。
66
+ runGuarded('slots.entryErrorHook', () => {
67
+ const slotsService = ctx.slots as unknown as Record<string, unknown>;
68
+ const core = (slotsService._core ?? slotsService) as Record<string, unknown>;
69
+ if (typeof core.onEntryError === 'function') {
70
+ (core.onEntryError as (fn: (info: Record<string, unknown>) => void) => void)((info) => {
71
+ const err = info?.error as Error | undefined;
72
+ diagError('slot.entry', err ?? info);
73
+ diagWindow().__dshCodinputErrs.push(
74
+ err instanceof Error ? err : new Error(JSON.stringify(info).slice(0, 800)),
75
+ );
76
+ });
77
+ }
78
+ });
79
+
80
+ // ---- 主输入接管:conversation.composer.bar(single,priority -1 遮蔽) ----
81
+ // 挂载条件二选一:启用接管(渲染接管卡片),或有会话的输入被侧栏
82
+ // Codinput 标签承载(渲染 null 以隐藏主输入,唯一输入组件约定)。
83
+ const bar = barRegistration();
84
+ let barDisposer: (() => void) | null = null;
85
+ const mountBar = (): void => {
86
+ if (barDisposer) return;
87
+ try {
88
+ barDisposer = ctx.slots.register(bar.options, CodinputBar);
89
+ diagWindow().__dshCodinputShell['bar'] = 'mounted';
90
+ } catch (error) {
91
+ diagError('bar.register', error);
92
+ }
93
+ };
94
+ const unmountBar = (): void => {
95
+ const disposer = barDisposer;
96
+ barDisposer = null;
97
+ try {
98
+ disposer?.();
99
+ diagWindow().__dshCodinputShell['bar'] = 'unmounted';
100
+ } catch (error) {
101
+ diagError('bar.dispose', error);
102
+ }
103
+ };
104
+ const barWanted = (): boolean => loadPrefs().enabled || isAnySidebarInputCarried();
105
+ const reconcileBar = (): void => {
106
+ if (barWanted()) mountBar();
107
+ else unmountBar();
108
+ };
109
+ tryRegister(() => {
110
+ if (barWanted()) mountBar();
111
+ }, 'bar');
112
+
113
+ runGuarded('bar.prefs', () => {
114
+ subscribePrefs(reconcileBar);
115
+ });
116
+
117
+ // 侧栏标签挂载/卸载:入口(prefs 关闭时也要为隐藏主输入而挂载)与出口
118
+ // (标签关闭后把主输入还给官方)。侧栏收起时小球由接管条目自己 portal
119
+ // 渲染(见 components/ExpandBall),不占官方槽位。
120
+ runGuarded('bar.sideinput', () => {
121
+ subscribeSidebarInput(reconcileBar);
122
+ });
123
+
124
+ // ---- 侧边模式:sidebarRightTabs 两段式注册(dockkit 承担停靠/浮动) ----
125
+ runGuarded('sidebar.register', () => {
126
+ ctx.inject(['slots', 'sidebarRightTabs', 'sidebarRight'], (scope: Record<string, unknown>) => {
127
+ const tabs = scope['sidebarRightTabs'] as { register(def: Record<string, unknown>): () => void } | undefined;
128
+ if (typeof tabs?.register !== 'function') {
129
+ diagError('sidebar', new Error('sidebarRightTabs unavailable'));
130
+ return;
131
+ }
132
+ const body = sidebarBodyRegistration();
133
+ // 标签类型定义 ride 调用方 fiber(官方约定:disposer 交 ctx.effect 持有)。
134
+ ctx.effect(() => tabs.register(sidebarTabDefinition()), 'dsh-codinput: sidebar tab type');
135
+ tryRegister(() => {
136
+ ctx.slots.register(body.options, body.component);
137
+ }, 'sidebar.body');
138
+ });
139
+ });
140
+
141
+ // ---- 设置 → Codinput 分组(settings.section 必须带 label/order) ----
142
+ runGuarded('settings.register', () => {
143
+ const settings = settingsRegistration();
144
+ tryRegister(() => {
145
+ ctx.slots.register(settings.options, settings.component);
146
+ }, 'settings');
147
+ });
148
+
149
+ // ---- /codinput 命令:官方命令通道的 action 贡献(进入/退出接管) ----
150
+ registerCodinputCommand(ctx);
151
+
152
+ // ---- 去掉 dockkit「拖动标签页→浮动/换位」手势(产品决策 2026-09-20:
153
+ // 悬浮只经主输入卡片 pin 按钮进入)。capture 相位拦掉 tab chip 的
154
+ // pointerdown,dockkit 的 press→drag 追踪不再启动;点击激活走独立
155
+ // click 事件、关闭按钮有专属 pointerdown 处理,均不受影响。 ----
156
+ runGuarded('draggate.install', () => {
157
+ const w = window as unknown as { __dshCodinputDragGate?: boolean };
158
+ if (w.__dshCodinputDragGate) return;
159
+ w.__dshCodinputDragGate = true;
160
+ document.addEventListener(
161
+ 'pointerdown',
162
+ (event) => {
163
+ if (!(event.target instanceof Element)) return;
164
+ if (event.target.closest('[data-dockkit-tab-close]')) return;
165
+ if (event.target.closest('[data-dockkit-tab]')) event.stopPropagation();
166
+ },
167
+ { capture: true },
168
+ );
169
+ });
170
+
171
+ // ---- 诊断口 ----
172
+ runGuarded('diag.boot', () => {
173
+ diagWindow().__dshCodinputShell['apply'] = 'done';
174
+ (window as unknown as Record<string, unknown>)['__dshCodinputCtx'] = ctx;
175
+ });
176
+ }
177
+
178
+ export { CodinputBar };
@@ -0,0 +1,42 @@
1
+ /**
2
+ * 模型目录:对齐官方 ui-model-selection 的服务形状——
3
+ * `ctx.modelDirectories.directoryFor(sessionId)` 返回 per-session 目录,
4
+ * `.store` 是 { current, groups, failures, status, error } 快照源,
5
+ * `.select(selection)` 走 selectModel RPC 写入。
6
+ * 注入注意:ctx.inject 列出 remote/remote.session 会让回调挂起
7
+ * (那些是目录服务自身的依赖)——这里用 ctx.get / 单服务 inject。
8
+ */
9
+
10
+ import type { HostObservable, ModelCatalog, ModelDirectoriesFace, ModelSelection, UseProjection } from './host-types';
11
+ import { stash } from './services';
12
+ import { diagDebug } from './diag';
13
+
14
+ /** per-session 目录快照(官方 ModelDirectory.store)。 */
15
+ export interface DirectorySnapshot {
16
+ current: ModelSelection | null;
17
+ routable: boolean | null;
18
+ groups: ModelCatalog['groups'];
19
+ failures: readonly { id: string; name: string; message: string }[];
20
+ status: 'idle' | 'loading' | 'ready' | 'selecting' | 'error';
21
+ error: string | null;
22
+ }
23
+
24
+ export interface SessionDirectory {
25
+ store: HostObservable<DirectorySnapshot>;
26
+ load(): Promise<unknown>;
27
+ select(selection: ModelSelection): Promise<void>;
28
+ }
29
+
30
+ /** 取 per-session 目录(官方 directoryFor);服务或会话缺席返回 null。 */
31
+ export function getSessionDirectory(sessionId: string | undefined): SessionDirectory | null {
32
+ const dir = stash.modelDirectories as (ModelDirectoriesFace & { directoryFor?(id: string): SessionDirectory }) | undefined;
33
+ if (!dir || typeof dir.directoryFor !== 'function' || !sessionId) return null;
34
+ try {
35
+ return dir.directoryFor(sessionId);
36
+ } catch (error) {
37
+ diagDebug('directoryFor failed', error);
38
+ return null;
39
+ }
40
+ }
41
+
42
+ export type { ModelCatalog, ModelSelection, UseProjection };
@@ -0,0 +1,123 @@
1
+ /**
2
+ * 权限预设:对齐官方 PermissionSelect——三个预设、文案与确认流程
3
+ * (danger-full-access 需勾选已知风险的确认弹层)。提交仍走命令通道
4
+ * 旁路(sessions.binding(id).session.command),缺失时草稿交换退化。
5
+ */
6
+
7
+ import type { InputActions, SessionsFace } from './host-types';
8
+ import { stash } from './services';
9
+ import { saveAppliedPermission } from './prefs';
10
+ import { diagDebug } from './diag';
11
+ import { t, tOptional } from './i18n';
12
+
13
+ /** 官方三个预设 id(文案走 i18n 的 perm.preset.*,宿主目录缺席时的兜底)。 */
14
+ export const PERMISSION_PRESET_IDS: readonly string[] = ['read-only', 'workspace-write', 'danger-full-access'];
15
+
16
+ /** 预设标签:i18n 词典(perm.preset.*),缺键回退 id。 */
17
+ export function presetLabel(id: string): string {
18
+ return tOptional(`perm.preset.${id}`) ?? id;
19
+ }
20
+
21
+ /** 官方 access.confirm.* 文案(取值函数:语言切换后按当拍语言重新取)。 */
22
+ export function fullAccessConfirm(): {
23
+ title: string;
24
+ description: string;
25
+ acknowledgeLabel: string;
26
+ cancelLabel: string;
27
+ enableLabel: string;
28
+ } {
29
+ return {
30
+ title: t('perm.confirmTitle'),
31
+ description: t('perm.warning'),
32
+ acknowledgeLabel: t('perm.ack'),
33
+ cancelLabel: t('perm.cancel'),
34
+ enableLabel: t('perm.confirmEnable'),
35
+ };
36
+ }
37
+
38
+ /** useProjection("permissions") 的宿主投影形状(官方 PermissionSelect 的 value prop)。 */
39
+ export interface PermissionProjection {
40
+ readonly currentValue: string;
41
+ readonly options: readonly { value: string; name: string; description?: string }[];
42
+ }
43
+
44
+ /**
45
+ * 官方 permissionLabel:内建预设名映射产品文案;宿主自配的 kebab 名转
46
+ * Title Case;其余(非 kebab)原样透传。
47
+ */
48
+ export function permissionLabel(value: string, name: string): string {
49
+ const builtIn = PERMISSION_PRESET_IDS.includes(value);
50
+ if (builtIn && (name === value || name === presetLabel(value))) return presetLabel(value);
51
+ if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(name)) return name;
52
+ return name
53
+ .split('-')
54
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
55
+ .join(' ');
56
+ }
57
+
58
+ export interface PermissionOutcome {
59
+ readonly ok: boolean;
60
+ readonly via: 'command' | 'draft-exchange';
61
+ readonly message?: string;
62
+ }
63
+
64
+ /** 命令通道:/permission <preset>。matched=false 视为该命令未被识别。 */
65
+ export async function applyViaCommand(sessionId: string, preset: string): Promise<PermissionOutcome | null> {
66
+ const session = stash.sessions?.binding(sessionId)?.session;
67
+ if (!session || typeof session.command !== 'function') return null;
68
+ try {
69
+ const result = await session.command(`/permission ${preset}`);
70
+ if (result?.ok) {
71
+ if (result.value?.matched === false) {
72
+ return { ok: false, via: 'command', message: t('perm.channelRejected') };
73
+ }
74
+ saveAppliedPermission(preset);
75
+ return { ok: true, via: 'command' };
76
+ }
77
+ return { ok: false, via: 'command', message: t('perm.channelRejected') };
78
+ } catch (error) {
79
+ diagDebug('permission command failed', error);
80
+ return { ok: false, via: 'command', message: String(error) };
81
+ }
82
+ }
83
+
84
+ /**
85
+ * 草稿交换退化:原草稿 → 命令行 → 回写原草稿。official 提交是异步事务,
86
+ * 命令被机器消费后按保存的原草稿恢复。
87
+ */
88
+ export function applyViaDraftExchange(
89
+ preset: string,
90
+ actions: InputActions,
91
+ currentDraft: string,
92
+ ): PermissionOutcome {
93
+ try {
94
+ actions.setDraft(`/permission ${preset}`);
95
+ actions.submit();
96
+ setTimeout(() => {
97
+ try {
98
+ actions.setDraft(currentDraft);
99
+ } catch (error) {
100
+ diagDebug('permission draft restore failed', error);
101
+ }
102
+ }, 600);
103
+ saveAppliedPermission(preset);
104
+ return { ok: true, via: 'draft-exchange' };
105
+ } catch (error) {
106
+ diagDebug('permission draft-exchange failed', error);
107
+ return { ok: false, via: 'draft-exchange', message: String(error) };
108
+ }
109
+ }
110
+
111
+ export async function applyPermission(
112
+ sessionId: string,
113
+ preset: string,
114
+ actions: InputActions | undefined,
115
+ currentDraft: string,
116
+ ): Promise<PermissionOutcome> {
117
+ const viaCommand = await applyViaCommand(sessionId, preset);
118
+ if (viaCommand) return viaCommand;
119
+ if (!actions) return { ok: false, via: 'draft-exchange', message: t('perm.noInputSurface') };
120
+ return applyViaDraftExchange(preset, actions, currentDraft);
121
+ }
122
+
123
+ export type { SessionsFace };