@agentdeck/bridge 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/adapters/claude-code.d.ts +11 -27
- package/dist/adapters/claude-code.d.ts.map +1 -1
- package/dist/adapters/claude-code.js +29 -123
- package/dist/adapters/claude-code.js.map +1 -1
- package/dist/adapters/codex-cli.d.ts +21 -0
- package/dist/adapters/codex-cli.d.ts.map +1 -0
- package/dist/adapters/codex-cli.js +53 -0
- package/dist/adapters/codex-cli.js.map +1 -0
- package/dist/adapters/index.d.ts +4 -0
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js +13 -0
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/monitor.d.ts +34 -0
- package/dist/adapters/monitor.d.ts.map +1 -0
- package/dist/adapters/monitor.js +86 -0
- package/dist/adapters/monitor.js.map +1 -0
- package/dist/adapters/openclaw.d.ts +36 -2
- package/dist/adapters/openclaw.d.ts.map +1 -1
- package/dist/adapters/openclaw.js +329 -59
- package/dist/adapters/openclaw.js.map +1 -1
- package/dist/adapters/opencode-adapter.d.ts +48 -0
- package/dist/adapters/opencode-adapter.d.ts.map +1 -0
- package/dist/adapters/opencode-adapter.js +345 -0
- package/dist/adapters/opencode-adapter.js.map +1 -0
- package/dist/adapters/pty-adapter.d.ts +56 -0
- package/dist/adapters/pty-adapter.d.ts.map +1 -0
- package/dist/adapters/pty-adapter.js +143 -0
- package/dist/adapters/pty-adapter.js.map +1 -0
- package/dist/adb-reverse.d.ts +25 -0
- package/dist/adb-reverse.d.ts.map +1 -0
- package/dist/adb-reverse.js +129 -0
- package/dist/adb-reverse.js.map +1 -0
- package/dist/antigravity-local.d.ts +7 -0
- package/dist/antigravity-local.d.ts.map +1 -0
- package/dist/antigravity-local.js +153 -0
- package/dist/antigravity-local.js.map +1 -0
- package/dist/apme/adapters/claude-hook.d.ts +15 -0
- package/dist/apme/adapters/claude-hook.d.ts.map +1 -0
- package/dist/apme/adapters/claude-hook.js +85 -0
- package/dist/apme/adapters/claude-hook.js.map +1 -0
- package/dist/apme/adapters/claude-pty.d.ts +28 -0
- package/dist/apme/adapters/claude-pty.d.ts.map +1 -0
- package/dist/apme/adapters/claude-pty.js +88 -0
- package/dist/apme/adapters/claude-pty.js.map +1 -0
- package/dist/apme/adapters/timeline.d.ts +16 -0
- package/dist/apme/adapters/timeline.d.ts.map +1 -0
- package/dist/apme/adapters/timeline.js +66 -0
- package/dist/apme/adapters/timeline.js.map +1 -0
- package/dist/apme/classifier.d.ts +53 -0
- package/dist/apme/classifier.d.ts.map +1 -0
- package/dist/apme/classifier.js +266 -0
- package/dist/apme/classifier.js.map +1 -0
- package/dist/apme/claude-transcript-reader.d.ts +38 -0
- package/dist/apme/claude-transcript-reader.d.ts.map +1 -0
- package/dist/apme/claude-transcript-reader.js +127 -0
- package/dist/apme/claude-transcript-reader.js.map +1 -0
- package/dist/apme/collector.d.ts +108 -0
- package/dist/apme/collector.d.ts.map +1 -0
- package/dist/apme/collector.js +659 -0
- package/dist/apme/collector.js.map +1 -0
- package/dist/apme/dashboard-html.d.ts +9 -0
- package/dist/apme/dashboard-html.d.ts.map +1 -0
- package/dist/apme/dashboard-html.js +416 -0
- package/dist/apme/dashboard-html.js.map +1 -0
- package/dist/apme/http.d.ts +19 -0
- package/dist/apme/http.d.ts.map +1 -0
- package/dist/apme/http.js +210 -0
- package/dist/apme/http.js.map +1 -0
- package/dist/apme/hw-sampler.d.ts +30 -0
- package/dist/apme/hw-sampler.d.ts.map +1 -0
- package/dist/apme/hw-sampler.js +106 -0
- package/dist/apme/hw-sampler.js.map +1 -0
- package/dist/apme/index.d.ts +43 -0
- package/dist/apme/index.d.ts.map +1 -0
- package/dist/apme/index.js +75 -0
- package/dist/apme/index.js.map +1 -0
- package/dist/apme/outcome.d.ts +48 -0
- package/dist/apme/outcome.d.ts.map +1 -0
- package/dist/apme/outcome.js +232 -0
- package/dist/apme/outcome.js.map +1 -0
- package/dist/apme/recommend.d.ts +32 -0
- package/dist/apme/recommend.d.ts.map +1 -0
- package/dist/apme/recommend.js +41 -0
- package/dist/apme/recommend.js.map +1 -0
- package/dist/apme/runner.d.ts +94 -0
- package/dist/apme/runner.d.ts.map +1 -0
- package/dist/apme/runner.js +812 -0
- package/dist/apme/runner.js.map +1 -0
- package/dist/apme/settings.d.ts +65 -0
- package/dist/apme/settings.d.ts.map +1 -0
- package/dist/apme/settings.js +97 -0
- package/dist/apme/settings.js.map +1 -0
- package/dist/apme/store.d.ts +99 -0
- package/dist/apme/store.d.ts.map +1 -0
- package/dist/apme/store.js +911 -0
- package/dist/apme/store.js.map +1 -0
- package/dist/apme/tuner.d.ts +64 -0
- package/dist/apme/tuner.d.ts.map +1 -0
- package/dist/apme/tuner.js +297 -0
- package/dist/apme/tuner.js.map +1 -0
- package/dist/apme/types.d.ts +11 -0
- package/dist/apme/types.d.ts.map +1 -0
- package/dist/apme/types.js +10 -0
- package/dist/apme/types.js.map +1 -0
- package/dist/auth.d.ts +1 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +15 -5
- package/dist/auth.js.map +1 -1
- package/dist/bridge-core.d.ts +162 -0
- package/dist/bridge-core.d.ts.map +1 -0
- package/dist/bridge-core.js +627 -0
- package/dist/bridge-core.js.map +1 -0
- package/dist/check-deps.d.ts +3 -1
- package/dist/check-deps.d.ts.map +1 -1
- package/dist/check-deps.js +34 -13
- package/dist/check-deps.js.map +1 -1
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +1235 -0
- package/dist/cli.js.map +1 -0
- package/dist/codex-auth.d.ts +10 -0
- package/dist/codex-auth.d.ts.map +1 -0
- package/dist/codex-auth.js +63 -0
- package/dist/codex-auth.js.map +1 -0
- package/dist/codex-output-parser.d.ts +21 -0
- package/dist/codex-output-parser.d.ts.map +1 -0
- package/dist/codex-output-parser.js +253 -0
- package/dist/codex-output-parser.js.map +1 -0
- package/dist/d200h/hid-protocol.d.ts +62 -0
- package/dist/d200h/hid-protocol.d.ts.map +1 -0
- package/dist/d200h/hid-protocol.js +124 -0
- package/dist/d200h/hid-protocol.js.map +1 -0
- package/dist/d200h/image-renderer.d.ts +36 -0
- package/dist/d200h/image-renderer.d.ts.map +1 -0
- package/dist/d200h/image-renderer.js +487 -0
- package/dist/d200h/image-renderer.js.map +1 -0
- package/dist/daemon-server.d.ts +19 -0
- package/dist/daemon-server.d.ts.map +1 -0
- package/dist/daemon-server.js +1168 -0
- package/dist/daemon-server.js.map +1 -0
- package/dist/daemon-ws-client.d.ts +82 -0
- package/dist/daemon-ws-client.d.ts.map +1 -0
- package/dist/daemon-ws-client.js +179 -0
- package/dist/daemon-ws-client.js.map +1 -0
- package/dist/daemon.d.ts +10 -0
- package/dist/daemon.d.ts.map +1 -0
- package/dist/daemon.js +56 -0
- package/dist/daemon.js.map +1 -0
- package/dist/display-monitor.d.ts +25 -0
- package/dist/display-monitor.d.ts.map +1 -0
- package/dist/display-monitor.js +143 -0
- package/dist/display-monitor.js.map +1 -0
- package/dist/esp32-serial.d.ts +112 -0
- package/dist/esp32-serial.d.ts.map +1 -0
- package/dist/esp32-serial.js +502 -0
- package/dist/esp32-serial.js.map +1 -0
- package/dist/gateway-probe.d.ts +12 -0
- package/dist/gateway-probe.d.ts.map +1 -0
- package/dist/gateway-probe.js +39 -0
- package/dist/gateway-probe.js.map +1 -0
- package/dist/hook-migration.d.ts +2 -0
- package/dist/hook-migration.d.ts.map +1 -0
- package/dist/hook-migration.js +104 -0
- package/dist/hook-migration.js.map +1 -0
- package/dist/hook-server.d.ts +29 -0
- package/dist/hook-server.d.ts.map +1 -1
- package/dist/hook-server.js +366 -8
- package/dist/hook-server.js.map +1 -1
- package/dist/index.d.ts +20 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1304 -562
- package/dist/index.js.map +1 -1
- package/dist/log-stream.d.ts +26 -0
- package/dist/log-stream.d.ts.map +1 -0
- package/dist/log-stream.js +111 -0
- package/dist/log-stream.js.map +1 -0
- package/dist/logger.d.ts +9 -2
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +36 -5
- package/dist/logger.js.map +1 -1
- package/dist/mdns.d.ts +15 -1
- package/dist/mdns.d.ts.map +1 -1
- package/dist/mdns.js +143 -28
- package/dist/mdns.js.map +1 -1
- package/dist/mlx-probe.d.ts +14 -0
- package/dist/mlx-probe.d.ts.map +1 -0
- package/dist/mlx-probe.js +44 -0
- package/dist/mlx-probe.js.map +1 -0
- package/dist/model-catalog.d.ts +5 -3
- package/dist/model-catalog.d.ts.map +1 -1
- package/dist/model-catalog.js +45 -29
- package/dist/model-catalog.js.map +1 -1
- package/dist/modules/adb-module.d.ts +14 -0
- package/dist/modules/adb-module.d.ts.map +1 -0
- package/dist/modules/adb-module.js +36 -0
- package/dist/modules/adb-module.js.map +1 -0
- package/dist/modules/d200h-module.d.ts +43 -0
- package/dist/modules/d200h-module.d.ts.map +1 -0
- package/dist/modules/d200h-module.js +360 -0
- package/dist/modules/d200h-module.js.map +1 -0
- package/dist/modules/d200h-renderer.d.ts +7 -0
- package/dist/modules/d200h-renderer.d.ts.map +1 -0
- package/dist/modules/d200h-renderer.js +321 -0
- package/dist/modules/d200h-renderer.js.map +1 -0
- package/dist/modules/index.d.ts +23 -0
- package/dist/modules/index.d.ts.map +1 -0
- package/dist/modules/index.js +65 -0
- package/dist/modules/index.js.map +1 -0
- package/dist/modules/mdns-module.d.ts +12 -0
- package/dist/modules/mdns-module.d.ts.map +1 -0
- package/dist/modules/mdns-module.js +21 -0
- package/dist/modules/mdns-module.js.map +1 -0
- package/dist/modules/pixoo-module.d.ts +8 -0
- package/dist/modules/pixoo-module.d.ts.map +1 -0
- package/dist/modules/pixoo-module.js +24 -0
- package/dist/modules/pixoo-module.js.map +1 -0
- package/dist/modules/serial-module.d.ts +18 -0
- package/dist/modules/serial-module.d.ts.map +1 -0
- package/dist/modules/serial-module.js +47 -0
- package/dist/modules/serial-module.js.map +1 -0
- package/dist/modules/types.d.ts +39 -0
- package/dist/modules/types.d.ts.map +1 -0
- package/dist/modules/types.js +2 -0
- package/dist/modules/types.js.map +1 -0
- package/dist/ollama-probe.d.ts +13 -0
- package/dist/ollama-probe.d.ts.map +1 -0
- package/dist/ollama-probe.js +46 -0
- package/dist/ollama-probe.js.map +1 -0
- package/dist/opencode-client.d.ts +126 -0
- package/dist/opencode-client.d.ts.map +1 -0
- package/dist/opencode-client.js +181 -0
- package/dist/opencode-client.js.map +1 -0
- package/dist/output-parser.d.ts +3 -0
- package/dist/output-parser.d.ts.map +1 -1
- package/dist/output-parser.js +29 -3
- package/dist/output-parser.js.map +1 -1
- package/dist/passive-observer.d.ts +38 -0
- package/dist/passive-observer.d.ts.map +1 -0
- package/dist/passive-observer.js +632 -0
- package/dist/passive-observer.js.map +1 -0
- package/dist/pixoo/pixoo-bridge.d.ts +50 -0
- package/dist/pixoo/pixoo-bridge.d.ts.map +1 -0
- package/dist/pixoo/pixoo-bridge.js +280 -0
- package/dist/pixoo/pixoo-bridge.js.map +1 -0
- package/dist/pixoo/pixoo-camera.d.ts +80 -0
- package/dist/pixoo/pixoo-camera.d.ts.map +1 -0
- package/dist/pixoo/pixoo-camera.js +271 -0
- package/dist/pixoo/pixoo-camera.js.map +1 -0
- package/dist/pixoo/pixoo-client.d.ts +67 -0
- package/dist/pixoo/pixoo-client.d.ts.map +1 -0
- package/dist/pixoo/pixoo-client.js +335 -0
- package/dist/pixoo/pixoo-client.js.map +1 -0
- package/dist/pixoo/pixoo-font.d.ts +24 -0
- package/dist/pixoo/pixoo-font.d.ts.map +1 -0
- package/dist/pixoo/pixoo-font.js +121 -0
- package/dist/pixoo/pixoo-font.js.map +1 -0
- package/dist/pixoo/pixoo-renderer.d.ts +32 -0
- package/dist/pixoo/pixoo-renderer.d.ts.map +1 -0
- package/dist/pixoo/pixoo-renderer.js +696 -0
- package/dist/pixoo/pixoo-renderer.js.map +1 -0
- package/dist/pixoo/pixoo-settings.d.ts +10 -0
- package/dist/pixoo/pixoo-settings.d.ts.map +1 -0
- package/dist/pixoo/pixoo-settings.js +47 -0
- package/dist/pixoo/pixoo-settings.js.map +1 -0
- package/dist/pixoo/pixoo-sprites.d.ts +187 -0
- package/dist/pixoo/pixoo-sprites.d.ts.map +1 -0
- package/dist/pixoo/pixoo-sprites.js +926 -0
- package/dist/pixoo/pixoo-sprites.js.map +1 -0
- package/dist/pty-manager.d.ts +1 -1
- package/dist/pty-manager.d.ts.map +1 -1
- package/dist/pty-manager.js +36 -15
- package/dist/pty-manager.js.map +1 -1
- package/dist/session-aggregator.d.ts +41 -0
- package/dist/session-aggregator.d.ts.map +1 -0
- package/dist/session-aggregator.js +76 -0
- package/dist/session-aggregator.js.map +1 -0
- package/dist/session-focus-relay.d.ts +35 -0
- package/dist/session-focus-relay.d.ts.map +1 -0
- package/dist/session-focus-relay.js +127 -0
- package/dist/session-focus-relay.js.map +1 -0
- package/dist/session-registry.d.ts +88 -1
- package/dist/session-registry.d.ts.map +1 -1
- package/dist/session-registry.js +222 -15
- package/dist/session-registry.js.map +1 -1
- package/dist/session-timeline-relay.d.ts +27 -0
- package/dist/session-timeline-relay.d.ts.map +1 -0
- package/dist/session-timeline-relay.js +124 -0
- package/dist/session-timeline-relay.js.map +1 -0
- package/dist/state-machine.d.ts +1 -0
- package/dist/state-machine.d.ts.map +1 -1
- package/dist/state-machine.js +14 -0
- package/dist/state-machine.js.map +1 -1
- package/dist/terminal-status.d.ts +28 -0
- package/dist/terminal-status.d.ts.map +1 -0
- package/dist/terminal-status.js +152 -0
- package/dist/terminal-status.js.map +1 -0
- package/dist/timeline-store.d.ts +22 -0
- package/dist/timeline-store.d.ts.map +1 -0
- package/dist/timeline-store.js +79 -0
- package/dist/timeline-store.js.map +1 -0
- package/dist/timeline-summarizer.d.ts +14 -0
- package/dist/timeline-summarizer.d.ts.map +1 -0
- package/dist/timeline-summarizer.js +154 -0
- package/dist/timeline-summarizer.js.map +1 -0
- package/dist/tts.d.ts +25 -0
- package/dist/tts.d.ts.map +1 -0
- package/dist/tts.js +99 -0
- package/dist/tts.js.map +1 -0
- package/dist/tui/ansi.d.ts +92 -0
- package/dist/tui/ansi.d.ts.map +1 -0
- package/dist/tui/ansi.js +181 -0
- package/dist/tui/ansi.js.map +1 -0
- package/dist/tui/dashboard.d.ts +35 -0
- package/dist/tui/dashboard.d.ts.map +1 -0
- package/dist/tui/dashboard.js +436 -0
- package/dist/tui/dashboard.js.map +1 -0
- package/dist/tui/gauge.d.ts +15 -0
- package/dist/tui/gauge.d.ts.map +1 -0
- package/dist/tui/gauge.js +82 -0
- package/dist/tui/gauge.js.map +1 -0
- package/dist/tui/renderer.d.ts +15 -0
- package/dist/tui/renderer.d.ts.map +1 -0
- package/dist/tui/renderer.js +758 -0
- package/dist/tui/renderer.js.map +1 -0
- package/dist/tui/screen.d.ts +27 -0
- package/dist/tui/screen.d.ts.map +1 -0
- package/dist/tui/screen.js +109 -0
- package/dist/tui/screen.js.map +1 -0
- package/dist/tui/terrarium.d.ts +96 -0
- package/dist/tui/terrarium.d.ts.map +1 -0
- package/dist/tui/terrarium.js +912 -0
- package/dist/tui/terrarium.js.map +1 -0
- package/dist/types.d.ts +4 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +3 -2
- package/dist/types.js.map +1 -1
- package/dist/usage-api.d.ts +9 -2
- package/dist/usage-api.d.ts.map +1 -1
- package/dist/usage-api.js +224 -42
- package/dist/usage-api.js.map +1 -1
- package/dist/usage-event.d.ts +12 -0
- package/dist/usage-event.d.ts.map +1 -0
- package/dist/usage-event.js +78 -0
- package/dist/usage-event.js.map +1 -0
- package/dist/usage-tracker.d.ts +4 -0
- package/dist/usage-tracker.d.ts.map +1 -1
- package/dist/usage-tracker.js +17 -0
- package/dist/usage-tracker.js.map +1 -1
- package/dist/utility-proxy.d.ts +30 -0
- package/dist/utility-proxy.d.ts.map +1 -0
- package/dist/utility-proxy.js +217 -0
- package/dist/utility-proxy.js.map +1 -0
- package/dist/utils/project-name.d.ts +22 -0
- package/dist/utils/project-name.d.ts.map +1 -0
- package/dist/utils/project-name.js +72 -0
- package/dist/utils/project-name.js.map +1 -0
- package/dist/version-check.d.ts +26 -0
- package/dist/version-check.d.ts.map +1 -0
- package/dist/version-check.js +225 -0
- package/dist/version-check.js.map +1 -0
- package/dist/voice-assistant.d.ts +63 -0
- package/dist/voice-assistant.d.ts.map +1 -0
- package/dist/voice-assistant.js +406 -0
- package/dist/voice-assistant.js.map +1 -0
- package/dist/voice.d.ts +2 -0
- package/dist/voice.d.ts.map +1 -1
- package/dist/voice.js +26 -1
- package/dist/voice.js.map +1 -1
- package/dist/wake-word.d.ts +35 -0
- package/dist/wake-word.d.ts.map +1 -0
- package/dist/wake-word.js +225 -0
- package/dist/wake-word.js.map +1 -0
- package/dist/whisper-server-manager.d.ts +1 -1
- package/dist/whisper-server-manager.js +1 -1
- package/dist/wifi-config.d.ts +24 -0
- package/dist/wifi-config.d.ts.map +1 -0
- package/dist/wifi-config.js +84 -0
- package/dist/wifi-config.js.map +1 -0
- package/dist/ws-server.d.ts +12 -0
- package/dist/ws-server.d.ts.map +1 -1
- package/dist/ws-server.js +81 -3
- package/dist/ws-server.js.map +1 -1
- package/package.json +21 -13
|
@@ -0,0 +1,911 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* APME SQLite store — wraps better-sqlite3 with a tiny DAO.
|
|
3
|
+
*
|
|
4
|
+
* better-sqlite3 is an optional native dep; if it fails to load (e.g. CI without
|
|
5
|
+
* build tooling), we fall back to a no-op store so the bridge still boots.
|
|
6
|
+
* Callers should check `store.enabled` before assuming persistence.
|
|
7
|
+
*/
|
|
8
|
+
import { existsSync, mkdirSync } from 'fs';
|
|
9
|
+
import { dirname, join } from 'path';
|
|
10
|
+
import { homedir } from 'os';
|
|
11
|
+
import { createRequire } from 'module';
|
|
12
|
+
import { debug } from '../logger.js';
|
|
13
|
+
// better-sqlite3 is an optional native dep. Resolving via createRequire from
|
|
14
|
+
// this file's URL lets Node walk `bridge/node_modules/*` via the pnpm
|
|
15
|
+
// workspace symlinks, regardless of the process CWD (vitest runs from the
|
|
16
|
+
// repo root, where the symlink doesn't exist).
|
|
17
|
+
const require = createRequire(import.meta.url);
|
|
18
|
+
// ─── Schema ────────────────────────────────────────────────────────────────────
|
|
19
|
+
const DDL = `
|
|
20
|
+
CREATE TABLE IF NOT EXISTS runs (
|
|
21
|
+
id TEXT PRIMARY KEY,
|
|
22
|
+
session_id TEXT NOT NULL,
|
|
23
|
+
agent_type TEXT NOT NULL,
|
|
24
|
+
model_id TEXT,
|
|
25
|
+
project_name TEXT,
|
|
26
|
+
project_path TEXT,
|
|
27
|
+
task_prompt TEXT,
|
|
28
|
+
started_at INTEGER NOT NULL,
|
|
29
|
+
ended_at INTEGER,
|
|
30
|
+
input_tokens INTEGER,
|
|
31
|
+
output_tokens INTEGER,
|
|
32
|
+
cost_usd REAL,
|
|
33
|
+
exit_code INTEGER,
|
|
34
|
+
git_before TEXT,
|
|
35
|
+
git_after TEXT,
|
|
36
|
+
hw_profile TEXT,
|
|
37
|
+
task_signals TEXT,
|
|
38
|
+
task_category TEXT,
|
|
39
|
+
task_category_source TEXT DEFAULT 'auto',
|
|
40
|
+
outcome TEXT,
|
|
41
|
+
outcome_confidence TEXT,
|
|
42
|
+
efficiency_json TEXT,
|
|
43
|
+
composite_score REAL
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
CREATE TABLE IF NOT EXISTS steps (
|
|
47
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
48
|
+
run_id TEXT NOT NULL REFERENCES runs(id) ON DELETE CASCADE,
|
|
49
|
+
ts INTEGER NOT NULL,
|
|
50
|
+
kind TEXT NOT NULL,
|
|
51
|
+
tool_name TEXT,
|
|
52
|
+
payload TEXT
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
CREATE TABLE IF NOT EXISTS turns (
|
|
56
|
+
id TEXT PRIMARY KEY,
|
|
57
|
+
run_id TEXT NOT NULL REFERENCES runs(id) ON DELETE CASCADE,
|
|
58
|
+
task_id TEXT,
|
|
59
|
+
turn_index INTEGER NOT NULL,
|
|
60
|
+
prompt TEXT,
|
|
61
|
+
response TEXT,
|
|
62
|
+
started_at INTEGER NOT NULL,
|
|
63
|
+
ended_at INTEGER,
|
|
64
|
+
tool_calls INTEGER DEFAULT 0,
|
|
65
|
+
files_modified INTEGER DEFAULT 0,
|
|
66
|
+
files_created INTEGER DEFAULT 0,
|
|
67
|
+
git_before TEXT,
|
|
68
|
+
git_after TEXT,
|
|
69
|
+
task_category TEXT,
|
|
70
|
+
outcome TEXT,
|
|
71
|
+
composite_score REAL,
|
|
72
|
+
efficiency_json TEXT
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
CREATE INDEX IF NOT EXISTS idx_turns_run ON turns(run_id);
|
|
76
|
+
CREATE INDEX IF NOT EXISTS idx_turns_task ON turns(task_id);
|
|
77
|
+
|
|
78
|
+
CREATE TABLE IF NOT EXISTS tasks (
|
|
79
|
+
id TEXT PRIMARY KEY,
|
|
80
|
+
run_id TEXT NOT NULL REFERENCES runs(id) ON DELETE CASCADE,
|
|
81
|
+
task_index INTEGER NOT NULL,
|
|
82
|
+
boundary_signal TEXT NOT NULL,
|
|
83
|
+
started_at INTEGER NOT NULL,
|
|
84
|
+
ended_at INTEGER,
|
|
85
|
+
first_turn_index INTEGER,
|
|
86
|
+
last_turn_index INTEGER,
|
|
87
|
+
summary TEXT,
|
|
88
|
+
outcome TEXT,
|
|
89
|
+
composite_score REAL,
|
|
90
|
+
task_category TEXT,
|
|
91
|
+
notes_json TEXT
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_run ON tasks(run_id);
|
|
95
|
+
|
|
96
|
+
CREATE TABLE IF NOT EXISTS artifacts (
|
|
97
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
98
|
+
run_id TEXT NOT NULL REFERENCES runs(id) ON DELETE CASCADE,
|
|
99
|
+
kind TEXT NOT NULL,
|
|
100
|
+
path TEXT NOT NULL,
|
|
101
|
+
sha256 TEXT,
|
|
102
|
+
bytes INTEGER
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
CREATE TABLE IF NOT EXISTS evals (
|
|
106
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
107
|
+
run_id TEXT NOT NULL REFERENCES runs(id) ON DELETE CASCADE,
|
|
108
|
+
turn_id TEXT REFERENCES turns(id) ON DELETE CASCADE,
|
|
109
|
+
task_id TEXT REFERENCES tasks(id) ON DELETE CASCADE,
|
|
110
|
+
layer TEXT NOT NULL,
|
|
111
|
+
metric TEXT NOT NULL,
|
|
112
|
+
score REAL,
|
|
113
|
+
raw TEXT,
|
|
114
|
+
rubric_ver INTEGER,
|
|
115
|
+
judge_model TEXT,
|
|
116
|
+
created_at INTEGER NOT NULL
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
CREATE TABLE IF NOT EXISTS rubrics (
|
|
120
|
+
version INTEGER PRIMARY KEY,
|
|
121
|
+
purpose TEXT NOT NULL,
|
|
122
|
+
prompt TEXT NOT NULL,
|
|
123
|
+
weights TEXT NOT NULL,
|
|
124
|
+
created_at INTEGER NOT NULL,
|
|
125
|
+
parent_ver INTEGER,
|
|
126
|
+
notes TEXT
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
CREATE TABLE IF NOT EXISTS vibe_feedback (
|
|
130
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
131
|
+
run_id TEXT NOT NULL REFERENCES runs(id) ON DELETE CASCADE,
|
|
132
|
+
verdict TEXT NOT NULL,
|
|
133
|
+
note TEXT,
|
|
134
|
+
ts INTEGER NOT NULL
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
CREATE INDEX IF NOT EXISTS idx_runs_model ON runs(model_id);
|
|
138
|
+
CREATE INDEX IF NOT EXISTS idx_runs_agent ON runs(agent_type);
|
|
139
|
+
CREATE INDEX IF NOT EXISTS idx_runs_started ON runs(started_at);
|
|
140
|
+
CREATE INDEX IF NOT EXISTS idx_evals_run ON evals(run_id);
|
|
141
|
+
CREATE INDEX IF NOT EXISTS idx_steps_run ON steps(run_id);
|
|
142
|
+
|
|
143
|
+
-- Pre-aggregate per-run eval metrics to avoid inflating cost_usd when
|
|
144
|
+
-- multiple eval rows exist per run (e.g. 3 deterministic + 5 judge axes).
|
|
145
|
+
CREATE VIEW IF NOT EXISTS v_run_metrics AS
|
|
146
|
+
SELECT
|
|
147
|
+
run_id,
|
|
148
|
+
MAX(CASE WHEN metric='overall' AND layer='llm_judge' THEN score END) AS overall,
|
|
149
|
+
MAX(CASE WHEN metric='tests_pass' AND layer='deterministic' THEN score END) AS tests_pass
|
|
150
|
+
FROM evals
|
|
151
|
+
GROUP BY run_id;
|
|
152
|
+
|
|
153
|
+
CREATE VIEW IF NOT EXISTS v_model_scorecard AS
|
|
154
|
+
SELECT
|
|
155
|
+
r.agent_type AS agent_type,
|
|
156
|
+
COALESCE(r.model_id, 'unknown') AS model_id,
|
|
157
|
+
COUNT(*) AS runs,
|
|
158
|
+
AVG(m.overall) AS avg_overall,
|
|
159
|
+
AVG(m.tests_pass) AS avg_tests_pass,
|
|
160
|
+
SUM(r.cost_usd) AS total_cost,
|
|
161
|
+
CASE
|
|
162
|
+
WHEN AVG(m.overall) > 0
|
|
163
|
+
THEN SUM(r.cost_usd) / AVG(m.overall)
|
|
164
|
+
ELSE NULL
|
|
165
|
+
END AS cost_per_quality
|
|
166
|
+
FROM runs r
|
|
167
|
+
LEFT JOIN v_run_metrics m ON m.run_id = r.id
|
|
168
|
+
GROUP BY r.agent_type, r.model_id;
|
|
169
|
+
|
|
170
|
+
CREATE VIEW IF NOT EXISTS v_category_scorecard AS
|
|
171
|
+
SELECT
|
|
172
|
+
r.task_category AS task_category,
|
|
173
|
+
COALESCE(r.model_id, 'unknown') AS model_id,
|
|
174
|
+
COUNT(*) AS runs,
|
|
175
|
+
AVG(m.overall) AS avg_overall,
|
|
176
|
+
AVG(m.tests_pass) AS avg_tests_pass,
|
|
177
|
+
SUM(r.cost_usd) AS total_cost
|
|
178
|
+
FROM runs r
|
|
179
|
+
LEFT JOIN v_run_metrics m ON m.run_id = r.id
|
|
180
|
+
WHERE r.task_category IS NOT NULL AND r.task_category != 'unknown'
|
|
181
|
+
GROUP BY r.task_category, r.model_id;
|
|
182
|
+
`;
|
|
183
|
+
// ─── Default rubric v1 (seeded on first boot) ──────────────────────────────────
|
|
184
|
+
const DEFAULT_RUBRIC_V1 = {
|
|
185
|
+
version: 1,
|
|
186
|
+
purpose: 'general',
|
|
187
|
+
prompt: `You are a senior engineer evaluating whether an AI coding agent completed the user's task.
|
|
188
|
+
|
|
189
|
+
Given the task prompt and the git diff produced, evaluate the agent's contribution.
|
|
190
|
+
Score each axis as a float in [0,1] where 0=failed and 1=excellent.
|
|
191
|
+
|
|
192
|
+
Axes:
|
|
193
|
+
- task_completion: Did the agent actually do what the user asked? A perfect score means the task prompt's request was fully addressed in the diff. A zero means nothing relevant was done.
|
|
194
|
+
- code_quality: Is the code correct, safe, and maintainable? Check for bugs, missing error handling, security issues, and dead code.
|
|
195
|
+
- efficiency: Did the agent make minimal, focused changes? Penalize unrelated modifications, unnecessary refactoring, or verbose solutions to simple problems.
|
|
196
|
+
- overall: Your holistic judgment. Weight task_completion most heavily — a session that completes the task with decent quality is better than a perfect-style session that misses the point.
|
|
197
|
+
|
|
198
|
+
Important: Explain your reasoning with specific references to what was done and what was missed. List concrete items with checkmarks (done) and crosses (missed). This reasoning will be shown to the user for verification.
|
|
199
|
+
|
|
200
|
+
Return strict JSON: {"task_completion":N,"code_quality":N,"efficiency":N,"overall":N,"reasoning":"...", "done":["item1","item2"], "missed":["item1"]}.`,
|
|
201
|
+
weights: JSON.stringify({ task_completion: 0.5, code_quality: 0.3, efficiency: 0.2 }),
|
|
202
|
+
notes: 'seeded default',
|
|
203
|
+
};
|
|
204
|
+
// ─── Category-specific rubrics ──────────────────────────────────────────────
|
|
205
|
+
// Each category has evaluation axes suited to its domain.
|
|
206
|
+
// The judge selects the rubric matching the run's taskCategory.
|
|
207
|
+
// Falls back to 'general' if no category-specific rubric exists.
|
|
208
|
+
const CATEGORY_RUBRICS = {
|
|
209
|
+
conversation: {
|
|
210
|
+
purpose: 'conversation',
|
|
211
|
+
prompt: `You are evaluating an AI assistant's response to a conversational query or question.
|
|
212
|
+
The user asked a question and the agent responded. Evaluate the quality of the response.
|
|
213
|
+
|
|
214
|
+
Score each axis as a float in [0,1] where 0=failed and 1=excellent.
|
|
215
|
+
|
|
216
|
+
Axes:
|
|
217
|
+
- accuracy: Is the answer factually correct? For math/logic questions, is the result right?
|
|
218
|
+
- helpfulness: Does the response address what the user actually wanted? Is it complete?
|
|
219
|
+
- conciseness: Is the response appropriately sized? Not too verbose, not too terse.
|
|
220
|
+
- overall: Holistic judgment. An accurate, helpful response scores high even if brief.
|
|
221
|
+
|
|
222
|
+
Return strict JSON: {"accuracy":N,"helpfulness":N,"conciseness":N,"overall":N,"reasoning":"...", "done":["item1"], "missed":["item1"]}.`,
|
|
223
|
+
weights: JSON.stringify({ accuracy: 0.5, helpfulness: 0.3, conciseness: 0.2 }),
|
|
224
|
+
notes: 'conversation/Q&A evaluation',
|
|
225
|
+
},
|
|
226
|
+
planning: {
|
|
227
|
+
purpose: 'planning',
|
|
228
|
+
prompt: `You are evaluating an AI agent's planning session. The user asked the agent to plan an approach for a task.
|
|
229
|
+
|
|
230
|
+
Score each axis as a float in [0,1] where 0=failed and 1=excellent.
|
|
231
|
+
|
|
232
|
+
Axes:
|
|
233
|
+
- completeness: Does the plan cover all aspects of the request? Are edge cases considered?
|
|
234
|
+
- feasibility: Is the plan technically sound and implementable? Are the proposed steps realistic?
|
|
235
|
+
- clarity: Is the plan well-structured, easy to follow, with clear priorities?
|
|
236
|
+
- overall: Holistic judgment. A thorough, actionable plan scores high.
|
|
237
|
+
|
|
238
|
+
Return strict JSON: {"completeness":N,"feasibility":N,"clarity":N,"overall":N,"reasoning":"...", "done":["item1"], "missed":["item1"]}.`,
|
|
239
|
+
weights: JSON.stringify({ completeness: 0.4, feasibility: 0.35, clarity: 0.25 }),
|
|
240
|
+
notes: 'planning/architecture evaluation',
|
|
241
|
+
},
|
|
242
|
+
research: {
|
|
243
|
+
purpose: 'research',
|
|
244
|
+
prompt: `You are evaluating an AI agent's research session. The user asked the agent to investigate, search, or gather information.
|
|
245
|
+
|
|
246
|
+
Score each axis as a float in [0,1] where 0=failed and 1=excellent.
|
|
247
|
+
|
|
248
|
+
Axes:
|
|
249
|
+
- thoroughness: Did the agent search broadly enough? Were relevant files, docs, or sources explored?
|
|
250
|
+
- relevance: Is the information found actually relevant to the user's question?
|
|
251
|
+
- synthesis: Did the agent synthesize findings into a clear answer or summary?
|
|
252
|
+
- overall: Holistic judgment. Research that finds the right answer efficiently scores high.
|
|
253
|
+
|
|
254
|
+
Return strict JSON: {"thoroughness":N,"relevance":N,"synthesis":N,"overall":N,"reasoning":"...", "done":["item1"], "missed":["item1"]}.`,
|
|
255
|
+
weights: JSON.stringify({ thoroughness: 0.3, relevance: 0.4, synthesis: 0.3 }),
|
|
256
|
+
notes: 'research/investigation evaluation',
|
|
257
|
+
},
|
|
258
|
+
debugging: {
|
|
259
|
+
purpose: 'debugging',
|
|
260
|
+
prompt: `You are evaluating an AI agent's debugging session. The user reported a bug and the agent investigated and attempted to fix it.
|
|
261
|
+
|
|
262
|
+
Given the task prompt and the git diff produced, evaluate the debugging effort.
|
|
263
|
+
Score each axis as a float in [0,1] where 0=failed and 1=excellent.
|
|
264
|
+
|
|
265
|
+
Axes:
|
|
266
|
+
- diagnosis: Did the agent correctly identify the root cause? Not just symptoms but the actual bug?
|
|
267
|
+
- fix_quality: Is the fix correct, minimal, and safe? Does it avoid introducing new bugs?
|
|
268
|
+
- verification: Did the agent verify the fix (run tests, check edge cases)?
|
|
269
|
+
- overall: Holistic judgment. A correct diagnosis + clean fix scores high.
|
|
270
|
+
|
|
271
|
+
Return strict JSON: {"diagnosis":N,"fix_quality":N,"verification":N,"overall":N,"reasoning":"...", "done":["item1"], "missed":["item1"]}.`,
|
|
272
|
+
weights: JSON.stringify({ diagnosis: 0.35, fix_quality: 0.4, verification: 0.25 }),
|
|
273
|
+
notes: 'debugging evaluation',
|
|
274
|
+
},
|
|
275
|
+
refactoring: {
|
|
276
|
+
purpose: 'refactoring',
|
|
277
|
+
prompt: `You are evaluating an AI agent's refactoring session. The user asked the agent to restructure or improve existing code.
|
|
278
|
+
|
|
279
|
+
Given the task prompt and the git diff produced, evaluate the refactoring.
|
|
280
|
+
Score each axis as a float in [0,1] where 0=failed and 1=excellent.
|
|
281
|
+
|
|
282
|
+
Axes:
|
|
283
|
+
- safety: Does the refactoring preserve existing behavior? No regressions introduced?
|
|
284
|
+
- improvement: Is the resulting code genuinely better? Cleaner, more maintainable, less duplication?
|
|
285
|
+
- scope: Was the refactoring appropriately scoped? Not too aggressive, not too timid?
|
|
286
|
+
- overall: Holistic judgment. Safe refactoring that clearly improves the code scores high.
|
|
287
|
+
|
|
288
|
+
Return strict JSON: {"safety":N,"improvement":N,"scope":N,"overall":N,"reasoning":"...", "done":["item1"], "missed":["item1"]}.`,
|
|
289
|
+
weights: JSON.stringify({ safety: 0.4, improvement: 0.35, scope: 0.25 }),
|
|
290
|
+
notes: 'refactoring evaluation',
|
|
291
|
+
},
|
|
292
|
+
review: {
|
|
293
|
+
purpose: 'review',
|
|
294
|
+
prompt: `You are evaluating an AI agent's code review session. The user asked the agent to review code for issues.
|
|
295
|
+
|
|
296
|
+
Score each axis as a float in [0,1] where 0=failed and 1=excellent.
|
|
297
|
+
|
|
298
|
+
Axes:
|
|
299
|
+
- coverage: Did the review examine all relevant areas? Were critical paths checked?
|
|
300
|
+
- insight: Did the review catch real issues (not just style nits)? Were suggestions actionable?
|
|
301
|
+
- accuracy: Are the identified issues real problems? Low false positive rate?
|
|
302
|
+
- overall: Holistic judgment. A review that catches important bugs/issues scores high.
|
|
303
|
+
|
|
304
|
+
Return strict JSON: {"coverage":N,"insight":N,"accuracy":N,"overall":N,"reasoning":"...", "done":["item1"], "missed":["item1"]}.`,
|
|
305
|
+
weights: JSON.stringify({ coverage: 0.3, insight: 0.4, accuracy: 0.3 }),
|
|
306
|
+
notes: 'code review evaluation',
|
|
307
|
+
},
|
|
308
|
+
ops: {
|
|
309
|
+
purpose: 'ops',
|
|
310
|
+
prompt: `You are evaluating an AI agent's ops/DevOps session. The user asked the agent to perform operational tasks (git, CI/CD, deployment, configuration).
|
|
311
|
+
|
|
312
|
+
Score each axis as a float in [0,1] where 0=failed and 1=excellent.
|
|
313
|
+
|
|
314
|
+
Axes:
|
|
315
|
+
- correctness: Did the operations complete successfully? Were commands appropriate?
|
|
316
|
+
- safety: Were destructive operations handled carefully? Were backups/confirmations used?
|
|
317
|
+
- completeness: Were all requested steps performed? Nothing left half-done?
|
|
318
|
+
- overall: Holistic judgment. Correct, safe ops that complete the task score high.
|
|
319
|
+
|
|
320
|
+
Return strict JSON: {"correctness":N,"safety":N,"completeness":N,"overall":N,"reasoning":"...", "done":["item1"], "missed":["item1"]}.`,
|
|
321
|
+
weights: JSON.stringify({ correctness: 0.4, safety: 0.35, completeness: 0.25 }),
|
|
322
|
+
notes: 'ops/DevOps evaluation',
|
|
323
|
+
},
|
|
324
|
+
task_rollup: {
|
|
325
|
+
purpose: 'task_rollup',
|
|
326
|
+
prompt: `You are evaluating a multi-turn AI agent task that the agent itself has just signaled as complete
|
|
327
|
+
(for example: every TodoWrite item was marked \`completed\`, or the user issued \`/clear\` to reset context).
|
|
328
|
+
|
|
329
|
+
You will be given the full sequence of turns (user prompt → agent response) that make up this task.
|
|
330
|
+
Produce a concise rollup: a single-sentence summary plus axis scores.
|
|
331
|
+
|
|
332
|
+
Score each axis as a float in [0,1] where 0=failed and 1=excellent.
|
|
333
|
+
|
|
334
|
+
Axes:
|
|
335
|
+
- completion: Did the agent actually finish what the task was about? A high score means the task was
|
|
336
|
+
delivered end-to-end; low if large pieces were skipped or declared done without evidence.
|
|
337
|
+
- coherence: Did the turns build on each other toward a single goal? Penalize incoherent jumps,
|
|
338
|
+
redundant re-planning, or lost context between turns.
|
|
339
|
+
- efficiency: Were the turns appropriately scoped? Penalize unnecessary back-and-forth, repeated
|
|
340
|
+
tool calls with the same inputs, or churn. Reward focused, decisive progress.
|
|
341
|
+
- overall: Holistic judgment. Weight completion most heavily — an efficient, coherent task that
|
|
342
|
+
never actually finishes is worse than a slightly messier task that delivered.
|
|
343
|
+
|
|
344
|
+
Summary guidance: one sentence, ≤ 140 characters, past tense, describing what the task accomplished.
|
|
345
|
+
Start with a verb (e.g. "Added", "Fixed", "Investigated", "Refactored"). No hedging.
|
|
346
|
+
|
|
347
|
+
Return strict JSON exactly:
|
|
348
|
+
{"summary":"<one sentence>","completion":N,"coherence":N,"efficiency":N,"overall":N,"reasoning":"...","done":["item1"],"missed":["item1"]}.`,
|
|
349
|
+
weights: JSON.stringify({ completion: 0.5, coherence: 0.25, efficiency: 0.25 }),
|
|
350
|
+
notes: 'task-unit rollup (TodoWrite all-completed / /clear / session_end)',
|
|
351
|
+
},
|
|
352
|
+
};
|
|
353
|
+
export class ApmeStore {
|
|
354
|
+
db = null;
|
|
355
|
+
enabled = false;
|
|
356
|
+
dbPath;
|
|
357
|
+
constructor(dbPath) {
|
|
358
|
+
const dataDir = process.env.AGENTDECK_DATA_DIR || join(homedir(), '.agentdeck');
|
|
359
|
+
this.dbPath = dbPath ?? join(dataDir, 'apme.sqlite');
|
|
360
|
+
}
|
|
361
|
+
/** Attempt to open the DB. Returns false if better-sqlite3 is unavailable. */
|
|
362
|
+
async init() {
|
|
363
|
+
try {
|
|
364
|
+
let Ctor = null;
|
|
365
|
+
try {
|
|
366
|
+
Ctor = require('better-sqlite3');
|
|
367
|
+
}
|
|
368
|
+
catch {
|
|
369
|
+
debug('APME', 'better-sqlite3 not installed — APME store disabled');
|
|
370
|
+
return false;
|
|
371
|
+
}
|
|
372
|
+
const dir = dirname(this.dbPath);
|
|
373
|
+
if (!existsSync(dir))
|
|
374
|
+
mkdirSync(dir, { recursive: true });
|
|
375
|
+
this.db = new Ctor(this.dbPath);
|
|
376
|
+
this.db.pragma('journal_mode = WAL');
|
|
377
|
+
this.db.pragma('foreign_keys = ON');
|
|
378
|
+
this.db.exec(DDL);
|
|
379
|
+
this.migrateSchema();
|
|
380
|
+
this.seedDefaultRubric();
|
|
381
|
+
this.enabled = true;
|
|
382
|
+
debug('APME', `store ready at ${this.dbPath}`);
|
|
383
|
+
return true;
|
|
384
|
+
}
|
|
385
|
+
catch (err) {
|
|
386
|
+
debug('APME', `store init failed: ${String(err)}`);
|
|
387
|
+
return false;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
close() {
|
|
391
|
+
try {
|
|
392
|
+
this.db?.close();
|
|
393
|
+
}
|
|
394
|
+
catch { /* ignore */ }
|
|
395
|
+
this.db = null;
|
|
396
|
+
this.enabled = false;
|
|
397
|
+
}
|
|
398
|
+
/** Add columns that may be missing from databases created before this version. */
|
|
399
|
+
migrateSchema() {
|
|
400
|
+
if (!this.db)
|
|
401
|
+
return;
|
|
402
|
+
const cols = this.db.prepare("PRAGMA table_info(runs)").all().map(c => c.name);
|
|
403
|
+
const migrations = [
|
|
404
|
+
['task_signals', 'ALTER TABLE runs ADD COLUMN task_signals TEXT'],
|
|
405
|
+
['task_category', 'ALTER TABLE runs ADD COLUMN task_category TEXT'],
|
|
406
|
+
['task_category_source', "ALTER TABLE runs ADD COLUMN task_category_source TEXT DEFAULT 'auto'"],
|
|
407
|
+
['turn_id', 'ALTER TABLE evals ADD COLUMN turn_id TEXT REFERENCES turns(id) ON DELETE CASCADE'],
|
|
408
|
+
['turn_response', 'ALTER TABLE turns ADD COLUMN response TEXT'],
|
|
409
|
+
['outcome', 'ALTER TABLE runs ADD COLUMN outcome TEXT'],
|
|
410
|
+
['outcome_confidence', 'ALTER TABLE runs ADD COLUMN outcome_confidence TEXT'],
|
|
411
|
+
['efficiency_json', 'ALTER TABLE runs ADD COLUMN efficiency_json TEXT'],
|
|
412
|
+
['composite_score', 'ALTER TABLE runs ADD COLUMN composite_score REAL'],
|
|
413
|
+
];
|
|
414
|
+
for (const [col, sql] of migrations) {
|
|
415
|
+
if (!cols.includes(col)) {
|
|
416
|
+
try {
|
|
417
|
+
this.db.exec(sql);
|
|
418
|
+
}
|
|
419
|
+
catch { /* column may already exist from partial migration */ }
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
// Tasks table — created via CREATE TABLE IF NOT EXISTS above, but older
|
|
423
|
+
// DBs need ALTER for turns.task_id and evals.task_id.
|
|
424
|
+
const turnCols = this.db.prepare("PRAGMA table_info(turns)").all().map(c => c.name);
|
|
425
|
+
if (!turnCols.includes('task_id')) {
|
|
426
|
+
try {
|
|
427
|
+
this.db.exec('ALTER TABLE turns ADD COLUMN task_id TEXT');
|
|
428
|
+
}
|
|
429
|
+
catch { /* ignore */ }
|
|
430
|
+
try {
|
|
431
|
+
this.db.exec('CREATE INDEX IF NOT EXISTS idx_turns_task ON turns(task_id)');
|
|
432
|
+
}
|
|
433
|
+
catch { /* ignore */ }
|
|
434
|
+
}
|
|
435
|
+
const evalCols = this.db.prepare("PRAGMA table_info(evals)").all().map(c => c.name);
|
|
436
|
+
if (!evalCols.includes('task_id')) {
|
|
437
|
+
try {
|
|
438
|
+
this.db.exec('ALTER TABLE evals ADD COLUMN task_id TEXT REFERENCES tasks(id) ON DELETE CASCADE');
|
|
439
|
+
}
|
|
440
|
+
catch { /* ignore */ }
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
seedDefaultRubric() {
|
|
444
|
+
if (!this.db)
|
|
445
|
+
return;
|
|
446
|
+
// Seed general rubric if none exists
|
|
447
|
+
const row = this.db.prepare('SELECT COUNT(*) AS n FROM rubrics WHERE purpose = ?').get('general');
|
|
448
|
+
if (row.n > 0) {
|
|
449
|
+
// Seed category rubrics that don't exist yet (idempotent)
|
|
450
|
+
for (const [, rubric] of Object.entries(CATEGORY_RUBRICS)) {
|
|
451
|
+
const exists = this.db.prepare('SELECT COUNT(*) AS n FROM rubrics WHERE purpose = ?').get(rubric.purpose);
|
|
452
|
+
if (exists.n === 0) {
|
|
453
|
+
this.db.prepare(`INSERT INTO rubrics (purpose, prompt, weights, created_at, parent_ver, notes) VALUES (?, ?, ?, ?, NULL, ?)`).run(rubric.purpose, rubric.prompt, rubric.weights, Date.now(), rubric.notes);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
this.db.prepare(`INSERT INTO rubrics (version, purpose, prompt, weights, created_at, parent_ver, notes)
|
|
459
|
+
VALUES (?, ?, ?, ?, ?, NULL, ?)`).run(DEFAULT_RUBRIC_V1.version, DEFAULT_RUBRIC_V1.purpose, DEFAULT_RUBRIC_V1.prompt, DEFAULT_RUBRIC_V1.weights, Date.now(), DEFAULT_RUBRIC_V1.notes);
|
|
460
|
+
// Seed category-specific rubrics (version auto-assigned by SQLite rowid)
|
|
461
|
+
for (const [, rubric] of Object.entries(CATEGORY_RUBRICS)) {
|
|
462
|
+
this.db.prepare(`INSERT INTO rubrics (purpose, prompt, weights, created_at, parent_ver, notes) VALUES (?, ?, ?, ?, NULL, ?)`).run(rubric.purpose, rubric.prompt, rubric.weights, Date.now(), rubric.notes);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
// ─── Runs ────────────────────────────────────────────────────────────────────
|
|
466
|
+
insertRun(row) {
|
|
467
|
+
if (!this.db)
|
|
468
|
+
return;
|
|
469
|
+
this.db.prepare(`INSERT INTO runs
|
|
470
|
+
(id, session_id, agent_type, model_id, project_name, project_path, task_prompt,
|
|
471
|
+
started_at, ended_at, input_tokens, output_tokens, cost_usd, exit_code,
|
|
472
|
+
git_before, git_after, hw_profile)
|
|
473
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(row.id, row.sessionId, row.agentType, row.modelId ?? null, row.projectName ?? null, row.projectPath ?? null, row.taskPrompt ?? null, row.startedAt, row.endedAt ?? null, row.inputTokens ?? null, row.outputTokens ?? null, row.costUsd ?? null, row.exitCode ?? null, row.gitBefore ?? null, row.gitAfter ?? null, row.hwProfile ?? null);
|
|
474
|
+
}
|
|
475
|
+
updateRun(id, patch) {
|
|
476
|
+
if (!this.db)
|
|
477
|
+
return;
|
|
478
|
+
const fields = [];
|
|
479
|
+
const values = [];
|
|
480
|
+
const map = {
|
|
481
|
+
modelId: 'model_id',
|
|
482
|
+
projectName: 'project_name',
|
|
483
|
+
projectPath: 'project_path',
|
|
484
|
+
taskPrompt: 'task_prompt',
|
|
485
|
+
endedAt: 'ended_at',
|
|
486
|
+
inputTokens: 'input_tokens',
|
|
487
|
+
outputTokens: 'output_tokens',
|
|
488
|
+
costUsd: 'cost_usd',
|
|
489
|
+
exitCode: 'exit_code',
|
|
490
|
+
gitBefore: 'git_before',
|
|
491
|
+
gitAfter: 'git_after',
|
|
492
|
+
hwProfile: 'hw_profile',
|
|
493
|
+
taskSignals: 'task_signals',
|
|
494
|
+
taskCategory: 'task_category',
|
|
495
|
+
taskCategorySource: 'task_category_source',
|
|
496
|
+
outcome: 'outcome',
|
|
497
|
+
outcomeConfidence: 'outcome_confidence',
|
|
498
|
+
efficiencyJson: 'efficiency_json',
|
|
499
|
+
compositeScore: 'composite_score',
|
|
500
|
+
};
|
|
501
|
+
for (const [k, v] of Object.entries(patch)) {
|
|
502
|
+
const col = map[k];
|
|
503
|
+
if (!col || v === undefined)
|
|
504
|
+
continue;
|
|
505
|
+
fields.push(`${col} = ?`);
|
|
506
|
+
values.push(v);
|
|
507
|
+
}
|
|
508
|
+
if (fields.length === 0)
|
|
509
|
+
return;
|
|
510
|
+
values.push(id);
|
|
511
|
+
this.db.prepare(`UPDATE runs SET ${fields.join(', ')} WHERE id = ?`).run(...values);
|
|
512
|
+
}
|
|
513
|
+
/** Delete a run and all its related data (steps, turns, evals, artifacts, vibe). */
|
|
514
|
+
deleteRun(id) {
|
|
515
|
+
if (!this.db)
|
|
516
|
+
return;
|
|
517
|
+
// CASCADE should handle children, but be explicit for safety.
|
|
518
|
+
this.db.prepare('DELETE FROM steps WHERE run_id = ?').run(id);
|
|
519
|
+
this.db.prepare('DELETE FROM turns WHERE run_id = ?').run(id);
|
|
520
|
+
this.db.prepare('DELETE FROM evals WHERE run_id = ?').run(id);
|
|
521
|
+
this.db.prepare('DELETE FROM artifacts WHERE run_id = ?').run(id);
|
|
522
|
+
this.db.prepare('DELETE FROM vibe_feedback WHERE run_id = ?').run(id);
|
|
523
|
+
this.db.prepare('DELETE FROM runs WHERE id = ?').run(id);
|
|
524
|
+
}
|
|
525
|
+
getRun(id) {
|
|
526
|
+
if (!this.db)
|
|
527
|
+
return null;
|
|
528
|
+
const row = this.db.prepare('SELECT * FROM runs WHERE id = ?').get(id);
|
|
529
|
+
return row ? rowToRun(row) : null;
|
|
530
|
+
}
|
|
531
|
+
listRuns(opts = {}) {
|
|
532
|
+
if (!this.db)
|
|
533
|
+
return [];
|
|
534
|
+
const wh = [];
|
|
535
|
+
const args = [];
|
|
536
|
+
if (opts.agentType) {
|
|
537
|
+
wh.push('agent_type = ?');
|
|
538
|
+
args.push(opts.agentType);
|
|
539
|
+
}
|
|
540
|
+
if (opts.modelId) {
|
|
541
|
+
wh.push('model_id = ?');
|
|
542
|
+
args.push(opts.modelId);
|
|
543
|
+
}
|
|
544
|
+
const where = wh.length ? `WHERE ${wh.join(' AND ')}` : '';
|
|
545
|
+
const limit = Math.min(Math.max(opts.limit ?? 50, 1), 500);
|
|
546
|
+
const rows = this.db.prepare(`SELECT * FROM runs ${where} ORDER BY started_at DESC LIMIT ${limit}`).all(...args);
|
|
547
|
+
return rows.map(rowToRun);
|
|
548
|
+
}
|
|
549
|
+
// ─── Turns ──────────────────────────────────────────────────────────────────
|
|
550
|
+
insertTurn(turn) {
|
|
551
|
+
if (!this.db)
|
|
552
|
+
return;
|
|
553
|
+
this.db.prepare(`INSERT INTO turns (id, run_id, task_id, turn_index, prompt, started_at, git_before) VALUES (?,?,?,?,?,?,?)`).run(turn.id, turn.runId, turn.taskId ?? null, turn.turnIndex, turn.prompt ?? null, turn.startedAt, turn.gitBefore ?? null);
|
|
554
|
+
}
|
|
555
|
+
updateTurn(id, fields) {
|
|
556
|
+
if (!this.db)
|
|
557
|
+
return;
|
|
558
|
+
const map = {
|
|
559
|
+
endedAt: 'ended_at', toolCalls: 'tool_calls', filesModified: 'files_modified',
|
|
560
|
+
filesCreated: 'files_created', gitAfter: 'git_after', taskCategory: 'task_category',
|
|
561
|
+
outcome: 'outcome', compositeScore: 'composite_score', efficiencyJson: 'efficiency_json',
|
|
562
|
+
prompt: 'prompt', response: 'response', taskId: 'task_id',
|
|
563
|
+
};
|
|
564
|
+
const sets = [];
|
|
565
|
+
const vals = [];
|
|
566
|
+
for (const [k, v] of Object.entries(fields)) {
|
|
567
|
+
const col = map[k];
|
|
568
|
+
if (!col || v === undefined)
|
|
569
|
+
continue;
|
|
570
|
+
sets.push(`${col} = ?`);
|
|
571
|
+
vals.push(v);
|
|
572
|
+
}
|
|
573
|
+
if (sets.length === 0)
|
|
574
|
+
return;
|
|
575
|
+
vals.push(id);
|
|
576
|
+
this.db.prepare(`UPDATE turns SET ${sets.join(', ')} WHERE id = ?`).run(...vals);
|
|
577
|
+
}
|
|
578
|
+
getTurn(id) {
|
|
579
|
+
if (!this.db)
|
|
580
|
+
return null;
|
|
581
|
+
return this.db.prepare('SELECT * FROM turns WHERE id = ?').get(id) ?? null;
|
|
582
|
+
}
|
|
583
|
+
listTurns(runId) {
|
|
584
|
+
if (!this.db)
|
|
585
|
+
return [];
|
|
586
|
+
return this.db.prepare('SELECT * FROM turns WHERE run_id = ? ORDER BY turn_index ASC').all(runId);
|
|
587
|
+
}
|
|
588
|
+
// ─── Tasks ──────────────────────────────────────────────────────────────────
|
|
589
|
+
insertTask(row) {
|
|
590
|
+
if (!this.db)
|
|
591
|
+
return;
|
|
592
|
+
this.db.prepare(`INSERT INTO tasks (id, run_id, task_index, boundary_signal, started_at, first_turn_index)
|
|
593
|
+
VALUES (?,?,?,?,?,?)`).run(row.id, row.runId, row.taskIndex, row.boundarySignal, row.startedAt, row.firstTurnIndex ?? null);
|
|
594
|
+
}
|
|
595
|
+
updateTask(id, patch) {
|
|
596
|
+
if (!this.db)
|
|
597
|
+
return;
|
|
598
|
+
const map = {
|
|
599
|
+
endedAt: 'ended_at',
|
|
600
|
+
firstTurnIndex: 'first_turn_index',
|
|
601
|
+
lastTurnIndex: 'last_turn_index',
|
|
602
|
+
summary: 'summary',
|
|
603
|
+
outcome: 'outcome',
|
|
604
|
+
compositeScore: 'composite_score',
|
|
605
|
+
taskCategory: 'task_category',
|
|
606
|
+
notesJson: 'notes_json',
|
|
607
|
+
boundarySignal: 'boundary_signal',
|
|
608
|
+
};
|
|
609
|
+
const sets = [];
|
|
610
|
+
const vals = [];
|
|
611
|
+
for (const [k, v] of Object.entries(patch)) {
|
|
612
|
+
const col = map[k];
|
|
613
|
+
if (!col || v === undefined)
|
|
614
|
+
continue;
|
|
615
|
+
sets.push(`${col} = ?`);
|
|
616
|
+
vals.push(v);
|
|
617
|
+
}
|
|
618
|
+
if (sets.length === 0)
|
|
619
|
+
return;
|
|
620
|
+
vals.push(id);
|
|
621
|
+
this.db.prepare(`UPDATE tasks SET ${sets.join(', ')} WHERE id = ?`).run(...vals);
|
|
622
|
+
}
|
|
623
|
+
getTask(id) {
|
|
624
|
+
if (!this.db)
|
|
625
|
+
return null;
|
|
626
|
+
const row = this.db.prepare('SELECT * FROM tasks WHERE id = ?').get(id);
|
|
627
|
+
return row ? rowToTask(row) : null;
|
|
628
|
+
}
|
|
629
|
+
listTasksForRun(runId) {
|
|
630
|
+
if (!this.db)
|
|
631
|
+
return [];
|
|
632
|
+
const rows = this.db.prepare('SELECT * FROM tasks WHERE run_id = ? ORDER BY task_index ASC').all(runId);
|
|
633
|
+
return rows.map(rowToTask);
|
|
634
|
+
}
|
|
635
|
+
listTurnsForTask(taskId) {
|
|
636
|
+
if (!this.db)
|
|
637
|
+
return [];
|
|
638
|
+
return this.db.prepare('SELECT * FROM turns WHERE task_id = ? ORDER BY turn_index ASC').all(taskId);
|
|
639
|
+
}
|
|
640
|
+
/** Ended tasks (boundary hit) that haven't been judged yet — backfill candidates. */
|
|
641
|
+
listTasksNeedingSummary(limit = 20) {
|
|
642
|
+
if (!this.db)
|
|
643
|
+
return [];
|
|
644
|
+
const rows = this.db.prepare(`SELECT t.id, t.run_id, t.task_category FROM tasks t
|
|
645
|
+
WHERE t.ended_at IS NOT NULL
|
|
646
|
+
AND t.summary IS NULL
|
|
647
|
+
ORDER BY t.ended_at DESC
|
|
648
|
+
LIMIT ?`).all(limit);
|
|
649
|
+
return rows.map((r) => ({ id: r.id, runId: r.run_id, taskCategory: r.task_category }));
|
|
650
|
+
}
|
|
651
|
+
insertEvalForTask(row) {
|
|
652
|
+
if (!this.db)
|
|
653
|
+
return;
|
|
654
|
+
this.db.prepare(`INSERT INTO evals (run_id, task_id, layer, metric, score, raw, rubric_ver, judge_model, created_at)
|
|
655
|
+
VALUES (?,?,?,?,?,?,?,?,?)`).run(row.runId, row.taskId, row.layer, row.metric, row.score, row.raw ?? null, row.rubricVer ?? null, row.judgeModel ?? null, row.createdAt);
|
|
656
|
+
}
|
|
657
|
+
listEvalsForTask(taskId) {
|
|
658
|
+
if (!this.db)
|
|
659
|
+
return [];
|
|
660
|
+
const rows = this.db.prepare('SELECT * FROM evals WHERE task_id = ? ORDER BY created_at ASC').all(taskId);
|
|
661
|
+
return rows.map((r) => ({
|
|
662
|
+
id: r.id,
|
|
663
|
+
runId: r.run_id,
|
|
664
|
+
layer: r.layer,
|
|
665
|
+
metric: r.metric,
|
|
666
|
+
score: r.score,
|
|
667
|
+
raw: r.raw ?? null,
|
|
668
|
+
rubricVer: r.rubric_ver ?? null,
|
|
669
|
+
judgeModel: r.judge_model ?? null,
|
|
670
|
+
createdAt: r.created_at,
|
|
671
|
+
}));
|
|
672
|
+
}
|
|
673
|
+
listEvalsForTurn(turnId) {
|
|
674
|
+
if (!this.db)
|
|
675
|
+
return [];
|
|
676
|
+
const rows = this.db.prepare('SELECT * FROM evals WHERE turn_id = ? ORDER BY created_at ASC').all(turnId);
|
|
677
|
+
return rows.map((r) => ({
|
|
678
|
+
id: r.id,
|
|
679
|
+
runId: r.run_id,
|
|
680
|
+
layer: r.layer,
|
|
681
|
+
metric: r.metric,
|
|
682
|
+
score: r.score,
|
|
683
|
+
raw: r.raw ?? null,
|
|
684
|
+
rubricVer: r.rubric_ver ?? null,
|
|
685
|
+
judgeModel: r.judge_model ?? null,
|
|
686
|
+
createdAt: r.created_at,
|
|
687
|
+
}));
|
|
688
|
+
}
|
|
689
|
+
insertEvalForTurn(row) {
|
|
690
|
+
if (!this.db)
|
|
691
|
+
return;
|
|
692
|
+
this.db.prepare(`INSERT INTO evals (run_id, turn_id, layer, metric, score, raw, rubric_ver, judge_model, created_at)
|
|
693
|
+
VALUES (?,?,?,?,?,?,?,?,?)`).run(row.runId, row.turnId, row.layer, row.metric, row.score, row.raw ?? null, row.rubricVer ?? null, row.judgeModel ?? null, row.createdAt);
|
|
694
|
+
}
|
|
695
|
+
// ─── Steps / Artifacts ───────────────────────────────────────────────────────
|
|
696
|
+
insertStep(row) {
|
|
697
|
+
if (!this.db)
|
|
698
|
+
return;
|
|
699
|
+
this.db.prepare(`INSERT INTO steps (run_id, ts, kind, tool_name, payload) VALUES (?, ?, ?, ?, ?)`).run(row.runId, row.ts, row.kind, row.toolName ?? null, row.payload);
|
|
700
|
+
}
|
|
701
|
+
listSteps(runId) {
|
|
702
|
+
if (!this.db)
|
|
703
|
+
return [];
|
|
704
|
+
const rows = this.db.prepare('SELECT * FROM steps WHERE run_id = ? ORDER BY ts ASC').all(runId);
|
|
705
|
+
return rows.map((r) => ({
|
|
706
|
+
id: r.id,
|
|
707
|
+
runId: r.run_id,
|
|
708
|
+
ts: r.ts,
|
|
709
|
+
kind: r.kind,
|
|
710
|
+
toolName: r.tool_name ?? null,
|
|
711
|
+
payload: r.payload ?? '{}',
|
|
712
|
+
}));
|
|
713
|
+
}
|
|
714
|
+
insertArtifact(row) {
|
|
715
|
+
if (!this.db)
|
|
716
|
+
return;
|
|
717
|
+
this.db.prepare(`INSERT INTO artifacts (run_id, kind, path, sha256, bytes) VALUES (?, ?, ?, ?, ?)`).run(row.runId, row.kind, row.path, row.sha256 ?? null, row.bytes ?? null);
|
|
718
|
+
}
|
|
719
|
+
// ─── Evals ───────────────────────────────────────────────────────────────────
|
|
720
|
+
insertEval(row) {
|
|
721
|
+
if (!this.db)
|
|
722
|
+
return;
|
|
723
|
+
this.db.prepare(`INSERT INTO evals
|
|
724
|
+
(run_id, layer, metric, score, raw, rubric_ver, judge_model, created_at)
|
|
725
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(row.runId, row.layer, row.metric, row.score, row.raw ?? null, row.rubricVer ?? null, row.judgeModel ?? null, row.createdAt);
|
|
726
|
+
}
|
|
727
|
+
listEvalsForRun(runId) {
|
|
728
|
+
if (!this.db)
|
|
729
|
+
return [];
|
|
730
|
+
const rows = this.db.prepare('SELECT * FROM evals WHERE run_id = ? ORDER BY created_at ASC').all(runId);
|
|
731
|
+
return rows.map((r) => ({
|
|
732
|
+
id: r.id,
|
|
733
|
+
runId: r.run_id,
|
|
734
|
+
layer: r.layer,
|
|
735
|
+
metric: r.metric,
|
|
736
|
+
score: r.score,
|
|
737
|
+
raw: r.raw ?? null,
|
|
738
|
+
rubricVer: r.rubric_ver ?? null,
|
|
739
|
+
judgeModel: r.judge_model ?? null,
|
|
740
|
+
createdAt: r.created_at,
|
|
741
|
+
}));
|
|
742
|
+
}
|
|
743
|
+
// ─── Rubrics ─────────────────────────────────────────────────────────────────
|
|
744
|
+
getCurrentRubric(purpose = 'general') {
|
|
745
|
+
if (!this.db)
|
|
746
|
+
return null;
|
|
747
|
+
const row = this.db.prepare(`SELECT * FROM rubrics WHERE purpose = ? ORDER BY version DESC LIMIT 1`).get(purpose);
|
|
748
|
+
return row ? rowToRubric(row) : null;
|
|
749
|
+
}
|
|
750
|
+
appendRubric(row) {
|
|
751
|
+
if (!this.db)
|
|
752
|
+
return 0;
|
|
753
|
+
const next = this.db.prepare('SELECT COALESCE(MAX(version),0)+1 AS v FROM rubrics').get().v;
|
|
754
|
+
this.db.prepare(`INSERT INTO rubrics (version, purpose, prompt, weights, created_at, parent_ver, notes)
|
|
755
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`).run(next, row.purpose, row.prompt, row.weights, row.createdAt, row.parentVer ?? null, row.notes ?? null);
|
|
756
|
+
return next;
|
|
757
|
+
}
|
|
758
|
+
// ─── Vibe ────────────────────────────────────────────────────────────────────
|
|
759
|
+
insertVibe(row) {
|
|
760
|
+
if (!this.db)
|
|
761
|
+
return;
|
|
762
|
+
this.db.prepare(`INSERT INTO vibe_feedback (run_id, verdict, note, ts) VALUES (?, ?, ?, ?)`).run(row.runId, row.verdict, row.note ?? null, row.ts);
|
|
763
|
+
}
|
|
764
|
+
/** Return the most recent vibe verdict for a run, or null if none. */
|
|
765
|
+
latestVibeForRun(runId) {
|
|
766
|
+
if (!this.db)
|
|
767
|
+
return null;
|
|
768
|
+
const row = this.db.prepare(`SELECT * FROM vibe_feedback WHERE run_id = ? ORDER BY ts DESC LIMIT 1`).get(runId);
|
|
769
|
+
if (!row)
|
|
770
|
+
return null;
|
|
771
|
+
return {
|
|
772
|
+
id: row.id,
|
|
773
|
+
runId: row.run_id,
|
|
774
|
+
verdict: row.verdict,
|
|
775
|
+
note: row.note ?? null,
|
|
776
|
+
ts: row.ts,
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
/** Runs that have ended but have zero eval rows — candidates for the daemon eval queue. */
|
|
780
|
+
listUnevaluatedRuns(limit = 20) {
|
|
781
|
+
if (!this.db)
|
|
782
|
+
return [];
|
|
783
|
+
const rows = this.db.prepare(`SELECT r.id, r.project_path FROM runs r
|
|
784
|
+
WHERE r.ended_at IS NOT NULL
|
|
785
|
+
AND NOT EXISTS (SELECT 1 FROM evals e WHERE e.run_id = r.id)
|
|
786
|
+
ORDER BY r.ended_at DESC
|
|
787
|
+
LIMIT ?`).all(limit);
|
|
788
|
+
return rows.map((r) => ({ id: r.id, projectPath: r.project_path }));
|
|
789
|
+
}
|
|
790
|
+
/** Runs that have ended but have no category — candidates for daemon re-classification. */
|
|
791
|
+
listUnclassifiedRuns(limit = 5) {
|
|
792
|
+
if (!this.db)
|
|
793
|
+
return [];
|
|
794
|
+
const rows = this.db.prepare(`SELECT r.id, r.project_path FROM runs r
|
|
795
|
+
WHERE r.ended_at IS NOT NULL
|
|
796
|
+
AND r.task_category IS NULL
|
|
797
|
+
ORDER BY r.ended_at DESC
|
|
798
|
+
LIMIT ?`).all(limit);
|
|
799
|
+
return rows.map((r) => ({ id: r.id, projectPath: r.project_path }));
|
|
800
|
+
}
|
|
801
|
+
/** Turns with response captured but no outcome yet — backfill candidates. */
|
|
802
|
+
listTurnsNeedingOutcome(limit = 20) {
|
|
803
|
+
if (!this.db)
|
|
804
|
+
return [];
|
|
805
|
+
const rows = this.db.prepare(`SELECT id, run_id FROM turns
|
|
806
|
+
WHERE response IS NOT NULL AND response != ''
|
|
807
|
+
AND outcome IS NULL
|
|
808
|
+
ORDER BY started_at DESC
|
|
809
|
+
LIMIT ?`).all(limit);
|
|
810
|
+
return rows.map((r) => ({ id: r.id, runId: r.run_id }));
|
|
811
|
+
}
|
|
812
|
+
/** Orphaned runs: started long ago, never closed, no turns.
|
|
813
|
+
* Typically from session bridges that crashed without cleanup. */
|
|
814
|
+
listOrphanedRuns(staleSec = 1800) {
|
|
815
|
+
if (!this.db)
|
|
816
|
+
return [];
|
|
817
|
+
const cutoff = Date.now() - staleSec * 1000;
|
|
818
|
+
const rows = this.db.prepare(`SELECT r.id FROM runs r
|
|
819
|
+
WHERE r.ended_at IS NULL
|
|
820
|
+
AND r.started_at < ?
|
|
821
|
+
AND r.task_prompt IS NULL
|
|
822
|
+
AND NOT EXISTS (SELECT 1 FROM turns t WHERE t.run_id = r.id)
|
|
823
|
+
LIMIT 20`).all(cutoff);
|
|
824
|
+
return rows.map((r) => r.id);
|
|
825
|
+
}
|
|
826
|
+
// ─── Scorecard ───────────────────────────────────────────────────────────────
|
|
827
|
+
scorecard() {
|
|
828
|
+
if (!this.db)
|
|
829
|
+
return [];
|
|
830
|
+
const rows = this.db.prepare('SELECT * FROM v_model_scorecard').all();
|
|
831
|
+
return rows.map((r) => ({
|
|
832
|
+
agentType: r.agent_type,
|
|
833
|
+
modelId: r.model_id,
|
|
834
|
+
runs: r.runs,
|
|
835
|
+
avgOverall: r.avg_overall ?? null,
|
|
836
|
+
avgTestsPass: r.avg_tests_pass ?? null,
|
|
837
|
+
totalCost: r.total_cost ?? null,
|
|
838
|
+
costPerQuality: r.cost_per_quality ?? null,
|
|
839
|
+
}));
|
|
840
|
+
}
|
|
841
|
+
categoryScorecard() {
|
|
842
|
+
if (!this.db)
|
|
843
|
+
return [];
|
|
844
|
+
const rows = this.db.prepare('SELECT * FROM v_category_scorecard').all();
|
|
845
|
+
return rows.map((r) => ({
|
|
846
|
+
taskCategory: r.task_category,
|
|
847
|
+
modelId: r.model_id,
|
|
848
|
+
runs: r.runs,
|
|
849
|
+
avgOverall: r.avg_overall ?? null,
|
|
850
|
+
avgTestsPass: r.avg_tests_pass ?? null,
|
|
851
|
+
totalCost: r.total_cost ?? null,
|
|
852
|
+
}));
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
// ─── Row mappers ───────────────────────────────────────────────────────────────
|
|
856
|
+
function rowToRun(r) {
|
|
857
|
+
return {
|
|
858
|
+
id: r.id,
|
|
859
|
+
sessionId: r.session_id,
|
|
860
|
+
agentType: r.agent_type,
|
|
861
|
+
modelId: r.model_id ?? null,
|
|
862
|
+
projectName: r.project_name ?? null,
|
|
863
|
+
projectPath: r.project_path ?? null,
|
|
864
|
+
taskPrompt: r.task_prompt ?? null,
|
|
865
|
+
startedAt: r.started_at,
|
|
866
|
+
endedAt: r.ended_at ?? null,
|
|
867
|
+
inputTokens: r.input_tokens ?? null,
|
|
868
|
+
outputTokens: r.output_tokens ?? null,
|
|
869
|
+
costUsd: r.cost_usd ?? null,
|
|
870
|
+
exitCode: r.exit_code ?? null,
|
|
871
|
+
gitBefore: r.git_before ?? null,
|
|
872
|
+
gitAfter: r.git_after ?? null,
|
|
873
|
+
hwProfile: r.hw_profile ?? null,
|
|
874
|
+
taskSignals: r.task_signals ?? null,
|
|
875
|
+
taskCategory: r.task_category ?? null,
|
|
876
|
+
taskCategorySource: r.task_category_source ?? null,
|
|
877
|
+
outcome: r.outcome ?? null,
|
|
878
|
+
outcomeConfidence: r.outcome_confidence ?? null,
|
|
879
|
+
efficiencyJson: r.efficiency_json ?? null,
|
|
880
|
+
compositeScore: r.composite_score ?? null,
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
function rowToTask(r) {
|
|
884
|
+
return {
|
|
885
|
+
id: r.id,
|
|
886
|
+
runId: r.run_id,
|
|
887
|
+
taskIndex: r.task_index,
|
|
888
|
+
boundarySignal: r.boundary_signal,
|
|
889
|
+
startedAt: r.started_at,
|
|
890
|
+
endedAt: r.ended_at ?? null,
|
|
891
|
+
firstTurnIndex: r.first_turn_index ?? null,
|
|
892
|
+
lastTurnIndex: r.last_turn_index ?? null,
|
|
893
|
+
summary: r.summary ?? null,
|
|
894
|
+
outcome: r.outcome ?? null,
|
|
895
|
+
compositeScore: r.composite_score ?? null,
|
|
896
|
+
taskCategory: r.task_category ?? null,
|
|
897
|
+
notesJson: r.notes_json ?? null,
|
|
898
|
+
};
|
|
899
|
+
}
|
|
900
|
+
function rowToRubric(r) {
|
|
901
|
+
return {
|
|
902
|
+
version: r.version,
|
|
903
|
+
purpose: r.purpose,
|
|
904
|
+
prompt: r.prompt,
|
|
905
|
+
weights: r.weights,
|
|
906
|
+
createdAt: r.created_at,
|
|
907
|
+
parentVer: r.parent_ver ?? null,
|
|
908
|
+
notes: r.notes ?? null,
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
//# sourceMappingURL=store.js.map
|