@h-rig/server 0.0.6-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -0
- package/dist/src/bootstrap.js +161 -0
- package/dist/src/index.js +13153 -0
- package/dist/src/inspector/agent-runtime.js +1077 -0
- package/dist/src/inspector/analysis.js +41 -0
- package/dist/src/inspector/discovery.js +137 -0
- package/dist/src/inspector/journal.js +518 -0
- package/dist/src/inspector/mission.js +562 -0
- package/dist/src/inspector/prompt.js +97 -0
- package/dist/src/inspector/provider-session.js +65 -0
- package/dist/src/inspector/reconcile.js +118 -0
- package/dist/src/inspector/review.js +13 -0
- package/dist/src/inspector/service.js +1759 -0
- package/dist/src/inspector/skills.js +155 -0
- package/dist/src/inspector/tools.js +1592 -0
- package/dist/src/inspector/types.js +1 -0
- package/dist/src/inspector/upstream-sync.js +479 -0
- package/dist/src/orchestration.js +402 -0
- package/dist/src/remote.js +123 -0
- package/dist/src/scheduler.js +84 -0
- package/dist/src/server-helpers/broadcasters.js +161 -0
- package/dist/src/server-helpers/conversation-snapshot.js +382 -0
- package/dist/src/server-helpers/event-emitter.js +41 -0
- package/dist/src/server-helpers/github-auth-store.js +155 -0
- package/dist/src/server-helpers/github-credentials.js +38 -0
- package/dist/src/server-helpers/github-project-status-sync.js +196 -0
- package/dist/src/server-helpers/github-projects.js +147 -0
- package/dist/src/server-helpers/github-reconciler.js +89 -0
- package/dist/src/server-helpers/http-router.js +3781 -0
- package/dist/src/server-helpers/http-utils.js +135 -0
- package/dist/src/server-helpers/inspector-agent-lifecycle.js +104 -0
- package/dist/src/server-helpers/inspector-jobs.js +4145 -0
- package/dist/src/server-helpers/issue-analysis.js +362 -0
- package/dist/src/server-helpers/normalizers.js +31 -0
- package/dist/src/server-helpers/notifications.js +96 -0
- package/dist/src/server-helpers/orchestration-ops.js +287 -0
- package/dist/src/server-helpers/orchestration.js +39 -0
- package/dist/src/server-helpers/plugin-host-cache.js +86 -0
- package/dist/src/server-helpers/project-fs-ops.js +194 -0
- package/dist/src/server-helpers/project-registry.js +124 -0
- package/dist/src/server-helpers/queue-state.js +78 -0
- package/dist/src/server-helpers/remote-checkout.js +140 -0
- package/dist/src/server-helpers/remote-snapshots.js +119 -0
- package/dist/src/server-helpers/run-io.js +262 -0
- package/dist/src/server-helpers/run-mutations.js +1784 -0
- package/dist/src/server-helpers/run-steering.js +176 -0
- package/dist/src/server-helpers/run-writers.js +75 -0
- package/dist/src/server-helpers/server-paths.js +27 -0
- package/dist/src/server-helpers/snapshot-orchestrator.js +832 -0
- package/dist/src/server-helpers/snapshot-service.js +1143 -0
- package/dist/src/server-helpers/summaries.js +126 -0
- package/dist/src/server-helpers/task-config.js +50 -0
- package/dist/src/server-helpers/task-projection.js +98 -0
- package/dist/src/server-helpers/terminal-runtime.js +156 -0
- package/dist/src/server-helpers/terminal-sessions.js +22 -0
- package/dist/src/server-helpers/validation-failure.js +31 -0
- package/dist/src/server-helpers/ws-router.js +1308 -0
- package/dist/src/server.js +12628 -0
- package/dist/src/websocket.js +63 -0
- package/package.json +33 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/server/src/server-helpers/normalizers.ts
|
|
3
|
+
function normalizeString(value) {
|
|
4
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
// packages/server/src/server-helpers/summaries.ts
|
|
8
|
+
function toTaskSummary(workspaceId, task) {
|
|
9
|
+
const createdAt = task.createdAt ?? task.updatedAt ?? new Date().toISOString();
|
|
10
|
+
const updatedAt = task.updatedAt ?? task.createdAt ?? createdAt;
|
|
11
|
+
const description = task.description ?? task.acceptanceCriteria ?? "";
|
|
12
|
+
return {
|
|
13
|
+
id: task.id,
|
|
14
|
+
workspaceId,
|
|
15
|
+
graphId: null,
|
|
16
|
+
externalId: task.externalRef,
|
|
17
|
+
title: task.title,
|
|
18
|
+
description,
|
|
19
|
+
status: task.status,
|
|
20
|
+
priority: task.priority,
|
|
21
|
+
role: task.role,
|
|
22
|
+
scope: task.scope,
|
|
23
|
+
validationKeys: task.validation,
|
|
24
|
+
sourceIssueId: task.sourceIssueId,
|
|
25
|
+
dependencies: task.dependencies,
|
|
26
|
+
parentChildDeps: task.parentChildDeps,
|
|
27
|
+
metadata: {
|
|
28
|
+
acceptanceCriteria: task.acceptanceCriteria,
|
|
29
|
+
issueType: task.issueType,
|
|
30
|
+
sourceIssueId: task.sourceIssueId,
|
|
31
|
+
...task.sourceStatus ? { sourceStatus: task.sourceStatus } : {},
|
|
32
|
+
dependencies: task.dependencies,
|
|
33
|
+
parentChildDeps: task.parentChildDeps,
|
|
34
|
+
labels: task.labels
|
|
35
|
+
},
|
|
36
|
+
createdAt,
|
|
37
|
+
updatedAt
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function buildWorkspaceGraphSummary(workspace, tasks) {
|
|
41
|
+
const timestamps = [
|
|
42
|
+
workspace.updatedAt,
|
|
43
|
+
...tasks.map((task) => task.updatedAt),
|
|
44
|
+
...tasks.map((task) => task.createdAt)
|
|
45
|
+
].filter((value) => typeof value === "string" && value.length > 0);
|
|
46
|
+
const updatedAt = timestamps.toSorted().at(-1) ?? workspace.updatedAt;
|
|
47
|
+
const taskCount = tasks.length;
|
|
48
|
+
return {
|
|
49
|
+
id: `graph:${workspace.id}`,
|
|
50
|
+
workspaceId: workspace.id,
|
|
51
|
+
title: `${workspace.title} graph`,
|
|
52
|
+
description: taskCount > 0 ? `${taskCount} imported task${taskCount === 1 ? "" : "s"} from the connected Rig workspace` : "No imported tasks are available yet for this workspace",
|
|
53
|
+
createdAt: workspace.createdAt,
|
|
54
|
+
updatedAt
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function toApprovalSummary(record) {
|
|
58
|
+
const createdAt = normalizeString(record.record.createdAt) ?? normalizeString(record.record.requestedAt) ?? new Date().toISOString();
|
|
59
|
+
const resolvedAt = normalizeString(record.record.resolvedAt) ?? normalizeString(record.record.respondedAt) ?? null;
|
|
60
|
+
return {
|
|
61
|
+
id: record.requestId ?? `${record.runId}-approval-${createdAt}`,
|
|
62
|
+
runId: record.runId,
|
|
63
|
+
actionId: normalizeString(record.record.actionId),
|
|
64
|
+
requestKind: normalizeString(record.record.requestKind) ?? normalizeString(record.record.kind) ?? "approval",
|
|
65
|
+
status: record.status === "resolved" ? "resolved" : "pending",
|
|
66
|
+
payload: record.record,
|
|
67
|
+
createdAt,
|
|
68
|
+
resolvedAt
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function toUserInputSummary(record) {
|
|
72
|
+
const createdAt = normalizeString(record.record.createdAt) ?? normalizeString(record.record.requestedAt) ?? new Date().toISOString();
|
|
73
|
+
const resolvedAt = normalizeString(record.record.resolvedAt) ?? normalizeString(record.record.respondedAt) ?? null;
|
|
74
|
+
return {
|
|
75
|
+
id: record.requestId ?? `${record.runId}-input-${createdAt}`,
|
|
76
|
+
runId: record.runId,
|
|
77
|
+
status: record.status === "resolved" ? "resolved" : "pending",
|
|
78
|
+
payload: record.record,
|
|
79
|
+
createdAt,
|
|
80
|
+
resolvedAt
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function toRunSummary(run, approvals, userInputs) {
|
|
84
|
+
const pendingApprovalCount = approvals.filter((entry) => entry.status !== "resolved").length;
|
|
85
|
+
const pendingUserInputCount = userInputs.filter((entry) => entry.status !== "resolved").length;
|
|
86
|
+
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";
|
|
87
|
+
const runMode = normalizeString(run.runMode) === "autonomous" ? "autonomous" : normalizeString(run.runMode) === "supervised" ? "supervised" : "interactive";
|
|
88
|
+
const runtimeMode = normalizeString(run.runtimeMode) ?? "approval-required";
|
|
89
|
+
const interactionMode = normalizeString(run.interactionMode) ?? "default";
|
|
90
|
+
return {
|
|
91
|
+
id: run.runId,
|
|
92
|
+
workspaceId: run.workspaceId,
|
|
93
|
+
taskId: run.taskId,
|
|
94
|
+
title: run.title,
|
|
95
|
+
runKind: run.taskId ? "task" : "adhoc",
|
|
96
|
+
mode: runMode,
|
|
97
|
+
runtimeMode,
|
|
98
|
+
interactionMode,
|
|
99
|
+
status: runStatus,
|
|
100
|
+
runtimeAdapter: run.runtimeAdapter,
|
|
101
|
+
model: normalizeString(run.model),
|
|
102
|
+
initialPrompt: normalizeString(run.initialPrompt),
|
|
103
|
+
executionTarget: run.mode === "remote" ? "remote" : "local",
|
|
104
|
+
remoteHostId: run.hostId,
|
|
105
|
+
remoteLeaseId: run.endpointId,
|
|
106
|
+
remoteLeaseClaimedAt: null,
|
|
107
|
+
activeRuntimeId: null,
|
|
108
|
+
latestMessageId: normalizeString(run.latestMessageId),
|
|
109
|
+
pendingApprovalCount,
|
|
110
|
+
pendingUserInputCount,
|
|
111
|
+
branch: normalizeString(run.branch),
|
|
112
|
+
worktreePath: run.worktreePath,
|
|
113
|
+
errorText: normalizeString(run.errorText),
|
|
114
|
+
createdAt: run.createdAt,
|
|
115
|
+
updatedAt: run.updatedAt,
|
|
116
|
+
startedAt: run.startedAt,
|
|
117
|
+
completedAt: run.completedAt
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
export {
|
|
121
|
+
toUserInputSummary,
|
|
122
|
+
toTaskSummary,
|
|
123
|
+
toRunSummary,
|
|
124
|
+
toApprovalSummary,
|
|
125
|
+
buildWorkspaceGraphSummary
|
|
126
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/server/src/server-helpers/task-config.ts
|
|
3
|
+
import { existsSync as existsSync2 } from "fs";
|
|
4
|
+
|
|
5
|
+
// packages/server/src/bootstrap.ts
|
|
6
|
+
import { existsSync, mkdirSync, unlinkSync, writeFileSync } from "fs";
|
|
7
|
+
import { dirname, resolve } from "path";
|
|
8
|
+
import { RIG_DEFINITION_DIRNAME, resolveMonorepoRoot } from "@rig/runtime";
|
|
9
|
+
function normalizeOptionalString(value) {
|
|
10
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
11
|
+
}
|
|
12
|
+
function resolveRigServerPaths(projectRoot) {
|
|
13
|
+
const taskWorkspace = normalizeOptionalString(process.env.RIG_TASK_WORKSPACE);
|
|
14
|
+
const explicitStateDir = normalizeOptionalString(process.env.RIG_STATE_DIR);
|
|
15
|
+
const explicitLogsDir = normalizeOptionalString(process.env.RIG_LOGS_DIR);
|
|
16
|
+
const explicitSessionFile = normalizeOptionalString(process.env.RIG_SESSION_FILE);
|
|
17
|
+
const hostStateRoot = resolve(projectRoot, ".rig");
|
|
18
|
+
const monorepoRoot = resolveMonorepoRoot(projectRoot);
|
|
19
|
+
const monorepoStateRoot = resolve(monorepoRoot, ".rig");
|
|
20
|
+
const stateRoot = taskWorkspace ? resolve(taskWorkspace, ".rig") : explicitStateDir ? dirname(resolve(explicitStateDir)) : explicitLogsDir ? dirname(resolve(explicitLogsDir)) : explicitSessionFile ? dirname(dirname(resolve(explicitSessionFile))) : existsSync(hostStateRoot) ? hostStateRoot : monorepoStateRoot;
|
|
21
|
+
const stateDir = explicitStateDir ? resolve(explicitStateDir) : resolve(stateRoot, "state");
|
|
22
|
+
const logsDir = explicitLogsDir ? resolve(explicitLogsDir) : resolve(stateRoot, "logs");
|
|
23
|
+
const taskConfigPath = taskWorkspace ? resolve(taskWorkspace, ".rig", "task-config.json") : existsSync(resolve(projectRoot, ".rig", "task-config.json")) ? resolve(projectRoot, ".rig", "task-config.json") : resolve(monorepoStateRoot, "task-config.json");
|
|
24
|
+
return {
|
|
25
|
+
stateRoot,
|
|
26
|
+
stateDir,
|
|
27
|
+
logsDir,
|
|
28
|
+
controlPlaneEventsFile: resolve(logsDir, "control-plane.events.jsonl"),
|
|
29
|
+
taskConfigPath,
|
|
30
|
+
notificationsFile: resolve(projectRoot, "rig", "notifications", "targets.json"),
|
|
31
|
+
keybindingsPath: resolve(projectRoot, "rig", "keybindings.json")
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// packages/server/src/server-helpers/task-config.ts
|
|
36
|
+
async function readTaskConfig(projectRoot) {
|
|
37
|
+
const taskConfigPath = resolveRigServerPaths(projectRoot).taskConfigPath;
|
|
38
|
+
if (!existsSync2(taskConfigPath)) {
|
|
39
|
+
return {};
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
const parsed = await Bun.file(taskConfigPath).json();
|
|
43
|
+
return parsed ?? {};
|
|
44
|
+
} catch {
|
|
45
|
+
return {};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export {
|
|
49
|
+
readTaskConfig
|
|
50
|
+
};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/server/src/server-helpers/task-projection.ts
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
4
|
+
import { resolve } from "path";
|
|
5
|
+
function projectionPath(projectRoot) {
|
|
6
|
+
return resolve(projectRoot, ".rig", "state", "task-projection.json");
|
|
7
|
+
}
|
|
8
|
+
function stateDir(projectRoot) {
|
|
9
|
+
return resolve(projectRoot, ".rig", "state");
|
|
10
|
+
}
|
|
11
|
+
function cloneTask(task) {
|
|
12
|
+
return { ...task };
|
|
13
|
+
}
|
|
14
|
+
function writeTaskProjection(projectRoot, input) {
|
|
15
|
+
const activeByTask = new Map((input.activeRuns ?? []).map((run) => [run.taskId, run]));
|
|
16
|
+
const tasks = input.tasks.map((task) => {
|
|
17
|
+
const projected = cloneTask(task);
|
|
18
|
+
const active = activeByTask.get(task.id);
|
|
19
|
+
if (active) {
|
|
20
|
+
projected.status = "in_progress";
|
|
21
|
+
projected.activeRun = {
|
|
22
|
+
runId: active.runId,
|
|
23
|
+
...active.status ? { status: active.status } : {},
|
|
24
|
+
...active.stage ? { stage: active.stage } : {}
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
return projected;
|
|
28
|
+
});
|
|
29
|
+
const snapshot = {
|
|
30
|
+
version: 1,
|
|
31
|
+
source: input.source,
|
|
32
|
+
reason: input.reason,
|
|
33
|
+
refreshedAt: input.refreshedAt ?? new Date().toISOString(),
|
|
34
|
+
tasks
|
|
35
|
+
};
|
|
36
|
+
mkdirSync(stateDir(projectRoot), { recursive: true });
|
|
37
|
+
writeFileSync(projectionPath(projectRoot), JSON.stringify(snapshot, null, 2), "utf8");
|
|
38
|
+
return snapshot;
|
|
39
|
+
}
|
|
40
|
+
function readTaskProjection(projectRoot) {
|
|
41
|
+
const file = projectionPath(projectRoot);
|
|
42
|
+
if (!existsSync(file))
|
|
43
|
+
return null;
|
|
44
|
+
try {
|
|
45
|
+
const parsed = JSON.parse(readFileSync(file, "utf8"));
|
|
46
|
+
return parsed && parsed.version === 1 && Array.isArray(parsed.tasks) ? parsed : null;
|
|
47
|
+
} catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async function refreshTaskProjection(projectRoot, input) {
|
|
52
|
+
const tasks = typeof input.tasks === "function" ? await input.tasks() : input.tasks;
|
|
53
|
+
const activeRuns = typeof input.activeRuns === "function" ? await input.activeRuns() : input.activeRuns;
|
|
54
|
+
return writeTaskProjection(projectRoot, {
|
|
55
|
+
source: input.source,
|
|
56
|
+
reason: input.reason ?? "refresh",
|
|
57
|
+
tasks,
|
|
58
|
+
activeRuns
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
function taskAssignees(task) {
|
|
62
|
+
const raw = task.assignees;
|
|
63
|
+
if (!Array.isArray(raw))
|
|
64
|
+
return [];
|
|
65
|
+
return raw.flatMap((value) => {
|
|
66
|
+
if (typeof value === "string")
|
|
67
|
+
return [value];
|
|
68
|
+
if (value && typeof value === "object" && typeof value.login === "string")
|
|
69
|
+
return [value.login];
|
|
70
|
+
return [];
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
function filterProjectedTasks(tasks, filter) {
|
|
74
|
+
const status = filter.status?.trim();
|
|
75
|
+
const assignedTo = filter.assignedTo?.trim();
|
|
76
|
+
const search = filter.search?.trim().toLowerCase();
|
|
77
|
+
return tasks.filter((task) => {
|
|
78
|
+
if (status && task.status !== status)
|
|
79
|
+
return false;
|
|
80
|
+
if (assignedTo && !taskAssignees(task).includes(assignedTo))
|
|
81
|
+
return false;
|
|
82
|
+
if (search) {
|
|
83
|
+
const title = typeof task.title === "string" ? task.title : "";
|
|
84
|
+
const body = typeof task.body === "string" ? task.body : "";
|
|
85
|
+
if (!`${task.id}
|
|
86
|
+
${title}
|
|
87
|
+
${body}`.toLowerCase().includes(search))
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
return true;
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
export {
|
|
94
|
+
writeTaskProjection,
|
|
95
|
+
refreshTaskProjection,
|
|
96
|
+
readTaskProjection,
|
|
97
|
+
filterProjectedTasks
|
|
98
|
+
};
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/server/src/server-helpers/terminal-runtime.ts
|
|
3
|
+
import { spawn } from "child_process";
|
|
4
|
+
import { WS_CHANNELS as WS_CHANNELS2 } from "@rig/contracts";
|
|
5
|
+
|
|
6
|
+
// packages/server/src/server-helpers/broadcasters.ts
|
|
7
|
+
import { RIG_WS_CHANNELS } from "@rig/contracts";
|
|
8
|
+
|
|
9
|
+
// packages/server/src/websocket.ts
|
|
10
|
+
import {
|
|
11
|
+
WS_CHANNELS
|
|
12
|
+
} from "@rig/contracts";
|
|
13
|
+
function encodeWebSocketPayload(payload) {
|
|
14
|
+
return JSON.stringify(payload);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// packages/server/src/server-helpers/run-writers.ts
|
|
18
|
+
import {
|
|
19
|
+
appendJsonlRecord,
|
|
20
|
+
readAuthorityRun as readAuthorityRun2,
|
|
21
|
+
resolveAuthorityRunDir as resolveAuthorityRunDir2,
|
|
22
|
+
writeJsonFile
|
|
23
|
+
} from "@rig/runtime/control-plane/authority-files";
|
|
24
|
+
|
|
25
|
+
// packages/server/src/server-helpers/run-io.ts
|
|
26
|
+
import {
|
|
27
|
+
listAuthorityRuns,
|
|
28
|
+
readAuthorityRun,
|
|
29
|
+
readJsonlFile,
|
|
30
|
+
resolveAuthorityRunDir
|
|
31
|
+
} from "@rig/runtime/control-plane/authority-files";
|
|
32
|
+
var INITIAL_RUN_LOG_TAIL_MAX_BYTES = 8 * 1024 * 1024;
|
|
33
|
+
|
|
34
|
+
// packages/server/src/server-helpers/remote-snapshots.ts
|
|
35
|
+
import { listAuthorityRemoteEndpoints } from "@rig/runtime/control-plane/authority-files";
|
|
36
|
+
|
|
37
|
+
// packages/server/src/server-helpers/broadcasters.ts
|
|
38
|
+
function broadcastPush(state, payload) {
|
|
39
|
+
const encoded = encodeWebSocketPayload(payload);
|
|
40
|
+
for (const socket of state.sockets) {
|
|
41
|
+
try {
|
|
42
|
+
socket.send(encoded);
|
|
43
|
+
} catch {
|
|
44
|
+
state.sockets.delete(socket);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// packages/server/src/server-helpers/terminal-sessions.ts
|
|
50
|
+
function terminalSessionKey(threadId, terminalId) {
|
|
51
|
+
return `${threadId}::${terminalId}`;
|
|
52
|
+
}
|
|
53
|
+
function terminalSnapshot(session) {
|
|
54
|
+
return {
|
|
55
|
+
threadId: session.threadId,
|
|
56
|
+
terminalId: session.terminalId,
|
|
57
|
+
cwd: session.cwd,
|
|
58
|
+
status: session.status,
|
|
59
|
+
pid: session.pid,
|
|
60
|
+
history: session.history,
|
|
61
|
+
exitCode: session.exitCode,
|
|
62
|
+
exitSignal: session.exitSignal,
|
|
63
|
+
updatedAt: session.updatedAt
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// packages/server/src/server-helpers/terminal-runtime.ts
|
|
68
|
+
var DEFAULT_TERMINAL_SHELL = process.env.SHELL || "/bin/zsh";
|
|
69
|
+
function pushTerminalEvent(state, event) {
|
|
70
|
+
broadcastPush(state, {
|
|
71
|
+
type: "push",
|
|
72
|
+
channel: WS_CHANNELS2.terminalEvent,
|
|
73
|
+
data: event
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
function createTerminalSession(state, input) {
|
|
77
|
+
const startedAt = new Date().toISOString();
|
|
78
|
+
const child = spawn(DEFAULT_TERMINAL_SHELL, [], {
|
|
79
|
+
cwd: input.cwd,
|
|
80
|
+
env: { ...process.env, ...input.env ?? {} },
|
|
81
|
+
stdio: "pipe"
|
|
82
|
+
});
|
|
83
|
+
const session = {
|
|
84
|
+
threadId: input.threadId,
|
|
85
|
+
terminalId: input.terminalId,
|
|
86
|
+
cwd: input.cwd,
|
|
87
|
+
history: "",
|
|
88
|
+
status: "running",
|
|
89
|
+
pid: child.pid ?? null,
|
|
90
|
+
exitCode: null,
|
|
91
|
+
exitSignal: null,
|
|
92
|
+
updatedAt: startedAt,
|
|
93
|
+
child
|
|
94
|
+
};
|
|
95
|
+
const sessionKey = terminalSessionKey(input.threadId, input.terminalId);
|
|
96
|
+
state.terminalSessions.set(sessionKey, session);
|
|
97
|
+
const appendOutput = (data) => {
|
|
98
|
+
const chunk = Buffer.isBuffer(data) ? data.toString("utf8") : String(data);
|
|
99
|
+
session.history += chunk;
|
|
100
|
+
session.updatedAt = new Date().toISOString();
|
|
101
|
+
pushTerminalEvent(state, {
|
|
102
|
+
type: "output",
|
|
103
|
+
threadId: session.threadId,
|
|
104
|
+
terminalId: session.terminalId,
|
|
105
|
+
createdAt: session.updatedAt,
|
|
106
|
+
data: chunk
|
|
107
|
+
});
|
|
108
|
+
};
|
|
109
|
+
child.stdout.on("data", appendOutput);
|
|
110
|
+
child.stderr.on("data", appendOutput);
|
|
111
|
+
child.on("exit", (code, signal) => {
|
|
112
|
+
session.status = "exited";
|
|
113
|
+
session.exitCode = typeof code === "number" ? code : null;
|
|
114
|
+
session.exitSignal = signal ?? null;
|
|
115
|
+
session.updatedAt = new Date().toISOString();
|
|
116
|
+
pushTerminalEvent(state, {
|
|
117
|
+
type: "exited",
|
|
118
|
+
threadId: session.threadId,
|
|
119
|
+
terminalId: session.terminalId,
|
|
120
|
+
createdAt: session.updatedAt,
|
|
121
|
+
exitCode: session.exitCode,
|
|
122
|
+
exitSignal: session.exitSignal
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
child.on("error", (error) => {
|
|
126
|
+
session.status = "error";
|
|
127
|
+
session.updatedAt = new Date().toISOString();
|
|
128
|
+
pushTerminalEvent(state, {
|
|
129
|
+
type: "error",
|
|
130
|
+
threadId: session.threadId,
|
|
131
|
+
terminalId: session.terminalId,
|
|
132
|
+
createdAt: session.updatedAt,
|
|
133
|
+
message: error.message
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
pushTerminalEvent(state, {
|
|
137
|
+
type: "started",
|
|
138
|
+
threadId: session.threadId,
|
|
139
|
+
terminalId: session.terminalId,
|
|
140
|
+
createdAt: startedAt,
|
|
141
|
+
snapshot: terminalSnapshot(session)
|
|
142
|
+
});
|
|
143
|
+
return session;
|
|
144
|
+
}
|
|
145
|
+
function getOrCreateTerminalSession(state, input) {
|
|
146
|
+
const existing = state.terminalSessions.get(terminalSessionKey(input.threadId, input.terminalId));
|
|
147
|
+
if (existing && existing.status !== "exited" && existing.status !== "error") {
|
|
148
|
+
return existing;
|
|
149
|
+
}
|
|
150
|
+
return createTerminalSession(state, input);
|
|
151
|
+
}
|
|
152
|
+
export {
|
|
153
|
+
pushTerminalEvent,
|
|
154
|
+
getOrCreateTerminalSession,
|
|
155
|
+
createTerminalSession
|
|
156
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/server/src/server-helpers/terminal-sessions.ts
|
|
3
|
+
function terminalSessionKey(threadId, terminalId) {
|
|
4
|
+
return `${threadId}::${terminalId}`;
|
|
5
|
+
}
|
|
6
|
+
function terminalSnapshot(session) {
|
|
7
|
+
return {
|
|
8
|
+
threadId: session.threadId,
|
|
9
|
+
terminalId: session.terminalId,
|
|
10
|
+
cwd: session.cwd,
|
|
11
|
+
status: session.status,
|
|
12
|
+
pid: session.pid,
|
|
13
|
+
history: session.history,
|
|
14
|
+
exitCode: session.exitCode,
|
|
15
|
+
exitSignal: session.exitSignal,
|
|
16
|
+
updatedAt: session.updatedAt
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export {
|
|
20
|
+
terminalSnapshot,
|
|
21
|
+
terminalSessionKey
|
|
22
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/server/src/server-helpers/validation-failure.ts
|
|
3
|
+
import { resolve } from "path";
|
|
4
|
+
import {
|
|
5
|
+
readJsonFile,
|
|
6
|
+
resolveTaskArtifactDirs
|
|
7
|
+
} from "@rig/runtime/control-plane/authority-files";
|
|
8
|
+
function summarizeRunValidationFailure(projectRoot, run) {
|
|
9
|
+
const artifactRoots = run.taskId ? resolveTaskArtifactDirs(projectRoot, run.taskId) : [];
|
|
10
|
+
const preferredRoots = run.artifactRoot ? [run.artifactRoot, ...artifactRoots] : artifactRoots;
|
|
11
|
+
const seen = new Set;
|
|
12
|
+
for (const artifactRoot of preferredRoots) {
|
|
13
|
+
if (!artifactRoot || seen.has(artifactRoot)) {
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
seen.add(artifactRoot);
|
|
17
|
+
const summary = readJsonFile(resolve(artifactRoot, "validation-summary.json"), null);
|
|
18
|
+
if (!summary || summary.status !== "fail") {
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
const failedCategories = Array.isArray(summary.categories) ? summary.categories.filter((entry) => String(entry?.status ?? "") !== "pass").map((entry) => String(entry?.category ?? "").trim()).filter(Boolean) : [];
|
|
22
|
+
if (failedCategories.length > 0) {
|
|
23
|
+
return `Task validation failed: ${failedCategories.join(", ")}`;
|
|
24
|
+
}
|
|
25
|
+
return "Task validation failed.";
|
|
26
|
+
}
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
export {
|
|
30
|
+
summarizeRunValidationFailure
|
|
31
|
+
};
|