@syengup/friday-channel-next 0.0.35 → 0.0.37

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 (185) hide show
  1. package/dist/attachments/0768c9b1-53b0-44df-83e8-be15c4ea188f.jpg +0 -0
  2. package/dist/attachments/0a379d01-116b-4da1-bf15-77cb2cbb0093.jpg +0 -0
  3. package/dist/attachments/181caab2-64a7-4004-a057-225a144f949e.mp3 +0 -0
  4. package/dist/attachments/19662331-e527-47d2-bc0e-0e19a7a91419.jpg +0 -0
  5. package/dist/attachments/26a23b2b-52df-4572-a5e1-15b34fb87e44.jpg +0 -0
  6. package/dist/attachments/2f9282c5-8db4-4c4a-a060-e65104f6f9ff.jpg +0 -0
  7. package/dist/attachments/3929ec3d-ea15-4de6-96bc-97e8b0b658a7.jpg +0 -0
  8. package/dist/attachments/403c0cbc-4e3c-4146-a3be-ff3746ee7cda.jpg +0 -0
  9. package/dist/attachments/441977f5-0f7b-4aa2-841a-1d63e787ea53.jpg +0 -0
  10. package/dist/attachments/453e8aa2-76e3-498d-8d6f-d7b96d6bf45b.jpg +0 -0
  11. package/dist/attachments/538cde71-d26e-4d3d-b901-e8dd905e668c.mp3 +0 -0
  12. package/dist/attachments/55c7f628-4ba2-4252-aa4b-4f3eb6045a8a.mp3 +0 -0
  13. package/dist/attachments/5f7683f5-8194-4698-b077-31d209525379.jpg +0 -0
  14. package/dist/attachments/60614a35-8f44-4197-b783-2f58f5a72ac8.jpeg +0 -0
  15. package/dist/attachments/62830489-8814-48b1-851c-3845e514f35e.mp3 +0 -0
  16. package/dist/attachments/66f4a62d-1531-4f38-a531-7456f9edf221.png +0 -0
  17. package/dist/attachments/6735d749-769e-483a-9b84-43b9338a720b.png +0 -0
  18. package/dist/attachments/6d1766b1-05e4-4b04-b3c8-1c25e9d182a1.png +0 -0
  19. package/dist/attachments/782b077b-06e3-484b-baf5-33e7160234ed.png +0 -0
  20. package/dist/attachments/7ad638b2-1f56-4d93-9ad8-b40346e0650f.jpg +0 -0
  21. package/dist/attachments/89f6fb15-e652-4111-a60c-baa414659052.png +0 -0
  22. package/dist/attachments/8a88b14f-442f-45fb-b01d-e51bab8f800d.mp3 +0 -0
  23. package/dist/attachments/92292034-9cf6-4f26-8d77-fddca3deb638.png +0 -0
  24. package/dist/attachments/92c2b414-d33d-4d93-bcb6-013da7bec9a4.jpg +0 -0
  25. package/dist/attachments/9664f69e-3c05-45ca-9a52-f2d0b9f9bf7e.jpg +0 -0
  26. package/dist/attachments/977d28c1-43c0-40e0-95e3-defe0f41afe8.jpg +0 -0
  27. package/dist/attachments/9df40f1a-c6e1-4177-8a03-06757a30b19e.png +0 -0
  28. package/dist/attachments/a68e6815-6163-4421-a70f-34493aa9a217.jpg +0 -0
  29. package/dist/attachments/aab32fea-6d99-47ec-ab1f-2340f31312eb.jpg +0 -0
  30. package/dist/attachments/ab403224-2fb1-49c1-8738-ea194ab65d44.png +0 -0
  31. package/dist/attachments/ac3da190-d6ee-4038-a673-8b893035a687.png +0 -0
  32. package/dist/attachments/af02be9c-87f7-4c5a-9969-7db32039bb58.png +0 -0
  33. package/dist/attachments/b011d42a-00e5-4f77-86bc-08da6112e6e1.mp3 +0 -0
  34. package/dist/attachments/b7d7df40-c627-4b1f-9b09-167b88545c25.mp3 +0 -0
  35. package/dist/attachments/c5e9bf09-a718-422c-bcb3-94c173e3755b.mp3 +0 -0
  36. package/dist/attachments/d5449e13-1995-44ba-9392-ecbfe5f9876f.jpg +0 -0
  37. package/dist/attachments/ea0069f5-01cf-4ea1-985e-3a1e426399c3.png +0 -0
  38. package/dist/attachments/f3989ff2-7b70-4a80-a896-74a6b197f7d8.png +0 -0
  39. package/dist/attachments/f64a4a14-e3aa-4eed-a8d9-1603f04baa5b.jpg +0 -0
  40. package/dist/index.d.ts +4 -0
  41. package/dist/index.js +176 -0
  42. package/dist/src/agent/abort-run.d.ts +1 -0
  43. package/dist/src/agent/abort-run.js +11 -0
  44. package/dist/src/agent/active-runs.d.ts +9 -0
  45. package/dist/src/agent/active-runs.js +20 -0
  46. package/dist/src/agent/dispatch-bridge.d.ts +5 -0
  47. package/dist/src/agent/dispatch-bridge.js +12 -0
  48. package/dist/src/agent/media-bridge.d.ts +4 -0
  49. package/dist/src/agent/media-bridge.js +21 -0
  50. package/dist/src/agent/subagent-registry.d.ts +68 -0
  51. package/dist/src/agent/subagent-registry.js +142 -0
  52. package/dist/src/agent-forward-runtime.d.ts +17 -0
  53. package/dist/src/agent-forward-runtime.js +16 -0
  54. package/dist/src/agent-run-context-bridge.d.ts +13 -0
  55. package/dist/src/agent-run-context-bridge.js +23 -0
  56. package/dist/src/channel-actions.d.ts +13 -0
  57. package/dist/src/channel-actions.js +101 -0
  58. package/dist/src/channel.d.ts +6 -0
  59. package/dist/src/channel.js +248 -0
  60. package/dist/src/collect-message-media-paths.d.ts +11 -0
  61. package/dist/src/collect-message-media-paths.js +143 -0
  62. package/dist/src/config.d.ts +15 -0
  63. package/dist/src/config.js +39 -0
  64. package/dist/src/friday-inbound-stats.d.ts +2 -0
  65. package/dist/src/friday-inbound-stats.js +8 -0
  66. package/dist/src/friday-session.d.ts +40 -0
  67. package/dist/src/friday-session.js +395 -0
  68. package/dist/src/host-config.d.ts +1 -0
  69. package/dist/src/host-config.js +15 -0
  70. package/dist/src/http/handlers/cancel.d.ts +2 -0
  71. package/dist/src/http/handlers/cancel.js +33 -0
  72. package/dist/src/http/handlers/device-approve.d.ts +2 -0
  73. package/dist/src/http/handlers/device-approve.js +125 -0
  74. package/dist/src/http/handlers/device-token.d.ts +2 -0
  75. package/dist/src/http/handlers/device-token.js +43 -0
  76. package/dist/src/http/handlers/files-download.d.ts +10 -0
  77. package/dist/src/http/handlers/files-download.js +210 -0
  78. package/dist/src/http/handlers/files-upload.d.ts +8 -0
  79. package/dist/src/http/handlers/files-upload.js +136 -0
  80. package/dist/src/http/handlers/files.d.ts +75 -0
  81. package/dist/src/http/handlers/files.js +305 -0
  82. package/dist/src/http/handlers/import.d.ts +7 -0
  83. package/dist/src/http/handlers/import.js +69 -0
  84. package/dist/src/http/handlers/info.d.ts +2 -0
  85. package/dist/src/http/handlers/info.js +13 -0
  86. package/dist/src/http/handlers/messages-list.d.ts +7 -0
  87. package/dist/src/http/handlers/messages-list.js +44 -0
  88. package/dist/src/http/handlers/messages.d.ts +34 -0
  89. package/dist/src/http/handlers/messages.js +476 -0
  90. package/dist/src/http/handlers/models-list.d.ts +10 -0
  91. package/dist/src/http/handlers/models-list.js +113 -0
  92. package/dist/src/http/handlers/nodes-approve.d.ts +2 -0
  93. package/dist/src/http/handlers/nodes-approve.js +146 -0
  94. package/dist/src/http/handlers/pair.d.ts +2 -0
  95. package/dist/src/http/handlers/pair.js +39 -0
  96. package/dist/src/http/handlers/sessions-delete.d.ts +2 -0
  97. package/dist/src/http/handlers/sessions-delete.js +49 -0
  98. package/dist/src/http/handlers/sessions-list.d.ts +8 -0
  99. package/dist/src/http/handlers/sessions-list.js +24 -0
  100. package/dist/src/http/handlers/sessions-messages-get.d.ts +2 -0
  101. package/dist/src/http/handlers/sessions-messages-get.js +55 -0
  102. package/dist/src/http/handlers/sessions-messages-post.d.ts +2 -0
  103. package/dist/src/http/handlers/sessions-messages-post.js +92 -0
  104. package/dist/src/http/handlers/sessions-messages.d.ts +2 -0
  105. package/dist/src/http/handlers/sessions-messages.js +135 -0
  106. package/dist/src/http/handlers/sessions-settings.d.ts +2 -0
  107. package/dist/src/http/handlers/sessions-settings.js +71 -0
  108. package/dist/src/http/handlers/sse.d.ts +2 -0
  109. package/dist/src/http/handlers/sse.js +70 -0
  110. package/dist/src/http/handlers/status.d.ts +2 -0
  111. package/dist/src/http/handlers/status.js +29 -0
  112. package/dist/src/http/handlers/sync.d.ts +7 -0
  113. package/dist/src/http/handlers/sync.js +56 -0
  114. package/dist/src/http/middleware/auth.d.ts +13 -0
  115. package/dist/src/http/middleware/auth.js +29 -0
  116. package/dist/src/http/middleware/body.d.ts +2 -0
  117. package/dist/src/http/middleware/body.js +24 -0
  118. package/dist/src/http/middleware/cors.d.ts +2 -0
  119. package/dist/src/http/middleware/cors.js +11 -0
  120. package/dist/src/http/server.d.ts +19 -0
  121. package/dist/src/http/server.js +87 -0
  122. package/dist/src/logging.d.ts +7 -0
  123. package/dist/src/logging.js +28 -0
  124. package/dist/src/push/apns.d.ts +15 -0
  125. package/dist/src/push/apns.js +56 -0
  126. package/dist/src/push/device-tokens.d.ts +3 -0
  127. package/dist/src/push/device-tokens.js +39 -0
  128. package/dist/src/run-metadata.d.ts +25 -0
  129. package/dist/src/run-metadata.js +139 -0
  130. package/dist/src/runtime.d.ts +13 -0
  131. package/dist/src/runtime.js +5 -0
  132. package/dist/src/session/session-manager.d.ts +22 -0
  133. package/dist/src/session/session-manager.js +190 -0
  134. package/dist/src/session-usage-snapshot.d.ts +23 -0
  135. package/dist/src/session-usage-snapshot.js +65 -0
  136. package/dist/src/sse/emitter.d.ts +59 -0
  137. package/dist/src/sse/emitter.js +219 -0
  138. package/dist/src/sse/offline-queue.d.ts +26 -0
  139. package/dist/src/sse/offline-queue.js +134 -0
  140. package/dist/src/sync/account-identity.d.ts +14 -0
  141. package/dist/src/sync/account-identity.js +101 -0
  142. package/dist/src/sync/archive.d.ts +9 -0
  143. package/dist/src/sync/archive.js +25 -0
  144. package/dist/src/sync/database.d.ts +66 -0
  145. package/dist/src/sync/database.js +364 -0
  146. package/dist/src/sync/init.d.ts +3 -0
  147. package/dist/src/sync/init.js +14 -0
  148. package/dist/src/sync/installation-id.d.ts +1 -0
  149. package/dist/src/sync/installation-id.js +41 -0
  150. package/dist/src/sync/message-accumulator.d.ts +29 -0
  151. package/dist/src/sync/message-accumulator.js +188 -0
  152. package/dist/src/sync/message-store.d.ts +68 -0
  153. package/dist/src/sync/message-store.js +262 -0
  154. package/dist/src/sync/push-store.d.ts +5 -0
  155. package/dist/src/sync/push-store.js +54 -0
  156. package/dist/src/sync/session-key.d.ts +12 -0
  157. package/dist/src/sync/session-key.js +47 -0
  158. package/dist/src/sync/sync-state.d.ts +5 -0
  159. package/dist/src/sync/sync-state.js +54 -0
  160. package/dist/src/sync/transcript-archive.d.ts +13 -0
  161. package/dist/src/sync/transcript-archive.js +37 -0
  162. package/dist/src/sync/transcript-store.d.ts +35 -0
  163. package/dist/src/sync/transcript-store.js +221 -0
  164. package/dist/src/sync/translate.d.ts +42 -0
  165. package/dist/src/sync/translate.js +171 -0
  166. package/dist/src/vendor/runtime-store.d.ts +26 -0
  167. package/dist/src/vendor/runtime-store.js +60 -0
  168. package/package.json +11 -10
  169. package/src/agent/subagent-registry.ts +195 -0
  170. package/src/channel.ts +6 -4
  171. package/src/e2e/subagent-smoke.e2e.test.ts +223 -0
  172. package/src/e2e/subagent.e2e.test.ts +502 -0
  173. package/src/friday-session.ts +140 -1
  174. package/src/http/handlers/device-approve.test.ts +0 -1
  175. package/src/http/handlers/device-approve.ts +0 -2
  176. package/src/http/handlers/files-download.ts +4 -1
  177. package/src/http/handlers/files.ts +7 -4
  178. package/src/http/handlers/messages.ts +54 -4
  179. package/src/http/handlers/models-list.ts +24 -2
  180. package/src/http/handlers/nodes-approve.test.ts +288 -0
  181. package/src/http/handlers/nodes-approve.ts +189 -0
  182. package/src/http/server.ts +5 -0
  183. package/src/openclaw.d.ts +5 -0
  184. package/src/sse/emitter.ts +1 -1
  185. package/src/test-support/mock-runtime.ts +2 -0
@@ -0,0 +1,364 @@
1
+ import Database from "better-sqlite3";
2
+ import crypto from "node:crypto";
3
+ import { mkdirSync } from "node:fs";
4
+ import path from "node:path";
5
+ import os from "node:os";
6
+ let db = null;
7
+ let _idCounter = 0;
8
+ let _idLastTs = 0;
9
+ /** ULID-like server ID: time-sortable (monotonic within same ms), globally unique. */
10
+ export function generateServerId() {
11
+ const now = Date.now();
12
+ if (now === _idLastTs) {
13
+ _idCounter += 1;
14
+ }
15
+ else {
16
+ _idLastTs = now;
17
+ _idCounter = 0;
18
+ }
19
+ const ts = now.toString(36).padStart(12, "0");
20
+ const seq = _idCounter.toString(36).padStart(4, "0");
21
+ const rand = crypto.randomBytes(8).toString("hex");
22
+ return `msg_${ts}_${seq}_${rand}`;
23
+ }
24
+ const SCHEMA = `
25
+ CREATE TABLE IF NOT EXISTS sessions (
26
+ session_key TEXT PRIMARY KEY,
27
+ device_id TEXT NOT NULL,
28
+ title TEXT NOT NULL DEFAULT '',
29
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
30
+ last_active_at TEXT NOT NULL DEFAULT (datetime('now')),
31
+ message_count INTEGER NOT NULL DEFAULT 0,
32
+ latest_msg_id TEXT
33
+ );
34
+
35
+ CREATE INDEX IF NOT EXISTS idx_sessions_device ON sessions(device_id);
36
+
37
+ CREATE TABLE IF NOT EXISTS messages (
38
+ server_id TEXT PRIMARY KEY,
39
+ session_key TEXT NOT NULL,
40
+ local_id TEXT,
41
+ sender_type TEXT NOT NULL CHECK(sender_type IN ('user','assistant')),
42
+ sender_name TEXT NOT NULL DEFAULT '',
43
+ content TEXT NOT NULL DEFAULT '',
44
+ timestamp_ms INTEGER NOT NULL,
45
+ seq INTEGER NOT NULL DEFAULT 0,
46
+ status TEXT NOT NULL DEFAULT 'sent',
47
+ is_from_current_user INTEGER NOT NULL DEFAULT 0,
48
+ message_json TEXT NOT NULL DEFAULT '{}',
49
+ run_id TEXT,
50
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
51
+ );
52
+
53
+ CREATE INDEX IF NOT EXISTS idx_messages_session ON messages(session_key, server_id);
54
+ CREATE INDEX IF NOT EXISTS idx_messages_session_seq ON messages(session_key, seq);
55
+
56
+ CREATE TABLE IF NOT EXISTS sync_state (
57
+ device_id TEXT PRIMARY KEY,
58
+ last_sync_seq INTEGER NOT NULL DEFAULT 0,
59
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
60
+ );
61
+ `;
62
+ const PRAGMAS = `
63
+ PRAGMA journal_mode=WAL;
64
+ PRAGMA foreign_keys=ON;
65
+ `;
66
+ /* ---- prepared statements (lazy) ---- */
67
+ let _insertMessage = null;
68
+ let _selectMessagesAfter = null;
69
+ let _selectLatestServerId = null;
70
+ let _selectLatestMessage = null;
71
+ let _incrementSessionCount = null;
72
+ let _selectSession = null;
73
+ let _deleteSession = null;
74
+ let _deleteSessionMessages = null;
75
+ let _selectSyncState = null;
76
+ let _upsertSyncState = null;
77
+ let _selectMessagesBefore = null;
78
+ let _selectSessionsByDevice = null;
79
+ function stmt(sql) {
80
+ if (!db)
81
+ throw new Error("DB not initialized");
82
+ return db.prepare(sql);
83
+ }
84
+ function getInsertMessage() {
85
+ if (!_insertMessage) {
86
+ _insertMessage = stmt(`
87
+ INSERT INTO messages (server_id, session_key, local_id, sender_type, sender_name,
88
+ content, timestamp_ms, seq, status, is_from_current_user, message_json, run_id)
89
+ VALUES (@server_id, @session_key, @local_id, @sender_type, @sender_name,
90
+ @content, @timestamp_ms, @seq, @status, @is_from_current_user, @message_json, @run_id)
91
+ `);
92
+ }
93
+ return _insertMessage;
94
+ }
95
+ function getSelectMessagesAfter() {
96
+ if (!_selectMessagesAfter) {
97
+ _selectMessagesAfter = stmt(`
98
+ SELECT message_json FROM messages
99
+ WHERE session_key = @session_key AND server_id > @after
100
+ ORDER BY server_id
101
+ LIMIT @limit
102
+ `);
103
+ }
104
+ return _selectMessagesAfter;
105
+ }
106
+ function getSelectLatestServerId() {
107
+ if (!_selectLatestServerId) {
108
+ _selectLatestServerId = stmt(`
109
+ SELECT server_id FROM messages
110
+ WHERE session_key = @session_key
111
+ ORDER BY server_id DESC
112
+ LIMIT 1
113
+ `);
114
+ }
115
+ return _selectLatestServerId;
116
+ }
117
+ function getSelectLatestMessage() {
118
+ if (!_selectLatestMessage) {
119
+ _selectLatestMessage = stmt(`
120
+ SELECT message_json FROM messages
121
+ WHERE session_key = @session_key
122
+ ORDER BY server_id DESC
123
+ LIMIT 1
124
+ `);
125
+ }
126
+ return _selectLatestMessage;
127
+ }
128
+ function getSelectMessagesBefore() {
129
+ if (!_selectMessagesBefore) {
130
+ _selectMessagesBefore = stmt(`
131
+ SELECT message_json FROM messages
132
+ WHERE session_key = @session_key AND (@before = '' OR server_id < @before)
133
+ ORDER BY server_id DESC
134
+ LIMIT @limit
135
+ `);
136
+ }
137
+ return _selectMessagesBefore;
138
+ }
139
+ let _createSessionIfNotExists = null;
140
+ let _updateSessionActivity = null;
141
+ function getCreateSessionIfNotExists() {
142
+ if (!_createSessionIfNotExists) {
143
+ _createSessionIfNotExists = stmt(`
144
+ INSERT OR IGNORE INTO sessions (session_key, device_id, title, created_at, last_active_at, message_count, latest_msg_id)
145
+ VALUES (@session_key, @device_id, COALESCE(@title, ''), COALESCE(@created_at, datetime('now')), datetime('now'), 0, @latest_msg_id)
146
+ `);
147
+ }
148
+ return _createSessionIfNotExists;
149
+ }
150
+ function getUpdateSessionActivity() {
151
+ if (!_updateSessionActivity) {
152
+ _updateSessionActivity = stmt(`
153
+ UPDATE sessions SET
154
+ last_active_at = datetime('now'),
155
+ latest_msg_id = @latest_msg_id,
156
+ device_id = CASE WHEN device_id = '' OR device_id IS NULL THEN @device_id ELSE device_id END
157
+ WHERE session_key = @session_key
158
+ `);
159
+ }
160
+ return _updateSessionActivity;
161
+ }
162
+ function getIncrementSessionCount() {
163
+ if (!_incrementSessionCount) {
164
+ _incrementSessionCount = stmt(`
165
+ UPDATE sessions SET message_count = message_count + 1, last_active_at = datetime('now')
166
+ WHERE session_key = @session_key
167
+ `);
168
+ }
169
+ return _incrementSessionCount;
170
+ }
171
+ function getSelectSession() {
172
+ if (!_selectSession) {
173
+ _selectSession = stmt(`SELECT * FROM sessions WHERE session_key = @session_key`);
174
+ }
175
+ return _selectSession;
176
+ }
177
+ function getDeleteSession() {
178
+ if (!_deleteSession) {
179
+ _deleteSession = stmt(`DELETE FROM sessions WHERE session_key = @session_key`);
180
+ }
181
+ return _deleteSession;
182
+ }
183
+ function getSelectSessionsByDevice() {
184
+ if (!_selectSessionsByDevice) {
185
+ _selectSessionsByDevice = stmt(`
186
+ SELECT session_key, device_id, title, created_at, last_active_at, message_count, latest_msg_id
187
+ FROM sessions WHERE device_id = @device_id ORDER BY last_active_at DESC
188
+ `);
189
+ }
190
+ return _selectSessionsByDevice;
191
+ }
192
+ function getDeleteSessionMessages() {
193
+ if (!_deleteSessionMessages) {
194
+ _deleteSessionMessages = stmt(`DELETE FROM messages WHERE session_key = @session_key`);
195
+ }
196
+ return _deleteSessionMessages;
197
+ }
198
+ function getSelectSyncState() {
199
+ if (!_selectSyncState) {
200
+ _selectSyncState = stmt(`SELECT * FROM sync_state WHERE device_id = @device_id`);
201
+ }
202
+ return _selectSyncState;
203
+ }
204
+ function getUpsertSyncState() {
205
+ if (!_upsertSyncState) {
206
+ _upsertSyncState = stmt(`
207
+ INSERT OR REPLACE INTO sync_state (device_id, last_sync_seq, updated_at)
208
+ VALUES (@device_id, @last_sync_seq, datetime('now'))
209
+ `);
210
+ }
211
+ return _upsertSyncState;
212
+ }
213
+ export function resolveSyncDbPath() {
214
+ return path.join(os.homedir(), ".openclaw", "friday-next", "sync.db");
215
+ }
216
+ export function initDatabase(dbPath) {
217
+ if (db)
218
+ return;
219
+ const resolved = dbPath ?? resolveSyncDbPath();
220
+ const dir = path.dirname(resolved);
221
+ mkdirSync(dir, { recursive: true });
222
+ db = new Database(resolved);
223
+ db.pragma("journal_mode = WAL");
224
+ db.pragma("foreign_keys = ON");
225
+ db.exec(SCHEMA);
226
+ }
227
+ /** Test-only: initialize with an in-memory database. */
228
+ export function initMemoryDatabase() {
229
+ closeDatabase();
230
+ db = new Database(":memory:");
231
+ db.pragma("foreign_keys = ON");
232
+ db.exec(SCHEMA);
233
+ }
234
+ export function getDatabase() {
235
+ if (!db)
236
+ throw new Error("DB not initialized — call initDatabase() first");
237
+ return db;
238
+ }
239
+ export function closeDatabase() {
240
+ if (db) {
241
+ db.close();
242
+ db = null;
243
+ _insertMessage = null;
244
+ _selectMessagesAfter = null;
245
+ _selectLatestServerId = null;
246
+ _selectLatestMessage = null;
247
+ _selectMessagesBefore = null;
248
+ _createSessionIfNotExists = null;
249
+ _updateSessionActivity = null;
250
+ _incrementSessionCount = null;
251
+ _selectSession = null;
252
+ _deleteSession = null;
253
+ _deleteSessionMessages = null;
254
+ _selectSessionsByDevice = null;
255
+ _selectAllSessions = null;
256
+ _selectSyncState = null;
257
+ _findMessageByRunAndSender = null;
258
+ _upsertSyncState = null;
259
+ }
260
+ }
261
+ export function insertMessage(params) {
262
+ getInsertMessage().run({
263
+ server_id: params.serverId,
264
+ session_key: params.sessionKey,
265
+ local_id: params.localId ?? null,
266
+ sender_type: params.senderType,
267
+ sender_name: params.senderName,
268
+ content: params.content,
269
+ timestamp_ms: params.timestampMs,
270
+ seq: params.seq,
271
+ status: params.status,
272
+ is_from_current_user: params.isFromCurrentUser ? 1 : 0,
273
+ message_json: params.messageJson,
274
+ run_id: params.runId ?? null,
275
+ });
276
+ // auto-create/update session record
277
+ getCreateSessionIfNotExists().run({
278
+ session_key: params.sessionKey,
279
+ device_id: params.deviceId ?? "",
280
+ title: "",
281
+ created_at: null,
282
+ latest_msg_id: params.serverId,
283
+ });
284
+ getUpdateSessionActivity().run({
285
+ session_key: params.sessionKey,
286
+ device_id: params.deviceId ?? "",
287
+ latest_msg_id: params.serverId,
288
+ });
289
+ getIncrementSessionCount().run({ session_key: params.sessionKey });
290
+ }
291
+ export function getMessagesAfterCursor(sessionKey, afterServerId, limit) {
292
+ const rows = getSelectMessagesAfter().all({
293
+ session_key: sessionKey,
294
+ after: afterServerId,
295
+ limit,
296
+ });
297
+ return rows.map((r) => r.message_json);
298
+ }
299
+ export function getLatestServerId(sessionKey) {
300
+ const row = getSelectLatestServerId().get({ session_key: sessionKey });
301
+ return row?.server_id ?? null;
302
+ }
303
+ export function getMessagesBeforeCursor(sessionKey, beforeServerId, limit) {
304
+ const rows = getSelectMessagesBefore().all({
305
+ session_key: sessionKey,
306
+ before: beforeServerId,
307
+ limit,
308
+ });
309
+ // ORDER BY server_id DESC, reverse to chronological
310
+ return rows.map((r) => r.message_json).reverse();
311
+ }
312
+ export function getLastMessageJson(sessionKey) {
313
+ const row = getSelectLatestMessage().get({ session_key: sessionKey });
314
+ return row?.message_json ?? null;
315
+ }
316
+ /* ---- sync state ---- */
317
+ export function getLastSyncSeq(deviceId) {
318
+ const row = getSelectSyncState().get({ device_id: deviceId.toUpperCase() });
319
+ return row?.last_sync_seq ?? 0;
320
+ }
321
+ export function setLastSyncSeq(deviceId, seq) {
322
+ getUpsertSyncState().run({ device_id: deviceId.toUpperCase(), last_sync_seq: seq });
323
+ }
324
+ /* ---- session management ---- */
325
+ export function sessionExists(sessionKey) {
326
+ const row = getSelectSession().get({ session_key: sessionKey });
327
+ return row !== undefined;
328
+ }
329
+ export function getSessionMessageCount(sessionKey) {
330
+ const row = getSelectSession().get({ session_key: sessionKey });
331
+ return row?.message_count ?? 0;
332
+ }
333
+ let _selectAllSessions = null;
334
+ function getSelectAllSessions() {
335
+ if (!_selectAllSessions) {
336
+ _selectAllSessions = stmt(`
337
+ SELECT session_key, device_id, title, created_at, last_active_at, message_count, latest_msg_id
338
+ FROM sessions ORDER BY last_active_at DESC
339
+ `);
340
+ }
341
+ return _selectAllSessions;
342
+ }
343
+ export function listSessionsByDevice(deviceId) {
344
+ return getSelectSessionsByDevice().all({
345
+ device_id: deviceId.trim().toUpperCase(),
346
+ });
347
+ }
348
+ export function listAllSessions() {
349
+ return getSelectAllSessions().all();
350
+ }
351
+ let _findMessageByRunAndSender = null;
352
+ export function findServerIdByRunAndSender(runId, senderType) {
353
+ if (!_findMessageByRunAndSender) {
354
+ _findMessageByRunAndSender = stmt(`
355
+ SELECT server_id FROM messages WHERE run_id = @run_id AND sender_type = @sender_type LIMIT 1
356
+ `);
357
+ }
358
+ const row = _findMessageByRunAndSender.get({ run_id: runId, sender_type: senderType });
359
+ return row?.server_id ?? null;
360
+ }
361
+ export function deleteSessionFromDb(sessionKey) {
362
+ getDeleteSessionMessages().run({ session_key: sessionKey });
363
+ getDeleteSession().run({ session_key: sessionKey });
364
+ }
@@ -0,0 +1,3 @@
1
+ export declare function ensureSyncDatabase(dbPath?: string): void;
2
+ /** Vitest-only: reset for tests that need a fresh DB */
3
+ export declare function resetSyncInitForTest(): void;
@@ -0,0 +1,14 @@
1
+ import { initDatabase, resolveSyncDbPath } from "./database.js";
2
+ let initialized = false;
3
+ export function ensureSyncDatabase(dbPath) {
4
+ if (initialized)
5
+ return;
6
+ initDatabase(dbPath);
7
+ initialized = true;
8
+ const path = dbPath ?? resolveSyncDbPath();
9
+ console.error(`[Friday-SYNC] Database initialized at ${path}`);
10
+ }
11
+ /** Vitest-only: reset for tests that need a fresh DB */
12
+ export function resetSyncInitForTest() {
13
+ initialized = false;
14
+ }
@@ -0,0 +1 @@
1
+ export declare function getInstallationId(historyDir?: string): string;
@@ -0,0 +1,41 @@
1
+ import crypto from "node:crypto";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { resolveFridayNextConfig } from "../config.js";
5
+ import { getHostOpenClawConfigSnapshot } from "../host-config.js";
6
+ import { getFridayNextRuntime } from "../runtime.js";
7
+ function currentHistoryDir() {
8
+ const cfg = resolveFridayNextConfig(getHostOpenClawConfigSnapshot(getFridayNextRuntime().config));
9
+ return cfg.historyDir;
10
+ }
11
+ export function getInstallationId(historyDir = currentHistoryDir()) {
12
+ const file = path.join(historyDir, "installation-id");
13
+ try {
14
+ const existing = fs.readFileSync(file, "utf8").trim();
15
+ if (existing)
16
+ return existing;
17
+ }
18
+ catch {
19
+ // Generate below.
20
+ }
21
+ const id = crypto.randomUUID();
22
+ fs.mkdirSync(path.dirname(file), { recursive: true });
23
+ const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
24
+ fs.writeFileSync(tmp, `${id}\n`, { encoding: "utf8", mode: 0o600 });
25
+ try {
26
+ fs.renameSync(tmp, file);
27
+ }
28
+ catch {
29
+ try {
30
+ fs.unlinkSync(tmp);
31
+ }
32
+ catch {
33
+ // ignore
34
+ }
35
+ const existing = fs.readFileSync(file, "utf8").trim();
36
+ if (existing)
37
+ return existing;
38
+ throw new Error("Unable to initialize Friday Next installation id");
39
+ }
40
+ return id;
41
+ }
@@ -0,0 +1,29 @@
1
+ export declare function createAccumulator(runId: string, deviceId: string, sessionKey: string): void;
2
+ export declare function feedDeliver(runId: string, text: string, _kind: string): void;
3
+ export declare function feedThinking(runId: string, data: {
4
+ delta?: string;
5
+ text?: string;
6
+ }): void;
7
+ export interface ToolHookEvent {
8
+ when: "before" | "after";
9
+ toolName?: string;
10
+ toolCallId?: string;
11
+ params?: unknown;
12
+ result?: unknown;
13
+ error?: string | null;
14
+ durationMs?: number;
15
+ displayLabel?: string;
16
+ displayEmoji?: string;
17
+ }
18
+ export declare function feedToolHook(runId: string, event: ToolHookEvent): void;
19
+ export declare function feedMetadata(runId: string, meta: {
20
+ modelName?: string;
21
+ totalTokens?: number;
22
+ contextTokensUsed?: number;
23
+ contextWindowMax?: number;
24
+ }): void;
25
+ export declare function feedLifecycle(runId: string, phase: string, _data: Record<string, unknown>): void;
26
+ export declare function finalizeAndStore(runId: string): string | null;
27
+ export declare function disposeAccumulator(runId: string): void;
28
+ /** Vitest-only: clear all accumulators */
29
+ export declare function resetAccumulatorsForTest(): void;
@@ -0,0 +1,188 @@
1
+ import { storeAssistantMessage } from "./message-store.js";
2
+ const accumulators = new Map();
3
+ function getAccumulator(runId) {
4
+ return accumulators.get(runId);
5
+ }
6
+ export function createAccumulator(runId, deviceId, sessionKey) {
7
+ accumulators.set(runId, {
8
+ runId,
9
+ deviceId: deviceId.toUpperCase(),
10
+ sessionKey,
11
+ deliverText: "",
12
+ thinkingEvents: [],
13
+ toolCalls: [],
14
+ lifecyclePhase: null,
15
+ pendingToolCalls: new Map(),
16
+ });
17
+ }
18
+ export function feedDeliver(runId, text, _kind) {
19
+ const acc = getAccumulator(runId);
20
+ if (!acc)
21
+ return;
22
+ // Final text replaces; partial accumulates
23
+ if (text)
24
+ acc.deliverText = text;
25
+ }
26
+ export function feedThinking(runId, data) {
27
+ const acc = getAccumulator(runId);
28
+ if (!acc)
29
+ return;
30
+ const delta = typeof data.delta === "string" && data.delta ? data.delta : (typeof data.text === "string" ? data.text : "");
31
+ if (!delta)
32
+ return;
33
+ if (acc.thinkingEvents.length > 0 &&
34
+ acc.thinkingEvents[acc.thinkingEvents.length - 1].kind === "thinking") {
35
+ // Append to the last thinking event
36
+ const last = acc.thinkingEvents[acc.thinkingEvents.length - 1];
37
+ last.thinkingText = (last.thinkingText ?? "") + delta;
38
+ }
39
+ else {
40
+ acc.thinkingEvents.push({ kind: "thinking", thinkingText: delta });
41
+ }
42
+ }
43
+ export function feedToolHook(runId, event) {
44
+ const acc = getAccumulator(runId);
45
+ if (!acc)
46
+ return;
47
+ const name = event.toolName ?? "unknown";
48
+ const toolCallId = event.toolCallId ?? `${name}_${Date.now()}`;
49
+ if (event.when === "before") {
50
+ const displayLabel = typeof event.params === "object" && event.params !== null
51
+ ? extractDisplayLabel(event.params)
52
+ : undefined;
53
+ acc.pendingToolCalls.set(toolCallId, {
54
+ name,
55
+ startTs: Date.now(),
56
+ displayLabel,
57
+ displayEmoji: event.displayEmoji,
58
+ });
59
+ // Add thought event
60
+ acc.thinkingEvents.push({
61
+ kind: "toolCall",
62
+ toolName: name,
63
+ toolCallId,
64
+ phase: "running",
65
+ startTs: Date.now(),
66
+ displayLabel: displayLabel ?? event.displayLabel,
67
+ displayEmoji: event.displayEmoji,
68
+ });
69
+ // Add tool call entry
70
+ acc.toolCalls.push({
71
+ id: toolCallId,
72
+ name,
73
+ phase: "running",
74
+ displayLabel: displayLabel ?? event.displayLabel,
75
+ displayEmoji: event.displayEmoji,
76
+ toolCallId,
77
+ startTs: Date.now(),
78
+ });
79
+ }
80
+ else {
81
+ // after
82
+ const pending = acc.pendingToolCalls.get(toolCallId);
83
+ const durationMs = event.durationMs ?? (pending ? Date.now() - pending.startTs : undefined);
84
+ const hasError = typeof event.error === "string" && event.error.length > 0;
85
+ // Update tool call entry
86
+ const existing = acc.toolCalls.find((t) => t.toolCallId === toolCallId);
87
+ if (existing) {
88
+ existing.phase = hasError ? "failed" : "completed";
89
+ existing.result = event.result;
90
+ existing.error = event.error ?? null;
91
+ existing.durationMs = durationMs;
92
+ }
93
+ // Add thought event
94
+ acc.thinkingEvents.push({
95
+ kind: "toolResult",
96
+ toolName: name,
97
+ toolCallId,
98
+ phase: hasError ? "failed" : "completed",
99
+ result: event.result,
100
+ error: typeof event.error === "string" ? event.error : undefined,
101
+ durationMs,
102
+ });
103
+ acc.pendingToolCalls.delete(toolCallId);
104
+ }
105
+ }
106
+ function extractDisplayLabel(params) {
107
+ // Common patterns: query, path, url, message, name
108
+ const str = params.query ?? params.path ?? params.url ?? params.message ?? params.name ?? params.text;
109
+ if (typeof str !== "string" || !str.trim())
110
+ return undefined;
111
+ if (str.length <= 80)
112
+ return str;
113
+ return str.slice(0, 77) + "...";
114
+ }
115
+ export function feedMetadata(runId, meta) {
116
+ const acc = getAccumulator(runId);
117
+ if (!acc)
118
+ return;
119
+ if (meta.modelName)
120
+ acc.modelName = meta.modelName;
121
+ if (typeof meta.totalTokens === "number")
122
+ acc.totalTokens = meta.totalTokens;
123
+ if (typeof meta.contextTokensUsed === "number")
124
+ acc.contextTokensUsed = meta.contextTokensUsed;
125
+ if (typeof meta.contextWindowMax === "number")
126
+ acc.contextWindowMax = meta.contextWindowMax;
127
+ }
128
+ export function feedLifecycle(runId, phase, _data) {
129
+ const acc = getAccumulator(runId);
130
+ if (!acc)
131
+ return;
132
+ acc.lifecyclePhase = phase;
133
+ }
134
+ export function finalizeAndStore(runId) {
135
+ const acc = getAccumulator(runId);
136
+ if (!acc)
137
+ return null;
138
+ console.error(`[Friday-ACCUM] finalize runId=${runId} deliverText="${acc.deliverText.substring(0, 80)}" thinkingEvents=${acc.thinkingEvents.length} toolCalls=${acc.toolCalls.length}`);
139
+ const message = {
140
+ id: "",
141
+ content: acc.deliverText,
142
+ sender: { assistant: { name: "Claude" } },
143
+ timestamp: new Date().toISOString(),
144
+ seq: 0,
145
+ status: "sent",
146
+ isFromCurrentUser: false,
147
+ toolCalls: acc.toolCalls.length > 0 ? acc.toolCalls.map(normalizeToolCall) : null,
148
+ attachment: null,
149
+ extraAttachments: null,
150
+ thoughtEvents: acc.thinkingEvents.length > 0 ? acc.thinkingEvents : null,
151
+ ttsAttachment: null,
152
+ commandTerminals: null,
153
+ isInternalTrace: false,
154
+ modelName: acc.modelName ?? null,
155
+ totalTokens: acc.totalTokens ?? null,
156
+ contextWindowMax: acc.contextWindowMax ?? null,
157
+ contextTokensUsed: acc.contextTokensUsed ?? null,
158
+ runId: acc.runId,
159
+ };
160
+ const serverId = storeAssistantMessage({
161
+ sessionKey: acc.sessionKey,
162
+ deviceId: acc.deviceId,
163
+ message,
164
+ runId: acc.runId,
165
+ });
166
+ return serverId;
167
+ }
168
+ function normalizeToolCall(tc) {
169
+ return {
170
+ id: tc.id,
171
+ name: tc.name,
172
+ phase: tc.phase,
173
+ displayLabel: tc.displayLabel ?? null,
174
+ displayEmoji: tc.displayEmoji ?? null,
175
+ result: tc.result ?? null,
176
+ error: tc.error ?? null,
177
+ toolCallId: tc.toolCallId,
178
+ durationMs: tc.durationMs ?? null,
179
+ startTs: tc.startTs ?? null,
180
+ };
181
+ }
182
+ export function disposeAccumulator(runId) {
183
+ accumulators.delete(runId);
184
+ }
185
+ /** Vitest-only: clear all accumulators */
186
+ export function resetAccumulatorsForTest() {
187
+ accumulators.clear();
188
+ }