@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
|
@@ -1,38 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
* Inject panel (design §5.1 view 3): the two-phase confirmation UI for
|
|
3
|
-
* message injection. Fully controlled and presentational — the component
|
|
4
|
-
* does NO data fetching; the integration layer supplies `onPrepare` /
|
|
5
|
-
* `onExecute` callbacks that speak to `POST <prefix>/action` and resolve
|
|
6
|
-
* with either the wire success shape or the data layer's normalized
|
|
7
|
-
* ApiError (matched structurally, never imported).
|
|
8
|
-
*
|
|
9
|
-
* Three zones, driven by the pure reducer in ./logic.ts:
|
|
10
|
-
*
|
|
11
|
-
* 1. editor — message textarea + live UTF-8 byte counter, queue/steer mode
|
|
12
|
-
* radios, target row, cursor-cli process-list warning (§4.d/S7) and the
|
|
13
|
-
* audit-fingerprint note (§5.3);
|
|
14
|
-
* 2. confirm — the prepare plan (live target-status snapshot + message
|
|
15
|
-
* digest), the 60s confirmToken countdown, and the deliberately
|
|
16
|
-
* restrained danger button;
|
|
17
|
-
* 3. result — delivered / failed (re-prepare offered) / unknown (terminal:
|
|
18
|
-
* the reducer itself refuses a reset, so no retry affordance can exist —
|
|
19
|
-
* S6 — and the copy points at the session to verify).
|
|
20
|
-
*
|
|
21
|
-
* `capability.inject === false` disables the whole panel with the
|
|
22
|
-
* "enable injection in Settings" note.
|
|
23
|
-
*/
|
|
24
|
-
|
|
1
|
+
import { Button, Pill } from '@deepseek-ai/dsh-client-ui-primitives'
|
|
25
2
|
import { useEffect, useId, useReducer, useState } from 'react'
|
|
26
3
|
import type { ReactNode } from 'react'
|
|
27
4
|
import { t as defaultT } from '../locales/index.ts'
|
|
28
5
|
import type { SidecarLocaleKey } from '../locales/index.ts'
|
|
6
|
+
import { surfaceProps } from '../theme/parts.ts'
|
|
29
7
|
import css from './inject.module.css'
|
|
30
8
|
import {
|
|
31
9
|
byteUsage,
|
|
32
10
|
classifyExecuteResponse,
|
|
11
|
+
classifyPanelKey,
|
|
33
12
|
classifyPrepareResponse,
|
|
34
13
|
deriveEditorGate,
|
|
35
14
|
errorCopy,
|
|
15
|
+
isDeliveredResult,
|
|
36
16
|
initialPanelState,
|
|
37
17
|
messageInvalidCopy,
|
|
38
18
|
MODE_COPY,
|
|
@@ -82,6 +62,12 @@ export interface InjectPanelProps {
|
|
|
82
62
|
onExecute(req: PanelExecuteRequest): Promise<InjectResultView | ApiErrorLike>
|
|
83
63
|
/** Close/dismiss the panel (the hosting modal owns the lifecycle). */
|
|
84
64
|
onClose?(): void
|
|
65
|
+
/**
|
|
66
|
+
* UX-05 observation loop: when given, the delivered result page offers a
|
|
67
|
+
* 「开启监听观察反应」 button (the host typically enables listen mode on
|
|
68
|
+
* the detail timeline and closes the panel).
|
|
69
|
+
*/
|
|
70
|
+
onObserve?(): void
|
|
85
71
|
/** Clock injection for the token countdown; defaults to Date.now. */
|
|
86
72
|
nowMs?: () => number
|
|
87
73
|
/** Locale seat override; defaults to the module-local table. */
|
|
@@ -95,10 +81,6 @@ function renderCopy(t: InjectTranslate, copy: CopyRef): string {
|
|
|
95
81
|
return t(copy.key, copy.params)
|
|
96
82
|
}
|
|
97
83
|
|
|
98
|
-
// ---------------------------------------------------------------------------
|
|
99
|
-
// Zone building blocks.
|
|
100
|
-
// ---------------------------------------------------------------------------
|
|
101
|
-
|
|
102
84
|
interface WarningsProps {
|
|
103
85
|
t: InjectTranslate
|
|
104
86
|
agent: string | null
|
|
@@ -133,7 +115,7 @@ function PlanBox(props: PlanBoxProps): ReactNode {
|
|
|
133
115
|
<div className={css['planRow']}>
|
|
134
116
|
<span className={css['planKey']}>{t('inject.planTargetLabel')}</span>
|
|
135
117
|
<span className={css['planValue']}>
|
|
136
|
-
<
|
|
118
|
+
<Pill className={css['agentTag']}>{plan.target.agent}</Pill>
|
|
137
119
|
<span className={css['planTitle']} title={plan.target.sessionId}>{targetName}</span>
|
|
138
120
|
</span>
|
|
139
121
|
</div>
|
|
@@ -157,15 +139,6 @@ function PlanBox(props: PlanBoxProps): ReactNode {
|
|
|
157
139
|
)
|
|
158
140
|
}
|
|
159
141
|
|
|
160
|
-
// ---------------------------------------------------------------------------
|
|
161
|
-
// Panel.
|
|
162
|
-
// ---------------------------------------------------------------------------
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Render the two-phase inject panel.
|
|
166
|
-
* @param props - capability, target, and the integration callbacks.
|
|
167
|
-
* @returns the panel.
|
|
168
|
-
*/
|
|
169
142
|
export function InjectPanel(props: InjectPanelProps): ReactNode {
|
|
170
143
|
const t = props.t ?? defaultT
|
|
171
144
|
const now = props.nowMs ?? Date.now
|
|
@@ -240,18 +213,44 @@ export function InjectPanel(props: InjectPanelProps): ReactNode {
|
|
|
240
213
|
dispatch(event)
|
|
241
214
|
}
|
|
242
215
|
|
|
216
|
+
// Keyboard affordances (UX-10): Esc closes in every zone; Cmd/Ctrl+Enter
|
|
217
|
+
// triggers PREPARE only (classifyPanelKey refuses everything else, so no
|
|
218
|
+
// keyboard path can shortcut the explicit confirm click).
|
|
219
|
+
const handleKeyDown = (event: {
|
|
220
|
+
key: string
|
|
221
|
+
metaKey: boolean
|
|
222
|
+
ctrlKey: boolean
|
|
223
|
+
stopPropagation(): void
|
|
224
|
+
preventDefault(): void
|
|
225
|
+
}): void => {
|
|
226
|
+
const intent = classifyPanelKey({
|
|
227
|
+
key: event.key,
|
|
228
|
+
metaKey: event.metaKey,
|
|
229
|
+
ctrlKey: event.ctrlKey,
|
|
230
|
+
phase: state.phase,
|
|
231
|
+
canPrepare: gate.canPrepare,
|
|
232
|
+
})
|
|
233
|
+
if (intent === 'close' && props.onClose !== undefined) {
|
|
234
|
+
event.stopPropagation()
|
|
235
|
+
props.onClose()
|
|
236
|
+
} else if (intent === 'prepare') {
|
|
237
|
+
event.preventDefault()
|
|
238
|
+
void handlePrepare()
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const panelSurface = surfaceProps('inject-panel', css['panel'])
|
|
243
243
|
const closeButton = props.onClose !== undefined
|
|
244
244
|
? (
|
|
245
|
-
<
|
|
245
|
+
<Button type="button" size="sm" variant="outline" onClick={props.onClose}>
|
|
246
246
|
{t('inject.close')}
|
|
247
|
-
</
|
|
247
|
+
</Button>
|
|
248
248
|
)
|
|
249
249
|
: null
|
|
250
250
|
|
|
251
|
-
// ── whole-panel gate: injection capability off ─────────────────────────
|
|
252
251
|
if (!props.capability.inject) {
|
|
253
252
|
return (
|
|
254
|
-
<section
|
|
253
|
+
<section {...panelSurface} aria-label={t('inject.title')} onKeyDown={handleKeyDown}>
|
|
255
254
|
<header className={css['header']}>
|
|
256
255
|
<h3 className={css['title']}>{t('inject.title')}</h3>
|
|
257
256
|
</header>
|
|
@@ -265,7 +264,6 @@ export function InjectPanel(props: InjectPanelProps): ReactNode {
|
|
|
265
264
|
)
|
|
266
265
|
}
|
|
267
266
|
|
|
268
|
-
// ── zone 3: result ──────────────────────────────────────────────────────
|
|
269
267
|
if (state.phase === 'result') {
|
|
270
268
|
const { result } = state
|
|
271
269
|
const actions = resultActions(result.outcome)
|
|
@@ -275,7 +273,7 @@ export function InjectPanel(props: InjectPanelProps): ReactNode {
|
|
|
275
273
|
? css['resultFail']
|
|
276
274
|
: css['resultUnknown']
|
|
277
275
|
return (
|
|
278
|
-
<section
|
|
276
|
+
<section {...panelSurface} aria-label={t('inject.title')} onKeyDown={handleKeyDown}>
|
|
279
277
|
<header className={css['header']}>
|
|
280
278
|
<h3 className={css['title']}>{t('inject.title')}</h3>
|
|
281
279
|
</header>
|
|
@@ -293,26 +291,41 @@ export function InjectPanel(props: InjectPanelProps): ReactNode {
|
|
|
293
291
|
: null}
|
|
294
292
|
<div className={css['footer']}>
|
|
295
293
|
{closeButton}
|
|
294
|
+
{isDeliveredResult(result) && props.onObserve !== undefined
|
|
295
|
+
? (
|
|
296
|
+
<Button
|
|
297
|
+
type="button"
|
|
298
|
+
size="sm"
|
|
299
|
+
variant="primary"
|
|
300
|
+
onClick={props.onObserve}
|
|
301
|
+
data-testid="agent-sidecar-inject-observe"
|
|
302
|
+
>
|
|
303
|
+
{t('inject.observeListen')}
|
|
304
|
+
</Button>
|
|
305
|
+
)
|
|
306
|
+
: null}
|
|
296
307
|
{actions.canReprepare
|
|
297
308
|
? (
|
|
298
|
-
<
|
|
309
|
+
<Button
|
|
299
310
|
type="button"
|
|
300
|
-
|
|
311
|
+
size="sm"
|
|
312
|
+
variant="primary"
|
|
301
313
|
onClick={() => { dispatch({ type: 'RESET' }) }}
|
|
302
314
|
>
|
|
303
315
|
{t('inject.reprepare')}
|
|
304
|
-
</
|
|
316
|
+
</Button>
|
|
305
317
|
)
|
|
306
318
|
: null}
|
|
307
319
|
{result.outcome === 'delivered' && props.onClose === undefined
|
|
308
320
|
? (
|
|
309
|
-
<
|
|
321
|
+
<Button
|
|
310
322
|
type="button"
|
|
311
|
-
|
|
323
|
+
size="sm"
|
|
324
|
+
variant="primary"
|
|
312
325
|
onClick={() => { dispatch({ type: 'RESET' }) }}
|
|
313
326
|
>
|
|
314
327
|
{t('inject.done')}
|
|
315
|
-
</
|
|
328
|
+
</Button>
|
|
316
329
|
)
|
|
317
330
|
: null}
|
|
318
331
|
</div>
|
|
@@ -321,14 +334,17 @@ export function InjectPanel(props: InjectPanelProps): ReactNode {
|
|
|
321
334
|
)
|
|
322
335
|
}
|
|
323
336
|
|
|
324
|
-
// ── zone 2: confirm / executing ─────────────────────────────────────────
|
|
325
337
|
if (state.phase === 'confirm' || state.phase === 'executing') {
|
|
326
338
|
const executing = state.phase === 'executing'
|
|
327
339
|
const countdown = state.phase === 'confirm'
|
|
328
340
|
? tokenCountdown(state.expiresAt, clock)
|
|
329
341
|
: null
|
|
330
342
|
return (
|
|
331
|
-
<section
|
|
343
|
+
<section
|
|
344
|
+
{...panelSurface}
|
|
345
|
+
aria-label={t('inject.confirmTitle')}
|
|
346
|
+
onKeyDown={handleKeyDown}
|
|
347
|
+
>
|
|
332
348
|
<header className={css['header']}>
|
|
333
349
|
<h3 className={css['title']}>{t('inject.confirmTitle')}</h3>
|
|
334
350
|
</header>
|
|
@@ -343,14 +359,15 @@ export function InjectPanel(props: InjectPanelProps): ReactNode {
|
|
|
343
359
|
)
|
|
344
360
|
: null}
|
|
345
361
|
<div className={css['footer']}>
|
|
346
|
-
<
|
|
362
|
+
<Button
|
|
347
363
|
type="button"
|
|
348
|
-
|
|
364
|
+
size="sm"
|
|
365
|
+
variant="outline"
|
|
349
366
|
disabled={executing}
|
|
350
367
|
onClick={() => { dispatch({ type: 'CANCEL' }) }}
|
|
351
368
|
>
|
|
352
369
|
{t('inject.cancel')}
|
|
353
|
-
</
|
|
370
|
+
</Button>
|
|
354
371
|
<button
|
|
355
372
|
type="button"
|
|
356
373
|
className={css['btnDanger']}
|
|
@@ -365,7 +382,6 @@ export function InjectPanel(props: InjectPanelProps): ReactNode {
|
|
|
365
382
|
)
|
|
366
383
|
}
|
|
367
384
|
|
|
368
|
-
// ── zone 1: editor (idle / preparing) ───────────────────────────────────
|
|
369
385
|
const preparing = state.phase === 'preparing'
|
|
370
386
|
const canEdit = props.target !== null && !preparing
|
|
371
387
|
const usage = byteUsage(validation.bytes)
|
|
@@ -373,7 +389,7 @@ export function InjectPanel(props: InjectPanelProps): ReactNode {
|
|
|
373
389
|
const showInvalid = !validation.ok && validation.code !== 'empty'
|
|
374
390
|
|
|
375
391
|
return (
|
|
376
|
-
<section
|
|
392
|
+
<section {...panelSurface} aria-label={t('inject.title')} onKeyDown={handleKeyDown}>
|
|
377
393
|
<header className={css['header']}>
|
|
378
394
|
<h3 className={css['title']}>{t('inject.title')}</h3>
|
|
379
395
|
</header>
|
|
@@ -397,7 +413,7 @@ export function InjectPanel(props: InjectPanelProps): ReactNode {
|
|
|
397
413
|
{props.target !== null
|
|
398
414
|
? (
|
|
399
415
|
<span className={css['planValue']}>
|
|
400
|
-
<
|
|
416
|
+
<Pill className={css['agentTag']}>{props.target.agent}</Pill>
|
|
401
417
|
<span className={css['planTitle']} title={props.target.sessionId}>
|
|
402
418
|
{props.target.title !== undefined && props.target.title !== ''
|
|
403
419
|
? props.target.title
|
|
@@ -419,6 +435,9 @@ export function InjectPanel(props: InjectPanelProps): ReactNode {
|
|
|
419
435
|
placeholder={t('inject.messagePlaceholder')}
|
|
420
436
|
disabled={!canEdit}
|
|
421
437
|
rows={5}
|
|
438
|
+
// eslint-disable-next-line jsx-a11y/no-autofocus -- the panel is
|
|
439
|
+
// a modal whose single task starts in this field (UX-10).
|
|
440
|
+
autoFocus
|
|
422
441
|
onChange={(event) => { setDraft(event.target.value) }}
|
|
423
442
|
/>
|
|
424
443
|
<div className={css['byteRow']}>
|
|
@@ -467,14 +486,15 @@ export function InjectPanel(props: InjectPanelProps): ReactNode {
|
|
|
467
486
|
|
|
468
487
|
<div className={css['footer']}>
|
|
469
488
|
{closeButton}
|
|
470
|
-
<
|
|
489
|
+
<Button
|
|
471
490
|
type="button"
|
|
472
|
-
|
|
491
|
+
size="sm"
|
|
492
|
+
variant="primary"
|
|
473
493
|
disabled={!gate.canPrepare}
|
|
474
494
|
onClick={() => { void handlePrepare() }}
|
|
475
495
|
>
|
|
476
496
|
{t(preparing ? 'inject.preparing' : 'inject.prepare')}
|
|
477
|
-
</
|
|
497
|
+
</Button>
|
|
478
498
|
</div>
|
|
479
499
|
</div>
|
|
480
500
|
</section>
|