Skip to content

feat: add live project tool-call activity traces - #148

Merged
Minitour merged 5 commits into
version-2.0from
feat/project-activity-traces
Aug 1, 2026
Merged

feat: add live project tool-call activity traces#148
Minitour merged 5 commits into
version-2.0from
feat/project-activity-traces

Conversation

@Minitour

@Minitour Minitour commented Aug 1, 2026

Copy link
Copy Markdown
Member

Summary

  • Persist MCP and capa sh tool calls (redacted args, truncated previews, original size/tokens) with per-project cap and prune
  • Expose activity list/stats (including 1-minute last-hour buckets) over REST and live tool-call SSE events
  • Add project Activity UI: stats bar, smooth last-hour area chart, collapsible traces with arg summaries and inspectable payloads
  • Also: command-tool arg defaults in the tools UI; Providers section moved under Capabilities

Test plan

  • Run tool calls via agent MCP and capa sh against a project; confirm rows appear live in Activity
  • Expand a trace: args/result/error render; View full dialog works; size/tokens look correct
  • Confirm last-hour chart updates and hover shows per-minute counts
  • Load more older traces; stats (calls/errors/avg/shell·mcp) match recent activity
  • Verify secrets in args are redacted in stored/previewed JSON
  • bun test ./src/server/__tests__/tool-call-tracer.test.ts

Made with Cursor

Persist MCP and capa sh tool calls, expose REST/SSE activity APIs, and show a project Activity section with stats, last-hour chart, and inspectable traces.

Co-authored-by: Cursor <cursoragent@cursor.com>
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Add live project tool-call activity traces (persisted + REST/SSE + UI)

✨ Enhancement 🧪 Tests 🕐 40+ Minutes

Grey Divider

AI Description

• Persist redacted tool-call traces per project with pruning and last-hour stats.
• Expose activity list/stats via REST and live tool-call SSE events.
• Add Project Activity UI with stats bar, last-hour chart, and inspectable traces.
Diagram

graph TD
  A{{"MCP client / capa sh"}} --> B["MCP handler"] --> C["ToolCallTracer"] --> D["ToolCallsRepo"] --> E[("tool_calls table")]
  H["Project Activity UI"] --> F["REST activity API"] --> D
  C --> G["SSE tool-call event"] --> H

  subgraph Legend
    direction LR
    _ext{{"External"}} ~~~ _svc["Service/module"] ~~~ _api["API"] ~~~ _db[("Database")]
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Aggregate stats purely in SQL
  • ➕ Avoids loading per-row data into JS for stats/avg/error counts
  • ➕ Scales better if caps increase beyond 1000 or windows expand
  • ➖ More complex SQL (multiple aggregations + histogram bucketing)
  • ➖ Harder to evolve/redact logic since previews are computed in JS
2. Use WebSockets instead of SSE for live tool-call events
  • ➕ Bidirectional channel (could support UI filtering/ack/pagination requests)
  • ➕ Often more flexible for future real-time features
  • ➖ Higher operational/implementation complexity than SSE
  • ➖ SSE already matches the existing /events pattern and needs only server push

Recommendation: Keep the current approach: a capped SQLite table plus REST for pagination/stats and SSE for live updates. It fits the existing project /events mechanism, remains simple to operate, and the 1000-row cap bounds performance. Consider shifting stats computations to SQL only if the window or retention grows significantly.

Files changed (23) +2271 / -294

Enhancement (20) +1969 / -294
database.tsExpose tool-call activity APIs via CapaDatabase +37/-0

Expose tool-call activity APIs via CapaDatabase

• Adds a ToolCallsRepo instance and database facade methods for inserting, finishing, fetching, listing recent calls (with pagination), and computing stats.

src/db/database.ts

schema.tsAdd tool_calls table + index and lightweight migration helper +45/-0

Add tool_calls table + index and lightweight migration helper

• Creates the tool_calls table and an index on (project_id, started_at DESC). Introduces ensureColumn() to add new columns in SQLite when missing (used for result_bytes/result_tokens).

src/db/schema.ts

tool-calls.tsImplement ToolCallsRepo with pruning, pagination, and last-hour buckets +243/-0

Implement ToolCallsRepo with pruning, pagination, and last-hour buckets

• Adds insert/finish/get/listRecent (cursor-based pagination) plus stats() and histogram() for 60 one-minute buckets. Enforces a per-project retention cap (default 1000) by pruning oldest rows.

src/db/tool-calls.ts

index.tsWire ToolCallTracer into server lifecycle and add activity routes +31/-0

Wire ToolCallTracer into server lifecycle and add activity routes

• Instantiates ToolCallTracer and broadcasts trace updates via SSE clients. Adds REST routes for /activity and /activity/stats, and passes the tracer into MCP server instances.

src/server/index.ts

mcp-handler.tsTrace MCP tool executions (setup_tools/call_tool/tool) with start/finish outcomes +284/-281

Trace MCP tool executions (setup_tools/call_tool/tool) with start/finish outcomes

• Captures MCP client name on initialize for source labeling, and emits traces for tool executions across multiple code paths including authorization denials and missing capabilities/servers. Refactors meta-tool handling to reuse helper methods and ensures traces are marked error/ok consistently.

src/server/mcp-handler.ts

project-routes.tsAdd tool-call SSE event broadcaster and REST activity handlers +57/-0

Add tool-call SSE event broadcaster and REST activity handlers

• Adds notifyToolCall() to publish 'event: tool-call' SSE payloads to connected project clients. Introduces GET handlers for activity history and activity stats with limit/before query parsing.

src/server/project-routes.ts

tool-call-tracer.tsAdd ToolCallTracer with redaction, truncation, and size/token measurement +206/-0

Add ToolCallTracer with redaction, truncation, and size/token measurement

• Implements start/finish tracing that persists tool-call records, redacts values matching project variables, truncates previews, and records original result byte length and estimated token count. Exports helper utilities used by the MCP handler and tests.

src/server/tool-call-tracer.ts

database.tsDefine tool-call record and stats types for DB layer +43/-0

Define tool-call record and stats types for DB layer

• Introduces ToolCallRecord, ToolCallStatus/Kind, and ToolCallStats/ToolCallBucket types used across db/repos and server APIs.

src/types/database.ts

CodeBlock.tsxEnable JSON highlighting in shared CodeBlock +4/-3

Enable JSON highlighting in shared CodeBlock

• Registers the highlight.js JSON grammar alongside TypeScript so activity payloads can render with JSON syntax highlighting.

web-ui/src/components/common/CodeBlock.tsx

api.tsAdd client API calls for activity history and stats +16/-0

Add client API calls for activity history and stats

• Adds projectsApi.getActivity() (with limit/before) and getActivityStats() to consume new server endpoints.

web-ui/src/features/projects/api.ts

ActivityChart.tsxAdd last-hour per-minute activity SVG chart with hover tooltip +177/-0

Add last-hour per-minute activity SVG chart with hover tooltip

• Implements a compact smoothed area/line chart for 60 buckets, including hover crosshair and tooltip with count and timestamp.

web-ui/src/features/projects/components/activity/ActivityChart.tsx

ActivityFeed.tsxAdd activity feed list with empty state and Load more +49/-0

Add activity feed list with empty state and Load more

• Renders a list of tool-call rows and supports loading older traces with a disabled/loading button state.

web-ui/src/features/projects/components/activity/ActivityFeed.tsx

ActivityRow.tsxAdd collapsible trace row with arg summary and inspectable payloads +370/-0

Add collapsible trace row with arg summary and inspectable payloads

• Provides a compact row header (status/source/duration/relative time) plus an expanded view showing args/result/error blocks. Adds copy-to-clipboard and a modal dialog to view full payloads, preferring server-provided size/token stats when available.

web-ui/src/features/projects/components/activity/ActivityRow.tsx

ActivitySection.tsxAdd Activity section container to project page +64/-0

Add Activity section container to project page

• Composes the stats bar, chart, and collapsible traces list; wires loading/error states via useProjectActivity().

web-ui/src/features/projects/components/activity/ActivitySection.tsx

ActivityStats.tsxAdd stats bar with live indicator and summary tiles +60/-0

Add stats bar with live indicator and summary tiles

• Displays live/offline status, call/error counts, average duration, and shell vs MCP split for the last hour window.

web-ui/src/features/projects/components/activity/ActivityStats.tsx

CommandToolDialog.tsxSupport default values for command-tool arguments +108/-7

Support default values for command-tool arguments

• Adds a default-value field per argument with type-aware parsing/validation (string/number/boolean/object/array). Serializes validated defaults into the capabilities tool definition and surfaces validation errors via i18n.

web-ui/src/features/projects/components/tools/CommandToolDialog.tsx

ConfiguredToolCard.tsxDisplay configured command argument defaults on tool cards +10/-0

Display configured command argument defaults on tool cards

• Shows a compact defaults summary when command tools have args with default values.

web-ui/src/features/projects/components/tools/ConfiguredToolCard.tsx

hooks.tsAdd useProjectActivity hook (REST pagination + live SSE updates) +117/-2

Add useProjectActivity hook (REST pagination + live SSE updates)

• Implements fetching recent activity and stats, subscribes to 'tool-call' SSE events on the existing /events stream, merges updates into local state, and provides load-more pagination for older traces.

web-ui/src/features/projects/hooks.ts

ProjectDetailPage.tsxInsert Activity section and move Providers below capabilities +4/-1

Insert Activity section and move Providers below capabilities

• Adds the ActivitySection to the project detail page and reorders ProvidersSection to appear under Capabilities.

web-ui/src/pages/ProjectDetailPage.tsx

api.tsAdd API types for activity traces and stats; extend CommandArg defaults +44/-0

Add API types for activity traces and stats; extend CommandArg defaults

• Introduces ToolCallRecord, ActivityResponse, ActivityStats, and bucket types for the UI client. Extends CommandArg with an optional default field.

web-ui/src/types/api.ts

Bug fix (1) +1 / -0
projects.tsDelete tool-call rows when removing projects +1/-0

Delete tool-call rows when removing projects

• Extends project deletion transaction to also delete rows from the new tool_calls table for the project.

src/db/projects.ts

Tests (1) +269 / -0
tool-call-tracer.test.tsAdd tests for tracer redaction/sizing, repo pruning, and SSE framing +269/-0

Add tests for tracer redaction/sizing, repo pruning, and SSE framing

• Covers helper functions (source mapping, truncation, redaction, preview serialization), ToolCallsRepo pruning and pagination, ToolCallTracer start/finish behavior and size measurement, and tool-call SSE event formatting.

src/server/tests/tool-call-tracer.test.ts

Documentation (1) +32 / -0
projects.jsonAdd i18n strings for Activity UI and command arg defaults +32/-0

Add i18n strings for Activity UI and command arg defaults

• Adds translations for the new Activity section (labels, empty/loading states, chart a11y text) and for command-tool default argument inputs and validation errors.

web-ui/src/locales/en/projects.json

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Aug 1, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Prune deletes running traces ✓ Resolved 🐞 Bug ☼ Reliability
Description
ToolCallsRepo.prune deletes the oldest rows purely by started_at without excluding
status='running', so a long-running trace can be removed before ToolCallTracer.finish runs,
causing finish to return null and the completion never to be persisted or notified.
Code

src/db/tool-calls.ts[R227-236]

+		this.db.run(
+			`DELETE FROM tool_calls
+       WHERE id IN (
+         SELECT id FROM tool_calls
+         WHERE project_id = ?
+         ORDER BY started_at ASC
+         LIMIT ?
+       )`,
+			[projectId, excess],
+		);
Evidence
insert() always calls prune(), and prune() deletes rows based on oldest started_at only.
ToolCallTracer.finish() explicitly returns null if the row can’t be found, so a pruned running
trace will never be finalized or notified.

src/db/tool-calls.ts[36-64]
src/db/tool-calls.ts[220-237]
src/server/tool-call-tracer.ts[69-75]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`insert()` prunes immediately after every insert, and `prune()` deletes the oldest rows by `started_at` regardless of `status`. This can delete an in-flight tool call row (`status='running'`) if the per-project cap is exceeded, and then `ToolCallTracer.finish()` will not be able to find/update it.
### Issue Context
This breaks the new “live traces” feature by losing completion updates and leaving the UI permanently inconsistent for that call.
### Fix Focus Areas
- src/db/tool-calls.ts[36-64]
- src/db/tool-calls.ts[220-237]
- src/server/tool-call-tracer.ts[69-75]
### Suggested fix
- Prefer pruning only completed rows first:
- In the prune subquery add `AND status <> 'running'`.
- Add a deterministic tie-break: `ORDER BY started_at ASC, id ASC`.
- If excess still remains after deleting completed rows, decide policy:
- either skip further pruning until running calls finish, or
- prune running rows only as a last resort (but then `finish()` should handle missing rows by re-inserting a terminal record or otherwise surfacing that the trace was pruned).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Activity pagination skips ties ✓ Resolved 🐞 Bug ≡ Correctness
Description
ToolCallsRepo.listRecent paginates using only an exclusive started_at < before cursor while
ordering only by started_at, so tool calls that share the same millisecond timestamp as the page
boundary can be permanently omitted from “load older” pagination.
Code

src/db/tool-calls.ts[R115-129]

+							`SELECT * FROM tool_calls
+             WHERE project_id = ?
+             ORDER BY started_at DESC
+             LIMIT ?`,
+						)
+						.all(projectId, limit + 1)
+				: this.db
+						.query(
+							`SELECT * FROM tool_calls
+             WHERE project_id = ? AND started_at < ?
+             ORDER BY started_at DESC
+             LIMIT ?`,
+						)
+						.all(projectId, before, limit + 1)
+		) as ToolCallRecord[];
Evidence
The DB query filters pages using only started_at < before and orders only by started_at, while
started_at is populated from Date.now() and the UI uses the oldest row’s started_at as the
next cursor—so same-ms ties at a page boundary are excluded from subsequent pages.

src/db/tool-calls.ts[98-129]
src/server/tool-call-tracer.ts[44-52]
web-ui/src/features/projects/hooks.ts[183-193]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`listRecent()` uses `started_at` as the sole pagination cursor (`started_at < before`) and also orders only by `started_at`. Because `started_at` is set via `Date.now()` (ms resolution), multiple calls can have the same timestamp; when the UI uses the last row’s `started_at` as `before`, any remaining rows with the same `started_at` are excluded from the next page.
### Issue Context
The UI passes `before: oldest.started_at`, so the timestamp-only cursor is end-to-end.
### Fix Focus Areas
- src/db/tool-calls.ts[98-139]
- src/server/tool-call-tracer.ts[44-75]
- web-ui/src/features/projects/hooks.ts[183-203]
### Suggested fix
- Make ordering deterministic: `ORDER BY started_at DESC, id DESC`.
- Switch cursor to a composite `(started_at, id)` cursor.
- SQL form (portable):
- `WHERE project_id=? AND (started_at < ? OR (started_at = ? AND id < ?))`
- `ORDER BY started_at DESC, id DESC LIMIT ?`
- Update the REST API to accept both cursor parts (e.g. `beforeStartedAt` + `beforeId`, or encode as a single cursor string), and update `useProjectActivity.loadMore()` to send both.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. String defaults lose whitespace ✓ Resolved 🐞 Bug ≡ Correctness
Description
CommandToolDialog.parseDefaultValue trims string defaults and treats empty/whitespace-only as “no
default”, so users cannot set an empty-string default and leading/trailing whitespace is silently
lost when saving.
Code

web-ui/src/features/projects/components/tools/CommandToolDialog.tsx[R28-37]

+function parseDefaultValue(
+  raw: string,
+  type: CommandArgDraft['type'],
+): { ok: true; value: unknown } | { ok: false } {
+  const trimmed = raw.trim();
+  if (!trimmed) return { ok: true, value: undefined };
+
+  if (type === 'string') {
+    return { ok: true, value: trimmed };
+  }
Evidence
The parser trims the raw input and uses !trimmed to omit defaults; the submit logic only includes
default when parsed value is not undefined, so whitespace and empty-string defaults cannot
round-trip.

web-ui/src/features/projects/components/tools/CommandToolDialog.tsx[28-37]
web-ui/src/features/projects/components/tools/CommandToolDialog.tsx[141-160]
src/server/tool-executor.ts[194-211]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new command-tool default UI trims `raw` for all types; for `type==='string'` it stores `trimmed`, and it uses `!trimmed` to mean “no default”. This makes it impossible to represent an explicit empty-string default and also changes whitespace-sensitive defaults.
### Issue Context
Back end supports `argDef.default` as a fallback for missing args, so the UI should preserve exact intended string defaults.
### Fix Focus Areas
- web-ui/src/features/projects/components/tools/CommandToolDialog.tsx[28-59]
- web-ui/src/features/projects/components/tools/CommandToolDialog.tsx[141-160]
### Suggested fix
- For `type === 'string'`, do not trim the value you persist; use the raw input as-is.
- Add an explicit presence toggle (e.g. `hasDefault` checkbox) so an empty string can be distinguished from “unset default”, or use a sentinel UI option to represent empty-string defaults.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment thread src/db/tool-calls.ts
Comment thread src/db/tool-calls.ts Outdated
Comment thread web-ui/src/features/projects/components/tools/CommandToolDialog.tsx
Minitour and others added 4 commits August 1, 2026 14:40
Detect fatal stderr (e.g. Rust Tokio-thread panics), kill the hung child so in-flight tools/call rejects immediately, and limit the traces list to a scrollable max height.

Co-authored-by: Cursor <cursoragent@cursor.com>
Skip pruning running traces, paginate with a composite (started_at, id) cursor, and preserve string argument defaults via an explicit Set a default toggle.

Co-authored-by: Cursor <cursoragent@cursor.com>
Treat executor success:false and remote isError results as failed activity, and ignore list_changed notify when no MCP transport is connected.

Co-authored-by: Cursor <cursoragent@cursor.com>
Fingerprint cmd/args/env/cwd/url so version bumps in capabilities.yaml close the stale client and spawn the new one without a full capa restart.

Co-authored-by: Cursor <cursoragent@cursor.com>
@Minitour
Minitour merged commit eb77e12 into version-2.0 Aug 1, 2026
@Minitour
Minitour deleted the feat/project-activity-traces branch August 1, 2026 13:27
@Minitour Minitour mentioned this pull request Aug 2, 2026
9 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant