@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,361 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure view-model logic for the cross-agent project correlation view
|
|
3
|
+
* (design §4.e.2 / §5.1 M3, T5.5). No React, no I/O, no data-layer
|
|
4
|
+
* imports — plain values in, plain values out, unit-testable in a bare
|
|
5
|
+
* node environment (same discipline as `logic.ts`).
|
|
6
|
+
*
|
|
7
|
+
* Wire contract (hand-written mirror, per module-ownership rules): the
|
|
8
|
+
* input types below mirror the host's `GET <prefix>/projects` response —
|
|
9
|
+
* `{groups: ProjectGroup[]}` where `ProjectGroup` is fusion.ts
|
|
10
|
+
* `getProjectGroups()` output: `project` (normalized path, '' when
|
|
11
|
+
* unknown), `agents` (distinct, sorted), `sessions` (UnifiedSession[],
|
|
12
|
+
* camelCase, **epoch milliseconds** `lastActivityAt`, most recent first)
|
|
13
|
+
* and the group-level `lastActivityAt`. Only the fields this view renders
|
|
14
|
+
* are mirrored; extra wire fields are structurally ignored.
|
|
15
|
+
*
|
|
16
|
+
* Reuse contract (T2.2, read-only): tone/glyph/relative-time/label tools
|
|
17
|
+
* are imported from `./logic.ts` without touching its exports, so both
|
|
18
|
+
* board views speak the same visual language. Strings NEW to this view
|
|
19
|
+
* live in the module-local {@link PROJECT_VIEW_STRINGS} table (S7 unifies
|
|
20
|
+
* locales later); strings emitted by reused board tools (status labels,
|
|
21
|
+
* relative time, the unknown-project label) intentionally stay with the
|
|
22
|
+
* board table so the two views can never drift apart.
|
|
23
|
+
*
|
|
24
|
+
* @module
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import {
|
|
28
|
+
abbreviateSessionId,
|
|
29
|
+
agentGlyph,
|
|
30
|
+
deriveBadge,
|
|
31
|
+
formatRelativeTime,
|
|
32
|
+
formatTemplate,
|
|
33
|
+
normalizeStatus,
|
|
34
|
+
projectDisplayName,
|
|
35
|
+
statusRank,
|
|
36
|
+
type StatusBadgeVM,
|
|
37
|
+
} from './logic.ts'
|
|
38
|
+
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
// Module-local strings (project-view-only vocabulary; Chinese primary).
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
export const PROJECT_VIEW_STRINGS = {
|
|
44
|
+
/** View heading. */
|
|
45
|
+
title: '项目关联',
|
|
46
|
+
/** Header count summary. */
|
|
47
|
+
summary: '{projects} 个项目 · {sessions} 个会话',
|
|
48
|
+
/** Cross-agent badge, shown when a project hosts 2+ agent kinds. */
|
|
49
|
+
crossAgent: '{n} 种 agent',
|
|
50
|
+
/** Group header session count. */
|
|
51
|
+
sessionCount: '{n} 个会话',
|
|
52
|
+
/** Group header recency line. */
|
|
53
|
+
lastActive: '最近活跃 {time}',
|
|
54
|
+
/** Marker on sessions live in this dsh process (UnifiedSession.live). */
|
|
55
|
+
liveChip: '实时',
|
|
56
|
+
/** Untitled-session fallback. */
|
|
57
|
+
untitled: '(无标题)',
|
|
58
|
+
/** Empty state (no groups at all). */
|
|
59
|
+
empty: {
|
|
60
|
+
title: '暂无项目关联',
|
|
61
|
+
hint: '时间窗内没有跨 agent 的项目活动;agent 在某个项目目录下开始工作后会出现在这里。',
|
|
62
|
+
},
|
|
63
|
+
/** Loading placeholder (first fetch, nothing to show yet). */
|
|
64
|
+
loading: '正在加载项目关联…',
|
|
65
|
+
/** Error banner prefix; the raw error detail is appended by the view. */
|
|
66
|
+
errorTitle: '项目关联加载失败',
|
|
67
|
+
} as const
|
|
68
|
+
|
|
69
|
+
export type ProjectViewStrings = typeof PROJECT_VIEW_STRINGS
|
|
70
|
+
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
// Input view models (hand-written wire mirror, see module doc).
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
|
|
75
|
+
/** One session row of a project group (mirror of `UnifiedSession` subset). */
|
|
76
|
+
export interface ProjectSessionVM {
|
|
77
|
+
agent: string
|
|
78
|
+
sessionId: string
|
|
79
|
+
/** Raw observed status (open vocabulary; sidecar-inferred). */
|
|
80
|
+
status: string
|
|
81
|
+
title: string
|
|
82
|
+
/** Unix epoch MILLISECONDS (fusion already normalized the sidecar seconds). */
|
|
83
|
+
lastActivityAt: number
|
|
84
|
+
/** True when the session is live in this dsh process. */
|
|
85
|
+
live?: boolean
|
|
86
|
+
/** Sidecar-observed seq discontinuity flag. */
|
|
87
|
+
gap?: boolean
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** One wire project group (mirror of fusion.ts `ProjectGroup`). */
|
|
91
|
+
export interface ProjectGroupVM {
|
|
92
|
+
/** Project path; '' for the unknown-project bucket. */
|
|
93
|
+
project: string
|
|
94
|
+
/** Distinct agents as reported by the host (re-derived defensively here). */
|
|
95
|
+
agents: string[]
|
|
96
|
+
sessions: ProjectSessionVM[]
|
|
97
|
+
/** Unix epoch MILLISECONDS of the freshest member session. */
|
|
98
|
+
lastActivityAt: number
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
// Derived view models (what project-view.tsx renders).
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
|
|
105
|
+
/** A session row with every render-ready derivation attached. */
|
|
106
|
+
export interface DerivedProjectSessionVM {
|
|
107
|
+
agent: string
|
|
108
|
+
sessionId: string
|
|
109
|
+
status: string
|
|
110
|
+
title: string
|
|
111
|
+
lastActivityAt: number
|
|
112
|
+
live: boolean
|
|
113
|
+
gap: boolean
|
|
114
|
+
badge: StatusBadgeVM
|
|
115
|
+
glyph: string
|
|
116
|
+
shortId: string
|
|
117
|
+
relativeTime: string
|
|
118
|
+
/** Title with the untitled fallback already resolved. */
|
|
119
|
+
displayTitle: string
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** One per-agent column/section inside a project group. */
|
|
123
|
+
export interface AgentLaneVM {
|
|
124
|
+
agent: string
|
|
125
|
+
glyph: string
|
|
126
|
+
sessions: DerivedProjectSessionVM[]
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** One agent participation marker of the group header. */
|
|
130
|
+
export interface AgentBadgeVM {
|
|
131
|
+
agent: string
|
|
132
|
+
glyph: string
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** One fully derived project section. */
|
|
136
|
+
export interface DerivedProjectGroupVM {
|
|
137
|
+
/** Normalized project path; '' for the unknown-project bucket. */
|
|
138
|
+
key: string
|
|
139
|
+
/** Display name (path basename; board's 未知项目 label for ''). */
|
|
140
|
+
label: string
|
|
141
|
+
/** Full path for the hover title; '' when unknown. */
|
|
142
|
+
fullPath: string
|
|
143
|
+
/** Distinct participating agents, sorted (derived from sessions). */
|
|
144
|
+
agentBadges: AgentBadgeVM[]
|
|
145
|
+
/** Cross-agent marker text; null when a single agent kind participates. */
|
|
146
|
+
crossAgentLabel: string | null
|
|
147
|
+
sessionCount: number
|
|
148
|
+
sessionCountLabel: string
|
|
149
|
+
/** Unix epoch ms of the freshest member session. */
|
|
150
|
+
lastActivityAt: number
|
|
151
|
+
/** '最近活跃 {relative time}'; empty when the group has no finite time. */
|
|
152
|
+
lastActiveLabel: string
|
|
153
|
+
/** Per-agent lanes, sorted by agent name (same order as agentBadges). */
|
|
154
|
+
lanes: AgentLaneVM[]
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Full render model for the project view. */
|
|
158
|
+
export interface ProjectViewModel {
|
|
159
|
+
groups: DerivedProjectGroupVM[]
|
|
160
|
+
/** Non-null exactly when there is no group to render. */
|
|
161
|
+
emptyState: { title: string; hint: string } | null
|
|
162
|
+
projectCount: number
|
|
163
|
+
sessionCount: number
|
|
164
|
+
/** Header summary line ('{projects} 个项目 · {sessions} 个会话'). */
|
|
165
|
+
summaryLabel: string
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ---------------------------------------------------------------------------
|
|
169
|
+
// Normalization helpers.
|
|
170
|
+
// ---------------------------------------------------------------------------
|
|
171
|
+
|
|
172
|
+
const KEY_SEP = '\u0000'
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Group-key normalization, aligned with fusion's correlation key: trim,
|
|
176
|
+
* strip trailing slashes (keeping root '/'), whitespace-only → '' (the
|
|
177
|
+
* unknown bucket). The host already normalizes; this re-run makes the
|
|
178
|
+
* view robust to hand-fed or merged inputs.
|
|
179
|
+
*/
|
|
180
|
+
export function normalizeProjectKey(project: string): string {
|
|
181
|
+
const trimmed = project.trim()
|
|
182
|
+
if (trimmed === '') return ''
|
|
183
|
+
if (trimmed.length > 1 && trimmed.endsWith('/')) {
|
|
184
|
+
const stripped = trimmed.replace(/\/+$/, '')
|
|
185
|
+
return stripped === '' ? '/' : stripped
|
|
186
|
+
}
|
|
187
|
+
return trimmed
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Distinct participating agents of a session list, sorted by name. This
|
|
192
|
+
* is derived from the sessions themselves (not the wire `agents` field)
|
|
193
|
+
* so the badge row can never disagree with the lanes actually rendered —
|
|
194
|
+
* notably after two wire groups merge under one normalized key.
|
|
195
|
+
*/
|
|
196
|
+
export function deriveAgentBadges(
|
|
197
|
+
sessions: ReadonlyArray<{ agent: string }>,
|
|
198
|
+
): AgentBadgeVM[] {
|
|
199
|
+
const names = new Set<string>()
|
|
200
|
+
for (const session of sessions) names.add(session.agent)
|
|
201
|
+
return [...names].sort().map((agent) => ({ agent, glyph: agentGlyph(agent) }))
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Session ordering inside a lane, mirroring the board's card order so
|
|
206
|
+
* both views read the same way: status rank (working first, dead last),
|
|
207
|
+
* then recency, then id as the deterministic tiebreak.
|
|
208
|
+
*/
|
|
209
|
+
export function compareProjectSessions(a: ProjectSessionVM, b: ProjectSessionVM): number {
|
|
210
|
+
const rankDelta = statusRank(normalizeStatus(a.status)) - statusRank(normalizeStatus(b.status))
|
|
211
|
+
if (rankDelta !== 0) return rankDelta
|
|
212
|
+
if (a.lastActivityAt !== b.lastActivityAt) return b.lastActivityAt - a.lastActivityAt
|
|
213
|
+
return a.sessionId.localeCompare(b.sessionId)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function deriveSession(session: ProjectSessionVM, nowMs: number): DerivedProjectSessionVM {
|
|
217
|
+
const live = session.live === true
|
|
218
|
+
const gap = session.gap === true
|
|
219
|
+
return {
|
|
220
|
+
agent: session.agent,
|
|
221
|
+
sessionId: session.sessionId,
|
|
222
|
+
status: session.status,
|
|
223
|
+
title: session.title,
|
|
224
|
+
lastActivityAt: session.lastActivityAt,
|
|
225
|
+
live,
|
|
226
|
+
gap,
|
|
227
|
+
// 'ok' stream health: the project view has no global stream signal,
|
|
228
|
+
// so badge attention reflects only the per-session gap marker.
|
|
229
|
+
badge: deriveBadge(session.status, gap, 'ok'),
|
|
230
|
+
glyph: agentGlyph(session.agent),
|
|
231
|
+
shortId: abbreviateSessionId(session.sessionId),
|
|
232
|
+
relativeTime: formatRelativeTime(session.lastActivityAt, nowMs),
|
|
233
|
+
displayTitle: session.title.trim() === '' ? PROJECT_VIEW_STRINGS.untitled : session.title,
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Split a group's sessions into per-agent lanes. Lanes are sorted by
|
|
239
|
+
* agent name (matching the header badge order); sessions inside a lane
|
|
240
|
+
* follow {@link compareProjectSessions}.
|
|
241
|
+
*/
|
|
242
|
+
export function buildAgentLanes(
|
|
243
|
+
sessions: readonly ProjectSessionVM[],
|
|
244
|
+
nowMs: number,
|
|
245
|
+
): AgentLaneVM[] {
|
|
246
|
+
const byAgent = new Map<string, ProjectSessionVM[]>()
|
|
247
|
+
for (const session of sessions) {
|
|
248
|
+
const lane = byAgent.get(session.agent)
|
|
249
|
+
if (lane === undefined) byAgent.set(session.agent, [session])
|
|
250
|
+
else lane.push(session)
|
|
251
|
+
}
|
|
252
|
+
return [...byAgent.keys()].sort().map((agent) => {
|
|
253
|
+
const members = byAgent.get(agent) ?? []
|
|
254
|
+
members.sort(compareProjectSessions)
|
|
255
|
+
return {
|
|
256
|
+
agent,
|
|
257
|
+
glyph: agentGlyph(agent),
|
|
258
|
+
sessions: members.map((session) => deriveSession(session, nowMs)),
|
|
259
|
+
}
|
|
260
|
+
})
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// ---------------------------------------------------------------------------
|
|
264
|
+
// Full pipeline.
|
|
265
|
+
// ---------------------------------------------------------------------------
|
|
266
|
+
|
|
267
|
+
export interface ProjectViewComputeInput {
|
|
268
|
+
groups: readonly ProjectGroupVM[]
|
|
269
|
+
/** Clock injection (epoch ms) for deterministic derivation. */
|
|
270
|
+
nowMs: number
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* normalize/merge → derive → sort; the one call project-view.tsx renders
|
|
275
|
+
* from.
|
|
276
|
+
*
|
|
277
|
+
* Normalization: groups collapsing onto the same normalized key are
|
|
278
|
+
* merged; duplicate sessions (same agent + sessionId) within a merged
|
|
279
|
+
* group keep the freshest copy. Group order is `lastActivityAt`
|
|
280
|
+
* descending (recomputed from member sessions, so a stale wire value
|
|
281
|
+
* cannot misplace a group), with the unknown-project bucket ('') always
|
|
282
|
+
* last — same rule as the board.
|
|
283
|
+
*/
|
|
284
|
+
export function buildProjectViewModel(input: ProjectViewComputeInput): ProjectViewModel {
|
|
285
|
+
const { groups, nowMs } = input
|
|
286
|
+
|
|
287
|
+
interface Bucket {
|
|
288
|
+
sessions: Map<string, ProjectSessionVM>
|
|
289
|
+
wireLastActivityAt: number
|
|
290
|
+
}
|
|
291
|
+
const buckets = new Map<string, Bucket>()
|
|
292
|
+
for (const group of groups) {
|
|
293
|
+
const key = normalizeProjectKey(group.project)
|
|
294
|
+
let bucket = buckets.get(key)
|
|
295
|
+
if (bucket === undefined) {
|
|
296
|
+
bucket = { sessions: new Map(), wireLastActivityAt: Number.NEGATIVE_INFINITY }
|
|
297
|
+
buckets.set(key, bucket)
|
|
298
|
+
}
|
|
299
|
+
if (Number.isFinite(group.lastActivityAt)) {
|
|
300
|
+
bucket.wireLastActivityAt = Math.max(bucket.wireLastActivityAt, group.lastActivityAt)
|
|
301
|
+
}
|
|
302
|
+
for (const session of group.sessions) {
|
|
303
|
+
const id = `${session.agent}${KEY_SEP}${session.sessionId}`
|
|
304
|
+
const existing = bucket.sessions.get(id)
|
|
305
|
+
if (existing === undefined || session.lastActivityAt > existing.lastActivityAt) {
|
|
306
|
+
bucket.sessions.set(id, session)
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const derived: DerivedProjectGroupVM[] = []
|
|
312
|
+
let sessionCount = 0
|
|
313
|
+
for (const [key, bucket] of buckets) {
|
|
314
|
+
const sessions = [...bucket.sessions.values()]
|
|
315
|
+
sessionCount += sessions.length
|
|
316
|
+
const memberMax = sessions.reduce(
|
|
317
|
+
(max, s) => (Number.isFinite(s.lastActivityAt) ? Math.max(max, s.lastActivityAt) : max),
|
|
318
|
+
Number.NEGATIVE_INFINITY,
|
|
319
|
+
)
|
|
320
|
+
const lastActivityAt = Math.max(memberMax, bucket.wireLastActivityAt)
|
|
321
|
+
const agentBadges = deriveAgentBadges(sessions)
|
|
322
|
+
derived.push({
|
|
323
|
+
key,
|
|
324
|
+
label: projectDisplayName(key),
|
|
325
|
+
fullPath: key,
|
|
326
|
+
agentBadges,
|
|
327
|
+
crossAgentLabel:
|
|
328
|
+
agentBadges.length > 1
|
|
329
|
+
? formatTemplate(PROJECT_VIEW_STRINGS.crossAgent, { n: agentBadges.length })
|
|
330
|
+
: null,
|
|
331
|
+
sessionCount: sessions.length,
|
|
332
|
+
sessionCountLabel: formatTemplate(PROJECT_VIEW_STRINGS.sessionCount, {
|
|
333
|
+
n: sessions.length,
|
|
334
|
+
}),
|
|
335
|
+
lastActivityAt: Number.isFinite(lastActivityAt) ? lastActivityAt : 0,
|
|
336
|
+
lastActiveLabel: Number.isFinite(lastActivityAt)
|
|
337
|
+
? formatTemplate(PROJECT_VIEW_STRINGS.lastActive, {
|
|
338
|
+
time: formatRelativeTime(lastActivityAt, nowMs),
|
|
339
|
+
})
|
|
340
|
+
: '',
|
|
341
|
+
lanes: buildAgentLanes(sessions, nowMs),
|
|
342
|
+
})
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
derived.sort((a, b) => {
|
|
346
|
+
if (a.key === '') return b.key === '' ? 0 : 1
|
|
347
|
+
if (b.key === '') return -1
|
|
348
|
+
return b.lastActivityAt - a.lastActivityAt || a.key.localeCompare(b.key)
|
|
349
|
+
})
|
|
350
|
+
|
|
351
|
+
return {
|
|
352
|
+
groups: derived,
|
|
353
|
+
emptyState: derived.length === 0 ? { ...PROJECT_VIEW_STRINGS.empty } : null,
|
|
354
|
+
projectCount: derived.length,
|
|
355
|
+
sessionCount,
|
|
356
|
+
summaryLabel: formatTemplate(PROJECT_VIEW_STRINGS.summary, {
|
|
357
|
+
projects: derived.length,
|
|
358
|
+
sessions: sessionCount,
|
|
359
|
+
}),
|
|
360
|
+
}
|
|
361
|
+
}
|
|
@@ -0,0 +1,307 @@
|
|
|
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
|
+
*/
|
|
7
|
+
|
|
8
|
+
.root {
|
|
9
|
+
display: flex;
|
|
10
|
+
flex-direction: column;
|
|
11
|
+
gap: 12px;
|
|
12
|
+
height: 100%;
|
|
13
|
+
min-height: 320px;
|
|
14
|
+
padding: 16px 20px;
|
|
15
|
+
box-sizing: border-box;
|
|
16
|
+
overflow-y: auto;
|
|
17
|
+
color: var(--dsw-alias-label-primary, #1f2328);
|
|
18
|
+
background: var(--dsw-alias-bg-base, transparent);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/* ------------------------------------------------------------- top bar */
|
|
22
|
+
|
|
23
|
+
.topbar {
|
|
24
|
+
display: flex;
|
|
25
|
+
align-items: center;
|
|
26
|
+
gap: 10px;
|
|
27
|
+
flex-wrap: wrap;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.title {
|
|
31
|
+
font-size: 15px;
|
|
32
|
+
font-weight: 650;
|
|
33
|
+
color: var(--dsw-alias-label-primary, #1f2328);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.summary {
|
|
37
|
+
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));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* -------------------------------------------------------------- banner */
|
|
51
|
+
|
|
52
|
+
.banner {
|
|
53
|
+
font-size: 12px;
|
|
54
|
+
line-height: 18px;
|
|
55
|
+
padding: 6px 10px;
|
|
56
|
+
border-radius: 8px;
|
|
57
|
+
border: 1px solid var(--dsw-alias-border-l1, rgba(0, 0, 0, 0.08));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.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));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/* -------------------------------------------------------- empty states */
|
|
67
|
+
|
|
68
|
+
.empty {
|
|
69
|
+
display: flex;
|
|
70
|
+
flex-direction: column;
|
|
71
|
+
align-items: center;
|
|
72
|
+
justify-content: center;
|
|
73
|
+
gap: 6px;
|
|
74
|
+
flex: 1;
|
|
75
|
+
min-height: 160px;
|
|
76
|
+
text-align: center;
|
|
77
|
+
padding: 24px;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.emptyTitle {
|
|
81
|
+
font-size: 14px;
|
|
82
|
+
font-weight: 600;
|
|
83
|
+
color: var(--dsw-alias-label-primary, #1f2328);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.emptyHint {
|
|
87
|
+
font-size: 12px;
|
|
88
|
+
max-width: 420px;
|
|
89
|
+
line-height: 20px;
|
|
90
|
+
color: var(--dsw-alias-label-secondary, #57606a);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/* -------------------------------------------------------- group blocks */
|
|
94
|
+
|
|
95
|
+
.group {
|
|
96
|
+
display: flex;
|
|
97
|
+
flex-direction: column;
|
|
98
|
+
gap: 8px;
|
|
99
|
+
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));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.groupHead {
|
|
106
|
+
display: flex;
|
|
107
|
+
align-items: baseline;
|
|
108
|
+
gap: 8px;
|
|
109
|
+
flex-wrap: wrap;
|
|
110
|
+
min-width: 0;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.groupName {
|
|
114
|
+
font-size: 13px;
|
|
115
|
+
font-weight: 600;
|
|
116
|
+
color: var(--dsw-alias-label-primary, #1f2328);
|
|
117
|
+
overflow: hidden;
|
|
118
|
+
text-overflow: ellipsis;
|
|
119
|
+
white-space: nowrap;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.agentBadges {
|
|
123
|
+
display: inline-flex;
|
|
124
|
+
align-items: center;
|
|
125
|
+
gap: 4px;
|
|
126
|
+
flex-wrap: wrap;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.agentBadge {
|
|
130
|
+
display: inline-flex;
|
|
131
|
+
align-items: center;
|
|
132
|
+
gap: 4px;
|
|
133
|
+
font-size: 11px;
|
|
134
|
+
line-height: 18px;
|
|
135
|
+
padding: 0 7px;
|
|
136
|
+
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);
|
|
140
|
+
white-space: nowrap;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.crossBadge {
|
|
144
|
+
font-size: 11px;
|
|
145
|
+
line-height: 18px;
|
|
146
|
+
padding: 0 7px;
|
|
147
|
+
border-radius: 999px;
|
|
148
|
+
color: var(--dsw-alias-static-white, #fff);
|
|
149
|
+
background: var(--dsw-alias-brand-primary, #4d6bfe);
|
|
150
|
+
white-space: nowrap;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.spacer {
|
|
154
|
+
flex: 1;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.groupMeta {
|
|
158
|
+
font-size: 11px;
|
|
159
|
+
flex: none;
|
|
160
|
+
color: var(--dsw-alias-label-tertiary, #6e7781);
|
|
161
|
+
white-space: nowrap;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/* --------------------------------------------------------- agent lanes */
|
|
165
|
+
|
|
166
|
+
.lanes {
|
|
167
|
+
display: flex;
|
|
168
|
+
flex-direction: column;
|
|
169
|
+
gap: 6px;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.lane {
|
|
173
|
+
display: flex;
|
|
174
|
+
flex-direction: column;
|
|
175
|
+
gap: 4px;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.laneHead {
|
|
179
|
+
display: inline-flex;
|
|
180
|
+
align-items: center;
|
|
181
|
+
gap: 5px;
|
|
182
|
+
font-size: 12px;
|
|
183
|
+
font-weight: 600;
|
|
184
|
+
color: var(--dsw-alias-label-primary, #1f2328);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.glyph {
|
|
188
|
+
flex: none;
|
|
189
|
+
color: var(--dsw-alias-brand-primary, #4d6bfe);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.laneSessions {
|
|
193
|
+
display: flex;
|
|
194
|
+
flex-direction: column;
|
|
195
|
+
gap: 4px;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/* -------------------------------------------------------- session rows */
|
|
199
|
+
|
|
200
|
+
.session {
|
|
201
|
+
display: flex;
|
|
202
|
+
align-items: center;
|
|
203
|
+
gap: 8px;
|
|
204
|
+
min-width: 0;
|
|
205
|
+
text-align: left;
|
|
206
|
+
font-family: inherit;
|
|
207
|
+
font-size: 12px;
|
|
208
|
+
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);
|
|
213
|
+
cursor: pointer;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.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));
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.session:focus-visible {
|
|
222
|
+
outline: 2px solid var(--dsw-alias-brand-primary, #4d6bfe);
|
|
223
|
+
outline-offset: 1px;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.badge {
|
|
227
|
+
display: inline-flex;
|
|
228
|
+
align-items: center;
|
|
229
|
+
gap: 5px;
|
|
230
|
+
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
|
+
}
|
|
240
|
+
|
|
241
|
+
.dot {
|
|
242
|
+
width: 7px;
|
|
243
|
+
height: 7px;
|
|
244
|
+
border-radius: 50%;
|
|
245
|
+
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);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
.dot[data-tone='neutral'] {
|
|
262
|
+
background: var(--dsw-alias-label-tertiary, #6e7781);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
.dot[data-tone='muted'] {
|
|
266
|
+
background: var(--dsw-alias-label-dimmed, #8c959f);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.attention {
|
|
270
|
+
font-size: 11px;
|
|
271
|
+
color: var(--dsw-alias-state-warn-primary, #9a6700);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.attention[data-kind='gap'] {
|
|
275
|
+
color: var(--dsw-alias-state-error-primary, #cf222e);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
.sessionTitle {
|
|
279
|
+
min-width: 0;
|
|
280
|
+
overflow: hidden;
|
|
281
|
+
text-overflow: ellipsis;
|
|
282
|
+
white-space: nowrap;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
.liveChip {
|
|
286
|
+
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));
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.sessionId {
|
|
296
|
+
flex: none;
|
|
297
|
+
font-size: 11px;
|
|
298
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
299
|
+
color: var(--dsw-alias-label-tertiary, #6e7781);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.sessionTime {
|
|
303
|
+
flex: none;
|
|
304
|
+
margin-left: auto;
|
|
305
|
+
font-size: 11px;
|
|
306
|
+
color: var(--dsw-alias-label-caption, var(--dsw-alias-label-tertiary, #6e7781));
|
|
307
|
+
}
|