@prismshadow/penguin-server 0.2.8 → 0.2.9

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.
@@ -615,6 +615,13 @@ interface ModelOAuthStatusResponse {
615
615
  status: "pending" | "done" | "error";
616
616
  /** The provider group the flow mints a key for. */
617
617
  provider: string;
618
+ /**
619
+ * How many models the minted key was written to — set on the `done` answer, the way
620
+ * {@link ModelOAuthCodeResponse} sets it on a redemption. The dialog reports this number, so
621
+ * it has to be the server's own: the caller's model table can outlive a rejected save and
622
+ * would otherwise name a count the server never wrote.
623
+ */
624
+ applied?: number;
618
625
  error?: ModelOAuthErrorCode;
619
626
  }
620
627
  /**
@@ -1483,7 +1490,7 @@ interface SessionProcessesResponse {
1483
1490
  processes: SessionProcessInfo[];
1484
1491
  }
1485
1492
  /** Messaging channels a Session can bind to. */
1486
- type MessagingChannel = "feishu" | "telegram" | "qq";
1493
+ type MessagingChannel = "feishu" | "telegram" | "qq" | "wechat";
1487
1494
  /** Event-connection runtime state of one binding (kept in memory, not persisted). */
1488
1495
  type MessagingRuntimeState = "disconnected" | "connecting" | "connected" | "error";
1489
1496
  /**
@@ -1542,18 +1549,24 @@ interface MessagingRuntimeStatus {
1542
1549
  detail: string;
1543
1550
  };
1544
1551
  }
1545
- /** The stored Feishu config, secret masked (plaintext never leaves the server). */
1546
- interface FeishuBindingInfo {
1547
- channel: "feishu";
1552
+ /**
1553
+ * What a messaging binding carries whatever channel it is: the connection intent, the three
1554
+ * delivery preferences, whether a chat is known, and the row's identity and timestamps.
1555
+ *
1556
+ * Split out because this is the half that grows. Three delivery preferences arrived in one
1557
+ * release, and each would otherwise have been written into three interfaces — the credential
1558
+ * slice is the only thing a channel genuinely differs in, and it is what stays per-channel.
1559
+ *
1560
+ * `channel` is NOT here: a discriminated union discriminates on a literal the member itself
1561
+ * declares, so each interface keeps its own.
1562
+ *
1563
+ * A channel may RE-DECLARE a field below when it has more to say about it — QQ does, for all
1564
+ * three preferences, because the platform's passive-reply budget and its expiring window
1565
+ * change what setting one costs there. The re-declared type must be identical, which the
1566
+ * compiler enforces, so the redeclaration can only add documentation and never a contract.
1567
+ */
1568
+ interface MessagingBindingCommon {
1548
1569
  sessionId: string;
1549
- appId: string;
1550
- /**
1551
- * Masked app secret (site-wide mask rule: `***`, or `first4…last4` for long values);
1552
- * absent when no secret is stored (never entered, or cleared) — the binding cannot be
1553
- * enabled until one is saved.
1554
- */
1555
- appSecretMasked?: string;
1556
- baseDomain: string;
1557
1570
  /** Connection INTENT (the state toggle's value); new bindings start disabled, and at most one of a Session's channels is enabled. */
1558
1571
  enabled: boolean;
1559
1572
  /**
@@ -1571,23 +1584,35 @@ interface FeishuBindingInfo {
1571
1584
  /**
1572
1585
  * Render a relayed reply's Markdown in this channel's own markup instead of sending its
1573
1586
  * characters as written. ON by default. Each channel shows what it can — Telegram has no
1574
- * headings, lists or tables, QQ has no code or tables, Feishu has all of them — and a
1587
+ * headings, lists or tables, QQ has no code or tables, Feishu has all of them, and WeChat
1588
+ * reads Markdown itself so the render is a subtraction rather than a translation — and a
1575
1589
  * rendering the channel refuses falls back to the plain source, so this can cost
1576
1590
  * formatting and never a message.
1577
1591
  */
1578
1592
  renderMarkdown: boolean;
1579
1593
  /**
1580
- * Whether an inbound Feishu chat is known (the bot has been messaged at least once).
1581
- * Replies and test messages target that chat; until it exists nothing can be sent.
1594
+ * Whether an inbound chat is known (the bot has been messaged at least once). Replies and
1595
+ * test messages target that chat; until it exists nothing can be sent.
1582
1596
  */
1583
1597
  lastChatKnown: boolean;
1584
1598
  createdAt: string;
1585
1599
  updatedAt: string;
1586
1600
  }
1601
+ /** The stored Feishu config, secret masked (plaintext never leaves the server). */
1602
+ interface FeishuBindingInfo extends MessagingBindingCommon {
1603
+ channel: "feishu";
1604
+ appId: string;
1605
+ /**
1606
+ * Masked app secret (site-wide mask rule: `***`, or `first4…last4` for long values);
1607
+ * absent when no secret is stored (never entered, or cleared) — the binding cannot be
1608
+ * enabled until one is saved.
1609
+ */
1610
+ appSecretMasked?: string;
1611
+ baseDomain: string;
1612
+ }
1587
1613
  /** The stored Telegram config, token masked (plaintext never leaves the server). */
1588
- interface TelegramBindingInfo {
1614
+ interface TelegramBindingInfo extends MessagingBindingCommon {
1589
1615
  channel: "telegram";
1590
- sessionId: string;
1591
1616
  /** The numeric bot id (the token's half before the colon) — the channel-scoped account identity, never secret. */
1592
1617
  botId: string;
1593
1618
  /**
@@ -1596,40 +1621,15 @@ interface TelegramBindingInfo {
1596
1621
  * is saved.
1597
1622
  */
1598
1623
  botTokenMasked?: string;
1599
- /** Connection INTENT (the state toggle's value); new bindings start disabled, and at most one of a Session's channels is enabled. */
1600
- enabled: boolean;
1601
- /**
1602
- * Send each non-blank line of a relayed assistant reply as its own message instead of one
1603
- * message per reply. Off by default; off is the original one-message-per-reply behaviour.
1604
- */
1605
- linePerMessage: boolean;
1606
- /**
1607
- * Relay only the LAST completed assistant message of a run, delivered when the run ends,
1608
- * instead of mirroring each completed message as it completes. Off by default; off is the
1609
- * original every-message behaviour. Independent of `linePerMessage`, which then applies to
1610
- * that one final message. The approval notice is not a reply and is unaffected.
1611
- */
1612
- finalReplyOnly: boolean;
1613
- /**
1614
- * Render a relayed reply's Markdown in this channel's own markup instead of sending its
1615
- * characters as written. ON by default. Each channel shows what it can — Telegram has no
1616
- * headings, lists or tables, QQ has no code or tables, Feishu has all of them — and a
1617
- * rendering the channel refuses falls back to the plain source, so this can cost
1618
- * formatting and never a message.
1619
- */
1620
- renderMarkdown: boolean;
1621
- /**
1622
- * Whether an inbound Telegram chat is known (the bot has been messaged at least once).
1623
- * Replies and test messages target that chat; until it exists nothing can be sent.
1624
- */
1625
- lastChatKnown: boolean;
1626
- createdAt: string;
1627
- updatedAt: string;
1628
1624
  }
1629
- /** The stored QQ config, secret masked (plaintext never leaves the server). */
1630
- interface QQBindingInfo {
1625
+ /**
1626
+ * The stored QQ config, secret masked (plaintext never leaves the server).
1627
+ *
1628
+ * The three preference fields are re-declared unchanged: every one of them costs something
1629
+ * different here, and the platform's reply budget is what a reader setting one needs to know.
1630
+ */
1631
+ interface QQBindingInfo extends MessagingBindingCommon {
1631
1632
  channel: "qq";
1632
- sessionId: string;
1633
1633
  /** The bot's App ID — the channel-scoped account identity, never secret. */
1634
1634
  appId: string;
1635
1635
  /**
@@ -1638,8 +1638,6 @@ interface QQBindingInfo {
1638
1638
  * enabled until one is saved.
1639
1639
  */
1640
1640
  appSecretMasked?: string;
1641
- /** Connection INTENT (the state toggle's value); new bindings start disabled, and at most one of a Session's channels is enabled. */
1642
- enabled: boolean;
1643
1641
  /**
1644
1642
  * Send each non-blank line of a relayed assistant reply as its own message instead of one
1645
1643
  * message per reply. Off by default. On QQ the split is additionally capped at the
@@ -1648,10 +1646,8 @@ interface QQBindingInfo {
1648
1646
  */
1649
1647
  linePerMessage: boolean;
1650
1648
  /**
1651
- * Relay only the LAST completed assistant message of a run, delivered when the run ends,
1652
- * instead of mirroring each completed message as it completes. Off by default; off is the
1653
- * original every-message behaviour. Independent of `linePerMessage`, which then applies to
1654
- * that one final message. The approval notice is not a reply and is unaffected.
1649
+ * Relay only the LAST completed assistant message of a run, delivered when the run ends.
1650
+ * Off by default.
1655
1651
  *
1656
1652
  * On QQ it cuts both ways, which is worth knowing before setting it here. It spends the
1657
1653
  * least of the platform's passive-reply budget a run can spend — one message, where an
@@ -1662,25 +1658,41 @@ interface QQBindingInfo {
1662
1658
  */
1663
1659
  finalReplyOnly: boolean;
1664
1660
  /**
1665
- * Render a relayed reply's Markdown in this channel's own markup instead of sending its
1666
- * characters as written. ON by default. QQ's own subset is the widest for prose and the
1667
- * narrowest for code: headings, lists, blockquotes and rules render, while inline code,
1668
- * fenced code and tables have no syntax at all here — a code block arrives as plain
1669
- * escaped lines. A rendering the platform refuses falls back to the plain source, at the
1670
- * cost of one more slot from the passive-reply budget.
1661
+ * Render a relayed reply's Markdown in this channel's own markup. ON by default. QQ's own
1662
+ * subset is the widest for prose and the narrowest for code: headings, lists, blockquotes
1663
+ * and rules render, while inline code, fenced code and tables have no syntax at all here —
1664
+ * a code block arrives as plain escaped lines. A rendering the platform refuses falls back
1665
+ * to the plain source, at the cost of one more slot from the passive-reply budget.
1671
1666
  */
1672
1667
  renderMarkdown: boolean;
1673
1668
  /**
1674
- * Whether an inbound QQ chat is known (the bot has been messaged at least once).
1675
- * Weaker than it looks on this channel: QQ accepts only replies to a recent message, so a
1676
- * known chat is necessary but not sufficient for anything to be deliverable right now.
1669
+ * Whether an inbound QQ chat is known (the bot has been messaged at least once). Weaker
1670
+ * than it looks on this channel: QQ accepts only replies to a recent message, so a known
1671
+ * chat is necessary but not sufficient for anything to be deliverable right now.
1677
1672
  */
1678
1673
  lastChatKnown: boolean;
1679
- createdAt: string;
1680
- updatedAt: string;
1674
+ }
1675
+ /**
1676
+ * The stored WeChat config, token masked (plaintext never leaves the server).
1677
+ *
1678
+ * No preference field is re-declared. The three delivery preferences cost exactly what they
1679
+ * cost on Feishu and Telegram here — this channel has no reply budget and no expiring reply
1680
+ * window — and re-declaring a field to say nothing new about it would only invite the next
1681
+ * reader to look for the difference.
1682
+ */
1683
+ interface WeChatBindingInfo extends MessagingBindingCommon {
1684
+ channel: "wechat";
1685
+ /** The bot id a scan issued — the channel-scoped account identity, never secret. */
1686
+ botId: string;
1687
+ /**
1688
+ * Masked bot token (site-wide mask rule: `***`, or `first4…last4` for long values);
1689
+ * absent when none is stored (cleared, or a scan that never completed) — the binding
1690
+ * cannot be enabled until a scan saves one.
1691
+ */
1692
+ botTokenMasked?: string;
1681
1693
  }
1682
1694
  /** A Session's saved config for one messaging channel (`channel` is the discriminant). */
1683
- type MessagingBindingInfo = FeishuBindingInfo | TelegramBindingInfo | QQBindingInfo;
1695
+ type MessagingBindingInfo = FeishuBindingInfo | TelegramBindingInfo | QQBindingInfo | WeChatBindingInfo;
1684
1696
  /** One saved channel config with its event-connection runtime status. */
1685
1697
  interface MessagingChannelState {
1686
1698
  binding: MessagingBindingInfo;
@@ -1710,12 +1722,32 @@ interface QQBindingResponse {
1710
1722
  binding: QQBindingInfo | null;
1711
1723
  status: MessagingRuntimeStatus;
1712
1724
  }
1725
+ /** GET / PUT …/messaging/wechat response (the WeChat narrowing of the same envelope). */
1726
+ interface WeChatBindingResponse {
1727
+ binding: WeChatBindingInfo | null;
1728
+ status: MessagingRuntimeStatus;
1729
+ }
1713
1730
  /**
1714
1731
  * PUT …/messaging/feishu — saves credentials/config ONLY, never flipping the connection
1715
1732
  * (exception: an enabled binding's connector restarts with the new credentials so stored
1716
1733
  * config and live connection never diverge). The connection toggle is POST …/state.
1717
1734
  */
1718
- interface FeishuBindingPutRequest {
1735
+ /**
1736
+ * The delivery preferences any channel's PUT may set. Each is optional and each behaves the
1737
+ * same way on every channel: an omitted field keeps the stored value, so a client that knows
1738
+ * about one preference can save it without having to send the others back. Their meanings are
1739
+ * on {@link MessagingBindingCommon}, and where a channel charges differently for one, on that
1740
+ * channel's own `*BindingInfo`.
1741
+ *
1742
+ * The defaults a binding created without them starts from: `linePerMessage` off,
1743
+ * `finalReplyOnly` off, `renderMarkdown` ON.
1744
+ */
1745
+ interface MessagingDeliveryPatch {
1746
+ linePerMessage?: boolean;
1747
+ finalReplyOnly?: boolean;
1748
+ renderMarkdown?: boolean;
1749
+ }
1750
+ interface FeishuBindingPutRequest extends MessagingDeliveryPatch {
1719
1751
  appId: string;
1720
1752
  /** Omitted or blank keeps the stored secret (the masked value never round-trips). */
1721
1753
  appSecret?: string;
@@ -1726,21 +1758,6 @@ interface FeishuBindingPutRequest {
1726
1758
  * it). Refused with 409 `messaging_disable_before_clear` while the binding is enabled.
1727
1759
  */
1728
1760
  clearAppSecret?: boolean;
1729
- /**
1730
- * Delivery preference (see `FeishuBindingInfo.linePerMessage`). Omitted keeps the stored
1731
- * value; a binding created without it starts with it off.
1732
- */
1733
- linePerMessage?: boolean;
1734
- /**
1735
- * Delivery preference (see `FeishuBindingInfo.finalReplyOnly`). Omitted keeps the stored
1736
- * value; a binding created without it starts with it off.
1737
- */
1738
- finalReplyOnly?: boolean;
1739
- /**
1740
- * Delivery preference (see `FeishuBindingInfo.renderMarkdown`). Omitted keeps the stored
1741
- * value; a binding created without it starts with it ON.
1742
- */
1743
- renderMarkdown?: boolean;
1744
1761
  }
1745
1762
  /**
1746
1763
  * PUT …/messaging/telegram — saves the credential ONLY, same contract as the Feishu PUT
@@ -1748,7 +1765,7 @@ interface FeishuBindingPutRequest {
1748
1765
  * POST …/state). Saving never conflicts across Sessions: the same bot may sit saved on
1749
1766
  * several, and only enabling it is exclusive.
1750
1767
  */
1751
- interface TelegramBindingPutRequest {
1768
+ interface TelegramBindingPutRequest extends MessagingDeliveryPatch {
1752
1769
  /** Omitted or blank keeps the stored token (the masked value never round-trips). */
1753
1770
  botToken?: string;
1754
1771
  /**
@@ -1757,21 +1774,6 @@ interface TelegramBindingPutRequest {
1757
1774
  * `messaging_disable_before_clear` while the binding is enabled.
1758
1775
  */
1759
1776
  clearBotToken?: boolean;
1760
- /**
1761
- * Delivery preference (see `TelegramBindingInfo.linePerMessage`). Omitted keeps the stored
1762
- * value; a binding created without it starts with it off.
1763
- */
1764
- linePerMessage?: boolean;
1765
- /**
1766
- * Delivery preference (see `TelegramBindingInfo.finalReplyOnly`). Omitted keeps the stored
1767
- * value; a binding created without it starts with it off.
1768
- */
1769
- finalReplyOnly?: boolean;
1770
- /**
1771
- * Delivery preference (see `TelegramBindingInfo.renderMarkdown`). Omitted keeps the stored
1772
- * value; a binding created without it starts with it ON.
1773
- */
1774
- renderMarkdown?: boolean;
1775
1777
  }
1776
1778
  /**
1777
1779
  * PUT …/messaging/qq — saves the credential pair ONLY, same contract as the Feishu PUT
@@ -1779,7 +1781,7 @@ interface TelegramBindingPutRequest {
1779
1781
  * is POST …/state). The App ID is the account identity, so changing it rebinds the row to
1780
1782
  * a different bot and drops the remembered chat.
1781
1783
  */
1782
- interface QQBindingPutRequest {
1784
+ interface QQBindingPutRequest extends MessagingDeliveryPatch {
1783
1785
  appId: string;
1784
1786
  /** Omitted or blank keeps the stored secret (the masked value never round-trips). */
1785
1787
  appSecret?: string;
@@ -1788,21 +1790,23 @@ interface QQBindingPutRequest {
1788
1790
  * it). Refused with 409 `messaging_disable_before_clear` while the binding is enabled.
1789
1791
  */
1790
1792
  clearAppSecret?: boolean;
1793
+ }
1794
+ /**
1795
+ * PUT …/messaging/wechat — the delivery preferences ONLY.
1796
+ *
1797
+ * The one PUT on this router that carries no credential, because there is none to carry: a
1798
+ * WeChat bot token exists only where a scan put it, and there is no console to copy one out
1799
+ * of. Saving therefore presupposes a binding — a PUT before any scan answers 400
1800
+ * `wechat_token_required` rather than creating an empty row — and the connection toggle
1801
+ * stays POST …/state, as on every channel.
1802
+ */
1803
+ interface WeChatBindingPutRequest extends MessagingDeliveryPatch {
1791
1804
  /**
1792
- * Delivery preference (see `QQBindingInfo.linePerMessage`). Omitted keeps the stored
1793
- * value; a binding created without it starts with it off.
1794
- */
1795
- linePerMessage?: boolean;
1796
- /**
1797
- * Delivery preference (see `QQBindingInfo.finalReplyOnly`). Omitted keeps the stored
1798
- * value; a binding created without it starts with it off.
1799
- */
1800
- finalReplyOnly?: boolean;
1801
- /**
1802
- * Delivery preference (see `QQBindingInfo.renderMarkdown`). Omitted keeps the stored
1803
- * value; a binding created without it starts with it ON.
1805
+ * Drops the STORED token (the models-page clear idiom). Refused with 409
1806
+ * `messaging_disable_before_clear` while the binding is enabled. The row and its bot
1807
+ * identity stay; only a fresh scan can make it connectable again.
1804
1808
  */
1805
- renderMarkdown?: boolean;
1809
+ clearBotToken?: boolean;
1806
1810
  }
1807
1811
  /**
1808
1812
  * POST …/messaging/<channel>/state — enable connects with the STORED credentials,
@@ -1895,6 +1899,82 @@ interface QQScanPollResponse {
1895
1899
  /** The saved binding, present on `completed` so the editor refreshes without a second GET. */
1896
1900
  binding?: QQBindingInfo;
1897
1901
  }
1902
+ /**
1903
+ * WeChat credential-test outcome. There is no request body and no draft to probe: this
1904
+ * channel's credential exists only where a scan put it, so the test always probes the STORED
1905
+ * binding. It answers 400 `wechat_token_required` when there is none.
1906
+ *
1907
+ * No account label, for the same reason QQ has none: the probe (`getconfig`) reports the
1908
+ * bot's settings and names neither the bot nor the person.
1909
+ */
1910
+ interface WeChatTestResponse {
1911
+ ok: boolean;
1912
+ latencyMs?: number;
1913
+ error?: string;
1914
+ }
1915
+ /**
1916
+ * POST …/messaging/wechat/scan — starts a scan-to-connect flow, which on this channel is the
1917
+ * ONLY way to bind: the bot token has no console to be copied out of.
1918
+ *
1919
+ * The server registers a code and answers with what the browser may know. The platform's own
1920
+ * poll handle is absent from this type on purpose — it is what collects the bot token, so it
1921
+ * never leaves the server, and `taskId` is a handle this server mints in its place.
1922
+ */
1923
+ interface WeChatScanStartResponse {
1924
+ /** Opaque scan handle, passed back to the poll, verify and cancel endpoints. */
1925
+ taskId: string;
1926
+ /** The URL to ENCODE into a QR code. It is opened by WeChat, never fetched by the browser. */
1927
+ qrUrl: string;
1928
+ /** How often to poll, in milliseconds. */
1929
+ pollMs: number;
1930
+ }
1931
+ /**
1932
+ * Where a WeChat scan stands. Richer than QQ's four states because the flow is: WeChat
1933
+ * separates scanning from confirming, and may interpose a pairing code shown on the phone.
1934
+ *
1935
+ * - `pending` — the code is up and nothing has happened; keep polling.
1936
+ * - `scanned` — scanned; the phone is showing the confirmation prompt.
1937
+ * - `need_verify_code` — the phone is showing digits that must be sent to the verify
1938
+ * endpoint before the bind proceeds.
1939
+ * - `blocked` — too many wrong pairing codes; the code is spent and a new scan is needed.
1940
+ * - `expired` — the code lapsed before it was used; show a new one.
1941
+ * - `already_bound` — this bot is already bound to this server, so no new credentials were
1942
+ * issued and nothing was saved. Not a failure: the existing binding still works.
1943
+ * - `completed` — the server has already SAVED the binding; `botId` names the bot.
1944
+ */
1945
+ type WeChatScanStatus = "pending" | "scanned" | "need_verify_code" | "blocked" | "expired" | "already_bound" | "completed";
1946
+ /**
1947
+ * POST …/messaging/wechat/scan/poll — one step of the scan.
1948
+ *
1949
+ * `completed` means the server has decrypted nothing and stored everything: the credentials
1950
+ * went from the platform into storage without passing through the browser. Saving is all it
1951
+ * does — enabling the connection stays the separate, exclusive act it is on every channel. A
1952
+ * task id that is unknown, belongs to another Session, or was already resolved answers 404
1953
+ * `wechat_scan_task_unknown`.
1954
+ *
1955
+ * Unlike QQ's poll, an overlapping request answers `pending` rather than 404: the upstream
1956
+ * call is a LONG poll, so one of them spans several of the client's intervals, and that
1957
+ * overlap is the normal rhythm rather than a replay.
1958
+ */
1959
+ interface WeChatScanPollResponse {
1960
+ status: WeChatScanStatus;
1961
+ /** The bound bot's id; present only on `completed`. Never the token. */
1962
+ botId?: string;
1963
+ /** The saved binding, present on `completed` so the editor refreshes without a second GET. */
1964
+ binding?: WeChatBindingInfo;
1965
+ }
1966
+ /**
1967
+ * POST …/messaging/wechat/scan/verify — the digits WeChat showed on the phone.
1968
+ *
1969
+ * They ride the NEXT poll rather than a request of their own, because the platform takes the
1970
+ * pairing code as a parameter of the status call: this endpoint records them and answers
1971
+ * 204. A wrong code is not reported here — the next poll asks for one again, which is how
1972
+ * the platform reports it.
1973
+ */
1974
+ interface WeChatScanVerifyRequest {
1975
+ taskId: string;
1976
+ verifyCode: string;
1977
+ }
1898
1978
  /**
1899
1979
  * POST …/messaging/<channel>/test-message — sent to the last known chat (409
1900
1980
  * `feishu_no_chat` / `telegram_no_chat` / `qq_no_chat` before one exists). On QQ this can
@@ -2450,6 +2530,23 @@ interface UsageModelSeries extends UsageEntitySeriesCounts {
2450
2530
  provider: string;
2451
2531
  modelId: string;
2452
2532
  }
2533
+ /**
2534
+ * Lifetime Token total for one Model, keyed by the (provider, modelId) pair. Deliberately
2535
+ * unfiltered — no date range, no Agent: it answers "how much has this model been used", a
2536
+ * question with no window attached, and the models page that shows it carries no filters of its
2537
+ * own to honour. Models never used are absent rather than zero.
2538
+ */
2539
+ interface UsageModelTotal {
2540
+ provider: string;
2541
+ modelId: string;
2542
+ /** The three buckets summed (cache read + cache write + output), matching the cost center's Token figure. */
2543
+ tokens: number;
2544
+ requests: number;
2545
+ }
2546
+ /** Response of `GET /usage/model-totals`. */
2547
+ interface UsageModelTotals {
2548
+ totals: UsageModelTotal[];
2549
+ }
2453
2550
  /**
2454
2551
  * The two categories an error record is filed under: `unexpected` is a 500 or an unforeseen
2455
2552
  * runtime exception, `expected` an HttpError / business 4xx. The panel separates them by
@@ -2481,6 +2578,15 @@ interface UsageErrorItem {
2481
2578
  */
2482
2579
  interface UsageErrors {
2483
2580
  total: number;
2581
+ /**
2582
+ * How many of {@link total} a clear would actually take (see {@link UsageErrorsClearResponse}).
2583
+ *
2584
+ * The same as `total` for an ordinary member, and smaller for an admin, whose reads include
2585
+ * unattributed rows that no Project-scoped clear removes. The confirmation is the only place
2586
+ * this matters, and it is the place it matters most: an irreversible delete has to name the
2587
+ * number that will really go, not the number on screen.
2588
+ */
2589
+ clearable: number;
2484
2590
  /** Count of unexpected ones (500 / runtime exceptions) among them — the part the frontend highlights. */
2485
2591
  unexpected: number;
2486
2592
  /** The most frequent source · code (null when there are no errors). */
@@ -2502,6 +2608,18 @@ interface UsageErrorsPage {
2502
2608
  /** Filtered row count, so the caller knows when it has reached the end. */
2503
2609
  total: number;
2504
2610
  }
2611
+ /**
2612
+ * DELETE /api/projects/:projectId/usage/errors — empties the error table for the filter the
2613
+ * panel is showing (its date range and Agent), Project owner only.
2614
+ *
2615
+ * Scoped to the filter rather than the Project's whole history, so a clear takes exactly the
2616
+ * rows on screen. Errors with no Project attribution are never included, whoever asks: they
2617
+ * belong to no Project and are surfaced in every Project's admin view.
2618
+ */
2619
+ interface UsageErrorsClearResponse {
2620
+ /** How many rows were deleted, so the caller can say what went instead of guessing. */
2621
+ deleted: number;
2622
+ }
2505
2623
  interface UsageResponse {
2506
2624
  summary: {
2507
2625
  today: UsageBucket;
@@ -2863,4 +2981,4 @@ interface MachinesResponse {
2863
2981
  job: MachineInstallJob | null;
2864
2982
  }
2865
2983
 
2866
- export type { AdminPasswordResetRequest, AdminUserCreateRequest, AdminUserCreateResponse, AdminUsersResponse, AgentCompactionConfigDto, AgentConfigDto, AgentConfigResponse, AgentConfigUpdateRequest, AgentCreateRequest, AgentCreateResponse, AgentImportRequest, AgentImportResponse, AgentKernelUpdateResponse, AgentMemoryConfigDto, AgentModelConfigDto, AgentSchedulesConfigDto, AgentSkillsConfigDto, AgentSkillsResponse, AgentSummary, AgentTraceDateGroup, AgentTraceFileRef, AgentTraceSessionEntry, AgentTraceSessionFile, AgentTraceSessionGroup, AgentTracesResponse, AgentVaultConfigDto, AgentsResponse, ApprovalDecisionRequest, ApprovalMode, AuthLoginRequest, AuthResponse, BenchmarkCaseScore, BenchmarkCaseSummary, BenchmarkCasesResponse, BenchmarkEvaluation, BenchmarkRunScore, BenchmarkSummary, BenchmarksResponse, CaseMaterial, ChatDefaultsDto, CommandPolicyDto, CommandPolicyRuleDto, ContextToolShare, CredentialInfo, DefaultModelResponse, DefaultModelUpdateRequest, DesktopUpdateStatus, DesktopUpdateStatusResponse, DesktopUpdaterCommandMessage, DesktopUpdaterStatusMessage, DirEntryInfo, DirListResponse, DirectorySkillItem, DirectorySkillsResponse, DraftShortcut, EndpointModelListRequest, EndpointModelListResponse, ErrorBody, FeishuBindingInfo, FeishuBindingPutRequest, FeishuBindingResponse, FeishuTestRequest, FeishuTestResponse, FilesStatRequest, FilesStatResponse, GoalResponse, GoalServerEvent, GoalStateView, HistoryMessage, InstallResponse, MachineInfo, MachineInstallJob, MachinesResponse, McpServerTestResponse, MeResponse, MemberAddRequest, MemberAddResponse, MemberInfo, MembersResponse, MemoryFileInfo, MemoryFileResponse, MemoryFilesResponse, MemoryImportMode, MemoryImportRequest, MemoryImportResponse, MemoryOverviewResponse, MemoryScopeExport, MemoryScopeInfo, MemoryTransferFile, MessagesLiveTail, MessagesPageInfo, MessagesResponse, MessagingBindingInfo, MessagingBindingStateRequest, MessagingBindingsResponse, MessagingChannel, MessagingChannelState, MessagingDeliveryError, MessagingRuntimeState, MessagingRuntimeStatus, MessagingTestMessageResponse, ModelInfo, ModelOAuthCodeRequest, ModelOAuthCodeResponse, ModelOAuthErrorCode, ModelOAuthMode, ModelOAuthStartRequest, ModelOAuthStartResponse, ModelOAuthStatusResponse, ModelPricingDto, ModelProtocolDetectRequest, ModelProtocolDetectResponse, ModelProtocolProbeDto, ModelRefDto, ModelTestRequest, ModelTestResponse, ModelUpdateEntry, ModelVisionDetectRequest, ModelVisionDetectResponse, ModelVisionOutcome, ModelsResponse, ModelsUpdateRequest, PasswordChangeRequest, PendingFollowUpInfo, PendingSteeringInfo, PrefsResponse, ProjectCreateRequest, ProjectCreateResponse, ProjectRole, ProjectSummary, ProjectUpdateRequest, ProjectUpdateResponse, ProjectsResponse, ProtocolProbeOutcome, QQBindingInfo, QQBindingPutRequest, QQBindingResponse, QQScanPollResponse, QQScanStartResponse, QQTestRequest, QQTestResponse, RecalledMessageResponse, RequestSpan, RetryNowResponse, ScheduleItem, ScheduleServerEvent, ScheduleStatus, ScheduleUpsertRequest, SchedulesResponse, ServerEvent, ServerSettings, ServerSettingsResponse, ServerSettingsUpdateRequest, SessionCategory, SessionCategoryCounts, SessionContextParts, SessionContextResponse, SessionCreateRequest, SessionCreateResponse, SessionForkRequest, SessionForkResponse, SessionInfo, SessionPatchRequest, SessionProcessInfo, SessionProcessesResponse, SessionResponse, SessionSource, SessionStatus, SessionTracesResponse, SessionsResponse, SkillArchiveInstallRequest, SkillGroupItem, SkillInstallRequest, SkillLibraryResponse, SkillMetadataItem, SkillUpdateRef, SteerRequest, SubagentMessageResponse, SubagentRuntimeInfo, TaskCreateRequest, TaskCreateResponse, TaskInputPart, TelegramBindingInfo, TelegramBindingPutRequest, TelegramBindingResponse, TelegramTestRequest, TelegramTestResponse, ToolCallSpan, TraceAnalysisResponse, TraceEventsResponse, TraceFileInfo, TraceImportRequest, TraceImportResponse, TraceModelSegment, TraceOtherSpan, TracePosition, TraceTaskStats, TraceToolSpan, UiPrefs, UpdateCheckResponse, UpdateRunResponse, UploadLimits, UsageAgentSeries, UsageBucket, UsageEntitySeriesCounts, UsageErrorCount, UsageErrorItem, UsageErrorKind, UsageErrors, UsageErrorsPage, UsageGranularity, UsageGroupBy, UsageGroupRow, UsageModelSeries, UsageResponse, UsageSeriesPoint, UsageTrendPointInTrace, UserInfo, VaultEntryInfo, VaultEntryUpdate, VaultResponse, VaultUpdateRequest, VersionResponse, WorkspaceFileEntry, WorkspaceFilesResponse };
2984
+ export type { AdminPasswordResetRequest, AdminUserCreateRequest, AdminUserCreateResponse, AdminUsersResponse, AgentCompactionConfigDto, AgentConfigDto, AgentConfigResponse, AgentConfigUpdateRequest, AgentCreateRequest, AgentCreateResponse, AgentImportRequest, AgentImportResponse, AgentKernelUpdateResponse, AgentMemoryConfigDto, AgentModelConfigDto, AgentSchedulesConfigDto, AgentSkillsConfigDto, AgentSkillsResponse, AgentSummary, AgentTraceDateGroup, AgentTraceFileRef, AgentTraceSessionEntry, AgentTraceSessionFile, AgentTraceSessionGroup, AgentTracesResponse, AgentVaultConfigDto, AgentsResponse, ApprovalDecisionRequest, ApprovalMode, AuthLoginRequest, AuthResponse, BenchmarkCaseScore, BenchmarkCaseSummary, BenchmarkCasesResponse, BenchmarkEvaluation, BenchmarkRunScore, BenchmarkSummary, BenchmarksResponse, CaseMaterial, ChatDefaultsDto, CommandPolicyDto, CommandPolicyRuleDto, ContextToolShare, CredentialInfo, DefaultModelResponse, DefaultModelUpdateRequest, DesktopUpdateStatus, DesktopUpdateStatusResponse, DesktopUpdaterCommandMessage, DesktopUpdaterStatusMessage, DirEntryInfo, DirListResponse, DirectorySkillItem, DirectorySkillsResponse, DraftShortcut, EndpointModelListRequest, EndpointModelListResponse, ErrorBody, FeishuBindingInfo, FeishuBindingPutRequest, FeishuBindingResponse, FeishuTestRequest, FeishuTestResponse, FilesStatRequest, FilesStatResponse, GoalResponse, GoalServerEvent, GoalStateView, HistoryMessage, InstallResponse, MachineInfo, MachineInstallJob, MachinesResponse, McpServerTestResponse, MeResponse, MemberAddRequest, MemberAddResponse, MemberInfo, MembersResponse, MemoryFileInfo, MemoryFileResponse, MemoryFilesResponse, MemoryImportMode, MemoryImportRequest, MemoryImportResponse, MemoryOverviewResponse, MemoryScopeExport, MemoryScopeInfo, MemoryTransferFile, MessagesLiveTail, MessagesPageInfo, MessagesResponse, MessagingBindingCommon, MessagingBindingInfo, MessagingBindingStateRequest, MessagingBindingsResponse, MessagingChannel, MessagingChannelState, MessagingDeliveryError, MessagingDeliveryPatch, MessagingRuntimeState, MessagingRuntimeStatus, MessagingTestMessageResponse, ModelInfo, ModelOAuthCodeRequest, ModelOAuthCodeResponse, ModelOAuthErrorCode, ModelOAuthMode, ModelOAuthStartRequest, ModelOAuthStartResponse, ModelOAuthStatusResponse, ModelPricingDto, ModelProtocolDetectRequest, ModelProtocolDetectResponse, ModelProtocolProbeDto, ModelRefDto, ModelTestRequest, ModelTestResponse, ModelUpdateEntry, ModelVisionDetectRequest, ModelVisionDetectResponse, ModelVisionOutcome, ModelsResponse, ModelsUpdateRequest, PasswordChangeRequest, PendingFollowUpInfo, PendingSteeringInfo, PrefsResponse, ProjectCreateRequest, ProjectCreateResponse, ProjectRole, ProjectSummary, ProjectUpdateRequest, ProjectUpdateResponse, ProjectsResponse, ProtocolProbeOutcome, QQBindingInfo, QQBindingPutRequest, QQBindingResponse, QQScanPollResponse, QQScanStartResponse, QQTestRequest, QQTestResponse, RecalledMessageResponse, RequestSpan, RetryNowResponse, ScheduleItem, ScheduleServerEvent, ScheduleStatus, ScheduleUpsertRequest, SchedulesResponse, ServerEvent, ServerSettings, ServerSettingsResponse, ServerSettingsUpdateRequest, SessionCategory, SessionCategoryCounts, SessionContextParts, SessionContextResponse, SessionCreateRequest, SessionCreateResponse, SessionForkRequest, SessionForkResponse, SessionInfo, SessionPatchRequest, SessionProcessInfo, SessionProcessesResponse, SessionResponse, SessionSource, SessionStatus, SessionTracesResponse, SessionsResponse, SkillArchiveInstallRequest, SkillGroupItem, SkillInstallRequest, SkillLibraryResponse, SkillMetadataItem, SkillUpdateRef, SteerRequest, SubagentMessageResponse, SubagentRuntimeInfo, TaskCreateRequest, TaskCreateResponse, TaskInputPart, TelegramBindingInfo, TelegramBindingPutRequest, TelegramBindingResponse, TelegramTestRequest, TelegramTestResponse, ToolCallSpan, TraceAnalysisResponse, TraceEventsResponse, TraceFileInfo, TraceImportRequest, TraceImportResponse, TraceModelSegment, TraceOtherSpan, TracePosition, TraceTaskStats, TraceToolSpan, UiPrefs, UpdateCheckResponse, UpdateRunResponse, UploadLimits, UsageAgentSeries, UsageBucket, UsageEntitySeriesCounts, UsageErrorCount, UsageErrorItem, UsageErrorKind, UsageErrors, UsageErrorsClearResponse, UsageErrorsPage, UsageGranularity, UsageGroupBy, UsageGroupRow, UsageModelSeries, UsageModelTotal, UsageModelTotals, UsageResponse, UsageSeriesPoint, UsageTrendPointInTrace, UserInfo, VaultEntryInfo, VaultEntryUpdate, VaultResponse, VaultUpdateRequest, VersionResponse, WeChatBindingInfo, WeChatBindingPutRequest, WeChatBindingResponse, WeChatScanPollResponse, WeChatScanStartResponse, WeChatScanStatus, WeChatScanVerifyRequest, WeChatTestResponse, WorkspaceFileEntry, WorkspaceFilesResponse };
@@ -2,7 +2,7 @@ import {
2
2
  AuthSessionsRepo,
3
3
  UsersRepo,
4
4
  openExistingDatabase
5
- } from "./chunk-622KL7BQ.js";
5
+ } from "./chunk-WL5FUO3A.js";
6
6
 
7
7
  // src/auth-token.ts
8
8
  import path from "path";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  sessionTokenHash
3
- } from "./chunk-622KL7BQ.js";
3
+ } from "./chunk-WL5FUO3A.js";
4
4
 
5
5
  // src/auth/password.ts
6
6
  import { randomBytes, scrypt, timingSafeEqual } from "crypto";
@@ -371,4 +371,4 @@ export {
371
371
  toUserInfo,
372
372
  AuthService
373
373
  };
374
- //# sourceMappingURL=chunk-UGAFPYVX.js.map
374
+ //# sourceMappingURL=chunk-OTYHSDV7.js.map
@@ -123,13 +123,13 @@ CREATE TABLE IF NOT EXISTS goal_state ( -- goal-mode runtime state (GOAL.
123
123
  CREATE INDEX IF NOT EXISTS idx_goal_session ON goal_state(session_id);
124
124
  CREATE TABLE IF NOT EXISTS messaging_bindings ( -- Session \u2194 messaging-channel bot bindings (runtime/messaging/ holds the long connections)
125
125
  session_id TEXT NOT NULL, -- a Session keeps at most one saved config PER channel (composite PK below); at most ONE of them is enabled at a time (route-enforced, 409 another_channel_enabled), and enabling is what binds the account to this Session
126
- channel TEXT NOT NULL, -- messaging channel discriminator ('feishu' | 'telegram' | 'qq')
127
- account_id TEXT NOT NULL, -- channel-scoped bot/app identity (feishu: app_id; telegram: the bot token's numeric id; qq: the bot's App ID); NOT unique \u2014 any number of Sessions may keep the same account saved side by side, each with its own config and last-chat memory; only ENABLING is exclusive (route-enforced, 409 account_enabled_elsewhere: one account has one event stream, two live connections on it would race)
128
- config_json TEXT NOT NULL, -- channel-specific credentials/config JSON (feishu: appId/appSecret/baseDomain; telegram: botToken; qq: appId/appSecret); secrets plaintext at rest (same trade-off as the proxy address in server_settings), masked at every API surface; a cleared secret is stored as "" (the row and its account identity stay)
126
+ channel TEXT NOT NULL, -- messaging channel discriminator ('feishu' | 'telegram' | 'qq' | 'wechat')
127
+ account_id TEXT NOT NULL, -- channel-scoped bot/app identity (feishu: app_id; telegram: the bot token's numeric id; qq: the bot's App ID; wechat: the scanned bot id); NOT unique \u2014 any number of Sessions may keep the same account saved side by side, each with its own config and last-chat memory; only ENABLING is exclusive (route-enforced, 409 account_enabled_elsewhere: one account has one event stream, two live connections on it would race)
128
+ config_json TEXT NOT NULL, -- channel-specific credentials/config JSON (feishu: appId/appSecret/baseDomain; telegram: botToken; qq: appId/appSecret; wechat: botId/botToken/baseUrl/userId); secrets plaintext at rest (same trade-off as the proxy address in server_settings), masked at every API surface; a cleared secret is stored as "" (the row and its account identity stay)
129
129
  enabled INTEGER NOT NULL DEFAULT 0, -- INTENT state (the connection's runtime status stays in memory): new bindings start disabled \u2014 saving credentials never opens a connection, the explicit state toggle does
130
130
  line_per_message INTEGER NOT NULL DEFAULT 0, -- delivery preference: 1 = every non-blank line of a relayed assistant reply is sent as its own message (a reply written as spoken lines arrives as spoken lines), 0 = one message per reply. Channel-agnostic, so it lives here rather than in config_json, which each connector owns
131
131
  final_reply_only INTEGER NOT NULL DEFAULT 0, -- delivery preference: 1 = a run's intermediate assistant messages are held and only its LAST completed one is sent, at the run's end, 0 = every completed assistant message mirrors the moment it completes. Channel-agnostic like the column above and independent of it: with both set, that one final text is the one split per line
132
- render_markdown INTEGER NOT NULL DEFAULT 1, -- delivery preference: 1 = a relayed reply's Markdown is rendered in the channel's own markup (Telegram HTML, a Feishu card, QQ markdown), 0 = the reply's characters are sent as written. DEFAULT 1 because the model writes Markdown and showing it raw was the defect; the per-channel subsets and the plain-text fallback live in the connectors
132
+ render_markdown INTEGER NOT NULL DEFAULT 1, -- delivery preference: 1 = a relayed reply's Markdown is rendered in the channel's own markup (Telegram HTML, a Feishu card, QQ markdown, the subset WeChat itself reads), 0 = the reply's characters are sent as written. DEFAULT 1 because the model writes Markdown and showing it raw was the defect; the per-channel subsets and the plain-text fallback live in the connectors
133
133
  last_chat_id TEXT, -- most recent inbound chat (NULL until the bot is messaged once; replies and test messages target it); CONNECTOR-OPAQUE, not necessarily a bare chat id \u2014 telegram appends the forum topic (see telegram-connector.ts chatRefOf), so only that channel's connector may interpret it
134
134
  last_chat_is_direct INTEGER NOT NULL DEFAULT 1, -- 1 = direct chat (reply by chat id), 0 = group chat (prefer reply-to-message)
135
135
  last_inbound_message_id TEXT, -- channel id of the most recently PROCESSED inbound message (NULL until one arrives); the durable half of the bridge's redelivery guard, so a channel replaying an event this server already turned into a Task after a restart does not run it twice
@@ -357,4 +357,4 @@ export {
357
357
  sessionTokenHash,
358
358
  AuthSessionsRepo
359
359
  };
360
- //# sourceMappingURL=chunk-622KL7BQ.js.map
360
+ //# sourceMappingURL=chunk-WL5FUO3A.js.map