@syengup/friday-channel-next 0.0.37 → 0.0.39

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 (93) hide show
  1. package/dist/index.js +10 -4
  2. package/index.ts +10 -4
  3. package/install.js +132 -257
  4. package/package.json +1 -1
  5. package/dist/attachments/0768c9b1-53b0-44df-83e8-be15c4ea188f.jpg +0 -0
  6. package/dist/attachments/0a379d01-116b-4da1-bf15-77cb2cbb0093.jpg +0 -0
  7. package/dist/attachments/181caab2-64a7-4004-a057-225a144f949e.mp3 +0 -0
  8. package/dist/attachments/19662331-e527-47d2-bc0e-0e19a7a91419.jpg +0 -0
  9. package/dist/attachments/26a23b2b-52df-4572-a5e1-15b34fb87e44.jpg +0 -0
  10. package/dist/attachments/2f9282c5-8db4-4c4a-a060-e65104f6f9ff.jpg +0 -0
  11. package/dist/attachments/3929ec3d-ea15-4de6-96bc-97e8b0b658a7.jpg +0 -0
  12. package/dist/attachments/403c0cbc-4e3c-4146-a3be-ff3746ee7cda.jpg +0 -0
  13. package/dist/attachments/441977f5-0f7b-4aa2-841a-1d63e787ea53.jpg +0 -0
  14. package/dist/attachments/453e8aa2-76e3-498d-8d6f-d7b96d6bf45b.jpg +0 -0
  15. package/dist/attachments/538cde71-d26e-4d3d-b901-e8dd905e668c.mp3 +0 -0
  16. package/dist/attachments/55c7f628-4ba2-4252-aa4b-4f3eb6045a8a.mp3 +0 -0
  17. package/dist/attachments/5f7683f5-8194-4698-b077-31d209525379.jpg +0 -0
  18. package/dist/attachments/60614a35-8f44-4197-b783-2f58f5a72ac8.jpeg +0 -0
  19. package/dist/attachments/62830489-8814-48b1-851c-3845e514f35e.mp3 +0 -0
  20. package/dist/attachments/66f4a62d-1531-4f38-a531-7456f9edf221.png +0 -0
  21. package/dist/attachments/6735d749-769e-483a-9b84-43b9338a720b.png +0 -0
  22. package/dist/attachments/6d1766b1-05e4-4b04-b3c8-1c25e9d182a1.png +0 -0
  23. package/dist/attachments/782b077b-06e3-484b-baf5-33e7160234ed.png +0 -0
  24. package/dist/attachments/7ad638b2-1f56-4d93-9ad8-b40346e0650f.jpg +0 -0
  25. package/dist/attachments/89f6fb15-e652-4111-a60c-baa414659052.png +0 -0
  26. package/dist/attachments/8a88b14f-442f-45fb-b01d-e51bab8f800d.mp3 +0 -0
  27. package/dist/attachments/92292034-9cf6-4f26-8d77-fddca3deb638.png +0 -0
  28. package/dist/attachments/92c2b414-d33d-4d93-bcb6-013da7bec9a4.jpg +0 -0
  29. package/dist/attachments/9664f69e-3c05-45ca-9a52-f2d0b9f9bf7e.jpg +0 -0
  30. package/dist/attachments/977d28c1-43c0-40e0-95e3-defe0f41afe8.jpg +0 -0
  31. package/dist/attachments/9df40f1a-c6e1-4177-8a03-06757a30b19e.png +0 -0
  32. package/dist/attachments/a68e6815-6163-4421-a70f-34493aa9a217.jpg +0 -0
  33. package/dist/attachments/aab32fea-6d99-47ec-ab1f-2340f31312eb.jpg +0 -0
  34. package/dist/attachments/ab403224-2fb1-49c1-8738-ea194ab65d44.png +0 -0
  35. package/dist/attachments/ac3da190-d6ee-4038-a673-8b893035a687.png +0 -0
  36. package/dist/attachments/af02be9c-87f7-4c5a-9969-7db32039bb58.png +0 -0
  37. package/dist/attachments/b011d42a-00e5-4f77-86bc-08da6112e6e1.mp3 +0 -0
  38. package/dist/attachments/b7d7df40-c627-4b1f-9b09-167b88545c25.mp3 +0 -0
  39. package/dist/attachments/c5e9bf09-a718-422c-bcb3-94c173e3755b.mp3 +0 -0
  40. package/dist/attachments/d5449e13-1995-44ba-9392-ecbfe5f9876f.jpg +0 -0
  41. package/dist/attachments/ea0069f5-01cf-4ea1-985e-3a1e426399c3.png +0 -0
  42. package/dist/attachments/f3989ff2-7b70-4a80-a896-74a6b197f7d8.png +0 -0
  43. package/dist/attachments/f64a4a14-e3aa-4eed-a8d9-1603f04baa5b.jpg +0 -0
  44. package/dist/src/http/handlers/device-token.d.ts +0 -2
  45. package/dist/src/http/handlers/device-token.js +0 -43
  46. package/dist/src/http/handlers/import.d.ts +0 -7
  47. package/dist/src/http/handlers/import.js +0 -69
  48. package/dist/src/http/handlers/info.d.ts +0 -2
  49. package/dist/src/http/handlers/info.js +0 -13
  50. package/dist/src/http/handlers/messages-list.d.ts +0 -7
  51. package/dist/src/http/handlers/messages-list.js +0 -44
  52. package/dist/src/http/handlers/pair.d.ts +0 -2
  53. package/dist/src/http/handlers/pair.js +0 -39
  54. package/dist/src/http/handlers/sessions-list.d.ts +0 -8
  55. package/dist/src/http/handlers/sessions-list.js +0 -24
  56. package/dist/src/http/handlers/sessions-messages-get.d.ts +0 -2
  57. package/dist/src/http/handlers/sessions-messages-get.js +0 -55
  58. package/dist/src/http/handlers/sessions-messages-post.d.ts +0 -2
  59. package/dist/src/http/handlers/sessions-messages-post.js +0 -92
  60. package/dist/src/http/handlers/sessions-messages.d.ts +0 -2
  61. package/dist/src/http/handlers/sessions-messages.js +0 -135
  62. package/dist/src/http/handlers/sync.d.ts +0 -7
  63. package/dist/src/http/handlers/sync.js +0 -56
  64. package/dist/src/push/apns.d.ts +0 -15
  65. package/dist/src/push/apns.js +0 -56
  66. package/dist/src/push/device-tokens.d.ts +0 -3
  67. package/dist/src/push/device-tokens.js +0 -39
  68. package/dist/src/sync/account-identity.d.ts +0 -14
  69. package/dist/src/sync/account-identity.js +0 -101
  70. package/dist/src/sync/archive.d.ts +0 -9
  71. package/dist/src/sync/archive.js +0 -25
  72. package/dist/src/sync/database.d.ts +0 -66
  73. package/dist/src/sync/database.js +0 -364
  74. package/dist/src/sync/init.d.ts +0 -3
  75. package/dist/src/sync/init.js +0 -14
  76. package/dist/src/sync/installation-id.d.ts +0 -1
  77. package/dist/src/sync/installation-id.js +0 -41
  78. package/dist/src/sync/message-accumulator.d.ts +0 -29
  79. package/dist/src/sync/message-accumulator.js +0 -188
  80. package/dist/src/sync/message-store.d.ts +0 -68
  81. package/dist/src/sync/message-store.js +0 -262
  82. package/dist/src/sync/push-store.d.ts +0 -5
  83. package/dist/src/sync/push-store.js +0 -54
  84. package/dist/src/sync/session-key.d.ts +0 -12
  85. package/dist/src/sync/session-key.js +0 -47
  86. package/dist/src/sync/sync-state.d.ts +0 -5
  87. package/dist/src/sync/sync-state.js +0 -54
  88. package/dist/src/sync/transcript-archive.d.ts +0 -13
  89. package/dist/src/sync/transcript-archive.js +0 -37
  90. package/dist/src/sync/transcript-store.d.ts +0 -35
  91. package/dist/src/sync/transcript-store.js +0 -221
  92. package/dist/src/sync/translate.d.ts +0 -42
  93. package/dist/src/sync/translate.js +0 -171
@@ -1,2 +0,0 @@
1
- import type { IncomingMessage, ServerResponse } from "node:http";
2
- export declare function handleDeviceToken(req: IncomingMessage, res: ServerResponse): Promise<boolean>;
@@ -1,43 +0,0 @@
1
- import { extractBearerToken } from "../middleware/auth.js";
2
- import { readJsonBody } from "../middleware/body.js";
3
- import { saveToken } from "../../push/device-tokens.js";
4
- export async function handleDeviceToken(req, res) {
5
- if (req.method !== "POST") {
6
- res.statusCode = 405;
7
- res.setHeader("Content-Type", "application/json");
8
- res.end(JSON.stringify({ error: "Method Not Allowed" }));
9
- return true;
10
- }
11
- if (!extractBearerToken(req)) {
12
- res.statusCode = 401;
13
- res.setHeader("Content-Type", "application/json");
14
- res.end(JSON.stringify({ error: "Unauthorized: bearer token mismatch" }));
15
- return true;
16
- }
17
- let body;
18
- try {
19
- body = await readJsonBody(req);
20
- }
21
- catch {
22
- body = null;
23
- }
24
- if (!body) {
25
- res.statusCode = 400;
26
- res.setHeader("Content-Type", "application/json");
27
- res.end(JSON.stringify({ error: "Invalid JSON body" }));
28
- return true;
29
- }
30
- const deviceId = typeof body.deviceId === "string" && body.deviceId.trim() ? body.deviceId.trim() : "";
31
- const token = typeof body.token === "string" && body.token.trim() ? body.token.trim() : "";
32
- if (!deviceId || !token) {
33
- res.statusCode = 400;
34
- res.setHeader("Content-Type", "application/json");
35
- res.end(JSON.stringify({ error: "deviceId and token are required" }));
36
- return true;
37
- }
38
- saveToken(deviceId, token);
39
- res.statusCode = 200;
40
- res.setHeader("Content-Type", "application/json");
41
- res.end(JSON.stringify({ ok: true }));
42
- return true;
43
- }
@@ -1,7 +0,0 @@
1
- /**
2
- * POST /friday-next/sessions/{sessionKey}/import
3
- *
4
- * First-time bulk import. Rejects if session already has data (409 Conflict).
5
- */
6
- import type { IncomingMessage, ServerResponse } from "node:http";
7
- export declare function handleImport(req: IncomingMessage, res: ServerResponse): Promise<boolean>;
@@ -1,69 +0,0 @@
1
- import { extractBearerToken } from "../middleware/auth.js";
2
- import { readJsonBody } from "../middleware/body.js";
3
- import { importMessages, ConflictError } from "../../sync/message-store.js";
4
- import { toSessionStoreKey } from "../../session/session-manager.js";
5
- export async function handleImport(req, res) {
6
- if (req.method !== "POST") {
7
- res.statusCode = 405;
8
- res.setHeader("Content-Type", "application/json");
9
- res.end(JSON.stringify({ error: "Method Not Allowed" }));
10
- return true;
11
- }
12
- const token = extractBearerToken(req);
13
- if (!token) {
14
- res.statusCode = 401;
15
- res.setHeader("Content-Type", "application/json");
16
- res.end(JSON.stringify({ error: "Unauthorized" }));
17
- return true;
18
- }
19
- const url = new URL(req.url ?? "/", "http://localhost");
20
- const pathname = url.pathname;
21
- const prefix = "/friday-next/sessions/";
22
- const suffix = "/import";
23
- if (!pathname.startsWith(prefix) || !pathname.endsWith(suffix)) {
24
- res.statusCode = 400;
25
- res.setHeader("Content-Type", "application/json");
26
- res.end(JSON.stringify({ error: "Missing sessionKey in URL path" }));
27
- return true;
28
- }
29
- const rawKey = pathname.slice(prefix.length, pathname.length - suffix.length);
30
- const sessionKey = toSessionStoreKey(decodeURIComponent(rawKey));
31
- const body = await readJsonBody(req, 50 * 1024 * 1024); // 50 MB for bulk import
32
- if (!body) {
33
- res.statusCode = 400;
34
- res.setHeader("Content-Type", "application/json");
35
- res.end(JSON.stringify({ error: "Invalid JSON body" }));
36
- return true;
37
- }
38
- const deviceId = typeof body.deviceId === "string" ? body.deviceId.trim() : "";
39
- if (!deviceId) {
40
- res.statusCode = 400;
41
- res.setHeader("Content-Type", "application/json");
42
- res.end(JSON.stringify({ error: "Missing required field: deviceId" }));
43
- return true;
44
- }
45
- const messages = Array.isArray(body.messages) ? body.messages : [];
46
- if (messages.length === 0) {
47
- res.statusCode = 400;
48
- res.setHeader("Content-Type", "application/json");
49
- res.end(JSON.stringify({ error: "Missing required field: messages (non-empty array)" }));
50
- return true;
51
- }
52
- try {
53
- const result = importMessages({ sessionKey, deviceId, messages });
54
- res.statusCode = 200;
55
- res.setHeader("Content-Type", "application/json");
56
- res.end(JSON.stringify(result));
57
- }
58
- catch (err) {
59
- if (err instanceof ConflictError) {
60
- res.statusCode = 409;
61
- res.setHeader("Content-Type", "application/json");
62
- res.end(JSON.stringify({ error: err.message }));
63
- }
64
- else {
65
- throw err;
66
- }
67
- }
68
- return true;
69
- }
@@ -1,2 +0,0 @@
1
- import type { IncomingMessage, ServerResponse } from "node:http";
2
- export declare function handleInfo(req: IncomingMessage, res: ServerResponse): Promise<boolean>;
@@ -1,13 +0,0 @@
1
- import { getInstallationId } from "../../sync/installation-id.js";
2
- export async function handleInfo(req, res) {
3
- if (req.method !== "GET") {
4
- res.statusCode = 405;
5
- res.setHeader("Content-Type", "application/json");
6
- res.end(JSON.stringify({ error: "Method Not Allowed" }));
7
- return true;
8
- }
9
- res.statusCode = 200;
10
- res.setHeader("Content-Type", "application/json");
11
- res.end(JSON.stringify({ ok: true, channel: "friday-next", version: "v2", installationId: getInstallationId() }));
12
- return true;
13
- }
@@ -1,7 +0,0 @@
1
- /**
2
- * GET /friday-next/sessions/{sessionKey}/messages?before=&limit=
3
- *
4
- * Paginated pull of historical messages for a session.
5
- */
6
- import type { IncomingMessage, ServerResponse } from "node:http";
7
- export declare function handleMessagesList(req: IncomingMessage, res: ServerResponse): Promise<boolean>;
@@ -1,44 +0,0 @@
1
- import { extractBearerToken } from "../middleware/auth.js";
2
- import { getMessagesBefore } from "../../sync/message-store.js";
3
- import { toSessionStoreKey } from "../../session/session-manager.js";
4
- export async function handleMessagesList(req, res) {
5
- if (req.method !== "GET") {
6
- res.statusCode = 405;
7
- res.setHeader("Content-Type", "application/json");
8
- res.end(JSON.stringify({ error: "Method Not Allowed" }));
9
- return true;
10
- }
11
- const token = extractBearerToken(req);
12
- if (!token) {
13
- res.statusCode = 401;
14
- res.setHeader("Content-Type", "application/json");
15
- res.end(JSON.stringify({ error: "Unauthorized" }));
16
- return true;
17
- }
18
- const url = new URL(req.url ?? "/", "http://localhost");
19
- const pathname = url.pathname;
20
- const prefix = "/friday-next/sessions/";
21
- const suffix = "/messages";
22
- if (!pathname.startsWith(prefix) || !pathname.endsWith(suffix)) {
23
- res.statusCode = 400;
24
- res.setHeader("Content-Type", "application/json");
25
- res.end(JSON.stringify({ error: "Missing sessionKey in URL path" }));
26
- return true;
27
- }
28
- const rawKey = pathname.slice(prefix.length, pathname.length - suffix.length);
29
- const sessionKey = toSessionStoreKey(decodeURIComponent(rawKey));
30
- const before = url.searchParams.get("before") ?? undefined;
31
- const limitRaw = url.searchParams.get("limit");
32
- const limit = limitRaw ? parseInt(limitRaw, 10) : 25;
33
- if (limitRaw && (isNaN(limit) || limit < 1 || limit > 50)) {
34
- res.statusCode = 400;
35
- res.setHeader("Content-Type", "application/json");
36
- res.end(JSON.stringify({ error: "Invalid limit (1-50)" }));
37
- return true;
38
- }
39
- const result = getMessagesBefore(sessionKey, before, limit);
40
- res.statusCode = 200;
41
- res.setHeader("Content-Type", "application/json");
42
- res.end(JSON.stringify(result));
43
- return true;
44
- }
@@ -1,2 +0,0 @@
1
- import type { IncomingMessage, ServerResponse } from "node:http";
2
- export declare function handlePair(req: IncomingMessage, res: ServerResponse): Promise<boolean>;
@@ -1,39 +0,0 @@
1
- import { getOrCreateAccountId } from "../../sync/account-identity.js";
2
- import { getInstallationId } from "../../sync/installation-id.js";
3
- import { extractBearerToken } from "../middleware/auth.js";
4
- import { readJsonBody } from "../middleware/body.js";
5
- export async function handlePair(req, res) {
6
- if (req.method !== "POST") {
7
- res.statusCode = 405;
8
- res.setHeader("Content-Type", "application/json");
9
- res.end(JSON.stringify({ error: "Method Not Allowed" }));
10
- return true;
11
- }
12
- if (!extractBearerToken(req)) {
13
- res.statusCode = 401;
14
- res.setHeader("Content-Type", "application/json");
15
- res.end(JSON.stringify({ error: "Unauthorized: bearer token mismatch" }));
16
- return true;
17
- }
18
- const body = await readJsonBody(req);
19
- const deviceId = typeof body?.deviceId === "string" ? body.deviceId.trim() : "";
20
- if (!deviceId) {
21
- res.statusCode = 400;
22
- res.setHeader("Content-Type", "application/json");
23
- res.end(JSON.stringify({ error: "Missing required field: deviceId" }));
24
- return true;
25
- }
26
- const claimAccountId = typeof body?.claimAccountId === "string" ? body.claimAccountId.trim() : undefined;
27
- const claim = body?.claim && typeof body.claim === "object" && !Array.isArray(body.claim)
28
- ? body.claim
29
- : {};
30
- const result = getOrCreateAccountId({
31
- deviceId,
32
- claimAccountId,
33
- claimConfirmed: claim.confirmed === true,
34
- });
35
- res.statusCode = 200;
36
- res.setHeader("Content-Type", "application/json");
37
- res.end(JSON.stringify({ ok: true, installationId: getInstallationId(), ...result }));
38
- return true;
39
- }
@@ -1,8 +0,0 @@
1
- /**
2
- * GET /friday-next/sessions?deviceId=...
3
- *
4
- * List sessions. If deviceId is provided, filters by device.
5
- * If deviceId is omitted, returns all sessions (for app reinstall recovery when deviceId changed).
6
- */
7
- import type { IncomingMessage, ServerResponse } from "node:http";
8
- export declare function handleSessionsList(req: IncomingMessage, res: ServerResponse): Promise<boolean>;
@@ -1,24 +0,0 @@
1
- import { extractBearerToken } from "../middleware/auth.js";
2
- import { listSessions, listAllSessionsList } from "../../sync/message-store.js";
3
- export async function handleSessionsList(req, res) {
4
- if (req.method !== "GET") {
5
- res.statusCode = 405;
6
- res.setHeader("Content-Type", "application/json");
7
- res.end(JSON.stringify({ error: "Method Not Allowed" }));
8
- return true;
9
- }
10
- const token = extractBearerToken(req);
11
- if (!token) {
12
- res.statusCode = 401;
13
- res.setHeader("Content-Type", "application/json");
14
- res.end(JSON.stringify({ error: "Unauthorized" }));
15
- return true;
16
- }
17
- const url = new URL(req.url ?? "/", "http://localhost");
18
- const deviceId = url.searchParams.get("deviceId")?.trim();
19
- const sessions = deviceId ? listSessions(deviceId) : listAllSessionsList();
20
- res.statusCode = 200;
21
- res.setHeader("Content-Type", "application/json");
22
- res.end(JSON.stringify({ sessions }));
23
- return true;
24
- }
@@ -1,2 +0,0 @@
1
- import type { IncomingMessage, ServerResponse } from "node:http";
2
- export declare function handleSessionMessagesGet(req: IncomingMessage, res: ServerResponse): Promise<boolean>;
@@ -1,55 +0,0 @@
1
- import { resolveAccountIdFromRequest } from "../../sync/account-identity.js";
2
- import { getTranscriptStore } from "../../sync/transcript-store.js";
3
- import { extractBearerToken } from "../middleware/auth.js";
4
- function sessionKeyFromPath(pathname) {
5
- const prefix = "/friday-next/sessions/";
6
- const suffix = "/messages";
7
- if (!pathname.startsWith(prefix) || !pathname.endsWith(suffix))
8
- return "";
9
- return decodeURIComponent(pathname.slice(prefix.length, -suffix.length));
10
- }
11
- export async function handleSessionMessagesGet(req, res) {
12
- if (req.method !== "GET") {
13
- res.statusCode = 405;
14
- res.setHeader("Content-Type", "application/json");
15
- res.end(JSON.stringify({ error: "Method Not Allowed" }));
16
- return true;
17
- }
18
- if (!extractBearerToken(req)) {
19
- res.statusCode = 401;
20
- res.setHeader("Content-Type", "application/json");
21
- res.end(JSON.stringify({ error: "Unauthorized: bearer token mismatch" }));
22
- return true;
23
- }
24
- const accountId = resolveAccountIdFromRequest(req);
25
- if (!accountId) {
26
- res.statusCode = 403;
27
- res.setHeader("Content-Type", "application/json");
28
- res.end(JSON.stringify({ error: "Unknown or missing X-Account-Id" }));
29
- return true;
30
- }
31
- const url = new URL(req.url ?? "/", "http://localhost");
32
- const sessionKey = sessionKeyFromPath(url.pathname);
33
- if (!sessionKey) {
34
- res.statusCode = 400;
35
- res.setHeader("Content-Type", "application/json");
36
- res.end(JSON.stringify({ error: "Missing session key in path" }));
37
- return true;
38
- }
39
- if (!sessionKey.includes(`friday:account:${accountId}:`) && !sessionKey.includes(`friday%3Aaccount%3A${accountId}%3A`)) {
40
- const legacyAllowed = sessionKey.startsWith("friday:direct:") || sessionKey.startsWith("agent:main:friday:direct:");
41
- if (!legacyAllowed) {
42
- res.statusCode = 403;
43
- res.setHeader("Content-Type", "application/json");
44
- res.end(JSON.stringify({ error: "Session does not belong to account" }));
45
- return true;
46
- }
47
- }
48
- const since = Number.parseInt(url.searchParams.get("since") ?? "0", 10);
49
- const limit = Number.parseInt(url.searchParams.get("limit") ?? "200", 10);
50
- const events = getTranscriptStore().readSince(accountId, sessionKey, Number.isFinite(since) ? since : 0, limit);
51
- res.statusCode = 200;
52
- res.setHeader("Content-Type", "application/json");
53
- res.end(JSON.stringify({ ok: true, events, nextSince: events.at(-1)?.seq ?? since }));
54
- return true;
55
- }
@@ -1,2 +0,0 @@
1
- import type { IncomingMessage, ServerResponse } from "node:http";
2
- export declare function handleSessionMessagesPost(req: IncomingMessage, res: ServerResponse): Promise<boolean>;
@@ -1,92 +0,0 @@
1
- import { resolveAccountIdFromRequest } from "../../sync/account-identity.js";
2
- import { getTranscriptStore } from "../../sync/transcript-store.js";
3
- import { extractBearerToken } from "../middleware/auth.js";
4
- import { readJsonBody } from "../middleware/body.js";
5
- const allowedKinds = new Set([
6
- "user_message",
7
- "assistant_message",
8
- "thought_event",
9
- "tool_event",
10
- "outbound_text",
11
- "outbound_media",
12
- "sync_upsert",
13
- ]);
14
- function sessionKeyFromPath(pathname) {
15
- const prefix = "/friday-next/sessions/";
16
- const suffix = "/messages";
17
- if (!pathname.startsWith(prefix) || !pathname.endsWith(suffix))
18
- return "";
19
- return decodeURIComponent(pathname.slice(prefix.length, -suffix.length));
20
- }
21
- function parseEvent(raw, accountId) {
22
- if (!raw || typeof raw !== "object" || Array.isArray(raw))
23
- return null;
24
- const obj = raw;
25
- const kind = typeof obj.kind === "string" && allowedKinds.has(obj.kind)
26
- ? obj.kind
27
- : null;
28
- if (!kind)
29
- return null;
30
- const payload = obj.payload && typeof obj.payload === "object" && !Array.isArray(obj.payload)
31
- ? obj.payload
32
- : null;
33
- if (!payload)
34
- return null;
35
- return {
36
- eventId: typeof obj.eventId === "string" ? obj.eventId.trim() : undefined,
37
- clientId: typeof obj.clientId === "string" && obj.clientId.trim() ? obj.clientId.trim() : accountId,
38
- kind,
39
- payload,
40
- ts: typeof obj.ts === "number" ? obj.ts : undefined,
41
- lamportTs: typeof obj.lamportTs === "number" ? obj.lamportTs : undefined,
42
- };
43
- }
44
- export async function handleSessionMessagesPost(req, res) {
45
- if (req.method !== "POST") {
46
- res.statusCode = 405;
47
- res.setHeader("Content-Type", "application/json");
48
- res.end(JSON.stringify({ error: "Method Not Allowed" }));
49
- return true;
50
- }
51
- if (!extractBearerToken(req)) {
52
- res.statusCode = 401;
53
- res.setHeader("Content-Type", "application/json");
54
- res.end(JSON.stringify({ error: "Unauthorized: bearer token mismatch" }));
55
- return true;
56
- }
57
- const accountId = resolveAccountIdFromRequest(req);
58
- if (!accountId) {
59
- res.statusCode = 403;
60
- res.setHeader("Content-Type", "application/json");
61
- res.end(JSON.stringify({ error: "Unknown or missing X-Account-Id" }));
62
- return true;
63
- }
64
- const url = new URL(req.url ?? "/", "http://localhost");
65
- const sessionKey = sessionKeyFromPath(url.pathname);
66
- if (!sessionKey) {
67
- res.statusCode = 400;
68
- res.setHeader("Content-Type", "application/json");
69
- res.end(JSON.stringify({ error: "Missing session key in path" }));
70
- return true;
71
- }
72
- if (!sessionKey.includes(`friday:account:${accountId}:`) && !sessionKey.startsWith("friday:direct:")) {
73
- res.statusCode = 403;
74
- res.setHeader("Content-Type", "application/json");
75
- res.end(JSON.stringify({ error: "Session does not belong to account" }));
76
- return true;
77
- }
78
- const body = await readJsonBody(req, 8 * 1024 * 1024);
79
- const rawEvents = Array.isArray(body?.events) ? body.events : [];
80
- const events = rawEvents.map((e) => parseEvent(e, accountId)).filter((e) => e !== null);
81
- if (events.length !== rawEvents.length) {
82
- res.statusCode = 400;
83
- res.setHeader("Content-Type", "application/json");
84
- res.end(JSON.stringify({ error: "Invalid transcript event batch" }));
85
- return true;
86
- }
87
- const appended = getTranscriptStore().appendEvents(accountId, sessionKey, events);
88
- res.statusCode = 200;
89
- res.setHeader("Content-Type", "application/json");
90
- res.end(JSON.stringify({ ok: true, appended: appended.length, events: appended }));
91
- return true;
92
- }
@@ -1,2 +0,0 @@
1
- import type { IncomingMessage, ServerResponse } from "node:http";
2
- export declare function handleSessionsMessages(req: IncomingMessage, res: ServerResponse): Promise<boolean>;
@@ -1,135 +0,0 @@
1
- import { resolveFridayNextConfig } from "../../config.js";
2
- import { getHostOpenClawConfigSnapshot } from "../../host-config.js";
3
- import { getFridayNextRuntime } from "../../runtime.js";
4
- import { accountExists, requireAccountId } from "../../sync/account-identity.js";
5
- import { sessionBelongsToAccount } from "../../sync/session-key.js";
6
- import { readSince, upsertEvents } from "../../sync/transcript-store.js";
7
- import { extractBearerToken } from "../middleware/auth.js";
8
- import { readJsonBody } from "../middleware/body.js";
9
- const VALID_KINDS = new Set([
10
- "user_message",
11
- "assistant_message",
12
- "thought_event",
13
- "tool_event",
14
- "outbound_text",
15
- "outbound_media",
16
- ]);
17
- function decodeSessionKeyFromPath(pathname) {
18
- const prefix = "/friday-next/sessions/";
19
- if (!pathname.startsWith(prefix))
20
- return null;
21
- const rest = pathname.slice(prefix.length);
22
- const messagesIdx = rest.indexOf("/messages");
23
- if (messagesIdx < 0)
24
- return null;
25
- const encoded = rest.slice(0, messagesIdx);
26
- if (!encoded)
27
- return null;
28
- try {
29
- return decodeURIComponent(encoded);
30
- }
31
- catch {
32
- return null;
33
- }
34
- }
35
- export async function handleSessionsMessages(req, res) {
36
- const url = new URL(req.url ?? "/", "http://localhost");
37
- const sessionKey = decodeSessionKeyFromPath(url.pathname);
38
- if (!sessionKey)
39
- return false;
40
- if (!extractBearerToken(req)) {
41
- res.statusCode = 401;
42
- res.setHeader("Content-Type", "application/json");
43
- res.end(JSON.stringify({ error: "Unauthorized: bearer token mismatch" }));
44
- return true;
45
- }
46
- const accountId = requireAccountId(req);
47
- if (!accountId) {
48
- res.statusCode = 400;
49
- res.setHeader("Content-Type", "application/json");
50
- res.end(JSON.stringify({ error: "Missing or invalid X-Account-Id header" }));
51
- return true;
52
- }
53
- const cfg = resolveFridayNextConfig(getHostOpenClawConfigSnapshot(getFridayNextRuntime().config));
54
- if (!accountExists(cfg.historyDir, accountId)) {
55
- res.statusCode = 403;
56
- res.setHeader("Content-Type", "application/json");
57
- res.end(JSON.stringify({ error: "Unknown account" }));
58
- return true;
59
- }
60
- if (!sessionBelongsToAccount(sessionKey, accountId)) {
61
- res.statusCode = 403;
62
- res.setHeader("Content-Type", "application/json");
63
- res.end(JSON.stringify({ error: "Session does not belong to account" }));
64
- return true;
65
- }
66
- if (req.method === "GET") {
67
- const since = Math.max(0, Number.parseInt(url.searchParams.get("since") ?? "0", 10) || 0);
68
- const limit = Math.min(cfg.syncMaxBatchSize, Math.max(1, Number.parseInt(url.searchParams.get("limit") ?? String(cfg.syncMaxBatchSize), 10) || cfg.syncMaxBatchSize));
69
- const direction = url.searchParams.get("direction") === "desc" ? "desc" : "asc";
70
- const events = readSince({
71
- accountId,
72
- sessionKey,
73
- sinceSeq: since,
74
- limit,
75
- direction,
76
- historyDir: cfg.historyDir,
77
- });
78
- res.statusCode = 200;
79
- res.setHeader("Content-Type", "application/json");
80
- res.end(JSON.stringify({ ok: true, accountId, sessionKey, events }));
81
- return true;
82
- }
83
- if (req.method === "POST") {
84
- const body = (await readJsonBody(req));
85
- const rawEvents = Array.isArray(body?.events) ? body.events : [];
86
- if (rawEvents.length > cfg.syncMaxBatchSize) {
87
- res.statusCode = 400;
88
- res.setHeader("Content-Type", "application/json");
89
- res.end(JSON.stringify({ error: `Too many events (max ${cfg.syncMaxBatchSize})` }));
90
- return true;
91
- }
92
- const parsed = rawEvents
93
- .map((item) => {
94
- if (!item || typeof item !== "object" || Array.isArray(item))
95
- return null;
96
- const o = item;
97
- const kind = typeof o.kind === "string" ? o.kind : "";
98
- if (!VALID_KINDS.has(kind))
99
- return null;
100
- const clientId = typeof o.clientId === "string" ? o.clientId : accountId;
101
- const payload = o.payload && typeof o.payload === "object" && !Array.isArray(o.payload)
102
- ? o.payload
103
- : {};
104
- return {
105
- kind: kind,
106
- clientId,
107
- payload,
108
- messageId: typeof o.messageId === "string" ? o.messageId : undefined,
109
- lamport: typeof o.lamport === "number" ? o.lamport : undefined,
110
- ts: typeof o.ts === "number" ? o.ts : undefined,
111
- };
112
- })
113
- .filter((x) => x !== null);
114
- const result = upsertEvents({
115
- accountId,
116
- sessionKey,
117
- events: parsed,
118
- historyDir: cfg.historyDir,
119
- });
120
- res.statusCode = 200;
121
- res.setHeader("Content-Type", "application/json");
122
- res.end(JSON.stringify({
123
- ok: true,
124
- accountId,
125
- sessionKey,
126
- applied: result.applied,
127
- events: result.events,
128
- }));
129
- return true;
130
- }
131
- res.statusCode = 405;
132
- res.setHeader("Content-Type", "application/json");
133
- res.end(JSON.stringify({ error: "Method Not Allowed" }));
134
- return true;
135
- }
@@ -1,7 +0,0 @@
1
- /**
2
- * POST /friday-next/sessions/{sessionKey}/sync
3
- *
4
- * Bidirectional incremental sync: pull remote messages (primary) + push local messages (offline catch-up).
5
- */
6
- import type { IncomingMessage, ServerResponse } from "node:http";
7
- export declare function handleSync(req: IncomingMessage, res: ServerResponse): Promise<boolean>;