@lincyaw/dsh 0.0.0-stage → 0.2.0-rc.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 (68) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +26 -2
  3. package/build/client.mjs +72 -0
  4. package/build/dist/client.js +1390 -0
  5. package/build/dist/client.json +274 -0
  6. package/build/ensure.mjs +39 -0
  7. package/cordis.patch.yml +26 -0
  8. package/entries/context.mjs +7 -0
  9. package/entries/events.mjs +7 -0
  10. package/entries/load.mjs +11 -0
  11. package/entries/policy.mjs +7 -0
  12. package/entries/routes.mjs +7 -0
  13. package/entries/service.mjs +7 -0
  14. package/entries/skills.mjs +7 -0
  15. package/entries/telemetry.mjs +7 -0
  16. package/entries/tools.mjs +7 -0
  17. package/entries/turns.mjs +7 -0
  18. package/entries/workbench.mjs +9 -0
  19. package/entries/workspaces.mjs +7 -0
  20. package/package.json +108 -3
  21. package/profile.patch.yml +25 -0
  22. package/src/client/RootLayout.tsx +332 -0
  23. package/src/client/agent-requests.ts +64 -0
  24. package/src/client/assistant-setup.tsx +185 -0
  25. package/src/client/author-settings.ts +57 -0
  26. package/src/client/brand.tsx +97 -0
  27. package/src/client/contract.ts +82 -0
  28. package/src/client/document-context.ts +39 -0
  29. package/src/client/dsh.d.ts +155 -0
  30. package/src/client/fallback.tsx +53 -0
  31. package/src/client/frame.css +505 -0
  32. package/src/client/index.tsx +157 -0
  33. package/src/client/launch-target.ts +89 -0
  34. package/src/client/layout.ts +230 -0
  35. package/src/client/mascot.tsx +37 -0
  36. package/src/client/messages.ts +171 -0
  37. package/src/client/navigation-shell.tsx +74 -0
  38. package/src/client/observable.ts +16 -0
  39. package/src/client/paths.ts +17 -0
  40. package/src/client/root-reload.ts +163 -0
  41. package/src/client/runtime-diagnostics.tsx +228 -0
  42. package/src/client/settings-opener.ts +26 -0
  43. package/src/client/source-links.tsx +71 -0
  44. package/src/client/speech-settings.tsx +7 -0
  45. package/src/client/styles.css +45 -0
  46. package/src/client/theme.ts +151 -0
  47. package/src/client/workbench-keys.ts +36 -0
  48. package/src/client/workspace-events.ts +114 -0
  49. package/src/node/bindings.ts +345 -0
  50. package/src/node/context.ts +166 -0
  51. package/src/node/dsh-types.ts +124 -0
  52. package/src/node/fake-collector.ts +46 -0
  53. package/src/node/fake-model.ts +80 -0
  54. package/src/node/launch.ts +176 -0
  55. package/src/node/policy.ts +56 -0
  56. package/src/node/routes.ts +56 -0
  57. package/src/node/server-routes.ts +3 -0
  58. package/src/node/service.ts +131 -0
  59. package/src/node/skills.ts +24 -0
  60. package/src/node/telemetry.ts +126 -0
  61. package/src/node/tools.ts +66 -0
  62. package/src/node/turns.ts +176 -0
  63. package/src/node/watchdog.mjs +36 -0
  64. package/src/node/workbench.ts +88 -0
  65. package/src/node/workspace-events.ts +167 -0
  66. package/src/node/workspaces.ts +10 -0
  67. package/src/workspace-events.ts +31 -0
  68. package/web.patch.yml +161 -0
@@ -0,0 +1,505 @@
1
+ html,
2
+ body,
3
+ #root {
4
+ height: 100%;
5
+ }
6
+
7
+ .pmw-frame {
8
+ position: relative;
9
+ display: grid;
10
+ grid-template-rows: auto minmax(0, 1fr) auto;
11
+ grid-template-areas:
12
+ 'appbar'
13
+ 'panels'
14
+ 'bottom';
15
+ grid-template-columns: minmax(0, 1fr);
16
+ height: 100%;
17
+ overflow: hidden;
18
+ background: var(--pm-color-chrome);
19
+ color: var(--pm-color-ink);
20
+ }
21
+
22
+ .pmw-frame__skip {
23
+ position: absolute;
24
+ z-index: 30;
25
+ top: var(--pm-space-1);
26
+ left: var(--pm-space-1);
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
+ /* The app bar across the top: the brand, where the author is, and the commands of the whole workbench. */
39
+
40
+ .pmw-appbar {
41
+ grid-area: appbar;
42
+ display: flex;
43
+ align-items: center;
44
+ gap: var(--pm-space-px);
45
+ min-width: 0;
46
+ height: var(--pm-appbar-height);
47
+ padding: 0 var(--pm-space-2) 0 var(--pm-space-3);
48
+ border-bottom: var(--pm-size-hairline) solid var(--pm-color-line);
49
+ background: var(--pm-color-chrome);
50
+ font-size: var(--pm-text-sm);
51
+ }
52
+
53
+ .pmw-appbar__brand {
54
+ display: inline-flex;
55
+ flex: none;
56
+ align-items: center;
57
+ gap: var(--pm-space-2);
58
+ margin-right: var(--pm-space-3);
59
+ font-size: var(--pm-text-md);
60
+ }
61
+
62
+ .pmw-appbar__places {
63
+ display: flex;
64
+ align-items: center;
65
+ min-width: 0;
66
+ margin: 0;
67
+ padding: 0;
68
+ list-style: none;
69
+ }
70
+
71
+ .pmw-appbar__places > li {
72
+ display: flex;
73
+ align-items: center;
74
+ min-width: 0;
75
+ }
76
+
77
+ .pmw-appbar__places > li + li::before {
78
+ flex: none;
79
+ margin: 0 var(--pm-space-px);
80
+ color: var(--pm-color-line-strong);
81
+ content: '/';
82
+ }
83
+
84
+ .pmw-appbar__place,
85
+ .pmw-appbar__button {
86
+ box-sizing: border-box;
87
+ position: relative;
88
+ display: inline-flex;
89
+ flex: none;
90
+ align-items: center;
91
+ gap: var(--pm-space-1);
92
+ height: var(--pm-size-tool);
93
+ min-width: var(--pm-size-tool);
94
+ margin: 0;
95
+ padding: 0 var(--pm-space-2);
96
+ border: var(--pm-size-hairline) solid transparent;
97
+ border-radius: var(--pm-radius-tool);
98
+ background: none;
99
+ color: var(--pm-color-muted);
100
+ font: inherit;
101
+ white-space: nowrap;
102
+ cursor: pointer;
103
+ }
104
+
105
+ .pmw-appbar__place {
106
+ flex: 0 1 auto;
107
+ }
108
+
109
+ .pmw-appbar__label {
110
+ min-width: 0;
111
+ overflow: hidden;
112
+ text-overflow: ellipsis;
113
+ }
114
+
115
+ .pmw-appbar__place[aria-current] {
116
+ color: var(--pm-color-ink);
117
+ font-weight: 600;
118
+ }
119
+
120
+ span.pmw-appbar__place {
121
+ cursor: default;
122
+ }
123
+
124
+ .pmw-appbar__place:is(button):hover,
125
+ .pmw-appbar__button:hover {
126
+ background: var(--pm-color-hover);
127
+ color: var(--pm-color-ink);
128
+ }
129
+
130
+ .pmw-appbar__button[aria-pressed='true'] {
131
+ border-color: var(--pm-color-accent-line);
132
+ background: var(--pm-color-selected);
133
+ color: var(--pm-color-accent);
134
+ }
135
+
136
+ .pmw-appbar__place:focus-visible,
137
+ .pmw-appbar__button:focus-visible {
138
+ outline: var(--pm-size-stroke) solid var(--pm-color-accent);
139
+ outline-offset: var(--pm-size-hairline);
140
+ }
141
+
142
+ .pmw-appbar__divider {
143
+ flex: none;
144
+ width: var(--pm-size-hairline);
145
+ height: var(--pm-size-divider);
146
+ margin: 0 var(--pm-space-1);
147
+ background: var(--pm-color-line);
148
+ }
149
+
150
+ .pmw-appbar__approvals {
151
+ display: inline-flex;
152
+ flex: none;
153
+ margin-right: var(--pm-space-1);
154
+ }
155
+
156
+ .pmw-appbar__approvals:empty {
157
+ display: none;
158
+ }
159
+
160
+ .pmw-appbar__more {
161
+ display: inline-flex;
162
+ color: var(--pm-color-muted);
163
+ }
164
+
165
+ .pmw-appbar .pm-ui-icon {
166
+ width: var(--pm-size-icon);
167
+ height: var(--pm-size-icon);
168
+ }
169
+
170
+ .pmw-appbar__brand > .pmw-logo {
171
+ width: var(--pm-size-logo);
172
+ height: var(--pm-size-logo);
173
+ }
174
+
175
+ .pmw-appbar__more > .pm-ui-icon-button {
176
+ width: var(--pm-size-tool);
177
+ height: var(--pm-size-tool);
178
+ border-radius: var(--pm-radius-tool);
179
+ }
180
+
181
+ .pmw-appbar__busy {
182
+ position: absolute;
183
+ top: var(--pm-space-1);
184
+ right: var(--pm-space-1);
185
+ width: var(--pm-size-dot);
186
+ height: var(--pm-size-dot);
187
+ border-radius: 50%;
188
+ background: var(--pm-color-accent);
189
+ animation: pmw-pulse var(--pm-duration-pulse) ease-in-out infinite;
190
+ }
191
+
192
+ @keyframes pmw-pulse {
193
+ 50% { opacity: 0.3; }
194
+ }
195
+
196
+ @media (prefers-reduced-motion: reduce) {
197
+ .pmw-appbar__busy { animation: none; }
198
+ }
199
+
200
+ .pmw-frame__sidebar {
201
+ grid-area: side;
202
+ min-width: 0;
203
+ min-height: 0;
204
+ overflow: hidden;
205
+ background: var(--pm-color-chrome);
206
+ }
207
+
208
+ .pmw-frame__sidebar:not([data-shown]) {
209
+ display: none;
210
+ }
211
+
212
+ .pmw-frame__centre {
213
+ grid-area: centre;
214
+ min-width: 0;
215
+ min-height: 0;
216
+ overflow: hidden;
217
+ background: var(--pm-color-bg);
218
+ }
219
+
220
+ .pmw-frame__centre[hidden] {
221
+ display: none;
222
+ }
223
+
224
+ .pmw-frame__centre > .pmw-home {
225
+ height: 100%;
226
+ }
227
+
228
+ .pmw-frame__panel {
229
+ height: 100%;
230
+ overflow: auto;
231
+ }
232
+
233
+ .pmw-frame__rightbar {
234
+ position: relative;
235
+ grid-area: rightbar;
236
+ min-width: 0;
237
+ overflow: visible;
238
+ }
239
+
240
+ .pmw-frame__conversation {
241
+ grid-area: conversation;
242
+ display: flex;
243
+ flex-direction: column;
244
+ min-width: 0;
245
+ min-height: 0;
246
+ overflow: hidden;
247
+ background: var(--pm-color-chrome);
248
+ }
249
+
250
+ .pmw-frame__conversation > * {
251
+ flex: 1;
252
+ min-height: 0;
253
+ }
254
+
255
+ .pmw-frame__conversation[hidden] { display: none; }
256
+ .pmw-chat-actions { display: flex; align-items: center; gap: var(--pm-space-1); }
257
+ .pmw-chat-actions__title { margin-right: var(--pm-space-2); color: var(--pm-color-muted); font-size: var(--pm-text-xs); font-weight: 600; letter-spacing: var(--pm-tracking-label); white-space: nowrap; }
258
+ .pmw-history { display: flex; flex-direction: column; height: 100%; min-height: 0; color: var(--pm-color-ink); }
259
+ .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: var(--pm-size-hairline) solid var(--pm-color-line); }
260
+ .pmw-history__header h2 { margin: 0; font-size: var(--pm-text-sm); font-weight: 600; }
261
+ .pmw-history__browser { flex: 1; min-height: 0; overflow: hidden; }
262
+
263
+ .pmw-frame__bottom {
264
+ grid-area: bottom;
265
+ min-width: 0;
266
+ }
267
+
268
+ .pmw-frame__bottom:empty {
269
+ display: none;
270
+ }
271
+
272
+ .pmw-frame__overlay {
273
+ position: absolute;
274
+ z-index: 20;
275
+ inset: 0;
276
+ pointer-events: none;
277
+ }
278
+
279
+ .pmw-frame__overlay > * {
280
+ pointer-events: auto;
281
+ }
282
+
283
+ .pmw-frame__panels { grid-area: panels; min-width: 0; min-height: 0; }
284
+ .pmw-frame__side-panel { display: grid; }
285
+ .pmw-frame__side-panel > * { grid-area: 1 / 1; }
286
+ .pmw-frame__sidebar, .pmw-frame__centre, .pmw-frame__conversation, .pmw-frame__rightbar { height: 100%; }
287
+ /* A hairline between columns, with a wider area to grab. */
288
+ .pmw-frame__handle { position: relative; width: var(--pm-size-hairline); z-index: 11; background: var(--pm-color-line); }
289
+ .pmw-frame__handle::after { position: absolute; inset: 0 calc(-1 * var(--pm-space-1)); content: ''; }
290
+ .pmw-frame__handle:hover, .pmw-frame__handle:focus-visible, .pmw-frame__handle[data-separator='active'] { background: var(--pm-color-accent); box-shadow: 0 0 0 var(--pm-size-hairline) var(--pm-color-accent); }
291
+
292
+ .pmw-frame[data-narrow] .pmw-appbar__brand > .pmw-brand-name,
293
+ .pmw-frame[data-narrow] .pmw-appbar__label,
294
+ .pmw-frame[data-narrow] .pmw-appbar__places > li + li::before {
295
+ display: none;
296
+ }
297
+
298
+ .pmw-frame[data-narrow] .pmw-appbar__brand {
299
+ margin-right: var(--pm-space-1);
300
+ }
301
+
302
+ .pmw-frame[data-narrow] .pmw-appbar__place[aria-current] {
303
+ background: var(--pm-color-selected);
304
+ color: var(--pm-color-accent);
305
+ }
306
+
307
+ .pmw-frame[data-narrow] .pmw-appbar__place,
308
+ .pmw-frame[data-narrow] .pmw-appbar__button {
309
+ min-width: var(--pm-size-touch);
310
+ height: var(--pm-size-touch);
311
+ justify-content: center;
312
+ }
313
+
314
+ .pmw-frame[data-narrow] .pmw-appbar {
315
+ height: var(--pm-appbar-height-touch);
316
+ }
317
+
318
+ .pmw-frame[data-narrow] .pmw-frame__sidebar,
319
+ .pmw-frame[data-narrow] .pmw-frame__conversation,
320
+ .pmw-frame[data-narrow] .pmw-frame__rightbar {
321
+ grid-area: centre;
322
+ border: 0;
323
+ }
324
+
325
+ .pmw-frame[data-narrow] .pmw-frame__conversation:not([data-shown]) {
326
+ display: none;
327
+ }
328
+
329
+ .pmw-frame[data-narrow] .pmw-frame__rightbar {
330
+ pointer-events: none;
331
+ }
332
+
333
+ .pmw-frame[data-narrow] .pmw-frame__rightbar > * {
334
+ pointer-events: auto;
335
+ }
336
+
337
+ .pmw-logo {
338
+ flex: none;
339
+ display: block;
340
+ }
341
+
342
+ .pmw-brand-name {
343
+ font-family: var(--pm-font-sans);
344
+ font-weight: 600;
345
+ letter-spacing: 0.01em;
346
+ }
347
+
348
+ .pmw-welcome__card {
349
+ display: flex;
350
+ flex-direction: column;
351
+ gap: var(--pm-space-3);
352
+ max-width: 30rem;
353
+ padding: var(--pm-space-6);
354
+ }
355
+
356
+ .pmw-welcome__title {
357
+ margin: 0;
358
+ font-family: var(--pm-font-serif);
359
+ font-size: var(--pm-text-title);
360
+ }
361
+
362
+ .pmw-welcome__card p {
363
+ margin: 0;
364
+ line-height: var(--pm-leading-ui);
365
+ }
366
+
367
+ .pmw-welcome__actions {
368
+ display: flex;
369
+ flex-wrap: wrap;
370
+ justify-content: flex-end;
371
+ gap: var(--pm-space-2);
372
+ }
373
+
374
+ [data-plugin-group='extensions'] {
375
+ display: none !important;
376
+ }
377
+
378
+ .pmw-fallback {
379
+ height: 100%;
380
+ overflow: auto;
381
+ background: var(--pm-color-bg);
382
+ }
383
+
384
+ .pmw-fallback__doc {
385
+ height: 100%;
386
+ }
387
+
388
+ .pmw-fallback__doc[hidden] { display: none; }
389
+
390
+ .pmw-fallback__tab {
391
+ height: 100%;
392
+ overflow: hidden;
393
+ }
394
+
395
+ .pmw-frame[data-narrow] .pmw-frame__panels { display: block !important; }
396
+ /* react-resizable-panels writes `height: auto` on each panel; on a narrow screen each one fills the page. */
397
+ .pmw-frame[data-narrow] .pmw-frame__panels > [data-panel] { width: 100%; height: 100% !important; }
398
+ .pmw-frame__panels > [hidden] { display: none !important; }
399
+ .pmw-frame[data-narrow] .pmw-frame__sidebar[data-shown] { height: 100%; }
400
+
401
+ .pmw-frame__rightbar-overlay { position: absolute; top: 0; bottom: 0; height: auto; z-index: 13; pointer-events: none; }
402
+ .pmw-frame__rightbar-track { height: 100%; width: 100%; }
403
+ .pmw-frame__rightbar-overlay [data-sidebar-right-session]:has([data-pmw-source-route]) { opacity: 0; }
404
+ .pmw-frame__rightbar-overlay [data-sidebar-right-session]:not([data-sidebar-right-open]) [data-dockkit-host='dock'] { transition: none; }
405
+ .pmw-frame__document { height: 100%; min-width: 0; min-height: 0; }
406
+ .pmw-frame__document[hidden] { display: none; }
407
+
408
+ /*
409
+ * Presmium opens one workspace: the conversation does not offer to choose another or another agent preset, and the
410
+ * chat history lists the sessions without the workspace row above them. Matched by slot names and row keys, not by
411
+ * Harness's generated class names.
412
+ */
413
+ [data-slot='conversation.hero.agentPreset'],
414
+ :has(> [data-slot='conversation.hero.agentPreset']) > button,
415
+ [data-slot='sidebar.workspaces'] [data-row-key^='workspace:'],
416
+ [data-slot='sidebar.workspaces'] :has(> [data-slot^='sidebar.workspaces.directory']) {
417
+ display: none !important;
418
+ }
419
+
420
+ /* Runtime diagnostics use shared controls; the shell owns their placement only. */
421
+ .pmw-runtime-feedback { flex: none; padding: var(--pm-space-2) var(--pm-space-3); }
422
+ .pmw-runtime-feedback details { max-block-size: 40dvh; overflow: auto; overflow-wrap: anywhere; }
423
+ .pmw-runtime-feedback__actions { display: flex; flex-wrap: wrap; gap: var(--pm-space-2); margin-block-start: var(--pm-space-2); }
424
+ .pmw-runtime-feedback p { margin: 0; }
425
+ .pmw-runtime-feedback ul { margin-block: var(--pm-space-2); padding-inline-start: var(--pm-space-4); }
426
+ .pmw-runtime-fallback { display: grid; align-content: center; min-height: 100dvh; padding: var(--pm-space-4); }
427
+
428
+ /* The AI assistant without a usable model: the notice in the composer's place, and a failed turn in the author's terms. */
429
+ .pmw-assistant-setup {
430
+ display: grid;
431
+ grid-template-columns: auto minmax(0, 1fr);
432
+ gap: var(--pm-space-2) var(--pm-space-3);
433
+ margin: 0 var(--pm-space-4) var(--pm-space-4);
434
+ padding: var(--pm-space-4);
435
+ border: var(--pm-size-hairline) solid var(--pm-color-line);
436
+ border-radius: var(--pm-radius-card);
437
+ background: var(--pm-color-surface);
438
+ color: var(--pm-color-ink);
439
+ box-shadow: var(--pm-shadow);
440
+ }
441
+
442
+ .pmw-assistant-setup__icon {
443
+ display: inline-flex;
444
+ color: var(--pm-color-accent);
445
+ }
446
+
447
+ .pmw-assistant-setup__copy,
448
+ .pmw-turn-failure__copy {
449
+ display: flex;
450
+ flex-direction: column;
451
+ gap: var(--pm-space-1);
452
+ min-width: 0;
453
+ }
454
+
455
+ .pmw-assistant-setup__copy p,
456
+ .pmw-turn-failure__copy p {
457
+ margin: 0;
458
+ line-height: var(--pm-leading-ui);
459
+ }
460
+
461
+ .pmw-assistant-setup__title,
462
+ .pmw-turn-failure__title {
463
+ font-weight: var(--pm-weight-semibold);
464
+ }
465
+
466
+ .pmw-assistant-setup__actions {
467
+ display: flex;
468
+ grid-column: 1 / -1;
469
+ justify-content: flex-end;
470
+ }
471
+
472
+ .pmw-turn-failure {
473
+ display: grid;
474
+ grid-template-columns: auto minmax(0, 1fr);
475
+ gap: var(--pm-space-2);
476
+ padding: var(--pm-space-3);
477
+ border-radius: var(--pm-radius-control);
478
+ background: var(--pm-color-warn-soft);
479
+ color: var(--pm-color-ink);
480
+ font-size: var(--pm-text-sm);
481
+ }
482
+
483
+ .pmw-turn-failure__mark {
484
+ display: inline-flex;
485
+ color: var(--pm-color-warn);
486
+ }
487
+
488
+ .pmw-turn-failure__actions {
489
+ display: flex;
490
+ margin-top: var(--pm-space-1);
491
+ }
492
+
493
+ .pmw-turn-failure__details {
494
+ color: var(--pm-color-muted);
495
+ font-size: var(--pm-text-xs);
496
+ }
497
+
498
+ .pmw-turn-failure__details summary {
499
+ cursor: pointer;
500
+ }
501
+
502
+ .pmw-turn-failure__details p {
503
+ margin-top: var(--pm-space-1);
504
+ overflow-wrap: anywhere;
505
+ }
@@ -0,0 +1,157 @@
1
+ import { connectAuthoringClient, localeOf, Workbench, workspaceClient } from '@lincyaw/workbench'
2
+ import type { Locale } from '@lincyaw/workbench'
3
+ import { workspaceSubscriptions } from './workspace-events.ts'
4
+ import { registerAuthorSettings } from './author-settings.ts'
5
+ import { registerSpeechSettings } from './speech-settings.tsx'
6
+ import { openSpeechSettings } from './settings-opener.ts'
7
+ import { registerBrand } from './brand.tsx'
8
+ import { registerMascot } from './mascot.tsx'
9
+ import { CLIENT_ID, isBootEntryLoaded, ROOT_CHILDREN, UI_LAYOUT, UI_SIDEBAR } from './contract.ts'
10
+ import { syncDocumentContext } from './document-context.ts'
11
+ import { FALLBACK_PANEL, registerFallbackLayout } from './fallback.tsx'
12
+ import { createLayoutStore, LayoutController } from './layout.ts'
13
+ import { connectLaunchTarget } from './launch-target.ts'
14
+ import { CLIENT_MESSAGES } from './messages.ts'
15
+ import { registerNavigationShell } from './navigation-shell.tsx'
16
+ import { createObservable } from './observable.ts'
17
+ import { isSamePath } from './paths.ts'
18
+ import { registerAgentRequests } from './agent-requests.ts'
19
+ import { registerAssistantSetup } from './assistant-setup.tsx'
20
+ import { rootLayoutFor } from './RootLayout.tsx'
21
+ import { beginRootReload, restoreRootReturnPoint, saveRootReturnPoint } from './root-reload.ts'
22
+ import { registerSourceLinks } from './source-links.tsx'
23
+ import { registerWorkbenchKeys } from './workbench-keys.ts'
24
+ import { runtimeDiagnostics } from './runtime-diagnostics.tsx'
25
+ import { DSH_THEME_TOKENS, syncDarkClass, ThemePresenter } from './theme.ts'
26
+
27
+ declare const __PRESMIUM_CSS__: string
28
+
29
+ if (typeof document !== 'undefined' && !document.querySelector(`style[data-plugin-css="${CLIENT_ID}"]`)) {
30
+ const style = document.createElement('style')
31
+ style.dataset.plugin = CLIENT_ID
32
+ style.dataset.pluginCss = CLIENT_ID
33
+ style.textContent = __PRESMIUM_CSS__
34
+ document.head.append(style)
35
+ }
36
+
37
+ export const inject = ['slots', 'theme', 'locale', 'shortcuts']
38
+
39
+ // The studio's one language source: the language chosen in Settings, else the browser's.
40
+ function workbenchLocale(active: string | undefined): Locale {
41
+ return localeOf(active) ?? localeOf(navigator.language) ?? 'en'
42
+ }
43
+
44
+ export function apply(ctx: DshClientContext): void {
45
+ const nativeLayout = isBootEntryLoaded(UI_LAYOUT)
46
+ const reload = nativeLayout ? { reloading: false } : beginRootReload()
47
+ if (reload.reloading) return
48
+ const uiWorkspace = new Promise<DshClientContext['uiWorkspace']>(resolve => ctx.inject(['uiWorkspace'], inner => resolve(inner.uiWorkspace)))
49
+ const dshWorkspaces = createObservable<ReturnType<DshClientContext['workspaces']['list']['getSnapshot']>>({ items: [] })
50
+ // Register the root before the renderer boots; remote-backed workspace data follows later.
51
+ ctx.inject(['workspaces'], inner => inner.effect(() => {
52
+ const update = () => dshWorkspaces.set(inner.workspaces.list.getSnapshot())
53
+ update()
54
+ return inner.workspaces.list.subscribe(update)
55
+ }, 'presmium: workspace list'))
56
+ const workbench = new Workbench({ client: workspaceClient('api/presmium/', workspaceSubscriptions(ctx)), locale: workbenchLocale(ctx.locale.getLocale().active) })
57
+ workbench.client.openSpeechSettings = openSpeechSettings
58
+ ctx.effect(() => connectAuthoringClient(workbench), 'presmium: authoring client control')
59
+ ctx.effect(() => runtimeDiagnostics(workbench).start(), 'presmium: runtime diagnostics')
60
+ let showEditor = (layout: DshClientContext['layout']) => layout.selectPanel(FALLBACK_PANEL)
61
+ registerSourceLinks(ctx, workbench, layout => showEditor(layout))
62
+ ctx.effect(() => () => {
63
+ workbench.dispose()
64
+ if (!nativeLayout) saveRootReturnPoint(workbench)
65
+ }, 'presmium: workbench')
66
+ syncDocumentContext(ctx, workbench)
67
+ registerWorkbenchKeys(ctx, workbench)
68
+ registerAgentRequests(ctx, workbench, registerAssistantSetup(ctx, workbench))
69
+ ctx.effect(() => ctx.locale.subscribe(() => workbench.setLocale(workbenchLocale(ctx.locale.getLocale().active))), 'presmium: language')
70
+ ctx.effect(() => ctx.theme.overrideTokens(CLIENT_ID, DSH_THEME_TOKENS), 'presmium: 书卷 tokens')
71
+ ctx.effect(() => {
72
+ const sync = (snapshot: DshThemeSnapshot) => workbench.setDark(syncDarkClass(snapshot))
73
+ sync(ctx.theme.getTheme())
74
+ return ctx.on('theme/change', sync)
75
+ }, 'presmium: light and dark')
76
+
77
+ if (nativeLayout) {
78
+ registerFallbackLayout(ctx, workbench)
79
+ } else {
80
+ ctx.effect(() => {
81
+ const presenter = new ThemePresenter()
82
+ presenter.apply(ctx.theme.getTheme())
83
+ const off = ctx.on('theme/change', (snapshot: DshThemeSnapshot) => presenter.apply(snapshot))
84
+ return () => {
85
+ off()
86
+ presenter.dispose()
87
+ }
88
+ }, 'presmium: theme presenter')
89
+ ctx.effect(() => {
90
+ const handle = createLayoutStore()
91
+ const instance = handle.create()
92
+ const globalPanels = createObservable<readonly DshSlotEntry[]>([])
93
+ registerNavigationShell(ctx, workbench, instance, globalPanels, async () => {
94
+ const { root } = await workbench.client.readWorkbenchInfo()
95
+ const workspace = dshWorkspaces.getSnapshot().items.find(item => root && isSamePath(item.path, root))
96
+ if (workspace) (await uiWorkspace).startSession(workspace.workspaceId)
97
+ })
98
+ const store = { ...handle, create: () => instance }
99
+ const mainKeys = () => ctx.slots.entries('main').flatMap(entry => (entry.options.key === undefined ? [] : [entry.options.key]))
100
+ const layout = new LayoutController(instance.actions, id => mainKeys().includes(id), {
101
+ getSnapshot: () => instance.getSnapshot().panelInfo,
102
+ subscribe: listener => instance.subscribe(listener),
103
+ })
104
+ showEditor = () => layout.showDocument()
105
+ const openSettings = () => {
106
+ const triggers = document.querySelectorAll<HTMLElement>('[data-pmw-slot="sidebar"] button[aria-haspopup="dialog"]')
107
+ triggers[triggers.length - 1]?.click()
108
+ }
109
+ const disposePanelInfo = ctx.slots.provideRoot({ hooks: { panelInfo: layout.panelInfo } })
110
+ const disposeRoot = ctx.slots.register({ name: 'root', locale: 'common', children: {
111
+ ...ROOT_CHILDREN,
112
+ ...(isBootEntryLoaded(UI_SIDEBAR) ? {} : { 'sidebar.panellist': { kind: 'list', scope: 'root' } as const }),
113
+ }, store }, rootLayoutFor({
114
+ workbench,
115
+ openSettings,
116
+ openDshWorkspace: id => uiWorkspace.then(service => service.openWorkspace(id, () => layout.preserveNextConversation())),
117
+ dshWorkspaces,
118
+ globalPanels,
119
+ }))
120
+ const disposeService = ctx.reflect.provide('layout', layout)
121
+ const binding = { code: 'KeyB', modifiers: ['primary', 'alt'] }
122
+ const disposeShortcut = ctx.shortcuts.register({
123
+ id: 'sidebar.left.toggle',
124
+ label: () => CLIENT_MESSAGES[workbench.state.locale].toggleSide,
125
+ aliases: ['sidebar', 'toggle left sidebar'],
126
+ defaults: {
127
+ 'desktop:macos': { code: 'KeyB', modifiers: ['primary'] },
128
+ 'desktop:windows': { code: 'KeyB', modifiers: ['primary'] },
129
+ 'desktop:linux': { code: 'KeyB', modifiers: ['primary'] },
130
+ 'web:macos': binding,
131
+ 'web:windows': binding,
132
+ },
133
+ regions: ['page', 'editable'],
134
+ modals: [],
135
+ resolve: () => ({ status: 'handled', run: () => layout.toggleSidebar() }),
136
+ })
137
+ const retain = () => instance.actions.retainMainPanels(mainKeys())
138
+ const disposePanels = ctx.slots.subscribe('main', retain)
139
+ retain()
140
+ return () => {
141
+ disposeShortcut()
142
+ layout.dispose()
143
+ disposePanels()
144
+ disposeService()
145
+ disposeRoot()
146
+ disposePanelInfo()
147
+ }
148
+ }, 'presmium: the root layout and ctx.layout')
149
+ }
150
+ registerBrand(ctx, workbench)
151
+ registerMascot(ctx, workbench)
152
+ registerAuthorSettings(ctx)
153
+ registerSpeechSettings(ctx, workbench)
154
+ ctx.inject(['layout'], inner => inner.effect(() => connectLaunchTarget(workbench,
155
+ () => nativeLayout ? () => {} : restoreRootReturnPoint(workbench, reload.point),
156
+ () => showEditor(inner.layout)), 'presmium: launch target and root return point'))
157
+ }