@hybridlabor-api/bdb-synapse 1.0.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 +173 -0
- package/bin/synapse +0 -0
- package/cmd/rubriceval/main.go +308 -0
- package/cmd/synapse/main.go +280 -0
- package/cmd/synapse/main_test.go +16 -0
- package/go.mod +7 -0
- package/go.sum +6 -0
- package/internal/adapter/adapter.go +1117 -0
- package/internal/adapter/adapter_test.go +518 -0
- package/internal/adapter/agy/adapter.go +193 -0
- package/internal/adapter/claudecode/adapter.go +415 -0
- package/internal/adapter/claudecode/adapter_test.go +260 -0
- package/internal/adapter/claudecode/agents.go +387 -0
- package/internal/adapter/claudecode/agents_test.go +480 -0
- package/internal/adapter/claudecode/summary_inputs_test.go +19 -0
- package/internal/adapter/codex/adapter.go +919 -0
- package/internal/adapter/codex/adapter_test.go +920 -0
- package/internal/adapter/codex/agents.go +401 -0
- package/internal/adapter/codex/agents_test.go +610 -0
- package/internal/adapter/codex/summary_inputs_test.go +20 -0
- package/internal/adapter/pi/adapter.go +483 -0
- package/internal/adapter/pi/adapter_test.go +517 -0
- package/internal/citymap/builder.go +1124 -0
- package/internal/citymap/builder_test.go +818 -0
- package/internal/judge/cache.go +180 -0
- package/internal/judge/cli.go +240 -0
- package/internal/judge/cli_test.go +68 -0
- package/internal/judge/fresh_summary_test.go +37 -0
- package/internal/judge/input.go +233 -0
- package/internal/judge/judge.go +416 -0
- package/internal/judge/judge_test.go +288 -0
- package/internal/judge/prompt.go +129 -0
- package/internal/judge/rubric.go +275 -0
- package/internal/judge/rubric_test.go +642 -0
- package/internal/model/agent.go +63 -0
- package/internal/model/agent_schema_test.go +86 -0
- package/internal/model/agent_test.go +64 -0
- package/internal/model/model.go +175 -0
- package/internal/model/report.go +166 -0
- package/internal/model/stats.go +151 -0
- package/internal/model/stats_test.go +89 -0
- package/internal/model/trace_schema_test.go +67 -0
- package/internal/server/analyze.go +286 -0
- package/internal/server/analyze_test.go +297 -0
- package/internal/server/codex_index_test.go +40 -0
- package/internal/server/hardening_test.go +147 -0
- package/internal/server/reportindex.go +91 -0
- package/internal/server/reportindex_test.go +116 -0
- package/internal/server/server.go +1099 -0
- package/internal/server/server_test.go +1389 -0
- package/internal/server/static/assets/fraunces-latin-ext-standard-italic-CGbN9UgK.woff2 +0 -0
- package/internal/server/static/assets/fraunces-latin-ext-standard-normal-CJcjJNj7.woff2 +0 -0
- package/internal/server/static/assets/fraunces-latin-standard-italic-lSdLDfvT.woff2 +0 -0
- package/internal/server/static/assets/fraunces-latin-standard-normal-DihXLNYH.woff2 +0 -0
- package/internal/server/static/assets/fraunces-vietnamese-standard-italic-DxWqP7Ku.woff2 +0 -0
- package/internal/server/static/assets/fraunces-vietnamese-standard-normal-Czevyj-6.woff2 +0 -0
- package/internal/server/static/assets/index-BNoY_BiB.css +1 -0
- package/internal/server/static/assets/index-C_adLrJr.js +3 -0
- package/internal/server/static/assets/react-gcHzaSmV.js +10 -0
- package/internal/server/static/assets/schibsted-grotesk-latin-ext-wght-normal-hsMS0n0O.woff2 +0 -0
- package/internal/server/static/assets/schibsted-grotesk-latin-wght-normal-Bb8VGrTG.woff2 +0 -0
- package/internal/server/static/assets/three-DnGjZfD1.js +4012 -0
- package/internal/server/static/index.html +26 -0
- package/internal/server/tracestore.go +173 -0
- package/internal/server/tracestore_test.go +51 -0
- package/internal/textutil/truncate.go +30 -0
- package/internal/textutil/truncate_test.go +39 -0
- package/package.json +35 -0
- package/web/e2e/agent-lens.spec.ts +688 -0
- package/web/index.html +23 -0
- package/web/package-lock.json +1933 -0
- package/web/package.json +33 -0
- package/web/playwright.config.ts +24 -0
- package/web/src/App.tsx +876 -0
- package/web/src/api/client.ts +74 -0
- package/web/src/main.tsx +12 -0
- package/web/src/playback/recorder.ts +160 -0
- package/web/src/playback/reducer.ts +91 -0
- package/web/src/scene/CityScene.tsx +638 -0
- package/web/src/scene/TreeScene.tsx +656 -0
- package/web/src/scene/dirLabels.ts +145 -0
- package/web/src/scene/sceneUtils.ts +144 -0
- package/web/src/scene/textures.ts +60 -0
- package/web/src/scene/trail.ts +79 -0
- package/web/src/scene/treeLayout.ts +169 -0
- package/web/src/state/filters.ts +40 -0
- package/web/src/state/store.ts +83 -0
- package/web/src/styles.css +2565 -0
- package/web/src/types.ts +315 -0
- package/web/src/ui/AgentsPanel.tsx +376 -0
- package/web/src/ui/Dock.tsx +104 -0
- package/web/src/ui/Hud.tsx +335 -0
- package/web/src/ui/Inspector.tsx +107 -0
- package/web/src/ui/LogoMark.tsx +38 -0
- package/web/src/ui/ReportPanel.tsx +491 -0
- package/web/src/ui/SessionRail.tsx +316 -0
- package/web/src/ui/Timeline.tsx +458 -0
- package/web/src/ui/ViewPanel.tsx +45 -0
- package/web/src/ui/shortcuts.ts +4 -0
- package/web/tsconfig.json +21 -0
- package/web/vite.config.ts +28 -0
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import { Eye, EyeOff, FolderOpen, PanelLeftClose, RefreshCw, Search } from "lucide-react";
|
|
2
|
+
import { memo, useEffect, useMemo, useRef, useState } from "react";
|
|
3
|
+
import { sessionVisible } from "../state/filters";
|
|
4
|
+
import { LogoMark } from "./LogoMark";
|
|
5
|
+
import { toggleRailShortcut } from "./shortcuts";
|
|
6
|
+
import type { SessionMeta } from "../types";
|
|
7
|
+
|
|
8
|
+
interface SessionRailProps {
|
|
9
|
+
sessions: SessionMeta[];
|
|
10
|
+
activeKey?: string;
|
|
11
|
+
loading: boolean;
|
|
12
|
+
hideEmpty: boolean;
|
|
13
|
+
harnessFilter?: string;
|
|
14
|
+
collapsed: boolean;
|
|
15
|
+
onSelect: (key: string) => void;
|
|
16
|
+
onRefresh: () => void;
|
|
17
|
+
onHideEmptyChange: (hide: boolean) => void;
|
|
18
|
+
onHarnessFilterChange: (harness?: string) => void;
|
|
19
|
+
onCollapse: () => void;
|
|
20
|
+
// opens the static full-repo map for a repo path in a new tab
|
|
21
|
+
onOpenMap: (repo: string) => void;
|
|
22
|
+
// the active session's repo, offered as the popover's one-click choice
|
|
23
|
+
activeRepo?: string;
|
|
24
|
+
// while a video export records, session switching is locked so it can't swap
|
|
25
|
+
// the canvas or playhead out from under the recorder
|
|
26
|
+
locked?: boolean;
|
|
27
|
+
// the panel's authoritative (digest-based) status for the active session;
|
|
28
|
+
// undefined = unknown (keep the list's approximate badge), null = no report
|
|
29
|
+
activeReportState?: "running" | "done" | "stale" | "failed" | null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// memo: the app re-renders every playback tick; the rail's props only change
|
|
33
|
+
// on scans, session switches, and filter changes
|
|
34
|
+
export const SessionRail = memo(function SessionRail({
|
|
35
|
+
sessions,
|
|
36
|
+
activeKey,
|
|
37
|
+
loading,
|
|
38
|
+
hideEmpty,
|
|
39
|
+
harnessFilter,
|
|
40
|
+
collapsed,
|
|
41
|
+
onSelect,
|
|
42
|
+
onRefresh,
|
|
43
|
+
onHideEmptyChange,
|
|
44
|
+
onHarnessFilterChange,
|
|
45
|
+
onCollapse,
|
|
46
|
+
onOpenMap,
|
|
47
|
+
activeRepo,
|
|
48
|
+
locked = false,
|
|
49
|
+
activeReportState
|
|
50
|
+
}: SessionRailProps) {
|
|
51
|
+
const [query, setQuery] = useState("");
|
|
52
|
+
const [repoPath, setRepoPath] = useState("");
|
|
53
|
+
const [mapOpen, setMapOpen] = useState(false);
|
|
54
|
+
const mapPopRef = useRef<HTMLDivElement | null>(null);
|
|
55
|
+
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
if (!mapOpen) return;
|
|
58
|
+
const onPointerDown = (event: PointerEvent) => {
|
|
59
|
+
if (mapPopRef.current?.contains(event.target as Node)) return;
|
|
60
|
+
setMapOpen(false);
|
|
61
|
+
};
|
|
62
|
+
const onKeyDown = (event: KeyboardEvent) => {
|
|
63
|
+
if (event.key === "Escape") setMapOpen(false);
|
|
64
|
+
};
|
|
65
|
+
document.addEventListener("pointerdown", onPointerDown);
|
|
66
|
+
document.addEventListener("keydown", onKeyDown);
|
|
67
|
+
return () => {
|
|
68
|
+
document.removeEventListener("pointerdown", onPointerDown);
|
|
69
|
+
document.removeEventListener("keydown", onKeyDown);
|
|
70
|
+
};
|
|
71
|
+
}, [mapOpen]);
|
|
72
|
+
const harnesses = useMemo(() => [...new Set(sessions.map((s) => s.harness))].sort(), [sessions]);
|
|
73
|
+
const emptyCount = useMemo(() => sessions.filter((s) => s.eventCount === 0).length, [sessions]);
|
|
74
|
+
// a persisted filter can name a harness with no sessions this scan; treating
|
|
75
|
+
// it as "all" avoids an empty list with no visible chip to clear it
|
|
76
|
+
const effectiveHarness = harnessFilter && harnesses.includes(harnessFilter) ? harnessFilter : undefined;
|
|
77
|
+
const shown = useMemo(() => {
|
|
78
|
+
const q = query.trim().toLowerCase();
|
|
79
|
+
return sessions.filter((session) => {
|
|
80
|
+
if (!sessionVisible(session, { hideEmpty, harness: effectiveHarness }, activeKey)) return false;
|
|
81
|
+
if (!q) return true;
|
|
82
|
+
return `${session.title ?? ""} ${session.id} ${session.gitBranch ?? ""} ${session.harness}`
|
|
83
|
+
.toLowerCase()
|
|
84
|
+
.includes(q);
|
|
85
|
+
});
|
|
86
|
+
}, [sessions, query, hideEmpty, effectiveHarness, activeKey]);
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<aside className={collapsed ? "session-rail collapsed" : "session-rail"}>
|
|
90
|
+
<div className="rail-head">
|
|
91
|
+
<h1 className="wordmark">
|
|
92
|
+
<LogoMark />
|
|
93
|
+
<span>
|
|
94
|
+
Synapse<span className="spark">.</span>
|
|
95
|
+
</span>
|
|
96
|
+
</h1>
|
|
97
|
+
<div className="rail-head-actions">
|
|
98
|
+
<div className="rail-map" ref={mapPopRef}>
|
|
99
|
+
<button
|
|
100
|
+
className="icon-btn"
|
|
101
|
+
onClick={() => setMapOpen((open) => !open)}
|
|
102
|
+
aria-expanded={mapOpen}
|
|
103
|
+
title="Open a repository map"
|
|
104
|
+
aria-label="Open a repository map"
|
|
105
|
+
>
|
|
106
|
+
<FolderOpen size={15} />
|
|
107
|
+
</button>
|
|
108
|
+
{mapOpen ? (
|
|
109
|
+
<div className="rail-map-pop">
|
|
110
|
+
{activeRepo ? (
|
|
111
|
+
<button
|
|
112
|
+
className="rail-map-primary"
|
|
113
|
+
onClick={() => {
|
|
114
|
+
onOpenMap(activeRepo);
|
|
115
|
+
setMapOpen(false);
|
|
116
|
+
}}
|
|
117
|
+
title={`Open the map of ${activeRepo}`}
|
|
118
|
+
>
|
|
119
|
+
<FolderOpen size={14} aria-hidden />
|
|
120
|
+
<span className="rail-map-primary-text">
|
|
121
|
+
<span className="rail-map-primary-name">{repoBasename(activeRepo)}</span>
|
|
122
|
+
{/* the leading LRM pins the path's neutral "/" runs to
|
|
123
|
+
LTR order inside the RTL ellipsis-at-start trick */}
|
|
124
|
+
<span className="rail-map-primary-path">{"\u200E" + activeRepo}</span>
|
|
125
|
+
</span>
|
|
126
|
+
</button>
|
|
127
|
+
) : null}
|
|
128
|
+
{activeRepo ? (
|
|
129
|
+
<div className="rail-map-divider" aria-hidden>
|
|
130
|
+
<span>or open any repository</span>
|
|
131
|
+
</div>
|
|
132
|
+
) : (
|
|
133
|
+
<p className="rail-map-label">Open a repository map</p>
|
|
134
|
+
)}
|
|
135
|
+
<form
|
|
136
|
+
className="rail-map-form"
|
|
137
|
+
onSubmit={(e) => {
|
|
138
|
+
e.preventDefault();
|
|
139
|
+
const path = repoPath.trim();
|
|
140
|
+
if (path) {
|
|
141
|
+
onOpenMap(path);
|
|
142
|
+
setMapOpen(false);
|
|
143
|
+
}
|
|
144
|
+
}}
|
|
145
|
+
>
|
|
146
|
+
<input
|
|
147
|
+
type="text"
|
|
148
|
+
className="rail-map-input"
|
|
149
|
+
placeholder="/path/to/repo"
|
|
150
|
+
value={repoPath}
|
|
151
|
+
onChange={(e) => setRepoPath(e.currentTarget.value)}
|
|
152
|
+
spellCheck={false}
|
|
153
|
+
/>
|
|
154
|
+
<button type="submit" className="rail-map-go" disabled={repoPath.trim() === ""}>
|
|
155
|
+
Open
|
|
156
|
+
</button>
|
|
157
|
+
</form>
|
|
158
|
+
</div>
|
|
159
|
+
) : null}
|
|
160
|
+
</div>
|
|
161
|
+
<button
|
|
162
|
+
className="icon-btn"
|
|
163
|
+
onClick={onRefresh}
|
|
164
|
+
disabled={locked}
|
|
165
|
+
title="Rescan sessions"
|
|
166
|
+
aria-label="Rescan sessions"
|
|
167
|
+
>
|
|
168
|
+
<RefreshCw size={15} />
|
|
169
|
+
</button>
|
|
170
|
+
<button
|
|
171
|
+
className="icon-btn"
|
|
172
|
+
onClick={onCollapse}
|
|
173
|
+
title={`Hide sidebar (${toggleRailShortcut})`}
|
|
174
|
+
aria-label="Hide session sidebar"
|
|
175
|
+
>
|
|
176
|
+
<PanelLeftClose size={15} />
|
|
177
|
+
</button>
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
180
|
+
<div className="rail-controls">
|
|
181
|
+
<label className="rail-filter">
|
|
182
|
+
<Search size={14} aria-hidden />
|
|
183
|
+
<input
|
|
184
|
+
type="search"
|
|
185
|
+
placeholder="Filter sessions"
|
|
186
|
+
value={query}
|
|
187
|
+
onChange={(e) => setQuery(e.currentTarget.value)}
|
|
188
|
+
aria-label="Filter sessions"
|
|
189
|
+
/>
|
|
190
|
+
</label>
|
|
191
|
+
{harnesses.length > 1 || emptyCount > 0 ? (
|
|
192
|
+
<div className="rail-chips" role="group" aria-label="Session filters">
|
|
193
|
+
{harnesses.length > 1 ? (
|
|
194
|
+
<>
|
|
195
|
+
<button
|
|
196
|
+
className={effectiveHarness === undefined ? "chip active" : "chip"}
|
|
197
|
+
onClick={() => onHarnessFilterChange(undefined)}
|
|
198
|
+
>
|
|
199
|
+
all
|
|
200
|
+
</button>
|
|
201
|
+
{harnesses.map((harness) => (
|
|
202
|
+
<button
|
|
203
|
+
key={harness}
|
|
204
|
+
className={effectiveHarness === harness ? "chip active" : "chip"}
|
|
205
|
+
onClick={() => onHarnessFilterChange(harness)}
|
|
206
|
+
>
|
|
207
|
+
{harnessLabel(harness)}
|
|
208
|
+
</button>
|
|
209
|
+
))}
|
|
210
|
+
</>
|
|
211
|
+
) : null}
|
|
212
|
+
{emptyCount > 0 ? (
|
|
213
|
+
<button
|
|
214
|
+
className={hideEmpty ? "eye-toggle" : "eye-toggle showing"}
|
|
215
|
+
onClick={() => onHideEmptyChange(!hideEmpty)}
|
|
216
|
+
aria-pressed={!hideEmpty}
|
|
217
|
+
title={
|
|
218
|
+
hideEmpty ? `Show ${emptyCount} empty sessions` : `Hide ${emptyCount} empty sessions`
|
|
219
|
+
}
|
|
220
|
+
aria-label={
|
|
221
|
+
hideEmpty ? `Show ${emptyCount} empty sessions` : `Hide ${emptyCount} empty sessions`
|
|
222
|
+
}
|
|
223
|
+
>
|
|
224
|
+
{hideEmpty ? <EyeOff size={13} aria-hidden /> : <Eye size={13} aria-hidden />}
|
|
225
|
+
</button>
|
|
226
|
+
) : null}
|
|
227
|
+
</div>
|
|
228
|
+
) : null}
|
|
229
|
+
</div>
|
|
230
|
+
<div className="session-list" aria-busy={loading}>
|
|
231
|
+
{shown.map((session) => (
|
|
232
|
+
<button
|
|
233
|
+
key={session.key}
|
|
234
|
+
className={session.key === activeKey ? "session-row active" : "session-row"}
|
|
235
|
+
onClick={() => onSelect(session.key)}
|
|
236
|
+
disabled={locked}
|
|
237
|
+
>
|
|
238
|
+
<span className="session-title">{session.title || session.id}</span>
|
|
239
|
+
<span className="session-meta">
|
|
240
|
+
<span className="session-meta-text">
|
|
241
|
+
{harnessLabel(session.harness)} · {session.eventCount}{" "}
|
|
242
|
+
{session.eventCount === 1 ? "call" : "calls"}
|
|
243
|
+
{session.gitBranch ? ` · ${session.gitBranch}` : ""}
|
|
244
|
+
{session.endedAt ? ` · ${shortDate(session.endedAt)}` : ""}
|
|
245
|
+
</span>
|
|
246
|
+
{(() => {
|
|
247
|
+
// the panel's digest-based status outranks the list's cheap
|
|
248
|
+
// event-count grading for the active session
|
|
249
|
+
const evalState =
|
|
250
|
+
session.key === activeKey && activeReportState !== undefined
|
|
251
|
+
? activeReportState
|
|
252
|
+
: session.reportState;
|
|
253
|
+
return evalState ? (
|
|
254
|
+
<span
|
|
255
|
+
className={`rail-eval rail-eval-${evalState}`}
|
|
256
|
+
title={evalHint(evalState)}
|
|
257
|
+
aria-label={evalHint(evalState)}
|
|
258
|
+
>
|
|
259
|
+
{evalState === "running" ? "evaluating" : ""}
|
|
260
|
+
</span>
|
|
261
|
+
) : null;
|
|
262
|
+
})()}
|
|
263
|
+
</span>
|
|
264
|
+
</button>
|
|
265
|
+
))}
|
|
266
|
+
{shown.length === 0 ? (
|
|
267
|
+
<p className="muted" style={{ padding: "10px 8px" }}>
|
|
268
|
+
{loading && sessions.length === 0 ? "Scanning sessions…" : "No matching sessions."}
|
|
269
|
+
</p>
|
|
270
|
+
) : null}
|
|
271
|
+
</div>
|
|
272
|
+
<div className="rail-foot">
|
|
273
|
+
{shown.length === sessions.length
|
|
274
|
+
? `${sessions.length} session${sessions.length === 1 ? "" : "s"}`
|
|
275
|
+
: `${shown.length} of ${sessions.length} sessions`}
|
|
276
|
+
</div>
|
|
277
|
+
</aside>
|
|
278
|
+
);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
function repoBasename(path: string): string {
|
|
282
|
+
const clean = path.replace(/\/+$/, "");
|
|
283
|
+
return clean.slice(clean.lastIndexOf("/") + 1) || clean;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function evalHint(state: "running" | "done" | "stale" | "failed"): string {
|
|
287
|
+
switch (state) {
|
|
288
|
+
case "running":
|
|
289
|
+
return "Evaluation in progress";
|
|
290
|
+
case "done":
|
|
291
|
+
return "Evaluation ready";
|
|
292
|
+
case "stale":
|
|
293
|
+
return "Evaluation ready, but the session has grown since";
|
|
294
|
+
case "failed":
|
|
295
|
+
return "Last evaluation failed";
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function harnessLabel(harness: string): string {
|
|
300
|
+
switch (harness) {
|
|
301
|
+
case "claude-code":
|
|
302
|
+
return "claude";
|
|
303
|
+
default:
|
|
304
|
+
return harness;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function shortDate(iso: string): string {
|
|
309
|
+
const d = new Date(iso);
|
|
310
|
+
if (Number.isNaN(d.getTime())) return "";
|
|
311
|
+
const now = new Date();
|
|
312
|
+
const sameYear = d.getFullYear() === now.getFullYear();
|
|
313
|
+
const md = `${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
314
|
+
const hm = `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
|
|
315
|
+
return sameYear ? `${md} ${hm}` : `${d.getFullYear()}-${md}`;
|
|
316
|
+
}
|