Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,5 @@
# @copilotkit/pathfinder

## 1.15.3

### Patch Changes

- **Analytics view — "Empty Result Queries" excludes blocked rows**: rows the v1.15.2 abuse blocklist short-circuited (`blocked = true`) are filtered out of the "Empty Result Queries (Last 7 days)" table. Those queries never reached retrieval, so `result_count = 0` is a known by-design outcome — not a content gap. Previously they showed up alongside genuine empty-result rows because the SQL filtered on `result_count = 0` alone. The blocked-column predicate is `blocked = false`, which is safe for historical rows that predate the column (the schema declares `BOOLEAN NOT NULL DEFAULT FALSE`).
- **New "Blocked Queries (Last 7 days)" panel on /analytics**: surfaces the rows the blocklist caught, grouped by `block_reason` with hit count, last-seen timestamp, and up to 5 sample queries per reason. Operators can now see the blocklist actively catching abuse instead of inferring it from the absence of off-topic rows in the empty-results table. The panel deliberately ignores tool/source/request-source filters — those carry no meaning for rows the blocklist short-circuited before retrieval — but does honor the same `days` window as the rest of the dashboard.
- **Analytics timestamps render in Pacific time (DST-aware)**: every absolute timestamp on the /analytics operator-facing page now renders in `America/Los_Angeles` via `Intl.DateTimeFormat`, emitting e.g. `2026-06-17 09:40:32 PDT` (or `PST` in winter). The dashboard is operated from Pacific; UTC timestamps forced an in-head conversion on every read. Storage and URL contracts are unchanged — Postgres still stores `created_at TIMESTAMPTZ` in UTC, the server still computes windows in UTC, and the URL `from`/`to` contract is still a UTC calendar date. Only display flips.

## 1.15.2

### Patch Changes
Expand Down
313 changes: 57 additions & 256 deletions docs/analytics.html

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@copilotkit/pathfinder",
"version": "1.15.3",
"version": "1.15.2",
"description": "The knowledge server for AI agents — index docs, code, Notion, Slack, and Discord into searchable, agent-accessible knowledge via MCP. Supports OpenAI, Ollama, and local transformers.js embeddings.",
"type": "module",
"repository": {
Expand Down
2 changes: 0 additions & 2 deletions src/__tests__/analytics-auth-length-check.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ vi.mock("../db/analytics.js", () => ({
getAnalyticsSummary: vi.fn(),
getTopQueries: vi.fn(),
getEmptyQueries: vi.fn(),
getBlockedQueries: vi.fn(),
getToolCounts: vi.fn(),
}));

Expand Down Expand Up @@ -80,7 +79,6 @@ describe("analyticsAuth length-check early return (R4-18)", () => {
getAnalyticsSummary: async () => ({ total: 0 }) as never,
getTopQueries: async () => [],
getEmptyQueries: async () => [],
getBlockedQueries: async () => [],
getToolCounts: async () => [],
});
server = http.createServer(app);
Expand Down
4 changes: 0 additions & 4 deletions src/__tests__/analytics-error-log-ip.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ vi.mock("../db/analytics.js", () => ({
getAnalyticsSummary: vi.fn(),
getTopQueries: vi.fn(),
getEmptyQueries: vi.fn(),
getBlockedQueries: vi.fn(),
getToolCounts: vi.fn(),
}));

Expand Down Expand Up @@ -48,9 +47,6 @@ function buildApp() {
getEmptyQueries: async () => {
throw new Error("db boom");
},
getBlockedQueries: async () => {
throw new Error("db boom");
},
getToolCounts: async () => {
throw new Error("db boom");
},
Expand Down
1 change: 0 additions & 1 deletion src/__tests__/analytics-sendfile-correlation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ vi.mock("../db/analytics.js", () => ({
getAnalyticsSummary: vi.fn(),
getTopQueries: vi.fn(),
getEmptyQueries: vi.fn(),
getBlockedQueries: vi.fn(),
getToolCounts: vi.fn(),
}));

Expand Down
84 changes: 0 additions & 84 deletions src/__tests__/analytics-server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,12 @@ import path from "node:path";
const mockGetAnalyticsSummary = vi.fn();
const mockGetTopQueries = vi.fn();
const mockGetEmptyQueries = vi.fn();
const mockGetBlockedQueries = vi.fn();
const mockGetToolCounts = vi.fn();

vi.mock("../db/analytics.js", () => ({
getAnalyticsSummary: (...args: unknown[]) => mockGetAnalyticsSummary(...args),
getTopQueries: (...args: unknown[]) => mockGetTopQueries(...args),
getEmptyQueries: (...args: unknown[]) => mockGetEmptyQueries(...args),
getBlockedQueries: (...args: unknown[]) => mockGetBlockedQueries(...args),
getToolCounts: (...args: unknown[]) => mockGetToolCounts(...args),
}));

Expand Down Expand Up @@ -84,8 +82,6 @@ function buildTestApp() {
mockGetTopQueries(...args),
getEmptyQueries: (...args: Parameters<typeof mockGetEmptyQueries>) =>
mockGetEmptyQueries(...args),
getBlockedQueries: (...args: Parameters<typeof mockGetBlockedQueries>) =>
mockGetBlockedQueries(...args),
getToolCounts: (...args: Parameters<typeof mockGetToolCounts>) =>
mockGetToolCounts(...args),
analyticsHtmlPath,
Expand Down Expand Up @@ -370,86 +366,6 @@ describe("Analytics server routes (HTTP-level)", () => {
});
});

// ---- /api/analytics/blocked-queries (v1.15.3) ----------------------------

describe("GET /api/analytics/blocked-queries (v1.15.3)", () => {
it("returns blocked-row groups with a valid token", async () => {
mockGetAnalyticsConfigFn.mockReturnValue({
enabled: true,
log_queries: true,
retention_days: 90,
token: "secret",
});
mockGetBlockedQueries.mockResolvedValue([
{
block_reason: "box-office",
hits: 142,
last_seen: "2026-06-17T16:40:32.000Z",
sample_queries: ["box office weekend"],
},
]);

await startApp();
const res = await request(
server,
"GET",
"/api/analytics/blocked-queries?days=7",
{ Authorization: "Bearer secret" },
);

expect(res.status).toBe(200);
const body = JSON.parse(res.body);
expect(Array.isArray(body)).toBe(true);
expect(body[0].block_reason).toBe("box-office");
expect(body[0].hits).toBe(142);
// The route forwards the parsed days value to the DB call.
expect(mockGetBlockedQueries).toHaveBeenCalledWith(7);
});

it("returns 401 without a token", async () => {
mockGetAnalyticsConfigFn.mockReturnValue({
enabled: true,
log_queries: true,
retention_days: 90,
token: "secret",
});

await startApp();
const res = await request(
server,
"GET",
"/api/analytics/blocked-queries",
);
expect(res.status).toBe(401);
});

it("returns 500 with a clean error envelope when the DB call rejects", async () => {
mockGetAnalyticsConfigFn.mockReturnValue({
enabled: true,
log_queries: true,
retention_days: 90,
token: "secret",
});
mockGetBlockedQueries.mockRejectedValue(new Error("boom"));
const consoleErrSpy = vi
.spyOn(console, "error")
.mockImplementation(() => {});

await startApp();
const res = await request(
server,
"GET",
"/api/analytics/blocked-queries",
{ Authorization: "Bearer secret" },
);

expect(res.status).toBe(500);
const body = JSON.parse(res.body);
expect(body.error).toBe("Failed to fetch blocked queries");
consoleErrSpy.mockRestore();
});
});

// ---- Auto-generated token via HTTP ---------------------------------------

describe("auto-generated token (HTTP-level)", () => {
Expand Down
Loading