talon-agent 3.34.1 → 3.36.0

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/LICENSE +1 -1
  2. package/README.md +0 -10
  3. package/package.json +3 -3
  4. package/src/backend/codex/auth.ts +40 -1
  5. package/src/backend/codex/handler/message.ts +16 -1
  6. package/src/backend/codex/plan-usage.ts +27 -2
  7. package/src/backend/kilo/handler/message.ts +2 -0
  8. package/src/backend/kilo/server.ts +1 -0
  9. package/src/backend/opencode/handler/message.ts +2 -0
  10. package/src/backend/opencode/server.ts +1 -0
  11. package/src/backend/remote-server/chat-turn.ts +13 -3
  12. package/src/backend/remote-server/lifecycle.ts +37 -0
  13. package/src/backend/remote-server/server-bindings.ts +12 -0
  14. package/src/backend/remote-server/state.ts +8 -0
  15. package/src/backend/remote-server/turn.ts +93 -23
  16. package/src/bootstrap.ts +24 -10
  17. package/src/core/auth/expiry-monitor.ts +89 -0
  18. package/src/core/auth/login-flow.ts +247 -0
  19. package/src/core/auth/status.ts +193 -0
  20. package/src/core/engine/gateway-actions/history.ts +12 -1
  21. package/src/core/errors.ts +1 -1
  22. package/src/core/tools/history.ts +10 -1
  23. package/src/frontend/native/chat-wire.ts +6 -2
  24. package/src/frontend/native/emit.ts +30 -11
  25. package/src/frontend/native/handlers.ts +62 -13
  26. package/src/frontend/native/history.ts +30 -6
  27. package/src/frontend/native/media.ts +271 -15
  28. package/src/frontend/native/protocol.ts +34 -1
  29. package/src/frontend/native/queue.ts +4 -7
  30. package/src/frontend/native/routes/chats.ts +30 -12
  31. package/src/frontend/native/routes/host.ts +17 -5
  32. package/src/frontend/native/routes/params.ts +21 -0
  33. package/src/frontend/native/runtime.ts +16 -4
  34. package/src/frontend/native/server.ts +2 -35
  35. package/src/frontend/native/status.ts +3 -1
  36. package/src/frontend/native/turn.ts +39 -17
  37. package/src/frontend/shared/model-commands.ts +400 -0
  38. package/src/frontend/telegram/auth-panel.ts +205 -0
  39. package/src/frontend/telegram/callbacks/auth.ts +70 -0
  40. package/src/frontend/telegram/callbacks/index.ts +16 -0
  41. package/src/frontend/telegram/callbacks/whatsapp.ts +50 -0
  42. package/src/frontend/telegram/commands/auth.ts +58 -0
  43. package/src/frontend/telegram/commands/definitions.ts +4 -0
  44. package/src/frontend/telegram/commands/index.ts +10 -4
  45. package/src/frontend/telegram/commands/whatsapp-pairing.ts +121 -79
  46. package/src/frontend/whatsapp/actions/chat-info.ts +2 -36
  47. package/src/frontend/whatsapp/actions/history.ts +100 -0
  48. package/src/frontend/whatsapp/actions/index.ts +4 -1
  49. package/src/frontend/whatsapp/commands.ts +371 -0
  50. package/src/frontend/whatsapp/inbound.ts +24 -46
  51. package/src/frontend/whatsapp/index.ts +12 -1
  52. package/src/frontend/whatsapp/media-store.ts +20 -1
  53. package/src/frontend/whatsapp/message-store.ts +101 -24
  54. package/src/storage/db.ts +7 -1
  55. package/src/storage/history.ts +26 -2
  56. package/src/storage/repositories/history-repo.ts +61 -1
  57. package/src/storage/repositories/whatsapp-messages-repo.ts +101 -0
  58. package/src/storage/sql/db.sql +6 -0
  59. package/src/storage/sql/history.sql +22 -9
  60. package/src/storage/sql/schema.sql +27 -1
  61. package/src/storage/sql/statements.generated.ts +70 -11
  62. package/src/storage/sql/whatsapp-messages.sql +29 -0
  63. package/src/storage/whatsapp-messages.ts +51 -0
@@ -25,7 +25,12 @@ CREATE TABLE IF NOT EXISTS history_messages (
25
25
  timestamp INTEGER NOT NULL,
26
26
  media_type TEXT,
27
27
  sticker_file_id TEXT,
28
- file_path TEXT
28
+ file_path TEXT,
29
+ -- Files attached to this message, as a JSON array of
30
+ -- {path,name,size,mimeType,image}. A message can carry several (the
31
+ -- companion's composer stages any number), so file_path above holds
32
+ -- only the first for the pre-multi-file row shape. Null when none.
33
+ attachments TEXT
29
34
  );
30
35
  CREATE INDEX IF NOT EXISTS idx_history_chat ON history_messages(chat_id, id);
31
36
  CREATE UNIQUE INDEX IF NOT EXISTS idx_history_chat_msg ON history_messages(chat_id, msg_id);
@@ -255,7 +260,28 @@ CREATE TABLE IF NOT EXISTS journal (
255
260
  type TEXT NOT NULL,
256
261
  payload TEXT NOT NULL
257
262
  );
258
- CREATE INDEX IF NOT EXISTS idx_journal_type ON journal(type, seq);`;
263
+ CREATE INDEX IF NOT EXISTS idx_journal_type ON journal(type, seq);
264
+
265
+ -- WhatsApp message keys: Talon's numeric message id ↔ WhatsApp's
266
+ -- (id, remoteJid, fromMe, participant) key, plus the full proto as JSON
267
+ -- so react/reply/forward/download keep working on messages from before
268
+ -- a restart and media past the CDN TTL can be re-requested.
269
+ CREATE TABLE IF NOT EXISTS whatsapp_messages (
270
+ chat_id TEXT NOT NULL,
271
+ msg_id INTEGER NOT NULL,
272
+ wa_id TEXT NOT NULL,
273
+ remote_jid TEXT NOT NULL,
274
+ from_me INTEGER NOT NULL DEFAULT 0,
275
+ participant TEXT,
276
+ sender_name TEXT NOT NULL DEFAULT '',
277
+ text TEXT NOT NULL DEFAULT '',
278
+ timestamp INTEGER NOT NULL,
279
+ message_json TEXT,
280
+ PRIMARY KEY (chat_id, msg_id)
281
+ );
282
+ CREATE INDEX IF NOT EXISTS idx_whatsapp_messages_msg ON whatsapp_messages(msg_id);
283
+ CREATE INDEX IF NOT EXISTS idx_whatsapp_messages_wa_id ON whatsapp_messages(wa_id);
284
+ CREATE INDEX IF NOT EXISTS idx_whatsapp_messages_time ON whatsapp_messages(timestamp);`;
259
285
 
260
286
  export const chatSettingsSql = {
261
287
  upsert: `INSERT OR REPLACE INTO chat_settings (chat_id, settings) VALUES (?, ?)`,
@@ -303,6 +329,10 @@ ALTER TABLE history_messages ADD COLUMN sender_handle TEXT`,
303
329
  addSessionsMetricsColumn: `-- Column reconciliation for databases that shipped before per-session
304
330
  -- metrics existed. Fresh databases get the column via schema.sql.
305
331
  ALTER TABLE sessions ADD COLUMN metrics TEXT NOT NULL DEFAULT '{"lifetime":{"counters":{"queries":0,"toolCalls":0,"turnsWithTools":0,"apiCalls":0,"inputTokens":0,"outputTokens":0,"cacheReadTokens":0,"cacheWriteTokens":0,"failedTurns":0,"flowViolationRetries":0,"flowViolationCapExhausted":0,"trailingTextDropped":0},"latency":{"count":0,"sumMs":0,"minMs":null,"maxMs":0},"toolCallsByName":{},"backend":{},"cacheHitPercent":{"count":0,"sumMs":0,"minMs":null,"maxMs":0},"toolCallsPerTurn":{"count":0,"sumMs":0,"minMs":null,"maxMs":0},"apiCallsPerTurn":{"count":0,"sumMs":0,"minMs":null,"maxMs":0}},"buckets":{}}'`,
332
+ addHistoryAttachmentsColumn: `-- Column reconciliation for databases that shipped before a message could
333
+ -- carry more than one attachment. Fresh databases get the column via
334
+ -- schema.sql.
335
+ ALTER TABLE history_messages ADD COLUMN attachments TEXT`,
306
336
  } as const;
307
337
 
308
338
  export const goalsSql = {
@@ -334,22 +364,23 @@ WHERE chat_id = ? AND status IN (/* statuses */)`,
334
364
  export const historySql = {
335
365
  insert: `INSERT OR IGNORE INTO history_messages
336
366
  (chat_id, msg_id, sender_id, sender_name, sender_handle, text,
337
- reply_to_msg_id, timestamp, media_type, sticker_file_id, file_path)
338
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
367
+ reply_to_msg_id, timestamp, media_type, sticker_file_id, file_path,
368
+ attachments)
369
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
339
370
  recent: `SELECT msg_id, sender_id, sender_name, sender_handle, text, reply_to_msg_id,
340
- timestamp, media_type, sticker_file_id, file_path
371
+ timestamp, media_type, sticker_file_id, file_path, attachments
341
372
  FROM history_messages
342
373
  WHERE chat_id = ? ORDER BY id DESC LIMIT ?`,
343
374
  recentBefore: `-- Scroll-back pagination: the window of messages strictly older than a
344
375
  -- given msg_id, newest-first (the repository reverses to chronological).
345
376
  SELECT msg_id, sender_id, sender_name, sender_handle, text, reply_to_msg_id,
346
- timestamp, media_type, sticker_file_id, file_path
377
+ timestamp, media_type, sticker_file_id, file_path, attachments
347
378
  FROM history_messages
348
379
  WHERE chat_id = ? AND msg_id < ? ORDER BY id DESC LIMIT ?`,
349
380
  recentBeforeTime: `-- Time-cursor variant of recentBefore for the read_history \`before\` date
350
381
  -- parameter: the newest \`limit\` messages strictly older than a timestamp.
351
382
  SELECT msg_id, sender_id, sender_name, sender_handle, text, reply_to_msg_id,
352
- timestamp, media_type, sticker_file_id, file_path
383
+ timestamp, media_type, sticker_file_id, file_path, attachments
353
384
  FROM history_messages
354
385
  WHERE chat_id = ? AND timestamp < ? ORDER BY id DESC LIMIT ?`,
355
386
  setFilePath: `UPDATE history_messages SET file_path = ? WHERE chat_id = ? AND msg_id = ?`,
@@ -357,23 +388,33 @@ WHERE chat_id = ? AND timestamp < ? ORDER BY id DESC LIMIT ?`,
357
388
  searchFts: `-- The match param must already be a valid FTS5 expression
358
389
  -- (see history.ts ftsQuery).
359
390
  SELECT msg_id, sender_id, sender_name, sender_handle, text, reply_to_msg_id,
360
- timestamp, media_type, sticker_file_id, file_path
391
+ timestamp, media_type, sticker_file_id, file_path, attachments
392
+ FROM history_messages
393
+ WHERE chat_id = ?
394
+ AND id IN (SELECT rowid FROM history_fts WHERE history_fts MATCH ?)
395
+ ORDER BY id DESC LIMIT ?`,
396
+ searchFtsBetween: `-- searchFts restricted to a timestamp window [after, before) — the
397
+ -- search_history \`after\` / \`before\` date parameters. Either bound may be
398
+ -- the open end of the range (0 / a far-future value).
399
+ SELECT msg_id, sender_id, sender_name, sender_handle, text, reply_to_msg_id,
400
+ timestamp, media_type, sticker_file_id, file_path, attachments
361
401
  FROM history_messages
362
402
  WHERE chat_id = ?
363
403
  AND id IN (SELECT rowid FROM history_fts WHERE history_fts MATCH ?)
404
+ AND timestamp >= ? AND timestamp < ?
364
405
  ORDER BY id DESC LIMIT ?`,
365
406
  bySenderName: `-- The fragment param is LIKE-escaped by the repository (backslash escape).
366
407
  SELECT msg_id, sender_id, sender_name, sender_handle, text, reply_to_msg_id,
367
- timestamp, media_type, sticker_file_id, file_path
408
+ timestamp, media_type, sticker_file_id, file_path, attachments
368
409
  FROM history_messages
369
410
  WHERE chat_id = ? AND lower(sender_name) LIKE ? ESCAPE '\\'
370
411
  ORDER BY id DESC LIMIT ?`,
371
412
  byMsgId: `SELECT msg_id, sender_id, sender_name, sender_handle, text, reply_to_msg_id,
372
- timestamp, media_type, sticker_file_id, file_path
413
+ timestamp, media_type, sticker_file_id, file_path, attachments
373
414
  FROM history_messages
374
415
  WHERE chat_id = ? AND msg_id = ? ORDER BY id DESC LIMIT 1`,
375
416
  bySenderId: `SELECT msg_id, sender_id, sender_name, sender_handle, text, reply_to_msg_id,
376
- timestamp, media_type, sticker_file_id, file_path
417
+ timestamp, media_type, sticker_file_id, file_path, attachments
377
418
  FROM history_messages
378
419
  WHERE chat_id = ? AND sender_id = ? ORDER BY id DESC LIMIT ?`,
379
420
  latestMsgId: `SELECT msg_id FROM history_messages WHERE chat_id = ? ORDER BY id DESC LIMIT 1`,
@@ -548,3 +589,21 @@ WHERE chat_id = ?1
548
589
  LIMIT ?2
549
590
  )`,
550
591
  } as const;
592
+
593
+ export const whatsappMessagesSql = {
594
+ insert: `INSERT OR IGNORE INTO whatsapp_messages
595
+ (chat_id, msg_id, wa_id, remote_jid, from_me, participant, sender_name,
596
+ text, timestamp, message_json)
597
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
598
+ byMsgId: `SELECT chat_id, msg_id, wa_id, remote_jid, from_me, participant, sender_name,
599
+ text, timestamp, message_json
600
+ FROM whatsapp_messages WHERE msg_id = ? LIMIT 1`,
601
+ byWaId: `-- Newest first: a WhatsApp id re-delivered on reconnect maps to the row
602
+ -- that already exists for it.
603
+ SELECT chat_id, msg_id, wa_id, remote_jid, from_me, participant, sender_name,
604
+ text, timestamp, message_json
605
+ FROM whatsapp_messages WHERE wa_id = ? ORDER BY msg_id DESC LIMIT 1`,
606
+ maxMsgId: `SELECT MAX(msg_id) AS max_id FROM whatsapp_messages`,
607
+ deleteOlderThan: `DELETE FROM whatsapp_messages WHERE timestamp < ?`,
608
+ deleteAll: `DELETE FROM whatsapp_messages`,
609
+ } as const;
@@ -0,0 +1,29 @@
1
+ -- Statements for the whatsapp_messages table (see
2
+ -- repositories/whatsapp-messages-repo.ts for parameter order and mapping).
3
+
4
+ -- name: insert
5
+ INSERT OR IGNORE INTO whatsapp_messages
6
+ (chat_id, msg_id, wa_id, remote_jid, from_me, participant, sender_name,
7
+ text, timestamp, message_json)
8
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
9
+
10
+ -- name: byMsgId
11
+ SELECT chat_id, msg_id, wa_id, remote_jid, from_me, participant, sender_name,
12
+ text, timestamp, message_json
13
+ FROM whatsapp_messages WHERE msg_id = ? LIMIT 1
14
+
15
+ -- name: byWaId
16
+ -- Newest first: a WhatsApp id re-delivered on reconnect maps to the row
17
+ -- that already exists for it.
18
+ SELECT chat_id, msg_id, wa_id, remote_jid, from_me, participant, sender_name,
19
+ text, timestamp, message_json
20
+ FROM whatsapp_messages WHERE wa_id = ? ORDER BY msg_id DESC LIMIT 1
21
+
22
+ -- name: maxMsgId
23
+ SELECT MAX(msg_id) AS max_id FROM whatsapp_messages
24
+
25
+ -- name: deleteOlderThan
26
+ DELETE FROM whatsapp_messages WHERE timestamp < ?
27
+
28
+ -- name: deleteAll
29
+ DELETE FROM whatsapp_messages
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Persistent WhatsApp message keys.
3
+ *
4
+ * The WhatsApp frontend hands every message it sees a numeric Talon id
5
+ * and needs the WhatsApp key (and, for forwards and media re-downloads,
6
+ * the whole proto) back later. Its in-memory store is a bounded cache;
7
+ * this table is the record, so a reply, reaction or download addressed
8
+ * at a message from before the last restart still resolves.
9
+ *
10
+ * The proto travels as JSON — the frontend serialises and rehydrates it;
11
+ * storage never imports Baileys.
12
+ */
13
+
14
+ import * as repo from "./repositories/whatsapp-messages-repo.js";
15
+
16
+ export type { WhatsAppMessageRecord } from "./repositories/whatsapp-messages-repo.js";
17
+
18
+ /** How long a message's key (and retained proto) stays retrievable. */
19
+ const WHATSAPP_MESSAGE_RETENTION_MS = 180 * 24 * 60 * 60 * 1000;
20
+
21
+ /** Persist a message; a (chat, msg_id) already stored is left as is. */
22
+ export function saveWhatsAppMessage(record: repo.WhatsAppMessageRecord): void {
23
+ repo.insert(record);
24
+ }
25
+
26
+ export function getWhatsAppMessage(
27
+ msgId: number,
28
+ ): repo.WhatsAppMessageRecord | undefined {
29
+ return repo.byMsgId(msgId);
30
+ }
31
+
32
+ export function getWhatsAppMessageByWaId(
33
+ waId: string,
34
+ ): repo.WhatsAppMessageRecord | undefined {
35
+ return repo.byWaId(waId);
36
+ }
37
+
38
+ /** Highest Talon id ever persisted — the restart seed for the id counter. */
39
+ export function maxWhatsAppMsgId(): number | undefined {
40
+ return repo.maxMsgId();
41
+ }
42
+
43
+ /** Drop rows past the retention window; returns the number removed. */
44
+ export function pruneWhatsAppMessages(now = Date.now()): number {
45
+ return repo.deleteOlderThan(now - WHATSAPP_MESSAGE_RETENTION_MS);
46
+ }
47
+
48
+ /** Test seam: forget every persisted message. */
49
+ export function clearWhatsAppMessages(): void {
50
+ repo.deleteAll();
51
+ }