@algosuite/vo-mcp 0.2.0-beta.22 → 0.2.0-beta.24
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/agent-auth-probe-cli.mjs +1386 -0
- package/dist/autostart-cli.js +29 -3
- package/dist/autostart-cli.js.map +2 -2
- package/dist/ci/check-local-pr-overlap.js +47142 -42080
- package/dist/cli.js +3 -10
- package/dist/cli.js.map +2 -2
- package/dist/index.js +3 -10
- package/dist/index.js.map +2 -2
- package/dist/install-cli.js +28 -2
- package/dist/install-cli.js.map +2 -2
- package/dist/runner-cli.js +3535 -1046
- package/dist/runner-cli.js.map +4 -4
- package/dist/runner-supervisor.js +218 -64
- package/dist/runner-supervisor.js.map +4 -4
- package/dist/thresholds.json +62 -0
- package/package.json +1 -1
|
@@ -61,8 +61,130 @@ async function fetchInstallationToken({ req, required = false, readOnly = false,
|
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
// ../../scripts/virtual-office/code-runner/control-plane-task-list.mjs
|
|
65
|
+
var PAGE_SIZE = 500;
|
|
66
|
+
async function listAllPrOpenedTasks(request) {
|
|
67
|
+
const tasks = [];
|
|
68
|
+
let beforeCreatedAt = "";
|
|
69
|
+
let beforeId = "";
|
|
70
|
+
for (; ; ) {
|
|
71
|
+
const params = new URLSearchParams({
|
|
72
|
+
status: "pr_opened",
|
|
73
|
+
limit: String(PAGE_SIZE),
|
|
74
|
+
runner_adoption: "1"
|
|
75
|
+
});
|
|
76
|
+
if (beforeCreatedAt) {
|
|
77
|
+
params.set("before_created_at", beforeCreatedAt);
|
|
78
|
+
params.set("before_id", beforeId);
|
|
79
|
+
}
|
|
80
|
+
const res = await request("GET", `/api/v1/code-task?${params}`);
|
|
81
|
+
if (!res.ok) throw new Error(`listPrOpenedTasks failed: HTTP ${res.status}`);
|
|
82
|
+
const json = await res.json();
|
|
83
|
+
const page = Array.isArray(json?.tasks) ? json.tasks : [];
|
|
84
|
+
tasks.push(...page);
|
|
85
|
+
if (page.length < PAGE_SIZE) return tasks;
|
|
86
|
+
const last = page.at(-1);
|
|
87
|
+
if (!last?.created_at || !last?.code_task_id) {
|
|
88
|
+
throw new Error("listPrOpenedTasks pagination cursor missing");
|
|
89
|
+
}
|
|
90
|
+
beforeCreatedAt = last.created_at;
|
|
91
|
+
beforeId = last.code_task_id;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ../../scripts/virtual-office/code-runner/control-plane-resume.mjs
|
|
96
|
+
async function resumeCodeTaskRequest(req, taskId, { automaticRateLimit = false, automaticContinuation = false } = {}, onUnauthorized = () => {
|
|
97
|
+
}) {
|
|
98
|
+
const res = await req(
|
|
99
|
+
"POST",
|
|
100
|
+
`/api/v1/code-task/${encodeURIComponent(taskId)}/resume`,
|
|
101
|
+
automaticRateLimit ? { automatic_rate_limit: true } : automaticContinuation ? { automatic_continuation: true } : {}
|
|
102
|
+
);
|
|
103
|
+
if (res.status === 401) {
|
|
104
|
+
onUnauthorized();
|
|
105
|
+
throw new Error("resume unauthorized (401)");
|
|
106
|
+
}
|
|
107
|
+
if (!res.ok) throw new Error(`resume failed: HTTP ${res.status}`);
|
|
108
|
+
const json = await res.json();
|
|
109
|
+
return json && json.task ? json.task : null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ../../scripts/virtual-office/code-runner/control-plane-autonomous-admission.mjs
|
|
113
|
+
function makeAutonomousDispatchAdmissionClient(req, timeoutMs, onUnauthorized = () => {
|
|
114
|
+
}) {
|
|
115
|
+
return {
|
|
116
|
+
async reserveAutonomousDispatchBudget({ requestedBudgetUsd, reservationId, occurrenceKey }) {
|
|
117
|
+
const res = await req("POST", "/api/v1/autonomous-dispatch/admission", {
|
|
118
|
+
requested_budget_usd: requestedBudgetUsd,
|
|
119
|
+
reservation_id: reservationId,
|
|
120
|
+
dispatch_occurrence_key: occurrenceKey
|
|
121
|
+
}, { timeoutMs });
|
|
122
|
+
if (res.status === 401) {
|
|
123
|
+
onUnauthorized();
|
|
124
|
+
throw new Error("autonomous dispatch admission unauthorized (401)");
|
|
125
|
+
}
|
|
126
|
+
if (!res.ok) throw new Error(`autonomous dispatch admission failed: HTTP ${res.status}`);
|
|
127
|
+
const body = await res.json();
|
|
128
|
+
return {
|
|
129
|
+
allowed: body?.allowed === true,
|
|
130
|
+
reason: typeof body?.reason === "string" ? body.reason : ""
|
|
131
|
+
};
|
|
132
|
+
},
|
|
133
|
+
async releaseAutonomousDispatchBudget(reservationId) {
|
|
134
|
+
const res = await req("POST", "/api/v1/autonomous-dispatch/reservation/release", {
|
|
135
|
+
reservation_id: reservationId
|
|
136
|
+
}, { timeoutMs });
|
|
137
|
+
if (res.status === 401) {
|
|
138
|
+
onUnauthorized();
|
|
139
|
+
throw new Error("autonomous dispatch release unauthorized (401)");
|
|
140
|
+
}
|
|
141
|
+
if (!res.ok) throw new Error(`autonomous dispatch release failed: HTTP ${res.status}`);
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ../../scripts/virtual-office/code-runner/control-plane-merge.mjs
|
|
148
|
+
async function mergeVerifiedPrRequest(req, prNumber, automationContext, onUnauthorized) {
|
|
149
|
+
const res = await req(
|
|
150
|
+
"POST",
|
|
151
|
+
"/api/v1/admin/pr/merge",
|
|
152
|
+
{ prNumber, automationContext },
|
|
153
|
+
{ timeoutMs: 12e4 }
|
|
154
|
+
);
|
|
155
|
+
if (res.status === 401) {
|
|
156
|
+
onUnauthorized();
|
|
157
|
+
throw new Error("gated merge unauthorized (401)");
|
|
158
|
+
}
|
|
159
|
+
const json = await res.json().catch(() => ({}));
|
|
160
|
+
if (res.ok && json?.ok === true) {
|
|
161
|
+
const result = json?.result && typeof json.result === "object" ? json.result : {};
|
|
162
|
+
const status = result.merged === true || result.status === "merged" ? "merged" : result.status === "auto-merge-enabled" || String(result.action || "").includes("auto-merge") ? "queued" : "accepted";
|
|
163
|
+
return {
|
|
164
|
+
status,
|
|
165
|
+
detail: typeof result.detail === "string" ? result.detail : null,
|
|
166
|
+
actionReceiptId: typeof json.action_receipt_id === "string" ? json.action_receipt_id : null
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
if (res.status === 503 && json?.error === "verify_unavailable") {
|
|
170
|
+
return { status: "retry", reason: json.reason || "verification unavailable" };
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
status: "blocked",
|
|
174
|
+
reason: json?.reason || json?.message || json?.error || `HTTP ${res.status}`,
|
|
175
|
+
actionReceiptId: typeof json?.action_receipt_id === "string" ? json.action_receipt_id : null
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
64
179
|
// ../../scripts/virtual-office/code-runner/control-plane-client.mjs
|
|
65
180
|
var cachedFirebaseToken = null;
|
|
181
|
+
var ClaimAuthorityChangedError = class extends Error {
|
|
182
|
+
constructor() {
|
|
183
|
+
super("code-task claim authority changed");
|
|
184
|
+
this.name = "ClaimAuthorityChangedError";
|
|
185
|
+
this.code = "code_task_claim_authority_changed";
|
|
186
|
+
}
|
|
187
|
+
};
|
|
66
188
|
async function resolveBearer(env) {
|
|
67
189
|
const adminToken = env.VO_CONTROL_PLANE_ADMIN_TOKEN;
|
|
68
190
|
if (adminToken) return adminToken;
|
|
@@ -84,12 +206,16 @@ function createControlPlaneClient({
|
|
|
84
206
|
heartbeatTimeoutMs = Math.min(
|
|
85
207
|
Math.max(Number(env.VO_CODE_RUNNER_HEARTBEAT_TIMEOUT_MS) || 15e3, 1e3),
|
|
86
208
|
6e4
|
|
87
|
-
)
|
|
209
|
+
),
|
|
210
|
+
taskRequestTimeoutMs = Math.min(
|
|
211
|
+
Math.max(Number(env.VO_CODE_RUNNER_TASK_REQUEST_TIMEOUT_MS) || 5e3, 100),
|
|
212
|
+
6e4
|
|
213
|
+
),
|
|
214
|
+
runnerId: runnerId2,
|
|
215
|
+
runnerInstanceId
|
|
88
216
|
} = {}) {
|
|
89
217
|
const resolvedBaseUrl = baseUrl ?? env.VO_CONTROL_PLANE_URL ?? "";
|
|
90
|
-
if (!resolvedBaseUrl)
|
|
91
|
-
throw new Error("VO_CONTROL_PLANE_URL is required for the code-runner daemon");
|
|
92
|
-
}
|
|
218
|
+
if (!resolvedBaseUrl) throw new Error("VO_CONTROL_PLANE_URL is required for the code-runner daemon");
|
|
93
219
|
const root = resolvedBaseUrl.replace(/\/+$/, "");
|
|
94
220
|
async function req(method, path2, body, { timeoutMs } = {}) {
|
|
95
221
|
const bearer = await resolveBearer(env);
|
|
@@ -117,7 +243,15 @@ function createControlPlaneClient({
|
|
|
117
243
|
clearTimeout(timeoutId);
|
|
118
244
|
}
|
|
119
245
|
}
|
|
246
|
+
const taskReq = (method, path2, body, options = {}) => req(method, path2, body, { timeoutMs: taskRequestTimeoutMs, ...options });
|
|
120
247
|
return {
|
|
248
|
+
...makeAutonomousDispatchAdmissionClient(
|
|
249
|
+
req,
|
|
250
|
+
taskRequestTimeoutMs,
|
|
251
|
+
() => {
|
|
252
|
+
cachedFirebaseToken = null;
|
|
253
|
+
}
|
|
254
|
+
),
|
|
121
255
|
/**
|
|
122
256
|
* Claim the next pending task. Returns the task or null (empty queue).
|
|
123
257
|
* `repos` (optional `owner/name` list) and `operatorIds` (optional
|
|
@@ -125,17 +259,20 @@ function createControlPlaneClient({
|
|
|
125
259
|
* serves — the control-plane filters by both (logical AND), so another
|
|
126
260
|
* operator's task never lands on (or bills) this machine.
|
|
127
261
|
*/
|
|
128
|
-
async claim(
|
|
129
|
-
const body = { runner_id:
|
|
262
|
+
async claim(runnerId3, repos, operatorIds, session = {}) {
|
|
263
|
+
const body = { runner_id: runnerId3 };
|
|
130
264
|
if (Array.isArray(repos) && repos.length > 0) body.repos = repos;
|
|
131
265
|
if (Array.isArray(operatorIds) && operatorIds.length > 0) body.operator_ids = operatorIds;
|
|
132
|
-
if (session.runnerInstanceId)
|
|
266
|
+
if (session.runnerInstanceId) {
|
|
267
|
+
body.runner_instance_id = session.runnerInstanceId;
|
|
268
|
+
body.runner_progress_protocol_version = 2;
|
|
269
|
+
}
|
|
133
270
|
if (session.runnerInstanceId && session.reconcileStale) body.reconcile_stale = true;
|
|
134
271
|
if (session.defaultAgent) body.default_agent = session.defaultAgent;
|
|
135
272
|
if (Array.isArray(session.availableAgents)) {
|
|
136
273
|
body.available_agents = session.availableAgents.filter((entry) => entry?.installed === true && entry?.authenticated === true).map((entry) => entry.agent);
|
|
137
274
|
}
|
|
138
|
-
const res = await
|
|
275
|
+
const res = await taskReq("POST", "/api/v1/code-task/claim", body);
|
|
139
276
|
if (res.status === 401) {
|
|
140
277
|
cachedFirebaseToken = null;
|
|
141
278
|
throw new Error("claim unauthorized (401)");
|
|
@@ -149,11 +286,19 @@ function createControlPlaneClient({
|
|
|
149
286
|
* Server derives operator/tenant from the daemon's authenticated principal.
|
|
150
287
|
* Returns the created task, or throws on a non-2xx response.
|
|
151
288
|
*/
|
|
152
|
-
async enqueueCodeTask({ repo, prompt, max_budget_usd, max_turns }) {
|
|
289
|
+
async enqueueCodeTask({ repo, prompt, max_budget_usd, max_turns, dispatch_mode, tier, agent, model, dispatch_occurrence_key, autonomous_reservation_id, on_behalf_of_operator_id, repair_pr_number, repair_kind, repair_head_sha, repair_chain }) {
|
|
153
290
|
const body = { repo, prompt };
|
|
154
291
|
if (typeof max_budget_usd === "number") body.max_budget_usd = max_budget_usd;
|
|
155
292
|
if (typeof max_turns === "number") body.max_turns = max_turns;
|
|
156
|
-
const
|
|
293
|
+
for (const [key, value] of Object.entries({ dispatch_mode, tier, agent, model, repair_kind, repair_head_sha })) {
|
|
294
|
+
if (value) body[key] = value;
|
|
295
|
+
}
|
|
296
|
+
if (dispatch_occurrence_key) body.dispatch_occurrence_key = dispatch_occurrence_key;
|
|
297
|
+
if (autonomous_reservation_id) body.autonomous_reservation_id = autonomous_reservation_id;
|
|
298
|
+
if (on_behalf_of_operator_id) body.on_behalf_of_operator_id = on_behalf_of_operator_id;
|
|
299
|
+
if (Number.isInteger(repair_pr_number) && repair_pr_number > 0) body.repair_pr_number = repair_pr_number;
|
|
300
|
+
if (repair_chain) body.repair_chain = repair_chain;
|
|
301
|
+
const res = await taskReq("POST", "/api/v1/code-task", body);
|
|
157
302
|
if (res.status === 401) {
|
|
158
303
|
cachedFirebaseToken = null;
|
|
159
304
|
throw new Error("enqueue unauthorized (401)");
|
|
@@ -166,15 +311,10 @@ function createControlPlaneClient({
|
|
|
166
311
|
* Resume a failed/cancelled/max-turn partial code-task. The PR watcher uses
|
|
167
312
|
* this after the runner opens a partial draft PR and CI is no longer pending.
|
|
168
313
|
*/
|
|
169
|
-
async resumeCodeTask(taskId) {
|
|
170
|
-
|
|
171
|
-
if (res.status === 401) {
|
|
314
|
+
async resumeCodeTask(taskId, { automaticRateLimit = false, automaticContinuation = false } = {}) {
|
|
315
|
+
return resumeCodeTaskRequest(taskReq, taskId, { automaticRateLimit, automaticContinuation }, () => {
|
|
172
316
|
cachedFirebaseToken = null;
|
|
173
|
-
|
|
174
|
-
}
|
|
175
|
-
if (!res.ok) throw new Error(`resume failed: HTTP ${res.status}`);
|
|
176
|
-
const json = await res.json();
|
|
177
|
-
return json && json.task ? json.task : null;
|
|
317
|
+
});
|
|
178
318
|
},
|
|
179
319
|
/**
|
|
180
320
|
* Send a CI-green PR through the production verify-before-act merge route.
|
|
@@ -182,29 +322,14 @@ function createControlPlaneClient({
|
|
|
182
322
|
* consensus, records a receipt, and direct-merges only the inspected SHA.
|
|
183
323
|
*/
|
|
184
324
|
async mergeVerifiedPr(prNumber, automationContext) {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
const status = result.merged === true || result.status === "merged" ? "merged" : result.status === "auto-merge-enabled" || String(result.action || "").includes("auto-merge") ? "queued" : "accepted";
|
|
194
|
-
return {
|
|
195
|
-
status,
|
|
196
|
-
detail: typeof result.detail === "string" ? result.detail : null,
|
|
197
|
-
actionReceiptId: typeof json.action_receipt_id === "string" ? json.action_receipt_id : null
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
if (res.status === 503 && json?.error === "verify_unavailable") {
|
|
201
|
-
return { status: "retry", reason: json.reason || "verification unavailable" };
|
|
202
|
-
}
|
|
203
|
-
return {
|
|
204
|
-
status: "blocked",
|
|
205
|
-
reason: json?.reason || json?.message || json?.error || `HTTP ${res.status}`,
|
|
206
|
-
actionReceiptId: typeof json?.action_receipt_id === "string" ? json.action_receipt_id : null
|
|
207
|
-
};
|
|
325
|
+
return mergeVerifiedPrRequest(
|
|
326
|
+
req,
|
|
327
|
+
prNumber,
|
|
328
|
+
automationContext,
|
|
329
|
+
() => {
|
|
330
|
+
cachedFirebaseToken = null;
|
|
331
|
+
}
|
|
332
|
+
);
|
|
208
333
|
},
|
|
209
334
|
/**
|
|
210
335
|
* Append progress / set terminal status. Returns
|
|
@@ -212,23 +337,37 @@ function createControlPlaneClient({
|
|
|
212
337
|
* { terminal: true } — task already terminal (operator cancelled): STOP
|
|
213
338
|
*/
|
|
214
339
|
async postProgress(taskId, patch) {
|
|
215
|
-
const
|
|
216
|
-
|
|
340
|
+
const progress = {
|
|
341
|
+
...patch,
|
|
342
|
+
...patch.runner_id ? {} : runnerId2 ? { runner_id: runnerId2 } : {},
|
|
343
|
+
...patch.runner_instance_id ? {} : runnerInstanceId ? { runner_instance_id: runnerInstanceId } : {}
|
|
344
|
+
};
|
|
345
|
+
const res = await taskReq("PATCH", `/api/v1/code-task/${taskId}/progress`, progress);
|
|
346
|
+
if (res.status === 409) {
|
|
347
|
+
const conflict = await res.json().catch(() => ({}));
|
|
348
|
+
if (conflict?.error === "code_task_claim_authority_changed") {
|
|
349
|
+
throw new ClaimAuthorityChangedError();
|
|
350
|
+
}
|
|
351
|
+
return { terminal: true };
|
|
352
|
+
}
|
|
217
353
|
if (res.status === 404) return { terminal: true, missing: true };
|
|
218
354
|
if (!res.ok) throw new Error(`progress failed: HTTP ${res.status}`);
|
|
219
355
|
const json = await res.json();
|
|
220
356
|
return { task: json && json.task };
|
|
221
357
|
},
|
|
222
358
|
async getTask(taskId) {
|
|
223
|
-
const res = await
|
|
359
|
+
const res = await taskReq("GET", `/api/v1/code-task/${taskId}`);
|
|
224
360
|
if (res.status === 404) return null;
|
|
225
361
|
if (!res.ok) throw new Error(`getTask failed: HTTP ${res.status}`);
|
|
226
362
|
const json = await res.json();
|
|
227
363
|
return json ? json.task : null;
|
|
228
364
|
},
|
|
365
|
+
async listPrOpenedTasks() {
|
|
366
|
+
return listAllPrOpenedTasks(taskReq);
|
|
367
|
+
},
|
|
229
368
|
async downloadTaskAttachment(taskId, attachmentId) {
|
|
230
369
|
const path2 = `/api/v1/code-task/${encodeURIComponent(taskId)}/attachment/${encodeURIComponent(attachmentId)}`;
|
|
231
|
-
const res = await
|
|
370
|
+
const res = await taskReq("GET", path2);
|
|
232
371
|
if (res.status === 401) cachedFirebaseToken = null;
|
|
233
372
|
if (!res.ok) throw new Error(`attachment download failed: HTTP ${res.status}`);
|
|
234
373
|
return Buffer.from(await res.arrayBuffer());
|
|
@@ -236,7 +375,7 @@ function createControlPlaneClient({
|
|
|
236
375
|
async getTaskKnowledgeContext(taskId, { query } = {}) {
|
|
237
376
|
const body = {};
|
|
238
377
|
if (typeof query === "string" && query.trim()) body.query = query;
|
|
239
|
-
const res = await
|
|
378
|
+
const res = await taskReq("POST", `/api/v1/code-task/${encodeURIComponent(taskId)}/knowledge-context`, body);
|
|
240
379
|
if (res.status === 401) {
|
|
241
380
|
cachedFirebaseToken = null;
|
|
242
381
|
throw new Error("knowledge-context unauthorized (401)");
|
|
@@ -255,10 +394,10 @@ function createControlPlaneClient({
|
|
|
255
394
|
* `tokens` = { input_tokens, output_tokens, cache_creation_tokens, cache_read_tokens }.
|
|
256
395
|
* Optional: `claudeWeeklyPct` (number) + `claudeWeeklyResetsAt` (ISO string | null).
|
|
257
396
|
*/
|
|
258
|
-
async postWeeklyTokens({ operatorId, runnerId:
|
|
397
|
+
async postWeeklyTokens({ operatorId, runnerId: runnerId3, tokens, claudeWeeklyPct, claudeWeeklyResetsAt }) {
|
|
259
398
|
const body = {
|
|
260
399
|
operator_id: operatorId,
|
|
261
|
-
runner_id:
|
|
400
|
+
runner_id: runnerId3,
|
|
262
401
|
input_tokens: tokens.input_tokens,
|
|
263
402
|
output_tokens: tokens.output_tokens,
|
|
264
403
|
cache_creation_tokens: tokens.cache_creation_tokens,
|
|
@@ -270,7 +409,7 @@ function createControlPlaneClient({
|
|
|
270
409
|
if (claudeWeeklyResetsAt !== void 0) {
|
|
271
410
|
body.claude_weekly_resets_at = claudeWeeklyResetsAt;
|
|
272
411
|
}
|
|
273
|
-
const res = await
|
|
412
|
+
const res = await taskReq("POST", "/api/v1/weekly-tokens", body);
|
|
274
413
|
if (res.status === 401) {
|
|
275
414
|
cachedFirebaseToken = null;
|
|
276
415
|
throw new Error("weekly-tokens unauthorized (401)");
|
|
@@ -283,9 +422,9 @@ function createControlPlaneClient({
|
|
|
283
422
|
* authenticated operator so the web shows a TRUE "runner online" signal.
|
|
284
423
|
* Best-effort caller; throws on 401/non-ok so the daemon can log + retry.
|
|
285
424
|
*/
|
|
286
|
-
async postHeartbeat({ runnerId:
|
|
287
|
-
const body = { runner_id:
|
|
288
|
-
if (
|
|
425
|
+
async postHeartbeat({ runnerId: runnerId3, runnerInstanceId: runnerInstanceId2, operatorId, uptimeSec, activeTasks, maxConcurrency, effectiveConcurrency, measuredTaskSlots, measuredCpuSlots, measuredMemorySlots, version, daemonVersion, defaultAgent, supervisorInstanceId: supervisorInstanceId2, supervisorVersion: supervisorVersion2, supervisorCapabilities, servedRepos, servedOperators, availableAgents, accountUsage, availableLocalModels }) {
|
|
426
|
+
const body = { runner_id: runnerId3 };
|
|
427
|
+
if (runnerInstanceId2) body.runner_instance_id = runnerInstanceId2;
|
|
289
428
|
if (operatorId) body.operator_id = operatorId;
|
|
290
429
|
if (typeof uptimeSec === "number") body.uptime_sec = uptimeSec;
|
|
291
430
|
if (typeof activeTasks === "number") body.active_tasks = activeTasks;
|
|
@@ -322,7 +461,17 @@ function createControlPlaneClient({
|
|
|
322
461
|
cachedFirebaseToken = null;
|
|
323
462
|
throw new Error("heartbeat unauthorized (401)");
|
|
324
463
|
}
|
|
325
|
-
if (!res.ok)
|
|
464
|
+
if (!res.ok) {
|
|
465
|
+
let detail = "";
|
|
466
|
+
try {
|
|
467
|
+
const body2 = await res.json();
|
|
468
|
+
if (Array.isArray(body2?.issue_paths) && body2.issue_paths.length > 0) {
|
|
469
|
+
detail = ` (rejected fields: ${body2.issue_paths.join(", ")})`;
|
|
470
|
+
}
|
|
471
|
+
} catch {
|
|
472
|
+
}
|
|
473
|
+
throw new Error(`heartbeat failed: HTTP ${res.status}${detail}`);
|
|
474
|
+
}
|
|
326
475
|
return res.json();
|
|
327
476
|
},
|
|
328
477
|
async getRunnerStatus({ operatorId } = {}) {
|
|
@@ -338,13 +487,13 @@ function createControlPlaneClient({
|
|
|
338
487
|
const body = await res.json();
|
|
339
488
|
return Array.isArray(body?.runners) ? body.runners : [];
|
|
340
489
|
},
|
|
341
|
-
async pollRunnerControl({ runnerId:
|
|
342
|
-
const body = { runner_id:
|
|
490
|
+
async pollRunnerControl({ runnerId: runnerId3, operatorId, supervisorInstanceId: supervisorInstanceId2, supervisorVersion: supervisorVersion2, capabilities }) {
|
|
491
|
+
const body = { runner_id: runnerId3 };
|
|
343
492
|
if (operatorId) body.operator_id = operatorId;
|
|
344
493
|
if (supervisorInstanceId2) body.supervisor_instance_id = supervisorInstanceId2;
|
|
345
494
|
if (supervisorVersion2) body.supervisor_version = supervisorVersion2;
|
|
346
495
|
if (Array.isArray(capabilities) && capabilities.length > 0) body.capabilities = capabilities;
|
|
347
|
-
const res = await
|
|
496
|
+
const res = await taskReq("POST", "/api/v1/runner/control/poll", body);
|
|
348
497
|
if (res.status === 401) {
|
|
349
498
|
cachedFirebaseToken = null;
|
|
350
499
|
throw new Error("runner control poll unauthorized (401)");
|
|
@@ -354,14 +503,14 @@ function createControlPlaneClient({
|
|
|
354
503
|
const action = json?.action;
|
|
355
504
|
return action && typeof action.action_id === "string" && action.action_id ? { ...action, actionId: action.action_id } : null;
|
|
356
505
|
},
|
|
357
|
-
async completeRunnerControl(actionId, { runnerId:
|
|
358
|
-
const body = { runner_id:
|
|
506
|
+
async completeRunnerControl(actionId, { runnerId: runnerId3, operatorId, supervisorInstanceId: supervisorInstanceId2, supervisorVersion: supervisorVersion2, capabilities, status, detail }) {
|
|
507
|
+
const body = { runner_id: runnerId3, status };
|
|
359
508
|
if (operatorId) body.operator_id = operatorId;
|
|
360
509
|
if (supervisorInstanceId2) body.supervisor_instance_id = supervisorInstanceId2;
|
|
361
510
|
if (supervisorVersion2) body.supervisor_version = supervisorVersion2;
|
|
362
511
|
if (Array.isArray(capabilities) && capabilities.length > 0) body.capabilities = capabilities;
|
|
363
512
|
if (detail) body.detail = detail;
|
|
364
|
-
const res = await
|
|
513
|
+
const res = await taskReq("POST", `/api/v1/runner/control/${encodeURIComponent(actionId)}/complete`, body);
|
|
365
514
|
if (res.status === 401) {
|
|
366
515
|
cachedFirebaseToken = null;
|
|
367
516
|
throw new Error("runner control completion unauthorized (401)");
|
|
@@ -372,7 +521,7 @@ function createControlPlaneClient({
|
|
|
372
521
|
},
|
|
373
522
|
/** Mint a GitHub App installation token — see installation-token.mjs. */
|
|
374
523
|
async getInstallationToken({ required = false, readOnly = false, repo = null } = {}) {
|
|
375
|
-
return fetchInstallationToken({ req, required, readOnly, repo });
|
|
524
|
+
return fetchInstallationToken({ req: taskReq, required, readOnly, repo });
|
|
376
525
|
},
|
|
377
526
|
/**
|
|
378
527
|
* Read the operator's dispatch-mode config (Fast→Ultracode effort setting).
|
|
@@ -381,7 +530,7 @@ function createControlPlaneClient({
|
|
|
381
530
|
*/
|
|
382
531
|
async getDispatchMode() {
|
|
383
532
|
try {
|
|
384
|
-
const res = await
|
|
533
|
+
const res = await taskReq("GET", "/api/v1/dispatch-mode-config");
|
|
385
534
|
if (!res.ok) return "standard";
|
|
386
535
|
const json = await res.json();
|
|
387
536
|
return json?.dispatchMode || "standard";
|
|
@@ -2217,8 +2366,9 @@ async function main() {
|
|
|
2217
2366
|
let child = null;
|
|
2218
2367
|
let stopping = false;
|
|
2219
2368
|
let handling = false;
|
|
2369
|
+
let degraded = false;
|
|
2220
2370
|
const respawn = () => {
|
|
2221
|
-
if (!stopping && !handling && (!child || child.exitCode !== null)) child = launchChild();
|
|
2371
|
+
if (!stopping && !degraded && !handling && (!child || child.exitCode !== null)) child = launchChild();
|
|
2222
2372
|
};
|
|
2223
2373
|
const launchChild = () => {
|
|
2224
2374
|
const next = spawnChild(childEnv);
|
|
@@ -2241,8 +2391,11 @@ async function main() {
|
|
|
2241
2391
|
launchPreviousChild: (entry) => spawnPreviousChild(entry, childEnv),
|
|
2242
2392
|
log: (message) => console.error(`[vo-runner supervisor] ${message}`)
|
|
2243
2393
|
})) {
|
|
2244
|
-
|
|
2245
|
-
|
|
2394
|
+
degraded = true;
|
|
2395
|
+
process.exitCode = 1;
|
|
2396
|
+
await stopChild(child);
|
|
2397
|
+
child = null;
|
|
2398
|
+
console.error("[vo-runner supervisor] activation FAILED \u2014 entering degraded mode: not serving tasks, still polling for remote control actions");
|
|
2246
2399
|
}
|
|
2247
2400
|
const shutdown = async () => {
|
|
2248
2401
|
if (stopping) return;
|
|
@@ -2308,6 +2461,7 @@ async function main() {
|
|
|
2308
2461
|
child = launchChild();
|
|
2309
2462
|
await sleep(CHILD_START_MS);
|
|
2310
2463
|
if (child.exitCode !== null || !await waitForLocalRunner(child)) result.ok = false;
|
|
2464
|
+
else degraded = false;
|
|
2311
2465
|
await client.completeRunnerControl(action.actionId, {
|
|
2312
2466
|
runnerId,
|
|
2313
2467
|
...operatorId ? { operatorId } : {},
|