@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,832 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/server/src/server-helpers/snapshot-orchestrator.ts
|
|
3
|
+
import { dirname as dirname4, resolve as resolve5 } from "path";
|
|
4
|
+
import {
|
|
5
|
+
listAuthorityRuns as listAuthorityRuns3,
|
|
6
|
+
readAuthorityRun as readAuthorityRun2
|
|
7
|
+
} from "@rig/runtime/control-plane/authority-files";
|
|
8
|
+
|
|
9
|
+
// packages/server/src/server-helpers/normalizers.ts
|
|
10
|
+
function normalizeString(value) {
|
|
11
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
12
|
+
}
|
|
13
|
+
function normalizeSequence(value) {
|
|
14
|
+
const parsed = typeof value === "number" ? value : typeof value === "string" ? Number.parseInt(value, 10) : NaN;
|
|
15
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// packages/server/src/server-helpers/summaries.ts
|
|
19
|
+
function toTaskSummary(workspaceId, task) {
|
|
20
|
+
const createdAt = task.createdAt ?? task.updatedAt ?? new Date().toISOString();
|
|
21
|
+
const updatedAt = task.updatedAt ?? task.createdAt ?? createdAt;
|
|
22
|
+
const description = task.description ?? task.acceptanceCriteria ?? "";
|
|
23
|
+
return {
|
|
24
|
+
id: task.id,
|
|
25
|
+
workspaceId,
|
|
26
|
+
graphId: null,
|
|
27
|
+
externalId: task.externalRef,
|
|
28
|
+
title: task.title,
|
|
29
|
+
description,
|
|
30
|
+
status: task.status,
|
|
31
|
+
priority: task.priority,
|
|
32
|
+
role: task.role,
|
|
33
|
+
scope: task.scope,
|
|
34
|
+
validationKeys: task.validation,
|
|
35
|
+
sourceIssueId: task.sourceIssueId,
|
|
36
|
+
dependencies: task.dependencies,
|
|
37
|
+
parentChildDeps: task.parentChildDeps,
|
|
38
|
+
metadata: {
|
|
39
|
+
acceptanceCriteria: task.acceptanceCriteria,
|
|
40
|
+
issueType: task.issueType,
|
|
41
|
+
sourceIssueId: task.sourceIssueId,
|
|
42
|
+
...task.sourceStatus ? { sourceStatus: task.sourceStatus } : {},
|
|
43
|
+
dependencies: task.dependencies,
|
|
44
|
+
parentChildDeps: task.parentChildDeps,
|
|
45
|
+
labels: task.labels
|
|
46
|
+
},
|
|
47
|
+
createdAt,
|
|
48
|
+
updatedAt
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function buildWorkspaceGraphSummary(workspace, tasks) {
|
|
52
|
+
const timestamps = [
|
|
53
|
+
workspace.updatedAt,
|
|
54
|
+
...tasks.map((task) => task.updatedAt),
|
|
55
|
+
...tasks.map((task) => task.createdAt)
|
|
56
|
+
].filter((value) => typeof value === "string" && value.length > 0);
|
|
57
|
+
const updatedAt = timestamps.toSorted().at(-1) ?? workspace.updatedAt;
|
|
58
|
+
const taskCount = tasks.length;
|
|
59
|
+
return {
|
|
60
|
+
id: `graph:${workspace.id}`,
|
|
61
|
+
workspaceId: workspace.id,
|
|
62
|
+
title: `${workspace.title} graph`,
|
|
63
|
+
description: taskCount > 0 ? `${taskCount} imported task${taskCount === 1 ? "" : "s"} from the connected Rig workspace` : "No imported tasks are available yet for this workspace",
|
|
64
|
+
createdAt: workspace.createdAt,
|
|
65
|
+
updatedAt
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function toApprovalSummary(record) {
|
|
69
|
+
const createdAt = normalizeString(record.record.createdAt) ?? normalizeString(record.record.requestedAt) ?? new Date().toISOString();
|
|
70
|
+
const resolvedAt = normalizeString(record.record.resolvedAt) ?? normalizeString(record.record.respondedAt) ?? null;
|
|
71
|
+
return {
|
|
72
|
+
id: record.requestId ?? `${record.runId}-approval-${createdAt}`,
|
|
73
|
+
runId: record.runId,
|
|
74
|
+
actionId: normalizeString(record.record.actionId),
|
|
75
|
+
requestKind: normalizeString(record.record.requestKind) ?? normalizeString(record.record.kind) ?? "approval",
|
|
76
|
+
status: record.status === "resolved" ? "resolved" : "pending",
|
|
77
|
+
payload: record.record,
|
|
78
|
+
createdAt,
|
|
79
|
+
resolvedAt
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function toUserInputSummary(record) {
|
|
83
|
+
const createdAt = normalizeString(record.record.createdAt) ?? normalizeString(record.record.requestedAt) ?? new Date().toISOString();
|
|
84
|
+
const resolvedAt = normalizeString(record.record.resolvedAt) ?? normalizeString(record.record.respondedAt) ?? null;
|
|
85
|
+
return {
|
|
86
|
+
id: record.requestId ?? `${record.runId}-input-${createdAt}`,
|
|
87
|
+
runId: record.runId,
|
|
88
|
+
status: record.status === "resolved" ? "resolved" : "pending",
|
|
89
|
+
payload: record.record,
|
|
90
|
+
createdAt,
|
|
91
|
+
resolvedAt
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function toRunSummary(run, approvals, userInputs) {
|
|
95
|
+
const pendingApprovalCount = approvals.filter((entry) => entry.status !== "resolved").length;
|
|
96
|
+
const pendingUserInputCount = userInputs.filter((entry) => entry.status !== "resolved").length;
|
|
97
|
+
const runStatus = pendingApprovalCount > 0 ? "waiting-approval" : pendingUserInputCount > 0 ? "waiting-user-input" : run.status === "waiting" ? "queued" : run.status === "preparing" ? "preparing" : run.status === "stopped" ? "cancelled" : run.status === "done" || run.status === "completed" ? "completed" : run.status === "error" || run.status === "failed" ? "failed" : run.status === "running" ? "running" : "created";
|
|
98
|
+
const runMode = normalizeString(run.runMode) === "autonomous" ? "autonomous" : normalizeString(run.runMode) === "supervised" ? "supervised" : "interactive";
|
|
99
|
+
const runtimeMode = normalizeString(run.runtimeMode) ?? "approval-required";
|
|
100
|
+
const interactionMode = normalizeString(run.interactionMode) ?? "default";
|
|
101
|
+
return {
|
|
102
|
+
id: run.runId,
|
|
103
|
+
workspaceId: run.workspaceId,
|
|
104
|
+
taskId: run.taskId,
|
|
105
|
+
title: run.title,
|
|
106
|
+
runKind: run.taskId ? "task" : "adhoc",
|
|
107
|
+
mode: runMode,
|
|
108
|
+
runtimeMode,
|
|
109
|
+
interactionMode,
|
|
110
|
+
status: runStatus,
|
|
111
|
+
runtimeAdapter: run.runtimeAdapter,
|
|
112
|
+
model: normalizeString(run.model),
|
|
113
|
+
initialPrompt: normalizeString(run.initialPrompt),
|
|
114
|
+
executionTarget: run.mode === "remote" ? "remote" : "local",
|
|
115
|
+
remoteHostId: run.hostId,
|
|
116
|
+
remoteLeaseId: run.endpointId,
|
|
117
|
+
remoteLeaseClaimedAt: null,
|
|
118
|
+
activeRuntimeId: null,
|
|
119
|
+
latestMessageId: normalizeString(run.latestMessageId),
|
|
120
|
+
pendingApprovalCount,
|
|
121
|
+
pendingUserInputCount,
|
|
122
|
+
branch: normalizeString(run.branch),
|
|
123
|
+
worktreePath: run.worktreePath,
|
|
124
|
+
errorText: normalizeString(run.errorText),
|
|
125
|
+
createdAt: run.createdAt,
|
|
126
|
+
updatedAt: run.updatedAt,
|
|
127
|
+
startedAt: run.startedAt,
|
|
128
|
+
completedAt: run.completedAt
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// packages/server/src/server-helpers/run-io.ts
|
|
133
|
+
import { dirname, resolve } from "path";
|
|
134
|
+
import { closeSync, existsSync, mkdirSync, openSync, readSync, statSync, writeFileSync } from "fs";
|
|
135
|
+
import {
|
|
136
|
+
listAuthorityRuns,
|
|
137
|
+
readAuthorityRun,
|
|
138
|
+
readJsonlFile,
|
|
139
|
+
resolveAuthorityRunDir
|
|
140
|
+
} from "@rig/runtime/control-plane/authority-files";
|
|
141
|
+
function matchesRunFilter(entry, filter) {
|
|
142
|
+
return (!filter.runId || entry.runId === filter.runId) && (!filter.taskId || entry.taskId === filter.taskId);
|
|
143
|
+
}
|
|
144
|
+
function readApprovalsForRuns(projectRoot, runs, filter = {}) {
|
|
145
|
+
return runs.filter((entry) => matchesRunFilter(entry, filter)).flatMap((entry) => readJsonlFile(resolve(resolveAuthorityRunDir(projectRoot, entry.runId), "approvals.jsonl")).flatMap((record) => {
|
|
146
|
+
if (!record || typeof record !== "object") {
|
|
147
|
+
return [];
|
|
148
|
+
}
|
|
149
|
+
const value = record;
|
|
150
|
+
return [
|
|
151
|
+
{
|
|
152
|
+
runId: entry.runId,
|
|
153
|
+
taskId: entry.taskId,
|
|
154
|
+
requestId: normalizeString(value.requestId) ?? normalizeString(value.id),
|
|
155
|
+
status: normalizeString(value.status),
|
|
156
|
+
record: value
|
|
157
|
+
}
|
|
158
|
+
];
|
|
159
|
+
}));
|
|
160
|
+
}
|
|
161
|
+
function readUserInputsForRuns(projectRoot, runs, filter = {}) {
|
|
162
|
+
return runs.filter((entry) => matchesRunFilter(entry, filter)).flatMap((entry) => readJsonlFile(resolve(resolveAuthorityRunDir(projectRoot, entry.runId), "user-input.jsonl")).flatMap((record) => {
|
|
163
|
+
if (!record || typeof record !== "object") {
|
|
164
|
+
return [];
|
|
165
|
+
}
|
|
166
|
+
const value = record;
|
|
167
|
+
return [
|
|
168
|
+
{
|
|
169
|
+
runId: entry.runId,
|
|
170
|
+
taskId: entry.taskId,
|
|
171
|
+
requestId: normalizeString(value.requestId) ?? normalizeString(value.id),
|
|
172
|
+
status: normalizeString(value.status),
|
|
173
|
+
record: value
|
|
174
|
+
}
|
|
175
|
+
];
|
|
176
|
+
}));
|
|
177
|
+
}
|
|
178
|
+
function runTimelinePath(projectRoot, runId) {
|
|
179
|
+
return resolve(resolveAuthorityRunDir(projectRoot, runId), "timeline.jsonl");
|
|
180
|
+
}
|
|
181
|
+
function runLogsPath(projectRoot, runId) {
|
|
182
|
+
return resolve(resolveAuthorityRunDir(projectRoot, runId), "logs.jsonl");
|
|
183
|
+
}
|
|
184
|
+
function parseJsonlRecords(lines) {
|
|
185
|
+
return lines.map((line) => line.trim()).filter(Boolean).flatMap((line) => {
|
|
186
|
+
try {
|
|
187
|
+
return [JSON.parse(line)];
|
|
188
|
+
} catch {
|
|
189
|
+
return [];
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
function readJsonlFileTail(path, options) {
|
|
194
|
+
const limit = Math.max(0, Math.trunc(options.limit));
|
|
195
|
+
if (limit === 0 || !existsSync(path))
|
|
196
|
+
return [];
|
|
197
|
+
const maxBytes = Math.max(1024, Math.trunc(options.maxBytes ?? 256 * 1024));
|
|
198
|
+
const size = statSync(path).size;
|
|
199
|
+
if (size === 0)
|
|
200
|
+
return [];
|
|
201
|
+
const start = Math.max(0, size - maxBytes);
|
|
202
|
+
const length = size - start;
|
|
203
|
+
const buffer = Buffer.alloc(length);
|
|
204
|
+
const fd = openSync(path, "r");
|
|
205
|
+
try {
|
|
206
|
+
readSync(fd, buffer, 0, length, start);
|
|
207
|
+
} finally {
|
|
208
|
+
closeSync(fd);
|
|
209
|
+
}
|
|
210
|
+
const text = buffer.toString("utf8");
|
|
211
|
+
const lines = text.split(/\r?\n/);
|
|
212
|
+
const completeLines = start > 0 ? lines.slice(1) : lines;
|
|
213
|
+
return parseJsonlRecords(completeLines.filter(Boolean).slice(-limit));
|
|
214
|
+
}
|
|
215
|
+
var INITIAL_RUN_LOG_TAIL_MAX_BYTES = 8 * 1024 * 1024;
|
|
216
|
+
|
|
217
|
+
// packages/server/src/server-helpers/queue-state.ts
|
|
218
|
+
import { resolve as resolve3 } from "path";
|
|
219
|
+
import { readJsonFile, writeJsonFile } from "@rig/runtime/control-plane/authority-files";
|
|
220
|
+
|
|
221
|
+
// packages/server/src/server-helpers/server-paths.ts
|
|
222
|
+
import { dirname as dirname2, resolve as resolve2 } from "path";
|
|
223
|
+
import { resolveMonorepoRoot } from "@rig/runtime/control-plane/native/utils";
|
|
224
|
+
function resolveServerAuthorityPaths(projectRoot) {
|
|
225
|
+
const taskWorkspace = process.env.RIG_TASK_WORKSPACE?.trim();
|
|
226
|
+
const explicitStateDir = process.env.RIG_STATE_DIR?.trim();
|
|
227
|
+
const explicitLogsDir = process.env.RIG_LOGS_DIR?.trim();
|
|
228
|
+
const explicitSessionFile = process.env.RIG_SESSION_FILE?.trim();
|
|
229
|
+
const monorepoRoot = resolveMonorepoRoot(projectRoot);
|
|
230
|
+
const stateRoot = taskWorkspace ? resolve2(taskWorkspace, ".rig") : explicitStateDir ? dirname2(resolve2(explicitStateDir)) : explicitLogsDir ? dirname2(resolve2(explicitLogsDir)) : explicitSessionFile ? dirname2(dirname2(resolve2(explicitSessionFile))) : resolve2(monorepoRoot, ".rig");
|
|
231
|
+
const stateDir = explicitStateDir ? resolve2(explicitStateDir) : resolve2(stateRoot, "state");
|
|
232
|
+
const logsDir = explicitLogsDir ? resolve2(explicitLogsDir) : resolve2(stateRoot, "logs");
|
|
233
|
+
const sessionFile = explicitSessionFile ? resolve2(explicitSessionFile) : resolve2(stateRoot, "session", "session.json");
|
|
234
|
+
const artifactsDir = taskWorkspace ? resolve2(taskWorkspace, "artifacts") : resolve2(monorepoRoot, "artifacts");
|
|
235
|
+
return {
|
|
236
|
+
stateRoot,
|
|
237
|
+
stateDir,
|
|
238
|
+
logsDir,
|
|
239
|
+
controlPlaneEventsFile: resolve2(logsDir, "control-plane.events.jsonl"),
|
|
240
|
+
sessionFile,
|
|
241
|
+
artifactsDir
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// packages/server/src/server-helpers/queue-state.ts
|
|
246
|
+
function resolveQueueStatePath(projectRoot) {
|
|
247
|
+
return resolve3(resolveServerAuthorityPaths(projectRoot).stateDir, "task-queue.json");
|
|
248
|
+
}
|
|
249
|
+
function readQueueState(projectRoot) {
|
|
250
|
+
const queue = readJsonFile(resolveQueueStatePath(projectRoot), null);
|
|
251
|
+
if (!Array.isArray(queue))
|
|
252
|
+
return [];
|
|
253
|
+
return queue.filter((entry) => entry && typeof entry === "object").map((entry, index) => ({
|
|
254
|
+
taskId: normalizeString(entry.taskId),
|
|
255
|
+
score: typeof entry.score === "number" ? Math.max(0, Math.trunc(entry.score)) : 0,
|
|
256
|
+
unblockCount: typeof entry.unblockCount === "number" ? Math.max(0, Math.trunc(entry.unblockCount)) : 0,
|
|
257
|
+
position: typeof entry.position === "number" ? Math.max(0, Math.trunc(entry.position)) : index
|
|
258
|
+
})).filter((entry) => Boolean(entry.taskId)).sort((left, right) => right.score - left.score || left.position - right.position).map((entry, index) => ({ ...entry, position: index }));
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// packages/server/src/server-helpers/remote-snapshots.ts
|
|
262
|
+
import { listAuthorityRemoteEndpoints } from "@rig/runtime/control-plane/authority-files";
|
|
263
|
+
function toRemoteConnectionStatus(status) {
|
|
264
|
+
switch (status) {
|
|
265
|
+
case "connected":
|
|
266
|
+
case "connecting":
|
|
267
|
+
case "authenticating":
|
|
268
|
+
case "reconnecting":
|
|
269
|
+
case "error":
|
|
270
|
+
case "disconnected":
|
|
271
|
+
return status;
|
|
272
|
+
case "ready":
|
|
273
|
+
case "busy":
|
|
274
|
+
case "degraded":
|
|
275
|
+
case "draining":
|
|
276
|
+
return "connected";
|
|
277
|
+
case "registering":
|
|
278
|
+
return "connecting";
|
|
279
|
+
case "offline":
|
|
280
|
+
case "quarantined":
|
|
281
|
+
return "error";
|
|
282
|
+
default:
|
|
283
|
+
return "disconnected";
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
function hostToRemoteEndpoint(host) {
|
|
287
|
+
let hostName = "127.0.0.1";
|
|
288
|
+
let port = 7890;
|
|
289
|
+
try {
|
|
290
|
+
const url = new URL(host.baseUrl);
|
|
291
|
+
hostName = url.hostname || hostName;
|
|
292
|
+
port = Number.parseInt(url.port || "80", 10) || port;
|
|
293
|
+
} catch {}
|
|
294
|
+
return {
|
|
295
|
+
id: host.hostId,
|
|
296
|
+
alias: host.name,
|
|
297
|
+
host: hostName,
|
|
298
|
+
port,
|
|
299
|
+
token: "",
|
|
300
|
+
tokenConfigured: false,
|
|
301
|
+
autoConnect: true,
|
|
302
|
+
addedAt: host.registeredAt,
|
|
303
|
+
lastConnectedAt: host.lastHeartbeatAt
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
function buildRemoteEndpointSnapshot(projectRoot, remoteHosts) {
|
|
307
|
+
const authorityEndpoints = listAuthorityRemoteEndpoints(projectRoot).map((entry) => ({
|
|
308
|
+
id: entry.id,
|
|
309
|
+
alias: entry.alias,
|
|
310
|
+
host: entry.host,
|
|
311
|
+
port: entry.port,
|
|
312
|
+
token: "",
|
|
313
|
+
tokenConfigured: entry.token.trim().length > 0,
|
|
314
|
+
autoConnect: entry.autoConnect,
|
|
315
|
+
addedAt: entry.addedAt,
|
|
316
|
+
lastConnectedAt: entry.lastConnectedAt
|
|
317
|
+
}));
|
|
318
|
+
const hostEndpoints = Array.from(remoteHosts.values()).map(hostToRemoteEndpoint);
|
|
319
|
+
const deduped = new Map;
|
|
320
|
+
for (const entry of [...authorityEndpoints, ...hostEndpoints]) {
|
|
321
|
+
deduped.set(entry.id, entry);
|
|
322
|
+
}
|
|
323
|
+
return Array.from(deduped.values()).sort((left, right) => left.alias.localeCompare(right.alias));
|
|
324
|
+
}
|
|
325
|
+
function buildRemoteConnectionSnapshot(remoteHosts, remoteConnections, endpoints) {
|
|
326
|
+
const connections = new Map;
|
|
327
|
+
for (const [endpointId, summary] of remoteConnections.entries()) {
|
|
328
|
+
connections.set(endpointId, summary);
|
|
329
|
+
}
|
|
330
|
+
for (const host of remoteHosts.values()) {
|
|
331
|
+
if (connections.has(host.hostId))
|
|
332
|
+
continue;
|
|
333
|
+
connections.set(host.hostId, {
|
|
334
|
+
endpointId: host.hostId,
|
|
335
|
+
status: toRemoteConnectionStatus(host.status),
|
|
336
|
+
error: host.status === "offline" || host.status === "quarantined" ? host.status : null,
|
|
337
|
+
connectedAt: host.lastHeartbeatAt,
|
|
338
|
+
tokenExpiresAt: null,
|
|
339
|
+
latencyMs: null,
|
|
340
|
+
subscribedEvents: []
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
for (const endpoint of endpoints) {
|
|
344
|
+
if (connections.has(endpoint.id))
|
|
345
|
+
continue;
|
|
346
|
+
connections.set(endpoint.id, {
|
|
347
|
+
endpointId: endpoint.id,
|
|
348
|
+
status: "disconnected",
|
|
349
|
+
error: null,
|
|
350
|
+
connectedAt: endpoint.lastConnectedAt,
|
|
351
|
+
tokenExpiresAt: null,
|
|
352
|
+
latencyMs: null,
|
|
353
|
+
subscribedEvents: []
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
return Array.from(connections.values()).sort((left, right) => String(left.endpointId).localeCompare(String(right.endpointId)));
|
|
357
|
+
}
|
|
358
|
+
function normalizeIsoTimestamp(value) {
|
|
359
|
+
const normalized = normalizeString(value);
|
|
360
|
+
if (!normalized)
|
|
361
|
+
return null;
|
|
362
|
+
const timestamp = Date.parse(normalized);
|
|
363
|
+
return Number.isFinite(timestamp) ? new Date(timestamp).toISOString() : null;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// packages/server/src/server-helpers/conversation-snapshot.ts
|
|
367
|
+
import { existsSync as existsSync2, readdirSync, statSync as statSync2 } from "fs";
|
|
368
|
+
import { dirname as dirname3, resolve as resolve4 } from "path";
|
|
369
|
+
import {
|
|
370
|
+
listAuthorityRuns as listAuthorityRuns2,
|
|
371
|
+
readJsonlFile as readJsonlFile2
|
|
372
|
+
} from "@rig/runtime/control-plane/authority-files";
|
|
373
|
+
function readClaudeSessionRecords(run) {
|
|
374
|
+
if (!run.worktreePath)
|
|
375
|
+
return [];
|
|
376
|
+
const runtimeRoot = dirname3(run.worktreePath);
|
|
377
|
+
const projectsRoot = resolve4(runtimeRoot, "home", ".claude", "projects");
|
|
378
|
+
if (!existsSync2(projectsRoot))
|
|
379
|
+
return [];
|
|
380
|
+
const candidatePaths = [];
|
|
381
|
+
const visit = (dirPath, depth) => {
|
|
382
|
+
if (depth > 2)
|
|
383
|
+
return;
|
|
384
|
+
for (const entry of readdirSync(dirPath, { withFileTypes: true })) {
|
|
385
|
+
const nextPath = resolve4(dirPath, entry.name);
|
|
386
|
+
if (entry.isDirectory()) {
|
|
387
|
+
visit(nextPath, depth + 1);
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
if (entry.isFile() && entry.name.endsWith(".jsonl")) {
|
|
391
|
+
candidatePaths.push(nextPath);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
visit(projectsRoot, 0);
|
|
396
|
+
const entries = candidatePaths.map((path) => {
|
|
397
|
+
const records2 = readJsonlFile2(path).filter((raw) => !!raw && typeof raw === "object" && !Array.isArray(raw));
|
|
398
|
+
return {
|
|
399
|
+
path,
|
|
400
|
+
mtimeMs: statSync2(path).mtimeMs,
|
|
401
|
+
records: records2
|
|
402
|
+
};
|
|
403
|
+
}).filter((entry) => {
|
|
404
|
+
const normalizedWorktree = run.worktreePath.replaceAll("\\", "/");
|
|
405
|
+
return entry.records.some((record) => {
|
|
406
|
+
const cwd = normalizeString(record.cwd)?.replaceAll("\\", "/");
|
|
407
|
+
return cwd === normalizedWorktree;
|
|
408
|
+
});
|
|
409
|
+
}).sort((left, right) => left.mtimeMs - right.mtimeMs);
|
|
410
|
+
if (entries.length === 0)
|
|
411
|
+
return [];
|
|
412
|
+
const startedAt = Date.parse(run.startedAt ?? run.createdAt);
|
|
413
|
+
const completedAt = run.completedAt ? Date.parse(run.completedAt) : Number.POSITIVE_INFINITY;
|
|
414
|
+
const lookbackMs = 60000;
|
|
415
|
+
const lookaheadMs = 5 * 60000;
|
|
416
|
+
const records = [];
|
|
417
|
+
for (const entry of entries) {
|
|
418
|
+
for (const record of entry.records) {
|
|
419
|
+
const timestamp = Date.parse(normalizeIsoTimestamp(record.timestamp) ?? "");
|
|
420
|
+
if (Number.isFinite(startedAt) && Number.isFinite(timestamp)) {
|
|
421
|
+
if (timestamp < startedAt - lookbackMs)
|
|
422
|
+
continue;
|
|
423
|
+
if (Number.isFinite(completedAt) && timestamp > completedAt + lookaheadMs)
|
|
424
|
+
continue;
|
|
425
|
+
}
|
|
426
|
+
records.push(record);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
return records.sort((left, right) => {
|
|
430
|
+
const leftAt = Date.parse(normalizeIsoTimestamp(left.timestamp) ?? "") || 0;
|
|
431
|
+
const rightAt = Date.parse(normalizeIsoTimestamp(right.timestamp) ?? "") || 0;
|
|
432
|
+
return leftAt - rightAt;
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
function buildClaudeStreamLogs(run) {
|
|
436
|
+
const sessionRecords = readClaudeSessionRecords(run);
|
|
437
|
+
if (sessionRecords.length === 0)
|
|
438
|
+
return [];
|
|
439
|
+
const logs = [];
|
|
440
|
+
const pendingToolUses = new Map;
|
|
441
|
+
for (const record of sessionRecords) {
|
|
442
|
+
const entryType = normalizeString(record.type);
|
|
443
|
+
const createdAt = normalizeIsoTimestamp(record.timestamp) ?? run.updatedAt;
|
|
444
|
+
if (entryType === "summary") {
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
if (entryType === "system" || entryType === "result") {
|
|
448
|
+
logs.push({
|
|
449
|
+
id: normalizeString(record.uuid) ?? `${run.runId}-claude-${logs.length + 1}`,
|
|
450
|
+
runId: run.runId,
|
|
451
|
+
title: entryType === "result" ? "Agent result" : "Agent session initialized",
|
|
452
|
+
detail: JSON.stringify(record),
|
|
453
|
+
tone: entryType === "result" && record.is_error === true ? "error" : "info",
|
|
454
|
+
status: run.completedAt ? "completed" : "running",
|
|
455
|
+
payload: record,
|
|
456
|
+
createdAt
|
|
457
|
+
});
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
const message = record.message;
|
|
461
|
+
if (!message || typeof message !== "object" || Array.isArray(message))
|
|
462
|
+
continue;
|
|
463
|
+
const messageRecord = message;
|
|
464
|
+
const role = normalizeString(messageRecord.role) ?? entryType ?? "assistant";
|
|
465
|
+
const content = Array.isArray(messageRecord.content) ? messageRecord.content : [];
|
|
466
|
+
for (const item of content) {
|
|
467
|
+
if (!item || typeof item !== "object" || Array.isArray(item))
|
|
468
|
+
continue;
|
|
469
|
+
const contentRecord = item;
|
|
470
|
+
const contentType = normalizeString(contentRecord.type);
|
|
471
|
+
if (contentType === "tool_use") {
|
|
472
|
+
const toolUseId = normalizeString(contentRecord.id);
|
|
473
|
+
if (!toolUseId)
|
|
474
|
+
continue;
|
|
475
|
+
pendingToolUses.set(toolUseId, {
|
|
476
|
+
id: toolUseId,
|
|
477
|
+
name: normalizeString(contentRecord.name),
|
|
478
|
+
input: contentRecord.input ?? {},
|
|
479
|
+
timestamp: createdAt,
|
|
480
|
+
record: contentRecord
|
|
481
|
+
});
|
|
482
|
+
continue;
|
|
483
|
+
}
|
|
484
|
+
if (contentType === "tool_result") {
|
|
485
|
+
const toolUseId = normalizeString(contentRecord.tool_use_id);
|
|
486
|
+
const pending = toolUseId ? pendingToolUses.get(toolUseId) ?? null : null;
|
|
487
|
+
if (pending && toolUseId) {
|
|
488
|
+
pendingToolUses.delete(toolUseId);
|
|
489
|
+
}
|
|
490
|
+
const detail2 = JSON.stringify({
|
|
491
|
+
type: "claude_tool_activity",
|
|
492
|
+
session_id: normalizeString(record.sessionId) ?? normalizeString(record.session_id),
|
|
493
|
+
uuid: normalizeString(record.uuid),
|
|
494
|
+
tool_use_id: toolUseId,
|
|
495
|
+
tool_name: pending?.name ?? null,
|
|
496
|
+
tool_input: pending?.input ?? null,
|
|
497
|
+
tool_result: contentRecord.content ?? contentRecord,
|
|
498
|
+
is_error: contentRecord.is_error === true,
|
|
499
|
+
raw: {
|
|
500
|
+
toolUse: pending?.record ?? null,
|
|
501
|
+
toolResult: contentRecord
|
|
502
|
+
}
|
|
503
|
+
});
|
|
504
|
+
logs.push({
|
|
505
|
+
id: toolUseId ? `${run.runId}-claude-tool-${toolUseId}` : normalizeString(record.uuid) ?? `${run.runId}-claude-${logs.length + 1}`,
|
|
506
|
+
runId: run.runId,
|
|
507
|
+
title: "Tool activity",
|
|
508
|
+
detail: detail2,
|
|
509
|
+
tone: contentRecord.is_error === true ? "error" : "tool",
|
|
510
|
+
status: run.completedAt ? "completed" : "running",
|
|
511
|
+
payload: {
|
|
512
|
+
toolUseId,
|
|
513
|
+
toolName: pending?.name ?? null,
|
|
514
|
+
toolInput: pending?.input ?? null,
|
|
515
|
+
content: contentRecord.content ?? null,
|
|
516
|
+
isError: contentRecord.is_error === true
|
|
517
|
+
},
|
|
518
|
+
createdAt: pending?.timestamp ?? createdAt
|
|
519
|
+
});
|
|
520
|
+
continue;
|
|
521
|
+
}
|
|
522
|
+
const detail = JSON.stringify({
|
|
523
|
+
type: role,
|
|
524
|
+
message: {
|
|
525
|
+
role,
|
|
526
|
+
content: [contentRecord]
|
|
527
|
+
},
|
|
528
|
+
session_id: normalizeString(record.sessionId) ?? normalizeString(record.session_id),
|
|
529
|
+
uuid: normalizeString(record.uuid)
|
|
530
|
+
});
|
|
531
|
+
logs.push({
|
|
532
|
+
id: normalizeString(record.uuid) ? `${normalizeString(record.uuid)}:${contentType ?? "content"}:${logs.length + 1}` : `${run.runId}-claude-${logs.length + 1}`,
|
|
533
|
+
runId: run.runId,
|
|
534
|
+
title: role === "assistant" ? "Assistant output" : role === "user" ? "Tool activity" : "Agent output",
|
|
535
|
+
detail,
|
|
536
|
+
tone: contentType === "text" ? "info" : "tool",
|
|
537
|
+
status: run.completedAt ? "completed" : "running",
|
|
538
|
+
payload: contentRecord,
|
|
539
|
+
createdAt
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
for (const pending of pendingToolUses.values()) {
|
|
544
|
+
logs.push({
|
|
545
|
+
id: `${run.runId}-claude-tool-${pending.id}`,
|
|
546
|
+
runId: run.runId,
|
|
547
|
+
title: "Tool activity",
|
|
548
|
+
detail: JSON.stringify({
|
|
549
|
+
type: "claude_tool_use",
|
|
550
|
+
tool_use_id: pending.id,
|
|
551
|
+
tool_name: pending.name,
|
|
552
|
+
tool_input: pending.input,
|
|
553
|
+
raw: pending.record
|
|
554
|
+
}),
|
|
555
|
+
tone: "tool",
|
|
556
|
+
status: "running",
|
|
557
|
+
payload: {
|
|
558
|
+
toolUseId: pending.id,
|
|
559
|
+
toolName: pending.name,
|
|
560
|
+
toolInput: pending.input
|
|
561
|
+
},
|
|
562
|
+
createdAt: pending.timestamp
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
return logs.sort((left, right) => Date.parse(left.createdAt) - Date.parse(right.createdAt));
|
|
566
|
+
}
|
|
567
|
+
var snapshotCache = new Map;
|
|
568
|
+
var RUN_LOG_SNAPSHOT_TAIL_LIMIT = 200;
|
|
569
|
+
function runListSignature(runs) {
|
|
570
|
+
return runs.slice().sort((a, b) => a.runId.localeCompare(b.runId)).map((run) => `${run.runId}|${run.updatedAt ?? ""}|${run.completedAt ?? ""}`).join(";");
|
|
571
|
+
}
|
|
572
|
+
function buildConversationSnapshot(projectRoot, options = {}) {
|
|
573
|
+
const runs = listAuthorityRuns2(projectRoot);
|
|
574
|
+
const signature = runListSignature(runs);
|
|
575
|
+
if (!options.force) {
|
|
576
|
+
const cached = snapshotCache.get(projectRoot);
|
|
577
|
+
if (cached && cached.signature === signature) {
|
|
578
|
+
return cached.snapshot;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
const conversations = [];
|
|
582
|
+
const messages = [];
|
|
583
|
+
const logs = [];
|
|
584
|
+
const actions = [];
|
|
585
|
+
for (const run of runs) {
|
|
586
|
+
const conversationId = `conversation:${run.runId}`;
|
|
587
|
+
conversations.push({
|
|
588
|
+
id: conversationId,
|
|
589
|
+
runId: run.runId,
|
|
590
|
+
title: run.title,
|
|
591
|
+
createdAt: run.createdAt,
|
|
592
|
+
updatedAt: run.updatedAt
|
|
593
|
+
});
|
|
594
|
+
const timeline = readJsonlFile2(runTimelinePath(projectRoot, run.runId));
|
|
595
|
+
for (const raw of timeline) {
|
|
596
|
+
if (!raw || typeof raw !== "object")
|
|
597
|
+
continue;
|
|
598
|
+
const entry = raw;
|
|
599
|
+
const type = normalizeString(entry.type) ?? "system_message";
|
|
600
|
+
const createdAt = normalizeString(entry.createdAt) ?? run.updatedAt;
|
|
601
|
+
if (type === "user_message" || type === "assistant_message" || type === "system_message") {
|
|
602
|
+
const role = type === "assistant_message" ? "assistant" : type === "user_message" ? "user" : "system";
|
|
603
|
+
messages.push({
|
|
604
|
+
id: normalizeString(entry.id) ?? `${run.runId}-${messages.length + 1}`,
|
|
605
|
+
conversationId,
|
|
606
|
+
role,
|
|
607
|
+
text: typeof entry.text === "string" ? entry.text : "",
|
|
608
|
+
attachments: Array.isArray(entry.attachments) ? entry.attachments : [],
|
|
609
|
+
state: normalizeString(entry.state) === "streaming" ? "streaming" : normalizeString(entry.state) === "interrupted" ? "interrupted" : normalizeString(entry.state) === "errored" ? "errored" : "completed",
|
|
610
|
+
createdAt,
|
|
611
|
+
completedAt: normalizeString(entry.completedAt) ?? (normalizeString(entry.state) === "streaming" ? null : createdAt)
|
|
612
|
+
});
|
|
613
|
+
continue;
|
|
614
|
+
}
|
|
615
|
+
if (type === "action") {
|
|
616
|
+
actions.push({
|
|
617
|
+
id: normalizeString(entry.id) ?? `${run.runId}-action-${actions.length + 1}`,
|
|
618
|
+
runId: run.runId,
|
|
619
|
+
messageId: normalizeString(entry.messageId),
|
|
620
|
+
actionType: normalizeString(entry.actionType) ?? "task",
|
|
621
|
+
title: normalizeString(entry.title) ?? "Run step",
|
|
622
|
+
detail: normalizeString(entry.detail),
|
|
623
|
+
state: normalizeString(entry.state) ?? "completed",
|
|
624
|
+
payload: entry.payload ?? {},
|
|
625
|
+
startedAt: createdAt,
|
|
626
|
+
completedAt: normalizeString(entry.completedAt) ?? null
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
const runLogs = readJsonlFileTail(runLogsPath(projectRoot, run.runId), {
|
|
631
|
+
limit: RUN_LOG_SNAPSHOT_TAIL_LIMIT,
|
|
632
|
+
maxBytes: 512 * 1024
|
|
633
|
+
});
|
|
634
|
+
const claudeStreamLogs = buildClaudeStreamLogs(run);
|
|
635
|
+
const hasClaudeStreamLogs = claudeStreamLogs.length > 0;
|
|
636
|
+
for (const raw of runLogs) {
|
|
637
|
+
if (!raw || typeof raw !== "object")
|
|
638
|
+
continue;
|
|
639
|
+
const entry = raw;
|
|
640
|
+
const createdAt = normalizeString(entry.createdAt) ?? run.updatedAt;
|
|
641
|
+
const rawDetail = normalizeString(entry.detail);
|
|
642
|
+
if (hasClaudeStreamLogs && rawDetail) {
|
|
643
|
+
try {
|
|
644
|
+
const parsed = JSON.parse(rawDetail);
|
|
645
|
+
const parsedType = normalizeString(parsed.type);
|
|
646
|
+
if (parsedType === "assistant" || parsedType === "user" || parsedType === "system" || parsedType === "result") {
|
|
647
|
+
continue;
|
|
648
|
+
}
|
|
649
|
+
} catch {}
|
|
650
|
+
}
|
|
651
|
+
logs.push({
|
|
652
|
+
id: normalizeString(entry.id) ?? `${run.runId}-log-${logs.length + 1}`,
|
|
653
|
+
runId: run.runId,
|
|
654
|
+
title: normalizeString(entry.title) ?? "Run log",
|
|
655
|
+
detail: normalizeString(entry.detail),
|
|
656
|
+
tone: normalizeString(entry.tone) === "error" ? "error" : normalizeString(entry.tone) === "tool" ? "tool" : normalizeString(entry.tone) === "thinking" ? "thinking" : "info",
|
|
657
|
+
status: normalizeString(entry.status),
|
|
658
|
+
payload: entry.payload ?? {},
|
|
659
|
+
createdAt
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
logs.push(...claudeStreamLogs);
|
|
663
|
+
}
|
|
664
|
+
logs.sort((left, right) => Date.parse(left.createdAt) - Date.parse(right.createdAt));
|
|
665
|
+
const snapshot = { conversations, messages, logs, actions };
|
|
666
|
+
snapshotCache.set(projectRoot, { signature, snapshot });
|
|
667
|
+
return snapshot;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// packages/server/src/server-helpers/snapshot-orchestrator.ts
|
|
671
|
+
async function buildTaskPrompt(projectRoot, taskId, readers) {
|
|
672
|
+
const task = (await readers.readWorkspaceTasks(projectRoot)).find((entry) => entry.id === taskId);
|
|
673
|
+
if (!task) {
|
|
674
|
+
return `Work on task ${taskId}.`;
|
|
675
|
+
}
|
|
676
|
+
const sections = [
|
|
677
|
+
`You are working on task ${task.id}: ${task.title}.`,
|
|
678
|
+
task.description ? `Description:
|
|
679
|
+
${task.description}` : null,
|
|
680
|
+
task.acceptanceCriteria ? `Acceptance criteria:
|
|
681
|
+
${task.acceptanceCriteria}` : null,
|
|
682
|
+
task.scope.length > 0 ? `Scope:
|
|
683
|
+
- ${task.scope.join(`
|
|
684
|
+
- `)}` : null,
|
|
685
|
+
task.validation.length > 0 ? `Validation:
|
|
686
|
+
- ${task.validation.join(`
|
|
687
|
+
- `)}` : null,
|
|
688
|
+
"Work directly in the assigned runtime workspace and leave the result in a reviewable state."
|
|
689
|
+
];
|
|
690
|
+
return sections.filter((value) => Boolean(value)).join(`
|
|
691
|
+
|
|
692
|
+
`);
|
|
693
|
+
}
|
|
694
|
+
async function buildRigSnapshotPayload(projectRoot, readers) {
|
|
695
|
+
const workspace = await readers.readWorkspaceSummaryRecord(projectRoot);
|
|
696
|
+
const queue = readQueueState(projectRoot);
|
|
697
|
+
const queuedTaskIds = new Set(queue.map((entry) => entry.taskId));
|
|
698
|
+
const graph = buildWorkspaceGraphSummary(workspace, []);
|
|
699
|
+
const taskSummaries = (await readers.readWorkspaceTasks(projectRoot)).map((task) => ({
|
|
700
|
+
...toTaskSummary(workspace.id, {
|
|
701
|
+
...task,
|
|
702
|
+
status: queuedTaskIds.has(task.id) && (task.status === "open" || task.status === "ready" || task.status === "draft") ? "queued" : task.status
|
|
703
|
+
}),
|
|
704
|
+
graphId: graph.id
|
|
705
|
+
}));
|
|
706
|
+
const populatedGraph = buildWorkspaceGraphSummary(workspace, taskSummaries);
|
|
707
|
+
const authorityRuns = listAuthorityRuns3(projectRoot);
|
|
708
|
+
const approvalRecords = readApprovalsForRuns(projectRoot, authorityRuns);
|
|
709
|
+
const userInputRecords = readUserInputsForRuns(projectRoot, authorityRuns);
|
|
710
|
+
const approvals = approvalRecords.map(toApprovalSummary);
|
|
711
|
+
const userInputs = userInputRecords.map(toUserInputSummary);
|
|
712
|
+
const runs = authorityRuns.map((run) => toRunSummary(run, approvalRecords.filter((entry) => entry.runId === run.runId), userInputRecords.filter((entry) => entry.runId === run.runId)));
|
|
713
|
+
const artifacts = await readers.listArtifactSummaries(projectRoot, null, { knownRuns: authorityRuns });
|
|
714
|
+
return {
|
|
715
|
+
workspace,
|
|
716
|
+
snapshot: {
|
|
717
|
+
workspaces: [workspace],
|
|
718
|
+
graphs: [populatedGraph],
|
|
719
|
+
tasks: taskSummaries,
|
|
720
|
+
runs,
|
|
721
|
+
approvals,
|
|
722
|
+
userInputs,
|
|
723
|
+
artifacts,
|
|
724
|
+
queue,
|
|
725
|
+
updatedAt: workspace.updatedAt
|
|
726
|
+
},
|
|
727
|
+
approvals,
|
|
728
|
+
userInputs,
|
|
729
|
+
graphs: [populatedGraph],
|
|
730
|
+
tasks: taskSummaries,
|
|
731
|
+
runs,
|
|
732
|
+
artifacts,
|
|
733
|
+
queue
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
function buildRuntimeSnapshot(inputs) {
|
|
737
|
+
const { projectRoot, runProcesses, agentRuntimes } = inputs;
|
|
738
|
+
const runtimes = [];
|
|
739
|
+
const worktrees = [];
|
|
740
|
+
const runtimesById = new Map(agentRuntimes.map((runtime) => [runtime.id, runtime]));
|
|
741
|
+
for (const processState of runProcesses.values()) {
|
|
742
|
+
const run = readAuthorityRun2(projectRoot, processState.runId);
|
|
743
|
+
if (!run)
|
|
744
|
+
continue;
|
|
745
|
+
const runtimeId = normalizeString(run.branch);
|
|
746
|
+
const runtime = runtimeId ? runtimesById.get(runtimeId) ?? null : null;
|
|
747
|
+
const updatedAt = run.updatedAt;
|
|
748
|
+
const workspaceDir = runtime?.workspaceDir ?? run.worktreePath ?? null;
|
|
749
|
+
const logsDir = runtime?.logsDir ?? (workspaceDir ? resolve5(workspaceDir, ".rig", "logs") : run.logRoot);
|
|
750
|
+
const stateDir = runtime?.stateDir ?? (workspaceDir ? resolve5(workspaceDir, ".rig", "state") : null);
|
|
751
|
+
const sessionDir = runtime?.sessionDir ?? (workspaceDir ? resolve5(workspaceDir, ".rig", "session") : run.sessionPath ? dirname4(run.sessionPath) : null);
|
|
752
|
+
const sessionLogPath = runtime?.sessionDir || workspaceDir ? logsDir ? resolve5(logsDir, "session.log") : null : run.sessionLogPath;
|
|
753
|
+
runtimes.push({
|
|
754
|
+
id: runtimeId ?? `runtime:${run.runId}`,
|
|
755
|
+
workspaceId: run.workspaceId,
|
|
756
|
+
runId: run.runId,
|
|
757
|
+
adapterKind: run.runtimeAdapter,
|
|
758
|
+
executionTarget: run.mode === "remote" ? "remote" : "local",
|
|
759
|
+
remoteHostId: run.hostId,
|
|
760
|
+
status: run.status === "failed" ? "failed" : run.status === "stopped" ? "interrupted" : run.status === "running" ? "running" : run.status === "preparing" ? "starting" : "exited",
|
|
761
|
+
sandboxMode: normalizeString(run.runtimeMode) === "full-access" ? "danger-full-access" : "workspace-write",
|
|
762
|
+
isolationMode: "worktree",
|
|
763
|
+
workspaceDir,
|
|
764
|
+
homeDir: runtime?.homeDir ?? null,
|
|
765
|
+
tmpDir: runtime?.tmpDir ?? null,
|
|
766
|
+
cacheDir: runtime?.cacheDir ?? null,
|
|
767
|
+
logsDir,
|
|
768
|
+
stateDir,
|
|
769
|
+
sessionDir,
|
|
770
|
+
sessionLogPath,
|
|
771
|
+
pid: processState.child?.pid ?? null,
|
|
772
|
+
startedAt: run.startedAt,
|
|
773
|
+
updatedAt,
|
|
774
|
+
exitedAt: run.completedAt
|
|
775
|
+
});
|
|
776
|
+
if (workspaceDir) {
|
|
777
|
+
worktrees.push({
|
|
778
|
+
id: `worktree:${run.runId}`,
|
|
779
|
+
workspaceId: run.workspaceId,
|
|
780
|
+
runId: run.runId,
|
|
781
|
+
taskId: run.taskId,
|
|
782
|
+
branchName: runtimeId ?? `run:${run.runId}`,
|
|
783
|
+
path: workspaceDir,
|
|
784
|
+
status: run.completedAt ? "preserved" : "active",
|
|
785
|
+
createdAt: run.startedAt ?? run.createdAt,
|
|
786
|
+
cleanedAt: null
|
|
787
|
+
});
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
return { runtimes, worktrees };
|
|
791
|
+
}
|
|
792
|
+
async function buildEngineSnapshotPayload(inputs) {
|
|
793
|
+
const payload = await buildRigSnapshotPayload(inputs.projectRoot, inputs.readers);
|
|
794
|
+
const { conversations, messages, logs, actions } = buildConversationSnapshot(inputs.projectRoot);
|
|
795
|
+
const { runtimes, worktrees } = buildRuntimeSnapshot({
|
|
796
|
+
projectRoot: inputs.projectRoot,
|
|
797
|
+
runProcesses: inputs.runProcesses,
|
|
798
|
+
agentRuntimes: inputs.agentRuntimes
|
|
799
|
+
});
|
|
800
|
+
const remoteEndpoints = buildRemoteEndpointSnapshot(inputs.projectRoot, inputs.remoteHosts);
|
|
801
|
+
const remoteConnections = buildRemoteConnectionSnapshot(inputs.remoteHosts, inputs.remoteConnections, remoteEndpoints);
|
|
802
|
+
return {
|
|
803
|
+
snapshotSequence: normalizeSequence(inputs.sequence) ?? 0,
|
|
804
|
+
workspaces: payload.snapshot.workspaces,
|
|
805
|
+
graphs: payload.snapshot.graphs,
|
|
806
|
+
tasks: payload.snapshot.tasks,
|
|
807
|
+
runs: payload.snapshot.runs,
|
|
808
|
+
runtimes,
|
|
809
|
+
conversations,
|
|
810
|
+
messages,
|
|
811
|
+
actions,
|
|
812
|
+
logs,
|
|
813
|
+
approvals: payload.snapshot.approvals,
|
|
814
|
+
userInputs: payload.snapshot.userInputs,
|
|
815
|
+
validations: [],
|
|
816
|
+
reviews: [],
|
|
817
|
+
artifacts: payload.snapshot.artifacts,
|
|
818
|
+
policyDecisions: [],
|
|
819
|
+
queue: payload.queue,
|
|
820
|
+
worktrees,
|
|
821
|
+
remoteEndpoints,
|
|
822
|
+
remoteConnections,
|
|
823
|
+
remoteOrchestrations: [],
|
|
824
|
+
updatedAt: payload.snapshot.updatedAt
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
export {
|
|
828
|
+
buildTaskPrompt,
|
|
829
|
+
buildRuntimeSnapshot,
|
|
830
|
+
buildRigSnapshotPayload,
|
|
831
|
+
buildEngineSnapshotPayload
|
|
832
|
+
};
|