@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.
- package/README.md +14 -0
- package/dist/src/bootstrap.js +161 -0
- package/dist/src/index.js +13153 -0
- package/dist/src/inspector/agent-runtime.js +1077 -0
- package/dist/src/inspector/analysis.js +41 -0
- package/dist/src/inspector/discovery.js +137 -0
- package/dist/src/inspector/journal.js +518 -0
- package/dist/src/inspector/mission.js +562 -0
- package/dist/src/inspector/prompt.js +97 -0
- package/dist/src/inspector/provider-session.js +65 -0
- package/dist/src/inspector/reconcile.js +118 -0
- package/dist/src/inspector/review.js +13 -0
- package/dist/src/inspector/service.js +1759 -0
- package/dist/src/inspector/skills.js +155 -0
- package/dist/src/inspector/tools.js +1592 -0
- package/dist/src/inspector/types.js +1 -0
- package/dist/src/inspector/upstream-sync.js +479 -0
- package/dist/src/orchestration.js +402 -0
- package/dist/src/remote.js +123 -0
- package/dist/src/scheduler.js +84 -0
- package/dist/src/server-helpers/broadcasters.js +161 -0
- package/dist/src/server-helpers/conversation-snapshot.js +382 -0
- package/dist/src/server-helpers/event-emitter.js +41 -0
- package/dist/src/server-helpers/github-auth-store.js +155 -0
- package/dist/src/server-helpers/github-credentials.js +38 -0
- package/dist/src/server-helpers/github-project-status-sync.js +196 -0
- package/dist/src/server-helpers/github-projects.js +147 -0
- package/dist/src/server-helpers/github-reconciler.js +89 -0
- package/dist/src/server-helpers/http-router.js +3781 -0
- package/dist/src/server-helpers/http-utils.js +135 -0
- package/dist/src/server-helpers/inspector-agent-lifecycle.js +104 -0
- package/dist/src/server-helpers/inspector-jobs.js +4145 -0
- package/dist/src/server-helpers/issue-analysis.js +362 -0
- package/dist/src/server-helpers/normalizers.js +31 -0
- package/dist/src/server-helpers/notifications.js +96 -0
- package/dist/src/server-helpers/orchestration-ops.js +287 -0
- package/dist/src/server-helpers/orchestration.js +39 -0
- package/dist/src/server-helpers/plugin-host-cache.js +86 -0
- package/dist/src/server-helpers/project-fs-ops.js +194 -0
- package/dist/src/server-helpers/project-registry.js +124 -0
- package/dist/src/server-helpers/queue-state.js +78 -0
- package/dist/src/server-helpers/remote-checkout.js +140 -0
- package/dist/src/server-helpers/remote-snapshots.js +119 -0
- package/dist/src/server-helpers/run-io.js +262 -0
- package/dist/src/server-helpers/run-mutations.js +1784 -0
- package/dist/src/server-helpers/run-steering.js +176 -0
- package/dist/src/server-helpers/run-writers.js +75 -0
- package/dist/src/server-helpers/server-paths.js +27 -0
- package/dist/src/server-helpers/snapshot-orchestrator.js +832 -0
- package/dist/src/server-helpers/snapshot-service.js +1143 -0
- package/dist/src/server-helpers/summaries.js +126 -0
- package/dist/src/server-helpers/task-config.js +50 -0
- package/dist/src/server-helpers/task-projection.js +98 -0
- package/dist/src/server-helpers/terminal-runtime.js +156 -0
- package/dist/src/server-helpers/terminal-sessions.js +22 -0
- package/dist/src/server-helpers/validation-failure.js +31 -0
- package/dist/src/server-helpers/ws-router.js +1308 -0
- package/dist/src/server.js +12628 -0
- package/dist/src/websocket.js +63 -0
- 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
|
+
};
|