@kodelyth/tlon 2026.5.42 → 2026.6.1

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.
Files changed (63) hide show
  1. package/klaw.plugin.json +203 -3
  2. package/package.json +17 -4
  3. package/api.ts +0 -16
  4. package/channel-plugin-api.ts +0 -1
  5. package/doctor-contract-api.ts +0 -1
  6. package/index.ts +0 -16
  7. package/runtime-api.ts +0 -17
  8. package/setup-api.ts +0 -2
  9. package/setup-entry.ts +0 -9
  10. package/src/account-fields.ts +0 -31
  11. package/src/channel.message-adapter.test.ts +0 -145
  12. package/src/channel.runtime.ts +0 -259
  13. package/src/channel.ts +0 -192
  14. package/src/config-schema.ts +0 -54
  15. package/src/core.test.ts +0 -298
  16. package/src/doctor-contract.ts +0 -9
  17. package/src/doctor.test.ts +0 -46
  18. package/src/doctor.ts +0 -10
  19. package/src/logger-runtime.ts +0 -1
  20. package/src/monitor/approval-runtime.ts +0 -363
  21. package/src/monitor/approval.test.ts +0 -33
  22. package/src/monitor/approval.ts +0 -283
  23. package/src/monitor/authorization.ts +0 -30
  24. package/src/monitor/cites.ts +0 -54
  25. package/src/monitor/discovery.ts +0 -68
  26. package/src/monitor/history.ts +0 -226
  27. package/src/monitor/index.ts +0 -1523
  28. package/src/monitor/media.test.ts +0 -80
  29. package/src/monitor/media.ts +0 -156
  30. package/src/monitor/processed-messages.test.ts +0 -58
  31. package/src/monitor/processed-messages.ts +0 -89
  32. package/src/monitor/settings-helpers.test.ts +0 -113
  33. package/src/monitor/settings-helpers.ts +0 -158
  34. package/src/monitor/utils.ts +0 -402
  35. package/src/runtime.ts +0 -9
  36. package/src/security.test.ts +0 -658
  37. package/src/session-route.ts +0 -40
  38. package/src/settings.ts +0 -391
  39. package/src/setup-core.ts +0 -231
  40. package/src/setup-surface.ts +0 -99
  41. package/src/targets.ts +0 -102
  42. package/src/tlon-api.test.ts +0 -572
  43. package/src/tlon-api.ts +0 -389
  44. package/src/types.ts +0 -160
  45. package/src/urbit/auth.ssrf.test.ts +0 -45
  46. package/src/urbit/auth.ts +0 -48
  47. package/src/urbit/base-url.test.ts +0 -48
  48. package/src/urbit/base-url.ts +0 -61
  49. package/src/urbit/channel-ops.test.ts +0 -36
  50. package/src/urbit/channel-ops.ts +0 -149
  51. package/src/urbit/context.ts +0 -50
  52. package/src/urbit/errors.ts +0 -51
  53. package/src/urbit/fetch.ts +0 -38
  54. package/src/urbit/foreigns.ts +0 -49
  55. package/src/urbit/send.test.ts +0 -83
  56. package/src/urbit/send.ts +0 -228
  57. package/src/urbit/sse-client.test.ts +0 -234
  58. package/src/urbit/sse-client.ts +0 -492
  59. package/src/urbit/story.ts +0 -332
  60. package/src/urbit/upload.test.ts +0 -155
  61. package/src/urbit/upload.ts +0 -60
  62. package/test-api.ts +0 -1
  63. package/tsconfig.json +0 -16
@@ -1,80 +0,0 @@
1
- import {
2
- readRemoteMediaBuffer,
3
- MAX_IMAGE_BYTES,
4
- saveRemoteMedia,
5
- } from "klaw/plugin-sdk/media-runtime";
6
- import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
7
- import { downloadMedia, extractImageBlocks } from "./media.js";
8
-
9
- vi.mock("klaw/plugin-sdk/media-runtime", () => ({
10
- MAX_IMAGE_BYTES: 6 * 1024 * 1024,
11
- readRemoteMediaBuffer: vi.fn(),
12
- saveRemoteMedia: vi.fn(),
13
- }));
14
-
15
- const readRemoteMediaBufferMock = vi.mocked(readRemoteMediaBuffer);
16
- const saveRemoteMediaMock = vi.mocked(saveRemoteMedia);
17
-
18
- describe("tlon monitor media", () => {
19
- beforeEach(() => {
20
- vi.clearAllMocks();
21
- vi.spyOn(console, "error").mockImplementation(() => undefined);
22
- vi.spyOn(console, "warn").mockImplementation(() => undefined);
23
- });
24
-
25
- afterEach(() => {
26
- vi.restoreAllMocks();
27
- });
28
-
29
- it("caps extracted images at eight per message", () => {
30
- const content = Array.from({ length: 10 }, (_, index) => ({
31
- block: { image: { src: `https://example.com/${index}.png`, alt: `image-${index}` } },
32
- }));
33
-
34
- const images = extractImageBlocks(content);
35
-
36
- expect(images).toHaveLength(8);
37
- expect(images.map((image) => image.url)).toEqual(
38
- Array.from({ length: 8 }, (_, index) => `https://example.com/${index}.png`),
39
- );
40
- });
41
-
42
- it("stores fetched media through the shared inbound media store with the image cap", async () => {
43
- saveRemoteMediaMock.mockResolvedValue({
44
- id: "photo---uuid.png",
45
- path: "/tmp/klaw/media/inbound/photo---uuid.png",
46
- size: "image-data".length,
47
- contentType: "image/png",
48
- });
49
-
50
- const result = await downloadMedia("https://example.com/photo.png");
51
-
52
- expect(readRemoteMediaBufferMock).not.toHaveBeenCalled();
53
- expect(saveRemoteMediaMock).toHaveBeenCalledTimes(1);
54
- expect(saveRemoteMediaMock).toHaveBeenCalledWith({
55
- url: "https://example.com/photo.png",
56
- maxBytes: MAX_IMAGE_BYTES,
57
- readIdleTimeoutMs: 30_000,
58
- ssrfPolicy: undefined,
59
- requestInit: { method: "GET" },
60
- });
61
- expect(result).toEqual({
62
- localPath: "/tmp/klaw/media/inbound/photo---uuid.png",
63
- contentType: "image/png",
64
- originalUrl: "https://example.com/photo.png",
65
- });
66
- });
67
-
68
- it("returns null when the fetch exceeds the image cap", async () => {
69
- saveRemoteMediaMock.mockRejectedValue(
70
- new Error(
71
- `Failed to fetch media from https://example.com/photo.png: payload exceeds maxBytes ${MAX_IMAGE_BYTES}`,
72
- ),
73
- );
74
-
75
- const result = await downloadMedia("https://example.com/photo.png");
76
-
77
- expect(result).toBeNull();
78
- expect(readRemoteMediaBufferMock).not.toHaveBeenCalled();
79
- });
80
- });
@@ -1,156 +0,0 @@
1
- import { randomUUID } from "node:crypto";
2
- import { mkdir, writeFile } from "node:fs/promises";
3
- import * as path from "node:path";
4
- import { formatErrorMessage } from "klaw/plugin-sdk/error-runtime";
5
- import { extensionForMime } from "klaw/plugin-sdk/media-mime";
6
- import {
7
- readRemoteMediaBuffer,
8
- MAX_IMAGE_BYTES,
9
- saveRemoteMedia,
10
- } from "klaw/plugin-sdk/media-runtime";
11
- import { normalizeLowercaseStringOrEmpty } from "klaw/plugin-sdk/string-coerce-runtime";
12
- import { getDefaultSsrFPolicy } from "../urbit/context.js";
13
-
14
- const MAX_IMAGES_PER_MESSAGE = 8;
15
- const TLON_MEDIA_DOWNLOAD_IDLE_TIMEOUT_MS = 30_000;
16
-
17
- interface ExtractedImage {
18
- url: string;
19
- alt?: string;
20
- }
21
-
22
- interface DownloadedMedia {
23
- localPath: string;
24
- contentType: string;
25
- originalUrl: string;
26
- }
27
-
28
- /**
29
- * Extract image blocks from Tlon message content.
30
- * Returns array of image URLs found in the message.
31
- */
32
- export function extractImageBlocks(content: unknown): ExtractedImage[] {
33
- if (!content || !Array.isArray(content)) {
34
- return [];
35
- }
36
-
37
- const images: ExtractedImage[] = [];
38
-
39
- for (const verse of content) {
40
- if (verse?.block?.image?.src) {
41
- images.push({
42
- url: verse.block.image.src,
43
- alt: verse.block.image.alt,
44
- });
45
- if (images.length >= MAX_IMAGES_PER_MESSAGE) {
46
- break;
47
- }
48
- }
49
- }
50
-
51
- return images;
52
- }
53
-
54
- /**
55
- * Download a media file from URL to local storage.
56
- * Returns the local path where the file was saved.
57
- */
58
- export async function downloadMedia(
59
- url: string,
60
- mediaDir?: string,
61
- ): Promise<DownloadedMedia | null> {
62
- try {
63
- // Validate URL is http/https before fetching
64
- const parsedUrl = new URL(url);
65
- if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") {
66
- console.warn(`[tlon-media] Rejected non-http(s) URL: ${url}`);
67
- return null;
68
- }
69
-
70
- const fetchOptions = {
71
- url,
72
- maxBytes: MAX_IMAGE_BYTES,
73
- readIdleTimeoutMs: TLON_MEDIA_DOWNLOAD_IDLE_TIMEOUT_MS,
74
- ssrfPolicy: getDefaultSsrFPolicy(),
75
- requestInit: { method: "GET" },
76
- };
77
-
78
- if (!mediaDir) {
79
- const saved = await saveRemoteMedia(fetchOptions);
80
- return {
81
- localPath: saved.path,
82
- contentType: saved.contentType ?? "application/octet-stream",
83
- originalUrl: url,
84
- };
85
- }
86
-
87
- const fetched = await readRemoteMediaBuffer(fetchOptions);
88
- await mkdir(mediaDir, { recursive: true });
89
- const ext =
90
- getExtensionFromFileName(fetched.fileName) ||
91
- getExtensionFromContentType(fetched.contentType ?? "") ||
92
- getExtensionFromUrl(url) ||
93
- "bin";
94
- const localPath = path.join(mediaDir, `${randomUUID()}.${ext}`);
95
- await writeFile(localPath, fetched.buffer);
96
-
97
- return {
98
- localPath,
99
- contentType: fetched.contentType ?? "application/octet-stream",
100
- originalUrl: url,
101
- };
102
- } catch (error: unknown) {
103
- console.error(`[tlon-media] Error downloading ${url}: ${formatErrorMessage(error)}`);
104
- return null;
105
- }
106
- }
107
-
108
- function getExtensionFromFileName(fileName?: string): string | null {
109
- if (!fileName) {
110
- return null;
111
- }
112
- const ext = path.extname(fileName).replace(/^\./, "");
113
- return ext || null;
114
- }
115
-
116
- function getExtensionFromContentType(contentType: string): string | null {
117
- return extensionForMime(contentType)?.replace(/^\./u, "") ?? null;
118
- }
119
-
120
- function getExtensionFromUrl(url: string): string | null {
121
- try {
122
- const pathname = new URL(url).pathname;
123
- const match = pathname.match(/\.([a-z0-9]+)$/i);
124
- return match ? normalizeLowercaseStringOrEmpty(match[1]) : null;
125
- } catch {
126
- return null;
127
- }
128
- }
129
-
130
- /**
131
- * Download all images from a message and return attachment metadata.
132
- * Format matches Klaw's expected attachment structure.
133
- */
134
- export async function downloadMessageImages(
135
- content: unknown,
136
- mediaDir?: string,
137
- ): Promise<Array<{ path: string; contentType: string }>> {
138
- const images = extractImageBlocks(content);
139
- if (images.length === 0) {
140
- return [];
141
- }
142
-
143
- const attachments: Array<{ path: string; contentType: string }> = [];
144
-
145
- for (const image of images) {
146
- const downloaded = await downloadMedia(image.url, mediaDir);
147
- if (downloaded) {
148
- attachments.push({
149
- path: downloaded.localPath,
150
- contentType: downloaded.contentType,
151
- });
152
- }
153
- }
154
-
155
- return attachments;
156
- }
@@ -1,58 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import {
3
- createProcessedMessageTracker,
4
- runWithProcessedMessageClaim,
5
- } from "./processed-messages.js";
6
-
7
- describe("createProcessedMessageTracker", () => {
8
- it("dedupes and evicts oldest entries", () => {
9
- const tracker = createProcessedMessageTracker(3);
10
-
11
- expect(tracker.mark("a")).toBe(true);
12
- expect(tracker.mark("a")).toBe(false);
13
- expect(tracker.has("a")).toBe(true);
14
-
15
- tracker.mark("b");
16
- tracker.mark("c");
17
- expect(tracker.size()).toBe(3);
18
-
19
- tracker.mark("d");
20
- expect(tracker.size()).toBe(3);
21
- expect(tracker.has("a")).toBe(false);
22
- expect(tracker.has("b")).toBe(true);
23
- expect(tracker.has("c")).toBe(true);
24
- expect(tracker.has("d")).toBe(true);
25
- });
26
-
27
- it("releases failed claims so retries can run again", async () => {
28
- const tracker = createProcessedMessageTracker();
29
-
30
- await expect(
31
- runWithProcessedMessageClaim({
32
- tracker,
33
- id: "evt-1",
34
- task: async () => {
35
- throw new Error("boom");
36
- },
37
- }),
38
- ).rejects.toThrow("boom");
39
-
40
- expect(tracker.has("evt-1")).toBe(false);
41
- expect(tracker.claim("evt-1")).toEqual({ kind: "claimed" });
42
- });
43
-
44
- it("keeps successful claims deduped", async () => {
45
- const tracker = createProcessedMessageTracker();
46
-
47
- await expect(
48
- runWithProcessedMessageClaim({
49
- tracker,
50
- id: "evt-2",
51
- task: async () => undefined,
52
- }),
53
- ).resolves.toEqual({ kind: "processed", value: undefined });
54
-
55
- expect(tracker.has("evt-2")).toBe(true);
56
- expect(tracker.claim("evt-2")).toEqual({ kind: "duplicate" });
57
- });
58
- });
@@ -1,89 +0,0 @@
1
- import { createDedupeCache } from "../../runtime-api.js";
2
-
3
- type ProcessedMessageTracker = {
4
- claim: (id?: string | null) => { kind: "claimed" } | { kind: "duplicate" };
5
- commit: (id?: string | null) => void;
6
- release: (id?: string | null) => void;
7
- mark: (id?: string | null) => boolean;
8
- has: (id?: string | null) => boolean;
9
- size: () => number;
10
- };
11
-
12
- export function createProcessedMessageTracker(limit = 2000): ProcessedMessageTracker {
13
- const dedupe = createDedupeCache({ ttlMs: 0, maxSize: limit });
14
- const inFlight = new Set<string>();
15
-
16
- const claim = (id?: string | null) => {
17
- const trimmed = id?.trim();
18
- if (!trimmed) {
19
- return { kind: "claimed" } as const;
20
- }
21
- if (inFlight.has(trimmed) || dedupe.peek(trimmed)) {
22
- return { kind: "duplicate" } as const;
23
- }
24
- inFlight.add(trimmed);
25
- return { kind: "claimed" } as const;
26
- };
27
-
28
- const commit = (id?: string | null) => {
29
- const trimmed = id?.trim();
30
- if (!trimmed) {
31
- return;
32
- }
33
- inFlight.delete(trimmed);
34
- dedupe.check(trimmed);
35
- };
36
-
37
- const release = (id?: string | null) => {
38
- const trimmed = id?.trim();
39
- if (!trimmed) {
40
- return;
41
- }
42
- inFlight.delete(trimmed);
43
- };
44
-
45
- const mark = (id?: string | null) => {
46
- const claimed = claim(id);
47
- if (claimed.kind === "duplicate") {
48
- return false;
49
- }
50
- commit(id);
51
- return true;
52
- };
53
-
54
- const has = (id?: string | null) => {
55
- const trimmed = id?.trim();
56
- if (!trimmed) {
57
- return false;
58
- }
59
- return dedupe.peek(trimmed);
60
- };
61
-
62
- return {
63
- claim,
64
- commit,
65
- release,
66
- mark,
67
- has,
68
- size: () => dedupe.size(),
69
- };
70
- }
71
-
72
- export async function runWithProcessedMessageClaim<T>(params: {
73
- tracker: ProcessedMessageTracker;
74
- id?: string | null;
75
- task: () => Promise<T>;
76
- }): Promise<{ kind: "processed"; value: T } | { kind: "duplicate" }> {
77
- const claim = params.tracker.claim(params.id);
78
- if (claim.kind === "duplicate") {
79
- return claim;
80
- }
81
- try {
82
- const value = await params.task();
83
- params.tracker.commit(params.id);
84
- return { kind: "processed", value };
85
- } catch (error) {
86
- params.tracker.release(params.id);
87
- throw error;
88
- }
89
- }
@@ -1,113 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import type { TlonResolvedAccount } from "../types.js";
3
- import {
4
- applyTlonSettingsOverrides,
5
- buildTlonSettingsMigrations,
6
- shouldMigrateTlonSetting,
7
- } from "./settings-helpers.js";
8
-
9
- const baseAccount: TlonResolvedAccount = {
10
- accountId: "default",
11
- name: "Tlon",
12
- enabled: true,
13
- configured: true,
14
- ship: "~sampel-palnet",
15
- url: "https://example.com",
16
- code: "lidlut-tabwed-pillex-ridrup",
17
- dangerouslyAllowPrivateNetwork: false,
18
- groupChannels: ["chat/~host/general"],
19
- dmAllowlist: ["~zod"],
20
- groupInviteAllowlist: ["~bus"],
21
- autoDiscoverChannels: true,
22
- showModelSignature: false,
23
- autoAcceptDmInvites: true,
24
- autoAcceptGroupInvites: true,
25
- defaultAuthorizedShips: ["~nec"],
26
- ownerShip: "~marzod",
27
- };
28
-
29
- function allowlistMigrationDecisions(currentSettings: Record<string, unknown>) {
30
- const allowlistKeys = new Set(["dmAllowlist", "groupInviteAllowlist", "defaultAuthorizedShips"]);
31
- return Object.fromEntries(
32
- buildTlonSettingsMigrations(baseAccount, currentSettings)
33
- .filter((migration) => allowlistKeys.has(migration.key))
34
- .map((migration) => [
35
- migration.key,
36
- shouldMigrateTlonSetting(migration.fileValue, migration.settingsValue),
37
- ]),
38
- );
39
- }
40
-
41
- describe("shouldMigrateTlonSetting", () => {
42
- it("does not rehydrate explicit empty-array revocations during startup migration", () => {
43
- const decisions = allowlistMigrationDecisions({
44
- dmAllowlist: [],
45
- groupInviteAllowlist: [],
46
- defaultAuthorizedShips: [],
47
- });
48
-
49
- expect(decisions).toEqual({
50
- dmAllowlist: false,
51
- groupInviteAllowlist: false,
52
- defaultAuthorizedShips: false,
53
- });
54
- });
55
-
56
- it("still seeds file-config allowlists on first run when settings are missing", () => {
57
- const decisions = allowlistMigrationDecisions({});
58
-
59
- expect(decisions).toEqual({
60
- dmAllowlist: true,
61
- groupInviteAllowlist: true,
62
- defaultAuthorizedShips: true,
63
- });
64
- });
65
- });
66
-
67
- describe("applyTlonSettingsOverrides", () => {
68
- it("treats explicit empty settings allowlists as authoritative deny-all", () => {
69
- const result = applyTlonSettingsOverrides({
70
- account: baseAccount,
71
- currentSettings: {
72
- dmAllowlist: [],
73
- groupInviteAllowlist: [],
74
- },
75
- });
76
-
77
- expect(result.effectiveDmAllowlist).toStrictEqual([]);
78
- expect(result.effectiveGroupInviteAllowlist).toStrictEqual([]);
79
- });
80
-
81
- it("falls back to file config when settings fields are removed", () => {
82
- const result = applyTlonSettingsOverrides({
83
- account: baseAccount,
84
- currentSettings: {},
85
- });
86
-
87
- expect(result.effectiveDmAllowlist).toEqual(baseAccount.dmAllowlist);
88
- expect(result.effectiveGroupInviteAllowlist).toEqual(baseAccount.groupInviteAllowlist);
89
- expect(result.effectiveAutoDiscoverChannels).toBe(baseAccount.autoDiscoverChannels);
90
- expect(result.effectiveOwnerShip).toBe(baseAccount.ownerShip);
91
- });
92
-
93
- it("keeps other explicit settings overrides authoritative", () => {
94
- const result = applyTlonSettingsOverrides({
95
- account: baseAccount,
96
- currentSettings: {
97
- autoDiscoverChannels: false,
98
- autoAcceptDmInvites: false,
99
- autoAcceptGroupInvites: false,
100
- showModelSig: true,
101
- ownerShip: "~nec",
102
- pendingApprovals: [],
103
- },
104
- });
105
-
106
- expect(result.effectiveAutoDiscoverChannels).toBe(false);
107
- expect(result.effectiveAutoAcceptDmInvites).toBe(false);
108
- expect(result.effectiveAutoAcceptGroupInvites).toBe(false);
109
- expect(result.effectiveShowModelSig).toBe(true);
110
- expect(result.effectiveOwnerShip).toBe("~nec");
111
- expect(result.pendingApprovals).toStrictEqual([]);
112
- });
113
- });
@@ -1,158 +0,0 @@
1
- import type { PendingApproval, TlonSettingsStore } from "../settings.js";
2
- import { normalizeShip } from "../targets.js";
3
- import type { TlonResolvedAccount } from "../types.js";
4
-
5
- type TlonMonitorSettingsState = {
6
- effectiveDmAllowlist: string[];
7
- effectiveShowModelSig: boolean;
8
- effectiveAutoAcceptDmInvites: boolean;
9
- effectiveAutoAcceptGroupInvites: boolean;
10
- effectiveGroupInviteAllowlist: string[];
11
- effectiveAutoDiscoverChannels: boolean;
12
- effectiveOwnerShip: string | null;
13
- pendingApprovals: PendingApproval[];
14
- currentSettings: TlonSettingsStore;
15
- };
16
-
17
- export function buildTlonSettingsMigrations(
18
- account: TlonResolvedAccount,
19
- currentSettings: TlonSettingsStore,
20
- ): Array<{ key: string; fileValue: unknown; settingsValue: unknown }> {
21
- return [
22
- {
23
- key: "dmAllowlist",
24
- fileValue: account.dmAllowlist,
25
- settingsValue: currentSettings.dmAllowlist,
26
- },
27
- {
28
- key: "groupInviteAllowlist",
29
- fileValue: account.groupInviteAllowlist,
30
- settingsValue: currentSettings.groupInviteAllowlist,
31
- },
32
- {
33
- key: "groupChannels",
34
- fileValue: account.groupChannels,
35
- settingsValue: currentSettings.groupChannels,
36
- },
37
- {
38
- key: "defaultAuthorizedShips",
39
- fileValue: account.defaultAuthorizedShips,
40
- settingsValue: currentSettings.defaultAuthorizedShips,
41
- },
42
- {
43
- key: "autoDiscoverChannels",
44
- fileValue: account.autoDiscoverChannels,
45
- settingsValue: currentSettings.autoDiscoverChannels,
46
- },
47
- {
48
- key: "autoAcceptDmInvites",
49
- fileValue: account.autoAcceptDmInvites,
50
- settingsValue: currentSettings.autoAcceptDmInvites,
51
- },
52
- {
53
- key: "autoAcceptGroupInvites",
54
- fileValue: account.autoAcceptGroupInvites,
55
- settingsValue: currentSettings.autoAcceptGroupInvites,
56
- },
57
- {
58
- key: "showModelSig",
59
- fileValue: account.showModelSignature,
60
- settingsValue: currentSettings.showModelSig,
61
- },
62
- ];
63
- }
64
-
65
- export function shouldMigrateTlonSetting(fileValue: unknown, settingsValue: unknown): boolean {
66
- const hasFileValue = Array.isArray(fileValue) ? fileValue.length > 0 : fileValue != null;
67
- const hasSettingsValue = settingsValue != null;
68
- return hasFileValue && !hasSettingsValue;
69
- }
70
-
71
- export function applyTlonSettingsOverrides(params: {
72
- account: TlonResolvedAccount;
73
- currentSettings: TlonSettingsStore;
74
- log?: (message: string) => void;
75
- }): TlonMonitorSettingsState {
76
- let effectiveDmAllowlist = params.account.dmAllowlist;
77
- let effectiveShowModelSig = params.account.showModelSignature ?? false;
78
- let effectiveAutoAcceptDmInvites = params.account.autoAcceptDmInvites ?? false;
79
- let effectiveAutoAcceptGroupInvites = params.account.autoAcceptGroupInvites ?? false;
80
- let effectiveGroupInviteAllowlist = params.account.groupInviteAllowlist;
81
- let effectiveAutoDiscoverChannels = params.account.autoDiscoverChannels ?? false;
82
- let effectiveOwnerShip = params.account.ownerShip
83
- ? normalizeShip(params.account.ownerShip)
84
- : null;
85
- let pendingApprovals: PendingApproval[] = [];
86
-
87
- if (params.currentSettings.defaultAuthorizedShips?.length) {
88
- params.log?.(
89
- `[tlon] Using defaultAuthorizedShips from settings store: ${params.currentSettings.defaultAuthorizedShips.join(", ")}`,
90
- );
91
- }
92
- if (params.currentSettings.autoDiscoverChannels !== undefined) {
93
- effectiveAutoDiscoverChannels = params.currentSettings.autoDiscoverChannels;
94
- params.log?.(
95
- `[tlon] Using autoDiscoverChannels from settings store: ${effectiveAutoDiscoverChannels}`,
96
- );
97
- }
98
- if (params.currentSettings.dmAllowlist !== undefined) {
99
- effectiveDmAllowlist = params.currentSettings.dmAllowlist;
100
- params.log?.(
101
- `[tlon] Using dmAllowlist from settings store: ${effectiveDmAllowlist.join(", ")}`,
102
- );
103
- }
104
- if (params.currentSettings.showModelSig !== undefined) {
105
- effectiveShowModelSig = params.currentSettings.showModelSig;
106
- }
107
- if (params.currentSettings.autoAcceptDmInvites !== undefined) {
108
- effectiveAutoAcceptDmInvites = params.currentSettings.autoAcceptDmInvites;
109
- params.log?.(
110
- `[tlon] Using autoAcceptDmInvites from settings store: ${effectiveAutoAcceptDmInvites}`,
111
- );
112
- }
113
- if (params.currentSettings.autoAcceptGroupInvites !== undefined) {
114
- effectiveAutoAcceptGroupInvites = params.currentSettings.autoAcceptGroupInvites;
115
- params.log?.(
116
- `[tlon] Using autoAcceptGroupInvites from settings store: ${effectiveAutoAcceptGroupInvites}`,
117
- );
118
- }
119
- if (params.currentSettings.groupInviteAllowlist !== undefined) {
120
- effectiveGroupInviteAllowlist = params.currentSettings.groupInviteAllowlist;
121
- params.log?.(
122
- `[tlon] Using groupInviteAllowlist from settings store: ${effectiveGroupInviteAllowlist.join(", ")}`,
123
- );
124
- }
125
- if (params.currentSettings.ownerShip) {
126
- effectiveOwnerShip = normalizeShip(params.currentSettings.ownerShip);
127
- params.log?.(`[tlon] Using ownerShip from settings store: ${effectiveOwnerShip}`);
128
- }
129
- if (params.currentSettings.pendingApprovals?.length) {
130
- pendingApprovals = params.currentSettings.pendingApprovals;
131
- params.log?.(`[tlon] Loaded ${pendingApprovals.length} pending approval(s) from settings`);
132
- }
133
-
134
- return {
135
- effectiveDmAllowlist,
136
- effectiveShowModelSig,
137
- effectiveAutoAcceptDmInvites,
138
- effectiveAutoAcceptGroupInvites,
139
- effectiveGroupInviteAllowlist,
140
- effectiveAutoDiscoverChannels,
141
- effectiveOwnerShip,
142
- pendingApprovals,
143
- currentSettings: params.currentSettings,
144
- };
145
- }
146
-
147
- export function mergeUniqueStrings(base: string[], next?: string[]): string[] {
148
- if (!next?.length) {
149
- return [...base];
150
- }
151
- const merged = [...base];
152
- for (const value of next) {
153
- if (!merged.includes(value)) {
154
- merged.push(value);
155
- }
156
- }
157
- return merged;
158
- }