fix(herdr): rewrite CodexActivity against the real live rollout shape (S11)
The previous function_call/function_call_output shape was never verified and doesn't exist in any real Codex rollout. Confirmed the real shape against this machine's own ~/.codex/sessions files: file edits arrive as event_msg/patch_apply_end (changes+success, no pairing needed), and shell commands arrive as a freeform custom_tool_call named "exec" whose input is a JS snippet embedding cmd:"..." rather than a flat arguments object, with failure signaled by a literal "Script error:" prefix in the output text. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W1rkJ2hBMybnJctPbcy4tT
This commit is contained in:
@@ -72,6 +72,112 @@ func TestClaudeActivityPairsToolUseWithResult(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func codexPatchApplyLine(t *testing.T, callID string, success bool, paths ...string) string {
|
||||
t.Helper()
|
||||
changes := map[string]any{}
|
||||
for _, p := range paths {
|
||||
changes[p] = map[string]any{"type": "update"}
|
||||
}
|
||||
b, err := json.Marshal(map[string]any{
|
||||
"type": "event_msg",
|
||||
"payload": map[string]any{
|
||||
"type": "patch_apply_end",
|
||||
"call_id": callID,
|
||||
"success": success,
|
||||
"changes": changes,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func codexExecCallLine(t *testing.T, callID, input string) string {
|
||||
t.Helper()
|
||||
b, err := json.Marshal(map[string]any{
|
||||
"type": "response_item",
|
||||
"payload": map[string]any{
|
||||
"type": "custom_tool_call",
|
||||
"call_id": callID,
|
||||
"name": "exec",
|
||||
"input": input,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func codexExecOutputLine(t *testing.T, callID string, texts ...string) string {
|
||||
t.Helper()
|
||||
var out []map[string]any
|
||||
for _, tx := range texts {
|
||||
out = append(out, map[string]any{"type": "input_text", "text": tx})
|
||||
}
|
||||
b, err := json.Marshal(map[string]any{
|
||||
"type": "response_item",
|
||||
"payload": map[string]any{
|
||||
"type": "custom_tool_call_output",
|
||||
"call_id": callID,
|
||||
"output": out,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// Fixture shape confirmed live against this machine's own ~/.codex/sessions
|
||||
// rollout files on 2026-07-28 (see activity.go's CodexActivity doc comment) —
|
||||
// not a guess at the schema.
|
||||
func TestCodexActivityParsesRealRolloutShape(t *testing.T) {
|
||||
path := writeJSONL(t, []string{
|
||||
codexExecCallLine(t, "c1", `const r = await tools.exec_command({cmd:"go test ./...","workdir":"/repo"}); text(r.output)`),
|
||||
codexExecOutputLine(t, "c1", "Script completed\nWall time 0.1 seconds\nOutput:\n", "FAIL"),
|
||||
codexExecCallLine(t, "c2", `const r = await tools.exec_command({cmd:"git commit -am x"}); text(r.output)`),
|
||||
codexExecOutputLine(t, "c2", "Script completed\n"),
|
||||
codexPatchApplyLine(t, "exec-1", true, "/repo/main.go"),
|
||||
codexExecCallLine(t, "c3", `const r = await tools.update_plan({plan:[]}); text(r)`),
|
||||
codexExecOutputLine(t, "c3", "Script error:\nSyntaxError: bad"),
|
||||
})
|
||||
calls, err := CodexActivity(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(calls) != 4 {
|
||||
t.Fatalf("calls=%+v, want 4 (update_plan call resolved with empty kind/key, not dropped)", calls)
|
||||
}
|
||||
if calls[3].Kind != "" || calls[3].Key != "" {
|
||||
t.Fatalf("calls[3]=%+v, want empty kind/key for a call with no embedded cmd:", calls[3])
|
||||
}
|
||||
if calls[0].Kind != "command" || calls[0].Key != "go test ./..." || !calls[0].IsTest || !calls[0].Success {
|
||||
t.Fatalf("calls[0]=%+v", calls[0])
|
||||
}
|
||||
if calls[1].Kind != "command" || calls[1].Key != "git commit -am x" || !calls[1].Success {
|
||||
t.Fatalf("calls[1]=%+v", calls[1])
|
||||
}
|
||||
if calls[2].Kind != "file" || calls[2].Key != "/repo/main.go" || !calls[2].Success {
|
||||
t.Fatalf("calls[2]=%+v", calls[2])
|
||||
}
|
||||
}
|
||||
|
||||
func TestCodexActivityMarksScriptErrorAsFailure(t *testing.T) {
|
||||
path := writeJSONL(t, []string{
|
||||
codexExecCallLine(t, "c1", `const r = await tools.exec_command({cmd:"pytest"}); text(r.output)`),
|
||||
codexExecOutputLine(t, "c1", "Script error:\napply_patch verification failed"),
|
||||
})
|
||||
calls, err := CodexActivity(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(calls) != 1 || calls[0].Success {
|
||||
t.Fatalf("calls=%+v, want one failed call", calls)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDetectThrashConsecutiveTestFailures(t *testing.T) {
|
||||
calls := []ToolCall{
|
||||
{Name: "Bash", Kind: "command", Key: "go test ./...", Success: false, IsTest: true},
|
||||
|
||||
Reference in New Issue
Block a user