@evident-ai/cli 3.5.2-dev.8c3e0f7 → 3.5.2-dev.91f886c
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/index.js +893 -190
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -315,14 +315,14 @@ function blank() {
|
|
|
315
315
|
console.log();
|
|
316
316
|
}
|
|
317
317
|
function waitForEnter(prompt = "Press Enter to continue...") {
|
|
318
|
-
return new Promise((
|
|
318
|
+
return new Promise((resolve5) => {
|
|
319
319
|
process.stdout.write(chalk.dim(prompt));
|
|
320
320
|
const handler = () => {
|
|
321
321
|
process.stdin.removeListener("data", handler);
|
|
322
322
|
process.stdin.setRawMode?.(false);
|
|
323
323
|
process.stdin.pause();
|
|
324
324
|
console.log();
|
|
325
|
-
|
|
325
|
+
resolve5();
|
|
326
326
|
};
|
|
327
327
|
if (process.stdin.isTTY) {
|
|
328
328
|
process.stdin.setRawMode?.(true);
|
|
@@ -332,7 +332,7 @@ function waitForEnter(prompt = "Press Enter to continue...") {
|
|
|
332
332
|
});
|
|
333
333
|
}
|
|
334
334
|
function sleep(ms) {
|
|
335
|
-
return new Promise((
|
|
335
|
+
return new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
336
336
|
}
|
|
337
337
|
|
|
338
338
|
// src/commands/login.ts
|
|
@@ -410,19 +410,19 @@ async function tokenLogin() {
|
|
|
410
410
|
);
|
|
411
411
|
blank();
|
|
412
412
|
process.stdout.write("Paste token: ");
|
|
413
|
-
const token = await new Promise((
|
|
413
|
+
const token = await new Promise((resolve5) => {
|
|
414
414
|
let data = "";
|
|
415
415
|
process.stdin.setEncoding("utf8");
|
|
416
416
|
process.stdin.on("data", (chunk) => {
|
|
417
417
|
data += chunk;
|
|
418
418
|
});
|
|
419
419
|
process.stdin.on("end", () => {
|
|
420
|
-
|
|
420
|
+
resolve5(data.trim());
|
|
421
421
|
});
|
|
422
422
|
if (process.stdin.isTTY) {
|
|
423
423
|
process.stdin.once("data", (chunk) => {
|
|
424
424
|
process.stdin.pause();
|
|
425
|
-
|
|
425
|
+
resolve5(chunk.toString().trim());
|
|
426
426
|
});
|
|
427
427
|
process.stdin.resume();
|
|
428
428
|
}
|
|
@@ -1158,7 +1158,7 @@ async function claudeUsage() {
|
|
|
1158
1158
|
// src/commands/run.ts
|
|
1159
1159
|
import { chmodSync as chmodSync3, existsSync as existsSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "node:fs";
|
|
1160
1160
|
import { homedir as homedir7 } from "node:os";
|
|
1161
|
-
import { isAbsolute as
|
|
1161
|
+
import { isAbsolute as isAbsolute4, join as join12, parse, resolve as resolvePath2 } from "node:path";
|
|
1162
1162
|
import chalk7 from "chalk";
|
|
1163
1163
|
|
|
1164
1164
|
// ../../packages/types/src/agents/index.ts
|
|
@@ -1293,7 +1293,11 @@ var FORWARD_FAILURE_REASON_HEADER = "X-Evident-Failure-Reason";
|
|
|
1293
1293
|
function log(level, event, fields) {
|
|
1294
1294
|
const method = level === "debug" ? "log" : level;
|
|
1295
1295
|
try {
|
|
1296
|
-
|
|
1296
|
+
const payload = Object.fromEntries(
|
|
1297
|
+
Object.entries({ level, event, ...fields }).filter(([, value]) => value !== void 0)
|
|
1298
|
+
);
|
|
1299
|
+
const secondArgument = globalThis.navigator?.userAgent === "Cloudflare-Workers" ? payload : JSON.stringify(payload);
|
|
1300
|
+
console[method]("[evident]", secondArgument);
|
|
1297
1301
|
} catch (err) {
|
|
1298
1302
|
console.error(
|
|
1299
1303
|
"[evident] log_serialize_failed",
|
|
@@ -1322,7 +1326,7 @@ import ora4 from "ora";
|
|
|
1322
1326
|
import { select as select4 } from "@inquirer/prompts";
|
|
1323
1327
|
|
|
1324
1328
|
// src/lib/telemetry.ts
|
|
1325
|
-
var CLI_VERSION = (true ? "3.5.2-dev.
|
|
1329
|
+
var CLI_VERSION = (true ? "3.5.2-dev.91f886c" : void 0) ?? process.env.npm_package_version ?? "unknown";
|
|
1326
1330
|
function getCliVersion() {
|
|
1327
1331
|
return CLI_VERSION;
|
|
1328
1332
|
}
|
|
@@ -1810,6 +1814,263 @@ async function reclaimSessionDbSpace(input) {
|
|
|
1810
1814
|
}
|
|
1811
1815
|
}
|
|
1812
1816
|
|
|
1817
|
+
// src/lib/opencode/dependency-install-diagnostic.ts
|
|
1818
|
+
import { lstatSync, realpathSync } from "node:fs";
|
|
1819
|
+
import { basename, dirname as dirname3, isAbsolute, join as join2, relative, resolve, sep } from "node:path";
|
|
1820
|
+
var DEPENDENCY_INSTALL_FAILURE_MESSAGE = "background dependency install failed";
|
|
1821
|
+
var MAX_DISTINCT_DIAGNOSTICS = 32;
|
|
1822
|
+
var EFFECT = "OpenCode could not install background dependencies for this scope; its plugins and tools may be unavailable";
|
|
1823
|
+
function readOpenCodeLogField(line, key) {
|
|
1824
|
+
const match = line.match(new RegExp(`(?:^|\\s)${key}=(?:"((?:\\\\.|[^"])*)"|(\\S+))`));
|
|
1825
|
+
if (!match) return void 0;
|
|
1826
|
+
if (match[1] === void 0) return match[2];
|
|
1827
|
+
return match[1].replace(/\\(["\\n])/g, (_match, escaped) => {
|
|
1828
|
+
return escaped === "n" ? "\n" : escaped;
|
|
1829
|
+
});
|
|
1830
|
+
}
|
|
1831
|
+
function parseTrigger(line) {
|
|
1832
|
+
if (readOpenCodeLogField(line, "message") !== DEPENDENCY_INSTALL_FAILURE_MESSAGE) {
|
|
1833
|
+
return null;
|
|
1834
|
+
}
|
|
1835
|
+
const error2 = readOpenCodeLogField(line, "error");
|
|
1836
|
+
if (!error2 || !/\bEACCES\b/.test(error2)) return null;
|
|
1837
|
+
const errnoMatch = error2.match(/\bEACCES:\s*permission denied,\s*([a-z_]+)\s+'([^']*)'/);
|
|
1838
|
+
const capturedPath = errnoMatch?.[2];
|
|
1839
|
+
return {
|
|
1840
|
+
dir: readOpenCodeLogField(line, "dir"),
|
|
1841
|
+
op: errnoMatch?.[1],
|
|
1842
|
+
path: capturedPath && isAbsolute(capturedPath) ? capturedPath : void 0
|
|
1843
|
+
};
|
|
1844
|
+
}
|
|
1845
|
+
function getErrorCode(error2) {
|
|
1846
|
+
if (typeof error2 === "object" && error2 !== null && "code" in error2) {
|
|
1847
|
+
const code = error2.code;
|
|
1848
|
+
if (typeof code === "string") return code;
|
|
1849
|
+
}
|
|
1850
|
+
return "UNKNOWN";
|
|
1851
|
+
}
|
|
1852
|
+
function isWithin(path, root) {
|
|
1853
|
+
const candidate = resolve(path);
|
|
1854
|
+
const resolvedRoot = resolve(root);
|
|
1855
|
+
return candidate === resolvedRoot || candidate.startsWith(`${resolvedRoot}${sep}`);
|
|
1856
|
+
}
|
|
1857
|
+
function rootSpecs(context) {
|
|
1858
|
+
return [
|
|
1859
|
+
{ token: "~", configured: resolve(context.homeDir) },
|
|
1860
|
+
{ token: "$WORKSPACE", configured: resolve(context.cwd) }
|
|
1861
|
+
];
|
|
1862
|
+
}
|
|
1863
|
+
function resolveRoots(context) {
|
|
1864
|
+
const resolved = [];
|
|
1865
|
+
const unresolved = [];
|
|
1866
|
+
for (const spec of rootSpecs(context)) {
|
|
1867
|
+
try {
|
|
1868
|
+
resolved.push({ ...spec, real: realpathSync(spec.configured) });
|
|
1869
|
+
} catch (_error) {
|
|
1870
|
+
unresolved.push(spec);
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
return { resolved, unresolved };
|
|
1874
|
+
}
|
|
1875
|
+
function resolveNearestAncestor(failingPath, remaining, error2) {
|
|
1876
|
+
let ancestor = dirname3(failingPath);
|
|
1877
|
+
const suffix = [basename(failingPath), ...remaining];
|
|
1878
|
+
while (ancestor) {
|
|
1879
|
+
try {
|
|
1880
|
+
return {
|
|
1881
|
+
path: resolve(realpathSync(ancestor), ...suffix),
|
|
1882
|
+
failure: { path: resolve(failingPath), error: error2 }
|
|
1883
|
+
};
|
|
1884
|
+
} catch (ancestorError) {
|
|
1885
|
+
if (ancestor === dirname3(ancestor) || !getErrorCode(ancestorError).startsWith("E")) {
|
|
1886
|
+
return null;
|
|
1887
|
+
}
|
|
1888
|
+
suffix.unshift(basename(ancestor));
|
|
1889
|
+
ancestor = dirname3(ancestor);
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
return null;
|
|
1893
|
+
}
|
|
1894
|
+
function resolveExistingPath(absolutePath) {
|
|
1895
|
+
if (!isAbsolute(absolutePath)) return null;
|
|
1896
|
+
const candidate = resolve(absolutePath);
|
|
1897
|
+
const remaining = [];
|
|
1898
|
+
let current = candidate;
|
|
1899
|
+
while (current) {
|
|
1900
|
+
try {
|
|
1901
|
+
const stat2 = lstatSync(current);
|
|
1902
|
+
if (stat2.isSymbolicLink()) {
|
|
1903
|
+
if (remaining.length === 0) {
|
|
1904
|
+
return { path: join2(realpathSync(dirname3(current)), basename(current)) };
|
|
1905
|
+
}
|
|
1906
|
+
return { path: resolve(realpathSync(current), ...remaining) };
|
|
1907
|
+
}
|
|
1908
|
+
return { path: resolve(realpathSync(current), ...remaining) };
|
|
1909
|
+
} catch (error2) {
|
|
1910
|
+
const code = getErrorCode(error2);
|
|
1911
|
+
if (code !== "ENOENT" && code !== "ENOTDIR") {
|
|
1912
|
+
return resolveNearestAncestor(current, remaining, error2);
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
if (current === dirname3(current)) return null;
|
|
1916
|
+
remaining.unshift(basename(current));
|
|
1917
|
+
current = dirname3(current);
|
|
1918
|
+
}
|
|
1919
|
+
return null;
|
|
1920
|
+
}
|
|
1921
|
+
function tokenise(absolutePath, roots) {
|
|
1922
|
+
const pathResolution = resolveExistingPath(absolutePath);
|
|
1923
|
+
if (!pathResolution) return null;
|
|
1924
|
+
const root = roots.reduce((selected, candidate) => {
|
|
1925
|
+
if (!isWithin(pathResolution.path, candidate.real)) return selected;
|
|
1926
|
+
if (!selected || candidate.real.length > selected.real.length || candidate.real.length === selected.real.length && candidate.token === "$WORKSPACE") {
|
|
1927
|
+
return candidate;
|
|
1928
|
+
}
|
|
1929
|
+
return selected;
|
|
1930
|
+
}, null);
|
|
1931
|
+
if (!root) return null;
|
|
1932
|
+
const suffix = relative(root.real, pathResolution.path);
|
|
1933
|
+
return {
|
|
1934
|
+
value: suffix ? `${root.token}/${suffix.split(sep).join("/")}` : root.token,
|
|
1935
|
+
root,
|
|
1936
|
+
resolutionFailure: pathResolution.failure
|
|
1937
|
+
};
|
|
1938
|
+
}
|
|
1939
|
+
function classifyScope(dir, roots) {
|
|
1940
|
+
if (!dir) return "other";
|
|
1941
|
+
const tokenisedDir = tokenise(dir, roots.resolved);
|
|
1942
|
+
if (tokenisedDir) return tokenisedDir.root.token === "$WORKSPACE" ? "workspace" : "global";
|
|
1943
|
+
for (const spec of roots.unresolved) {
|
|
1944
|
+
if (isWithin(dir, spec.configured)) {
|
|
1945
|
+
return spec.token === "$WORKSPACE" ? "workspace" : "global";
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
return "other";
|
|
1949
|
+
}
|
|
1950
|
+
function unresolvedRootForPath(path, unresolved) {
|
|
1951
|
+
return unresolved.find((spec) => isWithin(path, spec.configured));
|
|
1952
|
+
}
|
|
1953
|
+
function statType(stat2) {
|
|
1954
|
+
if (stat2.isDirectory()) return "d";
|
|
1955
|
+
if (stat2.isFile()) return "f";
|
|
1956
|
+
if (stat2.isSymbolicLink()) return "l";
|
|
1957
|
+
return "o";
|
|
1958
|
+
}
|
|
1959
|
+
function renderStat(name, stat2) {
|
|
1960
|
+
const mode = (stat2.mode & 4095).toString(8).padStart(4, "0");
|
|
1961
|
+
return `${name}:${statType(stat2)}:${stat2.uid}:${stat2.gid}:${mode}`;
|
|
1962
|
+
}
|
|
1963
|
+
function renderStatError(name, error2) {
|
|
1964
|
+
const code = getErrorCode(error2);
|
|
1965
|
+
return `${name}:${renderErrorCode(code)}`;
|
|
1966
|
+
}
|
|
1967
|
+
function renderErrorCode(code) {
|
|
1968
|
+
if (code === "ENOENT") return "missing";
|
|
1969
|
+
return code.startsWith("E") ? code : `E${code}`;
|
|
1970
|
+
}
|
|
1971
|
+
function inspectChain(rootReal, rootToken, absolutePath, resolutionFailure) {
|
|
1972
|
+
const target = resolve(absolutePath);
|
|
1973
|
+
const relativePath = relative(rootReal, target);
|
|
1974
|
+
const components = relativePath ? relativePath.split(sep) : [];
|
|
1975
|
+
const entries = [];
|
|
1976
|
+
let current = rootReal;
|
|
1977
|
+
let status2 = "complete";
|
|
1978
|
+
let finalStat;
|
|
1979
|
+
for (const name of [rootToken, ...components]) {
|
|
1980
|
+
if (name !== rootToken) current = join2(current, name);
|
|
1981
|
+
if (resolutionFailure && resolve(current) === resolve(resolutionFailure.path)) {
|
|
1982
|
+
entries.push(renderStatError(name, resolutionFailure.error));
|
|
1983
|
+
status2 = "stat_failed";
|
|
1984
|
+
break;
|
|
1985
|
+
}
|
|
1986
|
+
try {
|
|
1987
|
+
const stat2 = lstatSync(current);
|
|
1988
|
+
entries.push(renderStat(name, stat2));
|
|
1989
|
+
finalStat = stat2;
|
|
1990
|
+
} catch (error2) {
|
|
1991
|
+
entries.push(renderStatError(name, error2));
|
|
1992
|
+
if (getErrorCode(error2) !== "ENOENT") status2 = "stat_failed";
|
|
1993
|
+
break;
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
let symlinkTarget;
|
|
1997
|
+
let symlinkMeta;
|
|
1998
|
+
if (finalStat?.isSymbolicLink() && entries.length === components.length + 1) {
|
|
1999
|
+
try {
|
|
2000
|
+
symlinkTarget = realpathSync(current);
|
|
2001
|
+
try {
|
|
2002
|
+
symlinkMeta = renderStat("", lstatSync(symlinkTarget)).slice(1);
|
|
2003
|
+
} catch (error2) {
|
|
2004
|
+
symlinkMeta = renderErrorCode(getErrorCode(error2));
|
|
2005
|
+
status2 = "stat_failed";
|
|
2006
|
+
}
|
|
2007
|
+
} catch (error2) {
|
|
2008
|
+
symlinkTarget = "<unresolved>";
|
|
2009
|
+
symlinkMeta = renderErrorCode(getErrorCode(error2));
|
|
2010
|
+
status2 = "stat_failed";
|
|
2011
|
+
}
|
|
2012
|
+
}
|
|
2013
|
+
return { status: status2, chain: entries.join(","), symlinkTarget, symlinkMeta };
|
|
2014
|
+
}
|
|
2015
|
+
function buildDiagnostic(line, context) {
|
|
2016
|
+
const trigger = parseTrigger(line);
|
|
2017
|
+
if (!trigger) return null;
|
|
2018
|
+
const roots = resolveRoots(context);
|
|
2019
|
+
const scope = classifyScope(trigger.dir, roots);
|
|
2020
|
+
const pathSource = trigger.path ? "error" : trigger.dir ? "dir" : "none";
|
|
2021
|
+
const inspectedPath = trigger.path ?? trigger.dir;
|
|
2022
|
+
const microvmId = context.microvmId?.trim() || "unknown";
|
|
2023
|
+
const euid = context.euid === void 0 ? "unavailable" : String(context.euid);
|
|
2024
|
+
const egid = context.egid === void 0 ? "unavailable" : String(context.egid);
|
|
2025
|
+
const op = trigger.op ?? "unknown";
|
|
2026
|
+
if (!inspectedPath) {
|
|
2027
|
+
return {
|
|
2028
|
+
event: `OPENCODE-DEPENDENCY-INSTALL-EACCES: status=no_path path_source=${pathSource} scope=${scope} microvm_id=${microvmId} op=${op} path=unknown cli_euid=${euid} cli_egid=${egid} effect="${EFFECT}" chain=unavailable`,
|
|
2029
|
+
localDetail: null
|
|
2030
|
+
};
|
|
2031
|
+
}
|
|
2032
|
+
const tokenisedPath = tokenise(inspectedPath, roots.resolved);
|
|
2033
|
+
if (!tokenisedPath) {
|
|
2034
|
+
const unresolvedRoot = unresolvedRootForPath(inspectedPath, roots.unresolved);
|
|
2035
|
+
if (unresolvedRoot) {
|
|
2036
|
+
return {
|
|
2037
|
+
event: `OPENCODE-DEPENDENCY-INSTALL-EACCES: status=root_unresolved path_source=${pathSource} scope=${scope} microvm_id=${microvmId} op=${op} path=unknown cli_euid=${euid} cli_egid=${egid} effect="${EFFECT}" chain=unavailable`,
|
|
2038
|
+
localDetail: null
|
|
2039
|
+
};
|
|
2040
|
+
}
|
|
2041
|
+
return {
|
|
2042
|
+
event: `OPENCODE-DEPENDENCY-INSTALL-EACCES: status=outside_roots path_source=${pathSource} scope=${scope} microvm_id=${microvmId} op=${op} path=<outside-approved-roots> cli_euid=${euid} cli_egid=${egid} effect="${EFFECT}" chain=unavailable`,
|
|
2043
|
+
localDetail: `OPENCODE-DEPENDENCY-INSTALL-EACCES (local only): outside approved roots: ${inspectedPath}`
|
|
2044
|
+
};
|
|
2045
|
+
}
|
|
2046
|
+
const inspection = inspectChain(
|
|
2047
|
+
tokenisedPath.root.real,
|
|
2048
|
+
tokenisedPath.root.token,
|
|
2049
|
+
inspectedPath,
|
|
2050
|
+
tokenisedPath.resolutionFailure
|
|
2051
|
+
);
|
|
2052
|
+
const optionalSymlinkFields = inspection.symlinkTarget ? ` symlink_target=${tokenise(inspection.symlinkTarget, roots.resolved)?.value ?? "<outside-approved-roots>"}` + (inspection.symlinkMeta ? ` symlink_meta=${inspection.symlinkMeta}` : "") : "";
|
|
2053
|
+
return {
|
|
2054
|
+
event: `OPENCODE-DEPENDENCY-INSTALL-EACCES: status=${inspection.status} path_source=${pathSource} scope=${scope} microvm_id=${microvmId} op=${op} path=${tokenisedPath.value} cli_euid=${euid} cli_egid=${egid} effect="${EFFECT}" chain=${inspection.chain}${optionalSymlinkFields}`,
|
|
2055
|
+
localDetail: null
|
|
2056
|
+
};
|
|
2057
|
+
}
|
|
2058
|
+
function createDependencyInstallDiagnosticReporter({
|
|
2059
|
+
context,
|
|
2060
|
+
emitEvent: emitEvent2,
|
|
2061
|
+
emitLocal
|
|
2062
|
+
}) {
|
|
2063
|
+
const emittedEvents = /* @__PURE__ */ new Set();
|
|
2064
|
+
return (line) => {
|
|
2065
|
+
const diagnostic = buildDiagnostic(line, context);
|
|
2066
|
+
if (!diagnostic || emittedEvents.has(diagnostic.event)) return;
|
|
2067
|
+
if (emittedEvents.size >= MAX_DISTINCT_DIAGNOSTICS) return;
|
|
2068
|
+
emittedEvents.add(diagnostic.event);
|
|
2069
|
+
emitEvent2(diagnostic.event);
|
|
2070
|
+
if (diagnostic.localDetail) emitLocal(diagnostic.localDetail);
|
|
2071
|
+
};
|
|
2072
|
+
}
|
|
2073
|
+
|
|
1813
2074
|
// src/lib/runner-activity-telemetry.ts
|
|
1814
2075
|
var FORWARDED_LEVELS_BY_SOURCE = {
|
|
1815
2076
|
"cli.run": /* @__PURE__ */ new Set(["warn", "error"]),
|
|
@@ -1841,7 +2102,17 @@ function parseOpenCodeLogLine(line) {
|
|
|
1841
2102
|
}
|
|
1842
2103
|
var MAX_LINE_BUFFER_BYTES = 16 * 1024;
|
|
1843
2104
|
var REPEAT_SUPPRESSION_WINDOW_MS = 6e4;
|
|
1844
|
-
function
|
|
2105
|
+
function sanitiseDependencyInstallWarning(line) {
|
|
2106
|
+
const withoutDirectory = line.replace(
|
|
2107
|
+
/((?:^|\s)dir=)(?:"(?:\\.|[^"])*"|\/[^\s)"']+|\S+)/g,
|
|
2108
|
+
"$1<path>"
|
|
2109
|
+
);
|
|
2110
|
+
return withoutDirectory.replace(
|
|
2111
|
+
/((?:^|\s)error=)"((?:\\.|[^"])*)"/,
|
|
2112
|
+
(_match, prefix, error2) => `${prefix}"${error2.replace(/'\/[^']*'/g, "'<path>'")}"`
|
|
2113
|
+
);
|
|
2114
|
+
}
|
|
2115
|
+
function createOpenCodeActivityForwarder(getContext, onSessionStoreWriteFailure, onDependencyInstallFailure) {
|
|
1845
2116
|
let buffer = Buffer.alloc(0);
|
|
1846
2117
|
const repeatStates = /* @__PURE__ */ new Map();
|
|
1847
2118
|
const forwardLine = (line, parsed, repeatsSuppressed = 0) => {
|
|
@@ -1853,7 +2124,7 @@ function createOpenCodeActivityForwarder(getContext, onSessionStoreWriteFailure)
|
|
|
1853
2124
|
forwardRunnerActivity(
|
|
1854
2125
|
{
|
|
1855
2126
|
level: parsed.level,
|
|
1856
|
-
error: line,
|
|
2127
|
+
error: parsed.message === DEPENDENCY_INSTALL_FAILURE_MESSAGE ? sanitiseDependencyInstallWarning(line) : line,
|
|
1857
2128
|
metadata: Object.keys(metadata).length > 0 ? metadata : void 0,
|
|
1858
2129
|
source: "opencode"
|
|
1859
2130
|
},
|
|
@@ -1871,24 +2142,7 @@ function createOpenCodeActivityForwarder(getContext, onSessionStoreWriteFailure)
|
|
|
1871
2142
|
repeatStates.delete(key);
|
|
1872
2143
|
}
|
|
1873
2144
|
};
|
|
1874
|
-
const
|
|
1875
|
-
const parsed = parseOpenCodeLogLine(line);
|
|
1876
|
-
const isDiskFailure = isSessionStoreWriteFailure(line);
|
|
1877
|
-
const cause = sessionStoreCauseToken(line);
|
|
1878
|
-
if (onSessionStoreWriteFailure && (isDiskFailure || cause !== null)) {
|
|
1879
|
-
try {
|
|
1880
|
-
onSessionStoreWriteFailure({
|
|
1881
|
-
isDiskFailure,
|
|
1882
|
-
sessionId: parsed?.sessionID ?? null,
|
|
1883
|
-
cause
|
|
1884
|
-
});
|
|
1885
|
-
} catch (err) {
|
|
1886
|
-
reportTelemetryDiagnostic(
|
|
1887
|
-
`Runner activity telemetry could not record session-store write failure: ${err instanceof Error ? err.message : String(err)}`
|
|
1888
|
-
);
|
|
1889
|
-
}
|
|
1890
|
-
}
|
|
1891
|
-
if (!parsed) return;
|
|
2145
|
+
const forwardWithRepeatSuppression = (line, parsed) => {
|
|
1892
2146
|
const now = Date.now();
|
|
1893
2147
|
const key = parsed.message === void 0 ? void 0 : `${parsed.level}\0${parsed.message}\0${parsed.sessionID ?? ""}\0${parsed.messageID ?? ""}`;
|
|
1894
2148
|
expireRepeatStates(now, key);
|
|
@@ -1911,6 +2165,35 @@ function createOpenCodeActivityForwarder(getContext, onSessionStoreWriteFailure)
|
|
|
1911
2165
|
previous.parsed = parsed;
|
|
1912
2166
|
previous.repeatsSuppressed++;
|
|
1913
2167
|
};
|
|
2168
|
+
const flushLine = (line) => {
|
|
2169
|
+
const parsed = parseOpenCodeLogLine(line);
|
|
2170
|
+
const isDiskFailure = isSessionStoreWriteFailure(line);
|
|
2171
|
+
const cause = sessionStoreCauseToken(line);
|
|
2172
|
+
if (onSessionStoreWriteFailure && (isDiskFailure || cause !== null)) {
|
|
2173
|
+
try {
|
|
2174
|
+
onSessionStoreWriteFailure({
|
|
2175
|
+
isDiskFailure,
|
|
2176
|
+
sessionId: parsed?.sessionID ?? null,
|
|
2177
|
+
cause
|
|
2178
|
+
});
|
|
2179
|
+
} catch (err) {
|
|
2180
|
+
reportTelemetryDiagnostic(
|
|
2181
|
+
`Runner activity telemetry could not record session-store write failure: ${err instanceof Error ? err.message : String(err)}`
|
|
2182
|
+
);
|
|
2183
|
+
}
|
|
2184
|
+
}
|
|
2185
|
+
if (!parsed) return;
|
|
2186
|
+
forwardWithRepeatSuppression(line, parsed);
|
|
2187
|
+
if (onDependencyInstallFailure && parsed.message === DEPENDENCY_INSTALL_FAILURE_MESSAGE) {
|
|
2188
|
+
try {
|
|
2189
|
+
onDependencyInstallFailure(line);
|
|
2190
|
+
} catch (err) {
|
|
2191
|
+
reportTelemetryDiagnostic(
|
|
2192
|
+
`Runner activity telemetry could not record dependency-install failure diagnostic: ${err instanceof Error ? err.message : String(err)}`
|
|
2193
|
+
);
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
};
|
|
1914
2197
|
const forward = ((chunk) => {
|
|
1915
2198
|
buffer = Buffer.concat([buffer, Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, "utf-8")]);
|
|
1916
2199
|
let newlineIndex;
|
|
@@ -1950,6 +2233,23 @@ function truncate(message) {
|
|
|
1950
2233
|
const remaining = MAX_MESSAGE_LENGTH - marker.length;
|
|
1951
2234
|
const headLength = Math.ceil(remaining * 0.6);
|
|
1952
2235
|
const tailLength = remaining - headLength;
|
|
2236
|
+
const effectMatch = message.startsWith("OPENCODE-DEPENDENCY-INSTALL-EACCES:") ? message.match(/ effect="[^"]*"/) : null;
|
|
2237
|
+
if (effectMatch?.index !== void 0) {
|
|
2238
|
+
const effectEnd = effectMatch.index + effectMatch[0].length;
|
|
2239
|
+
if (effectEnd > headLength && effectEnd < message.length) {
|
|
2240
|
+
const effectLength = effectMatch[0].length;
|
|
2241
|
+
const nonEffectLength = remaining - effectLength;
|
|
2242
|
+
const preservedTailLength = Math.min(
|
|
2243
|
+
message.length - effectEnd,
|
|
2244
|
+
nonEffectLength - Math.min(effectMatch.index, Math.ceil(nonEffectLength * 0.6))
|
|
2245
|
+
);
|
|
2246
|
+
const preservedHeadLength = Math.min(
|
|
2247
|
+
effectMatch.index,
|
|
2248
|
+
nonEffectLength - preservedTailLength
|
|
2249
|
+
);
|
|
2250
|
+
return message.slice(0, preservedHeadLength) + marker + message.slice(effectMatch.index, effectEnd) + message.slice(message.length - preservedTailLength);
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
1953
2253
|
return message.slice(0, headLength) + marker + message.slice(message.length - tailLength);
|
|
1954
2254
|
}
|
|
1955
2255
|
function sanitiseMetadata(metadata) {
|
|
@@ -2027,10 +2327,10 @@ function forwardRunnerActivity(entry, context) {
|
|
|
2027
2327
|
|
|
2028
2328
|
// src/lib/opencode/session-db-recovery-report.ts
|
|
2029
2329
|
import { readFileSync as readFileSync2, unlinkSync } from "node:fs";
|
|
2030
|
-
import { join as
|
|
2330
|
+
import { join as join3 } from "node:path";
|
|
2031
2331
|
function sessionDbRecoveryReportPath(homeDir, env) {
|
|
2032
2332
|
const override = env.EVIDENT_SESSION_DB_RECOVERY_REPORT?.trim();
|
|
2033
|
-
return override ||
|
|
2333
|
+
return override || join3(homeDir, ".local", "state", "evident", "session-db-recovery.jsonl");
|
|
2034
2334
|
}
|
|
2035
2335
|
function drainSessionDbRecoveryReport({
|
|
2036
2336
|
homeDir,
|
|
@@ -2303,7 +2603,7 @@ async function waitForOpenCodeHealth(port, timeoutMs = 3e4) {
|
|
|
2303
2603
|
if (health.healthy || health.authFailed) {
|
|
2304
2604
|
return health;
|
|
2305
2605
|
}
|
|
2306
|
-
await new Promise((
|
|
2606
|
+
await new Promise((resolve5) => setTimeout(resolve5, 1e3));
|
|
2307
2607
|
}
|
|
2308
2608
|
return { healthy: false, error: "Timeout waiting for OpenCode to be healthy" };
|
|
2309
2609
|
}
|
|
@@ -2314,7 +2614,7 @@ async function waitForOpenCode2Health(port, password, timeoutMs = 3e4) {
|
|
|
2314
2614
|
if (health.healthy || health.authFailed) {
|
|
2315
2615
|
return health;
|
|
2316
2616
|
}
|
|
2317
|
-
await new Promise((
|
|
2617
|
+
await new Promise((resolve5) => setTimeout(resolve5, 1e3));
|
|
2318
2618
|
}
|
|
2319
2619
|
return { healthy: false, error: "Timeout waiting for OpenCode to be healthy" };
|
|
2320
2620
|
}
|
|
@@ -2904,7 +3204,7 @@ async function sendPromptAsync(port, sessionId, content, options, attachments, c
|
|
|
2904
3204
|
return dispatchedUserMessageId;
|
|
2905
3205
|
}
|
|
2906
3206
|
if (attempt < READ_BACK_ATTEMPTS - 1) {
|
|
2907
|
-
await new Promise((
|
|
3207
|
+
await new Promise((resolve5) => setTimeout(resolve5, READ_BACK_DELAY_MS));
|
|
2908
3208
|
}
|
|
2909
3209
|
}
|
|
2910
3210
|
return null;
|
|
@@ -3222,19 +3522,32 @@ function isAbortedTerminalReply(messages, userMessageId) {
|
|
|
3222
3522
|
}
|
|
3223
3523
|
return false;
|
|
3224
3524
|
}
|
|
3525
|
+
var CLAUDE_AUTH_EXPIRY_MESSAGES = Object.freeze([
|
|
3526
|
+
"Claude Code credentials are unavailable or expired. Run `claude` to refresh them."
|
|
3527
|
+
]);
|
|
3528
|
+
function extractReplyErrorShape(reply) {
|
|
3529
|
+
const error2 = errorOf(reply);
|
|
3530
|
+
if (error2 == null || typeof error2 !== "object") {
|
|
3531
|
+
return { name: null, statusCode: null };
|
|
3532
|
+
}
|
|
3533
|
+
const e = error2;
|
|
3534
|
+
const name = typeof e.name === "string" ? e.name.slice(0, 100) : null;
|
|
3535
|
+
const data = e.data;
|
|
3536
|
+
const statusCode = typeof data?.statusCode === "number" && Number.isFinite(data.statusCode) ? data.statusCode : null;
|
|
3537
|
+
return { name, statusCode };
|
|
3538
|
+
}
|
|
3225
3539
|
function classifyReplyAuthError(reply) {
|
|
3226
3540
|
const error2 = errorOf(reply);
|
|
3227
3541
|
if (error2 == null || typeof error2 !== "object") return null;
|
|
3228
3542
|
const e = error2;
|
|
3229
3543
|
const replyProviderId = reply?.info?.providerID ?? null;
|
|
3230
3544
|
const replyModelId = reply?.info?.modelID ?? null;
|
|
3545
|
+
const data = e.data;
|
|
3231
3546
|
if (e.name === "ProviderAuthError") {
|
|
3232
|
-
const data = e.data;
|
|
3233
3547
|
const providerId = typeof data?.providerID === "string" && data.providerID || replyProviderId;
|
|
3234
3548
|
return { kind: "model_auth", providerId, modelId: replyModelId, reason: "missing" };
|
|
3235
3549
|
}
|
|
3236
3550
|
if (e.name === "APIError") {
|
|
3237
|
-
const data = e.data;
|
|
3238
3551
|
const statusCode = data?.statusCode;
|
|
3239
3552
|
if (statusCode === 401 || statusCode === 403) {
|
|
3240
3553
|
return {
|
|
@@ -3245,11 +3558,23 @@ function classifyReplyAuthError(reply) {
|
|
|
3245
3558
|
};
|
|
3246
3559
|
}
|
|
3247
3560
|
}
|
|
3561
|
+
const errorMessage3 = typeof data?.message === "string" ? data.message : typeof e.message === "string" ? e.message : null;
|
|
3562
|
+
if (errorMessage3 !== null && CLAUDE_AUTH_EXPIRY_MESSAGES.some((knownMessage) => errorMessage3.includes(knownMessage))) {
|
|
3563
|
+
return {
|
|
3564
|
+
kind: "model_auth",
|
|
3565
|
+
providerId: typeof data?.providerID === "string" && data.providerID || replyProviderId,
|
|
3566
|
+
modelId: replyModelId,
|
|
3567
|
+
reason: "rejected"
|
|
3568
|
+
};
|
|
3569
|
+
}
|
|
3248
3570
|
return null;
|
|
3249
3571
|
}
|
|
3250
3572
|
function messageFailure(messages, userMessageId) {
|
|
3251
3573
|
return classifyReplyAuthError(findLastAssistantReplyFor(messages, userMessageId));
|
|
3252
3574
|
}
|
|
3575
|
+
function isProviderModelNotFoundError(reason) {
|
|
3576
|
+
return reason.includes("Model not found:");
|
|
3577
|
+
}
|
|
3253
3578
|
function findLatestSubagentAuthOutcome(messages, sinceMs) {
|
|
3254
3579
|
if (!messages || messages.length === 0) return null;
|
|
3255
3580
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
@@ -3458,23 +3783,49 @@ async function readSessionErrorStream(port, options, client) {
|
|
|
3458
3783
|
if (reader) void reader.cancel().catch(() => void 0);
|
|
3459
3784
|
}
|
|
3460
3785
|
}
|
|
3461
|
-
async function
|
|
3462
|
-
if (client?.version === "v2") return;
|
|
3786
|
+
async function disposeAndReloadProviderConfig(port, client) {
|
|
3787
|
+
if (client?.version === "v2") return { outcome: "skipped_v2" };
|
|
3463
3788
|
try {
|
|
3464
|
-
const res = await requestWithClient(
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3789
|
+
const res = await requestWithClient(
|
|
3790
|
+
port,
|
|
3791
|
+
client,
|
|
3792
|
+
"/config",
|
|
3793
|
+
{
|
|
3794
|
+
method: "PATCH",
|
|
3795
|
+
headers: { "Content-Type": "application/json" },
|
|
3796
|
+
body: JSON.stringify({})
|
|
3797
|
+
},
|
|
3798
|
+
{ allowStatuses: ALL_HTTP_STATUSES }
|
|
3799
|
+
);
|
|
3469
3800
|
if (!res.ok) {
|
|
3470
3801
|
console.error(
|
|
3471
|
-
`[
|
|
3802
|
+
`[disposeAndReloadProviderConfig] PATCH /config returned HTTP ${res.status} (port ${port})`
|
|
3472
3803
|
);
|
|
3804
|
+
return { outcome: "failed", status: res.status, detail: null };
|
|
3473
3805
|
}
|
|
3806
|
+
return { outcome: "reloaded" };
|
|
3474
3807
|
} catch (err) {
|
|
3475
3808
|
console.error(
|
|
3476
|
-
`[
|
|
3809
|
+
`[disposeAndReloadProviderConfig] PATCH /config failed (port ${port}): ${err instanceof Error ? err.message : String(err)}`
|
|
3477
3810
|
);
|
|
3811
|
+
return {
|
|
3812
|
+
outcome: "failed",
|
|
3813
|
+
status: null,
|
|
3814
|
+
detail: err instanceof Error ? err.message : String(err)
|
|
3815
|
+
};
|
|
3816
|
+
}
|
|
3817
|
+
}
|
|
3818
|
+
async function probeOpenCodeRequestReadiness(port, client) {
|
|
3819
|
+
try {
|
|
3820
|
+
const res = await requestWithClient(port, client, "/config", void 0, {
|
|
3821
|
+
allowStatuses: ALL_HTTP_STATUSES
|
|
3822
|
+
});
|
|
3823
|
+
const body = await res.text();
|
|
3824
|
+
return { ok: res.ok, status: res.status, bodyEmpty: body.trim().length === 0, detail: null };
|
|
3825
|
+
} catch (err) {
|
|
3826
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
3827
|
+
console.error(`[probeOpenCodeRequestReadiness] GET /config failed (port ${port}): ${detail}`);
|
|
3828
|
+
return { ok: false, status: null, bodyEmpty: null, detail };
|
|
3478
3829
|
}
|
|
3479
3830
|
}
|
|
3480
3831
|
|
|
@@ -3967,12 +4318,12 @@ async function readV2SessionErrorStream(client, options) {
|
|
|
3967
4318
|
|
|
3968
4319
|
// src/lib/opencode/credential-import-v2.ts
|
|
3969
4320
|
import { readFileSync as readFileSync4 } from "node:fs";
|
|
3970
|
-
import { join as
|
|
4321
|
+
import { join as join5 } from "node:path";
|
|
3971
4322
|
|
|
3972
4323
|
// src/lib/openai-usage.ts
|
|
3973
4324
|
import { readFileSync as readFileSync3 } from "node:fs";
|
|
3974
4325
|
import { homedir as homedir3 } from "node:os";
|
|
3975
|
-
import { join as
|
|
4326
|
+
import { join as join4 } from "node:path";
|
|
3976
4327
|
var CODEX_RESPONSES_URL = "https://chatgpt.com/backend-api/codex/responses";
|
|
3977
4328
|
var OPENCODE_AUTH_SEGMENTS = [".local", "share", "opencode", "auth.json"];
|
|
3978
4329
|
var OpenAiUsageError = class extends Error {
|
|
@@ -3986,7 +4337,7 @@ function isLocalCredentialProblem2(err) {
|
|
|
3986
4337
|
}
|
|
3987
4338
|
function readOpenCodeChatGptCredentials() {
|
|
3988
4339
|
try {
|
|
3989
|
-
const raw = readFileSync3(
|
|
4340
|
+
const raw = readFileSync3(join4(homedir3(), ...OPENCODE_AUTH_SEGMENTS), "utf-8");
|
|
3990
4341
|
let parsed;
|
|
3991
4342
|
try {
|
|
3992
4343
|
parsed = JSON.parse(raw);
|
|
@@ -4212,13 +4563,13 @@ var INTEGRATION_LIST_RETRY_DELAY_MS = 1e3;
|
|
|
4212
4563
|
function isRecord3(value) {
|
|
4213
4564
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4214
4565
|
}
|
|
4215
|
-
var sleep2 = (milliseconds) => new Promise((
|
|
4566
|
+
var sleep2 = (milliseconds) => new Promise((resolve5) => setTimeout(resolve5, milliseconds));
|
|
4216
4567
|
function emptyResult(unsupported = [], outcome = "completed") {
|
|
4217
4568
|
return { outcome, imported: [], alreadyConnected: [], unsupported, failed: [] };
|
|
4218
4569
|
}
|
|
4219
4570
|
function readOpenCodeCredentials(homeDir, log3) {
|
|
4220
4571
|
try {
|
|
4221
|
-
const raw = readFileSync4(
|
|
4572
|
+
const raw = readFileSync4(join5(homeDir, ...OPENCODE_AUTH_SEGMENTS), "utf8");
|
|
4222
4573
|
let parsed;
|
|
4223
4574
|
try {
|
|
4224
4575
|
parsed = JSON.parse(raw);
|
|
@@ -4420,7 +4771,7 @@ async function importOpenCodeV2Credentials({
|
|
|
4420
4771
|
import { spawn as spawn2 } from "node:child_process";
|
|
4421
4772
|
import { mkdirSync, statSync as statSync3, unlinkSync as unlinkSync2, writeFileSync } from "node:fs";
|
|
4422
4773
|
import { homedir as homedir4 } from "node:os";
|
|
4423
|
-
import { dirname as
|
|
4774
|
+
import { dirname as dirname4, resolve as resolvePath } from "node:path";
|
|
4424
4775
|
|
|
4425
4776
|
// src/lib/runner-synchroniser.ts
|
|
4426
4777
|
import { spawn } from "node:child_process";
|
|
@@ -4430,7 +4781,7 @@ function appendError(stderr, error2) {
|
|
|
4430
4781
|
${message}`;
|
|
4431
4782
|
}
|
|
4432
4783
|
function runSynchroniser(args, opts) {
|
|
4433
|
-
return new Promise((
|
|
4784
|
+
return new Promise((resolve5) => {
|
|
4434
4785
|
let child;
|
|
4435
4786
|
let stdout = "";
|
|
4436
4787
|
let stderr = "";
|
|
@@ -4444,7 +4795,7 @@ function runSynchroniser(args, opts) {
|
|
|
4444
4795
|
if (timer.handle) clearTimeout(timer.handle);
|
|
4445
4796
|
if (abortListener) opts.signal?.removeEventListener("abort", abortListener);
|
|
4446
4797
|
if (spawnListener) child.removeListener("spawn", spawnListener);
|
|
4447
|
-
|
|
4798
|
+
resolve5(result);
|
|
4448
4799
|
};
|
|
4449
4800
|
try {
|
|
4450
4801
|
child = spawn("runner-synchroniser", args, {
|
|
@@ -4548,7 +4899,7 @@ function clearMarker(options) {
|
|
|
4548
4899
|
function markNoReplicate(options, message) {
|
|
4549
4900
|
if (options.noReplicateMarker) {
|
|
4550
4901
|
try {
|
|
4551
|
-
mkdirSync(
|
|
4902
|
+
mkdirSync(dirname4(options.noReplicateMarker), { recursive: true });
|
|
4552
4903
|
writeFileSync(options.noReplicateMarker, "");
|
|
4553
4904
|
} catch (error2) {
|
|
4554
4905
|
options.log(
|
|
@@ -4607,7 +4958,7 @@ function parseSynchroniserEnv(stdout) {
|
|
|
4607
4958
|
return values;
|
|
4608
4959
|
}
|
|
4609
4960
|
function runCommand(command, args, options) {
|
|
4610
|
-
return new Promise((
|
|
4961
|
+
return new Promise((resolve5) => {
|
|
4611
4962
|
let child;
|
|
4612
4963
|
let stdout = "";
|
|
4613
4964
|
let stderr = "";
|
|
@@ -4616,7 +4967,7 @@ function runCommand(command, args, options) {
|
|
|
4616
4967
|
if (settled) return;
|
|
4617
4968
|
settled = true;
|
|
4618
4969
|
if (timer) clearTimeout(timer);
|
|
4619
|
-
|
|
4970
|
+
resolve5(result);
|
|
4620
4971
|
};
|
|
4621
4972
|
try {
|
|
4622
4973
|
child = spawn2(command, args, {
|
|
@@ -4624,7 +4975,7 @@ function runCommand(command, args, options) {
|
|
|
4624
4975
|
stdio: ["ignore", "pipe", "pipe"]
|
|
4625
4976
|
});
|
|
4626
4977
|
} catch (error2) {
|
|
4627
|
-
|
|
4978
|
+
resolve5({
|
|
4628
4979
|
code: null,
|
|
4629
4980
|
stdout,
|
|
4630
4981
|
stderr: error2 instanceof Error ? error2.message : String(error2),
|
|
@@ -4703,7 +5054,7 @@ async function ensureLitestreamConfig(options, env) {
|
|
|
4703
5054
|
return null;
|
|
4704
5055
|
}
|
|
4705
5056
|
try {
|
|
4706
|
-
mkdirSync(
|
|
5057
|
+
mkdirSync(dirname4(configPath), { recursive: true });
|
|
4707
5058
|
writeFileSync(configPath, rendered.stdout);
|
|
4708
5059
|
} catch (error2) {
|
|
4709
5060
|
options.log(
|
|
@@ -4970,7 +5321,7 @@ async function restoreAndVerifySessionDb(options) {
|
|
|
4970
5321
|
// src/lib/opencode/session-db-provenance.ts
|
|
4971
5322
|
import { createRequire } from "node:module";
|
|
4972
5323
|
import { mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "node:fs";
|
|
4973
|
-
import { dirname as
|
|
5324
|
+
import { dirname as dirname5, join as join6 } from "node:path";
|
|
4974
5325
|
var require2 = createRequire(import.meta.url);
|
|
4975
5326
|
function readSessionDbMigrationIds(dbPath) {
|
|
4976
5327
|
let db;
|
|
@@ -5007,7 +5358,7 @@ function readSessionDbMigrationIds(dbPath) {
|
|
|
5007
5358
|
}
|
|
5008
5359
|
function sessionDbProvenanceStatePath(homeDir, env) {
|
|
5009
5360
|
const override = env.EVIDENT_SESSION_DB_PROVENANCE_STATE?.trim();
|
|
5010
|
-
return override ||
|
|
5361
|
+
return override || join6(homeDir, ".local", "state", "evident", "session-db-provenance.json");
|
|
5011
5362
|
}
|
|
5012
5363
|
function loadSessionDbProvenanceState(path) {
|
|
5013
5364
|
let value;
|
|
@@ -5036,7 +5387,7 @@ function loadSessionDbProvenanceState(path) {
|
|
|
5036
5387
|
}
|
|
5037
5388
|
function saveSessionDbProvenanceState(path, state) {
|
|
5038
5389
|
try {
|
|
5039
|
-
mkdirSync2(
|
|
5390
|
+
mkdirSync2(dirname5(path), { recursive: true });
|
|
5040
5391
|
writeFileSync2(path, `${JSON.stringify(state, null, 2)}
|
|
5041
5392
|
`, "utf8");
|
|
5042
5393
|
} catch (error2) {
|
|
@@ -5127,7 +5478,7 @@ async function stopProcessAndWait(child, timeoutMs, sendTerm, sendKill) {
|
|
|
5127
5478
|
if (child.exitCode !== null || child.signalCode !== null) {
|
|
5128
5479
|
return { outcome: "exited", code: child.exitCode, signal: child.signalCode };
|
|
5129
5480
|
}
|
|
5130
|
-
return new Promise((
|
|
5481
|
+
return new Promise((resolve5, reject) => {
|
|
5131
5482
|
let forced = false;
|
|
5132
5483
|
let settled = false;
|
|
5133
5484
|
const timer = setTimeout(() => {
|
|
@@ -5147,7 +5498,7 @@ async function stopProcessAndWait(child, timeoutMs, sendTerm, sendKill) {
|
|
|
5147
5498
|
settled = true;
|
|
5148
5499
|
clearTimeout(timer);
|
|
5149
5500
|
child.removeListener("exit", onExit);
|
|
5150
|
-
|
|
5501
|
+
resolve5(result);
|
|
5151
5502
|
};
|
|
5152
5503
|
const fail = (error2) => {
|
|
5153
5504
|
if (settled) return;
|
|
@@ -5623,7 +5974,7 @@ function selectSessionsToDelete(sessions, opts) {
|
|
|
5623
5974
|
);
|
|
5624
5975
|
}
|
|
5625
5976
|
var DEFAULT_INTERVAL = "1h";
|
|
5626
|
-
function
|
|
5977
|
+
function resolve2(flag, envValue, fallback) {
|
|
5627
5978
|
return flag ?? envValue ?? fallback;
|
|
5628
5979
|
}
|
|
5629
5980
|
function parseMaxCount(input) {
|
|
@@ -5639,9 +5990,9 @@ function parseMaxCount(input) {
|
|
|
5639
5990
|
}
|
|
5640
5991
|
function resolveSessionCleanupConfig(flags, env = process.env) {
|
|
5641
5992
|
const warnings = [];
|
|
5642
|
-
const maxAgeRaw =
|
|
5643
|
-
const maxCountRaw =
|
|
5644
|
-
const intervalRaw =
|
|
5993
|
+
const maxAgeRaw = resolve2(flags.maxAge, env.EVIDENT_SESSION_CLEANUP_MAX_AGE);
|
|
5994
|
+
const maxCountRaw = resolve2(flags.maxCount, env.EVIDENT_SESSION_CLEANUP_MAX_COUNT);
|
|
5995
|
+
const intervalRaw = resolve2(
|
|
5645
5996
|
flags.interval,
|
|
5646
5997
|
env.EVIDENT_SESSION_CLEANUP_INTERVAL,
|
|
5647
5998
|
DEFAULT_INTERVAL
|
|
@@ -5681,10 +6032,10 @@ function resolveSessionCleanupConfig(flags, env = process.env) {
|
|
|
5681
6032
|
|
|
5682
6033
|
// src/lib/opencode/session-db-size.ts
|
|
5683
6034
|
import { statSync as statSync4 } from "node:fs";
|
|
5684
|
-
import { join as
|
|
6035
|
+
import { join as join7 } from "node:path";
|
|
5685
6036
|
var LARGE_DB_THRESHOLD_BYTES = 268435456;
|
|
5686
6037
|
function statSessionDbBytes(homeDir) {
|
|
5687
|
-
const dbPath =
|
|
6038
|
+
const dbPath = join7(homeDir, ".local", "share", "opencode", "opencode.db");
|
|
5688
6039
|
try {
|
|
5689
6040
|
return statSync4(dbPath).size;
|
|
5690
6041
|
} catch (err) {
|
|
@@ -5714,12 +6065,12 @@ function buildSessionStoreSizeWarning(input) {
|
|
|
5714
6065
|
// src/lib/opencode/log-tail.ts
|
|
5715
6066
|
import { statSync as statSync5 } from "node:fs";
|
|
5716
6067
|
import { homedir as homedir5 } from "node:os";
|
|
5717
|
-
import { join as
|
|
6068
|
+
import { join as join8 } from "node:path";
|
|
5718
6069
|
import { open as open2, stat } from "node:fs/promises";
|
|
5719
6070
|
var DEFAULT_POLL_INTERVAL_MS = 1e3;
|
|
5720
6071
|
function resolveOpenCodeLogPath(homeDir = homedir5(), env = process.env) {
|
|
5721
|
-
const dataDir = env.XDG_DATA_HOME ||
|
|
5722
|
-
return
|
|
6072
|
+
const dataDir = env.XDG_DATA_HOME || join8(homeDir, ".local", "share");
|
|
6073
|
+
return join8(dataDir, "opencode", "log", "opencode.log");
|
|
5723
6074
|
}
|
|
5724
6075
|
function isEnoent(error2) {
|
|
5725
6076
|
return error2?.code === "ENOENT";
|
|
@@ -5874,8 +6225,8 @@ var StreamForwarder = class {
|
|
|
5874
6225
|
pauseStream(sid, stream) {
|
|
5875
6226
|
if (!stream.paused) {
|
|
5876
6227
|
stream.paused = true;
|
|
5877
|
-
stream.pausePromise = new Promise((
|
|
5878
|
-
stream.resume =
|
|
6228
|
+
stream.pausePromise = new Promise((resolve5) => {
|
|
6229
|
+
stream.resume = resolve5;
|
|
5879
6230
|
});
|
|
5880
6231
|
}
|
|
5881
6232
|
if (stream.pauseTimer !== null) clearTimeout(stream.pauseTimer);
|
|
@@ -5942,12 +6293,12 @@ var StreamForwarder = class {
|
|
|
5942
6293
|
let endBody;
|
|
5943
6294
|
if (has_body) {
|
|
5944
6295
|
const chunks = [];
|
|
5945
|
-
bodyPromise = new Promise((
|
|
6296
|
+
bodyPromise = new Promise((resolve5) => {
|
|
5946
6297
|
pushBody = (buf) => {
|
|
5947
6298
|
chunks.push(buf);
|
|
5948
6299
|
};
|
|
5949
6300
|
endBody = () => {
|
|
5950
|
-
|
|
6301
|
+
resolve5(Buffer.concat(chunks));
|
|
5951
6302
|
};
|
|
5952
6303
|
});
|
|
5953
6304
|
}
|
|
@@ -6118,7 +6469,7 @@ function connectTunnel(options) {
|
|
|
6118
6469
|
} = options;
|
|
6119
6470
|
const tunnelUrl = getTunnelUrlConfig();
|
|
6120
6471
|
const url = `${tunnelUrl}/tunnel/${agentId}/connect`;
|
|
6121
|
-
return new Promise((
|
|
6472
|
+
return new Promise((resolve5, reject) => {
|
|
6122
6473
|
const ws = new WebSocket2(url, {
|
|
6123
6474
|
headers: {
|
|
6124
6475
|
Authorization: authHeader,
|
|
@@ -6227,7 +6578,7 @@ function connectTunnel(options) {
|
|
|
6227
6578
|
forwarder.setBinaryFramesSupported(message.binary_frames === true);
|
|
6228
6579
|
const connectedAgentId = message.agent_id ?? agentId;
|
|
6229
6580
|
onConnected?.(connectedAgentId);
|
|
6230
|
-
|
|
6581
|
+
resolve5({
|
|
6231
6582
|
ws,
|
|
6232
6583
|
close: () => ws.close(1e3, "CLI shutdown")
|
|
6233
6584
|
});
|
|
@@ -6782,12 +7133,12 @@ function createSessionStoreHealth(options = {}) {
|
|
|
6782
7133
|
import { homedir as homedir6 } from "node:os";
|
|
6783
7134
|
|
|
6784
7135
|
// src/lib/runner-file-sync.ts
|
|
6785
|
-
import { join as
|
|
7136
|
+
import { join as join10 } from "node:path";
|
|
6786
7137
|
|
|
6787
7138
|
// src/lib/file-push.ts
|
|
6788
7139
|
import { randomUUID } from "node:crypto";
|
|
6789
7140
|
import { chmod, mkdir, open as open3, realpath, rename, unlink } from "node:fs/promises";
|
|
6790
|
-
import { basename, dirname as
|
|
7141
|
+
import { basename as basename2, dirname as dirname6, isAbsolute as isAbsolute2, join as join9, relative as relative2, resolve as resolve3, sep as sep2 } from "node:path";
|
|
6791
7142
|
var FILE_MODE = 384;
|
|
6792
7143
|
var DIRECTORY_MODE = 448;
|
|
6793
7144
|
async function writePushedFile(request2) {
|
|
@@ -6818,9 +7169,9 @@ async function writePushedFile(request2) {
|
|
|
6818
7169
|
}
|
|
6819
7170
|
try {
|
|
6820
7171
|
const { existingAncestor, missingSegments } = await resolveNearestExistingAncestor(
|
|
6821
|
-
|
|
7172
|
+
dirname6(candidate)
|
|
6822
7173
|
);
|
|
6823
|
-
const realTarget =
|
|
7174
|
+
const realTarget = join9(existingAncestor, ...missingSegments, basename2(candidate));
|
|
6824
7175
|
const allowedDirectory = await findContainingAllowedDirectory(allowedDirectories, realTarget);
|
|
6825
7176
|
if (allowedDirectory === null) {
|
|
6826
7177
|
return refuse("path_not_allowed", "The runner does not allow writing to that location.", {
|
|
@@ -6830,8 +7181,8 @@ async function writePushedFile(request2) {
|
|
|
6830
7181
|
}
|
|
6831
7182
|
if (missingSegments.length > 0) {
|
|
6832
7183
|
await createMissingDirectories(existingAncestor, missingSegments);
|
|
6833
|
-
const realParent = await realpath(
|
|
6834
|
-
if (realParent !==
|
|
7184
|
+
const realParent = await realpath(dirname6(realTarget));
|
|
7185
|
+
if (realParent !== dirname6(realTarget) || !contains(allowedDirectory, realTarget)) {
|
|
6835
7186
|
return refuse("path_not_allowed", "The runner does not allow writing to that location.", {
|
|
6836
7187
|
path: realTarget,
|
|
6837
7188
|
bytes,
|
|
@@ -6856,15 +7207,15 @@ function expandAndValidate(requestedPath, homeDir) {
|
|
|
6856
7207
|
if (requestedPath.trim() === "" || requestedPath.includes("\0")) {
|
|
6857
7208
|
return null;
|
|
6858
7209
|
}
|
|
6859
|
-
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ?
|
|
7210
|
+
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join9(homeDir, requestedPath.slice(2)) : requestedPath;
|
|
6860
7211
|
if (expanded.split(/[/\\]/).includes("..")) {
|
|
6861
7212
|
return null;
|
|
6862
7213
|
}
|
|
6863
|
-
if (!
|
|
7214
|
+
if (!isAbsolute2(expanded)) {
|
|
6864
7215
|
return null;
|
|
6865
7216
|
}
|
|
6866
|
-
const candidate =
|
|
6867
|
-
const name =
|
|
7217
|
+
const candidate = resolve3(expanded);
|
|
7218
|
+
const name = basename2(candidate);
|
|
6868
7219
|
return name === "" || name === "." || name === ".." ? null : candidate;
|
|
6869
7220
|
}
|
|
6870
7221
|
async function resolveNearestExistingAncestor(directory) {
|
|
@@ -6874,18 +7225,18 @@ async function resolveNearestExistingAncestor(directory) {
|
|
|
6874
7225
|
try {
|
|
6875
7226
|
return { existingAncestor: await realpath(current), missingSegments };
|
|
6876
7227
|
} catch (err) {
|
|
6877
|
-
const parent =
|
|
7228
|
+
const parent = dirname6(current);
|
|
6878
7229
|
if (err.code !== "ENOENT" || parent === current) {
|
|
6879
7230
|
throw err;
|
|
6880
7231
|
}
|
|
6881
|
-
missingSegments.unshift(
|
|
7232
|
+
missingSegments.unshift(basename2(current));
|
|
6882
7233
|
current = parent;
|
|
6883
7234
|
}
|
|
6884
7235
|
}
|
|
6885
7236
|
}
|
|
6886
7237
|
async function findContainingAllowedDirectory(allowedDirectories, realTarget) {
|
|
6887
7238
|
for (const directory of allowedDirectories) {
|
|
6888
|
-
if (!
|
|
7239
|
+
if (!isAbsolute2(directory)) {
|
|
6889
7240
|
log("warn", "file_push_allowed_directory_skipped", { directory, reason: "not_absolute" });
|
|
6890
7241
|
continue;
|
|
6891
7242
|
}
|
|
@@ -6923,19 +7274,19 @@ async function realpathCreatingIfMissing(directory) {
|
|
|
6923
7274
|
}
|
|
6924
7275
|
}
|
|
6925
7276
|
function contains(realDirectory, realTarget) {
|
|
6926
|
-
const rel =
|
|
6927
|
-
return rel !== "" && rel !== ".." && !rel.startsWith(`..${
|
|
7277
|
+
const rel = relative2(realDirectory, realTarget);
|
|
7278
|
+
return rel !== "" && rel !== ".." && !rel.startsWith(`..${sep2}`) && !isAbsolute2(rel);
|
|
6928
7279
|
}
|
|
6929
7280
|
async function createMissingDirectories(existingAncestor, missingSegments) {
|
|
6930
7281
|
let current = existingAncestor;
|
|
6931
7282
|
for (const segment of missingSegments) {
|
|
6932
|
-
current =
|
|
7283
|
+
current = join9(current, segment);
|
|
6933
7284
|
await mkdir(current, { recursive: true, mode: DIRECTORY_MODE });
|
|
6934
7285
|
await chmod(current, DIRECTORY_MODE);
|
|
6935
7286
|
}
|
|
6936
7287
|
}
|
|
6937
7288
|
async function writeAtomically(realTarget, content) {
|
|
6938
|
-
const temporaryPath =
|
|
7289
|
+
const temporaryPath = join9(dirname6(realTarget), `.evident-push-${randomUUID()}.tmp`);
|
|
6939
7290
|
let handle;
|
|
6940
7291
|
try {
|
|
6941
7292
|
handle = await open3(temporaryPath, "wx", FILE_MODE);
|
|
@@ -7065,12 +7416,12 @@ var NOT_APPLIED = {
|
|
|
7065
7416
|
opencodeAuthApplied: false
|
|
7066
7417
|
};
|
|
7067
7418
|
function isClaudeCredentialPath(requestedPath, homeDir) {
|
|
7068
|
-
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ?
|
|
7069
|
-
return expanded ===
|
|
7419
|
+
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join10(homeDir, requestedPath.slice(2)) : requestedPath;
|
|
7420
|
+
return expanded === join10(homeDir, ...CLAUDE_CREDENTIALS_SEGMENTS);
|
|
7070
7421
|
}
|
|
7071
7422
|
function isOpenCodeAuthPath(requestedPath, homeDir) {
|
|
7072
|
-
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ?
|
|
7073
|
-
return expanded ===
|
|
7423
|
+
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join10(homeDir, requestedPath.slice(2)) : requestedPath;
|
|
7424
|
+
return expanded === join10(homeDir, ...OPENCODE_AUTH_SEGMENTS);
|
|
7074
7425
|
}
|
|
7075
7426
|
async function applyOne(options, file) {
|
|
7076
7427
|
const label = `${file.id.slice(0, 8)} (${file.path})`;
|
|
@@ -7241,7 +7592,7 @@ var SESSION_ERROR_BUFFER_TTL_MS = SESSION_ERROR_STREAM_HEALTHY_MS;
|
|
|
7241
7592
|
var MAX_BUFFERED_SESSION_ERRORS = 256;
|
|
7242
7593
|
var SESSION_ERROR_RETENTION_TTL_MS = 6e4;
|
|
7243
7594
|
var SESSION_STORE_UNWRITABLE_ERROR = "The runner session database could not be written, so this turn did not finish. Nothing was lost from the queue. Clear disk space and retry.";
|
|
7244
|
-
var SESSION_ERROR_RETAINED_DETAIL = "
|
|
7595
|
+
var SESSION_ERROR_RETAINED_DETAIL = "Runner-observed OpenCode session error: ";
|
|
7245
7596
|
var SESSION_ERROR_OBSERVED_CAUSE = "Runner-observed OpenCode session-store cause: ";
|
|
7246
7597
|
var DEFAULT_PAUSED_POLL_INTERVAL_MS = 2e3;
|
|
7247
7598
|
var DEFAULT_PAUSED_MAX_WAIT_MS = 10 * 60 * 1e3;
|
|
@@ -7365,12 +7716,15 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
7365
7716
|
/**
|
|
7366
7717
|
* Per-conversation throttle state for the #183 recurrence warning (#1618
|
|
7367
7718
|
* WI-4) — see `reportWedgedConversation`'s doc comment for why this exists.
|
|
7368
|
-
* `firstWedgedAt` anchors `stuck_for_ms`; `
|
|
7369
|
-
*
|
|
7370
|
-
*
|
|
7371
|
-
*
|
|
7372
|
-
* conversation dispatches anything
|
|
7373
|
-
*
|
|
7719
|
+
* `firstWedgedAt` anchors `stuck_for_ms`; `warnAnchorAt` is the throttle
|
|
7720
|
+
* anchor from first detection through the first warning, then from each
|
|
7721
|
+
* warning onward; `consecutiveTicks` is reported in the log text so the
|
|
7722
|
+
* operator sees magnitude, not repetition. Cleared the moment the
|
|
7723
|
+
* conversation dispatches anything, or when it drops off the pending list
|
|
7724
|
+
* (`runDrain`'s prune, the dominant real-world path — a wedge normally ends
|
|
7725
|
+
* with its message completing, not a fresh dispatch on the same
|
|
7726
|
+
* conversation) — either way a fresh wedge, if it recurs, is a new
|
|
7727
|
+
* incident. Bounded FIFO, mirroring `supersededSessions`
|
|
7374
7728
|
* (`MAX_WEDGED_CONVERSATIONS`).
|
|
7375
7729
|
*/
|
|
7376
7730
|
wedgeWarnings = /* @__PURE__ */ new Map();
|
|
@@ -7600,6 +7954,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
7600
7954
|
subagentInvocationPrefetches = /* @__PURE__ */ new Map();
|
|
7601
7955
|
/** Serialises drains so a reconnect during a drain doesn't double-process. */
|
|
7602
7956
|
draining = false;
|
|
7957
|
+
pendingProviderRegistryRepair = null;
|
|
7603
7958
|
/**
|
|
7604
7959
|
* Serialises runner-file syncs (#559) so the ~2s poll tick and a concurrent
|
|
7605
7960
|
* drain ping don't download, write and ack the same file twice.
|
|
@@ -7785,8 +8140,33 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
7785
8140
|
async runDrain() {
|
|
7786
8141
|
let dispatched = 0;
|
|
7787
8142
|
try {
|
|
8143
|
+
const pendingRepair = this.pendingProviderRegistryRepair;
|
|
8144
|
+
if (pendingRepair && this.protectedSessionIds().size === 0) {
|
|
8145
|
+
try {
|
|
8146
|
+
await this.issueProviderRegistryReload(
|
|
8147
|
+
pendingRepair.conversationId,
|
|
8148
|
+
pendingRepair.messageId
|
|
8149
|
+
);
|
|
8150
|
+
} catch (err) {
|
|
8151
|
+
this.log({
|
|
8152
|
+
level: "error",
|
|
8153
|
+
message: `Provider registry repair adoption failed for message ${pendingRepair.messageId.slice(0, 8)}: ${err instanceof Error ? err.message : String(err)}`,
|
|
8154
|
+
conversation_id: pendingRepair.conversationId,
|
|
8155
|
+
message_id: pendingRepair.messageId
|
|
8156
|
+
});
|
|
8157
|
+
}
|
|
8158
|
+
return 0;
|
|
8159
|
+
}
|
|
7788
8160
|
this.pendingMessageIds.clear();
|
|
7789
8161
|
const conversations = await this.getPendingConversations();
|
|
8162
|
+
if (this.wedgeWarnings.size > 0) {
|
|
8163
|
+
const pendingConversationIds = new Set(conversations.map(({ id }) => id));
|
|
8164
|
+
for (const conversationId of this.wedgeWarnings.keys()) {
|
|
8165
|
+
if (!pendingConversationIds.has(conversationId)) {
|
|
8166
|
+
this.wedgeWarnings.delete(conversationId);
|
|
8167
|
+
}
|
|
8168
|
+
}
|
|
8169
|
+
}
|
|
7790
8170
|
if (this.recycleRequestedFlag) {
|
|
7791
8171
|
this.stop();
|
|
7792
8172
|
}
|
|
@@ -8323,14 +8703,13 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
8323
8703
|
* skip-because-already-`dispatched`, the exact signature of a message stuck
|
|
8324
8704
|
* acknowledged-but-never-worked.
|
|
8325
8705
|
*
|
|
8326
|
-
*
|
|
8327
|
-
*
|
|
8328
|
-
*
|
|
8329
|
-
*
|
|
8330
|
-
*
|
|
8331
|
-
*
|
|
8332
|
-
*
|
|
8333
|
-
* than repetition.
|
|
8706
|
+
* The first detection starts the clock and emits a local `debug` entry. The
|
|
8707
|
+
* `warn` log and paired `dispatch_wedged` signal fire once the wedge has
|
|
8708
|
+
* lasted `wedgeWarningIntervalMs`, then at most once per interval, naming the
|
|
8709
|
+
* consecutive-tick count so the operator sees magnitude rather than repetition.
|
|
8710
|
+
* A watcher stall that the loop-liveness watchdog recovers is expected and is
|
|
8711
|
+
* already reported by its `watcher_recovered` signal, so it does not reach an
|
|
8712
|
+
* operator through this warning.
|
|
8334
8713
|
*
|
|
8335
8714
|
* Deliberately does NOT trigger a release: WI-1's `reconcileWatchers` runs
|
|
8336
8715
|
* unconditionally on this same tick and is already recovering anything it
|
|
@@ -8342,36 +8721,41 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
8342
8721
|
const existing = this.wedgeWarnings.get(conv.id);
|
|
8343
8722
|
const firstWedgedAt = existing?.firstWedgedAt ?? now;
|
|
8344
8723
|
const consecutiveTicks = (existing?.consecutiveTicks ?? 0) + 1;
|
|
8345
|
-
const
|
|
8346
|
-
|
|
8347
|
-
|
|
8348
|
-
this.
|
|
8349
|
-
|
|
8350
|
-
|
|
8351
|
-
|
|
8724
|
+
const warnAnchorAt = existing?.warnAnchorAt ?? now;
|
|
8725
|
+
const dueForWarn = now - warnAnchorAt >= this.wedgeWarningIntervalMs;
|
|
8726
|
+
if (!existing) {
|
|
8727
|
+
this.log({
|
|
8728
|
+
level: "debug",
|
|
8729
|
+
message: `Detected a possible wedge for conversation ${conv.id.slice(0, 8)} with ${messages.length} pending message(s); it will be reported only if it outlasts the watchdog's recovery.`,
|
|
8730
|
+
conversation_id: conv.id
|
|
8731
|
+
});
|
|
8732
|
+
}
|
|
8733
|
+
let untracked = 0;
|
|
8734
|
+
if (dueForWarn) {
|
|
8735
|
+
const stuckForMs = now - firstWedgedAt;
|
|
8736
|
+
untracked = this.countUntrackedIds(messages);
|
|
8737
|
+
this.log({
|
|
8738
|
+
level: "warn",
|
|
8739
|
+
message: `Conversation ${conv.id.slice(0, 8)} has ${messages.length} pending message(s) but ALL are already marked dispatched locally (in-flight set: ${this.dispatched.size}) \u2014 none sent to OpenCode for ${consecutiveTicks} consecutive tick(s) now (${Math.round(stuckForMs / 1e3)}s stuck). ` + (untracked > 0 ? `${untracked} of these id(s) are tracked by NO watcher \u2014 the dispatched/in-flight pairing invariant is violated for this conversation, which will NOT self-heal and needs a runner restart.` : `A watcher is tracking this work; the loop-liveness watchdog is already recovering it.`),
|
|
8740
|
+
conversation_id: conv.id
|
|
8352
8741
|
});
|
|
8353
|
-
return;
|
|
8354
8742
|
}
|
|
8355
|
-
const stuckForMs = now - firstWedgedAt;
|
|
8356
|
-
const untracked = this.countUntrackedIds(messages);
|
|
8357
|
-
this.log({
|
|
8358
|
-
level: "warn",
|
|
8359
|
-
message: `Conversation ${conv.id.slice(0, 8)} has ${messages.length} pending message(s) but ALL are already marked dispatched locally (in-flight set: ${this.dispatched.size}) \u2014 none sent to OpenCode for ${consecutiveTicks} consecutive tick(s) now (${stuckForMs}ms stuck). ` + (untracked > 0 ? `${untracked} of these id(s) are tracked by NO watcher \u2014 the dispatched/in-flight pairing invariant is violated for this conversation, which will NOT self-heal and needs a runner restart.` : `A watcher is tracking this work; the loop-liveness watchdog is already recovering it.`),
|
|
8360
|
-
conversation_id: conv.id
|
|
8361
|
-
});
|
|
8362
8743
|
this.wedgeWarnings.delete(conv.id);
|
|
8363
|
-
this.wedgeWarnings.set(conv.id, {
|
|
8744
|
+
this.wedgeWarnings.set(conv.id, {
|
|
8745
|
+
firstWedgedAt,
|
|
8746
|
+
warnAnchorAt: dueForWarn ? now : warnAnchorAt,
|
|
8747
|
+
consecutiveTicks
|
|
8748
|
+
});
|
|
8364
8749
|
while (this.wedgeWarnings.size > MAX_WEDGED_CONVERSATIONS) {
|
|
8365
8750
|
const oldest = this.wedgeWarnings.keys().next().value;
|
|
8366
8751
|
if (oldest === void 0) break;
|
|
8367
8752
|
this.wedgeWarnings.delete(oldest);
|
|
8368
8753
|
}
|
|
8369
|
-
if (
|
|
8370
|
-
|
|
8371
|
-
|
|
8372
|
-
|
|
8373
|
-
|
|
8374
|
-
}
|
|
8754
|
+
if (!dueForWarn) return;
|
|
8755
|
+
void this.postSignal(conv.id, messages[0].id, "dispatch_wedged", {
|
|
8756
|
+
stuck_for_ms: now - firstWedgedAt,
|
|
8757
|
+
untracked
|
|
8758
|
+
});
|
|
8375
8759
|
}
|
|
8376
8760
|
/**
|
|
8377
8761
|
* How many of `messages`' ids are tracked by NO watcher's `inFlight` (#1618
|
|
@@ -9637,14 +10021,40 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
9637
10021
|
conversation_id: watcher.conv.id,
|
|
9638
10022
|
message_id: inFlight.evidentMessageId
|
|
9639
10023
|
});
|
|
9640
|
-
|
|
9641
|
-
|
|
9642
|
-
inFlight.
|
|
9643
|
-
|
|
9644
|
-
|
|
9645
|
-
|
|
10024
|
+
const isMissingProvider = isProviderModelNotFoundError(event.reason);
|
|
10025
|
+
if (isMissingProvider) {
|
|
10026
|
+
const dispatchedModel = inFlight.message.opencode_model;
|
|
10027
|
+
const separator = dispatchedModel?.indexOf("/") ?? -1;
|
|
10028
|
+
const hasProviderAndModel = dispatchedModel !== null && separator > 0 && separator < dispatchedModel.length - 1;
|
|
10029
|
+
await this.markFailed(
|
|
10030
|
+
watcher.conv.id,
|
|
10031
|
+
inFlight.evidentMessageId,
|
|
10032
|
+
event.sessionId,
|
|
10033
|
+
`OpenCode could not run this turn: ${event.reason}`,
|
|
10034
|
+
void 0,
|
|
10035
|
+
{
|
|
10036
|
+
classification: {
|
|
10037
|
+
kind: "model_auth",
|
|
10038
|
+
providerId: hasProviderAndModel ? dispatchedModel.slice(0, separator) : null,
|
|
10039
|
+
modelId: hasProviderAndModel ? dispatchedModel.slice(separator + 1) : null,
|
|
10040
|
+
reason: "missing"
|
|
10041
|
+
},
|
|
10042
|
+
errorShape: { name: null, statusCode: null }
|
|
10043
|
+
}
|
|
10044
|
+
);
|
|
10045
|
+
} else {
|
|
10046
|
+
await this.markFailed(
|
|
10047
|
+
watcher.conv.id,
|
|
10048
|
+
inFlight.evidentMessageId,
|
|
10049
|
+
event.sessionId,
|
|
10050
|
+
`OpenCode could not run this turn: ${event.reason}`
|
|
10051
|
+
);
|
|
10052
|
+
}
|
|
9646
10053
|
inFlight.done = true;
|
|
9647
10054
|
this.removeInFlight(watcher, inFlight.evidentMessageId);
|
|
10055
|
+
if (isMissingProvider) {
|
|
10056
|
+
await this.repairProviderRegistry(watcher.conv.id, inFlight.evidentMessageId);
|
|
10057
|
+
}
|
|
9648
10058
|
} catch (err) {
|
|
9649
10059
|
if (err instanceof ChannelAuthError) {
|
|
9650
10060
|
this.log({
|
|
@@ -9665,6 +10075,44 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
9665
10075
|
this.sessionErrorHandled.delete(inFlight.evidentMessageId);
|
|
9666
10076
|
}
|
|
9667
10077
|
}
|
|
10078
|
+
async repairProviderRegistry(conversationId, messageId) {
|
|
10079
|
+
if (this.isV2) {
|
|
10080
|
+
void this.postSignal(conversationId, messageId, "provider_registry_reload_unsupported");
|
|
10081
|
+
return;
|
|
10082
|
+
}
|
|
10083
|
+
const activeSessions = this.protectedSessionIds().size;
|
|
10084
|
+
if (activeSessions > 0 || this.draining) {
|
|
10085
|
+
this.pendingProviderRegistryRepair = { conversationId, messageId };
|
|
10086
|
+
void this.postSignal(conversationId, messageId, "provider_registry_repair_deferred", {
|
|
10087
|
+
active_sessions: activeSessions
|
|
10088
|
+
});
|
|
10089
|
+
this.log({
|
|
10090
|
+
level: "warn",
|
|
10091
|
+
message: `Provider registry repair deferred until a lifecycle-safe point; ${activeSessions} active session(s) protected`,
|
|
10092
|
+
conversation_id: conversationId,
|
|
10093
|
+
message_id: messageId
|
|
10094
|
+
});
|
|
10095
|
+
return;
|
|
10096
|
+
}
|
|
10097
|
+
await this.issueProviderRegistryReload(conversationId, messageId);
|
|
10098
|
+
}
|
|
10099
|
+
async issueProviderRegistryReload(conversationId, messageId) {
|
|
10100
|
+
this.pendingProviderRegistryRepair = null;
|
|
10101
|
+
const result = await disposeAndReloadProviderConfig(this.port, this.openCodeClient);
|
|
10102
|
+
if (result.outcome === "failed") {
|
|
10103
|
+
const failure = result.status === null ? `transport failure${result.detail ? `: ${result.detail}` : ""}` : `HTTP ${result.status}`;
|
|
10104
|
+
this.log({
|
|
10105
|
+
level: "warn",
|
|
10106
|
+
message: `Provider registry reload failed (${failure})`,
|
|
10107
|
+
conversation_id: conversationId,
|
|
10108
|
+
message_id: messageId
|
|
10109
|
+
});
|
|
10110
|
+
}
|
|
10111
|
+
const repairOutcome = result.outcome === "reloaded" ? "reloaded" : "reload_failed";
|
|
10112
|
+
void this.postSignal(conversationId, messageId, "provider_registry_reload_issued", {
|
|
10113
|
+
repair_outcome: repairOutcome
|
|
10114
|
+
});
|
|
10115
|
+
}
|
|
9668
10116
|
/**
|
|
9669
10117
|
* The per-session polling loop (WI-3). Once per tick it:
|
|
9670
10118
|
* 1. polls `GET /session/:id/message` once and, per in-flight message,
|
|
@@ -9856,7 +10304,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
9856
10304
|
if (!inFlight.done) {
|
|
9857
10305
|
const messageErrorText = messageError(messages, inFlight.opencodeMessageId);
|
|
9858
10306
|
const retainedReason = this.readRetainedSessionError(sessionId);
|
|
9859
|
-
const retainedError = retainedReason === null ? null : `${SESSION_ERROR_RETAINED_DETAIL}${retainedReason}`;
|
|
10307
|
+
const retainedError = retainedReason === null || messageErrorText !== null && messageErrorText.includes(retainedReason) ? null : `${SESSION_ERROR_RETAINED_DETAIL}${retainedReason}`;
|
|
9860
10308
|
const observedCause = this.sessionStoreHealth?.readCause(sessionId);
|
|
9861
10309
|
const causeError = observedCause === null || observedCause === void 0 ? null : `${SESSION_ERROR_OBSERVED_CAUSE}${observedCause}`;
|
|
9862
10310
|
const storeError = this.sessionStoreHealth?.isUnwritable() ? SESSION_STORE_UNWRITABLE_ERROR : null;
|
|
@@ -10352,7 +10800,8 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
10352
10800
|
usage,
|
|
10353
10801
|
void 0,
|
|
10354
10802
|
usageAgentName,
|
|
10355
|
-
subagentInvocations
|
|
10803
|
+
subagentInvocations,
|
|
10804
|
+
true
|
|
10356
10805
|
);
|
|
10357
10806
|
} catch (err) {
|
|
10358
10807
|
if (err instanceof ChannelAuthError) throw err;
|
|
@@ -11565,14 +12014,14 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
11565
12014
|
const fetchChildWithoutBlocking = async (sessionId) => {
|
|
11566
12015
|
const pending = fetchChildMessages(sessionId);
|
|
11567
12016
|
let timer;
|
|
11568
|
-
const timeout = new Promise((
|
|
12017
|
+
const timeout = new Promise((resolve5) => {
|
|
11569
12018
|
timer = setTimeout(() => {
|
|
11570
12019
|
this.log({
|
|
11571
12020
|
level: "warn",
|
|
11572
12021
|
message: `Best-effort subagent session fetch for message ${messageId.slice(0, 8)} and child session ${sessionId.slice(0, 8)} was slow \u2014 omitting invocation telemetry without delaying completion`,
|
|
11573
12022
|
message_id: messageId
|
|
11574
12023
|
});
|
|
11575
|
-
|
|
12024
|
+
resolve5(null);
|
|
11576
12025
|
}, SUBAGENT_TELEMETRY_FETCH_TIMEOUT_MS);
|
|
11577
12026
|
});
|
|
11578
12027
|
try {
|
|
@@ -12198,7 +12647,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
12198
12647
|
* exists but is wedged, so the next attempt must get a fresh one
|
|
12199
12648
|
* instead of reusing it — see WI-1's server-side null-clearing PATCH).
|
|
12200
12649
|
*/
|
|
12201
|
-
async markFailed(conversationId, messageId, sessionId, error2, usage, failure, usageAgentName, subagentInvocations) {
|
|
12650
|
+
async markFailed(conversationId, messageId, sessionId, error2, usage, failure, usageAgentName, subagentInvocations, abandonedWithoutAnswer) {
|
|
12202
12651
|
const body = { status: "failed" };
|
|
12203
12652
|
if (sessionId === null) {
|
|
12204
12653
|
body.opencode_session_id = null;
|
|
@@ -12211,12 +12660,19 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
12211
12660
|
if (subagentInvocations && subagentInvocations.length > 0) {
|
|
12212
12661
|
body.subagent_invocations = subagentInvocations;
|
|
12213
12662
|
}
|
|
12214
|
-
if (failure) {
|
|
12215
|
-
body.
|
|
12216
|
-
body.failure_provider_id = failure.providerId;
|
|
12217
|
-
body.failure_model_id = failure.modelId;
|
|
12218
|
-
body.failure_reason = failure.reason;
|
|
12663
|
+
if (failure && failure.errorShape.name !== null) {
|
|
12664
|
+
body.failure_error_name = failure.errorShape.name;
|
|
12219
12665
|
}
|
|
12666
|
+
if (failure && failure.errorShape.statusCode !== null) {
|
|
12667
|
+
body.failure_status_code = failure.errorShape.statusCode;
|
|
12668
|
+
}
|
|
12669
|
+
if (failure?.classification) {
|
|
12670
|
+
body.failure_kind = failure.classification.kind;
|
|
12671
|
+
body.failure_provider_id = failure.classification.providerId;
|
|
12672
|
+
body.failure_model_id = failure.classification.modelId;
|
|
12673
|
+
body.failure_reason = failure.classification.reason;
|
|
12674
|
+
}
|
|
12675
|
+
if (abandonedWithoutAnswer) body.abandoned_without_answer = true;
|
|
12220
12676
|
try {
|
|
12221
12677
|
await this.callWithRetry(
|
|
12222
12678
|
"marking message as failed",
|
|
@@ -12236,27 +12692,28 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
12236
12692
|
this.clearSubagentInvocationCaches(messageId);
|
|
12237
12693
|
}
|
|
12238
12694
|
/**
|
|
12239
|
-
* Classify an errored turn
|
|
12695
|
+
* Classify an errored turn and extract its bounded error shape for `markFailed`.
|
|
12240
12696
|
*
|
|
12241
12697
|
* `messageFailure` alone (structured OpenCode error → `model_auth`) covers
|
|
12242
12698
|
* most cases; when it returns `null` on this ALREADY-FAILED turn, fall back
|
|
12243
12699
|
* to the P1-2b zero-provider check — one extra loopback call to
|
|
12244
12700
|
* `hasAnyConfiguredProvider`, only reached when the structured classifier
|
|
12245
12701
|
* couldn't place it. Fails open (never throws): a fallback probe failure
|
|
12246
|
-
* (`null`/indeterminate) leaves the classification `null
|
|
12247
|
-
*
|
|
12702
|
+
* (`null`/indeterminate) leaves the classification `null`; the raw error shape
|
|
12703
|
+
* is still retained for diagnostics.
|
|
12248
12704
|
*/
|
|
12249
12705
|
async classifyModelAuthFailure(messages, userMessageId) {
|
|
12250
|
-
const classified = messageFailure(messages, userMessageId);
|
|
12251
|
-
if (classified != null) return classified;
|
|
12252
12706
|
const reply = findLastAssistantReplyFor(messages, userMessageId);
|
|
12253
|
-
const
|
|
12254
|
-
|
|
12707
|
+
const errorShape = extractReplyErrorShape(reply);
|
|
12708
|
+
const classified = messageFailure(messages, userMessageId);
|
|
12709
|
+
if (classified != null) return { classification: classified, errorShape };
|
|
12710
|
+
const classification = applyZeroProviderFallback(
|
|
12255
12711
|
classified,
|
|
12256
|
-
|
|
12712
|
+
await this.hasAnyConfiguredProvider(),
|
|
12257
12713
|
reply?.info?.providerID ?? null,
|
|
12258
12714
|
reply?.info?.modelID ?? null
|
|
12259
12715
|
);
|
|
12716
|
+
return { classification, errorShape };
|
|
12260
12717
|
}
|
|
12261
12718
|
async recordSubagentModelAuthFailure(failure, conversationId, messageId) {
|
|
12262
12719
|
const providerId = failure.providerId ?? "(unknown)";
|
|
@@ -12814,7 +13271,7 @@ function commandError2(result) {
|
|
|
12814
13271
|
return result.stderr.trim() || `process exited with code ${result.code ?? "null"}`;
|
|
12815
13272
|
}
|
|
12816
13273
|
var runCommand2 = (command, args, opts) => {
|
|
12817
|
-
return new Promise((
|
|
13274
|
+
return new Promise((resolve5) => {
|
|
12818
13275
|
let child;
|
|
12819
13276
|
let stdout = "";
|
|
12820
13277
|
let stderr = "";
|
|
@@ -12824,7 +13281,7 @@ var runCommand2 = (command, args, opts) => {
|
|
|
12824
13281
|
if (settled) return;
|
|
12825
13282
|
settled = true;
|
|
12826
13283
|
if (timer.handle) clearTimeout(timer.handle);
|
|
12827
|
-
|
|
13284
|
+
resolve5(result);
|
|
12828
13285
|
};
|
|
12829
13286
|
try {
|
|
12830
13287
|
child = spawn5(command, args, { env: opts.env, stdio: ["ignore", "pipe", "pipe"] });
|
|
@@ -13080,7 +13537,7 @@ async function configureGitHubAccess({ env, log: log3 }) {
|
|
|
13080
13537
|
// src/lib/opencode/config-overlay.ts
|
|
13081
13538
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
13082
13539
|
import { copyFileSync, existsSync as existsSync2, statSync as statSync6 } from "node:fs";
|
|
13083
|
-
import { isAbsolute as
|
|
13540
|
+
import { isAbsolute as isAbsolute3, join as join11, resolve as resolve4 } from "node:path";
|
|
13084
13541
|
function isFile(filePath) {
|
|
13085
13542
|
return existsSync2(filePath) && statSync6(filePath).isFile();
|
|
13086
13543
|
}
|
|
@@ -13093,8 +13550,8 @@ function applyRunnerOpenCodeConfig({
|
|
|
13093
13550
|
log3("runner OpenCode config is not configured; using the baked project config", "debug");
|
|
13094
13551
|
return;
|
|
13095
13552
|
}
|
|
13096
|
-
const source =
|
|
13097
|
-
const target = isFile(
|
|
13553
|
+
const source = isAbsolute3(overlayPath) ? overlayPath : resolve4(cwd, overlayPath);
|
|
13554
|
+
const target = isFile(join11(cwd, "opencode.jsonc")) ? "opencode.jsonc" : "opencode.json";
|
|
13098
13555
|
if (!isFile(source)) {
|
|
13099
13556
|
log3(
|
|
13100
13557
|
`RUNNER-OPENCODE-CONFIG-MISSING: ${source} is not a file; headless turns will wedge on the first external-directory permission prompt (#563)`,
|
|
@@ -13102,7 +13559,7 @@ function applyRunnerOpenCodeConfig({
|
|
|
13102
13559
|
);
|
|
13103
13560
|
return;
|
|
13104
13561
|
}
|
|
13105
|
-
copyFileSync(source,
|
|
13562
|
+
copyFileSync(source, join11(cwd, target));
|
|
13106
13563
|
try {
|
|
13107
13564
|
execFileSync2("git", ["-C", cwd, "update-index", "--skip-worktree", target], {
|
|
13108
13565
|
stdio: "ignore"
|
|
@@ -13111,7 +13568,7 @@ function applyRunnerOpenCodeConfig({
|
|
|
13111
13568
|
const detail = error2 instanceof Error ? error2.message : String(error2);
|
|
13112
13569
|
log3(`could not mark ${target} skip-worktree: ${detail}; it may show as a local change`, "warn");
|
|
13113
13570
|
}
|
|
13114
|
-
log3(`Applied runner OpenCode config ${source} to ${
|
|
13571
|
+
log3(`Applied runner OpenCode config ${source} to ${join11(cwd, target)}`);
|
|
13115
13572
|
}
|
|
13116
13573
|
|
|
13117
13574
|
// src/lib/credential-sync.ts
|
|
@@ -13129,14 +13586,14 @@ function errorMessage2(error2) {
|
|
|
13129
13586
|
return error2 instanceof Error ? error2.message : String(error2);
|
|
13130
13587
|
}
|
|
13131
13588
|
function waitForSettlement(promise, timeoutMs) {
|
|
13132
|
-
return new Promise((
|
|
13589
|
+
return new Promise((resolve5) => {
|
|
13133
13590
|
let settled = false;
|
|
13134
13591
|
const timer = setTimeout(() => finish(false), Math.max(0, timeoutMs));
|
|
13135
13592
|
const finish = (value) => {
|
|
13136
13593
|
if (settled) return;
|
|
13137
13594
|
settled = true;
|
|
13138
13595
|
clearTimeout(timer);
|
|
13139
|
-
|
|
13596
|
+
resolve5(value);
|
|
13140
13597
|
};
|
|
13141
13598
|
promise.then(
|
|
13142
13599
|
() => finish(true),
|
|
@@ -13353,6 +13810,8 @@ function createCredentialSync({
|
|
|
13353
13810
|
var MAX_ACTIVITY_LOG_ENTRIES = 10;
|
|
13354
13811
|
var MAX_DISCONNECT_REASON_LENGTH = 200;
|
|
13355
13812
|
var CHANNEL_POLL_INTERVAL_MS = Number(process.env.EVIDENT_CHANNEL_POLL_INTERVAL_MS) || 2e3;
|
|
13813
|
+
var CREDENTIAL_ADOPTION_PROBE_ATTEMPTS = 3;
|
|
13814
|
+
var CREDENTIAL_ADOPTION_PROBE_INTERVAL_MS = 1e3;
|
|
13356
13815
|
var CHANNEL_STUCK_QUEUED_MS = Number(process.env.EVIDENT_STUCK_QUEUED_MS) || void 0;
|
|
13357
13816
|
var SHUTDOWN_DRAIN_TIMEOUT_MS = Number(process.env.EVIDENT_SHUTDOWN_DRAIN_MS) || 25e3;
|
|
13358
13817
|
var TELEMETRY_SHUTDOWN_TIMEOUT_MS = 5e3;
|
|
@@ -13387,8 +13846,8 @@ function resolveFileSyncDirectories(raw, homeDir) {
|
|
|
13387
13846
|
if (trimmed === "") {
|
|
13388
13847
|
throw new Error("--enable-file-sync-to requires a directory path (got an empty value)");
|
|
13389
13848
|
}
|
|
13390
|
-
const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ?
|
|
13391
|
-
if (!
|
|
13849
|
+
const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ? join12(homeDir, trimmed.slice(2)) : trimmed;
|
|
13850
|
+
if (!isAbsolute4(expanded)) {
|
|
13392
13851
|
throw new Error(`--enable-file-sync-to requires an absolute directory path; got "${entry}"`);
|
|
13393
13852
|
}
|
|
13394
13853
|
const normalized = resolvePath2(expanded);
|
|
@@ -13488,8 +13947,7 @@ function resolveMaxActiveSessions(options, env = process.env) {
|
|
|
13488
13947
|
function meetsThreshold(state, level) {
|
|
13489
13948
|
return LOG_LEVELS[level] >= LOG_LEVELS[state.logLevel];
|
|
13490
13949
|
}
|
|
13491
|
-
function
|
|
13492
|
-
if (!meetsThreshold(state, level)) return;
|
|
13950
|
+
function writeConsoleLog(state, message, level) {
|
|
13493
13951
|
if (state.json) {
|
|
13494
13952
|
console.log(
|
|
13495
13953
|
JSON.stringify({
|
|
@@ -13498,10 +13956,18 @@ function log2(state, message, level = "info") {
|
|
|
13498
13956
|
message
|
|
13499
13957
|
})
|
|
13500
13958
|
);
|
|
13501
|
-
|
|
13502
|
-
const prefix = level === "error" ? chalk7.red("\u2717") : level === "warn" ? chalk7.yellow("!") : level === "debug" ? chalk7.dim("\xB7") : chalk7.green("\u2022");
|
|
13503
|
-
console.log(`${prefix} ${message}`);
|
|
13959
|
+
return;
|
|
13504
13960
|
}
|
|
13961
|
+
const prefix = level === "error" ? chalk7.red("\u2717") : level === "warn" ? chalk7.yellow("!") : level === "debug" ? chalk7.dim("\xB7") : chalk7.green("\u2022");
|
|
13962
|
+
console.log(`${prefix} ${message}`);
|
|
13963
|
+
}
|
|
13964
|
+
function log2(state, message, level = "info") {
|
|
13965
|
+
if (!meetsThreshold(state, level)) return;
|
|
13966
|
+
if (state.json || !state.interactive) writeConsoleLog(state, message, level);
|
|
13967
|
+
}
|
|
13968
|
+
function logLocalOnly(state, message, level = "info") {
|
|
13969
|
+
if (!meetsThreshold(state, level)) return;
|
|
13970
|
+
writeConsoleLog(state, message, level);
|
|
13505
13971
|
}
|
|
13506
13972
|
function logActivity(state, entry) {
|
|
13507
13973
|
const level = entry.level ?? (entry.type === "error" ? "error" : "info");
|
|
@@ -13693,6 +14159,125 @@ async function handleAuthError(state, error2) {
|
|
|
13693
14159
|
return { success: false };
|
|
13694
14160
|
}
|
|
13695
14161
|
}
|
|
14162
|
+
function logCredentialAdoptionActivity(state, activity, level) {
|
|
14163
|
+
const message = (() => {
|
|
14164
|
+
switch (activity.outcome) {
|
|
14165
|
+
case "deferred":
|
|
14166
|
+
return `CREDENTIAL-ADOPTION: deferred generation ${activity.generation} with ${activity.active_sessions} active session(s)`;
|
|
14167
|
+
case "reloaded_ready":
|
|
14168
|
+
return `CREDENTIAL-ADOPTION: adopted generation ${activity.generation} with ${activity.active_sessions} active session(s); readiness probe succeeded after ${activity.probe_attempts} attempt(s)`;
|
|
14169
|
+
case "reloaded_interrupted": {
|
|
14170
|
+
const response = activity.probe_status === null ? "without an HTTP response" : `with HTTP ${activity.probe_status} and ${activity.probe_body_empty ? "an empty" : "a non-empty"} body`;
|
|
14171
|
+
return `CREDENTIAL-ADOPTION: generation ${activity.generation} with ${activity.active_sessions} active session(s); replacement instance answered ${response} after ${activity.probe_attempts} probe attempt(s)`;
|
|
14172
|
+
}
|
|
14173
|
+
case "reload_failed":
|
|
14174
|
+
return `CREDENTIAL-ADOPTION: failed to adopt generation ${activity.generation} with ${activity.active_sessions} active session(s)`;
|
|
14175
|
+
case "skipped_v2":
|
|
14176
|
+
return `CREDENTIAL-ADOPTION: skipped generation ${activity.generation} with ${activity.active_sessions} active session(s) on OpenCode V2`;
|
|
14177
|
+
}
|
|
14178
|
+
})();
|
|
14179
|
+
logActivity(state, {
|
|
14180
|
+
type: level === "error" ? "error" : "info",
|
|
14181
|
+
level,
|
|
14182
|
+
...level === "error" ? { error: message } : { message },
|
|
14183
|
+
metadata: {
|
|
14184
|
+
...activity,
|
|
14185
|
+
opencode_version: state.opencodeVersion,
|
|
14186
|
+
port: state.port
|
|
14187
|
+
}
|
|
14188
|
+
});
|
|
14189
|
+
}
|
|
14190
|
+
async function adoptCredentialGeneration(state, generation, adoption, activeSessions, deferredForMs) {
|
|
14191
|
+
const reload = await disposeAndReloadProviderConfig(
|
|
14192
|
+
state.port,
|
|
14193
|
+
state.opencodeClient ?? void 0
|
|
14194
|
+
);
|
|
14195
|
+
if (reload.outcome === "failed") {
|
|
14196
|
+
logCredentialAdoptionActivity(
|
|
14197
|
+
state,
|
|
14198
|
+
{
|
|
14199
|
+
outcome: "reload_failed",
|
|
14200
|
+
generation,
|
|
14201
|
+
active_sessions: activeSessions,
|
|
14202
|
+
adoption,
|
|
14203
|
+
deferred_for_ms: deferredForMs,
|
|
14204
|
+
reload_status: reload.status,
|
|
14205
|
+
reload_detail: reload.detail,
|
|
14206
|
+
probe_attempts: null,
|
|
14207
|
+
probe_status: null,
|
|
14208
|
+
probe_body_empty: null,
|
|
14209
|
+
probe_detail: null
|
|
14210
|
+
},
|
|
14211
|
+
"error"
|
|
14212
|
+
);
|
|
14213
|
+
return;
|
|
14214
|
+
}
|
|
14215
|
+
if (reload.outcome === "skipped_v2") {
|
|
14216
|
+
logCredentialAdoptionActivity(
|
|
14217
|
+
state,
|
|
14218
|
+
{
|
|
14219
|
+
outcome: "skipped_v2",
|
|
14220
|
+
generation,
|
|
14221
|
+
active_sessions: activeSessions,
|
|
14222
|
+
adoption,
|
|
14223
|
+
deferred_for_ms: deferredForMs,
|
|
14224
|
+
reload_status: null,
|
|
14225
|
+
reload_detail: null,
|
|
14226
|
+
probe_attempts: null,
|
|
14227
|
+
probe_status: null,
|
|
14228
|
+
probe_body_empty: null,
|
|
14229
|
+
probe_detail: null
|
|
14230
|
+
},
|
|
14231
|
+
"debug"
|
|
14232
|
+
);
|
|
14233
|
+
return;
|
|
14234
|
+
}
|
|
14235
|
+
let lastProbe = null;
|
|
14236
|
+
let attempts = 0;
|
|
14237
|
+
for (attempts = 1; attempts <= CREDENTIAL_ADOPTION_PROBE_ATTEMPTS; attempts++) {
|
|
14238
|
+
lastProbe = await probeOpenCodeRequestReadiness(state.port, state.opencodeClient ?? void 0);
|
|
14239
|
+
if (lastProbe.ok) {
|
|
14240
|
+
logCredentialAdoptionActivity(
|
|
14241
|
+
state,
|
|
14242
|
+
{
|
|
14243
|
+
outcome: "reloaded_ready",
|
|
14244
|
+
generation,
|
|
14245
|
+
active_sessions: activeSessions,
|
|
14246
|
+
adoption,
|
|
14247
|
+
deferred_for_ms: deferredForMs,
|
|
14248
|
+
reload_status: null,
|
|
14249
|
+
reload_detail: null,
|
|
14250
|
+
probe_attempts: attempts,
|
|
14251
|
+
probe_status: lastProbe.status,
|
|
14252
|
+
probe_body_empty: lastProbe.bodyEmpty,
|
|
14253
|
+
probe_detail: lastProbe.detail
|
|
14254
|
+
},
|
|
14255
|
+
"warn"
|
|
14256
|
+
);
|
|
14257
|
+
return;
|
|
14258
|
+
}
|
|
14259
|
+
if (attempts < CREDENTIAL_ADOPTION_PROBE_ATTEMPTS) {
|
|
14260
|
+
await new Promise((resolve5) => setTimeout(resolve5, CREDENTIAL_ADOPTION_PROBE_INTERVAL_MS));
|
|
14261
|
+
}
|
|
14262
|
+
}
|
|
14263
|
+
logCredentialAdoptionActivity(
|
|
14264
|
+
state,
|
|
14265
|
+
{
|
|
14266
|
+
outcome: "reloaded_interrupted",
|
|
14267
|
+
generation,
|
|
14268
|
+
active_sessions: activeSessions,
|
|
14269
|
+
adoption,
|
|
14270
|
+
deferred_for_ms: deferredForMs,
|
|
14271
|
+
reload_status: null,
|
|
14272
|
+
reload_detail: null,
|
|
14273
|
+
probe_attempts: CREDENTIAL_ADOPTION_PROBE_ATTEMPTS,
|
|
14274
|
+
probe_status: lastProbe?.status ?? null,
|
|
14275
|
+
probe_body_empty: lastProbe?.bodyEmpty ?? null,
|
|
14276
|
+
probe_detail: lastProbe?.detail ?? null
|
|
14277
|
+
},
|
|
14278
|
+
"error"
|
|
14279
|
+
);
|
|
14280
|
+
}
|
|
13696
14281
|
async function driveChannels(state, driver) {
|
|
13697
14282
|
let idlePolls = 0;
|
|
13698
14283
|
let idleMs = 0;
|
|
@@ -13702,6 +14287,15 @@ async function driveChannels(state, driver) {
|
|
|
13702
14287
|
let lastSeenAppliedFiles = driver.fileSyncActivity().appliedFiles;
|
|
13703
14288
|
let lastSeenClaudeApplies = driver.fileSyncActivity().claudeCredentialApplies;
|
|
13704
14289
|
let lastSeenOpencodeAuthApplies = driver.fileSyncActivity().opencodeAuthApplyCount;
|
|
14290
|
+
const credentialAdoption = {
|
|
14291
|
+
generation: 0,
|
|
14292
|
+
pendingGeneration: null,
|
|
14293
|
+
pendingSinceMs: null,
|
|
14294
|
+
deferredGeneration: null,
|
|
14295
|
+
adoption: 0
|
|
14296
|
+
};
|
|
14297
|
+
const stayAwakeHolds = () => state.stayAwakeUntilMs !== null && Date.now() < state.stayAwakeUntilMs;
|
|
14298
|
+
let stayAwakeSuppressionLogged = false;
|
|
13705
14299
|
while (state.running) {
|
|
13706
14300
|
const cycleStartedAtMs = performance.now();
|
|
13707
14301
|
let idleThisCycle = false;
|
|
@@ -13743,12 +14337,30 @@ async function driveChannels(state, driver) {
|
|
|
13743
14337
|
lastSeenOpencodeAuthApplies = opencodeAuthApplies;
|
|
13744
14338
|
if (opencodeAuthApplied) state.openaiUsageRearm?.();
|
|
13745
14339
|
if (claudeCredentialApplied || opencodeAuthApplied) {
|
|
13746
|
-
|
|
13747
|
-
|
|
13748
|
-
|
|
13749
|
-
|
|
13750
|
-
|
|
13751
|
-
|
|
14340
|
+
credentialAdoption.generation++;
|
|
14341
|
+
if (state.opencodeApiVersion === "v2") {
|
|
14342
|
+
logCredentialAdoptionActivity(
|
|
14343
|
+
state,
|
|
14344
|
+
{
|
|
14345
|
+
outcome: "skipped_v2",
|
|
14346
|
+
generation: credentialAdoption.generation,
|
|
14347
|
+
active_sessions: driver.protectedSessionIds().size,
|
|
14348
|
+
adoption: null,
|
|
14349
|
+
deferred_for_ms: null,
|
|
14350
|
+
reload_status: null,
|
|
14351
|
+
reload_detail: null,
|
|
14352
|
+
probe_attempts: null,
|
|
14353
|
+
probe_status: null,
|
|
14354
|
+
probe_body_empty: null,
|
|
14355
|
+
probe_detail: null
|
|
14356
|
+
},
|
|
14357
|
+
"debug"
|
|
14358
|
+
);
|
|
14359
|
+
} else {
|
|
14360
|
+
credentialAdoption.pendingGeneration = credentialAdoption.generation;
|
|
14361
|
+
credentialAdoption.pendingSinceMs = Date.now();
|
|
14362
|
+
credentialAdoption.deferredGeneration = null;
|
|
14363
|
+
}
|
|
13752
14364
|
}
|
|
13753
14365
|
if (opencodeAuthApplied && state.opencodeApiVersion === "v2") {
|
|
13754
14366
|
void importOpenCodeV2CredentialsForState(state).catch(
|
|
@@ -13803,7 +14415,50 @@ async function driveChannels(state, driver) {
|
|
|
13803
14415
|
}
|
|
13804
14416
|
}
|
|
13805
14417
|
}
|
|
13806
|
-
|
|
14418
|
+
if (credentialAdoption.pendingGeneration !== null) {
|
|
14419
|
+
const activeSessions = driver.protectedSessionIds();
|
|
14420
|
+
const generation = credentialAdoption.pendingGeneration;
|
|
14421
|
+
if (activeSessions.size > 0) {
|
|
14422
|
+
if (credentialAdoption.deferredGeneration !== generation) {
|
|
14423
|
+
credentialAdoption.deferredGeneration = generation;
|
|
14424
|
+
logCredentialAdoptionActivity(
|
|
14425
|
+
state,
|
|
14426
|
+
{
|
|
14427
|
+
outcome: "deferred",
|
|
14428
|
+
generation,
|
|
14429
|
+
active_sessions: activeSessions.size,
|
|
14430
|
+
adoption: null,
|
|
14431
|
+
deferred_for_ms: null,
|
|
14432
|
+
reload_status: null,
|
|
14433
|
+
reload_detail: null,
|
|
14434
|
+
probe_attempts: null,
|
|
14435
|
+
probe_status: null,
|
|
14436
|
+
probe_body_empty: null,
|
|
14437
|
+
probe_detail: null
|
|
14438
|
+
},
|
|
14439
|
+
"warn"
|
|
14440
|
+
);
|
|
14441
|
+
}
|
|
14442
|
+
} else {
|
|
14443
|
+
const deferredForMs = credentialAdoption.deferredGeneration === generation && credentialAdoption.pendingSinceMs !== null ? Math.max(0, Date.now() - credentialAdoption.pendingSinceMs) : null;
|
|
14444
|
+
credentialAdoption.pendingGeneration = null;
|
|
14445
|
+
credentialAdoption.pendingSinceMs = null;
|
|
14446
|
+
credentialAdoption.adoption++;
|
|
14447
|
+
void adoptCredentialGeneration(
|
|
14448
|
+
state,
|
|
14449
|
+
generation,
|
|
14450
|
+
credentialAdoption.adoption,
|
|
14451
|
+
activeSessions.size,
|
|
14452
|
+
deferredForMs
|
|
14453
|
+
).catch(
|
|
14454
|
+
(error2) => logActivity(state, {
|
|
14455
|
+
type: "error",
|
|
14456
|
+
error: `CREDENTIAL-ADOPTION: adoption failed for generation ${generation} with ${activeSessions.size} active session(s) on port ${state.port}: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
14457
|
+
})
|
|
14458
|
+
);
|
|
14459
|
+
}
|
|
14460
|
+
}
|
|
14461
|
+
await new Promise((resolve5) => setTimeout(resolve5, CHANNEL_POLL_INTERVAL_MS));
|
|
13807
14462
|
const cycleMs = performance.now() - cycleStartedAtMs;
|
|
13808
14463
|
if (idleThisCycle) idleMs += cycleMs;
|
|
13809
14464
|
if (unreachableThisCycle) unreachableMs += cycleMs;
|
|
@@ -13816,11 +14471,20 @@ async function driveChannels(state, driver) {
|
|
|
13816
14471
|
if (state.interactive) displayStatus(state);
|
|
13817
14472
|
break;
|
|
13818
14473
|
}
|
|
13819
|
-
if (state.idleTimeout !== null && idlePolls >= 2 && idleMs > state.idleTimeout * 1e3) {
|
|
14474
|
+
if (state.idleTimeout !== null && idlePolls >= 2 && idleMs > state.idleTimeout * 1e3 && !stayAwakeHolds()) {
|
|
13820
14475
|
logActivity(state, { type: "info", message: "Idle timeout reached" });
|
|
13821
14476
|
if (state.interactive) displayStatus(state);
|
|
13822
14477
|
break;
|
|
13823
14478
|
}
|
|
14479
|
+
if (state.idleTimeout !== null && idlePolls >= 2 && idleMs > state.idleTimeout * 1e3 && stayAwakeHolds()) {
|
|
14480
|
+
if (!stayAwakeSuppressionLogged) {
|
|
14481
|
+
stayAwakeSuppressionLogged = true;
|
|
14482
|
+
logActivity(state, {
|
|
14483
|
+
type: "info",
|
|
14484
|
+
message: "Idle, but staying awake until the requested minimum passes"
|
|
14485
|
+
});
|
|
14486
|
+
}
|
|
14487
|
+
}
|
|
13824
14488
|
}
|
|
13825
14489
|
}
|
|
13826
14490
|
var SESSION_CLEANUP_FIRST_SWEEP_MS = 1e4;
|
|
@@ -13838,7 +14502,7 @@ function shouldWarnForReclaimSkip(reason) {
|
|
|
13838
14502
|
return major > 22 || major === 22 && (minor > 13 || minor === 13 && patch >= 0);
|
|
13839
14503
|
}
|
|
13840
14504
|
function sessionDbPath() {
|
|
13841
|
-
return
|
|
14505
|
+
return join12(homedir7(), ".local", "share", "opencode", "opencode.db");
|
|
13842
14506
|
}
|
|
13843
14507
|
function logSessionCleanupActivity(state, phase, entry) {
|
|
13844
14508
|
logActivity(state, {
|
|
@@ -14149,10 +14813,12 @@ function scheduleUsageReporting(state, params) {
|
|
|
14149
14813
|
let phase = "dormant";
|
|
14150
14814
|
let rearmRequested = false;
|
|
14151
14815
|
const armProbe = () => {
|
|
14816
|
+
if (!state.running) return;
|
|
14152
14817
|
phase = "probe-pending";
|
|
14153
14818
|
params.setTimer(setTimeout(() => void tick(true), params.firstDelayMs()));
|
|
14154
14819
|
};
|
|
14155
14820
|
const scheduleNextTick = () => {
|
|
14821
|
+
if (!state.running) return;
|
|
14156
14822
|
if (rearmRequested) {
|
|
14157
14823
|
rearmRequested = false;
|
|
14158
14824
|
armProbe();
|
|
@@ -14186,6 +14852,7 @@ function scheduleUsageReporting(state, params) {
|
|
|
14186
14852
|
phase = "tick-in-flight";
|
|
14187
14853
|
try {
|
|
14188
14854
|
const usage = await params.fetchUsage();
|
|
14855
|
+
if (!state.running) return;
|
|
14189
14856
|
const result = await params.report(usage);
|
|
14190
14857
|
if (result.ok) {
|
|
14191
14858
|
if (consecutiveFailures > 0) {
|
|
@@ -14324,6 +14991,7 @@ function scheduleResourceUsageReporting(state, options) {
|
|
|
14324
14991
|
message: lowDiskDecision.warning
|
|
14325
14992
|
});
|
|
14326
14993
|
}
|
|
14994
|
+
if (!state.running) return;
|
|
14327
14995
|
const result = await reportResourceUsage(state.agentId, state.authHeader, usage);
|
|
14328
14996
|
if (result.ok) {
|
|
14329
14997
|
if (consecutiveFailures > 0) {
|
|
@@ -14356,7 +15024,9 @@ function scheduleResourceUsageReporting(state, options) {
|
|
|
14356
15024
|
message: `Resource usage reporting failed: ${message}${failureStreakSuffix(consecutiveFailures)}`
|
|
14357
15025
|
});
|
|
14358
15026
|
} finally {
|
|
14359
|
-
state.
|
|
15027
|
+
if (state.running) {
|
|
15028
|
+
state.resourceUsageTimer = setTimeout(() => void tick(), usageReportDelayMs());
|
|
15029
|
+
}
|
|
14360
15030
|
}
|
|
14361
15031
|
};
|
|
14362
15032
|
state.resourceUsageTimer = setTimeout(() => void tick(), firstReportDelayMs());
|
|
@@ -14518,8 +15188,8 @@ async function beginGracefulShutdown(state, trigger) {
|
|
|
14518
15188
|
return true;
|
|
14519
15189
|
}
|
|
14520
15190
|
);
|
|
14521
|
-
const timedOut = new Promise((
|
|
14522
|
-
timer = setTimeout(() =>
|
|
15191
|
+
const timedOut = new Promise((resolve5) => {
|
|
15192
|
+
timer = setTimeout(() => resolve5(false), telemetryBudgetMs);
|
|
14523
15193
|
});
|
|
14524
15194
|
if (!await Promise.race([flushed, timedOut])) {
|
|
14525
15195
|
log2(state, `Telemetry flush exceeded ${telemetryBudgetMs}ms \u2014 exiting anyway`, "warn");
|
|
@@ -14559,6 +15229,8 @@ async function run(options) {
|
|
|
14559
15229
|
port: options.port ?? 4096,
|
|
14560
15230
|
conversationFilter: options.conversation ?? null,
|
|
14561
15231
|
idleTimeout: options.idleTimeout ?? null,
|
|
15232
|
+
stayAwakeSeconds: null,
|
|
15233
|
+
stayAwakeUntilMs: null,
|
|
14562
15234
|
json: options.json ?? false,
|
|
14563
15235
|
interactive,
|
|
14564
15236
|
logLevel,
|
|
@@ -14748,6 +15420,17 @@ async function run(options) {
|
|
|
14748
15420
|
}
|
|
14749
15421
|
spinner?.succeed(`Runner: ${validation.agent.name || state.agentId}`);
|
|
14750
15422
|
state.agentName = validation.agent.name;
|
|
15423
|
+
const requestedStayAwakeSeconds = validation.agent?.wake_stay_awake_seconds;
|
|
15424
|
+
if (typeof requestedStayAwakeSeconds === "number" && Number.isFinite(requestedStayAwakeSeconds) && requestedStayAwakeSeconds > 0) {
|
|
15425
|
+
state.stayAwakeSeconds = requestedStayAwakeSeconds;
|
|
15426
|
+
} else if (requestedStayAwakeSeconds !== void 0 && requestedStayAwakeSeconds !== null) {
|
|
15427
|
+
state.stayAwakeSeconds = null;
|
|
15428
|
+
log2(
|
|
15429
|
+
state,
|
|
15430
|
+
`Ignoring invalid wake_stay_awake_seconds (type: ${typeof requestedStayAwakeSeconds})`,
|
|
15431
|
+
"warn"
|
|
15432
|
+
);
|
|
15433
|
+
}
|
|
14751
15434
|
const microvmId = process.env.MICROVM_ID?.trim();
|
|
14752
15435
|
if (microvmId) {
|
|
14753
15436
|
const reported = await reportMicrovmId(state.agentId, state.authHeader, microvmId);
|
|
@@ -14807,6 +15490,17 @@ async function run(options) {
|
|
|
14807
15490
|
}
|
|
14808
15491
|
if (githubTokenPopulated) await configureGitHubAccess(credentialContext);
|
|
14809
15492
|
state.credentialSync?.arm();
|
|
15493
|
+
const dependencyInstallDiagnosticReporter = createDependencyInstallDiagnosticReporter({
|
|
15494
|
+
context: {
|
|
15495
|
+
homeDir: homedir7(),
|
|
15496
|
+
cwd: process.cwd(),
|
|
15497
|
+
microvmId: process.env.MICROVM_ID?.trim(),
|
|
15498
|
+
euid: process.getuid?.(),
|
|
15499
|
+
egid: process.getgid?.()
|
|
15500
|
+
},
|
|
15501
|
+
emitEvent: (message) => logActivity(state, { type: "info", level: "warn", message }),
|
|
15502
|
+
emitLocal: (message) => logLocalOnly(state, message, "warn")
|
|
15503
|
+
});
|
|
14810
15504
|
const openCodeActivityForwarder = createOpenCodeActivityForwarder(
|
|
14811
15505
|
() => ({
|
|
14812
15506
|
agentId: state.agentId,
|
|
@@ -14817,7 +15511,8 @@ async function run(options) {
|
|
|
14817
15511
|
if (cause !== null && sessionId !== null) {
|
|
14818
15512
|
state.sessionStoreHealth.observeCause(sessionId, cause);
|
|
14819
15513
|
}
|
|
14820
|
-
}
|
|
15514
|
+
},
|
|
15515
|
+
dependencyInstallDiagnosticReporter
|
|
14821
15516
|
);
|
|
14822
15517
|
const openCodeLogTail = tailOpenCodeLogFile(
|
|
14823
15518
|
resolveOpenCodeLogPath(homedir7(), process.env),
|
|
@@ -14979,7 +15674,7 @@ async function run(options) {
|
|
|
14979
15674
|
logActivity(state, { type: "info", level: "warn", message: versionWarning });
|
|
14980
15675
|
}
|
|
14981
15676
|
}
|
|
14982
|
-
await
|
|
15677
|
+
await disposeAndReloadProviderConfig(state.port, state.opencodeClient ?? void 0);
|
|
14983
15678
|
if (state.opencodeApiVersion === "v2" && state.opencodeClient !== null) {
|
|
14984
15679
|
await importOpenCodeV2CredentialsForState(state);
|
|
14985
15680
|
}
|
|
@@ -15150,6 +15845,13 @@ async function run(options) {
|
|
|
15150
15845
|
type: "info",
|
|
15151
15846
|
message: connectionMessage
|
|
15152
15847
|
});
|
|
15848
|
+
if (!isReconnect && state.stayAwakeSeconds !== null && state.stayAwakeUntilMs === null) {
|
|
15849
|
+
state.stayAwakeUntilMs = Date.now() + state.stayAwakeSeconds * 1e3;
|
|
15850
|
+
logActivity(state, {
|
|
15851
|
+
type: "info",
|
|
15852
|
+
message: `Staying awake for at least ${state.stayAwakeSeconds}s as requested by the wake`
|
|
15853
|
+
});
|
|
15854
|
+
}
|
|
15153
15855
|
if (options.tunnelReadyFile) {
|
|
15154
15856
|
const marker = writeTunnelReadyMarker(options.tunnelReadyFile, agentId);
|
|
15155
15857
|
if (marker.ok) {
|
|
@@ -15284,6 +15986,7 @@ async function run(options) {
|
|
|
15284
15986
|
if (error2.message === "Unauthorized") tunnelSpinner?.fail("Unauthorized");
|
|
15285
15987
|
throw error2;
|
|
15286
15988
|
}
|
|
15989
|
+
if (!state.running) return;
|
|
15287
15990
|
scheduleSessionCleanup(state, channelDriver, options);
|
|
15288
15991
|
state.claudeUsageRearm = scheduleClaudeUsageReporting(state, options);
|
|
15289
15992
|
state.openaiUsageRearm = scheduleUsageReporting(state, {
|