@evident-ai/cli 3.0.1-dev.c6600b6 → 3.0.1-dev.ced5d3d
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 +114 -28
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
+
import { createRequire } from "module";
|
|
4
5
|
import { Command } from "commander";
|
|
5
6
|
|
|
6
7
|
// src/commands/login.ts
|
|
@@ -470,6 +471,12 @@ import chalk6 from "chalk";
|
|
|
470
471
|
import ora3 from "ora";
|
|
471
472
|
import { select as select3 } from "@inquirer/prompts";
|
|
472
473
|
|
|
474
|
+
// ../../packages/types/src/opencode/index.ts
|
|
475
|
+
function opencodeMessageIdFor(queuedMessageId) {
|
|
476
|
+
const sanitized = queuedMessageId.replace(/[^a-zA-Z0-9]/g, "_");
|
|
477
|
+
return `msg_${sanitized}`;
|
|
478
|
+
}
|
|
479
|
+
|
|
473
480
|
// ../../packages/types/src/telemetry/index.ts
|
|
474
481
|
var TelemetryEventTypes = {
|
|
475
482
|
// Agent activity events (shown in web UI activity log)
|
|
@@ -484,6 +491,29 @@ var TelemetryEventTypes = {
|
|
|
484
491
|
var MAX_FRAME_BYTES = 256 * 1024;
|
|
485
492
|
var TUNNEL_DRAIN_PING_PATH = "/__evident/drain";
|
|
486
493
|
|
|
494
|
+
// ../../packages/types/src/logging/index.ts
|
|
495
|
+
var CORRELATION_ID_HEADER = "x-evident-correlation-id";
|
|
496
|
+
function log(level, event, fields) {
|
|
497
|
+
const method = level === "debug" ? "log" : level;
|
|
498
|
+
try {
|
|
499
|
+
console[method]("[evident]", JSON.stringify({ level, event, ...fields }));
|
|
500
|
+
} catch (err) {
|
|
501
|
+
console.error(
|
|
502
|
+
"[evident] log_serialize_failed",
|
|
503
|
+
event,
|
|
504
|
+
err instanceof Error ? err.message : String(err)
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
function stripQuery(url) {
|
|
509
|
+
try {
|
|
510
|
+
return new URL(url).pathname;
|
|
511
|
+
} catch {
|
|
512
|
+
const q = url.indexOf("?");
|
|
513
|
+
return q === -1 ? url : url.slice(0, q);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
487
517
|
// src/lib/telemetry.ts
|
|
488
518
|
var CLI_VERSION = process.env.npm_package_version || "unknown";
|
|
489
519
|
var eventBuffer = [];
|
|
@@ -686,13 +716,13 @@ async function waitForOpenCodeHealth(port, timeoutMs = 3e4) {
|
|
|
686
716
|
|
|
687
717
|
// src/lib/opencode/opencode-version-gate.ts
|
|
688
718
|
var QUEUE_VALIDATED_OPENCODE_VERSIONS = ["1.17.11"];
|
|
689
|
-
function isQueueValidatedVersion(
|
|
690
|
-
if (!
|
|
691
|
-
return QUEUE_VALIDATED_OPENCODE_VERSIONS.includes(
|
|
719
|
+
function isQueueValidatedVersion(version2) {
|
|
720
|
+
if (!version2) return false;
|
|
721
|
+
return QUEUE_VALIDATED_OPENCODE_VERSIONS.includes(version2);
|
|
692
722
|
}
|
|
693
|
-
function buildOpenCodeVersionWarning(
|
|
694
|
-
if (isQueueValidatedVersion(
|
|
695
|
-
const detected =
|
|
723
|
+
function buildOpenCodeVersionWarning(version2) {
|
|
724
|
+
if (isQueueValidatedVersion(version2)) return null;
|
|
725
|
+
const detected = version2 ? `v${version2}` : "unknown";
|
|
696
726
|
const validated = QUEUE_VALIDATED_OPENCODE_VERSIONS.map((v) => `v${v}`).join(", ");
|
|
697
727
|
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.`;
|
|
698
728
|
}
|
|
@@ -1023,6 +1053,12 @@ function parentIdOf(m) {
|
|
|
1023
1053
|
const infoParent = m.info?.parentID;
|
|
1024
1054
|
return typeof infoParent === "string" ? infoParent : void 0;
|
|
1025
1055
|
}
|
|
1056
|
+
function finishOf(m) {
|
|
1057
|
+
if (!m || typeof m !== "object") return void 0;
|
|
1058
|
+
if (typeof m.finish === "string") return m.finish;
|
|
1059
|
+
const infoFinish = m.info?.finish;
|
|
1060
|
+
return typeof infoFinish === "string" ? infoFinish : void 0;
|
|
1061
|
+
}
|
|
1026
1062
|
async function createOpenCodeSession(port, directory) {
|
|
1027
1063
|
const url = new URL(`${opencodeBase(port)}/session`);
|
|
1028
1064
|
if (directory && directory.trim()) {
|
|
@@ -1080,19 +1116,36 @@ function findAssistantReplyAfter(messages, userMessageId) {
|
|
|
1080
1116
|
}
|
|
1081
1117
|
return null;
|
|
1082
1118
|
}
|
|
1119
|
+
function findLastAssistantReplyFor(messages, userMessageId) {
|
|
1120
|
+
if (!messages || messages.length === 0) return null;
|
|
1121
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
1122
|
+
const m = messages[i];
|
|
1123
|
+
if (roleOf(m) === "assistant" && parentIdOf(m) === userMessageId) return m;
|
|
1124
|
+
}
|
|
1125
|
+
const userIndex = messages.findIndex((m) => idOf(m) === userMessageId);
|
|
1126
|
+
if (userIndex === -1) return null;
|
|
1127
|
+
let last = null;
|
|
1128
|
+
for (let i = userIndex + 1; i < messages.length; i++) {
|
|
1129
|
+
const role = roleOf(messages[i]);
|
|
1130
|
+
if (role === "user") break;
|
|
1131
|
+
if (role === "assistant") last = messages[i];
|
|
1132
|
+
}
|
|
1133
|
+
return last;
|
|
1134
|
+
}
|
|
1083
1135
|
function messageRunState(messages, userMessageId) {
|
|
1084
1136
|
if (!messages || messages.length === 0) return "unknown";
|
|
1085
1137
|
const hasUser = messages.some((m) => idOf(m) === userMessageId);
|
|
1086
|
-
const reply =
|
|
1138
|
+
const reply = findLastAssistantReplyFor(messages, userMessageId);
|
|
1087
1139
|
if (!hasUser) {
|
|
1088
1140
|
if (!reply) return "unknown";
|
|
1089
1141
|
}
|
|
1090
1142
|
if (!reply) return "queued";
|
|
1091
|
-
|
|
1143
|
+
if (completedOf(reply) == null) return "running";
|
|
1144
|
+
if (finishOf(reply) === "tool-calls") return "running";
|
|
1145
|
+
return "done";
|
|
1092
1146
|
}
|
|
1093
|
-
function
|
|
1094
|
-
|
|
1095
|
-
return `msg_${sanitized}`;
|
|
1147
|
+
function opencodeMessageIdFor2(queuedMessageId) {
|
|
1148
|
+
return opencodeMessageIdFor(queuedMessageId);
|
|
1096
1149
|
}
|
|
1097
1150
|
|
|
1098
1151
|
// src/lib/tunnel/connection.ts
|
|
@@ -1163,12 +1216,20 @@ var StreamForwarder = class {
|
|
|
1163
1216
|
}
|
|
1164
1217
|
async handleOpen(frame) {
|
|
1165
1218
|
const { sid, method, path, headers, has_body } = frame;
|
|
1219
|
+
const correlationId = headers?.[CORRELATION_ID_HEADER];
|
|
1220
|
+
const startedAt = Date.now();
|
|
1166
1221
|
if (path === TUNNEL_DRAIN_PING_PATH) {
|
|
1167
1222
|
this.callbacks.onDrainPing?.();
|
|
1168
1223
|
this.send({ type: "head", sid, status: 204, headers: {} });
|
|
1169
1224
|
this.send({ type: "res_end", sid });
|
|
1170
1225
|
return;
|
|
1171
1226
|
}
|
|
1227
|
+
log("info", "agent_request", {
|
|
1228
|
+
correlation_id: correlationId,
|
|
1229
|
+
sid,
|
|
1230
|
+
method,
|
|
1231
|
+
path: stripQuery(path)
|
|
1232
|
+
});
|
|
1172
1233
|
const ac = new AbortController();
|
|
1173
1234
|
let bodyPromise;
|
|
1174
1235
|
let pushBody;
|
|
@@ -1215,6 +1276,12 @@ var StreamForwarder = class {
|
|
|
1215
1276
|
if (!STRIP_RES.has(key.toLowerCase())) resHeaders[key] = value;
|
|
1216
1277
|
});
|
|
1217
1278
|
this.send({ type: "head", sid, status: upstream.status, headers: resHeaders });
|
|
1279
|
+
log("info", "agent_response", {
|
|
1280
|
+
correlation_id: correlationId,
|
|
1281
|
+
sid,
|
|
1282
|
+
status: upstream.status,
|
|
1283
|
+
duration_ms: Date.now() - startedAt
|
|
1284
|
+
});
|
|
1218
1285
|
this.callbacks.onHead?.(sid, upstream.status);
|
|
1219
1286
|
try {
|
|
1220
1287
|
if (upstream.body) {
|
|
@@ -1645,11 +1712,13 @@ var ChannelDriver = class {
|
|
|
1645
1712
|
const sessionId = await this.ensureSession(conv);
|
|
1646
1713
|
const messages = await this.getPendingMessages(conv.id);
|
|
1647
1714
|
let dispatched = 0;
|
|
1715
|
+
let skippedAlreadyDispatched = 0;
|
|
1648
1716
|
for (const message of messages) {
|
|
1649
1717
|
if (this.dispatched.has(message.id)) {
|
|
1718
|
+
skippedAlreadyDispatched += 1;
|
|
1650
1719
|
continue;
|
|
1651
1720
|
}
|
|
1652
|
-
const opencodeMessageId =
|
|
1721
|
+
const opencodeMessageId = opencodeMessageIdFor2(message.id);
|
|
1653
1722
|
const options = {
|
|
1654
1723
|
agent: message.opencode_agent ?? void 0,
|
|
1655
1724
|
model: message.opencode_model ?? void 0
|
|
@@ -1679,6 +1748,13 @@ var ChannelDriver = class {
|
|
|
1679
1748
|
this.registerInFlight(conv, sessionId, message, opencodeMessageId);
|
|
1680
1749
|
dispatched += 1;
|
|
1681
1750
|
}
|
|
1751
|
+
if (messages.length > 0 && dispatched === 0 && skippedAlreadyDispatched === messages.length) {
|
|
1752
|
+
this.log({
|
|
1753
|
+
level: "error",
|
|
1754
|
+
message: `Conversation ${conv.id.slice(0, 8)} has ${messages.length} pending message(s) but ALL are already marked dispatched locally (in-flight set: ${this.dispatched.size}) \u2014 none sent to OpenCode this tick. If this repeats, a message may be stuck acknowledged-but-never-dispatched (its watcher never settled).`,
|
|
1755
|
+
conversation_id: conv.id
|
|
1756
|
+
});
|
|
1757
|
+
}
|
|
1682
1758
|
this.ensureWatcherRunning(sessionId);
|
|
1683
1759
|
return dispatched;
|
|
1684
1760
|
}
|
|
@@ -2497,7 +2573,7 @@ async function getAgentInfo(agentId, authHeader) {
|
|
|
2497
2573
|
// src/commands/run.ts
|
|
2498
2574
|
var MAX_ACTIVITY_LOG_ENTRIES = 10;
|
|
2499
2575
|
var CHANNEL_POLL_INTERVAL_MS = Number(process.env.EVIDENT_CHANNEL_POLL_INTERVAL_MS) || 2e3;
|
|
2500
|
-
function
|
|
2576
|
+
function log2(state, message, isError = false) {
|
|
2501
2577
|
if (state.json) {
|
|
2502
2578
|
console.log(
|
|
2503
2579
|
JSON.stringify({
|
|
@@ -2522,9 +2598,9 @@ function logActivity(state, entry) {
|
|
|
2522
2598
|
}
|
|
2523
2599
|
if (!state.interactive) {
|
|
2524
2600
|
if (entry.type === "error") {
|
|
2525
|
-
|
|
2601
|
+
log2(state, entry.error ?? "Unknown error", true);
|
|
2526
2602
|
} else if (entry.type === "info" && entry.message) {
|
|
2527
|
-
|
|
2603
|
+
log2(state, entry.message);
|
|
2528
2604
|
}
|
|
2529
2605
|
}
|
|
2530
2606
|
}
|
|
@@ -2610,6 +2686,7 @@ async function handleAuthError(state, error2) {
|
|
|
2610
2686
|
}
|
|
2611
2687
|
async function driveChannels(state, driver) {
|
|
2612
2688
|
let idlePolls = 0;
|
|
2689
|
+
let lastSeenProxiedActivityAt = state.lastProxiedActivityAt;
|
|
2613
2690
|
while (state.running) {
|
|
2614
2691
|
if (state.connection?.reconnecting && state.connection.reconnectPromise) {
|
|
2615
2692
|
logActivity(state, { type: "info", message: "Waiting for tunnel reconnection..." });
|
|
@@ -2619,7 +2696,9 @@ async function driveChannels(state, driver) {
|
|
|
2619
2696
|
try {
|
|
2620
2697
|
const processed = await driver.drainPending();
|
|
2621
2698
|
state.messageCount += processed;
|
|
2622
|
-
|
|
2699
|
+
const proxiedActivity = state.lastProxiedActivityAt !== lastSeenProxiedActivityAt;
|
|
2700
|
+
lastSeenProxiedActivityAt = state.lastProxiedActivityAt;
|
|
2701
|
+
if (processed > 0 || driver.hasInFlightWatchers() || proxiedActivity) {
|
|
2623
2702
|
idlePolls = 0;
|
|
2624
2703
|
if (processed > 0 && state.interactive) displayStatus(state);
|
|
2625
2704
|
} else if (state.idleTimeout !== null) {
|
|
@@ -2671,7 +2750,7 @@ async function cleanup(state) {
|
|
|
2671
2750
|
logActivity(state, { type: "info", message: "Stopped OpenCode process" });
|
|
2672
2751
|
displayStatus(state);
|
|
2673
2752
|
} else {
|
|
2674
|
-
|
|
2753
|
+
log2(state, "Stopped OpenCode process");
|
|
2675
2754
|
}
|
|
2676
2755
|
state.opencodeProcess = null;
|
|
2677
2756
|
}
|
|
@@ -2694,10 +2773,11 @@ async function run(options) {
|
|
|
2694
2773
|
running: true,
|
|
2695
2774
|
activityLog: [],
|
|
2696
2775
|
messageCount: 0,
|
|
2776
|
+
lastProxiedActivityAt: null,
|
|
2697
2777
|
authHeader: ""
|
|
2698
2778
|
};
|
|
2699
2779
|
if (state.idleTimeout === null && (process.env.GITHUB_ACTIONS || process.env.CI)) {
|
|
2700
|
-
|
|
2780
|
+
log2(
|
|
2701
2781
|
state,
|
|
2702
2782
|
"Warning: No --idle-timeout set in CI environment. The runner will poll indefinitely until the job times out. Consider adding --idle-timeout 30 to avoid wasting runner minutes.",
|
|
2703
2783
|
false
|
|
@@ -2708,7 +2788,7 @@ async function run(options) {
|
|
|
2708
2788
|
logActivity(state, { type: "info", message: "Shutting down..." });
|
|
2709
2789
|
displayStatus(state);
|
|
2710
2790
|
} else {
|
|
2711
|
-
|
|
2791
|
+
log2(state, "Shutting down...");
|
|
2712
2792
|
}
|
|
2713
2793
|
await cleanup(state);
|
|
2714
2794
|
await shutdownTelemetry();
|
|
@@ -2741,7 +2821,7 @@ async function run(options) {
|
|
|
2741
2821
|
const resolved = await resolveAgentIdFromKey(state.authHeader);
|
|
2742
2822
|
if (resolved.agent_id) {
|
|
2743
2823
|
state.agentId = resolved.agent_id;
|
|
2744
|
-
|
|
2824
|
+
log2(state, `Resolved agent ID from key: ${state.agentId}`);
|
|
2745
2825
|
if (state.interactive && !state.json) {
|
|
2746
2826
|
logActivity(state, {
|
|
2747
2827
|
type: "info",
|
|
@@ -2804,17 +2884,17 @@ async function run(options) {
|
|
|
2804
2884
|
port: state.port,
|
|
2805
2885
|
interactive: state.interactive,
|
|
2806
2886
|
agentId: state.agentId,
|
|
2807
|
-
log: (message) =>
|
|
2887
|
+
log: (message) => log2(state, message)
|
|
2808
2888
|
});
|
|
2809
2889
|
state.port = oc.port;
|
|
2810
2890
|
state.opencodeProcess = oc.process;
|
|
2811
2891
|
state.opencodeVersion = oc.version;
|
|
2812
2892
|
state.opencodeConnected = oc.process !== null || oc.version !== null;
|
|
2813
|
-
const
|
|
2814
|
-
ocSpinner?.succeed(`OpenCode running on port ${state.port}${
|
|
2893
|
+
const version2 = state.opencodeVersion ? ` (v${state.opencodeVersion})` : "";
|
|
2894
|
+
ocSpinner?.succeed(`OpenCode running on port ${state.port}${version2}`);
|
|
2815
2895
|
const versionWarning = buildOpenCodeVersionWarning(state.opencodeVersion);
|
|
2816
2896
|
if (versionWarning) {
|
|
2817
|
-
|
|
2897
|
+
log2(state, versionWarning, false);
|
|
2818
2898
|
if (state.interactive && !state.json) {
|
|
2819
2899
|
logActivity(state, { type: "info", message: versionWarning });
|
|
2820
2900
|
}
|
|
@@ -2883,9 +2963,14 @@ async function run(options) {
|
|
|
2883
2963
|
logActivity(state, { type: "error", error: error2 });
|
|
2884
2964
|
if (state.interactive) displayStatus(state);
|
|
2885
2965
|
},
|
|
2886
|
-
// Web traffic is proxied transparently;
|
|
2966
|
+
// Web traffic is proxied transparently; note opencode is live and stamp
|
|
2967
|
+
// proxied activity so the idle loop treats interactive proxy use as work.
|
|
2968
|
+
// Fires per forwarded response head (incl. every SSE open) and excludes
|
|
2969
|
+
// the internal drain-ping, so an actively-used proxy keeps the timer
|
|
2970
|
+
// fresh while a lone idle SSE with no follow-up requests still ages out.
|
|
2887
2971
|
onResponse: () => {
|
|
2888
2972
|
state.opencodeConnected = true;
|
|
2973
|
+
state.lastProxiedActivityAt = Date.now();
|
|
2889
2974
|
},
|
|
2890
2975
|
// A channel message was queued and the api-worker pinged us over the
|
|
2891
2976
|
// tunnel to drain immediately instead of waiting for the next poll tick.
|
|
@@ -2924,7 +3009,7 @@ async function run(options) {
|
|
|
2924
3009
|
throw error2;
|
|
2925
3010
|
}
|
|
2926
3011
|
if (!interactive || state.json) {
|
|
2927
|
-
|
|
3012
|
+
log2(state, "Driving channel messages...");
|
|
2928
3013
|
}
|
|
2929
3014
|
await driveChannels(state, channelDriver);
|
|
2930
3015
|
await cleanup(state);
|
|
@@ -2936,7 +3021,7 @@ async function run(options) {
|
|
|
2936
3021
|
})
|
|
2937
3022
|
);
|
|
2938
3023
|
} else if (!interactive) {
|
|
2939
|
-
|
|
3024
|
+
log2(state, `Completed. Processed ${state.messageCount} message(s).`);
|
|
2940
3025
|
}
|
|
2941
3026
|
await shutdownTelemetry();
|
|
2942
3027
|
process.exit(0);
|
|
@@ -2958,8 +3043,9 @@ async function run(options) {
|
|
|
2958
3043
|
}
|
|
2959
3044
|
|
|
2960
3045
|
// src/index.ts
|
|
3046
|
+
var { version } = createRequire(import.meta.url)("../package.json");
|
|
2961
3047
|
var program = new Command();
|
|
2962
|
-
program.name("evident").description("Run OpenCode locally and connect it to Evident").version(
|
|
3048
|
+
program.name("evident").description("Run OpenCode locally and connect it to Evident").version(version).option(
|
|
2963
3049
|
"--endpoint <url>",
|
|
2964
3050
|
"Evident API base URL (default: production; e.g. http://localhost:3001)"
|
|
2965
3051
|
).option("--tunnel <url>", "Tunnel WebSocket URL (default: production; e.g. ws://localhost:8787)").hook("preAction", (thisCommand) => {
|