feat(web): add session timeline#1788
Conversation
Greptile SummaryThis PR adds a session activity timeline to the session detail page, exposing a
Confidence Score: 5/5Safe to merge; the change is additive and degrades gracefully when the activity DB is unavailable. The new timeline is purely additive — a fetch error leaves the existing session detail intact. The domain-before-error classification ordering is correct, all eight filter categories are present in FILTERS and have CSS marker rules, the generation counter prevents stale poll overwrites, and the route properly gates on session existence before querying. No logic defects were found. globals.css — the shared runtime/error marker color is the only item worth revisiting before merge.
|
| Filename | Overview |
|---|---|
| packages/web/src/app/api/sessions/[id]/events/route.ts | New route: session existence check, clamped limit, synchronous queryActivityEvents call, JSON-parse of raw event data, observability recording, and proper error-path fallback. |
| packages/web/src/components/SessionTimeline.tsx | New component with 5 s polling, generation-counter race-protection, correct domain-before-error classification order, and all eight filter categories present in FILTERS array. |
| packages/web/src/app/globals.css | Timeline CSS added; runtime and error marker rules share the same color token, making their dots visually identical to each other. |
| packages/web/src/lib/types.ts | Three new exported types added cleanly; DashboardActivityEvent.data typed as unknown to accommodate both parsed objects and raw strings. |
| packages/web/src/components/tests/SessionTimeline.test.tsx | New test suite covers race-condition protection, domain-prefixed failure classification, subprocess-vs-PR routing, and empty filter state messaging. |
| packages/web/src/components/tests/SessionDetail.desktop.test.tsx | fetch mock updated to return a never-resolving Promise for /events requests in pre-existing tests; two new integration tests verify timeline rendering and the Actions filter. |
| packages/web/src/components/SessionDetail.tsx | Single-line addition of SessionTimeline below the main session content area. |
| packages/web/src/tests/api-routes.test.ts | Two new test cases covering successful event retrieval (with limit clamping verified) and 404 for unknown sessions. |
Sequence Diagram
sequenceDiagram
participant Browser as Browser (SessionTimeline)
participant Route as GET /api/sessions/:id/events
participant SM as SessionManager
participant DB as SQLite (queryActivityEvents)
participant SSE as SSE stream (session prop)
Browser->>Route: "fetch /events?limit=80 (initial + every 5s)"
Route->>SM: sessionManager.get(id)
SM-->>Route: session (or null → 404)
Route->>DB: "queryActivityEvents({ projectId, sessionId, limit })"
DB-->>Route: ActivityEvent[]
Route-->>Browser: "{ events: DashboardActivityEvent[] }"
Browser->>Browser: mergeTimelineEvents(activityEvents, session.agentReportAudit)
SSE-->>Browser: session.agentReportAudit updates (via parent prop)
Browser->>Browser: useMemo recomputes timelineEvents
Browser->>Browser: filter → visibleEvents → render list
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 2
packages/web/src/app/globals.css:2536-2539
The `runtime` and `error` marker rules share the same color token (`--color-status-error`), so their dots are visually identical. An operator using the "Runtime" filter to investigate probe failures will see red dots — the same shade as "Errors" — even though these categories are now logically separated. Giving runtime events a distinct color (e.g., `--color-status-working` or a dedicated warning token) would let dot color reinforce the filter distinction.
```suggestion
.session-timeline__item--runtime .session-timeline__marker {
background: var(--color-status-working);
}
.session-timeline__item--error .session-timeline__marker {
background: var(--color-status-error);
}
```
### Issue 2 of 2
packages/web/src/components/SessionTimeline.tsx:12-14
`TIMELINE_LIMIT` in the component and `DEFAULT_LIMIT` in the route are the same value (80) but are separate, unlinked constants. If the component's constant is bumped without updating the route (or vice versa), the URL `?limit=N` will silently diverge from the server default that operators hitting the API directly would receive. Extracting the value to a shared location or adding an inline cross-reference comment would make the coupling explicit.
```suggestion
/** Must match DEFAULT_LIMIT in packages/web/src/app/api/sessions/[id]/events/route.ts */
const TIMELINE_LIMIT = 80;
/** Matches dashboard SSE refresh cadence so the timeline stays in sync with session state. */
const TIMELINE_REFRESH_MS = 5000;
```
Reviews (6): Last reviewed commit: "fix(web): clarify filtered timeline stat..." | Re-trigger Greptile
- Add Actions/Other filters and marker colors for user_action and other
- Poll timeline every 5s without flickering loading state on refresh
- Remove redundant endsWith("_failed") check in category mapping
Co-authored-by: Cursor <cursoragent@cursor.com>
|
Thanks for the thorough review — addressed all four items in the latest push (branch rebased on current Filters / markers: Added Actions and Other chips so Freshness: Timeline refetches every 5s (same cadence as session SSE) so active sessions stay current. Follow-up polls no longer flip the whole block into a loading state; only the initial load does, and a failed refresh keeps the last good snapshot. Cleanup: Dropped the redundant Added a desktop test that kills a session via a |
Greptile: overlapping 5s fetches could apply out-of-order. Track a monotonic generation per request and ignore completions that are not the latest. Add SessionTimeline test covering slow-first/fast-second resolution order. Co-authored-by: Cursor <cursoragent@cursor.com>
|
Addressed the latest Greptile note on overlapping polls. Each timeline fetch now captures a monotonically increasing generation; Added Happy to have Greptile re-run on the new commit. |
Runtime/reaction/PR/lifecycle kinds that include "failed" now stay in their domain filters; level/kind error bucket applies only after those checks. Adds SessionTimeline regression test for runtime.probe_failed. Co-authored-by: Cursor <cursoragent@cursor.com>
|
Updated So e.g. Added a regression test that uses |
|
@harshitsinghbhandari please review |
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…-1788 # Conflicts: # packages/web/src/__tests__/api-routes.test.ts # packages/web/src/components/SessionDetail.tsx
Problem
Session detail showed the current state well, but operators still had to stitch together
ao events, metadata files, observability output, and agent-report audit entries to understand why a session changed state.What changed
GET /api/sessions/:id/eventsto expose recent activity events for one session, with a clampedlimitand session existence check.docs/observability.md.Why this approach
This reuses the existing SQLite activity-event log and agent-report audit trail instead of introducing new storage or changing lifecycle behavior. The UI is best-effort: if the activity DB is unavailable, the session page still renders and can show audit entries or an empty state.
Testing
pnpm --filter @aoagents/ao-web test -- SessionDetail.desktop api-routespnpm exec eslint packages/web/src/components/SessionTimeline.tsx packages/web/src/components/SessionDetail.tsx 'packages/web/src/app/api/sessions/[id]/events/route.ts' packages/web/src/lib/types.ts packages/web/src/__tests__/api-routes.test.ts packages/web/src/components/__tests__/SessionDetail.desktop.test.tsxpnpm exec prettier --check docs/observability.md packages/web/src/components/SessionTimeline.tsx packages/web/src/components/SessionDetail.tsx 'packages/web/src/app/api/sessions/[id]/events/route.ts' packages/web/src/lib/types.ts packages/web/src/__tests__/api-routes.test.ts packages/web/src/components/__tests__/SessionDetail.desktop.test.tsxNote:
pnpm --filter @aoagents/ao-web typecheckis blocked in this local checkout by a missing@aoagents/ao-plugin-runtime-processbuild artifact /node-ptyinstall (Cannot find module '@aoagents/ao-plugin-runtime-process'). The new timeline code type error found during the first run was fixed before pushing.