@elizaos/plugin-tailscale 2.0.0-beta.1 → 2.0.11-beta.7
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/README.md +38 -31
- package/package.json +27 -10
- package/dist/index.d.ts +0 -147
- package/dist/index.js +0 -902
- package/dist/index.js.map +0 -1
package/dist/index.js
DELETED
|
@@ -1,902 +0,0 @@
|
|
|
1
|
-
// src/index.ts
|
|
2
|
-
import {
|
|
3
|
-
elizaLogger as elizaLogger4,
|
|
4
|
-
getConnectorAccountManager
|
|
5
|
-
} from "@elizaos/core";
|
|
6
|
-
import { tunnelSlotIsFree } from "@elizaos/plugin-tunnel";
|
|
7
|
-
|
|
8
|
-
// src/services/CloudTailscaleService.ts
|
|
9
|
-
import { spawn } from "child_process";
|
|
10
|
-
import { elizaLogger, Service } from "@elizaos/core";
|
|
11
|
-
import { z as z2 } from "zod";
|
|
12
|
-
|
|
13
|
-
// src/accounts.ts
|
|
14
|
-
var DEFAULT_TAILSCALE_ACCOUNT_ID = "default";
|
|
15
|
-
function nonEmptyString(value) {
|
|
16
|
-
return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
|
|
17
|
-
}
|
|
18
|
-
function readSetting(runtime, key) {
|
|
19
|
-
return nonEmptyString(runtime.getSetting(key));
|
|
20
|
-
}
|
|
21
|
-
function normalizeTailscaleAccountId(value) {
|
|
22
|
-
return nonEmptyString(value) ?? DEFAULT_TAILSCALE_ACCOUNT_ID;
|
|
23
|
-
}
|
|
24
|
-
function resolveTailscaleAccountId(runtime, options) {
|
|
25
|
-
return normalizeTailscaleAccountId(
|
|
26
|
-
options?.accountId ?? options?.tailscaleAccountId ?? readSetting(runtime, "TAILSCALE_DEFAULT_ACCOUNT_ID") ?? readSetting(runtime, "TAILSCALE_ACCOUNT_ID")
|
|
27
|
-
);
|
|
28
|
-
}
|
|
29
|
-
function parseAccountsJson(raw) {
|
|
30
|
-
if (!raw) return [];
|
|
31
|
-
try {
|
|
32
|
-
const parsed = JSON.parse(raw);
|
|
33
|
-
if (Array.isArray(parsed)) {
|
|
34
|
-
return parsed.filter(
|
|
35
|
-
(item) => Boolean(item) && typeof item === "object" && !Array.isArray(item)
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
if (parsed && typeof parsed === "object") {
|
|
39
|
-
return Object.entries(parsed).filter(([, value]) => value && typeof value === "object").map(([id, value]) => ({
|
|
40
|
-
...value,
|
|
41
|
-
accountId: value.accountId ?? id
|
|
42
|
-
}));
|
|
43
|
-
}
|
|
44
|
-
} catch {
|
|
45
|
-
return [];
|
|
46
|
-
}
|
|
47
|
-
return [];
|
|
48
|
-
}
|
|
49
|
-
function readRawField(record, keys) {
|
|
50
|
-
const credentials = record.credentials && typeof record.credentials === "object" ? record.credentials : {};
|
|
51
|
-
const metadata = record.metadata && typeof record.metadata === "object" ? record.metadata : {};
|
|
52
|
-
const settings = record.settings && typeof record.settings === "object" ? record.settings : {};
|
|
53
|
-
for (const source of [record, credentials, metadata, settings]) {
|
|
54
|
-
for (const key of keys) {
|
|
55
|
-
const value = source[key];
|
|
56
|
-
if (value !== void 0 && value !== null && value !== "") return value;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
return void 0;
|
|
60
|
-
}
|
|
61
|
-
function normalizeBackend(value) {
|
|
62
|
-
return value === "local" || value === "cloud" || value === "auto" ? value : void 0;
|
|
63
|
-
}
|
|
64
|
-
function accountFromRecord(record) {
|
|
65
|
-
const accountId = normalizeTailscaleAccountId(
|
|
66
|
-
record.accountId ?? record.id ?? record.name
|
|
67
|
-
);
|
|
68
|
-
const account = {
|
|
69
|
-
accountId,
|
|
70
|
-
authKey: nonEmptyString(
|
|
71
|
-
readRawField(record, [
|
|
72
|
-
"TAILSCALE_AUTH_KEY",
|
|
73
|
-
"authKey",
|
|
74
|
-
"accessToken",
|
|
75
|
-
"access"
|
|
76
|
-
])
|
|
77
|
-
),
|
|
78
|
-
tags: readRawField(record, ["TAILSCALE_TAGS", "tags"]),
|
|
79
|
-
funnel: readRawField(record, ["TAILSCALE_FUNNEL", "funnel"]),
|
|
80
|
-
defaultPort: readRawField(record, [
|
|
81
|
-
"TAILSCALE_DEFAULT_PORT",
|
|
82
|
-
"defaultPort"
|
|
83
|
-
]),
|
|
84
|
-
backend: normalizeBackend(
|
|
85
|
-
readRawField(record, ["TAILSCALE_BACKEND", "backend"])
|
|
86
|
-
),
|
|
87
|
-
authKeyExpirySeconds: readRawField(record, [
|
|
88
|
-
"TAILSCALE_AUTH_KEY_EXPIRY_SECONDS",
|
|
89
|
-
"authKeyExpirySeconds"
|
|
90
|
-
]),
|
|
91
|
-
cloudApiKey: nonEmptyString(
|
|
92
|
-
readRawField(record, ["ELIZAOS_CLOUD_API_KEY", "cloudApiKey"])
|
|
93
|
-
),
|
|
94
|
-
cloudBaseUrl: nonEmptyString(
|
|
95
|
-
readRawField(record, ["ELIZAOS_CLOUD_BASE_URL", "cloudBaseUrl"])
|
|
96
|
-
),
|
|
97
|
-
label: nonEmptyString(record.label ?? record.displayName)
|
|
98
|
-
};
|
|
99
|
-
return account;
|
|
100
|
-
}
|
|
101
|
-
function addAccount(accounts, account) {
|
|
102
|
-
if (account) {
|
|
103
|
-
accounts.set(account.accountId, account);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
function readTailscaleAccounts(runtime) {
|
|
107
|
-
const accounts = /* @__PURE__ */ new Map();
|
|
108
|
-
const characterConfig = runtime.character?.settings?.tailscale;
|
|
109
|
-
const characterAccounts = characterConfig?.accounts;
|
|
110
|
-
if (Array.isArray(characterAccounts)) {
|
|
111
|
-
for (const item of characterAccounts) {
|
|
112
|
-
if (item && typeof item === "object") {
|
|
113
|
-
addAccount(accounts, accountFromRecord(item));
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
} else if (characterAccounts && typeof characterAccounts === "object") {
|
|
117
|
-
for (const [id, value] of Object.entries(
|
|
118
|
-
characterAccounts
|
|
119
|
-
)) {
|
|
120
|
-
if (value && typeof value === "object") {
|
|
121
|
-
addAccount(
|
|
122
|
-
accounts,
|
|
123
|
-
accountFromRecord({
|
|
124
|
-
...value,
|
|
125
|
-
accountId: value.accountId ?? id
|
|
126
|
-
})
|
|
127
|
-
);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
for (const record of parseAccountsJson(
|
|
132
|
-
readSetting(runtime, "TAILSCALE_ACCOUNTS")
|
|
133
|
-
)) {
|
|
134
|
-
addAccount(accounts, accountFromRecord(record));
|
|
135
|
-
}
|
|
136
|
-
addAccount(accounts, {
|
|
137
|
-
accountId: normalizeTailscaleAccountId(
|
|
138
|
-
readSetting(runtime, "TAILSCALE_ACCOUNT_ID") ?? readSetting(runtime, "TAILSCALE_DEFAULT_ACCOUNT_ID")
|
|
139
|
-
),
|
|
140
|
-
authKey: readSetting(runtime, "TAILSCALE_AUTH_KEY"),
|
|
141
|
-
tags: readSetting(runtime, "TAILSCALE_TAGS"),
|
|
142
|
-
funnel: readSetting(runtime, "TAILSCALE_FUNNEL"),
|
|
143
|
-
defaultPort: readSetting(runtime, "TAILSCALE_DEFAULT_PORT"),
|
|
144
|
-
backend: normalizeBackend(readSetting(runtime, "TAILSCALE_BACKEND")),
|
|
145
|
-
authKeyExpirySeconds: readSetting(
|
|
146
|
-
runtime,
|
|
147
|
-
"TAILSCALE_AUTH_KEY_EXPIRY_SECONDS"
|
|
148
|
-
),
|
|
149
|
-
cloudApiKey: readSetting(runtime, "ELIZAOS_CLOUD_API_KEY"),
|
|
150
|
-
cloudBaseUrl: readSetting(runtime, "ELIZAOS_CLOUD_BASE_URL")
|
|
151
|
-
});
|
|
152
|
-
return Array.from(accounts.values());
|
|
153
|
-
}
|
|
154
|
-
function resolveTailscaleAccount(accounts, accountId) {
|
|
155
|
-
return accounts.find((account) => account.accountId === accountId) ?? accounts.find(
|
|
156
|
-
(account) => account.accountId === DEFAULT_TAILSCALE_ACCOUNT_ID
|
|
157
|
-
) ?? accounts[0] ?? null;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// src/environment.ts
|
|
161
|
-
import { z } from "zod";
|
|
162
|
-
var tailscaleEnvSchema = z.object({
|
|
163
|
-
TAILSCALE_AUTH_KEY: z.string().optional(),
|
|
164
|
-
TAILSCALE_TAGS: z.union([z.string(), z.array(z.string())]).optional().transform((value) => {
|
|
165
|
-
if (Array.isArray(value)) return value.filter((tag) => tag.length > 0);
|
|
166
|
-
if (typeof value === "string" && value.length > 0)
|
|
167
|
-
return value.split(",").map((tag) => tag.trim()).filter((tag) => tag.length > 0);
|
|
168
|
-
return ["tag:eliza-tunnel"];
|
|
169
|
-
}).default(["tag:eliza-tunnel"]),
|
|
170
|
-
TAILSCALE_FUNNEL: z.union([z.string(), z.boolean()]).optional().transform((value) => value === true || value === "true" || value === "1").default(false),
|
|
171
|
-
TAILSCALE_DEFAULT_PORT: z.union([z.string(), z.number()]).optional().transform((value) => {
|
|
172
|
-
if (value === void 0 || value === "") return 3e3;
|
|
173
|
-
const num = typeof value === "string" ? Number.parseInt(value, 10) : value;
|
|
174
|
-
if (Number.isNaN(num) || num <= 0 || num > 65535) return 3e3;
|
|
175
|
-
return num;
|
|
176
|
-
}).default(3e3),
|
|
177
|
-
TAILSCALE_BACKEND: z.enum(["local", "cloud", "auto"]).optional().default("auto"),
|
|
178
|
-
TAILSCALE_AUTH_KEY_EXPIRY_SECONDS: z.union([z.string(), z.number()]).optional().transform((value) => {
|
|
179
|
-
if (value === void 0 || value === "") return 3600;
|
|
180
|
-
const num = typeof value === "string" ? Number.parseInt(value, 10) : value;
|
|
181
|
-
if (Number.isNaN(num) || num <= 0) return 3600;
|
|
182
|
-
return num;
|
|
183
|
-
}).default(3600)
|
|
184
|
-
});
|
|
185
|
-
function readSetting2(runtime, key) {
|
|
186
|
-
const value = runtime.getSetting(key);
|
|
187
|
-
if (value === null || value === void 0) return void 0;
|
|
188
|
-
return String(value);
|
|
189
|
-
}
|
|
190
|
-
async function validateTailscaleConfig(runtime, accountId) {
|
|
191
|
-
const resolvedAccountId = accountId ?? resolveTailscaleAccountId(runtime);
|
|
192
|
-
const account = resolveTailscaleAccount(
|
|
193
|
-
readTailscaleAccounts(runtime),
|
|
194
|
-
resolvedAccountId
|
|
195
|
-
);
|
|
196
|
-
const config = {
|
|
197
|
-
TAILSCALE_AUTH_KEY: account?.authKey ?? readSetting2(runtime, "TAILSCALE_AUTH_KEY") ?? process.env.TAILSCALE_AUTH_KEY,
|
|
198
|
-
TAILSCALE_TAGS: account?.tags ?? readSetting2(runtime, "TAILSCALE_TAGS") ?? process.env.TAILSCALE_TAGS,
|
|
199
|
-
TAILSCALE_FUNNEL: account?.funnel ?? readSetting2(runtime, "TAILSCALE_FUNNEL") ?? process.env.TAILSCALE_FUNNEL,
|
|
200
|
-
TAILSCALE_DEFAULT_PORT: account?.defaultPort ?? readSetting2(runtime, "TAILSCALE_DEFAULT_PORT") ?? process.env.TAILSCALE_DEFAULT_PORT,
|
|
201
|
-
TAILSCALE_BACKEND: account?.backend ?? readSetting2(runtime, "TAILSCALE_BACKEND") ?? process.env.TAILSCALE_BACKEND,
|
|
202
|
-
TAILSCALE_AUTH_KEY_EXPIRY_SECONDS: account?.authKeyExpirySeconds ?? readSetting2(runtime, "TAILSCALE_AUTH_KEY_EXPIRY_SECONDS") ?? process.env.TAILSCALE_AUTH_KEY_EXPIRY_SECONDS
|
|
203
|
-
};
|
|
204
|
-
return tailscaleEnvSchema.parse(config);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// src/services/CloudTailscaleService.ts
|
|
208
|
-
var CLOUD_BASE_FALLBACK = "https://api.elizacloud.ai/api/v1";
|
|
209
|
-
var authKeyResponseSchema = z2.object({
|
|
210
|
-
authKey: z2.string(),
|
|
211
|
-
tailnet: z2.string(),
|
|
212
|
-
loginServer: z2.string().optional(),
|
|
213
|
-
hostname: z2.string().optional(),
|
|
214
|
-
magicDnsName: z2.string()
|
|
215
|
-
});
|
|
216
|
-
function defaultCliRunner(cmd, args) {
|
|
217
|
-
return new Promise((resolve, reject) => {
|
|
218
|
-
const child = spawn(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
219
|
-
const out = [];
|
|
220
|
-
const err = [];
|
|
221
|
-
child.stdout?.on("data", (chunk) => out.push(chunk));
|
|
222
|
-
child.stderr?.on("data", (chunk) => err.push(chunk));
|
|
223
|
-
child.on("error", reject);
|
|
224
|
-
child.on(
|
|
225
|
-
"exit",
|
|
226
|
-
(code) => resolve({
|
|
227
|
-
code,
|
|
228
|
-
stdout: Buffer.concat(out).toString("utf8"),
|
|
229
|
-
stderr: Buffer.concat(err).toString("utf8")
|
|
230
|
-
})
|
|
231
|
-
);
|
|
232
|
-
});
|
|
233
|
-
}
|
|
234
|
-
async function defaultFetch(url, init) {
|
|
235
|
-
return normalizeFetchResponse(await fetch(url, init));
|
|
236
|
-
}
|
|
237
|
-
var CloudTailscaleService = class _CloudTailscaleService extends Service {
|
|
238
|
-
static serviceType = "tunnel";
|
|
239
|
-
capabilityDescription = "Provides Tailscale tunnel functionality via Eliza Cloud \u2014 auth keys are minted server-side and the local CLI joins the tailnet.";
|
|
240
|
-
fetchImpl;
|
|
241
|
-
cliRunner;
|
|
242
|
-
tunnelUrl = null;
|
|
243
|
-
tunnelPort = null;
|
|
244
|
-
startedAt = null;
|
|
245
|
-
isShuttingDown = false;
|
|
246
|
-
joinedTailnet = false;
|
|
247
|
-
constructor(runtime, options = {}) {
|
|
248
|
-
super(runtime);
|
|
249
|
-
this.fetchImpl = options.fetch ?? defaultFetch;
|
|
250
|
-
this.cliRunner = options.cliRunner ?? defaultCliRunner;
|
|
251
|
-
}
|
|
252
|
-
static async start(runtime) {
|
|
253
|
-
const service = new _CloudTailscaleService(runtime);
|
|
254
|
-
await service.start();
|
|
255
|
-
return service;
|
|
256
|
-
}
|
|
257
|
-
async start() {
|
|
258
|
-
elizaLogger.info("[CloudTailscaleService] started");
|
|
259
|
-
}
|
|
260
|
-
async stop() {
|
|
261
|
-
await this.stopTunnel();
|
|
262
|
-
}
|
|
263
|
-
async startTunnel(port, options = {}) {
|
|
264
|
-
if (this.isActive()) {
|
|
265
|
-
elizaLogger.warn("[CloudTailscaleService] tunnel already running");
|
|
266
|
-
return this.tunnelUrl ?? void 0;
|
|
267
|
-
}
|
|
268
|
-
if (port === void 0 || port === null) {
|
|
269
|
-
elizaLogger.warn(
|
|
270
|
-
"[CloudTailscaleService] startTunnel called without a port \u2014 service active but no tunnel started"
|
|
271
|
-
);
|
|
272
|
-
return;
|
|
273
|
-
}
|
|
274
|
-
if (port < 1 || port > 65535) {
|
|
275
|
-
throw new Error("Invalid port number");
|
|
276
|
-
}
|
|
277
|
-
const config = await validateTailscaleConfig(
|
|
278
|
-
this.runtime,
|
|
279
|
-
options.accountId
|
|
280
|
-
);
|
|
281
|
-
const { baseUrl, apiKey } = this.resolveCloudCredentials(options.accountId);
|
|
282
|
-
const response = await this.fetchImpl(
|
|
283
|
-
`${baseUrl}/apis/tunnels/tailscale/auth-key`,
|
|
284
|
-
{
|
|
285
|
-
method: "POST",
|
|
286
|
-
headers: {
|
|
287
|
-
"Content-Type": "application/json",
|
|
288
|
-
Authorization: `Bearer ${apiKey}`
|
|
289
|
-
},
|
|
290
|
-
body: JSON.stringify({
|
|
291
|
-
tags: config.TAILSCALE_TAGS,
|
|
292
|
-
expirySeconds: config.TAILSCALE_AUTH_KEY_EXPIRY_SECONDS
|
|
293
|
-
})
|
|
294
|
-
}
|
|
295
|
-
);
|
|
296
|
-
if (!response.ok) {
|
|
297
|
-
const text = await safeReadText(response);
|
|
298
|
-
throw new Error(
|
|
299
|
-
`Cloud Tailscale auth-key mint failed (${response.status} ${response.statusText}): ${text}`
|
|
300
|
-
);
|
|
301
|
-
}
|
|
302
|
-
const rawJson = await response.json();
|
|
303
|
-
const parsed = authKeyResponseSchema.safeParse(rawJson);
|
|
304
|
-
if (!parsed.success) {
|
|
305
|
-
throw new Error(
|
|
306
|
-
`Cloud Tailscale response malformed: ${parsed.error.issues.map((i) => i.message).join("; ")}`
|
|
307
|
-
);
|
|
308
|
-
}
|
|
309
|
-
await this.joinTailnet(parsed.data);
|
|
310
|
-
await this.runServe(port, config.TAILSCALE_FUNNEL);
|
|
311
|
-
this.tunnelUrl = `https://${parsed.data.magicDnsName}`;
|
|
312
|
-
this.tunnelPort = port;
|
|
313
|
-
this.startedAt = /* @__PURE__ */ new Date();
|
|
314
|
-
this.joinedTailnet = true;
|
|
315
|
-
elizaLogger.info(
|
|
316
|
-
`[CloudTailscaleService] tunnel started: ${this.tunnelUrl}`
|
|
317
|
-
);
|
|
318
|
-
return this.tunnelUrl;
|
|
319
|
-
}
|
|
320
|
-
async stopTunnel(_options = {}) {
|
|
321
|
-
if (!this.isActive() && !this.joinedTailnet) {
|
|
322
|
-
elizaLogger.warn("[CloudTailscaleService] no active tunnel to stop");
|
|
323
|
-
return;
|
|
324
|
-
}
|
|
325
|
-
this.isShuttingDown = true;
|
|
326
|
-
elizaLogger.info("[CloudTailscaleService] stopping tunnel");
|
|
327
|
-
if (this.tunnelPort !== null) {
|
|
328
|
-
await this.cliRunner("tailscale", ["serve", "reset"]);
|
|
329
|
-
await this.cliRunner("tailscale", ["funnel", "reset"]);
|
|
330
|
-
}
|
|
331
|
-
if (this.joinedTailnet) {
|
|
332
|
-
await this.cliRunner("tailscale", ["logout"]);
|
|
333
|
-
}
|
|
334
|
-
this.cleanup();
|
|
335
|
-
this.isShuttingDown = false;
|
|
336
|
-
elizaLogger.info("[CloudTailscaleService] tunnel stopped");
|
|
337
|
-
}
|
|
338
|
-
getUrl() {
|
|
339
|
-
return this.tunnelUrl;
|
|
340
|
-
}
|
|
341
|
-
isActive() {
|
|
342
|
-
return this.tunnelUrl !== null && !this.isShuttingDown;
|
|
343
|
-
}
|
|
344
|
-
getStatus() {
|
|
345
|
-
return {
|
|
346
|
-
active: this.isActive(),
|
|
347
|
-
url: this.tunnelUrl,
|
|
348
|
-
port: this.tunnelPort,
|
|
349
|
-
startedAt: this.startedAt,
|
|
350
|
-
provider: "tailscale"
|
|
351
|
-
};
|
|
352
|
-
}
|
|
353
|
-
async joinTailnet(payload) {
|
|
354
|
-
const args = ["up", `--auth-key=${payload.authKey}`];
|
|
355
|
-
const loginServer = payload.loginServer ?? (payload.tailnet.startsWith("http") ? payload.tailnet : null);
|
|
356
|
-
if (loginServer) {
|
|
357
|
-
args.push(`--login-server=${loginServer}`);
|
|
358
|
-
}
|
|
359
|
-
if (payload.hostname) {
|
|
360
|
-
args.push(`--hostname=${payload.hostname}`);
|
|
361
|
-
}
|
|
362
|
-
const result = await this.cliRunner("tailscale", args);
|
|
363
|
-
if (result.code !== 0) {
|
|
364
|
-
throw new Error(
|
|
365
|
-
`tailscale up failed (code ${result.code}): ${result.stderr.trim()}`
|
|
366
|
-
);
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
async runServe(port, funnel) {
|
|
370
|
-
const args = funnel ? ["funnel", String(port)] : ["serve", "--bg", "--https=443", `localhost:${port}`];
|
|
371
|
-
const result = await this.cliRunner("tailscale", args);
|
|
372
|
-
if (result.code !== 0) {
|
|
373
|
-
throw new Error(
|
|
374
|
-
`tailscale ${args[0]} failed (code ${result.code}): ${result.stderr.trim()}`
|
|
375
|
-
);
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
resolveCloudCredentials(accountId) {
|
|
379
|
-
const account = accountId ? resolveTailscaleAccount(readTailscaleAccounts(this.runtime), accountId) : null;
|
|
380
|
-
const apiKey = readNonEmptyString(account?.cloudApiKey) ?? readNonEmptyString(this.runtime.getSetting("ELIZAOS_CLOUD_API_KEY"));
|
|
381
|
-
if (!apiKey) {
|
|
382
|
-
throw new Error(
|
|
383
|
-
"CloudTailscaleService requires ELIZAOS_CLOUD_API_KEY. Set it or use the local backend."
|
|
384
|
-
);
|
|
385
|
-
}
|
|
386
|
-
const baseRaw = readNonEmptyString(account?.cloudBaseUrl) ?? readNonEmptyString(this.runtime.getSetting("ELIZAOS_CLOUD_BASE_URL")) ?? CLOUD_BASE_FALLBACK;
|
|
387
|
-
return { baseUrl: stripTrailingSlash(baseRaw), apiKey };
|
|
388
|
-
}
|
|
389
|
-
cleanup() {
|
|
390
|
-
this.tunnelUrl = null;
|
|
391
|
-
this.tunnelPort = null;
|
|
392
|
-
this.startedAt = null;
|
|
393
|
-
this.joinedTailnet = false;
|
|
394
|
-
}
|
|
395
|
-
};
|
|
396
|
-
function readNonEmptyString(value) {
|
|
397
|
-
if (value === null || value === void 0) return null;
|
|
398
|
-
const trimmed = String(value).trim();
|
|
399
|
-
return trimmed.length > 0 ? trimmed : null;
|
|
400
|
-
}
|
|
401
|
-
function stripTrailingSlash(url) {
|
|
402
|
-
return url.replace(/\/+$/, "");
|
|
403
|
-
}
|
|
404
|
-
async function safeReadText(response) {
|
|
405
|
-
const text = await response.text().catch(() => "");
|
|
406
|
-
return text.slice(0, 500);
|
|
407
|
-
}
|
|
408
|
-
function normalizeFetchResponse(value) {
|
|
409
|
-
if (!isRecord(value)) {
|
|
410
|
-
throw new Error("Cloud Tailscale fetch returned a non-object response");
|
|
411
|
-
}
|
|
412
|
-
const { ok, status, statusText, json, text } = value;
|
|
413
|
-
if (typeof ok !== "boolean" || typeof status !== "number" || typeof statusText !== "string" || typeof json !== "function" || typeof text !== "function") {
|
|
414
|
-
throw new Error(
|
|
415
|
-
"Cloud Tailscale fetch response is missing required fields"
|
|
416
|
-
);
|
|
417
|
-
}
|
|
418
|
-
return {
|
|
419
|
-
ok,
|
|
420
|
-
status,
|
|
421
|
-
statusText,
|
|
422
|
-
json: async () => json.call(value),
|
|
423
|
-
text: async () => String(await text.call(value))
|
|
424
|
-
};
|
|
425
|
-
}
|
|
426
|
-
function isRecord(value) {
|
|
427
|
-
return typeof value === "object" && value !== null;
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
// src/services/LocalTailscaleService.ts
|
|
431
|
-
import { spawn as spawn2 } from "child_process";
|
|
432
|
-
import { elizaLogger as elizaLogger2, Service as Service2 } from "@elizaos/core";
|
|
433
|
-
import { z as z3 } from "zod";
|
|
434
|
-
var tailscaleStatusPeerSchema = z3.object({
|
|
435
|
-
DNSName: z3.string().optional(),
|
|
436
|
-
Online: z3.boolean().optional()
|
|
437
|
-
});
|
|
438
|
-
var tailscaleStatusSchema = z3.object({
|
|
439
|
-
Self: z3.object({
|
|
440
|
-
DNSName: z3.string().optional()
|
|
441
|
-
}).optional(),
|
|
442
|
-
MagicDNSSuffix: z3.string().optional(),
|
|
443
|
-
Peer: z3.record(z3.string(), tailscaleStatusPeerSchema).optional()
|
|
444
|
-
});
|
|
445
|
-
function runCommand(cmd, args) {
|
|
446
|
-
return new Promise((resolve, reject) => {
|
|
447
|
-
const child = spawn2(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
448
|
-
const out = [];
|
|
449
|
-
const err = [];
|
|
450
|
-
child.stdout?.on("data", (chunk) => out.push(chunk));
|
|
451
|
-
child.stderr?.on("data", (chunk) => err.push(chunk));
|
|
452
|
-
child.on("error", reject);
|
|
453
|
-
child.on(
|
|
454
|
-
"exit",
|
|
455
|
-
(code) => resolve({
|
|
456
|
-
code,
|
|
457
|
-
stdout: Buffer.concat(out).toString("utf8"),
|
|
458
|
-
stderr: Buffer.concat(err).toString("utf8")
|
|
459
|
-
})
|
|
460
|
-
);
|
|
461
|
-
});
|
|
462
|
-
}
|
|
463
|
-
function checkTailscaleInstalled() {
|
|
464
|
-
return new Promise((resolve) => {
|
|
465
|
-
const proc = spawn2("which", ["tailscale"]);
|
|
466
|
-
proc.on("exit", (code) => resolve(code === 0));
|
|
467
|
-
proc.on("error", () => resolve(false));
|
|
468
|
-
});
|
|
469
|
-
}
|
|
470
|
-
function parseTailscaleStatus(stdout) {
|
|
471
|
-
let raw;
|
|
472
|
-
try {
|
|
473
|
-
raw = JSON.parse(stdout);
|
|
474
|
-
} catch {
|
|
475
|
-
return null;
|
|
476
|
-
}
|
|
477
|
-
const result = tailscaleStatusSchema.safeParse(raw);
|
|
478
|
-
return result.success ? result.data : null;
|
|
479
|
-
}
|
|
480
|
-
var LocalTailscaleService = class _LocalTailscaleService extends Service2 {
|
|
481
|
-
static serviceType = "tunnel";
|
|
482
|
-
capabilityDescription = "Provides secure tunnel functionality via the locally-installed `tailscale` CLI (serve / funnel).";
|
|
483
|
-
tunnelUrl = null;
|
|
484
|
-
tunnelPort = null;
|
|
485
|
-
startedAt = null;
|
|
486
|
-
isShuttingDown = false;
|
|
487
|
-
useFunnel = false;
|
|
488
|
-
static async start(runtime) {
|
|
489
|
-
const service = new _LocalTailscaleService(runtime);
|
|
490
|
-
await service.start();
|
|
491
|
-
return service;
|
|
492
|
-
}
|
|
493
|
-
async start() {
|
|
494
|
-
elizaLogger2.info("[LocalTailscaleService] starting");
|
|
495
|
-
const installed = await checkTailscaleInstalled();
|
|
496
|
-
if (!installed) {
|
|
497
|
-
throw new Error(
|
|
498
|
-
"tailscale is not installed. Install from https://tailscale.com/download or run: brew install tailscale"
|
|
499
|
-
);
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
async stop() {
|
|
503
|
-
await this.stopTunnel();
|
|
504
|
-
}
|
|
505
|
-
async startTunnel(port, options = {}) {
|
|
506
|
-
if (this.isActive()) {
|
|
507
|
-
elizaLogger2.warn("[LocalTailscaleService] tunnel already running");
|
|
508
|
-
return this.tunnelUrl ?? void 0;
|
|
509
|
-
}
|
|
510
|
-
if (port === void 0 || port === null) {
|
|
511
|
-
elizaLogger2.warn(
|
|
512
|
-
"[LocalTailscaleService] startTunnel called without a port \u2014 service active but no tunnel started"
|
|
513
|
-
);
|
|
514
|
-
return;
|
|
515
|
-
}
|
|
516
|
-
if (port < 1 || port > 65535) {
|
|
517
|
-
throw new Error("Invalid port number");
|
|
518
|
-
}
|
|
519
|
-
const config = await validateTailscaleConfig(
|
|
520
|
-
this.runtime,
|
|
521
|
-
options.accountId
|
|
522
|
-
);
|
|
523
|
-
this.useFunnel = config.TAILSCALE_FUNNEL;
|
|
524
|
-
elizaLogger2.info(
|
|
525
|
-
`[LocalTailscaleService] starting tunnel on port ${port} (funnel=${this.useFunnel})`
|
|
526
|
-
);
|
|
527
|
-
if (this.useFunnel) {
|
|
528
|
-
const result = await runCommand("tailscale", ["funnel", String(port)]);
|
|
529
|
-
if (result.code !== 0) {
|
|
530
|
-
throw new Error(
|
|
531
|
-
`tailscale funnel exited with code ${result.code}: ${result.stderr.trim()}`
|
|
532
|
-
);
|
|
533
|
-
}
|
|
534
|
-
} else {
|
|
535
|
-
const result = await runCommand("tailscale", [
|
|
536
|
-
"serve",
|
|
537
|
-
"--bg",
|
|
538
|
-
"--https=443",
|
|
539
|
-
`localhost:${port}`
|
|
540
|
-
]);
|
|
541
|
-
if (result.code !== 0) {
|
|
542
|
-
throw new Error(
|
|
543
|
-
`tailscale serve exited with code ${result.code}: ${result.stderr.trim()}`
|
|
544
|
-
);
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
const dnsName = await this.fetchSelfDnsName();
|
|
548
|
-
if (!dnsName) {
|
|
549
|
-
throw new Error(
|
|
550
|
-
"tailscale serve started but no DNSName resolved from `tailscale status --json`"
|
|
551
|
-
);
|
|
552
|
-
}
|
|
553
|
-
this.tunnelUrl = this.useFunnel ? `https://${dnsName}` : `https://${dnsName}`;
|
|
554
|
-
this.tunnelPort = port;
|
|
555
|
-
this.startedAt = /* @__PURE__ */ new Date();
|
|
556
|
-
elizaLogger2.info(
|
|
557
|
-
`[LocalTailscaleService] tunnel started: ${this.tunnelUrl}`
|
|
558
|
-
);
|
|
559
|
-
return this.tunnelUrl;
|
|
560
|
-
}
|
|
561
|
-
async stopTunnel(_options = {}) {
|
|
562
|
-
if (!this.isActive()) {
|
|
563
|
-
elizaLogger2.warn("[LocalTailscaleService] no active tunnel to stop");
|
|
564
|
-
return;
|
|
565
|
-
}
|
|
566
|
-
this.isShuttingDown = true;
|
|
567
|
-
elizaLogger2.info("[LocalTailscaleService] stopping tunnel");
|
|
568
|
-
if (this.useFunnel) {
|
|
569
|
-
await runCommand("tailscale", ["funnel", "reset"]);
|
|
570
|
-
} else {
|
|
571
|
-
await runCommand("tailscale", ["serve", "reset"]);
|
|
572
|
-
}
|
|
573
|
-
this.cleanup();
|
|
574
|
-
this.isShuttingDown = false;
|
|
575
|
-
elizaLogger2.info("[LocalTailscaleService] tunnel stopped");
|
|
576
|
-
}
|
|
577
|
-
getUrl() {
|
|
578
|
-
return this.tunnelUrl;
|
|
579
|
-
}
|
|
580
|
-
isActive() {
|
|
581
|
-
return this.tunnelUrl !== null && !this.isShuttingDown;
|
|
582
|
-
}
|
|
583
|
-
getStatus() {
|
|
584
|
-
return {
|
|
585
|
-
active: this.isActive(),
|
|
586
|
-
url: this.tunnelUrl,
|
|
587
|
-
port: this.tunnelPort,
|
|
588
|
-
startedAt: this.startedAt,
|
|
589
|
-
provider: "tailscale"
|
|
590
|
-
};
|
|
591
|
-
}
|
|
592
|
-
async fetchSelfDnsName() {
|
|
593
|
-
const result = await runCommand("tailscale", ["status", "--json"]);
|
|
594
|
-
if (result.code !== 0) {
|
|
595
|
-
elizaLogger2.error(
|
|
596
|
-
`[LocalTailscaleService] tailscale status failed: ${result.stderr.trim()}`
|
|
597
|
-
);
|
|
598
|
-
return null;
|
|
599
|
-
}
|
|
600
|
-
const status = parseTailscaleStatus(result.stdout);
|
|
601
|
-
if (!status) {
|
|
602
|
-
elizaLogger2.error(
|
|
603
|
-
"[LocalTailscaleService] tailscale status returned malformed JSON"
|
|
604
|
-
);
|
|
605
|
-
return null;
|
|
606
|
-
}
|
|
607
|
-
const raw = status.Self?.DNSName;
|
|
608
|
-
if (!raw) return null;
|
|
609
|
-
return raw.replace(/\.$/, "");
|
|
610
|
-
}
|
|
611
|
-
cleanup() {
|
|
612
|
-
this.tunnelUrl = null;
|
|
613
|
-
this.tunnelPort = null;
|
|
614
|
-
this.startedAt = null;
|
|
615
|
-
this.useFunnel = false;
|
|
616
|
-
}
|
|
617
|
-
};
|
|
618
|
-
|
|
619
|
-
// src/__tests__/TailscaleTestSuite.ts
|
|
620
|
-
var CANONICAL_TUNNEL_SERVICE_TYPE = "tunnel";
|
|
621
|
-
var TailscaleTestSuite = class {
|
|
622
|
-
name = "tailscale";
|
|
623
|
-
tests = [
|
|
624
|
-
{
|
|
625
|
-
name: "LocalTailscaleService claims canonical tunnel service-type",
|
|
626
|
-
fn: (_runtime) => {
|
|
627
|
-
if (LocalTailscaleService.serviceType !== CANONICAL_TUNNEL_SERVICE_TYPE) {
|
|
628
|
-
throw new Error(
|
|
629
|
-
`LocalTailscaleService.serviceType must be "${CANONICAL_TUNNEL_SERVICE_TYPE}"`
|
|
630
|
-
);
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
},
|
|
634
|
-
{
|
|
635
|
-
name: "CloudTailscaleService claims canonical tunnel service-type",
|
|
636
|
-
fn: (_runtime) => {
|
|
637
|
-
if (CloudTailscaleService.serviceType !== CANONICAL_TUNNEL_SERVICE_TYPE) {
|
|
638
|
-
throw new Error(
|
|
639
|
-
`CloudTailscaleService.serviceType must be "${CANONICAL_TUNNEL_SERVICE_TYPE}"`
|
|
640
|
-
);
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
];
|
|
645
|
-
};
|
|
646
|
-
|
|
647
|
-
// src/connector-account-provider.ts
|
|
648
|
-
var TAILSCALE_PROVIDER_ID = "tailscale";
|
|
649
|
-
var DEFAULT_PURPOSES = [
|
|
650
|
-
"admin",
|
|
651
|
-
"automation"
|
|
652
|
-
];
|
|
653
|
-
function hasExplicitConfig(account) {
|
|
654
|
-
return Boolean(
|
|
655
|
-
account.authKey || account.tags !== void 0 || account.funnel !== void 0 || account.defaultPort !== void 0 || account.backend !== void 0 || account.authKeyExpirySeconds !== void 0 || account.cloudApiKey || account.cloudBaseUrl
|
|
656
|
-
);
|
|
657
|
-
}
|
|
658
|
-
function authMethodForAccount(account) {
|
|
659
|
-
if (account.cloudApiKey) return "cloud_api_key";
|
|
660
|
-
if (account.authKey) return "auth_key";
|
|
661
|
-
if (account.backend === "local") return "local_cli";
|
|
662
|
-
return "runtime";
|
|
663
|
-
}
|
|
664
|
-
function toConnectorAccount(account, defaultAccountId) {
|
|
665
|
-
const now = Date.now();
|
|
666
|
-
const accountId = normalizeTailscaleAccountId(account.accountId);
|
|
667
|
-
const configured = hasExplicitConfig(account);
|
|
668
|
-
return {
|
|
669
|
-
id: accountId,
|
|
670
|
-
provider: TAILSCALE_PROVIDER_ID,
|
|
671
|
-
label: account.label ?? `Tailscale (${accountId})`,
|
|
672
|
-
role: "OWNER",
|
|
673
|
-
purpose: DEFAULT_PURPOSES,
|
|
674
|
-
accessGate: "open",
|
|
675
|
-
status: configured ? "connected" : "disabled",
|
|
676
|
-
displayHandle: account.label ?? accountId,
|
|
677
|
-
createdAt: now,
|
|
678
|
-
updatedAt: now,
|
|
679
|
-
metadata: {
|
|
680
|
-
authMethod: authMethodForAccount(account),
|
|
681
|
-
source: "legacy",
|
|
682
|
-
isDefault: accountId === defaultAccountId,
|
|
683
|
-
backend: account.backend ?? "auto",
|
|
684
|
-
funnel: account.funnel ?? null,
|
|
685
|
-
defaultPort: account.defaultPort ?? null,
|
|
686
|
-
tags: account.tags ?? null,
|
|
687
|
-
authKeyExpirySeconds: account.authKeyExpirySeconds ?? null,
|
|
688
|
-
hasAuthKey: Boolean(account.authKey),
|
|
689
|
-
hasCloudApiKey: Boolean(account.cloudApiKey),
|
|
690
|
-
cloudBaseUrl: account.cloudBaseUrl ?? null
|
|
691
|
-
}
|
|
692
|
-
};
|
|
693
|
-
}
|
|
694
|
-
function normalizePurposes(purpose, fallback) {
|
|
695
|
-
if (Array.isArray(purpose)) return purpose;
|
|
696
|
-
if (typeof purpose === "string" && purpose.trim()) return [purpose];
|
|
697
|
-
return fallback;
|
|
698
|
-
}
|
|
699
|
-
function mergeStoredAccountPatch(account, patch) {
|
|
700
|
-
return {
|
|
701
|
-
...account,
|
|
702
|
-
...patch,
|
|
703
|
-
provider: TAILSCALE_PROVIDER_ID,
|
|
704
|
-
id: account.id,
|
|
705
|
-
purpose: normalizePurposes(patch.purpose, account.purpose),
|
|
706
|
-
externalId: patch.externalId === void 0 ? account.externalId : patch.externalId ?? void 0,
|
|
707
|
-
displayHandle: patch.displayHandle === void 0 ? account.displayHandle : patch.displayHandle ?? void 0,
|
|
708
|
-
ownerBindingId: patch.ownerBindingId === void 0 ? account.ownerBindingId : patch.ownerBindingId ?? void 0,
|
|
709
|
-
ownerIdentityId: patch.ownerIdentityId === void 0 ? account.ownerIdentityId : patch.ownerIdentityId ?? void 0,
|
|
710
|
-
metadata: patch.metadata ?? account.metadata,
|
|
711
|
-
createdAt: account.createdAt
|
|
712
|
-
};
|
|
713
|
-
}
|
|
714
|
-
function createTailscaleConnectorAccountProvider(runtime) {
|
|
715
|
-
return {
|
|
716
|
-
provider: TAILSCALE_PROVIDER_ID,
|
|
717
|
-
label: "Tailscale",
|
|
718
|
-
listAccounts: async (manager) => {
|
|
719
|
-
const stored = await manager.getStorage().listAccounts(TAILSCALE_PROVIDER_ID);
|
|
720
|
-
const storedById = new Set(stored.map((account) => account.id));
|
|
721
|
-
const defaultAccountId = resolveTailscaleAccountId(runtime);
|
|
722
|
-
const synthesized = readTailscaleAccounts(runtime).map((account) => toConnectorAccount(account, defaultAccountId)).filter((account) => !storedById.has(account.id));
|
|
723
|
-
return [...stored, ...synthesized];
|
|
724
|
-
},
|
|
725
|
-
createAccount: async (input, _manager) => {
|
|
726
|
-
return {
|
|
727
|
-
...input,
|
|
728
|
-
provider: TAILSCALE_PROVIDER_ID,
|
|
729
|
-
role: input.role ?? "OWNER",
|
|
730
|
-
purpose: input.purpose ?? DEFAULT_PURPOSES,
|
|
731
|
-
accessGate: input.accessGate ?? "open",
|
|
732
|
-
status: input.status ?? "pending"
|
|
733
|
-
};
|
|
734
|
-
},
|
|
735
|
-
patchAccount: async (accountId, patch, manager) => {
|
|
736
|
-
const existing = await manager.getStorage().getAccount(TAILSCALE_PROVIDER_ID, accountId);
|
|
737
|
-
if (existing) {
|
|
738
|
-
return mergeStoredAccountPatch(existing, patch);
|
|
739
|
-
}
|
|
740
|
-
return { ...patch, provider: TAILSCALE_PROVIDER_ID };
|
|
741
|
-
},
|
|
742
|
-
deleteAccount: async (_accountId, _manager) => {
|
|
743
|
-
}
|
|
744
|
-
};
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
// src/types.ts
|
|
748
|
-
import {
|
|
749
|
-
getTunnelService
|
|
750
|
-
} from "@elizaos/plugin-tunnel";
|
|
751
|
-
|
|
752
|
-
// src/providers/tailscale-status.ts
|
|
753
|
-
function formatUptime(startedAt) {
|
|
754
|
-
const ms = Date.now() - startedAt.getTime();
|
|
755
|
-
const minutes = Math.floor(ms / 6e4);
|
|
756
|
-
const hours = Math.floor(minutes / 60);
|
|
757
|
-
if (hours > 0) {
|
|
758
|
-
return `${hours} hour${hours === 1 ? "" : "s"}, ${minutes % 60} minute${minutes % 60 === 1 ? "" : "s"}`;
|
|
759
|
-
}
|
|
760
|
-
return `${minutes} minute${minutes === 1 ? "" : "s"}`;
|
|
761
|
-
}
|
|
762
|
-
var tailscaleStatusProvider = {
|
|
763
|
-
name: "tailscaleStatus",
|
|
764
|
-
description: "Current Tailscale tunnel status: active flag, public URL, local port, uptime, backend provider.",
|
|
765
|
-
descriptionCompressed: "Tailscale tunnel status: active, url, port, uptime.",
|
|
766
|
-
dynamic: true,
|
|
767
|
-
contexts: ["settings", "connectors"],
|
|
768
|
-
contextGate: { anyOf: ["settings", "connectors"] },
|
|
769
|
-
cacheStable: false,
|
|
770
|
-
cacheScope: "turn",
|
|
771
|
-
get: async (runtime, _message, _state) => {
|
|
772
|
-
const tunnelService = getTunnelService(runtime);
|
|
773
|
-
if (!tunnelService) {
|
|
774
|
-
return { text: "" };
|
|
775
|
-
}
|
|
776
|
-
const status = tunnelService.getStatus();
|
|
777
|
-
const uptime = status.startedAt ? formatUptime(status.startedAt) : null;
|
|
778
|
-
const text = JSON.stringify({
|
|
779
|
-
tailscale: {
|
|
780
|
-
active: status.active,
|
|
781
|
-
url: status.url,
|
|
782
|
-
port: status.port,
|
|
783
|
-
uptime,
|
|
784
|
-
provider: status.provider
|
|
785
|
-
}
|
|
786
|
-
});
|
|
787
|
-
return {
|
|
788
|
-
text,
|
|
789
|
-
values: {
|
|
790
|
-
active: status.active,
|
|
791
|
-
url: status.url ?? "",
|
|
792
|
-
port: status.port ?? 0,
|
|
793
|
-
provider: status.provider
|
|
794
|
-
},
|
|
795
|
-
data: { status, uptime }
|
|
796
|
-
};
|
|
797
|
-
}
|
|
798
|
-
};
|
|
799
|
-
|
|
800
|
-
// src/services/TunnelBackendSelector.ts
|
|
801
|
-
import { isCloudConnected } from "@elizaos/cloud-routing";
|
|
802
|
-
import { elizaLogger as elizaLogger3 } from "@elizaos/core";
|
|
803
|
-
var ALLOWED_MODES = /* @__PURE__ */ new Set([
|
|
804
|
-
"local",
|
|
805
|
-
"cloud",
|
|
806
|
-
"auto"
|
|
807
|
-
]);
|
|
808
|
-
function readBackendMode(runtime) {
|
|
809
|
-
const account = resolveTailscaleAccount(
|
|
810
|
-
readTailscaleAccounts(runtime),
|
|
811
|
-
resolveTailscaleAccountId(runtime)
|
|
812
|
-
);
|
|
813
|
-
const raw = account?.backend ?? runtime.getSetting("TAILSCALE_BACKEND");
|
|
814
|
-
if (raw === null || raw === void 0) return "auto";
|
|
815
|
-
const normalized = String(raw).trim().toLowerCase();
|
|
816
|
-
if (ALLOWED_MODES.has(normalized)) {
|
|
817
|
-
return normalized;
|
|
818
|
-
}
|
|
819
|
-
elizaLogger3.warn(
|
|
820
|
-
`[TunnelBackendSelector] invalid TAILSCALE_BACKEND="${raw}" \u2014 falling back to "auto"`
|
|
821
|
-
);
|
|
822
|
-
return "auto";
|
|
823
|
-
}
|
|
824
|
-
function selectTunnelBackend(runtime) {
|
|
825
|
-
const mode = readBackendMode(runtime);
|
|
826
|
-
switch (mode) {
|
|
827
|
-
case "local":
|
|
828
|
-
return {
|
|
829
|
-
backend: LocalTailscaleService,
|
|
830
|
-
mode,
|
|
831
|
-
reason: "TAILSCALE_BACKEND=local"
|
|
832
|
-
};
|
|
833
|
-
case "cloud":
|
|
834
|
-
return {
|
|
835
|
-
backend: CloudTailscaleService,
|
|
836
|
-
mode,
|
|
837
|
-
reason: "TAILSCALE_BACKEND=cloud"
|
|
838
|
-
};
|
|
839
|
-
case "auto": {
|
|
840
|
-
if (isCloudConnected(runtime)) {
|
|
841
|
-
return {
|
|
842
|
-
backend: CloudTailscaleService,
|
|
843
|
-
mode,
|
|
844
|
-
reason: "auto: cloud connected"
|
|
845
|
-
};
|
|
846
|
-
}
|
|
847
|
-
return {
|
|
848
|
-
backend: LocalTailscaleService,
|
|
849
|
-
mode,
|
|
850
|
-
reason: "auto: cloud not connected"
|
|
851
|
-
};
|
|
852
|
-
}
|
|
853
|
-
}
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
// src/index.ts
|
|
857
|
-
var tailscalePlugin = {
|
|
858
|
-
name: "tailscale",
|
|
859
|
-
description: "Tunnel plugin with local Tailscale serve/funnel and cloud-proxy backends.",
|
|
860
|
-
actions: [],
|
|
861
|
-
providers: [tailscaleStatusProvider],
|
|
862
|
-
tests: [new TailscaleTestSuite()],
|
|
863
|
-
init: async (_config, runtime) => {
|
|
864
|
-
try {
|
|
865
|
-
const manager = getConnectorAccountManager(runtime);
|
|
866
|
-
manager.registerProvider(
|
|
867
|
-
createTailscaleConnectorAccountProvider(runtime)
|
|
868
|
-
);
|
|
869
|
-
} catch (err) {
|
|
870
|
-
elizaLogger4.warn(
|
|
871
|
-
`[plugin-tailscale] failed to register ConnectorAccountManager provider: ${err instanceof Error ? err.message : String(err)}`
|
|
872
|
-
);
|
|
873
|
-
}
|
|
874
|
-
if (!tunnelSlotIsFree(runtime)) {
|
|
875
|
-
elizaLogger4.info(
|
|
876
|
-
"[plugin-tailscale] another tunnel service already registered \u2014 skipping Tailscale backend"
|
|
877
|
-
);
|
|
878
|
-
return;
|
|
879
|
-
}
|
|
880
|
-
const decision = selectTunnelBackend(runtime);
|
|
881
|
-
elizaLogger4.info(
|
|
882
|
-
`[plugin-tailscale] tunnel backend: ${decision.backend.name} (${decision.reason})`
|
|
883
|
-
);
|
|
884
|
-
await runtime.registerService(decision.backend);
|
|
885
|
-
}
|
|
886
|
-
};
|
|
887
|
-
var index_default = tailscalePlugin;
|
|
888
|
-
export {
|
|
889
|
-
CloudTailscaleService,
|
|
890
|
-
DEFAULT_TAILSCALE_ACCOUNT_ID,
|
|
891
|
-
LocalTailscaleService,
|
|
892
|
-
createTailscaleConnectorAccountProvider,
|
|
893
|
-
index_default as default,
|
|
894
|
-
normalizeTailscaleAccountId,
|
|
895
|
-
readBackendMode,
|
|
896
|
-
readTailscaleAccounts,
|
|
897
|
-
resolveTailscaleAccount,
|
|
898
|
-
resolveTailscaleAccountId,
|
|
899
|
-
selectTunnelBackend,
|
|
900
|
-
tailscalePlugin
|
|
901
|
-
};
|
|
902
|
-
//# sourceMappingURL=index.js.map
|