@evident-ai/cli 3.6.1-dev.4831868 → 3.6.1-dev.4db2683
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 +4 -0
- package/dist/index.js +825 -147
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -723,6 +723,9 @@ async function reportResourceUsage(agentId, authHeader, usage) {
|
|
|
723
723
|
opencode_db_bytes: usage.opencodeDbBytes
|
|
724
724
|
});
|
|
725
725
|
}
|
|
726
|
+
async function reportSessionDeletions(agentId, authHeader, deletions) {
|
|
727
|
+
return postBestEffort(`/runners/${agentId}/session-deletions`, authHeader, { deletions });
|
|
728
|
+
}
|
|
726
729
|
async function getRunnerCredentialVault(agentId, authHeader) {
|
|
727
730
|
const empty = { prefix: null, vaultId: null, source: null };
|
|
728
731
|
try {
|
|
@@ -1176,9 +1179,9 @@ async function claudeUsage() {
|
|
|
1176
1179
|
}
|
|
1177
1180
|
|
|
1178
1181
|
// src/commands/run.ts
|
|
1179
|
-
import { chmodSync as chmodSync3, existsSync as existsSync3, readFileSync as
|
|
1182
|
+
import { chmodSync as chmodSync3, existsSync as existsSync3, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "node:fs";
|
|
1180
1183
|
import { homedir as homedir8 } from "node:os";
|
|
1181
|
-
import { isAbsolute as isAbsolute4, join as
|
|
1184
|
+
import { isAbsolute as isAbsolute4, join as join14, parse, resolve as resolvePath2 } from "node:path";
|
|
1182
1185
|
import chalk7 from "chalk";
|
|
1183
1186
|
|
|
1184
1187
|
// ../../packages/types/src/agents/index.ts
|
|
@@ -1332,8 +1335,29 @@ var NATIVE_PROVIDERS = {
|
|
|
1332
1335
|
name: "Evident Kimi credential proxy",
|
|
1333
1336
|
npm: "@ai-sdk/openai-compatible",
|
|
1334
1337
|
sdkName: "evident-kimi"
|
|
1338
|
+
},
|
|
1339
|
+
antigravity: {
|
|
1340
|
+
namespace: "evident-antigravity",
|
|
1341
|
+
name: "Evident Antigravity credential proxy",
|
|
1342
|
+
npm: "@ai-sdk/openai-compatible",
|
|
1343
|
+
sdkName: "evident-antigravity"
|
|
1344
|
+
},
|
|
1345
|
+
xai: {
|
|
1346
|
+
namespace: "evident-xai",
|
|
1347
|
+
name: "Evident xAI credential proxy",
|
|
1348
|
+
npm: "@ai-sdk/openai-compatible",
|
|
1349
|
+
sdkName: "evident-xai"
|
|
1350
|
+
},
|
|
1351
|
+
mistral: {
|
|
1352
|
+
namespace: "evident-mistral",
|
|
1353
|
+
name: "Evident Mistral credential proxy",
|
|
1354
|
+
npm: "@ai-sdk/openai-compatible",
|
|
1355
|
+
sdkName: "evident-mistral"
|
|
1335
1356
|
}
|
|
1336
1357
|
};
|
|
1358
|
+
var CREDENTIAL_PROXY_NATIVE_NAMESPACES = [
|
|
1359
|
+
...new Set(Object.values(NATIVE_PROVIDERS).map(({ namespace }) => namespace))
|
|
1360
|
+
];
|
|
1337
1361
|
function credentialProxyInferenceUrl(baseUrl) {
|
|
1338
1362
|
const trimmed = baseUrl.trim().replace(/\/+$/, "");
|
|
1339
1363
|
return /\/v1$/i.test(trimmed) ? trimmed : `${trimmed}/v1`;
|
|
@@ -1421,7 +1445,7 @@ import ora4 from "ora";
|
|
|
1421
1445
|
import { select as select4 } from "@inquirer/prompts";
|
|
1422
1446
|
|
|
1423
1447
|
// src/lib/telemetry.ts
|
|
1424
|
-
var CLI_VERSION = (true ? "3.6.1-dev.
|
|
1448
|
+
var CLI_VERSION = (true ? "3.6.1-dev.4db2683" : void 0) ?? process.env.npm_package_version ?? "unknown";
|
|
1425
1449
|
function getCliVersion() {
|
|
1426
1450
|
return CLI_VERSION;
|
|
1427
1451
|
}
|
|
@@ -2617,6 +2641,122 @@ function isSessionDbRecoveryRecord(value) {
|
|
|
2617
2641
|
);
|
|
2618
2642
|
}
|
|
2619
2643
|
|
|
2644
|
+
// src/lib/opencode/session-deletion-report.ts
|
|
2645
|
+
import { appendFileSync, mkdirSync, readFileSync as readFileSync3, unlinkSync as unlinkSync2, writeFileSync } from "node:fs";
|
|
2646
|
+
import { dirname as dirname4, join as join4 } from "node:path";
|
|
2647
|
+
var SESSION_DELETION_SPOOL_MAX_RECORDS = 1e3;
|
|
2648
|
+
var MAX_DIAGNOSTIC_LENGTH = 200;
|
|
2649
|
+
function sessionDeletionReportPath(homeDir, env) {
|
|
2650
|
+
const override = env.EVIDENT_SESSION_DELETION_REPORT?.trim();
|
|
2651
|
+
return override || join4(homeDir, ".local", "state", "evident", "session-deletions.jsonl");
|
|
2652
|
+
}
|
|
2653
|
+
function appendSessionDeletionRecords(path, records) {
|
|
2654
|
+
if (records.length === 0) return 0;
|
|
2655
|
+
mkdirSync(dirname4(path), { recursive: true });
|
|
2656
|
+
const current = readSessionDeletionRecords(path);
|
|
2657
|
+
if (current.readError !== null) {
|
|
2658
|
+
throw new Error(`cannot enforce the spool limit after reading ${path}: ${current.readError}`);
|
|
2659
|
+
}
|
|
2660
|
+
const combined = [...current.records, ...records];
|
|
2661
|
+
const kept = combined.slice(-SESSION_DELETION_SPOOL_MAX_RECORDS);
|
|
2662
|
+
const dropped = combined.length - kept.length;
|
|
2663
|
+
if (dropped > 0) {
|
|
2664
|
+
writeFileSync(path, `${kept.map((record) => JSON.stringify(record)).join("\n")}
|
|
2665
|
+
`, "utf8");
|
|
2666
|
+
} else {
|
|
2667
|
+
appendFileSync(path, `${records.map((record) => JSON.stringify(record)).join("\n")}
|
|
2668
|
+
`, "utf8");
|
|
2669
|
+
}
|
|
2670
|
+
return dropped;
|
|
2671
|
+
}
|
|
2672
|
+
function readSessionDeletionRecords(path) {
|
|
2673
|
+
let content;
|
|
2674
|
+
try {
|
|
2675
|
+
content = readFileSync3(path, "utf8");
|
|
2676
|
+
} catch (error2) {
|
|
2677
|
+
if (isMissingFile(error2)) return { records: [], skippedLines: 0, readError: null };
|
|
2678
|
+
const readError = boundedError(error2);
|
|
2679
|
+
console.error(`[session-deletion-report] could not read ${path}: ${readError}`);
|
|
2680
|
+
return { records: [], skippedLines: 0, readError };
|
|
2681
|
+
}
|
|
2682
|
+
let skippedLines = 0;
|
|
2683
|
+
const records = content.split("\n").flatMap((line) => {
|
|
2684
|
+
if (!line.trim()) return [];
|
|
2685
|
+
const record = parseRecord(line, path);
|
|
2686
|
+
if (record) return [record];
|
|
2687
|
+
skippedLines++;
|
|
2688
|
+
return [];
|
|
2689
|
+
});
|
|
2690
|
+
return { records, skippedLines, readError: null };
|
|
2691
|
+
}
|
|
2692
|
+
function removeDeliveredSessionDeletionRecords(path, delivered) {
|
|
2693
|
+
let content;
|
|
2694
|
+
try {
|
|
2695
|
+
content = readFileSync3(path, "utf8");
|
|
2696
|
+
} catch (error2) {
|
|
2697
|
+
if (isMissingFile(error2)) return;
|
|
2698
|
+
console.error(
|
|
2699
|
+
`[session-deletion-report] could not read ${path} before removing delivered records: ${boundedError(error2)}`
|
|
2700
|
+
);
|
|
2701
|
+
throw error2;
|
|
2702
|
+
}
|
|
2703
|
+
const toRemove = /* @__PURE__ */ new Map();
|
|
2704
|
+
for (const record of delivered) {
|
|
2705
|
+
toRemove.set(record.opencode_session_id, (toRemove.get(record.opencode_session_id) ?? 0) + 1);
|
|
2706
|
+
}
|
|
2707
|
+
const remaining = [];
|
|
2708
|
+
for (const line of content.split("\n")) {
|
|
2709
|
+
if (!line.trim()) continue;
|
|
2710
|
+
const record = parseRecord(line, path);
|
|
2711
|
+
const count = record ? toRemove.get(record.opencode_session_id) ?? 0 : 0;
|
|
2712
|
+
if (record && count > 0) {
|
|
2713
|
+
if (count === 1) toRemove.delete(record.opencode_session_id);
|
|
2714
|
+
else toRemove.set(record.opencode_session_id, count - 1);
|
|
2715
|
+
} else {
|
|
2716
|
+
remaining.push(line);
|
|
2717
|
+
}
|
|
2718
|
+
}
|
|
2719
|
+
try {
|
|
2720
|
+
if (remaining.length === 0) unlinkSync2(path);
|
|
2721
|
+
else writeFileSync(path, `${remaining.join("\n")}
|
|
2722
|
+
`, "utf8");
|
|
2723
|
+
} catch (error2) {
|
|
2724
|
+
if (isMissingFile(error2)) return;
|
|
2725
|
+
console.error(
|
|
2726
|
+
`[session-deletion-report] could not write ${path} after removing delivered records: ${boundedError(error2)}`
|
|
2727
|
+
);
|
|
2728
|
+
throw error2;
|
|
2729
|
+
}
|
|
2730
|
+
}
|
|
2731
|
+
function parseRecord(line, path) {
|
|
2732
|
+
try {
|
|
2733
|
+
const value = JSON.parse(line);
|
|
2734
|
+
if (isSessionDeletionRecord(value)) return value;
|
|
2735
|
+
console.error(`[session-deletion-report] skipped malformed record in ${path}: invalid shape`);
|
|
2736
|
+
return null;
|
|
2737
|
+
} catch (error2) {
|
|
2738
|
+
console.error(
|
|
2739
|
+
`[session-deletion-report] skipped malformed record in ${path}: ${boundedError(error2)}`
|
|
2740
|
+
);
|
|
2741
|
+
return null;
|
|
2742
|
+
}
|
|
2743
|
+
}
|
|
2744
|
+
function isSessionDeletionRecord(value) {
|
|
2745
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
2746
|
+
const record = value;
|
|
2747
|
+
const policy = record.policy;
|
|
2748
|
+
if (!policy || typeof policy !== "object" || Array.isArray(policy)) return false;
|
|
2749
|
+
const fields = policy;
|
|
2750
|
+
return typeof record.opencode_session_id === "string" && record.opencode_session_id.length > 0 && typeof record.deleted_at === "string" && record.reason === "auto_cleanup" && (fields.max_age_ms === null || typeof fields.max_age_ms === "number") && (fields.max_count === null || typeof fields.max_count === "number") && (fields.last_activity_at === null || typeof fields.last_activity_at === "string");
|
|
2751
|
+
}
|
|
2752
|
+
function isMissingFile(error2) {
|
|
2753
|
+
return error2 instanceof Error && "code" in error2 && error2.code === "ENOENT";
|
|
2754
|
+
}
|
|
2755
|
+
function boundedError(error2) {
|
|
2756
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
2757
|
+
return message.slice(0, MAX_DIAGNOSTIC_LENGTH);
|
|
2758
|
+
}
|
|
2759
|
+
|
|
2620
2760
|
// src/lib/opencode/auth.ts
|
|
2621
2761
|
function buildOpenCodeBasicAuthHeader(password) {
|
|
2622
2762
|
return `Basic ${Buffer.from(["opencode", password].join(":")).toString("base64")}`;
|
|
@@ -3188,6 +3328,52 @@ async function getModelAttachmentCapability(port, model, client) {
|
|
|
3188
3328
|
return null;
|
|
3189
3329
|
}
|
|
3190
3330
|
}
|
|
3331
|
+
async function isPinnedProviderLoaded(port, model, client) {
|
|
3332
|
+
if (client?.version === "v2") return null;
|
|
3333
|
+
const { model: baseModel } = splitModelVariant(model);
|
|
3334
|
+
if (!baseModel) return null;
|
|
3335
|
+
const slash = baseModel.indexOf("/");
|
|
3336
|
+
if (slash <= 0) return null;
|
|
3337
|
+
const providerId = baseModel.slice(0, slash);
|
|
3338
|
+
try {
|
|
3339
|
+
const res = await requestWithClient(port, client, "/config/providers");
|
|
3340
|
+
if (!res.ok) {
|
|
3341
|
+
console.error(
|
|
3342
|
+
`[isPinnedProviderLoaded] GET /config/providers returned HTTP ${res.status} (port ${port})`
|
|
3343
|
+
);
|
|
3344
|
+
return null;
|
|
3345
|
+
}
|
|
3346
|
+
const body = await res.json();
|
|
3347
|
+
if (body === null || typeof body !== "object" || Array.isArray(body)) {
|
|
3348
|
+
console.error(
|
|
3349
|
+
`[isPinnedProviderLoaded] GET /config/providers body was not a plain object (port ${port})`
|
|
3350
|
+
);
|
|
3351
|
+
return null;
|
|
3352
|
+
}
|
|
3353
|
+
const providers = body.providers;
|
|
3354
|
+
if (!Array.isArray(providers)) {
|
|
3355
|
+
console.error(
|
|
3356
|
+
`[isPinnedProviderLoaded] GET /config/providers body had no providers array (port ${port})`
|
|
3357
|
+
);
|
|
3358
|
+
return null;
|
|
3359
|
+
}
|
|
3360
|
+
const providerIds = providers.filter(
|
|
3361
|
+
(provider) => provider !== null && typeof provider === "object" && typeof provider.id === "string"
|
|
3362
|
+
).map((provider) => provider.id);
|
|
3363
|
+
if (providerIds.length === 0) {
|
|
3364
|
+
console.error(
|
|
3365
|
+
`[isPinnedProviderLoaded] GET /config/providers had no provider entries with string ids (port ${port})`
|
|
3366
|
+
);
|
|
3367
|
+
return null;
|
|
3368
|
+
}
|
|
3369
|
+
return providerIds.includes(providerId);
|
|
3370
|
+
} catch (err) {
|
|
3371
|
+
console.error(
|
|
3372
|
+
`[isPinnedProviderLoaded] GET /config/providers failed (port ${port}): ${err instanceof Error ? err.message : String(err)}`
|
|
3373
|
+
);
|
|
3374
|
+
return null;
|
|
3375
|
+
}
|
|
3376
|
+
}
|
|
3191
3377
|
async function buildFileParts(attachments, capable) {
|
|
3192
3378
|
const outcomes = [];
|
|
3193
3379
|
const parts = [];
|
|
@@ -3602,9 +3788,17 @@ function isPreamblePinnedRunning(messages, userMessageId) {
|
|
|
3602
3788
|
const reply = findLastAssistantReplyFor(messages, userMessageId);
|
|
3603
3789
|
return completedOf(reply) != null && finishOf(reply) === "tool-calls";
|
|
3604
3790
|
}
|
|
3791
|
+
function isMidFlightPinnedRunning(messages, userMessageId) {
|
|
3792
|
+
if (messageRunState(messages, userMessageId) !== "running") return false;
|
|
3793
|
+
const reply = findLastAssistantReplyFor(messages, userMessageId);
|
|
3794
|
+
return reply !== null && isAssistantInFlight(reply) && completedOf(reply) == null;
|
|
3795
|
+
}
|
|
3605
3796
|
function isB2AbandonmentConfirmed(params) {
|
|
3606
3797
|
return params.pinnedForMs >= params.minPinnedMs && params.descendantOngoing === false && params.rootOngoing === false;
|
|
3607
3798
|
}
|
|
3799
|
+
function isMidFlightOrphanConfirmed(params) {
|
|
3800
|
+
return params.pinnedForMs >= params.minPinnedMs && params.notOngoingStreak >= params.minNotOngoingStreak;
|
|
3801
|
+
}
|
|
3608
3802
|
function isAmbiguousTerminalFinish(m) {
|
|
3609
3803
|
if (completedOf(m) == null) return false;
|
|
3610
3804
|
if (errorOf(m) != null) return false;
|
|
@@ -4470,13 +4664,13 @@ async function readV2SessionErrorStream(client, options) {
|
|
|
4470
4664
|
}
|
|
4471
4665
|
|
|
4472
4666
|
// src/lib/opencode/credential-import-v2.ts
|
|
4473
|
-
import { readFileSync as
|
|
4474
|
-
import { join as
|
|
4667
|
+
import { readFileSync as readFileSync5 } from "node:fs";
|
|
4668
|
+
import { join as join6 } from "node:path";
|
|
4475
4669
|
|
|
4476
4670
|
// src/lib/openai-usage.ts
|
|
4477
|
-
import { readFileSync as
|
|
4671
|
+
import { readFileSync as readFileSync4 } from "node:fs";
|
|
4478
4672
|
import { homedir as homedir3 } from "node:os";
|
|
4479
|
-
import { join as
|
|
4673
|
+
import { join as join5 } from "node:path";
|
|
4480
4674
|
var CODEX_RESPONSES_URL = "https://chatgpt.com/backend-api/codex/responses";
|
|
4481
4675
|
var OPENCODE_AUTH_SEGMENTS = [".local", "share", "opencode", "auth.json"];
|
|
4482
4676
|
var OpenAiUsageError = class extends Error {
|
|
@@ -4490,7 +4684,7 @@ function isLocalCredentialProblem2(err) {
|
|
|
4490
4684
|
}
|
|
4491
4685
|
function readOpenCodeChatGptCredentials() {
|
|
4492
4686
|
try {
|
|
4493
|
-
const raw =
|
|
4687
|
+
const raw = readFileSync4(join5(homedir3(), ...OPENCODE_AUTH_SEGMENTS), "utf-8");
|
|
4494
4688
|
let parsed;
|
|
4495
4689
|
try {
|
|
4496
4690
|
parsed = JSON.parse(raw);
|
|
@@ -4722,7 +4916,7 @@ function emptyResult(unsupported = [], outcome = "completed") {
|
|
|
4722
4916
|
}
|
|
4723
4917
|
function readOpenCodeCredentials(homeDir, log3) {
|
|
4724
4918
|
try {
|
|
4725
|
-
const raw =
|
|
4919
|
+
const raw = readFileSync5(join6(homeDir, ...OPENCODE_AUTH_SEGMENTS), "utf8");
|
|
4726
4920
|
let parsed;
|
|
4727
4921
|
try {
|
|
4728
4922
|
parsed = JSON.parse(raw);
|
|
@@ -4922,9 +5116,9 @@ async function importOpenCodeV2Credentials({
|
|
|
4922
5116
|
|
|
4923
5117
|
// src/lib/opencode/session-db-boot.ts
|
|
4924
5118
|
import { spawn as spawn2 } from "node:child_process";
|
|
4925
|
-
import { mkdirSync, statSync as statSync3, unlinkSync as
|
|
5119
|
+
import { mkdirSync as mkdirSync2, statSync as statSync3, unlinkSync as unlinkSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
4926
5120
|
import { homedir as homedir4 } from "node:os";
|
|
4927
|
-
import { dirname as
|
|
5121
|
+
import { dirname as dirname5, resolve as resolvePath } from "node:path";
|
|
4928
5122
|
|
|
4929
5123
|
// src/lib/runner-synchroniser.ts
|
|
4930
5124
|
import { spawn } from "node:child_process";
|
|
@@ -5040,7 +5234,7 @@ function reportRecord(stage, outcome, reason, litestreamExitCode, options) {
|
|
|
5040
5234
|
function clearMarker(options) {
|
|
5041
5235
|
if (!options.noReplicateMarker) return;
|
|
5042
5236
|
try {
|
|
5043
|
-
|
|
5237
|
+
unlinkSync3(options.noReplicateMarker);
|
|
5044
5238
|
} catch (error2) {
|
|
5045
5239
|
if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT") return;
|
|
5046
5240
|
options.log(
|
|
@@ -5052,8 +5246,8 @@ function clearMarker(options) {
|
|
|
5052
5246
|
function markNoReplicate(options, message) {
|
|
5053
5247
|
if (options.noReplicateMarker) {
|
|
5054
5248
|
try {
|
|
5055
|
-
|
|
5056
|
-
|
|
5249
|
+
mkdirSync2(dirname5(options.noReplicateMarker), { recursive: true });
|
|
5250
|
+
writeFileSync2(options.noReplicateMarker, "");
|
|
5057
5251
|
} catch (error2) {
|
|
5058
5252
|
options.log(
|
|
5059
5253
|
`Could not write session-DB no-replicate marker ${options.noReplicateMarker}: ${error2 instanceof Error ? error2.message : String(error2)}`,
|
|
@@ -5066,7 +5260,7 @@ function markNoReplicate(options, message) {
|
|
|
5066
5260
|
function discardSessionDbDebris(options) {
|
|
5067
5261
|
for (const path of [options.dbPath, `${options.dbPath}-wal`, `${options.dbPath}-shm`]) {
|
|
5068
5262
|
try {
|
|
5069
|
-
|
|
5263
|
+
unlinkSync3(path);
|
|
5070
5264
|
} catch (error2) {
|
|
5071
5265
|
if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT") continue;
|
|
5072
5266
|
options.log(
|
|
@@ -5207,8 +5401,8 @@ async function ensureLitestreamConfig(options, env) {
|
|
|
5207
5401
|
return null;
|
|
5208
5402
|
}
|
|
5209
5403
|
try {
|
|
5210
|
-
|
|
5211
|
-
|
|
5404
|
+
mkdirSync2(dirname5(configPath), { recursive: true });
|
|
5405
|
+
writeFileSync2(configPath, rendered.stdout);
|
|
5212
5406
|
} catch (error2) {
|
|
5213
5407
|
options.log(
|
|
5214
5408
|
`Could not write ${configPath}: ${error2 instanceof Error ? error2.message : String(error2)}`,
|
|
@@ -5473,8 +5667,8 @@ async function restoreAndVerifySessionDb(options) {
|
|
|
5473
5667
|
|
|
5474
5668
|
// src/lib/opencode/session-db-provenance.ts
|
|
5475
5669
|
import { createRequire } from "node:module";
|
|
5476
|
-
import { mkdirSync as
|
|
5477
|
-
import { dirname as
|
|
5670
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "node:fs";
|
|
5671
|
+
import { dirname as dirname6, join as join7 } from "node:path";
|
|
5478
5672
|
var require2 = createRequire(import.meta.url);
|
|
5479
5673
|
function readSessionDbMigrationIds(dbPath) {
|
|
5480
5674
|
let db;
|
|
@@ -5511,12 +5705,12 @@ function readSessionDbMigrationIds(dbPath) {
|
|
|
5511
5705
|
}
|
|
5512
5706
|
function sessionDbProvenanceStatePath(homeDir, env) {
|
|
5513
5707
|
const override = env.EVIDENT_SESSION_DB_PROVENANCE_STATE?.trim();
|
|
5514
|
-
return override ||
|
|
5708
|
+
return override || join7(homeDir, ".local", "state", "evident", "session-db-provenance.json");
|
|
5515
5709
|
}
|
|
5516
5710
|
function loadSessionDbProvenanceState(path) {
|
|
5517
5711
|
let value;
|
|
5518
5712
|
try {
|
|
5519
|
-
value = JSON.parse(
|
|
5713
|
+
value = JSON.parse(readFileSync6(path, "utf8"));
|
|
5520
5714
|
} catch (error2) {
|
|
5521
5715
|
if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT") return {};
|
|
5522
5716
|
console.error(
|
|
@@ -5540,8 +5734,8 @@ function loadSessionDbProvenanceState(path) {
|
|
|
5540
5734
|
}
|
|
5541
5735
|
function saveSessionDbProvenanceState(path, state) {
|
|
5542
5736
|
try {
|
|
5543
|
-
|
|
5544
|
-
|
|
5737
|
+
mkdirSync3(dirname6(path), { recursive: true });
|
|
5738
|
+
writeFileSync3(path, `${JSON.stringify(state, null, 2)}
|
|
5545
5739
|
`, "utf8");
|
|
5546
5740
|
} catch (error2) {
|
|
5547
5741
|
console.error(
|
|
@@ -5600,7 +5794,11 @@ function isSessionDbProvenanceRecord(value) {
|
|
|
5600
5794
|
var OPENCODE2_PACKAGE_SPEC = "@opencode-ai/cli@0.0.0-beta-17823";
|
|
5601
5795
|
|
|
5602
5796
|
// src/lib/opencode/opencode-version-gate.ts
|
|
5603
|
-
var QUEUE_VALIDATED_OPENCODE_VERSIONS = [
|
|
5797
|
+
var QUEUE_VALIDATED_OPENCODE_VERSIONS = [
|
|
5798
|
+
"1.17.11",
|
|
5799
|
+
"1.18.3",
|
|
5800
|
+
"1.18.35"
|
|
5801
|
+
];
|
|
5604
5802
|
function isQueueValidatedVersion(version2) {
|
|
5605
5803
|
if (!version2) return false;
|
|
5606
5804
|
return QUEUE_VALIDATED_OPENCODE_VERSIONS.includes(version2);
|
|
@@ -6185,15 +6383,15 @@ function resolveSessionCleanupConfig(flags, env = process.env) {
|
|
|
6185
6383
|
|
|
6186
6384
|
// src/lib/opencode/session-db-size.ts
|
|
6187
6385
|
import { statSync as statSync4 } from "node:fs";
|
|
6188
|
-
import { join as
|
|
6386
|
+
import { join as join8 } from "node:path";
|
|
6189
6387
|
var LARGE_DB_THRESHOLD_BYTES = 268435456;
|
|
6190
6388
|
function statSessionDbBytes(homeDir) {
|
|
6191
|
-
const dbPath =
|
|
6389
|
+
const dbPath = join8(homeDir, ".local", "share", "opencode", "opencode.db");
|
|
6192
6390
|
try {
|
|
6193
6391
|
return statSync4(dbPath).size;
|
|
6194
6392
|
} catch (err) {
|
|
6195
|
-
const
|
|
6196
|
-
if (!
|
|
6393
|
+
const isMissingFile2 = err instanceof Error && "code" in err && err.code === "ENOENT";
|
|
6394
|
+
if (!isMissingFile2) {
|
|
6197
6395
|
console.error(
|
|
6198
6396
|
`[statSessionDbBytes] could not stat ${dbPath}: ${err instanceof Error ? err.message : String(err)}`
|
|
6199
6397
|
);
|
|
@@ -6218,12 +6416,12 @@ function buildSessionStoreSizeWarning(input) {
|
|
|
6218
6416
|
// src/lib/opencode/log-tail.ts
|
|
6219
6417
|
import { statSync as statSync5 } from "node:fs";
|
|
6220
6418
|
import { homedir as homedir5 } from "node:os";
|
|
6221
|
-
import { join as
|
|
6419
|
+
import { join as join9 } from "node:path";
|
|
6222
6420
|
import { open as open2, stat } from "node:fs/promises";
|
|
6223
6421
|
var DEFAULT_POLL_INTERVAL_MS = 1e3;
|
|
6224
6422
|
function resolveOpenCodeLogPath(homeDir = homedir5(), env = process.env) {
|
|
6225
|
-
const dataDir = env.XDG_DATA_HOME ||
|
|
6226
|
-
return
|
|
6423
|
+
const dataDir = env.XDG_DATA_HOME || join9(homeDir, ".local", "share");
|
|
6424
|
+
return join9(dataDir, "opencode", "log", "opencode.log");
|
|
6227
6425
|
}
|
|
6228
6426
|
function isEnoent(error2) {
|
|
6229
6427
|
return error2?.code === "ENOENT";
|
|
@@ -6645,6 +6843,7 @@ function connectTunnel(options) {
|
|
|
6645
6843
|
}, 3e4);
|
|
6646
6844
|
let relaySilenceTimeout;
|
|
6647
6845
|
let relaySilenceTimedOut = false;
|
|
6846
|
+
let relayConnectionId;
|
|
6648
6847
|
const clearRelaySilenceTimeout = () => {
|
|
6649
6848
|
if (relaySilenceTimeout === void 0) return;
|
|
6650
6849
|
clearTimeout(relaySilenceTimeout);
|
|
@@ -6730,7 +6929,8 @@ function connectTunnel(options) {
|
|
|
6730
6929
|
armRelaySilenceTimeout();
|
|
6731
6930
|
forwarder.setBinaryFramesSupported(message.binary_frames === true);
|
|
6732
6931
|
const connectedAgentId = message.agent_id ?? agentId;
|
|
6733
|
-
|
|
6932
|
+
relayConnectionId = message.connection_id;
|
|
6933
|
+
onConnected?.(connectedAgentId, relayConnectionId);
|
|
6734
6934
|
resolve5({
|
|
6735
6935
|
ws,
|
|
6736
6936
|
close: () => ws.close(1e3, "CLI shutdown")
|
|
@@ -6767,7 +6967,7 @@ function connectTunnel(options) {
|
|
|
6767
6967
|
clearRelaySilenceTimeout();
|
|
6768
6968
|
const reasonStr = (relaySilenceTimedOut ? "no relay heartbeat for 90s \u2014 reconnecting" : void 0) || reason.toString() || upgradeRejection || (code === 1006 ? "abnormal closure" : "No reason provided");
|
|
6769
6969
|
forwarder.abortAll();
|
|
6770
|
-
onDisconnected?.(code, reasonStr);
|
|
6970
|
+
onDisconnected?.(code, reasonStr, relayConnectionId);
|
|
6771
6971
|
});
|
|
6772
6972
|
});
|
|
6773
6973
|
}
|
|
@@ -6830,7 +7030,7 @@ var RunnerConnection = class _RunnerConnection {
|
|
|
6830
7030
|
authHeader: this.opts.getAuthHeader(),
|
|
6831
7031
|
port: this.opts.port,
|
|
6832
7032
|
openCodePassword: this.opts.openCodePassword,
|
|
6833
|
-
onConnected: (agentId) => {
|
|
7033
|
+
onConnected: (agentId, relayConnectionId) => {
|
|
6834
7034
|
const attempt = this.reconnectAttempt + 1;
|
|
6835
7035
|
this.reconnectAttempt = 0;
|
|
6836
7036
|
this.reconnecting = false;
|
|
@@ -6840,13 +7040,17 @@ var RunnerConnection = class _RunnerConnection {
|
|
|
6840
7040
|
this.disconnectedAtMs = null;
|
|
6841
7041
|
events.onConnected(agentId, isReconnect, {
|
|
6842
7042
|
generation: this.generation,
|
|
7043
|
+
relayConnectionId: relayConnectionId ?? null,
|
|
6843
7044
|
attempt,
|
|
6844
7045
|
downtimeMs
|
|
6845
7046
|
});
|
|
6846
7047
|
},
|
|
6847
|
-
onDisconnected: (code, reason) => {
|
|
7048
|
+
onDisconnected: (code, reason, relayConnectionId) => {
|
|
6848
7049
|
this.disconnectedAtMs = this.now();
|
|
6849
|
-
events.onDisconnected(code, reason, {
|
|
7050
|
+
events.onDisconnected(code, reason, {
|
|
7051
|
+
generation: this.generation,
|
|
7052
|
+
relayConnectionId: relayConnectionId ?? null
|
|
7053
|
+
});
|
|
6850
7054
|
const relayRequestedReconnect = code === 1e3 && _RunnerConnection.RELAY_RECONNECT_CLOSE_REASONS.has(reason);
|
|
6851
7055
|
if (this.opts.isRunning() && (code !== 1e3 || relayRequestedReconnect) && !this.reconnecting) {
|
|
6852
7056
|
this.reconnectPromise = this.connectWithRetry(true).catch((err) => {
|
|
@@ -6884,10 +7088,10 @@ var RunnerConnection = class _RunnerConnection {
|
|
|
6884
7088
|
};
|
|
6885
7089
|
|
|
6886
7090
|
// src/lib/tunnel/ready-marker.ts
|
|
6887
|
-
import { writeFileSync as
|
|
7091
|
+
import { writeFileSync as writeFileSync4 } from "node:fs";
|
|
6888
7092
|
function writeTunnelReadyMarker(path, agentId) {
|
|
6889
7093
|
try {
|
|
6890
|
-
|
|
7094
|
+
writeFileSync4(path, `${agentId}
|
|
6891
7095
|
`);
|
|
6892
7096
|
return { ok: true };
|
|
6893
7097
|
} catch (error2) {
|
|
@@ -6912,7 +7116,7 @@ async function stopSessionDbReplication(child, timeoutMs) {
|
|
|
6912
7116
|
}
|
|
6913
7117
|
|
|
6914
7118
|
// src/lib/process-liveness.ts
|
|
6915
|
-
import { readFileSync as
|
|
7119
|
+
import { readFileSync as readFileSync7 } from "node:fs";
|
|
6916
7120
|
function isProcessAlive(pid) {
|
|
6917
7121
|
try {
|
|
6918
7122
|
process.kill(pid, 0);
|
|
@@ -6927,7 +7131,7 @@ function isProcessAlive(pid) {
|
|
|
6927
7131
|
}
|
|
6928
7132
|
if (process.platform !== "linux") return true;
|
|
6929
7133
|
try {
|
|
6930
|
-
const status2 =
|
|
7134
|
+
const status2 = readFileSync7(`/proc/${pid}/status`, "utf8");
|
|
6931
7135
|
return !/^State:\s+Z(?:\s|$)/m.test(status2);
|
|
6932
7136
|
} catch (error2) {
|
|
6933
7137
|
console.error(
|
|
@@ -7286,12 +7490,12 @@ function createSessionStoreHealth(options = {}) {
|
|
|
7286
7490
|
import { homedir as homedir6 } from "node:os";
|
|
7287
7491
|
|
|
7288
7492
|
// src/lib/runner-file-sync.ts
|
|
7289
|
-
import { join as
|
|
7493
|
+
import { join as join11 } from "node:path";
|
|
7290
7494
|
|
|
7291
7495
|
// src/lib/file-push.ts
|
|
7292
7496
|
import { randomUUID } from "node:crypto";
|
|
7293
7497
|
import { chmod, mkdir, open as open3, realpath, rename, unlink } from "node:fs/promises";
|
|
7294
|
-
import { basename as basename2, dirname as
|
|
7498
|
+
import { basename as basename2, dirname as dirname7, isAbsolute as isAbsolute2, join as join10, relative as relative2, resolve as resolve3, sep as sep2 } from "node:path";
|
|
7295
7499
|
var FILE_MODE = 384;
|
|
7296
7500
|
var DIRECTORY_MODE = 448;
|
|
7297
7501
|
async function writePushedFile(request3) {
|
|
@@ -7322,9 +7526,9 @@ async function writePushedFile(request3) {
|
|
|
7322
7526
|
}
|
|
7323
7527
|
try {
|
|
7324
7528
|
const { existingAncestor, missingSegments } = await resolveNearestExistingAncestor(
|
|
7325
|
-
|
|
7529
|
+
dirname7(candidate)
|
|
7326
7530
|
);
|
|
7327
|
-
const realTarget =
|
|
7531
|
+
const realTarget = join10(existingAncestor, ...missingSegments, basename2(candidate));
|
|
7328
7532
|
const allowedDirectory = await findContainingAllowedDirectory(allowedDirectories, realTarget);
|
|
7329
7533
|
if (allowedDirectory === null) {
|
|
7330
7534
|
return refuse("path_not_allowed", "The runner does not allow writing to that location.", {
|
|
@@ -7334,8 +7538,8 @@ async function writePushedFile(request3) {
|
|
|
7334
7538
|
}
|
|
7335
7539
|
if (missingSegments.length > 0) {
|
|
7336
7540
|
await createMissingDirectories(existingAncestor, missingSegments);
|
|
7337
|
-
const realParent = await realpath(
|
|
7338
|
-
if (realParent !==
|
|
7541
|
+
const realParent = await realpath(dirname7(realTarget));
|
|
7542
|
+
if (realParent !== dirname7(realTarget) || !contains(allowedDirectory, realTarget)) {
|
|
7339
7543
|
return refuse("path_not_allowed", "The runner does not allow writing to that location.", {
|
|
7340
7544
|
path: realTarget,
|
|
7341
7545
|
bytes,
|
|
@@ -7360,7 +7564,7 @@ function expandAndValidate(requestedPath, homeDir) {
|
|
|
7360
7564
|
if (requestedPath.trim() === "" || requestedPath.includes("\0")) {
|
|
7361
7565
|
return null;
|
|
7362
7566
|
}
|
|
7363
|
-
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ?
|
|
7567
|
+
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join10(homeDir, requestedPath.slice(2)) : requestedPath;
|
|
7364
7568
|
if (expanded.split(/[/\\]/).includes("..")) {
|
|
7365
7569
|
return null;
|
|
7366
7570
|
}
|
|
@@ -7378,7 +7582,7 @@ async function resolveNearestExistingAncestor(directory) {
|
|
|
7378
7582
|
try {
|
|
7379
7583
|
return { existingAncestor: await realpath(current), missingSegments };
|
|
7380
7584
|
} catch (err) {
|
|
7381
|
-
const parent =
|
|
7585
|
+
const parent = dirname7(current);
|
|
7382
7586
|
if (err.code !== "ENOENT" || parent === current) {
|
|
7383
7587
|
throw err;
|
|
7384
7588
|
}
|
|
@@ -7433,13 +7637,13 @@ function contains(realDirectory, realTarget) {
|
|
|
7433
7637
|
async function createMissingDirectories(existingAncestor, missingSegments) {
|
|
7434
7638
|
let current = existingAncestor;
|
|
7435
7639
|
for (const segment of missingSegments) {
|
|
7436
|
-
current =
|
|
7640
|
+
current = join10(current, segment);
|
|
7437
7641
|
await mkdir(current, { recursive: true, mode: DIRECTORY_MODE });
|
|
7438
7642
|
await chmod(current, DIRECTORY_MODE);
|
|
7439
7643
|
}
|
|
7440
7644
|
}
|
|
7441
7645
|
async function writeAtomically(realTarget, content) {
|
|
7442
|
-
const temporaryPath =
|
|
7646
|
+
const temporaryPath = join10(dirname7(realTarget), `.evident-push-${randomUUID()}.tmp`);
|
|
7443
7647
|
let handle;
|
|
7444
7648
|
try {
|
|
7445
7649
|
handle = await open3(temporaryPath, "wx", FILE_MODE);
|
|
@@ -7569,12 +7773,12 @@ var NOT_APPLIED = {
|
|
|
7569
7773
|
opencodeAuthApplied: false
|
|
7570
7774
|
};
|
|
7571
7775
|
function isClaudeCredentialPath(requestedPath, homeDir) {
|
|
7572
|
-
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ?
|
|
7573
|
-
return expanded ===
|
|
7776
|
+
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join11(homeDir, requestedPath.slice(2)) : requestedPath;
|
|
7777
|
+
return expanded === join11(homeDir, ...CLAUDE_CREDENTIALS_SEGMENTS);
|
|
7574
7778
|
}
|
|
7575
7779
|
function isOpenCodeAuthPath(requestedPath, homeDir) {
|
|
7576
|
-
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ?
|
|
7577
|
-
return expanded ===
|
|
7780
|
+
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join11(homeDir, requestedPath.slice(2)) : requestedPath;
|
|
7781
|
+
return expanded === join11(homeDir, ...OPENCODE_AUTH_SEGMENTS);
|
|
7578
7782
|
}
|
|
7579
7783
|
async function applyOne(options, file) {
|
|
7580
7784
|
const label = `${file.id.slice(0, 8)} (${file.path})`;
|
|
@@ -7711,6 +7915,211 @@ function describe(err) {
|
|
|
7711
7915
|
return err instanceof Error ? err.message : String(err);
|
|
7712
7916
|
}
|
|
7713
7917
|
|
|
7918
|
+
// src/lib/vault-file-sync.ts
|
|
7919
|
+
import { createHash } from "node:crypto";
|
|
7920
|
+
async function syncVaultFiles(options) {
|
|
7921
|
+
const files = await listVaultFiles(options);
|
|
7922
|
+
if (files === null) return { applied: 0 };
|
|
7923
|
+
const listedIds = new Set(files.map((file) => file.id));
|
|
7924
|
+
for (const fileId of options.outcomes.keys()) {
|
|
7925
|
+
if (!listedIds.has(fileId)) options.outcomes.delete(fileId);
|
|
7926
|
+
}
|
|
7927
|
+
let applied = 0;
|
|
7928
|
+
for (const file of files) {
|
|
7929
|
+
try {
|
|
7930
|
+
if (await reconcileVaultFile(options, file)) applied += 1;
|
|
7931
|
+
} catch (err) {
|
|
7932
|
+
options.log({
|
|
7933
|
+
level: "error",
|
|
7934
|
+
message: `Vault file ${file.id} reconciliation failed unexpectedly (message processing is unaffected): ${describe2(err)}`
|
|
7935
|
+
});
|
|
7936
|
+
}
|
|
7937
|
+
}
|
|
7938
|
+
return { applied };
|
|
7939
|
+
}
|
|
7940
|
+
async function listVaultFiles(options) {
|
|
7941
|
+
let response;
|
|
7942
|
+
try {
|
|
7943
|
+
response = await options.fetchImpl(`${options.apiUrl}/runners/${options.agentId}/vault-files`, {
|
|
7944
|
+
headers: { Authorization: options.getAuthHeader() }
|
|
7945
|
+
});
|
|
7946
|
+
} catch (err) {
|
|
7947
|
+
options.log({
|
|
7948
|
+
level: "warn",
|
|
7949
|
+
message: `Could not list vault files \u2014 retrying on the next drain: ${describe2(err)}`
|
|
7950
|
+
});
|
|
7951
|
+
return null;
|
|
7952
|
+
}
|
|
7953
|
+
if (!response.ok) {
|
|
7954
|
+
options.log({
|
|
7955
|
+
level: response.status === 404 ? "debug" : "warn",
|
|
7956
|
+
message: `Listing vault files returned HTTP ${response.status} \u2014 retrying on the next drain`
|
|
7957
|
+
});
|
|
7958
|
+
return null;
|
|
7959
|
+
}
|
|
7960
|
+
let body;
|
|
7961
|
+
try {
|
|
7962
|
+
body = await response.json();
|
|
7963
|
+
} catch (err) {
|
|
7964
|
+
options.log({
|
|
7965
|
+
level: "warn",
|
|
7966
|
+
message: `Vault file list was not readable JSON \u2014 retrying on the next drain: ${describe2(err)}`
|
|
7967
|
+
});
|
|
7968
|
+
return null;
|
|
7969
|
+
}
|
|
7970
|
+
if (body === null || typeof body !== "object" || !Array.isArray(body.files)) {
|
|
7971
|
+
options.log({
|
|
7972
|
+
level: "warn",
|
|
7973
|
+
message: "Vault file list was not an object with a files array \u2014 ignoring it for this drain"
|
|
7974
|
+
});
|
|
7975
|
+
return null;
|
|
7976
|
+
}
|
|
7977
|
+
const files = [];
|
|
7978
|
+
for (const entry of body.files) {
|
|
7979
|
+
const file = asVaultFile(entry);
|
|
7980
|
+
if (file === null) {
|
|
7981
|
+
options.log({
|
|
7982
|
+
level: "warn",
|
|
7983
|
+
message: "Ignoring a malformed vault file entry"
|
|
7984
|
+
});
|
|
7985
|
+
continue;
|
|
7986
|
+
}
|
|
7987
|
+
files.push(file);
|
|
7988
|
+
}
|
|
7989
|
+
return files;
|
|
7990
|
+
}
|
|
7991
|
+
function asVaultFile(entry) {
|
|
7992
|
+
if (entry === null || typeof entry !== "object") return null;
|
|
7993
|
+
const value = entry;
|
|
7994
|
+
if (typeof value.id !== "string" || value.id === "" || typeof value.destination_path !== "string" || value.destination_path === "" || typeof value.size_bytes !== "number" || !Number.isFinite(value.size_bytes) || value.size_bytes < 0 || typeof value.content_sha256 !== "string" || value.content_sha256 === "" || typeof value.created_at !== "string" || typeof value.updated_at !== "string") {
|
|
7995
|
+
return null;
|
|
7996
|
+
}
|
|
7997
|
+
return {
|
|
7998
|
+
id: value.id,
|
|
7999
|
+
destination_path: value.destination_path,
|
|
8000
|
+
size_bytes: value.size_bytes,
|
|
8001
|
+
content_sha256: value.content_sha256,
|
|
8002
|
+
created_at: value.created_at,
|
|
8003
|
+
updated_at: value.updated_at
|
|
8004
|
+
};
|
|
8005
|
+
}
|
|
8006
|
+
async function reconcileVaultFile(options, file) {
|
|
8007
|
+
const existing = options.outcomes.get(file.id);
|
|
8008
|
+
if (existing?.sha256 === file.content_sha256) {
|
|
8009
|
+
if (existing.reported || existing.reportAttempts >= MAX_ACK_ATTEMPTS) return false;
|
|
8010
|
+
await reportOutcome(options, file, existing);
|
|
8011
|
+
return false;
|
|
8012
|
+
}
|
|
8013
|
+
const content = await downloadVaultFile(options, file);
|
|
8014
|
+
if (content === null) return false;
|
|
8015
|
+
const actualSha256 = createHash("sha256").update(content).digest("hex");
|
|
8016
|
+
if (actualSha256 !== file.content_sha256) {
|
|
8017
|
+
options.log({
|
|
8018
|
+
level: "warn",
|
|
8019
|
+
message: `Vault file ${file.id} content hash mismatch: expected ${file.content_sha256}, received ${actualSha256} \u2014 retrying on the next drain`
|
|
8020
|
+
});
|
|
8021
|
+
return false;
|
|
8022
|
+
}
|
|
8023
|
+
const outcome = await applyVaultFile(options, file, content);
|
|
8024
|
+
const entry = {
|
|
8025
|
+
sha256: file.content_sha256,
|
|
8026
|
+
outcome: outcome.ok ? "applied" : "refused",
|
|
8027
|
+
reason: outcome.ok ? null : outcome.code,
|
|
8028
|
+
reported: false,
|
|
8029
|
+
reportAttempts: 0
|
|
8030
|
+
};
|
|
8031
|
+
options.outcomes.set(file.id, entry);
|
|
8032
|
+
await reportOutcome(options, file, entry);
|
|
8033
|
+
return outcome.ok;
|
|
8034
|
+
}
|
|
8035
|
+
async function downloadVaultFile(options, file) {
|
|
8036
|
+
try {
|
|
8037
|
+
const response = await options.fetchImpl(
|
|
8038
|
+
`${options.apiUrl}/runners/${options.agentId}/vault-files/${file.id}/content`,
|
|
8039
|
+
{ headers: { Authorization: options.getAuthHeader() } }
|
|
8040
|
+
);
|
|
8041
|
+
if (!response.ok) {
|
|
8042
|
+
options.log({
|
|
8043
|
+
level: "warn",
|
|
8044
|
+
message: `Downloading vault file ${file.id} returned HTTP ${response.status} \u2014 retrying on the next drain`
|
|
8045
|
+
});
|
|
8046
|
+
return null;
|
|
8047
|
+
}
|
|
8048
|
+
return Buffer.from(await response.arrayBuffer());
|
|
8049
|
+
} catch (err) {
|
|
8050
|
+
options.log({
|
|
8051
|
+
level: "warn",
|
|
8052
|
+
message: `Downloading vault file ${file.id} failed \u2014 retrying on the next drain: ${describe2(err)}`
|
|
8053
|
+
});
|
|
8054
|
+
return null;
|
|
8055
|
+
}
|
|
8056
|
+
}
|
|
8057
|
+
async function applyVaultFile(options, file, content) {
|
|
8058
|
+
try {
|
|
8059
|
+
const outcome = await writePushedFile({
|
|
8060
|
+
requestedPath: file.destination_path,
|
|
8061
|
+
content,
|
|
8062
|
+
allowedDirectories: options.allowedDirectories,
|
|
8063
|
+
homeDir: options.homeDir
|
|
8064
|
+
});
|
|
8065
|
+
if (!outcome.ok) {
|
|
8066
|
+
options.log({
|
|
8067
|
+
level: "warn",
|
|
8068
|
+
message: `Vault file ${file.id} refused (${outcome.code}): ${outcome.message}`
|
|
8069
|
+
});
|
|
8070
|
+
} else {
|
|
8071
|
+
options.log({
|
|
8072
|
+
level: "info",
|
|
8073
|
+
message: `Vault file ${file.id} applied (${content.byteLength} bytes)`
|
|
8074
|
+
});
|
|
8075
|
+
}
|
|
8076
|
+
return outcome;
|
|
8077
|
+
} catch (err) {
|
|
8078
|
+
options.log({
|
|
8079
|
+
level: "error",
|
|
8080
|
+
message: `Vault file ${file.id} could not be written: ${describe2(err)}`
|
|
8081
|
+
});
|
|
8082
|
+
return { ok: false, code: "write_failed", message: "The runner could not write the file." };
|
|
8083
|
+
}
|
|
8084
|
+
}
|
|
8085
|
+
async function reportOutcome(options, file, entry) {
|
|
8086
|
+
const body = {
|
|
8087
|
+
status: entry.outcome === "applied" ? "applied" : "rejected",
|
|
8088
|
+
reported_sha256: entry.sha256
|
|
8089
|
+
};
|
|
8090
|
+
if (entry.outcome === "refused" && entry.reason !== null) body.reason = entry.reason;
|
|
8091
|
+
try {
|
|
8092
|
+
const response = await options.fetchImpl(
|
|
8093
|
+
`${options.apiUrl}/runners/${options.agentId}/vault-files/${file.id}/status`,
|
|
8094
|
+
{
|
|
8095
|
+
method: "POST",
|
|
8096
|
+
headers: {
|
|
8097
|
+
Authorization: options.getAuthHeader(),
|
|
8098
|
+
"Content-Type": "application/json"
|
|
8099
|
+
},
|
|
8100
|
+
body: JSON.stringify(body)
|
|
8101
|
+
}
|
|
8102
|
+
);
|
|
8103
|
+
if (response.ok) {
|
|
8104
|
+
entry.reported = true;
|
|
8105
|
+
return;
|
|
8106
|
+
}
|
|
8107
|
+
recordReportFailure(options, file, entry, `HTTP ${response.status}`);
|
|
8108
|
+
} catch (err) {
|
|
8109
|
+
recordReportFailure(options, file, entry, describe2(err));
|
|
8110
|
+
}
|
|
8111
|
+
}
|
|
8112
|
+
function recordReportFailure(options, file, entry, reason) {
|
|
8113
|
+
entry.reportAttempts += 1;
|
|
8114
|
+
options.log({
|
|
8115
|
+
level: "error",
|
|
8116
|
+
message: entry.reportAttempts >= MAX_ACK_ATTEMPTS ? `Reporting vault file ${file.id} as ${entry.outcome} failed (${reason}) \u2014 giving up after ${entry.reportAttempts} attempts` : `Reporting vault file ${file.id} as ${entry.outcome} failed (${reason}) \u2014 retrying on the next drain (attempt ${entry.reportAttempts} of ${MAX_ACK_ATTEMPTS})`
|
|
8117
|
+
});
|
|
8118
|
+
}
|
|
8119
|
+
function describe2(err) {
|
|
8120
|
+
return err instanceof Error ? err.message : String(err);
|
|
8121
|
+
}
|
|
8122
|
+
|
|
7714
8123
|
// src/lib/channels/driver.ts
|
|
7715
8124
|
function messageIdOf(m) {
|
|
7716
8125
|
if (!m || typeof m !== "object") return void 0;
|
|
@@ -7719,7 +8128,7 @@ function messageIdOf(m) {
|
|
|
7719
8128
|
return typeof infoId === "string" ? infoId : void 0;
|
|
7720
8129
|
}
|
|
7721
8130
|
function composeAbandonmentNote(finish, replyShape, outcome) {
|
|
7722
|
-
const base = `OpenCode ended this turn after a tool call (finish="${finish ?? "(unknown)"}") ` + (outcome === "without-answer" ? "without writing an answer, and confirmed it is no longer working on this conversation. Evident is reporting the run as failed so the failure and its reason are visible in failed runs." : "after writing only part of an answer, and confirmed it is no longer working on this conversation. Evident delivered what was written and is reporting the run as finished.");
|
|
8131
|
+
const base = outcome === "midflight-without-answer" || outcome === "midflight-with-answer" ? "OpenCode stopped this turn without ever finishing, and confirmed the session is no longer running. " + (outcome === "midflight-without-answer" ? "Evident is reporting the run as failed so the failure and its reason are visible in failed runs." : "Evident delivered what was written and is reporting the run as finished.") : `OpenCode ended this turn after a tool call (finish="${finish ?? "(unknown)"}") ` + (outcome === "without-answer" ? "without writing an answer, and confirmed it is no longer working on this conversation. Evident is reporting the run as failed so the failure and its reason are visible in failed runs." : "after writing only part of an answer, and confirmed it is no longer working on this conversation. Evident delivered what was written and is reporting the run as finished.");
|
|
7723
8132
|
if (replyShape === null) return base;
|
|
7724
8133
|
return `${base} ${replyShape.lastToolName === null ? "No tool call was recorded" : `The last tool call was "${replyShape.lastToolName}" with status "${replyShape.lastToolStatus ?? "(unknown)"}"`}; the reply's final part types were [${replyShape.partTypes.join(", ")}]${replyShape.droppedPartCount > 0 ? ` (${replyShape.droppedPartCount} earlier part types omitted)` : ""}.`;
|
|
7725
8134
|
}
|
|
@@ -7756,6 +8165,9 @@ var ALIVE_WITHHELD_THRESHOLD_MS = 3 * HEARTBEAT_MS;
|
|
|
7756
8165
|
var ABSOLUTE_MAX_PROCESSING_MS = 6 * 60 * 60 * 1e3;
|
|
7757
8166
|
var B2_ABANDONMENT_MIN_PINNED_MS = 3 * 6e4;
|
|
7758
8167
|
var AMBIGUOUS_FINISH_MAX_PINNED_MS = 3 * 6e4;
|
|
8168
|
+
var MIDFLIGHT_ORPHAN_MIN_PINNED_MS = 3 * 6e4;
|
|
8169
|
+
var MIDFLIGHT_ORPHAN_RECHECK_MS = 6e4;
|
|
8170
|
+
var MIDFLIGHT_ORPHAN_MIN_NOT_ONGOING_STREAK = 2;
|
|
7759
8171
|
var B2_ABANDONMENT_RECHECK_MS = HEARTBEAT_MS;
|
|
7760
8172
|
var POLL_MISS_GRACE_MS = HEARTBEAT_MS;
|
|
7761
8173
|
var WATCHER_STALL_MS = 3 * POLL_MISS_GRACE_MS;
|
|
@@ -8119,6 +8531,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
8119
8531
|
* re-downloaded and re-written every ~2s until the server expires it.
|
|
8120
8532
|
*/
|
|
8121
8533
|
fileAckFailures = /* @__PURE__ */ new Map();
|
|
8534
|
+
vaultFileOutcomes = /* @__PURE__ */ new Map();
|
|
8122
8535
|
/**
|
|
8123
8536
|
* Monotonic count of files this runner has pulled and written (#559). Only
|
|
8124
8537
|
* ever increases, so `run.ts` detects work by comparing it against the value
|
|
@@ -8276,10 +8689,21 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
8276
8689
|
ackFailures: this.fileAckFailures,
|
|
8277
8690
|
log: this.log
|
|
8278
8691
|
});
|
|
8279
|
-
|
|
8692
|
+
const vaultResult = await syncVaultFiles({
|
|
8693
|
+
agentId: this.agentId,
|
|
8694
|
+
apiUrl: this.apiUrl,
|
|
8695
|
+
getAuthHeader: this.getAuthHeader,
|
|
8696
|
+
fetchImpl: this.fetchImpl,
|
|
8697
|
+
allowedDirectories: this.fileSyncDirectories,
|
|
8698
|
+
homeDir: this.homeDir,
|
|
8699
|
+
outcomes: this.vaultFileOutcomes,
|
|
8700
|
+
log: this.log
|
|
8701
|
+
});
|
|
8702
|
+
const applied = result.applied + vaultResult.applied;
|
|
8703
|
+
this.appliedFileCount += applied;
|
|
8280
8704
|
if (result.claudeCredentialApplied) this.claudeCredentialApplyCount += 1;
|
|
8281
8705
|
if (result.opencodeAuthApplied) this.opencodeAuthApplyCount += 1;
|
|
8282
|
-
return
|
|
8706
|
+
return applied;
|
|
8283
8707
|
} catch (err) {
|
|
8284
8708
|
this.log({
|
|
8285
8709
|
level: "error",
|
|
@@ -8691,6 +9115,47 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
8691
9115
|
agent: message.opencode_agent ?? void 0,
|
|
8692
9116
|
model: message.opencode_model ?? void 0
|
|
8693
9117
|
};
|
|
9118
|
+
const pinnedProviderLoaded = await isPinnedProviderLoaded(
|
|
9119
|
+
this.port,
|
|
9120
|
+
options.model,
|
|
9121
|
+
this.openCodeClient
|
|
9122
|
+
);
|
|
9123
|
+
if (pinnedProviderLoaded === false) {
|
|
9124
|
+
const dispatchedModel = message.opencode_model;
|
|
9125
|
+
const separator = dispatchedModel?.indexOf("/") ?? -1;
|
|
9126
|
+
const hasProviderAndModel = dispatchedModel !== null && separator > 0 && separator < dispatchedModel.length - 1;
|
|
9127
|
+
const providerId = hasProviderAndModel ? dispatchedModel.slice(0, separator) : null;
|
|
9128
|
+
const modelId = hasProviderAndModel ? dispatchedModel.slice(separator + 1) : null;
|
|
9129
|
+
const errorMessage3 = `Provider namespace not loaded on this runner: ${message.opencode_model}. Refused before dispatch; no turn was started.`;
|
|
9130
|
+
this.log({
|
|
9131
|
+
level: "error",
|
|
9132
|
+
message: `Refusing message ${message.id.slice(0, 8)} in conversation ${conv.id.slice(0, 8)}: provider namespace ${providerId ?? "(unknown)"} is not loaded; no turn was started`,
|
|
9133
|
+
conversation_id: conv.id,
|
|
9134
|
+
message_id: message.id
|
|
9135
|
+
});
|
|
9136
|
+
try {
|
|
9137
|
+
await this.markFailed(conv.id, message.id, sessionId, errorMessage3, void 0, {
|
|
9138
|
+
classification: {
|
|
9139
|
+
kind: "model_auth",
|
|
9140
|
+
providerId,
|
|
9141
|
+
modelId,
|
|
9142
|
+
reason: "missing"
|
|
9143
|
+
},
|
|
9144
|
+
errorShape: { name: null, statusCode: null }
|
|
9145
|
+
});
|
|
9146
|
+
this.signalDispatchNotStarted(conv, message, "model_provider_absent");
|
|
9147
|
+
} catch (markErr) {
|
|
9148
|
+
this.log({
|
|
9149
|
+
level: "warn",
|
|
9150
|
+
message: `markFailed PATCH for refused message ${message.id.slice(0, 8)} (conversation ${conv.id.slice(0, 8)}) failed: ${markErr instanceof Error ? markErr.message : String(markErr)}`,
|
|
9151
|
+
conversation_id: conv.id,
|
|
9152
|
+
message_id: message.id
|
|
9153
|
+
});
|
|
9154
|
+
this.signalDispatchNotStarted(conv, message, "failure_unreported");
|
|
9155
|
+
}
|
|
9156
|
+
await this.repairProviderRegistry(conv.id, message.id);
|
|
9157
|
+
break;
|
|
9158
|
+
}
|
|
8694
9159
|
let opencodeMessageId;
|
|
8695
9160
|
let preDispatchUserIds = null;
|
|
8696
9161
|
try {
|
|
@@ -9815,6 +10280,12 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
9815
10280
|
b2AbandonedWithoutAnswer: false,
|
|
9816
10281
|
ambiguousPinnedSinceMs: 0,
|
|
9817
10282
|
ambiguousResolved: false,
|
|
10283
|
+
midFlightPinnedSinceMs: 0,
|
|
10284
|
+
midFlightNotOngoingStreak: 0,
|
|
10285
|
+
midFlightLastStatusCheckMs: 0,
|
|
10286
|
+
midFlightOrphanSignalled: false,
|
|
10287
|
+
midFlightOrphanWithoutAnswer: false,
|
|
10288
|
+
midFlightHeldLogged: false,
|
|
9818
10289
|
sessionStoreFailurePending: false,
|
|
9819
10290
|
neverStartedResolved: false
|
|
9820
10291
|
});
|
|
@@ -9954,6 +10425,12 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
9954
10425
|
b2AbandonedWithoutAnswer: false,
|
|
9955
10426
|
ambiguousPinnedSinceMs: 0,
|
|
9956
10427
|
ambiguousResolved: false,
|
|
10428
|
+
midFlightPinnedSinceMs: 0,
|
|
10429
|
+
midFlightNotOngoingStreak: 0,
|
|
10430
|
+
midFlightLastStatusCheckMs: 0,
|
|
10431
|
+
midFlightOrphanSignalled: false,
|
|
10432
|
+
midFlightOrphanWithoutAnswer: false,
|
|
10433
|
+
midFlightHeldLogged: false,
|
|
9957
10434
|
sessionStoreFailurePending: false,
|
|
9958
10435
|
neverStartedResolved: false
|
|
9959
10436
|
});
|
|
@@ -10725,7 +11202,11 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
10725
11202
|
watcher,
|
|
10726
11203
|
inFlight,
|
|
10727
11204
|
messages,
|
|
10728
|
-
|
|
11205
|
+
composeAbandonmentNote(
|
|
11206
|
+
finish,
|
|
11207
|
+
describeAbandonedReplyShape(messages, inFlight.opencodeMessageId),
|
|
11208
|
+
"without-answer"
|
|
11209
|
+
)
|
|
10729
11210
|
);
|
|
10730
11211
|
} else {
|
|
10731
11212
|
await this.settleMessageDone(
|
|
@@ -10784,7 +11265,11 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
10784
11265
|
watcher,
|
|
10785
11266
|
inFlight,
|
|
10786
11267
|
messages,
|
|
10787
|
-
|
|
11268
|
+
composeAbandonmentNote(
|
|
11269
|
+
finish,
|
|
11270
|
+
describeAbandonedReplyShape(messages, inFlight.opencodeMessageId),
|
|
11271
|
+
"without-answer"
|
|
11272
|
+
)
|
|
10788
11273
|
);
|
|
10789
11274
|
return;
|
|
10790
11275
|
}
|
|
@@ -10878,6 +11363,106 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
10878
11363
|
return;
|
|
10879
11364
|
}
|
|
10880
11365
|
}
|
|
11366
|
+
const midFlightPinnedNow = activelyRunning && isMidFlightPinnedRunning(messages, inFlight.opencodeMessageId);
|
|
11367
|
+
if (!midFlightPinnedNow) {
|
|
11368
|
+
if (snapshotReadable) {
|
|
11369
|
+
inFlight.midFlightPinnedSinceMs = 0;
|
|
11370
|
+
inFlight.midFlightNotOngoingStreak = 0;
|
|
11371
|
+
inFlight.midFlightLastStatusCheckMs = 0;
|
|
11372
|
+
inFlight.midFlightOrphanSignalled = false;
|
|
11373
|
+
inFlight.midFlightOrphanWithoutAnswer = false;
|
|
11374
|
+
inFlight.midFlightHeldLogged = false;
|
|
11375
|
+
}
|
|
11376
|
+
} else {
|
|
11377
|
+
if (inFlight.midFlightOrphanSignalled) {
|
|
11378
|
+
const note = composeAbandonmentNote(
|
|
11379
|
+
void 0,
|
|
11380
|
+
describeAbandonedReplyShape(messages, inFlight.opencodeMessageId),
|
|
11381
|
+
inFlight.midFlightOrphanWithoutAnswer ? "midflight-without-answer" : "midflight-with-answer"
|
|
11382
|
+
);
|
|
11383
|
+
if (inFlight.midFlightOrphanWithoutAnswer) {
|
|
11384
|
+
await this.settleMessageAbandonedWithoutAnswer(
|
|
11385
|
+
sessionId,
|
|
11386
|
+
watcher,
|
|
11387
|
+
inFlight,
|
|
11388
|
+
messages,
|
|
11389
|
+
note
|
|
11390
|
+
);
|
|
11391
|
+
} else {
|
|
11392
|
+
await this.settleMessageDone(sessionId, watcher, inFlight, messages, note);
|
|
11393
|
+
}
|
|
11394
|
+
return;
|
|
11395
|
+
}
|
|
11396
|
+
if (inFlight.midFlightPinnedSinceMs === 0) {
|
|
11397
|
+
inFlight.midFlightPinnedSinceMs = this.now();
|
|
11398
|
+
}
|
|
11399
|
+
const pinnedForMs = this.now() - inFlight.midFlightPinnedSinceMs;
|
|
11400
|
+
if (pinnedForMs >= MIDFLIGHT_ORPHAN_MIN_PINNED_MS && this.now() - inFlight.midFlightLastStatusCheckMs >= MIDFLIGHT_ORPHAN_RECHECK_MS) {
|
|
11401
|
+
inFlight.midFlightLastStatusCheckMs = this.now();
|
|
11402
|
+
const rootOngoing = await this.isSessionOngoing(sessionId);
|
|
11403
|
+
inFlight.midFlightNotOngoingStreak = rootOngoing === false ? inFlight.midFlightNotOngoingStreak + 1 : 0;
|
|
11404
|
+
if (isMidFlightOrphanConfirmed({
|
|
11405
|
+
pinnedForMs,
|
|
11406
|
+
minPinnedMs: MIDFLIGHT_ORPHAN_MIN_PINNED_MS,
|
|
11407
|
+
notOngoingStreak: inFlight.midFlightNotOngoingStreak,
|
|
11408
|
+
minNotOngoingStreak: MIDFLIGHT_ORPHAN_MIN_NOT_ONGOING_STREAK
|
|
11409
|
+
})) {
|
|
11410
|
+
if (this.sessionStoreHealth?.isUnwritable()) {
|
|
11411
|
+
inFlight.sessionStoreFailurePending = true;
|
|
11412
|
+
void this.postSignal(conv.id, id, "midflight_orphan_resolved", {
|
|
11413
|
+
watched_for_ms: pinnedForMs,
|
|
11414
|
+
...rootOngoing != null ? { root_ongoing: rootOngoing } : {},
|
|
11415
|
+
opencode_message_id: inFlight.opencodeMessageId,
|
|
11416
|
+
run_state: "failed"
|
|
11417
|
+
});
|
|
11418
|
+
await this.settleMessageSessionStoreUnwritable(sessionId, watcher, inFlight);
|
|
11419
|
+
return;
|
|
11420
|
+
}
|
|
11421
|
+
inFlight.midFlightOrphanSignalled = true;
|
|
11422
|
+
const noteOutcome = extractReplyTextForMessage(messages, inFlight.opencodeMessageId) === null ? "midflight-without-answer" : "midflight-with-answer";
|
|
11423
|
+
inFlight.midFlightOrphanWithoutAnswer = noteOutcome === "midflight-without-answer";
|
|
11424
|
+
void this.postSignal(conv.id, id, "midflight_orphan_resolved", {
|
|
11425
|
+
watched_for_ms: pinnedForMs,
|
|
11426
|
+
...rootOngoing != null ? { root_ongoing: rootOngoing } : {},
|
|
11427
|
+
opencode_message_id: inFlight.opencodeMessageId,
|
|
11428
|
+
...inFlight.midFlightOrphanWithoutAnswer ? { run_state: "failed" } : {}
|
|
11429
|
+
});
|
|
11430
|
+
const note = composeAbandonmentNote(
|
|
11431
|
+
void 0,
|
|
11432
|
+
describeAbandonedReplyShape(messages, inFlight.opencodeMessageId),
|
|
11433
|
+
noteOutcome
|
|
11434
|
+
);
|
|
11435
|
+
this.log({
|
|
11436
|
+
level: "warn",
|
|
11437
|
+
message: `Message ${id.slice(0, 8)} was pinned mid-flight for ${Math.round(pinnedForMs / 1e3)}s with root_ongoing=${rootOngoing} \u2014 resolving the orphaned turn`,
|
|
11438
|
+
conversation_id: conv.id,
|
|
11439
|
+
message_id: id
|
|
11440
|
+
});
|
|
11441
|
+
if (inFlight.midFlightOrphanWithoutAnswer) {
|
|
11442
|
+
await this.settleMessageAbandonedWithoutAnswer(
|
|
11443
|
+
sessionId,
|
|
11444
|
+
watcher,
|
|
11445
|
+
inFlight,
|
|
11446
|
+
messages,
|
|
11447
|
+
note
|
|
11448
|
+
);
|
|
11449
|
+
} else {
|
|
11450
|
+
await this.settleMessageDone(sessionId, watcher, inFlight, messages, note);
|
|
11451
|
+
}
|
|
11452
|
+
return;
|
|
11453
|
+
}
|
|
11454
|
+
if (rootOngoing === true && !inFlight.midFlightHeldLogged) {
|
|
11455
|
+
inFlight.midFlightHeldLogged = true;
|
|
11456
|
+
const replyShape = describeAbandonedReplyShape(messages, inFlight.opencodeMessageId);
|
|
11457
|
+
this.log({
|
|
11458
|
+
level: "warn",
|
|
11459
|
+
message: `Message ${id.slice(0, 8)} was pinned mid-flight for ${Math.round(pinnedForMs / 1e3)}s with root_ongoing=true \u2014 holding; last tool ${replyShape?.lastToolName ?? "(unknown)"} status ${replyShape?.lastToolStatus ?? "(unknown)"}`,
|
|
11460
|
+
conversation_id: conv.id,
|
|
11461
|
+
message_id: id
|
|
11462
|
+
});
|
|
11463
|
+
}
|
|
11464
|
+
}
|
|
11465
|
+
}
|
|
10881
11466
|
if (watcher.inFlight.has(inFlight.evidentMessageId) && activelyRunning && this.now() - inFlight.processingAnchorMs >= ABSOLUTE_MAX_PROCESSING_MS) {
|
|
10882
11467
|
await this.settleAbsoluteCeiling(sessionId, watcher, messages, ceilingAttemptsThisTick);
|
|
10883
11468
|
return;
|
|
@@ -11077,13 +11662,8 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
11077
11662
|
this.removeInFlight(watcher, inFlight.evidentMessageId);
|
|
11078
11663
|
}
|
|
11079
11664
|
/** Report a confirmed b2 abandonment that produced no answer text. */
|
|
11080
|
-
async settleMessageAbandonedWithoutAnswer(sessionId, watcher, inFlight, messages,
|
|
11665
|
+
async settleMessageAbandonedWithoutAnswer(sessionId, watcher, inFlight, messages, error2) {
|
|
11081
11666
|
const conv = watcher.conv;
|
|
11082
|
-
const error2 = composeAbandonmentNote(
|
|
11083
|
-
finish,
|
|
11084
|
-
describeAbandonedReplyShape(messages, inFlight.opencodeMessageId),
|
|
11085
|
-
"without-answer"
|
|
11086
|
-
);
|
|
11087
11667
|
this.anchorDeliveryDeadline(inFlight);
|
|
11088
11668
|
this.log({
|
|
11089
11669
|
level: "warn",
|
|
@@ -13595,7 +14175,7 @@ Port ${port} is already in use.`));
|
|
|
13595
14175
|
}
|
|
13596
14176
|
|
|
13597
14177
|
// src/lib/runner-credentials.ts
|
|
13598
|
-
import { chmodSync as chmodSync2, writeFileSync as
|
|
14178
|
+
import { chmodSync as chmodSync2, writeFileSync as writeFileSync5 } from "node:fs";
|
|
13599
14179
|
import { spawn as spawn5 } from "node:child_process";
|
|
13600
14180
|
var RUNNER_SECRET_FETCH_TIMEOUT_MS = 6e4;
|
|
13601
14181
|
var CREDENTIAL_RESTORE_TIMEOUT_MS = 6e4;
|
|
@@ -13838,8 +14418,8 @@ async function configureGitHubAccess({ env, log: log3 }) {
|
|
|
13838
14418
|
}
|
|
13839
14419
|
try {
|
|
13840
14420
|
env.GIT_CONFIG_GLOBAL = GIT_CONFIG_GLOBAL;
|
|
13841
|
-
|
|
13842
|
-
|
|
14421
|
+
writeFileSync5(GIT_CONFIG_GLOBAL, "");
|
|
14422
|
+
writeFileSync5(GIT_CREDENTIAL_HELPER, GIT_CREDENTIAL_HELPER_CONTENT, { mode: 448 });
|
|
13843
14423
|
chmodSync2(GIT_CREDENTIAL_HELPER, 448);
|
|
13844
14424
|
const config = [
|
|
13845
14425
|
["user.name", env.GIT_USER_NAME ?? "evident-bot"],
|
|
@@ -13872,13 +14452,7 @@ async function configureGitHubAccess({ env, log: log3 }) {
|
|
|
13872
14452
|
// src/lib/opencode/config-overlay.ts
|
|
13873
14453
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
13874
14454
|
import { copyFileSync, existsSync as existsSync2, statSync as statSync6 } from "node:fs";
|
|
13875
|
-
import { isAbsolute as isAbsolute3, join as
|
|
13876
|
-
var MANAGED_PROVIDER_NAMESPACES = [
|
|
13877
|
-
"evident-claude",
|
|
13878
|
-
"evident-codex",
|
|
13879
|
-
"evident-openai",
|
|
13880
|
-
"evident-kimi"
|
|
13881
|
-
];
|
|
14455
|
+
import { isAbsolute as isAbsolute3, join as join12, resolve as resolve4 } from "node:path";
|
|
13882
14456
|
function parseConfigContent(content) {
|
|
13883
14457
|
if (!content?.trim()) return {};
|
|
13884
14458
|
const parsed = JSON.parse(content);
|
|
@@ -13890,27 +14464,11 @@ function parseConfigContent(content) {
|
|
|
13890
14464
|
function mergeManagedOpenCodeConfigContent(content, managedProviders) {
|
|
13891
14465
|
const config = parseConfigContent(content);
|
|
13892
14466
|
const provider = typeof config.provider === "object" && config.provider !== null && !Array.isArray(config.provider) ? { ...config.provider } : {};
|
|
13893
|
-
for (const namespace of
|
|
14467
|
+
for (const namespace of CREDENTIAL_PROXY_NATIVE_NAMESPACES) {
|
|
13894
14468
|
delete provider[namespace];
|
|
13895
14469
|
}
|
|
13896
14470
|
return JSON.stringify({ ...config, provider: { ...provider, ...managedProviders } });
|
|
13897
14471
|
}
|
|
13898
|
-
function removeManagedOpenCodeConfigContent(content) {
|
|
13899
|
-
if (!content?.trim()) return content;
|
|
13900
|
-
const config = parseConfigContent(content);
|
|
13901
|
-
if (typeof config.provider !== "object" || config.provider === null || Array.isArray(config.provider)) {
|
|
13902
|
-
return content;
|
|
13903
|
-
}
|
|
13904
|
-
const provider = { ...config.provider };
|
|
13905
|
-
let changed = false;
|
|
13906
|
-
for (const namespace of MANAGED_PROVIDER_NAMESPACES) {
|
|
13907
|
-
if (namespace in provider) {
|
|
13908
|
-
delete provider[namespace];
|
|
13909
|
-
changed = true;
|
|
13910
|
-
}
|
|
13911
|
-
}
|
|
13912
|
-
return changed ? JSON.stringify({ ...config, provider }) : content;
|
|
13913
|
-
}
|
|
13914
14472
|
function isFile(filePath) {
|
|
13915
14473
|
return existsSync2(filePath) && statSync6(filePath).isFile();
|
|
13916
14474
|
}
|
|
@@ -13924,7 +14482,7 @@ function applyRunnerOpenCodeConfig({
|
|
|
13924
14482
|
return;
|
|
13925
14483
|
}
|
|
13926
14484
|
const source = isAbsolute3(overlayPath) ? overlayPath : resolve4(cwd, overlayPath);
|
|
13927
|
-
const target = isFile(
|
|
14485
|
+
const target = isFile(join12(cwd, "opencode.jsonc")) ? "opencode.jsonc" : "opencode.json";
|
|
13928
14486
|
if (!isFile(source)) {
|
|
13929
14487
|
log3(
|
|
13930
14488
|
`RUNNER-OPENCODE-CONFIG-MISSING: ${source} is not a file; headless turns will wedge on the first external-directory permission prompt (#563)`,
|
|
@@ -13932,7 +14490,7 @@ function applyRunnerOpenCodeConfig({
|
|
|
13932
14490
|
);
|
|
13933
14491
|
return;
|
|
13934
14492
|
}
|
|
13935
|
-
copyFileSync(source,
|
|
14493
|
+
copyFileSync(source, join12(cwd, target));
|
|
13936
14494
|
try {
|
|
13937
14495
|
execFileSync2("git", ["-C", cwd, "update-index", "--skip-worktree", target], {
|
|
13938
14496
|
stdio: "ignore"
|
|
@@ -13941,13 +14499,13 @@ function applyRunnerOpenCodeConfig({
|
|
|
13941
14499
|
const detail = error2 instanceof Error ? error2.message : String(error2);
|
|
13942
14500
|
log3(`could not mark ${target} skip-worktree: ${detail}; it may show as a local change`, "warn");
|
|
13943
14501
|
}
|
|
13944
|
-
log3(`Applied runner OpenCode config ${source} to ${
|
|
14502
|
+
log3(`Applied runner OpenCode config ${source} to ${join12(cwd, target)}`);
|
|
13945
14503
|
}
|
|
13946
14504
|
|
|
13947
14505
|
// src/lib/opencode/runtime-provenance.ts
|
|
13948
|
-
import { readFileSync as
|
|
14506
|
+
import { readFileSync as readFileSync8, readdirSync } from "node:fs";
|
|
13949
14507
|
import { homedir as homedir7 } from "node:os";
|
|
13950
|
-
import { join as
|
|
14508
|
+
import { join as join13 } from "node:path";
|
|
13951
14509
|
var PACKAGE_NAME = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/i;
|
|
13952
14510
|
var REGISTRY_SPEC = /^[a-z0-9.*+^~<>=|-]+$/i;
|
|
13953
14511
|
function errorKind(error2) {
|
|
@@ -14015,7 +14573,7 @@ function parseJsonc(value) {
|
|
|
14015
14573
|
return JSON.parse(output);
|
|
14016
14574
|
}
|
|
14017
14575
|
function openCodeCacheRoot(env = process.env, homeDir = homedir7()) {
|
|
14018
|
-
return
|
|
14576
|
+
return join13(env.XDG_CACHE_HOME ?? join13(homeDir, ".cache"), "opencode");
|
|
14019
14577
|
}
|
|
14020
14578
|
function readConfiguredPluginSpecs({
|
|
14021
14579
|
cwd = process.cwd(),
|
|
@@ -14024,10 +14582,10 @@ function readConfiguredPluginSpecs({
|
|
|
14024
14582
|
let config;
|
|
14025
14583
|
try {
|
|
14026
14584
|
try {
|
|
14027
|
-
config = parseJsonc(
|
|
14585
|
+
config = parseJsonc(readFileSync8(join13(cwd, "opencode.jsonc"), "utf8"));
|
|
14028
14586
|
} catch (error2) {
|
|
14029
14587
|
if (!("code" in Object(error2)) || Object(error2).code !== "ENOENT") throw error2;
|
|
14030
|
-
config = parseJsonc(
|
|
14588
|
+
config = parseJsonc(readFileSync8(join13(cwd, "opencode.json"), "utf8"));
|
|
14031
14589
|
}
|
|
14032
14590
|
} catch (error2) {
|
|
14033
14591
|
log3(`Could not read OpenCode plugin configuration: ${errorKind(error2)}`);
|
|
@@ -14055,7 +14613,7 @@ function resolveInstalledPluginVersions({
|
|
|
14055
14613
|
homeDir = homedir7(),
|
|
14056
14614
|
log: log3
|
|
14057
14615
|
}) {
|
|
14058
|
-
const packagesRoot =
|
|
14616
|
+
const packagesRoot = join13(cacheRoot ?? openCodeCacheRoot(env, homeDir), "packages");
|
|
14059
14617
|
let directories;
|
|
14060
14618
|
try {
|
|
14061
14619
|
directories = readdirSync(packagesRoot, { withFileTypes: true });
|
|
@@ -14076,8 +14634,8 @@ function resolveInstalledPluginVersions({
|
|
|
14076
14634
|
let resolvedVersion = null;
|
|
14077
14635
|
try {
|
|
14078
14636
|
const packageJson = JSON.parse(
|
|
14079
|
-
|
|
14080
|
-
|
|
14637
|
+
readFileSync8(
|
|
14638
|
+
join13(packagesRoot, directory.name, "node_modules", parsed.name, "package.json"),
|
|
14081
14639
|
"utf8"
|
|
14082
14640
|
)
|
|
14083
14641
|
);
|
|
@@ -14098,7 +14656,7 @@ function resolveInstalledPluginVersions({
|
|
|
14098
14656
|
}
|
|
14099
14657
|
|
|
14100
14658
|
// src/lib/credential-sync.ts
|
|
14101
|
-
import { renameSync, writeFileSync as
|
|
14659
|
+
import { renameSync, writeFileSync as writeFileSync6 } from "node:fs";
|
|
14102
14660
|
var CREDENTIAL_SYNC_TICK_TIMEOUT_MS = 3e4;
|
|
14103
14661
|
var CREDENTIAL_FLUSH_DEADLINE_MS = 8e3;
|
|
14104
14662
|
var CREDENTIAL_FLUSH_ABORT_GRACE_MS = 500;
|
|
@@ -14132,7 +14690,7 @@ function writeMarker(markerPath, outcomes, log3) {
|
|
|
14132
14690
|
`;
|
|
14133
14691
|
const temporaryPath = `${markerPath}.tmp`;
|
|
14134
14692
|
try {
|
|
14135
|
-
|
|
14693
|
+
writeFileSync6(temporaryPath, body, { mode: 384 });
|
|
14136
14694
|
renameSync(temporaryPath, markerPath);
|
|
14137
14695
|
} catch (error2) {
|
|
14138
14696
|
log3(`CREDENTIAL-FLUSH-MARKER-UNWRITABLE: ${markerPath}: ${errorMessage2(error2)}`, "warn");
|
|
@@ -14372,23 +14930,23 @@ async function responseJson(response) {
|
|
|
14372
14930
|
function readSetup(value) {
|
|
14373
14931
|
if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
|
|
14374
14932
|
const setup = value;
|
|
14375
|
-
if (typeof setup.runner_id !== "string" || typeof setup.generation !== "string" || typeof setup.status !== "string" || setup.token_id !== null && typeof setup.token_id !== "string") {
|
|
14933
|
+
if (typeof setup.runner_id !== "string" || typeof setup.generation !== "string" || typeof setup.status !== "string" || setup.token_id !== null && typeof setup.token_id !== "string" || typeof setup.acknowledged_at !== "string" && setup.acknowledged_at !== null) {
|
|
14376
14934
|
return null;
|
|
14377
14935
|
}
|
|
14378
14936
|
return setup;
|
|
14379
14937
|
}
|
|
14380
14938
|
function clearLocalSetup(options) {
|
|
14381
14939
|
const env = options.env ?? process.env;
|
|
14382
|
-
|
|
14383
|
-
|
|
14384
|
-
|
|
14385
|
-
|
|
14386
|
-
|
|
14387
|
-
|
|
14388
|
-
}
|
|
14940
|
+
const store = options.store ?? defaultStore;
|
|
14941
|
+
const stored = store.get(options.agentId);
|
|
14942
|
+
store.clear(options.agentId);
|
|
14943
|
+
const token = env.EVIDENT_PROXY_TOKEN;
|
|
14944
|
+
if (token !== void 0 && token === stored?.token) {
|
|
14945
|
+
delete env.EVIDENT_PROXY_TOKEN;
|
|
14946
|
+
} else if (token !== void 0) {
|
|
14389
14947
|
options.log(
|
|
14390
|
-
|
|
14391
|
-
"
|
|
14948
|
+
`MANAGED-INFERENCE-CLEANUP-PRESERVED-EXTERNAL-TOKEN: runner=${options.agentId}`,
|
|
14949
|
+
"debug"
|
|
14392
14950
|
);
|
|
14393
14951
|
}
|
|
14394
14952
|
}
|
|
@@ -14414,6 +14972,25 @@ async function discoverManagedInferenceSetup(options) {
|
|
|
14414
14972
|
const setup = readSetup(body?.setup);
|
|
14415
14973
|
return setup ? { kind: "setup", setup } : { kind: "failed", reason: "Managed setup discovery returned an invalid response" };
|
|
14416
14974
|
}
|
|
14975
|
+
async function enrollManagedInferenceSetup(options) {
|
|
14976
|
+
const response = await post(options, "enroll", {});
|
|
14977
|
+
if (!response) {
|
|
14978
|
+
return { kind: "failed", reason: safeFailure("Managed credential enrollment") };
|
|
14979
|
+
}
|
|
14980
|
+
if (response.status === 404) {
|
|
14981
|
+
return { kind: "none" };
|
|
14982
|
+
}
|
|
14983
|
+
const body = await responseJson(response);
|
|
14984
|
+
if (!response.ok) {
|
|
14985
|
+
const serverError = body?.error;
|
|
14986
|
+
return {
|
|
14987
|
+
kind: "failed",
|
|
14988
|
+
reason: typeof serverError === "string" && serverError.trim() ? redactDiagnosticText(serverError).slice(0, 500) : safeFailure("Managed credential enrollment", response.status)
|
|
14989
|
+
};
|
|
14990
|
+
}
|
|
14991
|
+
const setup = readSetup(body?.setup);
|
|
14992
|
+
return setup ? { kind: "setup", setup } : { kind: "failed", reason: "Managed credential enrollment returned an invalid response" };
|
|
14993
|
+
}
|
|
14417
14994
|
async function post(options, operation, body) {
|
|
14418
14995
|
const headers = requestHeaders(options);
|
|
14419
14996
|
try {
|
|
@@ -14428,31 +15005,37 @@ async function post(options, operation, body) {
|
|
|
14428
15005
|
}
|
|
14429
15006
|
}
|
|
14430
15007
|
function applyCredential(options, credential) {
|
|
14431
|
-
const { config, unsupportedProviders } = buildCredentialProxyNativeConfig({
|
|
14432
|
-
inferenceBaseUrl: credential.inferenceBaseUrl,
|
|
14433
|
-
modelDescriptors: credential.modelDescriptors
|
|
14434
|
-
});
|
|
14435
|
-
if (unsupportedProviders.length > 0 || Object.keys(config.provider).length === 0) {
|
|
14436
|
-
return "Managed setup contains unsupported provider descriptors";
|
|
14437
|
-
}
|
|
14438
15008
|
const env = options.env ?? process.env;
|
|
14439
15009
|
let merged;
|
|
14440
|
-
|
|
14441
|
-
|
|
14442
|
-
|
|
14443
|
-
|
|
15010
|
+
if (options.autoConfigureCredentialsProxy) {
|
|
15011
|
+
const { config, unsupportedProviders } = buildCredentialProxyNativeConfig({
|
|
15012
|
+
inferenceBaseUrl: credential.inferenceBaseUrl,
|
|
15013
|
+
modelDescriptors: credential.modelDescriptors
|
|
15014
|
+
});
|
|
15015
|
+
if (unsupportedProviders.length > 0 || Object.keys(config.provider).length === 0) {
|
|
15016
|
+
return "Managed setup contains unsupported provider descriptors";
|
|
15017
|
+
}
|
|
15018
|
+
try {
|
|
15019
|
+
merged = mergeManagedOpenCodeConfigContent(env.OPENCODE_CONFIG_CONTENT, config.provider);
|
|
15020
|
+
} catch (error2) {
|
|
15021
|
+
return "OPENCODE_CONFIG_CONTENT is malformed; managed providers were not installed";
|
|
15022
|
+
}
|
|
14444
15023
|
}
|
|
14445
15024
|
try {
|
|
14446
15025
|
(options.store ?? defaultStore).set(options.agentId, credential);
|
|
14447
15026
|
} catch (error2) {
|
|
15027
|
+
const detail = (error2 instanceof Error ? error2.message : String(error2)).replaceAll(
|
|
15028
|
+
credential.token,
|
|
15029
|
+
"[redacted]"
|
|
15030
|
+
);
|
|
14448
15031
|
options.log(
|
|
14449
|
-
`MANAGED-INFERENCE-STORE-FAILED: runner=${options.agentId} generation=${credential.generation}`,
|
|
15032
|
+
`MANAGED-INFERENCE-STORE-FAILED: runner=${options.agentId} generation=${credential.generation} detail=${redactDiagnosticText(detail)}`,
|
|
14450
15033
|
"warn"
|
|
14451
15034
|
);
|
|
14452
15035
|
return "Managed credentials could not be written to the protected store";
|
|
14453
15036
|
}
|
|
14454
15037
|
env.EVIDENT_PROXY_TOKEN = credential.token;
|
|
14455
|
-
env.OPENCODE_CONFIG_CONTENT = merged;
|
|
15038
|
+
if (merged !== void 0) env.OPENCODE_CONFIG_CONTENT = merged;
|
|
14456
15039
|
return null;
|
|
14457
15040
|
}
|
|
14458
15041
|
async function reportManagedInferenceApplyFailure(options, failure) {
|
|
@@ -14503,7 +15086,7 @@ async function prepareManagedInferenceSetup(options, discovery, health) {
|
|
|
14503
15086
|
}
|
|
14504
15087
|
return reaffirmStored(options, stored);
|
|
14505
15088
|
}
|
|
14506
|
-
if (discovery.setup.
|
|
15089
|
+
if (discovery.setup.acknowledged_at !== null && stored?.generation === discovery.setup.generation && stored.tokenId === discovery.setup.token_id) {
|
|
14507
15090
|
return reaffirmStored(options, stored);
|
|
14508
15091
|
}
|
|
14509
15092
|
const response = await post(options, "bootstrap", { generation: discovery.setup.generation });
|
|
@@ -14541,7 +15124,7 @@ async function acknowledgeManagedInferenceSetup(options, preparation) {
|
|
|
14541
15124
|
const response = await post(options, "acknowledge", {
|
|
14542
15125
|
generation: preparation.generation,
|
|
14543
15126
|
token_id: preparation.tokenId,
|
|
14544
|
-
config_validated: true
|
|
15127
|
+
config_validated: options.autoConfigureCredentialsProxy === true
|
|
14545
15128
|
});
|
|
14546
15129
|
if (!response?.ok) {
|
|
14547
15130
|
throw new Error(
|
|
@@ -14596,7 +15179,7 @@ function resolveFileSyncDirectories(raw, homeDir) {
|
|
|
14596
15179
|
if (trimmed === "") {
|
|
14597
15180
|
throw new Error("--enable-file-sync-to requires a directory path (got an empty value)");
|
|
14598
15181
|
}
|
|
14599
|
-
const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ?
|
|
15182
|
+
const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ? join14(homeDir, trimmed.slice(2)) : trimmed;
|
|
14600
15183
|
if (!isAbsolute4(expanded)) {
|
|
14601
15184
|
throw new Error(`--enable-file-sync-to requires an absolute directory path; got "${entry}"`);
|
|
14602
15185
|
}
|
|
@@ -15252,7 +15835,7 @@ function shouldWarnForReclaimSkip(reason) {
|
|
|
15252
15835
|
return major > 22 || major === 22 && (minor > 13 || minor === 13 && patch >= 0);
|
|
15253
15836
|
}
|
|
15254
15837
|
function sessionDbPath() {
|
|
15255
|
-
return
|
|
15838
|
+
return join14(homedir8(), ".local", "share", "opencode", "opencode.db");
|
|
15256
15839
|
}
|
|
15257
15840
|
function logSessionCleanupActivity(state, phase, entry) {
|
|
15258
15841
|
logActivity(state, {
|
|
@@ -15261,6 +15844,47 @@ function logSessionCleanupActivity(state, phase, entry) {
|
|
|
15261
15844
|
metadata: { ...entry.metadata, phase }
|
|
15262
15845
|
});
|
|
15263
15846
|
}
|
|
15847
|
+
async function drainSessionDeletionReports(state) {
|
|
15848
|
+
const path = sessionDeletionReportPath(homedir8(), process.env);
|
|
15849
|
+
let delivered = 0;
|
|
15850
|
+
let failure = null;
|
|
15851
|
+
try {
|
|
15852
|
+
const spool = readSessionDeletionRecords(path);
|
|
15853
|
+
if (spool.readError !== null) {
|
|
15854
|
+
failure = `could not read spool: ${spool.readError}`;
|
|
15855
|
+
} else if (spool.records.length === 0) {
|
|
15856
|
+
return;
|
|
15857
|
+
} else if (!state.agentId || !state.authHeader) {
|
|
15858
|
+
failure = "runner authentication is not available";
|
|
15859
|
+
} else {
|
|
15860
|
+
for (let offset = 0; offset < spool.records.length; offset += 200) {
|
|
15861
|
+
const batch = spool.records.slice(offset, offset + 200);
|
|
15862
|
+
const result = await reportSessionDeletions(state.agentId, state.authHeader, batch);
|
|
15863
|
+
if (!result.ok) {
|
|
15864
|
+
failure = result.error ?? "unknown API error";
|
|
15865
|
+
break;
|
|
15866
|
+
}
|
|
15867
|
+
removeDeliveredSessionDeletionRecords(path, batch);
|
|
15868
|
+
delivered += batch.length;
|
|
15869
|
+
}
|
|
15870
|
+
}
|
|
15871
|
+
} catch (error2) {
|
|
15872
|
+
failure = error2 instanceof Error ? error2.message : String(error2);
|
|
15873
|
+
}
|
|
15874
|
+
const pending = readSessionDeletionRecords(path);
|
|
15875
|
+
const pendingCount = pending.readError === null ? pending.records.length : null;
|
|
15876
|
+
const message = `Session deletion reports: delivered ${delivered}, ${pendingCount === null ? "pending count unavailable" : `${pendingCount} pending`}` + (failure === null ? "." : `; delivery failed: ${failure}.`);
|
|
15877
|
+
logActivity(state, {
|
|
15878
|
+
type: "info",
|
|
15879
|
+
level: failure === null ? "info" : "warn",
|
|
15880
|
+
message,
|
|
15881
|
+
metadata: {
|
|
15882
|
+
outcome: failure === null ? "session-deletion-reports-drained" : "session-deletion-reports-pending",
|
|
15883
|
+
delivered,
|
|
15884
|
+
pending: pendingCount
|
|
15885
|
+
}
|
|
15886
|
+
});
|
|
15887
|
+
}
|
|
15264
15888
|
function logSessionDbProvenanceMismatch(state, provenance, currentVersion, preBootMigrationCount) {
|
|
15265
15889
|
const record = {
|
|
15266
15890
|
v: 1,
|
|
@@ -15318,17 +15942,22 @@ async function runSweep(state, driver, config) {
|
|
|
15318
15942
|
let failedThrew = 0;
|
|
15319
15943
|
let skippedNewlyActive = 0;
|
|
15320
15944
|
let remaining = 0;
|
|
15945
|
+
const deletionRecords = [];
|
|
15321
15946
|
let protectedNow = driver.protectedSessionIds();
|
|
15322
15947
|
if (sessions !== null) {
|
|
15323
|
-
const
|
|
15324
|
-
|
|
15325
|
-
|
|
15326
|
-
|
|
15327
|
-
|
|
15328
|
-
|
|
15329
|
-
protectedIds: protectedNow
|
|
15330
|
-
}
|
|
15948
|
+
const snapshots = sessions.map((session) => ({
|
|
15949
|
+
id: session.id,
|
|
15950
|
+
lastActivityMs: sessionLastActivityMs(session)
|
|
15951
|
+
}));
|
|
15952
|
+
const lastActivityById = new Map(
|
|
15953
|
+
snapshots.map(({ id, lastActivityMs }) => [id, lastActivityMs])
|
|
15331
15954
|
);
|
|
15955
|
+
const toDelete = selectSessionsToDelete(snapshots, {
|
|
15956
|
+
maxAgeMs: config.maxAgeMs,
|
|
15957
|
+
maxCount: config.maxCount,
|
|
15958
|
+
nowMs: Date.now(),
|
|
15959
|
+
protectedIds: protectedNow
|
|
15960
|
+
});
|
|
15332
15961
|
const eligibleToDelete = toDelete.filter(
|
|
15333
15962
|
(id) => (state.sessionDeleteFailures.get(id)?.isolatedFailures ?? 0) < SESSION_CLEANUP_GIVE_UP_AFTER_FAILURES
|
|
15334
15963
|
);
|
|
@@ -15348,6 +15977,17 @@ async function runSweep(state, driver, config) {
|
|
|
15348
15977
|
const outcome = await deleteSession(state.port, id, state.opencodeClient ?? void 0);
|
|
15349
15978
|
if (outcome.ok) {
|
|
15350
15979
|
deleted++;
|
|
15980
|
+
const lastActivityMs = lastActivityById.get(id) ?? null;
|
|
15981
|
+
deletionRecords.push({
|
|
15982
|
+
opencode_session_id: id,
|
|
15983
|
+
deleted_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15984
|
+
reason: "auto_cleanup",
|
|
15985
|
+
policy: {
|
|
15986
|
+
max_age_ms: config.maxAgeMs ?? null,
|
|
15987
|
+
max_count: config.maxCount ?? null,
|
|
15988
|
+
last_activity_at: lastActivityMs === null ? null : new Date(lastActivityMs).toISOString()
|
|
15989
|
+
}
|
|
15990
|
+
});
|
|
15351
15991
|
} else if (outcome.status === "threw") {
|
|
15352
15992
|
failed++;
|
|
15353
15993
|
failedThrew++;
|
|
@@ -15403,6 +16043,32 @@ async function runSweep(state, driver, config) {
|
|
|
15403
16043
|
message: `Session cleanup: inspected ${sessions.length}, deleted ${deleted}${failedNote}${skippedNote}${remainingNote} (${mode})`
|
|
15404
16044
|
});
|
|
15405
16045
|
}
|
|
16046
|
+
if (deletionRecords.length > 0) {
|
|
16047
|
+
try {
|
|
16048
|
+
const dropped = appendSessionDeletionRecords(
|
|
16049
|
+
sessionDeletionReportPath(homedir8(), process.env),
|
|
16050
|
+
deletionRecords
|
|
16051
|
+
);
|
|
16052
|
+
if (dropped > 0) {
|
|
16053
|
+
logSessionCleanupActivity(state, "sweep", {
|
|
16054
|
+
type: "info",
|
|
16055
|
+
level: "warn",
|
|
16056
|
+
message: `Session cleanup: dropped ${dropped} oldest pending deletion report(s) after reaching the spool limit`,
|
|
16057
|
+
metadata: { outcome: "deletion-reports-dropped", dropped_count: dropped }
|
|
16058
|
+
});
|
|
16059
|
+
}
|
|
16060
|
+
} catch (error2) {
|
|
16061
|
+
logSessionCleanupActivity(state, "sweep", {
|
|
16062
|
+
type: "info",
|
|
16063
|
+
level: "warn",
|
|
16064
|
+
message: `Session cleanup: could not persist ${deletionRecords.length} confirmed deletion report(s): ${error2 instanceof Error ? error2.message : String(error2)}`,
|
|
16065
|
+
metadata: {
|
|
16066
|
+
outcome: "deletion-report-spool-write-failed",
|
|
16067
|
+
records: deletionRecords.length
|
|
16068
|
+
}
|
|
16069
|
+
});
|
|
16070
|
+
}
|
|
16071
|
+
}
|
|
15406
16072
|
const sessionMetadata = {
|
|
15407
16073
|
sessions_inspected: sessions?.length ?? null,
|
|
15408
16074
|
sessions_deleted: deleted,
|
|
@@ -15472,6 +16138,7 @@ async function runSweep(state, driver, config) {
|
|
|
15472
16138
|
}
|
|
15473
16139
|
});
|
|
15474
16140
|
}
|
|
16141
|
+
await drainSessionDeletionReports(state);
|
|
15475
16142
|
} catch (error2) {
|
|
15476
16143
|
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
15477
16144
|
logSessionCleanupActivity(state, "sweep", {
|
|
@@ -16314,6 +16981,7 @@ async function run(options) {
|
|
|
16314
16981
|
}
|
|
16315
16982
|
}
|
|
16316
16983
|
reportSessionDbRecovery(state);
|
|
16984
|
+
await drainSessionDeletionReports(state);
|
|
16317
16985
|
if (sessionDbVerifyFatal) {
|
|
16318
16986
|
throw new Error(
|
|
16319
16987
|
sessionDbVerifyFatalReason === void 0 ? "SESSION-DB-INTEGRITY-EXHAUSTED: the corrupt session DB could not be proven separated from the active backup prefix or removed from disk" : `SESSION-DB-VERIFY-TIMEOUT: session DB verification timed out (${sessionDbVerifyFatalReason}); refusing to start`
|
|
@@ -16344,13 +17012,17 @@ async function run(options) {
|
|
|
16344
17012
|
agentId: state.agentId,
|
|
16345
17013
|
apiUrl: getApiUrlConfig(),
|
|
16346
17014
|
getAuthHeader: () => state.authHeader,
|
|
17015
|
+
autoConfigureCredentialsProxy: options.autoConfigureCredentialsProxy,
|
|
16347
17016
|
log: (message, level = "info") => logActivity(state, {
|
|
16348
17017
|
type: level === "error" ? "error" : "info",
|
|
16349
17018
|
level,
|
|
16350
17019
|
...level === "error" ? { error: message } : { message }
|
|
16351
17020
|
})
|
|
16352
17021
|
};
|
|
16353
|
-
|
|
17022
|
+
let managedDiscovery = await discoverManagedInferenceSetup(managedSetupOptions);
|
|
17023
|
+
if (managedDiscovery.kind === "none") {
|
|
17024
|
+
managedDiscovery = await enrollManagedInferenceSetup(managedSetupOptions);
|
|
17025
|
+
}
|
|
16354
17026
|
let managedPreparation = { kind: "none" };
|
|
16355
17027
|
if (managedDiscovery.kind === "failed") {
|
|
16356
17028
|
managedPreparation = managedDiscovery;
|
|
@@ -16440,7 +17112,7 @@ async function run(options) {
|
|
|
16440
17112
|
state.opencodeClient = openCodeClient;
|
|
16441
17113
|
if (options.opencodePidFile && oc.process?.pid !== void 0) {
|
|
16442
17114
|
try {
|
|
16443
|
-
|
|
17115
|
+
writeFileSync7(options.opencodePidFile, `${oc.process.pid}
|
|
16444
17116
|
`, { mode: 384 });
|
|
16445
17117
|
chmodSync3(options.opencodePidFile, 384);
|
|
16446
17118
|
} catch (error2) {
|
|
@@ -16550,7 +17222,7 @@ async function run(options) {
|
|
|
16550
17222
|
let existingPid;
|
|
16551
17223
|
if (existsSync3(options.litestreamPidFile)) {
|
|
16552
17224
|
try {
|
|
16553
|
-
const rawPid =
|
|
17225
|
+
const rawPid = readFileSync9(options.litestreamPidFile, "utf8").trim();
|
|
16554
17226
|
const parsedPid = Number(rawPid);
|
|
16555
17227
|
if (/^\d+$/.test(rawPid) && Number.isSafeInteger(parsedPid) && parsedPid > 0) {
|
|
16556
17228
|
existingPid = parsedPid;
|
|
@@ -16587,7 +17259,7 @@ async function run(options) {
|
|
|
16587
17259
|
});
|
|
16588
17260
|
try {
|
|
16589
17261
|
if (litestreamProcess.pid !== void 0) {
|
|
16590
|
-
|
|
17262
|
+
writeFileSync7(options.litestreamPidFile, `${litestreamProcess.pid}
|
|
16591
17263
|
`, {
|
|
16592
17264
|
mode: 384
|
|
16593
17265
|
});
|
|
@@ -16674,7 +17346,7 @@ async function run(options) {
|
|
|
16674
17346
|
onConnected: (agentId, isReconnect, transition) => {
|
|
16675
17347
|
state.connected = true;
|
|
16676
17348
|
state.agentId = agentId;
|
|
16677
|
-
const connectionMessage = isReconnect ? `Tunnel reconnected (runner: ${agentId}, generation: ${transition.generation}, attempt: ${transition.attempt}, offline for ${transition.downtimeMs}ms)` : `Tunnel connected (runner: ${agentId}, generation: ${transition.generation})`;
|
|
17349
|
+
const connectionMessage = isReconnect ? `Tunnel reconnected (runner: ${agentId}, generation: ${transition.generation}, relay connection: ${transition.relayConnectionId ?? "unknown"}, attempt: ${transition.attempt}, offline for ${transition.downtimeMs}ms)` : `Tunnel connected (runner: ${agentId}, generation: ${transition.generation}, relay connection: ${transition.relayConnectionId ?? "unknown"})`;
|
|
16678
17350
|
logActivity(state, {
|
|
16679
17351
|
type: "info",
|
|
16680
17352
|
message: connectionMessage
|
|
@@ -16707,6 +17379,7 @@ async function run(options) {
|
|
|
16707
17379
|
connected: state.opencodeConnected
|
|
16708
17380
|
}),
|
|
16709
17381
|
connection_generation: transition.generation,
|
|
17382
|
+
...transition.relayConnectionId !== null ? { relay_connection_id: transition.relayConnectionId } : {},
|
|
16710
17383
|
...isReconnect && transition.downtimeMs !== null ? {
|
|
16711
17384
|
reconnect_attempt: transition.attempt,
|
|
16712
17385
|
downtime_ms: transition.downtimeMs
|
|
@@ -16737,12 +17410,13 @@ async function run(options) {
|
|
|
16737
17410
|
const safeReason2 = redactDiagnosticText(reason).slice(0, MAX_DISCONNECT_REASON_LENGTH);
|
|
16738
17411
|
logActivity(state, {
|
|
16739
17412
|
type: "info",
|
|
16740
|
-
message: `Tunnel disconnected (generation: ${transition.generation}, code: ${code}, reason: ${safeReason2})`
|
|
17413
|
+
message: `Tunnel disconnected (generation: ${transition.generation}, relay connection: ${transition.relayConnectionId ?? "unknown"}, code: ${code}, reason: ${safeReason2})`
|
|
16741
17414
|
});
|
|
16742
17415
|
emitAgentDisconnected(state.agentId, {
|
|
16743
17416
|
code,
|
|
16744
17417
|
reason: safeReason2,
|
|
16745
|
-
connection_generation: transition.generation
|
|
17418
|
+
connection_generation: transition.generation,
|
|
17419
|
+
...transition.relayConnectionId !== null ? { relay_connection_id: transition.relayConnectionId } : {}
|
|
16746
17420
|
});
|
|
16747
17421
|
if (state.interactive) displayStatus(state);
|
|
16748
17422
|
},
|
|
@@ -16969,6 +17643,9 @@ program.command("run").description("Connect to Evident and process messages").op
|
|
|
16969
17643
|
).option(
|
|
16970
17644
|
"--opencode-config-overlay <path>",
|
|
16971
17645
|
"Apply this runner-provided OpenCode config before starting OpenCode."
|
|
17646
|
+
).option(
|
|
17647
|
+
"--auto-configure-credentials-proxy",
|
|
17648
|
+
"Install Evident's generated OpenCode providers for the runner's credential pool (off by default)."
|
|
16972
17649
|
).option(
|
|
16973
17650
|
"--credential-sync-marker <path>",
|
|
16974
17651
|
"Own the interval credential sync and write this marker once the shutdown flush has finished, so the runner image's lifecycle hooks can wait on it."
|
|
@@ -17012,6 +17689,7 @@ program.command("run").description("Connect to Evident and process messages").op
|
|
|
17012
17689
|
restoreSessionDb: options.restoreSessionDb,
|
|
17013
17690
|
restoreRunnerCredentials: options.restoreRunnerCredentials,
|
|
17014
17691
|
opencodeConfigOverlay: options.opencodeConfigOverlay,
|
|
17692
|
+
autoConfigureCredentialsProxy: options.autoConfigureCredentialsProxy,
|
|
17015
17693
|
credentialSyncMarker: options.credentialSyncMarker
|
|
17016
17694
|
});
|
|
17017
17695
|
}
|