@alook/daemon 0.1.23 → 0.1.25

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.
Files changed (3) hide show
  1. package/dist/cli/index.js +1623 -201
  2. package/dist/index.js +1575 -205
  3. package/package.json +4 -2
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();
@@ -1252,25 +1255,19 @@ class ClaudeEventNormalizer {
1252
1255
  }
1253
1256
  buildUsageTelemetry(event) {
1254
1257
  const u = event?.usage;
1255
- if (!u && event?.total_cost_usd == null)
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
- usageKind: "per_turn",
1262
- attrs: {
1263
- inputTokens: u?.input_tokens,
1264
- outputTokens: u?.output_tokens,
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
  }
@@ -1502,49 +1499,153 @@ class ClaudeDriver {
1502
1499
  }
1503
1500
 
1504
1501
  // agent-driver/dist/adapters/codex/telemetry.js
1505
- function mapCodexTelemetry(method, params) {
1502
+ function metric(value) {
1503
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : null;
1504
+ }
1505
+ function nonCachedInput(input, cached) {
1506
+ if (typeof input !== "number" || !Number.isSafeInteger(input) || input < 0 || typeof cached !== "number" || !Number.isSafeInteger(cached) || cached < 0 || cached > input)
1507
+ return null;
1508
+ return input - cached;
1509
+ }
1510
+ function canonicalId(value, fallback) {
1511
+ return typeof value === "string" && value.length > 0 && new TextEncoder().encode(value).length <= 64 ? value : fallback;
1512
+ }
1513
+ function mappedPlanName(value) {
1514
+ switch (value) {
1515
+ case "free":
1516
+ return "Free";
1517
+ case "plus":
1518
+ return "Plus";
1519
+ case "pro":
1520
+ return "Pro";
1521
+ case "team":
1522
+ return "Team";
1523
+ case "business":
1524
+ return "Business";
1525
+ case "enterprise":
1526
+ return "Enterprise";
1527
+ case "edu":
1528
+ return "Education";
1529
+ default:
1530
+ return;
1531
+ }
1532
+ }
1533
+ function quotaWindow(minutes, slot) {
1534
+ if (typeof minutes !== "number" || !Number.isSafeInteger(minutes) || minutes <= 0)
1535
+ return null;
1536
+ if (minutes === 1440)
1537
+ return { kind: "calendar", period: "day", displayName: "Daily usage limit" };
1538
+ if (minutes === 10080)
1539
+ return { kind: "calendar", period: "week", displayName: "Weekly usage limit" };
1540
+ if (minutes === 43200)
1541
+ return { kind: "calendar", period: "month", displayName: "Monthly usage limit" };
1542
+ return {
1543
+ kind: "rolling",
1544
+ durationSeconds: minutes * 60,
1545
+ displayName: slot === "primary" && minutes === 300 ? "5 hour usage limit" : `${minutes} minute usage limit`
1546
+ };
1547
+ }
1548
+ function resetIso(value) {
1549
+ if (typeof value === "number" && Number.isFinite(value) && value >= 0) {
1550
+ const date = new Date(value < 10000000000 ? value * 1000 : value);
1551
+ return Number.isNaN(date.getTime()) ? undefined : date.toISOString();
1552
+ }
1553
+ if (typeof value === "string") {
1554
+ const date = new Date(value);
1555
+ return Number.isNaN(date.getTime()) ? undefined : date.toISOString();
1556
+ }
1557
+ return;
1558
+ }
1559
+ function mapCodexQuotaSnapshots(snapshots, sourceEpoch) {
1560
+ const limits = [];
1561
+ let planName;
1562
+ for (const snapshot of snapshots) {
1563
+ const limitId = canonicalId(snapshot?.limitId ?? snapshot?.limit_id, "codex");
1564
+ const spark = /spark/i.test(limitId) || /spark/i.test(String(snapshot?.limitName ?? snapshot?.limit_name ?? ""));
1565
+ const product = spark ? { kind: "reported", id: "codex-spark", displayName: "Spark" } : { kind: "reported", id: "codex", displayName: "Codex" };
1566
+ const model = spark ? { kind: "reported", id: "gpt-5.3-codex-spark" } : { kind: "not_applicable" };
1567
+ planName ??= mappedPlanName(snapshot?.planType ?? snapshot?.plan_type);
1568
+ for (const slot of ["primary", "secondary"]) {
1569
+ const value = snapshot?.[slot];
1570
+ const window2 = quotaWindow(value?.windowDurationMins ?? value?.window_duration_mins, slot);
1571
+ const usedPercent = value?.usedPercent ?? value?.used_percent;
1572
+ if (!window2 || typeof usedPercent !== "number" || !Number.isFinite(usedPercent) || usedPercent < 0 || usedPercent > 100)
1573
+ continue;
1574
+ const resetsAt = resetIso(value?.resetsAt ?? value?.resets_at);
1575
+ limits.push({
1576
+ bucket: { limitId, product, model, window: window2 },
1577
+ usedPercent,
1578
+ ...resetsAt ? { resetsAt } : {}
1579
+ });
1580
+ }
1581
+ }
1582
+ if (limits.length === 0) {
1583
+ return {
1584
+ kind: "telemetry",
1585
+ name: "rate_limits",
1586
+ source: "codex_account_rate_limits_updated",
1587
+ quota: { status: "error", sourceEpoch, code: "invalid_response", retryable: true }
1588
+ };
1589
+ }
1590
+ return {
1591
+ kind: "telemetry",
1592
+ name: "rate_limits",
1593
+ source: "codex_account_rate_limits_updated",
1594
+ quota: {
1595
+ status: "available",
1596
+ sourceEpoch,
1597
+ ...planName ? { planName } : {},
1598
+ freshForSeconds: 300,
1599
+ limits
1600
+ }
1601
+ };
1602
+ }
1603
+ function mapCodexTelemetry(method, params, sourceEpoch) {
1506
1604
  if (method === "thread/tokenUsage/updated") {
1507
- const u = params?.usage ?? params ?? {};
1508
- return [
1509
- {
1510
- kind: "telemetry",
1511
- name: "token_usage",
1512
- source: "codex_thread_token_usage_updated",
1513
- usageKind: "cumulative_session",
1514
- attrs: {
1515
- totalTokens: u.totalTokens ?? u.total_tokens,
1516
- inputTokens: u.inputTokens ?? u.input_tokens,
1517
- cachedInputTokens: u.cachedInputTokens ?? u.cached_input_tokens,
1518
- outputTokens: u.outputTokens ?? u.output_tokens,
1519
- reasoningOutputTokens: u.reasoningOutputTokens ?? u.reasoning_output_tokens,
1520
- modelContextWindow: u.modelContextWindow ?? u.model_context_window,
1521
- cachedInputRatio: u.cachedInputRatio,
1522
- contextUtilization: u.contextUtilization
1523
- }
1605
+ const u = params?.tokenUsage?.last ?? params?.token_usage?.last;
1606
+ if (!u)
1607
+ return [];
1608
+ const input = u.inputTokens ?? u.input_tokens;
1609
+ const cached = u.cachedInputTokens ?? u.cached_input_tokens;
1610
+ return [{
1611
+ kind: "telemetry",
1612
+ name: "token_usage",
1613
+ source: "codex_thread_token_usage_updated",
1614
+ usage: {
1615
+ input: nonCachedInput(input, cached),
1616
+ output: metric(u.outputTokens ?? u.output_tokens),
1617
+ cache: metric(cached)
1524
1618
  }
1525
- ];
1619
+ }];
1526
1620
  }
1527
1621
  if (method === "account/rateLimits/updated") {
1528
- const r = params ?? {};
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
- ];
1622
+ return [mapCodexQuotaSnapshots([params?.rateLimits ?? params ?? {}], sourceEpoch)];
1543
1623
  }
1544
1624
  return [];
1545
1625
  }
1546
1626
 
1547
1627
  // agent-driver/dist/adapters/codex/normalizer.js
1628
+ import { randomBytes } from "node:crypto";
1629
+ var codexQuotaSourceEpoch = randomBytes(16).toString("base64url");
1630
+ var codexQuotaSourceGeneration = 0;
1631
+ var codexAccountFingerprint = null;
1632
+ function rotateCodexQuotaSource() {
1633
+ codexQuotaSourceEpoch = randomBytes(16).toString("base64url");
1634
+ codexQuotaSourceGeneration += 1;
1635
+ codexAccountFingerprint = null;
1636
+ }
1637
+ function observeCodexAccount(result) {
1638
+ const account = result?.account;
1639
+ const fingerprint = account && typeof account === "object" ? JSON.stringify([
1640
+ account.type ?? "unknown",
1641
+ account.email ?? null,
1642
+ account.planType ?? account.plan_type ?? null
1643
+ ]) : "none";
1644
+ if (codexAccountFingerprint !== null && codexAccountFingerprint !== fingerprint) {
1645
+ rotateCodexQuotaSource();
1646
+ }
1647
+ codexAccountFingerprint = fingerprint;
1648
+ }
1548
1649
  function normalizeFileChangeInput(item) {
1549
1650
  const paths = [];
1550
1651
  const seen = new Set;
@@ -1567,6 +1668,12 @@ function normalizeFileChangeInput(item) {
1567
1668
  }
1568
1669
 
1569
1670
  class CodexEventNormalizer {
1671
+ quotaReadRequestIds = new Set;
1672
+ accountReadRequestIds = new Set;
1673
+ rateLimitSnapshots = new Map;
1674
+ quotaSnapshotInitialized = false;
1675
+ quotaSourceGeneration = codexQuotaSourceGeneration;
1676
+ pendingTurnUsage = null;
1570
1677
  threadId = null;
1571
1678
  turnId = null;
1572
1679
  terminalTurn = null;
@@ -1577,10 +1684,69 @@ class CodexEventNormalizer {
1577
1684
  get currentTurnId() {
1578
1685
  return this.turnId;
1579
1686
  }
1687
+ registerQuotaReadRequest(requestId) {
1688
+ this.quotaReadRequestIds.add(requestId);
1689
+ }
1690
+ registerAccountReadRequest(requestId) {
1691
+ this.accountReadRequestIds.add(requestId);
1692
+ }
1693
+ syncQuotaSourceGeneration() {
1694
+ if (this.quotaSourceGeneration === codexQuotaSourceGeneration)
1695
+ return;
1696
+ this.quotaSourceGeneration = codexQuotaSourceGeneration;
1697
+ this.rateLimitSnapshots.clear();
1698
+ this.quotaSnapshotInitialized = false;
1699
+ }
1700
+ quotaSnapshots(value) {
1701
+ const byLimitId = value?.rateLimitsByLimitId ?? value?.rate_limits_by_limit_id;
1702
+ if (byLimitId && typeof byLimitId === "object" && !Array.isArray(byLimitId)) {
1703
+ return Object.entries(byLimitId).flatMap(([key2, snapshot2]) => {
1704
+ if (!snapshot2 || typeof snapshot2 !== "object" || Array.isArray(snapshot2))
1705
+ return [];
1706
+ return [[key2, {
1707
+ ...snapshot2,
1708
+ limitId: snapshot2.limitId ?? snapshot2.limit_id ?? key2
1709
+ }]];
1710
+ });
1711
+ }
1712
+ const snapshot = value?.rateLimits ?? value?.rate_limits ?? value;
1713
+ if (!snapshot || typeof snapshot !== "object" || Array.isArray(snapshot))
1714
+ return [];
1715
+ const record = snapshot;
1716
+ const key = typeof record.limitId === "string" ? record.limitId : typeof record.limit_id === "string" ? record.limit_id : "codex";
1717
+ return [[key, { ...record, limitId: key }]];
1718
+ }
1719
+ replaceQuotaSnapshots(value) {
1720
+ this.syncQuotaSourceGeneration();
1721
+ this.rateLimitSnapshots.clear();
1722
+ for (const [key, snapshot] of this.quotaSnapshots(value)) {
1723
+ this.rateLimitSnapshots.set(key, snapshot);
1724
+ }
1725
+ this.quotaSnapshotInitialized = true;
1726
+ return [mapCodexQuotaSnapshots([...this.rateLimitSnapshots.values()], codexQuotaSourceEpoch)];
1727
+ }
1728
+ mergeQuotaSnapshots(value) {
1729
+ this.syncQuotaSourceGeneration();
1730
+ for (const [key, update] of this.quotaSnapshots(value)) {
1731
+ const merged = {
1732
+ ...this.rateLimitSnapshots.get(key) ?? {},
1733
+ limitId: key
1734
+ };
1735
+ for (const [field, fieldValue] of Object.entries(update)) {
1736
+ if (fieldValue !== undefined && fieldValue !== null)
1737
+ merged[field] = fieldValue;
1738
+ }
1739
+ this.rateLimitSnapshots.set(key, merged);
1740
+ }
1741
+ if (!this.quotaSnapshotInitialized)
1742
+ return [];
1743
+ return [mapCodexQuotaSnapshots([...this.rateLimitSnapshots.values()], codexQuotaSourceEpoch)];
1744
+ }
1580
1745
  adoptThreadId(threadId) {
1581
1746
  if (threadId !== this.threadId) {
1582
1747
  this.turnId = null;
1583
1748
  this.terminalTurn = null;
1749
+ this.pendingTurnUsage = null;
1584
1750
  }
1585
1751
  this.threadId = threadId;
1586
1752
  }
@@ -1598,6 +1764,25 @@ class CodexEventNormalizer {
1598
1764
  const msg = tryParseJsonLine(line);
1599
1765
  if (!msg)
1600
1766
  return [];
1767
+ if (msg?.id !== undefined && this.accountReadRequestIds.delete(msg.id)) {
1768
+ if (!msg.error) {
1769
+ observeCodexAccount(msg.result);
1770
+ this.syncQuotaSourceGeneration();
1771
+ }
1772
+ return [];
1773
+ }
1774
+ if (msg?.id !== undefined && this.quotaReadRequestIds.delete(msg.id)) {
1775
+ this.syncQuotaSourceGeneration();
1776
+ if (msg.error) {
1777
+ return [{
1778
+ kind: "telemetry",
1779
+ name: "rate_limits",
1780
+ source: "codex_account_rate_limits_read",
1781
+ quota: { status: "error", sourceEpoch: codexQuotaSourceEpoch, code: "provider_error", retryable: true }
1782
+ }];
1783
+ }
1784
+ return this.replaceQuotaSnapshots(msg.result ?? {});
1785
+ }
1601
1786
  if (msg?.error && msg.id !== undefined) {
1602
1787
  return [{ kind: "error", message: msg.error?.message ?? "Codex RPC error" }];
1603
1788
  }
@@ -1622,6 +1807,7 @@ class CodexEventNormalizer {
1622
1807
  return [];
1623
1808
  this.turnId = params.turn.id;
1624
1809
  this.terminalTurn = null;
1810
+ this.pendingTurnUsage = null;
1625
1811
  return [
1626
1812
  {
1627
1813
  kind: "turn_owner",
@@ -1650,24 +1836,36 @@ class CodexEventNormalizer {
1650
1836
  case "turn/completed":
1651
1837
  if (!this.acceptRootTerminal(params))
1652
1838
  return [];
1839
+ const usage = this.pendingTurnUsage;
1840
+ this.pendingTurnUsage = null;
1653
1841
  if (params.turn.status === "failed") {
1654
1842
  return [
1843
+ ...usage ? [usage] : [],
1655
1844
  { kind: "error", message: "Codex turn failed" },
1656
1845
  { kind: "turn_end", sessionId: this.threadId ?? undefined, turnOwner: this.turnReceipt(params.threadId, params.turn.id) }
1657
1846
  ];
1658
1847
  }
1659
1848
  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) }];
1849
+ 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
1850
  }
1662
- return [{ kind: "turn_end", sessionId: this.threadId ?? undefined, turnOwner: this.turnReceipt(params.threadId, params.turn.id) }];
1851
+ return [...usage ? [usage] : [], { kind: "turn_end", sessionId: this.threadId ?? undefined, turnOwner: this.turnReceipt(params.threadId, params.turn.id) }];
1663
1852
  case "error":
1664
1853
  if (params?.willRetry === true) {
1665
1854
  return [{ kind: "runtime_recovery", stage: "retrying", source: "codex_stream" }];
1666
1855
  }
1667
1856
  return [{ kind: "error", message: params?.error?.message ?? params?.message ?? "Codex error" }];
1668
- case "thread/tokenUsage/updated":
1857
+ case "thread/tokenUsage/updated": {
1858
+ const usage2 = mapCodexTelemetry(method, params, codexQuotaSourceEpoch)[0];
1859
+ if (usage2)
1860
+ this.pendingTurnUsage = usage2;
1861
+ return [];
1862
+ }
1669
1863
  case "account/rateLimits/updated":
1670
- return mapCodexTelemetry(method, params);
1864
+ return this.mergeQuotaSnapshots(params);
1865
+ case "account/updated":
1866
+ rotateCodexQuotaSource();
1867
+ this.syncQuotaSourceGeneration();
1868
+ return [];
1671
1869
  default:
1672
1870
  return [];
1673
1871
  }
@@ -1780,7 +1978,53 @@ function resolveCodexHomeRootFromEnv(env = process.env, opts = {}) {
1780
1978
  return path5.join(opts.defaultHomeDir ?? os.homedir(), ".codex");
1781
1979
  }
1782
1980
 
1981
+ // agent-driver/dist/internal/errors.js
1982
+ var MAX_PUBLIC_ERROR_MESSAGE = 1000;
1983
+ 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}`;
1984
+ var QUOTED_CREDENTIAL_ASSIGNMENT = new RegExp(String.raw`(["'])(${CREDENTIAL_NAME})\1\s*[:=]\s*(["'])[^"'\r\n]*\3`, "gi");
1985
+ var CREDENTIAL_ASSIGNMENT = new RegExp(String.raw`(?<![A-Za-z0-9_-])((?:${CREDENTIAL_NAME})\s*[:=]\s*)(?!\[redacted\])[^\s,;}\]]+`, "gi");
1986
+ function scrubDriverErrorMessage(value, fallback = "Runtime operation failed") {
1987
+ const text = value instanceof Error ? value.message : String(value ?? "");
1988
+ 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();
1989
+ return (scrubbed || fallback).slice(0, MAX_PUBLIC_ERROR_MESSAGE);
1990
+ }
1991
+ function scrubDriverError(error) {
1992
+ return {
1993
+ ...error,
1994
+ code: stableErrorCode(error.code, "runtime_error"),
1995
+ message: scrubDriverErrorMessage(error.message),
1996
+ ...error.details ? { details: scrubDetails(error.details) } : {}
1997
+ };
1998
+ }
1999
+ function scrubDetails(details) {
2000
+ const scrubValue = (value, key) => {
2001
+ if (key && /api[_-]?key|access[_-]?token|auth(?:orization)?|password|passwd|secret|token/i.test(key)) {
2002
+ return "[redacted]";
2003
+ }
2004
+ if (typeof value === "string")
2005
+ return scrubDriverErrorMessage(value, "[redacted]");
2006
+ if (Array.isArray(value))
2007
+ return value.map((item) => scrubValue(item));
2008
+ if (value && typeof value === "object") {
2009
+ return Object.fromEntries(Object.entries(value).map(([childKey, child]) => [
2010
+ childKey,
2011
+ scrubValue(child, childKey)
2012
+ ]));
2013
+ }
2014
+ return value;
2015
+ };
2016
+ return scrubValue(details);
2017
+ }
2018
+ function stableErrorCode(value, fallback) {
2019
+ const code = String(value ?? "");
2020
+ return /^[A-Za-z0-9_.-]{1,100}$/.test(code) ? code : fallback;
2021
+ }
2022
+
1783
2023
  // agent-driver/dist/adapters/codex/index.js
2024
+ var SETTINGS_UPDATE_TIMEOUT_MS = 5000;
2025
+ var MODEL_LIST_TIMEOUT_MS = 5000;
2026
+ var MODEL_LIST_MAX = 64;
2027
+ var MODEL_EFFORT_MAX = 16;
1784
2028
  function isCodexMissingRolloutError(message) {
1785
2029
  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
2030
  }
@@ -1801,19 +2045,157 @@ class CodexDriver {
1801
2045
  }
1802
2046
  };
1803
2047
  eventNormalizer = new CodexEventNormalizer;
2048
+ pendingAccountReadRequestIds = new Set;
1804
2049
  requestId = 0;
1805
2050
  codexHomeRoot = null;
1806
2051
  proc = null;
1807
2052
  pendingInitialPrompt = null;
1808
2053
  pendingResumeFallbackParams = null;
2054
+ pendingSettingsUpdates = new Map;
1809
2055
  nextRequestId() {
1810
2056
  return ++this.requestId;
1811
2057
  }
2058
+ requestAccountQuotaSnapshot() {
2059
+ if (!this.proc?.stdin || this.proc.stdin.destroyed)
2060
+ return;
2061
+ const accountReadRequestId = this.nextRequestId();
2062
+ this.pendingAccountReadRequestIds.add(accountReadRequestId);
2063
+ this.eventNormalizer.registerAccountReadRequest(accountReadRequestId);
2064
+ this.proc.stdin.write(jsonRpcRequest("account/read", { refreshToken: false }, accountReadRequestId) + `
2065
+ `);
2066
+ }
2067
+ requestQuotaSnapshot() {
2068
+ if (!this.proc?.stdin || this.proc.stdin.destroyed)
2069
+ return;
2070
+ const quotaReadRequestId = this.nextRequestId();
2071
+ this.eventNormalizer.registerQuotaReadRequest(quotaReadRequestId);
2072
+ this.proc.stdin.write(jsonRpcRequest("account/rateLimits/read", {}, quotaReadRequestId) + `
2073
+ `);
2074
+ }
1812
2075
  get codexHome() {
1813
2076
  return this.codexHomeRoot;
1814
2077
  }
1815
- probe(command) {
1816
- return probeCliRuntime("codex", {}, command);
2078
+ async probe(command) {
2079
+ const result = await probeCliRuntime("codex", {}, command);
2080
+ if (result.status !== "healthy")
2081
+ return result;
2082
+ return {
2083
+ ...result,
2084
+ reasoning: await this.probeReasoningCatalog(command)
2085
+ };
2086
+ }
2087
+ async probeReasoningCatalog(command) {
2088
+ const spec = resolveSpawnSpec("codex", ["app-server", "--listen", "stdio://"], command);
2089
+ let proc;
2090
+ try {
2091
+ proc = spawnAgentProcess(spec.command, spec.args, {
2092
+ cwd: process.cwd(),
2093
+ env: { ...process.env, CI: "1" },
2094
+ shell: spec.shell
2095
+ });
2096
+ } catch {
2097
+ return;
2098
+ }
2099
+ return new Promise((resolve2) => {
2100
+ let settled = false;
2101
+ let buffer = "";
2102
+ let nextId = 0;
2103
+ let initializeId = 0;
2104
+ let listId = 0;
2105
+ const models = [];
2106
+ const seenModels = new Set;
2107
+ let defaultModelId;
2108
+ const finish = (catalog) => {
2109
+ if (settled)
2110
+ return;
2111
+ settled = true;
2112
+ clearTimeout(timer);
2113
+ const done = proc.pid ? killProcessTree(proc.pid, { graceMs: 250 }).catch(() => {}) : Promise.resolve().then(() => {
2114
+ proc.kill("SIGTERM");
2115
+ });
2116
+ done.finally(() => resolve2(catalog));
2117
+ };
2118
+ const requestModelPage = (cursor) => {
2119
+ listId = ++nextId;
2120
+ proc.stdin?.write(jsonRpcRequest("model/list", { limit: Math.min(MODEL_LIST_MAX - models.length, MODEL_LIST_MAX), includeHidden: false, ...cursor ? { cursor } : {} }, listId) + `
2121
+ `);
2122
+ };
2123
+ const consumeModel = (value) => {
2124
+ if (!value || typeof value !== "object" || models.length >= MODEL_LIST_MAX)
2125
+ return;
2126
+ const model = value;
2127
+ const id = typeof model.id === "string" ? model.id.trim() : "";
2128
+ if (!id || id.length > 100 || seenModels.has(id))
2129
+ return;
2130
+ const rawOptions = Array.isArray(model.supportedReasoningEfforts) ? model.supportedReasoningEfforts : [];
2131
+ const seenEfforts = new Set;
2132
+ const supportedReasoningEfforts = rawOptions.flatMap((raw) => {
2133
+ if (!raw || typeof raw !== "object")
2134
+ return [];
2135
+ const option = raw;
2136
+ const value2 = typeof option.reasoningEffort === "string" ? option.reasoningEffort.trim() : "";
2137
+ if (!value2 || value2.length > 32 || !/^[A-Za-z0-9._-]+$/.test(value2) || seenEfforts.has(value2))
2138
+ return [];
2139
+ seenEfforts.add(value2);
2140
+ const description = typeof option.description === "string" ? option.description.slice(0, 256) : undefined;
2141
+ return [{ value: value2, ...description ? { description } : {} }];
2142
+ }).slice(0, MODEL_EFFORT_MAX);
2143
+ const candidateDefault = typeof model.defaultReasoningEffort === "string" ? model.defaultReasoningEffort : undefined;
2144
+ seenModels.add(id);
2145
+ if (model.isDefault === true)
2146
+ defaultModelId = id;
2147
+ models.push({
2148
+ id,
2149
+ supportedReasoningEfforts,
2150
+ ...candidateDefault && supportedReasoningEfforts.some((item) => item.value === candidateDefault) ? { defaultReasoningEffort: candidateDefault } : {}
2151
+ });
2152
+ };
2153
+ const onLine = (line) => {
2154
+ let message;
2155
+ try {
2156
+ message = JSON.parse(line);
2157
+ } catch {
2158
+ return;
2159
+ }
2160
+ if (message.id === initializeId) {
2161
+ if (message.error)
2162
+ return finish();
2163
+ requestModelPage();
2164
+ return;
2165
+ }
2166
+ if (message.id !== listId)
2167
+ return;
2168
+ if (message.error || !message.result || typeof message.result !== "object")
2169
+ return finish();
2170
+ const result = message.result;
2171
+ for (const model of Array.isArray(result.data) ? result.data : [])
2172
+ consumeModel(model);
2173
+ const cursor = typeof result.nextCursor === "string" ? result.nextCursor : undefined;
2174
+ if (cursor && models.length < MODEL_LIST_MAX)
2175
+ return requestModelPage(cursor);
2176
+ finish({
2177
+ updateMode: "live_next_turn",
2178
+ ...defaultModelId ? { defaultModelId } : {},
2179
+ models
2180
+ });
2181
+ };
2182
+ const timer = setTimeout(() => finish(), MODEL_LIST_TIMEOUT_MS);
2183
+ timer.unref?.();
2184
+ proc.stdout?.on("data", (chunk) => {
2185
+ buffer += chunk.toString();
2186
+ const lines = buffer.split(`
2187
+ `);
2188
+ buffer = lines.pop() ?? "";
2189
+ for (const line of lines)
2190
+ if (line.trim())
2191
+ onLine(line);
2192
+ });
2193
+ proc.on("error", () => finish());
2194
+ proc.on("exit", () => finish());
2195
+ initializeId = ++nextId;
2196
+ proc.stdin?.write(jsonRpcRequest("initialize", { clientInfo: { name: "alook-agent-driver-probe", version: "0.1.24" }, capabilities: { experimentalApi: true } }, initializeId) + `
2197
+ `);
2198
+ });
1817
2199
  }
1818
2200
  async openLane(ctx, options) {
1819
2201
  return createProcessLane(this, ctx, { onRawStdoutLine: options?.onRawStdoutLine });
@@ -1829,6 +2211,9 @@ class CodexDriver {
1829
2211
  shell: spec.shell
1830
2212
  });
1831
2213
  this.proc = proc;
2214
+ proc.once("exit", () => {
2215
+ this.failPendingSettingsUpdates("settings_process_exited", "Codex exited before acknowledging the settings update");
2216
+ });
1832
2217
  const initialPrompt = ctx.prompt?.trim() ? ctx.prompt : null;
1833
2218
  this.pendingInitialPrompt = initialPrompt;
1834
2219
  queueMicrotask(() => {
@@ -1857,11 +2242,21 @@ class CodexDriver {
1857
2242
  proc.stdin?.write(jsonRpcRequest("thread/start", freshParams, this.nextRequestId()) + `
1858
2243
  `);
1859
2244
  }
2245
+ this.requestAccountQuotaSnapshot();
1860
2246
  });
1861
2247
  return { process: proc };
1862
2248
  }
1863
2249
  normalizeLine(line) {
2250
+ const settingsResponse = this.consumeSettingsUpdateResponse(line);
2251
+ if (settingsResponse)
2252
+ return [];
2253
+ const parsed = tryParseJsonLine(line);
1864
2254
  const events = this.eventNormalizer.normalizeLine(line);
2255
+ if (typeof parsed?.id === "number" && this.pendingAccountReadRequestIds.delete(parsed.id)) {
2256
+ this.requestQuotaSnapshot();
2257
+ }
2258
+ if (parsed?.method === "account/updated")
2259
+ this.requestAccountQuotaSnapshot();
1865
2260
  if (this.pendingResumeFallbackParams && this.proc?.stdin && !this.proc.stdin.destroyed) {
1866
2261
  const rolloutErr = events.find((e) => e.kind === "error" && isCodexMissingRolloutError(e.message));
1867
2262
  if (rolloutErr) {
@@ -1885,6 +2280,90 @@ class CodexDriver {
1885
2280
  }
1886
2281
  return events;
1887
2282
  }
2283
+ updateSettings(input) {
2284
+ const threadId = this.eventNormalizer.currentSessionId;
2285
+ const stdin = this.proc?.stdin;
2286
+ if (!threadId || !stdin || stdin.destroyed || stdin.writableEnded || stdin.writable === false) {
2287
+ return Promise.resolve({
2288
+ status: "failed",
2289
+ error: this.settingsError("process", "settings_thread_unavailable", "Codex thread is not available for a settings update", true)
2290
+ });
2291
+ }
2292
+ const id = this.nextRequestId();
2293
+ return new Promise((resolve2) => {
2294
+ const timer = setTimeout(() => {
2295
+ if (!this.pendingSettingsUpdates.delete(id))
2296
+ return;
2297
+ resolve2({
2298
+ status: "failed",
2299
+ error: this.settingsError("timeout", "settings_update_timeout", "Codex did not acknowledge the settings update before the deadline", true)
2300
+ });
2301
+ }, SETTINGS_UPDATE_TIMEOUT_MS);
2302
+ timer.unref?.();
2303
+ this.pendingSettingsUpdates.set(id, { resolve: resolve2, timer });
2304
+ try {
2305
+ stdin.write(jsonRpcRequest("thread/settings/update", { threadId, effort: input.reasoningEffort }, id) + `
2306
+ `);
2307
+ } catch (error) {
2308
+ clearTimeout(timer);
2309
+ this.pendingSettingsUpdates.delete(id);
2310
+ resolve2({
2311
+ status: "failed",
2312
+ error: this.settingsError("process", "settings_update_write_failed", String(error), true)
2313
+ });
2314
+ }
2315
+ });
2316
+ }
2317
+ consumeSettingsUpdateResponse(line) {
2318
+ let value;
2319
+ try {
2320
+ value = JSON.parse(line);
2321
+ } catch {
2322
+ return false;
2323
+ }
2324
+ if (!value || typeof value !== "object")
2325
+ return false;
2326
+ const record = value;
2327
+ if (typeof record.id !== "number")
2328
+ return false;
2329
+ const pending = this.pendingSettingsUpdates.get(record.id);
2330
+ if (!pending)
2331
+ return false;
2332
+ clearTimeout(pending.timer);
2333
+ this.pendingSettingsUpdates.delete(record.id);
2334
+ const error = record.error;
2335
+ if (!error || typeof error !== "object") {
2336
+ pending.resolve({ status: "applied" });
2337
+ return true;
2338
+ }
2339
+ const rpcError = error;
2340
+ const message = typeof rpcError.message === "string" ? rpcError.message : "Codex rejected the settings update";
2341
+ if (rpcError.code === -32601 || /method\s+not\s+found/i.test(message)) {
2342
+ pending.resolve({
2343
+ status: "unsupported",
2344
+ error: this.settingsError("protocol", "settings_update_unsupported", "Codex does not support live reasoning settings updates", false)
2345
+ });
2346
+ } else {
2347
+ pending.resolve({
2348
+ status: "failed",
2349
+ error: this.settingsError("protocol", "settings_update_rejected", message, true)
2350
+ });
2351
+ }
2352
+ return true;
2353
+ }
2354
+ settingsError(category, code, message, retryable) {
2355
+ return { category, code, message: scrubDriverErrorMessage(message), retryable };
2356
+ }
2357
+ failPendingSettingsUpdates(code, message) {
2358
+ for (const [id, pending] of this.pendingSettingsUpdates) {
2359
+ clearTimeout(pending.timer);
2360
+ this.pendingSettingsUpdates.delete(id);
2361
+ pending.resolve({
2362
+ status: "failed",
2363
+ error: this.settingsError("process", code, message, true)
2364
+ });
2365
+ }
2366
+ }
1888
2367
  get currentSessionId() {
1889
2368
  return this.eventNormalizer.currentSessionId;
1890
2369
  }
@@ -2203,15 +2682,6 @@ class CursorAcpLane {
2203
2682
  }
2204
2683
  this.activePrompt = null;
2205
2684
  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
2685
  this.events.emit("runtime_event", {
2216
2686
  kind: "turn_end",
2217
2687
  sessionId: this.sessionId ?? undefined,
@@ -2561,10 +3031,10 @@ class CursorDriver {
2561
3031
  }
2562
3032
 
2563
3033
  // agent-driver/dist/adapters/opencode/index.js
2564
- import { randomBytes as randomBytes2 } from "node:crypto";
3034
+ import { randomBytes as randomBytes3 } from "node:crypto";
2565
3035
 
2566
3036
  // agent-driver/dist/adapters/opencode/service-lane.js
2567
- import { randomBytes } from "node:crypto";
3037
+ import { randomBytes as randomBytes2 } from "node:crypto";
2568
3038
  import { EventEmitter as EventEmitter3 } from "node:events";
2569
3039
  import { createServer as createServer2 } from "node:net";
2570
3040
  var SUPPORTED_VERSION = "1.17.20";
@@ -2703,7 +3173,7 @@ class OpenCodeServiceLane {
2703
3173
  this.ctx = ctx;
2704
3174
  this.options = options;
2705
3175
  this.fetchFn = options.fetch ?? fetch;
2706
- this.password = options.password ?? randomBytes(32).toString("base64url");
3176
+ this.password = options.password ?? randomBytes2(32).toString("base64url");
2707
3177
  }
2708
3178
  get currentSessionId() {
2709
3179
  return this.sessionId;
@@ -3317,12 +3787,20 @@ class OpenCodeServiceLane {
3317
3787
  });
3318
3788
  }
3319
3789
  const tokens = record2(data.tokens);
3320
- if (tokens) {
3790
+ if (tokens && data.finish !== "tool-calls") {
3791
+ const cache = record2(tokens.cache);
3792
+ const metric2 = (value2) => typeof value2 === "number" && Number.isSafeInteger(value2) && value2 >= 0 ? value2 : null;
3793
+ const cacheParts = [cache?.read, cache?.write].filter((value2) => typeof value2 === "number" && Number.isSafeInteger(value2) && value2 >= 0);
3794
+ const cacheTotal = cacheParts.reduce((sum, value2) => sum + value2, 0);
3321
3795
  this.events.emit("runtime_event", {
3322
3796
  kind: "telemetry",
3323
3797
  name: "token_usage",
3324
3798
  source: "opencode.v2",
3325
- attrs: tokens
3799
+ usage: {
3800
+ input: metric2(tokens.input),
3801
+ output: metric2(tokens.output),
3802
+ cache: cacheParts.length > 0 && Number.isSafeInteger(cacheTotal) ? cacheTotal : null
3803
+ }
3326
3804
  });
3327
3805
  }
3328
3806
  break;
@@ -3628,7 +4106,7 @@ class OpenCodeServiceLane {
3628
4106
  return headers;
3629
4107
  }
3630
4108
  newMessageId() {
3631
- return `msg_${randomBytes(16).toString("hex")}`;
4109
+ return `msg_${randomBytes2(16).toString("hex")}`;
3632
4110
  }
3633
4111
  diagnostic(severity, message) {
3634
4112
  this.events.emit("runtime_event", {
@@ -3693,7 +4171,7 @@ class OpenCodeServiceLane {
3693
4171
 
3694
4172
  // agent-driver/dist/adapters/opencode/index.js
3695
4173
  function createOpenCodeMessageId() {
3696
- return `msg_${randomBytes2(16).toString("hex")}`;
4174
+ return `msg_${randomBytes3(16).toString("hex")}`;
3697
4175
  }
3698
4176
 
3699
4177
  class OpenCodeDriver {
@@ -4471,48 +4949,6 @@ function assertInstructionFileName(name) {
4471
4949
  }
4472
4950
  }
4473
4951
 
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
4952
  // agent-driver/dist/controller/logical-session.js
4517
4953
  import { mkdirSync as mkdirSync3 } from "node:fs";
4518
4954
  var SEMANTIC_ASSEMBLER_MAX_BYTES = 1048576;
@@ -4630,6 +5066,8 @@ class LogicalAgentSession {
4630
5066
  toolBoundaryFlushDisabled = false;
4631
5067
  safeBoundaryFlush;
4632
5068
  safeBoundaryDelivery;
5069
+ settingsUpdateTail = Promise.resolve();
5070
+ settingsUpdatePending = false;
4633
5071
  turnAdmission;
4634
5072
  instructionsMaterialized = false;
4635
5073
  lifecycleGeneration = 0;
@@ -4685,6 +5123,35 @@ class LogicalAgentSession {
4685
5123
  send(message) {
4686
5124
  return this.admit("send", message);
4687
5125
  }
5126
+ updateSettings(input) {
5127
+ if (this.state === "closed" || this.state === "stopping" || this.finishing) {
5128
+ return Promise.resolve({
5129
+ status: "failed",
5130
+ error: driverError("process", "settings_session_closed", "Runtime session is closed", true)
5131
+ });
5132
+ }
5133
+ this.settingsUpdatePending = true;
5134
+ const operation = this.settingsUpdateTail.then(async () => {
5135
+ if (!this.lane?.updateSettings)
5136
+ return { status: "unsupported" };
5137
+ try {
5138
+ return await this.lane.updateSettings(input);
5139
+ } catch (error) {
5140
+ return {
5141
+ status: "failed",
5142
+ error: driverError("process", "settings_update_failed", String(error), true)
5143
+ };
5144
+ }
5145
+ });
5146
+ this.settingsUpdateTail = operation.then((result) => {
5147
+ if (result.status === "applied") {
5148
+ this.settingsUpdatePending = false;
5149
+ return;
5150
+ }
5151
+ return new Promise(() => {});
5152
+ });
5153
+ return operation;
5154
+ }
4688
5155
  async interrupt(input) {
4689
5156
  if (this.state === "closed" || this.state === "stopping" || this.finishing)
4690
5157
  return { status: "closed" };
@@ -4846,7 +5313,7 @@ class LogicalAgentSession {
4846
5313
  }
4847
5314
  return receipt;
4848
5315
  }
4849
- if (this.state === "idle" && (this.queued.length > 0 || this.safeBoundaryFlush !== undefined || this.safeBoundaryDelivery !== undefined)) {
5316
+ if (this.state === "idle" && (this.queued.length > 0 || this.safeBoundaryFlush !== undefined || this.safeBoundaryDelivery !== undefined || this.settingsUpdatePending)) {
4850
5317
  return this.queue(message, "runtime_busy");
4851
5318
  }
4852
5319
  return this.startTurn([message], "prompt");
@@ -5154,11 +5621,10 @@ class LogicalAgentSession {
5154
5621
  }
5155
5622
  return;
5156
5623
  case "telemetry": {
5157
- const details = jsonValue(event.attrs);
5158
5624
  if (event.name === "token_usage") {
5159
- this.emit({ type: "token_usage", turnId, source: event.source, usage: {}, details });
5625
+ this.emit({ type: "token_usage", turnId, source: event.source, usage: event.usage });
5160
5626
  } else {
5161
- this.emit({ type: "rate_limits", turnId, source: event.source, details });
5627
+ this.emit({ type: "rate_limits", turnId, source: event.source, quota: event.quota });
5162
5628
  }
5163
5629
  return;
5164
5630
  }
@@ -5259,7 +5725,7 @@ class LogicalAgentSession {
5259
5725
  if (this.adapter.execution.lifetime === "turn") {
5260
5726
  this.processTurnEnded = true;
5261
5727
  } else {
5262
- Promise.resolve().then(() => this.safeBoundaryFlush).then(() => this.startNextQueued());
5728
+ Promise.resolve().then(() => this.safeBoundaryFlush).then(() => this.settingsUpdateTail).then(() => this.startNextQueued());
5263
5729
  }
5264
5730
  }
5265
5731
  flushSafeBoundaryQueue() {
@@ -5759,8 +6225,14 @@ function createAgentDriverSdkWithRegistry(options) {
5759
6225
  assertAdapterCompatibility(String(registration.id), registration.capabilities, adapter);
5760
6226
  const command = capabilities2.commandOverride ? input.command : undefined;
5761
6227
  const result = await adapter.probe(command);
5762
- if (result.status === "healthy")
5763
- return { status: "healthy", version: result.version, capabilities: capabilities2 };
6228
+ if (result.status === "healthy") {
6229
+ return {
6230
+ status: "healthy",
6231
+ version: result.version,
6232
+ capabilities: capabilities2,
6233
+ reasoning: result.reasoning
6234
+ };
6235
+ }
5764
6236
  return {
5765
6237
  status: "unhealthy",
5766
6238
  error: {
@@ -5769,7 +6241,8 @@ function createAgentDriverSdkWithRegistry(options) {
5769
6241
  message: `Backend ${input.backend} is unavailable`,
5770
6242
  retryable: true
5771
6243
  },
5772
- capabilities: capabilities2
6244
+ capabilities: capabilities2,
6245
+ reasoning: result.reasoning
5773
6246
  };
5774
6247
  } catch (error) {
5775
6248
  const contractInvalid = error instanceof Error && (error.message.startsWith("Adapter ") || error.message.startsWith("Agent backend registration "));
@@ -5971,37 +6444,176 @@ class RuntimeNotificationState {
5971
6444
  return identity.length === 0 || !this.contributedIdentities.has(identity);
5972
6445
  });
5973
6446
  }
5974
- add(count = 1) {
5975
- this.pendingCountValue += count;
6447
+ add(count = 1) {
6448
+ this.pendingCountValue += count;
6449
+ }
6450
+ schedule(callback, delayMs) {
6451
+ if (this.timerValue)
6452
+ return false;
6453
+ this.timerValue = setTimeout(() => {
6454
+ this.timerValue = null;
6455
+ callback();
6456
+ }, delayMs);
6457
+ this.timerValue.unref?.();
6458
+ return true;
6459
+ }
6460
+ takePendingAndClearTimer() {
6461
+ const count = this.pendingCountValue;
6462
+ this.pendingCountValue = 0;
6463
+ if (this.timerValue) {
6464
+ clearTimeout(this.timerValue);
6465
+ this.timerValue = null;
6466
+ }
6467
+ return count;
6468
+ }
6469
+ ensureContributionSession(sessionId) {
6470
+ if (this.contributionSessionId !== sessionId) {
6471
+ this.contributionSessionId = sessionId;
6472
+ this.contributedIdentities = new Set;
6473
+ }
6474
+ }
6475
+ }
6476
+ // src/runtime/errorDiagnostics.ts
6477
+ import { createHash as createHash2 } from "crypto";
6478
+ // agent-driver/dist/provider-quota.js
6479
+ import { execFile } from "node:child_process";
6480
+ import { readFile } from "node:fs/promises";
6481
+ import { homedir as homedir3 } from "node:os";
6482
+ import { join as join9 } from "node:path";
6483
+ import { promisify } from "node:util";
6484
+ import { randomBytes as randomBytes4 } from "node:crypto";
6485
+ var execFileAsync = promisify(execFile);
6486
+ var claudeAccessToken = null;
6487
+ var claudeSourceEpoch = randomBytes4(16).toString("base64url");
6488
+ function parseCredentials(value) {
6489
+ try {
6490
+ const parsed = JSON.parse(value);
6491
+ return parsed && typeof parsed === "object" ? parsed : null;
6492
+ } catch {
6493
+ return null;
6494
+ }
6495
+ }
6496
+ async function claudeCredentials(options) {
6497
+ const platform = options.platform ?? process.platform;
6498
+ if (platform === "darwin") {
6499
+ try {
6500
+ const value = options.readKeychain ? await options.readKeychain() : (await execFileAsync("security", ["find-generic-password", "-s", "Claude Code-credentials", "-w"], {
6501
+ timeout: 3000,
6502
+ maxBuffer: 256 * 1024
6503
+ })).stdout;
6504
+ const parsed = parseCredentials(value.trim());
6505
+ if (parsed)
6506
+ return parsed;
6507
+ } catch {}
6508
+ }
6509
+ const env = options.env ?? process.env;
6510
+ const root = env.CLAUDE_CONFIG_DIR || join9(options.home ?? homedir3(), ".claude");
6511
+ try {
6512
+ const value = options.readCredentialsFile ? await options.readCredentialsFile(join9(root, ".credentials.json")) : await readFile(join9(root, ".credentials.json"), "utf8");
6513
+ return parseCredentials(value);
6514
+ } catch {
6515
+ return null;
6516
+ }
6517
+ }
6518
+ function mappedPlanName2(value) {
6519
+ switch (value) {
6520
+ case "free":
6521
+ return "Free";
6522
+ case "pro":
6523
+ return "Pro";
6524
+ case "max":
6525
+ return "Max";
6526
+ case "team":
6527
+ return "Team";
6528
+ case "enterprise":
6529
+ return "Enterprise";
6530
+ default:
6531
+ return;
6532
+ }
6533
+ }
6534
+ function resetIso2(value) {
6535
+ if (typeof value !== "string")
6536
+ return;
6537
+ const date = new Date(value);
6538
+ return Number.isNaN(date.getTime()) ? undefined : date.toISOString();
6539
+ }
6540
+ function claudeLimit(key, value) {
6541
+ if (!value || typeof value !== "object")
6542
+ return null;
6543
+ const row = value;
6544
+ if (typeof row.utilization !== "number" || !Number.isFinite(row.utilization) || row.utilization < 0 || row.utilization > 100)
6545
+ return null;
6546
+ const model = key.includes("sonnet") ? { kind: "reported", id: "claude-sonnet" } : key.includes("opus") ? { kind: "reported", id: "claude-opus" } : { kind: "not_applicable" };
6547
+ const window2 = key === "five_hour" ? { kind: "rolling", durationSeconds: 18000, displayName: "5 hour usage limit" } : { kind: "rolling", durationSeconds: 604800, displayName: "7 day usage limit" };
6548
+ const resetsAt = resetIso2(row.resets_at ?? row.resetsAt);
6549
+ return {
6550
+ bucket: {
6551
+ limitId: key,
6552
+ product: { kind: "reported", id: "claude", displayName: "Claude" },
6553
+ model,
6554
+ window: window2
6555
+ },
6556
+ usedPercent: row.utilization,
6557
+ ...resetsAt ? { resetsAt } : {}
6558
+ };
6559
+ }
6560
+ async function readClaudeQuota(options) {
6561
+ const env = options.env ?? process.env;
6562
+ if (env.ANTHROPIC_API_KEY || env.ANTHROPIC_BASE_URL)
6563
+ return null;
6564
+ const credentials = await claudeCredentials(options);
6565
+ const token = credentials?.claudeAiOauth?.accessToken;
6566
+ if (typeof token !== "string" || token.length === 0)
6567
+ return null;
6568
+ if (claudeAccessToken !== token) {
6569
+ claudeAccessToken = token;
6570
+ claudeSourceEpoch = randomBytes4(16).toString("base64url");
6571
+ }
6572
+ let response;
6573
+ try {
6574
+ response = await (options.fetchUsage ?? fetch)("https://api.anthropic.com/api/oauth/usage", {
6575
+ headers: {
6576
+ authorization: `Bearer ${token}`,
6577
+ "anthropic-beta": "oauth-2025-04-20"
6578
+ },
6579
+ signal: AbortSignal.timeout(5000)
6580
+ });
6581
+ } catch {
6582
+ return { status: "error", sourceEpoch: claudeSourceEpoch, code: "network", retryable: true };
6583
+ }
6584
+ if (response.status === 401 || response.status === 403) {
6585
+ return { status: "error", sourceEpoch: claudeSourceEpoch, code: "unauthorized", retryable: false };
5976
6586
  }
5977
- schedule(callback, delayMs) {
5978
- if (this.timerValue)
5979
- return false;
5980
- this.timerValue = setTimeout(() => {
5981
- this.timerValue = null;
5982
- callback();
5983
- }, delayMs);
5984
- this.timerValue.unref?.();
5985
- return true;
6587
+ if (!response.ok) {
6588
+ return { status: "error", sourceEpoch: claudeSourceEpoch, code: "provider_error", retryable: response.status === 429 || response.status >= 500 };
5986
6589
  }
5987
- takePendingAndClearTimer() {
5988
- const count = this.pendingCountValue;
5989
- this.pendingCountValue = 0;
5990
- if (this.timerValue) {
5991
- clearTimeout(this.timerValue);
5992
- this.timerValue = null;
5993
- }
5994
- return count;
6590
+ let body;
6591
+ try {
6592
+ body = await response.json();
6593
+ } catch {
6594
+ return { status: "error", sourceEpoch: claudeSourceEpoch, code: "invalid_response", retryable: true };
5995
6595
  }
5996
- ensureContributionSession(sessionId) {
5997
- if (this.contributionSessionId !== sessionId) {
5998
- this.contributionSessionId = sessionId;
5999
- this.contributedIdentities = new Set;
6000
- }
6596
+ if (!body || typeof body !== "object") {
6597
+ return { status: "error", sourceEpoch: claudeSourceEpoch, code: "invalid_response", retryable: true };
6001
6598
  }
6599
+ const record3 = body;
6600
+ const limits = ["five_hour", "seven_day", "seven_day_sonnet", "seven_day_opus"].map((key) => claudeLimit(key, record3[key])).filter((limit) => limit !== null);
6601
+ if (limits.length === 0) {
6602
+ return { status: "error", sourceEpoch: claudeSourceEpoch, code: "invalid_response", retryable: true };
6603
+ }
6604
+ const planName = mappedPlanName2(credentials?.claudeAiOauth?.subscriptionType);
6605
+ return {
6606
+ status: "available",
6607
+ sourceEpoch: claudeSourceEpoch,
6608
+ ...planName ? { planName } : {},
6609
+ freshForSeconds: 300,
6610
+ limits
6611
+ };
6612
+ }
6613
+ async function readBuiltinProviderQuota(backend, options = {}) {
6614
+ return backend === "claude" ? readClaudeQuota(options) : null;
6002
6615
  }
6003
6616
  // src/runtime/errorDiagnostics.ts
6004
- import { createHash as createHash2 } from "crypto";
6005
6617
  var ERROR_EXCERPT_MAX_BYTES = 4000;
6006
6618
  var ERROR_FINGERPRINT_LEN = 16;
6007
6619
  var ERROR_LEN_BUCKETS = [
@@ -6824,6 +7436,29 @@ function reduceManager(state, event) {
6824
7436
  a.inbox = [...a.inbox, event.message];
6825
7437
  a.idleSince = null;
6826
7438
  });
7439
+ case "runtime_config_queued":
7440
+ return mutate(state, event.agentId, (a) => {
7441
+ if (!a.inbox.some((message) => message.id === event.message.id)) {
7442
+ a.inbox = [...a.inbox, event.message];
7443
+ }
7444
+ syncExecutionProjection(a);
7445
+ a.idleSince = null;
7446
+ });
7447
+ case "runtime_config_applied": {
7448
+ const existing = state.agents[event.agentId];
7449
+ if (!existing)
7450
+ return { state, effects: [] };
7451
+ const agent = clone(existing);
7452
+ if (agent.status !== "running" || leaseIsWorking(agent.execution.lease) || agent.pendingAdmissions.length > 0 || agent.inbox.length === 0)
7453
+ return { state, effects: [] };
7454
+ const messages = drainInbox(agent);
7455
+ return commit(state, agent, messages.map((message) => ({
7456
+ type: "send",
7457
+ agentId: event.agentId,
7458
+ message,
7459
+ mode: "idle"
7460
+ })));
7461
+ }
6827
7462
  case "turn_started": {
6828
7463
  const existing = state.agents[event.agentId];
6829
7464
  if (!existing || existing.execution.sessionInstanceId !== event.sessionInstanceId)
@@ -7031,7 +7666,7 @@ function onTurnCompleted(state, agentId, sessionInstanceId, nowMs, turnId, endRe
7031
7666
  agent.stalledSessionId = null;
7032
7667
  syncExecutionProjection(agent);
7033
7668
  const clearEffects = clearedStallSessionId === null ? [] : [{ type: "clear_stall_recovery", agentId, sessionId: clearedStallSessionId }];
7034
- if (agent.inbox.length > 0) {
7669
+ if (agent.inbox.length > 0 && !agent.resetting) {
7035
7670
  const messages = drainInbox(agent);
7036
7671
  return commit(state, agent, [
7037
7672
  ...clearEffects,
@@ -7946,9 +8581,13 @@ class AgentProcessManager {
7946
8581
  state;
7947
8582
  sessions = new Map;
7948
8583
  runtimeConfigs = new Map;
8584
+ appliedRuntimeConfigs = new Map;
8585
+ pendingRuntimeConfigUpdates = new Map;
8586
+ runtimeConfigApplyRunning = new Set;
7949
8587
  resumeSessions = new Map;
7950
8588
  launchIds = new Map;
7951
8589
  liveSessions = new Map;
8590
+ liveBackendIds = new Map;
7952
8591
  activeSpawnState = new Map;
7953
8592
  publishedAgentActivity = new Map;
7954
8593
  traceProcessNonce = randomUUID5();
@@ -7977,19 +8616,156 @@ class AgentProcessManager {
7977
8616
  this.state = createInitialManagerState(this.opts.staleThresholdMs, this.opts.idleTimeoutMs, this.opts.resetStuckThresholdMs, this.opts.stoppingStuckThresholdMs, this.opts.idleResetTimeoutMs);
7978
8617
  }
7979
8618
  register(agentId, launch) {
7980
- if (launch?.runtimeConfig)
7981
- this.runtimeConfigs.set(agentId, launch.runtimeConfig);
8619
+ const runtimeConfigAcceptance = launch?.runtimeConfig ? this.acceptRuntimeConfig(agentId, launch.runtimeConfig) : undefined;
7982
8620
  if (launch?.sessionId)
7983
8621
  this.resumeSessions.set(agentId, launch.sessionId);
7984
8622
  if (launch?.launchId)
7985
8623
  this.launchIds.set(agentId, launch.launchId);
7986
8624
  this.dispatch({ type: "register", agentId });
8625
+ const registered = this.state.agents[agentId];
8626
+ if (launch?.runtimeConfig && launch.applyRuntimeConfig !== false && (runtimeConfigAcceptance === "accepted" || this.pendingRuntimeConfigUpdates.has(agentId)) && this.sessions.has(agentId) && registered && !isActivelyWorking(registered)) {
8627
+ this.convergeRuntimeConfig(agentId);
8628
+ }
8629
+ }
8630
+ async updateRuntimeConfig(agentId, config) {
8631
+ const accepted = this.acceptRuntimeConfig(agentId, config);
8632
+ if (accepted === "stale" || accepted === "idempotent")
8633
+ return accepted;
8634
+ const session = this.sessions.get(agentId);
8635
+ if (!session) {
8636
+ this.pendingRuntimeConfigUpdates.delete(agentId);
8637
+ return "saved_for_start";
8638
+ }
8639
+ const agent = this.state.agents[agentId];
8640
+ if (agent && (agent.turnActive || isActivelyWorking(agent)))
8641
+ return "deferred";
8642
+ return this.convergeRuntimeConfig(agentId);
8643
+ }
8644
+ acceptRuntimeConfig(agentId, config) {
8645
+ const desired = this.runtimeConfigs.get(agentId);
8646
+ const revision = config.runtimeConfigRevision ?? 0;
8647
+ const desiredRevision = desired?.runtimeConfigRevision ?? 0;
8648
+ if (desired && revision < desiredRevision)
8649
+ return "stale";
8650
+ if (desired && revision === desiredRevision) {
8651
+ if (this.runtimeConfigTuple(desired) !== this.runtimeConfigTuple(config)) {
8652
+ throw new Error(`Conflicting runtime config for ${agentId} at revision ${revision}`);
8653
+ }
8654
+ this.runtimeConfigs.set(agentId, config);
8655
+ return "idempotent";
8656
+ }
8657
+ this.runtimeConfigs.set(agentId, config);
8658
+ this.pendingRuntimeConfigUpdates.set(agentId, config);
8659
+ return "accepted";
8660
+ }
8661
+ runtimeConfigTuple(config) {
8662
+ return JSON.stringify({
8663
+ version: config.version,
8664
+ runtime: config.runtime,
8665
+ model: config.model,
8666
+ mode: config.mode,
8667
+ reasoningEffort: config.reasoningEffort ?? null,
8668
+ provider: config.provider ?? null,
8669
+ command: config.command ?? null,
8670
+ disallowedTools: config.disallowedTools ?? null,
8671
+ envVars: config.envVars ?? null
8672
+ });
8673
+ }
8674
+ runtimeLaunchTuple(config) {
8675
+ return JSON.stringify({
8676
+ version: config.version,
8677
+ runtime: config.runtime,
8678
+ model: config.model,
8679
+ mode: config.mode,
8680
+ provider: config.provider ?? null,
8681
+ command: config.command ?? null,
8682
+ disallowedTools: config.disallowedTools ?? null,
8683
+ envVars: config.envVars ?? null
8684
+ });
8685
+ }
8686
+ async convergeRuntimeConfig(agentId, restartOnFailure = true) {
8687
+ if (this.runtimeConfigApplyRunning.has(agentId))
8688
+ return "deferred";
8689
+ const session = this.sessions.get(agentId);
8690
+ if (!session)
8691
+ return "saved_for_start";
8692
+ this.runtimeConfigApplyRunning.add(agentId);
8693
+ try {
8694
+ while (this.sessions.get(agentId) === session) {
8695
+ const desired = this.pendingRuntimeConfigUpdates.get(agentId) ?? this.runtimeConfigs.get(agentId);
8696
+ if (!desired)
8697
+ return "idempotent";
8698
+ const desiredRevision = desired.runtimeConfigRevision ?? 0;
8699
+ const applied = this.appliedRuntimeConfigs.get(agentId);
8700
+ const appliedRevision = applied?.runtimeConfigRevision ?? -1;
8701
+ if (applied && desiredRevision <= appliedRevision) {
8702
+ this.pendingRuntimeConfigUpdates.delete(agentId);
8703
+ return desiredRevision === appliedRevision ? "idempotent" : "stale";
8704
+ }
8705
+ const canApplyNatively = applied && this.runtimeLaunchTuple(applied) === this.runtimeLaunchTuple(desired) && typeof session.updateSettings === "function";
8706
+ let result = { status: "unsupported" };
8707
+ if (canApplyNatively) {
8708
+ try {
8709
+ result = await session.updateSettings({
8710
+ reasoningEffort: desired.reasoningEffort ?? null
8711
+ });
8712
+ } catch (error) {
8713
+ this.log.warn("runtime config live apply threw; restarting at safe boundary", {
8714
+ agentId,
8715
+ revision: desiredRevision,
8716
+ error: String(error)
8717
+ });
8718
+ if (restartOnFailure)
8719
+ await this.restartForRuntimeConfig(agentId, session);
8720
+ return "saved_for_start";
8721
+ }
8722
+ }
8723
+ if (result.status !== "applied") {
8724
+ this.log.warn("runtime config live apply unavailable; restarting at safe boundary", {
8725
+ agentId,
8726
+ revision: desiredRevision,
8727
+ status: result.status,
8728
+ code: result.error?.code
8729
+ });
8730
+ if (restartOnFailure)
8731
+ await this.restartForRuntimeConfig(agentId, session);
8732
+ return "saved_for_start";
8733
+ }
8734
+ this.appliedRuntimeConfigs.set(agentId, desired);
8735
+ if ((this.pendingRuntimeConfigUpdates.get(agentId)?.runtimeConfigRevision ?? -1) === desiredRevision) {
8736
+ this.pendingRuntimeConfigUpdates.delete(agentId);
8737
+ }
8738
+ const latestRevision = this.runtimeConfigs.get(agentId)?.runtimeConfigRevision ?? 0;
8739
+ if (latestRevision <= desiredRevision) {
8740
+ this.dispatch({ type: "runtime_config_applied", agentId });
8741
+ return "applied";
8742
+ }
8743
+ }
8744
+ return "saved_for_start";
8745
+ } finally {
8746
+ this.runtimeConfigApplyRunning.delete(agentId);
8747
+ }
8748
+ }
8749
+ async restartForRuntimeConfig(agentId, session) {
8750
+ if (this.sessions.get(agentId) !== session)
8751
+ return;
8752
+ this.opts.timeline?.fenceSession(agentId);
8753
+ this.markResetting(agentId);
8754
+ await this.stop(agentId);
7987
8755
  }
7988
8756
  deliver(agentId, message) {
7989
8757
  const normalized = message.id ? message : {
7990
8758
  ...message,
7991
8759
  id: message.seq !== undefined ? `${agentId}:source:${message.seq}` : `${agentId}:synthetic:${this.nextDeliveryOrdinal++}`
7992
8760
  };
8761
+ if (this.sessions.has(agentId) && this.pendingRuntimeConfigUpdates.has(agentId)) {
8762
+ this.dispatch({
8763
+ type: "runtime_config_queued",
8764
+ agentId,
8765
+ message: normalized
8766
+ });
8767
+ return this.state.agents[agentId] !== undefined;
8768
+ }
7993
8769
  const effects = this.dispatch({ type: "wake", agentId, message: normalized, nowMs: this.now() });
7994
8770
  return effects.length > 0;
7995
8771
  }
@@ -8033,7 +8809,11 @@ class AgentProcessManager {
8033
8809
  this.emitErrorAudit(agentId, "reset", "resume_control_update_failed", "Reset aborted because resume control could not be persisted");
8034
8810
  throw new Error("Reset aborted because resume control could not be persisted");
8035
8811
  }
8036
- this.register(agentId, { runtimeConfig: opts.runtimeConfig, launchId: opts.launchId });
8812
+ this.register(agentId, {
8813
+ runtimeConfig: opts.runtimeConfig,
8814
+ launchId: opts.launchId,
8815
+ applyRuntimeConfig: false
8816
+ });
8037
8817
  if (!opts.forgetSession)
8038
8818
  this.opts.timeline?.fenceSession(agentId);
8039
8819
  this.abortCurrentTurn(agentId, opts.abortCause);
@@ -8134,6 +8914,9 @@ class AgentProcessManager {
8134
8914
  const agent = this.state.agents[agentId];
8135
8915
  return agent ? this.deriveActivity(agent) : null;
8136
8916
  }
8917
+ agentBackendId(agentId) {
8918
+ return this.liveBackendIds.get(agentId) ?? null;
8919
+ }
8137
8920
  statusProjection(nowMs) {
8138
8921
  return Object.values(this.state.agents).map((a) => ({
8139
8922
  agentId: a.agentId,
@@ -8659,6 +9442,7 @@ ${this.opts.wakePromptFooter}` : text;
8659
9442
  throw new Error(`AgentProcessManager: spawn for ${agentId} has no command`);
8660
9443
  const prompt = this.withFooter(first.text);
8661
9444
  const driver = this.opts.driverFor(agentId, this.runtimeConfigs.get(agentId));
9445
+ this.liveBackendIds.set(agentId, driver.id);
8662
9446
  const base = this.opts.baseContextFor(agentId);
8663
9447
  const configuredRuntime = this.runtimeConfigs.get(agentId) ?? base.config?.runtimeConfig;
8664
9448
  this.log.info("spawning agent", {
@@ -8779,7 +9563,10 @@ ${this.opts.wakePromptFooter}` : text;
8779
9563
  if (state.session && this.sessions.get(agentId) === state.session)
8780
9564
  this.sessions.delete(agentId);
8781
9565
  this.liveSessions.delete(agentId);
9566
+ if (this.activeSpawnState.get(agentId) === state)
9567
+ this.liveBackendIds.delete(agentId);
8782
9568
  if (this.activeSpawnState.get(agentId) === state) {
9569
+ this.appliedRuntimeConfigs.delete(agentId);
8783
9570
  this.activeSpawnState.delete(agentId);
8784
9571
  this.nonCleanEndMarker.delete(agentId);
8785
9572
  }
@@ -8827,6 +9614,10 @@ ${this.opts.wakePromptFooter}` : text;
8827
9614
  state.session = session;
8828
9615
  state.sessionInstanceId = session.sessionInstanceId;
8829
9616
  this.sessions.set(agentId, session);
9617
+ this.appliedRuntimeConfigs.set(agentId, runtimeConfig);
9618
+ if ((this.pendingRuntimeConfigUpdates.get(agentId)?.runtimeConfigRevision ?? -1) <= (runtimeConfig.runtimeConfigRevision ?? 0)) {
9619
+ this.pendingRuntimeConfigUpdates.delete(agentId);
9620
+ }
8830
9621
  this.dispatch({
8831
9622
  type: "attach_session",
8832
9623
  agentId,
@@ -9060,6 +9851,12 @@ ${this.opts.wakePromptFooter}` : text;
9060
9851
  runtime: runtimeId
9061
9852
  });
9062
9853
  }
9854
+ if (event.type === "token_usage") {
9855
+ this.opts.onTokenUsage?.({ agentId, backendId: runtimeId, usage: event.usage });
9856
+ }
9857
+ if (event.type === "rate_limits") {
9858
+ this.opts.onProviderQuota?.({ agentId, backendId: runtimeId, quota: event.quota });
9859
+ }
9063
9860
  if (event.type === "turn_started") {
9064
9861
  const timelineTurnOwner = {
9065
9862
  sessionInstanceId: event.sessionInstanceId,
@@ -9174,7 +9971,7 @@ ${this.opts.wakePromptFooter}` : text;
9174
9971
  this.logSessionEnded(agentId, "turn_end");
9175
9972
  const marker = this.nonCleanEndMarker.get(agentId);
9176
9973
  this.nonCleanEndMarker.delete(agentId);
9177
- this.dispatch(marker !== undefined ? {
9974
+ const completionEvent = marker !== undefined ? {
9178
9975
  type: "turn_completed",
9179
9976
  agentId,
9180
9977
  sessionInstanceId: event.sessionInstanceId,
@@ -9189,7 +9986,26 @@ ${this.opts.wakePromptFooter}` : text;
9189
9986
  sessionInstanceId: event.sessionInstanceId,
9190
9987
  nowMs: this.now(),
9191
9988
  turnId: event.turnId
9192
- }, owner);
9989
+ };
9990
+ if (this.pendingRuntimeConfigUpdates.has(agentId) && this.sessions.get(agentId) === owner.session) {
9991
+ this.convergeRuntimeConfig(agentId, false).then((result) => {
9992
+ if (result === "saved_for_start" && owner.session)
9993
+ this.markResetting(agentId);
9994
+ this.dispatch(completionEvent, owner);
9995
+ if (result === "saved_for_start" && owner.session) {
9996
+ this.restartForRuntimeConfig(agentId, owner.session);
9997
+ }
9998
+ }).catch((error) => {
9999
+ this.log.error("runtime config convergence failed", { agentId, error: String(error) });
10000
+ if (owner.session)
10001
+ this.markResetting(agentId);
10002
+ this.dispatch(completionEvent, owner);
10003
+ if (owner.session)
10004
+ this.restartForRuntimeConfig(agentId, owner.session);
10005
+ });
10006
+ return;
10007
+ }
10008
+ this.dispatch(completionEvent, owner);
9193
10009
  }
9194
10010
  }
9195
10011
  }
@@ -23489,6 +24305,7 @@ var exports_community_machine_schema = {};
23489
24305
  __export(exports_community_machine_schema, {
23490
24306
  communityMachineToken: () => communityMachineToken,
23491
24307
  communityMachineCredential: () => communityMachineCredential,
24308
+ communityMachineBackendQuota: () => communityMachineBackendQuota,
23492
24309
  communityMachine: () => communityMachine,
23493
24310
  communityDiagnosticReport: () => communityDiagnosticReport,
23494
24311
  communityBotBinding: () => communityBotBinding,
@@ -24085,7 +24902,7 @@ function sql(strings, ...params) {
24085
24902
  return new SQL([new StringChunk(str)]);
24086
24903
  }
24087
24904
  sql2.raw = raw;
24088
- function join9(chunks, separator) {
24905
+ function join10(chunks, separator) {
24089
24906
  const result = [];
24090
24907
  for (const [i, chunk] of chunks.entries()) {
24091
24908
  if (i > 0 && separator !== undefined) {
@@ -24095,7 +24912,7 @@ function sql(strings, ...params) {
24095
24912
  }
24096
24913
  return new SQL(result);
24097
24914
  }
24098
- sql2.join = join9;
24915
+ sql2.join = join10;
24099
24916
  function identifier(value) {
24100
24917
  return new Name(value);
24101
24918
  }
@@ -25092,6 +25909,8 @@ var user = sqliteTable("user", {
25092
25909
  email: text("email").unique().notNull(),
25093
25910
  emailVerified: integer2("emailVerified", { mode: "boolean" }),
25094
25911
  image: text("image"),
25912
+ avatarVersion: integer2("avatarVersion").notNull().default(0),
25913
+ avatarObjectKey: text("avatarObjectKey"),
25095
25914
  createdAt: text("createdAt").notNull().$defaultFn(() => new Date().toISOString()),
25096
25915
  updatedAt: text("updatedAt").notNull().$defaultFn(() => new Date().toISOString()),
25097
25916
  isBot: integer2("isBot", { mode: "boolean" }).notNull().default(false),
@@ -25765,8 +26584,23 @@ var communityBotBinding = sqliteTable("community_bot_binding", {
25765
26584
  runtime: text("runtime").notNull(),
25766
26585
  instruction: text("instruction").notNull().default(""),
25767
26586
  modelName: text("model_name"),
26587
+ reasoningEffort: text("reasoning_effort"),
26588
+ runtimeConfigRevision: integer2("runtime_config_revision").notNull().default(0),
25768
26589
  createdAt: text("created_at").notNull().$defaultFn(() => new Date().toISOString())
25769
26590
  }, (t) => [index("idx_community_bot_binding_machine").on(t.machineId)]);
26591
+ var communityMachineBackendQuota = sqliteTable("community_machine_backend_quota", {
26592
+ machineId: text("machine_id").notNull().references(() => communityMachine.id, { onDelete: "cascade" }),
26593
+ agentBackendId: text("agent_backend_id").$type().notNull(),
26594
+ sourceEpoch: text("source_epoch").notNull(),
26595
+ status: text("status").$type().notNull(),
26596
+ planName: text("plan_name"),
26597
+ freshForSeconds: integer2("fresh_for_seconds"),
26598
+ limits: text("limits", { mode: "json" }).$type(),
26599
+ errorCode: text("error_code"),
26600
+ retryable: integer2("retryable", { mode: "boolean" }),
26601
+ observedAt: text("observed_at").notNull(),
26602
+ updatedAt: text("updated_at").notNull()
26603
+ }, (t) => [primaryKey({ columns: [t.machineId, t.agentBackendId] })]);
25770
26604
  var communityAgentRunnerKey = sqliteTable("community_agent_runner_key", {
25771
26605
  id: text("id").primaryKey().$defaultFn(() => "crkid_" + nanoid3()),
25772
26606
  userId: text("user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
@@ -25945,6 +26779,11 @@ var HostCommandSchema = exports_external.discriminatedUnion("type", [
25945
26779
  config: exports_external.unknown(),
25946
26780
  launchId: exports_external.string().min(1)
25947
26781
  }),
26782
+ exports_external.object({
26783
+ type: exports_external.literal("agent:runtime_config_update"),
26784
+ agentId: exports_external.string().min(1),
26785
+ config: exports_external.unknown()
26786
+ }),
25948
26787
  exports_external.object({
25949
26788
  type: exports_external.literal("machine:reset_all"),
25950
26789
  resets: exports_external.array(exports_external.object({
@@ -26051,7 +26890,8 @@ class AgentRouter {
26051
26890
  version: r.version,
26052
26891
  status: r.status ?? "healthy",
26053
26892
  lastError: r.lastError,
26054
- lastErrorAt: r.lastErrorAt
26893
+ lastErrorAt: r.lastErrorAt,
26894
+ reasoning: r.reasoning
26055
26895
  });
26056
26896
  }
26057
26897
  }
@@ -26060,7 +26900,7 @@ class AgentRouter {
26060
26900
  this.opts.channel.onResync?.(() => ({
26061
26901
  ready: this.buildReady(),
26062
26902
  sessions: this.opts.manager.liveSessionReports(),
26063
- activities: this.opts.manager.liveAgentActivities()
26903
+ activities: this.opts.resyncActivities ? this.opts.resyncActivities() : this.opts.manager.liveAgentActivities()
26064
26904
  }));
26065
26905
  await this.opts.channel.reportReady(this.buildReady());
26066
26906
  }
@@ -26073,7 +26913,8 @@ class AgentRouter {
26073
26913
  platform: this.opts.platform,
26074
26914
  arch: this.opts.arch,
26075
26915
  osRelease: this.opts.osRelease,
26076
- daemonVersion: this.opts.daemonVersion
26916
+ daemonVersion: this.opts.daemonVersion,
26917
+ ...this.opts.providerQuotas ? { providerQuotas: this.opts.providerQuotas() } : {}
26077
26918
  };
26078
26919
  }
26079
26920
  healthyRuntimeIds() {
@@ -26295,6 +27136,27 @@ class AgentRouter {
26295
27136
  rewakePrompt: MODEL_SWITCH_REWAKE_PROMPT
26296
27137
  }));
26297
27138
  break;
27139
+ case "agent:runtime_config_update": {
27140
+ this.log.info("agent:runtime_config_update received", {
27141
+ agentId: cmd.agentId,
27142
+ revision: cmd.config.runtimeConfigRevision ?? 0
27143
+ });
27144
+ try {
27145
+ const result = await this.opts.manager.updateRuntimeConfig(cmd.agentId, cmd.config);
27146
+ this.log.info("agent:runtime_config_update accepted", {
27147
+ agentId: cmd.agentId,
27148
+ revision: cmd.config.runtimeConfigRevision ?? 0,
27149
+ result
27150
+ });
27151
+ } catch (err) {
27152
+ this.log.warn("agent:runtime_config_update rejected", {
27153
+ agentId: cmd.agentId,
27154
+ revision: cmd.config.runtimeConfigRevision ?? 0,
27155
+ error: err instanceof Error ? err.message : String(err)
27156
+ });
27157
+ }
27158
+ break;
27159
+ }
26298
27160
  case "agent:stop":
26299
27161
  this.log.info("agent:stop received", { agentId: cmd.agentId });
26300
27162
  try {
@@ -26779,7 +27641,7 @@ function joinPath(basePath, reqUrl) {
26779
27641
  return base + reqPath || "/";
26780
27642
  }
26781
27643
  // src/daemon/createDaemon.ts
26782
- import { homedir as homedir4 } from "os";
27644
+ import { homedir as homedir5 } from "os";
26783
27645
  import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync7 } from "node:fs";
26784
27646
 
26785
27647
  // src/util/rotatingFileSink.ts
@@ -27048,12 +27910,13 @@ function writeStatusFile(path9, snapshot) {
27048
27910
  } catch {}
27049
27911
  }
27050
27912
  // ../shared/src/constants/community.ts
27913
+ var MAX_FOLDER_NAME_LENGTH = 100;
27051
27914
  var MAX_PROFILE_NAME_LENGTH = 100;
27052
27915
  var MAX_PROFILE_ABOUT_LENGTH = 1000;
27053
27916
  var MAX_MESSAGE_CONTENT_LENGTH = 4000;
27054
27917
  var MAX_ATTACHMENTS_PER_MESSAGE = 10;
27055
27918
  var MAX_ATTACHMENT_SIZE_BYTES = 25 * 1024 * 1024;
27056
- var MAX_ATTACHMENT_THUMBNAIL_SIZE_BYTES = 50 * 1024;
27919
+ var MAX_ATTACHMENT_THUMBNAIL_SIZE_BYTES = 512 * 1024;
27057
27920
  var MAX_SERVER_ICON_SIZE_BYTES = 5 * 1024 * 1024;
27058
27921
  var ALLOWED_ICON_MIME_TYPES = [
27059
27922
  "image/png",
@@ -27062,6 +27925,92 @@ var ALLOWED_ICON_MIME_TYPES = [
27062
27925
  "image/gif"
27063
27926
  ];
27064
27927
  var MAX_ICON_SOURCE_FILE_SIZE_BYTES = 15 * 1024 * 1024;
27928
+ // ../shared/src/provider-telemetry.ts
27929
+ var safeToken = exports_external.number().int().min(0).max(Number.MAX_SAFE_INTEGER);
27930
+ var boundedText = exports_external.string().min(1).refine((value) => new TextEncoder().encode(value).length <= 64, { message: "must be at most 64 UTF-8 bytes" });
27931
+ var DailyUsageMetricSchema = safeToken.nullable();
27932
+ var DailyUsageSnapshotSchema = exports_external.object({
27933
+ botId: exports_external.string().min(1),
27934
+ day: exports_external.string().regex(/^\d{4}-\d{2}-\d{2}$/),
27935
+ metrics: exports_external.object({
27936
+ input: DailyUsageMetricSchema,
27937
+ output: DailyUsageMetricSchema,
27938
+ cache: DailyUsageMetricSchema
27939
+ }).strict()
27940
+ }).strict();
27941
+ var QuotaProductIdentitySchema = exports_external.discriminatedUnion("kind", [
27942
+ exports_external.object({ kind: exports_external.literal("reported"), id: boundedText, displayName: boundedText }).strict(),
27943
+ exports_external.object({ kind: exports_external.literal("unknown"), displayName: boundedText }).strict()
27944
+ ]);
27945
+ var QuotaModelIdentitySchema = exports_external.discriminatedUnion("kind", [
27946
+ exports_external.object({ kind: exports_external.literal("reported"), id: boundedText }).strict(),
27947
+ exports_external.object({ kind: exports_external.literal("not_applicable") }).strict(),
27948
+ exports_external.object({ kind: exports_external.literal("unknown") }).strict()
27949
+ ]);
27950
+ var QuotaWindowIdentitySchema = exports_external.discriminatedUnion("kind", [
27951
+ exports_external.object({
27952
+ kind: exports_external.literal("rolling"),
27953
+ durationSeconds: exports_external.number().int().positive().max(Number.MAX_SAFE_INTEGER),
27954
+ displayName: boundedText
27955
+ }).strict(),
27956
+ exports_external.object({
27957
+ kind: exports_external.literal("calendar"),
27958
+ period: exports_external.enum(["day", "week", "month"]),
27959
+ displayName: boundedText
27960
+ }).strict(),
27961
+ exports_external.object({
27962
+ kind: exports_external.literal("provider_defined"),
27963
+ id: boundedText,
27964
+ durationSeconds: exports_external.number().int().positive().max(Number.MAX_SAFE_INTEGER).optional(),
27965
+ displayName: boundedText
27966
+ }).strict()
27967
+ ]);
27968
+ var QuotaLimitSchema = exports_external.object({
27969
+ bucket: exports_external.object({
27970
+ limitId: boundedText,
27971
+ product: QuotaProductIdentitySchema,
27972
+ model: QuotaModelIdentitySchema,
27973
+ window: QuotaWindowIdentitySchema
27974
+ }).strict(),
27975
+ usedPercent: exports_external.number().finite().min(0).max(100),
27976
+ resetsAt: exports_external.string().datetime({ offset: true }).optional()
27977
+ }).strict();
27978
+ function quotaIdentity(limit) {
27979
+ const { product, model, window: window2, limitId } = limit.bucket;
27980
+ const productKey = product.kind === "reported" ? `reported:${product.id}` : "unknown";
27981
+ const modelKey = model.kind === "reported" ? `reported:${model.id}` : model.kind;
27982
+ const windowKey = window2.kind === "rolling" ? `rolling:${window2.durationSeconds}` : window2.kind === "calendar" ? `calendar:${window2.period}` : `provider_defined:${window2.id}:${window2.durationSeconds === undefined ? "absent" : window2.durationSeconds}`;
27983
+ return `${productKey}\x00${modelKey}\x00${windowKey}\x00${limitId}`;
27984
+ }
27985
+ var AvailableQuotaObservationSchema = exports_external.object({
27986
+ status: exports_external.literal("available"),
27987
+ sourceEpoch: exports_external.string().regex(/^[A-Za-z0-9_-]{22}$/),
27988
+ planName: boundedText.optional(),
27989
+ freshForSeconds: exports_external.number().int().positive().max(86400),
27990
+ limits: exports_external.array(QuotaLimitSchema).min(1).max(8)
27991
+ }).strict().superRefine((value, ctx) => {
27992
+ const identities = new Set;
27993
+ for (const [index2, limit] of value.limits.entries()) {
27994
+ const identity = quotaIdentity(limit);
27995
+ if (identities.has(identity)) {
27996
+ ctx.addIssue({ code: "custom", message: "duplicate quota bucket identity", path: ["limits", index2] });
27997
+ }
27998
+ identities.add(identity);
27999
+ }
28000
+ });
28001
+ var ProviderQuotaObservationSchema = exports_external.union([
28002
+ AvailableQuotaObservationSchema,
28003
+ exports_external.object({
28004
+ status: exports_external.literal("error"),
28005
+ sourceEpoch: exports_external.string().regex(/^[A-Za-z0-9_-]{22}$/),
28006
+ code: exports_external.enum(["unavailable", "unauthorized", "network", "provider_error", "invalid_response"]),
28007
+ retryable: exports_external.boolean()
28008
+ }).strict()
28009
+ ]);
28010
+ var ProviderQuotaSnapshotSchema = exports_external.object({
28011
+ agentBackendId: exports_external.enum(["claude", "codex"]),
28012
+ observation: ProviderQuotaObservationSchema
28013
+ }).strict();
27065
28014
  // ../shared/src/utils/slug.ts
27066
28015
  init_nanoid();
27067
28016
  var slugId = customAlphabet("0123456789abcdefghijklmnopqrstuvwxyz");
@@ -27218,10 +28167,13 @@ var ActivateTokenRuntimeSchema = exports_external.object({
27218
28167
  type: exports_external.string().min(1),
27219
28168
  version: exports_external.string().optional().default("")
27220
28169
  });
28170
+ var COMMUNITY_RUNTIME_ID_MAX = 64;
28171
+ var COMMUNITY_RUNTIME_VERSION_MAX = 64;
28172
+ var COMMUNITY_RUNTIME_LIST_MAX = 64;
27221
28173
  var ActivateTokenRequestSchema = exports_external.object({
27222
28174
  token: exports_external.string().min(1),
27223
28175
  hostname: exports_external.string().min(1),
27224
- runtimes: exports_external.array(ActivateTokenRuntimeSchema).min(1)
28176
+ runtimes: exports_external.array(ActivateTokenRuntimeSchema).min(1).max(COMMUNITY_RUNTIME_LIST_MAX)
27225
28177
  });
27226
28178
  var RegisterDaemonRequestSchema = exports_external.object({
27227
28179
  workspace_id: exports_external.string().min(1).optional(),
@@ -27229,7 +28181,7 @@ var RegisterDaemonRequestSchema = exports_external.object({
27229
28181
  device_name: exports_external.string().optional().default(""),
27230
28182
  cli_version: exports_external.string().optional().default(""),
27231
28183
  workspaces_root: exports_external.string().optional().default(""),
27232
- runtimes: exports_external.array(DaemonRuntimeItemSchema).min(1)
28184
+ runtimes: exports_external.array(DaemonRuntimeItemSchema).min(1).max(COMMUNITY_RUNTIME_LIST_MAX)
27233
28185
  });
27234
28186
  var DeregisterRequestSchema = exports_external.object({
27235
28187
  daemon_id: exports_external.string().min(1)
@@ -27572,16 +28524,61 @@ var CreateThreadRequestSchema = exports_external.object({
27572
28524
  content: exports_external.string().optional().default(""),
27573
28525
  attachment_ids: exports_external.array(exports_external.string()).optional()
27574
28526
  });
27575
- var COMMUNITY_RUNTIME_ID_MAX = 64;
27576
- var COMMUNITY_RUNTIME_VERSION_MAX = 64;
27577
- var COMMUNITY_RUNTIME_LIST_MAX = 64;
27578
28527
  var RUNTIME_ID_RE = /^[A-Za-z0-9._@/-]+$/;
28528
+ var REASONING_EFFORT_RE = /^[A-Za-z0-9._-]+$/;
28529
+ var COMMUNITY_REASONING_EFFORT_MAX = 32;
28530
+ var COMMUNITY_REASONING_DESCRIPTION_MAX = 256;
28531
+ var COMMUNITY_REASONING_OPTIONS_MAX = 16;
28532
+ var COMMUNITY_REASONING_MODELS_MAX = 64;
28533
+ var ReasoningEffortSchema = exports_external.string().min(1).max(COMMUNITY_REASONING_EFFORT_MAX).regex(REASONING_EFFORT_RE, "invalid reasoning effort charset");
28534
+ var RuntimeReasoningOptionSchema = exports_external.object({
28535
+ value: ReasoningEffortSchema,
28536
+ description: exports_external.string().max(COMMUNITY_REASONING_DESCRIPTION_MAX).optional()
28537
+ });
28538
+ var RuntimeReasoningModelSchema = exports_external.object({
28539
+ id: exports_external.string().min(1).max(100),
28540
+ supportedReasoningEfforts: exports_external.array(exports_external.unknown()).max(COMMUNITY_REASONING_OPTIONS_MAX).transform((options) => {
28541
+ const seen = new Set;
28542
+ return options.flatMap((candidate) => {
28543
+ const parsed = RuntimeReasoningOptionSchema.safeParse(candidate);
28544
+ if (!parsed.success)
28545
+ return [];
28546
+ const option = parsed.data;
28547
+ if (seen.has(option.value))
28548
+ return [];
28549
+ seen.add(option.value);
28550
+ return [option];
28551
+ });
28552
+ }),
28553
+ defaultReasoningEffort: ReasoningEffortSchema.optional().catch(undefined)
28554
+ }).transform((model) => {
28555
+ const { defaultReasoningEffort, ...rest } = model;
28556
+ return defaultReasoningEffort !== undefined && model.supportedReasoningEfforts.some((option) => option.value === defaultReasoningEffort) ? { ...rest, defaultReasoningEffort } : rest;
28557
+ });
28558
+ var RuntimeReasoningCatalogSchema = exports_external.object({
28559
+ updateMode: exports_external.enum(["live_next_turn", "context_preserving_restart", "unsupported"]),
28560
+ defaultModelId: exports_external.string().min(1).max(100).optional().catch(undefined),
28561
+ models: exports_external.array(exports_external.unknown()).max(COMMUNITY_REASONING_MODELS_MAX).transform((models) => {
28562
+ const seen = new Set;
28563
+ return models.flatMap((candidate) => {
28564
+ const parsed = RuntimeReasoningModelSchema.safeParse(candidate);
28565
+ if (!parsed.success)
28566
+ return [];
28567
+ const model = parsed.data;
28568
+ if (seen.has(model.id))
28569
+ return [];
28570
+ seen.add(model.id);
28571
+ return [model];
28572
+ });
28573
+ })
28574
+ });
27579
28575
  var CommunityMachineRuntimeSchema = exports_external.object({
27580
28576
  id: exports_external.string().min(1).max(COMMUNITY_RUNTIME_ID_MAX).regex(RUNTIME_ID_RE, "invalid runtime id charset"),
27581
28577
  version: exports_external.string().max(COMMUNITY_RUNTIME_VERSION_MAX).optional(),
27582
28578
  status: exports_external.enum(["healthy", "unhealthy"]).catch("healthy").default("healthy"),
27583
28579
  lastError: exports_external.string().max(128).optional(),
27584
- lastErrorAt: exports_external.string().optional()
28580
+ lastErrorAt: exports_external.string().optional(),
28581
+ reasoning: RuntimeReasoningCatalogSchema.optional().catch(undefined)
27585
28582
  });
27586
28583
  var CommunityMachineRuntimeListSchema = exports_external.array(CommunityMachineRuntimeSchema).max(COMMUNITY_RUNTIME_LIST_MAX).transform((list) => {
27587
28584
  const seen = new Set;
@@ -27594,6 +28591,17 @@ var CommunityMachineRuntimeListSchema = exports_external.array(CommunityMachineR
27594
28591
  }
27595
28592
  return out;
27596
28593
  });
28594
+ var CommunityRunningAgentListSchema = exports_external.array(exports_external.string()).transform((list) => {
28595
+ const seen = new Set;
28596
+ const out = [];
28597
+ for (const id of list) {
28598
+ if (seen.has(id))
28599
+ continue;
28600
+ seen.add(id);
28601
+ out.push(id);
28602
+ }
28603
+ return out;
28604
+ });
27597
28605
  var CommunityMachineSummarySchema = exports_external.object({
27598
28606
  id: exports_external.string(),
27599
28607
  hostname: exports_external.string(),
@@ -27617,16 +28625,17 @@ var HostReadyMessageSchema = exports_external.object({
27617
28625
  type: exports_external.literal("ready"),
27618
28626
  runtimeReport: CommunityMachineRuntimeListSchema,
27619
28627
  capabilities: exports_external.array(exports_external.string().min(1).max(64)).max(16).optional().default([]),
27620
- runningAgents: exports_external.array(exports_external.string()).default([]),
28628
+ runningAgents: CommunityRunningAgentListSchema.default([]),
27621
28629
  hostname: exports_external.string().optional(),
27622
28630
  platform: exports_external.string().optional(),
27623
28631
  arch: exports_external.string().optional(),
27624
28632
  osRelease: exports_external.string().optional(),
27625
- daemonVersion: exports_external.string().optional()
28633
+ daemonVersion: exports_external.string().optional(),
28634
+ providerQuotas: exports_external.array(ProviderQuotaSnapshotSchema).max(2).optional().default([])
27626
28635
  });
27627
28636
  var CommunityDaemonReadySchema = exports_external.object({
27628
28637
  runtimeReport: CommunityMachineRuntimeListSchema.optional(),
27629
- runningAgents: exports_external.array(exports_external.string()).default([]),
28638
+ runningAgents: CommunityRunningAgentListSchema.default([]),
27630
28639
  hostname: exports_external.string().optional(),
27631
28640
  os: exports_external.string().optional(),
27632
28641
  arch: exports_external.string().optional(),
@@ -27643,7 +28652,9 @@ var SessionErrorFrameSchema = exports_external.object({
27643
28652
  var AgentActivityMessageSchema = exports_external.object({
27644
28653
  type: exports_external.literal("agent_activity"),
27645
28654
  agentId: exports_external.string(),
27646
- state: exports_external.enum(["idle", "starting", "running", "stopping"])
28655
+ state: exports_external.enum(["idle", "starting", "running", "stopping"]),
28656
+ dailyUsage: exports_external.array(DailyUsageSnapshotSchema).max(7).optional(),
28657
+ quota: ProviderQuotaSnapshotSchema.optional()
27647
28658
  });
27648
28659
  var AgentTypingMessageSchema = exports_external.object({
27649
28660
  type: exports_external.literal("agent_typing"),
@@ -27718,15 +28729,17 @@ var CommunityBotCreateRequestSchema = exports_external.object({
27718
28729
  machineId: exports_external.string().min(1),
27719
28730
  runtime: exports_external.string().min(1),
27720
28731
  image: BotImageUrlSchema.optional(),
27721
- model: exports_external.string().trim().min(1).max(100).nullable().optional()
28732
+ model: exports_external.string().trim().min(1).max(100).nullable().optional(),
28733
+ reasoningEffort: ReasoningEffortSchema.nullable().optional()
27722
28734
  });
27723
28735
  var CommunityBotPatchRequestSchema = exports_external.object({
27724
28736
  name: exports_external.string().trim().min(COMMUNITY_BOT_NAME_MIN).max(COMMUNITY_BOT_NAME_MAX).refine(isMentionSafeName, { message: MENTION_SAFE_NAME_MSG }).optional(),
27725
28737
  description: exports_external.string().max(COMMUNITY_BOT_DESCRIPTION_MAX).optional(),
27726
28738
  image: BotImageUrlSchema.nullable().optional(),
27727
28739
  model: exports_external.string().trim().min(1).max(100).nullable().optional(),
27728
- runtime: exports_external.string().trim().min(1).max(COMMUNITY_RUNTIME_ID_MAX).optional()
27729
- }).refine((v) => v.name !== undefined || v.description !== undefined || v.image !== undefined || v.runtime !== undefined || ("model" in v), {
28740
+ runtime: exports_external.string().trim().min(1).max(COMMUNITY_RUNTIME_ID_MAX).optional(),
28741
+ reasoningEffort: ReasoningEffortSchema.nullable().optional()
28742
+ }).refine((v) => v.name !== undefined || v.description !== undefined || v.image !== undefined || v.runtime !== undefined || ("reasoningEffort" in v) || ("model" in v), {
27730
28743
  message: "at least one field must be provided"
27731
28744
  });
27732
28745
  var CommunityBotAddToServerRequestSchema = exports_external.object({
@@ -27928,6 +28941,7 @@ __export(exports_community_schema, {
27928
28941
  communityChannelMember: () => communityChannelMember,
27929
28942
  communityChannel: () => communityChannel,
27930
28943
  communityCategory: () => communityCategory,
28944
+ communityBotDailyTokenUsage: () => communityBotDailyTokenUsage,
27931
28945
  communityBotDailyActivity: () => communityBotDailyActivity,
27932
28946
  communityBotApprovalRequest: () => communityBotApprovalRequest,
27933
28947
  communityBotActivityEvent: () => communityBotActivityEvent,
@@ -28180,6 +29194,17 @@ var communityBotDailyActivity = sqliteTable("community_bot_daily_activity", {
28180
29194
  handledCount: integer2("handled_count").notNull().default(0),
28181
29195
  sentCount: integer2("sent_count").notNull().default(0)
28182
29196
  }, (t) => [primaryKey({ columns: [t.botId, t.day] })]);
29197
+ var communityBotDailyTokenUsage = sqliteTable("community_bot_daily_token_usage", {
29198
+ botId: text("bot_id").notNull().references(() => user.id, { onDelete: "cascade" }),
29199
+ day: text("day").notNull(),
29200
+ inputTokens: integer2("input_tokens"),
29201
+ outputTokens: integer2("output_tokens"),
29202
+ cacheTokens: integer2("cache_tokens"),
29203
+ updatedAt: text("updated_at").notNull()
29204
+ }, (t) => [
29205
+ primaryKey({ columns: [t.botId, t.day] }),
29206
+ index("idx_community_bot_daily_token_usage_day").on(t.day)
29207
+ ]);
28183
29208
  var communityMessageMark = sqliteTable("community_message_mark", {
28184
29209
  id: text("id").primaryKey().$defaultFn(() => nanoid3()),
28185
29210
  userId: text("user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
@@ -28304,7 +29329,8 @@ var listedMessageProjection = {
28304
29329
  clientNonce: communityMessage.clientNonce,
28305
29330
  authorName: user.name,
28306
29331
  authorEmail: user.email,
28307
- authorImage: user.image
29332
+ authorImage: user.image,
29333
+ authorAvatarVersion: user.avatarVersion
28308
29334
  };
28309
29335
 
28310
29336
  // ../shared/src/db/queries/user.ts
@@ -28314,6 +29340,7 @@ var publicUserColumns = {
28314
29340
  email: user.email,
28315
29341
  emailVerified: user.emailVerified,
28316
29342
  image: user.image,
29343
+ avatarVersion: user.avatarVersion,
28317
29344
  createdAt: user.createdAt,
28318
29345
  updatedAt: user.updatedAt,
28319
29346
  discriminator: user.discriminator
@@ -28324,6 +29351,12 @@ var internalUserColumns = {
28324
29351
  ownerUserId: user.ownerUserId,
28325
29352
  deletedAt: user.deletedAt
28326
29353
  };
29354
+ var avatarPublishColumns = {
29355
+ id: user.id,
29356
+ image: user.image,
29357
+ avatarVersion: user.avatarVersion,
29358
+ avatarObjectKey: user.avatarObjectKey
29359
+ };
28327
29360
 
28328
29361
  // ../shared/src/db/queries/community/channel.ts
28329
29362
  var CHANNEL_COLUMNS = {
@@ -28376,7 +29409,8 @@ var friendApprovalProfileSchema = exports_external.strictObject({
28376
29409
  id: string4,
28377
29410
  name: string4,
28378
29411
  discriminator: string4,
28379
- image: nullableString
29412
+ image: nullableString,
29413
+ avatarVersion: exports_external.number().int().nonnegative()
28380
29414
  });
28381
29415
  var FriendApprovalPayloadSchema = exports_external.strictObject({
28382
29416
  friendshipId: string4,
@@ -28402,6 +29436,7 @@ var messageSchema = exports_external.strictObject({
28402
29436
  authorId: string4,
28403
29437
  authorName: string4,
28404
29438
  authorAvatar: string4.optional(),
29439
+ authorAvatarVersion: exports_external.number().int().nonnegative(),
28405
29440
  content: string4,
28406
29441
  type: exports_external.enum(["chat", "system"]),
28407
29442
  systemKind: exports_external.literal("thread").optional(),
@@ -28409,6 +29444,7 @@ var messageSchema = exports_external.strictObject({
28409
29444
  replyToId: nullableString.optional(),
28410
29445
  replyTo: exports_external.strictObject({
28411
29446
  id: string4,
29447
+ authorId: string4.optional(),
28412
29448
  authorName: string4,
28413
29449
  text: string4,
28414
29450
  deleted: exports_external.boolean().optional()
@@ -28603,6 +29639,7 @@ var communityMemberJoinSchema = exports_external.strictObject({
28603
29639
  name: string4,
28604
29640
  discriminator: string4,
28605
29641
  avatar: string4.optional(),
29642
+ avatarVersion: exports_external.number().int().nonnegative(),
28606
29643
  role: string4,
28607
29644
  joinedAt: string4
28608
29645
  })
@@ -28705,6 +29742,22 @@ var communityStatusUpdateSchema = exports_external.strictObject({
28705
29742
  statusEmoji: nullableString,
28706
29743
  statusText: nullableString
28707
29744
  });
29745
+ var communityIdentityUpdateSchema = exports_external.strictObject({
29746
+ type: exports_external.literal("community:identity.update"),
29747
+ userId: string4,
29748
+ avatar: string4,
29749
+ avatarVersion: exports_external.number().int().positive()
29750
+ });
29751
+ var communityProfileUpdateSchema = exports_external.strictObject({
29752
+ type: exports_external.literal("community:profile.update"),
29753
+ userId: string4,
29754
+ name: string4,
29755
+ discriminator: string4,
29756
+ aboutMe: string4,
29757
+ bannerColor: nullableString,
29758
+ kind: exports_external.enum(["human", "bot"]),
29759
+ ownerUserId: nullableString
29760
+ });
28708
29761
  var machineRuntimeSchema = CommunityMachineRuntimeSchema.strict();
28709
29762
  var CommunityMachineSummarySchema2 = exports_external.strictObject({
28710
29763
  id: string4,
@@ -28793,6 +29846,8 @@ var CommunityWsEventDiscriminatedSchema = exports_external.discriminatedUnion("t
28793
29846
  communityInboxChangedSchema,
28794
29847
  communityPresenceUpdateSchema,
28795
29848
  communityStatusUpdateSchema,
29849
+ communityIdentityUpdateSchema,
29850
+ communityProfileUpdateSchema,
28796
29851
  communityMachineCreatedSchema,
28797
29852
  communityMachineStatusSchema,
28798
29853
  communityMachineUpdatedSchema,
@@ -28839,6 +29894,8 @@ var WS_EVENTS = {
28839
29894
  INBOX_CHANGED: "community:inbox.changed",
28840
29895
  PRESENCE_UPDATE: "community:presence.update",
28841
29896
  STATUS_UPDATE: "community:status.update",
29897
+ IDENTITY_UPDATE: "community:identity.update",
29898
+ PROFILE_UPDATE: "community:profile.update",
28842
29899
  MACHINE_CREATED: "community:machine.created",
28843
29900
  MACHINE_STATUS: "community:machine.status",
28844
29901
  MACHINE_UPDATED: "community:machine.updated",
@@ -28915,6 +29972,43 @@ var COMMUNITY_BROWSER_EVENT_BATCH_ENVELOPE_BYTES = 1024;
28915
29972
  var COMMUNITY_BROWSER_EVENT_BATCH_MAX_BYTES = MESSAGE_DELIVERY_MAX_EVENTS_PER_USER * COMMUNITY_BROWSER_EVENT_MAX_BYTES + COMMUNITY_BROWSER_EVENT_BATCH_ENVELOPE_BYTES;
28916
29973
  // ../shared/src/db/index.ts
28917
29974
  var allSchema = { ...exports_schema, ...exports_community_schema, ...exports_community_machine_schema };
29975
+ // ../shared/src/community-server-rail.ts
29976
+ var MAX_SERVER_RAIL_COMMANDS = 3;
29977
+ var idSchema = exports_external.string().trim().min(1);
29978
+ var idsSchema = exports_external.array(idSchema);
29979
+ var reorderServersSchema = exports_external.strictObject({
29980
+ kind: exports_external.literal("reorder-servers"),
29981
+ serverIds: idsSchema
29982
+ });
29983
+ var reorderFoldersSchema = exports_external.strictObject({
29984
+ kind: exports_external.literal("reorder-folders"),
29985
+ folderIds: idsSchema
29986
+ });
29987
+ var replaceFolderItemsSchema = exports_external.strictObject({
29988
+ kind: exports_external.literal("replace-folder-items"),
29989
+ folderId: idSchema,
29990
+ serverIds: idsSchema.min(1)
29991
+ });
29992
+ var deleteFolderSchema = exports_external.strictObject({
29993
+ kind: exports_external.literal("delete-folder"),
29994
+ folderId: idSchema
29995
+ });
29996
+ var createFolderSchema = exports_external.strictObject({
29997
+ kind: exports_external.literal("create-folder"),
29998
+ clientId: idSchema,
29999
+ name: exports_external.string().trim().min(1).max(MAX_FOLDER_NAME_LENGTH),
30000
+ serverIds: idsSchema.min(1)
30001
+ });
30002
+ var serverRailCommandSchema = exports_external.discriminatedUnion("kind", [
30003
+ reorderServersSchema,
30004
+ reorderFoldersSchema,
30005
+ replaceFolderItemsSchema,
30006
+ deleteFolderSchema,
30007
+ createFolderSchema
30008
+ ]);
30009
+ var serverRailCommitRequestSchema = exports_external.strictObject({
30010
+ commands: exports_external.array(serverRailCommandSchema).min(1).max(MAX_SERVER_RAIL_COMMANDS)
30011
+ });
28918
30012
  // ../shared/src/db/queries/task.ts
28919
30013
  var DEFAULT_STALE_SECONDS = Number(process.env.ALOOK_STALE_DISPATCH_TIMEOUT_S) || 20;
28920
30014
  var DEFAULT_STALE_RUNNING_SECONDS = Number(process.env.ALOOK_STALE_RUNNING_TIMEOUT_S) || 3600;
@@ -29362,23 +30456,43 @@ class WsControlChannel {
29362
30456
  this.ws.send(JSON.stringify(frame));
29363
30457
  }
29364
30458
  resyncOnConnect() {
30459
+ const sendActivities = (activities, counts) => {
30460
+ for (const activity of activities) {
30461
+ this.sendFrame({ type: "agent_activity", ...activity });
30462
+ }
30463
+ this.log.info("resync sent", {
30464
+ ready: counts.ready,
30465
+ sessions: counts.sessions,
30466
+ activities: activities.length,
30467
+ pendingAuditEvents: this.pendingBotAuditEvents.size
30468
+ });
30469
+ };
29365
30470
  if (this.resyncProvider) {
29366
- const { ready, sessions, activities } = this.resyncProvider();
29367
- this.sendFrame({ type: "ready", ...ready });
29368
- for (const s of sessions)
29369
- this.sendFrame({ type: "agent_session", ...s });
29370
- const liveActivities = activities ?? [];
29371
- for (const a of liveActivities)
29372
- this.sendFrame({ type: "agent_activity", ...a });
30471
+ const socketAtStart = this.ws;
30472
+ const snapshot = this.resyncProvider();
30473
+ this.sendFrame({ type: "ready", ...snapshot.ready });
30474
+ for (const session2 of snapshot.sessions) {
30475
+ this.sendFrame({ type: "agent_session", ...session2 });
30476
+ }
29373
30477
  for (const frame of this.pendingBotAuditEvents.values())
29374
30478
  this.sendFrame(frame);
29375
30479
  this.scheduleAuditRetry();
29376
- this.log.info("resync sent", {
29377
- ready: ready.runtimeReport.length,
29378
- sessions: sessions.length,
29379
- activities: liveActivities.length,
29380
- pendingAuditEvents: this.pendingBotAuditEvents.size
29381
- });
30480
+ const activities = snapshot.activities ?? [];
30481
+ const counts = {
30482
+ ready: snapshot.ready.runtimeReport.length,
30483
+ sessions: snapshot.sessions.length
30484
+ };
30485
+ if (activities instanceof Promise) {
30486
+ activities.then((resolved) => {
30487
+ if (this.ws === socketAtStart && this.statusValue === "open") {
30488
+ sendActivities(resolved, counts);
30489
+ }
30490
+ }).catch((err) => {
30491
+ this.log.warn("resync provider failed", { err: describeErr(err) });
30492
+ });
30493
+ } else {
30494
+ sendActivities(activities, counts);
30495
+ }
29382
30496
  }
29383
30497
  for (const hook of this.resyncHooks) {
29384
30498
  try {
@@ -29596,8 +30710,8 @@ class WsControlChannel {
29596
30710
  }
29597
30711
  // src/timeline/timeline.ts
29598
30712
  import * as fs7 from "node:fs";
29599
- import { createHash as createHash4, randomBytes as randomBytes4 } from "node:crypto";
29600
- import { basename, dirname as dirname3, join as join10 } from "node:path";
30713
+ import { createHash as createHash4, randomBytes as randomBytes6 } from "node:crypto";
30714
+ import { basename, dirname as dirname3, join as join11 } from "node:path";
29601
30715
 
29602
30716
  // src/timeline/filelock.ts
29603
30717
  import * as fs6 from "fs";
@@ -29940,7 +31054,7 @@ function scanTimelineFile(filePath) {
29940
31054
  }
29941
31055
  }
29942
31056
  function atomicReplaceTimeline(filePath, lines) {
29943
- const tempPath = join10(dirname3(filePath), `.${basename(filePath)}.${process.pid}.${randomBytes4(12).toString("hex")}.tmp`);
31057
+ const tempPath = join11(dirname3(filePath), `.${basename(filePath)}.${process.pid}.${randomBytes6(12).toString("hex")}.tmp`);
29944
31058
  let fd = null;
29945
31059
  try {
29946
31060
  fd = fs7.openSync(tempPath, "wx", 384);
@@ -30024,14 +31138,14 @@ function readRecentEntries(timelineDir, opts = {}) {
30024
31138
  const filenames = recentFilenames(maxDays, now).reverse();
30025
31139
  const entries = [];
30026
31140
  for (const filename of filenames) {
30027
- entries.push(...readJsonl(join10(timelineDir, filename)));
31141
+ entries.push(...readJsonl(join11(timelineDir, filename)));
30028
31142
  }
30029
31143
  return entries;
30030
31144
  }
30031
31145
  function readResumeControlState(timelineDir) {
30032
31146
  if (timelineDirectoryState(timelineDir) !== "safe")
30033
31147
  return { kind: "missing" };
30034
- const filePath = join10(timelineDir, RESUME_CONTROL_FILENAME);
31148
+ const filePath = join11(timelineDir, RESUME_CONTROL_FILENAME);
30035
31149
  let source;
30036
31150
  try {
30037
31151
  source = fs7.lstatSync(filePath);
@@ -30107,8 +31221,8 @@ function updateResumeControlState(timelineDir, update) {
30107
31221
  `;
30108
31222
  if (Buffer.byteLength(body, "utf8") > RESUME_CONTROL_MAX_BYTES)
30109
31223
  return false;
30110
- const filePath = join10(timelineDir, RESUME_CONTROL_FILENAME);
30111
- const tempPath = join10(timelineDir, `.${RESUME_CONTROL_FILENAME}.${process.pid}.${randomBytes4(12).toString("hex")}.tmp`);
31224
+ const filePath = join11(timelineDir, RESUME_CONTROL_FILENAME);
31225
+ const tempPath = join11(timelineDir, `.${RESUME_CONTROL_FILENAME}.${process.pid}.${randomBytes6(12).toString("hex")}.tmp`);
30112
31226
  let fd = null;
30113
31227
  try {
30114
31228
  fd = fs7.openSync(tempPath, "wx", 384);
@@ -30138,7 +31252,7 @@ function appendTrackedEntry(timelineDir, entry, now = new Date) {
30138
31252
  if (timelineDirectoryState(timelineDir) !== "safe")
30139
31253
  return { status: "rejected", reason: "unsafe" };
30140
31254
  const filename = filenameForDate(now);
30141
- const filePath = join10(timelineDir, filename);
31255
+ const filePath = join11(timelineDir, filename);
30142
31256
  const lockPath = lockPathFor(timelineDir, filename);
30143
31257
  try {
30144
31258
  if (!acquireLock(lockPath))
@@ -30166,7 +31280,7 @@ function updateTrackedEntry(timelineDir, handle, update) {
30166
31280
  if (!DATE_FILENAME_PATTERN.test(handle.filename) || basename(handle.filename) !== handle.filename) {
30167
31281
  return { status: "rejected", reason: "unsafe" };
30168
31282
  }
30169
- const filePath = join10(timelineDir, handle.filename);
31283
+ const filePath = join11(timelineDir, handle.filename);
30170
31284
  const lockPath = lockPathFor(timelineDir, handle.filename);
30171
31285
  try {
30172
31286
  if (!acquireLock(lockPath))
@@ -30225,10 +31339,10 @@ async function sweepTimelineHistory(workingDirectoryBase, opts = {}) {
30225
31339
  return;
30226
31340
  }
30227
31341
  for (const agentName of agentNames) {
30228
- const agentDir = join10(workingDirectoryBase, agentName);
31342
+ const agentDir = join11(workingDirectoryBase, agentName);
30229
31343
  if (!isRealDirectory(agentDir))
30230
31344
  continue;
30231
- const timelineDir = join10(agentDir, ".context_timeline");
31345
+ const timelineDir = join11(agentDir, ".context_timeline");
30232
31346
  if (!isRealDirectory(timelineDir))
30233
31347
  continue;
30234
31348
  let filenames;
@@ -30238,7 +31352,7 @@ async function sweepTimelineHistory(workingDirectoryBase, opts = {}) {
30238
31352
  continue;
30239
31353
  }
30240
31354
  for (const filename of filenames) {
30241
- const filePath = join10(timelineDir, filename);
31355
+ const filePath = join11(timelineDir, filename);
30242
31356
  let source;
30243
31357
  try {
30244
31358
  source = fs7.lstatSync(filePath);
@@ -30887,7 +32001,12 @@ async function detectRuntimes() {
30887
32001
  const driver = getDriver(id);
30888
32002
  const probe = await driver.probe();
30889
32003
  if (probe.status === "healthy") {
30890
- results.push({ id, status: "healthy", version: probe.version });
32004
+ results.push({
32005
+ id,
32006
+ status: "healthy",
32007
+ version: probe.version,
32008
+ reasoning: probe.reasoning
32009
+ });
30891
32010
  } else {
30892
32011
  results.push({
30893
32012
  id,
@@ -31052,8 +32171,8 @@ class MessageReminderScheduler {
31052
32171
 
31053
32172
  // src/manager/agentDriverHost.ts
31054
32173
  import { randomUUID as randomUUID6 } from "node:crypto";
31055
- import { homedir as homedir3 } from "node:os";
31056
- import { join as join12 } from "node:path";
32174
+ import { homedir as homedir4 } from "node:os";
32175
+ import { join as join13 } from "node:path";
31057
32176
 
31058
32177
  // src/drivers/gitIdentityEnv.ts
31059
32178
  import { execFileSync as execFileSync3 } from "child_process";
@@ -31178,7 +32297,7 @@ function createDaemonAgentDriverHost(ctx, onRawLine) {
31178
32297
  hostUser: readHostGitIdentity() ?? undefined
31179
32298
  }),
31180
32299
  platformProtected: {
31181
- ALOOK_HOME: process.env.ALOOK_HOME ?? join12(homedir3(), ".alook"),
32300
+ ALOOK_HOME: process.env.ALOOK_HOME ?? join13(homedir4(), ".alook"),
31182
32301
  ALOOK_ID: ctx.agentId,
31183
32302
  ALOOK_CLI: ctx.agentCliPath,
31184
32303
  ALOOK_SERVER_URL: ctx.config.serverUrl,
@@ -31283,6 +32402,182 @@ class DaemonSelfSleepScheduler {
31283
32402
  }
31284
32403
  }
31285
32404
 
32405
+ // src/telemetry/dailyTokenUsage.ts
32406
+ import { chmod, mkdir, open, readFile as readFile2, rename, rm } from "node:fs/promises";
32407
+ import { dirname as dirname5, join as join14 } from "node:path";
32408
+ import { randomUUID as randomUUID7 } from "node:crypto";
32409
+ function dayKey(at) {
32410
+ return at.toISOString().slice(0, 10);
32411
+ }
32412
+ function retainedDays(at) {
32413
+ const days = new Set;
32414
+ for (let offset = 0;offset < 7; offset += 1) {
32415
+ days.add(dayKey(new Date(at.getTime() - offset * 86400000)));
32416
+ }
32417
+ return days;
32418
+ }
32419
+ function isMetric(value) {
32420
+ return value === null || typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
32421
+ }
32422
+ function isSnapshot(value) {
32423
+ if (!value || typeof value !== "object")
32424
+ return false;
32425
+ const snapshot = value;
32426
+ const metrics = snapshot.metrics;
32427
+ 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);
32428
+ }
32429
+ function mergeMetric(existing, delta, hasExistingSnapshot) {
32430
+ if (delta === null)
32431
+ return null;
32432
+ if (!Number.isSafeInteger(delta) || delta < 0) {
32433
+ throw new RangeError("token usage delta must be a non-negative safe integer");
32434
+ }
32435
+ if (!hasExistingSnapshot)
32436
+ return delta;
32437
+ if (existing === null)
32438
+ return null;
32439
+ const sum = existing + delta;
32440
+ if (!Number.isSafeInteger(sum))
32441
+ throw new RangeError("daily token usage exceeds safe integer range");
32442
+ return sum;
32443
+ }
32444
+ function emptySnapshot(botId, day) {
32445
+ return {
32446
+ botId,
32447
+ day,
32448
+ metrics: {
32449
+ input: null,
32450
+ output: null,
32451
+ cache: null
32452
+ }
32453
+ };
32454
+ }
32455
+
32456
+ class DailyTokenUsageStore {
32457
+ now;
32458
+ tail = Promise.resolve();
32459
+ loaded = false;
32460
+ data = { version: 1, bots: {} };
32461
+ filePath;
32462
+ constructor(workingDirectoryBase, now = () => new Date) {
32463
+ this.now = now;
32464
+ this.filePath = join14(workingDirectoryBase, ".telemetry", "daily-token-usage.json");
32465
+ }
32466
+ record(botId, delta) {
32467
+ return this.enqueue(async () => {
32468
+ await this.load();
32469
+ const at = this.now();
32470
+ this.prune(at);
32471
+ const day = dayKey(at);
32472
+ const snapshots = this.data.bots[botId] ?? [];
32473
+ const existing = snapshots.find((snapshot) => snapshot.day === day);
32474
+ const next = existing ?? emptySnapshot(botId, day);
32475
+ next.metrics = {
32476
+ input: mergeMetric(next.metrics.input, delta.input, existing !== undefined),
32477
+ output: mergeMetric(next.metrics.output, delta.output, existing !== undefined),
32478
+ cache: mergeMetric(next.metrics.cache, delta.cache, existing !== undefined)
32479
+ };
32480
+ if (!existing)
32481
+ snapshots.push(next);
32482
+ snapshots.sort((a, b) => a.day.localeCompare(b.day));
32483
+ this.data.bots[botId] = snapshots;
32484
+ await this.persist();
32485
+ });
32486
+ }
32487
+ snapshots(botId) {
32488
+ let result = [];
32489
+ return this.enqueue(async () => {
32490
+ await this.load();
32491
+ if (this.prune(this.now()))
32492
+ await this.persist();
32493
+ result = (this.data.bots[botId] ?? []).map((snapshot) => structuredClone(snapshot));
32494
+ }).then(() => result);
32495
+ }
32496
+ enqueue(operation) {
32497
+ const result = this.tail.then(operation, operation);
32498
+ this.tail = result.then(() => {
32499
+ return;
32500
+ }, () => {
32501
+ return;
32502
+ });
32503
+ return result;
32504
+ }
32505
+ async load() {
32506
+ if (this.loaded)
32507
+ return;
32508
+ let source;
32509
+ try {
32510
+ source = await readFile2(this.filePath, "utf8");
32511
+ } catch (error51) {
32512
+ if (!error51 || typeof error51 !== "object" || !("code" in error51) || error51.code !== "ENOENT") {
32513
+ throw error51;
32514
+ }
32515
+ this.data = { version: 1, bots: {} };
32516
+ this.loaded = true;
32517
+ return;
32518
+ }
32519
+ const parsed = JSON.parse(source);
32520
+ if (!parsed || typeof parsed !== "object" || parsed.version !== 1) {
32521
+ throw new Error("invalid daily token usage file version");
32522
+ }
32523
+ const bots = parsed.bots;
32524
+ if (!bots || typeof bots !== "object" || Array.isArray(bots)) {
32525
+ throw new Error("invalid daily token usage bots map");
32526
+ }
32527
+ const valid = {};
32528
+ for (const [botId, value] of Object.entries(bots)) {
32529
+ if (!Array.isArray(value) || !value.every(isSnapshot) || value.some((snapshot) => snapshot.botId !== botId)) {
32530
+ throw new Error(`invalid daily token usage snapshots for bot ${botId}`);
32531
+ }
32532
+ if (value.length > 0) {
32533
+ valid[botId] = value;
32534
+ }
32535
+ }
32536
+ this.data = { version: 1, bots: valid };
32537
+ this.loaded = true;
32538
+ }
32539
+ prune(at) {
32540
+ const keep = retainedDays(at);
32541
+ let changed = false;
32542
+ for (const [botId, snapshots] of Object.entries(this.data.bots)) {
32543
+ const retained = snapshots.filter((snapshot) => keep.has(snapshot.day)).sort((a, b) => a.day.localeCompare(b.day)).slice(-7);
32544
+ if (retained.length !== snapshots.length || retained.some((snapshot, index2) => snapshot !== snapshots[index2]))
32545
+ changed = true;
32546
+ if (retained.length === 0)
32547
+ delete this.data.bots[botId];
32548
+ else
32549
+ this.data.bots[botId] = retained;
32550
+ }
32551
+ return changed;
32552
+ }
32553
+ async persist() {
32554
+ const directory = dirname5(this.filePath);
32555
+ await mkdir(directory, { recursive: true, mode: 448 });
32556
+ const temporary = `${this.filePath}.${randomUUID7()}.tmp`;
32557
+ try {
32558
+ const file2 = await open(temporary, "wx", 384);
32559
+ try {
32560
+ await file2.writeFile(JSON.stringify(this.data), { encoding: "utf8" });
32561
+ await file2.sync();
32562
+ } finally {
32563
+ await file2.close();
32564
+ }
32565
+ await rename(temporary, this.filePath);
32566
+ await chmod(this.filePath, 384);
32567
+ try {
32568
+ const directoryHandle = await open(directory, "r");
32569
+ try {
32570
+ await directoryHandle.sync();
32571
+ } finally {
32572
+ await directoryHandle.close();
32573
+ }
32574
+ } catch {}
32575
+ } catch (error51) {
32576
+ await rm(temporary, { force: true }).catch(() => {});
32577
+ throw error51;
32578
+ }
32579
+ }
32580
+ }
31286
32581
  // src/daemon/createDaemon.ts
31287
32582
  var WARMUP_BACKOFF_MS = [250, 500, 1000, 2000, 4000];
31288
32583
  var WARMUP_CEILING_MS = 30000;
@@ -31420,9 +32715,25 @@ function createBuiltinDaemonSessionFactory(onRuntimeRawLine) {
31420
32715
  }
31421
32716
  async function createDaemon(opts) {
31422
32717
  const log2 = opts.logger ?? createLogger({ header: "@alook/daemon" });
31423
- const fallbackBase = (process.env.ALOOK_PROJECT_ROOT || `${homedir4()}/.alook`) + "/daemon";
32718
+ const fallbackBase = (process.env.ALOOK_PROJECT_ROOT || `${homedir5()}/.alook`) + "/daemon";
31424
32719
  const workingDirectoryBase = opts.workingDirectoryBase ?? fallbackBase;
31425
32720
  const workdirFor = (agentId) => `${workingDirectoryBase}/${agentId}`;
32721
+ const dailyTokenUsage2 = new DailyTokenUsageStore(workingDirectoryBase);
32722
+ const providerQuotaReader = opts.providerQuotaReader ?? (opts.sessionFactory ? async () => null : readBuiltinProviderQuota);
32723
+ const providerQuotaByBackend = new Map;
32724
+ let requestReadyQuotaResend = () => {};
32725
+ const recordProviderQuota = (backendId, quota) => {
32726
+ const previous = providerQuotaByBackend.get(backendId);
32727
+ if (previous?.observation.status === "available" && quota.status === "error" && previous.observation.sourceEpoch === quota.sourceEpoch)
32728
+ return;
32729
+ providerQuotaByBackend.set(backendId, {
32730
+ agentBackendId: backendId,
32731
+ observation: structuredClone(quota)
32732
+ });
32733
+ if (previous && previous.observation.sourceEpoch !== quota.sourceEpoch) {
32734
+ requestReadyQuotaResend();
32735
+ }
32736
+ };
31426
32737
  sweepTimelineHistory(workingDirectoryBase).catch(() => {
31427
32738
  log2.warn("timeline startup sweep failed");
31428
32739
  });
@@ -31438,6 +32749,27 @@ async function createDaemon(opts) {
31438
32749
  });
31439
32750
  let channelRef = null;
31440
32751
  let managerRef = null;
32752
+ const providerQuotaSnapshots = () => [...providerQuotaByBackend.values()].map((snapshot) => structuredClone(snapshot));
32753
+ const activityPayload = async (info) => {
32754
+ if (info.state !== "idle")
32755
+ return info;
32756
+ const backendId = managerRef?.agentBackendId(info.agentId);
32757
+ if (backendId === "claude") {
32758
+ const observed = await providerQuotaReader("claude");
32759
+ if (observed)
32760
+ recordProviderQuota("claude", observed);
32761
+ }
32762
+ const quota = backendId === "claude" || backendId === "codex" ? providerQuotaByBackend.get(backendId) : undefined;
32763
+ const dailyUsage = await dailyTokenUsage2.snapshots(info.agentId);
32764
+ return {
32765
+ ...info,
32766
+ ...dailyUsage.length > 0 ? { dailyUsage } : {},
32767
+ ...quota ? { quota: structuredClone(quota) } : {}
32768
+ };
32769
+ };
32770
+ let reportAgentActivity = (info) => {
32771
+ channelRef?.reportAgentActivity?.(info);
32772
+ };
31441
32773
  let reminderSchedulerRef = null;
31442
32774
  const selfSleepScheduler = opts.onSelfSleep ? new DaemonSelfSleepScheduler({
31443
32775
  onSleep: opts.onSelfSleep,
@@ -31502,7 +32834,7 @@ async function createDaemon(opts) {
31502
32834
  function reassertAgentActivity(agentId) {
31503
32835
  const state = managerRef?.agentActivity(agentId);
31504
32836
  if (state)
31505
- channel2.reportAgentActivity?.({ agentId, state });
32837
+ reportAgentActivity({ agentId, state });
31506
32838
  }
31507
32839
  function startTypingHeartbeat(agentId) {
31508
32840
  stopTypingHeartbeat(agentId);
@@ -31646,6 +32978,20 @@ async function createDaemon(opts) {
31646
32978
  logger: log2.child("ws")
31647
32979
  });
31648
32980
  channelRef = channel2;
32981
+ const activityReportTails = new Map;
32982
+ reportAgentActivity = (info) => {
32983
+ const prior = activityReportTails.get(info.agentId) ?? Promise.resolve();
32984
+ const next = prior.then(async () => {
32985
+ await channel2.reportAgentActivity(await activityPayload(info));
32986
+ }).catch(() => {
32987
+ log2.warn("agent activity telemetry report failed", { agentId: info.agentId, state: info.state });
32988
+ });
32989
+ activityReportTails.set(info.agentId, next);
32990
+ next.finally(() => {
32991
+ if (activityReportTails.get(info.agentId) === next)
32992
+ activityReportTails.delete(info.agentId);
32993
+ });
32994
+ };
31649
32995
  function restorePendingIdleResetEvents(agentId) {
31650
32996
  for (const pending of timeline2.pendingIdleResetEvents(agentId)) {
31651
32997
  channel2.restorePendingBotAuditEvent({
@@ -31764,7 +33110,7 @@ async function createDaemon(opts) {
31764
33110
  onAgentSession: (info) => void channel2.reportAgentSession(info),
31765
33111
  onAgentActivity: (info) => {
31766
33112
  selfSleepScheduler?.observeAgentActivity(info.agentId, info.state === "running");
31767
- channel2.reportAgentActivity?.(info);
33113
+ reportAgentActivity(info);
31768
33114
  if (info.state === "starting" || info.state === "running") {
31769
33115
  if (!typingHeartbeats.has(info.agentId)) {
31770
33116
  startTypingHeartbeat(info.agentId);
@@ -31773,6 +33119,16 @@ async function createDaemon(opts) {
31773
33119
  emitTypingStopsAndClear(info.agentId);
31774
33120
  }
31775
33121
  },
33122
+ onTokenUsage: ({ agentId, usage }) => {
33123
+ dailyTokenUsage2.record(agentId, usage).catch(() => {
33124
+ log2.warn("daily token usage persistence failed", { agentId });
33125
+ });
33126
+ },
33127
+ onProviderQuota: ({ backendId, quota }) => {
33128
+ if (backendId !== "claude" && backendId !== "codex")
33129
+ return;
33130
+ recordProviderQuota(backendId, quota);
33131
+ },
31776
33132
  onBotAuditEvent: (agentId, event, context) => emitBotAuditEvent(agentId, event, context),
31777
33133
  onAgentLocallyStopped: (info) => router?.markLocallyStopped(info.agentId),
31778
33134
  onRuntimeRawLine,
@@ -31819,6 +33175,11 @@ async function createDaemon(opts) {
31819
33175
  arch: opts.arch,
31820
33176
  osRelease: opts.osRelease,
31821
33177
  daemonVersion: opts.daemonVersion,
33178
+ providerQuotas: providerQuotaSnapshots,
33179
+ resyncActivities: async () => {
33180
+ const activities = await Promise.all(manager.liveAgentActivities().map((info) => activityPayload(info)));
33181
+ return activities.filter((activity) => manager.agentActivity(activity.agentId) === activity.state);
33182
+ },
31822
33183
  typingTracker,
31823
33184
  logger: log2.child("router"),
31824
33185
  onBeforeAgent: async (agentId) => {
@@ -31834,6 +33195,10 @@ async function createDaemon(opts) {
31834
33195
  await enrollAgent(agentId);
31835
33196
  }
31836
33197
  });
33198
+ requestReadyQuotaResend = () => {
33199
+ if (router)
33200
+ channel2.sendReady?.(router.buildReady());
33201
+ };
31837
33202
  channel2.onCommand(createSelfUpdateCommandListener(opts.handleSelfUpdate));
31838
33203
  channel2.onCommand(createDiagnosticsCommandListener({
31839
33204
  handleDiagnosticCommand: opts.handleDiagnosticCommand,
@@ -31863,6 +33228,11 @@ async function createDaemon(opts) {
31863
33228
  resyncPendingWakes();
31864
33229
  resyncPendingDiagnostics();
31865
33230
  });
33231
+ if (opts.runtimeReport.some((runtime) => runtime.id === "claude")) {
33232
+ const observed = await providerQuotaReader("claude");
33233
+ if (observed)
33234
+ recordProviderQuota("claude", observed);
33235
+ }
31866
33236
  channel2.connect();
31867
33237
  await router.start();
31868
33238
  selfSleepScheduler?.start();