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