Files
openclaw/extensions/tlon/src/targets.ts
max 223eee0a20 refactor: unify peer kind to ChatType, rename dm to direct (#11881)
* fix: use .js extension for ESM imports of RoutePeerKind

The imports incorrectly used .ts extension which doesn't resolve
with moduleResolution: NodeNext. Changed to .js and added 'type'
import modifier.

* fix tsconfig

* refactor: unify peer kind to ChatType, rename dm to direct

- Replace RoutePeerKind with ChatType throughout codebase
- Change 'dm' literal values to 'direct' in routing/session keys
- Keep backward compat: normalizeChatType accepts 'dm' -> 'direct'
- Add ChatType export to plugin-sdk, deprecate RoutePeerKind
- Update session key parsing to accept both 'dm' and 'direct' markers
- Update all channel monitors and extensions to use ChatType

BREAKING CHANGE: Session keys now use 'direct' instead of 'dm'.
Existing 'dm' keys still work via backward compat layer.

* fix tests

* test: update session key expectations for dmdirect migration

- Fix test expectations to expect :direct: in generated output
- Add explicit backward compat test for normalizeChatType('dm')
- Keep input test data with :dm: keys to verify backward compat

* fix: accept legacy 'dm' in session key parsing for backward compat

getDmHistoryLimitFromSessionKey now accepts both :dm: and :direct:
to ensure old session keys continue to work correctly.

* test: add explicit backward compat tests for dmdirect migration

- session-key.test.ts: verify both :dm: and :direct: keys are valid
- getDmHistoryLimitFromSessionKey: verify both formats work

* feat: backward compat for resetByType.dm config key

* test: skip unix-path Nix tests on Windows
2026-02-09 09:20:52 +09:00

90 lines
2.4 KiB
TypeScript

export type TlonTarget =
| { kind: "direct"; ship: string }
| { kind: "group"; nest: string; hostShip: string; channelName: string };
const SHIP_RE = /^~?[a-z-]+$/i;
const NEST_RE = /^chat\/([^/]+)\/([^/]+)$/i;
export function normalizeShip(raw: string): string {
const trimmed = raw.trim();
if (!trimmed) {
return trimmed;
}
return trimmed.startsWith("~") ? trimmed : `~${trimmed}`;
}
export function parseChannelNest(raw: string): { hostShip: string; channelName: string } | null {
const match = NEST_RE.exec(raw.trim());
if (!match) {
return null;
}
const hostShip = normalizeShip(match[1]);
const channelName = match[2];
return { hostShip, channelName };
}
export function parseTlonTarget(raw?: string | null): TlonTarget | null {
const trimmed = raw?.trim();
if (!trimmed) {
return null;
}
const withoutPrefix = trimmed.replace(/^tlon:/i, "");
const dmPrefix = withoutPrefix.match(/^dm[/:](.+)$/i);
if (dmPrefix) {
return { kind: "direct", ship: normalizeShip(dmPrefix[1]) };
}
const groupPrefix = withoutPrefix.match(/^(group|room)[/:](.+)$/i);
if (groupPrefix) {
const groupTarget = groupPrefix[2].trim();
if (groupTarget.startsWith("chat/")) {
const parsed = parseChannelNest(groupTarget);
if (!parsed) {
return null;
}
return {
kind: "group",
nest: `chat/${parsed.hostShip}/${parsed.channelName}`,
hostShip: parsed.hostShip,
channelName: parsed.channelName,
};
}
const parts = groupTarget.split("/");
if (parts.length === 2) {
const hostShip = normalizeShip(parts[0]);
const channelName = parts[1];
return {
kind: "group",
nest: `chat/${hostShip}/${channelName}`,
hostShip,
channelName,
};
}
return null;
}
if (withoutPrefix.startsWith("chat/")) {
const parsed = parseChannelNest(withoutPrefix);
if (!parsed) {
return null;
}
return {
kind: "group",
nest: `chat/${parsed.hostShip}/${parsed.channelName}`,
hostShip: parsed.hostShip,
channelName: parsed.channelName,
};
}
if (SHIP_RE.test(withoutPrefix)) {
return { kind: "direct", ship: normalizeShip(withoutPrefix) };
}
return null;
}
export function formatTargetHint(): string {
return "dm/~sampel-palnet | ~sampel-palnet | chat/~host-ship/channel | group:~host-ship/channel";
}