@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,1308 @@
1
+ // @bun
2
+ // packages/server/src/server-helpers/ws-router.ts
3
+ import {
4
+ ORCHESTRATION_WS_CHANNELS,
5
+ ORCHESTRATION_WS_METHODS,
6
+ RIG_WS_METHODS,
7
+ WS_METHODS
8
+ } from "@rig/contracts";
9
+ import {
10
+ listManagedRemoteEndpoints as listManagedRemoteEndpoints2,
11
+ removeManagedRemoteEndpoint as removeManagedRemoteEndpoint2,
12
+ resolveRemoteEndpoint as resolveRemoteEndpoint2,
13
+ upsertManagedRemoteEndpoint as upsertManagedRemoteEndpoint2,
14
+ RemoteWsClient as RemoteWsClient2
15
+ } from "@rig/runtime/control-plane/remote";
16
+ import { deleteRunState } from "@rig/runtime/control-plane/native/run-ops";
17
+ import { readAuthorityRun as readAuthorityRun8 } from "@rig/runtime/control-plane/authority-files";
18
+
19
+ // packages/server/src/server.ts
20
+ import {
21
+ listAuthorityArtifactRoots,
22
+ listAuthorityRuns as listAuthorityRuns7,
23
+ readJsonFile as readJsonFile4,
24
+ readJsonlFile as readJsonlFile3
25
+ } from "@rig/runtime/control-plane/authority-files";
26
+ import { normalizeTaskLifecycleStatus as normalizeTaskLifecycleStatus3 } from "@rig/runtime/control-plane/state-sync/types";
27
+ import {
28
+ readWorkspaceSummary
29
+ } from "@rig/runtime/control-plane/native/workspace-ops";
30
+ import { resolveMonorepoRoot as resolveMonorepoRoot6 } from "@rig/runtime/control-plane/native/utils";
31
+
32
+ // packages/server/src/bootstrap.ts
33
+ import { RIG_DEFINITION_DIRNAME, resolveMonorepoRoot } from "@rig/runtime";
34
+
35
+ // packages/server/src/websocket.ts
36
+ import {
37
+ WS_CHANNELS
38
+ } from "@rig/contracts";
39
+
40
+ // packages/server/src/server-helpers/normalizers.ts
41
+ function normalizeString(value) {
42
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
43
+ }
44
+
45
+ // packages/server/src/server-helpers/run-io.ts
46
+ import { dirname, resolve } from "path";
47
+ import { closeSync, existsSync, mkdirSync, openSync, readSync, statSync, writeFileSync } from "fs";
48
+ import { createReadStream } from "fs";
49
+ import { createInterface } from "readline";
50
+ import {
51
+ listAuthorityRuns,
52
+ readAuthorityRun,
53
+ readJsonlFile,
54
+ resolveAuthorityRunDir
55
+ } from "@rig/runtime/control-plane/authority-files";
56
+ function writeRunJsonlFile(projectRoot, runId, fileName, rows) {
57
+ const filePath = resolve(resolveAuthorityRunDir(projectRoot, runId), fileName);
58
+ mkdirSync(dirname(filePath), { recursive: true });
59
+ const next = rows.map((row) => JSON.stringify(row)).join(`
60
+ `);
61
+ writeFileSync(filePath, next.length > 0 ? `${next}
62
+ ` : "", "utf8");
63
+ }
64
+ function resolveApproval(projectRoot, input) {
65
+ const approvalsPath = resolve(resolveAuthorityRunDir(projectRoot, input.runId), "approvals.jsonl");
66
+ const approvals = readJsonlFile(approvalsPath);
67
+ const resolvedAt = new Date().toISOString();
68
+ const rows = approvals.map((entry) => entry.requestId === input.requestId || entry.id === input.requestId ? {
69
+ ...entry,
70
+ status: "resolved",
71
+ decision: input.decision,
72
+ note: input.note ?? null,
73
+ resolvedAt
74
+ } : entry);
75
+ writeRunJsonlFile(projectRoot, input.runId, "approvals.jsonl", rows);
76
+ return { ok: true, runId: input.runId, requestId: input.requestId, decision: input.decision };
77
+ }
78
+ function respondToUserInput(projectRoot, input) {
79
+ const requestsPath = resolve(resolveAuthorityRunDir(projectRoot, input.runId), "user-input.jsonl");
80
+ const requests = readJsonlFile(requestsPath);
81
+ const resolvedAt = new Date().toISOString();
82
+ const rows = requests.map((entry) => entry.requestId === input.requestId || entry.id === input.requestId ? {
83
+ ...entry,
84
+ status: "resolved",
85
+ answers: input.answers,
86
+ respondedAt: resolvedAt,
87
+ resolvedAt
88
+ } : entry);
89
+ writeRunJsonlFile(projectRoot, input.runId, "user-input.jsonl", rows);
90
+ return { ok: true, runId: input.runId, requestId: input.requestId, answers: input.answers };
91
+ }
92
+ function runLogsPath(projectRoot, runId) {
93
+ return resolve(resolveAuthorityRunDir(projectRoot, runId), "logs.jsonl");
94
+ }
95
+ function parseJsonlRecords(lines) {
96
+ return lines.map((line) => line.trim()).filter(Boolean).flatMap((line) => {
97
+ try {
98
+ return [JSON.parse(line)];
99
+ } catch {
100
+ return [];
101
+ }
102
+ });
103
+ }
104
+ function readJsonlFileTail(path, options) {
105
+ const limit = Math.max(0, Math.trunc(options.limit));
106
+ if (limit === 0 || !existsSync(path))
107
+ return [];
108
+ const maxBytes = Math.max(1024, Math.trunc(options.maxBytes ?? 256 * 1024));
109
+ const size = statSync(path).size;
110
+ if (size === 0)
111
+ return [];
112
+ const start = Math.max(0, size - maxBytes);
113
+ const length = size - start;
114
+ const buffer = Buffer.alloc(length);
115
+ const fd = openSync(path, "r");
116
+ try {
117
+ readSync(fd, buffer, 0, length, start);
118
+ } finally {
119
+ closeSync(fd);
120
+ }
121
+ const text = buffer.toString("utf8");
122
+ const lines = text.split(/\r?\n/);
123
+ const completeLines = start > 0 ? lines.slice(1) : lines;
124
+ return parseJsonlRecords(completeLines.filter(Boolean).slice(-limit));
125
+ }
126
+ var INITIAL_RUN_LOG_TAIL_MAX_BYTES = 8 * 1024 * 1024;
127
+ async function readRunLogsPage(projectRoot, runId, options = {}) {
128
+ const limit = Math.max(1, Math.min(Math.trunc(options.limit ?? 200), 500));
129
+ const cursor = options.cursor == null ? null : Number.parseInt(options.cursor, 10);
130
+ const logsPath = runLogsPath(projectRoot, runId);
131
+ if ((options.cursor == null || !Number.isFinite(cursor)) && existsSync(logsPath)) {
132
+ const size = statSync(logsPath).size;
133
+ if (size > INITIAL_RUN_LOG_TAIL_MAX_BYTES) {
134
+ const tail = readJsonlFileTail(logsPath, {
135
+ limit,
136
+ maxBytes: INITIAL_RUN_LOG_TAIL_MAX_BYTES
137
+ }).filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry)));
138
+ return {
139
+ entries: tail.toReversed(),
140
+ nextCursor: null,
141
+ hasMore: true
142
+ };
143
+ }
144
+ }
145
+ const endExclusive = cursor == null || !Number.isFinite(cursor) ? Number.POSITIVE_INFINITY : Math.max(0, cursor);
146
+ const window = [];
147
+ let index = 0;
148
+ let hasMore = false;
149
+ const stream = createReadStream(logsPath, { encoding: "utf8" });
150
+ stream.on("error", (error) => {
151
+ if (error.code !== "ENOENT") {
152
+ stream.destroy(error);
153
+ }
154
+ });
155
+ const lines = createInterface({ input: stream, crlfDelay: Infinity });
156
+ try {
157
+ for await (const line of lines) {
158
+ const currentIndex = index;
159
+ index += 1;
160
+ if (currentIndex >= endExclusive) {
161
+ break;
162
+ }
163
+ const trimmed = line.trim();
164
+ if (trimmed.length === 0) {
165
+ continue;
166
+ }
167
+ try {
168
+ const parsed = JSON.parse(trimmed);
169
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
170
+ window.push({ index: currentIndex, entry: parsed });
171
+ }
172
+ } catch {
173
+ window.push({
174
+ index: currentIndex,
175
+ entry: { title: "Unparsed log line", detail: line, tone: "info" }
176
+ });
177
+ }
178
+ if (window.length > limit) {
179
+ window.shift();
180
+ hasMore = true;
181
+ }
182
+ }
183
+ } catch (error) {
184
+ const code = error.code;
185
+ if (code !== "ENOENT") {
186
+ throw error;
187
+ }
188
+ }
189
+ return {
190
+ entries: window.toReversed().map((item) => item.entry),
191
+ nextCursor: hasMore ? String(window[0]?.index ?? 0) : null,
192
+ hasMore
193
+ };
194
+ }
195
+
196
+ // packages/server/src/server-helpers/server-paths.ts
197
+ import { dirname as dirname2, resolve as resolve2 } from "path";
198
+ import { resolveMonorepoRoot as resolveMonorepoRoot2 } from "@rig/runtime/control-plane/native/utils";
199
+ function resolveServerAuthorityPaths(projectRoot) {
200
+ const taskWorkspace = process.env.RIG_TASK_WORKSPACE?.trim();
201
+ const explicitStateDir = process.env.RIG_STATE_DIR?.trim();
202
+ const explicitLogsDir = process.env.RIG_LOGS_DIR?.trim();
203
+ const explicitSessionFile = process.env.RIG_SESSION_FILE?.trim();
204
+ const monorepoRoot = resolveMonorepoRoot2(projectRoot);
205
+ const stateRoot = taskWorkspace ? resolve2(taskWorkspace, ".rig") : explicitStateDir ? dirname2(resolve2(explicitStateDir)) : explicitLogsDir ? dirname2(resolve2(explicitLogsDir)) : explicitSessionFile ? dirname2(dirname2(resolve2(explicitSessionFile))) : resolve2(monorepoRoot, ".rig");
206
+ const stateDir = explicitStateDir ? resolve2(explicitStateDir) : resolve2(stateRoot, "state");
207
+ const logsDir = explicitLogsDir ? resolve2(explicitLogsDir) : resolve2(stateRoot, "logs");
208
+ const sessionFile = explicitSessionFile ? resolve2(explicitSessionFile) : resolve2(stateRoot, "session", "session.json");
209
+ const artifactsDir = taskWorkspace ? resolve2(taskWorkspace, "artifacts") : resolve2(monorepoRoot, "artifacts");
210
+ return {
211
+ stateRoot,
212
+ stateDir,
213
+ logsDir,
214
+ controlPlaneEventsFile: resolve2(logsDir, "control-plane.events.jsonl"),
215
+ sessionFile,
216
+ artifactsDir
217
+ };
218
+ }
219
+
220
+ // packages/server/src/server-helpers/snapshot-service.ts
221
+ import { listAgentRuntimes } from "@rig/runtime/control-plane/runtime/isolation";
222
+
223
+ // packages/server/src/server-helpers/snapshot-orchestrator.ts
224
+ import {
225
+ listAuthorityRuns as listAuthorityRuns3,
226
+ readAuthorityRun as readAuthorityRun2
227
+ } from "@rig/runtime/control-plane/authority-files";
228
+
229
+ // packages/server/src/server-helpers/queue-state.ts
230
+ import { resolve as resolve3 } from "path";
231
+ import { readJsonFile, writeJsonFile } from "@rig/runtime/control-plane/authority-files";
232
+ function resolveQueueStatePath(projectRoot) {
233
+ return resolve3(resolveServerAuthorityPaths(projectRoot).stateDir, "task-queue.json");
234
+ }
235
+ function readQueueState(projectRoot) {
236
+ const queue = readJsonFile(resolveQueueStatePath(projectRoot), null);
237
+ if (!Array.isArray(queue))
238
+ return [];
239
+ return queue.filter((entry) => entry && typeof entry === "object").map((entry, index) => ({
240
+ taskId: normalizeString(entry.taskId),
241
+ score: typeof entry.score === "number" ? Math.max(0, Math.trunc(entry.score)) : 0,
242
+ unblockCount: typeof entry.unblockCount === "number" ? Math.max(0, Math.trunc(entry.unblockCount)) : 0,
243
+ position: typeof entry.position === "number" ? Math.max(0, Math.trunc(entry.position)) : index
244
+ })).filter((entry) => Boolean(entry.taskId)).sort((left, right) => right.score - left.score || left.position - right.position).map((entry, index) => ({ ...entry, position: index }));
245
+ }
246
+ function writeQueueState(projectRoot, queue) {
247
+ writeJsonFile(resolveQueueStatePath(projectRoot), queue);
248
+ }
249
+ function enqueueTaskState(projectRoot, taskId, score) {
250
+ const queue = readQueueState(projectRoot).filter((entry) => entry.taskId !== taskId);
251
+ const next = [
252
+ ...queue,
253
+ {
254
+ taskId,
255
+ score: Math.max(0, Math.trunc(score)),
256
+ unblockCount: 0,
257
+ position: queue.length
258
+ }
259
+ ].sort((left, right) => right.score - left.score || left.position - right.position).map((entry, index) => ({ ...entry, position: index }));
260
+ writeQueueState(projectRoot, next);
261
+ return next;
262
+ }
263
+ function dequeueTaskState(projectRoot, taskId) {
264
+ const next = readQueueState(projectRoot).filter((entry) => entry.taskId !== taskId).map((entry, index) => ({ ...entry, position: index }));
265
+ writeQueueState(projectRoot, next);
266
+ return next;
267
+ }
268
+
269
+ // packages/server/src/server-helpers/remote-snapshots.ts
270
+ import { listAuthorityRemoteEndpoints } from "@rig/runtime/control-plane/authority-files";
271
+ function hostToRemoteEndpoint(host) {
272
+ let hostName = "127.0.0.1";
273
+ let port = 7890;
274
+ try {
275
+ const url = new URL(host.baseUrl);
276
+ hostName = url.hostname || hostName;
277
+ port = Number.parseInt(url.port || "80", 10) || port;
278
+ } catch {}
279
+ return {
280
+ id: host.hostId,
281
+ alias: host.name,
282
+ host: hostName,
283
+ port,
284
+ token: "",
285
+ tokenConfigured: false,
286
+ autoConnect: true,
287
+ addedAt: host.registeredAt,
288
+ lastConnectedAt: host.lastHeartbeatAt
289
+ };
290
+ }
291
+ function buildRemoteEndpointSnapshot(projectRoot, remoteHosts) {
292
+ const authorityEndpoints = listAuthorityRemoteEndpoints(projectRoot).map((entry) => ({
293
+ id: entry.id,
294
+ alias: entry.alias,
295
+ host: entry.host,
296
+ port: entry.port,
297
+ token: "",
298
+ tokenConfigured: entry.token.trim().length > 0,
299
+ autoConnect: entry.autoConnect,
300
+ addedAt: entry.addedAt,
301
+ lastConnectedAt: entry.lastConnectedAt
302
+ }));
303
+ const hostEndpoints = Array.from(remoteHosts.values()).map(hostToRemoteEndpoint);
304
+ const deduped = new Map;
305
+ for (const entry of [...authorityEndpoints, ...hostEndpoints]) {
306
+ deduped.set(entry.id, entry);
307
+ }
308
+ return Array.from(deduped.values()).sort((left, right) => left.alias.localeCompare(right.alias));
309
+ }
310
+
311
+ // packages/server/src/server-helpers/conversation-snapshot.ts
312
+ import {
313
+ listAuthorityRuns as listAuthorityRuns2,
314
+ readJsonlFile as readJsonlFile2
315
+ } from "@rig/runtime/control-plane/authority-files";
316
+ var snapshotCache = new Map;
317
+
318
+ // packages/server/src/server-helpers/plugin-host-cache.ts
319
+ var contextCache = new Map;
320
+ var taskListCache = new Map;
321
+
322
+ // packages/server/src/server-helpers/terminal-runtime.ts
323
+ import { WS_CHANNELS as WS_CHANNELS2 } from "@rig/contracts";
324
+
325
+ // packages/server/src/server-helpers/broadcasters.ts
326
+ import { RIG_WS_CHANNELS } from "@rig/contracts";
327
+
328
+ // packages/server/src/server-helpers/run-writers.ts
329
+ import {
330
+ appendJsonlRecord,
331
+ readAuthorityRun as readAuthorityRun3,
332
+ resolveAuthorityRunDir as resolveAuthorityRunDir2,
333
+ writeJsonFile as writeJsonFile2
334
+ } from "@rig/runtime/control-plane/authority-files";
335
+
336
+ // packages/server/src/server-helpers/event-emitter.ts
337
+ function nextSequence(carrier) {
338
+ const current = carrier.sequence ?? 0;
339
+ const next = current + 1;
340
+ carrier.sequence = next;
341
+ return next;
342
+ }
343
+
344
+ // packages/server/src/server-helpers/terminal-sessions.ts
345
+ function terminalSessionKey(threadId, terminalId) {
346
+ return `${threadId}::${terminalId}`;
347
+ }
348
+ function terminalSnapshot(session) {
349
+ return {
350
+ threadId: session.threadId,
351
+ terminalId: session.terminalId,
352
+ cwd: session.cwd,
353
+ status: session.status,
354
+ pid: session.pid,
355
+ history: session.history,
356
+ exitCode: session.exitCode,
357
+ exitSignal: session.exitSignal,
358
+ updatedAt: session.updatedAt
359
+ };
360
+ }
361
+
362
+ // packages/server/src/server-helpers/terminal-runtime.ts
363
+ var DEFAULT_TERMINAL_SHELL = process.env.SHELL || "/bin/zsh";
364
+
365
+ // packages/server/src/server-helpers/run-mutations.ts
366
+ import { loadConfig } from "@rig/core/load-config";
367
+ import {
368
+ listAuthorityRuns as listAuthorityRuns4,
369
+ readAuthorityRun as readAuthorityRun4,
370
+ resolveAuthorityRunDir as resolveAuthorityRunDir3,
371
+ writeJsonFile as writeJsonFile3
372
+ } from "@rig/runtime/control-plane/authority-files";
373
+ import { readPublishedRigServerStateSync } from "@rig/runtime/local-server";
374
+ import { resolveMonorepoRoot as resolveMonorepoRoot3 } from "@rig/runtime/control-plane/native/utils";
375
+ import {
376
+ buildTaskRunLifecycleComment,
377
+ updateConfiguredTaskSourceTask
378
+ } from "@rig/runtime/control-plane/tasks/source-lifecycle";
379
+
380
+ // packages/server/src/scheduler.ts
381
+ import { normalizeTaskLifecycleStatus } from "@rig/runtime/control-plane/state-sync/types";
382
+ var TERMINAL_RUN_STATUSES = new Set(["done", "completed", "error", "failed", "stopped", "cancelled"]);
383
+ var RUNNABLE_TASK_STATUSES = new Set(["draft", "open", "ready", "queued"]);
384
+ var REMOTE_READY_STATUSES = new Set(["ready", "idle", "connected"]);
385
+
386
+ // packages/server/src/server-helpers/validation-failure.ts
387
+ import {
388
+ readJsonFile as readJsonFile2,
389
+ resolveTaskArtifactDirs
390
+ } from "@rig/runtime/control-plane/authority-files";
391
+
392
+ // packages/server/src/server-helpers/run-mutations.ts
393
+ var TERMINAL_RUN_STATUSES2 = new Set([
394
+ "completed",
395
+ "complete",
396
+ "done",
397
+ "merged",
398
+ "closed",
399
+ "failed",
400
+ "cancelled",
401
+ "canceled",
402
+ "needs_attention",
403
+ "needs-attention",
404
+ "stopped"
405
+ ]);
406
+ var ORPHANABLE_LOCAL_RUN_STATUSES = new Set(["preparing", "running"]);
407
+
408
+ // packages/server/src/server-helpers/http-router.ts
409
+ import {
410
+ listAuthorityRuns as listAuthorityRuns5,
411
+ readAuthorityRun as readAuthorityRun6,
412
+ resolveAuthorityPaths,
413
+ writeJsonFile as writeJsonFile4
414
+ } from "@rig/runtime/control-plane/authority-files";
415
+ import {
416
+ mutateWorkspaceServiceFabric,
417
+ readTaskArtifactPreview,
418
+ readWorkspaceRemoteFleet,
419
+ readWorkspaceTopology
420
+ } from "@rig/runtime/control-plane/native/workspace-ops";
421
+ import {
422
+ doctorManagedRemoteEndpoints,
423
+ listManagedRemoteEndpoints,
424
+ removeManagedRemoteEndpoint,
425
+ resolveRemoteEndpoint,
426
+ updateManagedRemoteEndpointInAuthority,
427
+ upsertManagedRemoteEndpoint,
428
+ RemoteWsClient
429
+ } from "@rig/runtime/control-plane/remote";
430
+
431
+ // packages/server/src/server-helpers/run-steering.ts
432
+ import { appendJsonlRecord as appendJsonlRecord2, readAuthorityRun as readAuthorityRun5, resolveAuthorityRunDir as resolveAuthorityRunDir4 } from "@rig/runtime/control-plane/authority-files";
433
+
434
+ // packages/server/src/server-helpers/http-router.ts
435
+ import { buildRigInitConfigSource } from "@rig/core";
436
+ import {
437
+ buildTaskRunLifecycleComment as buildTaskRunLifecycleComment2,
438
+ updateConfiguredTaskSourceTask as updateConfiguredTaskSourceTask2
439
+ } from "@rig/runtime/control-plane/tasks/source-lifecycle";
440
+ var RIG_CONFIG_PACKAGE_VERSION = "0.0.6-alpha.0";
441
+ var RIG_CONFIG_DEV_DEPENDENCIES = {
442
+ "@rig/core": `npm:@h-rig/core@${RIG_CONFIG_PACKAGE_VERSION}`,
443
+ "@rig/standard-plugin": `npm:@h-rig/standard-plugin@${RIG_CONFIG_PACKAGE_VERSION}`
444
+ };
445
+
446
+ // packages/server/src/server-helpers/inspector-jobs.ts
447
+ import { readJsonFile as readJsonFile3 } from "@rig/runtime/control-plane/authority-files";
448
+ import { resolveMonorepoRoot as resolveMonorepoRoot5 } from "@rig/runtime/control-plane/native/utils";
449
+ import { normalizeTaskLifecycleStatus as normalizeTaskLifecycleStatus2 } from "@rig/runtime/control-plane/state-sync/types";
450
+
451
+ // packages/server/src/inspector/discovery.ts
452
+ import {
453
+ runStatus
454
+ } from "@rig/runtime/control-plane/native/run-ops";
455
+ import {
456
+ listAuthorityRuns as listAuthorityRuns6,
457
+ readAuthorityRun as readAuthorityRun7
458
+ } from "@rig/runtime/control-plane/authority-files";
459
+
460
+ // packages/server/src/inspector/service.ts
461
+ var ACTIVE_RUN_STATUSES = new Set(["preparing", "running", "validating", "reviewing"]);
462
+
463
+ // packages/server/src/inspector/upstream-sync.ts
464
+ import { resolveMonorepoRoot as resolveMonorepoRoot4 } from "@rig/runtime/control-plane/native/utils";
465
+ var UPSTREAM_VALIDATION_DESCRIPTIONS = {
466
+ "integration:hg-auth-backport": "Preserves the upstream auth hardening cluster: nonce-backed node-client login, signature-aware JWT verification, shared-token onboarding semantics, and regression coverage.",
467
+ "integration:hg-core-security-backport": "Preserves the vendored core-app and backend security fixes for OTP validation, NoSQL/operator hardening, AES/encryption behavior, and rate-limit or redirect-state protections.",
468
+ "integration:hg-boundary-hardening": "Preserves boundary-safe CORS behavior and credential lookup error hygiene across the vendored backend and the extracted credentials-service analogue.",
469
+ "integration:hg-uudl-runtime": "Preserves the upstream uudl runtime/package fixes: production dependency placement, path-resolution/runtime bootstrap behavior, AWS/MSK environment detection, KMS coverage, and a compiling UUDL tree.",
470
+ "boundary:hg-auth-triage": "Requires a durable keep/adapt/reject triage record for the post-import auth policy and mobile-wallet SIWE commits so the watcher does not keep rediscovering the same ambiguity.",
471
+ "boundary:hg-upstream-triage": "Requires a durable keep/adapt/reject triage record for service-relevant upstream commits that do not match the current portable catalog, including commit hashes, touched paths, and follow-up routing decisions."
472
+ };
473
+ var CLUSTERS = {
474
+ "auth-hardening": {
475
+ classification: "portable-now",
476
+ title: "[HG-001] Preserve upstream auth and shared-token hardening across extracted auth work",
477
+ description: "Backport clearly portable auth changes introduced upstream after imported revision 58b56e15: 3196d5c (node-client login nonce), bb5b74f (JWT signature validation), and 92011ead (shared token verification for wallet signup onboarding). Apply them where they belong in this repo: hp-auth-service, hp-gateway if verification semantics surface there, and the upstream monorepo auth owner flows for the remaining wallet-login/shared-token work. This task must preserve the security behavior, not just copy code. It should explicitly reconcile with bd-k0y-10, bd-k0y-11, bd-k0y-12, and bd-k0y-13.",
478
+ acceptanceCriteria: "Map upstream commits 3196d5c, bb5b74f, and 92011ead to exact local targets; preserve nonce-backed node-client login, JWT signature validation, and shared-token verification behavior; add automated tests that fail without the hardening; document any deliberate non-ports.",
479
+ role: "extractor",
480
+ scope: [
481
+ "repos/spliter-monorepo/microservices/hp-auth-service/**",
482
+ "repos/spliter-monorepo/microservices/hp-gateway/**",
483
+ "repos/spliter-monorepo/humoongate/moongate/core-app/**"
484
+ ],
485
+ validation: ["integration:hg-auth-backport", "boundary:changed-files"],
486
+ validationDescriptions: {
487
+ "integration:hg-auth-backport": UPSTREAM_VALIDATION_DESCRIPTIONS["integration:hg-auth-backport"]
488
+ },
489
+ labels: ["upstream:monorepo", "kind:backport", "cluster:auth"]
490
+ },
491
+ "core-security": {
492
+ classification: "portable-now",
493
+ title: "[HG-002] Backport core-app and backend security fixes from post-import upstream",
494
+ description: "Backport the clearly portable security fixes from efdae709 (OTP validation bypass), f63830d (NoSQL operator injection hardening), d7f6919 (AES encryption), and af86ac16 (three critical/high vulnerabilities). Preserve the behavior in the vendored auth owner and the touched backend query surfaces instead of treating these as optional cleanups. The high-value touched files from af86ac16 must be reviewed and ported or consciously waived with rationale.",
495
+ acceptanceCriteria: "Review and port the exact security behaviors from efdae709, f63830d, d7f6919, and af86ac16; cover the listed core-app and backend touched files; add regression tests for OTP bypass, NoSQL operator injection, AES/encryption behavior, and rate-limit or redirect-state hardening; document any consciously waived upstream changes.",
496
+ role: "mechanic",
497
+ scope: [
498
+ "repos/spliter-monorepo/humoongate/moongate/core-app/**",
499
+ "repos/spliter-monorepo/humoongate/humanity/hp-backend-ts/**"
500
+ ],
501
+ validation: ["integration:hg-core-security-backport", "boundary:changed-files"],
502
+ validationDescriptions: {
503
+ "integration:hg-core-security-backport": UPSTREAM_VALIDATION_DESCRIPTIONS["integration:hg-core-security-backport"]
504
+ },
505
+ labels: ["upstream:monorepo", "kind:backport", "cluster:security"]
506
+ },
507
+ "boundary-hardening": {
508
+ classification: "portable-now",
509
+ title: "[HG-003] Backport request-boundary hardening for CORS and credential lookup flows",
510
+ description: "Backport the clearly portable boundary hardening from 083bbe4 (CORS config) and 36d52fba (credential lookup error hygiene). Preserve request-boundary behavior in the vendored backend and any analogous extracted credential-facing service seams so attackers cannot learn internals from credential lookup failures or exploit inconsistent CORS policy.",
511
+ acceptanceCriteria: "Port the exact request-boundary behavior from 083bbe4 and 36d52fba; preserve safe CORS policy and credential lookup error hygiene; add tests showing callers do not receive overly detailed credential lookup failures; document any extracted-service analogue that must mirror the behavior.",
512
+ role: "mechanic",
513
+ scope: [
514
+ "repos/spliter-monorepo/humoongate/humanity/hp-backend-ts/**",
515
+ "repos/spliter-monorepo/microservices/hp-credentials-service/**"
516
+ ],
517
+ validation: ["integration:hg-boundary-hardening", "boundary:changed-files"],
518
+ validationDescriptions: {
519
+ "integration:hg-boundary-hardening": UPSTREAM_VALIDATION_DESCRIPTIONS["integration:hg-boundary-hardening"]
520
+ },
521
+ labels: ["upstream:monorepo", "kind:backport", "cluster:boundary"]
522
+ },
523
+ "uudl-runtime": {
524
+ classification: "portable-now",
525
+ title: "[HG-004] Backport uudl runtime and packaging fixes from upstream",
526
+ description: "Backport the clearly portable uudl runtime fixes from 2141b4c, 1fa56d7, a9a0a99, 9fcfa6e, and ea5cb03. Preserve the runtime behavior rather than cherry-picking filenames: production dependency placement, tsconfig-paths resolution, aggregator cycle removal, AWS/MSK environment detection, and local KMS wiring all need to be reflected in whichever UUDL runtime this repo still owns.",
527
+ acceptanceCriteria: "Port the runtime behaviors from 2141b4c, 1fa56d7, a9a0a99, 9fcfa6e, and ea5cb03; preserve production dependency placement, tsconfig-paths runtime resolution, aggregator cycle removal, AWS/MSK environment detection, and local KMS configuration; add runtime-focused tests or smoke coverage for each preserved behavior.",
528
+ role: "extractor",
529
+ scope: ["repos/spliter-monorepo/humoongate/humanity/hp-uudl/**"],
530
+ validation: ["integration:hg-uudl-runtime", "boundary:changed-files"],
531
+ validationDescriptions: {
532
+ "integration:hg-uudl-runtime": UPSTREAM_VALIDATION_DESCRIPTIONS["integration:hg-uudl-runtime"]
533
+ },
534
+ labels: ["upstream:monorepo", "kind:backport", "cluster:uudl"]
535
+ },
536
+ "auth-triage": {
537
+ classification: "needs-human-triage",
538
+ title: "[HG-005] Triage post-import auth policy deltas and mobile-wallet SIWE changes",
539
+ description: "Review the upstream changes that are relevant but not safely auto-portable: 1262b75 (JWT_EXPIRES_IN 1d -> 7d), 15036424 (unlink-wallet endpoint and Wallet.default fix), and d4a47015 (unlink-wallet error handling). Produce an explicit keep/adapt/reject decision for each commit with rationale tied to the extracted auth plan, core-app ownership, and current threat model, and record the result in artifacts/bd-2ztk.5/decision-log.md so future agents do not silently re-litigate these decisions.",
540
+ acceptanceCriteria: "For 1262b75, 15036424, and d4a47015, produce explicit keep/adapt/reject decisions with rationale tied to auth ownership and threat model; record the decision in artifacts/bd-2ztk.5/decision-log.md; identify any follow-on implementation tasks if the answer is keep or adapt.",
541
+ role: "architect",
542
+ scope: ["artifacts/bd-2ztk.5/**", "docs/research/**"],
543
+ validation: ["boundary:hg-auth-triage"],
544
+ validationDescriptions: {
545
+ "boundary:hg-auth-triage": UPSTREAM_VALIDATION_DESCRIPTIONS["boundary:hg-auth-triage"]
546
+ },
547
+ labels: ["upstream:monorepo", "kind:backport", "cluster:triage"]
548
+ },
549
+ "generic-triage": {
550
+ classification: "needs-human-triage",
551
+ title: "[HG-007] Triage uncatalogued service-relevant upstream commits",
552
+ description: "Review future upstream commits that touch service-relevant paths but do not match the current portable-now catalog. For each commit or batch, produce keep/adapt/reject decisions with rationale, cite the touched paths and why they matter here, and either route them into an existing backport task or open a new follow-up task when they are worth preserving.",
553
+ acceptanceCriteria: "For each uncatalogued service-relevant upstream commit or batch, record keep/adapt/reject decisions with rationale in artifacts/bd-2ztk.7/decision-log.md; cite the commit hashes and touched paths; and either attach the work to an existing backport task or open a new follow-up task when preservation is warranted.",
554
+ role: "architect",
555
+ scope: ["artifacts/bd-2ztk.7/**", "docs/research/**"],
556
+ validation: ["boundary:hg-upstream-triage"],
557
+ validationDescriptions: {
558
+ "boundary:hg-upstream-triage": UPSTREAM_VALIDATION_DESCRIPTIONS["boundary:hg-upstream-triage"]
559
+ },
560
+ labels: ["upstream:monorepo", "kind:backport", "cluster:triage"]
561
+ }
562
+ };
563
+
564
+ // packages/server/src/server.ts
565
+ var RIG_WORKSPACE_ID = "rig-local-workspace";
566
+ var serverPathEnvQueue = Promise.resolve();
567
+ if (false) {}
568
+
569
+ // packages/server/src/server-helpers/ws-router.ts
570
+ function redactRemoteEndpoint(endpoint) {
571
+ const { token, ...rest } = endpoint;
572
+ return {
573
+ ...rest,
574
+ token: "",
575
+ tokenConfigured: token.trim().length > 0
576
+ };
577
+ }
578
+ async function routeWebSocketRequest(state, deps, request) {
579
+ return deps.withServerPathEnv(state.projectRoot, async () => {
580
+ const body = request.body;
581
+ switch (body._tag) {
582
+ case RIG_WS_METHODS.getSnapshot: {
583
+ return deps.snapshotService.getEngineSnapshot({
584
+ remoteHosts: state.remoteHosts,
585
+ remoteConnections: state.remoteConnections,
586
+ sequence: state.sequence,
587
+ runProcesses: state.runProcesses
588
+ });
589
+ }
590
+ case RIG_WS_METHODS.replayEvents: {
591
+ const fromSequenceExclusive = typeof body.fromSequenceExclusive === "number" ? body.fromSequenceExclusive : Number(body.fromSequenceExclusive ?? 0);
592
+ const from = Number.isFinite(fromSequenceExclusive) ? fromSequenceExclusive : 0;
593
+ const limitRaw = typeof body.limit === "number" ? body.limit : Number(body.limit ?? 1000);
594
+ const limit = Number.isFinite(limitRaw) ? Math.max(1, Math.min(Math.trunc(limitRaw), 1000)) : 1000;
595
+ const replay = [];
596
+ for (let index = state.eventJournal.length - 1;index >= 0 && replay.length < limit; index -= 1) {
597
+ const event = state.eventJournal[index];
598
+ if (event && event.sequence > from) {
599
+ replay.push(event);
600
+ }
601
+ }
602
+ return replay.reverse();
603
+ }
604
+ case RIG_WS_METHODS.listWorkspaces: {
605
+ const workspace = await deps.readWorkspaceSummaryRecord(state.projectRoot);
606
+ return [workspace];
607
+ }
608
+ case RIG_WS_METHODS.getWorkspace: {
609
+ const workspaceId = normalizeString(body.workspaceId) ?? RIG_WORKSPACE_ID;
610
+ const payload = await deps.snapshotService.getRigSnapshot();
611
+ const taskIds = new Set(payload.tasks.filter((task) => task.workspaceId === workspaceId).map((task) => task.id));
612
+ const runIds = new Set(payload.runs.filter((run) => run.workspaceId === workspaceId).map((run) => run.id));
613
+ return {
614
+ workspace: payload.workspace.id === workspaceId ? payload.workspace : null,
615
+ graph: payload.graphs.find((graph) => graph.workspaceId === workspaceId) ?? null,
616
+ tasks: payload.tasks.filter((task) => task.workspaceId === workspaceId),
617
+ runs: payload.runs.filter((run) => run.workspaceId === workspaceId),
618
+ approvals: payload.approvals.filter((approval) => runIds.has(approval.runId)),
619
+ userInputs: payload.userInputs.filter((entry) => runIds.has(entry.runId)),
620
+ artifacts: payload.artifacts.filter((artifact) => artifact.taskId && taskIds.has(artifact.taskId)),
621
+ updatedAt: payload.snapshot.updatedAt
622
+ };
623
+ }
624
+ case RIG_WS_METHODS.getRunLogs: {
625
+ const runId = normalizeString(body.runId);
626
+ if (!runId) {
627
+ throw new Error("runId is required");
628
+ }
629
+ const limit = typeof body.limit === "number" ? body.limit : undefined;
630
+ const cursor = normalizeString(body.cursor);
631
+ return readRunLogsPage(state.projectRoot, runId, { limit, cursor });
632
+ }
633
+ case RIG_WS_METHODS.createAdhocRun: {
634
+ const runId = normalizeString(body.runId);
635
+ const workspaceId = normalizeString(body.workspaceId);
636
+ const title = normalizeString(body.title);
637
+ const createdAt = normalizeString(body.createdAt) ?? new Date().toISOString();
638
+ if (!runId || !workspaceId || !title) {
639
+ throw new Error("runId, workspaceId, and title are required");
640
+ }
641
+ await deps.createRunRecord(state.projectRoot, {
642
+ runId,
643
+ workspaceId,
644
+ title,
645
+ runtimeAdapter: normalizeString(body.runtimeAdapter) ?? undefined,
646
+ model: normalizeString(body.model) ?? undefined,
647
+ runtimeMode: normalizeString(body.runtimeMode) ?? undefined,
648
+ interactionMode: normalizeString(body.interactionMode) ?? undefined,
649
+ executionTarget: normalizeString(body.executionTarget) === "remote" ? "remote" : "local",
650
+ remoteHostId: normalizeString(body.remoteHostId),
651
+ initialPrompt: normalizeString(body.initialPrompt) ?? undefined,
652
+ createdAt
653
+ });
654
+ if (normalizeString(body.executionTarget) !== "remote") {
655
+ queueMicrotask(() => {
656
+ deps.startLocalRun(state, runId).catch((error) => {
657
+ console.error("[server] failed to start local adhoc run", error);
658
+ });
659
+ });
660
+ }
661
+ const event = deps.emitRigEvent(state, {
662
+ type: "rig.run.created",
663
+ aggregateId: runId,
664
+ payload: { runId, workspaceId, title, taskId: null },
665
+ createdAt
666
+ });
667
+ deps.broadcastSnapshotInvalidation(state, "run-created");
668
+ return { sequence: event.sequence };
669
+ }
670
+ case RIG_WS_METHODS.createTaskRun: {
671
+ const runId = normalizeString(body.runId);
672
+ const workspaceId = normalizeString(body.workspaceId);
673
+ const taskId = normalizeString(body.taskId);
674
+ const createdAt = normalizeString(body.createdAt) ?? new Date().toISOString();
675
+ if (!runId || !workspaceId || !taskId) {
676
+ throw new Error("runId, workspaceId, and taskId are required");
677
+ }
678
+ dequeueTaskState(state.projectRoot, taskId);
679
+ await deps.createRunRecord(state.projectRoot, {
680
+ runId,
681
+ workspaceId,
682
+ taskId,
683
+ runtimeAdapter: normalizeString(body.runtimeAdapter) ?? undefined,
684
+ model: normalizeString(body.model) ?? undefined,
685
+ runtimeMode: normalizeString(body.runtimeMode) ?? undefined,
686
+ interactionMode: normalizeString(body.interactionMode) ?? undefined,
687
+ executionTarget: normalizeString(body.executionTarget) === "remote" ? "remote" : "local",
688
+ remoteHostId: normalizeString(body.remoteHostId),
689
+ initialPrompt: normalizeString(body.initialPrompt) ?? undefined,
690
+ createdAt
691
+ });
692
+ if (normalizeString(body.executionTarget) !== "remote") {
693
+ queueMicrotask(() => {
694
+ deps.startLocalRun(state, runId).catch((error) => {
695
+ console.error("[server] failed to start local task run", error);
696
+ });
697
+ });
698
+ }
699
+ const event = deps.emitRigEvent(state, {
700
+ type: "rig.task.run-created",
701
+ aggregateId: taskId,
702
+ payload: { runId, workspaceId, taskId },
703
+ createdAt
704
+ });
705
+ deps.broadcastSnapshotInvalidation(state, "task-run-created");
706
+ queueMicrotask(() => {
707
+ deps.reconcileScheduler(state, "task-run-created");
708
+ });
709
+ return { sequence: event.sequence, accepted: true };
710
+ }
711
+ case RIG_WS_METHODS.deleteRun: {
712
+ const runId = normalizeString(body.runId);
713
+ const createdAt = normalizeString(body.createdAt) ?? new Date().toISOString();
714
+ const purgeTaskArtifacts = body.purgeTaskArtifacts === true;
715
+ if (!runId) {
716
+ throw new Error("runId is required");
717
+ }
718
+ if (state.runProcesses.has(runId)) {
719
+ await deps.stopRunRecord(state, { runId, createdAt });
720
+ }
721
+ const result = await deleteRunState(state.projectRoot, {
722
+ runId,
723
+ purgeRuntime: true,
724
+ purgeTaskArtifacts
725
+ });
726
+ const event = deps.emitRigEvent(state, {
727
+ type: "rig.run.deleted",
728
+ aggregateId: runId,
729
+ payload: {
730
+ runId,
731
+ taskId: result.taskId,
732
+ runtimeIds: result.runtimeIds,
733
+ taskArtifactsDeleted: result.taskArtifactsDeleted
734
+ },
735
+ createdAt
736
+ });
737
+ deps.broadcastSnapshotInvalidation(state, "run-deleted");
738
+ return { sequence: event.sequence };
739
+ }
740
+ case RIG_WS_METHODS.enqueueTask: {
741
+ const taskId = normalizeString(body.taskId);
742
+ const createdAt = normalizeString(body.createdAt) ?? new Date().toISOString();
743
+ const score = typeof body.score === "number" ? body.score : Number(body.score ?? 100);
744
+ if (!taskId || !Number.isFinite(score)) {
745
+ throw new Error("taskId and numeric score are required");
746
+ }
747
+ const queue = enqueueTaskState(state.projectRoot, taskId, score);
748
+ const event = deps.emitRigEvent(state, {
749
+ type: "rig.task.enqueued",
750
+ aggregateId: taskId,
751
+ payload: { taskId, score, queueLength: queue.length },
752
+ createdAt
753
+ });
754
+ deps.broadcastSnapshotInvalidation(state, "task-enqueued");
755
+ await deps.reconcileScheduler(state, "task-enqueued");
756
+ return { sequence: event.sequence };
757
+ }
758
+ case RIG_WS_METHODS.resumeRun: {
759
+ const runId = normalizeString(body.runId);
760
+ const createdAt = normalizeString(body.createdAt) ?? new Date().toISOString();
761
+ const promptOverride = normalizeString(body.promptOverride);
762
+ if (!runId) {
763
+ throw new Error("runId is required");
764
+ }
765
+ await deps.resumeRunRecord(state, { runId, createdAt, promptOverride });
766
+ const event = deps.emitRigEvent(state, {
767
+ type: "rig.run.resumed",
768
+ aggregateId: runId,
769
+ payload: { runId, promptOverride: promptOverride ?? null },
770
+ createdAt
771
+ });
772
+ deps.broadcastSnapshotInvalidation(state, "run-resumed");
773
+ return { sequence: event.sequence };
774
+ }
775
+ case RIG_WS_METHODS.submitRunMessage: {
776
+ const runId = normalizeString(body.runId);
777
+ const messageId = normalizeString(body.messageId);
778
+ const text = normalizeString(body.text);
779
+ const createdAt = normalizeString(body.createdAt) ?? new Date().toISOString();
780
+ if (!runId || !messageId || text === null) {
781
+ throw new Error("runId, messageId, and text are required");
782
+ }
783
+ const run = readAuthorityRun8(state.projectRoot, runId);
784
+ if (!run) {
785
+ throw new Error(`Run not found: ${runId}`);
786
+ }
787
+ if (run.mode === "local") {
788
+ if (state.runProcesses.has(runId)) {
789
+ throw new Error("Run is already active. Wait for the current turn to finish before sending another message.");
790
+ }
791
+ await deps.startLocalRun(state, runId, { promptOverride: text });
792
+ } else {
793
+ deps.appendRunMessage(state.projectRoot, {
794
+ runId,
795
+ messageId,
796
+ text,
797
+ attachments: Array.isArray(body.attachments) ? body.attachments : undefined,
798
+ createdAt
799
+ });
800
+ }
801
+ const event = deps.emitRigEvent(state, {
802
+ type: "rig.run.message-submitted",
803
+ aggregateId: runId,
804
+ payload: { runId, messageId },
805
+ createdAt
806
+ });
807
+ deps.broadcastSnapshotInvalidation(state, "run-message");
808
+ return { sequence: event.sequence };
809
+ }
810
+ case RIG_WS_METHODS.interruptRun: {
811
+ const runId = normalizeString(body.runId);
812
+ const createdAt = normalizeString(body.createdAt) ?? new Date().toISOString();
813
+ if (!runId) {
814
+ throw new Error("runId is required");
815
+ }
816
+ deps.stopRunRecord(state, { runId, createdAt }).catch((error) => {
817
+ console.error("[server] failed to interrupt run", error);
818
+ });
819
+ const event = deps.emitRigEvent(state, {
820
+ type: "rig.run.interrupted",
821
+ aggregateId: runId,
822
+ payload: { runId },
823
+ createdAt
824
+ });
825
+ deps.broadcastSnapshotInvalidation(state, "run-interrupted");
826
+ return { sequence: event.sequence };
827
+ }
828
+ case RIG_WS_METHODS.stopRun: {
829
+ const runId = normalizeString(body.runId);
830
+ const createdAt = normalizeString(body.createdAt) ?? new Date().toISOString();
831
+ if (!runId) {
832
+ throw new Error("runId is required");
833
+ }
834
+ deps.stopRunRecord(state, {
835
+ runId,
836
+ createdAt
837
+ }).catch((error) => {
838
+ console.error("[server] failed to stop run", error);
839
+ });
840
+ const event = deps.emitRigEvent(state, {
841
+ type: "rig.run.stopped",
842
+ aggregateId: runId,
843
+ payload: { runId },
844
+ createdAt
845
+ });
846
+ deps.broadcastSnapshotInvalidation(state, "run-stopped");
847
+ queueMicrotask(() => {
848
+ deps.reconcileScheduler(state, "run-stopped");
849
+ });
850
+ return { sequence: event.sequence, accepted: true };
851
+ }
852
+ case RIG_WS_METHODS.resolveApproval: {
853
+ const runId = normalizeString(body.runId);
854
+ const requestId = normalizeString(body.requestId);
855
+ const decision = normalizeString(body.decision);
856
+ if (!runId || !requestId || decision !== "accept" && decision !== "acceptForSession" && decision !== "decline" && decision !== "cancel") {
857
+ throw new Error("runId, requestId, and decision accept|acceptForSession|decline|cancel are required");
858
+ }
859
+ resolveApproval(state.projectRoot, {
860
+ runId,
861
+ requestId,
862
+ decision: decision === "decline" || decision === "cancel" ? "reject" : "approve"
863
+ });
864
+ const event = deps.emitRigEvent(state, {
865
+ type: "rig.approval.resolved",
866
+ aggregateId: runId,
867
+ payload: { runId, requestId, decision }
868
+ });
869
+ deps.broadcastSnapshotInvalidation(state, "approval-resolved");
870
+ return { sequence: event.sequence };
871
+ }
872
+ case RIG_WS_METHODS.resolveUserInput: {
873
+ const runId = normalizeString(body.runId);
874
+ const requestId = normalizeString(body.requestId);
875
+ const answers = body.answers && typeof body.answers === "object" ? Object.fromEntries(Object.entries(body.answers).flatMap(([key, value]) => typeof value === "string" ? [[key, value]] : [])) : {};
876
+ if (!runId || !requestId || Object.keys(answers).length === 0) {
877
+ throw new Error("runId, requestId, and string-valued answers are required");
878
+ }
879
+ respondToUserInput(state.projectRoot, { runId, requestId, answers });
880
+ const event = deps.emitRigEvent(state, {
881
+ type: "rig.user-input.resolved",
882
+ aggregateId: runId,
883
+ payload: { runId, requestId }
884
+ });
885
+ deps.broadcastSnapshotInvalidation(state, "user-input-resolved");
886
+ return { sequence: event.sequence };
887
+ }
888
+ case RIG_WS_METHODS.getTaskArtifacts: {
889
+ const taskId = normalizeString(body.taskId);
890
+ if (!taskId) {
891
+ throw new Error("taskId is required");
892
+ }
893
+ return await deps.listArtifactSummaries(state.projectRoot, taskId);
894
+ }
895
+ case ORCHESTRATION_WS_METHODS.getSnapshot:
896
+ return deps.buildOrchestrationSnapshot(state);
897
+ case ORCHESTRATION_WS_METHODS.dispatchCommand: {
898
+ const command = body.command && typeof body.command === "object" ? body.command : null;
899
+ if (!command) {
900
+ throw new Error("command is required");
901
+ }
902
+ deps.applyOrchestrationCommand(state, command);
903
+ const sequence = nextSequence(state);
904
+ deps.broadcastPush(state, {
905
+ type: "push",
906
+ channel: ORCHESTRATION_WS_CHANNELS.domainEvent,
907
+ data: {
908
+ sequence,
909
+ eventId: `event-${sequence}`,
910
+ aggregateKind: "thread",
911
+ aggregateId: normalizeString(command.threadId) ?? normalizeString(command.projectId) ?? "orchestration",
912
+ occurredAt: normalizeString(command.createdAt) ?? new Date().toISOString(),
913
+ commandId: normalizeString(command.commandId),
914
+ causationEventId: null,
915
+ correlationId: normalizeString(command.commandId),
916
+ metadata: {},
917
+ type: `${normalizeString(command.type) ?? "command"}.accepted`,
918
+ payload: command
919
+ }
920
+ });
921
+ return { sequence };
922
+ }
923
+ case ORCHESTRATION_WS_METHODS.getTurnDiff:
924
+ case ORCHESTRATION_WS_METHODS.getFullThreadDiff: {
925
+ const threadId = normalizeString(body.threadId);
926
+ if (!threadId) {
927
+ throw new Error("threadId is required");
928
+ }
929
+ const fromTurnCount = typeof body.fromTurnCount === "number" ? body.fromTurnCount : Number(body.fromTurnCount ?? 0);
930
+ const toTurnCount = typeof body.toTurnCount === "number" ? body.toTurnCount : Number(body.toTurnCount ?? 0);
931
+ return {
932
+ threadId,
933
+ fromTurnCount: Number.isFinite(fromTurnCount) ? Math.max(0, fromTurnCount) : 0,
934
+ toTurnCount: Number.isFinite(toTurnCount) ? Math.max(0, toTurnCount) : 0,
935
+ diff: ""
936
+ };
937
+ }
938
+ case ORCHESTRATION_WS_METHODS.replayEvents:
939
+ return [];
940
+ case WS_METHODS.serverGetConfig:
941
+ return deps.buildServerConfig(state);
942
+ case WS_METHODS.serverUpsertKeybinding:
943
+ return {
944
+ keybindings: [],
945
+ issues: []
946
+ };
947
+ case WS_METHODS.projectsListDirectory: {
948
+ const cwd = normalizeString(body.cwd);
949
+ const relativePath = normalizeString(body.relativePath);
950
+ const limit = typeof body.limit === "number" ? body.limit : Number(body.limit ?? 200);
951
+ if (!cwd || !relativePath || !Number.isFinite(limit)) {
952
+ throw new Error("cwd, relativePath, and limit are required");
953
+ }
954
+ return deps.listDirectoryEntries({ cwd, relativePath, limit });
955
+ }
956
+ case WS_METHODS.projectsReadFile: {
957
+ const cwd = normalizeString(body.cwd);
958
+ const relativePath = normalizeString(body.relativePath);
959
+ if (!cwd || !relativePath) {
960
+ throw new Error("cwd and relativePath are required");
961
+ }
962
+ return deps.readProjectFile({ cwd, relativePath });
963
+ }
964
+ case WS_METHODS.projectsWriteFile: {
965
+ const cwd = normalizeString(body.cwd);
966
+ const relativePath = normalizeString(body.relativePath);
967
+ const contents = typeof body.contents === "string" ? body.contents : null;
968
+ if (!cwd || !relativePath || contents === null) {
969
+ throw new Error("cwd, relativePath, and contents are required");
970
+ }
971
+ return deps.writeProjectFile({ cwd, relativePath, contents });
972
+ }
973
+ case WS_METHODS.projectsSearchEntries: {
974
+ const cwd = normalizeString(body.cwd);
975
+ const query = normalizeString(body.query);
976
+ const limit = typeof body.limit === "number" ? body.limit : Number(body.limit ?? 100);
977
+ if (!cwd || !query || !Number.isFinite(limit)) {
978
+ throw new Error("cwd, query, and limit are required");
979
+ }
980
+ return deps.searchProjectEntries({ cwd, query, limit });
981
+ }
982
+ case WS_METHODS.shellOpenInEditor: {
983
+ const cwd = normalizeString(body.cwd);
984
+ const editor = normalizeString(body.editor);
985
+ if (!cwd || !editor) {
986
+ throw new Error("cwd and editor are required");
987
+ }
988
+ deps.openInEditor(cwd, editor);
989
+ return {};
990
+ }
991
+ case WS_METHODS.gitStatus: {
992
+ const cwd = normalizeString(body.cwd);
993
+ if (!cwd)
994
+ throw new Error("cwd is required");
995
+ return deps.readGitStatus(cwd);
996
+ }
997
+ case WS_METHODS.gitReadWorkingTreePatch: {
998
+ const cwd = normalizeString(body.cwd);
999
+ if (!cwd)
1000
+ throw new Error("cwd is required");
1001
+ return deps.readGitPatch(cwd, normalizeString(body.relativePath) ?? undefined);
1002
+ }
1003
+ case WS_METHODS.gitListBranches: {
1004
+ const cwd = normalizeString(body.cwd);
1005
+ if (!cwd)
1006
+ throw new Error("cwd is required");
1007
+ return deps.listGitBranches(cwd);
1008
+ }
1009
+ case WS_METHODS.gitCheckout: {
1010
+ const cwd = normalizeString(body.cwd);
1011
+ const branch = normalizeString(body.branch);
1012
+ if (!cwd || !branch)
1013
+ throw new Error("cwd and branch are required");
1014
+ const result = deps.gitResult(cwd, ["checkout", branch]);
1015
+ if (result.exitCode !== 0)
1016
+ throw new Error(result.stderr || "git checkout failed");
1017
+ return {};
1018
+ }
1019
+ case WS_METHODS.gitCreateBranch: {
1020
+ const cwd = normalizeString(body.cwd);
1021
+ const branch = normalizeString(body.branch);
1022
+ if (!cwd || !branch)
1023
+ throw new Error("cwd and branch are required");
1024
+ const result = deps.gitResult(cwd, ["branch", branch]);
1025
+ if (result.exitCode !== 0)
1026
+ throw new Error(result.stderr || "git branch failed");
1027
+ return {};
1028
+ }
1029
+ case WS_METHODS.gitCreateWorktree: {
1030
+ const cwd = normalizeString(body.cwd);
1031
+ const branch = normalizeString(body.branch);
1032
+ const newBranch = normalizeString(body.newBranch);
1033
+ const worktreePath = normalizeString(body.path);
1034
+ if (!cwd || !branch || !newBranch) {
1035
+ throw new Error("cwd, branch, and newBranch are required");
1036
+ }
1037
+ return deps.createGitWorktree({ cwd, branch, newBranch, path: worktreePath });
1038
+ }
1039
+ case WS_METHODS.gitRemoveWorktree: {
1040
+ const cwd = normalizeString(body.cwd);
1041
+ const worktreePath = normalizeString(body.path);
1042
+ if (!cwd || !worktreePath) {
1043
+ throw new Error("cwd and path are required");
1044
+ }
1045
+ deps.removeGitWorktree({ cwd, path: worktreePath, force: body.force === true });
1046
+ return {};
1047
+ }
1048
+ case WS_METHODS.gitInit: {
1049
+ const cwd = normalizeString(body.cwd);
1050
+ if (!cwd)
1051
+ throw new Error("cwd is required");
1052
+ const result = deps.gitResult(cwd, ["init"]);
1053
+ if (result.exitCode !== 0)
1054
+ throw new Error(result.stderr || "git init failed");
1055
+ return {};
1056
+ }
1057
+ case WS_METHODS.gitPull: {
1058
+ const cwd = normalizeString(body.cwd);
1059
+ if (!cwd)
1060
+ throw new Error("cwd is required");
1061
+ const branch = deps.gitResult(cwd, ["branch", "--show-current"]).stdout.trim();
1062
+ const result = deps.gitResult(cwd, ["pull", "--ff-only"]);
1063
+ if (result.exitCode !== 0 && !result.stderr.includes("up to date")) {
1064
+ throw new Error(result.stderr || "git pull failed");
1065
+ }
1066
+ const status = result.stdout.includes("Already up to date") || result.stderr.includes("up to date") ? "skipped_up_to_date" : "pulled";
1067
+ return {
1068
+ status,
1069
+ branch,
1070
+ upstreamBranch: branch || null
1071
+ };
1072
+ }
1073
+ case WS_METHODS.gitRunStackedAction: {
1074
+ const cwd = normalizeString(body.cwd);
1075
+ const action = normalizeString(body.action);
1076
+ if (!cwd || !action)
1077
+ throw new Error("cwd and action are required");
1078
+ const status = deps.readGitStatus(cwd);
1079
+ const result = {
1080
+ action,
1081
+ branch: { status: "skipped_not_requested" },
1082
+ commit: {
1083
+ status: status.hasWorkingTreeChanges ? "created" : "skipped_no_changes",
1084
+ ...status.branch ? { subject: status.branch } : {}
1085
+ },
1086
+ push: { status: "skipped_not_requested" },
1087
+ pr: { status: "skipped_not_requested" }
1088
+ };
1089
+ return result;
1090
+ }
1091
+ case WS_METHODS.terminalOpen: {
1092
+ const threadId = normalizeString(body.threadId);
1093
+ const terminalId = normalizeString(body.terminalId);
1094
+ const cwd = normalizeString(body.cwd);
1095
+ if (!threadId || !terminalId || !cwd) {
1096
+ throw new Error("threadId, terminalId, and cwd are required");
1097
+ }
1098
+ const env = body.env && typeof body.env === "object" ? Object.fromEntries(Object.entries(body.env).flatMap(([key, value]) => typeof value === "string" ? [[key, value]] : [])) : undefined;
1099
+ const session = deps.getOrCreateTerminalSession(state, { threadId, terminalId, cwd, env });
1100
+ return terminalSnapshot(session);
1101
+ }
1102
+ case WS_METHODS.terminalWrite: {
1103
+ const threadId = normalizeString(body.threadId);
1104
+ const terminalId = normalizeString(body.terminalId);
1105
+ const data = typeof body.data === "string" ? body.data : null;
1106
+ if (!threadId || !terminalId || data === null) {
1107
+ throw new Error("threadId, terminalId, and data are required");
1108
+ }
1109
+ const session = state.terminalSessions.get(terminalSessionKey(threadId, terminalId));
1110
+ if (!session?.child?.stdin)
1111
+ throw new Error("Terminal session not found");
1112
+ session.child.stdin.write(data);
1113
+ session.updatedAt = new Date().toISOString();
1114
+ return {};
1115
+ }
1116
+ case WS_METHODS.terminalResize:
1117
+ return {};
1118
+ case WS_METHODS.terminalClear: {
1119
+ const threadId = normalizeString(body.threadId);
1120
+ const terminalId = normalizeString(body.terminalId);
1121
+ if (!threadId || !terminalId)
1122
+ throw new Error("threadId and terminalId are required");
1123
+ const session = state.terminalSessions.get(terminalSessionKey(threadId, terminalId));
1124
+ if (!session)
1125
+ throw new Error("Terminal session not found");
1126
+ session.history = "";
1127
+ session.updatedAt = new Date().toISOString();
1128
+ deps.pushTerminalEvent(state, {
1129
+ type: "cleared",
1130
+ threadId,
1131
+ terminalId,
1132
+ createdAt: session.updatedAt
1133
+ });
1134
+ return {};
1135
+ }
1136
+ case WS_METHODS.terminalClose: {
1137
+ const threadId = normalizeString(body.threadId);
1138
+ const terminalId = normalizeString(body.terminalId) ?? "default";
1139
+ if (!threadId)
1140
+ throw new Error("threadId is required");
1141
+ const sessionKey = terminalSessionKey(threadId, terminalId);
1142
+ const session = state.terminalSessions.get(sessionKey);
1143
+ if (session?.child) {
1144
+ session.child.kill("SIGTERM");
1145
+ }
1146
+ state.terminalSessions.delete(sessionKey);
1147
+ return {};
1148
+ }
1149
+ case WS_METHODS.remoteListEndpoints:
1150
+ return listManagedRemoteEndpoints2(undefined, state.projectRoot).map((endpoint) => redactRemoteEndpoint(endpoint));
1151
+ case WS_METHODS.remoteRegisterEndpoint: {
1152
+ const alias = normalizeString(body.alias);
1153
+ const host = normalizeString(body.host);
1154
+ const token = normalizeString(body.token);
1155
+ const port = typeof body.port === "number" ? body.port : Number(body.port);
1156
+ if (!alias || !host || !Number.isFinite(port)) {
1157
+ throw new Error("alias, host, and port are required");
1158
+ }
1159
+ const endpoint = upsertManagedRemoteEndpoint2({ alias, host, port, token: token ?? undefined }, undefined, state.projectRoot);
1160
+ deps.emitRigEvent(state, {
1161
+ type: "rig.remote.endpoint-registered",
1162
+ aggregateId: endpoint.id,
1163
+ payload: { endpointId: endpoint.id, alias, host, port }
1164
+ });
1165
+ deps.broadcastSnapshotInvalidation(state, "remote-endpoint-registered");
1166
+ return redactRemoteEndpoint(endpoint);
1167
+ }
1168
+ case WS_METHODS.remoteRemoveEndpoint: {
1169
+ const endpointId = normalizeString(body.endpointId);
1170
+ if (!endpointId) {
1171
+ throw new Error("endpointId is required");
1172
+ }
1173
+ const removed = removeManagedRemoteEndpoint2(endpointId, undefined, state.projectRoot);
1174
+ if (!removed) {
1175
+ throw new Error("Remote endpoint not found");
1176
+ }
1177
+ deps.emitRigEvent(state, {
1178
+ type: "rig.remote.endpoint-removed",
1179
+ aggregateId: endpointId,
1180
+ payload: { endpointId }
1181
+ });
1182
+ deps.broadcastSnapshotInvalidation(state, "remote-endpoint-removed");
1183
+ return { ok: true };
1184
+ }
1185
+ case WS_METHODS.remoteConnect: {
1186
+ const endpointId = normalizeString(body.endpointId);
1187
+ if (!endpointId) {
1188
+ throw new Error("endpointId is required");
1189
+ }
1190
+ const endpoints = buildRemoteEndpointSnapshot(state.projectRoot, state.remoteHosts);
1191
+ const endpoint = endpoints.find((entry) => entry.id === endpointId);
1192
+ if (!endpoint) {
1193
+ throw new Error("Remote endpoint not found");
1194
+ }
1195
+ const connectedAt = new Date().toISOString();
1196
+ state.remoteConnections.set(endpointId, {
1197
+ endpointId,
1198
+ status: "connected",
1199
+ error: null,
1200
+ connectedAt,
1201
+ tokenExpiresAt: null,
1202
+ latencyMs: null,
1203
+ subscribedEvents: []
1204
+ });
1205
+ deps.broadcastSnapshotInvalidation(state);
1206
+ return {};
1207
+ }
1208
+ case WS_METHODS.remoteDisconnect: {
1209
+ const endpointId = normalizeString(body.endpointId);
1210
+ if (!endpointId) {
1211
+ throw new Error("endpointId is required");
1212
+ }
1213
+ state.remoteConnections.set(endpointId, {
1214
+ endpointId,
1215
+ status: "disconnected",
1216
+ error: null,
1217
+ connectedAt: null,
1218
+ tokenExpiresAt: null,
1219
+ latencyMs: null,
1220
+ subscribedEvents: []
1221
+ });
1222
+ deps.broadcastSnapshotInvalidation(state);
1223
+ return {};
1224
+ }
1225
+ case WS_METHODS.remoteCreateRunForTask: {
1226
+ const runId = normalizeString(body.runId);
1227
+ const workspaceId = normalizeString(body.workspaceId);
1228
+ const taskId = normalizeString(body.taskId);
1229
+ const endpointId = normalizeString(body.endpointId);
1230
+ const createdAt = normalizeString(body.createdAt) ?? new Date().toISOString();
1231
+ if (!runId || !workspaceId || !taskId || !endpointId) {
1232
+ throw new Error("runId, workspaceId, taskId, and endpointId are required");
1233
+ }
1234
+ await deps.createRunRecord(state.projectRoot, {
1235
+ runId,
1236
+ workspaceId,
1237
+ taskId,
1238
+ runtimeAdapter: normalizeString(body.runtimeAdapter) ?? undefined,
1239
+ model: normalizeString(body.model) ?? undefined,
1240
+ runtimeMode: normalizeString(body.runtimeMode) ?? undefined,
1241
+ interactionMode: normalizeString(body.interactionMode) ?? undefined,
1242
+ executionTarget: "remote",
1243
+ remoteHostId: endpointId,
1244
+ createdAt
1245
+ });
1246
+ const sequence = nextSequence(state);
1247
+ deps.broadcastSnapshotInvalidation(state);
1248
+ return { ok: true, sequence };
1249
+ }
1250
+ case WS_METHODS.remoteTerminalOpen: {
1251
+ const threadId = normalizeString(body.threadId);
1252
+ const terminalId = normalizeString(body.terminalId);
1253
+ const cwd = normalizeString(body.cwd) ?? state.projectRoot;
1254
+ if (!threadId || !terminalId) {
1255
+ throw new Error("threadId and terminalId are required");
1256
+ }
1257
+ const env = body.env && typeof body.env === "object" ? Object.fromEntries(Object.entries(body.env).flatMap(([key, value]) => typeof value === "string" ? [[key, value]] : [])) : undefined;
1258
+ return terminalSnapshot(deps.getOrCreateTerminalSession(state, { threadId, terminalId, cwd, env }));
1259
+ }
1260
+ case WS_METHODS.remoteTerminalWrite:
1261
+ return routeWebSocketRequest(state, deps, {
1262
+ id: request.id,
1263
+ body: {
1264
+ _tag: WS_METHODS.terminalWrite,
1265
+ threadId: normalizeString(body.threadId) ?? "",
1266
+ terminalId: normalizeString(body.terminalId) ?? "",
1267
+ data: typeof body.data === "string" ? body.data : ""
1268
+ }
1269
+ });
1270
+ case WS_METHODS.remoteTerminalResize:
1271
+ return {};
1272
+ case WS_METHODS.remoteTerminalClose:
1273
+ return routeWebSocketRequest(state, deps, {
1274
+ id: request.id,
1275
+ body: {
1276
+ _tag: WS_METHODS.terminalClose,
1277
+ threadId: normalizeString(body.threadId) ?? "",
1278
+ terminalId: normalizeString(body.terminalId) ?? ""
1279
+ }
1280
+ });
1281
+ case WS_METHODS.remoteTestEndpoint: {
1282
+ const host = normalizeString(body.host);
1283
+ const token = normalizeString(body.token);
1284
+ const port = typeof body.port === "number" ? body.port : Number(body.port);
1285
+ if (!host || !Number.isFinite(port)) {
1286
+ throw new Error("host and port are required");
1287
+ }
1288
+ const endpoint = resolveRemoteEndpoint2({
1289
+ projectRoot: state.projectRoot,
1290
+ host,
1291
+ port: String(port),
1292
+ token: token ?? undefined
1293
+ });
1294
+ const client = new RemoteWsClient2(endpoint);
1295
+ const startedAt = Date.now();
1296
+ await client.connect();
1297
+ await client.ping();
1298
+ client.disconnect();
1299
+ return { latencyMs: Math.max(1, Date.now() - startedAt) };
1300
+ }
1301
+ default:
1302
+ throw new Error(`Unsupported websocket method: ${String(body._tag)}`);
1303
+ }
1304
+ });
1305
+ }
1306
+ export {
1307
+ routeWebSocketRequest
1308
+ };