@h-rig/server 0.0.6-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +14 -0
  2. package/dist/src/bootstrap.js +161 -0
  3. package/dist/src/index.js +13153 -0
  4. package/dist/src/inspector/agent-runtime.js +1077 -0
  5. package/dist/src/inspector/analysis.js +41 -0
  6. package/dist/src/inspector/discovery.js +137 -0
  7. package/dist/src/inspector/journal.js +518 -0
  8. package/dist/src/inspector/mission.js +562 -0
  9. package/dist/src/inspector/prompt.js +97 -0
  10. package/dist/src/inspector/provider-session.js +65 -0
  11. package/dist/src/inspector/reconcile.js +118 -0
  12. package/dist/src/inspector/review.js +13 -0
  13. package/dist/src/inspector/service.js +1759 -0
  14. package/dist/src/inspector/skills.js +155 -0
  15. package/dist/src/inspector/tools.js +1592 -0
  16. package/dist/src/inspector/types.js +1 -0
  17. package/dist/src/inspector/upstream-sync.js +479 -0
  18. package/dist/src/orchestration.js +402 -0
  19. package/dist/src/remote.js +123 -0
  20. package/dist/src/scheduler.js +84 -0
  21. package/dist/src/server-helpers/broadcasters.js +161 -0
  22. package/dist/src/server-helpers/conversation-snapshot.js +382 -0
  23. package/dist/src/server-helpers/event-emitter.js +41 -0
  24. package/dist/src/server-helpers/github-auth-store.js +155 -0
  25. package/dist/src/server-helpers/github-credentials.js +38 -0
  26. package/dist/src/server-helpers/github-project-status-sync.js +196 -0
  27. package/dist/src/server-helpers/github-projects.js +147 -0
  28. package/dist/src/server-helpers/github-reconciler.js +89 -0
  29. package/dist/src/server-helpers/http-router.js +3781 -0
  30. package/dist/src/server-helpers/http-utils.js +135 -0
  31. package/dist/src/server-helpers/inspector-agent-lifecycle.js +104 -0
  32. package/dist/src/server-helpers/inspector-jobs.js +4145 -0
  33. package/dist/src/server-helpers/issue-analysis.js +362 -0
  34. package/dist/src/server-helpers/normalizers.js +31 -0
  35. package/dist/src/server-helpers/notifications.js +96 -0
  36. package/dist/src/server-helpers/orchestration-ops.js +287 -0
  37. package/dist/src/server-helpers/orchestration.js +39 -0
  38. package/dist/src/server-helpers/plugin-host-cache.js +86 -0
  39. package/dist/src/server-helpers/project-fs-ops.js +194 -0
  40. package/dist/src/server-helpers/project-registry.js +124 -0
  41. package/dist/src/server-helpers/queue-state.js +78 -0
  42. package/dist/src/server-helpers/remote-checkout.js +140 -0
  43. package/dist/src/server-helpers/remote-snapshots.js +119 -0
  44. package/dist/src/server-helpers/run-io.js +262 -0
  45. package/dist/src/server-helpers/run-mutations.js +1784 -0
  46. package/dist/src/server-helpers/run-steering.js +176 -0
  47. package/dist/src/server-helpers/run-writers.js +75 -0
  48. package/dist/src/server-helpers/server-paths.js +27 -0
  49. package/dist/src/server-helpers/snapshot-orchestrator.js +832 -0
  50. package/dist/src/server-helpers/snapshot-service.js +1143 -0
  51. package/dist/src/server-helpers/summaries.js +126 -0
  52. package/dist/src/server-helpers/task-config.js +50 -0
  53. package/dist/src/server-helpers/task-projection.js +98 -0
  54. package/dist/src/server-helpers/terminal-runtime.js +156 -0
  55. package/dist/src/server-helpers/terminal-sessions.js +22 -0
  56. package/dist/src/server-helpers/validation-failure.js +31 -0
  57. package/dist/src/server-helpers/ws-router.js +1308 -0
  58. package/dist/src/server.js +12628 -0
  59. package/dist/src/websocket.js +63 -0
  60. package/package.json +33 -0
@@ -0,0 +1,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
+ };