@evident-ai/cli 3.6.1-dev.8d3e144 → 3.6.1-dev.98128d1

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
@@ -100,6 +100,26 @@ function clearCredentials() {
100
100
  credentials.set("byEndpoint", byEndpoint);
101
101
  hardenCredentialsPermissions();
102
102
  }
103
+ function getManagedInferenceCredential(runnerId) {
104
+ hardenCredentialsPermissions();
105
+ return credentials.get("managedInferenceByEndpoint")?.[credentialsKey()]?.[runnerId];
106
+ }
107
+ function setManagedInferenceCredential(runnerId, credential) {
108
+ const byEndpoint = credentials.get("managedInferenceByEndpoint") ?? {};
109
+ byEndpoint[credentialsKey()] ??= {};
110
+ byEndpoint[credentialsKey()][runnerId] = credential;
111
+ credentials.set("managedInferenceByEndpoint", byEndpoint);
112
+ hardenCredentialsPermissions();
113
+ }
114
+ function clearManagedInferenceCredential(runnerId) {
115
+ const byEndpoint = credentials.get("managedInferenceByEndpoint") ?? {};
116
+ const runners = byEndpoint[credentialsKey()];
117
+ if (!runners || !(runnerId in runners)) return;
118
+ delete runners[runnerId];
119
+ if (Object.keys(runners).length === 0) delete byEndpoint[credentialsKey()];
120
+ credentials.set("managedInferenceByEndpoint", byEndpoint);
121
+ hardenCredentialsPermissions();
122
+ }
103
123
  function clearAllCredentials() {
104
124
  credentials.clear();
105
125
  hardenCredentialsPermissions();
@@ -119,13 +139,13 @@ function getCliName() {
119
139
  // src/lib/api.ts
120
140
  async function request(path, method, body, headers = {}) {
121
141
  const url = `${getApiUrlConfig()}${path}`;
122
- const requestHeaders = {
142
+ const requestHeaders2 = {
123
143
  "Content-Type": "application/json",
124
144
  ...headers
125
145
  };
126
146
  const response = await fetch(url, {
127
147
  method,
128
- headers: requestHeaders,
148
+ headers: requestHeaders2,
129
149
  body: body ? JSON.stringify(body) : void 0
130
150
  });
131
151
  if (!response.ok) {
@@ -1156,9 +1176,9 @@ async function claudeUsage() {
1156
1176
  }
1157
1177
 
1158
1178
  // src/commands/run.ts
1159
- import { chmodSync as chmodSync3, existsSync as existsSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "node:fs";
1160
- import { homedir as homedir7 } from "node:os";
1161
- import { isAbsolute as isAbsolute4, join as join12, parse, resolve as resolvePath2 } from "node:path";
1179
+ import { chmodSync as chmodSync3, existsSync as existsSync3, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "node:fs";
1180
+ import { homedir as homedir8 } from "node:os";
1181
+ import { isAbsolute as isAbsolute4, join as join13, parse, resolve as resolvePath2 } from "node:path";
1162
1182
  import chalk7 from "chalk";
1163
1183
 
1164
1184
  // ../../packages/types/src/agents/index.ts
@@ -1287,6 +1307,102 @@ var TUNNEL_USAGE_REARM_PING_PATH = "/__evident/usage-rearm";
1287
1307
  var MAX_FILE_PUSH_BYTES = 64 * 1024;
1288
1308
  var MAX_FILE_SYNC_DIRECTORIES = 16;
1289
1309
 
1310
+ // ../../packages/types/src/credential-proxy-native-config.ts
1311
+ var EVIDENT_PROXY_TOKEN_REFERENCE = "{env:EVIDENT_PROXY_TOKEN}";
1312
+ var CLAUDE_PROVIDER = {
1313
+ namespace: "evident-claude",
1314
+ name: "Evident Claude credential proxy",
1315
+ npm: "@ai-sdk/anthropic"
1316
+ };
1317
+ var NATIVE_PROVIDERS = {
1318
+ claude: CLAUDE_PROVIDER,
1319
+ anthropic: CLAUDE_PROVIDER,
1320
+ codex: {
1321
+ namespace: "evident-codex",
1322
+ name: "Evident Codex credential proxy",
1323
+ npm: "@ai-sdk/openai"
1324
+ },
1325
+ openai: {
1326
+ namespace: "evident-openai",
1327
+ name: "Evident OpenAI credential proxy",
1328
+ npm: "@ai-sdk/openai"
1329
+ },
1330
+ kimi: {
1331
+ namespace: "evident-kimi",
1332
+ name: "Evident Kimi credential proxy",
1333
+ npm: "@ai-sdk/openai-compatible",
1334
+ sdkName: "evident-kimi"
1335
+ },
1336
+ antigravity: {
1337
+ namespace: "evident-antigravity",
1338
+ name: "Evident Antigravity credential proxy",
1339
+ npm: "@ai-sdk/openai-compatible",
1340
+ sdkName: "evident-antigravity"
1341
+ },
1342
+ xai: {
1343
+ namespace: "evident-xai",
1344
+ name: "Evident xAI credential proxy",
1345
+ npm: "@ai-sdk/openai-compatible",
1346
+ sdkName: "evident-xai"
1347
+ },
1348
+ mistral: {
1349
+ namespace: "evident-mistral",
1350
+ name: "Evident Mistral credential proxy",
1351
+ npm: "@ai-sdk/openai-compatible",
1352
+ sdkName: "evident-mistral"
1353
+ }
1354
+ };
1355
+ var CREDENTIAL_PROXY_NATIVE_NAMESPACES = [
1356
+ ...new Set(Object.values(NATIVE_PROVIDERS).map(({ namespace }) => namespace))
1357
+ ];
1358
+ function credentialProxyInferenceUrl(baseUrl) {
1359
+ const trimmed = baseUrl.trim().replace(/\/+$/, "");
1360
+ return /\/v1$/i.test(trimmed) ? trimmed : `${trimmed}/v1`;
1361
+ }
1362
+ function buildCredentialProxyNativeConfig({
1363
+ inferenceBaseUrl,
1364
+ modelDescriptors
1365
+ }) {
1366
+ const baseURL = credentialProxyInferenceUrl(inferenceBaseUrl);
1367
+ const providers = {};
1368
+ const unsupportedProviders = /* @__PURE__ */ new Set();
1369
+ for (const descriptor of modelDescriptors) {
1370
+ const definition = NATIVE_PROVIDERS[descriptor.provider.trim().toLowerCase()];
1371
+ if (!definition) {
1372
+ unsupportedProviders.add(descriptor.provider.trim() || descriptor.provider);
1373
+ continue;
1374
+ }
1375
+ providers[definition.namespace] ??= definition.npm === "@ai-sdk/anthropic" ? {
1376
+ name: definition.name,
1377
+ npm: definition.npm,
1378
+ options: { baseURL, authToken: EVIDENT_PROXY_TOKEN_REFERENCE },
1379
+ models: {}
1380
+ } : definition.npm === "@ai-sdk/openai" ? {
1381
+ name: definition.name,
1382
+ npm: definition.npm,
1383
+ options: { baseURL, apiKey: EVIDENT_PROXY_TOKEN_REFERENCE },
1384
+ models: {}
1385
+ } : {
1386
+ name: definition.name,
1387
+ npm: definition.npm,
1388
+ options: {
1389
+ name: definition.sdkName,
1390
+ baseURL,
1391
+ apiKey: EVIDENT_PROXY_TOKEN_REFERENCE
1392
+ },
1393
+ models: {}
1394
+ };
1395
+ providers[definition.namespace].models[descriptor.id] = { name: descriptor.id };
1396
+ }
1397
+ return {
1398
+ unsupportedProviders: [...unsupportedProviders],
1399
+ config: {
1400
+ $schema: "https://opencode.ai/config.json",
1401
+ provider: providers
1402
+ }
1403
+ };
1404
+ }
1405
+
1290
1406
  // ../../packages/types/src/logging/index.ts
1291
1407
  var CORRELATION_ID_HEADER = "x-evident-correlation-id";
1292
1408
  var FORWARD_FAILURE_REASON_HEADER = "X-Evident-Failure-Reason";
@@ -1326,7 +1442,7 @@ import ora4 from "ora";
1326
1442
  import { select as select4 } from "@inquirer/prompts";
1327
1443
 
1328
1444
  // src/lib/telemetry.ts
1329
- var CLI_VERSION = (true ? "3.6.1-dev.8d3e144" : void 0) ?? process.env.npm_package_version ?? "unknown";
1445
+ var CLI_VERSION = (true ? "3.6.1-dev.98128d1" : void 0) ?? process.env.npm_package_version ?? "unknown";
1330
1446
  function getCliVersion() {
1331
1447
  return CLI_VERSION;
1332
1448
  }
@@ -1407,7 +1523,7 @@ async function flushEvents() {
1407
1523
  const controller = new AbortController();
1408
1524
  const timeout = setTimeout(() => controller.abort(), FLUSH_TIMEOUT_MS);
1409
1525
  try {
1410
- const request2 = {
1526
+ const request3 = {
1411
1527
  events,
1412
1528
  client_type: "cli",
1413
1529
  client_version: CLI_VERSION
@@ -1418,7 +1534,7 @@ async function flushEvents() {
1418
1534
  "Content-Type": "application/json",
1419
1535
  Authorization: authHeader
1420
1536
  },
1421
- body: JSON.stringify(request2),
1537
+ body: JSON.stringify(request3),
1422
1538
  signal: controller.signal
1423
1539
  });
1424
1540
  if (!response.ok) {
@@ -2075,7 +2191,8 @@ function createDependencyInstallDiagnosticReporter({
2075
2191
  var FORWARDED_LEVELS_BY_SOURCE = {
2076
2192
  "cli.run": /* @__PURE__ */ new Set(["warn", "error"]),
2077
2193
  opencode: /* @__PURE__ */ new Set(["warn", "error"]),
2078
- "cli.session-cleanup": /* @__PURE__ */ new Set(["info", "warn", "error"])
2194
+ "cli.session-cleanup": /* @__PURE__ */ new Set(["info", "warn", "error"]),
2195
+ "cli.runtime-provenance": /* @__PURE__ */ new Set(["info", "warn", "error"])
2079
2196
  };
2080
2197
  var SEVERITY_BY_LEVEL = {
2081
2198
  info: "info",
@@ -2871,6 +2988,58 @@ async function listAllSessions(port, client) {
2871
2988
  }
2872
2989
  return { sessions };
2873
2990
  }
2991
+ var INTERRUPT_NETWORK_CODES = /* @__PURE__ */ new Set([
2992
+ "ABORT_ERR",
2993
+ "EAI_AGAIN",
2994
+ "ECONNREFUSED",
2995
+ "ECONNRESET",
2996
+ "ENETUNREACH",
2997
+ "ENOTFOUND",
2998
+ "ETIMEDOUT",
2999
+ "UND_ERR_CONNECT_TIMEOUT",
3000
+ "UND_ERR_HEADERS_TIMEOUT",
3001
+ "UND_ERR_SOCKET"
3002
+ ]);
3003
+ function interruptFailureDiagnostic(error2) {
3004
+ const message = error2 instanceof Error ? error2.message : "";
3005
+ const httpStatus = /^OpenCode request failed: HTTP ([1-5]\d{2})(?::|$)/.exec(message)?.[1];
3006
+ if (httpStatus) return `HTTP ${httpStatus}`;
3007
+ const candidates = [error2, error2 instanceof Error ? error2.cause : void 0];
3008
+ for (const candidate of candidates) {
3009
+ if (typeof candidate !== "object" || candidate === null || !("code" in candidate)) continue;
3010
+ const code = candidate.code;
3011
+ if (typeof code === "string" && INTERRUPT_NETWORK_CODES.has(code)) {
3012
+ return redactDiagnosticText(`network ${code}`).slice(0, 120);
3013
+ }
3014
+ }
3015
+ const name = error2 instanceof Error && ["AbortError", "Error", "TimeoutError", "TypeError"].includes(error2.name) ? error2.name : "unknown error";
3016
+ return redactDiagnosticText(`request threw ${name}`).slice(0, 120);
3017
+ }
3018
+ async function interruptSession(port, id, client) {
3019
+ if (client?.version === "v2") {
3020
+ try {
3021
+ await interruptV2Session(client, id);
3022
+ return { kind: "confirmed_stopped" };
3023
+ } catch (error2) {
3024
+ return { kind: "not_confirmed", diagnostic: interruptFailureDiagnostic(error2) };
3025
+ }
3026
+ }
3027
+ try {
3028
+ const res = await requestWithClient(
3029
+ port,
3030
+ client,
3031
+ `/session/${id}/abort`,
3032
+ { method: "POST" },
3033
+ { allowStatuses: ALL_HTTP_STATUSES }
3034
+ );
3035
+ if (res.status >= 200 && res.status < 300) {
3036
+ return { kind: "confirmed_stopped", status: res.status };
3037
+ }
3038
+ return { kind: "not_confirmed", status: res.status };
3039
+ } catch (error2) {
3040
+ return { kind: "threw", diagnostic: interruptFailureDiagnostic(error2) };
3041
+ }
3042
+ }
2874
3043
  async function deleteSession(port, id, client) {
2875
3044
  if (client?.version === "v2") return deleteV2Session(client, id);
2876
3045
  try {
@@ -3040,6 +3209,52 @@ async function getModelAttachmentCapability(port, model, client) {
3040
3209
  return null;
3041
3210
  }
3042
3211
  }
3212
+ async function isPinnedProviderLoaded(port, model, client) {
3213
+ if (client?.version === "v2") return null;
3214
+ const { model: baseModel } = splitModelVariant(model);
3215
+ if (!baseModel) return null;
3216
+ const slash = baseModel.indexOf("/");
3217
+ if (slash <= 0) return null;
3218
+ const providerId = baseModel.slice(0, slash);
3219
+ try {
3220
+ const res = await requestWithClient(port, client, "/config/providers");
3221
+ if (!res.ok) {
3222
+ console.error(
3223
+ `[isPinnedProviderLoaded] GET /config/providers returned HTTP ${res.status} (port ${port})`
3224
+ );
3225
+ return null;
3226
+ }
3227
+ const body = await res.json();
3228
+ if (body === null || typeof body !== "object" || Array.isArray(body)) {
3229
+ console.error(
3230
+ `[isPinnedProviderLoaded] GET /config/providers body was not a plain object (port ${port})`
3231
+ );
3232
+ return null;
3233
+ }
3234
+ const providers = body.providers;
3235
+ if (!Array.isArray(providers)) {
3236
+ console.error(
3237
+ `[isPinnedProviderLoaded] GET /config/providers body had no providers array (port ${port})`
3238
+ );
3239
+ return null;
3240
+ }
3241
+ const providerIds = providers.filter(
3242
+ (provider) => provider !== null && typeof provider === "object" && typeof provider.id === "string"
3243
+ ).map((provider) => provider.id);
3244
+ if (providerIds.length === 0) {
3245
+ console.error(
3246
+ `[isPinnedProviderLoaded] GET /config/providers had no provider entries with string ids (port ${port})`
3247
+ );
3248
+ return null;
3249
+ }
3250
+ return providerIds.includes(providerId);
3251
+ } catch (err) {
3252
+ console.error(
3253
+ `[isPinnedProviderLoaded] GET /config/providers failed (port ${port}): ${err instanceof Error ? err.message : String(err)}`
3254
+ );
3255
+ return null;
3256
+ }
3257
+ }
3043
3258
  async function buildFileParts(attachments, capable) {
3044
3259
  const outcomes = [];
3045
3260
  const parts = [];
@@ -4221,6 +4436,11 @@ async function listV2Permissions(client, sessionId) {
4221
4436
  return null;
4222
4437
  }
4223
4438
  }
4439
+ async function interruptV2Session(client, sessionId) {
4440
+ await client.request(`/api/session/${encodeURIComponent(sessionId)}/interrupt`, {
4441
+ method: "POST"
4442
+ });
4443
+ }
4224
4444
  async function getV2ActiveSessions(client) {
4225
4445
  try {
4226
4446
  const body = await readJson(await client.request("/api/session/active"));
@@ -4623,11 +4843,11 @@ function failureMessage(status2) {
4623
4843
  function isRetryableStatus(status2) {
4624
4844
  return CONNECT_KEY_RETRY_STATUSES.includes(status2);
4625
4845
  }
4626
- async function importOneCredential(request2, credential, sleepForRetry, log3) {
4846
+ async function importOneCredential(request3, credential, sleepForRetry, log3) {
4627
4847
  const path = `/api/integration/${encodeURIComponent(credential.integrationId)}/connect/key`;
4628
4848
  for (let attempt = 1; attempt <= CONNECT_KEY_MAX_ATTEMPTS; attempt += 1) {
4629
4849
  try {
4630
- const response = await request2(
4850
+ const response = await request3(
4631
4851
  path,
4632
4852
  {
4633
4853
  method: "POST",
@@ -4662,7 +4882,7 @@ async function importOneCredential(request2, credential, sleepForRetry, log3) {
4662
4882
  throw new Error("OpenCode credential import retry loop did not return");
4663
4883
  }
4664
4884
  async function importOpenCodeCredentials({
4665
- request: request2,
4885
+ request: request3,
4666
4886
  classified,
4667
4887
  connectedIntegrationIds,
4668
4888
  sleepForRetry = sleep2,
@@ -4674,7 +4894,7 @@ async function importOpenCodeCredentials({
4674
4894
  result.alreadyConnected.push(credential.integrationId);
4675
4895
  continue;
4676
4896
  }
4677
- const outcome = await importOneCredential(request2, credential, sleepForRetry, log3);
4897
+ const outcome = await importOneCredential(request3, credential, sleepForRetry, log3);
4678
4898
  if (outcome.ok) result.imported.push(credential.integrationId);
4679
4899
  else result.failed.push(outcome.failure);
4680
4900
  }
@@ -4692,7 +4912,7 @@ function parseIntegrationList(value) {
4692
4912
  return { integrationIds, hasData: value.data.length > 0 };
4693
4913
  }
4694
4914
  async function getConnectedOpenCodeIntegrationIds({
4695
- request: request2,
4915
+ request: request3,
4696
4916
  directory,
4697
4917
  sleepForRetry = sleep2,
4698
4918
  log: log3
@@ -4700,7 +4920,7 @@ async function getConnectedOpenCodeIntegrationIds({
4700
4920
  const path = `/api/integration?location%5Bdirectory%5D=${encodeURIComponent(directory)}`;
4701
4921
  for (let attempt = 1; attempt <= INTEGRATION_LIST_MAX_READS; attempt += 1) {
4702
4922
  try {
4703
- const response = await request2(path);
4923
+ const response = await request3(path);
4704
4924
  const parsed = parseIntegrationList(await response.json());
4705
4925
  if (parsed === null) {
4706
4926
  log3(
@@ -4729,7 +4949,7 @@ async function getConnectedOpenCodeIntegrationIds({
4729
4949
  return null;
4730
4950
  }
4731
4951
  async function importOpenCodeV2Credentials({
4732
- request: request2,
4952
+ request: request3,
4733
4953
  homeDir,
4734
4954
  directory,
4735
4955
  sleepForRetry = sleep2,
@@ -4747,7 +4967,7 @@ async function importOpenCodeV2Credentials({
4747
4967
  const classified = classifyOpenCodeCredentials(credentials2);
4748
4968
  if (classified.importable.length === 0) return emptyResult(classified.unsupported);
4749
4969
  const connectedIntegrationIds = await getConnectedOpenCodeIntegrationIds({
4750
- request: request2,
4970
+ request: request3,
4751
4971
  directory,
4752
4972
  sleepForRetry,
4753
4973
  log: log3
@@ -4756,7 +4976,7 @@ async function importOpenCodeV2Credentials({
4756
4976
  return emptyResult(classified.unsupported, "connection-list-unavailable");
4757
4977
  }
4758
4978
  return importOpenCodeCredentials({
4759
- request: request2,
4979
+ request: request3,
4760
4980
  classified,
4761
4981
  connectedIntegrationIds,
4762
4982
  sleepForRetry,
@@ -5447,7 +5667,11 @@ function isSessionDbProvenanceRecord(value) {
5447
5667
  var OPENCODE2_PACKAGE_SPEC = "@opencode-ai/cli@0.0.0-beta-17823";
5448
5668
 
5449
5669
  // src/lib/opencode/opencode-version-gate.ts
5450
- var QUEUE_VALIDATED_OPENCODE_VERSIONS = ["1.17.11", "1.18.3"];
5670
+ var QUEUE_VALIDATED_OPENCODE_VERSIONS = [
5671
+ "1.17.11",
5672
+ "1.18.3",
5673
+ "1.18.35"
5674
+ ];
5451
5675
  function isQueueValidatedVersion(version2) {
5452
5676
  if (!version2) return false;
5453
5677
  return QUEUE_VALIDATED_OPENCODE_VERSIONS.includes(version2);
@@ -6492,6 +6716,7 @@ function connectTunnel(options) {
6492
6716
  }, 3e4);
6493
6717
  let relaySilenceTimeout;
6494
6718
  let relaySilenceTimedOut = false;
6719
+ let relayConnectionId;
6495
6720
  const clearRelaySilenceTimeout = () => {
6496
6721
  if (relaySilenceTimeout === void 0) return;
6497
6722
  clearTimeout(relaySilenceTimeout);
@@ -6577,7 +6802,8 @@ function connectTunnel(options) {
6577
6802
  armRelaySilenceTimeout();
6578
6803
  forwarder.setBinaryFramesSupported(message.binary_frames === true);
6579
6804
  const connectedAgentId = message.agent_id ?? agentId;
6580
- onConnected?.(connectedAgentId);
6805
+ relayConnectionId = message.connection_id;
6806
+ onConnected?.(connectedAgentId, relayConnectionId);
6581
6807
  resolve5({
6582
6808
  ws,
6583
6809
  close: () => ws.close(1e3, "CLI shutdown")
@@ -6614,7 +6840,7 @@ function connectTunnel(options) {
6614
6840
  clearRelaySilenceTimeout();
6615
6841
  const reasonStr = (relaySilenceTimedOut ? "no relay heartbeat for 90s \u2014 reconnecting" : void 0) || reason.toString() || upgradeRejection || (code === 1006 ? "abnormal closure" : "No reason provided");
6616
6842
  forwarder.abortAll();
6617
- onDisconnected?.(code, reasonStr);
6843
+ onDisconnected?.(code, reasonStr, relayConnectionId);
6618
6844
  });
6619
6845
  });
6620
6846
  }
@@ -6677,7 +6903,7 @@ var RunnerConnection = class _RunnerConnection {
6677
6903
  authHeader: this.opts.getAuthHeader(),
6678
6904
  port: this.opts.port,
6679
6905
  openCodePassword: this.opts.openCodePassword,
6680
- onConnected: (agentId) => {
6906
+ onConnected: (agentId, relayConnectionId) => {
6681
6907
  const attempt = this.reconnectAttempt + 1;
6682
6908
  this.reconnectAttempt = 0;
6683
6909
  this.reconnecting = false;
@@ -6687,13 +6913,17 @@ var RunnerConnection = class _RunnerConnection {
6687
6913
  this.disconnectedAtMs = null;
6688
6914
  events.onConnected(agentId, isReconnect, {
6689
6915
  generation: this.generation,
6916
+ relayConnectionId: relayConnectionId ?? null,
6690
6917
  attempt,
6691
6918
  downtimeMs
6692
6919
  });
6693
6920
  },
6694
- onDisconnected: (code, reason) => {
6921
+ onDisconnected: (code, reason, relayConnectionId) => {
6695
6922
  this.disconnectedAtMs = this.now();
6696
- events.onDisconnected(code, reason, { generation: this.generation });
6923
+ events.onDisconnected(code, reason, {
6924
+ generation: this.generation,
6925
+ relayConnectionId: relayConnectionId ?? null
6926
+ });
6697
6927
  const relayRequestedReconnect = code === 1e3 && _RunnerConnection.RELAY_RECONNECT_CLOSE_REASONS.has(reason);
6698
6928
  if (this.opts.isRunning() && (code !== 1e3 || relayRequestedReconnect) && !this.reconnecting) {
6699
6929
  this.reconnectPromise = this.connectWithRetry(true).catch((err) => {
@@ -7141,8 +7371,8 @@ import { chmod, mkdir, open as open3, realpath, rename, unlink } from "node:fs/p
7141
7371
  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";
7142
7372
  var FILE_MODE = 384;
7143
7373
  var DIRECTORY_MODE = 448;
7144
- async function writePushedFile(request2) {
7145
- const { requestedPath, content, allowedDirectories, homeDir } = request2;
7374
+ async function writePushedFile(request3) {
7375
+ const { requestedPath, content, allowedDirectories, homeDir } = request3;
7146
7376
  const bytes = content.byteLength;
7147
7377
  if (allowedDirectories.length === 0) {
7148
7378
  return refuse("file_sync_disabled", "File sync is not enabled on this runner.", {
@@ -7558,6 +7788,211 @@ function describe(err) {
7558
7788
  return err instanceof Error ? err.message : String(err);
7559
7789
  }
7560
7790
 
7791
+ // src/lib/vault-file-sync.ts
7792
+ import { createHash } from "node:crypto";
7793
+ async function syncVaultFiles(options) {
7794
+ const files = await listVaultFiles(options);
7795
+ if (files === null) return { applied: 0 };
7796
+ const listedIds = new Set(files.map((file) => file.id));
7797
+ for (const fileId of options.outcomes.keys()) {
7798
+ if (!listedIds.has(fileId)) options.outcomes.delete(fileId);
7799
+ }
7800
+ let applied = 0;
7801
+ for (const file of files) {
7802
+ try {
7803
+ if (await reconcileVaultFile(options, file)) applied += 1;
7804
+ } catch (err) {
7805
+ options.log({
7806
+ level: "error",
7807
+ message: `Vault file ${file.id} reconciliation failed unexpectedly (message processing is unaffected): ${describe2(err)}`
7808
+ });
7809
+ }
7810
+ }
7811
+ return { applied };
7812
+ }
7813
+ async function listVaultFiles(options) {
7814
+ let response;
7815
+ try {
7816
+ response = await options.fetchImpl(`${options.apiUrl}/runners/${options.agentId}/vault-files`, {
7817
+ headers: { Authorization: options.getAuthHeader() }
7818
+ });
7819
+ } catch (err) {
7820
+ options.log({
7821
+ level: "warn",
7822
+ message: `Could not list vault files \u2014 retrying on the next drain: ${describe2(err)}`
7823
+ });
7824
+ return null;
7825
+ }
7826
+ if (!response.ok) {
7827
+ options.log({
7828
+ level: response.status === 404 ? "debug" : "warn",
7829
+ message: `Listing vault files returned HTTP ${response.status} \u2014 retrying on the next drain`
7830
+ });
7831
+ return null;
7832
+ }
7833
+ let body;
7834
+ try {
7835
+ body = await response.json();
7836
+ } catch (err) {
7837
+ options.log({
7838
+ level: "warn",
7839
+ message: `Vault file list was not readable JSON \u2014 retrying on the next drain: ${describe2(err)}`
7840
+ });
7841
+ return null;
7842
+ }
7843
+ if (body === null || typeof body !== "object" || !Array.isArray(body.files)) {
7844
+ options.log({
7845
+ level: "warn",
7846
+ message: "Vault file list was not an object with a files array \u2014 ignoring it for this drain"
7847
+ });
7848
+ return null;
7849
+ }
7850
+ const files = [];
7851
+ for (const entry of body.files) {
7852
+ const file = asVaultFile(entry);
7853
+ if (file === null) {
7854
+ options.log({
7855
+ level: "warn",
7856
+ message: "Ignoring a malformed vault file entry"
7857
+ });
7858
+ continue;
7859
+ }
7860
+ files.push(file);
7861
+ }
7862
+ return files;
7863
+ }
7864
+ function asVaultFile(entry) {
7865
+ if (entry === null || typeof entry !== "object") return null;
7866
+ const value = entry;
7867
+ if (typeof value.id !== "string" || value.id === "" || typeof value.destination_path !== "string" || value.destination_path === "" || typeof value.size_bytes !== "number" || !Number.isFinite(value.size_bytes) || value.size_bytes < 0 || typeof value.content_sha256 !== "string" || value.content_sha256 === "" || typeof value.created_at !== "string" || typeof value.updated_at !== "string") {
7868
+ return null;
7869
+ }
7870
+ return {
7871
+ id: value.id,
7872
+ destination_path: value.destination_path,
7873
+ size_bytes: value.size_bytes,
7874
+ content_sha256: value.content_sha256,
7875
+ created_at: value.created_at,
7876
+ updated_at: value.updated_at
7877
+ };
7878
+ }
7879
+ async function reconcileVaultFile(options, file) {
7880
+ const existing = options.outcomes.get(file.id);
7881
+ if (existing?.sha256 === file.content_sha256) {
7882
+ if (existing.reported || existing.reportAttempts >= MAX_ACK_ATTEMPTS) return false;
7883
+ await reportOutcome(options, file, existing);
7884
+ return false;
7885
+ }
7886
+ const content = await downloadVaultFile(options, file);
7887
+ if (content === null) return false;
7888
+ const actualSha256 = createHash("sha256").update(content).digest("hex");
7889
+ if (actualSha256 !== file.content_sha256) {
7890
+ options.log({
7891
+ level: "warn",
7892
+ message: `Vault file ${file.id} content hash mismatch: expected ${file.content_sha256}, received ${actualSha256} \u2014 retrying on the next drain`
7893
+ });
7894
+ return false;
7895
+ }
7896
+ const outcome = await applyVaultFile(options, file, content);
7897
+ const entry = {
7898
+ sha256: file.content_sha256,
7899
+ outcome: outcome.ok ? "applied" : "refused",
7900
+ reason: outcome.ok ? null : outcome.code,
7901
+ reported: false,
7902
+ reportAttempts: 0
7903
+ };
7904
+ options.outcomes.set(file.id, entry);
7905
+ await reportOutcome(options, file, entry);
7906
+ return outcome.ok;
7907
+ }
7908
+ async function downloadVaultFile(options, file) {
7909
+ try {
7910
+ const response = await options.fetchImpl(
7911
+ `${options.apiUrl}/runners/${options.agentId}/vault-files/${file.id}/content`,
7912
+ { headers: { Authorization: options.getAuthHeader() } }
7913
+ );
7914
+ if (!response.ok) {
7915
+ options.log({
7916
+ level: "warn",
7917
+ message: `Downloading vault file ${file.id} returned HTTP ${response.status} \u2014 retrying on the next drain`
7918
+ });
7919
+ return null;
7920
+ }
7921
+ return Buffer.from(await response.arrayBuffer());
7922
+ } catch (err) {
7923
+ options.log({
7924
+ level: "warn",
7925
+ message: `Downloading vault file ${file.id} failed \u2014 retrying on the next drain: ${describe2(err)}`
7926
+ });
7927
+ return null;
7928
+ }
7929
+ }
7930
+ async function applyVaultFile(options, file, content) {
7931
+ try {
7932
+ const outcome = await writePushedFile({
7933
+ requestedPath: file.destination_path,
7934
+ content,
7935
+ allowedDirectories: options.allowedDirectories,
7936
+ homeDir: options.homeDir
7937
+ });
7938
+ if (!outcome.ok) {
7939
+ options.log({
7940
+ level: "warn",
7941
+ message: `Vault file ${file.id} refused (${outcome.code}): ${outcome.message}`
7942
+ });
7943
+ } else {
7944
+ options.log({
7945
+ level: "info",
7946
+ message: `Vault file ${file.id} applied (${content.byteLength} bytes)`
7947
+ });
7948
+ }
7949
+ return outcome;
7950
+ } catch (err) {
7951
+ options.log({
7952
+ level: "error",
7953
+ message: `Vault file ${file.id} could not be written: ${describe2(err)}`
7954
+ });
7955
+ return { ok: false, code: "write_failed", message: "The runner could not write the file." };
7956
+ }
7957
+ }
7958
+ async function reportOutcome(options, file, entry) {
7959
+ const body = {
7960
+ status: entry.outcome === "applied" ? "applied" : "rejected",
7961
+ reported_sha256: entry.sha256
7962
+ };
7963
+ if (entry.outcome === "refused" && entry.reason !== null) body.reason = entry.reason;
7964
+ try {
7965
+ const response = await options.fetchImpl(
7966
+ `${options.apiUrl}/runners/${options.agentId}/vault-files/${file.id}/status`,
7967
+ {
7968
+ method: "POST",
7969
+ headers: {
7970
+ Authorization: options.getAuthHeader(),
7971
+ "Content-Type": "application/json"
7972
+ },
7973
+ body: JSON.stringify(body)
7974
+ }
7975
+ );
7976
+ if (response.ok) {
7977
+ entry.reported = true;
7978
+ return;
7979
+ }
7980
+ recordReportFailure(options, file, entry, `HTTP ${response.status}`);
7981
+ } catch (err) {
7982
+ recordReportFailure(options, file, entry, describe2(err));
7983
+ }
7984
+ }
7985
+ function recordReportFailure(options, file, entry, reason) {
7986
+ entry.reportAttempts += 1;
7987
+ options.log({
7988
+ level: "error",
7989
+ message: entry.reportAttempts >= MAX_ACK_ATTEMPTS ? `Reporting vault file ${file.id} as ${entry.outcome} failed (${reason}) \u2014 giving up after ${entry.reportAttempts} attempts` : `Reporting vault file ${file.id} as ${entry.outcome} failed (${reason}) \u2014 retrying on the next drain (attempt ${entry.reportAttempts} of ${MAX_ACK_ATTEMPTS})`
7990
+ });
7991
+ }
7992
+ function describe2(err) {
7993
+ return err instanceof Error ? err.message : String(err);
7994
+ }
7995
+
7561
7996
  // src/lib/channels/driver.ts
7562
7997
  function messageIdOf(m) {
7563
7998
  if (!m || typeof m !== "object") return void 0;
@@ -7966,6 +8401,7 @@ var ChannelDriver = class _ChannelDriver {
7966
8401
  * re-downloaded and re-written every ~2s until the server expires it.
7967
8402
  */
7968
8403
  fileAckFailures = /* @__PURE__ */ new Map();
8404
+ vaultFileOutcomes = /* @__PURE__ */ new Map();
7969
8405
  /**
7970
8406
  * Monotonic count of files this runner has pulled and written (#559). Only
7971
8407
  * ever increases, so `run.ts` detects work by comparing it against the value
@@ -8123,10 +8559,21 @@ var ChannelDriver = class _ChannelDriver {
8123
8559
  ackFailures: this.fileAckFailures,
8124
8560
  log: this.log
8125
8561
  });
8126
- this.appliedFileCount += result.applied;
8562
+ const vaultResult = await syncVaultFiles({
8563
+ agentId: this.agentId,
8564
+ apiUrl: this.apiUrl,
8565
+ getAuthHeader: this.getAuthHeader,
8566
+ fetchImpl: this.fetchImpl,
8567
+ allowedDirectories: this.fileSyncDirectories,
8568
+ homeDir: this.homeDir,
8569
+ outcomes: this.vaultFileOutcomes,
8570
+ log: this.log
8571
+ });
8572
+ const applied = result.applied + vaultResult.applied;
8573
+ this.appliedFileCount += applied;
8127
8574
  if (result.claudeCredentialApplied) this.claudeCredentialApplyCount += 1;
8128
8575
  if (result.opencodeAuthApplied) this.opencodeAuthApplyCount += 1;
8129
- return result.applied;
8576
+ return applied;
8130
8577
  } catch (err) {
8131
8578
  this.log({
8132
8579
  level: "error",
@@ -8538,6 +8985,47 @@ var ChannelDriver = class _ChannelDriver {
8538
8985
  agent: message.opencode_agent ?? void 0,
8539
8986
  model: message.opencode_model ?? void 0
8540
8987
  };
8988
+ const pinnedProviderLoaded = await isPinnedProviderLoaded(
8989
+ this.port,
8990
+ options.model,
8991
+ this.openCodeClient
8992
+ );
8993
+ if (pinnedProviderLoaded === false) {
8994
+ const dispatchedModel = message.opencode_model;
8995
+ const separator = dispatchedModel?.indexOf("/") ?? -1;
8996
+ const hasProviderAndModel = dispatchedModel !== null && separator > 0 && separator < dispatchedModel.length - 1;
8997
+ const providerId = hasProviderAndModel ? dispatchedModel.slice(0, separator) : null;
8998
+ const modelId = hasProviderAndModel ? dispatchedModel.slice(separator + 1) : null;
8999
+ const errorMessage3 = `Provider namespace not loaded on this runner: ${message.opencode_model}. Refused before dispatch; no turn was started.`;
9000
+ this.log({
9001
+ level: "error",
9002
+ message: `Refusing message ${message.id.slice(0, 8)} in conversation ${conv.id.slice(0, 8)}: provider namespace ${providerId ?? "(unknown)"} is not loaded; no turn was started`,
9003
+ conversation_id: conv.id,
9004
+ message_id: message.id
9005
+ });
9006
+ try {
9007
+ await this.markFailed(conv.id, message.id, sessionId, errorMessage3, void 0, {
9008
+ classification: {
9009
+ kind: "model_auth",
9010
+ providerId,
9011
+ modelId,
9012
+ reason: "missing"
9013
+ },
9014
+ errorShape: { name: null, statusCode: null }
9015
+ });
9016
+ this.signalDispatchNotStarted(conv, message, "model_provider_absent");
9017
+ } catch (markErr) {
9018
+ this.log({
9019
+ level: "warn",
9020
+ message: `markFailed PATCH for refused message ${message.id.slice(0, 8)} (conversation ${conv.id.slice(0, 8)}) failed: ${markErr instanceof Error ? markErr.message : String(markErr)}`,
9021
+ conversation_id: conv.id,
9022
+ message_id: message.id
9023
+ });
9024
+ this.signalDispatchNotStarted(conv, message, "failure_unreported");
9025
+ }
9026
+ await this.repairProviderRegistry(conv.id, message.id);
9027
+ break;
9028
+ }
8541
9029
  let opencodeMessageId;
8542
9030
  let preDispatchUserIds = null;
8543
9031
  try {
@@ -8891,6 +9379,34 @@ var ChannelDriver = class _ChannelDriver {
8891
9379
  if (state === "failed" && isAbortedTerminalReply(messages, ocId ?? "")) {
8892
9380
  const ongoing = await this.isSessionOngoing(sessionId);
8893
9381
  if (ongoing === false) {
9382
+ const age = this.absoluteAgeForRow(message.processing_started_at);
9383
+ if (age.tripped && age.anchorMs !== null) {
9384
+ try {
9385
+ await this.markFailed(
9386
+ conv.id,
9387
+ message.id,
9388
+ sessionId,
9389
+ this.absoluteCeilingError(message.id, sessionId, age.anchorMs),
9390
+ void 0,
9391
+ {
9392
+ classification: null,
9393
+ errorShape: extractReplyErrorShape(findLastAssistantReplyFor(messages, ocId ?? ""))
9394
+ }
9395
+ );
9396
+ } catch (err) {
9397
+ if (err instanceof ChannelAuthError) throw err;
9398
+ this.log({
9399
+ level: "warn",
9400
+ message: `Re-drive: failed to report past-ceiling restart orphan ${message.id.slice(0, 8)} terminal; refusing to re-dispatch: ${err instanceof Error ? err.message : String(err)}`,
9401
+ conversation_id: conv.id,
9402
+ message_id: message.id
9403
+ });
9404
+ return "unresolved";
9405
+ }
9406
+ this.clearRedriveUnresolved(message.id);
9407
+ void this.postSignal(conv.id, message.id, "redrive_ceiling_refused");
9408
+ return "settled";
9409
+ }
8894
9410
  this.log({
8895
9411
  level: "info",
8896
9412
  message: `Re-drive: message ${message.id.slice(0, 8)} carries an abort-shaped terminal error and session ${sessionId.slice(0, 8)} is not-ongoing per the active-session status check \u2014 restart orphan, re-dispatching instead of marking it permanently failed`,
@@ -8961,12 +9477,35 @@ var ChannelDriver = class _ChannelDriver {
8961
9477
  message_id: message.id
8962
9478
  });
8963
9479
  }
8964
- const title = await this.resolveSessionTitle(sessionId, conv.id);
8965
- try {
8966
- await this.markProcessing(conv.id, message.id, sessionId, ocId, title);
8967
- } catch (err) {
8968
- if (err instanceof ChannelAuthError) throw err;
8969
- if (err instanceof ChannelTerminalError) {
9480
+ if (this.absoluteAgeForRow(message.processing_started_at).tripped) {
9481
+ try {
9482
+ await this.markFailed(
9483
+ conv.id,
9484
+ message.id,
9485
+ sessionId,
9486
+ this.absoluteCeilingError(message.id, sessionId, anchorMs)
9487
+ );
9488
+ } catch (err) {
9489
+ if (err instanceof ChannelAuthError) throw err;
9490
+ this.log({
9491
+ level: "warn",
9492
+ message: `Re-drive: failed to report past-ceiling message ${message.id.slice(0, 8)} terminal; refusing to re-attach: ${err instanceof Error ? err.message : String(err)}`,
9493
+ conversation_id: conv.id,
9494
+ message_id: message.id
9495
+ });
9496
+ const bound = await this.boundRedriveOutcome(conv, message, "reattach");
9497
+ return bound === "abandoned" ? "abandoned" : "unresolved";
9498
+ }
9499
+ this.clearRedriveUnresolved(message.id);
9500
+ void this.postSignal(conv.id, message.id, "redrive_ceiling_refused");
9501
+ return "abandoned";
9502
+ }
9503
+ const title = await this.resolveSessionTitle(sessionId, conv.id);
9504
+ try {
9505
+ await this.markProcessing(conv.id, message.id, sessionId, ocId, title);
9506
+ } catch (err) {
9507
+ if (err instanceof ChannelAuthError) throw err;
9508
+ if (err instanceof ChannelTerminalError) {
8970
9509
  this.log({
8971
9510
  level: "error",
8972
9511
  message: `Re-drive: the server definitively refused to restore message ${message.id.slice(0, 8)} to processing (terminal HTTP ${err.status} \u2014 the row is gone or the update was rejected); NOT reporting a re-attach`,
@@ -9227,8 +9766,7 @@ var ChannelDriver = class _ChannelDriver {
9227
9766
  const since = this.redriveOutcomeFailingSince.get(message.id);
9228
9767
  if (since === void 0) this.redriveOutcomeFailingSince.set(message.id, now);
9229
9768
  const durationTripped = now - (since ?? now) >= this.pausedMaxWaitMs;
9230
- const parsed = message.processing_started_at ? Date.parse(message.processing_started_at) : NaN;
9231
- const absoluteAgeTripped = !Number.isNaN(parsed) && now - parsed >= ABSOLUTE_MAX_PROCESSING_MS;
9769
+ const absoluteAgeTripped = this.absoluteAgeForRow(message.processing_started_at).tripped;
9232
9770
  if (!durationTripped && !absoluteAgeTripped) {
9233
9771
  this.signalRedriveOutcomeUnreported(conv, message, outcome);
9234
9772
  return "retry";
@@ -9267,6 +9805,15 @@ var ChannelDriver = class _ChannelDriver {
9267
9805
  });
9268
9806
  return "abandoned";
9269
9807
  }
9808
+ /** Row-level guards use the durable server stamp; in-flight guards use processingAnchorMs. */
9809
+ absoluteAgeForRow(stamp) {
9810
+ const anchorMs = stamp ? Date.parse(stamp) : NaN;
9811
+ return Number.isNaN(anchorMs) ? { tripped: false, anchorMs: null } : { tripped: this.now() - anchorMs >= ABSOLUTE_MAX_PROCESSING_MS, anchorMs };
9812
+ }
9813
+ absoluteCeilingError(messageId, sessionId, anchorMs, retainedReason = this.readRetainedSessionError(sessionId)) {
9814
+ const baseError = `Message ${messageId.slice(0, 8)} exceeded the absolute processing ceiling (${Math.round((this.now() - anchorMs) / 6e4)}min, session ${sessionId}) while still actively running.`;
9815
+ return retainedReason ? `${baseError} ${SESSION_ERROR_RETAINED_DETAIL}${retainedReason}` : baseError;
9816
+ }
9270
9817
  /**
9271
9818
  * Record one poll outcome toward the re-drive fence's consecutive-identical-
9272
9819
  * failure streak (#1348) and return the resulting count. `signature === null`
@@ -9708,10 +10255,7 @@ var ChannelDriver = class _ChannelDriver {
9708
10255
  opencodeMessageId,
9709
10256
  message,
9710
10257
  dispatchedAt: this.now(),
9711
- // Anchor the absolute-age ceiling to the SERVER-SIDE `processed_at`, NOT
9712
- // `dispatchedAt` — so a re-adopted zombie's age reflects the real turn duration
9713
- // and the ceiling fires on the ORIGINAL turn.
9714
- processingAnchorMs: processedAtMs,
10258
+ processingAnchorMs: this.absoluteAgeForRow(message.processing_started_at).anchorMs ?? processedAtMs,
9715
10259
  deadline: watchDeadlineAnchorMs + this.pausedMaxWaitMs,
9716
10260
  // The server row is ALREADY `processing`; do not re-fire markProcessing.
9717
10261
  started: true,
@@ -10162,7 +10706,9 @@ var ChannelDriver = class _ChannelDriver {
10162
10706
  }
10163
10707
  const { openQuestions, openPermissions, questionsPolledOk, permissionsPolledOk } = await this.pollInteractions(sessionId, watcher, messages);
10164
10708
  if (watcher.generation !== generation) return;
10709
+ const ceilingAttemptsThisTick = /* @__PURE__ */ new Set();
10165
10710
  for (const inFlight of [...watcher.inFlight.values()]) {
10711
+ if (ceilingAttemptsThisTick.has(inFlight.evidentMessageId)) continue;
10166
10712
  await this.serviceInFlightMessage(
10167
10713
  sessionId,
10168
10714
  watcher,
@@ -10171,7 +10717,8 @@ var ChannelDriver = class _ChannelDriver {
10171
10717
  openQuestions,
10172
10718
  openPermissions,
10173
10719
  questionsPolledOk,
10174
- permissionsPolledOk
10720
+ permissionsPolledOk,
10721
+ ceilingAttemptsThisTick
10175
10722
  );
10176
10723
  }
10177
10724
  }
@@ -10212,13 +10759,117 @@ var ChannelDriver = class _ChannelDriver {
10212
10759
  inFlight.deadline = this.now() + this.pausedMaxWaitMs;
10213
10760
  }
10214
10761
  }
10762
+ async settleAbsoluteCeiling(sessionId, watcher, messages, ceilingAttemptsThisTick) {
10763
+ const entries = [...watcher.inFlight.values()];
10764
+ const pastCeiling = entries.filter(
10765
+ (entry) => this.now() - entry.processingAnchorMs >= ABSOLUTE_MAX_PROCESSING_MS
10766
+ );
10767
+ const healthySiblings = entries.length - pastCeiling.length;
10768
+ const landed = [];
10769
+ let allRecordsLanded = true;
10770
+ const retainedReason = this.readRetainedSessionError(sessionId);
10771
+ for (const entry of pastCeiling) {
10772
+ if (!watcher.inFlight.has(entry.evidentMessageId)) continue;
10773
+ ceilingAttemptsThisTick.add(entry.evidentMessageId);
10774
+ const error2 = this.absoluteCeilingError(
10775
+ entry.evidentMessageId,
10776
+ sessionId,
10777
+ entry.processingAnchorMs,
10778
+ retainedReason
10779
+ );
10780
+ this.log({
10781
+ level: "warn",
10782
+ message: error2,
10783
+ conversation_id: watcher.conv.id,
10784
+ message_id: entry.evidentMessageId
10785
+ });
10786
+ try {
10787
+ await this.markFailed(
10788
+ watcher.conv.id,
10789
+ entry.evidentMessageId,
10790
+ sessionId,
10791
+ error2,
10792
+ void 0,
10793
+ {
10794
+ classification: null,
10795
+ errorShape: extractReplyErrorShape(
10796
+ findLastAssistantReplyFor(messages, entry.opencodeMessageId)
10797
+ )
10798
+ }
10799
+ );
10800
+ } catch (err) {
10801
+ if (err instanceof ChannelAuthError) throw err;
10802
+ allRecordsLanded = false;
10803
+ const watched_for_ms = this.now() - entry.processingAnchorMs;
10804
+ if (err instanceof ChannelTerminalError) {
10805
+ void this.postSignal(watcher.conv.id, entry.evidentMessageId, "gave_up", {
10806
+ watched_for_ms,
10807
+ interrupt_outcome: "record_terminal"
10808
+ });
10809
+ this.removeInFlight(watcher, entry.evidentMessageId);
10810
+ continue;
10811
+ }
10812
+ if (this.now() >= entry.deadline) {
10813
+ void this.postSignal(watcher.conv.id, entry.evidentMessageId, "gave_up", {
10814
+ watched_for_ms,
10815
+ interrupt_outcome: "record_deadline"
10816
+ });
10817
+ this.removeInFlight(watcher, entry.evidentMessageId);
10818
+ continue;
10819
+ }
10820
+ void this.postSignal(watcher.conv.id, entry.evidentMessageId, "gave_up", {
10821
+ watched_for_ms,
10822
+ interrupt_outcome: "record_retry"
10823
+ });
10824
+ this.log({
10825
+ level: "warn",
10826
+ message: `Failed to record the absolute-ceiling failure for message ${entry.evidentMessageId.slice(0, 8)}; leaving its running turn untouched and retrying next tick: ${err instanceof Error ? err.message : String(err)}`,
10827
+ conversation_id: watcher.conv.id,
10828
+ message_id: entry.evidentMessageId
10829
+ });
10830
+ continue;
10831
+ }
10832
+ entry.done = true;
10833
+ landed.push(entry);
10834
+ }
10835
+ if (!allRecordsLanded) {
10836
+ for (const entry of landed) this.removeInFlight(watcher, entry.evidentMessageId);
10837
+ return;
10838
+ }
10839
+ if (healthySiblings > 0) {
10840
+ for (const entry of landed) {
10841
+ void this.postSignal(watcher.conv.id, entry.evidentMessageId, "gave_up", {
10842
+ watched_for_ms: this.now() - entry.processingAnchorMs,
10843
+ interrupt_outcome: "deferred_for_healthy_sibling",
10844
+ healthy_siblings: healthySiblings
10845
+ });
10846
+ this.removeInFlight(watcher, entry.evidentMessageId);
10847
+ }
10848
+ return;
10849
+ }
10850
+ const interrupt = await interruptSession(this.port, sessionId, this.openCodeClient);
10851
+ for (const entry of landed) {
10852
+ void this.postSignal(watcher.conv.id, entry.evidentMessageId, "gave_up", {
10853
+ watched_for_ms: this.now() - entry.processingAnchorMs,
10854
+ interrupt_outcome: interrupt.kind
10855
+ });
10856
+ this.removeInFlight(watcher, entry.evidentMessageId);
10857
+ }
10858
+ if (interrupt.kind !== "confirmed_stopped") {
10859
+ this.log({
10860
+ level: "warn",
10861
+ message: `Absolute-ceiling records landed for session ${sessionId}, but OpenCode interruption was ${interrupt.kind}${interrupt.diagnostic ? ` (${interrupt.diagnostic})` : "status" in interrupt && typeof interrupt.status === "number" ? ` (HTTP ${interrupt.status})` : ""}; the rows are terminal and released, while the session may still be running`,
10862
+ conversation_id: watcher.conv.id
10863
+ });
10864
+ }
10865
+ }
10215
10866
  /**
10216
10867
  * Drive ONE in-flight message's lifecycle from the tick's message snapshot.
10217
10868
  * Fires markProcessing on queued→running and markDone on done (each once),
10218
10869
  * applies the idle-path re-dispatch guard, and removes the message from the
10219
10870
  * in-flight set on completion or timeout.
10220
10871
  */
10221
- async serviceInFlightMessage(sessionId, watcher, inFlight, messages, openQuestions, openPermissions, questionsPolledOk, permissionsPolledOk) {
10872
+ async serviceInFlightMessage(sessionId, watcher, inFlight, messages, openQuestions, openPermissions, questionsPolledOk, permissionsPolledOk, ceilingAttemptsThisTick) {
10222
10873
  const conv = watcher.conv;
10223
10874
  const state = messageRunState(messages, inFlight.opencodeMessageId);
10224
10875
  const id = inFlight.evidentMessageId;
@@ -10562,17 +11213,8 @@ var ChannelDriver = class _ChannelDriver {
10562
11213
  return;
10563
11214
  }
10564
11215
  }
10565
- if (activelyRunning && this.now() - inFlight.processingAnchorMs >= ABSOLUTE_MAX_PROCESSING_MS) {
10566
- this.log({
10567
- level: "warn",
10568
- message: `Message ${inFlight.evidentMessageId.slice(0, 8)} exceeded the absolute processing ceiling (${Math.round((this.now() - inFlight.processingAnchorMs) / 6e4)}min, session ${sessionId}) while still actively running \u2014 releasing so the cron can reclaim it`,
10569
- conversation_id: conv.id,
10570
- message_id: inFlight.evidentMessageId
10571
- });
10572
- void this.postSignal(conv.id, inFlight.evidentMessageId, "gave_up", {
10573
- watched_for_ms: this.now() - inFlight.processingAnchorMs
10574
- });
10575
- this.removeInFlight(watcher, inFlight.evidentMessageId);
11216
+ if (watcher.inFlight.has(inFlight.evidentMessageId) && activelyRunning && this.now() - inFlight.processingAnchorMs >= ABSOLUTE_MAX_PROCESSING_MS) {
11217
+ await this.settleAbsoluteCeiling(sessionId, watcher, messages, ceilingAttemptsThisTick);
10576
11218
  return;
10577
11219
  }
10578
11220
  const heartbeatDue = activelyRunning && !inFlight.awaitingHumanLatched && !inFlight.aliveInFlight && this.now() - inFlight.lastAliveAt >= HEARTBEAT_MS;
@@ -11195,6 +11837,33 @@ var ChannelDriver = class _ChannelDriver {
11195
11837
  }
11196
11838
  const restartAborted = state === "failed" && sessionOngoing === false && isAbortedTerminalReply(messages, ocId ?? "");
11197
11839
  if (restartAborted) {
11840
+ const age = this.absoluteAgeForRow(row.processing_started_at);
11841
+ if (age.tripped && age.anchorMs !== null) {
11842
+ try {
11843
+ await this.markFailed(
11844
+ row.conversation_id,
11845
+ row.id,
11846
+ sessionId,
11847
+ this.absoluteCeilingError(row.id, sessionId, age.anchorMs),
11848
+ void 0,
11849
+ {
11850
+ classification: null,
11851
+ errorShape: extractReplyErrorShape(findLastAssistantReplyFor(messages, ocId ?? ""))
11852
+ }
11853
+ );
11854
+ } catch (err) {
11855
+ if (err instanceof ChannelAuthError) throw err;
11856
+ this.log({
11857
+ level: "warn",
11858
+ message: `Re-adopt: failed to report past-ceiling restart orphan ${row.id.slice(0, 8)} terminal; refusing to re-dispatch: ${err instanceof Error ? err.message : String(err)}`,
11859
+ conversation_id: row.conversation_id,
11860
+ message_id: row.id
11861
+ });
11862
+ return;
11863
+ }
11864
+ void this.postSignal(row.conversation_id, row.id, "redrive_ceiling_refused");
11865
+ return;
11866
+ }
11198
11867
  this.log({
11199
11868
  level: "info",
11200
11869
  message: `Re-adopt: message ${row.id.slice(0, 8)} carries an abort-shaped terminal error and session ${sessionId.slice(0, 8)} is not-ongoing per the active-session status check \u2014 restart orphan, re-dispatching instead of marking it permanently failed`,
@@ -11740,7 +12409,8 @@ var ChannelDriver = class _ChannelDriver {
11740
12409
  source_message_id: row.source_message_id,
11741
12410
  slack_user_id: row.slack_user_id,
11742
12411
  attachments: row.attachments ?? null,
11743
- opencode_message_id: row.opencode_message_id
12412
+ opencode_message_id: row.opencode_message_id,
12413
+ processing_started_at: row.processing_started_at
11744
12414
  };
11745
12415
  }
11746
12416
  /**
@@ -13538,6 +14208,22 @@ async function configureGitHubAccess({ env, log: log3 }) {
13538
14208
  import { execFileSync as execFileSync2 } from "node:child_process";
13539
14209
  import { copyFileSync, existsSync as existsSync2, statSync as statSync6 } from "node:fs";
13540
14210
  import { isAbsolute as isAbsolute3, join as join11, resolve as resolve4 } from "node:path";
14211
+ function parseConfigContent(content) {
14212
+ if (!content?.trim()) return {};
14213
+ const parsed = JSON.parse(content);
14214
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
14215
+ throw new Error("OPENCODE_CONFIG_CONTENT must contain a JSON object");
14216
+ }
14217
+ return parsed;
14218
+ }
14219
+ function mergeManagedOpenCodeConfigContent(content, managedProviders) {
14220
+ const config = parseConfigContent(content);
14221
+ const provider = typeof config.provider === "object" && config.provider !== null && !Array.isArray(config.provider) ? { ...config.provider } : {};
14222
+ for (const namespace of CREDENTIAL_PROXY_NATIVE_NAMESPACES) {
14223
+ delete provider[namespace];
14224
+ }
14225
+ return JSON.stringify({ ...config, provider: { ...provider, ...managedProviders } });
14226
+ }
13541
14227
  function isFile(filePath) {
13542
14228
  return existsSync2(filePath) && statSync6(filePath).isFile();
13543
14229
  }
@@ -13571,6 +14257,159 @@ function applyRunnerOpenCodeConfig({
13571
14257
  log3(`Applied runner OpenCode config ${source} to ${join11(cwd, target)}`);
13572
14258
  }
13573
14259
 
14260
+ // src/lib/opencode/runtime-provenance.ts
14261
+ import { readFileSync as readFileSync7, readdirSync } from "node:fs";
14262
+ import { homedir as homedir7 } from "node:os";
14263
+ import { join as join12 } from "node:path";
14264
+ var PACKAGE_NAME = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/i;
14265
+ var REGISTRY_SPEC = /^[a-z0-9.*+^~<>=|-]+$/i;
14266
+ function errorKind(error2) {
14267
+ if (!(error2 instanceof Error)) return "unknown error";
14268
+ const code = "code" in error2 && typeof error2.code === "string" ? ` (${error2.code})` : "";
14269
+ return `${error2.name}${code}`;
14270
+ }
14271
+ function parseRegistryPluginSpec(value) {
14272
+ const separator = value.lastIndexOf("@");
14273
+ if (separator <= 0) return null;
14274
+ const name = value.slice(0, separator);
14275
+ const spec = value.slice(separator + 1);
14276
+ if (!PACKAGE_NAME.test(name) || !REGISTRY_SPEC.test(spec)) return null;
14277
+ return { name, spec };
14278
+ }
14279
+ function parseJsonc(value) {
14280
+ let output = "";
14281
+ let inString = false;
14282
+ let escaped = false;
14283
+ let lineComment = false;
14284
+ let blockComment = false;
14285
+ for (let index = 0; index < value.length; index++) {
14286
+ const character = value[index];
14287
+ const next = value[index + 1];
14288
+ if (lineComment) {
14289
+ if (character === "\n") {
14290
+ lineComment = false;
14291
+ output += character;
14292
+ }
14293
+ continue;
14294
+ }
14295
+ if (blockComment) {
14296
+ if (character === "*" && next === "/") {
14297
+ blockComment = false;
14298
+ index++;
14299
+ } else if (character === "\n") {
14300
+ output += character;
14301
+ }
14302
+ continue;
14303
+ }
14304
+ if (inString) {
14305
+ output += character;
14306
+ if (escaped) escaped = false;
14307
+ else if (character === "\\") escaped = true;
14308
+ else if (character === '"') inString = false;
14309
+ continue;
14310
+ }
14311
+ if (character === '"') {
14312
+ inString = true;
14313
+ output += character;
14314
+ } else if (character === "/" && next === "/") {
14315
+ lineComment = true;
14316
+ index++;
14317
+ } else if (character === "/" && next === "*") {
14318
+ blockComment = true;
14319
+ index++;
14320
+ } else if (character === ",") {
14321
+ let lookahead = index + 1;
14322
+ while (/\s/.test(value[lookahead] ?? "")) lookahead++;
14323
+ if (value[lookahead] !== "]" && value[lookahead] !== "}") output += character;
14324
+ } else {
14325
+ output += character;
14326
+ }
14327
+ }
14328
+ return JSON.parse(output);
14329
+ }
14330
+ function openCodeCacheRoot(env = process.env, homeDir = homedir7()) {
14331
+ return join12(env.XDG_CACHE_HOME ?? join12(homeDir, ".cache"), "opencode");
14332
+ }
14333
+ function readConfiguredPluginSpecs({
14334
+ cwd = process.cwd(),
14335
+ log: log3
14336
+ }) {
14337
+ let config;
14338
+ try {
14339
+ try {
14340
+ config = parseJsonc(readFileSync7(join12(cwd, "opencode.jsonc"), "utf8"));
14341
+ } catch (error2) {
14342
+ if (!("code" in Object(error2)) || Object(error2).code !== "ENOENT") throw error2;
14343
+ config = parseJsonc(readFileSync7(join12(cwd, "opencode.json"), "utf8"));
14344
+ }
14345
+ } catch (error2) {
14346
+ log3(`Could not read OpenCode plugin configuration: ${errorKind(error2)}`);
14347
+ return [];
14348
+ }
14349
+ if (typeof config !== "object" || config === null || !("plugin" in config)) return [];
14350
+ const plugins = config.plugin;
14351
+ if (!Array.isArray(plugins)) {
14352
+ log3("Could not read OpenCode plugin configuration: plugin is not an array");
14353
+ return [];
14354
+ }
14355
+ const configured = plugins.filter(
14356
+ (plugin) => typeof plugin === "string" && parseRegistryPluginSpec(plugin) !== null
14357
+ );
14358
+ if (configured.length !== plugins.length) {
14359
+ log3(
14360
+ `Skipped ${plugins.length - configured.length} non-registry OpenCode plugin spec(s); their values were not recorded`
14361
+ );
14362
+ }
14363
+ return configured;
14364
+ }
14365
+ function resolveInstalledPluginVersions({
14366
+ cacheRoot,
14367
+ env = process.env,
14368
+ homeDir = homedir7(),
14369
+ log: log3
14370
+ }) {
14371
+ const packagesRoot = join12(cacheRoot ?? openCodeCacheRoot(env, homeDir), "packages");
14372
+ let directories;
14373
+ try {
14374
+ directories = readdirSync(packagesRoot, { withFileTypes: true });
14375
+ } catch (error2) {
14376
+ if ("code" in Object(error2) && Object(error2).code === "ENOENT") return [];
14377
+ log3(`Could not inspect the OpenCode plugin cache: ${errorKind(error2)}`);
14378
+ return [];
14379
+ }
14380
+ const plugins = [];
14381
+ let skipped = 0;
14382
+ for (const directory of directories) {
14383
+ if (!directory.isDirectory()) continue;
14384
+ const parsed = parseRegistryPluginSpec(directory.name);
14385
+ if (parsed === null) {
14386
+ skipped++;
14387
+ continue;
14388
+ }
14389
+ let resolvedVersion = null;
14390
+ try {
14391
+ const packageJson = JSON.parse(
14392
+ readFileSync7(
14393
+ join12(packagesRoot, directory.name, "node_modules", parsed.name, "package.json"),
14394
+ "utf8"
14395
+ )
14396
+ );
14397
+ if (typeof packageJson.version === "string" && REGISTRY_SPEC.test(packageJson.version)) {
14398
+ resolvedVersion = packageJson.version;
14399
+ } else {
14400
+ log3(`Could not resolve OpenCode plugin ${parsed.name}@${parsed.spec}: invalid version`);
14401
+ }
14402
+ } catch (error2) {
14403
+ log3(`Could not resolve OpenCode plugin ${parsed.name}@${parsed.spec}: ${errorKind(error2)}`);
14404
+ }
14405
+ plugins.push({ ...parsed, resolvedVersion });
14406
+ }
14407
+ if (skipped > 0) {
14408
+ log3(`Skipped ${skipped} non-registry OpenCode package cache entry; its value was not recorded`);
14409
+ }
14410
+ return plugins;
14411
+ }
14412
+
13574
14413
  // src/lib/credential-sync.ts
13575
14414
  import { renameSync, writeFileSync as writeFileSync5 } from "node:fs";
13576
14415
  var CREDENTIAL_SYNC_TICK_TIMEOUT_MS = 3e4;
@@ -13806,6 +14645,255 @@ function createCredentialSync({
13806
14645
  };
13807
14646
  }
13808
14647
 
14648
+ // src/lib/managed-inference-setup.ts
14649
+ var defaultStore = {
14650
+ get: getManagedInferenceCredential,
14651
+ set: setManagedInferenceCredential,
14652
+ clear: clearManagedInferenceCredential
14653
+ };
14654
+ function endpoint(options, operation) {
14655
+ return `${options.apiUrl}/runners/${options.agentId}/managed-credentials/${operation}`;
14656
+ }
14657
+ function requestHeaders(options) {
14658
+ return { Authorization: options.getAuthHeader(), "Content-Type": "application/json" };
14659
+ }
14660
+ function request2(options) {
14661
+ return withRequestTimeout(options.fetchImpl ?? fetch, REQUEST_TIMEOUT_MS);
14662
+ }
14663
+ function safeFailure(operation, status2) {
14664
+ return status2 === void 0 ? `${operation} could not reach the managed credential service` : `${operation} returned HTTP ${status2}`;
14665
+ }
14666
+ function logTransportFailure(options, operation, error2, authorization) {
14667
+ let detail = error2 instanceof Error ? error2.message : String(error2);
14668
+ const credential = authorization.split(/\s+/, 2)[1];
14669
+ for (const secret of [authorization, credential]) {
14670
+ if (secret) detail = detail.replaceAll(secret, "[redacted]");
14671
+ }
14672
+ options.log(
14673
+ `MANAGED-INFERENCE-TRANSPORT-FAILED: operation=${operation} detail=${redactDiagnosticText(detail)}`,
14674
+ "debug"
14675
+ );
14676
+ }
14677
+ async function responseJson(response) {
14678
+ try {
14679
+ const value = await response.json();
14680
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
14681
+ } catch (error2) {
14682
+ return null;
14683
+ }
14684
+ }
14685
+ function readSetup(value) {
14686
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
14687
+ const setup = value;
14688
+ if (typeof setup.runner_id !== "string" || typeof setup.generation !== "string" || typeof setup.status !== "string" || setup.token_id !== null && typeof setup.token_id !== "string" || typeof setup.acknowledged_at !== "string" && setup.acknowledged_at !== null) {
14689
+ return null;
14690
+ }
14691
+ return setup;
14692
+ }
14693
+ function clearLocalSetup(options) {
14694
+ const env = options.env ?? process.env;
14695
+ const store = options.store ?? defaultStore;
14696
+ const stored = store.get(options.agentId);
14697
+ store.clear(options.agentId);
14698
+ const token = env.EVIDENT_PROXY_TOKEN;
14699
+ if (token !== void 0 && token === stored?.token) {
14700
+ delete env.EVIDENT_PROXY_TOKEN;
14701
+ } else if (token !== void 0) {
14702
+ options.log(
14703
+ `MANAGED-INFERENCE-CLEANUP-PRESERVED-EXTERNAL-TOKEN: runner=${options.agentId}`,
14704
+ "debug"
14705
+ );
14706
+ }
14707
+ }
14708
+ async function discoverManagedInferenceSetup(options) {
14709
+ const headers = requestHeaders(options);
14710
+ let response;
14711
+ try {
14712
+ response = await request2(options)(endpoint(options, "current"), {
14713
+ headers
14714
+ });
14715
+ } catch (error2) {
14716
+ logTransportFailure(options, "discovery", error2, headers.Authorization);
14717
+ return { kind: "failed", reason: safeFailure("Managed setup discovery") };
14718
+ }
14719
+ if (response.status === 404) {
14720
+ clearLocalSetup(options);
14721
+ return { kind: "none" };
14722
+ }
14723
+ if (!response.ok) {
14724
+ return { kind: "failed", reason: safeFailure("Managed setup discovery", response.status) };
14725
+ }
14726
+ const body = await responseJson(response);
14727
+ const setup = readSetup(body?.setup);
14728
+ return setup ? { kind: "setup", setup } : { kind: "failed", reason: "Managed setup discovery returned an invalid response" };
14729
+ }
14730
+ async function enrollManagedInferenceSetup(options) {
14731
+ const response = await post(options, "enroll", {});
14732
+ if (!response) {
14733
+ return { kind: "failed", reason: safeFailure("Managed credential enrollment") };
14734
+ }
14735
+ if (response.status === 404) {
14736
+ return { kind: "none" };
14737
+ }
14738
+ const body = await responseJson(response);
14739
+ if (!response.ok) {
14740
+ const serverError = body?.error;
14741
+ return {
14742
+ kind: "failed",
14743
+ reason: typeof serverError === "string" && serverError.trim() ? redactDiagnosticText(serverError).slice(0, 500) : safeFailure("Managed credential enrollment", response.status)
14744
+ };
14745
+ }
14746
+ const setup = readSetup(body?.setup);
14747
+ return setup ? { kind: "setup", setup } : { kind: "failed", reason: "Managed credential enrollment returned an invalid response" };
14748
+ }
14749
+ async function post(options, operation, body) {
14750
+ const headers = requestHeaders(options);
14751
+ try {
14752
+ return await request2(options)(endpoint(options, operation), {
14753
+ method: "POST",
14754
+ headers,
14755
+ body: JSON.stringify(body)
14756
+ });
14757
+ } catch (error2) {
14758
+ logTransportFailure(options, operation, error2, headers.Authorization);
14759
+ return null;
14760
+ }
14761
+ }
14762
+ function applyCredential(options, credential) {
14763
+ const env = options.env ?? process.env;
14764
+ let merged;
14765
+ if (options.autoConfigureCredentialsProxy) {
14766
+ const { config, unsupportedProviders } = buildCredentialProxyNativeConfig({
14767
+ inferenceBaseUrl: credential.inferenceBaseUrl,
14768
+ modelDescriptors: credential.modelDescriptors
14769
+ });
14770
+ if (unsupportedProviders.length > 0 || Object.keys(config.provider).length === 0) {
14771
+ return "Managed setup contains unsupported provider descriptors";
14772
+ }
14773
+ try {
14774
+ merged = mergeManagedOpenCodeConfigContent(env.OPENCODE_CONFIG_CONTENT, config.provider);
14775
+ } catch (error2) {
14776
+ return "OPENCODE_CONFIG_CONTENT is malformed; managed providers were not installed";
14777
+ }
14778
+ }
14779
+ try {
14780
+ (options.store ?? defaultStore).set(options.agentId, credential);
14781
+ } catch (error2) {
14782
+ const detail = (error2 instanceof Error ? error2.message : String(error2)).replaceAll(
14783
+ credential.token,
14784
+ "[redacted]"
14785
+ );
14786
+ options.log(
14787
+ `MANAGED-INFERENCE-STORE-FAILED: runner=${options.agentId} generation=${credential.generation} detail=${redactDiagnosticText(detail)}`,
14788
+ "warn"
14789
+ );
14790
+ return "Managed credentials could not be written to the protected store";
14791
+ }
14792
+ env.EVIDENT_PROXY_TOKEN = credential.token;
14793
+ if (merged !== void 0) env.OPENCODE_CONFIG_CONTENT = merged;
14794
+ return null;
14795
+ }
14796
+ async function reportManagedInferenceApplyFailure(options, failure) {
14797
+ const response = await post(options, "report-failure", {
14798
+ generation: failure.generation,
14799
+ token_id: failure.tokenId
14800
+ });
14801
+ if (!response?.ok) {
14802
+ options.log(
14803
+ `MANAGED-INFERENCE-REPORT-FAILED: runner=${options.agentId} generation=${failure.generation}${response ? ` status=${response.status}` : ""}`,
14804
+ "warn"
14805
+ );
14806
+ }
14807
+ }
14808
+ async function reaffirmStored(options, credential) {
14809
+ const response = await post(options, "reaffirm", {
14810
+ generation: credential.generation,
14811
+ token_id: credential.tokenId
14812
+ });
14813
+ if (!response?.ok) {
14814
+ return {
14815
+ kind: "failed",
14816
+ reason: response?.status === 409 ? "Stored managed credential no longer matches this setup; restart required" : response ? safeFailure("Managed credential reaffirmation", response.status) : safeFailure("Managed credential reaffirmation")
14817
+ };
14818
+ }
14819
+ const body = await responseJson(response);
14820
+ const setup = readSetup(body?.setup);
14821
+ if (!setup || setup.generation !== credential.generation || setup.token_id !== credential.tokenId) {
14822
+ return { kind: "failed", reason: "Managed credential reaffirmation returned a mismatch" };
14823
+ }
14824
+ const failure = applyCredential(options, credential);
14825
+ return failure ? { kind: "failed", reason: failure } : { kind: "ready", generation: credential.generation, tokenId: credential.tokenId };
14826
+ }
14827
+ async function prepareManagedInferenceSetup(options, discovery, health) {
14828
+ if (health.authFailed) {
14829
+ return {
14830
+ kind: "failed",
14831
+ reason: "OpenCode authentication failed; managed credentials were not rotated"
14832
+ };
14833
+ }
14834
+ const stored = (options.store ?? defaultStore).get(options.agentId);
14835
+ if (health.healthy) {
14836
+ if (!stored) {
14837
+ return {
14838
+ kind: "failed",
14839
+ reason: "Managed credentials are missing for the serving OpenCode process; restart required"
14840
+ };
14841
+ }
14842
+ return reaffirmStored(options, stored);
14843
+ }
14844
+ if (discovery.setup.acknowledged_at !== null && stored?.generation === discovery.setup.generation && stored.tokenId === discovery.setup.token_id) {
14845
+ return reaffirmStored(options, stored);
14846
+ }
14847
+ const response = await post(options, "bootstrap", { generation: discovery.setup.generation });
14848
+ if (!response?.ok) {
14849
+ return {
14850
+ kind: "failed",
14851
+ reason: response ? safeFailure("Managed credential bootstrap", response.status) : safeFailure("Managed credential bootstrap")
14852
+ };
14853
+ }
14854
+ const body = await responseJson(response);
14855
+ const setup = readSetup(body?.setup);
14856
+ const token = body?.inference_token;
14857
+ const inferenceBaseUrl = body?.inference_base_url;
14858
+ const modelDescriptors = body?.model_descriptors;
14859
+ if (!setup || setup.generation !== discovery.setup.generation || typeof setup.token_id !== "string" || typeof token !== "string" || typeof inferenceBaseUrl !== "string" || !Array.isArray(modelDescriptors) || !modelDescriptors.every(
14860
+ (descriptor) => typeof descriptor === "object" && descriptor !== null && typeof descriptor.id === "string" && typeof descriptor.provider === "string"
14861
+ )) {
14862
+ return { kind: "failed", reason: "Managed credential bootstrap returned an invalid response" };
14863
+ }
14864
+ const credential = {
14865
+ generation: setup.generation,
14866
+ tokenId: setup.token_id,
14867
+ token,
14868
+ inferenceBaseUrl,
14869
+ modelDescriptors
14870
+ };
14871
+ const failure = applyCredential(options, credential);
14872
+ if (failure) {
14873
+ await reportManagedInferenceApplyFailure(options, credential);
14874
+ return { kind: "failed", reason: failure };
14875
+ }
14876
+ return { kind: "ready", generation: credential.generation, tokenId: credential.tokenId };
14877
+ }
14878
+ async function acknowledgeManagedInferenceSetup(options, preparation) {
14879
+ const response = await post(options, "acknowledge", {
14880
+ generation: preparation.generation,
14881
+ token_id: preparation.tokenId,
14882
+ config_validated: options.autoConfigureCredentialsProxy === true
14883
+ });
14884
+ if (!response?.ok) {
14885
+ throw new Error(
14886
+ response ? safeFailure("Managed credential acknowledgment", response.status) : safeFailure("Managed credential acknowledgment")
14887
+ );
14888
+ }
14889
+ const body = await responseJson(response);
14890
+ const setup = readSetup(body?.setup);
14891
+ if (!setup || setup.generation !== preparation.generation || setup.token_id !== preparation.tokenId) {
14892
+ throw new Error("Managed credential acknowledgment returned a mismatch");
14893
+ }
14894
+ return response.status === 200 && setup.status === "acknowledged" ? "acknowledged" : "pending";
14895
+ }
14896
+
13809
14897
  // src/commands/run.ts
13810
14898
  var MAX_ACTIVITY_LOG_ENTRIES = 10;
13811
14899
  var MAX_DISCONNECT_REASON_LENGTH = 200;
@@ -13846,7 +14934,7 @@ function resolveFileSyncDirectories(raw, homeDir) {
13846
14934
  if (trimmed === "") {
13847
14935
  throw new Error("--enable-file-sync-to requires a directory path (got an empty value)");
13848
14936
  }
13849
- const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ? join12(homeDir, trimmed.slice(2)) : trimmed;
14937
+ const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ? join13(homeDir, trimmed.slice(2)) : trimmed;
13850
14938
  if (!isAbsolute4(expanded)) {
13851
14939
  throw new Error(`--enable-file-sync-to requires an absolute directory path; got "${entry}"`);
13852
14940
  }
@@ -14001,7 +15089,7 @@ function logActivity(state, entry) {
14001
15089
  }
14002
15090
  function reportSessionDbRecovery(state) {
14003
15091
  try {
14004
- const report = drainSessionDbRecoveryReport({ homeDir: homedir7(), env: process.env });
15092
+ const report = drainSessionDbRecoveryReport({ homeDir: homedir8(), env: process.env });
14005
15093
  for (const record of report.records) {
14006
15094
  const activity = buildSessionDbRecoveryActivity(record);
14007
15095
  if (!activity) throw new Error("could not map session-DB recovery record");
@@ -14082,7 +15170,7 @@ async function importOpenCodeV2CredentialsForState(state) {
14082
15170
  }
14083
15171
  const result = await importOpenCodeV2Credentials({
14084
15172
  request: client.request,
14085
- homeDir: homedir7(),
15173
+ homeDir: homedir8(),
14086
15174
  directory,
14087
15175
  guard: state.opencodeV2CredentialImportGuard,
14088
15176
  log: (message, level = "info") => logActivity(state, { type: "info", level, message })
@@ -14502,7 +15590,7 @@ function shouldWarnForReclaimSkip(reason) {
14502
15590
  return major > 22 || major === 22 && (minor > 13 || minor === 13 && patch >= 0);
14503
15591
  }
14504
15592
  function sessionDbPath() {
14505
- return join12(homedir7(), ".local", "share", "opencode", "opencode.db");
15593
+ return join13(homedir8(), ".local", "share", "opencode", "opencode.db");
14506
15594
  }
14507
15595
  function logSessionCleanupActivity(state, phase, entry) {
14508
15596
  logActivity(state, {
@@ -14746,7 +15834,7 @@ function scheduleSessionCleanup(state, driver, options) {
14746
15834
  for (const warning2 of config.warnings) {
14747
15835
  logActivity(state, { type: "info", level: "warn", message: `Session cleanup: ${warning2}` });
14748
15836
  }
14749
- const dbBytes = statSessionDbBytes(homedir7());
15837
+ const dbBytes = statSessionDbBytes(homedir8());
14750
15838
  void (async () => {
14751
15839
  const reclaimAvailability = dbBytes !== null && config.enabled ? await probeReclaimAvailability({ dbPath: sessionDbPath(), requiredBytes: dbBytes }) : null;
14752
15840
  if (reclaimAvailability !== null) {
@@ -14965,7 +16053,7 @@ function scheduleResourceUsageReporting(state, options) {
14965
16053
  });
14966
16054
  return;
14967
16055
  }
14968
- const { collect, stop } = createResourceUsageCollector(homedir7());
16056
+ const { collect, stop } = createResourceUsageCollector(homedir8());
14969
16057
  state.stopResourceUsageSampling = stop;
14970
16058
  let consecutiveFailures = 0;
14971
16059
  const tick = async () => {
@@ -15206,7 +16294,7 @@ async function run(options) {
15206
16294
  let fileSyncDirectories;
15207
16295
  try {
15208
16296
  logLevel = resolveLogLevel(options);
15209
- fileSyncDirectories = resolveFileSyncDirectories(options.enableFileSyncTo, homedir7());
16297
+ fileSyncDirectories = resolveFileSyncDirectories(options.enableFileSyncTo, homedir8());
15210
16298
  if (options.restoreSessionDb && !options.sessionDbNoReplicateMarker) {
15211
16299
  throw new Error(
15212
16300
  "--restore-session-db requires --session-db-no-replicate-marker <path>; restore failures must block Litestream replication"
@@ -15492,7 +16580,7 @@ async function run(options) {
15492
16580
  state.credentialSync?.arm();
15493
16581
  const dependencyInstallDiagnosticReporter = createDependencyInstallDiagnosticReporter({
15494
16582
  context: {
15495
- homeDir: homedir7(),
16583
+ homeDir: homedir8(),
15496
16584
  cwd: process.cwd(),
15497
16585
  microvmId: process.env.MICROVM_ID?.trim(),
15498
16586
  euid: process.getuid?.(),
@@ -15515,7 +16603,7 @@ async function run(options) {
15515
16603
  dependencyInstallDiagnosticReporter
15516
16604
  );
15517
16605
  const openCodeLogTail = tailOpenCodeLogFile(
15518
- resolveOpenCodeLogPath(homedir7(), process.env),
16606
+ resolveOpenCodeLogPath(homedir8(), process.env),
15519
16607
  openCodeActivityForwarder
15520
16608
  );
15521
16609
  state.stopOpenCodeLogTail = () => {
@@ -15590,6 +16678,44 @@ async function run(options) {
15590
16678
  for (const warning2 of opencodeVersionWarnings) {
15591
16679
  logActivity(state, { type: "info", level: "warn", message: warning2 });
15592
16680
  }
16681
+ const managedSetupOptions = {
16682
+ agentId: state.agentId,
16683
+ apiUrl: getApiUrlConfig(),
16684
+ getAuthHeader: () => state.authHeader,
16685
+ autoConfigureCredentialsProxy: options.autoConfigureCredentialsProxy,
16686
+ log: (message, level = "info") => logActivity(state, {
16687
+ type: level === "error" ? "error" : "info",
16688
+ level,
16689
+ ...level === "error" ? { error: message } : { message }
16690
+ })
16691
+ };
16692
+ let managedDiscovery = await discoverManagedInferenceSetup(managedSetupOptions);
16693
+ if (managedDiscovery.kind === "none") {
16694
+ managedDiscovery = await enrollManagedInferenceSetup(managedSetupOptions);
16695
+ }
16696
+ let managedPreparation = { kind: "none" };
16697
+ if (managedDiscovery.kind === "failed") {
16698
+ managedPreparation = managedDiscovery;
16699
+ managedSetupOptions.log(`MANAGED-INFERENCE-NOT-READY: ${managedDiscovery.reason}`, "warn");
16700
+ } else if (managedDiscovery.kind === "setup") {
16701
+ const managedHealth = opencodeVersion === "v2" ? await checkOpenCode2Health(state.port) : await checkOpenCodeHealth(state.port);
16702
+ managedPreparation = await prepareManagedInferenceSetup(
16703
+ managedSetupOptions,
16704
+ managedDiscovery,
16705
+ managedHealth
16706
+ );
16707
+ if (managedPreparation.kind === "failed") {
16708
+ managedSetupOptions.log(
16709
+ `MANAGED-INFERENCE-NOT-READY: ${managedPreparation.reason}`,
16710
+ "warn"
16711
+ );
16712
+ } else if (managedPreparation.kind === "ready") {
16713
+ managedSetupOptions.log(
16714
+ `MANAGED-INFERENCE-PREPARED: runner=${state.agentId} generation=${managedPreparation.generation}`,
16715
+ "info"
16716
+ );
16717
+ }
16718
+ }
15593
16719
  const { value: maxActiveSessions, warnings: maxActiveSessionsWarnings } = resolveMaxActiveSessions(options, process.env);
15594
16720
  for (const warning2 of maxActiveSessionsWarnings) {
15595
16721
  logActivity(state, { type: "info", level: "warn", message: warning2 });
@@ -15616,6 +16742,32 @@ async function run(options) {
15616
16742
  state.opencodeProcess = options.opencodePidFile ? null : oc.process;
15617
16743
  state.opencodeVersion = oc.version;
15618
16744
  state.opencodeApiVersion = opencodeVersion;
16745
+ let configuredPlugins = [];
16746
+ let resolvedPlugins = [];
16747
+ try {
16748
+ const provenanceLog = (message) => logActivity(state, { type: "info", level: "warn", message });
16749
+ configuredPlugins = readConfiguredPluginSpecs({ log: provenanceLog });
16750
+ resolvedPlugins = resolveInstalledPluginVersions({ log: provenanceLog });
16751
+ } catch (error2) {
16752
+ const kind = error2 instanceof Error ? error2.name : "unknown error";
16753
+ logActivity(state, {
16754
+ type: "info",
16755
+ level: "warn",
16756
+ message: `Could not collect OpenCode runtime provenance: ${kind}`
16757
+ });
16758
+ }
16759
+ logActivity(state, {
16760
+ type: "info",
16761
+ level: "info",
16762
+ message: "OpenCode runtime provenance",
16763
+ metadata: {
16764
+ opencode_version: state.opencodeVersion,
16765
+ opencode_api_version: state.opencodeApiVersion,
16766
+ configured_plugins: JSON.stringify(configuredPlugins),
16767
+ resolved_plugins: JSON.stringify(resolvedPlugins)
16768
+ },
16769
+ source: "cli.runtime-provenance"
16770
+ });
15619
16771
  let opencodePassword = null;
15620
16772
  if (opencodeVersion === "v2" && "password" in oc) {
15621
16773
  const value = oc.password;
@@ -15644,7 +16796,7 @@ async function run(options) {
15644
16796
  const provenance = checkSessionDbProvenance({
15645
16797
  dbPath: sessionDbPath(),
15646
16798
  currentVersion: state.opencodeVersion,
15647
- homeDir: homedir7(),
16799
+ homeDir: homedir8(),
15648
16800
  env: process.env
15649
16801
  });
15650
16802
  if (provenance.anomaly) {
@@ -15658,6 +16810,30 @@ async function run(options) {
15658
16810
  }
15659
16811
  }
15660
16812
  state.opencodeConnected = oc.notReadyReason === null;
16813
+ if (managedPreparation.kind === "ready" && oc.notReadyReason === null) {
16814
+ try {
16815
+ const acknowledgement = await acknowledgeManagedInferenceSetup(
16816
+ managedSetupOptions,
16817
+ managedPreparation
16818
+ );
16819
+ if (acknowledgement === "acknowledged") {
16820
+ managedSetupOptions.log(
16821
+ `MANAGED-INFERENCE-ACKNOWLEDGED: runner=${state.agentId} generation=${managedPreparation.generation}`,
16822
+ "info"
16823
+ );
16824
+ } else {
16825
+ managedSetupOptions.log(
16826
+ `MANAGED-INFERENCE-NOT-READY: policy refresh is pending for runner=${state.agentId} generation=${managedPreparation.generation}`,
16827
+ "warn"
16828
+ );
16829
+ }
16830
+ } catch (error2) {
16831
+ managedSetupOptions.log(
16832
+ `MANAGED-INFERENCE-NOT-READY: ${error2 instanceof Error ? error2.message : String(error2)}`,
16833
+ "warn"
16834
+ );
16835
+ }
16836
+ }
15661
16837
  const version2 = state.opencodeVersion ? ` (v${state.opencodeVersion})` : "";
15662
16838
  ocSpinner?.succeed(`OpenCode running on port ${state.port}${version2}`);
15663
16839
  if (!state.interactive && oc.notReadyReason !== null) {
@@ -15716,7 +16892,7 @@ async function run(options) {
15716
16892
  let existingPid;
15717
16893
  if (existsSync3(options.litestreamPidFile)) {
15718
16894
  try {
15719
- const rawPid = readFileSync7(options.litestreamPidFile, "utf8").trim();
16895
+ const rawPid = readFileSync8(options.litestreamPidFile, "utf8").trim();
15720
16896
  const parsedPid = Number(rawPid);
15721
16897
  if (/^\d+$/.test(rawPid) && Number.isSafeInteger(parsedPid) && parsedPid > 0) {
15722
16898
  existingPid = parsedPid;
@@ -15815,7 +16991,7 @@ async function run(options) {
15815
16991
  // #559: the `--enable-file-sync-to` allow-list. Empty ⇒ pending files are
15816
16992
  // REJECTED with `file_sync_disabled` on the ack, not silently ignored.
15817
16993
  fileSyncDirectories,
15818
- homeDir: homedir7(),
16994
+ homeDir: homedir8(),
15819
16995
  maxActiveSessions,
15820
16996
  log: (entry) => (
15821
16997
  // Thread the driver's real level straight through so `debug`/`warn`
@@ -15840,7 +17016,7 @@ async function run(options) {
15840
17016
  onConnected: (agentId, isReconnect, transition) => {
15841
17017
  state.connected = true;
15842
17018
  state.agentId = agentId;
15843
- const connectionMessage = isReconnect ? `Tunnel reconnected (runner: ${agentId}, generation: ${transition.generation}, attempt: ${transition.attempt}, offline for ${transition.downtimeMs}ms)` : `Tunnel connected (runner: ${agentId}, generation: ${transition.generation})`;
17019
+ const connectionMessage = isReconnect ? `Tunnel reconnected (runner: ${agentId}, generation: ${transition.generation}, relay connection: ${transition.relayConnectionId ?? "unknown"}, attempt: ${transition.attempt}, offline for ${transition.downtimeMs}ms)` : `Tunnel connected (runner: ${agentId}, generation: ${transition.generation}, relay connection: ${transition.relayConnectionId ?? "unknown"})`;
15844
17020
  logActivity(state, {
15845
17021
  type: "info",
15846
17022
  message: connectionMessage
@@ -15873,6 +17049,7 @@ async function run(options) {
15873
17049
  connected: state.opencodeConnected
15874
17050
  }),
15875
17051
  connection_generation: transition.generation,
17052
+ ...transition.relayConnectionId !== null ? { relay_connection_id: transition.relayConnectionId } : {},
15876
17053
  ...isReconnect && transition.downtimeMs !== null ? {
15877
17054
  reconnect_attempt: transition.attempt,
15878
17055
  downtime_ms: transition.downtimeMs
@@ -15903,12 +17080,13 @@ async function run(options) {
15903
17080
  const safeReason2 = redactDiagnosticText(reason).slice(0, MAX_DISCONNECT_REASON_LENGTH);
15904
17081
  logActivity(state, {
15905
17082
  type: "info",
15906
- message: `Tunnel disconnected (generation: ${transition.generation}, code: ${code}, reason: ${safeReason2})`
17083
+ message: `Tunnel disconnected (generation: ${transition.generation}, relay connection: ${transition.relayConnectionId ?? "unknown"}, code: ${code}, reason: ${safeReason2})`
15907
17084
  });
15908
17085
  emitAgentDisconnected(state.agentId, {
15909
17086
  code,
15910
17087
  reason: safeReason2,
15911
- connection_generation: transition.generation
17088
+ connection_generation: transition.generation,
17089
+ ...transition.relayConnectionId !== null ? { relay_connection_id: transition.relayConnectionId } : {}
15912
17090
  });
15913
17091
  if (state.interactive) displayStatus(state);
15914
17092
  },
@@ -16059,9 +17237,9 @@ program.name("evident").description("Run OpenCode locally and connect it to Evid
16059
17237
  "--endpoint <url>",
16060
17238
  "Evident API base URL (default: production; e.g. http://localhost:3001)"
16061
17239
  ).option("--tunnel <url>", "Tunnel WebSocket URL (default: production; e.g. ws://localhost:8787)").hook("preAction", (thisCommand) => {
16062
- const { endpoint, tunnel } = thisCommand.opts();
16063
- if (endpoint) {
16064
- setEndpoint(endpoint);
17240
+ const { endpoint: endpoint2, tunnel } = thisCommand.opts();
17241
+ if (endpoint2) {
17242
+ setEndpoint(endpoint2);
16065
17243
  }
16066
17244
  if (tunnel) {
16067
17245
  setTunnelUrl(tunnel);
@@ -16135,6 +17313,9 @@ program.command("run").description("Connect to Evident and process messages").op
16135
17313
  ).option(
16136
17314
  "--opencode-config-overlay <path>",
16137
17315
  "Apply this runner-provided OpenCode config before starting OpenCode."
17316
+ ).option(
17317
+ "--auto-configure-credentials-proxy",
17318
+ "Install Evident's generated OpenCode providers for the runner's credential pool (off by default)."
16138
17319
  ).option(
16139
17320
  "--credential-sync-marker <path>",
16140
17321
  "Own the interval credential sync and write this marker once the shutdown flush has finished, so the runner image's lifecycle hooks can wait on it."
@@ -16178,6 +17359,7 @@ program.command("run").description("Connect to Evident and process messages").op
16178
17359
  restoreSessionDb: options.restoreSessionDb,
16179
17360
  restoreRunnerCredentials: options.restoreRunnerCredentials,
16180
17361
  opencodeConfigOverlay: options.opencodeConfigOverlay,
17362
+ autoConfigureCredentialsProxy: options.autoConfigureCredentialsProxy,
16181
17363
  credentialSyncMarker: options.credentialSyncMarker
16182
17364
  });
16183
17365
  }