@algosuite/vo-mcp 0.2.0-beta.48 → 0.2.0-beta.50
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 +2 -0
- package/dist/ci/check-local-pr-overlap.js +30 -7
- package/dist/cli.js +169 -18
- package/dist/cli.js.map +4 -4
- package/dist/index.js +46 -8
- package/dist/index.js.map +2 -2
- package/dist/runner-cli.js +458 -107
- package/dist/runner-cli.js.map +4 -4
- package/dist/runner-supervisor.js +55 -29
- package/dist/runner-supervisor.js.map +4 -4
- package/package.json +1 -1
package/dist/runner-cli.js
CHANGED
|
@@ -1138,28 +1138,28 @@ async function assertManagedRoot(rootPath, worktreeRoot, fsApi) {
|
|
|
1138
1138
|
throw new Error(`[vo-mcp runner] dependency cleanup refused outside the task root: ${rootPath}`);
|
|
1139
1139
|
}
|
|
1140
1140
|
if (!await pathExists(rootPath, fsApi)) return false;
|
|
1141
|
-
const
|
|
1142
|
-
if (
|
|
1141
|
+
const stat3 = await fsApi.lstat(rootPath);
|
|
1142
|
+
if (stat3.isSymbolicLink()) {
|
|
1143
1143
|
throw new Error(`[vo-mcp runner] dependency cleanup refused symbolic ownership root: ${rootPath}`);
|
|
1144
1144
|
}
|
|
1145
|
-
if (!
|
|
1145
|
+
if (!stat3.isDirectory()) {
|
|
1146
1146
|
throw new Error(`[vo-mcp runner] dependency cleanup refused non-directory ownership root: ${rootPath}`);
|
|
1147
1147
|
}
|
|
1148
1148
|
return true;
|
|
1149
1149
|
}
|
|
1150
|
-
async function removeReparsePoint(target,
|
|
1150
|
+
async function removeReparsePoint(target, stat3, fsApi) {
|
|
1151
1151
|
try {
|
|
1152
|
-
if (
|
|
1152
|
+
if (stat3.isDirectory()) {
|
|
1153
1153
|
await fsApi.rmdir(target);
|
|
1154
1154
|
return;
|
|
1155
1155
|
}
|
|
1156
1156
|
await fsApi.unlink(target);
|
|
1157
1157
|
} catch (error) {
|
|
1158
|
-
if (
|
|
1158
|
+
if (stat3.isDirectory() && ["ENOTDIR", "EPERM", "EISDIR", "EACCES"].includes(error?.code)) {
|
|
1159
1159
|
await fsApi.unlink(target);
|
|
1160
1160
|
return;
|
|
1161
1161
|
}
|
|
1162
|
-
if (!
|
|
1162
|
+
if (!stat3.isDirectory() && ["EPERM", "EISDIR", "EACCES"].includes(error?.code)) {
|
|
1163
1163
|
await fsApi.rmdir(target);
|
|
1164
1164
|
return;
|
|
1165
1165
|
}
|
|
@@ -1187,17 +1187,17 @@ async function walkManagedRoots(ownership, options, onLink) {
|
|
|
1187
1187
|
for (const entry of await fsApi.readdir(current, { withFileTypes: true })) {
|
|
1188
1188
|
if (entry.name === ".git") continue;
|
|
1189
1189
|
const child = path.join(current, entry.name);
|
|
1190
|
-
const
|
|
1190
|
+
const stat3 = await fsApi.lstat(child);
|
|
1191
1191
|
scannedEntries += 1;
|
|
1192
1192
|
if (scannedEntries > scanLimit) {
|
|
1193
1193
|
throw new Error(`[vo-mcp runner] dependency cleanup scan limit exceeded inside ${rootPath}`);
|
|
1194
1194
|
}
|
|
1195
1195
|
await maybeYield(yieldState);
|
|
1196
|
-
if (
|
|
1197
|
-
await onLink(child,
|
|
1196
|
+
if (stat3.isSymbolicLink()) {
|
|
1197
|
+
await onLink(child, stat3, normalized, fsApi);
|
|
1198
1198
|
continue;
|
|
1199
1199
|
}
|
|
1200
|
-
if (
|
|
1200
|
+
if (stat3.isDirectory()) {
|
|
1201
1201
|
stack.push(child);
|
|
1202
1202
|
}
|
|
1203
1203
|
}
|
|
@@ -1236,8 +1236,8 @@ function snapshotDependencyOwnership(ownership) {
|
|
|
1236
1236
|
}
|
|
1237
1237
|
async function detachDependencyLinks(ownership, options = {}) {
|
|
1238
1238
|
let removedLinks = 0;
|
|
1239
|
-
const result = await walkManagedRoots(ownership, options, async (target,
|
|
1240
|
-
await removeReparsePoint(target,
|
|
1239
|
+
const result = await walkManagedRoots(ownership, options, async (target, stat3, _normalized, fsApi) => {
|
|
1240
|
+
await removeReparsePoint(target, stat3, fsApi);
|
|
1241
1241
|
removedLinks += 1;
|
|
1242
1242
|
});
|
|
1243
1243
|
return { ...result, removedLinks };
|
|
@@ -1671,8 +1671,8 @@ async function inspectCanonicalNodeModulesHealth({
|
|
|
1671
1671
|
if (!await pathExists3(nodeModulesDir, fsApi)) {
|
|
1672
1672
|
issues.push(`missing root node_modules: ${nodeModulesDir}`);
|
|
1673
1673
|
} else {
|
|
1674
|
-
const
|
|
1675
|
-
if (
|
|
1674
|
+
const stat3 = await fsApi.lstat(nodeModulesDir);
|
|
1675
|
+
if (stat3.isSymbolicLink() || !stat3.isDirectory()) {
|
|
1676
1676
|
issues.push(`root node_modules must be a real directory: ${nodeModulesDir}`);
|
|
1677
1677
|
}
|
|
1678
1678
|
}
|
|
@@ -1695,8 +1695,8 @@ async function inspectCanonicalNodeModulesHealth({
|
|
|
1695
1695
|
issues.push(`missing workspace node_modules: ${workspaceNodeModules}`);
|
|
1696
1696
|
continue;
|
|
1697
1697
|
}
|
|
1698
|
-
const
|
|
1699
|
-
if (
|
|
1698
|
+
const stat3 = await fsApi.lstat(workspaceNodeModules);
|
|
1699
|
+
if (stat3.isSymbolicLink() || !stat3.isDirectory()) {
|
|
1700
1700
|
issues.push(`workspace node_modules must be a real directory: ${workspaceNodeModules}`);
|
|
1701
1701
|
}
|
|
1702
1702
|
}
|
|
@@ -1936,12 +1936,12 @@ async function copyDirRecursive(sourceDir, targetDir, fsApi, yieldState) {
|
|
|
1936
1936
|
}
|
|
1937
1937
|
async function materializeEntry(sourceEntry, targetEntry, canonicalRoot, worktreeRoot, options) {
|
|
1938
1938
|
await options.beforeEntry?.(sourceEntry, targetEntry);
|
|
1939
|
-
const
|
|
1940
|
-
if (
|
|
1939
|
+
const stat3 = await options.fsApi.lstat(sourceEntry);
|
|
1940
|
+
if (stat3.isDirectory() && !stat3.isSymbolicLink() && path4.basename(sourceEntry) === ".bin") {
|
|
1941
1941
|
await copyDirRecursive(sourceEntry, targetEntry, options.fsApi, options.yieldState);
|
|
1942
1942
|
return;
|
|
1943
1943
|
}
|
|
1944
|
-
if (
|
|
1944
|
+
if (stat3.isDirectory() && !stat3.isSymbolicLink() && path4.basename(sourceEntry).startsWith("@")) {
|
|
1945
1945
|
await options.fsApi.mkdir(targetEntry, { recursive: true });
|
|
1946
1946
|
for (const nested of await options.fsApi.readdir(sourceEntry, { withFileTypes: true })) {
|
|
1947
1947
|
await maybeYield2(options.yieldState);
|
|
@@ -1955,7 +1955,7 @@ async function materializeEntry(sourceEntry, targetEntry, canonicalRoot, worktre
|
|
|
1955
1955
|
}
|
|
1956
1956
|
return;
|
|
1957
1957
|
}
|
|
1958
|
-
if (
|
|
1958
|
+
if (stat3.isSymbolicLink() || stat3.isDirectory()) {
|
|
1959
1959
|
const resolvedTarget = await resolveEntryTarget(sourceEntry, canonicalRoot, worktreeRoot, options.fsApi);
|
|
1960
1960
|
await ensureLinkedDirectory(resolvedTarget, targetEntry, options.fsApi);
|
|
1961
1961
|
return;
|
|
@@ -2660,12 +2660,12 @@ async function recoverManagedCanonicalResidue(root, options = {}) {
|
|
|
2660
2660
|
const symlinks = [];
|
|
2661
2661
|
for (const relative of paths.untracked) {
|
|
2662
2662
|
const source = canonicalPath(root, relative);
|
|
2663
|
-
const
|
|
2664
|
-
if (
|
|
2663
|
+
const stat3 = await fsp7.lstat(source);
|
|
2664
|
+
if (stat3.isSymbolicLink()) {
|
|
2665
2665
|
symlinks.push({ path: relative, target: await fsp7.readlink(source) });
|
|
2666
2666
|
continue;
|
|
2667
2667
|
}
|
|
2668
|
-
if (!
|
|
2668
|
+
if (!stat3.isFile()) {
|
|
2669
2669
|
throw new Error(`canonical recovery refuses unsupported untracked entry: ${relative}`);
|
|
2670
2670
|
}
|
|
2671
2671
|
const target = canonicalPath(path8.join(quarantineDir, "untracked"), relative);
|
|
@@ -3482,6 +3482,60 @@ var init_control_plane_merge = __esm({
|
|
|
3482
3482
|
}
|
|
3483
3483
|
});
|
|
3484
3484
|
|
|
3485
|
+
// ../../scripts/virtual-office/code-runner/control-plane-weekly-tokens.mjs
|
|
3486
|
+
async function postWeeklyTokensRequest(taskReq, { operatorId, runnerId, tokens, claudeWeeklyPct, claudeWeeklyResetsAt }, onUnauthorized = () => {
|
|
3487
|
+
}) {
|
|
3488
|
+
const body = {
|
|
3489
|
+
operator_id: operatorId,
|
|
3490
|
+
runner_id: runnerId,
|
|
3491
|
+
input_tokens: tokens.input_tokens,
|
|
3492
|
+
output_tokens: tokens.output_tokens,
|
|
3493
|
+
cache_creation_tokens: tokens.cache_creation_tokens,
|
|
3494
|
+
cache_read_tokens: tokens.cache_read_tokens
|
|
3495
|
+
};
|
|
3496
|
+
if (typeof claudeWeeklyPct === "number") {
|
|
3497
|
+
body.claude_weekly_pct = claudeWeeklyPct;
|
|
3498
|
+
}
|
|
3499
|
+
if (claudeWeeklyResetsAt !== void 0) {
|
|
3500
|
+
body.claude_weekly_resets_at = claudeWeeklyResetsAt;
|
|
3501
|
+
}
|
|
3502
|
+
const res = await taskReq("POST", "/api/v1/weekly-tokens", body);
|
|
3503
|
+
if (res.status === 401) {
|
|
3504
|
+
onUnauthorized();
|
|
3505
|
+
throw new Error("weekly-tokens unauthorized (401)");
|
|
3506
|
+
}
|
|
3507
|
+
if (!res.ok) throw new Error(`weekly-tokens failed: HTTP ${res.status}`);
|
|
3508
|
+
return true;
|
|
3509
|
+
}
|
|
3510
|
+
var init_control_plane_weekly_tokens = __esm({
|
|
3511
|
+
"../../scripts/virtual-office/code-runner/control-plane-weekly-tokens.mjs"() {
|
|
3512
|
+
"use strict";
|
|
3513
|
+
}
|
|
3514
|
+
});
|
|
3515
|
+
|
|
3516
|
+
// ../../scripts/virtual-office/code-runner/control-plane-telemetry-relay.mjs
|
|
3517
|
+
async function relayTelemetryEventsRequest(req, { events, source }, onUnauthorized = () => {
|
|
3518
|
+
}) {
|
|
3519
|
+
const res = await req("POST", "/api/v1/telemetry/relay", { events, ...source ? { source } : {} }, {
|
|
3520
|
+
timeoutMs: TELEMETRY_RELAY_TIMEOUT_MS
|
|
3521
|
+
});
|
|
3522
|
+
if (res.status === 401) onUnauthorized();
|
|
3523
|
+
let body = null;
|
|
3524
|
+
try {
|
|
3525
|
+
body = await res.json();
|
|
3526
|
+
} catch {
|
|
3527
|
+
body = null;
|
|
3528
|
+
}
|
|
3529
|
+
return { status: res.status, body };
|
|
3530
|
+
}
|
|
3531
|
+
var TELEMETRY_RELAY_TIMEOUT_MS;
|
|
3532
|
+
var init_control_plane_telemetry_relay = __esm({
|
|
3533
|
+
"../../scripts/virtual-office/code-runner/control-plane-telemetry-relay.mjs"() {
|
|
3534
|
+
"use strict";
|
|
3535
|
+
TELEMETRY_RELAY_TIMEOUT_MS = 3e4;
|
|
3536
|
+
}
|
|
3537
|
+
});
|
|
3538
|
+
|
|
3485
3539
|
// ../../scripts/virtual-office/code-runner/claim-gate-notice.mjs
|
|
3486
3540
|
function describeClaimGate(gate) {
|
|
3487
3541
|
if (!gate || gate.allowed !== false) return null;
|
|
@@ -3739,38 +3793,21 @@ function createControlPlaneClient({
|
|
|
3739
3793
|
if (!res.ok) throw new Error(`knowledge-context failed: HTTP ${res.status}`);
|
|
3740
3794
|
return res.json();
|
|
3741
3795
|
},
|
|
3796
|
+
/** Weekly Claude token usage report — see control-plane-weekly-tokens.mjs. */
|
|
3797
|
+
async postWeeklyTokens(report) {
|
|
3798
|
+
return postWeeklyTokensRequest(taskReq, report, () => {
|
|
3799
|
+
cachedFirebaseToken = null;
|
|
3800
|
+
});
|
|
3801
|
+
},
|
|
3742
3802
|
/**
|
|
3743
|
-
*
|
|
3744
|
-
*
|
|
3745
|
-
*
|
|
3746
|
-
* is named explicitly. Best-effort; throws on a non-2xx so the caller can
|
|
3747
|
-
* log + move on.
|
|
3748
|
-
*
|
|
3749
|
-
* `tokens` = { input_tokens, output_tokens, cache_creation_tokens, cache_read_tokens }.
|
|
3750
|
-
* Optional: `claudeWeeklyPct` (number) + `claudeWeeklyResetsAt` (ISO string | null).
|
|
3803
|
+
* Relay a batch of this machine's local vo-mcp events to vo-telemetry via
|
|
3804
|
+
* the control plane (telemetry-forwarder.mjs). Returns { status, body };
|
|
3805
|
+
* the forwarder owns backoff/disable policy. See control-plane-telemetry-relay.mjs.
|
|
3751
3806
|
*/
|
|
3752
|
-
async
|
|
3753
|
-
|
|
3754
|
-
operator_id: operatorId,
|
|
3755
|
-
runner_id: runnerId2,
|
|
3756
|
-
input_tokens: tokens.input_tokens,
|
|
3757
|
-
output_tokens: tokens.output_tokens,
|
|
3758
|
-
cache_creation_tokens: tokens.cache_creation_tokens,
|
|
3759
|
-
cache_read_tokens: tokens.cache_read_tokens
|
|
3760
|
-
};
|
|
3761
|
-
if (typeof claudeWeeklyPct === "number") {
|
|
3762
|
-
body.claude_weekly_pct = claudeWeeklyPct;
|
|
3763
|
-
}
|
|
3764
|
-
if (claudeWeeklyResetsAt !== void 0) {
|
|
3765
|
-
body.claude_weekly_resets_at = claudeWeeklyResetsAt;
|
|
3766
|
-
}
|
|
3767
|
-
const res = await taskReq("POST", "/api/v1/weekly-tokens", body);
|
|
3768
|
-
if (res.status === 401) {
|
|
3807
|
+
async relayTelemetryEvents(batch) {
|
|
3808
|
+
return relayTelemetryEventsRequest(req, batch, () => {
|
|
3769
3809
|
cachedFirebaseToken = null;
|
|
3770
|
-
|
|
3771
|
-
}
|
|
3772
|
-
if (!res.ok) throw new Error(`weekly-tokens failed: HTTP ${res.status}`);
|
|
3773
|
-
return true;
|
|
3810
|
+
});
|
|
3774
3811
|
},
|
|
3775
3812
|
/**
|
|
3776
3813
|
* Send a liveness heartbeat (M2). The control-plane upserts it under the
|
|
@@ -3904,6 +3941,8 @@ var init_control_plane_client = __esm({
|
|
|
3904
3941
|
init_control_plane_resume();
|
|
3905
3942
|
init_control_plane_autonomous_admission();
|
|
3906
3943
|
init_control_plane_merge();
|
|
3944
|
+
init_control_plane_weekly_tokens();
|
|
3945
|
+
init_control_plane_telemetry_relay();
|
|
3907
3946
|
init_claim_gate_notice();
|
|
3908
3947
|
cachedFirebaseToken = null;
|
|
3909
3948
|
ClaimAuthorityChangedError = class extends Error {
|
|
@@ -6690,8 +6729,8 @@ async function acquireLock(lockFile, { now = Date.now, sleep: sleep3 = delay } =
|
|
|
6690
6729
|
stale = !alive || !Number.isFinite(created) || now() - created > LOCK_STALE_MS;
|
|
6691
6730
|
} catch {
|
|
6692
6731
|
try {
|
|
6693
|
-
const
|
|
6694
|
-
stale = now() -
|
|
6732
|
+
const stat3 = await fsp10.stat(lockFile);
|
|
6733
|
+
stale = now() - stat3.mtimeMs > LOCK_INIT_GRACE_MS;
|
|
6695
6734
|
} catch {
|
|
6696
6735
|
stale = false;
|
|
6697
6736
|
}
|
|
@@ -6946,6 +6985,7 @@ var init_auto_merge = __esm({
|
|
|
6946
6985
|
// ../../scripts/virtual-office/code-runner/pr-overlap-gate.mjs
|
|
6947
6986
|
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
6948
6987
|
import { existsSync as existsSync11 } from "node:fs";
|
|
6988
|
+
import { dirname as dirname6, join as join9 } from "node:path";
|
|
6949
6989
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
6950
6990
|
function stripCredentials(env2 = process.env) {
|
|
6951
6991
|
const safe = { ...env2 };
|
|
@@ -8044,7 +8084,7 @@ var init_publish_async = __esm({
|
|
|
8044
8084
|
|
|
8045
8085
|
// ../../scripts/virtual-office/code-runner/skill-catalog.mjs
|
|
8046
8086
|
import { readdirSync as readdirSync3, readFileSync as readFileSync8, statSync as statSync4 } from "node:fs";
|
|
8047
|
-
import { dirname as
|
|
8087
|
+
import { dirname as dirname7, join as join10 } from "node:path";
|
|
8048
8088
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
8049
8089
|
function parseFrontmatterNameDescription(raw) {
|
|
8050
8090
|
const text = String(raw).replace(/\r\n/g, "\n");
|
|
@@ -8064,15 +8104,15 @@ function parseFrontmatterNameDescription(raw) {
|
|
|
8064
8104
|
return name && description ? { name, description } : null;
|
|
8065
8105
|
}
|
|
8066
8106
|
function resolveDefaultRepoRoot() {
|
|
8067
|
-
const starts = [
|
|
8107
|
+
const starts = [dirname7(fileURLToPath4(import.meta.url)), process.cwd()];
|
|
8068
8108
|
for (const start of starts) {
|
|
8069
8109
|
let dir = start;
|
|
8070
8110
|
for (let i = 0; i < 8; i += 1) {
|
|
8071
8111
|
try {
|
|
8072
|
-
if (statSync4(
|
|
8112
|
+
if (statSync4(join10(dir, ".claude", "skills")).isDirectory()) return dir;
|
|
8073
8113
|
} catch {
|
|
8074
8114
|
}
|
|
8075
|
-
const parent =
|
|
8115
|
+
const parent = dirname7(dir);
|
|
8076
8116
|
if (parent === dir) break;
|
|
8077
8117
|
dir = parent;
|
|
8078
8118
|
}
|
|
@@ -8081,14 +8121,14 @@ function resolveDefaultRepoRoot() {
|
|
|
8081
8121
|
}
|
|
8082
8122
|
function loadSkillCatalog({ repoRoot: repoRoot2 = resolveDefaultRepoRoot() } = {}) {
|
|
8083
8123
|
try {
|
|
8084
|
-
const skillsDir =
|
|
8124
|
+
const skillsDir = join10(repoRoot2, ".claude", "skills");
|
|
8085
8125
|
const catalog = [];
|
|
8086
8126
|
for (const entry of readdirSync3(skillsDir)) {
|
|
8087
|
-
const dir =
|
|
8127
|
+
const dir = join10(skillsDir, entry);
|
|
8088
8128
|
try {
|
|
8089
8129
|
if (!statSync4(dir).isDirectory()) continue;
|
|
8090
8130
|
const parsed = parseFrontmatterNameDescription(
|
|
8091
|
-
readFileSync8(
|
|
8131
|
+
readFileSync8(join10(dir, "SKILL.md"), "utf8")
|
|
8092
8132
|
);
|
|
8093
8133
|
if (parsed) catalog.push(parsed);
|
|
8094
8134
|
} catch {
|
|
@@ -8589,7 +8629,7 @@ var init_task_attachments = __esm({
|
|
|
8589
8629
|
|
|
8590
8630
|
// ../../scripts/virtual-office/code-runner/session-spool-forwarder.mjs
|
|
8591
8631
|
import { homedir as homedir7 } from "node:os";
|
|
8592
|
-
import { join as
|
|
8632
|
+
import { join as join11 } from "node:path";
|
|
8593
8633
|
import { readdir as readdir2, readFile as readFile2, unlink, writeFile as writeFile2 } from "node:fs/promises";
|
|
8594
8634
|
import { createHash as createHash4 } from "node:crypto";
|
|
8595
8635
|
function deriveUuid(seed) {
|
|
@@ -8621,9 +8661,9 @@ async function readSpool(spoolDir = SPOOL_DIR) {
|
|
|
8621
8661
|
for (const f of files) {
|
|
8622
8662
|
if (!f.endsWith(".json")) continue;
|
|
8623
8663
|
try {
|
|
8624
|
-
const record = JSON.parse(await readFile2(
|
|
8664
|
+
const record = JSON.parse(await readFile2(join11(spoolDir, f), "utf8"));
|
|
8625
8665
|
if (record && typeof record.session_key === "string") {
|
|
8626
|
-
out.push({ full:
|
|
8666
|
+
out.push({ full: join11(spoolDir, f), record });
|
|
8627
8667
|
}
|
|
8628
8668
|
} catch {
|
|
8629
8669
|
}
|
|
@@ -8705,8 +8745,8 @@ var SPOOL_DIR, CLOUD_MAP_FILE, STALE_MS, ACTIVE_SILENCE_MS;
|
|
|
8705
8745
|
var init_session_spool_forwarder = __esm({
|
|
8706
8746
|
"../../scripts/virtual-office/code-runner/session-spool-forwarder.mjs"() {
|
|
8707
8747
|
"use strict";
|
|
8708
|
-
SPOOL_DIR =
|
|
8709
|
-
CLOUD_MAP_FILE =
|
|
8748
|
+
SPOOL_DIR = join11(homedir7(), ".vo", "session-spool");
|
|
8749
|
+
CLOUD_MAP_FILE = join11(homedir7(), ".vo", "session-cloud-map.json");
|
|
8710
8750
|
STALE_MS = 60 * 60 * 1e3;
|
|
8711
8751
|
ACTIVE_SILENCE_MS = 10 * 60 * 1e3;
|
|
8712
8752
|
}
|
|
@@ -8739,8 +8779,8 @@ function selectDueEntries({ entries = [], now, alreadyDispatched = /* @__PURE__
|
|
|
8739
8779
|
const entryAtMs = new Date(at).getTime();
|
|
8740
8780
|
if (Number.isFinite(entryAtMs)) {
|
|
8741
8781
|
const attemptCount = typeof attempts === "number" ? attempts : 0;
|
|
8742
|
-
const
|
|
8743
|
-
const dueAtMs = entryAtMs +
|
|
8782
|
+
const backoffMs2 = NULL_RESUME_AFTER_BACKOFF_MS * Math.pow(2, attemptCount);
|
|
8783
|
+
const dueAtMs = entryAtMs + backoffMs2;
|
|
8744
8784
|
isDue = nowMs >= dueAtMs;
|
|
8745
8785
|
}
|
|
8746
8786
|
} else {
|
|
@@ -8777,7 +8817,7 @@ var init_rate_limit_resume_scheduler_core = __esm({
|
|
|
8777
8817
|
});
|
|
8778
8818
|
|
|
8779
8819
|
// ../../scripts/virtual-office/code-runner/rate-limit-resume-scheduler.mjs
|
|
8780
|
-
import { dirname as
|
|
8820
|
+
import { dirname as dirname8, join as join12, resolve as resolve2 } from "node:path";
|
|
8781
8821
|
function defaultLog(message) {
|
|
8782
8822
|
console.log(`[rate-limit-scheduler ${(/* @__PURE__ */ new Date()).toISOString()}] ${message}`);
|
|
8783
8823
|
}
|
|
@@ -8859,7 +8899,7 @@ async function runLockedScheduler({
|
|
|
8859
8899
|
async function runScheduler({
|
|
8860
8900
|
env: env2 = process.env,
|
|
8861
8901
|
queuePath = resumeQueuePath(),
|
|
8862
|
-
attemptsPath =
|
|
8902
|
+
attemptsPath = join12(dirname8(queuePath), "resume-attempts.json"),
|
|
8863
8903
|
client,
|
|
8864
8904
|
now,
|
|
8865
8905
|
log: log2 = defaultLog
|
|
@@ -8904,6 +8944,303 @@ var init_rate_limit_resume_scheduler = __esm({
|
|
|
8904
8944
|
}
|
|
8905
8945
|
});
|
|
8906
8946
|
|
|
8947
|
+
// ../../scripts/virtual-office/code-runner/telemetry-forwarder.mjs
|
|
8948
|
+
import { homedir as homedir8 } from "node:os";
|
|
8949
|
+
import { dirname as dirname9, join as join13 } from "node:path";
|
|
8950
|
+
import { mkdir as mkdir2, open, readFile as readFile3, stat as stat2, writeFile as writeFile3 } from "node:fs/promises";
|
|
8951
|
+
function defaultEventsPath(env2 = process.env) {
|
|
8952
|
+
const p = String(env2.VO_MCP_EVENTS_PATH || "").trim();
|
|
8953
|
+
return p || join13(homedir8(), ".claude", "vo-mcp-events.jsonl");
|
|
8954
|
+
}
|
|
8955
|
+
function defaultStatePath(env2 = process.env) {
|
|
8956
|
+
const p = String(env2.VO_MCP_EVENTS_FORWARD_STATE || "").trim();
|
|
8957
|
+
return p || join13(homedir8(), ".claude", "vo-mcp-events-forward-state.json");
|
|
8958
|
+
}
|
|
8959
|
+
function splitCompleteLines(buf) {
|
|
8960
|
+
const lines = [];
|
|
8961
|
+
let start = 0;
|
|
8962
|
+
let consumedBytes = 0;
|
|
8963
|
+
for (; ; ) {
|
|
8964
|
+
const nl = buf.indexOf(10, start);
|
|
8965
|
+
if (nl === -1) break;
|
|
8966
|
+
lines.push({ text: buf.subarray(start, nl).toString("utf8"), bytes: nl - start + 1 });
|
|
8967
|
+
consumedBytes = nl + 1;
|
|
8968
|
+
start = nl + 1;
|
|
8969
|
+
}
|
|
8970
|
+
return { lines, consumedBytes };
|
|
8971
|
+
}
|
|
8972
|
+
function backoffMs(streak, baseMs) {
|
|
8973
|
+
if (streak <= 0) return 0;
|
|
8974
|
+
return Math.min(baseMs * 2 ** Math.min(streak - 1, 20), MAX_BACKOFF_MS);
|
|
8975
|
+
}
|
|
8976
|
+
async function loadState(path22) {
|
|
8977
|
+
try {
|
|
8978
|
+
const parsed = JSON.parse(await readFile3(path22, "utf8"));
|
|
8979
|
+
if (parsed && typeof parsed === "object" && Number.isInteger(parsed.byte_offset) && parsed.byte_offset >= 0) {
|
|
8980
|
+
return { ...parsed, byte_offset: parsed.byte_offset };
|
|
8981
|
+
}
|
|
8982
|
+
} catch {
|
|
8983
|
+
}
|
|
8984
|
+
return { byte_offset: 0, last_event_id: null, forwarded_total: 0, rejected_total: 0, rejected_event_ids: [] };
|
|
8985
|
+
}
|
|
8986
|
+
async function saveState(path22, state) {
|
|
8987
|
+
await mkdir2(dirname9(path22), { recursive: true });
|
|
8988
|
+
await writeFile3(path22, JSON.stringify(state, null, 2), "utf8");
|
|
8989
|
+
}
|
|
8990
|
+
async function readNewBytes(path22, offset, max) {
|
|
8991
|
+
const st = await stat2(path22);
|
|
8992
|
+
if (st.size <= offset) return { buf: Buffer.alloc(0), size: st.size };
|
|
8993
|
+
const length = Math.min(st.size - offset, max);
|
|
8994
|
+
const fh = await open(path22, "r");
|
|
8995
|
+
try {
|
|
8996
|
+
const buf = Buffer.alloc(length);
|
|
8997
|
+
const { bytesRead } = await fh.read(buf, 0, length, offset);
|
|
8998
|
+
return { buf: buf.subarray(0, bytesRead), size: st.size };
|
|
8999
|
+
} finally {
|
|
9000
|
+
await fh.close();
|
|
9001
|
+
}
|
|
9002
|
+
}
|
|
9003
|
+
function createTelemetryForwarder({
|
|
9004
|
+
client,
|
|
9005
|
+
cfg = {},
|
|
9006
|
+
env: env2 = process.env,
|
|
9007
|
+
log: log2 = () => {
|
|
9008
|
+
},
|
|
9009
|
+
runnerInstanceId = "",
|
|
9010
|
+
now = () => Date.now(),
|
|
9011
|
+
eventsPath = defaultEventsPath(env2),
|
|
9012
|
+
statePath: statePath2 = defaultStatePath(env2)
|
|
9013
|
+
} = {}) {
|
|
9014
|
+
const optedOut = env2.VO_MCP_EVENTS_FORWARD === "0";
|
|
9015
|
+
const capable = typeof client?.relayTelemetryEvents === "function";
|
|
9016
|
+
const intervalSec = Math.max(MIN_FORWARD_INTERVAL_SEC, Number(env2.VO_MCP_EVENTS_FORWARD_SEC) || DEFAULT_FORWARD_INTERVAL_SEC);
|
|
9017
|
+
const maxPerRun = Math.max(MAX_EVENTS_PER_BATCH, Number(env2.VO_MCP_EVENTS_FORWARD_MAX_PER_MIN) || DEFAULT_MAX_EVENTS_PER_MIN);
|
|
9018
|
+
const intervalMs = intervalSec * 1e3;
|
|
9019
|
+
const enabled = capable && !optedOut;
|
|
9020
|
+
const disabledReason = optedOut ? "VO_MCP_EVENTS_FORWARD=0" : capable ? "" : "client has no relayTelemetryEvents";
|
|
9021
|
+
let running = false;
|
|
9022
|
+
let nextRunAt = 0;
|
|
9023
|
+
let failStreak = 0;
|
|
9024
|
+
const servedOperators = Array.isArray(cfg.servedOperators) ? cfg.servedOperators.filter((v) => typeof v === "string" && v).slice(0, 100) : [];
|
|
9025
|
+
const source = {
|
|
9026
|
+
runner_id: cfg.runnerId || "unknown",
|
|
9027
|
+
...runnerInstanceId ? { runner_instance_id: runnerInstanceId } : {},
|
|
9028
|
+
...servedOperators.length > 0 ? { served_operator_ids: servedOperators } : {}
|
|
9029
|
+
};
|
|
9030
|
+
log2(enabled ? `telemetry forwarder ON \u2014 ${eventsPath} \u2192 control-plane relay every ${intervalSec}s (\u2264${maxPerRun}/run; VO_MCP_EVENTS_FORWARD=0 to disable)` : `telemetry forwarder OFF (${disabledReason})`);
|
|
9031
|
+
function pauseForbidden(reason) {
|
|
9032
|
+
nextRunAt = now() + FORBIDDEN_PAUSE_MS;
|
|
9033
|
+
log2(`telemetry forwarder PAUSED ${Math.round(FORBIDDEN_PAUSE_MS / 36e5)}h \u2014 relay refused this runner (${reason}); enroll the operator (VO_TELEMETRY_RELAY_OPERATOR_IDS / HQ_WHITEBOARD_OPERATOR_IDS) or set VO_MCP_EVENTS_FORWARD=0`);
|
|
9034
|
+
}
|
|
9035
|
+
function scheduleFailure(reasonMs) {
|
|
9036
|
+
failStreak += 1;
|
|
9037
|
+
const delay2 = Math.max(reasonMs || 0, backoffMs(failStreak, intervalMs));
|
|
9038
|
+
nextRunAt = now() + delay2;
|
|
9039
|
+
return delay2;
|
|
9040
|
+
}
|
|
9041
|
+
async function runOnce() {
|
|
9042
|
+
const receipt = { forwarded: 0, duplicate: 0, rejected: 0, malformed: 0, halted: null, batches: 0 };
|
|
9043
|
+
let state = await loadState(statePath2);
|
|
9044
|
+
let read;
|
|
9045
|
+
try {
|
|
9046
|
+
read = await readNewBytes(eventsPath, state.byte_offset, MAX_READ_BYTES_PER_RUN);
|
|
9047
|
+
} catch (err) {
|
|
9048
|
+
if (err && err.code === "ENOENT") {
|
|
9049
|
+
nextRunAt = now() + intervalMs;
|
|
9050
|
+
return { ...receipt, status: "no-file" };
|
|
9051
|
+
}
|
|
9052
|
+
log2(`telemetry forwarder read failed: ${err.message}`);
|
|
9053
|
+
scheduleFailure();
|
|
9054
|
+
return { ...receipt, status: "read-error" };
|
|
9055
|
+
}
|
|
9056
|
+
if (read.size < state.byte_offset) {
|
|
9057
|
+
log2(`telemetry forwarder: events file rotated/truncated (size ${read.size} < cursor ${state.byte_offset}); restarting cursor at 0`);
|
|
9058
|
+
state = { ...state, byte_offset: 0 };
|
|
9059
|
+
await saveState(statePath2, state);
|
|
9060
|
+
read = await readNewBytes(eventsPath, 0, MAX_READ_BYTES_PER_RUN).catch(() => ({ buf: Buffer.alloc(0), size: 0 }));
|
|
9061
|
+
}
|
|
9062
|
+
const { lines } = splitCompleteLines(read.buf);
|
|
9063
|
+
if (lines.length === 0) {
|
|
9064
|
+
nextRunAt = now() + intervalMs;
|
|
9065
|
+
return { ...receipt, status: "idle" };
|
|
9066
|
+
}
|
|
9067
|
+
const pending = [];
|
|
9068
|
+
let cursor = state.byte_offset;
|
|
9069
|
+
let leadingConsumed = 0;
|
|
9070
|
+
for (const line of lines) {
|
|
9071
|
+
if (pending.length >= maxPerRun) break;
|
|
9072
|
+
if (line.text.trim().length === 0) {
|
|
9073
|
+
if (pending.length === 0) leadingConsumed += line.bytes;
|
|
9074
|
+
else pending[pending.length - 1].bytes += line.bytes;
|
|
9075
|
+
continue;
|
|
9076
|
+
}
|
|
9077
|
+
try {
|
|
9078
|
+
pending.push({ event: JSON.parse(line.text), bytes: line.bytes });
|
|
9079
|
+
} catch {
|
|
9080
|
+
receipt.malformed += 1;
|
|
9081
|
+
if (pending.length === 0) leadingConsumed += line.bytes;
|
|
9082
|
+
else pending[pending.length - 1].bytes += line.bytes;
|
|
9083
|
+
}
|
|
9084
|
+
}
|
|
9085
|
+
cursor += leadingConsumed;
|
|
9086
|
+
if (pending.length === 0) {
|
|
9087
|
+
state = { ...state, byte_offset: cursor };
|
|
9088
|
+
await saveState(statePath2, state);
|
|
9089
|
+
nextRunAt = now() + intervalMs;
|
|
9090
|
+
return { ...receipt, status: "idle" };
|
|
9091
|
+
}
|
|
9092
|
+
let halted = false;
|
|
9093
|
+
let batchSize = MAX_EVENTS_PER_BATCH;
|
|
9094
|
+
for (let i = 0; i < pending.length && !halted; ) {
|
|
9095
|
+
const batch = pending.slice(i, i + batchSize);
|
|
9096
|
+
let res;
|
|
9097
|
+
try {
|
|
9098
|
+
res = await client.relayTelemetryEvents({ events: batch.map((p) => p.event), source });
|
|
9099
|
+
} catch (err) {
|
|
9100
|
+
receipt.halted = `network: ${err && err.message ? err.message : String(err)}`.slice(0, 200);
|
|
9101
|
+
halted = true;
|
|
9102
|
+
break;
|
|
9103
|
+
}
|
|
9104
|
+
receipt.batches += 1;
|
|
9105
|
+
const body = res && res.body && typeof res.body === "object" ? res.body : {};
|
|
9106
|
+
if (res.status === 413) {
|
|
9107
|
+
if (batch.length > 1) {
|
|
9108
|
+
batchSize = 1;
|
|
9109
|
+
continue;
|
|
9110
|
+
}
|
|
9111
|
+
const item = batch[0];
|
|
9112
|
+
cursor += item.bytes;
|
|
9113
|
+
i += 1;
|
|
9114
|
+
receipt.rejected += 1;
|
|
9115
|
+
const id = typeof item.event?.event_id === "string" ? item.event.event_id : `offset:${cursor - item.bytes}`;
|
|
9116
|
+
state = {
|
|
9117
|
+
...state,
|
|
9118
|
+
byte_offset: cursor,
|
|
9119
|
+
rejected_total: (state.rejected_total || 0) + 1,
|
|
9120
|
+
rejected_event_ids: [...state.rejected_event_ids || [], `${id}:payload_too_large`].slice(-REJECTED_IDS_KEEP)
|
|
9121
|
+
};
|
|
9122
|
+
await saveState(statePath2, state);
|
|
9123
|
+
continue;
|
|
9124
|
+
}
|
|
9125
|
+
i += batch.length;
|
|
9126
|
+
if (res.status === 403) {
|
|
9127
|
+
pauseForbidden(body.error || "HTTP 403");
|
|
9128
|
+
receipt.halted = "forbidden";
|
|
9129
|
+
halted = true;
|
|
9130
|
+
break;
|
|
9131
|
+
}
|
|
9132
|
+
if (res.status === 503 && (body.error === "ingest_disabled" || body.error === "relay_unconfigured")) {
|
|
9133
|
+
const retrySec = Number(body.retry_after_sec) > 0 ? Number(body.retry_after_sec) : 600;
|
|
9134
|
+
receipt.halted = body.error;
|
|
9135
|
+
halted = true;
|
|
9136
|
+
scheduleFailure(retrySec * 1e3);
|
|
9137
|
+
break;
|
|
9138
|
+
}
|
|
9139
|
+
if (res.status !== 200 || body.ok !== true || !Array.isArray(body.results)) {
|
|
9140
|
+
receipt.halted = `HTTP ${res.status}${body.error ? ` ${body.error}` : ""}`;
|
|
9141
|
+
halted = true;
|
|
9142
|
+
break;
|
|
9143
|
+
}
|
|
9144
|
+
let applied = 0;
|
|
9145
|
+
let batchForwarded = 0;
|
|
9146
|
+
let batchRejected = 0;
|
|
9147
|
+
for (const r of body.results) {
|
|
9148
|
+
const item = batch[r.index];
|
|
9149
|
+
if (!item || r.index !== applied) break;
|
|
9150
|
+
if (r.status === "upstream_error") {
|
|
9151
|
+
receipt.halted = `upstream_error${r.error ? ` ${r.error}` : ""}`;
|
|
9152
|
+
halted = true;
|
|
9153
|
+
break;
|
|
9154
|
+
}
|
|
9155
|
+
cursor += item.bytes;
|
|
9156
|
+
applied += 1;
|
|
9157
|
+
if (r.status === "written") {
|
|
9158
|
+
receipt.forwarded += 1;
|
|
9159
|
+
batchForwarded += 1;
|
|
9160
|
+
} else if (r.status === "duplicate") receipt.duplicate += 1;
|
|
9161
|
+
else if (r.status === "rejected") {
|
|
9162
|
+
receipt.rejected += 1;
|
|
9163
|
+
batchRejected += 1;
|
|
9164
|
+
const id = typeof item.event?.event_id === "string" ? item.event.event_id : `offset:${cursor - item.bytes}`;
|
|
9165
|
+
state.rejected_event_ids = [...state.rejected_event_ids || [], `${id}:${r.error || "rejected"}`].slice(-REJECTED_IDS_KEEP);
|
|
9166
|
+
}
|
|
9167
|
+
if (typeof r.event_id === "string") state.last_event_id = r.event_id;
|
|
9168
|
+
}
|
|
9169
|
+
if (body.complete !== true && !halted) {
|
|
9170
|
+
receipt.halted = "incomplete-batch";
|
|
9171
|
+
halted = true;
|
|
9172
|
+
}
|
|
9173
|
+
if (applied < batch.length && !halted) {
|
|
9174
|
+
receipt.halted = "short-results";
|
|
9175
|
+
halted = true;
|
|
9176
|
+
}
|
|
9177
|
+
state = {
|
|
9178
|
+
...state,
|
|
9179
|
+
byte_offset: cursor,
|
|
9180
|
+
forwarded_total: (state.forwarded_total || 0) + batchForwarded,
|
|
9181
|
+
rejected_total: (state.rejected_total || 0) + batchRejected,
|
|
9182
|
+
last_forward_at: new Date(now()).toISOString(),
|
|
9183
|
+
last_error: receipt.halted
|
|
9184
|
+
};
|
|
9185
|
+
await saveState(statePath2, state);
|
|
9186
|
+
}
|
|
9187
|
+
if (halted) {
|
|
9188
|
+
state = { ...state, byte_offset: cursor, last_error: receipt.halted, last_halt_at: new Date(now()).toISOString() };
|
|
9189
|
+
await saveState(statePath2, state).catch(() => {
|
|
9190
|
+
});
|
|
9191
|
+
}
|
|
9192
|
+
const totals = { forwarded: state.forwarded_total || 0, rejected: state.rejected_total || 0 };
|
|
9193
|
+
if (halted && receipt.halted !== "forbidden" && receipt.halted !== "ingest_disabled" && receipt.halted !== "relay_unconfigured") {
|
|
9194
|
+
const delay2 = scheduleFailure();
|
|
9195
|
+
log2(`telemetry forwarder: halted (${receipt.halted}); cursor=${cursor}B; retry in ${Math.round(delay2 / 1e3)}s`);
|
|
9196
|
+
} else if (!halted) {
|
|
9197
|
+
failStreak = 0;
|
|
9198
|
+
nextRunAt = now() + intervalMs;
|
|
9199
|
+
log2(`telemetry forwarder: relayed ${receipt.batches} batch(es); cursor=${cursor}B; totals forwarded=${totals.forwarded} rejected=${totals.rejected}${receipt.malformed ? ` malformed=${receipt.malformed}` : ""}`);
|
|
9200
|
+
} else {
|
|
9201
|
+
log2(`telemetry forwarder: paused (${receipt.halted}); cursor=${cursor}B`);
|
|
9202
|
+
}
|
|
9203
|
+
return { ...receipt, status: halted ? "halted" : "ok", cursor, totals };
|
|
9204
|
+
}
|
|
9205
|
+
return {
|
|
9206
|
+
get enabled() {
|
|
9207
|
+
return enabled;
|
|
9208
|
+
},
|
|
9209
|
+
get disabledReason() {
|
|
9210
|
+
return disabledReason;
|
|
9211
|
+
},
|
|
9212
|
+
intervalMs,
|
|
9213
|
+
/** Fire-and-forget; safe to call every daemon loop iteration. */
|
|
9214
|
+
tick() {
|
|
9215
|
+
if (!enabled || running) return void 0;
|
|
9216
|
+
const t = now();
|
|
9217
|
+
if (t < nextRunAt) return void 0;
|
|
9218
|
+
running = true;
|
|
9219
|
+
return runOnce().catch((err) => {
|
|
9220
|
+
log2(`telemetry forwarder tick failed: ${err && err.message ? err.message : String(err)}`);
|
|
9221
|
+
scheduleFailure();
|
|
9222
|
+
}).finally(() => {
|
|
9223
|
+
running = false;
|
|
9224
|
+
});
|
|
9225
|
+
},
|
|
9226
|
+
runOnce
|
|
9227
|
+
};
|
|
9228
|
+
}
|
|
9229
|
+
var DEFAULT_FORWARD_INTERVAL_SEC, MIN_FORWARD_INTERVAL_SEC, DEFAULT_MAX_EVENTS_PER_MIN, MAX_EVENTS_PER_BATCH, MAX_READ_BYTES_PER_RUN, MAX_BACKOFF_MS, REJECTED_IDS_KEEP, FORBIDDEN_PAUSE_MS;
|
|
9230
|
+
var init_telemetry_forwarder = __esm({
|
|
9231
|
+
"../../scripts/virtual-office/code-runner/telemetry-forwarder.mjs"() {
|
|
9232
|
+
"use strict";
|
|
9233
|
+
DEFAULT_FORWARD_INTERVAL_SEC = 60;
|
|
9234
|
+
MIN_FORWARD_INTERVAL_SEC = 15;
|
|
9235
|
+
DEFAULT_MAX_EVENTS_PER_MIN = 500;
|
|
9236
|
+
MAX_EVENTS_PER_BATCH = 25;
|
|
9237
|
+
MAX_READ_BYTES_PER_RUN = 4 * 1024 * 1024;
|
|
9238
|
+
MAX_BACKOFF_MS = 30 * 60 * 1e3;
|
|
9239
|
+
REJECTED_IDS_KEEP = 100;
|
|
9240
|
+
FORBIDDEN_PAUSE_MS = 6 * 60 * 60 * 1e3;
|
|
9241
|
+
}
|
|
9242
|
+
});
|
|
9243
|
+
|
|
8907
9244
|
// ../../scripts/virtual-office/code-runner/loop-ticks.mjs
|
|
8908
9245
|
function makeLoopTicks({
|
|
8909
9246
|
client,
|
|
@@ -8933,7 +9270,11 @@ function makeLoopTicks({
|
|
|
8933
9270
|
getAccountUsage = () => [],
|
|
8934
9271
|
// Injectable for tests; default to the real scheduler + wall clock.
|
|
8935
9272
|
runResumeScheduler = runScheduler,
|
|
8936
|
-
now: nowFn = () => Date.now()
|
|
9273
|
+
now: nowFn = () => Date.now(),
|
|
9274
|
+
// Cursor-lessons §6 (2026-08-16): forwards this machine's vo-mcp events to
|
|
9275
|
+
// the control-plane telemetry relay (telemetry-forwarder.mjs). Inert when the
|
|
9276
|
+
// client lacks relayTelemetryEvents (test fakes) or VO_MCP_EVENTS_FORWARD=0.
|
|
9277
|
+
telemetryForwarder = createTelemetryForwarder({ client, cfg, env: env2, log: log2, runnerInstanceId, now: nowFn })
|
|
8937
9278
|
}) {
|
|
8938
9279
|
let lastSessionForward = 0;
|
|
8939
9280
|
let lastHeartbeat = 0;
|
|
@@ -9045,6 +9386,7 @@ function makeLoopTicks({
|
|
|
9045
9386
|
resumeRunning = false;
|
|
9046
9387
|
});
|
|
9047
9388
|
}
|
|
9389
|
+
telemetryForwarder.tick();
|
|
9048
9390
|
return Promise.all(heartbeatCompletions).then(() => void 0);
|
|
9049
9391
|
};
|
|
9050
9392
|
}
|
|
@@ -9054,6 +9396,7 @@ var init_loop_ticks = __esm({
|
|
|
9054
9396
|
"use strict";
|
|
9055
9397
|
init_session_spool_forwarder();
|
|
9056
9398
|
init_rate_limit_resume_scheduler();
|
|
9399
|
+
init_telemetry_forwarder();
|
|
9057
9400
|
HEARTBEAT_MS = 6e4;
|
|
9058
9401
|
DEFAULT_RESUME_SCHEDULE_SEC = 300;
|
|
9059
9402
|
}
|
|
@@ -10050,7 +10393,7 @@ async function scheduleCoordinationRetry({
|
|
|
10050
10393
|
const attemptsKey = kind === "resume" ? "resumeAttempts" : "fixAttempts";
|
|
10051
10394
|
entry[errorsKey] = (entry[errorsKey] || 0) + 1;
|
|
10052
10395
|
entry[attemptsKey] = Math.max(0, (entry[attemptsKey] || 1) - 1);
|
|
10053
|
-
const delay2 = Math.min(
|
|
10396
|
+
const delay2 = Math.min(MAX_BACKOFF_MS2, 3e4 * 2 ** Math.min(7, entry[errorsKey] - 1));
|
|
10054
10397
|
entry.nextRetryAt = now() + delay2;
|
|
10055
10398
|
if (entry[errorsKey] >= 3 && !entry.coordinationEscalatedAt && typeof reportBlocker === "function") {
|
|
10056
10399
|
try {
|
|
@@ -10065,12 +10408,12 @@ async function scheduleCoordinationRetry({
|
|
|
10065
10408
|
}
|
|
10066
10409
|
log2(`watch: pr #${prNumber} ${kind} coordination failed ${entry[errorsKey]}x; retry in ${Math.round(delay2 / 1e3)}s: ${boundedErrorMessage(error)}`);
|
|
10067
10410
|
}
|
|
10068
|
-
var
|
|
10411
|
+
var MAX_BACKOFF_MS2, MERGE_ENQUEUE_TTL_MS, TERMINAL_RESUME_REFUSALS;
|
|
10069
10412
|
var init_watcher_coordination = __esm({
|
|
10070
10413
|
"../../scripts/virtual-office/code-runner/watcher-coordination.mjs"() {
|
|
10071
10414
|
"use strict";
|
|
10072
10415
|
init_error_message();
|
|
10073
|
-
|
|
10416
|
+
MAX_BACKOFF_MS2 = 60 * 60 * 1e3;
|
|
10074
10417
|
MERGE_ENQUEUE_TTL_MS = 2 * 60 * 60 * 1e3;
|
|
10075
10418
|
TERMINAL_RESUME_REFUSALS = Object.freeze([
|
|
10076
10419
|
"automatic_continuation_budget_too_small",
|
|
@@ -10088,12 +10431,12 @@ var init_watcher_coordination = __esm({
|
|
|
10088
10431
|
|
|
10089
10432
|
// ../../scripts/virtual-office/code-runner/watcher-state.mjs
|
|
10090
10433
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
10091
|
-
import { mkdir as
|
|
10092
|
-
import { dirname as
|
|
10434
|
+
import { mkdir as mkdir3, open as open2, readFile as readFile4, rename, unlink as unlink2 } from "node:fs/promises";
|
|
10435
|
+
import { dirname as dirname10 } from "node:path";
|
|
10093
10436
|
async function readWatcherState(stateFile) {
|
|
10094
10437
|
let raw;
|
|
10095
10438
|
try {
|
|
10096
|
-
raw = await
|
|
10439
|
+
raw = await readFile4(stateFile, "utf8");
|
|
10097
10440
|
} catch (error) {
|
|
10098
10441
|
if (error?.code === "ENOENT") return {};
|
|
10099
10442
|
throw error;
|
|
@@ -10105,12 +10448,12 @@ async function readWatcherState(stateFile) {
|
|
|
10105
10448
|
return parsed;
|
|
10106
10449
|
}
|
|
10107
10450
|
async function writeWatcherState(stateFile, state) {
|
|
10108
|
-
const directory =
|
|
10109
|
-
await
|
|
10451
|
+
const directory = dirname10(stateFile);
|
|
10452
|
+
await mkdir3(directory, { recursive: true });
|
|
10110
10453
|
const temp = `${stateFile}.${process.pid}.${randomUUID3()}.tmp`;
|
|
10111
10454
|
let handle;
|
|
10112
10455
|
try {
|
|
10113
|
-
handle = await
|
|
10456
|
+
handle = await open2(temp, "wx");
|
|
10114
10457
|
await handle.writeFile(JSON.stringify(state, null, 2), "utf8");
|
|
10115
10458
|
await handle.sync();
|
|
10116
10459
|
await handle.close();
|
|
@@ -10434,8 +10777,8 @@ var init_enqueue_autonomous_code_task = __esm({
|
|
|
10434
10777
|
});
|
|
10435
10778
|
|
|
10436
10779
|
// ../../scripts/virtual-office/code-runner/pr-watcher.mjs
|
|
10437
|
-
import { homedir as
|
|
10438
|
-
import { join as
|
|
10780
|
+
import { homedir as homedir9 } from "node:os";
|
|
10781
|
+
import { join as join14 } from "node:path";
|
|
10439
10782
|
function parsePrCiStatus(view) {
|
|
10440
10783
|
const state = (view && typeof view.state === "string" ? view.state : "UNKNOWN").toUpperCase();
|
|
10441
10784
|
const rollup = view && Array.isArray(view.statusCheckRollup) ? view.statusCheckRollup : [];
|
|
@@ -10781,7 +11124,7 @@ var init_pr_watcher = __esm({
|
|
|
10781
11124
|
init_watcher_state();
|
|
10782
11125
|
init_superseded_pr_source();
|
|
10783
11126
|
init_ci_fix_prompt();
|
|
10784
|
-
DEFAULT_STATE_FILE =
|
|
11127
|
+
DEFAULT_STATE_FILE = join14(homedir9(), ".vo", "dispatched-prs.json");
|
|
10785
11128
|
FAIL_CONCLUSIONS = /* @__PURE__ */ new Set([
|
|
10786
11129
|
"FAILURE",
|
|
10787
11130
|
"TIMED_OUT",
|
|
@@ -12025,8 +12368,8 @@ var init_classify_task = __esm({
|
|
|
12025
12368
|
|
|
12026
12369
|
// ../../scripts/virtual-office/code-runner/auto-router/effort-policy.mjs
|
|
12027
12370
|
import { readFileSync as readFileSync9 } from "node:fs";
|
|
12028
|
-
import { homedir as
|
|
12029
|
-
import { join as
|
|
12371
|
+
import { homedir as homedir10 } from "node:os";
|
|
12372
|
+
import { join as join15 } from "node:path";
|
|
12030
12373
|
function difficultyToRung(difficulty, thresholds) {
|
|
12031
12374
|
const b = thresholds.rungBounds;
|
|
12032
12375
|
if (difficulty >= b.R5) return "R5";
|
|
@@ -12103,7 +12446,7 @@ var init_effort_policy = __esm({
|
|
|
12103
12446
|
init_meta_model_catalog();
|
|
12104
12447
|
RUNG_ORDER = ["R1", "R2", "R3", "R4", "R5"];
|
|
12105
12448
|
rungIndex = (rung) => RUNG_ORDER.indexOf(rung);
|
|
12106
|
-
DEFAULT_CODEX_MODELS_CACHE =
|
|
12449
|
+
DEFAULT_CODEX_MODELS_CACHE = join15(homedir10(), ".codex", "models_cache.json");
|
|
12107
12450
|
}
|
|
12108
12451
|
});
|
|
12109
12452
|
|
|
@@ -12234,8 +12577,8 @@ var init_role_cost_shadow = __esm({
|
|
|
12234
12577
|
|
|
12235
12578
|
// ../../scripts/virtual-office/code-runner/auto-router/auto-router.mjs
|
|
12236
12579
|
import { readFileSync as readFileSync10, appendFileSync, mkdirSync as mkdirSync8 } from "node:fs";
|
|
12237
|
-
import { homedir as
|
|
12238
|
-
import { join as
|
|
12580
|
+
import { homedir as homedir11 } from "node:os";
|
|
12581
|
+
import { join as join16, dirname as dirname11 } from "node:path";
|
|
12239
12582
|
import { fileURLToPath as fileURLToPath7 } from "node:url";
|
|
12240
12583
|
function getAutoRouterMode(env2 = process.env) {
|
|
12241
12584
|
const raw = String(env2.VO_CODE_RUNNER_AUTO_ROUTER || "").trim().toLowerCase();
|
|
@@ -12243,8 +12586,8 @@ function getAutoRouterMode(env2 = process.env) {
|
|
|
12243
12586
|
}
|
|
12244
12587
|
function loadThresholds() {
|
|
12245
12588
|
if (!cachedThresholds) {
|
|
12246
|
-
const here =
|
|
12247
|
-
cachedThresholds = JSON.parse(readFileSync10(
|
|
12589
|
+
const here = dirname11(fileURLToPath7(import.meta.url));
|
|
12590
|
+
cachedThresholds = JSON.parse(readFileSync10(join16(here, "thresholds.json"), "utf8"));
|
|
12248
12591
|
}
|
|
12249
12592
|
return cachedThresholds;
|
|
12250
12593
|
}
|
|
@@ -12310,9 +12653,9 @@ function formatDecisionReason(decision, maxLen = 480) {
|
|
|
12310
12653
|
const s = `[${decision.routerVersion}] ${decision.taskClass} d=${decision.difficulty} c=${decision.confidence} \u2192 ${decision.rung}/${decision.tier}${decision.effort ? ` effort=${decision.effort}` : ""} turns=${decision.maxTurns} $${decision.maxBudgetUsd}${decision.flags.length ? ` [${decision.flags.join(",")}]` : ""} :: ${decision.reasons.join("; ")}`;
|
|
12311
12654
|
return s.length > maxLen ? `${s.slice(0, maxLen - 1)}\u2026` : s;
|
|
12312
12655
|
}
|
|
12313
|
-
function appendDecisionFallback(decision, { path: path22 = DECISION_FALLBACK_PATH, append = appendFileSync, mkdir:
|
|
12656
|
+
function appendDecisionFallback(decision, { path: path22 = DECISION_FALLBACK_PATH, append = appendFileSync, mkdir: mkdir5 = mkdirSync8, task, thresholds, roleCostInputs } = {}) {
|
|
12314
12657
|
try {
|
|
12315
|
-
|
|
12658
|
+
mkdir5(dirname11(path22), { recursive: true });
|
|
12316
12659
|
append(path22, `${JSON.stringify(decision)}
|
|
12317
12660
|
`, "utf8");
|
|
12318
12661
|
if (isRouterDecision(decision)) {
|
|
@@ -12336,7 +12679,7 @@ var init_auto_router = __esm({
|
|
|
12336
12679
|
init_effort_policy();
|
|
12337
12680
|
init_role_cost_shadow();
|
|
12338
12681
|
ROUTER_VERSION = "0.1.0";
|
|
12339
|
-
DECISION_FALLBACK_PATH =
|
|
12682
|
+
DECISION_FALLBACK_PATH = join16(homedir11(), ".claude", "vo-auto-router-decisions.jsonl");
|
|
12340
12683
|
MODES = /* @__PURE__ */ new Set(["off", "shadow", "on"]);
|
|
12341
12684
|
cachedThresholds = null;
|
|
12342
12685
|
isRouterDecision = (d) => Boolean(d && typeof d === "object" && typeof d.taskClass === "string" && typeof d.confidence === "number");
|
|
@@ -12736,7 +13079,7 @@ function safeBaseEnv(env2 = {}) {
|
|
|
12736
13079
|
}
|
|
12737
13080
|
return result;
|
|
12738
13081
|
}
|
|
12739
|
-
function buildAgentProcessEnv(env2, { agent = "agent", runnerId = "vo-runner", taskId = "task", githubReadToken = null, swarmAdmission = null } = {}) {
|
|
13082
|
+
function buildAgentProcessEnv(env2, { agent = "agent", runnerId = "vo-runner", taskId = "task", repo = null, githubReadToken = null, swarmAdmission = null } = {}) {
|
|
12740
13083
|
const base = safeBaseEnv(env2);
|
|
12741
13084
|
if (swarmAdmission) {
|
|
12742
13085
|
for (const key of Object.keys(base)) {
|
|
@@ -12748,6 +13091,8 @@ function buildAgentProcessEnv(env2, { agent = "agent", runnerId = "vo-runner", t
|
|
|
12748
13091
|
base.GH_TOKEN = githubReadToken;
|
|
12749
13092
|
base.GITHUB_TOKEN = githubReadToken;
|
|
12750
13093
|
}
|
|
13094
|
+
if (typeof taskId === "string" && taskId && taskId !== "task") base.VO_CODE_TASK_ID = taskId;
|
|
13095
|
+
if (typeof repo === "string" && /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/u.test(repo)) base.VO_CODE_TASK_REPO = repo;
|
|
12751
13096
|
if (String(env2?.AGENT_ID || "").trim()) return { ...base, AGENT_ID: env2.AGENT_ID };
|
|
12752
13097
|
const generated = [
|
|
12753
13098
|
"vo",
|
|
@@ -12817,6 +13162,12 @@ var init_agent_process_env = __esm({
|
|
|
12817
13162
|
// NOT a credential and NOT an authorization input: it names a tier, it never
|
|
12818
13163
|
// grants one, and it carries no key material (see the module's header rule).
|
|
12819
13164
|
"VO_SWARM_TIER_BINDING",
|
|
13165
|
+
// Increased-moat staging flags (2026-08-16): a fleet machine's runner env
|
|
13166
|
+
// decides whether its agents' vo-mcp ALSO sends verdicts through the moat
|
|
13167
|
+
// (shadow) or lets the moat win (authoritative). Per-machine, default absent.
|
|
13168
|
+
"VO_CONSENSUS_MOAT_SHADOW",
|
|
13169
|
+
"VO_CONSENSUS_MOAT_SHADOW_PCT",
|
|
13170
|
+
"VO_CONSENSUS_MOAT_AUTHORITATIVE",
|
|
12820
13171
|
// Where the swarm SPAWN LEDGER lives (SWARM_LEDGER_DIR_ENV in
|
|
12821
13172
|
// packages/vo-mcp/src/swarm/spawn-ledger.ts). The ledger is the durable,
|
|
12822
13173
|
// host-shared counter that bounds the TOTAL spawns under one swarm_id; the
|
|
@@ -14052,9 +14403,9 @@ function recoveryLedgerCandidates(repo, clonesRoot2) {
|
|
|
14052
14403
|
path21.join(canonical, ".agent-worktrees", "recovery-ledger.jsonl")
|
|
14053
14404
|
];
|
|
14054
14405
|
}
|
|
14055
|
-
async function readLedger(file,
|
|
14406
|
+
async function readLedger(file, readFile6) {
|
|
14056
14407
|
try {
|
|
14057
|
-
return String(await
|
|
14408
|
+
return String(await readFile6(file, "utf8")).split(/\r?\n/).filter(Boolean).flatMap((line) => {
|
|
14058
14409
|
try {
|
|
14059
14410
|
return [JSON.parse(line)];
|
|
14060
14411
|
} catch {
|
|
@@ -14067,7 +14418,7 @@ async function readLedger(file, readFile5) {
|
|
|
14067
14418
|
}
|
|
14068
14419
|
async function findPreservedRecovery(task, {
|
|
14069
14420
|
clonesRoot: clonesRoot2 = process.env.VO_CODE_RUNNER_CLONES_ROOT || "",
|
|
14070
|
-
readFile:
|
|
14421
|
+
readFile: readFile6 = fsp13.readFile,
|
|
14071
14422
|
exists = fs13.existsSync,
|
|
14072
14423
|
alreadyPublished = preservedHeadAlreadyOnBranch,
|
|
14073
14424
|
log: log2 = () => {
|
|
@@ -14078,7 +14429,7 @@ async function findPreservedRecovery(task, {
|
|
|
14078
14429
|
if (!originalTaskId) return null;
|
|
14079
14430
|
if (task.pr_branch && !recoveryTaskId(task.prompt)) return null;
|
|
14080
14431
|
for (const ledgerPath of recoveryLedgerCandidates(task.repo, clonesRoot2)) {
|
|
14081
|
-
const entries = await readLedger(ledgerPath,
|
|
14432
|
+
const entries = await readLedger(ledgerPath, readFile6);
|
|
14082
14433
|
const resolved = entries.some((entry) => RESOLVED_RECOVERY_TYPES.has(entry.type) && entry.taskId === originalTaskId);
|
|
14083
14434
|
const preserved = [...entries].reverse().find((entry) => entry.taskId === originalTaskId && entry.worktreeDir);
|
|
14084
14435
|
if (!resolved && preserved && exists(preserved.worktreeDir)) {
|
|
@@ -14419,9 +14770,9 @@ var init_cancellation_probe = __esm({
|
|
|
14419
14770
|
});
|
|
14420
14771
|
|
|
14421
14772
|
// ../../scripts/virtual-office/code-runner/detached-economics-spool.mjs
|
|
14422
|
-
import { homedir as
|
|
14423
|
-
import { dirname as
|
|
14424
|
-
import { mkdir as
|
|
14773
|
+
import { homedir as homedir12 } from "node:os";
|
|
14774
|
+
import { dirname as dirname12, join as join17 } from "node:path";
|
|
14775
|
+
import { mkdir as mkdir4, readFile as readFile5, rename as rename2, writeFile as writeFile4 } from "node:fs/promises";
|
|
14425
14776
|
function withLock(operation) {
|
|
14426
14777
|
const result = serialized.then(operation, operation);
|
|
14427
14778
|
serialized = result.then(() => void 0, () => void 0);
|
|
@@ -14429,7 +14780,7 @@ function withLock(operation) {
|
|
|
14429
14780
|
}
|
|
14430
14781
|
async function readEntries(file) {
|
|
14431
14782
|
try {
|
|
14432
|
-
const parsed = JSON.parse(await
|
|
14783
|
+
const parsed = JSON.parse(await readFile5(file, "utf8"));
|
|
14433
14784
|
if (!Array.isArray(parsed)) throw new Error("detached economics spool is not an array");
|
|
14434
14785
|
return parsed;
|
|
14435
14786
|
} catch (error) {
|
|
@@ -14438,9 +14789,9 @@ async function readEntries(file) {
|
|
|
14438
14789
|
}
|
|
14439
14790
|
}
|
|
14440
14791
|
async function writeEntries(file, entries) {
|
|
14441
|
-
await
|
|
14792
|
+
await mkdir4(dirname12(file), { recursive: true });
|
|
14442
14793
|
const temp = `${file}.${process.pid}.tmp`;
|
|
14443
|
-
await
|
|
14794
|
+
await writeFile4(temp, `${JSON.stringify(entries)}
|
|
14444
14795
|
`, "utf8");
|
|
14445
14796
|
await rename2(temp, file);
|
|
14446
14797
|
}
|
|
@@ -14484,7 +14835,7 @@ var DEFAULT_FILE, serialized;
|
|
|
14484
14835
|
var init_detached_economics_spool = __esm({
|
|
14485
14836
|
"../../scripts/virtual-office/code-runner/detached-economics-spool.mjs"() {
|
|
14486
14837
|
"use strict";
|
|
14487
|
-
DEFAULT_FILE =
|
|
14838
|
+
DEFAULT_FILE = join17(homedir12(), ".vo", "detached-run-economics.json");
|
|
14488
14839
|
serialized = Promise.resolve();
|
|
14489
14840
|
}
|
|
14490
14841
|
});
|
|
@@ -14885,7 +15236,7 @@ async function processOneTask(client, task, cfg, runnerInstanceId, swarmAdmissio
|
|
|
14885
15236
|
maxBudgetUsd: sel.agent === "claude" ? effectiveMaxBudgetUsd : void 0,
|
|
14886
15237
|
researchHarness: methodology?.shape === "research",
|
|
14887
15238
|
// Workflow grant only for research-shaped tasks
|
|
14888
|
-
env: buildAgentProcessEnv(process.env, { agent: sel.agent, runnerId: cfg.runnerId, taskId: id, githubReadToken: agentGithubReadToken, swarmAdmission }),
|
|
15239
|
+
env: buildAgentProcessEnv(process.env, { agent: sel.agent, runnerId: cfg.runnerId, taskId: id, repo: task.repo, githubReadToken: agentGithubReadToken, swarmAdmission }),
|
|
14889
15240
|
// swarmAdmission mints VO_SWARM_TIER_BINDING: ONE tier decision for this task's whole agent tree
|
|
14890
15241
|
sandbox,
|
|
14891
15242
|
onProgress: (text, checkpoint) => {
|