@h-rig/run-worker 0.0.6-alpha.157 → 0.0.6-alpha.159
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/dist/src/autohost.d.ts +8 -10
- package/dist/src/autohost.js +683 -95
- package/dist/src/constants.d.ts +0 -1
- package/dist/src/constants.js +0 -2
- package/dist/src/extension.js +683 -95
- package/dist/src/host-kernel.d.ts +22 -0
- package/dist/src/host-kernel.js +78 -0
- package/dist/src/host.d.ts +2 -0
- package/dist/src/host.js +419 -0
- package/dist/src/index.d.ts +0 -6
- package/dist/src/index.js +1913 -133
- package/dist/src/local-run-changes.d.ts +3 -0
- package/dist/src/local-run-changes.js +65 -0
- package/dist/src/notifications.js +13 -5
- package/dist/src/notify-cap.d.ts +11 -0
- package/dist/src/notify-cap.js +13 -0
- package/dist/src/panel-plugin.js +3 -7
- package/dist/src/plugin.d.ts +0 -11
- package/dist/src/plugin.js +1910 -101
- package/dist/src/{runs → read-model-backend}/control.d.ts +0 -1
- package/dist/src/{runs → read-model-backend}/control.js +361 -38
- package/dist/src/{runs → read-model-backend}/diagnostics.d.ts +0 -1
- package/dist/src/{runs → read-model-backend}/diagnostics.js +1 -3
- package/dist/src/{runs → read-model-backend}/guard.js +2 -3
- package/dist/src/{runs → read-model-backend}/inbox.js +366 -36
- package/dist/src/{runs → read-model-backend}/index.js +552 -223
- package/dist/src/{runs → read-model-backend}/inspect.d.ts +0 -1
- package/dist/src/{runs → read-model-backend}/inspect.js +350 -34
- package/dist/src/{runs → read-model-backend}/projection.d.ts +21 -7
- package/dist/src/{runs → read-model-backend}/projection.js +349 -31
- package/dist/src/{runs → read-model-backend}/run-status.d.ts +6 -3
- package/dist/src/{runs → read-model-backend}/run-status.js +53 -33
- package/dist/src/{runs → read-model-backend}/stats.d.ts +0 -1
- package/dist/src/{runs → read-model-backend}/stats.js +373 -58
- package/dist/src/read-model-service.d.ts +2 -0
- package/dist/src/read-model-service.js +1433 -0
- package/dist/src/session-journal.d.ts +60 -0
- package/dist/src/session-journal.js +471 -0
- package/dist/src/stall.d.ts +8 -3
- package/dist/src/stall.js +0 -1
- package/dist/src/utils.js +282 -3
- package/package.json +9 -12
- package/dist/src/journal.d.ts +0 -33
- package/dist/src/journal.js +0 -31
- /package/dist/src/{runs → read-model-backend}/guard.d.ts +0 -0
- /package/dist/src/{runs → read-model-backend}/inbox.d.ts +0 -0
- /package/dist/src/{runs → read-model-backend}/index.d.ts +0 -0
package/dist/src/autohost.js
CHANGED
|
@@ -1,29 +1,631 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// packages/run-worker/src/autohost.ts
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
PLACEMENT_RUN_TRANSPORT,
|
|
5
|
+
RIG_RUN_STOP_PANEL_ACTION as RIG_RUN_STOP_PANEL_ACTION2,
|
|
6
|
+
RIG_SUPERVISOR_PANEL_ID as RIG_SUPERVISOR_PANEL_ID2,
|
|
7
|
+
RUN_IDENTITY_ENV,
|
|
8
|
+
RUN_REGISTRY_BACKBONE,
|
|
9
|
+
RUN_CLOSEOUT_CAPABILITY,
|
|
10
|
+
TASK_DATA_SERVICE_CAPABILITY
|
|
11
|
+
} from "@rig/contracts";
|
|
12
|
+
import { Duration, Effect as Effect2, Fiber, Stream as Stream2 } from "effect";
|
|
13
|
+
import { defineCapability as defineCapability2 } from "@rig/core/capability";
|
|
14
|
+
import { requireInstalledCapability, loadCapabilityForRoot } from "@rig/core/capability-loaders";
|
|
15
|
+
import { resolveOwnerNamespaceKey } from "@rig/core/remote-config";
|
|
16
16
|
|
|
17
|
-
// packages/run-worker/src/
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
17
|
+
// packages/run-worker/src/host-kernel.ts
|
|
18
|
+
import { existsSync, readFileSync } from "fs";
|
|
19
|
+
import { resolve } from "path";
|
|
20
|
+
import { applyConfigEnv } from "@rig/core/config-env";
|
|
21
|
+
import { loadConfig } from "@rig/core/load-config";
|
|
22
|
+
import { bootDefaultKernelIntoProcess } from "@rig/kernel-seed/boot-default";
|
|
23
|
+
import { createPlacementTransportPlugin } from "@rig/kernel-seed/default-kernel";
|
|
24
|
+
function unquoteDotenvValue(value) {
|
|
25
|
+
const trimmed = value.trim();
|
|
26
|
+
if (trimmed.length >= 2 && (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'"))) {
|
|
27
|
+
return trimmed.slice(1, -1);
|
|
28
|
+
}
|
|
29
|
+
return trimmed;
|
|
30
|
+
}
|
|
31
|
+
function hydrateDotenv(path, env) {
|
|
32
|
+
if (!existsSync(path))
|
|
33
|
+
return false;
|
|
34
|
+
const source = readFileSync(path, "utf8");
|
|
35
|
+
for (const rawLine of source.split(/\r?\n/)) {
|
|
36
|
+
const line = rawLine.trim();
|
|
37
|
+
if (!line || line.startsWith("#"))
|
|
38
|
+
continue;
|
|
39
|
+
const assignment = line.startsWith("export ") ? line.slice("export ".length).trim() : line;
|
|
40
|
+
const equals = assignment.indexOf("=");
|
|
41
|
+
if (equals <= 0)
|
|
42
|
+
continue;
|
|
43
|
+
const key = assignment.slice(0, equals).trim();
|
|
44
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key) || env[key])
|
|
45
|
+
continue;
|
|
46
|
+
env[key] = unquoteDotenvValue(assignment.slice(equals + 1));
|
|
47
|
+
}
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
function createRunWorkerPlacementTransportPlugin(transport) {
|
|
51
|
+
return createPlacementTransportPlugin(transport);
|
|
52
|
+
}
|
|
53
|
+
async function hydrateRunWorkerProjectEnv(projectRoot, options = {}) {
|
|
54
|
+
const normalizedRoot = resolve(projectRoot);
|
|
55
|
+
const env = options.env ?? process.env;
|
|
56
|
+
const errors = [];
|
|
57
|
+
let dotenvLoaded = false;
|
|
58
|
+
let configLoaded = false;
|
|
59
|
+
try {
|
|
60
|
+
dotenvLoaded = hydrateDotenv(resolve(normalizedRoot, options.dotenvPath ?? ".env"), env);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
errors.push(`dotenv: ${error instanceof Error ? error.message : String(error)}`);
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
applyConfigEnv(await loadConfig(normalizedRoot), env);
|
|
66
|
+
configLoaded = true;
|
|
67
|
+
} catch (error) {
|
|
68
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
69
|
+
if (!message.includes("no rig.config"))
|
|
70
|
+
errors.push(`config: ${message}`);
|
|
71
|
+
}
|
|
72
|
+
return { projectRoot: normalizedRoot, dotenvLoaded, configLoaded, errors };
|
|
73
|
+
}
|
|
74
|
+
async function adoptRunWorkerKernel(root, options = {}) {
|
|
75
|
+
if (options.hydrateEnv === true && root !== undefined) {
|
|
76
|
+
await hydrateRunWorkerProjectEnv(root, options);
|
|
77
|
+
}
|
|
78
|
+
const bootInput = {
|
|
79
|
+
...options.entrypoint !== undefined ? { entrypoint: options.entrypoint } : {},
|
|
80
|
+
...options.journal !== undefined ? { journal: options.journal } : {}
|
|
81
|
+
};
|
|
82
|
+
if (options.transport === undefined)
|
|
83
|
+
return await bootDefaultKernelIntoProcess(bootInput);
|
|
84
|
+
return await bootDefaultKernelIntoProcess({
|
|
85
|
+
...bootInput,
|
|
86
|
+
extraPlugins: [createRunWorkerPlacementTransportPlugin(options.transport)]
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// packages/run-worker/src/local-run-changes.ts
|
|
91
|
+
import { existsSync as existsSync2, mkdirSync, watch } from "fs";
|
|
92
|
+
import { dirname, resolve as resolve2 } from "path";
|
|
93
|
+
import { Effect, Queue, Stream } from "effect";
|
|
94
|
+
function findNearestGitCheckoutRoot(startDir) {
|
|
95
|
+
let current = resolve2(startDir);
|
|
96
|
+
for (;; ) {
|
|
97
|
+
if (existsSync2(resolve2(current, ".git")))
|
|
98
|
+
return current;
|
|
99
|
+
const parent = dirname(current);
|
|
100
|
+
if (parent === current)
|
|
101
|
+
return null;
|
|
102
|
+
current = parent;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function resolveCheckoutRoot(projectRoot) {
|
|
106
|
+
const normalizedProjectRoot = resolve2(projectRoot);
|
|
107
|
+
const explicit = process.env.MONOREPO_ROOT?.trim();
|
|
108
|
+
if (explicit) {
|
|
109
|
+
const explicitRoot = resolve2(explicit);
|
|
110
|
+
const gitRoot = findNearestGitCheckoutRoot(explicitRoot);
|
|
111
|
+
if (gitRoot)
|
|
112
|
+
return gitRoot;
|
|
113
|
+
throw new Error(`MONOREPO_ROOT points to ${explicitRoot}, but no git checkout was found there or above it.`);
|
|
114
|
+
}
|
|
115
|
+
return findNearestGitCheckoutRoot(normalizedProjectRoot) ?? normalizedProjectRoot;
|
|
116
|
+
}
|
|
117
|
+
function isRelevantLocalRunChange(filename) {
|
|
118
|
+
if (filename === null || filename === undefined)
|
|
119
|
+
return true;
|
|
120
|
+
const raw = filename.toString();
|
|
121
|
+
if (!raw.trim())
|
|
122
|
+
return true;
|
|
123
|
+
const segments = raw.split(/[\\/]+/).filter(Boolean);
|
|
124
|
+
if (segments.some((segment) => segment === ".git" || segment === "node_modules" || segment === "build" || segment === "dist" || segment === "out" || segment === ".next" || segment === "coverage")) {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
const rigIndex = segments.lastIndexOf(".rig");
|
|
128
|
+
if (rigIndex < 0)
|
|
129
|
+
return false;
|
|
130
|
+
const rigChild = segments[rigIndex + 1];
|
|
131
|
+
if (rigChild === "session")
|
|
132
|
+
return true;
|
|
133
|
+
return rigChild === "runtime-context.json" && segments[rigIndex + 2] === undefined;
|
|
134
|
+
}
|
|
135
|
+
var localRunChanges = (projectRoot) => Stream.callback((queue) => Effect.gen(function* () {
|
|
136
|
+
const worktreesRoot = resolve2(resolveCheckoutRoot(projectRoot), ".worktrees");
|
|
137
|
+
if (!existsSync2(worktreesRoot)) {
|
|
138
|
+
try {
|
|
139
|
+
mkdirSync(worktreesRoot, { recursive: true });
|
|
140
|
+
} catch {}
|
|
141
|
+
}
|
|
142
|
+
const watcher = watch(worktreesRoot, { recursive: true }, (_event, filename) => {
|
|
143
|
+
if (!isRelevantLocalRunChange(filename))
|
|
144
|
+
return;
|
|
145
|
+
Queue.offerUnsafe(queue, undefined);
|
|
146
|
+
});
|
|
147
|
+
watcher.on("error", () => {});
|
|
148
|
+
yield* Effect.addFinalizer(() => Effect.sync(() => watcher.close()));
|
|
149
|
+
}), { bufferSize: 1, strategy: "sliding" });
|
|
150
|
+
|
|
151
|
+
// packages/run-worker/src/notify-cap.ts
|
|
152
|
+
import { NOTIFY_SERVICE_CAPABILITY } from "@rig/contracts";
|
|
153
|
+
import { defineCapability } from "@rig/core/capability";
|
|
154
|
+
import { resolvePluginHost } from "@rig/core/project-plugins";
|
|
155
|
+
var NotifyCap = defineCapability(NOTIFY_SERVICE_CAPABILITY);
|
|
156
|
+
async function resolveNotifyService(projectRoot) {
|
|
157
|
+
const { host } = await resolvePluginHost(projectRoot);
|
|
158
|
+
return NotifyCap.resolve(host);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// packages/run-worker/src/notifications.ts
|
|
162
|
+
async function dispatchRunNotifications(projectRoot, runId, taskId, outcome, detail) {
|
|
163
|
+
try {
|
|
164
|
+
const notifier = await resolveNotifyService(projectRoot);
|
|
165
|
+
if (!notifier)
|
|
166
|
+
return;
|
|
167
|
+
const event = { runId, type: `run.${outcome}`, timestamp: new Date().toISOString(), payload: { taskId, detail } };
|
|
168
|
+
await Promise.race([
|
|
169
|
+
notifier.notify(event),
|
|
170
|
+
new Promise((resolveCap) => setTimeout(resolveCap, 5000))
|
|
171
|
+
]);
|
|
172
|
+
} catch {}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// packages/run-worker/src/panel-plugin.ts
|
|
176
|
+
import { createProjectPluginHost } from "@rig/core/project-plugins";
|
|
177
|
+
import { RIG_CAPABILITY_PANEL_SLOT, RIG_RUN_STOP_PANEL_ACTION, RIG_SUPERVISOR_PANEL_ID } from "@rig/contracts";
|
|
178
|
+
var RIG_PANELS_CUSTOM_MESSAGE_TYPE = "rig-panels";
|
|
179
|
+
var PANEL_PRODUCER_TIMEOUT_MS = 2000;
|
|
180
|
+
async function produceWorkerPanelPayload(producer, context) {
|
|
181
|
+
if (!producer.produce)
|
|
182
|
+
return;
|
|
183
|
+
let timeout;
|
|
184
|
+
try {
|
|
185
|
+
return await Promise.race([
|
|
186
|
+
Promise.resolve(producer.produce(context)),
|
|
187
|
+
new Promise((resolve3) => {
|
|
188
|
+
timeout = setTimeout(() => resolve3(undefined), PANEL_PRODUCER_TIMEOUT_MS);
|
|
189
|
+
})
|
|
190
|
+
]);
|
|
191
|
+
} finally {
|
|
192
|
+
clearTimeout(timeout);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
async function loadWorkerPanelRegistry(projectRoot) {
|
|
196
|
+
const { host } = await createProjectPluginHost(projectRoot, {
|
|
197
|
+
mode: "strict-config-only"
|
|
198
|
+
});
|
|
199
|
+
return {
|
|
200
|
+
registrations: host.listPanels().filter((registration) => registration.slot === RIG_CAPABILITY_PANEL_SLOT),
|
|
201
|
+
producers: host.listExecutablePanels().filter((registration) => registration.slot === RIG_CAPABILITY_PANEL_SLOT)
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// packages/run-worker/src/session-journal.ts
|
|
206
|
+
import { Schema } from "effect";
|
|
207
|
+
import {
|
|
208
|
+
CUSTOM_TYPE_FOR,
|
|
209
|
+
RIG_CONTROL_SENTINEL_END,
|
|
210
|
+
RIG_INBOX_RESOLUTION_SENTINEL,
|
|
211
|
+
RIG_PAUSE_SENTINEL,
|
|
212
|
+
RIG_RESUME_SENTINEL,
|
|
213
|
+
RIG_STOP_SENTINEL,
|
|
214
|
+
RIG_STOP_SENTINEL_END,
|
|
215
|
+
RIG_WORKFLOW_STATUS_CHANGED,
|
|
216
|
+
RunJournalEvent,
|
|
217
|
+
TYPE_FOR_CUSTOM
|
|
218
|
+
} from "@rig/contracts";
|
|
219
|
+
var decodeRunJournalEvent = Schema.decodeUnknownSync(RunJournalEvent);
|
|
220
|
+
var RUN_STATUS_TRANSITIONS = {
|
|
221
|
+
created: ["queued", "preparing", "running", "failed", "stopped"],
|
|
222
|
+
queued: ["preparing", "running", "failed", "stopped"],
|
|
223
|
+
preparing: ["queued", "running", "needs-attention", "failed", "stopped"],
|
|
224
|
+
running: [
|
|
225
|
+
"queued",
|
|
226
|
+
"waiting-approval",
|
|
227
|
+
"waiting-user-input",
|
|
228
|
+
"paused",
|
|
229
|
+
"validating",
|
|
230
|
+
"reviewing",
|
|
231
|
+
"closing-out",
|
|
232
|
+
"needs-attention",
|
|
233
|
+
"completed",
|
|
234
|
+
"failed",
|
|
235
|
+
"stopped"
|
|
236
|
+
],
|
|
237
|
+
"waiting-approval": ["running", "needs-attention", "failed", "stopped"],
|
|
238
|
+
"waiting-user-input": ["running", "needs-attention", "failed", "stopped"],
|
|
239
|
+
paused: ["running", "failed", "stopped"],
|
|
240
|
+
validating: ["running", "reviewing", "closing-out", "needs-attention", "completed", "failed", "stopped"],
|
|
241
|
+
reviewing: ["running", "validating", "closing-out", "needs-attention", "completed", "failed", "stopped"],
|
|
242
|
+
"closing-out": ["running", "needs-attention", "completed", "failed", "stopped"],
|
|
243
|
+
"needs-attention": ["queued", "preparing", "running", "closing-out", "completed", "failed", "stopped"],
|
|
244
|
+
completed: [],
|
|
245
|
+
failed: ["queued", "preparing", "running", "closing-out"],
|
|
246
|
+
stopped: ["queued", "preparing", "running", "closing-out"]
|
|
247
|
+
};
|
|
248
|
+
var TERMINAL_RUN_STATUSES = ["completed", "failed", "stopped"];
|
|
249
|
+
function isTerminalRunStatus(status) {
|
|
250
|
+
return TERMINAL_RUN_STATUSES.includes(status);
|
|
251
|
+
}
|
|
252
|
+
function canTransitionRunStatus(from, to) {
|
|
253
|
+
if (from === null)
|
|
254
|
+
return true;
|
|
255
|
+
if (from === to)
|
|
256
|
+
return true;
|
|
257
|
+
return RUN_STATUS_TRANSITIONS[from].includes(to);
|
|
258
|
+
}
|
|
259
|
+
function assertRunStatusTransition(from, to) {
|
|
260
|
+
if (!canTransitionRunStatus(from, to)) {
|
|
261
|
+
throw new Error(`Illegal run status transition: ${from ?? "(none)"} -> ${to}. ` + `Allowed from ${from ?? "(none)"}: ${from ? RUN_STATUS_TRANSITIONS[from].join(", ") : "(any)"}.`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
function reduceRunJournal(events, runId = null) {
|
|
265
|
+
let record = {};
|
|
266
|
+
let status = null;
|
|
267
|
+
const statusHistory = [];
|
|
268
|
+
const pendingApprovals = new Map;
|
|
269
|
+
const resolvedApprovals = [];
|
|
270
|
+
const pendingUserInputs = new Map;
|
|
271
|
+
const resolvedUserInputs = [];
|
|
272
|
+
const closeoutPhases = [];
|
|
273
|
+
let resolvedPipeline = null;
|
|
274
|
+
const stageOutcomes = [];
|
|
275
|
+
const anomalies = [];
|
|
276
|
+
let steeringCount = 0;
|
|
277
|
+
let stallCount = 0;
|
|
278
|
+
let lastSeq = 0;
|
|
279
|
+
let lastEventAt = null;
|
|
280
|
+
const projectedRunId = runId ?? events[0]?.runId ?? null;
|
|
281
|
+
for (const event of events) {
|
|
282
|
+
lastSeq = event.seq;
|
|
283
|
+
lastEventAt = event.at;
|
|
284
|
+
switch (event.type) {
|
|
285
|
+
case "status-changed": {
|
|
286
|
+
if (!canTransitionRunStatus(status, event.to)) {
|
|
287
|
+
anomalies.push({ seq: event.seq, kind: "illegal-transition", detail: `${status ?? "(none)"} -> ${event.to}` });
|
|
288
|
+
}
|
|
289
|
+
statusHistory.push({ seq: event.seq, at: event.at, from: status, to: event.to, reason: event.reason ?? null });
|
|
290
|
+
const wasTerminal = status !== null && isTerminalRunStatus(status);
|
|
291
|
+
status = event.to;
|
|
292
|
+
record = { ...record, updatedAt: event.at };
|
|
293
|
+
if (isTerminalRunStatus(event.to) && !record.completedAt)
|
|
294
|
+
record = { ...record, completedAt: event.at };
|
|
295
|
+
if (!isTerminalRunStatus(event.to) && wasTerminal)
|
|
296
|
+
record = { ...record, completedAt: null };
|
|
297
|
+
if (event.to === "running" && !record.startedAt)
|
|
298
|
+
record = { ...record, startedAt: event.at };
|
|
299
|
+
break;
|
|
300
|
+
}
|
|
301
|
+
case "record-patch": {
|
|
302
|
+
const next = { ...record };
|
|
303
|
+
for (const [key, value] of Object.entries(event.patch)) {
|
|
304
|
+
if (value !== undefined)
|
|
305
|
+
next[key] = value;
|
|
306
|
+
}
|
|
307
|
+
next.updatedAt = event.patch.updatedAt ?? event.at;
|
|
308
|
+
record = next;
|
|
309
|
+
break;
|
|
310
|
+
}
|
|
311
|
+
case "approval-requested": {
|
|
312
|
+
pendingApprovals.set(event.requestId, {
|
|
313
|
+
requestId: event.requestId,
|
|
314
|
+
requestKind: event.requestKind,
|
|
315
|
+
actionId: event.actionId ?? null,
|
|
316
|
+
payload: event.payload,
|
|
317
|
+
requestedAt: event.at
|
|
318
|
+
});
|
|
319
|
+
break;
|
|
320
|
+
}
|
|
321
|
+
case "approval-resolved": {
|
|
322
|
+
const pending = pendingApprovals.get(event.requestId);
|
|
323
|
+
if (!pending) {
|
|
324
|
+
const alreadyResolved = resolvedApprovals.some((entry) => entry.requestId === event.requestId);
|
|
325
|
+
anomalies.push({ seq: event.seq, kind: alreadyResolved ? "duplicate-resolution" : "unknown-request", detail: `approval ${event.requestId}` });
|
|
326
|
+
break;
|
|
327
|
+
}
|
|
328
|
+
pendingApprovals.delete(event.requestId);
|
|
329
|
+
resolvedApprovals.push({ ...pending, decision: event.decision, note: event.note ?? null, actor: event.actor, resolvedAt: event.at });
|
|
330
|
+
break;
|
|
331
|
+
}
|
|
332
|
+
case "input-requested": {
|
|
333
|
+
pendingUserInputs.set(event.requestId, {
|
|
334
|
+
requestId: event.requestId,
|
|
335
|
+
requestKind: "user-input",
|
|
336
|
+
actionId: null,
|
|
337
|
+
payload: event.payload,
|
|
338
|
+
requestedAt: event.at
|
|
339
|
+
});
|
|
340
|
+
break;
|
|
341
|
+
}
|
|
342
|
+
case "input-resolved": {
|
|
343
|
+
const pending = pendingUserInputs.get(event.requestId);
|
|
344
|
+
if (!pending) {
|
|
345
|
+
const alreadyResolved = resolvedUserInputs.some((entry) => entry.requestId === event.requestId);
|
|
346
|
+
anomalies.push({ seq: event.seq, kind: alreadyResolved ? "duplicate-resolution" : "unknown-request", detail: `user-input ${event.requestId}` });
|
|
347
|
+
break;
|
|
348
|
+
}
|
|
349
|
+
pendingUserInputs.delete(event.requestId);
|
|
350
|
+
resolvedUserInputs.push({ ...pending, answers: event.answers, actor: event.actor, resolvedAt: event.at });
|
|
351
|
+
break;
|
|
352
|
+
}
|
|
353
|
+
case "steering":
|
|
354
|
+
steeringCount += 1;
|
|
355
|
+
break;
|
|
356
|
+
case "adopted":
|
|
357
|
+
record = { ...record, pid: event.pid, updatedAt: event.at };
|
|
358
|
+
break;
|
|
359
|
+
case "stall-detected":
|
|
360
|
+
stallCount += 1;
|
|
361
|
+
break;
|
|
362
|
+
case "closeout-phase":
|
|
363
|
+
closeoutPhases.push({ seq: event.seq, at: event.at, phase: event.phase, outcome: event.outcome, detail: event.detail ?? null });
|
|
364
|
+
break;
|
|
365
|
+
case "pipeline-resolved":
|
|
366
|
+
resolvedPipeline = event.pipeline;
|
|
367
|
+
break;
|
|
368
|
+
case "stage-outcome":
|
|
369
|
+
stageOutcomes.push({ seq: event.seq, at: event.at, outcome: event.outcome });
|
|
370
|
+
break;
|
|
371
|
+
case "timeline-entry":
|
|
372
|
+
case "log-entry":
|
|
373
|
+
break;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
return {
|
|
377
|
+
runId: projectedRunId,
|
|
378
|
+
record,
|
|
379
|
+
status,
|
|
380
|
+
statusHistory,
|
|
381
|
+
pendingApprovals: [...pendingApprovals.values()],
|
|
382
|
+
resolvedApprovals,
|
|
383
|
+
pendingUserInputs: [...pendingUserInputs.values()],
|
|
384
|
+
resolvedUserInputs,
|
|
385
|
+
steeringCount,
|
|
386
|
+
stallCount,
|
|
387
|
+
closeoutPhases,
|
|
388
|
+
resolvedPipeline,
|
|
389
|
+
stageOutcomes,
|
|
390
|
+
lastSeq,
|
|
391
|
+
lastEventAt,
|
|
392
|
+
anomalies
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
function sessionIdFromSessionFile(sessionPath) {
|
|
396
|
+
if (!sessionPath)
|
|
397
|
+
return null;
|
|
398
|
+
const file = sessionPath.split(/[\\/]/).pop() ?? "";
|
|
399
|
+
const match = file.match(/_([0-9a-fA-F][0-9a-fA-F-]{7,})\.jsonl$/);
|
|
400
|
+
return match?.[1] ?? null;
|
|
401
|
+
}
|
|
402
|
+
function isRunSessionCustomType(customType) {
|
|
403
|
+
return customType !== undefined && Object.hasOwn(TYPE_FOR_CUSTOM, customType);
|
|
404
|
+
}
|
|
405
|
+
function foldRunSessionEntries(entries, runId) {
|
|
406
|
+
const events = [];
|
|
407
|
+
entries.forEach((entry, index) => {
|
|
408
|
+
if (entry.type !== "custom" || !isRunSessionCustomType(entry.customType))
|
|
409
|
+
return;
|
|
410
|
+
const data = entry.data !== null && typeof entry.data === "object" ? entry.data : {};
|
|
411
|
+
const stamped = {
|
|
412
|
+
v: 1,
|
|
413
|
+
seq: index + 1,
|
|
414
|
+
at: typeof data.at === "string" ? data.at : new Date(0).toISOString(),
|
|
415
|
+
runId,
|
|
416
|
+
...data,
|
|
417
|
+
type: TYPE_FOR_CUSTOM[entry.customType]
|
|
418
|
+
};
|
|
419
|
+
try {
|
|
420
|
+
events.push(decodeRunJournalEvent(stamped));
|
|
421
|
+
} catch {}
|
|
422
|
+
});
|
|
423
|
+
return reduceRunJournal(events, runId);
|
|
424
|
+
}
|
|
425
|
+
function projectRunFromSession(source, runId) {
|
|
426
|
+
const entries = "getEntries" in source ? source.getEntries() : source;
|
|
427
|
+
return foldRunSessionEntries(entries, runId);
|
|
428
|
+
}
|
|
429
|
+
function isRecord(value) {
|
|
430
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
431
|
+
}
|
|
432
|
+
function timelineEntryFromCustomEntry(entry) {
|
|
433
|
+
if (entry.customType !== CUSTOM_TYPE_FOR["timeline-entry"] || !isRecord(entry.data))
|
|
434
|
+
return null;
|
|
435
|
+
const payload = isRecord(entry.data.payload) ? entry.data.payload : entry.data;
|
|
436
|
+
const type = typeof payload.type === "string" ? payload.type : "timeline";
|
|
437
|
+
const stage = typeof payload.stage === "string" ? payload.stage : typeof payload.name === "string" ? payload.name : null;
|
|
438
|
+
const status = typeof payload.status === "string" ? payload.status : typeof payload.outcome === "string" ? payload.outcome : null;
|
|
439
|
+
const detail = typeof payload.detail === "string" ? payload.detail : typeof payload.message === "string" ? payload.message : null;
|
|
440
|
+
const at = typeof entry.data.at === "string" ? entry.data.at : typeof payload.at === "string" ? payload.at : null;
|
|
441
|
+
return { at, type, stage, status, detail };
|
|
442
|
+
}
|
|
443
|
+
function latestTimelineEntriesFromCustomEntries(entries, limit) {
|
|
444
|
+
if (limit <= 0)
|
|
445
|
+
return [];
|
|
446
|
+
const timeline = [];
|
|
447
|
+
for (let index = entries.length - 1;index >= 0 && timeline.length < limit; index -= 1) {
|
|
448
|
+
const timelineEntry = timelineEntryFromCustomEntry(entries[index]);
|
|
449
|
+
if (timelineEntry)
|
|
450
|
+
timeline.push(timelineEntry);
|
|
451
|
+
}
|
|
452
|
+
timeline.reverse();
|
|
453
|
+
return timeline;
|
|
454
|
+
}
|
|
455
|
+
function parseStopSentinel(text, expectedRunId) {
|
|
456
|
+
const start = text.indexOf(RIG_STOP_SENTINEL);
|
|
457
|
+
if (start < 0)
|
|
458
|
+
return null;
|
|
459
|
+
const end = text.indexOf(RIG_STOP_SENTINEL_END, start);
|
|
460
|
+
const body = text.slice(start + RIG_STOP_SENTINEL.length, end >= 0 ? end : undefined).trim();
|
|
461
|
+
const runId = body.match(/(?:^|\s)runId=([^\s>]+)/)?.[1] ?? "";
|
|
462
|
+
if (runId && runId !== expectedRunId)
|
|
463
|
+
return null;
|
|
464
|
+
const reason = body.match(/(?:^|\s)reason=(.+)$/)?.[1]?.trim() ?? null;
|
|
465
|
+
return { reason };
|
|
466
|
+
}
|
|
467
|
+
function parseControlSentinel(marker, text, expectedRunId) {
|
|
468
|
+
const start = text.indexOf(marker);
|
|
469
|
+
if (start < 0)
|
|
470
|
+
return null;
|
|
471
|
+
const end = text.indexOf(RIG_CONTROL_SENTINEL_END, start);
|
|
472
|
+
const body = text.slice(start + marker.length, end >= 0 ? end : undefined).trim();
|
|
473
|
+
const runId = body.match(/(?:^|\s)runId=([^\s>]+)/)?.[1] ?? "";
|
|
474
|
+
if (runId && runId !== expectedRunId)
|
|
475
|
+
return null;
|
|
476
|
+
const requestedBy = body.match(/(?:^|\s)requestedBy=([^\s>]+)/)?.[1] ?? null;
|
|
477
|
+
return { requestedBy };
|
|
478
|
+
}
|
|
479
|
+
function parsePauseSentinel(text, expectedRunId) {
|
|
480
|
+
return parseControlSentinel(RIG_PAUSE_SENTINEL, text, expectedRunId);
|
|
481
|
+
}
|
|
482
|
+
function parseResumeSentinel(text, expectedRunId) {
|
|
483
|
+
return parseControlSentinel(RIG_RESUME_SENTINEL, text, expectedRunId);
|
|
484
|
+
}
|
|
485
|
+
function decodeInboxResolutionPayload(payload) {
|
|
486
|
+
try {
|
|
487
|
+
const parsed = JSON.parse(decodeURIComponent(payload));
|
|
488
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
489
|
+
return null;
|
|
490
|
+
const record = parsed;
|
|
491
|
+
if (record.kind === "approval") {
|
|
492
|
+
const requestId = typeof record.requestId === "string" ? record.requestId.trim() : "";
|
|
493
|
+
const decision = record.decision === "approve" || record.decision === "reject" ? record.decision : null;
|
|
494
|
+
const note = typeof record.note === "string" ? record.note : record.note === null ? null : undefined;
|
|
495
|
+
if (!requestId || !decision)
|
|
496
|
+
return null;
|
|
497
|
+
return { kind: "approval", requestId, decision, ...note !== undefined ? { note } : {} };
|
|
498
|
+
}
|
|
499
|
+
if (record.kind === "input") {
|
|
500
|
+
const requestId = typeof record.requestId === "string" ? record.requestId.trim() : "";
|
|
501
|
+
const answers = record.answers && typeof record.answers === "object" && !Array.isArray(record.answers) ? Object.fromEntries(Object.entries(record.answers).filter((entry) => typeof entry[1] === "string")) : null;
|
|
502
|
+
if (!requestId || !answers)
|
|
503
|
+
return null;
|
|
504
|
+
return { kind: "input", requestId, answers };
|
|
505
|
+
}
|
|
506
|
+
} catch {
|
|
507
|
+
return null;
|
|
508
|
+
}
|
|
509
|
+
return null;
|
|
510
|
+
}
|
|
511
|
+
function parseInboxResolutionSentinel(text, expectedRunId) {
|
|
512
|
+
const start = text.indexOf(RIG_INBOX_RESOLUTION_SENTINEL);
|
|
513
|
+
if (start < 0)
|
|
514
|
+
return null;
|
|
515
|
+
const end = text.indexOf(RIG_CONTROL_SENTINEL_END, start);
|
|
516
|
+
const body = text.slice(start + RIG_INBOX_RESOLUTION_SENTINEL.length, end >= 0 ? end : undefined).trim();
|
|
517
|
+
const runId = body.match(/(?:^|\s)runId=([^\s>]+)/)?.[1] ?? "";
|
|
518
|
+
if (runId && runId !== expectedRunId)
|
|
519
|
+
return null;
|
|
520
|
+
const requestedBy = body.match(/(?:^|\s)requestedBy=([^\s>]+)/)?.[1] ?? null;
|
|
521
|
+
const payload = body.match(/(?:^|\s)data=([^\s>]+)/)?.[1] ?? "";
|
|
522
|
+
const decoded = decodeInboxResolutionPayload(payload);
|
|
523
|
+
if (!decoded)
|
|
524
|
+
return null;
|
|
525
|
+
return { ...decoded, requestedBy };
|
|
526
|
+
}
|
|
527
|
+
function asCustomEntries(entries) {
|
|
528
|
+
return entries.filter((entry) => entry.type === "custom");
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
class RunSessionJournal {
|
|
532
|
+
sm;
|
|
533
|
+
runId;
|
|
534
|
+
constructor(sm, runId) {
|
|
535
|
+
this.sm = sm;
|
|
536
|
+
this.runId = runId;
|
|
537
|
+
}
|
|
538
|
+
#append(init) {
|
|
539
|
+
this.sm.appendCustomEntry(CUSTOM_TYPE_FOR[init.type], { ...init, at: new Date().toISOString() });
|
|
540
|
+
}
|
|
541
|
+
appendStatus(to, opts = {}) {
|
|
542
|
+
const from = foldRunSessionEntries(asCustomEntries(this.sm.getEntries()), this.runId).status;
|
|
543
|
+
if (!opts.force)
|
|
544
|
+
assertRunStatusTransition(from, to);
|
|
545
|
+
if (opts.errorText !== undefined)
|
|
546
|
+
this.#append({ type: "record-patch", patch: { errorText: opts.errorText } });
|
|
547
|
+
this.#append({
|
|
548
|
+
type: "status-changed",
|
|
549
|
+
from,
|
|
550
|
+
to,
|
|
551
|
+
...opts.reason !== undefined ? { reason: opts.reason } : {},
|
|
552
|
+
...opts.actor !== undefined ? { actor: opts.actor } : {}
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
appendTimeline(payload) {
|
|
556
|
+
this.#append({ type: "timeline-entry", payload });
|
|
557
|
+
}
|
|
558
|
+
appendCloseoutPhase(input) {
|
|
559
|
+
this.#append({
|
|
560
|
+
type: "closeout-phase",
|
|
561
|
+
phase: input.phase,
|
|
562
|
+
outcome: input.outcome,
|
|
563
|
+
...input.detail !== undefined ? { detail: input.detail } : {}
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
appendApprovalResolved(input) {
|
|
567
|
+
this.#append({
|
|
568
|
+
type: "approval-resolved",
|
|
569
|
+
requestId: input.requestId,
|
|
570
|
+
decision: input.decision,
|
|
571
|
+
actor: input.actor,
|
|
572
|
+
...input.note !== undefined ? { note: input.note } : {}
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
appendInputResolved(input) {
|
|
576
|
+
this.#append({ type: "input-resolved", requestId: input.requestId, answers: input.answers, actor: input.actor });
|
|
577
|
+
}
|
|
578
|
+
appendStall(input) {
|
|
579
|
+
this.#append({ type: "stall-detected", detail: input.detail });
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
function isRunJournalEventInit(event) {
|
|
583
|
+
if (event === null || typeof event !== "object")
|
|
584
|
+
return false;
|
|
585
|
+
const type = event.type;
|
|
586
|
+
return typeof type === "string" && Object.hasOwn(CUSTOM_TYPE_FOR, type);
|
|
587
|
+
}
|
|
588
|
+
function createJournalSessionProvider(options) {
|
|
589
|
+
const now = options.now ?? (() => new Date);
|
|
590
|
+
const appendRunEvent = async (event, runId = options.runId) => {
|
|
591
|
+
options.store.appendCustomEntry(CUSTOM_TYPE_FOR[event.type], { ...event, runId, at: now().toISOString() });
|
|
592
|
+
};
|
|
593
|
+
const readEntries = () => asCustomEntries(options.store.getEntries());
|
|
594
|
+
const entriesForRun = (runId) => readEntries().filter((entry) => {
|
|
595
|
+
const data = entry.data;
|
|
596
|
+
if (data === null || typeof data !== "object")
|
|
597
|
+
return runId === options.runId;
|
|
598
|
+
const candidate = data.runId;
|
|
599
|
+
return candidate === undefined ? runId === options.runId : candidate === runId;
|
|
600
|
+
});
|
|
601
|
+
return {
|
|
602
|
+
runId: options.runId,
|
|
603
|
+
async append(event) {
|
|
604
|
+
if (isRunJournalEventInit(event))
|
|
605
|
+
await appendRunEvent(event);
|
|
606
|
+
},
|
|
607
|
+
appendRunEvent,
|
|
608
|
+
async recordPipeline(runId, pipeline) {
|
|
609
|
+
await appendRunEvent({ type: "pipeline-resolved", pipeline }, runId);
|
|
610
|
+
},
|
|
611
|
+
async recordStageOutcome(runId, outcome) {
|
|
612
|
+
await appendRunEvent({ type: "stage-outcome", outcome }, runId);
|
|
613
|
+
},
|
|
614
|
+
async read(runId) {
|
|
615
|
+
return entriesForRun(runId);
|
|
616
|
+
},
|
|
617
|
+
readEntries,
|
|
618
|
+
readProjection: () => foldRunSessionEntries(entriesForRun(options.runId), options.runId)
|
|
619
|
+
};
|
|
620
|
+
}
|
|
20
621
|
async function createRunJournal(sessionManager, runId) {
|
|
21
622
|
try {
|
|
22
|
-
const
|
|
623
|
+
const writableSessionManager = sessionManager;
|
|
624
|
+
const journal = new RunSessionJournal(writableSessionManager, runId);
|
|
23
625
|
const kernel = createJournalSessionProvider({
|
|
24
626
|
runId,
|
|
25
627
|
store: {
|
|
26
|
-
appendCustomEntry: (customType, data) =>
|
|
628
|
+
appendCustomEntry: (customType, data) => writableSessionManager.appendCustomEntry(customType, data),
|
|
27
629
|
getEntries: () => sessionManager.getEntries?.() ?? sessionManager.getBranch?.() ?? []
|
|
28
630
|
}
|
|
29
631
|
});
|
|
@@ -42,24 +644,7 @@ async function createRunJournal(sessionManager, runId) {
|
|
|
42
644
|
}
|
|
43
645
|
}
|
|
44
646
|
|
|
45
|
-
// packages/run-worker/src/notifications.ts
|
|
46
|
-
import { resolve } from "path";
|
|
47
|
-
import { dispatchEventToTargets, loadNotificationConfig } from "@rig/notifications-plugin/notifications";
|
|
48
|
-
async function dispatchRunNotifications(projectRoot, runId, taskId, outcome, detail) {
|
|
49
|
-
try {
|
|
50
|
-
const config = await loadNotificationConfig(resolve(projectRoot, ".rig", "notifications.json"));
|
|
51
|
-
if (config.targets.length === 0)
|
|
52
|
-
return;
|
|
53
|
-
const event = { runId, type: `run.${outcome}`, timestamp: new Date().toISOString(), payload: { taskId, detail } };
|
|
54
|
-
await Promise.race([
|
|
55
|
-
dispatchEventToTargets(event, config.targets),
|
|
56
|
-
new Promise((resolveCap) => setTimeout(resolveCap, 5000))
|
|
57
|
-
]);
|
|
58
|
-
} catch {}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
647
|
// packages/run-worker/src/constants.ts
|
|
62
|
-
var RUN_PROCESS_STEER_TIMEOUT_MS = 10 * 60 * 1000;
|
|
63
648
|
var TRACKED_RUN_STALL_MS = 20 * 60 * 1000;
|
|
64
649
|
var RUN_PROCESS_STALL_SWEEP_MS = 60 * 1000;
|
|
65
650
|
var RUN_PROCESS_STALL_DETAIL = "Run process made no OMP session progress for 20+ minutes; recording a stall so recovery can requeue or resume the session.";
|
|
@@ -108,43 +693,9 @@ function startRunProcessStallMonitor(opts) {
|
|
|
108
693
|
return () => clearInterval(timer);
|
|
109
694
|
}
|
|
110
695
|
|
|
111
|
-
// packages/run-worker/src/panel-plugin.ts
|
|
112
|
-
import { createProjectPluginHost, projectPluginResolutionWarningMessages } from "@rig/core/project-plugins";
|
|
113
|
-
import { RIG_CAPABILITY_PANEL_SLOT, RIG_RUN_STOP_PANEL_ACTION, RIG_SUPERVISOR_PANEL_ID } from "@rig/contracts";
|
|
114
|
-
var RIG_PANELS_CUSTOM_MESSAGE_TYPE = "rig-panels";
|
|
115
|
-
var PANEL_PRODUCER_TIMEOUT_MS = 2000;
|
|
116
|
-
async function produceWorkerPanelPayload(producer, context) {
|
|
117
|
-
if (!producer.produce)
|
|
118
|
-
return;
|
|
119
|
-
let timeout;
|
|
120
|
-
try {
|
|
121
|
-
return await Promise.race([
|
|
122
|
-
Promise.resolve(producer.produce(context)),
|
|
123
|
-
new Promise((resolve2) => {
|
|
124
|
-
timeout = setTimeout(() => resolve2(undefined), PANEL_PRODUCER_TIMEOUT_MS);
|
|
125
|
-
})
|
|
126
|
-
]);
|
|
127
|
-
} finally {
|
|
128
|
-
clearTimeout(timeout);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
async function loadWorkerPanelRegistry(projectRoot) {
|
|
132
|
-
const { host, resolved } = await createProjectPluginHost(projectRoot, {
|
|
133
|
-
mode: "strict-config-only",
|
|
134
|
-
surfaceName: "run-worker-panel-registry"
|
|
135
|
-
});
|
|
136
|
-
for (const message of projectPluginResolutionWarningMessages(resolved)) {
|
|
137
|
-
console.warn(`[rig-run] ${message}`);
|
|
138
|
-
}
|
|
139
|
-
return {
|
|
140
|
-
registrations: host.listPanels().filter((registration) => registration.slot === RIG_CAPABILITY_PANEL_SLOT),
|
|
141
|
-
producers: host.listExecutablePanels().filter((registration) => registration.slot === RIG_CAPABILITY_PANEL_SLOT)
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
|
|
145
696
|
// packages/run-worker/src/utils.ts
|
|
146
|
-
import {
|
|
147
|
-
import { resolveRegistryBaseUrl, resolveRelayUrl } from "@rig/
|
|
697
|
+
import { RIG_WORKFLOW_STATUS_CHANGED as RIG_WORKFLOW_STATUS_CHANGED2 } from "@rig/contracts";
|
|
698
|
+
import { resolveRegistryBaseUrl, resolveRelayUrl } from "@rig/core/remote-config";
|
|
148
699
|
function customEntries(entries) {
|
|
149
700
|
const customs = [];
|
|
150
701
|
for (const entry of entries) {
|
|
@@ -164,6 +715,12 @@ function rigRelayUrl() {
|
|
|
164
715
|
}
|
|
165
716
|
|
|
166
717
|
// packages/run-worker/src/autohost.ts
|
|
718
|
+
var TaskDataCap = defineCapability2(TASK_DATA_SERVICE_CAPABILITY);
|
|
719
|
+
var PlacementTransportCap = defineCapability2(PLACEMENT_RUN_TRANSPORT);
|
|
720
|
+
var RunIdentityEnvCap = defineCapability2(RUN_IDENTITY_ENV);
|
|
721
|
+
var RunRegistryBackboneCap = defineCapability2(RUN_REGISTRY_BACKBONE);
|
|
722
|
+
var RunCloseoutCap = defineCapability2(RUN_CLOSEOUT_CAPABILITY);
|
|
723
|
+
var taskData = () => requireInstalledCapability(TaskDataCap, "task-data capability unavailable in run-exec autohost: ensure @rig/task-sources-plugin (default bundle) is installed.");
|
|
167
724
|
function syntheticRegistryOwner(namespaceKey) {
|
|
168
725
|
const githubUserId = namespaceKey.startsWith("gh:") ? namespaceKey.slice(3) : namespaceKey;
|
|
169
726
|
return {
|
|
@@ -172,19 +729,44 @@ function syntheticRegistryOwner(namespaceKey) {
|
|
|
172
729
|
namespaceKey
|
|
173
730
|
};
|
|
174
731
|
}
|
|
732
|
+
var REGISTRY_STATUS = {
|
|
733
|
+
created: true,
|
|
734
|
+
queued: true,
|
|
735
|
+
preparing: true,
|
|
736
|
+
running: true,
|
|
737
|
+
"waiting-approval": true,
|
|
738
|
+
"waiting-user-input": true,
|
|
739
|
+
paused: true,
|
|
740
|
+
validating: true,
|
|
741
|
+
reviewing: true,
|
|
742
|
+
"closing-out": true,
|
|
743
|
+
"needs-attention": true,
|
|
744
|
+
completed: true,
|
|
745
|
+
failed: true,
|
|
746
|
+
stopped: true,
|
|
747
|
+
starting: true,
|
|
748
|
+
"waiting-input": true
|
|
749
|
+
};
|
|
750
|
+
function coerceRegistryStatus(value, fallback) {
|
|
751
|
+
if (value === "starting")
|
|
752
|
+
return "preparing";
|
|
753
|
+
if (value === "waiting-input")
|
|
754
|
+
return "waiting-user-input";
|
|
755
|
+
return typeof value === "string" && REGISTRY_STATUS[value] ? value : fallback;
|
|
756
|
+
}
|
|
175
757
|
var REGISTRY_PROJECTION_TIMELINE_LIMIT = 100;
|
|
176
758
|
async function reflectStoppedRunTaskSource(input) {
|
|
177
759
|
const taskId = input.taskId?.trim();
|
|
178
760
|
if (!taskId)
|
|
179
761
|
return false;
|
|
180
762
|
const reason = input.reason?.trim();
|
|
181
|
-
await (input.updateLifecycle ?? updateRunTaskSourceLifecycle)(input.projectRoot, {
|
|
763
|
+
await (input.updateLifecycle ?? taskData().updateRunTaskSourceLifecycle)(input.projectRoot, {
|
|
182
764
|
runId: input.runId,
|
|
183
765
|
taskId,
|
|
184
|
-
sourceTask: input.sourceTask,
|
|
185
|
-
worktreePath: input.worktreePath,
|
|
186
|
-
logRoot: input.logRoot,
|
|
187
|
-
sessionPath: input.sessionPath
|
|
766
|
+
...input.sourceTask !== undefined ? { sourceTask: input.sourceTask } : {},
|
|
767
|
+
...input.worktreePath !== undefined ? { worktreePath: input.worktreePath } : {},
|
|
768
|
+
...input.logRoot !== undefined ? { logRoot: input.logRoot } : {},
|
|
769
|
+
...input.sessionPath !== undefined ? { sessionPath: input.sessionPath } : {}
|
|
188
770
|
}, "cancelled", reason ? `Rig stopped by operator: ${reason}` : "Rig stopped by operator.");
|
|
189
771
|
return true;
|
|
190
772
|
}
|
|
@@ -266,8 +848,14 @@ async function maybeStartRunProcessAutohost(api, ctx) {
|
|
|
266
848
|
throw new Error("OMP session id is required when RIG_RUN_PROCESS=1; RIG_RUN_ID is only a dispatch handle.");
|
|
267
849
|
const projectRoot = process.env.PROJECT_RIG_ROOT ?? process.cwd();
|
|
268
850
|
const journal = await createRunJournal(ctx.sessionManager, runId);
|
|
269
|
-
await
|
|
851
|
+
const placementTransport = await loadCapabilityForRoot(projectRoot, PlacementTransportCap);
|
|
852
|
+
if (!placementTransport) {
|
|
853
|
+
throw new Error("placement run transport capability unavailable: ensure @rig/transport-plugin is installed.");
|
|
854
|
+
}
|
|
855
|
+
await adoptRunWorkerKernel(projectRoot, {
|
|
270
856
|
entrypoint: "run-worker",
|
|
857
|
+
hydrateEnv: true,
|
|
858
|
+
transport: placementTransport,
|
|
271
859
|
...journal ? { journal: journal.kernel } : {}
|
|
272
860
|
});
|
|
273
861
|
if (!ctx.hasUI)
|
|
@@ -276,7 +864,9 @@ async function maybeStartRunProcessAutohost(api, ctx) {
|
|
|
276
864
|
const startHost = collab?.startCollabHost ?? collab?.startHost;
|
|
277
865
|
if (!startHost)
|
|
278
866
|
throw new Error("OMP collab host facade is unavailable for Rig run process.");
|
|
279
|
-
const
|
|
867
|
+
const identityEnv = await loadCapabilityForRoot(projectRoot, RunIdentityEnvCap);
|
|
868
|
+
const identity = identityEnv?.resolveRigIdentity(ctx) ?? null;
|
|
869
|
+
const registryBackbone = await loadCapabilityForRoot(projectRoot, RunRegistryBackboneCap);
|
|
280
870
|
const panelRegistry = await loadWorkerPanelRegistry(projectRoot);
|
|
281
871
|
let runProjection = projectRunFromSession(customEntries(ctx.sessionManager.getBranch()), runId);
|
|
282
872
|
const taskIdAtStart = process.env.RIG_TASK_ID?.trim() || runProjection.record.taskId;
|
|
@@ -298,7 +888,7 @@ async function maybeStartRunProcessAutohost(api, ctx) {
|
|
|
298
888
|
const runRegistryNamespace = identity?.owner?.namespaceKey ?? resolveOwnerNamespaceKey(rigProjectRoot()) ?? "anonymous";
|
|
299
889
|
const runRegistryOwner = identity?.owner ?? syntheticRegistryOwner(runRegistryNamespace);
|
|
300
890
|
const runRegistryRepo = identity?.selectedRepo ?? "";
|
|
301
|
-
const runRegistry = createRegistryClient({ baseUrl: registryBaseUrl(), namespaceKey: runRegistryNamespace });
|
|
891
|
+
const runRegistry = registryBackbone?.createRegistryClient({ baseUrl: registryBaseUrl(), namespaceKey: runRegistryNamespace }) ?? null;
|
|
302
892
|
let runRegistryLinks = {};
|
|
303
893
|
const processedControlEntryIds = new Set((ctx.sessionManager.getEntries?.() ?? []).map((entry) => entry && typeof entry === "object" && typeof entry.id === "string" ? entry.id : null).filter((id) => id !== null));
|
|
304
894
|
const processedControlEntryObjects = new WeakSet;
|
|
@@ -332,7 +922,7 @@ async function maybeStartRunProcessAutohost(api, ctx) {
|
|
|
332
922
|
joinLink: links.joinLink,
|
|
333
923
|
webLink: links.webLink,
|
|
334
924
|
relayUrl: links.relayUrl,
|
|
335
|
-
sessionPath: ctx.sessionManager.getSessionFile(),
|
|
925
|
+
sessionPath: ctx.sessionManager.getSessionFile() ?? null,
|
|
336
926
|
timeline
|
|
337
927
|
});
|
|
338
928
|
};
|
|
@@ -346,7 +936,7 @@ async function maybeStartRunProcessAutohost(api, ctx) {
|
|
|
346
936
|
projectRoot,
|
|
347
937
|
runId,
|
|
348
938
|
folded,
|
|
349
|
-
taskIdAtStart,
|
|
939
|
+
...taskIdAtStart !== undefined ? { taskIdAtStart } : {},
|
|
350
940
|
runDisplayTitle
|
|
351
941
|
};
|
|
352
942
|
const payloadEntries = await Promise.all(panelRegistry.producers.map(async (producer) => {
|
|
@@ -406,7 +996,7 @@ async function maybeStartRunProcessAutohost(api, ctx) {
|
|
|
406
996
|
runRegistryHeartbeat = null;
|
|
407
997
|
}
|
|
408
998
|
if (workerProjectionFiber) {
|
|
409
|
-
|
|
999
|
+
Effect2.runFork(Fiber.interrupt(workerProjectionFiber));
|
|
410
1000
|
workerProjectionFiber = null;
|
|
411
1001
|
}
|
|
412
1002
|
if (workerProjectionConnection) {
|
|
@@ -518,7 +1108,7 @@ async function maybeStartRunProcessAutohost(api, ctx) {
|
|
|
518
1108
|
if (roomId !== runId) {
|
|
519
1109
|
throw new Error(`Collab host session id mismatch: expected ${runId}, got ${roomId}`);
|
|
520
1110
|
}
|
|
521
|
-
runRegistryLinks = { joinLink: projection.joinLink, webLink: projection.webLink, relayUrl: projection.relayUrl ?? rigRelayUrl() };
|
|
1111
|
+
runRegistryLinks = { joinLink: projection.joinLink ?? null, webLink: projection.webLink ?? null, relayUrl: projection.relayUrl ?? rigRelayUrl() };
|
|
522
1112
|
const timeline = {
|
|
523
1113
|
type: "collab-host-started",
|
|
524
1114
|
roomId,
|
|
@@ -530,10 +1120,11 @@ async function maybeStartRunProcessAutohost(api, ctx) {
|
|
|
530
1120
|
console.log(`[rig-run] collab-host-started joinLink=${projection.joinLink || "(empty)"} relayUrl=${projection.relayUrl || "(empty)"}`);
|
|
531
1121
|
ctx.ui.notify("Rig run collab host started.", "info");
|
|
532
1122
|
publishRigPanelsSnapshotBestEffort();
|
|
533
|
-
if (runRegistry) {
|
|
1123
|
+
if (runRegistry && registryBackbone) {
|
|
534
1124
|
try {
|
|
535
1125
|
runRegistryRoomId = roomId;
|
|
536
1126
|
const initialProjection = buildCurrentRegistryProjection();
|
|
1127
|
+
const sessionPath = ctx.sessionManager.getSessionFile();
|
|
537
1128
|
await runRegistry.registerRoom({
|
|
538
1129
|
roomId,
|
|
539
1130
|
owner: runRegistryOwner,
|
|
@@ -545,14 +1136,14 @@ async function maybeStartRunProcessAutohost(api, ctx) {
|
|
|
545
1136
|
relayUrl: projection.relayUrl ?? rigRelayUrl(),
|
|
546
1137
|
startedAt: new Date().toISOString(),
|
|
547
1138
|
cwd: process.cwd(),
|
|
548
|
-
sessionPath
|
|
1139
|
+
...sessionPath ? { sessionPath } : {},
|
|
549
1140
|
pid: process.pid,
|
|
550
1141
|
projection: initialProjection
|
|
551
1142
|
});
|
|
552
|
-
workerProjectionConnection = connectWorkerProjection({ baseUrl: registryBaseUrl(), namespaceKey: runRegistryNamespace, runId: roomId });
|
|
1143
|
+
workerProjectionConnection = registryBackbone.connectWorkerProjection({ baseUrl: registryBaseUrl(), namespaceKey: runRegistryNamespace, runId: roomId });
|
|
553
1144
|
workerProjectionConnection.ready.catch((err) => console.error(`[rig-run] worker-projection-degraded (heartbeat remains authoritative): ${err instanceof Error ? err.message : String(err)}`));
|
|
554
1145
|
pushWorkerProjection();
|
|
555
|
-
workerProjectionFiber =
|
|
1146
|
+
workerProjectionFiber = Effect2.runFork(localRunChanges(projectRoot).pipe(Stream2.debounce(Duration.millis(500)), Stream2.runForEach(() => Effect2.sync(() => pushWorkerProjection()))));
|
|
556
1147
|
journal?.appendTimeline({ type: "registry-registered", roomId });
|
|
557
1148
|
console.log(`[rig-run] registry-registered roomId=${roomId}`);
|
|
558
1149
|
runRegistryHeartbeat = setInterval(() => {
|
|
@@ -585,7 +1176,7 @@ async function maybeStartRunProcessAutohost(api, ctx) {
|
|
|
585
1176
|
if (taskIdAtStart) {
|
|
586
1177
|
journal?.appendTimeline({ type: "stage", stage: "GitHub-sync", status: "running", detail: "reflecting rig:running to task source" });
|
|
587
1178
|
try {
|
|
588
|
-
await updateRunTaskSourceLifecycle(projectRoot, {
|
|
1179
|
+
await taskData().updateRunTaskSourceLifecycle(projectRoot, {
|
|
589
1180
|
runId,
|
|
590
1181
|
taskId: taskIdAtStart,
|
|
591
1182
|
sourceTask: runProjection.record.sourceTask,
|
|
@@ -678,10 +1269,9 @@ async function maybeStartRunProcessAutohost(api, ctx) {
|
|
|
678
1269
|
setTimeout(() => process.exit(1), 0);
|
|
679
1270
|
return;
|
|
680
1271
|
}
|
|
681
|
-
const { command, gitCommand } = createEnvCloseoutRunners(process.env);
|
|
682
1272
|
let closeoutStatusAdvanced = false;
|
|
683
1273
|
try {
|
|
684
|
-
await
|
|
1274
|
+
await requireInstalledCapability(RunCloseoutCap, "run-closeout capability unavailable in run-worker autohost: ensure @rig/bundle-default-lifecycle (default bundle) is installed.")({
|
|
685
1275
|
projectRoot,
|
|
686
1276
|
runId,
|
|
687
1277
|
taskId,
|
|
@@ -689,8 +1279,6 @@ async function maybeStartRunProcessAutohost(api, ctx) {
|
|
|
689
1279
|
workspace: process.cwd(),
|
|
690
1280
|
artifactRoot: runProjection.record.artifactRoot ?? null,
|
|
691
1281
|
sourceTask: runProjection.record.sourceTask && typeof runProjection.record.sourceTask === "object" && !Array.isArray(runProjection.record.sourceTask) ? runProjection.record.sourceTask : null,
|
|
692
|
-
command,
|
|
693
|
-
gitCommand,
|
|
694
1282
|
steerPi,
|
|
695
1283
|
onValidationStart: async () => {
|
|
696
1284
|
journal?.appendStatus("validating", { actor: { kind: "agent" }, reason: "validating task before closeout", force: true });
|
|
@@ -706,7 +1294,7 @@ async function maybeStartRunProcessAutohost(api, ctx) {
|
|
|
706
1294
|
}
|
|
707
1295
|
},
|
|
708
1296
|
reflect: async (status, summary, opts) => {
|
|
709
|
-
await updateRunTaskSourceLifecycle(projectRoot, {
|
|
1297
|
+
await taskData().updateRunTaskSourceLifecycle(projectRoot, {
|
|
710
1298
|
runId,
|
|
711
1299
|
taskId,
|
|
712
1300
|
sourceTask: runProjection.record.sourceTask,
|
|
@@ -725,7 +1313,7 @@ async function maybeStartRunProcessAutohost(api, ctx) {
|
|
|
725
1313
|
setTimeout(() => process.exit(0), 0);
|
|
726
1314
|
} catch (error) {
|
|
727
1315
|
const detail = error instanceof Error ? error.message : String(error);
|
|
728
|
-
const status = error instanceof CloseoutValidationError ? "needs-attention" : "failed";
|
|
1316
|
+
const status = error instanceof Error && error.name === "CloseoutValidationError" ? "needs-attention" : "failed";
|
|
729
1317
|
journal?.appendStatus(status, { actor: { kind: "agent" }, errorText: detail, force: true });
|
|
730
1318
|
await publishRunProjection(status);
|
|
731
1319
|
await dispatchRunNotifications(projectRoot, runId, taskId, "failed", detail);
|
|
@@ -751,7 +1339,7 @@ async function maybeStartSpikeAutohost(ctx) {
|
|
|
751
1339
|
const startHost = collab?.startHost ?? collab?.startCollabHost;
|
|
752
1340
|
if (!startHost)
|
|
753
1341
|
throw new Error("OMP collab host facade is unavailable for detached PTY spike.");
|
|
754
|
-
const identity = resolveRigIdentity(ctx);
|
|
1342
|
+
const identity = (await loadCapabilityForRoot(rigProjectRoot(), RunIdentityEnvCap))?.resolveRigIdentity(ctx) ?? null;
|
|
755
1343
|
await startHost.call(collab, {
|
|
756
1344
|
relayUrl,
|
|
757
1345
|
title: "Rig detached PTY spike",
|