chore: Lint extensions folder.

This commit is contained in:
cpojer
2026-01-31 22:13:48 +09:00
parent 4f2166c503
commit 230ca789e2
221 changed files with 4006 additions and 1583 deletions

View File

@@ -1,4 +1,4 @@
import type { OpenClawConfig, OpenClawPluginApi } from "openclaw/plugin-sdk";
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
import { nostrPlugin } from "./src/channel.js";
@@ -20,13 +20,13 @@ const plugin = {
const httpHandler = createNostrProfileHttpHandler({
getConfigProfile: (accountId: string) => {
const runtime = getNostrRuntime();
const cfg = runtime.config.loadConfig() as OpenClawConfig;
const cfg = runtime.config.loadConfig();
const account = resolveNostrAccount({ cfg, accountId });
return account.profile;
},
updateConfigProfile: async (accountId: string, profile: NostrProfile) => {
const runtime = getNostrRuntime();
const cfg = runtime.config.loadConfig() as OpenClawConfig;
const cfg = runtime.config.loadConfig();
// Build the config patch for channels.nostr.profile
const channels = (cfg.channels ?? {}) as Record<string, unknown>;
@@ -49,7 +49,7 @@ const plugin = {
},
getAccountInfo: (accountId: string) => {
const runtime = getNostrRuntime();
const cfg = runtime.config.loadConfig() as OpenClawConfig;
const cfg = runtime.config.loadConfig();
const account = resolveNostrAccount({ cfg, accountId });
if (!account.configured || !account.publicKey) {
return null;

View File

@@ -61,14 +61,18 @@ describe("nostrPlugin", () => {
it("recognizes npub as valid target", () => {
const looksLikeId = nostrPlugin.messaging?.targetResolver?.looksLikeId;
if (!looksLikeId) return;
if (!looksLikeId) {
return;
}
expect(looksLikeId("npub1xyz123")).toBe(true);
});
it("recognizes hex pubkey as valid target", () => {
const looksLikeId = nostrPlugin.messaging?.targetResolver?.looksLikeId;
if (!looksLikeId) return;
if (!looksLikeId) {
return;
}
const hexPubkey = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
expect(looksLikeId(hexPubkey)).toBe(true);
@@ -76,7 +80,9 @@ describe("nostrPlugin", () => {
it("rejects invalid input", () => {
const looksLikeId = nostrPlugin.messaging?.targetResolver?.looksLikeId;
if (!looksLikeId) return;
if (!looksLikeId) {
return;
}
expect(looksLikeId("not-a-pubkey")).toBe(false);
expect(looksLikeId("")).toBe(false);
@@ -84,7 +90,9 @@ describe("nostrPlugin", () => {
it("normalizeTarget strips nostr: prefix", () => {
const normalize = nostrPlugin.messaging?.normalizeTarget;
if (!normalize) return;
if (!normalize) {
return;
}
const hexPubkey = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
expect(normalize(`nostr:${hexPubkey}`)).toBe(hexPubkey);
@@ -108,7 +116,9 @@ describe("nostrPlugin", () => {
it("normalizes nostr: prefix in allow entries", () => {
const normalize = nostrPlugin.pairing?.normalizeAllowEntry;
if (!normalize) return;
if (!normalize) {
return;
}
const hexPubkey = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
expect(normalize(`nostr:${hexPubkey}`)).toBe(hexPubkey);

View File

@@ -63,7 +63,9 @@ export const nostrPlugin: ChannelPlugin<ResolvedNostrAccount> = {
.map((entry) => String(entry).trim())
.filter(Boolean)
.map((entry) => {
if (entry === "*") return "*";
if (entry === "*") {
return "*";
}
try {
return normalizePubkey(entry);
} catch {
@@ -162,7 +164,9 @@ export const nostrPlugin: ChannelPlugin<ResolvedNostrAccount> = {
collectStatusIssues: (accounts) =>
accounts.flatMap((account) => {
const lastError = typeof account.lastError === "string" ? account.lastError.trim() : "";
if (!lastError) return [];
if (!lastError) {
return [];
}
return [
{
channel: "nostr",

View File

@@ -300,34 +300,54 @@ export function createMetrics(onMetric?: OnMetricCallback): NostrMetrics {
// Relay metrics
case "relay.connect":
if (relayUrl) getOrCreateRelay(relayUrl).connects += value;
if (relayUrl) {
getOrCreateRelay(relayUrl).connects += value;
}
break;
case "relay.disconnect":
if (relayUrl) getOrCreateRelay(relayUrl).disconnects += value;
if (relayUrl) {
getOrCreateRelay(relayUrl).disconnects += value;
}
break;
case "relay.reconnect":
if (relayUrl) getOrCreateRelay(relayUrl).reconnects += value;
if (relayUrl) {
getOrCreateRelay(relayUrl).reconnects += value;
}
break;
case "relay.error":
if (relayUrl) getOrCreateRelay(relayUrl).errors += value;
if (relayUrl) {
getOrCreateRelay(relayUrl).errors += value;
}
break;
case "relay.message.event":
if (relayUrl) getOrCreateRelay(relayUrl).messagesReceived.event += value;
if (relayUrl) {
getOrCreateRelay(relayUrl).messagesReceived.event += value;
}
break;
case "relay.message.eose":
if (relayUrl) getOrCreateRelay(relayUrl).messagesReceived.eose += value;
if (relayUrl) {
getOrCreateRelay(relayUrl).messagesReceived.eose += value;
}
break;
case "relay.message.closed":
if (relayUrl) getOrCreateRelay(relayUrl).messagesReceived.closed += value;
if (relayUrl) {
getOrCreateRelay(relayUrl).messagesReceived.closed += value;
}
break;
case "relay.message.notice":
if (relayUrl) getOrCreateRelay(relayUrl).messagesReceived.notice += value;
if (relayUrl) {
getOrCreateRelay(relayUrl).messagesReceived.notice += value;
}
break;
case "relay.message.ok":
if (relayUrl) getOrCreateRelay(relayUrl).messagesReceived.ok += value;
if (relayUrl) {
getOrCreateRelay(relayUrl).messagesReceived.ok += value;
}
break;
case "relay.message.auth":
if (relayUrl) getOrCreateRelay(relayUrl).messagesReceived.auth += value;
if (relayUrl) {
getOrCreateRelay(relayUrl).messagesReceived.auth += value;
}
break;
case "relay.circuit_breaker.open":
if (relayUrl) {

View File

@@ -1,4 +1,4 @@
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
import { describe, expect, it, vi } from "vitest";
import { createSeenTracker } from "./seen-tracker.js";
import { createMetrics, createNoopMetrics, type MetricEvent } from "./metrics.js";

View File

@@ -36,11 +36,6 @@ const STARTUP_LOOKBACK_SEC = 120; // tolerate relay lag / clock skew
const MAX_PERSISTED_EVENT_IDS = 5000;
const STATE_PERSIST_DEBOUNCE_MS = 5000; // Debounce state writes
// Reconnect configuration (exponential backoff with jitter)
const RECONNECT_BASE_MS = 1000; // 1 second base
const RECONNECT_MAX_MS = 60000; // 60 seconds max
const RECONNECT_JITTER = 0.3; // ±30% jitter
// Circuit breaker configuration
const CIRCUIT_BREAKER_THRESHOLD = 5; // failures before opening
const CIRCUIT_BREAKER_RESET_MS = 30000; // 30 seconds before half-open
@@ -137,7 +132,9 @@ function createCircuitBreaker(
return {
canAttempt(): boolean {
if (state.state === "closed") return true;
if (state.state === "closed") {
return true;
}
if (state.state === "open") {
// Check if enough time has passed to try half-open
@@ -243,10 +240,14 @@ function createRelayHealthTracker(): RelayHealthTracker {
getScore(relay: string): number {
const s = stats.get(relay);
if (!s) return 0.5; // Unknown relay gets neutral score
if (!s) {
return 0.5;
} // Unknown relay gets neutral score
const total = s.successCount + s.failureCount;
if (total === 0) return 0.5;
if (total === 0) {
return 0.5;
}
// Success rate (0-1)
const successRate = s.successCount / total;
@@ -266,25 +267,11 @@ function createRelayHealthTracker(): RelayHealthTracker {
},
getSortedRelays(relays: string[]): string[] {
return [...relays].sort((a, b) => this.getScore(b) - this.getScore(a));
return [...relays].toSorted((a, b) => this.getScore(b) - this.getScore(a));
},
};
}
// ============================================================================
// Reconnect with Exponential Backoff + Jitter
// ============================================================================
function computeReconnectDelay(attempt: number): number {
// Exponential backoff: base * 2^attempt
const exponential = RECONNECT_BASE_MS * Math.pow(2, attempt);
const capped = Math.min(exponential, RECONNECT_MAX_MS);
// Add jitter: ±JITTER%
const jitter = capped * RECONNECT_JITTER * (Math.random() * 2 - 1);
return Math.max(RECONNECT_BASE_MS, capped + jitter);
}
// ============================================================================
// Key Validation
// ============================================================================
@@ -397,7 +384,9 @@ export async function startNostrBus(options: NostrBusOptions): Promise<NostrBusH
recentEventIds = recentEventIds.slice(-MAX_PERSISTED_EVENT_IDS);
}
if (pendingWrite) clearTimeout(pendingWrite);
if (pendingWrite) {
clearTimeout(pendingWrite);
}
pendingWrite = setTimeout(() => {
writeNostrBusState({
accountId,
@@ -461,7 +450,7 @@ export async function startNostrBus(options: NostrBusOptions): Promise<NostrBusH
// Decrypt the message
let plaintext: string;
try {
plaintext = await decrypt(sk, event.pubkey, event.content);
plaintext = decrypt(sk, event.pubkey, event.content);
metrics.emit("decrypt.success");
} catch (err) {
metrics.emit("decrypt.failure");
@@ -515,7 +504,7 @@ export async function startNostrBus(options: NostrBusOptions): Promise<NostrBusH
metrics.emit("relay.message.closed", 1, { relay });
options.onDisconnect?.(relay);
}
onError?.(new Error(`Subscription closed: ${reason}`), "subscription");
onError?.(new Error(`Subscription closed: ${reason.join(", ")}`), "subscription");
},
});
@@ -614,7 +603,7 @@ async function sendEncryptedDm(
healthTracker: RelayHealthTracker,
onError?: (error: Error, context: string) => void,
): Promise<void> {
const ciphertext = await encrypt(sk, toPubkey, text);
const ciphertext = encrypt(sk, toPubkey, text);
const reply = finalizeEvent(
{
kind: 4,
@@ -640,6 +629,7 @@ async function sendEncryptedDm(
const startTime = Date.now();
try {
// oxlint-disable-next-line typescript/await-thenable typesciript/no-floating-promises
await pool.publish([relay], reply);
const latency = Date.now() - startTime;
@@ -672,7 +662,9 @@ async function sendEncryptedDm(
* Check if a string looks like a valid Nostr pubkey (hex or npub)
*/
export function isValidPubkey(input: string): boolean {
if (typeof input !== "string") return false;
if (typeof input !== "string") {
return false;
}
const trimmed = input.trim();
// npub format

View File

@@ -2,7 +2,7 @@
* Tests for Nostr Profile HTTP Handler
*/
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { describe, it, expect, vi, beforeEach } from "vitest";
import { IncomingMessage, ServerResponse } from "node:http";
import { Socket } from "node:net";
@@ -56,7 +56,6 @@ function createMockResponse(): ServerResponse & {
_getData: () => string;
_getStatusCode: () => number;
} {
const socket = new Socket();
const res = new ServerResponse({} as IncomingMessage);
let data = "";
@@ -68,7 +67,10 @@ function createMockResponse(): ServerResponse & {
};
res.end = function (chunk?: unknown) {
if (chunk) data += String(chunk);
if (chunk) {
// eslint-disable-next-line @typescript-eslint/no-base-to-string
data += String(chunk);
}
return this;
};

View File

@@ -113,33 +113,53 @@ function isPrivateIp(ip: string): boolean {
// Handle IPv4
const ipv4Match = ip.match(/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/);
if (ipv4Match) {
const [, a, b, c] = ipv4Match.map(Number);
const [, a, b] = ipv4Match.map(Number);
// 127.0.0.0/8 (loopback)
if (a === 127) return true;
if (a === 127) {
return true;
}
// 10.0.0.0/8 (private)
if (a === 10) return true;
if (a === 10) {
return true;
}
// 172.16.0.0/12 (private)
if (a === 172 && b >= 16 && b <= 31) return true;
if (a === 172 && b >= 16 && b <= 31) {
return true;
}
// 192.168.0.0/16 (private)
if (a === 192 && b === 168) return true;
if (a === 192 && b === 168) {
return true;
}
// 169.254.0.0/16 (link-local)
if (a === 169 && b === 254) return true;
if (a === 169 && b === 254) {
return true;
}
// 0.0.0.0/8
if (a === 0) return true;
if (a === 0) {
return true;
}
return false;
}
// Handle IPv6
const ipLower = ip.toLowerCase().replace(/^\[|\]$/g, "");
// ::1 (loopback)
if (ipLower === "::1") return true;
if (ipLower === "::1") {
return true;
}
// fe80::/10 (link-local)
if (ipLower.startsWith("fe80:")) return true;
if (ipLower.startsWith("fe80:")) {
return true;
}
// fc00::/7 (unique local)
if (ipLower.startsWith("fc") || ipLower.startsWith("fd")) return true;
if (ipLower.startsWith("fc") || ipLower.startsWith("fd")) {
return true;
}
// ::ffff:x.x.x.x (IPv4-mapped IPv6) - extract and check IPv4
const v4Mapped = ipLower.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/);
if (v4Mapped) return isPrivateIp(v4Mapped[1]);
if (v4Mapped) {
return isPrivateIp(v4Mapped[1]);
}
return false;
}

View File

@@ -2,9 +2,9 @@
* Tests for Nostr Profile Import
*/
import { describe, it, expect, vi, beforeEach } from "vitest";
import { describe, it, expect } from "vitest";
import { mergeProfiles, type ProfileImportOptions } from "./nostr-profile-import.js";
import { mergeProfiles } from "./nostr-profile-import.js";
import type { NostrProfile } from "./config-schema.js";
// Note: importProfileFromRelays requires real network calls or complex mocking

View File

@@ -243,8 +243,12 @@ export function mergeProfiles(
local: NostrProfile | undefined,
imported: NostrProfile | undefined,
): NostrProfile {
if (!imported) return local ?? {};
if (!local) return imported;
if (!imported) {
return local ?? {};
}
if (!local) {
return imported;
}
return {
name: local.name ?? imported.name,

View File

@@ -1,5 +1,4 @@
import { describe, expect, it } from "vitest";
import { getPublicKey } from "nostr-tools";
import {
createProfileEvent,
profileToContent,
@@ -422,7 +421,7 @@ describe("profile type confusion", () => {
it("handles prototype pollution attempt", () => {
const malicious = JSON.parse('{"__proto__": {"polluted": true}}') as unknown;
const result = validateProfile(malicious);
validateProfile(malicious);
// Should not pollute Object.prototype
expect(({} as Record<string, unknown>).polluted).toBeUndefined();
});

View File

@@ -49,14 +49,30 @@ export function profileToContent(profile: NostrProfile): ProfileContent {
const content: ProfileContent = {};
if (validated.name !== undefined) content.name = validated.name;
if (validated.displayName !== undefined) content.display_name = validated.displayName;
if (validated.about !== undefined) content.about = validated.about;
if (validated.picture !== undefined) content.picture = validated.picture;
if (validated.banner !== undefined) content.banner = validated.banner;
if (validated.website !== undefined) content.website = validated.website;
if (validated.nip05 !== undefined) content.nip05 = validated.nip05;
if (validated.lud16 !== undefined) content.lud16 = validated.lud16;
if (validated.name !== undefined) {
content.name = validated.name;
}
if (validated.displayName !== undefined) {
content.display_name = validated.displayName;
}
if (validated.about !== undefined) {
content.about = validated.about;
}
if (validated.picture !== undefined) {
content.picture = validated.picture;
}
if (validated.banner !== undefined) {
content.banner = validated.banner;
}
if (validated.website !== undefined) {
content.website = validated.website;
}
if (validated.nip05 !== undefined) {
content.nip05 = validated.nip05;
}
if (validated.lud16 !== undefined) {
content.lud16 = validated.lud16;
}
return content;
}
@@ -68,14 +84,30 @@ export function profileToContent(profile: NostrProfile): ProfileContent {
export function contentToProfile(content: ProfileContent): NostrProfile {
const profile: NostrProfile = {};
if (content.name !== undefined) profile.name = content.name;
if (content.display_name !== undefined) profile.displayName = content.display_name;
if (content.about !== undefined) profile.about = content.about;
if (content.picture !== undefined) profile.picture = content.picture;
if (content.banner !== undefined) profile.banner = content.banner;
if (content.website !== undefined) profile.website = content.website;
if (content.nip05 !== undefined) profile.nip05 = content.nip05;
if (content.lud16 !== undefined) profile.lud16 = content.lud16;
if (content.name !== undefined) {
profile.name = content.name;
}
if (content.display_name !== undefined) {
profile.displayName = content.display_name;
}
if (content.about !== undefined) {
profile.about = content.about;
}
if (content.picture !== undefined) {
profile.picture = content.picture;
}
if (content.banner !== undefined) {
profile.banner = content.banner;
}
if (content.website !== undefined) {
profile.website = content.website;
}
if (content.nip05 !== undefined) {
profile.nip05 = content.nip05;
}
if (content.lud16 !== undefined) {
profile.lud16 = content.lud16;
}
return profile;
}
@@ -150,6 +182,7 @@ export async function publishProfileEvent(
setTimeout(() => reject(new Error("timeout")), RELAY_PUBLISH_TIMEOUT_MS);
});
// oxlint-disable-next-line typescript/no-floating-promises
await Promise.race([pool.publish([relay], event), timeoutPromise]);
successes.push(relay);
@@ -220,7 +253,9 @@ export function validateProfile(profile: unknown): {
*/
export function sanitizeProfileForDisplay(profile: NostrProfile): NostrProfile {
const escapeHtml = (str: string | undefined): string | undefined => {
if (str === undefined) return undefined;
if (str === undefined) {
return undefined;
}
return str
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")

View File

@@ -20,7 +20,9 @@ async function withTempStateDir<T>(fn: (dir: string) => Promise<T>) {
state: {
resolveStateDir: (env, homedir) => {
const override = env.OPENCLAW_STATE_DIR?.trim() || env.OPENCLAW_STATE_DIR?.trim();
if (override) return override;
if (override) {
return override;
}
return path.join(homedir(), ".openclaw");
},
},
@@ -28,8 +30,11 @@ async function withTempStateDir<T>(fn: (dir: string) => Promise<T>) {
try {
return await fn(dir);
} finally {
if (previous === undefined) delete process.env.OPENCLAW_STATE_DIR;
else process.env.OPENCLAW_STATE_DIR = previous;
if (previous === undefined) {
delete process.env.OPENCLAW_STATE_DIR;
} else {
process.env.OPENCLAW_STATE_DIR = previous;
}
await fs.rm(dir, { recursive: true, force: true });
}
}

View File

@@ -39,7 +39,9 @@ export type NostrProfileState = {
function normalizeAccountId(accountId?: string): string {
const trimmed = accountId?.trim();
if (!trimmed) return "default";
if (!trimmed) {
return "default";
}
return trimmed.replace(/[^a-z0-9._-]+/gi, "_");
}
@@ -101,7 +103,9 @@ export async function readNostrBusState(params: {
return safeParseState(raw);
} catch (err) {
const code = (err as { code?: string }).code;
if (code === "ENOENT") return null;
if (code === "ENOENT") {
return null;
}
return null;
}
}
@@ -139,14 +143,18 @@ export function computeSinceTimestamp(
state: NostrBusState | null,
nowSec: number = Math.floor(Date.now() / 1000),
): number {
if (!state) return nowSec;
if (!state) {
return nowSec;
}
// Use the most recent timestamp we have
const candidates = [state.lastProcessedAt, state.gatewayStartedAt].filter(
(t): t is number => t !== null && t > 0,
);
if (candidates.length === 0) return nowSec;
if (candidates.length === 0) {
return nowSec;
}
return Math.max(...candidates);
}
@@ -166,7 +174,7 @@ function safeParseProfileState(raw: string): NostrProfileState | null {
typeof parsed.lastPublishedEventId === "string" ? parsed.lastPublishedEventId : null,
lastPublishResults:
parsed.lastPublishResults && typeof parsed.lastPublishResults === "object"
? (parsed.lastPublishResults as Record<string, "ok" | "failed" | "timeout">)
? parsed.lastPublishResults
: null,
};
}
@@ -187,7 +195,9 @@ export async function readNostrProfileState(params: {
return safeParseProfileState(raw);
} catch (err) {
const code = (err as { code?: string }).code;
if (code === "ENOENT") return null;
if (code === "ENOENT") {
return null;
}
return null;
}
}

View File

@@ -56,19 +56,27 @@ export function createSeenTracker(options?: SeenTrackerOptions): SeenTracker {
// Move an entry to the front (most recently used)
function moveToFront(id: string): void {
const entry = entries.get(id);
if (!entry) return;
if (!entry) {
return;
}
// Already at front
if (head === id) return;
if (head === id) {
return;
}
// Remove from current position
if (entry.prev) {
const prevEntry = entries.get(entry.prev);
if (prevEntry) prevEntry.next = entry.next;
if (prevEntry) {
prevEntry.next = entry.next;
}
}
if (entry.next) {
const nextEntry = entries.get(entry.next);
if (nextEntry) nextEntry.prev = entry.prev;
if (nextEntry) {
nextEntry.prev = entry.prev;
}
}
// Update tail if this was the tail
@@ -81,29 +89,39 @@ export function createSeenTracker(options?: SeenTrackerOptions): SeenTracker {
entry.next = head;
if (head) {
const headEntry = entries.get(head);
if (headEntry) headEntry.prev = id;
if (headEntry) {
headEntry.prev = id;
}
}
head = id;
// If no tail, this is also the tail
if (!tail) tail = id;
if (!tail) {
tail = id;
}
}
// Remove an entry from the linked list
function removeFromList(id: string): void {
const entry = entries.get(id);
if (!entry) return;
if (!entry) {
return;
}
if (entry.prev) {
const prevEntry = entries.get(entry.prev);
if (prevEntry) prevEntry.next = entry.next;
if (prevEntry) {
prevEntry.next = entry.next;
}
} else {
head = entry.next;
}
if (entry.next) {
const nextEntry = entries.get(entry.next);
if (nextEntry) nextEntry.prev = entry.prev;
if (nextEntry) {
nextEntry.prev = entry.prev;
}
} else {
tail = entry.prev;
}
@@ -111,7 +129,9 @@ export function createSeenTracker(options?: SeenTrackerOptions): SeenTracker {
// Evict the least recently used entry
function evictLRU(): void {
if (!tail) return;
if (!tail) {
return;
}
const idToEvict = tail;
removeFromList(idToEvict);
entries.delete(idToEvict);
@@ -139,7 +159,9 @@ export function createSeenTracker(options?: SeenTrackerOptions): SeenTracker {
if (pruneIntervalMs > 0) {
pruneTimer = setInterval(pruneExpired, pruneIntervalMs);
// Don't keep process alive just for pruning
if (pruneTimer.unref) pruneTimer.unref();
if (pruneTimer.unref) {
pruneTimer.unref();
}
}
function add(id: string): void {
@@ -167,12 +189,16 @@ export function createSeenTracker(options?: SeenTrackerOptions): SeenTracker {
if (head) {
const headEntry = entries.get(head);
if (headEntry) headEntry.prev = id;
if (headEntry) {
headEntry.prev = id;
}
}
entries.set(id, newEntry);
head = id;
if (!tail) tail = id;
if (!tail) {
tail = id;
}
}
function has(id: string): boolean {
@@ -198,7 +224,9 @@ export function createSeenTracker(options?: SeenTrackerOptions): SeenTracker {
function peek(id: string): boolean {
const entry = entries.get(id);
if (!entry) return false;
if (!entry) {
return false;
}
// Check if expired
if (Date.now() - entry.seenAt > ttlMs) {
@@ -248,12 +276,16 @@ export function createSeenTracker(options?: SeenTrackerOptions): SeenTracker {
if (head) {
const headEntry = entries.get(head);
if (headEntry) headEntry.prev = id;
if (headEntry) {
headEntry.prev = id;
}
}
entries.set(id, newEntry);
head = id;
if (!tail) tail = id;
if (!tail) {
tail = id;
}
}
}
}

View File

@@ -48,7 +48,9 @@ export function listNostrAccountIds(cfg: OpenClawConfig): string[] {
*/
export function resolveDefaultNostrAccountId(cfg: OpenClawConfig): string {
const ids = listNostrAccountIds(cfg);
if (ids.includes(DEFAULT_ACCOUNT_ID)) return DEFAULT_ACCOUNT_ID;
if (ids.includes(DEFAULT_ACCOUNT_ID)) {
return DEFAULT_ACCOUNT_ID;
}
return ids[0] ?? DEFAULT_ACCOUNT_ID;
}