Procházet zdrojové kódy

fix: upsert-nodes build in db-worker and transacts w/ new op

Ensure full db is available for building import edn.
Transacting w/ new op allows llm changes to be undone
Gabriel Horner před 4 dny
rodič
revize
0122425e68

+ 2 - 1
deps/cli/src/logseq/cli/common/mcp/server.cljs

@@ -132,7 +132,8 @@
     :config
     #js {:title "Upsert Nodes"
          :description
-         "Takes an object with field :operations, which is an array of operation objects.
+         "This tool must be called at most once per user request. Never re-call it unless explicitly asked.
+          It takes an object with field :operations, which is an array of operation objects.
           Each operation creates or edits a page, block, tag or property. Each operation is a object
           that must have :operation, :entityType and :data fields. More about fields in an operation object:
             * :operation  - Either :add or :edit

+ 16 - 5
deps/cli/src/logseq/cli/common/mcp/tools.cljs

@@ -141,6 +141,8 @@
                     new-pages)
               ;; existing pages
               (map (fn [[page-id ops]]
+                     (when-not (common-util/uuid-string? page-id)
+                       (throw (ex-info (str "Existing page id " (pr-str page-id) " must be a uuid") {})))
                      {:page {:block/uuid (uuid page-id)}
                       :blocks (mapv (fn [op]
                                       (if (= "add" (:operation op))
@@ -318,7 +320,7 @@
                            " " (pr-str (:title (ex-data e))) " is invalid: " (ex-message e))
                       (ex-data e))))))
 
-(defn- summarize-upsert-operations [operations {:keys [dry-run]}]
+(defn ^:api summarize-upsert-operations [operations {:keys [dry-run]}]
   (let [counts (reduce (fn [acc op]
                          (let [entity-type (keyword (:entityType op))
                                operation-type (keyword (:operation op))]
@@ -331,9 +333,11 @@
          (when (counts :edit)
            (str " Edited: " (pr-str (counts :edit)) ".")))))
 
-(defn upsert-nodes
-  [conn operations* {:keys [dry-run] :as opts}]
-  (ensure-db-graph @conn)
+(defn ^:api build-upsert-nodes-edn
+  "Given llm generated operations, builds the import EDN, validates it and returns it. It fails
+   fast on anything invalid"
+  [db operations*]
+  (ensure-db-graph db)
   ;; Only support these operations with appropriate outliner validations
   (when (seq (filter #(and (#{"page" "tag" "property"} (:entityType %)) (= "edit" (:operation %))) operations*))
     (throw (ex-info "Editing a page, tag or property isn't supported yet" {})))
@@ -348,7 +352,7 @@
         _ (when-let [errors (m/explain Upsert-nodes-operations-schema operations)]
             (throw (ex-info (str "Tool arguments are invalid:\n" (me/humanize errors))
                             {:errors errors})))
-        idents (operations->idents @conn operations)
+        idents (operations->idents db operations)
         pages-and-blocks (ops->pages-and-blocks operations idents)
         classes (ops->classes operations idents)
         properties (ops->properties operations idents)
@@ -362,5 +366,12 @@
           (assoc :properties properties))]
     (prn :import-edn import-edn)
     (validate-import-edn import-edn)
+    import-edn))
+
+(defn upsert-nodes
+  "Builds import-edn from llm generated operations and then imports resulting data. Only
+   used for CLI. See logseq.api/upsert_nodes for API equivalent"
+  [conn operations* {:keys [dry-run] :as opts}]
+  (let [import-edn (build-upsert-nodes-edn @conn operations*)]
     (when-not dry-run (import-edn-data conn import-edn))
     (summarize-upsert-operations operations* opts)))

+ 5 - 0
src/main/frontend/worker/db_worker.cljs

@@ -803,6 +803,11 @@
   (let [conn (worker-state/get-datascript-conn repo)]
     (cli-common-mcp-tools/list-pages @conn)))
 
+(def-thread-api :thread-api/api-build-upsert-nodes-edn
+  [repo ops]
+  (let [conn (worker-state/get-datascript-conn repo)]
+    (cli-common-mcp-tools/build-upsert-nodes-edn @conn ops)))
+
 (comment
   (def-thread-api :general/dangerousRemoveAllDbs
     []

+ 13 - 6
src/main/logseq/api.cljs

@@ -37,7 +37,9 @@
             [frontend.idb :as idb]
             [frontend.loader :as loader]
             [frontend.modules.layout.core]
+            [frontend.modules.outliner.op :as outliner-op]
             [frontend.modules.outliner.tree :as outliner-tree]
+            [frontend.modules.outliner.ui :as ui-outliner-tx]
             [frontend.modules.shortcut.config :as shortcut-config]
             [frontend.modules.shortcut.core :as st]
             [frontend.state :as state]
@@ -1184,13 +1186,18 @@
       #js {:error (str "Page " (pr-str page-title) " not found")})))
 
 (defn ^:export upsert_nodes
-  "Given a list of MCP operations, batch upserts resulting EDN data"
-  [operations options]
-  (p/let [resp (cli-common-mcp-tools/upsert-nodes (conn/get-db false)
-                                                  (js->clj operations :keywordize-keys true)
-                                                  (js->clj options :keywordize-keys true))]
+  "Given a list of MCP operations, batch imports with resulting EDN data"
+  [operations options*]
+  (p/let [ops (js->clj operations :keywordize-keys true)
+          {:keys [dry-run] :as options} (js->clj options* :keywordize-keys true)
+          edn-data (state/<invoke-db-worker :thread-api/api-build-upsert-nodes-edn (state/get-current-repo) ops)
+          {:keys [error]} (when-not dry-run
+                            (ui-outliner-tx/transact!
+                             {:outliner-op :batch-import-edn}
+                             (outliner-op/batch-import-edn! edn-data {})))]
+    (when error (throw (ex-info error {})))
     (ui-handler/re-render-root!)
-    resp))
+    (cli-common-mcp-tools/summarize-upsert-operations ops options)))
 
 ;; ui
 (def ^:export show_msg sdk-ui/-show_msg)