refactor(tlon): share outbound target resolution

This commit is contained in:
Peter Steinberger 2026-03-17 04:21:19 +00:00
parent ed06d21013
commit 966b8656d2
4 changed files with 55 additions and 28 deletions

View File

@ -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<T>(
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) => {

View File

@ -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()

View File

@ -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);
});
});

View File

@ -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";
}