Add /search command to search session history

ober

5b1f05286c99994c4996982d80f0f4d12d12128c

diff --git a/lib/jcode/core/session.sls b/lib/jcode/core/session.sls
index 65532cb..d76d4b7 100644
--- a/lib/jcode/core/session.sls
+++ b/lib/jcode/core/session.sls
@@ -4,7 +4,7 @@
 
 (library (jcode core session)
   (export session-init-db session-create session-load session-list
-    session-add-message session-get-messages
+    session-search session-add-message session-get-messages
     session-update-title session-delete session-id session-title
     session-created session-messages)
   (import
@@ -119,6 +119,21 @@
            "UPDATE sessions SET title = ?, updated_at = ? WHERE id = ?"
            title (timestamp-now) session-id)
          (sqlite-close db)))
+  (def (session-search term)
+       "Search all messages for TERM. Returns list of (session-title role snippet)."
+       (let* ([db (open-db)]
+              [pattern (string-append "%" term "%")]
+              [rows (sqlite-query
+                      db
+                      "SELECT s.title, m.role, m.content\n                  FROM messages m\n                  JOIN sessions s ON s.id = m.session_id\n                  WHERE m.content LIKE ?\n                  ORDER BY m.created_at DESC\n                  LIMIT 50"
+                      pattern)])
+         (sqlite-close db)
+         (map (lambda (row)
+                (list
+                  (vector-ref row 0)
+                  (vector-ref row 1)
+                  (vector-ref row 2)))
+              rows)))
   (def (session-delete session-id)
        (let ([db (open-db)])
          (sqlite-eval
diff --git a/lib/jcode/ui/tui-input.sls b/lib/jcode/ui/tui-input.sls
index cca0b25..d63e10d 100644
--- a/lib/jcode/ui/tui-input.sls
+++ b/lib/jcode/ui/tui-input.sls
@@ -34,6 +34,7 @@
          ("/tools" . "List available tools")
          ("/clear" . "Start new session")
          ("/sessions" . "List saved sessions")
+         ("/search" . "Search session history")
          ("/compact" . "Compact conversation")
          ("/quit" . "Exit jcode") ("/theme" . "Switch color theme")
          ("/sidebar" . "Toggle sidebar"))))
diff --git a/lib/jcode/ui/tui.sls b/lib/jcode/ui/tui.sls
index f578e33..d315ebf 100644
--- a/lib/jcode/ui/tui.sls
+++ b/lib/jcode/ui/tui.sls
@@ -338,6 +338,7 @@
                      "  /tools      List available tools"
                      "  /clear      Start new session"
                      "  /sessions   List sessions"
+                     "  /search <term>  Search session history"
                      "  /theme      Cycle theme (Ctrl-T)"
                      "  /sidebar    Toggle sidebar (Ctrl-B)"
                      "  /quit       Exit" ""
@@ -408,6 +409,41 @@
                                  (session-title s)))
                              sessions)
                         "\n")))))]
+           [(string-prefix? "search " cmd)
+            (let* ([term (string-trim
+                           (substring cmd 7 (string-length cmd)))]
+                   [results (session-search term)])
+              (add-message!
+                state
+                (msg-block-system
+                  (if (null? results)
+                      (format "No matches for \"~a\"." term)
+                      (let ([lines (map (lambda (r)
+                                          (let* ([title (car r)]
+                                                 [role (cadr r)]
+                                                 [content (caddr r)]
+                                                 [snippet (if (> (string-length
+                                                                   content)
+                                                                 120)
+                                                              (string-append
+                                                                (substring
+                                                                  content
+                                                                  0
+                                                                  120)
+                                                                "...")
+                                                              content)])
+                                            (format
+                                              "  [~a] (~a) ~a"
+                                              title
+                                              role
+                                              snippet)))
+                                        results)])
+                        (string-append
+                          (format
+                            "Search: \"~a\" (~a matches)\n"
+                            term
+                            (length results))
+                          (string-join lines "\n")))))))]
            [#t
             (add-message!
               state
diff --git a/src/jcode/core/session.ss b/src/jcode/core/session.ss
index b5f5069..d70dc2d 100644
--- a/src/jcode/core/session.ss
+++ b/src/jcode/core/session.ss
@@ -4,6 +4,7 @@
         session-create
         session-load
         session-list
+        session-search
         session-add-message
         session-get-messages
         session-update-title
@@ -150,6 +151,25 @@
       title (timestamp-now) session-id)
     (sqlite-close db)))
 
+(def (session-search term)
+  "Search all messages for TERM. Returns list of (session-title role snippet)."
+  (let* ((db (open-db))
+         (pattern (string-append "%" term "%"))
+         (rows (sqlite-query db
+                 "SELECT s.title, m.role, m.content
+                  FROM messages m
+                  JOIN sessions s ON s.id = m.session_id
+                  WHERE m.content LIKE ?
+                  ORDER BY m.created_at DESC
+                  LIMIT 50"
+                 pattern)))
+    (sqlite-close db)
+    (map (lambda (row)
+           (list (vector-ref row 0)
+                 (vector-ref row 1)
+                 (vector-ref row 2)))
+         rows)))
+
 (def (session-delete session-id)
   (let ((db (open-db)))
     (sqlite-eval db "DELETE FROM messages WHERE session_id = ?" session-id)
diff --git a/src/jcode/ui/tui-input.ss b/src/jcode/ui/tui-input.ss
index f96d048..02dc8fa 100644
--- a/src/jcode/ui/tui-input.ss
+++ b/src/jcode/ui/tui-input.ss
@@ -43,6 +43,7 @@
     ("/tools"    . "List available tools")
     ("/clear"    . "Start new session")
     ("/sessions" . "List saved sessions")
+    ("/search"   . "Search session history")
     ("/compact"  . "Compact conversation")
     ("/quit"     . "Exit jcode")
     ("/theme"    . "Switch color theme")
diff --git a/src/jcode/ui/tui.ss b/src/jcode/ui/tui.ss
index ac5ae49..f7934a3 100644
--- a/src/jcode/ui/tui.ss
+++ b/src/jcode/ui/tui.ss
@@ -437,6 +437,7 @@
                "  /tools      List available tools"
                "  /clear      Start new session"
                "  /sessions   List sessions"
+               "  /search <term>  Search session history"
                "  /theme      Cycle theme (Ctrl-T)"
                "  /sidebar    Toggle sidebar (Ctrl-B)"
                "  /quit       Exit"
@@ -496,6 +497,26 @@
                  (map (lambda (s) (format "  ~a  ~a" (session-id s) (session-title s)))
                       sessions)
                  "\n"))))))
+      ((string-prefix? "search " cmd)
+       (let* ((term (string-trim (substring cmd 7 (string-length cmd))))
+              (results (session-search term)))
+         (add-message! state
+           (msg-block-system
+             (if (null? results)
+               (format "No matches for \"~a\"." term)
+               (let ((lines
+                       (map (lambda (r)
+                              (let* ((title (car r))
+                                     (role  (cadr r))
+                                     (content (caddr r))
+                                     (snippet (if (> (string-length content) 120)
+                                                (string-append (substring content 0 120) "...")
+                                                content)))
+                                (format "  [~a] (~a) ~a" title role snippet)))
+                            results)))
+                 (string-append
+                   (format "Search: \"~a\" (~a matches)\n" term (length results))
+                   (string-join lines "\n"))))))))
       (#t
        (add-message! state (msg-block-system (format "Unknown command: /~a" cmd)))))))