mirror of
https://github.com/logseq/logseq.git
synced 2026-05-30 07:29:48 +00:00
fix: checkpoint
This commit is contained in:
6
deps/db/src/logseq/db/frontend/property.cljs
vendored
6
deps/db/src/logseq/db/frontend/property.cljs
vendored
@@ -431,6 +431,12 @@
|
||||
:public? false
|
||||
:hide? true}
|
||||
:queryable? false}
|
||||
:logseq.property/sandbox-checkpoint
|
||||
{:title "Sandbox checkpoint"
|
||||
:schema {:type :map
|
||||
:public? false
|
||||
:hide? true}
|
||||
:queryable? false}
|
||||
:logseq.property/git-repo
|
||||
{:title "Git Repo"
|
||||
:schema {:type :url
|
||||
|
||||
2
deps/db/src/logseq/db/frontend/schema.cljs
vendored
2
deps/db/src/logseq/db/frontend/schema.cljs
vendored
@@ -30,7 +30,7 @@
|
||||
(map (juxt :major :minor)
|
||||
[(parse-schema-version x) (parse-schema-version y)])))
|
||||
|
||||
(def version (parse-schema-version "65.27"))
|
||||
(def version (parse-schema-version "65.28"))
|
||||
|
||||
(defn major-version
|
||||
"Return a number.
|
||||
|
||||
148
deps/workers/src/logseq/agents/do.cljs
vendored
148
deps/workers/src/logseq/agents/do.cljs
vendored
@@ -100,8 +100,7 @@
|
||||
|
||||
(defn- runtime-snapshot-id
|
||||
[result]
|
||||
(or (some-> (:snapshot-id result) str string/trim not-empty)
|
||||
(some-> (:id result) str string/trim not-empty)))
|
||||
(some-> (:snapshot-id result) str string/trim not-empty))
|
||||
|
||||
(defn- runtime-checkpoint-payload
|
||||
[runtime result reason]
|
||||
@@ -126,6 +125,42 @@
|
||||
(string? backup-dir) (assoc :backup-dir backup-dir)
|
||||
(string? reason) (assoc :reason reason)))))
|
||||
|
||||
(def ^:private checkpoint-storage-key "sandbox.checkpoint")
|
||||
|
||||
(defn- checkpoint-payload-with-reason
|
||||
[checkpoint reason]
|
||||
(let [snapshot-id (runtime-snapshot-id checkpoint)]
|
||||
(when (string? snapshot-id)
|
||||
(cond-> (assoc checkpoint
|
||||
:snapshot-id snapshot-id
|
||||
:checkpoint-at (common/now-ms))
|
||||
(string? reason) (assoc :reason reason)))))
|
||||
|
||||
(defn- checkpoint-event-data
|
||||
[checkpoint data]
|
||||
(let [snapshot-id (runtime-snapshot-id checkpoint)
|
||||
provider (some-> (:provider checkpoint) str string/trim not-empty)
|
||||
backup-key (some-> (:backup-key checkpoint) str string/trim not-empty)
|
||||
backup-dir (some-> (:backup-dir checkpoint) str string/trim not-empty)]
|
||||
(cond-> data
|
||||
(string? snapshot-id) (assoc :snapshot-id snapshot-id)
|
||||
(string? provider) (assoc :provider provider)
|
||||
(string? backup-key) (assoc :backup-key backup-key)
|
||||
(string? backup-dir) (assoc :backup-dir backup-dir))))
|
||||
|
||||
(defn- <stored-checkpoint-payload
|
||||
[^js self reason]
|
||||
(p/let [checkpoint (<storage-get (.-storage self) checkpoint-storage-key)]
|
||||
(checkpoint-payload-with-reason checkpoint reason)))
|
||||
|
||||
(defn- <persist-stored-checkpoint!
|
||||
[^js self checkpoint]
|
||||
(if-let [checkpoint (checkpoint-payload-with-reason checkpoint nil)]
|
||||
(do
|
||||
(prn :debug :persist :checkpoint-storage-key checkpoint)
|
||||
(<storage-put! (.-storage self) checkpoint-storage-key checkpoint))
|
||||
(p/resolved nil)))
|
||||
|
||||
(defn- <persist-session-checkpoint!
|
||||
[^js self expected-session-id checkpoint]
|
||||
(if-not (and (string? expected-session-id) (map? checkpoint))
|
||||
@@ -135,48 +170,53 @@
|
||||
(= expected-session-id (:id latest-session)))
|
||||
(let [task (session-task latest-session)
|
||||
task (assoc task :sandbox-checkpoint checkpoint)]
|
||||
(<save-session! self (assoc latest-session :task task)))
|
||||
(p/let [_ (<save-session! self (assoc latest-session :task task))
|
||||
_ (<persist-stored-checkpoint! self checkpoint)]
|
||||
nil))
|
||||
nil))))
|
||||
|
||||
(defn- existing-checkpoint-payload
|
||||
[session reason]
|
||||
(let [checkpoint (some-> (session-task session) :sandbox-checkpoint)
|
||||
snapshot-id (some-> (:snapshot-id checkpoint) str string/trim not-empty)]
|
||||
(when (string? snapshot-id)
|
||||
(cond-> (assoc checkpoint :checkpoint-at (common/now-ms))
|
||||
(string? reason) (assoc :reason reason)))))
|
||||
(let [task-checkpoint (some-> (session-task session) :sandbox-checkpoint)
|
||||
task-snapshot-id (some-> (:snapshot-id task-checkpoint) str string/trim not-empty)
|
||||
runtime-checkpoint (runtime-checkpoint-payload (:runtime session) (:runtime session) nil)
|
||||
checkpoint (if (string? task-snapshot-id)
|
||||
task-checkpoint
|
||||
runtime-checkpoint)]
|
||||
(checkpoint-payload-with-reason checkpoint reason)))
|
||||
|
||||
(defn- <checkpoint-existing-snapshot!
|
||||
[^js self current-session {:keys [by reason]}]
|
||||
(let [checkpoint (existing-checkpoint-payload current-session reason)
|
||||
snapshot-id (runtime-snapshot-id checkpoint)]
|
||||
(-> (p/let [_ (<append-event! self {:type "sandbox.checkpoint.started"
|
||||
:data (cond-> {}
|
||||
(string? by) (assoc :by by)
|
||||
(string? reason) (assoc :reason reason))
|
||||
:ts (common/now-ms)})]
|
||||
(if (map? checkpoint)
|
||||
(p/let [_ (<persist-session-checkpoint! self (:id current-session) checkpoint)
|
||||
_ (<append-event! self {:type "sandbox.checkpoint.succeeded"
|
||||
:data (cond-> {:reused true}
|
||||
(string? by) (assoc :by by)
|
||||
(string? reason) (assoc :reason reason)
|
||||
(string? snapshot-id) (assoc :snapshot-id snapshot-id))
|
||||
:ts (common/now-ms)})]
|
||||
true)
|
||||
(p/let [_ (<append-event! self {:type "sandbox.checkpoint.failed"
|
||||
:data (cond-> {:error "missing existing checkpoint snapshot"}
|
||||
(string? by) (assoc :by by)
|
||||
(string? reason) (assoc :reason reason))
|
||||
:ts (common/now-ms)})]
|
||||
false)))
|
||||
(p/catch (fn [error]
|
||||
(log/error :agent/checkpoint-existing-snapshot-failed
|
||||
{:session-id (:id current-session)
|
||||
:runtime-session-id (get-in current-session [:runtime :session-id])
|
||||
:sandbox-id (get-in current-session [:runtime :sandbox-id])
|
||||
:error (str error)})
|
||||
nil)))))
|
||||
(-> (p/let [stored-checkpoint (<stored-checkpoint-payload self reason)
|
||||
checkpoint (or (existing-checkpoint-payload current-session reason)
|
||||
stored-checkpoint)
|
||||
_ (<append-event! self {:type "sandbox.checkpoint.started"
|
||||
:data (cond-> {}
|
||||
(string? by) (assoc :by by)
|
||||
(string? reason) (assoc :reason reason))
|
||||
:ts (common/now-ms)})]
|
||||
(if (map? checkpoint)
|
||||
(p/let [_ (<persist-session-checkpoint! self (:id current-session) checkpoint)
|
||||
_ (<append-event! self {:type "sandbox.checkpoint.succeeded"
|
||||
:data (checkpoint-event-data checkpoint
|
||||
(cond-> {:reused true}
|
||||
(string? by) (assoc :by by)
|
||||
(string? reason) (assoc :reason reason)))
|
||||
:ts (common/now-ms)})]
|
||||
true)
|
||||
(p/let [_ (<append-event! self {:type "sandbox.checkpoint.failed"
|
||||
:data (cond-> {:error "missing existing checkpoint snapshot"}
|
||||
(string? by) (assoc :by by)
|
||||
(string? reason) (assoc :reason reason))
|
||||
:ts (common/now-ms)})]
|
||||
false)))
|
||||
(p/catch (fn [error]
|
||||
(log/error :agent/checkpoint-existing-snapshot-failed
|
||||
{:session-id (:id current-session)
|
||||
:runtime-session-id (get-in current-session [:runtime :session-id])
|
||||
:sandbox-id (get-in current-session [:runtime :sandbox-id])
|
||||
:error (str error)})
|
||||
nil))))
|
||||
|
||||
(defn- stream-url [request session-id]
|
||||
(let [base (or (header request "x-stream-base")
|
||||
@@ -706,7 +746,25 @@
|
||||
(defn- <provision-runtime! [^js self task session-id]
|
||||
(let [provider (runtime-provider/resolve-provider (.-env self) nil)
|
||||
provider-kind (runtime-provider/provider-id provider)]
|
||||
(p/let [runtime (runtime-provider/<provision-runtime! provider session-id task)
|
||||
(p/let [stored-checkpoint (<stored-checkpoint-payload self nil)
|
||||
_ (prn :debug :stored-checkpoint stored-checkpoint)
|
||||
task-checkpoint (when (map? task) (:sandbox-checkpoint task))
|
||||
task-has-snapshot? (string? (runtime-snapshot-id task-checkpoint))
|
||||
_ (when task-has-snapshot?
|
||||
(<persist-stored-checkpoint! self task-checkpoint))
|
||||
task (cond
|
||||
(not (map? task))
|
||||
task
|
||||
|
||||
task-has-snapshot?
|
||||
task
|
||||
|
||||
(map? stored-checkpoint)
|
||||
(assoc task :sandbox-checkpoint stored-checkpoint)
|
||||
|
||||
:else
|
||||
task)
|
||||
runtime (runtime-provider/<provision-runtime! provider session-id task)
|
||||
session (<get-session self)]
|
||||
(cond
|
||||
(nil? runtime)
|
||||
@@ -718,7 +776,11 @@
|
||||
nil
|
||||
|
||||
:else
|
||||
(let [session (assoc session :runtime runtime)
|
||||
(let [runtime-checkpoint (runtime-checkpoint-payload runtime runtime "provisioned")
|
||||
session (-> session
|
||||
(assoc :runtime runtime)
|
||||
(cond-> (map? runtime-checkpoint)
|
||||
(assoc-in [:task :sandbox-checkpoint] runtime-checkpoint)))
|
||||
[session _ event] (session/append-event session [] {:type "session.provisioned"
|
||||
:data {:provider (:provider runtime)
|
||||
:runtime-session-id (:session-id runtime)
|
||||
@@ -1138,16 +1200,14 @@
|
||||
{:task (:task current-session)})
|
||||
checkpoint (runtime-checkpoint-payload runtime result "manual")
|
||||
_ (<persist-session-checkpoint! self (:id current-session) checkpoint)
|
||||
snapshot-id (or (:snapshot-id result)
|
||||
(:id result))
|
||||
_ (<append-event! self {:type "sandbox.snapshot.succeeded"
|
||||
:data (cond-> {:by user-id}
|
||||
(string? snapshot-id) (assoc :snapshot-id snapshot-id))
|
||||
:data (checkpoint-event-data checkpoint {:by user-id})
|
||||
:ts (common/now-ms)})]
|
||||
(http/json-response :sessions/snapshot
|
||||
(cond-> {:status "snapshot-created"
|
||||
:message "Sandbox snapshot created."}
|
||||
(string? snapshot-id) (assoc :snapshot-id snapshot-id))))
|
||||
(string? (runtime-snapshot-id checkpoint))
|
||||
(assoc :snapshot-id (runtime-snapshot-id checkpoint)))))
|
||||
(p/catch (fn [error]
|
||||
(let [reason (error-reason error)
|
||||
unsupported? (= reason "unsupported-snapshot")]
|
||||
|
||||
@@ -123,7 +123,7 @@
|
||||
(def ^:private default-repo-base-dir "/workspace")
|
||||
(def ^:private vercel-repo-base-dir "/vercel/sandbox")
|
||||
(def ^:private cloudflare-local-host "http://localhost")
|
||||
(def ^:private cloudflare-snapshot-ttl-seconds (* 7 24 60 60))
|
||||
(def ^:private cloudflare-snapshot-ttl-seconds (* 30 24 60 60))
|
||||
(defonce ^:private cloudflare-backup-cache (atom {}))
|
||||
(defonce ^:private vercel-snapshot-cache (atom {}))
|
||||
|
||||
@@ -505,7 +505,8 @@
|
||||
|
||||
(defn- task-sandbox-checkpoint
|
||||
[task]
|
||||
(let [checkpoint (when (map? task) (:sandbox-checkpoint task))
|
||||
(let [checkpoint (when (map? task)
|
||||
(:sandbox-checkpoint task))
|
||||
provider (normalize-provider (:provider checkpoint))
|
||||
snapshot-id (some-> (:snapshot-id checkpoint) str string/trim not-empty)
|
||||
backup-key (some-> (:backup-key checkpoint) str string/trim not-empty)
|
||||
@@ -1548,6 +1549,7 @@
|
||||
{:backup-key backup-key
|
||||
:snapshot-id snapshot-id})
|
||||
{:sandbox sandbox
|
||||
:snapshot-id snapshot-id
|
||||
:snapshot-dir snapshot-dir
|
||||
:restored? true}))
|
||||
(p/catch (fn [error]
|
||||
@@ -1558,6 +1560,7 @@
|
||||
:error (str error)})
|
||||
(p/let [sandbox (<vercel-create-sandbox! env nil)]
|
||||
{:sandbox sandbox
|
||||
:snapshot-id nil
|
||||
:snapshot-dir nil
|
||||
:restored? false})))))))
|
||||
|
||||
@@ -1902,7 +1905,8 @@
|
||||
:agent-token agent-token
|
||||
:session-id (:session-id response)
|
||||
:backup-key backup-key
|
||||
:backup-dir repo-dir})))
|
||||
:backup-dir repo-dir
|
||||
:snapshot-id snapshot-id})))
|
||||
|
||||
(<open-events-stream! [_ runtime]
|
||||
(let [agent-token (vercel-agent-token env runtime)]
|
||||
|
||||
@@ -240,6 +240,12 @@
|
||||
[:permission-mode {:optional true} :string]
|
||||
[:api-token {:optional true} :string]
|
||||
[:auth-json {:optional true} :string]]]]
|
||||
[:sandbox-checkpoint {:optional true}
|
||||
[:map
|
||||
[:provider {:optional true} :string]
|
||||
[:snapshot-id :string]
|
||||
[:backup-key {:optional true} :string]
|
||||
[:backup-dir {:optional true} :string]]]
|
||||
[:capabilities {:optional true}
|
||||
[:map
|
||||
[:push-enabled {:optional true} :boolean]
|
||||
|
||||
97
deps/workers/test/logseq/agents/do_test.cljs
vendored
97
deps/workers/test/logseq/agents/do_test.cljs
vendored
@@ -228,6 +228,103 @@
|
||||
(is false (str "unexpected error: " error))
|
||||
(done))))))))
|
||||
|
||||
(deftest provision-runtime-persists-runtime-checkpoint-test
|
||||
(testing "provisioned runtime snapshot metadata is persisted to task sandbox checkpoint"
|
||||
(async done
|
||||
(let [env #js {"AGENT_RUNTIME_PROVIDER" "vercel"}
|
||||
self (make-self env)
|
||||
task {:agent "codex"
|
||||
:project {:repo-url "https://github.com/example/repo"}}
|
||||
runtime {:provider "vercel"
|
||||
:session-id "sess-runtime"
|
||||
:sandbox-id "sbx-runtime"
|
||||
:backup-key "github/logseq/agent-test#main"
|
||||
:backup-dir "/vercel/sandbox/agent-test"
|
||||
:snapshot-id "vercel-snapshot-42"}
|
||||
provider (reify runtime-provider/RuntimeProvider
|
||||
(<provision-runtime! [_ _session-id _task]
|
||||
(js/Promise.resolve runtime))
|
||||
(<open-events-stream! [_ _runtime]
|
||||
(js/Promise.resolve nil))
|
||||
(<send-message! [_ _runtime _message]
|
||||
(js/Promise.resolve true))
|
||||
(<open-terminal! [_ _runtime _request _opts]
|
||||
(js/Promise.resolve nil))
|
||||
(<snapshot-runtime! [_ _runtime _opts]
|
||||
(js/Promise.resolve nil))
|
||||
(<push-branch! [_ _runtime _opts]
|
||||
(js/Promise.resolve nil))
|
||||
(<terminate-runtime! [_ _runtime]
|
||||
(js/Promise.resolve nil)))]
|
||||
(-> (.put (.-storage self)
|
||||
"session"
|
||||
(clj->js {:id "sess-runtime"
|
||||
:status "running"
|
||||
:task task
|
||||
:audit {}
|
||||
:created-at 0
|
||||
:updated-at 0}))
|
||||
(.then (fn [_]
|
||||
(with-redefs [runtime-provider/resolve-provider (fn [_env _runtime] provider)
|
||||
runtime-provider/provider-id (fn [_provider] "vercel")
|
||||
agent-do/start-runtime-events-stream-background! (fn [& _] nil)]
|
||||
(#'agent-do/<provision-runtime! self task "sess-runtime"))))
|
||||
(.then (fn [_]
|
||||
(.then (.get (.-storage self) "session")
|
||||
(fn [session-js]
|
||||
(let [session (js->clj session-js :keywordize-keys true)
|
||||
checkpoint (get-in session [:task :sandbox-checkpoint])]
|
||||
(is (= "vercel-snapshot-42" (:snapshot-id checkpoint)))
|
||||
(is (= "vercel" (:provider checkpoint)))
|
||||
(is (= "github/logseq/agent-test#main" (:backup-key checkpoint)))
|
||||
(is (= "/vercel/sandbox/agent-test" (:backup-dir checkpoint)))
|
||||
(is (= "provisioned" (:reason checkpoint)))
|
||||
(is (number? (:checkpoint-at checkpoint)))
|
||||
(done))))))
|
||||
(.catch (fn [error]
|
||||
(is false (str "unexpected provision checkpoint error: " error))
|
||||
(done))))))))
|
||||
|
||||
(deftest checkpoint-existing-snapshot-falls-back-to-runtime-snapshot-test
|
||||
(testing "checkpoint refresh reuses runtime snapshot metadata when task checkpoint is missing"
|
||||
(async done
|
||||
(let [env #js {"AGENT_RUNTIME_PROVIDER" "local-dev"}
|
||||
self (make-self env)
|
||||
session {:id "sess-runtime-checkpoint"
|
||||
:status "running"
|
||||
:task {:project {:repo-url "https://github.com/example/repo"}}
|
||||
:runtime {:provider "vercel"
|
||||
:session-id "sess-runtime-checkpoint"
|
||||
:sandbox-id "sbx-runtime-checkpoint"
|
||||
:snapshot-id "runtime-snapshot-7"
|
||||
:backup-key "github/logseq/agent-test#main"
|
||||
:backup-dir "/vercel/sandbox/agent-test"}
|
||||
:audit {}
|
||||
:created-at 0
|
||||
:updated-at 0}]
|
||||
(-> (.put (.-storage self) "session" (clj->js session))
|
||||
(.then (fn [_]
|
||||
(#'agent-do/<checkpoint-existing-snapshot! self
|
||||
session
|
||||
{:by "system"
|
||||
:reason "pr-ready"})))
|
||||
(.then (fn [ok?]
|
||||
(is (true? ok?))
|
||||
(.then (.get (.-storage self) "session")
|
||||
(fn [session-js]
|
||||
(let [session (js->clj session-js :keywordize-keys true)
|
||||
checkpoint (get-in session [:task :sandbox-checkpoint])]
|
||||
(is (= "runtime-snapshot-7" (:snapshot-id checkpoint)))
|
||||
(is (= "vercel" (:provider checkpoint)))
|
||||
(is (= "github/logseq/agent-test#main" (:backup-key checkpoint)))
|
||||
(is (= "/vercel/sandbox/agent-test" (:backup-dir checkpoint)))
|
||||
(is (= "pr-ready" (:reason checkpoint)))
|
||||
(is (number? (:checkpoint-at checkpoint)))
|
||||
(done))))))
|
||||
(.catch (fn [error]
|
||||
(is false (str "unexpected runtime-checkpoint fallback error: " error))
|
||||
(done))))))))
|
||||
|
||||
(deftest runtime-session-completed-checkpoints-existing-and-terminates-test
|
||||
(testing "session.completed runtime event checkpoints existing pointer and terminates runtime without snapshot creation"
|
||||
(async done
|
||||
|
||||
@@ -279,10 +279,11 @@
|
||||
(is (= "vercel-snap-1" (:snapshot-id snapshot-result)))
|
||||
(is (= 1 (:snapshots @calls)))
|
||||
(-> (runtime-provider/<provision-runtime! provider "sess-vercel-2" task)
|
||||
(.then (fn [_runtime-2]
|
||||
(.then (fn [runtime-2]
|
||||
(is (= 1 (:clone @calls)))
|
||||
(is (= 1 (:restores @calls)))
|
||||
(is (= 2 (:sessions @calls)))
|
||||
(is (= "vercel-snap-1" (:snapshot-id runtime-2)))
|
||||
(done)))
|
||||
(.catch (fn [error]
|
||||
(is false (str "unexpected second provision error: " error))
|
||||
@@ -326,6 +327,7 @@
|
||||
(-> (runtime-provider/<provision-runtime! provider "sess-vercel-checkpoint" task)
|
||||
(.then (fn [runtime]
|
||||
(is (= "vercel" (:provider runtime)))
|
||||
(is (= "persisted-snap-7" (:snapshot-id runtime)))
|
||||
(is (= 0 (:clone @calls)))
|
||||
(is (= "snapshot"
|
||||
(get-in (first (:sources @calls)) [:type])))
|
||||
|
||||
@@ -37,6 +37,36 @@
|
||||
(let [value (string/trim value)]
|
||||
(when-not (string/blank? value) value))))
|
||||
|
||||
(def ^:private task-sandbox-checkpoint-property :logseq.property/sandbox-checkpoint)
|
||||
(def ^:private task-session-id-property :logseq.property/agent-session-id)
|
||||
(def ^:private task-pr-property :logseq.property/pr)
|
||||
|
||||
(defn- normalize-sandbox-checkpoint
|
||||
[checkpoint]
|
||||
(when (map? checkpoint)
|
||||
(let [snapshot-id (blank->nil (or (:snapshot-id checkpoint) (get checkpoint "snapshot-id")))
|
||||
provider (some-> (or (:provider checkpoint) (get checkpoint "provider"))
|
||||
str
|
||||
string/trim
|
||||
string/lower-case
|
||||
blank->nil)
|
||||
backup-key (blank->nil (or (:backup-key checkpoint) (get checkpoint "backup-key")))
|
||||
backup-dir (blank->nil (or (:backup-dir checkpoint) (get checkpoint "backup-dir")))]
|
||||
(when (string? snapshot-id)
|
||||
(cond-> {:snapshot-id snapshot-id}
|
||||
(string? provider) (assoc :provider provider)
|
||||
(string? backup-key) (assoc :backup-key backup-key)
|
||||
(string? backup-dir) (assoc :backup-dir backup-dir))))))
|
||||
|
||||
(defn task-sandbox-checkpoint
|
||||
[block]
|
||||
(let [checkpoint (:logseq.property/sandbox-checkpoint block)]
|
||||
(normalize-sandbox-checkpoint checkpoint)))
|
||||
|
||||
(defn- checkpoint->property-value
|
||||
[checkpoint]
|
||||
(normalize-sandbox-checkpoint checkpoint))
|
||||
|
||||
(defn- agent-config
|
||||
[agent-page]
|
||||
(let [api-token (blank->nil (:logseq.property/agent-api-token agent-page))
|
||||
@@ -94,6 +124,9 @@
|
||||
node-title (blank->nil (:block/title block))
|
||||
content (task-content block)
|
||||
project-page (:logseq.property/project block)
|
||||
sandbox-checkpoint (or (task-sandbox-checkpoint block)
|
||||
(when project-page
|
||||
(task-sandbox-checkpoint project-page)))
|
||||
agent-page (:logseq.property/agent block)
|
||||
project (when project-page (project-config project-page opts))
|
||||
agent (when agent-page (agent-config agent-page))]
|
||||
@@ -102,6 +135,7 @@
|
||||
:node-title node-title
|
||||
:content content
|
||||
:attachments []
|
||||
:sandbox-checkpoint sandbox-checkpoint
|
||||
:project project
|
||||
:agent agent})))
|
||||
|
||||
@@ -229,18 +263,19 @@
|
||||
([block]
|
||||
(build-session-body block nil))
|
||||
([block opts]
|
||||
(let [{:keys [block-uuid node-id node-title content attachments project agent]} (task-context block opts)
|
||||
(let [{:keys [block-uuid node-id node-title content attachments sandbox-checkpoint 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}}))))
|
||||
(cond-> {: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}}
|
||||
(map? sandbox-checkpoint) (assoc :sandbox-checkpoint sandbox-checkpoint))))))
|
||||
|
||||
(def ^:private stream-reconnect-delay-ms 1500)
|
||||
|
||||
@@ -288,9 +323,6 @@
|
||||
"pr-created" :logseq.property/status.in-review
|
||||
"failed" :logseq.property/status.canceled
|
||||
"canceled" :logseq.property/status.canceled})
|
||||
(def ^:private task-session-id-property :logseq.property/agent-session-id)
|
||||
(def ^:private task-pr-property :logseq.property/pr)
|
||||
|
||||
(defn- terminal-status? [status]
|
||||
(contains? #{"completed" "failed" "canceled"} status))
|
||||
|
||||
@@ -304,6 +336,18 @@
|
||||
(when (= "session.provisioned" (:type event))
|
||||
(some-> (get-in event [:data :provider]) normalize-runtime-provider)))
|
||||
|
||||
(defn- event->sandbox-checkpoint
|
||||
[event runtime-provider]
|
||||
(when (contains? #{"sandbox.snapshot.succeeded"
|
||||
"sandbox.checkpoint.succeeded"}
|
||||
(:type event))
|
||||
(normalize-sandbox-checkpoint
|
||||
(cond-> (:data event)
|
||||
(and (map? (:data event))
|
||||
(nil? (get-in event [:data :provider]))
|
||||
(string? runtime-provider))
|
||||
(assoc :provider runtime-provider)))))
|
||||
|
||||
(defn session-terminal-enabled?
|
||||
[session]
|
||||
(let [provider (or (normalize-runtime-provider (:runtime-provider session))
|
||||
@@ -349,6 +393,24 @@
|
||||
(when (and session-id (not= current-session-id session-id))
|
||||
(property-handler/set-block-property! block-uuid task-session-id-property session-id)))))
|
||||
|
||||
(defn- maybe-store-task-sandbox-checkpoint!
|
||||
[block-uuid checkpoint]
|
||||
(when-let [block (db/entity [:block/uuid block-uuid])]
|
||||
(when-let [checkpoint (normalize-sandbox-checkpoint checkpoint)]
|
||||
(let [current (task-sandbox-checkpoint block)]
|
||||
(when (not= current checkpoint)
|
||||
(property-handler/set-block-property! block-uuid
|
||||
task-sandbox-checkpoint-property
|
||||
(checkpoint->property-value checkpoint))))
|
||||
(when-let [project-page (:logseq.property/project block)]
|
||||
(let [project-uuid (:block/uuid project-page)
|
||||
current-project-checkpoint (task-sandbox-checkpoint project-page)]
|
||||
(when (and project-uuid
|
||||
(not= current-project-checkpoint checkpoint))
|
||||
(property-handler/set-block-property! project-uuid
|
||||
task-sandbox-checkpoint-property
|
||||
(checkpoint->property-value checkpoint))))))))
|
||||
|
||||
(defn- update-session!
|
||||
[block-uuid f]
|
||||
(state/update-state! :agent/sessions
|
||||
@@ -443,12 +505,18 @@
|
||||
(p/then (fn [resp]
|
||||
(when (seq (:events resp))
|
||||
(append-events! block-uuid (:events resp))
|
||||
(when-let [provider (some->> (:events resp)
|
||||
reverse
|
||||
(keep event-runtime-provider)
|
||||
first)]
|
||||
(update-session-state! block-uuid {:runtime-provider provider
|
||||
:terminal-enabled (runtime-provider-terminal-enabled? provider)})))))
|
||||
(let [provider (some->> (:events resp)
|
||||
reverse
|
||||
(keep event-runtime-provider)
|
||||
first)]
|
||||
(when-let [checkpoint (some->> (:events resp)
|
||||
reverse
|
||||
(keep #(event->sandbox-checkpoint % provider))
|
||||
first)]
|
||||
(maybe-store-task-sandbox-checkpoint! block-uuid checkpoint))
|
||||
(when provider
|
||||
(update-session-state! block-uuid {:runtime-provider provider
|
||||
:terminal-enabled (runtime-provider-terminal-enabled? provider)}))))))
|
||||
(p/catch (fn [_] nil))))))
|
||||
|
||||
(defn <fetch-project-branches!
|
||||
@@ -499,6 +567,10 @@
|
||||
(when-let [provider (event-runtime-provider event)]
|
||||
(update-session-state! block-uuid {:runtime-provider provider
|
||||
:terminal-enabled (runtime-provider-terminal-enabled? provider)}))
|
||||
(let [runtime-provider (or (event-runtime-provider event)
|
||||
(:runtime-provider (session-state block-uuid)))]
|
||||
(when-let [checkpoint (event->sandbox-checkpoint event runtime-provider)]
|
||||
(maybe-store-task-sandbox-checkpoint! block-uuid checkpoint)))
|
||||
(when-let [status (event->status event)]
|
||||
(update-session-state! block-uuid {:status status})
|
||||
(maybe-update-task-status! block-uuid status)
|
||||
|
||||
@@ -85,7 +85,8 @@
|
||||
:logseq.property/agent-auth-json]}]
|
||||
["65.25" {:properties [:logseq.property/pr]}]
|
||||
["65.26" {:properties [:logseq.property/project-sandbox-init-setup]}]
|
||||
["65.27" {:properties [:logseq.property/agent-session-id]}]])
|
||||
["65.27" {:properties [:logseq.property/agent-session-id]}]
|
||||
["65.28" {:properties [:logseq.property/sandbox-checkpoint]}]])
|
||||
|
||||
(let [[major minor] (last (sort (map (comp (juxt :major :minor) db-schema/parse-schema-version first)
|
||||
schema-version->updates)))]
|
||||
|
||||
@@ -30,6 +30,61 @@
|
||||
(get-in (agent-handler/build-session-body block)
|
||||
[:project :sandbox-init-setup]))))))
|
||||
|
||||
(deftest build-session-body-includes-sandbox-checkpoint-test
|
||||
(let [project-page {:block/uuid #uuid "11111111-1111-1111-1111-111111111111"
|
||||
:block/title "Project"}
|
||||
checkpoint {:provider "vercel"
|
||||
:snapshot-id "snap-77"
|
||||
:backup-key "github/logseq/agent-test#main"
|
||||
:backup-dir "/vercel/sandbox/agent-test"}
|
||||
block {:block/uuid #uuid "22222222-2222-2222-2222-222222222223"
|
||||
:block/title "Task"
|
||||
:logseq.property/project project-page
|
||||
:logseq.property/agent {:block/title "Codex"}}]
|
||||
(p/with-redefs [pu/get-block-property-value (fn [entity k]
|
||||
(cond
|
||||
(= entity project-page)
|
||||
(case k
|
||||
:logseq.property/git-repo "https://github.com/example/repo"
|
||||
nil)
|
||||
|
||||
(and (= entity block)
|
||||
(= k :logseq.property/sandbox-checkpoint))
|
||||
checkpoint
|
||||
|
||||
:else
|
||||
(get entity k)))]
|
||||
(is (= checkpoint
|
||||
(:sandbox-checkpoint (agent-handler/build-session-body block)))))))
|
||||
|
||||
(deftest build-session-body-falls-back-to-project-sandbox-checkpoint-test
|
||||
(let [project-page {:block/uuid #uuid "11111111-1111-1111-1111-111111111112"
|
||||
:block/title "Project"}
|
||||
checkpoint {:provider "vercel"
|
||||
:snapshot-id "snap-project-1"
|
||||
:backup-key "github/logseq/agent-test#main"
|
||||
:backup-dir "/vercel/sandbox/agent-test"}
|
||||
block {:block/uuid #uuid "22222222-2222-2222-2222-222222222224"
|
||||
:block/title "Task"
|
||||
:logseq.property/project project-page
|
||||
:logseq.property/agent {:block/title "Codex"}}]
|
||||
(p/with-redefs [pu/get-block-property-value (fn [entity k]
|
||||
(cond
|
||||
(= entity project-page)
|
||||
(case k
|
||||
:logseq.property/git-repo "https://github.com/example/repo"
|
||||
:logseq.property/sandbox-checkpoint checkpoint
|
||||
nil)
|
||||
|
||||
(and (= entity block)
|
||||
(= k :logseq.property/sandbox-checkpoint))
|
||||
nil
|
||||
|
||||
:else
|
||||
(get entity k)))]
|
||||
(is (= checkpoint
|
||||
(:sandbox-checkpoint (agent-handler/build-session-body block)))))))
|
||||
|
||||
(deftest start-session-sends-initial-message-test
|
||||
(async done
|
||||
(let [calls (atom [])
|
||||
|
||||
@@ -80,3 +80,18 @@
|
||||
(is (= {:major 65 :minor 27}
|
||||
(:kv/value (d/entity @conn :logseq.kv/schema-version))))
|
||||
(is (some? (d/entity @conn property-ident)))))
|
||||
|
||||
(deftest migrate-adds-sandbox-checkpoint-property-builtin
|
||||
(let [conn (db-test/create-conn)
|
||||
property-ident :logseq.property/sandbox-checkpoint
|
||||
_ (d/transact! conn [{:db/ident :logseq.kv/schema-version
|
||||
:kv/value {:major 65 :minor 27}}])
|
||||
existing-eid (d/entid @conn property-ident)
|
||||
_ (when existing-eid
|
||||
(d/transact! conn [[:db/retractEntity existing-eid]]))
|
||||
_ (db-migrate/migrate conn :target-version "65.28")
|
||||
property (d/entity @conn property-ident)]
|
||||
(is (= {:major 65 :minor 28}
|
||||
(:kv/value (d/entity @conn :logseq.kv/schema-version))))
|
||||
(is (some? property))
|
||||
(is (= :map (:logseq.property/type property)))))
|
||||
|
||||
Reference in New Issue
Block a user