@linxin666/dsh-pet 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/LICENSE +29 -0
  2. package/README.md +106 -0
  3. package/assets/whale/pet.json +7 -0
  4. package/assets/whale/previews/failed.gif +0 -0
  5. package/assets/whale/previews/idle.gif +0 -0
  6. package/assets/whale/previews/jumping.gif +0 -0
  7. package/assets/whale/previews/review.gif +0 -0
  8. package/assets/whale/previews/running-left.gif +0 -0
  9. package/assets/whale/previews/running-right.gif +0 -0
  10. package/assets/whale/previews/running.gif +0 -0
  11. package/assets/whale/previews/waiting.gif +0 -0
  12. package/assets/whale/previews/waving.gif +0 -0
  13. package/assets/whale/spritesheet.webp +0 -0
  14. package/cordis.patch.yml +10 -0
  15. package/lib/client.js +1515 -0
  16. package/lib/index.js +642 -0
  17. package/lib/invariant.js +34 -0
  18. package/lib/types/affinity.d.ts +83 -0
  19. package/lib/types/affinity.d.ts.map +1 -0
  20. package/lib/types/client/PetDockEntry.d.ts +44 -0
  21. package/lib/types/client/PetDockEntry.d.ts.map +1 -0
  22. package/lib/types/client/PetSettingsCard.d.ts +67 -0
  23. package/lib/types/client/PetSettingsCard.d.ts.map +1 -0
  24. package/lib/types/client/PluginSettingsCard.d.ts +78 -0
  25. package/lib/types/client/PluginSettingsCard.d.ts.map +1 -0
  26. package/lib/types/client/WhalePet.d.ts +49 -0
  27. package/lib/types/client/WhalePet.d.ts.map +1 -0
  28. package/lib/types/client/index.d.ts +46 -0
  29. package/lib/types/client/index.d.ts.map +1 -0
  30. package/lib/types/client/locales.d.ts +101 -0
  31. package/lib/types/client/locales.d.ts.map +1 -0
  32. package/lib/types/client/pet-store.d.ts +49 -0
  33. package/lib/types/client/pet-store.d.ts.map +1 -0
  34. package/lib/types/client/settings-form.d.ts +119 -0
  35. package/lib/types/client/settings-form.d.ts.map +1 -0
  36. package/lib/types/client/slots-augment.d.ts +19 -0
  37. package/lib/types/client/slots-augment.d.ts.map +1 -0
  38. package/lib/types/client/spritesheet.d.ts +69 -0
  39. package/lib/types/client/spritesheet.d.ts.map +1 -0
  40. package/lib/types/index.d.ts +44 -0
  41. package/lib/types/index.d.ts.map +1 -0
  42. package/lib/types/invariant.d.ts +10 -0
  43. package/lib/types/invariant.d.ts.map +1 -0
  44. package/lib/types/persist.d.ts +45 -0
  45. package/lib/types/persist.d.ts.map +1 -0
  46. package/lib/types/routes.d.ts +22 -0
  47. package/lib/types/routes.d.ts.map +1 -0
  48. package/lib/types/service.d.ts +161 -0
  49. package/lib/types/service.d.ts.map +1 -0
  50. package/lib/types/state.d.ts +80 -0
  51. package/lib/types/state.d.ts.map +1 -0
  52. package/lib/types/treats.d.ts +59 -0
  53. package/lib/types/treats.d.ts.map +1 -0
  54. package/package.json +100 -0
  55. package/src/affinity.test.ts +74 -0
  56. package/src/affinity.ts +144 -0
  57. package/src/client/PetDockEntry.tsx +95 -0
  58. package/src/client/PetSettingsCard.tsx +186 -0
  59. package/src/client/PluginSettingsCard.tsx +207 -0
  60. package/src/client/WhalePet.tsx +342 -0
  61. package/src/client/css-modules.d.ts +6 -0
  62. package/src/client/index.ts +261 -0
  63. package/src/client/locales.ts +108 -0
  64. package/src/client/pet-store.ts +80 -0
  65. package/src/client/pet.module.css +185 -0
  66. package/src/client/settings-card.module.css +277 -0
  67. package/src/client/settings-form.ts +294 -0
  68. package/src/client/slots-augment.ts +27 -0
  69. package/src/client/spritesheet.ts +138 -0
  70. package/src/index.ts +148 -0
  71. package/src/invariant.ts +40 -0
  72. package/src/persist.test.ts +96 -0
  73. package/src/persist.ts +128 -0
  74. package/src/routes.ts +171 -0
  75. package/src/service.ts +389 -0
  76. package/src/state.test.ts +65 -0
  77. package/src/state.ts +156 -0
  78. package/src/treats.test.ts +86 -0
  79. package/src/treats.ts +101 -0
@@ -0,0 +1,277 @@
1
+ .card {
2
+ list-style: none;
3
+ border: 1px solid var(--dsw-alias-border-l2);
4
+ border-radius: 8px;
5
+ background: var(--dsw-alias-bg-layer-3);
6
+ overflow: hidden;
7
+ transition: border-color 0.16s, background 0.16s;
8
+ }
9
+
10
+ .cardOpen {
11
+ background: var(--dsw-alias-bg-layer-2);
12
+ border-color: var(--dsw-alias-label-dimmed);
13
+ }
14
+
15
+ .header {
16
+ display: flex;
17
+ align-items: center;
18
+ gap: 8px;
19
+ width: 100%;
20
+ padding: 10px 14px;
21
+ border: 0;
22
+ background: transparent;
23
+ cursor: pointer;
24
+ text-align: left;
25
+ font: inherit;
26
+ transition: background 120ms ease;
27
+ }
28
+
29
+ .header:hover {
30
+ background: var(--dsw-alias-interactive-bg-hover);
31
+ }
32
+
33
+ .header:active {
34
+ background: var(--dsw-alias-interactive-bg-hover-solid);
35
+ }
36
+
37
+ .header:focus-visible {
38
+ outline: none;
39
+ /* Inset ring so it is not clipped by the card's overflow:hidden. */
40
+ box-shadow: inset 0 0 0 2px var(--dsw-alias-button-info-fill);
41
+ }
42
+
43
+ .headText {
44
+ display: flex;
45
+ flex-direction: column;
46
+ gap: 2px;
47
+ flex: 1;
48
+ min-width: 0;
49
+ }
50
+
51
+ .name {
52
+ font-weight: 600;
53
+ color: var(--dsw-alias-label-primary);
54
+ }
55
+
56
+ .description {
57
+ font-size: 12px;
58
+ color: var(--dsw-alias-label-tertiary);
59
+ }
60
+
61
+ .pending {
62
+ font-size: 12px;
63
+ color: var(--dsw-alias-state-warn-primary);
64
+ }
65
+
66
+ .chevron {
67
+ transition: transform 120ms ease;
68
+ color: var(--dsw-alias-label-tertiary);
69
+ }
70
+
71
+ .chevronOpen {
72
+ transform: rotate(180deg);
73
+ }
74
+
75
+ .body {
76
+ padding: 0 14px 14px;
77
+ display: flex;
78
+ flex-direction: column;
79
+ gap: 14px;
80
+ }
81
+
82
+ .readOnly {
83
+ margin: 0;
84
+ font-size: 12px;
85
+ color: var(--dsw-alias-label-tertiary);
86
+ }
87
+
88
+ .footer {
89
+ display: flex;
90
+ align-items: center;
91
+ justify-content: flex-end;
92
+ gap: 8px;
93
+ }
94
+
95
+ .failed {
96
+ margin: 0 auto 0 0;
97
+ font-size: 12px;
98
+ color: var(--dsw-alias-state-error-primary);
99
+ }
100
+
101
+ .discard,
102
+ .save {
103
+ padding: 5px 12px;
104
+ border-radius: 6px;
105
+ font: inherit;
106
+ font-size: 13px;
107
+ cursor: pointer;
108
+ transition: color 120ms ease, border-color 120ms ease,
109
+ background 120ms ease, box-shadow 120ms ease;
110
+ }
111
+
112
+ .discard {
113
+ border: 1px solid var(--dsw-alias-border-l2);
114
+ background: transparent;
115
+ color: var(--dsw-alias-label-secondary);
116
+ }
117
+
118
+ .discard:hover:not(:disabled) {
119
+ color: var(--dsw-alias-label-primary);
120
+ border-color: var(--dsw-alias-label-dimmed);
121
+ }
122
+
123
+ .discard:active:not(:disabled) {
124
+ background: var(--dsw-alias-interactive-bg-hover);
125
+ }
126
+
127
+ .save {
128
+ border: 1px solid var(--dsw-alias-button-info-fill);
129
+ background: var(--dsw-alias-button-info-fill);
130
+ color: var(--dsw-alias-label-primary-foreground);
131
+ }
132
+
133
+ .save:hover:not(:disabled) {
134
+ border-color: var(--dsw-alias-button-info-hover);
135
+ background: var(--dsw-alias-button-info-hover);
136
+ }
137
+
138
+ .save:active:not(:disabled) {
139
+ filter: brightness(0.94);
140
+ }
141
+
142
+ .discard:focus-visible:not(:disabled),
143
+ .save:focus-visible:not(:disabled) {
144
+ outline: none;
145
+ box-shadow: 0 0 0 2px var(--dsw-alias-button-info-fill);
146
+ }
147
+
148
+ .discard:disabled,
149
+ .save:disabled {
150
+ opacity: 0.5;
151
+ cursor: default;
152
+ }
153
+
154
+ .field {
155
+ display: flex;
156
+ flex-direction: column;
157
+ gap: 4px;
158
+ }
159
+
160
+ .head {
161
+ display: flex;
162
+ align-items: center;
163
+ gap: 8px;
164
+ }
165
+
166
+ .label {
167
+ font-size: 13px;
168
+ font-weight: 500;
169
+ color: var(--dsw-alias-label-primary);
170
+ }
171
+
172
+ .badges {
173
+ display: flex;
174
+ align-items: center;
175
+ gap: 6px;
176
+ }
177
+
178
+ .badge {
179
+ font-size: 11px;
180
+ padding: 1px 6px;
181
+ border-radius: 999px;
182
+ background: var(--dsw-alias-interactive-bg-hover-accent);
183
+ color: var(--dsw-alias-state-business-primary);
184
+ }
185
+
186
+ .reset {
187
+ font-size: 11px;
188
+ border: 0;
189
+ background: transparent;
190
+ color: var(--dsw-alias-state-business-primary);
191
+ cursor: pointer;
192
+ padding: 0;
193
+ border-radius: 3px;
194
+ transition: color 120ms ease, box-shadow 120ms ease;
195
+ }
196
+
197
+ .reset:hover:not(:disabled) {
198
+ color: var(--dsw-alias-label-primary-bluish);
199
+ text-decoration: underline;
200
+ }
201
+
202
+ .reset:active:not(:disabled) {
203
+ color: var(--dsw-alias-state-business-primary);
204
+ }
205
+
206
+ .reset:focus-visible:not(:disabled) {
207
+ outline: none;
208
+ box-shadow: 0 0 0 2px var(--dsw-alias-button-info-fill);
209
+ }
210
+
211
+ .reset:disabled {
212
+ opacity: 0.5;
213
+ cursor: default;
214
+ }
215
+
216
+ .input,
217
+ .select {
218
+ padding: 6px 8px;
219
+ border: 1px solid var(--dsw-alias-border-l2);
220
+ border-radius: 6px;
221
+ font: inherit;
222
+ font-size: 13px;
223
+ color: var(--dsw-alias-label-primary);
224
+ background: var(--dsw-specific-input-major);
225
+ }
226
+
227
+ .inputInvalid {
228
+ padding: 6px 8px;
229
+ border: 1px solid var(--dsw-alias-state-error-primary);
230
+ border-radius: 6px;
231
+ font: inherit;
232
+ font-size: 13px;
233
+ color: var(--dsw-alias-label-primary);
234
+ }
235
+
236
+ .input:disabled,
237
+ .select:disabled {
238
+ opacity: 0.6;
239
+ }
240
+
241
+ .input:focus,
242
+ .select:focus {
243
+ outline: none;
244
+ border-color: var(--dsw-alias-button-info-fill);
245
+ box-shadow: 0 0 0 2px var(--dsw-alias-button-info-fill);
246
+ }
247
+
248
+ .inputInvalid:focus {
249
+ outline: none;
250
+ box-shadow: 0 0 0 2px var(--dsw-alias-state-error-primary);
251
+ }
252
+
253
+ .hint {
254
+ margin: 0;
255
+ font-size: 12px;
256
+ color: var(--dsw-alias-label-secondary);
257
+ }
258
+
259
+ .invalid {
260
+ margin: 0;
261
+ font-size: 12px;
262
+ color: var(--dsw-alias-state-error-primary);
263
+ }
264
+
265
+ /* Reduced motion: drop the card/button transitions. Token colors and the
266
+ focus rings stay, so interactive states remain visible without motion. */
267
+ @media (prefers-reduced-motion: reduce) {
268
+ .card,
269
+ .header,
270
+ .chevron,
271
+ .chevronOpen,
272
+ .reset,
273
+ .discard,
274
+ .save {
275
+ transition: none;
276
+ }
277
+ }
@@ -0,0 +1,294 @@
1
+ /**
2
+ * Staged form model behind the plugin settings card. A card stages what the
3
+ * user types and writes it only when they save — the settings write is a
4
+ * durable, revision-fenced document mutation, so staging keeps what is on
5
+ * screen exactly what a save would store. Mirrors the official
6
+ * ui-plugin-config card-store pattern in a self-contained slice: this
7
+ * package must not depend on a sibling UI package.
8
+ */
9
+
10
+ import type { SettingsScope, SettingsScopeSnapshot, SnapshotStore } from '@deepseek-ai/dsh-client-runtime/client'
11
+ import { createSnapshotStore } from '@deepseek-ai/dsh-client-runtime/client'
12
+
13
+ /** The write one field's staged text performs when the card is saved. */
14
+ export type FieldWrite =
15
+ | { kind: 'set'; value: unknown }
16
+ | { kind: 'clear' }
17
+
18
+ /** How one field converts between its stored value and its draft text. */
19
+ export interface FieldSpec {
20
+ /** Field name inside the namespace section. */
21
+ field: string
22
+ /** Render a stored value as draft text; the empty string when the section carries none. */
23
+ format: (value: unknown) => string
24
+ /**
25
+ * The write this draft text stages, or undefined when the text is not a
26
+ * value this field accepts — which blocks the save rather than discarding it.
27
+ */
28
+ parse: (text: string) => FieldWrite | undefined
29
+ }
30
+
31
+ /** One field as the card renders it. */
32
+ export interface FieldState {
33
+ /** Draft text the control renders. */
34
+ text: string
35
+ /** Whether saving would leave a user-layer entry for this field. */
36
+ overridden: boolean
37
+ /** Whether the draft is not a value this field accepts, which blocks saving. */
38
+ invalid: boolean
39
+ }
40
+
41
+ /** Form state every plugin settings card shares. */
42
+ export interface CardShell {
43
+ /** False while the namespace is not served to this client; the card renders nothing. */
44
+ available: boolean
45
+ /** Whether the Host document accepts writes. */
46
+ writable: boolean
47
+ /** Whether the form holds edits that a save would write. */
48
+ dirty: boolean
49
+ /** Whether any staged draft is invalid, which blocks the save. */
50
+ invalid: boolean
51
+ /** Whether a save is crossing the wire. */
52
+ saving: boolean
53
+ /** Whether the last save did not land as staged; cleared by the next edit or save. */
54
+ failed: boolean
55
+ }
56
+
57
+ /** The write actions the card's slot entry injects. */
58
+ export interface CardActions {
59
+ /** Stage draft text for one field. */
60
+ edit: (field: string, text: string) => void
61
+ /** Stage a clear, so saving lets the field re-inherit the composition layer. */
62
+ resetField: (field: string) => void
63
+ /** Write every staged edit, then re-seed from what the Host accepted. */
64
+ save: () => void
65
+ /** Drop every staged edit. */
66
+ discard: () => void
67
+ }
68
+
69
+ /** One field's staged edit. */
70
+ interface StagedEdit {
71
+ /** Draft text the control renders. */
72
+ text: string
73
+ /** True when this edit clears the field whatever text it shows. */
74
+ clear: boolean
75
+ }
76
+
77
+ /** One staged edit resolved into the write a save performs. */
78
+ interface PlannedWrite {
79
+ /** Field this entry writes. */
80
+ field: string
81
+ /** Perform the write and report whether the Host holds the staged value afterwards. */
82
+ run: (() => Promise<boolean>) | undefined
83
+ }
84
+
85
+ /** A whole-number field. An empty draft clears the field; any other draft that is not a finite number blocks the save. */
86
+ export function numberField(field: string): FieldSpec {
87
+ return {
88
+ field,
89
+ format: value => typeof value === 'number' ? String(value) : '',
90
+ parse: (text) => {
91
+ const trimmed = text.trim()
92
+ if (trimmed === '') return { kind: 'clear' }
93
+ const parsed = Number(trimmed)
94
+ return Number.isFinite(parsed) ? { kind: 'set', value: parsed } : undefined
95
+ },
96
+ }
97
+ }
98
+
99
+ /** A free-text field. An empty draft clears the field. */
100
+ export function textField(field: string): FieldSpec {
101
+ return {
102
+ field,
103
+ format: value => typeof value === 'string' ? value : '',
104
+ parse: (text) => {
105
+ const trimmed = text.trim()
106
+ return trimmed === '' ? { kind: 'clear' } : { kind: 'set', value: trimmed }
107
+ },
108
+ }
109
+ }
110
+
111
+ /** A boolean field, edited through true/false draft text. */
112
+ export function booleanField(field: string): FieldSpec {
113
+ return {
114
+ field,
115
+ format: value => typeof value === 'boolean' ? String(value) : '',
116
+ parse: (text) => {
117
+ if (text === 'true') return { kind: 'set', value: true }
118
+ if (text === 'false') return { kind: 'set', value: false }
119
+ return undefined
120
+ },
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Stages one card's edits over one settings namespace and writes them on save.
126
+ *
127
+ * The Host is the only authority on whether a value was accepted — its
128
+ * validators own the constraints no schema can express — so the outcome is
129
+ * read back from the section rather than predicted here. A save that did not
130
+ * land keeps its drafts, so the user can correct them instead of retyping.
131
+ */
132
+ export class CardForm<T> {
133
+ private readonly specs: Map<string, FieldSpec>
134
+ private readonly staged = new Map<string, StagedEdit>()
135
+ private readonly listeners = new Set<() => void>()
136
+ private saving = false
137
+ private failed = false
138
+
139
+ /** @param scope - the bound settings scope for this card's namespace. */
140
+ constructor(
141
+ private readonly scope: SettingsScope<T>,
142
+ specs: FieldSpec[],
143
+ ) {
144
+ this.specs = new Map(specs.map(spec => [spec.field, spec]))
145
+ scope.subscribe(() => { this.publish() })
146
+ }
147
+
148
+ /** Publish a projection of this form, rebuilt whenever the scope or a draft changes. */
149
+ bind<S>(project: () => S): SnapshotStore<S> {
150
+ const store = createSnapshotStore(project())
151
+ this.listeners.add(() => { store.set(project()) })
152
+ return store
153
+ }
154
+
155
+ /** Read the card-level state: what the Host serves, and what a save would do. */
156
+ shell(): CardShell {
157
+ const snapshot = this.scope.getSnapshot()
158
+ const plan = this.plan()
159
+ return {
160
+ available: snapshot.status === 'ready',
161
+ writable: snapshot.writable,
162
+ dirty: plan.length > 0,
163
+ invalid: plan.some(item => item.run === undefined),
164
+ saving: this.saving,
165
+ failed: this.failed,
166
+ }
167
+ }
168
+
169
+ /** Read one field's state from the effective section and its staged draft. */
170
+ field(field: string): FieldState {
171
+ const spec = this.specOf(field)
172
+ const staged = this.staged.get(field)
173
+ if (staged === undefined) {
174
+ return { text: spec.format(this.sectionValue(field)), overridden: this.stored(field), invalid: false }
175
+ }
176
+ const write = staged.clear ? { kind: 'clear' as const } : spec.parse(staged.text)
177
+ return {
178
+ text: staged.text,
179
+ overridden: write?.kind === 'set',
180
+ invalid: write === undefined,
181
+ }
182
+ }
183
+
184
+ /** The actions the card's slot registration injects. */
185
+ actions(): CardActions {
186
+ return {
187
+ edit: (field, text) => { this.stage(field, { text, clear: false }) },
188
+ resetField: (field) => {
189
+ this.stage(field, { text: this.specOf(field).format(this.baseValue(field)), clear: true })
190
+ },
191
+ save: () => { void this.save() },
192
+ discard: () => {
193
+ if (this.staged.size === 0 && !this.failed) return
194
+ this.staged.clear()
195
+ this.failed = false
196
+ this.publish()
197
+ },
198
+ }
199
+ }
200
+
201
+ /**
202
+ * Write every staged edit, then re-seed from what the Host accepted.
203
+ * @returns settlement after every write and the read-back.
204
+ */
205
+ async save(): Promise<void> {
206
+ const plan = this.plan()
207
+ const writes = plan.flatMap(item => item.run === undefined ? [] : [item.run])
208
+ if (plan.length === 0 || this.saving || writes.length !== plan.length) return
209
+ this.saving = true
210
+ this.failed = false
211
+ this.publish()
212
+ let landed = true
213
+ for (const write of writes) {
214
+ landed = await write() && landed
215
+ }
216
+ if (landed) this.staged.clear()
217
+ this.saving = false
218
+ this.failed = !landed
219
+ this.publish()
220
+ }
221
+
222
+ /**
223
+ * Every staged edit a save would write. An entry whose draft is not a value
224
+ * its field accepts carries no write: the form is still dirty, and the save
225
+ * refuses rather than dropping the edit. A staged edit that matches the
226
+ * effective section is not a write at all.
227
+ * @returns the planned writes, in the order the fields were staged.
228
+ */
229
+ private plan(): PlannedWrite[] {
230
+ const plan: PlannedWrite[] = []
231
+ for (const [field, staged] of this.staged) {
232
+ const spec = this.specOf(field)
233
+ if (staged.clear) {
234
+ if (this.stored(field)) plan.push({ field, run: () => this.clear(field) })
235
+ continue
236
+ }
237
+ if (staged.text === spec.format(this.sectionValue(field))) continue
238
+ const write = spec.parse(staged.text)
239
+ if (write === undefined) plan.push({ field, run: undefined })
240
+ else if (write.kind === 'clear') plan.push({ field, run: () => this.clear(field) })
241
+ else plan.push({ field, run: () => this.store(field, write.value) })
242
+ }
243
+ return plan
244
+ }
245
+
246
+ private async clear(field: string): Promise<boolean> {
247
+ await this.scope.unset(field)
248
+ return !this.stored(field)
249
+ }
250
+
251
+ private async store(field: string, value: unknown): Promise<boolean> {
252
+ await this.scope.set(field, value)
253
+ return this.userLayer()?.[field] === value
254
+ }
255
+
256
+ private stage(field: string, edit: StagedEdit): void {
257
+ this.staged.set(field, edit)
258
+ this.failed = false
259
+ this.publish()
260
+ }
261
+
262
+ private specOf(field: string): FieldSpec {
263
+ const spec = this.specs.get(field)
264
+ // Every call site names a field this card declared; a missing one is a
265
+ // wiring mistake that must not degrade into a silently inert control.
266
+ if (spec === undefined) throw new Error(`settings card has no field ${field}`)
267
+ return spec
268
+ }
269
+
270
+ private snapshotOf(): SettingsScopeSnapshot<T> {
271
+ return this.scope.getSnapshot()
272
+ }
273
+
274
+ private sectionValue(field: string): unknown {
275
+ return (this.snapshotOf().value as Record<string, unknown> | undefined)?.[field]
276
+ }
277
+
278
+ private baseValue(field: string): unknown {
279
+ return (this.snapshotOf().base as Record<string, unknown> | undefined)?.[field]
280
+ }
281
+
282
+ private userLayer(): Record<string, unknown> | undefined {
283
+ return this.snapshotOf().user as Record<string, unknown> | undefined
284
+ }
285
+
286
+ private stored(field: string): boolean {
287
+ const user = this.userLayer()
288
+ return user !== undefined && Object.hasOwn(user, field)
289
+ }
290
+
291
+ private publish(): void {
292
+ for (const listener of this.listeners) listener()
293
+ }
294
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Local slot-table augmentation for slots the official NPM SDK
3
+ * (ui-conversation rc.6) does not publish yet, but the running dsh worktree
4
+ * boots with (`conversation.input.selector.*`). Type-only supplement — the
5
+ * runtime surface comes from the dsh profile. Module form (with an import)
6
+ * so TS merges into the package declarations instead of shadowing them.
7
+ * Remove once the SDK ships these slots.
8
+ */
9
+ import type {} from '@deepseek-ai/dsh-client-ui-slots'
10
+
11
+ declare module '@deepseek-ai/dsh-client-ui-slots' {
12
+ interface SlotMap {
13
+ /**
14
+ * The input selector context-chip hole: feature chips rendered right
15
+ * after the workspace selector. Session-maybe: entries stay mounted
16
+ * without a session and hide themselves when their data source is absent.
17
+ */
18
+ 'conversation.input.selector.context': {
19
+ kind: 'list'
20
+ scope: 'session-maybe'
21
+ owner: InputSelectorContextOwnerProps
22
+ }
23
+ }
24
+
25
+ /** Owner share of the input selector context-chip hole (empty by contract). */
26
+ interface InputSelectorContextOwnerProps {}
27
+ }