add #Agent and #Project

This commit is contained in:
Tienson Qin
2026-02-03 12:09:00 +08:00
parent b0ae4d3403
commit 366226e69b
16 changed files with 137 additions and 26 deletions

View File

@@ -37,3 +37,4 @@
- DB-sync protocol reference: `docs/agent-guide/db-sync/protocol.md`.
- New properties should be added to `logseq.db.frontend.property/built-in-properties`.
- Avoid creating new class or property unless you have to.
- Create db migration when adding new properties/classes.

View File

@@ -1,14 +1,15 @@
# M7: Agent Assignment + Session Start UI
## Target
Allow users to assign an agent to a task and start a session from Logseq UI.
Allow users to start a session from Logseq UI.
## Scope
- UI control on `#Task` to select agent + project.
- Able to run task (when both Agent and Project title && Git repo specified)
- Create simplified sessions/create payload from task data.
- Display session status on the task card.
- Map session status to task status real-time
## Acceptance
1) User can pick an agent for a task and start a session.
2) Task shows status (created/running/paused/etc.).
2) Task shows status
3) Session created uses simplified payload shape.
4) UI should be intuitive and fit the current ui

View File

@@ -13,6 +13,11 @@ create_payload() {
"node-title": "Check weather in Hangzhou",
"content": "Tell me the weather today in Hangzhou.",
"attachments": [],
"project": {
"id": "project-weather",
"title": "Weather Demo",
"repo-url": "https://github.com/logseq/logseq"
},
"agent": {
"provider": "codex",
"mode": "build",

View File

@@ -224,7 +224,17 @@
[:node-title :string]
[:content :string]
[:attachments [:sequential :string]]
[:agent [:or :string :map]]])
[:project [:map
[:id :string]
[:title :string]
[:repo-url :string]]]
[:agent [:or :string
[:map
[:provider {:optional true} :string]
[:mode {:optional true} :string]
[:permission-mode {:optional true} :string]
[:api-token {:optional true} :string]
[:auth-json {:optional true} :string]]]]])
(def sessions-message-request-schema
[:map

View File

@@ -207,7 +207,11 @@
:else
(let [session (session/initial-session task audit now)
[session events _event] (session/append-event session [] {:type "session.created" :data {:requested-by user-id} :ts now})]
[session events _event] (session/append-event session [] {:type "session.created"
:data {:requested-by user-id
:project (:project task)
:agent (:agent task)}
:ts now})]
(p/let [_ (<put-session! self session)
_ (<put-events! self events)
_ (<provision-runtime! self task task-id)]

View File

@@ -9,5 +9,6 @@
:source {:node-id (:node-id body)
:node-title (:node-title body)}
:intent {:content (:content body)}
:project (:project body)
:agent (:agent body)}
(some? attachments) (assoc-in [:intent :attachments] attachments)))))

View File

@@ -10,6 +10,9 @@
:node-title "Title"
:content "Hello"
:attachments ["https://example.com/a.png"]
:project {:id "project-1"
:title "Demo Project"
:repo-url "https://github.com/example/repo"}
:agent "codex"}
coerced (http/coerce-http-request :sessions/create body)]
(is (= body coerced))))
@@ -23,7 +26,12 @@
:node-title "Title"
:content "Hello"
:attachments ["https://example.com/a.png" "https://example.com/b.png"]
:agent {:provider "codex"}}
:project {:id "project-1"
:title "Demo Project"
:repo-url "https://github.com/example/repo"}
:agent {:provider "codex"
:api-token "token-123"
:auth-json "{\"tokens\":{\"access_token\":\"abc\"}}"}}
normalized (request/normalize-session-create body)]
(is (= {:id "sess-1"
:source {:node-id "node-1"
@@ -31,5 +39,10 @@
:intent {:content "Hello"
:attachments ["https://example.com/a.png"
"https://example.com/b.png"]}
:agent {:provider "codex"}}
:project {:id "project-1"
:title "Demo Project"
:repo-url "https://github.com/example/repo"}
:agent {:provider "codex"
:api-token "token-123"
:auth-json "{\"tokens\":{\"access_token\":\"abc\"}}"}}
normalized)))))

View File

@@ -39,7 +39,17 @@
:logseq.class/Task
{:title "Task"
:schema {:properties [:logseq.property/status :logseq.property/priority :logseq.property/deadline :logseq.property/scheduled]}}
:schema {:properties [:logseq.property/status :logseq.property/priority :logseq.property/deadline
:logseq.property/scheduled :logseq.property/project :logseq.property/agent]}}
:logseq.class/Project
{:title "Project"
:schema {:properties [:logseq.property/git-repo]
:required-properties [:logseq.property/git-repo]}}
:logseq.class/Agent
{:title "Agent"
:schema {:properties [:logseq.property/agent-api-token :logseq.property/agent-auth-json]}}
:logseq.class/Query
{:title "Query"

View File

@@ -406,7 +406,37 @@
:schema {:type :property
:hide? true}}
;; TODO: Add more props :Assignee, :Estimate, :Cycle, :Project
;; TODO: Add more props :Assignee, :Estimate, :Cycle
:logseq.property/project
{:title "Project"
:schema {:type :page
:public? true
:classes #{:logseq.class/Project}}
:queryable? true}
:logseq.property/agent
{:title "Agent"
:schema {:type :page
:public? true
:classes #{:logseq.class/Agent}}
:queryable? true}
:logseq.property/git-repo
{:title "Git Repo"
:schema {:type :url
:public? true
:view-context :page}
:queryable? true}
:logseq.property/agent-api-token
{:title "API Token"
:schema {:type :string
:public? true
:view-context :page}}
:logseq.property/agent-auth-json
{:title "auth.json"
:schema {:type :string
:public? true
:view-context :page}
:properties {:logseq.property/description "Store a text block with the auth.json content."}}
:logseq.property/icon {:title "Icon"
:schema {:type :map}}

View File

@@ -30,7 +30,7 @@
(map (juxt :major :minor)
[(parse-schema-version x) (parse-schema-version y)])))
(def version (parse-schema-version "65.22"))
(def version (parse-schema-version "65.23"))
(defn major-version
"Return a number.

View File

@@ -250,7 +250,7 @@
hidden-pages (concat (build-initial-views) (build-favorites-page))
;; These classes bootstrap our tags and properties as they depend on each other e.g.
;; Root <-> Tag, classes-tx depends on logseq.property.class/extends, properties-tx depends on Property
bootstrap-class? (fn [c] (contains? #{:logseq.class/Root :logseq.class/Property :logseq.class/Tag :logseq.class/Template} (:db/ident c)))
bootstrap-class? (fn [c] (contains? #{:logseq.class/Root :logseq.class/Property :logseq.class/Tag :logseq.class/Template :logseq.class/Agent :logseq.class/Project} (:db/ident c)))
bootstrap-classes (filter bootstrap-class? default-classes)
bootstrap-class-ids (map #(select-keys % [:db/ident :block/uuid]) bootstrap-classes)
classes-tx (concat (map #(dissoc % :db/ident) bootstrap-classes)

View File

@@ -20,6 +20,6 @@
:compression compression}]
(p/let [zip-blob (.generateAsync zip opts
(when progress-fn
(fn [metadata]
(fn [^js metadata]
(progress-fn (.-percent metadata)))))]
(make-file zip-blob (str zip-filename ".zip") {:type "application/zip"}))))

View File

@@ -26,7 +26,15 @@
:else nil)))
reaction-username (fn [reaction]
(let [user (:logseq.property/created-by-ref reaction)]
(:block/title (db/entity (:db/id user)))))
(cond
(map? user)
(if (:db/id user)
(:logseq.property.user/name user)
(or (:logseq.property.user/name user)
(:block/title user)))
(number? user)
(:logseq.property.user/name (db/entity user))
:else nil)))
summary (reduce (fn [acc reaction]
(let [emoji-id (:logseq.property.reaction/emoji-id reaction)
user-id (reaction-user-id reaction)
@@ -43,10 +51,10 @@
reactions)]
(->> summary
(map (fn [[emoji-id {:keys [count reacted-by-me? usernames]}]]
{:emoji-id emoji-id
:count count
:reacted-by-me? (boolean reacted-by-me?)
:usernames (when (seq usernames)
(->> usernames sort vec))}))
(cond-> {:emoji-id emoji-id
:count count
:reacted-by-me? (boolean reacted-by-me?)}
(seq usernames)
(assoc :usernames (->> usernames sort vec)))))
(sort-by (juxt (comp - :count) :emoji-id))
vec)))

View File

@@ -945,11 +945,11 @@
fade-in? true
root-margin 100}}]
(let [[visible? set-visible!] (rum/use-state initial-state)
inViewState (useInView #js {:initialInView initial-state
:rootMargin (str root-margin "px")
:triggerOnce trigger-once?
:onChange (fn [in-view? _entry]
(set-visible! in-view?))})
^js inViewState (useInView #js {:initialInView initial-state
:rootMargin (str root-margin "px")
:triggerOnce trigger-once?
:onChange (fn [in-view? _entry]
(set-visible! in-view?))})
ref (.-ref inViewState)]
(lazy-visible-inner visible? content-fn ref fade-in? placeholder))))

View File

@@ -189,7 +189,14 @@
["65.20" {:properties [:logseq.property.class/bidirectional-property-title :logseq.property.class/enable-bidirectional?]}]
["65.21" {:properties [:logseq.property.sync/large-title-object]}]
["65.22" {:properties [:logseq.property.reaction/emoji-id
:logseq.property.reaction/target]}]])
:logseq.property.reaction/target]}]
["65.23" {:classes [:logseq.class/Project
:logseq.class/Agent]
:properties [:logseq.property/project
:logseq.property/agent
:logseq.property/git-repo
:logseq.property/agent-api-token
:logseq.property/agent-auth-json]}]])
(let [[major minor] (last (sort (map (comp (juxt :major :minor) db-schema/parse-schema-version first)
schema-version->updates)))]

View File

@@ -3,7 +3,8 @@
[cljs.test :refer [deftest is]]
[datascript.core :as d]
[frontend.worker.db.migrate :as db-migrate]
[logseq.db :as ldb]))
[logseq.db :as ldb]
[logseq.db.test.helper :as db-test]))
(deftest ensure-built-in-data-exists!
(let [db-transit (str (fs-node/readFileSync "src/test/migration/64.8.transit"))
@@ -20,3 +21,23 @@
(is (= graph-created-at
(:kv/value (d/entity @conn :logseq.kv/graph-created-at)))
"Graph created at not changed by fn")))
(deftest migrate-adds-project-agent-builtins
(let [conn (db-test/create-conn)
_ (d/transact! conn [{:db/ident :logseq.kv/schema-version
:kv/value {:major 65 :minor 22}}])
remove-idents [:logseq.class/Project
:logseq.class/Agent
:logseq.property/project
:logseq.property/git-repo
:logseq.property/agent-api-token
:logseq.property/agent-auth-json]
_ (doseq [ident remove-idents
:let [eid (d/entid @conn ident)]
:when eid]
(d/transact! conn [[:db/retractEntity eid]]))
_ (db-migrate/migrate conn :target-version "65.23")]
(is (= {:major 65 :minor 23}
(:kv/value (d/entity @conn :logseq.kv/schema-version))))
(doseq [ident remove-idents]
(is (some? (d/entity @conn ident))))))