talon-agent 5.26.2 → 5.26.4

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 (40) hide show
  1. package/README.md +1 -0
  2. package/package.json +1 -1
  3. package/src/backend/remote-server/sessions.ts +82 -13
  4. package/src/backend/runtime/turn/handle-retry.ts +6 -5
  5. package/src/cli/commands/backup.ts +7 -3
  6. package/src/cli/commands/history.ts +152 -0
  7. package/src/cli/index.ts +6 -0
  8. package/src/core/backup/archive/verify.ts +53 -0
  9. package/src/core/backup/plan.ts +3 -0
  10. package/src/core/backup/retention/policy.ts +195 -0
  11. package/src/core/backup/scheduler.ts +10 -4
  12. package/src/core/backup/snapshot.ts +3 -0
  13. package/src/core/backup/status.ts +7 -0
  14. package/src/core/backup/store.ts +20 -25
  15. package/src/core/backup/types.ts +14 -0
  16. package/src/core/backup/upload.ts +37 -20
  17. package/src/core/config/index.ts +26 -1
  18. package/src/core/errors.ts +13 -2
  19. package/src/frontend/discord/admin.ts +2 -2
  20. package/src/frontend/discord/callbacks/components/backend-select.ts +4 -4
  21. package/src/frontend/native/chats/chat-lifecycle.ts +5 -2
  22. package/src/frontend/native/chats/chats.ts +14 -2
  23. package/src/frontend/native/chats/empty-chat-sweep.ts +4 -1
  24. package/src/frontend/native/chats/reset.ts +39 -17
  25. package/src/frontend/native/commands/session.ts +2 -2
  26. package/src/frontend/native/surface/models.ts +3 -3
  27. package/src/frontend/native/turn/turn-meta.ts +0 -6
  28. package/src/frontend/presentation/backup-panel.ts +7 -1
  29. package/src/frontend/presentation/model-commands.ts +8 -11
  30. package/src/frontend/presentation/session-status.ts +12 -13
  31. package/src/frontend/presentation/text-commands.ts +6 -9
  32. package/src/frontend/telegram/admin/sessions.ts +2 -1
  33. package/src/frontend/telegram/callbacks/model/backend.ts +5 -6
  34. package/src/frontend/whatsapp/commands.ts +5 -8
  35. package/src/storage/history.ts +167 -17
  36. package/src/storage/repositories/history-repo.ts +75 -7
  37. package/src/storage/sql/history.sql +45 -4
  38. package/src/storage/sql/schema.sql +13 -0
  39. package/src/storage/sql/statements.generated.ts +49 -5
  40. package/src/storage/turn-meta.ts +1 -1
@@ -9,10 +9,13 @@ INSERT OR IGNORE INTO history_messages
9
9
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
10
10
 
11
11
  -- name: recent
12
+ -- The `id > ?` floor is the chat's context-reset marker (0 for none; see
13
+ -- chatState below): rows at or under it stay stored and searchable but
14
+ -- are no longer the chat's current conversation.
12
15
  SELECT msg_id, sender_id, sender_name, sender_handle, text, reply_to_msg_id,
13
16
  timestamp, media_type, sticker_file_id, file_path, attachments
14
17
  FROM history_messages
15
- WHERE chat_id = ? ORDER BY id DESC LIMIT ?
18
+ WHERE chat_id = ? AND id > ? ORDER BY id DESC LIMIT ?
16
19
 
17
20
  -- name: recentBefore
18
21
  -- Scroll-back pagination: the window of messages strictly older than a
@@ -20,7 +23,7 @@ WHERE chat_id = ? ORDER BY id DESC LIMIT ?
20
23
  SELECT msg_id, sender_id, sender_name, sender_handle, text, reply_to_msg_id,
21
24
  timestamp, media_type, sticker_file_id, file_path, attachments
22
25
  FROM history_messages
23
- WHERE chat_id = ? AND msg_id < ? ORDER BY id DESC LIMIT ?
26
+ WHERE chat_id = ? AND msg_id < ? AND id > ? ORDER BY id DESC LIMIT ?
24
27
 
25
28
  -- name: recentBeforeTime
26
29
  -- Time-cursor variant of recentBefore for the read_history `before` date
@@ -28,14 +31,52 @@ WHERE chat_id = ? AND msg_id < ? ORDER BY id DESC LIMIT ?
28
31
  SELECT msg_id, sender_id, sender_name, sender_handle, text, reply_to_msg_id,
29
32
  timestamp, media_type, sticker_file_id, file_path, attachments
30
33
  FROM history_messages
31
- WHERE chat_id = ? AND timestamp < ? ORDER BY id DESC LIMIT ?
34
+ WHERE chat_id = ? AND timestamp < ? AND id > ? ORDER BY id DESC LIMIT ?
32
35
 
33
36
  -- name: setFilePath
34
37
  UPDATE history_messages SET file_path = ? WHERE chat_id = ? AND msg_id = ?
35
38
 
36
- -- name: deleteChat
39
+ -- name: purgeChat
40
+ -- The ONLY statement that deletes history rows. Reached solely through the
41
+ -- operator's explicit `talon history purge` (history.ts purgeChatHistory);
42
+ -- resets, backend switches and chat deletion never delete rows.
37
43
  DELETE FROM history_messages WHERE chat_id = ?
38
44
 
45
+ -- name: purgeChatState
46
+ DELETE FROM history_chat_state WHERE chat_id = ?
47
+
48
+ -- name: chatState
49
+ SELECT cleared_through_id, cleared_at, hidden_at
50
+ FROM history_chat_state WHERE chat_id = ?
51
+
52
+ -- name: markCleared
53
+ -- Soft reset: move the chat's context floor to its newest stored row. The
54
+ -- rows stay; readers that build the bot's context skip everything at or
55
+ -- under the floor. Parameters: chat_id, chat_id, cleared_at.
56
+ INSERT INTO history_chat_state (chat_id, cleared_through_id, cleared_at)
57
+ VALUES (?, (SELECT COALESCE(MAX(id), 0) FROM history_messages WHERE chat_id = ?), ?)
58
+ ON CONFLICT(chat_id) DO UPDATE SET
59
+ cleared_through_id = excluded.cleared_through_id,
60
+ cleared_at = excluded.cleared_at
61
+
62
+ -- name: markHidden
63
+ -- Soft delete: a chat the user deleted is hidden (and its context floor
64
+ -- moved, so a chat that reappears under the same id starts fresh). The
65
+ -- rows stay. Parameters: chat_id, chat_id, cleared_at, hidden_at.
66
+ INSERT INTO history_chat_state (chat_id, cleared_through_id, cleared_at, hidden_at)
67
+ VALUES (?, (SELECT COALESCE(MAX(id), 0) FROM history_messages WHERE chat_id = ?), ?, ?)
68
+ ON CONFLICT(chat_id) DO UPDATE SET
69
+ cleared_through_id = excluded.cleared_through_id,
70
+ cleared_at = excluded.cleared_at,
71
+ hidden_at = excluded.hidden_at
72
+
73
+ -- name: hiddenChats
74
+ SELECT s.chat_id, s.hidden_at,
75
+ (SELECT COUNT(*) FROM history_messages h WHERE h.chat_id = s.chat_id) AS total
76
+ FROM history_chat_state s
77
+ WHERE s.hidden_at IS NOT NULL
78
+ ORDER BY s.hidden_at DESC
79
+
39
80
  -- name: searchFts
40
81
  -- The match param must already be a valid FTS5 expression
41
82
  -- (see history.ts ftsQuery).
@@ -52,6 +52,19 @@ CREATE TRIGGER IF NOT EXISTS history_au AFTER UPDATE OF text, sender_name ON his
52
52
  VALUES (new.id, new.text, new.sender_name);
53
53
  END;
54
54
 
55
+ -- Per-chat history state. Chat history is never deleted by a reset, a
56
+ -- backend switch or a chat deletion: a reset records a context floor
57
+ -- (`cleared_through_id`, the newest history_messages.id at reset time) so
58
+ -- the bot's context starts fresh after it while every row stays stored
59
+ -- and searchable; deleting a chat in a client sets `hidden_at`. Only the
60
+ -- operator's explicit `talon history purge` removes rows.
61
+ CREATE TABLE IF NOT EXISTS history_chat_state (
62
+ chat_id TEXT PRIMARY KEY,
63
+ cleared_through_id INTEGER NOT NULL DEFAULT 0,
64
+ cleared_at INTEGER,
65
+ hidden_at INTEGER
66
+ );
67
+
55
68
  -- Typed memory: one row per claim, with an FTS5 index over subject +
56
69
  -- text. Kinds are lifecycles, not labels (docs/memory-persona-plan.md
57
70
  -- §3.1): `directive` is durable human intent, `fact` is durable and
@@ -57,6 +57,19 @@ CREATE TRIGGER IF NOT EXISTS history_au AFTER UPDATE OF text, sender_name ON his
57
57
  VALUES (new.id, new.text, new.sender_name);
58
58
  END;
59
59
 
60
+ -- Per-chat history state. Chat history is never deleted by a reset, a
61
+ -- backend switch or a chat deletion: a reset records a context floor
62
+ -- (\`cleared_through_id\`, the newest history_messages.id at reset time) so
63
+ -- the bot's context starts fresh after it while every row stays stored
64
+ -- and searchable; deleting a chat in a client sets \`hidden_at\`. Only the
65
+ -- operator's explicit \`talon history purge\` removes rows.
66
+ CREATE TABLE IF NOT EXISTS history_chat_state (
67
+ chat_id TEXT PRIMARY KEY,
68
+ cleared_through_id INTEGER NOT NULL DEFAULT 0,
69
+ cleared_at INTEGER,
70
+ hidden_at INTEGER
71
+ );
72
+
60
73
  -- Typed memory: one row per claim, with an FTS5 index over subject +
61
74
  -- text. Kinds are lifecycles, not labels (docs/memory-persona-plan.md
62
75
  -- §3.1): \`directive\` is durable human intent, \`fact\` is durable and
@@ -518,24 +531,55 @@ export const historySql = {
518
531
  reply_to_msg_id, timestamp, media_type, sticker_file_id, file_path,
519
532
  attachments)
520
533
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
521
- recent: `SELECT msg_id, sender_id, sender_name, sender_handle, text, reply_to_msg_id,
534
+ recent: `-- The \`id > ?\` floor is the chat's context-reset marker (0 for none; see
535
+ -- chatState below): rows at or under it stay stored and searchable but
536
+ -- are no longer the chat's current conversation.
537
+ SELECT msg_id, sender_id, sender_name, sender_handle, text, reply_to_msg_id,
522
538
  timestamp, media_type, sticker_file_id, file_path, attachments
523
539
  FROM history_messages
524
- WHERE chat_id = ? ORDER BY id DESC LIMIT ?`,
540
+ WHERE chat_id = ? AND id > ? ORDER BY id DESC LIMIT ?`,
525
541
  recentBefore: `-- Scroll-back pagination: the window of messages strictly older than a
526
542
  -- given msg_id, newest-first (the repository reverses to chronological).
527
543
  SELECT msg_id, sender_id, sender_name, sender_handle, text, reply_to_msg_id,
528
544
  timestamp, media_type, sticker_file_id, file_path, attachments
529
545
  FROM history_messages
530
- WHERE chat_id = ? AND msg_id < ? ORDER BY id DESC LIMIT ?`,
546
+ WHERE chat_id = ? AND msg_id < ? AND id > ? ORDER BY id DESC LIMIT ?`,
531
547
  recentBeforeTime: `-- Time-cursor variant of recentBefore for the read_history \`before\` date
532
548
  -- parameter: the newest \`limit\` messages strictly older than a timestamp.
533
549
  SELECT msg_id, sender_id, sender_name, sender_handle, text, reply_to_msg_id,
534
550
  timestamp, media_type, sticker_file_id, file_path, attachments
535
551
  FROM history_messages
536
- WHERE chat_id = ? AND timestamp < ? ORDER BY id DESC LIMIT ?`,
552
+ WHERE chat_id = ? AND timestamp < ? AND id > ? ORDER BY id DESC LIMIT ?`,
537
553
  setFilePath: `UPDATE history_messages SET file_path = ? WHERE chat_id = ? AND msg_id = ?`,
538
- deleteChat: `DELETE FROM history_messages WHERE chat_id = ?`,
554
+ purgeChat: `-- The ONLY statement that deletes history rows. Reached solely through the
555
+ -- operator's explicit \`talon history purge\` (history.ts purgeChatHistory);
556
+ -- resets, backend switches and chat deletion never delete rows.
557
+ DELETE FROM history_messages WHERE chat_id = ?`,
558
+ purgeChatState: `DELETE FROM history_chat_state WHERE chat_id = ?`,
559
+ chatState: `SELECT cleared_through_id, cleared_at, hidden_at
560
+ FROM history_chat_state WHERE chat_id = ?`,
561
+ markCleared: `-- Soft reset: move the chat's context floor to its newest stored row. The
562
+ -- rows stay; readers that build the bot's context skip everything at or
563
+ -- under the floor. Parameters: chat_id, chat_id, cleared_at.
564
+ INSERT INTO history_chat_state (chat_id, cleared_through_id, cleared_at)
565
+ VALUES (?, (SELECT COALESCE(MAX(id), 0) FROM history_messages WHERE chat_id = ?), ?)
566
+ ON CONFLICT(chat_id) DO UPDATE SET
567
+ cleared_through_id = excluded.cleared_through_id,
568
+ cleared_at = excluded.cleared_at`,
569
+ markHidden: `-- Soft delete: a chat the user deleted is hidden (and its context floor
570
+ -- moved, so a chat that reappears under the same id starts fresh). The
571
+ -- rows stay. Parameters: chat_id, chat_id, cleared_at, hidden_at.
572
+ INSERT INTO history_chat_state (chat_id, cleared_through_id, cleared_at, hidden_at)
573
+ VALUES (?, (SELECT COALESCE(MAX(id), 0) FROM history_messages WHERE chat_id = ?), ?, ?)
574
+ ON CONFLICT(chat_id) DO UPDATE SET
575
+ cleared_through_id = excluded.cleared_through_id,
576
+ cleared_at = excluded.cleared_at,
577
+ hidden_at = excluded.hidden_at`,
578
+ hiddenChats: `SELECT s.chat_id, s.hidden_at,
579
+ (SELECT COUNT(*) FROM history_messages h WHERE h.chat_id = s.chat_id) AS total
580
+ FROM history_chat_state s
581
+ WHERE s.hidden_at IS NOT NULL
582
+ ORDER BY s.hidden_at DESC`,
539
583
  searchFts: `-- The match param must already be a valid FTS5 expression
540
584
  -- (see history.ts ftsQuery).
541
585
  SELECT msg_id, sender_id, sender_name, sender_handle, text, reply_to_msg_id,
@@ -121,7 +121,7 @@ export function getTurnMeta<T>(chatId: string, msgId: string): T | null {
121
121
  }
122
122
  }
123
123
 
124
- /** Forget a chat entirely (chat deleted / history cleared). */
124
+ /** Forget a chat's turn meta — only the operator's history purge does. */
125
125
  export function clearTurnMeta(chatId: string): void {
126
126
  ensureLoaded();
127
127
  try {