allow custom git branch

This commit is contained in:
Tienson Qin
2026-02-27 14:36:21 +08:00
parent 390bf74e6b
commit 23e83ec8b3
6 changed files with 207 additions and 126 deletions

View File

@@ -259,6 +259,44 @@
(or (some-> (aget env "GITHUB_DEFAULT_BASE_BRANCH") str string/trim not-empty)
"main"))
(defn- github-default-branch-token
[^js env]
(or (source-control/push-token env)
(source-control/pr-token env)))
(defn- task-requested-base-branch
[task]
(or (some-> (get-in task [:project :base-branch]) source-control/sanitize-branch-name)
(some-> (get-in task [:project :branch]) source-control/sanitize-branch-name)))
(defn- <ensure-task-base-branch!
[^js env task]
(let [repo-url (some-> (get-in task [:project :repo-url]) str string/trim not-empty)
requested-base (task-requested-base-branch task)]
(cond
(not (map? task))
(p/resolved task)
(not (map? (:project task)))
(p/resolved task)
(string? requested-base)
(p/resolved (assoc-in task [:project :base-branch] requested-base))
(not (string? repo-url))
(p/resolved task)
:else
(p/let [detected-base (source-control/<default-branch! env
(github-default-branch-token env)
repo-url)
detected-base (source-control/sanitize-branch-name detected-base)
fallback-base (source-control/sanitize-branch-name (default-base-branch env))
resolved-base (or detected-base fallback-base)]
(if (string? resolved-base)
(assoc-in task [:project :base-branch] resolved-base)
task)))))
(defn- error-reason
[error]
(let [reason (some-> error ex-data :reason)]
@@ -516,23 +554,24 @@
(http/forbidden)
:else
(let [session (session/initial-session task audit 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)
updated-session (<get-session self)]
(http/json-response :sessions/create
{:session-id task-id
:status (or (:status updated-session)
(:status session))
:runtime-provider (session-runtime-provider updated-session)
:terminal-enabled (session-terminal-enabled? updated-session)
:stream-url (stream-url request task-id)})))))))))))
(p/let [task (<ensure-task-base-branch! (.-env self) task)]
(let [session (session/initial-session task audit 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)
updated-session (<get-session self)]
(http/json-response :sessions/create
{:session-id task-id
:status (or (:status updated-session)
(:status session))
:runtime-provider (session-runtime-provider updated-session)
:terminal-enabled (session-terminal-enabled? updated-session)
:stream-url (stream-url request task-id)}))))))))))))
(defn- handle-status [^js self _request]
(p/let [session (<get-session self)]
@@ -692,8 +731,7 @@
(p/let [pr-token (source-control/pr-token (.-env self))
requested-base-branch (source-control/sanitize-branch-name (:base-branch body))
default-base (source-control/sanitize-branch-name (default-base-branch (.-env self)))
detected-base-branch (when (and (nil? requested-base-branch)
(string? pr-token))
detected-base-branch (when (nil? requested-base-branch)
(source-control/<default-branch! (.-env self)
pr-token
repo-url))

View File

@@ -146,25 +146,24 @@
(defn <default-branch!
[^js env token repo-url]
(if-not (string? token)
(p/resolved nil)
(if-let [{:keys [provider owner name]} (repo-ref repo-url)]
(if-not (= "github" provider)
(p/resolved nil)
(let [url (str (api-base-url env) "/repos/" owner "/" name)
headers (doto (js/Headers.)
(.set "accept" "application/vnd.github+json")
(.set "authorization" (str "Bearer " token))
(.set "user-agent" (user-agent env))
(.set "x-github-api-version" "2022-11-28"))]
(p/let [resp (js/fetch url #js {:method "GET" :headers headers})
status (.-status resp)
text (.text resp)
payload (parse-json-safe text)]
(if (<= 200 status 299)
(some-> (:default_branch payload) non-empty-str)
nil))))
(p/resolved nil))))
(if-let [{:keys [provider owner name]} (repo-ref repo-url)]
(if-not (= "github" provider)
(p/resolved nil)
(let [url (str (api-base-url env) "/repos/" owner "/" name)
headers (doto (js/Headers.)
(.set "accept" "application/vnd.github+json")
(.set "user-agent" (user-agent env))
(.set "x-github-api-version" "2022-11-28"))
_ (when (string? token)
(.set headers "authorization" (str "Bearer " token)))]
(p/let [resp (js/fetch url #js {:method "GET" :headers headers})
status (.-status resp)
text (.text resp)
payload (parse-json-safe text)]
(if (<= 200 status 299)
(some-> (:default_branch payload) non-empty-str)
nil))))
(p/resolved nil)))
(defn <create-pull-request!
[^js env token repo-url {:keys [title body head-branch base-branch draft]}]

View File

@@ -230,7 +230,8 @@
[:project [:map
[:id :string]
[:title :string]
[:repo-url :string]]]
[:repo-url :string]
[:base-branch {:optional true} :string]]]
[:agent [:or :string
[:map
[:provider {:optional true} :string]

View File

@@ -550,6 +550,8 @@
(remove nil?))
[active-view set-active-view!] (rum/use-state "chat")
[draft set-draft!] (rum/use-state "")
[start-branch set-start-branch!] (rum/use-state "")
[starting-session? set-starting-session?!] (rum/use-state false)
[publish-mode set-publish-mode!] (rum/use-state nil)
[terminal-visible? set-terminal-visible!] (rum/use-state false)
[terminal-status set-terminal-status!] (rum/use-state :idle)
@@ -563,9 +565,13 @@
terminal-open-disabled? (or (not session-started?)
(not (string? terminal-url)))
trimmed-draft (string/trim (or draft ""))
selected-start-branch (normalized-text start-branch)
busy? (contains? #{"submitted" "streaming"} chat-status)
input-disabled? (or (not session-started?) (not (agent-handler/task-ready? block)))
publish-busy? (some? publish-mode)
start-session-disabled? (or starting-session?
session-started?
(not (agent-handler/task-ready? block)))
publish-disabled? (or input-disabled? busy? publish-busy?)
can-send? (and (not input-disabled?)
(not (string/blank? trimmed-draft))
@@ -576,6 +582,15 @@
(set-draft! "")
(-> (.sendMessage chat #js {:text trimmed-draft})
(.catch (fn [_] nil)))))
start-session! (fn []
(when (and base session-id (not start-session-disabled?))
(set-starting-session?! true)
(let [opts (cond-> {}
(string? selected-start-branch)
(assoc :base-branch selected-start-branch))]
(-> (agent-handler/<start-session! block opts)
(p/catch (fn [_] nil))
(p/finally (fn [] (set-starting-session?! false)))))))
publish! (fn [create-pr?]
(when (and base session-id (not publish-disabled?))
(set-publish-mode! (if create-pr? :pr :push))
@@ -786,6 +801,27 @@
(when (and terminal-tab-active? (string? terminal-error))
[:div {:class "mt-0.5 rounded-lg border border-red-300/40 bg-red-500/5 px-3 py-1.5 text-xs text-red-500"}
terminal-error])
(when-not session-started?
[:div {:class "flex items-end gap-2 rounded-xl border border-border/70 bg-muted/30 p-2"}
[:div.flex-1
[:div.mb-1.text-xs.opacity-70 "Base branch (optional)"]
(shui/input
{:placeholder "Leave empty to auto-detect from GitHub"
:value start-branch
:disabled starting-session?
:on-change #(set-start-branch! (or (.. % -target -value) ""))
:on-key-down (fn [^js e]
(when (= "Enter" (.-key e))
(.preventDefault e)
(start-session!)))})]
(shui/button
{:size :sm
:class "h-9 px-3 text-xs"
:disabled start-session-disabled?
:on-click (fn [_] (start-session!))}
(if starting-session?
"Starting..."
"Start session"))])
[:div {:class "relative flex flex-1 flex-col overflow-hidden rounded-2xl border border-border/80 bg-gradient-to-b from-background via-background to-muted/40 shadow-sm"
:style {:minHeight 0}}
[:div.h-full.min-h-0

View File

@@ -2532,10 +2532,7 @@
:disabled (not ready?)
:on-click (fn [e]
(util/stop e)
(agent-chat/open-agent-chat-dialog! block)
(when-not session-started?
(-> (agent-handler/<start-session! block)
(p/catch (fn [_] nil)))))}
(agent-chat/open-agent-chat-dialog! block))}
(cond
running? "Running"
session-started? "Thread"

View File

@@ -47,36 +47,42 @@
(string? auth-json) (assoc :auth-json auth-json))))
(defn- project-config
[project-page]
(let [repo-url (blank->nil (pu/get-block-property-value project-page :logseq.property/git-repo))
project-id (some-> (:block/uuid project-page) str)
title (blank->nil (:block/title project-page))]
(when (and project-id title repo-url)
{:id project-id
:title title
:repo-url repo-url})))
([project-page]
(project-config project-page nil))
([project-page {:keys [base-branch]}]
(let [repo-url (blank->nil (pu/get-block-property-value project-page :logseq.property/git-repo))
project-id (some-> (:block/uuid project-page) str)
title (blank->nil (:block/title project-page))
base-branch (blank->nil base-branch)]
(when (and project-id title repo-url)
(cond-> {:id project-id
:title title
:repo-url repo-url}
(string? base-branch) (assoc :base-branch base-branch))))))
(defn- task-context
[block]
(let [block-uuid (:block/uuid block)
node-id (some-> block-uuid str)
node-title (or (blank->nil (:block/raw-title block))
(blank->nil (:block/title block))
"")
content (or (blank->nil (:block/raw-title block))
(blank->nil (:block/title block))
"")
project-page (:logseq.property/project block)
agent-page (:logseq.property/agent block)
project (when project-page (project-config project-page))
agent (when agent-page (agent-config agent-page))]
{:block-uuid block-uuid
:node-id node-id
:node-title node-title
:content content
:attachments []
:project project
:agent agent}))
([block]
(task-context block nil))
([block opts]
(let [block-uuid (:block/uuid block)
node-id (some-> block-uuid str)
node-title (or (blank->nil (:block/raw-title block))
(blank->nil (:block/title block))
"")
content (or (blank->nil (:block/raw-title block))
(blank->nil (:block/title block))
"")
project-page (:logseq.property/project block)
agent-page (:logseq.property/agent block)
project (when project-page (project-config project-page opts))
agent (when agent-page (agent-config agent-page))]
{:block-uuid block-uuid
:node-id node-id
:node-title node-title
:content content
:attachments []
:project project
:agent agent})))
(defn task-ready?
[block]
@@ -87,19 +93,21 @@
(> (count (:block/title block)) 4))))
(defn build-session-body
[block]
(let [{:keys [block-uuid node-id node-title content attachments project agent]} (task-context block)
session-id (some-> block-uuid str)]
(when (and session-id node-id (string? node-title) (string? content) (map? project) (map? agent))
{:session-id session-id
:node-id node-id
:node-title node-title
:content content
:attachments attachments
:project project
:agent agent
:capabilities {:push-enabled true
:pr-enabled true}})))
([block]
(build-session-body block nil))
([block opts]
(let [{:keys [block-uuid node-id node-title content attachments project agent]} (task-context block opts)
session-id (some-> block-uuid str)]
(when (and session-id node-id (string? node-title) (string? content) (map? project) (map? agent))
{:session-id session-id
:node-id node-id
:node-title node-title
:content content
:attachments attachments
:project project
:agent agent
:capabilities {:push-enabled true
:pr-enabled true}}))))
(def ^:private stream-reconnect-delay-ms 1500)
@@ -414,52 +422,54 @@
nil))))))))
(defn <start-session!
[block]
(let [base (db-sync/http-base)]
(cond
(not base)
(do
(notification/show! "DB sync is not configured." :error false)
(p/resolved nil))
([block]
(<start-session! block nil))
([block opts]
(let [base (db-sync/http-base)]
(cond
(not base)
(do
(notification/show! "DB sync is not configured." :error false)
(p/resolved nil))
(not (task-ready? block))
(do
(notification/show! "Task needs Project (with Git Repo) and Agent." :warning)
(p/resolved nil))
(not (task-ready? block))
(do
(notification/show! "Task needs Project (with Git Repo) and Agent." :warning)
(p/resolved nil))
:else
(p/let [_ (js/Promise. user-handler/task--ensure-id&access-token)
raw-body (build-session-body block)
body (coerce-http-request :sessions/create raw-body)]
(if (nil? body)
(do
(notification/show! "Invalid agent session payload." :error false)
nil)
(p/let [resp (db-sync/fetch-json (str base "/sessions")
{:method "POST"
:headers {"content-type" "application/json"}
:body (js/JSON.stringify (clj->js body))}
{:response-schema :sessions/create})
session-id (:session-id resp)
status (:status resp)
stream-url (:stream-url resp)
block-uuid (:block/uuid block)
_ (when-let [raw-message (message-body (:content raw-body))]
(let [coerced (coerce-http-request :sessions/message raw-message)
msg-body (if (map? coerced) coerced raw-message)]
(db-sync/fetch-json (str base "/sessions/" session-id "/messages")
:else
(p/let [_ (js/Promise. user-handler/task--ensure-id&access-token)
raw-body (build-session-body block opts)
body (coerce-http-request :sessions/create raw-body)]
(if (nil? body)
(do
(notification/show! "Invalid agent session payload." :error false)
nil)
(p/let [resp (db-sync/fetch-json (str base "/sessions")
{:method "POST"
:headers {"content-type" "application/json"}
:body (js/JSON.stringify (clj->js msg-body))}
{:response-schema :sessions/message})))]
(update-session-state! block-uuid {:session-id session-id
:status status
:runtime-provider (:runtime-provider resp)
:terminal-enabled (true? (:terminal-enabled resp))
:stream-url stream-url
:started-at (util/time-ms)})
(<connect-session-stream! block-uuid stream-url)
resp))))))
:body (js/JSON.stringify (clj->js body))}
{:response-schema :sessions/create})
session-id (:session-id resp)
status (:status resp)
stream-url (:stream-url resp)
block-uuid (:block/uuid block)
_ (when-let [raw-message (message-body (:content raw-body))]
(let [coerced (coerce-http-request :sessions/message raw-message)
msg-body (if (map? coerced) coerced raw-message)]
(db-sync/fetch-json (str base "/sessions/" session-id "/messages")
{:method "POST"
:headers {"content-type" "application/json"}
:body (js/JSON.stringify (clj->js msg-body))}
{:response-schema :sessions/message})))]
(update-session-state! block-uuid {:session-id session-id
:status status
:runtime-provider (:runtime-provider resp)
:terminal-enabled (true? (:terminal-enabled resp))
:stream-url stream-url
:started-at (util/time-ms)})
(<connect-session-stream! block-uuid stream-url)
resp)))))))
(defn- publish-request-body
[{:keys [title body commit-message head-branch base-branch create-pr? force?]}]