@evident-ai/cli 3.5.2-dev.3e69672 → 3.5.2-dev.3f3949d
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 +5 -3
- package/dist/index.js +855 -215
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -315,14 +315,14 @@ function blank() {
|
|
|
315
315
|
console.log();
|
|
316
316
|
}
|
|
317
317
|
function waitForEnter(prompt = "Press Enter to continue...") {
|
|
318
|
-
return new Promise((
|
|
318
|
+
return new Promise((resolve5) => {
|
|
319
319
|
process.stdout.write(chalk.dim(prompt));
|
|
320
320
|
const handler = () => {
|
|
321
321
|
process.stdin.removeListener("data", handler);
|
|
322
322
|
process.stdin.setRawMode?.(false);
|
|
323
323
|
process.stdin.pause();
|
|
324
324
|
console.log();
|
|
325
|
-
|
|
325
|
+
resolve5();
|
|
326
326
|
};
|
|
327
327
|
if (process.stdin.isTTY) {
|
|
328
328
|
process.stdin.setRawMode?.(true);
|
|
@@ -332,7 +332,7 @@ function waitForEnter(prompt = "Press Enter to continue...") {
|
|
|
332
332
|
});
|
|
333
333
|
}
|
|
334
334
|
function sleep(ms) {
|
|
335
|
-
return new Promise((
|
|
335
|
+
return new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
336
336
|
}
|
|
337
337
|
|
|
338
338
|
// src/commands/login.ts
|
|
@@ -410,19 +410,19 @@ async function tokenLogin() {
|
|
|
410
410
|
);
|
|
411
411
|
blank();
|
|
412
412
|
process.stdout.write("Paste token: ");
|
|
413
|
-
const token = await new Promise((
|
|
413
|
+
const token = await new Promise((resolve5) => {
|
|
414
414
|
let data = "";
|
|
415
415
|
process.stdin.setEncoding("utf8");
|
|
416
416
|
process.stdin.on("data", (chunk) => {
|
|
417
417
|
data += chunk;
|
|
418
418
|
});
|
|
419
419
|
process.stdin.on("end", () => {
|
|
420
|
-
|
|
420
|
+
resolve5(data.trim());
|
|
421
421
|
});
|
|
422
422
|
if (process.stdin.isTTY) {
|
|
423
423
|
process.stdin.once("data", (chunk) => {
|
|
424
424
|
process.stdin.pause();
|
|
425
|
-
|
|
425
|
+
resolve5(chunk.toString().trim());
|
|
426
426
|
});
|
|
427
427
|
process.stdin.resume();
|
|
428
428
|
}
|
|
@@ -1158,7 +1158,7 @@ async function claudeUsage() {
|
|
|
1158
1158
|
// src/commands/run.ts
|
|
1159
1159
|
import { chmodSync as chmodSync3, existsSync as existsSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "node:fs";
|
|
1160
1160
|
import { homedir as homedir7 } from "node:os";
|
|
1161
|
-
import { isAbsolute as
|
|
1161
|
+
import { isAbsolute as isAbsolute4, join as join12, parse, resolve as resolvePath2 } from "node:path";
|
|
1162
1162
|
import chalk7 from "chalk";
|
|
1163
1163
|
|
|
1164
1164
|
// ../../packages/types/src/agents/index.ts
|
|
@@ -1322,7 +1322,7 @@ import ora4 from "ora";
|
|
|
1322
1322
|
import { select as select4 } from "@inquirer/prompts";
|
|
1323
1323
|
|
|
1324
1324
|
// src/lib/telemetry.ts
|
|
1325
|
-
var CLI_VERSION = (true ? "3.5.2-dev.
|
|
1325
|
+
var CLI_VERSION = (true ? "3.5.2-dev.3f3949d" : void 0) ?? process.env.npm_package_version ?? "unknown";
|
|
1326
1326
|
function getCliVersion() {
|
|
1327
1327
|
return CLI_VERSION;
|
|
1328
1328
|
}
|
|
@@ -1483,20 +1483,47 @@ var EventTypes = {
|
|
|
1483
1483
|
DEPRECATED_AGENT_KEY_ENV_USED: "cli.deprecated_agent_key_env_used"
|
|
1484
1484
|
};
|
|
1485
1485
|
|
|
1486
|
-
// src/lib/runner-activity-telemetry.ts
|
|
1487
|
-
import { homedir as homedir2 } from "node:os";
|
|
1488
|
-
|
|
1489
1486
|
// src/lib/opencode/session-db-reclaim.ts
|
|
1490
1487
|
import { statSync as statSync2, statfsSync } from "node:fs";
|
|
1491
1488
|
import { dirname as dirname2 } from "node:path";
|
|
1492
|
-
|
|
1489
|
+
|
|
1490
|
+
// src/lib/diagnostic-redaction.ts
|
|
1491
|
+
import { homedir as homedir2 } from "node:os";
|
|
1492
|
+
function redactHomeDirectory(message) {
|
|
1493
|
+
const home = homedir2();
|
|
1494
|
+
if (!home || home === "/") {
|
|
1495
|
+
return message;
|
|
1496
|
+
}
|
|
1497
|
+
const pathSegmentCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._-";
|
|
1498
|
+
let result = "";
|
|
1499
|
+
let searchFrom = 0;
|
|
1500
|
+
let homeIndex;
|
|
1501
|
+
while ((homeIndex = message.indexOf(home, searchFrom)) !== -1) {
|
|
1502
|
+
const homeEnd = homeIndex + home.length;
|
|
1503
|
+
const previousCharacter = message[homeIndex - 1];
|
|
1504
|
+
const nextCharacter = message[homeEnd];
|
|
1505
|
+
const atPathBoundary = (!previousCharacter || !pathSegmentCharacters.includes(previousCharacter)) && (!nextCharacter || !pathSegmentCharacters.includes(nextCharacter));
|
|
1506
|
+
result += message.slice(searchFrom, homeIndex);
|
|
1507
|
+
result += atPathBoundary ? "~" : home;
|
|
1508
|
+
searchFrom = homeEnd;
|
|
1509
|
+
}
|
|
1510
|
+
return result + message.slice(searchFrom);
|
|
1511
|
+
}
|
|
1512
|
+
function redactDiagnosticText(message) {
|
|
1513
|
+
const redacted = message.replace(/esk_[A-Za-z0-9_-]+/g, "esk_***").replace(/ct_[A-Za-z0-9_-]+/g, "ct_***").replace(/(?<![A-Za-z0-9_-])sk-ant-[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g, "sk-ant-***").replace(/(?<![A-Za-z0-9_-])sk-proj-[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g, "sk-proj-***").replace(/(?<![A-Za-z0-9_-])sk-[A-Za-z0-9]{20,}(?![A-Za-z0-9_-])/g, "sk-***").replace(/(?<![A-Za-z0-9_-])(gh[oprsu])_[A-Za-z0-9]{20,}(?![A-Za-z0-9_-])/g, "$1_***").replace(/(?<![A-Za-z0-9_-])github_pat_[A-Za-z0-9_]{20,}(?![A-Za-z0-9_-])/g, "github_pat_***").replace(/https?:\/\/\S+/g, "<url>");
|
|
1514
|
+
return redactHomeDirectory(redacted);
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
// src/lib/opencode/session-db-reclaim.ts
|
|
1518
|
+
var SESSION_DB_RECLAIM_CHUNK_PAGES = 100;
|
|
1493
1519
|
var SESSION_DB_RECLAIM_TIME_BUDGET_MS = 500;
|
|
1494
1520
|
var SESSION_DB_RECLAIM_PAGE_CEILING = 4e4;
|
|
1521
|
+
var SESSION_DB_RECLAIM_BUSY_TIMEOUT_MS = 5e3;
|
|
1495
1522
|
function errorMessage(error2) {
|
|
1496
1523
|
if (!(error2 instanceof Error)) return String(error2);
|
|
1497
1524
|
return error2.cause instanceof Error ? error2.cause.message : error2.message;
|
|
1498
1525
|
}
|
|
1499
|
-
function
|
|
1526
|
+
function errorMessages(error2) {
|
|
1500
1527
|
const messages = [];
|
|
1501
1528
|
const visited = /* @__PURE__ */ new Set();
|
|
1502
1529
|
let current = error2;
|
|
@@ -1520,12 +1547,81 @@ function isSessionStoreWriteFailure(error2) {
|
|
|
1520
1547
|
}
|
|
1521
1548
|
break;
|
|
1522
1549
|
}
|
|
1523
|
-
return messages
|
|
1550
|
+
return messages;
|
|
1551
|
+
}
|
|
1552
|
+
function isSessionStoreWriteFailure(error2) {
|
|
1553
|
+
return errorMessages(error2).some(
|
|
1524
1554
|
(message) => /(?:database or disk is full|disk is full|no space left on device|SQLITE_(?:FULL|IOERR)(?:\b|_))/i.test(
|
|
1525
1555
|
message
|
|
1526
1556
|
)
|
|
1527
1557
|
);
|
|
1528
1558
|
}
|
|
1559
|
+
var SQLITE_CAUSE_TOKEN = /(?:^|cause:\s+)(SQLiteError:\s+[A-Za-z][A-Za-z0-9]*(?:\s+[A-Za-z][A-Za-z0-9]*){0,7})(?=\s*(?:[)\]"',;]|$))/i;
|
|
1560
|
+
var EFFECT_SQL_CAUSE_TOKEN = /(?:^|cause:\s+)(effect\/sql\/SqlError\/[A-Za-z][A-Za-z0-9_]*)/i;
|
|
1561
|
+
var PARAMS_FIELD = /\bparams\s*(?:=|:)/i;
|
|
1562
|
+
function isCauseBearingText(text) {
|
|
1563
|
+
return /^(?:SQLiteError:\s+|effect\/sql\/SqlError\/)/i.test(text.trim()) || /(?:^|\s)cause:\s+(?:SQLiteError:\s+|effect\/sql\/SqlError\/)/i.test(text);
|
|
1564
|
+
}
|
|
1565
|
+
function structuredLogFields(line) {
|
|
1566
|
+
const fields = [];
|
|
1567
|
+
for (let index = 0; index < line.length; index++) {
|
|
1568
|
+
if (line[index] === '"') {
|
|
1569
|
+
index++;
|
|
1570
|
+
while (index < line.length) {
|
|
1571
|
+
if (line[index] === "\\") {
|
|
1572
|
+
index++;
|
|
1573
|
+
} else if (line[index] === '"') {
|
|
1574
|
+
break;
|
|
1575
|
+
}
|
|
1576
|
+
index++;
|
|
1577
|
+
}
|
|
1578
|
+
continue;
|
|
1579
|
+
}
|
|
1580
|
+
const atFieldBoundary = index === 0 || /\s/.test(line[index - 1]);
|
|
1581
|
+
const key = line.startsWith("error=", index) ? "error=" : line.startsWith("cause=", index) ? "cause=" : null;
|
|
1582
|
+
if (!atFieldBoundary || key === null) continue;
|
|
1583
|
+
const valueStart = index + key.length;
|
|
1584
|
+
if (line[valueStart] !== '"') {
|
|
1585
|
+
const valueEnd2 = line.indexOf(" ", valueStart);
|
|
1586
|
+
fields.push(line.slice(valueStart, valueEnd2 === -1 ? line.length : valueEnd2));
|
|
1587
|
+
index = valueEnd2 === -1 ? line.length : valueEnd2;
|
|
1588
|
+
continue;
|
|
1589
|
+
}
|
|
1590
|
+
let valueEnd = valueStart + 1;
|
|
1591
|
+
for (; valueEnd < line.length; valueEnd++) {
|
|
1592
|
+
if (line[valueEnd] === "\\") {
|
|
1593
|
+
valueEnd++;
|
|
1594
|
+
} else if (line[valueEnd] === '"') {
|
|
1595
|
+
break;
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
fields.push(line.slice(valueStart + 1, valueEnd));
|
|
1599
|
+
index = valueEnd;
|
|
1600
|
+
}
|
|
1601
|
+
return fields;
|
|
1602
|
+
}
|
|
1603
|
+
function structuredCauseMessages(text) {
|
|
1604
|
+
const fields = structuredLogFields(text);
|
|
1605
|
+
if (fields.length > 0) return fields;
|
|
1606
|
+
return isCauseBearingText(text) ? [text] : [];
|
|
1607
|
+
}
|
|
1608
|
+
function causeTokenFromText(text) {
|
|
1609
|
+
const paramsIndex = text.search(PARAMS_FIELD);
|
|
1610
|
+
for (const pattern of [SQLITE_CAUSE_TOKEN, EFFECT_SQL_CAUSE_TOKEN]) {
|
|
1611
|
+
const match = pattern.exec(text);
|
|
1612
|
+
if (match?.[1] && (paramsIndex === -1 || match.index < paramsIndex)) {
|
|
1613
|
+
return match[1];
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
return null;
|
|
1617
|
+
}
|
|
1618
|
+
function sessionStoreCauseToken(value) {
|
|
1619
|
+
for (const message of structuredCauseMessages(value)) {
|
|
1620
|
+
const token = causeTokenFromText(message);
|
|
1621
|
+
if (token !== null) return redactDiagnosticText(token).slice(0, 120);
|
|
1622
|
+
}
|
|
1623
|
+
return null;
|
|
1624
|
+
}
|
|
1529
1625
|
function insufficientSpaceReason(dbPath, requiredBytes) {
|
|
1530
1626
|
try {
|
|
1531
1627
|
const fsStats = statfsSync(dirname2(dbPath));
|
|
@@ -1547,6 +1643,7 @@ function readFreelistCount(db) {
|
|
|
1547
1643
|
return db.prepare("PRAGMA freelist_count").get().freelist_count;
|
|
1548
1644
|
}
|
|
1549
1645
|
function readCheckpointResult(db) {
|
|
1646
|
+
db.exec("PRAGMA busy_timeout=0");
|
|
1550
1647
|
const row = db.prepare("PRAGMA wal_checkpoint(TRUNCATE)").get();
|
|
1551
1648
|
return { busy: row.busy !== 0, log: row.log, checkpointed: row.checkpointed };
|
|
1552
1649
|
}
|
|
@@ -1564,7 +1661,7 @@ async function probeReclaimAvailability(input) {
|
|
|
1564
1661
|
try {
|
|
1565
1662
|
const db = new sqlite.DatabaseSync(dbPath, { readOnly: true });
|
|
1566
1663
|
try {
|
|
1567
|
-
db.exec(
|
|
1664
|
+
db.exec(`PRAGMA busy_timeout=${SESSION_DB_RECLAIM_BUSY_TIMEOUT_MS}`);
|
|
1568
1665
|
autoVacuum = db.prepare("PRAGMA auto_vacuum").get().auto_vacuum;
|
|
1569
1666
|
} finally {
|
|
1570
1667
|
db.close();
|
|
@@ -1584,7 +1681,8 @@ async function reclaimSessionDbSpace(input) {
|
|
|
1584
1681
|
timeBudgetMs = SESSION_DB_RECLAIM_TIME_BUDGET_MS,
|
|
1585
1682
|
chunkPages = SESSION_DB_RECLAIM_CHUNK_PAGES,
|
|
1586
1683
|
now = () => performance.now(),
|
|
1587
|
-
|
|
1684
|
+
hasLiveTurn = false,
|
|
1685
|
+
hasProvenanceAnomaly = false
|
|
1588
1686
|
} = input;
|
|
1589
1687
|
let sqlite;
|
|
1590
1688
|
try {
|
|
@@ -1600,17 +1698,19 @@ async function reclaimSessionDbSpace(input) {
|
|
|
1600
1698
|
let db;
|
|
1601
1699
|
try {
|
|
1602
1700
|
db = new DatabaseSync(dbPath);
|
|
1603
|
-
db.exec(
|
|
1701
|
+
db.exec(`PRAGMA busy_timeout=${SESSION_DB_RECLAIM_BUSY_TIMEOUT_MS}`);
|
|
1604
1702
|
const autoVacuum = db.prepare("PRAGMA auto_vacuum").get().auto_vacuum;
|
|
1605
1703
|
if (autoVacuum === 0) {
|
|
1606
|
-
if (
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1704
|
+
if (hasLiveTurn || hasProvenanceAnomaly) {
|
|
1705
|
+
const detail = [
|
|
1706
|
+
...hasLiveTurn ? ["a session turn is live"] : [],
|
|
1707
|
+
...hasProvenanceAnomaly ? ["session-DB provenance is anomalous"] : []
|
|
1708
|
+
].join(" and ");
|
|
1709
|
+
console.warn(`[reclaimSessionDbSpace] skipping VACUUM conversion of ${dbPath}: ${detail}`);
|
|
1610
1710
|
return {
|
|
1611
1711
|
ok: false,
|
|
1612
1712
|
skipped: "full-vacuum-blocked",
|
|
1613
|
-
detail
|
|
1713
|
+
detail
|
|
1614
1714
|
};
|
|
1615
1715
|
}
|
|
1616
1716
|
const fileBytesForGuard = statSync2(dbPath).size;
|
|
@@ -1628,7 +1728,9 @@ async function reclaimSessionDbSpace(input) {
|
|
|
1628
1728
|
db.exec("VACUUM");
|
|
1629
1729
|
const afterBytes = readLogicalBytes(db);
|
|
1630
1730
|
const freelistAfter = readFreelistCount(db);
|
|
1731
|
+
const checkpointStartedAt = now();
|
|
1631
1732
|
const checkpoint = readCheckpointResult(db);
|
|
1733
|
+
const checkpointElapsedMs = Math.max(0, now() - checkpointStartedAt);
|
|
1632
1734
|
return {
|
|
1633
1735
|
ok: true,
|
|
1634
1736
|
mode: "convert",
|
|
@@ -1639,10 +1741,18 @@ async function reclaimSessionDbSpace(input) {
|
|
|
1639
1741
|
pagesReclaimed: Math.max(0, freelistBefore - freelistAfter),
|
|
1640
1742
|
stoppedBy: "freelist-drained",
|
|
1641
1743
|
elapsedMs: Math.max(0, now() - startedAt),
|
|
1744
|
+
checkpointElapsedMs,
|
|
1642
1745
|
checkpoint
|
|
1643
1746
|
};
|
|
1644
1747
|
}
|
|
1645
1748
|
if (autoVacuum === 2) {
|
|
1749
|
+
if (hasLiveTurn) {
|
|
1750
|
+
const detail = "a session turn is live";
|
|
1751
|
+
console.warn(
|
|
1752
|
+
`[reclaimSessionDbSpace] skipping incremental reclaim of ${dbPath}: ${detail}`
|
|
1753
|
+
);
|
|
1754
|
+
return { ok: false, skipped: "live-turn", detail };
|
|
1755
|
+
}
|
|
1646
1756
|
const beforeBytes = readLogicalBytes(db);
|
|
1647
1757
|
const freelistBefore = readFreelistCount(db);
|
|
1648
1758
|
const startedAt = now();
|
|
@@ -1667,7 +1777,9 @@ async function reclaimSessionDbSpace(input) {
|
|
|
1667
1777
|
freelistAfter = readFreelistCount(db);
|
|
1668
1778
|
}
|
|
1669
1779
|
const afterBytes = readLogicalBytes(db);
|
|
1780
|
+
const checkpointStartedAt = now();
|
|
1670
1781
|
const checkpoint = readCheckpointResult(db);
|
|
1782
|
+
const checkpointElapsedMs = Math.max(0, now() - checkpointStartedAt);
|
|
1671
1783
|
return {
|
|
1672
1784
|
ok: true,
|
|
1673
1785
|
mode: "incremental",
|
|
@@ -1678,6 +1790,7 @@ async function reclaimSessionDbSpace(input) {
|
|
|
1678
1790
|
pagesReclaimed: Math.max(0, freelistBefore - freelistAfter),
|
|
1679
1791
|
stoppedBy,
|
|
1680
1792
|
elapsedMs: Math.max(0, now() - startedAt),
|
|
1793
|
+
checkpointElapsedMs,
|
|
1681
1794
|
checkpoint
|
|
1682
1795
|
};
|
|
1683
1796
|
}
|
|
@@ -1697,6 +1810,263 @@ async function reclaimSessionDbSpace(input) {
|
|
|
1697
1810
|
}
|
|
1698
1811
|
}
|
|
1699
1812
|
|
|
1813
|
+
// src/lib/opencode/dependency-install-diagnostic.ts
|
|
1814
|
+
import { lstatSync, realpathSync } from "node:fs";
|
|
1815
|
+
import { basename, dirname as dirname3, isAbsolute, join as join2, relative, resolve, sep } from "node:path";
|
|
1816
|
+
var DEPENDENCY_INSTALL_FAILURE_MESSAGE = "background dependency install failed";
|
|
1817
|
+
var MAX_DISTINCT_DIAGNOSTICS = 32;
|
|
1818
|
+
var EFFECT = "OpenCode could not install background dependencies for this scope; its plugins and tools may be unavailable";
|
|
1819
|
+
function readOpenCodeLogField(line, key) {
|
|
1820
|
+
const match = line.match(new RegExp(`(?:^|\\s)${key}=(?:"((?:\\\\.|[^"])*)"|(\\S+))`));
|
|
1821
|
+
if (!match) return void 0;
|
|
1822
|
+
if (match[1] === void 0) return match[2];
|
|
1823
|
+
return match[1].replace(/\\(["\\n])/g, (_match, escaped) => {
|
|
1824
|
+
return escaped === "n" ? "\n" : escaped;
|
|
1825
|
+
});
|
|
1826
|
+
}
|
|
1827
|
+
function parseTrigger(line) {
|
|
1828
|
+
if (readOpenCodeLogField(line, "message") !== DEPENDENCY_INSTALL_FAILURE_MESSAGE) {
|
|
1829
|
+
return null;
|
|
1830
|
+
}
|
|
1831
|
+
const error2 = readOpenCodeLogField(line, "error");
|
|
1832
|
+
if (!error2 || !/\bEACCES\b/.test(error2)) return null;
|
|
1833
|
+
const errnoMatch = error2.match(/\bEACCES:\s*permission denied,\s*([a-z_]+)\s+'([^']*)'/);
|
|
1834
|
+
const capturedPath = errnoMatch?.[2];
|
|
1835
|
+
return {
|
|
1836
|
+
dir: readOpenCodeLogField(line, "dir"),
|
|
1837
|
+
op: errnoMatch?.[1],
|
|
1838
|
+
path: capturedPath && isAbsolute(capturedPath) ? capturedPath : void 0
|
|
1839
|
+
};
|
|
1840
|
+
}
|
|
1841
|
+
function getErrorCode(error2) {
|
|
1842
|
+
if (typeof error2 === "object" && error2 !== null && "code" in error2) {
|
|
1843
|
+
const code = error2.code;
|
|
1844
|
+
if (typeof code === "string") return code;
|
|
1845
|
+
}
|
|
1846
|
+
return "UNKNOWN";
|
|
1847
|
+
}
|
|
1848
|
+
function isWithin(path, root) {
|
|
1849
|
+
const candidate = resolve(path);
|
|
1850
|
+
const resolvedRoot = resolve(root);
|
|
1851
|
+
return candidate === resolvedRoot || candidate.startsWith(`${resolvedRoot}${sep}`);
|
|
1852
|
+
}
|
|
1853
|
+
function rootSpecs(context) {
|
|
1854
|
+
return [
|
|
1855
|
+
{ token: "~", configured: resolve(context.homeDir) },
|
|
1856
|
+
{ token: "$WORKSPACE", configured: resolve(context.cwd) }
|
|
1857
|
+
];
|
|
1858
|
+
}
|
|
1859
|
+
function resolveRoots(context) {
|
|
1860
|
+
const resolved = [];
|
|
1861
|
+
const unresolved = [];
|
|
1862
|
+
for (const spec of rootSpecs(context)) {
|
|
1863
|
+
try {
|
|
1864
|
+
resolved.push({ ...spec, real: realpathSync(spec.configured) });
|
|
1865
|
+
} catch (_error) {
|
|
1866
|
+
unresolved.push(spec);
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
return { resolved, unresolved };
|
|
1870
|
+
}
|
|
1871
|
+
function resolveNearestAncestor(failingPath, remaining, error2) {
|
|
1872
|
+
let ancestor = dirname3(failingPath);
|
|
1873
|
+
const suffix = [basename(failingPath), ...remaining];
|
|
1874
|
+
while (ancestor) {
|
|
1875
|
+
try {
|
|
1876
|
+
return {
|
|
1877
|
+
path: resolve(realpathSync(ancestor), ...suffix),
|
|
1878
|
+
failure: { path: resolve(failingPath), error: error2 }
|
|
1879
|
+
};
|
|
1880
|
+
} catch (ancestorError) {
|
|
1881
|
+
if (ancestor === dirname3(ancestor) || !getErrorCode(ancestorError).startsWith("E")) {
|
|
1882
|
+
return null;
|
|
1883
|
+
}
|
|
1884
|
+
suffix.unshift(basename(ancestor));
|
|
1885
|
+
ancestor = dirname3(ancestor);
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
return null;
|
|
1889
|
+
}
|
|
1890
|
+
function resolveExistingPath(absolutePath) {
|
|
1891
|
+
if (!isAbsolute(absolutePath)) return null;
|
|
1892
|
+
const candidate = resolve(absolutePath);
|
|
1893
|
+
const remaining = [];
|
|
1894
|
+
let current = candidate;
|
|
1895
|
+
while (current) {
|
|
1896
|
+
try {
|
|
1897
|
+
const stat2 = lstatSync(current);
|
|
1898
|
+
if (stat2.isSymbolicLink()) {
|
|
1899
|
+
if (remaining.length === 0) {
|
|
1900
|
+
return { path: join2(realpathSync(dirname3(current)), basename(current)) };
|
|
1901
|
+
}
|
|
1902
|
+
return { path: resolve(realpathSync(current), ...remaining) };
|
|
1903
|
+
}
|
|
1904
|
+
return { path: resolve(realpathSync(current), ...remaining) };
|
|
1905
|
+
} catch (error2) {
|
|
1906
|
+
const code = getErrorCode(error2);
|
|
1907
|
+
if (code !== "ENOENT" && code !== "ENOTDIR") {
|
|
1908
|
+
return resolveNearestAncestor(current, remaining, error2);
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
if (current === dirname3(current)) return null;
|
|
1912
|
+
remaining.unshift(basename(current));
|
|
1913
|
+
current = dirname3(current);
|
|
1914
|
+
}
|
|
1915
|
+
return null;
|
|
1916
|
+
}
|
|
1917
|
+
function tokenise(absolutePath, roots) {
|
|
1918
|
+
const pathResolution = resolveExistingPath(absolutePath);
|
|
1919
|
+
if (!pathResolution) return null;
|
|
1920
|
+
const root = roots.reduce((selected, candidate) => {
|
|
1921
|
+
if (!isWithin(pathResolution.path, candidate.real)) return selected;
|
|
1922
|
+
if (!selected || candidate.real.length > selected.real.length || candidate.real.length === selected.real.length && candidate.token === "$WORKSPACE") {
|
|
1923
|
+
return candidate;
|
|
1924
|
+
}
|
|
1925
|
+
return selected;
|
|
1926
|
+
}, null);
|
|
1927
|
+
if (!root) return null;
|
|
1928
|
+
const suffix = relative(root.real, pathResolution.path);
|
|
1929
|
+
return {
|
|
1930
|
+
value: suffix ? `${root.token}/${suffix.split(sep).join("/")}` : root.token,
|
|
1931
|
+
root,
|
|
1932
|
+
resolutionFailure: pathResolution.failure
|
|
1933
|
+
};
|
|
1934
|
+
}
|
|
1935
|
+
function classifyScope(dir, roots) {
|
|
1936
|
+
if (!dir) return "other";
|
|
1937
|
+
const tokenisedDir = tokenise(dir, roots.resolved);
|
|
1938
|
+
if (tokenisedDir) return tokenisedDir.root.token === "$WORKSPACE" ? "workspace" : "global";
|
|
1939
|
+
for (const spec of roots.unresolved) {
|
|
1940
|
+
if (isWithin(dir, spec.configured)) {
|
|
1941
|
+
return spec.token === "$WORKSPACE" ? "workspace" : "global";
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
return "other";
|
|
1945
|
+
}
|
|
1946
|
+
function unresolvedRootForPath(path, unresolved) {
|
|
1947
|
+
return unresolved.find((spec) => isWithin(path, spec.configured));
|
|
1948
|
+
}
|
|
1949
|
+
function statType(stat2) {
|
|
1950
|
+
if (stat2.isDirectory()) return "d";
|
|
1951
|
+
if (stat2.isFile()) return "f";
|
|
1952
|
+
if (stat2.isSymbolicLink()) return "l";
|
|
1953
|
+
return "o";
|
|
1954
|
+
}
|
|
1955
|
+
function renderStat(name, stat2) {
|
|
1956
|
+
const mode = (stat2.mode & 4095).toString(8).padStart(4, "0");
|
|
1957
|
+
return `${name}:${statType(stat2)}:${stat2.uid}:${stat2.gid}:${mode}`;
|
|
1958
|
+
}
|
|
1959
|
+
function renderStatError(name, error2) {
|
|
1960
|
+
const code = getErrorCode(error2);
|
|
1961
|
+
return `${name}:${renderErrorCode(code)}`;
|
|
1962
|
+
}
|
|
1963
|
+
function renderErrorCode(code) {
|
|
1964
|
+
if (code === "ENOENT") return "missing";
|
|
1965
|
+
return code.startsWith("E") ? code : `E${code}`;
|
|
1966
|
+
}
|
|
1967
|
+
function inspectChain(rootReal, rootToken, absolutePath, resolutionFailure) {
|
|
1968
|
+
const target = resolve(absolutePath);
|
|
1969
|
+
const relativePath = relative(rootReal, target);
|
|
1970
|
+
const components = relativePath ? relativePath.split(sep) : [];
|
|
1971
|
+
const entries = [];
|
|
1972
|
+
let current = rootReal;
|
|
1973
|
+
let status2 = "complete";
|
|
1974
|
+
let finalStat;
|
|
1975
|
+
for (const name of [rootToken, ...components]) {
|
|
1976
|
+
if (name !== rootToken) current = join2(current, name);
|
|
1977
|
+
if (resolutionFailure && resolve(current) === resolve(resolutionFailure.path)) {
|
|
1978
|
+
entries.push(renderStatError(name, resolutionFailure.error));
|
|
1979
|
+
status2 = "stat_failed";
|
|
1980
|
+
break;
|
|
1981
|
+
}
|
|
1982
|
+
try {
|
|
1983
|
+
const stat2 = lstatSync(current);
|
|
1984
|
+
entries.push(renderStat(name, stat2));
|
|
1985
|
+
finalStat = stat2;
|
|
1986
|
+
} catch (error2) {
|
|
1987
|
+
entries.push(renderStatError(name, error2));
|
|
1988
|
+
if (getErrorCode(error2) !== "ENOENT") status2 = "stat_failed";
|
|
1989
|
+
break;
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
let symlinkTarget;
|
|
1993
|
+
let symlinkMeta;
|
|
1994
|
+
if (finalStat?.isSymbolicLink() && entries.length === components.length + 1) {
|
|
1995
|
+
try {
|
|
1996
|
+
symlinkTarget = realpathSync(current);
|
|
1997
|
+
try {
|
|
1998
|
+
symlinkMeta = renderStat("", lstatSync(symlinkTarget)).slice(1);
|
|
1999
|
+
} catch (error2) {
|
|
2000
|
+
symlinkMeta = renderErrorCode(getErrorCode(error2));
|
|
2001
|
+
status2 = "stat_failed";
|
|
2002
|
+
}
|
|
2003
|
+
} catch (error2) {
|
|
2004
|
+
symlinkTarget = "<unresolved>";
|
|
2005
|
+
symlinkMeta = renderErrorCode(getErrorCode(error2));
|
|
2006
|
+
status2 = "stat_failed";
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
2009
|
+
return { status: status2, chain: entries.join(","), symlinkTarget, symlinkMeta };
|
|
2010
|
+
}
|
|
2011
|
+
function buildDiagnostic(line, context) {
|
|
2012
|
+
const trigger = parseTrigger(line);
|
|
2013
|
+
if (!trigger) return null;
|
|
2014
|
+
const roots = resolveRoots(context);
|
|
2015
|
+
const scope = classifyScope(trigger.dir, roots);
|
|
2016
|
+
const pathSource = trigger.path ? "error" : trigger.dir ? "dir" : "none";
|
|
2017
|
+
const inspectedPath = trigger.path ?? trigger.dir;
|
|
2018
|
+
const microvmId = context.microvmId?.trim() || "unknown";
|
|
2019
|
+
const euid = context.euid === void 0 ? "unavailable" : String(context.euid);
|
|
2020
|
+
const egid = context.egid === void 0 ? "unavailable" : String(context.egid);
|
|
2021
|
+
const op = trigger.op ?? "unknown";
|
|
2022
|
+
if (!inspectedPath) {
|
|
2023
|
+
return {
|
|
2024
|
+
event: `OPENCODE-DEPENDENCY-INSTALL-EACCES: status=no_path path_source=${pathSource} scope=${scope} microvm_id=${microvmId} op=${op} path=unknown cli_euid=${euid} cli_egid=${egid} effect="${EFFECT}" chain=unavailable`,
|
|
2025
|
+
localDetail: null
|
|
2026
|
+
};
|
|
2027
|
+
}
|
|
2028
|
+
const tokenisedPath = tokenise(inspectedPath, roots.resolved);
|
|
2029
|
+
if (!tokenisedPath) {
|
|
2030
|
+
const unresolvedRoot = unresolvedRootForPath(inspectedPath, roots.unresolved);
|
|
2031
|
+
if (unresolvedRoot) {
|
|
2032
|
+
return {
|
|
2033
|
+
event: `OPENCODE-DEPENDENCY-INSTALL-EACCES: status=root_unresolved path_source=${pathSource} scope=${scope} microvm_id=${microvmId} op=${op} path=unknown cli_euid=${euid} cli_egid=${egid} effect="${EFFECT}" chain=unavailable`,
|
|
2034
|
+
localDetail: null
|
|
2035
|
+
};
|
|
2036
|
+
}
|
|
2037
|
+
return {
|
|
2038
|
+
event: `OPENCODE-DEPENDENCY-INSTALL-EACCES: status=outside_roots path_source=${pathSource} scope=${scope} microvm_id=${microvmId} op=${op} path=<outside-approved-roots> cli_euid=${euid} cli_egid=${egid} effect="${EFFECT}" chain=unavailable`,
|
|
2039
|
+
localDetail: `OPENCODE-DEPENDENCY-INSTALL-EACCES (local only): outside approved roots: ${inspectedPath}`
|
|
2040
|
+
};
|
|
2041
|
+
}
|
|
2042
|
+
const inspection = inspectChain(
|
|
2043
|
+
tokenisedPath.root.real,
|
|
2044
|
+
tokenisedPath.root.token,
|
|
2045
|
+
inspectedPath,
|
|
2046
|
+
tokenisedPath.resolutionFailure
|
|
2047
|
+
);
|
|
2048
|
+
const optionalSymlinkFields = inspection.symlinkTarget ? ` symlink_target=${tokenise(inspection.symlinkTarget, roots.resolved)?.value ?? "<outside-approved-roots>"}` + (inspection.symlinkMeta ? ` symlink_meta=${inspection.symlinkMeta}` : "") : "";
|
|
2049
|
+
return {
|
|
2050
|
+
event: `OPENCODE-DEPENDENCY-INSTALL-EACCES: status=${inspection.status} path_source=${pathSource} scope=${scope} microvm_id=${microvmId} op=${op} path=${tokenisedPath.value} cli_euid=${euid} cli_egid=${egid} effect="${EFFECT}" chain=${inspection.chain}${optionalSymlinkFields}`,
|
|
2051
|
+
localDetail: null
|
|
2052
|
+
};
|
|
2053
|
+
}
|
|
2054
|
+
function createDependencyInstallDiagnosticReporter({
|
|
2055
|
+
context,
|
|
2056
|
+
emitEvent: emitEvent2,
|
|
2057
|
+
emitLocal
|
|
2058
|
+
}) {
|
|
2059
|
+
const emittedEvents = /* @__PURE__ */ new Set();
|
|
2060
|
+
return (line) => {
|
|
2061
|
+
const diagnostic = buildDiagnostic(line, context);
|
|
2062
|
+
if (!diagnostic || emittedEvents.has(diagnostic.event)) return;
|
|
2063
|
+
if (emittedEvents.size >= MAX_DISTINCT_DIAGNOSTICS) return;
|
|
2064
|
+
emittedEvents.add(diagnostic.event);
|
|
2065
|
+
emitEvent2(diagnostic.event);
|
|
2066
|
+
if (diagnostic.localDetail) emitLocal(diagnostic.localDetail);
|
|
2067
|
+
};
|
|
2068
|
+
}
|
|
2069
|
+
|
|
1700
2070
|
// src/lib/runner-activity-telemetry.ts
|
|
1701
2071
|
var FORWARDED_LEVELS_BY_SOURCE = {
|
|
1702
2072
|
"cli.run": /* @__PURE__ */ new Set(["warn", "error"]),
|
|
@@ -1728,7 +2098,17 @@ function parseOpenCodeLogLine(line) {
|
|
|
1728
2098
|
}
|
|
1729
2099
|
var MAX_LINE_BUFFER_BYTES = 16 * 1024;
|
|
1730
2100
|
var REPEAT_SUPPRESSION_WINDOW_MS = 6e4;
|
|
1731
|
-
function
|
|
2101
|
+
function sanitiseDependencyInstallWarning(line) {
|
|
2102
|
+
const withoutDirectory = line.replace(
|
|
2103
|
+
/((?:^|\s)dir=)(?:"(?:\\.|[^"])*"|\/[^\s)"']+|\S+)/g,
|
|
2104
|
+
"$1<path>"
|
|
2105
|
+
);
|
|
2106
|
+
return withoutDirectory.replace(
|
|
2107
|
+
/((?:^|\s)error=)"((?:\\.|[^"])*)"/,
|
|
2108
|
+
(_match, prefix, error2) => `${prefix}"${error2.replace(/'\/[^']*'/g, "'<path>'")}"`
|
|
2109
|
+
);
|
|
2110
|
+
}
|
|
2111
|
+
function createOpenCodeActivityForwarder(getContext, onSessionStoreWriteFailure, onDependencyInstallFailure) {
|
|
1732
2112
|
let buffer = Buffer.alloc(0);
|
|
1733
2113
|
const repeatStates = /* @__PURE__ */ new Map();
|
|
1734
2114
|
const forwardLine = (line, parsed, repeatsSuppressed = 0) => {
|
|
@@ -1740,7 +2120,7 @@ function createOpenCodeActivityForwarder(getContext, onSessionStoreWriteFailure)
|
|
|
1740
2120
|
forwardRunnerActivity(
|
|
1741
2121
|
{
|
|
1742
2122
|
level: parsed.level,
|
|
1743
|
-
error: line,
|
|
2123
|
+
error: parsed.message === DEPENDENCY_INSTALL_FAILURE_MESSAGE ? sanitiseDependencyInstallWarning(line) : line,
|
|
1744
2124
|
metadata: Object.keys(metadata).length > 0 ? metadata : void 0,
|
|
1745
2125
|
source: "opencode"
|
|
1746
2126
|
},
|
|
@@ -1758,18 +2138,7 @@ function createOpenCodeActivityForwarder(getContext, onSessionStoreWriteFailure)
|
|
|
1758
2138
|
repeatStates.delete(key);
|
|
1759
2139
|
}
|
|
1760
2140
|
};
|
|
1761
|
-
const
|
|
1762
|
-
if (onSessionStoreWriteFailure && isSessionStoreWriteFailure(line)) {
|
|
1763
|
-
try {
|
|
1764
|
-
onSessionStoreWriteFailure();
|
|
1765
|
-
} catch (err) {
|
|
1766
|
-
reportTelemetryDiagnostic(
|
|
1767
|
-
`Runner activity telemetry could not record session-store write failure: ${err instanceof Error ? err.message : String(err)}`
|
|
1768
|
-
);
|
|
1769
|
-
}
|
|
1770
|
-
}
|
|
1771
|
-
const parsed = parseOpenCodeLogLine(line);
|
|
1772
|
-
if (!parsed) return;
|
|
2141
|
+
const forwardWithRepeatSuppression = (line, parsed) => {
|
|
1773
2142
|
const now = Date.now();
|
|
1774
2143
|
const key = parsed.message === void 0 ? void 0 : `${parsed.level}\0${parsed.message}\0${parsed.sessionID ?? ""}\0${parsed.messageID ?? ""}`;
|
|
1775
2144
|
expireRepeatStates(now, key);
|
|
@@ -1792,6 +2161,35 @@ function createOpenCodeActivityForwarder(getContext, onSessionStoreWriteFailure)
|
|
|
1792
2161
|
previous.parsed = parsed;
|
|
1793
2162
|
previous.repeatsSuppressed++;
|
|
1794
2163
|
};
|
|
2164
|
+
const flushLine = (line) => {
|
|
2165
|
+
const parsed = parseOpenCodeLogLine(line);
|
|
2166
|
+
const isDiskFailure = isSessionStoreWriteFailure(line);
|
|
2167
|
+
const cause = sessionStoreCauseToken(line);
|
|
2168
|
+
if (onSessionStoreWriteFailure && (isDiskFailure || cause !== null)) {
|
|
2169
|
+
try {
|
|
2170
|
+
onSessionStoreWriteFailure({
|
|
2171
|
+
isDiskFailure,
|
|
2172
|
+
sessionId: parsed?.sessionID ?? null,
|
|
2173
|
+
cause
|
|
2174
|
+
});
|
|
2175
|
+
} catch (err) {
|
|
2176
|
+
reportTelemetryDiagnostic(
|
|
2177
|
+
`Runner activity telemetry could not record session-store write failure: ${err instanceof Error ? err.message : String(err)}`
|
|
2178
|
+
);
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2181
|
+
if (!parsed) return;
|
|
2182
|
+
forwardWithRepeatSuppression(line, parsed);
|
|
2183
|
+
if (onDependencyInstallFailure && parsed.message === DEPENDENCY_INSTALL_FAILURE_MESSAGE) {
|
|
2184
|
+
try {
|
|
2185
|
+
onDependencyInstallFailure(line);
|
|
2186
|
+
} catch (err) {
|
|
2187
|
+
reportTelemetryDiagnostic(
|
|
2188
|
+
`Runner activity telemetry could not record dependency-install failure diagnostic: ${err instanceof Error ? err.message : String(err)}`
|
|
2189
|
+
);
|
|
2190
|
+
}
|
|
2191
|
+
}
|
|
2192
|
+
};
|
|
1795
2193
|
const forward = ((chunk) => {
|
|
1796
2194
|
buffer = Buffer.concat([buffer, Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, "utf-8")]);
|
|
1797
2195
|
let newlineIndex;
|
|
@@ -1817,34 +2215,38 @@ function createOpenCodeActivityForwarder(getContext, onSessionStoreWriteFailure)
|
|
|
1817
2215
|
var MAX_MESSAGE_LENGTH = 500;
|
|
1818
2216
|
var MAX_METADATA_VALUE_LENGTH = 200;
|
|
1819
2217
|
var MAX_METADATA_ENTRIES = 20;
|
|
1820
|
-
var TRUNCATION_MARKER = "\u2026";
|
|
1821
|
-
function redactHomeDirectory(message) {
|
|
1822
|
-
const home = homedir2();
|
|
1823
|
-
if (!home || home === "/") {
|
|
1824
|
-
return message;
|
|
1825
|
-
}
|
|
1826
|
-
const pathSegmentCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._-";
|
|
1827
|
-
let result = "";
|
|
1828
|
-
let searchFrom = 0;
|
|
1829
|
-
let homeIndex;
|
|
1830
|
-
while ((homeIndex = message.indexOf(home, searchFrom)) !== -1) {
|
|
1831
|
-
const homeEnd = homeIndex + home.length;
|
|
1832
|
-
const previousCharacter = message[homeIndex - 1];
|
|
1833
|
-
const nextCharacter = message[homeEnd];
|
|
1834
|
-
const atPathBoundary = home === "/" || (!previousCharacter || !pathSegmentCharacters.includes(previousCharacter)) && (!nextCharacter || !pathSegmentCharacters.includes(nextCharacter));
|
|
1835
|
-
result += message.slice(searchFrom, homeIndex);
|
|
1836
|
-
result += atPathBoundary ? "~" : home;
|
|
1837
|
-
searchFrom = homeEnd;
|
|
1838
|
-
}
|
|
1839
|
-
return result + message.slice(searchFrom);
|
|
1840
|
-
}
|
|
1841
|
-
function redactDiagnosticText(message) {
|
|
1842
|
-
const redacted = message.replace(/esk_[A-Za-z0-9_-]+/g, "esk_***").replace(/ct_[A-Za-z0-9_-]+/g, "ct_***").replace(/(?<![A-Za-z0-9_-])sk-ant-[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g, "sk-ant-***").replace(/(?<![A-Za-z0-9_-])sk-proj-[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g, "sk-proj-***").replace(/(?<![A-Za-z0-9_-])sk-[A-Za-z0-9]{20,}(?![A-Za-z0-9_-])/g, "sk-***").replace(/(?<![A-Za-z0-9_-])(gh[oprsu])_[A-Za-z0-9]{20,}(?![A-Za-z0-9_-])/g, "$1_***").replace(/(?<![A-Za-z0-9_-])github_pat_[A-Za-z0-9_]{20,}(?![A-Za-z0-9_-])/g, "github_pat_***").replace(/https?:\/\/\S+/g, "<url>");
|
|
1843
|
-
return redactHomeDirectory(redacted);
|
|
1844
|
-
}
|
|
1845
2218
|
function truncate(message) {
|
|
1846
2219
|
if (message.length <= MAX_MESSAGE_LENGTH) return message;
|
|
1847
|
-
|
|
2220
|
+
let dropped = message.length - MAX_MESSAGE_LENGTH;
|
|
2221
|
+
let marker = `\u2026${dropped} chars omitted\u2026`;
|
|
2222
|
+
let previousDropped = -1;
|
|
2223
|
+
while (dropped !== previousDropped) {
|
|
2224
|
+
previousDropped = dropped;
|
|
2225
|
+
const nextDropped = message.length - MAX_MESSAGE_LENGTH + marker.length;
|
|
2226
|
+
dropped = nextDropped;
|
|
2227
|
+
marker = `\u2026${dropped} chars omitted\u2026`;
|
|
2228
|
+
}
|
|
2229
|
+
const remaining = MAX_MESSAGE_LENGTH - marker.length;
|
|
2230
|
+
const headLength = Math.ceil(remaining * 0.6);
|
|
2231
|
+
const tailLength = remaining - headLength;
|
|
2232
|
+
const effectMatch = message.startsWith("OPENCODE-DEPENDENCY-INSTALL-EACCES:") ? message.match(/ effect="[^"]*"/) : null;
|
|
2233
|
+
if (effectMatch?.index !== void 0) {
|
|
2234
|
+
const effectEnd = effectMatch.index + effectMatch[0].length;
|
|
2235
|
+
if (effectEnd > headLength && effectEnd < message.length) {
|
|
2236
|
+
const effectLength = effectMatch[0].length;
|
|
2237
|
+
const nonEffectLength = remaining - effectLength;
|
|
2238
|
+
const preservedTailLength = Math.min(
|
|
2239
|
+
message.length - effectEnd,
|
|
2240
|
+
nonEffectLength - Math.min(effectMatch.index, Math.ceil(nonEffectLength * 0.6))
|
|
2241
|
+
);
|
|
2242
|
+
const preservedHeadLength = Math.min(
|
|
2243
|
+
effectMatch.index,
|
|
2244
|
+
nonEffectLength - preservedTailLength
|
|
2245
|
+
);
|
|
2246
|
+
return message.slice(0, preservedHeadLength) + marker + message.slice(effectMatch.index, effectEnd) + message.slice(message.length - preservedTailLength);
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2249
|
+
return message.slice(0, headLength) + marker + message.slice(message.length - tailLength);
|
|
1848
2250
|
}
|
|
1849
2251
|
function sanitiseMetadata(metadata) {
|
|
1850
2252
|
if (!metadata) return {};
|
|
@@ -1921,10 +2323,10 @@ function forwardRunnerActivity(entry, context) {
|
|
|
1921
2323
|
|
|
1922
2324
|
// src/lib/opencode/session-db-recovery-report.ts
|
|
1923
2325
|
import { readFileSync as readFileSync2, unlinkSync } from "node:fs";
|
|
1924
|
-
import { join as
|
|
2326
|
+
import { join as join3 } from "node:path";
|
|
1925
2327
|
function sessionDbRecoveryReportPath(homeDir, env) {
|
|
1926
2328
|
const override = env.EVIDENT_SESSION_DB_RECOVERY_REPORT?.trim();
|
|
1927
|
-
return override ||
|
|
2329
|
+
return override || join3(homeDir, ".local", "state", "evident", "session-db-recovery.jsonl");
|
|
1928
2330
|
}
|
|
1929
2331
|
function drainSessionDbRecoveryReport({
|
|
1930
2332
|
homeDir,
|
|
@@ -2054,12 +2456,23 @@ function buildSessionDbRecoveryActivity(record) {
|
|
|
2054
2456
|
metadata: withoutContractFields(record),
|
|
2055
2457
|
message: `This runner started without restoring session history because it could not verify where its session history is backed up. Nothing in the backup was changed; check the runner backup configuration and permissions.${replication}`
|
|
2056
2458
|
};
|
|
2057
|
-
case "session_db_boot_refused":
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2459
|
+
case "session_db_boot_refused": {
|
|
2460
|
+
const message = (() => {
|
|
2461
|
+
switch (record.reason) {
|
|
2462
|
+
case "local_discard_failed":
|
|
2463
|
+
return `This runner did not come online because its damaged session database could not be safely separated from its active backup or proven removed. Backed-up session history remains readable at ${record.quarantine_destination ?? "its original location or the quarantine destination named in the boot logs"}; any local session-database files that remain were left in place and nothing opened or wrote them. See the runner boot logs for SESSION-DB-LOCAL-DISCARD-FAILED details.`;
|
|
2464
|
+
case "replica_separation_unproven":
|
|
2465
|
+
return `This runner did not come online because its damaged session database could not be safely separated from its active backup. The SESSION-DB-REPLICA-SEPARATION-UNVERIFIED boot log contains the partial-quarantine moved and remaining counts. Backed-up session history remains readable at ${record.quarantine_destination ?? "its original location or the quarantine destination named in the boot logs"}; any local session-database files that remain were left in place and nothing opened or wrote them.`;
|
|
2466
|
+
case "verification_timeout_before_separation":
|
|
2467
|
+
return "This runner did not come online because session database verification timed out after detecting corruption, before replica separation was attempted. No replica progress is claimed. See SESSION-DB-VERIFY-TIMEOUT in the runner boot logs before retrying.";
|
|
2468
|
+
case "verification_timeout_during_separation":
|
|
2469
|
+
return "This runner did not come online because session database verification timed out while replica separation was in progress. Separation completion and object removal were not proven. See SESSION-DB-VERIFY-TIMEOUT in the runner boot logs before retrying.";
|
|
2470
|
+
default:
|
|
2471
|
+
return "This runner did not come online because session database recovery could not be completed. Inspect the runner boot logs for the refusal reason before retrying.";
|
|
2472
|
+
}
|
|
2473
|
+
})();
|
|
2474
|
+
return { level, metadata: withoutContractFields(record), message };
|
|
2475
|
+
}
|
|
2063
2476
|
case "schema_provenance_mismatch":
|
|
2064
2477
|
return {
|
|
2065
2478
|
level,
|
|
@@ -2186,7 +2599,7 @@ async function waitForOpenCodeHealth(port, timeoutMs = 3e4) {
|
|
|
2186
2599
|
if (health.healthy || health.authFailed) {
|
|
2187
2600
|
return health;
|
|
2188
2601
|
}
|
|
2189
|
-
await new Promise((
|
|
2602
|
+
await new Promise((resolve5) => setTimeout(resolve5, 1e3));
|
|
2190
2603
|
}
|
|
2191
2604
|
return { healthy: false, error: "Timeout waiting for OpenCode to be healthy" };
|
|
2192
2605
|
}
|
|
@@ -2197,7 +2610,7 @@ async function waitForOpenCode2Health(port, password, timeoutMs = 3e4) {
|
|
|
2197
2610
|
if (health.healthy || health.authFailed) {
|
|
2198
2611
|
return health;
|
|
2199
2612
|
}
|
|
2200
|
-
await new Promise((
|
|
2613
|
+
await new Promise((resolve5) => setTimeout(resolve5, 1e3));
|
|
2201
2614
|
}
|
|
2202
2615
|
return { healthy: false, error: "Timeout waiting for OpenCode to be healthy" };
|
|
2203
2616
|
}
|
|
@@ -2464,7 +2877,18 @@ async function deleteSession(port, id, client) {
|
|
|
2464
2877
|
{ method: "DELETE" },
|
|
2465
2878
|
{ allowStatuses: ALL_HTTP_STATUSES }
|
|
2466
2879
|
);
|
|
2467
|
-
|
|
2880
|
+
if (res.status >= 200 && res.status < 300) {
|
|
2881
|
+
return { ok: true, status: res.status };
|
|
2882
|
+
}
|
|
2883
|
+
try {
|
|
2884
|
+
const body = await res.text();
|
|
2885
|
+
return body ? { ok: false, status: res.status, body } : { ok: false, status: res.status };
|
|
2886
|
+
} catch (error2) {
|
|
2887
|
+
console.error(
|
|
2888
|
+
`[deleteSession] DELETE /session/${id} response body read failed for HTTP ${res.status}: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
2889
|
+
);
|
|
2890
|
+
return { ok: false, status: res.status };
|
|
2891
|
+
}
|
|
2468
2892
|
} catch (error2) {
|
|
2469
2893
|
console.error(
|
|
2470
2894
|
`[deleteSession] DELETE /session/${id} failed: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
@@ -2776,7 +3200,7 @@ async function sendPromptAsync(port, sessionId, content, options, attachments, c
|
|
|
2776
3200
|
return dispatchedUserMessageId;
|
|
2777
3201
|
}
|
|
2778
3202
|
if (attempt < READ_BACK_ATTEMPTS - 1) {
|
|
2779
|
-
await new Promise((
|
|
3203
|
+
await new Promise((resolve5) => setTimeout(resolve5, READ_BACK_DELAY_MS));
|
|
2780
3204
|
}
|
|
2781
3205
|
}
|
|
2782
3206
|
return null;
|
|
@@ -2948,9 +3372,11 @@ function sumAssistantUsage(messages) {
|
|
|
2948
3372
|
let cacheWriteSum = 0;
|
|
2949
3373
|
let costSum = 0;
|
|
2950
3374
|
let sawCost = false;
|
|
3375
|
+
let toolCallCount = 0;
|
|
2951
3376
|
let modelId = null;
|
|
2952
3377
|
let providerId = null;
|
|
2953
3378
|
for (const m of selected) {
|
|
3379
|
+
toolCallCount += m.parts?.filter((part) => part.type === "tool").length ?? 0;
|
|
2954
3380
|
const info = m.info;
|
|
2955
3381
|
if (!info) continue;
|
|
2956
3382
|
const tokens = info.tokens;
|
|
@@ -2987,7 +3413,8 @@ function sumAssistantUsage(messages) {
|
|
|
2987
3413
|
usage_tokens_cache_write: cacheWriteSum,
|
|
2988
3414
|
// NULL means OpenCode never reported a cost; it is distinct from a genuine
|
|
2989
3415
|
// zero-cost message, which sets `sawCost` with `costSum === 0`.
|
|
2990
|
-
usage_cost_usd: sawCost ? costSum : null
|
|
3416
|
+
usage_cost_usd: sawCost ? costSum : null,
|
|
3417
|
+
turn_tool_calls: toolCallCount
|
|
2991
3418
|
};
|
|
2992
3419
|
}
|
|
2993
3420
|
function messageUsage(messages, userMessageId) {
|
|
@@ -3064,6 +3491,18 @@ function messageError(messages, userMessageId) {
|
|
|
3064
3491
|
) : void 0;
|
|
3065
3492
|
return typeof toolPart?.tool === "string" && toolPart.tool.length > 0 ? `${rendered} (tool "${toolPart.tool}")` : rendered;
|
|
3066
3493
|
}
|
|
3494
|
+
function describeAbandonedReplyShape(messages, userMessageId) {
|
|
3495
|
+
const reply = findLastAssistantReplyFor(messages, userMessageId);
|
|
3496
|
+
if (!reply) return null;
|
|
3497
|
+
const parts = Array.isArray(reply.parts) ? reply.parts : [];
|
|
3498
|
+
const lastTool = [...parts].reverse().find((part) => part?.type === "tool");
|
|
3499
|
+
return {
|
|
3500
|
+
partTypes: parts.slice(-12).map((part) => typeof part?.type === "string" ? part.type : "(unknown)"),
|
|
3501
|
+
droppedPartCount: Math.max(0, parts.length - 12),
|
|
3502
|
+
lastToolName: typeof lastTool?.tool === "string" && lastTool.tool.length > 0 ? lastTool.tool : null,
|
|
3503
|
+
lastToolStatus: typeof lastTool?.state?.status === "string" && lastTool.state.status.length > 0 ? lastTool.state.status : null
|
|
3504
|
+
};
|
|
3505
|
+
}
|
|
3067
3506
|
function isAbortedTerminalReply(messages, userMessageId) {
|
|
3068
3507
|
const reply = findLastAssistantReplyFor(messages, userMessageId);
|
|
3069
3508
|
const error2 = errorOf(reply);
|
|
@@ -3637,7 +4076,18 @@ async function deleteV2Session(client, sessionId) {
|
|
|
3637
4076
|
{ method: "DELETE" },
|
|
3638
4077
|
{ allowStatuses: Array.from({ length: 500 }, (_, index) => index + 100) }
|
|
3639
4078
|
);
|
|
3640
|
-
|
|
4079
|
+
if (response.status >= 200 && response.status < 300) {
|
|
4080
|
+
return { ok: true, status: response.status };
|
|
4081
|
+
}
|
|
4082
|
+
try {
|
|
4083
|
+
const body = await response.text();
|
|
4084
|
+
return body ? { ok: false, status: response.status, body } : { ok: false, status: response.status };
|
|
4085
|
+
} catch (error2) {
|
|
4086
|
+
console.error(
|
|
4087
|
+
`[deleteV2Session] DELETE /api/session/${sessionId} response body read failed for HTTP ${response.status}: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
4088
|
+
);
|
|
4089
|
+
return { ok: false, status: response.status };
|
|
4090
|
+
}
|
|
3641
4091
|
} catch (error2) {
|
|
3642
4092
|
console.error(
|
|
3643
4093
|
`[deleteV2Session] DELETE /api/session/${sessionId} failed: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
@@ -3813,12 +4263,12 @@ async function readV2SessionErrorStream(client, options) {
|
|
|
3813
4263
|
|
|
3814
4264
|
// src/lib/opencode/credential-import-v2.ts
|
|
3815
4265
|
import { readFileSync as readFileSync4 } from "node:fs";
|
|
3816
|
-
import { join as
|
|
4266
|
+
import { join as join5 } from "node:path";
|
|
3817
4267
|
|
|
3818
4268
|
// src/lib/openai-usage.ts
|
|
3819
4269
|
import { readFileSync as readFileSync3 } from "node:fs";
|
|
3820
4270
|
import { homedir as homedir3 } from "node:os";
|
|
3821
|
-
import { join as
|
|
4271
|
+
import { join as join4 } from "node:path";
|
|
3822
4272
|
var CODEX_RESPONSES_URL = "https://chatgpt.com/backend-api/codex/responses";
|
|
3823
4273
|
var OPENCODE_AUTH_SEGMENTS = [".local", "share", "opencode", "auth.json"];
|
|
3824
4274
|
var OpenAiUsageError = class extends Error {
|
|
@@ -3832,7 +4282,7 @@ function isLocalCredentialProblem2(err) {
|
|
|
3832
4282
|
}
|
|
3833
4283
|
function readOpenCodeChatGptCredentials() {
|
|
3834
4284
|
try {
|
|
3835
|
-
const raw = readFileSync3(
|
|
4285
|
+
const raw = readFileSync3(join4(homedir3(), ...OPENCODE_AUTH_SEGMENTS), "utf-8");
|
|
3836
4286
|
let parsed;
|
|
3837
4287
|
try {
|
|
3838
4288
|
parsed = JSON.parse(raw);
|
|
@@ -4058,13 +4508,13 @@ var INTEGRATION_LIST_RETRY_DELAY_MS = 1e3;
|
|
|
4058
4508
|
function isRecord3(value) {
|
|
4059
4509
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4060
4510
|
}
|
|
4061
|
-
var sleep2 = (milliseconds) => new Promise((
|
|
4511
|
+
var sleep2 = (milliseconds) => new Promise((resolve5) => setTimeout(resolve5, milliseconds));
|
|
4062
4512
|
function emptyResult(unsupported = [], outcome = "completed") {
|
|
4063
4513
|
return { outcome, imported: [], alreadyConnected: [], unsupported, failed: [] };
|
|
4064
4514
|
}
|
|
4065
4515
|
function readOpenCodeCredentials(homeDir, log3) {
|
|
4066
4516
|
try {
|
|
4067
|
-
const raw = readFileSync4(
|
|
4517
|
+
const raw = readFileSync4(join5(homeDir, ...OPENCODE_AUTH_SEGMENTS), "utf8");
|
|
4068
4518
|
let parsed;
|
|
4069
4519
|
try {
|
|
4070
4520
|
parsed = JSON.parse(raw);
|
|
@@ -4266,7 +4716,7 @@ async function importOpenCodeV2Credentials({
|
|
|
4266
4716
|
import { spawn as spawn2 } from "node:child_process";
|
|
4267
4717
|
import { mkdirSync, statSync as statSync3, unlinkSync as unlinkSync2, writeFileSync } from "node:fs";
|
|
4268
4718
|
import { homedir as homedir4 } from "node:os";
|
|
4269
|
-
import { dirname as
|
|
4719
|
+
import { dirname as dirname4, resolve as resolvePath } from "node:path";
|
|
4270
4720
|
|
|
4271
4721
|
// src/lib/runner-synchroniser.ts
|
|
4272
4722
|
import { spawn } from "node:child_process";
|
|
@@ -4276,7 +4726,7 @@ function appendError(stderr, error2) {
|
|
|
4276
4726
|
${message}`;
|
|
4277
4727
|
}
|
|
4278
4728
|
function runSynchroniser(args, opts) {
|
|
4279
|
-
return new Promise((
|
|
4729
|
+
return new Promise((resolve5) => {
|
|
4280
4730
|
let child;
|
|
4281
4731
|
let stdout = "";
|
|
4282
4732
|
let stderr = "";
|
|
@@ -4290,7 +4740,7 @@ function runSynchroniser(args, opts) {
|
|
|
4290
4740
|
if (timer.handle) clearTimeout(timer.handle);
|
|
4291
4741
|
if (abortListener) opts.signal?.removeEventListener("abort", abortListener);
|
|
4292
4742
|
if (spawnListener) child.removeListener("spawn", spawnListener);
|
|
4293
|
-
|
|
4743
|
+
resolve5(result);
|
|
4294
4744
|
};
|
|
4295
4745
|
try {
|
|
4296
4746
|
child = spawn("runner-synchroniser", args, {
|
|
@@ -4346,9 +4796,12 @@ function runSynchroniser(args, opts) {
|
|
|
4346
4796
|
|
|
4347
4797
|
// src/lib/opencode/session-db-boot.ts
|
|
4348
4798
|
var SESSION_DB_RESTORE_TIMEOUT_MS = 3e5;
|
|
4349
|
-
var SESSION_DB_VERIFY_TIMEOUT_MS =
|
|
4799
|
+
var SESSION_DB_VERIFY_TIMEOUT_MS = 36e4;
|
|
4350
4800
|
var SESSION_DB_SYNCHRONISER_TIMEOUT_MS = 12e4;
|
|
4351
|
-
var SESSION_DB_VERIFY_WALKBACK_BUDGET_SECONDS =
|
|
4801
|
+
var SESSION_DB_VERIFY_WALKBACK_BUDGET_SECONDS = 40;
|
|
4802
|
+
var SESSION_DB_VERIFY_RESTORE_POINTS_TIMEOUT_SECONDS = 30;
|
|
4803
|
+
var SESSION_DB_VERIFY_RESTORE_CANDIDATE_TIMEOUT_SECONDS = 120;
|
|
4804
|
+
var SESSION_DB_VERIFY_DISPOSAL_BUDGET_SECONDS = 90;
|
|
4352
4805
|
function commandError(result) {
|
|
4353
4806
|
return result.stderr.trim() || `process exited with code ${result.code ?? "null"}`;
|
|
4354
4807
|
}
|
|
@@ -4391,7 +4844,7 @@ function clearMarker(options) {
|
|
|
4391
4844
|
function markNoReplicate(options, message) {
|
|
4392
4845
|
if (options.noReplicateMarker) {
|
|
4393
4846
|
try {
|
|
4394
|
-
mkdirSync(
|
|
4847
|
+
mkdirSync(dirname4(options.noReplicateMarker), { recursive: true });
|
|
4395
4848
|
writeFileSync(options.noReplicateMarker, "");
|
|
4396
4849
|
} catch (error2) {
|
|
4397
4850
|
options.log(
|
|
@@ -4450,7 +4903,7 @@ function parseSynchroniserEnv(stdout) {
|
|
|
4450
4903
|
return values;
|
|
4451
4904
|
}
|
|
4452
4905
|
function runCommand(command, args, options) {
|
|
4453
|
-
return new Promise((
|
|
4906
|
+
return new Promise((resolve5) => {
|
|
4454
4907
|
let child;
|
|
4455
4908
|
let stdout = "";
|
|
4456
4909
|
let stderr = "";
|
|
@@ -4459,7 +4912,7 @@ function runCommand(command, args, options) {
|
|
|
4459
4912
|
if (settled) return;
|
|
4460
4913
|
settled = true;
|
|
4461
4914
|
if (timer) clearTimeout(timer);
|
|
4462
|
-
|
|
4915
|
+
resolve5(result);
|
|
4463
4916
|
};
|
|
4464
4917
|
try {
|
|
4465
4918
|
child = spawn2(command, args, {
|
|
@@ -4467,7 +4920,7 @@ function runCommand(command, args, options) {
|
|
|
4467
4920
|
stdio: ["ignore", "pipe", "pipe"]
|
|
4468
4921
|
});
|
|
4469
4922
|
} catch (error2) {
|
|
4470
|
-
|
|
4923
|
+
resolve5({
|
|
4471
4924
|
code: null,
|
|
4472
4925
|
stdout,
|
|
4473
4926
|
stderr: error2 instanceof Error ? error2.message : String(error2),
|
|
@@ -4546,7 +4999,7 @@ async function ensureLitestreamConfig(options, env) {
|
|
|
4546
4999
|
return null;
|
|
4547
5000
|
}
|
|
4548
5001
|
try {
|
|
4549
|
-
mkdirSync(
|
|
5002
|
+
mkdirSync(dirname4(configPath), { recursive: true });
|
|
4550
5003
|
writeFileSync(configPath, rendered.stdout);
|
|
4551
5004
|
} catch (error2) {
|
|
4552
5005
|
options.log(
|
|
@@ -4659,20 +5112,37 @@ async function verifySessionDb(options, configPath, env) {
|
|
|
4659
5112
|
timeoutMs: SESSION_DB_VERIFY_TIMEOUT_MS,
|
|
4660
5113
|
env: {
|
|
4661
5114
|
...env,
|
|
4662
|
-
// The synchroniser reads
|
|
4663
|
-
// 120_000, so the walkback gives up before the outer process bound.
|
|
5115
|
+
// The synchroniser reads all four values in SECONDS.
|
|
4664
5116
|
EVIDENT_SESSION_DB_WALKBACK_BUDGET_SECONDS: String(
|
|
4665
5117
|
SESSION_DB_VERIFY_WALKBACK_BUDGET_SECONDS
|
|
5118
|
+
),
|
|
5119
|
+
EVIDENT_SESSION_DB_RESTORE_POINTS_TIMEOUT_SECONDS: String(
|
|
5120
|
+
SESSION_DB_VERIFY_RESTORE_POINTS_TIMEOUT_SECONDS
|
|
5121
|
+
),
|
|
5122
|
+
EVIDENT_SESSION_DB_RESTORE_CANDIDATE_TIMEOUT_SECONDS: String(
|
|
5123
|
+
SESSION_DB_VERIFY_RESTORE_CANDIDATE_TIMEOUT_SECONDS
|
|
5124
|
+
),
|
|
5125
|
+
EVIDENT_SESSION_DB_DISPOSAL_BUDGET_SECONDS: String(
|
|
5126
|
+
SESSION_DB_VERIFY_DISPOSAL_BUDGET_SECONDS
|
|
4666
5127
|
)
|
|
4667
5128
|
}
|
|
4668
5129
|
});
|
|
4669
5130
|
logSynchroniserDiagnostics(result, options);
|
|
4670
5131
|
if (result.timedOut || result.code === 124 || result.code === 137) {
|
|
5132
|
+
if (result.stderr.includes("SESSION-DB-INTEGRITY-FAILED")) {
|
|
5133
|
+
const reason = result.stderr.includes("SESSION-DB-REPLICA-SIZE") ? "verification_timeout_during_separation" : "verification_timeout_before_separation";
|
|
5134
|
+
options.log(
|
|
5135
|
+
`SESSION-DB-VERIFY-TIMEOUT: verification did not finish within its ${SESSION_DB_VERIFY_TIMEOUT_MS}ms deadline; refusing to start with the restored opencode.db (${reason})`,
|
|
5136
|
+
"error"
|
|
5137
|
+
);
|
|
5138
|
+
reportRecord("verify", "session_db_boot_refused", reason, null, options);
|
|
5139
|
+
return { verifyFatal: true, verifyFatalReason: reason };
|
|
5140
|
+
}
|
|
4671
5141
|
options.log(
|
|
4672
5142
|
`SESSION-DB-VERIFY-TIMEOUT: verification did not finish within its ${SESSION_DB_VERIFY_TIMEOUT_MS}ms deadline; continuing with the restored opencode.db as-is, unverified`,
|
|
4673
5143
|
"warn"
|
|
4674
5144
|
);
|
|
4675
|
-
return false;
|
|
5145
|
+
return { verifyFatal: false };
|
|
4676
5146
|
}
|
|
4677
5147
|
if (result.code === 34) {
|
|
4678
5148
|
reportRecord(
|
|
@@ -4682,14 +5152,14 @@ async function verifySessionDb(options, configPath, env) {
|
|
|
4682
5152
|
null,
|
|
4683
5153
|
options
|
|
4684
5154
|
);
|
|
4685
|
-
return true;
|
|
5155
|
+
return { verifyFatal: true };
|
|
4686
5156
|
}
|
|
4687
5157
|
if (result.code === 33) {
|
|
4688
5158
|
options.log(
|
|
4689
5159
|
"SESSION-DB-INTEGRITY-EXHAUSTED: booting continues and litestream still replicates, starting an empty backup chain after the corrupt replica was separated.",
|
|
4690
5160
|
"warn"
|
|
4691
5161
|
);
|
|
4692
|
-
return false;
|
|
5162
|
+
return { verifyFatal: false };
|
|
4693
5163
|
}
|
|
4694
5164
|
if (result.code !== 0) {
|
|
4695
5165
|
options.log(
|
|
@@ -4697,13 +5167,13 @@ async function verifySessionDb(options, configPath, env) {
|
|
|
4697
5167
|
"warn"
|
|
4698
5168
|
);
|
|
4699
5169
|
}
|
|
4700
|
-
return false;
|
|
5170
|
+
return { verifyFatal: false };
|
|
4701
5171
|
}
|
|
4702
5172
|
options.log(
|
|
4703
5173
|
"skipping session-DB verification: this boot's session DB was not proven safe to replicate",
|
|
4704
5174
|
"debug"
|
|
4705
5175
|
);
|
|
4706
|
-
return false;
|
|
5176
|
+
return { verifyFatal: false };
|
|
4707
5177
|
}
|
|
4708
5178
|
function fileExists(path) {
|
|
4709
5179
|
try {
|
|
@@ -4790,13 +5260,13 @@ async function restoreAndVerifySessionDb(options) {
|
|
|
4790
5260
|
if (options.noReplicateMarker && fileExists(options.noReplicateMarker)) {
|
|
4791
5261
|
return { verifyFatal: false };
|
|
4792
5262
|
}
|
|
4793
|
-
return
|
|
5263
|
+
return verifySessionDb(options, configPath, env);
|
|
4794
5264
|
}
|
|
4795
5265
|
|
|
4796
5266
|
// src/lib/opencode/session-db-provenance.ts
|
|
4797
5267
|
import { createRequire } from "node:module";
|
|
4798
5268
|
import { mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "node:fs";
|
|
4799
|
-
import { dirname as
|
|
5269
|
+
import { dirname as dirname5, join as join6 } from "node:path";
|
|
4800
5270
|
var require2 = createRequire(import.meta.url);
|
|
4801
5271
|
function readSessionDbMigrationIds(dbPath) {
|
|
4802
5272
|
let db;
|
|
@@ -4833,7 +5303,7 @@ function readSessionDbMigrationIds(dbPath) {
|
|
|
4833
5303
|
}
|
|
4834
5304
|
function sessionDbProvenanceStatePath(homeDir, env) {
|
|
4835
5305
|
const override = env.EVIDENT_SESSION_DB_PROVENANCE_STATE?.trim();
|
|
4836
|
-
return override ||
|
|
5306
|
+
return override || join6(homeDir, ".local", "state", "evident", "session-db-provenance.json");
|
|
4837
5307
|
}
|
|
4838
5308
|
function loadSessionDbProvenanceState(path) {
|
|
4839
5309
|
let value;
|
|
@@ -4862,7 +5332,7 @@ function loadSessionDbProvenanceState(path) {
|
|
|
4862
5332
|
}
|
|
4863
5333
|
function saveSessionDbProvenanceState(path, state) {
|
|
4864
5334
|
try {
|
|
4865
|
-
mkdirSync2(
|
|
5335
|
+
mkdirSync2(dirname5(path), { recursive: true });
|
|
4866
5336
|
writeFileSync2(path, `${JSON.stringify(state, null, 2)}
|
|
4867
5337
|
`, "utf8");
|
|
4868
5338
|
} catch (error2) {
|
|
@@ -4953,7 +5423,7 @@ async function stopProcessAndWait(child, timeoutMs, sendTerm, sendKill) {
|
|
|
4953
5423
|
if (child.exitCode !== null || child.signalCode !== null) {
|
|
4954
5424
|
return { outcome: "exited", code: child.exitCode, signal: child.signalCode };
|
|
4955
5425
|
}
|
|
4956
|
-
return new Promise((
|
|
5426
|
+
return new Promise((resolve5, reject) => {
|
|
4957
5427
|
let forced = false;
|
|
4958
5428
|
let settled = false;
|
|
4959
5429
|
const timer = setTimeout(() => {
|
|
@@ -4973,7 +5443,7 @@ async function stopProcessAndWait(child, timeoutMs, sendTerm, sendKill) {
|
|
|
4973
5443
|
settled = true;
|
|
4974
5444
|
clearTimeout(timer);
|
|
4975
5445
|
child.removeListener("exit", onExit);
|
|
4976
|
-
|
|
5446
|
+
resolve5(result);
|
|
4977
5447
|
};
|
|
4978
5448
|
const fail = (error2) => {
|
|
4979
5449
|
if (settled) return;
|
|
@@ -5449,7 +5919,7 @@ function selectSessionsToDelete(sessions, opts) {
|
|
|
5449
5919
|
);
|
|
5450
5920
|
}
|
|
5451
5921
|
var DEFAULT_INTERVAL = "1h";
|
|
5452
|
-
function
|
|
5922
|
+
function resolve2(flag, envValue, fallback) {
|
|
5453
5923
|
return flag ?? envValue ?? fallback;
|
|
5454
5924
|
}
|
|
5455
5925
|
function parseMaxCount(input) {
|
|
@@ -5465,9 +5935,9 @@ function parseMaxCount(input) {
|
|
|
5465
5935
|
}
|
|
5466
5936
|
function resolveSessionCleanupConfig(flags, env = process.env) {
|
|
5467
5937
|
const warnings = [];
|
|
5468
|
-
const maxAgeRaw =
|
|
5469
|
-
const maxCountRaw =
|
|
5470
|
-
const intervalRaw =
|
|
5938
|
+
const maxAgeRaw = resolve2(flags.maxAge, env.EVIDENT_SESSION_CLEANUP_MAX_AGE);
|
|
5939
|
+
const maxCountRaw = resolve2(flags.maxCount, env.EVIDENT_SESSION_CLEANUP_MAX_COUNT);
|
|
5940
|
+
const intervalRaw = resolve2(
|
|
5471
5941
|
flags.interval,
|
|
5472
5942
|
env.EVIDENT_SESSION_CLEANUP_INTERVAL,
|
|
5473
5943
|
DEFAULT_INTERVAL
|
|
@@ -5507,10 +5977,10 @@ function resolveSessionCleanupConfig(flags, env = process.env) {
|
|
|
5507
5977
|
|
|
5508
5978
|
// src/lib/opencode/session-db-size.ts
|
|
5509
5979
|
import { statSync as statSync4 } from "node:fs";
|
|
5510
|
-
import { join as
|
|
5980
|
+
import { join as join7 } from "node:path";
|
|
5511
5981
|
var LARGE_DB_THRESHOLD_BYTES = 268435456;
|
|
5512
5982
|
function statSessionDbBytes(homeDir) {
|
|
5513
|
-
const dbPath =
|
|
5983
|
+
const dbPath = join7(homeDir, ".local", "share", "opencode", "opencode.db");
|
|
5514
5984
|
try {
|
|
5515
5985
|
return statSync4(dbPath).size;
|
|
5516
5986
|
} catch (err) {
|
|
@@ -5540,12 +6010,12 @@ function buildSessionStoreSizeWarning(input) {
|
|
|
5540
6010
|
// src/lib/opencode/log-tail.ts
|
|
5541
6011
|
import { statSync as statSync5 } from "node:fs";
|
|
5542
6012
|
import { homedir as homedir5 } from "node:os";
|
|
5543
|
-
import { join as
|
|
6013
|
+
import { join as join8 } from "node:path";
|
|
5544
6014
|
import { open as open2, stat } from "node:fs/promises";
|
|
5545
6015
|
var DEFAULT_POLL_INTERVAL_MS = 1e3;
|
|
5546
6016
|
function resolveOpenCodeLogPath(homeDir = homedir5(), env = process.env) {
|
|
5547
|
-
const dataDir = env.XDG_DATA_HOME ||
|
|
5548
|
-
return
|
|
6017
|
+
const dataDir = env.XDG_DATA_HOME || join8(homeDir, ".local", "share");
|
|
6018
|
+
return join8(dataDir, "opencode", "log", "opencode.log");
|
|
5549
6019
|
}
|
|
5550
6020
|
function isEnoent(error2) {
|
|
5551
6021
|
return error2?.code === "ENOENT";
|
|
@@ -5700,8 +6170,8 @@ var StreamForwarder = class {
|
|
|
5700
6170
|
pauseStream(sid, stream) {
|
|
5701
6171
|
if (!stream.paused) {
|
|
5702
6172
|
stream.paused = true;
|
|
5703
|
-
stream.pausePromise = new Promise((
|
|
5704
|
-
stream.resume =
|
|
6173
|
+
stream.pausePromise = new Promise((resolve5) => {
|
|
6174
|
+
stream.resume = resolve5;
|
|
5705
6175
|
});
|
|
5706
6176
|
}
|
|
5707
6177
|
if (stream.pauseTimer !== null) clearTimeout(stream.pauseTimer);
|
|
@@ -5768,12 +6238,12 @@ var StreamForwarder = class {
|
|
|
5768
6238
|
let endBody;
|
|
5769
6239
|
if (has_body) {
|
|
5770
6240
|
const chunks = [];
|
|
5771
|
-
bodyPromise = new Promise((
|
|
6241
|
+
bodyPromise = new Promise((resolve5) => {
|
|
5772
6242
|
pushBody = (buf) => {
|
|
5773
6243
|
chunks.push(buf);
|
|
5774
6244
|
};
|
|
5775
6245
|
endBody = () => {
|
|
5776
|
-
|
|
6246
|
+
resolve5(Buffer.concat(chunks));
|
|
5777
6247
|
};
|
|
5778
6248
|
});
|
|
5779
6249
|
}
|
|
@@ -5944,7 +6414,7 @@ function connectTunnel(options) {
|
|
|
5944
6414
|
} = options;
|
|
5945
6415
|
const tunnelUrl = getTunnelUrlConfig();
|
|
5946
6416
|
const url = `${tunnelUrl}/tunnel/${agentId}/connect`;
|
|
5947
|
-
return new Promise((
|
|
6417
|
+
return new Promise((resolve5, reject) => {
|
|
5948
6418
|
const ws = new WebSocket2(url, {
|
|
5949
6419
|
headers: {
|
|
5950
6420
|
Authorization: authHeader,
|
|
@@ -6053,7 +6523,7 @@ function connectTunnel(options) {
|
|
|
6053
6523
|
forwarder.setBinaryFramesSupported(message.binary_frames === true);
|
|
6054
6524
|
const connectedAgentId = message.agent_id ?? agentId;
|
|
6055
6525
|
onConnected?.(connectedAgentId);
|
|
6056
|
-
|
|
6526
|
+
resolve5({
|
|
6057
6527
|
ws,
|
|
6058
6528
|
close: () => ws.close(1e3, "CLI shutdown")
|
|
6059
6529
|
});
|
|
@@ -6571,6 +7041,7 @@ function createSessionStoreHealth(options = {}) {
|
|
|
6571
7041
|
const stalenessMs = Math.max(0, options.stalenessMs ?? SESSION_STORE_HEALTH_STALENESS_MS);
|
|
6572
7042
|
let lastUnwritableAt = null;
|
|
6573
7043
|
let lastWritableAt = null;
|
|
7044
|
+
const causes = /* @__PURE__ */ new Map();
|
|
6574
7045
|
return {
|
|
6575
7046
|
observeUnwritable: () => {
|
|
6576
7047
|
lastUnwritableAt = now();
|
|
@@ -6582,6 +7053,23 @@ function createSessionStoreHealth(options = {}) {
|
|
|
6582
7053
|
if (lastUnwritableAt === null) return false;
|
|
6583
7054
|
if (lastWritableAt !== null && lastWritableAt >= lastUnwritableAt) return false;
|
|
6584
7055
|
return now() - lastUnwritableAt < stalenessMs;
|
|
7056
|
+
},
|
|
7057
|
+
observeCause: (sessionId, cause) => {
|
|
7058
|
+
if (sessionId === null) return;
|
|
7059
|
+
causes.delete(sessionId);
|
|
7060
|
+
causes.set(sessionId, { cause, observedAt: now() });
|
|
7061
|
+
while (causes.size > 256) {
|
|
7062
|
+
const oldest = causes.keys().next().value;
|
|
7063
|
+
if (typeof oldest !== "string") break;
|
|
7064
|
+
causes.delete(oldest);
|
|
7065
|
+
}
|
|
7066
|
+
},
|
|
7067
|
+
readCause: (sessionId) => {
|
|
7068
|
+
const observed = causes.get(sessionId);
|
|
7069
|
+
if (!observed) return null;
|
|
7070
|
+
causes.delete(sessionId);
|
|
7071
|
+
if (now() - observed.observedAt >= stalenessMs) return null;
|
|
7072
|
+
return observed.cause;
|
|
6585
7073
|
}
|
|
6586
7074
|
};
|
|
6587
7075
|
}
|
|
@@ -6590,12 +7078,12 @@ function createSessionStoreHealth(options = {}) {
|
|
|
6590
7078
|
import { homedir as homedir6 } from "node:os";
|
|
6591
7079
|
|
|
6592
7080
|
// src/lib/runner-file-sync.ts
|
|
6593
|
-
import { join as
|
|
7081
|
+
import { join as join10 } from "node:path";
|
|
6594
7082
|
|
|
6595
7083
|
// src/lib/file-push.ts
|
|
6596
7084
|
import { randomUUID } from "node:crypto";
|
|
6597
7085
|
import { chmod, mkdir, open as open3, realpath, rename, unlink } from "node:fs/promises";
|
|
6598
|
-
import { basename, dirname as
|
|
7086
|
+
import { basename as basename2, dirname as dirname6, isAbsolute as isAbsolute2, join as join9, relative as relative2, resolve as resolve3, sep as sep2 } from "node:path";
|
|
6599
7087
|
var FILE_MODE = 384;
|
|
6600
7088
|
var DIRECTORY_MODE = 448;
|
|
6601
7089
|
async function writePushedFile(request2) {
|
|
@@ -6626,9 +7114,9 @@ async function writePushedFile(request2) {
|
|
|
6626
7114
|
}
|
|
6627
7115
|
try {
|
|
6628
7116
|
const { existingAncestor, missingSegments } = await resolveNearestExistingAncestor(
|
|
6629
|
-
|
|
7117
|
+
dirname6(candidate)
|
|
6630
7118
|
);
|
|
6631
|
-
const realTarget =
|
|
7119
|
+
const realTarget = join9(existingAncestor, ...missingSegments, basename2(candidate));
|
|
6632
7120
|
const allowedDirectory = await findContainingAllowedDirectory(allowedDirectories, realTarget);
|
|
6633
7121
|
if (allowedDirectory === null) {
|
|
6634
7122
|
return refuse("path_not_allowed", "The runner does not allow writing to that location.", {
|
|
@@ -6638,8 +7126,8 @@ async function writePushedFile(request2) {
|
|
|
6638
7126
|
}
|
|
6639
7127
|
if (missingSegments.length > 0) {
|
|
6640
7128
|
await createMissingDirectories(existingAncestor, missingSegments);
|
|
6641
|
-
const realParent = await realpath(
|
|
6642
|
-
if (realParent !==
|
|
7129
|
+
const realParent = await realpath(dirname6(realTarget));
|
|
7130
|
+
if (realParent !== dirname6(realTarget) || !contains(allowedDirectory, realTarget)) {
|
|
6643
7131
|
return refuse("path_not_allowed", "The runner does not allow writing to that location.", {
|
|
6644
7132
|
path: realTarget,
|
|
6645
7133
|
bytes,
|
|
@@ -6664,15 +7152,15 @@ function expandAndValidate(requestedPath, homeDir) {
|
|
|
6664
7152
|
if (requestedPath.trim() === "" || requestedPath.includes("\0")) {
|
|
6665
7153
|
return null;
|
|
6666
7154
|
}
|
|
6667
|
-
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ?
|
|
7155
|
+
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join9(homeDir, requestedPath.slice(2)) : requestedPath;
|
|
6668
7156
|
if (expanded.split(/[/\\]/).includes("..")) {
|
|
6669
7157
|
return null;
|
|
6670
7158
|
}
|
|
6671
|
-
if (!
|
|
7159
|
+
if (!isAbsolute2(expanded)) {
|
|
6672
7160
|
return null;
|
|
6673
7161
|
}
|
|
6674
|
-
const candidate =
|
|
6675
|
-
const name =
|
|
7162
|
+
const candidate = resolve3(expanded);
|
|
7163
|
+
const name = basename2(candidate);
|
|
6676
7164
|
return name === "" || name === "." || name === ".." ? null : candidate;
|
|
6677
7165
|
}
|
|
6678
7166
|
async function resolveNearestExistingAncestor(directory) {
|
|
@@ -6682,18 +7170,18 @@ async function resolveNearestExistingAncestor(directory) {
|
|
|
6682
7170
|
try {
|
|
6683
7171
|
return { existingAncestor: await realpath(current), missingSegments };
|
|
6684
7172
|
} catch (err) {
|
|
6685
|
-
const parent =
|
|
7173
|
+
const parent = dirname6(current);
|
|
6686
7174
|
if (err.code !== "ENOENT" || parent === current) {
|
|
6687
7175
|
throw err;
|
|
6688
7176
|
}
|
|
6689
|
-
missingSegments.unshift(
|
|
7177
|
+
missingSegments.unshift(basename2(current));
|
|
6690
7178
|
current = parent;
|
|
6691
7179
|
}
|
|
6692
7180
|
}
|
|
6693
7181
|
}
|
|
6694
7182
|
async function findContainingAllowedDirectory(allowedDirectories, realTarget) {
|
|
6695
7183
|
for (const directory of allowedDirectories) {
|
|
6696
|
-
if (!
|
|
7184
|
+
if (!isAbsolute2(directory)) {
|
|
6697
7185
|
log("warn", "file_push_allowed_directory_skipped", { directory, reason: "not_absolute" });
|
|
6698
7186
|
continue;
|
|
6699
7187
|
}
|
|
@@ -6731,19 +7219,19 @@ async function realpathCreatingIfMissing(directory) {
|
|
|
6731
7219
|
}
|
|
6732
7220
|
}
|
|
6733
7221
|
function contains(realDirectory, realTarget) {
|
|
6734
|
-
const rel =
|
|
6735
|
-
return rel !== "" && rel !== ".." && !rel.startsWith(`..${
|
|
7222
|
+
const rel = relative2(realDirectory, realTarget);
|
|
7223
|
+
return rel !== "" && rel !== ".." && !rel.startsWith(`..${sep2}`) && !isAbsolute2(rel);
|
|
6736
7224
|
}
|
|
6737
7225
|
async function createMissingDirectories(existingAncestor, missingSegments) {
|
|
6738
7226
|
let current = existingAncestor;
|
|
6739
7227
|
for (const segment of missingSegments) {
|
|
6740
|
-
current =
|
|
7228
|
+
current = join9(current, segment);
|
|
6741
7229
|
await mkdir(current, { recursive: true, mode: DIRECTORY_MODE });
|
|
6742
7230
|
await chmod(current, DIRECTORY_MODE);
|
|
6743
7231
|
}
|
|
6744
7232
|
}
|
|
6745
7233
|
async function writeAtomically(realTarget, content) {
|
|
6746
|
-
const temporaryPath =
|
|
7234
|
+
const temporaryPath = join9(dirname6(realTarget), `.evident-push-${randomUUID()}.tmp`);
|
|
6747
7235
|
let handle;
|
|
6748
7236
|
try {
|
|
6749
7237
|
handle = await open3(temporaryPath, "wx", FILE_MODE);
|
|
@@ -6873,12 +7361,12 @@ var NOT_APPLIED = {
|
|
|
6873
7361
|
opencodeAuthApplied: false
|
|
6874
7362
|
};
|
|
6875
7363
|
function isClaudeCredentialPath(requestedPath, homeDir) {
|
|
6876
|
-
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ?
|
|
6877
|
-
return expanded ===
|
|
7364
|
+
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join10(homeDir, requestedPath.slice(2)) : requestedPath;
|
|
7365
|
+
return expanded === join10(homeDir, ...CLAUDE_CREDENTIALS_SEGMENTS);
|
|
6878
7366
|
}
|
|
6879
7367
|
function isOpenCodeAuthPath(requestedPath, homeDir) {
|
|
6880
|
-
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ?
|
|
6881
|
-
return expanded ===
|
|
7368
|
+
const expanded = requestedPath === "~" ? homeDir : requestedPath.startsWith("~/") ? join10(homeDir, requestedPath.slice(2)) : requestedPath;
|
|
7369
|
+
return expanded === join10(homeDir, ...OPENCODE_AUTH_SEGMENTS);
|
|
6882
7370
|
}
|
|
6883
7371
|
async function applyOne(options, file) {
|
|
6884
7372
|
const label = `${file.id.slice(0, 8)} (${file.path})`;
|
|
@@ -7022,6 +7510,11 @@ function messageIdOf(m) {
|
|
|
7022
7510
|
const infoId = m.info?.id;
|
|
7023
7511
|
return typeof infoId === "string" ? infoId : void 0;
|
|
7024
7512
|
}
|
|
7513
|
+
function composeAbandonmentNote(finish, replyShape, outcome) {
|
|
7514
|
+
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.");
|
|
7515
|
+
if (replyShape === null) return base;
|
|
7516
|
+
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)` : ""}.`;
|
|
7517
|
+
}
|
|
7025
7518
|
function cleanImageMime(contentType) {
|
|
7026
7519
|
if (!contentType) return null;
|
|
7027
7520
|
const media = contentType.split(";")[0].trim().toLowerCase();
|
|
@@ -7042,6 +7535,10 @@ var SUBAGENT_TELEMETRY_FETCH_TIMEOUT_MS = 2e3;
|
|
|
7042
7535
|
var SESSION_ERROR_STREAM_HEALTHY_MS = 5e3;
|
|
7043
7536
|
var SESSION_ERROR_BUFFER_TTL_MS = SESSION_ERROR_STREAM_HEALTHY_MS;
|
|
7044
7537
|
var MAX_BUFFERED_SESSION_ERRORS = 256;
|
|
7538
|
+
var SESSION_ERROR_RETENTION_TTL_MS = 6e4;
|
|
7539
|
+
var SESSION_STORE_UNWRITABLE_ERROR = "The runner session database could not be written, so this turn did not finish. Nothing was lost from the queue. Clear disk space and retry.";
|
|
7540
|
+
var SESSION_ERROR_RETAINED_DETAIL = "Runner-observed OpenCode session error: ";
|
|
7541
|
+
var SESSION_ERROR_OBSERVED_CAUSE = "Runner-observed OpenCode session-store cause: ";
|
|
7045
7542
|
var DEFAULT_PAUSED_POLL_INTERVAL_MS = 2e3;
|
|
7046
7543
|
var DEFAULT_PAUSED_MAX_WAIT_MS = 10 * 60 * 1e3;
|
|
7047
7544
|
var DRIVER_SESSION_ENUMERATION_LIMIT = 20;
|
|
@@ -7199,6 +7696,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
7199
7696
|
* with a short TTL so an unmatched session cannot retain an event indefinitely.
|
|
7200
7697
|
*/
|
|
7201
7698
|
bufferedSessionErrors = /* @__PURE__ */ new Map();
|
|
7699
|
+
retainedSessionErrors = /* @__PURE__ */ new Map();
|
|
7202
7700
|
/**
|
|
7203
7701
|
* AUTHORITATIVE local dedup (WI-3): Evident message ids that have been
|
|
7204
7702
|
* dispatched and are still in-flight. A message in this set is never
|
|
@@ -9039,6 +9537,28 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
9039
9537
|
this.bufferedSessionErrors.delete(oldest);
|
|
9040
9538
|
}
|
|
9041
9539
|
}
|
|
9540
|
+
retainSessionError(event) {
|
|
9541
|
+
this.retainedSessionErrors.delete(event.sessionId);
|
|
9542
|
+
this.retainedSessionErrors.set(event.sessionId, {
|
|
9543
|
+
reason: event.reason,
|
|
9544
|
+
observedAt: this.now()
|
|
9545
|
+
});
|
|
9546
|
+
while (this.retainedSessionErrors.size > MAX_BUFFERED_SESSION_ERRORS) {
|
|
9547
|
+
const oldest = this.retainedSessionErrors.keys().next().value;
|
|
9548
|
+
if (typeof oldest !== "string") break;
|
|
9549
|
+
this.retainedSessionErrors.delete(oldest);
|
|
9550
|
+
}
|
|
9551
|
+
}
|
|
9552
|
+
readRetainedSessionError(sessionId) {
|
|
9553
|
+
const retained = this.retainedSessionErrors.get(sessionId);
|
|
9554
|
+
if (!retained) return null;
|
|
9555
|
+
if (this.now() - retained.observedAt >= SESSION_ERROR_RETENTION_TTL_MS) {
|
|
9556
|
+
this.retainedSessionErrors.delete(sessionId);
|
|
9557
|
+
return null;
|
|
9558
|
+
}
|
|
9559
|
+
this.retainedSessionErrors.delete(sessionId);
|
|
9560
|
+
return retained.reason;
|
|
9561
|
+
}
|
|
9042
9562
|
/**
|
|
9043
9563
|
* Build a fresh `SessionWatcher`. Seeds `lastTickAt`/`lastObservedTickAt`
|
|
9044
9564
|
* EQUAL (#1618) so a watcher whose loop has not started ticking yet is never
|
|
@@ -9365,9 +9885,10 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
9365
9885
|
return;
|
|
9366
9886
|
}
|
|
9367
9887
|
if ([...watcher.inFlight.values()].some((message) => message.started && !message.done)) {
|
|
9888
|
+
this.retainSessionError(event);
|
|
9368
9889
|
this.log({
|
|
9369
|
-
level: "
|
|
9370
|
-
message: `A turn is already running in session ${event.sessionId.slice(0, 8)} \u2014 deferring to transcript polling`,
|
|
9890
|
+
level: "warn",
|
|
9891
|
+
message: `A turn is already running in session ${event.sessionId.slice(0, 8)} \u2014 retaining the session error and deferring to transcript polling: ${event.reason}`,
|
|
9371
9892
|
conversation_id: watcher.conv.id
|
|
9372
9893
|
});
|
|
9373
9894
|
return;
|
|
@@ -9397,9 +9918,10 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
9397
9918
|
const messages = await this.getSessionMessages(event.sessionId);
|
|
9398
9919
|
const state = messageRunState(messages, inFlight.opencodeMessageId);
|
|
9399
9920
|
if (state !== "queued") {
|
|
9921
|
+
this.retainSessionError(event);
|
|
9400
9922
|
this.log({
|
|
9401
|
-
level: "
|
|
9402
|
-
message: `Session error for message ${inFlight.evidentMessageId.slice(0, 8)} observed state ${state};
|
|
9923
|
+
level: "warn",
|
|
9924
|
+
message: `Session error for message ${inFlight.evidentMessageId.slice(0, 8)} in session ${event.sessionId.slice(0, 8)} observed state ${state}; retaining the reason for transcript polling: ${event.reason}`,
|
|
9403
9925
|
conversation_id: watcher.conv.id,
|
|
9404
9926
|
message_id: inFlight.evidentMessageId
|
|
9405
9927
|
});
|
|
@@ -9628,7 +10150,16 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
9628
10150
|
if (state === "failed") {
|
|
9629
10151
|
this.anchorDeliveryDeadline(inFlight);
|
|
9630
10152
|
if (!inFlight.done) {
|
|
9631
|
-
const
|
|
10153
|
+
const messageErrorText = messageError(messages, inFlight.opencodeMessageId);
|
|
10154
|
+
const retainedReason = this.readRetainedSessionError(sessionId);
|
|
10155
|
+
const retainedError = retainedReason === null || messageErrorText !== null && messageErrorText.includes(retainedReason) ? null : `${SESSION_ERROR_RETAINED_DETAIL}${retainedReason}`;
|
|
10156
|
+
const observedCause = this.sessionStoreHealth?.readCause(sessionId);
|
|
10157
|
+
const causeError = observedCause === null || observedCause === void 0 ? null : `${SESSION_ERROR_OBSERVED_CAUSE}${observedCause}`;
|
|
10158
|
+
const storeError = this.sessionStoreHealth?.isUnwritable() ? SESSION_STORE_UNWRITABLE_ERROR : null;
|
|
10159
|
+
const runnerDetails = [retainedError, causeError, storeError].filter(
|
|
10160
|
+
(detail) => detail !== null
|
|
10161
|
+
);
|
|
10162
|
+
const error2 = messageErrorText === null ? runnerDetails.length === 0 ? void 0 : runnerDetails.join(" ") : [messageErrorText, ...runnerDetails].join(" ");
|
|
9632
10163
|
this.log({
|
|
9633
10164
|
level: "error",
|
|
9634
10165
|
message: `Message ${inFlight.evidentMessageId.slice(0, 8)} errored \u2014 marking failed: ${error2 ?? "(no error text)"}`,
|
|
@@ -9718,17 +10249,28 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
9718
10249
|
}
|
|
9719
10250
|
} else {
|
|
9720
10251
|
if (inFlight.b2AbandonedSignalled) {
|
|
10252
|
+
const reply = findLastAssistantReplyFor(messages, inFlight.opencodeMessageId);
|
|
10253
|
+
const finish = reply?.info?.finish ?? reply?.finish;
|
|
9721
10254
|
if (inFlight.b2AbandonedWithoutAnswer) {
|
|
9722
|
-
const reply = findLastAssistantReplyFor(messages, inFlight.opencodeMessageId);
|
|
9723
10255
|
await this.settleMessageAbandonedWithoutAnswer(
|
|
9724
10256
|
sessionId,
|
|
9725
10257
|
watcher,
|
|
9726
10258
|
inFlight,
|
|
9727
10259
|
messages,
|
|
9728
|
-
|
|
10260
|
+
finish
|
|
9729
10261
|
);
|
|
9730
10262
|
} else {
|
|
9731
|
-
await this.settleMessageDone(
|
|
10263
|
+
await this.settleMessageDone(
|
|
10264
|
+
sessionId,
|
|
10265
|
+
watcher,
|
|
10266
|
+
inFlight,
|
|
10267
|
+
messages,
|
|
10268
|
+
composeAbandonmentNote(
|
|
10269
|
+
finish,
|
|
10270
|
+
describeAbandonedReplyShape(messages, inFlight.opencodeMessageId),
|
|
10271
|
+
"with-answer"
|
|
10272
|
+
)
|
|
10273
|
+
);
|
|
9732
10274
|
}
|
|
9733
10275
|
return;
|
|
9734
10276
|
}
|
|
@@ -9791,7 +10333,17 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
9791
10333
|
...descendantOngoing != null ? { descendant_ongoing: descendantOngoing } : {},
|
|
9792
10334
|
opencode_message_id: inFlight.opencodeMessageId
|
|
9793
10335
|
});
|
|
9794
|
-
await this.settleMessageDone(
|
|
10336
|
+
await this.settleMessageDone(
|
|
10337
|
+
sessionId,
|
|
10338
|
+
watcher,
|
|
10339
|
+
inFlight,
|
|
10340
|
+
messages,
|
|
10341
|
+
composeAbandonmentNote(
|
|
10342
|
+
finish,
|
|
10343
|
+
describeAbandonedReplyShape(messages, inFlight.opencodeMessageId),
|
|
10344
|
+
"with-answer"
|
|
10345
|
+
)
|
|
10346
|
+
);
|
|
9795
10347
|
return;
|
|
9796
10348
|
}
|
|
9797
10349
|
if (rootOngoing === true && !inFlight.b2RootOngoingHeldLogged) {
|
|
@@ -9991,15 +10543,11 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
9991
10543
|
}
|
|
9992
10544
|
}
|
|
9993
10545
|
/**
|
|
9994
|
-
* Settle a message whose run-state has resolved `'done'
|
|
9995
|
-
*
|
|
9996
|
-
*
|
|
9997
|
-
* b2-abandonment resolution) can reach the exact same completion behavior
|
|
9998
|
-
* (delivery-deadline anchoring, title resolution, usage extraction, and
|
|
9999
|
-
* `markDone`'s auth/terminal/transient-retry discipline) without duplicating it
|
|
10000
|
-
* and risking the two copies silently drifting apart.
|
|
10546
|
+
* Settle a message whose run-state has resolved `'done'`. Normal completion and
|
|
10547
|
+
* confirmed b2 abandonment share the same delivery, usage, and terminal handling.
|
|
10548
|
+
* Normal completions omit the optional terminal diagnostic.
|
|
10001
10549
|
*/
|
|
10002
|
-
async settleMessageDone(sessionId, watcher, inFlight, messages) {
|
|
10550
|
+
async settleMessageDone(sessionId, watcher, inFlight, messages, note) {
|
|
10003
10551
|
const conv = watcher.conv;
|
|
10004
10552
|
this.anchorDeliveryDeadline(inFlight);
|
|
10005
10553
|
if (!inFlight.done) {
|
|
@@ -10026,7 +10574,8 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
10026
10574
|
title,
|
|
10027
10575
|
usage,
|
|
10028
10576
|
usageAgentName,
|
|
10029
|
-
subagentInvocations
|
|
10577
|
+
subagentInvocations,
|
|
10578
|
+
note
|
|
10030
10579
|
);
|
|
10031
10580
|
} catch (err) {
|
|
10032
10581
|
if (err instanceof ChannelAuthError) throw err;
|
|
@@ -10071,7 +10620,11 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
10071
10620
|
/** Report a confirmed b2 abandonment that produced no answer text. */
|
|
10072
10621
|
async settleMessageAbandonedWithoutAnswer(sessionId, watcher, inFlight, messages, finish) {
|
|
10073
10622
|
const conv = watcher.conv;
|
|
10074
|
-
const error2 =
|
|
10623
|
+
const error2 = composeAbandonmentNote(
|
|
10624
|
+
finish,
|
|
10625
|
+
describeAbandonedReplyShape(messages, inFlight.opencodeMessageId),
|
|
10626
|
+
"without-answer"
|
|
10627
|
+
);
|
|
10075
10628
|
this.anchorDeliveryDeadline(inFlight);
|
|
10076
10629
|
this.log({
|
|
10077
10630
|
level: "warn",
|
|
@@ -10198,7 +10751,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
10198
10751
|
*/
|
|
10199
10752
|
async settleMessageSessionStoreUnwritable(sessionId, watcher, inFlight) {
|
|
10200
10753
|
const conv = watcher.conv;
|
|
10201
|
-
const error2 =
|
|
10754
|
+
const error2 = SESSION_STORE_UNWRITABLE_ERROR;
|
|
10202
10755
|
this.log({
|
|
10203
10756
|
level: "error",
|
|
10204
10757
|
message: `Message ${inFlight.evidentMessageId.slice(0, 8)} was not resolved as done: ${error2}`,
|
|
@@ -11308,14 +11861,14 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
11308
11861
|
const fetchChildWithoutBlocking = async (sessionId) => {
|
|
11309
11862
|
const pending = fetchChildMessages(sessionId);
|
|
11310
11863
|
let timer;
|
|
11311
|
-
const timeout = new Promise((
|
|
11864
|
+
const timeout = new Promise((resolve5) => {
|
|
11312
11865
|
timer = setTimeout(() => {
|
|
11313
11866
|
this.log({
|
|
11314
11867
|
level: "warn",
|
|
11315
11868
|
message: `Best-effort subagent session fetch for message ${messageId.slice(0, 8)} and child session ${sessionId.slice(0, 8)} was slow \u2014 omitting invocation telemetry without delaying completion`,
|
|
11316
11869
|
message_id: messageId
|
|
11317
11870
|
});
|
|
11318
|
-
|
|
11871
|
+
resolve5(null);
|
|
11319
11872
|
}, SUBAGENT_TELEMETRY_FETCH_TIMEOUT_MS);
|
|
11320
11873
|
});
|
|
11321
11874
|
try {
|
|
@@ -11875,7 +12428,7 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
11875
12428
|
* watcher retries next tick within the
|
|
11876
12429
|
* deadline, Finding 4).
|
|
11877
12430
|
*/
|
|
11878
|
-
async markDone(conversationId, messageId, sessionId, opencodeMessageId, title, usage, usageAgentName, subagentInvocations) {
|
|
12431
|
+
async markDone(conversationId, messageId, sessionId, opencodeMessageId, title, usage, usageAgentName, subagentInvocations, error2) {
|
|
11879
12432
|
const res = await this.fetchImpl(
|
|
11880
12433
|
`${this.apiUrl}/runners/${this.agentId}/threads/${conversationId}/messages/${messageId}`,
|
|
11881
12434
|
{
|
|
@@ -11893,7 +12446,8 @@ var ChannelDriver = class _ChannelDriver {
|
|
|
11893
12446
|
...title ? { title } : {},
|
|
11894
12447
|
...usage ? usage : {},
|
|
11895
12448
|
...usageAgentName ? { usage_agent_name: usageAgentName } : {},
|
|
11896
|
-
...subagentInvocations && subagentInvocations.length > 0 ? { subagent_invocations: subagentInvocations } : {}
|
|
12449
|
+
...subagentInvocations && subagentInvocations.length > 0 ? { subagent_invocations: subagentInvocations } : {},
|
|
12450
|
+
...error2 ? { error: error2 } : {}
|
|
11897
12451
|
})
|
|
11898
12452
|
}
|
|
11899
12453
|
);
|
|
@@ -12556,7 +13110,7 @@ function commandError2(result) {
|
|
|
12556
13110
|
return result.stderr.trim() || `process exited with code ${result.code ?? "null"}`;
|
|
12557
13111
|
}
|
|
12558
13112
|
var runCommand2 = (command, args, opts) => {
|
|
12559
|
-
return new Promise((
|
|
13113
|
+
return new Promise((resolve5) => {
|
|
12560
13114
|
let child;
|
|
12561
13115
|
let stdout = "";
|
|
12562
13116
|
let stderr = "";
|
|
@@ -12566,7 +13120,7 @@ var runCommand2 = (command, args, opts) => {
|
|
|
12566
13120
|
if (settled) return;
|
|
12567
13121
|
settled = true;
|
|
12568
13122
|
if (timer.handle) clearTimeout(timer.handle);
|
|
12569
|
-
|
|
13123
|
+
resolve5(result);
|
|
12570
13124
|
};
|
|
12571
13125
|
try {
|
|
12572
13126
|
child = spawn5(command, args, { env: opts.env, stdio: ["ignore", "pipe", "pipe"] });
|
|
@@ -12621,7 +13175,9 @@ async function installRunnerSecret({
|
|
|
12621
13175
|
}) {
|
|
12622
13176
|
const arn = env.RUNNER_SECRET_ARN?.trim();
|
|
12623
13177
|
if (!arn) {
|
|
12624
|
-
|
|
13178
|
+
if (!env.GH_TOKEN) {
|
|
13179
|
+
log3("runner secret is not configured; continuing without GitHub and MCP credentials");
|
|
13180
|
+
}
|
|
12625
13181
|
return false;
|
|
12626
13182
|
}
|
|
12627
13183
|
const result = await (commandRunner ?? runCommand2)(
|
|
@@ -12820,7 +13376,7 @@ async function configureGitHubAccess({ env, log: log3 }) {
|
|
|
12820
13376
|
// src/lib/opencode/config-overlay.ts
|
|
12821
13377
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
12822
13378
|
import { copyFileSync, existsSync as existsSync2, statSync as statSync6 } from "node:fs";
|
|
12823
|
-
import { isAbsolute as
|
|
13379
|
+
import { isAbsolute as isAbsolute3, join as join11, resolve as resolve4 } from "node:path";
|
|
12824
13380
|
function isFile(filePath) {
|
|
12825
13381
|
return existsSync2(filePath) && statSync6(filePath).isFile();
|
|
12826
13382
|
}
|
|
@@ -12833,8 +13389,8 @@ function applyRunnerOpenCodeConfig({
|
|
|
12833
13389
|
log3("runner OpenCode config is not configured; using the baked project config", "debug");
|
|
12834
13390
|
return;
|
|
12835
13391
|
}
|
|
12836
|
-
const source =
|
|
12837
|
-
const target = isFile(
|
|
13392
|
+
const source = isAbsolute3(overlayPath) ? overlayPath : resolve4(cwd, overlayPath);
|
|
13393
|
+
const target = isFile(join11(cwd, "opencode.jsonc")) ? "opencode.jsonc" : "opencode.json";
|
|
12838
13394
|
if (!isFile(source)) {
|
|
12839
13395
|
log3(
|
|
12840
13396
|
`RUNNER-OPENCODE-CONFIG-MISSING: ${source} is not a file; headless turns will wedge on the first external-directory permission prompt (#563)`,
|
|
@@ -12842,7 +13398,7 @@ function applyRunnerOpenCodeConfig({
|
|
|
12842
13398
|
);
|
|
12843
13399
|
return;
|
|
12844
13400
|
}
|
|
12845
|
-
copyFileSync(source,
|
|
13401
|
+
copyFileSync(source, join11(cwd, target));
|
|
12846
13402
|
try {
|
|
12847
13403
|
execFileSync2("git", ["-C", cwd, "update-index", "--skip-worktree", target], {
|
|
12848
13404
|
stdio: "ignore"
|
|
@@ -12851,7 +13407,7 @@ function applyRunnerOpenCodeConfig({
|
|
|
12851
13407
|
const detail = error2 instanceof Error ? error2.message : String(error2);
|
|
12852
13408
|
log3(`could not mark ${target} skip-worktree: ${detail}; it may show as a local change`, "warn");
|
|
12853
13409
|
}
|
|
12854
|
-
log3(`Applied runner OpenCode config ${source} to ${
|
|
13410
|
+
log3(`Applied runner OpenCode config ${source} to ${join11(cwd, target)}`);
|
|
12855
13411
|
}
|
|
12856
13412
|
|
|
12857
13413
|
// src/lib/credential-sync.ts
|
|
@@ -12869,14 +13425,14 @@ function errorMessage2(error2) {
|
|
|
12869
13425
|
return error2 instanceof Error ? error2.message : String(error2);
|
|
12870
13426
|
}
|
|
12871
13427
|
function waitForSettlement(promise, timeoutMs) {
|
|
12872
|
-
return new Promise((
|
|
13428
|
+
return new Promise((resolve5) => {
|
|
12873
13429
|
let settled = false;
|
|
12874
13430
|
const timer = setTimeout(() => finish(false), Math.max(0, timeoutMs));
|
|
12875
13431
|
const finish = (value) => {
|
|
12876
13432
|
if (settled) return;
|
|
12877
13433
|
settled = true;
|
|
12878
13434
|
clearTimeout(timer);
|
|
12879
|
-
|
|
13435
|
+
resolve5(value);
|
|
12880
13436
|
};
|
|
12881
13437
|
promise.then(
|
|
12882
13438
|
() => finish(true),
|
|
@@ -13127,8 +13683,8 @@ function resolveFileSyncDirectories(raw, homeDir) {
|
|
|
13127
13683
|
if (trimmed === "") {
|
|
13128
13684
|
throw new Error("--enable-file-sync-to requires a directory path (got an empty value)");
|
|
13129
13685
|
}
|
|
13130
|
-
const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ?
|
|
13131
|
-
if (!
|
|
13686
|
+
const expanded = trimmed === "~" ? homeDir : trimmed.startsWith("~/") ? join12(homeDir, trimmed.slice(2)) : trimmed;
|
|
13687
|
+
if (!isAbsolute4(expanded)) {
|
|
13132
13688
|
throw new Error(`--enable-file-sync-to requires an absolute directory path; got "${entry}"`);
|
|
13133
13689
|
}
|
|
13134
13690
|
const normalized = resolvePath2(expanded);
|
|
@@ -13228,8 +13784,7 @@ function resolveMaxActiveSessions(options, env = process.env) {
|
|
|
13228
13784
|
function meetsThreshold(state, level) {
|
|
13229
13785
|
return LOG_LEVELS[level] >= LOG_LEVELS[state.logLevel];
|
|
13230
13786
|
}
|
|
13231
|
-
function
|
|
13232
|
-
if (!meetsThreshold(state, level)) return;
|
|
13787
|
+
function writeConsoleLog(state, message, level) {
|
|
13233
13788
|
if (state.json) {
|
|
13234
13789
|
console.log(
|
|
13235
13790
|
JSON.stringify({
|
|
@@ -13238,10 +13793,18 @@ function log2(state, message, level = "info") {
|
|
|
13238
13793
|
message
|
|
13239
13794
|
})
|
|
13240
13795
|
);
|
|
13241
|
-
|
|
13242
|
-
const prefix = level === "error" ? chalk7.red("\u2717") : level === "warn" ? chalk7.yellow("!") : level === "debug" ? chalk7.dim("\xB7") : chalk7.green("\u2022");
|
|
13243
|
-
console.log(`${prefix} ${message}`);
|
|
13796
|
+
return;
|
|
13244
13797
|
}
|
|
13798
|
+
const prefix = level === "error" ? chalk7.red("\u2717") : level === "warn" ? chalk7.yellow("!") : level === "debug" ? chalk7.dim("\xB7") : chalk7.green("\u2022");
|
|
13799
|
+
console.log(`${prefix} ${message}`);
|
|
13800
|
+
}
|
|
13801
|
+
function log2(state, message, level = "info") {
|
|
13802
|
+
if (!meetsThreshold(state, level)) return;
|
|
13803
|
+
if (state.json || !state.interactive) writeConsoleLog(state, message, level);
|
|
13804
|
+
}
|
|
13805
|
+
function logLocalOnly(state, message, level = "info") {
|
|
13806
|
+
if (!meetsThreshold(state, level)) return;
|
|
13807
|
+
writeConsoleLog(state, message, level);
|
|
13245
13808
|
}
|
|
13246
13809
|
function logActivity(state, entry) {
|
|
13247
13810
|
const level = entry.level ?? (entry.type === "error" ? "error" : "info");
|
|
@@ -13543,7 +14106,7 @@ async function driveChannels(state, driver) {
|
|
|
13543
14106
|
}
|
|
13544
14107
|
}
|
|
13545
14108
|
}
|
|
13546
|
-
await new Promise((
|
|
14109
|
+
await new Promise((resolve5) => setTimeout(resolve5, CHANNEL_POLL_INTERVAL_MS));
|
|
13547
14110
|
const cycleMs = performance.now() - cycleStartedAtMs;
|
|
13548
14111
|
if (idleThisCycle) idleMs += cycleMs;
|
|
13549
14112
|
if (unreachableThisCycle) unreachableMs += cycleMs;
|
|
@@ -13565,6 +14128,9 @@ async function driveChannels(state, driver) {
|
|
|
13565
14128
|
}
|
|
13566
14129
|
var SESSION_CLEANUP_FIRST_SWEEP_MS = 1e4;
|
|
13567
14130
|
var SESSION_CLEANUP_MAX_DELETES_PER_SWEEP = 100;
|
|
14131
|
+
var SESSION_CLEANUP_MAX_ISOLATED_DELETE_FAILURES = 2;
|
|
14132
|
+
var SESSION_CLEANUP_GIVE_UP_AFTER_FAILURES = 2;
|
|
14133
|
+
var MAX_SESSION_DELETE_BODY_LENGTH = 200;
|
|
13568
14134
|
function shouldWarnForReclaimSkip(reason) {
|
|
13569
14135
|
if (reason !== "sqlite-unavailable" && reason !== "disk-exhaustion") return false;
|
|
13570
14136
|
const version2 = /^v(\d+)\.(\d+)\.(\d+)$/.exec(process.version);
|
|
@@ -13575,7 +14141,7 @@ function shouldWarnForReclaimSkip(reason) {
|
|
|
13575
14141
|
return major > 22 || major === 22 && (minor > 13 || minor === 13 && patch >= 0);
|
|
13576
14142
|
}
|
|
13577
14143
|
function sessionDbPath() {
|
|
13578
|
-
return
|
|
14144
|
+
return join12(homedir7(), ".local", "share", "opencode", "opencode.db");
|
|
13579
14145
|
}
|
|
13580
14146
|
function logSessionCleanupActivity(state, phase, entry) {
|
|
13581
14147
|
logActivity(state, {
|
|
@@ -13652,8 +14218,12 @@ async function runSweep(state, driver, config) {
|
|
|
13652
14218
|
protectedIds: protectedNow
|
|
13653
14219
|
}
|
|
13654
14220
|
);
|
|
13655
|
-
const
|
|
13656
|
-
|
|
14221
|
+
const eligibleToDelete = toDelete.filter(
|
|
14222
|
+
(id) => (state.sessionDeleteFailures.get(id)?.isolatedFailures ?? 0) < SESSION_CLEANUP_GIVE_UP_AFTER_FAILURES
|
|
14223
|
+
);
|
|
14224
|
+
const deleteCandidates = eligibleToDelete.slice(0, SESSION_CLEANUP_MAX_DELETES_PER_SWEEP);
|
|
14225
|
+
remaining = eligibleToDelete.length - deleteCandidates.length;
|
|
14226
|
+
const answeredFailures = [];
|
|
13657
14227
|
protectedNow = driver.protectedSessionIds();
|
|
13658
14228
|
for (const id of deleteCandidates) {
|
|
13659
14229
|
if (protectedNow.has(id)) {
|
|
@@ -13676,8 +14246,44 @@ async function runSweep(state, driver, config) {
|
|
|
13676
14246
|
if (statusClass === 4) failed4xx++;
|
|
13677
14247
|
else if (statusClass === 5) failed5xx++;
|
|
13678
14248
|
else failedOtherStatus++;
|
|
14249
|
+
answeredFailures.push({
|
|
14250
|
+
id,
|
|
14251
|
+
status: outcome.status,
|
|
14252
|
+
...outcome.body !== void 0 ? { body: outcome.body } : {}
|
|
14253
|
+
});
|
|
13679
14254
|
}
|
|
13680
14255
|
}
|
|
14256
|
+
const givenUpFailures = [];
|
|
14257
|
+
if (failed > SESSION_CLEANUP_MAX_ISOLATED_DELETE_FAILURES) {
|
|
14258
|
+
for (const { id } of answeredFailures) state.sessionDeleteFailures.delete(id);
|
|
14259
|
+
} else {
|
|
14260
|
+
for (const failure of answeredFailures) {
|
|
14261
|
+
const previous = state.sessionDeleteFailures.get(failure.id);
|
|
14262
|
+
const record = previous?.status === failure.status ? { status: failure.status, isolatedFailures: previous.isolatedFailures + 1 } : { status: failure.status, isolatedFailures: 1 };
|
|
14263
|
+
state.sessionDeleteFailures.set(failure.id, record);
|
|
14264
|
+
if (record.isolatedFailures >= SESSION_CLEANUP_GIVE_UP_AFTER_FAILURES) {
|
|
14265
|
+
givenUpFailures.push(failure);
|
|
14266
|
+
}
|
|
14267
|
+
}
|
|
14268
|
+
}
|
|
14269
|
+
for (const failure of givenUpFailures) {
|
|
14270
|
+
const excerpt = redactDiagnosticText(failure.body ?? "(no response body)").slice(
|
|
14271
|
+
0,
|
|
14272
|
+
MAX_SESSION_DELETE_BODY_LENGTH
|
|
14273
|
+
);
|
|
14274
|
+
logSessionCleanupActivity(state, "sweep", {
|
|
14275
|
+
type: "info",
|
|
14276
|
+
level: "warn",
|
|
14277
|
+
message: `Session cleanup: gave up on session ${failure.id} after ${SESSION_CLEANUP_GIVE_UP_AFTER_FAILURES} isolated delete failures (status ${failure.status}): ${excerpt}`,
|
|
14278
|
+
metadata: {
|
|
14279
|
+
outcome: "session-delete-given-up",
|
|
14280
|
+
session_id: failure.id,
|
|
14281
|
+
status: failure.status,
|
|
14282
|
+
body_excerpt: excerpt,
|
|
14283
|
+
given_up_count: givenUpFailures.length
|
|
14284
|
+
}
|
|
14285
|
+
});
|
|
14286
|
+
}
|
|
13681
14287
|
const failedNote = failed > 0 ? `, failed ${failed}` : "";
|
|
13682
14288
|
const skippedNote = skippedNewlyActive > 0 ? `, skipped ${skippedNewlyActive} newly-active` : "";
|
|
13683
14289
|
const remainingNote = remaining > 0 ? `, remaining ${remaining}` : "";
|
|
@@ -13698,21 +14304,31 @@ async function runSweep(state, driver, config) {
|
|
|
13698
14304
|
sessions_remaining: remaining,
|
|
13699
14305
|
...enumerationReason !== null ? { session_enumeration_reason: enumerationReason } : {}
|
|
13700
14306
|
};
|
|
14307
|
+
const protectedForReclaim = driver.protectedSessionIds();
|
|
13701
14308
|
const reclaimResult = await reclaimSessionDbSpace({
|
|
13702
14309
|
dbPath: sessionDbPath(),
|
|
13703
14310
|
chunkPages: SESSION_DB_RECLAIM_CHUNK_PAGES,
|
|
13704
14311
|
pageCeiling: SESSION_DB_RECLAIM_PAGE_CEILING,
|
|
13705
14312
|
timeBudgetMs: SESSION_DB_RECLAIM_TIME_BUDGET_MS,
|
|
13706
|
-
|
|
14313
|
+
hasLiveTurn: protectedForReclaim.size > 0,
|
|
14314
|
+
hasProvenanceAnomaly: state.sessionDbProvenanceAnomaly
|
|
13707
14315
|
});
|
|
13708
14316
|
if (reclaimResult.ok) {
|
|
13709
14317
|
state.sessionStoreHealth.observeWritable();
|
|
13710
14318
|
const beforeMib = (reclaimResult.beforeBytes / 1024 / 1024).toFixed(1);
|
|
13711
14319
|
const afterMib = (reclaimResult.afterBytes / 1024 / 1024).toFixed(1);
|
|
13712
14320
|
const checkpointNote = reclaimResult.checkpoint.busy ? ` (on-disk file truncation deferred: checkpoint busy, ${reclaimResult.checkpoint.log} WAL frames pending)` : "";
|
|
14321
|
+
const loopElapsedMs = Math.max(
|
|
14322
|
+
0,
|
|
14323
|
+
reclaimResult.elapsedMs - reclaimResult.checkpointElapsedMs
|
|
14324
|
+
);
|
|
14325
|
+
const reclaimIsSlow = reclaimResult.elapsedMs >= 4 * SESSION_DB_RECLAIM_TIME_BUDGET_MS;
|
|
14326
|
+
const perPageNote = reclaimResult.pagesReclaimed > 0 ? `, per-page ${(loopElapsedMs / reclaimResult.pagesReclaimed).toFixed(2)} ms/page` : "";
|
|
14327
|
+
const slowReclaimNote = reclaimIsSlow ? ` (slow reclaim: total elapsed ${reclaimResult.elapsedMs} ms, checkpoint ${reclaimResult.checkpointElapsedMs} ms, loop ${loopElapsedMs} ms, budget ${SESSION_DB_RECLAIM_TIME_BUDGET_MS} ms${perPageNote})` : "";
|
|
13713
14328
|
logSessionCleanupActivity(state, "sweep", {
|
|
13714
14329
|
type: "info",
|
|
13715
|
-
|
|
14330
|
+
level: reclaimIsSlow ? "warn" : "info",
|
|
14331
|
+
message: `Session cleanup: reclaimed session-db space (${reclaimResult.mode}): ${beforeMib} MiB -> ${afterMib} MiB (stopped by ${reclaimResult.stoppedBy}; freelist ${reclaimResult.freelistBefore} -> ${reclaimResult.freelistAfter} pages)` + checkpointNote + slowReclaimNote,
|
|
13716
14332
|
metadata: {
|
|
13717
14333
|
outcome: "reclaimed",
|
|
13718
14334
|
mode: reclaimResult.mode,
|
|
@@ -13725,6 +14341,7 @@ async function runSweep(state, driver, config) {
|
|
|
13725
14341
|
freelist_after: reclaimResult.freelistAfter,
|
|
13726
14342
|
pages_reclaimed: reclaimResult.pagesReclaimed,
|
|
13727
14343
|
reclaim_elapsed_ms: reclaimResult.elapsedMs,
|
|
14344
|
+
checkpoint_elapsed_ms: reclaimResult.checkpointElapsedMs,
|
|
13728
14345
|
...sessionMetadata
|
|
13729
14346
|
}
|
|
13730
14347
|
});
|
|
@@ -14204,8 +14821,8 @@ async function beginGracefulShutdown(state, trigger) {
|
|
|
14204
14821
|
return true;
|
|
14205
14822
|
}
|
|
14206
14823
|
);
|
|
14207
|
-
const timedOut = new Promise((
|
|
14208
|
-
timer = setTimeout(() =>
|
|
14824
|
+
const timedOut = new Promise((resolve5) => {
|
|
14825
|
+
timer = setTimeout(() => resolve5(false), telemetryBudgetMs);
|
|
14209
14826
|
});
|
|
14210
14827
|
if (!await Promise.race([flushed, timedOut])) {
|
|
14211
14828
|
log2(state, `Telemetry flush exceeded ${telemetryBudgetMs}ms \u2014 exiting anyway`, "warn");
|
|
@@ -14268,6 +14885,7 @@ async function run(options) {
|
|
|
14268
14885
|
messageCount: 0,
|
|
14269
14886
|
lastProxiedActivityAt: null,
|
|
14270
14887
|
sessionCleanupTimers: [],
|
|
14888
|
+
sessionDeleteFailures: /* @__PURE__ */ new Map(),
|
|
14271
14889
|
claudeUsageTimer: null,
|
|
14272
14890
|
claudeUsageRearm: null,
|
|
14273
14891
|
openaiUsageTimer: null,
|
|
@@ -14476,29 +15094,45 @@ async function run(options) {
|
|
|
14476
15094
|
);
|
|
14477
15095
|
}
|
|
14478
15096
|
}
|
|
14479
|
-
|
|
14480
|
-
|
|
14481
|
-
|
|
14482
|
-
|
|
14483
|
-
|
|
14484
|
-
|
|
14485
|
-
|
|
14486
|
-
} else {
|
|
14487
|
-
logActivity(state, { type: "info", level, message });
|
|
14488
|
-
}
|
|
15097
|
+
const credentialContext = {
|
|
15098
|
+
env: process.env,
|
|
15099
|
+
log: (message, level = "info") => {
|
|
15100
|
+
if (level === "error") {
|
|
15101
|
+
logActivity(state, { type: "error", error: message });
|
|
15102
|
+
} else {
|
|
15103
|
+
logActivity(state, { type: "info", level, message });
|
|
14489
15104
|
}
|
|
14490
|
-
}
|
|
14491
|
-
|
|
15105
|
+
}
|
|
15106
|
+
};
|
|
15107
|
+
const githubTokenPopulated = await installRunnerSecret(credentialContext);
|
|
15108
|
+
if (options.restoreRunnerCredentials) {
|
|
14492
15109
|
await restoreCredentialStores(credentialContext);
|
|
14493
|
-
if (githubTokenPopulated) await configureGitHubAccess(credentialContext);
|
|
14494
15110
|
}
|
|
15111
|
+
if (githubTokenPopulated) await configureGitHubAccess(credentialContext);
|
|
14495
15112
|
state.credentialSync?.arm();
|
|
15113
|
+
const dependencyInstallDiagnosticReporter = createDependencyInstallDiagnosticReporter({
|
|
15114
|
+
context: {
|
|
15115
|
+
homeDir: homedir7(),
|
|
15116
|
+
cwd: process.cwd(),
|
|
15117
|
+
microvmId: process.env.MICROVM_ID?.trim(),
|
|
15118
|
+
euid: process.getuid?.(),
|
|
15119
|
+
egid: process.getgid?.()
|
|
15120
|
+
},
|
|
15121
|
+
emitEvent: (message) => logActivity(state, { type: "info", level: "warn", message }),
|
|
15122
|
+
emitLocal: (message) => logLocalOnly(state, message, "warn")
|
|
15123
|
+
});
|
|
14496
15124
|
const openCodeActivityForwarder = createOpenCodeActivityForwarder(
|
|
14497
15125
|
() => ({
|
|
14498
15126
|
agentId: state.agentId,
|
|
14499
15127
|
authHeader: state.authHeader
|
|
14500
15128
|
}),
|
|
14501
|
-
|
|
15129
|
+
({ isDiskFailure, sessionId, cause }) => {
|
|
15130
|
+
if (isDiskFailure) state.sessionStoreHealth.observeUnwritable();
|
|
15131
|
+
if (cause !== null && sessionId !== null) {
|
|
15132
|
+
state.sessionStoreHealth.observeCause(sessionId, cause);
|
|
15133
|
+
}
|
|
15134
|
+
},
|
|
15135
|
+
dependencyInstallDiagnosticReporter
|
|
14502
15136
|
);
|
|
14503
15137
|
const openCodeLogTail = tailOpenCodeLogFile(
|
|
14504
15138
|
resolveOpenCodeLogPath(homedir7(), process.env),
|
|
@@ -14509,22 +15143,27 @@ async function run(options) {
|
|
|
14509
15143
|
openCodeLogTail.stop();
|
|
14510
15144
|
};
|
|
14511
15145
|
let sessionDbVerifyFatal = false;
|
|
15146
|
+
let sessionDbVerifyFatalReason;
|
|
14512
15147
|
if (!options.restoreSessionDb) {
|
|
14513
|
-
|
|
15148
|
+
logActivity(state, {
|
|
15149
|
+
type: "info",
|
|
15150
|
+
level: "warn",
|
|
15151
|
+
message: "SESSION-DB-VERIFY-SKIPPED: reason=flag-absent \u2014 Skipping session-DB restore: --restore-session-db was not passed"
|
|
15152
|
+
});
|
|
14514
15153
|
} else {
|
|
14515
15154
|
const health = await checkOpenCodeHealth(state.port);
|
|
14516
15155
|
if (health.healthy) {
|
|
14517
|
-
|
|
14518
|
-
|
|
14519
|
-
|
|
14520
|
-
"
|
|
14521
|
-
);
|
|
15156
|
+
logActivity(state, {
|
|
15157
|
+
type: "info",
|
|
15158
|
+
level: "warn",
|
|
15159
|
+
message: "SESSION-DB-VERIFY-SKIPPED: reason=opencode-serving \u2014 Skipping session-DB restore: OpenCode is already serving this database"
|
|
15160
|
+
});
|
|
14522
15161
|
} else if (health.authFailed) {
|
|
14523
|
-
|
|
14524
|
-
|
|
14525
|
-
|
|
14526
|
-
"
|
|
14527
|
-
);
|
|
15162
|
+
logActivity(state, {
|
|
15163
|
+
type: "info",
|
|
15164
|
+
level: "warn",
|
|
15165
|
+
message: "SESSION-DB-VERIFY-SKIPPED: reason=opencode-auth-challenge \u2014 Skipping session-DB restore: OpenCode answered with an authentication challenge; assuming this database is in use"
|
|
15166
|
+
});
|
|
14528
15167
|
} else {
|
|
14529
15168
|
const result = await restoreAndVerifySessionDb({
|
|
14530
15169
|
dbPath: sessionDbPath(),
|
|
@@ -14541,12 +15180,13 @@ async function run(options) {
|
|
|
14541
15180
|
reportRecovery: (record) => reportSessionDbRecoveryRecord(state, record)
|
|
14542
15181
|
});
|
|
14543
15182
|
sessionDbVerifyFatal = result.verifyFatal;
|
|
15183
|
+
sessionDbVerifyFatalReason = result.verifyFatalReason;
|
|
14544
15184
|
}
|
|
14545
15185
|
}
|
|
14546
15186
|
reportSessionDbRecovery(state);
|
|
14547
15187
|
if (sessionDbVerifyFatal) {
|
|
14548
15188
|
throw new Error(
|
|
14549
|
-
"SESSION-DB-INTEGRITY-EXHAUSTED: the corrupt session DB could not be proven separated from the active backup prefix or removed from disk"
|
|
15189
|
+
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`
|
|
14550
15190
|
);
|
|
14551
15191
|
}
|
|
14552
15192
|
applyRunnerOpenCodeConfig({
|