@desplega.ai/agent-swarm 2.4.5 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/dist/{acp-adapter-2wdkktv6.js → acp-adapter-11gp67nr.js} +8 -6
- package/dist/{actions-c40ebcm4.js → actions-8p2kze8j.js} +23 -21
- package/dist/{amp-adapter-wb67m95z.js → amp-adapter-z4qvhacj.js} +2 -2
- package/dist/{anthropic-messages-g6pqmqk4.js → anthropic-messages-txs190dd.js} +17 -20
- package/dist/{app-353vre8g.js → app-0qp0a2gk.js} +3 -3
- package/dist/{assistant-sj1njh23.js → assistant-jedd3ktm.js} +24 -22
- package/dist/{azure-openai-responses-9dbmj346.js → azure-openai-responses-9kfwhmn7.js} +18 -13
- package/dist/{boot-reembed-qdkvxeem.js → boot-reembed-4xwq5tg0.js} +1 -1
- package/dist/{boot-reembed-b9c3az3c.js → boot-reembed-n4wq5dd2.js} +1 -1
- package/dist/{boot-scrub-sweep-m3wh8eks.js → boot-scrub-sweep-69h90xcq.js} +4 -4
- package/dist/{boot-seeds-j2273s54.js → boot-seeds-3vn0m9sg.js} +4 -4
- package/dist/{claude-adapter-m7q0a085.js → claude-adapter-221mvad5.js} +8 -6
- package/dist/{claude-managed-adapter-5ptb6tzs.js → claude-managed-adapter-gt6pa35d.js} +2 -2
- package/dist/{claude-managed-setup-smrqk5j5.js → claude-managed-setup-c3hp36ph.js} +4 -4
- package/dist/{claude-sdk-session-38xnz21e.js → claude-sdk-session-8thjsk5e.js} +9012 -8928
- package/dist/{cli-b9fcxxev.js → cli-007cnhm1.js} +50 -4
- package/dist/{cli-j4rqdf2n.js → cli-03vpq45j.js} +2 -2
- package/dist/{cli-dk44dxec.js → cli-0y6f0ckq.js} +1 -1
- package/dist/{cli-kpw04va0.js → cli-0z3vjrpm.js} +1 -1
- package/dist/{cli-94gwyg7m.js → cli-179xcyky.js} +9 -9
- package/dist/{cli-3r8samv3.js → cli-1f0gp9vz.js} +3 -2
- package/dist/{cli-4zfqgjc5.js → cli-1pr672hz.js} +2 -2
- package/dist/{cli-0bqteszy.js → cli-2nf3b08t.js} +328 -25
- package/dist/{cli-43x64hv3.js → cli-36j4jnk3.js} +4 -4
- package/dist/{cli-k8g16gz3.js → cli-3qv8naq8.js} +115 -119
- package/dist/cli-3zwye9sn.js +1193 -0
- package/dist/{cli-60cgh1rv.js → cli-459fk97x.js} +7 -7
- package/dist/{cli-v8fjakwf.js → cli-4w24f7z3.js} +1 -1
- package/dist/{cli-n72rg53p.js → cli-5kzk3472.js} +812 -418
- package/dist/{cli-h7gc0b4r.js → cli-5m84zd2k.js} +55 -18
- package/dist/{cli-j99azg3g.js → cli-5rvg1ddk.js} +5 -5
- package/dist/{cli-dvkmp9bt.js → cli-6ctwrf1z.js} +20 -10
- package/dist/{cli-848hxdwj.js → cli-6rg2jn2y.js} +2 -1
- package/dist/{cli-fv5hepyy.js → cli-7bwtc6yx.js} +3 -3
- package/dist/{cli-ms85309d.js → cli-7g40g8hq.js} +11 -11
- package/dist/{cli-xpa64rek.js → cli-92mqenkc.js} +2362 -159
- package/dist/{cli-018r4jq6.js → cli-9ce2xw98.js} +8 -8
- package/dist/{cli-1syn2xy1.js → cli-bgdtv62r.js} +32 -18
- package/dist/{cli-wxbn3yfx.js → cli-cz5nmdxt.js} +2 -2
- package/dist/{cli-wt645v7q.js → cli-dabehxhp.js} +1 -1
- package/dist/{cli-vfha21c9.js → cli-e5bxnjps.js} +2 -2
- package/dist/{cli-wt0jf9hm.js → cli-fcc5qe3f.js} +553 -193
- package/dist/{cli-ftc1g5tj.js → cli-fes51ya2.js} +3 -3
- package/dist/{cli-3a45zcgm.js → cli-ftmq4nvk.js} +2 -2
- package/dist/{cli-37j04k05.js → cli-fy1jyyym.js} +13 -13
- package/dist/{cli-s95dknxh.js → cli-g1cw7xtt.js} +1 -1
- package/dist/{cli-74c1wpm0.js → cli-gpv60278.js} +1 -1
- package/dist/{cli-pemywhfq.js → cli-ha9rmwgq.js} +2 -1
- package/dist/{cli-g35m2xwm.js → cli-j1eghqfm.js} +2 -2
- package/dist/{cli-qfxc84gh.js → cli-jpxsrxcb.js} +1 -1
- package/dist/{cli-ejs3m1sx.js → cli-kxpfx2xr.js} +1 -1
- package/dist/{cli-p14m2cnp.js → cli-mbfj11fq.js} +1 -1
- package/dist/{cli-pnp2j5zg.js → cli-mmhj8vd7.js} +2 -2
- package/dist/{cli-xf234ws8.js → cli-n4c8e3s5.js} +2 -0
- package/dist/{cli-f01h25yt.js → cli-n6kmqngw.js} +2 -2
- package/dist/{cli-v0yrfmdh.js → cli-nnvjb17s.js} +3 -3
- package/dist/{cli-1dmcq2eh.js → cli-nzfnt2gs.js} +1 -1
- package/dist/{cli-625tb7aq.js → cli-p859hcc8.js} +2 -2
- package/dist/{cli-smvcw9z5.js → cli-pasyvjmm.js} +1 -1
- package/dist/cli-pkaqk57w.js +128 -0
- package/dist/{cli-8nyhf7za.js → cli-qqzmjxkf.js} +129 -105
- package/dist/{cli-9yqgm9nd.js → cli-r25qpaxt.js} +161 -63
- package/dist/{cli-eww120g8.js → cli-rh5smt8t.js} +18 -4
- package/dist/{cli-qwkcwjdn.js → cli-rycdaj0a.js} +1 -1
- package/dist/{cli-a92r1127.js → cli-s4sntwt4.js} +2 -2
- package/dist/{cli-be2161h1.js → cli-segs7hx9.js} +6 -6
- package/dist/{cli-36ckw0wx.js → cli-stxdrqs2.js} +2 -2
- package/dist/{cli-z7d29rw9.js → cli-sy8f9705.js} +4 -4
- package/dist/{cli-1m17dc9d.js → cli-ta8bd7th.js} +2 -2
- package/dist/{cli-st8aw1m6.js → cli-we0ra057.js} +4 -4
- package/dist/{cli-7c9qs530.js → cli-x21dqn18.js} +5 -5
- package/dist/{cli-1sp861s4.js → cli-x6vf4sad.js} +11 -11
- package/dist/{cli-d2mb5s8a.js → cli-x77gen2p.js} +680 -440
- package/dist/{cli-tw4p90jb.js → cli-xawhpgc9.js} +5 -5
- package/dist/{cli-f0g3pgvf.js → cli-xd4eqtr2.js} +2 -2
- package/dist/{cli-1f3qzw6y.js → cli-xt768r5m.js} +2 -2
- package/dist/{cli-cxxe61f6.js → cli-y4x8ww6s.js} +2 -2
- package/dist/{cli-bnes4z6b.js → cli-y9jvxfhk.js} +3 -3
- package/dist/{cli-qwhy2qma.js → cli-z63a269p.js} +2 -2
- package/dist/{cli-sm8rses9.js → cli-zfdp01hn.js} +8 -5
- package/dist/cli.js +29 -27
- package/dist/{cloudflare-workers-ai-system-one-ch2wxfcp.js → cloudflare-workers-ai-system-one-43v9qkjk.js} +7 -2
- package/dist/{codex-adapter-tn6k5mkg.js → codex-adapter-tcg1q3jk.js} +6 -4
- package/dist/{codex-login-m819c41r.js → codex-login-rn7c5gxt.js} +1 -1
- package/dist/{codex-session-runner-7vjt7jbw.js → codex-session-runner-xfx4wd9x.js} +7 -5
- package/dist/{commands-t7rz9146.js → commands-ew83nj70.js} +2 -2
- package/dist/{controller-m2jasma5.js → controller-gnq1k1mf.js} +4 -4
- package/dist/{controllers-connect-9ftpex99.js → controllers-connect-tw1dan9r.js} +25 -23
- package/dist/{cursor-adapter-mjv79e4z.js → cursor-adapter-yq5xgfcg.js} +1 -1
- package/dist/{db-6sfrn9g3.js → db-7gbc53fd.js} +14 -2
- package/dist/{db-rn673r06.js → db-t86xrnes.js} +3 -3
- package/dist/{devin-adapter-zgqadvnm.js → devin-adapter-4ycy4vrf.js} +1 -1
- package/dist/{dispatcher-c155xz0p.js → dispatcher-64bdyhps.js} +22 -20
- package/dist/{docker-fkgtr75t.js → docker-nfy2gc8t.js} +2 -2
- package/dist/{dsh-adapter-3mmszc1x.js → dsh-adapter-tvhc4k2d.js} +1 -1
- package/dist/{e2b-16d6ch39.js → e2b-9gkdhm3c.js} +2 -2
- package/dist/{e2b-zn0b4w5w.js → e2b-cjtf8k1z.js} +2 -2
- package/dist/{easter-egg-3d-5cgynaja.js → easter-egg-3d-e4qv8dxj.js} +1 -1
- package/dist/{event-bus-9vpk8gmq.js → event-bus-3z78ep5n.js} +1 -1
- package/dist/{execute-t4e6m3rw.js → execute-0xpz8950.js} +85 -23
- package/dist/{external-channel-35k6tkv8.js → external-channel-e9nw24zv.js} +2 -2
- package/dist/{google-generative-ai-bmq8gkf3.js → google-generative-ai-mk9vcpe1.js} +15 -15
- package/dist/{google-vertex-b1rvp52s.js → google-vertex-q580kz8k.js} +20 -20
- package/dist/{grok-adapter-tfrq7tg1.js → grok-adapter-v82srhrx.js} +9 -7
- package/dist/handlers-c9ajzhbn.js +41 -0
- package/dist/{heartbeat-dyg4npkp.js → heartbeat-2vqs1319.js} +25 -23
- package/dist/{hook-x26b31bg.js → hook-01nk13wx.js} +7 -5
- package/dist/{hook-drspbwpe.js → hook-jzpheegg.js} +9 -7
- package/dist/{http-pk972xyw.js → http-fc1k6ec8.js} +544 -76
- package/dist/index-07ef7nkm.js +42 -0
- package/dist/{index-g6tbb6ve.js → index-31ceyf42.js} +1 -1
- package/dist/index-7yts8n9s.js +43 -0
- package/dist/index-q3t40ryv.js +46 -0
- package/dist/{index-arytgj5q.js → index-sepja052.js} +1 -1
- package/dist/{index-jwxkkex2.js → index-zdycv3ja.js} +13 -11
- package/dist/{index-g216q6jr.js → index-zsyx51ap.js} +1 -4
- package/dist/{keepalive-7yrng7px.js → keepalive-5nh2rhmr.js} +4 -4
- package/dist/lead-aayq64jn.js +79 -0
- package/dist/{llama-cpp-classify-vv43gswy.js → llama-cpp-classify-ee7vmd7q.js} +4 -1
- package/dist/{maintenance-e6cemdqt.js → maintenance-hjhzdt3k.js} +1 -1
- package/dist/{mistral-conversations-1medfwv7.js → mistral-conversations-sd0xf1tn.js} +13 -13
- package/dist/{node-de6akczr.js → node-jzjj7r9x.js} +1 -1
- package/dist/{oauth-refresh-sweep-yw3xdbcj.js → oauth-refresh-sweep-y3n1knmd.js} +3 -3
- package/dist/{onboard-0a9y5yrc.js → onboard-1zq3m7db.js} +2 -2
- package/dist/{openai-codex-responses-de9pvs4d.js → openai-codex-responses-79s8xnbh.js} +29 -23
- package/dist/{openai-completions-mtqge3nf.js → openai-completions-tdbyts0x.js} +21 -20
- package/dist/{openai-decisions-ygk4kkxd.js → openai-decisions-bqhnjyfz.js} +2 -1
- package/dist/{openai-responses-dkyng71g.js → openai-responses-dm3t5vqp.js} +26 -17
- package/dist/{opencode-adapter-x4tqzhkh.js → opencode-adapter-e56086m1.js} +7 -5
- package/dist/{openrouter-images-wvv7camj.js → openrouter-images-ssttr1y4.js} +6 -3
- package/dist/{otel-impl-trjpkfb7.js → otel-impl-h9yyza47.js} +2 -2
- package/dist/{pi-messages-nhh1pkz2.js → pi-messages-3nf8bq1a.js} +6 -4
- package/dist/{pi-mono-adapter-mcfkn9vf.js → pi-mono-adapter-tzps8f38.js} +9 -37
- package/dist/{pricing-refresh-d2b5my0y.js → pricing-refresh-nevhat8h.js} +3 -3
- package/dist/{rbac-roles-nxh5ahwk.js → rbac-roles-4y6ngvzq.js} +1 -1
- package/dist/{rbac-roles-hey3yv9d.js → rbac-roles-exbg8xb6.js} +2 -2
- package/dist/{render-v2-rr3p2atg.js → render-v2-cfsh13z3.js} +3 -3
- package/dist/{resume-2efkf1s1.js → resume-5s2t0dbs.js} +10 -8
- package/dist/{runtime-xe3aqy4x.js → runtime-bt5pkpm1.js} +1 -1
- package/dist/{session-access-s2mttxpw.js → session-access-dnbyp3cn.js} +6 -4
- package/dist/{setup-vypma5yt.js → setup-1wj1y77j.js} +1 -1
- package/dist/{typesafe-system-one-2hgpayzy.js → typesafe-system-one-bre38hvs.js} +4 -2
- package/dist/{virtual-modules-4r3zkc20.js → virtual-modules-mnfbh5mr.js} +8 -2
- package/dist/{work-objects-rvhckdkp.js → work-objects-7mg6216g.js} +25 -23
- package/dist/worker-nwb789mg.js +79 -0
- package/dist/{x-y45mthwn.js → x-pbpezr0s.js} +2 -2
- package/openapi.json +785 -5
- package/package.json +9 -9
- package/src/assets/key.ts +17 -0
- package/src/azure-devops/handlers.ts +1 -1
- package/src/be/db/approvals.ts +18 -0
- package/src/be/db/session-moves.ts +164 -0
- package/src/be/db/session-tree.ts +384 -0
- package/src/be/db/tasks/read.ts +40 -4
- package/src/be/db/workflow-runs.ts +96 -0
- package/src/be/db.ts +80 -236
- package/src/be/onboarding.ts +3 -0
- package/src/be/skill-sync.ts +43 -2
- package/src/be/swarm-config-guard.ts +1 -0
- package/src/bitbucket/api.ts +81 -0
- package/src/bitbucket/auth.ts +64 -0
- package/src/bitbucket/handlers.ts +434 -0
- package/src/bitbucket/index.ts +28 -0
- package/src/bitbucket/templates.ts +134 -0
- package/src/bitbucket/types.ts +65 -0
- package/src/commands/runner.ts +55 -3
- package/src/e2b/env.ts +1 -0
- package/src/extensions/contract-types.generated.ts +1 -1
- package/src/extensions/contract.ts +2 -2
- package/src/extensions/post-bridge.ts +1 -0
- package/src/github/handlers.ts +6 -6
- package/src/gitlab/handlers.ts +5 -5
- package/src/http/assets.ts +14 -2
- package/src/http/core.ts +12 -0
- package/src/http/index.ts +4 -0
- package/src/http/onboarding.ts +1 -0
- package/src/http/sessions.ts +366 -12
- package/src/http/skills.ts +48 -1
- package/src/http/tasks.ts +1 -1
- package/src/http/users.ts +1 -0
- package/src/http/webhooks.ts +141 -0
- package/src/integrations/chat-sdk/adapters.ts +19 -3
- package/src/integrations/chat-sdk/facts.ts +30 -12
- package/src/integrations/chat-sdk/outbound.ts +38 -1
- package/src/providers/acp-targets.ts +1 -1
- package/src/providers/amp-adapter.ts +1 -1
- package/src/providers/pi-mono-adapter.ts +5 -53
- package/src/scripts-runtime/types/swarm-extension.d.ts +6 -4
- package/src/slack/handlers.test.ts +158 -0
- package/src/slack/handlers.ts +19 -28
- package/src/tasks/context-key.ts +61 -1
- package/src/tests/bitbucket-auth.test.ts +156 -0
- package/src/tests/bitbucket-handlers.test.ts +425 -0
- package/src/tests/bitbucket-webhook.test.ts +220 -0
- package/src/tests/chat-sdk-inbound.test.ts +39 -0
- package/src/tests/chat-sdk-telegram-markdown.test.ts +92 -0
- package/src/tests/context-key.test.ts +50 -0
- package/src/tests/core-auth.test.ts +5 -0
- package/src/tests/db-task-read-contract.test.ts +7 -0
- package/src/tests/db-task-write-contract.test.ts +6 -0
- package/src/tests/docs-index-footprint.test.js +3 -3
- package/src/tests/fixtures/bitbucket/pullrequest-comment-created-inline.json +174 -0
- package/src/tests/fixtures/bitbucket/pullrequest-comment-created.json +170 -0
- package/src/tests/fixtures/bitbucket/pullrequest-created-mention.json +132 -0
- package/src/tests/fixtures/bitbucket/pullrequest-created-reviewer.json +140 -0
- package/src/tests/fixtures/bitbucket/pullrequest-fulfilled.json +134 -0
- package/src/tests/fixtures/bitbucket/pullrequest-rejected.json +133 -0
- package/src/tests/generate-mcp-docs.test.ts +101 -0
- package/src/tests/gitlab-handlers.test.ts +3 -3
- package/src/tests/gitlab-vcs-db.test.ts +4 -4
- package/src/tests/integration-disable-flags.test.ts +36 -0
- package/src/tests/mcp-tools.test.ts +1 -1
- package/src/tests/onboarding.test.ts +12 -0
- package/src/tests/pi-codemode.test.ts +8 -2
- package/src/tests/preload-env.test.ts +57 -0
- package/src/tests/preload.ts +21 -0
- package/src/tests/prompt-template-remaining.test.ts +38 -0
- package/src/tests/run-root-coverage.test.ts +39 -0
- package/src/tests/runner-bitbucket-vcs.test.ts +191 -0
- package/src/tests/runner-skills-refresh.test.ts +58 -0
- package/src/tests/secret-scrubber.test.ts +15 -0
- package/src/tests/sessions-move.test.ts +576 -0
- package/src/tests/sessions-tree.test.ts +493 -0
- package/src/tests/skill-files-http.test.ts +50 -0
- package/src/tests/skills-bundle.test.ts +99 -0
- package/src/tests/swarm-config-export-to-env.test.ts +2 -1
- package/src/tests/vcs-bitbucket.test.ts +91 -0
- package/src/tests/vcs-provider-isolation.test.ts +123 -0
- package/src/tests/vcs-provider.test.ts +16 -1
- package/src/tests/vcs-tracking.test.ts +3 -3
- package/src/tests/workflow-foreach-bodies.test.ts +637 -0
- package/src/tests/workflow-foreach-concurrency.test.ts +342 -0
- package/src/tests/workflow-foreach-retry.test.ts +537 -0
- package/src/tests/workflow-foreach.test.ts +44 -23
- package/src/tests/workflow-step-claims.test.ts +6 -6
- package/src/tests/workflow-tla-races.test.ts +2 -2
- package/src/tools/script-delete.ts +1 -1
- package/src/tools/workflows/create-workflow.ts +5 -3
- package/src/types.ts +3 -2
- package/src/utils/config-env-export.ts +1 -0
- package/src/utils/secret-scrubber.ts +4 -0
- package/src/utils/skills-refresh.ts +83 -43
- package/src/vcs/bitbucket.ts +58 -0
- package/src/vcs/index.ts +2 -0
- package/src/vcs/types.ts +2 -2
- package/src/workflows/approval-resolution.ts +4 -1
- package/src/workflows/checkpoint.ts +9 -1
- package/src/workflows/constants.ts +3 -0
- package/src/workflows/definition.ts +48 -17
- package/src/workflows/engine.ts +25 -15
- package/src/workflows/executors/foreach.ts +45 -99
- package/src/workflows/foreach-body.ts +146 -0
- package/src/workflows/foreach-driver.ts +552 -0
- package/src/workflows/foreach-join.ts +206 -7
- package/src/workflows/node-timeout.ts +21 -0
- package/src/workflows/recovery.ts +68 -3
- package/src/workflows/resume.ts +221 -43
- package/src/workflows/retry-poller.ts +39 -24
- package/src/workflows/step-input.ts +59 -0
- package/src/workflows/task-step-routing.ts +114 -56
- package/templates/skills/slack-interaction/content.md +5 -0
- package/templates/skills/work-on-task/SKILL.md +1 -1
- package/templates/skills/work-on-task/content.md +1 -1
- package/dist/cli-9hstj09h.js +0 -2657
- package/dist/cli-yw91rnmw.js +0 -13
- package/dist/handlers-7gw64dxw.js +0 -39
- package/dist/index-bgdyvcnv.js +0 -40
- package/dist/index-gabbcdn2.js +0 -43
- package/dist/index-tfb26zhy.js +0 -41
- package/dist/lead-7e02z839.js +0 -77
- package/dist/worker-5snytpbd.js +0 -77
package/src/be/db/tasks/read.ts
CHANGED
|
@@ -297,7 +297,8 @@ export function rowToAgentTask(row: AgentTaskRow): AgentTask {
|
|
|
297
297
|
slackReplySent: !!row.slackReplySent,
|
|
298
298
|
slackProgressMessageTs: row.slackProgressMessageTs ?? undefined,
|
|
299
299
|
slackTreeRootMessageTs: row.slackTreeRootMessageTs ?? undefined,
|
|
300
|
-
vcsProvider:
|
|
300
|
+
vcsProvider:
|
|
301
|
+
(row.vcsProvider as "github" | "gitlab" | "azure-devops" | "bitbucket" | null) ?? undefined,
|
|
301
302
|
vcsRepo: row.vcsRepo ?? undefined,
|
|
302
303
|
vcsEventType: row.vcsEventType ?? undefined,
|
|
303
304
|
vcsNumber: row.vcsNumber ?? undefined,
|
|
@@ -713,18 +714,53 @@ export async function getTasksByStatus(status: AgentTaskStatus): Promise<AgentTa
|
|
|
713
714
|
* in `src/types.ts`. SQL strings can't import a TS const — if you add a
|
|
714
715
|
* new terminal status, grep for `NOT IN ('completed'` across this file.
|
|
715
716
|
*/
|
|
716
|
-
|
|
717
|
+
/**
|
|
718
|
+
* Most recent active task for a provider + repo + PR/issue number. The
|
|
719
|
+
* provider is required: `owner/repo` exists on GitHub, GitLab and Bitbucket
|
|
720
|
+
* alike, so an unscoped lookup lets one host's webhook act on another host's
|
|
721
|
+
* task. A NULL `vcsProvider` is a legacy GitHub row (pre-provider writers), so
|
|
722
|
+
* the GitHub lookup still matches it.
|
|
723
|
+
*/
|
|
724
|
+
export async function findTaskByVcs(
|
|
725
|
+
vcsRepo: string,
|
|
726
|
+
vcsNumber: number,
|
|
727
|
+
vcsProvider: NonNullable<AgentTask["vcsProvider"]>,
|
|
728
|
+
): Promise<AgentTask | null> {
|
|
729
|
+
const providerClause =
|
|
730
|
+
vcsProvider === "github" ? "(vcsProvider = ? OR vcsProvider IS NULL)" : "vcsProvider = ?";
|
|
717
731
|
const row = await getDbClient().get<AgentTaskRow>(
|
|
718
732
|
`SELECT * FROM agent_tasks
|
|
719
|
-
WHERE vcsRepo = ? AND vcsNumber = ?
|
|
733
|
+
WHERE vcsRepo = ? AND vcsNumber = ? AND ${providerClause}
|
|
720
734
|
AND status NOT IN ('completed', 'failed', 'cancelled', 'superseded')
|
|
721
735
|
ORDER BY createdAt DESC
|
|
722
736
|
LIMIT 1`,
|
|
723
|
-
[vcsRepo, vcsNumber],
|
|
737
|
+
[vcsRepo, vcsNumber, vcsProvider],
|
|
724
738
|
);
|
|
725
739
|
return row ? rowToAgentTask(row) : null;
|
|
726
740
|
}
|
|
727
741
|
|
|
742
|
+
/**
|
|
743
|
+
* Every active task for a provider + repo + PR/issue number, newest first.
|
|
744
|
+
* Same scoping as `findTaskByVcs`; for callers that must settle all of them
|
|
745
|
+
* (a PR close leaves no task worth running).
|
|
746
|
+
*/
|
|
747
|
+
export async function findActiveTasksByVcs(
|
|
748
|
+
vcsRepo: string,
|
|
749
|
+
vcsNumber: number,
|
|
750
|
+
vcsProvider: NonNullable<AgentTask["vcsProvider"]>,
|
|
751
|
+
): Promise<AgentTask[]> {
|
|
752
|
+
const providerClause =
|
|
753
|
+
vcsProvider === "github" ? "(vcsProvider = ? OR vcsProvider IS NULL)" : "vcsProvider = ?";
|
|
754
|
+
const rows = await getDbClient().query<AgentTaskRow>(
|
|
755
|
+
`SELECT * FROM agent_tasks
|
|
756
|
+
WHERE vcsRepo = ? AND vcsNumber = ? AND ${providerClause}
|
|
757
|
+
AND status NOT IN ('completed', 'failed', 'cancelled', 'superseded')
|
|
758
|
+
ORDER BY createdAt DESC`,
|
|
759
|
+
[vcsRepo, vcsNumber, vcsProvider],
|
|
760
|
+
);
|
|
761
|
+
return rows.map(rowToAgentTask);
|
|
762
|
+
}
|
|
763
|
+
|
|
728
764
|
/** @deprecated Use findTaskByVcs instead */
|
|
729
765
|
export const findTaskByGitHub = findTaskByVcs;
|
|
730
766
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { WorkflowRunStatus } from "../../types";
|
|
2
2
|
import { scrubSecrets } from "../../utils/secret-scrubber";
|
|
3
|
+
import { scrubJsonValue } from "../scrub-json";
|
|
3
4
|
import { getDbClient } from "./runtime";
|
|
4
5
|
|
|
5
6
|
// Parent/child links between workflow runs (migration 203): a `sub-workflow`
|
|
@@ -115,3 +116,98 @@ function emitAfterCommit(childRunId: string, parentStepId: string): void {
|
|
|
115
116
|
);
|
|
116
117
|
});
|
|
117
118
|
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Insert a foreach parent's missing child step rows as `pending` (queued), all
|
|
122
|
+
* in one transaction, so a crash never leaves a partial child set and the join
|
|
123
|
+
* always sees every item. Children that already exist (any row for the child
|
|
124
|
+
* node id) are skipped inside the transaction, which makes a concurrent
|
|
125
|
+
* re-execution of the same parent insert nothing twice. Returns how many rows
|
|
126
|
+
* this call inserted.
|
|
127
|
+
*/
|
|
128
|
+
export async function createForeachChildSteps(
|
|
129
|
+
runId: string,
|
|
130
|
+
parentNodeId: string,
|
|
131
|
+
rows: Array<{
|
|
132
|
+
id: string;
|
|
133
|
+
nodeId: string;
|
|
134
|
+
nodeType: string;
|
|
135
|
+
input: unknown;
|
|
136
|
+
idempotencyKey: string;
|
|
137
|
+
}>,
|
|
138
|
+
): Promise<number> {
|
|
139
|
+
if (rows.length === 0) return 0;
|
|
140
|
+
const prefix = `${parentNodeId}#`;
|
|
141
|
+
return getDbClient().transaction(async (tx) => {
|
|
142
|
+
const existing = await tx.query<{ nodeId: string }>(
|
|
143
|
+
"SELECT DISTINCT nodeId FROM workflow_run_steps WHERE runId = ? AND substr(nodeId, 1, ?) = ?",
|
|
144
|
+
[runId, prefix.length, prefix],
|
|
145
|
+
);
|
|
146
|
+
const existingNodeIds = new Set(existing.map((row) => row.nodeId));
|
|
147
|
+
const now = new Date().toISOString();
|
|
148
|
+
let created = 0;
|
|
149
|
+
for (const row of rows) {
|
|
150
|
+
if (existingNodeIds.has(row.nodeId)) continue;
|
|
151
|
+
await tx.run(
|
|
152
|
+
`INSERT INTO workflow_run_steps (id, runId, nodeId, nodeType, status, startedAt, input, idempotencyKey)
|
|
153
|
+
VALUES (?, ?, ?, ?, 'pending', ?, ?, ?)`,
|
|
154
|
+
[
|
|
155
|
+
row.id,
|
|
156
|
+
runId,
|
|
157
|
+
row.nodeId,
|
|
158
|
+
row.nodeType,
|
|
159
|
+
now,
|
|
160
|
+
scrubJsonValue(row.input),
|
|
161
|
+
row.idempotencyKey,
|
|
162
|
+
],
|
|
163
|
+
);
|
|
164
|
+
existingNodeIds.add(row.nodeId);
|
|
165
|
+
created++;
|
|
166
|
+
}
|
|
167
|
+
return created;
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Insert a new `pending` attempt row for each foreach item being retried. Each
|
|
173
|
+
* row reuses its item's child node id and gets a fresh step id, so the body
|
|
174
|
+
* starts a new invocation (a new task, child run, approval or wait) while the
|
|
175
|
+
* earlier attempt rows stay as history. `attempt` is the number of rows the
|
|
176
|
+
* item already has, and the idempotency key `<runId>:<childNodeId>:<attempt>`
|
|
177
|
+
* is UNIQUE, so a duplicated retry of the same attempt inserts nothing.
|
|
178
|
+
* Call it inside the retry's transaction. Returns how many rows it inserted.
|
|
179
|
+
*/
|
|
180
|
+
export async function createForeachAttemptSteps(
|
|
181
|
+
runId: string,
|
|
182
|
+
rows: Array<{ nodeId: string; nodeType: string; itemKey: string; index: number }>,
|
|
183
|
+
): Promise<number> {
|
|
184
|
+
if (rows.length === 0) return 0;
|
|
185
|
+
return getDbClient().transaction(async (tx) => {
|
|
186
|
+
const now = new Date().toISOString();
|
|
187
|
+
let created = 0;
|
|
188
|
+
for (const row of rows) {
|
|
189
|
+
const count = await tx.get<{ n: number }>(
|
|
190
|
+
"SELECT COUNT(*) AS n FROM workflow_run_steps WHERE runId = ? AND nodeId = ?",
|
|
191
|
+
[runId, row.nodeId],
|
|
192
|
+
);
|
|
193
|
+
const attempt = count?.n ?? 0;
|
|
194
|
+
const inserted = await tx.get<{ id: string }>(
|
|
195
|
+
`INSERT INTO workflow_run_steps (id, runId, nodeId, nodeType, status, startedAt, input, idempotencyKey)
|
|
196
|
+
VALUES (?, ?, ?, ?, 'pending', ?, ?, ?)
|
|
197
|
+
ON CONFLICT(idempotencyKey) DO NOTHING
|
|
198
|
+
RETURNING id`,
|
|
199
|
+
[
|
|
200
|
+
crypto.randomUUID(),
|
|
201
|
+
runId,
|
|
202
|
+
row.nodeId,
|
|
203
|
+
row.nodeType,
|
|
204
|
+
now,
|
|
205
|
+
scrubJsonValue({ itemKey: row.itemKey, index: row.index, attempt }),
|
|
206
|
+
`${runId}:${row.nodeId}:${attempt}`,
|
|
207
|
+
],
|
|
208
|
+
);
|
|
209
|
+
if (inserted) created++;
|
|
210
|
+
}
|
|
211
|
+
return created;
|
|
212
|
+
});
|
|
213
|
+
}
|
package/src/be/db.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Database } from "bun:sqlite";
|
|
2
2
|
import pkg from "../../package.json";
|
|
3
|
-
import { defaultAssetKey, normalizeAssetKey } from "../assets/key";
|
|
3
|
+
import { defaultAssetKey, normalizeAssetKey, sessionGroupKey } from "../assets/key";
|
|
4
4
|
import {
|
|
5
5
|
generateDefaultClaudeMd,
|
|
6
6
|
generateDefaultIdentityMd,
|
|
@@ -195,7 +195,19 @@ import {
|
|
|
195
195
|
} from "./db/context-versions";
|
|
196
196
|
import { clearAgentModelProfileOnOverrideChange } from "./db/model-profiles";
|
|
197
197
|
import { getDb, getDbClient } from "./db/runtime";
|
|
198
|
-
import {
|
|
198
|
+
import {
|
|
199
|
+
assertAssetKeyMovesAllowed,
|
|
200
|
+
configureSessionMoveDependencies,
|
|
201
|
+
moveTaskChainKeyInTx,
|
|
202
|
+
} from "./db/session-moves";
|
|
203
|
+
import {
|
|
204
|
+
type SessionFilters,
|
|
205
|
+
type SessionReadState,
|
|
206
|
+
type SessionRowFields,
|
|
207
|
+
sessionCountQuery,
|
|
208
|
+
sessionPageFields,
|
|
209
|
+
sessionSummaryQuery,
|
|
210
|
+
} from "./db/session-tree";
|
|
199
211
|
import { classifyTaskHumanFree, reclassifyTaskHumanFree } from "./db/tasks/human-free";
|
|
200
212
|
import {
|
|
201
213
|
type AgentTaskRow,
|
|
@@ -269,6 +281,7 @@ export { recordKeySeatMismatch } from "./db/api-keys";
|
|
|
269
281
|
export {
|
|
270
282
|
type ApprovalRequestSummary,
|
|
271
283
|
type ApprovalVote,
|
|
284
|
+
getPendingApprovalsForTasks,
|
|
272
285
|
getPendingApprovalVoteState,
|
|
273
286
|
listApprovalRequestSummaries,
|
|
274
287
|
recordApprovalVotes,
|
|
@@ -281,6 +294,11 @@ export {
|
|
|
281
294
|
getLatestContextVersion,
|
|
282
295
|
} from "./db/context-versions";
|
|
283
296
|
export { getScriptRunExecutionArgs } from "./db/script-runs";
|
|
297
|
+
export {
|
|
298
|
+
AssetKeyMovesBlockedError,
|
|
299
|
+
assertAssetKeyMovesAllowed,
|
|
300
|
+
moveTaskChainKeyInTx,
|
|
301
|
+
} from "./db/session-moves";
|
|
284
302
|
|
|
285
303
|
configureAgentDependencies({
|
|
286
304
|
createLogEntry: (entry) => createLogEntry(entry),
|
|
@@ -300,6 +318,7 @@ export {
|
|
|
300
318
|
export {
|
|
301
319
|
type ExistingTrackerContextWork,
|
|
302
320
|
type ExistingTrackerContextWorkReason,
|
|
321
|
+
findActiveTasksByVcs,
|
|
303
322
|
findCompletedTaskInThread,
|
|
304
323
|
findExistingLinearTrackerContextWork,
|
|
305
324
|
findRecentCancelledTaskInThread,
|
|
@@ -387,6 +406,11 @@ configureTaskWriteDependencies({
|
|
|
387
406
|
cascadeFailDependents: (...args) => cascadeFailDependents(...args),
|
|
388
407
|
});
|
|
389
408
|
|
|
409
|
+
configureSessionMoveDependencies({
|
|
410
|
+
auditAssetKeys: () => auditAssetKeys(getDb()),
|
|
411
|
+
insertAssetKeyHistory: (...args) => insertAssetKeyHistory(...args),
|
|
412
|
+
});
|
|
413
|
+
|
|
390
414
|
configureTaskReadDependencies({
|
|
391
415
|
checkDependencies: (taskId) => checkDependencies(taskId),
|
|
392
416
|
assetKeyPrefixPattern: (input) => assetKeyPrefixPattern(input),
|
|
@@ -400,7 +424,7 @@ type TaskTelemetryContext = {
|
|
|
400
424
|
};
|
|
401
425
|
|
|
402
426
|
/** LIKE pattern matching strings that start with `prefix` literally (use with `ESCAPE '\\'`). */
|
|
403
|
-
function literalPrefixPattern(prefix: string): string {
|
|
427
|
+
export function literalPrefixPattern(prefix: string): string {
|
|
404
428
|
return `${prefix.replace(/[\\%_]/g, "\\$&")}%`;
|
|
405
429
|
}
|
|
406
430
|
|
|
@@ -1060,7 +1084,7 @@ export async function setSlackMessageTracking(
|
|
|
1060
1084
|
export async function updateTaskVcs(
|
|
1061
1085
|
taskId: string,
|
|
1062
1086
|
vcs: {
|
|
1063
|
-
vcsProvider: "github" | "gitlab" | "azure-devops";
|
|
1087
|
+
vcsProvider: "github" | "gitlab" | "azure-devops" | "bitbucket";
|
|
1064
1088
|
vcsRepo: string;
|
|
1065
1089
|
vcsNumber: number;
|
|
1066
1090
|
vcsUrl: string;
|
|
@@ -1742,17 +1766,7 @@ export async function moveAssetKey(input: {
|
|
|
1742
1766
|
descendantTaskIds?: readonly string[];
|
|
1743
1767
|
}): Promise<false | { movedTaskIds?: string[] }> {
|
|
1744
1768
|
const key = normalizeAssetKey(input.key);
|
|
1745
|
-
|
|
1746
|
-
// client transaction so this request-path read cannot observe another
|
|
1747
|
-
// request's uncommitted writes on the shared connection.
|
|
1748
|
-
const audit = await getDbClient().transaction(async () => auditAssetKeys(getDb()), {
|
|
1749
|
-
readOnly: true,
|
|
1750
|
-
});
|
|
1751
|
-
if (!audit.ok) {
|
|
1752
|
-
throw new Error(
|
|
1753
|
-
"Asset namespace moves are blocked until structural errors, unknown personal users, or provider mapping drift are repaired.",
|
|
1754
|
-
);
|
|
1755
|
-
}
|
|
1769
|
+
await assertAssetKeyMovesAllowed();
|
|
1756
1770
|
const previousKey = await currentAssetKey(input.entityType, input.id);
|
|
1757
1771
|
if (!previousKey) return false;
|
|
1758
1772
|
if (previousKey === key && input.entityType !== "task") return {};
|
|
@@ -1769,60 +1783,13 @@ export async function moveAssetKey(input: {
|
|
|
1769
1783
|
return getDbClient().transaction(async (tx) => {
|
|
1770
1784
|
switch (input.entityType) {
|
|
1771
1785
|
case "task": {
|
|
1772
|
-
const
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
const movedTaskIds: string[] = [];
|
|
1780
|
-
for (const task of tasks) {
|
|
1781
|
-
if (task.key === key) continue;
|
|
1782
|
-
// A file shared with another task keeps the namespace of the task whose attachment
|
|
1783
|
-
// sources its mapping. Mappings with no attachment source follow any attaching task.
|
|
1784
|
-
const mappedFiles = await tx.query<{ id: string; key: string }>(
|
|
1785
|
-
`SELECT DISTINCT m.id, m."key" AS key
|
|
1786
|
-
FROM asset_key_mappings m
|
|
1787
|
-
JOIN task_attachments a
|
|
1788
|
-
ON m.provider_id = COALESCE(NULLIF(a.provider_id, ''), 'agent-fs')
|
|
1789
|
-
AND m.provider_org_id = COALESCE(a.agent_fs_org_id, '')
|
|
1790
|
-
AND m.provider_drive_id = COALESCE(a.agent_fs_drive_id, '')
|
|
1791
|
-
AND m.provider_key = COALESCE(NULLIF(a.provider_key, ''), a.path)
|
|
1792
|
-
WHERE a.task_id = ?
|
|
1793
|
-
AND (m.source_entity_type IS NOT 'task-attachment'
|
|
1794
|
-
OR m.source_entity_id IN (SELECT id FROM task_attachments WHERE task_id = ?))`,
|
|
1795
|
-
[task.id, task.id],
|
|
1796
|
-
);
|
|
1797
|
-
// The namespace is metadata, and lastUpdatedAt tracks task activity, as in updateTaskTitle.
|
|
1798
|
-
await tx.run('UPDATE agent_tasks SET "key" = ?, updated_by = ? WHERE id = ?', [
|
|
1799
|
-
key,
|
|
1800
|
-
input.changedBy ?? null,
|
|
1801
|
-
task.id,
|
|
1802
|
-
]);
|
|
1803
|
-
for (const mapping of mappedFiles) {
|
|
1804
|
-
if (mapping.key === key) continue;
|
|
1805
|
-
await tx.run(
|
|
1806
|
-
'UPDATE asset_key_mappings SET "key" = ?, updated_at = ?, updated_by = ? WHERE id = ?',
|
|
1807
|
-
[key, now, input.changedBy ?? null, mapping.id],
|
|
1808
|
-
);
|
|
1809
|
-
await insertAssetKeyHistory({
|
|
1810
|
-
entityType: "file",
|
|
1811
|
-
entityId: mapping.id,
|
|
1812
|
-
previousKey: mapping.key,
|
|
1813
|
-
newKey: key,
|
|
1814
|
-
changedBy: input.changedBy,
|
|
1815
|
-
});
|
|
1816
|
-
}
|
|
1817
|
-
await insertAssetKeyHistory({
|
|
1818
|
-
entityType: "task",
|
|
1819
|
-
entityId: task.id,
|
|
1820
|
-
previousKey: task.key,
|
|
1821
|
-
newKey: key,
|
|
1822
|
-
changedBy: input.changedBy,
|
|
1823
|
-
});
|
|
1824
|
-
movedTaskIds.push(task.id);
|
|
1825
|
-
}
|
|
1786
|
+
const movedTaskIds = await moveTaskChainKeyInTx(
|
|
1787
|
+
input.id,
|
|
1788
|
+
input.descendantTaskIds ?? [],
|
|
1789
|
+
key,
|
|
1790
|
+
input.changedBy,
|
|
1791
|
+
);
|
|
1792
|
+
if (movedTaskIds === false) return false;
|
|
1826
1793
|
return { movedTaskIds };
|
|
1827
1794
|
}
|
|
1828
1795
|
case "workflow":
|
|
@@ -2605,7 +2572,7 @@ export async function createTaskExtended(
|
|
|
2605
2572
|
}
|
|
2606
2573
|
|
|
2607
2574
|
// VCS identity (GitHub / GitLab issue / PR / MR + webhook routing)
|
|
2608
|
-
// Webhook handlers locate active work via `findTaskByVcs(repo, number)`,
|
|
2575
|
+
// Webhook handlers locate active work via `findTaskByVcs(repo, number, provider)`,
|
|
2609
2576
|
// so a resume / follow-up child MUST carry the full VCS identity or
|
|
2610
2577
|
// subsequent review/update events get dropped.
|
|
2611
2578
|
if (parent.vcsProvider && !options.vcsProvider) {
|
|
@@ -12921,7 +12888,7 @@ export async function getRootTaskChain(rootTaskId: string): Promise<AgentTask[]>
|
|
|
12921
12888
|
return rows.map(rowToAgentTask);
|
|
12922
12889
|
}
|
|
12923
12890
|
|
|
12924
|
-
export interface SessionListItem {
|
|
12891
|
+
export interface SessionListItem extends SessionRowFields {
|
|
12925
12892
|
root: AgentTask;
|
|
12926
12893
|
chainTaskCount: number;
|
|
12927
12894
|
lastActivityAt: string;
|
|
@@ -12934,42 +12901,18 @@ export interface SessionListItem {
|
|
|
12934
12901
|
* The session list only renders a brief of the root; the full root + chain are
|
|
12935
12902
|
* on `GET /api/sessions/{rootTaskId}`.
|
|
12936
12903
|
*/
|
|
12937
|
-
export interface SessionListItemSummary {
|
|
12904
|
+
export interface SessionListItemSummary extends SessionRowFields {
|
|
12938
12905
|
root: AgentTaskSummary;
|
|
12939
12906
|
chainTaskCount: number;
|
|
12940
12907
|
lastActivityAt: string;
|
|
12941
12908
|
latestStatus: AgentTaskStatus;
|
|
12942
12909
|
}
|
|
12943
12910
|
|
|
12944
|
-
|
|
12945
|
-
* List the most recent sessions ordered by chain-wide latest activity.
|
|
12946
|
-
* A "session" here is any task with `parentTaskId IS NULL` — its descendants
|
|
12947
|
-
* (children, grand-children, …) are summarized via the recursive CTE.
|
|
12948
|
-
*
|
|
12949
|
-
* Single-pass CTE: seeds with root tasks matching the filter, walks the full
|
|
12950
|
-
* descendant tree once, then aggregates chainCount / lastActivityAt /
|
|
12951
|
-
* latestStatus in two lightweight non-recursive CTEs — replacing the original
|
|
12952
|
-
* pattern of 3 correlated subqueries each re-running the recursion per row.
|
|
12953
|
-
*/
|
|
12954
|
-
interface ListRecentSessionsOpts {
|
|
12911
|
+
interface ListRecentSessionsOpts extends SessionFilters {
|
|
12955
12912
|
limit?: number;
|
|
12956
12913
|
offset?: number;
|
|
12957
|
-
/**
|
|
12958
|
-
source?: string[];
|
|
12959
|
-
/** Case-insensitive substring match against `r.task`. */
|
|
12960
|
-
q?: string;
|
|
12961
|
-
/** When set, restrict to root tasks where `requestedByUserId` equals this value. NULL rows are excluded. */
|
|
12962
|
-
requestedByUserId?: string;
|
|
12963
|
-
/**
|
|
12964
|
-
* When set, restrict to root tasks whose `contextKey` starts with this
|
|
12965
|
-
* literal prefix (`%`, `_` and `\` are escaped). Powers the UI contextual
|
|
12966
|
-
* session panel's per-page dropdown.
|
|
12967
|
-
*/
|
|
12968
|
-
contextKeyPrefix?: string;
|
|
12969
|
-
/** When true, return slim `SessionListItemSummary` rows (default: full). */
|
|
12914
|
+
/** Return a slim root task summary instead of the full task. */
|
|
12970
12915
|
slim?: boolean;
|
|
12971
|
-
/** Restrict to sessions this viewer may open (session sharing). */
|
|
12972
|
-
viewer?: SessionViewer;
|
|
12973
12916
|
}
|
|
12974
12917
|
|
|
12975
12918
|
export async function listRecentSessions(
|
|
@@ -12979,149 +12922,50 @@ export async function listRecentSessions(
|
|
|
12979
12922
|
opts: ListRecentSessionsOpts & { slim: true },
|
|
12980
12923
|
): Promise<SessionListItemSummary[]>;
|
|
12981
12924
|
export async function listRecentSessions(
|
|
12982
|
-
opts
|
|
12925
|
+
opts: ListRecentSessionsOpts = {},
|
|
12983
12926
|
): Promise<SessionListItem[] | SessionListItemSummary[]> {
|
|
12984
|
-
const
|
|
12985
|
-
const offset = opts?.offset ?? 0;
|
|
12986
|
-
const sources = opts?.source?.filter((s) => s.length > 0) ?? [];
|
|
12987
|
-
const q = opts?.q?.trim();
|
|
12988
|
-
const requestedByUserId = opts?.requestedByUserId?.trim() || undefined;
|
|
12989
|
-
|
|
12990
|
-
const conditions: string[] = ["r.parentTaskId IS NULL"];
|
|
12991
|
-
const params: (string | number | null)[] = [];
|
|
12992
|
-
|
|
12993
|
-
if (sources.length > 0) {
|
|
12994
|
-
conditions.push(`r.source IN (${sources.map(() => "?").join(", ")})`);
|
|
12995
|
-
params.push(...sources);
|
|
12996
|
-
}
|
|
12997
|
-
if (q && q.length > 0) {
|
|
12998
|
-
conditions.push("(lower(r.task) LIKE ? OR lower(COALESCE(r.title, '')) LIKE ?)");
|
|
12999
|
-
const like = `%${q.toLowerCase()}%`;
|
|
13000
|
-
params.push(like, like);
|
|
13001
|
-
}
|
|
13002
|
-
if (requestedByUserId) {
|
|
13003
|
-
conditions.push("r.requestedByUserId = ?");
|
|
13004
|
-
params.push(requestedByUserId);
|
|
13005
|
-
}
|
|
13006
|
-
if (opts?.contextKeyPrefix) {
|
|
13007
|
-
conditions.push(`r.contextKey LIKE ? ESCAPE '\\'`);
|
|
13008
|
-
params.push(literalPrefixPattern(opts.contextKeyPrefix));
|
|
13009
|
-
}
|
|
13010
|
-
if (opts?.viewer) {
|
|
13011
|
-
const viewer = sessionViewerCondition(opts.viewer);
|
|
13012
|
-
conditions.push(viewer.sql);
|
|
13013
|
-
params.push(...viewer.params);
|
|
13014
|
-
}
|
|
13015
|
-
params.push(limit, offset);
|
|
13016
|
-
|
|
12927
|
+
const query = sessionSummaryQuery(opts);
|
|
13017
12928
|
const rootRows = await getDbClient().query<
|
|
13018
|
-
AgentTaskRow & {
|
|
12929
|
+
AgentTaskRow & {
|
|
12930
|
+
__chainCount: number;
|
|
12931
|
+
__lastActivityAt: string;
|
|
12932
|
+
__latestStatus: AgentTaskStatus;
|
|
12933
|
+
__readState: SessionReadState | null;
|
|
12934
|
+
__snoozeUntil: string | null;
|
|
12935
|
+
__needsYou: number;
|
|
12936
|
+
}
|
|
13019
12937
|
>(
|
|
13020
|
-
`
|
|
13021
|
-
|
|
13022
|
-
|
|
13023
|
-
|
|
13024
|
-
|
|
13025
|
-
|
|
13026
|
-
|
|
13027
|
-
|
|
13028
|
-
|
|
13029
|
-
|
|
13030
|
-
|
|
13031
|
-
|
|
13032
|
-
|
|
13033
|
-
|
|
13034
|
-
|
|
13035
|
-
|
|
13036
|
-
|
|
13037
|
-
|
|
13038
|
-
|
|
13039
|
-
|
|
13040
|
-
|
|
13041
|
-
|
|
13042
|
-
|
|
13043
|
-
|
|
13044
|
-
|
|
13045
|
-
a.chainCount AS __chainCount,
|
|
13046
|
-
a.lastActivityAt AS __lastActivityAt,
|
|
13047
|
-
COALESCE(ls.latestStatus, r.status) AS __latestStatus
|
|
13048
|
-
FROM agent_tasks r
|
|
13049
|
-
JOIN agg a ON a.root_id = r.id
|
|
13050
|
-
LEFT JOIN latest_status ls ON ls.root_id = r.id
|
|
13051
|
-
ORDER BY a.lastActivityAt DESC, r.createdAt DESC, r.id DESC
|
|
13052
|
-
LIMIT ? OFFSET ?`,
|
|
13053
|
-
params,
|
|
13054
|
-
);
|
|
13055
|
-
|
|
13056
|
-
if (opts?.slim) {
|
|
13057
|
-
return rootRows.map((row): SessionListItemSummary => {
|
|
13058
|
-
const { __chainCount, __lastActivityAt, __latestStatus, ...taskRow } = row;
|
|
13059
|
-
return {
|
|
13060
|
-
root: rowToAgentTaskSummary(taskRow as AgentTaskRow),
|
|
13061
|
-
chainTaskCount: __chainCount,
|
|
13062
|
-
lastActivityAt: __lastActivityAt ?? row.lastUpdatedAt,
|
|
13063
|
-
latestStatus: (__latestStatus as AgentTaskStatus) ?? row.status,
|
|
13064
|
-
};
|
|
13065
|
-
});
|
|
13066
|
-
}
|
|
13067
|
-
|
|
13068
|
-
return rootRows.map((row): SessionListItem => {
|
|
13069
|
-
const { __chainCount, __lastActivityAt, __latestStatus, ...taskRow } = row;
|
|
13070
|
-
return {
|
|
13071
|
-
root: rowToAgentTask(taskRow as AgentTaskRow),
|
|
13072
|
-
chainTaskCount: __chainCount,
|
|
13073
|
-
lastActivityAt: __lastActivityAt ?? row.lastUpdatedAt,
|
|
13074
|
-
latestStatus: (__latestStatus as AgentTaskStatus) ?? row.status,
|
|
13075
|
-
};
|
|
13076
|
-
});
|
|
12938
|
+
`SELECT r.*, a.chainCount AS __chainCount, a.lastActivityAt AS __lastActivityAt,
|
|
12939
|
+
a.latestStatus AS __latestStatus, a.readState AS __readState,
|
|
12940
|
+
a.snoozeUntil AS __snoozeUntil, a.needsYou AS __needsYou
|
|
12941
|
+
FROM (${query.sql}) a CROSS JOIN agent_tasks r ON r.id = a.root_id
|
|
12942
|
+
ORDER BY ${opts.triage === "later" ? "a.snoozeUntil ASC," : ""}
|
|
12943
|
+
a.lastActivityAt DESC, r.createdAt DESC, r.id DESC LIMIT ? OFFSET ?`,
|
|
12944
|
+
[...query.params, opts.limit ?? 25, opts.offset ?? 0],
|
|
12945
|
+
);
|
|
12946
|
+
const fields = await sessionPageFields(
|
|
12947
|
+
rootRows.map((row) => ({
|
|
12948
|
+
id: row.id,
|
|
12949
|
+
requestedByUserId: row.requestedByUserId ?? undefined,
|
|
12950
|
+
})),
|
|
12951
|
+
);
|
|
12952
|
+
return rootRows.map((row) => ({
|
|
12953
|
+
root: opts.slim ? rowToAgentTaskSummary(row) : rowToAgentTask(row),
|
|
12954
|
+
chainTaskCount: row.__chainCount,
|
|
12955
|
+
lastActivityAt: row.__lastActivityAt,
|
|
12956
|
+
latestStatus: row.__latestStatus,
|
|
12957
|
+
...(row.__readState ? { readState: row.__readState } : {}),
|
|
12958
|
+
snoozeUntil: row.__snoozeUntil,
|
|
12959
|
+
needsYou: !!row.__needsYou,
|
|
12960
|
+
groupKey: sessionGroupKey(row.key),
|
|
12961
|
+
...fields.get(row.id),
|
|
12962
|
+
}));
|
|
13077
12963
|
}
|
|
13078
12964
|
|
|
13079
|
-
/**
|
|
13080
|
-
|
|
13081
|
-
|
|
13082
|
-
|
|
13083
|
-
* a plain count, no recursive chain walk needed.
|
|
13084
|
-
*/
|
|
13085
|
-
export async function countSessions(
|
|
13086
|
-
opts?: Pick<
|
|
13087
|
-
ListRecentSessionsOpts,
|
|
13088
|
-
"source" | "q" | "requestedByUserId" | "contextKeyPrefix" | "viewer"
|
|
13089
|
-
>,
|
|
13090
|
-
): Promise<number> {
|
|
13091
|
-
const sources = opts?.source?.filter((s) => s.length > 0) ?? [];
|
|
13092
|
-
const q = opts?.q?.trim();
|
|
13093
|
-
const requestedByUserId = opts?.requestedByUserId?.trim() || undefined;
|
|
13094
|
-
|
|
13095
|
-
const conditions: string[] = ["r.parentTaskId IS NULL"];
|
|
13096
|
-
const params: (string | null)[] = [];
|
|
13097
|
-
|
|
13098
|
-
if (sources.length > 0) {
|
|
13099
|
-
conditions.push(`r.source IN (${sources.map(() => "?").join(", ")})`);
|
|
13100
|
-
params.push(...sources);
|
|
13101
|
-
}
|
|
13102
|
-
if (q && q.length > 0) {
|
|
13103
|
-
conditions.push("(lower(r.task) LIKE ? OR lower(COALESCE(r.title, '')) LIKE ?)");
|
|
13104
|
-
const like = `%${q.toLowerCase()}%`;
|
|
13105
|
-
params.push(like, like);
|
|
13106
|
-
}
|
|
13107
|
-
if (requestedByUserId) {
|
|
13108
|
-
conditions.push("r.requestedByUserId = ?");
|
|
13109
|
-
params.push(requestedByUserId);
|
|
13110
|
-
}
|
|
13111
|
-
if (opts?.contextKeyPrefix) {
|
|
13112
|
-
conditions.push(`r.contextKey LIKE ? ESCAPE '\\'`);
|
|
13113
|
-
params.push(literalPrefixPattern(opts.contextKeyPrefix));
|
|
13114
|
-
}
|
|
13115
|
-
if (opts?.viewer) {
|
|
13116
|
-
const viewer = sessionViewerCondition(opts.viewer);
|
|
13117
|
-
conditions.push(viewer.sql);
|
|
13118
|
-
params.push(...viewer.params);
|
|
13119
|
-
}
|
|
13120
|
-
|
|
13121
|
-
const row = await getDbClient().get<{ count: number }>(
|
|
13122
|
-
`SELECT COUNT(*) AS count FROM agent_tasks r WHERE ${conditions.join(" AND ")}`,
|
|
13123
|
-
params,
|
|
13124
|
-
);
|
|
12965
|
+
/** Count roots directly unless a filter needs chain activity or participants. */
|
|
12966
|
+
export async function countSessions(opts: SessionFilters = {}): Promise<number> {
|
|
12967
|
+
const query = sessionCountQuery(opts);
|
|
12968
|
+
const row = await getDbClient().get<{ count: number }>(query.sql, query.params);
|
|
13125
12969
|
return row?.count ?? 0;
|
|
13126
12970
|
}
|
|
13127
12971
|
|
package/src/be/onboarding.ts
CHANGED
|
@@ -58,6 +58,7 @@ export const OnboardingIntegrationMethodSchema = z.enum([
|
|
|
58
58
|
"slack",
|
|
59
59
|
"github",
|
|
60
60
|
"gitlab",
|
|
61
|
+
"bitbucket",
|
|
61
62
|
"linear_oauth",
|
|
62
63
|
"jira_oauth",
|
|
63
64
|
]);
|
|
@@ -139,6 +140,7 @@ export const OnboardingSignalsSchema = z.object({
|
|
|
139
140
|
slack: z.boolean(),
|
|
140
141
|
github: z.boolean(),
|
|
141
142
|
gitlab: z.boolean(),
|
|
143
|
+
bitbucket: z.boolean(),
|
|
142
144
|
linear: z.boolean(),
|
|
143
145
|
jira: z.boolean(),
|
|
144
146
|
}),
|
|
@@ -480,6 +482,7 @@ function inferIntegrationMethod(
|
|
|
480
482
|
if (signals.integrations.slack) return "slack";
|
|
481
483
|
if (signals.integrations.github) return "github";
|
|
482
484
|
if (signals.integrations.gitlab) return "gitlab";
|
|
485
|
+
if (signals.integrations.bitbucket) return "bitbucket";
|
|
483
486
|
if (signals.integrations.linear) return "linear_oauth";
|
|
484
487
|
if (signals.integrations.jira) return "jira_oauth";
|
|
485
488
|
return null;
|
package/src/be/skill-sync.ts
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { homedir } from "node:os";
|
|
13
|
+
import type { SkillFile, SkillWithInstallInfo } from "../types";
|
|
13
14
|
import {
|
|
14
15
|
type SkillFsEntry,
|
|
15
16
|
type SkillHarnessTarget,
|
|
@@ -71,8 +72,7 @@ export interface SkillsSignature {
|
|
|
71
72
|
* uninstall, toggle, or skill-update mutates at least one of them. Output is
|
|
72
73
|
* deterministic and contains no timestamps beyond per-row mutation fields.
|
|
73
74
|
*/
|
|
74
|
-
export
|
|
75
|
-
const skills = await getAgentSkills(agentId);
|
|
75
|
+
export function computeSkillsSignature(skills: readonly SkillWithInstallInfo[]): SkillsSignature {
|
|
76
76
|
const sorted = [...skills].sort((a, b) => a.id.localeCompare(b.id));
|
|
77
77
|
const canonical = JSON.stringify(
|
|
78
78
|
sorted.map((s) => [
|
|
@@ -89,3 +89,44 @@ export async function computeAgentSkillsSignature(agentId: string): Promise<Skil
|
|
|
89
89
|
const hash = new Bun.CryptoHasher("sha256").update(canonical).digest("hex");
|
|
90
90
|
return { hash, count: sorted.length };
|
|
91
91
|
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Fetch the agent's skills and sign them.
|
|
95
|
+
*
|
|
96
|
+
* Callers that already hold the rows (the bundle endpoint) must use
|
|
97
|
+
* `computeSkillsSignature` on those exact rows instead of calling this: a
|
|
98
|
+
* second read can observe a newer edit and pair a fresh hash with stale
|
|
99
|
+
* payload bytes, which makes later signature probes skip the refresh forever.
|
|
100
|
+
*/
|
|
101
|
+
export async function computeAgentSkillsSignature(agentId: string): Promise<SkillsSignature> {
|
|
102
|
+
return computeSkillsSignature(await getAgentSkills(agentId));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface SkillsBundle {
|
|
106
|
+
skills: (SkillWithInstallInfo & { files: SkillFile[] })[];
|
|
107
|
+
total: number;
|
|
108
|
+
signature: string;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Build the worker boot payload: every installed skill plus its bundled files,
|
|
113
|
+
* signed over the rows this same call returned.
|
|
114
|
+
*
|
|
115
|
+
* The readers are injected so the ordering the fix depends on is testable: the
|
|
116
|
+
* signature is taken before the first `readFiles`, so an edit that lands while
|
|
117
|
+
* the files are being read cannot pair stale bytes with a newer hash.
|
|
118
|
+
*/
|
|
119
|
+
export async function buildSkillsBundle(
|
|
120
|
+
readSkills: () => Promise<SkillWithInstallInfo[]>,
|
|
121
|
+
readFiles: (skillId: string) => Promise<SkillFile[]>,
|
|
122
|
+
): Promise<SkillsBundle> {
|
|
123
|
+
const skills = await readSkills();
|
|
124
|
+
const signature = computeSkillsSignature(skills).hash;
|
|
125
|
+
const entries = await Promise.all(
|
|
126
|
+
skills.map(async (skill) => ({
|
|
127
|
+
...skill,
|
|
128
|
+
files: skill.isComplex ? await readFiles(skill.id) : [],
|
|
129
|
+
})),
|
|
130
|
+
);
|
|
131
|
+
return { skills: entries, total: entries.length, signature };
|
|
132
|
+
}
|