@alook/daemon 0.1.24 → 0.1.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +2018 -325
- package/dist/index.js +1964 -323
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -865,6 +865,9 @@ class ProcessLane {
|
|
|
865
865
|
return false;
|
|
866
866
|
return proc.kill("SIGINT");
|
|
867
867
|
}
|
|
868
|
+
updateSettings(input) {
|
|
869
|
+
return this.driver.updateSettings?.(input) ?? Promise.resolve({ status: "unsupported" });
|
|
870
|
+
}
|
|
868
871
|
attachProcess(proc) {
|
|
869
872
|
proc.stdout?.on("data", (chunk) => {
|
|
870
873
|
const chunkText = chunk.toString();
|
|
@@ -939,8 +942,8 @@ function resolveLaunchFieldsOrDefault(input) {
|
|
|
939
942
|
const envVars = Object.fromEntries(Object.entries(normalized.environment ?? {}).filter(([key]) => !controlled.has(key)));
|
|
940
943
|
const providerEnv = {};
|
|
941
944
|
const model = normalized.model.kind === "default" ? undefined : normalized.model.name;
|
|
942
|
-
if (
|
|
943
|
-
providerEnv.ANTHROPIC_CUSTOM_MODEL_OPTION =
|
|
945
|
+
if (model && normalized.provider?.kind === "custom_endpoint") {
|
|
946
|
+
providerEnv.ANTHROPIC_CUSTOM_MODEL_OPTION = model;
|
|
944
947
|
}
|
|
945
948
|
if (normalized.provider?.kind === "custom_endpoint") {
|
|
946
949
|
providerEnv.ANTHROPIC_BASE_URL = normalized.provider.apiUrl;
|
|
@@ -1252,25 +1255,19 @@ class ClaudeEventNormalizer {
|
|
|
1252
1255
|
}
|
|
1253
1256
|
buildUsageTelemetry(event) {
|
|
1254
1257
|
const u = event?.usage;
|
|
1255
|
-
if (!u
|
|
1258
|
+
if (!u)
|
|
1256
1259
|
return null;
|
|
1260
|
+
const metric = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : null;
|
|
1261
|
+
const cacheParts = [u.cache_read_input_tokens, u.cache_creation_input_tokens].filter((value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0);
|
|
1262
|
+
const cache = cacheParts.length > 0 && Number.isSafeInteger(cacheParts.reduce((sum, value) => sum + value, 0)) ? cacheParts.reduce((sum, value) => sum + value, 0) : null;
|
|
1257
1263
|
return {
|
|
1258
1264
|
kind: "telemetry",
|
|
1259
1265
|
name: "token_usage",
|
|
1260
1266
|
source: "claude_result_usage",
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
cachedInputTokens: u?.cache_read_input_tokens,
|
|
1266
|
-
cacheCreationInputTokens: u?.cache_creation_input_tokens,
|
|
1267
|
-
totalCostUsd: event?.total_cost_usd,
|
|
1268
|
-
durationMs: event?.duration_ms,
|
|
1269
|
-
durationApiMs: event?.duration_api_ms,
|
|
1270
|
-
numTurns: event?.num_turns,
|
|
1271
|
-
resultSubtype: event?.subtype,
|
|
1272
|
-
resultIsError: event?.is_error,
|
|
1273
|
-
serviceTier: u?.service_tier
|
|
1267
|
+
usage: {
|
|
1268
|
+
input: metric(u.input_tokens),
|
|
1269
|
+
output: metric(u.output_tokens),
|
|
1270
|
+
cache
|
|
1274
1271
|
}
|
|
1275
1272
|
};
|
|
1276
1273
|
}
|
|
@@ -1281,6 +1278,7 @@ import { execFileSync as execFileSync2 } from "child_process";
|
|
|
1281
1278
|
import * as fs4 from "fs";
|
|
1282
1279
|
import * as path4 from "path";
|
|
1283
1280
|
var PROBE_TIMEOUT_MS = 5000;
|
|
1281
|
+
var PROBE_OUTPUT_MAX_BYTES = 1024 * 1024;
|
|
1284
1282
|
function resolveCommandOnPath(command, deps = {}) {
|
|
1285
1283
|
if (deps.which)
|
|
1286
1284
|
return deps.which(command);
|
|
@@ -1331,6 +1329,23 @@ function probeCommandVersion(command, args = [], deps = {}, platform = process.p
|
|
|
1331
1329
|
return { ok: false, error: String(code) };
|
|
1332
1330
|
}
|
|
1333
1331
|
}
|
|
1332
|
+
function probeCommandOutput(command, args, platform = process.platform) {
|
|
1333
|
+
try {
|
|
1334
|
+
const output = execFileSync2(command, args, {
|
|
1335
|
+
encoding: "utf8",
|
|
1336
|
+
timeout: PROBE_TIMEOUT_MS,
|
|
1337
|
+
maxBuffer: PROBE_OUTPUT_MAX_BYTES,
|
|
1338
|
+
shell: needsWindowsShimShell(command, platform),
|
|
1339
|
+
stdio: ["pipe", "pipe", "ignore"],
|
|
1340
|
+
input: "",
|
|
1341
|
+
env: { ...process.env, CI: "1" }
|
|
1342
|
+
});
|
|
1343
|
+
return { ok: true, output };
|
|
1344
|
+
} catch (err) {
|
|
1345
|
+
const code = err?.code ?? "command_probe_failed";
|
|
1346
|
+
return { ok: false, error: String(code) };
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1334
1349
|
function resolveHomePath(relativePath, deps = {}) {
|
|
1335
1350
|
return path4.join(deps.homeDir || process.env.HOME || ".", relativePath);
|
|
1336
1351
|
}
|
|
@@ -1437,6 +1452,14 @@ class ClaudeTurnProtocol {
|
|
|
1437
1452
|
}
|
|
1438
1453
|
|
|
1439
1454
|
// agent-driver/dist/adapters/claude/index.js
|
|
1455
|
+
var CLAUDE_MODEL_CATALOG = {
|
|
1456
|
+
updateMode: "unsupported",
|
|
1457
|
+
models: ["opus", "sonnet", "haiku"].map((id) => ({
|
|
1458
|
+
id,
|
|
1459
|
+
supportedReasoningEfforts: []
|
|
1460
|
+
}))
|
|
1461
|
+
};
|
|
1462
|
+
|
|
1440
1463
|
class ClaudeDriver {
|
|
1441
1464
|
id = "claude";
|
|
1442
1465
|
instructionDelivery = { kind: "workspace_file", canonical: "AGENTS.md", aliases: ["CLAUDE.md"] };
|
|
@@ -1453,10 +1476,11 @@ class ClaudeDriver {
|
|
|
1453
1476
|
}
|
|
1454
1477
|
probe(command) {
|
|
1455
1478
|
const explicit = command?.trim();
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1479
|
+
const base = explicit ? (() => {
|
|
1480
|
+
const result = probeCommandVersion(explicit);
|
|
1481
|
+
return result.ok ? { status: "healthy", version: result.version } : { status: "unhealthy", lastError: result.error };
|
|
1482
|
+
})() : probeClaude();
|
|
1483
|
+
return base.status === "healthy" ? { ...base, reasoning: CLAUDE_MODEL_CATALOG } : base;
|
|
1460
1484
|
}
|
|
1461
1485
|
async openLane(ctx, options) {
|
|
1462
1486
|
return createProcessLane(this, ctx, { onRawStdoutLine: options?.onRawStdoutLine });
|
|
@@ -1502,49 +1526,153 @@ class ClaudeDriver {
|
|
|
1502
1526
|
}
|
|
1503
1527
|
|
|
1504
1528
|
// agent-driver/dist/adapters/codex/telemetry.js
|
|
1505
|
-
function
|
|
1529
|
+
function metric(value) {
|
|
1530
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : null;
|
|
1531
|
+
}
|
|
1532
|
+
function nonCachedInput(input, cached) {
|
|
1533
|
+
if (typeof input !== "number" || !Number.isSafeInteger(input) || input < 0 || typeof cached !== "number" || !Number.isSafeInteger(cached) || cached < 0 || cached > input)
|
|
1534
|
+
return null;
|
|
1535
|
+
return input - cached;
|
|
1536
|
+
}
|
|
1537
|
+
function canonicalId(value, fallback) {
|
|
1538
|
+
return typeof value === "string" && value.length > 0 && new TextEncoder().encode(value).length <= 64 ? value : fallback;
|
|
1539
|
+
}
|
|
1540
|
+
function mappedPlanName(value) {
|
|
1541
|
+
switch (value) {
|
|
1542
|
+
case "free":
|
|
1543
|
+
return "Free";
|
|
1544
|
+
case "plus":
|
|
1545
|
+
return "Plus";
|
|
1546
|
+
case "pro":
|
|
1547
|
+
return "Pro";
|
|
1548
|
+
case "team":
|
|
1549
|
+
return "Team";
|
|
1550
|
+
case "business":
|
|
1551
|
+
return "Business";
|
|
1552
|
+
case "enterprise":
|
|
1553
|
+
return "Enterprise";
|
|
1554
|
+
case "edu":
|
|
1555
|
+
return "Education";
|
|
1556
|
+
default:
|
|
1557
|
+
return;
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
function quotaWindow(minutes, slot) {
|
|
1561
|
+
if (typeof minutes !== "number" || !Number.isSafeInteger(minutes) || minutes <= 0)
|
|
1562
|
+
return null;
|
|
1563
|
+
if (minutes === 1440)
|
|
1564
|
+
return { kind: "calendar", period: "day", displayName: "Daily usage limit" };
|
|
1565
|
+
if (minutes === 10080)
|
|
1566
|
+
return { kind: "calendar", period: "week", displayName: "Weekly usage limit" };
|
|
1567
|
+
if (minutes === 43200)
|
|
1568
|
+
return { kind: "calendar", period: "month", displayName: "Monthly usage limit" };
|
|
1569
|
+
return {
|
|
1570
|
+
kind: "rolling",
|
|
1571
|
+
durationSeconds: minutes * 60,
|
|
1572
|
+
displayName: slot === "primary" && minutes === 300 ? "5 hour usage limit" : `${minutes} minute usage limit`
|
|
1573
|
+
};
|
|
1574
|
+
}
|
|
1575
|
+
function resetIso(value) {
|
|
1576
|
+
if (typeof value === "number" && Number.isFinite(value) && value >= 0) {
|
|
1577
|
+
const date = new Date(value < 10000000000 ? value * 1000 : value);
|
|
1578
|
+
return Number.isNaN(date.getTime()) ? undefined : date.toISOString();
|
|
1579
|
+
}
|
|
1580
|
+
if (typeof value === "string") {
|
|
1581
|
+
const date = new Date(value);
|
|
1582
|
+
return Number.isNaN(date.getTime()) ? undefined : date.toISOString();
|
|
1583
|
+
}
|
|
1584
|
+
return;
|
|
1585
|
+
}
|
|
1586
|
+
function mapCodexQuotaSnapshots(snapshots, sourceEpoch) {
|
|
1587
|
+
const limits = [];
|
|
1588
|
+
let planName;
|
|
1589
|
+
for (const snapshot of snapshots) {
|
|
1590
|
+
const limitId = canonicalId(snapshot?.limitId ?? snapshot?.limit_id, "codex");
|
|
1591
|
+
const spark = /spark/i.test(limitId) || /spark/i.test(String(snapshot?.limitName ?? snapshot?.limit_name ?? ""));
|
|
1592
|
+
const product = spark ? { kind: "reported", id: "codex-spark", displayName: "Spark" } : { kind: "reported", id: "codex", displayName: "Codex" };
|
|
1593
|
+
const model = spark ? { kind: "reported", id: "gpt-5.3-codex-spark" } : { kind: "not_applicable" };
|
|
1594
|
+
planName ??= mappedPlanName(snapshot?.planType ?? snapshot?.plan_type);
|
|
1595
|
+
for (const slot of ["primary", "secondary"]) {
|
|
1596
|
+
const value = snapshot?.[slot];
|
|
1597
|
+
const window2 = quotaWindow(value?.windowDurationMins ?? value?.window_duration_mins, slot);
|
|
1598
|
+
const usedPercent = value?.usedPercent ?? value?.used_percent;
|
|
1599
|
+
if (!window2 || typeof usedPercent !== "number" || !Number.isFinite(usedPercent) || usedPercent < 0 || usedPercent > 100)
|
|
1600
|
+
continue;
|
|
1601
|
+
const resetsAt = resetIso(value?.resetsAt ?? value?.resets_at);
|
|
1602
|
+
limits.push({
|
|
1603
|
+
bucket: { limitId, product, model, window: window2 },
|
|
1604
|
+
usedPercent,
|
|
1605
|
+
...resetsAt ? { resetsAt } : {}
|
|
1606
|
+
});
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
if (limits.length === 0) {
|
|
1610
|
+
return {
|
|
1611
|
+
kind: "telemetry",
|
|
1612
|
+
name: "rate_limits",
|
|
1613
|
+
source: "codex_account_rate_limits_updated",
|
|
1614
|
+
quota: { status: "error", sourceEpoch, code: "invalid_response", retryable: true }
|
|
1615
|
+
};
|
|
1616
|
+
}
|
|
1617
|
+
return {
|
|
1618
|
+
kind: "telemetry",
|
|
1619
|
+
name: "rate_limits",
|
|
1620
|
+
source: "codex_account_rate_limits_updated",
|
|
1621
|
+
quota: {
|
|
1622
|
+
status: "available",
|
|
1623
|
+
sourceEpoch,
|
|
1624
|
+
...planName ? { planName } : {},
|
|
1625
|
+
freshForSeconds: 300,
|
|
1626
|
+
limits
|
|
1627
|
+
}
|
|
1628
|
+
};
|
|
1629
|
+
}
|
|
1630
|
+
function mapCodexTelemetry(method, params, sourceEpoch) {
|
|
1506
1631
|
if (method === "thread/tokenUsage/updated") {
|
|
1507
|
-
const u = params?.
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
modelContextWindow: u.modelContextWindow ?? u.model_context_window,
|
|
1521
|
-
cachedInputRatio: u.cachedInputRatio,
|
|
1522
|
-
contextUtilization: u.contextUtilization
|
|
1523
|
-
}
|
|
1632
|
+
const u = params?.tokenUsage?.last ?? params?.token_usage?.last;
|
|
1633
|
+
if (!u)
|
|
1634
|
+
return [];
|
|
1635
|
+
const input = u.inputTokens ?? u.input_tokens;
|
|
1636
|
+
const cached = u.cachedInputTokens ?? u.cached_input_tokens;
|
|
1637
|
+
return [{
|
|
1638
|
+
kind: "telemetry",
|
|
1639
|
+
name: "token_usage",
|
|
1640
|
+
source: "codex_thread_token_usage_updated",
|
|
1641
|
+
usage: {
|
|
1642
|
+
input: nonCachedInput(input, cached),
|
|
1643
|
+
output: metric(u.outputTokens ?? u.output_tokens),
|
|
1644
|
+
cache: metric(cached)
|
|
1524
1645
|
}
|
|
1525
|
-
];
|
|
1646
|
+
}];
|
|
1526
1647
|
}
|
|
1527
1648
|
if (method === "account/rateLimits/updated") {
|
|
1528
|
-
|
|
1529
|
-
return [
|
|
1530
|
-
{
|
|
1531
|
-
kind: "telemetry",
|
|
1532
|
-
name: "rate_limits",
|
|
1533
|
-
source: "codex_account_rate_limits_updated",
|
|
1534
|
-
attrs: {
|
|
1535
|
-
limitId: r.limitId,
|
|
1536
|
-
planType: r.planType,
|
|
1537
|
-
usedPercent: r.usedPercent,
|
|
1538
|
-
windowDurationMins: r.windowDurationMins,
|
|
1539
|
-
resetsAt: r.resetsAt
|
|
1540
|
-
}
|
|
1541
|
-
}
|
|
1542
|
-
];
|
|
1649
|
+
return [mapCodexQuotaSnapshots([params?.rateLimits ?? params ?? {}], sourceEpoch)];
|
|
1543
1650
|
}
|
|
1544
1651
|
return [];
|
|
1545
1652
|
}
|
|
1546
1653
|
|
|
1547
1654
|
// agent-driver/dist/adapters/codex/normalizer.js
|
|
1655
|
+
import { randomBytes } from "node:crypto";
|
|
1656
|
+
var codexQuotaSourceEpoch = randomBytes(16).toString("base64url");
|
|
1657
|
+
var codexQuotaSourceGeneration = 0;
|
|
1658
|
+
var codexAccountFingerprint = null;
|
|
1659
|
+
function rotateCodexQuotaSource() {
|
|
1660
|
+
codexQuotaSourceEpoch = randomBytes(16).toString("base64url");
|
|
1661
|
+
codexQuotaSourceGeneration += 1;
|
|
1662
|
+
codexAccountFingerprint = null;
|
|
1663
|
+
}
|
|
1664
|
+
function observeCodexAccount(result) {
|
|
1665
|
+
const account = result?.account;
|
|
1666
|
+
const fingerprint = account && typeof account === "object" ? JSON.stringify([
|
|
1667
|
+
account.type ?? "unknown",
|
|
1668
|
+
account.email ?? null,
|
|
1669
|
+
account.planType ?? account.plan_type ?? null
|
|
1670
|
+
]) : "none";
|
|
1671
|
+
if (codexAccountFingerprint !== null && codexAccountFingerprint !== fingerprint) {
|
|
1672
|
+
rotateCodexQuotaSource();
|
|
1673
|
+
}
|
|
1674
|
+
codexAccountFingerprint = fingerprint;
|
|
1675
|
+
}
|
|
1548
1676
|
function normalizeFileChangeInput(item) {
|
|
1549
1677
|
const paths = [];
|
|
1550
1678
|
const seen = new Set;
|
|
@@ -1567,6 +1695,12 @@ function normalizeFileChangeInput(item) {
|
|
|
1567
1695
|
}
|
|
1568
1696
|
|
|
1569
1697
|
class CodexEventNormalizer {
|
|
1698
|
+
quotaReadRequestIds = new Set;
|
|
1699
|
+
accountReadRequestIds = new Set;
|
|
1700
|
+
rateLimitSnapshots = new Map;
|
|
1701
|
+
quotaSnapshotInitialized = false;
|
|
1702
|
+
quotaSourceGeneration = codexQuotaSourceGeneration;
|
|
1703
|
+
pendingTurnUsage = null;
|
|
1570
1704
|
threadId = null;
|
|
1571
1705
|
turnId = null;
|
|
1572
1706
|
terminalTurn = null;
|
|
@@ -1577,10 +1711,69 @@ class CodexEventNormalizer {
|
|
|
1577
1711
|
get currentTurnId() {
|
|
1578
1712
|
return this.turnId;
|
|
1579
1713
|
}
|
|
1714
|
+
registerQuotaReadRequest(requestId) {
|
|
1715
|
+
this.quotaReadRequestIds.add(requestId);
|
|
1716
|
+
}
|
|
1717
|
+
registerAccountReadRequest(requestId) {
|
|
1718
|
+
this.accountReadRequestIds.add(requestId);
|
|
1719
|
+
}
|
|
1720
|
+
syncQuotaSourceGeneration() {
|
|
1721
|
+
if (this.quotaSourceGeneration === codexQuotaSourceGeneration)
|
|
1722
|
+
return;
|
|
1723
|
+
this.quotaSourceGeneration = codexQuotaSourceGeneration;
|
|
1724
|
+
this.rateLimitSnapshots.clear();
|
|
1725
|
+
this.quotaSnapshotInitialized = false;
|
|
1726
|
+
}
|
|
1727
|
+
quotaSnapshots(value) {
|
|
1728
|
+
const byLimitId = value?.rateLimitsByLimitId ?? value?.rate_limits_by_limit_id;
|
|
1729
|
+
if (byLimitId && typeof byLimitId === "object" && !Array.isArray(byLimitId)) {
|
|
1730
|
+
return Object.entries(byLimitId).flatMap(([key2, snapshot2]) => {
|
|
1731
|
+
if (!snapshot2 || typeof snapshot2 !== "object" || Array.isArray(snapshot2))
|
|
1732
|
+
return [];
|
|
1733
|
+
return [[key2, {
|
|
1734
|
+
...snapshot2,
|
|
1735
|
+
limitId: snapshot2.limitId ?? snapshot2.limit_id ?? key2
|
|
1736
|
+
}]];
|
|
1737
|
+
});
|
|
1738
|
+
}
|
|
1739
|
+
const snapshot = value?.rateLimits ?? value?.rate_limits ?? value;
|
|
1740
|
+
if (!snapshot || typeof snapshot !== "object" || Array.isArray(snapshot))
|
|
1741
|
+
return [];
|
|
1742
|
+
const record = snapshot;
|
|
1743
|
+
const key = typeof record.limitId === "string" ? record.limitId : typeof record.limit_id === "string" ? record.limit_id : "codex";
|
|
1744
|
+
return [[key, { ...record, limitId: key }]];
|
|
1745
|
+
}
|
|
1746
|
+
replaceQuotaSnapshots(value) {
|
|
1747
|
+
this.syncQuotaSourceGeneration();
|
|
1748
|
+
this.rateLimitSnapshots.clear();
|
|
1749
|
+
for (const [key, snapshot] of this.quotaSnapshots(value)) {
|
|
1750
|
+
this.rateLimitSnapshots.set(key, snapshot);
|
|
1751
|
+
}
|
|
1752
|
+
this.quotaSnapshotInitialized = true;
|
|
1753
|
+
return [mapCodexQuotaSnapshots([...this.rateLimitSnapshots.values()], codexQuotaSourceEpoch)];
|
|
1754
|
+
}
|
|
1755
|
+
mergeQuotaSnapshots(value) {
|
|
1756
|
+
this.syncQuotaSourceGeneration();
|
|
1757
|
+
for (const [key, update] of this.quotaSnapshots(value)) {
|
|
1758
|
+
const merged = {
|
|
1759
|
+
...this.rateLimitSnapshots.get(key) ?? {},
|
|
1760
|
+
limitId: key
|
|
1761
|
+
};
|
|
1762
|
+
for (const [field, fieldValue] of Object.entries(update)) {
|
|
1763
|
+
if (fieldValue !== undefined && fieldValue !== null)
|
|
1764
|
+
merged[field] = fieldValue;
|
|
1765
|
+
}
|
|
1766
|
+
this.rateLimitSnapshots.set(key, merged);
|
|
1767
|
+
}
|
|
1768
|
+
if (!this.quotaSnapshotInitialized)
|
|
1769
|
+
return [];
|
|
1770
|
+
return [mapCodexQuotaSnapshots([...this.rateLimitSnapshots.values()], codexQuotaSourceEpoch)];
|
|
1771
|
+
}
|
|
1580
1772
|
adoptThreadId(threadId) {
|
|
1581
1773
|
if (threadId !== this.threadId) {
|
|
1582
1774
|
this.turnId = null;
|
|
1583
1775
|
this.terminalTurn = null;
|
|
1776
|
+
this.pendingTurnUsage = null;
|
|
1584
1777
|
}
|
|
1585
1778
|
this.threadId = threadId;
|
|
1586
1779
|
}
|
|
@@ -1598,6 +1791,25 @@ class CodexEventNormalizer {
|
|
|
1598
1791
|
const msg = tryParseJsonLine(line);
|
|
1599
1792
|
if (!msg)
|
|
1600
1793
|
return [];
|
|
1794
|
+
if (msg?.id !== undefined && this.accountReadRequestIds.delete(msg.id)) {
|
|
1795
|
+
if (!msg.error) {
|
|
1796
|
+
observeCodexAccount(msg.result);
|
|
1797
|
+
this.syncQuotaSourceGeneration();
|
|
1798
|
+
}
|
|
1799
|
+
return [];
|
|
1800
|
+
}
|
|
1801
|
+
if (msg?.id !== undefined && this.quotaReadRequestIds.delete(msg.id)) {
|
|
1802
|
+
this.syncQuotaSourceGeneration();
|
|
1803
|
+
if (msg.error) {
|
|
1804
|
+
return [{
|
|
1805
|
+
kind: "telemetry",
|
|
1806
|
+
name: "rate_limits",
|
|
1807
|
+
source: "codex_account_rate_limits_read",
|
|
1808
|
+
quota: { status: "error", sourceEpoch: codexQuotaSourceEpoch, code: "provider_error", retryable: true }
|
|
1809
|
+
}];
|
|
1810
|
+
}
|
|
1811
|
+
return this.replaceQuotaSnapshots(msg.result ?? {});
|
|
1812
|
+
}
|
|
1601
1813
|
if (msg?.error && msg.id !== undefined) {
|
|
1602
1814
|
return [{ kind: "error", message: msg.error?.message ?? "Codex RPC error" }];
|
|
1603
1815
|
}
|
|
@@ -1622,6 +1834,7 @@ class CodexEventNormalizer {
|
|
|
1622
1834
|
return [];
|
|
1623
1835
|
this.turnId = params.turn.id;
|
|
1624
1836
|
this.terminalTurn = null;
|
|
1837
|
+
this.pendingTurnUsage = null;
|
|
1625
1838
|
return [
|
|
1626
1839
|
{
|
|
1627
1840
|
kind: "turn_owner",
|
|
@@ -1650,24 +1863,36 @@ class CodexEventNormalizer {
|
|
|
1650
1863
|
case "turn/completed":
|
|
1651
1864
|
if (!this.acceptRootTerminal(params))
|
|
1652
1865
|
return [];
|
|
1866
|
+
const usage = this.pendingTurnUsage;
|
|
1867
|
+
this.pendingTurnUsage = null;
|
|
1653
1868
|
if (params.turn.status === "failed") {
|
|
1654
1869
|
return [
|
|
1870
|
+
...usage ? [usage] : [],
|
|
1655
1871
|
{ kind: "error", message: "Codex turn failed" },
|
|
1656
1872
|
{ kind: "turn_end", sessionId: this.threadId ?? undefined, turnOwner: this.turnReceipt(params.threadId, params.turn.id) }
|
|
1657
1873
|
];
|
|
1658
1874
|
}
|
|
1659
1875
|
if (params.turn.status === "interrupted") {
|
|
1660
|
-
return [{ kind: "error", message: "Codex turn interrupted" }, { kind: "turn_end", sessionId: this.threadId ?? undefined, turnOwner: this.turnReceipt(params.threadId, params.turn.id) }];
|
|
1876
|
+
return [...usage ? [usage] : [], { kind: "error", message: "Codex turn interrupted" }, { kind: "turn_end", sessionId: this.threadId ?? undefined, turnOwner: this.turnReceipt(params.threadId, params.turn.id) }];
|
|
1661
1877
|
}
|
|
1662
|
-
return [{ kind: "turn_end", sessionId: this.threadId ?? undefined, turnOwner: this.turnReceipt(params.threadId, params.turn.id) }];
|
|
1878
|
+
return [...usage ? [usage] : [], { kind: "turn_end", sessionId: this.threadId ?? undefined, turnOwner: this.turnReceipt(params.threadId, params.turn.id) }];
|
|
1663
1879
|
case "error":
|
|
1664
1880
|
if (params?.willRetry === true) {
|
|
1665
1881
|
return [{ kind: "runtime_recovery", stage: "retrying", source: "codex_stream" }];
|
|
1666
1882
|
}
|
|
1667
1883
|
return [{ kind: "error", message: params?.error?.message ?? params?.message ?? "Codex error" }];
|
|
1668
|
-
case "thread/tokenUsage/updated":
|
|
1884
|
+
case "thread/tokenUsage/updated": {
|
|
1885
|
+
const usage2 = mapCodexTelemetry(method, params, codexQuotaSourceEpoch)[0];
|
|
1886
|
+
if (usage2)
|
|
1887
|
+
this.pendingTurnUsage = usage2;
|
|
1888
|
+
return [];
|
|
1889
|
+
}
|
|
1669
1890
|
case "account/rateLimits/updated":
|
|
1670
|
-
return
|
|
1891
|
+
return this.mergeQuotaSnapshots(params);
|
|
1892
|
+
case "account/updated":
|
|
1893
|
+
rotateCodexQuotaSource();
|
|
1894
|
+
this.syncQuotaSourceGeneration();
|
|
1895
|
+
return [];
|
|
1671
1896
|
default:
|
|
1672
1897
|
return [];
|
|
1673
1898
|
}
|
|
@@ -1780,7 +2005,102 @@ function resolveCodexHomeRootFromEnv(env = process.env, opts = {}) {
|
|
|
1780
2005
|
return path5.join(opts.defaultHomeDir ?? os.homedir(), ".codex");
|
|
1781
2006
|
}
|
|
1782
2007
|
|
|
2008
|
+
// agent-driver/dist/internal/errors.js
|
|
2009
|
+
var MAX_PUBLIC_ERROR_MESSAGE = 1000;
|
|
2010
|
+
var CREDENTIAL_NAME = String.raw`(?:[A-Za-z0-9]{1,32}[_-]){0,4}(?:api[_-]?key|access[_-]?key|secret(?:[_-]?access[_-]?key)?|client[_-]?secret|access[_-]?token|auth(?:orization)?|password|passwd|token|voucher)(?:[_-][A-Za-z0-9]{1,32}){0,4}`;
|
|
2011
|
+
var QUOTED_CREDENTIAL_ASSIGNMENT = new RegExp(String.raw`(["'])(${CREDENTIAL_NAME})\1\s*[:=]\s*(["'])[^"'\r\n]*\3`, "gi");
|
|
2012
|
+
var CREDENTIAL_ASSIGNMENT = new RegExp(String.raw`(?<![A-Za-z0-9_-])((?:${CREDENTIAL_NAME})\s*[:=]\s*)(?!\[redacted\])[^\s,;}\]]+`, "gi");
|
|
2013
|
+
function scrubDriverErrorMessage(value, fallback = "Runtime operation failed") {
|
|
2014
|
+
const text = value instanceof Error ? value.message : String(value ?? "");
|
|
2015
|
+
const scrubbed = text.replace(/\b(?:cmk|cmt|crk)_[A-Za-z0-9_-]+\b/g, "[redacted-token]").replace(/(Authorization\s*:\s*)(?:Bearer|Basic)\s+[^\s,;]+/gi, "$1[redacted]").replace(/Bearer\s+[A-Za-z0-9._\-]+/gi, "Bearer [redacted]").replace(/\b(?:sk|sk-ant|sk-proj|xox[abprs])-[A-Za-z0-9._\-]+/gi, "[redacted-token]").replace(QUOTED_CREDENTIAL_ASSIGNMENT, "$1$2$1:$3[redacted]$3").replace(CREDENTIAL_ASSIGNMENT, "$1[redacted]").replace(/(?<![A-Za-z0-9._%+\-])[A-Za-z0-9._%+\-]{1,320}@[A-Za-z0-9.\-]{1,255}\.[A-Za-z]{2,63}/g, "[redacted-email]").replace(/([?&])([^=\s]+)=([^&\s]+)/g, "$1$2=[redacted]").replace(/\/(?:Users|home)\/[^\r\n,;]+/g, "[redacted-path]").replace(/[A-Za-z]:\\Users\\[^\r\n,;]+/gi, "[redacted-path]").replace(/\\\\[^\\\s]+\\[^\r\n,;]+/g, "[redacted-path]").replace(/(?:[A-Za-z]:\\|\/)(?:[^\s/:]+[\\/]){1,}[^\s:]*/g, "[redacted-path]").trim();
|
|
2016
|
+
return (scrubbed || fallback).slice(0, MAX_PUBLIC_ERROR_MESSAGE);
|
|
2017
|
+
}
|
|
2018
|
+
function scrubDriverError(error) {
|
|
2019
|
+
return {
|
|
2020
|
+
...error,
|
|
2021
|
+
code: stableErrorCode(error.code, "runtime_error"),
|
|
2022
|
+
message: scrubDriverErrorMessage(error.message),
|
|
2023
|
+
...error.details ? { details: scrubDetails(error.details) } : {}
|
|
2024
|
+
};
|
|
2025
|
+
}
|
|
2026
|
+
function scrubDetails(details) {
|
|
2027
|
+
const scrubValue = (value, key) => {
|
|
2028
|
+
if (key && /api[_-]?key|access[_-]?token|auth(?:orization)?|password|passwd|secret|token/i.test(key)) {
|
|
2029
|
+
return "[redacted]";
|
|
2030
|
+
}
|
|
2031
|
+
if (typeof value === "string")
|
|
2032
|
+
return scrubDriverErrorMessage(value, "[redacted]");
|
|
2033
|
+
if (Array.isArray(value))
|
|
2034
|
+
return value.map((item) => scrubValue(item));
|
|
2035
|
+
if (value && typeof value === "object") {
|
|
2036
|
+
return Object.fromEntries(Object.entries(value).map(([childKey, child]) => [
|
|
2037
|
+
childKey,
|
|
2038
|
+
scrubValue(child, childKey)
|
|
2039
|
+
]));
|
|
2040
|
+
}
|
|
2041
|
+
return value;
|
|
2042
|
+
};
|
|
2043
|
+
return scrubValue(details);
|
|
2044
|
+
}
|
|
2045
|
+
function stableErrorCode(value, fallback) {
|
|
2046
|
+
const code = String(value ?? "");
|
|
2047
|
+
return /^[A-Za-z0-9_.-]{1,100}$/.test(code) ? code : fallback;
|
|
2048
|
+
}
|
|
2049
|
+
|
|
2050
|
+
// agent-driver/dist/internal/modelCatalog.js
|
|
2051
|
+
var RUNTIME_MODEL_CATALOG_MAX = 512;
|
|
2052
|
+
var RUNTIME_MODEL_ID_MAX = 100;
|
|
2053
|
+
function normalizeRuntimeModelId(value) {
|
|
2054
|
+
if (typeof value !== "string")
|
|
2055
|
+
return;
|
|
2056
|
+
const id = value.trim();
|
|
2057
|
+
if (!id || id.length > RUNTIME_MODEL_ID_MAX || /\s/.test(id))
|
|
2058
|
+
return;
|
|
2059
|
+
return id;
|
|
2060
|
+
}
|
|
2061
|
+
function catalogFromIds(ids) {
|
|
2062
|
+
const seen = new Set;
|
|
2063
|
+
const models = [];
|
|
2064
|
+
for (const rawId of ids) {
|
|
2065
|
+
const id = normalizeRuntimeModelId(rawId);
|
|
2066
|
+
if (!id || seen.has(id))
|
|
2067
|
+
continue;
|
|
2068
|
+
if (models.length >= RUNTIME_MODEL_CATALOG_MAX)
|
|
2069
|
+
return;
|
|
2070
|
+
seen.add(id);
|
|
2071
|
+
models.push({ id, supportedReasoningEfforts: [] });
|
|
2072
|
+
}
|
|
2073
|
+
if (models.length === 0)
|
|
2074
|
+
return;
|
|
2075
|
+
return { updateMode: "unsupported", models };
|
|
2076
|
+
}
|
|
2077
|
+
function parseOpenCodeModelCatalog(output) {
|
|
2078
|
+
const ids = output.split(/\r?\n/).flatMap((line) => {
|
|
2079
|
+
const id = normalizeRuntimeModelId(line);
|
|
2080
|
+
return id && /^[^/]+\/.+$/.test(id) ? [id] : [];
|
|
2081
|
+
});
|
|
2082
|
+
return catalogFromIds(ids);
|
|
2083
|
+
}
|
|
2084
|
+
function parsePiModelCatalog(values) {
|
|
2085
|
+
if (!Array.isArray(values))
|
|
2086
|
+
return;
|
|
2087
|
+
const ids = values.flatMap((value) => {
|
|
2088
|
+
if (!value || typeof value !== "object")
|
|
2089
|
+
return [];
|
|
2090
|
+
const model = value;
|
|
2091
|
+
const provider = normalizeRuntimeModelId(model.provider);
|
|
2092
|
+
const id = normalizeRuntimeModelId(model.id);
|
|
2093
|
+
return provider && id && !provider.includes("/") ? [`${provider}/${id}`] : [];
|
|
2094
|
+
});
|
|
2095
|
+
return catalogFromIds(ids);
|
|
2096
|
+
}
|
|
2097
|
+
|
|
1783
2098
|
// agent-driver/dist/adapters/codex/index.js
|
|
2099
|
+
var SETTINGS_UPDATE_TIMEOUT_MS = 5000;
|
|
2100
|
+
var MODEL_LIST_TIMEOUT_MS = 5000;
|
|
2101
|
+
var MODEL_LIST_OUTPUT_MAX_BYTES = 1024 * 1024;
|
|
2102
|
+
var MODEL_LIST_MAX = RUNTIME_MODEL_CATALOG_MAX;
|
|
2103
|
+
var MODEL_EFFORT_MAX = 16;
|
|
1784
2104
|
function isCodexMissingRolloutError(message) {
|
|
1785
2105
|
return /\bno\s+rollout\s+found\b/i.test(message) || /\bmissing\s+rollout\b/i.test(message) || /\brollout\b.*\b(not found|missing)\b/i.test(message) || /\b(not found|missing)\b.*\brollout\b/i.test(message);
|
|
1786
2106
|
}
|
|
@@ -1801,19 +2121,173 @@ class CodexDriver {
|
|
|
1801
2121
|
}
|
|
1802
2122
|
};
|
|
1803
2123
|
eventNormalizer = new CodexEventNormalizer;
|
|
2124
|
+
pendingAccountReadRequestIds = new Set;
|
|
1804
2125
|
requestId = 0;
|
|
1805
2126
|
codexHomeRoot = null;
|
|
1806
2127
|
proc = null;
|
|
1807
2128
|
pendingInitialPrompt = null;
|
|
1808
2129
|
pendingResumeFallbackParams = null;
|
|
2130
|
+
pendingSettingsUpdates = new Map;
|
|
1809
2131
|
nextRequestId() {
|
|
1810
2132
|
return ++this.requestId;
|
|
1811
2133
|
}
|
|
2134
|
+
requestAccountQuotaSnapshot() {
|
|
2135
|
+
if (!this.proc?.stdin || this.proc.stdin.destroyed)
|
|
2136
|
+
return;
|
|
2137
|
+
const accountReadRequestId = this.nextRequestId();
|
|
2138
|
+
this.pendingAccountReadRequestIds.add(accountReadRequestId);
|
|
2139
|
+
this.eventNormalizer.registerAccountReadRequest(accountReadRequestId);
|
|
2140
|
+
this.proc.stdin.write(jsonRpcRequest("account/read", { refreshToken: false }, accountReadRequestId) + `
|
|
2141
|
+
`);
|
|
2142
|
+
}
|
|
2143
|
+
requestQuotaSnapshot() {
|
|
2144
|
+
if (!this.proc?.stdin || this.proc.stdin.destroyed)
|
|
2145
|
+
return;
|
|
2146
|
+
const quotaReadRequestId = this.nextRequestId();
|
|
2147
|
+
this.eventNormalizer.registerQuotaReadRequest(quotaReadRequestId);
|
|
2148
|
+
this.proc.stdin.write(jsonRpcRequest("account/rateLimits/read", {}, quotaReadRequestId) + `
|
|
2149
|
+
`);
|
|
2150
|
+
}
|
|
1812
2151
|
get codexHome() {
|
|
1813
2152
|
return this.codexHomeRoot;
|
|
1814
2153
|
}
|
|
1815
|
-
probe(command) {
|
|
1816
|
-
|
|
2154
|
+
async probe(command) {
|
|
2155
|
+
const result = await probeCliRuntime("codex", {}, command);
|
|
2156
|
+
if (result.status !== "healthy")
|
|
2157
|
+
return result;
|
|
2158
|
+
return {
|
|
2159
|
+
...result,
|
|
2160
|
+
reasoning: await this.probeReasoningCatalog(command)
|
|
2161
|
+
};
|
|
2162
|
+
}
|
|
2163
|
+
async probeReasoningCatalog(command) {
|
|
2164
|
+
const spec = resolveSpawnSpec("codex", ["app-server", "--listen", "stdio://"], command);
|
|
2165
|
+
let proc;
|
|
2166
|
+
try {
|
|
2167
|
+
proc = spawnAgentProcess(spec.command, spec.args, {
|
|
2168
|
+
cwd: process.cwd(),
|
|
2169
|
+
env: { ...process.env, CI: "1" },
|
|
2170
|
+
shell: spec.shell
|
|
2171
|
+
});
|
|
2172
|
+
} catch {
|
|
2173
|
+
return;
|
|
2174
|
+
}
|
|
2175
|
+
return new Promise((resolve2) => {
|
|
2176
|
+
let settled = false;
|
|
2177
|
+
let buffer = "";
|
|
2178
|
+
let outputBytes = 0;
|
|
2179
|
+
let nextId = 0;
|
|
2180
|
+
let initializeId = 0;
|
|
2181
|
+
let listId = 0;
|
|
2182
|
+
const models = [];
|
|
2183
|
+
const seenModels = new Set;
|
|
2184
|
+
let overflow = false;
|
|
2185
|
+
let defaultModelId;
|
|
2186
|
+
const finish = (catalog) => {
|
|
2187
|
+
if (settled)
|
|
2188
|
+
return;
|
|
2189
|
+
settled = true;
|
|
2190
|
+
clearTimeout(timer);
|
|
2191
|
+
const done = proc.pid ? killProcessTree(proc.pid, { graceMs: 250 }).catch(() => {}) : Promise.resolve().then(() => {
|
|
2192
|
+
proc.kill("SIGTERM");
|
|
2193
|
+
});
|
|
2194
|
+
done.finally(() => resolve2(catalog));
|
|
2195
|
+
};
|
|
2196
|
+
const requestModelPage = (cursor) => {
|
|
2197
|
+
listId = ++nextId;
|
|
2198
|
+
proc.stdin?.write(jsonRpcRequest("model/list", { limit: Math.min(MODEL_LIST_MAX - models.length, MODEL_LIST_MAX), includeHidden: false, ...cursor ? { cursor } : {} }, listId) + `
|
|
2199
|
+
`);
|
|
2200
|
+
};
|
|
2201
|
+
const consumeModel = (value) => {
|
|
2202
|
+
if (!value || typeof value !== "object")
|
|
2203
|
+
return;
|
|
2204
|
+
const model = value;
|
|
2205
|
+
const id = normalizeRuntimeModelId(model.id);
|
|
2206
|
+
if (!id || seenModels.has(id))
|
|
2207
|
+
return;
|
|
2208
|
+
if (models.length >= MODEL_LIST_MAX) {
|
|
2209
|
+
overflow = true;
|
|
2210
|
+
return;
|
|
2211
|
+
}
|
|
2212
|
+
const rawOptions = Array.isArray(model.supportedReasoningEfforts) ? model.supportedReasoningEfforts : [];
|
|
2213
|
+
const seenEfforts = new Set;
|
|
2214
|
+
const supportedReasoningEfforts = rawOptions.flatMap((raw) => {
|
|
2215
|
+
if (!raw || typeof raw !== "object")
|
|
2216
|
+
return [];
|
|
2217
|
+
const option = raw;
|
|
2218
|
+
const value2 = typeof option.reasoningEffort === "string" ? option.reasoningEffort.trim() : "";
|
|
2219
|
+
if (!value2 || value2.length > 32 || !/^[A-Za-z0-9._-]+$/.test(value2) || seenEfforts.has(value2))
|
|
2220
|
+
return [];
|
|
2221
|
+
seenEfforts.add(value2);
|
|
2222
|
+
const description = typeof option.description === "string" ? option.description.slice(0, 256) : undefined;
|
|
2223
|
+
return [{ value: value2, ...description ? { description } : {} }];
|
|
2224
|
+
}).slice(0, MODEL_EFFORT_MAX);
|
|
2225
|
+
const candidateDefault = typeof model.defaultReasoningEffort === "string" ? model.defaultReasoningEffort : undefined;
|
|
2226
|
+
seenModels.add(id);
|
|
2227
|
+
if (model.isDefault === true)
|
|
2228
|
+
defaultModelId = id;
|
|
2229
|
+
models.push({
|
|
2230
|
+
id,
|
|
2231
|
+
supportedReasoningEfforts,
|
|
2232
|
+
...candidateDefault && supportedReasoningEfforts.some((item) => item.value === candidateDefault) ? { defaultReasoningEffort: candidateDefault } : {}
|
|
2233
|
+
});
|
|
2234
|
+
};
|
|
2235
|
+
const onLine = (line) => {
|
|
2236
|
+
let message;
|
|
2237
|
+
try {
|
|
2238
|
+
message = JSON.parse(line);
|
|
2239
|
+
} catch {
|
|
2240
|
+
return;
|
|
2241
|
+
}
|
|
2242
|
+
if (message.id === initializeId) {
|
|
2243
|
+
if (message.error)
|
|
2244
|
+
return finish();
|
|
2245
|
+
requestModelPage();
|
|
2246
|
+
return;
|
|
2247
|
+
}
|
|
2248
|
+
if (message.id !== listId)
|
|
2249
|
+
return;
|
|
2250
|
+
if (message.error || !message.result || typeof message.result !== "object")
|
|
2251
|
+
return finish();
|
|
2252
|
+
const result = message.result;
|
|
2253
|
+
for (const model of Array.isArray(result.data) ? result.data : [])
|
|
2254
|
+
consumeModel(model);
|
|
2255
|
+
if (overflow)
|
|
2256
|
+
return finish();
|
|
2257
|
+
const cursor = typeof result.nextCursor === "string" ? result.nextCursor : undefined;
|
|
2258
|
+
if (cursor && models.length >= MODEL_LIST_MAX)
|
|
2259
|
+
return finish();
|
|
2260
|
+
if (cursor)
|
|
2261
|
+
return requestModelPage(cursor);
|
|
2262
|
+
if (models.length === 0)
|
|
2263
|
+
return finish();
|
|
2264
|
+
finish({
|
|
2265
|
+
updateMode: "live_next_turn",
|
|
2266
|
+
...defaultModelId ? { defaultModelId } : {},
|
|
2267
|
+
models
|
|
2268
|
+
});
|
|
2269
|
+
};
|
|
2270
|
+
const timer = setTimeout(() => finish(), MODEL_LIST_TIMEOUT_MS);
|
|
2271
|
+
timer.unref?.();
|
|
2272
|
+
proc.stdout?.on("data", (chunk) => {
|
|
2273
|
+
const text = chunk.toString();
|
|
2274
|
+
outputBytes += Buffer.byteLength(text);
|
|
2275
|
+
if (outputBytes > MODEL_LIST_OUTPUT_MAX_BYTES)
|
|
2276
|
+
return finish();
|
|
2277
|
+
buffer += text;
|
|
2278
|
+
const lines = buffer.split(`
|
|
2279
|
+
`);
|
|
2280
|
+
buffer = lines.pop() ?? "";
|
|
2281
|
+
for (const line of lines)
|
|
2282
|
+
if (line.trim())
|
|
2283
|
+
onLine(line);
|
|
2284
|
+
});
|
|
2285
|
+
proc.on("error", () => finish());
|
|
2286
|
+
proc.on("exit", () => finish());
|
|
2287
|
+
initializeId = ++nextId;
|
|
2288
|
+
proc.stdin?.write(jsonRpcRequest("initialize", { clientInfo: { name: "alook-agent-driver-probe", version: "0.1.24" }, capabilities: { experimentalApi: true } }, initializeId) + `
|
|
2289
|
+
`);
|
|
2290
|
+
});
|
|
1817
2291
|
}
|
|
1818
2292
|
async openLane(ctx, options) {
|
|
1819
2293
|
return createProcessLane(this, ctx, { onRawStdoutLine: options?.onRawStdoutLine });
|
|
@@ -1829,6 +2303,9 @@ class CodexDriver {
|
|
|
1829
2303
|
shell: spec.shell
|
|
1830
2304
|
});
|
|
1831
2305
|
this.proc = proc;
|
|
2306
|
+
proc.once("exit", () => {
|
|
2307
|
+
this.failPendingSettingsUpdates("settings_process_exited", "Codex exited before acknowledging the settings update");
|
|
2308
|
+
});
|
|
1832
2309
|
const initialPrompt = ctx.prompt?.trim() ? ctx.prompt : null;
|
|
1833
2310
|
this.pendingInitialPrompt = initialPrompt;
|
|
1834
2311
|
queueMicrotask(() => {
|
|
@@ -1857,11 +2334,21 @@ class CodexDriver {
|
|
|
1857
2334
|
proc.stdin?.write(jsonRpcRequest("thread/start", freshParams, this.nextRequestId()) + `
|
|
1858
2335
|
`);
|
|
1859
2336
|
}
|
|
2337
|
+
this.requestAccountQuotaSnapshot();
|
|
1860
2338
|
});
|
|
1861
2339
|
return { process: proc };
|
|
1862
2340
|
}
|
|
1863
2341
|
normalizeLine(line) {
|
|
2342
|
+
const settingsResponse = this.consumeSettingsUpdateResponse(line);
|
|
2343
|
+
if (settingsResponse)
|
|
2344
|
+
return [];
|
|
2345
|
+
const parsed = tryParseJsonLine(line);
|
|
1864
2346
|
const events = this.eventNormalizer.normalizeLine(line);
|
|
2347
|
+
if (typeof parsed?.id === "number" && this.pendingAccountReadRequestIds.delete(parsed.id)) {
|
|
2348
|
+
this.requestQuotaSnapshot();
|
|
2349
|
+
}
|
|
2350
|
+
if (parsed?.method === "account/updated")
|
|
2351
|
+
this.requestAccountQuotaSnapshot();
|
|
1865
2352
|
if (this.pendingResumeFallbackParams && this.proc?.stdin && !this.proc.stdin.destroyed) {
|
|
1866
2353
|
const rolloutErr = events.find((e) => e.kind === "error" && isCodexMissingRolloutError(e.message));
|
|
1867
2354
|
if (rolloutErr) {
|
|
@@ -1885,6 +2372,90 @@ class CodexDriver {
|
|
|
1885
2372
|
}
|
|
1886
2373
|
return events;
|
|
1887
2374
|
}
|
|
2375
|
+
updateSettings(input) {
|
|
2376
|
+
const threadId = this.eventNormalizer.currentSessionId;
|
|
2377
|
+
const stdin = this.proc?.stdin;
|
|
2378
|
+
if (!threadId || !stdin || stdin.destroyed || stdin.writableEnded || stdin.writable === false) {
|
|
2379
|
+
return Promise.resolve({
|
|
2380
|
+
status: "failed",
|
|
2381
|
+
error: this.settingsError("process", "settings_thread_unavailable", "Codex thread is not available for a settings update", true)
|
|
2382
|
+
});
|
|
2383
|
+
}
|
|
2384
|
+
const id = this.nextRequestId();
|
|
2385
|
+
return new Promise((resolve2) => {
|
|
2386
|
+
const timer = setTimeout(() => {
|
|
2387
|
+
if (!this.pendingSettingsUpdates.delete(id))
|
|
2388
|
+
return;
|
|
2389
|
+
resolve2({
|
|
2390
|
+
status: "failed",
|
|
2391
|
+
error: this.settingsError("timeout", "settings_update_timeout", "Codex did not acknowledge the settings update before the deadline", true)
|
|
2392
|
+
});
|
|
2393
|
+
}, SETTINGS_UPDATE_TIMEOUT_MS);
|
|
2394
|
+
timer.unref?.();
|
|
2395
|
+
this.pendingSettingsUpdates.set(id, { resolve: resolve2, timer });
|
|
2396
|
+
try {
|
|
2397
|
+
stdin.write(jsonRpcRequest("thread/settings/update", { threadId, effort: input.reasoningEffort }, id) + `
|
|
2398
|
+
`);
|
|
2399
|
+
} catch (error) {
|
|
2400
|
+
clearTimeout(timer);
|
|
2401
|
+
this.pendingSettingsUpdates.delete(id);
|
|
2402
|
+
resolve2({
|
|
2403
|
+
status: "failed",
|
|
2404
|
+
error: this.settingsError("process", "settings_update_write_failed", String(error), true)
|
|
2405
|
+
});
|
|
2406
|
+
}
|
|
2407
|
+
});
|
|
2408
|
+
}
|
|
2409
|
+
consumeSettingsUpdateResponse(line) {
|
|
2410
|
+
let value;
|
|
2411
|
+
try {
|
|
2412
|
+
value = JSON.parse(line);
|
|
2413
|
+
} catch {
|
|
2414
|
+
return false;
|
|
2415
|
+
}
|
|
2416
|
+
if (!value || typeof value !== "object")
|
|
2417
|
+
return false;
|
|
2418
|
+
const record = value;
|
|
2419
|
+
if (typeof record.id !== "number")
|
|
2420
|
+
return false;
|
|
2421
|
+
const pending = this.pendingSettingsUpdates.get(record.id);
|
|
2422
|
+
if (!pending)
|
|
2423
|
+
return false;
|
|
2424
|
+
clearTimeout(pending.timer);
|
|
2425
|
+
this.pendingSettingsUpdates.delete(record.id);
|
|
2426
|
+
const error = record.error;
|
|
2427
|
+
if (!error || typeof error !== "object") {
|
|
2428
|
+
pending.resolve({ status: "applied" });
|
|
2429
|
+
return true;
|
|
2430
|
+
}
|
|
2431
|
+
const rpcError = error;
|
|
2432
|
+
const message = typeof rpcError.message === "string" ? rpcError.message : "Codex rejected the settings update";
|
|
2433
|
+
if (rpcError.code === -32601 || /method\s+not\s+found/i.test(message)) {
|
|
2434
|
+
pending.resolve({
|
|
2435
|
+
status: "unsupported",
|
|
2436
|
+
error: this.settingsError("protocol", "settings_update_unsupported", "Codex does not support live reasoning settings updates", false)
|
|
2437
|
+
});
|
|
2438
|
+
} else {
|
|
2439
|
+
pending.resolve({
|
|
2440
|
+
status: "failed",
|
|
2441
|
+
error: this.settingsError("protocol", "settings_update_rejected", message, true)
|
|
2442
|
+
});
|
|
2443
|
+
}
|
|
2444
|
+
return true;
|
|
2445
|
+
}
|
|
2446
|
+
settingsError(category, code, message, retryable) {
|
|
2447
|
+
return { category, code, message: scrubDriverErrorMessage(message), retryable };
|
|
2448
|
+
}
|
|
2449
|
+
failPendingSettingsUpdates(code, message) {
|
|
2450
|
+
for (const [id, pending] of this.pendingSettingsUpdates) {
|
|
2451
|
+
clearTimeout(pending.timer);
|
|
2452
|
+
this.pendingSettingsUpdates.delete(id);
|
|
2453
|
+
pending.resolve({
|
|
2454
|
+
status: "failed",
|
|
2455
|
+
error: this.settingsError("process", code, message, true)
|
|
2456
|
+
});
|
|
2457
|
+
}
|
|
2458
|
+
}
|
|
1888
2459
|
get currentSessionId() {
|
|
1889
2460
|
return this.eventNormalizer.currentSessionId;
|
|
1890
2461
|
}
|
|
@@ -1906,9 +2477,189 @@ class CodexDriver {
|
|
|
1906
2477
|
|
|
1907
2478
|
// agent-driver/dist/adapters/cursor/acp-lane.js
|
|
1908
2479
|
import { EventEmitter as EventEmitter2 } from "node:events";
|
|
2480
|
+
|
|
2481
|
+
// agent-driver/dist/adapters/cursor/catalog-probe.js
|
|
1909
2482
|
var ACP_PROTOCOL_VERSION = 1;
|
|
1910
|
-
var HANDSHAKE_TIMEOUT_MS = 15000;
|
|
1911
2483
|
var AUTH_METHOD_ID = "cursor_login";
|
|
2484
|
+
var CATALOG_PROBE_TIMEOUT_MS = 15000;
|
|
2485
|
+
var CATALOG_PROBE_OUTPUT_MAX_BYTES = 1024 * 1024;
|
|
2486
|
+
var MODEL_DISPLAY_NAME_MAX = 256;
|
|
2487
|
+
var MODEL_OPTION_NESTING_MAX = 16;
|
|
2488
|
+
function record(value) {
|
|
2489
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
2490
|
+
}
|
|
2491
|
+
function normalizeDisplayName(value) {
|
|
2492
|
+
if (typeof value !== "string")
|
|
2493
|
+
return;
|
|
2494
|
+
const displayName = value.trim();
|
|
2495
|
+
return displayName && displayName.length <= MODEL_DISPLAY_NAME_MAX ? displayName : undefined;
|
|
2496
|
+
}
|
|
2497
|
+
function flattenCursorAcpSelectOptions(value, depth = 0) {
|
|
2498
|
+
if (!Array.isArray(value) || depth > MODEL_OPTION_NESTING_MAX)
|
|
2499
|
+
return [];
|
|
2500
|
+
const options = [];
|
|
2501
|
+
for (const item of value) {
|
|
2502
|
+
if (Array.isArray(item)) {
|
|
2503
|
+
options.push(...flattenCursorAcpSelectOptions(item, depth + 1));
|
|
2504
|
+
continue;
|
|
2505
|
+
}
|
|
2506
|
+
const candidate = record(item);
|
|
2507
|
+
if (!candidate)
|
|
2508
|
+
continue;
|
|
2509
|
+
const exactValue = normalizeRuntimeModelId(candidate.value);
|
|
2510
|
+
if (exactValue) {
|
|
2511
|
+
const name = normalizeDisplayName(candidate.name);
|
|
2512
|
+
options.push({ value: exactValue, ...name ? { name } : {} });
|
|
2513
|
+
}
|
|
2514
|
+
if (Array.isArray(candidate.options)) {
|
|
2515
|
+
options.push(...flattenCursorAcpSelectOptions(candidate.options, depth + 1));
|
|
2516
|
+
}
|
|
2517
|
+
}
|
|
2518
|
+
return options;
|
|
2519
|
+
}
|
|
2520
|
+
function parseCursorAcpModelCatalog(session) {
|
|
2521
|
+
const payload = record(session);
|
|
2522
|
+
const configOptions = Array.isArray(payload?.configOptions) ? payload.configOptions : [];
|
|
2523
|
+
const modelConfig = configOptions.map(record).find((option) => option?.id === "model") ?? null;
|
|
2524
|
+
if (!modelConfig)
|
|
2525
|
+
return;
|
|
2526
|
+
const seen = new Set;
|
|
2527
|
+
const models = [];
|
|
2528
|
+
for (const option of flattenCursorAcpSelectOptions(modelConfig.options)) {
|
|
2529
|
+
if (option.value === "default[]" || seen.has(option.value))
|
|
2530
|
+
continue;
|
|
2531
|
+
if (models.length >= RUNTIME_MODEL_CATALOG_MAX)
|
|
2532
|
+
return;
|
|
2533
|
+
seen.add(option.value);
|
|
2534
|
+
models.push({
|
|
2535
|
+
id: option.value,
|
|
2536
|
+
...option.name ? { displayName: option.name } : {},
|
|
2537
|
+
supportedReasoningEfforts: []
|
|
2538
|
+
});
|
|
2539
|
+
}
|
|
2540
|
+
return models.length > 0 ? { updateMode: "unsupported", models } : undefined;
|
|
2541
|
+
}
|
|
2542
|
+
async function cleanupProbeProcess(process2) {
|
|
2543
|
+
if (process2.pid) {
|
|
2544
|
+
await killProcessTree(process2.pid, { graceMs: 250 }).catch(() => {});
|
|
2545
|
+
return;
|
|
2546
|
+
}
|
|
2547
|
+
if (process2.exitCode === null && process2.signalCode === null)
|
|
2548
|
+
process2.kill("SIGTERM");
|
|
2549
|
+
}
|
|
2550
|
+
async function probeCursorAcpCatalog(command, options = {}) {
|
|
2551
|
+
const cwd = options.cwd ?? process.cwd();
|
|
2552
|
+
const spec = resolveSpawnSpec("cursor-agent", ["acp"], command);
|
|
2553
|
+
let processHandle;
|
|
2554
|
+
try {
|
|
2555
|
+
processHandle = (options.spawn ?? spawnAgentProcess)(spec.command, spec.args, {
|
|
2556
|
+
cwd,
|
|
2557
|
+
env: { ...process.env, CI: "1" },
|
|
2558
|
+
shell: spec.shell
|
|
2559
|
+
});
|
|
2560
|
+
} catch {
|
|
2561
|
+
return;
|
|
2562
|
+
}
|
|
2563
|
+
return new Promise((resolve2) => {
|
|
2564
|
+
let settled = false;
|
|
2565
|
+
let buffer = "";
|
|
2566
|
+
let outputBytes = 0;
|
|
2567
|
+
let requestId = 0;
|
|
2568
|
+
let expectedId = 0;
|
|
2569
|
+
let expectedMethod = "";
|
|
2570
|
+
const finish = (catalog) => {
|
|
2571
|
+
if (settled)
|
|
2572
|
+
return;
|
|
2573
|
+
settled = true;
|
|
2574
|
+
clearTimeout(timer);
|
|
2575
|
+
const cleanup = options.cleanup ?? cleanupProbeProcess;
|
|
2576
|
+
Promise.resolve().then(() => cleanup(processHandle)).catch(() => {}).finally(() => resolve2(catalog));
|
|
2577
|
+
};
|
|
2578
|
+
const request = (method, params) => {
|
|
2579
|
+
if (settled)
|
|
2580
|
+
return;
|
|
2581
|
+
const stdin = processHandle.stdin;
|
|
2582
|
+
if (!stdin || stdin.destroyed || stdin.writableEnded || stdin.writable === false)
|
|
2583
|
+
return finish();
|
|
2584
|
+
expectedId = ++requestId;
|
|
2585
|
+
expectedMethod = method;
|
|
2586
|
+
try {
|
|
2587
|
+
stdin.write(`${jsonRpcRequest(method, params, expectedId)}
|
|
2588
|
+
`);
|
|
2589
|
+
} catch {
|
|
2590
|
+
finish();
|
|
2591
|
+
}
|
|
2592
|
+
};
|
|
2593
|
+
const onLine = (line) => {
|
|
2594
|
+
const parsed = tryParseJsonLine(line);
|
|
2595
|
+
const message = record(parsed);
|
|
2596
|
+
if (!message)
|
|
2597
|
+
return finish();
|
|
2598
|
+
if (message.id !== expectedId)
|
|
2599
|
+
return;
|
|
2600
|
+
if (message.error !== undefined)
|
|
2601
|
+
return finish();
|
|
2602
|
+
if (!Object.prototype.hasOwnProperty.call(message, "result"))
|
|
2603
|
+
return finish();
|
|
2604
|
+
if (expectedMethod === "authenticate") {
|
|
2605
|
+
request("session/new", { cwd, mcpServers: [] });
|
|
2606
|
+
return;
|
|
2607
|
+
}
|
|
2608
|
+
const result = record(message.result);
|
|
2609
|
+
if (!result)
|
|
2610
|
+
return finish();
|
|
2611
|
+
if (expectedMethod === "initialize") {
|
|
2612
|
+
const authMethods = Array.isArray(result.authMethods) ? result.authMethods : [];
|
|
2613
|
+
if (result.protocolVersion !== ACP_PROTOCOL_VERSION || !authMethods.some((method) => record(method)?.id === AUTH_METHOD_ID))
|
|
2614
|
+
return finish();
|
|
2615
|
+
request("authenticate", { methodId: AUTH_METHOD_ID });
|
|
2616
|
+
return;
|
|
2617
|
+
}
|
|
2618
|
+
if (expectedMethod !== "session/new" || typeof result.sessionId !== "string" || !result.sessionId.trim())
|
|
2619
|
+
return finish();
|
|
2620
|
+
finish(parseCursorAcpModelCatalog(result));
|
|
2621
|
+
};
|
|
2622
|
+
const timer = setTimeout(() => finish(), options.timeoutMs ?? CATALOG_PROBE_TIMEOUT_MS);
|
|
2623
|
+
timer.unref?.();
|
|
2624
|
+
processHandle.stdout?.on("data", (chunk) => {
|
|
2625
|
+
if (settled)
|
|
2626
|
+
return;
|
|
2627
|
+
const text = chunk.toString();
|
|
2628
|
+
outputBytes += Buffer.byteLength(text);
|
|
2629
|
+
if (outputBytes > (options.outputMaxBytes ?? CATALOG_PROBE_OUTPUT_MAX_BYTES))
|
|
2630
|
+
return finish();
|
|
2631
|
+
buffer += text;
|
|
2632
|
+
const lines = buffer.split(`
|
|
2633
|
+
`);
|
|
2634
|
+
buffer = lines.pop() ?? "";
|
|
2635
|
+
for (const line of lines)
|
|
2636
|
+
if (line.trim())
|
|
2637
|
+
onLine(line);
|
|
2638
|
+
});
|
|
2639
|
+
processHandle.stderr?.on("data", (chunk) => {
|
|
2640
|
+
if (settled)
|
|
2641
|
+
return;
|
|
2642
|
+
outputBytes += Buffer.byteLength(chunk.toString());
|
|
2643
|
+
if (outputBytes > (options.outputMaxBytes ?? CATALOG_PROBE_OUTPUT_MAX_BYTES))
|
|
2644
|
+
finish();
|
|
2645
|
+
});
|
|
2646
|
+
processHandle.on("error", () => finish());
|
|
2647
|
+
processHandle.on("exit", () => finish());
|
|
2648
|
+
request("initialize", {
|
|
2649
|
+
protocolVersion: ACP_PROTOCOL_VERSION,
|
|
2650
|
+
clientCapabilities: {
|
|
2651
|
+
fs: { readTextFile: false, writeTextFile: false },
|
|
2652
|
+
terminal: false
|
|
2653
|
+
},
|
|
2654
|
+
clientInfo: { name: "alook-agent-driver-probe", version: "0.1.25" }
|
|
2655
|
+
});
|
|
2656
|
+
});
|
|
2657
|
+
}
|
|
2658
|
+
|
|
2659
|
+
// agent-driver/dist/adapters/cursor/acp-lane.js
|
|
2660
|
+
var ACP_PROTOCOL_VERSION2 = 1;
|
|
2661
|
+
var HANDSHAKE_TIMEOUT_MS = 15000;
|
|
2662
|
+
var AUTH_METHOD_ID2 = "cursor_login";
|
|
1912
2663
|
var PROMPT_STOP_REASONS = new Set([
|
|
1913
2664
|
"end_turn",
|
|
1914
2665
|
"max_tokens",
|
|
@@ -1932,16 +2683,16 @@ class CursorAcpRpcError extends Error {
|
|
|
1932
2683
|
this.code = code;
|
|
1933
2684
|
}
|
|
1934
2685
|
}
|
|
1935
|
-
function
|
|
2686
|
+
function record2(value) {
|
|
1936
2687
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
1937
2688
|
}
|
|
1938
2689
|
function safeLabel(value) {
|
|
1939
2690
|
return typeof value === "string" && /^[a-z0-9_-]{1,64}$/i.test(value) ? value : "unknown";
|
|
1940
2691
|
}
|
|
1941
2692
|
function rpcErrorMessage(error) {
|
|
1942
|
-
const payload =
|
|
2693
|
+
const payload = record2(error);
|
|
1943
2694
|
const message = typeof payload?.message === "string" && payload.message.trim() ? payload.message : "Cursor ACP request failed";
|
|
1944
|
-
const data =
|
|
2695
|
+
const data = record2(payload?.data);
|
|
1945
2696
|
const detail = typeof data?.message === "string" && data.message.trim() ? data.message : undefined;
|
|
1946
2697
|
return detail ? `${message}: ${detail}` : message;
|
|
1947
2698
|
}
|
|
@@ -1949,26 +2700,6 @@ function isMissingSessionError(error) {
|
|
|
1949
2700
|
const message = error instanceof Error ? error.message : String(error);
|
|
1950
2701
|
return /\bsession\b.*\b(not found|missing|unknown|invalid)\b/i.test(message) || /\b(not found|missing|unknown|invalid)\b.*\bsession\b/i.test(message);
|
|
1951
2702
|
}
|
|
1952
|
-
function flattenSelectOptions(value) {
|
|
1953
|
-
if (!Array.isArray(value))
|
|
1954
|
-
return [];
|
|
1955
|
-
const out = [];
|
|
1956
|
-
for (const item of value) {
|
|
1957
|
-
if (Array.isArray(item)) {
|
|
1958
|
-
out.push(...flattenSelectOptions(item));
|
|
1959
|
-
continue;
|
|
1960
|
-
}
|
|
1961
|
-
const candidate = record(item);
|
|
1962
|
-
if (!candidate)
|
|
1963
|
-
continue;
|
|
1964
|
-
if (typeof candidate.value === "string") {
|
|
1965
|
-
out.push({ value: candidate.value, ...typeof candidate.name === "string" ? { name: candidate.name } : {} });
|
|
1966
|
-
}
|
|
1967
|
-
if (Array.isArray(candidate.options))
|
|
1968
|
-
out.push(...flattenSelectOptions(candidate.options));
|
|
1969
|
-
}
|
|
1970
|
-
return out;
|
|
1971
|
-
}
|
|
1972
2703
|
|
|
1973
2704
|
class CursorAcpLane {
|
|
1974
2705
|
factory;
|
|
@@ -2092,30 +2823,30 @@ class CursorAcpLane {
|
|
|
2092
2823
|
}
|
|
2093
2824
|
}
|
|
2094
2825
|
async handshake(ctx) {
|
|
2095
|
-
const initialize =
|
|
2096
|
-
protocolVersion:
|
|
2826
|
+
const initialize = record2(await this.call("initialize", {
|
|
2827
|
+
protocolVersion: ACP_PROTOCOL_VERSION2,
|
|
2097
2828
|
clientCapabilities: {
|
|
2098
2829
|
fs: { readTextFile: false, writeTextFile: false },
|
|
2099
2830
|
terminal: false
|
|
2100
2831
|
},
|
|
2101
2832
|
clientInfo: { name: "alook-agent-driver", version: "0.1.14" }
|
|
2102
2833
|
}));
|
|
2103
|
-
if (initialize?.protocolVersion !==
|
|
2834
|
+
if (initialize?.protocolVersion !== ACP_PROTOCOL_VERSION2) {
|
|
2104
2835
|
throw new CursorAcpIncompatibleError("Installed Cursor ACP does not support protocol version 1");
|
|
2105
2836
|
}
|
|
2106
|
-
const capabilities =
|
|
2837
|
+
const capabilities = record2(initialize.agentCapabilities);
|
|
2107
2838
|
if (capabilities?.loadSession !== true) {
|
|
2108
2839
|
throw new CursorAcpIncompatibleError("Installed Cursor ACP does not support persistent session loading");
|
|
2109
2840
|
}
|
|
2110
2841
|
const authMethods = Array.isArray(initialize.authMethods) ? initialize.authMethods : [];
|
|
2111
|
-
if (!authMethods.some((method) =>
|
|
2842
|
+
if (!authMethods.some((method) => record2(method)?.id === AUTH_METHOD_ID2)) {
|
|
2112
2843
|
throw new CursorAcpIncompatibleError("Installed Cursor ACP does not expose Cursor login authentication");
|
|
2113
2844
|
}
|
|
2114
|
-
await this.call("authenticate", { methodId:
|
|
2845
|
+
await this.call("authenticate", { methodId: AUTH_METHOD_ID2 });
|
|
2115
2846
|
let session;
|
|
2116
2847
|
if (ctx.config.sessionId) {
|
|
2117
2848
|
try {
|
|
2118
|
-
session =
|
|
2849
|
+
session = record2(await this.call("session/load", {
|
|
2119
2850
|
sessionId: ctx.config.sessionId,
|
|
2120
2851
|
cwd: ctx.workingDirectory,
|
|
2121
2852
|
mcpServers: []
|
|
@@ -2127,15 +2858,25 @@ class CursorAcpLane {
|
|
|
2127
2858
|
throw error;
|
|
2128
2859
|
}
|
|
2129
2860
|
} else {
|
|
2130
|
-
session =
|
|
2861
|
+
session = record2(await this.call("session/new", { cwd: ctx.workingDirectory, mcpServers: [] }));
|
|
2131
2862
|
}
|
|
2132
|
-
if (!session
|
|
2863
|
+
if (!session)
|
|
2864
|
+
throw new Error("Cursor ACP did not return a valid session response");
|
|
2865
|
+
const returnedSessionId = session.sessionId;
|
|
2866
|
+
if (returnedSessionId !== undefined && (typeof returnedSessionId !== "string" || !returnedSessionId.trim())) {
|
|
2133
2867
|
throw new Error("Cursor ACP did not return a valid session id");
|
|
2134
2868
|
}
|
|
2135
|
-
if (ctx.config.sessionId
|
|
2136
|
-
|
|
2869
|
+
if (ctx.config.sessionId) {
|
|
2870
|
+
if (returnedSessionId !== undefined && returnedSessionId !== ctx.config.sessionId) {
|
|
2871
|
+
throw new CursorAcpResetRequiredError("Cursor ACP loaded a different session; reset this agent before continuing");
|
|
2872
|
+
}
|
|
2873
|
+
this.sessionId = ctx.config.sessionId;
|
|
2874
|
+
} else {
|
|
2875
|
+
if (typeof returnedSessionId !== "string") {
|
|
2876
|
+
throw new Error("Cursor ACP did not return a valid session id");
|
|
2877
|
+
}
|
|
2878
|
+
this.sessionId = returnedSessionId;
|
|
2137
2879
|
}
|
|
2138
|
-
this.sessionId = session.sessionId;
|
|
2139
2880
|
await this.configureModel(session, ctx);
|
|
2140
2881
|
}
|
|
2141
2882
|
async configureModel(session, ctx) {
|
|
@@ -2143,24 +2884,30 @@ class CursorAcpLane {
|
|
|
2143
2884
|
if (!requestedModel)
|
|
2144
2885
|
return;
|
|
2145
2886
|
const configOptions = Array.isArray(session.configOptions) ? session.configOptions : [];
|
|
2146
|
-
const modelConfig = configOptions.map(
|
|
2887
|
+
const modelConfig = configOptions.map(record2).find((option) => option?.id === "model") ?? null;
|
|
2147
2888
|
if (!modelConfig) {
|
|
2148
2889
|
throw new CursorAcpIncompatibleError("Installed Cursor ACP does not support model configuration");
|
|
2149
2890
|
}
|
|
2150
|
-
const options =
|
|
2151
|
-
const match = options.find((option) => option.value === requestedModel)
|
|
2891
|
+
const options = flattenCursorAcpSelectOptions(modelConfig.options);
|
|
2892
|
+
const match = options.find((option) => option.value === requestedModel);
|
|
2152
2893
|
if (!match) {
|
|
2153
2894
|
throw new CursorAcpIncompatibleError(`Configured Cursor model is unavailable through ACP: ${requestedModel}`);
|
|
2154
2895
|
}
|
|
2896
|
+
let response;
|
|
2155
2897
|
try {
|
|
2156
|
-
await this.call("session/set_config_option", {
|
|
2898
|
+
response = record2(await this.call("session/set_config_option", {
|
|
2157
2899
|
sessionId: this.sessionId,
|
|
2158
2900
|
configId: "model",
|
|
2159
2901
|
value: match.value
|
|
2160
|
-
});
|
|
2902
|
+
}));
|
|
2161
2903
|
} catch {
|
|
2162
2904
|
throw new CursorAcpIncompatibleError("Installed Cursor ACP rejected model configuration");
|
|
2163
2905
|
}
|
|
2906
|
+
const confirmedOptions = Array.isArray(response?.configOptions) ? response.configOptions : [];
|
|
2907
|
+
const confirmedModel = confirmedOptions.map(record2).find((option) => option?.id === "model") ?? null;
|
|
2908
|
+
if (confirmedModel?.currentValue !== match.value) {
|
|
2909
|
+
throw new CursorAcpIncompatibleError("Cursor ACP did not confirm the exact configured model");
|
|
2910
|
+
}
|
|
2164
2911
|
}
|
|
2165
2912
|
admitPrompt(text) {
|
|
2166
2913
|
if (!this.sessionId)
|
|
@@ -2196,22 +2943,13 @@ class CursorAcpLane {
|
|
|
2196
2943
|
completePrompt(active, value) {
|
|
2197
2944
|
if (this.activePrompt?.requestId !== active.requestId)
|
|
2198
2945
|
return;
|
|
2199
|
-
const result =
|
|
2946
|
+
const result = record2(value);
|
|
2200
2947
|
if (!result || typeof result.stopReason !== "string" || !PROMPT_STOP_REASONS.has(result.stopReason)) {
|
|
2201
2948
|
this.failPrompt(active, new Error("Cursor ACP prompt response did not contain a supported stopReason"));
|
|
2202
2949
|
return;
|
|
2203
2950
|
}
|
|
2204
2951
|
this.activePrompt = null;
|
|
2205
2952
|
this.openToolCalls.clear();
|
|
2206
|
-
const usage = record(result.usage);
|
|
2207
|
-
if (usage) {
|
|
2208
|
-
this.events.emit("runtime_event", {
|
|
2209
|
-
kind: "telemetry",
|
|
2210
|
-
name: "token_usage",
|
|
2211
|
-
source: "cursor.acp",
|
|
2212
|
-
attrs: usage
|
|
2213
|
-
});
|
|
2214
|
-
}
|
|
2215
2953
|
this.events.emit("runtime_event", {
|
|
2216
2954
|
kind: "turn_end",
|
|
2217
2955
|
sessionId: this.sessionId ?? undefined,
|
|
@@ -2347,7 +3085,7 @@ class CursorAcpLane {
|
|
|
2347
3085
|
});
|
|
2348
3086
|
}
|
|
2349
3087
|
handleMessage(value) {
|
|
2350
|
-
const message =
|
|
3088
|
+
const message = record2(value);
|
|
2351
3089
|
if (!message || message.jsonrpc !== "2.0") {
|
|
2352
3090
|
this.protocolFailure("Cursor ACP emitted an invalid JSON-RPC message");
|
|
2353
3091
|
return;
|
|
@@ -2375,7 +3113,7 @@ class CursorAcpLane {
|
|
|
2375
3113
|
if (pending.kind === "prompt") {
|
|
2376
3114
|
this.pending.delete(id);
|
|
2377
3115
|
if (message.error !== undefined) {
|
|
2378
|
-
const payload =
|
|
3116
|
+
const payload = record2(message.error);
|
|
2379
3117
|
this.failPrompt(pending.active, new CursorAcpRpcError(pending.method, typeof payload?.code === "number" ? payload.code : undefined, rpcErrorMessage(message.error)));
|
|
2380
3118
|
} else if (!("result" in message)) {
|
|
2381
3119
|
this.failPrompt(pending.active, new Error("Cursor ACP response omitted result"));
|
|
@@ -2385,7 +3123,7 @@ class CursorAcpLane {
|
|
|
2385
3123
|
return;
|
|
2386
3124
|
}
|
|
2387
3125
|
if (message.error !== undefined) {
|
|
2388
|
-
const payload =
|
|
3126
|
+
const payload = record2(message.error);
|
|
2389
3127
|
this.settleRequest(id, false, new CursorAcpRpcError(pending.method, typeof payload?.code === "number" ? payload.code : undefined, rpcErrorMessage(message.error)));
|
|
2390
3128
|
return;
|
|
2391
3129
|
}
|
|
@@ -2417,9 +3155,9 @@ class CursorAcpLane {
|
|
|
2417
3155
|
this.diagnostic("warning", `Unsupported Cursor ACP client request: ${safeLabel(method)}`);
|
|
2418
3156
|
return;
|
|
2419
3157
|
}
|
|
2420
|
-
const payload =
|
|
3158
|
+
const payload = record2(params);
|
|
2421
3159
|
const sameSession = payload?.sessionId === this.sessionId;
|
|
2422
|
-
const options = Array.isArray(payload?.options) ? payload.options.map(
|
|
3160
|
+
const options = Array.isArray(payload?.options) ? payload.options.map(record2).filter(Boolean) : [];
|
|
2423
3161
|
const allowOnce = options.find((option) => option.kind === "allow_once" && typeof option.optionId === "string" && option.optionId.trim().length > 0);
|
|
2424
3162
|
if (!this.ready || !this.activePrompt || !sameSession || !allowOnce) {
|
|
2425
3163
|
this.write({ jsonrpc: "2.0", id, result: { outcome: { outcome: "cancelled" } } });
|
|
@@ -2440,7 +3178,7 @@ class CursorAcpLane {
|
|
|
2440
3178
|
this.diagnostic("warning", `Unsupported Cursor ACP notification: ${safeLabel(method)}`);
|
|
2441
3179
|
}
|
|
2442
3180
|
handleSessionUpdate(params) {
|
|
2443
|
-
const payload =
|
|
3181
|
+
const payload = record2(params);
|
|
2444
3182
|
if (!payload || payload.sessionId !== this.sessionId) {
|
|
2445
3183
|
this.diagnostic("warning", "Cursor ACP emitted an update for a different session");
|
|
2446
3184
|
return;
|
|
@@ -2449,18 +3187,18 @@ class CursorAcpLane {
|
|
|
2449
3187
|
this.diagnostic("warning", "Cursor ACP emitted a session update without an active prompt");
|
|
2450
3188
|
return;
|
|
2451
3189
|
}
|
|
2452
|
-
const update =
|
|
3190
|
+
const update = record2(payload.update) ?? {};
|
|
2453
3191
|
const updateType = update?.sessionUpdate;
|
|
2454
3192
|
switch (updateType) {
|
|
2455
3193
|
case "agent_message_chunk": {
|
|
2456
|
-
const content =
|
|
3194
|
+
const content = record2(update.content);
|
|
2457
3195
|
if (content?.type === "text" && typeof content.text === "string") {
|
|
2458
3196
|
this.events.emit("runtime_event", { kind: "assistant_message_delta", text: content.text });
|
|
2459
3197
|
}
|
|
2460
3198
|
return;
|
|
2461
3199
|
}
|
|
2462
3200
|
case "agent_thought_chunk": {
|
|
2463
|
-
const content =
|
|
3201
|
+
const content = record2(update.content);
|
|
2464
3202
|
if (content?.type === "text" && typeof content.text === "string") {
|
|
2465
3203
|
this.events.emit("runtime_event", { kind: "assistant_reasoning_delta", text: content.text });
|
|
2466
3204
|
}
|
|
@@ -2532,6 +3270,7 @@ class CursorAcpLane {
|
|
|
2532
3270
|
|
|
2533
3271
|
// agent-driver/dist/adapters/cursor/index.js
|
|
2534
3272
|
class CursorDriver {
|
|
3273
|
+
catalogProbe;
|
|
2535
3274
|
id = "cursor";
|
|
2536
3275
|
instructionDelivery = { kind: "workspace_file", canonical: "AGENTS.md", aliases: ["CLAUDE.md"] };
|
|
2537
3276
|
execution = {
|
|
@@ -2540,8 +3279,23 @@ class CursorDriver {
|
|
|
2540
3279
|
wakeStart: "immediate",
|
|
2541
3280
|
terminalOwnership: "transport_request"
|
|
2542
3281
|
};
|
|
2543
|
-
|
|
2544
|
-
|
|
3282
|
+
constructor(catalogProbe = probeCursorAcpCatalog) {
|
|
3283
|
+
this.catalogProbe = catalogProbe;
|
|
3284
|
+
}
|
|
3285
|
+
async probe(command) {
|
|
3286
|
+
const result = probeCliRuntime("cursor-agent", {}, command);
|
|
3287
|
+
if (result.status !== "healthy")
|
|
3288
|
+
return result;
|
|
3289
|
+
let reasoning;
|
|
3290
|
+
try {
|
|
3291
|
+
reasoning = await this.catalogProbe(command);
|
|
3292
|
+
} catch {
|
|
3293
|
+
reasoning = undefined;
|
|
3294
|
+
}
|
|
3295
|
+
return {
|
|
3296
|
+
...result,
|
|
3297
|
+
reasoning
|
|
3298
|
+
};
|
|
2545
3299
|
}
|
|
2546
3300
|
async openLane(ctx, options) {
|
|
2547
3301
|
return new CursorAcpLane(this, ctx, { onRawStdoutLine: options?.onRawStdoutLine });
|
|
@@ -2561,10 +3315,10 @@ class CursorDriver {
|
|
|
2561
3315
|
}
|
|
2562
3316
|
|
|
2563
3317
|
// agent-driver/dist/adapters/opencode/index.js
|
|
2564
|
-
import { randomBytes as
|
|
3318
|
+
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
2565
3319
|
|
|
2566
3320
|
// agent-driver/dist/adapters/opencode/service-lane.js
|
|
2567
|
-
import { randomBytes } from "node:crypto";
|
|
3321
|
+
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
2568
3322
|
import { EventEmitter as EventEmitter3 } from "node:events";
|
|
2569
3323
|
import { createServer as createServer2 } from "node:net";
|
|
2570
3324
|
var SUPPORTED_VERSION = "1.17.20";
|
|
@@ -2598,7 +3352,7 @@ class OpenCodeHttpError extends Error {
|
|
|
2598
3352
|
this.status = status;
|
|
2599
3353
|
}
|
|
2600
3354
|
}
|
|
2601
|
-
function
|
|
3355
|
+
function record3(value) {
|
|
2602
3356
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
2603
3357
|
}
|
|
2604
3358
|
function safeLabel2(value) {
|
|
@@ -2645,7 +3399,7 @@ function parseModelRef(model) {
|
|
|
2645
3399
|
return { providerID: model.slice(0, slash), id: model.slice(slash + 1) };
|
|
2646
3400
|
}
|
|
2647
3401
|
function messageFromError(value) {
|
|
2648
|
-
const payload =
|
|
3402
|
+
const payload = record3(value);
|
|
2649
3403
|
const message = typeof payload?.message === "string" && payload.message.trim() ? payload.message : undefined;
|
|
2650
3404
|
return message ? "OpenCode turn failed" : "OpenCode reported an inconsistent turn outcome";
|
|
2651
3405
|
}
|
|
@@ -2703,7 +3457,7 @@ class OpenCodeServiceLane {
|
|
|
2703
3457
|
this.ctx = ctx;
|
|
2704
3458
|
this.options = options;
|
|
2705
3459
|
this.fetchFn = options.fetch ?? fetch;
|
|
2706
|
-
this.password = options.password ??
|
|
3460
|
+
this.password = options.password ?? randomBytes2(32).toString("base64url");
|
|
2707
3461
|
}
|
|
2708
3462
|
get currentSessionId() {
|
|
2709
3463
|
return this.sessionId;
|
|
@@ -2979,7 +3733,7 @@ class OpenCodeServiceLane {
|
|
|
2979
3733
|
const healthTimeoutMs = Math.max(1, Math.min(1000, deadline - Date.now(), this.options.requestTimeoutMs ?? REQUEST_TIMEOUT_MS));
|
|
2980
3734
|
const { response, body } = await this.fetchJsonWithTimeout("/global/health", { method: "GET" }, "health", healthTimeoutMs);
|
|
2981
3735
|
if (response.ok) {
|
|
2982
|
-
const health =
|
|
3736
|
+
const health = record3(body);
|
|
2983
3737
|
if (health?.healthy !== true || health.version !== SUPPORTED_VERSION) {
|
|
2984
3738
|
throw new OpenCodeIncompatibleError(`Installed OpenCode service must be version ${SUPPORTED_VERSION}`);
|
|
2985
3739
|
}
|
|
@@ -3000,8 +3754,8 @@ class OpenCodeServiceLane {
|
|
|
3000
3754
|
const { response, body } = await this.fetchJsonWithTimeout("/doc", { method: "GET" }, "OpenAPI");
|
|
3001
3755
|
if (!response.ok)
|
|
3002
3756
|
throw new OpenCodeIncompatibleError("Installed OpenCode service does not expose its OpenAPI document");
|
|
3003
|
-
const document =
|
|
3004
|
-
const paths =
|
|
3757
|
+
const document = record3(body);
|
|
3758
|
+
const paths = record3(document?.paths);
|
|
3005
3759
|
const required = [
|
|
3006
3760
|
"/api/session",
|
|
3007
3761
|
"/api/session/active",
|
|
@@ -3014,7 +3768,7 @@ class OpenCodeServiceLane {
|
|
|
3014
3768
|
"/api/session/{sessionID}/permission/{requestID}/reply",
|
|
3015
3769
|
"/api/event"
|
|
3016
3770
|
];
|
|
3017
|
-
if (!paths || required.some((path6) => !
|
|
3771
|
+
if (!paths || required.some((path6) => !record3(paths[path6]))) {
|
|
3018
3772
|
throw new OpenCodeIncompatibleError("Installed OpenCode service is missing required v2 session capabilities");
|
|
3019
3773
|
}
|
|
3020
3774
|
}
|
|
@@ -3027,7 +3781,7 @@ class OpenCodeServiceLane {
|
|
|
3027
3781
|
}
|
|
3028
3782
|
if (!response2.ok)
|
|
3029
3783
|
throw new OpenCodeHttpError(response2.status, "session resume");
|
|
3030
|
-
const session2 =
|
|
3784
|
+
const session2 = record3(record3(body2)?.data);
|
|
3031
3785
|
if (session2?.id !== resumeId) {
|
|
3032
3786
|
throw new OpenCodeResetRequiredError("OpenCode v2 returned a different resumed session; reset this agent before continuing");
|
|
3033
3787
|
}
|
|
@@ -3048,8 +3802,8 @@ class OpenCodeServiceLane {
|
|
|
3048
3802
|
}, "session create");
|
|
3049
3803
|
if (!response.ok)
|
|
3050
3804
|
throw new OpenCodeHttpError(response.status, "session create");
|
|
3051
|
-
const payload =
|
|
3052
|
-
const session =
|
|
3805
|
+
const payload = record3(responseBody);
|
|
3806
|
+
const session = record3(payload?.data);
|
|
3053
3807
|
if (typeof session?.id !== "string" || !/^ses/.test(session.id)) {
|
|
3054
3808
|
throw new Error("OpenCode v2 did not return a valid session id");
|
|
3055
3809
|
}
|
|
@@ -3206,7 +3960,7 @@ class OpenCodeServiceLane {
|
|
|
3206
3960
|
const { response, body: responseBody } = await this.fetchJsonWithTimeout(`/api/session/${encodeURIComponent(this.sessionId)}/history?after=${historyCursor}&limit=${HISTORY_PAGE_LIMIT}`, { method: "GET" }, "session history");
|
|
3207
3961
|
if (!response.ok)
|
|
3208
3962
|
throw new OpenCodeHttpError(response.status, "session history");
|
|
3209
|
-
const body =
|
|
3963
|
+
const body = record3(responseBody);
|
|
3210
3964
|
if (!Array.isArray(body?.data) || typeof body.hasMore !== "boolean") {
|
|
3211
3965
|
throw new OpenCodeProtocolError("OpenCode session history returned an invalid page");
|
|
3212
3966
|
}
|
|
@@ -3224,9 +3978,9 @@ class OpenCodeServiceLane {
|
|
|
3224
3978
|
return run;
|
|
3225
3979
|
}
|
|
3226
3980
|
async handleDurableEvent(value, project) {
|
|
3227
|
-
const event =
|
|
3228
|
-
const durable =
|
|
3229
|
-
const data =
|
|
3981
|
+
const event = record3(value);
|
|
3982
|
+
const durable = record3(event?.durable);
|
|
3983
|
+
const data = record3(event?.data);
|
|
3230
3984
|
if (!event || typeof event.id !== "string" || typeof event.type !== "string" || !durable || durable.aggregateID !== this.sessionId || !Number.isInteger(durable.seq) || Number(durable.seq) < 0 || data?.sessionID !== this.sessionId) {
|
|
3231
3985
|
throw new OpenCodeProtocolError("OpenCode session stream emitted an invalid durable event");
|
|
3232
3986
|
}
|
|
@@ -3316,13 +4070,21 @@ class OpenCodeServiceLane {
|
|
|
3316
4070
|
...!successful ? { message: "OpenCode reported an unsupported final step outcome" } : {}
|
|
3317
4071
|
});
|
|
3318
4072
|
}
|
|
3319
|
-
const tokens =
|
|
3320
|
-
if (tokens) {
|
|
4073
|
+
const tokens = record3(data.tokens);
|
|
4074
|
+
if (tokens && data.finish !== "tool-calls") {
|
|
4075
|
+
const cache = record3(tokens.cache);
|
|
4076
|
+
const metric2 = (value2) => typeof value2 === "number" && Number.isSafeInteger(value2) && value2 >= 0 ? value2 : null;
|
|
4077
|
+
const cacheParts = [cache?.read, cache?.write].filter((value2) => typeof value2 === "number" && Number.isSafeInteger(value2) && value2 >= 0);
|
|
4078
|
+
const cacheTotal = cacheParts.reduce((sum, value2) => sum + value2, 0);
|
|
3321
4079
|
this.events.emit("runtime_event", {
|
|
3322
4080
|
kind: "telemetry",
|
|
3323
4081
|
name: "token_usage",
|
|
3324
4082
|
source: "opencode.v2",
|
|
3325
|
-
|
|
4083
|
+
usage: {
|
|
4084
|
+
input: metric2(tokens.input),
|
|
4085
|
+
output: metric2(tokens.output),
|
|
4086
|
+
cache: cacheParts.length > 0 && Number.isSafeInteger(cacheTotal) ? cacheTotal : null
|
|
4087
|
+
}
|
|
3326
4088
|
});
|
|
3327
4089
|
}
|
|
3328
4090
|
break;
|
|
@@ -3337,8 +4099,8 @@ class OpenCodeServiceLane {
|
|
|
3337
4099
|
return seq;
|
|
3338
4100
|
}
|
|
3339
4101
|
async handleLiveEvent(value) {
|
|
3340
|
-
const event =
|
|
3341
|
-
const data =
|
|
4102
|
+
const event = record3(value);
|
|
4103
|
+
const data = record3(event?.data);
|
|
3342
4104
|
if (event?.type !== "permission.v2.asked" || data?.sessionID !== this.sessionId)
|
|
3343
4105
|
return;
|
|
3344
4106
|
if (typeof data.id !== "string" || !/^per/.test(data.id)) {
|
|
@@ -3352,11 +4114,11 @@ class OpenCodeServiceLane {
|
|
|
3352
4114
|
const { response, body: responseBody } = await this.fetchJsonWithTimeout(`/api/session/${encodeURIComponent(this.sessionId)}/permission`, { method: "GET" }, "permission list");
|
|
3353
4115
|
if (!response.ok)
|
|
3354
4116
|
throw new OpenCodeHttpError(response.status, "permission list");
|
|
3355
|
-
const body =
|
|
4117
|
+
const body = record3(responseBody);
|
|
3356
4118
|
if (!Array.isArray(body?.data))
|
|
3357
4119
|
throw new OpenCodeProtocolError("OpenCode permission list returned invalid data");
|
|
3358
4120
|
for (const item of body.data) {
|
|
3359
|
-
const permission =
|
|
4121
|
+
const permission = record3(item);
|
|
3360
4122
|
if (permission?.sessionID === this.sessionId && typeof permission.id === "string") {
|
|
3361
4123
|
await this.replyPermission(permission.id);
|
|
3362
4124
|
}
|
|
@@ -3423,8 +4185,8 @@ class OpenCodeServiceLane {
|
|
|
3423
4185
|
}, "prompt admission");
|
|
3424
4186
|
if (!response.ok)
|
|
3425
4187
|
throw new OpenCodeHttpError(response.status, "prompt admission");
|
|
3426
|
-
const body =
|
|
3427
|
-
const admitted =
|
|
4188
|
+
const body = record3(responseBody);
|
|
4189
|
+
const admitted = record3(body?.data);
|
|
3428
4190
|
if (admitted?.id !== messageId || admitted.sessionID !== this.sessionId || admitted.delivery !== delivery || !Number.isInteger(admitted.admittedSeq) || Number(admitted.admittedSeq) < 0) {
|
|
3429
4191
|
throw new OpenCodeProtocolError("OpenCode prompt admission returned an invalid receipt");
|
|
3430
4192
|
}
|
|
@@ -3491,8 +4253,8 @@ class OpenCodeServiceLane {
|
|
|
3491
4253
|
const { response, body: responseBody } = await this.fetchJsonWithTimeout("/api/session/active", { method: "GET" }, "active session query");
|
|
3492
4254
|
if (!response.ok)
|
|
3493
4255
|
throw new OpenCodeHttpError(response.status, "active session query");
|
|
3494
|
-
const body =
|
|
3495
|
-
const active =
|
|
4256
|
+
const body = record3(responseBody);
|
|
4257
|
+
const active = record3(body?.data);
|
|
3496
4258
|
if (!active)
|
|
3497
4259
|
throw new OpenCodeProtocolError("OpenCode active session query returned invalid data");
|
|
3498
4260
|
if (!this.barrierStillCurrent(root, identity, generation))
|
|
@@ -3628,7 +4390,7 @@ class OpenCodeServiceLane {
|
|
|
3628
4390
|
return headers;
|
|
3629
4391
|
}
|
|
3630
4392
|
newMessageId() {
|
|
3631
|
-
return `msg_${
|
|
4393
|
+
return `msg_${randomBytes2(16).toString("hex")}`;
|
|
3632
4394
|
}
|
|
3633
4395
|
diagnostic(severity, message) {
|
|
3634
4396
|
this.events.emit("runtime_event", {
|
|
@@ -3693,10 +4455,11 @@ class OpenCodeServiceLane {
|
|
|
3693
4455
|
|
|
3694
4456
|
// agent-driver/dist/adapters/opencode/index.js
|
|
3695
4457
|
function createOpenCodeMessageId() {
|
|
3696
|
-
return `msg_${
|
|
4458
|
+
return `msg_${randomBytes3(16).toString("hex")}`;
|
|
3697
4459
|
}
|
|
3698
4460
|
|
|
3699
4461
|
class OpenCodeDriver {
|
|
4462
|
+
outputProbe;
|
|
3700
4463
|
id = "opencode";
|
|
3701
4464
|
instructionDelivery = { kind: "workspace_file", canonical: "AGENTS.md", aliases: ["CLAUDE.md"] };
|
|
3702
4465
|
execution = {
|
|
@@ -3705,8 +4468,19 @@ class OpenCodeDriver {
|
|
|
3705
4468
|
wakeStart: "immediate",
|
|
3706
4469
|
terminalOwnership: "transport_request"
|
|
3707
4470
|
};
|
|
4471
|
+
constructor(outputProbe = probeCommandOutput) {
|
|
4472
|
+
this.outputProbe = outputProbe;
|
|
4473
|
+
}
|
|
3708
4474
|
probe(command) {
|
|
3709
|
-
|
|
4475
|
+
const result = probeCliRuntime("opencode", {}, command);
|
|
4476
|
+
if (result.status !== "healthy")
|
|
4477
|
+
return result;
|
|
4478
|
+
const spec = resolveSpawnSpec("opencode", ["models", "--pure"], command);
|
|
4479
|
+
const output = this.outputProbe(spec.command, spec.args);
|
|
4480
|
+
return {
|
|
4481
|
+
...result,
|
|
4482
|
+
reasoning: output.ok ? parseOpenCodeModelCatalog(output.output) : undefined
|
|
4483
|
+
};
|
|
3710
4484
|
}
|
|
3711
4485
|
beginTurn() {
|
|
3712
4486
|
return createOpenCodeMessageId();
|
|
@@ -3960,6 +4734,7 @@ function createPiSessionDependencies(ctx, loadSdk = loadPiSdkModule) {
|
|
|
3960
4734
|
|
|
3961
4735
|
// agent-driver/dist/adapters/pi/index.js
|
|
3962
4736
|
var PI_SDK_PACKAGE_NAME2 = "@earendil-works/pi-coding-agent";
|
|
4737
|
+
var PI_MODEL_PROBE_TIMEOUT_MS = 5000;
|
|
3963
4738
|
function isPiSdkPackageJson(pkgJsonPath) {
|
|
3964
4739
|
if (!existsSync3(pkgJsonPath))
|
|
3965
4740
|
return false;
|
|
@@ -4068,6 +4843,8 @@ function mapPiSdkEvent(event, sessionId, state) {
|
|
|
4068
4843
|
|
|
4069
4844
|
class PiDriver {
|
|
4070
4845
|
dependenciesFor;
|
|
4846
|
+
loadSdk;
|
|
4847
|
+
readVersion;
|
|
4071
4848
|
id = "pi";
|
|
4072
4849
|
instructionDelivery = { kind: "workspace_file", canonical: "AGENTS.md", aliases: ["CLAUDE.md"] };
|
|
4073
4850
|
execution = {
|
|
@@ -4078,15 +4855,36 @@ class PiDriver {
|
|
|
4078
4855
|
};
|
|
4079
4856
|
sessionId = null;
|
|
4080
4857
|
terminalSequence = 0;
|
|
4081
|
-
constructor(dependenciesFor = createPiSessionDependencies) {
|
|
4858
|
+
constructor(dependenciesFor = createPiSessionDependencies, loadSdk = loadPiSdkModule, readVersion = readPiSdkVersion) {
|
|
4082
4859
|
this.dependenciesFor = dependenciesFor;
|
|
4860
|
+
this.loadSdk = loadSdk;
|
|
4861
|
+
this.readVersion = readVersion;
|
|
4083
4862
|
}
|
|
4084
|
-
probe() {
|
|
4085
|
-
const version =
|
|
4863
|
+
async probe() {
|
|
4864
|
+
const version = this.readVersion();
|
|
4086
4865
|
if (!version) {
|
|
4087
4866
|
return { status: "unhealthy", lastError: "sdk_not_installed" };
|
|
4088
4867
|
}
|
|
4089
|
-
|
|
4868
|
+
let timer;
|
|
4869
|
+
try {
|
|
4870
|
+
const reasoning = await Promise.race([
|
|
4871
|
+
this.loadSdk().then(async (sdk) => {
|
|
4872
|
+
const authStorage = sdk.AuthStorage.create();
|
|
4873
|
+
const registry = sdk.ModelRegistry.create(authStorage);
|
|
4874
|
+
return parsePiModelCatalog(await registry.getAvailable());
|
|
4875
|
+
}),
|
|
4876
|
+
new Promise((resolve3) => {
|
|
4877
|
+
timer = setTimeout(() => resolve3(undefined), PI_MODEL_PROBE_TIMEOUT_MS);
|
|
4878
|
+
timer.unref?.();
|
|
4879
|
+
})
|
|
4880
|
+
]);
|
|
4881
|
+
return { status: "healthy", version, reasoning };
|
|
4882
|
+
} catch {
|
|
4883
|
+
return { status: "healthy", version, reasoning: undefined };
|
|
4884
|
+
} finally {
|
|
4885
|
+
if (timer)
|
|
4886
|
+
clearTimeout(timer);
|
|
4887
|
+
}
|
|
4090
4888
|
}
|
|
4091
4889
|
async openLane(ctx) {
|
|
4092
4890
|
const deps = this.dependenciesFor(ctx);
|
|
@@ -4471,48 +5269,6 @@ function assertInstructionFileName(name) {
|
|
|
4471
5269
|
}
|
|
4472
5270
|
}
|
|
4473
5271
|
|
|
4474
|
-
// agent-driver/dist/internal/errors.js
|
|
4475
|
-
var MAX_PUBLIC_ERROR_MESSAGE = 1000;
|
|
4476
|
-
var CREDENTIAL_NAME = String.raw`(?:[A-Za-z0-9]{1,32}[_-]){0,4}(?:api[_-]?key|access[_-]?key|secret(?:[_-]?access[_-]?key)?|client[_-]?secret|access[_-]?token|auth(?:orization)?|password|passwd|token|voucher)(?:[_-][A-Za-z0-9]{1,32}){0,4}`;
|
|
4477
|
-
var QUOTED_CREDENTIAL_ASSIGNMENT = new RegExp(String.raw`(["'])(${CREDENTIAL_NAME})\1\s*[:=]\s*(["'])[^"'\r\n]*\3`, "gi");
|
|
4478
|
-
var CREDENTIAL_ASSIGNMENT = new RegExp(String.raw`(?<![A-Za-z0-9_-])((?:${CREDENTIAL_NAME})\s*[:=]\s*)(?!\[redacted\])[^\s,;}\]]+`, "gi");
|
|
4479
|
-
function scrubDriverErrorMessage(value, fallback = "Runtime operation failed") {
|
|
4480
|
-
const text = value instanceof Error ? value.message : String(value ?? "");
|
|
4481
|
-
const scrubbed = text.replace(/\b(?:cmk|cmt|crk)_[A-Za-z0-9_-]+\b/g, "[redacted-token]").replace(/(Authorization\s*:\s*)(?:Bearer|Basic)\s+[^\s,;]+/gi, "$1[redacted]").replace(/Bearer\s+[A-Za-z0-9._\-]+/gi, "Bearer [redacted]").replace(/\b(?:sk|sk-ant|sk-proj|xox[abprs])-[A-Za-z0-9._\-]+/gi, "[redacted-token]").replace(QUOTED_CREDENTIAL_ASSIGNMENT, "$1$2$1:$3[redacted]$3").replace(CREDENTIAL_ASSIGNMENT, "$1[redacted]").replace(/(?<![A-Za-z0-9._%+\-])[A-Za-z0-9._%+\-]{1,320}@[A-Za-z0-9.\-]{1,255}\.[A-Za-z]{2,63}/g, "[redacted-email]").replace(/([?&])([^=\s]+)=([^&\s]+)/g, "$1$2=[redacted]").replace(/\/(?:Users|home)\/[^\r\n,;]+/g, "[redacted-path]").replace(/[A-Za-z]:\\Users\\[^\r\n,;]+/gi, "[redacted-path]").replace(/\\\\[^\\\s]+\\[^\r\n,;]+/g, "[redacted-path]").replace(/(?:[A-Za-z]:\\|\/)(?:[^\s/:]+[\\/]){1,}[^\s:]*/g, "[redacted-path]").trim();
|
|
4482
|
-
return (scrubbed || fallback).slice(0, MAX_PUBLIC_ERROR_MESSAGE);
|
|
4483
|
-
}
|
|
4484
|
-
function scrubDriverError(error) {
|
|
4485
|
-
return {
|
|
4486
|
-
...error,
|
|
4487
|
-
code: stableErrorCode(error.code, "runtime_error"),
|
|
4488
|
-
message: scrubDriverErrorMessage(error.message),
|
|
4489
|
-
...error.details ? { details: scrubDetails(error.details) } : {}
|
|
4490
|
-
};
|
|
4491
|
-
}
|
|
4492
|
-
function scrubDetails(details) {
|
|
4493
|
-
const scrubValue = (value, key) => {
|
|
4494
|
-
if (key && /api[_-]?key|access[_-]?token|auth(?:orization)?|password|passwd|secret|token/i.test(key)) {
|
|
4495
|
-
return "[redacted]";
|
|
4496
|
-
}
|
|
4497
|
-
if (typeof value === "string")
|
|
4498
|
-
return scrubDriverErrorMessage(value, "[redacted]");
|
|
4499
|
-
if (Array.isArray(value))
|
|
4500
|
-
return value.map((item) => scrubValue(item));
|
|
4501
|
-
if (value && typeof value === "object") {
|
|
4502
|
-
return Object.fromEntries(Object.entries(value).map(([childKey, child]) => [
|
|
4503
|
-
childKey,
|
|
4504
|
-
scrubValue(child, childKey)
|
|
4505
|
-
]));
|
|
4506
|
-
}
|
|
4507
|
-
return value;
|
|
4508
|
-
};
|
|
4509
|
-
return scrubValue(details);
|
|
4510
|
-
}
|
|
4511
|
-
function stableErrorCode(value, fallback) {
|
|
4512
|
-
const code = String(value ?? "");
|
|
4513
|
-
return /^[A-Za-z0-9_.-]{1,100}$/.test(code) ? code : fallback;
|
|
4514
|
-
}
|
|
4515
|
-
|
|
4516
5272
|
// agent-driver/dist/controller/logical-session.js
|
|
4517
5273
|
import { mkdirSync as mkdirSync3 } from "node:fs";
|
|
4518
5274
|
var SEMANTIC_ASSEMBLER_MAX_BYTES = 1048576;
|
|
@@ -4630,6 +5386,8 @@ class LogicalAgentSession {
|
|
|
4630
5386
|
toolBoundaryFlushDisabled = false;
|
|
4631
5387
|
safeBoundaryFlush;
|
|
4632
5388
|
safeBoundaryDelivery;
|
|
5389
|
+
settingsUpdateTail = Promise.resolve();
|
|
5390
|
+
settingsUpdatePending = false;
|
|
4633
5391
|
turnAdmission;
|
|
4634
5392
|
instructionsMaterialized = false;
|
|
4635
5393
|
lifecycleGeneration = 0;
|
|
@@ -4685,6 +5443,35 @@ class LogicalAgentSession {
|
|
|
4685
5443
|
send(message) {
|
|
4686
5444
|
return this.admit("send", message);
|
|
4687
5445
|
}
|
|
5446
|
+
updateSettings(input) {
|
|
5447
|
+
if (this.state === "closed" || this.state === "stopping" || this.finishing) {
|
|
5448
|
+
return Promise.resolve({
|
|
5449
|
+
status: "failed",
|
|
5450
|
+
error: driverError("process", "settings_session_closed", "Runtime session is closed", true)
|
|
5451
|
+
});
|
|
5452
|
+
}
|
|
5453
|
+
this.settingsUpdatePending = true;
|
|
5454
|
+
const operation = this.settingsUpdateTail.then(async () => {
|
|
5455
|
+
if (!this.lane?.updateSettings)
|
|
5456
|
+
return { status: "unsupported" };
|
|
5457
|
+
try {
|
|
5458
|
+
return await this.lane.updateSettings(input);
|
|
5459
|
+
} catch (error) {
|
|
5460
|
+
return {
|
|
5461
|
+
status: "failed",
|
|
5462
|
+
error: driverError("process", "settings_update_failed", String(error), true)
|
|
5463
|
+
};
|
|
5464
|
+
}
|
|
5465
|
+
});
|
|
5466
|
+
this.settingsUpdateTail = operation.then((result) => {
|
|
5467
|
+
if (result.status === "applied") {
|
|
5468
|
+
this.settingsUpdatePending = false;
|
|
5469
|
+
return;
|
|
5470
|
+
}
|
|
5471
|
+
return new Promise(() => {});
|
|
5472
|
+
});
|
|
5473
|
+
return operation;
|
|
5474
|
+
}
|
|
4688
5475
|
async interrupt(input) {
|
|
4689
5476
|
if (this.state === "closed" || this.state === "stopping" || this.finishing)
|
|
4690
5477
|
return { status: "closed" };
|
|
@@ -4846,7 +5633,7 @@ class LogicalAgentSession {
|
|
|
4846
5633
|
}
|
|
4847
5634
|
return receipt;
|
|
4848
5635
|
}
|
|
4849
|
-
if (this.state === "idle" && (this.queued.length > 0 || this.safeBoundaryFlush !== undefined || this.safeBoundaryDelivery !== undefined)) {
|
|
5636
|
+
if (this.state === "idle" && (this.queued.length > 0 || this.safeBoundaryFlush !== undefined || this.safeBoundaryDelivery !== undefined || this.settingsUpdatePending)) {
|
|
4850
5637
|
return this.queue(message, "runtime_busy");
|
|
4851
5638
|
}
|
|
4852
5639
|
return this.startTurn([message], "prompt");
|
|
@@ -5154,11 +5941,10 @@ class LogicalAgentSession {
|
|
|
5154
5941
|
}
|
|
5155
5942
|
return;
|
|
5156
5943
|
case "telemetry": {
|
|
5157
|
-
const details = jsonValue(event.attrs);
|
|
5158
5944
|
if (event.name === "token_usage") {
|
|
5159
|
-
this.emit({ type: "token_usage", turnId, source: event.source, usage:
|
|
5945
|
+
this.emit({ type: "token_usage", turnId, source: event.source, usage: event.usage });
|
|
5160
5946
|
} else {
|
|
5161
|
-
this.emit({ type: "rate_limits", turnId, source: event.source,
|
|
5947
|
+
this.emit({ type: "rate_limits", turnId, source: event.source, quota: event.quota });
|
|
5162
5948
|
}
|
|
5163
5949
|
return;
|
|
5164
5950
|
}
|
|
@@ -5259,7 +6045,7 @@ class LogicalAgentSession {
|
|
|
5259
6045
|
if (this.adapter.execution.lifetime === "turn") {
|
|
5260
6046
|
this.processTurnEnded = true;
|
|
5261
6047
|
} else {
|
|
5262
|
-
Promise.resolve().then(() => this.safeBoundaryFlush).then(() => this.startNextQueued());
|
|
6048
|
+
Promise.resolve().then(() => this.safeBoundaryFlush).then(() => this.settingsUpdateTail).then(() => this.startNextQueued());
|
|
5263
6049
|
}
|
|
5264
6050
|
}
|
|
5265
6051
|
flushSafeBoundaryQueue() {
|
|
@@ -5759,8 +6545,14 @@ function createAgentDriverSdkWithRegistry(options) {
|
|
|
5759
6545
|
assertAdapterCompatibility(String(registration.id), registration.capabilities, adapter);
|
|
5760
6546
|
const command = capabilities2.commandOverride ? input.command : undefined;
|
|
5761
6547
|
const result = await adapter.probe(command);
|
|
5762
|
-
if (result.status === "healthy")
|
|
5763
|
-
return {
|
|
6548
|
+
if (result.status === "healthy") {
|
|
6549
|
+
return {
|
|
6550
|
+
status: "healthy",
|
|
6551
|
+
version: result.version,
|
|
6552
|
+
capabilities: capabilities2,
|
|
6553
|
+
reasoning: result.reasoning
|
|
6554
|
+
};
|
|
6555
|
+
}
|
|
5764
6556
|
return {
|
|
5765
6557
|
status: "unhealthy",
|
|
5766
6558
|
error: {
|
|
@@ -5769,7 +6561,8 @@ function createAgentDriverSdkWithRegistry(options) {
|
|
|
5769
6561
|
message: `Backend ${input.backend} is unavailable`,
|
|
5770
6562
|
retryable: true
|
|
5771
6563
|
},
|
|
5772
|
-
capabilities: capabilities2
|
|
6564
|
+
capabilities: capabilities2,
|
|
6565
|
+
reasoning: result.reasoning
|
|
5773
6566
|
};
|
|
5774
6567
|
} catch (error) {
|
|
5775
6568
|
const contractInvalid = error instanceof Error && (error.message.startsWith("Adapter ") || error.message.startsWith("Agent backend registration "));
|
|
@@ -5963,45 +6756,184 @@ class RuntimeNotificationState {
|
|
|
5963
6756
|
return false;
|
|
5964
6757
|
return this.lastEncodeFailedFingerprint === fingerprint && this.lastEncodeFailedSessionId === sessionId;
|
|
5965
6758
|
}
|
|
5966
|
-
filterUncontributedMessages(messages, sessionId) {
|
|
5967
|
-
if (this.contributionSessionId !== sessionId)
|
|
5968
|
-
return messages;
|
|
5969
|
-
return messages.filter((m) => {
|
|
5970
|
-
const identity = inboxNoticeMessageIdentity(m);
|
|
5971
|
-
return identity.length === 0 || !this.contributedIdentities.has(identity);
|
|
6759
|
+
filterUncontributedMessages(messages, sessionId) {
|
|
6760
|
+
if (this.contributionSessionId !== sessionId)
|
|
6761
|
+
return messages;
|
|
6762
|
+
return messages.filter((m) => {
|
|
6763
|
+
const identity = inboxNoticeMessageIdentity(m);
|
|
6764
|
+
return identity.length === 0 || !this.contributedIdentities.has(identity);
|
|
6765
|
+
});
|
|
6766
|
+
}
|
|
6767
|
+
add(count = 1) {
|
|
6768
|
+
this.pendingCountValue += count;
|
|
6769
|
+
}
|
|
6770
|
+
schedule(callback, delayMs) {
|
|
6771
|
+
if (this.timerValue)
|
|
6772
|
+
return false;
|
|
6773
|
+
this.timerValue = setTimeout(() => {
|
|
6774
|
+
this.timerValue = null;
|
|
6775
|
+
callback();
|
|
6776
|
+
}, delayMs);
|
|
6777
|
+
this.timerValue.unref?.();
|
|
6778
|
+
return true;
|
|
6779
|
+
}
|
|
6780
|
+
takePendingAndClearTimer() {
|
|
6781
|
+
const count = this.pendingCountValue;
|
|
6782
|
+
this.pendingCountValue = 0;
|
|
6783
|
+
if (this.timerValue) {
|
|
6784
|
+
clearTimeout(this.timerValue);
|
|
6785
|
+
this.timerValue = null;
|
|
6786
|
+
}
|
|
6787
|
+
return count;
|
|
6788
|
+
}
|
|
6789
|
+
ensureContributionSession(sessionId) {
|
|
6790
|
+
if (this.contributionSessionId !== sessionId) {
|
|
6791
|
+
this.contributionSessionId = sessionId;
|
|
6792
|
+
this.contributedIdentities = new Set;
|
|
6793
|
+
}
|
|
6794
|
+
}
|
|
6795
|
+
}
|
|
6796
|
+
// src/runtime/errorDiagnostics.ts
|
|
6797
|
+
import { createHash as createHash2 } from "crypto";
|
|
6798
|
+
// agent-driver/dist/provider-quota.js
|
|
6799
|
+
import { execFile } from "node:child_process";
|
|
6800
|
+
import { readFile } from "node:fs/promises";
|
|
6801
|
+
import { homedir as homedir3 } from "node:os";
|
|
6802
|
+
import { join as join9 } from "node:path";
|
|
6803
|
+
import { promisify } from "node:util";
|
|
6804
|
+
import { randomBytes as randomBytes4 } from "node:crypto";
|
|
6805
|
+
var execFileAsync = promisify(execFile);
|
|
6806
|
+
var claudeAccessToken = null;
|
|
6807
|
+
var claudeSourceEpoch = randomBytes4(16).toString("base64url");
|
|
6808
|
+
function parseCredentials(value) {
|
|
6809
|
+
try {
|
|
6810
|
+
const parsed = JSON.parse(value);
|
|
6811
|
+
return parsed && typeof parsed === "object" ? parsed : null;
|
|
6812
|
+
} catch {
|
|
6813
|
+
return null;
|
|
6814
|
+
}
|
|
6815
|
+
}
|
|
6816
|
+
async function claudeCredentials(options) {
|
|
6817
|
+
const platform = options.platform ?? process.platform;
|
|
6818
|
+
if (platform === "darwin") {
|
|
6819
|
+
try {
|
|
6820
|
+
const value = options.readKeychain ? await options.readKeychain() : (await execFileAsync("security", ["find-generic-password", "-s", "Claude Code-credentials", "-w"], {
|
|
6821
|
+
timeout: 3000,
|
|
6822
|
+
maxBuffer: 256 * 1024
|
|
6823
|
+
})).stdout;
|
|
6824
|
+
const parsed = parseCredentials(value.trim());
|
|
6825
|
+
if (parsed)
|
|
6826
|
+
return parsed;
|
|
6827
|
+
} catch {}
|
|
6828
|
+
}
|
|
6829
|
+
const env = options.env ?? process.env;
|
|
6830
|
+
const root = env.CLAUDE_CONFIG_DIR || join9(options.home ?? homedir3(), ".claude");
|
|
6831
|
+
try {
|
|
6832
|
+
const value = options.readCredentialsFile ? await options.readCredentialsFile(join9(root, ".credentials.json")) : await readFile(join9(root, ".credentials.json"), "utf8");
|
|
6833
|
+
return parseCredentials(value);
|
|
6834
|
+
} catch {
|
|
6835
|
+
return null;
|
|
6836
|
+
}
|
|
6837
|
+
}
|
|
6838
|
+
function mappedPlanName2(value) {
|
|
6839
|
+
switch (value) {
|
|
6840
|
+
case "free":
|
|
6841
|
+
return "Free";
|
|
6842
|
+
case "pro":
|
|
6843
|
+
return "Pro";
|
|
6844
|
+
case "max":
|
|
6845
|
+
return "Max";
|
|
6846
|
+
case "team":
|
|
6847
|
+
return "Team";
|
|
6848
|
+
case "enterprise":
|
|
6849
|
+
return "Enterprise";
|
|
6850
|
+
default:
|
|
6851
|
+
return;
|
|
6852
|
+
}
|
|
6853
|
+
}
|
|
6854
|
+
function resetIso2(value) {
|
|
6855
|
+
if (typeof value !== "string")
|
|
6856
|
+
return;
|
|
6857
|
+
const date = new Date(value);
|
|
6858
|
+
return Number.isNaN(date.getTime()) ? undefined : date.toISOString();
|
|
6859
|
+
}
|
|
6860
|
+
function claudeLimit(key, value) {
|
|
6861
|
+
if (!value || typeof value !== "object")
|
|
6862
|
+
return null;
|
|
6863
|
+
const row = value;
|
|
6864
|
+
if (typeof row.utilization !== "number" || !Number.isFinite(row.utilization) || row.utilization < 0 || row.utilization > 100)
|
|
6865
|
+
return null;
|
|
6866
|
+
const model = key.includes("sonnet") ? { kind: "reported", id: "claude-sonnet" } : key.includes("opus") ? { kind: "reported", id: "claude-opus" } : { kind: "not_applicable" };
|
|
6867
|
+
const window2 = key === "five_hour" ? { kind: "rolling", durationSeconds: 18000, displayName: "5 hour usage limit" } : { kind: "rolling", durationSeconds: 604800, displayName: "7 day usage limit" };
|
|
6868
|
+
const resetsAt = resetIso2(row.resets_at ?? row.resetsAt);
|
|
6869
|
+
return {
|
|
6870
|
+
bucket: {
|
|
6871
|
+
limitId: key,
|
|
6872
|
+
product: { kind: "reported", id: "claude", displayName: "Claude" },
|
|
6873
|
+
model,
|
|
6874
|
+
window: window2
|
|
6875
|
+
},
|
|
6876
|
+
usedPercent: row.utilization,
|
|
6877
|
+
...resetsAt ? { resetsAt } : {}
|
|
6878
|
+
};
|
|
6879
|
+
}
|
|
6880
|
+
async function readClaudeQuota(options) {
|
|
6881
|
+
const env = options.env ?? process.env;
|
|
6882
|
+
if (env.ANTHROPIC_API_KEY || env.ANTHROPIC_BASE_URL)
|
|
6883
|
+
return null;
|
|
6884
|
+
const credentials = await claudeCredentials(options);
|
|
6885
|
+
const token = credentials?.claudeAiOauth?.accessToken;
|
|
6886
|
+
if (typeof token !== "string" || token.length === 0)
|
|
6887
|
+
return null;
|
|
6888
|
+
if (claudeAccessToken !== token) {
|
|
6889
|
+
claudeAccessToken = token;
|
|
6890
|
+
claudeSourceEpoch = randomBytes4(16).toString("base64url");
|
|
6891
|
+
}
|
|
6892
|
+
let response;
|
|
6893
|
+
try {
|
|
6894
|
+
response = await (options.fetchUsage ?? fetch)("https://api.anthropic.com/api/oauth/usage", {
|
|
6895
|
+
headers: {
|
|
6896
|
+
authorization: `Bearer ${token}`,
|
|
6897
|
+
"anthropic-beta": "oauth-2025-04-20"
|
|
6898
|
+
},
|
|
6899
|
+
signal: AbortSignal.timeout(5000)
|
|
5972
6900
|
});
|
|
6901
|
+
} catch {
|
|
6902
|
+
return { status: "error", sourceEpoch: claudeSourceEpoch, code: "network", retryable: true };
|
|
5973
6903
|
}
|
|
5974
|
-
|
|
5975
|
-
|
|
6904
|
+
if (response.status === 401 || response.status === 403) {
|
|
6905
|
+
return { status: "error", sourceEpoch: claudeSourceEpoch, code: "unauthorized", retryable: false };
|
|
5976
6906
|
}
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
return false;
|
|
5980
|
-
this.timerValue = setTimeout(() => {
|
|
5981
|
-
this.timerValue = null;
|
|
5982
|
-
callback();
|
|
5983
|
-
}, delayMs);
|
|
5984
|
-
this.timerValue.unref?.();
|
|
5985
|
-
return true;
|
|
6907
|
+
if (!response.ok) {
|
|
6908
|
+
return { status: "error", sourceEpoch: claudeSourceEpoch, code: "provider_error", retryable: response.status === 429 || response.status >= 500 };
|
|
5986
6909
|
}
|
|
5987
|
-
|
|
5988
|
-
|
|
5989
|
-
|
|
5990
|
-
|
|
5991
|
-
|
|
5992
|
-
this.timerValue = null;
|
|
5993
|
-
}
|
|
5994
|
-
return count;
|
|
6910
|
+
let body;
|
|
6911
|
+
try {
|
|
6912
|
+
body = await response.json();
|
|
6913
|
+
} catch {
|
|
6914
|
+
return { status: "error", sourceEpoch: claudeSourceEpoch, code: "invalid_response", retryable: true };
|
|
5995
6915
|
}
|
|
5996
|
-
|
|
5997
|
-
|
|
5998
|
-
this.contributionSessionId = sessionId;
|
|
5999
|
-
this.contributedIdentities = new Set;
|
|
6000
|
-
}
|
|
6916
|
+
if (!body || typeof body !== "object") {
|
|
6917
|
+
return { status: "error", sourceEpoch: claudeSourceEpoch, code: "invalid_response", retryable: true };
|
|
6001
6918
|
}
|
|
6919
|
+
const record4 = body;
|
|
6920
|
+
const limits = ["five_hour", "seven_day", "seven_day_sonnet", "seven_day_opus"].map((key) => claudeLimit(key, record4[key])).filter((limit) => limit !== null);
|
|
6921
|
+
if (limits.length === 0) {
|
|
6922
|
+
return { status: "error", sourceEpoch: claudeSourceEpoch, code: "invalid_response", retryable: true };
|
|
6923
|
+
}
|
|
6924
|
+
const planName = mappedPlanName2(credentials?.claudeAiOauth?.subscriptionType);
|
|
6925
|
+
return {
|
|
6926
|
+
status: "available",
|
|
6927
|
+
sourceEpoch: claudeSourceEpoch,
|
|
6928
|
+
...planName ? { planName } : {},
|
|
6929
|
+
freshForSeconds: 300,
|
|
6930
|
+
limits
|
|
6931
|
+
};
|
|
6932
|
+
}
|
|
6933
|
+
async function readBuiltinProviderQuota(backend, options = {}) {
|
|
6934
|
+
return backend === "claude" ? readClaudeQuota(options) : null;
|
|
6002
6935
|
}
|
|
6003
6936
|
// src/runtime/errorDiagnostics.ts
|
|
6004
|
-
import { createHash as createHash2 } from "crypto";
|
|
6005
6937
|
var ERROR_EXCERPT_MAX_BYTES = 4000;
|
|
6006
6938
|
var ERROR_FINGERPRINT_LEN = 16;
|
|
6007
6939
|
var ERROR_LEN_BUCKETS = [
|
|
@@ -6642,10 +7574,10 @@ function stableNormalizeApmHeldFreshness(value) {
|
|
|
6642
7574
|
return value.map((item) => stableNormalizeApmHeldFreshness(item));
|
|
6643
7575
|
if (!value || typeof value !== "object")
|
|
6644
7576
|
return value;
|
|
6645
|
-
const
|
|
7577
|
+
const record4 = value;
|
|
6646
7578
|
const normalized = {};
|
|
6647
|
-
for (const key of Object.keys(
|
|
6648
|
-
normalized[key] = stableNormalizeApmHeldFreshness(
|
|
7579
|
+
for (const key of Object.keys(record4).sort()) {
|
|
7580
|
+
normalized[key] = stableNormalizeApmHeldFreshness(record4[key]);
|
|
6649
7581
|
}
|
|
6650
7582
|
return normalized;
|
|
6651
7583
|
}
|
|
@@ -6761,13 +7693,13 @@ function reduceManager(state, event) {
|
|
|
6761
7693
|
const existing = state.agents[event.agentId];
|
|
6762
7694
|
if (!existing || existing.execution.sessionInstanceId !== event.sessionInstanceId)
|
|
6763
7695
|
return { state, effects: [] };
|
|
6764
|
-
const
|
|
6765
|
-
if (!
|
|
7696
|
+
const record4 = existing.pendingAdmissions.find((entry) => entry.sessionInstanceId === event.sessionInstanceId && entry.commandId === event.commandId);
|
|
7697
|
+
if (!record4)
|
|
6766
7698
|
return { state, effects: [] };
|
|
6767
7699
|
const agent = clone(existing);
|
|
6768
7700
|
agent.pendingAdmissions = agent.pendingAdmissions.filter((entry) => entry.sessionInstanceId !== event.sessionInstanceId || entry.commandId !== event.commandId);
|
|
6769
7701
|
syncExecutionProjection(agent);
|
|
6770
|
-
return commit(state, agent, event.outcome === "failed" ? recoveryEffects(agent, [
|
|
7702
|
+
return commit(state, agent, event.outcome === "failed" ? recoveryEffects(agent, [record4]) : []);
|
|
6771
7703
|
}
|
|
6772
7704
|
case "admission_acknowledged": {
|
|
6773
7705
|
const existing = state.agents[event.agentId];
|
|
@@ -6824,6 +7756,29 @@ function reduceManager(state, event) {
|
|
|
6824
7756
|
a.inbox = [...a.inbox, event.message];
|
|
6825
7757
|
a.idleSince = null;
|
|
6826
7758
|
});
|
|
7759
|
+
case "runtime_config_queued":
|
|
7760
|
+
return mutate(state, event.agentId, (a) => {
|
|
7761
|
+
if (!a.inbox.some((message) => message.id === event.message.id)) {
|
|
7762
|
+
a.inbox = [...a.inbox, event.message];
|
|
7763
|
+
}
|
|
7764
|
+
syncExecutionProjection(a);
|
|
7765
|
+
a.idleSince = null;
|
|
7766
|
+
});
|
|
7767
|
+
case "runtime_config_applied": {
|
|
7768
|
+
const existing = state.agents[event.agentId];
|
|
7769
|
+
if (!existing)
|
|
7770
|
+
return { state, effects: [] };
|
|
7771
|
+
const agent = clone(existing);
|
|
7772
|
+
if (agent.status !== "running" || leaseIsWorking(agent.execution.lease) || agent.pendingAdmissions.length > 0 || agent.inbox.length === 0)
|
|
7773
|
+
return { state, effects: [] };
|
|
7774
|
+
const messages = drainInbox(agent);
|
|
7775
|
+
return commit(state, agent, messages.map((message) => ({
|
|
7776
|
+
type: "send",
|
|
7777
|
+
agentId: event.agentId,
|
|
7778
|
+
message,
|
|
7779
|
+
mode: "idle"
|
|
7780
|
+
})));
|
|
7781
|
+
}
|
|
6827
7782
|
case "turn_started": {
|
|
6828
7783
|
const existing = state.agents[event.agentId];
|
|
6829
7784
|
if (!existing || existing.execution.sessionInstanceId !== event.sessionInstanceId)
|
|
@@ -7031,7 +7986,7 @@ function onTurnCompleted(state, agentId, sessionInstanceId, nowMs, turnId, endRe
|
|
|
7031
7986
|
agent.stalledSessionId = null;
|
|
7032
7987
|
syncExecutionProjection(agent);
|
|
7033
7988
|
const clearEffects = clearedStallSessionId === null ? [] : [{ type: "clear_stall_recovery", agentId, sessionId: clearedStallSessionId }];
|
|
7034
|
-
if (agent.inbox.length > 0) {
|
|
7989
|
+
if (agent.inbox.length > 0 && !agent.resetting) {
|
|
7035
7990
|
const messages = drainInbox(agent);
|
|
7036
7991
|
return commit(state, agent, [
|
|
7037
7992
|
...clearEffects,
|
|
@@ -7262,11 +8217,11 @@ function syncExecutionProjection(agent) {
|
|
|
7262
8217
|
agent.lastDeliverAt = agent.pendingAdmissions.length > 0 ? Math.max(...agent.pendingAdmissions.map((entry) => entry.admittedAt)) : null;
|
|
7263
8218
|
}
|
|
7264
8219
|
function recoveryEffects(agent, records) {
|
|
7265
|
-
return records.filter((
|
|
8220
|
+
return records.filter((record4) => record4.requeueOnFailure).map((record4) => ({
|
|
7266
8221
|
type: "requeue_delivery",
|
|
7267
8222
|
agentId: agent.agentId,
|
|
7268
|
-
message:
|
|
7269
|
-
mode:
|
|
8223
|
+
message: record4.exactAgentMsg,
|
|
8224
|
+
mode: record4.mode
|
|
7270
8225
|
}));
|
|
7271
8226
|
}
|
|
7272
8227
|
function commit(state, agent, effects) {
|
|
@@ -7677,14 +8632,14 @@ function createLogger(options = {}) {
|
|
|
7677
8632
|
`));
|
|
7678
8633
|
const err = options.err ?? ((line) => process.stderr.write(line + `
|
|
7679
8634
|
`));
|
|
7680
|
-
const
|
|
8635
|
+
const record4 = options.record;
|
|
7681
8636
|
const emit = (level, message, data) => {
|
|
7682
8637
|
if (LEVEL_RANK[level] < minRank)
|
|
7683
8638
|
return;
|
|
7684
8639
|
const time = now();
|
|
7685
8640
|
const line = `${time} ${header} ${level.toUpperCase().padEnd(5)} ${message}${formatData(data)}`;
|
|
7686
8641
|
try {
|
|
7687
|
-
|
|
8642
|
+
record4?.({ time, header, level, message, fields: recordFields(data) });
|
|
7688
8643
|
} catch {}
|
|
7689
8644
|
(level === "warn" || level === "error" ? err : out)(line);
|
|
7690
8645
|
};
|
|
@@ -7946,9 +8901,13 @@ class AgentProcessManager {
|
|
|
7946
8901
|
state;
|
|
7947
8902
|
sessions = new Map;
|
|
7948
8903
|
runtimeConfigs = new Map;
|
|
8904
|
+
appliedRuntimeConfigs = new Map;
|
|
8905
|
+
pendingRuntimeConfigUpdates = new Map;
|
|
8906
|
+
runtimeConfigApplyRunning = new Set;
|
|
7949
8907
|
resumeSessions = new Map;
|
|
7950
8908
|
launchIds = new Map;
|
|
7951
8909
|
liveSessions = new Map;
|
|
8910
|
+
liveBackendIds = new Map;
|
|
7952
8911
|
activeSpawnState = new Map;
|
|
7953
8912
|
publishedAgentActivity = new Map;
|
|
7954
8913
|
traceProcessNonce = randomUUID5();
|
|
@@ -7977,19 +8936,156 @@ class AgentProcessManager {
|
|
|
7977
8936
|
this.state = createInitialManagerState(this.opts.staleThresholdMs, this.opts.idleTimeoutMs, this.opts.resetStuckThresholdMs, this.opts.stoppingStuckThresholdMs, this.opts.idleResetTimeoutMs);
|
|
7978
8937
|
}
|
|
7979
8938
|
register(agentId, launch) {
|
|
7980
|
-
|
|
7981
|
-
this.runtimeConfigs.set(agentId, launch.runtimeConfig);
|
|
8939
|
+
const runtimeConfigAcceptance = launch?.runtimeConfig ? this.acceptRuntimeConfig(agentId, launch.runtimeConfig) : undefined;
|
|
7982
8940
|
if (launch?.sessionId)
|
|
7983
8941
|
this.resumeSessions.set(agentId, launch.sessionId);
|
|
7984
8942
|
if (launch?.launchId)
|
|
7985
8943
|
this.launchIds.set(agentId, launch.launchId);
|
|
7986
8944
|
this.dispatch({ type: "register", agentId });
|
|
8945
|
+
const registered = this.state.agents[agentId];
|
|
8946
|
+
if (launch?.runtimeConfig && launch.applyRuntimeConfig !== false && (runtimeConfigAcceptance === "accepted" || this.pendingRuntimeConfigUpdates.has(agentId)) && this.sessions.has(agentId) && registered && !isActivelyWorking(registered)) {
|
|
8947
|
+
this.convergeRuntimeConfig(agentId);
|
|
8948
|
+
}
|
|
8949
|
+
}
|
|
8950
|
+
async updateRuntimeConfig(agentId, config) {
|
|
8951
|
+
const accepted = this.acceptRuntimeConfig(agentId, config);
|
|
8952
|
+
if (accepted === "stale" || accepted === "idempotent")
|
|
8953
|
+
return accepted;
|
|
8954
|
+
const session = this.sessions.get(agentId);
|
|
8955
|
+
if (!session) {
|
|
8956
|
+
this.pendingRuntimeConfigUpdates.delete(agentId);
|
|
8957
|
+
return "saved_for_start";
|
|
8958
|
+
}
|
|
8959
|
+
const agent = this.state.agents[agentId];
|
|
8960
|
+
if (agent && (agent.turnActive || isActivelyWorking(agent)))
|
|
8961
|
+
return "deferred";
|
|
8962
|
+
return this.convergeRuntimeConfig(agentId);
|
|
8963
|
+
}
|
|
8964
|
+
acceptRuntimeConfig(agentId, config) {
|
|
8965
|
+
const desired = this.runtimeConfigs.get(agentId);
|
|
8966
|
+
const revision = config.runtimeConfigRevision ?? 0;
|
|
8967
|
+
const desiredRevision = desired?.runtimeConfigRevision ?? 0;
|
|
8968
|
+
if (desired && revision < desiredRevision)
|
|
8969
|
+
return "stale";
|
|
8970
|
+
if (desired && revision === desiredRevision) {
|
|
8971
|
+
if (this.runtimeConfigTuple(desired) !== this.runtimeConfigTuple(config)) {
|
|
8972
|
+
throw new Error(`Conflicting runtime config for ${agentId} at revision ${revision}`);
|
|
8973
|
+
}
|
|
8974
|
+
this.runtimeConfigs.set(agentId, config);
|
|
8975
|
+
return "idempotent";
|
|
8976
|
+
}
|
|
8977
|
+
this.runtimeConfigs.set(agentId, config);
|
|
8978
|
+
this.pendingRuntimeConfigUpdates.set(agentId, config);
|
|
8979
|
+
return "accepted";
|
|
8980
|
+
}
|
|
8981
|
+
runtimeConfigTuple(config) {
|
|
8982
|
+
return JSON.stringify({
|
|
8983
|
+
version: config.version,
|
|
8984
|
+
runtime: config.runtime,
|
|
8985
|
+
model: config.model,
|
|
8986
|
+
mode: config.mode,
|
|
8987
|
+
reasoningEffort: config.reasoningEffort ?? null,
|
|
8988
|
+
provider: config.provider ?? null,
|
|
8989
|
+
command: config.command ?? null,
|
|
8990
|
+
disallowedTools: config.disallowedTools ?? null,
|
|
8991
|
+
envVars: config.envVars ?? null
|
|
8992
|
+
});
|
|
8993
|
+
}
|
|
8994
|
+
runtimeLaunchTuple(config) {
|
|
8995
|
+
return JSON.stringify({
|
|
8996
|
+
version: config.version,
|
|
8997
|
+
runtime: config.runtime,
|
|
8998
|
+
model: config.model,
|
|
8999
|
+
mode: config.mode,
|
|
9000
|
+
provider: config.provider ?? null,
|
|
9001
|
+
command: config.command ?? null,
|
|
9002
|
+
disallowedTools: config.disallowedTools ?? null,
|
|
9003
|
+
envVars: config.envVars ?? null
|
|
9004
|
+
});
|
|
9005
|
+
}
|
|
9006
|
+
async convergeRuntimeConfig(agentId, restartOnFailure = true) {
|
|
9007
|
+
if (this.runtimeConfigApplyRunning.has(agentId))
|
|
9008
|
+
return "deferred";
|
|
9009
|
+
const session = this.sessions.get(agentId);
|
|
9010
|
+
if (!session)
|
|
9011
|
+
return "saved_for_start";
|
|
9012
|
+
this.runtimeConfigApplyRunning.add(agentId);
|
|
9013
|
+
try {
|
|
9014
|
+
while (this.sessions.get(agentId) === session) {
|
|
9015
|
+
const desired = this.pendingRuntimeConfigUpdates.get(agentId) ?? this.runtimeConfigs.get(agentId);
|
|
9016
|
+
if (!desired)
|
|
9017
|
+
return "idempotent";
|
|
9018
|
+
const desiredRevision = desired.runtimeConfigRevision ?? 0;
|
|
9019
|
+
const applied = this.appliedRuntimeConfigs.get(agentId);
|
|
9020
|
+
const appliedRevision = applied?.runtimeConfigRevision ?? -1;
|
|
9021
|
+
if (applied && desiredRevision <= appliedRevision) {
|
|
9022
|
+
this.pendingRuntimeConfigUpdates.delete(agentId);
|
|
9023
|
+
return desiredRevision === appliedRevision ? "idempotent" : "stale";
|
|
9024
|
+
}
|
|
9025
|
+
const canApplyNatively = applied && this.runtimeLaunchTuple(applied) === this.runtimeLaunchTuple(desired) && typeof session.updateSettings === "function";
|
|
9026
|
+
let result = { status: "unsupported" };
|
|
9027
|
+
if (canApplyNatively) {
|
|
9028
|
+
try {
|
|
9029
|
+
result = await session.updateSettings({
|
|
9030
|
+
reasoningEffort: desired.reasoningEffort ?? null
|
|
9031
|
+
});
|
|
9032
|
+
} catch (error) {
|
|
9033
|
+
this.log.warn("runtime config live apply threw; restarting at safe boundary", {
|
|
9034
|
+
agentId,
|
|
9035
|
+
revision: desiredRevision,
|
|
9036
|
+
error: String(error)
|
|
9037
|
+
});
|
|
9038
|
+
if (restartOnFailure)
|
|
9039
|
+
await this.restartForRuntimeConfig(agentId, session);
|
|
9040
|
+
return "saved_for_start";
|
|
9041
|
+
}
|
|
9042
|
+
}
|
|
9043
|
+
if (result.status !== "applied") {
|
|
9044
|
+
this.log.warn("runtime config live apply unavailable; restarting at safe boundary", {
|
|
9045
|
+
agentId,
|
|
9046
|
+
revision: desiredRevision,
|
|
9047
|
+
status: result.status,
|
|
9048
|
+
code: result.error?.code
|
|
9049
|
+
});
|
|
9050
|
+
if (restartOnFailure)
|
|
9051
|
+
await this.restartForRuntimeConfig(agentId, session);
|
|
9052
|
+
return "saved_for_start";
|
|
9053
|
+
}
|
|
9054
|
+
this.appliedRuntimeConfigs.set(agentId, desired);
|
|
9055
|
+
if ((this.pendingRuntimeConfigUpdates.get(agentId)?.runtimeConfigRevision ?? -1) === desiredRevision) {
|
|
9056
|
+
this.pendingRuntimeConfigUpdates.delete(agentId);
|
|
9057
|
+
}
|
|
9058
|
+
const latestRevision = this.runtimeConfigs.get(agentId)?.runtimeConfigRevision ?? 0;
|
|
9059
|
+
if (latestRevision <= desiredRevision) {
|
|
9060
|
+
this.dispatch({ type: "runtime_config_applied", agentId });
|
|
9061
|
+
return "applied";
|
|
9062
|
+
}
|
|
9063
|
+
}
|
|
9064
|
+
return "saved_for_start";
|
|
9065
|
+
} finally {
|
|
9066
|
+
this.runtimeConfigApplyRunning.delete(agentId);
|
|
9067
|
+
}
|
|
9068
|
+
}
|
|
9069
|
+
async restartForRuntimeConfig(agentId, session) {
|
|
9070
|
+
if (this.sessions.get(agentId) !== session)
|
|
9071
|
+
return;
|
|
9072
|
+
this.opts.timeline?.fenceSession(agentId);
|
|
9073
|
+
this.markResetting(agentId);
|
|
9074
|
+
await this.stop(agentId);
|
|
7987
9075
|
}
|
|
7988
9076
|
deliver(agentId, message) {
|
|
7989
9077
|
const normalized = message.id ? message : {
|
|
7990
9078
|
...message,
|
|
7991
9079
|
id: message.seq !== undefined ? `${agentId}:source:${message.seq}` : `${agentId}:synthetic:${this.nextDeliveryOrdinal++}`
|
|
7992
9080
|
};
|
|
9081
|
+
if (this.sessions.has(agentId) && this.pendingRuntimeConfigUpdates.has(agentId)) {
|
|
9082
|
+
this.dispatch({
|
|
9083
|
+
type: "runtime_config_queued",
|
|
9084
|
+
agentId,
|
|
9085
|
+
message: normalized
|
|
9086
|
+
});
|
|
9087
|
+
return this.state.agents[agentId] !== undefined;
|
|
9088
|
+
}
|
|
7993
9089
|
const effects = this.dispatch({ type: "wake", agentId, message: normalized, nowMs: this.now() });
|
|
7994
9090
|
return effects.length > 0;
|
|
7995
9091
|
}
|
|
@@ -8033,7 +9129,11 @@ class AgentProcessManager {
|
|
|
8033
9129
|
this.emitErrorAudit(agentId, "reset", "resume_control_update_failed", "Reset aborted because resume control could not be persisted");
|
|
8034
9130
|
throw new Error("Reset aborted because resume control could not be persisted");
|
|
8035
9131
|
}
|
|
8036
|
-
this.register(agentId, {
|
|
9132
|
+
this.register(agentId, {
|
|
9133
|
+
runtimeConfig: opts.runtimeConfig,
|
|
9134
|
+
launchId: opts.launchId,
|
|
9135
|
+
applyRuntimeConfig: false
|
|
9136
|
+
});
|
|
8037
9137
|
if (!opts.forgetSession)
|
|
8038
9138
|
this.opts.timeline?.fenceSession(agentId);
|
|
8039
9139
|
this.abortCurrentTurn(agentId, opts.abortCause);
|
|
@@ -8134,6 +9234,9 @@ class AgentProcessManager {
|
|
|
8134
9234
|
const agent = this.state.agents[agentId];
|
|
8135
9235
|
return agent ? this.deriveActivity(agent) : null;
|
|
8136
9236
|
}
|
|
9237
|
+
agentBackendId(agentId) {
|
|
9238
|
+
return this.liveBackendIds.get(agentId) ?? null;
|
|
9239
|
+
}
|
|
8137
9240
|
statusProjection(nowMs) {
|
|
8138
9241
|
return Object.values(this.state.agents).map((a) => ({
|
|
8139
9242
|
agentId: a.agentId,
|
|
@@ -8659,6 +9762,7 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
8659
9762
|
throw new Error(`AgentProcessManager: spawn for ${agentId} has no command`);
|
|
8660
9763
|
const prompt = this.withFooter(first.text);
|
|
8661
9764
|
const driver = this.opts.driverFor(agentId, this.runtimeConfigs.get(agentId));
|
|
9765
|
+
this.liveBackendIds.set(agentId, driver.id);
|
|
8662
9766
|
const base = this.opts.baseContextFor(agentId);
|
|
8663
9767
|
const configuredRuntime = this.runtimeConfigs.get(agentId) ?? base.config?.runtimeConfig;
|
|
8664
9768
|
this.log.info("spawning agent", {
|
|
@@ -8779,7 +9883,10 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
8779
9883
|
if (state.session && this.sessions.get(agentId) === state.session)
|
|
8780
9884
|
this.sessions.delete(agentId);
|
|
8781
9885
|
this.liveSessions.delete(agentId);
|
|
9886
|
+
if (this.activeSpawnState.get(agentId) === state)
|
|
9887
|
+
this.liveBackendIds.delete(agentId);
|
|
8782
9888
|
if (this.activeSpawnState.get(agentId) === state) {
|
|
9889
|
+
this.appliedRuntimeConfigs.delete(agentId);
|
|
8783
9890
|
this.activeSpawnState.delete(agentId);
|
|
8784
9891
|
this.nonCleanEndMarker.delete(agentId);
|
|
8785
9892
|
}
|
|
@@ -8827,6 +9934,10 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
8827
9934
|
state.session = session;
|
|
8828
9935
|
state.sessionInstanceId = session.sessionInstanceId;
|
|
8829
9936
|
this.sessions.set(agentId, session);
|
|
9937
|
+
this.appliedRuntimeConfigs.set(agentId, runtimeConfig);
|
|
9938
|
+
if ((this.pendingRuntimeConfigUpdates.get(agentId)?.runtimeConfigRevision ?? -1) <= (runtimeConfig.runtimeConfigRevision ?? 0)) {
|
|
9939
|
+
this.pendingRuntimeConfigUpdates.delete(agentId);
|
|
9940
|
+
}
|
|
8830
9941
|
this.dispatch({
|
|
8831
9942
|
type: "attach_session",
|
|
8832
9943
|
agentId,
|
|
@@ -9060,6 +10171,12 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
9060
10171
|
runtime: runtimeId
|
|
9061
10172
|
});
|
|
9062
10173
|
}
|
|
10174
|
+
if (event.type === "token_usage") {
|
|
10175
|
+
this.opts.onTokenUsage?.({ agentId, backendId: runtimeId, usage: event.usage });
|
|
10176
|
+
}
|
|
10177
|
+
if (event.type === "rate_limits") {
|
|
10178
|
+
this.opts.onProviderQuota?.({ agentId, backendId: runtimeId, quota: event.quota });
|
|
10179
|
+
}
|
|
9063
10180
|
if (event.type === "turn_started") {
|
|
9064
10181
|
const timelineTurnOwner = {
|
|
9065
10182
|
sessionInstanceId: event.sessionInstanceId,
|
|
@@ -9174,7 +10291,7 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
9174
10291
|
this.logSessionEnded(agentId, "turn_end");
|
|
9175
10292
|
const marker = this.nonCleanEndMarker.get(agentId);
|
|
9176
10293
|
this.nonCleanEndMarker.delete(agentId);
|
|
9177
|
-
|
|
10294
|
+
const completionEvent = marker !== undefined ? {
|
|
9178
10295
|
type: "turn_completed",
|
|
9179
10296
|
agentId,
|
|
9180
10297
|
sessionInstanceId: event.sessionInstanceId,
|
|
@@ -9189,7 +10306,26 @@ ${this.opts.wakePromptFooter}` : text;
|
|
|
9189
10306
|
sessionInstanceId: event.sessionInstanceId,
|
|
9190
10307
|
nowMs: this.now(),
|
|
9191
10308
|
turnId: event.turnId
|
|
9192
|
-
}
|
|
10309
|
+
};
|
|
10310
|
+
if (this.pendingRuntimeConfigUpdates.has(agentId) && this.sessions.get(agentId) === owner.session) {
|
|
10311
|
+
this.convergeRuntimeConfig(agentId, false).then((result) => {
|
|
10312
|
+
if (result === "saved_for_start" && owner.session)
|
|
10313
|
+
this.markResetting(agentId);
|
|
10314
|
+
this.dispatch(completionEvent, owner);
|
|
10315
|
+
if (result === "saved_for_start" && owner.session) {
|
|
10316
|
+
this.restartForRuntimeConfig(agentId, owner.session);
|
|
10317
|
+
}
|
|
10318
|
+
}).catch((error) => {
|
|
10319
|
+
this.log.error("runtime config convergence failed", { agentId, error: String(error) });
|
|
10320
|
+
if (owner.session)
|
|
10321
|
+
this.markResetting(agentId);
|
|
10322
|
+
this.dispatch(completionEvent, owner);
|
|
10323
|
+
if (owner.session)
|
|
10324
|
+
this.restartForRuntimeConfig(agentId, owner.session);
|
|
10325
|
+
});
|
|
10326
|
+
return;
|
|
10327
|
+
}
|
|
10328
|
+
this.dispatch(completionEvent, owner);
|
|
9193
10329
|
}
|
|
9194
10330
|
}
|
|
9195
10331
|
}
|
|
@@ -9242,7 +10378,7 @@ __export(exports_external, {
|
|
|
9242
10378
|
regexes: () => exports_regexes,
|
|
9243
10379
|
regex: () => _regex,
|
|
9244
10380
|
refine: () => refine,
|
|
9245
|
-
record: () =>
|
|
10381
|
+
record: () => record4,
|
|
9246
10382
|
readonly: () => readonly,
|
|
9247
10383
|
property: () => _property,
|
|
9248
10384
|
promise: () => promise,
|
|
@@ -21398,7 +22534,7 @@ __export(exports_schemas2, {
|
|
|
21398
22534
|
strictObject: () => strictObject,
|
|
21399
22535
|
set: () => set,
|
|
21400
22536
|
refine: () => refine,
|
|
21401
|
-
record: () =>
|
|
22537
|
+
record: () => record4,
|
|
21402
22538
|
readonly: () => readonly,
|
|
21403
22539
|
promise: () => promise,
|
|
21404
22540
|
preprocess: () => preprocess,
|
|
@@ -22475,7 +23611,7 @@ var ZodRecord = /* @__PURE__ */ $constructor("ZodRecord", (inst, def) => {
|
|
|
22475
23611
|
inst.keyType = def.keyType;
|
|
22476
23612
|
inst.valueType = def.valueType;
|
|
22477
23613
|
});
|
|
22478
|
-
function
|
|
23614
|
+
function record4(keyType, valueType, params) {
|
|
22479
23615
|
if (!valueType || !valueType._zod) {
|
|
22480
23616
|
return new ZodRecord({
|
|
22481
23617
|
type: "record",
|
|
@@ -22940,7 +24076,7 @@ var stringbool = (...args) => _stringbool({
|
|
|
22940
24076
|
}, ...args);
|
|
22941
24077
|
function json(params) {
|
|
22942
24078
|
const jsonSchema = lazy(() => {
|
|
22943
|
-
return union([string2(params), number2(), boolean2(), _null3(), array(jsonSchema),
|
|
24079
|
+
return union([string2(params), number2(), boolean2(), _null3(), array(jsonSchema), record4(string2(), jsonSchema)]);
|
|
22944
24080
|
});
|
|
22945
24081
|
return jsonSchema;
|
|
22946
24082
|
}
|
|
@@ -23489,6 +24625,7 @@ var exports_community_machine_schema = {};
|
|
|
23489
24625
|
__export(exports_community_machine_schema, {
|
|
23490
24626
|
communityMachineToken: () => communityMachineToken,
|
|
23491
24627
|
communityMachineCredential: () => communityMachineCredential,
|
|
24628
|
+
communityMachineBackendQuota: () => communityMachineBackendQuota,
|
|
23492
24629
|
communityMachine: () => communityMachine,
|
|
23493
24630
|
communityDiagnosticReport: () => communityDiagnosticReport,
|
|
23494
24631
|
communityBotBinding: () => communityBotBinding,
|
|
@@ -24085,7 +25222,7 @@ function sql(strings, ...params) {
|
|
|
24085
25222
|
return new SQL([new StringChunk(str)]);
|
|
24086
25223
|
}
|
|
24087
25224
|
sql2.raw = raw;
|
|
24088
|
-
function
|
|
25225
|
+
function join10(chunks, separator) {
|
|
24089
25226
|
const result = [];
|
|
24090
25227
|
for (const [i, chunk] of chunks.entries()) {
|
|
24091
25228
|
if (i > 0 && separator !== undefined) {
|
|
@@ -24095,7 +25232,7 @@ function sql(strings, ...params) {
|
|
|
24095
25232
|
}
|
|
24096
25233
|
return new SQL(result);
|
|
24097
25234
|
}
|
|
24098
|
-
sql2.join =
|
|
25235
|
+
sql2.join = join10;
|
|
24099
25236
|
function identifier(value) {
|
|
24100
25237
|
return new Name(value);
|
|
24101
25238
|
}
|
|
@@ -25092,6 +26229,8 @@ var user = sqliteTable("user", {
|
|
|
25092
26229
|
email: text("email").unique().notNull(),
|
|
25093
26230
|
emailVerified: integer2("emailVerified", { mode: "boolean" }),
|
|
25094
26231
|
image: text("image"),
|
|
26232
|
+
avatarVersion: integer2("avatarVersion").notNull().default(0),
|
|
26233
|
+
avatarObjectKey: text("avatarObjectKey"),
|
|
25095
26234
|
createdAt: text("createdAt").notNull().$defaultFn(() => new Date().toISOString()),
|
|
25096
26235
|
updatedAt: text("updatedAt").notNull().$defaultFn(() => new Date().toISOString()),
|
|
25097
26236
|
isBot: integer2("isBot", { mode: "boolean" }).notNull().default(false),
|
|
@@ -25765,8 +26904,23 @@ var communityBotBinding = sqliteTable("community_bot_binding", {
|
|
|
25765
26904
|
runtime: text("runtime").notNull(),
|
|
25766
26905
|
instruction: text("instruction").notNull().default(""),
|
|
25767
26906
|
modelName: text("model_name"),
|
|
26907
|
+
reasoningEffort: text("reasoning_effort"),
|
|
26908
|
+
runtimeConfigRevision: integer2("runtime_config_revision").notNull().default(0),
|
|
25768
26909
|
createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString())
|
|
25769
26910
|
}, (t) => [index("idx_community_bot_binding_machine").on(t.machineId)]);
|
|
26911
|
+
var communityMachineBackendQuota = sqliteTable("community_machine_backend_quota", {
|
|
26912
|
+
machineId: text("machine_id").notNull().references(() => communityMachine.id, { onDelete: "cascade" }),
|
|
26913
|
+
agentBackendId: text("agent_backend_id").$type().notNull(),
|
|
26914
|
+
sourceEpoch: text("source_epoch").notNull(),
|
|
26915
|
+
status: text("status").$type().notNull(),
|
|
26916
|
+
planName: text("plan_name"),
|
|
26917
|
+
freshForSeconds: integer2("fresh_for_seconds"),
|
|
26918
|
+
limits: text("limits", { mode: "json" }).$type(),
|
|
26919
|
+
errorCode: text("error_code"),
|
|
26920
|
+
retryable: integer2("retryable", { mode: "boolean" }),
|
|
26921
|
+
observedAt: text("observed_at").notNull(),
|
|
26922
|
+
updatedAt: text("updated_at").notNull()
|
|
26923
|
+
}, (t) => [primaryKey({ columns: [t.machineId, t.agentBackendId] })]);
|
|
25770
26924
|
var communityAgentRunnerKey = sqliteTable("community_agent_runner_key", {
|
|
25771
26925
|
id: text("id").primaryKey().$defaultFn(() => "crkid_" + nanoid3()),
|
|
25772
26926
|
userId: text("user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
|
|
@@ -25945,6 +27099,11 @@ var HostCommandSchema = exports_external.discriminatedUnion("type", [
|
|
|
25945
27099
|
config: exports_external.unknown(),
|
|
25946
27100
|
launchId: exports_external.string().min(1)
|
|
25947
27101
|
}),
|
|
27102
|
+
exports_external.object({
|
|
27103
|
+
type: exports_external.literal("agent:runtime_config_update"),
|
|
27104
|
+
agentId: exports_external.string().min(1),
|
|
27105
|
+
config: exports_external.unknown()
|
|
27106
|
+
}),
|
|
25948
27107
|
exports_external.object({
|
|
25949
27108
|
type: exports_external.literal("machine:reset_all"),
|
|
25950
27109
|
resets: exports_external.array(exports_external.object({
|
|
@@ -26051,7 +27210,8 @@ class AgentRouter {
|
|
|
26051
27210
|
version: r.version,
|
|
26052
27211
|
status: r.status ?? "healthy",
|
|
26053
27212
|
lastError: r.lastError,
|
|
26054
|
-
lastErrorAt: r.lastErrorAt
|
|
27213
|
+
lastErrorAt: r.lastErrorAt,
|
|
27214
|
+
reasoning: r.reasoning
|
|
26055
27215
|
});
|
|
26056
27216
|
}
|
|
26057
27217
|
}
|
|
@@ -26060,7 +27220,7 @@ class AgentRouter {
|
|
|
26060
27220
|
this.opts.channel.onResync?.(() => ({
|
|
26061
27221
|
ready: this.buildReady(),
|
|
26062
27222
|
sessions: this.opts.manager.liveSessionReports(),
|
|
26063
|
-
activities: this.opts.manager.liveAgentActivities()
|
|
27223
|
+
activities: this.opts.resyncActivities ? this.opts.resyncActivities() : this.opts.manager.liveAgentActivities()
|
|
26064
27224
|
}));
|
|
26065
27225
|
await this.opts.channel.reportReady(this.buildReady());
|
|
26066
27226
|
}
|
|
@@ -26073,7 +27233,8 @@ class AgentRouter {
|
|
|
26073
27233
|
platform: this.opts.platform,
|
|
26074
27234
|
arch: this.opts.arch,
|
|
26075
27235
|
osRelease: this.opts.osRelease,
|
|
26076
|
-
daemonVersion: this.opts.daemonVersion
|
|
27236
|
+
daemonVersion: this.opts.daemonVersion,
|
|
27237
|
+
...this.opts.providerQuotas ? { providerQuotas: this.opts.providerQuotas() } : {}
|
|
26077
27238
|
};
|
|
26078
27239
|
}
|
|
26079
27240
|
healthyRuntimeIds() {
|
|
@@ -26114,11 +27275,10 @@ class AgentRouter {
|
|
|
26114
27275
|
return;
|
|
26115
27276
|
if (existing.status === "healthy" && !existing.lastError && !existing.lastErrorAt)
|
|
26116
27277
|
return;
|
|
26117
|
-
|
|
26118
|
-
|
|
26119
|
-
|
|
26120
|
-
|
|
26121
|
-
});
|
|
27278
|
+
const healthy = { ...existing, status: "healthy" };
|
|
27279
|
+
delete healthy.lastError;
|
|
27280
|
+
delete healthy.lastErrorAt;
|
|
27281
|
+
this.runtimes.set(id, healthy);
|
|
26122
27282
|
this.log.info("runtime marked healthy again", { runtimeId: id });
|
|
26123
27283
|
this.scheduleReadyFrameResend();
|
|
26124
27284
|
}
|
|
@@ -26295,6 +27455,27 @@ class AgentRouter {
|
|
|
26295
27455
|
rewakePrompt: MODEL_SWITCH_REWAKE_PROMPT
|
|
26296
27456
|
}));
|
|
26297
27457
|
break;
|
|
27458
|
+
case "agent:runtime_config_update": {
|
|
27459
|
+
this.log.info("agent:runtime_config_update received", {
|
|
27460
|
+
agentId: cmd.agentId,
|
|
27461
|
+
revision: cmd.config.runtimeConfigRevision ?? 0
|
|
27462
|
+
});
|
|
27463
|
+
try {
|
|
27464
|
+
const result = await this.opts.manager.updateRuntimeConfig(cmd.agentId, cmd.config);
|
|
27465
|
+
this.log.info("agent:runtime_config_update accepted", {
|
|
27466
|
+
agentId: cmd.agentId,
|
|
27467
|
+
revision: cmd.config.runtimeConfigRevision ?? 0,
|
|
27468
|
+
result
|
|
27469
|
+
});
|
|
27470
|
+
} catch (err) {
|
|
27471
|
+
this.log.warn("agent:runtime_config_update rejected", {
|
|
27472
|
+
agentId: cmd.agentId,
|
|
27473
|
+
revision: cmd.config.runtimeConfigRevision ?? 0,
|
|
27474
|
+
error: err instanceof Error ? err.message : String(err)
|
|
27475
|
+
});
|
|
27476
|
+
}
|
|
27477
|
+
break;
|
|
27478
|
+
}
|
|
26298
27479
|
case "agent:stop":
|
|
26299
27480
|
this.log.info("agent:stop received", { agentId: cmd.agentId });
|
|
26300
27481
|
try {
|
|
@@ -26517,20 +27698,20 @@ function parseLocalMessageReminderBody(body, agentId) {
|
|
|
26517
27698
|
}
|
|
26518
27699
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
26519
27700
|
return null;
|
|
26520
|
-
const
|
|
26521
|
-
if (Object.keys(
|
|
27701
|
+
const record5 = value;
|
|
27702
|
+
if (Object.keys(record5).sort().join(",") !== "channel,remindAfterMs,sentSeq")
|
|
26522
27703
|
return null;
|
|
26523
|
-
if (typeof
|
|
27704
|
+
if (typeof record5.channel !== "string" || !isCanonicalChannelScope(record5.channel))
|
|
26524
27705
|
return null;
|
|
26525
|
-
if (!Number.isSafeInteger(
|
|
27706
|
+
if (!Number.isSafeInteger(record5.sentSeq) || record5.sentSeq < 1)
|
|
26526
27707
|
return null;
|
|
26527
|
-
if (!Number.isSafeInteger(
|
|
27708
|
+
if (!Number.isSafeInteger(record5.remindAfterMs) || record5.remindAfterMs !== 0 && record5.remindAfterMs < LOCAL_MESSAGE_REMINDER_MIN_MS || record5.remindAfterMs > LOCAL_MESSAGE_REMINDER_MAX_MS)
|
|
26528
27709
|
return null;
|
|
26529
27710
|
return {
|
|
26530
27711
|
agentId,
|
|
26531
|
-
channel:
|
|
26532
|
-
sentSeq:
|
|
26533
|
-
remindAfterMs:
|
|
27712
|
+
channel: record5.channel,
|
|
27713
|
+
sentSeq: record5.sentSeq,
|
|
27714
|
+
remindAfterMs: record5.remindAfterMs
|
|
26534
27715
|
};
|
|
26535
27716
|
}
|
|
26536
27717
|
async function handleLocalMessageReminder(req, res, agentId, onArm) {
|
|
@@ -26779,7 +27960,7 @@ function joinPath(basePath, reqUrl) {
|
|
|
26779
27960
|
return base + reqPath || "/";
|
|
26780
27961
|
}
|
|
26781
27962
|
// src/daemon/createDaemon.ts
|
|
26782
|
-
import { homedir as
|
|
27963
|
+
import { homedir as homedir5 } from "os";
|
|
26783
27964
|
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync7 } from "node:fs";
|
|
26784
27965
|
|
|
26785
27966
|
// src/util/rotatingFileSink.ts
|
|
@@ -27054,7 +28235,7 @@ var MAX_PROFILE_ABOUT_LENGTH = 1000;
|
|
|
27054
28235
|
var MAX_MESSAGE_CONTENT_LENGTH = 4000;
|
|
27055
28236
|
var MAX_ATTACHMENTS_PER_MESSAGE = 10;
|
|
27056
28237
|
var MAX_ATTACHMENT_SIZE_BYTES = 25 * 1024 * 1024;
|
|
27057
|
-
var MAX_ATTACHMENT_THUMBNAIL_SIZE_BYTES =
|
|
28238
|
+
var MAX_ATTACHMENT_THUMBNAIL_SIZE_BYTES = 512 * 1024;
|
|
27058
28239
|
var MAX_SERVER_ICON_SIZE_BYTES = 5 * 1024 * 1024;
|
|
27059
28240
|
var ALLOWED_ICON_MIME_TYPES = [
|
|
27060
28241
|
"image/png",
|
|
@@ -27063,6 +28244,92 @@ var ALLOWED_ICON_MIME_TYPES = [
|
|
|
27063
28244
|
"image/gif"
|
|
27064
28245
|
];
|
|
27065
28246
|
var MAX_ICON_SOURCE_FILE_SIZE_BYTES = 15 * 1024 * 1024;
|
|
28247
|
+
// ../shared/src/provider-telemetry.ts
|
|
28248
|
+
var safeToken = exports_external.number().int().min(0).max(Number.MAX_SAFE_INTEGER);
|
|
28249
|
+
var boundedText = exports_external.string().min(1).refine((value) => new TextEncoder().encode(value).length <= 64, { message: "must be at most 64 UTF-8 bytes" });
|
|
28250
|
+
var DailyUsageMetricSchema = safeToken.nullable();
|
|
28251
|
+
var DailyUsageSnapshotSchema = exports_external.object({
|
|
28252
|
+
botId: exports_external.string().min(1),
|
|
28253
|
+
day: exports_external.string().regex(/^\d{4}-\d{2}-\d{2}$/),
|
|
28254
|
+
metrics: exports_external.object({
|
|
28255
|
+
input: DailyUsageMetricSchema,
|
|
28256
|
+
output: DailyUsageMetricSchema,
|
|
28257
|
+
cache: DailyUsageMetricSchema
|
|
28258
|
+
}).strict()
|
|
28259
|
+
}).strict();
|
|
28260
|
+
var QuotaProductIdentitySchema = exports_external.discriminatedUnion("kind", [
|
|
28261
|
+
exports_external.object({ kind: exports_external.literal("reported"), id: boundedText, displayName: boundedText }).strict(),
|
|
28262
|
+
exports_external.object({ kind: exports_external.literal("unknown"), displayName: boundedText }).strict()
|
|
28263
|
+
]);
|
|
28264
|
+
var QuotaModelIdentitySchema = exports_external.discriminatedUnion("kind", [
|
|
28265
|
+
exports_external.object({ kind: exports_external.literal("reported"), id: boundedText }).strict(),
|
|
28266
|
+
exports_external.object({ kind: exports_external.literal("not_applicable") }).strict(),
|
|
28267
|
+
exports_external.object({ kind: exports_external.literal("unknown") }).strict()
|
|
28268
|
+
]);
|
|
28269
|
+
var QuotaWindowIdentitySchema = exports_external.discriminatedUnion("kind", [
|
|
28270
|
+
exports_external.object({
|
|
28271
|
+
kind: exports_external.literal("rolling"),
|
|
28272
|
+
durationSeconds: exports_external.number().int().positive().max(Number.MAX_SAFE_INTEGER),
|
|
28273
|
+
displayName: boundedText
|
|
28274
|
+
}).strict(),
|
|
28275
|
+
exports_external.object({
|
|
28276
|
+
kind: exports_external.literal("calendar"),
|
|
28277
|
+
period: exports_external.enum(["day", "week", "month"]),
|
|
28278
|
+
displayName: boundedText
|
|
28279
|
+
}).strict(),
|
|
28280
|
+
exports_external.object({
|
|
28281
|
+
kind: exports_external.literal("provider_defined"),
|
|
28282
|
+
id: boundedText,
|
|
28283
|
+
durationSeconds: exports_external.number().int().positive().max(Number.MAX_SAFE_INTEGER).optional(),
|
|
28284
|
+
displayName: boundedText
|
|
28285
|
+
}).strict()
|
|
28286
|
+
]);
|
|
28287
|
+
var QuotaLimitSchema = exports_external.object({
|
|
28288
|
+
bucket: exports_external.object({
|
|
28289
|
+
limitId: boundedText,
|
|
28290
|
+
product: QuotaProductIdentitySchema,
|
|
28291
|
+
model: QuotaModelIdentitySchema,
|
|
28292
|
+
window: QuotaWindowIdentitySchema
|
|
28293
|
+
}).strict(),
|
|
28294
|
+
usedPercent: exports_external.number().finite().min(0).max(100),
|
|
28295
|
+
resetsAt: exports_external.string().datetime({ offset: true }).optional()
|
|
28296
|
+
}).strict();
|
|
28297
|
+
function quotaIdentity(limit) {
|
|
28298
|
+
const { product, model, window: window2, limitId } = limit.bucket;
|
|
28299
|
+
const productKey = product.kind === "reported" ? `reported:${product.id}` : "unknown";
|
|
28300
|
+
const modelKey = model.kind === "reported" ? `reported:${model.id}` : model.kind;
|
|
28301
|
+
const windowKey = window2.kind === "rolling" ? `rolling:${window2.durationSeconds}` : window2.kind === "calendar" ? `calendar:${window2.period}` : `provider_defined:${window2.id}:${window2.durationSeconds === undefined ? "absent" : window2.durationSeconds}`;
|
|
28302
|
+
return `${productKey}\x00${modelKey}\x00${windowKey}\x00${limitId}`;
|
|
28303
|
+
}
|
|
28304
|
+
var AvailableQuotaObservationSchema = exports_external.object({
|
|
28305
|
+
status: exports_external.literal("available"),
|
|
28306
|
+
sourceEpoch: exports_external.string().regex(/^[A-Za-z0-9_-]{22}$/),
|
|
28307
|
+
planName: boundedText.optional(),
|
|
28308
|
+
freshForSeconds: exports_external.number().int().positive().max(86400),
|
|
28309
|
+
limits: exports_external.array(QuotaLimitSchema).min(1).max(8)
|
|
28310
|
+
}).strict().superRefine((value, ctx) => {
|
|
28311
|
+
const identities = new Set;
|
|
28312
|
+
for (const [index2, limit] of value.limits.entries()) {
|
|
28313
|
+
const identity = quotaIdentity(limit);
|
|
28314
|
+
if (identities.has(identity)) {
|
|
28315
|
+
ctx.addIssue({ code: "custom", message: "duplicate quota bucket identity", path: ["limits", index2] });
|
|
28316
|
+
}
|
|
28317
|
+
identities.add(identity);
|
|
28318
|
+
}
|
|
28319
|
+
});
|
|
28320
|
+
var ProviderQuotaObservationSchema = exports_external.union([
|
|
28321
|
+
AvailableQuotaObservationSchema,
|
|
28322
|
+
exports_external.object({
|
|
28323
|
+
status: exports_external.literal("error"),
|
|
28324
|
+
sourceEpoch: exports_external.string().regex(/^[A-Za-z0-9_-]{22}$/),
|
|
28325
|
+
code: exports_external.enum(["unavailable", "unauthorized", "network", "provider_error", "invalid_response"]),
|
|
28326
|
+
retryable: exports_external.boolean()
|
|
28327
|
+
}).strict()
|
|
28328
|
+
]);
|
|
28329
|
+
var ProviderQuotaSnapshotSchema = exports_external.object({
|
|
28330
|
+
agentBackendId: exports_external.enum(["claude", "codex"]),
|
|
28331
|
+
observation: ProviderQuotaObservationSchema
|
|
28332
|
+
}).strict();
|
|
27066
28333
|
// ../shared/src/utils/slug.ts
|
|
27067
28334
|
init_nanoid();
|
|
27068
28335
|
var slugId = customAlphabet("0123456789abcdefghijklmnopqrstuvwxyz");
|
|
@@ -27577,12 +28844,61 @@ var CreateThreadRequestSchema = exports_external.object({
|
|
|
27577
28844
|
attachment_ids: exports_external.array(exports_external.string()).optional()
|
|
27578
28845
|
});
|
|
27579
28846
|
var RUNTIME_ID_RE = /^[A-Za-z0-9._@/-]+$/;
|
|
28847
|
+
var REASONING_EFFORT_RE = /^[A-Za-z0-9._-]+$/;
|
|
28848
|
+
var COMMUNITY_REASONING_EFFORT_MAX = 32;
|
|
28849
|
+
var COMMUNITY_REASONING_DESCRIPTION_MAX = 256;
|
|
28850
|
+
var COMMUNITY_REASONING_OPTIONS_MAX = 16;
|
|
28851
|
+
var COMMUNITY_REASONING_MODELS_MAX = 512;
|
|
28852
|
+
var ReasoningEffortSchema = exports_external.string().min(1).max(COMMUNITY_REASONING_EFFORT_MAX).regex(REASONING_EFFORT_RE, "invalid reasoning effort charset");
|
|
28853
|
+
var RuntimeReasoningOptionSchema = exports_external.object({
|
|
28854
|
+
value: ReasoningEffortSchema,
|
|
28855
|
+
description: exports_external.string().max(COMMUNITY_REASONING_DESCRIPTION_MAX).optional()
|
|
28856
|
+
});
|
|
28857
|
+
var RuntimeReasoningModelSchema = exports_external.object({
|
|
28858
|
+
id: exports_external.string().min(1).max(100),
|
|
28859
|
+
displayName: exports_external.string().min(1).max(COMMUNITY_REASONING_DESCRIPTION_MAX).optional().catch(undefined),
|
|
28860
|
+
supportedReasoningEfforts: exports_external.array(exports_external.unknown()).max(COMMUNITY_REASONING_OPTIONS_MAX).transform((options) => {
|
|
28861
|
+
const seen = new Set;
|
|
28862
|
+
return options.flatMap((candidate) => {
|
|
28863
|
+
const parsed = RuntimeReasoningOptionSchema.safeParse(candidate);
|
|
28864
|
+
if (!parsed.success)
|
|
28865
|
+
return [];
|
|
28866
|
+
const option = parsed.data;
|
|
28867
|
+
if (seen.has(option.value))
|
|
28868
|
+
return [];
|
|
28869
|
+
seen.add(option.value);
|
|
28870
|
+
return [option];
|
|
28871
|
+
});
|
|
28872
|
+
}),
|
|
28873
|
+
defaultReasoningEffort: ReasoningEffortSchema.optional().catch(undefined)
|
|
28874
|
+
}).transform((model) => {
|
|
28875
|
+
const { defaultReasoningEffort, ...rest } = model;
|
|
28876
|
+
return defaultReasoningEffort !== undefined && model.supportedReasoningEfforts.some((option) => option.value === defaultReasoningEffort) ? { ...rest, defaultReasoningEffort } : rest;
|
|
28877
|
+
});
|
|
28878
|
+
var RuntimeReasoningCatalogSchema = exports_external.object({
|
|
28879
|
+
updateMode: exports_external.enum(["live_next_turn", "context_preserving_restart", "unsupported"]),
|
|
28880
|
+
defaultModelId: exports_external.string().min(1).max(100).optional().catch(undefined),
|
|
28881
|
+
models: exports_external.array(exports_external.unknown()).max(COMMUNITY_REASONING_MODELS_MAX).transform((models) => {
|
|
28882
|
+
const seen = new Set;
|
|
28883
|
+
return models.flatMap((candidate) => {
|
|
28884
|
+
const parsed = RuntimeReasoningModelSchema.safeParse(candidate);
|
|
28885
|
+
if (!parsed.success)
|
|
28886
|
+
return [];
|
|
28887
|
+
const model = parsed.data;
|
|
28888
|
+
if (seen.has(model.id))
|
|
28889
|
+
return [];
|
|
28890
|
+
seen.add(model.id);
|
|
28891
|
+
return [model];
|
|
28892
|
+
});
|
|
28893
|
+
})
|
|
28894
|
+
});
|
|
27580
28895
|
var CommunityMachineRuntimeSchema = exports_external.object({
|
|
27581
28896
|
id: exports_external.string().min(1).max(COMMUNITY_RUNTIME_ID_MAX).regex(RUNTIME_ID_RE, "invalid runtime id charset"),
|
|
27582
28897
|
version: exports_external.string().max(COMMUNITY_RUNTIME_VERSION_MAX).optional(),
|
|
27583
28898
|
status: exports_external.enum(["healthy", "unhealthy"]).catch("healthy").default("healthy"),
|
|
27584
28899
|
lastError: exports_external.string().max(128).optional(),
|
|
27585
|
-
lastErrorAt: exports_external.string().optional()
|
|
28900
|
+
lastErrorAt: exports_external.string().optional(),
|
|
28901
|
+
reasoning: RuntimeReasoningCatalogSchema.optional().catch(undefined)
|
|
27586
28902
|
});
|
|
27587
28903
|
var CommunityMachineRuntimeListSchema = exports_external.array(CommunityMachineRuntimeSchema).max(COMMUNITY_RUNTIME_LIST_MAX).transform((list) => {
|
|
27588
28904
|
const seen = new Set;
|
|
@@ -27634,7 +28950,8 @@ var HostReadyMessageSchema = exports_external.object({
|
|
|
27634
28950
|
platform: exports_external.string().optional(),
|
|
27635
28951
|
arch: exports_external.string().optional(),
|
|
27636
28952
|
osRelease: exports_external.string().optional(),
|
|
27637
|
-
daemonVersion: exports_external.string().optional()
|
|
28953
|
+
daemonVersion: exports_external.string().optional(),
|
|
28954
|
+
providerQuotas: exports_external.array(ProviderQuotaSnapshotSchema).max(2).optional().default([])
|
|
27638
28955
|
});
|
|
27639
28956
|
var CommunityDaemonReadySchema = exports_external.object({
|
|
27640
28957
|
runtimeReport: CommunityMachineRuntimeListSchema.optional(),
|
|
@@ -27655,7 +28972,9 @@ var SessionErrorFrameSchema = exports_external.object({
|
|
|
27655
28972
|
var AgentActivityMessageSchema = exports_external.object({
|
|
27656
28973
|
type: exports_external.literal("agent_activity"),
|
|
27657
28974
|
agentId: exports_external.string(),
|
|
27658
|
-
state: exports_external.enum(["idle", "starting", "running", "stopping"])
|
|
28975
|
+
state: exports_external.enum(["idle", "starting", "running", "stopping"]),
|
|
28976
|
+
dailyUsage: exports_external.array(DailyUsageSnapshotSchema).max(7).optional(),
|
|
28977
|
+
quota: ProviderQuotaSnapshotSchema.optional()
|
|
27659
28978
|
});
|
|
27660
28979
|
var AgentTypingMessageSchema = exports_external.object({
|
|
27661
28980
|
type: exports_external.literal("agent_typing"),
|
|
@@ -27730,15 +29049,17 @@ var CommunityBotCreateRequestSchema = exports_external.object({
|
|
|
27730
29049
|
machineId: exports_external.string().min(1),
|
|
27731
29050
|
runtime: exports_external.string().min(1),
|
|
27732
29051
|
image: BotImageUrlSchema.optional(),
|
|
27733
|
-
model: exports_external.string().trim().min(1).max(100).nullable().optional()
|
|
29052
|
+
model: exports_external.string().trim().min(1).max(100).nullable().optional(),
|
|
29053
|
+
reasoningEffort: ReasoningEffortSchema.nullable().optional()
|
|
27734
29054
|
});
|
|
27735
29055
|
var CommunityBotPatchRequestSchema = exports_external.object({
|
|
27736
29056
|
name: exports_external.string().trim().min(COMMUNITY_BOT_NAME_MIN).max(COMMUNITY_BOT_NAME_MAX).refine(isMentionSafeName, { message: MENTION_SAFE_NAME_MSG }).optional(),
|
|
27737
29057
|
description: exports_external.string().max(COMMUNITY_BOT_DESCRIPTION_MAX).optional(),
|
|
27738
29058
|
image: BotImageUrlSchema.nullable().optional(),
|
|
27739
29059
|
model: exports_external.string().trim().min(1).max(100).nullable().optional(),
|
|
27740
|
-
runtime: exports_external.string().trim().min(1).max(COMMUNITY_RUNTIME_ID_MAX).optional()
|
|
27741
|
-
|
|
29060
|
+
runtime: exports_external.string().trim().min(1).max(COMMUNITY_RUNTIME_ID_MAX).optional(),
|
|
29061
|
+
reasoningEffort: ReasoningEffortSchema.nullable().optional()
|
|
29062
|
+
}).refine((v) => v.name !== undefined || v.description !== undefined || v.image !== undefined || v.runtime !== undefined || ("reasoningEffort" in v) || ("model" in v), {
|
|
27742
29063
|
message: "at least one field must be provided"
|
|
27743
29064
|
});
|
|
27744
29065
|
var CommunityBotAddToServerRequestSchema = exports_external.object({
|
|
@@ -27940,6 +29261,7 @@ __export(exports_community_schema, {
|
|
|
27940
29261
|
communityChannelMember: () => communityChannelMember,
|
|
27941
29262
|
communityChannel: () => communityChannel,
|
|
27942
29263
|
communityCategory: () => communityCategory,
|
|
29264
|
+
communityBotDailyTokenUsage: () => communityBotDailyTokenUsage,
|
|
27943
29265
|
communityBotDailyActivity: () => communityBotDailyActivity,
|
|
27944
29266
|
communityBotApprovalRequest: () => communityBotApprovalRequest,
|
|
27945
29267
|
communityBotActivityEvent: () => communityBotActivityEvent,
|
|
@@ -28192,6 +29514,17 @@ var communityBotDailyActivity = sqliteTable("community_bot_daily_activity", {
|
|
|
28192
29514
|
handledCount: integer2("handled_count").notNull().default(0),
|
|
28193
29515
|
sentCount: integer2("sent_count").notNull().default(0)
|
|
28194
29516
|
}, (t) => [primaryKey({ columns: [t.botId, t.day] })]);
|
|
29517
|
+
var communityBotDailyTokenUsage = sqliteTable("community_bot_daily_token_usage", {
|
|
29518
|
+
botId: text("bot_id").notNull().references(() => user.id, { onDelete: "cascade" }),
|
|
29519
|
+
day: text("day").notNull(),
|
|
29520
|
+
inputTokens: integer2("input_tokens"),
|
|
29521
|
+
outputTokens: integer2("output_tokens"),
|
|
29522
|
+
cacheTokens: integer2("cache_tokens"),
|
|
29523
|
+
updatedAt: text("updated_at").notNull()
|
|
29524
|
+
}, (t) => [
|
|
29525
|
+
primaryKey({ columns: [t.botId, t.day] }),
|
|
29526
|
+
index("idx_community_bot_daily_token_usage_day").on(t.day)
|
|
29527
|
+
]);
|
|
28195
29528
|
var communityMessageMark = sqliteTable("community_message_mark", {
|
|
28196
29529
|
id: text("id").primaryKey().$defaultFn(() => nanoid3()),
|
|
28197
29530
|
userId: text("user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
|
|
@@ -28316,7 +29649,8 @@ var listedMessageProjection = {
|
|
|
28316
29649
|
clientNonce: communityMessage.clientNonce,
|
|
28317
29650
|
authorName: user.name,
|
|
28318
29651
|
authorEmail: user.email,
|
|
28319
|
-
authorImage: user.image
|
|
29652
|
+
authorImage: user.image,
|
|
29653
|
+
authorAvatarVersion: user.avatarVersion
|
|
28320
29654
|
};
|
|
28321
29655
|
|
|
28322
29656
|
// ../shared/src/db/queries/user.ts
|
|
@@ -28326,6 +29660,7 @@ var publicUserColumns = {
|
|
|
28326
29660
|
email: user.email,
|
|
28327
29661
|
emailVerified: user.emailVerified,
|
|
28328
29662
|
image: user.image,
|
|
29663
|
+
avatarVersion: user.avatarVersion,
|
|
28329
29664
|
createdAt: user.createdAt,
|
|
28330
29665
|
updatedAt: user.updatedAt,
|
|
28331
29666
|
discriminator: user.discriminator
|
|
@@ -28336,6 +29671,12 @@ var internalUserColumns = {
|
|
|
28336
29671
|
ownerUserId: user.ownerUserId,
|
|
28337
29672
|
deletedAt: user.deletedAt
|
|
28338
29673
|
};
|
|
29674
|
+
var avatarPublishColumns = {
|
|
29675
|
+
id: user.id,
|
|
29676
|
+
image: user.image,
|
|
29677
|
+
avatarVersion: user.avatarVersion,
|
|
29678
|
+
avatarObjectKey: user.avatarObjectKey
|
|
29679
|
+
};
|
|
28339
29680
|
|
|
28340
29681
|
// ../shared/src/db/queries/community/channel.ts
|
|
28341
29682
|
var CHANNEL_COLUMNS = {
|
|
@@ -28388,7 +29729,8 @@ var friendApprovalProfileSchema = exports_external.strictObject({
|
|
|
28388
29729
|
id: string4,
|
|
28389
29730
|
name: string4,
|
|
28390
29731
|
discriminator: string4,
|
|
28391
|
-
image: nullableString
|
|
29732
|
+
image: nullableString,
|
|
29733
|
+
avatarVersion: exports_external.number().int().nonnegative()
|
|
28392
29734
|
});
|
|
28393
29735
|
var FriendApprovalPayloadSchema = exports_external.strictObject({
|
|
28394
29736
|
friendshipId: string4,
|
|
@@ -28414,6 +29756,7 @@ var messageSchema = exports_external.strictObject({
|
|
|
28414
29756
|
authorId: string4,
|
|
28415
29757
|
authorName: string4,
|
|
28416
29758
|
authorAvatar: string4.optional(),
|
|
29759
|
+
authorAvatarVersion: exports_external.number().int().nonnegative(),
|
|
28417
29760
|
content: string4,
|
|
28418
29761
|
type: exports_external.enum(["chat", "system"]),
|
|
28419
29762
|
systemKind: exports_external.literal("thread").optional(),
|
|
@@ -28421,6 +29764,7 @@ var messageSchema = exports_external.strictObject({
|
|
|
28421
29764
|
replyToId: nullableString.optional(),
|
|
28422
29765
|
replyTo: exports_external.strictObject({
|
|
28423
29766
|
id: string4,
|
|
29767
|
+
authorId: string4.optional(),
|
|
28424
29768
|
authorName: string4,
|
|
28425
29769
|
text: string4,
|
|
28426
29770
|
deleted: exports_external.boolean().optional()
|
|
@@ -28615,6 +29959,7 @@ var communityMemberJoinSchema = exports_external.strictObject({
|
|
|
28615
29959
|
name: string4,
|
|
28616
29960
|
discriminator: string4,
|
|
28617
29961
|
avatar: string4.optional(),
|
|
29962
|
+
avatarVersion: exports_external.number().int().nonnegative(),
|
|
28618
29963
|
role: string4,
|
|
28619
29964
|
joinedAt: string4
|
|
28620
29965
|
})
|
|
@@ -28717,6 +30062,22 @@ var communityStatusUpdateSchema = exports_external.strictObject({
|
|
|
28717
30062
|
statusEmoji: nullableString,
|
|
28718
30063
|
statusText: nullableString
|
|
28719
30064
|
});
|
|
30065
|
+
var communityIdentityUpdateSchema = exports_external.strictObject({
|
|
30066
|
+
type: exports_external.literal("community:identity.update"),
|
|
30067
|
+
userId: string4,
|
|
30068
|
+
avatar: string4,
|
|
30069
|
+
avatarVersion: exports_external.number().int().positive()
|
|
30070
|
+
});
|
|
30071
|
+
var communityProfileUpdateSchema = exports_external.strictObject({
|
|
30072
|
+
type: exports_external.literal("community:profile.update"),
|
|
30073
|
+
userId: string4,
|
|
30074
|
+
name: string4,
|
|
30075
|
+
discriminator: string4,
|
|
30076
|
+
aboutMe: string4,
|
|
30077
|
+
bannerColor: nullableString,
|
|
30078
|
+
kind: exports_external.enum(["human", "bot"]),
|
|
30079
|
+
ownerUserId: nullableString
|
|
30080
|
+
});
|
|
28720
30081
|
var machineRuntimeSchema = CommunityMachineRuntimeSchema.strict();
|
|
28721
30082
|
var CommunityMachineSummarySchema2 = exports_external.strictObject({
|
|
28722
30083
|
id: string4,
|
|
@@ -28805,6 +30166,8 @@ var CommunityWsEventDiscriminatedSchema = exports_external.discriminatedUnion("t
|
|
|
28805
30166
|
communityInboxChangedSchema,
|
|
28806
30167
|
communityPresenceUpdateSchema,
|
|
28807
30168
|
communityStatusUpdateSchema,
|
|
30169
|
+
communityIdentityUpdateSchema,
|
|
30170
|
+
communityProfileUpdateSchema,
|
|
28808
30171
|
communityMachineCreatedSchema,
|
|
28809
30172
|
communityMachineStatusSchema,
|
|
28810
30173
|
communityMachineUpdatedSchema,
|
|
@@ -28851,6 +30214,8 @@ var WS_EVENTS = {
|
|
|
28851
30214
|
INBOX_CHANGED: "community:inbox.changed",
|
|
28852
30215
|
PRESENCE_UPDATE: "community:presence.update",
|
|
28853
30216
|
STATUS_UPDATE: "community:status.update",
|
|
30217
|
+
IDENTITY_UPDATE: "community:identity.update",
|
|
30218
|
+
PROFILE_UPDATE: "community:profile.update",
|
|
28854
30219
|
MACHINE_CREATED: "community:machine.created",
|
|
28855
30220
|
MACHINE_STATUS: "community:machine.status",
|
|
28856
30221
|
MACHINE_UPDATED: "community:machine.updated",
|
|
@@ -29411,23 +30776,43 @@ class WsControlChannel {
|
|
|
29411
30776
|
this.ws.send(JSON.stringify(frame));
|
|
29412
30777
|
}
|
|
29413
30778
|
resyncOnConnect() {
|
|
30779
|
+
const sendActivities = (activities, counts) => {
|
|
30780
|
+
for (const activity of activities) {
|
|
30781
|
+
this.sendFrame({ type: "agent_activity", ...activity });
|
|
30782
|
+
}
|
|
30783
|
+
this.log.info("resync sent", {
|
|
30784
|
+
ready: counts.ready,
|
|
30785
|
+
sessions: counts.sessions,
|
|
30786
|
+
activities: activities.length,
|
|
30787
|
+
pendingAuditEvents: this.pendingBotAuditEvents.size
|
|
30788
|
+
});
|
|
30789
|
+
};
|
|
29414
30790
|
if (this.resyncProvider) {
|
|
29415
|
-
const
|
|
29416
|
-
this.
|
|
29417
|
-
|
|
29418
|
-
|
|
29419
|
-
|
|
29420
|
-
|
|
29421
|
-
this.sendFrame({ type: "agent_activity", ...a });
|
|
30791
|
+
const socketAtStart = this.ws;
|
|
30792
|
+
const snapshot = this.resyncProvider();
|
|
30793
|
+
this.sendFrame({ type: "ready", ...snapshot.ready });
|
|
30794
|
+
for (const session2 of snapshot.sessions) {
|
|
30795
|
+
this.sendFrame({ type: "agent_session", ...session2 });
|
|
30796
|
+
}
|
|
29422
30797
|
for (const frame of this.pendingBotAuditEvents.values())
|
|
29423
30798
|
this.sendFrame(frame);
|
|
29424
30799
|
this.scheduleAuditRetry();
|
|
29425
|
-
|
|
29426
|
-
|
|
29427
|
-
|
|
29428
|
-
|
|
29429
|
-
|
|
29430
|
-
|
|
30800
|
+
const activities = snapshot.activities ?? [];
|
|
30801
|
+
const counts = {
|
|
30802
|
+
ready: snapshot.ready.runtimeReport.length,
|
|
30803
|
+
sessions: snapshot.sessions.length
|
|
30804
|
+
};
|
|
30805
|
+
if (activities instanceof Promise) {
|
|
30806
|
+
activities.then((resolved) => {
|
|
30807
|
+
if (this.ws === socketAtStart && this.statusValue === "open") {
|
|
30808
|
+
sendActivities(resolved, counts);
|
|
30809
|
+
}
|
|
30810
|
+
}).catch((err) => {
|
|
30811
|
+
this.log.warn("resync provider failed", { err: describeErr(err) });
|
|
30812
|
+
});
|
|
30813
|
+
} else {
|
|
30814
|
+
sendActivities(activities, counts);
|
|
30815
|
+
}
|
|
29431
30816
|
}
|
|
29432
30817
|
for (const hook of this.resyncHooks) {
|
|
29433
30818
|
try {
|
|
@@ -29645,8 +31030,8 @@ class WsControlChannel {
|
|
|
29645
31030
|
}
|
|
29646
31031
|
// src/timeline/timeline.ts
|
|
29647
31032
|
import * as fs7 from "node:fs";
|
|
29648
|
-
import { createHash as createHash4, randomBytes as
|
|
29649
|
-
import { basename, dirname as dirname3, join as
|
|
31033
|
+
import { createHash as createHash4, randomBytes as randomBytes6 } from "node:crypto";
|
|
31034
|
+
import { basename, dirname as dirname3, join as join11 } from "node:path";
|
|
29650
31035
|
|
|
29651
31036
|
// src/timeline/filelock.ts
|
|
29652
31037
|
import * as fs6 from "fs";
|
|
@@ -29989,7 +31374,7 @@ function scanTimelineFile(filePath) {
|
|
|
29989
31374
|
}
|
|
29990
31375
|
}
|
|
29991
31376
|
function atomicReplaceTimeline(filePath, lines) {
|
|
29992
|
-
const tempPath =
|
|
31377
|
+
const tempPath = join11(dirname3(filePath), `.${basename(filePath)}.${process.pid}.${randomBytes6(12).toString("hex")}.tmp`);
|
|
29993
31378
|
let fd = null;
|
|
29994
31379
|
try {
|
|
29995
31380
|
fd = fs7.openSync(tempPath, "wx", 384);
|
|
@@ -30073,14 +31458,14 @@ function readRecentEntries(timelineDir, opts = {}) {
|
|
|
30073
31458
|
const filenames = recentFilenames(maxDays, now).reverse();
|
|
30074
31459
|
const entries = [];
|
|
30075
31460
|
for (const filename of filenames) {
|
|
30076
|
-
entries.push(...readJsonl(
|
|
31461
|
+
entries.push(...readJsonl(join11(timelineDir, filename)));
|
|
30077
31462
|
}
|
|
30078
31463
|
return entries;
|
|
30079
31464
|
}
|
|
30080
31465
|
function readResumeControlState(timelineDir) {
|
|
30081
31466
|
if (timelineDirectoryState(timelineDir) !== "safe")
|
|
30082
31467
|
return { kind: "missing" };
|
|
30083
|
-
const filePath =
|
|
31468
|
+
const filePath = join11(timelineDir, RESUME_CONTROL_FILENAME);
|
|
30084
31469
|
let source;
|
|
30085
31470
|
try {
|
|
30086
31471
|
source = fs7.lstatSync(filePath);
|
|
@@ -30156,8 +31541,8 @@ function updateResumeControlState(timelineDir, update) {
|
|
|
30156
31541
|
`;
|
|
30157
31542
|
if (Buffer.byteLength(body, "utf8") > RESUME_CONTROL_MAX_BYTES)
|
|
30158
31543
|
return false;
|
|
30159
|
-
const filePath =
|
|
30160
|
-
const tempPath =
|
|
31544
|
+
const filePath = join11(timelineDir, RESUME_CONTROL_FILENAME);
|
|
31545
|
+
const tempPath = join11(timelineDir, `.${RESUME_CONTROL_FILENAME}.${process.pid}.${randomBytes6(12).toString("hex")}.tmp`);
|
|
30161
31546
|
let fd = null;
|
|
30162
31547
|
try {
|
|
30163
31548
|
fd = fs7.openSync(tempPath, "wx", 384);
|
|
@@ -30187,7 +31572,7 @@ function appendTrackedEntry(timelineDir, entry, now = new Date) {
|
|
|
30187
31572
|
if (timelineDirectoryState(timelineDir) !== "safe")
|
|
30188
31573
|
return { status: "rejected", reason: "unsafe" };
|
|
30189
31574
|
const filename = filenameForDate(now);
|
|
30190
|
-
const filePath =
|
|
31575
|
+
const filePath = join11(timelineDir, filename);
|
|
30191
31576
|
const lockPath = lockPathFor(timelineDir, filename);
|
|
30192
31577
|
try {
|
|
30193
31578
|
if (!acquireLock(lockPath))
|
|
@@ -30215,7 +31600,7 @@ function updateTrackedEntry(timelineDir, handle, update) {
|
|
|
30215
31600
|
if (!DATE_FILENAME_PATTERN.test(handle.filename) || basename(handle.filename) !== handle.filename) {
|
|
30216
31601
|
return { status: "rejected", reason: "unsafe" };
|
|
30217
31602
|
}
|
|
30218
|
-
const filePath =
|
|
31603
|
+
const filePath = join11(timelineDir, handle.filename);
|
|
30219
31604
|
const lockPath = lockPathFor(timelineDir, handle.filename);
|
|
30220
31605
|
try {
|
|
30221
31606
|
if (!acquireLock(lockPath))
|
|
@@ -30274,10 +31659,10 @@ async function sweepTimelineHistory(workingDirectoryBase, opts = {}) {
|
|
|
30274
31659
|
return;
|
|
30275
31660
|
}
|
|
30276
31661
|
for (const agentName of agentNames) {
|
|
30277
|
-
const agentDir =
|
|
31662
|
+
const agentDir = join11(workingDirectoryBase, agentName);
|
|
30278
31663
|
if (!isRealDirectory(agentDir))
|
|
30279
31664
|
continue;
|
|
30280
|
-
const timelineDir =
|
|
31665
|
+
const timelineDir = join11(agentDir, ".context_timeline");
|
|
30281
31666
|
if (!isRealDirectory(timelineDir))
|
|
30282
31667
|
continue;
|
|
30283
31668
|
let filenames;
|
|
@@ -30287,7 +31672,7 @@ async function sweepTimelineHistory(workingDirectoryBase, opts = {}) {
|
|
|
30287
31672
|
continue;
|
|
30288
31673
|
}
|
|
30289
31674
|
for (const filename of filenames) {
|
|
30290
|
-
const filePath =
|
|
31675
|
+
const filePath = join11(timelineDir, filename);
|
|
30291
31676
|
let source;
|
|
30292
31677
|
try {
|
|
30293
31678
|
source = fs7.lstatSync(filePath);
|
|
@@ -30936,7 +32321,12 @@ async function detectRuntimes() {
|
|
|
30936
32321
|
const driver = getDriver(id);
|
|
30937
32322
|
const probe = await driver.probe();
|
|
30938
32323
|
if (probe.status === "healthy") {
|
|
30939
|
-
results.push({
|
|
32324
|
+
results.push({
|
|
32325
|
+
id,
|
|
32326
|
+
status: "healthy",
|
|
32327
|
+
version: probe.version,
|
|
32328
|
+
reasoning: probe.reasoning
|
|
32329
|
+
});
|
|
30940
32330
|
} else {
|
|
30941
32331
|
results.push({
|
|
30942
32332
|
id,
|
|
@@ -31041,15 +32431,15 @@ class MessageReminderScheduler {
|
|
|
31041
32431
|
const startedAt = this.now();
|
|
31042
32432
|
const dueAt = startedAt + input.remindAfterMs;
|
|
31043
32433
|
const sentRef = `${input.channel}#${input.sentSeq}`;
|
|
31044
|
-
const
|
|
32434
|
+
const record5 = {
|
|
31045
32435
|
...input,
|
|
31046
32436
|
sentRef,
|
|
31047
32437
|
startedAt,
|
|
31048
32438
|
dueAt,
|
|
31049
32439
|
timer: undefined
|
|
31050
32440
|
};
|
|
31051
|
-
|
|
31052
|
-
if (this.reminders.get(key) !==
|
|
32441
|
+
record5.timer = this.setTimer(() => {
|
|
32442
|
+
if (this.reminders.get(key) !== record5)
|
|
31053
32443
|
return;
|
|
31054
32444
|
this.reminders.delete(key);
|
|
31055
32445
|
try {
|
|
@@ -31060,8 +32450,8 @@ class MessageReminderScheduler {
|
|
|
31060
32450
|
Promise.resolve(delivery).catch(() => {});
|
|
31061
32451
|
} catch {}
|
|
31062
32452
|
}, input.remindAfterMs);
|
|
31063
|
-
|
|
31064
|
-
this.reminders.set(key,
|
|
32453
|
+
record5.timer.unref?.();
|
|
32454
|
+
this.reminders.set(key, record5);
|
|
31065
32455
|
return { armed: true, dueAt };
|
|
31066
32456
|
}
|
|
31067
32457
|
observe(agentId, channel2, latestSeq) {
|
|
@@ -31101,8 +32491,8 @@ class MessageReminderScheduler {
|
|
|
31101
32491
|
|
|
31102
32492
|
// src/manager/agentDriverHost.ts
|
|
31103
32493
|
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
31104
|
-
import { homedir as
|
|
31105
|
-
import { join as
|
|
32494
|
+
import { homedir as homedir4 } from "node:os";
|
|
32495
|
+
import { join as join13 } from "node:path";
|
|
31106
32496
|
|
|
31107
32497
|
// src/drivers/gitIdentityEnv.ts
|
|
31108
32498
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
@@ -31227,7 +32617,7 @@ function createDaemonAgentDriverHost(ctx, onRawLine) {
|
|
|
31227
32617
|
hostUser: readHostGitIdentity() ?? undefined
|
|
31228
32618
|
}),
|
|
31229
32619
|
platformProtected: {
|
|
31230
|
-
ALOOK_HOME: process.env.ALOOK_HOME ??
|
|
32620
|
+
ALOOK_HOME: process.env.ALOOK_HOME ?? join13(homedir4(), ".alook"),
|
|
31231
32621
|
ALOOK_ID: ctx.agentId,
|
|
31232
32622
|
ALOOK_CLI: ctx.agentCliPath,
|
|
31233
32623
|
ALOOK_SERVER_URL: ctx.config.serverUrl,
|
|
@@ -31332,6 +32722,182 @@ class DaemonSelfSleepScheduler {
|
|
|
31332
32722
|
}
|
|
31333
32723
|
}
|
|
31334
32724
|
|
|
32725
|
+
// src/telemetry/dailyTokenUsage.ts
|
|
32726
|
+
import { chmod, mkdir, open, readFile as readFile2, rename, rm } from "node:fs/promises";
|
|
32727
|
+
import { dirname as dirname5, join as join14 } from "node:path";
|
|
32728
|
+
import { randomUUID as randomUUID7 } from "node:crypto";
|
|
32729
|
+
function dayKey(at) {
|
|
32730
|
+
return at.toISOString().slice(0, 10);
|
|
32731
|
+
}
|
|
32732
|
+
function retainedDays(at) {
|
|
32733
|
+
const days = new Set;
|
|
32734
|
+
for (let offset = 0;offset < 7; offset += 1) {
|
|
32735
|
+
days.add(dayKey(new Date(at.getTime() - offset * 86400000)));
|
|
32736
|
+
}
|
|
32737
|
+
return days;
|
|
32738
|
+
}
|
|
32739
|
+
function isMetric(value) {
|
|
32740
|
+
return value === null || typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
32741
|
+
}
|
|
32742
|
+
function isSnapshot(value) {
|
|
32743
|
+
if (!value || typeof value !== "object")
|
|
32744
|
+
return false;
|
|
32745
|
+
const snapshot = value;
|
|
32746
|
+
const metrics = snapshot.metrics;
|
|
32747
|
+
return typeof snapshot.botId === "string" && snapshot.botId.length > 0 && typeof snapshot.day === "string" && /^\d{4}-\d{2}-\d{2}$/.test(snapshot.day) && !!metrics && isMetric(metrics.input) && isMetric(metrics.output) && isMetric(metrics.cache);
|
|
32748
|
+
}
|
|
32749
|
+
function mergeMetric(existing, delta, hasExistingSnapshot) {
|
|
32750
|
+
if (delta === null)
|
|
32751
|
+
return null;
|
|
32752
|
+
if (!Number.isSafeInteger(delta) || delta < 0) {
|
|
32753
|
+
throw new RangeError("token usage delta must be a non-negative safe integer");
|
|
32754
|
+
}
|
|
32755
|
+
if (!hasExistingSnapshot)
|
|
32756
|
+
return delta;
|
|
32757
|
+
if (existing === null)
|
|
32758
|
+
return null;
|
|
32759
|
+
const sum = existing + delta;
|
|
32760
|
+
if (!Number.isSafeInteger(sum))
|
|
32761
|
+
throw new RangeError("daily token usage exceeds safe integer range");
|
|
32762
|
+
return sum;
|
|
32763
|
+
}
|
|
32764
|
+
function emptySnapshot(botId, day) {
|
|
32765
|
+
return {
|
|
32766
|
+
botId,
|
|
32767
|
+
day,
|
|
32768
|
+
metrics: {
|
|
32769
|
+
input: null,
|
|
32770
|
+
output: null,
|
|
32771
|
+
cache: null
|
|
32772
|
+
}
|
|
32773
|
+
};
|
|
32774
|
+
}
|
|
32775
|
+
|
|
32776
|
+
class DailyTokenUsageStore {
|
|
32777
|
+
now;
|
|
32778
|
+
tail = Promise.resolve();
|
|
32779
|
+
loaded = false;
|
|
32780
|
+
data = { version: 1, bots: {} };
|
|
32781
|
+
filePath;
|
|
32782
|
+
constructor(workingDirectoryBase, now = () => new Date) {
|
|
32783
|
+
this.now = now;
|
|
32784
|
+
this.filePath = join14(workingDirectoryBase, ".telemetry", "daily-token-usage.json");
|
|
32785
|
+
}
|
|
32786
|
+
record(botId, delta) {
|
|
32787
|
+
return this.enqueue(async () => {
|
|
32788
|
+
await this.load();
|
|
32789
|
+
const at = this.now();
|
|
32790
|
+
this.prune(at);
|
|
32791
|
+
const day = dayKey(at);
|
|
32792
|
+
const snapshots = this.data.bots[botId] ?? [];
|
|
32793
|
+
const existing = snapshots.find((snapshot) => snapshot.day === day);
|
|
32794
|
+
const next = existing ?? emptySnapshot(botId, day);
|
|
32795
|
+
next.metrics = {
|
|
32796
|
+
input: mergeMetric(next.metrics.input, delta.input, existing !== undefined),
|
|
32797
|
+
output: mergeMetric(next.metrics.output, delta.output, existing !== undefined),
|
|
32798
|
+
cache: mergeMetric(next.metrics.cache, delta.cache, existing !== undefined)
|
|
32799
|
+
};
|
|
32800
|
+
if (!existing)
|
|
32801
|
+
snapshots.push(next);
|
|
32802
|
+
snapshots.sort((a, b) => a.day.localeCompare(b.day));
|
|
32803
|
+
this.data.bots[botId] = snapshots;
|
|
32804
|
+
await this.persist();
|
|
32805
|
+
});
|
|
32806
|
+
}
|
|
32807
|
+
snapshots(botId) {
|
|
32808
|
+
let result = [];
|
|
32809
|
+
return this.enqueue(async () => {
|
|
32810
|
+
await this.load();
|
|
32811
|
+
if (this.prune(this.now()))
|
|
32812
|
+
await this.persist();
|
|
32813
|
+
result = (this.data.bots[botId] ?? []).map((snapshot) => structuredClone(snapshot));
|
|
32814
|
+
}).then(() => result);
|
|
32815
|
+
}
|
|
32816
|
+
enqueue(operation) {
|
|
32817
|
+
const result = this.tail.then(operation, operation);
|
|
32818
|
+
this.tail = result.then(() => {
|
|
32819
|
+
return;
|
|
32820
|
+
}, () => {
|
|
32821
|
+
return;
|
|
32822
|
+
});
|
|
32823
|
+
return result;
|
|
32824
|
+
}
|
|
32825
|
+
async load() {
|
|
32826
|
+
if (this.loaded)
|
|
32827
|
+
return;
|
|
32828
|
+
let source;
|
|
32829
|
+
try {
|
|
32830
|
+
source = await readFile2(this.filePath, "utf8");
|
|
32831
|
+
} catch (error51) {
|
|
32832
|
+
if (!error51 || typeof error51 !== "object" || !("code" in error51) || error51.code !== "ENOENT") {
|
|
32833
|
+
throw error51;
|
|
32834
|
+
}
|
|
32835
|
+
this.data = { version: 1, bots: {} };
|
|
32836
|
+
this.loaded = true;
|
|
32837
|
+
return;
|
|
32838
|
+
}
|
|
32839
|
+
const parsed = JSON.parse(source);
|
|
32840
|
+
if (!parsed || typeof parsed !== "object" || parsed.version !== 1) {
|
|
32841
|
+
throw new Error("invalid daily token usage file version");
|
|
32842
|
+
}
|
|
32843
|
+
const bots = parsed.bots;
|
|
32844
|
+
if (!bots || typeof bots !== "object" || Array.isArray(bots)) {
|
|
32845
|
+
throw new Error("invalid daily token usage bots map");
|
|
32846
|
+
}
|
|
32847
|
+
const valid = {};
|
|
32848
|
+
for (const [botId, value] of Object.entries(bots)) {
|
|
32849
|
+
if (!Array.isArray(value) || !value.every(isSnapshot) || value.some((snapshot) => snapshot.botId !== botId)) {
|
|
32850
|
+
throw new Error(`invalid daily token usage snapshots for bot ${botId}`);
|
|
32851
|
+
}
|
|
32852
|
+
if (value.length > 0) {
|
|
32853
|
+
valid[botId] = value;
|
|
32854
|
+
}
|
|
32855
|
+
}
|
|
32856
|
+
this.data = { version: 1, bots: valid };
|
|
32857
|
+
this.loaded = true;
|
|
32858
|
+
}
|
|
32859
|
+
prune(at) {
|
|
32860
|
+
const keep = retainedDays(at);
|
|
32861
|
+
let changed = false;
|
|
32862
|
+
for (const [botId, snapshots] of Object.entries(this.data.bots)) {
|
|
32863
|
+
const retained = snapshots.filter((snapshot) => keep.has(snapshot.day)).sort((a, b) => a.day.localeCompare(b.day)).slice(-7);
|
|
32864
|
+
if (retained.length !== snapshots.length || retained.some((snapshot, index2) => snapshot !== snapshots[index2]))
|
|
32865
|
+
changed = true;
|
|
32866
|
+
if (retained.length === 0)
|
|
32867
|
+
delete this.data.bots[botId];
|
|
32868
|
+
else
|
|
32869
|
+
this.data.bots[botId] = retained;
|
|
32870
|
+
}
|
|
32871
|
+
return changed;
|
|
32872
|
+
}
|
|
32873
|
+
async persist() {
|
|
32874
|
+
const directory = dirname5(this.filePath);
|
|
32875
|
+
await mkdir(directory, { recursive: true, mode: 448 });
|
|
32876
|
+
const temporary = `${this.filePath}.${randomUUID7()}.tmp`;
|
|
32877
|
+
try {
|
|
32878
|
+
const file2 = await open(temporary, "wx", 384);
|
|
32879
|
+
try {
|
|
32880
|
+
await file2.writeFile(JSON.stringify(this.data), { encoding: "utf8" });
|
|
32881
|
+
await file2.sync();
|
|
32882
|
+
} finally {
|
|
32883
|
+
await file2.close();
|
|
32884
|
+
}
|
|
32885
|
+
await rename(temporary, this.filePath);
|
|
32886
|
+
await chmod(this.filePath, 384);
|
|
32887
|
+
try {
|
|
32888
|
+
const directoryHandle = await open(directory, "r");
|
|
32889
|
+
try {
|
|
32890
|
+
await directoryHandle.sync();
|
|
32891
|
+
} finally {
|
|
32892
|
+
await directoryHandle.close();
|
|
32893
|
+
}
|
|
32894
|
+
} catch {}
|
|
32895
|
+
} catch (error51) {
|
|
32896
|
+
await rm(temporary, { force: true }).catch(() => {});
|
|
32897
|
+
throw error51;
|
|
32898
|
+
}
|
|
32899
|
+
}
|
|
32900
|
+
}
|
|
31335
32901
|
// src/daemon/createDaemon.ts
|
|
31336
32902
|
var WARMUP_BACKOFF_MS = [250, 500, 1000, 2000, 4000];
|
|
31337
32903
|
var WARMUP_CEILING_MS = 30000;
|
|
@@ -31469,9 +33035,25 @@ function createBuiltinDaemonSessionFactory(onRuntimeRawLine) {
|
|
|
31469
33035
|
}
|
|
31470
33036
|
async function createDaemon(opts) {
|
|
31471
33037
|
const log2 = opts.logger ?? createLogger({ header: "@alook/daemon" });
|
|
31472
|
-
const fallbackBase = (process.env.ALOOK_PROJECT_ROOT || `${
|
|
33038
|
+
const fallbackBase = (process.env.ALOOK_PROJECT_ROOT || `${homedir5()}/.alook`) + "/daemon";
|
|
31473
33039
|
const workingDirectoryBase = opts.workingDirectoryBase ?? fallbackBase;
|
|
31474
33040
|
const workdirFor = (agentId) => `${workingDirectoryBase}/${agentId}`;
|
|
33041
|
+
const dailyTokenUsage2 = new DailyTokenUsageStore(workingDirectoryBase);
|
|
33042
|
+
const providerQuotaReader = opts.providerQuotaReader ?? (opts.sessionFactory ? async () => null : readBuiltinProviderQuota);
|
|
33043
|
+
const providerQuotaByBackend = new Map;
|
|
33044
|
+
let requestReadyQuotaResend = () => {};
|
|
33045
|
+
const recordProviderQuota = (backendId, quota) => {
|
|
33046
|
+
const previous = providerQuotaByBackend.get(backendId);
|
|
33047
|
+
if (previous?.observation.status === "available" && quota.status === "error" && previous.observation.sourceEpoch === quota.sourceEpoch)
|
|
33048
|
+
return;
|
|
33049
|
+
providerQuotaByBackend.set(backendId, {
|
|
33050
|
+
agentBackendId: backendId,
|
|
33051
|
+
observation: structuredClone(quota)
|
|
33052
|
+
});
|
|
33053
|
+
if (previous && previous.observation.sourceEpoch !== quota.sourceEpoch) {
|
|
33054
|
+
requestReadyQuotaResend();
|
|
33055
|
+
}
|
|
33056
|
+
};
|
|
31475
33057
|
sweepTimelineHistory(workingDirectoryBase).catch(() => {
|
|
31476
33058
|
log2.warn("timeline startup sweep failed");
|
|
31477
33059
|
});
|
|
@@ -31487,6 +33069,27 @@ async function createDaemon(opts) {
|
|
|
31487
33069
|
});
|
|
31488
33070
|
let channelRef = null;
|
|
31489
33071
|
let managerRef = null;
|
|
33072
|
+
const providerQuotaSnapshots = () => [...providerQuotaByBackend.values()].map((snapshot) => structuredClone(snapshot));
|
|
33073
|
+
const activityPayload = async (info) => {
|
|
33074
|
+
if (info.state !== "idle")
|
|
33075
|
+
return info;
|
|
33076
|
+
const backendId = managerRef?.agentBackendId(info.agentId);
|
|
33077
|
+
if (backendId === "claude") {
|
|
33078
|
+
const observed = await providerQuotaReader("claude");
|
|
33079
|
+
if (observed)
|
|
33080
|
+
recordProviderQuota("claude", observed);
|
|
33081
|
+
}
|
|
33082
|
+
const quota = backendId === "claude" || backendId === "codex" ? providerQuotaByBackend.get(backendId) : undefined;
|
|
33083
|
+
const dailyUsage = await dailyTokenUsage2.snapshots(info.agentId);
|
|
33084
|
+
return {
|
|
33085
|
+
...info,
|
|
33086
|
+
...dailyUsage.length > 0 ? { dailyUsage } : {},
|
|
33087
|
+
...quota ? { quota: structuredClone(quota) } : {}
|
|
33088
|
+
};
|
|
33089
|
+
};
|
|
33090
|
+
let reportAgentActivity = (info) => {
|
|
33091
|
+
channelRef?.reportAgentActivity?.(info);
|
|
33092
|
+
};
|
|
31490
33093
|
let reminderSchedulerRef = null;
|
|
31491
33094
|
const selfSleepScheduler = opts.onSelfSleep ? new DaemonSelfSleepScheduler({
|
|
31492
33095
|
onSleep: opts.onSelfSleep,
|
|
@@ -31551,7 +33154,7 @@ async function createDaemon(opts) {
|
|
|
31551
33154
|
function reassertAgentActivity(agentId) {
|
|
31552
33155
|
const state = managerRef?.agentActivity(agentId);
|
|
31553
33156
|
if (state)
|
|
31554
|
-
|
|
33157
|
+
reportAgentActivity({ agentId, state });
|
|
31555
33158
|
}
|
|
31556
33159
|
function startTypingHeartbeat(agentId) {
|
|
31557
33160
|
stopTypingHeartbeat(agentId);
|
|
@@ -31695,6 +33298,20 @@ async function createDaemon(opts) {
|
|
|
31695
33298
|
logger: log2.child("ws")
|
|
31696
33299
|
});
|
|
31697
33300
|
channelRef = channel2;
|
|
33301
|
+
const activityReportTails = new Map;
|
|
33302
|
+
reportAgentActivity = (info) => {
|
|
33303
|
+
const prior = activityReportTails.get(info.agentId) ?? Promise.resolve();
|
|
33304
|
+
const next = prior.then(async () => {
|
|
33305
|
+
await channel2.reportAgentActivity(await activityPayload(info));
|
|
33306
|
+
}).catch(() => {
|
|
33307
|
+
log2.warn("agent activity telemetry report failed", { agentId: info.agentId, state: info.state });
|
|
33308
|
+
});
|
|
33309
|
+
activityReportTails.set(info.agentId, next);
|
|
33310
|
+
next.finally(() => {
|
|
33311
|
+
if (activityReportTails.get(info.agentId) === next)
|
|
33312
|
+
activityReportTails.delete(info.agentId);
|
|
33313
|
+
});
|
|
33314
|
+
};
|
|
31698
33315
|
function restorePendingIdleResetEvents(agentId) {
|
|
31699
33316
|
for (const pending of timeline2.pendingIdleResetEvents(agentId)) {
|
|
31700
33317
|
channel2.restorePendingBotAuditEvent({
|
|
@@ -31813,7 +33430,7 @@ async function createDaemon(opts) {
|
|
|
31813
33430
|
onAgentSession: (info) => void channel2.reportAgentSession(info),
|
|
31814
33431
|
onAgentActivity: (info) => {
|
|
31815
33432
|
selfSleepScheduler?.observeAgentActivity(info.agentId, info.state === "running");
|
|
31816
|
-
|
|
33433
|
+
reportAgentActivity(info);
|
|
31817
33434
|
if (info.state === "starting" || info.state === "running") {
|
|
31818
33435
|
if (!typingHeartbeats.has(info.agentId)) {
|
|
31819
33436
|
startTypingHeartbeat(info.agentId);
|
|
@@ -31822,6 +33439,16 @@ async function createDaemon(opts) {
|
|
|
31822
33439
|
emitTypingStopsAndClear(info.agentId);
|
|
31823
33440
|
}
|
|
31824
33441
|
},
|
|
33442
|
+
onTokenUsage: ({ agentId, usage }) => {
|
|
33443
|
+
dailyTokenUsage2.record(agentId, usage).catch(() => {
|
|
33444
|
+
log2.warn("daily token usage persistence failed", { agentId });
|
|
33445
|
+
});
|
|
33446
|
+
},
|
|
33447
|
+
onProviderQuota: ({ backendId, quota }) => {
|
|
33448
|
+
if (backendId !== "claude" && backendId !== "codex")
|
|
33449
|
+
return;
|
|
33450
|
+
recordProviderQuota(backendId, quota);
|
|
33451
|
+
},
|
|
31825
33452
|
onBotAuditEvent: (agentId, event, context) => emitBotAuditEvent(agentId, event, context),
|
|
31826
33453
|
onAgentLocallyStopped: (info) => router?.markLocallyStopped(info.agentId),
|
|
31827
33454
|
onRuntimeRawLine,
|
|
@@ -31868,6 +33495,11 @@ async function createDaemon(opts) {
|
|
|
31868
33495
|
arch: opts.arch,
|
|
31869
33496
|
osRelease: opts.osRelease,
|
|
31870
33497
|
daemonVersion: opts.daemonVersion,
|
|
33498
|
+
providerQuotas: providerQuotaSnapshots,
|
|
33499
|
+
resyncActivities: async () => {
|
|
33500
|
+
const activities = await Promise.all(manager.liveAgentActivities().map((info) => activityPayload(info)));
|
|
33501
|
+
return activities.filter((activity) => manager.agentActivity(activity.agentId) === activity.state);
|
|
33502
|
+
},
|
|
31871
33503
|
typingTracker,
|
|
31872
33504
|
logger: log2.child("router"),
|
|
31873
33505
|
onBeforeAgent: async (agentId) => {
|
|
@@ -31883,6 +33515,10 @@ async function createDaemon(opts) {
|
|
|
31883
33515
|
await enrollAgent(agentId);
|
|
31884
33516
|
}
|
|
31885
33517
|
});
|
|
33518
|
+
requestReadyQuotaResend = () => {
|
|
33519
|
+
if (router)
|
|
33520
|
+
channel2.sendReady?.(router.buildReady());
|
|
33521
|
+
};
|
|
31886
33522
|
channel2.onCommand(createSelfUpdateCommandListener(opts.handleSelfUpdate));
|
|
31887
33523
|
channel2.onCommand(createDiagnosticsCommandListener({
|
|
31888
33524
|
handleDiagnosticCommand: opts.handleDiagnosticCommand,
|
|
@@ -31912,6 +33548,11 @@ async function createDaemon(opts) {
|
|
|
31912
33548
|
resyncPendingWakes();
|
|
31913
33549
|
resyncPendingDiagnostics();
|
|
31914
33550
|
});
|
|
33551
|
+
if (opts.runtimeReport.some((runtime) => runtime.id === "claude")) {
|
|
33552
|
+
const observed = await providerQuotaReader("claude");
|
|
33553
|
+
if (observed)
|
|
33554
|
+
recordProviderQuota("claude", observed);
|
|
33555
|
+
}
|
|
31915
33556
|
channel2.connect();
|
|
31916
33557
|
await router.start();
|
|
31917
33558
|
selfSleepScheduler?.start();
|