@evident-ai/cli 3.6.0 → 3.6.1-dev.807424b

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
@@ -1156,9 +1156,9 @@ async function claudeUsage() {
1156
1156
  }
1157
1157
 
1158
1158
  // 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";
1159
+ import { chmodSync as chmodSync3, existsSync as existsSync3, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "node:fs";
1160
+ import { homedir as homedir8 } from "node:os";
1161
+ import { isAbsolute as isAbsolute4, join as join13, parse, resolve as resolvePath2 } from "node:path";
1162
1162
  import chalk7 from "chalk";
1163
1163
 
1164
1164
  // ../../packages/types/src/agents/index.ts
@@ -1326,7 +1326,7 @@ import ora4 from "ora";
1326
1326
  import { select as select4 } from "@inquirer/prompts";
1327
1327
 
1328
1328
  // src/lib/telemetry.ts
1329
- var CLI_VERSION = (true ? "3.6.0" : void 0) ?? process.env.npm_package_version ?? "unknown";
1329
+ var CLI_VERSION = (true ? "3.6.1-dev.807424b" : void 0) ?? process.env.npm_package_version ?? "unknown";
1330
1330
  function getCliVersion() {
1331
1331
  return CLI_VERSION;
1332
1332
  }
@@ -2075,7 +2075,8 @@ function createDependencyInstallDiagnosticReporter({
2075
2075
  var FORWARDED_LEVELS_BY_SOURCE = {
2076
2076
  "cli.run": /* @__PURE__ */ new Set(["warn", "error"]),
2077
2077
  opencode: /* @__PURE__ */ new Set(["warn", "error"]),
2078
- "cli.session-cleanup": /* @__PURE__ */ new Set(["info", "warn", "error"])
2078
+ "cli.session-cleanup": /* @__PURE__ */ new Set(["info", "warn", "error"]),
2079
+ "cli.runtime-provenance": /* @__PURE__ */ new Set(["info", "warn", "error"])
2079
2080
  };
2080
2081
  var SEVERITY_BY_LEVEL = {
2081
2082
  info: "info",
@@ -2871,6 +2872,58 @@ async function listAllSessions(port, client) {
2871
2872
  }
2872
2873
  return { sessions };
2873
2874
  }
2875
+ var INTERRUPT_NETWORK_CODES = /* @__PURE__ */ new Set([
2876
+ "ABORT_ERR",
2877
+ "EAI_AGAIN",
2878
+ "ECONNREFUSED",
2879
+ "ECONNRESET",
2880
+ "ENETUNREACH",
2881
+ "ENOTFOUND",
2882
+ "ETIMEDOUT",
2883
+ "UND_ERR_CONNECT_TIMEOUT",
2884
+ "UND_ERR_HEADERS_TIMEOUT",
2885
+ "UND_ERR_SOCKET"
2886
+ ]);
2887
+ function interruptFailureDiagnostic(error2) {
2888
+ const message = error2 instanceof Error ? error2.message : "";
2889
+ const httpStatus = /^OpenCode request failed: HTTP ([1-5]\d{2})(?::|$)/.exec(message)?.[1];
2890
+ if (httpStatus) return `HTTP ${httpStatus}`;
2891
+ const candidates = [error2, error2 instanceof Error ? error2.cause : void 0];
2892
+ for (const candidate of candidates) {
2893
+ if (typeof candidate !== "object" || candidate === null || !("code" in candidate)) continue;
2894
+ const code = candidate.code;
2895
+ if (typeof code === "string" && INTERRUPT_NETWORK_CODES.has(code)) {
2896
+ return redactDiagnosticText(`network ${code}`).slice(0, 120);
2897
+ }
2898
+ }
2899
+ const name = error2 instanceof Error && ["AbortError", "Error", "TimeoutError", "TypeError"].includes(error2.name) ? error2.name : "unknown error";
2900
+ return redactDiagnosticText(`request threw ${name}`).slice(0, 120);
2901
+ }
2902
+ async function interruptSession(port, id, client) {
2903
+ if (client?.version === "v2") {
2904
+ try {
2905
+ await interruptV2Session(client, id);
2906
+ return { kind: "confirmed_stopped" };
2907
+ } catch (error2) {
2908
+ return { kind: "not_confirmed", diagnostic: interruptFailureDiagnostic(error2) };
2909
+ }
2910
+ }
2911
+ try {
2912
+ const res = await requestWithClient(
2913
+ port,
2914
+ client,
2915
+ `/session/${id}/abort`,
2916
+ { method: "POST" },
2917
+ { allowStatuses: ALL_HTTP_STATUSES }
2918
+ );
2919
+ if (res.status >= 200 && res.status < 300) {
2920
+ return { kind: "confirmed_stopped", status: res.status };
2921
+ }
2922
+ return { kind: "not_confirmed", status: res.status };
2923
+ } catch (error2) {
2924
+ return { kind: "threw", diagnostic: interruptFailureDiagnostic(error2) };
2925
+ }
2926
+ }
2874
2927
  async function deleteSession(port, id, client) {
2875
2928
  if (client?.version === "v2") return deleteV2Session(client, id);
2876
2929
  try {
@@ -4221,6 +4274,11 @@ async function listV2Permissions(client, sessionId) {
4221
4274
  return null;
4222
4275
  }
4223
4276
  }
4277
+ async function interruptV2Session(client, sessionId) {
4278
+ await client.request(`/api/session/${encodeURIComponent(sessionId)}/interrupt`, {
4279
+ method: "POST"
4280
+ });
4281
+ }
4224
4282
  async function getV2ActiveSessions(client) {
4225
4283
  try {
4226
4284
  const body = await readJson(await client.request("/api/session/active"));
@@ -8891,6 +8949,34 @@ var ChannelDriver = class _ChannelDriver {
8891
8949
  if (state === "failed" && isAbortedTerminalReply(messages, ocId ?? "")) {
8892
8950
  const ongoing = await this.isSessionOngoing(sessionId);
8893
8951
  if (ongoing === false) {
8952
+ const age = this.absoluteAgeForRow(message.processing_started_at);
8953
+ if (age.tripped && age.anchorMs !== null) {
8954
+ try {
8955
+ await this.markFailed(
8956
+ conv.id,
8957
+ message.id,
8958
+ sessionId,
8959
+ this.absoluteCeilingError(message.id, sessionId, age.anchorMs),
8960
+ void 0,
8961
+ {
8962
+ classification: null,
8963
+ errorShape: extractReplyErrorShape(findLastAssistantReplyFor(messages, ocId ?? ""))
8964
+ }
8965
+ );
8966
+ } catch (err) {
8967
+ if (err instanceof ChannelAuthError) throw err;
8968
+ this.log({
8969
+ level: "warn",
8970
+ 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)}`,
8971
+ conversation_id: conv.id,
8972
+ message_id: message.id
8973
+ });
8974
+ return "unresolved";
8975
+ }
8976
+ this.clearRedriveUnresolved(message.id);
8977
+ void this.postSignal(conv.id, message.id, "redrive_ceiling_refused");
8978
+ return "settled";
8979
+ }
8894
8980
  this.log({
8895
8981
  level: "info",
8896
8982
  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,6 +9047,29 @@ var ChannelDriver = class _ChannelDriver {
8961
9047
  message_id: message.id
8962
9048
  });
8963
9049
  }
9050
+ if (this.absoluteAgeForRow(message.processing_started_at).tripped) {
9051
+ try {
9052
+ await this.markFailed(
9053
+ conv.id,
9054
+ message.id,
9055
+ sessionId,
9056
+ this.absoluteCeilingError(message.id, sessionId, anchorMs)
9057
+ );
9058
+ } catch (err) {
9059
+ if (err instanceof ChannelAuthError) throw err;
9060
+ this.log({
9061
+ level: "warn",
9062
+ 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)}`,
9063
+ conversation_id: conv.id,
9064
+ message_id: message.id
9065
+ });
9066
+ const bound = await this.boundRedriveOutcome(conv, message, "reattach");
9067
+ return bound === "abandoned" ? "abandoned" : "unresolved";
9068
+ }
9069
+ this.clearRedriveUnresolved(message.id);
9070
+ void this.postSignal(conv.id, message.id, "redrive_ceiling_refused");
9071
+ return "abandoned";
9072
+ }
8964
9073
  const title = await this.resolveSessionTitle(sessionId, conv.id);
8965
9074
  try {
8966
9075
  await this.markProcessing(conv.id, message.id, sessionId, ocId, title);
@@ -9227,8 +9336,7 @@ var ChannelDriver = class _ChannelDriver {
9227
9336
  const since = this.redriveOutcomeFailingSince.get(message.id);
9228
9337
  if (since === void 0) this.redriveOutcomeFailingSince.set(message.id, now);
9229
9338
  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;
9339
+ const absoluteAgeTripped = this.absoluteAgeForRow(message.processing_started_at).tripped;
9232
9340
  if (!durationTripped && !absoluteAgeTripped) {
9233
9341
  this.signalRedriveOutcomeUnreported(conv, message, outcome);
9234
9342
  return "retry";
@@ -9267,6 +9375,15 @@ var ChannelDriver = class _ChannelDriver {
9267
9375
  });
9268
9376
  return "abandoned";
9269
9377
  }
9378
+ /** Row-level guards use the durable server stamp; in-flight guards use processingAnchorMs. */
9379
+ absoluteAgeForRow(stamp) {
9380
+ const anchorMs = stamp ? Date.parse(stamp) : NaN;
9381
+ return Number.isNaN(anchorMs) ? { tripped: false, anchorMs: null } : { tripped: this.now() - anchorMs >= ABSOLUTE_MAX_PROCESSING_MS, anchorMs };
9382
+ }
9383
+ absoluteCeilingError(messageId, sessionId, anchorMs, retainedReason = this.readRetainedSessionError(sessionId)) {
9384
+ 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.`;
9385
+ return retainedReason ? `${baseError} ${SESSION_ERROR_RETAINED_DETAIL}${retainedReason}` : baseError;
9386
+ }
9270
9387
  /**
9271
9388
  * Record one poll outcome toward the re-drive fence's consecutive-identical-
9272
9389
  * failure streak (#1348) and return the resulting count. `signature === null`
@@ -9708,10 +9825,7 @@ var ChannelDriver = class _ChannelDriver {
9708
9825
  opencodeMessageId,
9709
9826
  message,
9710
9827
  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,
9828
+ processingAnchorMs: this.absoluteAgeForRow(message.processing_started_at).anchorMs ?? processedAtMs,
9715
9829
  deadline: watchDeadlineAnchorMs + this.pausedMaxWaitMs,
9716
9830
  // The server row is ALREADY `processing`; do not re-fire markProcessing.
9717
9831
  started: true,
@@ -10162,7 +10276,9 @@ var ChannelDriver = class _ChannelDriver {
10162
10276
  }
10163
10277
  const { openQuestions, openPermissions, questionsPolledOk, permissionsPolledOk } = await this.pollInteractions(sessionId, watcher, messages);
10164
10278
  if (watcher.generation !== generation) return;
10279
+ const ceilingAttemptsThisTick = /* @__PURE__ */ new Set();
10165
10280
  for (const inFlight of [...watcher.inFlight.values()]) {
10281
+ if (ceilingAttemptsThisTick.has(inFlight.evidentMessageId)) continue;
10166
10282
  await this.serviceInFlightMessage(
10167
10283
  sessionId,
10168
10284
  watcher,
@@ -10171,7 +10287,8 @@ var ChannelDriver = class _ChannelDriver {
10171
10287
  openQuestions,
10172
10288
  openPermissions,
10173
10289
  questionsPolledOk,
10174
- permissionsPolledOk
10290
+ permissionsPolledOk,
10291
+ ceilingAttemptsThisTick
10175
10292
  );
10176
10293
  }
10177
10294
  }
@@ -10212,13 +10329,117 @@ var ChannelDriver = class _ChannelDriver {
10212
10329
  inFlight.deadline = this.now() + this.pausedMaxWaitMs;
10213
10330
  }
10214
10331
  }
10332
+ async settleAbsoluteCeiling(sessionId, watcher, messages, ceilingAttemptsThisTick) {
10333
+ const entries = [...watcher.inFlight.values()];
10334
+ const pastCeiling = entries.filter(
10335
+ (entry) => this.now() - entry.processingAnchorMs >= ABSOLUTE_MAX_PROCESSING_MS
10336
+ );
10337
+ const healthySiblings = entries.length - pastCeiling.length;
10338
+ const landed = [];
10339
+ let allRecordsLanded = true;
10340
+ const retainedReason = this.readRetainedSessionError(sessionId);
10341
+ for (const entry of pastCeiling) {
10342
+ if (!watcher.inFlight.has(entry.evidentMessageId)) continue;
10343
+ ceilingAttemptsThisTick.add(entry.evidentMessageId);
10344
+ const error2 = this.absoluteCeilingError(
10345
+ entry.evidentMessageId,
10346
+ sessionId,
10347
+ entry.processingAnchorMs,
10348
+ retainedReason
10349
+ );
10350
+ this.log({
10351
+ level: "warn",
10352
+ message: error2,
10353
+ conversation_id: watcher.conv.id,
10354
+ message_id: entry.evidentMessageId
10355
+ });
10356
+ try {
10357
+ await this.markFailed(
10358
+ watcher.conv.id,
10359
+ entry.evidentMessageId,
10360
+ sessionId,
10361
+ error2,
10362
+ void 0,
10363
+ {
10364
+ classification: null,
10365
+ errorShape: extractReplyErrorShape(
10366
+ findLastAssistantReplyFor(messages, entry.opencodeMessageId)
10367
+ )
10368
+ }
10369
+ );
10370
+ } catch (err) {
10371
+ if (err instanceof ChannelAuthError) throw err;
10372
+ allRecordsLanded = false;
10373
+ const watched_for_ms = this.now() - entry.processingAnchorMs;
10374
+ if (err instanceof ChannelTerminalError) {
10375
+ void this.postSignal(watcher.conv.id, entry.evidentMessageId, "gave_up", {
10376
+ watched_for_ms,
10377
+ interrupt_outcome: "record_terminal"
10378
+ });
10379
+ this.removeInFlight(watcher, entry.evidentMessageId);
10380
+ continue;
10381
+ }
10382
+ if (this.now() >= entry.deadline) {
10383
+ void this.postSignal(watcher.conv.id, entry.evidentMessageId, "gave_up", {
10384
+ watched_for_ms,
10385
+ interrupt_outcome: "record_deadline"
10386
+ });
10387
+ this.removeInFlight(watcher, entry.evidentMessageId);
10388
+ continue;
10389
+ }
10390
+ void this.postSignal(watcher.conv.id, entry.evidentMessageId, "gave_up", {
10391
+ watched_for_ms,
10392
+ interrupt_outcome: "record_retry"
10393
+ });
10394
+ this.log({
10395
+ level: "warn",
10396
+ 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)}`,
10397
+ conversation_id: watcher.conv.id,
10398
+ message_id: entry.evidentMessageId
10399
+ });
10400
+ continue;
10401
+ }
10402
+ entry.done = true;
10403
+ landed.push(entry);
10404
+ }
10405
+ if (!allRecordsLanded) {
10406
+ for (const entry of landed) this.removeInFlight(watcher, entry.evidentMessageId);
10407
+ return;
10408
+ }
10409
+ if (healthySiblings > 0) {
10410
+ for (const entry of landed) {
10411
+ void this.postSignal(watcher.conv.id, entry.evidentMessageId, "gave_up", {
10412
+ watched_for_ms: this.now() - entry.processingAnchorMs,
10413
+ interrupt_outcome: "deferred_for_healthy_sibling",
10414
+ healthy_siblings: healthySiblings
10415
+ });
10416
+ this.removeInFlight(watcher, entry.evidentMessageId);
10417
+ }
10418
+ return;
10419
+ }
10420
+ const interrupt = await interruptSession(this.port, sessionId, this.openCodeClient);
10421
+ for (const entry of landed) {
10422
+ void this.postSignal(watcher.conv.id, entry.evidentMessageId, "gave_up", {
10423
+ watched_for_ms: this.now() - entry.processingAnchorMs,
10424
+ interrupt_outcome: interrupt.kind
10425
+ });
10426
+ this.removeInFlight(watcher, entry.evidentMessageId);
10427
+ }
10428
+ if (interrupt.kind !== "confirmed_stopped") {
10429
+ this.log({
10430
+ level: "warn",
10431
+ 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`,
10432
+ conversation_id: watcher.conv.id
10433
+ });
10434
+ }
10435
+ }
10215
10436
  /**
10216
10437
  * Drive ONE in-flight message's lifecycle from the tick's message snapshot.
10217
10438
  * Fires markProcessing on queued→running and markDone on done (each once),
10218
10439
  * applies the idle-path re-dispatch guard, and removes the message from the
10219
10440
  * in-flight set on completion or timeout.
10220
10441
  */
10221
- async serviceInFlightMessage(sessionId, watcher, inFlight, messages, openQuestions, openPermissions, questionsPolledOk, permissionsPolledOk) {
10442
+ async serviceInFlightMessage(sessionId, watcher, inFlight, messages, openQuestions, openPermissions, questionsPolledOk, permissionsPolledOk, ceilingAttemptsThisTick) {
10222
10443
  const conv = watcher.conv;
10223
10444
  const state = messageRunState(messages, inFlight.opencodeMessageId);
10224
10445
  const id = inFlight.evidentMessageId;
@@ -10562,17 +10783,8 @@ var ChannelDriver = class _ChannelDriver {
10562
10783
  return;
10563
10784
  }
10564
10785
  }
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);
10786
+ if (watcher.inFlight.has(inFlight.evidentMessageId) && activelyRunning && this.now() - inFlight.processingAnchorMs >= ABSOLUTE_MAX_PROCESSING_MS) {
10787
+ await this.settleAbsoluteCeiling(sessionId, watcher, messages, ceilingAttemptsThisTick);
10576
10788
  return;
10577
10789
  }
10578
10790
  const heartbeatDue = activelyRunning && !inFlight.awaitingHumanLatched && !inFlight.aliveInFlight && this.now() - inFlight.lastAliveAt >= HEARTBEAT_MS;
@@ -11195,6 +11407,33 @@ var ChannelDriver = class _ChannelDriver {
11195
11407
  }
11196
11408
  const restartAborted = state === "failed" && sessionOngoing === false && isAbortedTerminalReply(messages, ocId ?? "");
11197
11409
  if (restartAborted) {
11410
+ const age = this.absoluteAgeForRow(row.processing_started_at);
11411
+ if (age.tripped && age.anchorMs !== null) {
11412
+ try {
11413
+ await this.markFailed(
11414
+ row.conversation_id,
11415
+ row.id,
11416
+ sessionId,
11417
+ this.absoluteCeilingError(row.id, sessionId, age.anchorMs),
11418
+ void 0,
11419
+ {
11420
+ classification: null,
11421
+ errorShape: extractReplyErrorShape(findLastAssistantReplyFor(messages, ocId ?? ""))
11422
+ }
11423
+ );
11424
+ } catch (err) {
11425
+ if (err instanceof ChannelAuthError) throw err;
11426
+ this.log({
11427
+ level: "warn",
11428
+ 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)}`,
11429
+ conversation_id: row.conversation_id,
11430
+ message_id: row.id
11431
+ });
11432
+ return;
11433
+ }
11434
+ void this.postSignal(row.conversation_id, row.id, "redrive_ceiling_refused");
11435
+ return;
11436
+ }
11198
11437
  this.log({
11199
11438
  level: "info",
11200
11439
  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 +11979,8 @@ var ChannelDriver = class _ChannelDriver {
11740
11979
  source_message_id: row.source_message_id,
11741
11980
  slack_user_id: row.slack_user_id,
11742
11981
  attachments: row.attachments ?? null,
11743
- opencode_message_id: row.opencode_message_id
11982
+ opencode_message_id: row.opencode_message_id,
11983
+ processing_started_at: row.processing_started_at
11744
11984
  };
11745
11985
  }
11746
11986
  /**
@@ -13571,6 +13811,159 @@ function applyRunnerOpenCodeConfig({
13571
13811
  log3(`Applied runner OpenCode config ${source} to ${join11(cwd, target)}`);
13572
13812
  }
13573
13813
 
13814
+ // src/lib/opencode/runtime-provenance.ts
13815
+ import { readFileSync as readFileSync7, readdirSync } from "node:fs";
13816
+ import { homedir as homedir7 } from "node:os";
13817
+ import { join as join12 } from "node:path";
13818
+ var PACKAGE_NAME = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/i;
13819
+ var REGISTRY_SPEC = /^[a-z0-9.*+^~<>=|-]+$/i;
13820
+ function errorKind(error2) {
13821
+ if (!(error2 instanceof Error)) return "unknown error";
13822
+ const code = "code" in error2 && typeof error2.code === "string" ? ` (${error2.code})` : "";
13823
+ return `${error2.name}${code}`;
13824
+ }
13825
+ function parseRegistryPluginSpec(value) {
13826
+ const separator = value.lastIndexOf("@");
13827
+ if (separator <= 0) return null;
13828
+ const name = value.slice(0, separator);
13829
+ const spec = value.slice(separator + 1);
13830
+ if (!PACKAGE_NAME.test(name) || !REGISTRY_SPEC.test(spec)) return null;
13831
+ return { name, spec };
13832
+ }
13833
+ function parseJsonc(value) {
13834
+ let output = "";
13835
+ let inString = false;
13836
+ let escaped = false;
13837
+ let lineComment = false;
13838
+ let blockComment = false;
13839
+ for (let index = 0; index < value.length; index++) {
13840
+ const character = value[index];
13841
+ const next = value[index + 1];
13842
+ if (lineComment) {
13843
+ if (character === "\n") {
13844
+ lineComment = false;
13845
+ output += character;
13846
+ }
13847
+ continue;
13848
+ }
13849
+ if (blockComment) {
13850
+ if (character === "*" && next === "/") {
13851
+ blockComment = false;
13852
+ index++;
13853
+ } else if (character === "\n") {
13854
+ output += character;
13855
+ }
13856
+ continue;
13857
+ }
13858
+ if (inString) {
13859
+ output += character;
13860
+ if (escaped) escaped = false;
13861
+ else if (character === "\\") escaped = true;
13862
+ else if (character === '"') inString = false;
13863
+ continue;
13864
+ }
13865
+ if (character === '"') {
13866
+ inString = true;
13867
+ output += character;
13868
+ } else if (character === "/" && next === "/") {
13869
+ lineComment = true;
13870
+ index++;
13871
+ } else if (character === "/" && next === "*") {
13872
+ blockComment = true;
13873
+ index++;
13874
+ } else if (character === ",") {
13875
+ let lookahead = index + 1;
13876
+ while (/\s/.test(value[lookahead] ?? "")) lookahead++;
13877
+ if (value[lookahead] !== "]" && value[lookahead] !== "}") output += character;
13878
+ } else {
13879
+ output += character;
13880
+ }
13881
+ }
13882
+ return JSON.parse(output);
13883
+ }
13884
+ function openCodeCacheRoot(env = process.env, homeDir = homedir7()) {
13885
+ return join12(env.XDG_CACHE_HOME ?? join12(homeDir, ".cache"), "opencode");
13886
+ }
13887
+ function readConfiguredPluginSpecs({
13888
+ cwd = process.cwd(),
13889
+ log: log3
13890
+ }) {
13891
+ let config;
13892
+ try {
13893
+ try {
13894
+ config = parseJsonc(readFileSync7(join12(cwd, "opencode.jsonc"), "utf8"));
13895
+ } catch (error2) {
13896
+ if (!("code" in Object(error2)) || Object(error2).code !== "ENOENT") throw error2;
13897
+ config = parseJsonc(readFileSync7(join12(cwd, "opencode.json"), "utf8"));
13898
+ }
13899
+ } catch (error2) {
13900
+ log3(`Could not read OpenCode plugin configuration: ${errorKind(error2)}`);
13901
+ return [];
13902
+ }
13903
+ if (typeof config !== "object" || config === null || !("plugin" in config)) return [];
13904
+ const plugins = config.plugin;
13905
+ if (!Array.isArray(plugins)) {
13906
+ log3("Could not read OpenCode plugin configuration: plugin is not an array");
13907
+ return [];
13908
+ }
13909
+ const configured = plugins.filter(
13910
+ (plugin) => typeof plugin === "string" && parseRegistryPluginSpec(plugin) !== null
13911
+ );
13912
+ if (configured.length !== plugins.length) {
13913
+ log3(
13914
+ `Skipped ${plugins.length - configured.length} non-registry OpenCode plugin spec(s); their values were not recorded`
13915
+ );
13916
+ }
13917
+ return configured;
13918
+ }
13919
+ function resolveInstalledPluginVersions({
13920
+ cacheRoot,
13921
+ env = process.env,
13922
+ homeDir = homedir7(),
13923
+ log: log3
13924
+ }) {
13925
+ const packagesRoot = join12(cacheRoot ?? openCodeCacheRoot(env, homeDir), "packages");
13926
+ let directories;
13927
+ try {
13928
+ directories = readdirSync(packagesRoot, { withFileTypes: true });
13929
+ } catch (error2) {
13930
+ if ("code" in Object(error2) && Object(error2).code === "ENOENT") return [];
13931
+ log3(`Could not inspect the OpenCode plugin cache: ${errorKind(error2)}`);
13932
+ return [];
13933
+ }
13934
+ const plugins = [];
13935
+ let skipped = 0;
13936
+ for (const directory of directories) {
13937
+ if (!directory.isDirectory()) continue;
13938
+ const parsed = parseRegistryPluginSpec(directory.name);
13939
+ if (parsed === null) {
13940
+ skipped++;
13941
+ continue;
13942
+ }
13943
+ let resolvedVersion = null;
13944
+ try {
13945
+ const packageJson = JSON.parse(
13946
+ readFileSync7(
13947
+ join12(packagesRoot, directory.name, "node_modules", parsed.name, "package.json"),
13948
+ "utf8"
13949
+ )
13950
+ );
13951
+ if (typeof packageJson.version === "string" && REGISTRY_SPEC.test(packageJson.version)) {
13952
+ resolvedVersion = packageJson.version;
13953
+ } else {
13954
+ log3(`Could not resolve OpenCode plugin ${parsed.name}@${parsed.spec}: invalid version`);
13955
+ }
13956
+ } catch (error2) {
13957
+ log3(`Could not resolve OpenCode plugin ${parsed.name}@${parsed.spec}: ${errorKind(error2)}`);
13958
+ }
13959
+ plugins.push({ ...parsed, resolvedVersion });
13960
+ }
13961
+ if (skipped > 0) {
13962
+ log3(`Skipped ${skipped} non-registry OpenCode package cache entry; its value was not recorded`);
13963
+ }
13964
+ return plugins;
13965
+ }
13966
+
13574
13967
  // src/lib/credential-sync.ts
13575
13968
  import { renameSync, writeFileSync as writeFileSync5 } from "node:fs";
13576
13969
  var CREDENTIAL_SYNC_TICK_TIMEOUT_MS = 3e4;
@@ -13846,7 +14239,7 @@ function resolveFileSyncDirectories(raw, homeDir) {
13846
14239
  if (trimmed === "") {
13847
14240
  throw new Error("--enable-file-sync-to requires a directory path (got an empty value)");
13848
14241
  }
13849
- const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ? join12(homeDir, trimmed.slice(2)) : trimmed;
14242
+ const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ? join13(homeDir, trimmed.slice(2)) : trimmed;
13850
14243
  if (!isAbsolute4(expanded)) {
13851
14244
  throw new Error(`--enable-file-sync-to requires an absolute directory path; got "${entry}"`);
13852
14245
  }
@@ -14001,7 +14394,7 @@ function logActivity(state, entry) {
14001
14394
  }
14002
14395
  function reportSessionDbRecovery(state) {
14003
14396
  try {
14004
- const report = drainSessionDbRecoveryReport({ homeDir: homedir7(), env: process.env });
14397
+ const report = drainSessionDbRecoveryReport({ homeDir: homedir8(), env: process.env });
14005
14398
  for (const record of report.records) {
14006
14399
  const activity = buildSessionDbRecoveryActivity(record);
14007
14400
  if (!activity) throw new Error("could not map session-DB recovery record");
@@ -14082,7 +14475,7 @@ async function importOpenCodeV2CredentialsForState(state) {
14082
14475
  }
14083
14476
  const result = await importOpenCodeV2Credentials({
14084
14477
  request: client.request,
14085
- homeDir: homedir7(),
14478
+ homeDir: homedir8(),
14086
14479
  directory,
14087
14480
  guard: state.opencodeV2CredentialImportGuard,
14088
14481
  log: (message, level = "info") => logActivity(state, { type: "info", level, message })
@@ -14502,7 +14895,7 @@ function shouldWarnForReclaimSkip(reason) {
14502
14895
  return major > 22 || major === 22 && (minor > 13 || minor === 13 && patch >= 0);
14503
14896
  }
14504
14897
  function sessionDbPath() {
14505
- return join12(homedir7(), ".local", "share", "opencode", "opencode.db");
14898
+ return join13(homedir8(), ".local", "share", "opencode", "opencode.db");
14506
14899
  }
14507
14900
  function logSessionCleanupActivity(state, phase, entry) {
14508
14901
  logActivity(state, {
@@ -14746,7 +15139,7 @@ function scheduleSessionCleanup(state, driver, options) {
14746
15139
  for (const warning2 of config.warnings) {
14747
15140
  logActivity(state, { type: "info", level: "warn", message: `Session cleanup: ${warning2}` });
14748
15141
  }
14749
- const dbBytes = statSessionDbBytes(homedir7());
15142
+ const dbBytes = statSessionDbBytes(homedir8());
14750
15143
  void (async () => {
14751
15144
  const reclaimAvailability = dbBytes !== null && config.enabled ? await probeReclaimAvailability({ dbPath: sessionDbPath(), requiredBytes: dbBytes }) : null;
14752
15145
  if (reclaimAvailability !== null) {
@@ -14965,7 +15358,7 @@ function scheduleResourceUsageReporting(state, options) {
14965
15358
  });
14966
15359
  return;
14967
15360
  }
14968
- const { collect, stop } = createResourceUsageCollector(homedir7());
15361
+ const { collect, stop } = createResourceUsageCollector(homedir8());
14969
15362
  state.stopResourceUsageSampling = stop;
14970
15363
  let consecutiveFailures = 0;
14971
15364
  const tick = async () => {
@@ -15206,7 +15599,7 @@ async function run(options) {
15206
15599
  let fileSyncDirectories;
15207
15600
  try {
15208
15601
  logLevel = resolveLogLevel(options);
15209
- fileSyncDirectories = resolveFileSyncDirectories(options.enableFileSyncTo, homedir7());
15602
+ fileSyncDirectories = resolveFileSyncDirectories(options.enableFileSyncTo, homedir8());
15210
15603
  if (options.restoreSessionDb && !options.sessionDbNoReplicateMarker) {
15211
15604
  throw new Error(
15212
15605
  "--restore-session-db requires --session-db-no-replicate-marker <path>; restore failures must block Litestream replication"
@@ -15492,7 +15885,7 @@ async function run(options) {
15492
15885
  state.credentialSync?.arm();
15493
15886
  const dependencyInstallDiagnosticReporter = createDependencyInstallDiagnosticReporter({
15494
15887
  context: {
15495
- homeDir: homedir7(),
15888
+ homeDir: homedir8(),
15496
15889
  cwd: process.cwd(),
15497
15890
  microvmId: process.env.MICROVM_ID?.trim(),
15498
15891
  euid: process.getuid?.(),
@@ -15515,7 +15908,7 @@ async function run(options) {
15515
15908
  dependencyInstallDiagnosticReporter
15516
15909
  );
15517
15910
  const openCodeLogTail = tailOpenCodeLogFile(
15518
- resolveOpenCodeLogPath(homedir7(), process.env),
15911
+ resolveOpenCodeLogPath(homedir8(), process.env),
15519
15912
  openCodeActivityForwarder
15520
15913
  );
15521
15914
  state.stopOpenCodeLogTail = () => {
@@ -15616,6 +16009,32 @@ async function run(options) {
15616
16009
  state.opencodeProcess = options.opencodePidFile ? null : oc.process;
15617
16010
  state.opencodeVersion = oc.version;
15618
16011
  state.opencodeApiVersion = opencodeVersion;
16012
+ let configuredPlugins = [];
16013
+ let resolvedPlugins = [];
16014
+ try {
16015
+ const provenanceLog = (message) => logActivity(state, { type: "info", level: "warn", message });
16016
+ configuredPlugins = readConfiguredPluginSpecs({ log: provenanceLog });
16017
+ resolvedPlugins = resolveInstalledPluginVersions({ log: provenanceLog });
16018
+ } catch (error2) {
16019
+ const kind = error2 instanceof Error ? error2.name : "unknown error";
16020
+ logActivity(state, {
16021
+ type: "info",
16022
+ level: "warn",
16023
+ message: `Could not collect OpenCode runtime provenance: ${kind}`
16024
+ });
16025
+ }
16026
+ logActivity(state, {
16027
+ type: "info",
16028
+ level: "info",
16029
+ message: "OpenCode runtime provenance",
16030
+ metadata: {
16031
+ opencode_version: state.opencodeVersion,
16032
+ opencode_api_version: state.opencodeApiVersion,
16033
+ configured_plugins: JSON.stringify(configuredPlugins),
16034
+ resolved_plugins: JSON.stringify(resolvedPlugins)
16035
+ },
16036
+ source: "cli.runtime-provenance"
16037
+ });
15619
16038
  let opencodePassword = null;
15620
16039
  if (opencodeVersion === "v2" && "password" in oc) {
15621
16040
  const value = oc.password;
@@ -15644,7 +16063,7 @@ async function run(options) {
15644
16063
  const provenance = checkSessionDbProvenance({
15645
16064
  dbPath: sessionDbPath(),
15646
16065
  currentVersion: state.opencodeVersion,
15647
- homeDir: homedir7(),
16066
+ homeDir: homedir8(),
15648
16067
  env: process.env
15649
16068
  });
15650
16069
  if (provenance.anomaly) {
@@ -15716,7 +16135,7 @@ async function run(options) {
15716
16135
  let existingPid;
15717
16136
  if (existsSync3(options.litestreamPidFile)) {
15718
16137
  try {
15719
- const rawPid = readFileSync7(options.litestreamPidFile, "utf8").trim();
16138
+ const rawPid = readFileSync8(options.litestreamPidFile, "utf8").trim();
15720
16139
  const parsedPid = Number(rawPid);
15721
16140
  if (/^\d+$/.test(rawPid) && Number.isSafeInteger(parsedPid) && parsedPid > 0) {
15722
16141
  existingPid = parsedPid;
@@ -15815,7 +16234,7 @@ async function run(options) {
15815
16234
  // #559: the `--enable-file-sync-to` allow-list. Empty ⇒ pending files are
15816
16235
  // REJECTED with `file_sync_disabled` on the ack, not silently ignored.
15817
16236
  fileSyncDirectories,
15818
- homeDir: homedir7(),
16237
+ homeDir: homedir8(),
15819
16238
  maxActiveSessions,
15820
16239
  log: (entry) => (
15821
16240
  // Thread the driver's real level straight through so `debug`/`warn`