@shendeguize/dsh-agent-sidecar 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 (68) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +167 -0
  3. package/cordis.patch.yml +10 -0
  4. package/lib/client.js +8062 -0
  5. package/lib/client.js.map +1 -0
  6. package/lib/index.d.ts +396 -0
  7. package/lib/index.js +4166 -0
  8. package/package.json +101 -0
  9. package/src/analysis.ts +782 -0
  10. package/src/bridge.ts +841 -0
  11. package/src/client/analysis/AnalysisPanel.tsx +191 -0
  12. package/src/client/analysis/analysis.module.css +183 -0
  13. package/src/client/analysis-glue.ts +331 -0
  14. package/src/client/api.ts +380 -0
  15. package/src/client/board/Board.tsx +214 -0
  16. package/src/client/board/board.module.css +302 -0
  17. package/src/client/board/logic.ts +556 -0
  18. package/src/client/board/project-view-logic.ts +361 -0
  19. package/src/client/board/project-view.module.css +307 -0
  20. package/src/client/board/project-view.tsx +189 -0
  21. package/src/client/board/strings.ts +112 -0
  22. package/src/client/commands.ts +484 -0
  23. package/src/client/controller.ts +360 -0
  24. package/src/client/css-modules.d.ts +11 -0
  25. package/src/client/detail/SessionDetail.tsx +270 -0
  26. package/src/client/detail/detail.module.css +433 -0
  27. package/src/client/detail/logic.ts +779 -0
  28. package/src/client/detail/strings.ts +98 -0
  29. package/src/client/detail/transport.ts +175 -0
  30. package/src/client/detail-glue.ts +397 -0
  31. package/src/client/detail-view.module.css +79 -0
  32. package/src/client/detail-view.tsx +233 -0
  33. package/src/client/dsh-tools/LineageTree.tsx +210 -0
  34. package/src/client/dsh-tools/SearchPanel.tsx +169 -0
  35. package/src/client/dsh-tools/dsh-tools.module.css +374 -0
  36. package/src/client/dsh-tools/logic.ts +596 -0
  37. package/src/client/dsh-tools/strings.ts +90 -0
  38. package/src/client/index.ts +315 -0
  39. package/src/client/inject/InjectPanel.tsx +482 -0
  40. package/src/client/inject/inject.module.css +446 -0
  41. package/src/client/inject/logic.ts +516 -0
  42. package/src/client/inject/overlay.module.css +22 -0
  43. package/src/client/inject-glue.ts +171 -0
  44. package/src/client/locales/command.ts +48 -0
  45. package/src/client/locales/en.ts +385 -0
  46. package/src/client/locales/index.ts +123 -0
  47. package/src/client/locales/zh.ts +402 -0
  48. package/src/client/m3-transport.ts +151 -0
  49. package/src/client/mount.tsx +307 -0
  50. package/src/client/project-glue.ts +134 -0
  51. package/src/client/search-glue.ts +143 -0
  52. package/src/client/settings-card.module.css +359 -0
  53. package/src/client/settings-card.tsx +565 -0
  54. package/src/client/settings-glue.ts +130 -0
  55. package/src/client/sidebar-tab.tsx +494 -0
  56. package/src/client/sse.ts +366 -0
  57. package/src/client/widget.tsx +80 -0
  58. package/src/config.ts +193 -0
  59. package/src/dsh-inject.ts +240 -0
  60. package/src/fusion.ts +988 -0
  61. package/src/guard.ts +274 -0
  62. package/src/index.ts +950 -0
  63. package/src/inject-gateway.ts +574 -0
  64. package/src/routes.ts +1133 -0
  65. package/src/send-cli.ts +340 -0
  66. package/src/session-store.ts +184 -0
  67. package/src/skills-provider.ts +293 -0
  68. package/src/supervisor.ts +463 -0
@@ -0,0 +1,315 @@
1
+ /**
2
+ * Agent Sidecar — browser client half (T2.4 full integration + S5/T4.9
3
+ * injection & command wiring).
4
+ *
5
+ * Mounts the completed modules into dsh Web on three slot seats plus one
6
+ * service contribution:
7
+ *
8
+ * - `conversation.view` (list slot, session scope): the cross-agent board
9
+ * as a "Sidecar" tab, order 30 — the official "multiple views per
10
+ * session" seat (registration shape follows the S0 skeleton and the
11
+ * dsh-project-kanban precedent). Selecting a session card opens the
12
+ * inject panel (T4.5) as a modal overlay: onPrepare/onExecute ride the
13
+ * action transport via inject-glue.ts, the capability bit comes from the
14
+ * state snapshot, and `inject.default-mode` is adopted late from the
15
+ * settings scope (seat 3) — 'queue', the schema default, until then;
16
+ * - `sidebar.footer.action` (list slot, root scope): the connection-dot
17
+ * footer widget (slot declared by dsh-client-ui-sidebar's footer rail;
18
+ * type merge imported from its installed d.ts);
19
+ * - `settings.plugin.item` (keyed slot, root scope): the settings card,
20
+ * keyed by the host-side settings namespace 'agent-sidecar' (the
21
+ * configurable-plugins tab dispatches one card per HOST-SERVED
22
+ * namespace — the host half registers it via `ctx.settings`);
23
+ * - `/sidecar` slash command (T4.6): registered lazily on the `commandUi`
24
+ * service via registerSidecarCommand — a composition without the
25
+ * slash-menu runtime simply never gains the command, and a duplicate
26
+ * registration (double apply) degrades to a logged no-op;
27
+ * - optional better-sidebar mini tab (T6.3): parked on the lazily-injected
28
+ * `betterSidebar` service via mountSidebarTab (optional peer, runtime
29
+ * duck-typed probe — never imported); not installed = silent skip.
30
+ *
31
+ * Lifecycle contract:
32
+ * - every resource rides `ctx.effect`: the data stream + visibilitychange
33
+ * listener, the injected `<style data-plugin>` tags (tsdown's CSS-module
34
+ * loader injects them at factory execution — once per materialization —
35
+ * so a keeper effect caches their text and restores them when a
36
+ * re-apply of the same materialized module follows an unload);
37
+ * - apply-guard idempotency: each mount checks the slot ledger for an
38
+ * entry with this plugin's id/key before registering, so a duplicate
39
+ * apply never double-registers (same cell + same priority would throw);
40
+ * - graceful degradation: every mount is individually try/catch-ed —
41
+ * a failing seat logs and is skipped, never taking the GUI down;
42
+ * - the settings card waits on the optional `settingsScope` service via
43
+ * `ctx.inject` (provided by dsh-client-ui-settings in the web app
44
+ * composition); board and widget mount without it.
45
+ */
46
+
47
+ import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'
48
+ // Type-only slot-contract merges into SlotMap (erased at runtime):
49
+ import type {} from '@deepseek-ai/dsh-client-ui-conversation/client'
50
+ import type {} from '@deepseek-ai/dsh-client-ui-sidebar/client'
51
+ import type {} from '@deepseek-ai/dsh-client-ui-settings-plugins/client'
52
+ import type { SettingsScope, SettingsScopeSpec } from '@deepseek-ai/dsh-client-runtime/client'
53
+ import { registerSidecarCommand } from './commands.ts'
54
+ import { PLUGIN_ID, SidecarController } from './controller.ts'
55
+ import { createInjectActions } from './inject-glue.ts'
56
+ import type { InjectMode } from './inject/logic.ts'
57
+ import { createBoardTab, createFooterWidget, createSettingsCardEntry } from './mount.tsx'
58
+ import { mountSidebarTab } from './sidebar-tab.tsx'
59
+ import { createDefaultIntegration, type SidecarUiIntegration } from './detail-view.tsx'
60
+ import type { SidecarConfigView } from './settings-glue.ts'
61
+
62
+ export const name = 'agent-sidecar'
63
+
64
+ /** The slot registry is the only hard dependency; settingsScope is lazy. */
65
+ export const inject = ['slots']
66
+
67
+ /** Entry id (list slots) and cell key (settings keyed slot) in one. */
68
+ const ENTRY_ID = 'agent-sidecar'
69
+
70
+ /** Host-side settings namespace (host half registers it via ctx.settings). */
71
+ const SETTINGS_NAMESPACE = 'agent-sidecar'
72
+
73
+ /**
74
+ * Structural face of the `settingsScope` binder service provided by
75
+ * dsh-client-ui-settings (not a published type surface for plugins in this
76
+ * dsh version; shape verified against harness
77
+ * packages/client/ui-settings/src/client/settings-scope.ts).
78
+ */
79
+ interface SettingsScopeBinderFace {
80
+ bind<T>(spec: SettingsScopeSpec<T>): SettingsScope<T>
81
+ }
82
+
83
+ /** The three slot seats this plugin occupies (all merged into SlotMap). */
84
+ type SidecarSlot = 'conversation.view' | 'sidebar.footer.action' | 'settings.plugin.item'
85
+
86
+ /**
87
+ * Apply-guard: whether the slot ledger already holds this plugin's entry
88
+ * (list slots match by `id`, the keyed settings slot by `key`; both use
89
+ * the same 'agent-sidecar' token). `entries()` answers [] for undeclared
90
+ * slots, and the check runs inside the deferred `slots.inject` callback —
91
+ * i.e. at actual registration time, when the ledger is authoritative.
92
+ */
93
+ function hasOwnEntry(ctx: ClientContext, slot: SidecarSlot): boolean {
94
+ return ctx.slots
95
+ .entries(slot)
96
+ .some((entry) => entry.options.id === ENTRY_ID || entry.options.key === SETTINGS_NAMESPACE)
97
+ }
98
+
99
+ // ---------------------------------------------------------------------------
100
+ // Style lifecycle.
101
+ // ---------------------------------------------------------------------------
102
+
103
+ /**
104
+ * Style text per data-plugin-css tag id, cached at module scope so it
105
+ * survives unload → re-apply cycles of one materialized module (the CSS
106
+ * factory body only runs once per materialization).
107
+ */
108
+ const styleTextCache = new Map<string, string>()
109
+
110
+ /**
111
+ * Keeper effect for the tsdown-injected `<style data-plugin>` tags: cache
112
+ * their text, restore any tag a previous unload removed, and remove all of
113
+ * this plugin's tags on dispose.
114
+ */
115
+ function keepStylesAlive(): () => void {
116
+ if (typeof document === 'undefined') return () => {}
117
+ const ownTags = `style[data-plugin=${JSON.stringify(PLUGIN_ID)}]`
118
+ for (const el of Array.from(document.querySelectorAll(ownTags))) {
119
+ const key = (el as HTMLStyleElement).dataset['pluginCss']
120
+ if (key !== undefined) styleTextCache.set(key, el.textContent ?? '')
121
+ }
122
+ for (const [key, cssText] of styleTextCache) {
123
+ if (document.querySelector(`style[data-plugin-css=${JSON.stringify(key)}]`) === null) {
124
+ const tag = document.createElement('style')
125
+ tag.dataset['plugin'] = PLUGIN_ID
126
+ tag.dataset['pluginCss'] = key
127
+ tag.textContent = cssText
128
+ document.head.appendChild(tag)
129
+ }
130
+ }
131
+ return () => {
132
+ for (const el of Array.from(document.querySelectorAll(ownTags))) el.remove()
133
+ }
134
+ }
135
+
136
+ // ---------------------------------------------------------------------------
137
+ // Plugin entry.
138
+ // ---------------------------------------------------------------------------
139
+
140
+ /**
141
+ * Mount the board tab, footer widget, and settings card, and run the data
142
+ * controller for as long as the plugin fiber lives.
143
+ * @param ctx - browser plugin context handed by the client loader.
144
+ */
145
+ export function apply(ctx: ClientContext): void {
146
+ const controller = new SidecarController()
147
+
148
+ // Inject integration (S5/T4.9): the detail view hosts the panel; a
149
+ // delivered execute pulls one fresh snapshot so the board reflects the
150
+ // injection promptly. The default mode is a mutable box because the
151
+ // settings scope (seat 3) resolves after the board mounts — the reader
152
+ // is late-bound and the box holds the schema default until then.
153
+ const injectPrefs: { defaultMode: InjectMode } = { defaultMode: 'queue' }
154
+
155
+ // Analysis capability (T5.10b): the state snapshot carries no analysis
156
+ // bit, so the UI gate mirrors the live `analysis.enabled` setting the
157
+ // same late-bound way (schema default false = fail-closed until the
158
+ // scope resolves); the server 403 gate stays authoritative regardless.
159
+ const analysisPrefs: { enabled: boolean } = { enabled: false }
160
+
161
+ // M3 integration handed to the board tab (design §5.1): detail routing,
162
+ // project view, dsh deep-query tools, and the analysis panel, each over
163
+ // its glue store on the real transports.
164
+ const uiIntegration: SidecarUiIntegration = createDefaultIntegration({
165
+ inject: {
166
+ actions: createInjectActions({
167
+ onDelivered: () => {
168
+ void controller.refresh()
169
+ },
170
+ }),
171
+ getDefaultMode: () => injectPrefs.defaultMode,
172
+ },
173
+ getAnalysisEnabled: () => analysisPrefs.enabled,
174
+ })
175
+
176
+ // Data feed + visibility resume (design §5.3: visibilitychange → pollNow).
177
+ ctx.effect(() => {
178
+ try {
179
+ controller.start()
180
+ } catch (err) {
181
+ // e.g. an environment without EventSource: the board renders its
182
+ // empty/unknown state instead of taking the plugin fiber down.
183
+ console.error('agent-sidecar: data stream start failed', err)
184
+ }
185
+ if (typeof document === 'undefined') {
186
+ return () => {
187
+ controller.stop()
188
+ }
189
+ }
190
+ const onVisibility = (): void => {
191
+ if (!document.hidden) controller.pollNow()
192
+ }
193
+ document.addEventListener('visibilitychange', onVisibility)
194
+ return () => {
195
+ document.removeEventListener('visibilitychange', onVisibility)
196
+ controller.stop()
197
+ }
198
+ }, 'agent-sidecar: client data feed')
199
+
200
+ ctx.effect(keepStylesAlive, 'agent-sidecar: injected styles')
201
+
202
+ // Seat 1: cross-agent board as the "Sidecar" conversation tab — since
203
+ // T5.10b the shell of the M3 information architecture: board/project
204
+ // switcher, card-click detail routing (timeline + inject + analysis +
205
+ // dsh lineage/search), all riding the integration seams above.
206
+ try {
207
+ const SidecarBoardTab = createBoardTab(controller, uiIntegration)
208
+ ctx.slots.inject('conversation.view', () => {
209
+ if (hasOwnEntry(ctx, 'conversation.view')) return () => {}
210
+ try {
211
+ return ctx.slots.register({
212
+ name: 'conversation.view',
213
+ id: ENTRY_ID,
214
+ order: 30,
215
+ label: 'Sidecar',
216
+ }, SidecarBoardTab)
217
+ } catch (err) {
218
+ console.error('agent-sidecar: board tab registration failed', err)
219
+ return () => {}
220
+ }
221
+ })
222
+ } catch (err) {
223
+ console.error('agent-sidecar: board tab mount failed', err)
224
+ }
225
+
226
+ // Seat 2: footer status widget.
227
+ try {
228
+ const SidecarFooterWidget = createFooterWidget(controller)
229
+ ctx.slots.inject('sidebar.footer.action', () => {
230
+ if (hasOwnEntry(ctx, 'sidebar.footer.action')) return () => {}
231
+ try {
232
+ return ctx.slots.register({
233
+ name: 'sidebar.footer.action',
234
+ id: ENTRY_ID,
235
+ order: 30,
236
+ }, SidecarFooterWidget)
237
+ } catch (err) {
238
+ console.error('agent-sidecar: footer widget registration failed', err)
239
+ return () => {}
240
+ }
241
+ })
242
+ } catch (err) {
243
+ console.error('agent-sidecar: footer widget mount failed', err)
244
+ }
245
+
246
+ // Seat 3: settings card, once the optional settingsScope service resolves.
247
+ try {
248
+ // The inject callback receives the base cordis Context; narrow once
249
+ // (slots/effect are inherited, settingsScope is the resolved service).
250
+ ctx.inject(['settingsScope'], (injected) => {
251
+ const sctx = injected as ClientContext & { settingsScope: SettingsScopeBinderFace }
252
+ try {
253
+ const scope = sctx.settingsScope.bind<SidecarConfigView>({
254
+ namespace: SETTINGS_NAMESPACE,
255
+ })
256
+
257
+ // ui.* settings defaults seed the board filters until the user
258
+ // touches them (localStorage value or filter gesture wins);
259
+ // inject.default-mode feeds the panel's late-bound mode reader;
260
+ // analysis.enabled feeds the analysis entry's late-bound gate.
261
+ const adoptDefaults = (): void => {
262
+ const value = scope.getSnapshot().value
263
+ if (value?.ui !== undefined) controller.adoptConfigDefaults(value.ui)
264
+ if (value?.inject !== undefined) injectPrefs.defaultMode = value.inject.defaultMode
265
+ if (value?.analysis !== undefined) analysisPrefs.enabled = value.analysis.enabled
266
+ }
267
+ sctx.effect(
268
+ () => scope.subscribe(adoptDefaults),
269
+ 'agent-sidecar: settings→filter defaults',
270
+ )
271
+ adoptDefaults()
272
+
273
+ const SidecarSettingsCardEntry = createSettingsCardEntry(controller, scope)
274
+ sctx.slots.inject('settings.plugin.item', () => {
275
+ if (hasOwnEntry(sctx, 'settings.plugin.item')) return () => {}
276
+ try {
277
+ return sctx.slots.register({
278
+ name: 'settings.plugin.item',
279
+ key: SETTINGS_NAMESPACE,
280
+ }, SidecarSettingsCardEntry)
281
+ } catch (err) {
282
+ console.error('agent-sidecar: settings card registration failed', err)
283
+ return () => {}
284
+ }
285
+ })
286
+ } catch (err) {
287
+ console.error('agent-sidecar: settings card mount failed', err)
288
+ }
289
+ })
290
+ } catch (err) {
291
+ console.error('agent-sidecar: settings scope injection failed', err)
292
+ }
293
+
294
+ // Seat 4: the `/sidecar` slash command, lazy on the commandUi service
295
+ // (T4.6). registerSidecarCommand catches its own registration failures
296
+ // (duplicate name on a double apply logs and no-ops); this try/catch
297
+ // keeps the degradation posture uniform with the slot seats. Disposal
298
+ // rides the injected fiber — unloading the plugin unregisters it.
299
+ try {
300
+ registerSidecarCommand(ctx)
301
+ } catch (err) {
302
+ console.error('agent-sidecar: /sidecar command mount failed', err)
303
+ }
304
+
305
+ // Seat 5 (optional, T6.3): the better-sidebar mini tab, parked on the
306
+ // lazily-injected `betterSidebar` service (optional peer; runtime
307
+ // duck-typed probe, never imported). Not installed → the fiber stays
308
+ // pending forever: silent skip, zero resources. A probe/registration
309
+ // failure degrades to a log and never touches the other seats.
310
+ try {
311
+ mountSidebarTab(ctx, controller)
312
+ } catch (err) {
313
+ console.error('agent-sidecar: better-sidebar tab mount failed', err)
314
+ }
315
+ }