kenn-io--agentsview
f99010fae1
CI / lint (push) Failing after 1s
CI / frontend (push) Failing after 1s
CI / scripts (push) Failing after 1s
CI / Go Test (ubuntu-latest) (push) Failing after 0s
CI / frontend-node-25 (push) Failing after 1s
CI / docs (push) Failing after 0s
CI / coverage (push) Failing after 0s
CI / e2e (push) Failing after 0s
Docker / build-and-push (push) Failing after 1s
CI / integration (push) Failing after 4m43s
CI / Go Test (windows-latest) (push) Has been cancelled
CI / Desktop Unit Tests (Windows) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux (arm64)) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Windows) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (aarch64)) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (x86_64)) (push) Has been cancelled
448 行
17 KiB
Bash
可执行文件
448 行
17 KiB
Bash
可执行文件
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# Extract only screenshot-safe project sessions from the source
|
|
# database. This runs on the host before the Docker build so the
|
|
# build context only contains the reduced screenshot database.
|
|
|
|
SOURCE="${1:-${SOURCE:-/data/source.db}}"
|
|
OUTPUT="${2:-${OUTPUT:-/data/test-sessions.db}}"
|
|
HISTORY_DAYS="${SCREENSHOT_HISTORY_DAYS:-60}"
|
|
DEFAULT_BLOCKED_TERMS_FILE="$HOME/.config/agentsview-docs/screenshot-blocked-terms.txt"
|
|
BLOCKED_TERMS_FILE="${SCREENSHOT_BLOCKED_TERMS_FILE:-$DEFAULT_BLOCKED_TERMS_FILE}"
|
|
PRIVATE_TERMS_FILE="${KENN_PRIVATE_TERMS_FILE:-$HOME/.config/kenn/private-terms.txt}"
|
|
HOME_PATH="${HOME:-}"
|
|
BLOCKED_TERMS="${SCREENSHOT_BLOCKED_TERMS:-}"
|
|
BLOCKED_PATTERNS_SQL="$(mktemp "${TMPDIR:-/tmp}/agentsview-screenshot-blocked-patterns.XXXXXX")"
|
|
trap 'rm -f "$BLOCKED_PATTERNS_SQL"' EXIT
|
|
|
|
append_blocked_terms() {
|
|
local terms="$1"
|
|
if [ -z "$terms" ]; then
|
|
return
|
|
fi
|
|
|
|
if [ -n "$BLOCKED_TERMS" ]; then
|
|
BLOCKED_TERMS="$BLOCKED_TERMS"$'\n'"$terms"
|
|
else
|
|
BLOCKED_TERMS="$terms"
|
|
fi
|
|
}
|
|
|
|
append_blocked_terms_file() {
|
|
local terms_file="$1"
|
|
if [ ! -f "$terms_file" ]; then
|
|
return
|
|
fi
|
|
|
|
append_blocked_terms "$(<"$terms_file")"
|
|
}
|
|
|
|
append_blocked_terms_file "$BLOCKED_TERMS_FILE"
|
|
append_blocked_terms_file "$PRIVATE_TERMS_FILE"
|
|
|
|
if [[ ! "$HISTORY_DAYS" =~ ^[0-9]+$ ]] || [ "$HISTORY_DAYS" -lt 1 ]; then
|
|
echo "Error: SCREENSHOT_HISTORY_DAYS must be a positive integer"
|
|
exit 1
|
|
fi
|
|
|
|
echo "Extracting screenshot-safe projects from source database..."
|
|
echo "Keeping sessions from the newest $HISTORY_DAYS-day window."
|
|
|
|
if ! command -v sqlite3 >/dev/null 2>&1; then
|
|
echo "Error: sqlite3 is required to prepare the screenshot database"
|
|
exit 1
|
|
fi
|
|
|
|
if [ ! -f "$SOURCE" ]; then
|
|
echo "Error: source database not found at $SOURCE"
|
|
exit 1
|
|
fi
|
|
|
|
if [ "$SOURCE" = "$OUTPUT" ]; then
|
|
echo "Error: source and output database paths must differ"
|
|
exit 1
|
|
fi
|
|
|
|
mkdir -p "$(dirname "$OUTPUT")"
|
|
rm -f "$OUTPUT"
|
|
|
|
{
|
|
echo "CREATE TEMP TABLE screenshot_redactions(from_text TEXT PRIMARY KEY, to_text TEXT NOT NULL);"
|
|
if [ -n "$HOME_PATH" ] && [ "$HOME_PATH" != "/" ]; then
|
|
home_sql="${HOME_PATH//\'/\'\'}"
|
|
printf "INSERT OR IGNORE INTO screenshot_redactions(from_text, to_text) VALUES ('%s', '~');\n" \
|
|
"$home_sql"
|
|
fi
|
|
|
|
echo "CREATE TEMP TABLE screenshot_blocked_patterns(pattern TEXT PRIMARY KEY);"
|
|
while IFS= read -r term || [ -n "$term" ]; do
|
|
trimmed="$(printf '%s' "$term" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
|
|
if [ -z "$trimmed" ]; then
|
|
continue
|
|
fi
|
|
# Skip comment lines so a terms file can document itself. Without this a
|
|
# bare '#' becomes the pattern '%#%', which matches almost every session.
|
|
case "$trimmed" in
|
|
'#'*) continue ;;
|
|
esac
|
|
|
|
pattern="$(printf '%s' "$trimmed" | tr '[:upper:]' '[:lower:]')"
|
|
pattern="${pattern//\\/\\\\}"
|
|
pattern="${pattern//%/\\%}"
|
|
pattern="${pattern//_/\\_}"
|
|
pattern="%$pattern%"
|
|
sql_pattern="${pattern//\'/\'\'}"
|
|
|
|
printf "INSERT OR IGNORE INTO screenshot_blocked_patterns(pattern) VALUES ('%s');\n" \
|
|
"$sql_pattern"
|
|
done <<<"$BLOCKED_TERMS"
|
|
} >"$BLOCKED_PATTERNS_SQL"
|
|
|
|
# Snapshot the full database with VACUUM INTO. A plain cp of a
|
|
# WAL-mode database reads a torn image and misses committed data still
|
|
# in the -wal file, surfacing as "database disk image is malformed".
|
|
# The online backup API (.backup) avoids that but restarts from
|
|
# scratch every time a live `agentsview serve` writes through its own
|
|
# connection, so it never converges against a busy daemon. VACUUM INTO
|
|
# reads one snapshot-isolated pass that concurrent writes do not
|
|
# restart, and does not touch the source. The target must not exist
|
|
# (rm -f above guarantees that).
|
|
sqlite3 "$SOURCE" "VACUUM INTO '$OUTPUT'"
|
|
|
|
# Delete sessions (and related data) for non-matching projects.
|
|
# The heredoc delimiter is quoted so bash does NOT expand $ or
|
|
# backticks inside — the embedded markdown contains both, and
|
|
# unquoted expansion would run file paths as commands.
|
|
sqlite3 "$OUTPUT" \
|
|
-cmd ".parameter init" \
|
|
-cmd ".parameter set @history_days $HISTORY_DAYS" \
|
|
-cmd ".read \"$BLOCKED_PATTERNS_SQL\"" <<'SQL'
|
|
CREATE TEMP TABLE screenshot_projects(name TEXT PRIMARY KEY);
|
|
INSERT INTO screenshot_projects(name) VALUES
|
|
('agentsview'),
|
|
('agentsview_docs'),
|
|
('conbench'),
|
|
('kata'),
|
|
('roborev'),
|
|
('roborev_docs');
|
|
|
|
CREATE TEMP TABLE screenshot_safe_sessions(id TEXT PRIMARY KEY);
|
|
CREATE TEMP TABLE screenshot_root_sessions(id TEXT PRIMARY KEY);
|
|
CREATE TEMP TABLE screenshot_sessions(id TEXT PRIMARY KEY);
|
|
CREATE TEMP TABLE screenshot_bounds(
|
|
cutoff_session_at TEXT,
|
|
newest_session_at TEXT
|
|
);
|
|
|
|
INSERT INTO screenshot_safe_sessions(id)
|
|
SELECT id
|
|
FROM sessions s
|
|
WHERE s.project IN (SELECT name FROM screenshot_projects)
|
|
AND s.message_count > 0
|
|
AND s.deleted_at IS NULL
|
|
AND NOT EXISTS (
|
|
SELECT 1
|
|
FROM screenshot_blocked_patterns p
|
|
WHERE lower(
|
|
COALESCE(s.first_message, '') || char(10) ||
|
|
COALESCE(s.display_name, '') || char(10) ||
|
|
COALESCE(s.session_name, '') || char(10) ||
|
|
COALESCE(s.cwd, '') || char(10) ||
|
|
COALESCE(s.file_path, '') || char(10) ||
|
|
COALESCE(s.git_branch, '')
|
|
) LIKE p.pattern ESCAPE '\'
|
|
)
|
|
AND NOT EXISTS (
|
|
SELECT 1
|
|
FROM messages m
|
|
JOIN screenshot_blocked_patterns p
|
|
ON lower(
|
|
COALESCE(m.content, '') || char(10) ||
|
|
COALESCE(m.thinking_text, '')
|
|
) LIKE p.pattern ESCAPE '\'
|
|
WHERE m.session_id = s.id
|
|
)
|
|
AND NOT EXISTS (
|
|
SELECT 1
|
|
FROM tool_calls tc
|
|
JOIN screenshot_blocked_patterns p
|
|
ON lower(
|
|
COALESCE(tc.input_json, '') || char(10) ||
|
|
COALESCE(tc.result_content, '') || char(10) ||
|
|
COALESCE(tc.file_path, '') || char(10) ||
|
|
COALESCE(tc.skill_name, '')
|
|
) LIKE p.pattern ESCAPE '\'
|
|
WHERE tc.session_id = s.id
|
|
)
|
|
AND NOT EXISTS (
|
|
SELECT 1
|
|
FROM tool_result_events tre
|
|
JOIN screenshot_blocked_patterns p
|
|
ON lower(COALESCE(tre.content, '')) LIKE p.pattern ESCAPE '\'
|
|
WHERE tre.session_id = s.id
|
|
);
|
|
|
|
INSERT INTO screenshot_bounds(cutoff_session_at, newest_session_at)
|
|
SELECT
|
|
datetime(max(session_at), printf('-%d days', @history_days)),
|
|
max(session_at)
|
|
FROM (
|
|
SELECT datetime(COALESCE(NULLIF(s.started_at, ''), s.created_at)) AS session_at
|
|
FROM sessions s
|
|
JOIN screenshot_safe_sessions safe ON safe.id = s.id
|
|
WHERE COALESCE(s.parent_session_id, '') = ''
|
|
AND s.relationship_type NOT IN ('subagent', 'fork', 'continuation')
|
|
);
|
|
|
|
INSERT INTO screenshot_root_sessions(id)
|
|
SELECT s.id
|
|
FROM sessions s
|
|
JOIN screenshot_safe_sessions safe ON safe.id = s.id
|
|
CROSS JOIN screenshot_bounds b
|
|
WHERE COALESCE(s.parent_session_id, '') = ''
|
|
AND s.relationship_type NOT IN ('subagent', 'fork', 'continuation')
|
|
AND datetime(COALESCE(NULLIF(s.started_at, ''), s.created_at)) >= b.cutoff_session_at;
|
|
|
|
WITH RECURSIVE screenshot_tree(id) AS (
|
|
SELECT id FROM screenshot_root_sessions
|
|
UNION
|
|
SELECT child.id
|
|
FROM sessions child
|
|
JOIN screenshot_tree parent ON child.parent_session_id = parent.id
|
|
JOIN screenshot_safe_sessions safe ON safe.id = child.id
|
|
)
|
|
INSERT INTO screenshot_sessions(id)
|
|
SELECT id FROM screenshot_tree;
|
|
|
|
-- Thinking-block screenshots navigate directly to one rich session because
|
|
-- thinking messages are rare and often older than the recent sidebar window.
|
|
-- Prefer a human root for this direct navigation fixture, while keeping
|
|
-- automated sessions available for usage/activity categorization screenshots.
|
|
INSERT OR IGNORE INTO screenshot_sessions(id)
|
|
SELECT s.id
|
|
FROM sessions s
|
|
JOIN screenshot_safe_sessions safe ON safe.id = s.id
|
|
WHERE COALESCE(s.parent_session_id, '') = ''
|
|
AND s.relationship_type NOT IN ('subagent', 'fork', 'continuation')
|
|
AND COALESCE(s.is_automated, 0) = 0
|
|
AND EXISTS (
|
|
SELECT 1
|
|
FROM messages m
|
|
WHERE m.session_id = s.id
|
|
AND COALESCE(m.thinking_text, '') != ''
|
|
)
|
|
ORDER BY (
|
|
SELECT COUNT(*)
|
|
FROM messages m
|
|
WHERE m.session_id = s.id
|
|
AND COALESCE(m.thinking_text, '') != ''
|
|
) DESC, s.id
|
|
LIMIT 1;
|
|
|
|
-- Drop FTS5 sync triggers before the bulk delete. Each
|
|
-- DELETE FROM messages otherwise fires messages_ad which
|
|
-- runs an FTS5 'delete' command, and that trips
|
|
-- "constraint failed (19)" at scale. We rebuild the FTS
|
|
-- index after the deletes and restore the triggers at
|
|
-- the end so the test DB still supports in-session search.
|
|
DROP TRIGGER IF EXISTS messages_ai;
|
|
DROP TRIGGER IF EXISTS messages_ad;
|
|
DROP TRIGGER IF EXISTS messages_au;
|
|
|
|
DELETE FROM tool_calls WHERE session_id IN (
|
|
SELECT id FROM sessions
|
|
WHERE id NOT IN (SELECT id FROM screenshot_sessions)
|
|
);
|
|
DELETE FROM tool_result_events WHERE session_id IN (
|
|
SELECT id FROM sessions
|
|
WHERE id NOT IN (SELECT id FROM screenshot_sessions)
|
|
);
|
|
DELETE FROM usage_events WHERE session_id IN (
|
|
SELECT id FROM sessions
|
|
WHERE id NOT IN (SELECT id FROM screenshot_sessions)
|
|
);
|
|
DELETE FROM pinned_messages WHERE session_id IN (
|
|
SELECT id FROM sessions
|
|
WHERE id NOT IN (SELECT id FROM screenshot_sessions)
|
|
);
|
|
DELETE FROM starred_sessions WHERE session_id IN (
|
|
SELECT id FROM sessions
|
|
WHERE id NOT IN (SELECT id FROM screenshot_sessions)
|
|
);
|
|
DELETE FROM secret_findings WHERE session_id IN (
|
|
SELECT id FROM sessions
|
|
WHERE id NOT IN (SELECT id FROM screenshot_sessions)
|
|
);
|
|
DELETE FROM messages WHERE session_id IN (
|
|
SELECT id FROM sessions
|
|
WHERE id NOT IN (SELECT id FROM screenshot_sessions)
|
|
);
|
|
DELETE FROM sessions
|
|
WHERE id NOT IN (SELECT id FROM screenshot_sessions);
|
|
|
|
UPDATE sessions
|
|
SET first_message = replace(first_message, r.from_text, r.to_text),
|
|
display_name = replace(display_name, r.from_text, r.to_text),
|
|
session_name = replace(session_name, r.from_text, r.to_text),
|
|
cwd = replace(cwd, r.from_text, r.to_text),
|
|
file_path = replace(file_path, r.from_text, r.to_text)
|
|
FROM screenshot_redactions r;
|
|
|
|
UPDATE messages
|
|
SET content = replace(content, r.from_text, r.to_text),
|
|
thinking_text = replace(thinking_text, r.from_text, r.to_text)
|
|
FROM screenshot_redactions r;
|
|
|
|
UPDATE tool_calls
|
|
SET file_path = replace(file_path, r.from_text, r.to_text),
|
|
input_json = replace(input_json, r.from_text, r.to_text),
|
|
result_content = replace(result_content, r.from_text, r.to_text)
|
|
FROM screenshot_redactions r;
|
|
|
|
UPDATE tool_result_events
|
|
SET content = replace(content, r.from_text, r.to_text)
|
|
FROM screenshot_redactions r;
|
|
|
|
-- Rebuild FTS index from the surviving messages.
|
|
INSERT INTO messages_fts(messages_fts) VALUES('rebuild');
|
|
|
|
-- Restore FTS sync triggers so future inserts/updates
|
|
-- keep the index current.
|
|
CREATE TRIGGER messages_ai AFTER INSERT ON messages BEGIN
|
|
INSERT INTO messages_fts(rowid, content)
|
|
VALUES (new.id, new.content);
|
|
END;
|
|
CREATE TRIGGER messages_ad AFTER DELETE ON messages BEGIN
|
|
INSERT INTO messages_fts(messages_fts, rowid, content)
|
|
VALUES('delete', old.id, old.content);
|
|
END;
|
|
CREATE TRIGGER messages_au AFTER UPDATE ON messages BEGIN
|
|
INSERT INTO messages_fts(messages_fts, rowid, content)
|
|
VALUES('delete', old.id, old.content);
|
|
INSERT INTO messages_fts(rowid, content)
|
|
VALUES (new.id, new.content);
|
|
END;
|
|
|
|
-- Update stats
|
|
INSERT OR REPLACE INTO stats (key, value) VALUES
|
|
('session_count', (SELECT COUNT(*) FROM sessions)),
|
|
('message_count', (SELECT COUNT(*) FROM messages));
|
|
|
|
-- Remove all real insights and replace with safe seed data
|
|
DELETE FROM insights;
|
|
|
|
INSERT INTO insights (type, date_from, date_to, project, agent, model, content, created_at) VALUES
|
|
('daily_activity', '2026-02-20', '2026-02-20', 'roborev', 'claude', 'claude-opus-4-1',
|
|
'## Summary
|
|
|
|
3 sessions across roborev today, totaling 47 messages and 28 tool calls.
|
|
|
|
### Code Review Engine Improvements
|
|
|
|
The main focus was on the code review engine. Two sessions worked on improving how review findings are grouped and deduplicated before being presented to the user.
|
|
|
|
- **Finding deduplication** — added content-hash-based dedup to prevent the same issue from appearing multiple times when files are re-reviewed. Implemented in `internal/review/dedup.go` with a sliding window approach.
|
|
- **Severity classification** — refined the severity model to distinguish between style issues, potential bugs, and architectural concerns. Updated the prompt templates in `internal/review/prompts/`.
|
|
|
|
### Test Coverage
|
|
|
|
One session focused entirely on adding test coverage for the new dedup logic. Added table-driven tests covering edge cases: overlapping findings, findings across file boundaries, and hash collisions.
|
|
|
|
### Tool Usage
|
|
|
|
| Category | Count |
|
|
|----------|-------|
|
|
| Read | 12 |
|
|
| Edit | 8 |
|
|
| Bash | 5 |
|
|
| Search | 3 |',
|
|
'2026-02-20T18:30:00.000Z'),
|
|
|
|
('agent_analysis', '2026-02-14', '2026-02-20', NULL, 'claude', 'claude-opus-4-1',
|
|
'## Weekly Analysis — Feb 14–20
|
|
|
|
### Overview
|
|
|
|
18 sessions across 3 projects over 7 days. Total: 312 messages, 187 tool calls.
|
|
|
|
| Project | Sessions | Messages | Tool Calls |
|
|
|---------|----------|----------|------------|
|
|
| roborev | 10 | 198 | 124 |
|
|
| agentsview | 5 | 72 | 41 |
|
|
|
|
### Patterns
|
|
|
|
**Session length** — median session was 14 messages. Longest session (38 messages) was a roborev refactoring session that touched 12 files. Shorter sessions (under 8 messages) were typically quick bug fixes or documentation updates.
|
|
|
|
**Tool distribution** — Read and Edit dominate at 68% of all tool calls. Bash usage is concentrated in roborev (test runs and linting). Search tool usage is low, suggesting good familiarity with the codebases.
|
|
|
|
**Time of day** — most sessions start between 9–11am and 2–4pm, with a gap around lunch. No late-night sessions this week.
|
|
|
|
### Effectiveness
|
|
|
|
- Sessions that started with a clear, specific prompt ("fix the failing test in dedup_test.go") completed faster (avg 8 messages) than open-ended ones ("improve the review engine") which averaged 22 messages.
|
|
- Tool call errors were rare (3 out of 187), all from file paths that had been renamed mid-session.
|
|
|
|
### Recommendations
|
|
|
|
1. **Break up long sessions** — the 38-message refactoring session could have been 2–3 focused sessions with clearer scope.
|
|
2. **Use search more** — several sessions spent multiple Read calls navigating to the right file. A Grep or Glob call upfront would save turns.
|
|
3. **Pin test commands** — repeated manual test invocations could be replaced with a single Bash alias or Makefile target.',
|
|
'2026-02-21T10:15:00.000Z'),
|
|
|
|
('daily_activity', '2026-02-19', '2026-02-19', 'agentsview', 'claude', 'claude-opus-4-1',
|
|
'## Summary
|
|
|
|
4 sessions in agentsview today, totaling 62 messages and 35 tool calls.
|
|
|
|
### Frontend Improvements
|
|
|
|
Two sessions focused on the analytics dashboard. Added a stacked activity timeline chart with daily, weekly, and monthly granularity. The chart uses SVG with D3 scales for responsive rendering.
|
|
|
|
- **Timeline chart** — implemented in `frontend/src/lib/components/analytics/ActivityTimeline.svelte` with agent-colored stacking and tooltip on hover.
|
|
- **Date range picker** — added quick presets (7d, 30d, 90d, 1y, All) alongside custom date inputs.
|
|
|
|
### Session Export
|
|
|
|
One session added standalone HTML export for sessions. The exported file includes all messages, tool calls, and thinking blocks with inline CSS so it renders correctly offline.
|
|
|
|
### Bug Fix
|
|
|
|
Fixed a race condition where concurrent search and session-load requests could return stale results. Added request cancellation via `AbortController`.',
|
|
'2026-02-19T17:45:00.000Z'),
|
|
|
|
('daily_activity', '2026-02-18', '2026-02-18', 'roborev', 'claude', 'claude-opus-4-1',
|
|
'## Summary
|
|
|
|
2 sessions in roborev today, totaling 31 messages and 19 tool calls.
|
|
|
|
### API Endpoint Refactoring
|
|
|
|
Refactored the review submission API to accept batch requests. Previously each file was submitted individually, which caused N+1 request patterns on large PRs. The new endpoint accepts an array of file paths and returns all findings in a single response.
|
|
|
|
- Updated `internal/server/review_handler.go` with batch support
|
|
- Added request validation for max batch size (50 files)
|
|
- Updated the CLI client to use the batch endpoint
|
|
|
|
### Documentation
|
|
|
|
One short session updated the API documentation in `docs/api.md` to reflect the new batch endpoint and its request/response schema.',
|
|
'2026-02-18T16:20:00.000Z');
|
|
|
|
VACUUM;
|
|
SQL
|
|
|
|
SESSIONS=$(sqlite3 "$OUTPUT" "SELECT COUNT(*) FROM sessions;")
|
|
MESSAGES=$(sqlite3 "$OUTPUT" "SELECT COUNT(*) FROM messages;")
|
|
SIZE=$(du -h "$OUTPUT" | cut -f1)
|
|
RANGE=$(sqlite3 -separator ' to ' "$OUTPUT" \
|
|
"SELECT COALESCE(min(datetime(COALESCE(NULLIF(started_at, ''), created_at))), '(none)'),
|
|
COALESCE(max(datetime(COALESCE(NULLIF(started_at, ''), created_at))), '(none)')
|
|
FROM sessions;")
|
|
|
|
echo "Extracted: $SESSIONS sessions, $MESSAGES messages ($SIZE)"
|
|
echo "Date range: $RANGE"
|
|
sqlite3 "$OUTPUT" \
|
|
"SELECT ' ' || project || ': ' || COUNT(*) || ' sessions' FROM sessions GROUP BY project ORDER BY project COLLATE NOCASE;"
|