@evident-ai/cli 3.1.1-dev.702ee74 → 3.1.1-dev.716aea1

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/README.md CHANGED
@@ -85,6 +85,7 @@ Options:
85
85
 
86
86
  - `-a, --agent [id]` — Runner ID to connect to. Optional when `EVIDENT_AGENT_KEY`
87
87
  is set (the runner is then resolved automatically from the key).
88
+ - `--runner [id]` — Alias for `--agent` (preferred name; wins if both are given).
88
89
  - `-p, --port <port>` — OpenCode port (default: `4096`).
89
90
  - `--log-level <level>` — Log verbosity: `debug | info | warn | error` (default:
90
91
  `info`). Env: `EVIDENT_LOG_LEVEL`.
@@ -105,15 +106,18 @@ targets the **production** Evident platform by default.
105
106
 
106
107
  ## Environment variables
107
108
 
108
- - `EVIDENT_AGENT_KEY` — A runner key. When set, `evident run` authenticates as
109
- that runner and resolves the runner ID automatically, so `--agent` is not
110
- required. Ideal for CI/CD.
109
+ - `EVIDENT_RUNNER_KEY` — A runner key. When set, `evident run` authenticates as
110
+ that runner and resolves the runner ID automatically, so `--runner`/`--agent`
111
+ is not required. Ideal for CI/CD. Preferred name; wins over `EVIDENT_AGENT_KEY`
112
+ if both are set.
113
+ - `EVIDENT_AGENT_KEY` — Alias for `EVIDENT_RUNNER_KEY` (still fully supported).
111
114
  - `EVIDENT_TOKEN` — A user token used for authentication (alternative to a
112
115
  keychain login from `evident login`).
113
116
  - `EVIDENT_API_URL` — Override the API base URL (equivalent to `--endpoint`).
114
117
  - `EVIDENT_TUNNEL_URL` — Override the tunnel relay URL (equivalent to `--tunnel`).
115
118
 
116
- Authentication precedence for `run`: `EVIDENT_AGENT_KEY` → `EVIDENT_TOKEN` →
119
+ Authentication precedence for `run`: `EVIDENT_RUNNER_KEY`/`EVIDENT_AGENT_KEY`
120
+ (tied; `EVIDENT_RUNNER_KEY` wins if both are set) → `EVIDENT_TOKEN` →
117
121
  credentials stored by `evident login`. For the URL flags, an explicit
118
122
  `--endpoint` / `--tunnel` flag wins over the matching environment variable, which
119
123
  in turn overrides the production default.
@@ -150,6 +154,8 @@ For CI or unattended use, set `EVIDENT_AGENT_KEY` and omit `--agent`:
150
154
  EVIDENT_AGENT_KEY=<agent-key> evident run --idle-timeout 30
151
155
  ```
152
156
 
157
+ (`EVIDENT_RUNNER_KEY` is equivalent and the preferred name — use whichever you like.)
158
+
153
159
  ## How it works
154
160
 
155
161
  ```
package/dist/index.js CHANGED
@@ -645,14 +645,27 @@ var EventTypes = {
645
645
  // CLI lifecycle
646
646
  CLI_STARTED: "cli.started",
647
647
  CLI_COMMAND: "cli.command",
648
- CLI_ERROR: "cli.error"
648
+ CLI_ERROR: "cli.error",
649
+ // Deprecation telemetry (#412) — usage of the old `--agent`/`EVIDENT_AGENT_KEY`
650
+ // names instead of the preferred `--runner`/`EVIDENT_RUNNER_KEY` (#409).
651
+ DEPRECATED_AGENT_FLAG_USED: "cli.deprecated_agent_flag_used",
652
+ DEPRECATED_AGENT_KEY_ENV_USED: "cli.deprecated_agent_key_env_used"
649
653
  };
650
654
 
651
655
  // src/lib/auth.ts
652
656
  async function getAuthCredentials() {
657
+ const runnerKey = process.env.EVIDENT_RUNNER_KEY;
653
658
  const agentKey = process.env.EVIDENT_AGENT_KEY;
659
+ if (runnerKey) {
660
+ return {
661
+ token: runnerKey,
662
+ authType: "agent_key",
663
+ keySource: "runner_key",
664
+ notice: agentKey ? "Both EVIDENT_RUNNER_KEY and EVIDENT_AGENT_KEY are set; using EVIDENT_RUNNER_KEY." : void 0
665
+ };
666
+ }
654
667
  if (agentKey) {
655
- return { token: agentKey, authType: "agent_key" };
668
+ return { token: agentKey, authType: "agent_key", keySource: "agent_key" };
656
669
  }
657
670
  const userToken = process.env.EVIDENT_TOKEN;
658
671
  if (userToken) {
@@ -721,7 +734,7 @@ function buildOpenCodeVersionWarning(version2) {
721
734
  if (isQueueValidatedVersion(version2)) return null;
722
735
  const detected = version2 ? `v${version2}` : "unknown";
723
736
  const validated = QUEUE_VALIDATED_OPENCODE_VERSIONS.map((v) => `v${v}`).join(", ");
724
- return `Warning: opencode ${detected} is not a queue-validated version (validated: ${validated}). Native message queuing \u2014 which channel (Slack/WhatsApp) message handling relies on \u2014 is unverified on this version; queued/follow-up messages may behave unexpectedly. Continuing anyway. Bumping the validated set requires re-running the queue validation.`;
737
+ return `Warning: opencode ${detected} is not a queue-validated version (validated: ${validated}). Native message queuing \u2014 which channel (Slack) message handling relies on \u2014 is unverified on this version; queued/follow-up messages may behave unexpectedly. Continuing anyway. Bumping the validated set requires re-running the queue validation.`;
725
738
  }
726
739
 
727
740
  // src/lib/opencode/process.ts
@@ -1013,6 +1026,12 @@ async function promptOpenCodeInstall(interactive) {
1013
1026
  return action;
1014
1027
  }
1015
1028
 
1029
+ // src/lib/opencode/provider-check.ts
1030
+ function buildNoProviderWarning(hasProvider) {
1031
+ if (hasProvider !== false) return null;
1032
+ return "Warning: opencode has no authenticated model provider configured, so it won't be able to answer prompts. Run `opencode auth login` to set one up (see https://opencode.ai for details).";
1033
+ }
1034
+
1016
1035
  // src/lib/opencode/session.ts
1017
1036
  function opencodeBase(port) {
1018
1037
  return `http://127.0.0.1:${port}`;
@@ -1215,6 +1234,11 @@ async function getModelAttachmentCapability(port, model) {
1215
1234
  }
1216
1235
  const entry = provider.models[modelId];
1217
1236
  if (!entry || typeof entry !== "object") return null;
1237
+ if (entry.capabilities && typeof entry.capabilities === "object") {
1238
+ if (typeof entry.capabilities.attachment === "boolean") {
1239
+ return entry.capabilities.attachment;
1240
+ }
1241
+ }
1218
1242
  return typeof entry.attachment === "boolean" ? entry.attachment : null;
1219
1243
  } catch (err) {
1220
1244
  console.error(
@@ -1370,6 +1394,72 @@ function findLastAssistantReplyFor(messages, userMessageId) {
1370
1394
  }
1371
1395
  return lastOk ?? last;
1372
1396
  }
1397
+ function messageUsage(messages, userMessageId) {
1398
+ if (!messages || messages.length === 0) return null;
1399
+ const byParentAll = messages.filter(
1400
+ (m) => roleOf(m) === "assistant" && parentIdOf(m) === userMessageId
1401
+ );
1402
+ const byParentNonErrored = byParentAll.filter((m) => errorOf(m) == null);
1403
+ const byParent = byParentNonErrored.length > 0 ? byParentNonErrored : byParentAll;
1404
+ let correlated;
1405
+ if (byParent.length > 0) {
1406
+ correlated = byParent;
1407
+ } else {
1408
+ const reply = findAssistantReplyAfter(messages, userMessageId);
1409
+ correlated = reply ? [reply] : [];
1410
+ }
1411
+ if (correlated.length === 0) return null;
1412
+ let sawAnyUsage = false;
1413
+ let inputSum = 0;
1414
+ let outputSum = 0;
1415
+ let reasoningSum = 0;
1416
+ let cacheReadSum = 0;
1417
+ let cacheWriteSum = 0;
1418
+ let costSum = 0;
1419
+ let sawCost = false;
1420
+ let modelId = null;
1421
+ let providerId = null;
1422
+ for (const m of correlated) {
1423
+ const info = m.info;
1424
+ if (!info) continue;
1425
+ const tokens = info.tokens;
1426
+ if (tokens) {
1427
+ sawAnyUsage = true;
1428
+ inputSum += tokens.input ?? 0;
1429
+ outputSum += tokens.output ?? 0;
1430
+ reasoningSum += tokens.reasoning ?? 0;
1431
+ cacheReadSum += tokens.cache?.read ?? 0;
1432
+ cacheWriteSum += tokens.cache?.write ?? 0;
1433
+ }
1434
+ if (typeof info.cost === "number") {
1435
+ sawAnyUsage = true;
1436
+ sawCost = true;
1437
+ costSum += info.cost;
1438
+ }
1439
+ if (typeof info.modelID === "string") {
1440
+ sawAnyUsage = true;
1441
+ modelId = info.modelID;
1442
+ }
1443
+ if (typeof info.providerID === "string") {
1444
+ sawAnyUsage = true;
1445
+ providerId = info.providerID;
1446
+ }
1447
+ }
1448
+ if (!sawAnyUsage) return null;
1449
+ return {
1450
+ usage_provider_id: providerId,
1451
+ usage_model_id: modelId,
1452
+ usage_tokens_input: inputSum,
1453
+ usage_tokens_output: outputSum,
1454
+ usage_tokens_reasoning: reasoningSum,
1455
+ usage_tokens_cache_read: cacheReadSum,
1456
+ usage_tokens_cache_write: cacheWriteSum,
1457
+ // NULL means "OpenCode never reported a cost" (never inferred from
1458
+ // tokens) — distinct from a genuine 0-cost turn, which would set
1459
+ // `sawCost` true with `costSum === 0`.
1460
+ usage_cost_usd: sawCost ? costSum : null
1461
+ };
1462
+ }
1373
1463
  function messageRunState(messages, userMessageId) {
1374
1464
  if (!messages || messages.length === 0) return "unknown";
1375
1465
  const hasUser = messages.some((m) => idOf(m) === userMessageId);
@@ -1405,6 +1495,37 @@ function hasRunningAssistantExcept(messages, exceptUserMessageId) {
1405
1495
  (m) => roleOf(m) === "assistant" && parentIdOf(m) !== exceptUserMessageId && isAssistantInFlight(m)
1406
1496
  );
1407
1497
  }
1498
+ async function hasAnyConfiguredProvider(port) {
1499
+ try {
1500
+ const res = await fetch(`${opencodeBase(port)}/config/providers`);
1501
+ if (!res.ok) {
1502
+ console.error(
1503
+ `[hasAnyConfiguredProvider] GET /config/providers returned HTTP ${res.status} (port ${port})`
1504
+ );
1505
+ return null;
1506
+ }
1507
+ const body = await res.json();
1508
+ if (!body || typeof body !== "object" || Array.isArray(body)) {
1509
+ console.error(
1510
+ `[hasAnyConfiguredProvider] GET /config/providers body was not a plain object (port ${port})`
1511
+ );
1512
+ return null;
1513
+ }
1514
+ const defaults2 = body.default;
1515
+ if (!defaults2 || typeof defaults2 !== "object" || Array.isArray(defaults2)) {
1516
+ console.error(
1517
+ `[hasAnyConfiguredProvider] GET /config/providers body had no \`default\` object (port ${port})`
1518
+ );
1519
+ return null;
1520
+ }
1521
+ return Object.keys(defaults2).length > 0;
1522
+ } catch (err) {
1523
+ console.error(
1524
+ `[hasAnyConfiguredProvider] GET /config/providers failed (port ${port}): ${err instanceof Error ? err.message : String(err)}`
1525
+ );
1526
+ return null;
1527
+ }
1528
+ }
1408
1529
 
1409
1530
  // src/lib/opencode/session-cleanup.ts
1410
1531
  var DURATION_UNIT_MS = {
@@ -1716,7 +1837,6 @@ function connectTunnel(options) {
1716
1837
  onConnected,
1717
1838
  onDisconnected,
1718
1839
  onError,
1719
- onRequest,
1720
1840
  onResponse,
1721
1841
  onInfo,
1722
1842
  onDrainPing
@@ -1729,18 +1849,8 @@ function connectTunnel(options) {
1729
1849
  Authorization: authHeader
1730
1850
  }
1731
1851
  });
1732
- const streamStartTimes = /* @__PURE__ */ new Map();
1733
1852
  const forwarder = new StreamForwarder(ws, port, {
1734
- onOpen: (sid, method, path) => {
1735
- if (path === TUNNEL_DRAIN_PING_PATH) return;
1736
- streamStartTimes.set(sid, Date.now());
1737
- onRequest?.(method, path, sid);
1738
- },
1739
- onHead: (sid, status) => {
1740
- const startedAt = streamStartTimes.get(sid);
1741
- streamStartTimes.delete(sid);
1742
- onResponse?.(status, startedAt ? Date.now() - startedAt : 0, sid);
1743
- },
1853
+ onHead: () => onResponse?.(),
1744
1854
  onDrainPing: () => onDrainPing?.()
1745
1855
  });
1746
1856
  const connectionTimeout = setTimeout(() => {
@@ -1816,7 +1926,6 @@ function connectTunnel(options) {
1816
1926
  ws.on("close", (code, reason) => {
1817
1927
  const reasonStr = reason.toString() || upgradeRejection || (code === 1006 ? "abnormal closure" : "No reason provided");
1818
1928
  forwarder.abortAll();
1819
- streamStartTimes.clear();
1820
1929
  onDisconnected?.(code, reasonStr);
1821
1930
  });
1822
1931
  });
@@ -1954,7 +2063,7 @@ function backoffDelay(attempt, policy) {
1954
2063
  function isRetryableStatus(status) {
1955
2064
  return status === 429 || status >= 500 && status <= 599;
1956
2065
  }
1957
- var ChannelDriver = class {
2066
+ var ChannelDriver = class _ChannelDriver {
1958
2067
  agentId;
1959
2068
  port;
1960
2069
  apiUrl;
@@ -2079,9 +2188,12 @@ var ChannelDriver = class {
2079
2188
  sessionParents = /* @__PURE__ */ new Map();
2080
2189
  /**
2081
2190
  * Per-session OpenCode title cache (#310), keyed by sessionId. Only a resolved
2082
- * NON-EMPTY name is stored (terminal — a real session name won't later un-name),
2083
- * so we do NOT re-GET `/session/:id` every tick. A missing entry = not yet
2084
- * resolved OR resolved-but-still-empty re-fetch on next need, since OpenCode
2191
+ * NON-EMPTY, non-placeholder name is stored (terminal — a real session name
2192
+ * won't later un-name), so we do NOT re-GET `/session/:id` every tick. "Non-empty"
2193
+ * excludes OpenCode's synchronous default title (see
2194
+ * `OPENCODE_DEFAULT_TITLE_PREFIX`, #549) — that placeholder is treated the same
2195
+ * as an empty title so it never latches. A missing entry = not yet resolved OR
2196
+ * resolved-but-still-empty/placeholder → re-fetch on next need, since OpenCode
2085
2197
  * names sessions asynchronously mid-turn. Driver-level (not per-watcher) so both
2086
2198
  * the watcher completion path AND the restart-recovery re-adopt path (which has
2087
2199
  * no watcher) can resolve the title.
@@ -2445,7 +2557,7 @@ var ChannelDriver = class {
2445
2557
  }
2446
2558
  /**
2447
2559
  * Fetch ONE inbound image's bytes through Evident's WI-6 endpoint
2448
- * (`GET {apiUrl}/agents/{agentId}/attachments/{messageId}/{index}`) using the
2560
+ * (`GET {apiUrl}/runners/{agentId}/attachments/{messageId}/{index}`) using the
2449
2561
  * existing authenticated fetch, and base64-encode into a
2450
2562
  * `data:<mime>;base64,<…>` URL for the opencode `file` part's `url`.
2451
2563
  *
@@ -2458,7 +2570,7 @@ var ChannelDriver = class {
2458
2570
  async fetchAttachmentDataUrl(messageId, index, mime) {
2459
2571
  try {
2460
2572
  const res = await this.fetchImpl(
2461
- `${this.apiUrl}/agents/${this.agentId}/attachments/${messageId}/${index}`,
2573
+ `${this.apiUrl}/runners/${this.agentId}/attachments/${messageId}/${index}`,
2462
2574
  { headers: { Authorization: this.getAuthHeader() } }
2463
2575
  );
2464
2576
  if (!res.ok) {
@@ -2790,13 +2902,15 @@ var ChannelDriver = class {
2790
2902
  message_id: inFlight.evidentMessageId
2791
2903
  });
2792
2904
  const title = await this.resolveSessionTitle(sessionId, watcher.conv.id);
2905
+ const usage = messageUsage(messages, inFlight.opencodeMessageId);
2793
2906
  try {
2794
2907
  await this.markDone(
2795
2908
  conv.id,
2796
2909
  inFlight.evidentMessageId,
2797
2910
  sessionId,
2798
2911
  inFlight.opencodeMessageId,
2799
- title
2912
+ title,
2913
+ usage
2800
2914
  );
2801
2915
  } catch (err) {
2802
2916
  if (err instanceof ChannelAuthError) throw err;
@@ -2843,8 +2957,9 @@ var ChannelDriver = class {
2843
2957
  conversation_id: conv.id,
2844
2958
  message_id: inFlight.evidentMessageId
2845
2959
  });
2960
+ const usage = messageUsage(messages, inFlight.opencodeMessageId);
2846
2961
  try {
2847
- await this.markFailed(conv.id, inFlight.evidentMessageId, sessionId, error2);
2962
+ await this.markFailed(conv.id, inFlight.evidentMessageId, sessionId, error2, usage);
2848
2963
  } catch (err) {
2849
2964
  if (err instanceof ChannelAuthError) throw err;
2850
2965
  if (err instanceof ChannelTerminalError) {
@@ -3081,7 +3196,8 @@ var ChannelDriver = class {
3081
3196
  });
3082
3197
  try {
3083
3198
  const title = await this.resolveSessionTitle(sessionId, row.conversation_id);
3084
- await this.markDone(row.conversation_id, row.id, sessionId, ocId, title);
3199
+ const usage = messageUsage(messages, ocId ?? "");
3200
+ await this.markDone(row.conversation_id, row.id, sessionId, ocId, title, usage);
3085
3201
  } catch (err) {
3086
3202
  if (err instanceof ChannelAuthError) throw err;
3087
3203
  if (err instanceof ChannelTerminalError) {
@@ -3109,6 +3225,7 @@ var ChannelDriver = class {
3109
3225
  }
3110
3226
  if (state === "failed") {
3111
3227
  const error2 = messageError(messages, ocId ?? "") ?? void 0;
3228
+ const usage = messageUsage(messages, ocId ?? "");
3112
3229
  this.log({
3113
3230
  level: "error",
3114
3231
  message: `Re-adopt: message ${row.id.slice(0, 8)} errored while unwatched \u2014 marking failed: ${error2 ?? "(no error text)"}`,
@@ -3116,7 +3233,7 @@ var ChannelDriver = class {
3116
3233
  message_id: row.id
3117
3234
  });
3118
3235
  try {
3119
- await this.markFailed(row.conversation_id, row.id, sessionId, error2);
3236
+ await this.markFailed(row.conversation_id, row.id, sessionId, error2, usage);
3120
3237
  } catch (err) {
3121
3238
  if (err instanceof ChannelAuthError) throw err;
3122
3239
  if (err instanceof ChannelTerminalError) {
@@ -3544,19 +3661,36 @@ var ChannelDriver = class {
3544
3661
  if (parent !== void 0) this.sessionParents.set(sessionId, parent);
3545
3662
  return parent;
3546
3663
  }
3664
+ /**
3665
+ * OpenCode's synchronous default session title (e.g.
3666
+ * `"New session - 1737800000000"`), assigned immediately when a session is
3667
+ * created — before OpenCode's async LLM-based auto-titling later renames it
3668
+ * mid-turn (#549). Matched by this literal, case-sensitive prefix only; the
3669
+ * timestamp suffix's exact format is deliberately NOT matched, since the prefix
3670
+ * alone is the stable, cheap signal and over-anchoring on the timestamp
3671
+ * representation risks silently breaking if OpenCode ever changes it. Accepted
3672
+ * trade-off: a genuine LLM-assigned title that happens to literally start with
3673
+ * this prefix would also fail to latch (see `resolveSessionTitle`) —
3674
+ * vanishingly unlikely in practice, and deliberately not engineered around.
3675
+ */
3676
+ static OPENCODE_DEFAULT_TITLE_PREFIX = /^New session - /;
3547
3677
  /**
3548
3678
  * Resolve (and cache in `sessionTitles`) the OpenCode session TITLE (#310) so the
3549
3679
  * status PATCH can carry it into the "Live sessions" list. Driver-level cache so
3550
3680
  * BOTH the watcher completion path and the restart-recovery re-adopt path (which
3551
3681
  * has no watcher) can use it. `conversationId` is passed only for log context.
3552
3682
  * Best-effort:
3553
- * - a resolved NON-EMPTY title is cached and terminal (a real session name
3683
+ * - a resolved NON-EMPTY title that does NOT match
3684
+ * `OPENCODE_DEFAULT_TITLE_PREFIX` is cached and terminal (a real session name
3554
3685
  * won't later un-name), so we do NOT re-GET `/session/:id` every tick;
3555
- * - while the title is still absent/empty we do NOT latch it — OpenCode names
3556
- * sessions asynchronously mid-turn, so an early call (e.g. at `processing`)
3557
- * must leave the cache unresolved and re-fetch on the next need so a later
3558
- * call (e.g. at `done`) picks up the name assigned in the meantime. Such a
3559
- * call returns `null` (omit the title on THIS PATCH) without caching;
3686
+ * - while the title is still absent, empty, or matches the OpenCode
3687
+ * placeholder prefix (#549) we do NOT latch it OpenCode names sessions
3688
+ * asynchronously mid-turn, so an early call (e.g. at `processing`) must leave
3689
+ * the cache unresolved and re-fetch on the next need so a later call (e.g. at
3690
+ * `done`) picks up the name assigned in the meantime. Such a call returns
3691
+ * `null` (omit the title on THIS PATCH) without caching. If a session is
3692
+ * never renamed, the title is omitted forever rather than ever persisting
3693
+ * the placeholder as a last resort;
3560
3694
  * - a failed request likewise leaves the cache unresolved (retry next need)
3561
3695
  * and returns `null` — it must NEVER throw or block completion.
3562
3696
  * A failure is logged with agent/session context (no silent catch).
@@ -3569,7 +3703,7 @@ var ChannelDriver = class {
3569
3703
  if (res.ok) {
3570
3704
  const body = await res.json();
3571
3705
  const title = body && typeof body.title === "string" ? body.title.trim() : "";
3572
- if (title.length > 0) {
3706
+ if (title.length > 0 && !_ChannelDriver.OPENCODE_DEFAULT_TITLE_PREFIX.test(title)) {
3573
3707
  this.sessionTitles.set(sessionId, title);
3574
3708
  return title;
3575
3709
  }
@@ -3719,7 +3853,7 @@ var ChannelDriver = class {
3719
3853
  // Evident API calls (combinedAuth thread routes)
3720
3854
  async getPendingConversations() {
3721
3855
  const res = await this.fetchImpl(
3722
- `${this.apiUrl}/agents/${this.agentId}/conversations/pending`,
3856
+ `${this.apiUrl}/runners/${this.agentId}/conversations/pending`,
3723
3857
  {
3724
3858
  headers: { Authorization: this.getAuthHeader() }
3725
3859
  }
@@ -3737,7 +3871,7 @@ var ChannelDriver = class {
3737
3871
  }
3738
3872
  async getPendingMessages(conversationId) {
3739
3873
  const res = await this.fetchImpl(
3740
- `${this.apiUrl}/agents/${this.agentId}/threads/${conversationId}/messages?status=pending`,
3874
+ `${this.apiUrl}/runners/${this.agentId}/threads/${conversationId}/messages?status=pending`,
3741
3875
  { headers: { Authorization: this.getAuthHeader() } }
3742
3876
  );
3743
3877
  this.assertAuth(res, "fetching pending messages");
@@ -3761,7 +3895,7 @@ var ChannelDriver = class {
3761
3895
  */
3762
3896
  async getProcessingMessages() {
3763
3897
  const res = await this.fetchImpl(
3764
- `${this.apiUrl}/agents/${this.agentId}/conversations/processing`,
3898
+ `${this.apiUrl}/runners/${this.agentId}/conversations/processing`,
3765
3899
  { headers: { Authorization: this.getAuthHeader() } }
3766
3900
  );
3767
3901
  this.assertAuth(res, "fetching processing messages");
@@ -3798,7 +3932,7 @@ var ChannelDriver = class {
3798
3932
  */
3799
3933
  async markProcessing(conversationId, messageId, sessionId, opencodeMessageId, title) {
3800
3934
  const res = await this.fetchImpl(
3801
- `${this.apiUrl}/agents/${this.agentId}/threads/${conversationId}/messages/${messageId}`,
3935
+ `${this.apiUrl}/runners/${this.agentId}/threads/${conversationId}/messages/${messageId}`,
3802
3936
  {
3803
3937
  method: "PATCH",
3804
3938
  headers: { Authorization: this.getAuthHeader(), "Content-Type": "application/json" },
@@ -3845,9 +3979,9 @@ var ChannelDriver = class {
3845
3979
  * watcher retries next tick within the
3846
3980
  * deadline, Finding 4).
3847
3981
  */
3848
- async markDone(conversationId, messageId, sessionId, opencodeMessageId, title) {
3982
+ async markDone(conversationId, messageId, sessionId, opencodeMessageId, title, usage) {
3849
3983
  const res = await this.fetchImpl(
3850
- `${this.apiUrl}/agents/${this.agentId}/threads/${conversationId}/messages/${messageId}`,
3984
+ `${this.apiUrl}/runners/${this.agentId}/threads/${conversationId}/messages/${messageId}`,
3851
3985
  {
3852
3986
  method: "PATCH",
3853
3987
  headers: { Authorization: this.getAuthHeader(), "Content-Type": "application/json" },
@@ -3855,7 +3989,8 @@ var ChannelDriver = class {
3855
3989
  status: "done",
3856
3990
  opencode_session_id: sessionId,
3857
3991
  ...opencodeMessageId ? { opencode_message_id: opencodeMessageId } : {},
3858
- ...title ? { title } : {}
3992
+ ...title ? { title } : {},
3993
+ ...usage ? usage : {}
3859
3994
  })
3860
3995
  }
3861
3996
  );
@@ -3873,14 +4008,15 @@ var ChannelDriver = class {
3873
4008
  * OpenCode turn sends `{status:'failed', opencode_session_id, error}` so the
3874
4009
  * failure reason reaches the channel.
3875
4010
  */
3876
- async markFailed(conversationId, messageId, sessionId, error2) {
4011
+ async markFailed(conversationId, messageId, sessionId, error2, usage) {
3877
4012
  const body = { status: "failed" };
3878
4013
  if (sessionId !== void 0) body.opencode_session_id = sessionId;
3879
4014
  if (error2 !== void 0) body.error = error2;
4015
+ if (usage) Object.assign(body, usage);
3880
4016
  await this.callWithRetry(
3881
4017
  "marking message as failed",
3882
4018
  () => this.fetchImpl(
3883
- `${this.apiUrl}/agents/${this.agentId}/threads/${conversationId}/messages/${messageId}`,
4019
+ `${this.apiUrl}/runners/${this.agentId}/threads/${conversationId}/messages/${messageId}`,
3884
4020
  {
3885
4021
  method: "PATCH",
3886
4022
  headers: { Authorization: this.getAuthHeader(), "Content-Type": "application/json" },
@@ -3907,7 +4043,7 @@ var ChannelDriver = class {
3907
4043
  async postSignal(conversationId, messageId, signal, extra) {
3908
4044
  try {
3909
4045
  const res = await this.fetchImpl(
3910
- `${this.apiUrl}/agents/${this.agentId}/threads/${conversationId}/messages/${messageId}/signal`,
4046
+ `${this.apiUrl}/runners/${this.agentId}/threads/${conversationId}/messages/${messageId}/signal`,
3911
4047
  {
3912
4048
  method: "POST",
3913
4049
  headers: { Authorization: this.getAuthHeader(), "Content-Type": "application/json" },
@@ -3936,7 +4072,7 @@ var ChannelDriver = class {
3936
4072
  }
3937
4073
  async persistSession(conversationId, sessionId) {
3938
4074
  const res = await this.fetchImpl(
3939
- `${this.apiUrl}/agents/${this.agentId}/threads/${conversationId}`,
4075
+ `${this.apiUrl}/runners/${this.agentId}/threads/${conversationId}`,
3940
4076
  {
3941
4077
  method: "PATCH",
3942
4078
  headers: { Authorization: this.getAuthHeader(), "Content-Type": "application/json" },
@@ -3962,7 +4098,7 @@ var ChannelDriver = class {
3962
4098
  await this.callWithRetry(
3963
4099
  "reporting interactive event",
3964
4100
  () => this.fetchImpl(
3965
- `${this.apiUrl}/agents/${this.agentId}/threads/${conversationId}/interactive-event`,
4101
+ `${this.apiUrl}/runners/${this.agentId}/threads/${conversationId}/interactive-event`,
3966
4102
  {
3967
4103
  method: "POST",
3968
4104
  headers: { Authorization: this.getAuthHeader(), "Content-Type": "application/json" },
@@ -4205,7 +4341,7 @@ async function resolveAgentIdFromKey(authHeader) {
4205
4341
  async function notifyAgentDisconnected(agentId, authHeader) {
4206
4342
  const apiUrl = getApiUrlConfig();
4207
4343
  try {
4208
- const response = await fetch(`${apiUrl}/agents/${agentId}/disconnect`, {
4344
+ const response = await fetch(`${apiUrl}/runners/${agentId}/disconnect`, {
4209
4345
  method: "POST",
4210
4346
  headers: { Authorization: authHeader }
4211
4347
  });
@@ -4224,7 +4360,7 @@ async function notifyAgentDisconnected(agentId, authHeader) {
4224
4360
  async function getAgentInfo(agentId, authHeader) {
4225
4361
  const apiUrl = getApiUrlConfig();
4226
4362
  try {
4227
- const response = await fetch(`${apiUrl}/agents/${agentId}`, {
4363
+ const response = await fetch(`${apiUrl}/runners/${agentId}`, {
4228
4364
  headers: { Authorization: authHeader }
4229
4365
  });
4230
4366
  if (response.status === 401) {
@@ -4611,7 +4747,7 @@ async function run(options) {
4611
4747
  return;
4612
4748
  }
4613
4749
  const state = {
4614
- agentId: options.agent || "",
4750
+ agentId: options.runner || options.agent || "",
4615
4751
  agentName: null,
4616
4752
  port: options.port ?? 4096,
4617
4753
  conversationFilter: options.conversation ?? null,
@@ -4633,6 +4769,19 @@ async function run(options) {
4633
4769
  sessionCleanupTimers: [],
4634
4770
  authHeader: ""
4635
4771
  };
4772
+ if (!options.runner && options.agent) {
4773
+ telemetry.info(
4774
+ EventTypes.DEPRECATED_AGENT_FLAG_USED,
4775
+ "Deprecated --agent flag used instead of --runner",
4776
+ { command: "run" },
4777
+ state.agentId
4778
+ );
4779
+ const agentFlagNotice = "--agent is deprecated, use --runner instead; will be removed in a future release.";
4780
+ log2(state, agentFlagNotice, "warn");
4781
+ if (state.interactive && !state.json) {
4782
+ logActivity(state, { type: "info", level: "warn", message: agentFlagNotice });
4783
+ }
4784
+ }
4636
4785
  if (state.idleTimeout === null && (process.env.GITHUB_ACTIONS || process.env.CI)) {
4637
4786
  log2(
4638
4787
  state,
@@ -4661,7 +4810,9 @@ async function run(options) {
4661
4810
  if (!interactive) {
4662
4811
  printError("Authentication required");
4663
4812
  blank();
4664
- console.log(chalk6.dim("Set EVIDENT_AGENT_KEY environment variable for CI"));
4813
+ console.log(
4814
+ chalk6.dim("Set EVIDENT_RUNNER_KEY (or EVIDENT_AGENT_KEY) environment variable for CI")
4815
+ );
4665
4816
  console.log(chalk6.dim("Or run `evident login` for interactive authentication"));
4666
4817
  blank();
4667
4818
  process.exit(1);
@@ -4675,6 +4826,25 @@ async function run(options) {
4675
4826
  );
4676
4827
  }
4677
4828
  state.authHeader = getAuthHeader(credentials2);
4829
+ if (credentials2.notice) {
4830
+ log2(state, credentials2.notice, "warn");
4831
+ if (state.interactive && !state.json) {
4832
+ logActivity(state, { type: "info", level: "warn", message: credentials2.notice });
4833
+ }
4834
+ }
4835
+ if (credentials2.keySource === "agent_key") {
4836
+ telemetry.info(
4837
+ EventTypes.DEPRECATED_AGENT_KEY_ENV_USED,
4838
+ "Deprecated EVIDENT_AGENT_KEY env var used instead of EVIDENT_RUNNER_KEY",
4839
+ { command: "run" },
4840
+ state.agentId
4841
+ );
4842
+ const agentKeyNotice = "EVIDENT_AGENT_KEY is deprecated, use EVIDENT_RUNNER_KEY instead; will be removed in a future release.";
4843
+ log2(state, agentKeyNotice, "warn");
4844
+ if (state.interactive && !state.json) {
4845
+ logActivity(state, { type: "info", level: "warn", message: agentKeyNotice });
4846
+ }
4847
+ }
4678
4848
  if (!state.agentId) {
4679
4849
  if (credentials2.authType === "agent_key") {
4680
4850
  const resolved = await resolveAgentIdFromKey(state.authHeader);
@@ -4692,9 +4862,15 @@ async function run(options) {
4692
4862
  process.exit(1);
4693
4863
  }
4694
4864
  } else {
4695
- printError("--agent is required when not using EVIDENT_AGENT_KEY");
4865
+ printError(
4866
+ "--runner (or --agent) is required when not using EVIDENT_RUNNER_KEY or EVIDENT_AGENT_KEY"
4867
+ );
4696
4868
  blank();
4697
- console.log(chalk6.dim("Either provide --agent <id> or set EVIDENT_AGENT_KEY"));
4869
+ console.log(
4870
+ chalk6.dim(
4871
+ "Either provide --runner/--agent <id> or set EVIDENT_RUNNER_KEY/EVIDENT_AGENT_KEY"
4872
+ )
4873
+ );
4698
4874
  blank();
4699
4875
  process.exit(1);
4700
4876
  }
@@ -4758,6 +4934,21 @@ async function run(options) {
4758
4934
  logActivity(state, { type: "info", level: "warn", message: versionWarning });
4759
4935
  }
4760
4936
  }
4937
+ const noProviderWarning = buildNoProviderWarning(await hasAnyConfiguredProvider(state.port));
4938
+ if (noProviderWarning) {
4939
+ log2(state, noProviderWarning, "warn");
4940
+ if (state.interactive && !state.json) {
4941
+ logActivity(state, { type: "info", level: "warn", message: noProviderWarning });
4942
+ blank();
4943
+ console.log(chalk6.yellow("\u26A0 No OpenCode model provider is configured."));
4944
+ console.log(
4945
+ chalk6.dim(
4946
+ `Run ${chalk6.cyan("opencode auth login")} to set one up \u2014 messages will fail until then.`
4947
+ )
4948
+ );
4949
+ blank();
4950
+ }
4951
+ }
4761
4952
  } catch (error2) {
4762
4953
  ocSpinner?.fail(error2.message);
4763
4954
  throw error2;
@@ -4909,7 +5100,7 @@ async function run(options) {
4909
5100
  }
4910
5101
  telemetry.error(EventTypes.CLI_ERROR, `Run command failed: ${message}`, {
4911
5102
  command: "run",
4912
- agentId: options.agent
5103
+ agentId: options.runner || options.agent
4913
5104
  });
4914
5105
  await shutdownTelemetry();
4915
5106
  process.exit(1);
@@ -4934,7 +5125,7 @@ program.name("evident").description("Run OpenCode locally and connect it to Evid
4934
5125
  program.command("login").description("Authenticate with Evident").option("--token", "Use token-based authentication (for CI/CD)").option("--no-browser", "Do not open the browser automatically").action(login);
4935
5126
  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 }));
4936
5127
  program.command("whoami").description("Show the currently logged in user").action(whoami);
4937
- program.command("run").description("Connect to Evident and process messages").option("-a, --agent [id]", "Runner ID to connect to (optional when EVIDENT_AGENT_KEY is set)").option("-p, --port <port>", "OpenCode port (default: 4096)", "4096").option(
5128
+ program.command("run").description("Connect to Evident and process messages").option("-a, --agent [id]", "Runner ID to connect to (optional when EVIDENT_AGENT_KEY is set)").option("--runner [id]", "Alias for --agent (preferred name; wins if both are given)").option("-p, --port <port>", "OpenCode port (default: 4096)", "4096").option(
4938
5129
  "--log-level <level>",
4939
5130
  "Log verbosity: debug | info | warn | error (default: info). Env: EVIDENT_LOG_LEVEL"
4940
5131
  ).option("-v, --verbose", "Alias for --log-level debug (ignored if --log-level is set)").option("-c, --conversation <id>", "Process only this specific conversation").option("--idle-timeout <seconds>", "Exit after N seconds idle").option("--json", "Output in JSON format").option(
@@ -4950,6 +5141,7 @@ program.command("run").description("Connect to Evident and process messages").op
4950
5141
  (options) => {
4951
5142
  run({
4952
5143
  agent: options.agent,
5144
+ runner: options.runner,
4953
5145
  port: parseInt(options.port, 10),
4954
5146
  // Raw string — validation/precedence is single-sourced in run.ts's
4955
5147
  // resolveLogLevel (flag > -v > EVIDENT_LOG_LEVEL > info).