mirror of
https://github.com/wu736139669/hapi.git
synced 2026-10-07 19:06:11 +00:00
Render ExitPlanMode and exit_plan_mode Markdown from input.plan in iOS and Android conversations. Preserve approvals, raw source and diagnostics while hiding empty output placeholders and prewarming plan documents. Add generated protocol fixtures and native regression coverage for long plans, live updates, recycling, themes and typography.
525 lines
21 KiB
TypeScript
525 lines
21 KiB
TypeScript
import type { FixtureCase } from '../fixtureTypes'
|
|
import { T0, wireMessage } from './support'
|
|
|
|
/**
|
|
* Generic agent family: content.type === 'codex' (AGENT_MESSAGE_PAYLOAD_TYPE,
|
|
* used by Codex/Cursor/OpenCode/Pi/…). Text and reasoning arrive as cumulative
|
|
* stream snapshots keyed by data.id.
|
|
*/
|
|
export const codexCases: FixtureCase[] = [
|
|
...(['ExitPlanMode', 'exit_plan_mode'] as const).map((name, index): FixtureCase => {
|
|
const plan = '# 实施计划\n\n1. Inspect **input.plan**\n2. Render Markdown\n\n```swift\nlet ready = true\n```\n'
|
|
const callId = `codex-proposed-plan:plan-${index}`
|
|
const pending = index === 1
|
|
const data = [
|
|
{ type: 'tool-call', name: 'Read', callId: 'read-before', input: { file_path: '/repo/README.md' } },
|
|
{ type: 'tool-call-result', callId: 'read-before', output: 'Readme' },
|
|
{ type: 'tool-call', name, callId, input: { plan }, id: `plan-${index}` },
|
|
...(!pending ? [{ type: 'tool-call-result', callId, output: null, id: `plan-${index}:result` }] : []),
|
|
{ type: 'tool-call', name: 'Read', callId: 'read-after', input: { file_path: '/repo/package.json' } },
|
|
{ type: 'tool-call-result', callId: 'read-after', output: '{}' }
|
|
]
|
|
return {
|
|
name: pending ? 'codex-plan-proposal-pending' : 'codex-plan-proposal-completed',
|
|
description: 'Proposal Markdown lives in input.plan. Keep it standalone between ordinary tools; null output completes a proposal without inventing approval. The lowercase alias also accepts a pending permission.',
|
|
messages: data.map((entry, seq) => wireMessage({
|
|
id: `proposal-${index}-${seq}`, seq: seq + 1, createdAt: T0 + seq * 1000,
|
|
content: { role: 'agent', content: { type: 'codex', data: entry } }
|
|
})),
|
|
...(pending ? { agentState: { requests: {
|
|
'plan-approval': { tool: name, toolCallId: callId, arguments: { plan }, createdAt: T0 + 2000 }
|
|
} } } : {})
|
|
}
|
|
}),
|
|
{
|
|
name: 'codex-message-stream-snapshot',
|
|
description: 'Codex family: two message payloads sharing a stream id (data.id) are cumulative snapshots. Expects a single agent-text block keyed by that stable stream id, carrying the final snapshot text.',
|
|
messages: [
|
|
wireMessage({
|
|
id: 'msg-codex-051',
|
|
seq: 1,
|
|
createdAt: T0,
|
|
content: {
|
|
role: 'agent',
|
|
content: {
|
|
type: 'codex',
|
|
data: {
|
|
type: 'message',
|
|
id: 'item_7',
|
|
message: 'Tracking down the failing pagination'
|
|
}
|
|
}
|
|
}
|
|
}),
|
|
wireMessage({
|
|
id: 'msg-codex-052',
|
|
seq: 2,
|
|
createdAt: T0 + 900,
|
|
content: {
|
|
role: 'agent',
|
|
content: {
|
|
type: 'codex',
|
|
data: {
|
|
type: 'message',
|
|
id: 'item_7',
|
|
message: 'Tracking down the failing pagination test: the cursor math drops the epoch check when seq wraps.'
|
|
}
|
|
}
|
|
}
|
|
})
|
|
]
|
|
},
|
|
{
|
|
name: 'codex-reasoning',
|
|
description: 'Codex family: reasoning payload becomes an agent-reasoning block keyed by its stream id (data.id).',
|
|
messages: [
|
|
wireMessage({
|
|
id: 'msg-codex-061',
|
|
seq: 1,
|
|
createdAt: T0,
|
|
content: {
|
|
role: 'agent',
|
|
content: {
|
|
type: 'codex',
|
|
data: {
|
|
type: 'reasoning',
|
|
id: 'rs_0af3d219',
|
|
message: '**Weighing pagination approaches**\n\nThe epoch guard only fires when the server resets, so the client must drop its window on mismatch.'
|
|
}
|
|
}
|
|
}
|
|
})
|
|
]
|
|
},
|
|
{
|
|
name: 'codex-blank-stream-ids',
|
|
description: 'Codex family: blank (empty or whitespace-only) data.id values are not stream identities (shared/src/messages.ts trims before accepting) — each payload keeps its own row-derived block id instead of collapsing onto a shared blank identity.',
|
|
messages: [
|
|
wireMessage({
|
|
id: 'msg-codex-065',
|
|
seq: 1,
|
|
createdAt: T0,
|
|
content: {
|
|
role: 'agent',
|
|
content: {
|
|
type: 'codex',
|
|
data: {
|
|
type: 'reasoning',
|
|
id: '',
|
|
message: 'blank id one'
|
|
}
|
|
}
|
|
}
|
|
}),
|
|
wireMessage({
|
|
id: 'msg-codex-066',
|
|
seq: 2,
|
|
createdAt: T0 + 100,
|
|
content: {
|
|
role: 'agent',
|
|
content: {
|
|
type: 'codex',
|
|
data: {
|
|
type: 'reasoning',
|
|
id: ' ',
|
|
message: 'blank id two'
|
|
}
|
|
}
|
|
}
|
|
}),
|
|
wireMessage({
|
|
id: 'msg-codex-067',
|
|
seq: 3,
|
|
createdAt: T0 + 200,
|
|
content: {
|
|
role: 'agent',
|
|
content: {
|
|
type: 'codex',
|
|
data: {
|
|
type: 'message',
|
|
id: '',
|
|
message: 'blank id text one'
|
|
}
|
|
}
|
|
}
|
|
}),
|
|
wireMessage({
|
|
id: 'msg-codex-068',
|
|
seq: 4,
|
|
createdAt: T0 + 300,
|
|
content: {
|
|
role: 'agent',
|
|
content: {
|
|
type: 'codex',
|
|
data: {
|
|
type: 'message',
|
|
id: ' ',
|
|
message: 'blank id text two'
|
|
}
|
|
}
|
|
}
|
|
})
|
|
]
|
|
},
|
|
{
|
|
name: 'codex-error-event',
|
|
description: 'Codex family: an error payload becomes an agent-event block with {type: error, message} carried verbatim.',
|
|
messages: [
|
|
wireMessage({
|
|
id: 'msg-user-501',
|
|
seq: 1,
|
|
createdAt: T0,
|
|
content: {
|
|
role: 'user',
|
|
content: { type: 'text', text: 'Continue the review.' }
|
|
}
|
|
}),
|
|
wireMessage({
|
|
id: 'msg-codex-502',
|
|
seq: 2,
|
|
createdAt: T0 + 2_700,
|
|
content: {
|
|
role: 'agent',
|
|
content: {
|
|
type: 'codex',
|
|
data: {
|
|
type: 'error',
|
|
message: 'Codex backend disconnected: stream closed before the turn completed'
|
|
}
|
|
}
|
|
}
|
|
})
|
|
]
|
|
},
|
|
{
|
|
name: 'codex-context-compacted',
|
|
description: 'Codex family: context_compacted maps to an agent-event of type compact with trigger and preTokens (accepting the snake_case pre_tokens wire key).',
|
|
messages: [
|
|
wireMessage({
|
|
id: 'msg-user-511',
|
|
seq: 1,
|
|
createdAt: T0,
|
|
content: {
|
|
role: 'user',
|
|
content: { type: 'text', text: 'Carry on.' }
|
|
}
|
|
}),
|
|
wireMessage({
|
|
id: 'msg-codex-512',
|
|
seq: 2,
|
|
createdAt: T0 + 3_100,
|
|
content: {
|
|
role: 'agent',
|
|
content: {
|
|
type: 'codex',
|
|
data: {
|
|
type: 'context_compacted',
|
|
trigger: 'auto',
|
|
pre_tokens: 186211,
|
|
thread_id: 'thread-42'
|
|
}
|
|
}
|
|
}
|
|
})
|
|
]
|
|
},
|
|
{
|
|
name: 'codex-generated-image',
|
|
description: 'Codex family: generated-image becomes a generated-image block keeping {imageId, fileName, mimeType}; the wire source (inline media provenance) and sourceImageId are web rendering concerns and are dropped by the projection.',
|
|
messages: [
|
|
wireMessage({
|
|
id: 'msg-codex-521',
|
|
seq: 1,
|
|
createdAt: T0,
|
|
content: {
|
|
role: 'agent',
|
|
content: {
|
|
type: 'codex',
|
|
data: {
|
|
type: 'generated-image',
|
|
imageId: 'img-01J9ZD8KM4TQ',
|
|
sourceImageId: 'ig_58210',
|
|
fileName: 'latency-chart.png',
|
|
mimeType: 'image/png',
|
|
id: 'aed0f1b2-5fb6-4c2d-8e9d-e2f3a4b5c521',
|
|
source: {
|
|
ingress: 'tool_result',
|
|
flavor: 'codex',
|
|
toolCallId: 'call_img_58210'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
})
|
|
]
|
|
},
|
|
{
|
|
name: 'codex-review-verdict',
|
|
description: 'Codex family: a message whose text is codex review JSON (findings + overall_correctness/…) parses into a codex-review block with the normalized review object (camelCase fields, code_location flattened to filePath/lineStart/lineEnd).',
|
|
messages: [
|
|
wireMessage({
|
|
id: 'msg-codex-531',
|
|
seq: 1,
|
|
createdAt: T0,
|
|
content: {
|
|
role: 'agent',
|
|
content: {
|
|
type: 'codex',
|
|
data: {
|
|
type: 'message',
|
|
id: '550e8400-e29b-41d4-a716-446655440531',
|
|
message: '{"findings":[{"title":"[P2] Remove retained sessions when sockets disconnect","body":"Retained sockets survive disconnects.","confidence_score":0.82,"priority":2,"code_location":{"absolute_file_path":"/repo/hub/src/pairing/manager.ts","line_range":{"start":1614,"end":1619}}}],"overall_correctness":"patch is incorrect","overall_explanation":"The message-sending feature retains long-lived sockets but does not fully manage their lifecycle.","overall_confidence_score":0.8}'
|
|
}
|
|
}
|
|
}
|
|
})
|
|
]
|
|
},
|
|
{
|
|
name: 'codex-thread-goal-updated',
|
|
description: 'Codex family: thread_goal_updated normalizes to a thread-goal-updated event which is SILENT in the timeline (filtered as a goal-state signal) — expected blocks contain only the user text. latestGoal is derived state outside the normative projection.',
|
|
messages: [
|
|
wireMessage({
|
|
id: 'msg-user-541',
|
|
seq: 1,
|
|
createdAt: T0,
|
|
content: {
|
|
role: 'user',
|
|
content: { type: 'text', text: '/goal Ship the pagination fix' }
|
|
}
|
|
}),
|
|
wireMessage({
|
|
id: 'msg-codex-542',
|
|
seq: 2,
|
|
createdAt: T0 + 1_300,
|
|
content: {
|
|
role: 'agent',
|
|
content: {
|
|
type: 'codex',
|
|
data: {
|
|
type: 'thread_goal_updated',
|
|
thread_id: 'thread-42',
|
|
turn_id: 'turn-7',
|
|
goal: {
|
|
threadId: 'thread-42',
|
|
objective: 'Ship the pagination fix',
|
|
status: 'active',
|
|
tokenBudget: 200000,
|
|
tokensUsed: 8016,
|
|
timeUsedSeconds: 42,
|
|
createdAt: 1755000001300,
|
|
updatedAt: 1755000001300
|
|
}
|
|
}
|
|
}
|
|
}
|
|
})
|
|
]
|
|
},
|
|
{
|
|
name: 'codex-thread-goal-cleared',
|
|
description: 'Codex family: thread_goal_cleared normalizes to a thread-goal-cleared event which is SILENT in the timeline — expected blocks contain only the user text.',
|
|
messages: [
|
|
wireMessage({
|
|
id: 'msg-user-551',
|
|
seq: 1,
|
|
createdAt: T0,
|
|
content: {
|
|
role: 'user',
|
|
content: { type: 'text', text: '/goal clear' }
|
|
}
|
|
}),
|
|
wireMessage({
|
|
id: 'msg-codex-552',
|
|
seq: 2,
|
|
createdAt: T0 + 1_100,
|
|
content: {
|
|
role: 'agent',
|
|
content: {
|
|
type: 'codex',
|
|
data: {
|
|
type: 'thread_goal_cleared',
|
|
thread_id: 'thread-42'
|
|
}
|
|
}
|
|
}
|
|
})
|
|
]
|
|
},
|
|
{
|
|
name: 'codex-tool-call-result-pair',
|
|
description: 'Codex family: a tool-call (callId + name + structured input) paired with its tool-call-result (output object) merges into one completed tool-call block; input and output are carried verbatim (command as argv array, output as {exit_code, stdout, …}).',
|
|
messages: [
|
|
wireMessage({
|
|
id: 'msg-user-561',
|
|
seq: 1,
|
|
createdAt: T0,
|
|
content: {
|
|
role: 'user',
|
|
content: { type: 'text', text: 'Run the test suite.' }
|
|
}
|
|
}),
|
|
wireMessage({
|
|
id: 'msg-codex-562',
|
|
seq: 2,
|
|
createdAt: T0 + 1_800,
|
|
content: {
|
|
role: 'agent',
|
|
content: {
|
|
type: 'codex',
|
|
data: {
|
|
type: 'tool-call',
|
|
name: 'CodexBash',
|
|
callId: 'call_9f2d81aa04',
|
|
input: {
|
|
command: ['bash', '-lc', 'bun test'],
|
|
cwd: '/repo'
|
|
},
|
|
id: 'bfe1a2c3-6ac7-4d3e-9fae-f3a4b5c6d562'
|
|
}
|
|
}
|
|
}
|
|
}),
|
|
wireMessage({
|
|
id: 'msg-codex-563',
|
|
seq: 3,
|
|
createdAt: T0 + 9_400,
|
|
content: {
|
|
role: 'agent',
|
|
content: {
|
|
type: 'codex',
|
|
data: {
|
|
type: 'tool-call-result',
|
|
callId: 'call_9f2d81aa04',
|
|
output: {
|
|
exit_code: 0,
|
|
stdout: '420 pass\n0 fail\nRan 420 tests across 37 files. [4.21s]',
|
|
duration_seconds: 4.21
|
|
},
|
|
is_error: false,
|
|
id: 'c0f2b3d4-7bd8-4e4f-8ab0-a4b5c6d7e563'
|
|
}
|
|
}
|
|
}
|
|
})
|
|
]
|
|
},
|
|
{
|
|
name: 'codex-exploration-tool-group',
|
|
description: 'Codex exploration grouping family: consecutive CodexBash calls whose command_actions are all read/listFiles/search (and command_source is not userShell) collapse into one tool-group in visibleBlocks — a family boundary distinct from the default read/search grouping.',
|
|
messages: [
|
|
wireMessage({
|
|
id: 'msg-user-571',
|
|
seq: 1,
|
|
createdAt: T0,
|
|
content: {
|
|
role: 'user',
|
|
content: { type: 'text', text: 'Where is the pagination cursor built?' }
|
|
}
|
|
}),
|
|
wireMessage({
|
|
id: 'msg-codex-572',
|
|
seq: 2,
|
|
createdAt: T0 + 1_500,
|
|
content: {
|
|
role: 'agent',
|
|
content: {
|
|
type: 'codex',
|
|
data: {
|
|
type: 'tool-call',
|
|
name: 'CodexBash',
|
|
callId: 'call_expl_read01',
|
|
input: {
|
|
command: ['bash', '-lc', 'cat package.json'],
|
|
cwd: '/repo',
|
|
command_source: 'agent',
|
|
command_actions: [
|
|
{
|
|
type: 'read',
|
|
command: 'cat package.json',
|
|
name: 'package.json',
|
|
path: '/repo/package.json'
|
|
}
|
|
]
|
|
},
|
|
id: 'd1a3c4e5-8ce9-4f5a-9bc1-b5c6d7e8f572'
|
|
}
|
|
}
|
|
}
|
|
}),
|
|
wireMessage({
|
|
id: 'msg-codex-573',
|
|
seq: 3,
|
|
createdAt: T0 + 2_400,
|
|
content: {
|
|
role: 'agent',
|
|
content: {
|
|
type: 'codex',
|
|
data: {
|
|
type: 'tool-call-result',
|
|
callId: 'call_expl_read01',
|
|
output: {
|
|
exit_code: 0,
|
|
stdout: '{\n "name": "hapi",\n "workspaces": ["cli", "hub", "web", "shared"]\n}'
|
|
},
|
|
is_error: false,
|
|
id: 'e2b4d5f6-9dfa-4a6b-8cd2-c6d7e8f9a573'
|
|
}
|
|
}
|
|
}
|
|
}),
|
|
wireMessage({
|
|
id: 'msg-codex-574',
|
|
seq: 4,
|
|
createdAt: T0 + 3_300,
|
|
content: {
|
|
role: 'agent',
|
|
content: {
|
|
type: 'codex',
|
|
data: {
|
|
type: 'tool-call',
|
|
name: 'CodexBash',
|
|
callId: 'call_expl_search01',
|
|
input: {
|
|
command: ['bash', '-lc', 'rg buildCursor web/src'],
|
|
cwd: '/repo',
|
|
command_source: 'agent',
|
|
command_actions: [
|
|
{
|
|
type: 'search',
|
|
command: 'rg buildCursor web/src',
|
|
query: 'buildCursor',
|
|
path: 'web/src'
|
|
}
|
|
]
|
|
},
|
|
id: 'f3c5e6a7-0eab-4b7c-9de3-d7e8f9a0b574'
|
|
}
|
|
}
|
|
}
|
|
}),
|
|
wireMessage({
|
|
id: 'msg-codex-575',
|
|
seq: 5,
|
|
createdAt: T0 + 4_100,
|
|
content: {
|
|
role: 'agent',
|
|
content: {
|
|
type: 'codex',
|
|
data: {
|
|
type: 'tool-call-result',
|
|
callId: 'call_expl_search01',
|
|
output: {
|
|
exit_code: 0,
|
|
stdout: 'web/src/lib/message-window-store.ts:412:function buildCursor(page) {'
|
|
},
|
|
is_error: false,
|
|
id: 'a4d6f7b8-1fbc-4c8d-8ef4-e8f9a0b1c575'
|
|
}
|
|
}
|
|
}
|
|
})
|
|
]
|
|
}
|
|
]
|