@kodelyth/tlon 2026.5.42 → 2026.6.2

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 +19 -6
  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,54 +0,0 @@
1
- import type { RuntimeEnv } from "klaw/plugin-sdk/runtime";
2
- import { asRecord, extractCites, extractMessageText, type ParsedCite } from "./utils.js";
3
-
4
- type TlonScryApi = {
5
- scry: (path: string) => Promise<unknown>;
6
- };
7
-
8
- export function createTlonCitationResolver(params: { api: TlonScryApi; runtime: RuntimeEnv }) {
9
- const { api, runtime } = params;
10
-
11
- const resolveCiteContent = async (cite: ParsedCite): Promise<string | null> => {
12
- if (cite.type !== "chan" || !cite.nest || !cite.postId) {
13
- return null;
14
- }
15
-
16
- try {
17
- const scryPath = `/channels/v4/${cite.nest}/posts/post/${cite.postId}.json`;
18
- runtime.log?.(`[tlon] Fetching cited post: ${scryPath}`);
19
-
20
- const data = asRecord(await api.scry(scryPath));
21
- const essay = asRecord(data?.essay);
22
- if (essay?.content) {
23
- return extractMessageText(essay.content) || null;
24
- }
25
-
26
- return null;
27
- } catch (err) {
28
- runtime.log?.(`[tlon] Failed to fetch cited post: ${String(err)}`);
29
- return null;
30
- }
31
- };
32
-
33
- const resolveAllCites = async (content: unknown): Promise<string> => {
34
- const cites = extractCites(content);
35
- if (cites.length === 0) {
36
- return "";
37
- }
38
-
39
- const resolved: string[] = [];
40
- for (const cite of cites) {
41
- const text = await resolveCiteContent(cite);
42
- if (text) {
43
- resolved.push(`> ${cite.author || "unknown"} wrote: ${text}`);
44
- }
45
- }
46
-
47
- return resolved.length > 0 ? `${resolved.join("\n")}\n\n` : "";
48
- };
49
-
50
- return {
51
- resolveCiteContent,
52
- resolveAllCites,
53
- };
54
- }
@@ -1,68 +0,0 @@
1
- import type { RuntimeEnv } from "klaw/plugin-sdk/runtime";
2
- import type { Foreigns } from "../urbit/foreigns.js";
3
- import { asRecord, formatErrorMessage } from "./utils.js";
4
-
5
- interface InitData {
6
- channels: string[];
7
- foreigns: Foreigns | null;
8
- }
9
-
10
- /**
11
- * Fetch groups-ui init data, returning channels and foreigns.
12
- * This is a single scry that provides both channel discovery and pending invites.
13
- */
14
- export async function fetchInitData(
15
- api: { scry: (path: string) => Promise<unknown> },
16
- runtime: RuntimeEnv,
17
- ): Promise<InitData> {
18
- try {
19
- runtime.log?.("[tlon] Fetching groups-ui init data...");
20
- const initData = asRecord(await api.scry("/groups-ui/v6/init.json"));
21
-
22
- const channels: string[] = [];
23
- const groups = asRecord(initData?.groups);
24
- if (groups) {
25
- for (const groupData of Object.values(groups)) {
26
- const typedGroupData = asRecord(groupData);
27
- const groupChannels = asRecord(typedGroupData?.channels);
28
- if (groupChannels) {
29
- for (const channelNest of Object.keys(groupChannels)) {
30
- if (channelNest.startsWith("chat/")) {
31
- channels.push(channelNest);
32
- }
33
- }
34
- }
35
- }
36
- }
37
-
38
- if (channels.length > 0) {
39
- runtime.log?.(`[tlon] Auto-discovered ${channels.length} chat channel(s)`);
40
- } else {
41
- runtime.log?.("[tlon] No chat channels found via auto-discovery");
42
- }
43
-
44
- const foreignsValue = asRecord(initData?.foreigns);
45
- const foreigns = foreignsValue ? (foreignsValue as Foreigns) : null;
46
- if (foreigns) {
47
- const pendingCount = Object.values(foreigns).filter((f) =>
48
- f.invites?.some((i) => i.valid),
49
- ).length;
50
- if (pendingCount > 0) {
51
- runtime.log?.(`[tlon] Found ${pendingCount} pending group invite(s)`);
52
- }
53
- }
54
-
55
- return { channels, foreigns };
56
- } catch (error: unknown) {
57
- runtime.log?.(`[tlon] Init data fetch failed: ${formatErrorMessage(error)}`);
58
- return { channels: [], foreigns: null };
59
- }
60
- }
61
-
62
- export async function fetchAllChannels(
63
- api: { scry: (path: string) => Promise<unknown> },
64
- runtime: RuntimeEnv,
65
- ): Promise<string[]> {
66
- const { channels } = await fetchInitData(api, runtime);
67
- return channels;
68
- }
@@ -1,226 +0,0 @@
1
- import type { RuntimeEnv } from "klaw/plugin-sdk/runtime";
2
- import { asRecord, extractMessageText, formatErrorMessage } from "./utils.js";
3
-
4
- /**
5
- * Format a number as @ud (with dots every 3 digits from the right)
6
- * e.g., 170141184507799509469114119040828178432 -> 170.141.184.507.799.509.469.114.119.040.828.178.432
7
- */
8
- function formatUd(id: string | number): string {
9
- const str = String(id).replace(/\./g, ""); // Remove any existing dots
10
- const reversed = str.split("").toReversed();
11
- const chunks: string[] = [];
12
- for (let i = 0; i < reversed.length; i += 3) {
13
- chunks.push(
14
- reversed
15
- .slice(i, i + 3)
16
- .toReversed()
17
- .join(""),
18
- );
19
- }
20
- return chunks.toReversed().join(".");
21
- }
22
-
23
- type TlonHistoryEntry = {
24
- author: string;
25
- content: string;
26
- timestamp: number;
27
- id?: string;
28
- };
29
-
30
- function createHistoryEntryFromMemo(params: {
31
- memo?: Record<string, unknown> | null;
32
- seal?: Record<string, unknown> | null;
33
- fallbackId?: unknown;
34
- }): TlonHistoryEntry {
35
- const { memo, seal, fallbackId } = params;
36
- return {
37
- author: typeof memo?.author === "string" ? memo.author : "unknown",
38
- content: extractMessageText(memo?.content || []),
39
- timestamp: typeof memo?.sent === "number" ? memo.sent : Date.now(),
40
- id:
41
- typeof seal?.id === "string"
42
- ? seal.id
43
- : typeof fallbackId === "string"
44
- ? fallbackId
45
- : undefined,
46
- };
47
- }
48
-
49
- const messageCache = new Map<string, TlonHistoryEntry[]>();
50
- const MAX_CACHED_MESSAGES = 100;
51
-
52
- export function cacheMessage(channelNest: string, message: TlonHistoryEntry) {
53
- if (!messageCache.has(channelNest)) {
54
- messageCache.set(channelNest, []);
55
- }
56
- const cache = messageCache.get(channelNest);
57
- if (!cache) {
58
- return;
59
- }
60
- cache.unshift(message);
61
- if (cache.length > MAX_CACHED_MESSAGES) {
62
- cache.pop();
63
- }
64
- }
65
-
66
- async function fetchChannelHistory(
67
- api: { scry: (path: string) => Promise<unknown> },
68
- channelNest: string,
69
- count = 50,
70
- runtime?: RuntimeEnv,
71
- ): Promise<TlonHistoryEntry[]> {
72
- try {
73
- const scryPath = `/channels/v4/${channelNest}/posts/newest/${count}/outline.json`;
74
- runtime?.log?.(`[tlon] Fetching history: ${scryPath}`);
75
-
76
- const data: unknown = await api.scry(scryPath);
77
- if (!data) {
78
- return [];
79
- }
80
-
81
- let posts: unknown[] = [];
82
- if (Array.isArray(data)) {
83
- posts = data;
84
- } else {
85
- const dataRecord = asRecord(data);
86
- const postMap = asRecord(dataRecord?.posts);
87
- if (postMap) {
88
- posts = Object.values(postMap);
89
- } else if (dataRecord) {
90
- posts = Object.values(dataRecord);
91
- }
92
- }
93
-
94
- const messages = posts
95
- .map((item) => {
96
- const itemRecord = asRecord(item);
97
- const replyPost = asRecord(itemRecord?.["r-post"]);
98
- const replyPostSet = asRecord(replyPost?.set);
99
- const essay = asRecord(itemRecord?.essay) ?? asRecord(replyPostSet?.essay);
100
- const seal = asRecord(itemRecord?.seal) ?? asRecord(replyPostSet?.seal);
101
-
102
- return {
103
- author: typeof essay?.author === "string" ? essay.author : "unknown",
104
- content: extractMessageText(essay?.content || []),
105
- timestamp: typeof essay?.sent === "number" ? essay.sent : Date.now(),
106
- id: typeof seal?.id === "string" ? seal.id : undefined,
107
- } as TlonHistoryEntry;
108
- })
109
- .filter((msg) => msg.content);
110
-
111
- runtime?.log?.(`[tlon] Extracted ${messages.length} messages from history`);
112
- return messages;
113
- } catch (error: unknown) {
114
- runtime?.log?.(`[tlon] Error fetching channel history: ${formatErrorMessage(error)}`);
115
- return [];
116
- }
117
- }
118
-
119
- export async function getChannelHistory(
120
- api: { scry: (path: string) => Promise<unknown> },
121
- channelNest: string,
122
- count = 50,
123
- runtime?: RuntimeEnv,
124
- ): Promise<TlonHistoryEntry[]> {
125
- const cache = messageCache.get(channelNest) ?? [];
126
- if (cache.length >= count) {
127
- runtime?.log?.(`[tlon] Using cached messages (${cache.length} available)`);
128
- return cache.slice(0, count);
129
- }
130
-
131
- runtime?.log?.(`[tlon] Cache has ${cache.length} messages, need ${count}, fetching from scry...`);
132
- return await fetchChannelHistory(api, channelNest, count, runtime);
133
- }
134
-
135
- /**
136
- * Fetch thread/reply history for a specific parent post.
137
- * Used to get context when entering a thread conversation.
138
- */
139
- export async function fetchThreadHistory(
140
- api: { scry: (path: string) => Promise<unknown> },
141
- channelNest: string,
142
- parentId: string,
143
- count = 50,
144
- runtime?: RuntimeEnv,
145
- ): Promise<TlonHistoryEntry[]> {
146
- try {
147
- // Tlon API: fetch replies to a specific post
148
- // Format: /channels/v4/{nest}/posts/post/{parentId}/replies/newest/{count}.json
149
- // parentId needs @ud formatting (dots every 3 digits)
150
- const formattedParentId = formatUd(parentId);
151
- runtime?.log?.(
152
- `[tlon] Thread history - parentId: ${parentId} -> formatted: ${formattedParentId}`,
153
- );
154
-
155
- const scryPath = `/channels/v4/${channelNest}/posts/post/id/${formattedParentId}/replies/newest/${count}.json`;
156
- runtime?.log?.(`[tlon] Fetching thread history: ${scryPath}`);
157
-
158
- const data: unknown = await api.scry(scryPath);
159
- if (!data) {
160
- runtime?.log?.(`[tlon] No thread history data returned`);
161
- return [];
162
- }
163
-
164
- let replies: unknown[] = [];
165
- if (Array.isArray(data)) {
166
- replies = data;
167
- } else {
168
- const dataRecord = asRecord(data);
169
- const replyValue = dataRecord?.replies;
170
- if (Array.isArray(replyValue)) {
171
- replies = replyValue;
172
- } else if (typeof replyValue === "object" && replyValue) {
173
- replies = Object.values(replyValue as Record<string, unknown>);
174
- } else if (dataRecord) {
175
- replies = Object.values(dataRecord);
176
- }
177
- }
178
-
179
- const messages = replies
180
- .map((item) => {
181
- // Thread replies use 'memo' structure
182
- const itemRecord = asRecord(item);
183
- const replyRecord = asRecord(itemRecord?.["r-reply"]);
184
- const replySet = asRecord(replyRecord?.set);
185
- const memo = asRecord(itemRecord?.memo) ?? asRecord(replySet?.memo) ?? itemRecord;
186
- const seal = asRecord(itemRecord?.seal) ?? asRecord(replySet?.seal);
187
-
188
- return createHistoryEntryFromMemo({ memo, seal, fallbackId: itemRecord?.id });
189
- })
190
- .filter((msg) => msg.content);
191
-
192
- runtime?.log?.(`[tlon] Extracted ${messages.length} thread replies from history`);
193
- return messages;
194
- } catch (error: unknown) {
195
- runtime?.log?.(`[tlon] Error fetching thread history: ${formatErrorMessage(error)}`);
196
- // Fall back to trying alternate path structure
197
- try {
198
- const altPath = `/channels/v4/${channelNest}/posts/post/id/${formatUd(parentId)}.json`;
199
- runtime?.log?.(`[tlon] Trying alternate path: ${altPath}`);
200
- const data = asRecord(await api.scry(altPath));
201
- const dataSeal = asRecord(data?.seal);
202
- const dataMeta = asRecord(dataSeal?.meta);
203
- const repliesValue = data?.replies;
204
-
205
- if (typeof dataMeta?.replyCount === "number" && dataMeta.replyCount > 0 && repliesValue) {
206
- const replies = Array.isArray(repliesValue)
207
- ? repliesValue
208
- : Object.values(repliesValue as Record<string, unknown>);
209
- const messages = replies
210
- .map((reply: unknown) => {
211
- const replyRecord = asRecord(reply);
212
- const memo = asRecord(replyRecord?.memo);
213
- const seal = asRecord(replyRecord?.seal);
214
- return createHistoryEntryFromMemo({ memo, seal });
215
- })
216
- .filter((msg: TlonHistoryEntry) => msg.content);
217
-
218
- runtime?.log?.(`[tlon] Extracted ${messages.length} replies from post data`);
219
- return messages;
220
- }
221
- } catch (altError: unknown) {
222
- runtime?.log?.(`[tlon] Alternate path also failed: ${formatErrorMessage(altError)}`);
223
- }
224
- return [];
225
- }
226
- }