@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 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((resolve4) => {
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
- resolve4();
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((resolve4) => setTimeout(resolve4, ms));
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((resolve4) => {
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
- resolve4(data.trim());
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
- resolve4(chunk.toString().trim());
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 isAbsolute3, join as join11, parse, resolve as resolvePath2 } from "node:path";
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
- console[method]("[evident]", JSON.stringify({ level, event, ...fields }));
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.8c3e0f7" : void 0) ?? process.env.npm_package_version ?? "unknown";
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 createOpenCodeActivityForwarder(getContext, onSessionStoreWriteFailure) {
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 flushLine = (line) => {
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 join2 } from "node:path";
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 || join2(homeDir, ".local", "state", "evident", "session-db-recovery.jsonl");
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((resolve4) => setTimeout(resolve4, 1e3));
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((resolve4) => setTimeout(resolve4, 1e3));
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((resolve4) => setTimeout(resolve4, READ_BACK_DELAY_MS));
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 reloadProviderCache(port, client) {
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(port, client, "/config", {
3465
- method: "PATCH",
3466
- headers: { "Content-Type": "application/json" },
3467
- body: JSON.stringify({})
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
- `[reloadProviderCache] PATCH /config returned HTTP ${res.status} (port ${port})`
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
- `[reloadProviderCache] PATCH /config failed (port ${port}): ${err instanceof Error ? err.message : String(err)}`
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 join4 } from "node:path";
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 join3 } from "node:path";
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(join3(homedir3(), ...OPENCODE_AUTH_SEGMENTS), "utf-8");
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((resolve4) => setTimeout(resolve4, milliseconds));
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(join4(homeDir, ...OPENCODE_AUTH_SEGMENTS), "utf8");
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 dirname3, resolve as resolvePath } from "node:path";
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((resolve4) => {
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
- resolve4(result);
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(dirname3(options.noReplicateMarker), { recursive: true });
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((resolve4) => {
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
- resolve4(result);
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
- resolve4({
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(dirname3(configPath), { recursive: true });
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 dirname4, join as join5 } from "node:path";
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 || join5(homeDir, ".local", "state", "evident", "session-db-provenance.json");
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(dirname4(path), { recursive: true });
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((resolve4, reject) => {
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
- resolve4(result);
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 resolve(flag, envValue, fallback) {
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 = resolve(flags.maxAge, env.EVIDENT_SESSION_CLEANUP_MAX_AGE);
5643
- const maxCountRaw = resolve(flags.maxCount, env.EVIDENT_SESSION_CLEANUP_MAX_COUNT);
5644
- const intervalRaw = resolve(
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 join6 } from "node:path";
6035
+ import { join as join7 } from "node:path";
5685
6036
  var LARGE_DB_THRESHOLD_BYTES = 268435456;
5686
6037
  function statSessionDbBytes(homeDir) {
5687
- const dbPath = join6(homeDir, ".local", "share", "opencode", "opencode.db");
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 join7 } from "node:path";
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 || join7(homeDir, ".local", "share");
5722
- return join7(dataDir, "opencode", "log", "opencode.log");
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((resolve4) => {
5878
- stream.resume = resolve4;
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((resolve4) => {
6296
+ bodyPromise = new Promise((resolve5) => {
5946
6297
  pushBody = (buf) => {
5947
6298
  chunks.push(buf);
5948
6299
  };
5949
6300
  endBody = () => {
5950
- resolve4(Buffer.concat(chunks));
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((resolve4, reject) => {
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
- resolve4({
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 join9 } from "node:path";
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 dirname5, isAbsolute, join as join8, relative, resolve as resolve2, sep } from "node:path";
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
- dirname5(candidate)
7172
+ dirname6(candidate)
6822
7173
  );
6823
- const realTarget = join8(existingAncestor, ...missingSegments, basename(candidate));
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(dirname5(realTarget));
6834
- if (realParent !== dirname5(realTarget) || !contains(allowedDirectory, realTarget)) {
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("~/") ? join8(homeDir, requestedPath.slice(2)) : requestedPath;
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 (!isAbsolute(expanded)) {
7214
+ if (!isAbsolute2(expanded)) {
6864
7215
  return null;
6865
7216
  }
6866
- const candidate = resolve2(expanded);
6867
- const name = basename(candidate);
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 = dirname5(current);
7228
+ const parent = dirname6(current);
6878
7229
  if (err.code !== "ENOENT" || parent === current) {
6879
7230
  throw err;
6880
7231
  }
6881
- missingSegments.unshift(basename(current));
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 (!isAbsolute(directory)) {
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 = relative(realDirectory, realTarget);
6927
- return rel !== "" && rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel);
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 = join8(current, segment);
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 = join8(dirname5(realTarget), `.evident-push-${randomUUID()}.tmp`);
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("~/") ? join9(homeDir, requestedPath.slice(2)) : requestedPath;
7069
- return expanded === join9(homeDir, ...CLAUDE_CREDENTIALS_SEGMENTS);
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("~/") ? join9(homeDir, requestedPath.slice(2)) : requestedPath;
7073
- return expanded === join9(homeDir, ...OPENCODE_AUTH_SEGMENTS);
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 = "The runner's session database could not be written, so this turn stopped part-way. Runner-observed session error: ";
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`; `lastWarnedAt` throttles both the
7369
- * log line and the `dispatch_wedged` signal to at most once per
7370
- * `wedgeWarningIntervalMs`; `consecutiveTicks` is reported in the log text
7371
- * so the operator sees magnitude, not repetition. Cleared the moment the
7372
- * conversation dispatches anything (a fresh wedge, if it recurs, is a new
7373
- * incident). Bounded FIFO, mirroring `supersededSessions`
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
- * Unthrottled, this fired every ~6s drain tick for as long as a wedge lasted
8327
- * (52,843 occurrences observed in one incident) — burning the GLOBAL
8328
- * 30-events/60s `runner-activity-telemetry.ts` budget that was itself
8329
- * suppressing the diagnostics needed to debug the wedge. The `warn` log (and
8330
- * the `dispatch_wedged` signal once the wedge has persisted past the same
8331
- * interval) fire at most once per `wedgeWarningIntervalMs` per conversation,
8332
- * naming the consecutive-tick count so the operator sees magnitude rather
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 dueForWarn = !existing || now - existing.lastWarnedAt >= this.wedgeWarningIntervalMs;
8346
- if (!dueForWarn) {
8347
- this.wedgeWarnings.delete(conv.id);
8348
- this.wedgeWarnings.set(conv.id, {
8349
- firstWedgedAt,
8350
- lastWarnedAt: existing.lastWarnedAt,
8351
- consecutiveTicks
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, { firstWedgedAt, lastWarnedAt: now, consecutiveTicks });
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 (stuckForMs >= this.wedgeWarningIntervalMs) {
8370
- void this.postSignal(conv.id, messages[0].id, "dispatch_wedged", {
8371
- stuck_for_ms: stuckForMs,
8372
- untracked
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
- await this.markFailed(
9641
- watcher.conv.id,
9642
- inFlight.evidentMessageId,
9643
- event.sessionId,
9644
- `OpenCode could not run this turn: ${event.reason}`
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((resolve4) => {
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
- resolve4(null);
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.failure_kind = failure.kind;
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 as a model-auth failure (#736 P1-4), for `markFailed`.
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`, which produces
12247
- * today's byte-identical PATCH body via `markFailed`'s `if (failure)` guard.
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 hasProvider = await this.hasAnyConfiguredProvider();
12254
- return applyZeroProviderFallback(
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
- hasProvider,
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((resolve4) => {
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
- resolve4(result);
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 isAbsolute2, join as join10, resolve as resolve3 } from "node:path";
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 = isAbsolute2(overlayPath) ? overlayPath : resolve3(cwd, overlayPath);
13097
- const target = isFile(join10(cwd, "opencode.jsonc")) ? "opencode.jsonc" : "opencode.json";
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, join10(cwd, target));
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 ${join10(cwd, target)}`);
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((resolve4) => {
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
- resolve4(value);
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("~/") ? join11(homeDir, trimmed.slice(2)) : trimmed;
13391
- if (!isAbsolute3(expanded)) {
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 log2(state, message, level = "info") {
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
- } else if (!state.interactive) {
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
- void reloadProviderCache(state.port, state.opencodeClient ?? void 0).catch(
13747
- (error2) => logActivity(state, {
13748
- type: "error",
13749
- error: `OpenCode provider cache reload failed on port ${state.port}: ${error2 instanceof Error ? error2.message : String(error2)}`
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
- await new Promise((resolve4) => setTimeout(resolve4, CHANNEL_POLL_INTERVAL_MS));
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 join11(homedir7(), ".local", "share", "opencode", "opencode.db");
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.resourceUsageTimer = setTimeout(() => void tick(), usageReportDelayMs());
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((resolve4) => {
14522
- timer = setTimeout(() => resolve4(false), telemetryBudgetMs);
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 reloadProviderCache(state.port, state.opencodeClient ?? void 0);
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, {