@desplega.ai/agent-swarm 2.5.4 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/dist/{acp-adapter-ek38cwsv.js → acp-adapter-ma1js5n7.js} +9 -9
- package/dist/{actions-92mepq03.js → actions-1vhmtvg9.js} +21 -21
- package/dist/{amp-adapter-9gaxg015.js → amp-adapter-sxqqvmdk.js} +4 -4
- package/dist/{app-ctn8gsgx.js → app-mwx1d5a9.js} +3 -3
- package/dist/{assistant-zyr5kz47.js → assistant-yx1p6g82.js} +30 -26
- package/dist/{boot-reembed-w75maga2.js → boot-reembed-5h37a2c2.js} +1 -1
- package/dist/{boot-reembed-dwzfttdr.js → boot-reembed-7h78vmvv.js} +2 -1
- package/dist/{boot-scrub-sweep-e9kq43kf.js → boot-scrub-sweep-8mawd4gw.js} +4 -4
- package/dist/{boot-seeds-x28v3fhb.js → boot-seeds-ca84dhnn.js} +4 -4
- package/dist/{claude-adapter-f0afqnne.js → claude-adapter-wqt4hfkq.js} +9 -9
- package/dist/{claude-managed-adapter-x81a7r65.js → claude-managed-adapter-6fjqtr2z.js} +2 -2
- package/dist/{claude-managed-setup-bqzwjd9x.js → claude-managed-setup-rqv98sea.js} +10 -3
- package/dist/{claude-sdk-session-7b7whkaq.js → claude-sdk-session-f29f3b2x.js} +11 -11
- package/dist/{cli-sxgtf5fw.js → cli-07q03xm2.js} +1 -1
- package/dist/{cli-kxpfx2xr.js → cli-0z1xjzwa.js} +2 -2
- package/dist/{cli-g9vd9ece.js → cli-1hhbzkbb.js} +14 -16
- package/dist/{cli-qqzmjxkf.js → cli-1m9x8dr5.js} +11 -6
- package/dist/{cli-jbnn4vxb.js → cli-2pvzmas1.js} +3 -3
- package/dist/{cli-x77gen2p.js → cli-2y7v133v.js} +7 -7
- package/dist/{cli-dew6x255.js → cli-39aet655.js} +2 -2
- package/dist/{cli-g1cw7xtt.js → cli-39rrw8yp.js} +1 -1
- package/dist/{cli-jb30dzvk.js → cli-48gxjmcz.js} +530 -190
- package/dist/{cli-pzatvw94.js → cli-54sj64fc.js} +2 -2
- package/dist/{cli-bvpgr9ny.js → cli-60sty27p.js} +5 -5
- package/dist/{cli-asq5ydm4.js → cli-6a9qfa8b.js} +411 -69
- package/dist/{cli-277c31dw.js → cli-6mgdba42.js} +225 -3
- package/dist/{cli-19202kew.js → cli-6srv653c.js} +5 -5
- package/dist/{cli-n4c8e3s5.js → cli-708wdy32.js} +1 -1
- package/dist/{cli-pasyvjmm.js → cli-737pyqxr.js} +1 -1
- package/dist/{cli-6x9ma5w5.js → cli-803188pb.js} +1 -1
- package/dist/{cli-hh5we352.js → cli-86ehn4s5.js} +7 -7
- package/dist/{cli-9s05422w.js → cli-8mbc9za9.js} +1 -1
- package/dist/{cli-ff1tm9n9.js → cli-8wmj18cz.js} +1 -1
- package/dist/{cli-1r5ad7j5.js → cli-9gsm2rzp.js} +12 -12
- package/dist/{cli-mz9afxxf.js → cli-9pshx6rd.js} +17 -3
- package/dist/{cli-s682zgqz.js → cli-a6ds2epe.js} +1 -1
- package/dist/{cli-6a1xg1p7.js → cli-a89zdqz9.js} +2 -2
- package/dist/{cli-03vpq45j.js → cli-ay40dnj3.js} +2 -2
- package/dist/{cli-tsma7p8v.js → cli-aypd09nf.js} +2 -2
- package/dist/{cli-ethgy7ha.js → cli-b3yzyc57.js} +2 -2
- package/dist/{cli-bn0rewsy.js → cli-c081bj3b.js} +2 -2
- package/dist/{cli-mmhj8vd7.js → cli-c668vvcf.js} +11 -2
- package/dist/{cli-sbwsxvtd.js → cli-d20qvpr2.js} +2 -2
- package/dist/{cli-c952c7ac.js → cli-e1595bgx.js} +3 -3
- package/dist/{cli-621ce7kw.js → cli-f12mr20z.js} +3 -3
- package/dist/{cli-dqygeeh5.js → cli-f1e0ptjj.js} +10 -1
- package/dist/{cli-xzfq4at3.js → cli-f4dbh7a5.js} +16 -12
- package/dist/{cli-ha9rmwgq.js → cli-fb8m699c.js} +2 -2
- package/dist/{cli-5bnsc0e1.js → cli-gsfk58nw.js} +5 -5
- package/dist/cli-gt4h8cxw.js +895 -0
- package/dist/{cli-nwyw3zhg.js → cli-gxee0bqa.js} +1 -1
- package/dist/{cli-25n3mv5d.js → cli-h5jeg46p.js} +6 -6
- package/dist/{cli-gn5af0x5.js → cli-he9gbdx0.js} +62 -12
- package/dist/{cli-gbfxp48k.js → cli-j0ppywdr.js} +42 -35
- package/dist/{cli-k4tjsdx9.js → cli-j3y0x2n4.js} +277 -199
- package/dist/{cli-7p4jxgtt.js → cli-j9bpmykh.js} +75 -27
- package/dist/{cli-dabehxhp.js → cli-kt907da6.js} +1 -1
- package/dist/{cli-wx2nj19m.js → cli-kv7sbc2c.js} +6 -6
- package/dist/{cli-abs8ndtr.js → cli-m30k9777.js} +26 -26
- package/dist/{cli-cz5nmdxt.js → cli-m5e802r7.js} +2 -2
- package/dist/{cli-0shx9j93.js → cli-pfk92eqz.js} +2 -2
- package/dist/{cli-xtrz6bzs.js → cli-ptfq202x.js} +5 -3
- package/dist/{cli-6n8axjv1.js → cli-qj442fws.js} +2 -2
- package/dist/{cli-c61mbdds.js → cli-rd5m70gj.js} +4 -4
- package/dist/{cli-eftzyh9y.js → cli-s09j9vp5.js} +2 -2
- package/dist/{cli-m4ecdxvz.js → cli-s0mgx214.js} +7 -7
- package/dist/{cli-wghjkr0e.js → cli-sgqm2582.js} +10 -7
- package/dist/{cli-2655k16q.js → cli-sjctjr48.js} +2 -2
- package/dist/{cli-nzfnt2gs.js → cli-tf1n0r0b.js} +1 -1
- package/dist/{cli-x0f2v69c.js → cli-twctnkng.js} +12 -7
- package/dist/{cli-j6rktc91.js → cli-v6shg7na.js} +1 -1
- package/dist/{cli-gx4rm0qx.js → cli-vz78s5wm.js} +1 -1
- package/dist/{cli-gwhjmh8g.js → cli-wqr7cea4.js} +5 -5
- package/dist/{cli-e42vss7b.js → cli-x37r7qwf.js} +16 -8
- package/dist/{cli-4b2z6n0m.js → cli-yd2nreqw.js} +17 -8
- package/dist/{cli-64mh9mf3.js → cli-ymjpkxgn.js} +69 -17
- package/dist/{cli-5kqnemen.js → cli-yvy48785.js} +1 -1
- package/dist/{cli-9j129brq.js → cli-yxbsmx56.js} +6 -6
- package/dist/cli.js +22 -20
- package/dist/{codex-adapter-wch12q99.js → codex-adapter-zk59x2zh.js} +7 -7
- package/dist/{codex-hook-brpnycn5.js → codex-hook-xyjhvxwd.js} +1 -1
- package/dist/{codex-login-rn7c5gxt.js → codex-login-8cg0vhf2.js} +1 -1
- package/dist/{codex-session-runner-41pfcr95.js → codex-session-runner-kh6s4j3n.js} +8 -8
- package/dist/{commands-9849dkj6.js → commands-68et1qv2.js} +2 -2
- package/dist/{controller-ja0sd95p.js → controller-fyrbnzg4.js} +4 -4
- package/dist/{controllers-connect-xp05dj6x.js → controllers-connect-nep9h2xv.js} +23 -23
- package/dist/{cursor-adapter-623qxqew.js → cursor-adapter-yss3pg7t.js} +2 -2
- package/dist/{db-w759qmx0.js → db-28gjyvxj.js} +4 -2
- package/dist/{db-47vj40dy.js → db-8ftwvzba.js} +3 -3
- package/dist/{devin-adapter-4ycy4vrf.js → devin-adapter-byen2cxt.js} +1 -1
- package/dist/{dispatcher-7b8e4z2j.js → dispatcher-hayexmkr.js} +20 -20
- package/dist/{docker-s84hjs91.js → docker-fdzyewpz.js} +2 -2
- package/dist/{dsh-adapter-2ezw3xea.js → dsh-adapter-46wawff9.js} +2 -2
- package/dist/{e2b-9gkdhm3c.js → e2b-222hsajc.js} +2 -2
- package/dist/{e2b-cjtf8k1z.js → e2b-fn3zacvt.js} +2 -2
- package/dist/{easter-egg-3d-e4qv8dxj.js → easter-egg-3d-8d1e0h4m.js} +1 -1
- package/dist/{event-bus-3z78ep5n.js → event-bus-pghg8hqy.js} +1 -1
- package/dist/{execute-0xpz8950.js → execute-cjrfxfnm.js} +1 -1
- package/dist/{external-channel-e2s028da.js → external-channel-xbmxcg09.js} +2 -2
- package/dist/{grok-adapter-ys7gaqee.js → grok-adapter-ag4wks2a.js} +10 -10
- package/dist/handlers-tzmevqb5.js +41 -0
- package/dist/{heartbeat-w3mntghj.js → heartbeat-psfvcgwp.js} +23 -23
- package/dist/{hook-g7kvv6x9.js → hook-8pz04697.js} +6 -6
- package/dist/{hook-3jjywnpb.js → hook-cyq4mryn.js} +8 -8
- package/dist/{http-tkp7d4kb.js → http-haj1nd6k.js} +921 -169
- package/dist/{index-q7m26kvs.js → index-jwt2bj6f.js} +265 -97
- package/dist/{index-3n8m9ea2.js → index-q52f47pe.js} +1 -1
- package/dist/{index-31ceyf42.js → index-syvhnvtw.js} +1 -1
- package/dist/index-y1ajx04a.js +43 -0
- package/dist/{index-1ef1myq2.js → index-z5h22pes.js} +20 -20
- package/dist/{index-xdaahthb.js → index-zyq8ct52.js} +20 -20
- package/dist/{keepalive-v4ta8wgg.js → keepalive-xdvsamvj.js} +4 -4
- package/dist/lead-hqg57kdp.js +79 -0
- package/dist/{maintenance-84n1ex21.js → maintenance-4y2xzh3a.js} +1 -1
- package/dist/{oauth-refresh-sweep-vjnnandw.js → oauth-refresh-sweep-jd4r7zbs.js} +9 -4
- package/dist/{onboard-sfmpe4np.js → onboard-18447q9q.js} +6 -6
- package/dist/{opencode-adapter-jmpst4q4.js → opencode-adapter-26b2frdf.js} +7 -7
- package/dist/{otel-impl-v6cc0zve.js → otel-impl-f9tgr8yw.js} +2 -2
- package/dist/{pi-mono-adapter-fk8fkcf6.js → pi-mono-adapter-qn6qbga1.js} +38 -17
- package/dist/{pricing-refresh-fsk1e1zc.js → pricing-refresh-dw2tgb53.js} +3 -3
- package/dist/{rbac-roles-prmkdnkw.js → rbac-roles-aygm2760.js} +2 -2
- package/dist/{rbac-roles-zxmxqa39.js → rbac-roles-s4nwj2m3.js} +1 -1
- package/dist/render-v2-yfvdfr0x.js +16 -0
- package/dist/{resume-frdtgfa0.js → resume-1bmvjbf3.js} +9 -9
- package/dist/{runtime-bt5pkpm1.js → runtime-f7tpzedy.js} +1 -1
- package/dist/{session-access-fpez4y5q.js → session-access-r5garej6.js} +4 -4
- package/dist/session-source-backfill-424wzv0r.js +110 -0
- package/dist/{setup-mq6m3m8q.js → setup-8tygvxkq.js} +1 -1
- package/dist/{virtual-modules-mnfbh5mr.js → virtual-modules-xsw323b3.js} +2 -2
- package/dist/{work-objects-kfahhjsq.js → work-objects-ggnghpgf.js} +23 -23
- package/dist/worker-sqjd1nqf.js +79 -0
- package/dist/{x-pbpezr0s.js → x-wxrzvzx8.js} +2 -2
- package/openapi.json +1324 -14
- package/package.json +5 -3
- package/src/assets/source-key.ts +95 -0
- package/src/be/automation-preflight.ts +2 -0
- package/src/be/db/api-keys.ts +89 -0
- package/src/be/db/session-labels.ts +28 -0
- package/src/be/db/session-moves.ts +5 -6
- package/src/be/db/session-tree.ts +153 -14
- package/src/be/db/tasks/read.ts +11 -0
- package/src/be/db/tasks/write.ts +6 -1
- package/src/be/db/workflow-runs.ts +219 -6
- package/src/be/db-retention.ts +61 -6
- package/src/be/db.ts +135 -132
- package/src/be/memory/providers/openai-embedding.ts +5 -1
- package/src/be/memory/task-learning.ts +155 -0
- package/src/be/migrations/244_api_key_validation.sql +4 -0
- package/src/be/migrations/245_script_executor.sql +3 -0
- package/src/be/oauth-refresh-sweep.ts +2 -1
- package/src/be/onboarding.ts +4 -0
- package/src/be/scripts/boot-reembed.ts +2 -0
- package/src/be/scripts/db.ts +14 -4
- package/src/be/scripts/embeddings.ts +2 -0
- package/src/be/scripts/run-saved.ts +1 -0
- package/src/be/scripts/typecheck.ts +10 -4
- package/src/be/seed-skills/index.ts +7 -0
- package/src/be/session-source-backfill.ts +163 -0
- package/src/be/swarm-config-guard.ts +1 -0
- package/src/comb/review-batch.ts +28 -1
- package/src/comb/templates.ts +7 -2
- package/src/commands/claude-managed-setup.ts +7 -1
- package/src/commands/credential-refresh.ts +76 -1
- package/src/commands/credential-wait.ts +2 -2
- package/src/commands/onboard/compose-generator.ts +5 -3
- package/src/commands/provider-credentials.ts +212 -171
- package/src/commands/runner.ts +25 -4
- package/src/extensions/apply-task-create.ts +8 -1
- package/src/extensions/contract-types.generated.ts +1 -1
- package/src/extensions/contract.ts +5 -0
- package/src/heartbeat/heartbeat.ts +14 -1
- package/src/heartbeat/script-runner-probe.ts +47 -0
- package/src/http/all-routes.ts +2 -0
- package/src/http/api-keys.ts +77 -9
- package/src/http/assets.ts +15 -0
- package/src/http/codex-oauth-accounts.ts +191 -0
- package/src/http/comb.ts +15 -2
- package/src/http/config.ts +1 -1
- package/src/http/core.ts +6 -0
- package/src/http/fleet-benchmark.ts +5 -2
- package/src/http/idempotency-key.ts +1 -1
- package/src/http/index.ts +14 -3
- package/src/http/integrations.ts +5 -1
- package/src/http/kv.ts +8 -3
- package/src/http/onboarding.ts +6 -1
- package/src/http/pages-public.ts +38 -5
- package/src/http/pool-keys.ts +354 -0
- package/src/http/scripts.ts +27 -0
- package/src/http/sessions.ts +37 -12
- package/src/http/stats.ts +1 -0
- package/src/http/status.ts +26 -12
- package/src/http/tasks.ts +3 -0
- package/src/http/used-by.ts +7 -2
- package/src/http/workflows.ts +88 -9
- package/src/http/x.ts +5 -0
- package/src/integrations/chat-sdk/inbound.ts +27 -0
- package/src/kv-reserved-namespaces.ts +19 -0
- package/src/prompts/base-prompt.ts +21 -1
- package/src/prompts/session-templates.ts +17 -3
- package/src/providers/acp-targets.ts +1 -1
- package/src/providers/amp-adapter.ts +1 -1
- package/src/providers/claude-adapter.ts +5 -1
- package/src/providers/claude-managed-adapter.ts +16 -9
- package/src/providers/codex-adapter.ts +71 -11
- package/src/providers/dsh-adapter.ts +1 -1
- package/src/providers/pi-mono-adapter.ts +41 -11
- package/src/scheduler/scheduler.ts +5 -3
- package/src/scripts-runtime/eval-harness.ts +8 -1
- package/src/scripts-runtime/executors/native.ts +22 -16
- package/src/scripts-runtime/executors/quickjs-prelude.ts +103 -1
- package/src/scripts-runtime/executors/quickjs-runner.ts +2 -1
- package/src/scripts-runtime/executors/quickjs.ts +7 -0
- package/src/scripts-runtime/executors/registry.ts +32 -0
- package/src/scripts-runtime/executors/types.ts +7 -0
- package/src/scripts-runtime/host-validated-args.ts +133 -0
- package/src/scripts-runtime/loader.ts +30 -3
- package/src/scripts-runtime/swarm-sdk.ts +16 -3
- package/src/scripts-runtime/types/stdlib.d.ts +10 -2
- package/src/scripts-runtime/types/swarm-extension.d.ts +17 -8
- package/src/scripts-runtime/types/swarm-sdk.d.ts +10 -2
- package/src/slack/assistant.ts +7 -0
- package/src/slack/channel-labels.ts +74 -0
- package/src/slack/handlers.ts +8 -0
- package/src/slack/inbound-files.ts +5 -2
- package/src/slack/thread-buffer.ts +4 -0
- package/src/tasks/sibling-awareness.ts +12 -3
- package/src/telemetry-context.ts +5 -6
- package/src/telemetry-identity.ts +3 -3
- package/src/tests/api-key-status-by-suffix.test.ts +242 -0
- package/src/tests/api-keys-validation.test.ts +170 -0
- package/src/tests/base-prompt.test.ts +70 -2
- package/src/tests/claude-managed-adapter.test.ts +46 -0
- package/src/tests/claude-routes-e2e.test.ts +7 -2
- package/src/tests/codex-credential-pools.test.ts +120 -0
- package/src/tests/codex-gateway-route.test.ts +165 -0
- package/src/tests/codex-oauth-accounts.test.ts +357 -0
- package/src/tests/comb-review-batch.test.ts +63 -2
- package/src/tests/community-linear-delivery-loop.test.ts +2099 -0
- package/src/tests/config-pool-keys.test.ts +533 -0
- package/src/tests/credential-check.test.ts +294 -4
- package/src/tests/credential-refresh.test.ts +172 -1
- package/src/tests/db-retention.test.ts +9 -3
- package/src/tests/db-task-read-contract.test.ts +1 -0
- package/src/tests/db-task-write-contract.test.ts +1 -3
- package/src/tests/docs-index-footprint.test.js +3 -3
- package/src/tests/docs-index.test.ts +135 -0
- package/src/tests/dsh-adapter.test.ts +1 -1
- package/src/tests/extensions-pre-task.test.ts +53 -1
- package/src/tests/fetch-resolved-env.test.ts +16 -2
- package/src/tests/fixtures/extensions/record-task-create/hooks.ts +9 -0
- package/src/tests/fixtures/extensions/record-task-create/manifest.json +7 -0
- package/src/tests/fleet-benchmark.test.ts +8 -0
- package/src/tests/get-workflow-run-tool.test.ts +98 -0
- package/src/tests/heartbeat-checklist.test.ts +89 -0
- package/src/tests/heartbeat-script-runner-probe.test.ts +83 -0
- package/src/tests/integrations-http.test.ts +32 -2
- package/src/tests/internal-ai/complete-structured.test.ts +37 -1
- package/src/tests/internal-ai/credentials.test.ts +23 -0
- package/src/tests/kv-http.test.ts +32 -0
- package/src/tests/kv-tool.test.ts +25 -0
- package/src/tests/multi-runtime-registration.test.ts +12 -12
- package/src/tests/oauth-refresh-sweep.test.ts +34 -1
- package/src/tests/onboard-compose.test.ts +20 -0
- package/src/tests/onboarding.test.ts +108 -1
- package/src/tests/openai-embedding-credentials.test.ts +35 -0
- package/src/tests/opencode-adapter.test.ts +2 -1
- package/src/tests/openrouter-attribution-call-sites.test.ts +2 -2
- package/src/tests/org-website.test.ts +36 -0
- package/src/tests/pages-frame-ancestors.test.ts +162 -0
- package/src/tests/pi-codemode.test.ts +2 -1
- package/src/tests/pi-mono-adapter.test.ts +54 -0
- package/src/tests/pi-native-mcp.test.ts +13 -1
- package/src/tests/pi-tool-exposure.test.ts +8 -8
- package/src/tests/probe-tool-schema-tokens.test.ts +144 -0
- package/src/tests/prompt-template-session.test.ts +4 -3
- package/src/tests/provider-key-probes.test.ts +181 -0
- package/src/tests/rbac-e2e-helpers.test.ts +88 -1
- package/src/tests/rbac-e2e-helpers.ts +36 -11
- package/src/tests/request-composition-derive.test.ts +329 -0
- package/src/tests/script-connections.test.ts +4 -0
- package/src/tests/script-executor-conformance.test.ts +36 -1
- package/src/tests/script-executor-quickjs-pool.test.ts +50 -0
- package/src/tests/script-executor-quickjs.test.ts +140 -0
- package/src/tests/script-host-validated-args-native.test.ts +196 -0
- package/src/tests/script-per-script-executor.test.ts +415 -0
- package/src/tests/script-runs-retention.test.ts +212 -0
- package/src/tests/scripts-external-api.test.ts +45 -0
- package/src/tests/sdk-allowlist.test.ts +42 -2
- package/src/tests/self-improvement.test.ts +4 -4
- package/src/tests/session-source-backfill.test.ts +429 -0
- package/src/tests/session-source-keys.test.ts +236 -0
- package/src/tests/session-source-labels.test.ts +419 -0
- package/src/tests/sessions-detail-batch.test.ts +2 -0
- package/src/tests/sessions-move.test.ts +131 -0
- package/src/tests/sessions-tree.test.ts +299 -2
- package/src/tests/slack-inbound-files.test.ts +103 -23
- package/src/tests/status.test.ts +101 -18
- package/src/tests/store-progress-learning.test.ts +288 -0
- package/src/tests/tasks-workflow-run-filter.test.ts +146 -0
- package/src/tests/test-net.ts +31 -0
- package/src/tests/transcription.test.ts +8 -0
- package/src/tests/ui-logs-parser.test.ts +180 -0
- package/src/tests/used-by-route.test.ts +11 -0
- package/src/tests/workflow-cascade-cancel.test.ts +172 -0
- package/src/tests/workflow-engine-v2.test.ts +116 -1
- package/src/tests/workflow-http-v2.test.ts +131 -0
- package/src/tests/workflow-mcp-trigger-schema.test.ts +209 -0
- package/src/tests/workflow-run-finished-event.test.ts +263 -0
- package/src/tests/workflow-step-order.test.ts +92 -0
- package/src/tests/workflow-sub-workflow.test.ts +30 -0
- package/src/tests/workflow-swarm-script.test.ts +34 -0
- package/src/tools/get-tasks.ts +8 -0
- package/src/tools/kv/kv-read-auth.ts +4 -2
- package/src/tools/script-common.ts +1 -0
- package/src/tools/script-run.ts +7 -0
- package/src/tools/script-upsert.ts +7 -0
- package/src/tools/store-progress.ts +67 -1
- package/src/tools/task-action.ts +1 -1
- package/src/tools/workflows/get-workflow-run.ts +14 -4
- package/src/tools/workflows/trigger-workflow.ts +104 -10
- package/src/types.ts +36 -1
- package/src/utils/company-profile.ts +51 -1
- package/src/utils/credentials.ts +10 -4
- package/src/utils/internal-ai/complete-structured.ts +5 -0
- package/src/utils/internal-ai/credentials.ts +4 -3
- package/src/utils/provider-key-probes.ts +328 -0
- package/src/utils/transcription.ts +4 -1
- package/src/workflows/engine.ts +18 -16
- package/src/workflows/executors/sub-workflow.ts +8 -1
- package/src/workflows/executors/swarm-script.ts +5 -0
- package/src/workflows/foreach-driver.ts +5 -11
- package/src/workflows/node-timeout.ts +30 -0
- package/templates/ai-toolbox.manifest.json +2 -2
- package/templates/community/linear-delivery-loop/PLAYBOOK.md +323 -0
- package/templates/community/linear-delivery-loop/README.md +38 -0
- package/templates/community/linear-delivery-loop/config.example.json +31 -0
- package/templates/community/linear-delivery-loop/lead-prompt.md +34 -0
- package/templates/community/linear-delivery-loop/schedule.json +10 -0
- package/templates/community/linear-delivery-loop/scripts/linear-delivery-setup.ts +148 -0
- package/templates/community/linear-delivery-loop/scripts/linear-delivery-tick.ts +1781 -0
- package/templates/skills/researching/content.md +2 -0
- package/templates/skills/swarm-docs/config.json +14 -0
- package/templates/skills/swarm-docs/content.md +144 -0
- package/templates/skills/swarm-scripts/SKILL.md +1 -0
- package/templates/skills/swarm-scripts/content.md +1 -0
- package/dist/cli-7421jzsw.js +0 -84
- package/dist/cli-e45e4m6b.js +0 -413
- package/dist/cli-zw88zjba.js +0 -307
- package/dist/handlers-wy1dr6ja.js +0 -41
- package/dist/index-nmm79qgr.js +0 -43
- package/dist/lead-sawnyqxk.js +0 -79
- package/dist/render-v2-pjwxvnkw.js +0 -12
- package/dist/worker-v5e5rxj3.js +0 -79
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@desplega.ai/agent-swarm",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.0",
|
|
4
4
|
"description": "agent-swarm.dev — an open-source operating system for AI work: a lead agent breaks goals into tasks, routes them to specialized workers such as Claude Code or Codex, runs each worker in an isolated container, and preserves shared memory, tools, schedules, and review gates so delegated work compounds across sessions.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent-swarm.dev",
|
|
@@ -80,6 +80,8 @@
|
|
|
80
80
|
"sync:ai-toolbox-skills": "bun scripts/sync-ai-toolbox-skills.ts",
|
|
81
81
|
"check:ai-toolbox-skills": "bun scripts/sync-ai-toolbox-skills.ts --check",
|
|
82
82
|
"check:skill-sources": "bun scripts/check-skill-sources.ts",
|
|
83
|
+
"build:docs-index": "bun scripts/build-docs-index.ts",
|
|
84
|
+
"check:docs-index": "bun scripts/build-docs-index.ts --check",
|
|
83
85
|
"check:operator-skill": "bun scripts/check-operator-skill.ts",
|
|
84
86
|
"sync:telemetry-contract": "bun scripts/sync-telemetry-contract.ts",
|
|
85
87
|
"check:telemetry-contract": "bun scripts/sync-telemetry-contract.ts --check",
|
|
@@ -185,7 +187,7 @@
|
|
|
185
187
|
"@oxlint/plugins": "1.78.0",
|
|
186
188
|
"@types/bun": "latest",
|
|
187
189
|
"dependency-cruiser": "^18.0.0",
|
|
188
|
-
"happy-dom": "^
|
|
190
|
+
"happy-dom": "^21.0.4",
|
|
189
191
|
"konsistent": "1.0.0-beta.1",
|
|
190
192
|
"openapi-typescript": "^7",
|
|
191
193
|
"oxlint": "1.78.0",
|
|
@@ -210,7 +212,7 @@
|
|
|
210
212
|
"@chat-adapter/teams": "4.41.1",
|
|
211
213
|
"@chat-adapter/telegram": "4.41.1",
|
|
212
214
|
"@cursor/sdk": "1.0.40",
|
|
213
|
-
"@desplega.ai/agent-fs-just-bash": "0.18.
|
|
215
|
+
"@desplega.ai/agent-fs-just-bash": "0.18.9",
|
|
214
216
|
"@desplega.ai/business-use": "^0.4.2",
|
|
215
217
|
"@earendil-works/pi-agent-core": "^1.4.0",
|
|
216
218
|
"@earendil-works/pi-ai": "^1.4.0",
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { chatThreadOfContextKey } from "../tasks/context-key";
|
|
2
|
+
import { normalizeAssetKey, sessionGroupKey } from "./key";
|
|
3
|
+
|
|
4
|
+
export const SESSION_SOURCES = ["slack", "telegram", "discord"] as const;
|
|
5
|
+
export type SessionSource = (typeof SESSION_SOURCES)[number];
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The source asset key of a root session, or null when the task has no Slack
|
|
9
|
+
* thread and no supported Chat SDK thread. Shapes:
|
|
10
|
+
* - Slack fields or chat-sdk `slack:<channel>:<ts>`: `shared/slack/<channel>/thread:<ts>/`
|
|
11
|
+
* - chat-sdk `slack:<channel>:` (a top-level DM, empty ts): `shared/slack/<channel>/task:<id>/`
|
|
12
|
+
* - `telegram:<chatId>[:<topicId>]`: `shared/telegram/<chatId>/thread:<topicId>/`, else `.../task:<id>/`
|
|
13
|
+
* - `discord:<guild|@me>:<channelId>[:<threadId>]`: `shared/discord/<channelId>/thread:<threadId>/`, else `.../task:<id>/`
|
|
14
|
+
*/
|
|
15
|
+
export function sessionSourceKey({
|
|
16
|
+
id,
|
|
17
|
+
slackChannelId,
|
|
18
|
+
slackThreadTs,
|
|
19
|
+
contextKey,
|
|
20
|
+
}: {
|
|
21
|
+
id: string;
|
|
22
|
+
slackChannelId?: string | null;
|
|
23
|
+
slackThreadTs?: string | null;
|
|
24
|
+
contextKey?: string | null;
|
|
25
|
+
}): string | null {
|
|
26
|
+
let parts: [SessionSource, string | undefined, string | undefined];
|
|
27
|
+
if (slackChannelId && slackThreadTs) {
|
|
28
|
+
parts = ["slack", slackChannelId, slackThreadTs];
|
|
29
|
+
} else {
|
|
30
|
+
const chat = chatThreadOfContextKey(contextKey);
|
|
31
|
+
if (!chat) return null;
|
|
32
|
+
// threadId keeps its adapter prefix, so index 0 is the adapter.
|
|
33
|
+
const ids = chat.threadId.split(":");
|
|
34
|
+
if (chat.adapter === "slack" && ids.length === 3 && !ids[2]) ids.pop();
|
|
35
|
+
if (ids.some((part) => !part)) return null;
|
|
36
|
+
if (chat.adapter === "slack" && (ids.length === 2 || ids.length === 3)) {
|
|
37
|
+
parts = ["slack", ids[1], ids[2]];
|
|
38
|
+
} else if (chat.adapter === "telegram" && (ids.length === 2 || ids.length === 3)) {
|
|
39
|
+
parts = ["telegram", ids[1], ids[2]];
|
|
40
|
+
} else if (chat.adapter === "discord" && (ids.length === 3 || ids.length === 4)) {
|
|
41
|
+
parts = ["discord", ids[2], ids[3]];
|
|
42
|
+
} else {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const [source, channel, thread] = parts;
|
|
47
|
+
// A "/" inside an id would add key segments and break the group shape.
|
|
48
|
+
if (!channel || channel.includes("/") || thread?.includes("/")) return null;
|
|
49
|
+
try {
|
|
50
|
+
return normalizeAssetKey(
|
|
51
|
+
`shared/${source}/${channel}/${thread ? `thread:${thread}` : `task:${id}`}/`,
|
|
52
|
+
);
|
|
53
|
+
} catch {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function sourceOfKey(key: string): SessionSource | null {
|
|
59
|
+
return SESSION_SOURCES.find((source) => key.startsWith(`shared/${source}/`)) ?? null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export const NESTED_THREAD_MIN_ROOTS = 2;
|
|
63
|
+
|
|
64
|
+
export function isSourceThreadKey(key: string): boolean {
|
|
65
|
+
return sourceOfKey(key) !== null && /\/thread:[^/]+\/$/.test(key);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** A source thread with at least two owned roots gets its own nested group. */
|
|
69
|
+
export function sessionThreadGroupKey(key: string, threadRoots: number): string {
|
|
70
|
+
const normalized = normalizeAssetKey(key);
|
|
71
|
+
return isSourceThreadKey(normalized) && threadRoots >= NESTED_THREAD_MIN_ROOTS
|
|
72
|
+
? normalized
|
|
73
|
+
: sessionGroupKey(normalized);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const SOURCE_GROUPS_READ_ONLY = "Source groups are read-only.";
|
|
77
|
+
|
|
78
|
+
/** The source a filter matches: the source of the key prefix, else the raw task source. */
|
|
79
|
+
export function effectiveSource(key: string, source: string): string {
|
|
80
|
+
return sourceOfKey(key) ?? source;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Why a session root may not move to `destination` (a normalized key), or null.
|
|
85
|
+
* Source groups are read-only: only a move back to the root's own source key
|
|
86
|
+
* (Undo after a move out) may land under a source prefix. Null is No group.
|
|
87
|
+
*/
|
|
88
|
+
export function sourceMoveError(
|
|
89
|
+
destination: string | null,
|
|
90
|
+
root: Parameters<typeof sessionSourceKey>[0],
|
|
91
|
+
): string | null {
|
|
92
|
+
return destination !== null && sourceOfKey(destination) && destination !== sessionSourceKey(root)
|
|
93
|
+
? SOURCE_GROUPS_READ_ONLY
|
|
94
|
+
: null;
|
|
95
|
+
}
|
|
@@ -3,6 +3,7 @@ import { getSlackConfiguration, isSlackConfigured } from "../slack/config";
|
|
|
3
3
|
import { isEnvFlagEnabled } from "../utils/env-flag";
|
|
4
4
|
import { scrubSecrets } from "../utils/secret-scrubber";
|
|
5
5
|
import { getDbClient } from "./db";
|
|
6
|
+
import { emitRunFinished } from "./db/workflow-runs";
|
|
6
7
|
import { getOAuthApp, getOAuthTokens } from "./db-queries/oauth";
|
|
7
8
|
import { scrubJsonValue } from "./scrub-json";
|
|
8
9
|
|
|
@@ -446,6 +447,7 @@ export async function recordWorkflowPreflightFailure(input: {
|
|
|
446
447
|
timestamp,
|
|
447
448
|
],
|
|
448
449
|
);
|
|
450
|
+
emitRunFinished(runId, input.workflowId, "failed");
|
|
449
451
|
// Claimed in this transaction, which also sees every child failure run
|
|
450
452
|
// committed before it: concurrent failures alert exactly once a day.
|
|
451
453
|
return {
|
package/src/be/db/api-keys.ts
CHANGED
|
@@ -68,3 +68,92 @@ export async function recordKeySeatMismatch(
|
|
|
68
68
|
return { planChanged };
|
|
69
69
|
});
|
|
70
70
|
}
|
|
71
|
+
|
|
72
|
+
/** Store definitive live-test results and retire rejections for removed pool members. */
|
|
73
|
+
export async function recordKeyValidation(
|
|
74
|
+
keyType: string,
|
|
75
|
+
keys: Array<{
|
|
76
|
+
keyIndex: number;
|
|
77
|
+
keySuffix: string;
|
|
78
|
+
ok: boolean;
|
|
79
|
+
httpStatus?: number;
|
|
80
|
+
error?: string;
|
|
81
|
+
}>,
|
|
82
|
+
scope = "global",
|
|
83
|
+
scopeId: string | null = null,
|
|
84
|
+
options: { retireAbsent?: boolean } = {},
|
|
85
|
+
): Promise<void> {
|
|
86
|
+
const effectiveScopeId = scopeId ?? "";
|
|
87
|
+
const provider = deriveProviderFromKeyType(keyType);
|
|
88
|
+
await getDbClient().transaction(async (tx) => {
|
|
89
|
+
const now = new Date().toISOString();
|
|
90
|
+
for (const key of keys) {
|
|
91
|
+
// Only 401 means the key is bad. A 403 on a list endpoint can be a valid
|
|
92
|
+
// key without that scope (an OpenAI restricted key, a gateway virtual key).
|
|
93
|
+
if (!key.ok && key.httpStatus !== 401) continue;
|
|
94
|
+
await tx.run(
|
|
95
|
+
`INSERT INTO api_key_status (keyType, keySuffix, keyIndex, scope, scopeId, lastValidationError, lastValidationFailedAt, provider, updatedAt)
|
|
96
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
97
|
+
ON CONFLICT(keyType, keySuffix, scope, scopeId)
|
|
98
|
+
DO UPDATE SET lastValidationError = excluded.lastValidationError,
|
|
99
|
+
lastValidationFailedAt = excluded.lastValidationFailedAt,
|
|
100
|
+
keyIndex = excluded.keyIndex, updatedAt = excluded.updatedAt`,
|
|
101
|
+
[
|
|
102
|
+
keyType,
|
|
103
|
+
key.keySuffix,
|
|
104
|
+
key.keyIndex,
|
|
105
|
+
scope,
|
|
106
|
+
effectiveScopeId,
|
|
107
|
+
key.ok ? null : `HTTP ${key.httpStatus}`,
|
|
108
|
+
key.ok ? null : now,
|
|
109
|
+
provider,
|
|
110
|
+
now,
|
|
111
|
+
],
|
|
112
|
+
);
|
|
113
|
+
// A passing test clears the key's rejection in every scope, since the
|
|
114
|
+
// suffix read in `getAvailableKeyIndices` matches them all.
|
|
115
|
+
if (key.ok) {
|
|
116
|
+
await tx.run(
|
|
117
|
+
`UPDATE api_key_status SET lastValidationError = NULL, lastValidationFailedAt = NULL, updatedAt = ?
|
|
118
|
+
WHERE keyType = ? AND keySuffix = ? AND lastValidationFailedAt IS NOT NULL`,
|
|
119
|
+
[now, keyType, key.keySuffix],
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (options.retireAbsent === false) return;
|
|
124
|
+
const placeholders = keys.map(() => "?").join(", ");
|
|
125
|
+
await tx.run(
|
|
126
|
+
`UPDATE api_key_status SET lastValidationError = NULL, lastValidationFailedAt = NULL, updatedAt = ?
|
|
127
|
+
WHERE keyType = ? AND scope = ? AND scopeId = ?
|
|
128
|
+
${keys.length ? `AND keySuffix NOT IN (${placeholders})` : ""}
|
|
129
|
+
AND lastValidationFailedAt IS NOT NULL`,
|
|
130
|
+
[now, keyType, scope, effectiveScopeId, ...keys.map((key) => key.keySuffix)],
|
|
131
|
+
);
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Forget the status of a key that left its pool, and store the new index of
|
|
137
|
+
* each remaining key. A new key that later takes the same index or Codex slot
|
|
138
|
+
* then starts with no rejection, rate limit or auth bench.
|
|
139
|
+
*/
|
|
140
|
+
export async function retirePoolKeyStatus(
|
|
141
|
+
keyType: string,
|
|
142
|
+
removedSuffix: string,
|
|
143
|
+
remaining: Array<{ keySuffix: string; keyIndex: number }>,
|
|
144
|
+
): Promise<void> {
|
|
145
|
+
await getDbClient().transaction(async (tx) => {
|
|
146
|
+
if (!remaining.some((key) => key.keySuffix === removedSuffix)) {
|
|
147
|
+
await tx.run(
|
|
148
|
+
"DELETE FROM api_key_status WHERE keyType = ? AND keySuffix = ? AND scope = 'global' AND scopeId = ''",
|
|
149
|
+
[keyType, removedSuffix],
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
for (const key of remaining) {
|
|
153
|
+
await tx.run(
|
|
154
|
+
"UPDATE api_key_status SET keyIndex = ? WHERE keyType = ? AND keySuffix = ? AND scope = 'global' AND scopeId = ''",
|
|
155
|
+
[key.keyIndex, keyType, key.keySuffix],
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { getDbClient, upsertKv } from "../db";
|
|
2
|
+
|
|
3
|
+
const NAMESPACE = "session-labels";
|
|
4
|
+
|
|
5
|
+
export async function getSessionLabels(keys: string[]): Promise<Record<string, string>> {
|
|
6
|
+
if (keys.length === 0) return {};
|
|
7
|
+
const rows = await getDbClient().query<{ key: string; value: string }>(
|
|
8
|
+
`SELECT key, value FROM kv_entries
|
|
9
|
+
WHERE namespace = ? AND key IN (${keys.map(() => "?").join(", ")})
|
|
10
|
+
AND (expires_at IS NULL OR expires_at > ?)`,
|
|
11
|
+
[NAMESPACE, ...keys, Date.now()],
|
|
12
|
+
);
|
|
13
|
+
return Object.fromEntries(rows.map((row) => [row.key, row.value]));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function rememberSessionLabel(
|
|
17
|
+
key: string,
|
|
18
|
+
label: string,
|
|
19
|
+
ttlMs?: number,
|
|
20
|
+
): Promise<void> {
|
|
21
|
+
await upsertKv({
|
|
22
|
+
namespace: NAMESPACE,
|
|
23
|
+
key,
|
|
24
|
+
value: label,
|
|
25
|
+
valueType: "string",
|
|
26
|
+
expiresAt: ttlMs === undefined ? null : Date.now() + ttlMs,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
@@ -85,15 +85,14 @@ export async function moveTaskChainKeyInTx(
|
|
|
85
85
|
[task.id, task.id],
|
|
86
86
|
);
|
|
87
87
|
// The namespace is metadata, and lastUpdatedAt tracks task activity, as in updateTaskTitle.
|
|
88
|
-
await client.run(
|
|
89
|
-
key,
|
|
90
|
-
changedBy ?? null,
|
|
91
|
-
|
|
92
|
-
]);
|
|
88
|
+
await client.run(
|
|
89
|
+
'UPDATE agent_tasks SET "key" = ?, updated_by = COALESCE(?, updated_by) WHERE id = ?',
|
|
90
|
+
[key, changedBy ?? null, task.id],
|
|
91
|
+
);
|
|
93
92
|
for (const mapping of mappedFiles) {
|
|
94
93
|
if (mapping.key === key) continue;
|
|
95
94
|
await client.run(
|
|
96
|
-
'UPDATE asset_key_mappings SET "key" = ?, updated_at = ?, updated_by =
|
|
95
|
+
'UPDATE asset_key_mappings SET "key" = ?, updated_at = ?, updated_by = COALESCE(?, updated_by) WHERE id = ?',
|
|
97
96
|
[key, now, changedBy ?? null, mapping.id],
|
|
98
97
|
);
|
|
99
98
|
await insertAssetKeyHistory({
|
|
@@ -4,6 +4,14 @@ import {
|
|
|
4
4
|
SESSION_LEAF_KINDS,
|
|
5
5
|
sessionGroupKey,
|
|
6
6
|
} from "../../assets/key";
|
|
7
|
+
import {
|
|
8
|
+
effectiveSource,
|
|
9
|
+
isSourceThreadKey,
|
|
10
|
+
NESTED_THREAD_MIN_ROOTS,
|
|
11
|
+
SESSION_SOURCES,
|
|
12
|
+
sessionThreadGroupKey,
|
|
13
|
+
sourceOfKey,
|
|
14
|
+
} from "../../assets/source-key";
|
|
7
15
|
import { isTerminalTaskStatus, TERMINAL_TASK_STATUSES } from "../../types";
|
|
8
16
|
import { getDbClient, literalPrefixPattern } from "../db";
|
|
9
17
|
import {
|
|
@@ -14,13 +22,17 @@ import {
|
|
|
14
22
|
|
|
15
23
|
export type SessionReadState = "unread" | "read" | "later" | "done";
|
|
16
24
|
export interface SessionFilters {
|
|
17
|
-
/** Restrict roots to these task sources. */
|
|
25
|
+
/** Restrict roots to these raw task sources (`agent_tasks.source`). */
|
|
18
26
|
source?: string[];
|
|
19
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* Leave out roots of these effective sources (the older sessions v2 source
|
|
29
|
+
* filter). The effective source of a root under a source key prefix such as
|
|
30
|
+
* `shared/slack/` is that source, else its raw task source.
|
|
31
|
+
*/
|
|
20
32
|
hideSource?: string[];
|
|
21
33
|
/**
|
|
22
|
-
* Keep only roots of these
|
|
23
|
-
* Unlike `source`, an empty list matches no root.
|
|
34
|
+
* Keep only roots of these effective sources (the sessions v2 source filter,
|
|
35
|
+
* see `hideSource`). Unlike `source`, an empty list matches no root.
|
|
24
36
|
*/
|
|
25
37
|
showSource?: string[];
|
|
26
38
|
/** Match root task text or title with a case-insensitive substring. */
|
|
@@ -74,15 +86,47 @@ function sessionReadStateSql(now = new Date().toISOString()): SqlFragment {
|
|
|
74
86
|
};
|
|
75
87
|
}
|
|
76
88
|
|
|
89
|
+
function sourceKeyRanges() {
|
|
90
|
+
return SESSION_SOURCES.map((source) => ({ source, ...keyPrefixRange(`shared/${source}/`) }));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Count every owned root at the exact source thread key, independent of viewer filters. */
|
|
94
|
+
export function threadRootsSql(): SqlFragment {
|
|
95
|
+
const key = 'r."key"';
|
|
96
|
+
// Trim the final slash, then all non-slash characters to locate the last segment.
|
|
97
|
+
// For example, shared/slack/c1/thread:1.2/ gives thread:1.2/.
|
|
98
|
+
const leaf = `substr(${key}, length(rtrim(rtrim(${key}, '/'), replace(${key}, '/', ''))) + 1)`;
|
|
99
|
+
const ranges = sourceKeyRanges();
|
|
100
|
+
return {
|
|
101
|
+
sql: `(CASE WHEN (${ranges.map(() => `(${key} >= ? AND ${key} < ?)`).join(" OR ")}) AND ${leaf} GLOB 'thread:?*/'
|
|
102
|
+
THEN (SELECT COUNT(*) FROM agent_tasks session_thread_root WHERE session_thread_root."key" = ${key}
|
|
103
|
+
AND session_thread_root.parentTaskId IS NULL AND session_thread_root.requestedByUserId IS NOT NULL)
|
|
104
|
+
ELSE 0 END)`,
|
|
105
|
+
params: ranges.flatMap(({ lo, hi }) => [lo, hi]),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** A task's group with the list row's rule (`groupKey` on `GET /api/sessions`). */
|
|
110
|
+
export async function getSessionGroupKey(task: { id: string; key: string }): Promise<string> {
|
|
111
|
+
const threadRoots = threadRootsSql();
|
|
112
|
+
const row = await getDbClient().get<{ threadRoots: number }>(
|
|
113
|
+
`SELECT ${threadRoots.sql} AS threadRoots FROM agent_tasks r WHERE r.id = ?`,
|
|
114
|
+
[...threadRoots.params, task.id],
|
|
115
|
+
);
|
|
116
|
+
return sessionThreadGroupKey(task.key, row?.threadRoots ?? 0);
|
|
117
|
+
}
|
|
118
|
+
|
|
77
119
|
function groupCondition(group: string): SqlFragment {
|
|
78
120
|
const { lo, hi } = keyPrefixRange(group);
|
|
79
121
|
// The remaining key must be one resource leaf, with no nested path.
|
|
80
122
|
// SQLite substr/length count code points, so count them here too (not UTF-16 units).
|
|
81
123
|
const start = [...lo].length + 1;
|
|
124
|
+
const threadRoots = threadRootsSql();
|
|
82
125
|
return {
|
|
83
126
|
sql: `(r."key" >= ? AND r."key" < ? AND (r."key" = ? OR (
|
|
84
127
|
instr(substr(r."key", ?, length(r."key") - ?), '/') = 0 AND
|
|
85
|
-
(${SESSION_LEAF_KINDS.map(() => 'substr(r."key", ?) GLOB ?').join(" OR ")})
|
|
128
|
+
(${SESSION_LEAF_KINDS.map(() => 'substr(r."key", ?) GLOB ?').join(" OR ")}) AND
|
|
129
|
+
${threadRoots.sql} < ?
|
|
86
130
|
)))`,
|
|
87
131
|
params: [
|
|
88
132
|
lo,
|
|
@@ -91,10 +135,21 @@ function groupCondition(group: string): SqlFragment {
|
|
|
91
135
|
start,
|
|
92
136
|
start,
|
|
93
137
|
...SESSION_LEAF_KINDS.flatMap((kind) => [start, `${kind}:?*/`]),
|
|
138
|
+
...threadRoots.params,
|
|
139
|
+
NESTED_THREAD_MIN_ROOTS,
|
|
94
140
|
],
|
|
95
141
|
};
|
|
96
142
|
}
|
|
97
143
|
|
|
144
|
+
/** `effectiveSource` of the root `r` in SQL, with key ranges so the key index still serves it. */
|
|
145
|
+
function effectiveSourceSql(): SqlFragment {
|
|
146
|
+
const ranges = sourceKeyRanges();
|
|
147
|
+
return {
|
|
148
|
+
sql: `(CASE ${ranges.map(() => `WHEN r."key" >= ? AND r."key" < ? THEN ?`).join(" ")} ELSE r.source END)`,
|
|
149
|
+
params: ranges.flatMap(({ source, lo, hi }) => [lo, hi, source]),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
98
153
|
export function sessionRootFilter(opts: SessionFilters = {}): SqlFragment {
|
|
99
154
|
const conditions = ["r.parentTaskId IS NULL"];
|
|
100
155
|
const params: SqlFragment["params"] = [];
|
|
@@ -107,13 +162,22 @@ export function sessionRootFilter(opts: SessionFilters = {}): SqlFragment {
|
|
|
107
162
|
if (sources.length)
|
|
108
163
|
add({ sql: `r.source IN (${sources.map(() => "?").join(",")})`, params: sources });
|
|
109
164
|
const hidden = opts.hideSource?.filter(Boolean) ?? [];
|
|
110
|
-
if (hidden.length)
|
|
111
|
-
|
|
165
|
+
if (hidden.length) {
|
|
166
|
+
const source = effectiveSourceSql();
|
|
167
|
+
add({
|
|
168
|
+
sql: `${source.sql} NOT IN (${hidden.map(() => "?").join(",")})`,
|
|
169
|
+
params: [...source.params, ...hidden],
|
|
170
|
+
});
|
|
171
|
+
}
|
|
112
172
|
if (opts.showSource) {
|
|
113
173
|
const shown = opts.showSource.filter(Boolean);
|
|
174
|
+
const source = effectiveSourceSql();
|
|
114
175
|
add(
|
|
115
176
|
shown.length
|
|
116
|
-
? {
|
|
177
|
+
? {
|
|
178
|
+
sql: `${source.sql} IN (${shown.map(() => "?").join(",")})`,
|
|
179
|
+
params: [...source.params, ...shown],
|
|
180
|
+
}
|
|
117
181
|
: { sql: "0", params: [] },
|
|
118
182
|
);
|
|
119
183
|
}
|
|
@@ -388,8 +452,45 @@ export interface SessionTree {
|
|
|
388
452
|
later: SessionSectionCounts;
|
|
389
453
|
done: SessionSectionCounts;
|
|
390
454
|
noGroup: SessionSectionCounts;
|
|
391
|
-
/** Each root source in this view with its session count, hidden sources included, most first. */
|
|
455
|
+
/** Each effective root source in this view with its session count, hidden sources included, most first. */
|
|
392
456
|
sources: { source: string; total: number }[];
|
|
457
|
+
/** Every source group key in this view, hidden sources included, so a pane of a hidden source still gets its label. */
|
|
458
|
+
sourceGroups: string[];
|
|
459
|
+
/** Slack channel ids backed by visible roots with matching Slack fields, including hidden sources. */
|
|
460
|
+
slackChannels: string[];
|
|
461
|
+
/**
|
|
462
|
+
* The oldest visible root of each nested source thread, including hidden
|
|
463
|
+
* sources: its title, else a preview of its prompt without the thread context.
|
|
464
|
+
*/
|
|
465
|
+
threadLabels: Record<string, string>;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* The first `<thread_context>` block of a prompt, as the Slack and Chat SDK
|
|
470
|
+
* templates write it (each tag alone on its line). It holds the earlier thread
|
|
471
|
+
* messages, so a label cut from the start of the prompt never reaches the ask.
|
|
472
|
+
*/
|
|
473
|
+
const THREAD_OPEN = "<thread_context>";
|
|
474
|
+
const THREAD_CLOSE = "</thread_context>";
|
|
475
|
+
const THREAD_OPEN_AT = `instr(r.task, '${THREAD_OPEN}' || char(10))`;
|
|
476
|
+
const THREAD_CLOSE_AT = `instr(r.task, char(10) || '${THREAD_CLOSE}')`;
|
|
477
|
+
/**
|
|
478
|
+
* The task list preview length. The UI reads this length plus "…" as a list
|
|
479
|
+
* preview and repairs a Slack token or a block that the cut splits
|
|
480
|
+
* (`isListPreview` in `apps/ui/src/lib/prompt-display.ts`).
|
|
481
|
+
*/
|
|
482
|
+
const THREAD_LABEL_LENGTH = 300;
|
|
483
|
+
/** Extra characters for the blank lines that `threadPromptPreview` trims, so the preview stays full. */
|
|
484
|
+
const THREAD_LABEL_TRIM_MARGIN = 100;
|
|
485
|
+
/** The prompt of `r` without its first thread context block, cut for `threadPromptPreview`. */
|
|
486
|
+
const THREAD_PROMPT_SQL = `substr(CASE
|
|
487
|
+
WHEN ${THREAD_OPEN_AT} > 0 AND ${THREAD_CLOSE_AT} > ${THREAD_OPEN_AT}
|
|
488
|
+
THEN substr(r.task, 1, ${THREAD_OPEN_AT} - 1) || substr(r.task, ${THREAD_CLOSE_AT} + ${THREAD_CLOSE.length + 1})
|
|
489
|
+
ELSE r.task END, 1, ${THREAD_LABEL_LENGTH + THREAD_LABEL_TRIM_MARGIN})`;
|
|
490
|
+
|
|
491
|
+
function threadPromptPreview(prompt: string): string {
|
|
492
|
+
const text = prompt.trim();
|
|
493
|
+
return text.length > THREAD_LABEL_LENGTH ? `${text.slice(0, THREAD_LABEL_LENGTH)}…` : text;
|
|
393
494
|
}
|
|
394
495
|
|
|
395
496
|
export async function getSessionTree(
|
|
@@ -405,17 +506,22 @@ export async function getSessionTree(
|
|
|
405
506
|
tree: true,
|
|
406
507
|
mine: opts.mine,
|
|
407
508
|
});
|
|
509
|
+
const threadRoots = threadRootsSql();
|
|
408
510
|
const rows = await getDbClient().query<{
|
|
511
|
+
id: string;
|
|
409
512
|
key: string;
|
|
410
513
|
source: string;
|
|
514
|
+
slackChannelId: string | null;
|
|
411
515
|
lastActivityAt: string;
|
|
412
516
|
latestStatus: string;
|
|
413
517
|
readState: SessionReadState | null;
|
|
414
518
|
needsYou: number;
|
|
519
|
+
threadRoots: number;
|
|
415
520
|
}>(
|
|
416
|
-
`SELECT r."key", r.source, a.lastActivityAt, a.latestStatus, a.readState, a.needsYou
|
|
521
|
+
`SELECT r.id, r."key", r.source, r.slackChannelId, a.lastActivityAt, a.latestStatus, a.readState, a.needsYou,
|
|
522
|
+
${threadRoots.sql} AS threadRoots
|
|
417
523
|
FROM (${query.sql}) a CROSS JOIN agent_tasks r ON r.id = a.root_id`,
|
|
418
|
-
query.params,
|
|
524
|
+
[...threadRoots.params, ...query.params],
|
|
419
525
|
);
|
|
420
526
|
const counts = (): SessionSectionCounts => ({ total: 0, unread: 0, running: 0, needsYou: 0 });
|
|
421
527
|
const result: SessionTree = {
|
|
@@ -425,10 +531,16 @@ export async function getSessionTree(
|
|
|
425
531
|
done: counts(),
|
|
426
532
|
noGroup: counts(),
|
|
427
533
|
sources: [],
|
|
534
|
+
sourceGroups: [],
|
|
535
|
+
slackChannels: [],
|
|
536
|
+
threadLabels: {},
|
|
428
537
|
};
|
|
429
538
|
const hidden = new Set(opts.hideSource);
|
|
430
539
|
const shown = opts.showSource ? new Set(opts.showSource) : null;
|
|
431
540
|
const sources = new Map<string, number>();
|
|
541
|
+
const sourceGroups = new Set<string>();
|
|
542
|
+
const slackChannels = new Set<string>();
|
|
543
|
+
const threadRootIds: string[] = [];
|
|
432
544
|
const groups = new Map<string, SessionTreeGroup>();
|
|
433
545
|
const add = (section: SessionSectionCounts, row: (typeof rows)[number]) => {
|
|
434
546
|
section.total++;
|
|
@@ -437,9 +549,20 @@ export async function getSessionTree(
|
|
|
437
549
|
if (row.needsYou) section.needsYou++;
|
|
438
550
|
};
|
|
439
551
|
for (const row of rows) {
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
const key =
|
|
552
|
+
const source = effectiveSource(row.key, row.source);
|
|
553
|
+
sources.set(source, (sources.get(source) ?? 0) + 1);
|
|
554
|
+
const key = sessionThreadGroupKey(row.key, row.threadRoots);
|
|
555
|
+
const sourceGroupKey = sessionGroupKey(row.key);
|
|
556
|
+
const channelId = /^shared\/slack\/([^/]+)\/$/.exec(sourceGroupKey)?.[1];
|
|
557
|
+
if (channelId && row.slackChannelId?.toLowerCase() === channelId) {
|
|
558
|
+
slackChannels.add(channelId);
|
|
559
|
+
}
|
|
560
|
+
if (row.threadRoots >= NESTED_THREAD_MIN_ROOTS && isSourceThreadKey(row.key)) {
|
|
561
|
+
threadRootIds.push(row.id);
|
|
562
|
+
sourceGroups.add(sourceGroupKey);
|
|
563
|
+
}
|
|
564
|
+
if (sourceOfKey(key)) sourceGroups.add(key);
|
|
565
|
+
if (hidden.has(source) || (shown && !shown.has(source))) continue;
|
|
443
566
|
const noGroup =
|
|
444
567
|
key === "shared/" || (actor.kind === "user" && key === `personal/${actor.userId}/`);
|
|
445
568
|
let group: SessionTreeGroup | undefined;
|
|
@@ -462,6 +585,22 @@ export async function getSessionTree(
|
|
|
462
585
|
result.sources = [...sources]
|
|
463
586
|
.map(([source, total]) => ({ source, total }))
|
|
464
587
|
.sort((a, b) => b.total - a.total || a.source.localeCompare(b.source));
|
|
588
|
+
result.sourceGroups = [...sourceGroups].sort();
|
|
589
|
+
result.slackChannels = [...slackChannels].sort();
|
|
590
|
+
if (threadRootIds.length) {
|
|
591
|
+
// A missing or empty title falls back to a preview of the prompt without its thread context.
|
|
592
|
+
const labels = await getDbClient().query<{ key: string; title: string | null; prompt: string }>(
|
|
593
|
+
`SELECT "key", title, prompt FROM (
|
|
594
|
+
SELECT r."key", NULLIF(trim(r.title), '') AS title, ${THREAD_PROMPT_SQL} AS prompt,
|
|
595
|
+
ROW_NUMBER() OVER (PARTITION BY r."key" ORDER BY r.createdAt, r.rowid) AS ordinal
|
|
596
|
+
FROM agent_tasks r WHERE r.id IN (${threadRootIds.map(() => "?").join(",")})
|
|
597
|
+
) WHERE ordinal = 1`,
|
|
598
|
+
threadRootIds,
|
|
599
|
+
);
|
|
600
|
+
result.threadLabels = Object.fromEntries(
|
|
601
|
+
labels.map((row) => [row.key, row.title ?? threadPromptPreview(row.prompt)]),
|
|
602
|
+
);
|
|
603
|
+
}
|
|
465
604
|
return result;
|
|
466
605
|
}
|
|
467
606
|
|
package/src/be/db/tasks/read.ts
CHANGED
|
@@ -803,6 +803,7 @@ export interface TaskFilters {
|
|
|
803
803
|
taskType?: string;
|
|
804
804
|
tags?: string[];
|
|
805
805
|
scheduleId?: string;
|
|
806
|
+
workflowRunId?: string;
|
|
806
807
|
/** Exact canonical asset namespace. */
|
|
807
808
|
key?: string;
|
|
808
809
|
/** Canonical namespace subtree prefix. */
|
|
@@ -937,6 +938,11 @@ export async function getAllTasks(
|
|
|
937
938
|
params.push(filters.scheduleId);
|
|
938
939
|
}
|
|
939
940
|
|
|
941
|
+
if (filters?.workflowRunId) {
|
|
942
|
+
conditions.push("workflowRunId = ?");
|
|
943
|
+
params.push(filters.workflowRunId);
|
|
944
|
+
}
|
|
945
|
+
|
|
940
946
|
if (filters?.key) {
|
|
941
947
|
conditions.push('"key" = ?');
|
|
942
948
|
params.push(normalizeAssetKey(filters.key));
|
|
@@ -1103,6 +1109,11 @@ export async function getTasksCount(
|
|
|
1103
1109
|
params.push(filters.scheduleId);
|
|
1104
1110
|
}
|
|
1105
1111
|
|
|
1112
|
+
if (filters?.workflowRunId) {
|
|
1113
|
+
conditions.push("workflowRunId = ?");
|
|
1114
|
+
params.push(filters.workflowRunId);
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1106
1117
|
if (filters?.key) {
|
|
1107
1118
|
conditions.push('"key" = ?');
|
|
1108
1119
|
params.push(normalizeAssetKey(filters.key));
|
package/src/be/db/tasks/write.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import pkg from "../../../../package.json";
|
|
2
2
|
import { defaultAssetKey } from "../../../assets/key";
|
|
3
|
+
import { sessionSourceKey } from "../../../assets/source-key";
|
|
3
4
|
import type { TaskTelemetryEvent, TaskTelemetryInput } from "../../../telemetry-trigger";
|
|
4
5
|
import type {
|
|
5
6
|
Agent,
|
|
@@ -89,7 +90,11 @@ export async function createTask(
|
|
|
89
90
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, strftime('%Y-%m-%dT%H:%M:%fZ', 'now'), strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) RETURNING *`,
|
|
90
91
|
[
|
|
91
92
|
id,
|
|
92
|
-
|
|
93
|
+
sessionSourceKey({
|
|
94
|
+
id,
|
|
95
|
+
slackChannelId: options?.slackChannelId,
|
|
96
|
+
slackThreadTs: options?.slackThreadTs,
|
|
97
|
+
}) ?? defaultAssetKey("task", id),
|
|
93
98
|
agentId,
|
|
94
99
|
scrubSecrets(task),
|
|
95
100
|
"pending",
|