@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.
- package/LICENSE +21 -0
- package/package.json +270 -0
- package/src/actions/emote.ts +101 -0
- package/src/actions/restart.ts +101 -0
- package/src/actions/send-message.ts +168 -0
- package/src/actions/stream-control.ts +439 -0
- package/src/actions/switch-stream-source.ts +126 -0
- package/src/actions/terminal.ts +186 -0
- package/src/api/agent-admin-routes.ts +178 -0
- package/src/api/agent-lifecycle-routes.ts +129 -0
- package/src/api/agent-model.ts +143 -0
- package/src/api/agent-transfer-routes.ts +211 -0
- package/src/api/apps-routes.ts +210 -0
- package/src/api/auth-routes.ts +90 -0
- package/src/api/bsc-trade.ts +736 -0
- package/src/api/bug-report-routes.ts +161 -0
- package/src/api/character-routes.ts +421 -0
- package/src/api/cloud-billing-routes.ts +598 -0
- package/src/api/cloud-compat-routes.ts +192 -0
- package/src/api/cloud-routes.ts +529 -0
- package/src/api/cloud-status-routes.ts +234 -0
- package/src/api/compat-utils.ts +154 -0
- package/src/api/connector-health.ts +135 -0
- package/src/api/coordinator-wiring.ts +179 -0
- package/src/api/credit-detection.ts +47 -0
- package/src/api/database.ts +1357 -0
- package/src/api/diagnostics-routes.ts +389 -0
- package/src/api/drop-service.ts +205 -0
- package/src/api/early-logs.ts +111 -0
- package/src/api/http-helpers.ts +252 -0
- package/src/api/index.ts +85 -0
- package/src/api/knowledge-routes.ts +1189 -0
- package/src/api/knowledge-service-loader.ts +92 -0
- package/src/api/memory-bounds.ts +121 -0
- package/src/api/memory-routes.ts +349 -0
- package/src/api/merkle-tree.ts +239 -0
- package/src/api/models-routes.ts +72 -0
- package/src/api/nfa-routes.ts +169 -0
- package/src/api/nft-verify.ts +188 -0
- package/src/api/og-tracker.ts +72 -0
- package/src/api/parse-action-block.ts +145 -0
- package/src/api/permissions-routes.ts +222 -0
- package/src/api/plugin-validation.ts +355 -0
- package/src/api/provider-switch-config.ts +455 -0
- package/src/api/registry-routes.ts +165 -0
- package/src/api/registry-service.ts +292 -0
- package/src/api/route-helpers.ts +21 -0
- package/src/api/sandbox-routes.ts +1480 -0
- package/src/api/server.ts +17674 -0
- package/src/api/signal-routes.ts +265 -0
- package/src/api/stream-persistence.ts +297 -0
- package/src/api/stream-route-state.ts +48 -0
- package/src/api/stream-routes.ts +1046 -0
- package/src/api/stream-voice-routes.ts +208 -0
- package/src/api/streaming-text.ts +129 -0
- package/src/api/streaming-types.ts +23 -0
- package/src/api/subscription-routes.ts +283 -0
- package/src/api/terminal-run-limits.ts +31 -0
- package/src/api/training-backend-check.ts +40 -0
- package/src/api/training-routes.ts +314 -0
- package/src/api/training-service-like.ts +46 -0
- package/src/api/trajectory-routes.ts +714 -0
- package/src/api/trigger-routes.ts +438 -0
- package/src/api/twitter-verify.ts +226 -0
- package/src/api/tx-service.ts +193 -0
- package/src/api/wallet-dex-prices.ts +206 -0
- package/src/api/wallet-evm-balance.ts +989 -0
- package/src/api/wallet-routes.ts +505 -0
- package/src/api/wallet-rpc.ts +523 -0
- package/src/api/wallet-trading-profile.ts +694 -0
- package/src/api/wallet.ts +745 -0
- package/src/api/whatsapp-routes.ts +282 -0
- package/src/api/zip-utils.ts +130 -0
- package/src/auth/anthropic.ts +63 -0
- package/src/auth/apply-stealth.ts +38 -0
- package/src/auth/claude-code-stealth.ts +141 -0
- package/src/auth/credentials.ts +226 -0
- package/src/auth/index.ts +18 -0
- package/src/auth/openai-codex.ts +94 -0
- package/src/auth/types.ts +24 -0
- package/src/awareness/registry.ts +220 -0
- package/src/bin.ts +10 -0
- package/src/cli/index.ts +36 -0
- package/src/cli/parse-duration.ts +43 -0
- package/src/cloud/auth.test.ts +370 -0
- package/src/cloud/auth.ts +176 -0
- package/src/cloud/backup.test.ts +150 -0
- package/src/cloud/backup.ts +50 -0
- package/src/cloud/base-url.ts +45 -0
- package/src/cloud/bridge-client.test.ts +481 -0
- package/src/cloud/bridge-client.ts +307 -0
- package/src/cloud/cloud-manager.test.ts +223 -0
- package/src/cloud/cloud-manager.ts +151 -0
- package/src/cloud/cloud-proxy.test.ts +122 -0
- package/src/cloud/cloud-proxy.ts +52 -0
- package/src/cloud/index.ts +23 -0
- package/src/cloud/reconnect.test.ts +178 -0
- package/src/cloud/reconnect.ts +108 -0
- package/src/cloud/validate-url.test.ts +147 -0
- package/src/cloud/validate-url.ts +176 -0
- package/src/config/character-schema.ts +44 -0
- package/src/config/config.ts +149 -0
- package/src/config/env-vars.ts +86 -0
- package/src/config/includes.ts +196 -0
- package/src/config/index.ts +15 -0
- package/src/config/object-utils.ts +10 -0
- package/src/config/paths.ts +92 -0
- package/src/config/plugin-auto-enable.ts +520 -0
- package/src/config/schema.ts +1342 -0
- package/src/config/telegram-custom-commands.ts +99 -0
- package/src/config/types.agent-defaults.ts +342 -0
- package/src/config/types.agents.ts +112 -0
- package/src/config/types.gateway.ts +243 -0
- package/src/config/types.hooks.ts +124 -0
- package/src/config/types.messages.ts +201 -0
- package/src/config/types.milady.ts +791 -0
- package/src/config/types.tools.ts +416 -0
- package/src/config/types.ts +7 -0
- package/src/config/zod-schema.agent-runtime.ts +777 -0
- package/src/config/zod-schema.core.ts +778 -0
- package/src/config/zod-schema.hooks.ts +139 -0
- package/src/config/zod-schema.providers-core.ts +1126 -0
- package/src/config/zod-schema.session.ts +98 -0
- package/src/config/zod-schema.ts +865 -0
- package/src/contracts/apps.ts +46 -0
- package/src/contracts/awareness.ts +56 -0
- package/src/contracts/config.ts +172 -0
- package/src/contracts/drop.ts +21 -0
- package/src/contracts/index.ts +8 -0
- package/src/contracts/onboarding.ts +592 -0
- package/src/contracts/permissions.ts +52 -0
- package/src/contracts/verification.ts +9 -0
- package/src/contracts/wallet.ts +503 -0
- package/src/diagnostics/integration-observability.ts +132 -0
- package/src/emotes/catalog.ts +655 -0
- package/src/external-modules.d.ts +7 -0
- package/src/hooks/discovery.test.ts +357 -0
- package/src/hooks/discovery.ts +231 -0
- package/src/hooks/eligibility.ts +146 -0
- package/src/hooks/hooks.test.ts +320 -0
- package/src/hooks/index.ts +8 -0
- package/src/hooks/loader.test.ts +418 -0
- package/src/hooks/loader.ts +256 -0
- package/src/hooks/registry.test.ts +168 -0
- package/src/hooks/registry.ts +74 -0
- package/src/hooks/types.ts +121 -0
- package/src/index.ts +19 -0
- package/src/onboarding-presets.ts +828 -0
- package/src/plugins/custom-rtmp/index.ts +40 -0
- package/src/providers/admin-trust.ts +76 -0
- package/src/providers/session-bridge.ts +143 -0
- package/src/providers/session-utils.ts +42 -0
- package/src/providers/simple-mode.ts +113 -0
- package/src/providers/ui-catalog.ts +135 -0
- package/src/providers/workspace-provider.ts +213 -0
- package/src/providers/workspace.ts +497 -0
- package/src/runtime/agent-event-service.ts +57 -0
- package/src/runtime/cloud-onboarding.test.ts +489 -0
- package/src/runtime/cloud-onboarding.ts +408 -0
- package/src/runtime/core-plugins.ts +53 -0
- package/src/runtime/custom-actions.ts +605 -0
- package/src/runtime/eliza.ts +4941 -0
- package/src/runtime/embedding-presets.ts +73 -0
- package/src/runtime/index.ts +8 -0
- package/src/runtime/milady-plugin.ts +180 -0
- package/src/runtime/onboarding-names.ts +76 -0
- package/src/runtime/release-plugin-policy.ts +119 -0
- package/src/runtime/restart.ts +59 -0
- package/src/runtime/trajectory-persistence.ts +2584 -0
- package/src/runtime/version.ts +6 -0
- package/src/security/audit-log.ts +222 -0
- package/src/security/network-policy.ts +91 -0
- package/src/server/index.ts +6 -0
- package/src/services/agent-export.ts +976 -0
- package/src/services/app-manager.ts +755 -0
- package/src/services/browser-capture.ts +215 -0
- package/src/services/coding-agent-context.ts +355 -0
- package/src/services/fallback-training-service.ts +196 -0
- package/src/services/index.ts +17 -0
- package/src/services/mcp-marketplace.ts +327 -0
- package/src/services/plugin-manager-types.ts +185 -0
- package/src/services/privy-wallets.ts +352 -0
- package/src/services/registry-client-app-meta.ts +201 -0
- package/src/services/registry-client-endpoints.ts +253 -0
- package/src/services/registry-client-local.ts +485 -0
- package/src/services/registry-client-network.ts +173 -0
- package/src/services/registry-client-queries.ts +176 -0
- package/src/services/registry-client-types.ts +104 -0
- package/src/services/registry-client.ts +366 -0
- package/src/services/remote-signing-service.ts +261 -0
- package/src/services/sandbox-engine.ts +753 -0
- package/src/services/sandbox-manager.ts +503 -0
- package/src/services/self-updater.ts +213 -0
- package/src/services/signal-pairing.ts +189 -0
- package/src/services/signing-policy.ts +230 -0
- package/src/services/skill-catalog-client.ts +195 -0
- package/src/services/skill-marketplace.ts +909 -0
- package/src/services/stream-manager.ts +707 -0
- package/src/services/tts-stream-bridge.ts +465 -0
- package/src/services/update-checker.ts +163 -0
- package/src/services/version-compat.ts +367 -0
- package/src/services/whatsapp-pairing.ts +279 -0
- package/src/shared/ui-catalog-prompt.ts +1158 -0
- package/src/test-support/process-helpers.ts +35 -0
- package/src/test-support/route-test-helpers.ts +113 -0
- package/src/test-support/test-helpers.ts +304 -0
- package/src/testing/index.ts +3 -0
- package/src/triggers/action.ts +342 -0
- package/src/triggers/runtime.ts +432 -0
- package/src/triggers/scheduling.ts +472 -0
- package/src/triggers/types.ts +133 -0
- package/src/types/app-hyperscape-routes-shim.d.ts +29 -0
- package/src/types/external-modules.d.ts +7 -0
- package/src/utils/exec-safety.ts +23 -0
- package/src/utils/number-parsing.ts +112 -0
- package/src/utils/spoken-text.ts +65 -0
- package/src/version-resolver.ts +60 -0
- package/test/api/agent-admin-routes.test.ts +160 -0
- package/test/api/agent-lifecycle-routes.test.ts +164 -0
- package/test/api/agent-transfer-routes.test.ts +136 -0
- package/test/api/apps-routes.test.ts +140 -0
- package/test/api/auth-routes.test.ts +160 -0
- package/test/api/bug-report-routes.test.ts +88 -0
- package/test/api/knowledge-routes.test.ts +73 -0
- package/test/api/lifecycle.test.ts +342 -0
- package/test/api/memory-routes.test.ts +74 -0
- package/test/api/models-routes.test.ts +112 -0
- package/test/api/nfa-routes.test.ts +78 -0
- package/test/api/permissions-routes.test.ts +185 -0
- package/test/api/registry-routes.test.ts +157 -0
- package/test/api/signal-routes.test.ts +113 -0
- package/test/api/subscription-routes.test.ts +90 -0
- package/test/api/trigger-routes.test.ts +87 -0
- package/test/api/wallet-routes.observability.test.ts +191 -0
- package/test/api/wallet-routes.test.ts +502 -0
- package/test/diagnostics/integration-observability.test.ts +135 -0
- package/test/security/audit-log.test.ts +229 -0
- package/test/security/network-policy.test.ts +143 -0
- package/test/services/version-compat.test.ts +127 -0
- package/tsconfig.build.json +21 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,605 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom Actions runtime loader.
|
|
3
|
+
*
|
|
4
|
+
* Converts `CustomActionDef[]` from config into elizaOS `Action[]` objects
|
|
5
|
+
* so the agent can use them in conversations.
|
|
6
|
+
*
|
|
7
|
+
* @module runtime/custom-actions
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { lookup as dnsLookup } from "node:dns/promises";
|
|
11
|
+
import {
|
|
12
|
+
type RequestOptions as HttpRequestOptions,
|
|
13
|
+
type IncomingMessage,
|
|
14
|
+
request as requestHttp,
|
|
15
|
+
} from "node:http";
|
|
16
|
+
import { request as requestHttps } from "node:https";
|
|
17
|
+
import net from "node:net";
|
|
18
|
+
import { Readable } from "node:stream";
|
|
19
|
+
import type { Action, HandlerOptions, IAgentRuntime } from "@elizaos/core";
|
|
20
|
+
import { loadMiladyConfig } from "../config/config";
|
|
21
|
+
import type {
|
|
22
|
+
CustomActionDef,
|
|
23
|
+
CustomActionHandler,
|
|
24
|
+
} from "../config/types.milady";
|
|
25
|
+
import {
|
|
26
|
+
isBlockedPrivateOrLinkLocalIp,
|
|
27
|
+
normalizeHostLike,
|
|
28
|
+
} from "../security/network-policy";
|
|
29
|
+
|
|
30
|
+
/** Cached runtime reference for hot-registration of new actions. */
|
|
31
|
+
let _runtime: IAgentRuntime | null = null;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Store the runtime reference so we can hot-register actions later.
|
|
35
|
+
* Called once from plugin.init().
|
|
36
|
+
*/
|
|
37
|
+
export function setCustomActionsRuntime(runtime: IAgentRuntime): void {
|
|
38
|
+
_runtime = runtime;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Hot-register a CustomActionDef into the running agent.
|
|
43
|
+
* Returns the elizaOS Action that was registered, or null if no runtime.
|
|
44
|
+
*/
|
|
45
|
+
export function registerCustomActionLive(def: CustomActionDef): Action | null {
|
|
46
|
+
if (!_runtime) return null;
|
|
47
|
+
const action = defToAction(def);
|
|
48
|
+
_runtime.registerAction(action);
|
|
49
|
+
return action;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** API port for shell handler requests. */
|
|
53
|
+
const API_PORT = process.env.API_PORT || process.env.SERVER_PORT || "2138";
|
|
54
|
+
|
|
55
|
+
/** Valid handler types that we actually support. */
|
|
56
|
+
const VALID_HANDLER_TYPES = new Set(["http", "shell", "code"]);
|
|
57
|
+
|
|
58
|
+
type VmRunner = {
|
|
59
|
+
runInNewContext: (
|
|
60
|
+
code: string,
|
|
61
|
+
contextObject: Record<string, unknown>,
|
|
62
|
+
options?: { filename?: string; timeout?: number },
|
|
63
|
+
) => unknown;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
let vmRunner: VmRunner | null = null;
|
|
67
|
+
|
|
68
|
+
const CUSTOM_ACTION_FETCH_TIMEOUT_MS = 15_000;
|
|
69
|
+
|
|
70
|
+
type ResolvedUrlTarget = {
|
|
71
|
+
parsed: URL;
|
|
72
|
+
hostname: string;
|
|
73
|
+
pinnedAddress: string;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
type PinnedFetchInput = {
|
|
77
|
+
url: URL;
|
|
78
|
+
init: RequestInit;
|
|
79
|
+
target: ResolvedUrlTarget;
|
|
80
|
+
timeoutMs: number;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
type PinnedFetchImpl = (input: PinnedFetchInput) => Promise<Response>;
|
|
84
|
+
|
|
85
|
+
function resolveFetchInputUrl(input: RequestInfo | URL): string | null {
|
|
86
|
+
if (typeof input === "string") return input;
|
|
87
|
+
if (input instanceof URL) return input.toString();
|
|
88
|
+
if (typeof Request !== "undefined" && input instanceof Request) {
|
|
89
|
+
return input.url;
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function safeCodeFetch(
|
|
95
|
+
input: RequestInfo | URL,
|
|
96
|
+
init?: RequestInit,
|
|
97
|
+
): Promise<Response> {
|
|
98
|
+
const url = resolveFetchInputUrl(input);
|
|
99
|
+
if (!url) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
"Blocked: cannot make requests to internal network addresses",
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const safety = await resolveUrlSafety(url);
|
|
106
|
+
if (safety.blocked) {
|
|
107
|
+
throw new Error(
|
|
108
|
+
"Blocked: cannot make requests to internal network addresses",
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const requestInit = await buildPinnedFetchInit(input, init);
|
|
113
|
+
const response = safety.target
|
|
114
|
+
? await fetchWithPinnedTarget(
|
|
115
|
+
safety.target,
|
|
116
|
+
requestInit,
|
|
117
|
+
CUSTOM_ACTION_FETCH_TIMEOUT_MS,
|
|
118
|
+
)
|
|
119
|
+
: await fetch(input, requestInit);
|
|
120
|
+
if (response.status >= 300 && response.status < 400) {
|
|
121
|
+
throw new Error(
|
|
122
|
+
"Blocked: redirects are not allowed for code custom actions",
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return response;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async function runCodeHandler(
|
|
130
|
+
code: string,
|
|
131
|
+
params: Record<string, string>,
|
|
132
|
+
): Promise<unknown> {
|
|
133
|
+
if (typeof process === "undefined" || !process.versions?.node) {
|
|
134
|
+
throw new Error("Code actions are only supported in Node runtimes.");
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (!vmRunner) {
|
|
138
|
+
vmRunner = (await import("node:vm")) as VmRunner;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const script = `(async () => { ${code} })();`;
|
|
142
|
+
const context: Record<string, unknown> = Object.create(null);
|
|
143
|
+
context.params = Object.freeze({ ...params });
|
|
144
|
+
|
|
145
|
+
const wrapperScript = `(function(hostFetch) {
|
|
146
|
+
return function fetch(input, init) { return hostFetch(input, init); };
|
|
147
|
+
})`;
|
|
148
|
+
const wrapFetch = vmRunner.runInNewContext(
|
|
149
|
+
wrapperScript,
|
|
150
|
+
Object.create(null),
|
|
151
|
+
{
|
|
152
|
+
filename: "milady-fetch-wrapper",
|
|
153
|
+
timeout: 1_000,
|
|
154
|
+
},
|
|
155
|
+
) as (fn: typeof safeCodeFetch) => typeof safeCodeFetch;
|
|
156
|
+
context.fetch = wrapFetch(safeCodeFetch);
|
|
157
|
+
|
|
158
|
+
return await vmRunner.runInNewContext(`"use strict"; ${script}`, context, {
|
|
159
|
+
filename: "milady-custom-action",
|
|
160
|
+
timeout: 30_000,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function shellEscape(value: string): string {
|
|
165
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function isBlockedIp(ip: string): boolean {
|
|
169
|
+
return isBlockedPrivateOrLinkLocalIp(ip);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function toRequestHeaders(headers: Headers): Record<string, string> {
|
|
173
|
+
const normalized: Record<string, string> = {};
|
|
174
|
+
headers.forEach((value, key) => {
|
|
175
|
+
normalized[key] = value;
|
|
176
|
+
});
|
|
177
|
+
return normalized;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async function toRequestBodyBuffer(
|
|
181
|
+
body: BodyInit | null | undefined,
|
|
182
|
+
): Promise<Buffer | null> {
|
|
183
|
+
if (body === null || body === undefined) return null;
|
|
184
|
+
if (typeof body === "string") return Buffer.from(body);
|
|
185
|
+
if (body instanceof URLSearchParams) return Buffer.from(body.toString());
|
|
186
|
+
if (body instanceof Blob) return Buffer.from(await body.arrayBuffer());
|
|
187
|
+
if (body instanceof ArrayBuffer) return Buffer.from(body);
|
|
188
|
+
if (ArrayBuffer.isView(body)) {
|
|
189
|
+
return Buffer.from(body.buffer, body.byteOffset, body.byteLength);
|
|
190
|
+
}
|
|
191
|
+
throw new Error("Unsupported request body type for custom action fetch");
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function responseFromIncomingMessage(response: IncomingMessage): Response {
|
|
195
|
+
const headers = new Headers();
|
|
196
|
+
for (const [key, value] of Object.entries(response.headers)) {
|
|
197
|
+
if (Array.isArray(value)) {
|
|
198
|
+
for (const item of value) headers.append(key, item);
|
|
199
|
+
} else if (typeof value === "string") {
|
|
200
|
+
headers.set(key, value);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const status = response.statusCode ?? 500;
|
|
205
|
+
const body =
|
|
206
|
+
status === 204 || status === 205 || status === 304
|
|
207
|
+
? null
|
|
208
|
+
: (Readable.toWeb(response) as unknown as ReadableStream<Uint8Array>);
|
|
209
|
+
|
|
210
|
+
return new Response(body, {
|
|
211
|
+
status,
|
|
212
|
+
statusText: response.statusMessage,
|
|
213
|
+
headers,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
async function requestWithPinnedAddress(
|
|
218
|
+
input: PinnedFetchInput,
|
|
219
|
+
): Promise<Response> {
|
|
220
|
+
const { url, init, target, timeoutMs } = input;
|
|
221
|
+
const method = (init.method ?? "GET").toUpperCase();
|
|
222
|
+
const headers = new Headers(init.headers);
|
|
223
|
+
const bodyBuffer = await toRequestBodyBuffer(
|
|
224
|
+
init.body as BodyInit | undefined,
|
|
225
|
+
);
|
|
226
|
+
if (bodyBuffer && !headers.has("content-length")) {
|
|
227
|
+
headers.set("content-length", String(bodyBuffer.byteLength));
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const requestFn = url.protocol === "https:" ? requestHttps : requestHttp;
|
|
231
|
+
const family = net.isIP(target.pinnedAddress) === 6 ? 6 : 4;
|
|
232
|
+
|
|
233
|
+
return await new Promise<Response>((resolve, reject) => {
|
|
234
|
+
let settled = false;
|
|
235
|
+
const signal = init.signal;
|
|
236
|
+
let timeoutHandle: ReturnType<typeof setTimeout> | null = null;
|
|
237
|
+
|
|
238
|
+
const settle = (callback: () => void) => {
|
|
239
|
+
if (settled) return;
|
|
240
|
+
settled = true;
|
|
241
|
+
if (timeoutHandle !== null) clearTimeout(timeoutHandle);
|
|
242
|
+
signal?.removeEventListener("abort", onAbort);
|
|
243
|
+
callback();
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
const onAbort = () => {
|
|
247
|
+
request.destroy(new DOMException("Aborted", "AbortError"));
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
const requestOptions: HttpRequestOptions = {
|
|
251
|
+
protocol: url.protocol,
|
|
252
|
+
hostname: target.hostname,
|
|
253
|
+
port: url.port ? Number(url.port) : undefined,
|
|
254
|
+
method,
|
|
255
|
+
path: `${url.pathname}${url.search}`,
|
|
256
|
+
headers: toRequestHeaders(headers),
|
|
257
|
+
lookup: (_hostname, _options, callback) => {
|
|
258
|
+
callback(null, target.pinnedAddress, family);
|
|
259
|
+
},
|
|
260
|
+
...(url.protocol === "https:"
|
|
261
|
+
? { servername: target.hostname }
|
|
262
|
+
: undefined),
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
const request = requestFn(requestOptions, (response) => {
|
|
266
|
+
settle(() => resolve(responseFromIncomingMessage(response)));
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
request.on("error", (error) => {
|
|
270
|
+
settle(() => reject(error));
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
if (signal) {
|
|
274
|
+
if (signal.aborted) {
|
|
275
|
+
onAbort();
|
|
276
|
+
} else {
|
|
277
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
timeoutHandle = setTimeout(() => {
|
|
282
|
+
request.destroy(
|
|
283
|
+
new Error(`Custom action request timed out after ${timeoutMs}ms`),
|
|
284
|
+
);
|
|
285
|
+
}, timeoutMs);
|
|
286
|
+
|
|
287
|
+
if (bodyBuffer) {
|
|
288
|
+
request.write(bodyBuffer);
|
|
289
|
+
}
|
|
290
|
+
request.end();
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
let pinnedFetchImpl: PinnedFetchImpl = requestWithPinnedAddress;
|
|
295
|
+
|
|
296
|
+
export function __setPinnedFetchImplForTests(
|
|
297
|
+
impl: PinnedFetchImpl | null,
|
|
298
|
+
): void {
|
|
299
|
+
pinnedFetchImpl = impl ?? requestWithPinnedAddress;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
async function resolveUrlSafety(url: string): Promise<{
|
|
303
|
+
blocked: boolean;
|
|
304
|
+
target: ResolvedUrlTarget | null;
|
|
305
|
+
}> {
|
|
306
|
+
try {
|
|
307
|
+
const parsed = new URL(url);
|
|
308
|
+
const hostname = normalizeHostLike(parsed.hostname);
|
|
309
|
+
if (!hostname) return { blocked: true, target: null };
|
|
310
|
+
|
|
311
|
+
if (
|
|
312
|
+
(hostname === "localhost" ||
|
|
313
|
+
hostname === "127.0.0.1" ||
|
|
314
|
+
hostname === "::1") &&
|
|
315
|
+
parsed.port === String(API_PORT)
|
|
316
|
+
) {
|
|
317
|
+
return { blocked: false, target: null };
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (
|
|
321
|
+
hostname === "localhost" ||
|
|
322
|
+
hostname === "127.0.0.1" ||
|
|
323
|
+
hostname === "::1" ||
|
|
324
|
+
hostname === "0.0.0.0" ||
|
|
325
|
+
hostname.endsWith(".local") ||
|
|
326
|
+
hostname === "[::1]" ||
|
|
327
|
+
hostname === "metadata.google.internal" ||
|
|
328
|
+
hostname === "169.254.169.254"
|
|
329
|
+
) {
|
|
330
|
+
return { blocked: true, target: null };
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (net.isIP(hostname)) {
|
|
334
|
+
if (isBlockedIp(hostname)) return { blocked: true, target: null };
|
|
335
|
+
return {
|
|
336
|
+
blocked: false,
|
|
337
|
+
target: {
|
|
338
|
+
parsed,
|
|
339
|
+
hostname,
|
|
340
|
+
pinnedAddress: hostname,
|
|
341
|
+
},
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const records = await dnsLookup(hostname, { all: true });
|
|
346
|
+
const addresses = Array.isArray(records) ? records : [records];
|
|
347
|
+
for (const entry of addresses) {
|
|
348
|
+
if (isBlockedIp(entry.address)) {
|
|
349
|
+
return { blocked: true, target: null };
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return {
|
|
354
|
+
blocked: false,
|
|
355
|
+
target: {
|
|
356
|
+
parsed,
|
|
357
|
+
hostname,
|
|
358
|
+
pinnedAddress: addresses[0]?.address ?? "",
|
|
359
|
+
},
|
|
360
|
+
};
|
|
361
|
+
} catch {
|
|
362
|
+
return { blocked: true, target: null };
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
async function buildPinnedFetchInit(
|
|
367
|
+
input: RequestInfo | URL,
|
|
368
|
+
init?: RequestInit,
|
|
369
|
+
): Promise<RequestInit> {
|
|
370
|
+
if (typeof Request !== "undefined" && input instanceof Request) {
|
|
371
|
+
const headers = new Headers(input.headers);
|
|
372
|
+
if (init?.headers) {
|
|
373
|
+
const overrideHeaders = new Headers(init.headers);
|
|
374
|
+
overrideHeaders.forEach((value, key) => {
|
|
375
|
+
headers.set(key, value);
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const method = init?.method ?? input.method;
|
|
380
|
+
const bodyFromInit = init?.body;
|
|
381
|
+
const bodyFromRequest =
|
|
382
|
+
bodyFromInit !== undefined
|
|
383
|
+
? undefined
|
|
384
|
+
: method === "GET" || method === "HEAD"
|
|
385
|
+
? undefined
|
|
386
|
+
: await input.clone().arrayBuffer();
|
|
387
|
+
|
|
388
|
+
return {
|
|
389
|
+
...init,
|
|
390
|
+
method,
|
|
391
|
+
headers,
|
|
392
|
+
body: bodyFromInit ?? bodyFromRequest,
|
|
393
|
+
signal: init?.signal ?? input.signal,
|
|
394
|
+
redirect: "manual",
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
return {
|
|
399
|
+
...init,
|
|
400
|
+
redirect: "manual",
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
async function fetchWithPinnedTarget(
|
|
405
|
+
target: ResolvedUrlTarget,
|
|
406
|
+
init: RequestInit,
|
|
407
|
+
timeoutMs: number,
|
|
408
|
+
): Promise<Response> {
|
|
409
|
+
if (!target.pinnedAddress) {
|
|
410
|
+
throw new Error(
|
|
411
|
+
"Blocked: cannot make requests to internal network addresses",
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
return pinnedFetchImpl({
|
|
415
|
+
url: target.parsed,
|
|
416
|
+
init,
|
|
417
|
+
target,
|
|
418
|
+
timeoutMs,
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function buildHandler(
|
|
423
|
+
handler: CustomActionHandler,
|
|
424
|
+
paramDefs: CustomActionDef["parameters"],
|
|
425
|
+
): (
|
|
426
|
+
params: Record<string, string>,
|
|
427
|
+
) => Promise<{ ok: boolean; output: string }> {
|
|
428
|
+
if (!VALID_HANDLER_TYPES.has(handler.type)) {
|
|
429
|
+
return async () => ({
|
|
430
|
+
ok: false,
|
|
431
|
+
output: `Unsupported handler type: ${handler.type}`,
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
switch (handler.type) {
|
|
436
|
+
case "http":
|
|
437
|
+
return async (params) => {
|
|
438
|
+
let url = handler.url;
|
|
439
|
+
let body = handler.bodyTemplate ?? "";
|
|
440
|
+
const headers: Record<string, string> = { ...handler.headers };
|
|
441
|
+
|
|
442
|
+
for (const p of paramDefs) {
|
|
443
|
+
const value = params[p.name] ?? "";
|
|
444
|
+
url = url.replaceAll(`{{${p.name}}}`, encodeURIComponent(value));
|
|
445
|
+
body = body.replaceAll(`{{${p.name}}}`, value);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const safety = await resolveUrlSafety(url);
|
|
449
|
+
if (safety.blocked) {
|
|
450
|
+
return {
|
|
451
|
+
ok: false,
|
|
452
|
+
output:
|
|
453
|
+
"Blocked: cannot make requests to internal network addresses",
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
if (!headers["Content-Type"] && body) {
|
|
458
|
+
headers["Content-Type"] = "application/json";
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const fetchOpts: RequestInit = {
|
|
462
|
+
method: handler.method || "GET",
|
|
463
|
+
headers,
|
|
464
|
+
redirect: "manual",
|
|
465
|
+
};
|
|
466
|
+
if (body && handler.method !== "GET" && handler.method !== "HEAD") {
|
|
467
|
+
fetchOpts.body = body;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
const response = safety.target
|
|
471
|
+
? await fetchWithPinnedTarget(
|
|
472
|
+
safety.target,
|
|
473
|
+
fetchOpts,
|
|
474
|
+
CUSTOM_ACTION_FETCH_TIMEOUT_MS,
|
|
475
|
+
)
|
|
476
|
+
: await fetch(url, fetchOpts);
|
|
477
|
+
if (response.status >= 300 && response.status < 400) {
|
|
478
|
+
return {
|
|
479
|
+
ok: false,
|
|
480
|
+
output:
|
|
481
|
+
"Blocked: redirects are not allowed for HTTP custom actions",
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
const text = await response.text();
|
|
485
|
+
return { ok: response.ok, output: text.slice(0, 4000) };
|
|
486
|
+
};
|
|
487
|
+
|
|
488
|
+
case "shell":
|
|
489
|
+
return async (params) => {
|
|
490
|
+
let command = handler.command;
|
|
491
|
+
for (const p of paramDefs) {
|
|
492
|
+
const value = params[p.name] ?? "";
|
|
493
|
+
command = command.replaceAll(`{{${p.name}}}`, shellEscape(value));
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
const response = await fetch(
|
|
497
|
+
`http://localhost:${API_PORT}/api/terminal/run`,
|
|
498
|
+
{
|
|
499
|
+
method: "POST",
|
|
500
|
+
headers: (() => {
|
|
501
|
+
const headers: Record<string, string> = {
|
|
502
|
+
"Content-Type": "application/json",
|
|
503
|
+
};
|
|
504
|
+
const token = process.env.MILADY_API_TOKEN?.trim();
|
|
505
|
+
if (token) {
|
|
506
|
+
headers.Authorization = /^Bearer\s+/i.test(token)
|
|
507
|
+
? token
|
|
508
|
+
: `Bearer ${token}`;
|
|
509
|
+
}
|
|
510
|
+
return headers;
|
|
511
|
+
})(),
|
|
512
|
+
body: JSON.stringify({ command, clientId: "runtime-shell-action" }),
|
|
513
|
+
},
|
|
514
|
+
);
|
|
515
|
+
|
|
516
|
+
if (!response.ok) {
|
|
517
|
+
return {
|
|
518
|
+
ok: false,
|
|
519
|
+
output: `Terminal request failed: HTTP ${response.status}`,
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
return { ok: true, output: `Executed: ${command}` };
|
|
524
|
+
};
|
|
525
|
+
|
|
526
|
+
case "code":
|
|
527
|
+
return async (params) => {
|
|
528
|
+
const result = await runCodeHandler(handler.code, params);
|
|
529
|
+
const output = result !== undefined ? String(result) : "Done";
|
|
530
|
+
return { ok: true, output: output.slice(0, 4000) };
|
|
531
|
+
};
|
|
532
|
+
|
|
533
|
+
default:
|
|
534
|
+
return async () => ({ ok: false, output: "Unknown handler type" });
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
function defToAction(def: CustomActionDef): Action {
|
|
539
|
+
const handler = buildHandler(def.handler, def.parameters);
|
|
540
|
+
|
|
541
|
+
return {
|
|
542
|
+
name: def.name,
|
|
543
|
+
similes: def.similes ?? [],
|
|
544
|
+
description: def.description,
|
|
545
|
+
validate: async () => true,
|
|
546
|
+
|
|
547
|
+
handler: async (_runtime, _message, _state, options) => {
|
|
548
|
+
try {
|
|
549
|
+
const opts = options as HandlerOptions | undefined;
|
|
550
|
+
const params: Record<string, string> = {};
|
|
551
|
+
|
|
552
|
+
for (const p of def.parameters) {
|
|
553
|
+
const value = opts?.parameters?.[p.name];
|
|
554
|
+
if (typeof value === "string") {
|
|
555
|
+
params[p.name] = value;
|
|
556
|
+
} else if (value !== undefined && value !== null) {
|
|
557
|
+
params[p.name] = String(value);
|
|
558
|
+
} else if (p.required) {
|
|
559
|
+
return {
|
|
560
|
+
text: `Missing required parameter: ${p.name}`,
|
|
561
|
+
success: false,
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
const result = await handler(params);
|
|
567
|
+
return {
|
|
568
|
+
text: result.output,
|
|
569
|
+
success: result.ok,
|
|
570
|
+
data: { actionId: def.id, params },
|
|
571
|
+
};
|
|
572
|
+
} catch (err) {
|
|
573
|
+
return {
|
|
574
|
+
text: `Action failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
575
|
+
success: false,
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
},
|
|
579
|
+
|
|
580
|
+
parameters: def.parameters.map((p) => ({
|
|
581
|
+
name: p.name,
|
|
582
|
+
description: p.description,
|
|
583
|
+
required: p.required,
|
|
584
|
+
schema: { type: "string" as const },
|
|
585
|
+
})),
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
export function loadCustomActions(): Action[] {
|
|
590
|
+
try {
|
|
591
|
+
const config = loadMiladyConfig();
|
|
592
|
+
const defs = config.customActions ?? [];
|
|
593
|
+
return defs.filter((d) => d.enabled).map(defToAction);
|
|
594
|
+
} catch {
|
|
595
|
+
return [];
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
export function buildTestHandler(
|
|
600
|
+
def: CustomActionDef,
|
|
601
|
+
): (
|
|
602
|
+
params: Record<string, string>,
|
|
603
|
+
) => Promise<{ ok: boolean; output: string }> {
|
|
604
|
+
return buildHandler(def.handler, def.parameters);
|
|
605
|
+
}
|