@elizaos/autonomous 2.0.0-alpha.10

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 (241) hide show
  1. package/LICENSE +21 -0
  2. package/package.json +270 -0
  3. package/src/actions/emote.ts +101 -0
  4. package/src/actions/restart.ts +101 -0
  5. package/src/actions/send-message.ts +168 -0
  6. package/src/actions/stream-control.ts +439 -0
  7. package/src/actions/switch-stream-source.ts +126 -0
  8. package/src/actions/terminal.ts +186 -0
  9. package/src/api/agent-admin-routes.ts +178 -0
  10. package/src/api/agent-lifecycle-routes.ts +129 -0
  11. package/src/api/agent-model.ts +143 -0
  12. package/src/api/agent-transfer-routes.ts +211 -0
  13. package/src/api/apps-routes.ts +210 -0
  14. package/src/api/auth-routes.ts +90 -0
  15. package/src/api/bsc-trade.ts +736 -0
  16. package/src/api/bug-report-routes.ts +161 -0
  17. package/src/api/character-routes.ts +421 -0
  18. package/src/api/cloud-billing-routes.ts +598 -0
  19. package/src/api/cloud-compat-routes.ts +192 -0
  20. package/src/api/cloud-routes.ts +529 -0
  21. package/src/api/cloud-status-routes.ts +234 -0
  22. package/src/api/compat-utils.ts +154 -0
  23. package/src/api/connector-health.ts +135 -0
  24. package/src/api/coordinator-wiring.ts +179 -0
  25. package/src/api/credit-detection.ts +47 -0
  26. package/src/api/database.ts +1357 -0
  27. package/src/api/diagnostics-routes.ts +389 -0
  28. package/src/api/drop-service.ts +205 -0
  29. package/src/api/early-logs.ts +111 -0
  30. package/src/api/http-helpers.ts +252 -0
  31. package/src/api/index.ts +85 -0
  32. package/src/api/knowledge-routes.ts +1189 -0
  33. package/src/api/knowledge-service-loader.ts +92 -0
  34. package/src/api/memory-bounds.ts +121 -0
  35. package/src/api/memory-routes.ts +349 -0
  36. package/src/api/merkle-tree.ts +239 -0
  37. package/src/api/models-routes.ts +72 -0
  38. package/src/api/nfa-routes.ts +169 -0
  39. package/src/api/nft-verify.ts +188 -0
  40. package/src/api/og-tracker.ts +72 -0
  41. package/src/api/parse-action-block.ts +145 -0
  42. package/src/api/permissions-routes.ts +222 -0
  43. package/src/api/plugin-validation.ts +355 -0
  44. package/src/api/provider-switch-config.ts +455 -0
  45. package/src/api/registry-routes.ts +165 -0
  46. package/src/api/registry-service.ts +292 -0
  47. package/src/api/route-helpers.ts +21 -0
  48. package/src/api/sandbox-routes.ts +1480 -0
  49. package/src/api/server.ts +17674 -0
  50. package/src/api/signal-routes.ts +265 -0
  51. package/src/api/stream-persistence.ts +297 -0
  52. package/src/api/stream-route-state.ts +48 -0
  53. package/src/api/stream-routes.ts +1046 -0
  54. package/src/api/stream-voice-routes.ts +208 -0
  55. package/src/api/streaming-text.ts +129 -0
  56. package/src/api/streaming-types.ts +23 -0
  57. package/src/api/subscription-routes.ts +283 -0
  58. package/src/api/terminal-run-limits.ts +31 -0
  59. package/src/api/training-backend-check.ts +40 -0
  60. package/src/api/training-routes.ts +314 -0
  61. package/src/api/training-service-like.ts +46 -0
  62. package/src/api/trajectory-routes.ts +714 -0
  63. package/src/api/trigger-routes.ts +438 -0
  64. package/src/api/twitter-verify.ts +226 -0
  65. package/src/api/tx-service.ts +193 -0
  66. package/src/api/wallet-dex-prices.ts +206 -0
  67. package/src/api/wallet-evm-balance.ts +989 -0
  68. package/src/api/wallet-routes.ts +505 -0
  69. package/src/api/wallet-rpc.ts +523 -0
  70. package/src/api/wallet-trading-profile.ts +694 -0
  71. package/src/api/wallet.ts +745 -0
  72. package/src/api/whatsapp-routes.ts +282 -0
  73. package/src/api/zip-utils.ts +130 -0
  74. package/src/auth/anthropic.ts +63 -0
  75. package/src/auth/apply-stealth.ts +38 -0
  76. package/src/auth/claude-code-stealth.ts +141 -0
  77. package/src/auth/credentials.ts +226 -0
  78. package/src/auth/index.ts +18 -0
  79. package/src/auth/openai-codex.ts +94 -0
  80. package/src/auth/types.ts +24 -0
  81. package/src/awareness/registry.ts +220 -0
  82. package/src/bin.ts +10 -0
  83. package/src/cli/index.ts +36 -0
  84. package/src/cli/parse-duration.ts +43 -0
  85. package/src/cloud/auth.test.ts +370 -0
  86. package/src/cloud/auth.ts +176 -0
  87. package/src/cloud/backup.test.ts +150 -0
  88. package/src/cloud/backup.ts +50 -0
  89. package/src/cloud/base-url.ts +45 -0
  90. package/src/cloud/bridge-client.test.ts +481 -0
  91. package/src/cloud/bridge-client.ts +307 -0
  92. package/src/cloud/cloud-manager.test.ts +223 -0
  93. package/src/cloud/cloud-manager.ts +151 -0
  94. package/src/cloud/cloud-proxy.test.ts +122 -0
  95. package/src/cloud/cloud-proxy.ts +52 -0
  96. package/src/cloud/index.ts +23 -0
  97. package/src/cloud/reconnect.test.ts +178 -0
  98. package/src/cloud/reconnect.ts +108 -0
  99. package/src/cloud/validate-url.test.ts +147 -0
  100. package/src/cloud/validate-url.ts +176 -0
  101. package/src/config/character-schema.ts +44 -0
  102. package/src/config/config.ts +149 -0
  103. package/src/config/env-vars.ts +86 -0
  104. package/src/config/includes.ts +196 -0
  105. package/src/config/index.ts +15 -0
  106. package/src/config/object-utils.ts +10 -0
  107. package/src/config/paths.ts +92 -0
  108. package/src/config/plugin-auto-enable.ts +520 -0
  109. package/src/config/schema.ts +1342 -0
  110. package/src/config/telegram-custom-commands.ts +99 -0
  111. package/src/config/types.agent-defaults.ts +342 -0
  112. package/src/config/types.agents.ts +112 -0
  113. package/src/config/types.gateway.ts +243 -0
  114. package/src/config/types.hooks.ts +124 -0
  115. package/src/config/types.messages.ts +201 -0
  116. package/src/config/types.milady.ts +791 -0
  117. package/src/config/types.tools.ts +416 -0
  118. package/src/config/types.ts +7 -0
  119. package/src/config/zod-schema.agent-runtime.ts +777 -0
  120. package/src/config/zod-schema.core.ts +778 -0
  121. package/src/config/zod-schema.hooks.ts +139 -0
  122. package/src/config/zod-schema.providers-core.ts +1126 -0
  123. package/src/config/zod-schema.session.ts +98 -0
  124. package/src/config/zod-schema.ts +865 -0
  125. package/src/contracts/apps.ts +46 -0
  126. package/src/contracts/awareness.ts +56 -0
  127. package/src/contracts/config.ts +172 -0
  128. package/src/contracts/drop.ts +21 -0
  129. package/src/contracts/index.ts +8 -0
  130. package/src/contracts/onboarding.ts +592 -0
  131. package/src/contracts/permissions.ts +52 -0
  132. package/src/contracts/verification.ts +9 -0
  133. package/src/contracts/wallet.ts +503 -0
  134. package/src/diagnostics/integration-observability.ts +132 -0
  135. package/src/emotes/catalog.ts +655 -0
  136. package/src/external-modules.d.ts +7 -0
  137. package/src/hooks/discovery.test.ts +357 -0
  138. package/src/hooks/discovery.ts +231 -0
  139. package/src/hooks/eligibility.ts +146 -0
  140. package/src/hooks/hooks.test.ts +320 -0
  141. package/src/hooks/index.ts +8 -0
  142. package/src/hooks/loader.test.ts +418 -0
  143. package/src/hooks/loader.ts +256 -0
  144. package/src/hooks/registry.test.ts +168 -0
  145. package/src/hooks/registry.ts +74 -0
  146. package/src/hooks/types.ts +121 -0
  147. package/src/index.ts +19 -0
  148. package/src/onboarding-presets.ts +828 -0
  149. package/src/plugins/custom-rtmp/index.ts +40 -0
  150. package/src/providers/admin-trust.ts +76 -0
  151. package/src/providers/session-bridge.ts +143 -0
  152. package/src/providers/session-utils.ts +42 -0
  153. package/src/providers/simple-mode.ts +113 -0
  154. package/src/providers/ui-catalog.ts +135 -0
  155. package/src/providers/workspace-provider.ts +213 -0
  156. package/src/providers/workspace.ts +497 -0
  157. package/src/runtime/agent-event-service.ts +57 -0
  158. package/src/runtime/cloud-onboarding.test.ts +489 -0
  159. package/src/runtime/cloud-onboarding.ts +408 -0
  160. package/src/runtime/core-plugins.ts +53 -0
  161. package/src/runtime/custom-actions.ts +605 -0
  162. package/src/runtime/eliza.ts +4941 -0
  163. package/src/runtime/embedding-presets.ts +73 -0
  164. package/src/runtime/index.ts +8 -0
  165. package/src/runtime/milady-plugin.ts +180 -0
  166. package/src/runtime/onboarding-names.ts +76 -0
  167. package/src/runtime/release-plugin-policy.ts +119 -0
  168. package/src/runtime/restart.ts +59 -0
  169. package/src/runtime/trajectory-persistence.ts +2584 -0
  170. package/src/runtime/version.ts +6 -0
  171. package/src/security/audit-log.ts +222 -0
  172. package/src/security/network-policy.ts +91 -0
  173. package/src/server/index.ts +6 -0
  174. package/src/services/agent-export.ts +976 -0
  175. package/src/services/app-manager.ts +755 -0
  176. package/src/services/browser-capture.ts +215 -0
  177. package/src/services/coding-agent-context.ts +355 -0
  178. package/src/services/fallback-training-service.ts +196 -0
  179. package/src/services/index.ts +17 -0
  180. package/src/services/mcp-marketplace.ts +327 -0
  181. package/src/services/plugin-manager-types.ts +185 -0
  182. package/src/services/privy-wallets.ts +352 -0
  183. package/src/services/registry-client-app-meta.ts +201 -0
  184. package/src/services/registry-client-endpoints.ts +253 -0
  185. package/src/services/registry-client-local.ts +485 -0
  186. package/src/services/registry-client-network.ts +173 -0
  187. package/src/services/registry-client-queries.ts +176 -0
  188. package/src/services/registry-client-types.ts +104 -0
  189. package/src/services/registry-client.ts +366 -0
  190. package/src/services/remote-signing-service.ts +261 -0
  191. package/src/services/sandbox-engine.ts +753 -0
  192. package/src/services/sandbox-manager.ts +503 -0
  193. package/src/services/self-updater.ts +213 -0
  194. package/src/services/signal-pairing.ts +189 -0
  195. package/src/services/signing-policy.ts +230 -0
  196. package/src/services/skill-catalog-client.ts +195 -0
  197. package/src/services/skill-marketplace.ts +909 -0
  198. package/src/services/stream-manager.ts +707 -0
  199. package/src/services/tts-stream-bridge.ts +465 -0
  200. package/src/services/update-checker.ts +163 -0
  201. package/src/services/version-compat.ts +367 -0
  202. package/src/services/whatsapp-pairing.ts +279 -0
  203. package/src/shared/ui-catalog-prompt.ts +1158 -0
  204. package/src/test-support/process-helpers.ts +35 -0
  205. package/src/test-support/route-test-helpers.ts +113 -0
  206. package/src/test-support/test-helpers.ts +304 -0
  207. package/src/testing/index.ts +3 -0
  208. package/src/triggers/action.ts +342 -0
  209. package/src/triggers/runtime.ts +432 -0
  210. package/src/triggers/scheduling.ts +472 -0
  211. package/src/triggers/types.ts +133 -0
  212. package/src/types/app-hyperscape-routes-shim.d.ts +29 -0
  213. package/src/types/external-modules.d.ts +7 -0
  214. package/src/utils/exec-safety.ts +23 -0
  215. package/src/utils/number-parsing.ts +112 -0
  216. package/src/utils/spoken-text.ts +65 -0
  217. package/src/version-resolver.ts +60 -0
  218. package/test/api/agent-admin-routes.test.ts +160 -0
  219. package/test/api/agent-lifecycle-routes.test.ts +164 -0
  220. package/test/api/agent-transfer-routes.test.ts +136 -0
  221. package/test/api/apps-routes.test.ts +140 -0
  222. package/test/api/auth-routes.test.ts +160 -0
  223. package/test/api/bug-report-routes.test.ts +88 -0
  224. package/test/api/knowledge-routes.test.ts +73 -0
  225. package/test/api/lifecycle.test.ts +342 -0
  226. package/test/api/memory-routes.test.ts +74 -0
  227. package/test/api/models-routes.test.ts +112 -0
  228. package/test/api/nfa-routes.test.ts +78 -0
  229. package/test/api/permissions-routes.test.ts +185 -0
  230. package/test/api/registry-routes.test.ts +157 -0
  231. package/test/api/signal-routes.test.ts +113 -0
  232. package/test/api/subscription-routes.test.ts +90 -0
  233. package/test/api/trigger-routes.test.ts +87 -0
  234. package/test/api/wallet-routes.observability.test.ts +191 -0
  235. package/test/api/wallet-routes.test.ts +502 -0
  236. package/test/diagnostics/integration-observability.test.ts +135 -0
  237. package/test/security/audit-log.test.ts +229 -0
  238. package/test/security/network-policy.test.ts +143 -0
  239. package/test/services/version-compat.test.ts +127 -0
  240. package/tsconfig.build.json +21 -0
  241. package/tsconfig.json +19 -0
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Signal pairing service — manages device linking via QR code.
3
+ *
4
+ * Mirrors whatsapp-pairing.ts but uses @elizaos/signal-native instead of
5
+ * Baileys. Signal linking produces a single provisioning URL (not a refresh
6
+ * loop) — if it times out, restart the session.
7
+ */
8
+
9
+ import fs from "node:fs";
10
+ import path from "node:path";
11
+
12
+ const LOG_PREFIX = "[signal-pairing]";
13
+ const SIGNAL_NATIVE_MODULE_ID = "@elizaos/signal-native";
14
+
15
+ /** Validate accountId to prevent path traversal. */
16
+ export function sanitizeAccountId(raw: string): string {
17
+ const cleaned = raw.replace(/[^a-zA-Z0-9_-]/g, "");
18
+ if (!cleaned || cleaned !== raw) {
19
+ throw new Error(
20
+ `Invalid accountId: must only contain alphanumeric characters, dashes, and underscores`,
21
+ );
22
+ }
23
+ return cleaned;
24
+ }
25
+
26
+ export type SignalPairingStatus =
27
+ | "idle"
28
+ | "initializing"
29
+ | "waiting_for_qr"
30
+ | "connected"
31
+ | "disconnected"
32
+ | "timeout"
33
+ | "error";
34
+
35
+ export interface SignalPairingEvent {
36
+ type: "signal-qr" | "signal-status";
37
+ accountId: string;
38
+ qrDataUrl?: string;
39
+ status?: SignalPairingStatus;
40
+ uuid?: string;
41
+ phoneNumber?: string;
42
+ error?: string;
43
+ }
44
+
45
+ export interface SignalPairingOptions {
46
+ authDir: string;
47
+ accountId: string;
48
+ onEvent: (event: SignalPairingEvent) => void;
49
+ }
50
+
51
+ interface QrCodeModule {
52
+ toDataURL: (
53
+ text: string,
54
+ options?: Record<string, unknown>,
55
+ ) => Promise<string>;
56
+ }
57
+
58
+ export class SignalPairingSession {
59
+ private status: SignalPairingStatus = "idle";
60
+ private options: SignalPairingOptions;
61
+ private aborted = false;
62
+
63
+ constructor(options: SignalPairingOptions) {
64
+ this.options = options;
65
+ }
66
+
67
+ async start(): Promise<void> {
68
+ this.aborted = false;
69
+ this.setStatus("initializing");
70
+
71
+ let native: typeof import("@elizaos/signal-native");
72
+ let qrCode: QrCodeModule;
73
+ try {
74
+ native = await import(/* @vite-ignore */ SIGNAL_NATIVE_MODULE_ID);
75
+ const importedQrCode = await import("qrcode");
76
+ qrCode = (importedQrCode.default ?? importedQrCode) as QrCodeModule;
77
+ } catch (err) {
78
+ this.setStatus("error");
79
+ this.options.onEvent({
80
+ type: "signal-status",
81
+ accountId: this.options.accountId,
82
+ status: "error",
83
+ error: `Failed to load dependencies: ${err instanceof Error ? err.message : String(err)}`,
84
+ });
85
+ return;
86
+ }
87
+
88
+ fs.mkdirSync(this.options.authDir, { recursive: true });
89
+
90
+ try {
91
+ console.info(`${LOG_PREFIX} Starting device linking...`);
92
+ const provisioningUrl = await native.linkDevice(
93
+ this.options.authDir,
94
+ "Milady AI",
95
+ );
96
+
97
+ if (this.aborted) return;
98
+
99
+ const qrDataUrl = await qrCode.toDataURL(provisioningUrl, {
100
+ width: 256,
101
+ margin: 2,
102
+ color: { dark: "#000000", light: "#ffffff" },
103
+ });
104
+
105
+ this.setStatus("waiting_for_qr");
106
+ this.options.onEvent({
107
+ type: "signal-qr",
108
+ accountId: this.options.accountId,
109
+ qrDataUrl,
110
+ });
111
+
112
+ console.info(
113
+ `${LOG_PREFIX} QR code generated, waiting for user to scan...`,
114
+ );
115
+
116
+ await native.finishLink(this.options.authDir);
117
+ if (this.aborted) return;
118
+
119
+ let uuid = "";
120
+ let phoneNumber = "";
121
+ try {
122
+ const profile = await native.getProfile(this.options.authDir);
123
+ uuid = profile.uuid;
124
+ phoneNumber = profile.phoneNumber ?? "";
125
+ } catch {
126
+ // Profile fetch is non-critical.
127
+ }
128
+
129
+ this.setStatus("connected");
130
+ this.options.onEvent({
131
+ type: "signal-status",
132
+ accountId: this.options.accountId,
133
+ status: "connected",
134
+ uuid,
135
+ phoneNumber,
136
+ });
137
+
138
+ console.info(
139
+ `${LOG_PREFIX} Device linked successfully${phoneNumber ? ` (${phoneNumber})` : ""}`,
140
+ );
141
+ } catch (err) {
142
+ if (this.aborted) return;
143
+
144
+ const errMsg = err instanceof Error ? err.message : String(err);
145
+ console.error(`${LOG_PREFIX} Linking failed:`, errMsg);
146
+
147
+ this.setStatus("error");
148
+ this.options.onEvent({
149
+ type: "signal-status",
150
+ accountId: this.options.accountId,
151
+ status: "error",
152
+ error: errMsg,
153
+ });
154
+ }
155
+ }
156
+
157
+ stop(): void {
158
+ this.aborted = true;
159
+ }
160
+
161
+ getStatus(): SignalPairingStatus {
162
+ return this.status;
163
+ }
164
+
165
+ private setStatus(status: SignalPairingStatus): void {
166
+ this.status = status;
167
+ this.options.onEvent({
168
+ type: "signal-status",
169
+ accountId: this.options.accountId,
170
+ status,
171
+ });
172
+ }
173
+ }
174
+
175
+ export function signalAuthExists(
176
+ workspaceDir: string,
177
+ accountId = "default",
178
+ ): boolean {
179
+ const authDir = path.join(workspaceDir, "signal-auth", accountId);
180
+ return fs.existsSync(authDir);
181
+ }
182
+
183
+ export function signalLogout(
184
+ workspaceDir: string,
185
+ accountId = "default",
186
+ ): void {
187
+ const authDir = path.join(workspaceDir, "signal-auth", accountId);
188
+ fs.rmSync(authDir, { recursive: true, force: true });
189
+ }
@@ -0,0 +1,230 @@
1
+ /**
2
+ * Transaction signing policy engine.
3
+ * Evaluates requests against chain/contract/value/rate/method/replay rules.
4
+ */
5
+
6
+ export interface SigningPolicy {
7
+ allowedChainIds: number[]; // empty = allow all
8
+ allowedContracts: string[]; // lowercase; empty = allow all
9
+ deniedContracts: string[]; // checked before allowlist
10
+ maxTransactionValueWei: string; // string for BigInt compat
11
+ maxTransactionsPerHour: number;
12
+ maxTransactionsPerDay: number;
13
+ allowedMethodSelectors: string[]; // 4-byte hex; empty = allow all
14
+ humanConfirmationThresholdWei: string;
15
+ requireHumanConfirmation: boolean;
16
+ }
17
+
18
+ export interface SigningRequest {
19
+ requestId: string;
20
+ chainId: number;
21
+ to: string;
22
+ value: string;
23
+ data: string;
24
+ nonce?: number;
25
+ gasLimit?: string;
26
+ createdAt: number;
27
+ }
28
+
29
+ export type PolicyDecision = {
30
+ allowed: boolean;
31
+ reason: string;
32
+ requiresHumanConfirmation: boolean;
33
+ matchedRule: string;
34
+ };
35
+
36
+ export function createDefaultPolicy(): SigningPolicy {
37
+ return {
38
+ allowedChainIds: [],
39
+ allowedContracts: [],
40
+ deniedContracts: [],
41
+ maxTransactionValueWei: "100000000000000000", // 0.1 ETH
42
+ maxTransactionsPerHour: 10,
43
+ maxTransactionsPerDay: 50,
44
+ allowedMethodSelectors: [],
45
+ humanConfirmationThresholdWei: "10000000000000000", // 0.01 ETH
46
+ requireHumanConfirmation: false,
47
+ };
48
+ }
49
+
50
+ export class SigningPolicyEvaluator {
51
+ private policy: SigningPolicy;
52
+ private requestLog: Array<{ requestId: string; timestamp: number }> = [];
53
+ private processedRequestIds = new Set<string>();
54
+
55
+ constructor(policy?: SigningPolicy) {
56
+ this.policy = policy ?? createDefaultPolicy();
57
+ }
58
+
59
+ updatePolicy(policy: SigningPolicy): void {
60
+ this.policy = policy;
61
+ }
62
+
63
+ getPolicy(): SigningPolicy {
64
+ return { ...this.policy };
65
+ }
66
+
67
+ evaluate(request: SigningRequest): PolicyDecision {
68
+ // ── Replay protection ────────────────────────────────────────────
69
+ if (this.processedRequestIds.has(request.requestId)) {
70
+ return {
71
+ allowed: false,
72
+ reason: `Replay detected: request ${request.requestId} already processed`,
73
+ requiresHumanConfirmation: false,
74
+ matchedRule: "replay_protection",
75
+ };
76
+ }
77
+
78
+ // ── Chain ID ─────────────────────────────────────────────────────
79
+ if (
80
+ this.policy.allowedChainIds.length > 0 &&
81
+ !this.policy.allowedChainIds.includes(request.chainId)
82
+ ) {
83
+ return {
84
+ allowed: false,
85
+ reason: `Chain ${request.chainId} not in allowlist`,
86
+ requiresHumanConfirmation: false,
87
+ matchedRule: "chain_id_allowlist",
88
+ };
89
+ }
90
+
91
+ // ── Contract denylist ────────────────────────────────────────────
92
+ const normalizedTo = request.to.toLowerCase();
93
+ if (
94
+ this.policy.deniedContracts.some((c) => c.toLowerCase() === normalizedTo)
95
+ ) {
96
+ return {
97
+ allowed: false,
98
+ reason: `Contract ${request.to} is denylisted`,
99
+ requiresHumanConfirmation: false,
100
+ matchedRule: "contract_denylist",
101
+ };
102
+ }
103
+
104
+ // ── Contract allowlist ───────────────────────────────────────────
105
+ if (
106
+ this.policy.allowedContracts.length > 0 &&
107
+ !this.policy.allowedContracts.some(
108
+ (c) => c.toLowerCase() === normalizedTo,
109
+ )
110
+ ) {
111
+ return {
112
+ allowed: false,
113
+ reason: `Contract ${request.to} not in allowlist`,
114
+ requiresHumanConfirmation: false,
115
+ matchedRule: "contract_allowlist",
116
+ };
117
+ }
118
+
119
+ // ── Value cap ────────────────────────────────────────────────────
120
+ try {
121
+ const txValue = BigInt(request.value || "0");
122
+ const maxValue = BigInt(this.policy.maxTransactionValueWei);
123
+ if (txValue > maxValue) {
124
+ return {
125
+ allowed: false,
126
+ reason: `Value ${request.value} exceeds max ${this.policy.maxTransactionValueWei}`,
127
+ requiresHumanConfirmation: false,
128
+ matchedRule: "value_cap",
129
+ };
130
+ }
131
+ } catch {
132
+ return {
133
+ allowed: false,
134
+ reason: "Invalid transaction value format",
135
+ requiresHumanConfirmation: false,
136
+ matchedRule: "value_parse_error",
137
+ };
138
+ }
139
+
140
+ // ── Method selector ──────────────────────────────────────────────
141
+ if (
142
+ this.policy.allowedMethodSelectors.length > 0 &&
143
+ request.data &&
144
+ request.data.length >= 10
145
+ ) {
146
+ const selector = request.data.substring(0, 10).toLowerCase();
147
+ if (
148
+ !this.policy.allowedMethodSelectors.some(
149
+ (s) => s.toLowerCase() === selector,
150
+ )
151
+ ) {
152
+ return {
153
+ allowed: false,
154
+ reason: `Method selector ${selector} not in allowlist`,
155
+ requiresHumanConfirmation: false,
156
+ matchedRule: "method_selector_allowlist",
157
+ };
158
+ }
159
+ }
160
+
161
+ // ── Rate limiting ────────────────────────────────────────────────
162
+ const now = Date.now();
163
+ const oneHourAgo = now - 60 * 60 * 1000;
164
+ const oneDayAgo = now - 24 * 60 * 60 * 1000;
165
+
166
+ // Prune old entries
167
+ this.requestLog = this.requestLog.filter((r) => r.timestamp > oneDayAgo);
168
+
169
+ const hourCount = this.requestLog.filter(
170
+ (r) => r.timestamp > oneHourAgo,
171
+ ).length;
172
+ if (hourCount >= this.policy.maxTransactionsPerHour) {
173
+ return {
174
+ allowed: false,
175
+ reason: `Rate limit: ${hourCount}/${this.policy.maxTransactionsPerHour} per hour`,
176
+ requiresHumanConfirmation: false,
177
+ matchedRule: "rate_limit_hourly",
178
+ };
179
+ }
180
+
181
+ const dayCount = this.requestLog.filter(
182
+ (r) => r.timestamp > oneDayAgo,
183
+ ).length;
184
+ if (dayCount >= this.policy.maxTransactionsPerDay) {
185
+ return {
186
+ allowed: false,
187
+ reason: `Rate limit: ${dayCount}/${this.policy.maxTransactionsPerDay} per day`,
188
+ requiresHumanConfirmation: false,
189
+ matchedRule: "rate_limit_daily",
190
+ };
191
+ }
192
+
193
+ // ── Human confirmation ───────────────────────────────────────────
194
+ let needsHumanConfirmation = this.policy.requireHumanConfirmation;
195
+ if (!needsHumanConfirmation) {
196
+ try {
197
+ const txValue = BigInt(request.value || "0");
198
+ const threshold = BigInt(this.policy.humanConfirmationThresholdWei);
199
+ if (txValue > threshold) {
200
+ needsHumanConfirmation = true;
201
+ }
202
+ } catch {
203
+ // If value parsing fails for confirmation check, require confirmation
204
+ needsHumanConfirmation = true;
205
+ }
206
+ }
207
+
208
+ // ── Allowed ──────────────────────────────────────────────────────
209
+ return {
210
+ allowed: true,
211
+ reason: "All policy checks passed",
212
+ requiresHumanConfirmation: needsHumanConfirmation,
213
+ matchedRule: "allowed",
214
+ };
215
+ }
216
+
217
+ /** Record after signing completes (for replay + rate tracking). */
218
+ recordRequest(requestId: string): void {
219
+ this.processedRequestIds.add(requestId);
220
+ this.requestLog.push({ requestId, timestamp: Date.now() });
221
+
222
+ // Bound replay cache
223
+ if (this.processedRequestIds.size > 10000) {
224
+ const oldest = [...this.processedRequestIds].slice(0, 5000);
225
+ for (const id of oldest) {
226
+ this.processedRequestIds.delete(id);
227
+ }
228
+ }
229
+ }
230
+ }
@@ -0,0 +1,195 @@
1
+ /**
2
+ * Skill Catalog Client for Milady.
3
+ *
4
+ * Provides a cached skill catalog (memory -> file) sourced from the
5
+ * local skills/.cache/catalog.json. Supports search and browse.
6
+ *
7
+ * @module services/skill-catalog-client
8
+ */
9
+
10
+ import fs from "node:fs/promises";
11
+ import path from "node:path";
12
+ import { fileURLToPath } from "node:url";
13
+ import { logger } from "@elizaos/core";
14
+
15
+ export interface CatalogSkillStats {
16
+ comments: number;
17
+ downloads: number;
18
+ installsAllTime: number;
19
+ installsCurrent: number;
20
+ stars: number;
21
+ versions: number;
22
+ }
23
+
24
+ export interface CatalogSkillVersion {
25
+ version: string;
26
+ createdAt: number;
27
+ changelog: string;
28
+ }
29
+
30
+ export interface CatalogSkill {
31
+ slug: string;
32
+ displayName: string;
33
+ summary: string | null;
34
+ tags: Record<string, string>;
35
+ stats: CatalogSkillStats;
36
+ createdAt: number;
37
+ updatedAt: number;
38
+ latestVersion: CatalogSkillVersion | null;
39
+ }
40
+
41
+ export interface CatalogSearchResult {
42
+ slug: string;
43
+ displayName: string;
44
+ summary: string | null;
45
+ score: number;
46
+ latestVersion: string | null;
47
+ downloads: number;
48
+ stars: number;
49
+ installs: number;
50
+ }
51
+
52
+ let memoryCache: {
53
+ skills: CatalogSkill[];
54
+ loadedAt: number;
55
+ } | null = null;
56
+
57
+ const MEMORY_TTL_MS = 600_000;
58
+
59
+ function findCatalogPaths(): string[] {
60
+ const paths: string[] = [];
61
+
62
+ const envPath = process.env.MILADY_SKILLS_CATALOG?.trim();
63
+ if (envPath) return [envPath];
64
+
65
+ let dir = import.meta.dirname ?? path.dirname(fileURLToPath(import.meta.url));
66
+ for (let i = 0; i < 5; i++) {
67
+ paths.push(path.join(dir, "skills", ".cache", "catalog.json"));
68
+ const parent = path.dirname(dir);
69
+ if (parent === dir) break;
70
+ dir = parent;
71
+ }
72
+
73
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
74
+ if (home) {
75
+ paths.push(path.join(home, ".milady", "skills", "catalog.json"));
76
+ }
77
+
78
+ return paths;
79
+ }
80
+
81
+ async function readCatalogFile(): Promise<CatalogSkill[] | null> {
82
+ for (const catalogPath of findCatalogPaths()) {
83
+ try {
84
+ const raw = await fs.readFile(catalogPath, "utf-8");
85
+ const parsed = JSON.parse(raw) as {
86
+ data?: CatalogSkill[];
87
+ cachedAt?: number;
88
+ };
89
+ if (Array.isArray(parsed.data) && parsed.data.length > 0) {
90
+ logger.debug(
91
+ `[skill-catalog] Loaded ${parsed.data.length} skills from ${catalogPath}`,
92
+ );
93
+ return parsed.data;
94
+ }
95
+ } catch {
96
+ // Try next path.
97
+ }
98
+ }
99
+ return null;
100
+ }
101
+
102
+ export async function getCatalogSkills(): Promise<CatalogSkill[]> {
103
+ if (memoryCache && Date.now() - memoryCache.loadedAt < MEMORY_TTL_MS) {
104
+ return memoryCache.skills;
105
+ }
106
+
107
+ const skills = await readCatalogFile();
108
+ if (!skills) {
109
+ logger.warn("[skill-catalog] No catalog file found");
110
+ return [];
111
+ }
112
+
113
+ memoryCache = { skills, loadedAt: Date.now() };
114
+ return skills;
115
+ }
116
+
117
+ export async function refreshCatalog(): Promise<CatalogSkill[]> {
118
+ memoryCache = null;
119
+ return getCatalogSkills();
120
+ }
121
+
122
+ export async function getCatalogSkill(
123
+ slug: string,
124
+ ): Promise<CatalogSkill | null> {
125
+ const skills = await getCatalogSkills();
126
+ return skills.find((s) => s.slug === slug) ?? null;
127
+ }
128
+
129
+ export async function searchCatalogSkills(
130
+ query: string,
131
+ limit = 30,
132
+ ): Promise<CatalogSearchResult[]> {
133
+ const skills = await getCatalogSkills();
134
+ const lq = query.toLowerCase();
135
+ const terms = lq.split(/\s+/).filter((t) => t.length > 1);
136
+
137
+ const scored: Array<{ s: CatalogSkill; score: number }> = [];
138
+
139
+ for (const skill of skills) {
140
+ const slug = skill.slug.toLowerCase();
141
+ const name = (skill.displayName ?? "").toLowerCase();
142
+ const summary = (skill.summary ?? "").toLowerCase();
143
+ let score = 0;
144
+
145
+ if (slug === lq || name === lq) score += 100;
146
+ else if (slug.includes(lq)) score += 50;
147
+ else if (name.includes(lq)) score += 45;
148
+
149
+ if (summary.includes(lq)) score += 30;
150
+
151
+ for (const tag of Object.keys(skill.tags)) {
152
+ if (tag.toLowerCase().includes(lq)) score += 20;
153
+ }
154
+
155
+ for (const term of terms) {
156
+ if (slug.includes(term)) score += 15;
157
+ if (name.includes(term)) score += 12;
158
+ if (summary.includes(term)) score += 8;
159
+ }
160
+
161
+ if (score > 0) {
162
+ if (skill.stats.downloads > 50) score += 3;
163
+ if (skill.stats.downloads > 200) score += 3;
164
+ if (skill.stats.stars > 0) score += 2;
165
+ if (skill.stats.installsCurrent > 0) score += 2;
166
+ scored.push({ s: skill, score });
167
+ }
168
+ }
169
+
170
+ scored.sort(
171
+ (a, b) => b.score - a.score || b.s.stats.downloads - a.s.stats.downloads,
172
+ );
173
+ const max = scored[0]?.score || 1;
174
+
175
+ return scored.slice(0, limit).map(({ s, score }) => ({
176
+ slug: s.slug,
177
+ displayName: s.displayName,
178
+ summary: s.summary,
179
+ score: score / max,
180
+ latestVersion: s.latestVersion?.version ?? null,
181
+ downloads: s.stats.downloads,
182
+ stars: s.stats.stars,
183
+ installs: s.stats.installsAllTime,
184
+ }));
185
+ }
186
+
187
+ export async function getTrendingSkills(limit = 30): Promise<CatalogSkill[]> {
188
+ const skills = await getCatalogSkills();
189
+ return [...skills]
190
+ .sort(
191
+ (a, b) =>
192
+ b.stats.downloads - a.stats.downloads || b.updatedAt - a.updatedAt,
193
+ )
194
+ .slice(0, limit);
195
+ }