diff --git a/AGENTS.md b/AGENTS.md index 5cd72d7..c309106 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -6,6 +6,7 @@ These are requirements, not preferences. Changing one needs the owner's agreemen - Never deploy unless the owner asks for it in the current turn. A finished change is reported undeployed; the owner deploys it, or asks you to. Never run `go install ./cmd/byre` or restart the service on your own initiative, not even to check that a change works. - `docs/operations.md` describes the layout, data, service and APIs; keep it current with every change. +- Never add tests. byre has no test suite; check a change with `go vet ./...` and `go generate ./web` (which type-checks the UI). ## Deploying diff --git a/api/openapi.yaml b/api/openapi.yaml index 7f3a7a9..5cfebd5 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -630,7 +630,7 @@ paths: - { name: sessionId, in: path, required: true, schema: { type: string } } post: operationId: sendMessage - description: Starts a new turn with this message. The session must be idle. + description: Starts a new turn with this message when the session is idle. While a task session's turn is running, the message is queued and reaches the agent after its next tool call, or as the next turn. requestBody: required: true content: diff --git a/docs/claude-protocol.md b/docs/claude-protocol.md index 10e9333..a95871d 100644 --- a/docs/claude-protocol.md +++ b/docs/claude-protocol.md @@ -1,6 +1,6 @@ # Claude Code stream-json protocol -How `internal/claude` drives the `claude` CLI. Worked out from the Agent SDKs (`@anthropic-ai/claude-agent-sdk` on npm, `claude-agent-sdk` on PyPI); the Python SDK's `_internal/query.py` and `_internal/transport/subprocess_cli.py` are the most readable references. Each test run writes its transcripts to `internal/claude/testdata/transcripts/` (gitignored); the CLI version that run used is `claude_code_version` in the `system`/`init` event of any of them. +How `internal/claude` drives the `claude` CLI. Worked out from the Agent SDKs (`@anthropic-ai/claude-agent-sdk` on npm, `claude-agent-sdk` on PyPI); the Python SDK's `_internal/query.py` and `_internal/transport/subprocess_cli.py` are the most readable references. Transcripts (`Options.Transcript`) record every line in both directions as `{"dir":"send"|"recv","msg":...}`. The account email from the `initialize` response is replaced with `[redacted]` everywhere it appears. diff --git a/docs/codex-protocol.md b/docs/codex-protocol.md index a5dfab7..5f0bb43 100644 --- a/docs/codex-protocol.md +++ b/docs/codex-protocol.md @@ -48,13 +48,6 @@ Closing stdin ends the process. Besides `item/tool/call`, server requests (approvals, user input) shouldn't arrive with approvals off; if one does, command and file-change approvals are accepted and anything else gets a JSON-RPC error. -## Transcripts and contract tests +## Transcripts -`Options.Transcript` records every line in both directions as `{"dir":"send"|"recv","msg":...}`, deltas included, with any `"email"` value replaced by `[redacted]`. - -The tests in `internal/codex/session_internal_test.go` are scenarios (commit identity, text deltas, interrupt, image input, resume, model switch, compaction, tools across a resume, fork, steer), each run two ways: - -- `TestLive` runs it against the real CLI with the models named by `testModel` and `switchModel`, and writes transcripts to `internal/codex/testdata/transcripts/` (gitignored). -- `TestContract` runs the same scenario against the committed transcripts in `internal/codex/testdata/contract/`. A replay server stands in for the CLI: it expects each recorded `send` line in turn (method and params equal, `cwd` aside) and writes each recorded `recv` line, so the client must send exactly what the recording shows and handle exactly what the server sent. - -Both then decode every received line of their transcripts strictly into the generated types, so a field or method the types don't know fails the test. After a CLI update: `go generate ./internal/codex`, `go test ./internal/codex -run TestLive`, and when it passes, `cp internal/codex/testdata/transcripts/*.jsonl internal/codex/testdata/contract/`. Check the copies for anything private before committing them. +`Options.Transcript` records every line in both directions as `{"dir":"send"|"recv","msg":...}`, deltas included, with any `"email"` value replaced by `[redacted]`. After a CLI update, run `go generate ./internal/codex` to regenerate the types. diff --git a/docs/operations.md b/docs/operations.md index 769b8de..13a92bb 100644 --- a/docs/operations.md +++ b/docs/operations.md @@ -88,7 +88,7 @@ curl -H 'Tailscale-User-Login: you@example.com' -H 'Tailscale-User-Name: You' ht ## Agent sessions -`POST /api/tasks/{id}/sessions` starts a Claude session in the task's worktree and sends the prompt as its first turn; `POST /api/sessions/{id}/messages` starts later turns and `POST /api/sessions/{id}/interrupt` ends the current one. A task has at most one session that isn't `completed`. A message may carry `model`; when it differs from the session's, byre writes it to `agent_sessions.model`, records a `session.model` event (shown as a note), and sends the live CLI a `set_model` control request before the message, so the switch applies from that turn and survives a resume (`--model`) or compaction. The composer's model menu sends `model` only when the choice differs from the session's model, or from the `session.context` model when the session has none. The session's commits use the git identity of the user who created it, so that user must have set one with `PUT /api/me`. +`POST /api/tasks/{id}/sessions` starts a Claude session in the task's worktree and sends the prompt as its first turn; `POST /api/sessions/{id}/messages` starts later turns and `POST /api/sessions/{id}/interrupt` ends the current one. A message sent while a task session's turn is running is queued (`Manager.Message` in `internal/agents/queue.go`) in `task_inputs`, keyed by task so it survives a handoff to a new session, and the request returns the running session with 202. A model it carries is applied to the session at once and takes effect from the next turn. The agent gets queued messages after its next tool call, the same way as inbox answers (see "Inbox"): each is recorded as its own `user.message` with the sender as actor and `mid_turn: true`, and the agent reads them, headed " sent you this message while you were working", in the hook's `additionalContext` (Codex: `turn/steer`). Messages are handed over oldest first, stopping at the first one with images, since mid-turn delivery carries only text; none are handed over during a handoff turn. Whatever is still queued when the turn ends goes out one message per turn, ahead of held inbox answers, from `Deliver`; startup does the same for idle sessions. Closing the task deletes its queued messages. `sqlite3 byre.db 'SELECT * FROM task_inputs'` lists them. A task has at most one session that isn't `completed`. A message may carry `model`; when it differs from the session's, byre writes it to `agent_sessions.model`, records a `session.model` event (shown as a note), and sends the live CLI a `set_model` control request before the message, so the switch applies from that turn and survives a resume (`--model`) or compaction. The composer's model menu sends `model` only when the choice differs from the session's model, or from the `session.context` model when the session has none. The session's commits use the git identity of the user who created it, so that user must have set one with `PUT /api/me`. Images: `POST /api/blobs` takes a raw image body (type sniffed from the bytes, PNG, JPEG, GIF or WebP only, at most 5 MiB, `maxBlobBytes` in `internal/server/blobs.go`) and returns its id; `GET /api/blobs/{id}` serves it. A message or session prompt may carry `images`, a list of blob ids (unknown ids are a 400); a message may then have empty text. byre reads each blob when the turn is sent and passes it before the text: to Claude as a base64 `image` content block (`claude.Session.Send`), to Codex as an `image` input with a `data:` URL (`codex.Session.Send`). The `user.message` event records the ids as `images`. In the composer, pasting or dropping an image adds a thumbnail with a × to remove it; images upload when the message is sent, and the transcript shows them on the user's message, each linking to the full image. @@ -129,7 +129,7 @@ The Status modal (the navbar's **Status** button; Esc, ×, a click outside it or Agent activity becomes events with `agent_session_id` set: `user.message`, `agent.message`, `agent.thinking`, `agent.tool_call`, `agent.tool_result`, `turn.ended`, `session.state`, `session.model`, and `session.context`. Anything else Claude Code emits (rate-limit status, task progress, state changes) is recorded unchanged as `agent.vendor`. Codex items map onto the same kinds (`handleCodex` in `internal/agents/codex.go`): an `agentMessage` becomes `agent.message`, a `reasoning` item's summary `agent.thinking`, and `commandExecution` (as `Bash`), `fileChange` (as `apply_patch`), `dynamicToolCall`, `mcpToolCall` and `webSearch` items an `agent.tool_call` when they start and an `agent.tool_result` when they complete. Other completed items and the `error` and `warning` notifications are recorded as `agent.vendor`; the remaining notifications (thread status, MCP startup and the like) aren't recorded. `item/agentMessage/delta` becomes `agent.delta`, and `thread/tokenUsage/updated` `session.context`. The one exception is partial output: each top-level `text_delta` is broadcast to SSE clients as an `agent.delta` event (`payload.text`, `seq` 0) that is never stored, and the transcript grows a live message from them until the finished `agent.message` replaces it. A `turn.ended` event carries `inbox_item_id` when its final message was filed as a `report` item. Activity inside a subagent (`agent.message`, `agent.thinking`, `agent.tool_call`, `agent.tool_result`) carries `payload.parent_tool_use_id`, the ID of the tool call that started the subagent, and never updates `session.context`. `session.context` records the context size whenever it changes: `used` is the last assistant message's input, cache-read, cache-creation and output tokens, and `window` is the model's context window from the turn's `result` (`modelUsage[model].contextWindow`), so it is 0 until the session's first turn after a server start ends. The task header's **Compact** button shows the latest one as a background fill across the button, proportional to the window, tinted amber from 70% and red from 90%; the exact count is in its tooltip. -Messages byre itself sends an agent (actor `byre`: the restart notice, Sync and Merge prompts, Compact's handoff request) show collapsed like a tool call, labelled `byre` with the first line as the brief; expanding it shows the full text. The transcript view has a border in the task's colour (see "Task colours"; the accent in the lead pane) while the session is `running`, and a braille spinner on its last line in the same colour, cycling ⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏ every 0.8s. It is one element with one CSS animation: the ten frames are drawn side by side in an inline SVG used as the `.spinner` mask over a coloured background, and the `braille` keyframes in `web/style.css` step the mask position through them. Orion freezes animated GIFs and per-dot `step-end` opacity animations partway through a turn, so it stays a single element with a single animation. Consecutive tool calls (with any thinking between them) collapse into one group whose summary counts calls per tool and file, and file paths show relative to the worktree. A Claude Code subagent call (the `Agent` or `Task` tool) stands on its own, collapsed, labelled with its `subagent_type` and `description`; expanding it shows the prompt and the call's result. Its summary carries a status from the `agent.vendor` `system` events whose `tool_use_id` names it (`task_started`: running, or running in the background; `task_progress`: its `description`; `task_notification`: its `status`, red unless `completed`). The subagent's own messages, thinking and tool calls (events whose `parent_tool_use_id` names the call) stream inline with the main agent's, in arrival order, each marked `↳ ` with the description as its tooltip; its messages also get an accent rule down their left edge. A run of tool calls groups only calls from the same agent, so a call from another agent starts a new group. A subagent's messages never end the main agent's live message, count as a turn's final message or hide a quiet turn. Claude Code streams a background subagent's activity, but for a foreground one only its prompt, so a foreground subagent shows only as its call, its status and its result. A turn's final message that is at least half visible in the transcript while the page is visible dismisses its `report` inbox item, as the viewing user. Selecting a task that has never had a session focuses the composer. In the composer, Enter sends and Shift-Enter adds a newline; unsent text is kept per task in memory, so it survives switching tabs or tasks but not a page reload. The composer has no `@`-file mentions or path picker: text reaches the agent exactly as typed (stream-json doesn't expand `@path` the way the interactive CLI does), and the agent reads any file the message names with its own tools. The composer is the same whether or not the task has a live session: Send posts to the session's messages, or starts a session with the text when there is none or the last one completed. A sent message shows at once as a "sending…" bubble, replaced by the server's `user.message` event with the same text when it arrives, and removed (text and images back in the composer) if the request fails. +Messages byre itself sends an agent (actor `byre`: the restart notice, Sync and Merge prompts, Compact's handoff request) show collapsed like a tool call, labelled `byre` with the first line as the brief; expanding it shows the full text. The transcript view has a border in the task's colour (see "Task colours"; the accent in the lead pane) while the session is `running`, and a braille spinner on its last line in the same colour, cycling ⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏ every 0.8s. It is one element with one CSS animation: the ten frames are drawn side by side in an inline SVG used as the `.spinner` mask over a coloured background, and the `braille` keyframes in `web/style.css` step the mask position through them. Orion freezes animated GIFs and per-dot `step-end` opacity animations partway through a turn, so it stays a single element with a single animation. Consecutive tool calls (with any thinking between them) collapse into one group whose summary counts calls per tool and file, and file paths show relative to the worktree. A Claude Code subagent call (the `Agent` or `Task` tool) stands on its own, collapsed, labelled with its `subagent_type` and `description`; expanding it shows the prompt and the call's result. Its summary carries a status from the `agent.vendor` `system` events whose `tool_use_id` names it (`task_started`: running, or running in the background; `task_progress`: its `description`; `task_notification`: its `status`, red unless `completed`). The subagent's own messages, thinking and tool calls (events whose `parent_tool_use_id` names the call) stream inline with the main agent's, in arrival order, each marked `↳ ` with the description as its tooltip; its messages also get an accent rule down their left edge. A run of tool calls groups only calls from the same agent, so a call from another agent starts a new group. A subagent's messages never end the main agent's live message, count as a turn's final message or hide a quiet turn. Claude Code streams a background subagent's activity, but for a foreground one only its prompt, so a foreground subagent shows only as its call, its status and its result. A turn's final message that is at least half visible in the transcript while the page is visible dismisses its `report` inbox item, as the viewing user. Selecting a task that has never had a session focuses the composer. In the composer, Enter sends and Shift-Enter adds a newline; unsent text is kept per task in memory, so it survives switching tabs or tasks but not a page reload. The composer has no `@`-file mentions or path picker: text reaches the agent exactly as typed (stream-json doesn't expand `@path` the way the interactive CLI does), and the agent reads any file the message names with its own tools. The composer is the same whether or not the task has a live session, and stays enabled while a turn runs: Send posts to the session's messages, or starts a session with the text when there is none or the last one completed. A sent message shows at once as a bubble reading "queued…" while the session is running and "sending…" otherwise, replaced by the server's `user.message` event with the same text when it arrives, and removed (text and images back in the composer) if the request fails. The task switcher (⌘K, or the project ▸ task picker at the right end of the right pane's tab strip) lists active tasks with their session state and open inbox item count, and **merge held** on a task with `merge_queued_at`, live while it is open. Tasks with inbox items come first; within each group, the task least recently visited in this browser comes first (never-visited tasks before all others, ties in the server's order: projects by name, tasks newest first). Visit times are kept in the browser's localStorage under `byre.visits`, stamped when a task is selected and again when it is left. After the tasks come the projects, one row each; picking one points the right pane at the project with no task, on the Project tab. Typing filters both and adds a last row that creates a task with the typed text as its title, in the right pane's project (the first project when there is none); Enter picks the highlighted row. Picking a task points the right pane at it on the Transcript tab (see "Lead agent"). @@ -140,7 +140,7 @@ The task switcher (⌘K, or the project ▸ task picker at the right end of the Each user has one long-running lead agent, shown in the left pane. It is not tied to a project: it watches every task, tells the human what needs them, points the right pane at it, and acts on the other agents. Its code is `internal/agents/lead.go` (sessions, held input, signals) and `internal/agents/leadtools.go` (its MCP tools). - **Sessions.** A lead session is an `agent_sessions` row with `owner` set to the user's login, `role` `lead` and no `task_id`. Its events carry `owner` and no project or task. It runs in `leads/` with `leadGuidance` (passed and re-attached the same way as a task agent's guidance) and the lead tools instead of the task tools. It has no Stop-hook bounce, no snapshots, no end-of-turn commit and no inbox report; a turn that ends in error records the error on its `session.state` event, which the pane shows as a note. -- **Messages.** `POST /api/me/lead/messages` sends the caller's message to their lead, starting a lead session when there is none. While the lead is mid-turn the message is held in `lead_inputs` and becomes its next turn; the composer stays enabled. `GET /api/me/lead/sessions` lists the caller's lead sessions; interrupt and compact use the usual session endpoints. +- **Messages.** `POST /api/me/lead/messages` sends the caller's message to their lead, starting a lead session when there is none. While the lead is mid-turn the message is held in `lead_inputs`; the lead's PostToolUse hook (Codex: `turn/steer` after each completed tool item) hands held messages over after its next tool call, like a task's queued messages (see "Agent sessions"), and any still held become its next turn. Signals are never handed over mid-turn. The composer stays enabled. `GET /api/me/lead/sessions` lists the caller's lead sessions; interrupt and compact use the usual session endpoints. - **Signals.** Every published event goes through `Manager.Observe`. These become signals in `lead_inputs` for every user who has a lead session: `inbox.created` (a question with its choices and recommendation, a report's text cut to 1500 characters, an error or rate limit), `inbox.updated` when a human answered a question or error, `task.created` (naming the TODO item it was started from, if any), `task.merged` and `task.discarded`. The human switching the right pane is not a signal. A signal caused by the lead itself (actor ` (lead)`) is skipped, as is the update from an answer it gave. A task's `task.created` event carries the actor that created it, separately from its `created_by` login: tasks the lead creates with `create_task` or `start_todo` keep the human's login as `created_by` but have actor ` (lead)`. Whenever the lead is idle, byre sends the oldest held message from the human as its own turn, or else every held signal as one `byre` message headed "Signals since your last turn (n)". Its guidance tells it not to respond to the human's own actions, and to make its final message exactly `NO_REPLY` when a batch needs nothing from it. A transcript (`quietReply` in `web/src/task.ts`) never draws an `agent.message` whose last line is `NO_REPLY` (the model sometimes writes a sentence before it), and hides the live partial message while its text is still a prefix of it. A lead turn that byre starts without a handoff (a signal batch or a handoff continuation) is held (`ctx.held`): its `byre` message isn't drawn, the spinner stays off and its partial message stays hidden, until the lead draws something else, such as a tool call, thinking or a real message, which draws the `byre` message ahead of it. A `NO_REPLY` turn that drew nothing else ends without anything of it ever drawn; one that did keeps all of it, without the `NO_REPLY`. - **Tools.** `list_tasks`, `read_transcript` (a task's events in brief, the last `limit` or those after `after_seq`), `read_inbox`, `answer_question` (an `option`, `other` or `dismiss` response recorded as the user, through the same checks as `POST /api/inbox/responses`), `send_message` (starts the task's agent if it has none; refused while it is mid-turn), `create_task`, `start_todo`, `interrupt`, `compact_task`, `merge_task`, `discard_task`, `show` and `compact_self`. The lead also has Claude Code's own tools and reads worktrees by path. Messages it sends task agents have actor ` (lead)`, so they don't make the user a participant; tasks, merges, discards and answers are done as the user. Its guidance says to answer a question only when the answer is clear or the human gave it, and to merge or discard only when the human asks. - **Focus.** `users.focus` holds what the user's right pane shows: `project_id`, `task_id`, `tabs` (the tabs put first, in order) and `tab` (the selected one). With a `task_id`, `project_id` is always that task's project, filled in by the server; with only a `project_id`, the pane shows that project with no task, which only the Project tab has anything for. `GET /api/me/focus` reads it; `PUT /api/me/focus` and the lead's `show` set it, refusing an unknown tab, a task that isn't active or an unknown project, and record a `focus.updated` event owned by the user. The page applies the focus on load and on every `focus.updated` it didn't cause itself; the human's own changes (⌘K, the right pane's task picker, clicking a tab, a task link, a notification) are sent with `PUT /api/me/focus`. The URL follows the focus: `#/task/`, `#/project/`, or `#/` for neither. @@ -172,7 +172,7 @@ Agents ask questions with the `ask_human` MCP tool. Each question carries a `con | `other` (free text in `text`: an answer, a question back, or new instructions) | questions, errors | `answered` | | `dismiss` | reports, errors | `dismissed` | -Answers reach the agent as one message holding every undelivered `option` and `other` response of the session (`deliveryMessage` in `internal/agents/inbox.go`), never while one of its blocking questions is still open; until then they stay held (`inbox_responses.delivered_at` empty). While a turn is running, the task session's PostToolUse hook (`afterTool` in `internal/agents/snapshots.go`, through `deliverMidTurn`) hands them over after the agent's next tool call as the hook's `additionalContext`, so the agent reads them alongside that tool's result without being interrupted (for Codex, after each completed tool item, `deliverMidTurn` hands them over with `turn/steer` instead); the `user.message` event recording it carries `mid_turn: true` and no `head`, and the transcript doesn't treat it as a turn start. A turn that makes no further tool call leaves them to `Deliver`, which runs after each submission and when a turn ends, and sends them as a new turn once the session is `idle`. The log traces each answer: `inbox response recorded` when it is stored, `deliver held` with the reason, `deliver sent mid-turn`, `deliver sent` with the session's state and whether its agent process had exited or byre was closing, and `undelivered responses` with a reason wherever a session has held answers when its turn ends in a handoff or merge, its agent process ends, startup resumes it, or startup finds it idle. `sqlite3 byre.db "SELECT * FROM inbox_responses WHERE delivered_at IS NULL"` lists the answers held now. +Answers reach the agent as one message holding every undelivered `option` and `other` response of the session (`deliveryMessage` in `internal/agents/inbox.go`), never while one of its blocking questions is still open; until then they stay held (`inbox_responses.delivered_at` empty). While a turn is running, the task session's PostToolUse hook (`afterTool` in `internal/agents/snapshots.go`, through `deliverMidTurn`) hands them over after the agent's next tool call as the hook's `additionalContext`, after any queued messages (see "Agent sessions"), so the agent reads them alongside that tool's result without being interrupted (for Codex, after each completed tool item, `deliverMidTurn` hands them over with `turn/steer` instead); the `user.message` event recording it carries `mid_turn: true` and no `head`, and the transcript doesn't treat it as a turn start. A turn that makes no further tool call leaves them to `Deliver`, which runs after each submission and when a turn ends, and sends them as a new turn once the session is `idle`. The log traces each answer: `inbox response recorded` when it is stored, `deliver held` with the reason, `deliver sent mid-turn`, `deliver sent` with the session's state and whether its agent process had exited or byre was closing, and `undelivered responses` with a reason wherever a session has held answers when its turn ends in a handoff or merge, its agent process ends, startup resumes it, or startup finds it idle. `sqlite3 byre.db "SELECT * FROM inbox_responses WHERE delivered_at IS NULL"` lists the answers held now. ## Notifications diff --git a/internal/agents/choices_internal_test.go b/internal/agents/choices_internal_test.go deleted file mode 100644 index a0a69e4..0000000 --- a/internal/agents/choices_internal_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package agents - -import ( - "slices" - "testing" -) - -func TestParseChoices(t *testing.T) { - src := "Background with a [link](https://example.com) and (parens).\n\n" + - "- ( ) **Merge now**: ships today\n" + - "* (*) Wait for review \n" + - " - [ ] nested checkbox\n" + - "+ [x] checked\n" + - "- [X] upper\n" + - "- plain item\n" + - "-( ) no space\n" + - "- ( )\n" + - "```\n- (*) fenced\n```\n" + - "~~~\n- ( ) tilde fenced\n~~~\n" + - "> - ( ) quoted\n" - - got := parseChoices(src) - want := []choice{ - {text: "**Merge now**: ships today"}, - {text: "Wait for review", recommended: true}, - {text: "nested checkbox", checkbox: true}, - {text: "checked", checkbox: true, recommended: true}, - {text: "upper", checkbox: true, recommended: true}, - } - if !slices.Equal(got, want) { - t.Fatalf("parseChoices:\n got %+v\nwant %+v", got, want) - } -} diff --git a/internal/agents/codex.go b/internal/agents/codex.go index d9e2917..e579495 100644 --- a/internal/agents/codex.go +++ b/internal/agents/codex.go @@ -160,7 +160,7 @@ func (m *Manager) codexItemCompleted(ctx context.Context, l *live, s *codex.Sess return err } - if !l.implementer() { + if l.reviewID != "" { return nil } text := m.deliverMidTurn(ctx, l) diff --git a/internal/agents/fork_internal_test.go b/internal/agents/fork_internal_test.go deleted file mode 100644 index f84c393..0000000 --- a/internal/agents/fork_internal_test.go +++ /dev/null @@ -1,49 +0,0 @@ -package agents - -import ( - "testing" - - "g.fc.run/misc/byre/internal/api" -) - -func TestForkPoint(t *testing.T) { - s1, s2 := "s1", "s2" - ev := func(seq int64, sess *string, kind string, payload map[string]any) api.Event { - return api.Event{Seq: seq, AgentSessionId: sess, Kind: kind, Payload: payload} - } - events := []api.Event{ - ev(1, &s1, "user.message", map[string]any{"text": "go"}), - ev(2, &s1, "agent.message", map[string]any{"text": "on it", "vendor_uuid": "u-msg"}), - ev(3, &s1, "agent.tool_call", map[string]any{"id": "a", "vendor_uuid": "u-call-a"}), - ev(4, &s1, "agent.tool_call", map[string]any{"id": "b", "vendor_uuid": "u-call-b"}), - ev(5, &s1, "agent.tool_call", map[string]any{"id": "x", "parent_tool_use_id": "b"}), - ev(6, nil, "snapshot.created", map[string]any{"n": 1}), - ev(7, &s1, "agent.tool_result", map[string]any{"tool_use_id": "a", "vendor_uuid": "u-res-a"}), - ev(8, &s2, "agent.tool_result", map[string]any{"tool_use_id": "b", "vendor_uuid": "u-other"}), - ev(9, &s1, "agent.tool_result", map[string]any{"tool_use_id": "b", "vendor_uuid": "u-res-b"}), - ev(10, &s1, "agent.message", map[string]any{"text": "done", "vendor_uuid": "u-done"}), - ev(11, &s1, "turn.ended", map[string]any{}), - ev(12, &s1, "user.message", map[string]any{"text": "more"}), - ev(13, &s1, "agent.tool_call", map[string]any{"id": "c", "vendor_uuid": "u-call-c"}), - ev(14, &s1, "agent.tool_result", map[string]any{"tool_use_id": "c", "vendor_uuid": "u-res-c"}), - } - - for _, c := range []struct { - seq int64 - want string - }{ - {1, ""}, - {2, "u-msg"}, - {3, "u-res-b"}, - {5, "u-res-b"}, - {9, "u-res-b"}, - {11, "u-done"}, - {12, "u-done"}, - {13, "u-res-c"}, - } { - got := forkPoint(events, s1, c.seq) - if got != c.want { - t.Errorf("forkPoint at seq %d = %q, want %q", c.seq, got, c.want) - } - } -} diff --git a/internal/agents/health_internal_test.go b/internal/agents/health_internal_test.go deleted file mode 100644 index 4221558..0000000 --- a/internal/agents/health_internal_test.go +++ /dev/null @@ -1,106 +0,0 @@ -package agents - -import ( - "fmt" - "strings" - "testing" - "time" - - "g.fc.run/misc/byre/internal/api" -) - -func TestAssessStuck(t *testing.T) { - now := time.Now() - procs := []procInfo{ - {sessionID: "a", pid: 10, state: api.AgentSessionStateRunning, lastLine: now.Add(-StuckAfter - time.Second)}, - {sessionID: "b", pid: 11, state: api.AgentSessionStateRunning, lastLine: now.Add(-StuckAfter + time.Second)}, - {sessionID: "c", pid: 12, state: api.AgentSessionStateIdle, lastLine: now.Add(-time.Hour)}, - } - running := []api.AgentSession{{Id: "a"}, {Id: "b"}} - - h := assess(now, procs, running, []int{10, 11, 12}) - - want := map[string]api.AgentProcessHealth{"a": api.HealthStuck, "b": api.HealthOk, "c": api.HealthOk} - for _, p := range h.Processes { - if p.Health != want[p.SessionId] { - t.Errorf("session %s health = %s, want %s", p.SessionId, p.Health, want[p.SessionId]) - } - } - if len(h.Zombies) != 0 { - t.Errorf("zombies = %+v, want none", h.Zombies) - } -} - -func TestAssessZombies(t *testing.T) { - now := time.Now() - procs := []procInfo{ - {sessionID: "exited", pid: 20, state: api.AgentSessionStateRunning, lastLine: now, exited: true}, - {sessionID: "done", pid: 21, state: api.AgentSessionStateCompleted, lastLine: now}, - {sessionID: "idle", pid: 22, state: api.AgentSessionStateIdle, lastLine: now}, - } - running := []api.AgentSession{{Id: "exited", TaskId: "t1"}, {Id: "gone", TaskId: "t2"}} - - h := assess(now, procs, running, []int{21, 22, 99}) - - got := []string{} - for _, z := range h.Zombies { - key := string(z.Kind) - if z.SessionId != nil { - key += " " + *z.SessionId - } - if z.Pid != nil { - key += fmt.Sprintf(" %d", *z.Pid) - } - got = append(got, key) - } - want := []string{ - "process_without_session done 21", - "running_without_process exited", - "running_without_process gone", - "process_without_session 99", - } - if strings.Join(got, "\n") != strings.Join(want, "\n") { - t.Errorf("zombies:\n%s\nwant:\n%s", strings.Join(got, "\n"), strings.Join(want, "\n")) - } - - for _, p := range h.Processes { - if p.SessionId == "exited" && p.Health != api.HealthExited { - t.Errorf("exited process health = %s", p.Health) - } - } -} - -func TestBadLinesRing(t *testing.T) { - b := newBadLines() - at := time.Now() - for i := range keptBadLines + 5 { - b.add("claude", "s", fmt.Sprintf("reason %d", i), []byte(fmt.Sprintf("line %d", i)), at) - } - b.add("other", "s", "big", []byte(strings.Repeat("x", badLineBytes*2)), at) - - snap := b.snapshot() - if snap.Counts["claude"] != keptBadLines+5 || snap.Counts["other"] != 1 { - t.Errorf("counts = %v", snap.Counts) - } - if len(snap.Recent) != keptBadLines { - t.Fatalf("kept %d, want %d", len(snap.Recent), keptBadLines) - } - if snap.Recent[0].Reason != "big" || len(snap.Recent[0].Raw) != badLineBytes || snap.Recent[0].Size != badLineBytes*2 { - t.Errorf("newest = %q, %d raw bytes, size %d", snap.Recent[0].Reason, len(snap.Recent[0].Raw), snap.Recent[0].Size) - } - if snap.Recent[1].Raw != fmt.Sprintf("line %d", keptBadLines+4) { - t.Errorf("second newest = %q", snap.Recent[1].Raw) - } - oldest := snap.Recent[len(snap.Recent)-1].Raw - if oldest != "line 6" { - t.Errorf("oldest = %q, want line 6", oldest) - } -} - -func TestParseChildren(t *testing.T) { - out := []byte(" 100 1 launchd\n 200 100 claude\n 201 100 /Users/x/.local/bin/claude\n 202 100 git\n 203 999 claude\n") - got := parseChildren(out, 100) - if fmt.Sprint(got) != "[200 201]" { - t.Errorf("children = %v", got) - } -} diff --git a/internal/agents/inbox.go b/internal/agents/inbox.go index 1a02eac..7f62035 100644 --- a/internal/agents/inbox.go +++ b/internal/agents/inbox.go @@ -305,6 +305,17 @@ func (m *Manager) Deliver(ctx context.Context, sessionID string) error { m.deliverMu.Lock() defer m.deliverMu.Unlock() + sess, err := m.store.GetSession(ctx, sessionID) + if err != nil { + return err + } + if sess.State == api.AgentSessionStateIdle { + sent, err := m.deliverQueued(ctx, sess) + if err != nil || sent { + return err + } + } + pending, err := m.store.Undelivered(ctx, sessionID) if err != nil { return err @@ -314,10 +325,6 @@ func (m *Manager) Deliver(ctx context.Context, sessionID string) error { } ids := responseIDs(pending) - sess, err := m.store.GetSession(ctx, sessionID) - if err != nil { - return err - } if sess.State != api.AgentSessionStateIdle { slog.Info("deliver held: session not idle", "session", sessionID, "state", sess.State, "responses", ids) return nil @@ -362,9 +369,30 @@ func (m *Manager) Deliver(ctx context.Context, sessionID string) error { } func (m *Manager) deliverMidTurn(ctx context.Context, l *live) string { + if l.owner != "" { + msgs := m.takeLeadQueuedMidTurn(ctx, l) + if len(msgs) == 0 { + return "" + } + return m.recordQueued(ctx, l, msgs) + } + m.deliverMu.Lock() defer m.deliverMu.Unlock() + parts := []string{} + msgs := m.takeQueuedMidTurn(ctx, l) + if len(msgs) > 0 { + parts = append(parts, m.recordQueued(ctx, l, msgs)) + } + responses := m.responsesMidTurn(ctx, l) + if responses != "" { + parts = append(parts, responses) + } + return strings.Join(parts, "\n\n") +} + +func (m *Manager) responsesMidTurn(ctx context.Context, l *live) string { pending, err := m.store.Undelivered(ctx, l.id) if err != nil { slog.Error("list undelivered responses", "session", l.id, "err", err) diff --git a/internal/agents/inbox_internal_test.go b/internal/agents/inbox_internal_test.go deleted file mode 100644 index 2ff17b4..0000000 --- a/internal/agents/inbox_internal_test.go +++ /dev/null @@ -1,242 +0,0 @@ -package agents - -import ( - "context" - "encoding/json" - "fmt" - "path/filepath" - "strings" - "testing" - "time" - - "g.fc.run/misc/byre/internal/api" - "g.fc.run/misc/byre/internal/store" -) - -func TestValidateAsk(t *testing.T) { - m := &Manager{} - l := &live{id: "s1"} - yes := true - - ask := func(context string) askArgs { - return askArgs{Context: context, Question: "q", WhyBlocked: "w", Blocking: &yes} - } - withAttachment := ask("- (*) a\n- ( ) b") - withAttachment.Attachments = []api.Attachment{{Kind: "video"}} - - cases := []struct { - name string - args askArgs - want []string - }{ - {"pick one", ask("Background.\n\n- (*) a: does a\n- ( ) b: does b"), nil}, - {"pick any", ask("- [ ] a\n- [x] b\n- [X] c"), nil}, - {"pick any, none recommended", ask("- [ ] a\n- [ ] b"), nil}, - {"empty", askArgs{}, []string{"context is empty", "question is empty", "why_blocked is empty", "at least two choices", "blocking is required"}}, - {"plain list", ask("- a\n- b"), []string{"at least two choices"}}, - {"one choice", ask("- (*) a"), []string{"at least two choices"}}, - {"in a code fence", ask("```\n- (*) a\n- ( ) b\n```"), []string{"at least two choices"}}, - {"no recommendation", ask("- ( ) a\n- ( ) b"), []string{"exactly one"}}, - {"two recommendations", ask("- (*) a\n- (*) b"), []string{"exactly one"}}, - {"mixed", ask("- (*) a\n- [ ] b"), []string{"mix"}}, - {"duplicate", ask("- (*) a\n- ( ) a"), []string{`"a" appears twice`}}, - {"bad attachment", withAttachment, []string{`attachments[0].kind "video"`}}, - } - - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - err := m.validateAsk(context.Background(), l, c.args) - if len(c.want) == 0 { - if err != nil { - t.Fatalf("unexpected error %v", err) - } - return - } - if err == nil { - t.Fatalf("want rejection mentioning %v", c.want) - } - if !strings.Contains(err.Error(), "nothing was filed") { - t.Fatalf("rejection doesn't say nothing was filed: %v", err) - } - for _, w := range c.want { - if !strings.Contains(err.Error(), w) { - t.Fatalf("rejection %q doesn't mention %q", err, w) - } - } - }) - } -} - -func TestCancelQuestion(t *testing.T) { - ctx := context.Background() - st, err := store.Open(filepath.Join(t.TempDir(), "byre.db"), store.DefaultPortFirst, func(api.Event) {}) - if err != nil { - t.Fatalf("open store: %v", err) - } - t.Cleanup(func() { st.Close() }) - - now := time.Now().UTC() - _, err = st.EnsureUser(ctx, "alice", "Alice") - if err != nil { - t.Fatalf("ensure user: %v", err) - } - err = st.CreateProject(ctx, api.Project{Id: "p1", Name: "demo", RepoPath: "/nonexistent", DefaultBranch: "main", Remote: "none", CreatedBy: "alice", CreatedAt: now}) - if err != nil { - t.Fatalf("create project: %v", err) - } - err = st.CreateTask(ctx, api.Task{Id: "t1", ProjectId: "p1", Title: "Fix the thing", Branch: "b", WorktreePath: "/nonexistent", BaseRef: "x", Status: api.TaskStatusActive, CreatedBy: "alice", CreatedAt: now}, "alice") - if err != nil { - t.Fatalf("create task: %v", err) - } - err = st.CreateSession(ctx, api.AgentSession{Id: "s1", TaskId: "t1", Vendor: api.Claude, VendorSessionId: "s1", State: api.AgentSessionStateIdle, Role: api.Implementer, CreatedBy: "alice", CreatedAt: now}, "p1") - if err != nil { - t.Fatalf("create session: %v", err) - } - - m := New(st, t.TempDir(), t.TempDir(), t.TempDir()) - l := &live{id: "s1", dir: t.TempDir(), vendor: api.Claude, taskID: "t1", projectID: "p1"} - other := &live{id: "s2", dir: t.TempDir(), vendor: api.Claude, taskID: "t1", projectID: "p1"} - - yes := true - raw, err := json.Marshal(askArgs{Context: "- (*) red\n- ( ) blue", Question: "Red or blue?", WhyBlocked: "w", Blocking: &yes}) - if err != nil { - t.Fatalf("marshal: %v", err) - } - out, err := m.ask(ctx, l, raw) - if err != nil { - t.Fatalf("ask: %v", err) - } - items, err := st.ListInbox(ctx, store.InboxFilter{SessionID: "s1", State: api.Open}) - if err != nil || len(items) != 1 { - t.Fatalf("inbox after ask: %+v %v", items, err) - } - id := items[0].Id - payload := items[0].Payload - if fmt.Sprint(payload["options"]) != "[red blue]" || payload["multiple"] != false || fmt.Sprint(payload["recommended"]) != "[red]" { - t.Fatalf("choices not parsed from context: %+v", payload) - } - if !strings.Contains(out, "question_id: "+id) || !strings.Contains(out, "cancel_question") { - t.Fatalf("ask result doesn't give the id and cancel_question: %s", out) - } - - cancel := func(l *live, id string) error { - raw, err := json.Marshal(cancelArgs{QuestionID: id}) - if err != nil { - t.Fatalf("marshal: %v", err) - } - _, err = m.cancelQuestion(ctx, l, raw) - return err - } - - if cancel(l, "nope") == nil { - t.Fatalf("cancelled a question that doesn't exist") - } - if cancel(other, id) == nil { - t.Fatalf("cancelled another session's question") - } - err = cancel(l, id) - if err != nil { - t.Fatalf("cancel: %v", err) - } - item, err := st.GetInboxItem(ctx, id) - if err != nil || item.State != api.Dismissed || item.ResolvedBy != nil || item.ResolvedAt == nil { - t.Fatalf("after cancel: %+v %v", item, err) - } - waiting, err := st.AwaitingHuman(ctx, "s1") - if err != nil || waiting != 0 { - t.Fatalf("still awaiting human after cancel: %d %v", waiting, err) - } - if cancel(l, id) == nil { - t.Fatalf("cancelled a question twice") - } -} - -func TestAsksQuestion(t *testing.T) { - cases := []struct { - name string - text string - want bool - }{ - {"prose question (seq 61)", "Which of these do you want to start with?", true}, - {"question mid-message", "I can do either.\n\nWhich do you want first? I'd start with the spinner.", true}, - {"bold question", "**Should I deploy now?**", true}, - {"question mark only inside quotes (seq 108)", "- **A:** block if the final message contains a \"?\".\n- **C:** block if the turn's report lists open questions.\n\nMy recommendation is A and C together.", false}, - {"quoted question", "Without it, a question like \"Which of these do you want to start with?\" still reaches you as plain text.", false}, - {"inline code", "The view calls `GET /api/inbox?task=x` on load.", false}, - {"fenced code", "Run:\n```\nif ok? then\n```\nDone.", false}, - {"url query", "See https://example.com/?a=1 for details.", false}, - {"blockquote", "> did it work?\n\nIt did.", false}, - {"no question", "I committed everything and the tests pass.", false}, - } - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - if got := asksQuestion(c.text); got != c.want { - t.Fatalf("asksQuestion(%q) = %v, want %v", c.text, got, c.want) - } - }) - } -} - -func TestBounceProseQuestion(t *testing.T) { - m := &Manager{} - stop := func(l *live, text string) map[string]any { - t.Helper() - input, err := json.Marshal(map[string]any{"hook_event_name": "Stop", "last_assistant_message": text}) - if err != nil { - t.Fatal(err) - } - out, err := m.bounceProseQuestion(l)(context.Background(), input) - if err != nil { - t.Fatal(err) - } - return out - } - - l := &live{} - if out := stop(l, "Which of these do you want to start with?"); out["decision"] != "block" || out["reason"] != proseQuestionNudge { - t.Fatalf("prose question not bounced: %v", out) - } - if out := stop(l, "Which of these do you want to start with?"); len(out) != 0 { - t.Fatalf("bounced twice in one turn: %v", out) - } - - asked := &live{asked: true} - if out := stop(asked, "Should I go ahead?"); len(out) != 0 { - t.Fatalf("bounced a turn that called ask_human: %v", out) - } - - if out := stop(&live{}, "I committed everything and the tests pass."); len(out) != 0 { - t.Fatalf("bounced a message with no question: %v", out) - } -} - -func TestDeliveryMessage(t *testing.T) { - str := func(s string) *string { return &s } - opts := func(s ...string) *[]string { return &s } - question := api.InboxItem{Id: "q1", ThreadId: "q1", Kind: api.Question, Payload: map[string]any{"question": "Which color?", "multiple": false}} - checklist := api.InboxItem{Id: "q2", ThreadId: "q2", Kind: api.Question, Payload: map[string]any{"question": "Which chores?", "multiple": true}} - failure := api.InboxItem{Id: "e1", ThreadId: "e1", Kind: api.SessionError, Payload: map[string]any{"error": "process died"}} - - msg := deliveryMessage([]store.Pending{ - {Item: question, Response: api.InboxResponse{User: "alice", Type: api.Option, Options: opts("blue")}}, - {Item: checklist, Response: api.InboxResponse{User: "alice", Type: api.Option, Options: opts("tests", "docs"), Text: str("also run go vet")}}, - {Item: question, Response: api.InboxResponse{User: "bob", Type: api.Other, Text: str("what is it for?"), - Attachments: []api.Attachment{{Kind: api.AttachmentKindUrl, Url: str("https://example.com")}}}}, - {Item: failure, Response: api.InboxResponse{User: "alice", Type: api.Other, Text: str("stop and write tests")}}, - }) - - for _, want := range []string{ - "Your question q1: Which color?", - "alice chose: blue", - "Your question q2: Which chores?\nalice checked:\n- tests\n- docs\nalice added: also run go vet\n", - "bob answered (none of your options): what is it for?", - `calling ask_human with thread_id "q1"`, - `"url":"https://example.com"`, - "Your error e1: process died", - "alice replied: stop and write tests", - } { - if !strings.Contains(msg, want) { - t.Fatalf("message missing %q:\n%s", want, msg) - } - } -} diff --git a/internal/agents/lead_internal_test.go b/internal/agents/lead_internal_test.go deleted file mode 100644 index 8acfdb4..0000000 --- a/internal/agents/lead_internal_test.go +++ /dev/null @@ -1,100 +0,0 @@ -package agents - -import ( - "context" - "path/filepath" - "strings" - "testing" - "time" - - "g.fc.run/misc/byre/internal/api" - "g.fc.run/misc/byre/internal/store" -) - -func TestPaneFreed(t *testing.T) { - ctx := context.Background() - st, err := store.Open(filepath.Join(t.TempDir(), "byre.db"), store.DefaultPortFirst, func(api.Event) {}) - if err != nil { - t.Fatalf("open store: %v", err) - } - t.Cleanup(func() { st.Close() }) - - now := time.Now().UTC() - _, err = st.EnsureUser(ctx, "alice", "Alice") - if err != nil { - t.Fatalf("ensure user: %v", err) - } - err = st.CreateProject(ctx, api.Project{Id: "p1", Name: "demo", RepoPath: "/nonexistent", DefaultBranch: "main", Remote: "none", CreatedBy: "alice", CreatedAt: now}) - if err != nil { - t.Fatalf("create project: %v", err) - } - for _, id := range []string{"t1", "t2"} { - err = st.CreateTask(ctx, api.Task{Id: id, ProjectId: "p1", Title: "Fix " + id, Branch: id, WorktreePath: "/nonexistent", BaseRef: "x", Status: api.TaskStatusActive, CreatedBy: "alice", CreatedAt: now}, "alice") - if err != nil { - t.Fatalf("create task: %v", err) - } - } - err = st.CreateSession(ctx, api.AgentSession{Id: "s1", TaskId: "t1", Vendor: api.Claude, VendorSessionId: "s1", State: api.AgentSessionStateIdle, Role: api.Implementer, CreatedBy: "alice", CreatedAt: now}, "p1") - if err != nil { - t.Fatalf("create session: %v", err) - } - err = st.CreateInboxItem(ctx, api.InboxItem{ - Id: "r1", ProjectId: "p1", TaskId: "t1", AgentSessionId: "s1", ThreadId: "r1", - Kind: api.Report, Priority: api.Fyi, Payload: map[string]any{"text": "done"}, - State: api.Open, CreatedAt: now, Responses: []api.InboxResponse{}, - }) - if err != nil { - t.Fatalf("file report: %v", err) - } - - m := New(st, t.TempDir(), t.TempDir(), t.TempDir()) - t1, t2, p1 := "t1", "t2", "p1" - focus := func(tab api.TaskTab) { - t.Helper() - err := st.SetFocus(ctx, "alice", api.Focus{TaskId: &t1, ProjectId: &p1, Tabs: []api.TaskTab{tab}, Tab: tab}, "alice") - if err != nil { - t.Fatalf("set focus: %v", err) - } - } - freed := func(kind, taskID string) string { - t.Helper() - text, err := m.freedText(ctx, "alice", api.Event{Kind: kind, TaskId: &taskID}) - if err != nil { - t.Fatalf("freed text: %v", err) - } - return text - } - - focus(api.TabInbox) - if text := freed("inbox.updated", t1); text != "" { - t.Fatalf("freed with an item still open: %q", text) - } - - err = st.WithdrawItem(ctx, "r1") - if err != nil { - t.Fatalf("withdraw: %v", err) - } - text := freed("inbox.updated", t1) - if !strings.HasPrefix(text, "Your right pane is free: ") || !strings.HasSuffix(text, "task t1] has nothing left in its inbox.") { - t.Fatalf("emptied inbox: %q", text) - } - if text := freed("inbox.updated", t2); text != "" { - t.Fatalf("freed by another task: %q", text) - } - - focus(api.TabTranscript) - if text := freed("inbox.updated", t1); text != "" { - t.Fatalf("freed on the transcript tab: %q", text) - } - - _, err = st.MergeTask(ctx, "t1", "abc", "origin/main", "alice") - if err != nil { - t.Fatalf("merge: %v", err) - } - if text := freed("task.merged", t1); !strings.HasSuffix(text, "task t1] is merged.") { - t.Fatalf("merged: %q", text) - } - if text := freed("inbox.updated", t1); text != "" { - t.Fatalf("inbox.updated on a merged task: %q", text) - } -} diff --git a/internal/agents/lead_test.go b/internal/agents/lead_test.go deleted file mode 100644 index bbdecd7..0000000 --- a/internal/agents/lead_test.go +++ /dev/null @@ -1,169 +0,0 @@ -package agents_test - -import ( - "fmt" - "slices" - "strings" - "testing" - "time" - - "g.fc.run/misc/byre/internal/api" - "g.fc.run/misc/byre/internal/store" -) - -func leadIdle(ev api.Event) bool { - return ev.Kind == "session.state" && ev.Owner != nil && ev.Payload["state"] == string(api.AgentSessionStateIdle) -} - -func TestLeadSignalsAndShow(t *testing.T) { - t.Parallel() - e := newEnv(t) - m := e.manager() - - lead, err := m.MessageLead(e.ctx, e.user, "Reply with the single word ready.", testModel, nil) - if err != nil { - t.Fatalf("message lead: %v", err) - } - if lead.Owner != login || lead.TaskId != "" || lead.Role != api.Lead { - t.Fatalf("lead session %+v", lead) - } - e.waitFor("lead idle", leadIdle) - - worker := api.AgentSession{ - Id: store.NewID(), TaskId: e.task.Id, Vendor: api.Claude, VendorSessionId: store.NewID(), - State: api.AgentSessionStateIdle, Role: api.Implementer, CreatedBy: login, CreatedAt: time.Now().UTC(), - } - err = e.store.CreateSession(e.ctx, worker, e.project.Id) - if err != nil { - t.Fatalf("create worker session: %v", err) - } - err = e.store.CreateInboxItem(e.ctx, api.InboxItem{ - Id: store.NewID(), ProjectId: e.project.Id, TaskId: e.task.Id, AgentSessionId: worker.Id, ThreadId: "t1", - Kind: api.Report, Priority: api.Fyi, Payload: map[string]any{"text": "Finished the parser. The tests pass."}, - State: api.Open, CreatedAt: time.Now().UTC(), Responses: []api.InboxResponse{}, - }) - if err != nil { - t.Fatalf("file report: %v", err) - } - created := e.waitFor("inbox.created", func(ev api.Event) bool { return ev.Kind == "inbox.created" }) - m.Observe(created[len(created)-1]) - - signalled := e.waitFor("signal delivered", func(ev api.Event) bool { - return ev.Kind == "user.message" && ev.Owner != nil && ev.Actor == "byre" - }) - text := signalled[len(signalled)-1].Payload["text"].(string) - if !strings.Contains(text, "Finished the parser") || !strings.Contains(text, e.task.Id) { - t.Fatalf("signal text %q", text) - } - e.waitFor("lead idle after signal", leadIdle) - - _, err = m.MessageLead(e.ctx, e.user, "Call the show tool with task_id "+e.task.Id+", tabs [\"inbox\"] and tab \"inbox\". Then say done.", "", nil) - if err != nil { - t.Fatalf("message lead: %v", err) - } - e.waitFor("focus.updated by the lead", func(ev api.Event) bool { - return ev.Kind == "focus.updated" && ev.Actor == login+" (lead)" - }) - f, err := e.store.GetFocus(e.ctx, login) - if err != nil { - t.Fatalf("get focus: %v", err) - } - if f.TaskId == nil || *f.TaskId != e.task.Id || f.ProjectId == nil || *f.ProjectId != e.project.Id || f.Tab != api.TabInbox { - t.Fatalf("focus %+v", f) - } - e.waitFor("lead idle after show", leadIdle) - - m.SetPaneFocus(login, true) - _, err = m.MessageLead(e.ctx, e.user, "Call the show tool with task_id "+e.task.Id+", tabs [\"changes\"] and tab \"changes\". Then say done.", "", nil) - if err != nil { - t.Fatalf("message lead: %v", err) - } - e.waitFor("show refused", func(ev api.Event) bool { - return ev.Kind == "agent.tool_result" && ev.Owner != nil && strings.Contains(fmt.Sprint(ev.Payload), "working in the right pane") - }) - e.waitFor("lead idle after refused show", leadIdle) - f, err = e.store.GetFocus(e.ctx, login) - if err != nil { - t.Fatalf("get focus: %v", err) - } - if f.Tab != api.TabInbox { - t.Fatalf("focus moved while the pane was focused: %+v", f) - } - - _, err = m.MessageLead(e.ctx, e.user, "Call the show tool with task_id "+e.task.Id+", tabs [\"inbox\", \"changes\"] and tab \"inbox\". Then say done.", "", nil) - if err != nil { - t.Fatalf("message lead: %v", err) - } - e.waitFor("show of what is already shown", func(ev api.Event) bool { - return ev.Kind == "agent.tool_result" && ev.Owner != nil && strings.Contains(fmt.Sprint(ev.Payload), "Already shown") - }) - e.waitFor("lead idle after already-shown show", leadIdle) - f, err = e.store.GetFocus(e.ctx, login) - if err != nil { - t.Fatalf("get focus: %v", err) - } - if f.Tab != api.TabInbox || !slices.Equal(f.Tabs, []api.TaskTab{api.TabInbox, api.TabChanges}) { - t.Fatalf("tabs not reordered on the shown tab: %+v", f) - } - - err = e.store.WithdrawItem(e.ctx, created[len(created)-1].Payload["id"].(string)) - if err != nil { - t.Fatalf("withdraw report: %v", err) - } - _, err = m.MessageLead(e.ctx, e.user, "Call the show tool with task_id "+e.task.Id+", tabs [\"changes\"] and tab \"changes\". Then say done.", "", nil) - if err != nil { - t.Fatalf("message lead: %v", err) - } - e.waitFor("show allowed on an emptied inbox", func(ev api.Event) bool { - return ev.Kind == "focus.updated" && ev.Actor == login+" (lead)" && ev.Payload["tab"] == string(api.TabChanges) - }) - e.waitFor("lead idle after show on an emptied inbox", leadIdle) - - err = m.SetFocus(e.ctx, login, api.Focus{ProjectId: &e.project.Id, Tabs: []api.TaskTab{api.TabProject}, Tab: api.TabProject}, login) - if err != nil { - t.Fatalf("focus project: %v", err) - } - _, err = m.MessageLead(e.ctx, e.user, "Call the show tool with task_id "+e.task.Id+", tabs [\"transcript\"] and tab \"transcript\". Then say done.", "", nil) - if err != nil { - t.Fatalf("message lead: %v", err) - } - e.waitFor("show allowed over the project tab", func(ev api.Event) bool { - return ev.Kind == "focus.updated" && ev.Actor == login+" (lead)" && ev.Payload["tab"] == string(api.TabTranscript) - }) -} - -func TestFocusProject(t *testing.T) { - t.Parallel() - e := newEnv(t) - m := e.manager() - - err := m.SetFocus(e.ctx, login, api.Focus{ProjectId: &e.project.Id, Tabs: []api.TaskTab{api.TabProject}, Tab: api.TabProject}, login) - if err != nil { - t.Fatalf("focus project: %v", err) - } - f, err := e.store.GetFocus(e.ctx, login) - if err != nil { - t.Fatalf("get focus: %v", err) - } - if f.TaskId != nil || f.ProjectId == nil || *f.ProjectId != e.project.Id || f.Tab != api.TabProject { - t.Fatalf("focus %+v", f) - } - - other := "nope" - err = m.SetFocus(e.ctx, login, api.Focus{TaskId: &e.task.Id, ProjectId: &other, Tabs: []api.TaskTab{}, Tab: api.TabTranscript}, login) - if err != nil { - t.Fatalf("focus task: %v", err) - } - f, err = e.store.GetFocus(e.ctx, login) - if err != nil { - t.Fatalf("get focus: %v", err) - } - if f.TaskId == nil || *f.TaskId != e.task.Id || f.ProjectId == nil || *f.ProjectId != e.project.Id { - t.Fatalf("focus %+v", f) - } - - err = m.SetFocus(e.ctx, login, api.Focus{ProjectId: &other, Tabs: []api.TaskTab{}, Tab: api.TabProject}, login) - if err == nil { - t.Fatal("focus on an unknown project was accepted") - } -} diff --git a/internal/agents/manager.go b/internal/agents/manager.go index 216f520..3ff4b7e 100644 --- a/internal/agents/manager.go +++ b/internal/agents/manager.go @@ -309,6 +309,7 @@ func (m *Manager) Recover(ctx context.Context) error { } go m.deliverAllLeads() + go m.deliverAllQueued(context.Background()) go m.startHeldMerges(context.Background()) return nil } diff --git a/internal/agents/manager_test.go b/internal/agents/manager_test.go deleted file mode 100644 index 25c932f..0000000 --- a/internal/agents/manager_test.go +++ /dev/null @@ -1,1355 +0,0 @@ -package agents_test - -import ( - "context" - "errors" - "fmt" - "os" - "os/exec" - "path/filepath" - "strings" - "testing" - "time" - - "g.fc.run/misc/byre/internal/agents" - "g.fc.run/misc/byre/internal/api" - "g.fc.run/misc/byre/internal/git" - "g.fc.run/misc/byre/internal/store" -) - -const longCommandPrompt = "Run the bash command `ping -c 120 127.0.0.1` in the foreground (not in the background) and wait for it to finish, then say done." - -const ( - testModel = "sonnet" - login = "alice@example.com" - gitName = "Alice Test" - gitEmail = "alice@byre.invalid" -) - -type env struct { - t *testing.T - ctx context.Context - store *store.Store - events chan api.Event - user api.User - repo string - remote string - project api.Project - task api.Task - - worktrees string - leads string -} - -func newEnv(t *testing.T) *env { - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) - t.Cleanup(cancel) - - e := &env{t: t, ctx: ctx, events: make(chan api.Event, 4096), worktrees: t.TempDir(), leads: t.TempDir()} - - var err error - e.store, err = store.Open(filepath.Join(t.TempDir(), "byre.db"), store.DefaultPortFirst, func(ev api.Event) { e.events <- ev }) - if err != nil { - t.Fatalf("open store: %v", err) - } - t.Cleanup(func() { e.store.Close() }) - - _, err = e.store.EnsureUser(ctx, login, "Alice") - if err != nil { - t.Fatalf("ensure user: %v", err) - } - e.user, err = e.store.UpdateUserGit(ctx, login, gitName, gitEmail) - if err != nil { - t.Fatalf("update user: %v", err) - } - - e.repo = scratchRepo(t) - e.remote = filepath.Join(t.TempDir(), "remote.git") - gitOut(t, e.repo, "init", "--quiet", "--bare", "--initial-branch=main", e.remote) - gitOut(t, e.repo, "remote", "add", git.Remote, e.remote) - gitOut(t, e.repo, "push", "--quiet", git.Remote, "main") - e.project = api.Project{Id: store.NewID(), Name: "demo", RepoPath: e.repo, DefaultBranch: "main", Remote: e.remote, CreatedBy: login, CreatedAt: time.Now().UTC()} - err = e.store.CreateProject(ctx, e.project) - if err != nil { - t.Fatalf("create project: %v", err) - } - - e.task = e.addTask() - return e -} - -func (e *env) addTask() api.Task { - e.t.Helper() - id := store.NewID() - task := api.Task{ - Id: id, - ProjectId: e.project.Id, - Title: "test", - Branch: "byre/" + id, - WorktreePath: filepath.Join(e.t.TempDir(), id), - Status: api.TaskStatusActive, - CreatedBy: login, - CreatedAt: time.Now().UTC(), - } - - var err error - task.BaseRef, err = git.AddWorktree(e.repo, task.WorktreePath, task.Branch, "main") - if err != nil { - e.t.Fatalf("add worktree: %v", err) - } - err = e.store.CreateTask(e.ctx, task, login) - if err != nil { - e.t.Fatalf("create task: %v", err) - } - return task -} - -func (e *env) manager() *agents.Manager { - m := agents.New(e.store, e.worktrees, e.leads, codexHome(e.t)) - e.t.Cleanup(m.Close) - return m -} - -func codexHome(t *testing.T) string { - home, err := os.UserHomeDir() - if err != nil { - t.Fatalf("home dir: %v", err) - } - return filepath.Join(home, ".codex") -} - -func (e *env) waitFor(desc string, match func(api.Event) bool) []api.Event { - e.t.Helper() - seen := []api.Event{} - for { - select { - case ev := <-e.events: - seen = append(seen, ev) - e.t.Logf("event %d %s %v", ev.Seq, ev.Kind, ev.Payload) - if match(ev) { - return seen - } - case <-e.ctx.Done(): - e.t.Fatalf("timed out waiting for %s", desc) - } - } -} - -func (e *env) waitIdle() []api.Event { - e.t.Helper() - return e.waitFor("session idle", func(ev api.Event) bool { - return ev.Kind == "session.state" && ev.Payload["state"] == string(api.AgentSessionStateIdle) - }) -} - -func kinds(events []api.Event) []string { - out := []string{} - for _, ev := range events { - out = append(out, ev.Kind) - } - return out -} - -func has(events []api.Event, kind string) bool { - for _, ev := range events { - if ev.Kind == kind { - return true - } - } - return false -} - -func TestTurnAndFollowUp(t *testing.T) { - t.Parallel() - e := newEnv(t) - m := e.manager() - - startHead := gitOut(t, e.task.WorktreePath, "rev-parse", "HEAD") - sess, err := m.Create(e.ctx, e.task, e.user, `Create a file hi.txt containing exactly the word hi.`, testModel) - if err != nil { - t.Fatalf("create session: %v", err) - } - if sess.State != api.AgentSessionStateRunning { - t.Fatalf("new session is %s, want running", sess.State) - } - - events := e.waitIdle() - for _, kind := range []string{"session.created", "user.message", "agent.tool_call", "agent.tool_result", "turn.ended", "session.context"} { - if !has(events, kind) { - t.Fatalf("missing %s in %v", kind, kinds(events)) - } - } - - for _, ev := range events { - if ev.Kind != "turn.ended" { - continue - } - id, _ := ev.Payload["inbox_item_id"].(string) - if id == "" { - t.Fatalf("turn.ended has no summary inbox item: %v", ev.Payload) - } - item, err := e.store.GetInboxItem(e.ctx, id) - if err != nil { - t.Fatalf("get summary item: %v", err) - } - if item.Kind != api.Report || item.Payload["text"] != ev.Payload["result"] { - t.Fatalf("summary item %+v doesn't carry the turn's final message %q", item, ev.Payload["result"]) - } - } - - if !has(events, "snapshot.created") { - events = append(events, e.waitFor("snapshot", func(ev api.Event) bool { return ev.Kind == "snapshot.created" })...) - } - snaps, err := e.store.ListSnapshots(e.ctx, e.task.Id) - if err != nil || len(snaps) == 0 { - t.Fatalf("no snapshots after a turn that wrote a file: %+v %v", snaps, err) - } - latest := snaps[len(snaps)-1] - if ref := gitOut(t, e.repo, "rev-parse", git.SnapshotRef(e.task.Id, latest.N)); ref != latest.CommitSha { - t.Fatalf("snapshot ref %s, want %s", ref, latest.CommitSha) - } - if got := gitOut(t, e.repo, "show", latest.CommitSha+":hi.txt"); strings.TrimSpace(got) != "hi" { - t.Fatalf("hi.txt in the latest snapshot: %q", got) - } - if len(snaps) != 1 || snaps[0].Trigger != api.SnapshotTriggerTurnEnd { - t.Fatalf("snapshots %+v, want one turn_end snapshot", snaps) - } - - window := 0 - for _, ev := range events { - if ev.Kind == "session.context" { - window = ev.Payload["window"].(int) - } - } - if window <= 0 { - t.Fatalf("no context window reported after the turn ended") - } - - head := strings.TrimSpace(gitOut(t, e.task.WorktreePath, "rev-parse", "HEAD")) - for _, ev := range events { - if ev.Kind == "user.message" && ev.Payload["head"] != strings.TrimSpace(startHead) { - t.Fatalf("user.message head %v, want the HEAD the turn started from %s", ev.Payload["head"], startHead) - } - if ev.Kind == "turn.ended" && ev.Payload["commit"] != head { - t.Fatalf("turn.ended commit %v, want the new HEAD %s", ev.Payload["commit"], head) - } - } - log := gitOut(t, e.task.WorktreePath, "log", "-1", "--format=%an <%ae>|%cn <%ce>", "--name-only") - if !strings.HasPrefix(log, gitName+" <"+gitEmail+">|"+gitName+" <"+gitEmail+">") || !strings.Contains(log, "hi.txt") { - t.Fatalf("byre's turn commit is wrong:\n%s", log) - } - if status := gitOut(t, e.task.WorktreePath, "status", "--porcelain", "--untracked-files=all"); status != "" { - t.Fatalf("worktree dirty after the turn commit:\n%s", status) - } - - _, err = m.Send(e.ctx, sess.Id, login, "Reply with the single word ok.", "haiku") - if err != nil { - t.Fatalf("send: %v", err) - } - - events = e.waitIdle() - reply := "" - switched := false - contextModel := "" - for _, ev := range events { - switch ev.Kind { - case "agent.message": - reply += ev.Payload["text"].(string) - case "session.model": - switched = ev.Payload["model"] == "haiku" - case "session.context": - contextModel = ev.Payload["model"].(string) - } - } - if !strings.Contains(strings.ToLower(reply), "ok") { - t.Fatalf("follow-up reply %q", reply) - } - if !switched || !strings.Contains(contextModel, "haiku") { - t.Fatalf("model switch: session.model recorded %v, context model %q", switched, contextModel) - } - stored, err := e.store.GetSession(e.ctx, sess.Id) - if err != nil { - t.Fatalf("get session: %v", err) - } - if stored.Model != "haiku" { - t.Fatalf("session model %q after switch", stored.Model) - } - - _, err = m.Send(e.ctx, sess.Id, login, "again", "") - if err != nil { - t.Fatalf("send while idle: %v", err) - } - _, err = m.Send(e.ctx, sess.Id, login, "too soon", "") - if err == nil { - t.Fatalf("send while running should conflict") - } - e.waitIdle() -} - -func TestInterrupt(t *testing.T) { - t.Parallel() - e := newEnv(t) - m := e.manager() - - sess, err := m.Create(e.ctx, e.task, e.user, longCommandPrompt, testModel) - if err != nil { - t.Fatalf("create session: %v", err) - } - - e.waitFor("tool call", func(ev api.Event) bool { return ev.Kind == "agent.tool_call" }) - time.Sleep(2 * time.Second) - - at := time.Now() - _, err = m.Interrupt(e.ctx, sess.Id) - if err != nil { - t.Fatalf("interrupt: %v", err) - } - - events := e.waitIdle() - if time.Since(at) > 30*time.Second { - t.Fatalf("interrupt took %s", time.Since(at)) - } - for _, ev := range events { - if ev.Kind != "agent.tool_result" { - continue - } - if ev.Payload["is_error"] != true || strings.Contains(fmt.Sprint(ev.Payload["content"]), "Blocked") { - t.Fatalf("command finished or was refused instead of being interrupted: %v", ev.Payload) - } - } -} - -func TestRecoverMidTurn(t *testing.T) { - t.Parallel() - e := newEnv(t) - first := agents.New(e.store, e.worktrees, e.leads, codexHome(t)) - - sess, err := first.Create(e.ctx, e.task, e.user, "Run the bash command `sleep 15` in the foreground (not in the background). After it finishes, create a file done.txt containing exactly the word done.", testModel) - if err != nil { - t.Fatalf("create session: %v", err) - } - - e.waitFor("tool call", func(ev api.Event) bool { return ev.Kind == "agent.tool_call" }) - first.Close() - - stored, err := e.store.GetSession(e.ctx, sess.Id) - if err != nil { - t.Fatalf("get session: %v", err) - } - if stored.State != api.AgentSessionStateRunning { - t.Fatalf("killed mid-turn session is %s, want running", stored.State) - } - - second := e.manager() - err = second.Recover(e.ctx) - if err != nil { - t.Fatalf("recover: %v", err) - } - - events := e.waitIdle() - notified := false - for _, ev := range events { - if ev.Kind == "user.message" && ev.Actor == "byre" { - notified = true - } - } - if !notified { - t.Fatalf("no restart notice in %v", kinds(events)) - } - - out, err := os.ReadFile(filepath.Join(e.task.WorktreePath, "done.txt")) - if err != nil { - t.Fatalf("resumed agent did not finish: %v", err) - } - if strings.TrimSpace(string(out)) != "done" { - t.Fatalf("done.txt contains %q", out) - } -} - -func TestStopTask(t *testing.T) { - t.Parallel() - e := newEnv(t) - m := e.manager() - - sess, err := m.Create(e.ctx, e.task, e.user, longCommandPrompt, testModel) - if err != nil { - t.Fatalf("create session: %v", err) - } - - e.waitFor("tool call", func(ev api.Event) bool { return ev.Kind == "agent.tool_call" }) - - err = m.StopTask(e.ctx, e.task.Id, login) - if err != nil { - t.Fatalf("stop task: %v", err) - } - - stored, err := e.store.GetSession(e.ctx, sess.Id) - if err != nil { - t.Fatalf("get session: %v", err) - } - if stored.State != api.AgentSessionStateCompleted || m.LiveCount() != 0 { - t.Fatalf("after stop: state %s, live %d", stored.State, m.LiveCount()) - } - - _, err = m.Send(e.ctx, sess.Id, login, "hello?", "") - if err == nil { - t.Fatalf("send to completed session should conflict") - } -} - -const colorPrompt = `I need a file color.txt containing exactly one color name. Don't pick it yourself: ` + - `ask me with ask_human (blocking) whether it should be red or blue, recommending red. ` + - `When you get my answer, write the file.` - -func (e *env) openQuestions(sessionID string) []api.InboxItem { - return e.openItems(sessionID, api.Question) -} - -func (e *env) openReports(sessionID string) []api.InboxItem { - return e.openItems(sessionID, api.Report) -} - -func (e *env) openItems(sessionID string, kind api.InboxItemKind) []api.InboxItem { - e.t.Helper() - items, err := e.store.ListInbox(e.ctx, store.InboxFilter{SessionID: sessionID, State: api.Open}) - if err != nil { - e.t.Fatalf("list inbox: %v", err) - } - matched := []api.InboxItem{} - for _, item := range items { - if item.Kind == kind { - matched = append(matched, item) - } - } - return matched -} - -func (e *env) respond(m *agents.Manager, item api.InboxItem, typ api.InboxResponseType, option, text string, state api.InboxState) { - e.t.Helper() - e.respondAs(m, login, item, typ, option, text, state) -} - -func (e *env) respondAs(m *agents.Manager, user string, item api.InboxItem, typ api.InboxResponseType, option, text string, state api.InboxState) { - e.t.Helper() - resp := api.InboxResponse{ - Id: store.NewID(), - ItemId: item.Id, - User: user, - Type: typ, - Attachments: []api.Attachment{}, - CreatedAt: time.Now().UTC(), - } - if option != "" { - resp.Options = &[]string{option} - } - if text != "" { - resp.Text = &text - } - - err := e.store.Respond(e.ctx, user, []store.Resolution{{Response: resp, State: state}}) - if err != nil { - e.t.Fatalf("respond: %v", err) - } - err = m.Deliver(e.ctx, item.AgentSessionId) - if err != nil { - e.t.Fatalf("deliver: %v", err) - } -} - -func TestAskHumanRoundTrip(t *testing.T) { - t.Parallel() - e := newEnv(t) - m := e.manager() - - sess, err := m.Create(e.ctx, e.task, e.user, colorPrompt, testModel) - if err != nil { - t.Fatalf("create session: %v", err) - } - e.waitIdle() - - questions := e.openQuestions(sess.Id) - if len(questions) != 1 { - t.Fatalf("want one open question, got %+v", questions) - } - q := questions[0] - options, _ := q.Payload["options"].([]any) - recommended, _ := q.Payload["recommended"].([]any) - if !q.Blocking || q.Priority != api.Blocked || len(options) != 2 || len(recommended) != 1 || !strings.Contains(fmt.Sprint(recommended[0]), "red") { - t.Fatalf("unexpected question %+v", q) - } - blue := "" - for _, o := range options { - if strings.Contains(fmt.Sprint(o), "blue") { - blue = fmt.Sprint(o) - } - } - if _, err := os.Stat(filepath.Join(e.task.WorktreePath, "color.txt")); err == nil { - t.Fatalf("agent wrote color.txt before getting an answer") - } - if reports := e.openReports(sess.Id); len(reports) != 0 { - t.Fatalf("turn that asked a blocking question filed a report: %+v", reports) - } - - waiting, err := e.store.AwaitingHuman(e.ctx, sess.Id) - if err != nil || waiting != 1 { - t.Fatalf("awaiting human: %d %v", waiting, err) - } - - e.respond(m, q, api.Option, blue, "", api.Answered) - events := e.waitIdle() - - delivered := false - for _, ev := range events { - if ev.Kind == "user.message" && strings.Contains(ev.Payload["text"].(string), "chose: "+blue) { - delivered = true - } - } - if !delivered { - t.Fatalf("answer was not delivered: %v", kinds(events)) - } - - out, err := os.ReadFile(filepath.Join(e.task.WorktreePath, "color.txt")) - if err != nil { - t.Fatalf("agent didn't write color.txt: %v", err) - } - if strings.TrimSpace(string(out)) != "blue" { - t.Fatalf("color.txt contains %q, want blue", out) - } - if reports := e.openReports(sess.Id); len(reports) != 1 { - t.Fatalf("want one report from the answered turn, got %+v", reports) - } - - pending, err := e.store.Undelivered(e.ctx, sess.Id) - if err != nil || len(pending) != 0 { - t.Fatalf("responses left undelivered: %+v %v", pending, err) - } -} - -const shapePrompt = `Ask me with ask_human (not blocking) whether shape.txt should say circle or square, recommending circle. ` + - "Then, without waiting for my answer, run these Bash commands one at a time, each as its own tool call: `sleep 8`, then `echo second`, then `echo third`. " + - `After those, write shape.txt containing exactly my answer if you have received it by then, or exactly unknown if not.` - -func TestAnswerDeliveredMidTurn(t *testing.T) { - t.Parallel() - e := newEnv(t) - m := e.manager() - - sess, err := m.Create(e.ctx, e.task, e.user, shapePrompt, testModel) - if err != nil { - t.Fatalf("create session: %v", err) - } - e.waitFor("question filed", func(ev api.Event) bool { - return ev.Kind == "inbox.created" && ev.Payload["kind"] == string(api.Question) - }) - - questions := e.openQuestions(sess.Id) - if len(questions) != 1 { - t.Fatalf("want one open question, got %+v", questions) - } - square := "" - options, _ := questions[0].Payload["options"].([]any) - for _, o := range options { - if strings.Contains(fmt.Sprint(o), "square") { - square = fmt.Sprint(o) - } - } - e.respond(m, questions[0], api.Option, square, "", api.Answered) - events := e.waitIdle() - - midTurn := false - for _, ev := range events { - if ev.Kind == "user.message" && ev.Payload["mid_turn"] == true && strings.Contains(ev.Payload["text"].(string), "chose: "+square) { - midTurn = true - } - if ev.Kind == "turn.ended" && !midTurn { - t.Fatalf("turn ended before the answer was delivered mid-turn: %v", kinds(events)) - } - } - if !midTurn { - t.Fatalf("answer was not delivered mid-turn: %v", kinds(events)) - } - - out, err := os.ReadFile(filepath.Join(e.task.WorktreePath, "shape.txt")) - if err != nil { - t.Fatalf("agent didn't write shape.txt: %v", err) - } - if strings.TrimSpace(string(out)) != "square" { - t.Fatalf("shape.txt contains %q, want square", out) - } - - pending, err := e.store.Undelivered(e.ctx, sess.Id) - if err != nil || len(pending) != 0 { - t.Fatalf("responses left undelivered: %+v %v", pending, err) - } -} - -func TestCompactCarriesHandoff(t *testing.T) { - t.Parallel() - e := newEnv(t) - m := e.manager() - - old, err := m.Create(e.ctx, e.task, e.user, `Remember the codeword AZURE-17; a later message will ask for it. Reply with the single word ok.`, testModel) - if err != nil { - t.Fatalf("create session: %v", err) - } - e.waitIdle() - - _, err = m.Compact(e.ctx, old.Id) - if err != nil { - t.Fatalf("compact: %v", err) - } - events := e.waitFor("new session idle", func(ev api.Event) bool { - return ev.Kind == "session.state" && ev.Payload["state"] == string(api.AgentSessionStateIdle) && - ev.AgentSessionId != nil && *ev.AgentSessionId != old.Id - }) - - continued := false - for _, ev := range events { - if ev.Kind == "user.message" && *ev.AgentSessionId != old.Id { - continued = ev.Payload["continuation"] == true - } - if ev.Kind == "turn.ended" && *ev.AgentSessionId == old.Id { - if ev.Payload["handoff"] != true || ev.Payload["inbox_item_id"] != nil { - t.Fatalf("handoff turn.ended %v: want handoff and no inbox summary", ev.Payload) - } - } - if ev.Kind == "user.message" && ev.Actor == "byre" && *ev.AgentSessionId == old.Id && ev.Payload["handoff"] != true { - t.Fatalf("handoff prompt %v: want handoff flag", ev.Payload) - } - } - if !continued { - t.Fatalf("new session's first message lacks the continuation flag") - } - - sessions, err := e.store.ListSessions(e.ctx, e.task.Id) - if err != nil { - t.Fatalf("list sessions: %v", err) - } - if len(sessions) != 2 { - t.Fatalf("want the old and the new session, got %+v", sessions) - } - var next api.AgentSession - for _, s := range sessions { - switch s.Id { - case old.Id: - if s.State != api.AgentSessionStateCompleted { - t.Fatalf("old session is %s, want completed", s.State) - } - default: - next = s - } - } - if next.State != api.AgentSessionStateIdle || next.Model != old.Model { - t.Fatalf("new session %+v, want idle on model %q", next, old.Model) - } - - _, err = m.Send(e.ctx, next.Id, login, "What was the codeword? Reply with just the codeword.", "") - if err != nil { - t.Fatalf("send to new session: %v", err) - } - reply := "" - for _, ev := range e.waitIdle() { - if ev.Kind == "agent.message" { - reply += ev.Payload["text"].(string) - } - } - if !strings.Contains(reply, "AZURE-17") { - t.Fatalf("handoff lost the codeword; new session said %q", reply) - } -} - -func TestMergeIn(t *testing.T) { - t.Parallel() - e := newEnv(t) - m := e.manager() - - _, err := m.Create(e.ctx, e.task, e.user, `Create a file task.txt containing exactly the word task.`, testModel) - if err != nil { - t.Fatalf("create session: %v", err) - } - e.waitIdle() - - err = os.WriteFile(filepath.Join(e.repo, "main.txt"), []byte("main\n"), 0o644) - if err != nil { - t.Fatalf("write main.txt: %v", err) - } - gitOut(t, e.repo, "add", "main.txt") - gitOut(t, e.repo, "commit", "--message", "main change") - mainHead := gitOut(t, e.repo, "rev-parse", "HEAD") - - _, err = m.MergeIn(e.ctx, e.task, e.user, "main") - if err != nil { - t.Fatalf("merge in: %v", err) - } - e.waitIdle() - - parents := strings.Fields(gitOut(t, e.task.WorktreePath, "log", "-1", "--format=%P")) - if len(parents) != 2 || parents[1] != mainHead { - t.Fatalf("HEAD parents %v, want a merge of main %s", parents, mainHead) - } - for _, f := range []string{"task.txt", "main.txt"} { - _, err = os.Stat(filepath.Join(e.task.WorktreePath, f)) - if err != nil { - t.Fatalf("%s missing after merge: %v", f, err) - } - } - if status := gitOut(t, e.task.WorktreePath, "status", "--porcelain", "--untracked-files=all"); status != "" { - t.Fatalf("worktree dirty after the merge turn:\n%s", status) - } -} - -func TestMergeResolvesConflict(t *testing.T) { - t.Parallel() - e := newEnv(t) - m := e.manager() - - _, err := m.Create(e.ctx, e.task, e.user, `Create a file notes.txt containing exactly the line "task".`, testModel) - if err != nil { - t.Fatalf("create session: %v", err) - } - e.waitIdle() - err = os.WriteFile(filepath.Join(e.task.WorktreePath, "uncommitted"), []byte("three\n"), 0o644) - if err != nil { - t.Fatalf("write uncommitted: %v", err) - } - - err = os.WriteFile(filepath.Join(e.repo, "notes.txt"), []byte("main\n"), 0o644) - if err != nil { - t.Fatalf("write notes.txt: %v", err) - } - gitOut(t, e.repo, "add", "notes.txt") - gitOut(t, e.repo, "commit", "--message", "main change") - moved := gitOut(t, e.repo, "rev-parse", "HEAD") - - _, err = m.Merge(e.ctx, e.task, e.project, e.user) - if err != nil { - t.Fatalf("merge: %v", err) - } - merged := e.waitFor("task merged", func(ev api.Event) bool { return ev.Kind == "task.merged" }) - if pushed := merged[len(merged)-1].Payload["pushed"]; pushed != "origin/main" { - t.Fatalf("task.merged pushed %v, want origin/main", pushed) - } - - task, err := e.store.GetTask(e.ctx, e.task.Id) - if err != nil { - t.Fatalf("get task: %v", err) - } - if task.Status != api.TaskStatusMerged || task.MergeCommit == nil { - t.Fatalf("task after merge %+v", task) - } - if head := gitOut(t, e.repo, "rev-parse", "main"); head != *task.MergeCommit { - t.Fatalf("main at %s, want merge commit %s", head, *task.MergeCommit) - } - if head := gitOut(t, e.remote, "rev-parse", "main"); head != *task.MergeCommit { - t.Fatalf("remote main at %s, want merge commit %s", head, *task.MergeCommit) - } - if parent := gitOut(t, e.repo, "rev-parse", "main^"); parent != moved { - t.Fatalf("merge commit parent %s, want %s", parent, moved) - } - if log := gitOut(t, e.repo, "log", "-1", "--format=%s|%b|%an|%cn"); log != task.MergeTitle+"||"+gitName+"|"+gitName { - t.Fatalf("unexpected merge commit %s, want title %q", log, task.MergeTitle) - } - t.Logf("merge commit title: %s", task.MergeTitle) - notes, err := os.ReadFile(filepath.Join(e.repo, "notes.txt")) - if err != nil { - t.Fatalf("read notes.txt: %v", err) - } - if !strings.Contains(string(notes), "task") || !strings.Contains(string(notes), "main") || strings.Contains(string(notes), "<<<<<<<") { - t.Fatalf("notes.txt on main doesn't hold both sides:\n%s", notes) - } - _, err = os.Stat(filepath.Join(e.repo, "uncommitted")) - if err != nil { - t.Fatalf("uncommitted file not on main: %v", err) - } - if status := gitOut(t, e.repo, "status", "--porcelain"); status != "" { - t.Fatalf("main checkout dirty after merge: %s", status) - } - if branches := gitOut(t, e.repo, "branch", "--list", e.task.Branch); branches != "" { - t.Fatalf("branch still exists: %s", branches) - } -} - -func TestMergeCoAuthors(t *testing.T) { - t.Parallel() - e := newEnv(t) - m := e.manager() - - people := []struct{ login, name, gitName, gitEmail string }{ - {"carol@example.com", "Carol", "Carol Test", "carol@byre.invalid"}, - {"bob@example.com", "Bob", "Bob Test", "bob@byre.invalid"}, - {"dave@example.com", "Dave", "", ""}, - } - const dismisser = "erin@example.com" - _, err := e.store.EnsureUser(e.ctx, dismisser, "Erin") - if err != nil { - t.Fatalf("ensure user: %v", err) - } - _, err = e.store.UpdateUserGit(e.ctx, dismisser, "Erin Test", "erin@byre.invalid") - if err != nil { - t.Fatalf("update user: %v", err) - } - for _, p := range people { - _, err := e.store.EnsureUser(e.ctx, p.login, p.name) - if err != nil { - t.Fatalf("ensure user: %v", err) - } - if p.gitName != "" { - _, err = e.store.UpdateUserGit(e.ctx, p.login, p.gitName, p.gitEmail) - if err != nil { - t.Fatalf("update user: %v", err) - } - } - } - - sess, err := m.Create(e.ctx, e.task, e.user, colorPrompt, testModel) - if err != nil { - t.Fatalf("create session: %v", err) - } - e.waitIdle() - questions := e.openQuestions(sess.Id) - if len(questions) != 1 { - t.Fatalf("want one open question, got %+v", questions) - } - options, _ := questions[0].Payload["options"].([]any) - e.respondAs(m, people[0].login, questions[0], api.Option, fmt.Sprint(options[0]), "", api.Answered) - e.waitIdle() - - for _, p := range people[1:] { - _, err = m.Send(e.ctx, sess.Id, p.login, "Reply with the single word ok.", "") - if err != nil { - t.Fatalf("send as %s: %v", p.login, err) - } - e.waitIdle() - } - - reports, err := e.store.ListInbox(e.ctx, store.InboxFilter{TaskID: e.task.Id, State: api.Open}) - if err != nil || len(reports) == 0 { - t.Fatalf("want open reports to dismiss: %+v %v", reports, err) - } - e.respondAs(m, dismisser, reports[0], api.Dismiss, "", "", api.Dismissed) - - coAuthors, err := m.CoAuthors(e.ctx, e.task.Id, login) - if err != nil { - t.Fatalf("co-authors: %v", err) - } - got := []string{} - for _, p := range coAuthors { - got = append(got, p.Login+"|"+p.GitName+"|"+p.GitEmail) - } - want := []string{} - for _, p := range people { - want = append(want, p.login+"|"+p.gitName+"|"+p.gitEmail) - } - if strings.Join(got, ",") != strings.Join(want, ",") { - t.Fatalf("co-authors %v, want %v", got, want) - } - - _, err = m.Merge(e.ctx, e.task, e.project, e.user) - if err != nil { - t.Fatalf("merge: %v", err) - } - e.waitFor("task merged", func(ev api.Event) bool { return ev.Kind == "task.merged" }) - - message := gitOut(t, e.repo, "log", "-1", "--format=%B") - t.Logf("merge commit message:\n%s", message) - if author := gitOut(t, e.repo, "log", "-1", "--format=%an <%ae>|%cn <%ce>"); author != gitName+" <"+gitEmail+">|"+gitName+" <"+gitEmail+">" { - t.Fatalf("merge commit author|committer %s, want %s", author, gitName) - } - cmd := exec.Command("git", "interpret-trailers", "--parse") - cmd.Stdin = strings.NewReader(message) - out, err := cmd.Output() - if err != nil { - t.Fatalf("interpret-trailers: %v", err) - } - trailers := strings.TrimSpace(string(out)) - wantTrailers := "Co-authored-by: Carol Test \nCo-authored-by: Bob Test " - if trailers != wantTrailers { - t.Fatalf("trailers:\n%s\nwant:\n%s", trailers, wantTrailers) - } -} - -func TestMergeFetchesMovedRemote(t *testing.T) { - t.Parallel() - e := newEnv(t) - m := e.manager() - - _, err := m.Create(e.ctx, e.task, e.user, `Create a file task.txt containing exactly the word task.`, testModel) - if err != nil { - t.Fatalf("create session: %v", err) - } - e.waitIdle() - - other := t.TempDir() - gitOut(t, other, "clone", "--quiet", e.remote, ".") - err = os.WriteFile(filepath.Join(other, "elsewhere.txt"), []byte("elsewhere\n"), 0o644) - if err != nil { - t.Fatalf("write elsewhere.txt: %v", err) - } - gitOut(t, other, "add", "elsewhere.txt") - gitOut(t, other, "commit", "--message", "pushed elsewhere") - gitOut(t, other, "push", "--quiet", "origin", "main") - moved := gitOut(t, other, "rev-parse", "HEAD") - - _, err = m.Merge(e.ctx, e.task, e.project, e.user) - if err != nil { - t.Fatalf("merge: %v", err) - } - events := e.waitFor("task merged", func(ev api.Event) bool { return ev.Kind == "task.merged" }) - - for _, ev := range events { - if ev.Kind == "inbox.created" && ev.Payload["kind"] == string(api.SessionError) { - t.Fatalf("merge filed an error: %v", ev.Payload) - } - } - - task, err := e.store.GetTask(e.ctx, e.task.Id) - if err != nil { - t.Fatalf("get task: %v", err) - } - if task.Status != api.TaskStatusMerged || task.MergeCommit == nil { - t.Fatalf("task after merge %+v", task) - } - for _, dir := range []string{e.repo, e.remote} { - if head := gitOut(t, dir, "rev-parse", "main"); head != *task.MergeCommit { - t.Fatalf("%s main at %s, want merge commit %s", dir, head, *task.MergeCommit) - } - } - if parent := gitOut(t, e.remote, "rev-parse", "main^"); parent != moved { - t.Fatalf("merge commit parent %s, want the moved remote head %s", parent, moved) - } - for _, f := range []string{"task.txt", "elsewhere.txt"} { - _, err = os.Stat(filepath.Join(e.repo, f)) - if err != nil { - t.Fatalf("%s missing from main after merge: %v", f, err) - } - } -} - -func TestMergeHeldWhileAnotherMerges(t *testing.T) { - t.Parallel() - e := newEnv(t) - m := e.manager() - second := e.addTask() - - for i, task := range []api.Task{e.task, second} { - _, err := m.Create(e.ctx, task, e.user, fmt.Sprintf("Create a file task%d.txt containing exactly the word task.", i), testModel) - if err != nil { - t.Fatalf("create session: %v", err) - } - e.waitIdle() - } - - first, err := m.Merge(e.ctx, e.task, e.project, e.user) - if err != nil { - t.Fatalf("merge first: %v", err) - } - if first.MergeQueuedAt != nil { - t.Fatalf("first merge held: %+v", first) - } - held, err := m.Merge(e.ctx, second, e.project, e.user) - if err != nil { - t.Fatalf("merge second: %v", err) - } - if held.MergeQueuedAt == nil { - t.Fatalf("second merge not held: %+v", held) - } - _, err = m.Merge(e.ctx, held, e.project, e.user) - if !errors.Is(err, agents.ErrConflict) { - t.Fatalf("merging a held task again: %v, want conflict", err) - } - - events := e.waitFor("second task merged", func(ev api.Event) bool { - return ev.Kind == "task.merged" && *ev.TaskId == second.Id - }) - firstMerged, unqueued := -1, -1 - for i, ev := range events { - switch { - case ev.Kind == "task.merged" && *ev.TaskId == e.task.Id: - firstMerged = i - case ev.Kind == "task.merge_unqueued" && *ev.TaskId == second.Id: - unqueued = i - case ev.Kind == "inbox.created" && ev.Payload["kind"] == string(api.SessionError): - t.Fatalf("merge filed an error: %v", ev.Payload) - } - } - if firstMerged < 0 || unqueued < firstMerged { - t.Fatalf("held merge started at event %d, first merged at %d", unqueued, firstMerged) - } - - tasks := map[string]api.Task{} - for _, id := range []string{e.task.Id, second.Id} { - tasks[id], err = e.store.GetTask(e.ctx, id) - if err != nil { - t.Fatalf("get task: %v", err) - } - if tasks[id].Status != api.TaskStatusMerged || tasks[id].MergeCommit == nil || tasks[id].MergeQueuedAt != nil { - t.Fatalf("task after merge %+v", tasks[id]) - } - } - if head := gitOut(t, e.remote, "rev-parse", "main"); head != *tasks[second.Id].MergeCommit { - t.Fatalf("remote main at %s, want second merge commit %s", head, *tasks[second.Id].MergeCommit) - } - if parent := gitOut(t, e.remote, "rev-parse", "main^"); parent != *tasks[e.task.Id].MergeCommit { - t.Fatalf("second merge commit parent %s, want first merge commit %s", parent, *tasks[e.task.Id].MergeCommit) - } -} - -func TestCreateTaskTool(t *testing.T) { - t.Parallel() - e := newEnv(t) - m := e.manager() - - gitOut(t, e.task.WorktreePath, "-c", "user.name=x", "-c", "user.email=x@byre.invalid", "commit", "--allow-empty", "--message", "parent work") - parentHead := strings.TrimSpace(gitOut(t, e.task.WorktreePath, "rev-parse", "HEAD")) - - _, err := m.Create(e.ctx, e.task, e.user, `Call the byre create_task tool once with title "child", base "this_task", and prompt "Reply with the single word ok." Then stop.`, testModel) - if err != nil { - t.Fatalf("create session: %v", err) - } - e.waitFor("child session", func(ev api.Event) bool { - return ev.Kind == "session.created" && ev.TaskId != nil && *ev.TaskId != e.task.Id - }) - - tasks, err := e.store.ListTasks(e.ctx, e.project.Id) - if err != nil { - t.Fatalf("list tasks: %v", err) - } - var child api.Task - for _, task := range tasks { - if task.Id != e.task.Id { - child = task - } - } - if child.Title != "child" || child.CreatedBy != login || filepath.Dir(child.WorktreePath) != filepath.Join(e.worktrees, e.project.Id) { - t.Fatalf("child task %+v", child) - } - - sessions, err := e.store.ListSessions(e.ctx, child.Id) - if err != nil || len(sessions) != 1 || sessions[0].Model != testModel || sessions[0].CreatedBy != login { - t.Fatalf("child sessions %+v %v", sessions, err) - } - - if child.BaseRef != parentHead { - t.Fatalf("child base %s, want the parent task's HEAD %s", child.BaseRef, parentHead) - } -} - -func TestStartTodo(t *testing.T) { - t.Parallel() - e := newEnv(t) - m := e.manager() - - writeTestFile(t, filepath.Join(e.repo, "TODO.md"), "# TODO\n\n- Create hello.txt\n It holds the single line `hi`.\n- Leave this one alone\n") - gitOut(t, e.repo, "add", "TODO.md") - gitOut(t, e.repo, "commit", "--quiet", "--message", "add todo") - - lead := login + " (lead)" - _, err := m.StartTodo(e.ctx, e.project, e.user, "No such item", testModel, lead) - if !errors.Is(err, store.ErrNotFound) { - t.Fatalf("start unknown item: %v", err) - } - - task, err := m.StartTodo(e.ctx, e.project, e.user, "Create hello.txt", testModel, lead) - if err != nil { - t.Fatalf("start todo: %v", err) - } - if task.TodoItem == nil || *task.TodoItem != "Create hello.txt" || task.Title != "Create hello.txt" { - t.Fatalf("todo task %+v", task) - } - - _, err = m.StartTodo(e.ctx, e.project, e.user, "Create hello.txt", testModel, lead) - if !errors.Is(err, agents.ErrConflict) { - t.Fatalf("second start of the same item: %v", err) - } - - events := e.waitFor("todo turn end", func(ev api.Event) bool { - return ev.Kind == "turn.ended" && ev.TaskId != nil && *ev.TaskId == task.Id - }) - for _, ev := range events { - if ev.Kind == "task.created" && ev.TaskId != nil && *ev.TaskId == task.Id && (ev.Actor != lead || ev.Payload["created_by"] != login) { - t.Fatalf("task.created actor %q, created_by %v", ev.Actor, ev.Payload["created_by"]) - } - if ev.Kind == "user.message" && ev.TaskId != nil && *ev.TaskId == task.Id { - text, _ := ev.Payload["text"].(string) - if !strings.Contains(text, "- Create hello.txt\n It holds the single line `hi`.") || !strings.Contains(text, "remove this item from TODO.md") { - t.Fatalf("first prompt %q", text) - } - } - } - - stored, err := e.store.GetTask(e.ctx, task.Id) - if err != nil || stored.TodoItem == nil || *stored.TodoItem != "Create hello.txt" { - t.Fatalf("stored task %+v %v", stored, err) - } - - todoAfter, err := os.ReadFile(filepath.Join(task.WorktreePath, "TODO.md")) - if err != nil { - t.Fatalf("read worktree TODO.md: %v", err) - } - t.Logf("TODO.md after the turn:\n%s", todoAfter) - if strings.Contains(string(todoAfter), "Create hello.txt") || !strings.Contains(string(todoAfter), "Leave this one alone") { - t.Fatalf("agent didn't remove exactly its item from TODO.md") - } -} - -func TestForkSnapshot(t *testing.T) { - t.Parallel() - e := newEnv(t) - m := e.manager() - - src, err := m.Create(e.ctx, e.task, e.user, "Use the Write tool to create a.txt containing exactly APPLE-1. Then reply with the single word ok.", testModel) - if err != nil { - t.Fatalf("create session: %v", err) - } - e.waitIdle() - e.waitFor("first snapshot", func(ev api.Event) bool { return ev.Kind == "snapshot.created" }) - - _, err = m.Send(e.ctx, src.Id, login, "Use the Write tool to create b.txt containing exactly BANANA-2. Then reply with the single word ok.", "") - if err != nil { - t.Fatalf("send: %v", err) - } - e.waitIdle() - - snaps, err := e.store.ListSnapshots(e.ctx, e.task.Id) - if err != nil { - t.Fatalf("list snapshots: %v", err) - } - n := 0 - for _, snap := range snaps { - files := gitOut(t, e.repo, "ls-tree", "--name-only", snap.CommitSha) - if strings.Contains(files, "a.txt") && !strings.Contains(files, "b.txt") { - n = snap.N - } - } - if n == 0 { - t.Fatalf("no snapshot holds a.txt without b.txt: %+v", snaps) - } - - fork, err := m.Fork(e.ctx, e.task, e.project, n, e.user) - if err != nil { - t.Fatalf("fork: %v", err) - } - if fork.ForkedFrom == nil || *fork.ForkedFrom != e.task.Id || fork.ForkedAt == nil || *fork.ForkedAt != n { - t.Fatalf("fork task %+v", fork) - } - if _, err := os.Stat(filepath.Join(fork.WorktreePath, "a.txt")); err != nil { - t.Fatalf("a.txt missing from the fork's worktree: %v", err) - } - if _, err := os.Stat(filepath.Join(fork.WorktreePath, "b.txt")); err == nil { - t.Fatalf("b.txt, written after the snapshot, is in the fork's worktree") - } - - sessions, err := e.store.ListSessions(e.ctx, fork.Id) - if err != nil || len(sessions) != 1 { - t.Fatalf("fork sessions %+v %v", sessions, err) - } - sess := sessions[0] - if sess.State != api.AgentSessionStateIdle || sess.ForkedFrom == nil || *sess.ForkedFrom != src.VendorSessionId || sess.VendorSessionId == src.VendorSessionId { - t.Fatalf("fork session %+v", sess) - } - - coAuthors, err := m.CoAuthors(e.ctx, fork.Id, "carol@example.com") - if err != nil || len(coAuthors) != 1 || coAuthors[0].Login != login { - t.Fatalf("fork co-authors %+v %v, want the forker %s", coAuthors, err, login) - } - coAuthors, err = m.CoAuthors(e.ctx, e.task.Id, "carol@example.com") - if err != nil || len(coAuthors) != 1 || coAuthors[0].Login != login { - t.Fatalf("source co-authors %+v %v", coAuthors, err) - } - - _, err = m.Send(e.ctx, sess.Id, login, "Without using any tools, list the contents of every file you have created in this conversation, one per line, and nothing else.", "") - if err != nil { - t.Fatalf("send to fork: %v", err) - } - reply := "" - for _, ev := range e.waitFor("fork turn end", func(ev api.Event) bool { - return ev.Kind == "turn.ended" && ev.TaskId != nil && *ev.TaskId == fork.Id - }) { - if ev.Kind == "agent.message" && ev.TaskId != nil && *ev.TaskId == fork.Id { - reply += ev.Payload["text"].(string) - } - } - if !strings.Contains(reply, "APPLE-1") || strings.Contains(reply, "BANANA-2") { - t.Fatalf("fork's conversation isn't cut at the snapshot; it said %q", reply) - } - - _, err = m.Fork(e.ctx, e.task, e.project, n+100, e.user) - if !errors.Is(err, store.ErrNotFound) { - t.Fatalf("fork of a missing snapshot: %v", err) - } -} - -func writeTestFile(t *testing.T, path, content string) { - t.Helper() - err := os.WriteFile(path, []byte(content), 0o644) - if err != nil { - t.Fatalf("write %s: %v", path, err) - } -} - -func TestQuestionReachesInbox(t *testing.T) { - t.Parallel() - e := newEnv(t) - m := e.manager() - - sess, err := m.Create(e.ctx, e.task, e.user, `I want a file hi.txt that says either red or blue. Before you create anything, find out from me which one.`, testModel) - if err != nil { - t.Fatalf("create session: %v", err) - } - events := e.waitIdle() - - questions := e.openQuestions(sess.Id) - if len(questions) == 0 { - t.Fatalf("the prose question never became an inbox question: %v", kinds(events)) - } -} - -func TestFollowUpQuestionThread(t *testing.T) { - t.Parallel() - e := newEnv(t) - m := e.manager() - - sess, err := m.Create(e.ctx, e.task, e.user, colorPrompt, testModel) - if err != nil { - t.Fatalf("create session: %v", err) - } - e.waitIdle() - - questions := e.openQuestions(sess.Id) - if len(questions) != 1 { - t.Fatalf("want one open question, got %+v", questions) - } - first := questions[0] - - e.respond(m, first, api.Other, "", "Before I pick: which of the two did you recommend, and why?", api.Answered) - e.waitIdle() - - questions = e.openQuestions(sess.Id) - if len(questions) != 1 { - t.Fatalf("want one open question after the follow-up, got %+v", questions) - } - second := questions[0] - if second.Id == first.Id || second.ThreadId != first.ThreadId { - t.Fatalf("follow-up didn't continue the thread: first %s/%s, second %s/%s", first.Id, first.ThreadId, second.Id, second.ThreadId) - } - - e.respond(m, second, api.Other, "", "Use green instead of either.", api.Answered) - e.waitIdle() - - out, err := os.ReadFile(filepath.Join(e.task.WorktreePath, "color.txt")) - if err != nil { - t.Fatalf("agent didn't write color.txt: %v", err) - } - if strings.TrimSpace(string(out)) != "green" { - t.Fatalf("color.txt contains %q, want green", out) - } -} - -const codexModel = "gpt-5.6-luna" - -func TestCodexSession(t *testing.T) { - t.Parallel() - e := newEnv(t) - m := e.manager() - - sess, err := m.Create(e.ctx, e.task, e.user, "Create a file hi.txt containing exactly the word hi. Then end your turn with a one-line summary.", codexModel) - if err != nil { - t.Fatalf("create session: %v", err) - } - if sess.Vendor != api.Codex { - t.Fatalf("session vendor %s for %s, want codex", sess.Vendor, codexModel) - } - events := e.waitIdle() - for _, kind := range []string{"user.message", "agent.tool_call", "agent.tool_result", "agent.message", "turn.ended", "session.context"} { - if !has(events, kind) { - t.Fatalf("missing %s in %v", kind, kinds(events)) - } - } - stored, err := e.store.GetSession(e.ctx, sess.Id) - if err != nil || stored.VendorSessionId == "" { - t.Fatalf("thread id not stored: %+v %v", stored, err) - } - for _, ev := range events { - if ev.Kind != "turn.ended" { - continue - } - if ev.Payload["is_error"] != false || ev.Payload["vendor_uuid"] == "" || ev.Payload["inbox_item_id"] == nil || ev.Payload["commit"] == nil { - t.Fatalf("turn.ended %v", ev.Payload) - } - } - if got := gitOut(t, e.task.WorktreePath, "show", "HEAD:hi.txt"); strings.TrimSpace(got) != "hi" { - t.Fatalf("hi.txt in byre's turn commit: %q", got) - } - - _, err = m.Send(e.ctx, sess.Id, login, "x", "claude-haiku-5-5") - if !errors.Is(err, agents.ErrInvalid) { - t.Fatalf("switching a codex session to a claude model: %v", err) - } - - _, err = m.Send(e.ctx, sess.Id, login, "Ask the human, with byre's ask_human tool and blocking false, whether hi.txt should say hello instead; recommend keeping hi. Then end your turn with a one-line summary.", "") - if err != nil { - t.Fatalf("send: %v", err) - } - events = e.waitIdle() - asked := false - for _, ev := range events { - if ev.Kind == "inbox.created" && ev.Payload["kind"] == string(api.Question) { - asked = true - } - } - if !asked { - t.Fatalf("no question filed through ask_human: %v", kinds(events)) - } - - snaps, err := e.store.ListSnapshots(e.ctx, e.task.Id) - if err != nil || len(snaps) == 0 { - t.Fatalf("no snapshots: %+v %v", snaps, err) - } - fork, err := m.Fork(e.ctx, e.task, e.project, snaps[0].N, e.user) - if err != nil { - t.Fatalf("fork: %v", err) - } - forked, err := e.store.ListSessions(e.ctx, fork.Id) - if err != nil || len(forked) != 1 || forked[0].Vendor != api.Codex { - t.Fatalf("fork sessions %+v %v", forked, err) - } - _, err = m.Send(e.ctx, forked[0].Id, login, "Without running any commands, from memory: what file did you create, and did you ask the human anything? Reply in one line.", "") - if err != nil { - t.Fatalf("send to fork: %v", err) - } - reply := "" - for _, ev := range e.waitFor("fork idle", func(ev api.Event) bool { - return ev.Kind == "session.state" && *ev.AgentSessionId == forked[0].Id && ev.Payload["state"] == string(api.AgentSessionStateIdle) - }) { - if ev.Kind == "agent.message" && *ev.AgentSessionId == forked[0].Id { - reply += ev.Payload["text"].(string) - } - } - if !strings.Contains(reply, "hi.txt") { - t.Fatalf("fork lost the first turn; assistant said %q", reply) - } -} - -func scratchRepo(t *testing.T) string { - dir := t.TempDir() - gitOut(t, dir, "init", "--initial-branch=main") - gitOut(t, dir, "commit", "--allow-empty", "--message", "root") - return dir -} - -func gitOut(t *testing.T, dir string, args ...string) string { - t.Helper() - cmd := exec.Command("git", args...) - cmd.Dir = dir - cmd.Env = append(os.Environ(), - "GIT_AUTHOR_NAME=Root", - "GIT_AUTHOR_EMAIL=root@byre.invalid", - "GIT_COMMITTER_NAME=Root", - "GIT_COMMITTER_EMAIL=root@byre.invalid", - ) - out, err := cmd.CombinedOutput() - if err != nil { - t.Fatalf("git %v: %v\n%s", args, err, out) - } - return strings.TrimSpace(string(out)) -} diff --git a/internal/agents/mergetitle_internal_test.go b/internal/agents/mergetitle_internal_test.go deleted file mode 100644 index f1ac2ed..0000000 --- a/internal/agents/mergetitle_internal_test.go +++ /dev/null @@ -1,79 +0,0 @@ -package agents - -import ( - "context" - "encoding/json" - "path/filepath" - "strings" - "testing" - "time" - - "g.fc.run/misc/byre/internal/api" - "g.fc.run/misc/byre/internal/store" -) - -func TestSetMergeTitle(t *testing.T) { - ctx := context.Background() - st, err := store.Open(filepath.Join(t.TempDir(), "byre.db"), store.DefaultPortFirst, func(api.Event) {}) - if err != nil { - t.Fatalf("open store: %v", err) - } - t.Cleanup(func() { st.Close() }) - - now := time.Now().UTC() - _, err = st.EnsureUser(ctx, "alice", "Alice") - if err != nil { - t.Fatalf("ensure user: %v", err) - } - err = st.CreateProject(ctx, api.Project{Id: "p1", Name: "demo", RepoPath: "/nonexistent", DefaultBranch: "main", Remote: "none", CreatedBy: "alice", CreatedAt: now}) - if err != nil { - t.Fatalf("create project: %v", err) - } - err = st.CreateTask(ctx, api.Task{Id: "t1", ProjectId: "p1", Title: "Fix the thing", Branch: "b", WorktreePath: "/nonexistent", BaseRef: "x", Status: api.TaskStatusActive, CreatedBy: "alice", CreatedAt: now}, "alice") - if err != nil { - t.Fatalf("create task: %v", err) - } - - task, err := st.GetTask(ctx, "t1") - if err != nil || task.MergeTitle != "Fix the thing" { - t.Fatalf("default merge title %q, want the task title: %v", task.MergeTitle, err) - } - - m := New(st, t.TempDir(), t.TempDir(), t.TempDir()) - l := &live{id: "s1", vendor: api.Claude, taskID: "t1", projectID: "p1"} - call := func(title string) error { - raw, err := json.Marshal(setMergeTitleArgs{Title: title}) - if err != nil { - t.Fatalf("marshal: %v", err) - } - _, err = m.setMergeTitle(ctx, l, raw) - return err - } - - for _, bad := range []string{"", " ", "two\nlines", strings.Repeat("x", maxMergeTitle+1)} { - if call(bad) == nil { - t.Fatalf("set_merge_title accepted %q", bad) - } - } - - err = call(" Add retry with backoff to webhook delivery ") - if err != nil { - t.Fatalf("set_merge_title: %v", err) - } - task, err = st.GetTask(ctx, "t1") - if err != nil || task.MergeTitle != "Add retry with backoff to webhook delivery" || task.Title != "Fix the thing" { - t.Fatalf("after set_merge_title: %+v %v", task, err) - } - - if !strings.Contains(mergeInPrompt("main", task.MergeTitle), `currently "Add retry with backoff to webhook delivery"`) { - t.Fatalf("merge-in prompt doesn't show the current title: %s", mergeInPrompt("main", task.MergeTitle)) - } - - _, err = st.DiscardTask(ctx, "t1", "alice") - if err != nil { - t.Fatalf("discard: %v", err) - } - if call("Too late") == nil { - t.Fatalf("set_merge_title accepted a title for a closed task") - } -} diff --git a/internal/agents/preview_internal_test.go b/internal/agents/preview_internal_test.go deleted file mode 100644 index b42e7c9..0000000 --- a/internal/agents/preview_internal_test.go +++ /dev/null @@ -1,71 +0,0 @@ -package agents - -import ( - "context" - "encoding/json" - "path/filepath" - "strings" - "testing" - "time" - - "g.fc.run/misc/byre/internal/api" - "g.fc.run/misc/byre/internal/store" -) - -func TestExposePortRefusals(t *testing.T) { - ctx := context.Background() - st, err := store.Open(filepath.Join(t.TempDir(), "byre.db"), store.DefaultPortFirst, func(api.Event) {}) - if err != nil { - t.Fatalf("open store: %v", err) - } - t.Cleanup(func() { st.Close() }) - - now := time.Now().UTC() - _, err = st.EnsureUser(ctx, "alice", "Alice") - if err != nil { - t.Fatalf("ensure user: %v", err) - } - err = st.CreateProject(ctx, api.Project{Id: "p1", Name: "demo", RepoPath: "/nonexistent", DefaultBranch: "main", Remote: "none", CreatedBy: "alice", CreatedAt: now}) - if err != nil { - t.Fatalf("create project: %v", err) - } - err = st.CreateTask(ctx, api.Task{Id: "t1", ProjectId: "p1", Title: "t1", Branch: "b", WorktreePath: "/nonexistent", BaseRef: "x", Status: api.TaskStatusActive, CreatedBy: "alice", CreatedAt: now}, "alice") - if err != nil { - t.Fatalf("create task: %v", err) - } - - m := New(st, t.TempDir(), t.TempDir(), t.TempDir()) - l := &live{id: "s1", vendor: api.Claude, taskID: "t1", projectID: "p1"} - call := func(name string, port int) error { - raw, err := json.Marshal(exposePortArgs{Name: name, Port: port}) - if err != nil { - t.Fatalf("marshal: %v", err) - } - _, err = m.exposePort(ctx, l, raw) - return err - } - - cases := []struct { - name string - port int - want string - }{ - {"", 41000, "name must be"}, - {"two\nlines", 41000, "name must be"}, - {strings.Repeat("x", maxPreviewName+1), 41000, "name must be"}, - {"web", 40999, "outside this task's block, 41000 to 41009"}, - {"web", 41010, "outside this task's block, 41000 to 41009"}, - {"web", 41009, "nothing is listening on 127.0.0.1:41009"}, - } - for _, c := range cases { - err = call(c.name, c.port) - if err == nil || !strings.Contains(err.Error(), c.want) { - t.Fatalf("expose_port(%q, %d) = %v, want an error containing %q", c.name, c.port, err, c.want) - } - } - - previews, err := st.ListPreviews(ctx, "t1") - if err != nil || len(previews) != 0 { - t.Fatalf("previews after refusals = %v, %v", previews, err) - } -} diff --git a/internal/agents/process.go b/internal/agents/process.go index 2c09e24..f9435c1 100644 --- a/internal/agents/process.go +++ b/internal/agents/process.go @@ -61,7 +61,13 @@ func (m *Manager) startClaude(ctx context.Context, sess api.AgentSession, l *liv "Stop": {{Func: m.bounceProseQuestion(l)}}, "PostToolUse": {{Func: m.afterTool(l)}}, } - if !l.implementer() { + if l.owner != "" { + hooks = map[string][]claude.HookMatcher{ + "UserPromptSubmit": {{Func: remindGuidance(g)}}, + "PostToolUse": {{Func: m.afterTool(l)}}, + } + } + if l.reviewID != "" { hooks = map[string][]claude.HookMatcher{"UserPromptSubmit": {{Func: remindGuidance(g)}}} } diff --git a/internal/agents/queue.go b/internal/agents/queue.go new file mode 100644 index 0000000..030b438 --- /dev/null +++ b/internal/agents/queue.go @@ -0,0 +1,180 @@ +package agents + +import ( + "context" + "errors" + "fmt" + "log/slog" + "strings" + + "g.fc.run/misc/byre/internal/api" + "g.fc.run/misc/byre/internal/store" +) + +func (m *Manager) Message(ctx context.Context, sessionID, actor, text, model string, images ...string) (api.AgentSession, error) { + m.deliverMu.Lock() + defer m.deliverMu.Unlock() + + sess, err := m.store.GetSession(ctx, sessionID) + if err != nil { + return api.AgentSession{}, err + } + if sess.State != api.AgentSessionStateRunning || sess.Role != api.Implementer { + return m.Send(ctx, sessionID, actor, text, model, images...) + } + + if model != "" && model != sess.Model { + err = checkModel(sess, model) + if err != nil { + return api.AgentSession{}, err + } + err = m.store.SetSessionModel(ctx, sess.Id, model, actor) + if err != nil { + return api.AgentSession{}, err + } + sess.Model = model + } + + err = m.store.AddTaskInput(ctx, store.TaskInput{TaskID: sess.TaskId, Actor: actor, Text: text, Images: images}) + if err != nil { + return api.AgentSession{}, err + } + slog.Info("message queued", "session", sess.Id, "task", sess.TaskId, "actor", actor) + return sess, nil +} + +func (m *Manager) deliverQueued(ctx context.Context, sess api.AgentSession) (bool, error) { + inputs, err := m.store.TaskInputs(ctx, sess.TaskId) + if err != nil || len(inputs) == 0 { + return false, err + } + + task, err := m.store.GetTask(ctx, sess.TaskId) + if err != nil { + return false, err + } + + in := inputs[0] + _, err = m.Send(ctx, sess.Id, in.Actor, in.Text, "", in.Images...) + if errors.Is(err, ErrConflict) { + slog.Info("queued message held: session changed state", "session", sess.Id, "task", task.Id, "err", err) + return true, nil + } + if err != nil { + return false, err + } + slog.Info("queued message sent", "session", sess.Id, "task", task.Id, "input", in.ID) + return true, m.store.DeleteTaskInputs(ctx, []int64{in.ID}) +} + +func (m *Manager) deliverAllQueued(ctx context.Context) { + idle, err := m.store.SessionsInState(ctx, api.AgentSessionStateIdle) + if err != nil { + slog.Error("list idle sessions", "err", err) + return + } + for _, sess := range idle { + if sess.Role != api.Implementer { + continue + } + m.deliverMu.Lock() + _, err = m.deliverQueued(ctx, sess) + m.deliverMu.Unlock() + if err != nil { + slog.Error("deliver queued message", "session", sess.Id, "err", err) + } + } +} + +type queued struct { + actor string + text string +} + +func (m *Manager) takeQueuedMidTurn(ctx context.Context, l *live) []queued { + m.mu.Lock() + compacting := l.compacting + m.mu.Unlock() + if compacting { + return nil + } + + inputs, err := m.store.TaskInputs(ctx, l.taskID) + if err != nil { + slog.Error("list queued messages", "session", l.id, "err", err) + return nil + } + out := []queued{} + ids := []int64{} + for _, in := range inputs { + if len(in.Images) > 0 { + break + } + out = append(out, queued{actor: in.Actor, text: in.Text}) + ids = append(ids, in.ID) + } + if len(ids) == 0 { + return nil + } + err = m.store.DeleteTaskInputs(ctx, ids) + if err != nil { + slog.Error("delete queued messages", "session", l.id, "err", err) + return nil + } + return out +} + +func (m *Manager) takeLeadQueuedMidTurn(ctx context.Context, l *live) []queued { + m.mu.Lock() + compacting := l.compacting + m.mu.Unlock() + if compacting { + return nil + } + + m.leadMu.Lock() + defer m.leadMu.Unlock() + + inputs, err := m.store.LeadInputs(ctx, l.owner) + if err != nil { + slog.Error("list held lead messages", "session", l.id, "err", err) + return nil + } + out := []queued{} + ids := []int64{} + for _, in := range inputs { + if in.Kind != store.LeadMessage { + continue + } + if len(in.Images) > 0 { + break + } + out = append(out, queued{actor: in.Actor, text: in.Text}) + ids = append(ids, in.ID) + } + if len(ids) == 0 { + return nil + } + err = m.store.DeleteLeadInputs(ctx, ids) + if err != nil { + slog.Error("delete held lead messages", "session", l.id, "err", err) + return nil + } + return out +} + +func (m *Manager) recordQueued(ctx context.Context, l *live, msgs []queued) string { + b := &strings.Builder{} + for i, q := range msgs { + err := m.emit(ctx, l, q.actor, "user.message", map[string]any{"text": q.text, "mid_turn": true}) + if err != nil { + slog.Error("record mid-turn message", "session", l.id, "err", err) + } + if i > 0 { + b.WriteString("\n\n") + } + fmt.Fprintf(b, "%s sent you this message while you were working; take it into account now:\n\n%s", q.actor, q.text) + } + slog.Info("queued messages sent mid-turn", "session", l.id, "count", len(msgs)) + return b.String() +} diff --git a/internal/agents/ratelimit_internal_test.go b/internal/agents/ratelimit_internal_test.go deleted file mode 100644 index 2bb8478..0000000 --- a/internal/agents/ratelimit_internal_test.go +++ /dev/null @@ -1,77 +0,0 @@ -package agents - -import ( - "context" - "path/filepath" - "testing" - "time" - - "g.fc.run/misc/byre/internal/api" - "g.fc.run/misc/byre/internal/claude" - "g.fc.run/misc/byre/internal/store" -) - -func TestRateLimitedTurn(t *testing.T) { - ctx := context.Background() - st, err := store.Open(filepath.Join(t.TempDir(), "byre.db"), store.DefaultPortFirst, func(api.Event) {}) - if err != nil { - t.Fatalf("open store: %v", err) - } - t.Cleanup(func() { st.Close() }) - - now := time.Now().UTC() - _, err = st.EnsureUser(ctx, "alice", "Alice") - if err != nil { - t.Fatalf("ensure user: %v", err) - } - p := api.Project{Id: "p1", Name: "demo", RepoPath: "/nonexistent", DefaultBranch: "main", Remote: "none", CreatedBy: "alice", CreatedAt: now} - err = st.CreateProject(ctx, p) - if err != nil { - t.Fatalf("create project: %v", err) - } - task := api.Task{Id: "t1", ProjectId: "p1", Title: "t", Branch: "b", WorktreePath: "/nonexistent", BaseRef: "x", Status: api.TaskStatusActive, CreatedBy: "alice", CreatedAt: now} - err = st.CreateTask(ctx, task, "alice") - if err != nil { - t.Fatalf("create task: %v", err) - } - sess := api.AgentSession{Id: "s1", TaskId: "t1", Vendor: api.Claude, VendorSessionId: "s1", State: api.AgentSessionStateRunning, Role: api.Implementer, CreatedBy: "alice", CreatedAt: now} - err = st.CreateSession(ctx, sess, "p1") - if err != nil { - t.Fatalf("create session: %v", err) - } - - m := New(st, t.TempDir(), t.TempDir(), t.TempDir()) - l := &live{id: "s1", dir: t.TempDir(), vendor: api.Claude, taskID: "t1", projectID: "p1"} - - feed := func(typ, raw string) { - t.Helper() - err := m.handle(ctx, l, claude.Event{Type: typ, Raw: []byte(raw)}) - if err != nil { - t.Fatalf("handle %s: %v", raw, err) - } - } - - feed("rate_limit_event", `{"type":"rate_limit_event","rate_limit_info":{"status":"allowed","rateLimitType":"five_hour","resetsAt":1791972000}}`) - usage, err := m.Usage(ctx) - if err != nil { - t.Fatalf("usage: %v", err) - } - if usage.RateLimits["claude"]["status"] != "allowed" { - t.Fatalf("rate limit status not recorded: %+v", usage.RateLimits) - } - - feed("rate_limit_event", `{"type":"rate_limit_event","rate_limit_info":{"status":"rejected","rateLimitType":"five_hour","resetsAt":1791972000}}`) - feed("result", `{"type":"result","subtype":"success","is_error":true,"result":"API Error: rate limited","num_turns":1}`) - - items, err := st.ListInbox(ctx, store.InboxFilter{SessionID: "s1", State: api.Open}) - if err != nil { - t.Fatalf("list inbox: %v", err) - } - if len(items) != 1 || items[0].Kind != api.RateLimited || items[0].Priority != api.Blocked { - t.Fatalf("want one blocked rate_limited item, got %+v", items) - } - limit, ok := items[0].Payload["rate_limit"].(map[string]any) - if !ok || limit["status"] != "rejected" || items[0].Payload["vendor"] != "claude" { - t.Fatalf("unexpected payload %+v", items[0].Payload) - } -} diff --git a/internal/agents/respond_internal_test.go b/internal/agents/respond_internal_test.go deleted file mode 100644 index 85694ab..0000000 --- a/internal/agents/respond_internal_test.go +++ /dev/null @@ -1,77 +0,0 @@ -package agents - -import ( - "errors" - "testing" - - "g.fc.run/misc/byre/internal/api" -) - -func TestCheckResponse(t *testing.T) { - question := api.InboxItem{ - Id: "q1", - Kind: api.Question, - State: api.Open, - Payload: map[string]any{"options": []any{"red", "blue"}}, - } - checklist := api.InboxItem{ - Id: "q2", - Kind: api.Question, - State: api.Open, - Payload: map[string]any{"options": []any{"tests", "docs", "lint"}, "multiple": true}, - } - report := api.InboxItem{Id: "r1", Kind: api.Report, State: api.Open, Payload: map[string]any{}} - failure := api.InboxItem{Id: "e1", Kind: api.SessionError, State: api.Open, Payload: map[string]any{}} - closed := question - closed.State = api.Answered - - str := func(s string) *string { return &s } - opts := func(s ...string) *[]string { return &s } - - cases := []struct { - name string - item api.InboxItem - create api.InboxResponseCreate - state api.InboxState - want error - }{ - {"option", question, api.InboxResponseCreate{Type: api.Option, Options: opts("blue")}, api.Answered, nil}, - {"option with text", question, api.InboxResponseCreate{Type: api.Option, Options: opts("red"), Text: str("but darker")}, "", ErrInvalid}, - {"unknown option", question, api.InboxResponseCreate{Type: api.Option, Options: opts("green")}, "", ErrInvalid}, - {"missing option", question, api.InboxResponseCreate{Type: api.Option}, "", ErrInvalid}, - {"no options", question, api.InboxResponseCreate{Type: api.Option, Options: opts()}, "", ErrInvalid}, - {"two picks on pick-one", question, api.InboxResponseCreate{Type: api.Option, Options: opts("red", "blue")}, "", ErrInvalid}, - {"checked", checklist, api.InboxResponseCreate{Type: api.Option, Options: opts("tests", "lint")}, api.Answered, nil}, - {"checked one", checklist, api.InboxResponseCreate{Type: api.Option, Options: opts("docs")}, api.Answered, nil}, - {"checked with text", checklist, api.InboxResponseCreate{Type: api.Option, Options: opts("tests", "lint"), Text: str("also run go vet")}, api.Answered, nil}, - {"checked twice", checklist, api.InboxResponseCreate{Type: api.Option, Options: opts("docs", "docs")}, "", ErrInvalid}, - {"checked unknown", checklist, api.InboxResponseCreate{Type: api.Option, Options: opts("docs", "deploy")}, "", ErrInvalid}, - {"other", question, api.InboxResponseCreate{Type: api.Other, Text: str("green")}, api.Answered, nil}, - {"empty other", question, api.InboxResponseCreate{Type: api.Other, Text: str(" ")}, "", ErrInvalid}, - {"other on error", failure, api.InboxResponseCreate{Type: api.Other, Text: str("retry")}, api.Answered, nil}, - {"other on report", report, api.InboxResponseCreate{Type: api.Other, Text: str("stop")}, "", ErrInvalid}, - {"snooze", question, api.InboxResponseCreate{Type: "snooze"}, "", ErrInvalid}, - {"dismiss report", report, api.InboxResponseCreate{Type: api.Dismiss}, api.Dismissed, nil}, - {"dismiss question", question, api.InboxResponseCreate{Type: api.Dismiss}, "", ErrInvalid}, - {"option on report", report, api.InboxResponseCreate{Type: api.Option, Options: opts("red")}, "", ErrInvalid}, - {"already answered", closed, api.InboxResponseCreate{Type: api.Option, Options: opts("red")}, "", ErrConflict}, - } - - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - state, err := CheckResponse(c.item, c.create) - if c.want != nil { - if !errors.Is(err, c.want) { - t.Fatalf("want %v, got %v", c.want, err) - } - return - } - if err != nil { - t.Fatalf("unexpected error %v", err) - } - if state != c.state { - t.Fatalf("want state %s, got %s", c.state, state) - } - }) - } -} diff --git a/internal/agents/review_internal_test.go b/internal/agents/review_internal_test.go deleted file mode 100644 index 3c1a2d1..0000000 --- a/internal/agents/review_internal_test.go +++ /dev/null @@ -1,38 +0,0 @@ -package agents - -import ( - "testing" - - "g.fc.run/misc/byre/internal/api" -) - -func TestReviewVendor(t *testing.T) { - if got := reviewVendor(api.Claude); got != api.Codex { - t.Fatalf("reviewer for a claude task: %s", got) - } - if got := reviewVendor(api.Codex); got != api.Claude { - t.Fatalf("reviewer for a codex task: %s", got) - } -} - -func TestReviewLine(t *testing.T) { - result := map[string]any{ - "summary": "Mostly fine.", - "findings": []any{ - map[string]any{"severity": "high", "title": "Nil map write", "detail": "Panics on first use.", "path": "a.go", "line": float64(12)}, - map[string]any{"severity": "low", "title": "Typo", "detail": "Spelling."}, - }, - } - got := reviewLine(api.Review{SnapshotN: 3, State: api.ReviewStateDone, Result: &result}) - want := "review of snapshot 3: Mostly fine.\n" + - " - [high] Nil map write (a.go:12): Panics on first use.\n" + - " - [low] Typo: Spelling." - if got != want { - t.Fatalf("reviewLine:\n%s\nwant:\n%s", got, want) - } - - failed := reviewLine(api.Review{SnapshotN: 4, State: api.ReviewStateFailed, Error: "boom"}) - if failed != "review of snapshot 4 failed: boom" { - t.Fatalf("failed reviewLine %q", failed) - } -} diff --git a/internal/agents/review_test.go b/internal/agents/review_test.go deleted file mode 100644 index 28ea97f..0000000 --- a/internal/agents/review_test.go +++ /dev/null @@ -1,65 +0,0 @@ -package agents_test - -import ( - "errors" - "os" - "path/filepath" - "testing" - "time" - - "g.fc.run/misc/byre/internal/api" -) - -const reviewAndAskPrompt = "Create a file hi.txt containing exactly the word hi. " + - "Then ask the human with byre's ask_human tool whether to keep the file: blocking, with the choices `- (*) Keep it` and `- ( ) Delete it`. Then end your turn." - -func TestSnapshotReview(t *testing.T) { - t.Parallel() - e := newEnv(t) - m := e.manager() - - sess, err := m.Create(e.ctx, e.task, e.user, reviewAndAskPrompt, testModel) - if err != nil { - t.Fatalf("create session: %v", err) - } - e.waitIdle() - - events := e.waitFor("review finished", func(ev api.Event) bool { return ev.Kind == "review.finished" }) - finished := events[len(events)-1] - review, err := e.store.GetReview(e.ctx, finished.Payload["id"].(string)) - if err != nil { - t.Fatalf("get review: %v", err) - } - if review.State != api.ReviewStateDone || review.SnapshotN != 1 || review.Vendor != string(api.Codex) || review.Result == nil { - t.Fatalf("review %+v", review) - } - if _, ok := (*review.Result)["summary"].(string); !ok { - t.Fatalf("review result has no summary: %+v", *review.Result) - } - - questions := e.openQuestions(sess.Id) - if len(questions) != 1 || len(questions[0].Reviews) != 1 || questions[0].Reviews[0].Id != review.Id { - t.Fatalf("question doesn't show its turn's review: %+v", questions) - } - - sessions, err := e.store.ListSessions(e.ctx, e.task.Id) - if err != nil || len(sessions) != 1 || sessions[0].Id != sess.Id { - t.Fatalf("task sessions %+v %v, want only the implementer", sessions, err) - } - - dir := filepath.Join(e.worktrees, e.project.Id, "review-"+review.Id) - for range 100 { - _, err = os.Stat(dir) - if errors.Is(err, os.ErrNotExist) { - break - } - time.Sleep(100 * time.Millisecond) - } - if !errors.Is(err, os.ErrNotExist) { - t.Fatalf("review worktree %s still there: %v", dir, err) - } - reviewer, err := e.store.GetSession(e.ctx, review.AgentSessionId) - if err != nil || reviewer.State != api.AgentSessionStateCompleted || reviewer.Role != api.Reviewer { - t.Fatalf("reviewer session %+v %v", reviewer, err) - } -} diff --git a/internal/agents/snapshots_internal_test.go b/internal/agents/snapshots_internal_test.go deleted file mode 100644 index fd0401d..0000000 --- a/internal/agents/snapshots_internal_test.go +++ /dev/null @@ -1,177 +0,0 @@ -package agents - -import ( - "context" - "os" - "os/exec" - "path/filepath" - "strings" - "testing" - "time" - - "g.fc.run/misc/byre/internal/api" - "g.fc.run/misc/byre/internal/git" - "g.fc.run/misc/byre/internal/store" -) - -func TestSnapshots(t *testing.T) { - ctx := context.Background() - events := make(chan api.Event, 256) - st, err := store.Open(filepath.Join(t.TempDir(), "byre.db"), store.DefaultPortFirst, func(ev api.Event) { events <- ev }) - if err != nil { - t.Fatalf("open store: %v", err) - } - t.Cleanup(func() { st.Close() }) - - repo := t.TempDir() - gitIn := func(dir string, args ...string) string { - t.Helper() - cmd := exec.Command("git", args...) - cmd.Dir = dir - cmd.Env = append(os.Environ(), "GIT_AUTHOR_NAME=Root", "GIT_AUTHOR_EMAIL=root@byre.invalid", "GIT_COMMITTER_NAME=Root", "GIT_COMMITTER_EMAIL=root@byre.invalid") - out, err := cmd.CombinedOutput() - if err != nil { - t.Fatalf("git %v: %v\n%s", args, err, out) - } - return strings.TrimSpace(string(out)) - } - gitIn(repo, "init", "--initial-branch=main") - err = os.WriteFile(filepath.Join(repo, "tracked.txt"), []byte("one\n"), 0o644) - if err != nil { - t.Fatal(err) - } - gitIn(repo, "add", "tracked.txt") - gitIn(repo, "commit", "--message", "root") - - now := time.Now().UTC() - _, err = st.EnsureUser(ctx, "alice", "Alice") - if err != nil { - t.Fatalf("ensure user: %v", err) - } - project := api.Project{Id: "p1", Name: "demo", RepoPath: repo, DefaultBranch: "main", Remote: "none", CreatedBy: "alice", CreatedAt: now} - err = st.CreateProject(ctx, project) - if err != nil { - t.Fatalf("create project: %v", err) - } - task := api.Task{Id: "t1", ProjectId: "p1", Title: "snap", Branch: "byre/t1", WorktreePath: filepath.Join(t.TempDir(), "t1"), Status: api.TaskStatusActive, CreatedBy: "alice", CreatedAt: now} - task.BaseRef, err = git.AddWorktree(repo, task.WorktreePath, task.Branch, "main") - if err != nil { - t.Fatalf("add worktree: %v", err) - } - err = st.CreateTask(ctx, task, "alice") - if err != nil { - t.Fatalf("create task: %v", err) - } - err = st.CreateSession(ctx, api.AgentSession{Id: "s1", TaskId: "t1", Vendor: api.Claude, VendorSessionId: "s1", State: api.AgentSessionStateIdle, Role: api.Implementer, CreatedBy: "alice", CreatedAt: now}, "p1") - if err != nil { - t.Fatalf("create session: %v", err) - } - - m := New(st, t.TempDir(), t.TempDir(), t.TempDir()) - l := &live{id: "s1", dir: task.WorktreePath, gitName: "Alice Test", gitEmail: "alice@byre.invalid", vendor: api.Claude, taskID: "t1", projectID: "p1"} - write := func(name, content string) { - t.Helper() - err := os.WriteFile(filepath.Join(task.WorktreePath, name), []byte(content), 0o644) - if err != nil { - t.Fatal(err) - } - } - snapshots := func() []api.Snapshot { - t.Helper() - snaps, err := st.ListSnapshots(ctx, "t1") - if err != nil { - t.Fatalf("list snapshots: %v", err) - } - return snaps - } - indexPath := gitIn(task.WorktreePath, "rev-parse", "--path-format=absolute", "--git-path", "index") - indexBefore, err := os.ReadFile(indexPath) - if err != nil { - t.Fatal(err) - } - - write("tracked.txt", "two\n") - write("untracked.txt", "new\n") - taken, err := m.takeSnapshot(ctx, l, api.SnapshotTriggerMessage, 0) - if err != nil || taken == nil || taken.N != 1 { - t.Fatalf("snapshot: %+v %v", taken, err) - } - snaps := snapshots() - if len(snaps) != 1 || snaps[0].N != 1 || snaps[0].Trigger != api.SnapshotTriggerMessage || snaps[0].AgentSessionId != "s1" { - t.Fatalf("after the first change: %+v", snaps) - } - first := snaps[0] - if ref := gitIn(repo, "rev-parse", "refs/byre/tasks/t1/snap/1"); ref != first.CommitSha { - t.Fatalf("snap/1 is %s, want %s", ref, first.CommitSha) - } - if tree := gitIn(repo, "rev-parse", first.CommitSha+"^{tree}"); tree != first.TreeSha { - t.Fatalf("commit tree %s, want %s", tree, first.TreeSha) - } - if parent := gitIn(repo, "rev-parse", first.CommitSha+"^"); parent != task.BaseRef { - t.Fatalf("snapshot parent %s, want the worktree HEAD %s", parent, task.BaseRef) - } - if got := gitIn(repo, "show", first.CommitSha+":untracked.txt"); got != "new" { - t.Fatalf("untracked.txt in snapshot: %q", got) - } - if got := gitIn(repo, "show", first.CommitSha+":tracked.txt"); got != "two" { - t.Fatalf("tracked.txt in snapshot: %q", got) - } - - indexAfter, err := os.ReadFile(indexPath) - if err != nil { - t.Fatal(err) - } - if string(indexBefore) != string(indexAfter) { - t.Fatalf("snapshot modified the worktree's index") - } - if staged := gitIn(task.WorktreePath, "diff", "--cached", "--name-only"); staged != "" { - t.Fatalf("snapshot staged files in the real index: %s", staged) - } - - taken, err = m.takeSnapshot(ctx, l, api.SnapshotTriggerMessage, 1) - if err != nil || taken != nil { - t.Fatalf("unchanged snapshot: %+v %v", taken, err) - } - if snaps := snapshots(); len(snaps) != 1 { - t.Fatalf("an unchanged tree made a snapshot: %+v", snaps) - } - - write("untracked.txt", "newer\n") - m.snapshotSoon(l, api.SnapshotTriggerTurnEnd, 0) - deadline := time.After(30 * time.Second) - for created := false; !created; { - select { - case ev := <-events: - created = ev.Kind == "snapshot.created" && ev.Payload["n"] == float64(2) - case <-deadline: - t.Fatalf("no snapshot.created event") - } - } - snaps = snapshots() - if len(snaps) != 2 || snaps[1].N != 2 || snaps[1].Trigger != api.SnapshotTriggerTurnEnd || snaps[1].EventSeq < first.EventSeq { - t.Fatalf("after the second change: %+v", snaps) - } - if got := gitIn(repo, "show", "refs/byre/tasks/t1/snap/2:untracked.txt"); got != "newer" { - t.Fatalf("untracked.txt in snap/2: %q", got) - } - - err = m.RemoveTask(ctx, task, project, "alice") - if err != nil { - t.Fatalf("remove task: %v", err) - } - _, err = st.DiscardTask(ctx, "t1", "alice") - if err != nil { - t.Fatalf("discard: %v", err) - } - if refs := gitIn(repo, "for-each-ref", "refs/byre/"); refs != "" { - t.Fatalf("snapshot refs left after discard:\n%s", refs) - } - if snaps := snapshots(); len(snaps) != 2 { - t.Fatalf("snapshot rows after discard: %+v", snaps) - } - - m.snapshotSoon(l, api.SnapshotTriggerMessage, 0) - if refs := gitIn(repo, "for-each-ref", "refs/byre/"); refs != "" { - t.Fatalf("snapshot taken after discard:\n%s", refs) - } -} diff --git a/internal/agents/subagent_internal_test.go b/internal/agents/subagent_internal_test.go deleted file mode 100644 index 227b13b..0000000 --- a/internal/agents/subagent_internal_test.go +++ /dev/null @@ -1,82 +0,0 @@ -package agents - -import ( - "context" - "path/filepath" - "testing" - "time" - - "g.fc.run/misc/byre/internal/api" - "g.fc.run/misc/byre/internal/claude" - "g.fc.run/misc/byre/internal/store" -) - -func TestSubagentEvents(t *testing.T) { - ctx := context.Background() - st, err := store.Open(filepath.Join(t.TempDir(), "byre.db"), store.DefaultPortFirst, func(api.Event) {}) - if err != nil { - t.Fatalf("open store: %v", err) - } - t.Cleanup(func() { st.Close() }) - - now := time.Now().UTC() - _, err = st.EnsureUser(ctx, "alice", "Alice") - if err != nil { - t.Fatalf("ensure user: %v", err) - } - err = st.CreateProject(ctx, api.Project{Id: "p1", Name: "demo", RepoPath: "/nonexistent", DefaultBranch: "main", Remote: "none", CreatedBy: "alice", CreatedAt: now}) - if err != nil { - t.Fatalf("create project: %v", err) - } - err = st.CreateTask(ctx, api.Task{Id: "t1", ProjectId: "p1", Title: "t", Branch: "b", WorktreePath: "/nonexistent", BaseRef: "x", Status: api.TaskStatusActive, CreatedBy: "alice", CreatedAt: now}, "alice") - if err != nil { - t.Fatalf("create task: %v", err) - } - - m := New(st, t.TempDir(), t.TempDir(), t.TempDir()) - l := &live{id: "s1", dir: t.TempDir(), vendor: api.Claude, taskID: "t1", projectID: "p1"} - - feed := func(typ, raw string) { - t.Helper() - err := m.handle(ctx, l, claude.Event{Type: typ, Raw: []byte(raw)}) - if err != nil { - t.Fatalf("handle %s: %v", raw, err) - } - } - - feed("assistant", `{"type":"assistant","message":{"model":"m","content":[{"type":"tool_use","id":"toolu_agent","name":"Agent","input":{"subagent_type":"general-purpose","description":"d","prompt":"p"}}],"usage":{"input_tokens":2,"cache_read_input_tokens":100,"output_tokens":17}},"parent_tool_use_id":null}`) - feed("assistant", `{"type":"assistant","message":{"model":"m","content":[{"type":"tool_use","id":"toolu_bash","name":"Bash","input":{"command":"ls"}}],"usage":{"input_tokens":2,"cache_creation_input_tokens":33494,"output_tokens":20}},"parent_tool_use_id":"toolu_agent","agent_id":"a1"}`) - feed("user", `{"type":"user","message":{"role":"user","content":[{"tool_use_id":"toolu_bash","type":"tool_result","content":"adsb-tools","is_error":false}]},"parent_tool_use_id":"toolu_agent","agent_id":"a1"}`) - feed("assistant", `{"type":"assistant","message":{"model":"m","content":[{"type":"text","text":"The first entry is adsb-tools."}],"usage":{"input_tokens":2,"output_tokens":3}},"parent_tool_use_id":"toolu_agent","agent_id":"a1"}`) - - events, err := st.TaskEvents(ctx, "t1", 0, 100) - if err != nil { - t.Fatalf("task events: %v", err) - } - got := []string{} - for _, ev := range events { - if ev.AgentSessionId == nil { - continue - } - parent, _ := ev.Payload["parent_tool_use_id"].(string) - got = append(got, ev.Kind+" "+parent) - } - want := []string{ - "agent.tool_call ", - "session.context ", - "agent.tool_call toolu_agent", - "agent.tool_result toolu_agent", - "agent.message toolu_agent", - } - if len(got) != len(want) { - t.Fatalf("want %q, got %q", want, got) - } - for i := range want { - if got[i] != want[i] { - t.Fatalf("want %q, got %q", want, got) - } - } - if l.contextUsed != 119 { - t.Fatalf("subagent usage changed the session's context: %d", l.contextUsed) - } -} diff --git a/internal/api/api.gen.go b/internal/api/api.gen.go index 1e97aba..2f4b042 100644 --- a/internal/api/api.gen.go +++ b/internal/api/api.gen.go @@ -2930,42 +2930,43 @@ var swaggerSpec = []string{ "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", - "t3o9Zoe4MMdQlzj/e7j0avqH3eGPH/9fAAAA//8=", + "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", + "H/9fAAAA//8=", } // decodeSpec returns the embedded OpenAPI spec as raw JSON bytes, diff --git a/internal/claude/badline_internal_test.go b/internal/claude/badline_internal_test.go deleted file mode 100644 index 8922f10..0000000 --- a/internal/claude/badline_internal_test.go +++ /dev/null @@ -1,55 +0,0 @@ -package claude - -import ( - "strings" - "testing" - "time" -) - -func TestBadLines(t *testing.T) { - bad := []string{} - s := &Session{ - events: make(chan Event, 16), - done: make(chan struct{}), - onBadLine: func(line []byte, reason string) { - bad = append(bad, string(line)+" | "+reason) - }, - } - - input := strings.Join([]string{ - `{"type":"assistant","message":{"content":[]}}`, - ``, - `warning: something on stdout`, - `{"type":"assistant",`, - `{"type":"mystery","x":1}`, - `{"type":"keep_alive"}`, - `{"type":"tool_progress","heartbeat":true}`, - }, "\n") - - before := time.Now() - s.readLoop(strings.NewReader(input)) - - want := []string{ - `warning: something on stdout | not a JSON object`, - `{"type":"assistant", | unexpected end of JSON input`, - `{"type":"mystery","x":1} | unrecognised type "mystery"`, - } - if strings.Join(bad, "\n") != strings.Join(want, "\n") { - t.Errorf("bad lines:\n%s\nwant:\n%s", strings.Join(bad, "\n"), strings.Join(want, "\n")) - } - - types := []string{} - for ev := range s.events { - types = append(types, ev.Type) - } - if strings.Join(types, ",") != "assistant,mystery,tool_progress" { - t.Errorf("events = %v", types) - } - - if s.LastLine().Before(before) { - t.Errorf("last line time %v not updated", s.LastLine()) - } - if !s.Exited() { - t.Errorf("session not exited after stdout closed") - } -} diff --git a/internal/claude/session_test.go b/internal/claude/session_test.go deleted file mode 100644 index 9e29caa..0000000 --- a/internal/claude/session_test.go +++ /dev/null @@ -1,452 +0,0 @@ -package claude_test - -import ( - "bytes" - "context" - "encoding/json" - "image" - "image/color" - "image/draw" - "image/png" - "os" - "os/exec" - "path/filepath" - "slices" - "strings" - "sync" - "testing" - "time" - - "g.fc.run/misc/byre/internal/claude" - "g.fc.run/misc/byre/internal/mcp" -) - -const ( - testModel = "sonnet" - gitName = "Byre Test" - gitEmail = "test@byre.invalid" -) - -func TestCommitIdentityHooksAndMCP(t *testing.T) { - t.Parallel() - ctx := testContext(t) - repo := scratchRepo(t) - - finished := 0 - mu := sync.Mutex{} - finish := mcp.Tool{ - Name: "finish", - Description: "Call this once the task is complete.", - InputSchema: json.RawMessage(`{"type": "object", "properties": {}}`), - Handler: func(ctx context.Context, args json.RawMessage) (string, error) { - mu.Lock() - defer mu.Unlock() - finished++ - return "noted", nil - }, - } - - tools := []string{} - s := start(t, ctx, claude.Options{ - Cwd: repo, - MCPServers: []*mcp.Server{{Name: "byre", Version: "0.0.0", Tools: []mcp.Tool{finish}}}, - Hooks: map[string][]claude.HookMatcher{ - "PostToolUse": {{Func: func(ctx context.Context, input json.RawMessage) (map[string]any, error) { - in := struct { - ToolName string `json:"tool_name"` - }{} - err := json.Unmarshal(input, &in) - if err != nil { - return nil, err - } - mu.Lock() - defer mu.Unlock() - tools = append(tools, in.ToolName) - return map[string]any{}, nil - }}}, - }, - }) - - send(t, s, `Create a file hello.txt containing exactly the word hi, then git add and git commit it with the message "add hello", then call the finish tool.`) - waitResult(t, ctx, s, nil) - - mu.Lock() - defer mu.Unlock() - t.Logf("tools=%v finished=%d", tools, finished) - - if finished == 0 { - t.Fatalf("the MCP finish tool was never called") - } - if len(tools) == 0 { - t.Fatalf("PostToolUse hook never fired") - } - if slices.Contains(tools, "ToolSearch") { - t.Fatalf("byre MCP tools were deferred behind ToolSearch") - } - - log := git(t, repo, "log", "-1", "--format=%an <%ae>%n%cn <%ce>%n%B") - want := gitName + " <" + gitEmail + ">\n" + gitName + " <" + gitEmail + ">\nadd hello" - if !strings.HasPrefix(log, want) { - t.Fatalf("unexpected commit:\n%s", log) - } - lower := strings.ToLower(log) - if strings.Contains(lower, "co-authored-by") || strings.Contains(lower, "claude") { - t.Fatalf("agent attribution in commit:\n%s", log) - } -} - -func TestStopHookBlocks(t *testing.T) { - t.Parallel() - ctx := testContext(t) - - blocked := false - mu := sync.Mutex{} - s := start(t, ctx, claude.Options{ - Cwd: scratchRepo(t), - Hooks: map[string][]claude.HookMatcher{ - "Stop": {{Func: func(ctx context.Context, input json.RawMessage) (map[string]any, error) { - mu.Lock() - defer mu.Unlock() - if blocked { - return map[string]any{}, nil - } - blocked = true - return map[string]any{"decision": "block", "reason": "Before stopping, reply with the single word PINEAPPLE."}, nil - }}}, - }, - }) - - send(t, s, "Reply with the single word ok.") - text := waitResult(t, ctx, s, nil) - - if !blocked { - t.Fatalf("Stop hook never fired") - } - if !strings.Contains(text, "PINEAPPLE") { - t.Fatalf("Stop hook block reason not acted on; assistant said %q", text) - } -} - -func TestTextDeltas(t *testing.T) { - t.Parallel() - ctx := testContext(t) - s := start(t, ctx, claude.Options{Cwd: scratchRepo(t)}) - - send(t, s, "Without using any tools, count from one to twenty in words, separated by spaces.") - deltas := []string{} - text := waitResult(t, ctx, s, func(ev claude.Event) { - if ev.Type != "stream_event" { - return - } - d, err := ev.TextDelta() - if err != nil { - t.Fatalf("parse stream event: %v", err) - } - if d != "" { - deltas = append(deltas, d) - } - }) - - if len(deltas) < 2 { - t.Fatalf("expected several text deltas, got %d", len(deltas)) - } - if strings.Join(deltas, "") != text { - t.Fatalf("deltas %q don't add up to the final text %q", deltas, text) - } -} - -func TestInterrupt(t *testing.T) { - t.Parallel() - ctx := testContext(t) - s := start(t, ctx, claude.Options{Cwd: scratchRepo(t)}) - - send(t, s, "Run the bash command `ping -c 120 127.0.0.1` in the foreground (not in the background) and wait for it to finish, then say done.") - - interruptedAt := time.Time{} - waitResult(t, ctx, s, func(ev claude.Event) { - if ev.Type == "user" { - blocks, err := ev.Blocks() - if err != nil { - t.Fatalf("parse user: %v", err) - } - for _, b := range blocks { - if b.Type == "tool_result" && (!b.IsError || strings.Contains(string(b.Content), "Blocked")) { - t.Fatalf("command finished or was refused instead of being interrupted: %s", b.Content) - } - } - } - - if !interruptedAt.IsZero() || !hasToolUse(t, ev) { - return - } - time.Sleep(2 * time.Second) - interruptedAt = time.Now() - err := s.Interrupt(ctx) - if err != nil { - t.Errorf("interrupt: %v", err) - } - }) - - if interruptedAt.IsZero() { - t.Fatalf("never saw a tool use to interrupt") - } - elapsed := time.Since(interruptedAt) - t.Logf("turn ended %s after interrupt", elapsed) - if elapsed > 30*time.Second { - t.Fatalf("interrupt took %s to end the turn", elapsed) - } - - send(t, s, "Reply with the single word ok.") - text := waitResult(t, ctx, s, nil) - if !strings.Contains(strings.ToLower(text), "ok") { - t.Fatalf("session unusable after interrupt; assistant said %q", text) - } -} - -func TestImageInput(t *testing.T) { - t.Parallel() - ctx := testContext(t) - s := start(t, ctx, claude.Options{Cwd: scratchRepo(t)}) - - img := image.NewRGBA(image.Rect(0, 0, 64, 64)) - draw.Draw(img, img.Bounds(), &image.Uniform{C: color.RGBA{R: 255, A: 255}}, image.Point{}, draw.Src) - buf := bytes.Buffer{} - err := png.Encode(&buf, img) - if err != nil { - t.Fatalf("encode png: %v", err) - } - - err = s.Send("Without using any tools, what single colour fills the attached image? Reply with just the colour name.", claude.Image{MediaType: "image/png", Data: buf.Bytes()}) - if err != nil { - t.Fatalf("send: %v", err) - } - text := waitResult(t, ctx, s, nil) - - if !strings.Contains(strings.ToLower(text), "red") { - t.Fatalf("model didn't see the image; assistant said %q", text) - } -} - -func TestResume(t *testing.T) { - t.Parallel() - ctx := testContext(t) - repo := scratchRepo(t) - - first, err := claude.Start(ctx, options(t, "first", claude.Options{Cwd: repo})) - if err != nil { - t.Fatalf("start: %v", err) - } - send(t, first, "Remember the codeword AZURE-17. Reply with the single word ok.") - waitResult(t, ctx, first, nil) - id := first.ID() - closeSession(t, first) - - if id == "" { - t.Fatalf("no session id from first session") - } - - resumed, err := claude.Start(ctx, options(t, "resumed", claude.Options{Cwd: repo, Resume: id})) - if err != nil { - t.Fatalf("resume: %v", err) - } - t.Cleanup(func() { closeSession(t, resumed) }) - - send(t, resumed, "What was the codeword? Reply with just the codeword.") - text := waitResult(t, ctx, resumed, nil) - - if !strings.Contains(text, "AZURE-17") { - t.Fatalf("resumed session lost context; assistant said %q", text) - } - if resumed.ID() != id { - t.Fatalf("resume changed session id: want %s, got %s", id, resumed.ID()) - } -} - -func TestForkAt(t *testing.T) { - t.Parallel() - ctx := testContext(t) - - first, err := claude.Start(ctx, options(t, "first", claude.Options{Cwd: scratchRepo(t)})) - if err != nil { - t.Fatalf("start: %v", err) - } - send(t, first, "Run the bash command `echo APPLE-1`. After it finishes, run the bash command `echo BANANA-2`. Then reply with the single word ok.") - cut := "" - waitResult(t, ctx, first, func(ev claude.Event) { - if cut != "" || ev.Type != "user" { - return - } - blocks, err := ev.Blocks() - if err != nil { - t.Fatalf("parse user: %v", err) - } - for _, b := range blocks { - if b.Type == "tool_result" && strings.Contains(string(b.Content), "APPLE-1") { - cut, err = ev.UUID() - if err != nil { - t.Fatalf("parse uuid: %v", err) - } - } - } - }) - id := first.ID() - closeSession(t, first) - - if cut == "" { - t.Fatalf("never saw the APPLE-1 tool result") - } - - forkID := "8a1b2c3d-0000-4000-8000-" + strings.ReplaceAll(time.Now().Format("150405.000000"), ".", "") - fork, err := claude.Start(ctx, options(t, "fork", claude.Options{ - Cwd: scratchRepo(t), - SessionID: forkID, - Resume: id, - ResumeAt: cut, - ForkSession: true, - })) - if err != nil { - t.Fatalf("fork: %v", err) - } - t.Cleanup(func() { closeSession(t, fork) }) - - send(t, fork, "Without running anything, list the exact output of every bash command you have run in this conversation, one per line, and nothing else.") - text := waitResult(t, ctx, fork, nil) - - if !strings.Contains(text, "APPLE-1") { - t.Fatalf("fork lost the conversation before the cut; assistant said %q", text) - } - if strings.Contains(text, "BANANA-2") { - t.Fatalf("fork kept the conversation after the cut; assistant said %q", text) - } - if fork.ID() != forkID { - t.Fatalf("fork has session id %s, want %s", fork.ID(), forkID) - } -} - -func testContext(t *testing.T) context.Context { - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) - t.Cleanup(cancel) - return ctx -} - -func scratchRepo(t *testing.T) string { - dir := t.TempDir() - git(t, dir, "init", "--initial-branch=main") - git(t, dir, "commit", "--allow-empty", "--message", "root") - return dir -} - -func options(t *testing.T, suffix string, opts claude.Options) claude.Options { - name := t.Name() - if suffix != "" { - name += "-" + suffix - } - - err := os.MkdirAll(filepath.Join("testdata", "transcripts"), 0o755) - if err != nil { - t.Fatalf("mkdir transcripts: %v", err) - } - - f, err := os.Create(filepath.Join("testdata", "transcripts", name+".jsonl")) - if err != nil { - t.Fatalf("create transcript: %v", err) - } - t.Cleanup(func() { f.Close() }) - - opts.Model = testModel - opts.GitName = gitName - opts.GitEmail = gitEmail - opts.Transcript = f - return opts -} - -func start(t *testing.T, ctx context.Context, opts claude.Options) *claude.Session { - s, err := claude.Start(ctx, options(t, "", opts)) - if err != nil { - t.Fatalf("start: %v", err) - } - t.Cleanup(func() { closeSession(t, s) }) - return s -} - -func closeSession(t *testing.T, s *claude.Session) { - err := s.Close() - if err != nil { - t.Logf("claude exit: %v", err) - } -} - -func send(t *testing.T, s *claude.Session, text string) { - err := s.Send(text) - if err != nil { - t.Fatalf("send: %v", err) - } -} - -func waitResult(t *testing.T, ctx context.Context, s *claude.Session, onEvent func(claude.Event)) string { - text := []string{} - for { - select { - case ev, ok := <-s.Events(): - if !ok { - t.Fatalf("claude exited before result") - } - if onEvent != nil { - onEvent(ev) - } - if ev.Type == "assistant" { - blocks, err := ev.Blocks() - if err != nil { - t.Fatalf("parse assistant: %v", err) - } - for _, b := range blocks { - if b.Type == "text" { - text = append(text, b.Text) - } - } - } - if ev.Type == "result" { - res, err := ev.Result() - if err != nil { - t.Fatalf("parse result: %v", err) - } - t.Logf("result %s: %d turns, %dms, $%.4f", res.Subtype, res.NumTurns, res.DurationMS, res.TotalCostUSD) - return strings.Join(text, "\n") - } - case <-ctx.Done(): - t.Fatalf("timed out waiting for result") - } - } -} - -func hasToolUse(t *testing.T, ev claude.Event) bool { - if ev.Type != "assistant" { - return false - } - blocks, err := ev.Blocks() - if err != nil { - t.Fatalf("parse assistant: %v", err) - } - for _, b := range blocks { - if b.Type == "tool_use" { - return true - } - } - return false -} - -func git(t *testing.T, dir string, args ...string) string { - cmd := exec.Command("git", args...) - cmd.Dir = dir - cmd.Env = append(os.Environ(), - "GIT_AUTHOR_NAME="+gitName, - "GIT_AUTHOR_EMAIL="+gitEmail, - "GIT_COMMITTER_NAME="+gitName, - "GIT_COMMITTER_EMAIL="+gitEmail, - ) - out, err := cmd.CombinedOutput() - if err != nil { - t.Fatalf("git %v: %v\n%s", args, err, out) - } - return string(out) -} diff --git a/internal/codex/badline_internal_test.go b/internal/codex/badline_internal_test.go deleted file mode 100644 index c60e17a..0000000 --- a/internal/codex/badline_internal_test.go +++ /dev/null @@ -1,53 +0,0 @@ -package codex - -import ( - "io" - "strings" - "testing" - "time" -) - -func TestBadLines(t *testing.T) { - bad := []string{} - input := strings.Join([]string{ - `{"method":"turn/started","params":{"threadId":"t","turn":{"id":"u","items":[],"status":"inProgress"}}}`, - ``, - `warning: something on stdout`, - `{"method":"turn/started",`, - `{"method":"mystery/event","params":{}}`, - `{"method":"turn/completed","params":{"threadId":"t","turn":{"id":"u","items":[],"status":"completed"}}}`, - }, "\n") - - before := time.Now() - r, w := io.Pipe() - s := newSession(Options{OnBadLine: func(line []byte, reason string) { - bad = append(bad, string(line)+" | "+reason) - }}, w, strings.NewReader(input)) - - methods := []string{} - for ev := range s.events { - methods = append(methods, ev.Method) - } - r.Close() - - want := []string{ - `warning: something on stdout | not a JSON object`, - `{"method":"turn/started", | unexpected end of JSON input`, - `{"method":"mystery/event","params":{}} | unrecognised method "mystery/event"`, - } - if strings.Join(bad, "\n") != strings.Join(want, "\n") { - t.Errorf("bad lines:\n%s\nwant:\n%s", strings.Join(bad, "\n"), strings.Join(want, "\n")) - } - if strings.Join(methods, ",") != "turn/started,mystery/event,turn/completed" { - t.Errorf("events = %v", methods) - } - if s.TurnID() != "" { - t.Errorf("turn %q still tracked after turn/completed", s.TurnID()) - } - if s.LastLine().Before(before) { - t.Errorf("last line time %v not updated", s.LastLine()) - } - if !s.Exited() { - t.Errorf("session not exited after stdout closed") - } -} diff --git a/internal/codex/session_internal_test.go b/internal/codex/session_internal_test.go deleted file mode 100644 index fe6fdd2..0000000 --- a/internal/codex/session_internal_test.go +++ /dev/null @@ -1,751 +0,0 @@ -package codex - -import ( - "bufio" - "bytes" - "context" - "encoding/json" - "image" - "image/color" - "image/draw" - "image/png" - "io" - "os" - "os/exec" - "path/filepath" - "reflect" - "strings" - "sync" - "testing" - "time" - - "g.fc.run/misc/byre/internal/mcp" -) - -const ( - testModel = "gpt-5.6-luna" - switchModel = "gpt-5.6-terra" - gitName = "Byre Test" - gitEmail = "test@byre.invalid" -) - -type scenario struct { - name string - run func(h *harness) -} - -var scenarios = []scenario{ - {"CommitIdentity", scenarioCommitIdentity}, - {"TextDeltas", scenarioTextDeltas}, - {"Interrupt", scenarioInterrupt}, - {"ImageInput", scenarioImageInput}, - {"Resume", scenarioResume}, - {"ModelSwitch", scenarioModelSwitch}, - {"Compact", scenarioCompact}, - {"Tools", scenarioTools}, - {"Fork", scenarioFork}, - {"Steer", scenarioSteer}, -} - -func TestLive(t *testing.T) { - for _, sc := range scenarios { - t.Run(sc.name, func(t *testing.T) { - t.Parallel() - h := &harness{t: t, name: sc.name, live: true, ctx: testContext(t, 5*time.Minute), repo: scratchRepo(t)} - sc.run(h) - h.closeAll() - for _, path := range h.files { - checkTranscript(t, path) - } - }) - } -} - -func TestContract(t *testing.T) { - for _, sc := range scenarios { - t.Run(sc.name, func(t *testing.T) { - t.Parallel() - h := &harness{t: t, name: sc.name, ctx: testContext(t, 30*time.Second), repo: t.TempDir()} - sc.run(h) - h.closeAll() - for _, path := range h.files { - checkTranscript(t, path) - } - }) - } -} - -func scenarioCommitIdentity(h *harness) { - s := h.start("", Options{}) - h.send(s, `Create a file hello.txt containing exactly the word hi, then git add and git commit it with the message "add hello". Reply done when finished.`) - turn := h.waitTurn(s, nil) - if turn.Status != TurnStatusCompleted { - h.t.Fatalf("turn ended %s", turn.Status) - } - - if !h.live { - return - } - log := git(h.t, h.repo, "log", "-1", "--format=%an <%ae>%n%cn <%ce>%n%B") - want := gitName + " <" + gitEmail + ">\n" + gitName + " <" + gitEmail + ">\nadd hello" - if !strings.HasPrefix(log, want) { - h.t.Fatalf("unexpected commit:\n%s", log) - } - lower := strings.ToLower(log) - if strings.Contains(lower, "co-authored-by") || strings.Contains(lower, "codex") { - h.t.Fatalf("agent attribution in commit:\n%s", log) - } -} - -func scenarioTextDeltas(h *harness) { - s := h.start("", Options{}) - h.send(s, "Without using any tools, count from one to twenty in words, separated by spaces.") - - deltas := map[string][]string{} - h.waitTurn(s, func(ev Event) { - if ev.Method != MethodItemAgentMessageDelta { - return - } - d := decode[*AgentMessageDeltaNotification](h.t, ev) - deltas[d.ItemID] = append(deltas[d.ItemID], d.Delta) - }) - - msgs := h.messages[s] - if len(msgs) == 0 { - h.t.Fatalf("no agent message") - } - last := msgs[len(msgs)-1] - got := deltas[last.ID] - if len(got) < 2 { - h.t.Fatalf("expected several text deltas for %s, got %d", last.ID, len(got)) - } - if strings.Join(got, "") != last.Text { - h.t.Fatalf("deltas %q don't add up to the final text %q", got, last.Text) - } -} - -func scenarioInterrupt(h *harness) { - s := h.start("", Options{}) - h.send(s, "Run the shell command `ping -c 120 127.0.0.1` and wait for it to finish, then say done.") - - interruptedAt := time.Time{} - turn := h.waitTurn(s, func(ev Event) { - if !interruptedAt.IsZero() || ev.Method != MethodItemStarted { - return - } - item := decode[*ItemStartedNotification](h.t, ev).Item - if item.Type != ThreadItemTypeCommandExecution { - return - } - if h.live { - time.Sleep(2 * time.Second) - } - interruptedAt = time.Now() - err := s.Interrupt(h.ctx) - if err != nil { - h.t.Errorf("interrupt: %v", err) - } - }) - - if interruptedAt.IsZero() { - h.t.Fatalf("never saw a command to interrupt") - } - if turn.Status != TurnStatusInterrupted { - h.t.Fatalf("turn ended %s, want interrupted", turn.Status) - } - elapsed := time.Since(interruptedAt) - h.t.Logf("turn ended %s after interrupt", elapsed) - if elapsed > 30*time.Second { - h.t.Fatalf("interrupt took %s to end the turn", elapsed) - } - - h.send(s, "Reply with the single word ok.") - h.waitTurn(s, nil) - if !strings.Contains(strings.ToLower(h.lastText(s)), "ok") { - h.t.Fatalf("session unusable after interrupt; assistant said %q", h.lastText(s)) - } -} - -func scenarioImageInput(h *harness) { - s := h.start("", Options{}) - - img := image.NewRGBA(image.Rect(0, 0, 64, 64)) - draw.Draw(img, img.Bounds(), &image.Uniform{C: color.RGBA{R: 255, A: 255}}, image.Point{}, draw.Src) - buf := bytes.Buffer{} - err := png.Encode(&buf, img) - if err != nil { - h.t.Fatalf("encode png: %v", err) - } - - _, err = s.Send(h.ctx, "Without using any tools, what single colour fills the attached image? Reply with just the colour name.", Image{MediaType: "image/png", Data: buf.Bytes()}) - if err != nil { - h.t.Fatalf("send: %v", err) - } - h.waitTurn(s, nil) - - if !strings.Contains(strings.ToLower(h.lastText(s)), "red") { - h.t.Fatalf("model didn't see the image; assistant said %q", h.lastText(s)) - } -} - -func scenarioResume(h *harness) { - first := h.start("first", Options{}) - h.send(first, "Remember the codeword AZURE-17. Reply with the single word ok.") - h.waitTurn(first, nil) - id := first.ID() - h.close(first) - - if id == "" { - h.t.Fatalf("no thread id from first session") - } - - resumed := h.start("resumed", Options{Resume: id}) - h.send(resumed, "What was the codeword? Reply with just the codeword.") - h.waitTurn(resumed, nil) - - if !strings.Contains(h.lastText(resumed), "AZURE-17") { - h.t.Fatalf("resumed session lost context; assistant said %q", h.lastText(resumed)) - } - if resumed.ID() != id { - h.t.Fatalf("resume changed thread id: want %s, got %s", id, resumed.ID()) - } -} - -func scenarioModelSwitch(h *harness) { - s := h.start("", Options{}) - h.send(s, "Reply with the single word ok.") - h.waitTurn(s, nil) - - before := h.threadModel(s) - if before != testModel { - h.t.Fatalf("thread model %q before the switch, want %s", before, testModel) - } - - s.SetModel(switchModel) - h.send(s, "Reply with the single word ok.") - turn := h.waitTurn(s, nil) - if turn.Status != TurnStatusCompleted { - h.t.Fatalf("turn on %s ended %s: %+v", switchModel, turn.Status, turn.Error) - } - - after := h.threadModel(s) - if after != switchModel { - h.t.Fatalf("thread model %q after the switch, want %s", after, switchModel) - } -} - -func (h *harness) threadModel(s *Session) string { - resp := ThreadReadResponse{} - err := s.Call(h.ctx, MethodThreadRead, ThreadReadParams{ThreadID: s.ID()}, &resp) - if err != nil { - h.t.Fatalf("read thread: %v", err) - } - return resp.Thread.Model -} - -func scenarioCompact(h *harness) { - s := h.start("", Options{}) - h.send(s, "Remember the codeword CORAL-42. Reply with the single word ok.") - h.waitTurn(s, nil) - - err := s.Compact(h.ctx) - if err != nil { - h.t.Fatalf("compact: %v", err) - } - compacted := false - h.waitTurn(s, func(ev Event) { - if ev.Method == MethodThreadCompacted { - compacted = true - } - if ev.Method == MethodItemCompleted && decode[*ItemCompletedNotification](h.t, ev).Item.Type == ThreadItemTypeContextCompaction { - compacted = true - } - }) - if !compacted { - h.t.Fatalf("compaction turn ended without compacting") - } - - h.send(s, "What was the codeword? Reply with just the codeword.") - h.waitTurn(s, nil) - if !strings.Contains(h.lastText(s), "CORAL-42") { - h.t.Fatalf("compacted session lost context; assistant said %q", h.lastText(s)) - } -} - -func lookupServer(calls *[]string) *mcp.Server { - return &mcp.Server{Name: "byre", Version: "0.0.0", Tools: []mcp.Tool{{ - Name: "lookup", - Description: "Look up the current codeword for a topic.", - InputSchema: json.RawMessage(`{"type":"object","properties":{"topic":{"type":"string"}},"required":["topic"]}`), - Handler: func(ctx context.Context, args json.RawMessage) (string, error) { - *calls = append(*calls, string(args)) - return "The codeword is TEAL-9.", nil - }, - }}} -} - -func scenarioTools(h *harness) { - calls := []string{} - srv := lookupServer(&calls) - - first := h.start("first", Options{MCPServers: []*mcp.Server{srv}, Reminder: "This is a byre test session."}) - h.send(first, "Call byre's lookup tool with topic birds, then reply with just the codeword it gives.") - h.waitTurn(first, nil) - if len(calls) != 1 || !strings.Contains(h.lastText(first), "TEAL-9") { - h.t.Fatalf("tool calls %v; assistant said %q", calls, h.lastText(first)) - } - id := first.ID() - h.close(first) - - resumed := h.start("resumed", Options{Resume: id, MCPServers: []*mcp.Server{srv}}) - h.send(resumed, "Call byre's lookup tool again with topic fish, then reply with just the codeword it gives.") - h.waitTurn(resumed, nil) - if len(calls) != 2 || !strings.Contains(h.lastText(resumed), "TEAL-9") { - h.t.Fatalf("resumed thread lost the tool: calls %v; assistant said %q", calls, h.lastText(resumed)) - } -} - -func scenarioFork(h *harness) { - src := h.start("source", Options{}) - h.send(src, "Remember the codeword AZURE-17. Reply with the single word ok.") - keep := h.waitTurn(src, nil) - h.send(src, "The codeword is now BEIGE-3; forget the old one. Reply with the single word ok.") - h.waitTurn(src, nil) - id := src.ID() - h.close(src) - - fork := h.start("fork", Options{ForkFrom: id, ForkAt: keep.ID}) - if fork.ID() == "" || fork.ID() == id { - h.t.Fatalf("fork thread id %q, source %q", fork.ID(), id) - } - h.send(fork, "What is the codeword? Reply with just the codeword.") - h.waitTurn(fork, nil) - text := h.lastText(fork) - if !strings.Contains(text, "AZURE-17") || strings.Contains(text, "BEIGE-3") { - h.t.Fatalf("fork didn't stop after the first turn; assistant said %q", text) - } -} - -func scenarioSteer(h *harness) { - s := h.start("", Options{}) - h.send(s, "Run the shell command `sleep 5 && echo finished`, then reply with what it printed.") - - steered := false - h.waitTurn(s, func(ev Event) { - if steered || ev.Method != MethodItemStarted { - return - } - if decode[*ItemStartedNotification](h.t, ev).Item.Type != ThreadItemTypeCommandExecution { - return - } - steered = true - err := s.Steer(h.ctx, "Also end your reply with the word PELICAN.") - if err != nil { - h.t.Errorf("steer: %v", err) - } - }) - - if !steered { - h.t.Fatalf("never saw a command to steer during") - } - if !strings.Contains(h.lastText(s), "PELICAN") { - h.t.Fatalf("steer didn't reach the turn; assistant said %q", h.lastText(s)) - } -} - -type harness struct { - t *testing.T - name string - live bool - ctx context.Context - repo string - - files []string - sessions []*Session - closed map[*Session]bool - replays map[*Session]*replay - messages map[*Session][]ThreadItem -} - -func (h *harness) path(dir, suffix string) string { - name := h.name - if suffix != "" { - name += "-" + suffix - } - return filepath.Join("testdata", dir, name+".jsonl") -} - -func (h *harness) start(suffix string, opts Options) *Session { - opts.Cwd = h.repo - opts.Model = testModel - opts.GitName = gitName - opts.GitEmail = gitEmail - opts.OnBadLine = func(line []byte, reason string) { - h.t.Errorf("bad line (%s): %s", reason, line) - } - - if h.messages == nil { - h.messages = map[*Session][]ThreadItem{} - h.closed = map[*Session]bool{} - h.replays = map[*Session]*replay{} - } - - if h.live { - path := h.path("transcripts", suffix) - err := os.MkdirAll(filepath.Dir(path), 0o755) - if err != nil { - h.t.Fatalf("mkdir transcripts: %v", err) - } - f, err := os.Create(path) - if err != nil { - h.t.Fatalf("create transcript: %v", err) - } - h.t.Cleanup(func() { f.Close() }) - opts.Transcript = f - h.files = append(h.files, path) - - s, err := Start(h.ctx, opts) - if err != nil { - h.t.Fatalf("start: %v", err) - } - h.sessions = append(h.sessions, s) - return s - } - - path := h.path("contract", suffix) - h.files = append(h.files, path) - r := newReplay(h.t, path) - s := newSession(opts, r.clientStdin, r.clientStdout) - h.sessions = append(h.sessions, s) - h.replays[s] = r - err := s.open(h.ctx, opts) - if err != nil { - h.t.Fatalf("open: %v", err) - } - return s -} - -func (h *harness) close(s *Session) { - if h.closed[s] { - return - } - h.closed[s] = true - - if h.live { - err := s.Close() - if err != nil { - h.t.Logf("codex exit: %v", err) - } - return - } - - s.stdin.Close() - <-s.done - h.replays[s].wait() -} - -func (h *harness) closeAll() { - for _, s := range h.sessions { - h.close(s) - } -} - -func (h *harness) send(s *Session, text string) { - _, err := s.Send(h.ctx, text) - if err != nil { - h.t.Fatalf("send: %v", err) - } -} - -func (h *harness) waitTurn(s *Session, onEvent func(Event)) Turn { - for { - select { - case ev, ok := <-s.Events(): - if !ok { - h.t.Fatalf("codex exited before the turn completed") - } - if onEvent != nil { - onEvent(ev) - } - if ev.Method == MethodItemCompleted { - item := decode[*ItemCompletedNotification](h.t, ev).Item - if item.Type == ThreadItemTypeAgentMessage { - h.messages[s] = append(h.messages[s], item) - } - } - if ev.Method == MethodTurnCompleted { - turn := decode[*TurnCompletedNotification](h.t, ev).Turn - h.t.Logf("turn %s %s", turn.ID, turn.Status) - return turn - } - case <-h.ctx.Done(): - h.t.Fatalf("timed out waiting for the turn to complete") - } - } -} - -func (h *harness) lastText(s *Session) string { - msgs := h.messages[s] - if len(msgs) == 0 { - return "" - } - return msgs[len(msgs)-1].Text -} - -func decode[T any](t *testing.T, ev Event) T { - v, err := ev.Decode() - if err != nil { - t.Fatalf("decode %s: %v", ev.Method, err) - } - out, ok := v.(T) - if !ok { - t.Fatalf("decode %s: got %T", ev.Method, v) - } - return out -} - -type entry struct { - Dir string `json:"dir"` - Msg json.RawMessage `json:"msg"` -} - -func readTranscript(t *testing.T, path string) []entry { - b, err := os.ReadFile(path) - if err != nil { - t.Fatalf("read transcript: %v", err) - } - out := []entry{} - for _, line := range bytes.Split(bytes.TrimSpace(b), []byte("\n")) { - e := entry{} - err := json.Unmarshal(line, &e) - if err != nil { - t.Fatalf("parse transcript line %s: %v", line, err) - } - out = append(out, e) - } - return out -} - -func strict(raw json.RawMessage, v any) error { - d := json.NewDecoder(bytes.NewReader(raw)) - d.DisallowUnknownFields() - return d.Decode(v) -} - -func checkTranscript(t *testing.T, path string) { - methods := map[string]string{} - for i, e := range readTranscript(t, path) { - m := message{} - err := json.Unmarshal(e.Msg, &m) - if err != nil { - t.Fatalf("%s:%d: %v", path, i+1, err) - } - - if e.Dir == "send" { - if m.Method != "" && len(m.ID) > 0 { - methods[string(m.ID)] = m.Method - } - continue - } - - var ctor func() any - var raw json.RawMessage - label := m.Method - switch { - case m.Method == "": - label = methods[string(m.ID)] + " response" - c, ok := clientResults[methods[string(m.ID)]] - if !ok { - t.Errorf("%s:%d: response to unknown request %s", path, i+1, m.ID) - continue - } - if m.Error != nil { - continue - } - ctor, raw = c, m.Result - case len(m.ID) > 0: - c, ok := serverRequestParams[m.Method] - if !ok { - t.Errorf("%s:%d: unknown server request %s", path, i+1, m.Method) - continue - } - ctor, raw = c, m.Params - default: - c, ok := notificationParams[m.Method] - if !ok { - t.Errorf("%s:%d: unknown notification %s", path, i+1, m.Method) - continue - } - ctor, raw = c, m.Params - } - if ctor == nil { - continue - } - err = strict(raw, ctor()) - if err != nil { - t.Errorf("%s:%d: %s doesn't match the generated types: %v", path, i+1, label, err) - } - } -} - -type replay struct { - t *testing.T - entries []entry - - clientStdin io.WriteCloser - clientStdout io.Reader - serverIn *bufio.Reader - serverOut io.WriteCloser - - ids map[string]json.RawMessage - done chan struct{} - once sync.Once -} - -func newReplay(t *testing.T, path string) *replay { - inR, inW := io.Pipe() - outR, outW := io.Pipe() - r := &replay{ - t: t, - entries: readTranscript(t, path), - clientStdin: inW, - clientStdout: outR, - serverIn: bufio.NewReader(inR), - serverOut: outW, - ids: map[string]json.RawMessage{}, - done: make(chan struct{}), - } - go r.run() - return r -} - -func (r *replay) wait() { - <-r.done -} - -func (r *replay) run() { - defer close(r.done) - defer r.serverOut.Close() - - for i, e := range r.entries { - if e.Dir == "recv" { - _, err := r.serverOut.Write(append(r.rewrite(e.Msg), '\n')) - if err != nil { - r.t.Errorf("replay entry %d: client stopped reading: %v", i+1, err) - return - } - continue - } - - line, err := r.serverIn.ReadBytes('\n') - if err != nil { - r.t.Errorf("replay entry %d: client closed instead of sending %s", i+1, e.Msg) - return - } - r.match(i+1, e.Msg, bytes.TrimSpace(line)) - } - - extra, err := r.serverIn.ReadBytes('\n') - if len(bytes.TrimSpace(extra)) > 0 { - r.t.Errorf("client sent more than the transcript: %s", extra) - } - if err == nil { - r.t.Errorf("client kept stdin open after the transcript ended") - } -} - -func (r *replay) rewrite(raw json.RawMessage) json.RawMessage { - m := message{} - err := json.Unmarshal(raw, &m) - if err != nil || m.Method != "" || len(m.ID) == 0 { - return raw - } - id, ok := r.ids[string(m.ID)] - if !ok { - return raw - } - m.ID = id - b, err := json.Marshal(m) - if err != nil { - r.t.Errorf("rewrite response: %v", err) - return raw - } - return b -} - -func (r *replay) match(n int, want, got json.RawMessage) { - w, g := message{}, message{} - err := json.Unmarshal(want, &w) - if err != nil { - r.t.Errorf("replay entry %d: %v", n, err) - return - } - err = json.Unmarshal(got, &g) - if err != nil { - r.t.Errorf("replay entry %d: client sent %s: %v", n, got, err) - return - } - - if w.Method != "" && len(w.ID) > 0 { - r.ids[string(w.ID)] = g.ID - } else if !bytes.Equal(w.ID, g.ID) { - r.t.Errorf("replay entry %d: id %s, want %s", n, g.ID, w.ID) - } - - if w.Method != g.Method { - r.t.Errorf("replay entry %d: client sent %s, want %s", n, got, want) - return - } - if !sameJSON(w.Params, g.Params) || !sameJSON(w.Result, g.Result) { - r.t.Errorf("replay entry %d: client sent\n%s\nwant\n%s", n, got, want) - } -} - -func sameJSON(a, b json.RawMessage) bool { - if len(a) == 0 || len(b) == 0 { - return len(a) == len(b) - } - var x, y any - if json.Unmarshal(a, &x) != nil || json.Unmarshal(b, &y) != nil { - return false - } - return reflect.DeepEqual(dropCwd(x), dropCwd(y)) -} - -func dropCwd(v any) any { - m, ok := v.(map[string]any) - if !ok { - return v - } - delete(m, "cwd") - return m -} - -func testContext(t *testing.T, d time.Duration) context.Context { - ctx, cancel := context.WithTimeout(context.Background(), d) - t.Cleanup(cancel) - return ctx -} - -func scratchRepo(t *testing.T) string { - dir := t.TempDir() - git(t, dir, "init", "--initial-branch=main") - git(t, dir, "commit", "--allow-empty", "--message", "root") - return dir -} - -func git(t *testing.T, dir string, args ...string) string { - cmd := exec.Command("git", args...) - cmd.Dir = dir - cmd.Env = append(os.Environ(), - "GIT_AUTHOR_NAME="+gitName, - "GIT_AUTHOR_EMAIL="+gitEmail, - "GIT_COMMITTER_NAME="+gitName, - "GIT_COMMITTER_EMAIL="+gitEmail, - ) - out, err := cmd.CombinedOutput() - if err != nil { - t.Fatalf("git %v: %v\n%s", args, err, out) - } - return string(out) -} diff --git a/internal/codex/testdata/contract/CommitIdentity.jsonl b/internal/codex/testdata/contract/CommitIdentity.jsonl deleted file mode 100644 index abdc64e..0000000 --- a/internal/codex/testdata/contract/CommitIdentity.jsonl +++ /dev/null @@ -1,63 +0,0 @@ -{"dir":"send","msg":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true},"clientInfo":{"name":"byre","title":"byre","version":"0.0.0"}}}} -{"dir":"recv","msg":{"id":1,"result":{"userAgent":"byre/0.159.0 (Mac OS 27.0.1; arm64) unknown (byre; 0.0.0)","codexHome":"/Users/flamingcow/.codex","platformFamily":"unix","platformOs":"macos"}}} -{"dir":"send","msg":{"method":"initialized"}} -{"dir":"send","msg":{"id":2,"method":"thread/start","params":{"approvalPolicy":"never","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveCommitIdentity2876285837/001","model":"gpt-5.6-luna","sandbox":"danger-full-access"}}} -{"dir":"recv","msg":{"method":"remoteControl/status/changed","params":{"status":"disabled","serverName":"roc.tailcecab.ts.net","installationId":"7b5574fb-530f-4cdf-925d-13b538911a88","environmentId":null},"emittedAtMs":1792100422701}} -{"dir":"recv","msg":{"id":2,"result":{"thread":{"id":"01a14182-545f-7922-94bb-adb44d83394b","environments":[{"environmentId":"local","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveCommitIdentity2876285837/001","runtimeWorkspaceRoots":["/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveCommitIdentity2876285837/001"]}],"extra":null,"sessionId":"01a14182-545f-7922-94bb-adb44d83394b","forkedFromId":null,"parentThreadId":null,"preview":"","ephemeral":false,"section":null,"sectionEnteredAt":null,"projectId":null,"historyMode":"paginated","modelProvider":"openai","model":"gpt-5.6-luna","reasoningEffort":null,"createdAt":1792100422,"updatedAt":1792100422,"recencyAt":1792100422,"status":{"type":"idle"},"path":"/Users/flamingcow/.codex/sessions/2026/10/15/rollout-2026-10-15T14-40-22-01a14182-545f-7922-94bb-adb44d83394b.jsonl","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveCommitIdentity2876285837/001","cliVersion":"0.159.0","originator":"byre","source":"vscode","canAcceptDirectInput":true,"threadSource":null,"agentNickname":null,"agentRole":null,"gitInfo":null,"name":null,"daybreakEnabled":null,"turns":[]},"model":"gpt-5.6-luna","modelProvider":"openai","serviceTier":null,"disabledPluginIds":[],"cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveCommitIdentity2876285837/001","runtimeWorkspaceRoots":["/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveCommitIdentity2876285837/001"],"instructionSources":[],"approvalPolicy":"never","approvalsReviewer":"user","sandbox":{"type":"dangerFullAccess"},"activePermissionProfile":null,"reasoningEffort":null,"multiAgentMode":"explicitRequestOnly"}}} -{"dir":"recv","msg":{"method":"thread/started","params":{"thread":{"id":"01a14182-545f-7922-94bb-adb44d83394b","environments":[{"environmentId":"local","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveCommitIdentity2876285837/001","runtimeWorkspaceRoots":["/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveCommitIdentity2876285837/001"]}],"extra":null,"sessionId":"01a14182-545f-7922-94bb-adb44d83394b","forkedFromId":null,"parentThreadId":null,"preview":"","ephemeral":false,"section":null,"sectionEnteredAt":null,"projectId":null,"historyMode":"paginated","modelProvider":"openai","model":"gpt-5.6-luna","reasoningEffort":null,"createdAt":1792100422,"updatedAt":1792100422,"recencyAt":1792100422,"status":{"type":"idle"},"path":"/Users/flamingcow/.codex/sessions/2026/10/15/rollout-2026-10-15T14-40-22-01a14182-545f-7922-94bb-adb44d83394b.jsonl","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveCommitIdentity2876285837/001","cliVersion":"0.159.0","originator":"byre","source":"vscode","canAcceptDirectInput":true,"threadSource":null,"agentNickname":null,"agentRole":null,"gitInfo":null,"name":null,"daybreakEnabled":null,"turns":[]}},"emittedAtMs":1792100422830}} -{"dir":"send","msg":{"id":3,"method":"turn/start","params":{"input":[{"text":"Create a file hello.txt containing exactly the word hi, then git add and git commit it with the message \"add hello\". Reply done when finished.","type":"text"}],"threadId":"01a14182-545f-7922-94bb-adb44d83394b"}}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-545f-7922-94bb-adb44d83394b","name":"node_repl","status":"starting","error":null,"failureReason":null},"emittedAtMs":1792100422831}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-545f-7922-94bb-adb44d83394b","name":"codex_apps","status":"starting","error":null,"failureReason":null},"emittedAtMs":1792100422831}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-545f-7922-94bb-adb44d83394b","name":"cua_repl","status":"starting","error":null,"failureReason":null},"emittedAtMs":1792100422831}} -{"dir":"recv","msg":{"id":3,"result":{"turn":{"id":"01a14182-54b2-7240-a770-142cac5fcbf7","items":[],"itemsView":"notLoaded","status":"inProgress","error":null,"startedAt":null,"completedAt":null,"durationMs":null}}}} -{"dir":"recv","msg":{"method":"thread/status/changed","params":{"threadId":"01a14182-545f-7922-94bb-adb44d83394b","status":{"type":"active","activeFlags":[]}},"emittedAtMs":1792100422917}} -{"dir":"recv","msg":{"method":"turn/started","params":{"threadId":"01a14182-545f-7922-94bb-adb44d83394b","turn":{"id":"01a14182-54b2-7240-a770-142cac5fcbf7","items":[],"itemsView":"notLoaded","status":"inProgress","error":null,"startedAt":1792100422,"completedAt":null,"durationMs":null}},"emittedAtMs":1792100422917}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-545f-7922-94bb-adb44d83394b","name":"node_repl","status":"ready","error":null,"failureReason":null},"emittedAtMs":1792100423399}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-545f-7922-94bb-adb44d83394b","name":"cua_repl","status":"ready","error":null,"failureReason":null},"emittedAtMs":1792100423464}} -{"dir":"recv","msg":{"method":"account/updated","params":{"authMode":"chatgpt","planType":"prolite"},"emittedAtMs":1792100423748}} -{"dir":"recv","msg":{"method":"item/started","params":{"item":{"type":"userMessage","id":"01a14182-6e98-7151-9c11-eca5a3416ab1","clientId":null,"content":[{"type":"text","text":"Create a file hello.txt containing exactly the word hi, then git add and git commit it with the message \"add hello\". Reply done when finished.","text_elements":[]}]},"threadId":"01a14182-545f-7922-94bb-adb44d83394b","turnId":"01a14182-54b2-7240-a770-142cac5fcbf7","startedAtMs":1792100429465},"emittedAtMs":1792100429465}} -{"dir":"recv","msg":{"method":"item/completed","params":{"item":{"type":"userMessage","id":"01a14182-6e98-7151-9c11-eca5a3416ab1","clientId":null,"content":[{"type":"text","text":"Create a file hello.txt containing exactly the word hi, then git add and git commit it with the message \"add hello\". Reply done when finished.","text_elements":[]}]},"threadId":"01a14182-545f-7922-94bb-adb44d83394b","turnId":"01a14182-54b2-7240-a770-142cac5fcbf7","completedAtMs":1792100429465},"emittedAtMs":1792100429467}} -{"dir":"recv","msg":{"method":"item/started","params":{"item":{"type":"reasoning","id":"rs_055424a5b7cbe0b2016ad1484e8fe0819986bc8037aa64a699","summary":[],"content":[]},"threadId":"01a14182-545f-7922-94bb-adb44d83394b","turnId":"01a14182-54b2-7240-a770-142cac5fcbf7","startedAtMs":1792100430794},"emittedAtMs":1792100430794}} -{"dir":"recv","msg":{"method":"item/completed","params":{"item":{"type":"reasoning","id":"rs_055424a5b7cbe0b2016ad1484e8fe0819986bc8037aa64a699","summary":[],"content":[]},"threadId":"01a14182-545f-7922-94bb-adb44d83394b","turnId":"01a14182-54b2-7240-a770-142cac5fcbf7","completedAtMs":1792100430794},"emittedAtMs":1792100430795}} -{"dir":"recv","msg":{"method":"item/started","params":{"item":{"type":"agentMessage","id":"msg_055424a5b7cbe0b2016ad1484e9994819993c310725a7770d2","text":"","phase":"commentary","memoryCitation":null,"delivery":null,"questions":null},"threadId":"01a14182-545f-7922-94bb-adb44d83394b","turnId":"01a14182-54b2-7240-a770-142cac5fcbf7","startedAtMs":1792100430822},"emittedAtMs":1792100430822}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-545f-7922-94bb-adb44d83394b","turnId":"01a14182-54b2-7240-a770-142cac5fcbf7","itemId":"msg_055424a5b7cbe0b2016ad1484e9994819993c310725a7770d2","delta":"I"},"emittedAtMs":1792100430824}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-545f-7922-94bb-adb44d83394b","name":"codex_apps","status":"ready","error":null,"failureReason":null},"emittedAtMs":1792100430871}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-545f-7922-94bb-adb44d83394b","name":"node_repl","status":"ready","error":null,"failureReason":null},"emittedAtMs":1792100430871}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-545f-7922-94bb-adb44d83394b","name":"cua_repl","status":"ready","error":null,"failureReason":null},"emittedAtMs":1792100430871}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-545f-7922-94bb-adb44d83394b","turnId":"01a14182-54b2-7240-a770-142cac5fcbf7","itemId":"msg_055424a5b7cbe0b2016ad1484e9994819993c310725a7770d2","delta":"’ll"},"emittedAtMs":1792100430904}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-545f-7922-94bb-adb44d83394b","turnId":"01a14182-54b2-7240-a770-142cac5fcbf7","itemId":"msg_055424a5b7cbe0b2016ad1484e9994819993c310725a7770d2","delta":" create"},"emittedAtMs":1792100430904}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-545f-7922-94bb-adb44d83394b","turnId":"01a14182-54b2-7240-a770-142cac5fcbf7","itemId":"msg_055424a5b7cbe0b2016ad1484e9994819993c310725a7770d2","delta":" `"},"emittedAtMs":1792100430904}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-545f-7922-94bb-adb44d83394b","turnId":"01a14182-54b2-7240-a770-142cac5fcbf7","itemId":"msg_055424a5b7cbe0b2016ad1484e9994819993c310725a7770d2","delta":"hello"},"emittedAtMs":1792100430904}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-545f-7922-94bb-adb44d83394b","turnId":"01a14182-54b2-7240-a770-142cac5fcbf7","itemId":"msg_055424a5b7cbe0b2016ad1484e9994819993c310725a7770d2","delta":".txt"},"emittedAtMs":1792100430904}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-545f-7922-94bb-adb44d83394b","turnId":"01a14182-54b2-7240-a770-142cac5fcbf7","itemId":"msg_055424a5b7cbe0b2016ad1484e9994819993c310725a7770d2","delta":"`,"},"emittedAtMs":1792100430904}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-545f-7922-94bb-adb44d83394b","turnId":"01a14182-54b2-7240-a770-142cac5fcbf7","itemId":"msg_055424a5b7cbe0b2016ad1484e9994819993c310725a7770d2","delta":" stage"},"emittedAtMs":1792100430904}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-545f-7922-94bb-adb44d83394b","turnId":"01a14182-54b2-7240-a770-142cac5fcbf7","itemId":"msg_055424a5b7cbe0b2016ad1484e9994819993c310725a7770d2","delta":" it"},"emittedAtMs":1792100430904}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-545f-7922-94bb-adb44d83394b","turnId":"01a14182-54b2-7240-a770-142cac5fcbf7","itemId":"msg_055424a5b7cbe0b2016ad1484e9994819993c310725a7770d2","delta":","},"emittedAtMs":1792100430904}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-545f-7922-94bb-adb44d83394b","turnId":"01a14182-54b2-7240-a770-142cac5fcbf7","itemId":"msg_055424a5b7cbe0b2016ad1484e9994819993c310725a7770d2","delta":" and"},"emittedAtMs":1792100430904}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-545f-7922-94bb-adb44d83394b","turnId":"01a14182-54b2-7240-a770-142cac5fcbf7","itemId":"msg_055424a5b7cbe0b2016ad1484e9994819993c310725a7770d2","delta":" commit"},"emittedAtMs":1792100430904}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-545f-7922-94bb-adb44d83394b","turnId":"01a14182-54b2-7240-a770-142cac5fcbf7","itemId":"msg_055424a5b7cbe0b2016ad1484e9994819993c310725a7770d2","delta":" it"},"emittedAtMs":1792100430907}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-545f-7922-94bb-adb44d83394b","turnId":"01a14182-54b2-7240-a770-142cac5fcbf7","itemId":"msg_055424a5b7cbe0b2016ad1484e9994819993c310725a7770d2","delta":" as"},"emittedAtMs":1792100430907}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-545f-7922-94bb-adb44d83394b","turnId":"01a14182-54b2-7240-a770-142cac5fcbf7","itemId":"msg_055424a5b7cbe0b2016ad1484e9994819993c310725a7770d2","delta":" requested"},"emittedAtMs":1792100430907}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-545f-7922-94bb-adb44d83394b","turnId":"01a14182-54b2-7240-a770-142cac5fcbf7","itemId":"msg_055424a5b7cbe0b2016ad1484e9994819993c310725a7770d2","delta":"."},"emittedAtMs":1792100430907}} -{"dir":"recv","msg":{"method":"item/completed","params":{"item":{"type":"agentMessage","id":"msg_055424a5b7cbe0b2016ad1484e9994819993c310725a7770d2","text":"I’ll create `hello.txt`, stage it, and commit it as requested.","phase":"commentary","memoryCitation":null,"delivery":null,"questions":null},"threadId":"01a14182-545f-7922-94bb-adb44d83394b","turnId":"01a14182-54b2-7240-a770-142cac5fcbf7","completedAtMs":1792100430907},"emittedAtMs":1792100430908}} -{"dir":"recv","msg":{"method":"item/started","params":{"item":{"type":"fileChange","id":"exec-bf6a2407-3020-4c33-8938-9ec8599a2b8a","changes":[{"path":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveCommitIdentity2876285837/001/hello.txt","kind":{"type":"add"},"diff":"hi\n"}],"status":"inProgress"},"threadId":"01a14182-545f-7922-94bb-adb44d83394b","turnId":"01a14182-54b2-7240-a770-142cac5fcbf7","startedAtMs":1792100431187},"emittedAtMs":1792100431187}} -{"dir":"recv","msg":{"method":"item/completed","params":{"item":{"type":"fileChange","id":"exec-bf6a2407-3020-4c33-8938-9ec8599a2b8a","changes":[{"path":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveCommitIdentity2876285837/001/hello.txt","kind":{"type":"add"},"diff":"hi\n"}],"status":"completed"},"threadId":"01a14182-545f-7922-94bb-adb44d83394b","turnId":"01a14182-54b2-7240-a770-142cac5fcbf7","completedAtMs":1792100431188},"emittedAtMs":1792100431188}} -{"dir":"recv","msg":{"method":"turn/diff/updated","params":{"threadId":"01a14182-545f-7922-94bb-adb44d83394b","turnId":"01a14182-54b2-7240-a770-142cac5fcbf7","diff":"diff --git a/hello.txt b/hello.txt\nnew file mode 100644\nindex 0000000000000000000000000000000000000000..45b983be36b73c0788dc9cbcb76cbb80fc7bb057\n--- /dev/null\n+++ b/hello.txt\n@@ -0,0 +1 @@\n+hi\n"},"emittedAtMs":1792100431188}} -{"dir":"recv","msg":{"method":"thread/tokenUsage/updated","params":{"threadId":"01a14182-545f-7922-94bb-adb44d83394b","turnId":"01a14182-54b2-7240-a770-142cac5fcbf7","tokenUsage":{"total":{"totalTokens":13464,"inputTokens":13338,"cachedInputTokens":11008,"cacheWriteInputTokens":0,"outputTokens":126,"reasoningOutputTokens":30},"last":{"totalTokens":13464,"inputTokens":13338,"cachedInputTokens":11008,"cacheWriteInputTokens":0,"outputTokens":126,"reasoningOutputTokens":30},"modelContextWindow":258400}},"emittedAtMs":1792100431213}} -{"dir":"recv","msg":{"method":"account/rateLimits/updated","params":{"rateLimits":{"limitId":"codex","limitName":null,"normalModelSlug":null,"primary":{"usedPercent":0,"windowDurationMins":10080,"resetsAt":1792701205},"secondary":null,"credits":{"hasCredits":false,"unlimited":false,"balance":"0"},"individualLimit":null,"spendControlReached":null,"planType":"prolite","rateLimitReachedType":null}},"emittedAtMs":1792100431213}} -{"dir":"recv","msg":{"method":"turn/diff/updated","params":{"threadId":"01a14182-545f-7922-94bb-adb44d83394b","turnId":"01a14182-54b2-7240-a770-142cac5fcbf7","diff":"diff --git a/hello.txt b/hello.txt\nnew file mode 100644\nindex 0000000000000000000000000000000000000000..45b983be36b73c0788dc9cbcb76cbb80fc7bb057\n--- /dev/null\n+++ b/hello.txt\n@@ -0,0 +1 @@\n+hi\n"},"emittedAtMs":1792100431213}} -{"dir":"recv","msg":{"method":"item/started","params":{"item":{"type":"reasoning","id":"rs_055424a5b7cbe0b2016ad14850a2648199aec5d2cc6ff21790","summary":[],"content":[]},"threadId":"01a14182-545f-7922-94bb-adb44d83394b","turnId":"01a14182-54b2-7240-a770-142cac5fcbf7","startedAtMs":1792100432854},"emittedAtMs":1792100432855}} -{"dir":"recv","msg":{"method":"item/completed","params":{"item":{"type":"reasoning","id":"rs_055424a5b7cbe0b2016ad14850a2648199aec5d2cc6ff21790","summary":[],"content":[]},"threadId":"01a14182-545f-7922-94bb-adb44d83394b","turnId":"01a14182-54b2-7240-a770-142cac5fcbf7","completedAtMs":1792100432855},"emittedAtMs":1792100432856}} -{"dir":"recv","msg":{"method":"item/started","params":{"item":{"type":"commandExecution","id":"exec-f3c1e4c8-da3c-4f9d-a5b3-b6a280577112","pluginId":null,"scriptPath":null,"command":"/bin/zsh -lc 'git add hello.txt \u0026\u0026 git commit -m \"add hello\" \u0026\u0026 git status --short'","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveCommitIdentity2876285837/001","processId":"14819","source":"unifiedExecStartup","status":"inProgress","commandActions":[{"type":"unknown","command":"git add hello.txt \u0026\u0026 git commit -m \"add hello\" \u0026\u0026 git status --short"}],"aggregatedOutput":null,"exitCode":null,"durationMs":null},"threadId":"01a14182-545f-7922-94bb-adb44d83394b","turnId":"01a14182-54b2-7240-a770-142cac5fcbf7","startedAtMs":1792100433251},"emittedAtMs":1792100433252}} -{"dir":"recv","msg":{"method":"item/completed","params":{"item":{"type":"commandExecution","id":"exec-f3c1e4c8-da3c-4f9d-a5b3-b6a280577112","pluginId":null,"scriptPath":null,"command":"/bin/zsh -lc 'git add hello.txt \u0026\u0026 git commit -m \"add hello\" \u0026\u0026 git status --short'","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveCommitIdentity2876285837/001","processId":"14819","source":"unifiedExecStartup","status":"completed","commandActions":[{"type":"unknown","command":"git add hello.txt \u0026\u0026 git commit -m \"add hello\" \u0026\u0026 git status --short"}],"aggregatedOutput":"[main e47d667] add hello\n 1 file changed, 1 insertion(+)\n create mode 100644 hello.txt\n","exitCode":0,"durationMs":0},"threadId":"01a14182-545f-7922-94bb-adb44d83394b","turnId":"01a14182-54b2-7240-a770-142cac5fcbf7","completedAtMs":1792100433251},"emittedAtMs":1792100433252}} -{"dir":"recv","msg":{"method":"thread/tokenUsage/updated","params":{"threadId":"01a14182-545f-7922-94bb-adb44d83394b","turnId":"01a14182-54b2-7240-a770-142cac5fcbf7","tokenUsage":{"total":{"totalTokens":27080,"inputTokens":26826,"cachedInputTokens":24064,"cacheWriteInputTokens":0,"outputTokens":254,"reasoningOutputTokens":64},"last":{"totalTokens":13616,"inputTokens":13488,"cachedInputTokens":13056,"cacheWriteInputTokens":0,"outputTokens":128,"reasoningOutputTokens":34},"modelContextWindow":258400}},"emittedAtMs":1792100433254}} -{"dir":"recv","msg":{"method":"account/rateLimits/updated","params":{"rateLimits":{"limitId":"codex","limitName":null,"normalModelSlug":null,"primary":{"usedPercent":0,"windowDurationMins":10080,"resetsAt":1792701205},"secondary":null,"credits":{"hasCredits":false,"unlimited":false,"balance":"0"},"individualLimit":null,"spendControlReached":null,"planType":"prolite","rateLimitReachedType":null}},"emittedAtMs":1792100433254}} -{"dir":"recv","msg":{"method":"turn/diff/updated","params":{"threadId":"01a14182-545f-7922-94bb-adb44d83394b","turnId":"01a14182-54b2-7240-a770-142cac5fcbf7","diff":"diff --git a/hello.txt b/hello.txt\nnew file mode 100644\nindex 0000000000000000000000000000000000000000..45b983be36b73c0788dc9cbcb76cbb80fc7bb057\n--- /dev/null\n+++ b/hello.txt\n@@ -0,0 +1 @@\n+hi\n"},"emittedAtMs":1792100433254}} -{"dir":"recv","msg":{"method":"item/started","params":{"item":{"type":"agentMessage","id":"msg_055424a5b7cbe0b2016ad14851f1248199a6d0c5cde949d94b","text":"","phase":"final_answer","memoryCitation":null,"delivery":null,"questions":null},"threadId":"01a14182-545f-7922-94bb-adb44d83394b","turnId":"01a14182-54b2-7240-a770-142cac5fcbf7","startedAtMs":1792100434120},"emittedAtMs":1792100434120}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-545f-7922-94bb-adb44d83394b","turnId":"01a14182-54b2-7240-a770-142cac5fcbf7","itemId":"msg_055424a5b7cbe0b2016ad14851f1248199a6d0c5cde949d94b","delta":"done"},"emittedAtMs":1792100434120}} -{"dir":"recv","msg":{"method":"item/completed","params":{"item":{"type":"agentMessage","id":"msg_055424a5b7cbe0b2016ad14851f1248199a6d0c5cde949d94b","text":"done","phase":"final_answer","memoryCitation":null,"delivery":null,"questions":null},"threadId":"01a14182-545f-7922-94bb-adb44d83394b","turnId":"01a14182-54b2-7240-a770-142cac5fcbf7","completedAtMs":1792100434120},"emittedAtMs":1792100434120}} -{"dir":"recv","msg":{"method":"thread/tokenUsage/updated","params":{"threadId":"01a14182-545f-7922-94bb-adb44d83394b","turnId":"01a14182-54b2-7240-a770-142cac5fcbf7","tokenUsage":{"total":{"totalTokens":40752,"inputTokens":40493,"cachedInputTokens":35072,"cacheWriteInputTokens":0,"outputTokens":259,"reasoningOutputTokens":64},"last":{"totalTokens":13672,"inputTokens":13667,"cachedInputTokens":11008,"cacheWriteInputTokens":0,"outputTokens":5,"reasoningOutputTokens":0},"modelContextWindow":258400}},"emittedAtMs":1792100434147}} -{"dir":"recv","msg":{"method":"account/rateLimits/updated","params":{"rateLimits":{"limitId":"codex","limitName":null,"normalModelSlug":null,"primary":{"usedPercent":0,"windowDurationMins":10080,"resetsAt":1792701205},"secondary":null,"credits":{"hasCredits":false,"unlimited":false,"balance":"0"},"individualLimit":null,"spendControlReached":null,"planType":"prolite","rateLimitReachedType":null}},"emittedAtMs":1792100434147}} -{"dir":"recv","msg":{"method":"turn/diff/updated","params":{"threadId":"01a14182-545f-7922-94bb-adb44d83394b","turnId":"01a14182-54b2-7240-a770-142cac5fcbf7","diff":"diff --git a/hello.txt b/hello.txt\nnew file mode 100644\nindex 0000000000000000000000000000000000000000..45b983be36b73c0788dc9cbcb76cbb80fc7bb057\n--- /dev/null\n+++ b/hello.txt\n@@ -0,0 +1 @@\n+hi\n"},"emittedAtMs":1792100434147}} -{"dir":"recv","msg":{"method":"thread/status/changed","params":{"threadId":"01a14182-545f-7922-94bb-adb44d83394b","status":{"type":"idle"}},"emittedAtMs":1792100434149}} -{"dir":"recv","msg":{"method":"turn/completed","params":{"threadId":"01a14182-545f-7922-94bb-adb44d83394b","turn":{"id":"01a14182-54b2-7240-a770-142cac5fcbf7","items":[{"type":"agentMessage","id":"msg_055424a5b7cbe0b2016ad14851f1248199a6d0c5cde949d94b","text":"done","phase":"final_answer","memoryCitation":null,"delivery":null,"questions":null}],"itemsView":"summary","status":"completed","error":null,"startedAt":1792100422,"completedAt":1792100434,"durationMs":11311}},"emittedAtMs":1792100434149}} diff --git a/internal/codex/testdata/contract/Compact.jsonl b/internal/codex/testdata/contract/Compact.jsonl deleted file mode 100644 index e7579b5..0000000 --- a/internal/codex/testdata/contract/Compact.jsonl +++ /dev/null @@ -1,55 +0,0 @@ -{"dir":"send","msg":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true},"clientInfo":{"name":"byre","title":"byre","version":"0.0.0"}}}} -{"dir":"recv","msg":{"id":1,"result":{"userAgent":"byre/0.159.0 (Mac OS 27.0.1; arm64) unknown (byre; 0.0.0)","codexHome":"/Users/flamingcow/.codex","platformFamily":"unix","platformOs":"macos"}}} -{"dir":"recv","msg":{"method":"remoteControl/status/changed","params":{"status":"disabled","serverName":"roc.tailcecab.ts.net","installationId":"7b5574fb-530f-4cdf-925d-13b538911a88","environmentId":null},"emittedAtMs":1792100422692}} -{"dir":"send","msg":{"method":"initialized"}} -{"dir":"send","msg":{"id":2,"method":"thread/start","params":{"approvalPolicy":"never","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveCompact149288099/001","model":"gpt-5.6-luna","sandbox":"danger-full-access"}}} -{"dir":"recv","msg":{"id":2,"result":{"thread":{"id":"01a14182-5448-7821-9676-4c42f3b02b89","environments":[{"environmentId":"local","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveCompact149288099/001","runtimeWorkspaceRoots":["/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveCompact149288099/001"]}],"extra":null,"sessionId":"01a14182-5448-7821-9676-4c42f3b02b89","forkedFromId":null,"parentThreadId":null,"preview":"","ephemeral":false,"section":null,"sectionEnteredAt":null,"projectId":null,"historyMode":"paginated","modelProvider":"openai","model":"gpt-5.6-luna","reasoningEffort":null,"createdAt":1792100422,"updatedAt":1792100422,"recencyAt":1792100422,"status":{"type":"idle"},"path":"/Users/flamingcow/.codex/sessions/2026/10/15/rollout-2026-10-15T14-40-22-01a14182-5448-7821-9676-4c42f3b02b89.jsonl","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveCompact149288099/001","cliVersion":"0.159.0","originator":"byre","source":"vscode","canAcceptDirectInput":true,"threadSource":null,"agentNickname":null,"agentRole":null,"gitInfo":null,"name":null,"daybreakEnabled":null,"turns":[]},"model":"gpt-5.6-luna","modelProvider":"openai","serviceTier":null,"disabledPluginIds":[],"cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveCompact149288099/001","runtimeWorkspaceRoots":["/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveCompact149288099/001"],"instructionSources":[],"approvalPolicy":"never","approvalsReviewer":"user","sandbox":{"type":"dangerFullAccess"},"activePermissionProfile":null,"reasoningEffort":null,"multiAgentMode":"explicitRequestOnly"}}} -{"dir":"recv","msg":{"method":"thread/started","params":{"thread":{"id":"01a14182-5448-7821-9676-4c42f3b02b89","environments":[{"environmentId":"local","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveCompact149288099/001","runtimeWorkspaceRoots":["/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveCompact149288099/001"]}],"extra":null,"sessionId":"01a14182-5448-7821-9676-4c42f3b02b89","forkedFromId":null,"parentThreadId":null,"preview":"","ephemeral":false,"section":null,"sectionEnteredAt":null,"projectId":null,"historyMode":"paginated","modelProvider":"openai","model":"gpt-5.6-luna","reasoningEffort":null,"createdAt":1792100422,"updatedAt":1792100422,"recencyAt":1792100422,"status":{"type":"idle"},"path":"/Users/flamingcow/.codex/sessions/2026/10/15/rollout-2026-10-15T14-40-22-01a14182-5448-7821-9676-4c42f3b02b89.jsonl","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveCompact149288099/001","cliVersion":"0.159.0","originator":"byre","source":"vscode","canAcceptDirectInput":true,"threadSource":null,"agentNickname":null,"agentRole":null,"gitInfo":null,"name":null,"daybreakEnabled":null,"turns":[]}},"emittedAtMs":1792100422855}} -{"dir":"send","msg":{"id":3,"method":"turn/start","params":{"input":[{"text":"Remember the codeword CORAL-42. Reply with the single word ok.","type":"text"}],"threadId":"01a14182-5448-7821-9676-4c42f3b02b89"}}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-5448-7821-9676-4c42f3b02b89","name":"codex_apps","status":"starting","error":null,"failureReason":null},"emittedAtMs":1792100422855}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-5448-7821-9676-4c42f3b02b89","name":"cua_repl","status":"starting","error":null,"failureReason":null},"emittedAtMs":1792100422855}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-5448-7821-9676-4c42f3b02b89","name":"node_repl","status":"starting","error":null,"failureReason":null},"emittedAtMs":1792100422855}} -{"dir":"recv","msg":{"id":3,"result":{"turn":{"id":"01a14182-54cb-79a2-94c2-f66cd74d2688","items":[],"itemsView":"notLoaded","status":"inProgress","error":null,"startedAt":null,"completedAt":null,"durationMs":null}}}} -{"dir":"recv","msg":{"method":"thread/status/changed","params":{"threadId":"01a14182-5448-7821-9676-4c42f3b02b89","status":{"type":"active","activeFlags":[]}},"emittedAtMs":1792100422905}} -{"dir":"recv","msg":{"method":"turn/started","params":{"threadId":"01a14182-5448-7821-9676-4c42f3b02b89","turn":{"id":"01a14182-54cb-79a2-94c2-f66cd74d2688","items":[],"itemsView":"notLoaded","status":"inProgress","error":null,"startedAt":1792100422,"completedAt":null,"durationMs":null}},"emittedAtMs":1792100422905}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-5448-7821-9676-4c42f3b02b89","name":"node_repl","status":"ready","error":null,"failureReason":null},"emittedAtMs":1792100423404}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-5448-7821-9676-4c42f3b02b89","name":"cua_repl","status":"ready","error":null,"failureReason":null},"emittedAtMs":1792100423439}} -{"dir":"recv","msg":{"method":"account/updated","params":{"authMode":"chatgpt","planType":"prolite"},"emittedAtMs":1792100423484}} -{"dir":"recv","msg":{"method":"item/started","params":{"item":{"type":"userMessage","id":"01a14182-6e10-78c3-b226-89cf64557f97","clientId":null,"content":[{"type":"text","text":"Remember the codeword CORAL-42. Reply with the single word ok.","text_elements":[]}]},"threadId":"01a14182-5448-7821-9676-4c42f3b02b89","turnId":"01a14182-54cb-79a2-94c2-f66cd74d2688","startedAtMs":1792100429329},"emittedAtMs":1792100429329}} -{"dir":"recv","msg":{"method":"item/completed","params":{"item":{"type":"userMessage","id":"01a14182-6e10-78c3-b226-89cf64557f97","clientId":null,"content":[{"type":"text","text":"Remember the codeword CORAL-42. Reply with the single word ok.","text_elements":[]}]},"threadId":"01a14182-5448-7821-9676-4c42f3b02b89","turnId":"01a14182-54cb-79a2-94c2-f66cd74d2688","completedAtMs":1792100429329},"emittedAtMs":1792100429330}} -{"dir":"recv","msg":{"method":"item/started","params":{"item":{"type":"agentMessage","id":"msg_083782bc1e662fd5016ad1484e04dc81989b1d31a3e9d17ed0","text":"","phase":"final_answer","memoryCitation":null,"delivery":null,"questions":null},"threadId":"01a14182-5448-7821-9676-4c42f3b02b89","turnId":"01a14182-54cb-79a2-94c2-f66cd74d2688","startedAtMs":1792100430304},"emittedAtMs":1792100430304}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-5448-7821-9676-4c42f3b02b89","turnId":"01a14182-54cb-79a2-94c2-f66cd74d2688","itemId":"msg_083782bc1e662fd5016ad1484e04dc81989b1d31a3e9d17ed0","delta":"ok"},"emittedAtMs":1792100430306}} -{"dir":"recv","msg":{"method":"item/completed","params":{"item":{"type":"agentMessage","id":"msg_083782bc1e662fd5016ad1484e04dc81989b1d31a3e9d17ed0","text":"ok","phase":"final_answer","memoryCitation":null,"delivery":null,"questions":null},"threadId":"01a14182-5448-7821-9676-4c42f3b02b89","turnId":"01a14182-54cb-79a2-94c2-f66cd74d2688","completedAtMs":1792100430311},"emittedAtMs":1792100430312}} -{"dir":"recv","msg":{"method":"thread/tokenUsage/updated","params":{"threadId":"01a14182-5448-7821-9676-4c42f3b02b89","turnId":"01a14182-54cb-79a2-94c2-f66cd74d2688","tokenUsage":{"total":{"totalTokens":13325,"inputTokens":13320,"cachedInputTokens":13056,"cacheWriteInputTokens":0,"outputTokens":5,"reasoningOutputTokens":0},"last":{"totalTokens":13325,"inputTokens":13320,"cachedInputTokens":13056,"cacheWriteInputTokens":0,"outputTokens":5,"reasoningOutputTokens":0},"modelContextWindow":258400}},"emittedAtMs":1792100430460}} -{"dir":"recv","msg":{"method":"account/rateLimits/updated","params":{"rateLimits":{"limitId":"codex","limitName":null,"normalModelSlug":null,"primary":{"usedPercent":0,"windowDurationMins":10080,"resetsAt":1792701205},"secondary":null,"credits":{"hasCredits":false,"unlimited":false,"balance":"0"},"individualLimit":null,"spendControlReached":null,"planType":"prolite","rateLimitReachedType":null}},"emittedAtMs":1792100430460}} -{"dir":"recv","msg":{"method":"thread/status/changed","params":{"threadId":"01a14182-5448-7821-9676-4c42f3b02b89","status":{"type":"idle"}},"emittedAtMs":1792100430468}} -{"dir":"recv","msg":{"method":"turn/completed","params":{"threadId":"01a14182-5448-7821-9676-4c42f3b02b89","turn":{"id":"01a14182-54cb-79a2-94c2-f66cd74d2688","items":[{"type":"agentMessage","id":"msg_083782bc1e662fd5016ad1484e04dc81989b1d31a3e9d17ed0","text":"ok","phase":"final_answer","memoryCitation":null,"delivery":null,"questions":null}],"itemsView":"summary","status":"completed","error":null,"startedAt":1792100422,"completedAt":1792100430,"durationMs":7604}},"emittedAtMs":1792100430469}} -{"dir":"send","msg":{"id":4,"method":"thread/compact/start","params":{"threadId":"01a14182-5448-7821-9676-4c42f3b02b89"}}} -{"dir":"recv","msg":{"id":4,"result":{}}} -{"dir":"recv","msg":{"method":"thread/status/changed","params":{"threadId":"01a14182-5448-7821-9676-4c42f3b02b89","status":{"type":"active","activeFlags":[]}},"emittedAtMs":1792100430487}} -{"dir":"recv","msg":{"method":"turn/started","params":{"threadId":"01a14182-5448-7821-9676-4c42f3b02b89","turn":{"id":"01a14182-7285-7bb1-98c3-dd5fbe1e12a1","items":[],"itemsView":"notLoaded","status":"inProgress","error":null,"startedAt":1792100430,"completedAt":null,"durationMs":null}},"emittedAtMs":1792100430487}} -{"dir":"recv","msg":{"method":"item/started","params":{"item":{"type":"contextCompaction","id":"01a14182-7297-7b02-9282-f3ce4916b2e7"},"threadId":"01a14182-5448-7821-9676-4c42f3b02b89","turnId":"01a14182-7285-7bb1-98c3-dd5fbe1e12a1","startedAtMs":1792100430487},"emittedAtMs":1792100430488}} -{"dir":"recv","msg":{"method":"thread/tokenUsage/updated","params":{"threadId":"01a14182-5448-7821-9676-4c42f3b02b89","turnId":"01a14182-7285-7bb1-98c3-dd5fbe1e12a1","tokenUsage":{"total":{"totalTokens":13325,"inputTokens":13320,"cachedInputTokens":13056,"cacheWriteInputTokens":0,"outputTokens":5,"reasoningOutputTokens":0},"last":{"totalTokens":4726,"inputTokens":0,"cachedInputTokens":0,"cacheWriteInputTokens":0,"outputTokens":0,"reasoningOutputTokens":0},"modelContextWindow":258400}},"emittedAtMs":1792100431833}} -{"dir":"recv","msg":{"method":"account/rateLimits/updated","params":{"rateLimits":{"limitId":"codex","limitName":null,"normalModelSlug":null,"primary":{"usedPercent":0,"windowDurationMins":10080,"resetsAt":1792701205},"secondary":null,"credits":{"hasCredits":false,"unlimited":false,"balance":"0"},"individualLimit":null,"spendControlReached":null,"planType":"prolite","rateLimitReachedType":null}},"emittedAtMs":1792100431833}} -{"dir":"recv","msg":{"method":"item/completed","params":{"item":{"type":"contextCompaction","id":"01a14182-7297-7b02-9282-f3ce4916b2e7"},"threadId":"01a14182-5448-7821-9676-4c42f3b02b89","turnId":"01a14182-7285-7bb1-98c3-dd5fbe1e12a1","completedAtMs":1792100431833},"emittedAtMs":1792100431834}} -{"dir":"recv","msg":{"method":"thread/status/changed","params":{"threadId":"01a14182-5448-7821-9676-4c42f3b02b89","status":{"type":"idle"}},"emittedAtMs":1792100431835}} -{"dir":"recv","msg":{"method":"turn/completed","params":{"threadId":"01a14182-5448-7821-9676-4c42f3b02b89","turn":{"id":"01a14182-7285-7bb1-98c3-dd5fbe1e12a1","items":[],"itemsView":"notLoaded","status":"completed","error":null,"startedAt":1792100430,"completedAt":1792100431,"durationMs":1353}},"emittedAtMs":1792100431835}} -{"dir":"send","msg":{"id":5,"method":"turn/start","params":{"input":[{"text":"What was the codeword? Reply with just the codeword.","type":"text"}],"threadId":"01a14182-5448-7821-9676-4c42f3b02b89"}}} -{"dir":"recv","msg":{"id":5,"result":{"turn":{"id":"01a14182-77dc-7f02-9fcb-d5195cbf6f14","items":[],"itemsView":"notLoaded","status":"inProgress","error":null,"startedAt":null,"completedAt":null,"durationMs":null}}}} -{"dir":"recv","msg":{"method":"thread/status/changed","params":{"threadId":"01a14182-5448-7821-9676-4c42f3b02b89","status":{"type":"active","activeFlags":[]}},"emittedAtMs":1792100431842}} -{"dir":"recv","msg":{"method":"turn/started","params":{"threadId":"01a14182-5448-7821-9676-4c42f3b02b89","turn":{"id":"01a14182-77dc-7f02-9fcb-d5195cbf6f14","items":[],"itemsView":"notLoaded","status":"inProgress","error":null,"startedAt":1792100431,"completedAt":null,"durationMs":null}},"emittedAtMs":1792100431842}} -{"dir":"recv","msg":{"method":"item/started","params":{"item":{"type":"userMessage","id":"01a14182-77f6-73f1-9fb7-9c324c754b0b","clientId":null,"content":[{"type":"text","text":"What was the codeword? Reply with just the codeword.","text_elements":[]}]},"threadId":"01a14182-5448-7821-9676-4c42f3b02b89","turnId":"01a14182-77dc-7f02-9fcb-d5195cbf6f14","startedAtMs":1792100431863},"emittedAtMs":1792100431863}} -{"dir":"recv","msg":{"method":"item/completed","params":{"item":{"type":"userMessage","id":"01a14182-77f6-73f1-9fb7-9c324c754b0b","clientId":null,"content":[{"type":"text","text":"What was the codeword? Reply with just the codeword.","text_elements":[]}]},"threadId":"01a14182-5448-7821-9676-4c42f3b02b89","turnId":"01a14182-77dc-7f02-9fcb-d5195cbf6f14","completedAtMs":1792100431863},"emittedAtMs":1792100431863}} -{"dir":"recv","msg":{"method":"item/started","params":{"item":{"type":"agentMessage","id":"msg_083782bc1e662fd5016ad14850bebc8198a244a56138a23360","text":"","phase":"final_answer","memoryCitation":null,"delivery":null,"questions":null},"threadId":"01a14182-5448-7821-9676-4c42f3b02b89","turnId":"01a14182-77dc-7f02-9fcb-d5195cbf6f14","startedAtMs":1792100432978},"emittedAtMs":1792100432978}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-5448-7821-9676-4c42f3b02b89","turnId":"01a14182-77dc-7f02-9fcb-d5195cbf6f14","itemId":"msg_083782bc1e662fd5016ad14850bebc8198a244a56138a23360","delta":"COR"},"emittedAtMs":1792100432978}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-5448-7821-9676-4c42f3b02b89","turnId":"01a14182-77dc-7f02-9fcb-d5195cbf6f14","itemId":"msg_083782bc1e662fd5016ad14850bebc8198a244a56138a23360","delta":"AL"},"emittedAtMs":1792100432978}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-5448-7821-9676-4c42f3b02b89","turnId":"01a14182-77dc-7f02-9fcb-d5195cbf6f14","itemId":"msg_083782bc1e662fd5016ad14850bebc8198a244a56138a23360","delta":"-"},"emittedAtMs":1792100432978}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-5448-7821-9676-4c42f3b02b89","turnId":"01a14182-77dc-7f02-9fcb-d5195cbf6f14","itemId":"msg_083782bc1e662fd5016ad14850bebc8198a244a56138a23360","delta":"42"},"emittedAtMs":1792100432993}} -{"dir":"recv","msg":{"method":"item/completed","params":{"item":{"type":"agentMessage","id":"msg_083782bc1e662fd5016ad14850bebc8198a244a56138a23360","text":"CORAL-42","phase":"final_answer","memoryCitation":null,"delivery":null,"questions":null},"threadId":"01a14182-5448-7821-9676-4c42f3b02b89","turnId":"01a14182-77dc-7f02-9fcb-d5195cbf6f14","completedAtMs":1792100433001},"emittedAtMs":1792100433003}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-5448-7821-9676-4c42f3b02b89","name":"codex_apps","status":"ready","error":null,"failureReason":null},"emittedAtMs":1792100433013}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-5448-7821-9676-4c42f3b02b89","name":"cua_repl","status":"ready","error":null,"failureReason":null},"emittedAtMs":1792100433013}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-5448-7821-9676-4c42f3b02b89","name":"node_repl","status":"ready","error":null,"failureReason":null},"emittedAtMs":1792100433013}} -{"dir":"recv","msg":{"method":"thread/tokenUsage/updated","params":{"threadId":"01a14182-5448-7821-9676-4c42f3b02b89","turnId":"01a14182-77dc-7f02-9fcb-d5195cbf6f14","tokenUsage":{"total":{"totalTokens":28204,"inputTokens":28191,"cachedInputTokens":24064,"cacheWriteInputTokens":0,"outputTokens":13,"reasoningOutputTokens":0},"last":{"totalTokens":14879,"inputTokens":14871,"cachedInputTokens":11008,"cacheWriteInputTokens":0,"outputTokens":8,"reasoningOutputTokens":0},"modelContextWindow":258400}},"emittedAtMs":1792100433128}} -{"dir":"recv","msg":{"method":"account/rateLimits/updated","params":{"rateLimits":{"limitId":"codex","limitName":null,"normalModelSlug":null,"primary":{"usedPercent":0,"windowDurationMins":10080,"resetsAt":1792701205},"secondary":null,"credits":{"hasCredits":false,"unlimited":false,"balance":"0"},"individualLimit":null,"spendControlReached":null,"planType":"prolite","rateLimitReachedType":null}},"emittedAtMs":1792100433128}} -{"dir":"recv","msg":{"method":"thread/status/changed","params":{"threadId":"01a14182-5448-7821-9676-4c42f3b02b89","status":{"type":"idle"}},"emittedAtMs":1792100433132}} -{"dir":"recv","msg":{"method":"turn/completed","params":{"threadId":"01a14182-5448-7821-9676-4c42f3b02b89","turn":{"id":"01a14182-77dc-7f02-9fcb-d5195cbf6f14","items":[{"type":"agentMessage","id":"msg_083782bc1e662fd5016ad14850bebc8198a244a56138a23360","text":"CORAL-42","phase":"final_answer","memoryCitation":null,"delivery":null,"questions":null}],"itemsView":"summary","status":"completed","error":null,"startedAt":1792100431,"completedAt":1792100433,"durationMs":1291}},"emittedAtMs":1792100433132}} diff --git a/internal/codex/testdata/contract/Fork-fork.jsonl b/internal/codex/testdata/contract/Fork-fork.jsonl deleted file mode 100644 index eddf0a3..0000000 --- a/internal/codex/testdata/contract/Fork-fork.jsonl +++ /dev/null @@ -1,36 +0,0 @@ -{"dir":"send","msg":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true},"clientInfo":{"name":"byre","title":"byre","version":"0.0.0"}}}} -{"dir":"recv","msg":{"id":1,"result":{"userAgent":"byre/0.159.0 (Mac OS 27.0.1; arm64) unknown (byre; 0.0.0)","codexHome":"/Users/flamingcow/.codex","platformFamily":"unix","platformOs":"macos"}}} -{"dir":"recv","msg":{"method":"remoteControl/status/changed","params":{"status":"disabled","serverName":"roc.tailcecab.ts.net","installationId":"7b5574fb-530f-4cdf-925d-13b538911a88","environmentId":null},"emittedAtMs":1792100433203}} -{"dir":"send","msg":{"method":"initialized"}} -{"dir":"send","msg":{"id":2,"method":"thread/fork","params":{"approvalPolicy":"never","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveFork1116318145/001","lastTurnId":"01a14182-54e3-7781-8468-52c0301688a7","model":"gpt-5.6-luna","sandbox":"danger-full-access","threadId":"01a14182-545a-7722-a522-00377cd91a07"}}} -{"dir":"recv","msg":{"method":"deprecationNotice","params":{"summary":"Full-history hydration is deprecated for paginated threads; use `excludeTurns: true`, then page with `thread/turns/list` and `thread/items/list`.","details":null},"emittedAtMs":1792100433204}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-7d3a-75c1-a9b7-b4a6a53454a6","name":"codex_apps","status":"starting","error":null,"failureReason":null},"emittedAtMs":1792100433349}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-7d3a-75c1-a9b7-b4a6a53454a6","name":"cua_repl","status":"starting","error":null,"failureReason":null},"emittedAtMs":1792100433349}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-7d3a-75c1-a9b7-b4a6a53454a6","name":"node_repl","status":"starting","error":null,"failureReason":null},"emittedAtMs":1792100433349}} -{"dir":"recv","msg":{"id":2,"result":{"thread":{"id":"01a14182-7d3a-75c1-a9b7-b4a6a53454a6","environments":[{"environmentId":"local","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveFork1116318145/001","runtimeWorkspaceRoots":["/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveFork1116318145/001"]}],"extra":null,"sessionId":"01a14182-7d3a-75c1-a9b7-b4a6a53454a6","forkedFromId":"01a14182-545a-7722-a522-00377cd91a07","parentThreadId":null,"preview":"","ephemeral":false,"section":null,"sectionEnteredAt":null,"projectId":null,"historyMode":"paginated","modelProvider":"openai","model":"gpt-5.6-luna","reasoningEffort":null,"createdAt":1792100433,"updatedAt":1792100433,"recencyAt":1792100433,"status":{"type":"idle"},"path":"/Users/flamingcow/.codex/sessions/2026/10/15/rollout-2026-10-15T14-40-33-01a14182-7d3a-75c1-a9b7-b4a6a53454a6.jsonl","cwd":"/private/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveFork1116318145/001","cliVersion":"0.159.0","originator":"byre","source":"vscode","canAcceptDirectInput":true,"threadSource":null,"agentNickname":null,"agentRole":null,"gitInfo":{"sha":"545185b66b04bfe51916ae9840d0f16c639a65c9","branch":"main","originUrl":null},"name":null,"daybreakEnabled":null,"turns":[{"id":"01a14182-54e3-7781-8468-52c0301688a7","items":[{"type":"userMessage","id":"01a14182-6eef-7f01-8bb2-260b55f08f07","clientId":null,"content":[{"type":"text","text":"Remember the codeword AZURE-17. Reply with the single word ok.","text_elements":[]}]},{"type":"agentMessage","id":"msg_001f6538ff6fd358016ad1484f9e7c8199ab276fd261e1379e","text":"ok","phase":"final_answer","memoryCitation":null,"delivery":null,"questions":null}],"itemsView":"full","status":"completed","error":null,"startedAt":1792100422,"completedAt":1792100431,"durationMs":9055}]},"model":"gpt-5.6-luna","modelProvider":"openai","serviceTier":null,"disabledPluginIds":[],"cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveFork1116318145/001","runtimeWorkspaceRoots":["/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveFork1116318145/001"],"instructionSources":[],"approvalPolicy":"never","approvalsReviewer":"user","sandbox":{"type":"dangerFullAccess"},"activePermissionProfile":null,"reasoningEffort":null,"multiAgentMode":"explicitRequestOnly"}}} -{"dir":"recv","msg":{"method":"thread/tokenUsage/updated","params":{"threadId":"01a14182-7d3a-75c1-a9b7-b4a6a53454a6","turnId":"01a14182-54e3-7781-8468-52c0301688a7","tokenUsage":{"total":{"totalTokens":13327,"inputTokens":13322,"cachedInputTokens":13056,"cacheWriteInputTokens":0,"outputTokens":5,"reasoningOutputTokens":0},"last":{"totalTokens":13327,"inputTokens":13322,"cachedInputTokens":13056,"cacheWriteInputTokens":0,"outputTokens":5,"reasoningOutputTokens":0},"modelContextWindow":258400}},"emittedAtMs":1792100433350}} -{"dir":"send","msg":{"id":3,"method":"turn/start","params":{"input":[{"text":"What is the codeword? Reply with just the codeword.","type":"text"}],"threadId":"01a14182-7d3a-75c1-a9b7-b4a6a53454a6"}}} -{"dir":"recv","msg":{"method":"thread/started","params":{"thread":{"id":"01a14182-7d3a-75c1-a9b7-b4a6a53454a6","environments":[{"environmentId":"local","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveFork1116318145/001","runtimeWorkspaceRoots":["/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveFork1116318145/001"]}],"extra":null,"sessionId":"01a14182-7d3a-75c1-a9b7-b4a6a53454a6","forkedFromId":"01a14182-545a-7722-a522-00377cd91a07","parentThreadId":null,"preview":"","ephemeral":false,"section":null,"sectionEnteredAt":null,"projectId":null,"historyMode":"paginated","modelProvider":"openai","model":"gpt-5.6-luna","reasoningEffort":null,"createdAt":1792100433,"updatedAt":1792100433,"recencyAt":1792100433,"status":{"type":"idle"},"path":"/Users/flamingcow/.codex/sessions/2026/10/15/rollout-2026-10-15T14-40-33-01a14182-7d3a-75c1-a9b7-b4a6a53454a6.jsonl","cwd":"/private/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveFork1116318145/001","cliVersion":"0.159.0","originator":"byre","source":"vscode","canAcceptDirectInput":true,"threadSource":null,"agentNickname":null,"agentRole":null,"gitInfo":{"sha":"545185b66b04bfe51916ae9840d0f16c639a65c9","branch":"main","originUrl":null},"name":null,"daybreakEnabled":null,"turns":[]}},"emittedAtMs":1792100433350}} -{"dir":"recv","msg":{"id":3,"result":{"turn":{"id":"01a14182-7dc7-7fc0-ada9-c69e3df9d39e","items":[],"itemsView":"notLoaded","status":"inProgress","error":null,"startedAt":null,"completedAt":null,"durationMs":null}}}} -{"dir":"recv","msg":{"method":"thread/status/changed","params":{"threadId":"01a14182-7d3a-75c1-a9b7-b4a6a53454a6","status":{"type":"active","activeFlags":[]}},"emittedAtMs":1792100433355}} -{"dir":"recv","msg":{"method":"turn/started","params":{"threadId":"01a14182-7d3a-75c1-a9b7-b4a6a53454a6","turn":{"id":"01a14182-7dc7-7fc0-ada9-c69e3df9d39e","items":[],"itemsView":"notLoaded","status":"inProgress","error":null,"startedAt":1792100433,"completedAt":null,"durationMs":null}},"emittedAtMs":1792100433355}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-7d3a-75c1-a9b7-b4a6a53454a6","name":"node_repl","status":"ready","error":null,"failureReason":null},"emittedAtMs":1792100433427}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-7d3a-75c1-a9b7-b4a6a53454a6","name":"cua_repl","status":"ready","error":null,"failureReason":null},"emittedAtMs":1792100433461}} -{"dir":"recv","msg":{"method":"account/updated","params":{"authMode":"chatgpt","planType":"prolite"},"emittedAtMs":1792100433587}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-7d3a-75c1-a9b7-b4a6a53454a6","name":"codex_apps","status":"ready","error":null,"failureReason":null},"emittedAtMs":1792100435245}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-7d3a-75c1-a9b7-b4a6a53454a6","name":"node_repl","status":"ready","error":null,"failureReason":null},"emittedAtMs":1792100435245}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-7d3a-75c1-a9b7-b4a6a53454a6","name":"cua_repl","status":"ready","error":null,"failureReason":null},"emittedAtMs":1792100435245}} -{"dir":"recv","msg":{"method":"item/started","params":{"item":{"type":"userMessage","id":"01a14182-87b6-7653-a8e9-8f3d94c2a508","clientId":null,"content":[{"type":"text","text":"What is the codeword? Reply with just the codeword.","text_elements":[]}]},"threadId":"01a14182-7d3a-75c1-a9b7-b4a6a53454a6","turnId":"01a14182-7dc7-7fc0-ada9-c69e3df9d39e","startedAtMs":1792100435895},"emittedAtMs":1792100435895}} -{"dir":"recv","msg":{"method":"item/completed","params":{"item":{"type":"userMessage","id":"01a14182-87b6-7653-a8e9-8f3d94c2a508","clientId":null,"content":[{"type":"text","text":"What is the codeword? Reply with just the codeword.","text_elements":[]}]},"threadId":"01a14182-7d3a-75c1-a9b7-b4a6a53454a6","turnId":"01a14182-7dc7-7fc0-ada9-c69e3df9d39e","completedAtMs":1792100435895},"emittedAtMs":1792100435897}} -{"dir":"recv","msg":{"method":"item/started","params":{"item":{"type":"reasoning","id":"rs_06353a6662cdbc1f016ad148549bb4819881b828c3ebed949e","summary":[],"content":[]},"threadId":"01a14182-7d3a-75c1-a9b7-b4a6a53454a6","turnId":"01a14182-7dc7-7fc0-ada9-c69e3df9d39e","startedAtMs":1792100436820},"emittedAtMs":1792100436820}} -{"dir":"recv","msg":{"method":"item/completed","params":{"item":{"type":"reasoning","id":"rs_06353a6662cdbc1f016ad148549bb4819881b828c3ebed949e","summary":[],"content":[]},"threadId":"01a14182-7d3a-75c1-a9b7-b4a6a53454a6","turnId":"01a14182-7dc7-7fc0-ada9-c69e3df9d39e","completedAtMs":1792100436820},"emittedAtMs":1792100436822}} -{"dir":"recv","msg":{"method":"item/started","params":{"item":{"type":"agentMessage","id":"msg_06353a6662cdbc1f016ad14854ad8c81988c7ba2a22ded4173","text":"","phase":"final_answer","memoryCitation":null,"delivery":null,"questions":null},"threadId":"01a14182-7d3a-75c1-a9b7-b4a6a53454a6","turnId":"01a14182-7dc7-7fc0-ada9-c69e3df9d39e","startedAtMs":1792100436870},"emittedAtMs":1792100436870}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-7d3a-75c1-a9b7-b4a6a53454a6","turnId":"01a14182-7dc7-7fc0-ada9-c69e3df9d39e","itemId":"msg_06353a6662cdbc1f016ad14854ad8c81988c7ba2a22ded4173","delta":"AZ"},"emittedAtMs":1792100436870}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-7d3a-75c1-a9b7-b4a6a53454a6","turnId":"01a14182-7dc7-7fc0-ada9-c69e3df9d39e","itemId":"msg_06353a6662cdbc1f016ad14854ad8c81988c7ba2a22ded4173","delta":"URE"},"emittedAtMs":1792100436870}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-7d3a-75c1-a9b7-b4a6a53454a6","turnId":"01a14182-7dc7-7fc0-ada9-c69e3df9d39e","itemId":"msg_06353a6662cdbc1f016ad14854ad8c81988c7ba2a22ded4173","delta":"-"},"emittedAtMs":1792100436870}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-7d3a-75c1-a9b7-b4a6a53454a6","turnId":"01a14182-7dc7-7fc0-ada9-c69e3df9d39e","itemId":"msg_06353a6662cdbc1f016ad14854ad8c81988c7ba2a22ded4173","delta":"17"},"emittedAtMs":1792100436870}} -{"dir":"recv","msg":{"method":"item/completed","params":{"item":{"type":"agentMessage","id":"msg_06353a6662cdbc1f016ad14854ad8c81988c7ba2a22ded4173","text":"AZURE-17","phase":"final_answer","memoryCitation":null,"delivery":null,"questions":null},"threadId":"01a14182-7d3a-75c1-a9b7-b4a6a53454a6","turnId":"01a14182-7dc7-7fc0-ada9-c69e3df9d39e","completedAtMs":1792100436870},"emittedAtMs":1792100436872}} -{"dir":"recv","msg":{"method":"thread/tokenUsage/updated","params":{"threadId":"01a14182-7d3a-75c1-a9b7-b4a6a53454a6","turnId":"01a14182-7dc7-7fc0-ada9-c69e3df9d39e","tokenUsage":{"total":{"totalTokens":26693,"inputTokens":26668,"cachedInputTokens":24064,"cacheWriteInputTokens":0,"outputTokens":25,"reasoningOutputTokens":10},"last":{"totalTokens":13366,"inputTokens":13346,"cachedInputTokens":11008,"cacheWriteInputTokens":0,"outputTokens":20,"reasoningOutputTokens":10},"modelContextWindow":258400}},"emittedAtMs":1792100436895}} -{"dir":"recv","msg":{"method":"account/rateLimits/updated","params":{"rateLimits":{"limitId":"codex","limitName":null,"normalModelSlug":null,"primary":{"usedPercent":0,"windowDurationMins":10080,"resetsAt":1792701205},"secondary":null,"credits":{"hasCredits":false,"unlimited":false,"balance":"0"},"individualLimit":null,"spendControlReached":null,"planType":"prolite","rateLimitReachedType":null}},"emittedAtMs":1792100436895}} -{"dir":"recv","msg":{"method":"thread/status/changed","params":{"threadId":"01a14182-7d3a-75c1-a9b7-b4a6a53454a6","status":{"type":"idle"}},"emittedAtMs":1792100436901}} -{"dir":"recv","msg":{"method":"turn/completed","params":{"threadId":"01a14182-7d3a-75c1-a9b7-b4a6a53454a6","turn":{"id":"01a14182-7dc7-7fc0-ada9-c69e3df9d39e","items":[{"type":"agentMessage","id":"msg_06353a6662cdbc1f016ad14854ad8c81988c7ba2a22ded4173","text":"AZURE-17","phase":"final_answer","memoryCitation":null,"delivery":null,"questions":null}],"itemsView":"summary","status":"completed","error":null,"startedAt":1792100433,"completedAt":1792100436,"durationMs":3547}},"emittedAtMs":1792100436901}} diff --git a/internal/codex/testdata/contract/Fork-source.jsonl b/internal/codex/testdata/contract/Fork-source.jsonl deleted file mode 100644 index 2f601b1..0000000 --- a/internal/codex/testdata/contract/Fork-source.jsonl +++ /dev/null @@ -1,42 +0,0 @@ -{"dir":"send","msg":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true},"clientInfo":{"name":"byre","title":"byre","version":"0.0.0"}}}} -{"dir":"recv","msg":{"id":1,"result":{"userAgent":"byre/0.159.0 (Mac OS 27.0.1; arm64) unknown (byre; 0.0.0)","codexHome":"/Users/flamingcow/.codex","platformFamily":"unix","platformOs":"macos"}}} -{"dir":"send","msg":{"method":"initialized"}} -{"dir":"send","msg":{"id":2,"method":"thread/start","params":{"approvalPolicy":"never","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveFork1116318145/001","model":"gpt-5.6-luna","sandbox":"danger-full-access"}}} -{"dir":"recv","msg":{"method":"remoteControl/status/changed","params":{"status":"disabled","serverName":"roc.tailcecab.ts.net","installationId":"7b5574fb-530f-4cdf-925d-13b538911a88","environmentId":null},"emittedAtMs":1792100422703}} -{"dir":"recv","msg":{"id":2,"result":{"thread":{"id":"01a14182-545a-7722-a522-00377cd91a07","environments":[{"environmentId":"local","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveFork1116318145/001","runtimeWorkspaceRoots":["/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveFork1116318145/001"]}],"extra":null,"sessionId":"01a14182-545a-7722-a522-00377cd91a07","forkedFromId":null,"parentThreadId":null,"preview":"","ephemeral":false,"section":null,"sectionEnteredAt":null,"projectId":null,"historyMode":"paginated","modelProvider":"openai","model":"gpt-5.6-luna","reasoningEffort":null,"createdAt":1792100422,"updatedAt":1792100422,"recencyAt":1792100422,"status":{"type":"idle"},"path":"/Users/flamingcow/.codex/sessions/2026/10/15/rollout-2026-10-15T14-40-22-01a14182-545a-7722-a522-00377cd91a07.jsonl","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveFork1116318145/001","cliVersion":"0.159.0","originator":"byre","source":"vscode","canAcceptDirectInput":true,"threadSource":null,"agentNickname":null,"agentRole":null,"gitInfo":null,"name":null,"daybreakEnabled":null,"turns":[]},"model":"gpt-5.6-luna","modelProvider":"openai","serviceTier":null,"disabledPluginIds":[],"cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveFork1116318145/001","runtimeWorkspaceRoots":["/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveFork1116318145/001"],"instructionSources":[],"approvalPolicy":"never","approvalsReviewer":"user","sandbox":{"type":"dangerFullAccess"},"activePermissionProfile":null,"reasoningEffort":null,"multiAgentMode":"explicitRequestOnly"}}} -{"dir":"send","msg":{"id":3,"method":"turn/start","params":{"input":[{"text":"Remember the codeword AZURE-17. Reply with the single word ok.","type":"text"}],"threadId":"01a14182-545a-7722-a522-00377cd91a07"}}} -{"dir":"recv","msg":{"method":"thread/started","params":{"thread":{"id":"01a14182-545a-7722-a522-00377cd91a07","environments":[{"environmentId":"local","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveFork1116318145/001","runtimeWorkspaceRoots":["/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveFork1116318145/001"]}],"extra":null,"sessionId":"01a14182-545a-7722-a522-00377cd91a07","forkedFromId":null,"parentThreadId":null,"preview":"","ephemeral":false,"section":null,"sectionEnteredAt":null,"projectId":null,"historyMode":"paginated","modelProvider":"openai","model":"gpt-5.6-luna","reasoningEffort":null,"createdAt":1792100422,"updatedAt":1792100422,"recencyAt":1792100422,"status":{"type":"idle"},"path":"/Users/flamingcow/.codex/sessions/2026/10/15/rollout-2026-10-15T14-40-22-01a14182-545a-7722-a522-00377cd91a07.jsonl","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveFork1116318145/001","cliVersion":"0.159.0","originator":"byre","source":"vscode","canAcceptDirectInput":true,"threadSource":null,"agentNickname":null,"agentRole":null,"gitInfo":null,"name":null,"daybreakEnabled":null,"turns":[]}},"emittedAtMs":1792100422881}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-545a-7722-a522-00377cd91a07","name":"codex_apps","status":"starting","error":null,"failureReason":null},"emittedAtMs":1792100422881}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-545a-7722-a522-00377cd91a07","name":"cua_repl","status":"starting","error":null,"failureReason":null},"emittedAtMs":1792100422881}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-545a-7722-a522-00377cd91a07","name":"node_repl","status":"starting","error":null,"failureReason":null},"emittedAtMs":1792100422881}} -{"dir":"recv","msg":{"id":3,"result":{"turn":{"id":"01a14182-54e3-7781-8468-52c0301688a7","items":[],"itemsView":"notLoaded","status":"inProgress","error":null,"startedAt":null,"completedAt":null,"durationMs":null}}}} -{"dir":"recv","msg":{"method":"thread/status/changed","params":{"threadId":"01a14182-545a-7722-a522-00377cd91a07","status":{"type":"active","activeFlags":[]}},"emittedAtMs":1792100422942}} -{"dir":"recv","msg":{"method":"turn/started","params":{"threadId":"01a14182-545a-7722-a522-00377cd91a07","turn":{"id":"01a14182-54e3-7781-8468-52c0301688a7","items":[],"itemsView":"notLoaded","status":"inProgress","error":null,"startedAt":1792100422,"completedAt":null,"durationMs":null}},"emittedAtMs":1792100422942}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-545a-7722-a522-00377cd91a07","name":"node_repl","status":"ready","error":null,"failureReason":null},"emittedAtMs":1792100423405}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-545a-7722-a522-00377cd91a07","name":"cua_repl","status":"ready","error":null,"failureReason":null},"emittedAtMs":1792100423446}} -{"dir":"recv","msg":{"method":"account/updated","params":{"authMode":"chatgpt","planType":"prolite"},"emittedAtMs":1792100423951}} -{"dir":"recv","msg":{"method":"item/started","params":{"item":{"type":"userMessage","id":"01a14182-6eef-7f01-8bb2-260b55f08f07","clientId":null,"content":[{"type":"text","text":"Remember the codeword AZURE-17. Reply with the single word ok.","text_elements":[]}]},"threadId":"01a14182-545a-7722-a522-00377cd91a07","turnId":"01a14182-54e3-7781-8468-52c0301688a7","startedAtMs":1792100429552},"emittedAtMs":1792100429552}} -{"dir":"recv","msg":{"method":"item/completed","params":{"item":{"type":"userMessage","id":"01a14182-6eef-7f01-8bb2-260b55f08f07","clientId":null,"content":[{"type":"text","text":"Remember the codeword AZURE-17. Reply with the single word ok.","text_elements":[]}]},"threadId":"01a14182-545a-7722-a522-00377cd91a07","turnId":"01a14182-54e3-7781-8468-52c0301688a7","completedAtMs":1792100429552},"emittedAtMs":1792100429552}} -{"dir":"recv","msg":{"method":"item/started","params":{"item":{"type":"agentMessage","id":"msg_001f6538ff6fd358016ad1484f9e7c8199ab276fd261e1379e","text":"","phase":"final_answer","memoryCitation":null,"delivery":null,"questions":null},"threadId":"01a14182-545a-7722-a522-00377cd91a07","turnId":"01a14182-54e3-7781-8468-52c0301688a7","startedAtMs":1792100431794},"emittedAtMs":1792100431794}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-545a-7722-a522-00377cd91a07","turnId":"01a14182-54e3-7781-8468-52c0301688a7","itemId":"msg_001f6538ff6fd358016ad1484f9e7c8199ab276fd261e1379e","delta":"ok"},"emittedAtMs":1792100431800}} -{"dir":"recv","msg":{"method":"item/completed","params":{"item":{"type":"agentMessage","id":"msg_001f6538ff6fd358016ad1484f9e7c8199ab276fd261e1379e","text":"ok","phase":"final_answer","memoryCitation":null,"delivery":null,"questions":null},"threadId":"01a14182-545a-7722-a522-00377cd91a07","turnId":"01a14182-54e3-7781-8468-52c0301688a7","completedAtMs":1792100431803},"emittedAtMs":1792100431804}} -{"dir":"recv","msg":{"method":"thread/tokenUsage/updated","params":{"threadId":"01a14182-545a-7722-a522-00377cd91a07","turnId":"01a14182-54e3-7781-8468-52c0301688a7","tokenUsage":{"total":{"totalTokens":13327,"inputTokens":13322,"cachedInputTokens":13056,"cacheWriteInputTokens":0,"outputTokens":5,"reasoningOutputTokens":0},"last":{"totalTokens":13327,"inputTokens":13322,"cachedInputTokens":13056,"cacheWriteInputTokens":0,"outputTokens":5,"reasoningOutputTokens":0},"modelContextWindow":258400}},"emittedAtMs":1792100431941}} -{"dir":"recv","msg":{"method":"account/rateLimits/updated","params":{"rateLimits":{"limitId":"codex","limitName":null,"normalModelSlug":null,"primary":{"usedPercent":0,"windowDurationMins":10080,"resetsAt":1792701205},"secondary":null,"credits":{"hasCredits":false,"unlimited":false,"balance":"0"},"individualLimit":null,"spendControlReached":null,"planType":"prolite","rateLimitReachedType":null}},"emittedAtMs":1792100431941}} -{"dir":"recv","msg":{"method":"thread/status/changed","params":{"threadId":"01a14182-545a-7722-a522-00377cd91a07","status":{"type":"idle"}},"emittedAtMs":1792100431943}} -{"dir":"recv","msg":{"method":"turn/completed","params":{"threadId":"01a14182-545a-7722-a522-00377cd91a07","turn":{"id":"01a14182-54e3-7781-8468-52c0301688a7","items":[{"type":"agentMessage","id":"msg_001f6538ff6fd358016ad1484f9e7c8199ab276fd261e1379e","text":"ok","phase":"final_answer","memoryCitation":null,"delivery":null,"questions":null}],"itemsView":"summary","status":"completed","error":null,"startedAt":1792100422,"completedAt":1792100431,"durationMs":9055}},"emittedAtMs":1792100431943}} -{"dir":"send","msg":{"id":4,"method":"turn/start","params":{"input":[{"text":"The codeword is now BEIGE-3; forget the old one. Reply with the single word ok.","type":"text"}],"threadId":"01a14182-545a-7722-a522-00377cd91a07"}}} -{"dir":"recv","msg":{"id":4,"result":{"turn":{"id":"01a14182-7848-7953-94b6-65c7ae3cadd7","items":[],"itemsView":"notLoaded","status":"inProgress","error":null,"startedAt":null,"completedAt":null,"durationMs":null}}}} -{"dir":"recv","msg":{"method":"thread/status/changed","params":{"threadId":"01a14182-545a-7722-a522-00377cd91a07","status":{"type":"active","activeFlags":[]}},"emittedAtMs":1792100431953}} -{"dir":"recv","msg":{"method":"turn/started","params":{"threadId":"01a14182-545a-7722-a522-00377cd91a07","turn":{"id":"01a14182-7848-7953-94b6-65c7ae3cadd7","items":[],"itemsView":"notLoaded","status":"inProgress","error":null,"startedAt":1792100431,"completedAt":null,"durationMs":null}},"emittedAtMs":1792100431953}} -{"dir":"recv","msg":{"method":"item/started","params":{"item":{"type":"userMessage","id":"01a14182-7862-76a0-b369-1c0a304a1b36","clientId":null,"content":[{"type":"text","text":"The codeword is now BEIGE-3; forget the old one. Reply with the single word ok.","text_elements":[]}]},"threadId":"01a14182-545a-7722-a522-00377cd91a07","turnId":"01a14182-7848-7953-94b6-65c7ae3cadd7","startedAtMs":1792100431970},"emittedAtMs":1792100431970}} -{"dir":"recv","msg":{"method":"item/completed","params":{"item":{"type":"userMessage","id":"01a14182-7862-76a0-b369-1c0a304a1b36","clientId":null,"content":[{"type":"text","text":"The codeword is now BEIGE-3; forget the old one. Reply with the single word ok.","text_elements":[]}]},"threadId":"01a14182-545a-7722-a522-00377cd91a07","turnId":"01a14182-7848-7953-94b6-65c7ae3cadd7","completedAtMs":1792100431970},"emittedAtMs":1792100431971}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-545a-7722-a522-00377cd91a07","name":"cua_repl","status":"ready","error":null,"failureReason":null},"emittedAtMs":1792100432122}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-545a-7722-a522-00377cd91a07","name":"node_repl","status":"ready","error":null,"failureReason":null},"emittedAtMs":1792100432122}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-545a-7722-a522-00377cd91a07","name":"codex_apps","status":"ready","error":null,"failureReason":null},"emittedAtMs":1792100432122}} -{"dir":"recv","msg":{"method":"item/started","params":{"item":{"type":"agentMessage","id":"msg_001f6538ff6fd358016ad14850e6308199a2d6d1131c4641f0","text":"","phase":"final_answer","memoryCitation":null,"delivery":null,"questions":null},"threadId":"01a14182-545a-7722-a522-00377cd91a07","turnId":"01a14182-7848-7953-94b6-65c7ae3cadd7","startedAtMs":1792100433140},"emittedAtMs":1792100433140}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-545a-7722-a522-00377cd91a07","turnId":"01a14182-7848-7953-94b6-65c7ae3cadd7","itemId":"msg_001f6538ff6fd358016ad14850e6308199a2d6d1131c4641f0","delta":"ok"},"emittedAtMs":1792100433140}} -{"dir":"recv","msg":{"method":"item/completed","params":{"item":{"type":"agentMessage","id":"msg_001f6538ff6fd358016ad14850e6308199a2d6d1131c4641f0","text":"ok","phase":"final_answer","memoryCitation":null,"delivery":null,"questions":null},"threadId":"01a14182-545a-7722-a522-00377cd91a07","turnId":"01a14182-7848-7953-94b6-65c7ae3cadd7","completedAtMs":1792100433140},"emittedAtMs":1792100433141}} -{"dir":"recv","msg":{"method":"thread/tokenUsage/updated","params":{"threadId":"01a14182-545a-7722-a522-00377cd91a07","turnId":"01a14182-7848-7953-94b6-65c7ae3cadd7","tokenUsage":{"total":{"totalTokens":29287,"inputTokens":29277,"cachedInputTokens":26112,"cacheWriteInputTokens":0,"outputTokens":10,"reasoningOutputTokens":0},"last":{"totalTokens":15960,"inputTokens":15955,"cachedInputTokens":13056,"cacheWriteInputTokens":0,"outputTokens":5,"reasoningOutputTokens":0},"modelContextWindow":258400}},"emittedAtMs":1792100433166}} -{"dir":"recv","msg":{"method":"account/rateLimits/updated","params":{"rateLimits":{"limitId":"codex","limitName":null,"normalModelSlug":null,"primary":{"usedPercent":0,"windowDurationMins":10080,"resetsAt":1792701205},"secondary":null,"credits":{"hasCredits":false,"unlimited":false,"balance":"0"},"individualLimit":null,"spendControlReached":null,"planType":"prolite","rateLimitReachedType":null}},"emittedAtMs":1792100433166}} -{"dir":"recv","msg":{"method":"thread/status/changed","params":{"threadId":"01a14182-545a-7722-a522-00377cd91a07","status":{"type":"idle"}},"emittedAtMs":1792100433168}} -{"dir":"recv","msg":{"method":"turn/completed","params":{"threadId":"01a14182-545a-7722-a522-00377cd91a07","turn":{"id":"01a14182-7848-7953-94b6-65c7ae3cadd7","items":[{"type":"agentMessage","id":"msg_001f6538ff6fd358016ad14850e6308199a2d6d1131c4641f0","text":"ok","phase":"final_answer","memoryCitation":null,"delivery":null,"questions":null}],"itemsView":"summary","status":"completed","error":null,"startedAt":1792100431,"completedAt":1792100433,"durationMs":1217}},"emittedAtMs":1792100433168}} diff --git a/internal/codex/testdata/contract/ImageInput.jsonl b/internal/codex/testdata/contract/ImageInput.jsonl deleted file mode 100644 index d207690..0000000 --- a/internal/codex/testdata/contract/ImageInput.jsonl +++ /dev/null @@ -1,26 +0,0 @@ -{"dir":"send","msg":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true},"clientInfo":{"name":"byre","title":"byre","version":"0.0.0"}}}} -{"dir":"recv","msg":{"id":1,"result":{"userAgent":"byre/0.159.0 (Mac OS 27.0.1; arm64) unknown (byre; 0.0.0)","codexHome":"/Users/flamingcow/.codex","platformFamily":"unix","platformOs":"macos"}}} -{"dir":"recv","msg":{"method":"remoteControl/status/changed","params":{"status":"disabled","serverName":"roc.tailcecab.ts.net","installationId":"7b5574fb-530f-4cdf-925d-13b538911a88","environmentId":null},"emittedAtMs":1792100422676}} -{"dir":"send","msg":{"method":"initialized"}} -{"dir":"send","msg":{"id":2,"method":"thread/start","params":{"approvalPolicy":"never","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveImageInput3939487208/001","model":"gpt-5.6-luna","sandbox":"danger-full-access"}}} -{"dir":"recv","msg":{"id":2,"result":{"thread":{"id":"01a14182-541d-7ff2-830c-1ae06bd7ef15","environments":[{"environmentId":"local","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveImageInput3939487208/001","runtimeWorkspaceRoots":["/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveImageInput3939487208/001"]}],"extra":null,"sessionId":"01a14182-541d-7ff2-830c-1ae06bd7ef15","forkedFromId":null,"parentThreadId":null,"preview":"","ephemeral":false,"section":null,"sectionEnteredAt":null,"projectId":null,"historyMode":"paginated","modelProvider":"openai","model":"gpt-5.6-luna","reasoningEffort":null,"createdAt":1792100422,"updatedAt":1792100422,"recencyAt":1792100422,"status":{"type":"idle"},"path":"/Users/flamingcow/.codex/sessions/2026/10/15/rollout-2026-10-15T14-40-22-01a14182-541d-7ff2-830c-1ae06bd7ef15.jsonl","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveImageInput3939487208/001","cliVersion":"0.159.0","originator":"byre","source":"vscode","canAcceptDirectInput":true,"threadSource":null,"agentNickname":null,"agentRole":null,"gitInfo":null,"name":null,"daybreakEnabled":null,"turns":[]},"model":"gpt-5.6-luna","modelProvider":"openai","serviceTier":null,"disabledPluginIds":[],"cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveImageInput3939487208/001","runtimeWorkspaceRoots":["/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveImageInput3939487208/001"],"instructionSources":[],"approvalPolicy":"never","approvalsReviewer":"user","sandbox":{"type":"dangerFullAccess"},"activePermissionProfile":null,"reasoningEffort":null,"multiAgentMode":"explicitRequestOnly"}}} -{"dir":"recv","msg":{"method":"thread/started","params":{"thread":{"id":"01a14182-541d-7ff2-830c-1ae06bd7ef15","environments":[{"environmentId":"local","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveImageInput3939487208/001","runtimeWorkspaceRoots":["/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveImageInput3939487208/001"]}],"extra":null,"sessionId":"01a14182-541d-7ff2-830c-1ae06bd7ef15","forkedFromId":null,"parentThreadId":null,"preview":"","ephemeral":false,"section":null,"sectionEnteredAt":null,"projectId":null,"historyMode":"paginated","modelProvider":"openai","model":"gpt-5.6-luna","reasoningEffort":null,"createdAt":1792100422,"updatedAt":1792100422,"recencyAt":1792100422,"status":{"type":"idle"},"path":"/Users/flamingcow/.codex/sessions/2026/10/15/rollout-2026-10-15T14-40-22-01a14182-541d-7ff2-830c-1ae06bd7ef15.jsonl","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveImageInput3939487208/001","cliVersion":"0.159.0","originator":"byre","source":"vscode","canAcceptDirectInput":true,"threadSource":null,"agentNickname":null,"agentRole":null,"gitInfo":null,"name":null,"daybreakEnabled":null,"turns":[]}},"emittedAtMs":1792100422791}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-541d-7ff2-830c-1ae06bd7ef15","name":"node_repl","status":"starting","error":null,"failureReason":null},"emittedAtMs":1792100422791}} -{"dir":"send","msg":{"id":3,"method":"turn/start","params":{"input":[{"type":"image","url":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAIAAAAlC+aJAAAAZElEQVR4nOzPUQkAIBTAQBH7V9YQfhwPdgm2c9dsWwf8akBrQGtAa0BrQGtAa0BrQGtAa0BrQGtAa0BrQGtAa0BrQGtAa0BrQGtAa0BrQGtAa0BrQGtAa0BrQGtAa0B7AQAA///jRQGCmaQfxwAAAABJRU5ErkJggg=="},{"text":"Without using any tools, what single colour fills the attached image? Reply with just the colour name.","type":"text"}],"threadId":"01a14182-541d-7ff2-830c-1ae06bd7ef15"}}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-541d-7ff2-830c-1ae06bd7ef15","name":"cua_repl","status":"starting","error":null,"failureReason":null},"emittedAtMs":1792100422791}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-541d-7ff2-830c-1ae06bd7ef15","name":"codex_apps","status":"starting","error":null,"failureReason":null},"emittedAtMs":1792100422791}} -{"dir":"recv","msg":{"id":3,"result":{"turn":{"id":"01a14182-5489-7742-888f-26a5379fde18","items":[],"itemsView":"notLoaded","status":"inProgress","error":null,"startedAt":null,"completedAt":null,"durationMs":null}}}} -{"dir":"recv","msg":{"method":"thread/status/changed","params":{"threadId":"01a14182-541d-7ff2-830c-1ae06bd7ef15","status":{"type":"active","activeFlags":[]}},"emittedAtMs":1792100422895}} -{"dir":"recv","msg":{"method":"turn/started","params":{"threadId":"01a14182-541d-7ff2-830c-1ae06bd7ef15","turn":{"id":"01a14182-5489-7742-888f-26a5379fde18","items":[],"itemsView":"notLoaded","status":"inProgress","error":null,"startedAt":1792100422,"completedAt":null,"durationMs":null}},"emittedAtMs":1792100422895}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-541d-7ff2-830c-1ae06bd7ef15","name":"node_repl","status":"ready","error":null,"failureReason":null},"emittedAtMs":1792100423258}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-541d-7ff2-830c-1ae06bd7ef15","name":"cua_repl","status":"ready","error":null,"failureReason":null},"emittedAtMs":1792100423462}} -{"dir":"recv","msg":{"method":"account/updated","params":{"authMode":"chatgpt","planType":"prolite"},"emittedAtMs":1792100423591}} -{"dir":"recv","msg":{"method":"item/started","params":{"item":{"type":"userMessage","id":"01a14182-6e24-7233-99bd-3e63e615a7d2","clientId":null,"content":[{"type":"image","url":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAIAAAAlC+aJAAAAZElEQVR4nOzPUQkAIBTAQBH7V9YQfhwPdgm2c9dsWwf8akBrQGtAa0BrQGtAa0BrQGtAa0BrQGtAa0BrQGtAa0BrQGtAa0BrQGtAa0BrQGtAa0BrQGtAa0BrQGtAa0B7AQAA///jRQGCmaQfxwAAAABJRU5ErkJggg==","detail":null},{"type":"text","text":"Without using any tools, what single colour fills the attached image? Reply with just the colour name.","text_elements":[]}]},"threadId":"01a14182-541d-7ff2-830c-1ae06bd7ef15","turnId":"01a14182-5489-7742-888f-26a5379fde18","startedAtMs":1792100429348},"emittedAtMs":1792100429348}} -{"dir":"recv","msg":{"method":"item/completed","params":{"item":{"type":"userMessage","id":"01a14182-6e24-7233-99bd-3e63e615a7d2","clientId":null,"content":[{"type":"image","url":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAIAAAAlC+aJAAAAZElEQVR4nOzPUQkAIBTAQBH7V9YQfhwPdgm2c9dsWwf8akBrQGtAa0BrQGtAa0BrQGtAa0BrQGtAa0BrQGtAa0BrQGtAa0BrQGtAa0BrQGtAa0BrQGtAa0BrQGtAa0B7AQAA///jRQGCmaQfxwAAAABJRU5ErkJggg==","detail":null},{"type":"text","text":"Without using any tools, what single colour fills the attached image? Reply with just the colour name.","text_elements":[]}]},"threadId":"01a14182-541d-7ff2-830c-1ae06bd7ef15","turnId":"01a14182-5489-7742-888f-26a5379fde18","completedAtMs":1792100429348},"emittedAtMs":1792100429349}} -{"dir":"recv","msg":{"method":"item/started","params":{"item":{"type":"agentMessage","id":"msg_0de22c0a3bf172d2016ad1484e0b5c819b8ff539257c80b31b","text":"","phase":"final_answer","memoryCitation":null,"delivery":null,"questions":null},"threadId":"01a14182-541d-7ff2-830c-1ae06bd7ef15","turnId":"01a14182-5489-7742-888f-26a5379fde18","startedAtMs":1792100430328},"emittedAtMs":1792100430328}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-541d-7ff2-830c-1ae06bd7ef15","turnId":"01a14182-5489-7742-888f-26a5379fde18","itemId":"msg_0de22c0a3bf172d2016ad1484e0b5c819b8ff539257c80b31b","delta":"Red"},"emittedAtMs":1792100430334}} -{"dir":"recv","msg":{"method":"item/completed","params":{"item":{"type":"agentMessage","id":"msg_0de22c0a3bf172d2016ad1484e0b5c819b8ff539257c80b31b","text":"Red","phase":"final_answer","memoryCitation":null,"delivery":null,"questions":null},"threadId":"01a14182-541d-7ff2-830c-1ae06bd7ef15","turnId":"01a14182-5489-7742-888f-26a5379fde18","completedAtMs":1792100430334},"emittedAtMs":1792100430335}} -{"dir":"recv","msg":{"method":"thread/tokenUsage/updated","params":{"threadId":"01a14182-541d-7ff2-830c-1ae06bd7ef15","turnId":"01a14182-5489-7742-888f-26a5379fde18","tokenUsage":{"total":{"totalTokens":13338,"inputTokens":13333,"cachedInputTokens":13056,"cacheWriteInputTokens":0,"outputTokens":5,"reasoningOutputTokens":0},"last":{"totalTokens":13338,"inputTokens":13333,"cachedInputTokens":13056,"cacheWriteInputTokens":0,"outputTokens":5,"reasoningOutputTokens":0},"modelContextWindow":258400}},"emittedAtMs":1792100430441}} -{"dir":"recv","msg":{"method":"account/rateLimits/updated","params":{"rateLimits":{"limitId":"codex","limitName":null,"normalModelSlug":null,"primary":{"usedPercent":0,"windowDurationMins":10080,"resetsAt":1792701205},"secondary":null,"credits":{"hasCredits":false,"unlimited":false,"balance":"0"},"individualLimit":null,"spendControlReached":null,"planType":"prolite","rateLimitReachedType":null}},"emittedAtMs":1792100430441}} -{"dir":"recv","msg":{"method":"thread/status/changed","params":{"threadId":"01a14182-541d-7ff2-830c-1ae06bd7ef15","status":{"type":"idle"}},"emittedAtMs":1792100430447}} -{"dir":"recv","msg":{"method":"turn/completed","params":{"threadId":"01a14182-541d-7ff2-830c-1ae06bd7ef15","turn":{"id":"01a14182-5489-7742-888f-26a5379fde18","items":[{"type":"agentMessage","id":"msg_0de22c0a3bf172d2016ad1484e0b5c819b8ff539257c80b31b","text":"Red","phase":"final_answer","memoryCitation":null,"delivery":null,"questions":null}],"itemsView":"summary","status":"completed","error":null,"startedAt":1792100422,"completedAt":1792100430,"durationMs":7648}},"emittedAtMs":1792100430447}} diff --git a/internal/codex/testdata/contract/Interrupt.jsonl b/internal/codex/testdata/contract/Interrupt.jsonl deleted file mode 100644 index 10b1e85..0000000 --- a/internal/codex/testdata/contract/Interrupt.jsonl +++ /dev/null @@ -1,61 +0,0 @@ -{"dir":"send","msg":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true},"clientInfo":{"name":"byre","title":"byre","version":"0.0.0"}}}} -{"dir":"recv","msg":{"id":1,"result":{"userAgent":"byre/0.159.0 (Mac OS 27.0.1; arm64) unknown (byre; 0.0.0)","codexHome":"/Users/flamingcow/.codex","platformFamily":"unix","platformOs":"macos"}}} -{"dir":"recv","msg":{"method":"remoteControl/status/changed","params":{"status":"disabled","serverName":"roc.tailcecab.ts.net","installationId":"7b5574fb-530f-4cdf-925d-13b538911a88","environmentId":null},"emittedAtMs":1792100422688}} -{"dir":"send","msg":{"method":"initialized"}} -{"dir":"send","msg":{"id":2,"method":"thread/start","params":{"approvalPolicy":"never","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveInterrupt2023918540/001","model":"gpt-5.6-luna","sandbox":"danger-full-access"}}} -{"dir":"recv","msg":{"id":2,"result":{"thread":{"id":"01a14182-543d-73c0-8796-58d1573fa855","environments":[{"environmentId":"local","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveInterrupt2023918540/001","runtimeWorkspaceRoots":["/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveInterrupt2023918540/001"]}],"extra":null,"sessionId":"01a14182-543d-73c0-8796-58d1573fa855","forkedFromId":null,"parentThreadId":null,"preview":"","ephemeral":false,"section":null,"sectionEnteredAt":null,"projectId":null,"historyMode":"paginated","modelProvider":"openai","model":"gpt-5.6-luna","reasoningEffort":null,"createdAt":1792100422,"updatedAt":1792100422,"recencyAt":1792100422,"status":{"type":"idle"},"path":"/Users/flamingcow/.codex/sessions/2026/10/15/rollout-2026-10-15T14-40-22-01a14182-543d-73c0-8796-58d1573fa855.jsonl","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveInterrupt2023918540/001","cliVersion":"0.159.0","originator":"byre","source":"vscode","canAcceptDirectInput":true,"threadSource":null,"agentNickname":null,"agentRole":null,"gitInfo":null,"name":null,"daybreakEnabled":null,"turns":[]},"model":"gpt-5.6-luna","modelProvider":"openai","serviceTier":null,"disabledPluginIds":[],"cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveInterrupt2023918540/001","runtimeWorkspaceRoots":["/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveInterrupt2023918540/001"],"instructionSources":[],"approvalPolicy":"never","approvalsReviewer":"user","sandbox":{"type":"dangerFullAccess"},"activePermissionProfile":null,"reasoningEffort":null,"multiAgentMode":"explicitRequestOnly"}}} -{"dir":"send","msg":{"id":3,"method":"turn/start","params":{"input":[{"text":"Run the shell command `ping -c 120 127.0.0.1` and wait for it to finish, then say done.","type":"text"}],"threadId":"01a14182-543d-73c0-8796-58d1573fa855"}}} -{"dir":"recv","msg":{"method":"thread/started","params":{"thread":{"id":"01a14182-543d-73c0-8796-58d1573fa855","environments":[{"environmentId":"local","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveInterrupt2023918540/001","runtimeWorkspaceRoots":["/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveInterrupt2023918540/001"]}],"extra":null,"sessionId":"01a14182-543d-73c0-8796-58d1573fa855","forkedFromId":null,"parentThreadId":null,"preview":"","ephemeral":false,"section":null,"sectionEnteredAt":null,"projectId":null,"historyMode":"paginated","modelProvider":"openai","model":"gpt-5.6-luna","reasoningEffort":null,"createdAt":1792100422,"updatedAt":1792100422,"recencyAt":1792100422,"status":{"type":"idle"},"path":"/Users/flamingcow/.codex/sessions/2026/10/15/rollout-2026-10-15T14-40-22-01a14182-543d-73c0-8796-58d1573fa855.jsonl","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveInterrupt2023918540/001","cliVersion":"0.159.0","originator":"byre","source":"vscode","canAcceptDirectInput":true,"threadSource":null,"agentNickname":null,"agentRole":null,"gitInfo":null,"name":null,"daybreakEnabled":null,"turns":[]}},"emittedAtMs":1792100422857}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-543d-73c0-8796-58d1573fa855","name":"codex_apps","status":"starting","error":null,"failureReason":null},"emittedAtMs":1792100422857}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-543d-73c0-8796-58d1573fa855","name":"node_repl","status":"starting","error":null,"failureReason":null},"emittedAtMs":1792100422857}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-543d-73c0-8796-58d1573fa855","name":"cua_repl","status":"starting","error":null,"failureReason":null},"emittedAtMs":1792100422857}} -{"dir":"recv","msg":{"id":3,"result":{"turn":{"id":"01a14182-54cb-7ad3-b920-fe7c5c2f8866","items":[],"itemsView":"notLoaded","status":"inProgress","error":null,"startedAt":null,"completedAt":null,"durationMs":null}}}} -{"dir":"recv","msg":{"method":"thread/status/changed","params":{"threadId":"01a14182-543d-73c0-8796-58d1573fa855","status":{"type":"active","activeFlags":[]}},"emittedAtMs":1792100422923}} -{"dir":"recv","msg":{"method":"turn/started","params":{"threadId":"01a14182-543d-73c0-8796-58d1573fa855","turn":{"id":"01a14182-54cb-7ad3-b920-fe7c5c2f8866","items":[],"itemsView":"notLoaded","status":"inProgress","error":null,"startedAt":1792100422,"completedAt":null,"durationMs":null}},"emittedAtMs":1792100422923}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-543d-73c0-8796-58d1573fa855","name":"node_repl","status":"ready","error":null,"failureReason":null},"emittedAtMs":1792100423403}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-543d-73c0-8796-58d1573fa855","name":"cua_repl","status":"ready","error":null,"failureReason":null},"emittedAtMs":1792100423461}} -{"dir":"recv","msg":{"method":"account/updated","params":{"authMode":"chatgpt","planType":"prolite"},"emittedAtMs":1792100423517}} -{"dir":"recv","msg":{"method":"item/started","params":{"item":{"type":"userMessage","id":"01a14182-6e36-7800-9129-b5c2714dbf2b","clientId":null,"content":[{"type":"text","text":"Run the shell command `ping -c 120 127.0.0.1` and wait for it to finish, then say done.","text_elements":[]}]},"threadId":"01a14182-543d-73c0-8796-58d1573fa855","turnId":"01a14182-54cb-7ad3-b920-fe7c5c2f8866","startedAtMs":1792100429366},"emittedAtMs":1792100429366}} -{"dir":"recv","msg":{"method":"item/completed","params":{"item":{"type":"userMessage","id":"01a14182-6e36-7800-9129-b5c2714dbf2b","clientId":null,"content":[{"type":"text","text":"Run the shell command `ping -c 120 127.0.0.1` and wait for it to finish, then say done.","text_elements":[]}]},"threadId":"01a14182-543d-73c0-8796-58d1573fa855","turnId":"01a14182-54cb-7ad3-b920-fe7c5c2f8866","completedAtMs":1792100429366},"emittedAtMs":1792100429367}} -{"dir":"recv","msg":{"method":"item/started","params":{"item":{"type":"reasoning","id":"rs_0b571e63f0870260016ad1484e509c819a9d3fa283e22120c8","summary":[],"content":[]},"threadId":"01a14182-543d-73c0-8796-58d1573fa855","turnId":"01a14182-54cb-7ad3-b920-fe7c5c2f8866","startedAtMs":1792100430615},"emittedAtMs":1792100430615}} -{"dir":"recv","msg":{"method":"item/completed","params":{"item":{"type":"reasoning","id":"rs_0b571e63f0870260016ad1484e509c819a9d3fa283e22120c8","summary":[],"content":[]},"threadId":"01a14182-543d-73c0-8796-58d1573fa855","turnId":"01a14182-54cb-7ad3-b920-fe7c5c2f8866","completedAtMs":1792100430615},"emittedAtMs":1792100430616}} -{"dir":"recv","msg":{"method":"item/started","params":{"item":{"type":"agentMessage","id":"msg_0b571e63f0870260016ad1484e5b78819abeba3d7fb58dae9d","text":"","phase":"commentary","memoryCitation":null,"delivery":null,"questions":null},"threadId":"01a14182-543d-73c0-8796-58d1573fa855","turnId":"01a14182-54cb-7ad3-b920-fe7c5c2f8866","startedAtMs":1792100430645},"emittedAtMs":1792100430645}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-543d-73c0-8796-58d1573fa855","turnId":"01a14182-54cb-7ad3-b920-fe7c5c2f8866","itemId":"msg_0b571e63f0870260016ad1484e5b78819abeba3d7fb58dae9d","delta":"I"},"emittedAtMs":1792100430646}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-543d-73c0-8796-58d1573fa855","turnId":"01a14182-54cb-7ad3-b920-fe7c5c2f8866","itemId":"msg_0b571e63f0870260016ad1484e5b78819abeba3d7fb58dae9d","delta":"’m"},"emittedAtMs":1792100430715}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-543d-73c0-8796-58d1573fa855","turnId":"01a14182-54cb-7ad3-b920-fe7c5c2f8866","itemId":"msg_0b571e63f0870260016ad1484e5b78819abeba3d7fb58dae9d","delta":" starting"},"emittedAtMs":1792100430715}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-543d-73c0-8796-58d1573fa855","turnId":"01a14182-54cb-7ad3-b920-fe7c5c2f8866","itemId":"msg_0b571e63f0870260016ad1484e5b78819abeba3d7fb58dae9d","delta":" the"},"emittedAtMs":1792100430715}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-543d-73c0-8796-58d1573fa855","turnId":"01a14182-54cb-7ad3-b920-fe7c5c2f8866","itemId":"msg_0b571e63f0870260016ad1484e5b78819abeba3d7fb58dae9d","delta":" ping"},"emittedAtMs":1792100430715}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-543d-73c0-8796-58d1573fa855","turnId":"01a14182-54cb-7ad3-b920-fe7c5c2f8866","itemId":"msg_0b571e63f0870260016ad1484e5b78819abeba3d7fb58dae9d","delta":" and"},"emittedAtMs":1792100430715}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-543d-73c0-8796-58d1573fa855","turnId":"01a14182-54cb-7ad3-b920-fe7c5c2f8866","itemId":"msg_0b571e63f0870260016ad1484e5b78819abeba3d7fb58dae9d","delta":" will"},"emittedAtMs":1792100430715}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-543d-73c0-8796-58d1573fa855","turnId":"01a14182-54cb-7ad3-b920-fe7c5c2f8866","itemId":"msg_0b571e63f0870260016ad1484e5b78819abeba3d7fb58dae9d","delta":" wait"},"emittedAtMs":1792100430715}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-543d-73c0-8796-58d1573fa855","turnId":"01a14182-54cb-7ad3-b920-fe7c5c2f8866","itemId":"msg_0b571e63f0870260016ad1484e5b78819abeba3d7fb58dae9d","delta":" for"},"emittedAtMs":1792100430715}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-543d-73c0-8796-58d1573fa855","turnId":"01a14182-54cb-7ad3-b920-fe7c5c2f8866","itemId":"msg_0b571e63f0870260016ad1484e5b78819abeba3d7fb58dae9d","delta":" it"},"emittedAtMs":1792100430715}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-543d-73c0-8796-58d1573fa855","turnId":"01a14182-54cb-7ad3-b920-fe7c5c2f8866","itemId":"msg_0b571e63f0870260016ad1484e5b78819abeba3d7fb58dae9d","delta":" to"},"emittedAtMs":1792100430715}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-543d-73c0-8796-58d1573fa855","turnId":"01a14182-54cb-7ad3-b920-fe7c5c2f8866","itemId":"msg_0b571e63f0870260016ad1484e5b78819abeba3d7fb58dae9d","delta":" complete"},"emittedAtMs":1792100430715}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-543d-73c0-8796-58d1573fa855","turnId":"01a14182-54cb-7ad3-b920-fe7c5c2f8866","itemId":"msg_0b571e63f0870260016ad1484e5b78819abeba3d7fb58dae9d","delta":"."},"emittedAtMs":1792100430715}} -{"dir":"recv","msg":{"method":"item/completed","params":{"item":{"type":"agentMessage","id":"msg_0b571e63f0870260016ad1484e5b78819abeba3d7fb58dae9d","text":"I’m starting the ping and will wait for it to complete.","phase":"commentary","memoryCitation":null,"delivery":null,"questions":null},"threadId":"01a14182-543d-73c0-8796-58d1573fa855","turnId":"01a14182-54cb-7ad3-b920-fe7c5c2f8866","completedAtMs":1792100430717},"emittedAtMs":1792100430719}} -{"dir":"recv","msg":{"method":"item/started","params":{"item":{"type":"commandExecution","id":"exec-13df69e2-bad4-4516-a355-bbce409d3429","pluginId":null,"scriptPath":null,"command":"/bin/zsh -lc 'ping -c 120 127.0.0.1'","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveInterrupt2023918540/001","processId":"76847","source":"unifiedExecStartup","status":"inProgress","commandActions":[{"type":"unknown","command":"ping -c 120 127.0.0.1"}],"aggregatedOutput":null,"exitCode":null,"durationMs":null},"threadId":"01a14182-543d-73c0-8796-58d1573fa855","turnId":"01a14182-54cb-7ad3-b920-fe7c5c2f8866","startedAtMs":1792100431274},"emittedAtMs":1792100431275}} -{"dir":"recv","msg":{"method":"item/commandExecution/outputDelta","params":{"threadId":"01a14182-543d-73c0-8796-58d1573fa855","turnId":"01a14182-54cb-7ad3-b920-fe7c5c2f8866","itemId":"exec-13df69e2-bad4-4516-a355-bbce409d3429","delta":"64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.083 ms\r\n"},"emittedAtMs":1792100432135}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-543d-73c0-8796-58d1573fa855","name":"codex_apps","status":"ready","error":null,"failureReason":null},"emittedAtMs":1792100432826}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-543d-73c0-8796-58d1573fa855","name":"node_repl","status":"ready","error":null,"failureReason":null},"emittedAtMs":1792100432826}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-543d-73c0-8796-58d1573fa855","name":"cua_repl","status":"ready","error":null,"failureReason":null},"emittedAtMs":1792100432826}} -{"dir":"recv","msg":{"method":"item/commandExecution/outputDelta","params":{"threadId":"01a14182-543d-73c0-8796-58d1573fa855","turnId":"01a14182-54cb-7ad3-b920-fe7c5c2f8866","itemId":"exec-13df69e2-bad4-4516-a355-bbce409d3429","delta":"64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.047 ms\r\n"},"emittedAtMs":1792100433240}} -{"dir":"send","msg":{"id":4,"method":"turn/interrupt","params":{"threadId":"01a14182-543d-73c0-8796-58d1573fa855","turnId":"01a14182-54cb-7ad3-b920-fe7c5c2f8866"}}} -{"dir":"recv","msg":{"method":"thread/tokenUsage/updated","params":{"threadId":"01a14182-543d-73c0-8796-58d1573fa855","turnId":"01a14182-54cb-7ad3-b920-fe7c5c2f8866","tokenUsage":{"total":{"totalTokens":13481,"inputTokens":13336,"cachedInputTokens":13056,"cacheWriteInputTokens":0,"outputTokens":145,"reasoningOutputTokens":33},"last":{"totalTokens":13481,"inputTokens":13336,"cachedInputTokens":13056,"cacheWriteInputTokens":0,"outputTokens":145,"reasoningOutputTokens":33},"modelContextWindow":258400}},"emittedAtMs":1792100433279}} -{"dir":"recv","msg":{"method":"account/rateLimits/updated","params":{"rateLimits":{"limitId":"codex","limitName":null,"normalModelSlug":null,"primary":{"usedPercent":0,"windowDurationMins":10080,"resetsAt":1792701205},"secondary":null,"credits":{"hasCredits":false,"unlimited":false,"balance":"0"},"individualLimit":null,"spendControlReached":null,"planType":"prolite","rateLimitReachedType":null}},"emittedAtMs":1792100433279}} -{"dir":"recv","msg":{"id":4,"result":{}}} -{"dir":"recv","msg":{"method":"thread/status/changed","params":{"threadId":"01a14182-543d-73c0-8796-58d1573fa855","status":{"type":"idle"}},"emittedAtMs":1792100433281}} -{"dir":"recv","msg":{"method":"turn/completed","params":{"threadId":"01a14182-543d-73c0-8796-58d1573fa855","turn":{"id":"01a14182-54cb-7ad3-b920-fe7c5c2f8866","items":[],"itemsView":"notLoaded","status":"interrupted","error":null,"startedAt":1792100422,"completedAt":1792100433,"durationMs":10417}},"emittedAtMs":1792100433281}} -{"dir":"send","msg":{"id":5,"method":"turn/start","params":{"input":[{"text":"Reply with the single word ok.","type":"text"}],"threadId":"01a14182-543d-73c0-8796-58d1573fa855"}}} -{"dir":"recv","msg":{"id":5,"result":{"turn":{"id":"01a14182-7d82-7d22-a986-6e31c45f5590","items":[],"itemsView":"notLoaded","status":"inProgress","error":null,"startedAt":null,"completedAt":null,"durationMs":null}}}} -{"dir":"recv","msg":{"method":"thread/status/changed","params":{"threadId":"01a14182-543d-73c0-8796-58d1573fa855","status":{"type":"active","activeFlags":[]}},"emittedAtMs":1792100433307}} -{"dir":"recv","msg":{"method":"turn/started","params":{"threadId":"01a14182-543d-73c0-8796-58d1573fa855","turn":{"id":"01a14182-7d82-7d22-a986-6e31c45f5590","items":[],"itemsView":"notLoaded","status":"inProgress","error":null,"startedAt":1792100433,"completedAt":null,"durationMs":null}},"emittedAtMs":1792100433307}} -{"dir":"recv","msg":{"method":"item/started","params":{"item":{"type":"userMessage","id":"01a14182-7da8-7d92-9364-65e35c3df16e","clientId":null,"content":[{"type":"text","text":"Reply with the single word ok.","text_elements":[]}]},"threadId":"01a14182-543d-73c0-8796-58d1573fa855","turnId":"01a14182-7d82-7d22-a986-6e31c45f5590","startedAtMs":1792100433321},"emittedAtMs":1792100433321}} -{"dir":"recv","msg":{"method":"item/completed","params":{"item":{"type":"userMessage","id":"01a14182-7da8-7d92-9364-65e35c3df16e","clientId":null,"content":[{"type":"text","text":"Reply with the single word ok.","text_elements":[]}]},"threadId":"01a14182-543d-73c0-8796-58d1573fa855","turnId":"01a14182-7d82-7d22-a986-6e31c45f5590","completedAtMs":1792100433321},"emittedAtMs":1792100433321}} -{"dir":"recv","msg":{"method":"item/started","params":{"item":{"type":"agentMessage","id":"msg_0b571e63f0870260016ad14851f648819a99f9d51509c1115a","text":"","phase":"final_answer","memoryCitation":null,"delivery":null,"questions":null},"threadId":"01a14182-543d-73c0-8796-58d1573fa855","turnId":"01a14182-7d82-7d22-a986-6e31c45f5590","startedAtMs":1792100434124},"emittedAtMs":1792100434124}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-543d-73c0-8796-58d1573fa855","turnId":"01a14182-7d82-7d22-a986-6e31c45f5590","itemId":"msg_0b571e63f0870260016ad14851f648819a99f9d51509c1115a","delta":"ok"},"emittedAtMs":1792100434124}} -{"dir":"recv","msg":{"method":"item/completed","params":{"item":{"type":"agentMessage","id":"msg_0b571e63f0870260016ad14851f648819a99f9d51509c1115a","text":"ok","phase":"final_answer","memoryCitation":null,"delivery":null,"questions":null},"threadId":"01a14182-543d-73c0-8796-58d1573fa855","turnId":"01a14182-7d82-7d22-a986-6e31c45f5590","completedAtMs":1792100434124},"emittedAtMs":1792100434124}} -{"dir":"recv","msg":{"method":"thread/tokenUsage/updated","params":{"threadId":"01a14182-543d-73c0-8796-58d1573fa855","turnId":"01a14182-7d82-7d22-a986-6e31c45f5590","tokenUsage":{"total":{"totalTokens":29648,"inputTokens":29498,"cachedInputTokens":26112,"cacheWriteInputTokens":0,"outputTokens":150,"reasoningOutputTokens":33},"last":{"totalTokens":16167,"inputTokens":16162,"cachedInputTokens":13056,"cacheWriteInputTokens":0,"outputTokens":5,"reasoningOutputTokens":0},"modelContextWindow":258400}},"emittedAtMs":1792100434240}} -{"dir":"recv","msg":{"method":"account/rateLimits/updated","params":{"rateLimits":{"limitId":"codex","limitName":null,"normalModelSlug":null,"primary":{"usedPercent":0,"windowDurationMins":10080,"resetsAt":1792701205},"secondary":null,"credits":{"hasCredits":false,"unlimited":false,"balance":"0"},"individualLimit":null,"spendControlReached":null,"planType":"prolite","rateLimitReachedType":null}},"emittedAtMs":1792100434240}} -{"dir":"recv","msg":{"method":"thread/status/changed","params":{"threadId":"01a14182-543d-73c0-8796-58d1573fa855","status":{"type":"idle"}},"emittedAtMs":1792100434242}} -{"dir":"recv","msg":{"method":"turn/completed","params":{"threadId":"01a14182-543d-73c0-8796-58d1573fa855","turn":{"id":"01a14182-7d82-7d22-a986-6e31c45f5590","items":[{"type":"agentMessage","id":"msg_0b571e63f0870260016ad14851f648819a99f9d51509c1115a","text":"ok","phase":"final_answer","memoryCitation":null,"delivery":null,"questions":null}],"itemsView":"summary","status":"completed","error":null,"startedAt":1792100433,"completedAt":1792100434,"durationMs":954}},"emittedAtMs":1792100434242}} diff --git a/internal/codex/testdata/contract/ModelSwitch.jsonl b/internal/codex/testdata/contract/ModelSwitch.jsonl deleted file mode 100644 index 8b29e6b..0000000 --- a/internal/codex/testdata/contract/ModelSwitch.jsonl +++ /dev/null @@ -1,46 +0,0 @@ -{"dir":"send","msg":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true},"clientInfo":{"name":"byre","title":"byre","version":"0.0.0"}}}} -{"dir":"recv","msg":{"id":1,"result":{"userAgent":"byre/0.159.0 (Mac OS 27.0.1; arm64) unknown (byre; 0.0.0)","codexHome":"/Users/flamingcow/.codex","platformFamily":"unix","platformOs":"macos"}}} -{"dir":"recv","msg":{"method":"remoteControl/status/changed","params":{"status":"disabled","serverName":"roc.tailcecab.ts.net","installationId":"7b5574fb-530f-4cdf-925d-13b538911a88","environmentId":null},"emittedAtMs":1792100422676}} -{"dir":"send","msg":{"method":"initialized"}} -{"dir":"send","msg":{"id":2,"method":"thread/start","params":{"approvalPolicy":"never","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveModelSwitch1165344666/001","model":"gpt-5.6-luna","sandbox":"danger-full-access"}}} -{"dir":"recv","msg":{"id":2,"result":{"thread":{"id":"01a14182-541c-74d2-92cb-883d4c01b0a5","environments":[{"environmentId":"local","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveModelSwitch1165344666/001","runtimeWorkspaceRoots":["/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveModelSwitch1165344666/001"]}],"extra":null,"sessionId":"01a14182-541c-74d2-92cb-883d4c01b0a5","forkedFromId":null,"parentThreadId":null,"preview":"","ephemeral":false,"section":null,"sectionEnteredAt":null,"projectId":null,"historyMode":"paginated","modelProvider":"openai","model":"gpt-5.6-luna","reasoningEffort":null,"createdAt":1792100422,"updatedAt":1792100422,"recencyAt":1792100422,"status":{"type":"idle"},"path":"/Users/flamingcow/.codex/sessions/2026/10/15/rollout-2026-10-15T14-40-22-01a14182-541c-74d2-92cb-883d4c01b0a5.jsonl","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveModelSwitch1165344666/001","cliVersion":"0.159.0","originator":"byre","source":"vscode","canAcceptDirectInput":true,"threadSource":null,"agentNickname":null,"agentRole":null,"gitInfo":null,"name":null,"daybreakEnabled":null,"turns":[]},"model":"gpt-5.6-luna","modelProvider":"openai","serviceTier":null,"disabledPluginIds":[],"cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveModelSwitch1165344666/001","runtimeWorkspaceRoots":["/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveModelSwitch1165344666/001"],"instructionSources":[],"approvalPolicy":"never","approvalsReviewer":"user","sandbox":{"type":"dangerFullAccess"},"activePermissionProfile":null,"reasoningEffort":null,"multiAgentMode":"explicitRequestOnly"}}} -{"dir":"send","msg":{"id":3,"method":"turn/start","params":{"input":[{"text":"Reply with the single word ok.","type":"text"}],"threadId":"01a14182-541c-74d2-92cb-883d4c01b0a5"}}} -{"dir":"recv","msg":{"method":"thread/started","params":{"thread":{"id":"01a14182-541c-74d2-92cb-883d4c01b0a5","environments":[{"environmentId":"local","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveModelSwitch1165344666/001","runtimeWorkspaceRoots":["/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveModelSwitch1165344666/001"]}],"extra":null,"sessionId":"01a14182-541c-74d2-92cb-883d4c01b0a5","forkedFromId":null,"parentThreadId":null,"preview":"","ephemeral":false,"section":null,"sectionEnteredAt":null,"projectId":null,"historyMode":"paginated","modelProvider":"openai","model":"gpt-5.6-luna","reasoningEffort":null,"createdAt":1792100422,"updatedAt":1792100422,"recencyAt":1792100422,"status":{"type":"idle"},"path":"/Users/flamingcow/.codex/sessions/2026/10/15/rollout-2026-10-15T14-40-22-01a14182-541c-74d2-92cb-883d4c01b0a5.jsonl","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveModelSwitch1165344666/001","cliVersion":"0.159.0","originator":"byre","source":"vscode","canAcceptDirectInput":true,"threadSource":null,"agentNickname":null,"agentRole":null,"gitInfo":null,"name":null,"daybreakEnabled":null,"turns":[]}},"emittedAtMs":1792100422859}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-541c-74d2-92cb-883d4c01b0a5","name":"cua_repl","status":"starting","error":null,"failureReason":null},"emittedAtMs":1792100422859}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-541c-74d2-92cb-883d4c01b0a5","name":"codex_apps","status":"starting","error":null,"failureReason":null},"emittedAtMs":1792100422859}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-541c-74d2-92cb-883d4c01b0a5","name":"node_repl","status":"starting","error":null,"failureReason":null},"emittedAtMs":1792100422859}} -{"dir":"recv","msg":{"id":3,"result":{"turn":{"id":"01a14182-54ce-7c92-b4c5-4b6950c0f8f3","items":[],"itemsView":"notLoaded","status":"inProgress","error":null,"startedAt":null,"completedAt":null,"durationMs":null}}}} -{"dir":"recv","msg":{"method":"thread/status/changed","params":{"threadId":"01a14182-541c-74d2-92cb-883d4c01b0a5","status":{"type":"active","activeFlags":[]}},"emittedAtMs":1792100422932}} -{"dir":"recv","msg":{"method":"turn/started","params":{"threadId":"01a14182-541c-74d2-92cb-883d4c01b0a5","turn":{"id":"01a14182-54ce-7c92-b4c5-4b6950c0f8f3","items":[],"itemsView":"notLoaded","status":"inProgress","error":null,"startedAt":1792100422,"completedAt":null,"durationMs":null}},"emittedAtMs":1792100422932}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-541c-74d2-92cb-883d4c01b0a5","name":"node_repl","status":"ready","error":null,"failureReason":null},"emittedAtMs":1792100423404}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-541c-74d2-92cb-883d4c01b0a5","name":"cua_repl","status":"ready","error":null,"failureReason":null},"emittedAtMs":1792100423448}} -{"dir":"recv","msg":{"method":"account/updated","params":{"authMode":"chatgpt","planType":"prolite"},"emittedAtMs":1792100423681}} -{"dir":"recv","msg":{"method":"item/started","params":{"item":{"type":"userMessage","id":"01a14182-6e99-7f92-82a8-527915b8617d","clientId":null,"content":[{"type":"text","text":"Reply with the single word ok.","text_elements":[]}]},"threadId":"01a14182-541c-74d2-92cb-883d4c01b0a5","turnId":"01a14182-54ce-7c92-b4c5-4b6950c0f8f3","startedAtMs":1792100429468},"emittedAtMs":1792100429469}} -{"dir":"recv","msg":{"method":"item/completed","params":{"item":{"type":"userMessage","id":"01a14182-6e99-7f92-82a8-527915b8617d","clientId":null,"content":[{"type":"text","text":"Reply with the single word ok.","text_elements":[]}]},"threadId":"01a14182-541c-74d2-92cb-883d4c01b0a5","turnId":"01a14182-54ce-7c92-b4c5-4b6950c0f8f3","completedAtMs":1792100429469},"emittedAtMs":1792100429470}} -{"dir":"recv","msg":{"method":"item/started","params":{"item":{"type":"agentMessage","id":"msg_0a28202ba3f82eef016ad1484e726c819bb5f9f594cc1f0f9d","text":"","phase":"final_answer","memoryCitation":null,"delivery":null,"questions":null},"threadId":"01a14182-541c-74d2-92cb-883d4c01b0a5","turnId":"01a14182-54ce-7c92-b4c5-4b6950c0f8f3","startedAtMs":1792100430724},"emittedAtMs":1792100430724}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-541c-74d2-92cb-883d4c01b0a5","turnId":"01a14182-54ce-7c92-b4c5-4b6950c0f8f3","itemId":"msg_0a28202ba3f82eef016ad1484e726c819bb5f9f594cc1f0f9d","delta":"ok"},"emittedAtMs":1792100430726}} -{"dir":"recv","msg":{"method":"item/completed","params":{"item":{"type":"agentMessage","id":"msg_0a28202ba3f82eef016ad1484e726c819bb5f9f594cc1f0f9d","text":"ok","phase":"final_answer","memoryCitation":null,"delivery":null,"questions":null},"threadId":"01a14182-541c-74d2-92cb-883d4c01b0a5","turnId":"01a14182-54ce-7c92-b4c5-4b6950c0f8f3","completedAtMs":1792100430731},"emittedAtMs":1792100430732}} -{"dir":"recv","msg":{"method":"thread/tokenUsage/updated","params":{"threadId":"01a14182-541c-74d2-92cb-883d4c01b0a5","turnId":"01a14182-54ce-7c92-b4c5-4b6950c0f8f3","tokenUsage":{"total":{"totalTokens":13320,"inputTokens":13315,"cachedInputTokens":11008,"cacheWriteInputTokens":0,"outputTokens":5,"reasoningOutputTokens":0},"last":{"totalTokens":13320,"inputTokens":13315,"cachedInputTokens":11008,"cacheWriteInputTokens":0,"outputTokens":5,"reasoningOutputTokens":0},"modelContextWindow":258400}},"emittedAtMs":1792100430807}} -{"dir":"recv","msg":{"method":"account/rateLimits/updated","params":{"rateLimits":{"limitId":"codex","limitName":null,"normalModelSlug":null,"primary":{"usedPercent":0,"windowDurationMins":10080,"resetsAt":1792701205},"secondary":null,"credits":{"hasCredits":false,"unlimited":false,"balance":"0"},"individualLimit":null,"spendControlReached":null,"planType":"prolite","rateLimitReachedType":null}},"emittedAtMs":1792100430807}} -{"dir":"recv","msg":{"method":"thread/status/changed","params":{"threadId":"01a14182-541c-74d2-92cb-883d4c01b0a5","status":{"type":"idle"}},"emittedAtMs":1792100430811}} -{"dir":"recv","msg":{"method":"turn/completed","params":{"threadId":"01a14182-541c-74d2-92cb-883d4c01b0a5","turn":{"id":"01a14182-54ce-7c92-b4c5-4b6950c0f8f3","items":[{"type":"agentMessage","id":"msg_0a28202ba3f82eef016ad1484e726c819bb5f9f594cc1f0f9d","text":"ok","phase":"final_answer","memoryCitation":null,"delivery":null,"questions":null}],"itemsView":"summary","status":"completed","error":null,"startedAt":1792100422,"completedAt":1792100430,"durationMs":7943}},"emittedAtMs":1792100430811}} -{"dir":"send","msg":{"id":4,"method":"thread/read","params":{"threadId":"01a14182-541c-74d2-92cb-883d4c01b0a5"}}} -{"dir":"recv","msg":{"id":4,"result":{"thread":{"id":"01a14182-541c-74d2-92cb-883d4c01b0a5","environments":[{"environmentId":"local","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveModelSwitch1165344666/001","runtimeWorkspaceRoots":["/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveModelSwitch1165344666/001"]}],"extra":null,"sessionId":"01a14182-541c-74d2-92cb-883d4c01b0a5","forkedFromId":null,"parentThreadId":null,"preview":"Reply with the single word ok.","ephemeral":false,"section":null,"sectionEnteredAt":null,"projectId":null,"historyMode":"paginated","modelProvider":"openai","model":"gpt-5.6-luna","reasoningEffort":null,"createdAt":1792100422,"updatedAt":1792100430,"recencyAt":1792100422,"status":{"type":"idle"},"path":"/Users/flamingcow/.codex/sessions/2026/10/15/rollout-2026-10-15T14-40-22-01a14182-541c-74d2-92cb-883d4c01b0a5.jsonl","cwd":"/private/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveModelSwitch1165344666/001","cliVersion":"0.159.0","originator":"byre","source":"vscode","canAcceptDirectInput":true,"threadSource":null,"agentNickname":null,"agentRole":null,"gitInfo":{"sha":"545185b66b04bfe51916ae9840d0f16c639a65c9","branch":"main","originUrl":null},"name":null,"daybreakEnabled":null,"turns":[]}}}} -{"dir":"send","msg":{"id":5,"method":"turn/start","params":{"input":[{"text":"Reply with the single word ok.","type":"text"}],"model":"gpt-5.6-terra","threadId":"01a14182-541c-74d2-92cb-883d4c01b0a5"}}} -{"dir":"recv","msg":{"method":"thread/settings/updated","params":{"threadId":"01a14182-541c-74d2-92cb-883d4c01b0a5","threadSettings":{"disabledPluginIds":[],"cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveModelSwitch1165344666/001","approvalPolicy":"never","approvalsReviewer":"user","sandboxPolicy":{"type":"dangerFullAccess"},"activePermissionProfile":null,"model":"gpt-5.6-terra","modelProvider":"openai","serviceTier":null,"effort":null,"summary":null,"collaborationMode":{"mode":"default","settings":{"model":"gpt-5.6-terra","reasoning_effort":null,"developer_instructions":null}},"multiAgentMode":"explicitRequestOnly","personality":null}},"emittedAtMs":1792100430823}} -{"dir":"recv","msg":{"id":5,"result":{"turn":{"id":"01a14182-73dd-78b0-839f-ae6745fcb2b8","items":[],"itemsView":"notLoaded","status":"inProgress","error":null,"startedAt":null,"completedAt":null,"durationMs":null}}}} -{"dir":"recv","msg":{"method":"thread/status/changed","params":{"threadId":"01a14182-541c-74d2-92cb-883d4c01b0a5","status":{"type":"active","activeFlags":[]}},"emittedAtMs":1792100430825}} -{"dir":"recv","msg":{"method":"turn/started","params":{"threadId":"01a14182-541c-74d2-92cb-883d4c01b0a5","turn":{"id":"01a14182-73dd-78b0-839f-ae6745fcb2b8","items":[],"itemsView":"notLoaded","status":"inProgress","error":null,"startedAt":1792100430,"completedAt":null,"durationMs":null}},"emittedAtMs":1792100430825}} -{"dir":"recv","msg":{"method":"item/started","params":{"item":{"type":"userMessage","id":"01a14182-73fe-7041-94bf-6f37922b0ce9","clientId":null,"content":[{"type":"text","text":"Reply with the single word ok.","text_elements":[]}]},"threadId":"01a14182-541c-74d2-92cb-883d4c01b0a5","turnId":"01a14182-73dd-78b0-839f-ae6745fcb2b8","startedAtMs":1792100430847},"emittedAtMs":1792100430847}} -{"dir":"recv","msg":{"method":"item/completed","params":{"item":{"type":"userMessage","id":"01a14182-73fe-7041-94bf-6f37922b0ce9","clientId":null,"content":[{"type":"text","text":"Reply with the single word ok.","text_elements":[]}]},"threadId":"01a14182-541c-74d2-92cb-883d4c01b0a5","turnId":"01a14182-73dd-78b0-839f-ae6745fcb2b8","completedAtMs":1792100430847},"emittedAtMs":1792100430849}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-541c-74d2-92cb-883d4c01b0a5","name":"node_repl","status":"ready","error":null,"failureReason":null},"emittedAtMs":1792100432044}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-541c-74d2-92cb-883d4c01b0a5","name":"codex_apps","status":"ready","error":null,"failureReason":null},"emittedAtMs":1792100432044}} -{"dir":"recv","msg":{"method":"item/started","params":{"item":{"type":"agentMessage","id":"msg_0a28202ba3f82eef016ad1485008e4819bb5c3f1e3e0ec3147","text":"","phase":"final_answer","memoryCitation":null,"delivery":null,"questions":null},"threadId":"01a14182-541c-74d2-92cb-883d4c01b0a5","turnId":"01a14182-73dd-78b0-839f-ae6745fcb2b8","startedAtMs":1792100432234},"emittedAtMs":1792100432234}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-541c-74d2-92cb-883d4c01b0a5","turnId":"01a14182-73dd-78b0-839f-ae6745fcb2b8","itemId":"msg_0a28202ba3f82eef016ad1485008e4819bb5c3f1e3e0ec3147","delta":"ok"},"emittedAtMs":1792100432234}} -{"dir":"recv","msg":{"method":"item/completed","params":{"item":{"type":"agentMessage","id":"msg_0a28202ba3f82eef016ad1485008e4819bb5c3f1e3e0ec3147","text":"ok","phase":"final_answer","memoryCitation":null,"delivery":null,"questions":null},"threadId":"01a14182-541c-74d2-92cb-883d4c01b0a5","turnId":"01a14182-73dd-78b0-839f-ae6745fcb2b8","completedAtMs":1792100432235},"emittedAtMs":1792100432236}} -{"dir":"recv","msg":{"method":"thread/tokenUsage/updated","params":{"threadId":"01a14182-541c-74d2-92cb-883d4c01b0a5","turnId":"01a14182-73dd-78b0-839f-ae6745fcb2b8","tokenUsage":{"total":{"totalTokens":32841,"inputTokens":32831,"cachedInputTokens":11008,"cacheWriteInputTokens":0,"outputTokens":10,"reasoningOutputTokens":0},"last":{"totalTokens":19521,"inputTokens":19516,"cachedInputTokens":0,"cacheWriteInputTokens":0,"outputTokens":5,"reasoningOutputTokens":0},"modelContextWindow":258400}},"emittedAtMs":1792100432279}} -{"dir":"recv","msg":{"method":"account/rateLimits/updated","params":{"rateLimits":{"limitId":"codex","limitName":null,"normalModelSlug":null,"primary":{"usedPercent":0,"windowDurationMins":10080,"resetsAt":1792701205},"secondary":null,"credits":{"hasCredits":false,"unlimited":false,"balance":"0"},"individualLimit":null,"spendControlReached":null,"planType":"prolite","rateLimitReachedType":null}},"emittedAtMs":1792100432279}} -{"dir":"recv","msg":{"method":"thread/status/changed","params":{"threadId":"01a14182-541c-74d2-92cb-883d4c01b0a5","status":{"type":"idle"}},"emittedAtMs":1792100432284}} -{"dir":"recv","msg":{"method":"turn/completed","params":{"threadId":"01a14182-541c-74d2-92cb-883d4c01b0a5","turn":{"id":"01a14182-73dd-78b0-839f-ae6745fcb2b8","items":[{"type":"agentMessage","id":"msg_0a28202ba3f82eef016ad1485008e4819bb5c3f1e3e0ec3147","text":"ok","phase":"final_answer","memoryCitation":null,"delivery":null,"questions":null}],"itemsView":"summary","status":"completed","error":null,"startedAt":1792100430,"completedAt":1792100432,"durationMs":1459}},"emittedAtMs":1792100432284}} -{"dir":"send","msg":{"id":6,"method":"thread/read","params":{"threadId":"01a14182-541c-74d2-92cb-883d4c01b0a5"}}} -{"dir":"recv","msg":{"id":6,"result":{"thread":{"id":"01a14182-541c-74d2-92cb-883d4c01b0a5","environments":[{"environmentId":"local","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveModelSwitch1165344666/001","runtimeWorkspaceRoots":["/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveModelSwitch1165344666/001"]}],"extra":null,"sessionId":"01a14182-541c-74d2-92cb-883d4c01b0a5","forkedFromId":null,"parentThreadId":null,"preview":"Reply with the single word ok.","ephemeral":false,"section":null,"sectionEnteredAt":null,"projectId":null,"historyMode":"paginated","modelProvider":"openai","model":"gpt-5.6-terra","reasoningEffort":null,"createdAt":1792100422,"updatedAt":1792100432,"recencyAt":1792100430,"status":{"type":"idle"},"path":"/Users/flamingcow/.codex/sessions/2026/10/15/rollout-2026-10-15T14-40-22-01a14182-541c-74d2-92cb-883d4c01b0a5.jsonl","cwd":"/private/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveModelSwitch1165344666/001","cliVersion":"0.159.0","originator":"byre","source":"vscode","canAcceptDirectInput":true,"threadSource":null,"agentNickname":null,"agentRole":null,"gitInfo":{"sha":"545185b66b04bfe51916ae9840d0f16c639a65c9","branch":"main","originUrl":null},"name":null,"daybreakEnabled":null,"turns":[]}}}} diff --git a/internal/codex/testdata/contract/Resume-first.jsonl b/internal/codex/testdata/contract/Resume-first.jsonl deleted file mode 100644 index 252305c..0000000 --- a/internal/codex/testdata/contract/Resume-first.jsonl +++ /dev/null @@ -1,26 +0,0 @@ -{"dir":"send","msg":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true},"clientInfo":{"name":"byre","title":"byre","version":"0.0.0"}}}} -{"dir":"recv","msg":{"id":1,"result":{"userAgent":"byre/0.159.0 (Mac OS 27.0.1; arm64) unknown (byre; 0.0.0)","codexHome":"/Users/flamingcow/.codex","platformFamily":"unix","platformOs":"macos"}}} -{"dir":"send","msg":{"method":"initialized"}} -{"dir":"send","msg":{"id":2,"method":"thread/start","params":{"approvalPolicy":"never","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveResume1649183158/001","model":"gpt-5.6-luna","sandbox":"danger-full-access"}}} -{"dir":"recv","msg":{"method":"remoteControl/status/changed","params":{"status":"disabled","serverName":"roc.tailcecab.ts.net","installationId":"7b5574fb-530f-4cdf-925d-13b538911a88","environmentId":null},"emittedAtMs":1792100422682}} -{"dir":"recv","msg":{"id":2,"result":{"thread":{"id":"01a14182-5438-73c3-99fe-af3f98e47e3a","environments":[{"environmentId":"local","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveResume1649183158/001","runtimeWorkspaceRoots":["/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveResume1649183158/001"]}],"extra":null,"sessionId":"01a14182-5438-73c3-99fe-af3f98e47e3a","forkedFromId":null,"parentThreadId":null,"preview":"","ephemeral":false,"section":null,"sectionEnteredAt":null,"projectId":null,"historyMode":"paginated","modelProvider":"openai","model":"gpt-5.6-luna","reasoningEffort":null,"createdAt":1792100422,"updatedAt":1792100422,"recencyAt":1792100422,"status":{"type":"idle"},"path":"/Users/flamingcow/.codex/sessions/2026/10/15/rollout-2026-10-15T14-40-22-01a14182-5438-73c3-99fe-af3f98e47e3a.jsonl","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveResume1649183158/001","cliVersion":"0.159.0","originator":"byre","source":"vscode","canAcceptDirectInput":true,"threadSource":null,"agentNickname":null,"agentRole":null,"gitInfo":null,"name":null,"daybreakEnabled":null,"turns":[]},"model":"gpt-5.6-luna","modelProvider":"openai","serviceTier":null,"disabledPluginIds":[],"cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveResume1649183158/001","runtimeWorkspaceRoots":["/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveResume1649183158/001"],"instructionSources":[],"approvalPolicy":"never","approvalsReviewer":"user","sandbox":{"type":"dangerFullAccess"},"activePermissionProfile":null,"reasoningEffort":null,"multiAgentMode":"explicitRequestOnly"}}} -{"dir":"send","msg":{"id":3,"method":"turn/start","params":{"input":[{"text":"Remember the codeword AZURE-17. Reply with the single word ok.","type":"text"}],"threadId":"01a14182-5438-73c3-99fe-af3f98e47e3a"}}} -{"dir":"recv","msg":{"method":"thread/started","params":{"thread":{"id":"01a14182-5438-73c3-99fe-af3f98e47e3a","environments":[{"environmentId":"local","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveResume1649183158/001","runtimeWorkspaceRoots":["/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveResume1649183158/001"]}],"extra":null,"sessionId":"01a14182-5438-73c3-99fe-af3f98e47e3a","forkedFromId":null,"parentThreadId":null,"preview":"","ephemeral":false,"section":null,"sectionEnteredAt":null,"projectId":null,"historyMode":"paginated","modelProvider":"openai","model":"gpt-5.6-luna","reasoningEffort":null,"createdAt":1792100422,"updatedAt":1792100422,"recencyAt":1792100422,"status":{"type":"idle"},"path":"/Users/flamingcow/.codex/sessions/2026/10/15/rollout-2026-10-15T14-40-22-01a14182-5438-73c3-99fe-af3f98e47e3a.jsonl","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveResume1649183158/001","cliVersion":"0.159.0","originator":"byre","source":"vscode","canAcceptDirectInput":true,"threadSource":null,"agentNickname":null,"agentRole":null,"gitInfo":null,"name":null,"daybreakEnabled":null,"turns":[]}},"emittedAtMs":1792100422805}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-5438-73c3-99fe-af3f98e47e3a","name":"cua_repl","status":"starting","error":null,"failureReason":null},"emittedAtMs":1792100422805}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-5438-73c3-99fe-af3f98e47e3a","name":"node_repl","status":"starting","error":null,"failureReason":null},"emittedAtMs":1792100422805}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-5438-73c3-99fe-af3f98e47e3a","name":"codex_apps","status":"starting","error":null,"failureReason":null},"emittedAtMs":1792100422805}} -{"dir":"recv","msg":{"id":3,"result":{"turn":{"id":"01a14182-5497-7b31-9d3e-6ba5944fe85c","items":[],"itemsView":"notLoaded","status":"inProgress","error":null,"startedAt":null,"completedAt":null,"durationMs":null}}}} -{"dir":"recv","msg":{"method":"thread/status/changed","params":{"threadId":"01a14182-5438-73c3-99fe-af3f98e47e3a","status":{"type":"active","activeFlags":[]}},"emittedAtMs":1792100422909}} -{"dir":"recv","msg":{"method":"turn/started","params":{"threadId":"01a14182-5438-73c3-99fe-af3f98e47e3a","turn":{"id":"01a14182-5497-7b31-9d3e-6ba5944fe85c","items":[],"itemsView":"notLoaded","status":"inProgress","error":null,"startedAt":1792100422,"completedAt":null,"durationMs":null}},"emittedAtMs":1792100422909}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-5438-73c3-99fe-af3f98e47e3a","name":"node_repl","status":"ready","error":null,"failureReason":null},"emittedAtMs":1792100423296}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-5438-73c3-99fe-af3f98e47e3a","name":"cua_repl","status":"ready","error":null,"failureReason":null},"emittedAtMs":1792100423439}} -{"dir":"recv","msg":{"method":"account/updated","params":{"authMode":"chatgpt","planType":"prolite"},"emittedAtMs":1792100423575}} -{"dir":"recv","msg":{"method":"item/started","params":{"item":{"type":"userMessage","id":"01a14182-6ef2-7961-9b25-fc730691e993","clientId":null,"content":[{"type":"text","text":"Remember the codeword AZURE-17. Reply with the single word ok.","text_elements":[]}]},"threadId":"01a14182-5438-73c3-99fe-af3f98e47e3a","turnId":"01a14182-5497-7b31-9d3e-6ba5944fe85c","startedAtMs":1792100429554},"emittedAtMs":1792100429554}} -{"dir":"recv","msg":{"method":"item/completed","params":{"item":{"type":"userMessage","id":"01a14182-6ef2-7961-9b25-fc730691e993","clientId":null,"content":[{"type":"text","text":"Remember the codeword AZURE-17. Reply with the single word ok.","text_elements":[]}]},"threadId":"01a14182-5438-73c3-99fe-af3f98e47e3a","turnId":"01a14182-5497-7b31-9d3e-6ba5944fe85c","completedAtMs":1792100429554},"emittedAtMs":1792100429555}} -{"dir":"recv","msg":{"method":"item/started","params":{"item":{"type":"agentMessage","id":"msg_06aa10c1bbbf4e36016ad1484e56c481988a7302982f03daac","text":"","phase":"final_answer","memoryCitation":null,"delivery":null,"questions":null},"threadId":"01a14182-5438-73c3-99fe-af3f98e47e3a","turnId":"01a14182-5497-7b31-9d3e-6ba5944fe85c","startedAtMs":1792100430657},"emittedAtMs":1792100430657}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-5438-73c3-99fe-af3f98e47e3a","turnId":"01a14182-5497-7b31-9d3e-6ba5944fe85c","itemId":"msg_06aa10c1bbbf4e36016ad1484e56c481988a7302982f03daac","delta":"ok"},"emittedAtMs":1792100430680}} -{"dir":"recv","msg":{"method":"item/completed","params":{"item":{"type":"agentMessage","id":"msg_06aa10c1bbbf4e36016ad1484e56c481988a7302982f03daac","text":"ok","phase":"final_answer","memoryCitation":null,"delivery":null,"questions":null},"threadId":"01a14182-5438-73c3-99fe-af3f98e47e3a","turnId":"01a14182-5497-7b31-9d3e-6ba5944fe85c","completedAtMs":1792100430683},"emittedAtMs":1792100430685}} -{"dir":"recv","msg":{"method":"thread/tokenUsage/updated","params":{"threadId":"01a14182-5438-73c3-99fe-af3f98e47e3a","turnId":"01a14182-5497-7b31-9d3e-6ba5944fe85c","tokenUsage":{"total":{"totalTokens":13327,"inputTokens":13322,"cachedInputTokens":13056,"cacheWriteInputTokens":0,"outputTokens":5,"reasoningOutputTokens":0},"last":{"totalTokens":13327,"inputTokens":13322,"cachedInputTokens":13056,"cacheWriteInputTokens":0,"outputTokens":5,"reasoningOutputTokens":0},"modelContextWindow":258400}},"emittedAtMs":1792100430748}} -{"dir":"recv","msg":{"method":"account/rateLimits/updated","params":{"rateLimits":{"limitId":"codex","limitName":null,"normalModelSlug":null,"primary":{"usedPercent":0,"windowDurationMins":10080,"resetsAt":1792701205},"secondary":null,"credits":{"hasCredits":false,"unlimited":false,"balance":"0"},"individualLimit":null,"spendControlReached":null,"planType":"prolite","rateLimitReachedType":null}},"emittedAtMs":1792100430748}} -{"dir":"recv","msg":{"method":"thread/status/changed","params":{"threadId":"01a14182-5438-73c3-99fe-af3f98e47e3a","status":{"type":"idle"}},"emittedAtMs":1792100430755}} -{"dir":"recv","msg":{"method":"turn/completed","params":{"threadId":"01a14182-5438-73c3-99fe-af3f98e47e3a","turn":{"id":"01a14182-5497-7b31-9d3e-6ba5944fe85c","items":[{"type":"agentMessage","id":"msg_06aa10c1bbbf4e36016ad1484e56c481988a7302982f03daac","text":"ok","phase":"final_answer","memoryCitation":null,"delivery":null,"questions":null}],"itemsView":"summary","status":"completed","error":null,"startedAt":1792100422,"completedAt":1792100430,"durationMs":7943}},"emittedAtMs":1792100430755}} diff --git a/internal/codex/testdata/contract/Resume-resumed.jsonl b/internal/codex/testdata/contract/Resume-resumed.jsonl deleted file mode 100644 index c7e0f37..0000000 --- a/internal/codex/testdata/contract/Resume-resumed.jsonl +++ /dev/null @@ -1,37 +0,0 @@ -{"dir":"send","msg":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true},"clientInfo":{"name":"byre","title":"byre","version":"0.0.0"}}}} -{"dir":"recv","msg":{"id":1,"result":{"userAgent":"byre/0.159.0 (Mac OS 27.0.1; arm64) unknown (byre; 0.0.0)","codexHome":"/Users/flamingcow/.codex","platformFamily":"unix","platformOs":"macos"}}} -{"dir":"send","msg":{"method":"initialized"}} -{"dir":"recv","msg":{"method":"remoteControl/status/changed","params":{"status":"disabled","serverName":"roc.tailcecab.ts.net","installationId":"7b5574fb-530f-4cdf-925d-13b538911a88","environmentId":null},"emittedAtMs":1792100430809}} -{"dir":"send","msg":{"id":2,"method":"thread/resume","params":{"approvalPolicy":"never","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveResume1649183158/001","model":"gpt-5.6-luna","sandbox":"danger-full-access","threadId":"01a14182-5438-73c3-99fe-af3f98e47e3a"}}} -{"dir":"recv","msg":{"method":"deprecationNotice","params":{"summary":"Full-history hydration is deprecated for paginated threads; use `excludeTurns: true`, then page with `thread/turns/list` and `thread/items/list`.","details":null},"emittedAtMs":1792100430812}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-5438-73c3-99fe-af3f98e47e3a","name":"node_repl","status":"starting","error":null,"failureReason":null},"emittedAtMs":1792100430847}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-5438-73c3-99fe-af3f98e47e3a","name":"cua_repl","status":"starting","error":null,"failureReason":null},"emittedAtMs":1792100430847}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-5438-73c3-99fe-af3f98e47e3a","name":"codex_apps","status":"starting","error":null,"failureReason":null},"emittedAtMs":1792100430847}} -{"dir":"recv","msg":{"method":"thread/status/changed","params":{"threadId":"01a14182-5438-73c3-99fe-af3f98e47e3a","status":{"type":"idle"}},"emittedAtMs":1792100430849}} -{"dir":"recv","msg":{"id":2,"result":{"thread":{"id":"01a14182-5438-73c3-99fe-af3f98e47e3a","environments":[{"environmentId":"local","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveResume1649183158/001","runtimeWorkspaceRoots":["/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveResume1649183158/001"]}],"extra":null,"sessionId":"01a14182-5438-73c3-99fe-af3f98e47e3a","forkedFromId":null,"parentThreadId":null,"preview":"Remember the codeword AZURE-17. Reply with the single word ok.","ephemeral":false,"section":null,"sectionEnteredAt":null,"projectId":null,"historyMode":"paginated","modelProvider":"openai","model":"gpt-5.6-luna","reasoningEffort":null,"createdAt":1792100422,"updatedAt":1792100430,"recencyAt":1792100422,"status":{"type":"idle"},"path":"/Users/flamingcow/.codex/sessions/2026/10/15/rollout-2026-10-15T14-40-22-01a14182-5438-73c3-99fe-af3f98e47e3a.jsonl","cwd":"/private/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveResume1649183158/001","cliVersion":"0.159.0","originator":"byre","source":"vscode","canAcceptDirectInput":true,"threadSource":null,"agentNickname":null,"agentRole":null,"gitInfo":{"sha":"545185b66b04bfe51916ae9840d0f16c639a65c9","branch":"main","originUrl":null},"name":null,"daybreakEnabled":null,"turns":[{"id":"01a14182-5497-7b31-9d3e-6ba5944fe85c","items":[{"type":"userMessage","id":"01a14182-6ef2-7961-9b25-fc730691e993","clientId":null,"content":[{"type":"text","text":"Remember the codeword AZURE-17. Reply with the single word ok.","text_elements":[]}]},{"type":"agentMessage","id":"msg_06aa10c1bbbf4e36016ad1484e56c481988a7302982f03daac","text":"ok","phase":"final_answer","memoryCitation":null,"delivery":null,"questions":null}],"itemsView":"full","status":"completed","error":null,"startedAt":1792100422,"completedAt":1792100430,"durationMs":7943}]},"model":"gpt-5.6-luna","modelProvider":"openai","serviceTier":null,"disabledPluginIds":[],"cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveResume1649183158/001","runtimeWorkspaceRoots":["/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveResume1649183158/001"],"instructionSources":[],"approvalPolicy":"never","approvalsReviewer":"user","sandbox":{"type":"dangerFullAccess"},"activePermissionProfile":null,"reasoningEffort":null,"collaborationMode":{"mode":"default","settings":{"model":"gpt-5.6-luna","reasoning_effort":null,"developer_instructions":null}},"multiAgentMode":"explicitRequestOnly","initialTurnsPage":null,"turnsBackwardsCursor":"{\"requestedThreadId\":\"01a14182-5438-73c3-99fe-af3f98e47e3a\",\"rolloutOrdinal\":1,\"includeAnchor\":true,\"scope\":{\"kind\":\"turns\"}}","itemsBackwardsCursor":"{\"requestedThreadId\":\"01a14182-5438-73c3-99fe-af3f98e47e3a\",\"rolloutOrdinal\":8,\"includeAnchor\":true,\"scope\":{\"kind\":\"itemsByCreatedAtOrdinal\"}}"}}} -{"dir":"recv","msg":{"method":"thread/tokenUsage/updated","params":{"threadId":"01a14182-5438-73c3-99fe-af3f98e47e3a","turnId":"01a14182-5497-7b31-9d3e-6ba5944fe85c","tokenUsage":{"total":{"totalTokens":13327,"inputTokens":13322,"cachedInputTokens":13056,"cacheWriteInputTokens":0,"outputTokens":5,"reasoningOutputTokens":0},"last":{"totalTokens":13327,"inputTokens":13322,"cachedInputTokens":13056,"cacheWriteInputTokens":0,"outputTokens":5,"reasoningOutputTokens":0},"modelContextWindow":258400}},"emittedAtMs":1792100430850}} -{"dir":"recv","msg":{"method":"thread/goal/cleared","params":{"threadId":"01a14182-5438-73c3-99fe-af3f98e47e3a"},"emittedAtMs":1792100430850}} -{"dir":"send","msg":{"id":3,"method":"turn/start","params":{"input":[{"text":"What was the codeword? Reply with just the codeword.","type":"text"}],"threadId":"01a14182-5438-73c3-99fe-af3f98e47e3a"}}} -{"dir":"recv","msg":{"id":3,"result":{"turn":{"id":"01a14182-7404-76a0-b47f-f140aba1652f","items":[],"itemsView":"notLoaded","status":"inProgress","error":null,"startedAt":null,"completedAt":null,"durationMs":null}}}} -{"dir":"recv","msg":{"method":"thread/status/changed","params":{"threadId":"01a14182-5438-73c3-99fe-af3f98e47e3a","status":{"type":"active","activeFlags":[]}},"emittedAtMs":1792100430856}} -{"dir":"recv","msg":{"method":"turn/started","params":{"threadId":"01a14182-5438-73c3-99fe-af3f98e47e3a","turn":{"id":"01a14182-7404-76a0-b47f-f140aba1652f","items":[],"itemsView":"notLoaded","status":"inProgress","error":null,"startedAt":1792100430,"completedAt":null,"durationMs":null}},"emittedAtMs":1792100430856}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-5438-73c3-99fe-af3f98e47e3a","name":"node_repl","status":"ready","error":null,"failureReason":null},"emittedAtMs":1792100430938}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-5438-73c3-99fe-af3f98e47e3a","name":"cua_repl","status":"ready","error":null,"failureReason":null},"emittedAtMs":1792100430963}} -{"dir":"recv","msg":{"method":"account/updated","params":{"authMode":"chatgpt","planType":"prolite"},"emittedAtMs":1792100431499}} -{"dir":"recv","msg":{"method":"item/started","params":{"item":{"type":"userMessage","id":"01a14182-7dc6-77f2-96ce-15ca0eaedccf","clientId":null,"content":[{"type":"text","text":"What was the codeword? Reply with just the codeword.","text_elements":[]}]},"threadId":"01a14182-5438-73c3-99fe-af3f98e47e3a","turnId":"01a14182-7404-76a0-b47f-f140aba1652f","startedAtMs":1792100433350},"emittedAtMs":1792100433351}} -{"dir":"recv","msg":{"method":"item/completed","params":{"item":{"type":"userMessage","id":"01a14182-7dc6-77f2-96ce-15ca0eaedccf","clientId":null,"content":[{"type":"text","text":"What was the codeword? Reply with just the codeword.","text_elements":[]}]},"threadId":"01a14182-5438-73c3-99fe-af3f98e47e3a","turnId":"01a14182-7404-76a0-b47f-f140aba1652f","completedAtMs":1792100433350},"emittedAtMs":1792100433352}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-5438-73c3-99fe-af3f98e47e3a","name":"codex_apps","status":"ready","error":null,"failureReason":null},"emittedAtMs":1792100433425}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-5438-73c3-99fe-af3f98e47e3a","name":"cua_repl","status":"ready","error":null,"failureReason":null},"emittedAtMs":1792100433425}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-5438-73c3-99fe-af3f98e47e3a","name":"node_repl","status":"ready","error":null,"failureReason":null},"emittedAtMs":1792100433425}} -{"dir":"recv","msg":{"method":"item/started","params":{"item":{"type":"reasoning","id":"rs_00c621cf5abb64d0016ad1485224d8819990635fe6dd8ad9fd","summary":[],"content":[]},"threadId":"01a14182-5438-73c3-99fe-af3f98e47e3a","turnId":"01a14182-7404-76a0-b47f-f140aba1652f","startedAtMs":1792100434261},"emittedAtMs":1792100434261}} -{"dir":"recv","msg":{"method":"item/completed","params":{"item":{"type":"reasoning","id":"rs_00c621cf5abb64d0016ad1485224d8819990635fe6dd8ad9fd","summary":[],"content":[]},"threadId":"01a14182-5438-73c3-99fe-af3f98e47e3a","turnId":"01a14182-7404-76a0-b47f-f140aba1652f","completedAtMs":1792100434261},"emittedAtMs":1792100434262}} -{"dir":"recv","msg":{"method":"item/started","params":{"item":{"type":"agentMessage","id":"msg_00c621cf5abb64d0016ad1485235e081998b3775bc1d1ff57a","text":"","phase":"final_answer","memoryCitation":null,"delivery":null,"questions":null},"threadId":"01a14182-5438-73c3-99fe-af3f98e47e3a","turnId":"01a14182-7404-76a0-b47f-f140aba1652f","startedAtMs":1792100434352},"emittedAtMs":1792100434352}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-5438-73c3-99fe-af3f98e47e3a","turnId":"01a14182-7404-76a0-b47f-f140aba1652f","itemId":"msg_00c621cf5abb64d0016ad1485235e081998b3775bc1d1ff57a","delta":"AZ"},"emittedAtMs":1792100434352}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-5438-73c3-99fe-af3f98e47e3a","turnId":"01a14182-7404-76a0-b47f-f140aba1652f","itemId":"msg_00c621cf5abb64d0016ad1485235e081998b3775bc1d1ff57a","delta":"URE"},"emittedAtMs":1792100434352}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-5438-73c3-99fe-af3f98e47e3a","turnId":"01a14182-7404-76a0-b47f-f140aba1652f","itemId":"msg_00c621cf5abb64d0016ad1485235e081998b3775bc1d1ff57a","delta":"-"},"emittedAtMs":1792100434357}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-5438-73c3-99fe-af3f98e47e3a","turnId":"01a14182-7404-76a0-b47f-f140aba1652f","itemId":"msg_00c621cf5abb64d0016ad1485235e081998b3775bc1d1ff57a","delta":"17"},"emittedAtMs":1792100434357}} -{"dir":"recv","msg":{"method":"item/completed","params":{"item":{"type":"agentMessage","id":"msg_00c621cf5abb64d0016ad1485235e081998b3775bc1d1ff57a","text":"AZURE-17","phase":"final_answer","memoryCitation":null,"delivery":null,"questions":null},"threadId":"01a14182-5438-73c3-99fe-af3f98e47e3a","turnId":"01a14182-7404-76a0-b47f-f140aba1652f","completedAtMs":1792100434357},"emittedAtMs":1792100434358}} -{"dir":"recv","msg":{"method":"thread/tokenUsage/updated","params":{"threadId":"01a14182-5438-73c3-99fe-af3f98e47e3a","turnId":"01a14182-7404-76a0-b47f-f140aba1652f","tokenUsage":{"total":{"totalTokens":26693,"inputTokens":26668,"cachedInputTokens":26112,"cacheWriteInputTokens":0,"outputTokens":25,"reasoningOutputTokens":10},"last":{"totalTokens":13366,"inputTokens":13346,"cachedInputTokens":13056,"cacheWriteInputTokens":0,"outputTokens":20,"reasoningOutputTokens":10},"modelContextWindow":258400}},"emittedAtMs":1792100434405}} -{"dir":"recv","msg":{"method":"account/rateLimits/updated","params":{"rateLimits":{"limitId":"codex","limitName":null,"normalModelSlug":null,"primary":{"usedPercent":0,"windowDurationMins":10080,"resetsAt":1792701205},"secondary":null,"credits":{"hasCredits":false,"unlimited":false,"balance":"0"},"individualLimit":null,"spendControlReached":null,"planType":"prolite","rateLimitReachedType":null}},"emittedAtMs":1792100434405}} -{"dir":"recv","msg":{"method":"thread/status/changed","params":{"threadId":"01a14182-5438-73c3-99fe-af3f98e47e3a","status":{"type":"idle"}},"emittedAtMs":1792100434408}} -{"dir":"recv","msg":{"method":"turn/completed","params":{"threadId":"01a14182-5438-73c3-99fe-af3f98e47e3a","turn":{"id":"01a14182-7404-76a0-b47f-f140aba1652f","items":[{"type":"agentMessage","id":"msg_00c621cf5abb64d0016ad1485235e081998b3775bc1d1ff57a","text":"AZURE-17","phase":"final_answer","memoryCitation":null,"delivery":null,"questions":null}],"itemsView":"summary","status":"completed","error":null,"startedAt":1792100430,"completedAt":1792100434,"durationMs":3553}},"emittedAtMs":1792100434408}} diff --git a/internal/codex/testdata/contract/Steer.jsonl b/internal/codex/testdata/contract/Steer.jsonl deleted file mode 100644 index bf54be3..0000000 --- a/internal/codex/testdata/contract/Steer.jsonl +++ /dev/null @@ -1,53 +0,0 @@ -{"dir":"send","msg":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true},"clientInfo":{"name":"byre","title":"byre","version":"0.0.0"}}}} -{"dir":"recv","msg":{"id":1,"result":{"userAgent":"byre/0.159.0 (Mac OS 27.0.1; arm64) unknown (byre; 0.0.0)","codexHome":"/Users/flamingcow/.codex","platformFamily":"unix","platformOs":"macos"}}} -{"dir":"recv","msg":{"method":"remoteControl/status/changed","params":{"status":"disabled","serverName":"roc.tailcecab.ts.net","installationId":"7b5574fb-530f-4cdf-925d-13b538911a88","environmentId":null},"emittedAtMs":1792100422683}} -{"dir":"send","msg":{"method":"initialized"}} -{"dir":"send","msg":{"id":2,"method":"thread/start","params":{"approvalPolicy":"never","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveSteer3091654996/001","model":"gpt-5.6-luna","sandbox":"danger-full-access"}}} -{"dir":"recv","msg":{"id":2,"result":{"thread":{"id":"01a14182-5431-7ec1-9f51-a8e24cc9c71c","environments":[{"environmentId":"local","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveSteer3091654996/001","runtimeWorkspaceRoots":["/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveSteer3091654996/001"]}],"extra":null,"sessionId":"01a14182-5431-7ec1-9f51-a8e24cc9c71c","forkedFromId":null,"parentThreadId":null,"preview":"","ephemeral":false,"section":null,"sectionEnteredAt":null,"projectId":null,"historyMode":"paginated","modelProvider":"openai","model":"gpt-5.6-luna","reasoningEffort":null,"createdAt":1792100422,"updatedAt":1792100422,"recencyAt":1792100422,"status":{"type":"idle"},"path":"/Users/flamingcow/.codex/sessions/2026/10/15/rollout-2026-10-15T14-40-22-01a14182-5431-7ec1-9f51-a8e24cc9c71c.jsonl","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveSteer3091654996/001","cliVersion":"0.159.0","originator":"byre","source":"vscode","canAcceptDirectInput":true,"threadSource":null,"agentNickname":null,"agentRole":null,"gitInfo":null,"name":null,"daybreakEnabled":null,"turns":[]},"model":"gpt-5.6-luna","modelProvider":"openai","serviceTier":null,"disabledPluginIds":[],"cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveSteer3091654996/001","runtimeWorkspaceRoots":["/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveSteer3091654996/001"],"instructionSources":[],"approvalPolicy":"never","approvalsReviewer":"user","sandbox":{"type":"dangerFullAccess"},"activePermissionProfile":null,"reasoningEffort":null,"multiAgentMode":"explicitRequestOnly"}}} -{"dir":"send","msg":{"id":3,"method":"turn/start","params":{"input":[{"text":"Run the shell command `sleep 5 \u0026\u0026 echo finished`, then reply with what it printed.","type":"text"}],"threadId":"01a14182-5431-7ec1-9f51-a8e24cc9c71c"}}} -{"dir":"recv","msg":{"method":"thread/started","params":{"thread":{"id":"01a14182-5431-7ec1-9f51-a8e24cc9c71c","environments":[{"environmentId":"local","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveSteer3091654996/001","runtimeWorkspaceRoots":["/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveSteer3091654996/001"]}],"extra":null,"sessionId":"01a14182-5431-7ec1-9f51-a8e24cc9c71c","forkedFromId":null,"parentThreadId":null,"preview":"","ephemeral":false,"section":null,"sectionEnteredAt":null,"projectId":null,"historyMode":"paginated","modelProvider":"openai","model":"gpt-5.6-luna","reasoningEffort":null,"createdAt":1792100422,"updatedAt":1792100422,"recencyAt":1792100422,"status":{"type":"idle"},"path":"/Users/flamingcow/.codex/sessions/2026/10/15/rollout-2026-10-15T14-40-22-01a14182-5431-7ec1-9f51-a8e24cc9c71c.jsonl","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveSteer3091654996/001","cliVersion":"0.159.0","originator":"byre","source":"vscode","canAcceptDirectInput":true,"threadSource":null,"agentNickname":null,"agentRole":null,"gitInfo":null,"name":null,"daybreakEnabled":null,"turns":[]}},"emittedAtMs":1792100422828}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-5431-7ec1-9f51-a8e24cc9c71c","name":"cua_repl","status":"starting","error":null,"failureReason":null},"emittedAtMs":1792100422828}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-5431-7ec1-9f51-a8e24cc9c71c","name":"codex_apps","status":"starting","error":null,"failureReason":null},"emittedAtMs":1792100422828}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-5431-7ec1-9f51-a8e24cc9c71c","name":"node_repl","status":"starting","error":null,"failureReason":null},"emittedAtMs":1792100422828}} -{"dir":"recv","msg":{"id":3,"result":{"turn":{"id":"01a14182-54ae-7400-906f-b5aacb242e8d","items":[],"itemsView":"notLoaded","status":"inProgress","error":null,"startedAt":null,"completedAt":null,"durationMs":null}}}} -{"dir":"recv","msg":{"method":"thread/status/changed","params":{"threadId":"01a14182-5431-7ec1-9f51-a8e24cc9c71c","status":{"type":"active","activeFlags":[]}},"emittedAtMs":1792100422924}} -{"dir":"recv","msg":{"method":"turn/started","params":{"threadId":"01a14182-5431-7ec1-9f51-a8e24cc9c71c","turn":{"id":"01a14182-54ae-7400-906f-b5aacb242e8d","items":[],"itemsView":"notLoaded","status":"inProgress","error":null,"startedAt":1792100422,"completedAt":null,"durationMs":null}},"emittedAtMs":1792100422924}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-5431-7ec1-9f51-a8e24cc9c71c","name":"node_repl","status":"ready","error":null,"failureReason":null},"emittedAtMs":1792100423399}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-5431-7ec1-9f51-a8e24cc9c71c","name":"cua_repl","status":"ready","error":null,"failureReason":null},"emittedAtMs":1792100423459}} -{"dir":"recv","msg":{"method":"account/updated","params":{"authMode":"chatgpt","planType":"prolite"},"emittedAtMs":1792100423497}} -{"dir":"recv","msg":{"method":"item/started","params":{"item":{"type":"userMessage","id":"01a14182-6e1f-7610-8bee-d6d01dee9f24","clientId":null,"content":[{"type":"text","text":"Run the shell command `sleep 5 \u0026\u0026 echo finished`, then reply with what it printed.","text_elements":[]}]},"threadId":"01a14182-5431-7ec1-9f51-a8e24cc9c71c","turnId":"01a14182-54ae-7400-906f-b5aacb242e8d","startedAtMs":1792100429343},"emittedAtMs":1792100429343}} -{"dir":"recv","msg":{"method":"item/completed","params":{"item":{"type":"userMessage","id":"01a14182-6e1f-7610-8bee-d6d01dee9f24","clientId":null,"content":[{"type":"text","text":"Run the shell command `sleep 5 \u0026\u0026 echo finished`, then reply with what it printed.","text_elements":[]}]},"threadId":"01a14182-5431-7ec1-9f51-a8e24cc9c71c","turnId":"01a14182-54ae-7400-906f-b5aacb242e8d","completedAtMs":1792100429343},"emittedAtMs":1792100429345}} -{"dir":"recv","msg":{"method":"item/started","params":{"item":{"type":"reasoning","id":"rs_0de8b619515ae76c016ad1484e0b748198b38be3f8f395d9d3","summary":[],"content":[]},"threadId":"01a14182-5431-7ec1-9f51-a8e24cc9c71c","turnId":"01a14182-54ae-7400-906f-b5aacb242e8d","startedAtMs":1792100430326},"emittedAtMs":1792100430326}} -{"dir":"recv","msg":{"method":"item/completed","params":{"item":{"type":"reasoning","id":"rs_0de8b619515ae76c016ad1484e0b748198b38be3f8f395d9d3","summary":[],"content":[]},"threadId":"01a14182-5431-7ec1-9f51-a8e24cc9c71c","turnId":"01a14182-54ae-7400-906f-b5aacb242e8d","completedAtMs":1792100430328},"emittedAtMs":1792100430329}} -{"dir":"recv","msg":{"method":"item/started","params":{"item":{"type":"agentMessage","id":"msg_0de8b619515ae76c016ad1484e1b2881989e99c98313af8186","text":"","phase":"commentary","memoryCitation":null,"delivery":null,"questions":null},"threadId":"01a14182-5431-7ec1-9f51-a8e24cc9c71c","turnId":"01a14182-54ae-7400-906f-b5aacb242e8d","startedAtMs":1792100430377},"emittedAtMs":1792100430377}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-5431-7ec1-9f51-a8e24cc9c71c","turnId":"01a14182-54ae-7400-906f-b5aacb242e8d","itemId":"msg_0de8b619515ae76c016ad1484e1b2881989e99c98313af8186","delta":"I"},"emittedAtMs":1792100430377}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-5431-7ec1-9f51-a8e24cc9c71c","turnId":"01a14182-54ae-7400-906f-b5aacb242e8d","itemId":"msg_0de8b619515ae76c016ad1484e1b2881989e99c98313af8186","delta":"’m"},"emittedAtMs":1792100430430}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-5431-7ec1-9f51-a8e24cc9c71c","turnId":"01a14182-54ae-7400-906f-b5aacb242e8d","itemId":"msg_0de8b619515ae76c016ad1484e1b2881989e99c98313af8186","delta":" running"},"emittedAtMs":1792100430430}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-5431-7ec1-9f51-a8e24cc9c71c","turnId":"01a14182-54ae-7400-906f-b5aacb242e8d","itemId":"msg_0de8b619515ae76c016ad1484e1b2881989e99c98313af8186","delta":" the"},"emittedAtMs":1792100430430}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-5431-7ec1-9f51-a8e24cc9c71c","turnId":"01a14182-54ae-7400-906f-b5aacb242e8d","itemId":"msg_0de8b619515ae76c016ad1484e1b2881989e99c98313af8186","delta":" command"},"emittedAtMs":1792100430434}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-5431-7ec1-9f51-a8e24cc9c71c","turnId":"01a14182-54ae-7400-906f-b5aacb242e8d","itemId":"msg_0de8b619515ae76c016ad1484e1b2881989e99c98313af8186","delta":" now"},"emittedAtMs":1792100430434}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-5431-7ec1-9f51-a8e24cc9c71c","turnId":"01a14182-54ae-7400-906f-b5aacb242e8d","itemId":"msg_0de8b619515ae76c016ad1484e1b2881989e99c98313af8186","delta":"."},"emittedAtMs":1792100430434}} -{"dir":"recv","msg":{"method":"item/completed","params":{"item":{"type":"agentMessage","id":"msg_0de8b619515ae76c016ad1484e1b2881989e99c98313af8186","text":"I’m running the command now.","phase":"commentary","memoryCitation":null,"delivery":null,"questions":null},"threadId":"01a14182-5431-7ec1-9f51-a8e24cc9c71c","turnId":"01a14182-54ae-7400-906f-b5aacb242e8d","completedAtMs":1792100430434},"emittedAtMs":1792100430435}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-5431-7ec1-9f51-a8e24cc9c71c","name":"codex_apps","status":"ready","error":null,"failureReason":null},"emittedAtMs":1792100430711}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-5431-7ec1-9f51-a8e24cc9c71c","name":"node_repl","status":"ready","error":null,"failureReason":null},"emittedAtMs":1792100430711}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-5431-7ec1-9f51-a8e24cc9c71c","name":"cua_repl","status":"ready","error":null,"failureReason":null},"emittedAtMs":1792100430711}} -{"dir":"recv","msg":{"method":"item/started","params":{"item":{"type":"commandExecution","id":"exec-ce083497-01e2-4153-bfe1-b12777465697","pluginId":null,"scriptPath":null,"command":"/bin/zsh -lc 'sleep 5 \u0026\u0026 echo finished'","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveSteer3091654996/001","processId":"75465","source":"unifiedExecStartup","status":"inProgress","commandActions":[{"type":"unknown","command":"sleep 5 \u0026\u0026 echo finished"}],"aggregatedOutput":null,"exitCode":null,"durationMs":null},"threadId":"01a14182-5431-7ec1-9f51-a8e24cc9c71c","turnId":"01a14182-54ae-7400-906f-b5aacb242e8d","startedAtMs":1792100431001},"emittedAtMs":1792100431002}} -{"dir":"send","msg":{"id":4,"method":"turn/steer","params":{"expectedTurnId":"01a14182-54ae-7400-906f-b5aacb242e8d","input":[{"text":"Also end your reply with the word PELICAN.","type":"text"}],"threadId":"01a14182-5431-7ec1-9f51-a8e24cc9c71c"}}} -{"dir":"recv","msg":{"id":4,"result":{"turnId":"01a14182-54ae-7400-906f-b5aacb242e8d"}}} -{"dir":"recv","msg":{"method":"item/commandExecution/outputDelta","params":{"threadId":"01a14182-5431-7ec1-9f51-a8e24cc9c71c","turnId":"01a14182-54ae-7400-906f-b5aacb242e8d","itemId":"exec-ce083497-01e2-4153-bfe1-b12777465697","delta":"finished\n"},"emittedAtMs":1792100436011}} -{"dir":"recv","msg":{"method":"item/completed","params":{"item":{"type":"commandExecution","id":"exec-ce083497-01e2-4153-bfe1-b12777465697","pluginId":null,"scriptPath":null,"command":"/bin/zsh -lc 'sleep 5 \u0026\u0026 echo finished'","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveSteer3091654996/001","processId":"75465","source":"unifiedExecStartup","status":"completed","commandActions":[{"type":"unknown","command":"sleep 5 \u0026\u0026 echo finished"}],"aggregatedOutput":"finished\n","exitCode":0,"durationMs":5010},"threadId":"01a14182-5431-7ec1-9f51-a8e24cc9c71c","turnId":"01a14182-54ae-7400-906f-b5aacb242e8d","completedAtMs":1792100436012},"emittedAtMs":1792100436014}} -{"dir":"recv","msg":{"method":"thread/tokenUsage/updated","params":{"threadId":"01a14182-5431-7ec1-9f51-a8e24cc9c71c","turnId":"01a14182-54ae-7400-906f-b5aacb242e8d","tokenUsage":{"total":{"totalTokens":13437,"inputTokens":13327,"cachedInputTokens":13056,"cacheWriteInputTokens":0,"outputTokens":110,"reasoningOutputTokens":14},"last":{"totalTokens":13437,"inputTokens":13327,"cachedInputTokens":13056,"cacheWriteInputTokens":0,"outputTokens":110,"reasoningOutputTokens":14},"modelContextWindow":258400}},"emittedAtMs":1792100436018}} -{"dir":"recv","msg":{"method":"account/rateLimits/updated","params":{"rateLimits":{"limitId":"codex","limitName":null,"normalModelSlug":null,"primary":{"usedPercent":0,"windowDurationMins":10080,"resetsAt":1792701205},"secondary":null,"credits":{"hasCredits":false,"unlimited":false,"balance":"0"},"individualLimit":null,"spendControlReached":null,"planType":"prolite","rateLimitReachedType":null}},"emittedAtMs":1792100436018}} -{"dir":"recv","msg":{"method":"item/started","params":{"item":{"type":"userMessage","id":"01a14182-8832-7972-88cb-ca5438199026","clientId":null,"content":[{"type":"text","text":"Also end your reply with the word PELICAN.","text_elements":[]}]},"threadId":"01a14182-5431-7ec1-9f51-a8e24cc9c71c","turnId":"01a14182-54ae-7400-906f-b5aacb242e8d","startedAtMs":1792100436019},"emittedAtMs":1792100436019}} -{"dir":"recv","msg":{"method":"item/completed","params":{"item":{"type":"userMessage","id":"01a14182-8832-7972-88cb-ca5438199026","clientId":null,"content":[{"type":"text","text":"Also end your reply with the word PELICAN.","text_elements":[]}]},"threadId":"01a14182-5431-7ec1-9f51-a8e24cc9c71c","turnId":"01a14182-54ae-7400-906f-b5aacb242e8d","completedAtMs":1792100436019},"emittedAtMs":1792100436021}} -{"dir":"recv","msg":{"method":"item/started","params":{"item":{"type":"agentMessage","id":"msg_0de8b619515ae76c016ad14854cf048198bdac9c205ab9c60f","text":"","phase":"final_answer","memoryCitation":null,"delivery":null,"questions":null},"threadId":"01a14182-5431-7ec1-9f51-a8e24cc9c71c","turnId":"01a14182-54ae-7400-906f-b5aacb242e8d","startedAtMs":1792100436897},"emittedAtMs":1792100436897}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-5431-7ec1-9f51-a8e24cc9c71c","turnId":"01a14182-54ae-7400-906f-b5aacb242e8d","itemId":"msg_0de8b619515ae76c016ad14854cf048198bdac9c205ab9c60f","delta":"finished"},"emittedAtMs":1792100436897}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-5431-7ec1-9f51-a8e24cc9c71c","turnId":"01a14182-54ae-7400-906f-b5aacb242e8d","itemId":"msg_0de8b619515ae76c016ad14854cf048198bdac9c205ab9c60f","delta":"\n\n"},"emittedAtMs":1792100436897}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-5431-7ec1-9f51-a8e24cc9c71c","turnId":"01a14182-54ae-7400-906f-b5aacb242e8d","itemId":"msg_0de8b619515ae76c016ad14854cf048198bdac9c205ab9c60f","delta":"PE"},"emittedAtMs":1792100436903}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-5431-7ec1-9f51-a8e24cc9c71c","turnId":"01a14182-54ae-7400-906f-b5aacb242e8d","itemId":"msg_0de8b619515ae76c016ad14854cf048198bdac9c205ab9c60f","delta":"LIC"},"emittedAtMs":1792100436903}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-5431-7ec1-9f51-a8e24cc9c71c","turnId":"01a14182-54ae-7400-906f-b5aacb242e8d","itemId":"msg_0de8b619515ae76c016ad14854cf048198bdac9c205ab9c60f","delta":"AN"},"emittedAtMs":1792100436903}} -{"dir":"recv","msg":{"method":"item/completed","params":{"item":{"type":"agentMessage","id":"msg_0de8b619515ae76c016ad14854cf048198bdac9c205ab9c60f","text":"finished\n\nPELICAN","phase":"final_answer","memoryCitation":null,"delivery":null,"questions":null},"threadId":"01a14182-5431-7ec1-9f51-a8e24cc9c71c","turnId":"01a14182-54ae-7400-906f-b5aacb242e8d","completedAtMs":1792100436903},"emittedAtMs":1792100436905}} -{"dir":"recv","msg":{"method":"thread/tokenUsage/updated","params":{"threadId":"01a14182-5431-7ec1-9f51-a8e24cc9c71c","turnId":"01a14182-54ae-7400-906f-b5aacb242e8d","tokenUsage":{"total":{"totalTokens":26924,"inputTokens":26805,"cachedInputTokens":24064,"cacheWriteInputTokens":0,"outputTokens":119,"reasoningOutputTokens":14},"last":{"totalTokens":13487,"inputTokens":13478,"cachedInputTokens":11008,"cacheWriteInputTokens":0,"outputTokens":9,"reasoningOutputTokens":0},"modelContextWindow":258400}},"emittedAtMs":1792100436982}} -{"dir":"recv","msg":{"method":"account/rateLimits/updated","params":{"rateLimits":{"limitId":"codex","limitName":null,"normalModelSlug":null,"primary":{"usedPercent":0,"windowDurationMins":10080,"resetsAt":1792701205},"secondary":null,"credits":{"hasCredits":false,"unlimited":false,"balance":"0"},"individualLimit":null,"spendControlReached":null,"planType":"prolite","rateLimitReachedType":null}},"emittedAtMs":1792100436982}} -{"dir":"recv","msg":{"method":"thread/status/changed","params":{"threadId":"01a14182-5431-7ec1-9f51-a8e24cc9c71c","status":{"type":"idle"}},"emittedAtMs":1792100436987}} -{"dir":"recv","msg":{"method":"turn/completed","params":{"threadId":"01a14182-5431-7ec1-9f51-a8e24cc9c71c","turn":{"id":"01a14182-54ae-7400-906f-b5aacb242e8d","items":[{"type":"agentMessage","id":"msg_0de8b619515ae76c016ad14854cf048198bdac9c205ab9c60f","text":"finished\n\nPELICAN","phase":"final_answer","memoryCitation":null,"delivery":null,"questions":null}],"itemsView":"summary","status":"completed","error":null,"startedAt":1792100422,"completedAt":1792100436,"durationMs":14152}},"emittedAtMs":1792100436987}} diff --git a/internal/codex/testdata/contract/TextDeltas.jsonl b/internal/codex/testdata/contract/TextDeltas.jsonl deleted file mode 100644 index 214e61c..0000000 --- a/internal/codex/testdata/contract/TextDeltas.jsonl +++ /dev/null @@ -1,47 +0,0 @@ -{"dir":"send","msg":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true},"clientInfo":{"name":"byre","title":"byre","version":"0.0.0"}}}} -{"dir":"recv","msg":{"id":1,"result":{"userAgent":"byre/0.159.0 (Mac OS 27.0.1; arm64) unknown (byre; 0.0.0)","codexHome":"/Users/flamingcow/.codex","platformFamily":"unix","platformOs":"macos"}}} -{"dir":"send","msg":{"method":"initialized"}} -{"dir":"send","msg":{"id":2,"method":"thread/start","params":{"approvalPolicy":"never","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveTextDeltas1472509376/001","model":"gpt-5.6-luna","sandbox":"danger-full-access"}}} -{"dir":"recv","msg":{"method":"remoteControl/status/changed","params":{"status":"disabled","serverName":"roc.tailcecab.ts.net","installationId":"7b5574fb-530f-4cdf-925d-13b538911a88","environmentId":null},"emittedAtMs":1792100422680}} -{"dir":"recv","msg":{"id":2,"result":{"thread":{"id":"01a14182-5426-7881-8ca4-83eff65a4c2d","environments":[{"environmentId":"local","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveTextDeltas1472509376/001","runtimeWorkspaceRoots":["/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveTextDeltas1472509376/001"]}],"extra":null,"sessionId":"01a14182-5426-7881-8ca4-83eff65a4c2d","forkedFromId":null,"parentThreadId":null,"preview":"","ephemeral":false,"section":null,"sectionEnteredAt":null,"projectId":null,"historyMode":"paginated","modelProvider":"openai","model":"gpt-5.6-luna","reasoningEffort":null,"createdAt":1792100422,"updatedAt":1792100422,"recencyAt":1792100422,"status":{"type":"idle"},"path":"/Users/flamingcow/.codex/sessions/2026/10/15/rollout-2026-10-15T14-40-22-01a14182-5426-7881-8ca4-83eff65a4c2d.jsonl","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveTextDeltas1472509376/001","cliVersion":"0.159.0","originator":"byre","source":"vscode","canAcceptDirectInput":true,"threadSource":null,"agentNickname":null,"agentRole":null,"gitInfo":null,"name":null,"daybreakEnabled":null,"turns":[]},"model":"gpt-5.6-luna","modelProvider":"openai","serviceTier":null,"disabledPluginIds":[],"cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveTextDeltas1472509376/001","runtimeWorkspaceRoots":["/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveTextDeltas1472509376/001"],"instructionSources":[],"approvalPolicy":"never","approvalsReviewer":"user","sandbox":{"type":"dangerFullAccess"},"activePermissionProfile":null,"reasoningEffort":null,"multiAgentMode":"explicitRequestOnly"}}} -{"dir":"recv","msg":{"method":"thread/started","params":{"thread":{"id":"01a14182-5426-7881-8ca4-83eff65a4c2d","environments":[{"environmentId":"local","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveTextDeltas1472509376/001","runtimeWorkspaceRoots":["/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveTextDeltas1472509376/001"]}],"extra":null,"sessionId":"01a14182-5426-7881-8ca4-83eff65a4c2d","forkedFromId":null,"parentThreadId":null,"preview":"","ephemeral":false,"section":null,"sectionEnteredAt":null,"projectId":null,"historyMode":"paginated","modelProvider":"openai","model":"gpt-5.6-luna","reasoningEffort":null,"createdAt":1792100422,"updatedAt":1792100422,"recencyAt":1792100422,"status":{"type":"idle"},"path":"/Users/flamingcow/.codex/sessions/2026/10/15/rollout-2026-10-15T14-40-22-01a14182-5426-7881-8ca4-83eff65a4c2d.jsonl","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveTextDeltas1472509376/001","cliVersion":"0.159.0","originator":"byre","source":"vscode","canAcceptDirectInput":true,"threadSource":null,"agentNickname":null,"agentRole":null,"gitInfo":null,"name":null,"daybreakEnabled":null,"turns":[]}},"emittedAtMs":1792100422874}} -{"dir":"send","msg":{"id":3,"method":"turn/start","params":{"input":[{"text":"Without using any tools, count from one to twenty in words, separated by spaces.","type":"text"}],"threadId":"01a14182-5426-7881-8ca4-83eff65a4c2d"}}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-5426-7881-8ca4-83eff65a4c2d","name":"node_repl","status":"starting","error":null,"failureReason":null},"emittedAtMs":1792100422874}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-5426-7881-8ca4-83eff65a4c2d","name":"cua_repl","status":"starting","error":null,"failureReason":null},"emittedAtMs":1792100422874}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-5426-7881-8ca4-83eff65a4c2d","name":"codex_apps","status":"starting","error":null,"failureReason":null},"emittedAtMs":1792100422874}} -{"dir":"recv","msg":{"id":3,"result":{"turn":{"id":"01a14182-54df-7e92-8acd-3010871cd4d8","items":[],"itemsView":"notLoaded","status":"inProgress","error":null,"startedAt":null,"completedAt":null,"durationMs":null}}}} -{"dir":"recv","msg":{"method":"thread/status/changed","params":{"threadId":"01a14182-5426-7881-8ca4-83eff65a4c2d","status":{"type":"active","activeFlags":[]}},"emittedAtMs":1792100422928}} -{"dir":"recv","msg":{"method":"turn/started","params":{"threadId":"01a14182-5426-7881-8ca4-83eff65a4c2d","turn":{"id":"01a14182-54df-7e92-8acd-3010871cd4d8","items":[],"itemsView":"notLoaded","status":"inProgress","error":null,"startedAt":1792100422,"completedAt":null,"durationMs":null}},"emittedAtMs":1792100422928}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-5426-7881-8ca4-83eff65a4c2d","name":"node_repl","status":"ready","error":null,"failureReason":null},"emittedAtMs":1792100423402}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-5426-7881-8ca4-83eff65a4c2d","name":"cua_repl","status":"ready","error":null,"failureReason":null},"emittedAtMs":1792100423449}} -{"dir":"recv","msg":{"method":"account/updated","params":{"authMode":"chatgpt","planType":"prolite"},"emittedAtMs":1792100423509}} -{"dir":"recv","msg":{"method":"item/started","params":{"item":{"type":"userMessage","id":"01a14182-6e69-73a0-a34c-c0aef3c0dc2d","clientId":null,"content":[{"type":"text","text":"Without using any tools, count from one to twenty in words, separated by spaces.","text_elements":[]}]},"threadId":"01a14182-5426-7881-8ca4-83eff65a4c2d","turnId":"01a14182-54df-7e92-8acd-3010871cd4d8","startedAtMs":1792100429417},"emittedAtMs":1792100429417}} -{"dir":"recv","msg":{"method":"item/completed","params":{"item":{"type":"userMessage","id":"01a14182-6e69-73a0-a34c-c0aef3c0dc2d","clientId":null,"content":[{"type":"text","text":"Without using any tools, count from one to twenty in words, separated by spaces.","text_elements":[]}]},"threadId":"01a14182-5426-7881-8ca4-83eff65a4c2d","turnId":"01a14182-54df-7e92-8acd-3010871cd4d8","completedAtMs":1792100429417},"emittedAtMs":1792100429418}} -{"dir":"recv","msg":{"method":"item/started","params":{"item":{"type":"reasoning","id":"rs_08c26fdb702799b1016ad1484ec3ac819aab40f691774d0b30","summary":[],"content":[]},"threadId":"01a14182-5426-7881-8ca4-83eff65a4c2d","turnId":"01a14182-54df-7e92-8acd-3010871cd4d8","startedAtMs":1792100431346},"emittedAtMs":1792100431346}} -{"dir":"recv","msg":{"method":"item/completed","params":{"item":{"type":"reasoning","id":"rs_08c26fdb702799b1016ad1484ec3ac819aab40f691774d0b30","summary":[],"content":[]},"threadId":"01a14182-5426-7881-8ca4-83eff65a4c2d","turnId":"01a14182-54df-7e92-8acd-3010871cd4d8","completedAtMs":1792100431346},"emittedAtMs":1792100431347}} -{"dir":"recv","msg":{"method":"item/started","params":{"item":{"type":"agentMessage","id":"msg_08c26fdb702799b1016ad1484f4784819a88af67651de2880a","text":"","phase":"final_answer","memoryCitation":null,"delivery":null,"questions":null},"threadId":"01a14182-5426-7881-8ca4-83eff65a4c2d","turnId":"01a14182-54df-7e92-8acd-3010871cd4d8","startedAtMs":1792100431376},"emittedAtMs":1792100431376}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-5426-7881-8ca4-83eff65a4c2d","turnId":"01a14182-54df-7e92-8acd-3010871cd4d8","itemId":"msg_08c26fdb702799b1016ad1484f4784819a88af67651de2880a","delta":"one"},"emittedAtMs":1792100431412}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-5426-7881-8ca4-83eff65a4c2d","turnId":"01a14182-54df-7e92-8acd-3010871cd4d8","itemId":"msg_08c26fdb702799b1016ad1484f4784819a88af67651de2880a","delta":" two"},"emittedAtMs":1792100431412}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-5426-7881-8ca4-83eff65a4c2d","turnId":"01a14182-54df-7e92-8acd-3010871cd4d8","itemId":"msg_08c26fdb702799b1016ad1484f4784819a88af67651de2880a","delta":" three"},"emittedAtMs":1792100431586}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-5426-7881-8ca4-83eff65a4c2d","turnId":"01a14182-54df-7e92-8acd-3010871cd4d8","itemId":"msg_08c26fdb702799b1016ad1484f4784819a88af67651de2880a","delta":" four"},"emittedAtMs":1792100431586}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-5426-7881-8ca4-83eff65a4c2d","turnId":"01a14182-54df-7e92-8acd-3010871cd4d8","itemId":"msg_08c26fdb702799b1016ad1484f4784819a88af67651de2880a","delta":" five"},"emittedAtMs":1792100431586}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-5426-7881-8ca4-83eff65a4c2d","turnId":"01a14182-54df-7e92-8acd-3010871cd4d8","itemId":"msg_08c26fdb702799b1016ad1484f4784819a88af67651de2880a","delta":" six"},"emittedAtMs":1792100431586}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-5426-7881-8ca4-83eff65a4c2d","turnId":"01a14182-54df-7e92-8acd-3010871cd4d8","itemId":"msg_08c26fdb702799b1016ad1484f4784819a88af67651de2880a","delta":" seven"},"emittedAtMs":1792100431586}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-5426-7881-8ca4-83eff65a4c2d","turnId":"01a14182-54df-7e92-8acd-3010871cd4d8","itemId":"msg_08c26fdb702799b1016ad1484f4784819a88af67651de2880a","delta":" eight"},"emittedAtMs":1792100431586}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-5426-7881-8ca4-83eff65a4c2d","turnId":"01a14182-54df-7e92-8acd-3010871cd4d8","itemId":"msg_08c26fdb702799b1016ad1484f4784819a88af67651de2880a","delta":" nine"},"emittedAtMs":1792100431586}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-5426-7881-8ca4-83eff65a4c2d","turnId":"01a14182-54df-7e92-8acd-3010871cd4d8","itemId":"msg_08c26fdb702799b1016ad1484f4784819a88af67651de2880a","delta":" ten"},"emittedAtMs":1792100431586}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-5426-7881-8ca4-83eff65a4c2d","turnId":"01a14182-54df-7e92-8acd-3010871cd4d8","itemId":"msg_08c26fdb702799b1016ad1484f4784819a88af67651de2880a","delta":" eleven"},"emittedAtMs":1792100431586}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-5426-7881-8ca4-83eff65a4c2d","turnId":"01a14182-54df-7e92-8acd-3010871cd4d8","itemId":"msg_08c26fdb702799b1016ad1484f4784819a88af67651de2880a","delta":" twelve"},"emittedAtMs":1792100431586}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-5426-7881-8ca4-83eff65a4c2d","turnId":"01a14182-54df-7e92-8acd-3010871cd4d8","itemId":"msg_08c26fdb702799b1016ad1484f4784819a88af67651de2880a","delta":" thirteen"},"emittedAtMs":1792100431615}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-5426-7881-8ca4-83eff65a4c2d","turnId":"01a14182-54df-7e92-8acd-3010871cd4d8","itemId":"msg_08c26fdb702799b1016ad1484f4784819a88af67651de2880a","delta":" fourteen"},"emittedAtMs":1792100431615}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-5426-7881-8ca4-83eff65a4c2d","turnId":"01a14182-54df-7e92-8acd-3010871cd4d8","itemId":"msg_08c26fdb702799b1016ad1484f4784819a88af67651de2880a","delta":" fifteen"},"emittedAtMs":1792100431615}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-5426-7881-8ca4-83eff65a4c2d","turnId":"01a14182-54df-7e92-8acd-3010871cd4d8","itemId":"msg_08c26fdb702799b1016ad1484f4784819a88af67651de2880a","delta":" sixteen"},"emittedAtMs":1792100431615}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-5426-7881-8ca4-83eff65a4c2d","turnId":"01a14182-54df-7e92-8acd-3010871cd4d8","itemId":"msg_08c26fdb702799b1016ad1484f4784819a88af67651de2880a","delta":" seventeen"},"emittedAtMs":1792100431615}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-5426-7881-8ca4-83eff65a4c2d","turnId":"01a14182-54df-7e92-8acd-3010871cd4d8","itemId":"msg_08c26fdb702799b1016ad1484f4784819a88af67651de2880a","delta":" eighteen"},"emittedAtMs":1792100431615}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-5426-7881-8ca4-83eff65a4c2d","turnId":"01a14182-54df-7e92-8acd-3010871cd4d8","itemId":"msg_08c26fdb702799b1016ad1484f4784819a88af67651de2880a","delta":" nineteen"},"emittedAtMs":1792100431615}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-5426-7881-8ca4-83eff65a4c2d","turnId":"01a14182-54df-7e92-8acd-3010871cd4d8","itemId":"msg_08c26fdb702799b1016ad1484f4784819a88af67651de2880a","delta":" twenty"},"emittedAtMs":1792100431615}} -{"dir":"recv","msg":{"method":"item/completed","params":{"item":{"type":"agentMessage","id":"msg_08c26fdb702799b1016ad1484f4784819a88af67651de2880a","text":"one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen seventeen eighteen nineteen twenty","phase":"final_answer","memoryCitation":null,"delivery":null,"questions":null},"threadId":"01a14182-5426-7881-8ca4-83eff65a4c2d","turnId":"01a14182-54df-7e92-8acd-3010871cd4d8","completedAtMs":1792100431615},"emittedAtMs":1792100431615}} -{"dir":"recv","msg":{"method":"thread/tokenUsage/updated","params":{"threadId":"01a14182-5426-7881-8ca4-83eff65a4c2d","turnId":"01a14182-54df-7e92-8acd-3010871cd4d8","tokenUsage":{"total":{"totalTokens":13362,"inputTokens":13327,"cachedInputTokens":13056,"cacheWriteInputTokens":0,"outputTokens":35,"reasoningOutputTokens":9},"last":{"totalTokens":13362,"inputTokens":13327,"cachedInputTokens":13056,"cacheWriteInputTokens":0,"outputTokens":35,"reasoningOutputTokens":9},"modelContextWindow":258400}},"emittedAtMs":1792100431869}} -{"dir":"recv","msg":{"method":"account/rateLimits/updated","params":{"rateLimits":{"limitId":"codex","limitName":null,"normalModelSlug":null,"primary":{"usedPercent":0,"windowDurationMins":10080,"resetsAt":1792701205},"secondary":null,"credits":{"hasCredits":false,"unlimited":false,"balance":"0"},"individualLimit":null,"spendControlReached":null,"planType":"prolite","rateLimitReachedType":null}},"emittedAtMs":1792100431869}} -{"dir":"recv","msg":{"method":"thread/status/changed","params":{"threadId":"01a14182-5426-7881-8ca4-83eff65a4c2d","status":{"type":"idle"}},"emittedAtMs":1792100431871}} -{"dir":"recv","msg":{"method":"turn/completed","params":{"threadId":"01a14182-5426-7881-8ca4-83eff65a4c2d","turn":{"id":"01a14182-54df-7e92-8acd-3010871cd4d8","items":[{"type":"agentMessage","id":"msg_08c26fdb702799b1016ad1484f4784819a88af67651de2880a","text":"one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen seventeen eighteen nineteen twenty","phase":"final_answer","memoryCitation":null,"delivery":null,"questions":null}],"itemsView":"summary","status":"completed","error":null,"startedAt":1792100422,"completedAt":1792100431,"durationMs":8985}},"emittedAtMs":1792100431871}} diff --git a/internal/codex/testdata/contract/Tools-first.jsonl b/internal/codex/testdata/contract/Tools-first.jsonl deleted file mode 100644 index 976f88e..0000000 --- a/internal/codex/testdata/contract/Tools-first.jsonl +++ /dev/null @@ -1,40 +0,0 @@ -{"dir":"send","msg":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true},"clientInfo":{"name":"byre","title":"byre","version":"0.0.0"}}}} -{"dir":"recv","msg":{"id":1,"result":{"userAgent":"byre/0.159.0 (Mac OS 27.0.1; arm64) unknown (byre; 0.0.0)","codexHome":"/Users/flamingcow/.codex","platformFamily":"unix","platformOs":"macos"}}} -{"dir":"recv","msg":{"method":"remoteControl/status/changed","params":{"status":"disabled","serverName":"roc.tailcecab.ts.net","installationId":"7b5574fb-530f-4cdf-925d-13b538911a88","environmentId":null},"emittedAtMs":1792100422684}} -{"dir":"send","msg":{"method":"initialized"}} -{"dir":"send","msg":{"id":2,"method":"thread/start","params":{"approvalPolicy":"never","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveTools3558027209/001","dynamicTools":[{"description":"byre's tools","name":"byre","tools":[{"description":"Look up the current codeword for a topic.","inputSchema":{"type":"object","properties":{"topic":{"type":"string"}},"required":["topic"]},"name":"lookup","type":"function"}],"type":"namespace"}],"model":"gpt-5.6-luna","sandbox":"danger-full-access"}}} -{"dir":"recv","msg":{"id":2,"result":{"thread":{"id":"01a14182-5430-72d3-8349-3be3b087cb51","environments":[{"environmentId":"local","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveTools3558027209/001","runtimeWorkspaceRoots":["/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveTools3558027209/001"]}],"extra":null,"sessionId":"01a14182-5430-72d3-8349-3be3b087cb51","forkedFromId":null,"parentThreadId":null,"preview":"","ephemeral":false,"section":null,"sectionEnteredAt":null,"projectId":null,"historyMode":"paginated","modelProvider":"openai","model":"gpt-5.6-luna","reasoningEffort":null,"createdAt":1792100422,"updatedAt":1792100422,"recencyAt":1792100422,"status":{"type":"idle"},"path":"/Users/flamingcow/.codex/sessions/2026/10/15/rollout-2026-10-15T14-40-22-01a14182-5430-72d3-8349-3be3b087cb51.jsonl","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveTools3558027209/001","cliVersion":"0.159.0","originator":"byre","source":"vscode","canAcceptDirectInput":true,"threadSource":null,"agentNickname":null,"agentRole":null,"gitInfo":null,"name":null,"daybreakEnabled":null,"turns":[]},"model":"gpt-5.6-luna","modelProvider":"openai","serviceTier":null,"disabledPluginIds":[],"cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveTools3558027209/001","runtimeWorkspaceRoots":["/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveTools3558027209/001"],"instructionSources":[],"approvalPolicy":"never","approvalsReviewer":"user","sandbox":{"type":"dangerFullAccess"},"activePermissionProfile":null,"reasoningEffort":null,"multiAgentMode":"explicitRequestOnly"}}} -{"dir":"send","msg":{"id":3,"method":"turn/start","params":{"additionalContext":{"byre":{"kind":"application","value":"This is a byre test session."}},"input":[{"text":"Call byre's lookup tool with topic birds, then reply with just the codeword it gives.","type":"text"}],"threadId":"01a14182-5430-72d3-8349-3be3b087cb51"}}} -{"dir":"recv","msg":{"method":"thread/started","params":{"thread":{"id":"01a14182-5430-72d3-8349-3be3b087cb51","environments":[{"environmentId":"local","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveTools3558027209/001","runtimeWorkspaceRoots":["/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveTools3558027209/001"]}],"extra":null,"sessionId":"01a14182-5430-72d3-8349-3be3b087cb51","forkedFromId":null,"parentThreadId":null,"preview":"","ephemeral":false,"section":null,"sectionEnteredAt":null,"projectId":null,"historyMode":"paginated","modelProvider":"openai","model":"gpt-5.6-luna","reasoningEffort":null,"createdAt":1792100422,"updatedAt":1792100422,"recencyAt":1792100422,"status":{"type":"idle"},"path":"/Users/flamingcow/.codex/sessions/2026/10/15/rollout-2026-10-15T14-40-22-01a14182-5430-72d3-8349-3be3b087cb51.jsonl","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveTools3558027209/001","cliVersion":"0.159.0","originator":"byre","source":"vscode","canAcceptDirectInput":true,"threadSource":null,"agentNickname":null,"agentRole":null,"gitInfo":null,"name":null,"daybreakEnabled":null,"turns":[]}},"emittedAtMs":1792100422827}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-5430-72d3-8349-3be3b087cb51","name":"cua_repl","status":"starting","error":null,"failureReason":null},"emittedAtMs":1792100422827}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-5430-72d3-8349-3be3b087cb51","name":"codex_apps","status":"starting","error":null,"failureReason":null},"emittedAtMs":1792100422827}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-5430-72d3-8349-3be3b087cb51","name":"node_repl","status":"starting","error":null,"failureReason":null},"emittedAtMs":1792100422827}} -{"dir":"recv","msg":{"id":3,"result":{"turn":{"id":"01a14182-54ad-7060-a9a3-51e93c099fca","items":[],"itemsView":"notLoaded","status":"inProgress","error":null,"startedAt":null,"completedAt":null,"durationMs":null}}}} -{"dir":"recv","msg":{"method":"thread/status/changed","params":{"threadId":"01a14182-5430-72d3-8349-3be3b087cb51","status":{"type":"active","activeFlags":[]}},"emittedAtMs":1792100422902}} -{"dir":"recv","msg":{"method":"turn/started","params":{"threadId":"01a14182-5430-72d3-8349-3be3b087cb51","turn":{"id":"01a14182-54ad-7060-a9a3-51e93c099fca","items":[],"itemsView":"notLoaded","status":"inProgress","error":null,"startedAt":1792100422,"completedAt":null,"durationMs":null}},"emittedAtMs":1792100422902}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-5430-72d3-8349-3be3b087cb51","name":"node_repl","status":"ready","error":null,"failureReason":null},"emittedAtMs":1792100423401}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-5430-72d3-8349-3be3b087cb51","name":"cua_repl","status":"ready","error":null,"failureReason":null},"emittedAtMs":1792100423464}} -{"dir":"recv","msg":{"method":"account/updated","params":{"authMode":"chatgpt","planType":"prolite"},"emittedAtMs":1792100423478}} -{"dir":"recv","msg":{"method":"item/started","params":{"item":{"type":"userMessage","id":"01a14182-6e0d-7d02-9f3e-14dec1def758","clientId":null,"content":[{"type":"text","text":"Call byre's lookup tool with topic birds, then reply with just the codeword it gives.","text_elements":[]}]},"threadId":"01a14182-5430-72d3-8349-3be3b087cb51","turnId":"01a14182-54ad-7060-a9a3-51e93c099fca","startedAtMs":1792100429325},"emittedAtMs":1792100429325}} -{"dir":"recv","msg":{"method":"item/completed","params":{"item":{"type":"userMessage","id":"01a14182-6e0d-7d02-9f3e-14dec1def758","clientId":null,"content":[{"type":"text","text":"Call byre's lookup tool with topic birds, then reply with just the codeword it gives.","text_elements":[]}]},"threadId":"01a14182-5430-72d3-8349-3be3b087cb51","turnId":"01a14182-54ad-7060-a9a3-51e93c099fca","completedAtMs":1792100429325},"emittedAtMs":1792100429327}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-5430-72d3-8349-3be3b087cb51","name":"codex_apps","status":"ready","error":null,"failureReason":null},"emittedAtMs":1792100431974}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-5430-72d3-8349-3be3b087cb51","name":"cua_repl","status":"ready","error":null,"failureReason":null},"emittedAtMs":1792100431974}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-5430-72d3-8349-3be3b087cb51","name":"node_repl","status":"ready","error":null,"failureReason":null},"emittedAtMs":1792100431974}} -{"dir":"recv","msg":{"method":"item/started","params":{"item":{"type":"reasoning","id":"rs_0d73c98bd49ed7ad016ad1484fec38819a8f0dfa65d35c2cbb","summary":[],"content":[]},"threadId":"01a14182-5430-72d3-8349-3be3b087cb51","turnId":"01a14182-54ad-7060-a9a3-51e93c099fca","startedAtMs":1792100432169},"emittedAtMs":1792100432169}} -{"dir":"recv","msg":{"method":"item/completed","params":{"item":{"type":"reasoning","id":"rs_0d73c98bd49ed7ad016ad1484fec38819a8f0dfa65d35c2cbb","summary":[],"content":[]},"threadId":"01a14182-5430-72d3-8349-3be3b087cb51","turnId":"01a14182-54ad-7060-a9a3-51e93c099fca","completedAtMs":1792100432169},"emittedAtMs":1792100432170}} -{"dir":"recv","msg":{"method":"item/started","params":{"item":{"type":"dynamicToolCall","id":"exec-36a61060-4c56-4c3f-bcc9-73e6a25f8087","namespace":"byre","tool":"lookup","arguments":{"topic":"birds"},"status":"inProgress","contentItems":null,"success":null,"durationMs":null},"threadId":"01a14182-5430-72d3-8349-3be3b087cb51","turnId":"01a14182-54ad-7060-a9a3-51e93c099fca","startedAtMs":1792100432349},"emittedAtMs":1792100432349}} -{"dir":"recv","msg":{"method":"item/tool/call","id":0,"params":{"threadId":"01a14182-5430-72d3-8349-3be3b087cb51","turnId":"01a14182-54ad-7060-a9a3-51e93c099fca","callId":"exec-36a61060-4c56-4c3f-bcc9-73e6a25f8087","namespace":"byre","tool":"lookup","arguments":{"topic":"birds"}}}} -{"dir":"send","msg":{"id":0,"result":{"contentItems":[{"text":"The codeword is TEAL-9.","type":"inputText"}],"success":true}}} -{"dir":"recv","msg":{"method":"item/completed","params":{"item":{"type":"dynamicToolCall","id":"exec-36a61060-4c56-4c3f-bcc9-73e6a25f8087","namespace":"byre","tool":"lookup","arguments":{"topic":"birds"},"status":"completed","contentItems":[{"type":"inputText","text":"The codeword is TEAL-9."}],"success":true,"durationMs":0},"threadId":"01a14182-5430-72d3-8349-3be3b087cb51","turnId":"01a14182-54ad-7060-a9a3-51e93c099fca","completedAtMs":1792100432350},"emittedAtMs":1792100432350}} -{"dir":"recv","msg":{"method":"thread/tokenUsage/updated","params":{"threadId":"01a14182-5430-72d3-8349-3be3b087cb51","turnId":"01a14182-54ad-7060-a9a3-51e93c099fca","tokenUsage":{"total":{"totalTokens":13449,"inputTokens":13405,"cachedInputTokens":0,"cacheWriteInputTokens":0,"outputTokens":44,"reasoningOutputTokens":14},"last":{"totalTokens":13449,"inputTokens":13405,"cachedInputTokens":0,"cacheWriteInputTokens":0,"outputTokens":44,"reasoningOutputTokens":14},"modelContextWindow":258400}},"emittedAtMs":1792100432364}} -{"dir":"recv","msg":{"method":"account/rateLimits/updated","params":{"rateLimits":{"limitId":"codex","limitName":null,"normalModelSlug":null,"primary":{"usedPercent":0,"windowDurationMins":10080,"resetsAt":1792701205},"secondary":null,"credits":{"hasCredits":false,"unlimited":false,"balance":"0"},"individualLimit":null,"spendControlReached":null,"planType":"prolite","rateLimitReachedType":null}},"emittedAtMs":1792100432364}} -{"dir":"recv","msg":{"method":"item/started","params":{"item":{"type":"agentMessage","id":"msg_0d73c98bd49ed7ad016ad148519bc8819a8a94a147d6a0c127","text":"","phase":"final_answer","memoryCitation":null,"delivery":null,"questions":null},"threadId":"01a14182-5430-72d3-8349-3be3b087cb51","turnId":"01a14182-54ad-7060-a9a3-51e93c099fca","startedAtMs":1792100433883},"emittedAtMs":1792100433883}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-5430-72d3-8349-3be3b087cb51","turnId":"01a14182-54ad-7060-a9a3-51e93c099fca","itemId":"msg_0d73c98bd49ed7ad016ad148519bc8819a8a94a147d6a0c127","delta":"TE"},"emittedAtMs":1792100433883}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-5430-72d3-8349-3be3b087cb51","turnId":"01a14182-54ad-7060-a9a3-51e93c099fca","itemId":"msg_0d73c98bd49ed7ad016ad148519bc8819a8a94a147d6a0c127","delta":"AL"},"emittedAtMs":1792100433883}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-5430-72d3-8349-3be3b087cb51","turnId":"01a14182-54ad-7060-a9a3-51e93c099fca","itemId":"msg_0d73c98bd49ed7ad016ad148519bc8819a8a94a147d6a0c127","delta":"-"},"emittedAtMs":1792100433883}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-5430-72d3-8349-3be3b087cb51","turnId":"01a14182-54ad-7060-a9a3-51e93c099fca","itemId":"msg_0d73c98bd49ed7ad016ad148519bc8819a8a94a147d6a0c127","delta":"9"},"emittedAtMs":1792100433883}} -{"dir":"recv","msg":{"method":"item/completed","params":{"item":{"type":"agentMessage","id":"msg_0d73c98bd49ed7ad016ad148519bc8819a8a94a147d6a0c127","text":"TEAL-9","phase":"final_answer","memoryCitation":null,"delivery":null,"questions":null},"threadId":"01a14182-5430-72d3-8349-3be3b087cb51","turnId":"01a14182-54ad-7060-a9a3-51e93c099fca","completedAtMs":1792100433883},"emittedAtMs":1792100433885}} -{"dir":"recv","msg":{"method":"thread/tokenUsage/updated","params":{"threadId":"01a14182-5430-72d3-8349-3be3b087cb51","turnId":"01a14182-54ad-7060-a9a3-51e93c099fca","tokenUsage":{"total":{"totalTokens":26938,"inputTokens":26886,"cachedInputTokens":13056,"cacheWriteInputTokens":0,"outputTokens":52,"reasoningOutputTokens":14},"last":{"totalTokens":13489,"inputTokens":13481,"cachedInputTokens":13056,"cacheWriteInputTokens":0,"outputTokens":8,"reasoningOutputTokens":0},"modelContextWindow":258400}},"emittedAtMs":1792100434003}} -{"dir":"recv","msg":{"method":"account/rateLimits/updated","params":{"rateLimits":{"limitId":"codex","limitName":null,"normalModelSlug":null,"primary":{"usedPercent":0,"windowDurationMins":10080,"resetsAt":1792701205},"secondary":null,"credits":{"hasCredits":false,"unlimited":false,"balance":"0"},"individualLimit":null,"spendControlReached":null,"planType":"prolite","rateLimitReachedType":null}},"emittedAtMs":1792100434003}} -{"dir":"recv","msg":{"method":"thread/status/changed","params":{"threadId":"01a14182-5430-72d3-8349-3be3b087cb51","status":{"type":"idle"}},"emittedAtMs":1792100434007}} -{"dir":"recv","msg":{"method":"turn/completed","params":{"threadId":"01a14182-5430-72d3-8349-3be3b087cb51","turn":{"id":"01a14182-54ad-7060-a9a3-51e93c099fca","items":[{"type":"agentMessage","id":"msg_0d73c98bd49ed7ad016ad148519bc8819a8a94a147d6a0c127","text":"TEAL-9","phase":"final_answer","memoryCitation":null,"delivery":null,"questions":null}],"itemsView":"summary","status":"completed","error":null,"startedAt":1792100422,"completedAt":1792100434,"durationMs":11173}},"emittedAtMs":1792100434007}} diff --git a/internal/codex/testdata/contract/Tools-resumed.jsonl b/internal/codex/testdata/contract/Tools-resumed.jsonl deleted file mode 100644 index 6350761..0000000 --- a/internal/codex/testdata/contract/Tools-resumed.jsonl +++ /dev/null @@ -1,41 +0,0 @@ -{"dir":"send","msg":{"id":1,"method":"initialize","params":{"capabilities":{"experimentalApi":true},"clientInfo":{"name":"byre","title":"byre","version":"0.0.0"}}}} -{"dir":"recv","msg":{"id":1,"result":{"userAgent":"byre/0.159.0 (Mac OS 27.0.1; arm64) unknown (byre; 0.0.0)","codexHome":"/Users/flamingcow/.codex","platformFamily":"unix","platformOs":"macos"}}} -{"dir":"recv","msg":{"method":"remoteControl/status/changed","params":{"status":"disabled","serverName":"roc.tailcecab.ts.net","installationId":"7b5574fb-530f-4cdf-925d-13b538911a88","environmentId":null},"emittedAtMs":1792100434045}} -{"dir":"send","msg":{"method":"initialized"}} -{"dir":"send","msg":{"id":2,"method":"thread/resume","params":{"approvalPolicy":"never","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveTools3558027209/001","model":"gpt-5.6-luna","sandbox":"danger-full-access","threadId":"01a14182-5430-72d3-8349-3be3b087cb51"}}} -{"dir":"recv","msg":{"method":"deprecationNotice","params":{"summary":"Full-history hydration is deprecated for paginated threads; use `excludeTurns: true`, then page with `thread/turns/list` and `thread/items/list`.","details":null},"emittedAtMs":1792100434047}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-5430-72d3-8349-3be3b087cb51","name":"codex_apps","status":"starting","error":null,"failureReason":null},"emittedAtMs":1792100434078}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-5430-72d3-8349-3be3b087cb51","name":"cua_repl","status":"starting","error":null,"failureReason":null},"emittedAtMs":1792100434078}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-5430-72d3-8349-3be3b087cb51","name":"node_repl","status":"starting","error":null,"failureReason":null},"emittedAtMs":1792100434078}} -{"dir":"recv","msg":{"method":"thread/status/changed","params":{"threadId":"01a14182-5430-72d3-8349-3be3b087cb51","status":{"type":"idle"}},"emittedAtMs":1792100434078}} -{"dir":"recv","msg":{"id":2,"result":{"thread":{"id":"01a14182-5430-72d3-8349-3be3b087cb51","environments":[{"environmentId":"local","cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveTools3558027209/001","runtimeWorkspaceRoots":["/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveTools3558027209/001"]}],"extra":null,"sessionId":"01a14182-5430-72d3-8349-3be3b087cb51","forkedFromId":null,"parentThreadId":null,"preview":"Call byre's lookup tool with topic birds, then reply with just the codeword it gives.","ephemeral":false,"section":null,"sectionEnteredAt":null,"projectId":null,"historyMode":"paginated","modelProvider":"openai","model":"gpt-5.6-luna","reasoningEffort":null,"createdAt":1792100422,"updatedAt":1792100434,"recencyAt":1792100422,"status":{"type":"idle"},"path":"/Users/flamingcow/.codex/sessions/2026/10/15/rollout-2026-10-15T14-40-22-01a14182-5430-72d3-8349-3be3b087cb51.jsonl","cwd":"/private/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveTools3558027209/001","cliVersion":"0.159.0","originator":"byre","source":"vscode","canAcceptDirectInput":true,"threadSource":null,"agentNickname":null,"agentRole":null,"gitInfo":{"sha":"545185b66b04bfe51916ae9840d0f16c639a65c9","branch":"main","originUrl":null},"name":null,"daybreakEnabled":null,"turns":[{"id":"01a14182-54ad-7060-a9a3-51e93c099fca","items":[{"type":"userMessage","id":"01a14182-6e0d-7d02-9f3e-14dec1def758","clientId":null,"content":[{"type":"text","text":"Call byre's lookup tool with topic birds, then reply with just the codeword it gives.","text_elements":[]}]},{"type":"reasoning","id":"rs_0d73c98bd49ed7ad016ad1484fec38819a8f0dfa65d35c2cbb","summary":[],"content":[]},{"type":"dynamicToolCall","id":"exec-36a61060-4c56-4c3f-bcc9-73e6a25f8087","namespace":"byre","tool":"lookup","arguments":{"topic":"birds"},"status":"completed","contentItems":[{"type":"inputText","text":"The codeword is TEAL-9."}],"success":true,"durationMs":0},{"type":"agentMessage","id":"msg_0d73c98bd49ed7ad016ad148519bc8819a8a94a147d6a0c127","text":"TEAL-9","phase":"final_answer","memoryCitation":null,"delivery":null,"questions":null}],"itemsView":"full","status":"completed","error":null,"startedAt":1792100422,"completedAt":1792100434,"durationMs":11173}]},"model":"gpt-5.6-luna","modelProvider":"openai","serviceTier":null,"disabledPluginIds":[],"cwd":"/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveTools3558027209/001","runtimeWorkspaceRoots":["/var/folders/r4/m529kfnd537_x0xfbgcgqfs40000gn/T/TestLiveTools3558027209/001"],"instructionSources":[],"approvalPolicy":"never","approvalsReviewer":"user","sandbox":{"type":"dangerFullAccess"},"activePermissionProfile":null,"reasoningEffort":null,"collaborationMode":{"mode":"default","settings":{"model":"gpt-5.6-luna","reasoning_effort":null,"developer_instructions":null}},"multiAgentMode":"explicitRequestOnly","initialTurnsPage":null,"turnsBackwardsCursor":"{\"requestedThreadId\":\"01a14182-5430-72d3-8349-3be3b087cb51\",\"rolloutOrdinal\":1,\"includeAnchor\":true,\"scope\":{\"kind\":\"turns\"}}","itemsBackwardsCursor":"{\"requestedThreadId\":\"01a14182-5430-72d3-8349-3be3b087cb51\",\"rolloutOrdinal\":16,\"includeAnchor\":true,\"scope\":{\"kind\":\"itemsByCreatedAtOrdinal\"}}"}}} -{"dir":"recv","msg":{"method":"thread/tokenUsage/updated","params":{"threadId":"01a14182-5430-72d3-8349-3be3b087cb51","turnId":"01a14182-54ad-7060-a9a3-51e93c099fca","tokenUsage":{"total":{"totalTokens":26938,"inputTokens":26886,"cachedInputTokens":13056,"cacheWriteInputTokens":0,"outputTokens":52,"reasoningOutputTokens":14},"last":{"totalTokens":13489,"inputTokens":13481,"cachedInputTokens":13056,"cacheWriteInputTokens":0,"outputTokens":8,"reasoningOutputTokens":0},"modelContextWindow":258400}},"emittedAtMs":1792100434079}} -{"dir":"recv","msg":{"method":"thread/goal/cleared","params":{"threadId":"01a14182-5430-72d3-8349-3be3b087cb51"},"emittedAtMs":1792100434079}} -{"dir":"send","msg":{"id":3,"method":"turn/start","params":{"input":[{"text":"Call byre's lookup tool again with topic fish, then reply with just the codeword it gives.","type":"text"}],"threadId":"01a14182-5430-72d3-8349-3be3b087cb51"}}} -{"dir":"recv","msg":{"id":3,"result":{"turn":{"id":"01a14182-80a0-76c2-84c5-ac39aac6bc2a","items":[],"itemsView":"notLoaded","status":"inProgress","error":null,"startedAt":null,"completedAt":null,"durationMs":null}}}} -{"dir":"recv","msg":{"method":"thread/status/changed","params":{"threadId":"01a14182-5430-72d3-8349-3be3b087cb51","status":{"type":"active","activeFlags":[]}},"emittedAtMs":1792100434085}} -{"dir":"recv","msg":{"method":"turn/started","params":{"threadId":"01a14182-5430-72d3-8349-3be3b087cb51","turn":{"id":"01a14182-80a0-76c2-84c5-ac39aac6bc2a","items":[],"itemsView":"notLoaded","status":"inProgress","error":null,"startedAt":1792100434,"completedAt":null,"durationMs":null}},"emittedAtMs":1792100434085}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-5430-72d3-8349-3be3b087cb51","name":"node_repl","status":"ready","error":null,"failureReason":null},"emittedAtMs":1792100434171}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-5430-72d3-8349-3be3b087cb51","name":"cua_repl","status":"ready","error":null,"failureReason":null},"emittedAtMs":1792100434191}} -{"dir":"recv","msg":{"method":"account/updated","params":{"authMode":"chatgpt","planType":"prolite"},"emittedAtMs":1792100434938}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-5430-72d3-8349-3be3b087cb51","name":"codex_apps","status":"ready","error":null,"failureReason":null},"emittedAtMs":1792100436100}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-5430-72d3-8349-3be3b087cb51","name":"node_repl","status":"ready","error":null,"failureReason":null},"emittedAtMs":1792100436100}} -{"dir":"recv","msg":{"method":"mcpServer/startupStatus/updated","params":{"threadId":"01a14182-5430-72d3-8349-3be3b087cb51","name":"cua_repl","status":"ready","error":null,"failureReason":null},"emittedAtMs":1792100436100}} -{"dir":"recv","msg":{"method":"item/started","params":{"item":{"type":"userMessage","id":"01a14182-8bad-7982-b7a3-38b6aeb0313a","clientId":null,"content":[{"type":"text","text":"Call byre's lookup tool again with topic fish, then reply with just the codeword it gives.","text_elements":[]}]},"threadId":"01a14182-5430-72d3-8349-3be3b087cb51","turnId":"01a14182-80a0-76c2-84c5-ac39aac6bc2a","startedAtMs":1792100436910},"emittedAtMs":1792100436910}} -{"dir":"recv","msg":{"method":"item/completed","params":{"item":{"type":"userMessage","id":"01a14182-8bad-7982-b7a3-38b6aeb0313a","clientId":null,"content":[{"type":"text","text":"Call byre's lookup tool again with topic fish, then reply with just the codeword it gives.","text_elements":[]}]},"threadId":"01a14182-5430-72d3-8349-3be3b087cb51","turnId":"01a14182-80a0-76c2-84c5-ac39aac6bc2a","completedAtMs":1792100436910},"emittedAtMs":1792100436912}} -{"dir":"recv","msg":{"method":"item/started","params":{"item":{"type":"dynamicToolCall","id":"exec-5d969bac-bff2-43dc-bf6f-11564eb5f33a","namespace":"byre","tool":"lookup","arguments":{"topic":"fish"},"status":"inProgress","contentItems":null,"success":null,"durationMs":null},"threadId":"01a14182-5430-72d3-8349-3be3b087cb51","turnId":"01a14182-80a0-76c2-84c5-ac39aac6bc2a","startedAtMs":1792100438388},"emittedAtMs":1792100438388}} -{"dir":"recv","msg":{"method":"item/tool/call","id":0,"params":{"threadId":"01a14182-5430-72d3-8349-3be3b087cb51","turnId":"01a14182-80a0-76c2-84c5-ac39aac6bc2a","callId":"exec-5d969bac-bff2-43dc-bf6f-11564eb5f33a","namespace":"byre","tool":"lookup","arguments":{"topic":"fish"}}}} -{"dir":"send","msg":{"id":0,"result":{"contentItems":[{"text":"The codeword is TEAL-9.","type":"inputText"}],"success":true}}} -{"dir":"recv","msg":{"method":"item/completed","params":{"item":{"type":"dynamicToolCall","id":"exec-5d969bac-bff2-43dc-bf6f-11564eb5f33a","namespace":"byre","tool":"lookup","arguments":{"topic":"fish"},"status":"completed","contentItems":[{"type":"inputText","text":"The codeword is TEAL-9."}],"success":true,"durationMs":0},"threadId":"01a14182-5430-72d3-8349-3be3b087cb51","turnId":"01a14182-80a0-76c2-84c5-ac39aac6bc2a","completedAtMs":1792100438388},"emittedAtMs":1792100438389}} -{"dir":"recv","msg":{"method":"thread/tokenUsage/updated","params":{"threadId":"01a14182-5430-72d3-8349-3be3b087cb51","turnId":"01a14182-80a0-76c2-84c5-ac39aac6bc2a","tokenUsage":{"total":{"totalTokens":40482,"inputTokens":40402,"cachedInputTokens":26112,"cacheWriteInputTokens":0,"outputTokens":80,"reasoningOutputTokens":14},"last":{"totalTokens":13544,"inputTokens":13516,"cachedInputTokens":13056,"cacheWriteInputTokens":0,"outputTokens":28,"reasoningOutputTokens":0},"modelContextWindow":258400}},"emittedAtMs":1792100438391}} -{"dir":"recv","msg":{"method":"account/rateLimits/updated","params":{"rateLimits":{"limitId":"codex","limitName":null,"normalModelSlug":null,"primary":{"usedPercent":0,"windowDurationMins":10080,"resetsAt":1792701205},"secondary":null,"credits":{"hasCredits":false,"unlimited":false,"balance":"0"},"individualLimit":null,"spendControlReached":null,"planType":"prolite","rateLimitReachedType":null}},"emittedAtMs":1792100438391}} -{"dir":"recv","msg":{"method":"item/started","params":{"item":{"type":"agentMessage","id":"msg_0082be4c526dfb33016ad148582b3c819a9dbe997fe1bb198d","text":"","phase":"final_answer","memoryCitation":null,"delivery":null,"questions":null},"threadId":"01a14182-5430-72d3-8349-3be3b087cb51","turnId":"01a14182-80a0-76c2-84c5-ac39aac6bc2a","startedAtMs":1792100440194},"emittedAtMs":1792100440194}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-5430-72d3-8349-3be3b087cb51","turnId":"01a14182-80a0-76c2-84c5-ac39aac6bc2a","itemId":"msg_0082be4c526dfb33016ad148582b3c819a9dbe997fe1bb198d","delta":"TE"},"emittedAtMs":1792100440194}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-5430-72d3-8349-3be3b087cb51","turnId":"01a14182-80a0-76c2-84c5-ac39aac6bc2a","itemId":"msg_0082be4c526dfb33016ad148582b3c819a9dbe997fe1bb198d","delta":"AL"},"emittedAtMs":1792100440194}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-5430-72d3-8349-3be3b087cb51","turnId":"01a14182-80a0-76c2-84c5-ac39aac6bc2a","itemId":"msg_0082be4c526dfb33016ad148582b3c819a9dbe997fe1bb198d","delta":"-"},"emittedAtMs":1792100440194}} -{"dir":"recv","msg":{"method":"item/agentMessage/delta","params":{"threadId":"01a14182-5430-72d3-8349-3be3b087cb51","turnId":"01a14182-80a0-76c2-84c5-ac39aac6bc2a","itemId":"msg_0082be4c526dfb33016ad148582b3c819a9dbe997fe1bb198d","delta":"9"},"emittedAtMs":1792100440194}} -{"dir":"recv","msg":{"method":"item/completed","params":{"item":{"type":"agentMessage","id":"msg_0082be4c526dfb33016ad148582b3c819a9dbe997fe1bb198d","text":"TEAL-9","phase":"final_answer","memoryCitation":null,"delivery":null,"questions":null},"threadId":"01a14182-5430-72d3-8349-3be3b087cb51","turnId":"01a14182-80a0-76c2-84c5-ac39aac6bc2a","completedAtMs":1792100440196},"emittedAtMs":1792100440198}} -{"dir":"recv","msg":{"method":"thread/tokenUsage/updated","params":{"threadId":"01a14182-5430-72d3-8349-3be3b087cb51","turnId":"01a14182-80a0-76c2-84c5-ac39aac6bc2a","tokenUsage":{"total":{"totalTokens":54066,"inputTokens":53978,"cachedInputTokens":39168,"cacheWriteInputTokens":0,"outputTokens":88,"reasoningOutputTokens":14},"last":{"totalTokens":13584,"inputTokens":13576,"cachedInputTokens":13056,"cacheWriteInputTokens":0,"outputTokens":8,"reasoningOutputTokens":0},"modelContextWindow":258400}},"emittedAtMs":1792100440269}} -{"dir":"recv","msg":{"method":"account/rateLimits/updated","params":{"rateLimits":{"limitId":"codex","limitName":null,"normalModelSlug":null,"primary":{"usedPercent":0,"windowDurationMins":10080,"resetsAt":1792701205},"secondary":null,"credits":{"hasCredits":false,"unlimited":false,"balance":"0"},"individualLimit":null,"spendControlReached":null,"planType":"prolite","rateLimitReachedType":null}},"emittedAtMs":1792100440269}} -{"dir":"recv","msg":{"method":"thread/status/changed","params":{"threadId":"01a14182-5430-72d3-8349-3be3b087cb51","status":{"type":"idle"}},"emittedAtMs":1792100440274}} -{"dir":"recv","msg":{"method":"turn/completed","params":{"threadId":"01a14182-5430-72d3-8349-3be3b087cb51","turn":{"id":"01a14182-80a0-76c2-84c5-ac39aac6bc2a","items":[{"type":"agentMessage","id":"msg_0082be4c526dfb33016ad148582b3c819a9dbe997fe1bb198d","text":"TEAL-9","phase":"final_answer","memoryCitation":null,"delivery":null,"questions":null}],"itemsView":"summary","status":"completed","error":null,"startedAt":1792100434,"completedAt":1792100440,"durationMs":6191}},"emittedAtMs":1792100440274}} diff --git a/internal/git/changes_test.go b/internal/git/changes_test.go deleted file mode 100644 index e1afa6f..0000000 --- a/internal/git/changes_test.go +++ /dev/null @@ -1,228 +0,0 @@ -package git - -import ( - "os" - "path/filepath" - "strings" - "testing" - - "g.fc.run/misc/byre/internal/api" -) - -func TestChanges(t *testing.T) { - dir := t.TempDir() - git := func(args ...string) string { - t.Helper() - out, err := run(dir, args...) - if err != nil { - t.Fatal(err) - } - return out - } - write := func(name, content string) { - t.Helper() - err := os.WriteFile(filepath.Join(dir, name), []byte(content), 0o644) - if err != nil { - t.Fatal(err) - } - } - - git("init", "--quiet", "--initial-branch=main") - git("config", "user.email", "test@example.com") - git("config", "user.name", "test") - write(".gitignore", "build/\n") - write("keep.txt", "one\ntwo\nthree\n") - write("gone.txt", "bye\n") - write("old.txt", "a\nb\nc\nd\ne\nf\ng\nh\n") - git("add", ".") - git("commit", "--quiet", "-m", "base") - - git("switch", "--quiet", "-c", "task") - write("committed.txt", "c\n") - git("add", "committed.txt") - git("commit", "--quiet", "-m", "task work") - - write("keep.txt", "one\nTWO\nthree\nfour\n") - write("untracked.txt", "u\n") - write("blob.bin", "\x00\x01\x02") - os.MkdirAll(filepath.Join(dir, "build"), 0o755) - write("build/out.txt", "ignored\n") - os.Remove(filepath.Join(dir, "gone.txt")) - git("mv", "old.txt", "new.txt") - - indexBefore, err := os.ReadFile(filepath.Join(dir, ".git", "index")) - if err != nil { - t.Fatal(err) - } - - changes, err := Changes(dir, "main", "") - if err != nil { - t.Fatal(err) - } - - indexAfter, err := os.ReadFile(filepath.Join(dir, ".git", "index")) - if err != nil { - t.Fatal(err) - } - if string(indexBefore) != string(indexAfter) { - t.Fatalf("Changes modified the worktree's index") - } - - got := map[string]api.FileChange{} - for _, f := range changes.Files { - got[f.Path] = f - } - want := map[string]api.FileChange{ - "committed.txt": {Status: api.Added, Additions: 1}, - "keep.txt": {Status: api.Modified, Additions: 2, Deletions: 1}, - "untracked.txt": {Status: api.Added, Additions: 1}, - "blob.bin": {Status: api.Added, Binary: true}, - "gone.txt": {Status: api.Deleted, Deletions: 1}, - "new.txt": {Status: api.Renamed}, - } - if len(got) != len(want) { - t.Fatalf("got %d files %+v, want %d", len(got), changes.Files, len(want)) - } - for path, w := range want { - g, ok := got[path] - if !ok { - t.Fatalf("missing %s in %+v", path, changes.Files) - } - if g.Status != w.Status || g.Additions != w.Additions || g.Deletions != w.Deletions || g.Binary != w.Binary { - t.Fatalf("%s: got %+v, want %+v", path, g, w) - } - } - if got["new.txt"].OldPath == nil || *got["new.txt"].OldPath != "old.txt" { - t.Fatalf("rename lost its old path: %+v", got["new.txt"]) - } - - head, err := Head(dir) - if err != nil { - t.Fatal(err) - } - sinceHead, err := Changes(dir, "main", head) - if err != nil { - t.Fatal(err) - } - for _, f := range sinceHead.Files { - if f.Path == "committed.txt" { - t.Fatalf("changes since HEAD include the already committed file: %+v", sinceHead.Files) - } - } - if len(sinceHead.Files) != len(want)-1 { - t.Fatalf("changes since HEAD: got %+v, want every change but committed.txt", sinceHead.Files) - } - - patch, err := FileDiff(dir, changes.Base, changes.Tree, "keep.txt", "") - if err != nil { - t.Fatal(err) - } - for _, line := range []string{"-two", "+TWO", "+four"} { - if !strings.Contains(patch, "\n"+line+"\n") && !strings.HasSuffix(patch, "\n"+line) { - t.Fatalf("patch missing %q:\n%s", line, patch) - } - } - - sha, err := CommitAll(dir, "turn work", "Turn Author", "turn@example.com") - if err != nil { - t.Fatal(err) - } - if sha == "" || sha == head { - t.Fatalf("CommitAll made no commit: %q", sha) - } - if got := git("log", "-1", "--format=%an <%ae>|%cn <%ce>|%s"); got != "Turn Author |Turn Author |turn work" { - t.Fatalf("unexpected commit: %s", got) - } - if status := git("status", "--porcelain", "--untracked-files=all"); status != "" { - t.Fatalf("worktree still dirty after CommitAll:\n%s", status) - } - - again, err := CommitAll(dir, "nothing", "Turn Author", "turn@example.com") - if err != nil || again != "" { - t.Fatalf("CommitAll on a clean tree: %q %v", again, err) - } -} - -func TestCompareSnapshots(t *testing.T) { - dir := t.TempDir() - git := func(args ...string) string { - t.Helper() - out, err := run(dir, args...) - if err != nil { - t.Fatal(err) - } - return out - } - write := func(name, content string) { - t.Helper() - err := os.WriteFile(filepath.Join(dir, name), []byte(content), 0o644) - if err != nil { - t.Fatal(err) - } - } - snap := func(n int) (string, string) { - t.Helper() - tree, err := Snapshot(dir) - if err != nil { - t.Fatal(err) - } - commit, err := CommitSnapshot(dir, tree, SnapshotRef("t1", n), "snap", nil) - if err != nil { - t.Fatal(err) - } - return commit, tree - } - paths := func(c api.TaskChanges) string { - names := []string{} - for _, f := range c.Files { - names = append(names, f.Path) - } - return strings.Join(names, ",") - } - - git("init", "--quiet", "--initial-branch=main") - git("config", "user.email", "test@example.com") - git("config", "user.name", "test") - write("a.txt", "a\n") - git("add", ".") - git("commit", "--quiet", "-m", "base") - git("switch", "--quiet", "-c", "task") - - write("b.txt", "b\n") - commit1, tree1 := snap(1) - write("c.txt", "c\n") - commit2, tree2 := snap(2) - write("d.txt", "d\n") - - sinceMain, err := Compare(dir, "main", commit2, tree2, "") - if err != nil { - t.Fatal(err) - } - if got := paths(sinceMain); got != "b.txt,c.txt" { - t.Fatalf("snapshot 2 since main: got %s", got) - } - - step, err := Compare(dir, "main", commit2, tree2, tree1) - if err != nil { - t.Fatal(err) - } - if got := paths(step); got != "c.txt" { - t.Fatalf("snapshot 2 step: got %s", got) - } - patch, err := FileDiff(dir, step.Base, step.Tree, "c.txt", "") - if err != nil || !strings.Contains(patch, "+c") { - t.Fatalf("snapshot 2 step patch: %q %v", patch, err) - } - - parent, err := Parent(dir, commit1) - if err != nil { - t.Fatal(err) - } - first, err := Compare(dir, "main", commit1, tree1, parent) - if err != nil { - t.Fatal(err) - } - if got := paths(first); got != "b.txt" { - t.Fatalf("snapshot 1 step: got %s", got) - } -} diff --git a/internal/git/push_test.go b/internal/git/push_test.go deleted file mode 100644 index 33d4bdc..0000000 --- a/internal/git/push_test.go +++ /dev/null @@ -1,113 +0,0 @@ -package git - -import ( - "errors" - "path/filepath" - "testing" -) - -var testEnv = []string{ - "GIT_AUTHOR_NAME=Test", - "GIT_AUTHOR_EMAIL=test@byre.invalid", - "GIT_COMMITTER_NAME=Test", - "GIT_COMMITTER_EMAIL=test@byre.invalid", -} - -func mustGit(t *testing.T, dir string, args ...string) string { - t.Helper() - out, err := runEnv(dir, testEnv, args...) - if err != nil { - t.Fatal(err) - } - return out -} - -func pushFixture(t *testing.T) (string, string) { - t.Helper() - bare := filepath.Join(t.TempDir(), "remote.git") - mustGit(t, t.TempDir(), "init", "--quiet", "--bare", "--initial-branch=main", bare) - - repo := t.TempDir() - mustGit(t, repo, "init", "--quiet", "--initial-branch=main") - mustGit(t, repo, "commit", "--quiet", "--allow-empty", "-m", "root") - mustGit(t, repo, "remote", "add", Remote, bare) - mustGit(t, repo, "push", "--quiet", Remote, "main") - return repo, bare -} - -func moveRemote(t *testing.T, bare string) string { - t.Helper() - other := t.TempDir() - mustGit(t, other, "clone", "--quiet", bare, ".") - mustGit(t, other, "commit", "--quiet", "--allow-empty", "-m", "elsewhere") - mustGit(t, other, "push", "--quiet", Remote, "main") - return mustGit(t, other, "rev-parse", "HEAD") -} - -func squash(t *testing.T, repo, message string) string { - t.Helper() - tree := mustGit(t, repo, "rev-parse", "main^{tree}") - return mustGit(t, repo, "commit-tree", tree, "-p", "main", "-m", message) -} - -func TestPushClean(t *testing.T) { - repo, bare := pushFixture(t) - commit := squash(t, repo, "task") - - err := Push(repo, commit, "main") - if err != nil { - t.Fatal(err) - } - if got := mustGit(t, bare, "rev-parse", "main"); got != commit { - t.Fatalf("remote main at %s, want %s", got, commit) - } - err = FastForward(repo, commit) - if err != nil { - t.Fatal(err) - } - if got := mustGit(t, repo, "rev-parse", "main"); got != commit { - t.Fatalf("local main at %s, want %s", got, commit) - } -} - -func TestPushRemoteMoved(t *testing.T) { - repo, bare := pushFixture(t) - moved := moveRemote(t, bare) - commit := squash(t, repo, "task") - - err := Push(repo, commit, "main") - if !errors.Is(err, ErrRemoteMoved) { - t.Fatalf("push onto a moved remote: %v, want ErrRemoteMoved", err) - } - if got := mustGit(t, bare, "rev-parse", "main"); got != moved { - t.Fatalf("remote main at %s after a refused push, want %s", got, moved) - } - - err = FetchFastForward(repo, "main") - if err != nil { - t.Fatal(err) - } - if got := mustGit(t, repo, "rev-parse", "main"); got != moved { - t.Fatalf("local main at %s after fetch, want %s", got, moved) - } - - commit = squash(t, repo, "task again") - err = Push(repo, commit, "main") - if err != nil { - t.Fatal(err) - } - if got := mustGit(t, bare, "rev-parse", "main"); got != commit { - t.Fatalf("remote main at %s, want %s", got, commit) - } -} - -func TestPushOtherFailure(t *testing.T) { - repo, _ := pushFixture(t) - mustGit(t, repo, "remote", "set-url", Remote, filepath.Join(t.TempDir(), "missing.git")) - - err := Push(repo, squash(t, repo, "task"), "main") - if err == nil || errors.Is(err, ErrRemoteMoved) { - t.Fatalf("push to a missing remote: %v, want an error other than ErrRemoteMoved", err) - } - t.Logf("push error: %v", err) -} diff --git a/internal/push/push_test.go b/internal/push/push_test.go deleted file mode 100644 index 0b1495a..0000000 --- a/internal/push/push_test.go +++ /dev/null @@ -1,170 +0,0 @@ -package push_test - -import ( - "context" - "crypto/ecdh" - "crypto/rand" - "encoding/base64" - "net/http" - "net/http/httptest" - "path/filepath" - "strings" - "testing" - "time" - - "g.fc.run/misc/byre/internal/api" - "g.fc.run/misc/byre/internal/push" - "g.fc.run/misc/byre/internal/store" -) - -type received struct { - urgency string - auth string -} - -func TestBurstIsOneNotification(t *testing.T) { - ctx := context.Background() - var notifier *push.Notifier - st, err := store.Open(filepath.Join(t.TempDir(), "byre.db"), store.DefaultPortFirst, func(ev api.Event) { notifier.Observe(ev) }) - if err != nil { - t.Fatalf("open store: %v", err) - } - t.Cleanup(func() { st.Close() }) - - notifier, err = push.New(st, 100*time.Millisecond) - if err != nil { - t.Fatalf("new notifier: %v", err) - } - t.Cleanup(notifier.Close) - - got := make(chan received, 10) - endpoint := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - got <- received{urgency: r.Header.Get("Urgency"), auth: r.Header.Get("Authorization")} - w.WriteHeader(http.StatusCreated) - })) - t.Cleanup(endpoint.Close) - gone := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusGone) - })) - t.Cleanup(gone.Close) - - now := time.Now().UTC() - _, err = st.EnsureUser(ctx, "alice", "Alice") - if err != nil { - t.Fatalf("ensure user: %v", err) - } - err = st.CreateProject(ctx, api.Project{Id: "p1", Name: "demo", RepoPath: "/nonexistent", DefaultBranch: "main", Remote: "none", CreatedBy: "alice", CreatedAt: now}) - if err != nil { - t.Fatalf("create project: %v", err) - } - err = st.CreateTask(ctx, api.Task{Id: "t1", ProjectId: "p1", Title: "Fix login", Branch: "b", WorktreePath: "/nonexistent", BaseRef: "x", Status: api.TaskStatusActive, CreatedBy: "alice", CreatedAt: now}, "alice") - if err != nil { - t.Fatalf("create task: %v", err) - } - err = st.CreateSession(ctx, api.AgentSession{Id: "s1", TaskId: "t1", Vendor: api.Claude, VendorSessionId: "s1", State: api.AgentSessionStateIdle, Role: api.Implementer, CreatedBy: "alice", CreatedAt: now}, "p1") - if err != nil { - t.Fatalf("create session: %v", err) - } - - for _, url := range []string{endpoint.URL, gone.URL} { - err = st.AddPushSubscription(ctx, store.PushSubscription{Endpoint: url, UserLogin: "alice", P256dh: clientKey(t), Auth: secret(t), Subscriber: "https://byre.example"}) - if err != nil { - t.Fatalf("add subscription: %v", err) - } - } - - create := func(id string, kind api.InboxItemKind, blocking bool, payload map[string]any) { - t.Helper() - err := st.CreateInboxItem(ctx, api.InboxItem{Id: id, ProjectId: "p1", TaskId: "t1", AgentSessionId: "s1", ThreadId: id, - Kind: kind, Priority: api.Blocked, Blocking: blocking, Payload: payload, State: api.Open, CreatedAt: now}) - if err != nil { - t.Fatalf("create inbox item: %v", err) - } - } - - create("r1", api.Report, false, map[string]any{"text": "done"}) - expectNone(t, got) - - create("q1", api.Question, true, map[string]any{"question": "Which port?"}) - create("q2", api.Question, true, map[string]any{"question": "Which host?"}) - create("r2", api.Report, false, map[string]any{"text": "done"}) - - select { - case r := <-got: - if r.urgency != "high" || !strings.HasPrefix(r.auth, "vapid t=") { - t.Fatalf("unexpected push request %+v", r) - } - case <-time.After(5 * time.Second): - t.Fatalf("no push delivered") - } - expectNone(t, got) - - targets, err := st.PushTargets(ctx) - if err != nil { - t.Fatalf("push targets: %v", err) - } - if len(targets) != 1 || targets[0].Subscription.Endpoint != endpoint.URL { - t.Fatalf("expired subscription not removed: %+v", targets) - } -} - -func TestCompose(t *testing.T) { - titles := map[string]string{"t1": "Fix login", "t2": "Add search"} - one := push.Compose([]api.InboxItem{ - {TaskId: "t1", Kind: api.Question, Payload: map[string]any{"question": "Which port?\nmore"}}, - }, titles) - if one != (push.Message{Title: "byre · Fix login", Body: "Which port?", URL: "/#/task/t1"}) { - t.Fatalf("unexpected single message %+v", one) - } - - burst := push.Compose([]api.InboxItem{ - {TaskId: "t2", Kind: api.Question, Payload: map[string]any{"question": "Which index?"}}, - {TaskId: "t1", Kind: api.SessionError, Payload: map[string]any{"error": "exit 1"}}, - }, titles) - want := push.Message{Title: "byre · 2 new inbox items", Body: "Add search: Which index?\nFix login: Error: exit 1", URL: "/#/task/t2"} - if burst != want { - t.Fatalf("unexpected burst message %+v", burst) - } -} - -func TestWantsDefaults(t *testing.T) { - prefs := api.NotificationPrefs{BlockingQuestions: true} - cases := []struct { - item api.InboxItem - want bool - }{ - {api.InboxItem{Kind: api.Question, Blocking: true}, true}, - {api.InboxItem{Kind: api.Question}, false}, - {api.InboxItem{Kind: api.Report}, false}, - {api.InboxItem{Kind: api.SessionError, Blocking: true}, false}, - {api.InboxItem{Kind: api.RateLimited}, false}, - } - for _, c := range cases { - if push.Wants(prefs, c.item) != c.want { - t.Fatalf("wants %+v: got %v", c.item, !c.want) - } - } -} - -func expectNone(t *testing.T, got chan received) { - t.Helper() - select { - case r := <-got: - t.Fatalf("unexpected push %+v", r) - case <-time.After(400 * time.Millisecond): - } -} - -func clientKey(t *testing.T) string { - k, err := ecdh.P256().GenerateKey(rand.Reader) - if err != nil { - t.Fatalf("generate key: %v", err) - } - return base64.RawURLEncoding.EncodeToString(k.PublicKey().Bytes()) -} - -func secret(t *testing.T) string { - b := make([]byte, 16) - rand.Read(b) - return base64.RawURLEncoding.EncodeToString(b) -} diff --git a/internal/server/files_test.go b/internal/server/files_test.go deleted file mode 100644 index f910f9f..0000000 --- a/internal/server/files_test.go +++ /dev/null @@ -1,122 +0,0 @@ -package server_test - -import ( - "net/http" - "net/url" - "os" - "path/filepath" - "strings" - "testing" - - "g.fc.run/misc/byre/internal/api" -) - -func TestTaskFiles(t *testing.T) { - h := newHarness(t) - p := h.createProject("demo") - task := api.Task{} - h.do("POST", "/api/projects/"+p.Id+"/tasks", api.TaskCreate{Title: "files"}, &task) - wt := task.WorktreePath - - outside := t.TempDir() - writeFile(t, filepath.Join(outside, "secret"), "secret\n") - err := os.Mkdir(filepath.Join(wt, "src"), 0o755) - if err != nil { - t.Fatalf("mkdir: %v", err) - } - writeFile(t, filepath.Join(wt, "src", "main.go"), "package main\n") - writeFile(t, filepath.Join(wt, "src", "main.o"), "obj") - writeFile(t, filepath.Join(wt, ".gitignore"), "*.o\n/out/\n") - err = os.Mkdir(filepath.Join(wt, "out"), 0o755) - if err != nil { - t.Fatalf("mkdir: %v", err) - } - writeFile(t, filepath.Join(wt, "out", "app"), "bin") - writeFile(t, filepath.Join(wt, "blob.bin"), "a\x00b") - writeFile(t, filepath.Join(wt, "big.txt"), strings.Repeat("é", 200_000)) - for name, target := range map[string]string{ - "inlink": "src", - "outlink": filepath.Join(outside, "secret"), - "outdir": outside, - "dangling": "nope", - } { - err = os.Symlink(target, filepath.Join(wt, name)) - if err != nil { - t.Fatalf("symlink: %v", err) - } - } - - dir := func(path string) (api.DirListing, int) { - l := api.DirListing{} - code := h.do("GET", "/api/tasks/"+task.Id+"/dir?path="+url.QueryEscape(path), nil, &l) - return l, code - } - file := func(path string) (api.FileContent, int) { - f := api.FileContent{} - code := h.do("GET", "/api/tasks/"+task.Id+"/file?path="+url.QueryEscape(path), nil, &f) - return f, code - } - - root, code := dir("") - if code != http.StatusOK { - t.Fatalf("list root: %d", code) - } - names := func(l api.DirListing) string { - got := []string{} - for _, e := range l.Entries { - s := string(e.Type) + ":" + e.Name - if e.Ignored { - s += "!" - } - got = append(got, s) - } - return strings.Join(got, " ") - } - want := "dir:inlink dir:out! dir:src file:.gitignore file:README file:big.txt file:blob.bin other:dangling other:outdir other:outlink" - if names(root) != want { - t.Fatalf("root listing %q, want %q", names(root), want) - } - - for path, want := range map[string]string{ - "src": "file:main.go file:main.o!", - "inlink": "file:main.go file:main.o!", - "out": "file:app!", - } { - sub, code := dir(path) - if code != http.StatusOK || sub.Path != path || names(sub) != want { - t.Fatalf("list %s: %d %q, want %q", path, code, names(sub), want) - } - } - - f, code := file("src/main.go") - if code != http.StatusOK || f.Text != "package main\n" || f.Binary || f.Truncated { - t.Fatalf("read text: %d %+v", code, f) - } - - f, code = file("blob.bin") - if code != http.StatusOK || !f.Binary || f.Text != "" || f.Size != 3 { - t.Fatalf("read binary: %d %+v", code, f) - } - - f, code = file("big.txt") - if code != http.StatusOK || f.Binary || !f.Truncated || f.Size != 400_000 || len(f.Text) != 256<<10 { - t.Fatalf("read big: %d binary=%v truncated=%v size=%d len=%d", code, f.Binary, f.Truncated, f.Size, len(f.Text)) - } - - for _, path := range []string{"../secret", "/etc/passwd", "src/../../x", "outlink", "outdir/secret", ".git", "src"} { - _, code = file(path) - if code != http.StatusBadRequest { - t.Fatalf("read %s: want 400, got %d", path, code) - } - } - for _, path := range []string{"..", "outdir", "README"} { - _, code = dir(path) - if code != http.StatusBadRequest { - t.Fatalf("list %s: want 400, got %d", path, code) - } - } - _, code = file("missing") - if code != http.StatusNotFound { - t.Fatalf("read missing: want 404, got %d", code) - } -} diff --git a/internal/server/server.go b/internal/server/server.go index 967b595..ff95db4 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -655,7 +655,7 @@ func (s *Server) SendMessage(w http.ResponseWriter, r *http.Request, sessionID s model = *req.Model } - sess, err := s.agents.Send(r.Context(), sessionID, actor(r), req.Text, model, images...) + sess, err := s.agents.Message(r.Context(), sessionID, actor(r), req.Text, model, images...) if err != nil { writeAgentError(w, err) return diff --git a/internal/server/server_test.go b/internal/server/server_test.go deleted file mode 100644 index e42b86e..0000000 --- a/internal/server/server_test.go +++ /dev/null @@ -1,577 +0,0 @@ -package server_test - -import ( - "bufio" - "bytes" - "context" - "encoding/json" - "fmt" - "io" - "net/http" - "net/http/httptest" - "net/url" - "os" - "os/exec" - "path/filepath" - "strings" - "testing" - "testing/fstest" - "time" - - "g.fc.run/misc/byre/internal/api" - "g.fc.run/misc/byre/internal/server" - "g.fc.run/misc/byre/internal/store" - "g.fc.run/misc/byre/web" -) - -const login = "alice@example.com" - -type harness struct { - t *testing.T - url string -} - -func newHarness(t *testing.T) *harness { - srv, err := server.New(t.TempDir(), store.DefaultPortFirst, fstest.MapFS{"index.html": {Data: []byte("shell")}}) - if err != nil { - t.Fatalf("new server: %v", err) - } - t.Cleanup(func() { srv.Close() }) - - hs := httptest.NewServer(srv.Handler()) - t.Cleanup(hs.Close) - - return &harness{t: t, url: hs.URL} -} - -func (h *harness) do(method, path string, body any, out any) int { - h.t.Helper() - - buf := &bytes.Buffer{} - if body != nil { - err := json.NewEncoder(buf).Encode(body) - if err != nil { - h.t.Fatalf("encode: %v", err) - } - } - - req, err := http.NewRequest(method, h.url+path, buf) - if err != nil { - h.t.Fatalf("new request: %v", err) - } - req.Header.Set("Tailscale-User-Login", login) - req.Header.Set("Tailscale-User-Name", "Alice") - - resp, err := http.DefaultClient.Do(req) - if err != nil { - h.t.Fatalf("%s %s: %v", method, path, err) - } - defer resp.Body.Close() - - if out != nil && resp.StatusCode < 300 { - err = json.NewDecoder(resp.Body).Decode(out) - if err != nil { - h.t.Fatalf("decode %s %s: %v", method, path, err) - } - } - return resp.StatusCode -} - -func (h *harness) createProject(name string) api.Project { - h.t.Helper() - p := api.Project{} - code := h.do("POST", "/api/projects", api.ProjectCreate{Name: name, RepoPath: repoWithOrigin(h.t)}, &p) - if code != http.StatusCreated { - h.t.Fatalf("create project: %d", code) - } - return p -} - -func TestIdentityRequired(t *testing.T) { - h := newHarness(t) - - resp, err := http.Get(h.url + "/api/me") - if err != nil { - t.Fatalf("get: %v", err) - } - resp.Body.Close() - if resp.StatusCode != http.StatusUnauthorized { - t.Fatalf("want 401 without tailscale headers, got %d", resp.StatusCode) - } - - resp, err = http.Get(h.url + "/") - if err != nil { - t.Fatalf("get spa: %v", err) - } - resp.Body.Close() - if resp.StatusCode != http.StatusOK { - t.Fatalf("want spa shell without auth, got %d", resp.StatusCode) - } -} - -func TestServesEmbeddedSPA(t *testing.T) { - srv, err := server.New(t.TempDir(), store.DefaultPortFirst, web.FS) - if err != nil { - t.Fatalf("new server: %v", err) - } - t.Cleanup(func() { srv.Close() }) - hs := httptest.NewServer(srv.Handler()) - t.Cleanup(hs.Close) - - for path, contentType := range map[string]string{ - "/": "text/html", - "/style.css": "text/css", - "/dist/app.js": "text/javascript", - "/dist/app.js.map": "", - "/src/app.ts": "", - "/dist/inbox.js": "text/javascript", - "/dist/api.gen.js": "text/javascript", - "/dist/lead.js": "text/javascript", - "/dist/navbar.js": "text/javascript", - "/dist/panes.js": "text/javascript", - "/dist/task.js": "text/javascript", - "/dist/state.js": "text/javascript", - "/dist/dom.js": "text/javascript", - "/dist/api.js": "text/javascript", - "/dist/settings.js": "text/javascript", - "/dist/files.js": "text/javascript", - "/sw.js": "text/javascript", - "/dist/mermaid.js": "text/javascript", - "/vendor/mermaid.min.js": "text/javascript", - "/vendor/mermaid.LICENSE": "", - "/dist/terminal.js": "text/javascript", - "/vendor/xterm.mjs": "text/javascript", - "/vendor/addon-fit.mjs": "text/javascript", - "/vendor/xterm.css": "text/css", - "/vendor/xterm.LICENSE": "", - "/fonts/atkinson-hyperlegible-mono-latin-wght-normal.woff2": "font/woff2", - "/fonts/LICENSE": "", - } { - resp, err := http.Get(hs.URL + path) - if err != nil { - t.Fatalf("get %s: %v", path, err) - } - resp.Body.Close() - if resp.StatusCode != http.StatusOK { - t.Fatalf("get %s: %d", path, resp.StatusCode) - } - if contentType != "" && !strings.HasPrefix(resp.Header.Get("Content-Type"), contentType) { - t.Fatalf("get %s: content type %q, want %s", path, resp.Header.Get("Content-Type"), contentType) - } - } -} - -func TestMe(t *testing.T) { - h := newHarness(t) - - me := api.User{} - h.do("GET", "/api/me", nil, &me) - if me.Login != login || me.DisplayName != "Alice" || me.GitEmail != "" { - t.Fatalf("unexpected user %+v", me) - } - - code := h.do("PUT", "/api/me", api.UserUpdate{GitName: "Alice", GitEmail: "nope"}, nil) - if code != http.StatusBadRequest { - t.Fatalf("want 400 for bad email, got %d", code) - } - - code = h.do("PUT", "/api/me", api.UserUpdate{GitName: "Alice A", GitEmail: "alice@example.org"}, &me) - if code != http.StatusOK || me.GitName != "Alice A" || me.GitEmail != "alice@example.org" { - t.Fatalf("update failed: %d %+v", code, me) - } -} - -func TestNotificationSettings(t *testing.T) { - h := newHarness(t) - - me := api.User{} - h.do("GET", "/api/me", nil, &me) - if me.NotificationPrefs != (api.NotificationPrefs{BlockingQuestions: true}) { - t.Fatalf("unexpected default prefs %+v", me.NotificationPrefs) - } - - code := h.do("PUT", "/api/me/notifications", api.NotificationPrefs{BlockingQuestions: true, Reports: true}, &me) - if code != http.StatusOK || me.NotificationPrefs != (api.NotificationPrefs{BlockingQuestions: true, Reports: true}) { - t.Fatalf("update prefs: %d %+v", code, me.NotificationPrefs) - } - - key := api.PushKey{} - h.do("GET", "/api/push/key", nil, &key) - if len(key.PublicKey) < 80 { - t.Fatalf("unexpected vapid key %q", key.PublicKey) - } - - code = h.do("POST", "/api/me/push-subscriptions", api.PushSubscription{Endpoint: "http://push.example/x", P256dh: "k", Auth: "a"}, nil) - if code != http.StatusBadRequest { - t.Fatalf("want 400 for a non-https endpoint, got %d", code) - } - code = h.do("POST", "/api/me/push-subscriptions", api.PushSubscription{Endpoint: "https://push.example/x", P256dh: "k", Auth: "a"}, nil) - if code != http.StatusNoContent { - t.Fatalf("subscribe: %d", code) - } - code = h.do("DELETE", "/api/me/push-subscriptions?endpoint=https%3A%2F%2Fpush.example%2Fx", nil, nil) - if code != http.StatusNoContent { - t.Fatalf("unsubscribe: %d", code) - } -} - -func TestProjectAndTaskLifecycle(t *testing.T) { - h := newHarness(t) - p := h.createProject("demo") - - if p.DefaultBranch != "main" || !strings.HasSuffix(p.Remote, "origin.git") { - t.Fatalf("unexpected project %+v", p) - } - - code := h.do("POST", "/api/projects", api.ProjectCreate{Name: "demo", RepoPath: p.RepoPath}, nil) - if code != http.StatusBadRequest { - t.Fatalf("want 400 for duplicate project, got %d", code) - } - - task := api.Task{} - code = h.do("POST", "/api/projects/"+p.Id+"/tasks", api.TaskCreate{Title: "first"}, &task) - if code != http.StatusCreated { - t.Fatalf("create task: %d", code) - } - - _, err := os.Stat(filepath.Join(task.WorktreePath, "README")) - if err != nil { - t.Fatalf("worktree not checked out: %v", err) - } - head := gitOut(t, task.WorktreePath, "rev-parse", "--abbrev-ref", "HEAD") - if head != task.Branch { - t.Fatalf("worktree on %s, want %s", head, task.Branch) - } - if task.BaseRef != gitOut(t, p.RepoPath, "rev-parse", "main") { - t.Fatalf("base ref %s is not main's head", task.BaseRef) - } - - tasks := []api.Task{} - h.do("GET", "/api/projects/"+p.Id+"/tasks", nil, &tasks) - if len(tasks) != 1 || tasks[0].Id != task.Id { - t.Fatalf("unexpected task list %+v", tasks) - } - - err = os.WriteFile(filepath.Join(task.WorktreePath, "dirty"), []byte("x"), 0o644) - if err != nil { - t.Fatalf("write: %v", err) - } - - unmerged := api.Unmerged{} - h.do("GET", "/api/tasks/"+task.Id+"/unmerged", nil, &unmerged) - if unmerged != (api.Unmerged{Commits: 0, Files: 1, Insertions: 1, Deletions: 0, Uncommitted: 1}) { - t.Fatalf("unexpected unmerged %+v", unmerged) - } - - done := api.Task{} - code = h.do("POST", "/api/tasks/"+task.Id+"/discard", nil, &done) - if code != http.StatusOK || done.Status != api.TaskStatusDiscarded || done.ClosedAt == nil || done.MergeCommit != nil { - t.Fatalf("discard: %d %+v", code, done) - } - if gitOut(t, p.RepoPath, "rev-parse", "main") != task.BaseRef { - t.Fatalf("discard moved main") - } - - _, err = os.Stat(task.WorktreePath) - if !os.IsNotExist(err) { - t.Fatalf("worktree still exists: %v", err) - } - branches := gitOut(t, p.RepoPath, "branch", "--list", task.Branch) - if branches != "" { - t.Fatalf("branch still exists: %s", branches) - } - - code = h.do("POST", "/api/tasks/"+task.Id+"/discard", nil, nil) - if code != http.StatusConflict { - t.Fatalf("want 409 discarding twice, got %d", code) - } - code = h.do("POST", "/api/tasks/"+task.Id+"/merge", nil, nil) - if code != http.StatusConflict { - t.Fatalf("want 409 merging a discarded task, got %d", code) - } - - code = h.do("GET", "/api/tasks/nope", nil, nil) - if code != http.StatusNotFound { - t.Fatalf("want 404 for unknown task, got %d", code) - } -} - -func TestMergeTask(t *testing.T) { - h := newHarness(t) - p := h.createProject("demo") - - task := api.Task{} - h.do("POST", "/api/projects/"+p.Id+"/tasks", api.TaskCreate{Title: "Add notes"}, &task) - - coAuthors := []api.Participant{} - code := h.do("GET", "/api/tasks/"+task.Id+"/co-authors", nil, &coAuthors) - if code != http.StatusOK || len(coAuthors) != 0 { - t.Fatalf("co-authors: %d %+v", code, coAuthors) - } - code = h.do("GET", "/api/tasks/nope/co-authors", nil, nil) - if code != http.StatusNotFound { - t.Fatalf("want 404 for co-authors of an unknown task, got %d", code) - } - - code = h.do("POST", "/api/tasks/"+task.Id+"/merge", nil, nil) - if code != http.StatusBadRequest { - t.Fatalf("want 400 merging without a git identity, got %d", code) - } - - got := api.Task{} - h.do("GET", "/api/tasks/"+task.Id, nil, &got) - if got.Status != api.TaskStatusActive { - t.Fatalf("refused merge changed the task: %+v", got) - } -} - -func TestTaskImage(t *testing.T) { - h := newHarness(t) - p := h.createProject("demo") - - task := api.Task{} - h.do("POST", "/api/projects/"+p.Id+"/tasks", api.TaskCreate{Title: "images"}, &task) - - err := os.MkdirAll(filepath.Join(task.WorktreePath, "shots"), 0o755) - if err != nil { - t.Fatalf("mkdir: %v", err) - } - writeFile(t, filepath.Join(task.WorktreePath, "shots", "a.png"), "\x89PNG\r\n\x1a\nfake") - writeFile(t, filepath.Join(task.WorktreePath, "notes.txt"), "text") - outside := filepath.Join(t.TempDir(), "secret.png") - writeFile(t, outside, "secret") - err = os.Symlink(outside, filepath.Join(task.WorktreePath, "link.png")) - if err != nil { - t.Fatalf("symlink: %v", err) - } - sibling := task.WorktreePath + "x" - err = os.MkdirAll(sibling, 0o755) - if err != nil { - t.Fatalf("mkdir: %v", err) - } - writeFile(t, filepath.Join(sibling, "a.png"), "sibling") - - for path, want := range map[string]int{ - "shots/a.png": http.StatusOK, - filepath.Join(task.WorktreePath, "shots", "a.png"): http.StatusOK, - "shots/../shots/a.png": http.StatusOK, - "missing.png": http.StatusNotFound, - "notes.txt": http.StatusBadRequest, - "shots": http.StatusBadRequest, - "link.png": http.StatusForbidden, - outside: http.StatusForbidden, - "../" + filepath.Base(sibling) + "/a.png": http.StatusForbidden, - } { - req, err := http.NewRequest("GET", h.url+"/api/tasks/"+task.Id+"/image?path="+url.QueryEscape(path), nil) - if err != nil { - t.Fatalf("new request: %v", err) - } - req.Header.Set("Tailscale-User-Login", login) - req.Header.Set("Tailscale-User-Name", "Alice") - resp, err := http.DefaultClient.Do(req) - if err != nil { - t.Fatalf("get %s: %v", path, err) - } - body, _ := io.ReadAll(resp.Body) - resp.Body.Close() - if resp.StatusCode != want { - t.Fatalf("get %s: %d %s, want %d", path, resp.StatusCode, body, want) - } - if want == http.StatusOK && (resp.Header.Get("Content-Type") != "image/png" || !bytes.HasPrefix(body, []byte("\x89PNG"))) { - t.Fatalf("get %s: %q %q", path, resp.Header.Get("Content-Type"), body) - } - } -} - -func writeFile(t *testing.T, path, content string) { - t.Helper() - err := os.WriteFile(path, []byte(content), 0o644) - if err != nil { - t.Fatalf("write %s: %v", path, err) - } -} - -func TestEventStreamResume(t *testing.T) { - h := newHarness(t) - - stream := h.openStream("", nil) - p := h.createProject("demo") - first := stream.next() - if first.Kind != "project.created" || *first.ProjectId != p.Id { - t.Fatalf("unexpected first event %+v", first) - } - stream.close() - - task := api.Task{} - h.do("POST", "/api/projects/"+p.Id+"/tasks", api.TaskCreate{Title: "while away"}, &task) - h.do("PUT", "/api/me", api.UserUpdate{GitName: "Alice", GitEmail: "alice@example.org"}, nil) - - resumed := h.openStream("", &first.Seq) - missed := resumed.next() - if missed.Kind != "task.created" || *missed.TaskId != task.Id || missed.Seq != first.Seq+1 { - t.Fatalf("unexpected replayed event %+v", missed) - } - if next := resumed.next(); next.Kind != "user.updated" { - t.Fatalf("unexpected second replayed event %+v", next) - } - - h.do("POST", "/api/tasks/"+task.Id+"/discard", nil, nil) - if live := resumed.next(); live.Kind != "task.discarded" { - t.Fatalf("unexpected live event %+v", live) - } - - status := api.Status{} - h.do("GET", "/api/admin/status", nil, &status) - if status.LastEventSeq != 4 || status.Projects != 1 || status.ActiveTasks != 0 || status.SseClients < 1 { - t.Fatalf("unexpected status %+v", status) - } -} - -func TestTaskEventStream(t *testing.T) { - h := newHarness(t) - p := h.createProject("demo") - - mine := api.Task{} - h.do("POST", "/api/projects/"+p.Id+"/tasks", api.TaskCreate{Title: "mine"}, &mine) - other := api.Task{} - h.do("POST", "/api/projects/"+p.Id+"/tasks", api.TaskCreate{Title: "other"}, &other) - - stream := h.openStream("?task_id="+mine.Id, nil) - replayed := stream.next() - if replayed.Kind != "task.created" || *replayed.TaskId != mine.Id { - t.Fatalf("unexpected replayed event %+v", replayed) - } - - h.do("POST", "/api/tasks/"+other.Id+"/discard", nil, nil) - h.do("POST", "/api/tasks/"+mine.Id+"/discard", nil, nil) - if live := stream.next(); live.Kind != "task.discarded" || *live.TaskId != mine.Id { - t.Fatalf("unexpected live event %+v", live) - } - - code := h.do("GET", "/api/events?task_id=nope", nil, nil) - if code != http.StatusNotFound { - t.Fatalf("want 404 streaming an unknown task, got %d", code) - } -} - -type stream struct { - t *testing.T - cancel context.CancelFunc - resp *http.Response - r *bufio.Reader -} - -func (h *harness) openStream(query string, lastEventID *int64) *stream { - h.t.Helper() - - ctx, cancel := context.WithCancel(context.Background()) - req, err := http.NewRequestWithContext(ctx, "GET", h.url+"/api/events"+query, nil) - if err != nil { - h.t.Fatalf("new request: %v", err) - } - req.Header.Set("Tailscale-User-Login", login) - req.Header.Set("Tailscale-User-Name", "Alice") - if lastEventID != nil { - req.Header.Set("Last-Event-ID", fmt.Sprint(*lastEventID)) - } - - resp, err := http.DefaultClient.Do(req) - if err != nil { - h.t.Fatalf("open stream: %v", err) - } - if resp.StatusCode != http.StatusOK { - h.t.Fatalf("open stream: %d", resp.StatusCode) - } - - s := &stream{t: h.t, cancel: cancel, resp: resp, r: bufio.NewReader(resp.Body)} - h.t.Cleanup(s.close) - return s -} - -func (s *stream) next() api.Event { - s.t.Helper() - - type result struct { - ev api.Event - err error - } - ch := make(chan result, 1) - - go func() { - id := "" - data := "" - for { - line, err := s.r.ReadString('\n') - if err != nil { - ch <- result{err: err} - return - } - line = strings.TrimRight(line, "\n") - switch { - case strings.HasPrefix(line, "id: "): - id = strings.TrimPrefix(line, "id: ") - case strings.HasPrefix(line, "data: "): - data = strings.TrimPrefix(line, "data: ") - case line == "" && data != "": - ev := api.Event{} - err := json.Unmarshal([]byte(data), &ev) - if err == nil && fmt.Sprint(ev.Seq) != id { - err = fmt.Errorf("sse id %s does not match seq %d", id, ev.Seq) - } - ch <- result{ev: ev, err: err} - return - } - } - }() - - select { - case res := <-ch: - if res.err != nil { - s.t.Fatalf("read event: %v", res.err) - } - return res.ev - case <-time.After(5 * time.Second): - s.t.Fatalf("timed out waiting for event") - return api.Event{} - } -} - -func (s *stream) close() { - s.cancel() - s.resp.Body.Close() -} - -func repoWithOrigin(t *testing.T) string { - dir := t.TempDir() - origin := filepath.Join(dir, "origin.git") - repo := filepath.Join(dir, "repo") - - gitOut(t, dir, "init", "--bare", "--initial-branch=main", origin) - gitOut(t, dir, "init", "--initial-branch=main", repo) - gitOut(t, repo, "remote", "add", "origin", origin) - err := os.WriteFile(filepath.Join(repo, "README"), []byte("hi\n"), 0o644) - if err != nil { - t.Fatalf("write readme: %v", err) - } - gitOut(t, repo, "add", "README") - gitOut(t, repo, "commit", "--message", "root") - gitOut(t, repo, "push", "origin", "main") - return repo -} - -func gitOut(t *testing.T, dir string, args ...string) string { - t.Helper() - cmd := exec.Command("git", args...) - cmd.Dir = dir - cmd.Env = append(os.Environ(), - "GIT_AUTHOR_NAME=Test", - "GIT_AUTHOR_EMAIL=test@example.com", - "GIT_COMMITTER_NAME=Test", - "GIT_COMMITTER_EMAIL=test@example.com", - ) - out, err := cmd.CombinedOutput() - if err != nil { - t.Fatalf("git %v: %v\n%s", args, err, out) - } - return strings.TrimSpace(string(out)) -} diff --git a/internal/server/terminal_test.go b/internal/server/terminal_test.go deleted file mode 100644 index 28fae3e..0000000 --- a/internal/server/terminal_test.go +++ /dev/null @@ -1,147 +0,0 @@ -package server_test - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "net/http" - "path/filepath" - "strings" - "testing" - "time" - - "github.com/coder/websocket" - - "g.fc.run/misc/byre/internal/api" -) - -type termConn struct { - t *testing.T - conn *websocket.Conn - seen strings.Builder -} - -func (h *harness) openTerminal(taskID string) *termConn { - h.t.Helper() - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - header := http.Header{} - header.Set("Tailscale-User-Login", login) - header.Set("Tailscale-User-Name", "Alice") - url := "ws" + strings.TrimPrefix(h.url, "http") + "/api/tasks/" + taskID + "/terminal?cols=80&rows=24" - conn, _, err := websocket.Dial(ctx, url, &websocket.DialOptions{HTTPHeader: header}) - if err != nil { - h.t.Fatalf("dial terminal: %v", err) - } - h.t.Cleanup(func() { conn.CloseNow() }) - return &termConn{t: h.t, conn: conn} -} - -func (c *termConn) send(text string) { - c.t.Helper() - err := c.conn.Write(context.Background(), websocket.MessageBinary, []byte(text)) - if err != nil { - c.t.Fatalf("write terminal: %v", err) - } -} - -func (c *termConn) waitFor(want string) { - c.t.Helper() - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - for !strings.Contains(c.seen.String(), want) { - _, b, err := c.conn.Read(ctx) - if err != nil { - c.t.Fatalf("waiting for %q: %v; seen %q", want, err, c.seen.String()) - } - c.seen.Write(b) - } -} - -func (c *termConn) waitClosed() websocket.CloseError { - c.t.Helper() - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - for { - _, b, err := c.conn.Read(ctx) - if err == nil { - c.seen.Write(b) - continue - } - ce := websocket.CloseError{} - if !errors.As(err, &ce) { - c.t.Fatalf("want a close frame, got %v", err) - } - return ce - } -} - -func TestTaskTerminal(t *testing.T) { - t.Setenv("SHELL", "/bin/sh") - h := newHarness(t) - p := h.createProject("demo") - task := api.Task{} - h.do("POST", "/api/projects/"+p.Id+"/tasks", api.TaskCreate{Title: "terminal"}, &task) - - wt, err := filepath.EvalSymlinks(task.WorktreePath) - if err != nil { - t.Fatalf("resolve worktree: %v", err) - } - header := http.Header{} - header.Set("Tailscale-User-Login", login) - header.Set("Tailscale-User-Name", "Alice") - header.Set("Origin", "https://elsewhere.example") - _, resp, err := websocket.Dial(context.Background(), "ws"+strings.TrimPrefix(h.url, "http")+"/api/tasks/"+task.Id+"/terminal?cols=80&rows=24", &websocket.DialOptions{HTTPHeader: header}) - if err == nil || resp == nil || resp.StatusCode != http.StatusForbidden { - t.Fatalf("cross-origin terminal: want 403, got %v %v", resp, err) - } - - a := h.openTerminal(task.Id) - a.send("echo \"$PORT:$(pwd -P)\"\n") - a.waitFor(fmt.Sprintf("%d:%s", task.PortBase, wt)) - a.send("echo byre-$((40+2))\n") - a.waitFor("byre-42") - - size, err := json.Marshal(api.TerminalSize{Cols: 100, Rows: 30}) - if err != nil { - t.Fatalf("marshal size: %v", err) - } - err = a.conn.Write(context.Background(), websocket.MessageText, size) - if err != nil { - t.Fatalf("resize: %v", err) - } - a.send("stty size\n") - a.waitFor("30 100") - - b := h.openTerminal(task.Id) - b.waitFor("byre-42") - b.send("exit\n") - for _, c := range []*termConn{a, b} { - ce := c.waitClosed() - if ce.Code != 4000 || ce.Reason != "exit status 0" { - t.Fatalf("close after exit: %d %q", ce.Code, ce.Reason) - } - } - - c := h.openTerminal(task.Id) - c.send("echo fresh\n") - c.waitFor("fresh") - if strings.Contains(c.seen.String(), "byre-42") { - t.Fatalf("new shell replayed the old one's output: %q", c.seen.String()) - } - - code := h.do("POST", "/api/tasks/"+task.Id+"/discard", nil, nil) - if code != http.StatusOK { - t.Fatalf("discard: %d", code) - } - ce := c.waitClosed() - if ce.Code != 4000 { - t.Fatalf("close after discard: %d %q", ce.Code, ce.Reason) - } - - code = h.do("GET", "/api/tasks/"+task.Id+"/terminal?cols=80&rows=24", nil, nil) - if code != http.StatusConflict { - t.Fatalf("terminal of a discarded task: want 409, got %d", code) - } -} diff --git a/internal/store/blob_test.go b/internal/store/blob_test.go deleted file mode 100644 index 7290b42..0000000 --- a/internal/store/blob_test.go +++ /dev/null @@ -1,41 +0,0 @@ -package store_test - -import ( - "context" - "path/filepath" - "testing" - "time" - - "g.fc.run/misc/byre/internal/api" - "g.fc.run/misc/byre/internal/store" -) - -func TestBlobRoundTrip(t *testing.T) { - ctx := context.Background() - st, err := store.Open(filepath.Join(t.TempDir(), "byre.db"), store.DefaultPortFirst, func(api.Event) {}) - if err != nil { - t.Fatalf("open store: %v", err) - } - t.Cleanup(func() { st.Close() }) - - now := time.Now().UTC() - _, err = st.EnsureUser(ctx, "alice", "Alice") - if err != nil { - t.Fatalf("ensure user: %v", err) - } - err = st.CreateProject(ctx, api.Project{Id: "p1", Name: "demo", RepoPath: "/nonexistent", DefaultBranch: "main", Remote: "none", CreatedBy: "alice", CreatedAt: now}) - if err != nil { - t.Fatalf("create project: %v", err) - } - b, err := st.CreateBlob(ctx, "image/png", []byte("not really a png"), "alice") - if err != nil { - t.Fatalf("create blob: %v", err) - } - got, data, err := st.ReadBlob(ctx, b.Id) - if err != nil { - t.Fatalf("read blob: %v", err) - } - if got.ContentType != "image/png" || got.CreatedBy != "alice" || string(data) != "not really a png" { - t.Fatalf("blob round trip: %+v %q", got, data) - } -} diff --git a/internal/store/color_test.go b/internal/store/color_test.go deleted file mode 100644 index be1eef5..0000000 --- a/internal/store/color_test.go +++ /dev/null @@ -1,60 +0,0 @@ -package store_test - -import ( - "context" - "fmt" - "path/filepath" - "testing" - "time" - - "g.fc.run/misc/byre/internal/api" - "g.fc.run/misc/byre/internal/store" -) - -func TestTaskColorsAvoidActiveAndRecent(t *testing.T) { - ctx := context.Background() - st, err := store.Open(filepath.Join(t.TempDir(), "byre.db"), 51000, func(api.Event) {}) - if err != nil { - t.Fatalf("open store: %v", err) - } - t.Cleanup(func() { st.Close() }) - - now := time.Now().UTC() - _, err = st.EnsureUser(ctx, "alice", "Alice") - if err != nil { - t.Fatalf("ensure user: %v", err) - } - err = st.CreateProject(ctx, api.Project{Id: "p1", Name: "demo", RepoPath: "/nonexistent", DefaultBranch: "main", Remote: "none", CreatedBy: "alice", CreatedAt: now}) - if err != nil { - t.Fatalf("create project: %v", err) - } - - create := func(id string) int { - err := st.CreateTask(ctx, api.Task{Id: id, ProjectId: "p1", Title: id, Branch: id, WorktreePath: "/nonexistent", BaseRef: "x", Status: api.TaskStatusActive, CreatedBy: "alice", CreatedAt: now}, "alice") - if err != nil { - t.Fatalf("create task %s: %v", id, err) - } - task, err := st.GetTask(ctx, id) - if err != nil { - t.Fatalf("get task %s: %v", id, err) - } - return task.Color - } - - if c1, c2 := create("t1"), create("t2"); c1 != 0 || c2 != 1 { - t.Fatalf("colors = %d, %d, want 0, 1", c1, c2) - } - _, err = st.DiscardTask(ctx, "t1", "alice") - if err != nil { - t.Fatalf("discard: %v", err) - } - if c := create("t3"); c != 2 { - t.Fatalf("t3 color = %d, want 2, not the just-freed 0", c) - } - for i := 4; i <= store.TaskColors; i++ { - create(fmt.Sprintf("t%d", i)) - } - if c := create("t12"); c != 0 { - t.Fatalf("t12 color = %d, want 0, the only one no active task holds", c) - } -} diff --git a/internal/store/inbox_test.go b/internal/store/inbox_test.go deleted file mode 100644 index 8e66f5b..0000000 --- a/internal/store/inbox_test.go +++ /dev/null @@ -1,80 +0,0 @@ -package store_test - -import ( - "context" - "path/filepath" - "testing" - "time" - - "g.fc.run/misc/byre/internal/api" - "g.fc.run/misc/byre/internal/store" -) - -func TestClosingTaskDismissesOpenInbox(t *testing.T) { - ctx := context.Background() - events := []api.Event{} - st, err := store.Open(filepath.Join(t.TempDir(), "byre.db"), store.DefaultPortFirst, func(ev api.Event) { events = append(events, ev) }) - if err != nil { - t.Fatalf("open store: %v", err) - } - t.Cleanup(func() { st.Close() }) - - now := time.Now().UTC() - _, err = st.EnsureUser(ctx, "alice", "Alice") - if err != nil { - t.Fatalf("ensure user: %v", err) - } - err = st.CreateProject(ctx, api.Project{Id: "p1", Name: "demo", RepoPath: "/nonexistent", DefaultBranch: "main", Remote: "none", CreatedBy: "alice", CreatedAt: now}) - if err != nil { - t.Fatalf("create project: %v", err) - } - for _, id := range []string{"t1", "t2"} { - err = st.CreateTask(ctx, api.Task{Id: id, ProjectId: "p1", Title: id, Branch: id, WorktreePath: "/nonexistent", BaseRef: "x", Status: api.TaskStatusActive, CreatedBy: "alice", CreatedAt: now}, "alice") - if err != nil { - t.Fatalf("create task: %v", err) - } - err = st.CreateSession(ctx, api.AgentSession{Id: "s" + id, TaskId: id, Vendor: api.Claude, VendorSessionId: "s" + id, State: api.AgentSessionStateIdle, Role: api.Implementer, CreatedBy: "alice", CreatedAt: now}, "p1") - if err != nil { - t.Fatalf("create session: %v", err) - } - } - - for _, item := range []struct{ id, task string }{{"q1", "t1"}, {"r1", "t1"}, {"q2", "t2"}} { - err = st.CreateInboxItem(ctx, api.InboxItem{Id: item.id, ProjectId: "p1", TaskId: item.task, AgentSessionId: "s" + item.task, ThreadId: item.id, - Kind: api.Question, Priority: api.Blocked, Blocking: true, Payload: map[string]any{}, State: api.Open, CreatedAt: now}) - if err != nil { - t.Fatalf("create inbox item: %v", err) - } - } - - events = events[:0] - _, err = st.DiscardTask(ctx, "t1", "alice") - if err != nil { - t.Fatalf("discard: %v", err) - } - - for _, id := range []string{"q1", "r1"} { - item, err := st.GetInboxItem(ctx, id) - if err != nil { - t.Fatalf("get %s: %v", id, err) - } - if item.State != api.Dismissed || item.ResolvedBy == nil || *item.ResolvedBy != "alice" || item.ResolvedAt == nil { - t.Fatalf("%s not dismissed by alice: %+v", id, item) - } - } - other, err := st.GetInboxItem(ctx, "q2") - if err != nil { - t.Fatalf("get q2: %v", err) - } - if other.State != api.Open { - t.Fatalf("other task's item changed: %+v", other) - } - - kinds := []string{} - for _, ev := range events { - kinds = append(kinds, ev.Kind) - } - if len(kinds) != 3 || kinds[0] != "task.discarded" || kinds[1] != "inbox.updated" || kinds[2] != "inbox.updated" { - t.Fatalf("unexpected events %v", kinds) - } -} diff --git a/internal/store/merging_test.go b/internal/store/merging_test.go deleted file mode 100644 index 7494048..0000000 --- a/internal/store/merging_test.go +++ /dev/null @@ -1,62 +0,0 @@ -package store_test - -import ( - "context" - "path/filepath" - "testing" - "time" - - "g.fc.run/misc/byre/internal/api" - "g.fc.run/misc/byre/internal/store" -) - -func TestMergingBySurvivesReopen(t *testing.T) { - ctx := context.Background() - path := filepath.Join(t.TempDir(), "byre.db") - st, err := store.Open(path, store.DefaultPortFirst, func(api.Event) {}) - if err != nil { - t.Fatalf("open store: %v", err) - } - - now := time.Now().UTC() - _, err = st.EnsureUser(ctx, "alice", "Alice") - if err != nil { - t.Fatalf("ensure user: %v", err) - } - err = st.CreateProject(ctx, api.Project{Id: "p1", Name: "demo", RepoPath: "/nonexistent", DefaultBranch: "main", Remote: "none", CreatedBy: "alice", CreatedAt: now}) - if err != nil { - t.Fatalf("create project: %v", err) - } - err = st.CreateTask(ctx, api.Task{Id: "t1", ProjectId: "p1", Title: "t1", Branch: "t1", WorktreePath: "/nonexistent", BaseRef: "x", Status: api.TaskStatusActive, CreatedBy: "alice", CreatedAt: now}, "alice") - if err != nil { - t.Fatalf("create task: %v", err) - } - - err = st.SetMergingBy(ctx, "t1", "alice") - if err != nil { - t.Fatalf("set merging by: %v", err) - } - st.Close() - - st, err = store.Open(path, store.DefaultPortFirst, func(api.Event) {}) - if err != nil { - t.Fatalf("reopen store: %v", err) - } - t.Cleanup(func() { st.Close() }) - - login, err := st.TakeMergingBy(ctx, "t1") - if err != nil { - t.Fatalf("take merging by: %v", err) - } - if login != "alice" { - t.Fatalf("merging by = %q, want alice", login) - } - - login, err = st.TakeMergingBy(ctx, "t1") - if err != nil { - t.Fatalf("take merging by again: %v", err) - } - if login != "" { - t.Fatalf("merging by after take = %q, want empty", login) - } -} diff --git a/internal/store/migrate_internal_test.go b/internal/store/migrate_internal_test.go deleted file mode 100644 index c748314..0000000 --- a/internal/store/migrate_internal_test.go +++ /dev/null @@ -1,270 +0,0 @@ -package store - -import ( - "database/sql" - "path/filepath" - "slices" - "testing" -) - -func openTestDB(t *testing.T, path string) *sql.DB { - t.Helper() - db, err := sql.Open("sqlite", "file:"+path+"?_pragma=foreign_keys(1)&_txlock=immediate") - if err != nil { - t.Fatalf("open db: %v", err) - } - t.Cleanup(func() { db.Close() }) - return db -} - -func appliedNames(t *testing.T, db *sql.DB) []string { - t.Helper() - rows, err := db.Query(`SELECT name FROM migrations ORDER BY rowid`) - if err != nil { - t.Fatalf("list migrations: %v", err) - } - defer rows.Close() - - names := []string{} - for rows.Next() { - name := "" - err = rows.Scan(&name) - if err != nil { - t.Fatalf("scan migration: %v", err) - } - names = append(names, name) - } - if rows.Err() != nil { - t.Fatalf("list migrations: %v", rows.Err()) - } - return names -} - -func migrationNames(ms []migration) []string { - names := []string{} - for _, m := range ms { - names = append(names, m.name) - } - return names -} - -func insertReport(t *testing.T, db *sql.DB, id, payload string) { - t.Helper() - _, err := db.Exec(` - INSERT OR IGNORE INTO users (login, display_name, created_at) VALUES ('alice', 'Alice', 'x'); - INSERT OR IGNORE INTO projects (id, name, repo_path, default_branch, remote, created_by, created_at) VALUES ('p1', 'demo', '/nonexistent', 'main', 'none', 'alice', 'x'); - INSERT OR IGNORE INTO tasks (id, project_id, title, branch, worktree_path, base_ref, status, created_by, created_at) VALUES ('t1', 'p1', 't1', 't1', '/nonexistent', 'x', 'active', 'alice', 'x'); - INSERT OR IGNORE INTO agent_sessions (id, task_id, vendor, model, vendor_session_id, state, role, created_by, created_at) VALUES ('s1', 't1', 'claude', '', 's1', 'idle', 'implementer', 'alice', 'x'); - `) - if err != nil { - t.Fatalf("insert parents: %v", err) - } - _, err = db.Exec(` - INSERT INTO inbox_items (id, project_id, task_id, agent_session_id, thread_id, kind, priority, blocking, payload, state, created_at) - VALUES (?, 'p1', 't1', 's1', ?, 'report', 0, 0, ?, 'open', 'x') - `, id, id, payload) - if err != nil { - t.Fatalf("insert report: %v", err) - } -} - -func reportPayload(t *testing.T, db *sql.DB, id string) string { - t.Helper() - payload := "" - err := db.QueryRow(`SELECT payload FROM inbox_items WHERE id = ?`, id).Scan(&payload) - if err != nil { - t.Fatalf("read report: %v", err) - } - return payload -} - -func TestMigrateFreshDatabase(t *testing.T) { - db := openTestDB(t, filepath.Join(t.TempDir(), "byre.db")) - - err := migrate(db, migrations) - if err != nil { - t.Fatalf("migrate: %v", err) - } - err = migrate(db, migrations) - if err != nil { - t.Fatalf("migrate again: %v", err) - } - - got := appliedNames(t, db) - want := migrationNames(migrations) - if !slices.Equal(got, want) { - t.Fatalf("applied %v, want %v", got, want) - } -} - -func TestMigrateFromUserVersion(t *testing.T) { - path := filepath.Join(t.TempDir(), "byre.db") - db := openTestDB(t, path) - for _, m := range migrations[:9] { - _, err := db.Exec(m.sql) - if err != nil { - t.Fatalf("old migration %s: %v", m.name, err) - } - } - _, err := db.Exec(`PRAGMA user_version = 9`) - if err != nil { - t.Fatalf("set user_version: %v", err) - } - insertReport(t, db, "r1", `{"text":"- shipped"}`) - db.Close() - - db = openTestDB(t, path) - err = migrate(db, migrations) - if err != nil { - t.Fatalf("migrate: %v", err) - } - - got := appliedNames(t, db) - want := migrationNames(migrations) - if !slices.Equal(got, want) { - t.Fatalf("applied %v, want %v", got, want) - } - payload := reportPayload(t, db, "r1") - if payload != `{"text":"- shipped"}` { - t.Fatalf("report payload %s, want it unchanged", payload) - } -} - -func TestMigrateRunsMigrationAddedBeforeApplied(t *testing.T) { - db := openTestDB(t, filepath.Join(t.TempDir(), "byre.db")) - first := migration{"create-a", `CREATE TABLE a (x TEXT)`} - other := migration{"create-b", `CREATE TABLE b (x TEXT)`} - last := migration{"create-c", `CREATE TABLE c (x TEXT)`} - - err := migrate(db, []migration{first, last}) - if err != nil { - t.Fatalf("migrate: %v", err) - } - err = migrate(db, []migration{first, other, last}) - if err != nil { - t.Fatalf("migrate with other branch: %v", err) - } - - got := appliedNames(t, db) - want := []string{"create-a", "create-c", "create-b"} - if !slices.Equal(got, want) { - t.Fatalf("applied %v, want %v", got, want) - } - _, err = db.Exec(`INSERT INTO b (x) VALUES ('y')`) - if err != nil { - t.Fatalf("table from other branch: %v", err) - } -} - -func TestBlobsMigrationOverPositionalCopy(t *testing.T) { - db := openTestDB(t, filepath.Join(t.TempDir(), "byre.db")) - err := migrate(db, migrations[:9]) - if err != nil { - t.Fatalf("migrate: %v", err) - } - insertReport(t, db, "r1", `{"text":"x"}`) - _, err = db.Exec(` - CREATE TABLE blobs (id TEXT PRIMARY KEY, content_type TEXT NOT NULL, path TEXT NOT NULL, created_by TEXT NOT NULL REFERENCES users(login), created_at TEXT NOT NULL); - INSERT INTO blobs VALUES ('b1', 'image/png', 'blobs/b1', 'alice', 'x'); - ALTER TABLE turn_queue ADD COLUMN images TEXT NOT NULL DEFAULT '[]'; - INSERT INTO turn_queue (session_id, actor, text, created_at) VALUES ('s1', 'alice', 'queued', 'x'); - `) - if err != nil { - t.Fatalf("positional copy: %v", err) - } - - through := slices.IndexFunc(migrations, func(m migration) bool { return m.name == "add-blobs-and-queued-images" }) - err = migrate(db, migrations[:through+1]) - if err != nil { - t.Fatalf("migrate: %v", err) - } - - text := "" - images := "" - err = db.QueryRow(`SELECT text, images FROM turn_queue WHERE session_id = 's1'`).Scan(&text, &images) - if err != nil { - t.Fatalf("queued turn: %v", err) - } - if text != "queued" || images != "[]" { - t.Fatalf("queued turn %q %q", text, images) - } - blobs := 0 - err = db.QueryRow(`SELECT count(*) FROM blobs`).Scan(&blobs) - if err != nil || blobs != 1 { - t.Fatalf("blobs %d: %v", blobs, err) - } -} - -func TestInlineQuestionChoicesMigration(t *testing.T) { - db := openTestDB(t, filepath.Join(t.TempDir(), "byre.db")) - i := slices.IndexFunc(migrations, func(m migration) bool { return m.name == "inline-question-choices" }) - err := migrate(db, migrations[:i]) - if err != nil { - t.Fatalf("migrate: %v", err) - } - insertReport(t, db, "r1", `{"text":"x"}`) - _, err = db.Exec(` - INSERT INTO inbox_items (id, project_id, task_id, agent_session_id, thread_id, kind, priority, blocking, payload, state, created_at) - VALUES ('q1', 'p1', 't1', 's1', 'q1', 'question', 0, 1, - '{"context":"Pick a color.","question":"Which?","why_blocked":"w","options":["red","blue"],"recommended_option":"blue","attachments":[]}', 'answered', 'x'); - INSERT INTO inbox_responses (id, item_id, user_login, type, option, text, attachments, created_at) VALUES ('a1', 'q1', 'alice', 'option', 'red', NULL, '[]', 'x'); - INSERT INTO inbox_responses (id, item_id, user_login, type, option, text, attachments, created_at) VALUES ('a2', 'q1', 'alice', 'other', NULL, 'green', '[]', 'x'); - `) - if err != nil { - t.Fatalf("insert question: %v", err) - } - - err = migrate(db, migrations) - if err != nil { - t.Fatalf("migrate: %v", err) - } - - payload := reportPayload(t, db, "q1") - want := `{"context":"Pick a color.\n\n- ( ) red\n- (*) blue","question":"Which?","why_blocked":"w","options":["red","blue"],"attachments":[],"multiple":false,"recommended":["blue"]}` - if payload != want { - t.Fatalf("question payload\n got %s\nwant %s", payload, want) - } - if reportPayload(t, db, "r1") != `{"text":"x"}` { - t.Fatalf("report payload changed") - } - - options := map[string]sql.NullString{} - rows, err := db.Query(`SELECT id, options FROM inbox_responses`) - if err != nil { - t.Fatalf("responses: %v", err) - } - defer rows.Close() - for rows.Next() { - id := "" - o := sql.NullString{} - err = rows.Scan(&id, &o) - if err != nil { - t.Fatalf("scan: %v", err) - } - options[id] = o - } - if options["a1"].String != `["red"]` || options["a2"].Valid { - t.Fatalf("response options %+v", options) - } -} - -func TestFlattenReportPayloadsRunsTwice(t *testing.T) { - db := openTestDB(t, filepath.Join(t.TempDir(), "byre.db")) - err := migrate(db, migrations) - if err != nil { - t.Fatalf("migrate: %v", err) - } - insertReport(t, db, "r1", `{"done":["one","two"]}`) - - i := slices.IndexFunc(migrations, func(m migration) bool { return m.name == "flatten-report-payloads-to-text" }) - for range 2 { - _, err = db.Exec(migrations[i].sql) - if err != nil { - t.Fatalf("flatten: %v", err) - } - payload := reportPayload(t, db, "r1") - if payload != `{"text":"- one\n- two"}` { - t.Fatalf("report payload %s", payload) - } - } -} diff --git a/internal/store/ports_test.go b/internal/store/ports_test.go deleted file mode 100644 index 82d03bb..0000000 --- a/internal/store/ports_test.go +++ /dev/null @@ -1,58 +0,0 @@ -package store_test - -import ( - "context" - "path/filepath" - "testing" - "time" - - "g.fc.run/misc/byre/internal/api" - "g.fc.run/misc/byre/internal/store" -) - -func TestPortBlocksStartAtPortFirst(t *testing.T) { - ctx := context.Background() - st, err := store.Open(filepath.Join(t.TempDir(), "byre.db"), 51000, func(api.Event) {}) - if err != nil { - t.Fatalf("open store: %v", err) - } - t.Cleanup(func() { st.Close() }) - - now := time.Now().UTC() - _, err = st.EnsureUser(ctx, "alice", "Alice") - if err != nil { - t.Fatalf("ensure user: %v", err) - } - err = st.CreateProject(ctx, api.Project{Id: "p1", Name: "demo", RepoPath: "/nonexistent", DefaultBranch: "main", Remote: "none", CreatedBy: "alice", CreatedAt: now}) - if err != nil { - t.Fatalf("create project: %v", err) - } - - for i, want := range []int{51000, 51010} { - id := []string{"t1", "t2"}[i] - err = st.CreateTask(ctx, api.Task{Id: id, ProjectId: "p1", Title: id, Branch: id, WorktreePath: "/nonexistent", BaseRef: "x", Status: api.TaskStatusActive, CreatedBy: "alice", CreatedAt: now}, "alice") - if err != nil { - t.Fatalf("create task %s: %v", id, err) - } - task, err := st.GetTask(ctx, id) - if err != nil { - t.Fatalf("get task %s: %v", id, err) - } - if task.PortBase != want { - t.Fatalf("task %s port_base = %d, want %d", id, task.PortBase, want) - } - } - - _, err = st.DiscardTask(ctx, "t1", "alice") - if err != nil { - t.Fatalf("discard: %v", err) - } - err = st.CreateTask(ctx, api.Task{Id: "t3", ProjectId: "p1", Title: "t3", Branch: "t3", WorktreePath: "/nonexistent", BaseRef: "x", Status: api.TaskStatusActive, CreatedBy: "alice", CreatedAt: now}, "alice") - if err != nil { - t.Fatalf("create task t3: %v", err) - } - reused, err := st.GetTask(ctx, "t3") - if err != nil || reused.PortBase != 51000 { - t.Fatalf("t3 port_base = %d, want the freed 51000: %v", reused.PortBase, err) - } -} diff --git a/internal/store/preview_test.go b/internal/store/preview_test.go deleted file mode 100644 index f6e5384..0000000 --- a/internal/store/preview_test.go +++ /dev/null @@ -1,90 +0,0 @@ -package store_test - -import ( - "context" - "errors" - "fmt" - "path/filepath" - "slices" - "testing" - "time" - - "g.fc.run/misc/byre/internal/api" - "g.fc.run/misc/byre/internal/store" -) - -func TestSetPreviewReplacesNameAndPort(t *testing.T) { - ctx := context.Background() - events := []api.Event{} - st, err := store.Open(filepath.Join(t.TempDir(), "byre.db"), store.DefaultPortFirst, func(ev api.Event) { events = append(events, ev) }) - if err != nil { - t.Fatalf("open store: %v", err) - } - t.Cleanup(func() { st.Close() }) - - now := time.Now().UTC() - _, err = st.EnsureUser(ctx, "alice", "Alice") - if err != nil { - t.Fatalf("ensure user: %v", err) - } - err = st.CreateProject(ctx, api.Project{Id: "p1", Name: "demo", RepoPath: "/nonexistent", DefaultBranch: "main", Remote: "none", CreatedBy: "alice", CreatedAt: now}) - if err != nil { - t.Fatalf("create project: %v", err) - } - err = st.CreateTask(ctx, api.Task{Id: "t1", ProjectId: "p1", Title: "t1", Branch: "t1", WorktreePath: "/nonexistent", BaseRef: "x", Status: api.TaskStatusActive, CreatedBy: "alice", CreatedAt: now}, "alice") - if err != nil { - t.Fatalf("create task: %v", err) - } - - set := func(name string, port int) []int { - t.Helper() - dropped, err := st.SetPreview(ctx, api.Preview{TaskId: "t1", Name: name, Port: port, CreatedAt: now}, "agent") - if err != nil { - t.Fatalf("set preview %s:%d: %v", name, port, err) - } - return dropped - } - list := func() []string { - t.Helper() - previews, err := st.ListPreviews(ctx, "t1") - if err != nil { - t.Fatalf("list previews: %v", err) - } - out := []string{} - for _, p := range previews { - out = append(out, fmt.Sprintf("%s:%d", p.Name, p.Port)) - } - return out - } - - if d := set("web", 41001); len(d) != 0 { - t.Fatalf("first preview dropped %v", d) - } - if d := set("api", 41002); len(d) != 0 { - t.Fatalf("second preview dropped %v", d) - } - if d := set("web", 41001); len(d) != 0 { - t.Fatalf("re-exposing the same name and port dropped %v", d) - } - if d := set("web", 41003); !slices.Equal(d, []int{41001}) { - t.Fatalf("moving web to 41003 dropped %v, want [41001]", d) - } - if d := set("storybook", 41002); len(d) != 0 { - t.Fatalf("renaming 41002 dropped %v, want none", d) - } - if got, want := list(), []string{"storybook:41002", "web:41003"}; !slices.Equal(got, want) { - t.Fatalf("previews = %v, want %v", got, want) - } - if last := events[len(events)-1]; last.Kind != "preview.exposed" || last.Payload["name"] != "storybook" { - t.Fatalf("last event = %s %v", last.Kind, last.Payload) - } - - _, err = st.DiscardTask(ctx, "t1", "alice") - if err != nil { - t.Fatalf("discard: %v", err) - } - _, err = st.SetPreview(ctx, api.Preview{TaskId: "t1", Name: "late", Port: 41004, CreatedAt: now}, "agent") - if !errors.Is(err, store.ErrNotFound) { - t.Fatalf("set preview on a closed task: %v, want ErrNotFound", err) - } -} diff --git a/internal/store/review_test.go b/internal/store/review_test.go deleted file mode 100644 index 6d1979f..0000000 --- a/internal/store/review_test.go +++ /dev/null @@ -1,102 +0,0 @@ -package store_test - -import ( - "context" - "errors" - "path/filepath" - "testing" - "time" - - "g.fc.run/misc/byre/internal/api" - "g.fc.run/misc/byre/internal/store" -) - -func TestReviewLifecycle(t *testing.T) { - ctx := context.Background() - st, err := store.Open(filepath.Join(t.TempDir(), "byre.db"), store.DefaultPortFirst, func(api.Event) {}) - if err != nil { - t.Fatalf("open store: %v", err) - } - t.Cleanup(func() { st.Close() }) - - now := time.Now().UTC() - _, err = st.EnsureUser(ctx, "alice", "Alice") - if err != nil { - t.Fatalf("ensure user: %v", err) - } - err = st.CreateProject(ctx, api.Project{Id: "p1", Name: "demo", RepoPath: "/nonexistent", DefaultBranch: "main", Remote: "none", CreatedBy: "alice", CreatedAt: now}) - if err != nil { - t.Fatalf("create project: %v", err) - } - err = st.CreateTask(ctx, api.Task{Id: "t1", ProjectId: "p1", Title: "t1", Branch: "t1", WorktreePath: "/nonexistent", BaseRef: "x", Status: api.TaskStatusActive, CreatedBy: "alice", CreatedAt: now}, "alice") - if err != nil { - t.Fatalf("create task: %v", err) - } - for _, s := range []struct { - id string - role api.AgentSessionRole - }{{"impl", api.Implementer}, {"rev1", api.Reviewer}, {"rev2", api.Reviewer}} { - err = st.CreateSession(ctx, api.AgentSession{Id: s.id, TaskId: "t1", Vendor: api.Claude, VendorSessionId: s.id, State: api.AgentSessionStateIdle, Role: s.role, CreatedBy: "alice", CreatedAt: now}, "p1") - if err != nil { - t.Fatalf("create session: %v", err) - } - } - sessions, err := st.ListSessions(ctx, "t1") - if err != nil || len(sessions) != 1 || sessions[0].Id != "impl" { - t.Fatalf("task sessions %+v %v, want only the implementer", sessions, err) - } - - snap := func(n int, at time.Time) { - t.Helper() - err := st.CreateSnapshot(ctx, api.Snapshot{Id: "s" + string(rune('0'+n)), TaskId: "t1", AgentSessionId: "impl", N: n, CommitSha: "c", TreeSha: "tree" + string(rune('0'+n)), EventSeq: 1, Trigger: api.SnapshotTriggerTurnEnd, CreatedAt: at}, "p1") - if err != nil { - t.Fatalf("create snapshot %d: %v", n, err) - } - } - snap(1, now) - - asked := now.Add(time.Second) - err = st.CreateInboxItem(ctx, api.InboxItem{Id: "q1", ProjectId: "p1", TaskId: "t1", AgentSessionId: "impl", ThreadId: "q1", - Kind: api.Question, Priority: api.Blocked, Blocking: true, Payload: map[string]any{}, State: api.Open, CreatedAt: asked}) - if err != nil { - t.Fatalf("create question: %v", err) - } - snap(2, asked.Add(time.Second)) - - for _, r := range []api.Review{ - {Id: "r1", TaskId: "t1", SnapshotN: 1, AgentSessionId: "rev1", Vendor: "codex", Model: "m", State: api.ReviewStateRunning, CreatedAt: now}, - {Id: "r2", TaskId: "t1", SnapshotN: 2, AgentSessionId: "rev2", Vendor: "codex", Model: "m", State: api.ReviewStateRunning, CreatedAt: now}, - } { - err = st.CreateReview(ctx, r) - if err != nil { - t.Fatalf("create review: %v", err) - } - } - - byRev, err := st.ReviewBySession(ctx, "rev2") - if err != nil || byRev.Id != "r2" { - t.Fatalf("review by session %+v %v", byRev, err) - } - - err = st.FinishReview(ctx, "r2", api.ReviewStateDone, map[string]any{"summary": "fine", "findings": []any{}}, "") - if err != nil { - t.Fatalf("finish: %v", err) - } - err = st.FinishReview(ctx, "r2", api.ReviewStateFailed, nil, "late") - if !errors.Is(err, store.ErrNotFound) { - t.Fatalf("finishing a finished review: %v, want not found", err) - } - - item, err := st.GetInboxItem(ctx, "q1") - if err != nil { - t.Fatalf("get question: %v", err) - } - if len(item.Reviews) != 1 || item.Reviews[0].Id != "r2" || item.Reviews[0].Result == nil || (*item.Reviews[0].Result)["summary"] != "fine" { - t.Fatalf("question's reviews %+v, want the review of the snapshot after it", item.Reviews) - } - - running, err := st.RunningReviews(ctx) - if err != nil || len(running) != 1 || running[0].Id != "r1" { - t.Fatalf("running reviews %+v %v", running, err) - } -} diff --git a/internal/store/store.go b/internal/store/store.go index 80f61b2..e716ca4 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -305,6 +305,16 @@ CREATE TABLE reviews ( UNIQUE (task_id, snapshot_n) ); CREATE UNIQUE INDEX reviews_session ON reviews(agent_session_id); +`}, {"add-task-inputs", ` +CREATE TABLE task_inputs ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + task_id TEXT NOT NULL REFERENCES tasks(id), + actor TEXT NOT NULL, + text TEXT NOT NULL, + images TEXT NOT NULL DEFAULT '[]', + created_at TEXT NOT NULL +); +CREATE INDEX task_inputs_task ON task_inputs(task_id, id); `}} const ( @@ -783,6 +793,10 @@ func (s *Store) closeTask(ctx context.Context, id string, status api.TaskStatus, if err != nil { return err } + _, err = tx.ExecContext(ctx, `DELETE FROM task_inputs WHERE task_id = ?`, id) + if err != nil { + return err + } return dismissTaskInbox(ctx, tx, emit, id, actor) }) if err != nil { diff --git a/internal/store/taskinputs.go b/internal/store/taskinputs.go new file mode 100644 index 0000000..05cb7d8 --- /dev/null +++ b/internal/store/taskinputs.go @@ -0,0 +1,70 @@ +package store + +import ( + "context" + "database/sql" + "encoding/json" + "time" +) + +type TaskInput struct { + ID int64 + TaskID string + Actor string + Text string + Images []string + CreatedAt time.Time +} + +func (s *Store) AddTaskInput(ctx context.Context, in TaskInput) error { + images, err := json.Marshal(append([]string{}, in.Images...)) + if err != nil { + return err + } + return s.write(ctx, func(tx *sql.Tx, emit emitFunc) error { + _, err := tx.ExecContext(ctx, `INSERT INTO task_inputs (task_id, actor, text, images, created_at) VALUES (?, ?, ?, ?, ?)`, + in.TaskID, in.Actor, in.Text, string(images), now()) + return err + }) +} + +func (s *Store) TaskInputs(ctx context.Context, taskID string) ([]TaskInput, error) { + rows, err := s.db.QueryContext(ctx, `SELECT id, task_id, actor, text, images, created_at FROM task_inputs WHERE task_id = ? ORDER BY id`, taskID) + if err != nil { + return nil, err + } + defer rows.Close() + + inputs := []TaskInput{} + for rows.Next() { + in := TaskInput{} + images := "" + createdAt := "" + err = rows.Scan(&in.ID, &in.TaskID, &in.Actor, &in.Text, &images, &createdAt) + if err != nil { + return nil, err + } + err = json.Unmarshal([]byte(images), &in.Images) + if err != nil { + return nil, err + } + in.CreatedAt, err = parseTime(createdAt) + if err != nil { + return nil, err + } + inputs = append(inputs, in) + } + return inputs, rows.Err() +} + +func (s *Store) DeleteTaskInputs(ctx context.Context, ids []int64) error { + return s.write(ctx, func(tx *sql.Tx, emit emitFunc) error { + for _, id := range ids { + _, err := tx.ExecContext(ctx, `DELETE FROM task_inputs WHERE id = ?`, id) + if err != nil { + return err + } + } + return nil + }) +} diff --git a/internal/todo/todo_test.go b/internal/todo/todo_test.go deleted file mode 100644 index 34d654a..0000000 --- a/internal/todo/todo_test.go +++ /dev/null @@ -1,58 +0,0 @@ -package todo_test - -import ( - "reflect" - "testing" - - "g.fc.run/misc/byre/internal/api" - "g.fc.run/misc/byre/internal/todo" -) - -func TestParse(t *testing.T) { - src := "# TODO\n\n" + - "Notes before the list are not items.\n\n" + - "- Public key pinning\n" + - " When libcurl4-openssl-dev >= 7.39.0, add support to the C client via the\n" + - " options struct for public key pinning.\n" + - "\n" + - " ```\n" + - " - not an item\n" + - " ```\n" + - "- [ ] Client ID rotation\n" + - " - JS client\n" + - " - C client\n" + - "- [x] Already done\n" + - " with a body\n" + - "\n" + - "## Web\n\n" + - "1. Double-click zoom in\n" + - "2. Pinch to zoom\n" + - "\n" + - "A trailing paragraph.\n" - - want := []api.TodoItem{ - {Title: "Public key pinning", Section: "TODO", Body: "When libcurl4-openssl-dev >= 7.39.0, add support to the C client via the\noptions struct for public key pinning.\n\n```\n- not an item\n```"}, - {Title: "Client ID rotation", Section: "TODO", Body: "- JS client\n- C client"}, - {Title: "Double-click zoom in", Section: "Web"}, - {Title: "Pinch to zoom", Section: "Web"}, - } - - got := todo.Parse(src) - if !reflect.DeepEqual(got, want) { - t.Fatalf("parse:\n got %#v\nwant %#v", got, want) - } - - item, ok := todo.Find(got, "Client ID rotation") - if !ok { - t.Fatalf("find: no item") - } - if md := todo.Markdown(item); md != "- Client ID rotation\n - JS client\n - C client" { - t.Fatalf("markdown: %q", md) - } -} - -func TestParseEmpty(t *testing.T) { - if got := todo.Parse(""); len(got) != 0 { - t.Fatalf("parse empty: %#v", got) - } -} diff --git a/web/dist/lead.js b/web/dist/lead.js index adae11a..2774320 100644 --- a/web/dist/lead.js +++ b/web/dist/lead.js @@ -14,7 +14,7 @@ const leadSource = { streamTopic: "lead.stream", empty: "", canSend: () => true, - placeholder: (s) => (s === "running" ? "Message your lead; it gets this when its turn ends" : "Message your lead. Paste or drop images to attach them."), + placeholder: (s) => (s === "running" ? "Message your lead; it reads this after its next tool call, or when its turn ends" : "Message your lead. Paste or drop images to attach them."), send: async ({ text, images, model, firstModel, sessionId }) => { await post("/api/me/lead/messages", { body: { text, images, model: sessionId ? model : firstModel } }); }, diff --git a/web/dist/lead.js.map b/web/dist/lead.js.map index 4934f6b..05f0c1e 100644 --- a/web/dist/lead.js.map +++ b/web/dist/lead.js.map @@ -1 +1 @@ -{"version":3,"file":"lead.js","sourceRoot":"","sources":["../src/lead.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAChC,OAAO,EAAE,CAAC,EAAE,MAAM,UAAU,CAAC;AAC7B,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,SAAS,EAA8B,MAAM,WAAW,CAAC;AAEjF,MAAM,UAAU,GAAe;IAC7B,GAAG,EAAE,GAAG,EAAE,CAAC,MAAM;IACjB,MAAM,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,cAAc;IAClC,OAAO,EAAE,WAAW;IACpB,IAAI,EAAE,GAAG,EAAE,CAAC,EAAE;IACd,MAAM,EAAE,GAAG,EAAE,CAAC,SAAS;IACvB,WAAW,EAAE,EAAE;IACf,aAAa,EAAE,CAAC,eAAe,CAAC;IAChC,eAAe,EAAE,iBAAiB;IAClC,WAAW,EAAE,aAAa;IAC1B,KAAK,EAAE,EAAE;IACT,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI;IACnB,WAAW,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,oDAAoD,CAAC,CAAC,CAAC,yDAAyD,CAAC;IACxJ,IAAI,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,EAAE,EAAE;QAC7D,MAAM,IAAI,CAAC,uBAAuB,EAAE,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IACzG,CAAC;CACF,CAAC;AAEF,SAAS,QAAQ;IACf,OAAO;QACL,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE;YAChB,MAAM,IAAI,GAAG,WAAW,EAAE,CAAC;YAC3B,MAAM,IAAI,GAAG,IAAI,IAAI,IAAI,CAAC,KAAK,KAAK,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;YACnE,OAAO,CAAC,CAAC,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,8BAA8B,EAAE,EAAE,aAAa,CAAC,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QAC7H,CAAC;KACF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,IAAiB;IACzC,OAAO,SAAS,CAAC,IAAI,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC;AAC/C,CAAC"} \ No newline at end of file +{"version":3,"file":"lead.js","sourceRoot":"","sources":["../src/lead.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAChC,OAAO,EAAE,CAAC,EAAE,MAAM,UAAU,CAAC;AAC7B,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,SAAS,EAA8B,MAAM,WAAW,CAAC;AAEjF,MAAM,UAAU,GAAe;IAC7B,GAAG,EAAE,GAAG,EAAE,CAAC,MAAM;IACjB,MAAM,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,cAAc;IAClC,OAAO,EAAE,WAAW;IACpB,IAAI,EAAE,GAAG,EAAE,CAAC,EAAE;IACd,MAAM,EAAE,GAAG,EAAE,CAAC,SAAS;IACvB,WAAW,EAAE,EAAE;IACf,aAAa,EAAE,CAAC,eAAe,CAAC;IAChC,eAAe,EAAE,iBAAiB;IAClC,WAAW,EAAE,aAAa;IAC1B,KAAK,EAAE,EAAE;IACT,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI;IACnB,WAAW,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,kFAAkF,CAAC,CAAC,CAAC,yDAAyD,CAAC;IACtL,IAAI,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,EAAE,EAAE;QAC7D,MAAM,IAAI,CAAC,uBAAuB,EAAE,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IACzG,CAAC;CACF,CAAC;AAEF,SAAS,QAAQ;IACf,OAAO;QACL,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE;YAChB,MAAM,IAAI,GAAG,WAAW,EAAE,CAAC;YAC3B,MAAM,IAAI,GAAG,IAAI,IAAI,IAAI,CAAC,KAAK,KAAK,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;YACnE,OAAO,CAAC,CAAC,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,8BAA8B,EAAE,EAAE,aAAa,CAAC,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QAC7H,CAAC;KACF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,IAAiB;IACzC,OAAO,SAAS,CAAC,IAAI,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC;AAC/C,CAAC"} \ No newline at end of file diff --git a/web/dist/task.js b/web/dist/task.js index 18eac50..28815ca 100644 --- a/web/dist/task.js +++ b/web/dist/task.js @@ -30,8 +30,8 @@ const taskSource = { transcriptTopic: "transcript", streamTopic: "stream", empty: "No task selected. Ask your lead, or pick one with ⌘K.", - canSend: (s) => s === "idle", - placeholder: (s) => (s === "idle" ? "Message the agent. Paste or drop images to attach them." : "Wait for the turn to end, or interrupt it"), + canSend: () => true, + placeholder: (s) => s === "running" ? "Message the agent; it reads this after its next tool call, or when its turn ends" : "Message the agent. Paste or drop images to attach them.", send: async ({ key, sessionId, text, images, model, firstModel }) => { if (sessionId) { await post("/api/sessions/{sessionId}/messages", { path: { sessionId }, body: { text, images, model } }); @@ -269,7 +269,7 @@ export function mountChat(root, src, makeHead) { }; let pending = []; const showPending = (text, imgs) => { - const el = h("div", { class: "msg user pending" }, h("div", { class: "who" }, "sending…"), imgs.length > 0 ? h("div", { class: "msg-images" }, ...imgs.map((img) => h("img", { src: img.url, alt: "attached image" }))) : null, text.trim() ? markdown(text, "text md") : null); + const el = h("div", { class: "msg user pending" }, h("div", { class: "who" }, src.session()?.state === "running" ? "queued…" : "sending…"), imgs.length > 0 ? h("div", { class: "msg-images" }, ...imgs.map((img) => h("img", { src: img.url, alt: "attached image" }))) : null, text.trim() ? markdown(text, "text md") : null); const entry = { el, text, imgs }; pending.push(entry); ctx.run = undefined; diff --git a/web/dist/task.js.map b/web/dist/task.js.map index 3abb9fa..484b693 100644 --- a/web/dist/task.js.map +++ b/web/dist/task.js.map @@ -1 +1 @@ -{"version":3,"file":"task.js","sourceRoot":"","sources":["../src/task.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"task.js","sourceRoot":"","sources":["../src/task.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/web/src/api.gen.ts b/web/src/api.gen.ts index ee28856..5eba1f4 100644 --- a/web/src/api.gen.ts +++ b/web/src/api.gen.ts @@ -600,7 +600,7 @@ export interface paths { }; get?: never; put?: never; - /** @description Starts a new turn with this message. The session must be idle. */ + /** @description Starts a new turn with this message when the session is idle. While a task session's turn is running, the message is queued and reaches the agent after its next tool call, or as the next turn. */ post: operations["sendMessage"]; delete?: never; options?: never; diff --git a/web/src/lead.ts b/web/src/lead.ts index c5eecf0..f75824f 100644 --- a/web/src/lead.ts +++ b/web/src/lead.ts @@ -15,7 +15,7 @@ const leadSource: ChatSource = { streamTopic: "lead.stream", empty: "", canSend: () => true, - placeholder: (s) => (s === "running" ? "Message your lead; it gets this when its turn ends" : "Message your lead. Paste or drop images to attach them."), + placeholder: (s) => (s === "running" ? "Message your lead; it reads this after its next tool call, or when its turn ends" : "Message your lead. Paste or drop images to attach them."), send: async ({ text, images, model, firstModel, sessionId }) => { await post("/api/me/lead/messages", { body: { text, images, model: sessionId ? model : firstModel } }); }, diff --git a/web/src/task.ts b/web/src/task.ts index 53569b0..7605c81 100644 --- a/web/src/task.ts +++ b/web/src/task.ts @@ -89,8 +89,9 @@ const taskSource: ChatSource = { transcriptTopic: "transcript", streamTopic: "stream", empty: "No task selected. Ask your lead, or pick one with ⌘K.", - canSend: (s) => s === "idle", - placeholder: (s) => (s === "idle" ? "Message the agent. Paste or drop images to attach them." : "Wait for the turn to end, or interrupt it"), + canSend: () => true, + placeholder: (s) => + s === "running" ? "Message the agent; it reads this after its next tool call, or when its turn ends" : "Message the agent. Paste or drop images to attach them.", send: async ({ key, sessionId, text, images, model, firstModel }) => { if (sessionId) { await post("/api/sessions/{sessionId}/messages", { path: { sessionId }, body: { text, images, model } }); @@ -344,7 +345,7 @@ export function mountChat(root: HTMLElement, src: ChatSource, makeHead: (rerende const el = h( "div", { class: "msg user pending" }, - h("div", { class: "who" }, "sending…"), + h("div", { class: "who" }, src.session()?.state === "running" ? "queued…" : "sending…"), imgs.length > 0 ? h("div", { class: "msg-images" }, ...imgs.map((img) => h("img", { src: img.url, alt: "attached image" }))) : null, text.trim() ? markdown(text, "text md") : null, );