fix: checkpoint

This commit is contained in:
Tienson Qin
2026-03-02 15:12:55 +08:00
parent 508ce4ebb3
commit bab62a5e46
11 changed files with 387 additions and 69 deletions

View File

@@ -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

View File

@@ -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.

View File

@@ -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")]

View File

@@ -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)]

View File

@@ -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]

View File

@@ -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

View File

@@ -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])))

View File

@@ -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)

View File

@@ -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)))]

View File

@@ -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 [])

View File

@@ -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)))))