@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.
- package/LICENSE +21 -0
- package/README.md +167 -0
- package/cordis.patch.yml +10 -0
- package/lib/client.js +8062 -0
- package/lib/client.js.map +1 -0
- package/lib/index.d.ts +396 -0
- package/lib/index.js +4166 -0
- package/package.json +101 -0
- package/src/analysis.ts +782 -0
- package/src/bridge.ts +841 -0
- package/src/client/analysis/AnalysisPanel.tsx +191 -0
- package/src/client/analysis/analysis.module.css +183 -0
- package/src/client/analysis-glue.ts +331 -0
- package/src/client/api.ts +380 -0
- package/src/client/board/Board.tsx +214 -0
- package/src/client/board/board.module.css +302 -0
- package/src/client/board/logic.ts +556 -0
- package/src/client/board/project-view-logic.ts +361 -0
- package/src/client/board/project-view.module.css +307 -0
- package/src/client/board/project-view.tsx +189 -0
- package/src/client/board/strings.ts +112 -0
- package/src/client/commands.ts +484 -0
- package/src/client/controller.ts +360 -0
- package/src/client/css-modules.d.ts +11 -0
- package/src/client/detail/SessionDetail.tsx +270 -0
- package/src/client/detail/detail.module.css +433 -0
- package/src/client/detail/logic.ts +779 -0
- package/src/client/detail/strings.ts +98 -0
- package/src/client/detail/transport.ts +175 -0
- package/src/client/detail-glue.ts +397 -0
- package/src/client/detail-view.module.css +79 -0
- package/src/client/detail-view.tsx +233 -0
- package/src/client/dsh-tools/LineageTree.tsx +210 -0
- package/src/client/dsh-tools/SearchPanel.tsx +169 -0
- package/src/client/dsh-tools/dsh-tools.module.css +374 -0
- package/src/client/dsh-tools/logic.ts +596 -0
- package/src/client/dsh-tools/strings.ts +90 -0
- package/src/client/index.ts +315 -0
- package/src/client/inject/InjectPanel.tsx +482 -0
- package/src/client/inject/inject.module.css +446 -0
- package/src/client/inject/logic.ts +516 -0
- package/src/client/inject/overlay.module.css +22 -0
- package/src/client/inject-glue.ts +171 -0
- package/src/client/locales/command.ts +48 -0
- package/src/client/locales/en.ts +385 -0
- package/src/client/locales/index.ts +123 -0
- package/src/client/locales/zh.ts +402 -0
- package/src/client/m3-transport.ts +151 -0
- package/src/client/mount.tsx +307 -0
- package/src/client/project-glue.ts +134 -0
- package/src/client/search-glue.ts +143 -0
- package/src/client/settings-card.module.css +359 -0
- package/src/client/settings-card.tsx +565 -0
- package/src/client/settings-glue.ts +130 -0
- package/src/client/sidebar-tab.tsx +494 -0
- package/src/client/sse.ts +366 -0
- package/src/client/widget.tsx +80 -0
- package/src/config.ts +193 -0
- package/src/dsh-inject.ts +240 -0
- package/src/fusion.ts +988 -0
- package/src/guard.ts +274 -0
- package/src/index.ts +950 -0
- package/src/inject-gateway.ts +574 -0
- package/src/routes.ts +1133 -0
- package/src/send-cli.ts +340 -0
- package/src/session-store.ts +184 -0
- package/src/skills-provider.ts +293 -0
- 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
|
+
}
|