@evident-ai/cli 3.5.2-dev.03d3dab → 3.5.2-dev.06654ec

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
@@ -703,6 +703,31 @@ async function reportResourceUsage(agentId, authHeader, usage) {
703
703
  opencode_db_bytes: usage.opencodeDbBytes
704
704
  });
705
705
  }
706
+ async function getRunnerCredentialVault(agentId, authHeader) {
707
+ const empty = { prefix: null, vaultId: null, source: null };
708
+ try {
709
+ const apiUrl = getApiUrlConfig();
710
+ const response = await fetch(`${apiUrl}/runners/${agentId}/credential-vault`, {
711
+ headers: { Authorization: authHeader },
712
+ signal: AbortSignal.timeout(1e4)
713
+ });
714
+ if (!response.ok) {
715
+ const serverMessage = await readErrorMessage(response);
716
+ return {
717
+ ...empty,
718
+ error: `HTTP ${response.status}${serverMessage ? `: ${serverMessage}` : ""}`
719
+ };
720
+ }
721
+ const data = await response.json();
722
+ return {
723
+ prefix: data.credential_vault_prefix,
724
+ vaultId: data.vault_id,
725
+ source: data.source
726
+ };
727
+ } catch (error2) {
728
+ return { ...empty, error: describeTimeoutError(error2, 1e4) };
729
+ }
730
+ }
706
731
  async function getAgentInfo(agentId, authHeader) {
707
732
  const apiUrl = getApiUrlConfig();
708
733
  try {
@@ -877,6 +902,76 @@ async function status(options = {}) {
877
902
  process.exit(result.exitCode);
878
903
  }
879
904
 
905
+ // src/commands/credential-vault-env.ts
906
+ function shellQuote(value) {
907
+ return `'${value.replaceAll("'", "'\\''")}'`;
908
+ }
909
+ function printAssignment(prefix) {
910
+ console.log(`CREDENTIAL_VAULT_PREFIX=${shellQuote(prefix ?? "")}`);
911
+ }
912
+ function safeReason(reason, credentials2, authHeader) {
913
+ let safe = reason;
914
+ for (const secret of [
915
+ authHeader,
916
+ credentials2?.token,
917
+ process.env.EVIDENT_RUNNER_KEY,
918
+ process.env.EVIDENT_AGENT_KEY,
919
+ process.env.EVIDENT_TOKEN
920
+ ]) {
921
+ if (secret) safe = safe.replaceAll(secret, "[redacted]");
922
+ }
923
+ return safe.replace(/[\r\n]+/g, " ");
924
+ }
925
+ function printFallback(reason, credentials2, authHeader) {
926
+ printAssignment(null);
927
+ console.error(
928
+ `WARNING: ${safeReason(reason, credentials2, authHeader)}; using this runner's own credential prefix.`
929
+ );
930
+ }
931
+ async function credentialVaultEnv() {
932
+ let credentials2 = null;
933
+ let authHeader;
934
+ try {
935
+ credentials2 = await getAuthCredentials();
936
+ if (!credentials2) {
937
+ printFallback("no runner credentials are configured");
938
+ return;
939
+ }
940
+ authHeader = getAuthHeader(credentials2);
941
+ const runner = await resolveAgentIdFromKey(authHeader);
942
+ if (!runner.agent_id) {
943
+ printFallback(
944
+ runner.error ?? "the runner identity could not be resolved",
945
+ credentials2,
946
+ authHeader
947
+ );
948
+ return;
949
+ }
950
+ const lookup = await getRunnerCredentialVault(runner.agent_id, authHeader);
951
+ if (lookup.error) {
952
+ printFallback(`credential vault lookup failed: ${lookup.error}`, credentials2, authHeader);
953
+ return;
954
+ }
955
+ if (lookup.prefix === null) {
956
+ printFallback("this runner is not linked to a credential vault", credentials2, authHeader);
957
+ return;
958
+ }
959
+ if (/[\r\n]/.test(lookup.prefix)) {
960
+ printFallback("credential vault lookup returned an invalid prefix", credentials2, authHeader);
961
+ return;
962
+ }
963
+ printAssignment(lookup.prefix);
964
+ console.error(
965
+ `VAULT-MODE: credential stores use vault=${lookup.vaultId} source=${lookup.source} prefix=${lookup.prefix}.`
966
+ );
967
+ } catch (error2) {
968
+ const detail = credentials2 === null ? "credential resolution failed" : `credential vault lookup could not be completed: ${error2 instanceof Error ? error2.message : String(error2)}`;
969
+ printFallback(detail, credentials2, authHeader);
970
+ } finally {
971
+ process.exitCode = 0;
972
+ }
973
+ }
974
+
880
975
  // src/lib/claude-usage.ts
881
976
  import { execFileSync } from "node:child_process";
882
977
  import { readFileSync } from "node:fs";
@@ -1227,7 +1322,7 @@ import ora4 from "ora";
1227
1322
  import { select as select4 } from "@inquirer/prompts";
1228
1323
 
1229
1324
  // src/lib/telemetry.ts
1230
- var CLI_VERSION = (true ? "3.5.2-dev.03d3dab" : void 0) ?? process.env.npm_package_version ?? "unknown";
1325
+ var CLI_VERSION = (true ? "3.5.2-dev.06654ec" : void 0) ?? process.env.npm_package_version ?? "unknown";
1231
1326
  function getCliVersion() {
1232
1327
  return CLI_VERSION;
1233
1328
  }
@@ -2621,18 +2716,16 @@ function findDispatchedUserMessageId(messages, content, excludeIds) {
2621
2716
  }
2622
2717
  return best?.id ?? null;
2623
2718
  }
2624
- async function sendPromptAsync(port, sessionId, content, options, attachments, client, onKnownUserIds) {
2719
+ async function sendPromptAsync(port, sessionId, content, options, attachments, client, callbacks) {
2625
2720
  const before = await getSessionMessages(port, sessionId, client);
2626
2721
  const knownUserIds = new Set(
2627
2722
  (before ?? []).filter((m) => roleOf2(m) === "user").map((m) => idOf2(m)).filter((id) => typeof id === "string")
2628
2723
  );
2629
- if (onKnownUserIds) {
2724
+ if (callbacks) {
2630
2725
  try {
2631
- onKnownUserIds(before === null ? null : knownUserIds);
2726
+ callbacks.onKnownUserIds(before === null ? null : knownUserIds);
2632
2727
  } catch (error2) {
2633
- console.error(
2634
- `[sendPromptAsync] pre-dispatch snapshot callback failed: ${error2 instanceof Error ? error2.message : String(error2)}`
2635
- );
2728
+ callbacks.onKnownUserIdsError(error2);
2636
2729
  }
2637
2730
  }
2638
2731
  const parts = [{ type: "text", text: content }];
@@ -7112,6 +7205,8 @@ var ChannelDriver = class _ChannelDriver {
7112
7205
  * row is `processing` or `pending`; clear it only when absent from both lists.
7113
7206
  */
7114
7207
  untrackableAck = /* @__PURE__ */ new Map();
7208
+ /** Session binding used by the first untrackable-ack report, for retries. */
7209
+ untrackableAckSessionIds = /* @__PURE__ */ new Map();
7115
7210
  /** Messages whose untrackable-ack failure signal was emitted while fenced. */
7116
7211
  untrackableAckSignalled = /* @__PURE__ */ new Set();
7117
7212
  /** Messages whose first untrackable-ack fence warning was already logged. */
@@ -7561,6 +7656,7 @@ var ChannelDriver = class _ChannelDriver {
7561
7656
  const dispatchDescription = source === "readopt" ? "re-dispatched message" : "message";
7562
7657
  const errorMessage3 = `OpenCode accepted this ${dispatchDescription} once, but its assigned id was never confirmed after ${attempts} attempts to match it up \u2014 the message was deliberately not sent again, so no duplicate turn was created; session ${sessionId.slice(0, 8)} was abandoned; a fresh one is used for further messages.`;
7563
7658
  this.untrackableAck.set(messageId, errorMessage3);
7659
+ this.untrackableAckSessionIds.set(messageId, null);
7564
7660
  this.log({
7565
7661
  level: "error",
7566
7662
  message: errorMessage3,
@@ -7866,8 +7962,18 @@ var ChannelDriver = class _ChannelDriver {
7866
7962
  options,
7867
7963
  sendAttachments,
7868
7964
  this.openCodeClient,
7869
- (ids) => {
7870
- preDispatchUserIds = ids;
7965
+ {
7966
+ onKnownUserIds: (ids) => {
7967
+ preDispatchUserIds = ids;
7968
+ },
7969
+ onKnownUserIdsError: (error2) => {
7970
+ this.log({
7971
+ level: "warn",
7972
+ message: `Pre-dispatch snapshot callback failed for message ${message.id.slice(0, 8)}: ${error2 instanceof Error ? error2.message : String(error2)}`,
7973
+ conversation_id: conv.id,
7974
+ message_id: message.id
7975
+ });
7976
+ }
7871
7977
  }
7872
7978
  )
7873
7979
  );
@@ -7876,6 +7982,7 @@ var ChannelDriver = class _ChannelDriver {
7876
7982
  if (this.isV2 && err instanceof OpenCodeV2PromptAckError) {
7877
7983
  const errorMessage4 = err instanceof Error ? err.message : String(err);
7878
7984
  this.untrackableAck.set(message.id, errorMessage4);
7985
+ this.untrackableAckSessionIds.set(message.id, null);
7879
7986
  this.log({
7880
7987
  level: "error",
7881
7988
  message: `V2 prompt dispatch for message ${message.id.slice(0, 8)} failed after a positive ack with no usable id: ${errorMessage4}`,
@@ -10171,6 +10278,7 @@ var ChannelDriver = class _ChannelDriver {
10171
10278
  for (const id of [...this.untrackableAck.keys()]) {
10172
10279
  if (!stillProcessing.has(id) && !this.pendingMessageIds.has(id)) {
10173
10280
  this.untrackableAck.delete(id);
10281
+ this.untrackableAckSessionIds.delete(id);
10174
10282
  this.untrackableAckSignalled.delete(id);
10175
10283
  this.untrackableAckWarned.delete(id);
10176
10284
  this.log({
@@ -10319,8 +10427,10 @@ var ChannelDriver = class _ChannelDriver {
10319
10427
  * Branches on `messageRunState(messages, row.opencode_message_id)` — the
10320
10428
  * opencode-assigned user-message id persisted on the first `processing` PATCH
10321
10429
  * (#218). A row with a NULL stored id (dispatched but the read-back never landed
10322
- * before the restart) has no id to correlate → treated as an orphan and
10323
- * re-dispatched (at most once, see `forceReadoptRun`):
10430
+ * before the restart) is first offered to an awaiting-correlation fence recorded
10431
+ * by this process; without that fence, it has no id to correlate after restart
10432
+ * and is treated as an orphan and re-dispatched (at most once, see
10433
+ * `forceReadoptRun`):
10324
10434
  * - `done` → `markDone` now (guarded like the watcher's done branch);
10325
10435
  * - `failed` → `markFailed` with the surfaced error (issue #182), so an
10326
10436
  * errored turn is reported failed on restart, NOT re-dispatched —
@@ -10329,7 +10439,8 @@ var ChannelDriver = class _ChannelDriver {
10329
10439
  * re-dispatched instead (issue #1310, see the branch below);
10330
10440
  * - `running`/`queued` → re-attach a watcher via `registerReadopted` (no re-dispatch),
10331
10441
  * tracking the stored id so the reply correlates by it;
10332
- * - `unknown`/null id → re-dispatch (opencode assigns a fresh id) + attach a watcher.
10442
+ * - `unknown`/null id → correlate through an awaiting fence when present;
10443
+ * otherwise re-dispatch (opencode assigns a fresh id) + attach a watcher.
10333
10444
  *
10334
10445
  * Only `ChannelAuthError` propagates.
10335
10446
  */
@@ -10410,12 +10521,48 @@ var ChannelDriver = class _ChannelDriver {
10410
10521
  await this.reportSubagentAuthFailures(row.conversation_id, ocId ?? "", row.id, messages);
10411
10522
  this.dontRedispatch.delete(row.id);
10412
10523
  this.untrackableAck.delete(row.id);
10524
+ this.untrackableAckSessionIds.delete(row.id);
10413
10525
  this.untrackableAckSignalled.delete(row.id);
10414
10526
  this.untrackableAckWarned.delete(row.id);
10415
10527
  void this.postSignal(row.conversation_id, row.id, "readopt_failed");
10416
10528
  return;
10417
10529
  }
10418
- if (this.dontRedispatch.has(row.id) || this.untrackableAck.has(row.id)) {
10530
+ if (this.untrackableAck.has(row.id)) {
10531
+ if (this.untrackableAckSignalled.has(row.id)) {
10532
+ const firstFenceWarning = !this.untrackableAckWarned.has(row.id);
10533
+ this.untrackableAckWarned.add(row.id);
10534
+ this.log({
10535
+ level: firstFenceWarning ? "warn" : "debug",
10536
+ message: `Re-adopt: message ${row.id.slice(0, 8)} is fenced after an untrackable OpenCode turn \u2014 skipping re-dispatch and retrying the terminal report`,
10537
+ conversation_id: row.conversation_id,
10538
+ message_id: row.id
10539
+ });
10540
+ const errorMessage3 = this.untrackableAck.get(row.id);
10541
+ const reportError = await this.reportUntrackableAck(
10542
+ row.conversation_id,
10543
+ row.id,
10544
+ this.untrackableAckSessionIds.get(row.id) ?? null,
10545
+ errorMessage3
10546
+ );
10547
+ if (reportError !== null) {
10548
+ this.log({
10549
+ level: "warn",
10550
+ message: `Re-adopt: failed to report untrackable message ${row.id.slice(0, 8)} (will retry next drain): ${reportError.message}`,
10551
+ conversation_id: row.conversation_id,
10552
+ message_id: row.id
10553
+ });
10554
+ }
10555
+ } else {
10556
+ this.log({
10557
+ level: "debug",
10558
+ message: `Re-adopt: message ${row.id.slice(0, 8)} already gave up \u2014 left to the cron; skipping until it leaves processing`,
10559
+ conversation_id: row.conversation_id,
10560
+ message_id: row.id
10561
+ });
10562
+ }
10563
+ return;
10564
+ }
10565
+ if (this.dontRedispatch.has(row.id)) {
10419
10566
  this.log({
10420
10567
  level: "debug",
10421
10568
  message: `Re-adopt: message ${row.id.slice(0, 8)} already gave up \u2014 left to the cron; skipping until it leaves processing`,
@@ -10593,6 +10740,7 @@ var ChannelDriver = class _ChannelDriver {
10593
10740
  }
10594
10741
  this.dontRedispatch.delete(row.id);
10595
10742
  this.untrackableAck.delete(row.id);
10743
+ this.untrackableAckSessionIds.delete(row.id);
10596
10744
  this.untrackableAckSignalled.delete(row.id);
10597
10745
  this.untrackableAckWarned.delete(row.id);
10598
10746
  void this.postSignal(row.conversation_id, row.id, "readopt_done");
@@ -10688,8 +10836,18 @@ var ChannelDriver = class _ChannelDriver {
10688
10836
  options,
10689
10837
  sendAttachments,
10690
10838
  this.openCodeClient,
10691
- (ids) => {
10692
- preDispatchUserIds = ids;
10839
+ {
10840
+ onKnownUserIds: (ids) => {
10841
+ preDispatchUserIds = ids;
10842
+ },
10843
+ onKnownUserIdsError: (error2) => {
10844
+ this.log({
10845
+ level: "warn",
10846
+ message: `Pre-dispatch snapshot callback failed for message ${row.id.slice(0, 8)}: ${error2 instanceof Error ? error2.message : String(error2)}`,
10847
+ conversation_id: row.conversation_id,
10848
+ message_id: row.id
10849
+ });
10850
+ }
10693
10851
  }
10694
10852
  )
10695
10853
  );
@@ -10723,6 +10881,7 @@ var ChannelDriver = class _ChannelDriver {
10723
10881
  }
10724
10882
  } else {
10725
10883
  this.untrackableAck.set(row.id, errorMessage3);
10884
+ this.untrackableAckSessionIds.set(row.id, sessionId);
10726
10885
  }
10727
10886
  this.log({
10728
10887
  level: "error",
@@ -14263,6 +14422,34 @@ async function run(options) {
14263
14422
  } else {
14264
14423
  log2(state, "Not running in a MicroVM (MICROVM_ID unset) \u2014 nothing to report", "debug");
14265
14424
  }
14425
+ if (options.restoreRunnerCredentials || options.credentialSyncMarker) {
14426
+ const logCredentialVaultResolution = (message, level) => {
14427
+ log2(state, message, level);
14428
+ if (state.interactive && !state.json) {
14429
+ logActivity(state, { type: "info", level, message });
14430
+ }
14431
+ };
14432
+ const vaultResolution = await getRunnerCredentialVault(state.agentId, state.authHeader);
14433
+ if (vaultResolution.error) {
14434
+ delete process.env.CREDENTIAL_VAULT_PREFIX;
14435
+ logCredentialVaultResolution(
14436
+ `CREDENTIAL-VAULT-LOOKUP-FAILED: ${vaultResolution.error}; credentials will be read from and written to this runner's own prefix this boot`,
14437
+ "warn"
14438
+ );
14439
+ } else if (vaultResolution.prefix !== null && vaultResolution.vaultId !== null && vaultResolution.source !== null) {
14440
+ process.env.CREDENTIAL_VAULT_PREFIX = vaultResolution.prefix;
14441
+ logCredentialVaultResolution(
14442
+ `CREDENTIAL-VAULT: source=${vaultResolution.source} vault=${vaultResolution.vaultId} prefix=${vaultResolution.prefix}`,
14443
+ "info"
14444
+ );
14445
+ } else {
14446
+ delete process.env.CREDENTIAL_VAULT_PREFIX;
14447
+ logCredentialVaultResolution(
14448
+ "CREDENTIAL-VAULT-NONE: this runner is not linked to a credential vault; using its own prefix",
14449
+ "debug"
14450
+ );
14451
+ }
14452
+ }
14266
14453
  if (options.restoreRunnerCredentials) {
14267
14454
  log2(state, "Restoring runner credentials before starting OpenCode");
14268
14455
  const credentialContext = {
@@ -14660,14 +14847,14 @@ async function run(options) {
14660
14847
  },
14661
14848
  onDisconnected: (code, reason, transition) => {
14662
14849
  state.connected = false;
14663
- const safeReason = redactDiagnosticText(reason).slice(0, MAX_DISCONNECT_REASON_LENGTH);
14850
+ const safeReason2 = redactDiagnosticText(reason).slice(0, MAX_DISCONNECT_REASON_LENGTH);
14664
14851
  logActivity(state, {
14665
14852
  type: "info",
14666
- message: `Tunnel disconnected (generation: ${transition.generation}, code: ${code}, reason: ${safeReason})`
14853
+ message: `Tunnel disconnected (generation: ${transition.generation}, code: ${code}, reason: ${safeReason2})`
14667
14854
  });
14668
14855
  emitAgentDisconnected(state.agentId, {
14669
14856
  code,
14670
- reason: safeReason,
14857
+ reason: safeReason2,
14671
14858
  connection_generation: transition.generation
14672
14859
  });
14673
14860
  if (state.interactive) displayStatus(state);
@@ -14830,6 +15017,7 @@ program.command("login").description("Authenticate with Evident").option("--toke
14830
15017
  program.command("logout").description("Remove stored credentials for the current endpoint").option("--all", "Remove stored credentials for all endpoints").action((options) => logout({ all: options.all }));
14831
15018
  program.command("whoami").description("Show the currently logged in user").action(whoami);
14832
15019
  program.command("status").description("Check whether the configured credentials can reach Evident").option("--json", "Output in JSON format").action((options) => status({ json: options.json }));
15020
+ program.command("credential-vault-env").description("Print the shell assignment a runner image evals before starting the synchroniser").action(credentialVaultEnv);
14833
15021
  program.command("claude-usage").description("[spike] Show Claude subscription usage (requires a local `claude login`)").action(claudeUsage);
14834
15022
  program.command("run").description("Connect to Evident and process messages").option("--runner [id]", "Runner ID to connect to (optional when EVIDENT_RUNNER_KEY is set)").option(
14835
15023
  "-a, --agent [id]",