lua-cli 3.38.0 → 3.39.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1181,6 +1181,155 @@ function foldRichPartsIntoMessages(messages, records, makeMessage, sameTurnGroup
1181
1181
  while (next < synthetic.length) combined.push(synthetic[next++].message);
1182
1182
  return mergeRichPartMirrorMessages(combined, sameTurnGroup);
1183
1183
  }
1184
+ function utf8WidthAt(value3, index) {
1185
+ const code = value3.charCodeAt(index);
1186
+ if (code < 128) return {
1187
+ bytes: 1,
1188
+ units: 1
1189
+ };
1190
+ if (code < 2048) return {
1191
+ bytes: 2,
1192
+ units: 1
1193
+ };
1194
+ if (code >= 55296 && code <= 56319) {
1195
+ const next = value3.charCodeAt(index + 1);
1196
+ if (next >= 56320 && next <= 57343) return {
1197
+ bytes: 4,
1198
+ units: 2
1199
+ };
1200
+ }
1201
+ return {
1202
+ bytes: 3,
1203
+ units: 1
1204
+ };
1205
+ }
1206
+ function utf8ByteLength(value3) {
1207
+ let bytes = 0;
1208
+ for (let i = 0; i < value3.length; ) {
1209
+ const width = utf8WidthAt(value3, i);
1210
+ bytes += width.bytes;
1211
+ i += width.units;
1212
+ }
1213
+ return bytes;
1214
+ }
1215
+ function clampUtf8(value3, maxBytes) {
1216
+ let bytes = 0;
1217
+ for (let i = 0; i < value3.length; ) {
1218
+ const width = utf8WidthAt(value3, i);
1219
+ if (bytes + width.bytes > maxBytes) return value3.slice(0, i);
1220
+ bytes += width.bytes;
1221
+ i += width.units;
1222
+ }
1223
+ return value3;
1224
+ }
1225
+ function mapBytes(fields, withMark) {
1226
+ return utf8ByteLength(JSON.stringify(withMark ? {
1227
+ ...fields,
1228
+ [LOG_FIELDS_CLAMPED_KEY]: true
1229
+ } : fields));
1230
+ }
1231
+ function withValueCap(fields, capBytes) {
1232
+ const out = {};
1233
+ for (const [key, value3] of Object.entries(fields)) {
1234
+ out[key] = typeof value3 === "string" ? clampUtf8(value3, capBytes) : value3;
1235
+ }
1236
+ return out;
1237
+ }
1238
+ function describeValue(value3) {
1239
+ if (value3 === null) return "null";
1240
+ if (value3 === void 0) return "undefined";
1241
+ if (Array.isArray(value3)) return "an array";
1242
+ return typeof value3 === "object" ? "an object" : `a ${typeof value3}`;
1243
+ }
1244
+ function assertValidLogFieldKey(key) {
1245
+ if (!LOG_FIELD_KEY_PATTERN.test(key)) {
1246
+ throw new LuaLogFieldsError(`log fields: key \`${key}\` is not allowed \u2014 a key must match ${LOG_FIELD_KEY_PATTERN.source} (lower-case, starts with a letter, then letters/digits/underscore, at most 64 characters)`);
1247
+ }
1248
+ if (LOG_FIELD_SECRET_SHAPED_KEY_PATTERNS.some((pattern) => pattern.test(key))) {
1249
+ throw new LuaLogFieldsError(`log fields: key \`${key}\` is not allowed \u2014 it has the shape of a credential that the log-drain scrubber masks in values; a field KEY is never scrubbed, so it may never carry a secret shape`);
1250
+ }
1251
+ if (key === LOG_FIELDS_CLAMPED_KEY) {
1252
+ throw new LuaLogFieldsError(`log fields: key \`${key}\` is reserved \u2014 the platform sets it to mark a field map it had to clamp`);
1253
+ }
1254
+ }
1255
+ function normalizeLogFields(fields) {
1256
+ if (fields === void 0 || fields === null) return void 0;
1257
+ if (typeof fields !== "object" || Array.isArray(fields)) {
1258
+ throw new LuaLogFieldsError(`log fields: expected a plain object of fields, received ${describeValue(fields)}`);
1259
+ }
1260
+ const entries = Object.entries(fields);
1261
+ if (entries.length > LOG_FIELDS_MAX_KEYS) {
1262
+ throw new LuaLogFieldsError(`log fields: ${entries.length} keys exceeds the cap of ${LOG_FIELDS_MAX_KEYS} keys per call`);
1263
+ }
1264
+ const out = {};
1265
+ for (const [key, value3] of entries) {
1266
+ assertValidLogFieldKey(key);
1267
+ if (typeof value3 === "string" || typeof value3 === "boolean") {
1268
+ out[key] = value3;
1269
+ continue;
1270
+ }
1271
+ if (typeof value3 === "number") {
1272
+ if (!Number.isFinite(value3)) {
1273
+ throw new LuaLogFieldsError(`log fields: \`${key}\` must be a finite number \u2014 NaN and Infinity have no JSON form`);
1274
+ }
1275
+ out[key] = value3;
1276
+ continue;
1277
+ }
1278
+ if (Array.isArray(value3)) {
1279
+ const parts = [];
1280
+ for (const element of value3) {
1281
+ if (typeof element !== "string") {
1282
+ throw new LuaLogFieldsError(`log fields: \`${key}\` is an array, so every element must be a string (found ${describeValue(element)})`);
1283
+ }
1284
+ if (element.includes(",")) {
1285
+ throw new LuaLogFieldsError(`log fields: \`${key}\` \u2014 an array element may not contain a comma; elements are comma-joined into one value (with \`${key}${LOG_FIELD_ARRAY_COUNT_SUFFIX}\` beside it), so a comma inside one would be indistinguishable from the separator`);
1286
+ }
1287
+ parts.push(element);
1288
+ }
1289
+ out[key] = parts.join(",");
1290
+ out[`${key}${LOG_FIELD_ARRAY_COUNT_SUFFIX}`] = parts.length;
1291
+ continue;
1292
+ }
1293
+ throw new LuaLogFieldsError(`log fields: \`${key}\` must be a string, number, boolean or string[] \u2014 received ${describeValue(value3)}` + (value3 !== null && typeof value3 === "object" ? " (nested objects are not supported)" : ""));
1294
+ }
1295
+ const keys = Object.keys(out);
1296
+ if (keys.length === 0) return void 0;
1297
+ if (keys.length > LOG_FIELDS_MAX_KEYS) {
1298
+ throw new LuaLogFieldsError(`log fields: ${keys.length} keys exceeds the cap of ${LOG_FIELDS_MAX_KEYS} keys per call \u2014 an array field also contributes its \`${LOG_FIELD_ARRAY_COUNT_SUFFIX.slice(1)}\` sibling`);
1299
+ }
1300
+ if (clampLogFields(out)) out[LOG_FIELDS_CLAMPED_KEY] = true;
1301
+ return out;
1302
+ }
1303
+ function clampLogFields(out) {
1304
+ let clamped = false;
1305
+ for (const [key, value3] of Object.entries(out)) {
1306
+ if (typeof value3 !== "string") continue;
1307
+ const capped = clampUtf8(value3, LOG_FIELD_MAX_VALUE_BYTES);
1308
+ if (capped !== value3) {
1309
+ out[key] = capped;
1310
+ clamped = true;
1311
+ }
1312
+ }
1313
+ if (mapBytes(out, clamped) <= LOG_FIELDS_MAX_BYTES) return clamped;
1314
+ const base = {
1315
+ ...out
1316
+ };
1317
+ let low = 0;
1318
+ let high = LOG_FIELD_MAX_VALUE_BYTES;
1319
+ let best = 0;
1320
+ while (low <= high) {
1321
+ const mid = Math.floor((low + high) / 2);
1322
+ if (mapBytes(withValueCap(base, mid), true) <= LOG_FIELDS_MAX_BYTES) {
1323
+ best = mid;
1324
+ low = mid + 1;
1325
+ } else {
1326
+ high = mid - 1;
1327
+ }
1328
+ }
1329
+ const fitted = withValueCap(base, best);
1330
+ for (const key of Object.keys(out)) out[key] = fitted[key];
1331
+ return true;
1332
+ }
1184
1333
  function isDesktopFileCommandName(value3) {
1185
1334
  return typeof value3 === "string" && DESKTOP_FILE_COMMAND_SET.has(value3);
1186
1335
  }
@@ -1408,6 +1557,81 @@ function buildDefaultPersona(agentName) {
1408
1557
  function buildPersonalSpaceStartingPersona(agentName) {
1409
1558
  return PERSONAL_SPACE_STARTING_PERSONA.replace(new RegExp(AGENT_NAME_TOKEN.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g"), () => agentName || "Your assistant");
1410
1559
  }
1560
+ function utf8Bytes(value3) {
1561
+ let bytes = 0;
1562
+ for (let i = 0; i < value3.length; ) {
1563
+ const code = value3.charCodeAt(i);
1564
+ if (code < 128) {
1565
+ bytes += 1;
1566
+ i += 1;
1567
+ } else if (code < 2048) {
1568
+ bytes += 2;
1569
+ i += 1;
1570
+ } else if (code >= 55296 && code <= 56319) {
1571
+ const next = value3.charCodeAt(i + 1);
1572
+ if (next >= 56320 && next <= 57343) {
1573
+ bytes += 4;
1574
+ i += 2;
1575
+ } else {
1576
+ bytes += 3;
1577
+ i += 1;
1578
+ }
1579
+ } else {
1580
+ bytes += 3;
1581
+ i += 1;
1582
+ }
1583
+ }
1584
+ return bytes;
1585
+ }
1586
+ function isPlainFieldKey(key) {
1587
+ if (!LOG_FIELD_KEY_PATTERN.test(key)) return false;
1588
+ return !LOG_FIELD_SECRET_SHAPED_KEY_PATTERNS.some((pattern) => pattern.test(key));
1589
+ }
1590
+ function describeAgentLogFieldsViolation(value3) {
1591
+ if (typeof value3 !== "object" || value3 === null || Array.isArray(value3)) {
1592
+ return "fields must be an object";
1593
+ }
1594
+ const entries = Object.entries(value3);
1595
+ let userKeys = 0;
1596
+ for (const [key, entry] of entries) {
1597
+ if (key === LOG_FIELDS_CLAMPED_KEY) {
1598
+ if (entry !== true) return `fields.${key} must be true`;
1599
+ continue;
1600
+ }
1601
+ userKeys += 1;
1602
+ if (key.endsWith(LOG_FIELD_ARRAY_COUNT_SUFFIX)) {
1603
+ const base = key.slice(0, -LOG_FIELD_ARRAY_COUNT_SUFFIX.length);
1604
+ if (!isPlainFieldKey(base)) return `fields key \`${key}\` is not a legal field key`;
1605
+ if (typeof entry !== "number" || !Number.isFinite(entry)) {
1606
+ return `fields.${key} must be a finite number`;
1607
+ }
1608
+ continue;
1609
+ }
1610
+ if (!isPlainFieldKey(key)) return `fields key \`${key}\` is not a legal field key`;
1611
+ if (typeof entry === "string") {
1612
+ if (utf8Bytes(entry) > LOG_FIELD_MAX_VALUE_BYTES) {
1613
+ return `fields.${key} exceeds ${LOG_FIELD_MAX_VALUE_BYTES} bytes`;
1614
+ }
1615
+ continue;
1616
+ }
1617
+ if (typeof entry === "boolean") continue;
1618
+ if (typeof entry === "number") {
1619
+ if (!Number.isFinite(entry)) return `fields.${key} must be a finite number`;
1620
+ continue;
1621
+ }
1622
+ return `fields.${key} must be a string, number or boolean`;
1623
+ }
1624
+ if (userKeys > LOG_FIELDS_MAX_KEYS) {
1625
+ return `fields has ${userKeys} keys, over the cap of ${LOG_FIELDS_MAX_KEYS}`;
1626
+ }
1627
+ if (utf8Bytes(JSON.stringify(value3)) > LOG_FIELDS_MAX_BYTES) {
1628
+ return `fields exceeds ${LOG_FIELDS_MAX_BYTES} bytes`;
1629
+ }
1630
+ return void 0;
1631
+ }
1632
+ function isAgentLogFields(value3) {
1633
+ return describeAgentLogFieldsViolation(value3) === void 0;
1634
+ }
1411
1635
  function maxDrainsForTier(tier) {
1412
1636
  return tier === "enterprise" ? LOG_DRAIN_MAX_PER_ORG_ENTERPRISE : LOG_DRAIN_MAX_PER_ORG;
1413
1637
  }
@@ -1416,6 +1640,148 @@ function resolvePresetEndpoint(type, site) {
1416
1640
  if (!template3 || !site) return void 0;
1417
1641
  return template3.replace("{site}", site);
1418
1642
  }
1643
+ function isLogDrainBucketHostAllowed(provider, host) {
1644
+ const normalized = host.trim().toLowerCase().replace(/^\[|\]$/g, "");
1645
+ if (!normalized) return false;
1646
+ return (LOG_DRAIN_BUCKET_HOST_PATTERNS[provider] ?? []).some((pattern) => pattern.test(normalized));
1647
+ }
1648
+ function resolveDrainBucketEndpoint(bucket) {
1649
+ if (!LOG_DRAIN_BUCKET_NAME_PATTERN.test(bucket.bucket)) {
1650
+ throw new Error("log drain bucket name is not a valid S3 bucket label");
1651
+ }
1652
+ if (bucket.provider === "aws") {
1653
+ const region = bucket.region.trim().toLowerCase();
1654
+ if (!/^[a-z0-9-]{1,32}$/.test(region)) throw new Error("log drain bucket region is not an AWS region");
1655
+ return `https://${bucket.bucket}.s3.${region}.amazonaws.com`;
1656
+ }
1657
+ if (!bucket.endpoint) throw new Error(`log drain '${bucket.provider}' bucket requires an endpoint`);
1658
+ let url;
1659
+ try {
1660
+ url = new URL(bucket.endpoint);
1661
+ } catch {
1662
+ throw new Error(`log drain '${bucket.provider}' bucket endpoint is not an absolute URL`);
1663
+ }
1664
+ if (url.protocol !== "https:") throw new Error(`log drain '${bucket.provider}' bucket endpoint must be https`);
1665
+ return `https://${url.host}/${bucket.bucket}`;
1666
+ }
1667
+ function drainBucketUsesPathStyle(provider) {
1668
+ return provider !== "aws";
1669
+ }
1670
+ function presetIntakeHosts(type) {
1671
+ const template3 = LOG_DRAIN_ENDPOINT_TEMPLATES[type];
1672
+ const sites = LOG_DRAIN_PRESET_SITES[type];
1673
+ if (!template3 || !sites) return [];
1674
+ const hosts = [];
1675
+ for (const site of sites) {
1676
+ try {
1677
+ hosts.push(new URL(template3.replace("{site}", site)).hostname.toLowerCase());
1678
+ } catch {
1679
+ }
1680
+ }
1681
+ return hosts;
1682
+ }
1683
+ function presetHostAllowlist(type) {
1684
+ return [
1685
+ ...presetIntakeHosts(type),
1686
+ ...LOG_DRAIN_PRESET_HOST_SUFFIXES[type] ?? []
1687
+ ];
1688
+ }
1689
+ function drainHostAllowlistAllows(allowlist, host) {
1690
+ const needle = host.toLowerCase();
1691
+ return allowlist.some((raw) => {
1692
+ const entry = raw.toLowerCase();
1693
+ if (entry.startsWith(".")) return needle === entry.slice(1) || needle.endsWith(entry);
1694
+ return entry === needle;
1695
+ });
1696
+ }
1697
+ function drainDestinationIsVendorPinned(type) {
1698
+ if (LOG_DRAIN_ENDPOINT_TEMPLATES[type]) return true;
1699
+ return presetHostAllowlist(type).length > 0;
1700
+ }
1701
+ function drainVerificationOwnership(input) {
1702
+ if (input.proved === true) return "verified";
1703
+ if (input.mode === "test-post" && drainDestinationIsVendorPinned(input.type)) return "verified";
1704
+ return "reachability-only";
1705
+ }
1706
+ function drainOwnershipOrDefault(ownership) {
1707
+ return ownership === "verified" ? "verified" : "reachability-only";
1708
+ }
1709
+ function assertDrainContentAck(input) {
1710
+ const turningOn = input.next === true && input.was !== true;
1711
+ if (!turningOn) return void 0;
1712
+ if (input.ack !== true) throw new DrainContentAckRequiredError();
1713
+ return {
1714
+ by: input.actor,
1715
+ source: input.source,
1716
+ at: input.at,
1717
+ ...input.inheritedFrom ? {
1718
+ inheritedFrom: input.inheritedFrom
1719
+ } : {}
1720
+ };
1721
+ }
1722
+ function composePresetEndpoint(type, endpoint) {
1723
+ const rule = LOG_DRAIN_PRESET_PATHS[type];
1724
+ if (!rule) return void 0;
1725
+ let url;
1726
+ try {
1727
+ url = new URL(endpoint);
1728
+ } catch {
1729
+ return {
1730
+ ok: false,
1731
+ reason: "invalid_url"
1732
+ };
1733
+ }
1734
+ if (url.protocol !== "https:" || !url.hostname) return {
1735
+ ok: false,
1736
+ reason: "invalid_url"
1737
+ };
1738
+ const origin = url.origin;
1739
+ const path26 = url.pathname.replace(/\/+$/, "");
1740
+ if (!rule.segment) {
1741
+ const base2 = path26.endsWith(rule.path) ? path26.slice(0, path26.length - rule.path.length) : path26;
1742
+ return {
1743
+ ok: true,
1744
+ endpoint: `${origin}${base2}${rule.path}`
1745
+ };
1746
+ }
1747
+ const marker = `${rule.path}/`;
1748
+ const at = path26.indexOf(marker);
1749
+ if (at < 0) {
1750
+ return {
1751
+ ok: false,
1752
+ reason: rule.segment === "dataset" ? "missing_dataset" : "missing_token"
1753
+ };
1754
+ }
1755
+ const base = path26.slice(0, at);
1756
+ const afterMarker = path26.slice(at + marker.length);
1757
+ const rest = rule.suffix && afterMarker.endsWith(rule.suffix) ? afterMarker.slice(0, afterMarker.length - rule.suffix.length) : afterMarker;
1758
+ if (!rest || rest.includes("/")) {
1759
+ return {
1760
+ ok: false,
1761
+ reason: rule.segment === "dataset" ? "missing_dataset" : "missing_token"
1762
+ };
1763
+ }
1764
+ if (!PRESET_SEGMENT_RE.test(rest)) return {
1765
+ ok: false,
1766
+ reason: "bad_segment"
1767
+ };
1768
+ if (rule.segment === "dataset") {
1769
+ return {
1770
+ ok: true,
1771
+ endpoint: `${origin}${base}${rule.path}/${rest}${rule.suffix ?? ""}`,
1772
+ dataset: rest
1773
+ };
1774
+ }
1775
+ return {
1776
+ ok: true,
1777
+ endpoint: `${origin}${base}${rule.path}/`,
1778
+ token: rest
1779
+ };
1780
+ }
1781
+ function joinPresetEndpointToken(endpoint, token) {
1782
+ if (typeof token !== "string" || !PRESET_SEGMENT_RE.test(token)) return endpoint;
1783
+ return endpoint.endsWith("/") ? `${endpoint}${token}` : `${endpoint}/${token}`;
1784
+ }
1419
1785
  function nextLogDrainQuotaResetAt(now) {
1420
1786
  return new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() + 1)).toISOString();
1421
1787
  }
@@ -1462,6 +1828,264 @@ function logDrainQuotaDroppedSeverities(degradation) {
1462
1828
  return [];
1463
1829
  }
1464
1830
  }
1831
+ function isLogExportSource(source) {
1832
+ return LOG_EXPORT_SOURCES.includes(source);
1833
+ }
1834
+ function clamp(value3, max) {
1835
+ if (typeof value3 !== "string") return void 0;
1836
+ const trimmed2 = value3.trim();
1837
+ if (!trimmed2) return void 0;
1838
+ return trimmed2.length <= max ? trimmed2 : `${trimmed2.slice(0, max - 1)}\u2026`;
1839
+ }
1840
+ function numberOr(value3, fallback) {
1841
+ return typeof value3 === "number" && Number.isFinite(value3) ? value3 : fallback;
1842
+ }
1843
+ function isoOr(value3) {
1844
+ return typeof value3 === "string" && value3.length > 0 ? value3 : void 0;
1845
+ }
1846
+ function logDrainNoticeView(drain, now) {
1847
+ const health = drain.health ?? {};
1848
+ const quota = drain.quota;
1849
+ const view = {
1850
+ id: String(drain.id ?? ""),
1851
+ name: String(drain.name ?? ""),
1852
+ type: drain.type,
1853
+ state: drain.state,
1854
+ deliveredCount24h: numberOr(health.deliveredCount24h, 0),
1855
+ droppedCount24h: numberOr(health.droppedCount24h, 0),
1856
+ rejectedCount24h: numberOr(health.rejectedCount24h, 0),
1857
+ backlog: numberOr(health.backlog, 0),
1858
+ quotaRatio: quota ? logDrainQuotaRatio(quota, now) : 0
1859
+ };
1860
+ if (health.pauseReason) view.pauseReason = health.pauseReason;
1861
+ const lastError = clamp(health.lastError, LOG_DRAIN_NOTICE_ERROR_MAX_CHARS);
1862
+ if (lastError) view.lastError = lastError;
1863
+ if (typeof health.lastStatusCode === "number") view.lastStatusCode = health.lastStatusCode;
1864
+ const lastSuccessAt = isoOr(health.lastSuccessAt);
1865
+ if (lastSuccessAt) view.lastSuccessAt = lastSuccessAt;
1866
+ const lastFailureAt = isoOr(health.lastFailureAt);
1867
+ if (lastFailureAt) view.lastFailureAt = lastFailureAt;
1868
+ const resetsAt = isoOr(quota?.resetsAt);
1869
+ if (resetsAt) view.quotaResetsAt = resetsAt;
1870
+ return view;
1871
+ }
1872
+ function logDrainHealthCardPath(orgId, drainId) {
1873
+ return `/orgs/${encodeURIComponent(orgId)}/log-drains/${encodeURIComponent(drainId)}`;
1874
+ }
1875
+ function percent(ratio) {
1876
+ if (!Number.isFinite(ratio) || ratio <= 0) return "0%";
1877
+ return `${Math.round(ratio * 100)}%`;
1878
+ }
1879
+ function becauseClause(view) {
1880
+ const bits = [];
1881
+ if (view.lastStatusCode) bits.push(`HTTP ${view.lastStatusCode}`);
1882
+ if (view.lastError) bits.push(view.lastError);
1883
+ return bits.length ? ` Last error: ${bits.join(" \u2014 ")}.` : "";
1884
+ }
1885
+ function logDrainNoticeCopy(payload) {
1886
+ const view = payload.drain;
1887
+ const name = view.name || view.id;
1888
+ const backlog = view.backlog > 0 ? ` ${view.backlog.toLocaleString("en-US")} records are buffered.` : "";
1889
+ switch (payload.kind) {
1890
+ case "failing":
1891
+ return {
1892
+ title: name,
1893
+ subject: `Log drain "${name}" is failing`,
1894
+ previewLine: "Failing \u2014 15 minutes without a successful delivery",
1895
+ summary: `The log drain "${name}" (${view.type}) has gone 15 minutes without a successful delivery and is now failing.${becauseClause(view)}${backlog} Records keep buffering; the drain pauses itself after 24 hours without a success.`,
1896
+ status: "failed",
1897
+ priority: "high"
1898
+ };
1899
+ case "paused_auto":
1900
+ return {
1901
+ title: name,
1902
+ subject: `Log drain "${name}" was paused`,
1903
+ previewLine: "Paused \u2014 24 hours without a successful delivery",
1904
+ summary: `The log drain "${name}" (${view.type}) was paused automatically after 24 hours without a successful delivery.${becauseClause(view)}${backlog} Nothing is being sent while it is paused, and the buffer is delivered on resume. Fix the destination, then resume it from the drain's health card or with \`lua drains resume ${view.id}\`.`,
1905
+ status: "failed",
1906
+ priority: "high"
1907
+ };
1908
+ case "paused_quota":
1909
+ return {
1910
+ title: name,
1911
+ subject: `Log drain "${name}" was paused \u2014 daily quota exhausted`,
1912
+ previewLine: `Paused \u2014 ${percent(view.quotaRatio)} of the daily allowance used`,
1913
+ summary: `The log drain "${name}" (${view.type}) reached ${percent(view.quotaRatio)} of its daily allowance and was paused.${backlog} Records keep buffering. The allowance resets${view.quotaResetsAt ? ` at ${view.quotaResetsAt}` : " at 00:00 UTC"}; narrow the drain's sources or raise the plan's limit to stop it happening again, then resume it with \`lua drains resume ${view.id}\`.`,
1914
+ status: "failed",
1915
+ priority: "high"
1916
+ };
1917
+ case "quota_warning":
1918
+ return {
1919
+ title: name,
1920
+ subject: `Log drain "${name}" has used 80% of its daily allowance`,
1921
+ previewLine: `${percent(view.quotaRatio)} of the daily allowance used`,
1922
+ summary: `The log drain "${name}" (${view.type}) has used ${percent(view.quotaRatio)} of today's allowance. Past 100% debug records are dropped, past 125% info records as well, and at 150% the drain pauses. The allowance resets${view.quotaResetsAt ? ` at ${view.quotaResetsAt}` : " at 00:00 UTC"}.`,
1923
+ status: "failed",
1924
+ priority: "normal"
1925
+ };
1926
+ case "dropped": {
1927
+ const dropped = payload.droppedCount ?? view.droppedCount24h;
1928
+ return {
1929
+ title: name,
1930
+ subject: `Log drain "${name}" is dropping records`,
1931
+ previewLine: `${dropped.toLocaleString("en-US")} records dropped`,
1932
+ summary: `The log drain "${name}" (${view.type}) dropped ${dropped.toLocaleString("en-US")} records because its buffer filled up faster than the destination accepted them.${becauseClause(view)} The gap is reported in band on the next successful batch, so the range is recoverable from the destination itself.`,
1933
+ status: "failed",
1934
+ priority: "high"
1935
+ };
1936
+ }
1937
+ case "verification_pending":
1938
+ return {
1939
+ title: name,
1940
+ subject: `Log drain "${name}" is still unverified`,
1941
+ previewLine: "Unverified after 24 hours \u2014 nothing is being delivered",
1942
+ summary: `The log drain "${name}" (${view.type}) was created more than 24 hours ago and has never completed verification, so nothing is being delivered to it.${becauseClause(view)} Run \`lua drains verify ${view.id}\` once the destination echoes the verification token.`,
1943
+ status: "failed",
1944
+ priority: "normal"
1945
+ };
1946
+ default: {
1947
+ const exhaustive = payload.kind;
1948
+ throw new Error(`unhandled log-drain notice kind: ${String(exhaustive)}`);
1949
+ }
1950
+ }
1951
+ }
1952
+ function logDrainNoticeLabel(kind) {
1953
+ switch (kind) {
1954
+ case "failing":
1955
+ return "failing";
1956
+ case "paused_auto":
1957
+ return "paused (no success for 24 h)";
1958
+ case "paused_quota":
1959
+ return "paused (daily allowance exhausted)";
1960
+ case "quota_warning":
1961
+ return "80% of the daily allowance used";
1962
+ case "dropped":
1963
+ return "dropping records";
1964
+ case "verification_pending":
1965
+ return "still unverified";
1966
+ default: {
1967
+ const exhaustive = kind;
1968
+ throw new Error(`unhandled log-drain notice kind: ${String(exhaustive)}`);
1969
+ }
1970
+ }
1971
+ }
1972
+ function logDrainNoticeDigestCopy(payload) {
1973
+ const entries = [
1974
+ ...payload.entries ?? []
1975
+ ].sort((a, b) => a.at < b.at ? -1 : a.at > b.at ? 1 : 0).slice(0, LOG_DRAIN_DIGEST_MAX_ENTRIES);
1976
+ const drains = new Set(entries.map((entry) => entry.drainId));
1977
+ const noun = `${entries.length} notice${entries.length === 1 ? "" : "s"}`;
1978
+ const across = `${drains.size} drain${drains.size === 1 ? "" : "s"}`;
1979
+ const lines = entries.map((entry) => `${entry.drainName || entry.drainId} \u2014 ${logDrainNoticeLabel(entry.kind)}`);
1980
+ return {
1981
+ title: "Log drains \u2014 daily summary",
1982
+ subject: `Log drains \u2014 ${noun} in the last day`,
1983
+ previewLine: `${noun} across ${across}`,
1984
+ summary: `${noun} across ${across} in the last day: ${lines.join("; ")}.`,
1985
+ status: "failed",
1986
+ priority: "normal"
1987
+ };
1988
+ }
1989
+ function finiteNonNegative(value3) {
1990
+ return typeof value3 === "number" && Number.isFinite(value3) && value3 >= 0 ? value3 : void 0;
1991
+ }
1992
+ function mergeLimits(base, stored) {
1993
+ const patch = stored ?? {};
1994
+ return {
1995
+ eventsPerDay: finiteNonNegative(patch.eventsPerDay) ?? base.eventsPerDay,
1996
+ bytesPerDay: finiteNonNegative(patch.bytesPerDay) ?? base.bytesPerDay
1997
+ };
1998
+ }
1999
+ function mergeCard(base, stored) {
2000
+ const patch = stored ?? {};
2001
+ return {
2002
+ includedEventsPerMonth: finiteNonNegative(patch.includedEventsPerMonth) ?? base.includedEventsPerMonth,
2003
+ includedBytesPerMonth: finiteNonNegative(patch.includedBytesPerMonth) ?? base.includedBytesPerMonth,
2004
+ usdPerMillionEvents: finiteNonNegative(patch.usdPerMillionEvents) ?? base.usdPerMillionEvents,
2005
+ usdPerGiB: finiteNonNegative(patch.usdPerGiB) ?? base.usdPerGiB
2006
+ };
2007
+ }
2008
+ function resolveStoredLogDrainPlanConfig(stored) {
2009
+ const values = stored;
2010
+ if (!values || typeof values.version !== "number" || !Number.isFinite(values.version)) {
2011
+ return DEFAULT_LOG_DRAIN_PLAN_CONFIG;
2012
+ }
2013
+ const tiers = {};
2014
+ const pricing = {};
2015
+ for (const tier of TIERS) {
2016
+ tiers[tier] = mergeLimits(DEFAULT_LOG_DRAIN_PLAN_CONFIG.tiers[tier], values.tiers?.[tier]);
2017
+ pricing[tier] = mergeCard(DEFAULT_LOG_DRAIN_PLAN_CONFIG.pricing[tier], values.pricing?.[tier]);
2018
+ }
2019
+ const orgs = {};
2020
+ for (const [orgId, override] of Object.entries(values.orgs ?? {})) {
2021
+ if (!orgId || !override || typeof override !== "object") continue;
2022
+ const row2 = {};
2023
+ if (override.tier && TIERS.includes(override.tier)) row2.tier = override.tier;
2024
+ const events = finiteNonNegative(override.eventsPerDay);
2025
+ const bytes = finiteNonNegative(override.bytesPerDay);
2026
+ if (events !== void 0) row2.eventsPerDay = events;
2027
+ if (bytes !== void 0) row2.bytesPerDay = bytes;
2028
+ if (override.pricing && typeof override.pricing === "object") row2.pricing = {
2029
+ ...override.pricing
2030
+ };
2031
+ if (Object.keys(row2).length) orgs[orgId] = row2;
2032
+ }
2033
+ return {
2034
+ version: values.version,
2035
+ tiers,
2036
+ pricing,
2037
+ ...Object.keys(orgs).length ? {
2038
+ orgs
2039
+ } : {}
2040
+ };
2041
+ }
2042
+ function logDrainPlanTierOverride(config, orgId) {
2043
+ return orgId ? config.orgs?.[orgId]?.tier : void 0;
2044
+ }
2045
+ function logDrainQuotaLimitsFor(config, orgId, tier) {
2046
+ const base = config.tiers[tier] ?? config.tiers.team ?? LOG_DRAIN_QUOTAS.team;
2047
+ const override = orgId ? config.orgs?.[orgId] : void 0;
2048
+ if (!override) return {
2049
+ ...base
2050
+ };
2051
+ return {
2052
+ eventsPerDay: override.eventsPerDay ?? base.eventsPerDay,
2053
+ bytesPerDay: override.bytesPerDay ?? base.bytesPerDay
2054
+ };
2055
+ }
2056
+ function logDrainPriceCardFor(config, orgId, tier) {
2057
+ const base = config.pricing[tier] ?? config.pricing.team ?? DEFAULT_LOG_DRAIN_PLAN_CONFIG.pricing.team;
2058
+ const override = orgId ? config.orgs?.[orgId]?.pricing : void 0;
2059
+ return override ? mergeCard(base, override) : {
2060
+ ...base
2061
+ };
2062
+ }
2063
+ function priceDrainUsage(totals, card, tier) {
2064
+ const billableRecords = Math.max(0, Math.floor(finiteNonNegative(totals?.billableRecords) ?? 0));
2065
+ const billableBytes = Math.max(0, Math.floor(finiteNonNegative(totals?.billableBytes) ?? 0));
2066
+ const includedEvents = card.includedEventsPerMonth;
2067
+ const includedBytes = card.includedBytesPerMonth;
2068
+ const overageEvents = Math.max(0, billableRecords - includedEvents);
2069
+ const overageBytes = Math.max(0, billableBytes - includedBytes);
2070
+ const eventsUsd = overageEvents / 1e6 * card.usdPerMillionEvents;
2071
+ const bytesUsd = overageBytes / LOG_DRAIN_BYTES_PER_GIB * card.usdPerGiB;
2072
+ const unpriced = card.usdPerMillionEvents === 0 && card.usdPerGiB === 0;
2073
+ const round = /* @__PURE__ */ __name2((usd) => Math.round(usd * 100) / 100, "round");
2074
+ return {
2075
+ lineItem: LOG_DRAIN_USAGE_LINE_ITEM,
2076
+ tier,
2077
+ billableRecords,
2078
+ billableBytes,
2079
+ includedEvents,
2080
+ includedBytes,
2081
+ overageEvents,
2082
+ overageBytes,
2083
+ eventsUsd: round(eventsUsd),
2084
+ bytesUsd: round(bytesUsd),
2085
+ usd: round(Math.max(eventsUsd, bytesUsd)),
2086
+ unpriced
2087
+ };
2088
+ }
1465
2089
  function encodeLogCursor(c) {
1466
2090
  return Buffer.from(JSON.stringify({
1467
2091
  t: c.t,
@@ -2180,7 +2804,7 @@ function effectiveAgentFeatures(base, override, rule) {
2180
2804
  if (rule === "wholesale") return override || base || void 0;
2181
2805
  return effectiveAgentFeatureRows(base, override).rows;
2182
2806
  }
2183
- var __defProp2, __name2, PLUMBING_THREAD_CHANNELS, CHANNEL_CONVERSATION_CHANNELS, HIDDEN_THREAD_CHANNELS, REVIEWABLE_ACTION_EXECUTE_TOOL_ALLOWLIST, REVIEWABLE_STANDING_START_TOOLS, REVIEWABLE_MCP_SEND_TOOL_SUFFIX, MCP_TOOL_READ_VERB_RE, MCP_DRAFT_CREATE_VERBS, NON_INTERACTIVE_CHANNELS, RICH_PARTS_MESSAGE_ID_PREFIX, SCREENSHOT_MESSAGE_ID_PREFIX, BROWSER_COMMANDS, BROWSER_COMMAND_NAMES, DESKTOP_FILE_COMMANDS, DESKTOP_FILE_COMMAND_SET, WORKFLOW_RETRY_BACKOFFS, WORKFLOW_RETRY_MIN_ATTEMPTS, WORKFLOW_RETRY_POLICY_KEYS, WORKFLOW_RETRY_MAX_ATTEMPTS, WORKFLOW_RETRY_ENGINE_KEYS, WORKFLOW_JOB_RESOURCES, WORKFLOW_SIDE_EFFECTS, WORKFLOW_AGENT_EFFORTS, WORKFLOW_MODEL_CLASSES, WORKFLOW_TASK_CLASSES, WORKFLOW_MODEL_TRAITS, WORKFLOW_MODEL_REASON_MAX_LENGTH, WORKFLOW_MODELS_COMPOSE_MODES, WORKFLOW_MODELS_PIN_MODES, WORKFLOW_MODELS_FALLBACK_MODES, WORKFLOW_MODELS_CONSENT_ACTIONS_MIN, WORKFLOW_DEFAULT_TASK_CLASS_MAP, WORKFLOW_MODELS_MAX_CLASS_DEFAULT, WORKFLOW_AUTONOMY_FORMS, WORKFLOW_AUTONOMY_NUMERIC_KEYS, WORKFLOW_AUTONOMY_ENVELOPE_KEYS, WORKFLOW_AUTONOMY_ENABLED_DEFAULT, WORKFLOW_AUTONOMY_DEFAULTS, WORKFLOW_AUTONOMY_CEILINGS, WORKFLOW_AUTONOMY_MAX_RUNS_PER_HOUR_ENV, WORKFLOW_AUTONOMY_MAX_RUNS_PER_HOUR_CEILING, WORKFLOW_AUTONOMY_FORMS_DEFAULT, WORKFLOW_AUTONOMY_MIN_VALUE, WORKFLOW_JOB_RANGES, WORKFLOW_JOB_RANGE_MEMBERS, WORKFLOW_SINGLE_STEP_TYPES, WORKFLOW_HITL_ENTRY_TYPES, WORKFLOW_ARM_ENTRY_TYPES, WORKFLOW_HITL_ARM_CONTAINERS, WORKFLOW_GRAPH_ENTRY_STEP_KINDS, WORKFLOW_ARM_ENTRY_STEP_KINDS, WORKFLOW_BUDGET_MAX_DURATION_SECONDS, MODEL_ID_BYOK_PROVIDERS, MODEL_CLASS_REF_PREFIX, MODEL_SNAPSHOT_SUFFIX, REASONING_EFFORT_VALUES, IMPLICIT_MODEL_SELECTION_SOURCES, PLATFORM_FALLBACK_MODEL_SOURCE, AGENT_NAME_TOKEN, DEFAULT_PERSONA_GUIDE, PERSONAL_SPACE_STARTING_PERSONA, AGENT_LOG_SOURCES, AGENT_LOG_ENVIRONMENTS, LOG_DRAIN_TYPES_PHASE1, LOG_DRAIN_FORMATS, LOG_DRAIN_SEVERITIES, LOG_DRAIN_SOURCES, LOG_DRAIN_CONTENT_SOURCES, LOG_DRAIN_DEFAULT_SOURCES, LOG_DRAIN_MAX_PER_ORG, LOG_DRAIN_MAX_PER_ORG_ENTERPRISE, LOG_DRAIN_DATADOG_SITES, LOG_DRAIN_ENDPOINT_TEMPLATES, LOG_DRAIN_QUOTAS, LOG_DRAIN_QUOTA_WARN_RATIO, LOG_DRAIN_QUOTA_DROP_DEBUG_RATIO, LOG_DRAIN_QUOTA_DROP_INFO_RATIO, LOG_DRAIN_QUOTA_PAUSE_RATIO, LOG_CURSOR_MAX_LENGTH, LOG_CURSOR_MAX_ID_LENGTH, LOG_RELATIVE_TIME_PATTERN, LOG_ISO_TIME_PATTERN, RELATIVE_UNIT_MS, CORE_DRAINING_CODE, CORE_DRAINING_DEFAULT_RETRY_MS, CORE_DRAINING_MAX_RETRY_MS, VoiceNameSchema, PluginProviderSchema, RealtimeProviderSchema, PluginClassSchema, ModelDescriptorSchema, InferenceModelSchema, PluginModelSchema, RealtimeModelSchema, LuaVoiceModelSchema, TurnDetectionSchema, InterruptionSchema, BuiltinAudioClipSchema, AudioConfigSchema, BackgroundAudioEntrySchema, BackgroundAudioSchema, LuaVoiceConfigInnerSchema, LuaVoiceConfigSchema, LuaVoiceRefSchema, EventType, LUA_JOB_DEFAULT_TIMEOUT_SECONDS, LUA_JOB_MIN_TIMEOUT_SECONDS, LUA_JOB_MAX_TIMEOUT_SECONDS, KEYS, MAX_BODY_BYTES, MAX_ID_LENGTH, UTC_ISO, TEMPLATE_TRIGGER_URL_ENV_PREFIX, TEMPLATE_INSTALL_POLICY_PER_WORKSPACE_VALUES, SUBJECT_TYPES, SubjectTypeSchema, CREDENTIAL_TYPES, CredentialTypeSchema, DEVICE_OPERATIONS, DeviceOperationSchema, DEVICE_SCOPE_BY_OPERATION, DeviceBindingSchema, TYPED_API_KEY, IdSchema, SESSION_AUTH_TIME_MAX_S, PrincipalDescriptorSchema, ActorDescriptorSchema, PrincipalOwnerSchema, CredentialLifecycleSchema, GeneralCredentialDescriptorSchema, DeviceCredentialDescriptorSchema, GeneralPrincipalContextSchema, DeviceCredentialPrincipalContextSchema, RawPrincipalContextSchema, PrincipalContextSchema, DeviceCredentialClaimSchema, LUA_CLIENT_HEADER, LUA_CLIENT_APPS, SEMVER_PATTERN, WEB_RELEASE_PATTERN, CLIENT_HEADER_PATTERN, LUA_SESSION_ID_CLAIM, SESSION_REVOKED_CODE, AUTHZ_PROJECTION_VERSION, ProjectedScopeSchema, DisplayRoleSchema, AuthorizationPrincipalSchema, CredentialContextSchema, ProjectionAnomalySchema, ProjectedOrgSchema, ProjectedResourceSchema, CapabilityProfilesSchema, RoleCatalogSchema, EffectiveAuthorizationSchema, ResourcePageSchema, SYSTEM_USER_PREFIX, WORKFLOW_RUN_IN_FLIGHT, WORKFLOW_RUN_IDLE, WORKFLOW_RUN_TERMINAL, WORKFLOW_RUN_STATUSES, WORKFLOW_RUN_GATE_KINDS, WORKFLOW_STEP_STATUSES, WORKFLOW_STEP_IN_FLIGHT, WORKFLOW_SIGNAL_EVENT_SITES, ARCHIVE_WINDOW_MARGIN_DAYS, WORKFLOW_ORG_PURGING_TTL_S, WORKFLOW_ORG_PURGE_FORCE_AFTER_MS, IDEMPOTENCY_HOLDING_STATUSES, WORKFLOW_SCHEDULED_RUN_ID_PREFIX, CLOUD_TASK_RUN_ID_PREFIX, WORKFLOW_SCHEDULE_KEY_MAX, WORKFLOW_SCHEDULE_IDEMPOTENCY_KEY_PREFIX, WORKFLOW_OPERATION_ID_PREFIX, WORKFLOW_JOURNAL_PROTOCOL_VERSION, WORKFLOW_CONNECTION_KEY_RE, WORKFLOW_JOB_MODEL_SOURCE_LABELS, WORKFLOW_SUSPEND_KINDS, WORKFLOW_RUN_NOTIFICATION_SUSPENDED_KINDS, WORKFLOW_SIGNAL_PAYLOAD_MAX_BYTES, WORKFLOW_RESOLVE_OUTPUT_MAX_BYTES, WORKFLOW_GOAL_JUDGE_SELF, REDACTED_PLACEHOLDER, PROVIDER_MESSAGE_MAX_CHARS, ERROR_MESSAGE_MAX_CHARS, SECRET_LITERAL_PATTERNS, SECRET_NAME, SECRET_PAIR_PATTERNS, GROUP_COUNT, WORKFLOW_SECRET_KEY_RE, WORKFLOW_RESERVED_SECRET_KEYS, SCRUB_INPUT_MAX_CHARS, SCRUB_CUT_BACKOFF_CHARS, WORKFLOW_AUDIT_EVENTS, WORKFLOW_AUDIT_METADATA_MAX_BYTES, INDENT, WRAP_WIDTH, NOUNS, GET_TOOL_NAMES, PREAMBLE, WORKFLOW_APPROVAL_OUTPUT_DECISIONS, WORKFLOW_APPROVAL_OUTPUT_SCHEMA, JSON_FENCE_RE, DEFAULT_ON_AGENT_FEATURES, SUBAGENT_PER_KEY_FLAG_ENV, PER_KEY_FLAG_VALUES;
2807
+ var __defProp2, __name2, PLUMBING_THREAD_CHANNELS, CHANNEL_CONVERSATION_CHANNELS, HIDDEN_THREAD_CHANNELS, REVIEWABLE_ACTION_EXECUTE_TOOL_ALLOWLIST, REVIEWABLE_STANDING_START_TOOLS, REVIEWABLE_MCP_SEND_TOOL_SUFFIX, MCP_TOOL_READ_VERB_RE, MCP_DRAFT_CREATE_VERBS, NON_INTERACTIVE_CHANNELS, RICH_PARTS_MESSAGE_ID_PREFIX, SCREENSHOT_MESSAGE_ID_PREFIX, BROWSER_COMMANDS, BROWSER_COMMAND_NAMES, LOG_FIELDS_MAX_KEYS, LOG_FIELD_MAX_VALUE_BYTES, LOG_FIELDS_MAX_BYTES, LOG_FIELD_KEY_PATTERN, LOG_FIELD_SECRET_SHAPED_KEY_PATTERNS, LOG_FIELDS_CLAMPED_KEY, LOG_FIELD_ARRAY_COUNT_SUFFIX, LuaLogFieldsError, DESKTOP_FILE_COMMANDS, DESKTOP_FILE_COMMAND_SET, WORKFLOW_RETRY_BACKOFFS, WORKFLOW_RETRY_MIN_ATTEMPTS, WORKFLOW_RETRY_POLICY_KEYS, WORKFLOW_RETRY_MAX_ATTEMPTS, WORKFLOW_RETRY_ENGINE_KEYS, WORKFLOW_JOB_RESOURCES, WORKFLOW_SIDE_EFFECTS, WORKFLOW_AGENT_EFFORTS, WORKFLOW_MODEL_CLASSES, WORKFLOW_TASK_CLASSES, WORKFLOW_MODEL_TRAITS, WORKFLOW_MODEL_REASON_MAX_LENGTH, WORKFLOW_MODELS_COMPOSE_MODES, WORKFLOW_MODELS_PIN_MODES, WORKFLOW_MODELS_FALLBACK_MODES, WORKFLOW_MODELS_CONSENT_ACTIONS_MIN, WORKFLOW_DEFAULT_TASK_CLASS_MAP, WORKFLOW_MODELS_MAX_CLASS_DEFAULT, WORKFLOW_AUTONOMY_FORMS, WORKFLOW_AUTONOMY_NUMERIC_KEYS, WORKFLOW_AUTONOMY_ENVELOPE_KEYS, WORKFLOW_AUTONOMY_ENABLED_DEFAULT, WORKFLOW_AUTONOMY_DEFAULTS, WORKFLOW_AUTONOMY_CEILINGS, WORKFLOW_AUTONOMY_MAX_RUNS_PER_HOUR_ENV, WORKFLOW_AUTONOMY_MAX_RUNS_PER_HOUR_CEILING, WORKFLOW_AUTONOMY_FORMS_DEFAULT, WORKFLOW_AUTONOMY_MIN_VALUE, WORKFLOW_JOB_RANGES, WORKFLOW_JOB_RANGE_MEMBERS, WORKFLOW_SINGLE_STEP_TYPES, WORKFLOW_HITL_ENTRY_TYPES, WORKFLOW_ARM_ENTRY_TYPES, WORKFLOW_HITL_ARM_CONTAINERS, WORKFLOW_GRAPH_ENTRY_STEP_KINDS, WORKFLOW_ARM_ENTRY_STEP_KINDS, WORKFLOW_BUDGET_MAX_DURATION_SECONDS, MODEL_ID_BYOK_PROVIDERS, MODEL_CLASS_REF_PREFIX, MODEL_SNAPSHOT_SUFFIX, REASONING_EFFORT_VALUES, IMPLICIT_MODEL_SELECTION_SOURCES, PLATFORM_FALLBACK_MODEL_SOURCE, AGENT_NAME_TOKEN, DEFAULT_PERSONA_GUIDE, PERSONAL_SPACE_STARTING_PERSONA, AGENT_LOG_SOURCES, AGENT_LOG_ENVIRONMENTS, AGENT_LOG_FIELDS_MAX_WIRE_KEYS, LOG_DRAIN_TYPES_PHASE1, LOG_DRAIN_TYPES_PHASE2, LOG_DRAIN_TYPES_IMPLEMENTED, LOG_DRAIN_FORMATS, LOG_DRAIN_SEVERITIES, LOG_DRAIN_SOURCES, LOG_DRAIN_CONTENT_SOURCES, LOG_DRAIN_DEFAULT_SOURCES, LOG_DRAIN_MAX_PER_ORG, LOG_DRAIN_MAX_PER_ORG_ENTERPRISE, LOG_DRAIN_DATADOG_SITES, LOG_DRAIN_NEWRELIC_SITES, LOG_DRAIN_ENDPOINT_TEMPLATES, LOG_DRAIN_PRESET_SITES, LOG_DRAIN_BUCKET_NAME_PATTERN, LOG_DRAIN_BUCKET_HOST_PATTERNS, LOG_DRAIN_PRESET_HOST_SUFFIXES, DRAIN_CONTENT_ACK_ERROR_CODE, DRAIN_CONTENT_ACK_FIELD, DrainContentAckRequiredError, LOG_DRAIN_PRESET_PATHS, PRESET_SEGMENT_RE, LOG_DRAIN_QUOTAS, LOG_DRAIN_QUOTA_WARN_RATIO, LOG_DRAIN_QUOTA_DROP_DEBUG_RATIO, LOG_DRAIN_QUOTA_DROP_INFO_RATIO, LOG_DRAIN_QUOTA_PAUSE_RATIO, LOG_EXPORT_SOURCES, LOG_EXPORT_MAX_WINDOW_DAYS, LOG_EXPORT_MAX_WINDOW_MS, LOG_EXPORT_URL_TTL_SECONDS, LOG_EXPORT_FILE_RETENTION_DAYS, LOG_EXPORT_FILE_RETENTION_MS, LOG_EXPORT_PART_MAX_BYTES, LOG_DRAIN_NOTICE_RATE_LIMIT_MS, LOG_DRAIN_VERIFICATION_STALE_MS, LOG_DRAIN_DIGEST_INTERVAL_MS, LOG_DRAIN_DIGEST_MAX_ENTRIES, LOG_DRAIN_NOTICE_ERROR_MAX_CHARS, LOG_DRAIN_USAGE_LINE_ITEM, LOG_DRAIN_BYTES_PER_GIB, DEFAULT_LOG_DRAIN_PLAN_CONFIG, TIERS, LOG_CURSOR_MAX_LENGTH, LOG_CURSOR_MAX_ID_LENGTH, LOG_RELATIVE_TIME_PATTERN, LOG_ISO_TIME_PATTERN, RELATIVE_UNIT_MS, CORE_DRAINING_CODE, CORE_DRAINING_DEFAULT_RETRY_MS, CORE_DRAINING_MAX_RETRY_MS, VoiceNameSchema, PluginProviderSchema, RealtimeProviderSchema, PluginClassSchema, ModelDescriptorSchema, InferenceModelSchema, PluginModelSchema, RealtimeModelSchema, LuaVoiceModelSchema, TurnDetectionSchema, InterruptionSchema, BuiltinAudioClipSchema, AudioConfigSchema, BackgroundAudioEntrySchema, BackgroundAudioSchema, LuaVoiceConfigInnerSchema, LuaVoiceConfigSchema, LuaVoiceRefSchema, EventType, LUA_JOB_DEFAULT_TIMEOUT_SECONDS, LUA_JOB_MIN_TIMEOUT_SECONDS, LUA_JOB_MAX_TIMEOUT_SECONDS, KEYS, MAX_BODY_BYTES, MAX_ID_LENGTH, UTC_ISO, TEMPLATE_TRIGGER_URL_ENV_PREFIX, TEMPLATE_INSTALL_POLICY_PER_WORKSPACE_VALUES, SUBJECT_TYPES, SubjectTypeSchema, CREDENTIAL_TYPES, CredentialTypeSchema, DEVICE_OPERATIONS, DeviceOperationSchema, DEVICE_SCOPE_BY_OPERATION, DeviceBindingSchema, TYPED_API_KEY, IdSchema, SESSION_AUTH_TIME_MAX_S, PrincipalDescriptorSchema, ActorDescriptorSchema, PrincipalOwnerSchema, CredentialLifecycleSchema, GeneralCredentialDescriptorSchema, DeviceCredentialDescriptorSchema, GeneralPrincipalContextSchema, DeviceCredentialPrincipalContextSchema, RawPrincipalContextSchema, PrincipalContextSchema, DeviceCredentialClaimSchema, LUA_CLIENT_HEADER, LUA_CLIENT_APPS, SEMVER_PATTERN, WEB_RELEASE_PATTERN, CLIENT_HEADER_PATTERN, LUA_SESSION_ID_CLAIM, SESSION_REVOKED_CODE, AUTHZ_PROJECTION_VERSION, ProjectedScopeSchema, DisplayRoleSchema, AuthorizationPrincipalSchema, CredentialContextSchema, ProjectionAnomalySchema, ProjectedOrgSchema, ProjectedResourceSchema, CapabilityProfilesSchema, RoleCatalogSchema, EffectiveAuthorizationSchema, ResourcePageSchema, SYSTEM_USER_PREFIX, WORKFLOW_RUN_IN_FLIGHT, WORKFLOW_RUN_IDLE, WORKFLOW_RUN_TERMINAL, WORKFLOW_RUN_STATUSES, WORKFLOW_RUN_GATE_KINDS, WORKFLOW_STEP_STATUSES, WORKFLOW_STEP_IN_FLIGHT, WORKFLOW_SIGNAL_EVENT_SITES, ARCHIVE_WINDOW_MARGIN_DAYS, WORKFLOW_ORG_PURGING_TTL_S, WORKFLOW_ORG_PURGE_FORCE_AFTER_MS, IDEMPOTENCY_HOLDING_STATUSES, WORKFLOW_SCHEDULED_RUN_ID_PREFIX, CLOUD_TASK_RUN_ID_PREFIX, WORKFLOW_SCHEDULE_KEY_MAX, WORKFLOW_SCHEDULE_IDEMPOTENCY_KEY_PREFIX, WORKFLOW_OPERATION_ID_PREFIX, WORKFLOW_JOURNAL_PROTOCOL_VERSION, WORKFLOW_CONNECTION_KEY_RE, WORKFLOW_JOB_MODEL_SOURCE_LABELS, WORKFLOW_SUSPEND_KINDS, WORKFLOW_RUN_NOTIFICATION_SUSPENDED_KINDS, WORKFLOW_SIGNAL_PAYLOAD_MAX_BYTES, WORKFLOW_RESOLVE_OUTPUT_MAX_BYTES, WORKFLOW_GOAL_JUDGE_SELF, REDACTED_PLACEHOLDER, PROVIDER_MESSAGE_MAX_CHARS, ERROR_MESSAGE_MAX_CHARS, SECRET_LITERAL_PATTERNS, SECRET_NAME, SECRET_PAIR_PATTERNS, GROUP_COUNT, WORKFLOW_SECRET_KEY_RE, WORKFLOW_RESERVED_SECRET_KEYS, SCRUB_INPUT_MAX_CHARS, SCRUB_CUT_BACKOFF_CHARS, WORKFLOW_AUDIT_EVENTS, WORKFLOW_AUDIT_METADATA_MAX_BYTES, INDENT, WRAP_WIDTH, NOUNS, GET_TOOL_NAMES, PREAMBLE, WORKFLOW_APPROVAL_OUTPUT_DECISIONS, WORKFLOW_APPROVAL_OUTPUT_SCHEMA, JSON_FENCE_RE, DEFAULT_ON_AGENT_FEATURES, SUBAGENT_PER_KEY_FLAG_ENV, PER_KEY_FLAG_VALUES;
2184
2808
  var init_dist = __esm({
2185
2809
  "../shared-types/dist/index.mjs"() {
2186
2810
  "use strict";
@@ -2499,6 +3123,50 @@ var init_dist = __esm({
2499
3123
  }
2500
3124
  ];
2501
3125
  BROWSER_COMMAND_NAMES = BROWSER_COMMANDS.map((c) => c.name);
3126
+ LOG_FIELDS_MAX_KEYS = 32;
3127
+ LOG_FIELD_MAX_VALUE_BYTES = 1024;
3128
+ LOG_FIELDS_MAX_BYTES = 8192;
3129
+ LOG_FIELD_KEY_PATTERN = /^[a-z][a-z0-9_]{0,63}$/;
3130
+ LOG_FIELD_SECRET_SHAPED_KEY_PATTERNS = Object.freeze([
3131
+ /^api_[0-9a-f]{24,}$/,
3132
+ /^gh[pousr]_[a-z0-9]{36}$/,
3133
+ /^(?:sk|rk|pk)_(?:live|test)_[a-z0-9]{16,}$/,
3134
+ /^lua_hc_[a-z0-9]{20,}/,
3135
+ /^lua_sk_[a-z0-9]{20,}/
3136
+ ]);
3137
+ LOG_FIELDS_CLAMPED_KEY = "lua_fields_clamped";
3138
+ LOG_FIELD_ARRAY_COUNT_SUFFIX = ".count";
3139
+ LuaLogFieldsError = class _LuaLogFieldsError extends Error {
3140
+ static {
3141
+ __name(this, "_LuaLogFieldsError");
3142
+ }
3143
+ static {
3144
+ __name2(this, "LuaLogFieldsError");
3145
+ }
3146
+ constructor(message) {
3147
+ super(message);
3148
+ this.name = "LuaLogFieldsError";
3149
+ Object.setPrototypeOf(this, _LuaLogFieldsError.prototype);
3150
+ }
3151
+ };
3152
+ __name(utf8WidthAt, "utf8WidthAt");
3153
+ __name2(utf8WidthAt, "utf8WidthAt");
3154
+ __name(utf8ByteLength, "utf8ByteLength");
3155
+ __name2(utf8ByteLength, "utf8ByteLength");
3156
+ __name(clampUtf8, "clampUtf8");
3157
+ __name2(clampUtf8, "clampUtf8");
3158
+ __name(mapBytes, "mapBytes");
3159
+ __name2(mapBytes, "mapBytes");
3160
+ __name(withValueCap, "withValueCap");
3161
+ __name2(withValueCap, "withValueCap");
3162
+ __name(describeValue, "describeValue");
3163
+ __name2(describeValue, "describeValue");
3164
+ __name(assertValidLogFieldKey, "assertValidLogFieldKey");
3165
+ __name2(assertValidLogFieldKey, "assertValidLogFieldKey");
3166
+ __name(normalizeLogFields, "normalizeLogFields");
3167
+ __name2(normalizeLogFields, "normalizeLogFields");
3168
+ __name(clampLogFields, "clampLogFields");
3169
+ __name2(clampLogFields, "clampLogFields");
2502
3170
  DESKTOP_FILE_COMMANDS = [
2503
3171
  "files_roots",
2504
3172
  "files_stat",
@@ -2888,12 +3556,37 @@ This text is who you are for this person. As you learn them, their name, their w
2888
3556
  "production",
2889
3557
  "sandbox"
2890
3558
  ];
3559
+ AGENT_LOG_FIELDS_MAX_WIRE_KEYS = LOG_FIELDS_MAX_KEYS + 1;
3560
+ __name(utf8Bytes, "utf8Bytes");
3561
+ __name2(utf8Bytes, "utf8Bytes");
3562
+ __name(isPlainFieldKey, "isPlainFieldKey");
3563
+ __name2(isPlainFieldKey, "isPlainFieldKey");
3564
+ __name(describeAgentLogFieldsViolation, "describeAgentLogFieldsViolation");
3565
+ __name2(describeAgentLogFieldsViolation, "describeAgentLogFieldsViolation");
3566
+ __name(isAgentLogFields, "isAgentLogFields");
3567
+ __name2(isAgentLogFields, "isAgentLogFields");
2891
3568
  LOG_DRAIN_TYPES_PHASE1 = [
2892
3569
  "http",
2893
3570
  "otlp",
2894
3571
  "datadog",
2895
3572
  "betterstack"
2896
3573
  ];
3574
+ LOG_DRAIN_TYPES_PHASE2 = [
3575
+ "splunk",
3576
+ "loki",
3577
+ "axiom",
3578
+ "newrelic",
3579
+ "sumologic"
3580
+ ];
3581
+ LOG_DRAIN_TYPES_IMPLEMENTED = [
3582
+ ...LOG_DRAIN_TYPES_PHASE1,
3583
+ ...LOG_DRAIN_TYPES_PHASE2,
3584
+ // RECONCILE-4 (W4-4). `s3` is implemented (H4 / PRO-1887) but is NOT in
3585
+ // `LOG_DRAIN_TYPES_PHASE2`, because its own flag opens it and `*` must not. It
3586
+ // belongs here all the same: this list is what a CLIENT may offer, and the server
3587
+ // remains the authority that answers 422 with the live list.
3588
+ "s3"
3589
+ ];
2897
3590
  LOG_DRAIN_FORMATS = [
2898
3591
  "json",
2899
3592
  "ndjson"
@@ -2912,7 +3605,10 @@ This text is who you are for this person. As you learn them, their name, their w
2912
3605
  "trigger-execution",
2913
3606
  "job-execution",
2914
3607
  "workflow-run",
2915
- "workflow-audit"
3608
+ "workflow-audit",
3609
+ // PRO-1884 (H2). Appended AFTER 'workflow-audit' so the enum stays additive and
3610
+ // no existing member changes position.
3611
+ "voice-call"
2916
3612
  ];
2917
3613
  LOG_DRAIN_CONTENT_SOURCES = [
2918
3614
  "user_message",
@@ -2935,7 +3631,22 @@ This text is who you are for this person. As you learn them, their name, their w
2935
3631
  "mcp",
2936
3632
  "rag",
2937
3633
  "execution",
2938
- "agent_error"
3634
+ "agent_error",
3635
+ // ── phase 2 ──
3636
+ // PRO-1882 / H1. `delivery` carries one record per applied status transition of an
3637
+ // outbound message. It is on by default because a send that failed at the vendor is
3638
+ // operational truth a customer's own on-call needs, and nothing in it is content:
3639
+ // the recipient is masked to its last four characters unless the drain selects
3640
+ // `includeContent`.
3641
+ "delivery",
3642
+ // PRO-1884 / H2. The three adjacent execution ledgers. ON for a NEW drain: they are
3643
+ // the platform's own record of a dispatch, a run and a job outcome — the same class
3644
+ // of operational line the phase-1 defaults already carry, and none of them is
3645
+ // content-bearing without `includeContent`. A drain created before this shipped keeps
3646
+ // the source set it was created with; nothing is added to an existing drain.
3647
+ "trigger-execution",
3648
+ "job-execution",
3649
+ "workflow-run"
2939
3650
  ];
2940
3651
  LOG_DRAIN_MAX_PER_ORG = 5;
2941
3652
  LOG_DRAIN_MAX_PER_ORG_ENTERPRISE = 10;
@@ -2949,11 +3660,106 @@ This text is who you are for this person. As you learn them, their name, their w
2949
3660
  "ap1.datadoghq.com",
2950
3661
  "ddog-gov.com"
2951
3662
  ];
3663
+ LOG_DRAIN_NEWRELIC_SITES = [
3664
+ "log-api.newrelic.com",
3665
+ "log-api.eu.newrelic.com",
3666
+ "log-api.jp.newrelic.com"
3667
+ ];
2952
3668
  LOG_DRAIN_ENDPOINT_TEMPLATES = {
2953
- datadog: "https://http-intake.logs.{site}/api/v2/logs"
3669
+ datadog: "https://http-intake.logs.{site}/api/v2/logs",
3670
+ newrelic: "https://{site}/log/v1"
3671
+ };
3672
+ LOG_DRAIN_PRESET_SITES = {
3673
+ datadog: LOG_DRAIN_DATADOG_SITES,
3674
+ newrelic: LOG_DRAIN_NEWRELIC_SITES
2954
3675
  };
2955
3676
  __name(resolvePresetEndpoint, "resolvePresetEndpoint");
2956
3677
  __name2(resolvePresetEndpoint, "resolvePresetEndpoint");
3678
+ LOG_DRAIN_BUCKET_NAME_PATTERN = /^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$/;
3679
+ LOG_DRAIN_BUCKET_HOST_PATTERNS = Object.freeze({
3680
+ aws: [
3681
+ // virtual-host style, regional and legacy global
3682
+ /^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]\.s3(\.dualstack)?(\.[a-z0-9-]+)?\.amazonaws\.com$/,
3683
+ // path style
3684
+ /^s3(\.dualstack)?(\.[a-z0-9-]+)?\.amazonaws\.com$/
3685
+ ],
3686
+ r2: [
3687
+ /^[a-f0-9]{32}\.r2\.cloudflarestorage\.com$/
3688
+ ],
3689
+ gcs: [
3690
+ /^storage\.googleapis\.com$/
3691
+ ]
3692
+ });
3693
+ __name(isLogDrainBucketHostAllowed, "isLogDrainBucketHostAllowed");
3694
+ __name2(isLogDrainBucketHostAllowed, "isLogDrainBucketHostAllowed");
3695
+ __name(resolveDrainBucketEndpoint, "resolveDrainBucketEndpoint");
3696
+ __name2(resolveDrainBucketEndpoint, "resolveDrainBucketEndpoint");
3697
+ __name(drainBucketUsesPathStyle, "drainBucketUsesPathStyle");
3698
+ __name2(drainBucketUsesPathStyle, "drainBucketUsesPathStyle");
3699
+ __name(presetIntakeHosts, "presetIntakeHosts");
3700
+ __name2(presetIntakeHosts, "presetIntakeHosts");
3701
+ LOG_DRAIN_PRESET_HOST_SUFFIXES = {
3702
+ axiom: [
3703
+ ".axiom.co"
3704
+ ],
3705
+ sumologic: [
3706
+ ".sumologic.com"
3707
+ ]
3708
+ };
3709
+ __name(presetHostAllowlist, "presetHostAllowlist");
3710
+ __name2(presetHostAllowlist, "presetHostAllowlist");
3711
+ __name(drainHostAllowlistAllows, "drainHostAllowlistAllows");
3712
+ __name2(drainHostAllowlistAllows, "drainHostAllowlistAllows");
3713
+ __name(drainDestinationIsVendorPinned, "drainDestinationIsVendorPinned");
3714
+ __name2(drainDestinationIsVendorPinned, "drainDestinationIsVendorPinned");
3715
+ __name(drainVerificationOwnership, "drainVerificationOwnership");
3716
+ __name2(drainVerificationOwnership, "drainVerificationOwnership");
3717
+ __name(drainOwnershipOrDefault, "drainOwnershipOrDefault");
3718
+ __name2(drainOwnershipOrDefault, "drainOwnershipOrDefault");
3719
+ DRAIN_CONTENT_ACK_ERROR_CODE = "DRAIN_CONTENT_ACK_REQUIRED";
3720
+ DRAIN_CONTENT_ACK_FIELD = "includeContentAck";
3721
+ DrainContentAckRequiredError = class extends Error {
3722
+ static {
3723
+ __name(this, "DrainContentAckRequiredError");
3724
+ }
3725
+ static {
3726
+ __name2(this, "DrainContentAckRequiredError");
3727
+ }
3728
+ code = DRAIN_CONTENT_ACK_ERROR_CODE;
3729
+ field = DRAIN_CONTENT_ACK_FIELD;
3730
+ constructor() {
3731
+ super("includeContent ships end-user message content to a third party; set includeContentAck: true to confirm.");
3732
+ this.name = "DrainContentAckRequiredError";
3733
+ }
3734
+ };
3735
+ __name(assertDrainContentAck, "assertDrainContentAck");
3736
+ __name2(assertDrainContentAck, "assertDrainContentAck");
3737
+ LOG_DRAIN_PRESET_PATHS = {
3738
+ // https://docs.splunk.com/Documentation/Splunk/latest/Data/HECExamples
3739
+ splunk: {
3740
+ path: "/services/collector/event"
3741
+ },
3742
+ // https://grafana.com/docs/loki/latest/reference/loki-http-api/#ingest-logs
3743
+ loki: {
3744
+ path: "/loki/api/v1/push"
3745
+ },
3746
+ // https://axiom.co/docs/restapi/endpoints/ingestIntoDataset
3747
+ axiom: {
3748
+ path: "/v1/datasets",
3749
+ segment: "dataset",
3750
+ suffix: "/ingest"
3751
+ },
3752
+ // https://help.sumologic.com/docs/send-data/hosted-collectors/http-source/logs-metrics/upload-logs/
3753
+ sumologic: {
3754
+ path: "/receiver/v1/http",
3755
+ segment: "token"
3756
+ }
3757
+ };
3758
+ PRESET_SEGMENT_RE = /^[A-Za-z0-9._~%=+-]{1,512}$/;
3759
+ __name(composePresetEndpoint, "composePresetEndpoint");
3760
+ __name2(composePresetEndpoint, "composePresetEndpoint");
3761
+ __name(joinPresetEndpointToken, "joinPresetEndpointToken");
3762
+ __name2(joinPresetEndpointToken, "joinPresetEndpointToken");
2957
3763
  LOG_DRAIN_QUOTAS = {
2958
3764
  team: {
2959
3765
  eventsPerDay: 1e6,
@@ -2986,6 +3792,100 @@ This text is who you are for this person. As you learn them, their name, their w
2986
3792
  __name2(logDrainQuotaWarned, "logDrainQuotaWarned");
2987
3793
  __name(logDrainQuotaDroppedSeverities, "logDrainQuotaDroppedSeverities");
2988
3794
  __name2(logDrainQuotaDroppedSeverities, "logDrainQuotaDroppedSeverities");
3795
+ LOG_EXPORT_SOURCES = [
3796
+ ...AGENT_LOG_SOURCES,
3797
+ "execution"
3798
+ ];
3799
+ LOG_EXPORT_MAX_WINDOW_DAYS = 31;
3800
+ LOG_EXPORT_MAX_WINDOW_MS = LOG_EXPORT_MAX_WINDOW_DAYS * 24 * 60 * 60 * 1e3;
3801
+ LOG_EXPORT_URL_TTL_SECONDS = 24 * 60 * 60;
3802
+ LOG_EXPORT_FILE_RETENTION_DAYS = 7;
3803
+ LOG_EXPORT_FILE_RETENTION_MS = LOG_EXPORT_FILE_RETENTION_DAYS * 24 * 60 * 60 * 1e3;
3804
+ LOG_EXPORT_PART_MAX_BYTES = 256 * 1024 * 1024;
3805
+ __name(isLogExportSource, "isLogExportSource");
3806
+ __name2(isLogExportSource, "isLogExportSource");
3807
+ LOG_DRAIN_NOTICE_RATE_LIMIT_MS = 60 * 6e4;
3808
+ LOG_DRAIN_VERIFICATION_STALE_MS = 24 * 60 * 6e4;
3809
+ LOG_DRAIN_DIGEST_INTERVAL_MS = 24 * 60 * 6e4;
3810
+ LOG_DRAIN_DIGEST_MAX_ENTRIES = 50;
3811
+ LOG_DRAIN_NOTICE_ERROR_MAX_CHARS = 200;
3812
+ __name(clamp, "clamp");
3813
+ __name2(clamp, "clamp");
3814
+ __name(numberOr, "numberOr");
3815
+ __name2(numberOr, "numberOr");
3816
+ __name(isoOr, "isoOr");
3817
+ __name2(isoOr, "isoOr");
3818
+ __name(logDrainNoticeView, "logDrainNoticeView");
3819
+ __name2(logDrainNoticeView, "logDrainNoticeView");
3820
+ __name(logDrainHealthCardPath, "logDrainHealthCardPath");
3821
+ __name2(logDrainHealthCardPath, "logDrainHealthCardPath");
3822
+ __name(percent, "percent");
3823
+ __name2(percent, "percent");
3824
+ __name(becauseClause, "becauseClause");
3825
+ __name2(becauseClause, "becauseClause");
3826
+ __name(logDrainNoticeCopy, "logDrainNoticeCopy");
3827
+ __name2(logDrainNoticeCopy, "logDrainNoticeCopy");
3828
+ __name(logDrainNoticeLabel, "logDrainNoticeLabel");
3829
+ __name2(logDrainNoticeLabel, "logDrainNoticeLabel");
3830
+ __name(logDrainNoticeDigestCopy, "logDrainNoticeDigestCopy");
3831
+ __name2(logDrainNoticeDigestCopy, "logDrainNoticeDigestCopy");
3832
+ LOG_DRAIN_USAGE_LINE_ITEM = "log_drain_events";
3833
+ LOG_DRAIN_BYTES_PER_GIB = 1024 ** 3;
3834
+ DEFAULT_LOG_DRAIN_PLAN_CONFIG = {
3835
+ version: 1,
3836
+ tiers: {
3837
+ team: {
3838
+ ...LOG_DRAIN_QUOTAS.team
3839
+ },
3840
+ business: {
3841
+ ...LOG_DRAIN_QUOTAS.business
3842
+ },
3843
+ enterprise: {
3844
+ ...LOG_DRAIN_QUOTAS.enterprise
3845
+ }
3846
+ },
3847
+ pricing: {
3848
+ team: {
3849
+ includedEventsPerMonth: 0,
3850
+ includedBytesPerMonth: 0,
3851
+ usdPerMillionEvents: 0,
3852
+ usdPerGiB: 0
3853
+ },
3854
+ business: {
3855
+ includedEventsPerMonth: 0,
3856
+ includedBytesPerMonth: 0,
3857
+ usdPerMillionEvents: 0,
3858
+ usdPerGiB: 0
3859
+ },
3860
+ enterprise: {
3861
+ includedEventsPerMonth: 0,
3862
+ includedBytesPerMonth: 0,
3863
+ usdPerMillionEvents: 0,
3864
+ usdPerGiB: 0
3865
+ }
3866
+ }
3867
+ };
3868
+ TIERS = [
3869
+ "team",
3870
+ "business",
3871
+ "enterprise"
3872
+ ];
3873
+ __name(finiteNonNegative, "finiteNonNegative");
3874
+ __name2(finiteNonNegative, "finiteNonNegative");
3875
+ __name(mergeLimits, "mergeLimits");
3876
+ __name2(mergeLimits, "mergeLimits");
3877
+ __name(mergeCard, "mergeCard");
3878
+ __name2(mergeCard, "mergeCard");
3879
+ __name(resolveStoredLogDrainPlanConfig, "resolveStoredLogDrainPlanConfig");
3880
+ __name2(resolveStoredLogDrainPlanConfig, "resolveStoredLogDrainPlanConfig");
3881
+ __name(logDrainPlanTierOverride, "logDrainPlanTierOverride");
3882
+ __name2(logDrainPlanTierOverride, "logDrainPlanTierOverride");
3883
+ __name(logDrainQuotaLimitsFor, "logDrainQuotaLimitsFor");
3884
+ __name2(logDrainQuotaLimitsFor, "logDrainQuotaLimitsFor");
3885
+ __name(logDrainPriceCardFor, "logDrainPriceCardFor");
3886
+ __name2(logDrainPriceCardFor, "logDrainPriceCardFor");
3887
+ __name(priceDrainUsage, "priceDrainUsage");
3888
+ __name2(priceDrainUsage, "priceDrainUsage");
2989
3889
  LOG_CURSOR_MAX_LENGTH = 512;
2990
3890
  LOG_CURSOR_MAX_ID_LENGTH = 128;
2991
3891
  LOG_RELATIVE_TIME_PATTERN = /^(\d{1,9})([smhd])$/;
@@ -5218,15 +6118,15 @@ function validateApproverBlock(node, opts = {
5218
6118
  const users = spec?.users;
5219
6119
  if (Array.isArray(users) && users.length > APPROVER_SPEC_MAX_USERS) push("cap-exceeded", path26, `at most ${APPROVER_SPEC_MAX_USERS} users`);
5220
6120
  else {
5221
- const refusal = describeApproverSpecRefusal(spec);
6121
+ const refusal2 = describeApproverSpecRefusal(spec);
5222
6122
  issues.push({
5223
6123
  code: "approver-invalid",
5224
6124
  path: path26,
5225
6125
  severity: "error",
5226
- message: refusal.message,
6126
+ message: refusal2.message,
5227
6127
  repair: {
5228
- approver: refusal.approver,
5229
- written: refusal.written
6128
+ approver: refusal2.approver,
6129
+ written: refusal2.written
5230
6130
  }
5231
6131
  });
5232
6132
  }
@@ -13765,7 +14665,26 @@ var init_ast_helpers = __esm({
13765
14665
  });
13766
14666
 
13767
14667
  // src/compiler/utils/class-hierarchy.ts
14668
+ import path3 from "path";
13768
14669
  import { Node as Node2 } from "ts-morph";
14670
+ function normalizePath(filePath) {
14671
+ return path3.resolve(filePath).split(path3.sep).join("/");
14672
+ }
14673
+ function sdkDistDir() {
14674
+ if (sdkDistDirCache !== void 0) return sdkDistDirCache;
14675
+ try {
14676
+ sdkDistDirCache = `${normalizePath(getPackageRoot())}/dist`;
14677
+ } catch {
14678
+ sdkDistDirCache = null;
14679
+ }
14680
+ return sdkDistDirCache;
14681
+ }
14682
+ function isSdkDeclaration(classDecl) {
14683
+ const filePath = normalizePath(classDecl.getSourceFile().getFilePath());
14684
+ if (filePath.includes(SDK_NODE_MODULES_SEGMENT)) return true;
14685
+ const distDir = sdkDistDir();
14686
+ return distDir !== null && filePath.startsWith(`${distDir}/`);
14687
+ }
13769
14688
  function getClassHierarchy(classDecl, maxDepth = DEFAULT_MAX_DEPTH) {
13770
14689
  const chain = [
13771
14690
  classDecl
@@ -13794,9 +14713,10 @@ function extendsAny(classDecl, baseNames) {
13794
14713
  }
13795
14714
  return false;
13796
14715
  }
13797
- function findClassMember(classDecl, memberName, kind = "either") {
14716
+ function findClassMember(classDecl, memberName, kind = "either", options = {}) {
13798
14717
  const chain = getClassHierarchy(classDecl);
13799
14718
  for (const cls of chain) {
14719
+ if (!options.includeSdkDeclarations && isSdkDeclaration(cls)) break;
13800
14720
  if (kind === "property" || kind === "either") {
13801
14721
  const prop = cls.getProperty(memberName);
13802
14722
  if (prop && Node2.isPropertyDeclaration(prop)) {
@@ -13837,11 +14757,16 @@ function safeGetBaseClass(classDecl) {
13837
14757
  function classKey(c) {
13838
14758
  return `${c.getSourceFile().getFilePath()}#${c.getName() ?? "<anonymous>"}#${c.getStartLineNumber()}`;
13839
14759
  }
13840
- var DEFAULT_MAX_DEPTH;
14760
+ var DEFAULT_MAX_DEPTH, SDK_NODE_MODULES_SEGMENT, sdkDistDirCache;
13841
14761
  var init_class_hierarchy = __esm({
13842
14762
  "src/compiler/utils/class-hierarchy.ts"() {
13843
14763
  "use strict";
14764
+ init_package_root();
13844
14765
  DEFAULT_MAX_DEPTH = 10;
14766
+ SDK_NODE_MODULES_SEGMENT = "/node_modules/lua-cli/";
14767
+ __name(normalizePath, "normalizePath");
14768
+ __name(sdkDistDir, "sdkDistDir");
14769
+ __name(isSdkDeclaration, "isSdkDeclaration");
13845
14770
  __name(getClassHierarchy, "getClassHierarchy");
13846
14771
  __name(extendsAny, "extendsAny");
13847
14772
  __name(findClassMember, "findClassMember");
@@ -14858,9 +15783,8 @@ var init_job_plugin = __esm({
14858
15783
  });
14859
15784
 
14860
15785
  // src/compiler/plugins/webhook.plugin.ts
14861
- import path3 from "path";
14862
15786
  import { Node as Node6 } from "ts-morph";
14863
- var LUA_CLI_DECLARATION_PATH, WebhookPlugin;
15787
+ var WebhookPlugin;
14864
15788
  var init_webhook_plugin = __esm({
14865
15789
  "src/compiler/plugins/webhook.plugin.ts"() {
14866
15790
  "use strict";
@@ -14868,8 +15792,6 @@ var init_webhook_plugin = __esm({
14868
15792
  init_ast_helpers();
14869
15793
  init_class_hierarchy();
14870
15794
  init_schema_converter();
14871
- init_package_root();
14872
- LUA_CLI_DECLARATION_PATH = path3.join(getPackageRoot(), "dist", "api-exports.d.ts");
14873
15795
  WebhookPlugin = class extends BasePlugin {
14874
15796
  static {
14875
15797
  __name(this, "WebhookPlugin");
@@ -14896,8 +15818,7 @@ var init_webhook_plugin = __esm({
14896
15818
  const exportName = isDefaultExport ? "default" : className;
14897
15819
  const name = readStringMember(classDecl, "name") ?? deriveNameFromClassName(className);
14898
15820
  const description = readStringMember(classDecl, "description") ?? "";
14899
- const secretMember = findClassMember(classDecl, "secret", "property");
14900
- const hasSecret = secretMember !== void 0 && !(secretMember.owner.getName() === "LuaWebhook" && path3.resolve(secretMember.owner.getSourceFile().getFilePath()) === path3.resolve(LUA_CLI_DECLARATION_PATH));
15821
+ const hasSecret = findClassMember(classDecl, "secret", "property") !== void 0;
14901
15822
  return {
14902
15823
  kind: this.kind,
14903
15824
  name,
@@ -18692,13 +19613,15 @@ var init_voice_plugin = __esm({
18692
19613
 
18693
19614
  // ../shared-sandbox/dist/index.mjs
18694
19615
  import vm from "vm";
18695
- import { createRequire } from "module";
19616
+ import { builtinModules, createRequire } from "module";
18696
19617
  import path6 from "path";
18697
19618
  import dns from "dns";
18698
19619
  import net2 from "net";
18699
19620
  import { promisify } from "util";
18700
19621
  import * as net from "net";
18701
19622
  import { Agent as UndiciAgent } from "undici";
19623
+ import { isIP } from "net";
19624
+ import { Agent } from "undici";
18702
19625
  import vm2 from "vm";
18703
19626
  function buildSandboxProcess(opts) {
18704
19627
  const hrtimeFn = /* @__PURE__ */ __name6((time) => process.hrtime(time), "hrtimeFn");
@@ -18841,10 +19764,13 @@ function buildSandboxGlobals() {
18841
19764
  undefined: void 0
18842
19765
  };
18843
19766
  }
19767
+ function isRequireBlocklisted(id) {
19768
+ return REQUIRE_BLOCKLIST.has(id) || BLOCKLISTED_PACKAGES.some((name) => id.startsWith(`${name}/`));
19769
+ }
18844
19770
  function buildSandboxRequire(opts) {
18845
19771
  const realRequire = createRequire(path6.join(opts.cwd, "package.json"));
18846
19772
  const wrapped = /* @__PURE__ */ __name6((id) => {
18847
- if (REQUIRE_BLOCKLIST.has(id)) {
19773
+ if (isRequireBlocklisted(id)) {
18848
19774
  const evt = {
18849
19775
  event: "sandbox_require_blocklisted",
18850
19776
  module: id,
@@ -19586,6 +20512,67 @@ function resolveExtraAllowedHostnames() {
19586
20512
  if (!csv) return [];
19587
20513
  return csv.split(",").map((s) => s.trim()).filter(Boolean);
19588
20514
  }
20515
+ function pinnedLookup(addresses) {
20516
+ const rows3 = addresses.map((address) => ({
20517
+ address,
20518
+ family: isIP(address) === 6 ? 6 : 4
20519
+ }));
20520
+ return (hostname, optionsOrCb, maybeCb) => {
20521
+ const options = typeof optionsOrCb === "function" ? {} : optionsOrCb ?? {};
20522
+ const cb = typeof optionsOrCb === "function" ? optionsOrCb : maybeCb;
20523
+ const family = options.family === 4 || options.family === 6 ? options.family : void 0;
20524
+ const matching = family ? rows3.filter((row2) => row2.family === family) : rows3;
20525
+ if (matching.length === 0) {
20526
+ const error = new Error(`pinned lookup has no address for ${hostname}`);
20527
+ error.code = "ENOTFOUND";
20528
+ error.syscall = "getaddrinfo";
20529
+ error.hostname = hostname;
20530
+ cb(error);
20531
+ return;
20532
+ }
20533
+ if (options.all) {
20534
+ cb(null, matching);
20535
+ return;
20536
+ }
20537
+ cb(null, matching[0].address, matching[0].family);
20538
+ };
20539
+ }
20540
+ function pinnedDispatcherFor(addresses) {
20541
+ if (!addresses || addresses.length === 0) return void 0;
20542
+ const pinned = [
20543
+ ...addresses
20544
+ ];
20545
+ const dispatcher = new Agent({
20546
+ connect: {
20547
+ // undici forwards `connect.lookup` to `net.createConnection`, so this is the
20548
+ // hook that runs at TCP-connection time — the same hook the sandbox guard uses.
20549
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
20550
+ lookup: pinnedLookup(pinned)
20551
+ }
20552
+ });
20553
+ dispatcher[PINNED_ADDRESSES] = pinned;
20554
+ return {
20555
+ dispatcher,
20556
+ addresses: pinned,
20557
+ // `destroy`, not `close`: close waits for in-flight requests, and this is called
20558
+ // once the body has been read or cancelled, where waiting can only hang.
20559
+ close: /* @__PURE__ */ __name6(async () => {
20560
+ try {
20561
+ await dispatcher.destroy();
20562
+ } catch {
20563
+ }
20564
+ }, "close")
20565
+ };
20566
+ }
20567
+ function pinnedAddressesOf(dispatcher) {
20568
+ return dispatcher?.[PINNED_ADDRESSES];
20569
+ }
20570
+ function withPinnedDispatcher(init3, pin) {
20571
+ return pin ? {
20572
+ ...init3,
20573
+ dispatcher: pin.dispatcher
20574
+ } : init3;
20575
+ }
19589
20576
  function wrapBundleAsCjsModule(source) {
19590
20577
  return "(function(module, exports, require) {\n" + source + "\n})(module, module.exports, require);";
19591
20578
  }
@@ -19690,7 +20677,7 @@ function logUserCodeOrError(logger, message, error, context = {}) {
19690
20677
  logger.error(message, error, context);
19691
20678
  }
19692
20679
  }
19693
- var __defProp6, __name6, REQUIRE_BLOCKLIST, SANDBOX_FAKE_DIRNAME, SANDBOX_FAKE_FILENAME, EGRESS_DENIED_CODE, EGRESS_PROBE_DEFAULT_MS, PRIVATE_RANGES, dnsLookup, DEFAULT_BLOCK_CIDRS, HOSTNAME_NON_OVERRIDABLE_CIDRS, WorkflowDeterminismError, WORKFLOW_SCRIPT_REMOVED_GLOBALS, WORKFLOW_SCRIPT_TRAPPED_GLOBALS, WORKFLOW_SCRIPT_FORBIDDEN_PLATFORM_KEYS, USER_CODE_ERROR_CODE, PLATFORM_VM_ERROR_CODE, UserCodeError, DEFAULT_MAX_CAUSE_DEPTH, MissingPrimitiveCodeError;
20680
+ var __defProp6, __name6, REQUIRE_BLOCKLIST, SANDBOX_FAKE_DIRNAME, SANDBOX_FAKE_FILENAME, BLOCKLISTED_PACKAGES, EGRESS_DENIED_CODE, EGRESS_PROBE_DEFAULT_MS, PRIVATE_RANGES, dnsLookup, DEFAULT_BLOCK_CIDRS, HOSTNAME_NON_OVERRIDABLE_CIDRS, PINNED_ADDRESSES, WorkflowDeterminismError, WORKFLOW_SCRIPT_REMOVED_GLOBALS, WORKFLOW_SCRIPT_TRAPPED_GLOBALS, WORKFLOW_SCRIPT_FORBIDDEN_PLATFORM_KEYS, USER_CODE_ERROR_CODE, PLATFORM_VM_ERROR_CODE, UserCodeError, DEFAULT_MAX_CAUSE_DEPTH, MissingPrimitiveCodeError;
19694
20681
  var init_dist4 = __esm({
19695
20682
  "../shared-sandbox/dist/index.mjs"() {
19696
20683
  "use strict";
@@ -19734,6 +20721,11 @@ var init_dist4 = __esm({
19734
20721
  ]);
19735
20722
  SANDBOX_FAKE_DIRNAME = "/";
19736
20723
  SANDBOX_FAKE_FILENAME = "/index.ts";
20724
+ BLOCKLISTED_PACKAGES = [
20725
+ ...REQUIRE_BLOCKLIST
20726
+ ].filter((id) => !id.startsWith("node:") && !builtinModules.includes(id));
20727
+ __name(isRequireBlocklisted, "isRequireBlocklisted");
20728
+ __name6(isRequireBlocklisted, "isRequireBlocklisted");
19737
20729
  __name(buildSandboxRequire, "buildSandboxRequire");
19738
20730
  __name6(buildSandboxRequire, "buildSandboxRequire");
19739
20731
  EGRESS_DENIED_CODE = "EGRESS_DENIED";
@@ -19849,6 +20841,15 @@ var init_dist4 = __esm({
19849
20841
  __name6(resolveExtraAllowedCidrs, "resolveExtraAllowedCidrs");
19850
20842
  __name(resolveExtraAllowedHostnames, "resolveExtraAllowedHostnames");
19851
20843
  __name6(resolveExtraAllowedHostnames, "resolveExtraAllowedHostnames");
20844
+ PINNED_ADDRESSES = /* @__PURE__ */ Symbol.for("lua.pinnedDispatch.addresses");
20845
+ __name(pinnedLookup, "pinnedLookup");
20846
+ __name6(pinnedLookup, "pinnedLookup");
20847
+ __name(pinnedDispatcherFor, "pinnedDispatcherFor");
20848
+ __name6(pinnedDispatcherFor, "pinnedDispatcherFor");
20849
+ __name(pinnedAddressesOf, "pinnedAddressesOf");
20850
+ __name6(pinnedAddressesOf, "pinnedAddressesOf");
20851
+ __name(withPinnedDispatcher, "withPinnedDispatcher");
20852
+ __name6(withPinnedDispatcher, "withPinnedDispatcher");
19852
20853
  __name(wrapBundleAsCjsModule, "wrapBundleAsCjsModule");
19853
20854
  __name6(wrapBundleAsCjsModule, "wrapBundleAsCjsModule");
19854
20855
  __name(runBundleInContext, "runBundleInContext");
@@ -20481,6 +21482,12 @@ function safeDate() {
20481
21482
  SafeDate.UTC = Date.UTC;
20482
21483
  return SafeDate;
20483
21484
  }
21485
+ function safeMath() {
21486
+ const math = {};
21487
+ for (const key of Reflect.ownKeys(Math)) math[key] = Math[key];
21488
+ math.random = notStaticStub("Math.random");
21489
+ return Object.freeze(math);
21490
+ }
20484
21491
  function createSerializationContext() {
20485
21492
  const moduleRef = {
20486
21493
  exports: {}
@@ -20536,10 +21543,7 @@ function createSerializationContext() {
20536
21543
  setImmediate: notStaticStub("setImmediate"),
20537
21544
  queueMicrotask: notStaticStub("queueMicrotask"),
20538
21545
  Date: safeDate(),
20539
- Math: Object.freeze({
20540
- ...Math,
20541
- random: notStaticStub("Math.random")
20542
- }),
21546
+ Math: safeMath(),
20543
21547
  process: {
20544
21548
  env: new Proxy({}, {
20545
21549
  get: /* @__PURE__ */ __name((_t, prop) => typeof prop === "symbol" ? void 0 : notStaticStub(`process.env.${String(prop)}`)(), "get")
@@ -21131,6 +22135,7 @@ var init_graph_serializer = __esm({
21131
22135
  "Workflows"
21132
22136
  ];
21133
22137
  __name(safeDate, "safeDate");
22138
+ __name(safeMath, "safeMath");
21134
22139
  __name(createSerializationContext, "createSerializationContext");
21135
22140
  __name(serializeViaVm, "serializeViaVm");
21136
22141
  __name(locateWorkflowExpression, "locateWorkflowExpression");
@@ -34856,7 +35861,7 @@ async function runWorkflowLocallyInner(rawOpts) {
34856
35861
  ]
34857
35862
  ]));
34858
35863
  let seq = 0;
34859
- const emit2 = /* @__PURE__ */ __name((type, stepId, payload) => {
35864
+ const emit3 = /* @__PURE__ */ __name((type, stepId, payload) => {
34860
35865
  seq += 1;
34861
35866
  ledger.events.push({
34862
35867
  seq,
@@ -34939,7 +35944,7 @@ async function runWorkflowLocallyInner(rawOpts) {
34939
35944
  completedAt: clock
34940
35945
  });
34941
35946
  if (status === "completed") stepResults[row2.stepId] = row2.output;
34942
- emit2(`step.${status}`, row2.stepId, row2.error ? {
35947
+ emit3(`step.${status}`, row2.stepId, row2.error ? {
34943
35948
  error: row2.error
34944
35949
  } : void 0);
34945
35950
  say(`[${stamp()}] ${row2.stepId} \xB7 ${status}${row2.error ? ` \xB7 ${row2.error.code}: ${row2.error.message}` : ""}`);
@@ -35113,7 +36118,7 @@ async function runWorkflowLocallyInner(rawOpts) {
35113
36118
  throw new BailRunSignal(output2);
35114
36119
  }, "bailRun"),
35115
36120
  log: /* @__PURE__ */ __name((message) => {
35116
- emit2("step.progress", row2.stepId, {
36121
+ emit3("step.progress", row2.stepId, {
35117
36122
  message
35118
36123
  });
35119
36124
  say(`[${stamp()}] ${row2.stepId} \xB7 log \xB7 ${message}`);
@@ -35182,7 +36187,7 @@ async function runWorkflowLocallyInner(rawOpts) {
35182
36187
  clock += wallS * 1e3;
35183
36188
  const requeued = s < segments;
35184
36189
  const nextSession = `session-${id}-s${s}`;
35185
- emit2("step.checkpointed", id, {
36190
+ emit3("step.checkpointed", id, {
35186
36191
  segment: s,
35187
36192
  segmentsMax: segments,
35188
36193
  headSha: `local${s}`,
@@ -35239,7 +36244,7 @@ async function runWorkflowLocallyInner(rawOpts) {
35239
36244
  } catch (e) {
35240
36245
  if (e instanceof SuspendSignal) {
35241
36246
  row2.status = "suspended";
35242
- emit2("step.suspended", id, {
36247
+ emit3("step.suspended", id, {
35243
36248
  kind: "input"
35244
36249
  });
35245
36250
  say(`[${stamp()}] ${id} \xB7 suspended \xB7 awaiting resumeData`);
@@ -35304,7 +36309,7 @@ async function runWorkflowLocallyInner(rawOpts) {
35304
36309
  }
35305
36310
  for (; ; ) {
35306
36311
  say(`[park:${row2.stepId}] ${code} \u2014 retry/skip/complete/fail?`);
35307
- emit2("step.parked", row2.stepId, {
36312
+ emit3("step.parked", row2.stepId, {
35308
36313
  code
35309
36314
  });
35310
36315
  const answer = await opts.prompt("park", row2.stepId, {
@@ -35356,7 +36361,7 @@ async function runWorkflowLocallyInner(rawOpts) {
35356
36361
  const maxAttempts = retryOf(single);
35357
36362
  row2.status = "running";
35358
36363
  row2.startedAt = clock;
35359
- emit2("step.started", row2.stepId);
36364
+ emit3("step.started", row2.stepId);
35360
36365
  const parkBaseId = row2.stepId.replace(/(\[\d+\]|#\d+)$/, "");
35361
36366
  if (opts.park?.includes(row2.stepId) || opts.park?.includes(parkBaseId)) {
35362
36367
  row2.attempt = Math.max(1, row2.attempt);
@@ -35830,7 +36835,7 @@ async function runWorkflowLocallyInner(rawOpts) {
35830
36835
  }
35831
36836
  }, "runRow");
35832
36837
  const throttleAt = /* @__PURE__ */ new Map();
35833
- emit2("run.started");
36838
+ emit3("run.started");
35834
36839
  say(`[${stamp()}] run ${runId} \xB7 ${plan.order.length} planned step(s)`);
35835
36840
  try {
35836
36841
  for (; ; ) {
@@ -35898,7 +36903,7 @@ async function runWorkflowLocallyInner(rawOpts) {
35898
36903
  ledger.status = "completed";
35899
36904
  ledger.output = bailed.output;
35900
36905
  ledger.bailedFromStepId = bailed.stepId;
35901
- emit2("run.completed", void 0, {
36906
+ emit3("run.completed", void 0, {
35902
36907
  bailed: true,
35903
36908
  bailedFromStepId: bailed.stepId
35904
36909
  });
@@ -35915,7 +36920,7 @@ async function runWorkflowLocallyInner(rawOpts) {
35915
36920
  if (runFailure) {
35916
36921
  for (const r of rows3.values()) skipRow(r, "run_failed");
35917
36922
  ledger.status = "failed";
35918
- emit2("run.failed", runFailure.stepId, {
36923
+ emit3("run.failed", runFailure.stepId, {
35919
36924
  code: runFailure.code
35920
36925
  });
35921
36926
  say(`[${stamp()}] run \xB7 failed \xB7 ${runFailure.code}: ${runFailure.message}`);
@@ -35972,7 +36977,7 @@ async function runWorkflowLocallyInner(rawOpts) {
35972
36977
  }
35973
36978
  ledger.status = "completed";
35974
36979
  ledger.output = output;
35975
- emit2("run.completed");
36980
+ emit3("run.completed");
35976
36981
  say(`[${stamp()}] run \xB7 completed`);
35977
36982
  finalizeLedger();
35978
36983
  return {
@@ -36068,6 +37073,23 @@ function parseJsonOrFile(raw, what) {
36068
37073
  __name(parseJsonOrFile, "parseJsonOrFile");
36069
37074
 
36070
37075
  // src/utils/sandbox.ts
37076
+ init_dist();
37077
+ function createSandboxLog(consoleObj) {
37078
+ const emit3 = /* @__PURE__ */ __name((level, message, fields) => {
37079
+ const normalized = normalizeLogFields(fields);
37080
+ const text = typeof message === "object" && message !== null ? JSON.stringify(message) : String(message);
37081
+ const write = level === "error" ? consoleObj.error : level === "warn" ? consoleObj.warn : level === "info" ? consoleObj.info : consoleObj.log;
37082
+ if (normalized) write.call(consoleObj, text, JSON.stringify(normalized));
37083
+ else write.call(consoleObj, text);
37084
+ }, "emit");
37085
+ return Object.freeze({
37086
+ debug: /* @__PURE__ */ __name((message, fields) => emit3("debug", message, fields), "debug"),
37087
+ info: /* @__PURE__ */ __name((message, fields) => emit3("info", message, fields), "info"),
37088
+ warn: /* @__PURE__ */ __name((message, fields) => emit3("warn", message, fields), "warn"),
37089
+ error: /* @__PURE__ */ __name((message, fields) => emit3("error", message, fields), "error")
37090
+ });
37091
+ }
37092
+ __name(createSandboxLog, "createSandboxLog");
36071
37093
  function resolveRequireMode() {
36072
37094
  return process.env.LUA_SANDBOX_REQUIRE_MODE === "enforce" ? "enforce" : "log";
36073
37095
  }
@@ -36211,6 +37233,13 @@ function createSandbox(options) {
36211
37233
  __envRef: key
36212
37234
  }), "template")
36213
37235
  }),
37236
+ // K1a (PRO-2142) — the `log` global, so `lua test` runs the same code
37237
+ // production runs. There is no `VmLogSink` here, so the line goes to the
37238
+ // developer's console with its fields rendered beside it; the VALIDATION is
37239
+ // the identical `normalizeLogFields` the production VM calls, which is the
37240
+ // half that has to agree — a field map `lua test` accepts is one production
37241
+ // accepts, and one it refuses is refused here with the same message.
37242
+ log: createSandboxLog(consoleObj),
36214
37243
  BasketStatus,
36215
37244
  OrderStatus,
36216
37245
  AI: {
@@ -36341,10 +37370,7 @@ async function executeTool(options) {
36341
37370
  return await primitive.execute(inputs);
36342
37371
  } catch (e) {
36343
37372
  console.error(e);
36344
- return {
36345
- status: "error",
36346
- error: e.message
36347
- };
37373
+ throw e;
36348
37374
  }
36349
37375
  } else {
36350
37376
  const commonJsWrapper = `
@@ -36355,7 +37381,7 @@ module.exports = async (input) => {
36355
37381
  return await executeFunction(input);
36356
37382
  } catch(e) {
36357
37383
  console.error(e);
36358
- return { status: 'error', error: e.message };
37384
+ throw e;
36359
37385
  }
36360
37386
  };
36361
37387
  `;
@@ -36391,10 +37417,7 @@ async function executeWebhook(options) {
36391
37417
  return await primitive.execute(event);
36392
37418
  } catch (e) {
36393
37419
  console.error(e);
36394
- return {
36395
- status: "error",
36396
- error: e.message
36397
- };
37420
+ throw e;
36398
37421
  }
36399
37422
  } else {
36400
37423
  const commonJsWrapper = `
@@ -36405,7 +37428,7 @@ module.exports = async (event) => {
36405
37428
  return await executeFunction(event);
36406
37429
  } catch(e) {
36407
37430
  console.error(e);
36408
- return { status: 'error', error: e.message };
37431
+ throw e;
36409
37432
  }
36410
37433
  };
36411
37434
  `;
@@ -36457,10 +37480,7 @@ async function executeJob(options) {
36457
37480
  return await primitive.execute(jobInstance);
36458
37481
  } catch (e) {
36459
37482
  console.error(e);
36460
- return {
36461
- status: "error",
36462
- error: e.message
36463
- };
37483
+ throw e;
36464
37484
  }
36465
37485
  } else {
36466
37486
  const commonJsWrapper = `
@@ -36471,7 +37491,7 @@ module.exports = async (job) => {
36471
37491
  return await executeFunction(job);
36472
37492
  } catch(e) {
36473
37493
  console.error(e);
36474
- return { status: 'error', error: e.message };
37494
+ throw e;
36475
37495
  }
36476
37496
  };
36477
37497
  `;
@@ -36557,10 +37577,7 @@ async function executePreProcessor(options) {
36557
37577
  return await primitive.execute(userInstance, messages, channel);
36558
37578
  } catch (e) {
36559
37579
  console.error(e);
36560
- return {
36561
- status: "error",
36562
- error: e.message
36563
- };
37580
+ throw e;
36564
37581
  }
36565
37582
  } else {
36566
37583
  const commonJsWrapper = `
@@ -36571,7 +37588,7 @@ module.exports = async (input) => {
36571
37588
  return await executeFunction(input.user, input.messages, input.channel);
36572
37589
  } catch(e) {
36573
37590
  console.error(e);
36574
- return { status: 'error', error: e.message };
37591
+ throw e;
36575
37592
  }
36576
37593
  };
36577
37594
  `;
@@ -37419,8 +38436,13 @@ var ALIAS_MAP = {
37419
38436
  "delete",
37420
38437
  "test",
37421
38438
  "verify",
38439
+ // H4 / PRO-1887: the second half of a BUCKET verification, for the write-only
38440
+ // role that cannot read its own challenge object back.
38441
+ "confirm",
37422
38442
  "pause",
37423
38443
  "resume",
38444
+ // PRO-2126: re-enqueue one batch's records after a terminal refusal.
38445
+ "replay",
37424
38446
  "rotate-secret"
37425
38447
  ],
37426
38448
  aliases: lowerKeys({
@@ -39562,13 +40584,18 @@ async function compileQuietly(asJson) {
39562
40584
  }
39563
40585
  }
39564
40586
  __name(compileQuietly, "compileQuietly");
39565
- function handleExecutionError(error, entityType) {
39566
- console.error(`
40587
+ function handleExecutionError(error, entityType, asJson = false) {
40588
+ if (asJson) {
40589
+ console.log(JSON.stringify(cliErrorEnvelope(error), null, 2));
40590
+ } else {
40591
+ console.error(`
39567
40592
  \u274C ${entityType} execution failed:`);
39568
- console.error(error.message);
39569
- if (error.stack) {
39570
- console.error(error.stack);
40593
+ console.error(error.message);
40594
+ if (error.stack) {
40595
+ console.error(error.stack);
40596
+ }
39571
40597
  }
40598
+ process.exitCode = classifyCliError(error).exitCode;
39572
40599
  }
39573
40600
  __name(handleExecutionError, "handleExecutionError");
39574
40601
  function extractToolsFromManifest(manifest) {
@@ -39744,7 +40771,7 @@ async function testSkill(entityName, inputJson, asJson = false) {
39744
40771
  });
39745
40772
  }
39746
40773
  } catch (error) {
39747
- handleExecutionError(error, "Tool");
40774
+ handleExecutionError(error, "Tool", asJson);
39748
40775
  if (!asJson) {
39749
40776
  writeHintBlock({
39750
40777
  headline: "Tool ran locally \u2014 no server logs exist for this execution.",
@@ -39863,7 +40890,7 @@ async function testWebhook(entityName, inputJson, asJson = false) {
39863
40890
  });
39864
40891
  }
39865
40892
  } catch (error) {
39866
- handleExecutionError(error, "Webhook");
40893
+ handleExecutionError(error, "Webhook", asJson);
39867
40894
  if (!asJson) {
39868
40895
  writeHintBlock({
39869
40896
  headline: "Webhook ran locally \u2014 no server logs exist for this execution.",
@@ -39955,7 +40982,7 @@ async function testJob(entityName, asJson = false) {
39955
40982
  });
39956
40983
  }
39957
40984
  } catch (error) {
39958
- handleExecutionError(error, "Job");
40985
+ handleExecutionError(error, "Job", asJson);
39959
40986
  if (!asJson) {
39960
40987
  writeHintBlock({
39961
40988
  headline: "Job ran locally \u2014 no server logs exist for this execution.",
@@ -40107,7 +41134,7 @@ Raw result:`);
40107
41134
  });
40108
41135
  }
40109
41136
  } catch (error) {
40110
- handleExecutionError(error, "PreProcessor");
41137
+ handleExecutionError(error, "PreProcessor", asJson);
40111
41138
  if (!asJson) {
40112
41139
  writeHintBlock({
40113
41140
  headline: "Preprocessor ran locally \u2014 no server logs exist for this execution.",
@@ -40236,7 +41263,7 @@ async function testPostProcessor(entityName, inputJson, asJson = false) {
40236
41263
  });
40237
41264
  }
40238
41265
  } catch (error) {
40239
- handleExecutionError(error, "PostProcessor");
41266
+ handleExecutionError(error, "PostProcessor", asJson);
40240
41267
  if (!asJson) {
40241
41268
  writeHintBlock({
40242
41269
  headline: "Postprocessor ran locally \u2014 no server logs exist for this execution.",
@@ -41194,6 +42221,10 @@ function pickPushAllNextStepVariant(opts) {
41194
42221
  return null;
41195
42222
  }
41196
42223
  __name(pickPushAllNextStepVariant, "pickPushAllNextStepVariant");
42224
+ function pushAllExitCode(variant) {
42225
+ return variant === "partial" || variant === "error" ? CLI_EXIT.ERROR : void 0;
42226
+ }
42227
+ __name(pushAllExitCode, "pushAllExitCode");
41197
42228
  function formatPushFailureSummary(failedItems) {
41198
42229
  if (failedItems.length === 0) return "";
41199
42230
  const lines = [];
@@ -42331,6 +43362,7 @@ Until the backup catches up, \`lua init\` for this agent will restore an older s
42331
43362
  pushedSomething,
42332
43363
  failedCount: failedItems.length + deployFailedCount
42333
43364
  });
43365
+ process.exitCode = pushAllExitCode(variant);
42334
43366
  if (variant === "success") {
42335
43367
  console.log("");
42336
43368
  if (!options.suppressVersionCreateHint) {
@@ -42380,6 +43412,7 @@ init_command_utils();
42380
43412
 
42381
43413
  // src/utils/deploy-helpers.ts
42382
43414
  init_cli();
43415
+ init_cli_error();
42383
43416
  import inquirer8 from "inquirer";
42384
43417
  function formatVersionChoice(version) {
42385
43418
  const currentIndicator = version.isCurrent ? " (CURRENT)" : "";
@@ -42428,6 +43461,10 @@ function formatDeploySuccess(params) {
42428
43461
  return `\u2705 ${label2} "${name}" v${version} deployed successfully`;
42429
43462
  }
42430
43463
  __name(formatDeploySuccess, "formatDeploySuccess");
43464
+ function deployAllExitCode(failedCount) {
43465
+ return failedCount > 0 ? CLI_EXIT.ERROR : void 0;
43466
+ }
43467
+ __name(deployAllExitCode, "deployAllExitCode");
42431
43468
 
42432
43469
  // src/commands/deploy.ts
42433
43470
  init_constants();
@@ -42869,6 +43906,12 @@ async function deployAllCommand(_options) {
42869
43906
  const versions = await deployConfig.getVersions(apiKey, agentId, entityId);
42870
43907
  if (!versions || versions.length === 0) {
42871
43908
  writeInfo(` \u2139\uFE0F No versions for ${deployConfig.label} "${entity.name}", skipping`);
43909
+ failedItems.push({
43910
+ kind: deployConfig.type,
43911
+ name: entity.name,
43912
+ error: `No versions found on the server for ${deployConfig.idField} "${entityId}" \u2014 push it first, or remove the stale id from lua.skill.yaml if it was deleted server-side`
43913
+ });
43914
+ failedCount++;
42872
43915
  continue;
42873
43916
  }
42874
43917
  const latestVersion = sortVersionsByDate(versions)[0].version;
@@ -42985,6 +44028,7 @@ async function deployAllCommand(_options) {
42985
44028
  when: "partial"
42986
44029
  });
42987
44030
  }
44031
+ process.exitCode = deployAllExitCode(failedCount);
42988
44032
  }
42989
44033
  __name(deployAllCommand, "deployAllCommand");
42990
44034
 
@@ -43816,6 +44860,7 @@ var LogsApi = class extends HttpClient {
43816
44860
  if (filters.toolName) queryParams.append("toolName", filters.toolName);
43817
44861
  if (filters.logType) queryParams.append("logType", filters.logType);
43818
44862
  if (filters.userId) queryParams.append("userId", filters.userId);
44863
+ if (filters.channel) queryParams.append("channel", filters.channel);
43819
44864
  if (filters.since) queryParams.append("since", filters.since);
43820
44865
  if (filters.until) queryParams.append("until", filters.until);
43821
44866
  if (filters.environment) queryParams.append("environment", filters.environment);
@@ -43881,6 +44926,14 @@ async function probeAgentErrorsSince(apiKey, agentId, sinceISO) {
43881
44926
  }
43882
44927
  __name(probeAgentErrorsSince, "probeAgentErrorsSince");
43883
44928
 
44929
+ // src/commands/chat-log-environment.ts
44930
+ function declaredLogEnvironment(session) {
44931
+ if (session.type !== "production") return void 0;
44932
+ if (session.previewVersion != null) return void 0;
44933
+ return "production";
44934
+ }
44935
+ __name(declaredLogEnvironment, "declaredLogEnvironment");
44936
+
43884
44937
  // src/commands/chat.ts
43885
44938
  init_cli_error();
43886
44939
  async function runChatProbe(chatEnv, window) {
@@ -44323,6 +45376,10 @@ async function sendProductionMessageStream(chatEnv, messages, callbacks) {
44323
45376
  if (chatEnv.previewVersion != null) {
44324
45377
  chatRequest.version = chatEnv.previewVersion;
44325
45378
  }
45379
+ const declared = declaredLogEnvironment(chatEnv);
45380
+ if (declared) {
45381
+ chatRequest.environment = declared;
45382
+ }
44326
45383
  const chatApi = new ChatApi(BASE_URLS.CHAT, chatEnv.apiKey);
44327
45384
  await chatApi.sendMessageStream(chatEnv.agentId, chatRequest, callbacks.onChunk, callbacks.onPostprocessComplete, callbacks.onPreprocessorBlocked, callbacks.onBatchAbort, callbacks.onBatchHandled);
44328
45385
  }
@@ -47920,6 +48977,7 @@ async function logsCommand(cmdObj) {
47920
48977
  pageGiven,
47921
48978
  json: cmdObj?.json || false,
47922
48979
  userId: cmdObj?.userId || null,
48980
+ channel: cmdObj?.channel || null,
47923
48981
  agentId: cmdObj?.agentId || null,
47924
48982
  direction: cmdObj?.direction || null,
47925
48983
  status: cmdObj?.status || null,
@@ -47945,7 +49003,7 @@ async function logsCommand(cmdObj) {
47945
49003
  return;
47946
49004
  }
47947
49005
  const logsApi = new LogsApi(BASE_URLS.API, apiKey);
47948
- const hasNonInteractiveFlags = Boolean(options.type || options.name || options.json || options.userId || options.since || options.until || options.environment || options.follow);
49006
+ const hasNonInteractiveFlags = Boolean(options.type || options.name || options.json || options.userId || options.channel || options.since || options.until || options.environment || options.follow);
47949
49007
  trackEvent("cli_logs_action", {
47950
49008
  filter_type: options.type || null,
47951
49009
  json_output: options.json || false,
@@ -48010,6 +49068,12 @@ async function listCallLogs(agentId, apiKey, options) {
48010
49068
  }
48011
49069
  }
48012
49070
  __name(listCallLogs, "listCallLogs");
49071
+ function formatLogFields(fields) {
49072
+ if (!fields || typeof fields !== "object" || Array.isArray(fields)) return void 0;
49073
+ const pairs = Object.keys(fields).sort().map((key) => `${key}=${String(fields[key])}`);
49074
+ return pairs.length > 0 ? pairs.join(" ") : void 0;
49075
+ }
49076
+ __name(formatLogFields, "formatLogFields");
48013
49077
  function renderLogEntry(log, isLast) {
48014
49078
  const timestamp = new Date(log.timestamp).toLocaleString();
48015
49079
  const logSource = log.metadata?.logSource;
@@ -48048,6 +49112,10 @@ function renderLogEntry(log, isLast) {
48048
49112
  console.log(chalk2.gray(` Duration: ${log.duration}ms`));
48049
49113
  }
48050
49114
  console.log(` ${log.message}`);
49115
+ const fieldsLine = formatLogFields(log.metadata?.fields);
49116
+ if (fieldsLine) {
49117
+ console.log(chalk2.gray(` ${fieldsLine}`));
49118
+ }
48051
49119
  if (!isLast) {
48052
49120
  console.log(chalk2.gray(` ${"-".repeat(78)}`));
48053
49121
  }
@@ -48289,6 +49357,9 @@ async function nonInteractiveLogs(logsApi, agentId, apiKey, options) {
48289
49357
  if (options.userId) {
48290
49358
  filters.userId = options.userId;
48291
49359
  }
49360
+ if (options.channel) {
49361
+ filters.channel = options.channel;
49362
+ }
48292
49363
  if (options.since) filters.since = options.since;
48293
49364
  if (options.until) filters.until = options.until;
48294
49365
  if (options.environment) filters.environment = options.environment;
@@ -48661,6 +49732,38 @@ function getLogColor(subType, logSource) {
48661
49732
  }
48662
49733
  __name(getLogColor, "getLogColor");
48663
49734
 
49735
+ // src/commands/logs.export.ts
49736
+ init_cli();
49737
+ init_cli_error();
49738
+ init_analytics();
49739
+ import { basename } from "path";
49740
+ import { existsSync as existsSync9, mkdirSync as mkdirSync8, writeFileSync as writeFileSync10 } from "fs";
49741
+ import { join as join9, resolve as resolvePath2 } from "path";
49742
+ init_constants();
49743
+ init_lua_fetch();
49744
+
49745
+ // src/api/log-export.api.service.ts
49746
+ init_http_client();
49747
+ var LogExportApi = class extends HttpClient {
49748
+ static {
49749
+ __name(this, "LogExportApi");
49750
+ }
49751
+ orgId;
49752
+ constructor(baseUrl, credential, orgId) {
49753
+ super(baseUrl, credential), this.orgId = orgId;
49754
+ }
49755
+ /** RELATIVE to `baseUrl` — every `HttpClient` verb prepends it itself. */
49756
+ base() {
49757
+ return `/admin/orgs/${encodeURIComponent(this.orgId)}/logs/export`;
49758
+ }
49759
+ async startExport(input) {
49760
+ return this.httpPost(this.base(), input);
49761
+ }
49762
+ async getExport(jobId) {
49763
+ return this.httpGet(`${this.base()}/${encodeURIComponent(jobId)}`);
49764
+ }
49765
+ };
49766
+
48664
49767
  // src/commands/drains.ts
48665
49768
  init_cli();
48666
49769
  init_command_utils();
@@ -48715,6 +49818,15 @@ var DrainsApi = class extends HttpClient {
48715
49818
  async verifyDrain(drainId) {
48716
49819
  return this.httpPost(this.drainPath(drainId, "/verify"));
48717
49820
  }
49821
+ /**
49822
+ * Settle a bucket ownership challenge (H4). Not `httpPostOnce`: confirming is
49823
+ * idempotent and answers the same `verifiedAt`, so a retried request is safe.
49824
+ */
49825
+ async confirmChallenge(drainId, token) {
49826
+ return this.httpPost(this.drainPath(drainId, "/challenge/confirm"), {
49827
+ token
49828
+ });
49829
+ }
48718
49830
  async pauseDrain(drainId, reason) {
48719
49831
  return this.httpPost(this.drainPath(drainId, "/pause"), reason ? {
48720
49832
  reason
@@ -48731,6 +49843,17 @@ var DrainsApi = class extends HttpClient {
48731
49843
  const path26 = this.drainPath(drainId, `/rotate-secret${finalize ? "?finalize=true" : ""}`);
48732
49844
  return finalize ? this.httpPost(path26, {}) : this.httpPostOnce(path26, {});
48733
49845
  }
49846
+ /**
49847
+ * Re-enqueue one batch's records (PRO-2126).
49848
+ *
49849
+ * `httpPost`, not `httpPostOnce`: a replay is idempotent in the way that matters —
49850
+ * the second call finds the rows already `pending`, matches nothing and answers 409
49851
+ * — so a retried request cannot double-deliver. It DOES spend a second slot of the
49852
+ * per-drain budget, which is the price of a retry and not a correctness problem.
49853
+ */
49854
+ async replayBatch(drainId, batchId) {
49855
+ return this.httpPost(this.drainPath(drainId, `/deliveries/${encodeURIComponent(batchId)}/replay`));
49856
+ }
48734
49857
  async listDeliveries(drainId, opts = {}) {
48735
49858
  const query = new URLSearchParams();
48736
49859
  if (opts.limit !== void 0) query.set("limit", String(opts.limit));
@@ -48752,19 +49875,30 @@ var HEADER_NAME_PATTERN = /^[A-Za-z0-9-]{1,64}$/;
48752
49875
  var ENDPOINT_TYPES = [
48753
49876
  "http",
48754
49877
  "otlp",
48755
- "betterstack"
49878
+ "betterstack",
49879
+ "splunk",
49880
+ "loki",
49881
+ "axiom",
49882
+ "sumologic"
48756
49883
  ];
48757
49884
  var SITE_TYPES = [
48758
- "datadog"
48759
- ];
48760
- var PHASE2_SOURCES = [
48761
- "delivery",
48762
- "trigger-execution",
48763
- "job-execution",
48764
- "workflow-run",
48765
- "workflow-audit"
49885
+ "datadog",
49886
+ "newrelic"
48766
49887
  ];
48767
- var SELECTABLE_SOURCES = LOG_DRAIN_SOURCES.filter((source) => !PHASE2_SOURCES.includes(source));
49888
+ function sitesFor(type) {
49889
+ return LOG_DRAIN_PRESET_SITES[type] ?? LOG_DRAIN_DATADOG_SITES;
49890
+ }
49891
+ __name(sitesFor, "sitesFor");
49892
+ function endpointHint(type) {
49893
+ const rule = LOG_DRAIN_PRESET_PATHS[type];
49894
+ if (!rule) return void 0;
49895
+ if (rule.segment === "dataset") return `the full ingest URL, ending in ${rule.path}/<dataset>${rule.suffix ?? ""}`;
49896
+ if (rule.segment === "token") return `the full HTTP Source URL, ending in ${rule.path}/<collector code>`;
49897
+ return `your host \u2014 ${rule.path} is appended for you`;
49898
+ }
49899
+ __name(endpointHint, "endpointHint");
49900
+ var UNAVAILABLE_SOURCES = [];
49901
+ var SELECTABLE_SOURCES = LOG_DRAIN_SOURCES.filter((source) => !UNAVAILABLE_SOURCES.includes(source));
48768
49902
  var NON_CONTENT_SOURCES = SELECTABLE_SOURCES.filter((source) => !LOG_DRAIN_CONTENT_SOURCES.includes(source));
48769
49903
  var TOKEN_ECHO_TYPES = [
48770
49904
  "http"
@@ -48809,8 +49943,8 @@ __name(trimmed, "trimmed");
48809
49943
  function parseType(raw) {
48810
49944
  const value3 = trimmed(raw)?.toLowerCase();
48811
49945
  if (!value3) return void 0;
48812
- if (!LOG_DRAIN_TYPES_PHASE1.includes(value3)) {
48813
- throw CliError.usage(`--type "${raw}" is not available.`, listHint("Available destinations", LOG_DRAIN_TYPES_PHASE1));
49946
+ if (!LOG_DRAIN_TYPES_IMPLEMENTED.includes(value3)) {
49947
+ throw CliError.usage(`--type "${raw}" is not a destination lua-cli knows how to address.`, listHint("Destinations", LOG_DRAIN_TYPES_IMPLEMENTED));
48814
49948
  }
48815
49949
  return value3;
48816
49950
  }
@@ -49073,12 +50207,13 @@ async function resolveDestination(type, options) {
49073
50207
  const endpoint = trimmed(options.endpoint);
49074
50208
  const site = trimmed(options.site);
49075
50209
  if (SITE_TYPES.includes(type)) {
50210
+ const sites = sitesFor(type);
49076
50211
  if (endpoint) {
49077
- throw CliError.usage(`--endpoint is not accepted for ${type}: its URL is derived from --site.`, listHint("Sites", LOG_DRAIN_DATADOG_SITES));
50212
+ throw CliError.usage(`--endpoint is not accepted for ${type}: its URL is derived from --site.`, listHint("Sites", sites));
49078
50213
  }
49079
- const chosen = site ?? await promptChoice("Which site?", LOG_DRAIN_DATADOG_SITES, "--site", `${type}`);
49080
- if (!LOG_DRAIN_DATADOG_SITES.includes(chosen)) {
49081
- throw CliError.usage(`--site "${chosen}" is not a ${type} site.`, listHint("Sites", LOG_DRAIN_DATADOG_SITES));
50214
+ const chosen = site ?? await promptChoice("Which site?", sites, "--site", `${type}`);
50215
+ if (!sites.includes(chosen)) {
50216
+ throw CliError.usage(`--site "${chosen}" is not a ${type} site.`, listHint("Sites", sites));
49082
50217
  }
49083
50218
  return {
49084
50219
  site: chosen
@@ -49090,12 +50225,108 @@ async function resolveDestination(type, options) {
49090
50225
  if (site) {
49091
50226
  throw CliError.usage(`--site is not accepted for ${type}: give the destination URL with --endpoint.`);
49092
50227
  }
49093
- const url = endpoint ?? await promptText(type === "otlp" ? "OTLP logs endpoint (the full /v1/logs URL):" : "Destination URL:", "--endpoint", `${type} sends to your own URL`);
50228
+ const hint = endpointHint(type);
50229
+ const url = endpoint ?? await promptText(type === "otlp" ? "OTLP logs endpoint (the full /v1/logs URL):" : "Destination URL:", "--endpoint", hint ?? `${type} sends to your own URL`);
49094
50230
  return {
49095
50231
  endpoint: url
49096
50232
  };
49097
50233
  }
49098
50234
  __name(resolveDestination, "resolveDestination");
50235
+ var BUCKET_PROVIDERS = [
50236
+ "aws",
50237
+ "r2",
50238
+ "gcs"
50239
+ ];
50240
+ function parseProvider(raw) {
50241
+ const value3 = trimmed(raw)?.toLowerCase() ?? "aws";
50242
+ if (!BUCKET_PROVIDERS.includes(value3)) {
50243
+ throw CliError.usage(`--provider "${raw}" is not an object store.`, listHint("Valid values", BUCKET_PROVIDERS));
50244
+ }
50245
+ return value3;
50246
+ }
50247
+ __name(parseProvider, "parseProvider");
50248
+ function resolveBucketCredentials(options) {
50249
+ const roleArn = trimmed(options.roleArn);
50250
+ const spec = trimmed(options.credentialsFromEnv);
50251
+ if (roleArn && spec) {
50252
+ throw CliError.usage("Give either --role-arn or --credentials-from-env, not both.", "A role is assumed at send time; a key pair is stored. One drain uses one of them.");
50253
+ }
50254
+ if (roleArn) {
50255
+ return {
50256
+ roleArn,
50257
+ ...trimmed(options.externalId) ? {
50258
+ externalId: trimmed(options.externalId)
50259
+ } : {}
50260
+ };
50261
+ }
50262
+ if (!spec) return void 0;
50263
+ const eq3 = spec.indexOf("=");
50264
+ if (eq3 === -1) {
50265
+ throw CliError.usage(`--credentials-from-env "${spec}" must read <KEY_ID_VAR>=<SECRET_VAR>.`, "Both values stay in the environment \u2014 e.g. --credentials-from-env AWS_KEY_ID=AWS_SECRET");
50266
+ }
50267
+ const keyVar = spec.slice(0, eq3).trim();
50268
+ const secretVar = spec.slice(eq3 + 1).trim();
50269
+ if (!keyVar || !secretVar) {
50270
+ throw CliError.usage(`--credentials-from-env "${spec}" must name two environment variables.`);
50271
+ }
50272
+ const accessKeyId = process.env[keyVar];
50273
+ const secretAccessKey = process.env[secretVar];
50274
+ for (const [variable, value3] of [
50275
+ [
50276
+ keyVar,
50277
+ accessKeyId
50278
+ ],
50279
+ [
50280
+ secretVar,
50281
+ secretAccessKey
50282
+ ]
50283
+ ]) {
50284
+ if (!value3) {
50285
+ throw CliError.usage(`--credentials-from-env ${keyVar}=${secretVar}: the environment variable ${variable} is unset or empty.`, "Export it in the shell or the CI job that runs this command");
50286
+ }
50287
+ }
50288
+ return {
50289
+ accessKeyId,
50290
+ secretAccessKey
50291
+ };
50292
+ }
50293
+ __name(resolveBucketCredentials, "resolveBucketCredentials");
50294
+ async function resolveBucket(options) {
50295
+ const provider = parseProvider(options.provider);
50296
+ const endpoint = trimmed(options.endpoint);
50297
+ if (provider === "aws" && endpoint) {
50298
+ throw CliError.usage("--endpoint is not accepted for an aws bucket: its URL is derived from --bucket and --region.", "Use --endpoint only for --provider r2 or gcs");
50299
+ }
50300
+ const bucket = trimmed(options.bucket) ?? await promptText("Bucket name:", "--bucket", "the bucket to write to");
50301
+ const region = trimmed(options.region) ?? await promptText("Region:", "--region", provider === "r2" ? "R2 uses 'auto'" : "the bucket's region");
50302
+ const host = provider === "aws" ? void 0 : endpoint ?? await promptText("S3-compatible endpoint:", "--endpoint", provider === "r2" ? "https://<account>.r2.cloudflarestorage.com" : "https://storage.googleapis.com");
50303
+ return {
50304
+ provider,
50305
+ bucket,
50306
+ region,
50307
+ ...trimmed(options.prefix) ? {
50308
+ prefix: trimmed(options.prefix)
50309
+ } : {},
50310
+ ...host ? {
50311
+ endpoint: host
50312
+ } : {},
50313
+ ...resolveBucketCredentials(options) ? {
50314
+ credentials: resolveBucketCredentials(options)
50315
+ } : {}
50316
+ };
50317
+ }
50318
+ __name(resolveBucket, "resolveBucket");
50319
+ async function confirmChallengeCore(context, drainId, options) {
50320
+ const token = trimmed(options.token) ?? await promptText("Token from the _lua-verify-<token> object in your bucket:", "--token", "list your prefix for _lua-verify-* to find it");
50321
+ const result = await drainsCall(() => drainsApiFor(context).confirmChallenge(drainId, token));
50322
+ if (options.json) {
50323
+ printJson(result);
50324
+ return;
50325
+ }
50326
+ writeSuccess(`\u2705 Ownership confirmed for ${drainId}`);
50327
+ writeInfo(` verified at ${result.verifiedAt}`);
50328
+ }
50329
+ __name(confirmChallengeCore, "confirmChallengeCore");
49099
50330
  function verificationCurl(type, endpoint, headers) {
49100
50331
  const target = endpoint ?? "<your endpoint>";
49101
50332
  const headerArgs = headers.map((header) => header.fromEnv ? ` -H "${header.name}: $${header.fromEnv}" \\` : ` -H '${header.name}: <the value you entered>' \\`);
@@ -49116,11 +50347,37 @@ function verificationCurl(type, endpoint, headers) {
49116
50347
  ];
49117
50348
  }
49118
50349
  __name(verificationCurl, "verificationCurl");
50350
+ function bucketVerificationInstructions(drain, json) {
50351
+ const prefix = drain.bucket?.prefix;
50352
+ const where = prefix ? `${prefix}/_lua-verify-*` : "_lua-verify-*";
50353
+ const bucket = drain.bucket?.bucket ?? "<your bucket>";
50354
+ const endpointArg = drain.bucket?.endpoint ? ` --endpoint-url '${drain.bucket.endpoint}'` : "";
50355
+ emitAside("Lua writes a challenge object into the bucket itself \u2014 nothing is POSTed to a URL.", json);
50356
+ emitAside("Check the credentials can write under the prefix first:", json);
50357
+ emitAside("", json);
50358
+ emitAside(` aws s3 cp - 's3://${bucket}/${prefix ? `${prefix}/` : ""}_lua-write-check'${endpointArg} </dev/null`, json);
50359
+ emitAside("", json);
50360
+ emitAside(" (a zero-byte object, yours to delete \u2014 a write-only role cannot remove it itself)", json);
50361
+ emitAside("", json);
50362
+ emitAside(`Then: lua drains verify ${drain.id}`, json);
50363
+ emitAside("", json);
50364
+ emitAside("That is what mints the challenge, and how it settles depends on the role:", json);
50365
+ emitAside(" \xB7 with s3:GetObject, Lua reads the object back and 'verify' finishes on its own;", json);
50366
+ emitAside(" \xB7 write-only \u2014 the grant we recommend \u2014 leaves the claim open. List the prefix", json);
50367
+ emitAside(` for ${where}, take the <token> off the object name, and run:`, json);
50368
+ emitAside("", json);
50369
+ emitAside(` lua drains confirm ${drain.id} --token <token>`, json);
50370
+ }
50371
+ __name(bucketVerificationInstructions, "bucketVerificationInstructions");
49119
50372
  function verificationInstructions(drain, headers, options) {
49120
50373
  const type = drain.type;
49121
50374
  const json = options.json;
49122
50375
  emitAside("", json);
49123
50376
  emitAside("Before anything is delivered, Lua has to confirm you own this destination.", json);
50377
+ if (type === "s3") {
50378
+ bucketVerificationInstructions(drain, json);
50379
+ return;
50380
+ }
49124
50381
  if (TOKEN_ECHO_TYPES.includes(type)) {
49125
50382
  emitAside("Lua POSTs one record carrying an X-Lua-Verify header; your receiver must answer 2xx AND", json);
49126
50383
  emitAside("repeat that header back on the response. Check yours first:", json);
@@ -49136,8 +50393,9 @@ function verificationInstructions(drain, headers, options) {
49136
50393
  __name(verificationInstructions, "verificationInstructions");
49137
50394
  async function createDrainCore(context, options) {
49138
50395
  const name = trimmed(options.name) ?? await promptText("Drain name:", "--name", "1\u201364 chars, unique per org");
49139
- const type = parseType(options.type) ?? await promptChoice("Where should the logs go?", LOG_DRAIN_TYPES_PHASE1, "--type", "the destination kind");
49140
- const destination = await resolveDestination(type, options);
50396
+ const type = parseType(options.type) ?? await promptChoice("Where should the logs go?", LOG_DRAIN_TYPES_IMPLEMENTED, "--type", "the destination kind");
50397
+ const bucket = type === "s3" ? await resolveBucket(options) : void 0;
50398
+ const destination = bucket ? {} : await resolveDestination(type, options);
49141
50399
  const format = parseFormat(options.format);
49142
50400
  const headers = await resolveHeaders(options);
49143
50401
  const flagSources = parseSources(options.sources);
@@ -49159,6 +50417,9 @@ async function createDrainCore(context, options) {
49159
50417
  ...destination.site ? {
49160
50418
  site: destination.site
49161
50419
  } : {},
50420
+ ...bucket ? {
50421
+ bucket
50422
+ } : {},
49162
50423
  ...format ? {
49163
50424
  format
49164
50425
  } : {},
@@ -49211,6 +50472,14 @@ async function createDrainCore(context, options) {
49211
50472
  label: "Verify",
49212
50473
  command: `lua drains verify ${drain.id}`
49213
50474
  },
50475
+ // W4-D3: on a bucket drain the step after `verify` is `confirm`, not `test` —
50476
+ // a write-only role (the recommended grant) cannot settle its own claim.
50477
+ ...drain.type === "s3" ? [
50478
+ {
50479
+ label: "Settle the challenge",
50480
+ command: `lua drains confirm ${drain.id} --token <token>`
50481
+ }
50482
+ ] : [],
49214
50483
  {
49215
50484
  label: "Send one record",
49216
50485
  command: `lua drains test ${drain.id}`
@@ -49526,6 +50795,30 @@ async function rotateSecretCore(context, drainId, options) {
49526
50795
  });
49527
50796
  }
49528
50797
  __name(rotateSecretCore, "rotateSecretCore");
50798
+ async function replayDrainCore(context, drainId, batchId, options) {
50799
+ const replayed = await drainsCall(() => drainsApiFor(context).replayBatch(drainId, batchId));
50800
+ if (options.json) {
50801
+ printJson({
50802
+ replayOf: replayed.replayOf,
50803
+ records: replayed.records,
50804
+ enqueuedAt: replayed.enqueuedAt
50805
+ });
50806
+ return;
50807
+ }
50808
+ const records = replayed.records === 1 ? "1 record" : `${replayed.records} records`;
50809
+ writeSuccess(`\u2705 Re-enqueued ${records} from batch ${replayed.replayOf}`);
50810
+ writeInfo(" They go out in a NEW batch with a new id; delivery is asynchronous.");
50811
+ writeHintBlock({
50812
+ headline: "Watch for it:",
50813
+ lines: [
50814
+ {
50815
+ label: "Attempts",
50816
+ command: `lua drains deliveries ${drainId} --kind batch`
50817
+ }
50818
+ ]
50819
+ });
50820
+ }
50821
+ __name(replayDrainCore, "replayDrainCore");
49529
50822
 
49530
50823
  // src/commands/drains.ts
49531
50824
  var DRAIN_ACTIONS = [
@@ -49537,8 +50830,10 @@ var DRAIN_ACTIONS = [
49537
50830
  "delete",
49538
50831
  "test",
49539
50832
  "verify",
50833
+ "confirm",
49540
50834
  "pause",
49541
50835
  "resume",
50836
+ "replay",
49542
50837
  "rotate-secret"
49543
50838
  ];
49544
50839
  var MISSING_SCOPE_MESSAGE = "Missing scope: logs:manage. Create a key with that scope (lua auth key) or ask an org admin.";
@@ -49554,6 +50849,8 @@ var DELIVERY_KINDS = [
49554
50849
  var DEFAULT_DELIVERY_LIMIT = 20;
49555
50850
  var MAX_DELIVERY_LIMIT = 100;
49556
50851
  var MAX_TABLE_WIDTH = 100;
50852
+ var DRAIN_ID_PREFIX = "drn_";
50853
+ var MAX_LISTED_DRAINS = 10;
49557
50854
  async function resolveDrainsContext(options) {
49558
50855
  const apiKey = await requireAuth();
49559
50856
  const flagOrg = typeof options.org === "string" ? options.org.trim() : "";
@@ -49611,6 +50908,25 @@ async function drainsCall(call) {
49611
50908
  throw CliError.fromStatus(response.error?.statusCode, drainRefusalMessage(response.error), void 0, apiErrorDetail(response.error));
49612
50909
  }
49613
50910
  __name(drainsCall, "drainsCall");
50911
+ function identify(drain) {
50912
+ return `${drain.id} \u2014 ${drain.name}`;
50913
+ }
50914
+ __name(identify, "identify");
50915
+ async function resolveDrainRef(context, ref, options = {}) {
50916
+ const wanted = `${ref ?? ""}`.trim();
50917
+ if (wanted.startsWith(DRAIN_ID_PREFIX)) return wanted;
50918
+ const drains = (await drainsCall(() => drainsApiFor(context).listDrains())).drains ?? [];
50919
+ const matches = drains.filter((drain) => drain.name === wanted);
50920
+ if (matches.length === 1) {
50921
+ if (!options.json) writeInfo(`\u{1F6B0} ${wanted} \u2192 ${matches[0].id}`);
50922
+ return matches[0].id;
50923
+ }
50924
+ if (matches.length > 1) {
50925
+ throw CliError.usage(`${matches.length} log drains in ${context.orgId} are named "${wanted}" \u2014 address one by id.`, listHint("Matching drains", matches.map(identify)));
50926
+ }
50927
+ throw CliError.usage(`No log drain named "${wanted}" in ${context.orgId}.`, joinHint(`A drain is addressed by its id (${DRAIN_ID_PREFIX}\u2026) or by its exact name`, drains.length > 0 ? listHint("Drains", drains.slice(0, MAX_LISTED_DRAINS).map(identify)) : void 0, drains.length > MAX_LISTED_DRAINS && "Run lua drains list for the rest", drains.length === 0 && "This organization has no log drains \u2014 lua drains create"));
50928
+ }
50929
+ __name(resolveDrainRef, "resolveDrainRef");
49614
50930
  function printJson2(payload) {
49615
50931
  console.log(JSON.stringify(payload, null, 2));
49616
50932
  }
@@ -49730,8 +51046,8 @@ function quotaRatio(drain) {
49730
51046
  const quota = drain.quota;
49731
51047
  if (!quota || !quota.eventsPerDay) return "\u2014";
49732
51048
  const ratio = quota.eventsPerDay > 0 ? (quota.usedEvents ?? 0) / quota.eventsPerDay : 0;
49733
- const percent = Math.round(ratio * 1e3) / 10;
49734
- return quota.degradation && quota.degradation !== "none" ? `${percent}% ${quota.degradation}` : `${percent}%`;
51049
+ const percent2 = Math.round(ratio * 1e3) / 10;
51050
+ return quota.degradation && quota.degradation !== "none" ? `${percent2}% ${quota.degradation}` : `${percent2}%`;
49735
51051
  }
49736
51052
  __name(quotaRatio, "quotaRatio");
49737
51053
  function printDrainDetail(drain) {
@@ -49778,6 +51094,13 @@ function printDrainDetail(drain) {
49778
51094
  "verifiedAt",
49779
51095
  shortIso2(drain.verifiedAt)
49780
51096
  ],
51097
+ // PRO-2120 (G5-04). `verifiedAt` alone says WHEN, never WHAT — and for a vendor
51098
+ // preset on a host the customer supplied, what was proved is reachability, not
51099
+ // ownership. Absent (every drain verified before this shipped) reads the same way.
51100
+ [
51101
+ "ownership",
51102
+ drain.verification?.ownership ?? "reachability-only"
51103
+ ],
49781
51104
  [
49782
51105
  "lastSuccessAt",
49783
51106
  shortIso2(health.lastSuccessAt)
@@ -49937,7 +51260,9 @@ async function listDrainsCore(context, options) {
49937
51260
  });
49938
51261
  }
49939
51262
  __name(listDrainsCore, "listDrainsCore");
49940
- async function statusDrainsCore(context, drainId, options) {
51263
+ async function statusDrainsCore(context, drainRef, options) {
51264
+ const wanted = `${drainRef ?? ""}`.trim();
51265
+ const drainId = wanted ? await resolveDrainRef(context, wanted, options) : void 0;
49941
51266
  const api = drainsApiFor(context);
49942
51267
  const drains = drainId ? [
49943
51268
  await drainsCall(() => api.getDrain(drainId))
@@ -49972,9 +51297,10 @@ async function statusDrainsCore(context, drainId, options) {
49972
51297
  }
49973
51298
  }
49974
51299
  __name(statusDrainsCore, "statusDrainsCore");
49975
- async function deliveriesCore(context, drainId, options) {
51300
+ async function deliveriesCore(context, drainRef, options) {
49976
51301
  const limit = parseLimit(options.limit);
49977
51302
  const kind = parseKind(options.kind);
51303
+ const drainId = await resolveDrainRef(context, drainRef, options);
49978
51304
  const page = await drainsCall(() => drainsApiFor(context).listDeliveries(drainId, {
49979
51305
  limit,
49980
51306
  ...kind ? {
@@ -50014,17 +51340,25 @@ function parseKind(raw) {
50014
51340
  return kind;
50015
51341
  }
50016
51342
  __name(parseKind, "parseKind");
50017
- async function drainsCommand(action, drainId, cmdObj) {
51343
+ async function drainsCommand(action, drainId, batchIdOrOptions, cmdObj) {
51344
+ const batchId = typeof batchIdOrOptions === "string" ? batchIdOrOptions : void 0;
51345
+ const resolvedOptions = batchIdOrOptions && typeof batchIdOrOptions === "object" ? batchIdOrOptions : cmdObj;
50018
51346
  return withErrorHandling(
50019
51347
  async () => {
50020
- const options = cmdObj ?? {};
51348
+ const options = resolvedOptions ?? {};
50021
51349
  const verb = action ? validateOrSuggest("drains.action", action) : "list";
50022
- const needsId = /* @__PURE__ */ __name((v) => v === "deliveries" || v === "update" || v === "delete" || v === "test" || v === "verify" || v === "pause" || v === "resume" || v === "rotate-secret", "needsId");
51350
+ const needsId = /* @__PURE__ */ __name((v) => v === "deliveries" || v === "update" || v === "delete" || v === "test" || v === "verify" || v === "confirm" || v === "pause" || v === "resume" || v === "replay" || v === "rotate-secret", "needsId");
50023
51351
  if (needsId(verb) && !drainId) {
50024
- throw CliError.usage(`lua drains ${verb} needs a drain id.`, listHint("Run", [
51352
+ const takesName = verb === "deliveries";
51353
+ throw CliError.usage(`lua drains ${verb} needs a drain id${takesName ? " or name" : ""}.`, listHint("Run", [
50025
51354
  "lua drains list"
50026
51355
  ]));
50027
51356
  }
51357
+ if (verb === "replay" && !batchId) {
51358
+ throw CliError.usage("lua drains replay needs a drain id and a batch id.", listHint("Find one", [
51359
+ `lua drains deliveries ${drainId ?? "<drain id>"} --kind batch`
51360
+ ]));
51361
+ }
50028
51362
  const context = await resolveDrainsContext(options);
50029
51363
  switch (verb) {
50030
51364
  case "list":
@@ -50051,12 +51385,18 @@ async function drainsCommand(action, drainId, cmdObj) {
50051
51385
  case "verify":
50052
51386
  await verifyDrainCore(context, drainId, options);
50053
51387
  break;
51388
+ case "confirm":
51389
+ await confirmChallengeCore(context, drainId, options);
51390
+ break;
50054
51391
  case "pause":
50055
51392
  await pauseDrainCore(context, drainId, options);
50056
51393
  break;
50057
51394
  case "resume":
50058
51395
  await resumeDrainCore(context, drainId, options);
50059
51396
  break;
51397
+ case "replay":
51398
+ await replayDrainCore(context, drainId, batchId, options);
51399
+ break;
50060
51400
  case "rotate-secret":
50061
51401
  await rotateSecretCore(context, drainId, options);
50062
51402
  break;
@@ -50078,6 +51418,207 @@ async function drainsCommand(action, drainId, cmdObj) {
50078
51418
  }
50079
51419
  __name(drainsCommand, "drainsCommand");
50080
51420
 
51421
+ // src/commands/logs.export.ts
51422
+ var LOG_EXPORT_POLL = {
51423
+ /** First delay; doubles to `maxIntervalMs`. */
51424
+ intervalMs: 2e3,
51425
+ maxIntervalMs: 15e3,
51426
+ /** Polls before the command gives up and prints how to resume. */
51427
+ maxAttempts: 240,
51428
+ sleep: /* @__PURE__ */ __name((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)), "sleep")
51429
+ };
51430
+ var LOG_EXPORT_DOWNLOAD = {
51431
+ fetchBytes: /* @__PURE__ */ __name(async (url) => {
51432
+ const res = await luaFetch(url);
51433
+ if (!res.ok) throw new Error(`download failed: HTTP ${res.status}`);
51434
+ return Buffer.from(await res.arrayBuffer());
51435
+ }, "fetchBytes")
51436
+ };
51437
+ var ALREADY_RUNNING = "EXPORT_ALREADY_RUNNING";
51438
+ var MISSING_EXPORT_SCOPE_MESSAGE = "Missing scope: logs:read. Create a key with that scope (lua auth key) or ask an org admin.";
51439
+ var TERMINAL2 = /* @__PURE__ */ new Set([
51440
+ "succeeded",
51441
+ "failed",
51442
+ "expired"
51443
+ ]);
51444
+ async function logsExportCommand(cmdObj) {
51445
+ return withErrorHandling(
51446
+ async () => {
51447
+ const options = cmdObj ?? {};
51448
+ const since = options.since ? parseWindowBound("--since", String(options.since)) : void 0;
51449
+ const until = options.until ? parseWindowBound("--until", String(options.until)) : void 0;
51450
+ if (!since) {
51451
+ throw CliError.usage("logs export: --since <when> is required", "An ISO 8601 instant, or a relative form such as 24h or 7d");
51452
+ }
51453
+ const sources = parseTypeFlag(options.type);
51454
+ const outDir = options.out ? resolveOutDir(String(options.out)) : void 0;
51455
+ const context = await resolveDrainsContext(options);
51456
+ const api = new LogExportApi(BASE_URLS.API, context.apiKey, context.orgId);
51457
+ const say = /* @__PURE__ */ __name((line) => {
51458
+ if (!options.json) writeProgress(line);
51459
+ }, "say");
51460
+ const jobId = await startOrAttach(api, {
51461
+ from: since,
51462
+ to: until ?? (/* @__PURE__ */ new Date()).toISOString(),
51463
+ sources
51464
+ }, say);
51465
+ if (options.noWait || options.wait === false) {
51466
+ emit2(options, {
51467
+ jobId,
51468
+ state: "queued",
51469
+ files: []
51470
+ });
51471
+ trackEvent("cli_logs_export", {
51472
+ waited: false,
51473
+ json_output: !!options.json,
51474
+ downloaded: 0
51475
+ });
51476
+ return;
51477
+ }
51478
+ const job = await pollToTerminal(api, jobId, say);
51479
+ if (job.state === "failed") {
51480
+ throw CliError.fromStatus(500, `The export failed: ${job.error ?? "no reason recorded"}`);
51481
+ }
51482
+ if (job.state === "expired") {
51483
+ throw CliError.fromStatus(410, "The export expired before it could be downloaded.");
51484
+ }
51485
+ const written = outDir ? await downloadParts(job, outDir, say) : [];
51486
+ emit2(options, {
51487
+ jobId,
51488
+ state: job.state,
51489
+ files: job.files,
51490
+ written,
51491
+ truncated: job.truncated
51492
+ });
51493
+ if (!options.json) {
51494
+ writeInfo(`Export ${jobId}: ${job.counts.rows} record(s) in ${job.files.length} file(s)` + (job.truncated ? " (TRUNCATED \u2014 the row cap was reached; narrow the window)" : ""));
51495
+ if (!outDir && job.files.length) {
51496
+ writeHintBlock({
51497
+ headline: `${job.files.length} part(s) are ready for 24 h`,
51498
+ lines: [
51499
+ {
51500
+ label: "Download them",
51501
+ command: `lua logs export --since ${options.since} --out ./export`
51502
+ },
51503
+ {
51504
+ label: "Read the signed URLs",
51505
+ command: `lua logs export --since ${options.since} --json`
51506
+ }
51507
+ ]
51508
+ });
51509
+ }
51510
+ }
51511
+ trackEvent("cli_logs_export", {
51512
+ waited: true,
51513
+ json_output: !!options.json,
51514
+ downloaded: written.length,
51515
+ truncated: !!job.truncated
51516
+ });
51517
+ },
51518
+ "logs export",
51519
+ // LUA-803: a refusal that escapes under `--json` is the typed envelope on stdout.
51520
+ {
51521
+ json: /* @__PURE__ */ __name(() => !!cmdObj?.json, "json")
51522
+ }
51523
+ );
51524
+ }
51525
+ __name(logsExportCommand, "logsExportCommand");
51526
+ function parseTypeFlag(raw) {
51527
+ const value3 = typeof raw === "string" ? raw.trim() : "";
51528
+ if (!value3 || value3 === "all") return void 0;
51529
+ return value3.split(",").map((one) => one.trim()).filter(Boolean).map((one) => validateOrSuggest("logs.type", one));
51530
+ }
51531
+ __name(parseTypeFlag, "parseTypeFlag");
51532
+ function resolveOutDir(raw) {
51533
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(raw)) {
51534
+ throw CliError.usage(`logs export: --out must be a local directory, not ${raw}`);
51535
+ }
51536
+ const dir = resolvePath2(raw);
51537
+ if (!existsSync9(dir)) mkdirSync8(dir, {
51538
+ recursive: true
51539
+ });
51540
+ return dir;
51541
+ }
51542
+ __name(resolveOutDir, "resolveOutDir");
51543
+ async function startOrAttach(api, input, say) {
51544
+ const body = {
51545
+ from: input.from,
51546
+ to: input.to,
51547
+ ...input.sources ? {
51548
+ sources: input.sources
51549
+ } : {}
51550
+ };
51551
+ const started = await callExport(() => api.startExport(body));
51552
+ if (started.success && started.data?.jobId) {
51553
+ say(`Export ${started.data.jobId} queued`);
51554
+ return started.data.jobId;
51555
+ }
51556
+ if (started.error?.code === ALREADY_RUNNING) {
51557
+ const running = /\b(lex_[0-9a-f]{24})\b/.exec(started.error.message ?? "")?.[1];
51558
+ if (running) {
51559
+ say(`This organization already has export ${running} running \u2014 following it`);
51560
+ return running;
51561
+ }
51562
+ }
51563
+ throw refusal(started);
51564
+ }
51565
+ __name(startOrAttach, "startOrAttach");
51566
+ async function pollToTerminal(api, jobId, say) {
51567
+ let delay = LOG_EXPORT_POLL.intervalMs;
51568
+ let last;
51569
+ for (let attempt = 0; attempt < LOG_EXPORT_POLL.maxAttempts; attempt++) {
51570
+ if (attempt > 0) {
51571
+ await LOG_EXPORT_POLL.sleep(delay);
51572
+ delay = Math.min(LOG_EXPORT_POLL.maxIntervalMs, delay * 2);
51573
+ }
51574
+ const res = await callExport(() => api.getExport(jobId));
51575
+ if (!res.success || !res.data) throw refusal(res);
51576
+ last = res.data;
51577
+ if (TERMINAL2.has(last.state)) return last;
51578
+ say(`Export ${jobId}: ${last.state} \u2014 ${last.counts.rows} record(s) so far`);
51579
+ }
51580
+ throw CliError.fromStatus(504, `The export ${jobId} is still running after ${LOG_EXPORT_POLL.maxAttempts} polls.`, `Check it later with: lua logs export --since ${last?.window.from ?? "24h"} --no-wait`);
51581
+ }
51582
+ __name(pollToTerminal, "pollToTerminal");
51583
+ async function downloadParts(job, outDir, say) {
51584
+ const written = [];
51585
+ for (const file of job.files) {
51586
+ if (!file.url) {
51587
+ throw CliError.fromStatus(410, `Part ${file.seq} of ${job.id} no longer has a download URL (it expires 24 h after the export finishes).`);
51588
+ }
51589
+ const target = join9(outDir, basename(String(file.name)));
51590
+ say(`Downloading ${basename(target)} (${file.rows} record(s))`);
51591
+ const bytes = await LOG_EXPORT_DOWNLOAD.fetchBytes(file.url);
51592
+ writeFileSync10(target, bytes);
51593
+ written.push(target);
51594
+ }
51595
+ return written;
51596
+ }
51597
+ __name(downloadParts, "downloadParts");
51598
+ async function callExport(call) {
51599
+ try {
51600
+ return await call();
51601
+ } catch (error) {
51602
+ if (CliError.isCliError(error) && error.statusCode === 403) {
51603
+ throw CliError.forbidden(MISSING_EXPORT_SCOPE_MESSAGE);
51604
+ }
51605
+ throw error;
51606
+ }
51607
+ }
51608
+ __name(callExport, "callExport");
51609
+ function emit2(options, payload) {
51610
+ if (options.json) console.log(JSON.stringify(payload, null, 2));
51611
+ }
51612
+ __name(emit2, "emit");
51613
+ function refusal(response) {
51614
+ const error = response.error;
51615
+ if (error?.statusCode === 403) return CliError.forbidden(MISSING_EXPORT_SCOPE_MESSAGE);
51616
+ const message = error?.message || "The log export API refused the request.";
51617
+ const line = error?.code ? `${error.code}: ${message}` : message;
51618
+ return CliError.fromStatus(error?.statusCode, line, void 0, apiErrorDetail(error));
51619
+ }
51620
+ __name(refusal, "refusal");
51621
+
50081
51622
  // src/commands/completion.ts
50082
51623
  init_cli();
50083
51624
  init_analytics();
@@ -50985,8 +52526,8 @@ init_credential_operational_context();
50985
52526
  init_files();
50986
52527
  init_artifact_loader();
50987
52528
  import chalk3 from "chalk";
50988
- import { existsSync as existsSync9, readFileSync as readFileSync13 } from "fs";
50989
- import { join as join9 } from "path";
52529
+ import { existsSync as existsSync10, readFileSync as readFileSync13 } from "fs";
52530
+ import { join as join10 } from "path";
50990
52531
  import { performance } from "perf_hooks";
50991
52532
  import * as os from "os";
50992
52533
  init_semver();
@@ -51176,7 +52717,7 @@ function gatherProject(config) {
51176
52717
  };
51177
52718
  }
51178
52719
  const rootDir = process.cwd();
51179
- const configPath = join9(rootDir, COMPILE_FILES.LUA_SKILL_YAML);
52720
+ const configPath = join10(rootDir, COMPILE_FILES.LUA_SKILL_YAML);
51180
52721
  const agentId = config.agent?.agentId != null ? String(config.agent.agentId) : null;
51181
52722
  let agentName = null;
51182
52723
  let manifestFound = false;
@@ -51358,7 +52899,7 @@ function gatherTelemetry() {
51358
52899
  ].includes(envVal.toLowerCase());
51359
52900
  } else {
51360
52901
  try {
51361
- if (existsSync9(TELEMETRY_FILE)) {
52902
+ if (existsSync10(TELEMETRY_FILE)) {
51362
52903
  const raw = readFileSync13(TELEMETRY_FILE, "utf8");
51363
52904
  const cfg = JSON.parse(raw);
51364
52905
  if (typeof cfg.enabled === "boolean") enabled = cfg.enabled;
@@ -54404,7 +55945,7 @@ var TERMINAL_CANCELLED = /* @__PURE__ */ new Set([
54404
55945
  "cancelled",
54405
55946
  "abandoned"
54406
55947
  ]);
54407
- var TERMINAL2 = /* @__PURE__ */ new Set([
55948
+ var TERMINAL3 = /* @__PURE__ */ new Set([
54408
55949
  ...TERMINAL_FAILED,
54409
55950
  ...TERMINAL_CANCELLED,
54410
55951
  "completed"
@@ -55648,14 +57189,14 @@ function frameSeq(frame, ev) {
55648
57189
  return typeof ev.seq === "number" ? ev.seq : void 0;
55649
57190
  }
55650
57191
  __name(frameSeq, "frameSeq");
55651
- async function watchBoundary(ctx, runId, state3, seq, line, code, emit2) {
57192
+ async function watchBoundary(ctx, runId, state3, seq, line, code, emit3) {
55652
57193
  if (state3.truth === void 0 || state3.truth !== null && state3.truth.watermark === void 0) {
55653
57194
  state3.truth = await readWatchTruth(ctx, runId);
55654
57195
  }
55655
57196
  const truth = state3.truth;
55656
- if (truth === null) return emit2(line, code);
55657
- if (truth.watermark === void 0 || seq === void 0) return truth.parked ? emit2(line, code) : void 0;
55658
- if (seq > truth.watermark) return emit2(line, code);
57197
+ if (truth === null) return emit3(line, code);
57198
+ if (truth.watermark === void 0 || seq === void 0) return truth.parked ? emit3(line, code) : void 0;
57199
+ if (seq > truth.watermark) return emit3(line, code);
55659
57200
  if (!truth.parked) return void 0;
55660
57201
  state3.pending = {
55661
57202
  line,
@@ -55788,7 +57329,7 @@ async function cancelCore(ctx, runId, o) {
55788
57329
  emitJson(ctx, res);
55789
57330
  if (!ctx.json) {
55790
57331
  const v = res.data;
55791
- const alreadyTerminal = v.transitioned !== true && (v.state === "terminal" || v.state === "abandoned" || TERMINAL2.has(v.status));
57332
+ const alreadyTerminal = v.transitioned !== true && (v.state === "terminal" || v.state === "abandoned" || TERMINAL3.has(v.status));
55792
57333
  if (alreadyTerminal) {
55793
57334
  writeInfo(`\u2139\uFE0F run ${runId} was already ${v.status} \u2014 nothing to cancel`);
55794
57335
  return WORKFLOW_EXIT.OK;
@@ -56725,7 +58266,7 @@ async function exportCore(ctx, name, o) {
56725
58266
  } : {},
56726
58267
  message: w.message
56727
58268
  }));
56728
- const refusal = issues.length === 0 ? void 0 : checkIssues.length === 0 ? new CliError("export_not_expressible", `"${wf.name}" v${d.version} has ${inexpressible.length} node${inexpressible.length === 1 ? "" : "s"} the export codegen has no SDK spelling for \u2014 the file compiles WITHOUT ${inexpressible.length === 1 ? "it" : "them"} and cannot round-trip`, {
58269
+ const refusal2 = issues.length === 0 ? void 0 : checkIssues.length === 0 ? new CliError("export_not_expressible", `"${wf.name}" v${d.version} has ${inexpressible.length} node${inexpressible.length === 1 ? "" : "s"} the export codegen has no SDK spelling for \u2014 the file compiles WITHOUT ${inexpressible.length === 1 ? "it" : "them"} and cannot round-trip`, {
56729
58270
  issues,
56730
58271
  hint: "The file is written for review \u2014 add the node by hand, or keep the version dynamic (the chat composer edits it in place)."
56731
58272
  }) : new CliError("export_not_compilable", `"${wf.name}" v${d.version} exported to a file \`lua compile\` refuses \u2014 ${checkIssues.length} error${checkIssues.length === 1 ? "" : "s"} (the export codegen could not produce a file the SDK builder accepts)`, {
@@ -56737,12 +58278,12 @@ async function exportCore(ctx, name, o) {
56737
58278
  ...d,
56738
58279
  warnings
56739
58280
  };
56740
- if (refusal) {
58281
+ if (refusal2) {
56741
58282
  console.log(JSON.stringify({
56742
- ...cliErrorEnvelope(refusal),
58283
+ ...cliErrorEnvelope(refusal2),
56743
58284
  data
56744
58285
  }, null, 2));
56745
- return refusal.exitCode;
58286
+ return refusal2.exitCode;
56746
58287
  }
56747
58288
  console.log(JSON.stringify({
56748
58289
  ...res,
@@ -56764,14 +58305,14 @@ async function exportCore(ctx, name, o) {
56764
58305
  console.log(` ${f.path}`);
56765
58306
  }
56766
58307
  const written = `exported "${wf.name}" v${d.version} (${d.form} form) \u2014 ${d.files.length} file(s) under ${outDir}`;
56767
- if (refusal) {
58308
+ if (refusal2) {
56768
58309
  console.error(`\u274C ${written}, but ${check && !check.ok ? "the file does not compile as written" : "the file cannot round-trip"}`);
56769
58310
  } else {
56770
58311
  const verdict = check ? ` \xB7 compiles as written${check.workflows[0]?.graphHash ? ` (${check.workflows[0].graphHash})` : ""}` : ` \xB7 dry compile skipped \u2014 the file compiles once the TODO tool import${todoTools.length === 1 ? "" : "s"} ${todoTools.length === 1 ? "is" : "are"} resolved`;
56771
58312
  writeSuccess(`\u2705 ${written}${verdict}`);
56772
58313
  }
56773
58314
  for (const w of warnings) console.warn(`\u26A0\uFE0F ${w.code}${w.path ? ` at ${w.path}` : ""}: ${w.message}`);
56774
- if (refusal) throw refusal;
58315
+ if (refusal2) throw refusal2;
56775
58316
  if (warnings.length) writeInfo("\u{1F4A1} Review the TODOs the warnings name; `lua push workflow` then mints a static version from the file.");
56776
58317
  return WORKFLOW_EXIT.OK;
56777
58318
  }
@@ -56789,7 +58330,7 @@ function parseSinceFlag(raw, now = Date.now()) {
56789
58330
  __name(parseSinceFlag, "parseSinceFlag");
56790
58331
  var ARCHIVE_EXPORT_TTL_DAYS = 7;
56791
58332
  var ARCHIVE_TERMINAL_STATUS_FILTER = [
56792
- ...TERMINAL2
58333
+ ...TERMINAL3
56793
58334
  ].join(",");
56794
58335
  var ARCHIVE_DEFAULT_RETENTION_DAYS = 90;
56795
58336
  var archiveDownload = {
@@ -57204,7 +58745,7 @@ async function jobLogsCore(ctx, runId, stepId, o) {
57204
58745
  }
57205
58746
  for (const line of v.logs.slice(printed)) console.log(` ${line}`);
57206
58747
  printed = Math.max(printed, v.logs.length);
57207
- const terminal = TERMINAL2.has(v.status) || /^(completed|failed|cancelled|skipped|reaped|deleted)$/.test(v.status);
58748
+ const terminal = TERMINAL3.has(v.status) || /^(completed|failed|cancelled|skipped|reaped|deleted)$/.test(v.status);
57208
58749
  if (!o.follow || terminal || v.attemptFinished || v.phase === "deleted") {
57209
58750
  if (o.follow && !ctx.json) writeInfo(` \u2014 ${v.status} (${v.phase})`);
57210
58751
  return WORKFLOW_EXIT.OK;
@@ -67000,8 +68541,8 @@ __name(telemetryCommand, "telemetryCommand");
67000
68541
  init_cli();
67001
68542
  init_command_utils();
67002
68543
  init_analytics();
67003
- import { writeFileSync as writeFileSync10, existsSync as existsSync10, unlinkSync as unlinkSync3 } from "fs";
67004
- import { resolve as resolve5, join as join10 } from "path";
68544
+ import { writeFileSync as writeFileSync11, existsSync as existsSync11, unlinkSync as unlinkSync3 } from "fs";
68545
+ import { resolve as resolve5, join as join11 } from "path";
67005
68546
  init_artifact_loader();
67006
68547
  init_types();
67007
68548
  function getProjectToolNames() {
@@ -67024,9 +68565,9 @@ async function governanceCommand(action) {
67024
68565
  return;
67025
68566
  }
67026
68567
  const srcDir = resolve5(process.cwd(), "src");
67027
- const targetDir = existsSync10(srcDir) ? srcDir : process.cwd();
67028
- const filePath = join10(targetDir, "governance.ts");
67029
- if (existsSync10(filePath)) {
68568
+ const targetDir = existsSync11(srcDir) ? srcDir : process.cwd();
68569
+ const filePath = join11(targetDir, "governance.ts");
68570
+ if (existsSync11(filePath)) {
67030
68571
  const { overwrite } = await safePrompt([
67031
68572
  {
67032
68573
  type: "confirm",
@@ -67117,7 +68658,7 @@ async function governanceCommand(action) {
67117
68658
  }
67118
68659
  }
67119
68660
  const content = generateGovernanceFile(setup);
67120
- writeFileSync10(filePath, content, "utf-8");
68661
+ writeFileSync11(filePath, content, "utf-8");
67121
68662
  const relativePath = filePath.replace(process.cwd() + "/", "");
67122
68663
  writeSuccess(`Created ${relativePath}`);
67123
68664
  console.log("");
@@ -67155,9 +68696,9 @@ async function governanceCommand(action) {
67155
68696
  __name(governanceCommand, "governanceCommand");
67156
68697
  async function governanceRemove() {
67157
68698
  const srcDir = resolve5(process.cwd(), "src");
67158
- const targetDir = existsSync10(srcDir) ? srcDir : process.cwd();
67159
- const filePath = join10(targetDir, "governance.ts");
67160
- const hasLocal = existsSync10(filePath);
68699
+ const targetDir = existsSync11(srcDir) ? srcDir : process.cwd();
68700
+ const filePath = join11(targetDir, "governance.ts");
68701
+ const hasLocal = existsSync11(filePath);
67161
68702
  const { confirm } = await safePrompt([
67162
68703
  {
67163
68704
  type: "confirm",
@@ -67508,8 +69049,8 @@ init_command_utils();
67508
69049
  init_artifact_loader();
67509
69050
  init_types();
67510
69051
  import { spawn as spawn3 } from "child_process";
67511
- import { existsSync as existsSync11, readdirSync as readdirSync4, statSync as statSync6 } from "fs";
67512
- import { join as join11, relative as relative2, resolve as resolve6 } from "path";
69052
+ import { existsSync as existsSync12, readdirSync as readdirSync4, statSync as statSync6 } from "fs";
69053
+ import { join as join12, relative as relative2, resolve as resolve6 } from "path";
67513
69054
  init_voice_api_service();
67514
69055
  init_constants();
67515
69056
 
@@ -68097,8 +69638,8 @@ function escapeRegex(s) {
68097
69638
  }
68098
69639
  __name(escapeRegex, "escapeRegex");
68099
69640
  function detectRunner(cwd) {
68100
- const pkgPath = join11(cwd, "package.json");
68101
- if (!existsSync11(pkgPath)) return null;
69641
+ const pkgPath = join12(cwd, "package.json");
69642
+ if (!existsSync12(pkgPath)) return null;
68102
69643
  try {
68103
69644
  const pkg2 = JSON.parse(__require("fs").readFileSync(pkgPath, "utf8"));
68104
69645
  const deps = {
@@ -68470,7 +70011,7 @@ function findVoiceTestFiles(root) {
68470
70011
  return;
68471
70012
  }
68472
70013
  for (const entry of entries) {
68473
- const full = join11(dir, entry);
70014
+ const full = join12(dir, entry);
68474
70015
  let s;
68475
70016
  try {
68476
70017
  s = statSync6(full);
@@ -69512,6 +71053,22 @@ function collect(value3, previous) {
69512
71053
  ];
69513
71054
  }
69514
71055
  __name(collect, "collect");
71056
+ function withGroupOptions(options, command) {
71057
+ const parent = command.parent;
71058
+ if (!parent) return options;
71059
+ const merged = {
71060
+ ...options
71061
+ };
71062
+ for (const option of command.options) {
71063
+ const key = option.attributeName();
71064
+ if (merged[key] !== void 0) continue;
71065
+ if (!parent.options.some((parentOption) => parentOption.attributeName() === key)) continue;
71066
+ if (parent.getOptionValueSource(key) !== "cli") continue;
71067
+ merged[key] = parent.getOptionValue(key);
71068
+ }
71069
+ return merged;
71070
+ }
71071
+ __name(withGroupOptions, "withGroupOptions");
69515
71072
  function setupAuthCommands(program2) {
69516
71073
  const authCommand = program2.command("auth").description("\u{1F510} Authentication management").addHelpText("after", `
69517
71074
  Examples:
@@ -69828,11 +71385,12 @@ Examples:
69828
71385
  $ lua channels Interactive management
69829
71386
  $ lua channels list List all channels
69830
71387
  `).action(channelsCommand);
69831
- program2.command("logs").description("\u{1F4CA} View agent and skill logs").option("--type <type>", "Filter by type: all, calls, or any of the 18 log sources (skill, job, webhook, trigger, preprocessor, postprocessor, user_message, agent_response, agent_error, runtime, mcp, rag, device, device-trigger, model-resolver, workflow-step, workflow-script, workflow)").option("--name <name>", "Entity name to filter (requires --type)").option("--user-id <userId>", "Filter logs by user ID").option("--agent-id <agentId>", "View logs for a specific agent ID (admin access required)").option("--limit <n>", "Number of logs to show (default: 20)").option("--page <n>", "Page number for pagination").option("--json", "Output as JSON (for scripting)").option("--direction <direction>", "Filter calls by direction (inbound | outbound) \u2014 only with --type calls").option("--status <status>", "Filter calls by status \u2014 only with --type calls").option("--since <when>", "Only logs at or after this time: an ISO 8601 instant, or 15m | 2h | 7d").option("--until <when>", "Only logs at or before this time; same formats (ends a --follow)").option("--environment <env>", "production | sandbox (a row with no environment reads as production)").option("--follow", "Stream new logs as they arrive (Ctrl-C to stop); not valid with --page").addHelpText("after", `
71388
+ const logsGroup = program2.command("logs").description("\u{1F4CA} View agent and skill logs").option("--type <type>", "Filter by type: all, calls, or any of the 18 log sources (skill, job, webhook, trigger, preprocessor, postprocessor, user_message, agent_response, agent_error, runtime, mcp, rag, device, device-trigger, model-resolver, workflow-step, workflow-script, workflow)").option("--name <name>", "Entity name to filter (requires --type)").option("--user-id <userId>", "Filter logs by user ID").option("--channel <channel>", "Filter logs by messaging channel (e.g. whatsapp, slack, integration:<type>)").option("--agent-id <agentId>", "View logs for a specific agent ID (admin access required)").option("--limit <n>", "Number of logs to show (default: 20)").option("--page <n>", "Page number for pagination").option("--json", "Output as JSON (for scripting)").option("--direction <direction>", "Filter calls by direction (inbound | outbound) \u2014 only with --type calls").option("--status <status>", "Filter calls by status \u2014 only with --type calls").option("--since <when>", "Only logs at or after this time: an ISO 8601 instant, or 15m | 2h | 7d").option("--until <when>", "Only logs at or before this time; same formats (ends a --follow)").option("--environment <env>", "production | sandbox (a row with no environment reads as production)").option("--follow", "Stream new logs as they arrive (Ctrl-C to stop); not valid with --page").addHelpText("after", `
69832
71389
  Options:
69833
71390
  --type <type> Filter by type: all, calls, or any of the 18 log sources (skill, job, webhook, trigger, preprocessor, postprocessor, user_message, agent_response, agent_error, runtime, mcp, rag, device, device-trigger, model-resolver, workflow-step, workflow-script, workflow)
69834
71391
  --name <name> Entity name to filter (requires --type, not for message types)
69835
71392
  --user-id <userId> Filter logs by user ID
71393
+ --channel <channel> Filter logs by messaging channel (e.g. whatsapp, slack, integration:<type>)
69836
71394
  --agent-id <agentId> View logs for a specific agent ID (admin access required, overrides the agent in lua.config.yaml)
69837
71395
  --limit <n> Number of logs to show (default: 20)
69838
71396
  --page <n> Page number for pagination
@@ -69853,6 +71411,7 @@ Examples:
69853
71411
  $ lua logs --type mcp View MCP tool execution logs
69854
71412
  $ lua logs --user-id user-123 View all logs for a user
69855
71413
  $ lua logs --user-id user-123 --json > user-logs.json Export user logs
71414
+ $ lua logs --channel integration:lua_crm Isolate one integration's wake-up turns
69856
71415
  $ lua logs --agent-id agent-abc123 Logs for a different agent you admin
69857
71416
  $ lua logs --type calls Recent voice calls (renders as table)
69858
71417
  $ lua logs --type calls --direction outbound Outbound calls only
@@ -69868,6 +71427,28 @@ Notes:
69868
71427
  shift the window. --type calls reads voice calls and ignores --since/--until/--environment
69869
71428
  /--follow. --since is INCLUSIVE; the boundary row is de-duplicated by id while following.
69870
71429
  `).action(logsCommand);
71430
+ logsGroup.command("export").description("\u{1F4E6} Export this organization's logs as gzip NDJSON (asynchronous job)").option("--since <when>", "Inclusive lower bound (REQUIRED): an ISO 8601 instant, or 24h | 7d").option("--until <when>", "Inclusive upper bound; same formats (default: now)").option("--type <type>", "Comma-separated log sources, or all (default: all)").option("--out <dir>", "Download the parts into this directory (created if missing)").option("--org <id>", "The organization to export (default: the project, or the key when it reaches one org)").option("--no-wait", "Start the export and print the job id without polling it").option("--json", "Output as JSON").addHelpText("after", `
71431
+ Options:
71432
+ --since <when> REQUIRED. Inclusive lower bound: an ISO 8601 instant, or 24h | 7d
71433
+ --until <when> Inclusive upper bound, same formats (default: now)
71434
+ --type <type> Comma-separated log sources, or all (default: all)
71435
+ --out <dir> Download every part into this local directory
71436
+ --org <id> The organization, when the key reaches more than one
71437
+ --no-wait Start it and print the job id; poll later
71438
+ --json Output as JSON for scripting
71439
+
71440
+ Examples:
71441
+ $ lua logs export --since 7d --out ./export A week, downloaded
71442
+ $ lua logs export --since 30d --type skill,job A month, two sources
71443
+ $ lua logs export --since 24h --no-wait --json Start it and return
71444
+
71445
+ Notes:
71446
+ The window is at most 31 days and the job at most 10 000 000 records; narrow the window
71447
+ or the sources rather than raising either. One export runs per organization at a time \u2014
71448
+ a second invocation FOLLOWS the running one instead of refusing. Download links expire
71449
+ 24 h after the export finishes and the files are removed after 7 days. Message bodies
71450
+ are excluded unless the export was started with the logs:manage scope.
71451
+ `).action((options, command) => logsExportCommand(withGroupOptions(options ?? {}, command)));
69871
71452
  program2.command("skills [actionOrEnv] [action]").description("\u2699\uFE0F Manage and view skills").option("--skill-name <name>", "Skill name").option("--skill-version <version>", "Version for deploy action (or 'latest')").addHelpText("after", `
69872
71453
  Arguments:
69873
71454
  actionOrEnv Optional: environment ('sandbox', 'production') or action ('view', 'versions', 'deploy', 'delete')
@@ -69909,18 +71490,25 @@ Examples:
69909
71490
  $ lua webhooks subscribe --webhook-name myWebhook --event message.delivered Subscribe to event
69910
71491
  $ lua webhooks unsubscribe --webhook-name myWebhook --event message.delivered Unsubscribe from event
69911
71492
  `).action(webhooksCommand);
69912
- program2.command("drains [action] [id]").description("\u{1F6B0} Manage log drains (stream this organization's logs to your own destination)").option("--json", "Output as JSON").option("--yes", "Skip confirmation prompts (required with --include-content in CI)").option("--org <id>", "The organization to act on (default: the project, or the key when it reaches one org)").option("--name <name>", "Drain name (1-64 chars, unique per org)").option("--type <type>", "http | otlp | datadog | betterstack").option("--endpoint <url>", "Destination URL (http and otlp only; https, public host)").option("--site <site>", "Preset site/region, e.g. datadoghq.eu").option("--format <format>", "json | ndjson").option("--header <name>", "Custom header NAME (the value is prompted)", collect, []).option("--header-from-env <name=ENV_VAR>", "Custom header, value read from an env var (CI form)", collect, []).option("--sources <list>", "Comma-separated source selectors (default: the recommended set)").option("--min-severity <level>", "debug | info | warn | error (default info)").option("--agents <list>", "'all' or a comma-separated list of agent ids").option("--environments <list>", "production,sandbox (default production)").option("--sampling <spec>", "Head sampling, e.g. debug=0.1").option("--include-content", "Include user messages and agent responses (prints an acknowledgement)").option("--no-include-content", "update: stop sending message bodies (the drain keeps its sources)").option("--limit <n>", "deliveries: rows to return (max 100)").option("--kind <kind>", "deliveries: batch | test | verify | heartbeat | dropped | truncated").option("--reason <text>", "pause: why").option("--finalize", "rotate-secret: close an open rotation window early").addHelpText("after", `
71493
+ program2.command("drains [action] [id] [batchId]").description("\u{1F6B0} Manage log drains (stream this organization's logs to your own destination)").option("--json", "Output as JSON").option("--yes", "Skip confirmation prompts (required with --include-content in CI)").option("--org <id>", "The organization to act on (default: the project, or the key when it reaches one org)").option("--name <name>", "Drain name (1-64 chars, unique per org)").option("--type <type>", "http | otlp | datadog | betterstack | splunk | loki | axiom | newrelic | sumologic | s3").option("--endpoint <url>", "Destination URL (http/otlp), or the S3-compatible endpoint for --type s3 on R2/GCS").option("--site <site>", "Preset site/region, e.g. datadoghq.eu").option("--provider <name>", "s3: aws | r2 | gcs (default aws)").option("--bucket <name>", "s3: the bucket name").option("--region <code>", "s3: the bucket's region (use 'auto' for R2)").option("--prefix <key>", "s3: key prefix under which objects are written").option("--credentials-from-env <KEY_ID_VAR=SECRET_VAR>", "s3: read the access key pair from two environment variables").option("--role-arn <arn>", "s3 (aws): assume this role instead of holding a key pair").option("--external-id <id>", "s3 (aws): the sts:ExternalId the role trust policy requires").option("--token <token>", "confirm: the token from the _lua-verify-<token> object in your bucket").option("--format <format>", "json | ndjson").option("--header <name>", "Custom header NAME (the value is prompted)", collect, []).option("--header-from-env <name=ENV_VAR>", "Custom header, value read from an env var (CI form)", collect, []).option("--sources <list>", "Comma-separated source selectors (default: the recommended set)").option("--min-severity <level>", "debug | info | warn | error (default info)").option("--agents <list>", "'all' or a comma-separated list of agent ids").option("--environments <list>", "production,sandbox (default production)").option("--sampling <spec>", "Head sampling, e.g. debug=0.1").option("--include-content", "Include user messages and agent responses (prints an acknowledgement)").option("--no-include-content", "update: stop sending message bodies (the drain keeps its sources)").option("--limit <n>", "deliveries: rows to return (max 100)").option("--kind <kind>", "deliveries: batch | test | verify | heartbeat | dropped | truncated").option("--reason <text>", "pause: why").option("--finalize", "rotate-secret: close an open rotation window early").addHelpText("after", `
69913
71494
  Arguments:
69914
71495
  action Optional: list (default), status, deliveries, create, update, delete,
69915
- test, verify, pause, resume, rotate-secret
69916
- id The drain id, for every action but list, status and create
71496
+ test, verify, confirm, pause, resume, replay, rotate-secret
71497
+ id The drain id, for every action but list and create. The READ verbs
71498
+ (status, deliveries) also accept a drain's exact name (PRO-2124);
71499
+ the mutating verbs, replay included, stay id-only
71500
+ batchId replay only: the batch id from a delivery row
69917
71501
 
69918
71502
  Examples:
69919
71503
  $ lua drains List this org's drains
69920
71504
  $ lua drains status --json Health + quota (exit 2 if any is failing)
71505
+ $ lua drains deliveries prod-siem By name \u2014 read verbs resolve it to an id
69921
71506
  $ lua drains create --name prod-siem --type http --endpoint https://logs.example.com/lua
71507
+ $ lua drains create --name archive --type s3 --bucket acme-logs --region eu-west-1 --prefix lua/logs --credentials-from-env AWS_KEY_ID=AWS_SECRET Object storage (S3/R2/GCS)
69922
71508
  $ lua drains verify drn_... Prove you own the endpoint
71509
+ $ lua drains confirm drn_... --token <token> Settle a bucket ownership challenge
69923
71510
  $ lua drains deliveries drn_... --limit 20 --kind batch Recent delivery attempts
71511
+ $ lua drains replay drn_... 01J... Re-enqueue that batch's records
69924
71512
  $ lua drains rotate-secret drn_... New signing secret (shown once)
69925
71513
  $ lua drains rotate-secret drn_... --finalize Close the rotation window
69926
71514
  $ lua drains pause drn_... --reason "noisy vendor" Stop delivering (buffering continues)
@@ -69929,7 +71517,16 @@ Examples:
69929
71517
  Notes:
69930
71518
  Secrets are never flags. --header takes a NAME and prompts for the value; in --ci mode use
69931
71519
  --header-from-env DD_API_KEY=DATADOG_KEY. The HMAC signing secret is minted by the server and
69932
- printed exactly once, at create and at rotate-secret.
71520
+ printed exactly once, at create and at rotate-secret. An object-storage drain's secret access
71521
+ key is the same: --credentials-from-env KEY_ID_VAR=SECRET_VAR, never a value on the command line.
71522
+
71523
+ A terminal refusal (a 4xx that is not 408/429) drops the whole batch. 'lua drains replay <id>
71524
+ <batchId>' puts its records back in the queue, for as long as they are inside the six-hour
71525
+ delivery horizon \u2014 after that they are gone and the command says so.
71526
+
71527
+ A bucket drain proves ownership by writing _lua-verify-<token> under its prefix. If the role can
71528
+ read it back, 'lua drains verify' finishes on its own; if it is write-only \u2014 the grant we
71529
+ recommend \u2014 list your prefix, find the object, and run 'lua drains confirm <id> --token <token>'.
69933
71530
  `).action(drainsCommand);
69934
71531
  program2.command("devices [action]").description("\u{1F4E1} Manage connected devices").option("--device-name <name>", "Device name").option("--group <group>", "Filter by device group").option("--payload <json>", "JSON payload for test/test-trigger", "{}").option("--timeout <ms>", "Timeout in milliseconds", "30000").option("--force", "Skip confirmation prompts").addHelpText("after", `
69935
71532
  Arguments: