@shendeguize/dsh-agent-sidecar 0.1.1 → 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 +8659 -7589
- 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 +115 -61
- package/src/client/board/board.module.css +72 -151
- package/src/client/board/project-view-logic.ts +21 -34
- package/src/client/board/project-view.module.css +41 -96
- package/src/client/board/project-view.tsx +29 -13
- package/src/client/board/strings.ts +68 -107
- package/src/client/commands.ts +30 -15
- package/src/client/detail/SessionDetail.tsx +92 -58
- package/src/client/detail/detail.module.css +62 -218
- package/src/client/detail/strings.ts +64 -88
- package/src/client/detail-view.module.css +30 -55
- package/src/client/detail-view.tsx +80 -108
- 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 +50 -139
- package/src/client/dsh-tools/strings.ts +42 -77
- package/src/client/index.ts +285 -124
- package/src/client/inject/InjectPanel.tsx +31 -64
- package/src/client/inject/inject.module.css +97 -198
- package/src/client/lifecycle/handoff.ts +83 -0
- package/src/client/locales/en.ts +74 -2
- 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 +194 -134
- package/src/client/mount.tsx +72 -38
- 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 +9 -8
- package/src/client/inject/overlay.module.css +0 -22
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shendeguize/dsh-agent-sidecar",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Agent Sidecar as a native DSH plugin: cross-agent monitoring, injection and bypass analysis / 跨 agent 监控、注入与旁路分析",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -86,6 +86,8 @@
|
|
|
86
86
|
"@deepseek-ai/cordis": "4.0.1",
|
|
87
87
|
"@deepseek-ai/dsh-client-runtime": "0.1.1-rc.2",
|
|
88
88
|
"@deepseek-ai/dsh-client-ui-conversation": "0.1.1-rc.2",
|
|
89
|
+
"@deepseek-ai/dsh-client-ui-layout": "0.1.1-rc.2",
|
|
90
|
+
"@deepseek-ai/dsh-client-ui-primitives": "0.1.1-rc.2",
|
|
89
91
|
"@deepseek-ai/dsh-client-ui-settings-plugins": "0.1.1-rc.2",
|
|
90
92
|
"@deepseek-ai/dsh-client-ui-sidebar": "0.1.1-rc.2",
|
|
91
93
|
"@deepseek-ai/dsh-client-ui-slots": "0.1.1-rc.2",
|
|
@@ -1,21 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
* AI bypass-analysis panel (T5.10b, design §4.e.3 / §5.1 view 2): fully
|
|
3
|
-
* controlled presentation over an {@link AnalysisGlueState} — the owner
|
|
4
|
-
* (detail view) holds the AnalysisStore and passes state + intents down.
|
|
5
|
-
*
|
|
6
|
-
* Honesty posture (§5.3): the engine disclaimer (fallback copy when a
|
|
7
|
-
* settle never happened) renders with every result, truncation is called
|
|
8
|
-
* out per exchange, terminal errors carry the vocabulary code, and the
|
|
9
|
-
* capability-off state explains where to enable analysis instead of
|
|
10
|
-
* hiding the entry. Strings ride the main locale table (`analysis.*`).
|
|
11
|
-
*
|
|
12
|
-
* @module
|
|
13
|
-
*/
|
|
14
|
-
|
|
1
|
+
import { Button, Input } from '@deepseek-ai/dsh-client-ui-primitives'
|
|
15
2
|
import { useState } from 'react'
|
|
16
3
|
import type { ReactElement } from 'react'
|
|
17
4
|
import { t } from '../locales/index.ts'
|
|
18
5
|
import type { AnalysisExchange, AnalysisGlueState } from '../analysis-glue.ts'
|
|
6
|
+
import { surfaceProps } from '../theme/parts.ts'
|
|
19
7
|
import css from './analysis.module.css'
|
|
20
8
|
|
|
21
9
|
export interface AnalysisPanelProps {
|
|
@@ -87,24 +75,25 @@ export function AnalysisPanel(props: AnalysisPanelProps): ReactElement {
|
|
|
87
75
|
}
|
|
88
76
|
|
|
89
77
|
return (
|
|
90
|
-
<section
|
|
78
|
+
<section {...surfaceProps('analysis-panel', css['panel'])} data-testid="agent-sidecar-analysis">
|
|
91
79
|
<div className={css['head']}>
|
|
92
80
|
<span className={css['title']}>{t('analysis.title')}</span>
|
|
93
81
|
<span className={css['spacer']} />
|
|
94
82
|
{conversationLive && (
|
|
95
|
-
<
|
|
83
|
+
<Button
|
|
96
84
|
type="button"
|
|
97
|
-
|
|
85
|
+
size="sm"
|
|
86
|
+
variant="ghost"
|
|
98
87
|
disabled={!enabled}
|
|
99
88
|
onClick={props.onStop}
|
|
100
89
|
data-testid="agent-sidecar-analysis-stop"
|
|
101
90
|
>
|
|
102
91
|
{t('analysis.stop')}
|
|
103
|
-
</
|
|
92
|
+
</Button>
|
|
104
93
|
)}
|
|
105
|
-
<
|
|
94
|
+
<Button type="button" size="sm" variant="ghost" onClick={props.onClose}>
|
|
106
95
|
{t('analysis.close')}
|
|
107
|
-
</
|
|
96
|
+
</Button>
|
|
108
97
|
</div>
|
|
109
98
|
|
|
110
99
|
{!enabled && (
|
|
@@ -136,14 +125,16 @@ export function AnalysisPanel(props: AnalysisPanelProps): ReactElement {
|
|
|
136
125
|
{state.phase === 'idle' && (
|
|
137
126
|
<div className={css['mutedLine']}>{t('analysis.idleHint')}</div>
|
|
138
127
|
)}
|
|
139
|
-
<
|
|
128
|
+
<Button
|
|
140
129
|
type="button"
|
|
141
|
-
|
|
130
|
+
size="sm"
|
|
131
|
+
variant="primary"
|
|
132
|
+
className={css['startButton']}
|
|
142
133
|
onClick={props.onStart}
|
|
143
134
|
data-testid="agent-sidecar-analysis-start"
|
|
144
135
|
>
|
|
145
136
|
{state.phase === 'idle' ? t('analysis.start') : t('analysis.restart')}
|
|
146
|
-
</
|
|
137
|
+
</Button>
|
|
147
138
|
</>
|
|
148
139
|
)}
|
|
149
140
|
|
|
@@ -156,7 +147,7 @@ export function AnalysisPanel(props: AnalysisPanelProps): ReactElement {
|
|
|
156
147
|
|
|
157
148
|
{conversationLive && (
|
|
158
149
|
<div className={css['followupForm']}>
|
|
159
|
-
<
|
|
150
|
+
<Input
|
|
160
151
|
className={css['followupInput']}
|
|
161
152
|
value={question}
|
|
162
153
|
placeholder={t('analysis.followupPlaceholder')}
|
|
@@ -169,15 +160,16 @@ export function AnalysisPanel(props: AnalysisPanelProps): ReactElement {
|
|
|
169
160
|
}}
|
|
170
161
|
data-testid="agent-sidecar-analysis-question"
|
|
171
162
|
/>
|
|
172
|
-
<
|
|
163
|
+
<Button
|
|
173
164
|
type="button"
|
|
174
|
-
|
|
165
|
+
size="sm"
|
|
166
|
+
variant="ghost"
|
|
175
167
|
disabled={!enabled || state.phase !== 'ready' || question.trim() === ''}
|
|
176
168
|
onClick={submitFollowup}
|
|
177
169
|
data-testid="agent-sidecar-analysis-followup"
|
|
178
170
|
>
|
|
179
171
|
{t('analysis.followupSubmit')}
|
|
180
|
-
</
|
|
172
|
+
</Button>
|
|
181
173
|
</div>
|
|
182
174
|
)}
|
|
183
175
|
|
|
@@ -1,20 +1,13 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* AI bypass-analysis panel styles (design §4.e.3 / §5.1 view 2): every
|
|
3
|
-
* color rides a --dsw-alias-* design token with a neutral fallback — no
|
|
4
|
-
* theme colors are hardcoded as the only value. Same posture as
|
|
5
|
-
* dsh-tools.module.css.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
1
|
.panel {
|
|
9
2
|
display: flex;
|
|
10
3
|
flex-direction: column;
|
|
11
4
|
gap: 8px;
|
|
12
5
|
min-width: 0;
|
|
13
6
|
padding: 10px 12px;
|
|
14
|
-
border
|
|
15
|
-
border:
|
|
16
|
-
background: var(--
|
|
17
|
-
color: var(--
|
|
7
|
+
border: 1px solid var(--agsc-border-strong);
|
|
8
|
+
border-radius: var(--agsc-radius-control);
|
|
9
|
+
background: var(--agsc-bg);
|
|
10
|
+
color: var(--agsc-fg);
|
|
18
11
|
}
|
|
19
12
|
|
|
20
13
|
.head {
|
|
@@ -27,84 +20,43 @@
|
|
|
27
20
|
.title {
|
|
28
21
|
font-size: 13px;
|
|
29
22
|
font-weight: 600;
|
|
30
|
-
color: var(--
|
|
23
|
+
color: var(--agsc-fg);
|
|
31
24
|
}
|
|
32
25
|
|
|
33
26
|
.spacer {
|
|
34
27
|
flex: 1;
|
|
35
28
|
}
|
|
36
29
|
|
|
37
|
-
.
|
|
38
|
-
.textButton {
|
|
39
|
-
flex: none;
|
|
40
|
-
font-size: 12px;
|
|
41
|
-
padding: 2px 8px;
|
|
42
|
-
border-radius: 6px;
|
|
43
|
-
border: 1px solid var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.12));
|
|
44
|
-
background: transparent;
|
|
45
|
-
color: var(--dsw-alias-label-secondary, #57606a);
|
|
46
|
-
cursor: pointer;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
.closeButton:hover,
|
|
50
|
-
.textButton:hover {
|
|
51
|
-
background: var(--dsw-alias-bg-layer-2, rgba(0, 0, 0, 0.04));
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
.primaryButton {
|
|
30
|
+
.startButton {
|
|
55
31
|
align-self: flex-start;
|
|
56
|
-
font-size: 12px;
|
|
57
|
-
font-weight: 600;
|
|
58
|
-
padding: 4px 12px;
|
|
59
|
-
border-radius: 6px;
|
|
60
|
-
border: 1px solid var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.12));
|
|
61
|
-
background: var(--dsw-alias-bg-layer-2, rgba(0, 0, 0, 0.04));
|
|
62
|
-
color: var(--dsw-alias-label-primary, #1f2328);
|
|
63
|
-
cursor: pointer;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
.primaryButton:disabled,
|
|
67
|
-
.textButton:disabled {
|
|
68
|
-
opacity: 0.5;
|
|
69
|
-
cursor: not-allowed;
|
|
70
32
|
}
|
|
71
33
|
|
|
72
34
|
.mutedLine {
|
|
73
35
|
font-size: 12px;
|
|
74
36
|
line-height: 18px;
|
|
75
|
-
color: var(--
|
|
37
|
+
color: var(--agsc-fg-secondary);
|
|
76
38
|
}
|
|
77
39
|
|
|
78
|
-
|
|
79
|
-
.
|
|
40
|
+
.noteCard,
|
|
41
|
+
.errorCard,
|
|
42
|
+
.noticeBar {
|
|
43
|
+
padding: 8px 10px;
|
|
44
|
+
border: 1px solid var(--agsc-border-strong);
|
|
45
|
+
border-radius: var(--agsc-radius-control);
|
|
46
|
+
background: var(--agsc-bg-raised);
|
|
80
47
|
font-size: 12px;
|
|
81
48
|
line-height: 18px;
|
|
82
|
-
|
|
83
|
-
border-radius: 8px;
|
|
84
|
-
border: 1px dashed var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.12));
|
|
85
|
-
background: var(--dsw-alias-bg-layer-1, rgba(0, 0, 0, 0.02));
|
|
86
|
-
color: var(--dsw-alias-label-secondary, #57606a);
|
|
49
|
+
color: var(--agsc-fg-secondary);
|
|
87
50
|
}
|
|
88
51
|
|
|
89
52
|
.errorCard {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
padding: 8px 10px;
|
|
93
|
-
border-radius: 8px;
|
|
94
|
-
color: var(--dsw-alias-state-error-primary, #cf222e);
|
|
95
|
-
background: var(--dsw-alias-state-error-secondary, rgba(207, 34, 46, 0.08));
|
|
96
|
-
border: 1px solid var(--dsw-alias-state-error-secondary, rgba(207, 34, 46, 0.24));
|
|
53
|
+
border-color: var(--agsc-err);
|
|
54
|
+
color: var(--agsc-err);
|
|
97
55
|
}
|
|
98
56
|
|
|
99
|
-
/* Non-terminal retryable notice. */
|
|
100
57
|
.noticeBar {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
padding: 6px 10px;
|
|
104
|
-
border-radius: 8px;
|
|
105
|
-
color: var(--dsw-alias-state-warning-primary, #9a6700);
|
|
106
|
-
background: var(--dsw-alias-state-warning-secondary, rgba(154, 103, 0, 0.1));
|
|
107
|
-
border: 1px solid var(--dsw-alias-state-warning-secondary, rgba(154, 103, 0, 0.28));
|
|
58
|
+
border-color: var(--agsc-warn);
|
|
59
|
+
color: var(--agsc-warn);
|
|
108
60
|
}
|
|
109
61
|
|
|
110
62
|
.exchange {
|
|
@@ -116,48 +68,47 @@
|
|
|
116
68
|
.exchangeLabel {
|
|
117
69
|
font-size: 11px;
|
|
118
70
|
font-weight: 600;
|
|
119
|
-
color: var(--
|
|
71
|
+
color: var(--agsc-fg-secondary);
|
|
120
72
|
}
|
|
121
73
|
|
|
122
74
|
.question {
|
|
75
|
+
padding: 6px 10px;
|
|
76
|
+
border-radius: var(--agsc-radius-control);
|
|
77
|
+
background: var(--agsc-bg-raised);
|
|
78
|
+
color: var(--agsc-fg);
|
|
123
79
|
font-size: 12px;
|
|
124
80
|
line-height: 18px;
|
|
125
|
-
padding: 6px 10px;
|
|
126
|
-
border-radius: 8px;
|
|
127
|
-
background: var(--dsw-alias-bg-layer-2, rgba(0, 0, 0, 0.04));
|
|
128
|
-
color: var(--dsw-alias-label-primary, #1f2328);
|
|
129
81
|
white-space: pre-wrap;
|
|
130
82
|
word-break: break-word;
|
|
131
83
|
}
|
|
132
84
|
|
|
133
85
|
.summary {
|
|
134
86
|
margin: 0;
|
|
87
|
+
max-height: 320px;
|
|
88
|
+
overflow: auto;
|
|
89
|
+
padding: 8px 10px;
|
|
90
|
+
border: 1px solid var(--agsc-border-strong);
|
|
91
|
+
border-radius: var(--agsc-radius-control);
|
|
92
|
+
background: var(--agsc-bg-raised);
|
|
93
|
+
color: var(--agsc-fg);
|
|
94
|
+
font-family: inherit;
|
|
135
95
|
font-size: 12px;
|
|
136
96
|
line-height: 18px;
|
|
137
|
-
padding: 8px 10px;
|
|
138
|
-
border-radius: 8px;
|
|
139
|
-
background: var(--dsw-alias-bg-layer-1, rgba(0, 0, 0, 0.02));
|
|
140
|
-
border: 1px solid var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.12));
|
|
141
|
-
color: var(--dsw-alias-label-primary, #1f2328);
|
|
142
97
|
white-space: pre-wrap;
|
|
143
98
|
word-break: break-word;
|
|
144
|
-
font-family: inherit;
|
|
145
|
-
max-height: 320px;
|
|
146
|
-
overflow: auto;
|
|
147
99
|
}
|
|
148
100
|
|
|
149
101
|
.truncated {
|
|
102
|
+
color: var(--agsc-warn);
|
|
150
103
|
font-size: 11px;
|
|
151
|
-
color: var(--dsw-alias-state-warning-primary, #9a6700);
|
|
152
104
|
}
|
|
153
105
|
|
|
154
|
-
/* The §5.3 honesty line — always visible with results. */
|
|
155
106
|
.disclaimer {
|
|
107
|
+
padding-top: 6px;
|
|
108
|
+
border-top: 1px solid var(--agsc-border-strong);
|
|
109
|
+
color: var(--agsc-fg-secondary);
|
|
156
110
|
font-size: 11px;
|
|
157
111
|
line-height: 16px;
|
|
158
|
-
color: var(--dsw-alias-label-secondary, #57606a);
|
|
159
|
-
border-top: 1px solid var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.08));
|
|
160
|
-
padding-top: 6px;
|
|
161
112
|
}
|
|
162
113
|
|
|
163
114
|
.followupForm {
|
|
@@ -168,16 +119,4 @@
|
|
|
168
119
|
.followupInput {
|
|
169
120
|
flex: 1;
|
|
170
121
|
min-width: 0;
|
|
171
|
-
font-size: 12px;
|
|
172
|
-
padding: 4px 8px;
|
|
173
|
-
border-radius: 6px;
|
|
174
|
-
border: 1px solid var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.16));
|
|
175
|
-
background: var(--dsw-alias-bg-layer-0, #fff);
|
|
176
|
-
color: var(--dsw-alias-label-primary, #1f2328);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
.actionsRow {
|
|
180
|
-
display: flex;
|
|
181
|
-
align-items: center;
|
|
182
|
-
gap: 8px;
|
|
183
122
|
}
|
|
@@ -18,8 +18,9 @@
|
|
|
18
18
|
* concerns — useState here, reset on tab remount.
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
-
import {
|
|
22
|
-
import
|
|
21
|
+
import { Button, Pill, StateDot, type StateDotState } from '@deepseek-ai/dsh-client-ui-primitives'
|
|
22
|
+
import { useEffect, useRef, useState } from 'react'
|
|
23
|
+
import type { ReactElement } from 'react'
|
|
23
24
|
import {
|
|
24
25
|
buildBoardViewModel,
|
|
25
26
|
formatTemplate,
|
|
@@ -35,6 +36,7 @@ import {
|
|
|
35
36
|
type StreamHealthToken,
|
|
36
37
|
} from './logic.ts'
|
|
37
38
|
import { BOARD_STRINGS } from './strings.ts'
|
|
39
|
+
import { surfaceProps } from '../theme/parts.ts'
|
|
38
40
|
import styles from './board.module.css'
|
|
39
41
|
|
|
40
42
|
/** Time-window choices offered by the top bar (hours). */
|
|
@@ -62,24 +64,47 @@ export interface BoardProps {
|
|
|
62
64
|
nowMs?: number
|
|
63
65
|
}
|
|
64
66
|
|
|
67
|
+
function sessionDotState(status: DerivedSessionCardVM['badge']['status']): StateDotState | null {
|
|
68
|
+
if (status === 'working') return 'ongoing'
|
|
69
|
+
if (status === 'waiting') return 'warning'
|
|
70
|
+
return null
|
|
71
|
+
}
|
|
72
|
+
|
|
65
73
|
function SessionCard(props: {
|
|
66
74
|
card: DerivedSessionCardVM
|
|
67
75
|
onSelect: (sessionId: string) => void
|
|
68
76
|
}): ReactElement {
|
|
69
77
|
const { card, onSelect } = props
|
|
70
78
|
const [copied, setCopied] = useState(false)
|
|
79
|
+
const copyTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
|
80
|
+
const copyAliveRef = useRef(true)
|
|
81
|
+
const dotState = sessionDotState(card.badge.status)
|
|
82
|
+
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
copyAliveRef.current = true
|
|
85
|
+
return () => {
|
|
86
|
+
copyAliveRef.current = false
|
|
87
|
+
if (copyTimerRef.current !== null) {
|
|
88
|
+
clearTimeout(copyTimerRef.current)
|
|
89
|
+
copyTimerRef.current = null
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}, [])
|
|
71
93
|
|
|
72
|
-
// UX-17:
|
|
73
|
-
//
|
|
74
|
-
|
|
75
|
-
const onCopyId = (ev: MouseEvent): void => {
|
|
76
|
-
ev.stopPropagation()
|
|
94
|
+
// UX-17: opening and copying are sibling buttons inside a semantic card,
|
|
95
|
+
// so both actions are independently keyboard reachable.
|
|
96
|
+
const onCopyId = (): void => {
|
|
77
97
|
const clipboard = typeof navigator === 'undefined' ? undefined : navigator.clipboard
|
|
78
98
|
if (clipboard === undefined) return
|
|
79
99
|
clipboard.writeText(card.sessionId).then(
|
|
80
100
|
() => {
|
|
101
|
+
if (!copyAliveRef.current) return
|
|
102
|
+
if (copyTimerRef.current !== null) clearTimeout(copyTimerRef.current)
|
|
81
103
|
setCopied(true)
|
|
82
|
-
setTimeout(() => {
|
|
104
|
+
copyTimerRef.current = setTimeout(() => {
|
|
105
|
+
copyTimerRef.current = null
|
|
106
|
+
setCopied(false)
|
|
107
|
+
}, 2000)
|
|
83
108
|
},
|
|
84
109
|
() => {
|
|
85
110
|
// Clipboard permission denied: the hover title still carries the id.
|
|
@@ -88,33 +113,43 @@ function SessionCard(props: {
|
|
|
88
113
|
}
|
|
89
114
|
|
|
90
115
|
return (
|
|
91
|
-
<
|
|
92
|
-
|
|
93
|
-
className={styles['card']}
|
|
94
|
-
onClick={() => onSelect(card.sessionId)}
|
|
116
|
+
<article
|
|
117
|
+
{...surfaceProps('board-card', styles['card'])}
|
|
95
118
|
data-testid="agent-sidecar-card"
|
|
96
119
|
>
|
|
97
|
-
<
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
120
|
+
<button
|
|
121
|
+
type="button"
|
|
122
|
+
className={styles['cardOpen']}
|
|
123
|
+
onClick={() => onSelect(card.sessionId)}
|
|
124
|
+
>
|
|
125
|
+
<span className={styles['cardHead']}>
|
|
126
|
+
<span className={styles['agent']}>
|
|
127
|
+
<span className={styles['glyph']} aria-hidden>
|
|
128
|
+
{card.glyph}
|
|
129
|
+
</span>
|
|
130
|
+
{card.agent}
|
|
131
|
+
</span>
|
|
132
|
+
<span title={card.hoverTitle}>
|
|
133
|
+
<Pill className={styles['statusPill']}>
|
|
134
|
+
{dotState === null
|
|
135
|
+
? <span className={styles['dot']} data-tone={card.badge.tone} />
|
|
136
|
+
: <StateDot state={dotState} size={8} />}
|
|
137
|
+
{card.badge.label}
|
|
138
|
+
{card.badge.attention !== null && (
|
|
139
|
+
<span className={styles['attention']} data-kind={card.badge.attention}>
|
|
140
|
+
{card.badge.attentionLabel}
|
|
141
|
+
</span>
|
|
142
|
+
)}
|
|
143
|
+
</Pill>
|
|
101
144
|
</span>
|
|
102
|
-
{card.agent}
|
|
103
145
|
</span>
|
|
104
|
-
<span className={styles['
|
|
105
|
-
|
|
106
|
-
{card.badge.label}
|
|
107
|
-
{card.badge.attention !== null && (
|
|
108
|
-
<span className={styles['attention']} data-kind={card.badge.attention}>
|
|
109
|
-
{card.badge.attentionLabel}
|
|
110
|
-
</span>
|
|
111
|
-
)}
|
|
146
|
+
<span className={styles['cardTitle']} title={card.title}>
|
|
147
|
+
{card.title.trim() === '' ? BOARD_STRINGS.card.untitled : card.title}
|
|
112
148
|
</span>
|
|
113
|
-
</
|
|
114
|
-
<
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
<div
|
|
149
|
+
</button>
|
|
150
|
+
<Button
|
|
151
|
+
size="sm"
|
|
152
|
+
variant="ghost"
|
|
118
153
|
className={styles['cardId']}
|
|
119
154
|
title={`${card.sessionId}\n${BOARD_STRINGS.card.copyId}`}
|
|
120
155
|
onClick={onCopyId}
|
|
@@ -122,14 +157,14 @@ function SessionCard(props: {
|
|
|
122
157
|
>
|
|
123
158
|
{card.shortId}
|
|
124
159
|
{copied && <span className={styles['copied']} role="status">{BOARD_STRINGS.card.copied}</span>}
|
|
125
|
-
</
|
|
160
|
+
</Button>
|
|
126
161
|
<div className={styles['cardEvent']}>
|
|
127
162
|
{card.lastEvent === null
|
|
128
163
|
? BOARD_STRINGS.card.noEvent
|
|
129
164
|
: `${card.lastEvent.kind} · ${card.lastEvent.text}`}
|
|
130
165
|
</div>
|
|
131
166
|
<div className={styles['cardTime']}>{card.relativeTime}</div>
|
|
132
|
-
</
|
|
167
|
+
</article>
|
|
133
168
|
)
|
|
134
169
|
}
|
|
135
170
|
|
|
@@ -180,22 +215,24 @@ function ProjectGroup(props: {
|
|
|
180
215
|
))}
|
|
181
216
|
</div>
|
|
182
217
|
{hiddenCount > 0 && (
|
|
183
|
-
<
|
|
184
|
-
|
|
218
|
+
<Button
|
|
219
|
+
size="sm"
|
|
220
|
+
variant="outline"
|
|
185
221
|
className={styles['showMore']}
|
|
186
222
|
onClick={() => { setExpanded(true) }}
|
|
187
223
|
>
|
|
188
224
|
{formatTemplate(BOARD_STRINGS.group.showAll, { n: group.cards.length })}
|
|
189
|
-
</
|
|
225
|
+
</Button>
|
|
190
226
|
)}
|
|
191
227
|
{expanded && group.cards.length > GROUP_CARD_LIMIT && (
|
|
192
|
-
<
|
|
193
|
-
|
|
228
|
+
<Button
|
|
229
|
+
size="sm"
|
|
230
|
+
variant="outline"
|
|
194
231
|
className={styles['showMore']}
|
|
195
232
|
onClick={() => { setExpanded(false) }}
|
|
196
233
|
>
|
|
197
234
|
{formatTemplate(BOARD_STRINGS.group.showLess, { n: GROUP_CARD_LIMIT })}
|
|
198
|
-
</
|
|
235
|
+
</Button>
|
|
199
236
|
)}
|
|
200
237
|
</>
|
|
201
238
|
)}
|
|
@@ -249,41 +286,57 @@ export function Board(props: BoardProps): ReactElement {
|
|
|
249
286
|
: formatTemplate(BOARD_STRINGS.topbar.filterByStatusTitle, {
|
|
250
287
|
label: BOARD_STRINGS.status[status],
|
|
251
288
|
})
|
|
289
|
+
const daemonDotState: StateDotState =
|
|
290
|
+
props.daemonState === 'failed'
|
|
291
|
+
? 'error'
|
|
292
|
+
: props.daemonState === 'defer' || props.daemonState === 'backoff'
|
|
293
|
+
? 'warning'
|
|
294
|
+
: props.daemonState === 'adopted' || props.daemonState === 'hosted'
|
|
295
|
+
? 'done'
|
|
296
|
+
: 'ongoing'
|
|
297
|
+
const streamDotState: StateDotState =
|
|
298
|
+
props.streamHealth === 'ok' ? 'done' : props.streamHealth === 'degraded' ? 'warning' : 'ongoing'
|
|
252
299
|
|
|
253
300
|
return (
|
|
254
|
-
<div
|
|
255
|
-
<header
|
|
301
|
+
<div {...surfaceProps('board', styles['root'])} data-testid="agent-sidecar-board">
|
|
302
|
+
<header {...surfaceProps('board-toolbar', styles['topbar'])}>
|
|
256
303
|
<span className={styles['title']}>{BOARD_STRINGS.topbar.title}</span>
|
|
257
|
-
<span
|
|
258
|
-
<
|
|
259
|
-
|
|
304
|
+
<span title={props.daemonDetail}>
|
|
305
|
+
<Pill>
|
|
306
|
+
<StateDot state={daemonDotState} size={8} />
|
|
307
|
+
{vm.daemonBadge.label}
|
|
308
|
+
</Pill>
|
|
260
309
|
</span>
|
|
261
|
-
<
|
|
262
|
-
<
|
|
310
|
+
<Pill>
|
|
311
|
+
<StateDot state={streamDotState} size={8} />
|
|
263
312
|
{vm.streamLabel}
|
|
264
|
-
</
|
|
265
|
-
<
|
|
266
|
-
type="button"
|
|
313
|
+
</Pill>
|
|
314
|
+
<Pill
|
|
267
315
|
className={styles['countBadge']}
|
|
316
|
+
active={props.filters.statusFilter === 'working'}
|
|
268
317
|
aria-pressed={props.filters.statusFilter === 'working'}
|
|
269
318
|
title={statusBadgeTitle('working')}
|
|
270
319
|
onClick={() => { toggleStatusFilter('working') }}
|
|
271
320
|
data-testid="agent-sidecar-count-working"
|
|
272
321
|
>
|
|
273
|
-
|
|
322
|
+
{vm.workingCount > 0
|
|
323
|
+
? <StateDot state="ongoing" size={8} />
|
|
324
|
+
: <span className={styles['dot']} data-tone="neutral" />}
|
|
274
325
|
{formatTemplate(BOARD_STRINGS.topbar.countWorking, { n: vm.workingCount })}
|
|
275
|
-
</
|
|
276
|
-
<
|
|
277
|
-
type="button"
|
|
326
|
+
</Pill>
|
|
327
|
+
<Pill
|
|
278
328
|
className={styles['countBadge']}
|
|
329
|
+
active={props.filters.statusFilter === 'waiting'}
|
|
279
330
|
aria-pressed={props.filters.statusFilter === 'waiting'}
|
|
280
331
|
title={statusBadgeTitle('waiting')}
|
|
281
332
|
onClick={() => { toggleStatusFilter('waiting') }}
|
|
282
333
|
data-testid="agent-sidecar-count-waiting"
|
|
283
334
|
>
|
|
284
|
-
|
|
335
|
+
{vm.waitingCount > 0
|
|
336
|
+
? <StateDot state="warning" size={8} />
|
|
337
|
+
: <span className={styles['dot']} data-tone="neutral" />}
|
|
285
338
|
{formatTemplate(BOARD_STRINGS.topbar.countWaiting, { n: vm.waitingCount })}
|
|
286
|
-
</
|
|
339
|
+
</Pill>
|
|
287
340
|
<span className={styles['countTotal']} data-testid="agent-sidecar-count-total">
|
|
288
341
|
{formatTemplate(BOARD_STRINGS.topbar.countTotal, { n: vm.totalCount })}
|
|
289
342
|
</span>
|
|
@@ -318,27 +371,28 @@ export function Board(props: BoardProps): ReactElement {
|
|
|
318
371
|
/>
|
|
319
372
|
{BOARD_STRINGS.topbar.showDead}
|
|
320
373
|
</label>
|
|
321
|
-
<
|
|
322
|
-
|
|
323
|
-
|
|
374
|
+
<Button
|
|
375
|
+
size="sm"
|
|
376
|
+
variant="toolbar"
|
|
324
377
|
title={BOARD_STRINGS.topbar.refreshTitle}
|
|
325
378
|
disabled={refreshing}
|
|
326
379
|
onClick={onRefreshClick}
|
|
327
380
|
>
|
|
328
381
|
{refreshing ? BOARD_STRINGS.topbar.refreshing : BOARD_STRINGS.topbar.refresh}
|
|
329
|
-
</
|
|
382
|
+
</Button>
|
|
330
383
|
</header>
|
|
331
384
|
|
|
332
385
|
{refreshFailed && (
|
|
333
386
|
<div className={styles['banner']} data-tone="warn" role="status">
|
|
334
387
|
{BOARD_STRINGS.topbar.refreshFailed}
|
|
335
|
-
<
|
|
336
|
-
|
|
388
|
+
<Button
|
|
389
|
+
size="sm"
|
|
390
|
+
variant="ghost"
|
|
337
391
|
className={styles['bannerDismiss']}
|
|
338
392
|
onClick={() => { setRefreshFailed(false) }}
|
|
339
393
|
>
|
|
340
394
|
{BOARD_STRINGS.topbar.dismiss}
|
|
341
|
-
</
|
|
395
|
+
</Button>
|
|
342
396
|
</div>
|
|
343
397
|
)}
|
|
344
398
|
|