birdclaw 0.8.1 → 0.8.3
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.
- package/CHANGELOG.md +25 -0
- package/package.json +2 -4
- package/scripts/browser-perf.mjs +27 -0
- package/src/cli/command-context.ts +17 -0
- package/src/cli/register-analysis.ts +500 -0
- package/src/cli/register-compose.ts +40 -0
- package/src/cli/register-graph.ts +132 -0
- package/src/cli/register-inbox.ts +41 -0
- package/src/cli/register-storage.ts +106 -0
- package/src/cli.ts +30 -750
- package/src/components/AccountSwitcher.tsx +7 -15
- package/src/components/AvatarChip.tsx +1 -1
- package/src/components/AvatarPreload.ts +149 -0
- package/src/components/ConversationThread.tsx +4 -0
- package/src/components/EmbeddedTweetCard.tsx +4 -0
- package/src/components/FloatingPreview.tsx +382 -0
- package/src/components/MarkdownCitations.tsx +680 -0
- package/src/components/MarkdownViewer.tsx +7 -715
- package/src/components/ProfileAnalysisClient.ts +191 -0
- package/src/components/ProfileAnalysisStream.tsx +16 -185
- package/src/components/ProfilePreview.tsx +41 -81
- package/src/components/TimelineCard.tsx +18 -2
- package/src/components/TweetArticleCard.tsx +66 -0
- package/src/components/TweetRichText.tsx +33 -2
- package/src/components/links-controller.ts +162 -0
- package/src/components/links-model.ts +198 -0
- package/src/components/network-map-controller.ts +84 -0
- package/src/components/network-map-model.ts +255 -0
- package/src/components/useDebouncedValue.ts +12 -0
- package/src/components/useTimelineRouteData.ts +142 -170
- package/src/lib/analysis-runtime.ts +238 -0
- package/src/lib/api-client.ts +16 -100
- package/src/lib/api-contracts.ts +328 -0
- package/src/lib/archive-finder.ts +38 -0
- package/src/lib/archive-import-plan.ts +102 -0
- package/src/lib/archive-import.ts +170 -239
- package/src/lib/authored-live.ts +77 -182
- package/src/lib/backup.ts +335 -424
- package/src/lib/bird.ts +32 -1
- package/src/lib/blocks-write.ts +30 -26
- package/src/lib/blocks.ts +18 -20
- package/src/lib/database-metrics.ts +88 -0
- package/src/lib/database-migrations.ts +34 -0
- package/src/lib/database-schema.ts +312 -0
- package/src/lib/database-writer.ts +69 -0
- package/src/lib/db.ts +141 -334
- package/src/lib/dm-read-model.ts +533 -0
- package/src/lib/dms-live.ts +34 -97
- package/src/lib/follow-graph.ts +17 -27
- package/src/lib/import-repository.ts +138 -0
- package/src/lib/inbox.ts +2 -1
- package/src/lib/live-sync-engine.ts +209 -0
- package/src/lib/live-transport-gateway.ts +128 -0
- package/src/lib/mention-threads-live.ts +90 -176
- package/src/lib/mentions-export.ts +1 -1
- package/src/lib/mentions-live.ts +57 -225
- package/src/lib/moderation-target.ts +15 -4
- package/src/lib/moderation-write.ts +1 -1
- package/src/lib/mutes-write.ts +30 -26
- package/src/lib/openai-response-runtime.ts +251 -0
- package/src/lib/paginated-sync.ts +93 -0
- package/src/lib/period-digest.ts +116 -304
- package/src/lib/profile-analysis.ts +37 -111
- package/src/lib/queries.ts +6 -2380
- package/src/lib/query-actions.ts +437 -0
- package/src/lib/query-client.tsx +47 -0
- package/src/lib/query-read-model-shared.ts +52 -0
- package/src/lib/query-read-models.ts +5 -0
- package/src/lib/query-resource.ts +41 -0
- package/src/lib/query-status.ts +164 -0
- package/src/lib/research.ts +1 -1
- package/src/lib/runtime-services.ts +20 -0
- package/src/lib/search-discussion.ts +75 -279
- package/src/lib/server-runtime-services.ts +30 -0
- package/src/lib/sqlite.ts +53 -14
- package/src/lib/streaming-ingestion.ts +240 -0
- package/src/lib/sync-cache.ts +6 -1
- package/src/lib/sync-plan.ts +175 -0
- package/src/lib/timeline-collections-live.ts +83 -256
- package/src/lib/timeline-live.ts +86 -235
- package/src/lib/timeline-read-model.ts +1191 -0
- package/src/lib/tweet-render.ts +78 -0
- package/src/lib/tweet-repository.ts +156 -0
- package/src/lib/tweet-search-live.ts +63 -166
- package/src/lib/types.ts +8 -0
- package/src/lib/ui.ts +1 -1
- package/src/lib/web-sync.ts +67 -50
- package/src/lib/whois.ts +2 -1
- package/src/router.tsx +1 -1
- package/src/routes/__root.tsx +11 -21
- package/src/routes/api/action.tsx +1 -1
- package/src/routes/api/conversation.tsx +1 -1
- package/src/routes/api/query.tsx +32 -26
- package/src/routes/api/status.tsx +6 -2
- package/src/routes/api/sync.tsx +5 -2
- package/src/routes/blocks.tsx +97 -131
- package/src/routes/data-sources.tsx +17 -25
- package/src/routes/dms.tsx +168 -167
- package/src/routes/inbox.tsx +63 -57
- package/src/routes/links.tsx +31 -329
- package/src/routes/network-map.tsx +41 -344
- package/src/routes/rate-limits.tsx +17 -21
- package/vite.config.ts +0 -2
package/src/lib/authored-live.ts
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import type { Database } from "./sqlite";
|
|
2
2
|
import { Effect } from "effect";
|
|
3
|
+
import { databaseWriteEffect } from "./database-writer";
|
|
3
4
|
import { getNativeDb } from "./db";
|
|
4
|
-
import { runEffectPromise
|
|
5
|
+
import { runEffectPromise } from "./effect-runtime";
|
|
6
|
+
import { liveTransportGateway } from "./live-transport-gateway";
|
|
7
|
+
import { resolveLiveSyncAccount } from "./live-sync-engine";
|
|
5
8
|
import { readSyncCache, writeSyncCache } from "./sync-cache";
|
|
9
|
+
import { runSyncPlanEffect } from "./sync-plan";
|
|
10
|
+
import { tweetEntitiesFromXurl } from "./tweet-render";
|
|
6
11
|
import type {
|
|
7
12
|
TweetEntities,
|
|
8
13
|
TweetMediaItem,
|
|
@@ -20,11 +25,6 @@ import {
|
|
|
20
25
|
ensureStubProfileForXUser,
|
|
21
26
|
upsertProfileFromXUser,
|
|
22
27
|
} from "./x-profile";
|
|
23
|
-
import {
|
|
24
|
-
getTransportStatus,
|
|
25
|
-
listUserTweets,
|
|
26
|
-
lookupAuthenticatedUser,
|
|
27
|
-
} from "./xurl";
|
|
28
28
|
|
|
29
29
|
export type AuthoredSyncMode = "xurl";
|
|
30
30
|
|
|
@@ -359,43 +359,6 @@ function getOldestTweetId(tweets: XurlMentionData[]) {
|
|
|
359
359
|
}, null);
|
|
360
360
|
}
|
|
361
361
|
|
|
362
|
-
function resolveAccount(db: Database, accountId?: string) {
|
|
363
|
-
const row = accountId
|
|
364
|
-
? (db
|
|
365
|
-
.prepare(
|
|
366
|
-
"select id, handle, external_user_id from accounts where id = ?",
|
|
367
|
-
)
|
|
368
|
-
.get(accountId) as
|
|
369
|
-
| { id: string; handle: string; external_user_id: string | null }
|
|
370
|
-
| undefined)
|
|
371
|
-
: (db
|
|
372
|
-
.prepare(
|
|
373
|
-
`
|
|
374
|
-
select id, handle, external_user_id
|
|
375
|
-
from accounts
|
|
376
|
-
order by is_default desc, created_at asc
|
|
377
|
-
limit 1
|
|
378
|
-
`,
|
|
379
|
-
)
|
|
380
|
-
.get() as
|
|
381
|
-
| { id: string; handle: string; external_user_id: string | null }
|
|
382
|
-
| undefined);
|
|
383
|
-
|
|
384
|
-
if (!row) {
|
|
385
|
-
throw new Error(`Unknown account: ${accountId ?? "default"}`);
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
return {
|
|
389
|
-
accountId: row.id,
|
|
390
|
-
username: row.handle.replace(/^@/, ""),
|
|
391
|
-
externalUserId:
|
|
392
|
-
typeof row.external_user_id === "string" &&
|
|
393
|
-
row.external_user_id.length > 0
|
|
394
|
-
? row.external_user_id
|
|
395
|
-
: undefined,
|
|
396
|
-
};
|
|
397
|
-
}
|
|
398
|
-
|
|
399
362
|
function normalizeUsername(value: string) {
|
|
400
363
|
return value.replace(/^@/, "").trim().toLowerCase();
|
|
401
364
|
}
|
|
@@ -443,12 +406,14 @@ function resolveAuthoredIdentityEffect({
|
|
|
443
406
|
db: Database;
|
|
444
407
|
}) {
|
|
445
408
|
return Effect.gen(function* () {
|
|
446
|
-
const status = yield*
|
|
409
|
+
const status = yield* liveTransportGateway.xurl.getTransportStatus();
|
|
447
410
|
if (status.availableTransport !== "xurl") {
|
|
448
411
|
return yield* Effect.fail(new AuthoredSyncError(status.statusText, 4));
|
|
449
412
|
}
|
|
450
413
|
|
|
451
|
-
const resolvedAccount = yield* trySync(() =>
|
|
414
|
+
const resolvedAccount = yield* trySync(() =>
|
|
415
|
+
resolveLiveSyncAccount(db, account),
|
|
416
|
+
);
|
|
452
417
|
if (resolvedAccount.externalUserId) {
|
|
453
418
|
return {
|
|
454
419
|
...resolvedAccount,
|
|
@@ -457,7 +422,8 @@ function resolveAuthoredIdentityEffect({
|
|
|
457
422
|
};
|
|
458
423
|
}
|
|
459
424
|
|
|
460
|
-
const authenticated =
|
|
425
|
+
const authenticated =
|
|
426
|
+
yield* liveTransportGateway.xurl.lookupAuthenticatedUser();
|
|
461
427
|
const authenticatedUser = userFromAuthenticatedPayload(authenticated);
|
|
462
428
|
if (!authenticatedUser?.id) {
|
|
463
429
|
return yield* Effect.fail(
|
|
@@ -523,68 +489,7 @@ function toMentionData(tweet: XurlUserTweet, fallbackAuthorId: string) {
|
|
|
523
489
|
}
|
|
524
490
|
|
|
525
491
|
function toLocalEntities(tweet: XurlMentionData): TweetEntities {
|
|
526
|
-
|
|
527
|
-
if (!raw || typeof raw !== "object") {
|
|
528
|
-
return {};
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
const entities = raw as Record<string, unknown>;
|
|
532
|
-
const rawMentions = Array.isArray(entities.mentions) ? entities.mentions : [];
|
|
533
|
-
const rawUrls = Array.isArray(entities.urls) ? entities.urls : [];
|
|
534
|
-
const rawHashtags = Array.isArray(entities.hashtags) ? entities.hashtags : [];
|
|
535
|
-
|
|
536
|
-
return {
|
|
537
|
-
...(rawMentions.length
|
|
538
|
-
? {
|
|
539
|
-
mentions: rawMentions.map((mention) => {
|
|
540
|
-
const value =
|
|
541
|
-
mention && typeof mention === "object"
|
|
542
|
-
? (mention as Record<string, unknown>)
|
|
543
|
-
: {};
|
|
544
|
-
return {
|
|
545
|
-
username: String(value.username ?? ""),
|
|
546
|
-
id: typeof value.id === "string" ? String(value.id) : undefined,
|
|
547
|
-
start: Number(value.start ?? 0),
|
|
548
|
-
end: Number(value.end ?? 0),
|
|
549
|
-
};
|
|
550
|
-
}),
|
|
551
|
-
}
|
|
552
|
-
: {}),
|
|
553
|
-
...(rawUrls.length
|
|
554
|
-
? {
|
|
555
|
-
urls: rawUrls.map((url) => {
|
|
556
|
-
const value =
|
|
557
|
-
url && typeof url === "object"
|
|
558
|
-
? (url as Record<string, unknown>)
|
|
559
|
-
: {};
|
|
560
|
-
return {
|
|
561
|
-
url: String(value.url ?? ""),
|
|
562
|
-
expandedUrl: String(value.expanded_url ?? value.url ?? ""),
|
|
563
|
-
displayUrl: String(
|
|
564
|
-
value.display_url ?? value.expanded_url ?? value.url ?? "",
|
|
565
|
-
),
|
|
566
|
-
start: Number(value.start ?? 0),
|
|
567
|
-
end: Number(value.end ?? 0),
|
|
568
|
-
};
|
|
569
|
-
}),
|
|
570
|
-
}
|
|
571
|
-
: {}),
|
|
572
|
-
...(rawHashtags.length
|
|
573
|
-
? {
|
|
574
|
-
hashtags: rawHashtags.map((hashtag) => {
|
|
575
|
-
const value =
|
|
576
|
-
hashtag && typeof hashtag === "object"
|
|
577
|
-
? (hashtag as Record<string, unknown>)
|
|
578
|
-
: {};
|
|
579
|
-
return {
|
|
580
|
-
tag: String(value.tag ?? ""),
|
|
581
|
-
start: Number(value.start ?? 0),
|
|
582
|
-
end: Number(value.end ?? 0),
|
|
583
|
-
};
|
|
584
|
-
}),
|
|
585
|
-
}
|
|
586
|
-
: {}),
|
|
587
|
-
};
|
|
492
|
+
return tweetEntitiesFromXurl(tweet.entities);
|
|
588
493
|
}
|
|
589
494
|
|
|
590
495
|
function toMediaType(type: string): TweetMediaItem["type"] {
|
|
@@ -971,7 +876,7 @@ export function syncAuthoredTweetsEffect({
|
|
|
971
876
|
archiveSinceSeed ??
|
|
972
877
|
(untilId ? persistedUntilSinceId : cursor.sinceId) ??
|
|
973
878
|
null;
|
|
974
|
-
|
|
879
|
+
const initialToken = usePersistedForward
|
|
975
880
|
? cursor.token
|
|
976
881
|
: usePersistedUntil
|
|
977
882
|
? cursor.token
|
|
@@ -979,19 +884,20 @@ export function syncAuthoredTweetsEffect({
|
|
|
979
884
|
let newestSeenId = usePersistedForward
|
|
980
885
|
? maxTweetId(cursor.sinceId, cursor.pendingNewestId)
|
|
981
886
|
: cursor.sinceId;
|
|
982
|
-
const pages: XurlUserTweetsResponse[] = [];
|
|
983
887
|
const sourceUser = toFallbackUser({
|
|
984
888
|
userId: identity.userId,
|
|
985
889
|
username: identity.username,
|
|
986
890
|
authenticatedUser: identity.authenticatedUser,
|
|
987
891
|
});
|
|
988
|
-
let pageCount = 0;
|
|
989
892
|
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
893
|
+
const planResult = yield* runSyncPlanEffect({
|
|
894
|
+
allowPartialFailure: true,
|
|
895
|
+
initialCursor: initialToken,
|
|
896
|
+
maxPages: parsedMaxPages ?? undefined,
|
|
897
|
+
fetchPage: ({ cursor: paginationToken }) =>
|
|
898
|
+
liveTransportGateway.xurl.listUserTweets(identity.userId, {
|
|
993
899
|
maxResults: pageLimit,
|
|
994
|
-
paginationToken
|
|
900
|
+
paginationToken,
|
|
995
901
|
excludeRetweets: false,
|
|
996
902
|
sinceId: effectiveSinceId ?? undefined,
|
|
997
903
|
untilId,
|
|
@@ -1002,77 +908,66 @@ export function syncAuthoredTweetsEffect({
|
|
|
1002
908
|
auth: "oauth2",
|
|
1003
909
|
username: identity.username,
|
|
1004
910
|
}),
|
|
1005
|
-
).
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
),
|
|
1010
|
-
);
|
|
1011
|
-
|
|
1012
|
-
if (!fetched.ok) {
|
|
1013
|
-
if (pages.length === 0) {
|
|
1014
|
-
return yield* Effect.fail(fetched.error);
|
|
1015
|
-
}
|
|
1016
|
-
const payload = mergePages({
|
|
1017
|
-
pages,
|
|
1018
|
-
userId: identity.userId,
|
|
1019
|
-
nextToken: nextToken ?? null,
|
|
1020
|
-
});
|
|
1021
|
-
return buildResult({
|
|
1022
|
-
accountId: identity.accountId,
|
|
911
|
+
getNextCursor: (page) => page.nextToken,
|
|
912
|
+
persistPage: ({ page, nextCursor: pendingToken }) => {
|
|
913
|
+
const pagePayload = mergePages({
|
|
914
|
+
pages: [page],
|
|
1023
915
|
userId: identity.userId,
|
|
1024
|
-
|
|
1025
|
-
nextSinceId: untilId ? cursor.sinceId : effectiveSinceId,
|
|
1026
|
-
nextToken: nextToken ?? null,
|
|
1027
|
-
pageCount,
|
|
1028
|
-
payload,
|
|
1029
|
-
partial: true,
|
|
1030
|
-
error: formatError(fetched.error),
|
|
916
|
+
nextToken: page.nextToken,
|
|
1031
917
|
});
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
nextToken: page.nextToken,
|
|
1039
|
-
});
|
|
1040
|
-
yield* trySync(() =>
|
|
1041
|
-
mergeAuthoredPayloadIntoLocalStore({
|
|
1042
|
-
db,
|
|
1043
|
-
accountId: identity.accountId,
|
|
1044
|
-
payload: pagePayload,
|
|
1045
|
-
sourceUser,
|
|
1046
|
-
}),
|
|
1047
|
-
);
|
|
1048
|
-
pages.push(page);
|
|
1049
|
-
pageCount += 1;
|
|
1050
|
-
newestSeenId = maxTweetId(newestSeenId, pagePayload.meta.newest_id);
|
|
1051
|
-
nextToken = page.nextToken ?? undefined;
|
|
1052
|
-
|
|
1053
|
-
const pendingToken = nextToken;
|
|
1054
|
-
if (pendingToken && untilId) {
|
|
1055
|
-
yield* trySync(() =>
|
|
1056
|
-
writePendingUntilCursor(db, identity.accountId, {
|
|
1057
|
-
sinceId: cursor.sinceId,
|
|
1058
|
-
token: pendingToken,
|
|
1059
|
-
untilId,
|
|
1060
|
-
requestedSinceId: effectiveSinceId,
|
|
918
|
+
return databaseWriteEffect((writeDb) =>
|
|
919
|
+
mergeAuthoredPayloadIntoLocalStore({
|
|
920
|
+
db: writeDb,
|
|
921
|
+
accountId: identity.accountId,
|
|
922
|
+
payload: pagePayload,
|
|
923
|
+
sourceUser,
|
|
1061
924
|
}),
|
|
925
|
+
).pipe(
|
|
926
|
+
Effect.flatMap(() =>
|
|
927
|
+
trySync(() => {
|
|
928
|
+
newestSeenId = maxTweetId(
|
|
929
|
+
newestSeenId,
|
|
930
|
+
pagePayload.meta.newest_id,
|
|
931
|
+
);
|
|
932
|
+
if (pendingToken && untilId) {
|
|
933
|
+
writePendingUntilCursor(db, identity.accountId, {
|
|
934
|
+
sinceId: cursor.sinceId,
|
|
935
|
+
token: pendingToken,
|
|
936
|
+
untilId,
|
|
937
|
+
requestedSinceId: effectiveSinceId,
|
|
938
|
+
});
|
|
939
|
+
} else if (pendingToken) {
|
|
940
|
+
writePendingForwardCursor(db, identity.accountId, {
|
|
941
|
+
sinceId: effectiveSinceId,
|
|
942
|
+
token: pendingToken,
|
|
943
|
+
pendingNewestId: newestSeenId,
|
|
944
|
+
});
|
|
945
|
+
}
|
|
946
|
+
}),
|
|
947
|
+
),
|
|
1062
948
|
);
|
|
1063
|
-
}
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
949
|
+
},
|
|
950
|
+
});
|
|
951
|
+
const pages = planResult.pages;
|
|
952
|
+
const pageCount = pages.length;
|
|
953
|
+
const nextToken = planResult.nextCursor;
|
|
954
|
+
if (planResult.stopReason === "error") {
|
|
955
|
+
const payload = mergePages({
|
|
956
|
+
pages,
|
|
957
|
+
userId: identity.userId,
|
|
958
|
+
nextToken: nextToken ?? null,
|
|
959
|
+
});
|
|
960
|
+
return buildResult({
|
|
961
|
+
accountId: identity.accountId,
|
|
962
|
+
userId: identity.userId,
|
|
963
|
+
effectiveSinceId,
|
|
964
|
+
nextSinceId: untilId ? cursor.sinceId : effectiveSinceId,
|
|
965
|
+
nextToken: nextToken ?? null,
|
|
966
|
+
pageCount,
|
|
967
|
+
payload,
|
|
968
|
+
partial: true,
|
|
969
|
+
error: formatError(planResult.error),
|
|
970
|
+
});
|
|
1076
971
|
}
|
|
1077
972
|
|
|
1078
973
|
const capped = Boolean(nextToken);
|