@shendeguize/dsh-agent-sidecar 0.1.0 → 0.2.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/README.md +78 -11
- package/lib/client.js +4708 -2932
- package/lib/client.js.map +1 -1
- package/package.json +3 -1
- package/src/client/analysis/AnalysisPanel.tsx +19 -27
- package/src/client/analysis/analysis.module.css +36 -97
- package/src/client/board/Board.tsx +255 -48
- package/src/client/board/board.module.css +113 -92
- package/src/client/board/logic.ts +99 -21
- package/src/client/board/project-view-logic.ts +27 -34
- package/src/client/board/project-view.module.css +46 -83
- package/src/client/board/project-view.tsx +50 -8
- package/src/client/board/strings.ts +70 -85
- package/src/client/commands.ts +30 -15
- package/src/client/controller.ts +27 -7
- package/src/client/detail/SessionDetail.tsx +234 -37
- package/src/client/detail/detail.module.css +100 -153
- package/src/client/detail/logic.ts +220 -29
- package/src/client/detail/strings.ts +66 -77
- package/src/client/detail-glue.ts +33 -0
- package/src/client/detail-view.module.css +43 -35
- package/src/client/detail-view.tsx +138 -118
- package/src/client/dsh-tools/LineageTree.tsx +22 -13
- package/src/client/dsh-tools/SearchPanel.tsx +18 -11
- package/src/client/dsh-tools/dsh-tools.module.css +53 -140
- package/src/client/dsh-tools/strings.ts +42 -77
- package/src/client/index.ts +285 -124
- package/src/client/inject/InjectPanel.tsx +81 -61
- package/src/client/inject/inject.module.css +97 -197
- package/src/client/inject/logic.ts +38 -0
- package/src/client/lifecycle/handoff.ts +83 -0
- package/src/client/locales/en.ts +91 -4
- package/src/client/locales/host.ts +131 -0
- package/src/client/locales/index.ts +196 -8
- package/src/client/locales/react.ts +10 -0
- package/src/client/locales/view.ts +38 -0
- package/src/client/locales/zh.ts +200 -125
- package/src/client/mount.tsx +75 -41
- package/src/client/navigation/CenterOverlay.tsx +40 -0
- package/src/client/navigation/center-overlay.module.css +51 -0
- package/src/client/navigation/center.ts +45 -0
- package/src/client/navigation/modal-isolation.ts +152 -0
- package/src/client/navigation/modal-surface-anchor.ts +72 -0
- package/src/client/navigation/sidebar-entry.module.css +73 -0
- package/src/client/navigation/sidebar-entry.ts +309 -0
- package/src/client/primitives/StaticPill.tsx +21 -0
- package/src/client/settings-card.module.css +35 -119
- package/src/client/settings-card.tsx +31 -153
- package/src/client/settings-fields.tsx +131 -0
- package/src/client/sidebar/SidebarTab.tsx +156 -0
- package/src/client/sidebar/model.ts +65 -0
- package/src/client/sidebar/sidebar-tab.module.css +118 -0
- package/src/client/sidebar-tab.tsx +46 -320
- package/src/client/theme/agsc.module.css +31 -0
- package/src/client/theme/parts.ts +56 -0
- package/src/client/ui-integration.ts +86 -0
- package/src/client/widget.tsx +42 -16
- package/src/client/inject/overlay.module.css +0 -22
package/src/client/index.ts
CHANGED
|
@@ -1,42 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Agent Sidecar
|
|
3
|
-
* injection & command wiring).
|
|
2
|
+
* Agent Sidecar browser composition root.
|
|
4
3
|
*
|
|
5
|
-
*
|
|
6
|
-
* service contribution:
|
|
4
|
+
* Builds the controller and narrow UI ports, then mounts:
|
|
7
5
|
*
|
|
8
|
-
* - `
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
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;
|
|
6
|
+
* - `shell.overlay` (list slot, root scope): the first-class Agent Center,
|
|
7
|
+
* reachable with or without an active conversation;
|
|
8
|
+
* - `conversation.view` (list slot, session scope): a second entry to the
|
|
9
|
+
* same cross-agent board and its project/detail routes;
|
|
10
|
+
* - a self-healing Agent Center row in the host sidebar;
|
|
16
11
|
* - `sidebar.footer.action` (list slot, root scope): the connection-dot
|
|
17
|
-
* footer widget
|
|
18
|
-
* type merge imported from its installed d.ts);
|
|
12
|
+
* footer widget;
|
|
19
13
|
* - `settings.plugin.item` (keyed slot, root scope): the settings card,
|
|
20
|
-
* keyed by the host-side
|
|
21
|
-
*
|
|
22
|
-
*
|
|
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.
|
|
14
|
+
* keyed by the host-side namespace `agent-sidecar`;
|
|
15
|
+
* - the lazily registered `/sidecar` command and optional better-sidebar
|
|
16
|
+
* mini tab.
|
|
30
17
|
*
|
|
31
18
|
* Lifecycle contract:
|
|
32
19
|
* - every resource rides `ctx.effect`: the data stream + visibilitychange
|
|
33
20
|
* listener, the injected `<style data-plugin>` tags (tsdown's CSS-module
|
|
34
|
-
* loader
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
21
|
+
* loader resets a current-bundle manifest and injects them at factory
|
|
22
|
+
* execution — once per materialization — so a keeper effect prunes stale
|
|
23
|
+
* tags and restores authoritative manifest text when a re-apply of the
|
|
24
|
+
* same materialized module follows an unload);
|
|
25
|
+
* - the optional Host locale bridge rides a `locale`-injected child fiber;
|
|
26
|
+
* without that service the local table stays on zh, while every independent
|
|
27
|
+
* React root subscribes once and refreshes its complete descendant tree;
|
|
28
|
+
* - HMR handoff: each slot waits briefly while an overlapping old fiber owns
|
|
29
|
+
* this plugin's id/key, then registers the new component closure;
|
|
40
30
|
* - graceful degradation: every mount is individually try/catch-ed —
|
|
41
31
|
* a failing seat logs and is skipped, never taking the GUI down;
|
|
42
32
|
* - the settings card waits on the optional `settingsScope` service via
|
|
@@ -47,6 +37,7 @@
|
|
|
47
37
|
import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'
|
|
48
38
|
// Type-only slot-contract merges into SlotMap (erased at runtime):
|
|
49
39
|
import type {} from '@deepseek-ai/dsh-client-ui-conversation/client'
|
|
40
|
+
import type {} from '@deepseek-ai/dsh-client-ui-layout/client'
|
|
50
41
|
import type {} from '@deepseek-ai/dsh-client-ui-sidebar/client'
|
|
51
42
|
import type {} from '@deepseek-ai/dsh-client-ui-settings-plugins/client'
|
|
52
43
|
import type { SettingsScope, SettingsScopeSpec } from '@deepseek-ai/dsh-client-runtime/client'
|
|
@@ -54,19 +45,38 @@ import { registerSidecarCommand } from './commands.ts'
|
|
|
54
45
|
import { PLUGIN_ID, SidecarController } from './controller.ts'
|
|
55
46
|
import { createInjectActions } from './inject-glue.ts'
|
|
56
47
|
import type { InjectMode } from './inject/logic.ts'
|
|
57
|
-
import {
|
|
48
|
+
import {
|
|
49
|
+
acquireWithHandoff,
|
|
50
|
+
isRegistrationCollision,
|
|
51
|
+
} from './lifecycle/handoff.ts'
|
|
52
|
+
import type { HostLocalePort } from './locales/host.ts'
|
|
53
|
+
import { attachHostLocale, subscribeLocale, t } from './locales/index.ts'
|
|
54
|
+
import {
|
|
55
|
+
createBoardTab,
|
|
56
|
+
createCenterOverlay,
|
|
57
|
+
createFooterWidget,
|
|
58
|
+
createSettingsCardEntry,
|
|
59
|
+
} from './mount.tsx'
|
|
60
|
+
import { createCenterNavigation } from './navigation/center.ts'
|
|
61
|
+
import {
|
|
62
|
+
mountSidebarEntry,
|
|
63
|
+
type SidebarEntryCopyPort,
|
|
64
|
+
} from './navigation/sidebar-entry.ts'
|
|
58
65
|
import { mountSidebarTab } from './sidebar-tab.tsx'
|
|
59
|
-
import { createDefaultIntegration, type
|
|
66
|
+
import { createDefaultIntegration, type BoardUiPort } from './ui-integration.ts'
|
|
60
67
|
import type { SidecarConfigView } from './settings-glue.ts'
|
|
61
68
|
|
|
62
69
|
export const name = 'agent-sidecar'
|
|
63
70
|
|
|
64
|
-
/** The slot registry is the only hard dependency; settingsScope
|
|
71
|
+
/** The slot registry is the only hard dependency; settingsScope and locale are lazy. */
|
|
65
72
|
export const inject = ['slots']
|
|
66
73
|
|
|
67
74
|
/** Entry id (list slots) and cell key (settings keyed slot) in one. */
|
|
68
75
|
const ENTRY_ID = 'agent-sidecar'
|
|
69
76
|
|
|
77
|
+
/** Distinct list-seat id for the frame-wide Agent Center surface. */
|
|
78
|
+
const CENTER_ENTRY_ID = 'agent-sidecar-center'
|
|
79
|
+
|
|
70
80
|
/** Host-side settings namespace (host half registers it via ctx.settings). */
|
|
71
81
|
const SETTINGS_NAMESPACE = 'agent-sidecar'
|
|
72
82
|
|
|
@@ -80,56 +90,146 @@ interface SettingsScopeBinderFace {
|
|
|
80
90
|
bind<T>(spec: SettingsScopeSpec<T>): SettingsScope<T>
|
|
81
91
|
}
|
|
82
92
|
|
|
83
|
-
/**
|
|
84
|
-
|
|
93
|
+
/** Structural Cordis face keeps the unpublished locale package out of runtime imports. */
|
|
94
|
+
interface LocaleInjectContextFace {
|
|
95
|
+
inject(deps: string[], callback: (ctx: unknown) => void): unknown
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** The four slot seats this plugin occupies (all merged into SlotMap). */
|
|
99
|
+
type SidecarSlot =
|
|
100
|
+
| 'shell.overlay'
|
|
101
|
+
| 'conversation.view'
|
|
102
|
+
| 'sidebar.footer.action'
|
|
103
|
+
| 'settings.plugin.item'
|
|
85
104
|
|
|
86
105
|
/**
|
|
87
106
|
* Apply-guard: whether the slot ledger already holds this plugin's entry
|
|
88
|
-
* (list slots match
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
107
|
+
* (list slots match their seat-specific `id`, the keyed settings slot by
|
|
108
|
+
* namespace `key`). `entries()` answers [] for undeclared slots, and the
|
|
109
|
+
* check runs inside the deferred `slots.inject` callback — i.e. at actual
|
|
110
|
+
* registration time, when the ledger is authoritative.
|
|
92
111
|
*/
|
|
93
112
|
function hasOwnEntry(ctx: ClientContext, slot: SidecarSlot): boolean {
|
|
113
|
+
const entryId = slot === 'shell.overlay' ? CENTER_ENTRY_ID : ENTRY_ID
|
|
94
114
|
return ctx.slots
|
|
95
115
|
.entries(slot)
|
|
96
|
-
.some((entry) =>
|
|
116
|
+
.some((entry) =>
|
|
117
|
+
entry.options.id === entryId ||
|
|
118
|
+
(slot === 'settings.plugin.item' && entry.options.key === SETTINGS_NAMESPACE))
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Lease the optional Host locale service for this injected child fiber.
|
|
123
|
+
* The locale core arbitrates overlapping fibers that share one service.
|
|
124
|
+
*/
|
|
125
|
+
function mountHostLocale(ctx: ClientContext): void {
|
|
126
|
+
const mount = ctx as unknown as LocaleInjectContextFace
|
|
127
|
+
mount.inject(['locale'], (injected) => {
|
|
128
|
+
const lctx = injected as ClientContext & HostLocalePort
|
|
129
|
+
lctx.effect(
|
|
130
|
+
() => attachHostLocale(lctx),
|
|
131
|
+
'agent-sidecar: host locale bridge',
|
|
132
|
+
)
|
|
133
|
+
})
|
|
97
134
|
}
|
|
98
135
|
|
|
99
136
|
// ---------------------------------------------------------------------------
|
|
100
137
|
// Style lifecycle.
|
|
101
138
|
// ---------------------------------------------------------------------------
|
|
102
139
|
|
|
140
|
+
const STYLE_OWNER = Symbol.for('@shendeguize/dsh-agent-sidecar/style-owner')
|
|
141
|
+
const STYLE_MANIFEST = Symbol.for('@shendeguize/dsh-agent-sidecar/style-manifest')
|
|
142
|
+
const STYLE_GENERATION = Symbol.for('@shendeguize/dsh-agent-sidecar/style-generation')
|
|
143
|
+
|
|
144
|
+
type StyleGlobals = Record<PropertyKey, unknown>
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* A fallback only for a sequential re-apply of this exact materialization.
|
|
148
|
+
* A new intro installs a new generation object, so it can never inherit an
|
|
149
|
+
* earlier materialization's cached CSS.
|
|
150
|
+
*/
|
|
151
|
+
let cachedStyleManifest:
|
|
152
|
+
| { generation: object; styles: ReadonlyMap<string, string> }
|
|
153
|
+
| undefined
|
|
154
|
+
|
|
155
|
+
function setStyleOwner(tag: HTMLStyleElement, owner: object): void {
|
|
156
|
+
const sharedTag = tag as unknown as Record<PropertyKey, unknown>
|
|
157
|
+
sharedTag[STYLE_OWNER] = owner
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function isStyleOwner(tag: Element, owner: object): boolean {
|
|
161
|
+
return (tag as unknown as Record<PropertyKey, unknown>)[STYLE_OWNER] === owner
|
|
162
|
+
}
|
|
163
|
+
|
|
103
164
|
/**
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
* factory body only runs once per materialization).
|
|
165
|
+
* Freeze the current materialization's CSS before another bundle can reset
|
|
166
|
+
* it. A present Map is authoritative, including when it is empty.
|
|
107
167
|
*/
|
|
108
|
-
|
|
168
|
+
function snapshotStyleManifest(globals: StyleGlobals): ReadonlyMap<string, string> {
|
|
169
|
+
const generation = globals[STYLE_GENERATION]
|
|
170
|
+
const manifest = globals[STYLE_MANIFEST]
|
|
171
|
+
if (manifest instanceof Map) {
|
|
172
|
+
const styles = new Map<string, string>()
|
|
173
|
+
for (const [tagId, cssText] of manifest) {
|
|
174
|
+
if (typeof tagId === 'string' && typeof cssText === 'string') {
|
|
175
|
+
styles.set(tagId, cssText)
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (typeof generation === 'object' && generation !== null) {
|
|
179
|
+
cachedStyleManifest = { generation, styles }
|
|
180
|
+
}
|
|
181
|
+
return styles
|
|
182
|
+
}
|
|
183
|
+
if (
|
|
184
|
+
typeof generation === 'object' &&
|
|
185
|
+
generation !== null &&
|
|
186
|
+
cachedStyleManifest?.generation === generation
|
|
187
|
+
) {
|
|
188
|
+
return new Map(cachedStyleManifest.styles)
|
|
189
|
+
}
|
|
190
|
+
return new Map()
|
|
191
|
+
}
|
|
109
192
|
|
|
110
193
|
/**
|
|
111
|
-
* Keeper effect for the tsdown-injected `<style data-plugin>` tags
|
|
112
|
-
*
|
|
113
|
-
*
|
|
194
|
+
* Keeper effect for the tsdown-injected `<style data-plugin>` tags. Ownership
|
|
195
|
+
* lives on the DOM node through Symbol.for so the latest HMR fiber wins. The
|
|
196
|
+
* current bundle manifest is authoritative: plugin tags for CSS modules
|
|
197
|
+
* absent from it are stale and removed.
|
|
114
198
|
*/
|
|
115
|
-
function keepStylesAlive(
|
|
116
|
-
|
|
199
|
+
function keepStylesAlive(
|
|
200
|
+
documentRef: Document | undefined = typeof document === 'undefined' ? undefined : document,
|
|
201
|
+
globals: StyleGlobals = globalThis as unknown as StyleGlobals,
|
|
202
|
+
): () => void {
|
|
203
|
+
if (documentRef === undefined) return () => {}
|
|
204
|
+
const manifest = snapshotStyleManifest(globals)
|
|
205
|
+
const owner = {}
|
|
117
206
|
const ownTags = `style[data-plugin=${JSON.stringify(PLUGIN_ID)}]`
|
|
118
|
-
for (const el of Array.from(
|
|
119
|
-
const
|
|
120
|
-
|
|
207
|
+
for (const el of Array.from(documentRef.querySelectorAll(ownTags))) {
|
|
208
|
+
const tag = el as HTMLStyleElement
|
|
209
|
+
const key = tag.dataset['pluginCss']
|
|
210
|
+
const cssText = key === undefined ? undefined : manifest.get(key)
|
|
211
|
+
if (cssText === undefined) {
|
|
212
|
+
tag.remove()
|
|
213
|
+
continue
|
|
214
|
+
}
|
|
215
|
+
tag.textContent = cssText
|
|
216
|
+
setStyleOwner(tag, owner)
|
|
121
217
|
}
|
|
122
|
-
for (const [key, cssText] of
|
|
123
|
-
|
|
124
|
-
|
|
218
|
+
for (const [key, cssText] of manifest) {
|
|
219
|
+
const selector = `${ownTags}[data-plugin-css=${JSON.stringify(key)}]`
|
|
220
|
+
if (documentRef.querySelector(selector) === null) {
|
|
221
|
+
const tag = documentRef.createElement('style')
|
|
125
222
|
tag.dataset['plugin'] = PLUGIN_ID
|
|
126
223
|
tag.dataset['pluginCss'] = key
|
|
127
224
|
tag.textContent = cssText
|
|
128
|
-
|
|
225
|
+
setStyleOwner(tag, owner)
|
|
226
|
+
documentRef.head.appendChild(tag)
|
|
129
227
|
}
|
|
130
228
|
}
|
|
131
229
|
return () => {
|
|
132
|
-
for (const el of Array.from(
|
|
230
|
+
for (const el of Array.from(documentRef.querySelectorAll(ownTags))) {
|
|
231
|
+
if (isStyleOwner(el, owner)) el.remove()
|
|
232
|
+
}
|
|
133
233
|
}
|
|
134
234
|
}
|
|
135
235
|
|
|
@@ -144,24 +244,35 @@ function keepStylesAlive(): () => void {
|
|
|
144
244
|
*/
|
|
145
245
|
export function apply(ctx: ClientContext): void {
|
|
146
246
|
const controller = new SidecarController()
|
|
247
|
+
const navigation = createCenterNavigation()
|
|
248
|
+
const openAgentCenter = navigation.open
|
|
249
|
+
const sidebarEntryCopy: SidebarEntryCopyPort = {
|
|
250
|
+
get label() { return t('sidebar.centerEntryLabel') },
|
|
251
|
+
get accessibilityLabel() { return t('sidebar.centerEntryAria') },
|
|
252
|
+
subscribe: subscribeLocale,
|
|
253
|
+
}
|
|
147
254
|
|
|
148
|
-
//
|
|
149
|
-
//
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
255
|
+
// Optional service: a pending inject fiber allocates no resources and keeps
|
|
256
|
+
// the module-owned zh fallback. A bridge failure never blocks other seats.
|
|
257
|
+
try {
|
|
258
|
+
mountHostLocale(ctx)
|
|
259
|
+
} catch {
|
|
260
|
+
// Optional locale injection must not block the remaining client seats.
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// The detail view hosts injection. A delivered execute pulls one fresh
|
|
264
|
+
// snapshot so the board reflects it promptly. The default mode remains
|
|
265
|
+
// late-bound because settings resolve after the board mounts.
|
|
153
266
|
const injectPrefs: { defaultMode: InjectMode } = { defaultMode: 'queue' }
|
|
154
267
|
|
|
155
|
-
//
|
|
156
|
-
//
|
|
157
|
-
//
|
|
158
|
-
// scope resolves); the server 403 gate stays authoritative regardless.
|
|
268
|
+
// The state snapshot carries no analysis bit, so the UI mirrors the live
|
|
269
|
+
// `analysis.enabled` setting. False is fail-closed until settings resolve;
|
|
270
|
+
// the server's 403 gate remains authoritative.
|
|
159
271
|
const analysisPrefs: { enabled: boolean } = { enabled: false }
|
|
160
272
|
|
|
161
|
-
//
|
|
162
|
-
//
|
|
163
|
-
|
|
164
|
-
const uiIntegration: SidecarUiIntegration = createDefaultIntegration({
|
|
273
|
+
// Compose the production stores and detail capabilities behind the
|
|
274
|
+
// board's narrow port.
|
|
275
|
+
const uiIntegration: BoardUiPort = createDefaultIntegration({
|
|
165
276
|
inject: {
|
|
166
277
|
actions: createInjectActions({
|
|
167
278
|
onDelivered: () => {
|
|
@@ -199,51 +310,96 @@ export function apply(ctx: ClientContext): void {
|
|
|
199
310
|
|
|
200
311
|
ctx.effect(keepStylesAlive, 'agent-sidecar: injected styles')
|
|
201
312
|
|
|
202
|
-
// Seat 1:
|
|
203
|
-
//
|
|
204
|
-
//
|
|
205
|
-
|
|
313
|
+
// Seat 1: frame-wide Agent Center. Root scope keeps it reachable from an
|
|
314
|
+
// empty conversation, and the retained navigation snapshot makes early
|
|
315
|
+
// open requests visible as soon as the layout declares this official seat.
|
|
316
|
+
try {
|
|
317
|
+
const SidecarCenterOverlay = createCenterOverlay(controller, uiIntegration, navigation)
|
|
318
|
+
ctx.slots.inject('shell.overlay', () => acquireWithHandoff(
|
|
319
|
+
() => hasOwnEntry(ctx, 'shell.overlay')
|
|
320
|
+
? undefined
|
|
321
|
+
: ctx.slots.register({
|
|
322
|
+
name: 'shell.overlay',
|
|
323
|
+
id: CENTER_ENTRY_ID,
|
|
324
|
+
order: 30,
|
|
325
|
+
}, SidecarCenterOverlay),
|
|
326
|
+
{
|
|
327
|
+
isCollision: isRegistrationCollision,
|
|
328
|
+
onError: (error) => {
|
|
329
|
+
console.error('agent-sidecar: center overlay registration failed', error)
|
|
330
|
+
},
|
|
331
|
+
onTimeout: () => {
|
|
332
|
+
console.error('agent-sidecar: center overlay handoff timed out')
|
|
333
|
+
},
|
|
334
|
+
},
|
|
335
|
+
))
|
|
336
|
+
} catch (err) {
|
|
337
|
+
console.error('agent-sidecar: center overlay mount failed', err)
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Sidebar navigation shares the same narrow callback as the footer and
|
|
341
|
+
// `/sidecar`; this adapter owns only its host-side DOM placement.
|
|
342
|
+
try {
|
|
343
|
+
ctx.effect(
|
|
344
|
+
() => mountSidebarEntry(openAgentCenter, sidebarEntryCopy),
|
|
345
|
+
'agent-sidecar: sidebar entry',
|
|
346
|
+
)
|
|
347
|
+
} catch (err) {
|
|
348
|
+
console.error('agent-sidecar: sidebar entry mount failed', err)
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Seat 2: cross-agent board as the "Sidecar" conversation tab.
|
|
206
352
|
try {
|
|
207
353
|
const SidecarBoardTab = createBoardTab(controller, uiIntegration)
|
|
208
|
-
ctx.slots.inject('conversation.view', () =>
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
354
|
+
ctx.slots.inject('conversation.view', () => acquireWithHandoff(
|
|
355
|
+
() => hasOwnEntry(ctx, 'conversation.view')
|
|
356
|
+
? undefined
|
|
357
|
+
: ctx.slots.register({
|
|
358
|
+
name: 'conversation.view',
|
|
359
|
+
id: ENTRY_ID,
|
|
360
|
+
order: 30,
|
|
361
|
+
label: 'Sidecar',
|
|
362
|
+
}, SidecarBoardTab),
|
|
363
|
+
{
|
|
364
|
+
isCollision: isRegistrationCollision,
|
|
365
|
+
onError: (error) => {
|
|
366
|
+
console.error('agent-sidecar: board tab registration failed', error)
|
|
367
|
+
},
|
|
368
|
+
onTimeout: () => {
|
|
369
|
+
console.error('agent-sidecar: board tab handoff timed out')
|
|
370
|
+
},
|
|
371
|
+
},
|
|
372
|
+
))
|
|
222
373
|
} catch (err) {
|
|
223
374
|
console.error('agent-sidecar: board tab mount failed', err)
|
|
224
375
|
}
|
|
225
376
|
|
|
226
|
-
// Seat
|
|
377
|
+
// Seat 3: footer status widget.
|
|
227
378
|
try {
|
|
228
|
-
const SidecarFooterWidget = createFooterWidget(controller)
|
|
229
|
-
ctx.slots.inject('sidebar.footer.action', () =>
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
379
|
+
const SidecarFooterWidget = createFooterWidget(controller, openAgentCenter)
|
|
380
|
+
ctx.slots.inject('sidebar.footer.action', () => acquireWithHandoff(
|
|
381
|
+
() => hasOwnEntry(ctx, 'sidebar.footer.action')
|
|
382
|
+
? undefined
|
|
383
|
+
: ctx.slots.register({
|
|
384
|
+
name: 'sidebar.footer.action',
|
|
385
|
+
id: ENTRY_ID,
|
|
386
|
+
order: 30,
|
|
387
|
+
}, SidecarFooterWidget),
|
|
388
|
+
{
|
|
389
|
+
isCollision: isRegistrationCollision,
|
|
390
|
+
onError: (error) => {
|
|
391
|
+
console.error('agent-sidecar: footer widget registration failed', error)
|
|
392
|
+
},
|
|
393
|
+
onTimeout: () => {
|
|
394
|
+
console.error('agent-sidecar: footer widget handoff timed out')
|
|
395
|
+
},
|
|
396
|
+
},
|
|
397
|
+
))
|
|
242
398
|
} catch (err) {
|
|
243
399
|
console.error('agent-sidecar: footer widget mount failed', err)
|
|
244
400
|
}
|
|
245
401
|
|
|
246
|
-
// Seat
|
|
402
|
+
// Seat 4: settings card, once the optional settingsScope service resolves.
|
|
247
403
|
try {
|
|
248
404
|
// The inject callback receives the base cordis Context; narrow once
|
|
249
405
|
// (slots/effect are inherited, settingsScope is the resolved service).
|
|
@@ -271,18 +427,23 @@ export function apply(ctx: ClientContext): void {
|
|
|
271
427
|
adoptDefaults()
|
|
272
428
|
|
|
273
429
|
const SidecarSettingsCardEntry = createSettingsCardEntry(controller, scope)
|
|
274
|
-
sctx.slots.inject('settings.plugin.item', () =>
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
430
|
+
sctx.slots.inject('settings.plugin.item', () => acquireWithHandoff(
|
|
431
|
+
() => hasOwnEntry(sctx, 'settings.plugin.item')
|
|
432
|
+
? undefined
|
|
433
|
+
: sctx.slots.register({
|
|
434
|
+
name: 'settings.plugin.item',
|
|
435
|
+
key: SETTINGS_NAMESPACE,
|
|
436
|
+
}, SidecarSettingsCardEntry),
|
|
437
|
+
{
|
|
438
|
+
isCollision: isRegistrationCollision,
|
|
439
|
+
onError: (error) => {
|
|
440
|
+
console.error('agent-sidecar: settings card registration failed', error)
|
|
441
|
+
},
|
|
442
|
+
onTimeout: () => {
|
|
443
|
+
console.error('agent-sidecar: settings card handoff timed out')
|
|
444
|
+
},
|
|
445
|
+
},
|
|
446
|
+
))
|
|
286
447
|
} catch (err) {
|
|
287
448
|
console.error('agent-sidecar: settings card mount failed', err)
|
|
288
449
|
}
|
|
@@ -291,19 +452,19 @@ export function apply(ctx: ClientContext): void {
|
|
|
291
452
|
console.error('agent-sidecar: settings scope injection failed', err)
|
|
292
453
|
}
|
|
293
454
|
|
|
294
|
-
// Seat
|
|
295
|
-
//
|
|
296
|
-
//
|
|
297
|
-
//
|
|
298
|
-
//
|
|
455
|
+
// Seat 5: the `/sidecar` slash command, lazy on the commandUi service.
|
|
456
|
+
// registerSidecarCommand catches its own registration failures
|
|
457
|
+
// and owns the bounded overlap handoff; this try/catch keeps the
|
|
458
|
+
// degradation posture uniform with the slot seats. Disposal rides the
|
|
459
|
+
// injected fiber — unloading the plugin unregisters it.
|
|
299
460
|
try {
|
|
300
|
-
registerSidecarCommand(ctx)
|
|
461
|
+
registerSidecarCommand(ctx, { openCenter: openAgentCenter })
|
|
301
462
|
} catch (err) {
|
|
302
463
|
console.error('agent-sidecar: /sidecar command mount failed', err)
|
|
303
464
|
}
|
|
304
465
|
|
|
305
|
-
// Seat
|
|
306
|
-
// lazily
|
|
466
|
+
// Seat 6 (optional): the better-sidebar mini tab, parked on the
|
|
467
|
+
// lazily injected `betterSidebar` service (optional peer; runtime
|
|
307
468
|
// duck-typed probe, never imported). Not installed → the fiber stays
|
|
308
469
|
// pending forever: silent skip, zero resources. A probe/registration
|
|
309
470
|
// failure degrades to a log and never touches the other seats.
|