agentix-cli 0.15.0 → 0.17.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.
Files changed (148) hide show
  1. package/README.md +12 -6
  2. package/dist/agent-4AOMVXRJ.js +2 -0
  3. package/dist/board-dashboard-CXIVJKDP.js +7282 -0
  4. package/dist/board-dashboard-CXIVJKDP.js.map +1 -0
  5. package/dist/chunk-34NG7UDX.js +11 -0
  6. package/dist/chunk-34NG7UDX.js.map +1 -0
  7. package/dist/chunk-4HP6UC2X.js +13 -0
  8. package/dist/chunk-4HP6UC2X.js.map +1 -0
  9. package/dist/chunk-5F3J55LG.js +2 -0
  10. package/dist/chunk-5F3J55LG.js.map +1 -0
  11. package/dist/chunk-5WNGHDFC.js +164 -0
  12. package/dist/chunk-5WNGHDFC.js.map +1 -0
  13. package/dist/{chunk-7MBTMCKY.js → chunk-63UYRJIC.js} +2 -2
  14. package/dist/chunk-64P4QHSZ.js +2 -0
  15. package/dist/chunk-64P4QHSZ.js.map +1 -0
  16. package/dist/chunk-6PPIFDOQ.js +1742 -0
  17. package/dist/chunk-6PPIFDOQ.js.map +1 -0
  18. package/dist/chunk-ABCHHNG6.js +625 -0
  19. package/dist/chunk-ABCHHNG6.js.map +1 -0
  20. package/dist/chunk-AFRHKA7B.js +2 -0
  21. package/dist/chunk-AFRHKA7B.js.map +1 -0
  22. package/dist/chunk-BFZUG66X.js +45 -0
  23. package/dist/chunk-BFZUG66X.js.map +1 -0
  24. package/dist/chunk-CDNWCYF7.js +2 -0
  25. package/dist/chunk-CDNWCYF7.js.map +1 -0
  26. package/dist/{chunk-6MKAXEE5.js → chunk-EKTPCSN4.js} +2 -2
  27. package/dist/{chunk-6MKAXEE5.js.map → chunk-EKTPCSN4.js.map} +1 -1
  28. package/dist/chunk-EW27SCCC.js +2 -0
  29. package/dist/chunk-EW27SCCC.js.map +1 -0
  30. package/dist/chunk-HCFSHGK4.js +46 -0
  31. package/dist/chunk-HCFSHGK4.js.map +1 -0
  32. package/dist/chunk-HHJGKD5B.js +71 -0
  33. package/dist/chunk-HHJGKD5B.js.map +1 -0
  34. package/dist/chunk-LEIYC3BF.js +104 -0
  35. package/dist/chunk-LEIYC3BF.js.map +1 -0
  36. package/dist/{chunk-HLRVDCNH.js → chunk-MBEQASP3.js} +2 -2
  37. package/dist/chunk-N67JZAXU.js +2 -0
  38. package/dist/chunk-N67JZAXU.js.map +1 -0
  39. package/dist/{chunk-RGUYBH77.js → chunk-SRHLXZRF.js} +2 -2
  40. package/dist/chunk-TT2Q7YMZ.js +118 -0
  41. package/dist/chunk-TT2Q7YMZ.js.map +1 -0
  42. package/dist/{chunk-HI24KAEY.js → chunk-WJXU5F4Z.js} +3 -4
  43. package/dist/{chunk-HI24KAEY.js.map → chunk-WJXU5F4Z.js.map} +1 -1
  44. package/dist/chunk-WZEYII2S.js +6 -0
  45. package/dist/chunk-WZEYII2S.js.map +1 -0
  46. package/dist/chunk-XDJSYQST.js +6 -0
  47. package/dist/chunk-XDJSYQST.js.map +1 -0
  48. package/dist/chunk-YPOELN6Z.js +798 -0
  49. package/dist/chunk-YPOELN6Z.js.map +1 -0
  50. package/dist/{chunk-QZP65I2H.js → chunk-ZE2YFX4K.js} +2 -2
  51. package/dist/cli.js +1 -302
  52. package/dist/cli.js.map +1 -1
  53. package/dist/{compaction-JBZZ7I7Y.js → compaction-X46EQEPS.js} +6 -6
  54. package/dist/compaction-X46EQEPS.js.map +1 -0
  55. package/dist/{config-DCJYECL4.js → config-KCYHMKCT.js} +2 -2
  56. package/dist/config-mutator-QP4GLBOE.js +2 -0
  57. package/dist/context-planner-G2J7FNNU.js +17 -0
  58. package/dist/context-planner-G2J7FNNU.js.map +1 -0
  59. package/dist/editor-chat-R54E65W5.js +137 -0
  60. package/dist/editor-chat-R54E65W5.js.map +1 -0
  61. package/dist/heal-JDXUPRET.js +2 -0
  62. package/dist/inbox-L5ZZ623Y.js +315 -0
  63. package/dist/inbox-L5ZZ623Y.js.map +1 -0
  64. package/dist/index.d.ts +1996 -18
  65. package/dist/index.js +22 -49
  66. package/dist/index.js.map +1 -1
  67. package/dist/ingest-whatsapp-NS4OWDOU.js +6 -0
  68. package/dist/ingest-whatsapp-NS4OWDOU.js.map +1 -0
  69. package/dist/memory-extract-WH4FZ456.js +2 -0
  70. package/dist/processes-XF5CV4LK.js +256 -0
  71. package/dist/processes-XF5CV4LK.js.map +1 -0
  72. package/dist/program-FFWEILAS.js +2 -0
  73. package/dist/providers-AKQ4HTB4.js +2 -0
  74. package/dist/query-PKGYE5HR.js +2 -0
  75. package/dist/registry-IFMDJWQ7.js +2 -0
  76. package/dist/scheduler-63D5KFO7.js +2 -0
  77. package/dist/scheduler-63D5KFO7.js.map +1 -0
  78. package/dist/slack-PSQLVTGB.js +3 -0
  79. package/dist/slack-PSQLVTGB.js.map +1 -0
  80. package/dist/store-5MJOO74U.js +2 -0
  81. package/dist/store-5MJOO74U.js.map +1 -0
  82. package/dist/task-store-NM7VKNLE.js +2 -0
  83. package/dist/task-store-NM7VKNLE.js.map +1 -0
  84. package/dist/telegram-W4SLFMNY.js +3 -0
  85. package/dist/telegram-W4SLFMNY.js.map +1 -0
  86. package/dist/timers-PCB4ZIBD.js +2 -0
  87. package/dist/timers-PCB4ZIBD.js.map +1 -0
  88. package/dist/types-C7IFH42Z.js +2 -0
  89. package/dist/types-C7IFH42Z.js.map +1 -0
  90. package/dist/types-OIFU3KSQ.js +2 -0
  91. package/dist/types-OIFU3KSQ.js.map +1 -0
  92. package/dist/usage-dashboard-AXPXB2KP.js +803 -0
  93. package/dist/usage-dashboard-AXPXB2KP.js.map +1 -0
  94. package/dist/web/workflow-editor.global.js +1427 -0
  95. package/dist/web/workflow-editor.global.js.map +1 -0
  96. package/dist/whatsapp-DCHRBRVV.js +3 -0
  97. package/dist/whatsapp-DCHRBRVV.js.map +1 -0
  98. package/dist/wiki-UDPYFJBL.js +2 -0
  99. package/dist/wiki-UDPYFJBL.js.map +1 -0
  100. package/dist/workspace-setup-C4VB3QZV.js +2 -0
  101. package/dist/workspace-setup-C4VB3QZV.js.map +1 -0
  102. package/package.json +10 -3
  103. package/dist/agent-LRSSVFDR.js +0 -2
  104. package/dist/board-dashboard-DAPHL6W2.js +0 -4508
  105. package/dist/board-dashboard-DAPHL6W2.js.map +0 -1
  106. package/dist/chunk-3XULOWYB.js +0 -199
  107. package/dist/chunk-3XULOWYB.js.map +0 -1
  108. package/dist/chunk-6U2XOQHU.js +0 -7
  109. package/dist/chunk-6U2XOQHU.js.map +0 -1
  110. package/dist/chunk-AU37OREL.js +0 -108
  111. package/dist/chunk-AU37OREL.js.map +0 -1
  112. package/dist/chunk-B4Q4X7D5.js +0 -6
  113. package/dist/chunk-B4Q4X7D5.js.map +0 -1
  114. package/dist/chunk-CXY2RYNI.js +0 -11
  115. package/dist/chunk-CXY2RYNI.js.map +0 -1
  116. package/dist/chunk-IEVEMKAI.js +0 -168
  117. package/dist/chunk-IEVEMKAI.js.map +0 -1
  118. package/dist/chunk-KXZMYLHQ.js +0 -46
  119. package/dist/chunk-KXZMYLHQ.js.map +0 -1
  120. package/dist/chunk-M7HKBG3V.js +0 -2
  121. package/dist/chunk-M7HKBG3V.js.map +0 -1
  122. package/dist/chunk-RB7265LO.js +0 -12
  123. package/dist/chunk-RB7265LO.js.map +0 -1
  124. package/dist/chunk-WVNSWBCU.js +0 -138
  125. package/dist/chunk-WVNSWBCU.js.map +0 -1
  126. package/dist/compaction-JBZZ7I7Y.js.map +0 -1
  127. package/dist/config-mutator-6EJHSP3Y.js +0 -2
  128. package/dist/heal-WKY5I4KB.js +0 -2
  129. package/dist/memory-extract-AD7KMRPC.js +0 -2
  130. package/dist/providers-VUIWO3ZK.js +0 -2
  131. package/dist/registry-5ND7GLFJ.js +0 -2
  132. package/dist/scheduler-FCXOXCN5.js +0 -2
  133. package/dist/usage-dashboard-QX3CF6KK.js +0 -404
  134. package/dist/usage-dashboard-QX3CF6KK.js.map +0 -1
  135. package/dist/workspace-setup-ZWTT42XT.js +0 -2
  136. /package/dist/{agent-LRSSVFDR.js.map → agent-4AOMVXRJ.js.map} +0 -0
  137. /package/dist/{chunk-7MBTMCKY.js.map → chunk-63UYRJIC.js.map} +0 -0
  138. /package/dist/{chunk-HLRVDCNH.js.map → chunk-MBEQASP3.js.map} +0 -0
  139. /package/dist/{chunk-RGUYBH77.js.map → chunk-SRHLXZRF.js.map} +0 -0
  140. /package/dist/{chunk-QZP65I2H.js.map → chunk-ZE2YFX4K.js.map} +0 -0
  141. /package/dist/{config-DCJYECL4.js.map → config-KCYHMKCT.js.map} +0 -0
  142. /package/dist/{config-mutator-6EJHSP3Y.js.map → config-mutator-QP4GLBOE.js.map} +0 -0
  143. /package/dist/{heal-WKY5I4KB.js.map → heal-JDXUPRET.js.map} +0 -0
  144. /package/dist/{memory-extract-AD7KMRPC.js.map → memory-extract-WH4FZ456.js.map} +0 -0
  145. /package/dist/{providers-VUIWO3ZK.js.map → program-FFWEILAS.js.map} +0 -0
  146. /package/dist/{registry-5ND7GLFJ.js.map → providers-AKQ4HTB4.js.map} +0 -0
  147. /package/dist/{scheduler-FCXOXCN5.js.map → query-PKGYE5HR.js.map} +0 -0
  148. /package/dist/{workspace-setup-ZWTT42XT.js.map → registry-IFMDJWQ7.js.map} +0 -0
package/dist/index.d.ts CHANGED
@@ -977,6 +977,14 @@ declare class HookRegistry {
977
977
  * For blocking events, stops at the first hook that returns { blocked: true }.
978
978
  */
979
979
  execute(event: HookEvent, context: HookContext): Promise<HookResult>;
980
+ /**
981
+ * Register an in-process handler for an event. Used by modules that ship
982
+ * their hook behavior as plain JS functions rather than as command/script/
983
+ * prompt definitions loaded from disk (e.g. the workflow engine's
984
+ * subscribers to on:gitlab-issue / on:gitlab-pipeline / post:response).
985
+ * Priority follows the same rule as register(): lower runs first.
986
+ */
987
+ registerHandler(event: HookEvent, name: string, handler: HookHandler, priority?: number): void;
980
988
  /**
981
989
  * Check if any hooks are registered for an event.
982
990
  */
@@ -985,6 +993,9 @@ declare class HookRegistry {
985
993
  * Clear all hooks (useful for testing).
986
994
  */
987
995
  clear(): void;
996
+ /** Total number of registered hook entries across all events. Used by the
997
+ * daemon reload path to surface hook-count deltas in the reload summary. */
998
+ size(): number;
988
999
  /**
989
1000
  * Create a HookHandler from a HookDefinition.
990
1001
  */
@@ -1537,20 +1548,45 @@ declare const daemonConfigSchema: z.ZodObject<{
1537
1548
  accounts: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
1538
1549
  token: z.ZodString;
1539
1550
  agentBinding: z.ZodString;
1551
+ /** Per-account sender allowlist. When set, OVERRIDES the global
1552
+ * `policy.allowFrom`. Entries accept:
1553
+ * - numeric user id ("1816212449") — matches sender (from.id)
1554
+ * - numeric chat id ("-1003861455814") — matches chat (chat.id)
1555
+ * - "@username" — matches sender username
1556
+ * A message is dispatched iff at least one entry matches. */
1557
+ allowFrom: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
1558
+ /** When false, the daemon keeps the bot token registered so outbound
1559
+ * `send()` / ring notifications work, but does NOT long-poll for inbound
1560
+ * updates. Use this on nodes where the bound agent lives on a different
1561
+ * daemon — otherwise two daemons race on the same `getUpdates` cursor and
1562
+ * Telegram responds 409 Conflict on every poll.
1563
+ * Defaults to true (poll) so existing single-node setups are unchanged. */
1564
+ pollInbound: z.ZodDefault<z.ZodBoolean>;
1540
1565
  }, "strip", z.ZodTypeAny, {
1541
1566
  token: string;
1542
1567
  agentBinding: string;
1568
+ pollInbound: boolean;
1569
+ allowFrom?: string[] | undefined;
1543
1570
  }, {
1544
1571
  token: string;
1545
1572
  agentBinding: string;
1573
+ allowFrom?: string[] | undefined;
1574
+ pollInbound?: boolean | undefined;
1546
1575
  }>>>;
1547
1576
  policy: z.ZodDefault<z.ZodObject<{
1548
1577
  dm: z.ZodDefault<z.ZodEnum<["pair", "block"]>>;
1549
1578
  group: z.ZodDefault<z.ZodEnum<["mention-required", "all"]>>;
1579
+ /** Global sender allowlist applied to every account that doesn't set
1580
+ * its own `allowFrom`. When neither is configured, every incoming
1581
+ * message is dropped — closed by default. Same entry forms as the
1582
+ * per-account list (user id, chat id, @username). */
1583
+ allowFrom: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
1550
1584
  }, "strip", z.ZodTypeAny, {
1551
1585
  dm: "block" | "pair";
1552
1586
  group: "all" | "mention-required";
1587
+ allowFrom?: string[] | undefined;
1553
1588
  }, {
1589
+ allowFrom?: string[] | undefined;
1554
1590
  dm?: "block" | "pair" | undefined;
1555
1591
  group?: "all" | "mention-required" | undefined;
1556
1592
  }>>;
@@ -1559,18 +1595,24 @@ declare const daemonConfigSchema: z.ZodObject<{
1559
1595
  accounts: Record<string, {
1560
1596
  token: string;
1561
1597
  agentBinding: string;
1598
+ pollInbound: boolean;
1599
+ allowFrom?: string[] | undefined;
1562
1600
  }>;
1563
1601
  policy: {
1564
1602
  dm: "block" | "pair";
1565
1603
  group: "all" | "mention-required";
1604
+ allowFrom?: string[] | undefined;
1566
1605
  };
1567
1606
  }, {
1568
1607
  enabled?: boolean | undefined;
1569
1608
  accounts?: Record<string, {
1570
1609
  token: string;
1571
1610
  agentBinding: string;
1611
+ allowFrom?: string[] | undefined;
1612
+ pollInbound?: boolean | undefined;
1572
1613
  }> | undefined;
1573
1614
  policy?: {
1615
+ allowFrom?: string[] | undefined;
1574
1616
  dm?: "block" | "pair" | undefined;
1575
1617
  group?: "all" | "mention-required" | undefined;
1576
1618
  } | undefined;
@@ -1593,6 +1635,78 @@ declare const daemonConfigSchema: z.ZodObject<{
1593
1635
  group?: string | undefined;
1594
1636
  contact?: string | undefined;
1595
1637
  }>, "many">>;
1638
+ /** Data-source ingestion — turns WhatsApp from a messaging-only channel
1639
+ * into a source that seeds the wiki with contact/group metadata (and
1640
+ * optionally bounded message windows) per an explicit allowlist.
1641
+ * Default-deny: `enabled: false` and empty allowlists mean nothing is
1642
+ * ingested. See docs/reference/whatsapp-ingest.md for the full story. */
1643
+ ingest: z.ZodDefault<z.ZodObject<{
1644
+ enabled: z.ZodDefault<z.ZodBoolean>;
1645
+ /** `metadata-only` pulls contact/group info; `messages` additionally
1646
+ * pulls the last `messageCap` messages per allowlisted chat. */
1647
+ mode: z.ZodDefault<z.ZodEnum<["metadata-only", "messages"]>>;
1648
+ /** Phone numbers or JIDs. Substring match, same semantics as allowFrom. */
1649
+ allowContacts: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
1650
+ allowGroups: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
1651
+ denyContacts: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
1652
+ denyGroups: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
1653
+ /** Per-chat cap when mode = "messages". Keeps the raw-entry size bounded. */
1654
+ messageCap: z.ZodDefault<z.ZodNumber>;
1655
+ /** Max age (days) of messages to consider for the bounded window. */
1656
+ historyDays: z.ZodDefault<z.ZodNumber>;
1657
+ /** Skip re-writing a contact entry unless this many days have passed
1658
+ * OR the profile hash differs. Prevents churn on unchanged profiles. */
1659
+ contactRefreshDays: z.ZodDefault<z.ZodNumber>;
1660
+ /** Safety rails on live Baileys reads — personal-account accounts
1661
+ * can get throttled/banned under burst reads. */
1662
+ throttle: z.ZodDefault<z.ZodObject<{
1663
+ minMsBetweenCalls: z.ZodDefault<z.ZodNumber>;
1664
+ maxCallsPerMinute: z.ZodDefault<z.ZodNumber>;
1665
+ maxChatsPerSweep: z.ZodDefault<z.ZodNumber>;
1666
+ }, "strip", z.ZodTypeAny, {
1667
+ minMsBetweenCalls: number;
1668
+ maxCallsPerMinute: number;
1669
+ maxChatsPerSweep: number;
1670
+ }, {
1671
+ minMsBetweenCalls?: number | undefined;
1672
+ maxCallsPerMinute?: number | undefined;
1673
+ maxChatsPerSweep?: number | undefined;
1674
+ }>>;
1675
+ /** Purge absorbed raw entries older than this many days. `0` = never. */
1676
+ retentionDays: z.ZodDefault<z.ZodNumber>;
1677
+ }, "strip", z.ZodTypeAny, {
1678
+ enabled: boolean;
1679
+ mode: "messages" | "metadata-only";
1680
+ allowContacts: string[];
1681
+ allowGroups: string[];
1682
+ denyContacts: string[];
1683
+ denyGroups: string[];
1684
+ messageCap: number;
1685
+ historyDays: number;
1686
+ contactRefreshDays: number;
1687
+ throttle: {
1688
+ minMsBetweenCalls: number;
1689
+ maxCallsPerMinute: number;
1690
+ maxChatsPerSweep: number;
1691
+ };
1692
+ retentionDays: number;
1693
+ }, {
1694
+ enabled?: boolean | undefined;
1695
+ mode?: "messages" | "metadata-only" | undefined;
1696
+ allowContacts?: string[] | undefined;
1697
+ allowGroups?: string[] | undefined;
1698
+ denyContacts?: string[] | undefined;
1699
+ denyGroups?: string[] | undefined;
1700
+ messageCap?: number | undefined;
1701
+ historyDays?: number | undefined;
1702
+ contactRefreshDays?: number | undefined;
1703
+ throttle?: {
1704
+ minMsBetweenCalls?: number | undefined;
1705
+ maxCallsPerMinute?: number | undefined;
1706
+ maxChatsPerSweep?: number | undefined;
1707
+ } | undefined;
1708
+ retentionDays?: number | undefined;
1709
+ }>>;
1596
1710
  }, "strip", z.ZodTypeAny, {
1597
1711
  enabled: boolean;
1598
1712
  sessionDir: string;
@@ -1601,18 +1715,52 @@ declare const daemonConfigSchema: z.ZodObject<{
1601
1715
  group?: string | undefined;
1602
1716
  contact?: string | undefined;
1603
1717
  }[];
1604
- defaultAgent?: string | undefined;
1718
+ ingest: {
1719
+ enabled: boolean;
1720
+ mode: "messages" | "metadata-only";
1721
+ allowContacts: string[];
1722
+ allowGroups: string[];
1723
+ denyContacts: string[];
1724
+ denyGroups: string[];
1725
+ messageCap: number;
1726
+ historyDays: number;
1727
+ contactRefreshDays: number;
1728
+ throttle: {
1729
+ minMsBetweenCalls: number;
1730
+ maxCallsPerMinute: number;
1731
+ maxChatsPerSweep: number;
1732
+ };
1733
+ retentionDays: number;
1734
+ };
1605
1735
  allowFrom?: string[] | undefined;
1736
+ defaultAgent?: string | undefined;
1606
1737
  }, {
1607
1738
  enabled?: boolean | undefined;
1739
+ allowFrom?: string[] | undefined;
1608
1740
  sessionDir?: string | undefined;
1609
1741
  defaultAgent?: string | undefined;
1610
- allowFrom?: string[] | undefined;
1611
1742
  routes?: {
1612
1743
  agent: string;
1613
1744
  group?: string | undefined;
1614
1745
  contact?: string | undefined;
1615
1746
  }[] | undefined;
1747
+ ingest?: {
1748
+ enabled?: boolean | undefined;
1749
+ mode?: "messages" | "metadata-only" | undefined;
1750
+ allowContacts?: string[] | undefined;
1751
+ allowGroups?: string[] | undefined;
1752
+ denyContacts?: string[] | undefined;
1753
+ denyGroups?: string[] | undefined;
1754
+ messageCap?: number | undefined;
1755
+ historyDays?: number | undefined;
1756
+ contactRefreshDays?: number | undefined;
1757
+ throttle?: {
1758
+ minMsBetweenCalls?: number | undefined;
1759
+ maxCallsPerMinute?: number | undefined;
1760
+ maxChatsPerSweep?: number | undefined;
1761
+ } | undefined;
1762
+ retentionDays?: number | undefined;
1763
+ } | undefined;
1616
1764
  }>>;
1617
1765
  discord: z.ZodDefault<z.ZodObject<{
1618
1766
  enabled: z.ZodDefault<z.ZodBoolean>;
@@ -1719,6 +1867,247 @@ declare const daemonConfigSchema: z.ZodObject<{
1719
1867
  node?: string | undefined;
1720
1868
  }[] | undefined;
1721
1869
  }>>;
1870
+ github: z.ZodDefault<z.ZodObject<{
1871
+ enabled: z.ZodDefault<z.ZodBoolean>;
1872
+ /** GitHub PAT or env var (${GITHUB_TOKEN}) for posting comments back. */
1873
+ token: z.ZodOptional<z.ZodString>;
1874
+ /** Path to file containing the token (first line read at startup). */
1875
+ tokenFile: z.ZodOptional<z.ZodString>;
1876
+ /** GitHub App ID (legacy issuer). */
1877
+ appId: z.ZodOptional<z.ZodNumber>;
1878
+ /** GitHub App Client ID (preferred JWT issuer per GitHub's updated docs). */
1879
+ clientId: z.ZodOptional<z.ZodString>;
1880
+ /** Path to the GitHub App private key PEM file. */
1881
+ privateKeyFile: z.ZodOptional<z.ZodString>;
1882
+ /** Webhook secret for validating X-Hub-Signature-256. */
1883
+ webhookSecret: z.ZodOptional<z.ZodString>;
1884
+ /** Repo → agent routing. Use "owner/repo" or "*" for default. */
1885
+ routes: z.ZodDefault<z.ZodArray<z.ZodObject<{
1886
+ repo: z.ZodString;
1887
+ agent: z.ZodString;
1888
+ }, "strip", z.ZodTypeAny, {
1889
+ agent: string;
1890
+ repo: string;
1891
+ }, {
1892
+ agent: string;
1893
+ repo: string;
1894
+ }>, "many">>;
1895
+ /** Per-agent identity mappings (GitHub usernames, tokens, mesh nodes). */
1896
+ agentMappings: z.ZodDefault<z.ZodArray<z.ZodObject<{
1897
+ agentId: z.ZodString;
1898
+ githubUsernames: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
1899
+ token: z.ZodOptional<z.ZodString>;
1900
+ tokenFile: z.ZodOptional<z.ZodString>;
1901
+ node: z.ZodOptional<z.ZodString>;
1902
+ }, "strip", z.ZodTypeAny, {
1903
+ agentId: string;
1904
+ githubUsernames: string[];
1905
+ token?: string | undefined;
1906
+ node?: string | undefined;
1907
+ tokenFile?: string | undefined;
1908
+ }, {
1909
+ agentId: string;
1910
+ token?: string | undefined;
1911
+ node?: string | undefined;
1912
+ tokenFile?: string | undefined;
1913
+ githubUsernames?: string[] | undefined;
1914
+ }>, "many">>;
1915
+ }, "strip", z.ZodTypeAny, {
1916
+ enabled: boolean;
1917
+ routes: {
1918
+ agent: string;
1919
+ repo: string;
1920
+ }[];
1921
+ agentMappings: {
1922
+ agentId: string;
1923
+ githubUsernames: string[];
1924
+ token?: string | undefined;
1925
+ node?: string | undefined;
1926
+ tokenFile?: string | undefined;
1927
+ }[];
1928
+ token?: string | undefined;
1929
+ webhookSecret?: string | undefined;
1930
+ tokenFile?: string | undefined;
1931
+ appId?: number | undefined;
1932
+ clientId?: string | undefined;
1933
+ privateKeyFile?: string | undefined;
1934
+ }, {
1935
+ enabled?: boolean | undefined;
1936
+ token?: string | undefined;
1937
+ routes?: {
1938
+ agent: string;
1939
+ repo: string;
1940
+ }[] | undefined;
1941
+ webhookSecret?: string | undefined;
1942
+ agentMappings?: {
1943
+ agentId: string;
1944
+ token?: string | undefined;
1945
+ node?: string | undefined;
1946
+ tokenFile?: string | undefined;
1947
+ githubUsernames?: string[] | undefined;
1948
+ }[] | undefined;
1949
+ tokenFile?: string | undefined;
1950
+ appId?: number | undefined;
1951
+ clientId?: string | undefined;
1952
+ privateKeyFile?: string | undefined;
1953
+ }>>;
1954
+ webrtc: z.ZodDefault<z.ZodObject<{
1955
+ enabled: z.ZodDefault<z.ZodBoolean>;
1956
+ /** ICE STUN servers for NAT discovery. Default is Google's public STUN. */
1957
+ stunServers: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
1958
+ /** Optional TURN relays (required when both peers are behind symmetric NAT). */
1959
+ turnServers: z.ZodDefault<z.ZodArray<z.ZodObject<{
1960
+ urls: z.ZodString;
1961
+ username: z.ZodOptional<z.ZodString>;
1962
+ credential: z.ZodOptional<z.ZodString>;
1963
+ }, "strip", z.ZodTypeAny, {
1964
+ urls: string;
1965
+ username?: string | undefined;
1966
+ credential?: string | undefined;
1967
+ }, {
1968
+ urls: string;
1969
+ username?: string | undefined;
1970
+ credential?: string | undefined;
1971
+ }>, "many">>;
1972
+ /** Peer names permitted to *initiate* a call into this daemon. Empty = allow all mesh peers. */
1973
+ allowedCallers: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
1974
+ /** Where to send "someone is calling" notifications when an inbound ring
1975
+ * arrives. Each entry is delivered via the matching channel adapter —
1976
+ * same plumbing as any other outbound message. Empty disables notifications. */
1977
+ ringNotify: z.ZodDefault<z.ZodArray<z.ZodObject<{
1978
+ channel: z.ZodString;
1979
+ chatId: z.ZodString;
1980
+ accountId: z.ZodOptional<z.ZodString>;
1981
+ }, "strip", z.ZodTypeAny, {
1982
+ channel: string;
1983
+ chatId: string;
1984
+ accountId?: string | undefined;
1985
+ }, {
1986
+ channel: string;
1987
+ chatId: string;
1988
+ accountId?: string | undefined;
1989
+ }>, "many">>;
1990
+ /** Base URL used when building the tap-to-join link in ring notifications.
1991
+ * Defaults to `http://<node.bind>` — override when the daemon's public
1992
+ * hostname differs (e.g. HTTPS-terminated tunnel, Tailscale MagicDNS). */
1993
+ callUrlBase: z.ZodOptional<z.ZodString>;
1994
+ /** AI participant ("bot") joins calls when a browser opens with `?bot=<id>`.
1995
+ * v1 is transcribe-only — bot consumes remote audio, transcribes via
1996
+ * Whisper, posts chunks to the configured channel. No TTS-back. */
1997
+ bot: z.ZodDefault<z.ZodObject<{
1998
+ enabled: z.ZodDefault<z.ZodBoolean>;
1999
+ /** Default agent id used to attribute the transcript when the URL
2000
+ * doesn't override via `?bot=<other-agent>`. */
2001
+ defaultAgentId: z.ZodOptional<z.ZodString>;
2002
+ /** "auto" tries mlx-whisper, falls back to OpenAI; explicit forces. */
2003
+ whisperBackend: z.ZodDefault<z.ZodEnum<["auto", "mlx", "openai"]>>;
2004
+ whisperModel: z.ZodOptional<z.ZodString>;
2005
+ whisperLanguage: z.ZodDefault<z.ZodString>;
2006
+ /** Absolute path to mlx_whisper if not on the daemon's PATH (common
2007
+ * on macOS launchd, where ~/.pyenv/shims is missing from the env). */
2008
+ mlxBinary: z.ZodOptional<z.ZodString>;
2009
+ /** Where to send each transcribed chunk. Same shape as ringNotify[]. */
2010
+ transcriptChannel: z.ZodOptional<z.ZodObject<{
2011
+ channel: z.ZodString;
2012
+ chatId: z.ZodString;
2013
+ accountId: z.ZodOptional<z.ZodString>;
2014
+ }, "strip", z.ZodTypeAny, {
2015
+ channel: string;
2016
+ chatId: string;
2017
+ accountId?: string | undefined;
2018
+ }, {
2019
+ channel: string;
2020
+ chatId: string;
2021
+ accountId?: string | undefined;
2022
+ }>>;
2023
+ /** Hard cap so a forgotten bot doesn't run forever. */
2024
+ maxCallMinutes: z.ZodDefault<z.ZodNumber>;
2025
+ }, "strip", z.ZodTypeAny, {
2026
+ enabled: boolean;
2027
+ whisperBackend: "openai" | "auto" | "mlx";
2028
+ whisperLanguage: string;
2029
+ maxCallMinutes: number;
2030
+ defaultAgentId?: string | undefined;
2031
+ whisperModel?: string | undefined;
2032
+ mlxBinary?: string | undefined;
2033
+ transcriptChannel?: {
2034
+ channel: string;
2035
+ chatId: string;
2036
+ accountId?: string | undefined;
2037
+ } | undefined;
2038
+ }, {
2039
+ enabled?: boolean | undefined;
2040
+ defaultAgentId?: string | undefined;
2041
+ whisperBackend?: "openai" | "auto" | "mlx" | undefined;
2042
+ whisperModel?: string | undefined;
2043
+ whisperLanguage?: string | undefined;
2044
+ mlxBinary?: string | undefined;
2045
+ transcriptChannel?: {
2046
+ channel: string;
2047
+ chatId: string;
2048
+ accountId?: string | undefined;
2049
+ } | undefined;
2050
+ maxCallMinutes?: number | undefined;
2051
+ }>>;
2052
+ }, "strip", z.ZodTypeAny, {
2053
+ enabled: boolean;
2054
+ stunServers: string[];
2055
+ turnServers: {
2056
+ urls: string;
2057
+ username?: string | undefined;
2058
+ credential?: string | undefined;
2059
+ }[];
2060
+ allowedCallers: string[];
2061
+ ringNotify: {
2062
+ channel: string;
2063
+ chatId: string;
2064
+ accountId?: string | undefined;
2065
+ }[];
2066
+ bot: {
2067
+ enabled: boolean;
2068
+ whisperBackend: "openai" | "auto" | "mlx";
2069
+ whisperLanguage: string;
2070
+ maxCallMinutes: number;
2071
+ defaultAgentId?: string | undefined;
2072
+ whisperModel?: string | undefined;
2073
+ mlxBinary?: string | undefined;
2074
+ transcriptChannel?: {
2075
+ channel: string;
2076
+ chatId: string;
2077
+ accountId?: string | undefined;
2078
+ } | undefined;
2079
+ };
2080
+ callUrlBase?: string | undefined;
2081
+ }, {
2082
+ enabled?: boolean | undefined;
2083
+ stunServers?: string[] | undefined;
2084
+ turnServers?: {
2085
+ urls: string;
2086
+ username?: string | undefined;
2087
+ credential?: string | undefined;
2088
+ }[] | undefined;
2089
+ allowedCallers?: string[] | undefined;
2090
+ ringNotify?: {
2091
+ channel: string;
2092
+ chatId: string;
2093
+ accountId?: string | undefined;
2094
+ }[] | undefined;
2095
+ callUrlBase?: string | undefined;
2096
+ bot?: {
2097
+ enabled?: boolean | undefined;
2098
+ defaultAgentId?: string | undefined;
2099
+ whisperBackend?: "openai" | "auto" | "mlx" | undefined;
2100
+ whisperModel?: string | undefined;
2101
+ whisperLanguage?: string | undefined;
2102
+ mlxBinary?: string | undefined;
2103
+ transcriptChannel?: {
2104
+ channel: string;
2105
+ chatId: string;
2106
+ accountId?: string | undefined;
2107
+ } | undefined;
2108
+ maxCallMinutes?: number | undefined;
2109
+ } | undefined;
2110
+ }>>;
1722
2111
  }, "strip", z.ZodTypeAny, {
1723
2112
  gitlab: {
1724
2113
  enabled: boolean;
@@ -1743,10 +2132,13 @@ declare const daemonConfigSchema: z.ZodObject<{
1743
2132
  accounts: Record<string, {
1744
2133
  token: string;
1745
2134
  agentBinding: string;
2135
+ pollInbound: boolean;
2136
+ allowFrom?: string[] | undefined;
1746
2137
  }>;
1747
2138
  policy: {
1748
2139
  dm: "block" | "pair";
1749
2140
  group: "all" | "mention-required";
2141
+ allowFrom?: string[] | undefined;
1750
2142
  };
1751
2143
  };
1752
2144
  whatsapp: {
@@ -1757,8 +2149,25 @@ declare const daemonConfigSchema: z.ZodObject<{
1757
2149
  group?: string | undefined;
1758
2150
  contact?: string | undefined;
1759
2151
  }[];
1760
- defaultAgent?: string | undefined;
2152
+ ingest: {
2153
+ enabled: boolean;
2154
+ mode: "messages" | "metadata-only";
2155
+ allowContacts: string[];
2156
+ allowGroups: string[];
2157
+ denyContacts: string[];
2158
+ denyGroups: string[];
2159
+ messageCap: number;
2160
+ historyDays: number;
2161
+ contactRefreshDays: number;
2162
+ throttle: {
2163
+ minMsBetweenCalls: number;
2164
+ maxCallsPerMinute: number;
2165
+ maxChatsPerSweep: number;
2166
+ };
2167
+ retentionDays: number;
2168
+ };
1761
2169
  allowFrom?: string[] | undefined;
2170
+ defaultAgent?: string | undefined;
1762
2171
  };
1763
2172
  discord: {
1764
2173
  enabled: boolean;
@@ -1771,6 +2180,56 @@ declare const daemonConfigSchema: z.ZodObject<{
1771
2180
  botToken?: string | undefined;
1772
2181
  appToken?: string | undefined;
1773
2182
  };
2183
+ github: {
2184
+ enabled: boolean;
2185
+ routes: {
2186
+ agent: string;
2187
+ repo: string;
2188
+ }[];
2189
+ agentMappings: {
2190
+ agentId: string;
2191
+ githubUsernames: string[];
2192
+ token?: string | undefined;
2193
+ node?: string | undefined;
2194
+ tokenFile?: string | undefined;
2195
+ }[];
2196
+ token?: string | undefined;
2197
+ webhookSecret?: string | undefined;
2198
+ tokenFile?: string | undefined;
2199
+ appId?: number | undefined;
2200
+ clientId?: string | undefined;
2201
+ privateKeyFile?: string | undefined;
2202
+ };
2203
+ webrtc: {
2204
+ enabled: boolean;
2205
+ stunServers: string[];
2206
+ turnServers: {
2207
+ urls: string;
2208
+ username?: string | undefined;
2209
+ credential?: string | undefined;
2210
+ }[];
2211
+ allowedCallers: string[];
2212
+ ringNotify: {
2213
+ channel: string;
2214
+ chatId: string;
2215
+ accountId?: string | undefined;
2216
+ }[];
2217
+ bot: {
2218
+ enabled: boolean;
2219
+ whisperBackend: "openai" | "auto" | "mlx";
2220
+ whisperLanguage: string;
2221
+ maxCallMinutes: number;
2222
+ defaultAgentId?: string | undefined;
2223
+ whisperModel?: string | undefined;
2224
+ mlxBinary?: string | undefined;
2225
+ transcriptChannel?: {
2226
+ channel: string;
2227
+ chatId: string;
2228
+ accountId?: string | undefined;
2229
+ } | undefined;
2230
+ };
2231
+ callUrlBase?: string | undefined;
2232
+ };
1774
2233
  }, {
1775
2234
  gitlab?: {
1776
2235
  enabled?: boolean | undefined;
@@ -1795,22 +2254,42 @@ declare const daemonConfigSchema: z.ZodObject<{
1795
2254
  accounts?: Record<string, {
1796
2255
  token: string;
1797
2256
  agentBinding: string;
2257
+ allowFrom?: string[] | undefined;
2258
+ pollInbound?: boolean | undefined;
1798
2259
  }> | undefined;
1799
2260
  policy?: {
2261
+ allowFrom?: string[] | undefined;
1800
2262
  dm?: "block" | "pair" | undefined;
1801
2263
  group?: "all" | "mention-required" | undefined;
1802
2264
  } | undefined;
1803
2265
  } | undefined;
1804
2266
  whatsapp?: {
1805
2267
  enabled?: boolean | undefined;
2268
+ allowFrom?: string[] | undefined;
1806
2269
  sessionDir?: string | undefined;
1807
2270
  defaultAgent?: string | undefined;
1808
- allowFrom?: string[] | undefined;
1809
2271
  routes?: {
1810
2272
  agent: string;
1811
2273
  group?: string | undefined;
1812
2274
  contact?: string | undefined;
1813
2275
  }[] | undefined;
2276
+ ingest?: {
2277
+ enabled?: boolean | undefined;
2278
+ mode?: "messages" | "metadata-only" | undefined;
2279
+ allowContacts?: string[] | undefined;
2280
+ allowGroups?: string[] | undefined;
2281
+ denyContacts?: string[] | undefined;
2282
+ denyGroups?: string[] | undefined;
2283
+ messageCap?: number | undefined;
2284
+ historyDays?: number | undefined;
2285
+ contactRefreshDays?: number | undefined;
2286
+ throttle?: {
2287
+ minMsBetweenCalls?: number | undefined;
2288
+ maxCallsPerMinute?: number | undefined;
2289
+ maxChatsPerSweep?: number | undefined;
2290
+ } | undefined;
2291
+ retentionDays?: number | undefined;
2292
+ } | undefined;
1814
2293
  } | undefined;
1815
2294
  discord?: {
1816
2295
  enabled?: boolean | undefined;
@@ -1823,6 +2302,56 @@ declare const daemonConfigSchema: z.ZodObject<{
1823
2302
  botToken?: string | undefined;
1824
2303
  appToken?: string | undefined;
1825
2304
  } | undefined;
2305
+ github?: {
2306
+ enabled?: boolean | undefined;
2307
+ token?: string | undefined;
2308
+ routes?: {
2309
+ agent: string;
2310
+ repo: string;
2311
+ }[] | undefined;
2312
+ webhookSecret?: string | undefined;
2313
+ agentMappings?: {
2314
+ agentId: string;
2315
+ token?: string | undefined;
2316
+ node?: string | undefined;
2317
+ tokenFile?: string | undefined;
2318
+ githubUsernames?: string[] | undefined;
2319
+ }[] | undefined;
2320
+ tokenFile?: string | undefined;
2321
+ appId?: number | undefined;
2322
+ clientId?: string | undefined;
2323
+ privateKeyFile?: string | undefined;
2324
+ } | undefined;
2325
+ webrtc?: {
2326
+ enabled?: boolean | undefined;
2327
+ stunServers?: string[] | undefined;
2328
+ turnServers?: {
2329
+ urls: string;
2330
+ username?: string | undefined;
2331
+ credential?: string | undefined;
2332
+ }[] | undefined;
2333
+ allowedCallers?: string[] | undefined;
2334
+ ringNotify?: {
2335
+ channel: string;
2336
+ chatId: string;
2337
+ accountId?: string | undefined;
2338
+ }[] | undefined;
2339
+ callUrlBase?: string | undefined;
2340
+ bot?: {
2341
+ enabled?: boolean | undefined;
2342
+ defaultAgentId?: string | undefined;
2343
+ whisperBackend?: "openai" | "auto" | "mlx" | undefined;
2344
+ whisperModel?: string | undefined;
2345
+ whisperLanguage?: string | undefined;
2346
+ mlxBinary?: string | undefined;
2347
+ transcriptChannel?: {
2348
+ channel: string;
2349
+ chatId: string;
2350
+ accountId?: string | undefined;
2351
+ } | undefined;
2352
+ maxCallMinutes?: number | undefined;
2353
+ } | undefined;
2354
+ } | undefined;
1826
2355
  }>>;
1827
2356
  crons: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
1828
2357
  enabled: z.ZodDefault<z.ZodBoolean>;
@@ -2408,6 +2937,7 @@ declare const daemonConfigSchema: z.ZodObject<{
2408
2937
  url: z.ZodString;
2409
2938
  dashboardUrl: z.ZodOptional<z.ZodString>;
2410
2939
  token: z.ZodOptional<z.ZodString>;
2940
+ /** Phone numbers or JIDs. Substring match, same semantics as allowFrom. */
2411
2941
  dashboardToken: z.ZodOptional<z.ZodString>;
2412
2942
  }, "strip", z.ZodTypeAny, {
2413
2943
  name: string;
@@ -2422,7 +2952,8 @@ declare const daemonConfigSchema: z.ZodObject<{
2422
2952
  dashboardUrl?: string | undefined;
2423
2953
  dashboardToken?: string | undefined;
2424
2954
  }>, "many">>;
2425
- draftAgent: z.ZodOptional<z.ZodString>;
2955
+ draftAgent: z.ZodOptional<z.ZodString>; /** Safety rails on live Baileys reads — personal-account accounts
2956
+ * can get throttled/banned under burst reads. */
2426
2957
  }, "strip", z.ZodTypeAny, {
2427
2958
  enabled: boolean;
2428
2959
  port: number;
@@ -2452,6 +2983,89 @@ declare const daemonConfigSchema: z.ZodObject<{
2452
2983
  }[] | undefined;
2453
2984
  draftAgent?: string | undefined;
2454
2985
  }>>;
2986
+ graph: z.ZodDefault<z.ZodObject<{
2987
+ enabled: z.ZodDefault<z.ZodBoolean>;
2988
+ /** Where the schema/nodes/classifications live. Relative to cwd. */
2989
+ baseDir: z.ZodDefault<z.ZodString>;
2990
+ /** Which agent makes LLM classification proposals. Falls back to
2991
+ * `dashboard.draftAgent` at call sites if unset. */
2992
+ draftAgent: z.ZodOptional<z.ZodString>;
2993
+ /** Agent used by `agentx graph review` to triage pending classifications.
2994
+ * Should be an agent with the wiki skill so it can call `wiki query` for
2995
+ * context before deciding approve/reject. Falls back to draftAgent. */
2996
+ reviewAgent: z.ZodOptional<z.ZodString>;
2997
+ /** Structural auto-approval policy. Evaluated against each classification
2998
+ * independently of `autoApproveConfidence`; either triggers approval.
2999
+ * - "strict" : never approve via structure — every classification
3000
+ * waits for human review
3001
+ * - "extend-leaves" : auto-approve when the proposed path (a) reuses only
3002
+ * existing nodes, or (b) adds exactly one NEW node at
3003
+ * the deepest level. Structural changes (new mid-path
3004
+ * node, new root) still queue for review. Default.
3005
+ * - "any" : auto-approve every classification regardless of
3006
+ * structural change
3007
+ */
3008
+ autoApproveStructure: z.ZodDefault<z.ZodEnum<["strict", "extend-leaves", "any"]>>;
3009
+ /** Minimum classifier confidence (0..1) to auto-approve. OR'd with the
3010
+ * structural policy — either hitting the threshold lets the classification
3011
+ * bypass the pending queue. 1.0 (default) disables this knob, so approval
3012
+ * is driven by `autoApproveStructure` alone. Lower it to e.g. 0.7 if you
3013
+ * also want high-confidence structural changes auto-approved. */
3014
+ autoApproveConfidence: z.ZodDefault<z.ZodNumber>;
3015
+ /** Weights for the wiki hybrid retrieval score. Path-ancestry match vs
3016
+ * BM25 over article text. Sum need not be 1. */
3017
+ retrievalWeights: z.ZodDefault<z.ZodObject<{
3018
+ graph: z.ZodDefault<z.ZodNumber>;
3019
+ bm25: z.ZodDefault<z.ZodNumber>;
3020
+ }, "strip", z.ZodTypeAny, {
3021
+ graph: number;
3022
+ bm25: number;
3023
+ }, {
3024
+ graph?: number | undefined;
3025
+ bm25?: number | undefined;
3026
+ }>>;
3027
+ }, "strip", z.ZodTypeAny, {
3028
+ enabled: boolean;
3029
+ baseDir: string;
3030
+ autoApproveStructure: "any" | "strict" | "extend-leaves";
3031
+ autoApproveConfidence: number;
3032
+ retrievalWeights: {
3033
+ graph: number;
3034
+ bm25: number;
3035
+ };
3036
+ draftAgent?: string | undefined;
3037
+ reviewAgent?: string | undefined;
3038
+ }, {
3039
+ enabled?: boolean | undefined;
3040
+ draftAgent?: string | undefined;
3041
+ baseDir?: string | undefined;
3042
+ reviewAgent?: string | undefined;
3043
+ autoApproveStructure?: "any" | "strict" | "extend-leaves" | undefined;
3044
+ autoApproveConfidence?: number | undefined;
3045
+ retrievalWeights?: {
3046
+ graph?: number | undefined;
3047
+ bm25?: number | undefined;
3048
+ } | undefined;
3049
+ }>>;
3050
+ /** Workflow engine — declarative state machines that bind channel events
3051
+ * to agents. Off by default; existing installs see no change until
3052
+ * flipped. Definitions live under `dir` (default .agentx/workflows/). */
3053
+ workflows: z.ZodDefault<z.ZodObject<{
3054
+ enabled: z.ZodDefault<z.ZodBoolean>;
3055
+ dir: z.ZodDefault<z.ZodString>;
3056
+ /** Controls whether the dashboard exposes the visual editor. "readonly"
3057
+ * serves the list + run timelines but strips write controls from the
3058
+ * page. "disabled" hides the tab entirely. */
3059
+ editor: z.ZodDefault<z.ZodEnum<["disabled", "readonly", "edit"]>>;
3060
+ }, "strip", z.ZodTypeAny, {
3061
+ dir: string;
3062
+ enabled: boolean;
3063
+ editor: "disabled" | "readonly" | "edit";
3064
+ }, {
3065
+ dir?: string | undefined;
3066
+ enabled?: boolean | undefined;
3067
+ editor?: "disabled" | "readonly" | "edit" | undefined;
3068
+ }>>;
2455
3069
  /** Registered inbound webhooks — an inventory the dashboard manages. Each
2456
3070
  * entry binds an (agent, source) pair to an optional signing secret. The
2457
3071
  * actual inbound URL is always POST /webhook/<agentId>/<source>. */
@@ -2462,12 +3076,15 @@ declare const daemonConfigSchema: z.ZodObject<{
2462
3076
  secretEnv: z.ZodOptional<z.ZodString>;
2463
3077
  description: z.ZodOptional<z.ZodString>;
2464
3078
  enabled: z.ZodDefault<z.ZodBoolean>;
3079
+ /** Route to a mesh peer instead of local execution. */
3080
+ node: z.ZodOptional<z.ZodString>;
2465
3081
  }, "strip", z.ZodTypeAny, {
2466
3082
  enabled: boolean;
2467
3083
  id: string;
2468
3084
  source: "custom" | "gitlab" | "discord" | "slack" | "github" | "sentry" | "stripe";
2469
3085
  agentId: string;
2470
3086
  description?: string | undefined;
3087
+ node?: string | undefined;
2471
3088
  secretEnv?: string | undefined;
2472
3089
  }, {
2473
3090
  id: string;
@@ -2475,18 +3092,57 @@ declare const daemonConfigSchema: z.ZodObject<{
2475
3092
  agentId: string;
2476
3093
  enabled?: boolean | undefined;
2477
3094
  description?: string | undefined;
3095
+ node?: string | undefined;
2478
3096
  secretEnv?: string | undefined;
2479
3097
  }>, "many">>;
2480
- /** Session cache-reuse policy. `staleMinutes` controls how long a Claude
2481
- * session can idle before we rebuild the prompt from scratch (paying the
2482
- * full cache-create tax again). Longer is cheaper at Opus rates but means
2483
- * an agent may resume with mildly outdated context. */
3098
+ /** Session cache-reuse policy. Controls when we drop a Claude `--resume`
3099
+ * session and rebuild the prompt from scratch.
3100
+ * - `staleMinutes`: idle timeout before rotation. Longer is cheaper at
3101
+ * Opus rates but lets a session accumulate tool-result bloat across an
3102
+ * all-day chat.
3103
+ * - `maxTurnsPerSession`: hard cap on turns per Claude session. Prevents
3104
+ * unbounded `--resume` growth. When hit, next turn starts fresh with
3105
+ * the compacted summary + recent-messages context.
3106
+ * - `tierTwoThresholdTokens`: if the prior turn's (input + cacheRead +
3107
+ * cacheCreate) exceeded this, rotate proactively. Claude bills tier-2
3108
+ * at 1.5× above 200K total input, so rotating before that threshold
3109
+ * undercuts the multiplier. */
2484
3110
  session: z.ZodDefault<z.ZodObject<{
2485
3111
  staleMinutes: z.ZodDefault<z.ZodNumber>;
3112
+ maxTurnsPerSession: z.ZodDefault<z.ZodNumber>;
3113
+ tierTwoThresholdTokens: z.ZodDefault<z.ZodNumber>;
3114
+ /** Context assembly strategy:
3115
+ * - "layered" (default): the classic stacked layers — session history,
3116
+ * memory, cross-chat, wiki hint all appended every turn.
3117
+ * - "planner": a Haiku pre-call decides what to retrieve before the
3118
+ * main agent runs. Always-on core is kept (channel/scope/landscape/
3119
+ * identity/intent + last 3 turns verbatim); history blob, wiki hint,
3120
+ * and cross-chat are replaced by planner-selected bundles. Per-task
3121
+ * overrides via AgentTask.contextStrategy for A/B benchmarking. */
3122
+ contextStrategy: z.ZodDefault<z.ZodEnum<["layered", "planner"]>>;
3123
+ /** Soft dispatch budget for claude-code-tier agents (shared OAuth pools
3124
+ * one counter across the fleet). Warms at 80% of the cap; short-circuits
3125
+ * cold dispatches (no warm Claude session) when exceeded. Warm sessions
3126
+ * are always allowed through. Defaults sized for Max 5×:
3127
+ * - maxClaudeCodeDispatchesPerHour: 80 (headroom under typical hourly cap)
3128
+ * - maxClaudeCodeDispatchesPer5h: 180 (headroom under ~225/5h tier)
3129
+ * Raise for Max 20×, lower if your workload stays under the cap naturally. */
3130
+ maxClaudeCodeDispatchesPerHour: z.ZodDefault<z.ZodNumber>;
3131
+ maxClaudeCodeDispatchesPer5h: z.ZodDefault<z.ZodNumber>;
2486
3132
  }, "strip", z.ZodTypeAny, {
2487
3133
  staleMinutes: number;
3134
+ maxTurnsPerSession: number;
3135
+ tierTwoThresholdTokens: number;
3136
+ contextStrategy: "layered" | "planner";
3137
+ maxClaudeCodeDispatchesPerHour: number;
3138
+ maxClaudeCodeDispatchesPer5h: number;
2488
3139
  }, {
2489
3140
  staleMinutes?: number | undefined;
3141
+ maxTurnsPerSession?: number | undefined;
3142
+ tierTwoThresholdTokens?: number | undefined;
3143
+ contextStrategy?: "layered" | "planner" | undefined;
3144
+ maxClaudeCodeDispatchesPerHour?: number | undefined;
3145
+ maxClaudeCodeDispatchesPer5h?: number | undefined;
2490
3146
  }>>;
2491
3147
  }, "strip", z.ZodTypeAny, {
2492
3148
  agents: Record<string, {
@@ -2528,6 +3184,18 @@ declare const daemonConfigSchema: z.ZodObject<{
2528
3184
  bind: string;
2529
3185
  defaultAgent?: string | undefined;
2530
3186
  };
3187
+ graph: {
3188
+ enabled: boolean;
3189
+ baseDir: string;
3190
+ autoApproveStructure: "any" | "strict" | "extend-leaves";
3191
+ autoApproveConfidence: number;
3192
+ retrievalWeights: {
3193
+ graph: number;
3194
+ bm25: number;
3195
+ };
3196
+ draftAgent?: string | undefined;
3197
+ reviewAgent?: string | undefined;
3198
+ };
2531
3199
  providers: Record<string, {
2532
3200
  apiKey?: string | undefined;
2533
3201
  defaultModel?: string | undefined;
@@ -2557,10 +3225,13 @@ declare const daemonConfigSchema: z.ZodObject<{
2557
3225
  accounts: Record<string, {
2558
3226
  token: string;
2559
3227
  agentBinding: string;
3228
+ pollInbound: boolean;
3229
+ allowFrom?: string[] | undefined;
2560
3230
  }>;
2561
3231
  policy: {
2562
3232
  dm: "block" | "pair";
2563
3233
  group: "all" | "mention-required";
3234
+ allowFrom?: string[] | undefined;
2564
3235
  };
2565
3236
  };
2566
3237
  whatsapp: {
@@ -2571,8 +3242,25 @@ declare const daemonConfigSchema: z.ZodObject<{
2571
3242
  group?: string | undefined;
2572
3243
  contact?: string | undefined;
2573
3244
  }[];
2574
- defaultAgent?: string | undefined;
3245
+ ingest: {
3246
+ enabled: boolean;
3247
+ mode: "messages" | "metadata-only";
3248
+ allowContacts: string[];
3249
+ allowGroups: string[];
3250
+ denyContacts: string[];
3251
+ denyGroups: string[];
3252
+ messageCap: number;
3253
+ historyDays: number;
3254
+ contactRefreshDays: number;
3255
+ throttle: {
3256
+ minMsBetweenCalls: number;
3257
+ maxCallsPerMinute: number;
3258
+ maxChatsPerSweep: number;
3259
+ };
3260
+ retentionDays: number;
3261
+ };
2575
3262
  allowFrom?: string[] | undefined;
3263
+ defaultAgent?: string | undefined;
2576
3264
  };
2577
3265
  discord: {
2578
3266
  enabled: boolean;
@@ -2585,6 +3273,56 @@ declare const daemonConfigSchema: z.ZodObject<{
2585
3273
  botToken?: string | undefined;
2586
3274
  appToken?: string | undefined;
2587
3275
  };
3276
+ github: {
3277
+ enabled: boolean;
3278
+ routes: {
3279
+ agent: string;
3280
+ repo: string;
3281
+ }[];
3282
+ agentMappings: {
3283
+ agentId: string;
3284
+ githubUsernames: string[];
3285
+ token?: string | undefined;
3286
+ node?: string | undefined;
3287
+ tokenFile?: string | undefined;
3288
+ }[];
3289
+ token?: string | undefined;
3290
+ webhookSecret?: string | undefined;
3291
+ tokenFile?: string | undefined;
3292
+ appId?: number | undefined;
3293
+ clientId?: string | undefined;
3294
+ privateKeyFile?: string | undefined;
3295
+ };
3296
+ webrtc: {
3297
+ enabled: boolean;
3298
+ stunServers: string[];
3299
+ turnServers: {
3300
+ urls: string;
3301
+ username?: string | undefined;
3302
+ credential?: string | undefined;
3303
+ }[];
3304
+ allowedCallers: string[];
3305
+ ringNotify: {
3306
+ channel: string;
3307
+ chatId: string;
3308
+ accountId?: string | undefined;
3309
+ }[];
3310
+ bot: {
3311
+ enabled: boolean;
3312
+ whisperBackend: "openai" | "auto" | "mlx";
3313
+ whisperLanguage: string;
3314
+ maxCallMinutes: number;
3315
+ defaultAgentId?: string | undefined;
3316
+ whisperModel?: string | undefined;
3317
+ mlxBinary?: string | undefined;
3318
+ transcriptChannel?: {
3319
+ channel: string;
3320
+ chatId: string;
3321
+ accountId?: string | undefined;
3322
+ } | undefined;
3323
+ };
3324
+ callUrlBase?: string | undefined;
3325
+ };
2588
3326
  };
2589
3327
  crons: Record<string, {
2590
3328
  enabled: boolean;
@@ -2679,16 +3417,27 @@ declare const daemonConfigSchema: z.ZodObject<{
2679
3417
  token?: string | undefined;
2680
3418
  draftAgent?: string | undefined;
2681
3419
  };
3420
+ workflows: {
3421
+ dir: string;
3422
+ enabled: boolean;
3423
+ editor: "disabled" | "readonly" | "edit";
3424
+ };
2682
3425
  webhooks: {
2683
3426
  enabled: boolean;
2684
3427
  id: string;
2685
3428
  source: "custom" | "gitlab" | "discord" | "slack" | "github" | "sentry" | "stripe";
2686
3429
  agentId: string;
2687
3430
  description?: string | undefined;
3431
+ node?: string | undefined;
2688
3432
  secretEnv?: string | undefined;
2689
3433
  }[];
2690
3434
  session: {
2691
3435
  staleMinutes: number;
3436
+ maxTurnsPerSession: number;
3437
+ tierTwoThresholdTokens: number;
3438
+ contextStrategy: "layered" | "planner";
3439
+ maxClaudeCodeDispatchesPerHour: number;
3440
+ maxClaudeCodeDispatchesPer5h: number;
2692
3441
  };
2693
3442
  business?: {
2694
3443
  enabled: boolean;
@@ -2772,6 +3521,18 @@ declare const daemonConfigSchema: z.ZodObject<{
2772
3521
  interval?: number | undefined;
2773
3522
  } | undefined;
2774
3523
  } | undefined;
3524
+ graph?: {
3525
+ enabled?: boolean | undefined;
3526
+ draftAgent?: string | undefined;
3527
+ baseDir?: string | undefined;
3528
+ reviewAgent?: string | undefined;
3529
+ autoApproveStructure?: "any" | "strict" | "extend-leaves" | undefined;
3530
+ autoApproveConfidence?: number | undefined;
3531
+ retrievalWeights?: {
3532
+ graph?: number | undefined;
3533
+ bm25?: number | undefined;
3534
+ } | undefined;
3535
+ } | undefined;
2775
3536
  providers?: Record<string, {
2776
3537
  apiKey?: string | undefined;
2777
3538
  defaultModel?: string | undefined;
@@ -2801,22 +3562,42 @@ declare const daemonConfigSchema: z.ZodObject<{
2801
3562
  accounts?: Record<string, {
2802
3563
  token: string;
2803
3564
  agentBinding: string;
3565
+ allowFrom?: string[] | undefined;
3566
+ pollInbound?: boolean | undefined;
2804
3567
  }> | undefined;
2805
3568
  policy?: {
3569
+ allowFrom?: string[] | undefined;
2806
3570
  dm?: "block" | "pair" | undefined;
2807
3571
  group?: "all" | "mention-required" | undefined;
2808
3572
  } | undefined;
2809
3573
  } | undefined;
2810
3574
  whatsapp?: {
2811
3575
  enabled?: boolean | undefined;
3576
+ allowFrom?: string[] | undefined;
2812
3577
  sessionDir?: string | undefined;
2813
3578
  defaultAgent?: string | undefined;
2814
- allowFrom?: string[] | undefined;
2815
3579
  routes?: {
2816
3580
  agent: string;
2817
3581
  group?: string | undefined;
2818
3582
  contact?: string | undefined;
2819
3583
  }[] | undefined;
3584
+ ingest?: {
3585
+ enabled?: boolean | undefined;
3586
+ mode?: "messages" | "metadata-only" | undefined;
3587
+ allowContacts?: string[] | undefined;
3588
+ allowGroups?: string[] | undefined;
3589
+ denyContacts?: string[] | undefined;
3590
+ denyGroups?: string[] | undefined;
3591
+ messageCap?: number | undefined;
3592
+ historyDays?: number | undefined;
3593
+ contactRefreshDays?: number | undefined;
3594
+ throttle?: {
3595
+ minMsBetweenCalls?: number | undefined;
3596
+ maxCallsPerMinute?: number | undefined;
3597
+ maxChatsPerSweep?: number | undefined;
3598
+ } | undefined;
3599
+ retentionDays?: number | undefined;
3600
+ } | undefined;
2820
3601
  } | undefined;
2821
3602
  discord?: {
2822
3603
  enabled?: boolean | undefined;
@@ -2829,6 +3610,56 @@ declare const daemonConfigSchema: z.ZodObject<{
2829
3610
  botToken?: string | undefined;
2830
3611
  appToken?: string | undefined;
2831
3612
  } | undefined;
3613
+ github?: {
3614
+ enabled?: boolean | undefined;
3615
+ token?: string | undefined;
3616
+ routes?: {
3617
+ agent: string;
3618
+ repo: string;
3619
+ }[] | undefined;
3620
+ webhookSecret?: string | undefined;
3621
+ agentMappings?: {
3622
+ agentId: string;
3623
+ token?: string | undefined;
3624
+ node?: string | undefined;
3625
+ tokenFile?: string | undefined;
3626
+ githubUsernames?: string[] | undefined;
3627
+ }[] | undefined;
3628
+ tokenFile?: string | undefined;
3629
+ appId?: number | undefined;
3630
+ clientId?: string | undefined;
3631
+ privateKeyFile?: string | undefined;
3632
+ } | undefined;
3633
+ webrtc?: {
3634
+ enabled?: boolean | undefined;
3635
+ stunServers?: string[] | undefined;
3636
+ turnServers?: {
3637
+ urls: string;
3638
+ username?: string | undefined;
3639
+ credential?: string | undefined;
3640
+ }[] | undefined;
3641
+ allowedCallers?: string[] | undefined;
3642
+ ringNotify?: {
3643
+ channel: string;
3644
+ chatId: string;
3645
+ accountId?: string | undefined;
3646
+ }[] | undefined;
3647
+ callUrlBase?: string | undefined;
3648
+ bot?: {
3649
+ enabled?: boolean | undefined;
3650
+ defaultAgentId?: string | undefined;
3651
+ whisperBackend?: "openai" | "auto" | "mlx" | undefined;
3652
+ whisperModel?: string | undefined;
3653
+ whisperLanguage?: string | undefined;
3654
+ mlxBinary?: string | undefined;
3655
+ transcriptChannel?: {
3656
+ channel: string;
3657
+ chatId: string;
3658
+ accountId?: string | undefined;
3659
+ } | undefined;
3660
+ maxCallMinutes?: number | undefined;
3661
+ } | undefined;
3662
+ } | undefined;
2832
3663
  } | undefined;
2833
3664
  crons?: Record<string, {
2834
3665
  agent: string;
@@ -2965,16 +3796,27 @@ declare const daemonConfigSchema: z.ZodObject<{
2965
3796
  }[] | undefined;
2966
3797
  draftAgent?: string | undefined;
2967
3798
  } | undefined;
3799
+ workflows?: {
3800
+ dir?: string | undefined;
3801
+ enabled?: boolean | undefined;
3802
+ editor?: "disabled" | "readonly" | "edit" | undefined;
3803
+ } | undefined;
2968
3804
  webhooks?: {
2969
3805
  id: string;
2970
3806
  source: "custom" | "gitlab" | "discord" | "slack" | "github" | "sentry" | "stripe";
2971
3807
  agentId: string;
2972
3808
  enabled?: boolean | undefined;
2973
3809
  description?: string | undefined;
3810
+ node?: string | undefined;
2974
3811
  secretEnv?: string | undefined;
2975
3812
  }[] | undefined;
2976
3813
  session?: {
2977
3814
  staleMinutes?: number | undefined;
3815
+ maxTurnsPerSession?: number | undefined;
3816
+ tierTwoThresholdTokens?: number | undefined;
3817
+ contextStrategy?: "layered" | "planner" | undefined;
3818
+ maxClaudeCodeDispatchesPerHour?: number | undefined;
3819
+ maxClaudeCodeDispatchesPer5h?: number | undefined;
2978
3820
  } | undefined;
2979
3821
  }>;
2980
3822
  type DaemonConfig = z.infer<typeof daemonConfigSchema>;
@@ -2997,6 +3839,12 @@ interface PeerState {
2997
3839
  lastCheck?: Date;
2998
3840
  agentCard?: AgentCard;
2999
3841
  agents: AgentSkill[];
3842
+ /** Count of back-to-back failed health probes since the last successful one.
3843
+ * Used to suppress transient flaps: the `healthy` flag only flips to false
3844
+ * after N consecutive failures (see UNHEALTHY_AFTER). Reset to 0 on success. */
3845
+ consecutiveFailures: number;
3846
+ /** Last probe error message — surfaced for debugging via /mesh. */
3847
+ lastError?: string;
3000
3848
  }
3001
3849
  declare class A2AMesh {
3002
3850
  private peers;
@@ -3009,6 +3857,16 @@ declare class A2AMesh {
3009
3857
  */
3010
3858
  start(): Promise<void>;
3011
3859
  stop(): Promise<void>;
3860
+ /** Hot-reload the peer set from a fresh config. Adds new peers (kicks off
3861
+ * immediate discovery), removes vanished ones, and rebuilds the A2AClient
3862
+ * for peers whose url or token changed. Health-check interval is honored
3863
+ * on the next tick — we don't reset the timer just for peer-set edits.
3864
+ * Returns the diff for the caller to log. */
3865
+ reloadPeers(next: DaemonConfig): Promise<{
3866
+ added: string[];
3867
+ removed: string[];
3868
+ updated: string[];
3869
+ }>;
3012
3870
  /**
3013
3871
  * Discover agent cards from all peers.
3014
3872
  */
@@ -3019,19 +3877,44 @@ declare class A2AMesh {
3019
3877
  * Uses the peer's /task HTTP endpoint (agentx daemon API).
3020
3878
  * If no agent specified, uses the first available agent on the peer.
3021
3879
  */
3022
- sendTask(peerName: string, text: string, agentId?: string): Promise<string>;
3880
+ sendTask(peerName: string, text: string, agentId?: string, opts?: {
3881
+ timeoutMs?: number;
3882
+ }): Promise<string>;
3883
+ /**
3884
+ * Forward a WebRTC signaling message (SDP / ICE / hangup) to a remote peer's
3885
+ * /webrtc/signal endpoint. Not gated by peer health — a healthy control plane
3886
+ * is useful but a one-off probe miss should not drop a live call; the browser
3887
+ * layer will retry on timeout.
3888
+ *
3889
+ * Peer lookup is tolerant: the `to` field comes from whichever daemon
3890
+ * originated the signal, which often spells the same node differently
3891
+ * ("MacBook-Local" vs "macbook-local"). Match on a normalized name so config
3892
+ * drift across sides doesn't break the signaling path.
3893
+ */
3894
+ sendSignal(peerName: string, signal: unknown): Promise<boolean>;
3895
+ /** Look up a peer by a name that differs only in case / non-alphanumeric
3896
+ * characters (e.g. spaces, hyphens). Used by WebRTC signaling where the
3897
+ * `to` value is controlled by the opposite daemon and may not match the
3898
+ * exact spelling in this daemon's `mesh.peers[].name`. */
3899
+ private findPeerByNormalizedName;
3023
3900
  /**
3024
3901
  * Find a peer that has a specific skill.
3025
3902
  */
3026
3903
  findPeerWithSkill(skillId: string): PeerState | undefined;
3027
3904
  /**
3028
3905
  * Get the combined agent directory across all healthy peers.
3906
+ *
3907
+ * `channels` lists the channel adapter names each peer hosts (telegram,
3908
+ * whatsapp, gitlab, ...) — sourced from the peer's agent-card. Used by
3909
+ * workflow `action.send` to forward outbound messages back to the peer
3910
+ * that owns the channel when the workflow runs on a different node.
3029
3911
  */
3030
3912
  directory(): Array<{
3031
3913
  peer: string;
3032
3914
  peerUrl: string;
3033
3915
  healthy: boolean;
3034
3916
  skills: AgentSkill[];
3917
+ channels: string[];
3035
3918
  lastCheck?: Date;
3036
3919
  }>;
3037
3920
  }
@@ -3048,6 +3931,18 @@ declare class AgentXDaemon {
3048
3931
  private business?;
3049
3932
  private httpServer?;
3050
3933
  private webhooks;
3934
+ private github?;
3935
+ private webrtc?;
3936
+ private botManager?;
3937
+ private workflowDispatcher?;
3938
+ private workflowStore?;
3939
+ private workflowRuns?;
3940
+ /** Structured per-agent memory (Claude-Code-style user / feedback /
3941
+ * project / reference). Deliberately owned by the daemon (not the
3942
+ * registry) so HTTP callers — including an agent curling from its
3943
+ * own Claude Code session — can write without going through the
3944
+ * dispatcher. Inlined into agent system prompts happens elsewhere. */
3945
+ private readonly agentMemory;
3051
3946
  private log;
3052
3947
  private sseClients;
3053
3948
  private configPath?;
@@ -3077,6 +3972,11 @@ declare class AgentXDaemon {
3077
3972
  private scheduleMidnightHook;
3078
3973
  private runDailyCostReport;
3079
3974
  private startChannels;
3975
+ /** Wires the workflow dispatcher + hook subscribers against the running
3976
+ * daemon. No-op when `workflows.enabled` is false — the observability
3977
+ * page + editor still work (they read/write disk directly via the
3978
+ * board-dashboard routes), but no transitions fire. */
3979
+ private bootWorkflowEngine;
3080
3980
  private startHttpApi;
3081
3981
  /**
3082
3982
  * Broadcast an SSE event to all connected clients.
@@ -3093,6 +3993,17 @@ declare class AgentXDaemon {
3093
3993
  * then `chunk` events for every new delta, and `end` when the task finishes.
3094
3994
  */
3095
3995
  private handleTaskStream;
3996
+ /**
3997
+ * SSE stream for WebRTC signaling. Browser connects here identifying itself
3998
+ * with (callId, as). The broker fans out any signal addressed `to=<as>` on
3999
+ * this `callId` to the stream.
4000
+ */
4001
+ private handleWebRtcSSE;
4002
+ /**
4003
+ * Serve the minimal browser call page. Static HTML; no framework.
4004
+ * The page does getUserMedia, RTCPeerConnection, and POSTs/listens signals.
4005
+ */
4006
+ private serveCallPage;
3096
4007
  private handleHttp;
3097
4008
  /**
3098
4009
  * OpenAI-compatible chat completions endpoint.
@@ -3107,6 +4018,48 @@ declare class AgentXDaemon {
3107
4018
  */
3108
4019
  private handleOpenAICompat;
3109
4020
  private json;
4021
+ /** Boot-time: install the `remember` skill into every agent workspace
4022
+ * that doesn't already have it, and sync each agent's existing memory
4023
+ * into <workspace>/CLAUDE.md + .agentx-memory.md. Idempotent — safe to
4024
+ * run on every daemon start. Write-if-absent for the skill, sentinel-
4025
+ * replace for CLAUDE.md, so operator edits survive. */
4026
+ private installAgentMemorySurface;
4027
+ /** Look up an agent's workspace from the live registry. Returns null
4028
+ * for agents the daemon doesn't know about (e.g., a stale memory
4029
+ * entry for a since-removed agent). */
4030
+ private workspaceFor;
4031
+ /** Agent-memory HTTP surface. Agents call this from inside a Claude
4032
+ * Code session via `Bash` + `curl` — see the `remember` skill. The
4033
+ * heavy lifting is in `AgentRegistry.agentMemory` (AgentMemory); this
4034
+ * is a thin JSON wrapper that validates + delegates. */
4035
+ private handleMemoryApi;
4036
+ /** Mesh receiver for forwarded workflow events. Peers POST the same
4037
+ * payload shape the MeshForwarder sends. Enforces that the run is
4038
+ * actually home'd here before acting — protects against routing loops. */
4039
+ private handleTaskApi;
4040
+ private handleWorkflowEvent;
4041
+ /** Mesh-callable outbound send. A peer's workflow `action.send` invokes
4042
+ * this when the channel lives on this node (e.g. clawd-server hosts
4043
+ * whatsapp; macbook's workflow forwards here). Just unwraps to the local
4044
+ * router's outbound path so all the same per-account/per-bot resolution
4045
+ * applies. Authentication is currently the mesh token at the network
4046
+ * edge — the endpoint trusts callers that reach it. */
4047
+ private handleChannelSend;
4048
+ /** List chats the given channel adapter has observed. Merges local with
4049
+ * every healthy mesh peer that hosts the channel — a channel's "true"
4050
+ * chat list is spread across whichever nodes are actually paired
4051
+ * (e.g. macbook has the channel enabled but WhatsApp Baileys is unpaired,
4052
+ * while clawd-server is paired; macbook's list is empty but we still want
4053
+ * the author to pick from clawd-server's cache). Dedup by `id`; the first
4054
+ * source with a given id wins.
4055
+ *
4056
+ * `sources` in the response tells the editor where the entries came from
4057
+ * so it can show that metadata alongside. */
4058
+ private handleChannelChats;
4059
+ /** Manual run endpoint used by `agentx workflow run <id>`. Only workflows
4060
+ * whose trigger node is `trigger.manual` are runnable here — any other
4061
+ * source expects a live event and shouldn't race with a manual kick. */
4062
+ private handleWorkflowManualRun;
3110
4063
  }
3111
4064
 
3112
4065
  declare const CACHE_AWARE_PRICING: Record<string, {
@@ -3228,6 +4181,28 @@ declare class TokenTracker {
3228
4181
  * breakdown inline for at-a-glance spend attribution.
3229
4182
  */
3230
4183
  appendToTokenCosts(report: DailyReport, filePath?: string): void;
4184
+ /**
4185
+ * Return true if TOKEN_COSTS.md already has a row for `date`. Lets callers
4186
+ * skip duplicate appends after daemon restarts or timer-edge double fires.
4187
+ */
4188
+ hasTokenCostsEntry(date: string, filePath?: string): boolean;
4189
+ /**
4190
+ * Find the most recent date already logged to TOKEN_COSTS.md (YYYY-MM-DD).
4191
+ * Returns null when the file doesn't exist or has no rows.
4192
+ */
4193
+ lastTokenCostsDate(filePath?: string): string | null;
4194
+ /**
4195
+ * Append reports for any days between (lastLogged + 1) and `throughDate`
4196
+ * that have usage on disk but no row in TOKEN_COSTS.md. Idempotent —
4197
+ * safe to call on every daemon startup. Bounded to `maxDays` to avoid
4198
+ * a stalled daemon backfilling a year's worth on first start.
4199
+ *
4200
+ * Returns the list of dates actually appended, for logging.
4201
+ */
4202
+ catchUpTokenCosts(throughDate: string, opts?: {
4203
+ maxDays?: number;
4204
+ filePath?: string;
4205
+ }): string[];
3231
4206
  /**
3232
4207
  * Get summary across last N days.
3233
4208
  */
@@ -3273,6 +4248,10 @@ interface AgentPeer {
3273
4248
  interface AgentTask {
3274
4249
  message: string;
3275
4250
  agentId: string;
4251
+ /** Correlator threaded by the workflow engine. When the engine dispatches
4252
+ * an agent as part of a state transition, this carries the run id so
4253
+ * post:response can re-enter the engine with an agentResult condition. */
4254
+ workflowRunId?: string;
3276
4255
  /** Per-invocation model override (e.g. cron model). Falls back to agent.model. */
3277
4256
  model?: string;
3278
4257
  /** Cacheable text delivered to Claude via --append-system-prompt. Typically
@@ -3280,6 +4259,16 @@ interface AgentTask {
3280
4259
  * here (instead of in the user-message body) keeps it inside Claude's
3281
4260
  * cached system prompt across session resumes. */
3282
4261
  systemPromptAppend?: string;
4262
+ /** Per-invocation override for the context assembly strategy. When unset,
4263
+ * the registry falls back to config.session.contextStrategy. Set this
4264
+ * from benchmark harnesses that want to A/B the same request under
4265
+ * "layered" and "planner" without reloading daemon config. */
4266
+ contextStrategy?: "layered" | "planner";
4267
+ /** Upper bound on how long this task may run. Applied to mesh
4268
+ * forwarding (as fetch timeout) and local execution when the runtime
4269
+ * supports it. Workflow `agent` nodes pass this through from their
4270
+ * config.timeoutMinutes. */
4271
+ timeoutMinutes?: number;
3283
4272
  context?: {
3284
4273
  channel?: string;
3285
4274
  sender?: string;
@@ -3333,6 +4322,10 @@ interface AgentResponse {
3333
4322
  * trustworthy — a task that ran sonnet but was logged as opus would
3334
4323
  * overstate cost by ~5×. */
3335
4324
  billedModel?: string;
4325
+ /** Set when the task was forwarded to a mesh peer that hosted the
4326
+ * requested agent (the local registry didn't have it). Name of the
4327
+ * peer that actually ran it. */
4328
+ viaMesh?: string;
3336
4329
  }
3337
4330
  /** Callback for streaming text deltas */
3338
4331
  type StreamCallback = (delta: string, fullText: string) => void;
@@ -3368,10 +4361,23 @@ declare function executeTask(agent: AgentDef, task: AgentTask, providers: Record
3368
4361
 
3369
4362
  type WikiAccess = "private" | "shared" | "public";
3370
4363
  /**
3371
- * Article metadata. Intentionally minimal — tags do the heavy lifting.
4364
+ * Article type — the organizational spine. Matches Farzapedia's
4365
+ * `type: person | project | place | concept | event`, extended with
4366
+ * `decision` and `pattern` for workflow-heavy corpora.
4367
+ * Optional on legacy articles; Phase 2 migration backfills it.
4368
+ */
4369
+ type WikiArticleType = "person" | "project" | "place" | "concept" | "event" | "decision" | "pattern";
4370
+ /**
4371
+ * Article metadata. `type` + `related` are the primary retrieval surface;
4372
+ * tags remain as a secondary hint but are no longer central.
3372
4373
  */
3373
4374
  interface WikiArticleMeta {
3374
4375
  title: string;
4376
+ /** Organizational spine — Phase 2 migration backfills any missing values. */
4377
+ type?: WikiArticleType;
4378
+ /** Wikilink targets (by title) — persisted from `[[wikilinks]]` in body. */
4379
+ related?: string[];
4380
+ /** Secondary hint, no longer the retrieval spine. */
3375
4381
  tags: string[];
3376
4382
  owner: string;
3377
4383
  access: WikiAccess;
@@ -3379,6 +4385,10 @@ interface WikiArticleMeta {
3379
4385
  created: string;
3380
4386
  lastUpdated: string;
3381
4387
  sources: string[];
4388
+ /** Intent graph path (root → leaf node ids) when the article was authored
4389
+ * under an active intent graph. Empty for legacy articles — retrieval
4390
+ * falls back to pure BM25 for those. */
4391
+ graphPath?: string[];
3382
4392
  }
3383
4393
  interface WikiArticle {
3384
4394
  meta: WikiArticleMeta;
@@ -3398,6 +4408,8 @@ interface WikiIndex {
3398
4408
  articles: Array<{
3399
4409
  path: string;
3400
4410
  title: string;
4411
+ type?: WikiArticleType;
4412
+ related?: string[];
3401
4413
  tags: string[];
3402
4414
  owner: string;
3403
4415
  access: WikiAccess;
@@ -3411,7 +4423,7 @@ interface WikiIndex {
3411
4423
  }
3412
4424
 
3413
4425
  declare class WikiStore {
3414
- private baseDir;
4426
+ readonly baseDir: string;
3415
4427
  private rawDir;
3416
4428
  private log;
3417
4429
  constructor(baseDir?: string, log?: (...args: unknown[]) => void);
@@ -3475,9 +4487,19 @@ declare class WikiStore {
3475
4487
  search(query: string, agentId: string, maxResults?: number): WikiArticle[];
3476
4488
  /**
3477
4489
  * Find articles relevant to a message (for context injection).
3478
- * Extracts keywords from the message and searches the wiki.
4490
+ *
4491
+ * Scoring:
4492
+ * - BM25 over title+tags+content (always)
4493
+ * - + ancestry bonus if the caller classified the message through the
4494
+ * intent graph and articles carry `graphPath` from a previous classification
4495
+ *
4496
+ * `weights.graph` is scaled against `weights.bm25`. When the caller omits
4497
+ * `messagePath`, retrieval collapses to pure BM25 and matches legacy behavior.
3479
4498
  */
3480
- findRelevant(message: string, agentId?: string, maxArticles?: number): WikiArticle[];
4499
+ findRelevant(message: string, agentId?: string, maxArticles?: number, messagePath?: string[], weights?: {
4500
+ graph: number;
4501
+ bm25: number;
4502
+ }): WikiArticle[];
3481
4503
  /**
3482
4504
  * Build context string from relevant wiki articles for prompt injection.
3483
4505
  * Token-efficient: includes title + first ~500 chars of each article.
@@ -3487,6 +4509,15 @@ declare class WikiStore {
3487
4509
  * Rebuild the master index.
3488
4510
  */
3489
4511
  rebuildIndex(): WikiIndex;
4512
+ /**
4513
+ * Write `_index.md` — the Farzapedia-style content catalog grouped by
4514
+ * article `type`. This is the one file the agentic query (Phase 3) reads
4515
+ * to pick candidate articles before walking the wikilink subgraph.
4516
+ *
4517
+ * Articles without a `type` land in "Untyped" at the bottom; the Phase 2
4518
+ * migration backfills those.
4519
+ */
4520
+ private writeCatalog;
3490
4521
  /**
3491
4522
  * Get wiki stats.
3492
4523
  */
@@ -3608,6 +4639,606 @@ interface AgentWikiSummary {
3608
4639
  }>;
3609
4640
  }
3610
4641
 
4642
+ declare const axisTypeSchema: z.ZodEnum<["enum", "free", "ref"]>;
4643
+ type AxisType = z.infer<typeof axisTypeSchema>;
4644
+ declare const axisDefSchema: z.ZodObject<{
4645
+ name: z.ZodString;
4646
+ type: z.ZodEnum<["enum", "free", "ref"]>;
4647
+ /** For type="enum": closed set of allowed values. */
4648
+ values: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
4649
+ /** For type="ref": which level's nodes may be referenced. */
4650
+ refLevel: z.ZodOptional<z.ZodString>;
4651
+ /** If true, this axis may be absent from a node without failing validation.
4652
+ * Use for axes that apply conditionally (e.g. a department's "lead" ref
4653
+ * doesn't apply to a person-kind unit). Absent/false = required. */
4654
+ optional: z.ZodOptional<z.ZodBoolean>;
4655
+ /** Optional human-readable help, shown as tooltip in the admin UI. */
4656
+ description: z.ZodOptional<z.ZodString>;
4657
+ }, "strip", z.ZodTypeAny, {
4658
+ type: "enum" | "free" | "ref";
4659
+ name: string;
4660
+ values?: string[] | undefined;
4661
+ description?: string | undefined;
4662
+ refLevel?: string | undefined;
4663
+ optional?: boolean | undefined;
4664
+ }, {
4665
+ type: "enum" | "free" | "ref";
4666
+ name: string;
4667
+ values?: string[] | undefined;
4668
+ description?: string | undefined;
4669
+ refLevel?: string | undefined;
4670
+ optional?: boolean | undefined;
4671
+ }>;
4672
+ type AxisDef = z.infer<typeof axisDefSchema>;
4673
+ declare const levelDefSchema: z.ZodObject<{
4674
+ id: z.ZodString;
4675
+ /** Axes every node at this level MUST carry. Order matters for UI rendering. */
4676
+ axes: z.ZodArray<z.ZodObject<{
4677
+ name: z.ZodString;
4678
+ type: z.ZodEnum<["enum", "free", "ref"]>;
4679
+ /** For type="enum": closed set of allowed values. */
4680
+ values: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
4681
+ /** For type="ref": which level's nodes may be referenced. */
4682
+ refLevel: z.ZodOptional<z.ZodString>;
4683
+ /** If true, this axis may be absent from a node without failing validation.
4684
+ * Use for axes that apply conditionally (e.g. a department's "lead" ref
4685
+ * doesn't apply to a person-kind unit). Absent/false = required. */
4686
+ optional: z.ZodOptional<z.ZodBoolean>;
4687
+ /** Optional human-readable help, shown as tooltip in the admin UI. */
4688
+ description: z.ZodOptional<z.ZodString>;
4689
+ }, "strip", z.ZodTypeAny, {
4690
+ type: "enum" | "free" | "ref";
4691
+ name: string;
4692
+ values?: string[] | undefined;
4693
+ description?: string | undefined;
4694
+ refLevel?: string | undefined;
4695
+ optional?: boolean | undefined;
4696
+ }, {
4697
+ type: "enum" | "free" | "ref";
4698
+ name: string;
4699
+ values?: string[] | undefined;
4700
+ description?: string | undefined;
4701
+ refLevel?: string | undefined;
4702
+ optional?: boolean | undefined;
4703
+ }>, "many">;
4704
+ description: z.ZodOptional<z.ZodString>;
4705
+ }, "strip", z.ZodTypeAny, {
4706
+ id: string;
4707
+ axes: {
4708
+ type: "enum" | "free" | "ref";
4709
+ name: string;
4710
+ values?: string[] | undefined;
4711
+ description?: string | undefined;
4712
+ refLevel?: string | undefined;
4713
+ optional?: boolean | undefined;
4714
+ }[];
4715
+ description?: string | undefined;
4716
+ }, {
4717
+ id: string;
4718
+ axes: {
4719
+ type: "enum" | "free" | "ref";
4720
+ name: string;
4721
+ values?: string[] | undefined;
4722
+ description?: string | undefined;
4723
+ refLevel?: string | undefined;
4724
+ optional?: boolean | undefined;
4725
+ }[];
4726
+ description?: string | undefined;
4727
+ }>;
4728
+ type LevelDef = z.infer<typeof levelDefSchema>;
4729
+ declare const graphSchemaSchema: z.ZodObject<{
4730
+ version: z.ZodDefault<z.ZodNumber>;
4731
+ /** Ordered from root to leaf. Max 5 to keep the taxonomy scannable. */
4732
+ levels: z.ZodArray<z.ZodObject<{
4733
+ id: z.ZodString;
4734
+ /** Axes every node at this level MUST carry. Order matters for UI rendering. */
4735
+ axes: z.ZodArray<z.ZodObject<{
4736
+ name: z.ZodString;
4737
+ type: z.ZodEnum<["enum", "free", "ref"]>;
4738
+ /** For type="enum": closed set of allowed values. */
4739
+ values: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
4740
+ /** For type="ref": which level's nodes may be referenced. */
4741
+ refLevel: z.ZodOptional<z.ZodString>;
4742
+ /** If true, this axis may be absent from a node without failing validation.
4743
+ * Use for axes that apply conditionally (e.g. a department's "lead" ref
4744
+ * doesn't apply to a person-kind unit). Absent/false = required. */
4745
+ optional: z.ZodOptional<z.ZodBoolean>;
4746
+ /** Optional human-readable help, shown as tooltip in the admin UI. */
4747
+ description: z.ZodOptional<z.ZodString>;
4748
+ }, "strip", z.ZodTypeAny, {
4749
+ type: "enum" | "free" | "ref";
4750
+ name: string;
4751
+ values?: string[] | undefined;
4752
+ description?: string | undefined;
4753
+ refLevel?: string | undefined;
4754
+ optional?: boolean | undefined;
4755
+ }, {
4756
+ type: "enum" | "free" | "ref";
4757
+ name: string;
4758
+ values?: string[] | undefined;
4759
+ description?: string | undefined;
4760
+ refLevel?: string | undefined;
4761
+ optional?: boolean | undefined;
4762
+ }>, "many">;
4763
+ description: z.ZodOptional<z.ZodString>;
4764
+ }, "strip", z.ZodTypeAny, {
4765
+ id: string;
4766
+ axes: {
4767
+ type: "enum" | "free" | "ref";
4768
+ name: string;
4769
+ values?: string[] | undefined;
4770
+ description?: string | undefined;
4771
+ refLevel?: string | undefined;
4772
+ optional?: boolean | undefined;
4773
+ }[];
4774
+ description?: string | undefined;
4775
+ }, {
4776
+ id: string;
4777
+ axes: {
4778
+ type: "enum" | "free" | "ref";
4779
+ name: string;
4780
+ values?: string[] | undefined;
4781
+ description?: string | undefined;
4782
+ refLevel?: string | undefined;
4783
+ optional?: boolean | undefined;
4784
+ }[];
4785
+ description?: string | undefined;
4786
+ }>, "many">;
4787
+ /** Axis describing where a leaf classification came from (channel, cron, etc.). */
4788
+ leafInput: z.ZodOptional<z.ZodObject<{
4789
+ name: z.ZodString;
4790
+ type: z.ZodEnum<["enum", "free", "ref"]>;
4791
+ /** For type="enum": closed set of allowed values. */
4792
+ values: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
4793
+ /** For type="ref": which level's nodes may be referenced. */
4794
+ refLevel: z.ZodOptional<z.ZodString>;
4795
+ /** If true, this axis may be absent from a node without failing validation.
4796
+ * Use for axes that apply conditionally (e.g. a department's "lead" ref
4797
+ * doesn't apply to a person-kind unit). Absent/false = required. */
4798
+ optional: z.ZodOptional<z.ZodBoolean>;
4799
+ /** Optional human-readable help, shown as tooltip in the admin UI. */
4800
+ description: z.ZodOptional<z.ZodString>;
4801
+ }, "strip", z.ZodTypeAny, {
4802
+ type: "enum" | "free" | "ref";
4803
+ name: string;
4804
+ values?: string[] | undefined;
4805
+ description?: string | undefined;
4806
+ refLevel?: string | undefined;
4807
+ optional?: boolean | undefined;
4808
+ }, {
4809
+ type: "enum" | "free" | "ref";
4810
+ name: string;
4811
+ values?: string[] | undefined;
4812
+ description?: string | undefined;
4813
+ refLevel?: string | undefined;
4814
+ optional?: boolean | undefined;
4815
+ }>>;
4816
+ /** Free-form description of the expected result of a leaf classification. */
4817
+ leafOutput: z.ZodOptional<z.ZodObject<{
4818
+ name: z.ZodString;
4819
+ type: z.ZodEnum<["enum", "free", "ref"]>;
4820
+ /** For type="enum": closed set of allowed values. */
4821
+ values: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
4822
+ /** For type="ref": which level's nodes may be referenced. */
4823
+ refLevel: z.ZodOptional<z.ZodString>;
4824
+ /** If true, this axis may be absent from a node without failing validation.
4825
+ * Use for axes that apply conditionally (e.g. a department's "lead" ref
4826
+ * doesn't apply to a person-kind unit). Absent/false = required. */
4827
+ optional: z.ZodOptional<z.ZodBoolean>;
4828
+ /** Optional human-readable help, shown as tooltip in the admin UI. */
4829
+ description: z.ZodOptional<z.ZodString>;
4830
+ }, "strip", z.ZodTypeAny, {
4831
+ type: "enum" | "free" | "ref";
4832
+ name: string;
4833
+ values?: string[] | undefined;
4834
+ description?: string | undefined;
4835
+ refLevel?: string | undefined;
4836
+ optional?: boolean | undefined;
4837
+ }, {
4838
+ type: "enum" | "free" | "ref";
4839
+ name: string;
4840
+ values?: string[] | undefined;
4841
+ description?: string | undefined;
4842
+ refLevel?: string | undefined;
4843
+ optional?: boolean | undefined;
4844
+ }>>;
4845
+ }, "strip", z.ZodTypeAny, {
4846
+ version: number;
4847
+ levels: {
4848
+ id: string;
4849
+ axes: {
4850
+ type: "enum" | "free" | "ref";
4851
+ name: string;
4852
+ values?: string[] | undefined;
4853
+ description?: string | undefined;
4854
+ refLevel?: string | undefined;
4855
+ optional?: boolean | undefined;
4856
+ }[];
4857
+ description?: string | undefined;
4858
+ }[];
4859
+ leafInput?: {
4860
+ type: "enum" | "free" | "ref";
4861
+ name: string;
4862
+ values?: string[] | undefined;
4863
+ description?: string | undefined;
4864
+ refLevel?: string | undefined;
4865
+ optional?: boolean | undefined;
4866
+ } | undefined;
4867
+ leafOutput?: {
4868
+ type: "enum" | "free" | "ref";
4869
+ name: string;
4870
+ values?: string[] | undefined;
4871
+ description?: string | undefined;
4872
+ refLevel?: string | undefined;
4873
+ optional?: boolean | undefined;
4874
+ } | undefined;
4875
+ }, {
4876
+ levels: {
4877
+ id: string;
4878
+ axes: {
4879
+ type: "enum" | "free" | "ref";
4880
+ name: string;
4881
+ values?: string[] | undefined;
4882
+ description?: string | undefined;
4883
+ refLevel?: string | undefined;
4884
+ optional?: boolean | undefined;
4885
+ }[];
4886
+ description?: string | undefined;
4887
+ }[];
4888
+ version?: number | undefined;
4889
+ leafInput?: {
4890
+ type: "enum" | "free" | "ref";
4891
+ name: string;
4892
+ values?: string[] | undefined;
4893
+ description?: string | undefined;
4894
+ refLevel?: string | undefined;
4895
+ optional?: boolean | undefined;
4896
+ } | undefined;
4897
+ leafOutput?: {
4898
+ type: "enum" | "free" | "ref";
4899
+ name: string;
4900
+ values?: string[] | undefined;
4901
+ description?: string | undefined;
4902
+ refLevel?: string | undefined;
4903
+ optional?: boolean | undefined;
4904
+ } | undefined;
4905
+ }>;
4906
+ type GraphSchema = z.infer<typeof graphSchemaSchema>;
4907
+ declare const graphNodeSchema: z.ZodObject<{
4908
+ id: z.ZodString;
4909
+ /** Matches a `levels[*].id` from the schema. */
4910
+ level: z.ZodString;
4911
+ /** null for root-level nodes (level[0]); otherwise the id of a node at level-1. */
4912
+ parentId: z.ZodNullable<z.ZodString>;
4913
+ /** Values for every axis declared at this level's schema entry. `ref` axes
4914
+ * hold the id of another node; `enum` axes hold one of the declared values;
4915
+ * `free` axes hold any string. */
4916
+ axes: z.ZodRecord<z.ZodString, z.ZodString>;
4917
+ createdAt: z.ZodString;
4918
+ createdBy: z.ZodOptional<z.ZodString>;
4919
+ }, "strip", z.ZodTypeAny, {
4920
+ id: string;
4921
+ createdAt: string;
4922
+ axes: Record<string, string>;
4923
+ level: string;
4924
+ parentId: string | null;
4925
+ createdBy?: string | undefined;
4926
+ }, {
4927
+ id: string;
4928
+ createdAt: string;
4929
+ axes: Record<string, string>;
4930
+ level: string;
4931
+ parentId: string | null;
4932
+ createdBy?: string | undefined;
4933
+ }>;
4934
+ type GraphNode = z.infer<typeof graphNodeSchema>;
4935
+ declare const classificationSourceSchema: z.ZodEnum<["llm", "user", "cache"]>;
4936
+ type ClassificationSource = z.infer<typeof classificationSourceSchema>;
4937
+ declare const classificationStatusSchema: z.ZodEnum<["pending", "approved", "rejected"]>;
4938
+ type ClassificationStatus = z.infer<typeof classificationStatusSchema>;
4939
+ declare const classificationSchema: z.ZodObject<{
4940
+ ts: z.ZodString;
4941
+ msgHash: z.ZodString;
4942
+ agentId: z.ZodOptional<z.ZodString>;
4943
+ channel: z.ZodOptional<z.ZodString>;
4944
+ sender: z.ZodOptional<z.ZodString>;
4945
+ /** Node ids from root to leaf, one per level. Shorter-than-full paths are
4946
+ * allowed if the classifier was only confident down to a certain depth. */
4947
+ path: z.ZodArray<z.ZodString, "many">;
4948
+ /** Axis values asserted by the classifier for new nodes it proposed along
4949
+ * the path. Keyed by node id. */
4950
+ proposedAxes: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>>>;
4951
+ /** Leaf-level extras. */
4952
+ leaf: z.ZodDefault<z.ZodObject<{
4953
+ input: z.ZodOptional<z.ZodOptional<z.ZodString>>;
4954
+ output: z.ZodOptional<z.ZodOptional<z.ZodString>>;
4955
+ }, "strip", z.ZodTypeAny, {
4956
+ output?: string | undefined;
4957
+ input?: string | undefined;
4958
+ }, {
4959
+ output?: string | undefined;
4960
+ input?: string | undefined;
4961
+ }>>;
4962
+ source: z.ZodEnum<["llm", "user", "cache"]>;
4963
+ status: z.ZodEnum<["pending", "approved", "rejected"]>;
4964
+ confidence: z.ZodOptional<z.ZodNumber>;
4965
+ /** Redacted first ~200 chars of the message, for the approval UI. */
4966
+ preview: z.ZodOptional<z.ZodString>;
4967
+ }, "strip", z.ZodTypeAny, {
4968
+ path: string[];
4969
+ status: "pending" | "approved" | "rejected";
4970
+ source: "user" | "llm" | "cache";
4971
+ ts: string;
4972
+ msgHash: string;
4973
+ proposedAxes: Record<string, Record<string, string>>;
4974
+ leaf: {
4975
+ output?: string | undefined;
4976
+ input?: string | undefined;
4977
+ };
4978
+ channel?: string | undefined;
4979
+ agentId?: string | undefined;
4980
+ sender?: string | undefined;
4981
+ confidence?: number | undefined;
4982
+ preview?: string | undefined;
4983
+ }, {
4984
+ path: string[];
4985
+ status: "pending" | "approved" | "rejected";
4986
+ source: "user" | "llm" | "cache";
4987
+ ts: string;
4988
+ msgHash: string;
4989
+ channel?: string | undefined;
4990
+ agentId?: string | undefined;
4991
+ sender?: string | undefined;
4992
+ proposedAxes?: Record<string, Record<string, string>> | undefined;
4993
+ leaf?: {
4994
+ output?: string | undefined;
4995
+ input?: string | undefined;
4996
+ } | undefined;
4997
+ confidence?: number | undefined;
4998
+ preview?: string | undefined;
4999
+ }>;
5000
+ type Classification = z.infer<typeof classificationSchema>;
5001
+ /** Row in the fingerprint → path snap cache. */
5002
+ declare const fingerprintEntrySchema: z.ZodObject<{
5003
+ fingerprint: z.ZodString;
5004
+ path: z.ZodArray<z.ZodString, "many">;
5005
+ leaf: z.ZodDefault<z.ZodObject<{
5006
+ input: z.ZodOptional<z.ZodOptional<z.ZodString>>;
5007
+ output: z.ZodOptional<z.ZodOptional<z.ZodString>>;
5008
+ }, "strip", z.ZodTypeAny, {
5009
+ output?: string | undefined;
5010
+ input?: string | undefined;
5011
+ }, {
5012
+ output?: string | undefined;
5013
+ input?: string | undefined;
5014
+ }>>;
5015
+ updatedAt: z.ZodString;
5016
+ }, "strip", z.ZodTypeAny, {
5017
+ path: string[];
5018
+ leaf: {
5019
+ output?: string | undefined;
5020
+ input?: string | undefined;
5021
+ };
5022
+ fingerprint: string;
5023
+ updatedAt: string;
5024
+ }, {
5025
+ path: string[];
5026
+ fingerprint: string;
5027
+ updatedAt: string;
5028
+ leaf?: {
5029
+ output?: string | undefined;
5030
+ input?: string | undefined;
5031
+ } | undefined;
5032
+ }>;
5033
+ type FingerprintEntry = z.infer<typeof fingerprintEntrySchema>;
5034
+ /** Nodes file is a plain list, not a nested tree — lookups are by id. */
5035
+ declare const nodesFileSchema: z.ZodObject<{
5036
+ version: z.ZodDefault<z.ZodNumber>;
5037
+ nodes: z.ZodDefault<z.ZodArray<z.ZodObject<{
5038
+ id: z.ZodString;
5039
+ /** Matches a `levels[*].id` from the schema. */
5040
+ level: z.ZodString;
5041
+ /** null for root-level nodes (level[0]); otherwise the id of a node at level-1. */
5042
+ parentId: z.ZodNullable<z.ZodString>;
5043
+ /** Values for every axis declared at this level's schema entry. `ref` axes
5044
+ * hold the id of another node; `enum` axes hold one of the declared values;
5045
+ * `free` axes hold any string. */
5046
+ axes: z.ZodRecord<z.ZodString, z.ZodString>;
5047
+ createdAt: z.ZodString;
5048
+ createdBy: z.ZodOptional<z.ZodString>;
5049
+ }, "strip", z.ZodTypeAny, {
5050
+ id: string;
5051
+ createdAt: string;
5052
+ axes: Record<string, string>;
5053
+ level: string;
5054
+ parentId: string | null;
5055
+ createdBy?: string | undefined;
5056
+ }, {
5057
+ id: string;
5058
+ createdAt: string;
5059
+ axes: Record<string, string>;
5060
+ level: string;
5061
+ parentId: string | null;
5062
+ createdBy?: string | undefined;
5063
+ }>, "many">>;
5064
+ }, "strip", z.ZodTypeAny, {
5065
+ version: number;
5066
+ nodes: {
5067
+ id: string;
5068
+ createdAt: string;
5069
+ axes: Record<string, string>;
5070
+ level: string;
5071
+ parentId: string | null;
5072
+ createdBy?: string | undefined;
5073
+ }[];
5074
+ }, {
5075
+ version?: number | undefined;
5076
+ nodes?: {
5077
+ id: string;
5078
+ createdAt: string;
5079
+ axes: Record<string, string>;
5080
+ level: string;
5081
+ parentId: string | null;
5082
+ createdBy?: string | undefined;
5083
+ }[] | undefined;
5084
+ }>;
5085
+ type NodesFile = z.infer<typeof nodesFileSchema>;
5086
+ /** Index file — record of fingerprints. Hot path; kept as a map on disk. */
5087
+ declare const indexFileSchema: z.ZodObject<{
5088
+ version: z.ZodDefault<z.ZodNumber>;
5089
+ entries: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
5090
+ fingerprint: z.ZodString;
5091
+ path: z.ZodArray<z.ZodString, "many">;
5092
+ leaf: z.ZodDefault<z.ZodObject<{
5093
+ input: z.ZodOptional<z.ZodOptional<z.ZodString>>;
5094
+ output: z.ZodOptional<z.ZodOptional<z.ZodString>>;
5095
+ }, "strip", z.ZodTypeAny, {
5096
+ output?: string | undefined;
5097
+ input?: string | undefined;
5098
+ }, {
5099
+ output?: string | undefined;
5100
+ input?: string | undefined;
5101
+ }>>;
5102
+ updatedAt: z.ZodString;
5103
+ }, "strip", z.ZodTypeAny, {
5104
+ path: string[];
5105
+ leaf: {
5106
+ output?: string | undefined;
5107
+ input?: string | undefined;
5108
+ };
5109
+ fingerprint: string;
5110
+ updatedAt: string;
5111
+ }, {
5112
+ path: string[];
5113
+ fingerprint: string;
5114
+ updatedAt: string;
5115
+ leaf?: {
5116
+ output?: string | undefined;
5117
+ input?: string | undefined;
5118
+ } | undefined;
5119
+ }>>>;
5120
+ }, "strip", z.ZodTypeAny, {
5121
+ entries: Record<string, {
5122
+ path: string[];
5123
+ leaf: {
5124
+ output?: string | undefined;
5125
+ input?: string | undefined;
5126
+ };
5127
+ fingerprint: string;
5128
+ updatedAt: string;
5129
+ }>;
5130
+ version: number;
5131
+ }, {
5132
+ entries?: Record<string, {
5133
+ path: string[];
5134
+ fingerprint: string;
5135
+ updatedAt: string;
5136
+ leaf?: {
5137
+ output?: string | undefined;
5138
+ input?: string | undefined;
5139
+ } | undefined;
5140
+ }> | undefined;
5141
+ version?: number | undefined;
5142
+ }>;
5143
+ type IndexFile = z.infer<typeof indexFileSchema>;
5144
+
5145
+ /**
5146
+ * Ships with the install. When .agentx/graph/schema.json is missing, the
5147
+ * store seeds this so a fresh daemon has something to classify against.
5148
+ *
5149
+ * Five levels, three primaries at the leaf (who / what / where) — matches
5150
+ * the "primary colors per level" design from the plan.
5151
+ */
5152
+ declare const STARTER_SCHEMA: GraphSchema;
5153
+
5154
+ interface GraphStoreOptions {
5155
+ baseDir?: string;
5156
+ log?: (...args: unknown[]) => void;
5157
+ }
5158
+ declare class GraphStore {
5159
+ readonly baseDir: string;
5160
+ private log;
5161
+ constructor(opts?: GraphStoreOptions);
5162
+ schemaPath(): string;
5163
+ nodesPath(): string;
5164
+ indexPath(): string;
5165
+ classificationsPath(): string;
5166
+ /** Load the graph schema, seeding STARTER_SCHEMA if the file is missing. */
5167
+ loadSchema(): GraphSchema;
5168
+ saveSchema(schema: GraphSchema): void;
5169
+ loadNodes(): NodesFile;
5170
+ saveNodes(file: NodesFile): void;
5171
+ /** Validate a node against the schema AND uniqueness within the nodes file. */
5172
+ validateNode(node: GraphNode, schema: GraphSchema, all: GraphNode[]): void;
5173
+ /**
5174
+ * Commit a full classification path: for each path element that doesn't
5175
+ * yet exist, infer its schema level by matching the proposed axes against
5176
+ * the level's required axes (first match ≥ cursor wins — this handles
5177
+ * paths that skip levels, like business → noqta where location is
5178
+ * absent). Refreshes the node list between adds so callers in a loop
5179
+ * don't hit "Node id already exists" on the second iteration.
5180
+ */
5181
+ commitNodesAlongPath(path: string[], proposedAxes: Record<string, Record<string, string>>, schema: GraphSchema, createdBy?: string): {
5182
+ added: GraphNode[];
5183
+ skipped: string[];
5184
+ };
5185
+ /** Append a node. Caller is responsible for id uniqueness; we check and throw. */
5186
+ addNode(node: GraphNode): GraphNode;
5187
+ appendClassification(c: Classification): boolean;
5188
+ /**
5189
+ * All pending classifications, newest first. Collapses the append-only
5190
+ * ledger by msgHash — the most recent entry per message wins (so if an
5191
+ * entry was updated via updateClassificationStatus, we see the current
5192
+ * state, not the original).
5193
+ */
5194
+ listPendingClassifications(limit?: number): Classification[];
5195
+ /** Same contract as listPendingClassifications but filtered to any status. */
5196
+ listByStatus(status: Classification["status"], limit?: number): Classification[];
5197
+ /**
5198
+ * Read every classification, collapse by msgHash keeping the newest entry
5199
+ * per message. Used by listByStatus + the review loop to see current state.
5200
+ * Returns newest-first order.
5201
+ */
5202
+ private readAllCollapsed;
5203
+ /**
5204
+ * Append a status-update entry for an existing classification. The ledger
5205
+ * stays append-only; readers collapse by msgHash. Returns the updated
5206
+ * classification, or null if no prior entry for msgHash exists.
5207
+ */
5208
+ updateClassificationStatus(msgHash: string, update: {
5209
+ status: Classification["status"];
5210
+ reviewer?: string;
5211
+ reviewReason?: string;
5212
+ }): Classification | null;
5213
+ /** Read the last N classifications, newest first. Cheap enough for the admin UI. */
5214
+ readRecentClassifications(limit?: number): Classification[];
5215
+ /**
5216
+ * Hash that represents "this kind of message" for snap-to-path caching.
5217
+ * Normalizes casing + whitespace; includes channel + sender so two people
5218
+ * writing the same words can still land on different paths if they should.
5219
+ */
5220
+ fingerprint(msg: {
5221
+ text: string;
5222
+ channel?: string;
5223
+ sender?: string;
5224
+ }): string;
5225
+ loadIndex(): IndexFile;
5226
+ saveIndex(file: IndexFile): void;
5227
+ getFingerprint(fp: string): FingerprintEntry | undefined;
5228
+ setFingerprint(fp: string, entry: Omit<FingerprintEntry, "fingerprint" | "updatedAt">): void;
5229
+ /** Update the axes on an existing node. Schema-validates the result. */
5230
+ updateNodeAxes(id: string, axes: Record<string, string>): GraphNode;
5231
+ /** Remove a node. Refuses to delete if any other node still references it
5232
+ * (child via parentId, or axis ref). */
5233
+ deleteNode(id: string): void;
5234
+ /** Approve a pending classification. Commits any new nodes along its path,
5235
+ * stamps the fingerprint index so future similar messages hit the cache,
5236
+ * and appends an audit entry. Idempotent — no-op if already cached. */
5237
+ approveClassification(c: Classification): void;
5238
+ /** Record rejection — no nodes committed, no fingerprint set. */
5239
+ rejectClassification(c: Classification): void;
5240
+ }
5241
+
3611
5242
  interface MeshPeerInfo {
3612
5243
  peer: string;
3613
5244
  healthy: boolean;
@@ -3704,7 +5335,16 @@ declare class AgentRegistry {
3704
5335
  private rateLimiter;
3705
5336
  private tokenTracker;
3706
5337
  private landscape?;
5338
+ /** Mesh handle. When set, `execute` falls back to a peer that advertises
5339
+ * the requested agent before returning "Unknown agent". Duck-typed so
5340
+ * we don't create a circular import with src/a2a/mesh.ts. */
5341
+ private meshFallback?;
3707
5342
  private messageQueue;
5343
+ /** Intent Knowledge Graph classifier. Null when graph.enabled=false. */
5344
+ private classifier?;
5345
+ private graphStore?;
5346
+ /** Runtime handover store — shared with MessageRouter via file on disk. */
5347
+ private handoverStore;
3708
5348
  /** Active soul profile per agent+chat session: "agentId:channel:chatId" → profile name */
3709
5349
  private activeSouls;
3710
5350
  /** Live output captured per running task id — drives the dashboard streaming modal. */
@@ -3719,6 +5359,28 @@ declare class AgentRegistry {
3719
5359
  * Set landscape builder (called after mesh init).
3720
5360
  */
3721
5361
  setLandscape(builder: LandscapeBuilder): void;
5362
+ /** Wire the mesh so `execute` can fall back to a peer that hosts an
5363
+ * agent this node doesn't have. Called by the daemon after mesh boot.
5364
+ * Pass `undefined` to disable fallback (testing, mesh disabled). */
5365
+ setMeshFallback(mesh: NonNullable<AgentRegistry["meshFallback"]>): void;
5366
+ /** Hot-swap the provider map. Daemon reload calls this after agentx.json
5367
+ * changes — the next task that executes will resolve credentials through
5368
+ * the fresh table (see executeTask call-site). In-flight tasks keep the
5369
+ * old provider reference from their closure, which is the desired behavior
5370
+ * (rotating a key mid-task shouldn't fail the task). */
5371
+ setProviders(next: Record<string, {
5372
+ apiKey?: string;
5373
+ }>): void;
5374
+ /** Hot-swap the live DaemonConfig reference. Registry reads it lazily at
5375
+ * execute-time for landscape + session policies, so next-task semantics
5376
+ * match setProviders. */
5377
+ setConfig(next: DaemonConfig): void;
5378
+ /**
5379
+ * If there's an active handover routing TO this agent for this (channel,
5380
+ * chatId) pair AND the operator's summary hasn't been consumed yet, pull
5381
+ * + clear it so the target agent sees the note exactly once.
5382
+ */
5383
+ private buildHandoverNote;
3722
5384
  /**
3723
5385
  * Get agent definition by ID.
3724
5386
  */
@@ -3743,6 +5405,7 @@ declare class AgentRegistry {
3743
5405
  findAllMentioned(text: string): string[];
3744
5406
  /** Wiki hub accessor. */
3745
5407
  getWikiHub(): WikiHub;
5408
+ getGraphStore(): GraphStore | undefined;
3746
5409
  /**
3747
5410
  * Build peer list for context engine.
3748
5411
  */
@@ -3916,6 +5579,11 @@ interface IncomingMessage {
3916
5579
  resolvedAgent?: string;
3917
5580
  preferNode?: string;
3918
5581
  channelMeta?: ChannelMeta;
5582
+ /** Correlator threaded by the workflow engine. When a workflow dispatches
5583
+ * an agent, this carries the run id through the pipeline so post:response
5584
+ * can re-enter the engine with `kind: agentResult` transitions. Opaque
5585
+ * everywhere outside src/workflows/. */
5586
+ workflowRunId?: string;
3919
5587
  }
3920
5588
  interface OutgoingMessage {
3921
5589
  channel: string;
@@ -3945,6 +5613,58 @@ interface ChannelAdapter {
3945
5613
  getChannelMeta?(chatId: string): Promise<ChannelMeta | undefined>;
3946
5614
  }
3947
5615
 
5616
+ interface HandoverOverride {
5617
+ channel: string;
5618
+ chatId: string;
5619
+ accountId?: string;
5620
+ fromAgent: string;
5621
+ toAgent: string;
5622
+ summary?: string;
5623
+ createdAt: string;
5624
+ createdBy?: string;
5625
+ /** When set, overrides past this time are treated as expired (ignored on
5626
+ * read, optionally pruned on access). null = indefinite. */
5627
+ expiresAt?: string | null;
5628
+ /** Set after the summary has been injected once. Route stays active. */
5629
+ summaryConsumedAt?: string;
5630
+ }
5631
+ interface HandoverFile {
5632
+ version: number;
5633
+ overrides: Record<string, HandoverOverride>;
5634
+ }
5635
+ declare class HandoverStore {
5636
+ readonly filePath: string;
5637
+ private log;
5638
+ constructor(opts?: {
5639
+ baseDir?: string;
5640
+ log?: (...args: unknown[]) => void;
5641
+ });
5642
+ /** Canonical key for an override. Using "-" for missing accountId keeps
5643
+ * the key shape uniform across channels. */
5644
+ key(channel: string, chatId: string, accountId?: string): string;
5645
+ load(): HandoverFile;
5646
+ private save;
5647
+ /** Return the active override for a chat, or undefined. Skips expired. */
5648
+ get(channel: string, chatId: string, accountId?: string): HandoverOverride | undefined;
5649
+ /** Create or replace an override. If `fromAgent` isn't supplied the caller
5650
+ * can pass "" and the router will fill it in from current routing. */
5651
+ set(o: Omit<HandoverOverride, "createdAt"> & {
5652
+ createdAt?: string;
5653
+ }): HandoverOverride;
5654
+ /** Remove an override (release the handover). Idempotent. */
5655
+ remove(channel: string, chatId: string, accountId?: string): boolean;
5656
+ /** Mark the summary as consumed (after it's been injected once). Route
5657
+ * stays active; subsequent messages just skip the note. */
5658
+ consumeSummary(channel: string, chatId: string, accountId?: string): string | undefined;
5659
+ /** List all non-expired overrides. Used by the admin UI. */
5660
+ listActive(): HandoverOverride[];
5661
+ /** Overrides pointing at this agent (incoming) or away from it (outgoing). */
5662
+ listByAgent(agentId: string): {
5663
+ incoming: HandoverOverride[];
5664
+ outgoing: HandoverOverride[];
5665
+ };
5666
+ }
5667
+
3948
5668
  interface ServiceTrigger {
3949
5669
  pattern: string;
3950
5670
  channel?: string;
@@ -3974,6 +5694,14 @@ declare class ServiceMatcher {
3974
5694
  private services;
3975
5695
  private log;
3976
5696
  constructor(serviceDefs: Record<string, Omit<ServiceDef, "id">>, log?: (...args: unknown[]) => void);
5697
+ /** Recompile the service set from a fresh config. Safe to call at runtime —
5698
+ * the only state is the compiled regex array, which is fully replaced. A
5699
+ * message currently inside match() sees the old list for one iteration,
5700
+ * but that's fine: match() is synchronous and holds no cross-call state. */
5701
+ reload(serviceDefs: Record<string, Omit<ServiceDef, "id">>): {
5702
+ count: number;
5703
+ };
5704
+ private compile;
3977
5705
  /**
3978
5706
  * Match an incoming message against all service triggers.
3979
5707
  * Returns the first matching service or null.
@@ -4526,7 +6254,13 @@ declare class MessageRouter {
4526
6254
  private serviceMatcher?;
4527
6255
  private business?;
4528
6256
  private groupLog;
6257
+ /** Runtime per-chat routing overrides (handovers). Consulted at the top
6258
+ * of resolveAgent; config routes + mention matching only run when no
6259
+ * override applies. */
6260
+ private handoverStore;
4529
6261
  private log;
6262
+ /** Exposed so the admin panel + registry can share the same store. */
6263
+ getHandoverStore(): HandoverStore;
4530
6264
  /**
4531
6265
  * Short-TTL cache of recently processed incoming message IDs, keyed by
4532
6266
  * `<channel>:<accountId>:<id>`. Prevents duplicate replies from:
@@ -4563,7 +6297,19 @@ declare class MessageRouter {
4563
6297
  flushPersistence(): void;
4564
6298
  setMesh(mesh: A2AMesh): void;
4565
6299
  setServiceMatcher(matcher: ServiceMatcher): void;
6300
+ /** Accessor so the daemon reload path can call matcher.reload() without
6301
+ * holding a duplicate reference. Returns undefined if services were
6302
+ * empty at boot. */
6303
+ getServiceMatcher(): ServiceMatcher | undefined;
4566
6304
  setBusiness(business: BusinessLayer): void;
6305
+ /** Swap the live DaemonConfig reference after a hot-reload. Router reads
6306
+ * from this.config at send-time for things like per-account Telegram token
6307
+ * resolution, default agents, and policy allowlists — so any /reload that
6308
+ * touches channels must call this to avoid sending from a stale snapshot. */
6309
+ updateConfig(next: DaemonConfig): void;
6310
+ /** Look up a live adapter by name, e.g. "telegram". Used by the daemon
6311
+ * reload path to hot-swap account configs without a restart. */
6312
+ getChannel(name: string): ChannelAdapter | undefined;
4567
6313
  addChannel(adapter: ChannelAdapter): void;
4568
6314
  /**
4569
6315
  * Send an outbound message to any registered channel.
@@ -4628,6 +6374,9 @@ declare class MessageRouter {
4628
6374
  interface TelegramAccountConfig {
4629
6375
  token: string;
4630
6376
  agentBinding: string;
6377
+ allowFrom?: string[];
6378
+ /** When false, register the token for outbound send only (no long-poll). */
6379
+ pollInbound?: boolean;
4631
6380
  }
4632
6381
  declare class TelegramAdapter implements ChannelAdapter {
4633
6382
  readonly name = "telegram";
@@ -4635,11 +6384,55 @@ declare class TelegramAdapter implements ChannelAdapter {
4635
6384
  private offsetStore;
4636
6385
  private handler?;
4637
6386
  private polling;
6387
+ /** Per-account polling gate — flipped to false when an account is removed
6388
+ * or its token changes, so its pollLoop observes the stop and exits without
6389
+ * affecting siblings. Keyed by accountId. */
6390
+ private accountPolling;
4638
6391
  private log;
4639
- constructor(accounts: Record<string, TelegramAccountConfig>, log?: (...args: unknown[]) => void);
6392
+ /** Global allowlist fallback. When a per-account allowFrom is unset,
6393
+ * this list applies. When BOTH are unset, everything is rejected
6394
+ * (closed by default). */
6395
+ private globalAllowFrom?;
6396
+ constructor(accounts: Record<string, TelegramAccountConfig>, opts?: {
6397
+ policy?: {
6398
+ allowFrom?: string[];
6399
+ };
6400
+ }, log?: (...args: unknown[]) => void);
6401
+ /** Effective sender allowlist for an account. Returns undefined when the
6402
+ * account has no explicit config AND the global policy is unset — the
6403
+ * caller treats undefined as "closed, drop everything". */
6404
+ private effectiveAllowFrom;
6405
+ /** Accept a message only when at least one allowlist entry matches the
6406
+ * sender's user id, the chat id, or the sender's @username. */
6407
+ private isAllowed;
4640
6408
  onMessage(handler: (msg: IncomingMessage) => Promise<void>): void;
4641
6409
  start(): Promise<void>;
6410
+ /** Start a single account's poll loop. Used by both start() (boot) and
6411
+ * reloadAccounts() (hot-add on /reload). Verifies the bot token, records
6412
+ * the bot's identity, then spawns the long-poll loop. No-op if the account
6413
+ * is already polling. */
6414
+ private startAccount;
6415
+ /** Stop a single account's poll loop without touching siblings. Flips the
6416
+ * per-account gate and aborts the in-flight long-poll so Telegram releases
6417
+ * the server-side session immediately (otherwise a new start with the same
6418
+ * token hits 409 Conflict). */
6419
+ private stopAccount;
4642
6420
  stop(): Promise<void>;
6421
+ /** Hot-reload the account map in place. Diffs the new config against the
6422
+ * current one: removed accounts get their pollers stopped, added accounts
6423
+ * get started, and accounts whose token changed get restarted (allowFrom
6424
+ * changes are effectively read-through via effectiveAllowFrom so they
6425
+ * don't need a restart, but we do refresh the stored config). Returns the
6426
+ * diff for the caller to log/surface.
6427
+ *
6428
+ * Requires the adapter to be already started (polling=true). */
6429
+ reloadAccounts(next: Record<string, TelegramAccountConfig>, policy?: {
6430
+ allowFrom?: string[];
6431
+ }): Promise<{
6432
+ added: string[];
6433
+ removed: string[];
6434
+ tokenChanged: string[];
6435
+ }>;
4643
6436
  /**
4644
6437
  * Get token for a specific account ID. Used by router to send from correct bot.
4645
6438
  */
@@ -4663,10 +6456,46 @@ declare class TelegramAdapter implements ChannelAdapter {
4663
6456
  send(msg: OutgoingMessage & {
4664
6457
  accountId?: string;
4665
6458
  }): Promise<string>;
6459
+ /**
6460
+ * Send a message with an inline keyboard. Used by the workflow user-task
6461
+ * renderer so approve/reject-style tasks get one-tap URL buttons in
6462
+ * Telegram pointing at the daemon's one-click submit endpoint.
6463
+ *
6464
+ * Each button is a URL button (not callback_data) so the user's client
6465
+ * opens the link in a browser — this avoids wiring callback_query
6466
+ * plumbing through the adapter's polling loop for Phase 3 MVP.
6467
+ */
6468
+ sendWithInlineButtons(args: {
6469
+ chatId: string;
6470
+ text: string;
6471
+ buttons: Array<{
6472
+ label: string;
6473
+ url: string;
6474
+ }>;
6475
+ accountId?: string;
6476
+ parseMode?: "markdown" | "html" | "plain";
6477
+ }): Promise<string>;
4666
6478
  /**
4667
6479
  * Edit an existing message (for streaming updates).
4668
6480
  */
4669
6481
  editMessage(chatId: string, messageId: string, text: string, parseMode?: string, accountId?: string): Promise<boolean>;
6482
+ /**
6483
+ * Push a streaming draft to a private chat (Bot API 9.5+ `sendMessageDraft`).
6484
+ * Subsequent calls with the same `draftId` animate in place — much smoother
6485
+ * than `editMessageText` and not subject to the same edit-rate limits.
6486
+ *
6487
+ * **Constraint:** private chats only (DMs). Telegram returns an error for
6488
+ * groups/channels. Callers must check `isDirectMessage(chatId)` first.
6489
+ *
6490
+ * Returns `true` on success, `false` on any error (best-effort streaming —
6491
+ * never throw or callers will lose blocks). Falls back to no-formatting on
6492
+ * a parse-mode error so a malformed HTML chunk doesn't kill the stream.
6493
+ */
6494
+ sendDraft(chatId: string, draftId: number, text: string, parseMode?: string, accountId?: string): Promise<boolean>;
6495
+ /** Telegram convention: positive chat_id → private chat (DM with a user),
6496
+ * negative → group/supergroup/channel. Used to gate features that only
6497
+ * work in DMs (sendMessageDraft, message_effect_id, ...). */
6498
+ static isDirectMessage(chatId: string): boolean;
4670
6499
  /**
4671
6500
  * React to a message with an emoji.
4672
6501
  */
@@ -4680,6 +6509,18 @@ declare class TelegramAdapter implements ChannelAdapter {
4680
6509
  * Get verified context for a Telegram chat.
4681
6510
  * Reads from persistent group store (fed by my_chat_member events + API seed).
4682
6511
  */
6512
+ /** Surface chats the adapter has observed, so the workflow editor (or CLI)
6513
+ * can present a picker when an author is authoring an `action.send` that
6514
+ * targets a different chat than the trigger's. DMs come from the per-chat
6515
+ * account map (populated when a user messages a bot); groups come from
6516
+ * the persistent group membership store. Both lists are small — bounded
6517
+ * by the number of distinct chats the bots have been in. */
6518
+ listKnownChats(): Array<{
6519
+ id: string;
6520
+ name?: string;
6521
+ kind: "dm" | "group";
6522
+ accountId?: string;
6523
+ }>;
4683
6524
  getChannelMeta(chatId: string): Promise<ChannelMeta | undefined>;
4684
6525
  /**
4685
6526
  * Seed group membership by querying the Telegram API for each bot.
@@ -4689,6 +6530,105 @@ declare class TelegramAdapter implements ChannelAdapter {
4689
6530
  private apiCall;
4690
6531
  }
4691
6532
 
6533
+ interface ContactRecord {
6534
+ jid: string;
6535
+ /** Digits extracted from the JID's user segment. May be empty for LIDs. */
6536
+ phone: string;
6537
+ /** The contact's own "push name" (what they set on their device). */
6538
+ pushName?: string;
6539
+ /** Saved name from WhatsApp address book (if the linked device has it). */
6540
+ savedName?: string;
6541
+ /** Status / about text. Rarely populated from events alone; usually needs a live fetch. */
6542
+ status?: string;
6543
+ /** Last time we got any update for this contact. ISO8601. */
6544
+ updatedAt: string;
6545
+ }
6546
+ interface ChatRecord {
6547
+ jid: string;
6548
+ /** Best display name we have for the chat. Falls back to phone if missing. */
6549
+ name?: string;
6550
+ /** Derived from the JID — group chats end with @g.us. */
6551
+ isGroup: boolean;
6552
+ /** Last conversation timestamp we observed, as unix seconds. */
6553
+ lastMessageAt?: number;
6554
+ unreadCount?: number;
6555
+ updatedAt: string;
6556
+ }
6557
+ interface GroupMember {
6558
+ jid: string;
6559
+ admin?: "admin" | "superadmin";
6560
+ }
6561
+ interface GroupRecord {
6562
+ jid: string;
6563
+ subject: string;
6564
+ description?: string;
6565
+ owner?: string;
6566
+ members: GroupMember[];
6567
+ updatedAt: string;
6568
+ }
6569
+ declare class WhatsAppCache {
6570
+ private contacts;
6571
+ private chats;
6572
+ private groups;
6573
+ private cachePath;
6574
+ constructor(sessionDir: string);
6575
+ listContacts(): ContactRecord[];
6576
+ listChats(): ChatRecord[];
6577
+ listGroups(): GroupRecord[];
6578
+ getContact(jid: string): ContactRecord | undefined;
6579
+ getChat(jid: string): ChatRecord | undefined;
6580
+ getGroup(jid: string): GroupRecord | undefined;
6581
+ applyContactsUpdate(updates: Array<Record<string, unknown>>): void;
6582
+ applyChatsUpsert(chats: Array<Record<string, unknown>>): void;
6583
+ applyGroupsUpdate(updates: Array<Record<string, unknown>>): void;
6584
+ applyHistorySet(data: Record<string, unknown>): void;
6585
+ /** Write snapshot to cache.json. Call on graceful shutdown and periodically. */
6586
+ save(): void;
6587
+ private load;
6588
+ }
6589
+
6590
+ /** Shapes returned by the adapter's read API — used by the wiki ingestor.
6591
+ * These are stable contracts so the ingestor can mock the adapter without
6592
+ * pulling in Baileys types. */
6593
+ interface ContactSummary {
6594
+ jid: string;
6595
+ phone: string;
6596
+ name?: string;
6597
+ pushName?: string;
6598
+ savedName?: string;
6599
+ status?: string;
6600
+ updatedAt?: string;
6601
+ }
6602
+ interface ChatSummary {
6603
+ jid: string;
6604
+ name: string;
6605
+ isGroup: boolean;
6606
+ lastMessageAt?: number;
6607
+ unreadCount?: number;
6608
+ }
6609
+ interface GroupInfo {
6610
+ jid: string;
6611
+ subject: string;
6612
+ description?: string;
6613
+ owner?: string;
6614
+ members: Array<{
6615
+ jid: string;
6616
+ admin?: "admin" | "superadmin";
6617
+ }>;
6618
+ memberCount: number;
6619
+ }
6620
+ interface HistoryMessage {
6621
+ id: string;
6622
+ fromJid: string;
6623
+ fromMe: boolean;
6624
+ timestamp?: number;
6625
+ text: string;
6626
+ media?: {
6627
+ kind: "image" | "audio" | "video" | "document" | "sticker";
6628
+ caption?: string;
6629
+ filename?: string;
6630
+ };
6631
+ }
4692
6632
  interface WhatsAppRoute {
4693
6633
  contact?: string;
4694
6634
  group?: string;
@@ -4707,6 +6647,16 @@ declare class WhatsAppAdapter implements ChannelAdapter {
4707
6647
  private generation;
4708
6648
  private sentMessageIds;
4709
6649
  private log;
6650
+ /** Passive snapshot of contacts/chats/groups populated from Baileys events.
6651
+ * Consumed by the wiki ingestor. Persisted to {sessionDir}/cache.json on stop. */
6652
+ private cache;
6653
+ /** Token-bucket throttle for live Baileys reads. Configurable via
6654
+ * channels.whatsapp.ingest.throttle. Defaults keep the read surface
6655
+ * below what personal-account rate limits tend to trigger. */
6656
+ private throttleMinMs;
6657
+ private throttleMaxPerMinute;
6658
+ private lastReadAt;
6659
+ private readsInWindow;
4710
6660
  /** Fires whenever the underlying WhatsApp socket emits a new QR or clears it.
4711
6661
  * Passing null means the session is now connected. */
4712
6662
  private onQR?;
@@ -4719,7 +6669,19 @@ declare class WhatsAppAdapter implements ChannelAdapter {
4719
6669
  routes?: WhatsAppRoute[];
4720
6670
  onQR?: (qr: string | null) => void;
4721
6671
  onStatus?: (status: "connecting" | "open" | "close", detail?: string) => void;
6672
+ /** Throttle settings for live Baileys reads. Falls back to safe defaults. */
6673
+ throttle?: {
6674
+ minMsBetweenCalls?: number;
6675
+ maxCallsPerMinute?: number;
6676
+ };
4722
6677
  }, log?: (...args: unknown[]) => void);
6678
+ /** Read-only access for the wiki ingestor. Exposed so the ingestor can
6679
+ * iterate the passive snapshot without reaching into adapter internals. */
6680
+ getCache(): WhatsAppCache;
6681
+ /** True once the socket has connected at least once and emitted the
6682
+ * authenticated user. The ingestor checks this before attempting any
6683
+ * live read — a torn-down session will otherwise throw a cryptic error. */
6684
+ isConnected(): boolean;
4723
6685
  /**
4724
6686
  * Resolve which agent handles a message based on routes.
4725
6687
  */
@@ -4731,6 +6693,22 @@ declare class WhatsAppAdapter implements ChannelAdapter {
4731
6693
  editMessage(chatId: string, messageId: string, text: string): Promise<boolean>;
4732
6694
  sendTyping(chatId: string): Promise<void>;
4733
6695
  react(chatId: string, messageId: string, emoji?: string): Promise<void>;
6696
+ listContacts(): ContactSummary[];
6697
+ listChats(): ChatSummary[];
6698
+ getContactProfile(jid: string): Promise<ContactSummary | null>;
6699
+ getGroupMetadata(jid: string): Promise<GroupInfo | null>;
6700
+ /** Fetch up to `limit` recent messages for `jid`. This is the one read
6701
+ * that always hits Baileys live — there's no cached message body and
6702
+ * baileys doesn't persist history by default with the options we use.
6703
+ * Throttled. */
6704
+ getHistory(jid: string, opts?: {
6705
+ limit?: number;
6706
+ before?: string;
6707
+ }): Promise<HistoryMessage[]>;
6708
+ /** Token-bucket gate for live Baileys reads. Enforces
6709
+ * `throttleMinMs` between calls and caps the per-minute call count.
6710
+ * Returns a promise that resolves once it's safe to fire. */
6711
+ private throttleGate;
4734
6712
  }
4735
6713
 
4736
6714
  interface CronJobState {
@@ -4891,4 +6869,4 @@ declare function handleError(error: unknown): void;
4891
6869
  */
4892
6870
  declare function getPackageInfo(): PackageJson;
4893
6871
 
4894
- export { A2AClient, A2AMesh, A2AServer, A2AServerConfig, ALL_TOOL_NAMES, AgentCard, AgentConfig, AgentContext, AgentDef, AgentProvider, AgentRegistry, AgentResponse, AgentTask, AgentXDaemon, AgentXRuntime, AnthropicMessage, AuthConfig, BLOCKING_EVENTS, ChannelAdapter, ClaudeCodeProvider, ClaudeProvider, ContentBlock, ContextBuilder, CronJobDef, CronJobState, CronRunResult, CronScheduler, DaemonConfig, EnhanceConfig, EnhanceEngine, EnhanceResult, GenerateOptions, GenerateResult, GenerateStreamEvent, GeneratedFile, GenerationMessage, GenerationResult, GitCommitResult, GitDiffFile, GitLogEntry, GitManager, GitStatus, HOOK_EVENTS, HealConfig, HealEngine, HealResult, HookContext, HookDefinition, HookEvent, HookHandler, HookRegistry, HookResult, IncomingMessage, Memory, MemoryEntry, MemoryHierarchy, MemoryStore, MeshPeer, MessageRouter, MiddlewareFn, ModelUsage, OUTPUT_CONFIGS, OUTPUT_TYPES, OutgoingMessage, OutputConfig, OutputType, PERMISSION_MODES, PROVIDER_CAPABILITIES, PermissionAction, PermissionConfig, PermissionManager, PermissionMode, PermissionRule, Pipeline, PipelineRequest, PipelineResponse, ProjectSchemas, ProviderCapabilities, ProviderName, ProviderOptions, RawGenerationResult, ReplEngine, ReplOptions, RuntimeConfig, Session, StepUsage, StreamEvent, Task, TaskArtifact, TaskMessage, TaskState, TaskStatusUpdate, TechStack, TelegramAdapter, ToolCallInput, ToolExecutor, ToolExecutorOptions, ToolResult, UsageSummary, UsageTracker, WhatsAppAdapter, WikiAccess, WikiArticle, WikiArticleMeta, WikiEntry, WikiIndex, WikiStore, agentConfigSchema$1 as agentConfigSchema, checkCapabilities, createAgentContext, createProvider, createSession, daemonConfigSchema, debug, detectSchemas, detectTechStack, ensureCredentials, executeClaudeCode, executeOrchestrator, executeSdk, executeTask, exportSession, formatSchemas, formatTechStack, formatToolsForSystemPrompt, gatherContext7Docs, generate, generateSkill, generateSkillMd, generateStream, getAnthropicTools, getCommand, getLegacyTools, getPackageInfo, globalHooks, globalPermissions, globalTracker, handleError, installSkillPackage, isCommand, isDebug, listInstalledSkills, listSessions, loadAuthConfig, loadDaemonConfig, loadHooks, loadLatestSession, loadLocalSkills, loadProjectInstructions, loadSession, logger, matchSkillsToTask, outputTypeDescriptions, parseCommand, parseSkillContent, parseSkillFile, permissionConfigSchema, permissionModeSchema, registerCommand, resolveAtImports, resolveOutputType, resolveToken, runModelSetup, saveAuthConfig, saveSession, setDebug, startMcpServer, validateWorkspaces };
6872
+ export { A2AClient, A2AMesh, A2AServer, A2AServerConfig, ALL_TOOL_NAMES, AgentCard, AgentConfig, AgentContext, AgentDef, AgentProvider, AgentRegistry, AgentResponse, AgentTask, AgentXDaemon, AgentXRuntime, AnthropicMessage, AuthConfig, AxisDef, AxisType, BLOCKING_EVENTS, ChannelAdapter, Classification, ClassificationSource, ClassificationStatus, ClaudeCodeProvider, ClaudeProvider, ContentBlock, ContextBuilder, CronJobDef, CronJobState, CronRunResult, CronScheduler, DaemonConfig, EnhanceConfig, EnhanceEngine, EnhanceResult, FingerprintEntry, GenerateOptions, GenerateResult, GenerateStreamEvent, GeneratedFile, GenerationMessage, GenerationResult, GitCommitResult, GitDiffFile, GitLogEntry, GitManager, GitStatus, GraphNode, GraphSchema, GraphStore, GraphStoreOptions, HOOK_EVENTS, HealConfig, HealEngine, HealResult, HookContext, HookDefinition, HookEvent, HookHandler, HookRegistry, HookResult, IncomingMessage, IndexFile, LevelDef, Memory, MemoryEntry, MemoryHierarchy, MemoryStore, MeshPeer, MessageRouter, MiddlewareFn, ModelUsage, NodesFile, OUTPUT_CONFIGS, OUTPUT_TYPES, OutgoingMessage, OutputConfig, OutputType, PERMISSION_MODES, PROVIDER_CAPABILITIES, PermissionAction, PermissionConfig, PermissionManager, PermissionMode, PermissionRule, Pipeline, PipelineRequest, PipelineResponse, ProjectSchemas, ProviderCapabilities, ProviderName, ProviderOptions, RawGenerationResult, ReplEngine, ReplOptions, RuntimeConfig, STARTER_SCHEMA, Session, StepUsage, StreamEvent, Task, TaskArtifact, TaskMessage, TaskState, TaskStatusUpdate, TechStack, TelegramAdapter, ToolCallInput, ToolExecutor, ToolExecutorOptions, ToolResult, UsageSummary, UsageTracker, WhatsAppAdapter, WikiAccess, WikiArticle, WikiArticleMeta, WikiEntry, WikiIndex, WikiStore, agentConfigSchema$1 as agentConfigSchema, checkCapabilities, createAgentContext, createProvider, createSession, daemonConfigSchema, debug, detectSchemas, detectTechStack, ensureCredentials, executeClaudeCode, executeOrchestrator, executeSdk, executeTask, exportSession, formatSchemas, formatTechStack, formatToolsForSystemPrompt, gatherContext7Docs, generate, generateSkill, generateSkillMd, generateStream, getAnthropicTools, getCommand, getLegacyTools, getPackageInfo, globalHooks, globalPermissions, globalTracker, handleError, installSkillPackage, isCommand, isDebug, listInstalledSkills, listSessions, loadAuthConfig, loadDaemonConfig, loadHooks, loadLatestSession, loadLocalSkills, loadProjectInstructions, loadSession, logger, matchSkillsToTask, outputTypeDescriptions, parseCommand, parseSkillContent, parseSkillFile, permissionConfigSchema, permissionModeSchema, registerCommand, resolveAtImports, resolveOutputType, resolveToken, runModelSetup, saveAuthConfig, saveSession, setDebug, startMcpServer, validateWorkspaces };