@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.
Files changed (68) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +167 -0
  3. package/cordis.patch.yml +10 -0
  4. package/lib/client.js +8062 -0
  5. package/lib/client.js.map +1 -0
  6. package/lib/index.d.ts +396 -0
  7. package/lib/index.js +4166 -0
  8. package/package.json +101 -0
  9. package/src/analysis.ts +782 -0
  10. package/src/bridge.ts +841 -0
  11. package/src/client/analysis/AnalysisPanel.tsx +191 -0
  12. package/src/client/analysis/analysis.module.css +183 -0
  13. package/src/client/analysis-glue.ts +331 -0
  14. package/src/client/api.ts +380 -0
  15. package/src/client/board/Board.tsx +214 -0
  16. package/src/client/board/board.module.css +302 -0
  17. package/src/client/board/logic.ts +556 -0
  18. package/src/client/board/project-view-logic.ts +361 -0
  19. package/src/client/board/project-view.module.css +307 -0
  20. package/src/client/board/project-view.tsx +189 -0
  21. package/src/client/board/strings.ts +112 -0
  22. package/src/client/commands.ts +484 -0
  23. package/src/client/controller.ts +360 -0
  24. package/src/client/css-modules.d.ts +11 -0
  25. package/src/client/detail/SessionDetail.tsx +270 -0
  26. package/src/client/detail/detail.module.css +433 -0
  27. package/src/client/detail/logic.ts +779 -0
  28. package/src/client/detail/strings.ts +98 -0
  29. package/src/client/detail/transport.ts +175 -0
  30. package/src/client/detail-glue.ts +397 -0
  31. package/src/client/detail-view.module.css +79 -0
  32. package/src/client/detail-view.tsx +233 -0
  33. package/src/client/dsh-tools/LineageTree.tsx +210 -0
  34. package/src/client/dsh-tools/SearchPanel.tsx +169 -0
  35. package/src/client/dsh-tools/dsh-tools.module.css +374 -0
  36. package/src/client/dsh-tools/logic.ts +596 -0
  37. package/src/client/dsh-tools/strings.ts +90 -0
  38. package/src/client/index.ts +315 -0
  39. package/src/client/inject/InjectPanel.tsx +482 -0
  40. package/src/client/inject/inject.module.css +446 -0
  41. package/src/client/inject/logic.ts +516 -0
  42. package/src/client/inject/overlay.module.css +22 -0
  43. package/src/client/inject-glue.ts +171 -0
  44. package/src/client/locales/command.ts +48 -0
  45. package/src/client/locales/en.ts +385 -0
  46. package/src/client/locales/index.ts +123 -0
  47. package/src/client/locales/zh.ts +402 -0
  48. package/src/client/m3-transport.ts +151 -0
  49. package/src/client/mount.tsx +307 -0
  50. package/src/client/project-glue.ts +134 -0
  51. package/src/client/search-glue.ts +143 -0
  52. package/src/client/settings-card.module.css +359 -0
  53. package/src/client/settings-card.tsx +565 -0
  54. package/src/client/settings-glue.ts +130 -0
  55. package/src/client/sidebar-tab.tsx +494 -0
  56. package/src/client/sse.ts +366 -0
  57. package/src/client/widget.tsx +80 -0
  58. package/src/config.ts +193 -0
  59. package/src/dsh-inject.ts +240 -0
  60. package/src/fusion.ts +988 -0
  61. package/src/guard.ts +274 -0
  62. package/src/index.ts +950 -0
  63. package/src/inject-gateway.ts +574 -0
  64. package/src/routes.ts +1133 -0
  65. package/src/send-cli.ts +340 -0
  66. package/src/session-store.ts +184 -0
  67. package/src/skills-provider.ts +293 -0
  68. package/src/supervisor.ts +463 -0
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Cross-agent project correlation view (design §4.e.2 / §5.1 M3, T5.5).
3
+ *
4
+ * Presentation-only and fully controlled: no data fetching, no api/sse
5
+ * imports. The owner fetches `GET <prefix>/projects`, hands the wire
6
+ * groups in as props (the wire shape IS the input view model — camelCase,
7
+ * epoch-ms `lastActivityAt`), and receives session clicks back through
8
+ * `onSelectSession` (pass-through to the detail view, same as Board).
9
+ *
10
+ * Render precedence when there is nothing to show: error > loading >
11
+ * empty state. When groups ARE available, an error renders as a banner
12
+ * above the (possibly stale) content instead of replacing it, and
13
+ * `loading` shows as a quiet header chip — honest degradation without
14
+ * blanking data the user already has.
15
+ */
16
+
17
+ import type { ReactElement } from 'react'
18
+ import {
19
+ buildProjectViewModel,
20
+ PROJECT_VIEW_STRINGS,
21
+ type AgentLaneVM,
22
+ type DerivedProjectGroupVM,
23
+ type DerivedProjectSessionVM,
24
+ type ProjectGroupVM,
25
+ } from './project-view-logic.ts'
26
+ import styles from './project-view.module.css'
27
+
28
+ export interface ProjectViewProps {
29
+ /** Wire groups of `GET projects` (owner passes the response through). */
30
+ groups: ProjectGroupVM[]
31
+ loading: boolean
32
+ /** Human-readable fetch error, or null. */
33
+ error: string | null
34
+ onSelectSession: (sessionId: string) => void
35
+ /** Clock injection for deterministic rendering; defaults to Date.now(). */
36
+ nowMs?: number
37
+ }
38
+
39
+ function SessionRow(props: {
40
+ session: DerivedProjectSessionVM
41
+ onSelect: (sessionId: string) => void
42
+ }): ReactElement {
43
+ const { session, onSelect } = props
44
+ return (
45
+ <button
46
+ type="button"
47
+ className={styles['session']}
48
+ onClick={() => onSelect(session.sessionId)}
49
+ data-testid="agent-sidecar-project-session"
50
+ >
51
+ <span className={styles['badge']} data-tone={session.badge.tone}>
52
+ <span className={styles['dot']} data-tone={session.badge.tone} />
53
+ {session.badge.label}
54
+ {session.badge.attention !== null && (
55
+ <span className={styles['attention']} data-kind={session.badge.attention}>
56
+ {session.badge.attentionLabel}
57
+ </span>
58
+ )}
59
+ </span>
60
+ <span className={styles['sessionTitle']} title={session.title}>
61
+ {session.displayTitle}
62
+ </span>
63
+ {session.live && <span className={styles['liveChip']}>{PROJECT_VIEW_STRINGS.liveChip}</span>}
64
+ <span className={styles['sessionId']} title={session.sessionId}>
65
+ {session.shortId}
66
+ </span>
67
+ <span className={styles['sessionTime']}>{session.relativeTime}</span>
68
+ </button>
69
+ )
70
+ }
71
+
72
+ function AgentLane(props: {
73
+ lane: AgentLaneVM
74
+ onSelect: (sessionId: string) => void
75
+ }): ReactElement {
76
+ const { lane, onSelect } = props
77
+ return (
78
+ <div className={styles['lane']}>
79
+ <div className={styles['laneHead']}>
80
+ <span className={styles['glyph']} aria-hidden>
81
+ {lane.glyph}
82
+ </span>
83
+ {lane.agent}
84
+ </div>
85
+ <div className={styles['laneSessions']}>
86
+ {lane.sessions.map((session) => (
87
+ <SessionRow
88
+ key={`${session.agent}:${session.sessionId}`}
89
+ session={session}
90
+ onSelect={onSelect}
91
+ />
92
+ ))}
93
+ </div>
94
+ </div>
95
+ )
96
+ }
97
+
98
+ function ProjectSection(props: {
99
+ group: DerivedProjectGroupVM
100
+ onSelect: (sessionId: string) => void
101
+ }): ReactElement {
102
+ const { group, onSelect } = props
103
+ return (
104
+ <section className={styles['group']} data-testid="agent-sidecar-project-group">
105
+ <div className={styles['groupHead']}>
106
+ <span
107
+ className={styles['groupName']}
108
+ title={group.fullPath === '' ? undefined : group.fullPath}
109
+ >
110
+ {group.label}
111
+ </span>
112
+ <span className={styles['agentBadges']}>
113
+ {group.agentBadges.map((badge) => (
114
+ <span key={badge.agent} className={styles['agentBadge']} title={badge.agent}>
115
+ <span className={styles['glyph']} aria-hidden>
116
+ {badge.glyph}
117
+ </span>
118
+ {badge.agent}
119
+ </span>
120
+ ))}
121
+ {group.crossAgentLabel !== null && (
122
+ <span className={styles['crossBadge']}>{group.crossAgentLabel}</span>
123
+ )}
124
+ </span>
125
+ <span className={styles['spacer']} />
126
+ <span className={styles['groupMeta']}>{group.sessionCountLabel}</span>
127
+ <span className={styles['groupMeta']}>{group.lastActiveLabel}</span>
128
+ </div>
129
+ <div className={styles['lanes']}>
130
+ {group.lanes.map((lane) => (
131
+ <AgentLane key={lane.agent} lane={lane} onSelect={onSelect} />
132
+ ))}
133
+ </div>
134
+ </section>
135
+ )
136
+ }
137
+
138
+ /** The project correlation view. Pure render of `buildProjectViewModel`. */
139
+ export function ProjectView(props: ProjectViewProps): ReactElement {
140
+ const nowMs = props.nowMs ?? Date.now()
141
+ const vm = buildProjectViewModel({ groups: props.groups, nowMs })
142
+ const hasContent = vm.groups.length > 0
143
+
144
+ return (
145
+ <div className={styles['root']} data-testid="agent-sidecar-project-view">
146
+ <header className={styles['topbar']}>
147
+ <span className={styles['title']}>{PROJECT_VIEW_STRINGS.title}</span>
148
+ <span className={styles['summary']}>{vm.summaryLabel}</span>
149
+ {props.loading && (
150
+ <span className={styles['loadingChip']} role="status">
151
+ {PROJECT_VIEW_STRINGS.loading}
152
+ </span>
153
+ )}
154
+ </header>
155
+
156
+ {props.error !== null && hasContent && (
157
+ <div className={styles['banner']} data-tone="danger" role="alert">
158
+ {PROJECT_VIEW_STRINGS.errorTitle}: {props.error}
159
+ </div>
160
+ )}
161
+
162
+ {hasContent ? (
163
+ vm.groups.map((group) => (
164
+ <ProjectSection
165
+ key={group.key === '' ? '\u0000unknown' : group.key}
166
+ group={group}
167
+ onSelect={props.onSelectSession}
168
+ />
169
+ ))
170
+ ) : props.error !== null ? (
171
+ <div className={styles['empty']} data-kind="error" role="alert">
172
+ <div className={styles['emptyTitle']}>{PROJECT_VIEW_STRINGS.errorTitle}</div>
173
+ <div className={styles['emptyHint']}>{props.error}</div>
174
+ </div>
175
+ ) : props.loading ? (
176
+ <div className={styles['empty']} data-kind="loading" role="status">
177
+ <div className={styles['emptyHint']}>{PROJECT_VIEW_STRINGS.loading}</div>
178
+ </div>
179
+ ) : (
180
+ vm.emptyState !== null && (
181
+ <div className={styles['empty']} data-kind="no-projects">
182
+ <div className={styles['emptyTitle']}>{vm.emptyState.title}</div>
183
+ <div className={styles['emptyHint']}>{vm.emptyState.hint}</div>
184
+ </div>
185
+ )
186
+ )}
187
+ </div>
188
+ )
189
+ }
@@ -0,0 +1,112 @@
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
+ */
15
+
16
+ export const BOARD_STRINGS = {
17
+ /** Session status badge labels (observed values, see hover disclaimer). */
18
+ status: {
19
+ working: '工作中',
20
+ waiting: '等待中',
21
+ idle: '空闲',
22
+ dead: '已结束',
23
+ unknown: '未知',
24
+ },
25
+ /** Badge attention markers; gap outranks stale (per-session > global). */
26
+ attention: {
27
+ gap: '事件缺口',
28
+ stale: '可能滞后',
29
+ },
30
+ /** Daemon supervisor state labels for the top-bar badge. */
31
+ daemon: {
32
+ probe: '探测中',
33
+ adopted: '已连接 · 领养',
34
+ defer: '等待系统服务',
35
+ reprobe: '重新探测中',
36
+ hosting: '正在启动',
37
+ hosted: '已连接 · 托管',
38
+ backoff: '重启退避中',
39
+ failed: '离线',
40
+ },
41
+ /** Stream health indicator labels. */
42
+ stream: {
43
+ ok: '实时流正常',
44
+ degraded: '实时流重连中',
45
+ unknown: '实时流未建立',
46
+ },
47
+ /** Top banner texts (design §5.3: degraded warning / FAILED red banner). */
48
+ banner: {
49
+ daemonFailed: 'sidecar 离线:看板显示最后一次快照,数据不再更新',
50
+ streamDegraded: '实时流重连中,数据可能滞后',
51
+ },
52
+ /** Empty-state guidance blocks. */
53
+ 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 …)开始工作后会自动出现在这里。',
65
+ },
66
+ /** Top bar controls. */
67
+ topbar: {
68
+ title: 'Sidecar 多 agent 看板',
69
+ refresh: '刷新',
70
+ refreshTitle: '手动拉取最新快照',
71
+ showDead: '显示已结束',
72
+ timeWindow: '时间窗',
73
+ },
74
+ /** Session card texts. */
75
+ 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: '尚未对账',
83
+ },
84
+ /** Relative time templates. */
85
+ time: {
86
+ justNow: '刚刚',
87
+ minutesAgo: '{n} 分钟前',
88
+ hoursAgo: '{n} 小时前',
89
+ daysAgo: '{n} 天前',
90
+ },
91
+ /** Time-window option templates. */
92
+ timeWindow: {
93
+ hours: '{n} 小时',
94
+ days: '{n} 天',
95
+ },
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. */
101
+ widget: {
102
+ label: 'Sidecar',
103
+ connection: {
104
+ ok: '已连接',
105
+ degraded: '连接不稳定',
106
+ off: '离线',
107
+ },
108
+ working: '{n} 个会话工作中',
109
+ },
110
+ } as const
111
+
112
+ export type BoardStrings = typeof BOARD_STRINGS