fix: align agent timeline event contract
This commit is contained in:
@ -81,8 +81,8 @@ class SessionProcessProjector:
|
|||||||
root["summary"] = payload.get("reason") or ""
|
root["summary"] = payload.get("reason") or ""
|
||||||
root["metadata"] = {
|
root["metadata"] = {
|
||||||
**root.get("metadata", {}),
|
**root.get("metadata", {}),
|
||||||
"plan_mode": payload.get("plan_mode"),
|
"plan_mode": plan_mode,
|
||||||
"strategy": payload.get("strategy"),
|
"strategy": strategy,
|
||||||
"node_ids": node_ids,
|
"node_ids": node_ids,
|
||||||
"skill_queries": payload.get("skill_queries") or [],
|
"skill_queries": payload.get("skill_queries") or [],
|
||||||
"selected_skill_names": payload.get("selected_skill_names") or [],
|
"selected_skill_names": payload.get("selected_skill_names") or [],
|
||||||
@ -234,7 +234,6 @@ class SessionProcessProjector:
|
|||||||
"task_id": task_id,
|
"task_id": task_id,
|
||||||
"attempt_index": attempt_index,
|
"attempt_index": attempt_index,
|
||||||
"timeline_type": "agent_progress",
|
"timeline_type": "agent_progress",
|
||||||
"node_result": dict(item),
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -137,6 +137,8 @@ def test_process_projection_maps_task_team_events(tmp_path: Path) -> None:
|
|||||||
|
|
||||||
planned_event = next(event for event in projection["events"] if event["kind"] == "task_planned")
|
planned_event = next(event for event in projection["events"] if event["kind"] == "task_planned")
|
||||||
assert planned_event["metadata"]["timeline_type"] == "plan"
|
assert planned_event["metadata"]["timeline_type"] == "plan"
|
||||||
|
assert planned_event["metadata"]["plan_mode"] == "team"
|
||||||
|
assert planned_event["metadata"]["strategy"] == "sequence"
|
||||||
assert planned_event["metadata"]["selected_skill_names"] == ["research-workflow"]
|
assert planned_event["metadata"]["selected_skill_names"] == ["research-workflow"]
|
||||||
|
|
||||||
skill_event = next(event for event in projection["events"] if event["kind"] == "skill_selected")
|
skill_event = next(event for event in projection["events"] if event["kind"] == "skill_selected")
|
||||||
@ -149,6 +151,7 @@ def test_process_projection_maps_task_team_events(tmp_path: Path) -> None:
|
|||||||
|
|
||||||
node_event = next(event for event in projection["events"] if event["kind"] == "agent_finished")
|
node_event = next(event for event in projection["events"] if event["kind"] == "agent_finished")
|
||||||
assert node_event["metadata"]["timeline_type"] == "agent_progress"
|
assert node_event["metadata"]["timeline_type"] == "agent_progress"
|
||||||
|
assert "node_result" not in node_event["metadata"]
|
||||||
|
|
||||||
evidence_event = next(event for event in projection["events"] if event["kind"] == "task_result_ready")
|
evidence_event = next(event for event in projection["events"] if event["kind"] == "task_result_ready")
|
||||||
assert evidence_event["metadata"]["timeline_type"] == "result"
|
assert evidence_event["metadata"]["timeline_type"] == "result"
|
||||||
@ -209,6 +212,32 @@ def test_process_projection_maps_failed_task_team_events(tmp_path: Path) -> None
|
|||||||
assert node_event["metadata"]["timeline_type"] == "agent_progress"
|
assert node_event["metadata"]["timeline_type"] == "agent_progress"
|
||||||
|
|
||||||
|
|
||||||
|
def test_process_projection_uses_normalized_plan_metadata_defaults(tmp_path: Path) -> None:
|
||||||
|
session = SessionManager(tmp_path)
|
||||||
|
run_store = RunMemoryStore(tmp_path / "memory" / "runs")
|
||||||
|
session.append_message(
|
||||||
|
"web:test",
|
||||||
|
role="system",
|
||||||
|
event_type="task_execution_planned",
|
||||||
|
event_payload={
|
||||||
|
"task_id": "task-1",
|
||||||
|
"attempt_index": 1,
|
||||||
|
"plan_mode": None,
|
||||||
|
"strategy": None,
|
||||||
|
},
|
||||||
|
context_visible=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
projection = SessionProcessProjector(session, run_store).project("web:test")
|
||||||
|
|
||||||
|
root_run = next(run for run in projection["runs"] if run["run_id"] == "task:task-1:attempt:1")
|
||||||
|
assert root_run["metadata"]["plan_mode"] == "single"
|
||||||
|
assert root_run["metadata"]["strategy"] == "single"
|
||||||
|
planned_event = next(event for event in projection["events"] if event["kind"] == "task_planned")
|
||||||
|
assert planned_event["metadata"]["plan_mode"] == "single"
|
||||||
|
assert planned_event["metadata"]["strategy"] == "single"
|
||||||
|
|
||||||
|
|
||||||
def test_process_projection_exposes_ephemeral_guidance_artifacts(tmp_path: Path) -> None:
|
def test_process_projection_exposes_ephemeral_guidance_artifacts(tmp_path: Path) -> None:
|
||||||
session = SessionManager(tmp_path)
|
session = SessionManager(tmp_path)
|
||||||
run_store = RunMemoryStore(tmp_path / "memory" / "runs")
|
run_store = RunMemoryStore(tmp_path / "memory" / "runs")
|
||||||
|
|||||||
@ -201,6 +201,28 @@ describe('buildTaskTimelineCards', () => {
|
|||||||
expect(cards.map((card) => card.id)).not.toContain('run-research:fallback-progress');
|
expect(cards.map((card) => card.id)).not.toContain('run-research:fallback-progress');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('maps agent_finished events without timeline metadata to agent progress cards', () => {
|
||||||
|
const task = makeTask();
|
||||||
|
const processEvents: ProcessEvent[] = [
|
||||||
|
{
|
||||||
|
event_id: 'evt-agent-finished',
|
||||||
|
run_id: 'run-research',
|
||||||
|
parent_run_id: 'run-main',
|
||||||
|
kind: 'agent_finished',
|
||||||
|
actor_type: 'agent',
|
||||||
|
actor_id: 'research-agent',
|
||||||
|
actor_name: 'Research Agent',
|
||||||
|
text: 'Finished reading source documents.',
|
||||||
|
status: 'done',
|
||||||
|
created_at: '2026-05-26T10:02:00.000Z',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const cards = buildTaskTimelineCards({ task, processEvents });
|
||||||
|
|
||||||
|
expect(cards.find((card) => card.id === 'evt-agent-finished')?.type).toBe('agent_progress');
|
||||||
|
});
|
||||||
|
|
||||||
it('sorts invalid timestamps after valid timestamps while preserving insertion order', () => {
|
it('sorts invalid timestamps after valid timestamps while preserving insertion order', () => {
|
||||||
const task = makeTask();
|
const task = makeTask();
|
||||||
const processEvents: ProcessEvent[] = [
|
const processEvents: ProcessEvent[] = [
|
||||||
|
|||||||
@ -95,6 +95,7 @@ function cardTypeForEvent(event: ProcessEvent): TaskTimelineCardType | null {
|
|||||||
return 'agent_team';
|
return 'agent_team';
|
||||||
case 'agent_handoff':
|
case 'agent_handoff':
|
||||||
return 'agent_handoff';
|
return 'agent_handoff';
|
||||||
|
case 'agent_finished':
|
||||||
case 'run_progress':
|
case 'run_progress':
|
||||||
case 'run_finished':
|
case 'run_finished':
|
||||||
return 'agent_progress';
|
return 'agent_progress';
|
||||||
|
|||||||
@ -455,6 +455,7 @@ export type ProcessEventKind =
|
|||||||
| 'tool_call_started'
|
| 'tool_call_started'
|
||||||
| 'tool_call_finished'
|
| 'tool_call_finished'
|
||||||
| 'agent_team_created'
|
| 'agent_team_created'
|
||||||
|
| 'agent_finished'
|
||||||
| 'agent_handoff'
|
| 'agent_handoff'
|
||||||
| 'task_result_ready'
|
| 'task_result_ready'
|
||||||
| 'task_acceptance_recorded'
|
| 'task_acceptance_recorded'
|
||||||
|
|||||||
Reference in New Issue
Block a user