@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,1143 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
var __require = import.meta.require;
|
|
3
|
+
|
|
4
|
+
// packages/server/src/server-helpers/snapshot-service.ts
|
|
5
|
+
import { listAgentRuntimes } from "@rig/runtime/control-plane/runtime/isolation";
|
|
6
|
+
|
|
7
|
+
// packages/server/src/server-helpers/snapshot-orchestrator.ts
|
|
8
|
+
import { dirname as dirname4, resolve as resolve5 } from "path";
|
|
9
|
+
import {
|
|
10
|
+
listAuthorityRuns as listAuthorityRuns3,
|
|
11
|
+
readAuthorityRun as readAuthorityRun2
|
|
12
|
+
} from "@rig/runtime/control-plane/authority-files";
|
|
13
|
+
|
|
14
|
+
// packages/server/src/server-helpers/normalizers.ts
|
|
15
|
+
function normalizeString(value) {
|
|
16
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
17
|
+
}
|
|
18
|
+
function normalizeSequence(value) {
|
|
19
|
+
const parsed = typeof value === "number" ? value : typeof value === "string" ? Number.parseInt(value, 10) : NaN;
|
|
20
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// packages/server/src/server-helpers/summaries.ts
|
|
24
|
+
function toTaskSummary(workspaceId, task) {
|
|
25
|
+
const createdAt = task.createdAt ?? task.updatedAt ?? new Date().toISOString();
|
|
26
|
+
const updatedAt = task.updatedAt ?? task.createdAt ?? createdAt;
|
|
27
|
+
const description = task.description ?? task.acceptanceCriteria ?? "";
|
|
28
|
+
return {
|
|
29
|
+
id: task.id,
|
|
30
|
+
workspaceId,
|
|
31
|
+
graphId: null,
|
|
32
|
+
externalId: task.externalRef,
|
|
33
|
+
title: task.title,
|
|
34
|
+
description,
|
|
35
|
+
status: task.status,
|
|
36
|
+
priority: task.priority,
|
|
37
|
+
role: task.role,
|
|
38
|
+
scope: task.scope,
|
|
39
|
+
validationKeys: task.validation,
|
|
40
|
+
sourceIssueId: task.sourceIssueId,
|
|
41
|
+
dependencies: task.dependencies,
|
|
42
|
+
parentChildDeps: task.parentChildDeps,
|
|
43
|
+
metadata: {
|
|
44
|
+
acceptanceCriteria: task.acceptanceCriteria,
|
|
45
|
+
issueType: task.issueType,
|
|
46
|
+
sourceIssueId: task.sourceIssueId,
|
|
47
|
+
...task.sourceStatus ? { sourceStatus: task.sourceStatus } : {},
|
|
48
|
+
dependencies: task.dependencies,
|
|
49
|
+
parentChildDeps: task.parentChildDeps,
|
|
50
|
+
labels: task.labels
|
|
51
|
+
},
|
|
52
|
+
createdAt,
|
|
53
|
+
updatedAt
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function buildWorkspaceGraphSummary(workspace, tasks) {
|
|
57
|
+
const timestamps = [
|
|
58
|
+
workspace.updatedAt,
|
|
59
|
+
...tasks.map((task) => task.updatedAt),
|
|
60
|
+
...tasks.map((task) => task.createdAt)
|
|
61
|
+
].filter((value) => typeof value === "string" && value.length > 0);
|
|
62
|
+
const updatedAt = timestamps.toSorted().at(-1) ?? workspace.updatedAt;
|
|
63
|
+
const taskCount = tasks.length;
|
|
64
|
+
return {
|
|
65
|
+
id: `graph:${workspace.id}`,
|
|
66
|
+
workspaceId: workspace.id,
|
|
67
|
+
title: `${workspace.title} graph`,
|
|
68
|
+
description: taskCount > 0 ? `${taskCount} imported task${taskCount === 1 ? "" : "s"} from the connected Rig workspace` : "No imported tasks are available yet for this workspace",
|
|
69
|
+
createdAt: workspace.createdAt,
|
|
70
|
+
updatedAt
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function toApprovalSummary(record) {
|
|
74
|
+
const createdAt = normalizeString(record.record.createdAt) ?? normalizeString(record.record.requestedAt) ?? new Date().toISOString();
|
|
75
|
+
const resolvedAt = normalizeString(record.record.resolvedAt) ?? normalizeString(record.record.respondedAt) ?? null;
|
|
76
|
+
return {
|
|
77
|
+
id: record.requestId ?? `${record.runId}-approval-${createdAt}`,
|
|
78
|
+
runId: record.runId,
|
|
79
|
+
actionId: normalizeString(record.record.actionId),
|
|
80
|
+
requestKind: normalizeString(record.record.requestKind) ?? normalizeString(record.record.kind) ?? "approval",
|
|
81
|
+
status: record.status === "resolved" ? "resolved" : "pending",
|
|
82
|
+
payload: record.record,
|
|
83
|
+
createdAt,
|
|
84
|
+
resolvedAt
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function toUserInputSummary(record) {
|
|
88
|
+
const createdAt = normalizeString(record.record.createdAt) ?? normalizeString(record.record.requestedAt) ?? new Date().toISOString();
|
|
89
|
+
const resolvedAt = normalizeString(record.record.resolvedAt) ?? normalizeString(record.record.respondedAt) ?? null;
|
|
90
|
+
return {
|
|
91
|
+
id: record.requestId ?? `${record.runId}-input-${createdAt}`,
|
|
92
|
+
runId: record.runId,
|
|
93
|
+
status: record.status === "resolved" ? "resolved" : "pending",
|
|
94
|
+
payload: record.record,
|
|
95
|
+
createdAt,
|
|
96
|
+
resolvedAt
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function toRunSummary(run, approvals, userInputs) {
|
|
100
|
+
const pendingApprovalCount = approvals.filter((entry) => entry.status !== "resolved").length;
|
|
101
|
+
const pendingUserInputCount = userInputs.filter((entry) => entry.status !== "resolved").length;
|
|
102
|
+
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";
|
|
103
|
+
const runMode = normalizeString(run.runMode) === "autonomous" ? "autonomous" : normalizeString(run.runMode) === "supervised" ? "supervised" : "interactive";
|
|
104
|
+
const runtimeMode = normalizeString(run.runtimeMode) ?? "approval-required";
|
|
105
|
+
const interactionMode = normalizeString(run.interactionMode) ?? "default";
|
|
106
|
+
return {
|
|
107
|
+
id: run.runId,
|
|
108
|
+
workspaceId: run.workspaceId,
|
|
109
|
+
taskId: run.taskId,
|
|
110
|
+
title: run.title,
|
|
111
|
+
runKind: run.taskId ? "task" : "adhoc",
|
|
112
|
+
mode: runMode,
|
|
113
|
+
runtimeMode,
|
|
114
|
+
interactionMode,
|
|
115
|
+
status: runStatus,
|
|
116
|
+
runtimeAdapter: run.runtimeAdapter,
|
|
117
|
+
model: normalizeString(run.model),
|
|
118
|
+
initialPrompt: normalizeString(run.initialPrompt),
|
|
119
|
+
executionTarget: run.mode === "remote" ? "remote" : "local",
|
|
120
|
+
remoteHostId: run.hostId,
|
|
121
|
+
remoteLeaseId: run.endpointId,
|
|
122
|
+
remoteLeaseClaimedAt: null,
|
|
123
|
+
activeRuntimeId: null,
|
|
124
|
+
latestMessageId: normalizeString(run.latestMessageId),
|
|
125
|
+
pendingApprovalCount,
|
|
126
|
+
pendingUserInputCount,
|
|
127
|
+
branch: normalizeString(run.branch),
|
|
128
|
+
worktreePath: run.worktreePath,
|
|
129
|
+
errorText: normalizeString(run.errorText),
|
|
130
|
+
createdAt: run.createdAt,
|
|
131
|
+
updatedAt: run.updatedAt,
|
|
132
|
+
startedAt: run.startedAt,
|
|
133
|
+
completedAt: run.completedAt
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// packages/server/src/server-helpers/run-io.ts
|
|
138
|
+
import { dirname, resolve } from "path";
|
|
139
|
+
import { closeSync, existsSync, mkdirSync, openSync, readSync, statSync, writeFileSync } from "fs";
|
|
140
|
+
import {
|
|
141
|
+
listAuthorityRuns,
|
|
142
|
+
readAuthorityRun,
|
|
143
|
+
readJsonlFile,
|
|
144
|
+
resolveAuthorityRunDir
|
|
145
|
+
} from "@rig/runtime/control-plane/authority-files";
|
|
146
|
+
function matchesRunFilter(entry, filter) {
|
|
147
|
+
return (!filter.runId || entry.runId === filter.runId) && (!filter.taskId || entry.taskId === filter.taskId);
|
|
148
|
+
}
|
|
149
|
+
function readApprovalsForRuns(projectRoot, runs, filter = {}) {
|
|
150
|
+
return runs.filter((entry) => matchesRunFilter(entry, filter)).flatMap((entry) => readJsonlFile(resolve(resolveAuthorityRunDir(projectRoot, entry.runId), "approvals.jsonl")).flatMap((record) => {
|
|
151
|
+
if (!record || typeof record !== "object") {
|
|
152
|
+
return [];
|
|
153
|
+
}
|
|
154
|
+
const value = record;
|
|
155
|
+
return [
|
|
156
|
+
{
|
|
157
|
+
runId: entry.runId,
|
|
158
|
+
taskId: entry.taskId,
|
|
159
|
+
requestId: normalizeString(value.requestId) ?? normalizeString(value.id),
|
|
160
|
+
status: normalizeString(value.status),
|
|
161
|
+
record: value
|
|
162
|
+
}
|
|
163
|
+
];
|
|
164
|
+
}));
|
|
165
|
+
}
|
|
166
|
+
function readUserInputsForRuns(projectRoot, runs, filter = {}) {
|
|
167
|
+
return runs.filter((entry) => matchesRunFilter(entry, filter)).flatMap((entry) => readJsonlFile(resolve(resolveAuthorityRunDir(projectRoot, entry.runId), "user-input.jsonl")).flatMap((record) => {
|
|
168
|
+
if (!record || typeof record !== "object") {
|
|
169
|
+
return [];
|
|
170
|
+
}
|
|
171
|
+
const value = record;
|
|
172
|
+
return [
|
|
173
|
+
{
|
|
174
|
+
runId: entry.runId,
|
|
175
|
+
taskId: entry.taskId,
|
|
176
|
+
requestId: normalizeString(value.requestId) ?? normalizeString(value.id),
|
|
177
|
+
status: normalizeString(value.status),
|
|
178
|
+
record: value
|
|
179
|
+
}
|
|
180
|
+
];
|
|
181
|
+
}));
|
|
182
|
+
}
|
|
183
|
+
function runTimelinePath(projectRoot, runId) {
|
|
184
|
+
return resolve(resolveAuthorityRunDir(projectRoot, runId), "timeline.jsonl");
|
|
185
|
+
}
|
|
186
|
+
function runLogsPath(projectRoot, runId) {
|
|
187
|
+
return resolve(resolveAuthorityRunDir(projectRoot, runId), "logs.jsonl");
|
|
188
|
+
}
|
|
189
|
+
function parseJsonlRecords(lines) {
|
|
190
|
+
return lines.map((line) => line.trim()).filter(Boolean).flatMap((line) => {
|
|
191
|
+
try {
|
|
192
|
+
return [JSON.parse(line)];
|
|
193
|
+
} catch {
|
|
194
|
+
return [];
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
function readJsonlFileTail(path, options) {
|
|
199
|
+
const limit = Math.max(0, Math.trunc(options.limit));
|
|
200
|
+
if (limit === 0 || !existsSync(path))
|
|
201
|
+
return [];
|
|
202
|
+
const maxBytes = Math.max(1024, Math.trunc(options.maxBytes ?? 256 * 1024));
|
|
203
|
+
const size = statSync(path).size;
|
|
204
|
+
if (size === 0)
|
|
205
|
+
return [];
|
|
206
|
+
const start = Math.max(0, size - maxBytes);
|
|
207
|
+
const length = size - start;
|
|
208
|
+
const buffer = Buffer.alloc(length);
|
|
209
|
+
const fd = openSync(path, "r");
|
|
210
|
+
try {
|
|
211
|
+
readSync(fd, buffer, 0, length, start);
|
|
212
|
+
} finally {
|
|
213
|
+
closeSync(fd);
|
|
214
|
+
}
|
|
215
|
+
const text = buffer.toString("utf8");
|
|
216
|
+
const lines = text.split(/\r?\n/);
|
|
217
|
+
const completeLines = start > 0 ? lines.slice(1) : lines;
|
|
218
|
+
return parseJsonlRecords(completeLines.filter(Boolean).slice(-limit));
|
|
219
|
+
}
|
|
220
|
+
var INITIAL_RUN_LOG_TAIL_MAX_BYTES = 8 * 1024 * 1024;
|
|
221
|
+
|
|
222
|
+
// packages/server/src/server-helpers/queue-state.ts
|
|
223
|
+
import { resolve as resolve3 } from "path";
|
|
224
|
+
import { readJsonFile, writeJsonFile } from "@rig/runtime/control-plane/authority-files";
|
|
225
|
+
|
|
226
|
+
// packages/server/src/server-helpers/server-paths.ts
|
|
227
|
+
import { dirname as dirname2, resolve as resolve2 } from "path";
|
|
228
|
+
import { resolveMonorepoRoot } from "@rig/runtime/control-plane/native/utils";
|
|
229
|
+
function resolveServerAuthorityPaths(projectRoot) {
|
|
230
|
+
const taskWorkspace = process.env.RIG_TASK_WORKSPACE?.trim();
|
|
231
|
+
const explicitStateDir = process.env.RIG_STATE_DIR?.trim();
|
|
232
|
+
const explicitLogsDir = process.env.RIG_LOGS_DIR?.trim();
|
|
233
|
+
const explicitSessionFile = process.env.RIG_SESSION_FILE?.trim();
|
|
234
|
+
const monorepoRoot = resolveMonorepoRoot(projectRoot);
|
|
235
|
+
const stateRoot = taskWorkspace ? resolve2(taskWorkspace, ".rig") : explicitStateDir ? dirname2(resolve2(explicitStateDir)) : explicitLogsDir ? dirname2(resolve2(explicitLogsDir)) : explicitSessionFile ? dirname2(dirname2(resolve2(explicitSessionFile))) : resolve2(monorepoRoot, ".rig");
|
|
236
|
+
const stateDir = explicitStateDir ? resolve2(explicitStateDir) : resolve2(stateRoot, "state");
|
|
237
|
+
const logsDir = explicitLogsDir ? resolve2(explicitLogsDir) : resolve2(stateRoot, "logs");
|
|
238
|
+
const sessionFile = explicitSessionFile ? resolve2(explicitSessionFile) : resolve2(stateRoot, "session", "session.json");
|
|
239
|
+
const artifactsDir = taskWorkspace ? resolve2(taskWorkspace, "artifacts") : resolve2(monorepoRoot, "artifacts");
|
|
240
|
+
return {
|
|
241
|
+
stateRoot,
|
|
242
|
+
stateDir,
|
|
243
|
+
logsDir,
|
|
244
|
+
controlPlaneEventsFile: resolve2(logsDir, "control-plane.events.jsonl"),
|
|
245
|
+
sessionFile,
|
|
246
|
+
artifactsDir
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// packages/server/src/server-helpers/queue-state.ts
|
|
251
|
+
function resolveQueueStatePath(projectRoot) {
|
|
252
|
+
return resolve3(resolveServerAuthorityPaths(projectRoot).stateDir, "task-queue.json");
|
|
253
|
+
}
|
|
254
|
+
function readQueueState(projectRoot) {
|
|
255
|
+
const queue = readJsonFile(resolveQueueStatePath(projectRoot), null);
|
|
256
|
+
if (!Array.isArray(queue))
|
|
257
|
+
return [];
|
|
258
|
+
return queue.filter((entry) => entry && typeof entry === "object").map((entry, index) => ({
|
|
259
|
+
taskId: normalizeString(entry.taskId),
|
|
260
|
+
score: typeof entry.score === "number" ? Math.max(0, Math.trunc(entry.score)) : 0,
|
|
261
|
+
unblockCount: typeof entry.unblockCount === "number" ? Math.max(0, Math.trunc(entry.unblockCount)) : 0,
|
|
262
|
+
position: typeof entry.position === "number" ? Math.max(0, Math.trunc(entry.position)) : index
|
|
263
|
+
})).filter((entry) => Boolean(entry.taskId)).sort((left, right) => right.score - left.score || left.position - right.position).map((entry, index) => ({ ...entry, position: index }));
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// packages/server/src/server-helpers/remote-snapshots.ts
|
|
267
|
+
import { listAuthorityRemoteEndpoints } from "@rig/runtime/control-plane/authority-files";
|
|
268
|
+
function toRemoteConnectionStatus(status) {
|
|
269
|
+
switch (status) {
|
|
270
|
+
case "connected":
|
|
271
|
+
case "connecting":
|
|
272
|
+
case "authenticating":
|
|
273
|
+
case "reconnecting":
|
|
274
|
+
case "error":
|
|
275
|
+
case "disconnected":
|
|
276
|
+
return status;
|
|
277
|
+
case "ready":
|
|
278
|
+
case "busy":
|
|
279
|
+
case "degraded":
|
|
280
|
+
case "draining":
|
|
281
|
+
return "connected";
|
|
282
|
+
case "registering":
|
|
283
|
+
return "connecting";
|
|
284
|
+
case "offline":
|
|
285
|
+
case "quarantined":
|
|
286
|
+
return "error";
|
|
287
|
+
default:
|
|
288
|
+
return "disconnected";
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
function hostToRemoteEndpoint(host) {
|
|
292
|
+
let hostName = "127.0.0.1";
|
|
293
|
+
let port = 7890;
|
|
294
|
+
try {
|
|
295
|
+
const url = new URL(host.baseUrl);
|
|
296
|
+
hostName = url.hostname || hostName;
|
|
297
|
+
port = Number.parseInt(url.port || "80", 10) || port;
|
|
298
|
+
} catch {}
|
|
299
|
+
return {
|
|
300
|
+
id: host.hostId,
|
|
301
|
+
alias: host.name,
|
|
302
|
+
host: hostName,
|
|
303
|
+
port,
|
|
304
|
+
token: "",
|
|
305
|
+
tokenConfigured: false,
|
|
306
|
+
autoConnect: true,
|
|
307
|
+
addedAt: host.registeredAt,
|
|
308
|
+
lastConnectedAt: host.lastHeartbeatAt
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
function buildRemoteEndpointSnapshot(projectRoot, remoteHosts) {
|
|
312
|
+
const authorityEndpoints = listAuthorityRemoteEndpoints(projectRoot).map((entry) => ({
|
|
313
|
+
id: entry.id,
|
|
314
|
+
alias: entry.alias,
|
|
315
|
+
host: entry.host,
|
|
316
|
+
port: entry.port,
|
|
317
|
+
token: "",
|
|
318
|
+
tokenConfigured: entry.token.trim().length > 0,
|
|
319
|
+
autoConnect: entry.autoConnect,
|
|
320
|
+
addedAt: entry.addedAt,
|
|
321
|
+
lastConnectedAt: entry.lastConnectedAt
|
|
322
|
+
}));
|
|
323
|
+
const hostEndpoints = Array.from(remoteHosts.values()).map(hostToRemoteEndpoint);
|
|
324
|
+
const deduped = new Map;
|
|
325
|
+
for (const entry of [...authorityEndpoints, ...hostEndpoints]) {
|
|
326
|
+
deduped.set(entry.id, entry);
|
|
327
|
+
}
|
|
328
|
+
return Array.from(deduped.values()).sort((left, right) => left.alias.localeCompare(right.alias));
|
|
329
|
+
}
|
|
330
|
+
function buildRemoteConnectionSnapshot(remoteHosts, remoteConnections, endpoints) {
|
|
331
|
+
const connections = new Map;
|
|
332
|
+
for (const [endpointId, summary] of remoteConnections.entries()) {
|
|
333
|
+
connections.set(endpointId, summary);
|
|
334
|
+
}
|
|
335
|
+
for (const host of remoteHosts.values()) {
|
|
336
|
+
if (connections.has(host.hostId))
|
|
337
|
+
continue;
|
|
338
|
+
connections.set(host.hostId, {
|
|
339
|
+
endpointId: host.hostId,
|
|
340
|
+
status: toRemoteConnectionStatus(host.status),
|
|
341
|
+
error: host.status === "offline" || host.status === "quarantined" ? host.status : null,
|
|
342
|
+
connectedAt: host.lastHeartbeatAt,
|
|
343
|
+
tokenExpiresAt: null,
|
|
344
|
+
latencyMs: null,
|
|
345
|
+
subscribedEvents: []
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
for (const endpoint of endpoints) {
|
|
349
|
+
if (connections.has(endpoint.id))
|
|
350
|
+
continue;
|
|
351
|
+
connections.set(endpoint.id, {
|
|
352
|
+
endpointId: endpoint.id,
|
|
353
|
+
status: "disconnected",
|
|
354
|
+
error: null,
|
|
355
|
+
connectedAt: endpoint.lastConnectedAt,
|
|
356
|
+
tokenExpiresAt: null,
|
|
357
|
+
latencyMs: null,
|
|
358
|
+
subscribedEvents: []
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
return Array.from(connections.values()).sort((left, right) => String(left.endpointId).localeCompare(String(right.endpointId)));
|
|
362
|
+
}
|
|
363
|
+
function normalizeIsoTimestamp(value) {
|
|
364
|
+
const normalized = normalizeString(value);
|
|
365
|
+
if (!normalized)
|
|
366
|
+
return null;
|
|
367
|
+
const timestamp = Date.parse(normalized);
|
|
368
|
+
return Number.isFinite(timestamp) ? new Date(timestamp).toISOString() : null;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// packages/server/src/server-helpers/conversation-snapshot.ts
|
|
372
|
+
import { existsSync as existsSync2, readdirSync, statSync as statSync2 } from "fs";
|
|
373
|
+
import { dirname as dirname3, resolve as resolve4 } from "path";
|
|
374
|
+
import {
|
|
375
|
+
listAuthorityRuns as listAuthorityRuns2,
|
|
376
|
+
readJsonlFile as readJsonlFile2
|
|
377
|
+
} from "@rig/runtime/control-plane/authority-files";
|
|
378
|
+
function readClaudeSessionRecords(run) {
|
|
379
|
+
if (!run.worktreePath)
|
|
380
|
+
return [];
|
|
381
|
+
const runtimeRoot = dirname3(run.worktreePath);
|
|
382
|
+
const projectsRoot = resolve4(runtimeRoot, "home", ".claude", "projects");
|
|
383
|
+
if (!existsSync2(projectsRoot))
|
|
384
|
+
return [];
|
|
385
|
+
const candidatePaths = [];
|
|
386
|
+
const visit = (dirPath, depth) => {
|
|
387
|
+
if (depth > 2)
|
|
388
|
+
return;
|
|
389
|
+
for (const entry of readdirSync(dirPath, { withFileTypes: true })) {
|
|
390
|
+
const nextPath = resolve4(dirPath, entry.name);
|
|
391
|
+
if (entry.isDirectory()) {
|
|
392
|
+
visit(nextPath, depth + 1);
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
if (entry.isFile() && entry.name.endsWith(".jsonl")) {
|
|
396
|
+
candidatePaths.push(nextPath);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
visit(projectsRoot, 0);
|
|
401
|
+
const entries = candidatePaths.map((path) => {
|
|
402
|
+
const records2 = readJsonlFile2(path).filter((raw) => !!raw && typeof raw === "object" && !Array.isArray(raw));
|
|
403
|
+
return {
|
|
404
|
+
path,
|
|
405
|
+
mtimeMs: statSync2(path).mtimeMs,
|
|
406
|
+
records: records2
|
|
407
|
+
};
|
|
408
|
+
}).filter((entry) => {
|
|
409
|
+
const normalizedWorktree = run.worktreePath.replaceAll("\\", "/");
|
|
410
|
+
return entry.records.some((record) => {
|
|
411
|
+
const cwd = normalizeString(record.cwd)?.replaceAll("\\", "/");
|
|
412
|
+
return cwd === normalizedWorktree;
|
|
413
|
+
});
|
|
414
|
+
}).sort((left, right) => left.mtimeMs - right.mtimeMs);
|
|
415
|
+
if (entries.length === 0)
|
|
416
|
+
return [];
|
|
417
|
+
const startedAt = Date.parse(run.startedAt ?? run.createdAt);
|
|
418
|
+
const completedAt = run.completedAt ? Date.parse(run.completedAt) : Number.POSITIVE_INFINITY;
|
|
419
|
+
const lookbackMs = 60000;
|
|
420
|
+
const lookaheadMs = 5 * 60000;
|
|
421
|
+
const records = [];
|
|
422
|
+
for (const entry of entries) {
|
|
423
|
+
for (const record of entry.records) {
|
|
424
|
+
const timestamp = Date.parse(normalizeIsoTimestamp(record.timestamp) ?? "");
|
|
425
|
+
if (Number.isFinite(startedAt) && Number.isFinite(timestamp)) {
|
|
426
|
+
if (timestamp < startedAt - lookbackMs)
|
|
427
|
+
continue;
|
|
428
|
+
if (Number.isFinite(completedAt) && timestamp > completedAt + lookaheadMs)
|
|
429
|
+
continue;
|
|
430
|
+
}
|
|
431
|
+
records.push(record);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
return records.sort((left, right) => {
|
|
435
|
+
const leftAt = Date.parse(normalizeIsoTimestamp(left.timestamp) ?? "") || 0;
|
|
436
|
+
const rightAt = Date.parse(normalizeIsoTimestamp(right.timestamp) ?? "") || 0;
|
|
437
|
+
return leftAt - rightAt;
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
function buildClaudeStreamLogs(run) {
|
|
441
|
+
const sessionRecords = readClaudeSessionRecords(run);
|
|
442
|
+
if (sessionRecords.length === 0)
|
|
443
|
+
return [];
|
|
444
|
+
const logs = [];
|
|
445
|
+
const pendingToolUses = new Map;
|
|
446
|
+
for (const record of sessionRecords) {
|
|
447
|
+
const entryType = normalizeString(record.type);
|
|
448
|
+
const createdAt = normalizeIsoTimestamp(record.timestamp) ?? run.updatedAt;
|
|
449
|
+
if (entryType === "summary") {
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
if (entryType === "system" || entryType === "result") {
|
|
453
|
+
logs.push({
|
|
454
|
+
id: normalizeString(record.uuid) ?? `${run.runId}-claude-${logs.length + 1}`,
|
|
455
|
+
runId: run.runId,
|
|
456
|
+
title: entryType === "result" ? "Agent result" : "Agent session initialized",
|
|
457
|
+
detail: JSON.stringify(record),
|
|
458
|
+
tone: entryType === "result" && record.is_error === true ? "error" : "info",
|
|
459
|
+
status: run.completedAt ? "completed" : "running",
|
|
460
|
+
payload: record,
|
|
461
|
+
createdAt
|
|
462
|
+
});
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
const message = record.message;
|
|
466
|
+
if (!message || typeof message !== "object" || Array.isArray(message))
|
|
467
|
+
continue;
|
|
468
|
+
const messageRecord = message;
|
|
469
|
+
const role = normalizeString(messageRecord.role) ?? entryType ?? "assistant";
|
|
470
|
+
const content = Array.isArray(messageRecord.content) ? messageRecord.content : [];
|
|
471
|
+
for (const item of content) {
|
|
472
|
+
if (!item || typeof item !== "object" || Array.isArray(item))
|
|
473
|
+
continue;
|
|
474
|
+
const contentRecord = item;
|
|
475
|
+
const contentType = normalizeString(contentRecord.type);
|
|
476
|
+
if (contentType === "tool_use") {
|
|
477
|
+
const toolUseId = normalizeString(contentRecord.id);
|
|
478
|
+
if (!toolUseId)
|
|
479
|
+
continue;
|
|
480
|
+
pendingToolUses.set(toolUseId, {
|
|
481
|
+
id: toolUseId,
|
|
482
|
+
name: normalizeString(contentRecord.name),
|
|
483
|
+
input: contentRecord.input ?? {},
|
|
484
|
+
timestamp: createdAt,
|
|
485
|
+
record: contentRecord
|
|
486
|
+
});
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
489
|
+
if (contentType === "tool_result") {
|
|
490
|
+
const toolUseId = normalizeString(contentRecord.tool_use_id);
|
|
491
|
+
const pending = toolUseId ? pendingToolUses.get(toolUseId) ?? null : null;
|
|
492
|
+
if (pending && toolUseId) {
|
|
493
|
+
pendingToolUses.delete(toolUseId);
|
|
494
|
+
}
|
|
495
|
+
const detail2 = JSON.stringify({
|
|
496
|
+
type: "claude_tool_activity",
|
|
497
|
+
session_id: normalizeString(record.sessionId) ?? normalizeString(record.session_id),
|
|
498
|
+
uuid: normalizeString(record.uuid),
|
|
499
|
+
tool_use_id: toolUseId,
|
|
500
|
+
tool_name: pending?.name ?? null,
|
|
501
|
+
tool_input: pending?.input ?? null,
|
|
502
|
+
tool_result: contentRecord.content ?? contentRecord,
|
|
503
|
+
is_error: contentRecord.is_error === true,
|
|
504
|
+
raw: {
|
|
505
|
+
toolUse: pending?.record ?? null,
|
|
506
|
+
toolResult: contentRecord
|
|
507
|
+
}
|
|
508
|
+
});
|
|
509
|
+
logs.push({
|
|
510
|
+
id: toolUseId ? `${run.runId}-claude-tool-${toolUseId}` : normalizeString(record.uuid) ?? `${run.runId}-claude-${logs.length + 1}`,
|
|
511
|
+
runId: run.runId,
|
|
512
|
+
title: "Tool activity",
|
|
513
|
+
detail: detail2,
|
|
514
|
+
tone: contentRecord.is_error === true ? "error" : "tool",
|
|
515
|
+
status: run.completedAt ? "completed" : "running",
|
|
516
|
+
payload: {
|
|
517
|
+
toolUseId,
|
|
518
|
+
toolName: pending?.name ?? null,
|
|
519
|
+
toolInput: pending?.input ?? null,
|
|
520
|
+
content: contentRecord.content ?? null,
|
|
521
|
+
isError: contentRecord.is_error === true
|
|
522
|
+
},
|
|
523
|
+
createdAt: pending?.timestamp ?? createdAt
|
|
524
|
+
});
|
|
525
|
+
continue;
|
|
526
|
+
}
|
|
527
|
+
const detail = JSON.stringify({
|
|
528
|
+
type: role,
|
|
529
|
+
message: {
|
|
530
|
+
role,
|
|
531
|
+
content: [contentRecord]
|
|
532
|
+
},
|
|
533
|
+
session_id: normalizeString(record.sessionId) ?? normalizeString(record.session_id),
|
|
534
|
+
uuid: normalizeString(record.uuid)
|
|
535
|
+
});
|
|
536
|
+
logs.push({
|
|
537
|
+
id: normalizeString(record.uuid) ? `${normalizeString(record.uuid)}:${contentType ?? "content"}:${logs.length + 1}` : `${run.runId}-claude-${logs.length + 1}`,
|
|
538
|
+
runId: run.runId,
|
|
539
|
+
title: role === "assistant" ? "Assistant output" : role === "user" ? "Tool activity" : "Agent output",
|
|
540
|
+
detail,
|
|
541
|
+
tone: contentType === "text" ? "info" : "tool",
|
|
542
|
+
status: run.completedAt ? "completed" : "running",
|
|
543
|
+
payload: contentRecord,
|
|
544
|
+
createdAt
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
for (const pending of pendingToolUses.values()) {
|
|
549
|
+
logs.push({
|
|
550
|
+
id: `${run.runId}-claude-tool-${pending.id}`,
|
|
551
|
+
runId: run.runId,
|
|
552
|
+
title: "Tool activity",
|
|
553
|
+
detail: JSON.stringify({
|
|
554
|
+
type: "claude_tool_use",
|
|
555
|
+
tool_use_id: pending.id,
|
|
556
|
+
tool_name: pending.name,
|
|
557
|
+
tool_input: pending.input,
|
|
558
|
+
raw: pending.record
|
|
559
|
+
}),
|
|
560
|
+
tone: "tool",
|
|
561
|
+
status: "running",
|
|
562
|
+
payload: {
|
|
563
|
+
toolUseId: pending.id,
|
|
564
|
+
toolName: pending.name,
|
|
565
|
+
toolInput: pending.input
|
|
566
|
+
},
|
|
567
|
+
createdAt: pending.timestamp
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
return logs.sort((left, right) => Date.parse(left.createdAt) - Date.parse(right.createdAt));
|
|
571
|
+
}
|
|
572
|
+
var snapshotCache = new Map;
|
|
573
|
+
var RUN_LOG_SNAPSHOT_TAIL_LIMIT = 200;
|
|
574
|
+
function runListSignature(runs) {
|
|
575
|
+
return runs.slice().sort((a, b) => a.runId.localeCompare(b.runId)).map((run) => `${run.runId}|${run.updatedAt ?? ""}|${run.completedAt ?? ""}`).join(";");
|
|
576
|
+
}
|
|
577
|
+
function invalidateConversationSnapshotCache(projectRoot) {
|
|
578
|
+
if (projectRoot) {
|
|
579
|
+
snapshotCache.delete(projectRoot);
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
snapshotCache.clear();
|
|
583
|
+
}
|
|
584
|
+
function buildConversationSnapshot(projectRoot, options = {}) {
|
|
585
|
+
const runs = listAuthorityRuns2(projectRoot);
|
|
586
|
+
const signature = runListSignature(runs);
|
|
587
|
+
if (!options.force) {
|
|
588
|
+
const cached = snapshotCache.get(projectRoot);
|
|
589
|
+
if (cached && cached.signature === signature) {
|
|
590
|
+
return cached.snapshot;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
const conversations = [];
|
|
594
|
+
const messages = [];
|
|
595
|
+
const logs = [];
|
|
596
|
+
const actions = [];
|
|
597
|
+
for (const run of runs) {
|
|
598
|
+
const conversationId = `conversation:${run.runId}`;
|
|
599
|
+
conversations.push({
|
|
600
|
+
id: conversationId,
|
|
601
|
+
runId: run.runId,
|
|
602
|
+
title: run.title,
|
|
603
|
+
createdAt: run.createdAt,
|
|
604
|
+
updatedAt: run.updatedAt
|
|
605
|
+
});
|
|
606
|
+
const timeline = readJsonlFile2(runTimelinePath(projectRoot, run.runId));
|
|
607
|
+
for (const raw of timeline) {
|
|
608
|
+
if (!raw || typeof raw !== "object")
|
|
609
|
+
continue;
|
|
610
|
+
const entry = raw;
|
|
611
|
+
const type = normalizeString(entry.type) ?? "system_message";
|
|
612
|
+
const createdAt = normalizeString(entry.createdAt) ?? run.updatedAt;
|
|
613
|
+
if (type === "user_message" || type === "assistant_message" || type === "system_message") {
|
|
614
|
+
const role = type === "assistant_message" ? "assistant" : type === "user_message" ? "user" : "system";
|
|
615
|
+
messages.push({
|
|
616
|
+
id: normalizeString(entry.id) ?? `${run.runId}-${messages.length + 1}`,
|
|
617
|
+
conversationId,
|
|
618
|
+
role,
|
|
619
|
+
text: typeof entry.text === "string" ? entry.text : "",
|
|
620
|
+
attachments: Array.isArray(entry.attachments) ? entry.attachments : [],
|
|
621
|
+
state: normalizeString(entry.state) === "streaming" ? "streaming" : normalizeString(entry.state) === "interrupted" ? "interrupted" : normalizeString(entry.state) === "errored" ? "errored" : "completed",
|
|
622
|
+
createdAt,
|
|
623
|
+
completedAt: normalizeString(entry.completedAt) ?? (normalizeString(entry.state) === "streaming" ? null : createdAt)
|
|
624
|
+
});
|
|
625
|
+
continue;
|
|
626
|
+
}
|
|
627
|
+
if (type === "action") {
|
|
628
|
+
actions.push({
|
|
629
|
+
id: normalizeString(entry.id) ?? `${run.runId}-action-${actions.length + 1}`,
|
|
630
|
+
runId: run.runId,
|
|
631
|
+
messageId: normalizeString(entry.messageId),
|
|
632
|
+
actionType: normalizeString(entry.actionType) ?? "task",
|
|
633
|
+
title: normalizeString(entry.title) ?? "Run step",
|
|
634
|
+
detail: normalizeString(entry.detail),
|
|
635
|
+
state: normalizeString(entry.state) ?? "completed",
|
|
636
|
+
payload: entry.payload ?? {},
|
|
637
|
+
startedAt: createdAt,
|
|
638
|
+
completedAt: normalizeString(entry.completedAt) ?? null
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
const runLogs = readJsonlFileTail(runLogsPath(projectRoot, run.runId), {
|
|
643
|
+
limit: RUN_LOG_SNAPSHOT_TAIL_LIMIT,
|
|
644
|
+
maxBytes: 512 * 1024
|
|
645
|
+
});
|
|
646
|
+
const claudeStreamLogs = buildClaudeStreamLogs(run);
|
|
647
|
+
const hasClaudeStreamLogs = claudeStreamLogs.length > 0;
|
|
648
|
+
for (const raw of runLogs) {
|
|
649
|
+
if (!raw || typeof raw !== "object")
|
|
650
|
+
continue;
|
|
651
|
+
const entry = raw;
|
|
652
|
+
const createdAt = normalizeString(entry.createdAt) ?? run.updatedAt;
|
|
653
|
+
const rawDetail = normalizeString(entry.detail);
|
|
654
|
+
if (hasClaudeStreamLogs && rawDetail) {
|
|
655
|
+
try {
|
|
656
|
+
const parsed = JSON.parse(rawDetail);
|
|
657
|
+
const parsedType = normalizeString(parsed.type);
|
|
658
|
+
if (parsedType === "assistant" || parsedType === "user" || parsedType === "system" || parsedType === "result") {
|
|
659
|
+
continue;
|
|
660
|
+
}
|
|
661
|
+
} catch {}
|
|
662
|
+
}
|
|
663
|
+
logs.push({
|
|
664
|
+
id: normalizeString(entry.id) ?? `${run.runId}-log-${logs.length + 1}`,
|
|
665
|
+
runId: run.runId,
|
|
666
|
+
title: normalizeString(entry.title) ?? "Run log",
|
|
667
|
+
detail: normalizeString(entry.detail),
|
|
668
|
+
tone: normalizeString(entry.tone) === "error" ? "error" : normalizeString(entry.tone) === "tool" ? "tool" : normalizeString(entry.tone) === "thinking" ? "thinking" : "info",
|
|
669
|
+
status: normalizeString(entry.status),
|
|
670
|
+
payload: entry.payload ?? {},
|
|
671
|
+
createdAt
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
logs.push(...claudeStreamLogs);
|
|
675
|
+
}
|
|
676
|
+
logs.sort((left, right) => Date.parse(left.createdAt) - Date.parse(right.createdAt));
|
|
677
|
+
const snapshot = { conversations, messages, logs, actions };
|
|
678
|
+
snapshotCache.set(projectRoot, { signature, snapshot });
|
|
679
|
+
return snapshot;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// packages/server/src/server-helpers/snapshot-orchestrator.ts
|
|
683
|
+
async function buildTaskPrompt(projectRoot, taskId, readers) {
|
|
684
|
+
const task = (await readers.readWorkspaceTasks(projectRoot)).find((entry) => entry.id === taskId);
|
|
685
|
+
if (!task) {
|
|
686
|
+
return `Work on task ${taskId}.`;
|
|
687
|
+
}
|
|
688
|
+
const sections = [
|
|
689
|
+
`You are working on task ${task.id}: ${task.title}.`,
|
|
690
|
+
task.description ? `Description:
|
|
691
|
+
${task.description}` : null,
|
|
692
|
+
task.acceptanceCriteria ? `Acceptance criteria:
|
|
693
|
+
${task.acceptanceCriteria}` : null,
|
|
694
|
+
task.scope.length > 0 ? `Scope:
|
|
695
|
+
- ${task.scope.join(`
|
|
696
|
+
- `)}` : null,
|
|
697
|
+
task.validation.length > 0 ? `Validation:
|
|
698
|
+
- ${task.validation.join(`
|
|
699
|
+
- `)}` : null,
|
|
700
|
+
"Work directly in the assigned runtime workspace and leave the result in a reviewable state."
|
|
701
|
+
];
|
|
702
|
+
return sections.filter((value) => Boolean(value)).join(`
|
|
703
|
+
|
|
704
|
+
`);
|
|
705
|
+
}
|
|
706
|
+
async function buildRigSnapshotPayload(projectRoot, readers) {
|
|
707
|
+
const workspace = await readers.readWorkspaceSummaryRecord(projectRoot);
|
|
708
|
+
const queue = readQueueState(projectRoot);
|
|
709
|
+
const queuedTaskIds = new Set(queue.map((entry) => entry.taskId));
|
|
710
|
+
const graph = buildWorkspaceGraphSummary(workspace, []);
|
|
711
|
+
const taskSummaries = (await readers.readWorkspaceTasks(projectRoot)).map((task) => ({
|
|
712
|
+
...toTaskSummary(workspace.id, {
|
|
713
|
+
...task,
|
|
714
|
+
status: queuedTaskIds.has(task.id) && (task.status === "open" || task.status === "ready" || task.status === "draft") ? "queued" : task.status
|
|
715
|
+
}),
|
|
716
|
+
graphId: graph.id
|
|
717
|
+
}));
|
|
718
|
+
const populatedGraph = buildWorkspaceGraphSummary(workspace, taskSummaries);
|
|
719
|
+
const authorityRuns = listAuthorityRuns3(projectRoot);
|
|
720
|
+
const approvalRecords = readApprovalsForRuns(projectRoot, authorityRuns);
|
|
721
|
+
const userInputRecords = readUserInputsForRuns(projectRoot, authorityRuns);
|
|
722
|
+
const approvals = approvalRecords.map(toApprovalSummary);
|
|
723
|
+
const userInputs = userInputRecords.map(toUserInputSummary);
|
|
724
|
+
const runs = authorityRuns.map((run) => toRunSummary(run, approvalRecords.filter((entry) => entry.runId === run.runId), userInputRecords.filter((entry) => entry.runId === run.runId)));
|
|
725
|
+
const artifacts = await readers.listArtifactSummaries(projectRoot, null, { knownRuns: authorityRuns });
|
|
726
|
+
return {
|
|
727
|
+
workspace,
|
|
728
|
+
snapshot: {
|
|
729
|
+
workspaces: [workspace],
|
|
730
|
+
graphs: [populatedGraph],
|
|
731
|
+
tasks: taskSummaries,
|
|
732
|
+
runs,
|
|
733
|
+
approvals,
|
|
734
|
+
userInputs,
|
|
735
|
+
artifacts,
|
|
736
|
+
queue,
|
|
737
|
+
updatedAt: workspace.updatedAt
|
|
738
|
+
},
|
|
739
|
+
approvals,
|
|
740
|
+
userInputs,
|
|
741
|
+
graphs: [populatedGraph],
|
|
742
|
+
tasks: taskSummaries,
|
|
743
|
+
runs,
|
|
744
|
+
artifacts,
|
|
745
|
+
queue
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
function buildRuntimeSnapshot(inputs) {
|
|
749
|
+
const { projectRoot, runProcesses, agentRuntimes } = inputs;
|
|
750
|
+
const runtimes = [];
|
|
751
|
+
const worktrees = [];
|
|
752
|
+
const runtimesById = new Map(agentRuntimes.map((runtime) => [runtime.id, runtime]));
|
|
753
|
+
for (const processState of runProcesses.values()) {
|
|
754
|
+
const run = readAuthorityRun2(projectRoot, processState.runId);
|
|
755
|
+
if (!run)
|
|
756
|
+
continue;
|
|
757
|
+
const runtimeId = normalizeString(run.branch);
|
|
758
|
+
const runtime = runtimeId ? runtimesById.get(runtimeId) ?? null : null;
|
|
759
|
+
const updatedAt = run.updatedAt;
|
|
760
|
+
const workspaceDir = runtime?.workspaceDir ?? run.worktreePath ?? null;
|
|
761
|
+
const logsDir = runtime?.logsDir ?? (workspaceDir ? resolve5(workspaceDir, ".rig", "logs") : run.logRoot);
|
|
762
|
+
const stateDir = runtime?.stateDir ?? (workspaceDir ? resolve5(workspaceDir, ".rig", "state") : null);
|
|
763
|
+
const sessionDir = runtime?.sessionDir ?? (workspaceDir ? resolve5(workspaceDir, ".rig", "session") : run.sessionPath ? dirname4(run.sessionPath) : null);
|
|
764
|
+
const sessionLogPath = runtime?.sessionDir || workspaceDir ? logsDir ? resolve5(logsDir, "session.log") : null : run.sessionLogPath;
|
|
765
|
+
runtimes.push({
|
|
766
|
+
id: runtimeId ?? `runtime:${run.runId}`,
|
|
767
|
+
workspaceId: run.workspaceId,
|
|
768
|
+
runId: run.runId,
|
|
769
|
+
adapterKind: run.runtimeAdapter,
|
|
770
|
+
executionTarget: run.mode === "remote" ? "remote" : "local",
|
|
771
|
+
remoteHostId: run.hostId,
|
|
772
|
+
status: run.status === "failed" ? "failed" : run.status === "stopped" ? "interrupted" : run.status === "running" ? "running" : run.status === "preparing" ? "starting" : "exited",
|
|
773
|
+
sandboxMode: normalizeString(run.runtimeMode) === "full-access" ? "danger-full-access" : "workspace-write",
|
|
774
|
+
isolationMode: "worktree",
|
|
775
|
+
workspaceDir,
|
|
776
|
+
homeDir: runtime?.homeDir ?? null,
|
|
777
|
+
tmpDir: runtime?.tmpDir ?? null,
|
|
778
|
+
cacheDir: runtime?.cacheDir ?? null,
|
|
779
|
+
logsDir,
|
|
780
|
+
stateDir,
|
|
781
|
+
sessionDir,
|
|
782
|
+
sessionLogPath,
|
|
783
|
+
pid: processState.child?.pid ?? null,
|
|
784
|
+
startedAt: run.startedAt,
|
|
785
|
+
updatedAt,
|
|
786
|
+
exitedAt: run.completedAt
|
|
787
|
+
});
|
|
788
|
+
if (workspaceDir) {
|
|
789
|
+
worktrees.push({
|
|
790
|
+
id: `worktree:${run.runId}`,
|
|
791
|
+
workspaceId: run.workspaceId,
|
|
792
|
+
runId: run.runId,
|
|
793
|
+
taskId: run.taskId,
|
|
794
|
+
branchName: runtimeId ?? `run:${run.runId}`,
|
|
795
|
+
path: workspaceDir,
|
|
796
|
+
status: run.completedAt ? "preserved" : "active",
|
|
797
|
+
createdAt: run.startedAt ?? run.createdAt,
|
|
798
|
+
cleanedAt: null
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
return { runtimes, worktrees };
|
|
803
|
+
}
|
|
804
|
+
async function buildEngineSnapshotPayload(inputs) {
|
|
805
|
+
const payload = await buildRigSnapshotPayload(inputs.projectRoot, inputs.readers);
|
|
806
|
+
const { conversations, messages, logs, actions } = buildConversationSnapshot(inputs.projectRoot);
|
|
807
|
+
const { runtimes, worktrees } = buildRuntimeSnapshot({
|
|
808
|
+
projectRoot: inputs.projectRoot,
|
|
809
|
+
runProcesses: inputs.runProcesses,
|
|
810
|
+
agentRuntimes: inputs.agentRuntimes
|
|
811
|
+
});
|
|
812
|
+
const remoteEndpoints = buildRemoteEndpointSnapshot(inputs.projectRoot, inputs.remoteHosts);
|
|
813
|
+
const remoteConnections = buildRemoteConnectionSnapshot(inputs.remoteHosts, inputs.remoteConnections, remoteEndpoints);
|
|
814
|
+
return {
|
|
815
|
+
snapshotSequence: normalizeSequence(inputs.sequence) ?? 0,
|
|
816
|
+
workspaces: payload.snapshot.workspaces,
|
|
817
|
+
graphs: payload.snapshot.graphs,
|
|
818
|
+
tasks: payload.snapshot.tasks,
|
|
819
|
+
runs: payload.snapshot.runs,
|
|
820
|
+
runtimes,
|
|
821
|
+
conversations,
|
|
822
|
+
messages,
|
|
823
|
+
actions,
|
|
824
|
+
logs,
|
|
825
|
+
approvals: payload.snapshot.approvals,
|
|
826
|
+
userInputs: payload.snapshot.userInputs,
|
|
827
|
+
validations: [],
|
|
828
|
+
reviews: [],
|
|
829
|
+
artifacts: payload.snapshot.artifacts,
|
|
830
|
+
policyDecisions: [],
|
|
831
|
+
queue: payload.queue,
|
|
832
|
+
worktrees,
|
|
833
|
+
remoteEndpoints,
|
|
834
|
+
remoteConnections,
|
|
835
|
+
remoteOrchestrations: [],
|
|
836
|
+
updatedAt: payload.snapshot.updatedAt
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
// packages/server/src/server-helpers/plugin-host-cache.ts
|
|
841
|
+
import { existsSync as existsSync3, statSync as statSync3 } from "fs";
|
|
842
|
+
import { resolve as resolve6 } from "path";
|
|
843
|
+
var contextCache = new Map;
|
|
844
|
+
var taskListCache = new Map;
|
|
845
|
+
var DEFAULT_TASK_LIST_TTL_MS = 2000;
|
|
846
|
+
function getPluginHostConfigMtime(projectRoot) {
|
|
847
|
+
for (const name of ["rig.config.ts", "rig.config.json"]) {
|
|
848
|
+
const path = resolve6(projectRoot, name);
|
|
849
|
+
if (existsSync3(path)) {
|
|
850
|
+
try {
|
|
851
|
+
return statSync3(path).mtimeMs;
|
|
852
|
+
} catch {
|
|
853
|
+
return null;
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
return null;
|
|
858
|
+
}
|
|
859
|
+
async function getCachedPluginHostContext(projectRoot) {
|
|
860
|
+
const mtimeMs = getPluginHostConfigMtime(projectRoot);
|
|
861
|
+
if (mtimeMs === null) {
|
|
862
|
+
contextCache.delete(projectRoot);
|
|
863
|
+
return null;
|
|
864
|
+
}
|
|
865
|
+
const cached = contextCache.get(projectRoot);
|
|
866
|
+
if (cached && cached.mtimeMs === mtimeMs && cached.ctx) {
|
|
867
|
+
return cached.ctx;
|
|
868
|
+
}
|
|
869
|
+
try {
|
|
870
|
+
const { buildPluginHostContext } = await import("@rig/runtime/control-plane/plugin-host-context");
|
|
871
|
+
const ctx = await buildPluginHostContext(projectRoot);
|
|
872
|
+
if (!ctx) {
|
|
873
|
+
contextCache.delete(projectRoot);
|
|
874
|
+
return null;
|
|
875
|
+
}
|
|
876
|
+
contextCache.set(projectRoot, { mtimeMs, ctx });
|
|
877
|
+
return ctx;
|
|
878
|
+
} catch (err) {
|
|
879
|
+
contextCache.delete(projectRoot);
|
|
880
|
+
throw err;
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
async function getCachedTaskSourceList(projectRoot, options = {}) {
|
|
884
|
+
const ctx = await getCachedPluginHostContext(projectRoot);
|
|
885
|
+
if (!ctx)
|
|
886
|
+
return null;
|
|
887
|
+
const sources = ctx.taskSourceRegistry.list();
|
|
888
|
+
if (sources.length === 0)
|
|
889
|
+
return null;
|
|
890
|
+
const source = sources[0];
|
|
891
|
+
const cacheKey = `${projectRoot}::${source.kind}`;
|
|
892
|
+
const ttl = Math.max(0, options.ttlMs ?? DEFAULT_TASK_LIST_TTL_MS);
|
|
893
|
+
const now = Date.now();
|
|
894
|
+
if (!options.force) {
|
|
895
|
+
const cached = taskListCache.get(cacheKey);
|
|
896
|
+
if (cached && cached.expiresAt > now) {
|
|
897
|
+
return { kind: source.kind, tasks: cached.tasks };
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
const tasks = await source.list();
|
|
901
|
+
taskListCache.set(cacheKey, { expiresAt: now + ttl, tasks });
|
|
902
|
+
return { kind: source.kind, tasks };
|
|
903
|
+
}
|
|
904
|
+
function invalidatePluginHostCache(projectRoot) {
|
|
905
|
+
contextCache.delete(projectRoot);
|
|
906
|
+
for (const key of taskListCache.keys()) {
|
|
907
|
+
if (key.startsWith(`${projectRoot}::`)) {
|
|
908
|
+
taskListCache.delete(key);
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
// packages/server/src/server-helpers/snapshot-service.ts
|
|
914
|
+
var DEFAULT_TASK_SOURCE_TTL_MS = 2000;
|
|
915
|
+
function errorMessage(error) {
|
|
916
|
+
return error instanceof Error ? error.message : String(error);
|
|
917
|
+
}
|
|
918
|
+
function errorName(error) {
|
|
919
|
+
return error instanceof Error ? error.name : null;
|
|
920
|
+
}
|
|
921
|
+
function toTaskSourceError(input) {
|
|
922
|
+
return {
|
|
923
|
+
source: input.source,
|
|
924
|
+
reason: input.reason,
|
|
925
|
+
projectRoot: input.projectRoot,
|
|
926
|
+
kind: input.kind,
|
|
927
|
+
message: errorMessage(input.error),
|
|
928
|
+
causeName: errorName(input.error)
|
|
929
|
+
};
|
|
930
|
+
}
|
|
931
|
+
function normalizeTaskRecord(task, now) {
|
|
932
|
+
if (!task || typeof task !== "object" || Array.isArray(task)) {
|
|
933
|
+
throw new Error("task source returned a non-object task record");
|
|
934
|
+
}
|
|
935
|
+
const t = task;
|
|
936
|
+
const idValue = typeof t.id === "string" ? t.id : "";
|
|
937
|
+
const titleValue = typeof t.title === "string" ? t.title : idValue;
|
|
938
|
+
const statusValue = typeof t.status === "string" ? t.status : "open";
|
|
939
|
+
const depsInput = Array.isArray(t.deps) ? t.deps : Array.isArray(t.dependencies) ? t.dependencies : [];
|
|
940
|
+
const depsValue = depsInput.filter((dependency) => typeof dependency === "string");
|
|
941
|
+
const parentChildInput = Array.isArray(t.parentChildDeps) ? t.parentChildDeps : [];
|
|
942
|
+
const parentChildDepsValue = parentChildInput.filter((dependency) => typeof dependency === "string");
|
|
943
|
+
const labelsValue = Array.isArray(t.labels) ? t.labels.filter((label) => typeof label === "string") : [];
|
|
944
|
+
const scopeValue = Array.isArray(t.scope) ? t.scope.filter((scope) => typeof scope === "string") : [];
|
|
945
|
+
const description = typeof t.description === "string" ? t.description : typeof t.body === "string" ? t.body : null;
|
|
946
|
+
const validationValue = Array.isArray(t.validators) ? t.validators.filter((validator) => typeof validator === "string") : Array.isArray(t.validation) ? t.validation.filter((validator) => typeof validator === "string") : [];
|
|
947
|
+
return {
|
|
948
|
+
id: idValue,
|
|
949
|
+
title: titleValue,
|
|
950
|
+
description,
|
|
951
|
+
acceptanceCriteria: typeof t.acceptanceCriteria === "string" ? t.acceptanceCriteria : null,
|
|
952
|
+
status: statusValue,
|
|
953
|
+
sourceStatus: null,
|
|
954
|
+
priority: typeof t.priority === "number" ? t.priority : null,
|
|
955
|
+
issueType: typeof t.issueType === "string" ? t.issueType : null,
|
|
956
|
+
role: typeof t.role === "string" ? t.role : null,
|
|
957
|
+
externalRef: typeof t.externalRef === "string" ? t.externalRef : typeof t.url === "string" ? t.url : null,
|
|
958
|
+
sourceIssueId: typeof t.sourceIssueId === "string" ? t.sourceIssueId : null,
|
|
959
|
+
dependencies: depsValue.slice(),
|
|
960
|
+
parentChildDeps: parentChildDepsValue.slice(),
|
|
961
|
+
scope: scopeValue,
|
|
962
|
+
validation: validationValue,
|
|
963
|
+
createdAt: typeof t.createdAt === "string" ? t.createdAt : now,
|
|
964
|
+
updatedAt: typeof t.updatedAt === "string" ? t.updatedAt : now,
|
|
965
|
+
labels: labelsValue,
|
|
966
|
+
...t.raw && typeof t.raw === "object" && !Array.isArray(t.raw) ? { raw: t.raw } : {},
|
|
967
|
+
...typeof t.issueNodeId === "string" ? { issueNodeId: t.issueNodeId } : {},
|
|
968
|
+
...typeof t.nodeId === "string" ? { nodeId: t.nodeId } : {},
|
|
969
|
+
...typeof t.node_id === "string" ? { node_id: t.node_id } : {}
|
|
970
|
+
};
|
|
971
|
+
}
|
|
972
|
+
function buildEmptyTaskSourceHint(kind) {
|
|
973
|
+
if (kind === "github-issues") {
|
|
974
|
+
return `Verify (1) "gh auth status" is signed in, (2) the configured owner/repo has issues with the configured labels (default: "task"), and (3) the gh CLI can list them: "gh issue list --repo <owner>/<repo> --label task".`;
|
|
975
|
+
}
|
|
976
|
+
if (kind === "files") {
|
|
977
|
+
return "Check the configured taskSource.path directory exists and contains JSON files matching the pattern.";
|
|
978
|
+
}
|
|
979
|
+
return "The configured task source returned no tasks. Check the plugin's factory logs for details.";
|
|
980
|
+
}
|
|
981
|
+
function createSnapshotService(options) {
|
|
982
|
+
const { projectRoot, readers } = options;
|
|
983
|
+
const deps = options.deps ?? {};
|
|
984
|
+
const readConfigMtime = deps.readConfigMtime ?? getPluginHostConfigMtime;
|
|
985
|
+
const readTaskSourceList = deps.readTaskSourceList ?? ((root, readOptions) => getCachedTaskSourceList(root, readOptions));
|
|
986
|
+
const readPluginHostContext = deps.readPluginHostContext ?? getCachedPluginHostContext;
|
|
987
|
+
const invalidateHostCache = deps.invalidatePluginHostCache ?? invalidatePluginHostCache;
|
|
988
|
+
const invalidateConversationCache = deps.invalidateConversationSnapshotCache ?? invalidateConversationSnapshotCache;
|
|
989
|
+
const readAgentRuntimes = deps.listAgentRuntimes ?? listAgentRuntimes;
|
|
990
|
+
const nowMs = deps.nowMs ?? Date.now;
|
|
991
|
+
const nowIso = deps.nowIso ?? (() => new Date().toISOString());
|
|
992
|
+
const logger = deps.logger ?? console;
|
|
993
|
+
const taskSourceTtlMs = Math.max(0, deps.taskSourceTtlMs ?? DEFAULT_TASK_SOURCE_TTL_MS);
|
|
994
|
+
let pluginTaskCache = null;
|
|
995
|
+
let taskSourceErrors = [];
|
|
996
|
+
let lastEmptyTaskSourceWarning = null;
|
|
997
|
+
function setOutcome(outcome, configMtime) {
|
|
998
|
+
pluginTaskCache = {
|
|
999
|
+
configMtime,
|
|
1000
|
+
expiresAt: nowMs() + taskSourceTtlMs,
|
|
1001
|
+
outcome
|
|
1002
|
+
};
|
|
1003
|
+
taskSourceErrors = outcome.errors;
|
|
1004
|
+
return outcome;
|
|
1005
|
+
}
|
|
1006
|
+
async function readPluginHostTasks() {
|
|
1007
|
+
const configMtime = readConfigMtime(projectRoot);
|
|
1008
|
+
const now = nowMs();
|
|
1009
|
+
if (pluginTaskCache && pluginTaskCache.configMtime === configMtime && pluginTaskCache.expiresAt > now) {
|
|
1010
|
+
taskSourceErrors = pluginTaskCache.outcome.errors;
|
|
1011
|
+
return pluginTaskCache.outcome;
|
|
1012
|
+
}
|
|
1013
|
+
let cacheResult;
|
|
1014
|
+
try {
|
|
1015
|
+
cacheResult = await readTaskSourceList(projectRoot, { force: true, ttlMs: 0 });
|
|
1016
|
+
} catch (error) {
|
|
1017
|
+
const kind2 = await readConfiguredTaskSourceKind();
|
|
1018
|
+
const structured = toTaskSourceError({
|
|
1019
|
+
source: "task-source",
|
|
1020
|
+
reason: "read-failed",
|
|
1021
|
+
projectRoot,
|
|
1022
|
+
kind: kind2,
|
|
1023
|
+
error
|
|
1024
|
+
});
|
|
1025
|
+
logger.warn(`[server] plugin-host task source read failed: ${structured.message}`);
|
|
1026
|
+
return setOutcome({ kind: kind2, tasks: null, errors: [structured] }, configMtime);
|
|
1027
|
+
}
|
|
1028
|
+
if (!cacheResult) {
|
|
1029
|
+
const ctx = await readPluginHostContext(projectRoot).catch((error) => {
|
|
1030
|
+
const structured = toTaskSourceError({
|
|
1031
|
+
source: "plugin-host",
|
|
1032
|
+
reason: "read-failed",
|
|
1033
|
+
projectRoot,
|
|
1034
|
+
kind: null,
|
|
1035
|
+
error
|
|
1036
|
+
});
|
|
1037
|
+
taskSourceErrors = [structured];
|
|
1038
|
+
logger.warn(`[server] plugin-host context read failed: ${structured.message}`);
|
|
1039
|
+
return null;
|
|
1040
|
+
});
|
|
1041
|
+
if (ctx && ctx.taskSourceRegistry.list().length === 0) {
|
|
1042
|
+
const key = `${projectRoot}::no-source`;
|
|
1043
|
+
if (lastEmptyTaskSourceWarning !== key) {
|
|
1044
|
+
logger.warn(`[server] rig.config loaded but no task source registered \u2014 check that the configured plugin contributes a factory for kind "${ctx.config.taskSource.kind}".`);
|
|
1045
|
+
lastEmptyTaskSourceWarning = key;
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
return setOutcome({ kind: null, tasks: null, errors: taskSourceErrors }, configMtime);
|
|
1049
|
+
}
|
|
1050
|
+
const { kind, tasks } = cacheResult;
|
|
1051
|
+
if (tasks.length === 0) {
|
|
1052
|
+
const key = `${projectRoot}::${kind}::empty`;
|
|
1053
|
+
if (lastEmptyTaskSourceWarning !== key) {
|
|
1054
|
+
logger.warn(`[server] task source "${kind}" returned no tasks for ${projectRoot}. ${buildEmptyTaskSourceHint(kind)}`);
|
|
1055
|
+
lastEmptyTaskSourceWarning = key;
|
|
1056
|
+
}
|
|
1057
|
+
return setOutcome({ kind, tasks: null, errors: [] }, configMtime);
|
|
1058
|
+
}
|
|
1059
|
+
lastEmptyTaskSourceWarning = null;
|
|
1060
|
+
try {
|
|
1061
|
+
return setOutcome({ kind, tasks: tasks.map((task) => normalizeTaskRecord(task, nowIso())), errors: [] }, configMtime);
|
|
1062
|
+
} catch (error) {
|
|
1063
|
+
const structured = toTaskSourceError({
|
|
1064
|
+
source: "task-source",
|
|
1065
|
+
reason: "map-failed",
|
|
1066
|
+
projectRoot,
|
|
1067
|
+
kind,
|
|
1068
|
+
error
|
|
1069
|
+
});
|
|
1070
|
+
logger.warn(`[server] plugin-host task source read failed: ${structured.message}`);
|
|
1071
|
+
return setOutcome({ kind, tasks: null, errors: [structured] }, configMtime);
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
async function readConfiguredTaskSourceKind() {
|
|
1075
|
+
try {
|
|
1076
|
+
return (await readPluginHostContext(projectRoot))?.config.taskSource.kind ?? null;
|
|
1077
|
+
} catch {
|
|
1078
|
+
return null;
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
async function readWorkspaceTasks() {
|
|
1082
|
+
const fromPluginHost = await readPluginHostTasks();
|
|
1083
|
+
if (fromPluginHost.tasks && fromPluginHost.tasks.length > 0) {
|
|
1084
|
+
return fromPluginHost.tasks;
|
|
1085
|
+
}
|
|
1086
|
+
if (fromPluginHost.kind === "github-issues") {
|
|
1087
|
+
return [];
|
|
1088
|
+
}
|
|
1089
|
+
return readers.readLegacyWorkspaceTasks(projectRoot);
|
|
1090
|
+
}
|
|
1091
|
+
function makeWorkspaceReaders() {
|
|
1092
|
+
let taskMemo = null;
|
|
1093
|
+
const memoizedTasks = () => {
|
|
1094
|
+
taskMemo ??= readWorkspaceTasks();
|
|
1095
|
+
return taskMemo;
|
|
1096
|
+
};
|
|
1097
|
+
return {
|
|
1098
|
+
readWorkspaceTasks: () => memoizedTasks(),
|
|
1099
|
+
readWorkspaceSummaryRecord: (root) => readers.readWorkspaceSummaryRecord(root),
|
|
1100
|
+
listArtifactSummaries: async (root, taskId, options2) => {
|
|
1101
|
+
const knownTaskIds = taskId ? undefined : (await memoizedTasks()).map((task) => task.id);
|
|
1102
|
+
return readers.listArtifactSummaries(root, taskId, knownTaskIds, options2?.knownRuns);
|
|
1103
|
+
}
|
|
1104
|
+
};
|
|
1105
|
+
}
|
|
1106
|
+
return {
|
|
1107
|
+
getWorkspaceTasks: readWorkspaceTasks,
|
|
1108
|
+
getRigSnapshot: () => buildRigSnapshotPayload(projectRoot, makeWorkspaceReaders()),
|
|
1109
|
+
async getRuntimeSnapshot(input) {
|
|
1110
|
+
const agentRuntimes = await readAgentRuntimes(projectRoot);
|
|
1111
|
+
return buildRuntimeSnapshot({
|
|
1112
|
+
projectRoot,
|
|
1113
|
+
runProcesses: input.runProcesses,
|
|
1114
|
+
agentRuntimes
|
|
1115
|
+
});
|
|
1116
|
+
},
|
|
1117
|
+
async getEngineSnapshot(input) {
|
|
1118
|
+
const agentRuntimes = await readAgentRuntimes(projectRoot);
|
|
1119
|
+
return buildEngineSnapshotPayload({
|
|
1120
|
+
projectRoot,
|
|
1121
|
+
remoteHosts: input.remoteHosts,
|
|
1122
|
+
remoteConnections: input.remoteConnections,
|
|
1123
|
+
sequence: input.sequence,
|
|
1124
|
+
runProcesses: input.runProcesses,
|
|
1125
|
+
agentRuntimes,
|
|
1126
|
+
readers: makeWorkspaceReaders()
|
|
1127
|
+
});
|
|
1128
|
+
},
|
|
1129
|
+
getTaskPrompt: (taskId) => buildTaskPrompt(projectRoot, taskId, makeWorkspaceReaders()),
|
|
1130
|
+
getTaskSourceErrors: () => taskSourceErrors,
|
|
1131
|
+
invalidate(reason) {
|
|
1132
|
+
pluginTaskCache = null;
|
|
1133
|
+
taskSourceErrors = [];
|
|
1134
|
+
invalidateConversationCache(projectRoot);
|
|
1135
|
+
if (reason.toLowerCase().includes("config")) {
|
|
1136
|
+
invalidateHostCache(projectRoot);
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
1141
|
+
export {
|
|
1142
|
+
createSnapshotService
|
|
1143
|
+
};
|