@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.
Files changed (58) hide show
  1. package/README.md +78 -11
  2. package/lib/client.js +4708 -2932
  3. package/lib/client.js.map +1 -1
  4. package/package.json +3 -1
  5. package/src/client/analysis/AnalysisPanel.tsx +19 -27
  6. package/src/client/analysis/analysis.module.css +36 -97
  7. package/src/client/board/Board.tsx +255 -48
  8. package/src/client/board/board.module.css +113 -92
  9. package/src/client/board/logic.ts +99 -21
  10. package/src/client/board/project-view-logic.ts +27 -34
  11. package/src/client/board/project-view.module.css +46 -83
  12. package/src/client/board/project-view.tsx +50 -8
  13. package/src/client/board/strings.ts +70 -85
  14. package/src/client/commands.ts +30 -15
  15. package/src/client/controller.ts +27 -7
  16. package/src/client/detail/SessionDetail.tsx +234 -37
  17. package/src/client/detail/detail.module.css +100 -153
  18. package/src/client/detail/logic.ts +220 -29
  19. package/src/client/detail/strings.ts +66 -77
  20. package/src/client/detail-glue.ts +33 -0
  21. package/src/client/detail-view.module.css +43 -35
  22. package/src/client/detail-view.tsx +138 -118
  23. package/src/client/dsh-tools/LineageTree.tsx +22 -13
  24. package/src/client/dsh-tools/SearchPanel.tsx +18 -11
  25. package/src/client/dsh-tools/dsh-tools.module.css +53 -140
  26. package/src/client/dsh-tools/strings.ts +42 -77
  27. package/src/client/index.ts +285 -124
  28. package/src/client/inject/InjectPanel.tsx +81 -61
  29. package/src/client/inject/inject.module.css +97 -197
  30. package/src/client/inject/logic.ts +38 -0
  31. package/src/client/lifecycle/handoff.ts +83 -0
  32. package/src/client/locales/en.ts +91 -4
  33. package/src/client/locales/host.ts +131 -0
  34. package/src/client/locales/index.ts +196 -8
  35. package/src/client/locales/react.ts +10 -0
  36. package/src/client/locales/view.ts +38 -0
  37. package/src/client/locales/zh.ts +200 -125
  38. package/src/client/mount.tsx +75 -41
  39. package/src/client/navigation/CenterOverlay.tsx +40 -0
  40. package/src/client/navigation/center-overlay.module.css +51 -0
  41. package/src/client/navigation/center.ts +45 -0
  42. package/src/client/navigation/modal-isolation.ts +152 -0
  43. package/src/client/navigation/modal-surface-anchor.ts +72 -0
  44. package/src/client/navigation/sidebar-entry.module.css +73 -0
  45. package/src/client/navigation/sidebar-entry.ts +309 -0
  46. package/src/client/primitives/StaticPill.tsx +21 -0
  47. package/src/client/settings-card.module.css +35 -119
  48. package/src/client/settings-card.tsx +31 -153
  49. package/src/client/settings-fields.tsx +131 -0
  50. package/src/client/sidebar/SidebarTab.tsx +156 -0
  51. package/src/client/sidebar/model.ts +65 -0
  52. package/src/client/sidebar/sidebar-tab.module.css +118 -0
  53. package/src/client/sidebar-tab.tsx +46 -320
  54. package/src/client/theme/agsc.module.css +31 -0
  55. package/src/client/theme/parts.ts +56 -0
  56. package/src/client/ui-integration.ts +86 -0
  57. package/src/client/widget.tsx +42 -16
  58. package/src/client/inject/overlay.module.css +0 -22
@@ -1,9 +1,4 @@
1
- /*
2
- * Project correlation view styles (T5.5): same visual language as
3
- * board.module.css — every color rides a --dsw-alias-* design token with
4
- * a neutral fallback, flex top bar, grouped sections. Sessions render as
5
- * compact rows inside per-agent lanes instead of the board's card grid.
6
- */
1
+ /* Project correlation layout consumes the shared Agent Sidecar theme. */
7
2
 
8
3
  .root {
9
4
  display: flex;
@@ -14,8 +9,8 @@
14
9
  padding: 16px 20px;
15
10
  box-sizing: border-box;
16
11
  overflow-y: auto;
17
- color: var(--dsw-alias-label-primary, #1f2328);
18
- background: var(--dsw-alias-bg-base, transparent);
12
+ color: var(--agsc-fg);
13
+ background: var(--agsc-bg);
19
14
  }
20
15
 
21
16
  /* ------------------------------------------------------------- top bar */
@@ -30,21 +25,12 @@
30
25
  .title {
31
26
  font-size: 15px;
32
27
  font-weight: 650;
33
- color: var(--dsw-alias-label-primary, #1f2328);
28
+ color: var(--agsc-fg);
34
29
  }
35
30
 
36
31
  .summary {
37
32
  font-size: 12px;
38
- color: var(--dsw-alias-label-secondary, #57606a);
39
- }
40
-
41
- .loadingChip {
42
- font-size: 11px;
43
- line-height: 18px;
44
- padding: 0 8px;
45
- border-radius: 999px;
46
- color: var(--dsw-alias-label-secondary, #57606a);
47
- background: var(--dsw-alias-bg-layer-2, rgba(0, 0, 0, 0.04));
33
+ color: var(--agsc-fg-secondary);
48
34
  }
49
35
 
50
36
  /* -------------------------------------------------------------- banner */
@@ -53,14 +39,14 @@
53
39
  font-size: 12px;
54
40
  line-height: 18px;
55
41
  padding: 6px 10px;
56
- border-radius: 8px;
57
- border: 1px solid var(--dsw-alias-border-l1, rgba(0, 0, 0, 0.08));
42
+ border-radius: var(--agsc-radius-control);
43
+ border: 1px solid var(--agsc-border);
58
44
  }
59
45
 
60
46
  .banner[data-tone='danger'] {
61
- color: var(--dsw-alias-state-error-primary, #cf222e);
62
- background: var(--dsw-alias-state-error-secondary, rgba(207, 34, 46, 0.08));
63
- border-color: var(--dsw-alias-state-error-secondary, rgba(207, 34, 46, 0.24));
47
+ color: var(--agsc-err);
48
+ background: var(--dsw-alias-state-error-secondary);
49
+ border-color: var(--dsw-alias-state-error-secondary);
64
50
  }
65
51
 
66
52
  /* -------------------------------------------------------- empty states */
@@ -80,14 +66,14 @@
80
66
  .emptyTitle {
81
67
  font-size: 14px;
82
68
  font-weight: 600;
83
- color: var(--dsw-alias-label-primary, #1f2328);
69
+ color: var(--agsc-fg);
84
70
  }
85
71
 
86
72
  .emptyHint {
87
73
  font-size: 12px;
88
74
  max-width: 420px;
89
75
  line-height: 20px;
90
- color: var(--dsw-alias-label-secondary, #57606a);
76
+ color: var(--agsc-fg-secondary);
91
77
  }
92
78
 
93
79
  /* -------------------------------------------------------- group blocks */
@@ -97,9 +83,9 @@
97
83
  flex-direction: column;
98
84
  gap: 8px;
99
85
  padding: 10px 12px;
100
- border-radius: 10px;
101
- border: 1px solid var(--dsw-alias-border-l1, rgba(0, 0, 0, 0.08));
102
- background: var(--dsw-alias-bg-layer-1, rgba(0, 0, 0, 0.02));
86
+ border-radius: var(--agsc-radius-card);
87
+ border: 1px solid var(--agsc-border-strong);
88
+ background: var(--agsc-bg-raised);
103
89
  }
104
90
 
105
91
  .groupHead {
@@ -113,7 +99,7 @@
113
99
  .groupName {
114
100
  font-size: 13px;
115
101
  font-weight: 600;
116
- color: var(--dsw-alias-label-primary, #1f2328);
102
+ color: var(--agsc-fg);
117
103
  overflow: hidden;
118
104
  text-overflow: ellipsis;
119
105
  white-space: nowrap;
@@ -134,9 +120,9 @@
134
120
  line-height: 18px;
135
121
  padding: 0 7px;
136
122
  border-radius: 999px;
137
- border: 1px solid var(--dsw-alias-border-l1, rgba(0, 0, 0, 0.08));
138
- background: var(--dsw-alias-bg-layer-2, rgba(0, 0, 0, 0.04));
139
- color: var(--dsw-alias-label-secondary, #57606a);
123
+ border: 1px solid var(--agsc-border);
124
+ background: var(--dsw-alias-bg-layer-2);
125
+ color: var(--agsc-fg-secondary);
140
126
  white-space: nowrap;
141
127
  }
142
128
 
@@ -145,8 +131,8 @@
145
131
  line-height: 18px;
146
132
  padding: 0 7px;
147
133
  border-radius: 999px;
148
- color: var(--dsw-alias-static-white, #fff);
149
- background: var(--dsw-alias-brand-primary, #4d6bfe);
134
+ color: var(--dsw-alias-label-primary-foreground);
135
+ background: var(--agsc-accent);
150
136
  white-space: nowrap;
151
137
  }
152
138
 
@@ -157,7 +143,7 @@
157
143
  .groupMeta {
158
144
  font-size: 11px;
159
145
  flex: none;
160
- color: var(--dsw-alias-label-tertiary, #6e7781);
146
+ color: var(--agsc-fg-dimmed);
161
147
  white-space: nowrap;
162
148
  }
163
149
 
@@ -181,12 +167,12 @@
181
167
  gap: 5px;
182
168
  font-size: 12px;
183
169
  font-weight: 600;
184
- color: var(--dsw-alias-label-primary, #1f2328);
170
+ color: var(--agsc-fg);
185
171
  }
186
172
 
187
173
  .glyph {
188
174
  flex: none;
189
- color: var(--dsw-alias-brand-primary, #4d6bfe);
175
+ color: var(--agsc-accent);
190
176
  }
191
177
 
192
178
  .laneSessions {
@@ -195,6 +181,11 @@
195
181
  gap: 4px;
196
182
  }
197
183
 
184
+ /* UX-20: lane truncation fold (same look as the board's group fold). */
185
+ .showMore {
186
+ align-self: flex-start;
187
+ }
188
+
198
189
  /* -------------------------------------------------------- session rows */
199
190
 
200
191
  .session {
@@ -206,36 +197,25 @@
206
197
  font-family: inherit;
207
198
  font-size: 12px;
208
199
  padding: 5px 8px;
209
- border-radius: 7px;
210
- border: 1px solid var(--dsw-alias-border-l1, rgba(0, 0, 0, 0.08));
211
- background: var(--dsw-alias-bg-base, transparent);
212
- color: var(--dsw-alias-label-primary, #1f2328);
200
+ border-radius: var(--agsc-radius-control);
201
+ border: 1px solid var(--agsc-border);
202
+ background: var(--dsw-alias-bg-base);
203
+ color: var(--agsc-fg);
213
204
  cursor: pointer;
214
205
  }
215
206
 
216
207
  .session:hover {
217
- border-color: var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.12));
218
- background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.04));
208
+ border-color: var(--agsc-border-strong);
209
+ background: var(--dsw-alias-interactive-bg-hover);
219
210
  }
220
211
 
221
212
  .session:focus-visible {
222
- outline: 2px solid var(--dsw-alias-brand-primary, #4d6bfe);
213
+ outline: 2px solid var(--agsc-accent);
223
214
  outline-offset: 1px;
224
215
  }
225
216
 
226
- .badge {
227
- display: inline-flex;
228
- align-items: center;
229
- gap: 5px;
217
+ .statusPill {
230
218
  flex: none;
231
- font-size: 11px;
232
- line-height: 18px;
233
- padding: 0 7px;
234
- border-radius: 999px;
235
- border: 1px solid var(--dsw-alias-border-l1, rgba(0, 0, 0, 0.08));
236
- background: var(--dsw-alias-bg-layer-1, rgba(0, 0, 0, 0.03));
237
- color: var(--dsw-alias-label-secondary, #57606a);
238
- white-space: nowrap;
239
219
  }
240
220
 
241
221
  .dot {
@@ -243,36 +223,24 @@
243
223
  height: 7px;
244
224
  border-radius: 50%;
245
225
  flex: none;
246
- background: var(--dsw-alias-label-tertiary, #6e7781);
247
- }
248
-
249
- .dot[data-tone='success'] {
250
- background: var(--dsw-alias-state-success-primary, #1a7f37);
251
- }
252
-
253
- .dot[data-tone='warn'] {
254
- background: var(--dsw-alias-state-warn-primary, #9a6700);
255
- }
256
-
257
- .dot[data-tone='danger'] {
258
- background: var(--dsw-alias-state-error-primary, #cf222e);
226
+ background: var(--dsw-alias-label-tertiary);
259
227
  }
260
228
 
261
229
  .dot[data-tone='neutral'] {
262
- background: var(--dsw-alias-label-tertiary, #6e7781);
230
+ background: var(--dsw-alias-label-tertiary);
263
231
  }
264
232
 
265
233
  .dot[data-tone='muted'] {
266
- background: var(--dsw-alias-label-dimmed, #8c959f);
234
+ background: var(--agsc-fg-dimmed);
267
235
  }
268
236
 
269
237
  .attention {
270
238
  font-size: 11px;
271
- color: var(--dsw-alias-state-warn-primary, #9a6700);
239
+ color: var(--agsc-warn);
272
240
  }
273
241
 
274
242
  .attention[data-kind='gap'] {
275
- color: var(--dsw-alias-state-error-primary, #cf222e);
243
+ color: var(--agsc-err);
276
244
  }
277
245
 
278
246
  .sessionTitle {
@@ -284,24 +252,19 @@
284
252
 
285
253
  .liveChip {
286
254
  flex: none;
287
- font-size: 10px;
288
- line-height: 16px;
289
- padding: 0 6px;
290
- border-radius: 999px;
291
- color: var(--dsw-alias-state-success-primary, #1a7f37);
292
- border: 1px solid var(--dsw-alias-state-success-primary, rgba(26, 127, 55, 0.4));
255
+ color: var(--agsc-ok);
293
256
  }
294
257
 
295
258
  .sessionId {
296
259
  flex: none;
297
260
  font-size: 11px;
298
- font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
299
- color: var(--dsw-alias-label-tertiary, #6e7781);
261
+ font-family: var(--agsc-font-mono);
262
+ color: var(--agsc-fg-dimmed);
300
263
  }
301
264
 
302
265
  .sessionTime {
303
266
  flex: none;
304
267
  margin-left: auto;
305
268
  font-size: 11px;
306
- color: var(--dsw-alias-label-caption, var(--dsw-alias-label-tertiary, #6e7781));
269
+ color: var(--agsc-fg-dimmed);
307
270
  }
@@ -14,15 +14,20 @@
14
14
  * blanking data the user already has.
15
15
  */
16
16
 
17
+ import { Button, Pill, StateDot, type StateDotState } from '@deepseek-ai/dsh-client-ui-primitives'
18
+ import { useState } from 'react'
17
19
  import type { ReactElement } from 'react'
18
20
  import {
19
21
  buildProjectViewModel,
22
+ LANE_SESSION_LIMIT,
20
23
  PROJECT_VIEW_STRINGS,
21
24
  type AgentLaneVM,
22
25
  type DerivedProjectGroupVM,
23
26
  type DerivedProjectSessionVM,
24
27
  type ProjectGroupVM,
25
28
  } from './project-view-logic.ts'
29
+ import { formatTemplate, sliceCardsForDisplay } from './logic.ts'
30
+ import { surfaceProps } from '../theme/parts.ts'
26
31
  import styles from './project-view.module.css'
27
32
 
28
33
  export interface ProjectViewProps {
@@ -36,11 +41,18 @@ export interface ProjectViewProps {
36
41
  nowMs?: number
37
42
  }
38
43
 
44
+ function sessionDotState(status: DerivedProjectSessionVM['badge']['status']): StateDotState | null {
45
+ if (status === 'working') return 'ongoing'
46
+ if (status === 'waiting') return 'warning'
47
+ return null
48
+ }
49
+
39
50
  function SessionRow(props: {
40
51
  session: DerivedProjectSessionVM
41
52
  onSelect: (sessionId: string) => void
42
53
  }): ReactElement {
43
54
  const { session, onSelect } = props
55
+ const dotState = sessionDotState(session.badge.status)
44
56
  return (
45
57
  <button
46
58
  type="button"
@@ -48,19 +60,21 @@ function SessionRow(props: {
48
60
  onClick={() => onSelect(session.sessionId)}
49
61
  data-testid="agent-sidecar-project-session"
50
62
  >
51
- <span className={styles['badge']} data-tone={session.badge.tone}>
52
- <span className={styles['dot']} data-tone={session.badge.tone} />
63
+ <Pill className={styles['statusPill']}>
64
+ {dotState === null
65
+ ? <span className={styles['dot']} data-tone={session.badge.tone} />
66
+ : <StateDot state={dotState} size={8} />}
53
67
  {session.badge.label}
54
68
  {session.badge.attention !== null && (
55
69
  <span className={styles['attention']} data-kind={session.badge.attention}>
56
70
  {session.badge.attentionLabel}
57
71
  </span>
58
72
  )}
59
- </span>
73
+ </Pill>
60
74
  <span className={styles['sessionTitle']} title={session.title}>
61
75
  {session.displayTitle}
62
76
  </span>
63
- {session.live && <span className={styles['liveChip']}>{PROJECT_VIEW_STRINGS.liveChip}</span>}
77
+ {session.live && <Pill className={styles['liveChip']}>{PROJECT_VIEW_STRINGS.liveChip}</Pill>}
64
78
  <span className={styles['sessionId']} title={session.sessionId}>
65
79
  {session.shortId}
66
80
  </span>
@@ -74,6 +88,11 @@ function AgentLane(props: {
74
88
  onSelect: (sessionId: string) => void
75
89
  }): ReactElement {
76
90
  const { lane, onSelect } = props
91
+ // UX-20: lanes fold past the row limit — ephemeral view state, same
92
+ // pattern as the board's group truncation. Rows are status-sorted, so
93
+ // the fold never hides a leading working/waiting run (slice guard).
94
+ const [expanded, setExpanded] = useState(false)
95
+ const { shown, hiddenCount } = sliceCardsForDisplay(lane.sessions, LANE_SESSION_LIMIT, expanded)
77
96
  return (
78
97
  <div className={styles['lane']}>
79
98
  <div className={styles['laneHead']}>
@@ -83,7 +102,7 @@ function AgentLane(props: {
83
102
  {lane.agent}
84
103
  </div>
85
104
  <div className={styles['laneSessions']}>
86
- {lane.sessions.map((session) => (
105
+ {shown.map((session) => (
87
106
  <SessionRow
88
107
  key={`${session.agent}:${session.sessionId}`}
89
108
  session={session}
@@ -91,6 +110,26 @@ function AgentLane(props: {
91
110
  />
92
111
  ))}
93
112
  </div>
113
+ {hiddenCount > 0 && (
114
+ <Button
115
+ size="sm"
116
+ variant="outline"
117
+ className={styles['showMore']}
118
+ onClick={() => { setExpanded(true) }}
119
+ >
120
+ {formatTemplate(PROJECT_VIEW_STRINGS.showAllSessions, { n: lane.sessions.length })}
121
+ </Button>
122
+ )}
123
+ {expanded && lane.sessions.length > LANE_SESSION_LIMIT && (
124
+ <Button
125
+ size="sm"
126
+ variant="outline"
127
+ className={styles['showMore']}
128
+ onClick={() => { setExpanded(false) }}
129
+ >
130
+ {formatTemplate(PROJECT_VIEW_STRINGS.showLessSessions, { n: LANE_SESSION_LIMIT })}
131
+ </Button>
132
+ )}
94
133
  </div>
95
134
  )
96
135
  }
@@ -142,13 +181,16 @@ export function ProjectView(props: ProjectViewProps): ReactElement {
142
181
  const hasContent = vm.groups.length > 0
143
182
 
144
183
  return (
145
- <div className={styles['root']} data-testid="agent-sidecar-project-view">
184
+ <div
185
+ {...surfaceProps('project-view', styles['root'])}
186
+ data-testid="agent-sidecar-project-view"
187
+ >
146
188
  <header className={styles['topbar']}>
147
189
  <span className={styles['title']}>{PROJECT_VIEW_STRINGS.title}</span>
148
190
  <span className={styles['summary']}>{vm.summaryLabel}</span>
149
191
  {props.loading && (
150
- <span className={styles['loadingChip']} role="status">
151
- {PROJECT_VIEW_STRINGS.loading}
192
+ <span role="status">
193
+ <Pill>{PROJECT_VIEW_STRINGS.loading}</Pill>
152
194
  </span>
153
195
  )}
154
196
  </header>
@@ -1,112 +1,97 @@
1
- /**
2
- * Centralized user-facing strings for the board view and the footer widget
3
- * (design §5.1 view 1 / view 4, §5.3 honesty wording).
4
- *
5
- * Chinese is the primary UI language for now; every string the components
6
- * render lives in this single table so the i18n skeleton (T2.3,
7
- * `ctx.locale.register`) can take it over without hunting through JSX.
8
- * Templates use `{name}` placeholders resolved by `formatTemplate` in
9
- * `logic.ts` — plain message strings, locale-registry friendly.
10
- *
11
- * Pure data module: no imports, no logic.
12
- *
13
- * @module
14
- */
1
+ import { createLocaleView } from '../locales/view.ts'
15
2
 
16
- export const BOARD_STRINGS = {
17
- /** Session status badge labels (observed values, see hover disclaimer). */
3
+ /** Dynamic board vocabulary; template leaves are interpolated by `formatTemplate`. */
4
+ export const BOARD_STRINGS = createLocaleView({
18
5
  status: {
19
- working: '工作中',
20
- waiting: '等待中',
21
- idle: '空闲',
22
- dead: '已结束',
23
- unknown: '未知',
6
+ working: 'board.status.working',
7
+ waiting: 'board.status.waiting',
8
+ idle: 'board.status.idle',
9
+ dead: 'board.status.dead',
10
+ unknown: 'board.status.unknown',
24
11
  },
25
- /** Badge attention markers; gap outranks stale (per-session > global). */
26
12
  attention: {
27
- gap: '事件缺口',
28
- stale: '可能滞后',
13
+ gap: 'board.attention.gap',
29
14
  },
30
- /** Daemon supervisor state labels for the top-bar badge. */
31
15
  daemon: {
32
- probe: '探测中',
33
- adopted: '已连接 · 领养',
34
- defer: '等待系统服务',
35
- reprobe: '重新探测中',
36
- hosting: '正在启动',
37
- hosted: '已连接 · 托管',
38
- backoff: '重启退避中',
39
- failed: '离线',
16
+ probe: 'board.daemon.probe',
17
+ adopted: 'board.daemon.adopted',
18
+ defer: 'board.daemon.defer',
19
+ reprobe: 'board.daemon.reprobe',
20
+ hosting: 'board.daemon.hosting',
21
+ hosted: 'board.daemon.hosted',
22
+ backoff: 'board.daemon.backoff',
23
+ failed: 'board.daemon.failed',
40
24
  },
41
- /** Stream health indicator labels. */
42
25
  stream: {
43
- ok: '实时流正常',
44
- degraded: '实时流重连中',
45
- unknown: '实时流未建立',
26
+ ok: 'board.stream.ok',
27
+ degraded: 'board.stream.degraded',
28
+ unknown: 'board.stream.unknown',
46
29
  },
47
- /** Top banner texts (design §5.3: degraded warning / FAILED red banner). */
48
30
  banner: {
49
- daemonFailed: 'sidecar 离线:看板显示最后一次快照,数据不再更新',
50
- streamDegraded: '实时流重连中,数据可能滞后',
31
+ daemonFailed: 'board.banner.daemonFailed',
32
+ streamDegraded: 'board.banner.streamDegraded',
51
33
  },
52
- /** Empty-state guidance blocks. */
53
34
  empty: {
54
- daemonFailedTitle: 'sidecar 已离线',
55
- daemonFailedHint:
56
- 'daemon 连续启动失败已熔断。可在设置卡重试,或手动运行 agent-sidecar daemon start 后等待自动领养。',
57
- daemonDeferTitle: '等待系统服务拉起 daemon',
58
- daemonDeferHint:
59
- '检测到 LaunchAgent 托管,插件不会自行启动 daemon;服务拉起后看板会自动出数。',
60
- filteredTitle: '当前过滤条件下没有会话',
61
- filteredHint: '试试放宽时间窗,或打开「显示已结束」。',
62
- noSessionsTitle: '暂无被观测的会话',
63
- noSessionsHint:
64
- '本机 agent(claude / codex / cursor / dsh …)开始工作后会自动出现在这里。',
35
+ daemonFailedTitle: 'board.empty.daemonFailedTitle',
36
+ daemonFailedHint: 'board.empty.daemonFailedHint',
37
+ daemonDeferTitle: 'board.empty.daemonDeferTitle',
38
+ daemonDeferHint: 'board.empty.daemonDeferHint',
39
+ filteredTitle: 'board.empty.filteredTitle',
40
+ filteredHint: 'board.empty.filteredHint',
41
+ noSessionsTitle: 'board.empty.noSessionsTitle',
42
+ noSessionsHint: 'board.empty.noSessionsHint',
65
43
  },
66
- /** Top bar controls. */
67
44
  topbar: {
68
- title: 'Sidecar 多 agent 看板',
69
- refresh: '刷新',
70
- refreshTitle: '手动拉取最新快照',
71
- showDead: '显示已结束',
72
- timeWindow: '时间窗',
45
+ title: 'board.topbar.title',
46
+ refresh: 'board.topbar.refresh',
47
+ refreshing: 'board.topbar.refreshing',
48
+ refreshTitle: 'board.topbar.refreshTitle',
49
+ refreshFailed: 'board.topbar.refreshFailed',
50
+ dismiss: 'board.topbar.dismiss',
51
+ showDead: 'board.topbar.showDead',
52
+ timeWindow: 'board.topbar.timeWindow',
53
+ countWorking: 'board.topbar.countWorking',
54
+ countWaiting: 'board.topbar.countWaiting',
55
+ countTotal: 'board.topbar.countTotal',
56
+ filterByStatusTitle: 'board.topbar.filterByStatusTitle',
57
+ clearStatusFilterTitle: 'board.topbar.clearStatusFilterTitle',
58
+ },
59
+ group: {
60
+ collapseTitle: 'board.group.collapseTitle',
61
+ expandTitle: 'board.group.expandTitle',
62
+ showAll: 'board.group.showAll',
63
+ showLess: 'board.group.showLess',
73
64
  },
74
- /** Session card texts. */
75
65
  card: {
76
- noEvent: '暂无事件',
77
- untitled: '(无标题)',
78
- /** Design §5.3 / SKILL.md wording: statuses are inferred observations. */
79
- observedDisclaimer: '状态为从持久化数据推断的观察值,可能滞后',
80
- observedValue: '观察值: {status}',
81
- lastReconcile: '最近对账: {time}',
82
- neverReconciled: '尚未对账',
66
+ noEvent: 'board.card.noEvent',
67
+ untitled: 'board.card.untitled',
68
+ observedDisclaimer: 'board.card.observedDisclaimer',
69
+ observedValue: 'board.card.observedValue',
70
+ lastReconcile: 'board.card.lastReconcile',
71
+ neverReconciled: 'board.card.neverReconciled',
72
+ copyId: 'board.card.copyId',
73
+ copied: 'board.card.copied',
83
74
  },
84
- /** Relative time templates. */
85
75
  time: {
86
- justNow: '刚刚',
87
- minutesAgo: '{n} 分钟前',
88
- hoursAgo: '{n} 小时前',
89
- daysAgo: '{n} 天前',
76
+ justNow: 'board.time.justNow',
77
+ minutesAgo: 'board.time.minutesAgo',
78
+ hoursAgo: 'board.time.hoursAgo',
90
79
  },
91
- /** Time-window option templates. */
92
80
  timeWindow: {
93
- hours: '{n} 小时',
94
- days: '{n} 天',
81
+ hours: 'board.timeWindow.hours',
82
+ days: 'board.timeWindow.days',
95
83
  },
96
- /** Group header session count. */
97
- groupCount: '{n} 个会话',
98
- /** Sessions with an empty `project` fall into this group (task spec). */
99
- unknownProject: '未知项目',
100
- /** Footer widget. */
84
+ groupCount: 'board.groupCount',
85
+ unknownProject: 'board.unknownProject',
101
86
  widget: {
102
- label: 'Sidecar',
87
+ label: 'board.widget.label',
103
88
  connection: {
104
- ok: '已连接',
105
- degraded: '连接不稳定',
106
- off: '离线',
89
+ ok: 'board.widget.connection.ok',
90
+ degraded: 'board.widget.connection.degraded',
91
+ off: 'board.widget.connection.off',
107
92
  },
108
- working: '{n} 个会话工作中',
93
+ working: 'board.widget.working',
109
94
  },
110
- } as const
95
+ } as const)
111
96
 
112
97
  export type BoardStrings = typeof BOARD_STRINGS