@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.
Files changed (102) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +173 -0
  3. package/bin/synapse +0 -0
  4. package/cmd/rubriceval/main.go +308 -0
  5. package/cmd/synapse/main.go +280 -0
  6. package/cmd/synapse/main_test.go +16 -0
  7. package/go.mod +7 -0
  8. package/go.sum +6 -0
  9. package/internal/adapter/adapter.go +1117 -0
  10. package/internal/adapter/adapter_test.go +518 -0
  11. package/internal/adapter/agy/adapter.go +193 -0
  12. package/internal/adapter/claudecode/adapter.go +415 -0
  13. package/internal/adapter/claudecode/adapter_test.go +260 -0
  14. package/internal/adapter/claudecode/agents.go +387 -0
  15. package/internal/adapter/claudecode/agents_test.go +480 -0
  16. package/internal/adapter/claudecode/summary_inputs_test.go +19 -0
  17. package/internal/adapter/codex/adapter.go +919 -0
  18. package/internal/adapter/codex/adapter_test.go +920 -0
  19. package/internal/adapter/codex/agents.go +401 -0
  20. package/internal/adapter/codex/agents_test.go +610 -0
  21. package/internal/adapter/codex/summary_inputs_test.go +20 -0
  22. package/internal/adapter/pi/adapter.go +483 -0
  23. package/internal/adapter/pi/adapter_test.go +517 -0
  24. package/internal/citymap/builder.go +1124 -0
  25. package/internal/citymap/builder_test.go +818 -0
  26. package/internal/judge/cache.go +180 -0
  27. package/internal/judge/cli.go +240 -0
  28. package/internal/judge/cli_test.go +68 -0
  29. package/internal/judge/fresh_summary_test.go +37 -0
  30. package/internal/judge/input.go +233 -0
  31. package/internal/judge/judge.go +416 -0
  32. package/internal/judge/judge_test.go +288 -0
  33. package/internal/judge/prompt.go +129 -0
  34. package/internal/judge/rubric.go +275 -0
  35. package/internal/judge/rubric_test.go +642 -0
  36. package/internal/model/agent.go +63 -0
  37. package/internal/model/agent_schema_test.go +86 -0
  38. package/internal/model/agent_test.go +64 -0
  39. package/internal/model/model.go +175 -0
  40. package/internal/model/report.go +166 -0
  41. package/internal/model/stats.go +151 -0
  42. package/internal/model/stats_test.go +89 -0
  43. package/internal/model/trace_schema_test.go +67 -0
  44. package/internal/server/analyze.go +286 -0
  45. package/internal/server/analyze_test.go +297 -0
  46. package/internal/server/codex_index_test.go +40 -0
  47. package/internal/server/hardening_test.go +147 -0
  48. package/internal/server/reportindex.go +91 -0
  49. package/internal/server/reportindex_test.go +116 -0
  50. package/internal/server/server.go +1099 -0
  51. package/internal/server/server_test.go +1389 -0
  52. package/internal/server/static/assets/fraunces-latin-ext-standard-italic-CGbN9UgK.woff2 +0 -0
  53. package/internal/server/static/assets/fraunces-latin-ext-standard-normal-CJcjJNj7.woff2 +0 -0
  54. package/internal/server/static/assets/fraunces-latin-standard-italic-lSdLDfvT.woff2 +0 -0
  55. package/internal/server/static/assets/fraunces-latin-standard-normal-DihXLNYH.woff2 +0 -0
  56. package/internal/server/static/assets/fraunces-vietnamese-standard-italic-DxWqP7Ku.woff2 +0 -0
  57. package/internal/server/static/assets/fraunces-vietnamese-standard-normal-Czevyj-6.woff2 +0 -0
  58. package/internal/server/static/assets/index-BNoY_BiB.css +1 -0
  59. package/internal/server/static/assets/index-C_adLrJr.js +3 -0
  60. package/internal/server/static/assets/react-gcHzaSmV.js +10 -0
  61. package/internal/server/static/assets/schibsted-grotesk-latin-ext-wght-normal-hsMS0n0O.woff2 +0 -0
  62. package/internal/server/static/assets/schibsted-grotesk-latin-wght-normal-Bb8VGrTG.woff2 +0 -0
  63. package/internal/server/static/assets/three-DnGjZfD1.js +4012 -0
  64. package/internal/server/static/index.html +26 -0
  65. package/internal/server/tracestore.go +173 -0
  66. package/internal/server/tracestore_test.go +51 -0
  67. package/internal/textutil/truncate.go +30 -0
  68. package/internal/textutil/truncate_test.go +39 -0
  69. package/package.json +35 -0
  70. package/web/e2e/agent-lens.spec.ts +688 -0
  71. package/web/index.html +23 -0
  72. package/web/package-lock.json +1933 -0
  73. package/web/package.json +33 -0
  74. package/web/playwright.config.ts +24 -0
  75. package/web/src/App.tsx +876 -0
  76. package/web/src/api/client.ts +74 -0
  77. package/web/src/main.tsx +12 -0
  78. package/web/src/playback/recorder.ts +160 -0
  79. package/web/src/playback/reducer.ts +91 -0
  80. package/web/src/scene/CityScene.tsx +638 -0
  81. package/web/src/scene/TreeScene.tsx +656 -0
  82. package/web/src/scene/dirLabels.ts +145 -0
  83. package/web/src/scene/sceneUtils.ts +144 -0
  84. package/web/src/scene/textures.ts +60 -0
  85. package/web/src/scene/trail.ts +79 -0
  86. package/web/src/scene/treeLayout.ts +169 -0
  87. package/web/src/state/filters.ts +40 -0
  88. package/web/src/state/store.ts +83 -0
  89. package/web/src/styles.css +2565 -0
  90. package/web/src/types.ts +315 -0
  91. package/web/src/ui/AgentsPanel.tsx +376 -0
  92. package/web/src/ui/Dock.tsx +104 -0
  93. package/web/src/ui/Hud.tsx +335 -0
  94. package/web/src/ui/Inspector.tsx +107 -0
  95. package/web/src/ui/LogoMark.tsx +38 -0
  96. package/web/src/ui/ReportPanel.tsx +491 -0
  97. package/web/src/ui/SessionRail.tsx +316 -0
  98. package/web/src/ui/Timeline.tsx +458 -0
  99. package/web/src/ui/ViewPanel.tsx +45 -0
  100. package/web/src/ui/shortcuts.ts +4 -0
  101. package/web/tsconfig.json +21 -0
  102. package/web/vite.config.ts +28 -0
@@ -0,0 +1,387 @@
1
+ package claudecode
2
+
3
+ import (
4
+ "encoding/json"
5
+ "os"
6
+ "path/filepath"
7
+ "sort"
8
+ "strings"
9
+
10
+ "github.com/hybridlabor-api/bdb-synapse/internal/adapter"
11
+ "github.com/hybridlabor-api/bdb-synapse/internal/model"
12
+ )
13
+
14
+ type claudeAgentLaunch struct {
15
+ callID string
16
+ seq int
17
+ input map[string]any
18
+ resultObserved bool
19
+ resultError bool
20
+ owner *claudeAgentArtifact
21
+ }
22
+
23
+ type claudeChildSidecar struct {
24
+ Name string `json:"name"`
25
+ AgentType string `json:"agentType"`
26
+ Description string `json:"description"`
27
+ ToolUseID string `json:"toolUseId"`
28
+ SpawnDepth int `json:"spawnDepth"`
29
+ }
30
+
31
+ type claudeAgentArtifact struct {
32
+ jsonPath string
33
+ session *model.SessionMeta
34
+ sidecar *claudeChildSidecar
35
+ nodeID string
36
+ depth int
37
+ }
38
+
39
+ func (a Adapter) AgentGraphInputs(root model.SessionMeta, _ []model.SessionMeta) ([]string, error) {
40
+ inputs := []string{root.Path}
41
+ subagentsDir := filepath.Join(filepath.Dir(root.Path), root.ID, "subagents")
42
+ entries, err := os.ReadDir(subagentsDir)
43
+ if os.IsNotExist(err) {
44
+ return inputs, nil
45
+ }
46
+ if err != nil {
47
+ return nil, err
48
+ }
49
+ for _, entry := range entries {
50
+ if entry.IsDir() || (!strings.HasSuffix(entry.Name(), ".jsonl") && !strings.HasSuffix(entry.Name(), ".meta.json")) {
51
+ continue
52
+ }
53
+ inputs = append(inputs, filepath.Join(subagentsDir, entry.Name()))
54
+ }
55
+ sort.Strings(inputs)
56
+ return inputs, nil
57
+ }
58
+
59
+ func (a Adapter) BuildAgentGraph(root model.SessionMeta, catalog []model.SessionMeta) (*model.AgentGraph, error) {
60
+ mainID := adapter.AgentNodeID(a.Harness(), root.Key, "root:"+root.Key)
61
+ graph := &model.AgentGraph{
62
+ Version: model.AgentGraphVersion,
63
+ RootSessionKey: root.Key,
64
+ Agents: []model.AgentNode{{
65
+ ID: mainID,
66
+ Depth: 0,
67
+ Kind: model.AgentKindMain,
68
+ Label: "Main",
69
+ Status: model.AgentStatusMain,
70
+ TraceAvailability: model.TraceAvailabilityAvailable,
71
+ TraceSessionKey: root.Key,
72
+ TraceEventCount: root.EventCount,
73
+ LinkQuality: model.AgentLinkQualityExact,
74
+ LinkMethod: model.AgentLinkMethodRoot,
75
+ }},
76
+ }
77
+
78
+ subagentsDir := filepath.Join(filepath.Dir(root.Path), root.ID, "subagents")
79
+ artifacts, err := discoverClaudeAgentArtifacts(subagentsDir, catalog)
80
+ if err != nil {
81
+ return nil, err
82
+ }
83
+
84
+ rootLaunches, err := readClaudeAgentLaunches(root.Path)
85
+ if err != nil {
86
+ return nil, err
87
+ }
88
+ launches := append([]*claudeAgentLaunch(nil), rootLaunches...)
89
+ launchByCallID := make(map[string]*claudeAgentLaunch)
90
+ for _, launch := range rootLaunches {
91
+ launchByCallID[launch.callID] = launch
92
+ }
93
+ for _, artifact := range artifacts {
94
+ if artifact.session == nil {
95
+ continue
96
+ }
97
+ actorLaunches, readErr := readClaudeAgentLaunches(artifact.session.Path)
98
+ if readErr != nil {
99
+ return nil, readErr
100
+ }
101
+ for _, launch := range actorLaunches {
102
+ launch.owner = artifact
103
+ if existing := launchByCallID[launch.callID]; existing != nil {
104
+ if !existing.resultObserved && launch.resultObserved {
105
+ existing.resultObserved = true
106
+ existing.resultError = launch.resultError
107
+ }
108
+ continue
109
+ }
110
+ launchByCallID[launch.callID] = launch
111
+ launches = append(launches, launch)
112
+ }
113
+ }
114
+
115
+ for _, artifact := range artifacts {
116
+ identity := "session:" + claudeArtifactSessionKey(a.Harness(), artifact)
117
+ if artifact.sidecar != nil && artifact.sidecar.ToolUseID != "" {
118
+ identity = "claude-tool:" + artifact.sidecar.ToolUseID
119
+ }
120
+ artifact.nodeID = adapter.AgentNodeID(a.Harness(), root.Key, identity)
121
+ }
122
+ for _, artifact := range artifacts {
123
+ resolveClaudeArtifactDepth(artifact, launchByCallID, map[*claudeAgentArtifact]bool{})
124
+ }
125
+
126
+ matchedLaunches := make(map[string]bool)
127
+ for _, artifact := range artifacts {
128
+ node, launch := claudeArtifactNode(root.Key, mainID, artifact, launchByCallID)
129
+ graph.Agents = append(graph.Agents, node)
130
+ if launch != nil {
131
+ matchedLaunches[launch.callID] = true
132
+ }
133
+ }
134
+ for _, launch := range launches {
135
+ if matchedLaunches[launch.callID] {
136
+ continue
137
+ }
138
+ graph.Agents = append(graph.Agents, unlinkedClaudeLaunchNode(a.Harness(), root.Key, mainID, launch))
139
+ }
140
+
141
+ graph.Agents = adapter.OrderAgentNodesPreorder(graph.Agents)
142
+ return graph, nil
143
+ }
144
+
145
+ func discoverClaudeAgentArtifacts(subagentsDir string, catalog []model.SessionMeta) ([]*claudeAgentArtifact, error) {
146
+ byBasename := make(map[string]*claudeAgentArtifact)
147
+ for i := range catalog {
148
+ session := &catalog[i]
149
+ if filepath.Clean(filepath.Dir(session.Path)) != filepath.Clean(subagentsDir) || filepath.Ext(session.Path) != ".jsonl" {
150
+ continue
151
+ }
152
+ basename := strings.TrimSuffix(filepath.Base(session.Path), ".jsonl")
153
+ byBasename[basename] = &claudeAgentArtifact{jsonPath: session.Path, session: session}
154
+ }
155
+
156
+ entries, err := os.ReadDir(subagentsDir)
157
+ if os.IsNotExist(err) {
158
+ return sortedClaudeArtifacts(byBasename), nil
159
+ }
160
+ if err != nil {
161
+ return nil, err
162
+ }
163
+ for _, entry := range entries {
164
+ if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".meta.json") {
165
+ continue
166
+ }
167
+ basename := strings.TrimSuffix(entry.Name(), ".meta.json")
168
+ artifact := byBasename[basename]
169
+ if artifact == nil {
170
+ artifact = &claudeAgentArtifact{
171
+ jsonPath: filepath.Join(subagentsDir, basename+".jsonl"),
172
+ }
173
+ byBasename[basename] = artifact
174
+ }
175
+ data, readErr := os.ReadFile(filepath.Join(subagentsDir, entry.Name()))
176
+ if readErr != nil {
177
+ return nil, readErr
178
+ }
179
+ var sidecar claudeChildSidecar
180
+ if json.Unmarshal(data, &sidecar) == nil {
181
+ artifact.sidecar = &sidecar
182
+ }
183
+ }
184
+ return sortedClaudeArtifacts(byBasename), nil
185
+ }
186
+
187
+ func sortedClaudeArtifacts(byBasename map[string]*claudeAgentArtifact) []*claudeAgentArtifact {
188
+ basenames := make([]string, 0, len(byBasename))
189
+ for basename := range byBasename {
190
+ basenames = append(basenames, basename)
191
+ }
192
+ sort.Strings(basenames)
193
+ artifacts := make([]*claudeAgentArtifact, 0, len(basenames))
194
+ for _, basename := range basenames {
195
+ artifacts = append(artifacts, byBasename[basename])
196
+ }
197
+ return artifacts
198
+ }
199
+
200
+ func readClaudeAgentLaunches(path string) ([]*claudeAgentLaunch, error) {
201
+ f, err := os.Open(path)
202
+ if err != nil {
203
+ return nil, err
204
+ }
205
+ defer f.Close()
206
+
207
+ launches := []*claudeAgentLaunch{}
208
+ launchByCallID := make(map[string]*claudeAgentLaunch)
209
+ seenCalls := make(map[string]bool)
210
+ seq := 0
211
+ err = adapter.ReadJSONLines(f, func(data []byte) {
212
+ var line rawLine
213
+ if json.Unmarshal(data, &line) != nil || len(line.Message) == 0 {
214
+ return
215
+ }
216
+ var msg message
217
+ if json.Unmarshal(line.Message, &msg) != nil {
218
+ return
219
+ }
220
+ for _, item := range msg.Content.Items {
221
+ switch item.Type {
222
+ case "tool_use":
223
+ if seenCalls[item.ID] {
224
+ continue
225
+ }
226
+ seenCalls[item.ID] = true
227
+ if item.Name == "Agent" || item.Name == "Task" {
228
+ launch := &claudeAgentLaunch{callID: item.ID, seq: seq, input: item.Input}
229
+ launches = append(launches, launch)
230
+ launchByCallID[item.ID] = launch
231
+ }
232
+ seq++
233
+ case "tool_result":
234
+ launch := launchByCallID[item.ToolUseID]
235
+ if launch == nil || launch.resultObserved {
236
+ continue
237
+ }
238
+ launch.resultObserved = true
239
+ launch.resultError = item.IsError != nil && *item.IsError
240
+ }
241
+ }
242
+ })
243
+ return launches, err
244
+ }
245
+
246
+ func claudeArtifactNode(rootKey, mainID string, artifact *claudeAgentArtifact, launchByCallID map[string]*claudeAgentLaunch) (model.AgentNode, *claudeAgentLaunch) {
247
+ var launch *claudeAgentLaunch
248
+ if artifact.sidecar != nil && artifact.sidecar.ToolUseID != "" {
249
+ launch = launchByCallID[artifact.sidecar.ToolUseID]
250
+ }
251
+ parentID := mainID
252
+ quality := model.AgentLinkQualityDerived
253
+ method := model.AgentLinkMethodClaudeSubagentsDirectory
254
+ if launch != nil && launch.owner != artifact {
255
+ quality = model.AgentLinkQualityExact
256
+ method = model.AgentLinkMethodClaudeToolUseID
257
+ if launch.owner != nil {
258
+ parentID = launch.owner.nodeID
259
+ }
260
+ } else {
261
+ launch = nil
262
+ }
263
+
264
+ node := model.AgentNode{
265
+ ID: artifact.nodeID,
266
+ ParentID: parentID,
267
+ Depth: artifact.depth,
268
+ Kind: model.AgentKindSubagent,
269
+ Label: claudeArtifactLabel(artifact, launch),
270
+ Role: claudeArtifactRole(artifact, launch),
271
+ Status: model.AgentStatusLaunched,
272
+ TraceAvailability: model.TraceAvailabilityMissing,
273
+ LinkQuality: quality,
274
+ LinkMethod: method,
275
+ }
276
+ if launch != nil {
277
+ seq := launch.seq
278
+ node.InstructionPreview = adapter.AgentInstructionPreview(claudeLaunchInput(launch, "prompt"))
279
+ node.LaunchSeq = &seq
280
+ node.LaunchCallID = launch.callID
281
+ }
282
+ if artifact.session != nil {
283
+ node.TraceAvailability = model.TraceAvailabilityAvailable
284
+ node.TraceSessionKey = artifact.session.Key
285
+ node.TraceEventCount = artifact.session.EventCount
286
+ }
287
+ return node, launch
288
+ }
289
+
290
+ func unlinkedClaudeLaunchNode(harness, rootKey, mainID string, launch *claudeAgentLaunch) model.AgentNode {
291
+ parentID := mainID
292
+ parentDepth := 0
293
+ if launch.owner != nil {
294
+ parentID = launch.owner.nodeID
295
+ parentDepth = launch.owner.depth
296
+ }
297
+ status := model.AgentStatusUnknown
298
+ if launch.resultObserved && launch.resultError {
299
+ status = model.AgentStatusFailed
300
+ }
301
+ seq := launch.seq
302
+ label := claudeLaunchInput(launch, "description")
303
+ if label == "" {
304
+ label = claudeLaunchInput(launch, "subagent_type")
305
+ }
306
+ if label == "" {
307
+ label = "Subagent"
308
+ }
309
+ return model.AgentNode{
310
+ ID: adapter.AgentNodeID(harness, rootKey, "launch:"+parentID+":"+launch.callID),
311
+ ParentID: parentID,
312
+ Depth: parentDepth + 1,
313
+ Kind: model.AgentKindSubagent,
314
+ Label: label,
315
+ Role: claudeLaunchInput(launch, "subagent_type"),
316
+ InstructionPreview: adapter.AgentInstructionPreview(claudeLaunchInput(launch, "prompt")),
317
+ LaunchSeq: &seq,
318
+ LaunchCallID: launch.callID,
319
+ Status: status,
320
+ TraceAvailability: model.TraceAvailabilityUnavailable,
321
+ LinkQuality: model.AgentLinkQualityUnavailable,
322
+ LinkMethod: model.AgentLinkMethodUnavailable,
323
+ }
324
+ }
325
+
326
+ func resolveClaudeArtifactDepth(artifact *claudeAgentArtifact, launchByCallID map[string]*claudeAgentLaunch, visiting map[*claudeAgentArtifact]bool) int {
327
+ if artifact.depth > 0 {
328
+ return artifact.depth
329
+ }
330
+ if artifact.sidecar != nil && artifact.sidecar.SpawnDepth > 0 {
331
+ artifact.depth = artifact.sidecar.SpawnDepth
332
+ return artifact.depth
333
+ }
334
+ if visiting[artifact] {
335
+ return 1
336
+ }
337
+ visiting[artifact] = true
338
+ parentDepth := 0
339
+ if artifact.sidecar != nil {
340
+ if launch := launchByCallID[artifact.sidecar.ToolUseID]; launch != nil && launch.owner != nil && launch.owner != artifact {
341
+ parentDepth = resolveClaudeArtifactDepth(launch.owner, launchByCallID, visiting)
342
+ }
343
+ }
344
+ delete(visiting, artifact)
345
+ artifact.depth = parentDepth + 1
346
+ return artifact.depth
347
+ }
348
+
349
+ func claudeArtifactSessionKey(harness string, artifact *claudeAgentArtifact) string {
350
+ if artifact.session != nil {
351
+ return artifact.session.Key
352
+ }
353
+ return adapter.SessionKey(harness, artifact.jsonPath)
354
+ }
355
+
356
+ func claudeArtifactLabel(artifact *claudeAgentArtifact, launch *claudeAgentLaunch) string {
357
+ if artifact.sidecar != nil {
358
+ for _, value := range []string{artifact.sidecar.Name, artifact.sidecar.Description, artifact.sidecar.AgentType} {
359
+ if value != "" {
360
+ return value
361
+ }
362
+ }
363
+ }
364
+ if launch != nil {
365
+ for _, key := range []string{"description", "subagent_type"} {
366
+ if value := claudeLaunchInput(launch, key); value != "" {
367
+ return value
368
+ }
369
+ }
370
+ }
371
+ return "Subagent"
372
+ }
373
+
374
+ func claudeArtifactRole(artifact *claudeAgentArtifact, launch *claudeAgentLaunch) string {
375
+ if artifact.sidecar != nil && artifact.sidecar.AgentType != "" {
376
+ return artifact.sidecar.AgentType
377
+ }
378
+ return claudeLaunchInput(launch, "subagent_type")
379
+ }
380
+
381
+ func claudeLaunchInput(launch *claudeAgentLaunch, key string) string {
382
+ if launch == nil {
383
+ return ""
384
+ }
385
+ value, _ := launch.input[key].(string)
386
+ return value
387
+ }