@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
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
* but it must be VISIBLE.
|
|
28
28
|
*/
|
|
29
29
|
|
|
30
|
+
import { rename } from "node:fs/promises";
|
|
30
31
|
import { resolveTemplateAsync } from "../prompts/resolver.ts";
|
|
31
32
|
import type { Agent, ProfileExpectedHashes, SwarmEvent } from "../types.ts";
|
|
32
33
|
import { MAX_PROFILE_FILE_LENGTH } from "../utils/constants.ts";
|
|
@@ -52,10 +53,13 @@ export const SETUP_SCRIPT_PATH = "/workspace/start-up.sh";
|
|
|
52
53
|
// Flow:
|
|
53
54
|
// 1. Runner writes DB content → /workspace/*.md at session start.
|
|
54
55
|
// 2. Runner records SHA-256 hashes of the written content (the "baselines").
|
|
55
|
-
// 3.
|
|
56
|
+
// 3. Every sync (session end AND on-edit) compares each file's hash against
|
|
57
|
+
// its baseline.
|
|
56
58
|
// - Hash matches → file untouched by the agent → skip sync (preserves
|
|
57
59
|
// any DB-side edits Lead made during the session).
|
|
58
60
|
// - Hash differs → agent modified the file → sync it back to DB.
|
|
61
|
+
// 4. After the API accepts a file, its baseline advances to the synced hash,
|
|
62
|
+
// so a file edited once is not re-sent over a later DB-side edit.
|
|
59
63
|
// ──────────────────────────────────────────────────────────────────────────
|
|
60
64
|
export const IDENTITY_BASELINES_PATH = "/tmp/identity-baselines.json";
|
|
61
65
|
|
|
@@ -337,8 +341,71 @@ export async function prependProfileSyncRejectionBanner(
|
|
|
337
341
|
: { prompt, injected: false };
|
|
338
342
|
}
|
|
339
343
|
|
|
344
|
+
/**
|
|
345
|
+
* Serializes every read-modify-write of IDENTITY_BASELINES_PATH across
|
|
346
|
+
* processes: the runner, the Claude hook and the pi extension each advance it,
|
|
347
|
+
* and two unserialized advances drop one another's hashes.
|
|
348
|
+
*/
|
|
349
|
+
export const IDENTITY_BASELINES_LOCK_PATH = `${IDENTITY_BASELINES_PATH}.lock`;
|
|
350
|
+
/** The baselines critical section is one small read and write; wait this long for it. */
|
|
351
|
+
const BASELINES_LOCK_WAIT_MS = 5_000;
|
|
352
|
+
|
|
353
|
+
/** Profile-file IO, injectable so tests never touch the real workspace. */
|
|
354
|
+
export interface ProfileFileIO {
|
|
355
|
+
readFile: FileReader;
|
|
356
|
+
writeFile: FileWriter;
|
|
357
|
+
/** Lock for baseline updates; defaults to IDENTITY_BASELINES_LOCK_PATH. */
|
|
358
|
+
baselinesLockPath?: string;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
export const PROFILE_FILE_IO: ProfileFileIO = {
|
|
362
|
+
readFile: readFileIfExists,
|
|
363
|
+
writeFile: writeFileAtomic,
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Replace the baselines at boot. Taken under the baselines lock so an advance
|
|
368
|
+
* still in flight from an earlier session cannot reapply its pre-boot read; if
|
|
369
|
+
* the lock is unavailable the boot baselines are written anyway.
|
|
370
|
+
*/
|
|
340
371
|
export async function writeIdentityBaselines(baselines: IdentityBaselines): Promise<void> {
|
|
341
|
-
|
|
372
|
+
const write = () => writeFileAtomic(IDENTITY_BASELINES_PATH, JSON.stringify(baselines));
|
|
373
|
+
const locked = await withFileLock(IDENTITY_BASELINES_LOCK_PATH, write, {
|
|
374
|
+
waitMs: BASELINES_LOCK_WAIT_MS,
|
|
375
|
+
});
|
|
376
|
+
if (!locked.acquired) await write();
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* The baselines the runner records at boot: the hash of each DB value the
|
|
381
|
+
* workspace file now derives from. An empty HEARTBEAT.md is a valid DB value
|
|
382
|
+
* and is never rewritten at boot, so it is baselined too; otherwise the empty
|
|
383
|
+
* file that survives a restart would count as an edit and be sent over a later
|
|
384
|
+
* DB edit. The setup script is baselined from the entrypoint-composed file.
|
|
385
|
+
*/
|
|
386
|
+
export function bootIdentityBaselines(
|
|
387
|
+
profile: {
|
|
388
|
+
soulMd?: string;
|
|
389
|
+
identityMd?: string;
|
|
390
|
+
toolsMd?: string;
|
|
391
|
+
heartbeatMd?: string | null;
|
|
392
|
+
claudeMd?: string;
|
|
393
|
+
},
|
|
394
|
+
setupScriptFile: string | undefined,
|
|
395
|
+
agentId = "unknown",
|
|
396
|
+
): IdentityBaselines {
|
|
397
|
+
const baselines: IdentityBaselines = {};
|
|
398
|
+
if (profile.soulMd) baselines.soulMd = contentSha256(profile.soulMd);
|
|
399
|
+
if (profile.identityMd) baselines.identityMd = contentSha256(profile.identityMd);
|
|
400
|
+
if (profile.toolsMd) baselines.toolsMd = contentSha256(profile.toolsMd);
|
|
401
|
+
if (typeof profile.heartbeatMd === "string") {
|
|
402
|
+
baselines.heartbeatMd = contentSha256(profile.heartbeatMd);
|
|
403
|
+
}
|
|
404
|
+
if (profile.claudeMd) baselines.claudeMd = contentSha256(profile.claudeMd);
|
|
405
|
+
const setupContent =
|
|
406
|
+
setupScriptFile === undefined ? null : extractSetupScriptContent(setupScriptFile, agentId);
|
|
407
|
+
if (setupContent) baselines.setupScript = contentSha256(setupContent);
|
|
408
|
+
return baselines;
|
|
342
409
|
}
|
|
343
410
|
|
|
344
411
|
export async function readIdentityBaselines(
|
|
@@ -353,6 +420,42 @@ export async function readIdentityBaselines(
|
|
|
353
420
|
}
|
|
354
421
|
}
|
|
355
422
|
|
|
423
|
+
/**
|
|
424
|
+
* Record the hashes of profile files the API just accepted: the DB now holds
|
|
425
|
+
* that content, so the same bytes on disk are no longer an edit. The update
|
|
426
|
+
* runs under IDENTITY_BASELINES_LOCK_PATH so concurrent syncs never drop each
|
|
427
|
+
* other's hashes. Best effort: on failure the older baselines stay (logged).
|
|
428
|
+
*/
|
|
429
|
+
export async function advanceIdentityBaselines(
|
|
430
|
+
synced: Record<string, string>,
|
|
431
|
+
io: ProfileFileIO = PROFILE_FILE_IO,
|
|
432
|
+
): Promise<void> {
|
|
433
|
+
if (Object.keys(synced).length === 0) return;
|
|
434
|
+
const update = async () => {
|
|
435
|
+
const baselines = (await readIdentityBaselines(io.readFile)) ?? {};
|
|
436
|
+
for (const [field, content] of Object.entries(synced)) {
|
|
437
|
+
baselines[field] = contentSha256(content);
|
|
438
|
+
}
|
|
439
|
+
await io.writeFile(IDENTITY_BASELINES_PATH, JSON.stringify(baselines));
|
|
440
|
+
};
|
|
441
|
+
try {
|
|
442
|
+
const locked = await withFileLock(
|
|
443
|
+
io.baselinesLockPath ?? IDENTITY_BASELINES_LOCK_PATH,
|
|
444
|
+
update,
|
|
445
|
+
{
|
|
446
|
+
waitMs: BASELINES_LOCK_WAIT_MS,
|
|
447
|
+
},
|
|
448
|
+
);
|
|
449
|
+
if (locked.acquired) return;
|
|
450
|
+
// No flock on this platform: nothing else can serialize it, so update anyway.
|
|
451
|
+
if (locked.reason === "unsupported") return await update();
|
|
452
|
+
console.warn(`[profile-sync] identity baselines not advanced: lock ${locked.reason}`);
|
|
453
|
+
} catch (err) {
|
|
454
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
455
|
+
console.warn(scrubSecrets(`[profile-sync] identity baselines not advanced: ${msg}`));
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
356
459
|
/**
|
|
357
460
|
* Claude Code's personal-file CLAUDE.md path. This is what the Claude plugin
|
|
358
461
|
* Stop hook reads and owns — the runner only uses it as a backstop for an
|
|
@@ -581,6 +684,8 @@ export interface ProfileSyncOptions {
|
|
|
581
684
|
claudeMdLock?: { path?: string; waitMs?: number };
|
|
582
685
|
/** Injectable fetch for tests. Defaults to the global `fetch`. */
|
|
583
686
|
fetchImpl?: typeof fetch;
|
|
687
|
+
/** Injectable profile-file IO for tests. Defaults to the worker's FS. */
|
|
688
|
+
io?: ProfileFileIO;
|
|
584
689
|
}
|
|
585
690
|
|
|
586
691
|
/**
|
|
@@ -658,7 +763,7 @@ export function extractSetupScriptContent(raw: string, agentId = "unknown"): str
|
|
|
658
763
|
*
|
|
659
764
|
* When `baselines` is provided, skips any field whose content hash matches the
|
|
660
765
|
* baseline (i.e. the file was not modified during the session). This prevents
|
|
661
|
-
*
|
|
766
|
+
* a sync from clobbering DB-side edits made by Lead.
|
|
662
767
|
*/
|
|
663
768
|
export function buildIdentityPayload(
|
|
664
769
|
files: {
|
|
@@ -729,7 +834,7 @@ export function buildIdentityPayload(
|
|
|
729
834
|
export type FileReader = (path: string) => Promise<string | undefined>;
|
|
730
835
|
|
|
731
836
|
/** Default file reader — reads from the worker's local FS via Bun. */
|
|
732
|
-
async function readFileIfExists(path: string): Promise<string | undefined> {
|
|
837
|
+
export async function readFileIfExists(path: string): Promise<string | undefined> {
|
|
733
838
|
try {
|
|
734
839
|
const file = Bun.file(path);
|
|
735
840
|
if (!(await file.exists())) return undefined;
|
|
@@ -739,6 +844,16 @@ async function readFileIfExists(path: string): Promise<string | undefined> {
|
|
|
739
844
|
}
|
|
740
845
|
}
|
|
741
846
|
|
|
847
|
+
/** Writes a file's text. */
|
|
848
|
+
export type FileWriter = (path: string, content: string) => Promise<void>;
|
|
849
|
+
|
|
850
|
+
/** Write via a temp file and rename, so a concurrent reader never sees a partial file. */
|
|
851
|
+
async function writeFileAtomic(path: string, content: string): Promise<void> {
|
|
852
|
+
const tmp = `${path}.${process.pid}.${crypto.randomUUID()}.tmp`;
|
|
853
|
+
await Bun.write(tmp, content);
|
|
854
|
+
await rename(tmp, path);
|
|
855
|
+
}
|
|
856
|
+
|
|
742
857
|
/**
|
|
743
858
|
* Compare the five local profile markdown files with the authenticated agent's
|
|
744
859
|
* stored DB values. This must run inside the agent's local environment; a
|
|
@@ -788,11 +903,11 @@ export async function auditProfileDivergences(
|
|
|
788
903
|
* `fields` selects which groups to include. The file reader is injectable so
|
|
789
904
|
* the field-selection / guard logic can be unit-tested without touching the FS.
|
|
790
905
|
*
|
|
791
|
-
*
|
|
792
|
-
*
|
|
793
|
-
*
|
|
794
|
-
*
|
|
795
|
-
*
|
|
906
|
+
* Loads the baseline hashes and skips identity fields and the setup script
|
|
907
|
+
* whose content hasn't changed — this prevents blind-overwriting DB-side edits
|
|
908
|
+
* made by Lead during the session. That holds for on-edit syncs
|
|
909
|
+
* (`"self_edit"`) too: an edit to one file triggers a sync of every identity
|
|
910
|
+
* file, and the untouched ones still hold the boot-time copy.
|
|
796
911
|
*/
|
|
797
912
|
export async function collectProfilePayloads(
|
|
798
913
|
fields: ProfileSyncField[],
|
|
@@ -803,7 +918,7 @@ export async function collectProfilePayloads(
|
|
|
803
918
|
): Promise<ProfilePayload[]> {
|
|
804
919
|
const payloads: ProfilePayload[] = [];
|
|
805
920
|
|
|
806
|
-
const baselines =
|
|
921
|
+
const baselines = await readIdentityBaselines(readFile);
|
|
807
922
|
|
|
808
923
|
if (fields.includes("identity")) {
|
|
809
924
|
const updates = buildIdentityPayload(
|
|
@@ -843,7 +958,7 @@ export async function collectProfilePayloads(
|
|
|
843
958
|
const raw = await readFile(SETUP_SCRIPT_PATH);
|
|
844
959
|
if (raw !== undefined) {
|
|
845
960
|
const content = extractSetupScriptContent(raw, agentId);
|
|
846
|
-
if (content !== null) {
|
|
961
|
+
if (content !== null && baselines?.setupScript !== contentSha256(content)) {
|
|
847
962
|
payloads.push({ label: "setup", body: { setupScript: content, changeSource } });
|
|
848
963
|
}
|
|
849
964
|
}
|
|
@@ -855,12 +970,13 @@ export async function collectProfilePayloads(
|
|
|
855
970
|
/**
|
|
856
971
|
* POST a single profile update. NON-FATAL but VISIBLE: a non-2xx response or a
|
|
857
972
|
* thrown error is logged (scrubbed) and swallowed so it never fails the task,
|
|
858
|
-
* but — unlike the original copies — it is never silently ignored.
|
|
973
|
+
* but — unlike the original copies — it is never silently ignored. Resolves to
|
|
974
|
+
* whether the API accepted it.
|
|
859
975
|
*/
|
|
860
976
|
export async function postProfileUpdate(
|
|
861
977
|
opts: Pick<ProfileSyncOptions, "agentId" | "apiUrl" | "apiKey" | "fetchImpl">,
|
|
862
978
|
payload: ProfilePayload,
|
|
863
|
-
): Promise<
|
|
979
|
+
): Promise<boolean> {
|
|
864
980
|
const doFetch = opts.fetchImpl ?? fetch;
|
|
865
981
|
try {
|
|
866
982
|
const resp = await doFetch(`${opts.apiUrl}/api/agents/${opts.agentId}/profile`, {
|
|
@@ -885,10 +1001,26 @@ export async function postProfileUpdate(
|
|
|
885
1001
|
),
|
|
886
1002
|
);
|
|
887
1003
|
}
|
|
1004
|
+
return resp.ok;
|
|
888
1005
|
} catch (err) {
|
|
889
1006
|
const msg = err instanceof Error ? err.message : String(err);
|
|
890
1007
|
console.warn(scrubSecrets(`[profile-sync] ${payload.label} sync errored: ${msg}`));
|
|
1008
|
+
return false;
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
/**
|
|
1013
|
+
* The baseline-tracked profile fields a payload carried. Only for payloads sent
|
|
1014
|
+
* outside the CLAUDE.md lock: the hook-owned personal CLAUDE.md keeps its own
|
|
1015
|
+
* lineage record, but an unconditional CLAUDE.md sync (the workspace copy, or a
|
|
1016
|
+
* self_edit) is tracked by its baseline like every other file.
|
|
1017
|
+
*/
|
|
1018
|
+
function baselineFieldsOf(payload: ProfilePayload): Record<string, string> {
|
|
1019
|
+
const fields: Record<string, string> = {};
|
|
1020
|
+
for (const [field, value] of Object.entries(payload.body)) {
|
|
1021
|
+
if (field !== "changeSource" && typeof value === "string") fields[field] = value;
|
|
891
1022
|
}
|
|
1023
|
+
return fields;
|
|
892
1024
|
}
|
|
893
1025
|
|
|
894
1026
|
/**
|
|
@@ -912,16 +1044,19 @@ export async function syncProfileFilesToServer(opts: ProfileSyncOptions): Promis
|
|
|
912
1044
|
fields.includes("claude") && syncsHookOwnedClaudeMd(claudeMdPath, changeSource);
|
|
913
1045
|
const unlockedFields = claudeUnderLock ? fields.filter((f) => f !== "claude") : fields;
|
|
914
1046
|
|
|
1047
|
+
const io = opts.io ?? PROFILE_FILE_IO;
|
|
915
1048
|
const payloads = await collectProfilePayloads(
|
|
916
1049
|
unlockedFields,
|
|
917
1050
|
changeSource,
|
|
918
|
-
|
|
1051
|
+
io.readFile,
|
|
919
1052
|
claudeMdPath,
|
|
920
1053
|
opts.agentId,
|
|
921
1054
|
);
|
|
1055
|
+
const synced: Record<string, string> = {};
|
|
922
1056
|
for (const payload of payloads) {
|
|
923
|
-
await postProfileUpdate(opts, payload);
|
|
1057
|
+
if (await postProfileUpdate(opts, payload)) Object.assign(synced, baselineFieldsOf(payload));
|
|
924
1058
|
}
|
|
1059
|
+
await advanceIdentityBaselines(synced, io);
|
|
925
1060
|
|
|
926
1061
|
if (claudeUnderLock) {
|
|
927
1062
|
const { path: lockPath = CLAUDE_MD_LOCK_PATH, waitMs = RUNNER_LOCK_WAIT_MS } =
|
|
@@ -933,7 +1068,7 @@ export async function syncProfileFilesToServer(opts: ProfileSyncOptions): Promis
|
|
|
933
1068
|
const claude = await collectProfilePayloads(
|
|
934
1069
|
["claude"],
|
|
935
1070
|
changeSource,
|
|
936
|
-
|
|
1071
|
+
io.readFile,
|
|
937
1072
|
claudeMdPath,
|
|
938
1073
|
opts.agentId,
|
|
939
1074
|
);
|
package/src/commands/runner.ts
CHANGED
|
@@ -135,7 +135,7 @@ import {
|
|
|
135
135
|
} from "./credential-wait.ts";
|
|
136
136
|
import { refreshIdentityIfChanged } from "./identity-refresh.ts";
|
|
137
137
|
import {
|
|
138
|
-
|
|
138
|
+
bootIdentityBaselines,
|
|
139
139
|
prependProfileSyncRejectionBanner,
|
|
140
140
|
resolveClaudeMdPath,
|
|
141
141
|
syncProfileFilesToServer,
|
|
@@ -6779,12 +6779,20 @@ export async function runAgent(config: RunnerConfig, opts: RunnerOptions) {
|
|
|
6779
6779
|
// are skipped, which prevents clobbering DB-side edits made by Lead via
|
|
6780
6780
|
// update-profile during the running session.
|
|
6781
6781
|
try {
|
|
6782
|
-
|
|
6783
|
-
|
|
6784
|
-
|
|
6785
|
-
|
|
6786
|
-
|
|
6787
|
-
|
|
6782
|
+
// start-up.sh is not rewritten here when it exists: the entrypoint composed
|
|
6783
|
+
// it from the DB at container start, so its agent-managed section is the base.
|
|
6784
|
+
const setupFile = Bun.file("/workspace/start-up.sh");
|
|
6785
|
+
const baselines = bootIdentityBaselines(
|
|
6786
|
+
{
|
|
6787
|
+
soulMd: agentSoulMd,
|
|
6788
|
+
identityMd: agentIdentityMd,
|
|
6789
|
+
toolsMd: agentToolsMd,
|
|
6790
|
+
heartbeatMd: agentHeartbeatMd,
|
|
6791
|
+
claudeMd: agentClaudeMd,
|
|
6792
|
+
},
|
|
6793
|
+
(await setupFile.exists()) ? await setupFile.text() : undefined,
|
|
6794
|
+
agentId,
|
|
6795
|
+
);
|
|
6788
6796
|
await writeIdentityBaselines(baselines);
|
|
6789
6797
|
console.log(`[${role}] Recorded identity file baselines for session-end sync`);
|
|
6790
6798
|
} catch {
|
package/src/hooks/hook.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
} from "../be/memory/raters/llm";
|
|
12
12
|
import { materializeClaudeMd, stopClaudeMd } from "../commands/claude-md-session";
|
|
13
13
|
import {
|
|
14
|
+
advanceIdentityBaselines,
|
|
14
15
|
buildIndependentIdentityPayloads,
|
|
15
16
|
contentSha256,
|
|
16
17
|
fetchWithSyncTimeout,
|
|
@@ -148,7 +149,7 @@ export async function postHookProfileUpdate({
|
|
|
148
149
|
body: ProfilePayload["body"];
|
|
149
150
|
label: string;
|
|
150
151
|
fetchImpl?: typeof fetch;
|
|
151
|
-
}): Promise<
|
|
152
|
+
}): Promise<boolean> {
|
|
152
153
|
try {
|
|
153
154
|
const response = await fetchImpl(url, {
|
|
154
155
|
method: "PUT",
|
|
@@ -166,9 +167,11 @@ export async function postHookProfileUpdate({
|
|
|
166
167
|
),
|
|
167
168
|
);
|
|
168
169
|
}
|
|
170
|
+
return response.ok;
|
|
169
171
|
} catch (error) {
|
|
170
172
|
const message = error instanceof Error ? error.message : String(error);
|
|
171
173
|
console.error(scrubSecrets(`[hook] ${label} profile sync errored: ${message}`));
|
|
174
|
+
return false;
|
|
172
175
|
}
|
|
173
176
|
}
|
|
174
177
|
|
|
@@ -676,10 +679,10 @@ export async function handleHook(): Promise<void> {
|
|
|
676
679
|
/**
|
|
677
680
|
* Sync SOUL.md and IDENTITY.md content back to the server.
|
|
678
681
|
*
|
|
679
|
-
*
|
|
680
|
-
*
|
|
681
|
-
*
|
|
682
|
-
*
|
|
682
|
+
* Loads the baseline hashes and skips any file whose content hasn't changed.
|
|
683
|
+
* This prevents clobbering DB-side edits that Lead made via `update-profile`
|
|
684
|
+
* during the running session: at Stop, and on a `"self_edit"` too, where an
|
|
685
|
+
* edit to one file re-reads all four.
|
|
683
686
|
*/
|
|
684
687
|
const syncIdentityFilesToServer = async (
|
|
685
688
|
agentId: string,
|
|
@@ -687,7 +690,7 @@ export async function handleHook(): Promise<void> {
|
|
|
687
690
|
): Promise<void> => {
|
|
688
691
|
if (!mcpConfig) return;
|
|
689
692
|
|
|
690
|
-
const baselines =
|
|
693
|
+
const baselines = await readIdentityBaselines();
|
|
691
694
|
|
|
692
695
|
const updates: Record<string, string> = {};
|
|
693
696
|
|
|
@@ -747,14 +750,19 @@ export async function handleHook(): Promise<void> {
|
|
|
747
750
|
|
|
748
751
|
if (Object.keys(updates).length === 0) return;
|
|
749
752
|
|
|
750
|
-
|
|
751
|
-
|
|
753
|
+
const synced: Record<string, string> = {};
|
|
754
|
+
for (const [field, value] of Object.entries(updates)) {
|
|
755
|
+
const [payload] = buildIndependentIdentityPayloads({ [field]: value }, changeSource);
|
|
756
|
+
if (!payload) continue;
|
|
757
|
+
const ok = await postHookProfileUpdate({
|
|
752
758
|
url: `${getBaseUrl()}/api/agents/${agentId}/profile`,
|
|
753
759
|
headers: mcpConfig.headers,
|
|
754
760
|
body: payload.body,
|
|
755
761
|
label: payload.label,
|
|
756
762
|
});
|
|
763
|
+
if (ok) synced[field] = value;
|
|
757
764
|
}
|
|
765
|
+
await advanceIdentityBaselines(synced);
|
|
758
766
|
};
|
|
759
767
|
|
|
760
768
|
/**
|
|
@@ -792,9 +800,11 @@ export async function handleHook(): Promise<void> {
|
|
|
792
800
|
warnProfileFileTooLarge(agentId, "setupScript", content.length, "hook");
|
|
793
801
|
return;
|
|
794
802
|
}
|
|
803
|
+
// Unchanged since it last matched the DB: not an edit, so don't send it.
|
|
804
|
+
if ((await readIdentityBaselines())?.setupScript === contentSha256(content)) return;
|
|
795
805
|
|
|
796
806
|
try {
|
|
797
|
-
await fetch(`${getBaseUrl()}/api/agents/${agentId}/profile`, {
|
|
807
|
+
const resp = await fetch(`${getBaseUrl()}/api/agents/${agentId}/profile`, {
|
|
798
808
|
method: "PUT",
|
|
799
809
|
headers: {
|
|
800
810
|
...mcpConfig.headers,
|
|
@@ -802,6 +812,7 @@ export async function handleHook(): Promise<void> {
|
|
|
802
812
|
},
|
|
803
813
|
body: JSON.stringify({ setupScript: content, changeSource }),
|
|
804
814
|
});
|
|
815
|
+
if (resp.ok) await advanceIdentityBaselines({ setupScript: content });
|
|
805
816
|
} catch {
|
|
806
817
|
/* silently fail */
|
|
807
818
|
}
|
package/src/http/index.ts
CHANGED
|
@@ -33,6 +33,7 @@ import { startRuntimeSupervisor, stopRuntimeSupervisor } from "../be/runtime-poo
|
|
|
33
33
|
import { startScratchScriptGc, stopScratchScriptGc } from "../be/scripts/retention";
|
|
34
34
|
import { loadSecretRegistry } from "../be/secret-registry";
|
|
35
35
|
import { seedLegacyCapabilitiesConfig } from "../be/seed-capabilities";
|
|
36
|
+
import { startTaskAutoTitleListener } from "../be/task-auto-title-listener";
|
|
36
37
|
import { initApiTelemetry } from "../be/telemetry-enable";
|
|
37
38
|
import { startWorkerNudgeListener } from "../be/worker-nudge-listener";
|
|
38
39
|
import { initBitbucket } from "../bitbucket";
|
|
@@ -465,6 +466,7 @@ const detachRealtime = attachRealtimeTransport(httpServer);
|
|
|
465
466
|
const { attachControllerTransport } = await import("./controllers-connect");
|
|
466
467
|
const detachControllers = attachControllerTransport(httpServer);
|
|
467
468
|
const stopWorkerNudges = startWorkerNudgeListener();
|
|
469
|
+
const taskAutoTitles = startTaskAutoTitleListener();
|
|
468
470
|
const stopRoomDeletion = realtimeBus.subscribe("room:namespace-deleted", (namespace) => {
|
|
469
471
|
void Promise.resolve(removeNamespaceRooms(String(namespace))).catch((error) =>
|
|
470
472
|
console.error("[rooms] Deletion failed:", scrubSecrets(String(error))),
|
|
@@ -474,6 +476,7 @@ globalState.__closeRealtime = () => {
|
|
|
474
476
|
detachRealtime();
|
|
475
477
|
detachControllers();
|
|
476
478
|
stopWorkerNudges();
|
|
479
|
+
taskAutoTitles.stop();
|
|
477
480
|
stopRoomDeletion();
|
|
478
481
|
};
|
|
479
482
|
globalState.__httpServer = httpServer;
|
|
@@ -877,7 +880,7 @@ httpServer
|
|
|
877
880
|
// Delete expired MCP overflow values (10-min tick, off the spill path)
|
|
878
881
|
startMcpOverflowSweep();
|
|
879
882
|
|
|
880
|
-
// Start expired-memory garbage collector (1-hour tick,
|
|
883
|
+
// Start expired-memory garbage collector (1-hour tick, first run 2 min after boot)
|
|
881
884
|
await startMemoryGc();
|
|
882
885
|
|
|
883
886
|
// Start the opt-in DB retention sweep after config hydration. Every key is
|
|
@@ -3,8 +3,11 @@
|
|
|
3
3
|
*
|
|
4
4
|
* A list total is a COUNT over the same filters as the page, and on the large
|
|
5
5
|
* tables behind the sessions and task lists it costs as much as the page
|
|
6
|
-
* itself.
|
|
7
|
-
* triage move) shows at once where the user looks first.
|
|
6
|
+
* itself. By default page 1 (offset 0) always counts, so a write (a new
|
|
7
|
+
* session, a triage move) shows at once where the user looks first. A list
|
|
8
|
+
* whose count is expensive and whose total may lag can pass
|
|
9
|
+
* `firstPageTtlMs`: page 1 then reuses a count younger than that, so
|
|
10
|
+
* dashboard tabs that poll the same list share one count. Later pages reuse
|
|
8
11
|
* that count for 30 s. A cold later page (deep link, expired entry) counts
|
|
9
12
|
* too, so `total` is always set.
|
|
10
13
|
*/
|
|
@@ -13,7 +16,7 @@ export const LIST_TOTAL_TTL_MS = 30_000;
|
|
|
13
16
|
const MAX_ENTRIES = 500;
|
|
14
17
|
|
|
15
18
|
interface Entry {
|
|
16
|
-
|
|
19
|
+
countedAt: number;
|
|
17
20
|
total: Promise<number>;
|
|
18
21
|
}
|
|
19
22
|
|
|
@@ -34,19 +37,23 @@ function filterKey(value: unknown): string {
|
|
|
34
37
|
);
|
|
35
38
|
}
|
|
36
39
|
|
|
37
|
-
export function createListTotalCache(
|
|
40
|
+
export function createListTotalCache(
|
|
41
|
+
ttlMs = LIST_TOTAL_TTL_MS,
|
|
42
|
+
firstPageTtlMs = 0,
|
|
43
|
+
): ListTotalCache {
|
|
38
44
|
const entries = new Map<string, Entry>();
|
|
39
45
|
return {
|
|
40
46
|
get(filters, offset, compute) {
|
|
41
47
|
const key = filterKey(filters);
|
|
42
48
|
const now = Date.now();
|
|
43
49
|
const hit = entries.get(key);
|
|
44
|
-
|
|
50
|
+
const maxAge = (offset ?? 0) > 0 ? ttlMs : Math.min(ttlMs, firstPageTtlMs);
|
|
51
|
+
if (hit && hit.countedAt + maxAge > now) return hit.total;
|
|
45
52
|
if (hit) entries.delete(key);
|
|
46
53
|
|
|
47
|
-
// Cache the promise, so a
|
|
54
|
+
// Cache the promise, so a request that arrives mid-count shares it.
|
|
48
55
|
const total = compute();
|
|
49
|
-
entries.set(key, {
|
|
56
|
+
entries.set(key, { countedAt: now, total });
|
|
50
57
|
total.catch(() => {
|
|
51
58
|
if (entries.get(key)?.total === total) entries.delete(key);
|
|
52
59
|
});
|
package/src/http/memory.ts
CHANGED
|
@@ -1568,17 +1568,31 @@ const MEMORY_GC_INTERVAL_MS = 60 * 60 * 1000; // 1 hour
|
|
|
1568
1568
|
let memoryGcTimer: ReturnType<typeof setInterval> | null = null;
|
|
1569
1569
|
|
|
1570
1570
|
const SEARCH_RETRIEVAL_TTL_DAYS = 90;
|
|
1571
|
+
/** Rows per purge statement. The purge yields the event loop between batches. */
|
|
1572
|
+
const SEARCH_RETRIEVAL_PURGE_BATCH = 2000;
|
|
1571
1573
|
|
|
1572
|
-
async function purgeStaleSearchRetrievals(
|
|
1574
|
+
export async function purgeStaleSearchRetrievals(
|
|
1575
|
+
batchSize = SEARCH_RETRIEVAL_PURGE_BATCH,
|
|
1576
|
+
): Promise<number> {
|
|
1573
1577
|
try {
|
|
1574
1578
|
const cutoff = new Date(
|
|
1575
1579
|
Date.now() - SEARCH_RETRIEVAL_TTL_DAYS * 24 * 60 * 60 * 1000,
|
|
1576
1580
|
).toISOString();
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1581
|
+
let purged = 0;
|
|
1582
|
+
for (;;) {
|
|
1583
|
+
// `+eventType` keeps the planner on the retrievedAt range. Without
|
|
1584
|
+
// sqlite_stat1 it prefers the eventType equality, which reads every
|
|
1585
|
+
// search row to test its date, even when nothing is stale.
|
|
1586
|
+
const result = await getDbClient().run(
|
|
1587
|
+
`DELETE FROM memory_retrieval WHERE rowid IN (
|
|
1588
|
+
SELECT rowid FROM memory_retrieval
|
|
1589
|
+
WHERE +eventType = 'search' AND retrievedAt < ? LIMIT ?)`,
|
|
1590
|
+
[cutoff, batchSize],
|
|
1591
|
+
);
|
|
1592
|
+
purged += result.changes;
|
|
1593
|
+
if (result.changes < batchSize) return purged;
|
|
1594
|
+
await new Promise<void>((resolve) => setImmediate(resolve));
|
|
1595
|
+
}
|
|
1582
1596
|
} catch (err) {
|
|
1583
1597
|
console.error("[memory-gc] Search retrieval purge failed:", (err as Error).message);
|
|
1584
1598
|
return 0;
|
|
@@ -1602,11 +1616,22 @@ async function runMemoryGcTick(label: "Initial" | "Periodic"): Promise<void> {
|
|
|
1602
1616
|
}
|
|
1603
1617
|
}
|
|
1604
1618
|
|
|
1605
|
-
|
|
1619
|
+
/** The first purge waits this long, so it does not compete with boot work. */
|
|
1620
|
+
const MEMORY_GC_INITIAL_DELAY_MS = 2 * 60 * 1000;
|
|
1621
|
+
let memoryGcInitialTimer: ReturnType<typeof setTimeout> | null = null;
|
|
1622
|
+
|
|
1623
|
+
export async function startMemoryGc(
|
|
1624
|
+
intervalMs = MEMORY_GC_INTERVAL_MS,
|
|
1625
|
+
initialDelayMs = MEMORY_GC_INITIAL_DELAY_MS,
|
|
1626
|
+
): Promise<void> {
|
|
1606
1627
|
if (memoryGcTimer) return;
|
|
1607
1628
|
|
|
1608
|
-
//
|
|
1609
|
-
|
|
1629
|
+
// Clear any backlog soon after startup, but off the boot path.
|
|
1630
|
+
memoryGcInitialTimer = setTimeout(() => {
|
|
1631
|
+
memoryGcInitialTimer = null;
|
|
1632
|
+
void runMemoryGcTick("Initial");
|
|
1633
|
+
}, initialDelayMs);
|
|
1634
|
+
if (typeof memoryGcInitialTimer?.unref === "function") memoryGcInitialTimer.unref();
|
|
1610
1635
|
|
|
1611
1636
|
memoryGcTimer = setInterval(() => {
|
|
1612
1637
|
void runMemoryGcTick("Periodic");
|
|
@@ -1615,6 +1640,10 @@ export async function startMemoryGc(intervalMs = MEMORY_GC_INTERVAL_MS): Promise
|
|
|
1615
1640
|
}
|
|
1616
1641
|
|
|
1617
1642
|
export function stopMemoryGc(): void {
|
|
1643
|
+
if (memoryGcInitialTimer) {
|
|
1644
|
+
clearTimeout(memoryGcInitialTimer);
|
|
1645
|
+
memoryGcInitialTimer = null;
|
|
1646
|
+
}
|
|
1618
1647
|
if (memoryGcTimer) {
|
|
1619
1648
|
clearInterval(memoryGcTimer);
|
|
1620
1649
|
memoryGcTimer = null;
|