@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,401 @@
1
+ package codex
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 agentLaunch struct {
15
+ callID string
16
+ seq int
17
+ arguments map[string]any
18
+ output string
19
+ outputObserved bool
20
+ }
21
+
22
+ type agentLaunchOutput struct {
23
+ AgentID string `json:"agent_id"`
24
+ Nickname string `json:"nickname"`
25
+ TaskName string `json:"task_name"`
26
+ }
27
+
28
+ type codexGraphActor struct {
29
+ session model.SessionMeta
30
+ nodeID string
31
+ depth int
32
+ sourceID string
33
+ }
34
+
35
+ func (a Adapter) AgentGraphInputs(root model.SessionMeta, catalog []model.SessionMeta) ([]string, error) {
36
+ paths := map[string]bool{root.Path: true}
37
+ childrenByParent := make(map[string][]model.SessionMeta)
38
+ for _, session := range catalog {
39
+ if session.Agent == nil || session.Agent.ParentSessionID == "" || session.Harness != a.Harness() {
40
+ continue
41
+ }
42
+ childrenByParent[session.Agent.ParentSessionID] = append(childrenByParent[session.Agent.ParentSessionID], session)
43
+ }
44
+
45
+ visitedSessions := map[string]bool{root.Key: true}
46
+ queue := []string{root.ID}
47
+ for len(queue) > 0 {
48
+ parentID := queue[0]
49
+ queue = queue[1:]
50
+ for _, child := range childrenByParent[parentID] {
51
+ if visitedSessions[child.Key] {
52
+ continue
53
+ }
54
+ visitedSessions[child.Key] = true
55
+ paths[child.Path] = true
56
+ queue = append(queue, codexSessionSourceID(child))
57
+ }
58
+ }
59
+
60
+ inputs := make([]string, 0, len(paths))
61
+ for path := range paths {
62
+ if path != "" {
63
+ inputs = append(inputs, path)
64
+ }
65
+ }
66
+ sort.Strings(inputs)
67
+ return inputs, nil
68
+ }
69
+
70
+ func (a Adapter) BuildAgentGraph(root model.SessionMeta, catalog []model.SessionMeta) (*model.AgentGraph, error) {
71
+ mainID := adapter.AgentNodeID(a.Harness(), root.Key, "root:"+root.Key)
72
+ graph := &model.AgentGraph{
73
+ Version: model.AgentGraphVersion,
74
+ RootSessionKey: root.Key,
75
+ Agents: []model.AgentNode{{
76
+ ID: mainID,
77
+ Depth: 0,
78
+ Kind: model.AgentKindMain,
79
+ Label: "Main",
80
+ Status: model.AgentStatusMain,
81
+ TraceAvailability: model.TraceAvailabilityAvailable,
82
+ TraceSessionKey: root.Key,
83
+ TraceEventCount: root.EventCount,
84
+ LinkQuality: model.AgentLinkQualityExact,
85
+ LinkMethod: model.AgentLinkMethodRoot,
86
+ }},
87
+ }
88
+
89
+ childrenByParent := make(map[string][]model.SessionMeta)
90
+ ambiguousRootID := false
91
+ for _, session := range catalog {
92
+ if session.Key != root.Key && session.Harness == a.Harness() && !session.Auxiliary && session.Agent == nil && session.ID == root.ID {
93
+ ambiguousRootID = true
94
+ }
95
+ if session.Agent == nil || session.Agent.ParentSessionID == "" {
96
+ continue
97
+ }
98
+ parentID := session.Agent.ParentSessionID
99
+ childrenByParent[parentID] = append(childrenByParent[parentID], session)
100
+ }
101
+ for parentID := range childrenByParent {
102
+ sort.Slice(childrenByParent[parentID], func(i, j int) bool {
103
+ return childrenByParent[parentID][i].Key < childrenByParent[parentID][j].Key
104
+ })
105
+ }
106
+
107
+ visitedSessions := map[string]bool{root.Key: true}
108
+ addedNodes := map[string]bool{mainID: true}
109
+ queue := []codexGraphActor{{session: root, nodeID: mainID, sourceID: root.ID}}
110
+ for len(queue) > 0 {
111
+ actor := queue[0]
112
+ queue = queue[1:]
113
+
114
+ launches, err := readAgentLaunches(actor.session.Path)
115
+ if err != nil {
116
+ return nil, err
117
+ }
118
+ children := childrenByParent[actor.sourceID]
119
+ linkedChildren := make(map[string]bool)
120
+ for _, launch := range launches {
121
+ launchOutput, hasIdentity := parseAgentLaunchOutput(launch.output)
122
+ if hasIdentity && launchOutput.AgentID != "" {
123
+ var child *model.SessionMeta
124
+ for i := range children {
125
+ candidate := &children[i]
126
+ if candidate.Agent.SourceID == launchOutput.AgentID && !visitedSessions[candidate.Key] {
127
+ child = candidate
128
+ break
129
+ }
130
+ }
131
+ node := exactCodexAgentNode(a.Harness(), root.Key, actor, launch, launchOutput, child)
132
+ if !addedNodes[node.ID] {
133
+ graph.Agents = append(graph.Agents, node)
134
+ addedNodes[node.ID] = true
135
+ }
136
+ if child != nil {
137
+ linkedChildren[child.Key] = true
138
+ visitedSessions[child.Key] = true
139
+ queue = append(queue, codexGraphActor{
140
+ session: *child,
141
+ nodeID: node.ID,
142
+ depth: node.Depth,
143
+ sourceID: codexSessionSourceID(*child),
144
+ })
145
+ }
146
+ continue
147
+ }
148
+ if hasIdentity && launchOutput.TaskName != "" {
149
+ var child *model.SessionMeta
150
+ for i := range children {
151
+ candidate := &children[i]
152
+ if candidate.Agent.AgentPath == launchOutput.TaskName && !visitedSessions[candidate.Key] {
153
+ child = candidate
154
+ break
155
+ }
156
+ }
157
+ node := legacyCodexAgentNode(a.Harness(), root.Key, actor, launch, launchOutput, child)
158
+ if !addedNodes[node.ID] {
159
+ graph.Agents = append(graph.Agents, node)
160
+ addedNodes[node.ID] = true
161
+ }
162
+ if child != nil {
163
+ linkedChildren[child.Key] = true
164
+ visitedSessions[child.Key] = true
165
+ queue = append(queue, codexGraphActor{
166
+ session: *child, nodeID: node.ID, depth: node.Depth, sourceID: codexSessionSourceID(*child),
167
+ })
168
+ }
169
+ continue
170
+ }
171
+
172
+ node := unlinkedCodexLaunchNode(a.Harness(), root.Key, actor, launch)
173
+ if !addedNodes[node.ID] {
174
+ graph.Agents = append(graph.Agents, node)
175
+ addedNodes[node.ID] = true
176
+ }
177
+ }
178
+
179
+ for i := range children {
180
+ child := children[i]
181
+ if linkedChildren[child.Key] || visitedSessions[child.Key] {
182
+ continue
183
+ }
184
+ if actor.session.Key == root.Key && ambiguousRootID {
185
+ continue
186
+ }
187
+ node := derivedCodexAgentNode(a.Harness(), root.Key, actor, child)
188
+ if addedNodes[node.ID] {
189
+ continue
190
+ }
191
+ graph.Agents = append(graph.Agents, node)
192
+ addedNodes[node.ID] = true
193
+ visitedSessions[child.Key] = true
194
+ queue = append(queue, codexGraphActor{
195
+ session: child,
196
+ nodeID: node.ID,
197
+ depth: node.Depth,
198
+ sourceID: codexSessionSourceID(child),
199
+ })
200
+ }
201
+ }
202
+
203
+ graph.Agents = adapter.OrderAgentNodesPreorder(graph.Agents)
204
+ return graph, nil
205
+ }
206
+
207
+ func readAgentLaunches(path string) ([]agentLaunch, error) {
208
+ f, err := os.Open(path)
209
+ if err != nil {
210
+ return nil, err
211
+ }
212
+ defer f.Close()
213
+
214
+ launches := []agentLaunch{}
215
+ launchByCallID := make(map[string]int)
216
+ seenCalls := make(map[string]bool)
217
+ seq := 0
218
+ err = adapter.ReadJSONLines(f, func(data []byte) {
219
+ var line rawLine
220
+ if json.Unmarshal(data, &line) != nil || line.Type != "response_item" {
221
+ return
222
+ }
223
+ var payload responseItemPayload
224
+ if json.Unmarshal(line.Payload, &payload) != nil {
225
+ return
226
+ }
227
+ if call, _, ok := decodeCall(payload, line.Timestamp); ok {
228
+ if seenCalls[call.ID] {
229
+ return
230
+ }
231
+ seenCalls[call.ID] = true
232
+ if call.Name == "spawn_agent" {
233
+ launchByCallID[call.ID] = len(launches)
234
+ launches = append(launches, agentLaunch{
235
+ callID: call.ID,
236
+ seq: seq,
237
+ arguments: call.Input,
238
+ })
239
+ }
240
+ seq++
241
+ return
242
+ }
243
+ callID, result, ok := decodeOutput(payload)
244
+ if !ok {
245
+ return
246
+ }
247
+ index, ok := launchByCallID[callID]
248
+ if !ok || launches[index].outputObserved {
249
+ return
250
+ }
251
+ launches[index].output = result.Content
252
+ launches[index].outputObserved = true
253
+ })
254
+ return launches, err
255
+ }
256
+
257
+ func exactCodexAgentNode(harness, rootKey string, actor codexGraphActor, launch agentLaunch, output agentLaunchOutput, child *model.SessionMeta) model.AgentNode {
258
+ seq := launch.seq
259
+ node := model.AgentNode{
260
+ ID: adapter.AgentNodeID(harness, rootKey, "codex-agent:"+output.AgentID),
261
+ ParentID: actor.nodeID,
262
+ Depth: actor.depth + 1,
263
+ Kind: model.AgentKindSubagent,
264
+ Label: output.Nickname,
265
+ Role: agentArgument(launch.arguments, "agent_type"),
266
+ InstructionPreview: adapter.AgentInstructionPreview(agentArgument(launch.arguments, "message")),
267
+ LaunchSeq: &seq,
268
+ LaunchCallID: launch.callID,
269
+ Status: model.AgentStatusLaunched,
270
+ TraceAvailability: model.TraceAvailabilityMissing,
271
+ LinkQuality: model.AgentLinkQualityExact,
272
+ LinkMethod: model.AgentLinkMethodCodexAgentID,
273
+ }
274
+ if child != nil {
275
+ node.Depth = codexChildDepth(*child, actor.depth)
276
+ node.Label = child.Agent.Label
277
+ if child.Agent.Role != "" {
278
+ node.Role = child.Agent.Role
279
+ }
280
+ node.TraceAvailability = model.TraceAvailabilityAvailable
281
+ node.TraceSessionKey = child.Key
282
+ node.TraceEventCount = child.EventCount
283
+ }
284
+ if node.Label == "" {
285
+ node.Label = output.Nickname
286
+ }
287
+ if node.Label == "" {
288
+ node.Label = "Subagent"
289
+ }
290
+ return node
291
+ }
292
+
293
+ func unlinkedCodexLaunchNode(harness, rootKey string, actor codexGraphActor, launch agentLaunch) model.AgentNode {
294
+ seq := launch.seq
295
+ status := model.AgentStatusUnknown
296
+ trimmedOutput := strings.TrimSpace(launch.output)
297
+ if launch.outputObserved && trimmedOutput != "" && !json.Valid([]byte(trimmedOutput)) {
298
+ status = model.AgentStatusFailed
299
+ }
300
+ return model.AgentNode{
301
+ ID: adapter.AgentNodeID(harness, rootKey, "launch:"+actor.nodeID+":"+launch.callID),
302
+ ParentID: actor.nodeID,
303
+ Depth: actor.depth + 1,
304
+ Kind: model.AgentKindSubagent,
305
+ Label: "Subagent",
306
+ Role: agentArgument(launch.arguments, "agent_type"),
307
+ InstructionPreview: adapter.AgentInstructionPreview(agentArgument(launch.arguments, "message")),
308
+ LaunchSeq: &seq,
309
+ LaunchCallID: launch.callID,
310
+ Status: status,
311
+ TraceAvailability: model.TraceAvailabilityUnavailable,
312
+ LinkQuality: model.AgentLinkQualityUnavailable,
313
+ LinkMethod: model.AgentLinkMethodUnavailable,
314
+ }
315
+ }
316
+
317
+ func legacyCodexAgentNode(harness, rootKey string, actor codexGraphActor, launch agentLaunch, output agentLaunchOutput, child *model.SessionMeta) model.AgentNode {
318
+ seq := launch.seq
319
+ label := filepath.Base(output.TaskName)
320
+ if label == "." || label == "/" || label == "" {
321
+ label = "Subagent"
322
+ }
323
+ node := model.AgentNode{
324
+ ID: adapter.AgentNodeID(harness, rootKey, "codex-task:"+output.TaskName),
325
+ ParentID: actor.nodeID,
326
+ Depth: actor.depth + 1,
327
+ Kind: model.AgentKindSubagent,
328
+ Label: label,
329
+ Role: agentArgument(launch.arguments, "agent_type"),
330
+ InstructionPreview: adapter.AgentInstructionPreview(agentArgument(launch.arguments, "message")),
331
+ LaunchSeq: &seq,
332
+ LaunchCallID: launch.callID,
333
+ Status: model.AgentStatusLaunched,
334
+ TraceAvailability: model.TraceAvailabilityMissing,
335
+ LinkQuality: model.AgentLinkQualityUnavailable,
336
+ LinkMethod: model.AgentLinkMethodUnavailable,
337
+ }
338
+ if child != nil {
339
+ node.Depth = codexChildDepth(*child, actor.depth)
340
+ if child.Agent.Label != "" {
341
+ node.Label = child.Agent.Label
342
+ }
343
+ if child.Agent.Role != "" {
344
+ node.Role = child.Agent.Role
345
+ }
346
+ node.TraceAvailability = model.TraceAvailabilityAvailable
347
+ node.TraceSessionKey = child.Key
348
+ node.TraceEventCount = child.EventCount
349
+ node.LinkQuality = model.AgentLinkQualityDerived
350
+ node.LinkMethod = model.AgentLinkMethodCodexParentThreadID
351
+ }
352
+ return node
353
+ }
354
+
355
+ func derivedCodexAgentNode(harness, rootKey string, actor codexGraphActor, child model.SessionMeta) model.AgentNode {
356
+ label := child.Agent.Label
357
+ if label == "" {
358
+ label = "Subagent"
359
+ }
360
+ return model.AgentNode{
361
+ ID: adapter.AgentNodeID(harness, rootKey, "session:"+child.Key),
362
+ ParentID: actor.nodeID,
363
+ Depth: codexChildDepth(child, actor.depth),
364
+ Kind: model.AgentKindSubagent,
365
+ Label: label,
366
+ Role: child.Agent.Role,
367
+ Status: model.AgentStatusUnknown,
368
+ TraceAvailability: model.TraceAvailabilityAvailable,
369
+ TraceSessionKey: child.Key,
370
+ TraceEventCount: child.EventCount,
371
+ LinkQuality: model.AgentLinkQualityDerived,
372
+ LinkMethod: model.AgentLinkMethodCodexParentThreadID,
373
+ }
374
+ }
375
+
376
+ func parseAgentLaunchOutput(output string) (agentLaunchOutput, bool) {
377
+ var parsed agentLaunchOutput
378
+ if json.Unmarshal([]byte(strings.TrimSpace(output)), &parsed) != nil || (parsed.AgentID == "" && parsed.TaskName == "") {
379
+ return agentLaunchOutput{}, false
380
+ }
381
+ return parsed, true
382
+ }
383
+
384
+ func agentArgument(arguments map[string]any, key string) string {
385
+ value, _ := arguments[key].(string)
386
+ return value
387
+ }
388
+
389
+ func codexChildDepth(child model.SessionMeta, parentDepth int) int {
390
+ if child.Agent.Depth > 0 {
391
+ return child.Agent.Depth
392
+ }
393
+ return parentDepth + 1
394
+ }
395
+
396
+ func codexSessionSourceID(session model.SessionMeta) string {
397
+ if session.Agent != nil && session.Agent.SourceID != "" {
398
+ return session.Agent.SourceID
399
+ }
400
+ return session.ID
401
+ }