@desplega.ai/agent-swarm 2.7.5 → 2.7.7
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/dist/{acp-adapter-g3ads55s.js → acp-adapter-7b4vk3h5.js} +5 -4
- package/dist/{actions-zwfykqqk.js → actions-3tt153af.js} +18 -17
- package/dist/{amp-adapter-z3s0ssdt.js → amp-adapter-njpts0yr.js} +1 -1
- package/dist/{anthropic-messages-x4q6rfq5.js → anthropic-messages-xey2skwx.js} +7 -7
- package/dist/{app-vmp4fgep.js → app-nky4378f.js} +3 -3
- package/dist/{assistant-vwde653f.js → assistant-jnb669pa.js} +24 -22
- package/dist/{azure-openai-responses-s1w5n70z.js → azure-openai-responses-26t7j9ra.js} +5 -5
- package/dist/{boot-reembed-375t7tbk.js → boot-reembed-9mgvaztt.js} +1 -1
- package/dist/{boot-reembed-gq9rcpnj.js → boot-reembed-qs2djdej.js} +1 -1
- package/dist/{boot-scrub-sweep-nd938kyf.js → boot-scrub-sweep-yjpdj7s1.js} +3 -3
- package/dist/{boot-seeds-aykx51v3.js → boot-seeds-5cm6ambd.js} +4 -4
- package/dist/{boxd-k1j26g3h.js → boxd-nv507va8.js} +5 -5
- package/dist/{claude-adapter-8j4hm099.js → claude-adapter-fn287zmm.js} +5 -4
- package/dist/{claude-managed-setup-0wyynpn1.js → claude-managed-setup-wtdz2mj2.js} +1 -1
- package/dist/{claude-sdk-session-q6wj5hmy.js → claude-sdk-session-x91kqyhm.js} +9677 -9514
- package/dist/{cli-j3jvnpe8.js → cli-08bmz9z9.js} +1 -1
- package/dist/{cli-gjr2y2xk.js → cli-0agcv1v5.js} +4 -1
- package/dist/{cli-y24sc29k.js → cli-1c6tzt8g.js} +30 -8
- package/dist/{cli-mjzynrse.js → cli-35rsp8ye.js} +1 -1
- package/dist/{cli-rpm2sgv0.js → cli-3fg0bdcv.js} +19 -2
- package/dist/{cli-nn4d8dne.js → cli-5b456bpf.js} +2 -2
- package/dist/{cli-bhjt02p8.js → cli-5jnhv72d.js} +1 -1
- package/dist/{cli-fqzx7gwa.js → cli-5zx699j9.js} +2 -2
- package/dist/{cli-hf6zt5m6.js → cli-69szn7w7.js} +17 -1
- package/dist/{cli-vqypxzh4.js → cli-6g0hb4cp.js} +8 -8
- package/dist/{cli-tqgg40ek.js → cli-6pfd54vx.js} +9 -8
- package/dist/{cli-bsn8nhrq.js → cli-6vcp76qk.js} +3 -3
- package/dist/{cli-zppwg9p9.js → cli-72v799xn.js} +1 -1
- package/dist/cli-82j36f2m.js +45 -0
- package/dist/{cli-hn9hxmz9.js → cli-8305gbq0.js} +29 -16
- package/dist/{cli-z8eh3ex8.js → cli-8sckt74b.js} +8 -8
- package/dist/{cli-bnz7a7qf.js → cli-95jzxcp3.js} +1 -1
- package/dist/{cli-0bxqnvq9.js → cli-adeegh7d.js} +29 -7
- package/dist/{cli-g61qxmge.js → cli-afkqkrtw.js} +3 -3
- package/dist/{cli-7a42nfy0.js → cli-ayayvfg4.js} +24 -4
- package/dist/{cli-bqt1mptt.js → cli-az48n0nr.js} +4 -4
- package/dist/{cli-z6jkx23q.js → cli-bfv7vdg6.js} +9 -9
- package/dist/{cli-pywf3eh5.js → cli-caj33vcq.js} +5 -5
- package/dist/{cli-5pag6rmn.js → cli-cb2a8pna.js} +1 -1
- package/dist/{cli-wvaawacd.js → cli-cppkf404.js} +5236 -72
- package/dist/{cli-mry0qzym.js → cli-ddaanqkd.js} +123 -10
- package/dist/{cli-0gey5fzy.js → cli-e0kmphd6.js} +2 -2
- package/dist/{cli-zh3fp8v2.js → cli-e46hg0xw.js} +1 -1
- package/dist/{cli-jm2vfpfd.js → cli-e5bzrn01.js} +1 -1
- package/dist/{cli-qxnf8z2z.js → cli-excnnjg2.js} +165 -153
- package/dist/{cli-71ptav7x.js → cli-ferkkrg9.js} +1 -1
- package/dist/{cli-vpf89f34.js → cli-fmjd0t1y.js} +3 -3
- package/dist/{cli-rm6hqj3z.js → cli-fyt2g4wd.js} +33 -139
- package/dist/cli-g08zdaec.js +55 -0
- package/dist/{cli-ehq736cn.js → cli-g7s47cc5.js} +4 -4
- package/dist/{cli-82ae98pz.js → cli-gz6q4eve.js} +3 -3
- package/dist/{cli-82jg7fp9.js → cli-he5nxaa8.js} +1 -1
- package/dist/{cli-rqkxrmgw.js → cli-je518txm.js} +39 -14
- package/dist/{cli-8hs41zy2.js → cli-jtjtabxe.js} +1 -1
- package/dist/{cli-611a1294.js → cli-k0wwfqbg.js} +18 -1
- package/dist/{cli-nzxr556s.js → cli-k1nje3v3.js} +38 -43
- package/dist/{cli-3h4nhjn8.js → cli-k8196sh2.js} +9 -7
- package/dist/{cli-afrrppsw.js → cli-k899ehvh.js} +2 -2
- package/dist/{cli-0731hvbw.js → cli-m0da8j35.js} +23 -9
- package/dist/cli-m2z8jagn.js +12078 -0
- package/dist/{cli-8veytvbh.js → cli-mkk9rnzz.js} +5 -5
- package/dist/{cli-7wspm5gm.js → cli-n6bv12jn.js} +528 -6
- package/dist/{cli-spknvxbq.js → cli-nmzhp1b5.js} +14 -12
- package/dist/{cli-p599f1qm.js → cli-p82cnnjq.js} +1 -1
- package/dist/{cli-xtjhfmpc.js → cli-pc6n7fre.js} +1 -1
- package/dist/{cli-vbyfwh92.js → cli-pjctyea3.js} +2 -2
- package/dist/{cli-nmbmy64z.js → cli-pq73h5mz.js} +3 -3
- package/dist/{cli-aa6yxc7m.js → cli-pz4rk021.js} +1 -1
- package/dist/{cli-7wcyk2s5.js → cli-q3tkegby.js} +1 -1
- package/dist/cli-qghe0h66.js +67 -0
- package/dist/{cli-6s7vdcta.js → cli-qh4d4tvk.js} +4 -4
- package/dist/{cli-mmwm7zbf.js → cli-rtt8g4v9.js} +1 -1
- package/dist/{cli-kg8hchyg.js → cli-sa02jsqt.js} +1 -1
- package/dist/{cli-1h3wcpbw.js → cli-snpj5zbj.js} +2 -2
- package/dist/{cli-a7v854rf.js → cli-tgrv9cjx.js} +263 -12
- package/dist/{cli-a47xy8eq.js → cli-vz22fyqv.js} +186 -54
- package/dist/{cli-1ayk1eas.js → cli-wk5yrfwd.js} +285 -42
- package/dist/{cli-1338796d.js → cli-ws19768n.js} +1755 -119
- package/dist/{cli-70r77n04.js → cli-xae4g51r.js} +1 -1
- package/dist/{cli-gf5hbmdr.js → cli-yjdgea32.js} +3 -3
- package/dist/{cli-xbkdm710.js → cli-yndmfxb3.js} +3 -3
- package/dist/{cli-rhw40jgb.js → cli-yyj3j5m4.js} +2 -2
- package/dist/cli.js +26 -25
- package/dist/{cloudflare-workers-ai-system-one-bm60fjm0.js → cloudflare-workers-ai-system-one-ef2a980q.js} +2 -2
- package/dist/{codex-adapter-q6k3gnyg.js → codex-adapter-cf2yxe9m.js} +3 -2
- package/dist/{codex-hook-9efm2x1m.js → codex-hook-a0c6g6t6.js} +1 -1
- package/dist/{codex-session-runner-3mwmk3d9.js → codex-session-runner-6vfzdgbd.js} +3 -2
- package/dist/{commands-evrenjn9.js → commands-t3mfy7vn.js} +9 -3
- package/dist/{controller-xjp8vswh.js → controller-q7xrbaz0.js} +8 -3
- package/dist/{controllers-connect-pp5jnxvc.js → controllers-connect-c6nm70kg.js} +20 -19
- package/dist/{cursor-adapter-yf47w4t0.js → cursor-adapter-ftd81ga8.js} +1 -1
- package/dist/{db-zxpw6aht.js → db-dk572x25.js} +3 -3
- package/dist/{db-rgfsh91f.js → db-vemtpmcc.js} +14 -4
- package/dist/{devtools-82fzqra8.js → devtools-qxfgbsqz.js} +1 -1
- package/dist/{dispatcher-smasgws7.js → dispatcher-kh7ecxh9.js} +17 -16
- package/dist/{docker-rrjhaqt9.js → docker-c1a3gqmf.js} +89 -22
- package/dist/{dsh-adapter-ftyy8dnp.js → dsh-adapter-v8628r89.js} +1 -1
- package/dist/{e2b-w1p0bnpm.js → e2b-fr12yccx.js} +5 -5
- package/dist/{e2b-qcaj675y.js → e2b-x6ywzak3.js} +3 -3
- package/dist/{easter-egg-3d-yyts7p16.js → easter-egg-3d-nenw4x63.js} +1 -1
- package/dist/{embedding-space-1z0mbg95.js → embedding-space-0jepb239.js} +6 -6
- package/dist/{embeddings-ygeg7tm7.js → embeddings-xxt49yxg.js} +1 -1
- package/dist/{execute-14qhqp4d.js → execute-1mhw6e4x.js} +1 -1
- package/dist/{external-channel-ttgrs3sx.js → external-channel-55v12e60.js} +2 -2
- package/dist/{fx-adapter-gt6pncj2.js → fx-adapter-bs8wqrk5.js} +6 -5
- package/dist/{google-generative-ai-yvmdxyxa.js → google-generative-ai-v94hr7h0.js} +5 -5
- package/dist/{google-vertex-jrtdh9gs.js → google-vertex-g7vr6cf8.js} +5 -5
- package/dist/{grok-adapter-sm4m5k4g.js → grok-adapter-gpjqmkcq.js} +6 -5
- package/dist/handlers-zxmp1ta4.js +32 -0
- package/dist/{heartbeat-qahz4p62.js → heartbeat-k42svnr0.js} +20 -19
- package/dist/{hook-4fd5wq5w.js → hook-114m7hap.js} +4 -3
- package/dist/{hook-kj3d398m.js → hook-6nphweh0.js} +24 -9
- package/dist/{http-1ted0j1k.js → http-r8cc4wz7.js} +1196 -211
- package/dist/index-a8phxqnk.js +37 -0
- package/dist/{index-q4b917zs.js → index-e3s8wmxy.js} +1 -1
- package/dist/{index-6rq3r5zb.js → index-fpk4bcd9.js} +1 -1
- package/dist/{index-5rk6rfzr.js → index-fvzke5kx.js} +1 -1
- package/dist/index-k6h00t4c.js +30 -0
- package/dist/{index-7v51awbn.js → index-nh3wyqdt.js} +1027 -577
- package/dist/{index-yqhz88yy.js → index-pxn30mse.js} +8 -8
- package/dist/index-xrz7te8h.js +33 -0
- package/dist/{keepalive-6cf311t5.js → keepalive-x060nt9p.js} +3 -3
- package/dist/lead-956sd2yc.js +71 -0
- package/dist/{maintenance-a4bm1bmt.js → maintenance-2xxfexhf.js} +1 -1
- package/dist/{mistral-conversations-4pmwj1c0.js → mistral-conversations-3gjpdb4b.js} +4 -4
- package/dist/{oauth-refresh-sweep-3h0dfw1k.js → oauth-refresh-sweep-fckc4ar5.js} +2 -2
- package/dist/{onboard-myn5hraa.js → onboard-zc48j237.js} +5 -5
- package/dist/{openai-codex-responses-44j573nm.js → openai-codex-responses-8pbxq9h5.js} +5 -5
- package/dist/{openai-completions-a4gee2a8.js → openai-completions-rm00s912.js} +4 -4
- package/dist/{openai-decisions-w4qkrcar.js → openai-decisions-8v957r2r.js} +1 -1
- package/dist/{openai-responses-rbp6h5kr.js → openai-responses-agxpf96k.js} +5 -5
- package/dist/{opencode-adapter-p2nkb5am.js → opencode-adapter-r690h1cr.js} +4 -3
- package/dist/{otel-impl-a1a0ef6b.js → otel-impl-r5xbh81q.js} +4 -4
- package/dist/{pi-mono-adapter-rzmpb72a.js → pi-mono-adapter-c7v8sdec.js} +7 -6
- package/dist/{pricing-refresh-83drrnxj.js → pricing-refresh-3pmre11v.js} +3 -3
- package/dist/{rbac-roles-s8y6ecb5.js → rbac-roles-3vbshwsb.js} +1 -1
- package/dist/{rbac-roles-0fb6e7pj.js → rbac-roles-910mmkgz.js} +2 -2
- package/dist/{render-v2-twmbgrz4.js → render-v2-pxykkbab.js} +3 -3
- package/dist/{resume-zfqc36xj.js → resume-adda0bwy.js} +6 -6
- package/dist/{runtime-qwggdpqr.js → runtime-fnbm7stk.js} +1 -1
- package/dist/{session-access-sc9pn9da.js → session-access-2fvamrdc.js} +4 -4
- package/dist/{session-source-backfill-qh07xr8g.js → session-source-backfill-08xvv0sm.js} +2 -2
- package/dist/{setup-4fr0m7a1.js → setup-1aqmvmh1.js} +3 -3
- package/dist/{typesafe-system-one-5mavm259.js → typesafe-system-one-0bmqcxfg.js} +2 -2
- package/dist/{virtual-modules-fh4ha15b.js → virtual-modules-d2pjk9j1.js} +5 -5
- package/dist/{work-objects-0wr8zxct.js → work-objects-9s0yrm5j.js} +20 -19
- package/dist/{worker-follow-up-ktyyazyp.js → worker-follow-up-3q78fqmr.js} +17 -16
- package/dist/worker-y15hy08h.js +71 -0
- package/openapi.json +289 -4
- package/package.json +9 -8
- package/src/apps/definition.ts +6 -5
- package/src/be/db/page-theme-tokens.ts +12 -0
- package/src/be/db/script-runs.ts +23 -0
- package/src/be/db/tasks/read.ts +131 -4
- package/src/be/db/tasks/write.ts +19 -0
- package/src/be/db-client.ts +114 -11
- package/src/be/db.ts +49 -6
- package/src/be/memory/providers/sqlite-store.ts +49 -12
- package/src/be/migrations/260_runtime_pool_cache.sql +20 -0
- package/src/be/migrations/262_session_visibility_indexes.sql +18 -0
- package/src/be/migrations/263_task_list_filter_index.sql +11 -0
- package/src/be/migrations/264_slack_thread_cover_index.sql +9 -0
- package/src/be/migrations/266_script_runs_source_task.sql +11 -0
- package/src/be/migrations/269_pages_design_system.sql +23 -0
- package/src/be/runtime-pools/cache-cleanup.ts +109 -0
- package/src/be/runtime-pools/db.ts +65 -6
- package/src/be/runtime-pools/remote-controller.ts +5 -0
- package/src/be/runtime-pools/supervisor.ts +33 -2
- package/src/be/scripts/typecheck.ts +22 -0
- package/src/be/seed/schedules-seeder.ts +0 -10
- package/src/be/seed-scripts/catalog/catalog-report.inline.ts +2 -0
- package/src/be/seed-scripts/catalog/catalog-report.ts +2 -0
- package/src/be/seed-scripts/catalog/memory-eval.ts +2 -0
- package/src/be/seed-scripts/catalog/swarm-update-check.ts +150 -0
- package/src/be/seed-scripts/catalog/task-failure-audit.ts +5 -1
- package/src/be/seed-scripts/index.ts +9 -0
- package/src/be/seed-skills/bundled-files.generated.json +3 -3
- package/src/be/swarm-config-guard.ts +32 -0
- package/src/be/task-auto-title-listener.ts +127 -0
- package/src/commands/controller.ts +4 -0
- package/src/commands/entrypoint-api-readiness.ts +74 -0
- package/src/commands/onboard/agent-fs-version.ts +1 -1
- package/src/commands/profile-sync.ts +151 -16
- package/src/commands/runner.ts +15 -7
- package/src/hooks/hook.ts +20 -9
- package/src/http/index.ts +4 -1
- package/src/http/list-total-cache.ts +14 -7
- package/src/http/memory.ts +38 -9
- package/src/http/pages-public.ts +93 -129
- package/src/http/pages.ts +94 -21
- package/src/http/script-runs.ts +174 -5
- package/src/http/status.ts +10 -2
- package/src/http/tasks.ts +4 -2
- package/src/pages/browser-sdk.ts +48 -21
- package/src/pages/color-literals.ts +115 -0
- package/src/pages/page-head.ts +766 -0
- package/src/pages/version.ts +3 -0
- package/src/providers/amp-adapter.ts +1 -1
- package/src/providers/codex-adapter.ts +1 -0
- package/src/providers/codex-app-server.ts +27 -1
- package/src/providers/fx-adapter.ts +1 -1
- package/src/providers/pi-mono-extension.ts +30 -15
- package/src/runtime-providers/controller-protocol.ts +14 -1
- package/src/runtime-providers/controller.ts +5 -0
- package/src/runtime-providers/docker.ts +108 -23
- package/src/runtime-providers/types.ts +16 -0
- package/src/script-workflows/workflow-ctx.ts +8 -0
- package/src/scripts-runtime/types/stdlib.d.ts +22 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +22 -0
- package/src/server-user.ts +1 -0
- package/src/slack/commands.ts +5 -1
- package/src/slack/handlers.ts +37 -2
- package/src/slack/left-threads.ts +27 -4
- package/src/tasks/follow-up-gate.ts +318 -0
- package/src/tasks/follow-up-redelegation-guard.ts +155 -0
- package/src/tasks/worker-follow-up.ts +21 -1
- package/src/tests/claude-artifacts-read-script.test.ts +209 -0
- package/src/tests/codex-app-server.test.ts +59 -0
- package/src/tests/codex-gateway-route.test.ts +50 -1
- package/src/tests/create-page-tool.test.ts +501 -0
- package/src/tests/db-client-busy-retry.test.ts +81 -0
- package/src/tests/db-client.test.ts +21 -0
- package/src/tests/db-task-read-contract.test.ts +9 -2
- package/src/tests/db-task-write-contract.test.ts +7 -1
- package/src/tests/db-tasks-read.test.ts +21 -0
- package/src/tests/entrypoint-api-readiness.test.ts +416 -210
- package/src/tests/explicit-model-validation.test.ts +14 -8
- package/src/tests/fixtures/pages/head-v1.html +56 -0
- package/src/tests/follow-up-gate-shadow.test.ts +513 -0
- package/src/tests/follow-up-redelivery-guard.test.ts +432 -202
- package/src/tests/identity-field-budget.test.ts +1 -1
- package/src/tests/internal-ai/credentials.test.ts +62 -0
- package/src/tests/internal-ai/task-title.test.ts +283 -0
- package/src/tests/list-total-cache.test.ts +27 -2
- package/src/tests/memory-fts-populate.test.ts +11 -9
- package/src/tests/memory-retrieval-purge.test.ts +80 -0
- package/src/tests/merge-gate-detect-changes.test.ts +145 -0
- package/src/tests/migration-260-runtime-pool-cache.test.ts +118 -0
- package/src/tests/page-proxy.test.ts +1 -1
- package/src/tests/page-session-dev-cookie.test.ts +188 -0
- package/src/tests/pages-frame-ancestors.test.ts +28 -1
- package/src/tests/pages-head-v2.test.ts +534 -0
- package/src/tests/pages-http.test.ts +330 -0
- package/src/tests/pages-public-html.test.ts +43 -0
- package/src/tests/pages-storage.test.ts +182 -0
- package/src/tests/pages-versioning.test.ts +72 -0
- package/src/tests/profile-sync-stale-writeback.test.ts +334 -0
- package/src/tests/profile-sync.test.ts +11 -15
- package/src/tests/query-audit-plans.test.ts +27 -0
- package/src/tests/runtime-cache-cleanup.test.ts +367 -0
- package/src/tests/runtime-e2b-pools.test.ts +41 -0
- package/src/tests/runtime-pools-db.test.ts +98 -0
- package/src/tests/runtime-pools-http.test.ts +29 -0
- package/src/tests/runtime-providers-docker.test.ts +253 -0
- package/src/tests/runtime-remote-controller.test.ts +66 -2
- package/src/tests/runtime-supervisor.test.ts +66 -2
- package/src/tests/scheduled-task-required-params-migration.test.ts +3 -1
- package/src/tests/script-connections.test.ts +13 -0
- package/src/tests/script-runs-http.test.ts +684 -2
- package/src/tests/seed-registry.test.ts +28 -6
- package/src/tests/seed-schedules.test.ts +61 -8
- package/src/tests/seed-scripts.test.ts +168 -2
- package/src/tests/slack-leave-thread.test.ts +145 -5
- package/src/tests/slack-render-v2-query-plans.test.ts +24 -2
- package/src/tests/status.test.ts +11 -0
- package/src/tests/swarm-diff.test.ts +121 -2
- package/src/tests/task-auto-title-config-guard.test.ts +37 -0
- package/src/tests/task-auto-title-listener.test.ts +165 -0
- package/src/tests/tasks-readable-row.test.ts +155 -0
- package/src/tests/theme-import.test.ts +758 -0
- package/src/tests/themes-page-theme.test.ts +155 -0
- package/src/tests/themes-presets.test.ts +172 -0
- package/src/tests/workflow-script-principal.test.ts +2 -1
- package/src/themes/classics.ts +949 -0
- package/src/themes/import.ts +420 -0
- package/src/themes/page-fields.ts +40 -0
- package/src/themes/page-theme.ts +102 -0
- package/src/themes/presets.ts +561 -0
- package/src/themes/tokens.ts +218 -0
- package/src/tools/app-upsert.ts +1 -1
- package/src/tools/create-page.ts +118 -12
- package/src/tools/send-task.ts +35 -47
- package/src/tools/slack-leave-thread.ts +1 -1
- package/src/tools/task-dedup.ts +45 -21
- package/src/tools/utils.ts +33 -0
- package/src/types.ts +83 -3
- package/src/utils/constants.ts +19 -0
- package/src/utils/follow-up-gate-schedule-ids.ts +13 -0
- package/src/utils/internal-ai/credentials.ts +146 -93
- package/src/utils/internal-ai/index.ts +10 -0
- package/src/utils/internal-ai/models.ts +18 -0
- package/src/utils/internal-ai/task-title.ts +282 -0
- package/src/utils/page-session.ts +37 -0
- package/templates/schedules/daily-status-report/content.md +88 -2
- package/templates/schedules/daily-swarm-update-check/content.md +16 -40
- package/templates/skills/apps/SKILL.md +1 -1
- package/templates/skills/apps/content.md +1 -1
- package/templates/skills/composio-google-docs/content.md +10 -0
- package/templates/skills/heartbeat-runbook/content.md +1 -1
- package/templates/skills/pages/config.json +1 -1
- package/templates/skills/pages/content.md +539 -86
- package/templates/skills/pages/files/examples/annotated-pr.html +21 -12
- package/templates/skills/pages/files/examples/multiplayer-board.html +41 -41
- package/templates/skills/pages/files/examples/report-page.html +125 -124
- package/templates/skills/script-workflows/content.md +19 -1
- package/templates/skills/swarm-docs/content.md +1 -0
- package/templates/skills/swarm-scripts/SKILL.md +2 -0
- package/templates/skills/swarm-scripts/content.md +2 -0
- package/dist/cli-2pnswgh8.js +0 -189
- package/dist/cli-dznrgaep.js +0 -30
- package/dist/cli-w3hcrxht.js +0 -6985
- package/dist/handlers-vwmba1m2.js +0 -31
- package/dist/index-5r71rf98.js +0 -32
- package/dist/index-anmwxs87.js +0 -29
- package/dist/index-spp0ra5c.js +0 -36
- package/dist/lead-fjy418cw.js +0 -69
- package/dist/worker-0c5tyzqe.js +0 -69
- package/templates/schedules/daily-workflow-health-audit/config.json +0 -19
- package/templates/schedules/daily-workflow-health-audit/content.md +0 -191
package/src/apps/definition.ts
CHANGED
|
@@ -446,11 +446,12 @@ export const AppElementsSchema = z.record(AppNameSchema, AppElementSchema);
|
|
|
446
446
|
|
|
447
447
|
/**
|
|
448
448
|
* Preset theme id applied to the app's rendered surface. The catalog of
|
|
449
|
-
* presets lives in
|
|
450
|
-
*
|
|
451
|
-
*
|
|
452
|
-
*
|
|
453
|
-
* `$theme` user-config key
|
|
449
|
+
* presets lives in `src/themes/presets.ts` (the dashboard re-exports it from
|
|
450
|
+
* `apps/ui/src/lib/themes.ts`). App definitions are validated by shape only,
|
|
451
|
+
* and the renderer resolves unknown ids to its default theme, so definitions
|
|
452
|
+
* stay portable across dashboard versions whose preset catalogs differ.
|
|
453
|
+
* Viewers can override it per-user via the reserved `$theme` user-config key
|
|
454
|
+
* (see `user-config.ts`).
|
|
454
455
|
*/
|
|
455
456
|
export const AppThemeIdSchema = z.string().regex(/^[a-z][a-z0-9-]{0,39}$/, {
|
|
456
457
|
message: "must be a lowercase slug (letters, digits, dashes)",
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type Page, PageThemeTokensSchema } from "../../types";
|
|
2
|
+
|
|
3
|
+
/** Stored `theme_tokens` JSON, or null when the column is empty, is not JSON, or has the wrong shape. */
|
|
4
|
+
export function parsePageThemeTokens(raw: string | null): Page["themeTokens"] {
|
|
5
|
+
if (!raw) return null;
|
|
6
|
+
try {
|
|
7
|
+
const parsed = PageThemeTokensSchema.safeParse(JSON.parse(raw));
|
|
8
|
+
return parsed.success ? parsed.data : null;
|
|
9
|
+
} catch {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
}
|
package/src/be/db/script-runs.ts
CHANGED
|
@@ -13,3 +13,26 @@ export async function getScriptRunExecutionArgs(id: string): Promise<unknown> {
|
|
|
13
13
|
);
|
|
14
14
|
return row ? openSealedJson(row.args) : undefined;
|
|
15
15
|
}
|
|
16
|
+
|
|
17
|
+
/** A durable run's follow-up re-delegation guard decision (see migration 266). */
|
|
18
|
+
export type ScriptRunFollowUpGuard = "passed" | "same-work" | "loop-cap";
|
|
19
|
+
|
|
20
|
+
export async function getScriptRunFollowUpGuard(
|
|
21
|
+
runId: string,
|
|
22
|
+
): Promise<ScriptRunFollowUpGuard | null> {
|
|
23
|
+
const row = await getDbClient().get<{ followUpGuard: ScriptRunFollowUpGuard | null }>(
|
|
24
|
+
"SELECT followUpGuard FROM script_runs WHERE id = ?",
|
|
25
|
+
[runId],
|
|
26
|
+
);
|
|
27
|
+
return row?.followUpGuard ?? null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function setScriptRunFollowUpGuard(
|
|
31
|
+
runId: string,
|
|
32
|
+
decision: ScriptRunFollowUpGuard,
|
|
33
|
+
): Promise<void> {
|
|
34
|
+
await getDbClient().run("UPDATE script_runs SET followUpGuard = ? WHERE id = ?", [
|
|
35
|
+
decision,
|
|
36
|
+
runId,
|
|
37
|
+
]);
|
|
38
|
+
}
|
package/src/be/db/tasks/read.ts
CHANGED
|
@@ -869,6 +869,52 @@ export function readableSessionCondition(
|
|
|
869
869
|
};
|
|
870
870
|
}
|
|
871
871
|
|
|
872
|
+
/** Parent hops the row-at-a-time walk takes before it falls back to the set form. */
|
|
873
|
+
const READABLE_WALK_MAX_DEPTH = 64;
|
|
874
|
+
|
|
875
|
+
/**
|
|
876
|
+
* The row-at-a-time form of {@link readableSessionCondition}, for a query that
|
|
877
|
+
* stops at a LIMIT. It walks up from each row it tests to the root, so a page
|
|
878
|
+
* costs a few key lookups per row instead of the set of every hidden task,
|
|
879
|
+
* which on a large DB took hundreds of milliseconds per request. The rule is
|
|
880
|
+
* the same: the root is the first task whose parent is null or missing. A row
|
|
881
|
+
* whose walk finds no root within {@link READABLE_WALK_MAX_DEPTH} hops (a very
|
|
882
|
+
* deep chain, or a parent cycle written before migration 253) falls back to
|
|
883
|
+
* the set form, which SQLite then builds once for the statement. A count reads
|
|
884
|
+
* every row, so it keeps the set form.
|
|
885
|
+
*/
|
|
886
|
+
function readableSessionRowCondition(
|
|
887
|
+
userId: string | null,
|
|
888
|
+
taskIdColumn = "agent_tasks.id",
|
|
889
|
+
): { sql: string; params: string[] } {
|
|
890
|
+
const otherOwner = userId
|
|
891
|
+
? "up.requestedByUserId IS NOT NULL AND up.requestedByUserId != ?"
|
|
892
|
+
: "up.requestedByUserId IS NOT NULL";
|
|
893
|
+
const shareMatch = userId ? "(s.userId IS NULL OR s.userId = ?)" : "s.userId IS NULL";
|
|
894
|
+
const set = readableSessionCondition(userId, taskIdColumn);
|
|
895
|
+
return {
|
|
896
|
+
sql: `(CASE (
|
|
897
|
+
WITH RECURSIVE up(id, parentTaskId, requestedByUserId, scheduleId, depth) AS (
|
|
898
|
+
SELECT u.id, u.parentTaskId, u.requestedByUserId, u.scheduleId, 0
|
|
899
|
+
FROM agent_tasks u WHERE u.id = ${taskIdColumn}
|
|
900
|
+
UNION ALL
|
|
901
|
+
SELECT p.id, p.parentTaskId, p.requestedByUserId, p.scheduleId, up.depth + 1
|
|
902
|
+
FROM up JOIN agent_tasks p ON p.id = up.parentTaskId
|
|
903
|
+
WHERE up.depth < ${READABLE_WALK_MAX_DEPTH}
|
|
904
|
+
)
|
|
905
|
+
SELECT CASE WHEN ${otherOwner} AND up.scheduleId IS NULL
|
|
906
|
+
AND NOT EXISTS (SELECT 1 FROM session_shares s
|
|
907
|
+
WHERE s.rootTaskId = up.id AND s.revokedAt IS NULL AND ${shareMatch})
|
|
908
|
+
THEN 1 ELSE 0 END
|
|
909
|
+
FROM up
|
|
910
|
+
WHERE up.parentTaskId IS NULL
|
|
911
|
+
OR NOT EXISTS (SELECT 1 FROM agent_tasks p WHERE p.id = up.parentTaskId)
|
|
912
|
+
LIMIT 1
|
|
913
|
+
) WHEN 0 THEN 1 WHEN 1 THEN 0 ELSE ${set.sql} END)`,
|
|
914
|
+
params: [...(userId ? [userId, userId] : []), ...set.params],
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
|
|
872
918
|
export function getAllTasks(filters?: TaskFilters): Promise<AgentTask[]>;
|
|
873
919
|
export function getAllTasks(
|
|
874
920
|
filters: TaskFilters | undefined,
|
|
@@ -977,6 +1023,14 @@ export async function getAllTasks(
|
|
|
977
1023
|
params.push(filters.requestedByUserId);
|
|
978
1024
|
}
|
|
979
1025
|
|
|
1026
|
+
// Without selective filters, tiny LIMITs can make SQLite choose a full
|
|
1027
|
+
// scan and sort when ANALYZE statistics are absent. Keep filtered pages
|
|
1028
|
+
// planner-controlled so they can use their selective indexes.
|
|
1029
|
+
const pageIndex =
|
|
1030
|
+
conditions.length === 0 && !filters?.readableBy && filters?.orderBy !== "createdAt"
|
|
1031
|
+
? "INDEXED BY idx_agent_tasks_last_updated"
|
|
1032
|
+
: "";
|
|
1033
|
+
|
|
980
1034
|
// Exclude system/heartbeat tasks by default. The flag is still called
|
|
981
1035
|
// `includeHeartbeat` for backward compat with existing API callers, but we
|
|
982
1036
|
// also gate boot-triage + heartbeat-checklist behind it since those are
|
|
@@ -988,7 +1042,8 @@ export async function getAllTasks(
|
|
|
988
1042
|
}
|
|
989
1043
|
|
|
990
1044
|
if (filters?.readableBy) {
|
|
991
|
-
|
|
1045
|
+
// The page stops at LIMIT, so test only the rows it reads.
|
|
1046
|
+
const readable = readableSessionRowCondition(filters.readableBy.userId);
|
|
992
1047
|
conditions.push(readable.sql);
|
|
993
1048
|
params.push(...readable.params);
|
|
994
1049
|
}
|
|
@@ -1009,7 +1064,7 @@ export async function getAllTasks(
|
|
|
1009
1064
|
// lastUpdatedAt index for a full scan. The outer sort adds it only to fix
|
|
1010
1065
|
// the order of exact ties inside the page.
|
|
1011
1066
|
const query = `WITH page AS (
|
|
1012
|
-
SELECT rowid AS rid FROM agent_tasks ${whereClause}
|
|
1067
|
+
SELECT rowid AS rid FROM agent_tasks ${pageIndex} ${whereClause}
|
|
1013
1068
|
ORDER BY ${orderColumns.join(", ")} LIMIT ${limit} OFFSET ${offset}
|
|
1014
1069
|
)
|
|
1015
1070
|
SELECT ${LIST_COLUMNS[fields]},
|
|
@@ -1865,7 +1920,44 @@ export async function getMostRecentTaskInThread(
|
|
|
1865
1920
|
return row ? rowToAgentTask(row) : null;
|
|
1866
1921
|
}
|
|
1867
1922
|
|
|
1868
|
-
|
|
1923
|
+
/**
|
|
1924
|
+
* One page of completed tasks in a Slack thread, most recent first. Lead
|
|
1925
|
+
* `follow-up` sessions are excluded: they coordinate the work, they are not the
|
|
1926
|
+
* work, so they never count as "the request was already handled". The caller
|
|
1927
|
+
* matches descriptions in JS, so it pages through the whole window with the
|
|
1928
|
+
* `before` keyset cursor (the last row of the previous page) instead of
|
|
1929
|
+
* loading every row in one query.
|
|
1930
|
+
*/
|
|
1931
|
+
export async function findCompletedTasksInThread(
|
|
1932
|
+
channelId: string,
|
|
1933
|
+
threadTs: string,
|
|
1934
|
+
windowMinutes: number,
|
|
1935
|
+
limit = 200,
|
|
1936
|
+
before?: { lastUpdatedAt: string; id: string },
|
|
1937
|
+
): Promise<AgentTask[]> {
|
|
1938
|
+
const since = new Date(Date.now() - windowMinutes * 60 * 1000).toISOString();
|
|
1939
|
+
const cursorSql = before ? "AND (lastUpdatedAt < ? OR (lastUpdatedAt = ? AND id < ?))" : "";
|
|
1940
|
+
const cursorParams = before ? [before.lastUpdatedAt, before.lastUpdatedAt, before.id] : [];
|
|
1941
|
+
const rows = await getDbClient().query<AgentTaskRow>(
|
|
1942
|
+
`SELECT * FROM agent_tasks
|
|
1943
|
+
WHERE slackChannelId = ?
|
|
1944
|
+
AND slackThreadTs = ?
|
|
1945
|
+
AND status = 'completed'
|
|
1946
|
+
AND COALESCE(taskType, '') != 'follow-up'
|
|
1947
|
+
AND lastUpdatedAt > ?
|
|
1948
|
+
${cursorSql}
|
|
1949
|
+
ORDER BY lastUpdatedAt DESC, id DESC
|
|
1950
|
+
LIMIT ?`,
|
|
1951
|
+
[channelId, threadTs, since, ...cursorParams, limit],
|
|
1952
|
+
);
|
|
1953
|
+
return rows.map(rowToAgentTask);
|
|
1954
|
+
}
|
|
1955
|
+
|
|
1956
|
+
/**
|
|
1957
|
+
* Most recent FAILED task in a Slack thread. A failure more recent than a
|
|
1958
|
+
* completion means the follow-up re-delegation guard must let a retry through.
|
|
1959
|
+
*/
|
|
1960
|
+
export async function findRecentFailedTaskInThread(
|
|
1869
1961
|
channelId: string,
|
|
1870
1962
|
threadTs: string,
|
|
1871
1963
|
windowMinutes: number,
|
|
@@ -1875,7 +1967,7 @@ export async function findCompletedTaskInThread(
|
|
|
1875
1967
|
`SELECT * FROM agent_tasks
|
|
1876
1968
|
WHERE slackChannelId = ?
|
|
1877
1969
|
AND slackThreadTs = ?
|
|
1878
|
-
AND status = '
|
|
1970
|
+
AND status = 'failed'
|
|
1879
1971
|
AND lastUpdatedAt > ?
|
|
1880
1972
|
ORDER BY lastUpdatedAt DESC
|
|
1881
1973
|
LIMIT 1`,
|
|
@@ -1884,6 +1976,41 @@ export async function findCompletedTaskInThread(
|
|
|
1884
1976
|
return row ? rowToAgentTask(row) : null;
|
|
1885
1977
|
}
|
|
1886
1978
|
|
|
1979
|
+
/**
|
|
1980
|
+
* Count the dispatches one agent made into a Slack thread since the window
|
|
1981
|
+
* start: each `send-task` task counts once, and each durable script run counts
|
|
1982
|
+
* once however many `ctx.step.agentTask` steps it created (step tasks share the
|
|
1983
|
+
* `script-run:<runId>:` contextKey prefix). A run is dated by its first step,
|
|
1984
|
+
* the dispatch the guard checked, so a run that started before the window does
|
|
1985
|
+
* not count again for steps it adds later. The follow-up re-delegation loop cap
|
|
1986
|
+
* reads this.
|
|
1987
|
+
*/
|
|
1988
|
+
export async function countAgentDispatchesInThread(
|
|
1989
|
+
channelId: string,
|
|
1990
|
+
threadTs: string,
|
|
1991
|
+
creatorAgentId: string,
|
|
1992
|
+
windowMinutes: number,
|
|
1993
|
+
): Promise<number> {
|
|
1994
|
+
const since = new Date(Date.now() - windowMinutes * 60 * 1000).toISOString();
|
|
1995
|
+
const row = await getDbClient().get<{ c: number }>(
|
|
1996
|
+
`SELECT COUNT(*) AS c FROM (
|
|
1997
|
+
SELECT MIN(createdAt) AS firstAt FROM agent_tasks
|
|
1998
|
+
WHERE slackChannelId = ?
|
|
1999
|
+
AND slackThreadTs = ?
|
|
2000
|
+
AND creatorAgentId = ?
|
|
2001
|
+
AND source = 'mcp'
|
|
2002
|
+
AND (createdAt > ? OR taskType = 'script-run-step')
|
|
2003
|
+
GROUP BY CASE
|
|
2004
|
+
WHEN taskType = 'script-run-step' AND contextKey LIKE 'script-run:%:%'
|
|
2005
|
+
THEN substr(contextKey, 1, instr(substr(contextKey, 12), ':') + 10)
|
|
2006
|
+
ELSE id
|
|
2007
|
+
END
|
|
2008
|
+
) WHERE firstAt > ?`,
|
|
2009
|
+
[channelId, threadTs, creatorAgentId, since, since],
|
|
2010
|
+
);
|
|
2011
|
+
return row?.c ?? 0;
|
|
2012
|
+
}
|
|
2013
|
+
|
|
1887
2014
|
/**
|
|
1888
2015
|
* Find the most recent CANCELLED task in a Slack thread. Used by the
|
|
1889
2016
|
* follow-up re-delegation guard so a cancellation (worker SIGTERM,
|
package/src/be/db/tasks/write.ts
CHANGED
|
@@ -394,6 +394,25 @@ export async function updateTaskTitle(
|
|
|
394
394
|
return row ? rowToAgentTask(row) : null;
|
|
395
395
|
}
|
|
396
396
|
|
|
397
|
+
/**
|
|
398
|
+
* Sets a generated display title only while the task has none. A rename that
|
|
399
|
+
* landed first (a person, or another API replica) wins: the row is left alone
|
|
400
|
+
* and `null` comes back. Like `updateTaskTitle`, it does not bump
|
|
401
|
+
* `lastUpdatedAt`.
|
|
402
|
+
*/
|
|
403
|
+
export async function setTaskTitleIfUnset(
|
|
404
|
+
taskId: string,
|
|
405
|
+
title: string,
|
|
406
|
+
): Promise<AgentTask | null> {
|
|
407
|
+
const normalized = title.trim();
|
|
408
|
+
if (!normalized) return null;
|
|
409
|
+
const row = await getDbClient().get<AgentTaskRow>(
|
|
410
|
+
"UPDATE agent_tasks SET title = ? WHERE id = ? AND title IS NULL RETURNING *",
|
|
411
|
+
[normalized, taskId],
|
|
412
|
+
);
|
|
413
|
+
return row ? rowToAgentTask(row) : null;
|
|
414
|
+
}
|
|
415
|
+
|
|
397
416
|
export async function completeTask(
|
|
398
417
|
id: string,
|
|
399
418
|
output?: string,
|
package/src/be/db-client.ts
CHANGED
|
@@ -177,6 +177,11 @@ export type BusyRetryOptions = {
|
|
|
177
177
|
* Defaults to `DEFAULT_FINAL_ATTEMPT_SPIN_MS`.
|
|
178
178
|
*/
|
|
179
179
|
finalAttemptSpinMs?: number;
|
|
180
|
+
/**
|
|
181
|
+
* Longest gap between spinning probes while an external holder is
|
|
182
|
+
* detected (see `BusyGate`). Defaults to `DEFAULT_MAX_GATE_MS`.
|
|
183
|
+
*/
|
|
184
|
+
maxGateMs?: number;
|
|
180
185
|
};
|
|
181
186
|
|
|
182
187
|
/**
|
|
@@ -247,8 +252,81 @@ function isSqliteBusy(err: unknown): boolean {
|
|
|
247
252
|
return typeof code === "string" && code.startsWith("SQLITE_BUSY");
|
|
248
253
|
}
|
|
249
254
|
|
|
255
|
+
/** Ceiling on the gap between spinning probes while a holder is detected. */
|
|
256
|
+
const DEFAULT_MAX_GATE_MS = 1_000;
|
|
257
|
+
/** A quiet stretch this long ends a BUSY episode even without a proven write. */
|
|
258
|
+
const GATE_DECAY_MS = 5_000;
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Process-wide memory of a cross-process write-lock holder.
|
|
262
|
+
*
|
|
263
|
+
* Every retrying writer used to spin `attemptSpinMs` (100ms, or 250ms for the
|
|
264
|
+
* last attempt) SYNCHRONOUSLY inside the FIFO lock on every attempt. With N
|
|
265
|
+
* writers queued behind a holder that lasts minutes, one round of attempts
|
|
266
|
+
* costs N x 100ms of blocked event loop, and each attempt waits for that
|
|
267
|
+
* whole round before its own turn. A 5-minute Litestream snapshot turned
|
|
268
|
+
* the nominal 11s retry budget into 90-105s request hangs, 3s /health and
|
|
269
|
+
* 3s page loads, although only writes were contending.
|
|
270
|
+
*
|
|
271
|
+
* The gate records a BUSY seen by a spinning probe. For the next interval
|
|
272
|
+
* (doubling from `attemptSpinMs` up to `maxGateMs`) every attempt runs with
|
|
273
|
+
* a ZERO busy_timeout: it still tries, so recovery is immediate, but it
|
|
274
|
+
* fails in microseconds instead of blocking the loop, and the async backoff
|
|
275
|
+
* sleeps do the waiting. Once the interval lapses, the next attempt is the
|
|
276
|
+
* lone spinning probe again.
|
|
277
|
+
*/
|
|
278
|
+
class BusyGate {
|
|
279
|
+
private openUntil = 0;
|
|
280
|
+
private streak = 0;
|
|
281
|
+
private lastBusyAt = 0;
|
|
282
|
+
private episodeStartedAt = 0;
|
|
283
|
+
private episodeProbes = 0;
|
|
284
|
+
|
|
285
|
+
constructor(
|
|
286
|
+
private readonly baseMs: number,
|
|
287
|
+
private readonly maxMs: number,
|
|
288
|
+
) {}
|
|
289
|
+
|
|
290
|
+
/** True while attempts must not spin. */
|
|
291
|
+
isOpen(now = performance.now()): boolean {
|
|
292
|
+
return now < this.openUntil;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/** A spinning probe hit BUSY: a holder exists. Open the gate and widen it. */
|
|
296
|
+
recordBusy(now = performance.now()): void {
|
|
297
|
+
if (this.streak > 0 && now - this.lastBusyAt > GATE_DECAY_MS) this.endEpisode(now, "decayed");
|
|
298
|
+
if (this.streak === 0) {
|
|
299
|
+
this.episodeStartedAt = now;
|
|
300
|
+
this.episodeProbes = 0;
|
|
301
|
+
console.warn(
|
|
302
|
+
"[db-client] SQLITE_BUSY: another process holds the write lock; spinning at most one probe per gate interval until it releases",
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
this.streak += 1;
|
|
306
|
+
this.episodeProbes += 1;
|
|
307
|
+
this.lastBusyAt = now;
|
|
308
|
+
const interval = Math.min(this.maxMs, this.baseMs * 2 ** Math.min(this.streak - 1, 20));
|
|
309
|
+
this.openUntil = now + interval;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/** A write got through after BUSY: the holder is gone. */
|
|
313
|
+
recordRecovered(now = performance.now()): void {
|
|
314
|
+
if (this.streak === 0) return;
|
|
315
|
+
this.endEpisode(now, "released");
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
private endEpisode(now: number, how: "released" | "decayed"): void {
|
|
319
|
+
console.warn(
|
|
320
|
+
`[db-client] write lock ${how} after ${Math.round(now - this.episodeStartedAt)}ms (${this.episodeProbes} spinning probes)`,
|
|
321
|
+
);
|
|
322
|
+
this.streak = 0;
|
|
323
|
+
this.openUntil = 0;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
250
327
|
class BunSqliteClient implements DbClient {
|
|
251
328
|
private readonly lock = new FifoLock();
|
|
329
|
+
private readonly gate: BusyGate;
|
|
252
330
|
private savepointSeq = 0;
|
|
253
331
|
/** Lazily captured ambient busy_timeout, restored after each short-spin attempt. */
|
|
254
332
|
private restoreBusyTimeoutMs: number | null = null;
|
|
@@ -256,7 +334,12 @@ class BunSqliteClient implements DbClient {
|
|
|
256
334
|
constructor(
|
|
257
335
|
private readonly getDatabase: () => Database,
|
|
258
336
|
private readonly busyRetry: BusyRetryOptions = DEFAULT_BUSY_RETRY,
|
|
259
|
-
) {
|
|
337
|
+
) {
|
|
338
|
+
this.gate = new BusyGate(
|
|
339
|
+
busyRetry.attemptSpinMs,
|
|
340
|
+
Math.max(busyRetry.attemptSpinMs, busyRetry.maxGateMs ?? DEFAULT_MAX_GATE_MS),
|
|
341
|
+
);
|
|
342
|
+
}
|
|
260
343
|
|
|
261
344
|
query<T>(sql: string, params: DbParam[] = []): Promise<T[]> {
|
|
262
345
|
return this.execute(sql, (db) => db.query<T, DbParam[]>(sql).all(...params));
|
|
@@ -352,6 +435,7 @@ class BunSqliteClient implements DbClient {
|
|
|
352
435
|
// not. The short spin keeps the driver's synchronous busy wait off
|
|
353
436
|
// the event loop; the real waiting is the caller's async backoff.
|
|
354
437
|
this.shortSpin(db, () => db.run("BEGIN IMMEDIATE"), finalAttempt);
|
|
438
|
+
this.gate.recordRecovered();
|
|
355
439
|
} catch (err) {
|
|
356
440
|
if (isSqliteBusy(err)) return { ok: false, busyBegin: err };
|
|
357
441
|
throw err;
|
|
@@ -366,7 +450,7 @@ class BunSqliteClient implements DbClient {
|
|
|
366
450
|
// is exact. The open transaction pins the connection, so these
|
|
367
451
|
// retries hold the FIFO lock and their sleeps block queued work;
|
|
368
452
|
// WAL-mode COMMIT BUSY is rare enough for that to be acceptable.
|
|
369
|
-
await this.retryHoldingLock(() => this.shortSpin(db, () => db.run("COMMIT")));
|
|
453
|
+
await this.retryHoldingLock(() => this.shortSpin(db, () => db.run("COMMIT"), false, false));
|
|
370
454
|
}
|
|
371
455
|
for (const hook of ctx.afterCommit) this.scheduleHook(hook);
|
|
372
456
|
return { ok: true, value: result };
|
|
@@ -468,11 +552,17 @@ class BunSqliteClient implements DbClient {
|
|
|
468
552
|
if (!willRetry) warnIfSlowStatement(sql, ms);
|
|
469
553
|
}
|
|
470
554
|
};
|
|
555
|
+
// The first call opens the database and applies migrations; resolve it
|
|
556
|
+
// before the timer, so boot work is not logged as this statement's cost.
|
|
557
|
+
// Outside a transaction it resolves inside the retry loop, so a boot that
|
|
558
|
+
// hits SQLITE_BUSY boots again.
|
|
471
559
|
const ctx = txContext.getStore();
|
|
472
560
|
if (ctx && !ctx.closed) {
|
|
473
|
-
|
|
561
|
+
const db = this.getDatabase();
|
|
562
|
+
return timed(() => op(db));
|
|
474
563
|
}
|
|
475
564
|
let sleptMs = 0;
|
|
565
|
+
let sawBusy = false;
|
|
476
566
|
for (let attemptIndex = 0; ; attemptIndex++) {
|
|
477
567
|
const next = this.busyRetry.backoffMs[attemptIndex];
|
|
478
568
|
const canRetryAfter = next !== undefined && sleptMs + next <= this.busyRetry.maxWaitMs;
|
|
@@ -482,12 +572,15 @@ class BunSqliteClient implements DbClient {
|
|
|
482
572
|
// A top-level single statement is atomic, so a repeat on BUSY is
|
|
483
573
|
// exact. The final attempt spins `finalAttemptSpinMs` (see
|
|
484
574
|
// transaction()).
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
575
|
+
const db = this.getDatabase();
|
|
576
|
+
const result = timed(() => this.shortSpin(db, () => op(db), !canRetryAfter), canRetryAfter);
|
|
577
|
+
// A statement that was BUSY earlier in this call and now landed is a
|
|
578
|
+
// write that got through: the external holder is gone.
|
|
579
|
+
if (sawBusy) this.gate.recordRecovered();
|
|
580
|
+
return result;
|
|
489
581
|
} catch (err) {
|
|
490
582
|
if (!isSqliteBusy(err) || !canRetryAfter || next === undefined) throw err;
|
|
583
|
+
sawBusy = true;
|
|
491
584
|
backoff = next;
|
|
492
585
|
sleptMs += next;
|
|
493
586
|
} finally {
|
|
@@ -506,17 +599,27 @@ class BunSqliteClient implements DbClient {
|
|
|
506
599
|
* lock is held, so the set/attempt/restore sequence cannot interleave with
|
|
507
600
|
* other client work.
|
|
508
601
|
*/
|
|
509
|
-
private shortSpin<T>(db: Database, attempt: () => T, finalAttempt = false): T {
|
|
602
|
+
private shortSpin<T>(db: Database, attempt: () => T, finalAttempt = false, gateable = true): T {
|
|
510
603
|
if (this.restoreBusyTimeoutMs === null) {
|
|
511
604
|
const row = db.query<{ timeout: number }, []>("PRAGMA busy_timeout").get();
|
|
512
605
|
this.restoreBusyTimeoutMs = row?.timeout ?? 5000;
|
|
513
606
|
}
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
607
|
+
// While a holder is known (BusyGate) the attempt runs without a spin: it
|
|
608
|
+
// still tries, so a released lock is taken at once, but a held one costs
|
|
609
|
+
// microseconds of event loop instead of a full spin per queued writer.
|
|
610
|
+
// Only the spinning probe that follows a lapsed gate reports BUSY back.
|
|
611
|
+
const gated = gateable && this.gate.isOpen();
|
|
612
|
+
const spinMs = gated
|
|
613
|
+
? 0
|
|
614
|
+
: finalAttempt
|
|
615
|
+
? (this.busyRetry.finalAttemptSpinMs ?? DEFAULT_FINAL_ATTEMPT_SPIN_MS)
|
|
616
|
+
: this.busyRetry.attemptSpinMs;
|
|
517
617
|
db.exec(`PRAGMA busy_timeout = ${spinMs}`);
|
|
518
618
|
try {
|
|
519
619
|
return attempt();
|
|
620
|
+
} catch (err) {
|
|
621
|
+
if (gateable && !gated && isSqliteBusy(err)) this.gate.recordBusy();
|
|
622
|
+
throw err;
|
|
520
623
|
} finally {
|
|
521
624
|
db.exec(`PRAGMA busy_timeout = ${this.restoreBusyTimeoutMs}`);
|
|
522
625
|
}
|
package/src/be/db.ts
CHANGED
|
@@ -198,6 +198,7 @@ import {
|
|
|
198
198
|
} from "./db/context-versions";
|
|
199
199
|
import { clearAgentModelProfileOnOverrideChange } from "./db/model-profiles";
|
|
200
200
|
import { pageSearchSql } from "./db/page-search";
|
|
201
|
+
import { parsePageThemeTokens } from "./db/page-theme-tokens";
|
|
201
202
|
import { getDb, getDbClient } from "./db/runtime";
|
|
202
203
|
import {
|
|
203
204
|
assertAssetKeyMovesAllowed,
|
|
@@ -313,7 +314,11 @@ export {
|
|
|
313
314
|
getContextVersionHistory,
|
|
314
315
|
getLatestContextVersion,
|
|
315
316
|
} from "./db/context-versions";
|
|
316
|
-
export {
|
|
317
|
+
export {
|
|
318
|
+
getScriptRunExecutionArgs,
|
|
319
|
+
getScriptRunFollowUpGuard,
|
|
320
|
+
setScriptRunFollowUpGuard,
|
|
321
|
+
} from "./db/script-runs";
|
|
317
322
|
export {
|
|
318
323
|
AssetKeyMovesBlockedError,
|
|
319
324
|
assertAssetKeyMovesAllowed,
|
|
@@ -336,12 +341,14 @@ export {
|
|
|
336
341
|
} from "./db/runtime";
|
|
337
342
|
|
|
338
343
|
export {
|
|
344
|
+
countAgentDispatchesInThread,
|
|
339
345
|
type ExistingTrackerContextWork,
|
|
340
346
|
type ExistingTrackerContextWorkReason,
|
|
341
347
|
findActiveTasksByVcs,
|
|
342
|
-
|
|
348
|
+
findCompletedTasksInThread,
|
|
343
349
|
findExistingLinearTrackerContextWork,
|
|
344
350
|
findRecentCancelledTaskInThread,
|
|
351
|
+
findRecentFailedTaskInThread,
|
|
345
352
|
findTaskByGitHub,
|
|
346
353
|
findTaskByVcs,
|
|
347
354
|
getAgentCurrentTask,
|
|
@@ -407,6 +414,7 @@ export {
|
|
|
407
414
|
recordTaskProviderIfUnset,
|
|
408
415
|
resetOrphanedInProgressTasksForAgent,
|
|
409
416
|
resumeTask,
|
|
417
|
+
setTaskTitleIfUnset,
|
|
410
418
|
settleSupersededTaskDependents,
|
|
411
419
|
startTask,
|
|
412
420
|
supersedeTask,
|
|
@@ -8613,6 +8621,9 @@ type PageRow = {
|
|
|
8613
8621
|
createdAt: string;
|
|
8614
8622
|
updatedAt: string;
|
|
8615
8623
|
view_count: number;
|
|
8624
|
+
theme: string | null;
|
|
8625
|
+
theme_tokens: string | null;
|
|
8626
|
+
design_version: number;
|
|
8616
8627
|
};
|
|
8617
8628
|
|
|
8618
8629
|
function rowToPage(row: PageRow): Page {
|
|
@@ -8630,6 +8641,9 @@ function rowToPage(row: PageRow): Page {
|
|
|
8630
8641
|
needsCredentials: row.needsCredentials
|
|
8631
8642
|
? (JSON.parse(row.needsCredentials) as string[])
|
|
8632
8643
|
: undefined,
|
|
8644
|
+
theme: row.theme ?? null,
|
|
8645
|
+
themeTokens: parsePageThemeTokens(row.theme_tokens),
|
|
8646
|
+
designVersion: typeof row.design_version === "number" ? row.design_version : 1,
|
|
8633
8647
|
viewCount: typeof row.view_count === "number" ? row.view_count : 0,
|
|
8634
8648
|
createdAt: normalizeDateRequired(row.createdAt),
|
|
8635
8649
|
updatedAt: normalizeDateRequired(row.updatedAt),
|
|
@@ -8647,13 +8661,17 @@ export async function createPage(data: {
|
|
|
8647
8661
|
passwordHash?: string;
|
|
8648
8662
|
body: string;
|
|
8649
8663
|
needsCredentials?: string[];
|
|
8664
|
+
theme?: string | null;
|
|
8665
|
+
themeTokens?: Page["themeTokens"];
|
|
8666
|
+
/** Defaults to 2 (the swarm design system). Rows inserted outside this function default to 1. */
|
|
8667
|
+
designVersion?: number;
|
|
8650
8668
|
}): Promise<Page> {
|
|
8651
8669
|
// Match the historical SQL default ID shape while making the value
|
|
8652
8670
|
// available before insert so the default namespace can include it.
|
|
8653
8671
|
const id = crypto.randomUUID().replace(/-/g, "");
|
|
8654
8672
|
const row = await getDbClient().get<PageRow>(
|
|
8655
|
-
`INSERT INTO pages (id, "key", agentId, slug, title, description, contentType, authMode, passwordHash, body, needsCredentials)
|
|
8656
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *`,
|
|
8673
|
+
`INSERT INTO pages (id, "key", agentId, slug, title, description, contentType, authMode, passwordHash, body, needsCredentials, theme, theme_tokens, design_version)
|
|
8674
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *`,
|
|
8657
8675
|
[
|
|
8658
8676
|
id,
|
|
8659
8677
|
normalizeAssetKey(data.key ?? defaultAssetKey("page", id)),
|
|
@@ -8666,6 +8684,9 @@ export async function createPage(data: {
|
|
|
8666
8684
|
data.passwordHash ?? null,
|
|
8667
8685
|
data.body,
|
|
8668
8686
|
data.needsCredentials ? JSON.stringify(data.needsCredentials) : null,
|
|
8687
|
+
data.theme ?? null,
|
|
8688
|
+
data.themeTokens ? JSON.stringify(data.themeTokens) : null,
|
|
8689
|
+
data.designVersion ?? 2,
|
|
8669
8690
|
],
|
|
8670
8691
|
);
|
|
8671
8692
|
if (!row) throw new Error("Failed to create page");
|
|
@@ -8852,6 +8873,11 @@ export async function updatePage(
|
|
|
8852
8873
|
body?: string;
|
|
8853
8874
|
needsCredentials?: string[] | null;
|
|
8854
8875
|
slug?: string;
|
|
8876
|
+
/** undefined keeps the stored value, null clears it. */
|
|
8877
|
+
theme?: string | null;
|
|
8878
|
+
/** undefined keeps the stored value, null clears it. */
|
|
8879
|
+
themeTokens?: Page["themeTokens"];
|
|
8880
|
+
designVersion?: number;
|
|
8855
8881
|
},
|
|
8856
8882
|
): Promise<Page | null> {
|
|
8857
8883
|
const updates: string[] = [];
|
|
@@ -8892,6 +8918,18 @@ export async function updatePage(
|
|
|
8892
8918
|
updates.push("slug = ?");
|
|
8893
8919
|
params.push(data.slug);
|
|
8894
8920
|
}
|
|
8921
|
+
if (data.theme !== undefined) {
|
|
8922
|
+
updates.push("theme = ?");
|
|
8923
|
+
params.push(data.theme);
|
|
8924
|
+
}
|
|
8925
|
+
if (data.themeTokens !== undefined) {
|
|
8926
|
+
updates.push("theme_tokens = ?");
|
|
8927
|
+
params.push(data.themeTokens ? JSON.stringify(data.themeTokens) : null);
|
|
8928
|
+
}
|
|
8929
|
+
if (data.designVersion !== undefined) {
|
|
8930
|
+
updates.push("design_version = ?");
|
|
8931
|
+
params.push(data.designVersion);
|
|
8932
|
+
}
|
|
8895
8933
|
if (updates.length === 0) return getPage(id);
|
|
8896
8934
|
updates.push("updatedAt = ?");
|
|
8897
8935
|
params.push(new Date().toISOString());
|
|
@@ -14116,6 +14154,7 @@ type ScriptRunRow = {
|
|
|
14116
14154
|
last_heartbeat_at: string | null;
|
|
14117
14155
|
idempotencyKey: string | null;
|
|
14118
14156
|
requestedByUserId: string | null;
|
|
14157
|
+
sourceTaskId: string | null;
|
|
14119
14158
|
created_by: string | null;
|
|
14120
14159
|
updated_by: string | null;
|
|
14121
14160
|
};
|
|
@@ -14160,6 +14199,7 @@ function rowToScriptRun(row: ScriptRunRow): ScriptRun {
|
|
|
14160
14199
|
lastHeartbeatAt: row.last_heartbeat_at ?? undefined,
|
|
14161
14200
|
idempotencyKey: row.idempotencyKey ?? undefined,
|
|
14162
14201
|
requestedByUserId: row.requestedByUserId ?? undefined,
|
|
14202
|
+
sourceTaskId: row.sourceTaskId ?? undefined,
|
|
14163
14203
|
};
|
|
14164
14204
|
}
|
|
14165
14205
|
|
|
@@ -14188,6 +14228,7 @@ export async function createScriptRun(data: {
|
|
|
14188
14228
|
scriptName?: string;
|
|
14189
14229
|
idempotencyKey?: string;
|
|
14190
14230
|
requestedByUserId?: string;
|
|
14231
|
+
sourceTaskId?: string;
|
|
14191
14232
|
createdBy?: string;
|
|
14192
14233
|
updatedBy?: string;
|
|
14193
14234
|
}): Promise<{ run: ScriptRun; existing: boolean }> {
|
|
@@ -14202,8 +14243,9 @@ export async function createScriptRun(data: {
|
|
|
14202
14243
|
|
|
14203
14244
|
const row = await client.get<ScriptRunRow>(
|
|
14204
14245
|
`INSERT INTO script_runs
|
|
14205
|
-
(id, agentId, scriptName, source, args, idempotencyKey, requestedByUserId,
|
|
14206
|
-
|
|
14246
|
+
(id, agentId, scriptName, source, args, idempotencyKey, requestedByUserId, sourceTaskId,
|
|
14247
|
+
created_by, updated_by)
|
|
14248
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
14207
14249
|
RETURNING *`,
|
|
14208
14250
|
[
|
|
14209
14251
|
data.id,
|
|
@@ -14216,6 +14258,7 @@ export async function createScriptRun(data: {
|
|
|
14216
14258
|
sealJson(data.args ?? null),
|
|
14217
14259
|
data.idempotencyKey ?? null,
|
|
14218
14260
|
data.requestedByUserId ?? null,
|
|
14261
|
+
data.sourceTaskId ?? null,
|
|
14219
14262
|
data.createdBy ?? null,
|
|
14220
14263
|
data.updatedBy ?? data.createdBy ?? null,
|
|
14221
14264
|
],
|