feat: session activity history and fix ANSI in session logs#116
feat: session activity history and fix ANSI in session logs#116jcanizalez merged 2 commits intomainfrom
Conversation
Add persistent session lifecycle event logging for all sessions — not just task-linked ones — enabling post-mortem analysis, metrics, and multi-agent coordination. - Add session_events table (id, session_id, event_type, timestamp, metadata) - Log created, exited, task_linked, renamed, archived, unarchived events - Add sessionEvent:list and sessionEvent:listBySession RPC methods - Add list_session_events MCP tool for agent coordination - Extract ANSI stripping to shared utility (ansi-strip.ts) - Strip ANSI escape codes from session log output before storage
There was a problem hiding this comment.
Pull request overview
Introduces persistent session lifecycle event history across all sessions (not only task-linked) and centralizes ANSI stripping so stored session output is readable/plain text.
Changes:
- Added shared
SessionEventType/SessionEventtypes and new RPC method definitions for listing session events. - Added
session_eventsSQLite table with insert/list helpers, and server-side logging hooks for lifecycle events (created/exited/linked/renamed/archived/unarchived). - Replaced inline ANSI-stripping logic with a
stripAnsi()utility and applied it before storing/buffering session output; added an MCP tool to query session events.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/shared/src/types.ts | Adds SessionEventType/SessionEvent and new IPC method constants for session event listing. |
| packages/shared/src/protocol.ts | Adds typed RPC method entries for sessionEvent:list and sessionEvent:listBySession. |
| packages/server/src/register-methods.ts | Logs session lifecycle events and strips ANSI codes before buffering session output for storage; registers new RPC methods. |
| packages/server/src/pty-manager.ts | Uses shared stripAnsi() utility when building the rolling output buffer. |
| packages/server/src/database.ts | Creates session_events table + indexes and implements insert/list query helpers (with per-session pruning). |
| packages/server/src/ansi-strip.ts | Adds reusable ANSI stripping helper used by server components. |
| packages/mcp/src/tools/sessions.ts | Adds list_session_events MCP tool that calls the new RPC methods. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
packages/mcp/src/tools/sessions.ts
Outdated
|
|
||
| server.tool( | ||
| 'list_session_events', | ||
| 'List session lifecycle events (created, exited, renamed, archived, task_linked). Use for post-mortem analysis and multi-agent coordination.', |
There was a problem hiding this comment.
Tool description lists event types but omits unarchived, which is also supported/logged. Update the description so it matches the actual event set.
| 'List session lifecycle events (created, exited, renamed, archived, task_linked). Use for post-mortem analysis and multi-agent coordination.', | |
| 'List session lifecycle events (created, exited, renamed, archived, unarchived, task_linked). Use for post-mortem analysis and multi-agent coordination.', |
packages/mcp/src/tools/sessions.ts
Outdated
| event_type: z | ||
| .string() | ||
| .max(50) | ||
| .optional() | ||
| .describe( | ||
| 'Filter by event type (created, exited, task_linked, renamed, archived, unarchived)' | ||
| ), |
There was a problem hiding this comment.
event_type is currently validated as an arbitrary string. Since the supported values are a fixed set, consider validating with an enum (or reusing the shared SessionEventType union) to give immediate feedback on typos and keep the MCP tool contract tight.
| 'sessionEvent:list': { | ||
| params: { eventType?: string; limit?: number } | ||
| result: SessionEvent[] | ||
| } | ||
| 'sessionEvent:listBySession': { | ||
| params: { sessionId: string; limit?: number } | ||
| result: SessionEvent[] |
There was a problem hiding this comment.
sessionEvent:list / sessionEvent:listBySession params currently type eventType as string, even though SessionEventType exists in shared types. This weakens type-safety for RPC consumers and makes it easier to pass invalid event types. Consider importing SessionEventType and typing eventType?: SessionEventType (and keeping the same wire format).
packages/server/src/database.ts
Outdated
| metadata TEXT | ||
| ); | ||
|
|
||
| CREATE INDEX IF NOT EXISTS idx_session_events_session ON session_events(session_id); |
There was a problem hiding this comment.
session_events queries sort by timestamp (e.g., ORDER BY timestamp DESC) but the schema only indexes session_id and event_type. This can cause full scans / temp sorts as the table grows. Consider adding an index that supports the common access patterns, e.g. (session_id, timestamp) and/or (event_type, timestamp), or at least an index on timestamp.
| CREATE INDEX IF NOT EXISTS idx_session_events_session ON session_events(session_id); | |
| CREATE INDEX IF NOT EXISTS idx_session_events_session ON session_events(session_id); | |
| CREATE INDEX IF NOT EXISTS idx_session_events_session_timestamp ON session_events(session_id, timestamp DESC); |
packages/server/src/database.ts
Outdated
| export function listSessionEvents(eventType?: string, limit = 100): SessionEvent[] { | ||
| const d = getDb() | ||
| let rows: Array<Record<string, unknown>> | ||
| if (eventType) { | ||
| rows = d | ||
| .prepare('SELECT * FROM session_events WHERE event_type = ? ORDER BY timestamp DESC LIMIT ?') | ||
| .all(eventType, limit) as Array<Record<string, unknown>> | ||
| } else { |
There was a problem hiding this comment.
listSessionEvents accepts eventType?: string even though the shared type defines SessionEventType. Using the union type here (and in the RPC method handler) would prevent accidental logging/querying of unsupported event types and keep the API consistent end-to-end.
| // Strip ANSI escape sequences so stored output is plain text. | ||
| // eslint-disable-next-line no-control-regex | ||
| const ANSI_RE = /\x1b\[[0-9;]*[A-Za-z]|\x1b\].*?(?:\x07|\x1b\\)|\x1b[()][0-2B]|\x1b[=>]/g | ||
|
|
||
| export function stripAnsi(data: string): string { | ||
| return data.replace(ANSI_RE, '').replace(/\r/g, '') | ||
| } |
There was a problem hiding this comment.
PR description says ANSI stripping was extracted to a shared ansi-strip.ts utility, but this utility was added under packages/server/src/ansi-strip.ts and is only used server-side. Either move it into the shared package (and import from there) if it’s intended to be reused across packages, or update the PR description to match the current location/scope.
Summary
session_eventstable for persistent session lifecycle event logging across all sessions (not just task-linked)created,exited,task_linked,renamed,archived,unarchivedsessionEvent:listandsessionEvent:listBySessionRPC methods +list_session_eventsMCP toolansi-strip.tsutility)Closes #96
Test plan
yarn buildpassesyarn test— 433 tests passsessionEvent:listBySessionvia MCP to verify events logged