@fml-inc/panopticon 0.1.0
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/.claude-plugin/plugin.json +10 -0
- package/LICENSE +5 -0
- package/README.md +363 -0
- package/bin/hook-handler +3 -0
- package/bin/mcp-server +3 -0
- package/bin/panopticon +3 -0
- package/bin/proxy +3 -0
- package/bin/server +3 -0
- package/dist/api/client.d.ts +67 -0
- package/dist/api/client.js +48 -0
- package/dist/api/client.js.map +1 -0
- package/dist/chunk-3BUJ7URA.js +387 -0
- package/dist/chunk-3BUJ7URA.js.map +1 -0
- package/dist/chunk-3TZAKV3M.js +158 -0
- package/dist/chunk-3TZAKV3M.js.map +1 -0
- package/dist/chunk-4SM2H22C.js +169 -0
- package/dist/chunk-4SM2H22C.js.map +1 -0
- package/dist/chunk-7Q3BJMLG.js +62 -0
- package/dist/chunk-7Q3BJMLG.js.map +1 -0
- package/dist/chunk-BVOE7A2Z.js +412 -0
- package/dist/chunk-BVOE7A2Z.js.map +1 -0
- package/dist/chunk-CF4GPWLI.js +170 -0
- package/dist/chunk-CF4GPWLI.js.map +1 -0
- package/dist/chunk-DZ5HJFB4.js +467 -0
- package/dist/chunk-DZ5HJFB4.js.map +1 -0
- package/dist/chunk-HQCY722C.js +428 -0
- package/dist/chunk-HQCY722C.js.map +1 -0
- package/dist/chunk-HRCEIYKU.js +134 -0
- package/dist/chunk-HRCEIYKU.js.map +1 -0
- package/dist/chunk-K7YUPLES.js +76 -0
- package/dist/chunk-K7YUPLES.js.map +1 -0
- package/dist/chunk-L7G27XWF.js +130 -0
- package/dist/chunk-L7G27XWF.js.map +1 -0
- package/dist/chunk-LWXF7YRG.js +626 -0
- package/dist/chunk-LWXF7YRG.js.map +1 -0
- package/dist/chunk-NXH7AONS.js +1120 -0
- package/dist/chunk-NXH7AONS.js.map +1 -0
- package/dist/chunk-QK5442ZP.js +55 -0
- package/dist/chunk-QK5442ZP.js.map +1 -0
- package/dist/chunk-QVK6VGCV.js +1703 -0
- package/dist/chunk-QVK6VGCV.js.map +1 -0
- package/dist/chunk-RX2RXHBH.js +1699 -0
- package/dist/chunk-RX2RXHBH.js.map +1 -0
- package/dist/chunk-SEXU2WYG.js +788 -0
- package/dist/chunk-SEXU2WYG.js.map +1 -0
- package/dist/chunk-SUGSQ4YI.js +264 -0
- package/dist/chunk-SUGSQ4YI.js.map +1 -0
- package/dist/chunk-TGXFVAID.js +138 -0
- package/dist/chunk-TGXFVAID.js.map +1 -0
- package/dist/chunk-WLBNFVIG.js +447 -0
- package/dist/chunk-WLBNFVIG.js.map +1 -0
- package/dist/chunk-XLTCUH5A.js +1072 -0
- package/dist/chunk-XLTCUH5A.js.map +1 -0
- package/dist/chunk-YVRWVDIA.js +146 -0
- package/dist/chunk-YVRWVDIA.js.map +1 -0
- package/dist/chunk-ZEC4LRKS.js +176 -0
- package/dist/chunk-ZEC4LRKS.js.map +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +1084 -0
- package/dist/cli.js.map +1 -0
- package/dist/config-NwoZC-GM.d.ts +20 -0
- package/dist/db.d.ts +46 -0
- package/dist/db.js +15 -0
- package/dist/db.js.map +1 -0
- package/dist/doctor.d.ts +37 -0
- package/dist/doctor.js +14 -0
- package/dist/doctor.js.map +1 -0
- package/dist/hooks/handler.d.ts +23 -0
- package/dist/hooks/handler.js +295 -0
- package/dist/hooks/handler.js.map +1 -0
- package/dist/index.d.ts +57 -0
- package/dist/index.js +101 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/server.d.ts +1 -0
- package/dist/mcp/server.js +243 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/otlp/server.d.ts +7 -0
- package/dist/otlp/server.js +17 -0
- package/dist/otlp/server.js.map +1 -0
- package/dist/permissions.d.ts +33 -0
- package/dist/permissions.js +14 -0
- package/dist/permissions.js.map +1 -0
- package/dist/pricing.d.ts +29 -0
- package/dist/pricing.js +13 -0
- package/dist/pricing.js.map +1 -0
- package/dist/proxy/server.d.ts +10 -0
- package/dist/proxy/server.js +20 -0
- package/dist/proxy/server.js.map +1 -0
- package/dist/prune.d.ts +18 -0
- package/dist/prune.js +13 -0
- package/dist/prune.js.map +1 -0
- package/dist/query.d.ts +56 -0
- package/dist/query.js +27 -0
- package/dist/query.js.map +1 -0
- package/dist/reparse-636YZCE3.js +14 -0
- package/dist/reparse-636YZCE3.js.map +1 -0
- package/dist/repo.d.ts +17 -0
- package/dist/repo.js +9 -0
- package/dist/repo.js.map +1 -0
- package/dist/scanner.d.ts +73 -0
- package/dist/scanner.js +15 -0
- package/dist/scanner.js.map +1 -0
- package/dist/sdk.d.ts +82 -0
- package/dist/sdk.js +208 -0
- package/dist/sdk.js.map +1 -0
- package/dist/server.d.ts +5 -0
- package/dist/server.js +25 -0
- package/dist/server.js.map +1 -0
- package/dist/setup.d.ts +35 -0
- package/dist/setup.js +19 -0
- package/dist/setup.js.map +1 -0
- package/dist/sync/index.d.ts +29 -0
- package/dist/sync/index.js +32 -0
- package/dist/sync/index.js.map +1 -0
- package/dist/targets.d.ts +279 -0
- package/dist/targets.js +20 -0
- package/dist/targets.js.map +1 -0
- package/dist/types-D-MYCBol.d.ts +128 -0
- package/dist/types.d.ts +164 -0
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -0
- package/hooks/hooks.json +274 -0
- package/package.json +124 -0
- package/skills/panopticon-optimize/SKILL.md +222 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/db/store.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { gzipSync } from \"node:zlib\";\n\nimport { getDb } from \"./schema.js\";\n\nexport interface OtelLogRow {\n timestamp_ns: number;\n observed_timestamp_ns?: number;\n severity_number?: number;\n severity_text?: string;\n body?: string;\n attributes?: Record<string, unknown>;\n resource_attributes?: Record<string, unknown>;\n session_id?: string;\n prompt_id?: string;\n trace_id?: string;\n span_id?: string;\n}\n\nexport interface OtelMetricRow {\n timestamp_ns: number;\n name: string;\n value: number;\n metric_type?: string;\n unit?: string;\n attributes?: Record<string, unknown>;\n resource_attributes?: Record<string, unknown>;\n session_id?: string;\n}\n\nexport interface OtelSpanRow {\n trace_id: string;\n span_id: string;\n parent_span_id?: string;\n name: string;\n kind?: number;\n start_time_ns: number;\n end_time_ns: number;\n status_code?: number;\n status_message?: string;\n attributes?: Record<string, unknown>;\n resource_attributes?: Record<string, unknown>;\n session_id?: string;\n}\n\nexport interface HookEventRow {\n session_id: string;\n event_type: string;\n timestamp_ms: number;\n cwd?: string;\n repository?: string;\n tool_name?: string;\n target?: string;\n user_prompt?: string;\n file_path?: string;\n command?: string;\n plan?: string;\n allowed_prompts?: string;\n payload: unknown;\n}\n\nconst INSERT_LOG_SQL = `\n INSERT INTO otel_logs (timestamp_ns, observed_timestamp_ns, severity_number, severity_text, body, attributes, resource_attributes, session_id, prompt_id, trace_id, span_id)\n VALUES (@timestamp_ns, @observed_timestamp_ns, @severity_number, @severity_text, @body, @attributes, @resource_attributes, @session_id, @prompt_id, @trace_id, @span_id)\n`;\n\nconst INSERT_METRIC_SQL = `\n INSERT INTO otel_metrics (timestamp_ns, name, value, metric_type, unit, attributes, resource_attributes, session_id)\n VALUES (@timestamp_ns, @name, @value, @metric_type, @unit, @attributes, @resource_attributes, @session_id)\n`;\n\nconst INSERT_SPAN_SQL = `\n INSERT OR IGNORE INTO otel_spans (trace_id, span_id, parent_span_id, name, kind, start_time_ns, end_time_ns, status_code, status_message, attributes, resource_attributes, session_id)\n VALUES (@trace_id, @span_id, @parent_span_id, @name, @kind, @start_time_ns, @end_time_ns, @status_code, @status_message, @attributes, @resource_attributes, @session_id)\n`;\n\nconst INSERT_HOOK_SQL = `\n INSERT INTO hook_events (session_id, event_type, timestamp_ms, cwd, repository, tool_name,\n target, user_prompt, file_path, command, tool_result, plan, allowed_prompts, payload)\n VALUES (@session_id, @event_type, @timestamp_ms, @cwd, @repository, @tool_name,\n @target, @user_prompt, @file_path, @command, @tool_result, @plan, @allowed_prompts, @payload)\n`;\n\nexport function insertOtelLogs(rows: OtelLogRow[]): void {\n const db = getDb();\n const stmt = db.prepare(INSERT_LOG_SQL);\n const insertMany = db.transaction((rows: OtelLogRow[]) => {\n for (const row of rows) {\n stmt.run({\n timestamp_ns: row.timestamp_ns,\n observed_timestamp_ns: row.observed_timestamp_ns ?? null,\n severity_number: row.severity_number ?? null,\n severity_text: row.severity_text ?? null,\n body: row.body ?? null,\n attributes: row.attributes ? JSON.stringify(row.attributes) : null,\n resource_attributes: row.resource_attributes\n ? JSON.stringify(row.resource_attributes)\n : null,\n session_id: row.session_id ?? null,\n prompt_id: row.prompt_id ?? null,\n trace_id: row.trace_id ?? null,\n span_id: row.span_id ?? null,\n });\n }\n });\n insertMany(rows);\n}\n\nexport function insertOtelMetrics(rows: OtelMetricRow[]): void {\n const db = getDb();\n const stmt = db.prepare(INSERT_METRIC_SQL);\n\n const insertMany = db.transaction((rows: OtelMetricRow[]) => {\n for (const row of rows) {\n const sessionId = row.session_id ?? null;\n\n stmt.run({\n timestamp_ns: row.timestamp_ns,\n name: row.name,\n value: row.value,\n metric_type: row.metric_type ?? null,\n unit: row.unit ?? null,\n attributes: row.attributes ? JSON.stringify(row.attributes) : null,\n resource_attributes: row.resource_attributes\n ? JSON.stringify(row.resource_attributes)\n : null,\n session_id: sessionId,\n });\n }\n });\n insertMany(rows);\n}\n\nexport function insertOtelSpans(rows: OtelSpanRow[]): void {\n const db = getDb();\n const stmt = db.prepare(INSERT_SPAN_SQL);\n\n const insertMany = db.transaction((rows: OtelSpanRow[]) => {\n for (const row of rows) {\n stmt.run({\n trace_id: row.trace_id,\n span_id: row.span_id,\n parent_span_id: row.parent_span_id ?? null,\n name: row.name,\n kind: row.kind ?? null,\n start_time_ns: row.start_time_ns,\n end_time_ns: row.end_time_ns,\n status_code: row.status_code ?? null,\n status_message: row.status_message ?? null,\n attributes: row.attributes ? JSON.stringify(row.attributes) : null,\n resource_attributes: row.resource_attributes\n ? JSON.stringify(row.resource_attributes)\n : null,\n session_id: row.session_id ?? null,\n });\n }\n });\n insertMany(rows);\n}\n\nexport function upsertSessionRepository(\n sessionId: string,\n repository: string,\n timestampMs: number,\n gitIdentity?: { name: string | null; email: string | null },\n branch?: string | null,\n): void {\n const db = getDb();\n const existing = db\n .prepare(\n `SELECT 1 FROM session_repositories WHERE session_id = ? AND repository = ?`,\n )\n .get(sessionId, repository);\n\n db.prepare(\n `INSERT INTO session_repositories (session_id, repository, first_seen_ms, git_user_name, git_user_email, branch)\n VALUES (?, ?, ?, ?, ?, ?)\n ON CONFLICT(session_id, repository) DO UPDATE SET\n git_user_name = COALESCE(session_repositories.git_user_name, excluded.git_user_name),\n git_user_email = COALESCE(session_repositories.git_user_email, excluded.git_user_email),\n branch = COALESCE(excluded.branch, session_repositories.branch)`,\n ).run(\n sessionId,\n repository,\n timestampMs,\n gitIdentity?.name ?? null,\n gitIdentity?.email ?? null,\n branch ?? null,\n );\n\n // When a NEW repo is associated, bump sync_seq so the session re-syncs\n // with updated repository info (avoids backend repo-filter rejections)\n if (!existing) {\n db.prepare(\n `UPDATE sessions SET sync_seq = COALESCE(sync_seq, 0) + 1 WHERE session_id = ?`,\n ).run(sessionId);\n }\n}\n\nexport function upsertSessionCwd(\n sessionId: string,\n cwd: string,\n timestampMs: number,\n): void {\n const db = getDb();\n db.prepare(\n \"INSERT INTO session_cwds (session_id, cwd, first_seen_ms) VALUES (?, ?, ?) ON CONFLICT DO NOTHING\",\n ).run(sessionId, cwd, timestampMs);\n}\n\n// ---------------------------------------------------------------------------\n// Config snapshots\n// ---------------------------------------------------------------------------\n\nfunction contentHash(obj: Record<string, unknown>): string {\n return createHash(\"sha256\")\n .update(JSON.stringify(obj, Object.keys(obj).sort()))\n .digest(\"hex\");\n}\n\nexport interface UserConfigSnapshot {\n deviceName: string;\n permissions: unknown;\n enabledPlugins: unknown;\n hooks: unknown;\n commands: unknown;\n rules: unknown;\n skills: unknown;\n}\n\n/**\n * Insert a user config snapshot if the content has changed since the last one\n * for this device. Returns true if a new row was inserted.\n */\nexport function insertUserConfigSnapshot(snap: UserConfigSnapshot): boolean {\n const db = getDb();\n const hash = contentHash({\n permissions: snap.permissions,\n enabledPlugins: snap.enabledPlugins,\n hooks: snap.hooks,\n commands: snap.commands,\n rules: snap.rules,\n skills: snap.skills,\n });\n\n // Check if latest snapshot for this device has the same hash\n const existing = db\n .prepare(\n \"SELECT content_hash FROM user_config_snapshots WHERE device_name = ? ORDER BY snapshot_at_ms DESC LIMIT 1\",\n )\n .get(snap.deviceName) as { content_hash: string } | undefined;\n\n if (existing?.content_hash === hash) return false;\n\n db.prepare(\n `INSERT INTO user_config_snapshots\n (device_name, snapshot_at_ms, content_hash, permissions, enabled_plugins, hooks, commands, rules, skills)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n ).run(\n snap.deviceName,\n Date.now(),\n hash,\n JSON.stringify(snap.permissions),\n JSON.stringify(snap.enabledPlugins),\n JSON.stringify(snap.hooks),\n JSON.stringify(snap.commands),\n JSON.stringify(snap.rules),\n JSON.stringify(snap.skills),\n );\n return true;\n}\n\nexport interface RepoConfigSnapshot {\n repository: string;\n cwd: string;\n sessionId?: string;\n // project layer\n hooks: unknown;\n mcpServers: unknown;\n commands: unknown;\n agents: unknown;\n rules: unknown;\n // project local layer\n localHooks: unknown;\n localMcpServers: unknown;\n localPermissions: unknown;\n localIsGitignored: boolean;\n // instructions\n instructions: unknown;\n}\n\n/**\n * Insert a repo config snapshot if the content has changed since the last one\n * for this repository. Returns true if a new row was inserted.\n */\nexport function insertRepoConfigSnapshot(snap: RepoConfigSnapshot): boolean {\n const db = getDb();\n const hash = contentHash({\n hooks: snap.hooks,\n mcpServers: snap.mcpServers,\n commands: snap.commands,\n agents: snap.agents,\n rules: snap.rules,\n localHooks: snap.localHooks,\n localMcpServers: snap.localMcpServers,\n localPermissions: snap.localPermissions,\n localIsGitignored: snap.localIsGitignored,\n instructions: snap.instructions,\n });\n\n const existing = db\n .prepare(\n \"SELECT content_hash FROM repo_config_snapshots WHERE repository = ? ORDER BY snapshot_at_ms DESC LIMIT 1\",\n )\n .get(snap.repository) as { content_hash: string } | undefined;\n\n if (existing?.content_hash === hash) return false;\n\n db.prepare(\n `INSERT INTO repo_config_snapshots\n (repository, cwd, session_id, snapshot_at_ms, content_hash,\n hooks, mcp_servers, commands, agents, rules,\n local_hooks, local_mcp_servers, local_permissions, local_is_gitignored,\n instructions)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n ).run(\n snap.repository,\n snap.cwd,\n snap.sessionId ?? null,\n Date.now(),\n hash,\n JSON.stringify(snap.hooks),\n JSON.stringify(snap.mcpServers),\n JSON.stringify(snap.commands),\n JSON.stringify(snap.agents),\n JSON.stringify(snap.rules),\n JSON.stringify(snap.localHooks),\n JSON.stringify(snap.localMcpServers),\n JSON.stringify(snap.localPermissions),\n snap.localIsGitignored ? 1 : 0,\n JSON.stringify(snap.instructions),\n );\n return true;\n}\n\nexport interface SessionUpsert {\n session_id: string;\n target?: string;\n started_at_ms?: number;\n ended_at_ms?: number;\n first_prompt?: string;\n permission_mode?: string;\n agent_version?: string;\n // Scanner-sourced fields\n model?: string;\n cli_version?: string;\n scanner_file_path?: string;\n total_input_tokens?: number;\n total_output_tokens?: number;\n total_cache_read_tokens?: number;\n total_cache_creation_tokens?: number;\n total_reasoning_tokens?: number;\n turn_count?: number;\n // OTLP-sourced tokens\n otel_input_tokens?: number;\n otel_output_tokens?: number;\n otel_cache_read_tokens?: number;\n otel_cache_creation_tokens?: number;\n // Metadata\n project?: string;\n created_at?: number;\n has_hooks?: number;\n has_otel?: number;\n has_scanner?: number;\n parent_session_id?: string;\n relationship_type?: string;\n is_automated?: number;\n}\n\nexport function upsertSession(row: SessionUpsert): void {\n const db = getDb();\n db.prepare(\n `INSERT INTO sessions (session_id, target, started_at_ms, ended_at_ms, first_prompt,\n permission_mode, agent_version, model, cli_version, scanner_file_path,\n total_input_tokens, total_output_tokens, total_cache_read_tokens,\n total_cache_creation_tokens, total_reasoning_tokens, turn_count,\n otel_input_tokens, otel_output_tokens, otel_cache_read_tokens, otel_cache_creation_tokens,\n models, project, created_at, parent_session_id, relationship_type, is_automated,\n has_hooks, has_otel, has_scanner)\n VALUES (@session_id, @target, @started_at_ms, @ended_at_ms, @first_prompt,\n @permission_mode, @agent_version, @model, @cli_version, @scanner_file_path,\n @total_input_tokens, @total_output_tokens, @total_cache_read_tokens,\n @total_cache_creation_tokens, @total_reasoning_tokens, @turn_count,\n @otel_input_tokens, @otel_output_tokens, @otel_cache_read_tokens, @otel_cache_creation_tokens,\n @model, @project, @created_at, @parent_session_id, @relationship_type, @is_automated,\n @has_hooks, @has_otel, @has_scanner)\n ON CONFLICT(session_id) DO UPDATE SET\n target = COALESCE(excluded.target, sessions.target),\n started_at_ms = COALESCE(excluded.started_at_ms, sessions.started_at_ms),\n ended_at_ms = COALESCE(excluded.ended_at_ms, sessions.ended_at_ms),\n first_prompt = COALESCE(sessions.first_prompt, excluded.first_prompt),\n permission_mode = COALESCE(excluded.permission_mode, sessions.permission_mode),\n agent_version = COALESCE(excluded.agent_version, sessions.agent_version),\n model = COALESCE(excluded.model, sessions.model),\n cli_version = COALESCE(excluded.cli_version, sessions.cli_version),\n scanner_file_path = COALESCE(excluded.scanner_file_path, sessions.scanner_file_path),\n total_input_tokens = COALESCE(excluded.total_input_tokens, sessions.total_input_tokens),\n total_output_tokens = COALESCE(excluded.total_output_tokens, sessions.total_output_tokens),\n total_cache_read_tokens = COALESCE(excluded.total_cache_read_tokens, sessions.total_cache_read_tokens),\n total_cache_creation_tokens = COALESCE(excluded.total_cache_creation_tokens, sessions.total_cache_creation_tokens),\n total_reasoning_tokens = COALESCE(excluded.total_reasoning_tokens, sessions.total_reasoning_tokens),\n turn_count = COALESCE(excluded.turn_count, sessions.turn_count),\n otel_input_tokens = COALESCE(excluded.otel_input_tokens, sessions.otel_input_tokens),\n otel_output_tokens = COALESCE(excluded.otel_output_tokens, sessions.otel_output_tokens),\n otel_cache_read_tokens = COALESCE(excluded.otel_cache_read_tokens, sessions.otel_cache_read_tokens),\n otel_cache_creation_tokens = COALESCE(excluded.otel_cache_creation_tokens, sessions.otel_cache_creation_tokens),\n models = CASE\n WHEN excluded.model IS NULL THEN sessions.models\n WHEN sessions.models IS NULL THEN excluded.model\n WHEN sessions.models LIKE '%' || excluded.model || '%' THEN sessions.models\n ELSE sessions.models || ',' || excluded.model\n END,\n project = COALESCE(sessions.project, excluded.project),\n parent_session_id = COALESCE(excluded.parent_session_id, sessions.parent_session_id),\n relationship_type = COALESCE(excluded.relationship_type, sessions.relationship_type),\n is_automated = COALESCE(excluded.is_automated, sessions.is_automated),\n has_hooks = MAX(COALESCE(sessions.has_hooks, 0), COALESCE(excluded.has_hooks, 0)),\n has_otel = MAX(COALESCE(sessions.has_otel, 0), COALESCE(excluded.has_otel, 0)),\n has_scanner = MAX(COALESCE(sessions.has_scanner, 0), COALESCE(excluded.has_scanner, 0)),\n sync_dirty = 1,\n sync_seq = COALESCE(sessions.sync_seq, 0) + 1`,\n ).run({\n session_id: row.session_id,\n target: row.target ?? null,\n started_at_ms: row.started_at_ms ?? null,\n ended_at_ms: row.ended_at_ms ?? null,\n first_prompt: row.first_prompt ?? null,\n permission_mode: row.permission_mode ?? null,\n agent_version: row.agent_version ?? null,\n model: row.model ?? null,\n cli_version: row.cli_version ?? null,\n scanner_file_path: row.scanner_file_path ?? null,\n total_input_tokens: row.total_input_tokens ?? null,\n total_output_tokens: row.total_output_tokens ?? null,\n total_cache_read_tokens: row.total_cache_read_tokens ?? null,\n total_cache_creation_tokens: row.total_cache_creation_tokens ?? null,\n total_reasoning_tokens: row.total_reasoning_tokens ?? null,\n turn_count: row.turn_count ?? null,\n otel_input_tokens: row.otel_input_tokens ?? null,\n otel_output_tokens: row.otel_output_tokens ?? null,\n otel_cache_read_tokens: row.otel_cache_read_tokens ?? null,\n otel_cache_creation_tokens: row.otel_cache_creation_tokens ?? null,\n project: row.project ?? null,\n created_at: row.created_at ?? null,\n parent_session_id: row.parent_session_id ?? null,\n relationship_type: row.relationship_type ?? null,\n is_automated: row.is_automated ?? null,\n has_hooks: row.has_hooks ?? null,\n has_otel: row.has_otel ?? null,\n has_scanner: row.has_scanner ?? null,\n });\n}\n\n/** Prefixes/substrings that identify automated (non-interactive) sessions. */\nconst AUTOMATED_PREFIXES = [\n \"You are a code reviewer.\",\n \"You are a security code reviewer.\",\n \"You are a design reviewer.\",\n \"You are a code assistant. Your task is to address\",\n \"You are a code review insights analyst.\",\n \"You are reviewing whether an implementation matches\",\n \"You are a plan document reviewer.\",\n \"You are a spec document reviewer.\",\n \"You are summarizing a day of AI agent activity.\",\n \"You are analyzing AI agent sessions.\",\n \"## Analysis Request\",\n \"# Fix Request\",\n];\nconst AUTOMATED_SUBSTRINGS = [\"invoked by roborev to perform this review\"];\n\nfunction isAutomatedPrompt(firstPrompt: string): boolean {\n for (const p of AUTOMATED_PREFIXES) {\n if (firstPrompt.startsWith(p)) return true;\n }\n for (const s of AUTOMATED_SUBSTRINGS) {\n if (firstPrompt.includes(s)) return true;\n }\n return false;\n}\n\n/**\n * Recompute message_count, user_message_count, and is_automated\n * from the messages table. is_automated is set when user_message_count <= 1\n * and first_prompt matches a known automated pattern.\n */\nexport function updateSessionMessageCounts(sessionId: string): void {\n const db = getDb();\n\n // Count non-system user messages\n const counts = db\n .prepare(\n `SELECT\n (SELECT COUNT(*) FROM messages WHERE session_id = ?) as msg_count,\n (SELECT COUNT(*) FROM messages WHERE session_id = ? AND role = 'user' AND is_system = 0) as user_count,\n (SELECT first_prompt FROM sessions WHERE session_id = ?) as first_prompt`,\n )\n .get(sessionId, sessionId, sessionId) as {\n msg_count: number;\n user_count: number;\n first_prompt: string | null;\n };\n\n const isAutomated =\n counts.user_count <= 1 &&\n counts.first_prompt != null &&\n isAutomatedPrompt(counts.first_prompt)\n ? 1\n : 0;\n\n db.prepare(\n `UPDATE sessions SET\n message_count = ?,\n user_message_count = ?,\n is_automated = CASE WHEN ? > 1 THEN 0 ELSE ? END,\n sync_seq = COALESCE(sync_seq, 0) + 1\n WHERE session_id = ?`,\n ).run(\n counts.msg_count,\n counts.user_count,\n counts.user_count,\n isAutomated,\n sessionId,\n );\n}\n\n/**\n * Increment a hook tool count for a session. Uses JSON_SET to atomically\n * update the hook_tool_counts JSON object.\n * Does NOT bump sync_seq — the scanner drives sync_seq via updateSessionTotals.\n * Hook data syncs via Phase 2 (hook_events table), not the session row.\n */\nexport function incrementToolCount(sessionId: string, toolName: string): void {\n const db = getDb();\n db.prepare(\n `UPDATE sessions\n SET hook_tool_counts = JSON_SET(\n COALESCE(hook_tool_counts, '{}'),\n '$.' || @tool,\n COALESCE(JSON_EXTRACT(hook_tool_counts, '$.' || @tool), 0) + 1\n )\n WHERE session_id = @session_id`,\n ).run({ session_id: sessionId, tool: toolName });\n}\n\n/**\n * Increment a hook event type count for a session.\n * Does NOT bump sync_seq — same rationale as incrementToolCount.\n */\nexport function incrementEventTypeCount(\n sessionId: string,\n eventType: string,\n): void {\n const db = getDb();\n db.prepare(\n `UPDATE sessions\n SET hook_event_type_counts = JSON_SET(\n COALESCE(hook_event_type_counts, '{}'),\n '$.' || @event_type,\n COALESCE(JSON_EXTRACT(hook_event_type_counts, '$.' || @event_type), 0) + 1\n )\n WHERE session_id = @session_id`,\n ).run({ session_id: sessionId, event_type: eventType });\n}\n\nfunction extractStr(\n obj: Record<string, unknown> | undefined,\n key: string,\n): string | undefined {\n const v = obj?.[key];\n return typeof v === \"string\" ? v : undefined;\n}\n\nexport function insertHookEvent(row: HookEventRow): void {\n const db = getDb();\n const data = row.payload as Record<string, unknown>;\n const toolInput = data.tool_input as Record<string, unknown> | undefined;\n\n // Extract high-value fields into columns for indexed queries\n const userPrompt =\n extractStr(data, \"prompt\") ?? extractStr(data, \"user_prompt\");\n const filePath = extractStr(toolInput, \"file_path\");\n const command = extractStr(toolInput, \"command\");\n const plan = extractStr(toolInput, \"plan\");\n const toolResultRaw = data.tool_result ?? data.tool_response;\n const toolResult = toolResultRaw\n ? typeof toolResultRaw === \"string\"\n ? toolResultRaw\n : JSON.stringify(toolResultRaw)\n : undefined;\n const allowedPrompts = toolInput?.allowedPrompts\n ? JSON.stringify(toolInput.allowedPrompts)\n : undefined;\n\n const fullJson = JSON.stringify(data);\n\n const insertWithFts = db.transaction(() => {\n db.prepare(INSERT_HOOK_SQL).run({\n session_id: row.session_id,\n event_type: row.event_type,\n timestamp_ms: row.timestamp_ms,\n cwd: row.cwd ?? null,\n repository: row.repository ?? null,\n tool_name: row.tool_name ?? null,\n target: row.target ?? null,\n user_prompt: userPrompt ?? null,\n file_path: filePath ?? null,\n command: command ?? null,\n tool_result: toolResult ?? null,\n plan: plan ?? null,\n allowed_prompts: allowedPrompts ?? null,\n payload: gzipSync(Buffer.from(fullJson)),\n });\n const { id } = db.prepare(\"SELECT last_insert_rowid() as id\").get() as {\n id: number;\n };\n db.prepare(\"INSERT INTO hook_events_fts(rowid, payload) VALUES (?, ?)\").run(\n id,\n fullJson,\n );\n });\n insertWithFts();\n}\n"],"mappings":"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","names":["rows"]}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import {
|
|
2
|
+
config
|
|
3
|
+
} from "./chunk-K7YUPLES.js";
|
|
4
|
+
|
|
5
|
+
// src/sentry.ts
|
|
6
|
+
import os from "os";
|
|
7
|
+
import {
|
|
8
|
+
createStackParser,
|
|
9
|
+
createTransport,
|
|
10
|
+
isInitialized,
|
|
11
|
+
nodeStackLineParser,
|
|
12
|
+
ServerRuntimeClient,
|
|
13
|
+
addBreadcrumb as sentryAddBreadcrumb,
|
|
14
|
+
captureException as sentryCaptureException,
|
|
15
|
+
flush as sentryFlush,
|
|
16
|
+
setTag as sentrySetTag,
|
|
17
|
+
setCurrentClient,
|
|
18
|
+
withScope
|
|
19
|
+
} from "@sentry/core";
|
|
20
|
+
var initialized = false;
|
|
21
|
+
function getVersion() {
|
|
22
|
+
return true ? "0.1.0+2aee981" : "dev";
|
|
23
|
+
}
|
|
24
|
+
var SCRUBBED_BREADCRUMB_FIELDS = [
|
|
25
|
+
"prompt",
|
|
26
|
+
"user_prompt",
|
|
27
|
+
"content",
|
|
28
|
+
"body",
|
|
29
|
+
"command",
|
|
30
|
+
"file_content",
|
|
31
|
+
"stdin"
|
|
32
|
+
];
|
|
33
|
+
var SENSITIVE_VAR_PATTERNS = [
|
|
34
|
+
"token",
|
|
35
|
+
"secret",
|
|
36
|
+
"password",
|
|
37
|
+
"dsn",
|
|
38
|
+
"prompt",
|
|
39
|
+
"body"
|
|
40
|
+
];
|
|
41
|
+
function scrubEvent(event) {
|
|
42
|
+
if (event.breadcrumbs) {
|
|
43
|
+
for (const bc of event.breadcrumbs) {
|
|
44
|
+
if (bc.data) {
|
|
45
|
+
for (const key of SCRUBBED_BREADCRUMB_FIELDS) {
|
|
46
|
+
if (key in bc.data) {
|
|
47
|
+
bc.data[key] = "[scrubbed]";
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (bc.data.headers && typeof bc.data.headers === "object") {
|
|
51
|
+
const headers = bc.data.headers;
|
|
52
|
+
if (headers.authorization) headers.authorization = "[scrubbed]";
|
|
53
|
+
if (headers.Authorization) headers.Authorization = "[scrubbed]";
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (event.request?.headers) {
|
|
59
|
+
delete event.request.headers.authorization;
|
|
60
|
+
delete event.request.headers.cookie;
|
|
61
|
+
}
|
|
62
|
+
if (event.exception?.values) {
|
|
63
|
+
for (const ex of event.exception.values) {
|
|
64
|
+
if (ex.stacktrace?.frames) {
|
|
65
|
+
for (const frame of ex.stacktrace.frames) {
|
|
66
|
+
if (frame.vars) {
|
|
67
|
+
for (const key of Object.keys(frame.vars)) {
|
|
68
|
+
const lk = key.toLowerCase();
|
|
69
|
+
if (SENSITIVE_VAR_PATTERNS.some((p) => lk.includes(p))) {
|
|
70
|
+
frame.vars[key] = "[scrubbed]";
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return event;
|
|
79
|
+
}
|
|
80
|
+
function filterBreadcrumb(breadcrumb) {
|
|
81
|
+
if (breadcrumb.category === "console" && breadcrumb.level === "debug") {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
if (breadcrumb.category === "http" && breadcrumb.data?.status_code === 200) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
return breadcrumb;
|
|
88
|
+
}
|
|
89
|
+
function initSentry() {
|
|
90
|
+
if (initialized) return isInitialized();
|
|
91
|
+
initialized = true;
|
|
92
|
+
const dsn = process.env.PANOPTICON_SENTRY_DSN ?? "https://dcf9fb5ae8ac18803d98c3ee577faf39@o4510167429873664.ingest.us.sentry.io/4511107500343296";
|
|
93
|
+
if (!dsn) return false;
|
|
94
|
+
const version = getVersion();
|
|
95
|
+
const client = new ServerRuntimeClient({
|
|
96
|
+
dsn,
|
|
97
|
+
release: `panopticon@${version}`,
|
|
98
|
+
environment: process.env.NODE_ENV ?? "production",
|
|
99
|
+
serverName: os.hostname(),
|
|
100
|
+
tracesSampleRate: 0,
|
|
101
|
+
sendDefaultPii: false,
|
|
102
|
+
integrations: [],
|
|
103
|
+
stackParser: createStackParser(nodeStackLineParser()),
|
|
104
|
+
transport: (options) => createTransport(options, async (request) => {
|
|
105
|
+
const res = await fetch(options.url, {
|
|
106
|
+
method: "POST",
|
|
107
|
+
headers: { "Content-Type": "application/x-sentry-envelope" },
|
|
108
|
+
body: request.body
|
|
109
|
+
});
|
|
110
|
+
return { statusCode: res.status };
|
|
111
|
+
}),
|
|
112
|
+
initialScope: {
|
|
113
|
+
tags: {
|
|
114
|
+
panopticon_version: version,
|
|
115
|
+
platform: os.platform(),
|
|
116
|
+
node_version: process.version
|
|
117
|
+
},
|
|
118
|
+
contexts: {
|
|
119
|
+
panopticon: {
|
|
120
|
+
port: config.port,
|
|
121
|
+
data_dir: config.dataDir,
|
|
122
|
+
arch: os.arch()
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
beforeSend: scrubEvent,
|
|
127
|
+
maxBreadcrumbs: 30,
|
|
128
|
+
beforeBreadcrumb: filterBreadcrumb
|
|
129
|
+
});
|
|
130
|
+
setCurrentClient(client);
|
|
131
|
+
client.init();
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
function captureException(err, context) {
|
|
135
|
+
if (!isInitialized()) return;
|
|
136
|
+
withScope((scope) => {
|
|
137
|
+
if (context) {
|
|
138
|
+
if (context.component) {
|
|
139
|
+
scope.setTag("component", context.component);
|
|
140
|
+
}
|
|
141
|
+
const extra = { ...context };
|
|
142
|
+
delete extra.component;
|
|
143
|
+
if (Object.keys(extra).length > 0) {
|
|
144
|
+
scope.setContext("panopticon_error", extra);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
sentryCaptureException(err);
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
function addBreadcrumb(category, message, data, level = "info") {
|
|
151
|
+
if (!isInitialized()) return;
|
|
152
|
+
sentryAddBreadcrumb({ category, message, data, level });
|
|
153
|
+
}
|
|
154
|
+
function setTag(key, value) {
|
|
155
|
+
if (!isInitialized()) return;
|
|
156
|
+
sentrySetTag(key, value);
|
|
157
|
+
}
|
|
158
|
+
async function flushSentry(timeoutMs = 2e3) {
|
|
159
|
+
if (!isInitialized()) return;
|
|
160
|
+
await sentryFlush(timeoutMs);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export {
|
|
164
|
+
initSentry,
|
|
165
|
+
captureException,
|
|
166
|
+
addBreadcrumb,
|
|
167
|
+
setTag,
|
|
168
|
+
flushSentry
|
|
169
|
+
};
|
|
170
|
+
//# sourceMappingURL=chunk-CF4GPWLI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/sentry.ts"],"sourcesContent":["/**\n * Sentry error reporting — uses @sentry/core (no OTel instrumentation bloat).\n *\n * Provides breadcrumbs, structured context, and PII scrubbing so that\n * error reports carry rich debugging context without leaking user data.\n */\n\nimport os from \"node:os\";\nimport type { Breadcrumb, ErrorEvent } from \"@sentry/core\";\nimport {\n createStackParser,\n createTransport,\n isInitialized,\n nodeStackLineParser,\n ServerRuntimeClient,\n addBreadcrumb as sentryAddBreadcrumb,\n captureException as sentryCaptureException,\n flush as sentryFlush,\n setTag as sentrySetTag,\n setCurrentClient,\n withScope,\n} from \"@sentry/core\";\nimport { config } from \"./config.js\";\n\ndeclare const __PANOPTICON_VERSION__: string;\ndeclare const __SENTRY_DSN__: string;\n\nlet initialized = false;\n\nfunction getVersion(): string {\n return typeof __PANOPTICON_VERSION__ !== \"undefined\"\n ? __PANOPTICON_VERSION__\n : \"dev\";\n}\n\n// ── Scrubbing & Filtering (exported for testing) ────────────────────────────\n\n/** Fields in breadcrumb data that may contain user content. */\nconst SCRUBBED_BREADCRUMB_FIELDS = [\n \"prompt\",\n \"user_prompt\",\n \"content\",\n \"body\",\n \"command\",\n \"file_content\",\n \"stdin\",\n];\n\n/** Local variable names that may contain secrets. */\nconst SENSITIVE_VAR_PATTERNS = [\n \"token\",\n \"secret\",\n \"password\",\n \"dsn\",\n \"prompt\",\n \"body\",\n];\n\n/** Strip sensitive data from a Sentry event before it leaves the machine. */\nexport function scrubEvent(event: ErrorEvent): ErrorEvent | null {\n // Scrub breadcrumb data that might contain user prompts or file contents\n if (event.breadcrumbs) {\n for (const bc of event.breadcrumbs) {\n if (bc.data) {\n for (const key of SCRUBBED_BREADCRUMB_FIELDS) {\n if (key in bc.data) {\n bc.data[key] = \"[scrubbed]\";\n }\n }\n // Scrub authorization headers\n if (bc.data.headers && typeof bc.data.headers === \"object\") {\n const headers = bc.data.headers as Record<string, unknown>;\n if (headers.authorization) headers.authorization = \"[scrubbed]\";\n if (headers.Authorization) headers.Authorization = \"[scrubbed]\";\n }\n }\n }\n }\n\n // Scrub request data if present\n if (event.request?.headers) {\n delete event.request.headers.authorization;\n delete event.request.headers.cookie;\n }\n\n // Scrub local variables that might contain sensitive data\n if (event.exception?.values) {\n for (const ex of event.exception.values) {\n if (ex.stacktrace?.frames) {\n for (const frame of ex.stacktrace.frames) {\n if (frame.vars) {\n for (const key of Object.keys(frame.vars)) {\n const lk = key.toLowerCase();\n if (SENSITIVE_VAR_PATTERNS.some((p) => lk.includes(p))) {\n frame.vars[key] = \"[scrubbed]\";\n }\n }\n }\n }\n }\n }\n }\n\n return event;\n}\n\n/** Filter out noisy breadcrumbs that would drown signal. */\nexport function filterBreadcrumb(breadcrumb: Breadcrumb): Breadcrumb | null {\n // Drop debug-level console breadcrumbs (sync log lines, etc.)\n if (breadcrumb.category === \"console\" && breadcrumb.level === \"debug\") {\n return null;\n }\n // Drop routine outbound HTTP to sync targets (keep errors only)\n if (breadcrumb.category === \"http\" && breadcrumb.data?.status_code === 200) {\n return null;\n }\n return breadcrumb;\n}\n\n// ── Init ────────────────────────────────────────────────────────────────────\n\n/**\n * Initialize Sentry if a DSN is available. Safe to call multiple times —\n * subsequent calls are no-ops. Returns true if Sentry is active.\n */\nexport function initSentry(): boolean {\n if (initialized) return isInitialized();\n\n initialized = true;\n\n const dsn = process.env.PANOPTICON_SENTRY_DSN ?? __SENTRY_DSN__;\n if (!dsn) return false;\n\n const version = getVersion();\n\n const client = new ServerRuntimeClient({\n dsn,\n release: `panopticon@${version}`,\n environment: process.env.NODE_ENV ?? \"production\",\n serverName: os.hostname(),\n\n tracesSampleRate: 0,\n sendDefaultPii: false,\n integrations: [],\n stackParser: createStackParser(nodeStackLineParser()),\n transport: (options) =>\n createTransport(options, async (request) => {\n const res = await fetch(options.url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-sentry-envelope\" },\n body: request.body as BodyInit,\n });\n return { statusCode: res.status };\n }),\n\n initialScope: {\n tags: {\n panopticon_version: version,\n platform: os.platform(),\n node_version: process.version,\n },\n contexts: {\n panopticon: {\n port: config.port,\n data_dir: config.dataDir,\n arch: os.arch(),\n },\n },\n },\n\n beforeSend: scrubEvent,\n maxBreadcrumbs: 30,\n beforeBreadcrumb: filterBreadcrumb,\n });\n\n setCurrentClient(client);\n client.init();\n\n return true;\n}\n\n// ── Public API ──────────────────────────────────────────────────────────────\n\n/**\n * Report an exception to Sentry with structured context.\n * No-op if Sentry is not initialized.\n */\nexport function captureException(\n err: unknown,\n context?: {\n component?: string;\n [key: string]: string | number | undefined;\n },\n): void {\n if (!isInitialized()) return;\n\n withScope((scope) => {\n if (context) {\n if (context.component) {\n scope.setTag(\"component\", context.component);\n }\n const extra = { ...context };\n delete extra.component;\n if (Object.keys(extra).length > 0) {\n scope.setContext(\"panopticon_error\", extra);\n }\n }\n sentryCaptureException(err);\n });\n}\n\n/**\n * Add a breadcrumb to the current Sentry scope.\n * No-op if Sentry is not initialized.\n */\nexport function addBreadcrumb(\n category: string,\n message: string,\n data?: Record<string, unknown>,\n level: \"info\" | \"warning\" | \"error\" | \"debug\" = \"info\",\n): void {\n if (!isInitialized()) return;\n sentryAddBreadcrumb({ category, message, data, level });\n}\n\n/**\n * Set a global tag on all future Sentry events.\n */\nexport function setTag(key: string, value: string | number): void {\n if (!isInitialized()) return;\n sentrySetTag(key, value);\n}\n\n/**\n * Flush pending Sentry events before process exit.\n */\nexport async function flushSentry(timeoutMs = 2000): Promise<void> {\n if (!isInitialized()) return;\n await sentryFlush(timeoutMs);\n}\n"],"mappings":"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","names":[]}
|