@h-rig/server 0.0.6-alpha.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.
Files changed (60) hide show
  1. package/README.md +14 -0
  2. package/dist/src/bootstrap.js +161 -0
  3. package/dist/src/index.js +13153 -0
  4. package/dist/src/inspector/agent-runtime.js +1077 -0
  5. package/dist/src/inspector/analysis.js +41 -0
  6. package/dist/src/inspector/discovery.js +137 -0
  7. package/dist/src/inspector/journal.js +518 -0
  8. package/dist/src/inspector/mission.js +562 -0
  9. package/dist/src/inspector/prompt.js +97 -0
  10. package/dist/src/inspector/provider-session.js +65 -0
  11. package/dist/src/inspector/reconcile.js +118 -0
  12. package/dist/src/inspector/review.js +13 -0
  13. package/dist/src/inspector/service.js +1759 -0
  14. package/dist/src/inspector/skills.js +155 -0
  15. package/dist/src/inspector/tools.js +1592 -0
  16. package/dist/src/inspector/types.js +1 -0
  17. package/dist/src/inspector/upstream-sync.js +479 -0
  18. package/dist/src/orchestration.js +402 -0
  19. package/dist/src/remote.js +123 -0
  20. package/dist/src/scheduler.js +84 -0
  21. package/dist/src/server-helpers/broadcasters.js +161 -0
  22. package/dist/src/server-helpers/conversation-snapshot.js +382 -0
  23. package/dist/src/server-helpers/event-emitter.js +41 -0
  24. package/dist/src/server-helpers/github-auth-store.js +155 -0
  25. package/dist/src/server-helpers/github-credentials.js +38 -0
  26. package/dist/src/server-helpers/github-project-status-sync.js +196 -0
  27. package/dist/src/server-helpers/github-projects.js +147 -0
  28. package/dist/src/server-helpers/github-reconciler.js +89 -0
  29. package/dist/src/server-helpers/http-router.js +3781 -0
  30. package/dist/src/server-helpers/http-utils.js +135 -0
  31. package/dist/src/server-helpers/inspector-agent-lifecycle.js +104 -0
  32. package/dist/src/server-helpers/inspector-jobs.js +4145 -0
  33. package/dist/src/server-helpers/issue-analysis.js +362 -0
  34. package/dist/src/server-helpers/normalizers.js +31 -0
  35. package/dist/src/server-helpers/notifications.js +96 -0
  36. package/dist/src/server-helpers/orchestration-ops.js +287 -0
  37. package/dist/src/server-helpers/orchestration.js +39 -0
  38. package/dist/src/server-helpers/plugin-host-cache.js +86 -0
  39. package/dist/src/server-helpers/project-fs-ops.js +194 -0
  40. package/dist/src/server-helpers/project-registry.js +124 -0
  41. package/dist/src/server-helpers/queue-state.js +78 -0
  42. package/dist/src/server-helpers/remote-checkout.js +140 -0
  43. package/dist/src/server-helpers/remote-snapshots.js +119 -0
  44. package/dist/src/server-helpers/run-io.js +262 -0
  45. package/dist/src/server-helpers/run-mutations.js +1784 -0
  46. package/dist/src/server-helpers/run-steering.js +176 -0
  47. package/dist/src/server-helpers/run-writers.js +75 -0
  48. package/dist/src/server-helpers/server-paths.js +27 -0
  49. package/dist/src/server-helpers/snapshot-orchestrator.js +832 -0
  50. package/dist/src/server-helpers/snapshot-service.js +1143 -0
  51. package/dist/src/server-helpers/summaries.js +126 -0
  52. package/dist/src/server-helpers/task-config.js +50 -0
  53. package/dist/src/server-helpers/task-projection.js +98 -0
  54. package/dist/src/server-helpers/terminal-runtime.js +156 -0
  55. package/dist/src/server-helpers/terminal-sessions.js +22 -0
  56. package/dist/src/server-helpers/validation-failure.js +31 -0
  57. package/dist/src/server-helpers/ws-router.js +1308 -0
  58. package/dist/src/server.js +12628 -0
  59. package/dist/src/websocket.js +63 -0
  60. package/package.json +33 -0
@@ -0,0 +1,41 @@
1
+ // @bun
2
+ // packages/server/src/inspector/analysis.ts
3
+ function summarizeInspectorJournal(options) {
4
+ const now = options.now ?? (() => new Date().toISOString());
5
+ const counts = options.journal.getCounts();
6
+ const activeRuns = options.journal.listActiveRuns();
7
+ const recentFindings = options.journal.listRecentFindings({ limit: 50 });
8
+ const severityCounts = recentFindings.reduce((acc, finding) => {
9
+ acc[finding.severity] = (acc[finding.severity] ?? 0) + 1;
10
+ return acc;
11
+ }, {});
12
+ const conflictCount = activeRuns.filter((run) => run.conflictState !== "none").length;
13
+ const summary = `Inspector journal summary: observations=${counts.observations}, decisions=${counts.decisions}, ` + `actions=${counts.actions}, followups=${counts.followups}, activeRuns=${activeRuns.length}, conflicts=${conflictCount}`;
14
+ options.journal.appendAnalysisReport({
15
+ id: options.reportId,
16
+ dedupeKey: `${options.reportType}:${options.reportId}`,
17
+ reportType: options.reportType,
18
+ status: "completed",
19
+ summary,
20
+ details: {
21
+ ...counts,
22
+ activeRuns: activeRuns.length,
23
+ conflictCount,
24
+ severityCounts
25
+ },
26
+ createdAt: now()
27
+ });
28
+ return {
29
+ status: "completed",
30
+ summary,
31
+ details: {
32
+ ...counts,
33
+ activeRuns: activeRuns.length,
34
+ conflictCount,
35
+ severityCounts
36
+ }
37
+ };
38
+ }
39
+ export {
40
+ summarizeInspectorJournal
41
+ };
@@ -0,0 +1,137 @@
1
+ // @bun
2
+ // packages/server/src/inspector/discovery.ts
3
+ import {
4
+ runStatus
5
+ } from "@rig/runtime/control-plane/native/run-ops";
6
+ import {
7
+ listAuthorityRuns,
8
+ readAuthorityRun
9
+ } from "@rig/runtime/control-plane/authority-files";
10
+ function providerFromRuntimeAdapter(runtimeAdapter) {
11
+ if (!runtimeAdapter) {
12
+ return null;
13
+ }
14
+ return runtimeAdapter;
15
+ }
16
+ function uniqueStrings(values) {
17
+ return [...new Set(values.filter((value) => typeof value === "string" && value.length > 0))];
18
+ }
19
+ function preferString(...values) {
20
+ return values.find((value) => typeof value === "string" && value.length > 0) ?? null;
21
+ }
22
+ function chooseStatus(surfaces) {
23
+ const authority = surfaces.find((surface) => surface.source === "authority-record");
24
+ if (authority?.status) {
25
+ return authority.status;
26
+ }
27
+ return preferString(...surfaces.map((surface) => surface.status)) ?? "unknown";
28
+ }
29
+ function chooseUpdatedAt(surfaces) {
30
+ const timestamps = uniqueStrings(surfaces.map((surface) => surface.updatedAt)).sort();
31
+ return timestamps.at(-1) ?? "";
32
+ }
33
+ function buildConflict(surfaces) {
34
+ const statuses = uniqueStrings(surfaces.map((surface) => surface.status));
35
+ const titles = uniqueStrings(surfaces.map((surface) => surface.title));
36
+ const activeSurface = surfaces.find((surface) => new Set(["preparing", "running", "validating", "reviewing"]).has(surface.status));
37
+ const authoritySurface = surfaces.find((surface) => surface.source === "authority-record");
38
+ if (activeSurface && authoritySurface && new Set(["failed", "stopped", "completed", "done"]).has(authoritySurface.status)) {
39
+ return {
40
+ state: "stale-active",
41
+ summary: `Run ${authoritySurface.runId} is still reported active by ${activeSurface.source} but authority says ${authoritySurface.status}.`
42
+ };
43
+ }
44
+ if (statuses.length > 1 || titles.length > 1) {
45
+ const sourceSummary = surfaces.map((surface) => `${surface.source}:${surface.status}:${surface.title}`).join(" | ");
46
+ return {
47
+ state: "source-disagreement",
48
+ summary: `Conflicting run surfaces for ${surfaces[0]?.runId ?? "unknown"}: ${sourceSummary}`
49
+ };
50
+ }
51
+ return { state: "none", summary: null };
52
+ }
53
+ function mergeRunSurfaces(surfaces) {
54
+ const authority = surfaces.find((surface) => surface.source === "authority-record") ?? null;
55
+ const conflict = buildConflict(surfaces);
56
+ return {
57
+ runId: surfaces[0].runId,
58
+ workspaceId: preferString(authority?.workspaceId, ...surfaces.map((surface) => surface.workspaceId)),
59
+ taskId: preferString(authority?.taskId, ...surfaces.map((surface) => surface.taskId)),
60
+ provider: preferString(authority?.provider, ...surfaces.map((surface) => surface.provider)),
61
+ runtimeAdapter: preferString(authority?.runtimeAdapter, ...surfaces.map((surface) => surface.runtimeAdapter)),
62
+ threadId: preferString(authority?.threadId, ...surfaces.map((surface) => surface.threadId)),
63
+ status: chooseStatus(surfaces),
64
+ title: preferString(authority?.title, ...surfaces.map((surface) => surface.title)) ?? surfaces[0].runId,
65
+ source: authority?.source ?? surfaces[0].source,
66
+ rawSourceKinds: uniqueStrings(surfaces.map((surface) => surface.source)),
67
+ conflictState: conflict.state,
68
+ conflictSummary: conflict.summary,
69
+ worktreePath: preferString(authority?.worktreePath, ...surfaces.map((surface) => surface.worktreePath)),
70
+ artifactRoot: preferString(authority?.artifactRoot, ...surfaces.map((surface) => surface.artifactRoot)),
71
+ logRoot: preferString(authority?.logRoot, ...surfaces.map((surface) => surface.logRoot)),
72
+ sessionPath: preferString(authority?.sessionPath, ...surfaces.map((surface) => surface.sessionPath)),
73
+ sessionLogPath: preferString(authority?.sessionLogPath, ...surfaces.map((surface) => surface.sessionLogPath)),
74
+ startedAt: preferString(authority?.startedAt, ...surfaces.map((surface) => surface.startedAt)),
75
+ updatedAt: chooseUpdatedAt(surfaces),
76
+ completedAt: preferString(authority?.completedAt, ...surfaces.map((surface) => surface.completedAt))
77
+ };
78
+ }
79
+ function discoverInspectorRuns(options) {
80
+ const summary = options.statusSummary ?? runStatus(options.projectRoot);
81
+ const discovered = new Map;
82
+ const pushSurface = (surface) => {
83
+ const existing = discovered.get(surface.runId) ?? [];
84
+ existing.push(surface);
85
+ discovered.set(surface.runId, existing);
86
+ };
87
+ for (const authorityEntry of listAuthorityRuns(options.projectRoot)) {
88
+ const run = readAuthorityRun(options.projectRoot, authorityEntry.runId);
89
+ if (!run) {
90
+ continue;
91
+ }
92
+ pushSurface({
93
+ runId: run.runId,
94
+ workspaceId: run.workspaceId ?? null,
95
+ taskId: run.taskId ?? null,
96
+ provider: providerFromRuntimeAdapter(run.runtimeAdapter ?? null),
97
+ runtimeAdapter: run.runtimeAdapter ?? null,
98
+ threadId: run.threadId ?? null,
99
+ status: run.status ?? "unknown",
100
+ title: run.title ?? run.taskId ?? run.runId,
101
+ source: "authority-record",
102
+ worktreePath: run.worktreePath ?? null,
103
+ artifactRoot: run.artifactRoot ?? null,
104
+ logRoot: run.logRoot ?? null,
105
+ sessionPath: run.sessionPath ?? null,
106
+ sessionLogPath: run.sessionLogPath ?? null,
107
+ startedAt: run.startedAt ?? null,
108
+ updatedAt: run.updatedAt,
109
+ completedAt: run.completedAt ?? null
110
+ });
111
+ }
112
+ for (const entry of [...summary.activeRuns, ...summary.recentRuns]) {
113
+ pushSurface({
114
+ runId: entry.runId,
115
+ workspaceId: null,
116
+ taskId: entry.taskId ?? null,
117
+ provider: null,
118
+ runtimeAdapter: null,
119
+ threadId: null,
120
+ status: entry.status,
121
+ title: entry.title,
122
+ source: "run-status",
123
+ worktreePath: null,
124
+ artifactRoot: null,
125
+ logRoot: null,
126
+ sessionPath: null,
127
+ sessionLogPath: null,
128
+ startedAt: null,
129
+ updatedAt: "",
130
+ completedAt: null
131
+ });
132
+ }
133
+ return [...discovered.values()].map((surfaces) => mergeRunSurfaces(surfaces)).sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
134
+ }
135
+ export {
136
+ discoverInspectorRuns
137
+ };
@@ -0,0 +1,518 @@
1
+ // @bun
2
+ // packages/server/src/inspector/journal.ts
3
+ import { Database } from "bun:sqlite";
4
+ import { mkdirSync as mkdirSync2 } from "fs";
5
+ import { dirname as dirname2, resolve as resolve2 } from "path";
6
+
7
+ // packages/server/src/bootstrap.ts
8
+ import { existsSync, mkdirSync, unlinkSync, writeFileSync } from "fs";
9
+ import { dirname, resolve } from "path";
10
+ import { RIG_DEFINITION_DIRNAME, resolveMonorepoRoot } from "@rig/runtime";
11
+ function normalizeOptionalString(value) {
12
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
13
+ }
14
+ function resolveRigServerPaths(projectRoot) {
15
+ const taskWorkspace = normalizeOptionalString(process.env.RIG_TASK_WORKSPACE);
16
+ const explicitStateDir = normalizeOptionalString(process.env.RIG_STATE_DIR);
17
+ const explicitLogsDir = normalizeOptionalString(process.env.RIG_LOGS_DIR);
18
+ const explicitSessionFile = normalizeOptionalString(process.env.RIG_SESSION_FILE);
19
+ const hostStateRoot = resolve(projectRoot, ".rig");
20
+ const monorepoRoot = resolveMonorepoRoot(projectRoot);
21
+ const monorepoStateRoot = resolve(monorepoRoot, ".rig");
22
+ const stateRoot = taskWorkspace ? resolve(taskWorkspace, ".rig") : explicitStateDir ? dirname(resolve(explicitStateDir)) : explicitLogsDir ? dirname(resolve(explicitLogsDir)) : explicitSessionFile ? dirname(dirname(resolve(explicitSessionFile))) : existsSync(hostStateRoot) ? hostStateRoot : monorepoStateRoot;
23
+ const stateDir = explicitStateDir ? resolve(explicitStateDir) : resolve(stateRoot, "state");
24
+ const logsDir = explicitLogsDir ? resolve(explicitLogsDir) : resolve(stateRoot, "logs");
25
+ const taskConfigPath = taskWorkspace ? resolve(taskWorkspace, ".rig", "task-config.json") : existsSync(resolve(projectRoot, ".rig", "task-config.json")) ? resolve(projectRoot, ".rig", "task-config.json") : resolve(monorepoStateRoot, "task-config.json");
26
+ return {
27
+ stateRoot,
28
+ stateDir,
29
+ logsDir,
30
+ controlPlaneEventsFile: resolve(logsDir, "control-plane.events.jsonl"),
31
+ taskConfigPath,
32
+ notificationsFile: resolve(projectRoot, "rig", "notifications", "targets.json"),
33
+ keybindingsPath: resolve(projectRoot, "rig", "keybindings.json")
34
+ };
35
+ }
36
+
37
+ // packages/server/src/inspector/journal.ts
38
+ var SCHEMA = [
39
+ `CREATE TABLE IF NOT EXISTS inspector_runs (
40
+ run_id TEXT PRIMARY KEY,
41
+ workspace_id TEXT,
42
+ task_id TEXT,
43
+ runtime_adapter TEXT,
44
+ provider TEXT,
45
+ status TEXT NOT NULL,
46
+ conflict_state TEXT NOT NULL DEFAULT 'none',
47
+ conflict_summary TEXT,
48
+ source TEXT,
49
+ title TEXT,
50
+ started_at TEXT,
51
+ updated_at TEXT NOT NULL,
52
+ completed_at TEXT
53
+ )`,
54
+ `CREATE TABLE IF NOT EXISTS inspector_observations (
55
+ id TEXT PRIMARY KEY,
56
+ run_id TEXT,
57
+ dedupe_key TEXT,
58
+ kind TEXT NOT NULL,
59
+ severity TEXT NOT NULL,
60
+ source TEXT NOT NULL,
61
+ summary TEXT NOT NULL,
62
+ details_json TEXT,
63
+ created_at TEXT NOT NULL
64
+ )`,
65
+ `CREATE TABLE IF NOT EXISTS inspector_decisions (
66
+ id TEXT PRIMARY KEY,
67
+ run_id TEXT,
68
+ dedupe_key TEXT,
69
+ decision_type TEXT NOT NULL,
70
+ summary TEXT NOT NULL,
71
+ rationale TEXT NOT NULL,
72
+ details_json TEXT,
73
+ created_at TEXT NOT NULL
74
+ )`,
75
+ `CREATE TABLE IF NOT EXISTS inspector_actions (
76
+ id TEXT PRIMARY KEY,
77
+ run_id TEXT,
78
+ dedupe_key TEXT,
79
+ action_type TEXT NOT NULL,
80
+ status TEXT NOT NULL,
81
+ target TEXT,
82
+ input_json TEXT,
83
+ result_json TEXT,
84
+ started_at TEXT NOT NULL,
85
+ completed_at TEXT
86
+ )`,
87
+ `CREATE TABLE IF NOT EXISTS inspector_reviews (
88
+ id TEXT PRIMARY KEY,
89
+ run_id TEXT,
90
+ dedupe_key TEXT,
91
+ reviewer_type TEXT NOT NULL,
92
+ status TEXT NOT NULL,
93
+ findings_json TEXT,
94
+ created_at TEXT NOT NULL
95
+ )`,
96
+ `CREATE TABLE IF NOT EXISTS inspector_followups (
97
+ id TEXT PRIMARY KEY,
98
+ origin_run_id TEXT,
99
+ dedupe_key TEXT,
100
+ task_id TEXT,
101
+ kind TEXT NOT NULL,
102
+ status TEXT NOT NULL,
103
+ summary TEXT NOT NULL,
104
+ details_json TEXT,
105
+ created_at TEXT NOT NULL
106
+ )`,
107
+ `CREATE TABLE IF NOT EXISTS upstream_sync_scans (
108
+ id TEXT PRIMARY KEY,
109
+ dedupe_key TEXT,
110
+ started_at TEXT NOT NULL,
111
+ completed_at TEXT,
112
+ status TEXT NOT NULL,
113
+ summary TEXT NOT NULL,
114
+ details_json TEXT
115
+ )`,
116
+ `CREATE TABLE IF NOT EXISTS analysis_reports (
117
+ id TEXT PRIMARY KEY,
118
+ dedupe_key TEXT,
119
+ report_type TEXT NOT NULL,
120
+ status TEXT NOT NULL,
121
+ summary TEXT NOT NULL,
122
+ details_json TEXT,
123
+ created_at TEXT NOT NULL
124
+ )`,
125
+ `CREATE UNIQUE INDEX IF NOT EXISTS idx_inspector_observations_dedupe
126
+ ON inspector_observations(dedupe_key) WHERE dedupe_key IS NOT NULL`,
127
+ `CREATE UNIQUE INDEX IF NOT EXISTS idx_inspector_decisions_dedupe
128
+ ON inspector_decisions(dedupe_key) WHERE dedupe_key IS NOT NULL`,
129
+ `CREATE UNIQUE INDEX IF NOT EXISTS idx_inspector_actions_dedupe
130
+ ON inspector_actions(dedupe_key) WHERE dedupe_key IS NOT NULL`,
131
+ `CREATE UNIQUE INDEX IF NOT EXISTS idx_inspector_reviews_dedupe
132
+ ON inspector_reviews(dedupe_key) WHERE dedupe_key IS NOT NULL`,
133
+ `CREATE UNIQUE INDEX IF NOT EXISTS idx_inspector_followups_dedupe
134
+ ON inspector_followups(dedupe_key) WHERE dedupe_key IS NOT NULL`,
135
+ `CREATE UNIQUE INDEX IF NOT EXISTS idx_upstream_sync_scans_dedupe
136
+ ON upstream_sync_scans(dedupe_key) WHERE dedupe_key IS NOT NULL`,
137
+ `CREATE UNIQUE INDEX IF NOT EXISTS idx_analysis_reports_dedupe
138
+ ON analysis_reports(dedupe_key) WHERE dedupe_key IS NOT NULL`,
139
+ `CREATE INDEX IF NOT EXISTS idx_inspector_runs_status_updated
140
+ ON inspector_runs(status, updated_at DESC)`,
141
+ `CREATE INDEX IF NOT EXISTS idx_inspector_observations_run_created
142
+ ON inspector_observations(run_id, created_at DESC)`
143
+ ];
144
+ function jsonText(value) {
145
+ return value == null ? null : JSON.stringify(value);
146
+ }
147
+ function parseJsonRecord(value) {
148
+ if (typeof value !== "string" || value.length === 0) {
149
+ return null;
150
+ }
151
+ return JSON.parse(value);
152
+ }
153
+ function normalizeError(error) {
154
+ return error instanceof Error ? error.message : String(error);
155
+ }
156
+ function safeWrite(sqlite, record, fn) {
157
+ try {
158
+ fn();
159
+ const row = sqlite.prepare("SELECT changes() AS count").get();
160
+ return { ok: true, record, changed: Number(row?.count ?? 0) > 0 };
161
+ } catch (error) {
162
+ return { ok: false, error: normalizeError(error) };
163
+ }
164
+ }
165
+ function resolveInspectorJournalPath(projectRoot) {
166
+ return resolve2(resolveRigServerPaths(projectRoot).stateDir, "inspector", "journal.sqlite");
167
+ }
168
+ function createInspectorJournal(options) {
169
+ const dbPath = resolve2(options.dbPath ?? resolveInspectorJournalPath(options.projectRoot));
170
+ mkdirSync2(dirname2(dbPath), { recursive: true });
171
+ const sqlite = new Database(dbPath, { create: true, strict: true });
172
+ sqlite.exec("PRAGMA journal_mode = WAL");
173
+ sqlite.exec("PRAGMA busy_timeout = 50");
174
+ for (const statement of SCHEMA) {
175
+ sqlite.exec(statement);
176
+ }
177
+ const upsertRunStatement = sqlite.prepare(`
178
+ INSERT INTO inspector_runs (
179
+ run_id, workspace_id, task_id, runtime_adapter, provider, status, conflict_state, conflict_summary,
180
+ source, title, started_at, updated_at, completed_at
181
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
182
+ ON CONFLICT(run_id) DO UPDATE SET
183
+ workspace_id = excluded.workspace_id,
184
+ task_id = excluded.task_id,
185
+ runtime_adapter = excluded.runtime_adapter,
186
+ provider = excluded.provider,
187
+ status = excluded.status,
188
+ conflict_state = excluded.conflict_state,
189
+ conflict_summary = excluded.conflict_summary,
190
+ source = excluded.source,
191
+ title = excluded.title,
192
+ started_at = excluded.started_at,
193
+ updated_at = excluded.updated_at,
194
+ completed_at = excluded.completed_at
195
+ `);
196
+ const observationStatement = sqlite.prepare(`
197
+ INSERT OR IGNORE INTO inspector_observations (
198
+ id, run_id, dedupe_key, kind, severity, source, summary, details_json, created_at
199
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
200
+ `);
201
+ const decisionStatement = sqlite.prepare(`
202
+ INSERT OR IGNORE INTO inspector_decisions (
203
+ id, run_id, dedupe_key, decision_type, summary, rationale, details_json, created_at
204
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
205
+ `);
206
+ const actionStatement = sqlite.prepare(`
207
+ INSERT OR IGNORE INTO inspector_actions (
208
+ id, run_id, dedupe_key, action_type, status, target, input_json, result_json, started_at, completed_at
209
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
210
+ `);
211
+ const reviewStatement = sqlite.prepare(`
212
+ INSERT OR IGNORE INTO inspector_reviews (
213
+ id, run_id, dedupe_key, reviewer_type, status, findings_json, created_at
214
+ ) VALUES (?, ?, ?, ?, ?, ?, ?)
215
+ `);
216
+ const followupStatement = sqlite.prepare(`
217
+ INSERT OR IGNORE INTO inspector_followups (
218
+ id, origin_run_id, dedupe_key, task_id, kind, status, summary, details_json, created_at
219
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
220
+ `);
221
+ const scanStatement = sqlite.prepare(`
222
+ INSERT OR IGNORE INTO upstream_sync_scans (
223
+ id, dedupe_key, started_at, completed_at, status, summary, details_json
224
+ ) VALUES (?, ?, ?, ?, ?, ?, ?)
225
+ `);
226
+ const reportStatement = sqlite.prepare(`
227
+ INSERT OR IGNORE INTO analysis_reports (
228
+ id, dedupe_key, report_type, status, summary, details_json, created_at
229
+ ) VALUES (?, ?, ?, ?, ?, ?, ?)
230
+ `);
231
+ const attachFollowupTaskStatement = sqlite.prepare(`
232
+ UPDATE inspector_followups
233
+ SET task_id = ?, status = ?, details_json = ?
234
+ WHERE dedupe_key = ?
235
+ `);
236
+ const getFollowupByDedupeKeyStatement = sqlite.prepare(`
237
+ SELECT id, origin_run_id, dedupe_key, task_id, kind, status, summary, details_json, created_at
238
+ FROM inspector_followups
239
+ WHERE dedupe_key = ?
240
+ LIMIT 1
241
+ `);
242
+ const listActiveRunsStatement = sqlite.prepare(`
243
+ SELECT
244
+ run_id,
245
+ workspace_id,
246
+ task_id,
247
+ runtime_adapter,
248
+ provider,
249
+ status,
250
+ conflict_state,
251
+ conflict_summary,
252
+ source,
253
+ title,
254
+ started_at,
255
+ updated_at,
256
+ completed_at
257
+ FROM inspector_runs
258
+ WHERE status IN ('preparing', 'running', 'validating', 'reviewing')
259
+ ORDER BY updated_at DESC
260
+ `);
261
+ const listRecentFindingsStatement = sqlite.prepare(`
262
+ SELECT id, run_id, kind, severity, source, summary, details_json, created_at
263
+ FROM inspector_observations
264
+ ORDER BY created_at DESC
265
+ LIMIT ?
266
+ `);
267
+ const listRecentDecisionsStatement = sqlite.prepare(`
268
+ SELECT id, run_id, decision_type, summary, rationale, details_json, created_at
269
+ FROM inspector_decisions
270
+ ORDER BY created_at DESC
271
+ LIMIT ?
272
+ `);
273
+ const listRecentActionsStatement = sqlite.prepare(`
274
+ SELECT id, run_id, action_type, status, target, input_json, result_json, started_at, completed_at
275
+ FROM inspector_actions
276
+ ORDER BY started_at DESC
277
+ LIMIT ?
278
+ `);
279
+ function mapFollowupRow(row) {
280
+ return {
281
+ id: String(row.id),
282
+ originRunId: row.origin_run_id == null ? null : String(row.origin_run_id),
283
+ dedupeKey: row.dedupe_key == null ? null : String(row.dedupe_key),
284
+ taskId: row.task_id == null ? null : String(row.task_id),
285
+ kind: String(row.kind),
286
+ status: String(row.status),
287
+ summary: String(row.summary),
288
+ details: parseJsonRecord(row.details_json),
289
+ createdAt: String(row.created_at)
290
+ };
291
+ }
292
+ return {
293
+ path: dbPath,
294
+ upsertRun(record) {
295
+ return safeWrite(sqlite, record, () => {
296
+ upsertRunStatement.run(record.runId, record.workspaceId, record.taskId, record.runtimeAdapter, record.provider, record.status, record.conflictState, record.conflictSummary, record.source, record.title, record.startedAt, record.updatedAt, record.completedAt);
297
+ });
298
+ },
299
+ appendObservation(record) {
300
+ return safeWrite(sqlite, record, () => {
301
+ observationStatement.run(record.id, record.runId, record.dedupeKey ?? null, record.kind, record.severity, record.source, record.summary, jsonText(record.details), record.createdAt);
302
+ });
303
+ },
304
+ appendDecision(record) {
305
+ return safeWrite(sqlite, record, () => {
306
+ decisionStatement.run(record.id, record.runId, record.dedupeKey ?? null, record.decisionType, record.summary, record.rationale, jsonText(record.details), record.createdAt);
307
+ });
308
+ },
309
+ appendAction(record) {
310
+ return safeWrite(sqlite, record, () => {
311
+ actionStatement.run(record.id, record.runId, record.dedupeKey ?? null, record.actionType, record.status, record.target, jsonText(record.input), jsonText(record.result), record.startedAt, record.completedAt);
312
+ });
313
+ },
314
+ appendReview(record) {
315
+ return safeWrite(sqlite, record, () => {
316
+ reviewStatement.run(record.id, record.runId, record.dedupeKey ?? null, record.reviewerType, record.status, jsonText(record.findings), record.createdAt);
317
+ });
318
+ },
319
+ appendFollowup(record) {
320
+ return safeWrite(sqlite, record, () => {
321
+ followupStatement.run(record.id, record.originRunId, record.dedupeKey ?? null, record.taskId, record.kind, record.status, record.summary, jsonText(record.details), record.createdAt);
322
+ });
323
+ },
324
+ attachFollowupTask(input) {
325
+ const record = {
326
+ dedupeKey: input.dedupeKey,
327
+ taskId: input.taskId,
328
+ status: input.status,
329
+ details: input.details ?? null
330
+ };
331
+ return safeWrite(sqlite, record, () => {
332
+ attachFollowupTaskStatement.run(input.taskId, input.status, jsonText(input.details), input.dedupeKey);
333
+ });
334
+ },
335
+ getFollowupByDedupeKey(dedupeKey) {
336
+ const row = getFollowupByDedupeKeyStatement.get(dedupeKey);
337
+ return row ? mapFollowupRow(row) : null;
338
+ },
339
+ appendUpstreamSyncScan(record) {
340
+ return safeWrite(sqlite, record, () => {
341
+ scanStatement.run(record.id, record.dedupeKey ?? null, record.startedAt, record.completedAt, record.status, record.summary, jsonText(record.details));
342
+ });
343
+ },
344
+ appendAnalysisReport(record) {
345
+ return safeWrite(sqlite, record, () => {
346
+ reportStatement.run(record.id, record.dedupeKey ?? null, record.reportType, record.status, record.summary, jsonText(record.details), record.createdAt);
347
+ });
348
+ },
349
+ listActiveRuns() {
350
+ return listActiveRunsStatement.all().map((row) => ({
351
+ runId: String(row.run_id),
352
+ workspaceId: row.workspace_id == null ? null : String(row.workspace_id),
353
+ taskId: row.task_id == null ? null : String(row.task_id),
354
+ runtimeAdapter: row.runtime_adapter == null ? null : String(row.runtime_adapter),
355
+ provider: row.provider == null ? null : String(row.provider),
356
+ status: String(row.status),
357
+ conflictState: String(row.conflict_state),
358
+ conflictSummary: row.conflict_summary == null ? null : String(row.conflict_summary),
359
+ source: row.source == null ? null : String(row.source),
360
+ title: row.title == null ? null : String(row.title),
361
+ startedAt: row.started_at == null ? null : String(row.started_at),
362
+ updatedAt: String(row.updated_at),
363
+ completedAt: row.completed_at == null ? null : String(row.completed_at)
364
+ }));
365
+ },
366
+ listRecentFindings(options2 = {}) {
367
+ const limit = options2.limit ?? 10;
368
+ return listRecentFindingsStatement.all(limit).map((row) => ({
369
+ id: String(row.id),
370
+ runId: row.run_id == null ? null : String(row.run_id),
371
+ kind: String(row.kind),
372
+ severity: String(row.severity),
373
+ source: String(row.source),
374
+ summary: String(row.summary),
375
+ details: parseJsonRecord(row.details_json),
376
+ createdAt: String(row.created_at)
377
+ }));
378
+ },
379
+ listRecentDecisions(options2 = {}) {
380
+ const limit = options2.limit ?? 10;
381
+ return listRecentDecisionsStatement.all(limit).map((row) => ({
382
+ id: String(row.id),
383
+ runId: row.run_id == null ? null : String(row.run_id),
384
+ decisionType: String(row.decision_type),
385
+ summary: String(row.summary),
386
+ rationale: String(row.rationale),
387
+ details: parseJsonRecord(row.details_json),
388
+ createdAt: String(row.created_at)
389
+ }));
390
+ },
391
+ listRecentActions(options2 = {}) {
392
+ const limit = options2.limit ?? 10;
393
+ return listRecentActionsStatement.all(limit).map((row) => ({
394
+ id: String(row.id),
395
+ runId: row.run_id == null ? null : String(row.run_id),
396
+ actionType: String(row.action_type),
397
+ status: String(row.status),
398
+ target: row.target == null ? null : String(row.target),
399
+ input: parseJsonRecord(row.input_json),
400
+ result: parseJsonRecord(row.result_json),
401
+ startedAt: String(row.started_at),
402
+ completedAt: row.completed_at == null ? null : String(row.completed_at)
403
+ }));
404
+ },
405
+ listDecisions(runId) {
406
+ return sqlite.prepare(`
407
+ SELECT id, run_id, decision_type, summary, rationale, details_json, created_at
408
+ FROM inspector_decisions
409
+ WHERE run_id = ?
410
+ ORDER BY created_at DESC
411
+ `).all(runId).map((row) => ({
412
+ id: String(row.id),
413
+ runId: row.run_id == null ? null : String(row.run_id),
414
+ decisionType: String(row.decision_type),
415
+ summary: String(row.summary),
416
+ rationale: String(row.rationale),
417
+ details: parseJsonRecord(row.details_json),
418
+ createdAt: String(row.created_at)
419
+ }));
420
+ },
421
+ listActions(runId) {
422
+ return sqlite.prepare(`
423
+ SELECT id, run_id, action_type, status, target, input_json, result_json, started_at, completed_at
424
+ FROM inspector_actions
425
+ WHERE run_id = ?
426
+ ORDER BY started_at DESC
427
+ `).all(runId).map((row) => ({
428
+ id: String(row.id),
429
+ runId: row.run_id == null ? null : String(row.run_id),
430
+ actionType: String(row.action_type),
431
+ status: String(row.status),
432
+ target: row.target == null ? null : String(row.target),
433
+ input: parseJsonRecord(row.input_json),
434
+ result: parseJsonRecord(row.result_json),
435
+ startedAt: String(row.started_at),
436
+ completedAt: row.completed_at == null ? null : String(row.completed_at)
437
+ }));
438
+ },
439
+ listReviews(runId) {
440
+ return sqlite.prepare(`
441
+ SELECT id, run_id, reviewer_type, status, findings_json, created_at
442
+ FROM inspector_reviews
443
+ WHERE run_id = ?
444
+ ORDER BY created_at DESC
445
+ `).all(runId).map((row) => ({
446
+ id: String(row.id),
447
+ runId: row.run_id == null ? null : String(row.run_id),
448
+ reviewerType: String(row.reviewer_type),
449
+ status: String(row.status),
450
+ findings: parseJsonRecord(row.findings_json),
451
+ createdAt: String(row.created_at)
452
+ }));
453
+ },
454
+ listFollowups() {
455
+ return sqlite.prepare(`
456
+ SELECT id, origin_run_id, dedupe_key, task_id, kind, status, summary, details_json, created_at
457
+ FROM inspector_followups
458
+ ORDER BY created_at DESC
459
+ `).all().map(mapFollowupRow);
460
+ },
461
+ listUpstreamSyncScans() {
462
+ return sqlite.prepare(`
463
+ SELECT id, started_at, completed_at, status, summary, details_json
464
+ FROM upstream_sync_scans
465
+ ORDER BY started_at DESC
466
+ `).all().map((row) => ({
467
+ id: String(row.id),
468
+ startedAt: String(row.started_at),
469
+ completedAt: row.completed_at == null ? null : String(row.completed_at),
470
+ status: String(row.status),
471
+ summary: String(row.summary),
472
+ details: parseJsonRecord(row.details_json)
473
+ }));
474
+ },
475
+ listAnalysisReports() {
476
+ return sqlite.prepare(`
477
+ SELECT id, report_type, status, summary, details_json, created_at
478
+ FROM analysis_reports
479
+ ORDER BY created_at DESC
480
+ `).all().map((row) => ({
481
+ id: String(row.id),
482
+ reportType: String(row.report_type),
483
+ status: String(row.status),
484
+ summary: String(row.summary),
485
+ details: parseJsonRecord(row.details_json),
486
+ createdAt: String(row.created_at)
487
+ }));
488
+ },
489
+ getCounts() {
490
+ const row = sqlite.prepare(`
491
+ SELECT
492
+ (SELECT COUNT(*) FROM inspector_observations) AS observations,
493
+ (SELECT COUNT(*) FROM inspector_decisions) AS decisions,
494
+ (SELECT COUNT(*) FROM inspector_actions) AS actions,
495
+ (SELECT COUNT(*) FROM inspector_followups) AS followups
496
+ `).get();
497
+ return {
498
+ observations: Number(row?.observations ?? 0),
499
+ decisions: Number(row?.decisions ?? 0),
500
+ actions: Number(row?.actions ?? 0),
501
+ followups: Number(row?.followups ?? 0)
502
+ };
503
+ },
504
+ beginExclusiveTransaction() {
505
+ sqlite.exec("BEGIN EXCLUSIVE");
506
+ },
507
+ rollbackTransaction() {
508
+ sqlite.exec("ROLLBACK");
509
+ },
510
+ close() {
511
+ sqlite.close();
512
+ }
513
+ };
514
+ }
515
+ export {
516
+ resolveInspectorJournalPath,
517
+ createInspectorJournal
518
+ };