From 966b8656d2d497fa35b0c2e2d7cad128cd0b7e67 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 17 Mar 2026 04:21:19 +0000 Subject: [PATCH] refactor(tlon): share outbound target resolution --- extensions/tlon/src/channel.runtime.ts | 21 +++++++------------- extensions/tlon/src/channel.ts | 21 +++++++------------- extensions/tlon/src/targets.test.ts | 27 ++++++++++++++++++++++++++ extensions/tlon/src/targets.ts | 14 +++++++++++++ 4 files changed, 55 insertions(+), 28 deletions(-) create mode 100644 extensions/tlon/src/targets.test.ts diff --git a/extensions/tlon/src/channel.runtime.ts b/extensions/tlon/src/channel.runtime.ts index f9f11e4620c..525359a2a4e 100644 --- a/extensions/tlon/src/channel.runtime.ts +++ b/extensions/tlon/src/channel.runtime.ts @@ -7,7 +7,12 @@ import type { } from "openclaw/plugin-sdk/tlon"; import { monitorTlonProvider } from "./monitor/index.js"; import { tlonSetupWizard } from "./setup-surface.js"; -import { formatTargetHint, normalizeShip, parseTlonTarget } from "./targets.js"; +import { + formatTargetHint, + normalizeShip, + parseTlonTarget, + resolveTlonOutboundTarget, +} from "./targets.js"; import { resolveTlonAccount } from "./types.js"; import { authenticate } from "./urbit/auth.js"; import { ssrfPolicyFromAllowPrivateNetwork } from "./urbit/context.js"; @@ -131,19 +136,7 @@ async function withHttpPokeAccountApi( export const tlonRuntimeOutbound: ChannelOutboundAdapter = { deliveryMode: "direct", textChunkLimit: 10000, - resolveTarget: ({ to }) => { - const parsed = parseTlonTarget(to ?? ""); - if (!parsed) { - return { - ok: false, - error: new Error(`Invalid Tlon target. Use ${formatTargetHint()}`), - }; - } - if (parsed.kind === "dm") { - return { ok: true, to: parsed.ship }; - } - return { ok: true, to: parsed.nest }; - }, + resolveTarget: ({ to }) => resolveTlonOutboundTarget(to), sendText: async ({ cfg, to, text, accountId, replyToId, threadId }) => { const { account, parsed } = resolveOutboundContext({ cfg, accountId, to }); return withHttpPokeAccountApi(account, async (api) => { diff --git a/extensions/tlon/src/channel.ts b/extensions/tlon/src/channel.ts index fa7c702354d..5f754201ac1 100644 --- a/extensions/tlon/src/channel.ts +++ b/extensions/tlon/src/channel.ts @@ -6,7 +6,12 @@ import { resolveTlonSetupConfigured, tlonSetupAdapter, } from "./setup-core.js"; -import { formatTargetHint, normalizeShip, parseTlonTarget } from "./targets.js"; +import { + formatTargetHint, + normalizeShip, + parseTlonTarget, + resolveTlonOutboundTarget, +} from "./targets.js"; import { resolveTlonAccount, listTlonAccountIds } from "./types.js"; import { validateUrbitBaseUrl } from "./urbit/base-url.js"; @@ -151,19 +156,7 @@ export const tlonPlugin: ChannelPlugin = { outbound: { deliveryMode: "direct", textChunkLimit: 10000, - resolveTarget: ({ to }) => { - const parsed = parseTlonTarget(to ?? ""); - if (!parsed) { - return { - ok: false, - error: new Error(`Invalid Tlon target. Use ${formatTargetHint()}`), - }; - } - if (parsed.kind === "dm") { - return { ok: true, to: parsed.ship }; - } - return { ok: true, to: parsed.nest }; - }, + resolveTarget: ({ to }) => resolveTlonOutboundTarget(to), sendText: async (params) => await ( await loadTlonChannelRuntime() diff --git a/extensions/tlon/src/targets.test.ts b/extensions/tlon/src/targets.test.ts new file mode 100644 index 00000000000..3ac4d010f38 --- /dev/null +++ b/extensions/tlon/src/targets.test.ts @@ -0,0 +1,27 @@ +import { describe, expect, it } from "vitest"; +import { resolveTlonOutboundTarget } from "./targets.js"; + +describe("resolveTlonOutboundTarget", () => { + it("resolves dm targets to normalized ships", () => { + expect(resolveTlonOutboundTarget("dm/sampel-palnet")).toEqual({ + ok: true, + to: "~sampel-palnet", + }); + }); + + it("resolves group targets to canonical chat nests", () => { + expect(resolveTlonOutboundTarget("group:host-ship/general")).toEqual({ + ok: true, + to: "chat/~host-ship/general", + }); + }); + + it("returns a helpful error for invalid targets", () => { + const resolved = resolveTlonOutboundTarget("group:bad-target"); + expect(resolved.ok).toBe(false); + if (resolved.ok) { + throw new Error("expected invalid target"); + } + expect(resolved.error.message).toMatch(/invalid tlon target/i); + }); +}); diff --git a/extensions/tlon/src/targets.ts b/extensions/tlon/src/targets.ts index bacc6d576c0..b8aa17e5e8c 100644 --- a/extensions/tlon/src/targets.ts +++ b/extensions/tlon/src/targets.ts @@ -84,6 +84,20 @@ export function parseTlonTarget(raw?: string | null): TlonTarget | null { return null; } +export function resolveTlonOutboundTarget(to?: string | null) { + const parsed = parseTlonTarget(to ?? ""); + if (!parsed) { + return { + ok: false as const, + error: new Error(`Invalid Tlon target. Use ${formatTargetHint()}`), + }; + } + if (parsed.kind === "dm") { + return { ok: true as const, to: parsed.ship }; + } + return { ok: true as const, to: parsed.nest }; +} + export function formatTargetHint(): string { return "dm/~sampel-palnet | ~sampel-palnet | chat/~host-ship/channel | group:~host-ship/channel"; }