@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,610 @@
1
+ package codex
2
+
3
+ import (
4
+ "encoding/json"
5
+ "os"
6
+ "path/filepath"
7
+ "reflect"
8
+ "sort"
9
+ "testing"
10
+
11
+ baseadapter "github.com/hybridlabor-api/bdb-synapse/internal/adapter"
12
+ "github.com/hybridlabor-api/bdb-synapse/internal/model"
13
+ )
14
+
15
+ type codexAgentChildFixture struct {
16
+ id string
17
+ parentID string
18
+ depth int
19
+ agentPath string
20
+ label string
21
+ role string
22
+ lines []any
23
+ }
24
+
25
+ type codexAgentFixture struct {
26
+ adapter Adapter
27
+ root model.SessionMeta
28
+ catalog []model.SessionMeta
29
+ }
30
+
31
+ type rawAgentJSONL string
32
+
33
+ func TestCodexAgentGraphFixtures(t *testing.T) {
34
+ tests := []struct {
35
+ name string
36
+ setup func(*testing.T) (codexAgentFixture, []model.AgentNode)
37
+ }{
38
+ {
39
+ name: "exact",
40
+ setup: func(t *testing.T) (codexAgentFixture, []model.AgentNode) {
41
+ fixture := newCodexAgentFixture(t, []any{
42
+ call("", "fc-read", "call-read", "exec_command", map[string]any{"cmd": "true"}),
43
+ output("", "call-read", "Process exited with code 0"),
44
+ call("", "fc-exact", "call-exact", "spawn_agent", map[string]any{
45
+ "agent_type": "reviewer",
46
+ "message": " review\n this change ",
47
+ }),
48
+ output("", "call-exact", `{"agent_id":"agent-exact","nickname":"Launch Nick"}`),
49
+ }, codexAgentChildFixture{
50
+ id: "agent-exact",
51
+ parentID: "root-session",
52
+ depth: 1,
53
+ label: "Tesla",
54
+ role: "reviewer",
55
+ lines: []any{
56
+ call("", "fc-child", "call-child", "exec_command", map[string]any{"cmd": "true"}),
57
+ output("", "call-child", "Process exited with code 0"),
58
+ },
59
+ })
60
+ seq := 1
61
+ return fixture, []model.AgentNode{
62
+ mainAgentNode(fixture.root),
63
+ {
64
+ ID: codexAgentID(fixture.root, "codex-agent:agent-exact"),
65
+ ParentID: codexMainID(fixture.root),
66
+ Depth: 1,
67
+ Kind: model.AgentKindSubagent,
68
+ Label: "Tesla",
69
+ Role: "reviewer",
70
+ InstructionPreview: "review this change",
71
+ LaunchSeq: &seq,
72
+ LaunchCallID: "call-exact",
73
+ Status: model.AgentStatusLaunched,
74
+ TraceAvailability: model.TraceAvailabilityAvailable,
75
+ TraceSessionKey: fixture.catalog[0].Key,
76
+ TraceEventCount: 1,
77
+ LinkQuality: model.AgentLinkQualityExact,
78
+ LinkMethod: model.AgentLinkMethodCodexAgentID,
79
+ },
80
+ }
81
+ },
82
+ },
83
+ {
84
+ name: "failed",
85
+ setup: func(t *testing.T) (codexAgentFixture, []model.AgentNode) {
86
+ fixture := newCodexAgentFixture(t, []any{
87
+ call("", "fc-failed", "call-failed", "spawn_agent", map[string]any{
88
+ "agent_type": "default",
89
+ "message": "try the fork",
90
+ }),
91
+ output("", "call-failed", "fork requires a previous turn"),
92
+ })
93
+ seq := 0
94
+ return fixture, []model.AgentNode{
95
+ mainAgentNode(fixture.root),
96
+ {
97
+ ID: codexAgentID(fixture.root, "launch:"+codexMainID(fixture.root)+":call-failed"),
98
+ ParentID: codexMainID(fixture.root),
99
+ Depth: 1,
100
+ Kind: model.AgentKindSubagent,
101
+ Label: "Subagent",
102
+ Role: "default",
103
+ InstructionPreview: "try the fork",
104
+ LaunchSeq: &seq,
105
+ LaunchCallID: "call-failed",
106
+ Status: model.AgentStatusFailed,
107
+ TraceAvailability: model.TraceAvailabilityUnavailable,
108
+ LinkQuality: model.AgentLinkQualityUnavailable,
109
+ LinkMethod: model.AgentLinkMethodUnavailable,
110
+ },
111
+ }
112
+ },
113
+ },
114
+ {
115
+ name: "missing",
116
+ setup: func(t *testing.T) (codexAgentFixture, []model.AgentNode) {
117
+ fixture := newCodexAgentFixture(t, []any{
118
+ call("", "fc-missing", "call-missing", "spawn_agent", map[string]any{
119
+ "agent_type": "explorer",
120
+ "message": "find the path",
121
+ }),
122
+ output("", "call-missing", `{"agent_id":"agent-missing","nickname":"Nova"}`),
123
+ })
124
+ seq := 0
125
+ return fixture, []model.AgentNode{
126
+ mainAgentNode(fixture.root),
127
+ {
128
+ ID: codexAgentID(fixture.root, "codex-agent:agent-missing"),
129
+ ParentID: codexMainID(fixture.root),
130
+ Depth: 1,
131
+ Kind: model.AgentKindSubagent,
132
+ Label: "Nova",
133
+ Role: "explorer",
134
+ InstructionPreview: "find the path",
135
+ LaunchSeq: &seq,
136
+ LaunchCallID: "call-missing",
137
+ Status: model.AgentStatusLaunched,
138
+ TraceAvailability: model.TraceAvailabilityMissing,
139
+ LinkQuality: model.AgentLinkQualityExact,
140
+ LinkMethod: model.AgentLinkMethodCodexAgentID,
141
+ },
142
+ }
143
+ },
144
+ },
145
+ {
146
+ name: "nested",
147
+ setup: func(t *testing.T) (codexAgentFixture, []model.AgentNode) {
148
+ fixture := newCodexAgentFixture(t, []any{
149
+ call("", "fc-child", "call-child", "spawn_agent", map[string]any{"message": "first"}),
150
+ output("", "call-child", `{"agent_id":"agent-child"}`),
151
+ },
152
+ codexAgentChildFixture{
153
+ id: "agent-child",
154
+ parentID: "root-session",
155
+ depth: 1,
156
+ label: "Child",
157
+ lines: []any{
158
+ call("", "fc-read", "call-read", "exec_command", map[string]any{"cmd": "true"}),
159
+ output("", "call-read", "Process exited with code 0"),
160
+ call("", "fc-grand", "call-grand", "spawn_agent", map[string]any{"message": "second"}),
161
+ output("", "call-grand", `{"agent_id":"agent-grand"}`),
162
+ },
163
+ },
164
+ codexAgentChildFixture{
165
+ id: "agent-grand",
166
+ parentID: "agent-child",
167
+ depth: 2,
168
+ label: "Grandchild",
169
+ },
170
+ )
171
+ rootSeq, nestedSeq := 0, 1
172
+ childID := codexAgentID(fixture.root, "codex-agent:agent-child")
173
+ return fixture, []model.AgentNode{
174
+ mainAgentNode(fixture.root),
175
+ {
176
+ ID: childID,
177
+ ParentID: codexMainID(fixture.root),
178
+ Depth: 1,
179
+ Kind: model.AgentKindSubagent,
180
+ Label: "Child",
181
+ InstructionPreview: "first",
182
+ LaunchSeq: &rootSeq,
183
+ LaunchCallID: "call-child",
184
+ Status: model.AgentStatusLaunched,
185
+ TraceAvailability: model.TraceAvailabilityAvailable,
186
+ TraceSessionKey: fixture.catalog[0].Key,
187
+ TraceEventCount: 2,
188
+ LinkQuality: model.AgentLinkQualityExact,
189
+ LinkMethod: model.AgentLinkMethodCodexAgentID,
190
+ },
191
+ {
192
+ ID: codexAgentID(fixture.root, "codex-agent:agent-grand"),
193
+ ParentID: childID,
194
+ Depth: 2,
195
+ Kind: model.AgentKindSubagent,
196
+ Label: "Grandchild",
197
+ InstructionPreview: "second",
198
+ LaunchSeq: &nestedSeq,
199
+ LaunchCallID: "call-grand",
200
+ Status: model.AgentStatusLaunched,
201
+ TraceAvailability: model.TraceAvailabilityAvailable,
202
+ TraceSessionKey: fixture.catalog[1].Key,
203
+ TraceEventCount: 0,
204
+ LinkQuality: model.AgentLinkQualityExact,
205
+ LinkMethod: model.AgentLinkMethodCodexAgentID,
206
+ },
207
+ }
208
+ },
209
+ },
210
+ {
211
+ name: "orphan",
212
+ setup: func(t *testing.T) (codexAgentFixture, []model.AgentNode) {
213
+ fixture := newCodexAgentFixture(t, nil, codexAgentChildFixture{
214
+ id: "agent-orphan",
215
+ parentID: "root-session",
216
+ depth: 1,
217
+ label: "Orphan",
218
+ role: "explorer",
219
+ })
220
+ return fixture, []model.AgentNode{
221
+ mainAgentNode(fixture.root),
222
+ {
223
+ ID: codexAgentID(fixture.root, "session:"+fixture.catalog[0].Key),
224
+ ParentID: codexMainID(fixture.root),
225
+ Depth: 1,
226
+ Kind: model.AgentKindSubagent,
227
+ Label: "Orphan",
228
+ Role: "explorer",
229
+ Status: model.AgentStatusUnknown,
230
+ TraceAvailability: model.TraceAvailabilityAvailable,
231
+ TraceSessionKey: fixture.catalog[0].Key,
232
+ TraceEventCount: 0,
233
+ LinkQuality: model.AgentLinkQualityDerived,
234
+ LinkMethod: model.AgentLinkMethodCodexParentThreadID,
235
+ },
236
+ }
237
+ },
238
+ },
239
+ {
240
+ name: "malformed",
241
+ setup: func(t *testing.T) (codexAgentFixture, []model.AgentNode) {
242
+ fixture := newCodexAgentFixture(t, []any{
243
+ rawAgentJSONL(`{"type":"response_item","payload":`),
244
+ call("", "fc-valid", "call-valid", "spawn_agent", map[string]any{"message": "valid"}),
245
+ output("", "call-valid", `{"agent_id":"agent-valid"}`),
246
+ }, codexAgentChildFixture{
247
+ id: "agent-valid",
248
+ parentID: "root-session",
249
+ depth: 1,
250
+ label: "Valid",
251
+ })
252
+ seq := 0
253
+ return fixture, []model.AgentNode{
254
+ mainAgentNode(fixture.root),
255
+ {
256
+ ID: codexAgentID(fixture.root, "codex-agent:agent-valid"),
257
+ ParentID: codexMainID(fixture.root),
258
+ Depth: 1,
259
+ Kind: model.AgentKindSubagent,
260
+ Label: "Valid",
261
+ InstructionPreview: "valid",
262
+ LaunchSeq: &seq,
263
+ LaunchCallID: "call-valid",
264
+ Status: model.AgentStatusLaunched,
265
+ TraceAvailability: model.TraceAvailabilityAvailable,
266
+ TraceSessionKey: fixture.catalog[0].Key,
267
+ TraceEventCount: 0,
268
+ LinkQuality: model.AgentLinkQualityExact,
269
+ LinkMethod: model.AgentLinkMethodCodexAgentID,
270
+ },
271
+ }
272
+ },
273
+ },
274
+ {
275
+ name: "zero event",
276
+ setup: func(t *testing.T) (codexAgentFixture, []model.AgentNode) {
277
+ fixture := newCodexAgentFixture(t, []any{
278
+ call("", "fc-zero", "call-zero", "spawn_agent", map[string]any{"message": "zero"}),
279
+ output("", "call-zero", `{"agent_id":"agent-zero"}`),
280
+ }, codexAgentChildFixture{
281
+ id: "agent-zero",
282
+ parentID: "root-session",
283
+ depth: 1,
284
+ label: "Zero",
285
+ })
286
+ seq := 0
287
+ return fixture, []model.AgentNode{
288
+ mainAgentNode(fixture.root),
289
+ {
290
+ ID: codexAgentID(fixture.root, "codex-agent:agent-zero"),
291
+ ParentID: codexMainID(fixture.root),
292
+ Depth: 1,
293
+ Kind: model.AgentKindSubagent,
294
+ Label: "Zero",
295
+ InstructionPreview: "zero",
296
+ LaunchSeq: &seq,
297
+ LaunchCallID: "call-zero",
298
+ Status: model.AgentStatusLaunched,
299
+ TraceAvailability: model.TraceAvailabilityAvailable,
300
+ TraceSessionKey: fixture.catalog[0].Key,
301
+ TraceEventCount: 0,
302
+ LinkQuality: model.AgentLinkQualityExact,
303
+ LinkMethod: model.AgentLinkMethodCodexAgentID,
304
+ },
305
+ }
306
+ },
307
+ },
308
+ {
309
+ name: "no result is unknown",
310
+ setup: func(t *testing.T) (codexAgentFixture, []model.AgentNode) {
311
+ fixture := newCodexAgentFixture(t, []any{
312
+ call("", "fc-unknown", "call-unknown", "spawn_agent", map[string]any{
313
+ "agent_type": "default",
314
+ "message": "wait for output",
315
+ }),
316
+ })
317
+ seq := 0
318
+ return fixture, []model.AgentNode{
319
+ mainAgentNode(fixture.root),
320
+ {
321
+ ID: codexAgentID(fixture.root, "launch:"+codexMainID(fixture.root)+":call-unknown"),
322
+ ParentID: codexMainID(fixture.root),
323
+ Depth: 1,
324
+ Kind: model.AgentKindSubagent,
325
+ Label: "Subagent",
326
+ Role: "default",
327
+ InstructionPreview: "wait for output",
328
+ LaunchSeq: &seq,
329
+ LaunchCallID: "call-unknown",
330
+ Status: model.AgentStatusUnknown,
331
+ TraceAvailability: model.TraceAvailabilityUnavailable,
332
+ LinkQuality: model.AgentLinkQualityUnavailable,
333
+ LinkMethod: model.AgentLinkMethodUnavailable,
334
+ },
335
+ }
336
+ },
337
+ },
338
+ {
339
+ name: "stable ids and order",
340
+ setup: func(t *testing.T) (codexAgentFixture, []model.AgentNode) {
341
+ fixture := newCodexAgentFixture(t, []any{
342
+ call("", "fc-zulu", "call-zulu", "spawn_agent", map[string]any{"message": "zulu"}),
343
+ output("", "call-zulu", `{"agent_id":"agent-zulu"}`),
344
+ call("", "fc-read", "call-read", "exec_command", map[string]any{"cmd": "true"}),
345
+ output("", "call-read", "Process exited with code 0"),
346
+ call("", "fc-alpha", "call-alpha", "spawn_agent", map[string]any{"message": "alpha"}),
347
+ output("", "call-alpha", `{"agent_id":"agent-alpha"}`),
348
+ },
349
+ codexAgentChildFixture{id: "agent-orphan", parentID: "root-session", depth: 1, label: "Aaron"},
350
+ codexAgentChildFixture{id: "agent-alpha", parentID: "root-session", depth: 1, label: "Alpha"},
351
+ codexAgentChildFixture{id: "agent-zulu", parentID: "root-session", depth: 1, label: "Zulu"},
352
+ )
353
+ zuluSeq, alphaSeq := 0, 2
354
+ return fixture, []model.AgentNode{
355
+ mainAgentNode(fixture.root),
356
+ availableCodexLaunch(fixture.root, fixture.catalog[2], "agent-zulu", "call-zulu", "zulu", zuluSeq),
357
+ availableCodexLaunch(fixture.root, fixture.catalog[1], "agent-alpha", "call-alpha", "alpha", alphaSeq),
358
+ {
359
+ ID: codexAgentID(fixture.root, "session:"+fixture.catalog[0].Key),
360
+ ParentID: codexMainID(fixture.root),
361
+ Depth: 1,
362
+ Kind: model.AgentKindSubagent,
363
+ Label: "Aaron",
364
+ Status: model.AgentStatusUnknown,
365
+ TraceAvailability: model.TraceAvailabilityAvailable,
366
+ TraceSessionKey: fixture.catalog[0].Key,
367
+ LinkQuality: model.AgentLinkQualityDerived,
368
+ LinkMethod: model.AgentLinkMethodCodexParentThreadID,
369
+ },
370
+ }
371
+ },
372
+ },
373
+ }
374
+
375
+ for _, test := range tests {
376
+ t.Run(test.name, func(t *testing.T) {
377
+ fixture, want := test.setup(t)
378
+ first, err := fixture.adapter.BuildAgentGraph(fixture.root, fixture.catalog)
379
+ if err != nil {
380
+ t.Fatal(err)
381
+ }
382
+ second, err := fixture.adapter.BuildAgentGraph(fixture.root, fixture.catalog)
383
+ if err != nil {
384
+ t.Fatal(err)
385
+ }
386
+ if !reflect.DeepEqual(first, second) {
387
+ t.Fatalf("successive builds differ:\nfirst: %#v\nsecond: %#v", first, second)
388
+ }
389
+ if first.Version != model.AgentGraphVersion || first.RootSessionKey != fixture.root.Key {
390
+ t.Fatalf("graph header = %#v", first)
391
+ }
392
+ if !reflect.DeepEqual(first.Agents, want) {
393
+ t.Fatalf("agents:\n got: %#v\nwant: %#v", first.Agents, want)
394
+ }
395
+ })
396
+ }
397
+ }
398
+
399
+ func TestCodexAgentGraphLegacyTaskNameMergesDerivedChild(t *testing.T) {
400
+ fixture := newCodexAgentFixture(t, []any{
401
+ call("", "fc-legacy", "call-legacy", "spawn_agent", map[string]any{
402
+ "message": "review the legacy trace",
403
+ }),
404
+ output("", "call-legacy", `{"task_name":"/root/legacy-review"}`),
405
+ }, codexAgentChildFixture{
406
+ id: "legacy-child",
407
+ parentID: "root-session",
408
+ depth: 1,
409
+ agentPath: "/root/legacy-review",
410
+ label: "Legacy Reviewer",
411
+ })
412
+
413
+ graph, err := fixture.adapter.BuildAgentGraph(fixture.root, fixture.catalog)
414
+ if err != nil {
415
+ t.Fatal(err)
416
+ }
417
+ if len(graph.Agents) != 2 {
418
+ t.Fatalf("agents = %#v, want Main plus one merged legacy child", graph.Agents)
419
+ }
420
+ node := graph.Agents[1]
421
+ if node.Label != "Legacy Reviewer" || node.LaunchCallID != "call-legacy" ||
422
+ node.Status != model.AgentStatusLaunched || node.TraceAvailability != model.TraceAvailabilityAvailable ||
423
+ node.TraceSessionKey != fixture.catalog[0].Key {
424
+ t.Fatalf("legacy node = %#v", node)
425
+ }
426
+ }
427
+
428
+ func TestCodexAgentGraphUnknownJSONObjectIsNotFailure(t *testing.T) {
429
+ fixture := newCodexAgentFixture(t, []any{
430
+ call("", "fc-unknown-shape", "call-unknown-shape", "spawn_agent", map[string]any{"message": "try it"}),
431
+ output("", "call-unknown-shape", `{"message":"accepted"}`),
432
+ })
433
+
434
+ graph, err := fixture.adapter.BuildAgentGraph(fixture.root, fixture.catalog)
435
+ if err != nil {
436
+ t.Fatal(err)
437
+ }
438
+ if len(graph.Agents) != 2 || graph.Agents[1].Status != model.AgentStatusUnknown {
439
+ t.Fatalf("unknown output node = %#v", graph.Agents)
440
+ }
441
+ }
442
+
443
+ func TestCodexAgentGraphUsesStablePreorder(t *testing.T) {
444
+ fixture := newCodexAgentFixture(t, []any{
445
+ call("", "fc-a", "call-a", "spawn_agent", map[string]any{"message": "A"}),
446
+ output("", "call-a", `{"agent_id":"agent-a"}`),
447
+ call("", "fc-b", "call-b", "spawn_agent", map[string]any{"message": "B"}),
448
+ output("", "call-b", `{"agent_id":"agent-b"}`),
449
+ },
450
+ codexAgentChildFixture{
451
+ id: "agent-a", parentID: "root-session", depth: 1, label: "A",
452
+ lines: []any{
453
+ call("", "fc-a1", "call-a1", "spawn_agent", map[string]any{"message": "A1"}),
454
+ output("", "call-a1", `{"agent_id":"agent-a1"}`),
455
+ },
456
+ },
457
+ codexAgentChildFixture{id: "agent-b", parentID: "root-session", depth: 1, label: "B"},
458
+ codexAgentChildFixture{id: "agent-a1", parentID: "agent-a", depth: 2, label: "A1"},
459
+ )
460
+
461
+ graph, err := fixture.adapter.BuildAgentGraph(fixture.root, fixture.catalog)
462
+ if err != nil {
463
+ t.Fatal(err)
464
+ }
465
+ labels := make([]string, len(graph.Agents))
466
+ for i, node := range graph.Agents {
467
+ labels[i] = node.Label
468
+ }
469
+ want := []string{"Main", "A", "A1", "B"}
470
+ if !reflect.DeepEqual(labels, want) {
471
+ t.Fatalf("agent order = %v, want %v", labels, want)
472
+ }
473
+ }
474
+
475
+ func TestCodexAgentGraphInputsIncludeNestedChildren(t *testing.T) {
476
+ fixture := newCodexAgentFixture(t, nil,
477
+ codexAgentChildFixture{id: "agent-a", parentID: "root-session", depth: 1, label: "A"},
478
+ codexAgentChildFixture{id: "agent-a1", parentID: "agent-a", depth: 2, label: "A1"},
479
+ codexAgentChildFixture{id: "unrelated", parentID: "other-root", depth: 1, label: "Other"},
480
+ )
481
+
482
+ inputs, err := fixture.adapter.AgentGraphInputs(fixture.root, fixture.catalog)
483
+ if err != nil {
484
+ t.Fatal(err)
485
+ }
486
+ want := []string{fixture.root.Path, fixture.catalog[0].Path, fixture.catalog[1].Path}
487
+ sort.Strings(want)
488
+ if !reflect.DeepEqual(inputs, want) {
489
+ t.Fatalf("graph inputs = %v, want %v", inputs, want)
490
+ }
491
+ }
492
+
493
+ func newCodexAgentFixture(t *testing.T, rootLines []any, children ...codexAgentChildFixture) codexAgentFixture {
494
+ t.Helper()
495
+ dir := t.TempDir()
496
+ a := Adapter{Dir: dir}
497
+ rootPath := filepath.Join(dir, "root.jsonl")
498
+ writeAgentJSONL(t, rootPath, append([]any{codexRootSessionMeta()}, rootLines...)...)
499
+ root, err := a.Summarize(rootPath)
500
+ if err != nil {
501
+ t.Fatal(err)
502
+ }
503
+ catalog := make([]model.SessionMeta, 0, len(children))
504
+ for _, child := range children {
505
+ path := filepath.Join(dir, child.id+".jsonl")
506
+ lines := append([]any{codexChildSessionMeta(child)}, child.lines...)
507
+ writeAgentJSONL(t, path, lines...)
508
+ meta, err := a.Summarize(path)
509
+ if err != nil {
510
+ t.Fatal(err)
511
+ }
512
+ catalog = append(catalog, meta)
513
+ }
514
+ return codexAgentFixture{adapter: a, root: root, catalog: catalog}
515
+ }
516
+
517
+ func codexRootSessionMeta() map[string]any {
518
+ return map[string]any{
519
+ "type": "session_meta",
520
+ "payload": map[string]any{
521
+ "id": "root-session",
522
+ },
523
+ }
524
+ }
525
+
526
+ func codexChildSessionMeta(child codexAgentChildFixture) map[string]any {
527
+ return map[string]any{
528
+ "type": "session_meta",
529
+ "payload": map[string]any{
530
+ "id": child.id,
531
+ "session_id": "root-session",
532
+ "parent_thread_id": child.parentID,
533
+ "thread_source": "subagent",
534
+ "source": map[string]any{
535
+ "subagent": map[string]any{
536
+ "thread_spawn": map[string]any{
537
+ "parent_thread_id": child.parentID,
538
+ "depth": child.depth,
539
+ "agent_path": child.agentPath,
540
+ "agent_nickname": child.label,
541
+ "agent_role": child.role,
542
+ },
543
+ },
544
+ },
545
+ },
546
+ }
547
+ }
548
+
549
+ func writeAgentJSONL(t *testing.T, path string, values ...any) {
550
+ t.Helper()
551
+ var content []byte
552
+ for _, value := range values {
553
+ if raw, ok := value.(rawAgentJSONL); ok {
554
+ content = append(content, raw...)
555
+ content = append(content, '\n')
556
+ continue
557
+ }
558
+ line, err := json.Marshal(value)
559
+ if err != nil {
560
+ t.Fatal(err)
561
+ }
562
+ content = append(content, line...)
563
+ content = append(content, '\n')
564
+ }
565
+ if err := os.WriteFile(path, content, 0o644); err != nil {
566
+ t.Fatal(err)
567
+ }
568
+ }
569
+
570
+ func mainAgentNode(root model.SessionMeta) model.AgentNode {
571
+ return model.AgentNode{
572
+ ID: codexMainID(root),
573
+ Depth: 0,
574
+ Kind: model.AgentKindMain,
575
+ Label: "Main",
576
+ Status: model.AgentStatusMain,
577
+ TraceAvailability: model.TraceAvailabilityAvailable,
578
+ TraceSessionKey: root.Key,
579
+ TraceEventCount: root.EventCount,
580
+ LinkQuality: model.AgentLinkQualityExact,
581
+ LinkMethod: model.AgentLinkMethodRoot,
582
+ }
583
+ }
584
+
585
+ func availableCodexLaunch(root model.SessionMeta, child model.SessionMeta, agentID, callID, message string, seq int) model.AgentNode {
586
+ return model.AgentNode{
587
+ ID: codexAgentID(root, "codex-agent:"+agentID),
588
+ ParentID: codexMainID(root),
589
+ Depth: child.Agent.Depth,
590
+ Kind: model.AgentKindSubagent,
591
+ Label: child.Agent.Label,
592
+ InstructionPreview: message,
593
+ LaunchSeq: &seq,
594
+ LaunchCallID: callID,
595
+ Status: model.AgentStatusLaunched,
596
+ TraceAvailability: model.TraceAvailabilityAvailable,
597
+ TraceSessionKey: child.Key,
598
+ TraceEventCount: child.EventCount,
599
+ LinkQuality: model.AgentLinkQualityExact,
600
+ LinkMethod: model.AgentLinkMethodCodexAgentID,
601
+ }
602
+ }
603
+
604
+ func codexMainID(root model.SessionMeta) string {
605
+ return codexAgentID(root, "root:"+root.Key)
606
+ }
607
+
608
+ func codexAgentID(root model.SessionMeta, identity string) string {
609
+ return baseadapter.AgentNodeID((Adapter{}).Harness(), root.Key, identity)
610
+ }
@@ -0,0 +1,20 @@
1
+ package codex
2
+
3
+ import (
4
+ "path/filepath"
5
+ "testing"
6
+ )
7
+
8
+ func TestSummaryInputsDeclaresTitleIndex(t *testing.T) {
9
+ a := Adapter{Dir: filepath.Join("root", "sessions")}
10
+ inputs := a.SummaryInputs("any.jsonl")
11
+ want := filepath.Join("root", "session_index.jsonl")
12
+ if len(inputs) == 0 || inputs[0] != want {
13
+ t.Fatalf("SummaryInputs = %v, want first candidate %s", inputs, want)
14
+ }
15
+
16
+ override := Adapter{Dir: "root", IndexPath: filepath.Join("custom", "idx.jsonl")}
17
+ if inputs := override.SummaryInputs("any.jsonl"); len(inputs) != 1 || inputs[0] != override.IndexPath {
18
+ t.Fatalf("IndexPath override not honored: %v", inputs)
19
+ }
20
+ }