@elizaos/autonomous 2.0.0-alpha.21 → 2.0.0-alpha.23

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 (1077) hide show
  1. package/package.json +14 -6
  2. package/test/agent-export.e2e.test.ts +376 -0
  3. package/test/agent-orchestration.e2e.test.ts +1568 -0
  4. package/test/agent-restart-recovery.e2e.test.ts +149 -0
  5. package/test/agent-runtime.e2e.test.ts +1515 -0
  6. package/test/anvil-contracts.e2e.test.ts +533 -0
  7. package/test/anvil-helper.ts +285 -0
  8. package/test/api-auth-live.e2e.test.ts +519 -0
  9. package/test/api-auth.e2e.test.ts +1039 -0
  10. package/test/api-server.e2e.test.ts +4582 -0
  11. package/test/apps-e2e.e2e.test.ts +1108 -0
  12. package/test/auth-modules.e2e.test.ts +71 -0
  13. package/test/cloud-auth-state.e2e.test.ts +145 -0
  14. package/test/cloud-persistence.e2e.test.ts +260 -0
  15. package/test/cloud-providers.e2e.test.ts +781 -0
  16. package/test/config-hot-reload.e2e.test.ts +131 -0
  17. package/test/contract-deployer.ts +151 -0
  18. package/test/contracts/MockMiladyAgentRegistry.sol +216 -0
  19. package/test/contracts/MockMiladyCollection.sol +195 -0
  20. package/test/contracts/cache/solidity-files-cache.json +1 -0
  21. package/test/contracts/foundry.toml +14 -0
  22. package/test/contracts/lib/openzeppelin-contracts/.changeset/config.json +12 -0
  23. package/test/contracts/lib/openzeppelin-contracts/.codecov.yml +12 -0
  24. package/test/contracts/lib/openzeppelin-contracts/.editorconfig +21 -0
  25. package/test/contracts/lib/openzeppelin-contracts/.eslintrc +20 -0
  26. package/test/contracts/lib/openzeppelin-contracts/.github/ISSUE_TEMPLATE/bug_report.md +21 -0
  27. package/test/contracts/lib/openzeppelin-contracts/.github/ISSUE_TEMPLATE/config.yml +4 -0
  28. package/test/contracts/lib/openzeppelin-contracts/.github/ISSUE_TEMPLATE/feature_request.md +14 -0
  29. package/test/contracts/lib/openzeppelin-contracts/.github/PULL_REQUEST_TEMPLATE.md +20 -0
  30. package/test/contracts/lib/openzeppelin-contracts/.github/actions/gas-compare/action.yml +49 -0
  31. package/test/contracts/lib/openzeppelin-contracts/.github/actions/setup/action.yml +19 -0
  32. package/test/contracts/lib/openzeppelin-contracts/.github/actions/storage-layout/action.yml +55 -0
  33. package/test/contracts/lib/openzeppelin-contracts/.github/workflows/actionlint.yml +18 -0
  34. package/test/contracts/lib/openzeppelin-contracts/.github/workflows/changeset.yml +28 -0
  35. package/test/contracts/lib/openzeppelin-contracts/.github/workflows/checks.yml +111 -0
  36. package/test/contracts/lib/openzeppelin-contracts/.github/workflows/docs.yml +19 -0
  37. package/test/contracts/lib/openzeppelin-contracts/.github/workflows/formal-verification.yml +68 -0
  38. package/test/contracts/lib/openzeppelin-contracts/.github/workflows/release-cycle.yml +218 -0
  39. package/test/contracts/lib/openzeppelin-contracts/.github/workflows/upgradeable.yml +30 -0
  40. package/test/contracts/lib/openzeppelin-contracts/.gitmodules +7 -0
  41. package/test/contracts/lib/openzeppelin-contracts/.mocharc.js +4 -0
  42. package/test/contracts/lib/openzeppelin-contracts/.prettierrc +14 -0
  43. package/test/contracts/lib/openzeppelin-contracts/.solcover.js +13 -0
  44. package/test/contracts/lib/openzeppelin-contracts/.solhint.json +14 -0
  45. package/test/contracts/lib/openzeppelin-contracts/CHANGELOG.md +743 -0
  46. package/test/contracts/lib/openzeppelin-contracts/CODE_OF_CONDUCT.md +73 -0
  47. package/test/contracts/lib/openzeppelin-contracts/CONTRIBUTING.md +36 -0
  48. package/test/contracts/lib/openzeppelin-contracts/GUIDELINES.md +117 -0
  49. package/test/contracts/lib/openzeppelin-contracts/LICENSE +22 -0
  50. package/test/contracts/lib/openzeppelin-contracts/README.md +90 -0
  51. package/test/contracts/lib/openzeppelin-contracts/RELEASING.md +47 -0
  52. package/test/contracts/lib/openzeppelin-contracts/SECURITY.md +42 -0
  53. package/test/contracts/lib/openzeppelin-contracts/audits/2017-03.md +292 -0
  54. package/test/contracts/lib/openzeppelin-contracts/audits/2018-10.pdf +0 -0
  55. package/test/contracts/lib/openzeppelin-contracts/audits/2022-10-Checkpoints.pdf +0 -0
  56. package/test/contracts/lib/openzeppelin-contracts/audits/2022-10-ERC4626.pdf +0 -0
  57. package/test/contracts/lib/openzeppelin-contracts/audits/2023-05-v4.9.pdf +0 -0
  58. package/test/contracts/lib/openzeppelin-contracts/audits/README.md +16 -0
  59. package/test/contracts/lib/openzeppelin-contracts/certora/Makefile +54 -0
  60. package/test/contracts/lib/openzeppelin-contracts/certora/README.md +60 -0
  61. package/test/contracts/lib/openzeppelin-contracts/certora/diff/token_ERC721_ERC721.sol.patch +14 -0
  62. package/test/contracts/lib/openzeppelin-contracts/certora/harnesses/AccessControlDefaultAdminRulesHarness.sol +47 -0
  63. package/test/contracts/lib/openzeppelin-contracts/certora/harnesses/AccessControlHarness.sol +7 -0
  64. package/test/contracts/lib/openzeppelin-contracts/certora/harnesses/DoubleEndedQueueHarness.sol +59 -0
  65. package/test/contracts/lib/openzeppelin-contracts/certora/harnesses/ERC20FlashMintHarness.sol +36 -0
  66. package/test/contracts/lib/openzeppelin-contracts/certora/harnesses/ERC20PermitHarness.sol +17 -0
  67. package/test/contracts/lib/openzeppelin-contracts/certora/harnesses/ERC20WrapperHarness.sol +25 -0
  68. package/test/contracts/lib/openzeppelin-contracts/certora/harnesses/ERC3156FlashBorrowerHarness.sol +13 -0
  69. package/test/contracts/lib/openzeppelin-contracts/certora/harnesses/ERC721Harness.sol +37 -0
  70. package/test/contracts/lib/openzeppelin-contracts/certora/harnesses/ERC721ReceiverHarness.sol +11 -0
  71. package/test/contracts/lib/openzeppelin-contracts/certora/harnesses/EnumerableMapHarness.sol +55 -0
  72. package/test/contracts/lib/openzeppelin-contracts/certora/harnesses/EnumerableSetHarness.sol +35 -0
  73. package/test/contracts/lib/openzeppelin-contracts/certora/harnesses/InitializableHarness.sol +23 -0
  74. package/test/contracts/lib/openzeppelin-contracts/certora/harnesses/Ownable2StepHarness.sol +9 -0
  75. package/test/contracts/lib/openzeppelin-contracts/certora/harnesses/OwnableHarness.sol +9 -0
  76. package/test/contracts/lib/openzeppelin-contracts/certora/harnesses/PausableHarness.sol +19 -0
  77. package/test/contracts/lib/openzeppelin-contracts/certora/harnesses/TimelockControllerHarness.sol +12 -0
  78. package/test/contracts/lib/openzeppelin-contracts/certora/reports/2021-10.pdf +0 -0
  79. package/test/contracts/lib/openzeppelin-contracts/certora/reports/2022-03.pdf +0 -0
  80. package/test/contracts/lib/openzeppelin-contracts/certora/reports/2022-05.pdf +0 -0
  81. package/test/contracts/lib/openzeppelin-contracts/certora/run.js +146 -0
  82. package/test/contracts/lib/openzeppelin-contracts/certora/specs/AccessControl.spec +126 -0
  83. package/test/contracts/lib/openzeppelin-contracts/certora/specs/AccessControlDefaultAdminRules.spec +500 -0
  84. package/test/contracts/lib/openzeppelin-contracts/certora/specs/DoubleEndedQueue.spec +366 -0
  85. package/test/contracts/lib/openzeppelin-contracts/certora/specs/ERC20.spec +414 -0
  86. package/test/contracts/lib/openzeppelin-contracts/certora/specs/ERC20FlashMint.spec +48 -0
  87. package/test/contracts/lib/openzeppelin-contracts/certora/specs/ERC20Wrapper.spec +198 -0
  88. package/test/contracts/lib/openzeppelin-contracts/certora/specs/ERC721.spec +589 -0
  89. package/test/contracts/lib/openzeppelin-contracts/certora/specs/EnumerableMap.spec +334 -0
  90. package/test/contracts/lib/openzeppelin-contracts/certora/specs/EnumerableSet.spec +247 -0
  91. package/test/contracts/lib/openzeppelin-contracts/certora/specs/Initializable.spec +165 -0
  92. package/test/contracts/lib/openzeppelin-contracts/certora/specs/Ownable.spec +78 -0
  93. package/test/contracts/lib/openzeppelin-contracts/certora/specs/Ownable2Step.spec +108 -0
  94. package/test/contracts/lib/openzeppelin-contracts/certora/specs/Pausable.spec +96 -0
  95. package/test/contracts/lib/openzeppelin-contracts/certora/specs/TimelockController.spec +275 -0
  96. package/test/contracts/lib/openzeppelin-contracts/certora/specs/helpers/helpers.spec +1 -0
  97. package/test/contracts/lib/openzeppelin-contracts/certora/specs/methods/IAccessControl.spec +7 -0
  98. package/test/contracts/lib/openzeppelin-contracts/certora/specs/methods/IAccessControlDefaultAdminRules.spec +36 -0
  99. package/test/contracts/lib/openzeppelin-contracts/certora/specs/methods/IERC20.spec +11 -0
  100. package/test/contracts/lib/openzeppelin-contracts/certora/specs/methods/IERC2612.spec +5 -0
  101. package/test/contracts/lib/openzeppelin-contracts/certora/specs/methods/IERC3156.spec +5 -0
  102. package/test/contracts/lib/openzeppelin-contracts/certora/specs/methods/IERC5313.spec +3 -0
  103. package/test/contracts/lib/openzeppelin-contracts/certora/specs/methods/IERC721.spec +20 -0
  104. package/test/contracts/lib/openzeppelin-contracts/certora/specs/methods/IOwnable.spec +5 -0
  105. package/test/contracts/lib/openzeppelin-contracts/certora/specs/methods/IOwnable2Step.spec +7 -0
  106. package/test/contracts/lib/openzeppelin-contracts/certora/specs.json +86 -0
  107. package/test/contracts/lib/openzeppelin-contracts/contracts/access/AccessControl.sol +248 -0
  108. package/test/contracts/lib/openzeppelin-contracts/contracts/access/AccessControlCrossChain.sol +45 -0
  109. package/test/contracts/lib/openzeppelin-contracts/contracts/access/AccessControlDefaultAdminRules.sol +383 -0
  110. package/test/contracts/lib/openzeppelin-contracts/contracts/access/AccessControlEnumerable.sol +64 -0
  111. package/test/contracts/lib/openzeppelin-contracts/contracts/access/IAccessControl.sol +88 -0
  112. package/test/contracts/lib/openzeppelin-contracts/contracts/access/IAccessControlDefaultAdminRules.sol +172 -0
  113. package/test/contracts/lib/openzeppelin-contracts/contracts/access/IAccessControlEnumerable.sol +31 -0
  114. package/test/contracts/lib/openzeppelin-contracts/contracts/access/Ownable.sol +83 -0
  115. package/test/contracts/lib/openzeppelin-contracts/contracts/access/Ownable2Step.sol +57 -0
  116. package/test/contracts/lib/openzeppelin-contracts/contracts/access/README.adoc +27 -0
  117. package/test/contracts/lib/openzeppelin-contracts/contracts/crosschain/CrossChainEnabled.sol +54 -0
  118. package/test/contracts/lib/openzeppelin-contracts/contracts/crosschain/README.adoc +34 -0
  119. package/test/contracts/lib/openzeppelin-contracts/contracts/crosschain/amb/CrossChainEnabledAMB.sol +49 -0
  120. package/test/contracts/lib/openzeppelin-contracts/contracts/crosschain/amb/LibAMB.sol +35 -0
  121. package/test/contracts/lib/openzeppelin-contracts/contracts/crosschain/arbitrum/CrossChainEnabledArbitrumL1.sol +44 -0
  122. package/test/contracts/lib/openzeppelin-contracts/contracts/crosschain/arbitrum/CrossChainEnabledArbitrumL2.sol +40 -0
  123. package/test/contracts/lib/openzeppelin-contracts/contracts/crosschain/arbitrum/LibArbitrumL1.sol +42 -0
  124. package/test/contracts/lib/openzeppelin-contracts/contracts/crosschain/arbitrum/LibArbitrumL2.sol +45 -0
  125. package/test/contracts/lib/openzeppelin-contracts/contracts/crosschain/errors.sol +7 -0
  126. package/test/contracts/lib/openzeppelin-contracts/contracts/crosschain/optimism/CrossChainEnabledOptimism.sol +41 -0
  127. package/test/contracts/lib/openzeppelin-contracts/contracts/crosschain/optimism/LibOptimism.sol +36 -0
  128. package/test/contracts/lib/openzeppelin-contracts/contracts/crosschain/polygon/CrossChainEnabledPolygonChild.sol +72 -0
  129. package/test/contracts/lib/openzeppelin-contracts/contracts/finance/PaymentSplitter.sol +214 -0
  130. package/test/contracts/lib/openzeppelin-contracts/contracts/finance/README.adoc +20 -0
  131. package/test/contracts/lib/openzeppelin-contracts/contracts/finance/VestingWallet.sol +145 -0
  132. package/test/contracts/lib/openzeppelin-contracts/contracts/governance/Governor.sol +723 -0
  133. package/test/contracts/lib/openzeppelin-contracts/contracts/governance/IGovernor.sol +313 -0
  134. package/test/contracts/lib/openzeppelin-contracts/contracts/governance/README.adoc +176 -0
  135. package/test/contracts/lib/openzeppelin-contracts/contracts/governance/TimelockController.sol +422 -0
  136. package/test/contracts/lib/openzeppelin-contracts/contracts/governance/compatibility/GovernorCompatibilityBravo.sol +333 -0
  137. package/test/contracts/lib/openzeppelin-contracts/contracts/governance/compatibility/IGovernorCompatibilityBravo.sol +118 -0
  138. package/test/contracts/lib/openzeppelin-contracts/contracts/governance/extensions/GovernorCountingSimple.sol +100 -0
  139. package/test/contracts/lib/openzeppelin-contracts/contracts/governance/extensions/GovernorPreventLateQuorum.sol +105 -0
  140. package/test/contracts/lib/openzeppelin-contracts/contracts/governance/extensions/GovernorProposalThreshold.sol +23 -0
  141. package/test/contracts/lib/openzeppelin-contracts/contracts/governance/extensions/GovernorSettings.sol +110 -0
  142. package/test/contracts/lib/openzeppelin-contracts/contracts/governance/extensions/GovernorTimelockCompound.sol +190 -0
  143. package/test/contracts/lib/openzeppelin-contracts/contracts/governance/extensions/GovernorTimelockControl.sol +166 -0
  144. package/test/contracts/lib/openzeppelin-contracts/contracts/governance/extensions/GovernorVotes.sol +55 -0
  145. package/test/contracts/lib/openzeppelin-contracts/contracts/governance/extensions/GovernorVotesComp.sol +55 -0
  146. package/test/contracts/lib/openzeppelin-contracts/contracts/governance/extensions/GovernorVotesQuorumFraction.sol +121 -0
  147. package/test/contracts/lib/openzeppelin-contracts/contracts/governance/extensions/IGovernorTimelock.sol +26 -0
  148. package/test/contracts/lib/openzeppelin-contracts/contracts/governance/utils/IVotes.sol +56 -0
  149. package/test/contracts/lib/openzeppelin-contracts/contracts/governance/utils/Votes.sol +244 -0
  150. package/test/contracts/lib/openzeppelin-contracts/contracts/interfaces/IERC1155.sol +6 -0
  151. package/test/contracts/lib/openzeppelin-contracts/contracts/interfaces/IERC1155MetadataURI.sol +6 -0
  152. package/test/contracts/lib/openzeppelin-contracts/contracts/interfaces/IERC1155Receiver.sol +6 -0
  153. package/test/contracts/lib/openzeppelin-contracts/contracts/interfaces/IERC1271.sol +19 -0
  154. package/test/contracts/lib/openzeppelin-contracts/contracts/interfaces/IERC1363.sol +80 -0
  155. package/test/contracts/lib/openzeppelin-contracts/contracts/interfaces/IERC1363Receiver.sol +35 -0
  156. package/test/contracts/lib/openzeppelin-contracts/contracts/interfaces/IERC1363Spender.sol +29 -0
  157. package/test/contracts/lib/openzeppelin-contracts/contracts/interfaces/IERC165.sol +6 -0
  158. package/test/contracts/lib/openzeppelin-contracts/contracts/interfaces/IERC1820Implementer.sol +6 -0
  159. package/test/contracts/lib/openzeppelin-contracts/contracts/interfaces/IERC1820Registry.sol +6 -0
  160. package/test/contracts/lib/openzeppelin-contracts/contracts/interfaces/IERC1967.sol +26 -0
  161. package/test/contracts/lib/openzeppelin-contracts/contracts/interfaces/IERC20.sol +6 -0
  162. package/test/contracts/lib/openzeppelin-contracts/contracts/interfaces/IERC20Metadata.sol +6 -0
  163. package/test/contracts/lib/openzeppelin-contracts/contracts/interfaces/IERC2309.sol +21 -0
  164. package/test/contracts/lib/openzeppelin-contracts/contracts/interfaces/IERC2612.sol +8 -0
  165. package/test/contracts/lib/openzeppelin-contracts/contracts/interfaces/IERC2981.sol +25 -0
  166. package/test/contracts/lib/openzeppelin-contracts/contracts/interfaces/IERC3156.sol +7 -0
  167. package/test/contracts/lib/openzeppelin-contracts/contracts/interfaces/IERC3156FlashBorrower.sol +29 -0
  168. package/test/contracts/lib/openzeppelin-contracts/contracts/interfaces/IERC3156FlashLender.sol +43 -0
  169. package/test/contracts/lib/openzeppelin-contracts/contracts/interfaces/IERC4626.sol +232 -0
  170. package/test/contracts/lib/openzeppelin-contracts/contracts/interfaces/IERC4906.sol +20 -0
  171. package/test/contracts/lib/openzeppelin-contracts/contracts/interfaces/IERC5267.sol +28 -0
  172. package/test/contracts/lib/openzeppelin-contracts/contracts/interfaces/IERC5313.sol +18 -0
  173. package/test/contracts/lib/openzeppelin-contracts/contracts/interfaces/IERC5805.sol +9 -0
  174. package/test/contracts/lib/openzeppelin-contracts/contracts/interfaces/IERC6372.sol +17 -0
  175. package/test/contracts/lib/openzeppelin-contracts/contracts/interfaces/IERC721.sol +6 -0
  176. package/test/contracts/lib/openzeppelin-contracts/contracts/interfaces/IERC721Enumerable.sol +6 -0
  177. package/test/contracts/lib/openzeppelin-contracts/contracts/interfaces/IERC721Metadata.sol +6 -0
  178. package/test/contracts/lib/openzeppelin-contracts/contracts/interfaces/IERC721Receiver.sol +6 -0
  179. package/test/contracts/lib/openzeppelin-contracts/contracts/interfaces/IERC777.sol +6 -0
  180. package/test/contracts/lib/openzeppelin-contracts/contracts/interfaces/IERC777Recipient.sol +6 -0
  181. package/test/contracts/lib/openzeppelin-contracts/contracts/interfaces/IERC777Sender.sol +6 -0
  182. package/test/contracts/lib/openzeppelin-contracts/contracts/interfaces/README.adoc +73 -0
  183. package/test/contracts/lib/openzeppelin-contracts/contracts/interfaces/draft-IERC1822.sol +20 -0
  184. package/test/contracts/lib/openzeppelin-contracts/contracts/interfaces/draft-IERC2612.sol +8 -0
  185. package/test/contracts/lib/openzeppelin-contracts/contracts/metatx/ERC2771Context.sol +54 -0
  186. package/test/contracts/lib/openzeppelin-contracts/contracts/metatx/MinimalForwarder.sol +72 -0
  187. package/test/contracts/lib/openzeppelin-contracts/contracts/metatx/README.adoc +12 -0
  188. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/AccessControlCrossChainMock.sol +8 -0
  189. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/ArraysMock.sol +51 -0
  190. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/Base64Dirty.sol +19 -0
  191. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/CallReceiverMock.sol +61 -0
  192. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/ConditionalEscrowMock.sol +18 -0
  193. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/ContextMock.sol +35 -0
  194. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/DummyImplementation.sol +57 -0
  195. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/EIP712Verifier.sol +16 -0
  196. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/ERC1271WalletMock.sol +26 -0
  197. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/ERC165/ERC165MaliciousData.sol +12 -0
  198. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/ERC165/ERC165MissingData.sol +7 -0
  199. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/ERC165/ERC165NotSupported.sol +5 -0
  200. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/ERC165/ERC165ReturnBomb.sol +18 -0
  201. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/ERC20Mock.sol +16 -0
  202. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/ERC20Reentrant.sol +43 -0
  203. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/ERC2771ContextMock.sol +27 -0
  204. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/ERC3156FlashBorrowerMock.sol +53 -0
  205. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/ERC4626Mock.sol +16 -0
  206. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/EtherReceiverMock.sol +17 -0
  207. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/InitializableMock.sol +130 -0
  208. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/MulticallTest.sol +23 -0
  209. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/MultipleInheritanceInitializableMocks.sol +131 -0
  210. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/PausableMock.sol +31 -0
  211. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/PullPaymentMock.sol +15 -0
  212. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/ReentrancyAttack.sol +12 -0
  213. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/ReentrancyMock.sol +51 -0
  214. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/RegressionImplementation.sol +61 -0
  215. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/SafeMathMemoryCheck.sol +72 -0
  216. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/SingleInheritanceInitializableMocks.sol +49 -0
  217. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/StorageSlotMock.sol +77 -0
  218. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/TimelockReentrant.sol +26 -0
  219. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/TimersBlockNumberImpl.sol +39 -0
  220. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/TimersTimestampImpl.sol +39 -0
  221. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/VotesMock.sol +45 -0
  222. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/compound/CompTimelock.sol +174 -0
  223. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/crosschain/bridges.sol +94 -0
  224. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/crosschain/receivers.sol +54 -0
  225. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/docs/ERC4626Fees.sol +100 -0
  226. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/docs/governance/MyGovernor.sol +88 -0
  227. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/docs/governance/MyToken.sol +24 -0
  228. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/docs/governance/MyTokenTimestampBased.sol +35 -0
  229. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/docs/governance/MyTokenWrapped.sol +31 -0
  230. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/governance/GovernorCompMock.sol +20 -0
  231. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/governance/GovernorCompatibilityBravoMock.sol +100 -0
  232. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/governance/GovernorMock.sol +28 -0
  233. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/governance/GovernorPreventLateQuorumMock.sol +45 -0
  234. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/governance/GovernorTimelockCompoundMock.sol +60 -0
  235. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/governance/GovernorTimelockControlMock.sol +60 -0
  236. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/governance/GovernorVoteMock.sol +20 -0
  237. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/governance/GovernorWithParamsMock.sol +50 -0
  238. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/proxy/BadBeacon.sol +11 -0
  239. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/proxy/ClashingImplementation.sol +17 -0
  240. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/proxy/UUPSLegacy.sol +54 -0
  241. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/proxy/UUPSUpgradeableMock.sol +33 -0
  242. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/token/ERC1155ReceiverMock.sol +47 -0
  243. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/token/ERC20DecimalsMock.sol +17 -0
  244. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/token/ERC20ExcessDecimalsMock.sol +9 -0
  245. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/token/ERC20FlashMintMock.sol +26 -0
  246. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/token/ERC20ForceApproveMock.sol +13 -0
  247. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/token/ERC20MulticallMock.sol +8 -0
  248. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/token/ERC20NoReturnMock.sol +28 -0
  249. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/token/ERC20PermitNoRevertMock.sol +36 -0
  250. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/token/ERC20ReturnFalseMock.sol +19 -0
  251. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/token/ERC20VotesLegacyMock.sol +262 -0
  252. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/token/ERC4626OffsetMock.sol +17 -0
  253. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/token/ERC4646FeesMock.sol +40 -0
  254. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/token/ERC721ConsecutiveEnumerableMock.sol +51 -0
  255. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/token/ERC721ConsecutiveMock.sol +61 -0
  256. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/token/ERC721ReceiverMock.sol +42 -0
  257. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/token/ERC721URIStorageMock.sol +17 -0
  258. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/token/ERC777Mock.sol +13 -0
  259. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/token/ERC777SenderRecipientMock.sol +152 -0
  260. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/token/VotesTimestamp.sol +40 -0
  261. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/wizard/MyGovernor1.sol +79 -0
  262. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/wizard/MyGovernor2.sol +85 -0
  263. package/test/contracts/lib/openzeppelin-contracts/contracts/mocks/wizard/MyGovernor3.sol +94 -0
  264. package/test/contracts/lib/openzeppelin-contracts/contracts/package.json +32 -0
  265. package/test/contracts/lib/openzeppelin-contracts/contracts/proxy/Clones.sol +88 -0
  266. package/test/contracts/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol +32 -0
  267. package/test/contracts/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Upgrade.sol +157 -0
  268. package/test/contracts/lib/openzeppelin-contracts/contracts/proxy/Proxy.sol +86 -0
  269. package/test/contracts/lib/openzeppelin-contracts/contracts/proxy/README.adoc +87 -0
  270. package/test/contracts/lib/openzeppelin-contracts/contracts/proxy/beacon/BeaconProxy.sol +61 -0
  271. package/test/contracts/lib/openzeppelin-contracts/contracts/proxy/beacon/IBeacon.sol +16 -0
  272. package/test/contracts/lib/openzeppelin-contracts/contracts/proxy/beacon/UpgradeableBeacon.sol +65 -0
  273. package/test/contracts/lib/openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol +81 -0
  274. package/test/contracts/lib/openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol +191 -0
  275. package/test/contracts/lib/openzeppelin-contracts/contracts/proxy/utils/Initializable.sol +166 -0
  276. package/test/contracts/lib/openzeppelin-contracts/contracts/proxy/utils/UUPSUpgradeable.sol +99 -0
  277. package/test/contracts/lib/openzeppelin-contracts/contracts/security/Pausable.sol +105 -0
  278. package/test/contracts/lib/openzeppelin-contracts/contracts/security/PullPayment.sol +74 -0
  279. package/test/contracts/lib/openzeppelin-contracts/contracts/security/README.adoc +20 -0
  280. package/test/contracts/lib/openzeppelin-contracts/contracts/security/ReentrancyGuard.sol +77 -0
  281. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC1155/ERC1155.sol +497 -0
  282. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC1155/IERC1155.sol +119 -0
  283. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC1155/IERC1155Receiver.sol +58 -0
  284. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC1155/README.adoc +49 -0
  285. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC1155/extensions/ERC1155Burnable.sol +32 -0
  286. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC1155/extensions/ERC1155Pausable.sol +44 -0
  287. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC1155/extensions/ERC1155Supply.sol +64 -0
  288. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC1155/extensions/ERC1155URIStorage.sol +63 -0
  289. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol +22 -0
  290. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC1155/presets/ERC1155PresetMinterPauser.sol +114 -0
  291. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC1155/presets/README.md +1 -0
  292. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC1155/utils/ERC1155Holder.sol +36 -0
  293. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC1155/utils/ERC1155Receiver.sol +19 -0
  294. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol +365 -0
  295. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol +78 -0
  296. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC20/README.adoc +80 -0
  297. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Burnable.sol +39 -0
  298. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Capped.sol +37 -0
  299. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20FlashMint.sol +109 -0
  300. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Pausable.sol +35 -0
  301. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Permit.sol +95 -0
  302. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Snapshot.sol +191 -0
  303. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Votes.sol +290 -0
  304. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20VotesComp.sol +46 -0
  305. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Wrapper.sol +73 -0
  306. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC4626.sol +256 -0
  307. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol +28 -0
  308. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Permit.sol +90 -0
  309. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/draft-ERC20Permit.sol +8 -0
  310. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/draft-IERC20Permit.sol +8 -0
  311. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC20/presets/ERC20PresetFixedSupply.sol +30 -0
  312. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol +94 -0
  313. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC20/presets/README.md +1 -0
  314. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol +143 -0
  315. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC20/utils/TokenTimelock.sol +72 -0
  316. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC721/ERC721.sol +466 -0
  317. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC721/IERC721.sol +132 -0
  318. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC721/IERC721Receiver.sol +27 -0
  319. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC721/README.adoc +73 -0
  320. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721Burnable.sol +26 -0
  321. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721Consecutive.sol +148 -0
  322. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721Enumerable.sol +159 -0
  323. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721Pausable.sol +40 -0
  324. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721Royalty.sol +38 -0
  325. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721URIStorage.sol +74 -0
  326. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721Votes.sol +43 -0
  327. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721Wrapper.sol +97 -0
  328. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC721/extensions/IERC721Enumerable.sol +29 -0
  329. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC721/extensions/IERC721Metadata.sol +27 -0
  330. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC721/extensions/draft-ERC721Votes.sol +9 -0
  331. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC721/presets/ERC721PresetMinterPauserAutoId.sol +132 -0
  332. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC721/presets/README.md +1 -0
  333. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC721/utils/ERC721Holder.sol +23 -0
  334. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC777/ERC777.sol +514 -0
  335. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC777/IERC777.sol +200 -0
  336. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC777/IERC777Recipient.sol +35 -0
  337. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC777/IERC777Sender.sol +35 -0
  338. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC777/README.adoc +32 -0
  339. package/test/contracts/lib/openzeppelin-contracts/contracts/token/ERC777/presets/ERC777PresetFixedSupply.sol +30 -0
  340. package/test/contracts/lib/openzeppelin-contracts/contracts/token/common/ERC2981.sol +107 -0
  341. package/test/contracts/lib/openzeppelin-contracts/contracts/token/common/README.adoc +10 -0
  342. package/test/contracts/lib/openzeppelin-contracts/contracts/utils/Address.sol +244 -0
  343. package/test/contracts/lib/openzeppelin-contracts/contracts/utils/Arrays.sol +105 -0
  344. package/test/contracts/lib/openzeppelin-contracts/contracts/utils/Base64.sol +101 -0
  345. package/test/contracts/lib/openzeppelin-contracts/contracts/utils/Checkpoints.sol +560 -0
  346. package/test/contracts/lib/openzeppelin-contracts/contracts/utils/Context.sol +28 -0
  347. package/test/contracts/lib/openzeppelin-contracts/contracts/utils/Counters.sol +43 -0
  348. package/test/contracts/lib/openzeppelin-contracts/contracts/utils/Create2.sol +75 -0
  349. package/test/contracts/lib/openzeppelin-contracts/contracts/utils/Multicall.sol +39 -0
  350. package/test/contracts/lib/openzeppelin-contracts/contracts/utils/README.adoc +113 -0
  351. package/test/contracts/lib/openzeppelin-contracts/contracts/utils/ShortStrings.sol +122 -0
  352. package/test/contracts/lib/openzeppelin-contracts/contracts/utils/StorageSlot.sol +138 -0
  353. package/test/contracts/lib/openzeppelin-contracts/contracts/utils/Strings.sol +85 -0
  354. package/test/contracts/lib/openzeppelin-contracts/contracts/utils/Timers.sol +75 -0
  355. package/test/contracts/lib/openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol +217 -0
  356. package/test/contracts/lib/openzeppelin-contracts/contracts/utils/cryptography/EIP712.sol +142 -0
  357. package/test/contracts/lib/openzeppelin-contracts/contracts/utils/cryptography/MerkleProof.sol +227 -0
  358. package/test/contracts/lib/openzeppelin-contracts/contracts/utils/cryptography/SignatureChecker.sol +50 -0
  359. package/test/contracts/lib/openzeppelin-contracts/contracts/utils/cryptography/draft-EIP712.sol +8 -0
  360. package/test/contracts/lib/openzeppelin-contracts/contracts/utils/escrow/ConditionalEscrow.sol +25 -0
  361. package/test/contracts/lib/openzeppelin-contracts/contracts/utils/escrow/Escrow.sol +67 -0
  362. package/test/contracts/lib/openzeppelin-contracts/contracts/utils/escrow/RefundEscrow.sol +100 -0
  363. package/test/contracts/lib/openzeppelin-contracts/contracts/utils/introspection/ERC165.sol +29 -0
  364. package/test/contracts/lib/openzeppelin-contracts/contracts/utils/introspection/ERC165Checker.sol +126 -0
  365. package/test/contracts/lib/openzeppelin-contracts/contracts/utils/introspection/ERC165Storage.sol +42 -0
  366. package/test/contracts/lib/openzeppelin-contracts/contracts/utils/introspection/ERC1820Implementer.sol +43 -0
  367. package/test/contracts/lib/openzeppelin-contracts/contracts/utils/introspection/IERC165.sol +25 -0
  368. package/test/contracts/lib/openzeppelin-contracts/contracts/utils/introspection/IERC1820Implementer.sol +20 -0
  369. package/test/contracts/lib/openzeppelin-contracts/contracts/utils/introspection/IERC1820Registry.sol +112 -0
  370. package/test/contracts/lib/openzeppelin-contracts/contracts/utils/math/Math.sol +339 -0
  371. package/test/contracts/lib/openzeppelin-contracts/contracts/utils/math/SafeCast.sol +1136 -0
  372. package/test/contracts/lib/openzeppelin-contracts/contracts/utils/math/SafeMath.sol +215 -0
  373. package/test/contracts/lib/openzeppelin-contracts/contracts/utils/math/SignedMath.sol +43 -0
  374. package/test/contracts/lib/openzeppelin-contracts/contracts/utils/math/SignedSafeMath.sol +68 -0
  375. package/test/contracts/lib/openzeppelin-contracts/contracts/utils/structs/BitMaps.sol +51 -0
  376. package/test/contracts/lib/openzeppelin-contracts/contracts/utils/structs/DoubleEndedQueue.sol +170 -0
  377. package/test/contracts/lib/openzeppelin-contracts/contracts/utils/structs/EnumerableMap.sol +598 -0
  378. package/test/contracts/lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol +378 -0
  379. package/test/contracts/lib/openzeppelin-contracts/contracts/vendor/amb/IAMB.sol +41 -0
  380. package/test/contracts/lib/openzeppelin-contracts/contracts/vendor/arbitrum/IArbSys.sol +134 -0
  381. package/test/contracts/lib/openzeppelin-contracts/contracts/vendor/arbitrum/IBridge.sol +102 -0
  382. package/test/contracts/lib/openzeppelin-contracts/contracts/vendor/arbitrum/IDelayedMessageProvider.sol +16 -0
  383. package/test/contracts/lib/openzeppelin-contracts/contracts/vendor/arbitrum/IInbox.sol +152 -0
  384. package/test/contracts/lib/openzeppelin-contracts/contracts/vendor/arbitrum/IOutbox.sol +117 -0
  385. package/test/contracts/lib/openzeppelin-contracts/contracts/vendor/compound/ICompoundTimelock.sol +86 -0
  386. package/test/contracts/lib/openzeppelin-contracts/contracts/vendor/compound/LICENSE +11 -0
  387. package/test/contracts/lib/openzeppelin-contracts/contracts/vendor/optimism/ICrossDomainMessenger.sol +34 -0
  388. package/test/contracts/lib/openzeppelin-contracts/contracts/vendor/optimism/LICENSE +22 -0
  389. package/test/contracts/lib/openzeppelin-contracts/contracts/vendor/polygon/IFxMessageProcessor.sol +7 -0
  390. package/test/contracts/lib/openzeppelin-contracts/docs/README.md +16 -0
  391. package/test/contracts/lib/openzeppelin-contracts/docs/antora.yml +6 -0
  392. package/test/contracts/lib/openzeppelin-contracts/docs/config.js +21 -0
  393. package/test/contracts/lib/openzeppelin-contracts/docs/modules/ROOT/images/erc4626-attack-3a.png +0 -0
  394. package/test/contracts/lib/openzeppelin-contracts/docs/modules/ROOT/images/erc4626-attack-3b.png +0 -0
  395. package/test/contracts/lib/openzeppelin-contracts/docs/modules/ROOT/images/erc4626-attack-6.png +0 -0
  396. package/test/contracts/lib/openzeppelin-contracts/docs/modules/ROOT/images/erc4626-attack.png +0 -0
  397. package/test/contracts/lib/openzeppelin-contracts/docs/modules/ROOT/images/erc4626-deposit.png +0 -0
  398. package/test/contracts/lib/openzeppelin-contracts/docs/modules/ROOT/images/erc4626-mint.png +0 -0
  399. package/test/contracts/lib/openzeppelin-contracts/docs/modules/ROOT/images/erc4626-rate-linear.png +0 -0
  400. package/test/contracts/lib/openzeppelin-contracts/docs/modules/ROOT/images/erc4626-rate-loglog.png +0 -0
  401. package/test/contracts/lib/openzeppelin-contracts/docs/modules/ROOT/images/erc4626-rate-loglogext.png +0 -0
  402. package/test/contracts/lib/openzeppelin-contracts/docs/modules/ROOT/images/tally-exec.png +0 -0
  403. package/test/contracts/lib/openzeppelin-contracts/docs/modules/ROOT/images/tally-vote.png +0 -0
  404. package/test/contracts/lib/openzeppelin-contracts/docs/modules/ROOT/nav.adoc +23 -0
  405. package/test/contracts/lib/openzeppelin-contracts/docs/modules/ROOT/pages/access-control.adoc +219 -0
  406. package/test/contracts/lib/openzeppelin-contracts/docs/modules/ROOT/pages/crosschain.adoc +210 -0
  407. package/test/contracts/lib/openzeppelin-contracts/docs/modules/ROOT/pages/crowdsales.adoc +11 -0
  408. package/test/contracts/lib/openzeppelin-contracts/docs/modules/ROOT/pages/drafts.adoc +19 -0
  409. package/test/contracts/lib/openzeppelin-contracts/docs/modules/ROOT/pages/erc1155.adoc +153 -0
  410. package/test/contracts/lib/openzeppelin-contracts/docs/modules/ROOT/pages/erc20-supply.adoc +113 -0
  411. package/test/contracts/lib/openzeppelin-contracts/docs/modules/ROOT/pages/erc20.adoc +85 -0
  412. package/test/contracts/lib/openzeppelin-contracts/docs/modules/ROOT/pages/erc4626.adoc +214 -0
  413. package/test/contracts/lib/openzeppelin-contracts/docs/modules/ROOT/pages/erc721.adoc +90 -0
  414. package/test/contracts/lib/openzeppelin-contracts/docs/modules/ROOT/pages/erc777.adoc +75 -0
  415. package/test/contracts/lib/openzeppelin-contracts/docs/modules/ROOT/pages/extending-contracts.adoc +131 -0
  416. package/test/contracts/lib/openzeppelin-contracts/docs/modules/ROOT/pages/governance.adoc +237 -0
  417. package/test/contracts/lib/openzeppelin-contracts/docs/modules/ROOT/pages/index.adoc +65 -0
  418. package/test/contracts/lib/openzeppelin-contracts/docs/modules/ROOT/pages/releases-stability.adoc +85 -0
  419. package/test/contracts/lib/openzeppelin-contracts/docs/modules/ROOT/pages/tokens.adoc +32 -0
  420. package/test/contracts/lib/openzeppelin-contracts/docs/modules/ROOT/pages/upgradeable.adoc +73 -0
  421. package/test/contracts/lib/openzeppelin-contracts/docs/modules/ROOT/pages/utilities.adoc +190 -0
  422. package/test/contracts/lib/openzeppelin-contracts/docs/templates/contract.hbs +85 -0
  423. package/test/contracts/lib/openzeppelin-contracts/docs/templates/helpers.js +46 -0
  424. package/test/contracts/lib/openzeppelin-contracts/docs/templates/page.hbs +4 -0
  425. package/test/contracts/lib/openzeppelin-contracts/docs/templates/properties.js +45 -0
  426. package/test/contracts/lib/openzeppelin-contracts/foundry.toml +3 -0
  427. package/test/contracts/lib/openzeppelin-contracts/hardhat/env-artifacts.js +24 -0
  428. package/test/contracts/lib/openzeppelin-contracts/hardhat/env-contract.js +10 -0
  429. package/test/contracts/lib/openzeppelin-contracts/hardhat/ignore-unreachable-warnings.js +45 -0
  430. package/test/contracts/lib/openzeppelin-contracts/hardhat/skip-foundry-tests.js +6 -0
  431. package/test/contracts/lib/openzeppelin-contracts/hardhat/task-test-get-files.js +25 -0
  432. package/test/contracts/lib/openzeppelin-contracts/hardhat.config.js +118 -0
  433. package/test/contracts/lib/openzeppelin-contracts/lib/erc4626-tests/ERC4626.prop.sol +404 -0
  434. package/test/contracts/lib/openzeppelin-contracts/lib/erc4626-tests/ERC4626.test.sol +349 -0
  435. package/test/contracts/lib/openzeppelin-contracts/lib/erc4626-tests/LICENSE +661 -0
  436. package/test/contracts/lib/openzeppelin-contracts/lib/erc4626-tests/README.md +116 -0
  437. package/test/contracts/lib/openzeppelin-contracts/lib/forge-std/.github/workflows/ci.yml +92 -0
  438. package/test/contracts/lib/openzeppelin-contracts/lib/forge-std/.gitmodules +3 -0
  439. package/test/contracts/lib/openzeppelin-contracts/lib/forge-std/LICENSE-APACHE +203 -0
  440. package/test/contracts/lib/openzeppelin-contracts/lib/forge-std/LICENSE-MIT +25 -0
  441. package/test/contracts/lib/openzeppelin-contracts/lib/forge-std/README.md +250 -0
  442. package/test/contracts/lib/openzeppelin-contracts/lib/forge-std/foundry.toml +21 -0
  443. package/test/contracts/lib/openzeppelin-contracts/lib/forge-std/lib/ds-test/.github/workflows/build.yml +41 -0
  444. package/test/contracts/lib/openzeppelin-contracts/lib/forge-std/lib/ds-test/LICENSE +674 -0
  445. package/test/contracts/lib/openzeppelin-contracts/lib/forge-std/lib/ds-test/Makefile +14 -0
  446. package/test/contracts/lib/openzeppelin-contracts/lib/forge-std/lib/ds-test/default.nix +4 -0
  447. package/test/contracts/lib/openzeppelin-contracts/lib/forge-std/lib/ds-test/demo/demo.sol +222 -0
  448. package/test/contracts/lib/openzeppelin-contracts/lib/forge-std/lib/ds-test/package.json +15 -0
  449. package/test/contracts/lib/openzeppelin-contracts/lib/forge-std/lib/ds-test/src/test.sol +469 -0
  450. package/test/contracts/lib/openzeppelin-contracts/lib/forge-std/lib/ds-test/src/test.t.sol +313 -0
  451. package/test/contracts/lib/openzeppelin-contracts/lib/forge-std/package.json +16 -0
  452. package/test/contracts/lib/openzeppelin-contracts/lib/forge-std/src/Base.sol +33 -0
  453. package/test/contracts/lib/openzeppelin-contracts/lib/forge-std/src/Script.sol +26 -0
  454. package/test/contracts/lib/openzeppelin-contracts/lib/forge-std/src/StdAssertions.sol +376 -0
  455. package/test/contracts/lib/openzeppelin-contracts/lib/forge-std/src/StdChains.sol +233 -0
  456. package/test/contracts/lib/openzeppelin-contracts/lib/forge-std/src/StdCheats.sol +624 -0
  457. package/test/contracts/lib/openzeppelin-contracts/lib/forge-std/src/StdError.sol +15 -0
  458. package/test/contracts/lib/openzeppelin-contracts/lib/forge-std/src/StdInvariant.sol +92 -0
  459. package/test/contracts/lib/openzeppelin-contracts/lib/forge-std/src/StdJson.sol +179 -0
  460. package/test/contracts/lib/openzeppelin-contracts/lib/forge-std/src/StdMath.sol +43 -0
  461. package/test/contracts/lib/openzeppelin-contracts/lib/forge-std/src/StdStorage.sol +327 -0
  462. package/test/contracts/lib/openzeppelin-contracts/lib/forge-std/src/StdStyle.sol +333 -0
  463. package/test/contracts/lib/openzeppelin-contracts/lib/forge-std/src/StdUtils.sol +189 -0
  464. package/test/contracts/lib/openzeppelin-contracts/lib/forge-std/src/Test.sol +32 -0
  465. package/test/contracts/lib/openzeppelin-contracts/lib/forge-std/src/Vm.sol +409 -0
  466. package/test/contracts/lib/openzeppelin-contracts/lib/forge-std/src/console.sol +1533 -0
  467. package/test/contracts/lib/openzeppelin-contracts/lib/forge-std/src/console2.sol +1546 -0
  468. package/test/contracts/lib/openzeppelin-contracts/lib/forge-std/src/interfaces/IERC1155.sol +105 -0
  469. package/test/contracts/lib/openzeppelin-contracts/lib/forge-std/src/interfaces/IERC165.sol +12 -0
  470. package/test/contracts/lib/openzeppelin-contracts/lib/forge-std/src/interfaces/IERC20.sol +43 -0
  471. package/test/contracts/lib/openzeppelin-contracts/lib/forge-std/src/interfaces/IERC4626.sol +190 -0
  472. package/test/contracts/lib/openzeppelin-contracts/lib/forge-std/src/interfaces/IERC721.sol +164 -0
  473. package/test/contracts/lib/openzeppelin-contracts/lib/forge-std/src/interfaces/IMulticall3.sol +73 -0
  474. package/test/contracts/lib/openzeppelin-contracts/lib/forge-std/test/StdAssertions.t.sol +954 -0
  475. package/test/contracts/lib/openzeppelin-contracts/lib/forge-std/test/StdChains.t.sol +160 -0
  476. package/test/contracts/lib/openzeppelin-contracts/lib/forge-std/test/StdCheats.t.sol +401 -0
  477. package/test/contracts/lib/openzeppelin-contracts/lib/forge-std/test/StdError.t.sol +118 -0
  478. package/test/contracts/lib/openzeppelin-contracts/lib/forge-std/test/StdMath.t.sol +197 -0
  479. package/test/contracts/lib/openzeppelin-contracts/lib/forge-std/test/StdStorage.t.sol +283 -0
  480. package/test/contracts/lib/openzeppelin-contracts/lib/forge-std/test/StdStyle.t.sol +110 -0
  481. package/test/contracts/lib/openzeppelin-contracts/lib/forge-std/test/StdUtils.t.sol +297 -0
  482. package/test/contracts/lib/openzeppelin-contracts/lib/forge-std/test/compilation/CompilationScript.sol +10 -0
  483. package/test/contracts/lib/openzeppelin-contracts/lib/forge-std/test/compilation/CompilationScriptBase.sol +10 -0
  484. package/test/contracts/lib/openzeppelin-contracts/lib/forge-std/test/compilation/CompilationTest.sol +10 -0
  485. package/test/contracts/lib/openzeppelin-contracts/lib/forge-std/test/compilation/CompilationTestBase.sol +10 -0
  486. package/test/contracts/lib/openzeppelin-contracts/lib/forge-std/test/fixtures/broadcast.log.json +187 -0
  487. package/test/contracts/lib/openzeppelin-contracts/logo.svg +15 -0
  488. package/test/contracts/lib/openzeppelin-contracts/netlify.toml +3 -0
  489. package/test/contracts/lib/openzeppelin-contracts/package-lock.json +28833 -0
  490. package/test/contracts/lib/openzeppelin-contracts/package.json +96 -0
  491. package/test/contracts/lib/openzeppelin-contracts/remappings.txt +1 -0
  492. package/test/contracts/lib/openzeppelin-contracts/renovate.json +4 -0
  493. package/test/contracts/lib/openzeppelin-contracts/requirements.txt +1 -0
  494. package/test/contracts/lib/openzeppelin-contracts/scripts/checks/compare-layout.js +19 -0
  495. package/test/contracts/lib/openzeppelin-contracts/scripts/checks/compareGasReports.js +243 -0
  496. package/test/contracts/lib/openzeppelin-contracts/scripts/checks/extract-layout.js +40 -0
  497. package/test/contracts/lib/openzeppelin-contracts/scripts/checks/generation.sh +6 -0
  498. package/test/contracts/lib/openzeppelin-contracts/scripts/checks/inheritance-ordering.js +54 -0
  499. package/test/contracts/lib/openzeppelin-contracts/scripts/gen-nav.js +41 -0
  500. package/test/contracts/lib/openzeppelin-contracts/scripts/generate/format-lines.js +16 -0
  501. package/test/contracts/lib/openzeppelin-contracts/scripts/generate/run.js +49 -0
  502. package/test/contracts/lib/openzeppelin-contracts/scripts/generate/templates/Checkpoints.js +304 -0
  503. package/test/contracts/lib/openzeppelin-contracts/scripts/generate/templates/Checkpoints.opts.js +22 -0
  504. package/test/contracts/lib/openzeppelin-contracts/scripts/generate/templates/Checkpoints.t.js +256 -0
  505. package/test/contracts/lib/openzeppelin-contracts/scripts/generate/templates/EnumerableMap.js +310 -0
  506. package/test/contracts/lib/openzeppelin-contracts/scripts/generate/templates/EnumerableSet.js +250 -0
  507. package/test/contracts/lib/openzeppelin-contracts/scripts/generate/templates/SafeCast.js +163 -0
  508. package/test/contracts/lib/openzeppelin-contracts/scripts/generate/templates/StorageSlot.js +87 -0
  509. package/test/contracts/lib/openzeppelin-contracts/scripts/generate/templates/conversion.js +30 -0
  510. package/test/contracts/lib/openzeppelin-contracts/scripts/git-user-config.sh +6 -0
  511. package/test/contracts/lib/openzeppelin-contracts/scripts/helpers.js +37 -0
  512. package/test/contracts/lib/openzeppelin-contracts/scripts/migrate-imports.js +180 -0
  513. package/test/contracts/lib/openzeppelin-contracts/scripts/prepack.sh +12 -0
  514. package/test/contracts/lib/openzeppelin-contracts/scripts/prepare-contracts-package.sh +15 -0
  515. package/test/contracts/lib/openzeppelin-contracts/scripts/prepare-docs.sh +26 -0
  516. package/test/contracts/lib/openzeppelin-contracts/scripts/prepare.sh +10 -0
  517. package/test/contracts/lib/openzeppelin-contracts/scripts/release/format-changelog.js +33 -0
  518. package/test/contracts/lib/openzeppelin-contracts/scripts/release/synchronize-versions.js +15 -0
  519. package/test/contracts/lib/openzeppelin-contracts/scripts/release/update-comment.js +34 -0
  520. package/test/contracts/lib/openzeppelin-contracts/scripts/release/version.sh +11 -0
  521. package/test/contracts/lib/openzeppelin-contracts/scripts/release/workflow/exit-prerelease.sh +8 -0
  522. package/test/contracts/lib/openzeppelin-contracts/scripts/release/workflow/github-release.js +47 -0
  523. package/test/contracts/lib/openzeppelin-contracts/scripts/release/workflow/integrity-check.sh +20 -0
  524. package/test/contracts/lib/openzeppelin-contracts/scripts/release/workflow/pack.sh +26 -0
  525. package/test/contracts/lib/openzeppelin-contracts/scripts/release/workflow/publish.sh +26 -0
  526. package/test/contracts/lib/openzeppelin-contracts/scripts/release/workflow/rerun.js +7 -0
  527. package/test/contracts/lib/openzeppelin-contracts/scripts/release/workflow/set-changesets-pr-title.js +17 -0
  528. package/test/contracts/lib/openzeppelin-contracts/scripts/release/workflow/start.sh +35 -0
  529. package/test/contracts/lib/openzeppelin-contracts/scripts/release/workflow/state.js +112 -0
  530. package/test/contracts/lib/openzeppelin-contracts/scripts/remove-ignored-artifacts.js +45 -0
  531. package/test/contracts/lib/openzeppelin-contracts/scripts/update-docs-branch.js +63 -0
  532. package/test/contracts/lib/openzeppelin-contracts/scripts/upgradeable/README.md +21 -0
  533. package/test/contracts/lib/openzeppelin-contracts/scripts/upgradeable/patch-apply.sh +19 -0
  534. package/test/contracts/lib/openzeppelin-contracts/scripts/upgradeable/patch-save.sh +18 -0
  535. package/test/contracts/lib/openzeppelin-contracts/scripts/upgradeable/transpile-onto.sh +44 -0
  536. package/test/contracts/lib/openzeppelin-contracts/scripts/upgradeable/transpile.sh +35 -0
  537. package/test/contracts/lib/openzeppelin-contracts/scripts/upgradeable/upgradeable.patch +481 -0
  538. package/test/contracts/lib/openzeppelin-contracts/slither.config.json +5 -0
  539. package/test/contracts/lib/openzeppelin-contracts/test/TESTING.md +3 -0
  540. package/test/contracts/lib/openzeppelin-contracts/test/access/AccessControl.behavior.js +867 -0
  541. package/test/contracts/lib/openzeppelin-contracts/test/access/AccessControl.test.js +12 -0
  542. package/test/contracts/lib/openzeppelin-contracts/test/access/AccessControlCrossChain.test.js +49 -0
  543. package/test/contracts/lib/openzeppelin-contracts/test/access/AccessControlDefaultAdminRules.test.js +25 -0
  544. package/test/contracts/lib/openzeppelin-contracts/test/access/AccessControlEnumerable.test.js +17 -0
  545. package/test/contracts/lib/openzeppelin-contracts/test/access/Ownable.test.js +59 -0
  546. package/test/contracts/lib/openzeppelin-contracts/test/access/Ownable2Step.test.js +67 -0
  547. package/test/contracts/lib/openzeppelin-contracts/test/crosschain/CrossChainEnabled.test.js +78 -0
  548. package/test/contracts/lib/openzeppelin-contracts/test/finance/PaymentSplitter.test.js +217 -0
  549. package/test/contracts/lib/openzeppelin-contracts/test/finance/VestingWallet.behavior.js +59 -0
  550. package/test/contracts/lib/openzeppelin-contracts/test/finance/VestingWallet.test.js +67 -0
  551. package/test/contracts/lib/openzeppelin-contracts/test/governance/Governor.t.sol +55 -0
  552. package/test/contracts/lib/openzeppelin-contracts/test/governance/Governor.test.js +782 -0
  553. package/test/contracts/lib/openzeppelin-contracts/test/governance/TimelockController.test.js +1254 -0
  554. package/test/contracts/lib/openzeppelin-contracts/test/governance/compatibility/GovernorCompatibilityBravo.test.js +283 -0
  555. package/test/contracts/lib/openzeppelin-contracts/test/governance/extensions/GovernorComp.test.js +88 -0
  556. package/test/contracts/lib/openzeppelin-contracts/test/governance/extensions/GovernorERC721.test.js +115 -0
  557. package/test/contracts/lib/openzeppelin-contracts/test/governance/extensions/GovernorPreventLateQuorum.test.js +189 -0
  558. package/test/contracts/lib/openzeppelin-contracts/test/governance/extensions/GovernorTimelockCompound.test.js +352 -0
  559. package/test/contracts/lib/openzeppelin-contracts/test/governance/extensions/GovernorTimelockControl.test.js +445 -0
  560. package/test/contracts/lib/openzeppelin-contracts/test/governance/extensions/GovernorVotesQuorumFraction.test.js +154 -0
  561. package/test/contracts/lib/openzeppelin-contracts/test/governance/extensions/GovernorWithParams.test.js +173 -0
  562. package/test/contracts/lib/openzeppelin-contracts/test/governance/utils/EIP6372.behavior.js +23 -0
  563. package/test/contracts/lib/openzeppelin-contracts/test/governance/utils/Votes.behavior.js +361 -0
  564. package/test/contracts/lib/openzeppelin-contracts/test/governance/utils/Votes.test.js +71 -0
  565. package/test/contracts/lib/openzeppelin-contracts/test/helpers/chainid.js +10 -0
  566. package/test/contracts/lib/openzeppelin-contracts/test/helpers/create2.js +11 -0
  567. package/test/contracts/lib/openzeppelin-contracts/test/helpers/crosschain.js +61 -0
  568. package/test/contracts/lib/openzeppelin-contracts/test/helpers/customError.js +24 -0
  569. package/test/contracts/lib/openzeppelin-contracts/test/helpers/eip712.js +67 -0
  570. package/test/contracts/lib/openzeppelin-contracts/test/helpers/enums.js +12 -0
  571. package/test/contracts/lib/openzeppelin-contracts/test/helpers/erc1967.js +24 -0
  572. package/test/contracts/lib/openzeppelin-contracts/test/helpers/governance.js +201 -0
  573. package/test/contracts/lib/openzeppelin-contracts/test/helpers/map-values.js +7 -0
  574. package/test/contracts/lib/openzeppelin-contracts/test/helpers/sign.js +63 -0
  575. package/test/contracts/lib/openzeppelin-contracts/test/helpers/time.js +17 -0
  576. package/test/contracts/lib/openzeppelin-contracts/test/helpers/txpool.js +38 -0
  577. package/test/contracts/lib/openzeppelin-contracts/test/metatx/ERC2771Context.test.js +175 -0
  578. package/test/contracts/lib/openzeppelin-contracts/test/metatx/MinimalForwarder.test.js +169 -0
  579. package/test/contracts/lib/openzeppelin-contracts/test/migrate-imports.test.js +33 -0
  580. package/test/contracts/lib/openzeppelin-contracts/test/proxy/Clones.behaviour.js +136 -0
  581. package/test/contracts/lib/openzeppelin-contracts/test/proxy/Clones.test.js +61 -0
  582. package/test/contracts/lib/openzeppelin-contracts/test/proxy/ERC1967/ERC1967Proxy.test.js +13 -0
  583. package/test/contracts/lib/openzeppelin-contracts/test/proxy/Proxy.behaviour.js +225 -0
  584. package/test/contracts/lib/openzeppelin-contracts/test/proxy/beacon/BeaconProxy.test.js +139 -0
  585. package/test/contracts/lib/openzeppelin-contracts/test/proxy/beacon/UpgradeableBeacon.test.js +44 -0
  586. package/test/contracts/lib/openzeppelin-contracts/test/proxy/transparent/ProxyAdmin.test.js +127 -0
  587. package/test/contracts/lib/openzeppelin-contracts/test/proxy/transparent/TransparentUpgradeableProxy.behaviour.js +433 -0
  588. package/test/contracts/lib/openzeppelin-contracts/test/proxy/transparent/TransparentUpgradeableProxy.test.js +17 -0
  589. package/test/contracts/lib/openzeppelin-contracts/test/proxy/utils/Initializable.test.js +218 -0
  590. package/test/contracts/lib/openzeppelin-contracts/test/proxy/utils/UUPSUpgradeable.test.js +85 -0
  591. package/test/contracts/lib/openzeppelin-contracts/test/security/Pausable.test.js +85 -0
  592. package/test/contracts/lib/openzeppelin-contracts/test/security/PullPayment.test.js +51 -0
  593. package/test/contracts/lib/openzeppelin-contracts/test/security/ReentrancyGuard.test.js +43 -0
  594. package/test/contracts/lib/openzeppelin-contracts/test/token/ERC1155/ERC1155.behavior.js +767 -0
  595. package/test/contracts/lib/openzeppelin-contracts/test/token/ERC1155/ERC1155.test.js +235 -0
  596. package/test/contracts/lib/openzeppelin-contracts/test/token/ERC1155/extensions/ERC1155Burnable.test.js +67 -0
  597. package/test/contracts/lib/openzeppelin-contracts/test/token/ERC1155/extensions/ERC1155Pausable.test.js +108 -0
  598. package/test/contracts/lib/openzeppelin-contracts/test/token/ERC1155/extensions/ERC1155Supply.test.js +107 -0
  599. package/test/contracts/lib/openzeppelin-contracts/test/token/ERC1155/extensions/ERC1155URIStorage.test.js +66 -0
  600. package/test/contracts/lib/openzeppelin-contracts/test/token/ERC1155/presets/ERC1155PresetMinterPauser.test.js +156 -0
  601. package/test/contracts/lib/openzeppelin-contracts/test/token/ERC1155/utils/ERC1155Holder.test.js +64 -0
  602. package/test/contracts/lib/openzeppelin-contracts/test/token/ERC20/ERC20.behavior.js +322 -0
  603. package/test/contracts/lib/openzeppelin-contracts/test/token/ERC20/ERC20.test.js +305 -0
  604. package/test/contracts/lib/openzeppelin-contracts/test/token/ERC20/extensions/ERC20Burnable.behavior.js +106 -0
  605. package/test/contracts/lib/openzeppelin-contracts/test/token/ERC20/extensions/ERC20Burnable.test.js +20 -0
  606. package/test/contracts/lib/openzeppelin-contracts/test/token/ERC20/extensions/ERC20Capped.behavior.js +32 -0
  607. package/test/contracts/lib/openzeppelin-contracts/test/token/ERC20/extensions/ERC20Capped.test.js +23 -0
  608. package/test/contracts/lib/openzeppelin-contracts/test/token/ERC20/extensions/ERC20FlashMint.test.js +204 -0
  609. package/test/contracts/lib/openzeppelin-contracts/test/token/ERC20/extensions/ERC20Pausable.test.js +133 -0
  610. package/test/contracts/lib/openzeppelin-contracts/test/token/ERC20/extensions/ERC20Snapshot.test.js +207 -0
  611. package/test/contracts/lib/openzeppelin-contracts/test/token/ERC20/extensions/ERC20Votes.test.js +578 -0
  612. package/test/contracts/lib/openzeppelin-contracts/test/token/ERC20/extensions/ERC20VotesComp.test.js +543 -0
  613. package/test/contracts/lib/openzeppelin-contracts/test/token/ERC20/extensions/ERC20Wrapper.test.js +190 -0
  614. package/test/contracts/lib/openzeppelin-contracts/test/token/ERC20/extensions/ERC4626.t.sol +42 -0
  615. package/test/contracts/lib/openzeppelin-contracts/test/token/ERC20/extensions/ERC4626.test.js +1031 -0
  616. package/test/contracts/lib/openzeppelin-contracts/test/token/ERC20/extensions/draft-ERC20Permit.test.js +103 -0
  617. package/test/contracts/lib/openzeppelin-contracts/test/token/ERC20/presets/ERC20PresetFixedSupply.test.js +42 -0
  618. package/test/contracts/lib/openzeppelin-contracts/test/token/ERC20/presets/ERC20PresetMinterPauser.test.js +110 -0
  619. package/test/contracts/lib/openzeppelin-contracts/test/token/ERC20/utils/SafeERC20.test.js +350 -0
  620. package/test/contracts/lib/openzeppelin-contracts/test/token/ERC20/utils/TokenTimelock.test.js +71 -0
  621. package/test/contracts/lib/openzeppelin-contracts/test/token/ERC721/ERC721.behavior.js +893 -0
  622. package/test/contracts/lib/openzeppelin-contracts/test/token/ERC721/ERC721.test.js +15 -0
  623. package/test/contracts/lib/openzeppelin-contracts/test/token/ERC721/ERC721Enumerable.test.js +20 -0
  624. package/test/contracts/lib/openzeppelin-contracts/test/token/ERC721/extensions/ERC721Burnable.test.js +70 -0
  625. package/test/contracts/lib/openzeppelin-contracts/test/token/ERC721/extensions/ERC721Consecutive.t.sol +122 -0
  626. package/test/contracts/lib/openzeppelin-contracts/test/token/ERC721/extensions/ERC721Consecutive.test.js +206 -0
  627. package/test/contracts/lib/openzeppelin-contracts/test/token/ERC721/extensions/ERC721Pausable.test.js +92 -0
  628. package/test/contracts/lib/openzeppelin-contracts/test/token/ERC721/extensions/ERC721Royalty.test.js +41 -0
  629. package/test/contracts/lib/openzeppelin-contracts/test/token/ERC721/extensions/ERC721URIStorage.test.js +100 -0
  630. package/test/contracts/lib/openzeppelin-contracts/test/token/ERC721/extensions/ERC721Votes.test.js +184 -0
  631. package/test/contracts/lib/openzeppelin-contracts/test/token/ERC721/extensions/ERC721Wrapper.test.js +283 -0
  632. package/test/contracts/lib/openzeppelin-contracts/test/token/ERC721/presets/ERC721PresetMinterPauserAutoId.test.js +122 -0
  633. package/test/contracts/lib/openzeppelin-contracts/test/token/ERC721/utils/ERC721Holder.test.js +22 -0
  634. package/test/contracts/lib/openzeppelin-contracts/test/token/ERC777/ERC777.behavior.js +597 -0
  635. package/test/contracts/lib/openzeppelin-contracts/test/token/ERC777/ERC777.test.js +556 -0
  636. package/test/contracts/lib/openzeppelin-contracts/test/token/ERC777/presets/ERC777PresetFixedSupply.test.js +49 -0
  637. package/test/contracts/lib/openzeppelin-contracts/test/token/common/ERC2981.behavior.js +157 -0
  638. package/test/contracts/lib/openzeppelin-contracts/test/utils/Address.test.js +361 -0
  639. package/test/contracts/lib/openzeppelin-contracts/test/utils/Arrays.test.js +123 -0
  640. package/test/contracts/lib/openzeppelin-contracts/test/utils/Base64.test.js +43 -0
  641. package/test/contracts/lib/openzeppelin-contracts/test/utils/Checkpoints.t.sol +347 -0
  642. package/test/contracts/lib/openzeppelin-contracts/test/utils/Checkpoints.test.js +255 -0
  643. package/test/contracts/lib/openzeppelin-contracts/test/utils/Context.behavior.js +42 -0
  644. package/test/contracts/lib/openzeppelin-contracts/test/utils/Context.test.js +17 -0
  645. package/test/contracts/lib/openzeppelin-contracts/test/utils/Counters.test.js +84 -0
  646. package/test/contracts/lib/openzeppelin-contracts/test/utils/Create2.test.js +89 -0
  647. package/test/contracts/lib/openzeppelin-contracts/test/utils/Multicall.test.js +68 -0
  648. package/test/contracts/lib/openzeppelin-contracts/test/utils/ShortStrings.t.sol +55 -0
  649. package/test/contracts/lib/openzeppelin-contracts/test/utils/ShortStrings.test.js +55 -0
  650. package/test/contracts/lib/openzeppelin-contracts/test/utils/StorageSlot.test.js +210 -0
  651. package/test/contracts/lib/openzeppelin-contracts/test/utils/Strings.test.js +150 -0
  652. package/test/contracts/lib/openzeppelin-contracts/test/utils/TimersBlockNumberImpl.test.js +55 -0
  653. package/test/contracts/lib/openzeppelin-contracts/test/utils/TimersTimestamp.test.js +55 -0
  654. package/test/contracts/lib/openzeppelin-contracts/test/utils/cryptography/ECDSA.test.js +260 -0
  655. package/test/contracts/lib/openzeppelin-contracts/test/utils/cryptography/EIP712.test.js +103 -0
  656. package/test/contracts/lib/openzeppelin-contracts/test/utils/cryptography/MerkleProof.test.js +200 -0
  657. package/test/contracts/lib/openzeppelin-contracts/test/utils/cryptography/SignatureChecker.test.js +87 -0
  658. package/test/contracts/lib/openzeppelin-contracts/test/utils/escrow/ConditionalEscrow.test.js +37 -0
  659. package/test/contracts/lib/openzeppelin-contracts/test/utils/escrow/Escrow.behavior.js +90 -0
  660. package/test/contracts/lib/openzeppelin-contracts/test/utils/escrow/Escrow.test.js +14 -0
  661. package/test/contracts/lib/openzeppelin-contracts/test/utils/escrow/RefundEscrow.test.js +143 -0
  662. package/test/contracts/lib/openzeppelin-contracts/test/utils/introspection/ERC165.test.js +11 -0
  663. package/test/contracts/lib/openzeppelin-contracts/test/utils/introspection/ERC165Checker.test.js +302 -0
  664. package/test/contracts/lib/openzeppelin-contracts/test/utils/introspection/ERC165Storage.test.js +23 -0
  665. package/test/contracts/lib/openzeppelin-contracts/test/utils/introspection/ERC1820Implementer.test.js +71 -0
  666. package/test/contracts/lib/openzeppelin-contracts/test/utils/introspection/SupportsInterface.behavior.js +146 -0
  667. package/test/contracts/lib/openzeppelin-contracts/test/utils/math/Math.t.sol +217 -0
  668. package/test/contracts/lib/openzeppelin-contracts/test/utils/math/Math.test.js +289 -0
  669. package/test/contracts/lib/openzeppelin-contracts/test/utils/math/SafeCast.test.js +152 -0
  670. package/test/contracts/lib/openzeppelin-contracts/test/utils/math/SafeMath.test.js +433 -0
  671. package/test/contracts/lib/openzeppelin-contracts/test/utils/math/SignedMath.test.js +95 -0
  672. package/test/contracts/lib/openzeppelin-contracts/test/utils/math/SignedSafeMath.test.js +152 -0
  673. package/test/contracts/lib/openzeppelin-contracts/test/utils/structs/BitMap.test.js +145 -0
  674. package/test/contracts/lib/openzeppelin-contracts/test/utils/structs/DoubleEndedQueue.test.js +99 -0
  675. package/test/contracts/lib/openzeppelin-contracts/test/utils/structs/EnumerableMap.behavior.js +185 -0
  676. package/test/contracts/lib/openzeppelin-contracts/test/utils/structs/EnumerableMap.test.js +154 -0
  677. package/test/contracts/lib/openzeppelin-contracts/test/utils/structs/EnumerableSet.behavior.js +129 -0
  678. package/test/contracts/lib/openzeppelin-contracts/test/utils/structs/EnumerableSet.test.js +79 -0
  679. package/test/contracts/out/AccessControl.sol/AccessControl.json +1 -0
  680. package/test/contracts/out/AccessControlCrossChain.sol/AccessControlCrossChain.json +1 -0
  681. package/test/contracts/out/AccessControlCrossChainMock.sol/AccessControlCrossChainMock.json +1 -0
  682. package/test/contracts/out/AccessControlDefaultAdminRules.sol/AccessControlDefaultAdminRules.json +1 -0
  683. package/test/contracts/out/AccessControlEnumerable.sol/AccessControlEnumerable.json +1 -0
  684. package/test/contracts/out/Address.sol/Address.json +1 -0
  685. package/test/contracts/out/Arrays.sol/Arrays.json +1 -0
  686. package/test/contracts/out/ArraysMock.sol/AddressArraysMock.json +1 -0
  687. package/test/contracts/out/ArraysMock.sol/Bytes32ArraysMock.json +1 -0
  688. package/test/contracts/out/ArraysMock.sol/Uint256ArraysMock.json +1 -0
  689. package/test/contracts/out/BadBeacon.sol/BadBeaconNoImpl.json +1 -0
  690. package/test/contracts/out/BadBeacon.sol/BadBeaconNotContract.json +1 -0
  691. package/test/contracts/out/Base.sol/CommonBase.json +1 -0
  692. package/test/contracts/out/Base.sol/ScriptBase.json +1 -0
  693. package/test/contracts/out/Base.sol/TestBase.json +1 -0
  694. package/test/contracts/out/Base64.sol/Base64.json +1 -0
  695. package/test/contracts/out/Base64Dirty.sol/Base64Dirty.json +1 -0
  696. package/test/contracts/out/BeaconProxy.sol/BeaconProxy.json +1 -0
  697. package/test/contracts/out/BitMaps.sol/BitMaps.json +1 -0
  698. package/test/contracts/out/CallReceiverMock.sol/CallReceiverMock.json +1 -0
  699. package/test/contracts/out/Checkpoints.sol/Checkpoints.json +1 -0
  700. package/test/contracts/out/Checkpoints.t.sol/CheckpointsHistoryTest.json +1 -0
  701. package/test/contracts/out/Checkpoints.t.sol/CheckpointsTrace160Test.json +1 -0
  702. package/test/contracts/out/Checkpoints.t.sol/CheckpointsTrace224Test.json +1 -0
  703. package/test/contracts/out/ClashingImplementation.sol/ClashingImplementation.json +1 -0
  704. package/test/contracts/out/Clones.sol/Clones.json +1 -0
  705. package/test/contracts/out/CompTimelock.sol/CompTimelock.json +1 -0
  706. package/test/contracts/out/CompilationScript.sol/CompilationScript.json +1 -0
  707. package/test/contracts/out/CompilationScriptBase.sol/CompilationScriptBase.json +1 -0
  708. package/test/contracts/out/CompilationTest.sol/CompilationTest.json +1 -0
  709. package/test/contracts/out/CompilationTestBase.sol/CompilationTestBase.json +1 -0
  710. package/test/contracts/out/ConditionalEscrow.sol/ConditionalEscrow.json +1 -0
  711. package/test/contracts/out/ConditionalEscrowMock.sol/ConditionalEscrowMock.json +1 -0
  712. package/test/contracts/out/Context.sol/Context.json +1 -0
  713. package/test/contracts/out/ContextMock.sol/ContextMock.json +1 -0
  714. package/test/contracts/out/ContextMock.sol/ContextMockCaller.json +1 -0
  715. package/test/contracts/out/Counters.sol/Counters.json +1 -0
  716. package/test/contracts/out/Create2.sol/Create2.json +1 -0
  717. package/test/contracts/out/CrossChainEnabled.sol/CrossChainEnabled.json +1 -0
  718. package/test/contracts/out/CrossChainEnabledAMB.sol/CrossChainEnabledAMB.json +1 -0
  719. package/test/contracts/out/CrossChainEnabledArbitrumL1.sol/CrossChainEnabledArbitrumL1.json +1 -0
  720. package/test/contracts/out/CrossChainEnabledArbitrumL2.sol/CrossChainEnabledArbitrumL2.json +1 -0
  721. package/test/contracts/out/CrossChainEnabledOptimism.sol/CrossChainEnabledOptimism.json +1 -0
  722. package/test/contracts/out/CrossChainEnabledPolygonChild.sol/CrossChainEnabledPolygonChild.json +1 -0
  723. package/test/contracts/out/DoubleEndedQueue.sol/DoubleEndedQueue.json +1 -0
  724. package/test/contracts/out/DummyImplementation.sol/DummyImplementation.json +1 -0
  725. package/test/contracts/out/DummyImplementation.sol/DummyImplementationV2.json +1 -0
  726. package/test/contracts/out/DummyImplementation.sol/Impl.json +1 -0
  727. package/test/contracts/out/ECDSA.sol/ECDSA.json +1 -0
  728. package/test/contracts/out/EIP712.sol/EIP712.json +1 -0
  729. package/test/contracts/out/EIP712Verifier.sol/EIP712Verifier.json +1 -0
  730. package/test/contracts/out/ERC1155.sol/ERC1155.json +1 -0
  731. package/test/contracts/out/ERC1155Burnable.sol/ERC1155Burnable.json +1 -0
  732. package/test/contracts/out/ERC1155Holder.sol/ERC1155Holder.json +1 -0
  733. package/test/contracts/out/ERC1155Pausable.sol/ERC1155Pausable.json +1 -0
  734. package/test/contracts/out/ERC1155PresetMinterPauser.sol/ERC1155PresetMinterPauser.json +1 -0
  735. package/test/contracts/out/ERC1155Receiver.sol/ERC1155Receiver.json +1 -0
  736. package/test/contracts/out/ERC1155ReceiverMock.sol/ERC1155ReceiverMock.json +1 -0
  737. package/test/contracts/out/ERC1155Supply.sol/ERC1155Supply.json +1 -0
  738. package/test/contracts/out/ERC1155URIStorage.sol/ERC1155URIStorage.json +1 -0
  739. package/test/contracts/out/ERC1271WalletMock.sol/ERC1271MaliciousMock.json +1 -0
  740. package/test/contracts/out/ERC1271WalletMock.sol/ERC1271WalletMock.json +1 -0
  741. package/test/contracts/out/ERC165.sol/ERC165.json +1 -0
  742. package/test/contracts/out/ERC165Checker.sol/ERC165Checker.json +1 -0
  743. package/test/contracts/out/ERC165MaliciousData.sol/ERC165MaliciousData.json +1 -0
  744. package/test/contracts/out/ERC165MissingData.sol/ERC165MissingData.json +1 -0
  745. package/test/contracts/out/ERC165NotSupported.sol/ERC165NotSupported.json +1 -0
  746. package/test/contracts/out/ERC165ReturnBomb.sol/ERC165ReturnBombMock.json +1 -0
  747. package/test/contracts/out/ERC165Storage.sol/ERC165Storage.json +1 -0
  748. package/test/contracts/out/ERC1820Implementer.sol/ERC1820Implementer.json +1 -0
  749. package/test/contracts/out/ERC1967Proxy.sol/ERC1967Proxy.json +1 -0
  750. package/test/contracts/out/ERC1967Upgrade.sol/ERC1967Upgrade.json +1 -0
  751. package/test/contracts/out/ERC20.sol/ERC20.json +1 -0
  752. package/test/contracts/out/ERC20Burnable.sol/ERC20Burnable.json +1 -0
  753. package/test/contracts/out/ERC20Capped.sol/ERC20Capped.json +1 -0
  754. package/test/contracts/out/ERC20DecimalsMock.sol/ERC20DecimalsMock.json +1 -0
  755. package/test/contracts/out/ERC20ExcessDecimalsMock.sol/ERC20ExcessDecimalsMock.json +1 -0
  756. package/test/contracts/out/ERC20FlashMint.sol/ERC20FlashMint.json +1 -0
  757. package/test/contracts/out/ERC20FlashMintMock.sol/ERC20FlashMintMock.json +1 -0
  758. package/test/contracts/out/ERC20ForceApproveMock.sol/ERC20ForceApproveMock.json +1 -0
  759. package/test/contracts/out/ERC20Mock.sol/ERC20Mock.json +1 -0
  760. package/test/contracts/out/ERC20MulticallMock.sol/ERC20MulticallMock.json +1 -0
  761. package/test/contracts/out/ERC20NoReturnMock.sol/ERC20NoReturnMock.json +1 -0
  762. package/test/contracts/out/ERC20Pausable.sol/ERC20Pausable.json +1 -0
  763. package/test/contracts/out/ERC20Permit.sol/ERC20Permit.json +1 -0
  764. package/test/contracts/out/ERC20PermitNoRevertMock.sol/ERC20PermitNoRevertMock.json +1 -0
  765. package/test/contracts/out/ERC20PresetFixedSupply.sol/ERC20PresetFixedSupply.json +1 -0
  766. package/test/contracts/out/ERC20PresetMinterPauser.sol/ERC20PresetMinterPauser.json +1 -0
  767. package/test/contracts/out/ERC20Reentrant.sol/ERC20Reentrant.json +1 -0
  768. package/test/contracts/out/ERC20ReturnFalseMock.sol/ERC20ReturnFalseMock.json +1 -0
  769. package/test/contracts/out/ERC20Snapshot.sol/ERC20Snapshot.json +1 -0
  770. package/test/contracts/out/ERC20Votes.sol/ERC20Votes.json +1 -0
  771. package/test/contracts/out/ERC20VotesComp.sol/ERC20VotesComp.json +1 -0
  772. package/test/contracts/out/ERC20VotesLegacyMock.sol/ERC20VotesLegacyMock.json +1 -0
  773. package/test/contracts/out/ERC20Wrapper.sol/ERC20Wrapper.json +1 -0
  774. package/test/contracts/out/ERC2771Context.sol/ERC2771Context.json +1 -0
  775. package/test/contracts/out/ERC2771ContextMock.sol/ERC2771ContextMock.json +1 -0
  776. package/test/contracts/out/ERC2981.sol/ERC2981.json +1 -0
  777. package/test/contracts/out/ERC3156FlashBorrowerMock.sol/ERC3156FlashBorrowerMock.json +1 -0
  778. package/test/contracts/out/ERC4626.prop.sol/ERC4626Prop.json +1 -0
  779. package/test/contracts/out/ERC4626.prop.sol/IERC20.json +1 -0
  780. package/test/contracts/out/ERC4626.prop.sol/IERC4626.json +1 -0
  781. package/test/contracts/out/ERC4626.sol/ERC4626.json +1 -0
  782. package/test/contracts/out/ERC4626.t.sol/ERC4626StdTest.json +1 -0
  783. package/test/contracts/out/ERC4626.t.sol/ERC4626VaultOffsetMock.json +1 -0
  784. package/test/contracts/out/ERC4626.test.sol/ERC4626Test.json +1 -0
  785. package/test/contracts/out/ERC4626.test.sol/IMockERC20.json +1 -0
  786. package/test/contracts/out/ERC4626Fees.sol/ERC4626Fees.json +1 -0
  787. package/test/contracts/out/ERC4626Mock.sol/ERC4626Mock.json +1 -0
  788. package/test/contracts/out/ERC4626OffsetMock.sol/ERC4626OffsetMock.json +1 -0
  789. package/test/contracts/out/ERC4646FeesMock.sol/ERC4626FeesMock.json +1 -0
  790. package/test/contracts/out/ERC721.sol/ERC721.json +1 -0
  791. package/test/contracts/out/ERC721Burnable.sol/ERC721Burnable.json +1 -0
  792. package/test/contracts/out/ERC721Consecutive.sol/ERC721Consecutive.json +1 -0
  793. package/test/contracts/out/ERC721Consecutive.t.sol/ERC721ConsecutiveTarget.json +1 -0
  794. package/test/contracts/out/ERC721Consecutive.t.sol/ERC721ConsecutiveTest.json +1 -0
  795. package/test/contracts/out/ERC721ConsecutiveEnumerableMock.sol/ERC721ConsecutiveEnumerableMock.json +1 -0
  796. package/test/contracts/out/ERC721ConsecutiveMock.sol/ERC721ConsecutiveMock.json +1 -0
  797. package/test/contracts/out/ERC721ConsecutiveMock.sol/ERC721ConsecutiveNoConstructorMintMock.json +1 -0
  798. package/test/contracts/out/ERC721Enumerable.sol/ERC721Enumerable.json +1 -0
  799. package/test/contracts/out/ERC721Holder.sol/ERC721Holder.json +1 -0
  800. package/test/contracts/out/ERC721Pausable.sol/ERC721Pausable.json +1 -0
  801. package/test/contracts/out/ERC721PresetMinterPauserAutoId.sol/ERC721PresetMinterPauserAutoId.json +1 -0
  802. package/test/contracts/out/ERC721ReceiverMock.sol/ERC721ReceiverMock.json +1 -0
  803. package/test/contracts/out/ERC721Royalty.sol/ERC721Royalty.json +1 -0
  804. package/test/contracts/out/ERC721URIStorage.sol/ERC721URIStorage.json +1 -0
  805. package/test/contracts/out/ERC721URIStorageMock.sol/ERC721URIStorageMock.json +1 -0
  806. package/test/contracts/out/ERC721Votes.sol/ERC721Votes.json +1 -0
  807. package/test/contracts/out/ERC721Wrapper.sol/ERC721Wrapper.json +1 -0
  808. package/test/contracts/out/ERC777.sol/ERC777.json +1 -0
  809. package/test/contracts/out/ERC777Mock.sol/ERC777Mock.json +1 -0
  810. package/test/contracts/out/ERC777PresetFixedSupply.sol/ERC777PresetFixedSupply.json +1 -0
  811. package/test/contracts/out/ERC777SenderRecipientMock.sol/ERC777SenderRecipientMock.json +1 -0
  812. package/test/contracts/out/EnumerableMap.sol/EnumerableMap.json +1 -0
  813. package/test/contracts/out/EnumerableSet.sol/EnumerableSet.json +1 -0
  814. package/test/contracts/out/Escrow.sol/Escrow.json +1 -0
  815. package/test/contracts/out/EtherReceiverMock.sol/EtherReceiverMock.json +1 -0
  816. package/test/contracts/out/Governor.sol/Governor.json +1 -0
  817. package/test/contracts/out/Governor.t.sol/GovernorInternalTest.json +1 -0
  818. package/test/contracts/out/GovernorCompMock.sol/GovernorCompMock.json +1 -0
  819. package/test/contracts/out/GovernorCompatibilityBravo.sol/GovernorCompatibilityBravo.json +1 -0
  820. package/test/contracts/out/GovernorCompatibilityBravoMock.sol/GovernorCompatibilityBravoMock.json +1 -0
  821. package/test/contracts/out/GovernorCountingSimple.sol/GovernorCountingSimple.json +1 -0
  822. package/test/contracts/out/GovernorMock.sol/GovernorMock.json +1 -0
  823. package/test/contracts/out/GovernorPreventLateQuorum.sol/GovernorPreventLateQuorum.json +1 -0
  824. package/test/contracts/out/GovernorPreventLateQuorumMock.sol/GovernorPreventLateQuorumMock.json +1 -0
  825. package/test/contracts/out/GovernorProposalThreshold.sol/GovernorProposalThreshold.json +1 -0
  826. package/test/contracts/out/GovernorSettings.sol/GovernorSettings.json +1 -0
  827. package/test/contracts/out/GovernorTimelockCompound.sol/GovernorTimelockCompound.json +1 -0
  828. package/test/contracts/out/GovernorTimelockCompoundMock.sol/GovernorTimelockCompoundMock.json +1 -0
  829. package/test/contracts/out/GovernorTimelockControl.sol/GovernorTimelockControl.json +1 -0
  830. package/test/contracts/out/GovernorTimelockControlMock.sol/GovernorTimelockControlMock.json +1 -0
  831. package/test/contracts/out/GovernorVoteMock.sol/GovernorVoteMocks.json +1 -0
  832. package/test/contracts/out/GovernorVotes.sol/GovernorVotes.json +1 -0
  833. package/test/contracts/out/GovernorVotesComp.sol/GovernorVotesComp.json +1 -0
  834. package/test/contracts/out/GovernorVotesQuorumFraction.sol/GovernorVotesQuorumFraction.json +1 -0
  835. package/test/contracts/out/GovernorWithParamsMock.sol/GovernorWithParamsMock.json +1 -0
  836. package/test/contracts/out/IAMB.sol/IAMB.json +1 -0
  837. package/test/contracts/out/IAccessControl.sol/IAccessControl.json +1 -0
  838. package/test/contracts/out/IAccessControlDefaultAdminRules.sol/IAccessControlDefaultAdminRules.json +1 -0
  839. package/test/contracts/out/IAccessControlEnumerable.sol/IAccessControlEnumerable.json +1 -0
  840. package/test/contracts/out/IArbSys.sol/IArbSys.json +1 -0
  841. package/test/contracts/out/IBeacon.sol/IBeacon.json +1 -0
  842. package/test/contracts/out/IBridge.sol/IBridge.json +1 -0
  843. package/test/contracts/out/ICompoundTimelock.sol/ICompoundTimelock.json +1 -0
  844. package/test/contracts/out/ICrossDomainMessenger.sol/ICrossDomainMessenger.json +1 -0
  845. package/test/contracts/out/IDelayedMessageProvider.sol/IDelayedMessageProvider.json +1 -0
  846. package/test/contracts/out/IERC1155.sol/IERC1155.json +1 -0
  847. package/test/contracts/out/IERC1155MetadataURI.sol/IERC1155MetadataURI.json +1 -0
  848. package/test/contracts/out/IERC1155Receiver.sol/IERC1155Receiver.json +1 -0
  849. package/test/contracts/out/IERC1271.sol/IERC1271.json +1 -0
  850. package/test/contracts/out/IERC1363.sol/IERC1363.json +1 -0
  851. package/test/contracts/out/IERC1363Receiver.sol/IERC1363Receiver.json +1 -0
  852. package/test/contracts/out/IERC1363Spender.sol/IERC1363Spender.json +1 -0
  853. package/test/contracts/out/IERC165.sol/IERC165.json +1 -0
  854. package/test/contracts/out/IERC1820Implementer.sol/IERC1820Implementer.json +1 -0
  855. package/test/contracts/out/IERC1820Registry.sol/IERC1820Registry.json +1 -0
  856. package/test/contracts/out/IERC1967.sol/IERC1967.json +1 -0
  857. package/test/contracts/out/IERC20.sol/IERC20.json +1 -0
  858. package/test/contracts/out/IERC20Metadata.sol/IERC20Metadata.json +1 -0
  859. package/test/contracts/out/IERC20Permit.sol/IERC20Permit.json +1 -0
  860. package/test/contracts/out/IERC2309.sol/IERC2309.json +1 -0
  861. package/test/contracts/out/IERC2612.sol/IERC2612.json +1 -0
  862. package/test/contracts/out/IERC2981.sol/IERC2981.json +1 -0
  863. package/test/contracts/out/IERC3156FlashBorrower.sol/IERC3156FlashBorrower.json +1 -0
  864. package/test/contracts/out/IERC3156FlashLender.sol/IERC3156FlashLender.json +1 -0
  865. package/test/contracts/out/IERC4626.sol/IERC4626.json +1 -0
  866. package/test/contracts/out/IERC4906.sol/IERC4906.json +1 -0
  867. package/test/contracts/out/IERC5267.sol/IERC5267.json +1 -0
  868. package/test/contracts/out/IERC5313.sol/IERC5313.json +1 -0
  869. package/test/contracts/out/IERC5805.sol/IERC5805.json +1 -0
  870. package/test/contracts/out/IERC6372.sol/IERC6372.json +1 -0
  871. package/test/contracts/out/IERC721.sol/IERC721.json +1 -0
  872. package/test/contracts/out/IERC721.sol/IERC721Enumerable.json +1 -0
  873. package/test/contracts/out/IERC721.sol/IERC721Metadata.json +1 -0
  874. package/test/contracts/out/IERC721.sol/IERC721TokenReceiver.json +1 -0
  875. package/test/contracts/out/IERC721Enumerable.sol/IERC721Enumerable.json +1 -0
  876. package/test/contracts/out/IERC721Metadata.sol/IERC721Metadata.json +1 -0
  877. package/test/contracts/out/IERC721Receiver.sol/IERC721Receiver.json +1 -0
  878. package/test/contracts/out/IERC777.sol/IERC777.json +1 -0
  879. package/test/contracts/out/IERC777Recipient.sol/IERC777Recipient.json +1 -0
  880. package/test/contracts/out/IERC777Sender.sol/IERC777Sender.json +1 -0
  881. package/test/contracts/out/IFxMessageProcessor.sol/IFxMessageProcessor.json +1 -0
  882. package/test/contracts/out/IGovernor.sol/IGovernor.json +1 -0
  883. package/test/contracts/out/IGovernorCompatibilityBravo.sol/IGovernorCompatibilityBravo.json +1 -0
  884. package/test/contracts/out/IGovernorTimelock.sol/IGovernorTimelock.json +1 -0
  885. package/test/contracts/out/IInbox.sol/IInbox.json +1 -0
  886. package/test/contracts/out/IMulticall3.sol/IMulticall3.json +1 -0
  887. package/test/contracts/out/IOutbox.sol/IOutbox.json +1 -0
  888. package/test/contracts/out/IVotes.sol/IVotes.json +1 -0
  889. package/test/contracts/out/Initializable.sol/Initializable.json +1 -0
  890. package/test/contracts/out/InitializableMock.sol/ChildConstructorInitializableMock.json +1 -0
  891. package/test/contracts/out/InitializableMock.sol/ConstructorInitializableMock.json +1 -0
  892. package/test/contracts/out/InitializableMock.sol/DisableBad1.json +1 -0
  893. package/test/contracts/out/InitializableMock.sol/DisableBad2.json +1 -0
  894. package/test/contracts/out/InitializableMock.sol/DisableNew.json +1 -0
  895. package/test/contracts/out/InitializableMock.sol/DisableOk.json +1 -0
  896. package/test/contracts/out/InitializableMock.sol/DisableOld.json +1 -0
  897. package/test/contracts/out/InitializableMock.sol/InitializableMock.json +1 -0
  898. package/test/contracts/out/InitializableMock.sol/ReinitializerMock.json +1 -0
  899. package/test/contracts/out/LibAMB.sol/LibAMB.json +1 -0
  900. package/test/contracts/out/LibArbitrumL1.sol/LibArbitrumL1.json +1 -0
  901. package/test/contracts/out/LibArbitrumL2.sol/LibArbitrumL2.json +1 -0
  902. package/test/contracts/out/LibOptimism.sol/LibOptimism.json +1 -0
  903. package/test/contracts/out/Math.sol/Math.json +1 -0
  904. package/test/contracts/out/Math.t.sol/MathTest.json +1 -0
  905. package/test/contracts/out/MerkleProof.sol/MerkleProof.json +1 -0
  906. package/test/contracts/out/MinimalForwarder.sol/MinimalForwarder.json +1 -0
  907. package/test/contracts/out/MockMiladyAgentRegistry.sol/MockMiladyAgentRegistry.json +1 -0
  908. package/test/contracts/out/MockMiladyCollection.sol/MockMiladyCollection.json +1 -0
  909. package/test/contracts/out/Multicall.sol/Multicall.json +1 -0
  910. package/test/contracts/out/MulticallTest.sol/MulticallTest.json +1 -0
  911. package/test/contracts/out/MultipleInheritanceInitializableMocks.sol/SampleChild.json +1 -0
  912. package/test/contracts/out/MultipleInheritanceInitializableMocks.sol/SampleFather.json +1 -0
  913. package/test/contracts/out/MultipleInheritanceInitializableMocks.sol/SampleGramps.json +1 -0
  914. package/test/contracts/out/MultipleInheritanceInitializableMocks.sol/SampleHuman.json +1 -0
  915. package/test/contracts/out/MultipleInheritanceInitializableMocks.sol/SampleMother.json +1 -0
  916. package/test/contracts/out/MyGovernor.sol/MyGovernor.json +1 -0
  917. package/test/contracts/out/MyGovernor1.sol/MyGovernor1.json +1 -0
  918. package/test/contracts/out/MyGovernor2.sol/MyGovernor2.json +1 -0
  919. package/test/contracts/out/MyGovernor3.sol/MyGovernor3.json +1 -0
  920. package/test/contracts/out/MyToken.sol/MyToken.json +1 -0
  921. package/test/contracts/out/MyTokenTimestampBased.sol/MyTokenTimestampBased.json +1 -0
  922. package/test/contracts/out/MyTokenWrapped.sol/MyTokenWrapped.json +1 -0
  923. package/test/contracts/out/Ownable.sol/Ownable.json +1 -0
  924. package/test/contracts/out/Ownable2Step.sol/Ownable2Step.json +1 -0
  925. package/test/contracts/out/Pausable.sol/Pausable.json +1 -0
  926. package/test/contracts/out/PausableMock.sol/PausableMock.json +1 -0
  927. package/test/contracts/out/PaymentSplitter.sol/PaymentSplitter.json +1 -0
  928. package/test/contracts/out/Proxy.sol/Proxy.json +1 -0
  929. package/test/contracts/out/ProxyAdmin.sol/ProxyAdmin.json +1 -0
  930. package/test/contracts/out/PullPayment.sol/PullPayment.json +1 -0
  931. package/test/contracts/out/PullPaymentMock.sol/PullPaymentMock.json +1 -0
  932. package/test/contracts/out/ReentrancyAttack.sol/ReentrancyAttack.json +1 -0
  933. package/test/contracts/out/ReentrancyGuard.sol/ReentrancyGuard.json +1 -0
  934. package/test/contracts/out/ReentrancyMock.sol/ReentrancyMock.json +1 -0
  935. package/test/contracts/out/RefundEscrow.sol/RefundEscrow.json +1 -0
  936. package/test/contracts/out/RegressionImplementation.sol/Implementation1.json +1 -0
  937. package/test/contracts/out/RegressionImplementation.sol/Implementation2.json +1 -0
  938. package/test/contracts/out/RegressionImplementation.sol/Implementation3.json +1 -0
  939. package/test/contracts/out/RegressionImplementation.sol/Implementation4.json +1 -0
  940. package/test/contracts/out/SafeCast.sol/SafeCast.json +1 -0
  941. package/test/contracts/out/SafeERC20.sol/SafeERC20.json +1 -0
  942. package/test/contracts/out/SafeMath.sol/SafeMath.json +1 -0
  943. package/test/contracts/out/SafeMathMemoryCheck.sol/SafeMathMemoryCheck.json +1 -0
  944. package/test/contracts/out/Script.sol/Script.json +1 -0
  945. package/test/contracts/out/ShortStrings.sol/ShortStrings.json +1 -0
  946. package/test/contracts/out/ShortStrings.t.sol/ShortStringsTest.json +1 -0
  947. package/test/contracts/out/SignatureChecker.sol/SignatureChecker.json +1 -0
  948. package/test/contracts/out/SignedMath.sol/SignedMath.json +1 -0
  949. package/test/contracts/out/SignedSafeMath.sol/SignedSafeMath.json +1 -0
  950. package/test/contracts/out/SingleInheritanceInitializableMocks.sol/MigratableMockV1.json +1 -0
  951. package/test/contracts/out/SingleInheritanceInitializableMocks.sol/MigratableMockV2.json +1 -0
  952. package/test/contracts/out/SingleInheritanceInitializableMocks.sol/MigratableMockV3.json +1 -0
  953. package/test/contracts/out/StdAssertions.sol/StdAssertions.json +1 -0
  954. package/test/contracts/out/StdAssertions.t.sol/StdAssertionsTest.json +1 -0
  955. package/test/contracts/out/StdAssertions.t.sol/TestMockCall.json +1 -0
  956. package/test/contracts/out/StdAssertions.t.sol/TestTest.json +1 -0
  957. package/test/contracts/out/StdChains.sol/StdChains.json +1 -0
  958. package/test/contracts/out/StdChains.t.sol/StdChainsTest.json +1 -0
  959. package/test/contracts/out/StdCheats.sol/StdCheats.json +1 -0
  960. package/test/contracts/out/StdCheats.sol/StdCheatsSafe.json +1 -0
  961. package/test/contracts/out/StdCheats.t.sol/Bar.json +1 -0
  962. package/test/contracts/out/StdCheats.t.sol/BarERC1155.json +1 -0
  963. package/test/contracts/out/StdCheats.t.sol/BarERC721.json +1 -0
  964. package/test/contracts/out/StdCheats.t.sol/RevertingContract.json +1 -0
  965. package/test/contracts/out/StdCheats.t.sol/StdCheatsTest.json +1 -0
  966. package/test/contracts/out/StdError.sol/stdError.json +1 -0
  967. package/test/contracts/out/StdError.t.sol/ErrorsTest.json +1 -0
  968. package/test/contracts/out/StdError.t.sol/StdErrorsTest.json +1 -0
  969. package/test/contracts/out/StdInvariant.sol/StdInvariant.json +1 -0
  970. package/test/contracts/out/StdJson.sol/stdJson.json +1 -0
  971. package/test/contracts/out/StdMath.sol/stdMath.json +1 -0
  972. package/test/contracts/out/StdMath.t.sol/StdMathTest.json +1 -0
  973. package/test/contracts/out/StdStorage.sol/stdStorage.json +1 -0
  974. package/test/contracts/out/StdStorage.sol/stdStorageSafe.json +1 -0
  975. package/test/contracts/out/StdStorage.t.sol/StdStorageTest.json +1 -0
  976. package/test/contracts/out/StdStorage.t.sol/StorageTest.json +1 -0
  977. package/test/contracts/out/StdStyle.sol/StdStyle.json +1 -0
  978. package/test/contracts/out/StdStyle.t.sol/StdStyleTest.json +1 -0
  979. package/test/contracts/out/StdUtils.sol/StdUtils.json +1 -0
  980. package/test/contracts/out/StdUtils.t.sol/StdUtilsForkTest.json +1 -0
  981. package/test/contracts/out/StdUtils.t.sol/StdUtilsMock.json +1 -0
  982. package/test/contracts/out/StdUtils.t.sol/StdUtilsTest.json +1 -0
  983. package/test/contracts/out/StorageSlot.sol/StorageSlot.json +1 -0
  984. package/test/contracts/out/StorageSlotMock.sol/StorageSlotMock.json +1 -0
  985. package/test/contracts/out/Strings.sol/Strings.json +1 -0
  986. package/test/contracts/out/TimelockController.sol/TimelockController.json +1 -0
  987. package/test/contracts/out/TimelockReentrant.sol/TimelockReentrant.json +1 -0
  988. package/test/contracts/out/Timers.sol/Timers.json +1 -0
  989. package/test/contracts/out/TimersBlockNumberImpl.sol/TimersBlockNumberImpl.json +1 -0
  990. package/test/contracts/out/TimersTimestampImpl.sol/TimersTimestampImpl.json +1 -0
  991. package/test/contracts/out/TokenTimelock.sol/TokenTimelock.json +1 -0
  992. package/test/contracts/out/TransparentUpgradeableProxy.sol/ITransparentUpgradeableProxy.json +1 -0
  993. package/test/contracts/out/TransparentUpgradeableProxy.sol/TransparentUpgradeableProxy.json +1 -0
  994. package/test/contracts/out/UUPSLegacy.sol/UUPSUpgradeableLegacyMock.json +1 -0
  995. package/test/contracts/out/UUPSUpgradeable.sol/UUPSUpgradeable.json +1 -0
  996. package/test/contracts/out/UUPSUpgradeableMock.sol/NonUpgradeableMock.json +1 -0
  997. package/test/contracts/out/UUPSUpgradeableMock.sol/UUPSUpgradeableMock.json +1 -0
  998. package/test/contracts/out/UUPSUpgradeableMock.sol/UUPSUpgradeableUnsafeMock.json +1 -0
  999. package/test/contracts/out/UpgradeableBeacon.sol/UpgradeableBeacon.json +1 -0
  1000. package/test/contracts/out/VestingWallet.sol/VestingWallet.json +1 -0
  1001. package/test/contracts/out/Vm.sol/Vm.json +1 -0
  1002. package/test/contracts/out/Vm.sol/VmSafe.json +1 -0
  1003. package/test/contracts/out/Votes.sol/Votes.json +1 -0
  1004. package/test/contracts/out/VotesMock.sol/VotesMock.json +1 -0
  1005. package/test/contracts/out/VotesMock.sol/VotesTimestampMock.json +1 -0
  1006. package/test/contracts/out/VotesTimestamp.sol/ERC20VotesCompTimestampMock.json +1 -0
  1007. package/test/contracts/out/VotesTimestamp.sol/ERC20VotesTimestampMock.json +1 -0
  1008. package/test/contracts/out/VotesTimestamp.sol/ERC721VotesTimestampMock.json +1 -0
  1009. package/test/contracts/out/bridges.sol/BaseRelayMock.json +1 -0
  1010. package/test/contracts/out/bridges.sol/BridgeAMBMock.json +1 -0
  1011. package/test/contracts/out/bridges.sol/BridgeArbitrumL1Inbox.json +1 -0
  1012. package/test/contracts/out/bridges.sol/BridgeArbitrumL1Mock.json +1 -0
  1013. package/test/contracts/out/bridges.sol/BridgeArbitrumL1Outbox.json +1 -0
  1014. package/test/contracts/out/bridges.sol/BridgeArbitrumL2Mock.json +1 -0
  1015. package/test/contracts/out/bridges.sol/BridgeOptimismMock.json +1 -0
  1016. package/test/contracts/out/bridges.sol/BridgePolygonChildMock.json +1 -0
  1017. package/test/contracts/out/build-info/2be03bb8eb1ccda1.json +1 -0
  1018. package/test/contracts/out/console.sol/console.json +1 -0
  1019. package/test/contracts/out/console2.sol/console2.json +1 -0
  1020. package/test/contracts/out/demo.sol/DemoTest.json +1 -0
  1021. package/test/contracts/out/demo.sol/DemoTestWithSetUp.json +1 -0
  1022. package/test/contracts/out/draft-IERC1822.sol/IERC1822Proxiable.json +1 -0
  1023. package/test/contracts/out/interfaces/IERC1155.sol/IERC1155.json +1 -0
  1024. package/test/contracts/out/interfaces/IERC165.sol/IERC165.json +1 -0
  1025. package/test/contracts/out/interfaces/IERC20.sol/IERC20.json +1 -0
  1026. package/test/contracts/out/interfaces/IERC4626.sol/IERC4626.json +1 -0
  1027. package/test/contracts/out/interfaces/IERC721.sol/IERC721.json +1 -0
  1028. package/test/contracts/out/receivers.sol/CrossChainEnabledAMBMock.json +1 -0
  1029. package/test/contracts/out/receivers.sol/CrossChainEnabledArbitrumL1Mock.json +1 -0
  1030. package/test/contracts/out/receivers.sol/CrossChainEnabledArbitrumL2Mock.json +1 -0
  1031. package/test/contracts/out/receivers.sol/CrossChainEnabledOptimismMock.json +1 -0
  1032. package/test/contracts/out/receivers.sol/CrossChainEnabledPolygonChildMock.json +1 -0
  1033. package/test/contracts/out/receivers.sol/Receiver.json +1 -0
  1034. package/test/contracts/out/test.sol/DSTest.json +1 -0
  1035. package/test/contracts/out/test.sol/Test.json +1 -0
  1036. package/test/contracts/out/test.t.sol/DemoTest.json +1 -0
  1037. package/test/database-api.e2e.test.ts +666 -0
  1038. package/test/debug-anvil.ts +44 -0
  1039. package/test/deferred-restart.e2e.test.ts +368 -0
  1040. package/test/discord-connector.e2e.test.ts +463 -0
  1041. package/test/e2e-global-setup.ts +29 -0
  1042. package/test/e2e-validation.e2e.test.ts +1588 -0
  1043. package/test/health-endpoint.e2e.test.ts +95 -0
  1044. package/test/knowledge-e2e-flow.e2e.test.ts +134 -0
  1045. package/test/knowledge-live.e2e.test.ts +405 -0
  1046. package/test/mcp-config.e2e.test.ts +630 -0
  1047. package/test/native-modules.e2e.test.ts +470 -0
  1048. package/test/permissions-api.e2e.test.ts +637 -0
  1049. package/test/plugin-install.e2e.test.ts +645 -0
  1050. package/test/plugin-lifecycle.e2e.test.ts +617 -0
  1051. package/test/plugin-management.e2e.test.ts +308 -0
  1052. package/test/provider-switch.e2e.test.ts +322 -0
  1053. package/test/scripts/test-force.ts +139 -0
  1054. package/test/scripts/test-parallel.mjs +192 -0
  1055. package/test/scripts/validate-all-features.sh +557 -0
  1056. package/test/setup.ts +122 -0
  1057. package/test/signal-connector.e2e.test.ts +229 -0
  1058. package/test/skills-marketplace-api.e2e.test.ts +585 -0
  1059. package/test/skills-marketplace-services.e2e.test.ts +518 -0
  1060. package/test/skills-marketplace.e2e.test.ts +268 -0
  1061. package/test/stubs/coding-agent-module.ts +18 -0
  1062. package/test/stubs/electron-module.ts +17 -0
  1063. package/test/stubs/empty-module.mjs +4 -0
  1064. package/test/stubs/pi-ai-module.ts +12 -0
  1065. package/test/subscription-auth.e2e.test.ts +747 -0
  1066. package/test/terminal-execution.e2e.test.ts +134 -0
  1067. package/test/terminal-run-limits.e2e.test.ts +132 -0
  1068. package/test/test-env.ts +156 -0
  1069. package/test/trajectory-collection.e2e.test.ts +800 -0
  1070. package/test/trajectory-database.e2e.test.ts +209 -0
  1071. package/test/trajectory-embedding-filter.e2e.test.ts +317 -0
  1072. package/test/trajectory-restart-carryover.e2e.test.ts +306 -0
  1073. package/test/trigger-execution-flow.e2e.test.ts +132 -0
  1074. package/test/trigger-runtime.e2e.test.ts +247 -0
  1075. package/test/wallet-api.e2e.test.ts +1295 -0
  1076. package/test/wallet-live.e2e.test.ts +428 -0
  1077. package/vitest.e2e.config.ts +71 -0
@@ -0,0 +1,4582 @@
1
+ /**
2
+ * E2E tests for the real API server — NO MOCKS, NO API KEYS NEEDED.
3
+ *
4
+ * Imports and starts the actual `startApiServer()` from src/api/server.ts.
5
+ * Tests every endpoint that doesn't require a running AgentRuntime:
6
+ * - Status reporting
7
+ * - Plugin discovery (real filesystem scan)
8
+ * - Skill discovery (real filesystem scan)
9
+ * - Onboarding options and status
10
+ * - Config endpoints
11
+ * - Log buffer
12
+ * - Lifecycle state transitions
13
+ * - Chat rejection when no runtime
14
+ * - 404 handling
15
+ * - CORS preflight
16
+ *
17
+ * These tests exercise REAL production code, not mocks.
18
+ */
19
+
20
+ import crypto from "node:crypto";
21
+ import fs from "node:fs/promises";
22
+ import http from "node:http";
23
+ import os from "node:os";
24
+ import path from "node:path";
25
+ import type { AgentRuntime, Content, Task, UUID } from "@elizaos/core";
26
+ import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
27
+ import { WebSocket } from "ws";
28
+ import { startApiServer } from "../src/api/server";
29
+ import { AGENT_NAME_POOL } from "../src/runtime/onboarding-names";
30
+
31
+ vi.mock("../src/services/mcp-marketplace", () => ({
32
+ searchMcpMarketplace: vi
33
+ .fn()
34
+ .mockResolvedValue({ results: [{ name: "test", vendor: "test" }] }),
35
+ getMcpServerDetails: vi.fn((name: string) =>
36
+ name === "nonexistent-server-xyz-123"
37
+ ? Promise.resolve(null)
38
+ : Promise.resolve({ name: "test", description: "test" }),
39
+ ),
40
+ }));
41
+
42
+ // ---------------------------------------------------------------------------
43
+ // HTTP helper (identical to the one in agent-runtime.e2e.test.ts)
44
+ // ---------------------------------------------------------------------------
45
+
46
+ function req(
47
+ port: number,
48
+ method: string,
49
+ p: string,
50
+ body?: Record<string, unknown>,
51
+ ): Promise<{
52
+ status: number;
53
+ headers: http.IncomingHttpHeaders;
54
+ data: Record<string, unknown>;
55
+ }> {
56
+ return new Promise((resolve, reject) => {
57
+ const b = body ? JSON.stringify(body) : undefined;
58
+ const r = http.request(
59
+ {
60
+ hostname: "127.0.0.1",
61
+ port,
62
+ path: p,
63
+ method,
64
+ headers: {
65
+ "Content-Type": "application/json",
66
+ ...(b ? { "Content-Length": Buffer.byteLength(b) } : {}),
67
+ },
68
+ },
69
+ (res) => {
70
+ const ch: Buffer[] = [];
71
+ res.on("data", (c: Buffer) => ch.push(c));
72
+ res.on("end", () => {
73
+ const raw = Buffer.concat(ch).toString("utf-8");
74
+ let data: Record<string, unknown> = {};
75
+ try {
76
+ data = JSON.parse(raw) as Record<string, unknown>;
77
+ } catch {
78
+ data = { _raw: raw };
79
+ }
80
+ resolve({ status: res.statusCode ?? 0, headers: res.headers, data });
81
+ });
82
+ },
83
+ );
84
+ r.on("error", reject);
85
+ if (b) r.write(b);
86
+ r.end();
87
+ });
88
+ }
89
+
90
+ function reqRaw(
91
+ port: number,
92
+ method: string,
93
+ p: string,
94
+ body?: Record<string, unknown>,
95
+ ): Promise<{
96
+ status: number;
97
+ headers: http.IncomingHttpHeaders;
98
+ data: Buffer;
99
+ }> {
100
+ return new Promise((resolve, reject) => {
101
+ const b = body ? JSON.stringify(body) : undefined;
102
+ const r = http.request(
103
+ {
104
+ hostname: "127.0.0.1",
105
+ port,
106
+ path: p,
107
+ method,
108
+ headers: {
109
+ "Content-Type": "application/json",
110
+ ...(b ? { "Content-Length": Buffer.byteLength(b) } : {}),
111
+ },
112
+ },
113
+ (res) => {
114
+ const chunks: Buffer[] = [];
115
+ res.on("data", (chunk: Buffer) => chunks.push(chunk));
116
+ res.on("end", () => {
117
+ resolve({
118
+ status: res.statusCode ?? 0,
119
+ headers: res.headers,
120
+ data: Buffer.concat(chunks),
121
+ });
122
+ });
123
+ },
124
+ );
125
+ r.on("error", reject);
126
+ if (b) r.write(b);
127
+ r.end();
128
+ });
129
+ }
130
+
131
+ type SseEventPayload = {
132
+ type?: string;
133
+ text?: string;
134
+ fullText?: string;
135
+ agentName?: string;
136
+ message?: string;
137
+ };
138
+
139
+ function reqSse(
140
+ port: number,
141
+ p: string,
142
+ body: Record<string, unknown>,
143
+ ): Promise<{
144
+ status: number;
145
+ headers: http.IncomingHttpHeaders;
146
+ events: SseEventPayload[];
147
+ }> {
148
+ return new Promise((resolve, reject) => {
149
+ const b = JSON.stringify(body);
150
+ const r = http.request(
151
+ {
152
+ hostname: "127.0.0.1",
153
+ port,
154
+ path: p,
155
+ method: "POST",
156
+ headers: {
157
+ "Content-Type": "application/json",
158
+ Accept: "text/event-stream",
159
+ "Content-Length": Buffer.byteLength(b),
160
+ },
161
+ },
162
+ (res) => {
163
+ const chunks: Buffer[] = [];
164
+ res.on("data", (c: Buffer) => chunks.push(c));
165
+ res.on("end", () => {
166
+ const raw = Buffer.concat(chunks).toString("utf-8");
167
+ const events: SseEventPayload[] = [];
168
+ const blocks = raw
169
+ .split("\n\n")
170
+ .map((block) => block.trim())
171
+ .filter((block) => block.length > 0);
172
+
173
+ for (const block of blocks) {
174
+ for (const line of block.split("\n")) {
175
+ if (!line.startsWith("data:")) continue;
176
+ const payloadText = line.slice(5).trim();
177
+ if (!payloadText) continue;
178
+ try {
179
+ events.push(JSON.parse(payloadText) as SseEventPayload);
180
+ } catch {
181
+ // Ignore malformed SSE payloads in test parsing.
182
+ }
183
+ }
184
+ }
185
+
186
+ resolve({
187
+ status: res.statusCode ?? 0,
188
+ headers: res.headers,
189
+ events,
190
+ });
191
+ });
192
+ },
193
+ );
194
+ r.on("error", reject);
195
+ r.write(b);
196
+ r.end();
197
+ });
198
+ }
199
+
200
+ function waitForWsMessage(
201
+ ws: WebSocket,
202
+ predicate: (message: Record<string, unknown>) => boolean,
203
+ timeoutMs = 3000,
204
+ ): Promise<Record<string, unknown>> {
205
+ return new Promise((resolve, reject) => {
206
+ const timer = setTimeout(() => {
207
+ cleanup();
208
+ reject(new Error("Timed out waiting for websocket message"));
209
+ }, timeoutMs);
210
+
211
+ const onMessage = (raw: WebSocket.RawData) => {
212
+ try {
213
+ const text = Buffer.isBuffer(raw) ? raw.toString("utf-8") : String(raw);
214
+ const message = JSON.parse(text) as Record<string, unknown>;
215
+ if (predicate(message)) {
216
+ cleanup();
217
+ resolve(message);
218
+ }
219
+ } catch {
220
+ // Ignore malformed WS frames in tests.
221
+ }
222
+ };
223
+
224
+ const onError = (err: Error) => {
225
+ cleanup();
226
+ reject(err);
227
+ };
228
+
229
+ const cleanup = () => {
230
+ clearTimeout(timer);
231
+ ws.off("message", onMessage);
232
+ ws.off("error", onError);
233
+ };
234
+
235
+ ws.on("message", onMessage);
236
+ ws.on("error", onError);
237
+ });
238
+ }
239
+
240
+ function createDeferred<T>() {
241
+ let resolve!: (value: T | PromiseLike<T>) => void;
242
+ const promise = new Promise<T>((res) => {
243
+ resolve = res;
244
+ });
245
+ return { promise, resolve };
246
+ }
247
+
248
+ type TestAgentEvent = {
249
+ runId: string;
250
+ seq: number;
251
+ stream: string;
252
+ ts: number;
253
+ data: Record<string, unknown>;
254
+ sessionKey?: string;
255
+ agentId?: string;
256
+ roomId?: string;
257
+ };
258
+
259
+ type TestHeartbeatEvent = {
260
+ ts: number;
261
+ status: string;
262
+ preview?: string;
263
+ };
264
+
265
+ class TestAgentEventService {
266
+ private eventListeners = new Set<(event: TestAgentEvent) => void>();
267
+ private heartbeatListeners = new Set<(event: TestHeartbeatEvent) => void>();
268
+
269
+ subscribe(listener: (event: TestAgentEvent) => void): () => void {
270
+ this.eventListeners.add(listener);
271
+ return () => this.eventListeners.delete(listener);
272
+ }
273
+
274
+ subscribeHeartbeat(
275
+ listener: (event: TestHeartbeatEvent) => void,
276
+ ): () => void {
277
+ this.heartbeatListeners.add(listener);
278
+ return () => this.heartbeatListeners.delete(listener);
279
+ }
280
+
281
+ emit(event: TestAgentEvent): void {
282
+ for (const listener of this.eventListeners) {
283
+ listener(event);
284
+ }
285
+ }
286
+
287
+ emitHeartbeat(event: TestHeartbeatEvent): void {
288
+ for (const listener of this.heartbeatListeners) {
289
+ listener(event);
290
+ }
291
+ }
292
+ }
293
+
294
+ function createRuntimeForStreamTests(options: {
295
+ eventService?: TestAgentEventService;
296
+ loopRunning?: boolean;
297
+ }): AgentRuntime {
298
+ const runtimeSubset = {
299
+ agentId: "test-agent-id",
300
+ character: { name: "StreamTestAgent" } as AgentRuntime["character"],
301
+ getService: (serviceType: string) => {
302
+ if (serviceType === "AGENT_EVENT") {
303
+ return options.eventService ?? null;
304
+ }
305
+ if (serviceType === "AUTONOMY") {
306
+ return {
307
+ enableAutonomy: async () => {},
308
+ disableAutonomy: async () => {},
309
+ isLoopRunning: () => options.loopRunning ?? false,
310
+ getStatus: () => ({ enabled: options.loopRunning ?? false }),
311
+ } as never;
312
+ }
313
+ return null;
314
+ },
315
+ getRoomsByWorld: async () => [],
316
+ getMemories: async () => [],
317
+ getCache: async () => null,
318
+ setCache: async () => {},
319
+ };
320
+ return runtimeSubset as unknown as AgentRuntime;
321
+ }
322
+
323
+ function createRuntimeForAutonomySurfaceTests(options: {
324
+ eventService: TestAgentEventService;
325
+ loopRunning?: boolean;
326
+ }): AgentRuntime {
327
+ const memoriesByRoom = new Map<string, Array<Record<string, unknown>>>();
328
+ let tasks: Task[] = [
329
+ {
330
+ id: "00000000-0000-0000-0000-00000000a001" as UUID,
331
+ name: "Autonomy surface task",
332
+ description: "Validate workbench task visibility",
333
+ tags: ["workbench-task"],
334
+ metadata: {
335
+ isCompleted: false,
336
+ updatedAt: Date.now(),
337
+ workbench: { kind: "task" },
338
+ },
339
+ } as Task,
340
+ {
341
+ id: "00000000-0000-0000-0000-00000000a002" as UUID,
342
+ name: "TRIGGER_DISPATCH",
343
+ description: "Autonomy surface trigger",
344
+ tags: ["queue", "repeat", "trigger"],
345
+ metadata: {
346
+ updatedAt: Date.now(),
347
+ updateInterval: 60_000,
348
+ trigger: {
349
+ triggerId: "00000000-0000-0000-0000-00000000a111",
350
+ displayName: "Autonomy surface trigger",
351
+ instructions: "Emit a proactive autonomy update",
352
+ triggerType: "interval",
353
+ enabled: true,
354
+ wakeMode: "inject_now",
355
+ createdBy: "test",
356
+ intervalMs: 60_000,
357
+ runCount: 0,
358
+ nextRunAtMs: Date.now() + 60_000,
359
+ },
360
+ },
361
+ } as Task,
362
+ ];
363
+
364
+ const runtimeSubset = {
365
+ agentId: "autonomy-surface-agent",
366
+ character: { name: "AutonomySurfaceAgent" } as AgentRuntime["character"],
367
+ messageService: {
368
+ handleMessage: async (
369
+ _runtime: AgentRuntime,
370
+ message: Record<string, unknown>,
371
+ ) => {
372
+ const prompt =
373
+ typeof (message.content as Record<string, unknown> | undefined)
374
+ ?.text === "string"
375
+ ? String(
376
+ (message.content as Record<string, unknown> | undefined)?.text,
377
+ )
378
+ : "autonomy";
379
+ return {
380
+ didRespond: true,
381
+ responseContent: { text: `Autonomy says: ${prompt}` },
382
+ responseMessages: [
383
+ {
384
+ id: crypto.randomUUID(),
385
+ entityId: "autonomy-surface-agent",
386
+ roomId: message.roomId as string,
387
+ createdAt: Date.now(),
388
+ content: {
389
+ text: `Autonomy says: ${prompt}`,
390
+ },
391
+ },
392
+ ],
393
+ mode: "power",
394
+ };
395
+ },
396
+ } as AgentRuntime["messageService"],
397
+ getService: (serviceType: string) => {
398
+ if (serviceType === "AGENT_EVENT") {
399
+ return options.eventService;
400
+ }
401
+ if (serviceType === "AUTONOMY") {
402
+ return {
403
+ enableAutonomy: async () => {},
404
+ disableAutonomy: async () => {},
405
+ isLoopRunning: () => options.loopRunning ?? true,
406
+ getStatus: () => ({ enabled: options.loopRunning ?? true }),
407
+ } as never;
408
+ }
409
+ return null;
410
+ },
411
+ ensureConnection: async () => {},
412
+ getWorld: async () => null,
413
+ updateWorld: async () => {},
414
+ createMemory: async (memory: Record<string, unknown>) => {
415
+ const roomId = String(memory.roomId ?? "");
416
+ if (!roomId) return;
417
+ const current = memoriesByRoom.get(roomId) ?? [];
418
+ current.push({
419
+ ...memory,
420
+ createdAt:
421
+ typeof memory.createdAt === "number" ? memory.createdAt : Date.now(),
422
+ });
423
+ memoriesByRoom.set(roomId, current);
424
+ },
425
+ getMemories: async (query: { roomId?: string; count?: number }) => {
426
+ const roomId = String(query.roomId ?? "");
427
+ const current = memoriesByRoom.get(roomId) ?? [];
428
+ const count = Math.max(1, query.count ?? current.length);
429
+ return current.slice(-count) as unknown as Awaited<
430
+ ReturnType<AgentRuntime["getMemories"]>
431
+ >;
432
+ },
433
+ getRoomsByWorld: async () => [],
434
+ getTasks: async (query?: { tags?: string[] }) => {
435
+ if (!query?.tags || query.tags.length === 0) return tasks;
436
+ return tasks.filter((task) =>
437
+ query.tags?.every((tag) => task.tags?.includes(tag)),
438
+ );
439
+ },
440
+ getTask: async (taskId: UUID) =>
441
+ tasks.find((task) => task.id === taskId) ?? null,
442
+ deleteTask: async (taskId: UUID) => {
443
+ tasks = tasks.filter((task) => task.id !== taskId);
444
+ },
445
+ getCache: async () => null,
446
+ setCache: async () => {},
447
+ };
448
+
449
+ return runtimeSubset as unknown as AgentRuntime;
450
+ }
451
+
452
+ function createRuntimeForWorkbenchCrudTests(options?: {
453
+ loopRunning?: boolean;
454
+ }): AgentRuntime {
455
+ let tasks: Task[] = [];
456
+ const runtimeSubset = {
457
+ agentId: "workbench-crud-agent",
458
+ character: { name: "WorkbenchCrudAgent" } as AgentRuntime["character"],
459
+ getSetting: () => undefined,
460
+ getService: (serviceType: string) => {
461
+ if (serviceType === "AUTONOMY") {
462
+ return {
463
+ isLoopRunning: () => options?.loopRunning ?? false,
464
+ getStatus: () => ({ enabled: options?.loopRunning ?? false }),
465
+ getAutonomousRoomId: () =>
466
+ "00000000-0000-0000-0000-000000000201" as UUID,
467
+ } as never;
468
+ }
469
+ return null;
470
+ },
471
+ getRoomsByWorld: async () => [],
472
+ getTasks: async (query?: { tags?: string[] }) => {
473
+ if (!query?.tags || query.tags.length === 0) return tasks;
474
+ return tasks.filter((task) =>
475
+ query.tags?.every((tag) => task.tags?.includes(tag)),
476
+ );
477
+ },
478
+ getTask: async (taskId: UUID) =>
479
+ tasks.find((task) => task.id === taskId) ?? null,
480
+ createTask: async (task: Task) => {
481
+ const id = (task.id as UUID | undefined) ?? (crypto.randomUUID() as UUID);
482
+ const created: Task = {
483
+ ...task,
484
+ id,
485
+ };
486
+ tasks.push(created);
487
+ return id;
488
+ },
489
+ updateTask: async (taskId: UUID, update: Partial<Task>) => {
490
+ tasks = tasks.map((task) =>
491
+ task.id === taskId
492
+ ? {
493
+ ...task,
494
+ ...update,
495
+ metadata: {
496
+ ...((task.metadata as Record<string, unknown> | undefined) ??
497
+ {}),
498
+ ...((update.metadata as Record<string, unknown> | undefined) ??
499
+ {}),
500
+ } as Task["metadata"],
501
+ }
502
+ : task,
503
+ );
504
+ },
505
+ deleteTask: async (taskId: UUID) => {
506
+ tasks = tasks.filter((task) => task.id !== taskId);
507
+ },
508
+ };
509
+
510
+ return runtimeSubset as unknown as AgentRuntime;
511
+ }
512
+
513
+ function createRuntimeForChatSseTests(options?: {
514
+ onEmitEvent?: (
515
+ event: Parameters<AgentRuntime["emitEvent"]>[0],
516
+ payload: Parameters<AgentRuntime["emitEvent"]>[1],
517
+ ) => void | Promise<void>;
518
+ getService?: (serviceType: string) => unknown;
519
+ getServicesByType?: (serviceType: string) => unknown;
520
+ handleMessage?: (
521
+ runtime: AgentRuntime,
522
+ message: object,
523
+ onResponse: (content: Content) => Promise<object[]>,
524
+ messageOptions?: {
525
+ onStreamChunk?: (chunk: string, messageId?: string) => Promise<void>;
526
+ },
527
+ ) => Promise<{
528
+ responseContent?: {
529
+ text?: string;
530
+ };
531
+ }>;
532
+ }): AgentRuntime {
533
+ const memoriesByRoom = new Map<string, Array<Record<string, unknown>>>();
534
+
535
+ const runtimeSubset = {
536
+ agentId: "chat-stream-agent",
537
+ character: {
538
+ name: "ChatStreamAgent",
539
+ postExamples: ["Welcome to the conversation."],
540
+ } as AgentRuntime["character"],
541
+ messageService: {
542
+ handleMessage: async (
543
+ runtime: AgentRuntime,
544
+ message: object,
545
+ onResponse: (content: Content) => Promise<object[]>,
546
+ messageOptions?: {
547
+ onStreamChunk?: (chunk: string, messageId?: string) => Promise<void>;
548
+ },
549
+ ) =>
550
+ options?.handleMessage?.(
551
+ runtime,
552
+ message,
553
+ onResponse,
554
+ messageOptions,
555
+ ) ??
556
+ (await (async () => {
557
+ await onResponse({ text: "Hello " } as Content);
558
+ await onResponse({ text: "world" } as Content);
559
+ return {
560
+ responseContent: {
561
+ text: "Hello world",
562
+ },
563
+ };
564
+ })()),
565
+ } as AgentRuntime["messageService"],
566
+ ensureConnection: async () => {},
567
+ getWorld: async () => null,
568
+ updateWorld: async () => {},
569
+ createMemory: async (memory: Record<string, unknown>) => {
570
+ const roomId = String(memory.roomId ?? "");
571
+ if (!roomId) return;
572
+ const current = memoriesByRoom.get(roomId) ?? [];
573
+ current.push({
574
+ ...memory,
575
+ createdAt:
576
+ typeof memory.createdAt === "number" ? memory.createdAt : Date.now(),
577
+ });
578
+ memoriesByRoom.set(roomId, current);
579
+ },
580
+ getService: (serviceType: string) =>
581
+ options?.getService?.(serviceType) ?? null,
582
+ getServicesByType: (serviceType: string) =>
583
+ options?.getServicesByType?.(serviceType) ?? [],
584
+ emitEvent: async (
585
+ event: Parameters<AgentRuntime["emitEvent"]>[0],
586
+ payload: Parameters<AgentRuntime["emitEvent"]>[1],
587
+ ) => {
588
+ await options?.onEmitEvent?.(event, payload);
589
+ },
590
+ getMemoriesByRoomIds: async (query: {
591
+ roomIds?: string[];
592
+ limit?: number;
593
+ }) => {
594
+ const roomIds = Array.isArray(query.roomIds) ? query.roomIds : [];
595
+ const limit = Math.max(1, query.limit ?? 200);
596
+ const merged: Array<Record<string, unknown>> = [];
597
+ for (const roomId of roomIds) {
598
+ const current = memoriesByRoom.get(String(roomId)) ?? [];
599
+ merged.push(...current);
600
+ }
601
+ merged.sort(
602
+ (a, b) => Number(a.createdAt ?? 0) - Number(b.createdAt ?? 0),
603
+ );
604
+ return merged.slice(-limit) as unknown as Awaited<
605
+ ReturnType<AgentRuntime["getMemoriesByRoomIds"]>
606
+ >;
607
+ },
608
+ getRoomsByWorld: async () => [],
609
+ getMemories: async (query: { roomId?: string; count?: number }) => {
610
+ const roomId = String(query.roomId ?? "");
611
+ const current = memoriesByRoom.get(roomId) ?? [];
612
+ const count = Math.max(1, query.count ?? current.length);
613
+ return current.slice(-count) as unknown as Awaited<
614
+ ReturnType<AgentRuntime["getMemories"]>
615
+ >;
616
+ },
617
+ getCache: async () => null,
618
+ setCache: async () => {},
619
+ };
620
+
621
+ return runtimeSubset as unknown as AgentRuntime;
622
+ }
623
+
624
+ function createRuntimeForCompatEndpointTests(): AgentRuntime {
625
+ const runtimeSubset = {
626
+ agentId: "compat-endpoint-agent",
627
+ character: { name: "CompatAgent" } as AgentRuntime["character"],
628
+ messageService: {
629
+ handleMessage: async (
630
+ _runtime: AgentRuntime,
631
+ _message: object,
632
+ onResponse: (content: Content) => Promise<object[]>,
633
+ ) => {
634
+ await onResponse({ text: "Compat " } as Content);
635
+ await onResponse({ text: "reply" } as Content);
636
+ return {
637
+ didRespond: true,
638
+ responseContent: {
639
+ text: "Compat reply",
640
+ },
641
+ responseMessages: [],
642
+ mode: "power",
643
+ };
644
+ },
645
+ } as AgentRuntime["messageService"],
646
+ ensureConnection: async () => {},
647
+ getWorld: async () => null,
648
+ updateWorld: async () => {},
649
+ getService: () => null,
650
+ getRoomsByWorld: async () => [],
651
+ getMemories: async () => [],
652
+ getCache: async () => null,
653
+ setCache: async () => {},
654
+ };
655
+
656
+ return runtimeSubset as unknown as AgentRuntime;
657
+ }
658
+
659
+ function createRuntimeForCreditNoResponseTests(): AgentRuntime {
660
+ const runtimeLogger = {
661
+ debug: () => {},
662
+ info: () => {},
663
+ warn: () => {},
664
+ error: () => {},
665
+ } as AgentRuntime["logger"];
666
+
667
+ const runtimeSubset = {
668
+ agentId: "credit-no-response-agent",
669
+ character: { name: "CreditAgent" } as AgentRuntime["character"],
670
+ logger: runtimeLogger,
671
+ messageService: {
672
+ handleMessage: async (_runtime: AgentRuntime) => {
673
+ _runtime.logger.error(
674
+ "#Youmu Model call failed: AI_APICallError: Insufficient credits. Required: $0.2250",
675
+ );
676
+ return {
677
+ didRespond: true,
678
+ responseContent: null,
679
+ responseMessages: [],
680
+ mode: "none",
681
+ };
682
+ },
683
+ } as AgentRuntime["messageService"],
684
+ ensureConnection: async () => {},
685
+ getWorld: async () => null,
686
+ updateWorld: async () => {},
687
+ getService: () => null,
688
+ getRoomsByWorld: async () => [],
689
+ getMemories: async () => [],
690
+ getCache: async () => null,
691
+ setCache: async () => {},
692
+ };
693
+
694
+ return runtimeSubset as unknown as AgentRuntime;
695
+ }
696
+
697
+ function createRuntimeForCreditLiteralNoResponseTests(): AgentRuntime {
698
+ const runtimeLogger = {
699
+ debug: () => {},
700
+ info: () => {},
701
+ warn: () => {},
702
+ error: () => {},
703
+ } as AgentRuntime["logger"];
704
+
705
+ const runtimeSubset = {
706
+ agentId: "credit-literal-no-response-agent",
707
+ character: { name: "CreditAgent" } as AgentRuntime["character"],
708
+ logger: runtimeLogger,
709
+ messageService: {
710
+ handleMessage: async (_runtime: AgentRuntime) => {
711
+ _runtime.logger.error(
712
+ "#Youmu Model call failed: AI_APICallError: Insufficient credits. Required: $0.2250",
713
+ );
714
+ return {
715
+ didRespond: true,
716
+ responseContent: { text: "(no response)" },
717
+ responseMessages: [],
718
+ mode: "none",
719
+ };
720
+ },
721
+ } as AgentRuntime["messageService"],
722
+ ensureConnection: async () => {},
723
+ getWorld: async () => null,
724
+ updateWorld: async () => {},
725
+ getService: () => null,
726
+ getRoomsByWorld: async () => [],
727
+ getMemories: async () => [],
728
+ getCache: async () => null,
729
+ setCache: async () => {},
730
+ };
731
+
732
+ return runtimeSubset as unknown as AgentRuntime;
733
+ }
734
+
735
+ function createRuntimeForCreditErrorTests(): AgentRuntime {
736
+ const runtimeSubset = {
737
+ agentId: "credit-error-agent",
738
+ character: { name: "CreditAgent" } as AgentRuntime["character"],
739
+ messageService: {
740
+ handleMessage: async () => {
741
+ throw new Error(
742
+ "AI_APICallError: Insufficient credits. Required: $0.2250",
743
+ );
744
+ },
745
+ } as AgentRuntime["messageService"],
746
+ ensureConnection: async () => {},
747
+ getWorld: async () => null,
748
+ updateWorld: async () => {},
749
+ getService: () => null,
750
+ getRoomsByWorld: async () => [],
751
+ getMemories: async () => [],
752
+ getCache: async () => null,
753
+ setCache: async () => {},
754
+ };
755
+
756
+ return runtimeSubset as unknown as AgentRuntime;
757
+ }
758
+
759
+ // ---------------------------------------------------------------------------
760
+ // Test isolation — redirect all state to a temp directory so tests never
761
+ // touch the real ~/.milady config, database, or plugins.
762
+ // ---------------------------------------------------------------------------
763
+
764
+ let _e2eTempDir: string;
765
+ let _origStateDirEnv: string | undefined;
766
+
767
+ beforeAll(async () => {
768
+ _origStateDirEnv = process.env.MILADY_STATE_DIR;
769
+ _e2eTempDir = await fs.mkdtemp(path.join(os.tmpdir(), "milady-e2e-"));
770
+ process.env.MILADY_STATE_DIR = _e2eTempDir;
771
+
772
+ // Seed a minimal config so the server can start
773
+ await fs.writeFile(
774
+ path.join(_e2eTempDir, "milady.json"),
775
+ JSON.stringify({
776
+ agents: {
777
+ defaults: { workspace: path.join(_e2eTempDir, "workspace") },
778
+ list: [{ id: "main", default: true, name: "TestAgent" }],
779
+ },
780
+ ui: { theme: "dark" },
781
+ cloud: { enabled: false },
782
+ env: {},
783
+ features: {},
784
+ plugins: { entries: {} },
785
+ database: { provider: "pglite" },
786
+ }),
787
+ );
788
+ await fs.mkdir(path.join(_e2eTempDir, "workspace"), { recursive: true });
789
+ });
790
+
791
+ afterAll(async () => {
792
+ if (_origStateDirEnv !== undefined) {
793
+ process.env.MILADY_STATE_DIR = _origStateDirEnv;
794
+ } else {
795
+ delete process.env.MILADY_STATE_DIR;
796
+ }
797
+ if (_e2eTempDir) {
798
+ await fs.rm(_e2eTempDir, { recursive: true, force: true });
799
+ }
800
+ });
801
+
802
+ // ---------------------------------------------------------------------------
803
+ // Tests
804
+ // ---------------------------------------------------------------------------
805
+
806
+ describe("API Server E2E (no runtime)", () => {
807
+ let port: number;
808
+ let close: () => Promise<void>;
809
+ let updateStartup: (update: {
810
+ phase?: string;
811
+ attempt?: number;
812
+ lastError?: string;
813
+ lastErrorAt?: number;
814
+ nextRetryAt?: number;
815
+ state?:
816
+ | "not_started"
817
+ | "starting"
818
+ | "running"
819
+ | "paused"
820
+ | "stopped"
821
+ | "restarting"
822
+ | "error";
823
+ }) => void;
824
+
825
+ beforeAll(async () => {
826
+ // Start the REAL server with no runtime (port 0 = auto-assign)
827
+ const server = await startApiServer({ port: 0 });
828
+ port = server.port;
829
+ close = server.close;
830
+ updateStartup = server.updateStartup;
831
+ }, 30_000);
832
+
833
+ afterAll(async () => {
834
+ await close();
835
+ });
836
+
837
+ // -- Status --
838
+
839
+ describe("GET /api/status", () => {
840
+ it("returns not_started state (no runtime)", async () => {
841
+ const { status, data } = await req(port, "GET", "/api/status");
842
+ expect(status).toBe(200);
843
+ expect(data.state).toBe("not_started");
844
+ expect(typeof data.agentName).toBe("string");
845
+ });
846
+
847
+ it("has no uptime or startedAt when not started", async () => {
848
+ const { data } = await req(port, "GET", "/api/status");
849
+ expect(data.uptime).toBeUndefined();
850
+ expect(data.startedAt).toBeUndefined();
851
+ });
852
+
853
+ it("includes startup status diagnostics and reflects updates", async () => {
854
+ const now = Date.now();
855
+ updateStartup({
856
+ phase: "runtime-retry",
857
+ attempt: 2,
858
+ lastError: "bootstrap failed",
859
+ lastErrorAt: now,
860
+ nextRetryAt: now + 1_000,
861
+ state: "starting",
862
+ });
863
+ const { data } = await req(port, "GET", "/api/status");
864
+ const startup = data.startup as
865
+ | {
866
+ phase?: string;
867
+ attempt?: number;
868
+ lastError?: string;
869
+ }
870
+ | undefined;
871
+ expect(data.startup).toBeDefined();
872
+ expect(startup?.phase).toBe("runtime-retry");
873
+ expect(startup?.attempt).toBe(2);
874
+ expect(startup?.lastError).toContain("bootstrap failed");
875
+ expect(data.state).toBe("starting");
876
+
877
+ updateStartup({
878
+ phase: "idle",
879
+ attempt: 0,
880
+ lastError: undefined,
881
+ lastErrorAt: undefined,
882
+ nextRetryAt: undefined,
883
+ state: "not_started",
884
+ });
885
+ });
886
+ });
887
+
888
+ // -- Lifecycle state transitions --
889
+
890
+ describe("lifecycle state transitions", () => {
891
+ it("start → running", async () => {
892
+ const { data } = await req(port, "POST", "/api/agent/start");
893
+ expect(data.ok).toBe(true);
894
+ const status = await req(port, "GET", "/api/status");
895
+ expect(status.data.state).toBe("paused");
896
+ expect(typeof status.data.uptime).toBe("number");
897
+ });
898
+
899
+ it("pause → paused", async () => {
900
+ const { data } = await req(port, "POST", "/api/agent/pause");
901
+ expect(data.ok).toBe(true);
902
+ expect((await req(port, "GET", "/api/status")).data.state).toBe("paused");
903
+ });
904
+
905
+ it("resume → running", async () => {
906
+ const { data } = await req(port, "POST", "/api/agent/resume");
907
+ expect(data.ok).toBe(true);
908
+ expect((await req(port, "GET", "/api/status")).data.state).toBe(
909
+ "running",
910
+ );
911
+ });
912
+
913
+ it("stop → stopped, clears model and timing", async () => {
914
+ const { data } = await req(port, "POST", "/api/agent/stop");
915
+ expect(data.ok).toBe(true);
916
+ const status = await req(port, "GET", "/api/status");
917
+ expect(status.data.state).toBe("stopped");
918
+ expect(status.data.model).toBeUndefined();
919
+ expect(status.data.startedAt).toBeUndefined();
920
+ });
921
+
922
+ it("full cycle: start → pause → resume → stop", async () => {
923
+ await req(port, "POST", "/api/agent/start");
924
+ expect((await req(port, "GET", "/api/status")).data.state).toBe("paused");
925
+
926
+ await req(port, "POST", "/api/agent/pause");
927
+ expect((await req(port, "GET", "/api/status")).data.state).toBe("paused");
928
+
929
+ await req(port, "POST", "/api/agent/resume");
930
+ expect((await req(port, "GET", "/api/status")).data.state).toBe(
931
+ "running",
932
+ );
933
+
934
+ await req(port, "POST", "/api/agent/stop");
935
+ expect((await req(port, "GET", "/api/status")).data.state).toBe(
936
+ "stopped",
937
+ );
938
+ });
939
+ });
940
+
941
+ // -- Chat rejection without runtime --
942
+
943
+ describe("POST /api/chat (no runtime)", () => {
944
+ it("rejects with 503 when no runtime", async () => {
945
+ const { status, data } = await req(port, "POST", "/api/chat", {
946
+ text: "hello",
947
+ });
948
+ expect(status).toBe(503);
949
+ expect(data.error).toContain("not running");
950
+ });
951
+
952
+ it("rejects empty text with 400", async () => {
953
+ const { status } = await req(port, "POST", "/api/chat", { text: "" });
954
+ expect(status).toBe(400);
955
+ });
956
+
957
+ it("rejects missing text with 400", async () => {
958
+ const { status } = await req(port, "POST", "/api/chat", {});
959
+ expect(status).toBe(400);
960
+ });
961
+ });
962
+
963
+ describe("POST /api/chat/stream (no runtime)", () => {
964
+ it("rejects with 503 when no runtime", async () => {
965
+ const { status, data } = await req(port, "POST", "/api/chat/stream", {
966
+ text: "hello",
967
+ });
968
+ expect(status).toBe(503);
969
+ expect(String(data.error)).toContain("not running");
970
+ });
971
+
972
+ it("rejects empty text with 400", async () => {
973
+ const { status } = await req(port, "POST", "/api/chat/stream", {
974
+ text: "",
975
+ });
976
+ expect(status).toBe(400);
977
+ });
978
+ });
979
+
980
+ describe("POST /api/conversations/:id/messages/stream (no runtime)", () => {
981
+ it("returns 404 when conversation does not exist", async () => {
982
+ const { status } = await req(
983
+ port,
984
+ "POST",
985
+ "/api/conversations/missing/messages/stream",
986
+ {
987
+ text: "hello",
988
+ },
989
+ );
990
+ expect(status).toBe(404);
991
+ });
992
+
993
+ it("returns 503 when conversation exists but runtime is absent", async () => {
994
+ const create = await req(port, "POST", "/api/conversations", {
995
+ title: "Streaming test",
996
+ });
997
+ expect(create.status).toBe(200);
998
+ const conversation = create.data.conversation as { id?: string };
999
+ const conversationId = conversation.id ?? "";
1000
+ expect(conversationId.length).toBeGreaterThan(0);
1001
+
1002
+ const { status, data } = await req(
1003
+ port,
1004
+ "POST",
1005
+ `/api/conversations/${conversationId}/messages/stream`,
1006
+ {
1007
+ text: "hello",
1008
+ },
1009
+ );
1010
+ expect(status).toBe(503);
1011
+ expect(String(data.error)).toContain("not running");
1012
+ });
1013
+ });
1014
+
1015
+ describe("streaming chat endpoints (runtime stub)", () => {
1016
+ it("POST /api/chat emits MESSAGE_RECEIVED before handling", async () => {
1017
+ const emitted: Array<{
1018
+ event: string;
1019
+ source: string | null;
1020
+ text: string | null;
1021
+ }> = [];
1022
+ const runtime = createRuntimeForChatSseTests({
1023
+ onEmitEvent: (event, payload) => {
1024
+ if (typeof event !== "string") return;
1025
+ const message =
1026
+ payload && typeof payload === "object" && "message" in payload
1027
+ ? (payload.message as { content?: { text?: string } })
1028
+ : null;
1029
+ const source =
1030
+ payload && typeof payload === "object" && "source" in payload
1031
+ ? payload.source
1032
+ : null;
1033
+ emitted.push({
1034
+ event,
1035
+ source: typeof source === "string" ? source : null,
1036
+ text:
1037
+ typeof message?.content?.text === "string"
1038
+ ? message.content.text
1039
+ : null,
1040
+ });
1041
+ },
1042
+ });
1043
+ const streamServer = await startApiServer({ port: 0, runtime });
1044
+ try {
1045
+ const { status, data } = await req(
1046
+ streamServer.port,
1047
+ "POST",
1048
+ "/api/chat",
1049
+ {
1050
+ text: "hello",
1051
+ mode: "simple",
1052
+ },
1053
+ );
1054
+
1055
+ expect(status).toBe(200);
1056
+ expect(String(data.text ?? "")).toBe("Hello world");
1057
+
1058
+ const received = emitted.find(
1059
+ (entry) => entry.event === "MESSAGE_RECEIVED",
1060
+ );
1061
+ expect(received).toBeDefined();
1062
+ expect(received?.source).toBe("client_chat");
1063
+ expect(received?.text).toBe("hello");
1064
+ } finally {
1065
+ await streamServer.close();
1066
+ }
1067
+ });
1068
+
1069
+ it("POST /api/chat does not use deprecated direct trajectory fallback when hooks do not set a step id", async () => {
1070
+ const starts: Array<{ stepId: string; source?: string }> = [];
1071
+ const ends: Array<{ stepId: string; status?: string }> = [];
1072
+ const trajectoryLogger = {
1073
+ isEnabled: () => true,
1074
+ startTrajectory: async (
1075
+ stepId: string,
1076
+ options: { source?: string },
1077
+ ) => {
1078
+ starts.push({ stepId, source: options.source });
1079
+ return stepId;
1080
+ },
1081
+ endTrajectory: async (stepId: string, status?: string) => {
1082
+ ends.push({ stepId, status });
1083
+ },
1084
+ };
1085
+ const runtime = createRuntimeForChatSseTests({
1086
+ getService: (serviceType) =>
1087
+ serviceType === "trajectory_logger" ? trajectoryLogger : null,
1088
+ });
1089
+ const streamServer = await startApiServer({ port: 0, runtime });
1090
+ try {
1091
+ const { status, data } = await req(
1092
+ streamServer.port,
1093
+ "POST",
1094
+ "/api/chat",
1095
+ {
1096
+ text: "fallback trajectory path",
1097
+ mode: "simple",
1098
+ },
1099
+ );
1100
+
1101
+ expect(status).toBe(200);
1102
+ expect(String(data.text ?? "")).toBe("Hello world");
1103
+ expect(starts).toHaveLength(0);
1104
+ expect(ends).toHaveLength(0);
1105
+ } finally {
1106
+ await streamServer.close();
1107
+ }
1108
+ });
1109
+
1110
+ it("POST /api/chat does not call deprecated direct trajectory fallback even when logger is only in getServicesByType", async () => {
1111
+ const starts: Array<{ stepId: string; source?: string }> = [];
1112
+ const ends: Array<{ stepId: string; status?: string }> = [];
1113
+ const trajectoryLogger = {
1114
+ isEnabled: () => true,
1115
+ startTrajectory: async (
1116
+ stepId: string,
1117
+ options: { source?: string },
1118
+ ) => {
1119
+ starts.push({ stepId, source: options.source });
1120
+ return stepId;
1121
+ },
1122
+ endTrajectory: async (stepId: string, status?: string) => {
1123
+ ends.push({ stepId, status });
1124
+ },
1125
+ };
1126
+ const runtime = createRuntimeForChatSseTests({
1127
+ getService: () => null,
1128
+ getServicesByType: (serviceType) =>
1129
+ serviceType === "trajectory_logger" ? [trajectoryLogger] : [],
1130
+ });
1131
+ const streamServer = await startApiServer({ port: 0, runtime });
1132
+ try {
1133
+ const { status, data } = await req(
1134
+ streamServer.port,
1135
+ "POST",
1136
+ "/api/chat",
1137
+ {
1138
+ text: "trajectory logger by type",
1139
+ mode: "simple",
1140
+ },
1141
+ );
1142
+
1143
+ expect(status).toBe(200);
1144
+ expect(String(data.text ?? "")).toBe("Hello world");
1145
+ expect(starts).toHaveLength(0);
1146
+ expect(ends).toHaveLength(0);
1147
+ } finally {
1148
+ await streamServer.close();
1149
+ }
1150
+ });
1151
+
1152
+ it("POST /api/chat does not end trajectories directly when hook metadata provides a step id", async () => {
1153
+ const starts: Array<{ stepId: string }> = [];
1154
+ const ends: Array<{ stepId: string; status?: string }> = [];
1155
+ const trajectoryLogger = {
1156
+ isEnabled: () => true,
1157
+ startTrajectory: async (stepId: string) => {
1158
+ starts.push({ stepId });
1159
+ return stepId;
1160
+ },
1161
+ endTrajectory: async (stepId: string, status?: string) => {
1162
+ ends.push({ stepId, status });
1163
+ },
1164
+ };
1165
+
1166
+ const runtime = createRuntimeForChatSseTests({
1167
+ getService: (serviceType) =>
1168
+ serviceType === "trajectory_logger" ? trajectoryLogger : null,
1169
+ onEmitEvent: (_event, payload) => {
1170
+ if (
1171
+ payload &&
1172
+ typeof payload === "object" &&
1173
+ "message" in payload &&
1174
+ payload.message &&
1175
+ typeof payload.message === "object"
1176
+ ) {
1177
+ const msg = payload.message as {
1178
+ metadata?: Record<string, unknown>;
1179
+ };
1180
+ if (!msg.metadata) msg.metadata = {};
1181
+ msg.metadata.trajectoryStepId = "hook-step-id";
1182
+ }
1183
+ },
1184
+ });
1185
+
1186
+ const streamServer = await startApiServer({ port: 0, runtime });
1187
+ try {
1188
+ const { status, data } = await req(
1189
+ streamServer.port,
1190
+ "POST",
1191
+ "/api/chat",
1192
+ {
1193
+ text: "trajectory end by hook",
1194
+ mode: "simple",
1195
+ },
1196
+ );
1197
+
1198
+ expect(status).toBe(200);
1199
+ expect(String(data.text ?? "")).toBe("Hello world");
1200
+ expect(starts).toHaveLength(0);
1201
+ expect(ends).toHaveLength(0);
1202
+ } finally {
1203
+ await streamServer.close();
1204
+ }
1205
+ });
1206
+
1207
+ it("POST /api/chat no longer proxies trajectory logger routing through deprecated fallback", async () => {
1208
+ const starts: Array<{ stepId: string }> = [];
1209
+ const ends: Array<{ stepId: string; status?: string }> = [];
1210
+ const persistentLlmCalls: Array<{ stepId: string; model?: string }> = [];
1211
+ const coreLlmCalls: Array<{ stepId: string; model?: string }> = [];
1212
+ const persistentLogger = {
1213
+ isEnabled: () => true,
1214
+ startTrajectory: async (stepId: string) => {
1215
+ starts.push({ stepId });
1216
+ return stepId;
1217
+ },
1218
+ endTrajectory: async (stepId: string, status?: string) => {
1219
+ ends.push({ stepId, status });
1220
+ },
1221
+ logLlmCall: (params: { stepId: string; model?: string }) => {
1222
+ persistentLlmCalls.push(params);
1223
+ },
1224
+ };
1225
+ const coreLogger = {
1226
+ logLlmCall: (params: { stepId: string; model?: string }) => {
1227
+ coreLlmCalls.push(params);
1228
+ },
1229
+ };
1230
+ const runtime = createRuntimeForChatSseTests({
1231
+ getService: (serviceType) =>
1232
+ serviceType === "trajectory_logger" ? coreLogger : null,
1233
+ getServicesByType: (serviceType) =>
1234
+ serviceType === "trajectory_logger"
1235
+ ? [coreLogger, persistentLogger]
1236
+ : [],
1237
+ handleMessage: async (runtimeArg, message, onResponse) => {
1238
+ const trajectoryLogger = (
1239
+ runtimeArg as unknown as {
1240
+ getService: (serviceType: string) => {
1241
+ logLlmCall?: (params: {
1242
+ stepId: string;
1243
+ model?: string;
1244
+ }) => void;
1245
+ } | null;
1246
+ }
1247
+ ).getService("trajectory_logger");
1248
+ const metadata =
1249
+ message && typeof message === "object" && "metadata" in message
1250
+ ? (message.metadata as { trajectoryStepId?: string } | undefined)
1251
+ : undefined;
1252
+ const stepId = metadata?.trajectoryStepId;
1253
+ if (stepId) {
1254
+ trajectoryLogger?.logLlmCall?.({
1255
+ stepId,
1256
+ model: "unit-test-model",
1257
+ });
1258
+ }
1259
+ await onResponse({ text: "Hello world" } as Content);
1260
+ return {
1261
+ responseContent: {
1262
+ text: "Hello world",
1263
+ },
1264
+ };
1265
+ },
1266
+ });
1267
+
1268
+ const streamServer = await startApiServer({ port: 0, runtime });
1269
+ try {
1270
+ const { status, data } = await req(
1271
+ streamServer.port,
1272
+ "POST",
1273
+ "/api/chat",
1274
+ {
1275
+ text: "trajectory logger routing",
1276
+ mode: "simple",
1277
+ },
1278
+ );
1279
+
1280
+ expect(status).toBe(200);
1281
+ expect(String(data.text ?? "")).toBe("Hello world");
1282
+ expect(starts).toHaveLength(0);
1283
+ expect(ends).toHaveLength(0);
1284
+ expect(persistentLlmCalls).toHaveLength(0);
1285
+ expect(coreLlmCalls).toHaveLength(0);
1286
+ } finally {
1287
+ await streamServer.close();
1288
+ }
1289
+ });
1290
+
1291
+ it("POST /api/chat/stream emits token and done events", async () => {
1292
+ const runtime = createRuntimeForChatSseTests();
1293
+ const streamServer = await startApiServer({ port: 0, runtime });
1294
+ try {
1295
+ const { status, headers, events } = await reqSse(
1296
+ streamServer.port,
1297
+ "/api/chat/stream",
1298
+ { text: "hello", mode: "power" },
1299
+ );
1300
+
1301
+ expect(status).toBe(200);
1302
+ expect(String(headers["content-type"] ?? "")).toContain(
1303
+ "text/event-stream",
1304
+ );
1305
+
1306
+ const tokenEvents = events.filter((event) => event.type === "token");
1307
+ expect(tokenEvents.map((event) => event.text)).toEqual([
1308
+ "Hello ",
1309
+ "world",
1310
+ ]);
1311
+
1312
+ const doneEvent = events.find((event) => event.type === "done");
1313
+ expect(doneEvent?.fullText).toBe("Hello world");
1314
+ expect(doneEvent?.agentName).toBe("ChatStreamAgent");
1315
+ } finally {
1316
+ await streamServer.close();
1317
+ }
1318
+ });
1319
+
1320
+ it("POST /api/chat/stream emits token events from runtime onStreamChunk", async () => {
1321
+ const runtime = createRuntimeForChatSseTests({
1322
+ handleMessage: async (
1323
+ _runtime,
1324
+ _message,
1325
+ onResponse,
1326
+ messageOptions,
1327
+ ) => {
1328
+ await messageOptions?.onStreamChunk?.("Hello ");
1329
+ await messageOptions?.onStreamChunk?.("world");
1330
+ await onResponse({ text: "Hello world" } as Content);
1331
+ return {
1332
+ responseContent: {
1333
+ text: "Hello world",
1334
+ },
1335
+ };
1336
+ },
1337
+ });
1338
+ const streamServer = await startApiServer({ port: 0, runtime });
1339
+ try {
1340
+ const { status, events } = await reqSse(
1341
+ streamServer.port,
1342
+ "/api/chat/stream",
1343
+ { text: "hello", mode: "power" },
1344
+ );
1345
+
1346
+ expect(status).toBe(200);
1347
+ const tokenEvents = events.filter((event) => event.type === "token");
1348
+ expect(tokenEvents.map((event) => event.text)).toEqual([
1349
+ "Hello ",
1350
+ "world",
1351
+ ]);
1352
+ expect(tokenEvents.map((event) => event.fullText)).toEqual([
1353
+ "Hello ",
1354
+ "Hello world",
1355
+ ]);
1356
+
1357
+ const doneEvent = events.find((event) => event.type === "done");
1358
+ expect(doneEvent?.fullText).toBe("Hello world");
1359
+ expect(doneEvent?.agentName).toBe("ChatStreamAgent");
1360
+ } finally {
1361
+ await streamServer.close();
1362
+ }
1363
+ });
1364
+
1365
+ it("POST /api/chat/stream avoids mixed-source duplication when callback text arrives before onStreamChunk", async () => {
1366
+ const runtime = createRuntimeForChatSseTests({
1367
+ handleMessage: async (
1368
+ _runtime,
1369
+ _message,
1370
+ onResponse,
1371
+ messageOptions,
1372
+ ) => {
1373
+ await onResponse({ text: "Hello world" } as Content);
1374
+ await messageOptions?.onStreamChunk?.("Hello ");
1375
+ await messageOptions?.onStreamChunk?.("world");
1376
+ return {
1377
+ responseContent: {
1378
+ text: "Hello world",
1379
+ },
1380
+ };
1381
+ },
1382
+ });
1383
+ const streamServer = await startApiServer({ port: 0, runtime });
1384
+ try {
1385
+ const { status, events } = await reqSse(
1386
+ streamServer.port,
1387
+ "/api/chat/stream",
1388
+ { text: "hello", mode: "power" },
1389
+ );
1390
+
1391
+ expect(status).toBe(200);
1392
+ const tokenEvents = events.filter((event) => event.type === "token");
1393
+ expect(tokenEvents.map((event) => event.text)).toEqual(["Hello world"]);
1394
+
1395
+ const doneEvent = events.find((event) => event.type === "done");
1396
+ expect(doneEvent?.fullText).toBe("Hello world");
1397
+ expect(doneEvent?.agentName).toBe("ChatStreamAgent");
1398
+ } finally {
1399
+ await streamServer.close();
1400
+ }
1401
+ });
1402
+
1403
+ it("POST /api/chat/stream de-duplicates cumulative callback text updates", async () => {
1404
+ const runtime = createRuntimeForChatSseTests({
1405
+ handleMessage: async (_runtime, _message, onResponse) => {
1406
+ await onResponse({ text: "Hello " } as Content);
1407
+ await onResponse({ text: "Hello world" } as Content);
1408
+ return {
1409
+ responseContent: {
1410
+ text: "Hello world",
1411
+ },
1412
+ };
1413
+ },
1414
+ });
1415
+ const streamServer = await startApiServer({ port: 0, runtime });
1416
+ try {
1417
+ const { status, events } = await reqSse(
1418
+ streamServer.port,
1419
+ "/api/chat/stream",
1420
+ { text: "hello", mode: "power" },
1421
+ );
1422
+
1423
+ expect(status).toBe(200);
1424
+ const tokenEvents = events.filter((event) => event.type === "token");
1425
+ expect(tokenEvents.map((event) => event.text)).toEqual([
1426
+ "Hello ",
1427
+ "world",
1428
+ ]);
1429
+
1430
+ const doneEvent = events.find((event) => event.type === "done");
1431
+ expect(doneEvent?.fullText).toBe("Hello world");
1432
+ expect(doneEvent?.agentName).toBe("ChatStreamAgent");
1433
+ } finally {
1434
+ await streamServer.close();
1435
+ }
1436
+ });
1437
+
1438
+ it("POST /api/chat/stream emits replacement snapshots for corrected callback text", async () => {
1439
+ const runtime = createRuntimeForChatSseTests({
1440
+ handleMessage: async (_runtime, _message, onResponse) => {
1441
+ await onResponse({ text: "Hello wrld" } as Content);
1442
+ await onResponse({ text: "Hello world" } as Content);
1443
+ await onResponse({ text: "Hello world!" } as Content);
1444
+ return {
1445
+ responseContent: {
1446
+ text: "Hello world!",
1447
+ },
1448
+ };
1449
+ },
1450
+ });
1451
+ const streamServer = await startApiServer({ port: 0, runtime });
1452
+ try {
1453
+ const { status, events } = await reqSse(
1454
+ streamServer.port,
1455
+ "/api/chat/stream",
1456
+ { text: "hello", mode: "power" },
1457
+ );
1458
+
1459
+ expect(status).toBe(200);
1460
+ const tokenEvents = events.filter((event) => event.type === "token");
1461
+ expect(tokenEvents.map((event) => event.text)).toEqual([
1462
+ "Hello wrld",
1463
+ "Hello world",
1464
+ "!",
1465
+ ]);
1466
+ expect(tokenEvents.map((event) => event.fullText)).toEqual([
1467
+ "Hello wrld",
1468
+ "Hello world",
1469
+ "Hello world!",
1470
+ ]);
1471
+
1472
+ const doneEvent = events.find((event) => event.type === "done");
1473
+ expect(doneEvent?.fullText).toBe("Hello world!");
1474
+ expect(doneEvent?.agentName).toBe("ChatStreamAgent");
1475
+ } finally {
1476
+ await streamServer.close();
1477
+ }
1478
+ });
1479
+
1480
+ it("POST /api/chat/stream preserves repeated characters in incremental callback tokens", async () => {
1481
+ const runtime = createRuntimeForChatSseTests({
1482
+ handleMessage: async (_runtime, _message, onResponse) => {
1483
+ for (const token of [
1484
+ "H",
1485
+ "e",
1486
+ "l",
1487
+ "l",
1488
+ "o",
1489
+ " ",
1490
+ "w",
1491
+ "o",
1492
+ "r",
1493
+ "l",
1494
+ "d",
1495
+ ]) {
1496
+ await onResponse({ text: token } as Content);
1497
+ }
1498
+ return {
1499
+ responseContent: {
1500
+ text: "Hello world",
1501
+ },
1502
+ };
1503
+ },
1504
+ });
1505
+ const streamServer = await startApiServer({ port: 0, runtime });
1506
+ try {
1507
+ const { status, events } = await reqSse(
1508
+ streamServer.port,
1509
+ "/api/chat/stream",
1510
+ { text: "hello", mode: "power" },
1511
+ );
1512
+
1513
+ expect(status).toBe(200);
1514
+ const tokenText = events
1515
+ .filter((event) => event.type === "token")
1516
+ .map((event) => event.text ?? "")
1517
+ .join("");
1518
+ expect(tokenText).toBe("Hello world");
1519
+
1520
+ const doneEvent = events.find((event) => event.type === "done");
1521
+ expect(doneEvent?.fullText).toBe("Hello world");
1522
+ expect(doneEvent?.agentName).toBe("ChatStreamAgent");
1523
+ } finally {
1524
+ await streamServer.close();
1525
+ }
1526
+ });
1527
+
1528
+ it("GET /api/conversations waits for a pending restore before reporting the list", async () => {
1529
+ const restoreGate =
1530
+ createDeferred<Array<{ id: UUID; channelId: string; name: string }>>();
1531
+ const restoredConversationId = "11111111-1111-4111-8111-111111111111";
1532
+ const restoredRoomId = "00000000-0000-0000-0000-00000000c001" as UUID;
1533
+ const restoredAt = Date.parse("2026-02-01T12:00:00.000Z");
1534
+ const runtime = {
1535
+ agentId: "restore-list-agent",
1536
+ character: { name: "RestoreListAgent" } as AgentRuntime["character"],
1537
+ getService: () => null,
1538
+ getRoomsByWorld: async () => restoreGate.promise,
1539
+ getMemories: async (query: { count?: number }) =>
1540
+ query.count === 1 ? ([{ createdAt: restoredAt }] as never[]) : [],
1541
+ getCache: async () => null,
1542
+ setCache: async () => {},
1543
+ } as unknown as AgentRuntime;
1544
+
1545
+ const streamServer = await startApiServer({ port: 0, runtime });
1546
+ try {
1547
+ const listPromise = req(streamServer.port, "GET", "/api/conversations");
1548
+ const earlyState = await Promise.race([
1549
+ listPromise.then(() => "resolved" as const),
1550
+ new Promise<"pending">((resolve) =>
1551
+ setTimeout(() => resolve("pending"), 25),
1552
+ ),
1553
+ ]);
1554
+ expect(earlyState).toBe("pending");
1555
+
1556
+ restoreGate.resolve([
1557
+ {
1558
+ id: restoredRoomId,
1559
+ channelId: `web-conv-${restoredConversationId}`,
1560
+ name: "Restored Chat",
1561
+ },
1562
+ ]);
1563
+
1564
+ const response = await listPromise;
1565
+ expect(response.status).toBe(200);
1566
+ expect(response.data.conversations).toEqual([
1567
+ expect.objectContaining({
1568
+ id: restoredConversationId,
1569
+ title: "Restored Chat",
1570
+ }),
1571
+ ]);
1572
+ } finally {
1573
+ await streamServer.close();
1574
+ }
1575
+ });
1576
+
1577
+ it("GET /api/conversations/:id/messages waits for a pending restore before 404ing", async () => {
1578
+ const restoreGate =
1579
+ createDeferred<Array<{ id: UUID; channelId: string; name: string }>>();
1580
+ const restoredConversationId = "22222222-2222-4222-8222-222222222222";
1581
+ const restoredRoomId = "00000000-0000-0000-0000-00000000c002" as UUID;
1582
+ const restoredAt = Date.parse("2026-02-02T12:00:00.000Z");
1583
+ const runtime = {
1584
+ agentId: "restore-messages-agent",
1585
+ character: {
1586
+ name: "RestoreMessagesAgent",
1587
+ } as AgentRuntime["character"],
1588
+ getService: () => null,
1589
+ getRoomsByWorld: async () => restoreGate.promise,
1590
+ getMemories: async (query: { count?: number }) =>
1591
+ query.count === 1 ? ([{ createdAt: restoredAt }] as never[]) : [],
1592
+ getCache: async () => null,
1593
+ setCache: async () => {},
1594
+ } as unknown as AgentRuntime;
1595
+
1596
+ const streamServer = await startApiServer({ port: 0, runtime });
1597
+ try {
1598
+ const messagesPromise = req(
1599
+ streamServer.port,
1600
+ "GET",
1601
+ `/api/conversations/${restoredConversationId}/messages`,
1602
+ );
1603
+ const earlyState = await Promise.race([
1604
+ messagesPromise.then(() => "resolved" as const),
1605
+ new Promise<"pending">((resolve) =>
1606
+ setTimeout(() => resolve("pending"), 25),
1607
+ ),
1608
+ ]);
1609
+ expect(earlyState).toBe("pending");
1610
+
1611
+ restoreGate.resolve([
1612
+ {
1613
+ id: restoredRoomId,
1614
+ channelId: `web-conv-${restoredConversationId}`,
1615
+ name: "Restored Chat",
1616
+ },
1617
+ ]);
1618
+
1619
+ const response = await messagesPromise;
1620
+ expect(response.status).toBe(200);
1621
+ expect(response.data.messages).toEqual([]);
1622
+ } finally {
1623
+ await streamServer.close();
1624
+ }
1625
+ });
1626
+
1627
+ it("POST /api/conversations/:id/messages/stream emits token events from runtime onStreamChunk", async () => {
1628
+ const runtime = createRuntimeForChatSseTests({
1629
+ handleMessage: async (
1630
+ _runtime,
1631
+ _message,
1632
+ onResponse,
1633
+ messageOptions,
1634
+ ) => {
1635
+ await messageOptions?.onStreamChunk?.("Hello ");
1636
+ await messageOptions?.onStreamChunk?.("world");
1637
+ await onResponse({ text: "Hello world" } as Content);
1638
+ return {
1639
+ responseContent: {
1640
+ text: "Hello world",
1641
+ },
1642
+ };
1643
+ },
1644
+ });
1645
+ const streamServer = await startApiServer({ port: 0, runtime });
1646
+ try {
1647
+ const create = await req(
1648
+ streamServer.port,
1649
+ "POST",
1650
+ "/api/conversations",
1651
+ {
1652
+ title: "SSE onStreamChunk conversation",
1653
+ },
1654
+ );
1655
+ expect(create.status).toBe(200);
1656
+ const conversation = create.data.conversation as { id?: string };
1657
+ const conversationId = conversation.id ?? "";
1658
+ expect(conversationId.length).toBeGreaterThan(0);
1659
+
1660
+ const { status, events } = await reqSse(
1661
+ streamServer.port,
1662
+ `/api/conversations/${conversationId}/messages/stream`,
1663
+ { text: "hello", mode: "power" },
1664
+ );
1665
+
1666
+ expect(status).toBe(200);
1667
+ const tokenEvents = events.filter((event) => event.type === "token");
1668
+ expect(tokenEvents.map((event) => event.text)).toEqual([
1669
+ "Hello ",
1670
+ "world",
1671
+ ]);
1672
+ expect(tokenEvents.map((event) => event.fullText)).toEqual([
1673
+ "Hello ",
1674
+ "Hello world",
1675
+ ]);
1676
+
1677
+ const doneEvent = events.find((event) => event.type === "done");
1678
+ expect(doneEvent?.fullText).toBe("Hello world");
1679
+ expect(doneEvent?.agentName).toBe("ChatStreamAgent");
1680
+ } finally {
1681
+ await streamServer.close();
1682
+ }
1683
+ });
1684
+
1685
+ it("POST /api/conversations/:id/messages/stream emits token and done events", async () => {
1686
+ const runtime = createRuntimeForChatSseTests();
1687
+ const streamServer = await startApiServer({ port: 0, runtime });
1688
+ try {
1689
+ const create = await req(
1690
+ streamServer.port,
1691
+ "POST",
1692
+ "/api/conversations",
1693
+ {
1694
+ title: "SSE conversation",
1695
+ },
1696
+ );
1697
+ expect(create.status).toBe(200);
1698
+ const conversation = create.data.conversation as { id?: string };
1699
+ const conversationId = conversation.id ?? "";
1700
+ expect(conversationId.length).toBeGreaterThan(0);
1701
+
1702
+ const { status, events } = await reqSse(
1703
+ streamServer.port,
1704
+ `/api/conversations/${conversationId}/messages/stream`,
1705
+ { text: "hello", mode: "simple" },
1706
+ );
1707
+
1708
+ expect(status).toBe(200);
1709
+ const tokenEvents = events.filter((event) => event.type === "token");
1710
+ expect(tokenEvents.map((event) => event.text)).toEqual([
1711
+ "Hello ",
1712
+ "world",
1713
+ ]);
1714
+ const doneEvent = events.find((event) => event.type === "done");
1715
+ expect(doneEvent?.fullText).toBe("Hello world");
1716
+ expect(doneEvent?.agentName).toBe("ChatStreamAgent");
1717
+ } finally {
1718
+ await streamServer.close();
1719
+ }
1720
+ });
1721
+
1722
+ it("POST /api/conversations/:id/messages/stream emits replacement snapshots for corrected callback text", async () => {
1723
+ const runtime = createRuntimeForChatSseTests({
1724
+ handleMessage: async (_runtime, _message, onResponse) => {
1725
+ await onResponse({ text: "Hello wrld" } as Content);
1726
+ await onResponse({ text: "Hello world" } as Content);
1727
+ await onResponse({ text: "Hello world!" } as Content);
1728
+ return {
1729
+ responseContent: {
1730
+ text: "Hello world!",
1731
+ },
1732
+ };
1733
+ },
1734
+ });
1735
+ const streamServer = await startApiServer({ port: 0, runtime });
1736
+ try {
1737
+ const create = await req(
1738
+ streamServer.port,
1739
+ "POST",
1740
+ "/api/conversations",
1741
+ {
1742
+ title: "Corrected snapshot conversation",
1743
+ },
1744
+ );
1745
+ expect(create.status).toBe(200);
1746
+ const conversation = create.data.conversation as { id?: string };
1747
+ const conversationId = conversation.id ?? "";
1748
+ expect(conversationId.length).toBeGreaterThan(0);
1749
+
1750
+ const { status, events } = await reqSse(
1751
+ streamServer.port,
1752
+ `/api/conversations/${conversationId}/messages/stream`,
1753
+ { text: "hello", mode: "power" },
1754
+ );
1755
+
1756
+ expect(status).toBe(200);
1757
+ const tokenEvents = events.filter((event) => event.type === "token");
1758
+ expect(tokenEvents.map((event) => event.text)).toEqual([
1759
+ "Hello wrld",
1760
+ "Hello world",
1761
+ "!",
1762
+ ]);
1763
+ expect(tokenEvents.map((event) => event.fullText)).toEqual([
1764
+ "Hello wrld",
1765
+ "Hello world",
1766
+ "Hello world!",
1767
+ ]);
1768
+
1769
+ const doneEvent = events.find((event) => event.type === "done");
1770
+ expect(doneEvent?.fullText).toBe("Hello world!");
1771
+ expect(doneEvent?.agentName).toBe("ChatStreamAgent");
1772
+ } finally {
1773
+ await streamServer.close();
1774
+ }
1775
+ });
1776
+
1777
+ it("persists greeting and streamed turn messages to conversation memory", async () => {
1778
+ const runtime = createRuntimeForChatSseTests();
1779
+ const streamServer = await startApiServer({ port: 0, runtime });
1780
+ try {
1781
+ const create = await req(
1782
+ streamServer.port,
1783
+ "POST",
1784
+ "/api/conversations",
1785
+ {
1786
+ title: "Persistence test",
1787
+ },
1788
+ );
1789
+ expect(create.status).toBe(200);
1790
+ const conversation = create.data.conversation as { id?: string };
1791
+ const conversationId = conversation.id ?? "";
1792
+ expect(conversationId.length).toBeGreaterThan(0);
1793
+
1794
+ const greeting = await req(
1795
+ streamServer.port,
1796
+ "POST",
1797
+ `/api/conversations/${conversationId}/greeting`,
1798
+ );
1799
+ expect(greeting.status).toBe(200);
1800
+ const greetingText = String(greeting.data.text ?? "");
1801
+ expect(greetingText.length).toBeGreaterThan(0);
1802
+
1803
+ const stream = await reqSse(
1804
+ streamServer.port,
1805
+ `/api/conversations/${conversationId}/messages/stream`,
1806
+ { text: "hello", mode: "simple" },
1807
+ );
1808
+ expect(stream.status).toBe(200);
1809
+
1810
+ const messagesResponse = await req(
1811
+ streamServer.port,
1812
+ "GET",
1813
+ `/api/conversations/${conversationId}/messages`,
1814
+ );
1815
+ expect(messagesResponse.status).toBe(200);
1816
+ const messages = (messagesResponse.data.messages ?? []) as Array<
1817
+ Record<string, unknown>
1818
+ >;
1819
+ expect(messages.length).toBeGreaterThanOrEqual(3);
1820
+
1821
+ const greetingPersisted = messages.some(
1822
+ (message) =>
1823
+ message.role === "assistant" && message.text === greetingText,
1824
+ );
1825
+ const userPersisted = messages.some(
1826
+ (message) => message.role === "user" && message.text === "hello",
1827
+ );
1828
+ const assistantPersisted = messages.some(
1829
+ (message) =>
1830
+ message.role === "assistant" && message.text === "Hello world",
1831
+ );
1832
+
1833
+ expect(greetingPersisted).toBe(true);
1834
+ expect(userPersisted).toBe(true);
1835
+ expect(assistantPersisted).toBe(true);
1836
+ } finally {
1837
+ await streamServer.close();
1838
+ }
1839
+ });
1840
+
1841
+ it("POST /api/conversations can bootstrap the intro greeting atomically", async () => {
1842
+ const runtime = createRuntimeForChatSseTests();
1843
+ const streamServer = await startApiServer({ port: 0, runtime });
1844
+ try {
1845
+ const create = await req(
1846
+ streamServer.port,
1847
+ "POST",
1848
+ "/api/conversations",
1849
+ {
1850
+ title: "Bootstrap greeting test",
1851
+ bootstrapGreeting: true,
1852
+ lang: "en",
1853
+ },
1854
+ );
1855
+ expect(create.status).toBe(200);
1856
+ const conversation = create.data.conversation as { id?: string };
1857
+ const conversationId = conversation.id ?? "";
1858
+ expect(conversationId.length).toBeGreaterThan(0);
1859
+ expect(create.data.greeting).toMatchObject({
1860
+ text: "Welcome to the conversation.",
1861
+ agentName: "ChatStreamAgent",
1862
+ generated: true,
1863
+ });
1864
+
1865
+ const messagesResponse = await req(
1866
+ streamServer.port,
1867
+ "GET",
1868
+ `/api/conversations/${conversationId}/messages`,
1869
+ );
1870
+ expect(messagesResponse.status).toBe(200);
1871
+ const messages = (messagesResponse.data.messages ?? []) as Array<
1872
+ Record<string, unknown>
1873
+ >;
1874
+ expect(messages).toEqual([
1875
+ expect.objectContaining({
1876
+ role: "assistant",
1877
+ text: "Welcome to the conversation.",
1878
+ source: "agent_greeting",
1879
+ }),
1880
+ ]);
1881
+ } finally {
1882
+ await streamServer.close();
1883
+ }
1884
+ });
1885
+
1886
+ it("POST /api/conversations/:id/greeting is idempotent after the intro is stored", async () => {
1887
+ const runtime = createRuntimeForChatSseTests();
1888
+ const streamServer = await startApiServer({ port: 0, runtime });
1889
+ try {
1890
+ const create = await req(
1891
+ streamServer.port,
1892
+ "POST",
1893
+ "/api/conversations",
1894
+ {
1895
+ title: "Idempotent greeting test",
1896
+ bootstrapGreeting: true,
1897
+ lang: "en",
1898
+ },
1899
+ );
1900
+ expect(create.status).toBe(200);
1901
+ const conversation = create.data.conversation as { id?: string };
1902
+ const conversationId = conversation.id ?? "";
1903
+ expect(conversationId.length).toBeGreaterThan(0);
1904
+
1905
+ const greeting = await req(
1906
+ streamServer.port,
1907
+ "POST",
1908
+ `/api/conversations/${conversationId}/greeting?lang=en`,
1909
+ );
1910
+ expect(greeting.status).toBe(200);
1911
+ expect(greeting.data).toMatchObject({
1912
+ text: "Welcome to the conversation.",
1913
+ agentName: "ChatStreamAgent",
1914
+ generated: true,
1915
+ });
1916
+
1917
+ const messagesResponse = await req(
1918
+ streamServer.port,
1919
+ "GET",
1920
+ `/api/conversations/${conversationId}/messages`,
1921
+ );
1922
+ expect(messagesResponse.status).toBe(200);
1923
+ const messages = (messagesResponse.data.messages ?? []) as Array<
1924
+ Record<string, unknown>
1925
+ >;
1926
+ const greetings = messages.filter(
1927
+ (message) =>
1928
+ message.role === "assistant" &&
1929
+ message.source === "agent_greeting" &&
1930
+ message.text === "Welcome to the conversation.",
1931
+ );
1932
+ expect(greetings).toHaveLength(1);
1933
+ } finally {
1934
+ await streamServer.close();
1935
+ }
1936
+ });
1937
+
1938
+ it("GET /api/conversations/:id/messages returns an empty messages array on runtime read failure", async () => {
1939
+ const runtime = createRuntimeForChatSseTests();
1940
+ runtime.getMemories = async () => {
1941
+ throw new Error("forced-memories-failure");
1942
+ };
1943
+
1944
+ const streamServer = await startApiServer({ port: 0, runtime });
1945
+ try {
1946
+ const create = await req(
1947
+ streamServer.port,
1948
+ "POST",
1949
+ "/api/conversations",
1950
+ {
1951
+ title: "Conversation messages failure fallback",
1952
+ },
1953
+ );
1954
+ expect(create.status).toBe(200);
1955
+ const conversation = create.data.conversation as { id?: string };
1956
+ const conversationId = conversation.id ?? "";
1957
+ expect(conversationId.length).toBeGreaterThan(0);
1958
+
1959
+ const response = await req(
1960
+ streamServer.port,
1961
+ "GET",
1962
+ `/api/conversations/${conversationId}/messages`,
1963
+ );
1964
+
1965
+ expect(response.status).toBe(500);
1966
+ expect(Array.isArray(response.data.messages)).toBe(true);
1967
+ expect((response.data.messages as unknown[]).length).toBe(0);
1968
+ expect(typeof response.data.error).toBe("string");
1969
+ } finally {
1970
+ await streamServer.close();
1971
+ }
1972
+ });
1973
+ });
1974
+
1975
+ describe("trajectory endpoints (runtime stub)", () => {
1976
+ it("GET /api/trajectories/:id returns llm calls from plugin detail payload", async () => {
1977
+ const trajectoryId = "trajectory-array-shape";
1978
+ const startTime = Date.now() - 2_000;
1979
+ const endTime = startTime + 1_200;
1980
+ const callTimestamp = startTime + 400;
1981
+ const rawSteps = [
1982
+ {
1983
+ stepId: "step-1",
1984
+ stepNumber: 1,
1985
+ timestamp: startTime + 100,
1986
+ llmCalls: [
1987
+ {
1988
+ callId: "call-1",
1989
+ timestamp: callTimestamp,
1990
+ model: "unit-test-model",
1991
+ systemPrompt: "system",
1992
+ userPrompt: "hello from fallback",
1993
+ response: "fallback response",
1994
+ temperature: 0.1,
1995
+ maxTokens: 512,
1996
+ purpose: "response",
1997
+ promptTokens: 12,
1998
+ completionTokens: 18,
1999
+ latencyMs: 33,
2000
+ },
2001
+ ],
2002
+ providerAccesses: [],
2003
+ },
2004
+ ];
2005
+
2006
+ const trajectoryLogger = {
2007
+ isEnabled: () => true,
2008
+ setEnabled: () => {},
2009
+ listTrajectories: async () => ({
2010
+ trajectories: [
2011
+ {
2012
+ id: trajectoryId,
2013
+ agentId: "chat-stream-agent",
2014
+ source: "client_chat",
2015
+ status: "completed",
2016
+ startTime,
2017
+ endTime,
2018
+ durationMs: endTime - startTime,
2019
+ stepCount: 1,
2020
+ llmCallCount: 1,
2021
+ totalPromptTokens: 12,
2022
+ totalCompletionTokens: 18,
2023
+ totalReward: 0,
2024
+ scenarioId: null,
2025
+ batchId: null,
2026
+ createdAt: new Date(startTime).toISOString(),
2027
+ },
2028
+ ],
2029
+ total: 1,
2030
+ offset: 0,
2031
+ limit: 50,
2032
+ }),
2033
+ getTrajectoryDetail: async () => ({
2034
+ trajectoryId,
2035
+ agentId: "chat-stream-agent",
2036
+ startTime,
2037
+ endTime,
2038
+ durationMs: endTime - startTime,
2039
+ steps: rawSteps,
2040
+ totalReward: 0,
2041
+ metrics: {
2042
+ episodeLength: 1,
2043
+ finalStatus: "completed",
2044
+ },
2045
+ metadata: {
2046
+ source: "client_chat",
2047
+ },
2048
+ }),
2049
+ getStats: async () => ({
2050
+ totalTrajectories: 1,
2051
+ totalSteps: 1,
2052
+ totalLlmCalls: 1,
2053
+ totalPromptTokens: 12,
2054
+ totalCompletionTokens: 18,
2055
+ averageDurationMs: endTime - startTime,
2056
+ averageReward: 0,
2057
+ bySource: { client_chat: 1 },
2058
+ byStatus: { completed: 1 },
2059
+ byScenario: {},
2060
+ }),
2061
+ deleteTrajectories: async () => 0,
2062
+ clearAllTrajectories: async () => 0,
2063
+ exportTrajectories: async () => ({
2064
+ data: "[]",
2065
+ filename: "trajectories.json",
2066
+ mimeType: "application/json",
2067
+ }),
2068
+ };
2069
+
2070
+ const runtime = createRuntimeForChatSseTests({
2071
+ getService: (serviceType) =>
2072
+ serviceType === "trajectory_logger" ? trajectoryLogger : null,
2073
+ getServicesByType: (serviceType) =>
2074
+ serviceType === "trajectory_logger" ? [trajectoryLogger] : [],
2075
+ }) as AgentRuntime & { adapter?: unknown };
2076
+ runtime.adapter = {};
2077
+
2078
+ const streamServer = await startApiServer({ port: 0, runtime });
2079
+ try {
2080
+ const list = await req(
2081
+ streamServer.port,
2082
+ "GET",
2083
+ "/api/trajectories?limit=10",
2084
+ );
2085
+ expect(list.status).toBe(200);
2086
+ const listRows = list.data.trajectories as Array<
2087
+ Record<string, unknown>
2088
+ >;
2089
+ expect(Array.isArray(listRows)).toBe(true);
2090
+ expect(listRows[0]?.llmCallCount).toBe(1);
2091
+
2092
+ const detail = await req(
2093
+ streamServer.port,
2094
+ "GET",
2095
+ `/api/trajectories/${encodeURIComponent(trajectoryId)}`,
2096
+ );
2097
+ expect(detail.status).toBe(200);
2098
+ const llmCalls = detail.data.llmCalls as Array<Record<string, unknown>>;
2099
+ expect(Array.isArray(llmCalls)).toBe(true);
2100
+ expect(llmCalls).toHaveLength(1);
2101
+ expect(llmCalls[0]?.userPrompt).toBe("hello from fallback");
2102
+ expect(llmCalls[0]?.response).toBe("fallback response");
2103
+ expect(llmCalls[0]?.promptTokens).toBe(12);
2104
+ expect(llmCalls[0]?.completionTokens).toBe(18);
2105
+ } finally {
2106
+ await streamServer.close();
2107
+ }
2108
+ });
2109
+
2110
+ it("POST /api/trajectories/export returns a zip with trajectory folders", async () => {
2111
+ const trajectoryId = "trajectory-zip-export";
2112
+ const startTime = Date.now() - 2_000;
2113
+ const endTime = startTime + 1_100;
2114
+
2115
+ const trajectoryLogger = {
2116
+ isEnabled: () => true,
2117
+ setEnabled: () => {},
2118
+ listTrajectories: async () => ({
2119
+ trajectories: [
2120
+ {
2121
+ id: trajectoryId,
2122
+ agentId: "chat-stream-agent",
2123
+ source: "client_chat",
2124
+ status: "completed",
2125
+ startTime,
2126
+ endTime,
2127
+ durationMs: endTime - startTime,
2128
+ stepCount: 1,
2129
+ llmCallCount: 1,
2130
+ totalPromptTokens: 10,
2131
+ totalCompletionTokens: 20,
2132
+ totalReward: 0,
2133
+ scenarioId: null,
2134
+ batchId: null,
2135
+ createdAt: new Date(startTime).toISOString(),
2136
+ },
2137
+ ],
2138
+ total: 1,
2139
+ offset: 0,
2140
+ limit: 50,
2141
+ }),
2142
+ getTrajectoryDetail: async () => ({
2143
+ trajectoryId,
2144
+ agentId: "chat-stream-agent",
2145
+ startTime,
2146
+ endTime,
2147
+ durationMs: endTime - startTime,
2148
+ steps: [
2149
+ {
2150
+ stepId: "step-1",
2151
+ stepNumber: 1,
2152
+ timestamp: startTime + 100,
2153
+ llmCalls: [
2154
+ {
2155
+ callId: "call-1",
2156
+ timestamp: startTime + 200,
2157
+ model: "test-model",
2158
+ systemPrompt: "system",
2159
+ userPrompt: "hello",
2160
+ response: "world",
2161
+ temperature: 0.1,
2162
+ maxTokens: 200,
2163
+ purpose: "response",
2164
+ promptTokens: 10,
2165
+ completionTokens: 20,
2166
+ latencyMs: 12,
2167
+ },
2168
+ ],
2169
+ providerAccesses: [],
2170
+ },
2171
+ ],
2172
+ totalReward: 0,
2173
+ metrics: {
2174
+ episodeLength: 1,
2175
+ finalStatus: "completed",
2176
+ },
2177
+ metadata: {
2178
+ source: "client_chat",
2179
+ },
2180
+ }),
2181
+ getStats: async () => ({
2182
+ totalTrajectories: 1,
2183
+ totalSteps: 1,
2184
+ totalLlmCalls: 1,
2185
+ totalPromptTokens: 10,
2186
+ totalCompletionTokens: 20,
2187
+ averageDurationMs: endTime - startTime,
2188
+ averageReward: 0,
2189
+ bySource: { client_chat: 1 },
2190
+ byStatus: { completed: 1 },
2191
+ byScenario: {},
2192
+ }),
2193
+ deleteTrajectories: async () => 0,
2194
+ clearAllTrajectories: async () => 0,
2195
+ exportTrajectories: async () => ({
2196
+ data: "[]",
2197
+ filename: "trajectories.json",
2198
+ mimeType: "application/json",
2199
+ }),
2200
+ exportTrajectoriesZip: async () => ({
2201
+ filename: "trajectories-export.zip",
2202
+ entries: [
2203
+ { name: "manifest.json", data: "{}" },
2204
+ { name: `${trajectoryId}/summary.json`, data: "{}" },
2205
+ { name: `${trajectoryId}/trajectory.json`, data: "{}" },
2206
+ ],
2207
+ }),
2208
+ };
2209
+
2210
+ const runtime = createRuntimeForChatSseTests({
2211
+ getService: (serviceType) =>
2212
+ serviceType === "trajectory_logger" ? trajectoryLogger : null,
2213
+ getServicesByType: (serviceType) =>
2214
+ serviceType === "trajectory_logger" ? [trajectoryLogger] : [],
2215
+ }) as AgentRuntime & { adapter?: unknown };
2216
+ runtime.adapter = {};
2217
+
2218
+ const streamServer = await startApiServer({ port: 0, runtime });
2219
+ try {
2220
+ const zipRes = await reqRaw(
2221
+ streamServer.port,
2222
+ "POST",
2223
+ "/api/trajectories/export",
2224
+ { format: "zip" },
2225
+ );
2226
+ expect(zipRes.status).toBe(200);
2227
+ expect(String(zipRes.headers["content-type"] ?? "")).toContain(
2228
+ "application/zip",
2229
+ );
2230
+ expect(String(zipRes.headers["content-disposition"] ?? "")).toContain(
2231
+ ".zip",
2232
+ );
2233
+ expect(zipRes.data.subarray(0, 2).toString("utf-8")).toBe("PK");
2234
+ const zipText = zipRes.data.toString("utf-8");
2235
+ expect(zipText).toContain("manifest.json");
2236
+ expect(zipText).toContain(`${trajectoryId}/summary.json`);
2237
+ } finally {
2238
+ await streamServer.close();
2239
+ }
2240
+ });
2241
+
2242
+ it("GET/PUT /api/trajectories/config reflects plugin logger enabled state", async () => {
2243
+ let enabled = false;
2244
+ const setEnabledCalls: boolean[] = [];
2245
+
2246
+ const trajectoryLogger = {
2247
+ isEnabled: () => enabled,
2248
+ setEnabled: (next: boolean) => {
2249
+ setEnabledCalls.push(next);
2250
+ enabled = next;
2251
+ },
2252
+ listTrajectories: async () => ({
2253
+ trajectories: [],
2254
+ total: 0,
2255
+ offset: 0,
2256
+ limit: 50,
2257
+ }),
2258
+ getTrajectoryDetail: async () => null,
2259
+ getStats: async () => ({
2260
+ totalTrajectories: 0,
2261
+ totalSteps: 0,
2262
+ totalLlmCalls: 0,
2263
+ totalPromptTokens: 0,
2264
+ totalCompletionTokens: 0,
2265
+ averageDurationMs: 0,
2266
+ averageReward: 0,
2267
+ bySource: {},
2268
+ byStatus: {},
2269
+ byScenario: {},
2270
+ }),
2271
+ deleteTrajectories: async () => 0,
2272
+ clearAllTrajectories: async () => 0,
2273
+ exportTrajectories: async () => ({
2274
+ data: "[]",
2275
+ filename: "trajectories.json",
2276
+ mimeType: "application/json",
2277
+ }),
2278
+ };
2279
+
2280
+ const runtime = createRuntimeForChatSseTests({
2281
+ getService: (serviceType) =>
2282
+ serviceType === "trajectory_logger" ? trajectoryLogger : null,
2283
+ getServicesByType: (serviceType) =>
2284
+ serviceType === "trajectory_logger" ? [trajectoryLogger] : [],
2285
+ }) as AgentRuntime & { adapter?: unknown };
2286
+ runtime.adapter = {};
2287
+
2288
+ const streamServer = await startApiServer({ port: 0, runtime });
2289
+ try {
2290
+ const before = await req(
2291
+ streamServer.port,
2292
+ "GET",
2293
+ "/api/trajectories/config",
2294
+ );
2295
+ expect(before.status).toBe(200);
2296
+ expect(before.data.enabled).toBe(false);
2297
+
2298
+ const enabledUpdate = await req(
2299
+ streamServer.port,
2300
+ "PUT",
2301
+ "/api/trajectories/config",
2302
+ { enabled: true },
2303
+ );
2304
+ expect(enabledUpdate.status).toBe(200);
2305
+ expect(enabledUpdate.data.enabled).toBe(true);
2306
+ expect(enabled).toBe(true);
2307
+
2308
+ const disabledUpdate = await req(
2309
+ streamServer.port,
2310
+ "PUT",
2311
+ "/api/trajectories/config",
2312
+ { enabled: false },
2313
+ );
2314
+ expect(disabledUpdate.status).toBe(200);
2315
+ expect(disabledUpdate.data.enabled).toBe(false);
2316
+ expect(enabled).toBe(false);
2317
+ expect(setEnabledCalls).toEqual([true, false]);
2318
+ } finally {
2319
+ await streamServer.close();
2320
+ }
2321
+ });
2322
+
2323
+ it.skip("persists core trajectory rows to DB and loads them after restart", async () => {
2324
+ type RawSqlQuery = {
2325
+ queryChunks?: Array<{
2326
+ value?: string[];
2327
+ }>;
2328
+ };
2329
+
2330
+ const readSqlText = (query: RawSqlQuery): string => {
2331
+ const chunks = query.queryChunks ?? [];
2332
+ return chunks
2333
+ .map((chunk) =>
2334
+ Array.isArray(chunk.value) ? chunk.value.join("") : "",
2335
+ )
2336
+ .join("")
2337
+ .trim();
2338
+ };
2339
+
2340
+ const splitSqlTuple = (valueList: string): string[] => {
2341
+ const values: string[] = [];
2342
+ let current = "";
2343
+ let inString = false;
2344
+ for (let i = 0; i < valueList.length; i += 1) {
2345
+ const char = valueList[i];
2346
+ if (char === "'") {
2347
+ current += char;
2348
+ if (inString && valueList[i + 1] === "'") {
2349
+ current += "'";
2350
+ i += 1;
2351
+ continue;
2352
+ }
2353
+ inString = !inString;
2354
+ continue;
2355
+ }
2356
+ if (char === "," && !inString) {
2357
+ values.push(current.trim());
2358
+ current = "";
2359
+ continue;
2360
+ }
2361
+ current += char;
2362
+ }
2363
+ if (current.trim().length > 0) values.push(current.trim());
2364
+ return values;
2365
+ };
2366
+
2367
+ const parseSqlScalar = (token: string): string | number | null => {
2368
+ if (token.toUpperCase() === "NULL") return null;
2369
+ if (token.startsWith("'") && token.endsWith("'")) {
2370
+ return token.slice(1, -1).replace(/''/g, "'");
2371
+ }
2372
+ const asNumber = Number(token);
2373
+ return Number.isFinite(asNumber) ? asNumber : token;
2374
+ };
2375
+
2376
+ class InMemoryTrajectoryDb {
2377
+ private rows = new Map<string, Record<string, unknown>>();
2378
+
2379
+ async execute(query: RawSqlQuery): Promise<{ rows: unknown[] }> {
2380
+ const sql = readSqlText(query);
2381
+ const normalized = sql.toLowerCase().replace(/\s+/g, " ").trim();
2382
+
2383
+ if (
2384
+ normalized.startsWith("create table if not exists trajectories")
2385
+ ) {
2386
+ return { rows: [] };
2387
+ }
2388
+
2389
+ if (normalized.startsWith("insert into trajectories")) {
2390
+ const match =
2391
+ /insert into trajectories\s*\(([\s\S]+?)\)\s*values\s*\(([\s\S]+?)\)\s*on conflict/i.exec(
2392
+ sql,
2393
+ );
2394
+ if (!match) return { rows: [] };
2395
+ const columns = splitSqlTuple(match[1]).map((col) => col.trim());
2396
+ const values = splitSqlTuple(match[2]).map(parseSqlScalar);
2397
+ const row: Record<string, unknown> = {};
2398
+ for (let i = 0; i < columns.length; i += 1) {
2399
+ row[columns[i]] = values[i] ?? null;
2400
+ }
2401
+ const id = String(row.id ?? "");
2402
+ if (id) {
2403
+ const existing = this.rows.get(id) ?? {};
2404
+ this.rows.set(id, { ...existing, ...row });
2405
+ }
2406
+ return { rows: [] };
2407
+ }
2408
+
2409
+ if (normalized.startsWith("select * from trajectories")) {
2410
+ const limitMatch = /limit\s+(\d+)/i.exec(sql);
2411
+ const limit = limitMatch ? Number(limitMatch[1]) : 5000;
2412
+ const rows = Array.from(this.rows.values()).sort((a, b) =>
2413
+ String(b.created_at ?? "").localeCompare(
2414
+ String(a.created_at ?? ""),
2415
+ ),
2416
+ );
2417
+ return { rows: rows.slice(0, limit) };
2418
+ }
2419
+
2420
+ if (
2421
+ normalized.startsWith("select count(*) as total from trajectories")
2422
+ ) {
2423
+ return { rows: [{ total: this.rows.size }] };
2424
+ }
2425
+
2426
+ if (normalized.startsWith("delete from trajectories where id in")) {
2427
+ const inMatch = /where id in \(([\s\S]+)\)/i.exec(sql);
2428
+ const deleted: Array<Record<string, unknown>> = [];
2429
+ if (inMatch) {
2430
+ const ids = splitSqlTuple(inMatch[1])
2431
+ .map(parseSqlScalar)
2432
+ .filter((id): id is string => typeof id === "string");
2433
+ for (const id of ids) {
2434
+ if (this.rows.delete(id)) deleted.push({ id });
2435
+ }
2436
+ }
2437
+ return normalized.includes("returning id")
2438
+ ? { rows: deleted }
2439
+ : { rows: [] };
2440
+ }
2441
+
2442
+ if (normalized.startsWith("delete from trajectories")) {
2443
+ this.rows.clear();
2444
+ return { rows: [] };
2445
+ }
2446
+
2447
+ return { rows: [] };
2448
+ }
2449
+ }
2450
+
2451
+ const db = new InMemoryTrajectoryDb();
2452
+
2453
+ const createCoreLogger = () => {
2454
+ const llmCalls: Array<Record<string, unknown>> = [];
2455
+ const providerAccess: Array<Record<string, unknown>> = [];
2456
+ return {
2457
+ logLlmCall: (params: Record<string, unknown>) => {
2458
+ llmCalls.push({
2459
+ ...params,
2460
+ timestamp:
2461
+ typeof params.timestamp === "number"
2462
+ ? params.timestamp
2463
+ : Date.now(),
2464
+ });
2465
+ },
2466
+ logProviderAccess: (params: Record<string, unknown>) => {
2467
+ providerAccess.push({
2468
+ ...params,
2469
+ timestamp:
2470
+ typeof params.timestamp === "number"
2471
+ ? params.timestamp
2472
+ : Date.now(),
2473
+ });
2474
+ },
2475
+ getLlmCallLogs: () => llmCalls,
2476
+ getProviderAccessLogs: () => providerAccess,
2477
+ };
2478
+ };
2479
+
2480
+ const createRuntime = () => {
2481
+ const coreLogger = createCoreLogger();
2482
+ const runtime = createRuntimeForChatSseTests({
2483
+ getService: (serviceType) =>
2484
+ serviceType === "trajectory_logger" ? coreLogger : null,
2485
+ getServicesByType: (serviceType) =>
2486
+ serviceType === "trajectory_logger" ? [coreLogger] : [],
2487
+ handleMessage: async (runtimeArg, message, onResponse) => {
2488
+ const metadata =
2489
+ message && typeof message === "object" && "metadata" in message
2490
+ ? (message as { metadata?: { trajectoryStepId?: string } })
2491
+ .metadata
2492
+ : undefined;
2493
+ const stepId = metadata?.trajectoryStepId;
2494
+ if (stepId) {
2495
+ const logger = runtimeArg.getService("trajectory_logger") as {
2496
+ logLlmCall?: (params: Record<string, unknown>) => void;
2497
+ } | null;
2498
+ logger?.logLlmCall?.({
2499
+ stepId,
2500
+ model: "unit-test-model",
2501
+ systemPrompt: "system",
2502
+ userPrompt: "persist me",
2503
+ response: "persisted",
2504
+ temperature: 0,
2505
+ maxTokens: 32,
2506
+ purpose: "response",
2507
+ actionType: "test",
2508
+ promptTokens: 3,
2509
+ completionTokens: 4,
2510
+ latencyMs: 10,
2511
+ });
2512
+ }
2513
+ await onResponse({ text: "persisted" } as Content);
2514
+ return {
2515
+ responseContent: {
2516
+ text: "persisted",
2517
+ },
2518
+ };
2519
+ },
2520
+ }) as AgentRuntime & {
2521
+ adapter?: {
2522
+ db: { execute: (query: RawSqlQuery) => Promise<unknown> };
2523
+ };
2524
+ };
2525
+ runtime.adapter = { db };
2526
+ return runtime;
2527
+ };
2528
+
2529
+ const runtimeA = createRuntime();
2530
+ const serverA = await startApiServer({ port: 0, runtime: runtimeA });
2531
+ let firstTrajectoryId: string | null = null;
2532
+ try {
2533
+ const chat = await req(serverA.port, "POST", "/api/chat", {
2534
+ text: "persist this trajectory",
2535
+ mode: "simple",
2536
+ });
2537
+ expect(chat.status).toBe(200);
2538
+
2539
+ const list = await req(serverA.port, "GET", "/api/trajectories");
2540
+ expect(list.status).toBe(200);
2541
+ const rows = list.data.trajectories as Array<Record<string, unknown>>;
2542
+ expect(Array.isArray(rows)).toBe(true);
2543
+ expect(rows.length).toBeGreaterThan(0);
2544
+ firstTrajectoryId = String(rows[0]?.id ?? "");
2545
+ expect(firstTrajectoryId.length).toBeGreaterThan(0);
2546
+ } finally {
2547
+ await serverA.close();
2548
+ }
2549
+
2550
+ const runtimeB = createRuntime();
2551
+ const serverB = await startApiServer({ port: 0, runtime: runtimeB });
2552
+ try {
2553
+ const listAfterRestart = await req(
2554
+ serverB.port,
2555
+ "GET",
2556
+ "/api/trajectories",
2557
+ );
2558
+ expect(listAfterRestart.status).toBe(200);
2559
+ const rows = listAfterRestart.data.trajectories as Array<
2560
+ Record<string, unknown>
2561
+ >;
2562
+ expect(Array.isArray(rows)).toBe(true);
2563
+ expect(rows.length).toBeGreaterThan(0);
2564
+ const ids = rows.map((row) => String(row.id ?? ""));
2565
+ expect(ids).toContain(firstTrajectoryId);
2566
+ } finally {
2567
+ await serverB.close();
2568
+ }
2569
+ });
2570
+ });
2571
+
2572
+ describe("insufficient credits fallback", () => {
2573
+ it("POST /api/chat replaces '(no response)' with a top-up message", async () => {
2574
+ const runtime = createRuntimeForCreditNoResponseTests();
2575
+ const streamServer = await startApiServer({ port: 0, runtime });
2576
+ try {
2577
+ const { status, data } = await req(
2578
+ streamServer.port,
2579
+ "POST",
2580
+ "/api/chat",
2581
+ {
2582
+ text: "hello",
2583
+ mode: "power",
2584
+ },
2585
+ );
2586
+ expect(status).toBe(200);
2587
+ expect(String(data.text)).toMatch(/top up your credits/i);
2588
+ expect(String(data.text)).not.toBe("(no response)");
2589
+ } finally {
2590
+ await streamServer.close();
2591
+ }
2592
+ });
2593
+
2594
+ it("POST /api/chat/stream emits a done event with top-up text", async () => {
2595
+ const runtime = createRuntimeForCreditNoResponseTests();
2596
+ const streamServer = await startApiServer({ port: 0, runtime });
2597
+ try {
2598
+ const { status, events } = await reqSse(
2599
+ streamServer.port,
2600
+ "/api/chat/stream",
2601
+ { text: "hello", mode: "power" },
2602
+ );
2603
+ expect(status).toBe(200);
2604
+ const doneEvent = events.find((event) => event.type === "done");
2605
+ expect(doneEvent).toBeDefined();
2606
+ expect(String(doneEvent?.fullText ?? "")).toMatch(
2607
+ /top up your credits/i,
2608
+ );
2609
+ } finally {
2610
+ await streamServer.close();
2611
+ }
2612
+ });
2613
+
2614
+ it("POST /api/chat returns a top-up message when the provider throws insufficient credits", async () => {
2615
+ const runtime = createRuntimeForCreditErrorTests();
2616
+ const streamServer = await startApiServer({ port: 0, runtime });
2617
+ try {
2618
+ const { status, data } = await req(
2619
+ streamServer.port,
2620
+ "POST",
2621
+ "/api/chat",
2622
+ {
2623
+ text: "hello",
2624
+ mode: "power",
2625
+ },
2626
+ );
2627
+ expect(status).toBe(200);
2628
+ expect(String(data.text)).toMatch(/top up your credits/i);
2629
+ } finally {
2630
+ await streamServer.close();
2631
+ }
2632
+ });
2633
+
2634
+ it("POST /api/chat replaces literal '(no response)' payloads with a top-up message", async () => {
2635
+ const runtime = createRuntimeForCreditLiteralNoResponseTests();
2636
+ const streamServer = await startApiServer({ port: 0, runtime });
2637
+ try {
2638
+ const { status, data } = await req(
2639
+ streamServer.port,
2640
+ "POST",
2641
+ "/api/chat",
2642
+ {
2643
+ text: "hello",
2644
+ mode: "power",
2645
+ },
2646
+ );
2647
+ expect(status).toBe(200);
2648
+ expect(String(data.text)).toMatch(/top up your credits/i);
2649
+ expect(String(data.text)).not.toBe("(no response)");
2650
+ } finally {
2651
+ await streamServer.close();
2652
+ }
2653
+ });
2654
+
2655
+ it("GET /api/trajectories prefers route-compatible logger when byType contains core logger", async () => {
2656
+ const coreLogger = {
2657
+ logLlmCall: () => {},
2658
+ };
2659
+
2660
+ const fullLogger = {
2661
+ isEnabled: () => true,
2662
+ setEnabled: () => {},
2663
+ listTrajectories: async () => ({
2664
+ trajectories: [
2665
+ {
2666
+ id: "trajectory-1",
2667
+ agentId: "chat-stream-agent",
2668
+ source: "client_chat",
2669
+ status: "completed",
2670
+ startTime: Date.now() - 1000,
2671
+ endTime: Date.now(),
2672
+ durationMs: 1000,
2673
+ stepCount: 1,
2674
+ llmCallCount: 1,
2675
+ totalPromptTokens: 10,
2676
+ totalCompletionTokens: 20,
2677
+ totalReward: 0,
2678
+ scenarioId: null,
2679
+ batchId: null,
2680
+ createdAt: new Date().toISOString(),
2681
+ },
2682
+ ],
2683
+ total: 1,
2684
+ offset: 0,
2685
+ limit: 50,
2686
+ }),
2687
+ getTrajectoryDetail: async () => null,
2688
+ getStats: async () => ({
2689
+ totalTrajectories: 1,
2690
+ totalSteps: 1,
2691
+ totalLlmCalls: 1,
2692
+ totalPromptTokens: 10,
2693
+ totalCompletionTokens: 20,
2694
+ averageDurationMs: 1000,
2695
+ averageReward: 0,
2696
+ bySource: { client_chat: 1 },
2697
+ byStatus: { completed: 1 },
2698
+ byScenario: {},
2699
+ }),
2700
+ deleteTrajectories: async () => 0,
2701
+ clearAllTrajectories: async () => 0,
2702
+ exportTrajectories: async () => ({
2703
+ data: "[]",
2704
+ filename: "trajectories.json",
2705
+ mimeType: "application/json",
2706
+ }),
2707
+ };
2708
+
2709
+ const runtime = createRuntimeForChatSseTests({
2710
+ getServicesByType: (serviceType) =>
2711
+ serviceType === "trajectory_logger" ? [coreLogger] : [],
2712
+ getService: (serviceType) =>
2713
+ serviceType === "trajectory_logger" ? fullLogger : null,
2714
+ }) as AgentRuntime & { adapter?: unknown };
2715
+ runtime.adapter = {};
2716
+
2717
+ const streamServer = await startApiServer({ port: 0, runtime });
2718
+ try {
2719
+ const res = await req(streamServer.port, "GET", "/api/trajectories");
2720
+ expect(res.status).toBe(200);
2721
+ const rows = res.data.trajectories as Array<Record<string, unknown>>;
2722
+ expect(Array.isArray(rows)).toBe(true);
2723
+ expect(rows).toHaveLength(1);
2724
+ expect(rows[0]?.llmCallCount).toBe(1);
2725
+ } finally {
2726
+ await streamServer.close();
2727
+ }
2728
+ });
2729
+
2730
+ it("GET /api/trajectories returns 503 when no route-compatible logger is available", async () => {
2731
+ const runtime = createRuntimeForChatSseTests({
2732
+ getServicesByType: (serviceType) =>
2733
+ serviceType === "trajectory_logger" ? [{ logLlmCall: () => {} }] : [],
2734
+ getService: (serviceType) =>
2735
+ serviceType === "trajectory_logger" ? { logLlmCall: () => {} } : null,
2736
+ }) as AgentRuntime & { adapter?: unknown };
2737
+ runtime.adapter = {};
2738
+
2739
+ const streamServer = await startApiServer({ port: 0, runtime });
2740
+ try {
2741
+ const res = await req(streamServer.port, "GET", "/api/trajectories");
2742
+ expect(res.status).toBe(503);
2743
+ expect(String(res.data.error)).toContain(
2744
+ "Trajectory logger service not available",
2745
+ );
2746
+ } finally {
2747
+ await streamServer.close();
2748
+ }
2749
+ });
2750
+ });
2751
+
2752
+ describe("trigger endpoints (no runtime)", () => {
2753
+ it("GET /api/triggers returns 503", async () => {
2754
+ const { status, data } = await req(port, "GET", "/api/triggers");
2755
+ expect(status).toBe(503);
2756
+ expect(String(data.error)).toContain("not running");
2757
+ });
2758
+
2759
+ it("POST /api/triggers returns 503", async () => {
2760
+ const { status } = await req(port, "POST", "/api/triggers", {
2761
+ displayName: "Heartbeat",
2762
+ instructions: "Status heartbeat",
2763
+ triggerType: "interval",
2764
+ intervalMs: 120000,
2765
+ });
2766
+ expect(status).toBe(503);
2767
+ });
2768
+
2769
+ it("GET /api/triggers/health returns 503", async () => {
2770
+ const { status } = await req(port, "GET", "/api/triggers/health");
2771
+ expect(status).toBe(503);
2772
+ });
2773
+ });
2774
+
2775
+ // -- Fine-tuning endpoints --
2776
+
2777
+ describe("GET /api/training/* (no runtime)", () => {
2778
+ it("returns training status with runtimeUnavailable", async () => {
2779
+ const { status, data } = await req(port, "GET", "/api/training/status");
2780
+ expect(status).toBe(200);
2781
+ expect(data.runtimeAvailable).toBe(false);
2782
+ expect(typeof data.runningJobs).toBe("number");
2783
+ expect(typeof data.datasetCount).toBe("number");
2784
+ expect(typeof data.modelCount).toBe("number");
2785
+ });
2786
+
2787
+ it("returns unavailable trajectories when runtime is missing", async () => {
2788
+ const { status, data } = await req(
2789
+ port,
2790
+ "GET",
2791
+ "/api/training/trajectories?limit=10&offset=0",
2792
+ );
2793
+ expect(status).toBe(200);
2794
+ expect(data.available).toBe(false);
2795
+ expect(data.reason).toBe("runtime_not_started");
2796
+ });
2797
+
2798
+ it("returns datasets, jobs, and models lists", async () => {
2799
+ const datasets = await req(port, "GET", "/api/training/datasets");
2800
+ const jobs = await req(port, "GET", "/api/training/jobs");
2801
+ const models = await req(port, "GET", "/api/training/models");
2802
+
2803
+ expect(datasets.status).toBe(200);
2804
+ expect(jobs.status).toBe(200);
2805
+ expect(models.status).toBe(200);
2806
+ expect(Array.isArray(datasets.data.datasets)).toBe(true);
2807
+ expect(Array.isArray(jobs.data.jobs)).toBe(true);
2808
+ expect(Array.isArray(models.data.models)).toBe(true);
2809
+ });
2810
+
2811
+ it("returns 404 for missing trajectory and missing job", async () => {
2812
+ const trajectory = await req(
2813
+ port,
2814
+ "GET",
2815
+ "/api/training/trajectories/not-found",
2816
+ );
2817
+ const job = await req(port, "GET", "/api/training/jobs/not-found");
2818
+ expect(trajectory.status).toBe(404);
2819
+ expect(job.status).toBe(404);
2820
+ });
2821
+
2822
+ it("streams dataset build events over websocket", async () => {
2823
+ const ws = new WebSocket(`ws://127.0.0.1:${port}/ws`);
2824
+ try {
2825
+ await waitForWsMessage(ws, (message) => message.type === "status");
2826
+ const waitForDatasetBuilt = waitForWsMessage(
2827
+ ws,
2828
+ (message) =>
2829
+ message.type === "training_event" &&
2830
+ ((message.payload as Record<string, unknown>)?.kind as string) ===
2831
+ "dataset_built",
2832
+ );
2833
+
2834
+ const response = await req(
2835
+ port,
2836
+ "POST",
2837
+ "/api/training/datasets/build",
2838
+ {
2839
+ limit: 5,
2840
+ minLlmCallsPerTrajectory: 1,
2841
+ },
2842
+ );
2843
+ expect(response.status).toBe(201);
2844
+
2845
+ const message = await waitForDatasetBuilt;
2846
+ expect(message.type).toBe("training_event");
2847
+ expect((message.payload as Record<string, unknown>)?.kind).toBe(
2848
+ "dataset_built",
2849
+ );
2850
+ } finally {
2851
+ ws.close();
2852
+ }
2853
+ });
2854
+
2855
+ it("returns 400 for invalid job/model mutation requests", async () => {
2856
+ const startJob = await req(port, "POST", "/api/training/jobs", {
2857
+ datasetId: "dataset-does-not-exist",
2858
+ });
2859
+ const importModel = await req(
2860
+ port,
2861
+ "POST",
2862
+ "/api/training/models/model-does-not-exist/import-ollama",
2863
+ {},
2864
+ );
2865
+ const activateModel = await req(
2866
+ port,
2867
+ "POST",
2868
+ "/api/training/models/model-does-not-exist/activate",
2869
+ {},
2870
+ );
2871
+ const benchmarkModel = await req(
2872
+ port,
2873
+ "POST",
2874
+ "/api/training/models/model-does-not-exist/benchmark",
2875
+ {},
2876
+ );
2877
+
2878
+ expect(startJob.status).toBe(400);
2879
+ expect(importModel.status).toBe(400);
2880
+ expect(activateModel.status).toBe(400);
2881
+ expect(benchmarkModel.status).toBe(400);
2882
+ });
2883
+
2884
+ it("returns 404 when cancelling unknown training job", async () => {
2885
+ const response = await req(
2886
+ port,
2887
+ "POST",
2888
+ "/api/training/jobs/job-does-not-exist/cancel",
2889
+ {},
2890
+ );
2891
+ expect(response.status).toBe(404);
2892
+ });
2893
+ });
2894
+
2895
+ // -- Plugin discovery (real filesystem) --
2896
+
2897
+ describe("GET /api/plugins", () => {
2898
+ it("returns a plugins array from real filesystem scan", async () => {
2899
+ const { status, data } = await req(port, "GET", "/api/plugins");
2900
+ expect(status).toBe(200);
2901
+ expect(Array.isArray(data.plugins)).toBe(true);
2902
+ });
2903
+
2904
+ it("plugins have correct shape", async () => {
2905
+ const { data } = await req(port, "GET", "/api/plugins");
2906
+ const plugins = data.plugins as Array<Record<string, unknown>>;
2907
+ if (plugins.length > 0) {
2908
+ const p = plugins[0];
2909
+ expect(typeof p.id).toBe("string");
2910
+ expect(typeof p.name).toBe("string");
2911
+ expect(typeof p.description).toBe("string");
2912
+ expect(typeof p.enabled).toBe("boolean");
2913
+ expect(typeof p.configured).toBe("boolean");
2914
+ expect(["ai-provider", "connector", "database", "feature"]).toContain(
2915
+ p.category,
2916
+ );
2917
+ expect(Array.isArray(p.configKeys)).toBe(true);
2918
+ }
2919
+ });
2920
+
2921
+ it("hides Vercel OIDC token key from plugin metadata", async () => {
2922
+ const { data } = await req(port, "GET", "/api/plugins");
2923
+ const plugins = data.plugins as Array<Record<string, unknown>>;
2924
+ const vercel = plugins.find((p) => p.id === "vercel-ai-gateway");
2925
+ if (!vercel) return;
2926
+
2927
+ const configKeys = Array.isArray(vercel.configKeys)
2928
+ ? (vercel.configKeys as string[])
2929
+ : [];
2930
+ expect(configKeys).not.toContain("VERCEL_OIDC_TOKEN");
2931
+
2932
+ const parameters = Array.isArray(vercel.parameters)
2933
+ ? (vercel.parameters as Array<Record<string, unknown>>)
2934
+ : [];
2935
+ const parameterKeys = parameters.map((param) => param.key);
2936
+ expect(parameterKeys).not.toContain("VERCEL_OIDC_TOKEN");
2937
+ });
2938
+ });
2939
+
2940
+ // -- Skills discovery --
2941
+
2942
+ describe("GET /api/skills", () => {
2943
+ it("returns a skills array", async () => {
2944
+ const { status, data } = await req(port, "GET", "/api/skills");
2945
+ expect(status).toBe(200);
2946
+ expect(Array.isArray(data.skills)).toBe(true);
2947
+ });
2948
+
2949
+ it("includes marketplace-installed skills from the hidden .marketplace directory", async () => {
2950
+ const marketplaceSkillDir = path.join(
2951
+ _e2eTempDir,
2952
+ "workspace",
2953
+ "skills",
2954
+ ".marketplace",
2955
+ "remote-skill",
2956
+ );
2957
+ await fs.mkdir(marketplaceSkillDir, { recursive: true });
2958
+ await fs.writeFile(
2959
+ path.join(marketplaceSkillDir, "SKILL.md"),
2960
+ [
2961
+ "---",
2962
+ "name: Remote Skill",
2963
+ "description: Installed from marketplace",
2964
+ "---",
2965
+ "",
2966
+ "# Remote skill",
2967
+ ].join("\n"),
2968
+ "utf-8",
2969
+ );
2970
+
2971
+ try {
2972
+ const refreshed = await req(port, "POST", "/api/skills/refresh", {});
2973
+ expect(refreshed.status).toBe(200);
2974
+ const skills = refreshed.data.skills as Array<{ id?: string }>;
2975
+ expect(skills.some((skill) => skill.id === "remote-skill")).toBe(true);
2976
+ } finally {
2977
+ await fs.rm(path.join(_e2eTempDir, "workspace", "skills"), {
2978
+ recursive: true,
2979
+ force: true,
2980
+ });
2981
+ await req(port, "POST", "/api/skills/refresh", {});
2982
+ }
2983
+ });
2984
+
2985
+ it("falls back to runtime-provided skill directories when AgentSkillsService is empty", async () => {
2986
+ const tempRoot = await fs.mkdtemp(
2987
+ path.join(os.tmpdir(), "milady-skills-"),
2988
+ );
2989
+ const bundledDir = path.join(tempRoot, "bundled-skills");
2990
+ const skillDir = path.join(bundledDir, "fallback-skill");
2991
+ await fs.mkdir(skillDir, { recursive: true });
2992
+ await fs.writeFile(
2993
+ path.join(skillDir, "SKILL.md"),
2994
+ [
2995
+ "---",
2996
+ "name: Fallback Skill",
2997
+ "description: Loads when catalog sync fails",
2998
+ "---",
2999
+ "",
3000
+ "# Fallback skill",
3001
+ ].join("\n"),
3002
+ "utf-8",
3003
+ );
3004
+
3005
+ const runtime = {
3006
+ agentId: "skills-fallback-agent",
3007
+ character: { name: "SkillsFallbackAgent" },
3008
+ getService: (serviceType: string) => {
3009
+ if (serviceType === "AGENT_SKILLS_SERVICE") {
3010
+ return {
3011
+ getLoadedSkills: () => [],
3012
+ };
3013
+ }
3014
+ return null;
3015
+ },
3016
+ getSetting: (key: string) => {
3017
+ if (key === "BUNDLED_SKILLS_DIRS") return bundledDir;
3018
+ return undefined;
3019
+ },
3020
+ getRoomsByWorld: async () => [],
3021
+ getTasks: async () => [],
3022
+ getTask: async () => null,
3023
+ createTask: async () => crypto.randomUUID() as UUID,
3024
+ updateTask: async () => {},
3025
+ deleteTask: async () => {},
3026
+ } as unknown as unknown as AgentRuntime;
3027
+
3028
+ const streamServer = await startApiServer({ port: 0, runtime });
3029
+ const streamPort = streamServer.port;
3030
+
3031
+ try {
3032
+ const refreshed = await req(
3033
+ streamPort,
3034
+ "POST",
3035
+ "/api/skills/refresh",
3036
+ {},
3037
+ );
3038
+ expect(refreshed.status).toBe(200);
3039
+ const skills = refreshed.data.skills as Array<{ id?: string }>;
3040
+ expect(skills.some((skill) => skill.id === "fallback-skill")).toBe(
3041
+ true,
3042
+ );
3043
+ } finally {
3044
+ await streamServer.close();
3045
+ await fs.rm(tempRoot, { recursive: true, force: true });
3046
+ }
3047
+ });
3048
+
3049
+ it("discovers managed skills from the state dir", async () => {
3050
+ const managedSkillDir = path.join(_e2eTempDir, "skills", "managed-skill");
3051
+ await fs.mkdir(managedSkillDir, { recursive: true });
3052
+ await fs.writeFile(
3053
+ path.join(managedSkillDir, "SKILL.md"),
3054
+ [
3055
+ "---",
3056
+ "name: managed-skill",
3057
+ "description: Seeded from the managed skills store",
3058
+ "---",
3059
+ "",
3060
+ "# Managed skill",
3061
+ ].join("\n"),
3062
+ "utf-8",
3063
+ );
3064
+
3065
+ try {
3066
+ const refreshed = await req(port, "POST", "/api/skills/refresh", {});
3067
+ expect(refreshed.status).toBe(200);
3068
+ const skills = refreshed.data.skills as Array<{ id?: string }>;
3069
+ expect(skills.some((skill) => skill.id === "managed-skill")).toBe(true);
3070
+ } finally {
3071
+ await fs.rm(path.join(_e2eTempDir, "skills"), {
3072
+ recursive: true,
3073
+ force: true,
3074
+ });
3075
+ await req(port, "POST", "/api/skills/refresh", {});
3076
+ }
3077
+ });
3078
+ });
3079
+
3080
+ describe("skills marketplace endpoints", () => {
3081
+ it("GET /api/skills/marketplace/search requires query", async () => {
3082
+ const { status, data } = await req(
3083
+ port,
3084
+ "GET",
3085
+ "/api/skills/marketplace/search",
3086
+ );
3087
+ expect(status).toBe(400);
3088
+ expect(String(data.error)).toContain("Query");
3089
+ });
3090
+
3091
+ it("GET /api/skills/marketplace/installed returns array", async () => {
3092
+ const { status, data } = await req(
3093
+ port,
3094
+ "GET",
3095
+ "/api/skills/marketplace/installed",
3096
+ );
3097
+ expect(status).toBe(200);
3098
+ expect(Array.isArray(data.skills)).toBe(true);
3099
+ });
3100
+
3101
+ it("GET /api/skills/marketplace/search reports upstream network failures", async () => {
3102
+ const savedRegistry = process.env.SKILLS_REGISTRY;
3103
+ try {
3104
+ process.env.SKILLS_REGISTRY = "http://127.0.0.1:1";
3105
+ const { status, data } = await req(
3106
+ port,
3107
+ "GET",
3108
+ "/api/skills/marketplace/search?q=agent",
3109
+ );
3110
+ expect(status).toBe(502);
3111
+ expect(String(data.error).toLowerCase()).toContain("network");
3112
+ } finally {
3113
+ if (savedRegistry === undefined) delete process.env.SKILLS_REGISTRY;
3114
+ else process.env.SKILLS_REGISTRY = savedRegistry;
3115
+ }
3116
+ });
3117
+
3118
+ it("POST /api/skills/marketplace/install validates source input", async () => {
3119
+ const { status, data } = await req(
3120
+ port,
3121
+ "POST",
3122
+ "/api/skills/marketplace/install",
3123
+ { name: "test" },
3124
+ );
3125
+ expect(status).toBe(400);
3126
+ expect(String(data.error)).toContain("githubUrl");
3127
+ });
3128
+
3129
+ it("POST /api/skills/marketplace/uninstall validates id input", async () => {
3130
+ const { status, data } = await req(
3131
+ port,
3132
+ "POST",
3133
+ "/api/skills/marketplace/uninstall",
3134
+ {},
3135
+ );
3136
+ expect(status).toBe(400);
3137
+ expect(String(data.error)).toContain("id");
3138
+ });
3139
+ });
3140
+
3141
+ // -- Logs --
3142
+
3143
+ describe("GET /api/logs", () => {
3144
+ it("returns entries array with at least the startup log", async () => {
3145
+ const { status, data } = await req(port, "GET", "/api/logs");
3146
+ expect(status).toBe(200);
3147
+ expect(Array.isArray(data.entries)).toBe(true);
3148
+ const entries = data.entries as Array<Record<string, unknown>>;
3149
+ expect(entries.length).toBeGreaterThan(0);
3150
+ // Verify log entry shape
3151
+ expect(typeof entries[0].timestamp).toBe("number");
3152
+ expect(typeof entries[0].level).toBe("string");
3153
+ expect(typeof entries[0].message).toBe("string");
3154
+ });
3155
+ });
3156
+
3157
+ describe("GET /api/agent/events", () => {
3158
+ it("returns replay response shape", async () => {
3159
+ const { status, data } = await req(port, "GET", "/api/agent/events");
3160
+ expect(status).toBe(200);
3161
+ expect(Array.isArray(data.events)).toBe(true);
3162
+ expect(typeof data.replayed).toBe("boolean");
3163
+ expect(typeof data.totalBuffered).toBe("number");
3164
+ expect(
3165
+ data.latestEventId === null || typeof data.latestEventId === "string",
3166
+ ).toBe(true);
3167
+ });
3168
+
3169
+ it("captures runtime AGENT_EVENT emissions in replay buffer", async () => {
3170
+ const eventService = new TestAgentEventService();
3171
+ const runtime = createRuntimeForStreamTests({
3172
+ eventService,
3173
+ loopRunning: false,
3174
+ });
3175
+ const streamServer = await startApiServer({ port: 0, runtime });
3176
+ try {
3177
+ eventService.emit({
3178
+ runId: "run-stream",
3179
+ seq: 1,
3180
+ stream: "assistant",
3181
+ ts: Date.now(),
3182
+ data: { text: "stream-check" },
3183
+ agentId: "test-agent-id",
3184
+ });
3185
+
3186
+ const { status, data } = await req(
3187
+ streamServer.port,
3188
+ "GET",
3189
+ "/api/agent/events",
3190
+ );
3191
+ expect(status).toBe(200);
3192
+ const events = data.events as Array<Record<string, unknown>>;
3193
+ const hasExpectedEvent = events.some((event) => {
3194
+ const payload = event.payload as Record<string, unknown>;
3195
+ return (
3196
+ event.type === "agent_event" && payload.text === "stream-check"
3197
+ );
3198
+ });
3199
+ expect(hasExpectedEvent).toBe(true);
3200
+ } finally {
3201
+ await streamServer.close();
3202
+ }
3203
+ });
3204
+
3205
+ it("streams AGENT_EVENT over websocket clients", async () => {
3206
+ const eventService = new TestAgentEventService();
3207
+ const runtime = createRuntimeForStreamTests({
3208
+ eventService,
3209
+ loopRunning: false,
3210
+ });
3211
+ const streamServer = await startApiServer({ port: 0, runtime });
3212
+ const ws = new WebSocket(`ws://127.0.0.1:${streamServer.port}/ws`);
3213
+ try {
3214
+ await waitForWsMessage(ws, (message) => message.type === "status");
3215
+
3216
+ const waitForAgentEvent = waitForWsMessage(
3217
+ ws,
3218
+ (message) =>
3219
+ message.type === "agent_event" &&
3220
+ ((message.payload as Record<string, unknown>)?.text as string) ===
3221
+ "ws-stream-check",
3222
+ );
3223
+
3224
+ eventService.emit({
3225
+ runId: "run-stream-ws",
3226
+ seq: 1,
3227
+ stream: "assistant",
3228
+ ts: Date.now(),
3229
+ data: { text: "ws-stream-check" },
3230
+ agentId: "test-agent-id",
3231
+ });
3232
+
3233
+ const message = await waitForAgentEvent;
3234
+ expect(message.type).toBe("agent_event");
3235
+ } finally {
3236
+ ws.close();
3237
+ await streamServer.close();
3238
+ }
3239
+ }, 30_000);
3240
+
3241
+ it("routes proactive autonomy output to active chat and exposes matching overview/event surfaces", async () => {
3242
+ const eventService = new TestAgentEventService();
3243
+ const runtime = createRuntimeForAutonomySurfaceTests({
3244
+ eventService,
3245
+ loopRunning: true,
3246
+ });
3247
+ const streamServer = await startApiServer({ port: 0, runtime });
3248
+ const ws = new WebSocket(`ws://127.0.0.1:${streamServer.port}/ws`);
3249
+ try {
3250
+ await waitForWsMessage(ws, (message) => message.type === "status");
3251
+
3252
+ const createConversation = await req(
3253
+ streamServer.port,
3254
+ "POST",
3255
+ "/api/conversations",
3256
+ {
3257
+ title: "Autonomy routing test",
3258
+ },
3259
+ );
3260
+ expect(createConversation.status).toBe(200);
3261
+ const conversation = createConversation.data.conversation as {
3262
+ id?: string;
3263
+ };
3264
+ const conversationId = conversation.id ?? "";
3265
+ expect(conversationId.length).toBeGreaterThan(0);
3266
+
3267
+ ws.send(
3268
+ JSON.stringify({
3269
+ type: "active-conversation",
3270
+ conversationId,
3271
+ }),
3272
+ );
3273
+
3274
+ const waitForThought = waitForWsMessage(
3275
+ ws,
3276
+ (message) =>
3277
+ message.type === "agent_event" &&
3278
+ ((message.payload as Record<string, unknown>)?.text as string) ===
3279
+ "autonomy-thought",
3280
+ );
3281
+ const waitForAction = waitForWsMessage(
3282
+ ws,
3283
+ (message) =>
3284
+ message.type === "agent_event" &&
3285
+ ((message.payload as Record<string, unknown>)?.text as string) ===
3286
+ "autonomy-action",
3287
+ );
3288
+ const waitForProactive = waitForWsMessage(
3289
+ ws,
3290
+ (message) =>
3291
+ message.type === "proactive-message" &&
3292
+ message.conversationId === conversationId,
3293
+ 6000,
3294
+ );
3295
+
3296
+ eventService.emit({
3297
+ runId: "run-autonomy-surface",
3298
+ seq: 1,
3299
+ stream: "provider",
3300
+ ts: Date.now() - 5,
3301
+ data: { text: "autonomy-thought" },
3302
+ agentId: "autonomy-surface-agent",
3303
+ });
3304
+ eventService.emit({
3305
+ runId: "run-autonomy-surface",
3306
+ seq: 2,
3307
+ stream: "provider",
3308
+ ts: Date.now() - 1,
3309
+ data: { text: "autonomy-action" },
3310
+ agentId: "autonomy-surface-agent",
3311
+ });
3312
+ eventService.emit({
3313
+ runId: "run-autonomy-surface",
3314
+ seq: 3,
3315
+ stream: "assistant",
3316
+ ts: Date.now(),
3317
+ data: {
3318
+ text: "Autonomy says: trigger follow-up",
3319
+ source: "trigger-dispatch",
3320
+ },
3321
+ agentId: "autonomy-surface-agent",
3322
+ roomId: "00000000-0000-0000-0000-00000000a999" as UUID,
3323
+ });
3324
+
3325
+ await waitForThought;
3326
+ await waitForAction;
3327
+ const proactive = await waitForProactive;
3328
+ const proactiveMessage = proactive.message as Record<string, unknown>;
3329
+ expect(proactiveMessage.source).toBe("trigger-dispatch");
3330
+ expect(String(proactiveMessage.text ?? "")).toContain("Autonomy says:");
3331
+
3332
+ const messagesResponse = await req(
3333
+ streamServer.port,
3334
+ "GET",
3335
+ `/api/conversations/${encodeURIComponent(conversationId)}/messages`,
3336
+ );
3337
+ expect(messagesResponse.status).toBe(200);
3338
+ const messages = messagesResponse.data.messages as Array<
3339
+ Record<string, unknown>
3340
+ >;
3341
+ const routed = messages.find(
3342
+ (message) =>
3343
+ message.role === "assistant" &&
3344
+ message.source === "trigger-dispatch",
3345
+ );
3346
+ expect(routed).toBeDefined();
3347
+ expect(String(routed?.text ?? "")).toContain("Autonomy says:");
3348
+
3349
+ const replayResponse = await req(
3350
+ streamServer.port,
3351
+ "GET",
3352
+ "/api/agent/events?limit=50",
3353
+ );
3354
+ expect(replayResponse.status).toBe(200);
3355
+ const replayEvents = replayResponse.data.events as Array<
3356
+ Record<string, unknown>
3357
+ >;
3358
+ const replayPayloads = replayEvents
3359
+ .filter((event) => event.type === "agent_event")
3360
+ .map((event) => event.payload as Record<string, unknown>);
3361
+ expect(
3362
+ replayPayloads.some((payload) => payload.text === "autonomy-thought"),
3363
+ ).toBe(true);
3364
+ expect(
3365
+ replayPayloads.some((payload) => payload.text === "autonomy-action"),
3366
+ ).toBe(true);
3367
+
3368
+ const overviewResponse = await req(
3369
+ streamServer.port,
3370
+ "GET",
3371
+ "/api/workbench/overview",
3372
+ );
3373
+ expect(overviewResponse.status).toBe(200);
3374
+ const tasks = (overviewResponse.data.tasks ?? []) as Array<
3375
+ Record<string, unknown>
3376
+ >;
3377
+ const triggers = (overviewResponse.data.triggers ?? []) as Array<
3378
+ Record<string, unknown>
3379
+ >;
3380
+ expect(
3381
+ tasks.some((task) => task.name === "Autonomy surface task"),
3382
+ ).toBe(true);
3383
+ expect(
3384
+ triggers.some(
3385
+ (trigger) => trigger.displayName === "Autonomy surface trigger",
3386
+ ),
3387
+ ).toBe(true);
3388
+ } finally {
3389
+ ws.close();
3390
+ await streamServer.close();
3391
+ }
3392
+ }, 30_000);
3393
+
3394
+ it("does not route client_chat assistant events into proactive messages", async () => {
3395
+ const eventService = new TestAgentEventService();
3396
+ const runtime = createRuntimeForAutonomySurfaceTests({
3397
+ eventService,
3398
+ loopRunning: true,
3399
+ });
3400
+ const streamServer = await startApiServer({ port: 0, runtime });
3401
+ const ws = new WebSocket(`ws://127.0.0.1:${streamServer.port}/ws`);
3402
+ try {
3403
+ await waitForWsMessage(ws, (message) => message.type === "status");
3404
+
3405
+ const createConversation = await req(
3406
+ streamServer.port,
3407
+ "POST",
3408
+ "/api/conversations",
3409
+ {
3410
+ title: "No client_chat proactive routing",
3411
+ },
3412
+ );
3413
+ expect(createConversation.status).toBe(200);
3414
+ const conversation = createConversation.data.conversation as {
3415
+ id?: string;
3416
+ };
3417
+ const conversationId = conversation.id ?? "";
3418
+ expect(conversationId.length).toBeGreaterThan(0);
3419
+
3420
+ ws.send(
3421
+ JSON.stringify({
3422
+ type: "active-conversation",
3423
+ conversationId,
3424
+ }),
3425
+ );
3426
+
3427
+ eventService.emit({
3428
+ runId: "run-client-chat-no-proactive",
3429
+ seq: 1,
3430
+ stream: "assistant",
3431
+ ts: Date.now(),
3432
+ data: {
3433
+ text: "should-not-route-to-proactive",
3434
+ source: "client_chat",
3435
+ },
3436
+ agentId: "autonomy-surface-agent",
3437
+ });
3438
+
3439
+ await expect(
3440
+ waitForWsMessage(
3441
+ ws,
3442
+ (message) =>
3443
+ message.type === "proactive-message" &&
3444
+ message.conversationId === conversationId,
3445
+ 900,
3446
+ ),
3447
+ ).rejects.toThrow("Timed out waiting for websocket message");
3448
+
3449
+ const messagesResponse = await req(
3450
+ streamServer.port,
3451
+ "GET",
3452
+ `/api/conversations/${encodeURIComponent(conversationId)}/messages`,
3453
+ );
3454
+ expect(messagesResponse.status).toBe(200);
3455
+ const messages = messagesResponse.data.messages as Array<
3456
+ Record<string, unknown>
3457
+ >;
3458
+ const routed = messages.find(
3459
+ (message) =>
3460
+ String(message.text ?? "") === "should-not-route-to-proactive",
3461
+ );
3462
+ expect(routed).toBeUndefined();
3463
+ } finally {
3464
+ ws.close();
3465
+ await streamServer.close();
3466
+ }
3467
+ }, 30_000);
3468
+
3469
+ it("does not route ambiguous assistant events without source or room metadata", async () => {
3470
+ const eventService = new TestAgentEventService();
3471
+ const runtime = createRuntimeForAutonomySurfaceTests({
3472
+ eventService,
3473
+ loopRunning: true,
3474
+ });
3475
+ const streamServer = await startApiServer({ port: 0, runtime });
3476
+ const ws = new WebSocket(`ws://127.0.0.1:${streamServer.port}/ws`);
3477
+ try {
3478
+ await waitForWsMessage(ws, (message) => message.type === "status");
3479
+
3480
+ const createConversation = await req(
3481
+ streamServer.port,
3482
+ "POST",
3483
+ "/api/conversations",
3484
+ {
3485
+ title: "No ambiguous proactive routing",
3486
+ },
3487
+ );
3488
+ expect(createConversation.status).toBe(200);
3489
+ const conversation = createConversation.data.conversation as {
3490
+ id?: string;
3491
+ };
3492
+ const conversationId = conversation.id ?? "";
3493
+ expect(conversationId.length).toBeGreaterThan(0);
3494
+
3495
+ ws.send(
3496
+ JSON.stringify({
3497
+ type: "active-conversation",
3498
+ conversationId,
3499
+ }),
3500
+ );
3501
+
3502
+ eventService.emit({
3503
+ runId: "run-ambiguous-no-proactive",
3504
+ seq: 1,
3505
+ stream: "assistant",
3506
+ ts: Date.now(),
3507
+ data: {
3508
+ text: "ambiguous-should-not-route",
3509
+ },
3510
+ agentId: "autonomy-surface-agent",
3511
+ });
3512
+
3513
+ await expect(
3514
+ waitForWsMessage(
3515
+ ws,
3516
+ (message) =>
3517
+ message.type === "proactive-message" &&
3518
+ message.conversationId === conversationId,
3519
+ 900,
3520
+ ),
3521
+ ).rejects.toThrow("Timed out waiting for websocket message");
3522
+
3523
+ const messagesResponse = await req(
3524
+ streamServer.port,
3525
+ "GET",
3526
+ `/api/conversations/${encodeURIComponent(conversationId)}/messages`,
3527
+ );
3528
+ expect(messagesResponse.status).toBe(200);
3529
+ const messages = messagesResponse.data.messages as Array<
3530
+ Record<string, unknown>
3531
+ >;
3532
+ const routed = messages.find(
3533
+ (message) =>
3534
+ String(message.text ?? "") === "ambiguous-should-not-route",
3535
+ );
3536
+ expect(routed).toBeUndefined();
3537
+ } finally {
3538
+ ws.close();
3539
+ await streamServer.close();
3540
+ }
3541
+ }, 30_000);
3542
+ });
3543
+
3544
+ // -- Onboarding --
3545
+
3546
+ describe("onboarding endpoints", () => {
3547
+ it("GET /api/onboarding/status returns complete flag", async () => {
3548
+ const { status, data } = await req(port, "GET", "/api/onboarding/status");
3549
+ expect(status).toBe(200);
3550
+ expect(typeof data.complete).toBe("boolean");
3551
+ });
3552
+
3553
+ it("GET /api/onboarding/options returns real presets", async () => {
3554
+ const { status, data } = await req(
3555
+ port,
3556
+ "GET",
3557
+ "/api/onboarding/options",
3558
+ );
3559
+ expect(status).toBe(200);
3560
+ const names = data.names as string[];
3561
+ const styles = data.styles as unknown[];
3562
+ const providers = data.providers as unknown[];
3563
+
3564
+ expect(names.length).toBeGreaterThan(0);
3565
+ expect(styles.length).toBeGreaterThan(0);
3566
+ expect(providers.length).toBeGreaterThan(0);
3567
+
3568
+ // Verify names come from the real preset pool (random subset)
3569
+ for (const name of names) {
3570
+ expect(AGENT_NAME_POOL).toContain(name);
3571
+ }
3572
+ // Ensure names are unique
3573
+ expect(new Set(names).size).toBe(names.length);
3574
+ });
3575
+
3576
+ it("POST /api/onboarding stores adminEntityId in defaults", async () => {
3577
+ const res = await req(port, "POST", "/api/onboarding", {
3578
+ name: "AdminAgent",
3579
+ connection: {
3580
+ kind: "local-provider",
3581
+ provider: "anthropic",
3582
+ },
3583
+ runMode: "local",
3584
+ });
3585
+ expect(res.status).toBe(200);
3586
+
3587
+ const cfg = await req(port, "GET", "/api/config");
3588
+ const defaults = (
3589
+ cfg.data as {
3590
+ agents?: { defaults?: { adminEntityId?: string } };
3591
+ }
3592
+ ).agents?.defaults;
3593
+ const adminEntityId = defaults?.adminEntityId ?? "";
3594
+ expect(typeof adminEntityId).toBe("string");
3595
+ expect(adminEntityId).toMatch(
3596
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
3597
+ );
3598
+ });
3599
+ });
3600
+
3601
+ // -- Config --
3602
+
3603
+ describe("config endpoints", () => {
3604
+ it("GET /api/config returns config object", async () => {
3605
+ const { status, data } = await req(port, "GET", "/api/config");
3606
+ expect(status).toBe(200);
3607
+ expect(typeof data).toBe("object");
3608
+ });
3609
+
3610
+ it("PUT /api/config → GET /api/config round-trips", async () => {
3611
+ const original = (await req(port, "GET", "/api/config")).data;
3612
+
3613
+ // Write new config — use "features" (an allowed top-level key)
3614
+ await req(port, "PUT", "/api/config", {
3615
+ features: { roundTrip: { enabled: true } },
3616
+ });
3617
+ const { data } = await req(port, "GET", "/api/config");
3618
+ expect(
3619
+ (data as Record<string, Record<string, Record<string, boolean>>>)
3620
+ .features?.roundTrip?.enabled,
3621
+ ).toBe(true);
3622
+
3623
+ // Restore
3624
+ await req(port, "PUT", "/api/config", original);
3625
+ });
3626
+ });
3627
+
3628
+ // -- Autonomy --
3629
+
3630
+ // -- Workbench --
3631
+
3632
+ describe("workbench endpoints", () => {
3633
+ it("GET /api/workbench/overview returns summary + arrays", async () => {
3634
+ const { status, data } = await req(
3635
+ port,
3636
+ "GET",
3637
+ "/api/workbench/overview",
3638
+ );
3639
+ expect(status).toBe(200);
3640
+ expect(Array.isArray(data.tasks)).toBe(true);
3641
+ expect(Array.isArray(data.triggers)).toBe(true);
3642
+ expect(Array.isArray(data.todos)).toBe(true);
3643
+ expect(typeof data.summary).toBe("object");
3644
+ });
3645
+
3646
+ it("GET /api/workbench/tasks returns 503 when runtime is absent", async () => {
3647
+ const { status } = await req(port, "GET", "/api/workbench/tasks");
3648
+ expect(status).toBe(503);
3649
+ });
3650
+
3651
+ it("POST /api/workbench/tasks returns 503 when runtime is absent", async () => {
3652
+ const { status } = await req(port, "POST", "/api/workbench/tasks", {
3653
+ name: "test task",
3654
+ });
3655
+ expect(status).toBe(503);
3656
+ });
3657
+
3658
+ it("PUT /api/workbench/todos/:id returns 503 when runtime is absent", async () => {
3659
+ const { status } = await req(port, "PUT", "/api/workbench/todos/fake", {
3660
+ isCompleted: true,
3661
+ });
3662
+ expect(status).toBe(503);
3663
+ });
3664
+
3665
+ it("POST /api/workbench/todos returns 503 when runtime is absent", async () => {
3666
+ const { status } = await req(port, "POST", "/api/workbench/todos", {
3667
+ name: "test todo",
3668
+ });
3669
+ expect(status).toBe(503);
3670
+ });
3671
+ });
3672
+
3673
+ describe("share ingest endpoints", () => {
3674
+ it("POST /api/ingest/share accepts payload", async () => {
3675
+ const { status, data } = await req(port, "POST", "/api/ingest/share", {
3676
+ source: "e2e-test",
3677
+ title: "Shared article",
3678
+ url: "https://example.com/story",
3679
+ });
3680
+ expect(status).toBe(200);
3681
+ expect(data.ok).toBe(true);
3682
+ expect(typeof data.item).toBe("object");
3683
+ expect(
3684
+ typeof (data.item as Record<string, unknown>).suggestedPrompt,
3685
+ ).toBe("string");
3686
+ });
3687
+
3688
+ it("GET /api/ingest/share?consume=1 drains queued items", async () => {
3689
+ await req(port, "POST", "/api/ingest/share", {
3690
+ source: "e2e-test",
3691
+ text: "something to analyze",
3692
+ });
3693
+ const first = await req(port, "GET", "/api/ingest/share?consume=1");
3694
+ expect(first.status).toBe(200);
3695
+ expect(Array.isArray(first.data.items)).toBe(true);
3696
+ expect((first.data.items as unknown[]).length).toBeGreaterThan(0);
3697
+
3698
+ const second = await req(port, "GET", "/api/ingest/share?consume=1");
3699
+ expect(second.status).toBe(200);
3700
+ expect(Array.isArray(second.data.items)).toBe(true);
3701
+ expect((second.data.items as unknown[]).length).toBe(0);
3702
+ });
3703
+ });
3704
+
3705
+ // -- CORS --
3706
+
3707
+ describe("CORS", () => {
3708
+ it("OPTIONS returns 204", async () => {
3709
+ const { status } = await req(port, "OPTIONS", "/api/status");
3710
+ expect(status).toBe(204);
3711
+ });
3712
+
3713
+ it("localhost origin echoed back in CORS header", async () => {
3714
+ const origin = `http://localhost:${port}`;
3715
+ const { status, headers } = await new Promise<{
3716
+ status: number;
3717
+ headers: http.IncomingHttpHeaders;
3718
+ }>((resolve, reject) => {
3719
+ const r = http.request(
3720
+ {
3721
+ hostname: "127.0.0.1",
3722
+ port,
3723
+ path: "/api/status",
3724
+ method: "GET",
3725
+ headers: { Origin: origin },
3726
+ },
3727
+ (res) => {
3728
+ res.resume();
3729
+ resolve({ status: res.statusCode ?? 0, headers: res.headers });
3730
+ },
3731
+ );
3732
+ r.on("error", reject);
3733
+ r.end();
3734
+ });
3735
+ expect(status).toBe(200);
3736
+ expect(headers["access-control-allow-origin"]).toBe(origin);
3737
+ });
3738
+
3739
+ it("non-local origin is rejected", async () => {
3740
+ const { status } = await new Promise<{ status: number }>(
3741
+ (resolve, reject) => {
3742
+ const r = http.request(
3743
+ {
3744
+ hostname: "127.0.0.1",
3745
+ port,
3746
+ path: "/api/status",
3747
+ method: "GET",
3748
+ headers: { Origin: "https://evil.example.com" },
3749
+ },
3750
+ (res) => {
3751
+ res.resume();
3752
+ resolve({ status: res.statusCode ?? 0 });
3753
+ },
3754
+ );
3755
+ r.on("error", reject);
3756
+ r.end();
3757
+ },
3758
+ );
3759
+ expect(status).toBe(403);
3760
+ });
3761
+ });
3762
+
3763
+ // -- Error handling --
3764
+
3765
+ describe("error handling", () => {
3766
+ it("non-JSON POST body → 400", async () => {
3767
+ const { status } = await new Promise<{ status: number }>(
3768
+ (resolve, reject) => {
3769
+ const r = http.request(
3770
+ {
3771
+ hostname: "127.0.0.1",
3772
+ port,
3773
+ path: "/api/chat",
3774
+ method: "POST",
3775
+ headers: {
3776
+ "Content-Type": "application/json",
3777
+ "Content-Length": 11,
3778
+ },
3779
+ },
3780
+ (res) => {
3781
+ res.resume();
3782
+ resolve({ status: res.statusCode ?? 0 });
3783
+ },
3784
+ );
3785
+ r.on("error", reject);
3786
+ r.write("not-json!!!");
3787
+ r.end();
3788
+ },
3789
+ );
3790
+ expect(status).toBe(400);
3791
+ });
3792
+
3793
+ it("unknown route → 404", async () => {
3794
+ expect((await req(port, "GET", "/api/does-not-exist")).status).toBe(404);
3795
+ });
3796
+
3797
+ it("PUT /api/plugins/nonexistent → 404", async () => {
3798
+ expect(
3799
+ (
3800
+ await req(port, "PUT", "/api/plugins/nonexistent-plugin", {
3801
+ enabled: true,
3802
+ })
3803
+ ).status,
3804
+ ).toBe(404);
3805
+ });
3806
+ });
3807
+
3808
+ // -- SPA asset fallback guard --
3809
+
3810
+ describe("SPA asset fallback guard", () => {
3811
+ it("GET /nonexistent-file.vrm does not return 200 with text/html", async () => {
3812
+ const { status, headers } = await reqRaw(
3813
+ port,
3814
+ "GET",
3815
+ "/nonexistent-file.vrm",
3816
+ );
3817
+ // The SPA fallback must never serve index.html for asset extension requests.
3818
+ // Before the fix, missing .vrm files were incorrectly returned as 200 text/html.
3819
+ expect(status).not.toBe(200);
3820
+ expect(String(headers["content-type"] ?? "")).not.toContain("text/html");
3821
+ });
3822
+
3823
+ it("GET /some-unknown-route (no extension) is not blocked by the asset extension guard", async () => {
3824
+ const { status, headers } = await reqRaw(
3825
+ port,
3826
+ "GET",
3827
+ "/some-unknown-route",
3828
+ );
3829
+ // Extensionless paths must pass through the SPA fallback guard unchanged.
3830
+ // Possible outcomes depending on environment:
3831
+ // 200 — SPA fallback served index.html (production build with UI)
3832
+ // 401 — auth gate intercepted (auth-enabled config)
3833
+ // 404 — no built UI available, no matching route (CI/test environment)
3834
+ // All are acceptable; the test only checks this was NOT blocked by the
3835
+ // asset extension guard (which rejects paths like .vrm, .glb, .png).
3836
+ expect([200, 401, 404]).toContain(status);
3837
+ if (status === 200) {
3838
+ const contentType = String(headers["content-type"] ?? "");
3839
+ expect(contentType).toContain("text/html");
3840
+ }
3841
+ });
3842
+ });
3843
+
3844
+ // -- MCP Marketplace & Config --
3845
+
3846
+ describe("MCP marketplace endpoints", () => {
3847
+ it("GET /api/mcp/marketplace/search returns results array", async () => {
3848
+ const { status, data } = await req(
3849
+ port,
3850
+ "GET",
3851
+ "/api/mcp/marketplace/search?q=test&limit=5",
3852
+ );
3853
+ expect(status).toBe(200);
3854
+ expect(data.ok).toBe(true);
3855
+ expect(Array.isArray(data.results)).toBe(true);
3856
+ });
3857
+
3858
+ it("GET /api/mcp/marketplace/search works with empty query", async () => {
3859
+ const { status, data } = await req(
3860
+ port,
3861
+ "GET",
3862
+ "/api/mcp/marketplace/search",
3863
+ );
3864
+ expect(status).toBe(200);
3865
+ expect(data.ok).toBe(true);
3866
+ expect(Array.isArray(data.results)).toBe(true);
3867
+ });
3868
+
3869
+ it("GET /api/mcp/marketplace/details/:name returns 404 for nonexistent server", async () => {
3870
+ const { status, data } = await req(
3871
+ port,
3872
+ "GET",
3873
+ "/api/mcp/marketplace/details/nonexistent-server-xyz-123",
3874
+ );
3875
+ expect(status).toBe(404);
3876
+ expect(typeof data.error).toBe("string");
3877
+ });
3878
+
3879
+ it("GET /api/mcp/marketplace/details requires name parameter", async () => {
3880
+ const { status } = await req(
3881
+ port,
3882
+ "GET",
3883
+ "/api/mcp/marketplace/details/",
3884
+ );
3885
+ expect(status).toBe(400);
3886
+ });
3887
+ });
3888
+
3889
+ describe("MCP config endpoints", () => {
3890
+ it("GET /api/mcp/config returns servers object", async () => {
3891
+ const { status, data } = await req(port, "GET", "/api/mcp/config");
3892
+ expect(status).toBe(200);
3893
+ expect(data.ok).toBe(true);
3894
+ expect(typeof data.servers).toBe("object");
3895
+ });
3896
+
3897
+ it("POST /api/mcp/config/server adds a server and returns requiresRestart", async () => {
3898
+ const { status, data } = await req(
3899
+ port,
3900
+ "POST",
3901
+ "/api/mcp/config/server",
3902
+ {
3903
+ name: "test-server",
3904
+ config: {
3905
+ type: "stdio",
3906
+ command: "npx",
3907
+ args: ["-y", "@test/mcp-server"],
3908
+ },
3909
+ },
3910
+ );
3911
+ expect(status).toBe(200);
3912
+ expect(data.ok).toBe(true);
3913
+ expect(data.name).toBe("test-server");
3914
+ expect(data.requiresRestart).toBe(true);
3915
+
3916
+ // Verify it persisted
3917
+ const { data: configData } = await req(port, "GET", "/api/mcp/config");
3918
+ const servers = configData.servers as Record<
3919
+ string,
3920
+ Record<string, unknown>
3921
+ >;
3922
+ expect(servers["test-server"]).toBeDefined();
3923
+ expect(servers["test-server"].type).toBe("stdio");
3924
+ expect(servers["test-server"].command).toBe("npx");
3925
+ });
3926
+
3927
+ it("POST /api/mcp/config/server validates name", async () => {
3928
+ const { status } = await req(port, "POST", "/api/mcp/config/server", {
3929
+ name: "",
3930
+ config: { type: "stdio", command: "npx" },
3931
+ });
3932
+ expect(status).toBe(400);
3933
+ });
3934
+
3935
+ it("POST /api/mcp/config/server rejects reserved server names", async () => {
3936
+ const { status } = await req(port, "POST", "/api/mcp/config/server", {
3937
+ name: "__proto__",
3938
+ config: { type: "stdio", command: "npx" },
3939
+ });
3940
+ expect(status).toBe(400);
3941
+ });
3942
+
3943
+ it("POST /api/mcp/config/server validates config type", async () => {
3944
+ const { status } = await req(port, "POST", "/api/mcp/config/server", {
3945
+ name: "bad-type",
3946
+ config: { type: "invalid" },
3947
+ });
3948
+ expect(status).toBe(400);
3949
+ });
3950
+
3951
+ it("POST /api/mcp/config/server validates command for stdio", async () => {
3952
+ const { status } = await req(port, "POST", "/api/mcp/config/server", {
3953
+ name: "no-cmd",
3954
+ config: { type: "stdio" },
3955
+ });
3956
+ expect(status).toBe(400);
3957
+ });
3958
+
3959
+ it("POST /api/mcp/config/server validates url for remote servers", async () => {
3960
+ const { status } = await req(port, "POST", "/api/mcp/config/server", {
3961
+ name: "no-url",
3962
+ config: { type: "streamable-http" },
3963
+ });
3964
+ expect(status).toBe(400);
3965
+ });
3966
+
3967
+ it("POST /api/mcp/config/server rejects reserved keys in nested config", async () => {
3968
+ const { status } = await req(port, "POST", "/api/mcp/config/server", {
3969
+ name: "bad-nested-keys",
3970
+ config: {
3971
+ type: "stdio",
3972
+ command: "npx",
3973
+ env: {
3974
+ constructor: { polluted: "yes" },
3975
+ },
3976
+ },
3977
+ });
3978
+ expect(status).toBe(400);
3979
+ });
3980
+
3981
+ it("POST /api/mcp/config/server adds remote server with url", async () => {
3982
+ const { status, data } = await req(
3983
+ port,
3984
+ "POST",
3985
+ "/api/mcp/config/server",
3986
+ {
3987
+ name: "test-remote",
3988
+ config: {
3989
+ type: "streamable-http",
3990
+ url: "https://93.184.216.34/api",
3991
+ },
3992
+ },
3993
+ );
3994
+ expect(status).toBe(200);
3995
+ expect(data.ok).toBe(true);
3996
+ expect(data.requiresRestart).toBe(true);
3997
+ });
3998
+
3999
+ it("DELETE /api/mcp/config/server/:name removes and returns requiresRestart", async () => {
4000
+ // First add
4001
+ await req(port, "POST", "/api/mcp/config/server", {
4002
+ name: "to-delete",
4003
+ config: {
4004
+ type: "stdio",
4005
+ command: "npx",
4006
+ args: ["-y", "@test/mcp-server"],
4007
+ },
4008
+ });
4009
+
4010
+ // Then remove
4011
+ const { status, data } = await req(
4012
+ port,
4013
+ "DELETE",
4014
+ "/api/mcp/config/server/to-delete",
4015
+ );
4016
+ expect(status).toBe(200);
4017
+ expect(data.ok).toBe(true);
4018
+ expect(data.requiresRestart).toBe(true);
4019
+
4020
+ // Verify removed
4021
+ const { data: configData } = await req(port, "GET", "/api/mcp/config");
4022
+ const servers = configData.servers as Record<string, unknown>;
4023
+ expect(servers["to-delete"]).toBeUndefined();
4024
+ });
4025
+
4026
+ it("DELETE /api/mcp/config/server/:name is idempotent for nonexistent", async () => {
4027
+ const { status, data } = await req(
4028
+ port,
4029
+ "DELETE",
4030
+ "/api/mcp/config/server/does-not-exist",
4031
+ );
4032
+ expect(status).toBe(200);
4033
+ expect(data.ok).toBe(true);
4034
+ });
4035
+
4036
+ it("DELETE /api/mcp/config/server/:name returns 400 for malformed encoding", async () => {
4037
+ const { status, data } = await req(
4038
+ port,
4039
+ "DELETE",
4040
+ "/api/mcp/config/server/%E0%A4%A",
4041
+ );
4042
+ expect(status).toBe(400);
4043
+ expect(typeof data.error).toBe("string");
4044
+ });
4045
+
4046
+ it("PUT /api/mcp/config replaces entire config", async () => {
4047
+ const newServers = {
4048
+ "bulk-a": { type: "stdio", command: "npx", args: ["-y", "@test/a"] },
4049
+ "bulk-b": { type: "streamable-http", url: "https://93.184.216.34/mcp" },
4050
+ };
4051
+
4052
+ const { status, data } = await req(port, "PUT", "/api/mcp/config", {
4053
+ servers: newServers,
4054
+ });
4055
+ expect(status).toBe(200);
4056
+ expect(data.ok).toBe(true);
4057
+
4058
+ const { data: configData } = await req(port, "GET", "/api/mcp/config");
4059
+ const servers = configData.servers as Record<string, unknown>;
4060
+ expect(servers["bulk-a"]).toBeDefined();
4061
+ expect(servers["bulk-b"]).toBeDefined();
4062
+ });
4063
+
4064
+ it("PUT /api/mcp/config rejects reserved keys in servers payload", async () => {
4065
+ const { status } = await req(port, "PUT", "/api/mcp/config", {
4066
+ servers: {
4067
+ constructor: {
4068
+ type: "stdio",
4069
+ command: "npx",
4070
+ },
4071
+ },
4072
+ });
4073
+ expect(status).toBe(400);
4074
+ });
4075
+
4076
+ it("DELETE /api/mcp/config/server/:name rejects reserved server names", async () => {
4077
+ const { status } = await req(
4078
+ port,
4079
+ "DELETE",
4080
+ "/api/mcp/config/server/__proto__",
4081
+ );
4082
+ expect(status).toBe(400);
4083
+ });
4084
+ });
4085
+
4086
+ describe("MCP status endpoint", () => {
4087
+ it("GET /api/mcp/status returns servers array (empty without runtime MCP service)", async () => {
4088
+ const { status, data } = await req(port, "GET", "/api/mcp/status");
4089
+ expect(status).toBe(200);
4090
+ expect(data.ok).toBe(true);
4091
+ expect(Array.isArray(data.servers)).toBe(true);
4092
+ });
4093
+
4094
+ it("GET /api/mcp/status server entries have correct shape when present", async () => {
4095
+ const { data } = await req(port, "GET", "/api/mcp/status");
4096
+ const servers = data.servers as Array<Record<string, unknown>>;
4097
+ // With no runtime, it returns empty — but shape is valid
4098
+ for (const server of servers) {
4099
+ expect(typeof server.name).toBe("string");
4100
+ expect(typeof server.status).toBe("string");
4101
+ expect(typeof server.toolCount).toBe("number");
4102
+ expect(typeof server.resourceCount).toBe("number");
4103
+ }
4104
+ });
4105
+ });
4106
+ });
4107
+
4108
+ describe("API Server E2E (workbench CRUD)", () => {
4109
+ let port: number;
4110
+ let close: () => Promise<void>;
4111
+
4112
+ beforeAll(async () => {
4113
+ const server = await startApiServer({
4114
+ port: 0,
4115
+ runtime: createRuntimeForWorkbenchCrudTests({ loopRunning: true }),
4116
+ });
4117
+ port = server.port;
4118
+ close = server.close;
4119
+ }, 30_000);
4120
+
4121
+ afterAll(async () => {
4122
+ await close();
4123
+ });
4124
+
4125
+ it("supports full CRUD for workbench tasks", async () => {
4126
+ const create = await req(port, "POST", "/api/workbench/tasks", {
4127
+ name: "Task CRUD Alpha",
4128
+ description: "Initial task description",
4129
+ tags: ["ops"],
4130
+ });
4131
+ expect(create.status).toBe(201);
4132
+ const createdTask = create.data.task as Record<string, unknown>;
4133
+ const taskId = createdTask.id as string;
4134
+ expect(typeof taskId).toBe("string");
4135
+
4136
+ const list = await req(port, "GET", "/api/workbench/tasks");
4137
+ expect(list.status).toBe(200);
4138
+ expect(Array.isArray(list.data.tasks)).toBe(true);
4139
+ expect(
4140
+ (list.data.tasks as Array<Record<string, unknown>>).some(
4141
+ (task) => task.id === taskId,
4142
+ ),
4143
+ ).toBe(true);
4144
+
4145
+ const read = await req(
4146
+ port,
4147
+ "GET",
4148
+ `/api/workbench/tasks/${encodeURIComponent(taskId)}`,
4149
+ );
4150
+ expect(read.status).toBe(200);
4151
+ expect((read.data.task as Record<string, unknown>).name).toBe(
4152
+ "Task CRUD Alpha",
4153
+ );
4154
+
4155
+ const update = await req(
4156
+ port,
4157
+ "PUT",
4158
+ `/api/workbench/tasks/${encodeURIComponent(taskId)}`,
4159
+ {
4160
+ name: "Task CRUD Beta",
4161
+ isCompleted: true,
4162
+ },
4163
+ );
4164
+ expect(update.status).toBe(200);
4165
+ expect((update.data.task as Record<string, unknown>).name).toBe(
4166
+ "Task CRUD Beta",
4167
+ );
4168
+ expect((update.data.task as Record<string, unknown>).isCompleted).toBe(
4169
+ true,
4170
+ );
4171
+
4172
+ const del = await req(
4173
+ port,
4174
+ "DELETE",
4175
+ `/api/workbench/tasks/${encodeURIComponent(taskId)}`,
4176
+ );
4177
+ expect(del.status).toBe(200);
4178
+ expect(del.data.ok).toBe(true);
4179
+
4180
+ const readAfterDelete = await req(
4181
+ port,
4182
+ "GET",
4183
+ `/api/workbench/tasks/${encodeURIComponent(taskId)}`,
4184
+ );
4185
+ expect(readAfterDelete.status).toBe(404);
4186
+ });
4187
+
4188
+ it("supports full CRUD for triggers", async () => {
4189
+ const create = await req(port, "POST", "/api/triggers", {
4190
+ displayName: "Trigger CRUD Alpha",
4191
+ instructions: "Run trigger CRUD test",
4192
+ triggerType: "interval",
4193
+ intervalMs: 60_000,
4194
+ });
4195
+ expect(create.status).toBe(201);
4196
+ const createdTrigger = create.data.trigger as Record<string, unknown>;
4197
+ const triggerId = createdTrigger.id as string;
4198
+ expect(typeof triggerId).toBe("string");
4199
+
4200
+ const list = await req(port, "GET", "/api/triggers");
4201
+ expect(list.status).toBe(200);
4202
+ expect(Array.isArray(list.data.triggers)).toBe(true);
4203
+ expect(
4204
+ (list.data.triggers as Array<Record<string, unknown>>).some(
4205
+ (trigger) => trigger.id === triggerId,
4206
+ ),
4207
+ ).toBe(true);
4208
+
4209
+ const read = await req(
4210
+ port,
4211
+ "GET",
4212
+ `/api/triggers/${encodeURIComponent(triggerId)}`,
4213
+ );
4214
+ expect(read.status).toBe(200);
4215
+ expect((read.data.trigger as Record<string, unknown>).displayName).toBe(
4216
+ "Trigger CRUD Alpha",
4217
+ );
4218
+
4219
+ const update = await req(
4220
+ port,
4221
+ "PUT",
4222
+ `/api/triggers/${encodeURIComponent(triggerId)}`,
4223
+ {
4224
+ displayName: "Trigger CRUD Beta",
4225
+ enabled: false,
4226
+ },
4227
+ );
4228
+ expect(update.status).toBe(200);
4229
+ expect((update.data.trigger as Record<string, unknown>).displayName).toBe(
4230
+ "Trigger CRUD Beta",
4231
+ );
4232
+ expect((update.data.trigger as Record<string, unknown>).enabled).toBe(
4233
+ false,
4234
+ );
4235
+
4236
+ const del = await req(
4237
+ port,
4238
+ "DELETE",
4239
+ `/api/triggers/${encodeURIComponent(triggerId)}`,
4240
+ );
4241
+ expect(del.status).toBe(200);
4242
+ expect(del.data.ok).toBe(true);
4243
+
4244
+ const readAfterDelete = await req(
4245
+ port,
4246
+ "GET",
4247
+ `/api/triggers/${encodeURIComponent(triggerId)}`,
4248
+ );
4249
+ expect(readAfterDelete.status).toBe(404);
4250
+ });
4251
+
4252
+ it("supports full CRUD for todos", async () => {
4253
+ const create = await req(port, "POST", "/api/workbench/todos", {
4254
+ name: "Todo CRUD Alpha",
4255
+ description: "Initial todo description",
4256
+ priority: 3,
4257
+ isUrgent: true,
4258
+ type: "task",
4259
+ });
4260
+ expect(create.status).toBe(201);
4261
+ const createdTodo = create.data.todo as Record<string, unknown>;
4262
+ const todoId = createdTodo.id as string;
4263
+ expect(typeof todoId).toBe("string");
4264
+
4265
+ const list = await req(port, "GET", "/api/workbench/todos");
4266
+ expect(list.status).toBe(200);
4267
+ expect(Array.isArray(list.data.todos)).toBe(true);
4268
+ expect(
4269
+ (list.data.todos as Array<Record<string, unknown>>).some(
4270
+ (todo) => todo.id === todoId,
4271
+ ),
4272
+ ).toBe(true);
4273
+
4274
+ const read = await req(
4275
+ port,
4276
+ "GET",
4277
+ `/api/workbench/todos/${encodeURIComponent(todoId)}`,
4278
+ );
4279
+ expect(read.status).toBe(200);
4280
+ expect((read.data.todo as Record<string, unknown>).name).toBe(
4281
+ "Todo CRUD Alpha",
4282
+ );
4283
+
4284
+ const update = await req(
4285
+ port,
4286
+ "PUT",
4287
+ `/api/workbench/todos/${encodeURIComponent(todoId)}`,
4288
+ {
4289
+ name: "Todo CRUD Beta",
4290
+ priority: 1,
4291
+ isUrgent: false,
4292
+ },
4293
+ );
4294
+ expect(update.status).toBe(200);
4295
+ expect((update.data.todo as Record<string, unknown>).name).toBe(
4296
+ "Todo CRUD Beta",
4297
+ );
4298
+ expect((update.data.todo as Record<string, unknown>).priority).toBe(1);
4299
+ expect((update.data.todo as Record<string, unknown>).isUrgent).toBe(false);
4300
+
4301
+ const complete = await req(
4302
+ port,
4303
+ "POST",
4304
+ `/api/workbench/todos/${encodeURIComponent(todoId)}/complete`,
4305
+ {
4306
+ isCompleted: true,
4307
+ },
4308
+ );
4309
+ expect(complete.status).toBe(200);
4310
+ expect(complete.data.ok).toBe(true);
4311
+
4312
+ const readCompleted = await req(
4313
+ port,
4314
+ "GET",
4315
+ `/api/workbench/todos/${encodeURIComponent(todoId)}`,
4316
+ );
4317
+ expect(readCompleted.status).toBe(200);
4318
+ expect(
4319
+ (readCompleted.data.todo as Record<string, unknown>).isCompleted,
4320
+ ).toBe(true);
4321
+
4322
+ const del = await req(
4323
+ port,
4324
+ "DELETE",
4325
+ `/api/workbench/todos/${encodeURIComponent(todoId)}`,
4326
+ );
4327
+ expect(del.status).toBe(200);
4328
+ expect(del.data.ok).toBe(true);
4329
+
4330
+ const readAfterDelete = await req(
4331
+ port,
4332
+ "GET",
4333
+ `/api/workbench/todos/${encodeURIComponent(todoId)}`,
4334
+ );
4335
+ expect(readAfterDelete.status).toBe(404);
4336
+ });
4337
+ });
4338
+
4339
+ describe("API Server E2E (compat endpoints)", () => {
4340
+ let port: number;
4341
+ let close: () => Promise<void>;
4342
+
4343
+ beforeAll(async () => {
4344
+ const server = await startApiServer({
4345
+ port: 0,
4346
+ runtime: createRuntimeForCompatEndpointTests(),
4347
+ });
4348
+ port = server.port;
4349
+ close = server.close;
4350
+ }, 30_000);
4351
+
4352
+ afterAll(async () => {
4353
+ await close();
4354
+ });
4355
+
4356
+ it("GET /v1/models returns OpenAI-compatible model list", async () => {
4357
+ const { status, data } = await req(port, "GET", "/v1/models");
4358
+ expect(status).toBe(200);
4359
+ expect(data.object).toBe("list");
4360
+ const models = data.data as Array<Record<string, unknown>>;
4361
+ expect(models.length).toBeGreaterThan(0);
4362
+ expect(models.some((item) => item.id === "milady")).toBe(true);
4363
+ expect(models.some((item) => item.id === "CompatAgent")).toBe(true);
4364
+ });
4365
+
4366
+ it("GET /v1/models/:id returns OpenAI-compatible model detail", async () => {
4367
+ const { status, data } = await req(
4368
+ port,
4369
+ "GET",
4370
+ "/v1/models/compat-model-id",
4371
+ );
4372
+ expect(status).toBe(200);
4373
+ expect(data.object).toBe("model");
4374
+ expect(data.id).toBe("compat-model-id");
4375
+ expect(data.owned_by).toBe("milady");
4376
+ });
4377
+
4378
+ it("POST /v1/chat/completions returns OpenAI-compatible completion", async () => {
4379
+ const { status, data } = await req(port, "POST", "/v1/chat/completions", {
4380
+ model: "milady",
4381
+ user: "compat-e2e",
4382
+ messages: [
4383
+ { role: "system", content: "You are concise." },
4384
+ { role: "user", content: "Say hi." },
4385
+ ],
4386
+ });
4387
+ expect(status).toBe(200);
4388
+ expect(data.object).toBe("chat.completion");
4389
+ expect(typeof data.id).toBe("string");
4390
+ const choices = data.choices as Array<Record<string, unknown>>;
4391
+ expect(Array.isArray(choices)).toBe(true);
4392
+ const firstChoice = choices[0] as Record<string, unknown>;
4393
+ const message = firstChoice.message as Record<string, unknown>;
4394
+ expect(message.role).toBe("assistant");
4395
+ expect(message.content).toBe("Compat reply");
4396
+ expect(firstChoice.finish_reason).toBe("stop");
4397
+ });
4398
+
4399
+ it("POST /v1/chat/completions streams OpenAI-compatible SSE chunks", async () => {
4400
+ const { status, headers, events } = await reqSse(
4401
+ port,
4402
+ "/v1/chat/completions",
4403
+ {
4404
+ model: "milady",
4405
+ stream: true,
4406
+ user: "compat-sse-e2e",
4407
+ messages: [{ role: "user", content: "Stream a short answer." }],
4408
+ },
4409
+ );
4410
+
4411
+ expect(status).toBe(200);
4412
+ expect(String(headers["content-type"])).toContain("text/event-stream");
4413
+
4414
+ const chunks = events.filter(
4415
+ (event) =>
4416
+ (event as Record<string, unknown>).object === "chat.completion.chunk",
4417
+ ) as Array<Record<string, unknown>>;
4418
+ expect(chunks.length).toBeGreaterThan(0);
4419
+
4420
+ const hasRoleChunk = chunks.some((chunk) => {
4421
+ const choices = chunk.choices;
4422
+ if (!Array.isArray(choices) || choices.length === 0) return false;
4423
+ const firstChoice = choices[0] as Record<string, unknown>;
4424
+ const delta = firstChoice.delta as Record<string, unknown> | undefined;
4425
+ return delta?.role === "assistant";
4426
+ });
4427
+ expect(hasRoleChunk).toBe(true);
4428
+
4429
+ const content = chunks
4430
+ .map((chunk) => {
4431
+ const choices = chunk.choices;
4432
+ if (!Array.isArray(choices) || choices.length === 0) return "";
4433
+ const firstChoice = choices[0] as Record<string, unknown>;
4434
+ const delta = firstChoice.delta as Record<string, unknown> | undefined;
4435
+ return typeof delta?.content === "string" ? delta.content : "";
4436
+ })
4437
+ .join("");
4438
+ expect(content).toContain("Compat reply");
4439
+ });
4440
+
4441
+ it("POST /v1/messages returns Anthropic-compatible message", async () => {
4442
+ const { status, data } = await req(port, "POST", "/v1/messages", {
4443
+ model: "milady",
4444
+ system: "You are concise.",
4445
+ metadata: { conversation_id: "compat-room-1" },
4446
+ messages: [{ role: "user", content: "Say hi." }],
4447
+ });
4448
+ expect(status).toBe(200);
4449
+ expect(data.type).toBe("message");
4450
+ expect(data.role).toBe("assistant");
4451
+ const content = data.content as Array<Record<string, unknown>>;
4452
+ expect(Array.isArray(content)).toBe(true);
4453
+ const first = content[0] as Record<string, unknown>;
4454
+ expect(first.type).toBe("text");
4455
+ expect(first.text).toBe("Compat reply");
4456
+ expect(data.stop_reason).toBe("end_turn");
4457
+ });
4458
+
4459
+ it("POST /v1/messages streams Anthropic-compatible SSE events", async () => {
4460
+ const { status, headers, events } = await reqSse(port, "/v1/messages", {
4461
+ model: "milady",
4462
+ stream: true,
4463
+ metadata: { conversation_id: "compat-room-2" },
4464
+ messages: [{ role: "user", content: "Stream a short answer." }],
4465
+ });
4466
+
4467
+ expect(status).toBe(200);
4468
+ expect(String(headers["content-type"])).toContain("text/event-stream");
4469
+
4470
+ const eventTypes = events
4471
+ .map((event) => event.type)
4472
+ .filter((value): value is string => typeof value === "string");
4473
+ expect(eventTypes).toContain("message_start");
4474
+ expect(eventTypes).toContain("content_block_start");
4475
+ expect(eventTypes).toContain("content_block_delta");
4476
+ expect(eventTypes).toContain("content_block_stop");
4477
+ expect(eventTypes).toContain("message_delta");
4478
+ expect(eventTypes).toContain("message_stop");
4479
+
4480
+ const streamedText = events
4481
+ .filter((event) => event.type === "content_block_delta")
4482
+ .map((event) => {
4483
+ const delta = (event as Record<string, unknown>).delta as
4484
+ | Record<string, unknown>
4485
+ | undefined;
4486
+ return typeof delta?.text === "string" ? delta.text : "";
4487
+ })
4488
+ .join("");
4489
+ expect(streamedText).toContain("Compat reply");
4490
+ });
4491
+ });
4492
+
4493
+ describe("API Server E2E (chat SSE)", () => {
4494
+ let port: number;
4495
+ let close: () => Promise<void>;
4496
+
4497
+ beforeAll(async () => {
4498
+ const server = await startApiServer({
4499
+ port: 0,
4500
+ runtime: createRuntimeForChatSseTests(),
4501
+ });
4502
+ port = server.port;
4503
+ close = server.close;
4504
+ }, 30_000);
4505
+
4506
+ afterAll(async () => {
4507
+ await close();
4508
+ });
4509
+
4510
+ it("POST /api/chat/stream emits token and done events", async () => {
4511
+ const { status, headers, events } = await reqSse(port, "/api/chat/stream", {
4512
+ text: "hello",
4513
+ mode: "simple",
4514
+ });
4515
+
4516
+ expect(status).toBe(200);
4517
+ expect(String(headers["content-type"])).toContain("text/event-stream");
4518
+ expect(events).toContainEqual(
4519
+ expect.objectContaining({
4520
+ type: "token",
4521
+ text: "Hello ",
4522
+ fullText: "Hello ",
4523
+ }),
4524
+ );
4525
+ expect(events).toContainEqual(
4526
+ expect.objectContaining({
4527
+ type: "token",
4528
+ text: "world",
4529
+ fullText: "Hello world",
4530
+ }),
4531
+ );
4532
+ expect(events).toContainEqual(
4533
+ expect.objectContaining({
4534
+ type: "done",
4535
+ fullText: "Hello world",
4536
+ agentName: "ChatStreamAgent",
4537
+ }),
4538
+ );
4539
+ });
4540
+
4541
+ it("POST /api/conversations/:id/messages/stream emits token and done events", async () => {
4542
+ const create = await req(port, "POST", "/api/conversations", {
4543
+ title: "SSE Conversation",
4544
+ });
4545
+ expect(create.status).toBe(200);
4546
+ const createdConversation = create.data.conversation as { id?: string };
4547
+ const conversationId = createdConversation.id ?? "";
4548
+ expect(conversationId.length).toBeGreaterThan(0);
4549
+
4550
+ const { status, events } = await reqSse(
4551
+ port,
4552
+ `/api/conversations/${conversationId}/messages/stream`,
4553
+ {
4554
+ text: "hello",
4555
+ mode: "power",
4556
+ },
4557
+ );
4558
+
4559
+ expect(status).toBe(200);
4560
+ expect(events).toContainEqual(
4561
+ expect.objectContaining({
4562
+ type: "token",
4563
+ text: "Hello ",
4564
+ fullText: "Hello ",
4565
+ }),
4566
+ );
4567
+ expect(events).toContainEqual(
4568
+ expect.objectContaining({
4569
+ type: "token",
4570
+ text: "world",
4571
+ fullText: "Hello world",
4572
+ }),
4573
+ );
4574
+ expect(events).toContainEqual(
4575
+ expect.objectContaining({
4576
+ type: "done",
4577
+ fullText: "Hello world",
4578
+ agentName: "ChatStreamAgent",
4579
+ }),
4580
+ );
4581
+ });
4582
+ });