@lincyaw/dsh 0.0.0-stage → 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 (55) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +22 -2
  3. package/build/client.mjs +66 -0
  4. package/build/dist/client.js +90 -0
  5. package/build/dist/client.json +70 -0
  6. package/build/ensure.mjs +30 -0
  7. package/cordis.patch.yml +17 -0
  8. package/package.json +95 -3
  9. package/profile.patch.yml +25 -0
  10. package/rows/context.mjs +7 -0
  11. package/rows/load.mjs +8 -0
  12. package/rows/policy.mjs +7 -0
  13. package/rows/routes.mjs +7 -0
  14. package/rows/service.mjs +7 -0
  15. package/rows/skills.mjs +7 -0
  16. package/rows/tools.mjs +7 -0
  17. package/rows/turns.mjs +7 -0
  18. package/rows/workbench.mjs +9 -0
  19. package/rows/workspaces.mjs +7 -0
  20. package/skills/presmium-author/SKILL.md +95 -0
  21. package/skills/presmium-author/references/assistance.md +27 -0
  22. package/skills/presmium-author/references/code.md +52 -0
  23. package/skills/presmium-author/references/directives.md +592 -0
  24. package/skills/presmium-author/references/publishing.md +60 -0
  25. package/skills/presmium-author/references/writing.md +72 -0
  26. package/skills/presmium-present/SKILL.md +69 -0
  27. package/skills/presmium-present/references/classroom.md +46 -0
  28. package/skills/presmium-present/references/delivery.md +63 -0
  29. package/skills/presmium-present/references/lecture.md +58 -0
  30. package/src/client/Frame.tsx +314 -0
  31. package/src/client/brand.tsx +90 -0
  32. package/src/client/chrome.tsx +79 -0
  33. package/src/client/contract.ts +35 -0
  34. package/src/client/dsh.d.ts +129 -0
  35. package/src/client/fallback.tsx +49 -0
  36. package/src/client/frame.css +345 -0
  37. package/src/client/index.tsx +154 -0
  38. package/src/client/layout.ts +217 -0
  39. package/src/client/messages.ts +78 -0
  40. package/src/client/source.tsx +81 -0
  41. package/src/client/styles.css +3 -0
  42. package/src/client/theme.ts +149 -0
  43. package/src/node/context.ts +55 -0
  44. package/src/node/dsh.ts +93 -0
  45. package/src/node/launch.ts +59 -0
  46. package/src/node/policy.ts +59 -0
  47. package/src/node/routes.ts +46 -0
  48. package/src/node/service.ts +280 -0
  49. package/src/node/skills.ts +46 -0
  50. package/src/node/tools.ts +43 -0
  51. package/src/node/turns/index.ts +165 -0
  52. package/src/node/watchdog.mjs +36 -0
  53. package/src/node/workbench.ts +43 -0
  54. package/src/node/workspaces.ts +10 -0
  55. package/web.patch.yml +190 -0
@@ -0,0 +1,345 @@
1
+ html,
2
+ body,
3
+ #root {
4
+ height: 100%;
5
+ }
6
+
7
+ .pmw-frame {
8
+ position: relative;
9
+ display: grid;
10
+ grid-template-rows: minmax(0, 1fr) auto auto;
11
+ grid-template-areas:
12
+ 'activity panels'
13
+ 'status status'
14
+ 'bottom bottom';
15
+ grid-template-columns: 48px minmax(0, 1fr);
16
+ height: 100%;
17
+ overflow: hidden;
18
+ background: var(--pm-color-bg);
19
+ color: var(--pm-color-ink);
20
+ }
21
+
22
+ .pmw-frame__skip {
23
+ position: absolute;
24
+ z-index: 30;
25
+ top: 4px;
26
+ left: 4px;
27
+ padding: var(--pm-space-2) var(--pm-space-3);
28
+ border-radius: var(--pm-radius-control);
29
+ background: var(--pm-color-surface);
30
+ color: var(--pm-color-accent);
31
+ transform: translateY(-200%);
32
+ }
33
+
34
+ .pmw-frame__skip:focus {
35
+ transform: none;
36
+ }
37
+
38
+ .pmw-activity {
39
+ grid-area: activity;
40
+ display: flex;
41
+ flex-direction: column;
42
+ align-items: center;
43
+ gap: var(--pm-space-1);
44
+ padding: var(--pm-space-2) 0;
45
+ border-right: 1px solid var(--pm-color-line);
46
+ background: var(--pm-color-sunk);
47
+ }
48
+
49
+ .pmw-activity__logo {
50
+ display: flex;
51
+ margin-bottom: var(--pm-space-2);
52
+ }
53
+
54
+ .pmw-activity__end {
55
+ display: flex;
56
+ flex-direction: column;
57
+ gap: var(--pm-space-1);
58
+ margin-top: auto;
59
+ }
60
+
61
+ .pmw-activity__button {
62
+ position: relative;
63
+ display: grid;
64
+ place-items: center;
65
+ width: 36px;
66
+ height: 36px;
67
+ padding: 0;
68
+ border: 0;
69
+ border-radius: var(--pm-radius-control);
70
+ background: none;
71
+ color: var(--pm-color-muted);
72
+ cursor: pointer;
73
+ }
74
+
75
+ .pmw-activity__count {
76
+ position: absolute;
77
+ top: 2px;
78
+ right: 2px;
79
+ min-width: 14px;
80
+ padding: 0 3px;
81
+ border-radius: var(--pm-radius-pill);
82
+ background: var(--pm-color-accent);
83
+ color: var(--pm-color-on-accent);
84
+ font-size: 10px;
85
+ line-height: 14px;
86
+ }
87
+
88
+ .pmw-activity__button:hover {
89
+ background: color-mix(in srgb, var(--pm-color-ink) 6%, transparent);
90
+ color: var(--pm-color-ink);
91
+ }
92
+
93
+ .pmw-activity__button[aria-pressed='true'] {
94
+ background: var(--pm-color-surface);
95
+ box-shadow: inset 2px 0 0 var(--pm-color-accent);
96
+ color: var(--pm-color-accent);
97
+ }
98
+
99
+ .pmw-activity__button:focus-visible {
100
+ outline: 2px solid var(--pm-color-accent);
101
+ outline-offset: 1px;
102
+ }
103
+
104
+ .pmw-frame__side,
105
+ .pmw-frame__sidebar {
106
+ grid-area: side;
107
+ min-width: 0;
108
+ min-height: 0;
109
+ overflow: auto;
110
+ border-right: 1px solid var(--pm-color-line);
111
+ background: var(--pm-color-bg);
112
+ }
113
+
114
+ .pmw-frame__sidebar {
115
+ overflow: hidden;
116
+ background: var(--dsw-specific-sidebar-fill, var(--pm-color-sunk));
117
+ }
118
+
119
+ .pmw-frame__side:not([data-shown]),
120
+ .pmw-frame__side[data-content='sessions'],
121
+ .pmw-frame__sidebar:not([data-shown]) {
122
+ display: none;
123
+ }
124
+
125
+ .pmw-frame__side-files[hidden] {
126
+ display: none;
127
+ }
128
+
129
+ .pmw-frame__centre {
130
+ grid-area: centre;
131
+ min-width: 0;
132
+ min-height: 0;
133
+ overflow: hidden;
134
+ background: var(--pm-color-bg);
135
+ }
136
+
137
+ .pmw-frame__centre[hidden] {
138
+ display: none;
139
+ }
140
+
141
+ .pmw-frame__centre > .pmw-home {
142
+ height: 100%;
143
+ }
144
+
145
+ .pmw-frame__panel {
146
+ height: 100%;
147
+ overflow: auto;
148
+ }
149
+
150
+ .pmw-frame__rightbar {
151
+ position: relative;
152
+ grid-area: rightbar;
153
+ min-width: 0;
154
+ overflow: visible;
155
+ }
156
+
157
+ .pmw-frame__dock {
158
+ grid-area: dock;
159
+ display: flex;
160
+ flex-direction: column;
161
+ min-width: 0;
162
+ min-height: 0;
163
+ overflow: hidden;
164
+ border-left: 1px solid var(--pm-color-line);
165
+ background: var(--dsw-alias-bg-base, var(--pm-color-bg));
166
+ }
167
+
168
+ .pmw-frame__dock > * {
169
+ flex: 1;
170
+ min-height: 0;
171
+ }
172
+
173
+ .pmw-frame__dock[hidden] { display: none; }
174
+ .pmw-chat-actions { display: flex; align-items: center; gap: var(--pm-space-1); }
175
+ .pmw-history { display: flex; flex-direction: column; height: 100%; min-height: 0; color: var(--pm-color-ink); }
176
+ .pmw-history__header { display: flex; align-items: center; justify-content: space-between; gap: var(--pm-space-2); padding: var(--pm-space-2) var(--pm-space-3); border-bottom: 1px solid var(--pm-color-line); }
177
+ .pmw-history__header h2 { margin: 0; font-size: var(--pm-text-sm); font-weight: 600; }
178
+ .pmw-history__browser { flex: 1; min-height: 0; overflow: hidden; }
179
+
180
+ .pmw-frame__status {
181
+ grid-area: status;
182
+ display: flex;
183
+ align-items: center;
184
+ gap: var(--pm-space-4);
185
+ min-height: var(--pm-statusbar-height);
186
+ padding: 0 var(--pm-space-3);
187
+ overflow: hidden;
188
+ border-top: 1px solid var(--pm-color-line);
189
+ background: var(--pm-color-sunk);
190
+ color: var(--pm-color-muted);
191
+ font-family: var(--pm-font-mono);
192
+ font-size: var(--pm-text-xs);
193
+ }
194
+
195
+ .pmw-frame__bottom {
196
+ grid-area: bottom;
197
+ min-width: 0;
198
+ }
199
+
200
+ .pmw-frame__bottom:empty {
201
+ display: none;
202
+ }
203
+
204
+ .pmw-frame__overlay {
205
+ position: absolute;
206
+ z-index: 20;
207
+ inset: 0;
208
+ pointer-events: none;
209
+ }
210
+
211
+ .pmw-frame__overlay > * {
212
+ pointer-events: auto;
213
+ }
214
+
215
+ .pmw-frame__panels { grid-area: panels; min-width: 0; min-height: 0; }
216
+ .pmw-frame__side-panel { display: grid; }
217
+ .pmw-frame__side-panel > * { grid-area: 1 / 1; }
218
+ .pmw-frame__side, .pmw-frame__sidebar, .pmw-frame__centre, .pmw-frame__dock, .pmw-frame__rightbar { height: 100%; }
219
+ .pmw-frame__handle { width: 8px; z-index: 11; background: var(--pm-color-line); }
220
+ .pmw-frame__handle:hover, .pmw-frame__handle:focus-visible { background: var(--pm-color-accent); }
221
+
222
+ .pmw-frame[data-narrow] {
223
+ grid-template-columns: minmax(0, 1fr);
224
+ grid-template-rows: auto minmax(0, 1fr) auto auto;
225
+ grid-template-areas:
226
+ 'activity'
227
+ 'panels'
228
+ 'status'
229
+ 'bottom';
230
+ }
231
+
232
+ .pmw-frame[data-narrow] .pmw-activity {
233
+ flex-direction: row;
234
+ justify-content: space-between;
235
+ padding: var(--pm-space-1) var(--pm-space-2);
236
+ overflow-x: auto;
237
+ border-right: 0;
238
+ border-bottom: 1px solid var(--pm-color-line);
239
+ }
240
+
241
+ .pmw-frame[data-narrow] .pmw-activity__logo {
242
+ margin: 0 var(--pm-space-1) 0 0;
243
+ }
244
+
245
+ .pmw-frame[data-narrow] .pmw-activity__end {
246
+ flex-direction: row;
247
+ margin: 0;
248
+ }
249
+
250
+ .pmw-frame[data-narrow] .pmw-activity__button[aria-pressed='true'] {
251
+ box-shadow: inset 0 -2px 0 var(--pm-color-accent);
252
+ }
253
+
254
+ .pmw-frame[data-narrow] .pmw-frame__sidebar,
255
+ .pmw-frame[data-narrow] .pmw-frame__dock,
256
+ .pmw-frame[data-narrow] .pmw-frame__rightbar {
257
+ grid-area: centre;
258
+ border: 0;
259
+ }
260
+
261
+ .pmw-frame[data-narrow] .pmw-frame__dock:not([data-shown]) {
262
+ display: none;
263
+ }
264
+
265
+ .pmw-frame[data-narrow] .pmw-frame__rightbar {
266
+ pointer-events: none;
267
+ }
268
+
269
+ .pmw-frame[data-narrow] .pmw-frame__rightbar > * {
270
+ pointer-events: auto;
271
+ }
272
+
273
+ .pmw-frame[data-narrow] .pmw-frame__status {
274
+ gap: var(--pm-space-2);
275
+ }
276
+
277
+ .pmw-logo {
278
+ flex: none;
279
+ display: block;
280
+ }
281
+
282
+ .pmw-brand-name {
283
+ font-family: var(--pm-font-sans);
284
+ font-weight: 600;
285
+ letter-spacing: 0.01em;
286
+ }
287
+
288
+ .pmw-welcome__card {
289
+ display: flex;
290
+ flex-direction: column;
291
+ gap: var(--pm-space-3);
292
+ max-width: 30rem;
293
+ padding: var(--pm-space-6);
294
+ }
295
+
296
+ .pmw-welcome__title {
297
+ margin: 0;
298
+ font-family: var(--pm-font-serif);
299
+ font-size: var(--pm-text-title);
300
+ }
301
+
302
+ .pmw-welcome__card p {
303
+ margin: 0;
304
+ line-height: var(--pm-leading-ui);
305
+ }
306
+
307
+ .pmw-welcome__actions {
308
+ display: flex;
309
+ flex-wrap: wrap;
310
+ justify-content: flex-end;
311
+ gap: var(--pm-space-2);
312
+ }
313
+
314
+ [data-plugin-group='extensions'] {
315
+ display: none !important;
316
+ }
317
+
318
+ .pmw-fallback {
319
+ height: 100%;
320
+ overflow: auto;
321
+ background: var(--pm-color-bg);
322
+ }
323
+
324
+ .pmw-fallback__doc {
325
+ display: grid;
326
+ grid-template-columns: 240px minmax(0, 1fr);
327
+ height: 100%;
328
+ }
329
+
330
+ .pmw-fallback__tab {
331
+ height: 100%;
332
+ overflow: hidden;
333
+ }
334
+
335
+ .pmw-frame[data-narrow] .pmw-frame__panels { display: block !important; }
336
+ .pmw-frame[data-narrow] .pmw-frame__panels > [data-panel] { width: 100%; height: 100%; }
337
+ .pmw-frame__panels > [hidden] { display: none !important; }
338
+ .pmw-frame[data-narrow] .pmw-frame__sidebar[data-shown] { height: 100%; }
339
+
340
+ .pmw-frame__rightbar-overlay { position: absolute; top: 0; bottom: var(--pm-statusbar-height); height: auto; z-index: 13; pointer-events: none; }
341
+ .pmw-frame__rightbar-track { height: 100%; width: 100%; }
342
+ .pmw-frame__rightbar-overlay [data-sidebar-right-session]:has([data-pmw-source-route]) { opacity: 0; }
343
+ .pmw-frame__rightbar-overlay [data-sidebar-right-session]:not([data-sidebar-right-open]) [data-dockkit-host='dock'] { transition: none; }
344
+ .pmw-frame__document { height: 100%; min-width: 0; min-height: 0; }
345
+ .pmw-frame__document[hidden] { display: none; }
@@ -0,0 +1,154 @@
1
+ import { localeOf, Workbench } from '@lincyaw/workbench'
2
+ import type { Locale } from '@lincyaw/workbench'
3
+ import { brand } from './brand.tsx'
4
+ import { chrome } from './chrome.tsx'
5
+ import { CLIENT_ID, ROOT_CHILDREN, UI_LAYOUT } from './contract.ts'
6
+ import { FALLBACK_PANEL, fallback } from './fallback.tsx'
7
+ import { frameFor } from './Frame.tsx'
8
+ import { createLayoutStore, LayoutController } from './layout.ts'
9
+ import { sourceLinks } from './source.tsx'
10
+ import { followScheme, ThemePresenter, TOKENS } from './theme.ts'
11
+
12
+ declare const __PRESMIUM_CSS__: string
13
+
14
+ if (typeof document !== 'undefined' && !document.querySelector(`style[data-plugin-css="${CLIENT_ID}"]`)) {
15
+ const style = document.createElement('style')
16
+ style.dataset.plugin = CLIENT_ID
17
+ style.dataset.pluginCss = CLIENT_ID
18
+ style.textContent = __PRESMIUM_CSS__
19
+ document.head.append(style)
20
+ }
21
+
22
+ export const inject = ['slots', 'theme', 'locale', 'shortcuts']
23
+
24
+ function workbenchLocale(active: string | undefined): Locale {
25
+ const tag = active?.toLowerCase() ?? ''
26
+ if (tag.startsWith('zh') || tag.startsWith('en')) return localeOf(tag)
27
+ return localeOf(navigator.language)
28
+ }
29
+
30
+ export function apply(ctx: DshClientContext): void {
31
+ const navigation = new Promise<DshClientContext['uiWorkspace']>(resolve => ctx.inject(['uiWorkspace'], inner => resolve(inner.uiWorkspace)))
32
+ let workspaceSnapshot: ReturnType<DshClientContext['workspaces']['list']['getSnapshot']> = { items: [] }
33
+ const workspaceListeners = new Set<() => void>()
34
+ const workspaceStore = {
35
+ getSnapshot: () => workspaceSnapshot,
36
+ subscribe(listener: () => void) {
37
+ workspaceListeners.add(listener)
38
+ return () => { workspaceListeners.delete(listener) }
39
+ },
40
+ }
41
+ // Register the root before the renderer boots; remote-backed workspace data follows later.
42
+ ctx.inject(['workspaces'], inner => inner.effect(() => {
43
+ const update = () => {
44
+ workspaceSnapshot = inner.workspaces.list.getSnapshot()
45
+ for (const listener of workspaceListeners) listener()
46
+ }
47
+ update()
48
+ return inner.workspaces.list.subscribe(update)
49
+ }, 'presmium: workspace list'))
50
+ const workbench = new Workbench({ locale: workbenchLocale(ctx.locale.getLocale().active) })
51
+ const ownLayout = window.__DSH_BOOT__?.entries.some(entry => entry.id === UI_LAYOUT)
52
+ let showEditor = (layout: DshClientContext['layout']) => layout.selectPanel(FALLBACK_PANEL)
53
+ sourceLinks(ctx, workbench, layout => showEditor(layout))
54
+ ctx.effect(() => () => workbench.dispose(), 'presmium: workbench')
55
+ ctx.inject(['sessions'], inner => inner.effect(() => {
56
+ let previous: string | undefined
57
+ let pending: AbortController | undefined
58
+ const sync = () => {
59
+ const session = Object.values(inner.sessions.list.getSnapshot().byId).find(row => (row.retainedBy.mainView ?? 0) > 0)
60
+ if (!session) return
61
+ const document = workbench.state.document === '' && workbench.state.documents?.single === false ? null : workbench.state.document ?? null
62
+ const key = JSON.stringify([session.id, document])
63
+ if (key === previous) return
64
+ previous = key
65
+ pending?.abort()
66
+ pending = new AbortController()
67
+ void fetch('/api/presmium/context', {
68
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
69
+ body: JSON.stringify({ session: session.id, document }), signal: pending.signal,
70
+ }).catch(() => {})
71
+ }
72
+ const offSession = inner.sessions.list.subscribe(sync)
73
+ const offDocument = workbench.subscribe(sync)
74
+ sync()
75
+ return () => { offSession(); offDocument(); pending?.abort() }
76
+ }, 'presmium: selected document context (all layouts)'))
77
+ ctx.effect(() => ctx.locale.subscribe(() => workbench.setLocale(workbenchLocale(ctx.locale.getLocale().active))), 'presmium: language')
78
+ ctx.effect(() => ctx.theme.overrideTokens(CLIENT_ID, TOKENS), 'presmium: 书卷 tokens')
79
+ ctx.effect(() => {
80
+ const follow = (snapshot: DshThemeSnapshot) => workbench.setDark(followScheme(snapshot))
81
+ follow(ctx.theme.getTheme())
82
+ return ctx.on('theme/change', follow)
83
+ }, 'presmium: light and dark')
84
+
85
+ if (ownLayout) {
86
+ fallback(ctx, workbench)
87
+ } else {
88
+ ctx.effect(() => {
89
+ const presenter = new ThemePresenter()
90
+ presenter.apply(ctx.theme.getTheme())
91
+ const off = ctx.on('theme/change', (snapshot: DshThemeSnapshot) => presenter.apply(snapshot))
92
+ return () => {
93
+ off()
94
+ presenter.dispose()
95
+ }
96
+ }, 'presmium: theme presenter')
97
+ ctx.effect(() => {
98
+ const handle = createLayoutStore()
99
+ const instance = handle.create()
100
+ const panelStore = chrome(ctx, workbench, instance.actions, instance.getSnapshot, async () => {
101
+ const { root } = await workbench.client.workbench()
102
+ const normalize = (path: string) => path.replace(/\\/g, '/').replace(/\/$/, '')
103
+ const workspace = workspaceSnapshot.items.find(item => root && normalize(item.path) === normalize(root))
104
+ if (workspace) (await navigation).startSession(workspace.workspaceId)
105
+ })
106
+ const store = { ...handle, create: () => instance }
107
+ const mainKeys = () => ctx.slots.entries('main').flatMap(entry => (entry.options.key === undefined ? [] : [entry.options.key]))
108
+ const layout = new LayoutController(instance.actions, id => mainKeys().includes(id), {
109
+ getSnapshot: () => instance.getSnapshot().panelInfo,
110
+ subscribe: listener => instance.subscribe(listener),
111
+ })
112
+ showEditor = () => layout.showDocument()
113
+ const openSettings = () => {
114
+ const triggers = document.querySelectorAll<HTMLElement>('[data-pmw-slot="sidebar"] button[aria-haspopup="dialog"]')
115
+ triggers[triggers.length - 1]?.click()
116
+ }
117
+ const disposePanelInfo = ctx.slots.provideRoot({ hooks: { panelInfo: layout.panelInfo } })
118
+ const disposeService = ctx.reflect.provide('layout', layout)
119
+ const nativeSidebar = window.__DSH_BOOT__?.entries.some(entry => entry.id === '@deepseek-ai/dsh-client-ui-sidebar')
120
+ const disposeRoot = ctx.slots.register({ name: 'root', locale: 'common', children: {
121
+ ...ROOT_CHILDREN,
122
+ ...(nativeSidebar ? {} : { 'sidebar.panellist': { kind: 'list', scope: 'root' } as const }),
123
+ }, store }, frameFor(workbench, openSettings, id => navigation.then(service => service.openWorkspace(id, () => layout.preserveNextConversation())), workspaceStore, panelStore))
124
+ const binding = { code: 'KeyB', modifiers: ['primary', 'alt'] }
125
+ const disposeShortcut = ctx.shortcuts.register({
126
+ id: 'sidebar.left.toggle',
127
+ label: () => (workbench.state.locale === 'zh-CN' ? '展开/收起侧栏' : 'Toggle the side panel'),
128
+ aliases: ['sidebar', 'toggle left sidebar'],
129
+ defaults: {
130
+ 'desktop:macos': { code: 'KeyB', modifiers: ['primary'] },
131
+ 'desktop:windows': { code: 'KeyB', modifiers: ['primary'] },
132
+ 'desktop:linux': { code: 'KeyB', modifiers: ['primary'] },
133
+ 'web:macos': binding,
134
+ 'web:windows': binding,
135
+ },
136
+ regions: ['page', 'editable'],
137
+ modals: [],
138
+ resolve: () => ({ status: 'handled', run: () => layout.toggleSidebar() }),
139
+ })
140
+ const retain = () => instance.actions.retainMainPanels(mainKeys())
141
+ const disposePanels = ctx.slots.subscribe('main', retain)
142
+ retain()
143
+ return () => {
144
+ disposeShortcut()
145
+ layout.dispose()
146
+ disposePanels()
147
+ disposeRoot()
148
+ disposePanelInfo()
149
+ disposeService()
150
+ }
151
+ }, 'presmium: the root layout and ctx.layout')
152
+ }
153
+ brand(ctx, workbench)
154
+ }