@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,161 @@
1
+ // @bun
2
+ // packages/server/src/server-helpers/broadcasters.ts
3
+ import { RIG_WS_CHANNELS } from "@rig/contracts";
4
+
5
+ // packages/server/src/websocket.ts
6
+ import {
7
+ WS_CHANNELS
8
+ } from "@rig/contracts";
9
+ function encodeWebSocketPayload(payload) {
10
+ return JSON.stringify(payload);
11
+ }
12
+
13
+ // packages/server/src/server-helpers/run-writers.ts
14
+ import { resolve as resolve2 } from "path";
15
+ import {
16
+ appendJsonlRecord,
17
+ readAuthorityRun as readAuthorityRun2,
18
+ resolveAuthorityRunDir as resolveAuthorityRunDir2,
19
+ writeJsonFile
20
+ } from "@rig/runtime/control-plane/authority-files";
21
+
22
+ // packages/server/src/server-helpers/normalizers.ts
23
+ function normalizeString(value) {
24
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
25
+ }
26
+
27
+ // packages/server/src/server-helpers/run-io.ts
28
+ import { dirname, resolve } from "path";
29
+ import {
30
+ listAuthorityRuns,
31
+ readAuthorityRun,
32
+ readJsonlFile,
33
+ resolveAuthorityRunDir
34
+ } from "@rig/runtime/control-plane/authority-files";
35
+ function runLogsPath(projectRoot, runId) {
36
+ return resolve(resolveAuthorityRunDir(projectRoot, runId), "logs.jsonl");
37
+ }
38
+ var INITIAL_RUN_LOG_TAIL_MAX_BYTES = 8 * 1024 * 1024;
39
+
40
+ // packages/server/src/server-helpers/run-writers.ts
41
+ function appendRunLogEntry(projectRoot, runId, value) {
42
+ if (!readAuthorityRun2(projectRoot, runId)) {
43
+ return;
44
+ }
45
+ appendJsonlRecord(runLogsPath(projectRoot, runId), value);
46
+ patchRunRecord(projectRoot, runId, {});
47
+ return value;
48
+ }
49
+ function patchRunRecord(projectRoot, runId, patch) {
50
+ const current = readAuthorityRun2(projectRoot, runId);
51
+ if (!current) {
52
+ throw new Error(`Run not found: ${runId}`);
53
+ }
54
+ const next = {
55
+ ...current,
56
+ ...patch,
57
+ updatedAt: normalizeString(patch.updatedAt) ?? new Date().toISOString()
58
+ };
59
+ writeJsonFile(resolve2(resolveAuthorityRunDir2(projectRoot, runId), "run.json"), next);
60
+ return next;
61
+ }
62
+
63
+ // packages/server/src/server-helpers/remote-snapshots.ts
64
+ import { listAuthorityRemoteEndpoints } from "@rig/runtime/control-plane/authority-files";
65
+ function normalizeIsoTimestamp(value) {
66
+ const normalized = normalizeString(value);
67
+ if (!normalized)
68
+ return null;
69
+ const timestamp = Date.parse(normalized);
70
+ return Number.isFinite(timestamp) ? new Date(timestamp).toISOString() : null;
71
+ }
72
+
73
+ // packages/server/src/server-helpers/event-emitter.ts
74
+ function nextSequence(carrier) {
75
+ const current = carrier.sequence ?? 0;
76
+ const next = current + 1;
77
+ carrier.sequence = next;
78
+ return next;
79
+ }
80
+ function buildRigEvent(carrier, input) {
81
+ const createdAt = normalizeIsoTimestamp(input.createdAt) ?? new Date().toISOString();
82
+ const sequence = nextSequence(carrier);
83
+ return {
84
+ id: `rig-event-${sequence}`,
85
+ sequence,
86
+ createdAt,
87
+ type: input.type,
88
+ aggregateId: input.aggregateId,
89
+ payload: input.payload ?? {}
90
+ };
91
+ }
92
+
93
+ // packages/server/src/server-helpers/broadcasters.ts
94
+ var SERVER_EVENT_JOURNAL_LIMIT = 5000;
95
+ function broadcastPush(state, payload) {
96
+ const encoded = encodeWebSocketPayload(payload);
97
+ for (const socket of state.sockets) {
98
+ try {
99
+ socket.send(encoded);
100
+ } catch {
101
+ state.sockets.delete(socket);
102
+ }
103
+ }
104
+ }
105
+ function buildSnapshotInvalidatedPush(state, reason = "snapshot-mutated") {
106
+ return {
107
+ type: "push",
108
+ channel: RIG_WS_CHANNELS.snapshotInvalidated,
109
+ data: {
110
+ sequence: state.sequence,
111
+ updatedAt: new Date().toISOString(),
112
+ reason
113
+ }
114
+ };
115
+ }
116
+ function broadcastSnapshotInvalidation(state, reason) {
117
+ broadcastPush(state, buildSnapshotInvalidatedPush(state, reason));
118
+ }
119
+ function buildRunLogAppendedPush(runId, entry) {
120
+ return {
121
+ type: "push",
122
+ channel: RIG_WS_CHANNELS.runLogAppended,
123
+ data: {
124
+ runId,
125
+ entry
126
+ }
127
+ };
128
+ }
129
+ function broadcastRunLogAppended(state, runId, entry) {
130
+ if (!entry) {
131
+ return;
132
+ }
133
+ broadcastPush(state, buildRunLogAppendedPush(runId, entry));
134
+ }
135
+ function appendRunLogEntryAndBroadcast(state, runId, value, reason) {
136
+ const entry = appendRunLogEntry(state.projectRoot, runId, value);
137
+ broadcastRunLogAppended(state, runId, entry);
138
+ broadcastSnapshotInvalidation(state, reason);
139
+ return entry;
140
+ }
141
+ function emitRigEvent(state, input) {
142
+ const event = buildRigEvent(state, input);
143
+ state.eventJournal.push(event);
144
+ if (state.eventJournal.length > SERVER_EVENT_JOURNAL_LIMIT) {
145
+ state.eventJournal.splice(0, state.eventJournal.length - SERVER_EVENT_JOURNAL_LIMIT);
146
+ }
147
+ broadcastPush(state, {
148
+ type: "push",
149
+ channel: RIG_WS_CHANNELS.event,
150
+ data: event
151
+ });
152
+ return event;
153
+ }
154
+ export {
155
+ emitRigEvent,
156
+ broadcastSnapshotInvalidation,
157
+ broadcastRunLogAppended,
158
+ broadcastPush,
159
+ appendRunLogEntryAndBroadcast,
160
+ SERVER_EVENT_JOURNAL_LIMIT
161
+ };
@@ -0,0 +1,382 @@
1
+ // @bun
2
+ // packages/server/src/server-helpers/conversation-snapshot.ts
3
+ import { existsSync as existsSync2, readdirSync, statSync as statSync2 } from "fs";
4
+ import { dirname as dirname2, resolve as resolve2 } from "path";
5
+ import {
6
+ listAuthorityRuns as listAuthorityRuns2,
7
+ readJsonlFile as readJsonlFile2
8
+ } from "@rig/runtime/control-plane/authority-files";
9
+
10
+ // packages/server/src/server-helpers/normalizers.ts
11
+ function normalizeString(value) {
12
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
13
+ }
14
+
15
+ // packages/server/src/server-helpers/remote-snapshots.ts
16
+ import { listAuthorityRemoteEndpoints } from "@rig/runtime/control-plane/authority-files";
17
+ function normalizeIsoTimestamp(value) {
18
+ const normalized = normalizeString(value);
19
+ if (!normalized)
20
+ return null;
21
+ const timestamp = Date.parse(normalized);
22
+ return Number.isFinite(timestamp) ? new Date(timestamp).toISOString() : null;
23
+ }
24
+
25
+ // packages/server/src/server-helpers/run-io.ts
26
+ import { dirname, resolve } from "path";
27
+ import { closeSync, existsSync, mkdirSync, openSync, readSync, statSync, writeFileSync } from "fs";
28
+ import {
29
+ listAuthorityRuns,
30
+ readAuthorityRun,
31
+ readJsonlFile,
32
+ resolveAuthorityRunDir
33
+ } from "@rig/runtime/control-plane/authority-files";
34
+ function runTimelinePath(projectRoot, runId) {
35
+ return resolve(resolveAuthorityRunDir(projectRoot, runId), "timeline.jsonl");
36
+ }
37
+ function runLogsPath(projectRoot, runId) {
38
+ return resolve(resolveAuthorityRunDir(projectRoot, runId), "logs.jsonl");
39
+ }
40
+ function parseJsonlRecords(lines) {
41
+ return lines.map((line) => line.trim()).filter(Boolean).flatMap((line) => {
42
+ try {
43
+ return [JSON.parse(line)];
44
+ } catch {
45
+ return [];
46
+ }
47
+ });
48
+ }
49
+ function readJsonlFileTail(path, options) {
50
+ const limit = Math.max(0, Math.trunc(options.limit));
51
+ if (limit === 0 || !existsSync(path))
52
+ return [];
53
+ const maxBytes = Math.max(1024, Math.trunc(options.maxBytes ?? 256 * 1024));
54
+ const size = statSync(path).size;
55
+ if (size === 0)
56
+ return [];
57
+ const start = Math.max(0, size - maxBytes);
58
+ const length = size - start;
59
+ const buffer = Buffer.alloc(length);
60
+ const fd = openSync(path, "r");
61
+ try {
62
+ readSync(fd, buffer, 0, length, start);
63
+ } finally {
64
+ closeSync(fd);
65
+ }
66
+ const text = buffer.toString("utf8");
67
+ const lines = text.split(/\r?\n/);
68
+ const completeLines = start > 0 ? lines.slice(1) : lines;
69
+ return parseJsonlRecords(completeLines.filter(Boolean).slice(-limit));
70
+ }
71
+ var INITIAL_RUN_LOG_TAIL_MAX_BYTES = 8 * 1024 * 1024;
72
+
73
+ // packages/server/src/server-helpers/conversation-snapshot.ts
74
+ function readClaudeSessionRecords(run) {
75
+ if (!run.worktreePath)
76
+ return [];
77
+ const runtimeRoot = dirname2(run.worktreePath);
78
+ const projectsRoot = resolve2(runtimeRoot, "home", ".claude", "projects");
79
+ if (!existsSync2(projectsRoot))
80
+ return [];
81
+ const candidatePaths = [];
82
+ const visit = (dirPath, depth) => {
83
+ if (depth > 2)
84
+ return;
85
+ for (const entry of readdirSync(dirPath, { withFileTypes: true })) {
86
+ const nextPath = resolve2(dirPath, entry.name);
87
+ if (entry.isDirectory()) {
88
+ visit(nextPath, depth + 1);
89
+ continue;
90
+ }
91
+ if (entry.isFile() && entry.name.endsWith(".jsonl")) {
92
+ candidatePaths.push(nextPath);
93
+ }
94
+ }
95
+ };
96
+ visit(projectsRoot, 0);
97
+ const entries = candidatePaths.map((path) => {
98
+ const records2 = readJsonlFile2(path).filter((raw) => !!raw && typeof raw === "object" && !Array.isArray(raw));
99
+ return {
100
+ path,
101
+ mtimeMs: statSync2(path).mtimeMs,
102
+ records: records2
103
+ };
104
+ }).filter((entry) => {
105
+ const normalizedWorktree = run.worktreePath.replaceAll("\\", "/");
106
+ return entry.records.some((record) => {
107
+ const cwd = normalizeString(record.cwd)?.replaceAll("\\", "/");
108
+ return cwd === normalizedWorktree;
109
+ });
110
+ }).sort((left, right) => left.mtimeMs - right.mtimeMs);
111
+ if (entries.length === 0)
112
+ return [];
113
+ const startedAt = Date.parse(run.startedAt ?? run.createdAt);
114
+ const completedAt = run.completedAt ? Date.parse(run.completedAt) : Number.POSITIVE_INFINITY;
115
+ const lookbackMs = 60000;
116
+ const lookaheadMs = 5 * 60000;
117
+ const records = [];
118
+ for (const entry of entries) {
119
+ for (const record of entry.records) {
120
+ const timestamp = Date.parse(normalizeIsoTimestamp(record.timestamp) ?? "");
121
+ if (Number.isFinite(startedAt) && Number.isFinite(timestamp)) {
122
+ if (timestamp < startedAt - lookbackMs)
123
+ continue;
124
+ if (Number.isFinite(completedAt) && timestamp > completedAt + lookaheadMs)
125
+ continue;
126
+ }
127
+ records.push(record);
128
+ }
129
+ }
130
+ return records.sort((left, right) => {
131
+ const leftAt = Date.parse(normalizeIsoTimestamp(left.timestamp) ?? "") || 0;
132
+ const rightAt = Date.parse(normalizeIsoTimestamp(right.timestamp) ?? "") || 0;
133
+ return leftAt - rightAt;
134
+ });
135
+ }
136
+ function buildClaudeStreamLogs(run) {
137
+ const sessionRecords = readClaudeSessionRecords(run);
138
+ if (sessionRecords.length === 0)
139
+ return [];
140
+ const logs = [];
141
+ const pendingToolUses = new Map;
142
+ for (const record of sessionRecords) {
143
+ const entryType = normalizeString(record.type);
144
+ const createdAt = normalizeIsoTimestamp(record.timestamp) ?? run.updatedAt;
145
+ if (entryType === "summary") {
146
+ continue;
147
+ }
148
+ if (entryType === "system" || entryType === "result") {
149
+ logs.push({
150
+ id: normalizeString(record.uuid) ?? `${run.runId}-claude-${logs.length + 1}`,
151
+ runId: run.runId,
152
+ title: entryType === "result" ? "Agent result" : "Agent session initialized",
153
+ detail: JSON.stringify(record),
154
+ tone: entryType === "result" && record.is_error === true ? "error" : "info",
155
+ status: run.completedAt ? "completed" : "running",
156
+ payload: record,
157
+ createdAt
158
+ });
159
+ continue;
160
+ }
161
+ const message = record.message;
162
+ if (!message || typeof message !== "object" || Array.isArray(message))
163
+ continue;
164
+ const messageRecord = message;
165
+ const role = normalizeString(messageRecord.role) ?? entryType ?? "assistant";
166
+ const content = Array.isArray(messageRecord.content) ? messageRecord.content : [];
167
+ for (const item of content) {
168
+ if (!item || typeof item !== "object" || Array.isArray(item))
169
+ continue;
170
+ const contentRecord = item;
171
+ const contentType = normalizeString(contentRecord.type);
172
+ if (contentType === "tool_use") {
173
+ const toolUseId = normalizeString(contentRecord.id);
174
+ if (!toolUseId)
175
+ continue;
176
+ pendingToolUses.set(toolUseId, {
177
+ id: toolUseId,
178
+ name: normalizeString(contentRecord.name),
179
+ input: contentRecord.input ?? {},
180
+ timestamp: createdAt,
181
+ record: contentRecord
182
+ });
183
+ continue;
184
+ }
185
+ if (contentType === "tool_result") {
186
+ const toolUseId = normalizeString(contentRecord.tool_use_id);
187
+ const pending = toolUseId ? pendingToolUses.get(toolUseId) ?? null : null;
188
+ if (pending && toolUseId) {
189
+ pendingToolUses.delete(toolUseId);
190
+ }
191
+ const detail2 = JSON.stringify({
192
+ type: "claude_tool_activity",
193
+ session_id: normalizeString(record.sessionId) ?? normalizeString(record.session_id),
194
+ uuid: normalizeString(record.uuid),
195
+ tool_use_id: toolUseId,
196
+ tool_name: pending?.name ?? null,
197
+ tool_input: pending?.input ?? null,
198
+ tool_result: contentRecord.content ?? contentRecord,
199
+ is_error: contentRecord.is_error === true,
200
+ raw: {
201
+ toolUse: pending?.record ?? null,
202
+ toolResult: contentRecord
203
+ }
204
+ });
205
+ logs.push({
206
+ id: toolUseId ? `${run.runId}-claude-tool-${toolUseId}` : normalizeString(record.uuid) ?? `${run.runId}-claude-${logs.length + 1}`,
207
+ runId: run.runId,
208
+ title: "Tool activity",
209
+ detail: detail2,
210
+ tone: contentRecord.is_error === true ? "error" : "tool",
211
+ status: run.completedAt ? "completed" : "running",
212
+ payload: {
213
+ toolUseId,
214
+ toolName: pending?.name ?? null,
215
+ toolInput: pending?.input ?? null,
216
+ content: contentRecord.content ?? null,
217
+ isError: contentRecord.is_error === true
218
+ },
219
+ createdAt: pending?.timestamp ?? createdAt
220
+ });
221
+ continue;
222
+ }
223
+ const detail = JSON.stringify({
224
+ type: role,
225
+ message: {
226
+ role,
227
+ content: [contentRecord]
228
+ },
229
+ session_id: normalizeString(record.sessionId) ?? normalizeString(record.session_id),
230
+ uuid: normalizeString(record.uuid)
231
+ });
232
+ logs.push({
233
+ id: normalizeString(record.uuid) ? `${normalizeString(record.uuid)}:${contentType ?? "content"}:${logs.length + 1}` : `${run.runId}-claude-${logs.length + 1}`,
234
+ runId: run.runId,
235
+ title: role === "assistant" ? "Assistant output" : role === "user" ? "Tool activity" : "Agent output",
236
+ detail,
237
+ tone: contentType === "text" ? "info" : "tool",
238
+ status: run.completedAt ? "completed" : "running",
239
+ payload: contentRecord,
240
+ createdAt
241
+ });
242
+ }
243
+ }
244
+ for (const pending of pendingToolUses.values()) {
245
+ logs.push({
246
+ id: `${run.runId}-claude-tool-${pending.id}`,
247
+ runId: run.runId,
248
+ title: "Tool activity",
249
+ detail: JSON.stringify({
250
+ type: "claude_tool_use",
251
+ tool_use_id: pending.id,
252
+ tool_name: pending.name,
253
+ tool_input: pending.input,
254
+ raw: pending.record
255
+ }),
256
+ tone: "tool",
257
+ status: "running",
258
+ payload: {
259
+ toolUseId: pending.id,
260
+ toolName: pending.name,
261
+ toolInput: pending.input
262
+ },
263
+ createdAt: pending.timestamp
264
+ });
265
+ }
266
+ return logs.sort((left, right) => Date.parse(left.createdAt) - Date.parse(right.createdAt));
267
+ }
268
+ var snapshotCache = new Map;
269
+ var RUN_LOG_SNAPSHOT_TAIL_LIMIT = 200;
270
+ function runListSignature(runs) {
271
+ return runs.slice().sort((a, b) => a.runId.localeCompare(b.runId)).map((run) => `${run.runId}|${run.updatedAt ?? ""}|${run.completedAt ?? ""}`).join(";");
272
+ }
273
+ function invalidateConversationSnapshotCache(projectRoot) {
274
+ if (projectRoot) {
275
+ snapshotCache.delete(projectRoot);
276
+ return;
277
+ }
278
+ snapshotCache.clear();
279
+ }
280
+ function buildConversationSnapshot(projectRoot, options = {}) {
281
+ const runs = listAuthorityRuns2(projectRoot);
282
+ const signature = runListSignature(runs);
283
+ if (!options.force) {
284
+ const cached = snapshotCache.get(projectRoot);
285
+ if (cached && cached.signature === signature) {
286
+ return cached.snapshot;
287
+ }
288
+ }
289
+ const conversations = [];
290
+ const messages = [];
291
+ const logs = [];
292
+ const actions = [];
293
+ for (const run of runs) {
294
+ const conversationId = `conversation:${run.runId}`;
295
+ conversations.push({
296
+ id: conversationId,
297
+ runId: run.runId,
298
+ title: run.title,
299
+ createdAt: run.createdAt,
300
+ updatedAt: run.updatedAt
301
+ });
302
+ const timeline = readJsonlFile2(runTimelinePath(projectRoot, run.runId));
303
+ for (const raw of timeline) {
304
+ if (!raw || typeof raw !== "object")
305
+ continue;
306
+ const entry = raw;
307
+ const type = normalizeString(entry.type) ?? "system_message";
308
+ const createdAt = normalizeString(entry.createdAt) ?? run.updatedAt;
309
+ if (type === "user_message" || type === "assistant_message" || type === "system_message") {
310
+ const role = type === "assistant_message" ? "assistant" : type === "user_message" ? "user" : "system";
311
+ messages.push({
312
+ id: normalizeString(entry.id) ?? `${run.runId}-${messages.length + 1}`,
313
+ conversationId,
314
+ role,
315
+ text: typeof entry.text === "string" ? entry.text : "",
316
+ attachments: Array.isArray(entry.attachments) ? entry.attachments : [],
317
+ state: normalizeString(entry.state) === "streaming" ? "streaming" : normalizeString(entry.state) === "interrupted" ? "interrupted" : normalizeString(entry.state) === "errored" ? "errored" : "completed",
318
+ createdAt,
319
+ completedAt: normalizeString(entry.completedAt) ?? (normalizeString(entry.state) === "streaming" ? null : createdAt)
320
+ });
321
+ continue;
322
+ }
323
+ if (type === "action") {
324
+ actions.push({
325
+ id: normalizeString(entry.id) ?? `${run.runId}-action-${actions.length + 1}`,
326
+ runId: run.runId,
327
+ messageId: normalizeString(entry.messageId),
328
+ actionType: normalizeString(entry.actionType) ?? "task",
329
+ title: normalizeString(entry.title) ?? "Run step",
330
+ detail: normalizeString(entry.detail),
331
+ state: normalizeString(entry.state) ?? "completed",
332
+ payload: entry.payload ?? {},
333
+ startedAt: createdAt,
334
+ completedAt: normalizeString(entry.completedAt) ?? null
335
+ });
336
+ }
337
+ }
338
+ const runLogs = readJsonlFileTail(runLogsPath(projectRoot, run.runId), {
339
+ limit: RUN_LOG_SNAPSHOT_TAIL_LIMIT,
340
+ maxBytes: 512 * 1024
341
+ });
342
+ const claudeStreamLogs = buildClaudeStreamLogs(run);
343
+ const hasClaudeStreamLogs = claudeStreamLogs.length > 0;
344
+ for (const raw of runLogs) {
345
+ if (!raw || typeof raw !== "object")
346
+ continue;
347
+ const entry = raw;
348
+ const createdAt = normalizeString(entry.createdAt) ?? run.updatedAt;
349
+ const rawDetail = normalizeString(entry.detail);
350
+ if (hasClaudeStreamLogs && rawDetail) {
351
+ try {
352
+ const parsed = JSON.parse(rawDetail);
353
+ const parsedType = normalizeString(parsed.type);
354
+ if (parsedType === "assistant" || parsedType === "user" || parsedType === "system" || parsedType === "result") {
355
+ continue;
356
+ }
357
+ } catch {}
358
+ }
359
+ logs.push({
360
+ id: normalizeString(entry.id) ?? `${run.runId}-log-${logs.length + 1}`,
361
+ runId: run.runId,
362
+ title: normalizeString(entry.title) ?? "Run log",
363
+ detail: normalizeString(entry.detail),
364
+ tone: normalizeString(entry.tone) === "error" ? "error" : normalizeString(entry.tone) === "tool" ? "tool" : normalizeString(entry.tone) === "thinking" ? "thinking" : "info",
365
+ status: normalizeString(entry.status),
366
+ payload: entry.payload ?? {},
367
+ createdAt
368
+ });
369
+ }
370
+ logs.push(...claudeStreamLogs);
371
+ }
372
+ logs.sort((left, right) => Date.parse(left.createdAt) - Date.parse(right.createdAt));
373
+ const snapshot = { conversations, messages, logs, actions };
374
+ snapshotCache.set(projectRoot, { signature, snapshot });
375
+ return snapshot;
376
+ }
377
+ export {
378
+ readClaudeSessionRecords,
379
+ invalidateConversationSnapshotCache,
380
+ buildConversationSnapshot,
381
+ buildClaudeStreamLogs
382
+ };
@@ -0,0 +1,41 @@
1
+ // @bun
2
+ // packages/server/src/server-helpers/remote-snapshots.ts
3
+ import { listAuthorityRemoteEndpoints } from "@rig/runtime/control-plane/authority-files";
4
+
5
+ // packages/server/src/server-helpers/normalizers.ts
6
+ function normalizeString(value) {
7
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
8
+ }
9
+
10
+ // packages/server/src/server-helpers/remote-snapshots.ts
11
+ function normalizeIsoTimestamp(value) {
12
+ const normalized = normalizeString(value);
13
+ if (!normalized)
14
+ return null;
15
+ const timestamp = Date.parse(normalized);
16
+ return Number.isFinite(timestamp) ? new Date(timestamp).toISOString() : null;
17
+ }
18
+
19
+ // packages/server/src/server-helpers/event-emitter.ts
20
+ function nextSequence(carrier) {
21
+ const current = carrier.sequence ?? 0;
22
+ const next = current + 1;
23
+ carrier.sequence = next;
24
+ return next;
25
+ }
26
+ function buildRigEvent(carrier, input) {
27
+ const createdAt = normalizeIsoTimestamp(input.createdAt) ?? new Date().toISOString();
28
+ const sequence = nextSequence(carrier);
29
+ return {
30
+ id: `rig-event-${sequence}`,
31
+ sequence,
32
+ createdAt,
33
+ type: input.type,
34
+ aggregateId: input.aggregateId,
35
+ payload: input.payload ?? {}
36
+ };
37
+ }
38
+ export {
39
+ nextSequence,
40
+ buildRigEvent
41
+ };