@desplega.ai/agent-swarm 2.7.4 → 2.7.6
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 +1 -1
- package/dist/acp-adapter-bp91d37f.js +15 -0
- package/dist/{actions-gfrr9vb7.js → actions-yj0eeaqh.js} +19 -18
- package/dist/{amp-adapter-0s58gm46.js → amp-adapter-4pg9h34q.js} +3 -3
- package/dist/{app-stvq692g.js → app-909e97cr.js} +3 -3
- package/dist/{assistant-5s37fr9n.js → assistant-qdyb5fyy.js} +32 -23
- package/dist/{boot-reembed-yzy1841n.js → boot-reembed-fk0s9z18.js} +1 -1
- package/dist/{boot-reembed-cv82gz7y.js → boot-reembed-gs1rzrnh.js} +1 -1
- package/dist/{boot-scrub-sweep-8k4q95ap.js → boot-scrub-sweep-4qfd4kfx.js} +4 -4
- package/dist/{boot-seeds-ftt9htah.js → boot-seeds-yr00k13d.js} +4 -4
- package/dist/{boxd-515v1t8z.js → boxd-vdmfssd9.js} +5 -5
- package/dist/{claude-adapter-t00wrzeh.js → claude-adapter-qta1f1xn.js} +7 -7
- package/dist/{claude-managed-adapter-g6vmjccb.js → claude-managed-adapter-dvs2xw5n.js} +2 -2
- package/dist/{claude-managed-setup-afpa82f7.js → claude-managed-setup-sc6j4dm0.js} +3 -3
- package/dist/{claude-sdk-session-vj0c2azx.js → claude-sdk-session-zgrz6yvc.js} +9175 -9076
- package/dist/{cli-gjr2y2xk.js → cli-0agcv1v5.js} +4 -1
- package/dist/{cli-edmm9d9y.js → cli-0at8w34w.js} +7 -7
- package/dist/{cli-a81qevyg.js → cli-0qatpy8q.js} +20 -36
- package/dist/{cli-bcz074zx.js → cli-0yv5z2pg.js} +1 -1
- package/dist/{cli-a4btqqft.js → cli-1b7btd38.js} +1 -1
- package/dist/{cli-a2jw6ee5.js → cli-1p89xny0.js} +1 -1
- package/dist/{cli-v726afdm.js → cli-2pnswgh8.js} +1 -1
- package/dist/{cli-jkt52mgt.js → cli-386tyxxg.js} +2 -2
- package/dist/{cli-az0bpf8p.js → cli-3ypk9vk1.js} +3 -3
- package/dist/{cli-wk9md5px.js → cli-436532ck.js} +4 -4
- package/dist/{cli-7n5sbw0z.js → cli-4necwtsv.js} +5 -5
- package/dist/cli-54t00gt8.js +151 -0
- package/dist/{cli-655xm9ms.js → cli-583frbyr.js} +1 -1
- package/dist/{cli-4r12t6w6.js → cli-5c4hjzne.js} +101 -4
- package/dist/{cli-hf6zt5m6.js → cli-69szn7w7.js} +17 -1
- package/dist/{cli-rtk9g7av.js → cli-73jzwksr.js} +6 -2
- package/dist/{cli-jg0nfda6.js → cli-75gkpjvw.js} +52 -21
- package/dist/{cli-wcmnan2g.js → cli-89knwags.js} +5 -5
- package/dist/{cli-pwyevs29.js → cli-8bc1w0tw.js} +22 -21
- package/dist/{cli-t282jgab.js → cli-8snvgs5r.js} +41 -16
- package/dist/{cli-vqgnyh76.js → cli-98rw5dt5.js} +15 -3
- package/dist/{cli-9cctx1tn.js → cli-a22h291q.js} +1 -1
- package/dist/{cli-ycm6180s.js → cli-a7v854rf.js} +5 -3
- package/dist/{cli-98pke4vk.js → cli-ae7mrdf9.js} +2 -2
- package/dist/{cli-je8yeyc5.js → cli-bjb3z51z.js} +5 -5
- package/dist/{cli-mc97xy2s.js → cli-c60vetmm.js} +16 -4
- package/dist/{cli-j4kw3rsx.js → cli-cm5yqn35.js} +18 -0
- package/dist/{cli-wndv9p7g.js → cli-decfb6v8.js} +6 -6
- package/dist/{cli-h55y2bjc.js → cli-dhar9qay.js} +2 -2
- package/dist/{cli-8yy525ad.js → cli-drr5v6av.js} +2 -2
- package/dist/{cli-5gr1g711.js → cli-dxae2yg7.js} +2 -2
- package/dist/{cli-pe09p6dv.js → cli-e3r2neck.js} +8 -7
- package/dist/{cli-13jvmew3.js → cli-ekwks2vs.js} +7 -7
- package/dist/{cli-jg4p5t36.js → cli-es6va8jk.js} +121 -51
- package/dist/{cli-j4kt1mpb.js → cli-faxe6thg.js} +3 -3
- package/dist/{cli-tzrpv5sc.js → cli-ftyxyspd.js} +1 -1
- package/dist/cli-fw2w0kva.js +47 -0
- package/dist/{cli-2dfbv8qr.js → cli-fy6x31ps.js} +1 -0
- package/dist/{cli-aq58fk5m.js → cli-g83s9ewh.js} +7 -1
- package/dist/cli-gdhpcbhf.js +55 -0
- package/dist/{cli-82jg7fp9.js → cli-he5nxaa8.js} +1 -1
- package/dist/{cli-8veytvbh.js → cli-he9wchme.js} +1 -1
- package/dist/{cli-k76tgq03.js → cli-hgaazk69.js} +2 -2
- package/dist/{cli-d2pzqej1.js → cli-hr2e2fft.js} +741 -7
- package/dist/{cli-dae4sbgy.js → cli-jm2vfpfd.js} +2 -2
- package/dist/{cli-1s1dqn1j.js → cli-k2gzwsj2.js} +297 -140
- package/dist/{cli-b1afgtj3.js → cli-kqdg1bsb.js} +3 -3
- package/dist/cli-m2z8jagn.js +12078 -0
- package/dist/{cli-mvckssyq.js → cli-m48g6p95.js} +5 -5
- package/dist/{cli-bwty35gx.js → cli-mthwvjre.js} +1 -1
- package/dist/{cli-41z8xwj0.js → cli-nhrvpaj7.js} +34 -8
- package/dist/{cli-ahd90bwt.js → cli-nwj0ee2w.js} +6 -6
- package/dist/{cli-kvtc7aez.js → cli-p5gp8pcx.js} +44 -14
- package/dist/{cli-avgcz4ra.js → cli-pd7vagab.js} +436 -171
- package/dist/{cli-zn15c46z.js → cli-q0k1w5zd.js} +1 -1
- package/dist/{cli-bq2pcphn.js → cli-rmmmrjkd.js} +1 -1
- package/dist/{cli-2qpvmmkw.js → cli-s0qnd4wv.js} +1 -1
- package/dist/{cli-kg8hchyg.js → cli-sa02jsqt.js} +1 -1
- package/dist/{cli-1yekbyqm.js → cli-shpbx5ee.js} +283 -82
- package/dist/{cli-9y354g3j.js → cli-srye3vdt.js} +1 -1
- package/dist/{cli-byqfbnjw.js → cli-svj5090z.js} +9 -9
- package/dist/{cli-gcbdjt47.js → cli-vfym7wmz.js} +6 -6
- package/dist/{cli-v1yw3wha.js → cli-vvpx3gsx.js} +4 -4
- package/dist/{cli-a1fhj4n9.js → cli-w6dt9ged.js} +2 -2
- package/dist/{cli-1gm8jcyr.js → cli-waqbxg08.js} +5232 -68
- package/dist/{cli-5hjced2v.js → cli-wp9sp1qb.js} +6 -6
- package/dist/{cli-eny54typ.js → cli-wx3rhnm0.js} +1 -1
- package/dist/{cli-s0exq74r.js → cli-xbbndvjy.js} +3 -3
- package/dist/{cli-f255yaqn.js → cli-xds5f77d.js} +3 -3
- package/dist/{cli-0sbkss7h.js → cli-xnqgpshy.js} +2 -2
- package/dist/{cli-0fgr1w9b.js → cli-xnrwazav.js} +20 -3
- package/dist/{cli-gf5hbmdr.js → cli-yjdgea32.js} +3 -3
- package/dist/{cli-cq1pf336.js → cli-yn4kxs06.js} +124 -11
- package/dist/{cli-224epwwf.js → cli-yx4kfngt.js} +3 -3
- package/dist/{cli-eqx6k993.js → cli-yy3rhsdw.js} +47 -13
- package/dist/{cli-36g23z21.js → cli-zbbwez0r.js} +4 -4
- package/dist/{cli-ktmvb20s.js → cli-zm72anhg.js} +12 -12
- package/dist/cli.js +26 -25
- package/dist/{codex-adapter-g0gctw29.js → codex-adapter-ayd1fb6h.js} +5 -5
- package/dist/{codex-hook-vrzz2xhr.js → codex-hook-9efm2x1m.js} +1 -1
- package/dist/{codex-login-0gg46ygn.js → codex-login-y0pmff1c.js} +1 -1
- package/dist/{codex-session-runner-dr2cxe1c.js → codex-session-runner-jesa32xc.js} +6 -6
- package/dist/{commands-1p07cvff.js → commands-hanewgf2.js} +9 -3
- package/dist/{controller-z0s7sv6r.js → controller-dt1n3c1q.js} +9 -4
- package/dist/{controllers-connect-x9kvvhyk.js → controllers-connect-xx8x64n5.js} +22 -21
- package/dist/{cursor-adapter-m5b98pvk.js → cursor-adapter-9s4dwwdw.js} +2 -2
- package/dist/{db-capk2raj.js → db-3vttcbv4.js} +3 -3
- package/dist/{db-rsh04kh3.js → db-9dv0g1ab.js} +12 -4
- package/dist/{devin-adapter-yxm87fe9.js → devin-adapter-r9phbnc9.js} +1 -1
- package/dist/{devtools-82fzqra8.js → devtools-qxfgbsqz.js} +1 -1
- package/dist/{dispatcher-304q6fwa.js → dispatcher-tx9hd1sv.js} +18 -17
- package/dist/{docker-7rxt0rry.js → docker-c1a3gqmf.js} +91 -24
- package/dist/{dsh-adapter-btewem0y.js → dsh-adapter-007y4ejs.js} +2 -2
- package/dist/{e2b-renc0k8q.js → e2b-a19d28rz.js} +4 -4
- package/dist/{e2b-7fc9b9bd.js → e2b-we6hs6q5.js} +6 -6
- package/dist/{easter-egg-3d-fm1p3n4d.js → easter-egg-3d-rgbrqb8s.js} +1 -1
- package/dist/{embedding-space-s68v49ct.js → embedding-space-ny1e2940.js} +6 -6
- package/dist/{embeddings-4ms3604w.js → embeddings-2t386t13.js} +1 -1
- package/dist/{event-bus-dfjx2qdh.js → event-bus-g0ka3pbw.js} +1 -1
- package/dist/{execute-mcdgccjs.js → execute-pksh0ceh.js} +1 -1
- package/dist/{external-channel-9dxwfztn.js → external-channel-phgmhmd7.js} +2 -2
- package/dist/fx-adapter-fy8rr921.js +18 -0
- package/dist/{google-generative-ai-yvmdxyxa.js → google-generative-ai-8y3m3k4b.js} +1 -1
- package/dist/{google-vertex-jrtdh9gs.js → google-vertex-w3v98n7b.js} +1 -1
- package/dist/{grok-adapter-05ecv4h0.js → grok-adapter-q0abvpaj.js} +8 -8
- package/dist/handlers-by3sfxyx.js +32 -0
- package/dist/{heartbeat-3n94heh3.js → heartbeat-kadyek8p.js} +21 -20
- package/dist/{hook-aadrn69x.js → hook-5zs3msf6.js} +6 -6
- package/dist/{hook-zxwje86c.js → hook-9k551xnj.js} +4 -4
- package/dist/{http-xzxk9mst.js → http-mhz9vw1f.js} +272 -103
- package/dist/index-4f2pvxae.js +37 -0
- package/dist/index-98gz7tmp.js +30 -0
- package/dist/{index-5rk6rfzr.js → index-fvzke5kx.js} +1 -1
- package/dist/index-k08z2cak.js +33 -0
- package/dist/{index-yqhz88yy.js → index-pxn30mse.js} +8 -8
- package/dist/{index-d34f6r87.js → index-q361cg2f.js} +1 -1
- package/dist/{index-9j4zwcht.js → index-rvr00sg7.js} +168 -675
- package/dist/{index-qeb82z37.js → index-vdygbq10.js} +1 -1
- package/dist/{keepalive-w4aakbyk.js → keepalive-w56f32cg.js} +4 -4
- package/dist/lead-enq35nqp.js +70 -0
- package/dist/{maintenance-hbptpk6p.js → maintenance-7mdgxcea.js} +1 -1
- package/dist/{oauth-refresh-sweep-217semrv.js → oauth-refresh-sweep-29txy9vk.js} +3 -3
- package/dist/{onboard-vz2s5df3.js → onboard-kpsrkarj.js} +5 -5
- package/dist/{opencode-adapter-gtfd7d7v.js → opencode-adapter-j25vm3rr.js} +5 -5
- package/dist/{otel-impl-aefave37.js → otel-impl-6kkct7a8.js} +2 -2
- package/dist/{pi-mono-adapter-f8h5yseh.js → pi-mono-adapter-exhkzhn2.js} +117 -23
- package/dist/{pricing-refresh-m1yhh9ya.js → pricing-refresh-n2x7jh6k.js} +3 -3
- package/dist/{rbac-roles-rrw8twdz.js → rbac-roles-81ypx6es.js} +1 -1
- package/dist/{rbac-roles-svj72cdf.js → rbac-roles-b5htkjby.js} +2 -2
- package/dist/{render-v2-amwbnfxh.js → render-v2-qn6jmpk4.js} +3 -3
- package/dist/{resume-9cd6wf6z.js → resume-wd30wx23.js} +7 -7
- package/dist/{runtime-fp4vrx7m.js → runtime-4hsg7h60.js} +1 -1
- package/dist/{session-access-21t6yjyg.js → session-access-5qvfevhk.js} +4 -4
- package/dist/{session-source-backfill-pxbzcsc6.js → session-source-backfill-n1czavbr.js} +3 -3
- package/dist/{setup-4592tqz4.js → setup-dnb56qy5.js} +3 -3
- package/dist/{virtual-modules-gzfnpmnz.js → virtual-modules-76xxv9fs.js} +2 -2
- package/dist/{work-objects-t9tcf174.js → work-objects-4n21tfkm.js} +21 -20
- package/dist/{worker-follow-up-q32jh4yk.js → worker-follow-up-77hvzxww.js} +18 -17
- package/dist/worker-gg1576j3.js +70 -0
- package/dist/{x-b6wxntdt.js → x-vmmye1kb.js} +2 -2
- package/openapi.json +189 -32
- package/package.json +8 -7
- package/src/be/db/script-runs.ts +23 -0
- package/src/be/db/session-summaries.ts +8 -2
- package/src/be/db/session-tree.ts +174 -61
- package/src/be/db/session-visibility.ts +5 -4
- package/src/be/db/tasks/read.ts +122 -3
- package/src/be/db-client.ts +8 -5
- package/src/be/db.ts +23 -8
- package/src/be/memory/providers/sqlite-store.ts +49 -12
- package/src/be/migrations/259_session_summaries_open_tasks.sql +111 -0
- 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/model-validation.ts +5 -1
- package/src/be/pricing-normalize.ts +7 -2
- 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 +23 -0
- package/src/be/seed/schedules-seeder.ts +135 -9
- package/src/be/seed/workflows-seeder.ts +65 -1
- package/src/be/seed-pricing.ts +23 -0
- package/src/be/seed-scripts/catalog/task-failure-audit.ts +5 -1
- package/src/be/steering.ts +50 -3
- package/src/be/swarm-config-guard.ts +1 -0
- package/src/commands/controller.ts +4 -0
- package/src/commands/entrypoint-api-readiness.ts +74 -0
- package/src/commands/provider-credentials.ts +29 -2
- package/src/commands/runner.ts +86 -5
- package/src/extensions/contract-types.generated.ts +1 -1
- package/src/extensions/contract.ts +27 -1
- package/src/extensions/dispatcher.ts +4 -0
- package/src/http/agents.ts +1 -0
- package/src/http/index.ts +1 -1
- package/src/http/list-total-cache.ts +14 -7
- package/src/http/mcp-bridge.ts +4 -0
- package/src/http/memory.ts +38 -9
- package/src/http/script-runs.ts +174 -5
- package/src/http/status.ts +10 -2
- package/src/http/tasks.ts +4 -2
- package/src/integrations/chat-sdk/inbound.ts +15 -5
- package/src/integrations/chat-sdk/steering.ts +3 -1
- package/src/kv-reserved-namespaces.ts +2 -0
- package/src/prompts/base-prompt.ts +4 -1
- package/src/prompts/session-templates.ts +26 -0
- package/src/providers/acp-adapter.ts +190 -26
- package/src/providers/acp-targets.ts +306 -6
- package/src/providers/amp-adapter.ts +1 -1
- package/src/providers/codex-adapter.ts +9 -44
- package/src/providers/codex-oauth/flow.ts +24 -3
- package/src/providers/codex-oauth/storage.ts +56 -17
- package/src/providers/codex-oauth/subscription.ts +67 -0
- package/src/providers/fx-adapter.ts +211 -0
- package/src/providers/index.ts +5 -1
- package/src/providers/mcp-loopback-proxy.ts +94 -0
- package/src/providers/pi-mono-adapter.ts +98 -13
- package/src/providers/pricing-sources.md +8 -0
- package/src/providers/reasoning-effort.ts +17 -1
- package/src/providers/types.ts +2 -1
- 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/scheduler/scheduler.ts +36 -3
- package/src/script-workflows/workflow-ctx.ts +8 -0
- package/src/scripts-runtime/sdk-allowlist.ts +1 -0
- package/src/scripts-runtime/types/stdlib.d.ts +27 -0
- package/src/scripts-runtime/types/swarm-extension.d.ts +33 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +27 -0
- package/src/server-user.ts +1 -0
- package/src/server.ts +7 -0
- package/src/slack/assistant.ts +17 -1
- package/src/slack/commands.ts +5 -1
- package/src/slack/handlers.ts +53 -5
- package/src/slack/left-threads.ts +88 -0
- package/src/slack/steering.ts +3 -0
- package/src/slack/thread-buffer.ts +40 -16
- package/src/tasks/follow-up-redelegation-guard.ts +155 -0
- package/src/tests/acp-adapter.test.ts +79 -0
- package/src/tests/base-prompt.test.ts +12 -0
- package/src/tests/chat-sdk-inbound.test.ts +30 -0
- package/src/tests/chatgpt-subscription-model-groups.test.ts +62 -0
- package/src/tests/chatgpt-subscription-pi.test.ts +170 -0
- package/src/tests/chatgpt-subscription-session-bench.test.ts +198 -0
- package/src/tests/claude-artifacts-read-script.test.ts +209 -0
- package/src/tests/codex-oauth-credential-info.test.ts +40 -0
- package/src/tests/codex-oauth-refresh-egress.test.ts +194 -0
- package/src/tests/context-window.test.ts +7 -0
- package/src/tests/db-client.test.ts +21 -0
- package/src/tests/db-task-read-contract.test.ts +8 -2
- package/src/tests/db-task-write-contract.test.ts +5 -1
- package/src/tests/entrypoint-api-readiness.test.ts +416 -210
- package/src/tests/explicit-model-validation.test.ts +113 -8
- package/src/tests/extensions-run-context.test.ts +11 -0
- package/src/tests/extensions-steering.test.ts +353 -0
- package/src/tests/extensions-validate.test.ts +5 -0
- package/src/tests/fixtures/extensions/steering-policy/hooks.ts +39 -0
- package/src/tests/fixtures/extensions/steering-policy/manifest.json +7 -0
- package/src/tests/fixtures/fake-fx.ts +167 -0
- package/src/tests/fixtures/fx/acp-responses-recorded.json +157 -0
- package/src/tests/fixtures/fx/acp-updates-recorded.jsonl +15 -0
- package/src/tests/follow-up-redelivery-guard.test.ts +432 -202
- package/src/tests/fx-adapter.test.ts +771 -0
- package/src/tests/grok-adapter.test.ts +36 -0
- package/src/tests/list-total-cache.test.ts +27 -2
- package/src/tests/mcp-loopback-proxy.test.ts +193 -0
- package/src/tests/memory-fts-populate.test.ts +11 -9
- package/src/tests/memory-retrieval-purge.test.ts +80 -0
- package/src/tests/migration-260-runtime-pool-cache.test.ts +118 -0
- package/src/tests/model-tier-resolution.test.ts +3 -3
- package/src/tests/pricing-refresh.test.ts +5 -5
- package/src/tests/prompt-template-session.test.ts +5 -3
- package/src/tests/provider-registration.test.ts +2 -1
- package/src/tests/query-audit-plans.test.ts +30 -2
- 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/script-connections.test.ts +13 -0
- package/src/tests/script-runs-http.test.ts +684 -2
- package/src/tests/seed-model-profiles.test.ts +2 -2
- package/src/tests/seed-schedules.test.ts +138 -0
- package/src/tests/seed-scripts.test.ts +26 -0
- package/src/tests/session-cost-recompute-unit.test.ts +39 -0
- package/src/tests/session-costs-golden.test.ts +26 -0
- package/src/tests/session-summaries-plans.test.ts +120 -0
- package/src/tests/session-summaries.test.ts +72 -12
- package/src/tests/sessions-tree.test.ts +11 -3
- package/src/tests/slack-leave-thread.test.ts +571 -0
- package/src/tests/slack-render-v2-query-plans.test.ts +24 -2
- package/src/tests/status.test.ts +11 -0
- package/src/tests/tasks-readable-row.test.ts +155 -0
- package/src/tests/tool-annotations.test.ts +2 -1
- package/src/tests/workflow-engine-v2.test.ts +42 -0
- package/src/tests/workflow-script-principal.test.ts +207 -7
- package/src/tools/send-task.ts +35 -47
- package/src/tools/slack-leave-thread.ts +104 -0
- package/src/tools/steer-task.ts +3 -2
- package/src/tools/task-dedup.ts +45 -21
- package/src/tools/tool-config.ts +2 -1
- package/src/types.ts +57 -0
- package/src/utils/codex-auth-failure.ts +30 -0
- package/src/utils/context-window.ts +4 -1
- package/src/utils/credentials.ts +7 -0
- package/src/utils/fx-models.ts +43 -0
- package/src/workflows/cooldown.ts +3 -2
- package/src/workflows/engine.ts +24 -9
- package/templates/schedules/dreaming-daily/config.json +1 -1
- package/templates/schedules/dreaming-daily/content.md +6 -14
- package/templates/skills/composio-google-docs/content.md +10 -0
- package/templates/skills/script-workflows/content.md +19 -1
- package/templates/skills/swarm-docs/content.md +2 -0
- package/templates/skills/swarm-scripts/SKILL.md +2 -0
- package/templates/skills/swarm-scripts/content.md +2 -0
- package/templates/workflows/dreaming/content.md +5 -1
- package/dist/acp-adapter-96dc64m2.js +0 -15
- package/dist/cli-dznrgaep.js +0 -30
- package/dist/cli-w3hcrxht.js +0 -6985
- package/dist/handlers-jg7v74ty.js +0 -31
- package/dist/index-8c1zbz59.js +0 -29
- package/dist/index-8k8tmfxd.js +0 -32
- package/dist/index-hq617xdj.js +0 -36
- package/dist/lead-567kw7x3.js +0 -68
- package/dist/worker-fjdqra6q.js +0 -68
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import * as z from "zod";
|
|
3
|
+
import { getAgentById, getTaskById } from "@/be/db";
|
|
4
|
+
import { can } from "@/rbac";
|
|
5
|
+
import {
|
|
6
|
+
isSlackLeaveThreadDisabled,
|
|
7
|
+
leaveSlackThread,
|
|
8
|
+
SLACK_LEAVE_THREAD_DISABLE,
|
|
9
|
+
} from "@/slack/left-threads";
|
|
10
|
+
import { parseSlackTs } from "@/slack/message-text";
|
|
11
|
+
import { createToolRegistrar, swarmToolOutputSchema, toolErr, toolOk } from "@/tools/utils";
|
|
12
|
+
|
|
13
|
+
export const registerSlackLeaveThreadTool = (server: McpServer) => {
|
|
14
|
+
createToolRegistrar(server)(
|
|
15
|
+
"slack-leave-thread",
|
|
16
|
+
{
|
|
17
|
+
title: "Leave a Slack thread",
|
|
18
|
+
description:
|
|
19
|
+
"Stop following a Slack thread until someone @mentions the bot there again. Messages in the thread that do not @mention the bot are then ignored, with no task and no reply. Posts nothing to Slack. Pass taskId for the thread of your Slack-originated task, or (lead only) channelId and threadTs.",
|
|
20
|
+
annotations: { openWorldHint: true, idempotentHint: true },
|
|
21
|
+
|
|
22
|
+
inputSchema: z.object({
|
|
23
|
+
taskId: z.uuid().optional().describe("A task whose Slack thread to leave."),
|
|
24
|
+
channelId: z.string().min(1).optional().describe("The Slack channel ID of the thread."),
|
|
25
|
+
threadTs: z
|
|
26
|
+
.string()
|
|
27
|
+
.min(1)
|
|
28
|
+
.optional()
|
|
29
|
+
.describe("Timestamp of the thread root (dotted, 'p' deep-link, or full permalink URL)."),
|
|
30
|
+
}),
|
|
31
|
+
outputSchema: swarmToolOutputSchema({
|
|
32
|
+
channelId: z.string().optional(),
|
|
33
|
+
threadTs: z.string().optional(),
|
|
34
|
+
}),
|
|
35
|
+
},
|
|
36
|
+
async ({ taskId, channelId, threadTs }, requestInfo, _meta) => {
|
|
37
|
+
// Sessions created before the flag was set still list the tool.
|
|
38
|
+
if (isSlackLeaveThreadDisabled()) {
|
|
39
|
+
return toolErr(`slack-leave-thread is disabled by ${SLACK_LEAVE_THREAD_DISABLE}.`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!requestInfo.agentId) {
|
|
43
|
+
return toolErr("Agent ID not found.");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const agent = await getAgentById(requestInfo.agentId);
|
|
47
|
+
if (!agent) {
|
|
48
|
+
return toolErr("Agent not found.");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Any thread is open to leads, like slack-post. Task Slack context and
|
|
52
|
+
// source can be set by any agent (send-task, POST /api/tasks), so a non-lead
|
|
53
|
+
// may only leave the thread of a task assigned to it that Slack ingress
|
|
54
|
+
// created: only the Slack handlers write slackTriggerMessageTs.
|
|
55
|
+
const mayLeaveAnyThread = can({
|
|
56
|
+
principal: { kind: "agent", agentId: agent.id, isLead: agent.isLead },
|
|
57
|
+
verb: "integration.slack.post",
|
|
58
|
+
resource: { kind: "none" },
|
|
59
|
+
source: "mcp",
|
|
60
|
+
}).allow;
|
|
61
|
+
|
|
62
|
+
let slackChannelId: string | undefined;
|
|
63
|
+
let slackThreadTs: string | undefined;
|
|
64
|
+
|
|
65
|
+
if (taskId) {
|
|
66
|
+
if (channelId || threadTs) {
|
|
67
|
+
return toolErr("Provide either taskId or channelId and threadTs, not both.");
|
|
68
|
+
}
|
|
69
|
+
const task = await getTaskById(taskId);
|
|
70
|
+
if (!task) {
|
|
71
|
+
return toolErr("Task not found.");
|
|
72
|
+
}
|
|
73
|
+
if (
|
|
74
|
+
!mayLeaveAnyThread &&
|
|
75
|
+
(task.agentId !== agent.id || task.source !== "slack" || !task.slackTriggerMessageTs)
|
|
76
|
+
) {
|
|
77
|
+
return toolErr(
|
|
78
|
+
"Only a lead or the agent assigned a Slack-originated task can leave its thread.",
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
slackChannelId = task.slackChannelId;
|
|
82
|
+
slackThreadTs = task.slackThreadTs;
|
|
83
|
+
} else if (channelId && threadTs) {
|
|
84
|
+
if (!mayLeaveAnyThread) {
|
|
85
|
+
return toolErr("Leaving a thread by channelId and threadTs requires lead privileges.");
|
|
86
|
+
}
|
|
87
|
+
slackChannelId = channelId;
|
|
88
|
+
slackThreadTs = parseSlackTs(threadTs);
|
|
89
|
+
} else {
|
|
90
|
+
return toolErr("Must provide taskId, or both channelId and threadTs.");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (!slackChannelId || !slackThreadTs) {
|
|
94
|
+
return toolErr("No Slack thread context available.");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
await leaveSlackThread(slackChannelId, slackThreadTs, { agentId: agent.id });
|
|
98
|
+
return toolOk(
|
|
99
|
+
`Left Slack thread ${slackChannelId}/${slackThreadTs}. Messages there are ignored until someone @mentions the bot.`,
|
|
100
|
+
{ data: { channelId: slackChannelId, threadTs: slackThreadTs } },
|
|
101
|
+
);
|
|
102
|
+
},
|
|
103
|
+
);
|
|
104
|
+
};
|
package/src/tools/steer-task.ts
CHANGED
|
@@ -99,7 +99,8 @@ export async function steerTaskHandler(
|
|
|
99
99
|
message,
|
|
100
100
|
mode,
|
|
101
101
|
onUnsupported,
|
|
102
|
-
source:
|
|
102
|
+
source:
|
|
103
|
+
ctx.kind === "owner" && ctx.requestInfo?.callOrigin === "script-sdk" ? "script" : "mcp",
|
|
103
104
|
createdByKind: ctx.kind === "owner" ? "agent" : "user",
|
|
104
105
|
createdByAgentId: ctx.kind === "owner" ? ctx.agentId : undefined,
|
|
105
106
|
createdByUserId: ctx.kind === "user" ? ctx.userId : undefined,
|
|
@@ -126,7 +127,7 @@ export const registerSteerTaskTool = (server: McpServer) => {
|
|
|
126
127
|
{
|
|
127
128
|
title: "Steer Task",
|
|
128
129
|
description:
|
|
129
|
-
'Send a message to a task that is already running. `mode:"steer"` is honored on pi, claude-managed, codex and
|
|
130
|
+
'Send a message to a task that is already running. `mode:"steer"` is honored on pi, claude-managed, codex, cursor and fx (fx supports steer only); claude, devin, opencode and amp support queue only (amp delivery lands at the next tool result). Pass `onUnsupported:"fail"` to get an error instead of a downgrade.',
|
|
130
131
|
annotations: { destructiveHint: true },
|
|
131
132
|
inputSchema: steerTaskInputSchema,
|
|
132
133
|
outputSchema: steerTaskOutputSchema,
|
package/src/tools/task-dedup.ts
CHANGED
|
@@ -34,11 +34,53 @@ const HIGH_SIMILARITY_THRESHOLD = 0.8;
|
|
|
34
34
|
/** Lower threshold used when the tasks also target the same agent */
|
|
35
35
|
const SAME_AGENT_SIMILARITY_THRESHOLD = 0.6;
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Rows one duplicate check scans. The JS similarity match needs more than the
|
|
39
|
+
* query's default 50 (an older duplicate can sit behind newer unrelated tasks),
|
|
40
|
+
* but the scan must stay bounded: send-task runs it twice per call, once inside
|
|
41
|
+
* the write transaction.
|
|
42
|
+
*/
|
|
43
|
+
export const DEDUP_SCAN_LIMIT = 500;
|
|
44
|
+
|
|
37
45
|
export interface DedupMatch {
|
|
38
46
|
task: AgentTask;
|
|
39
47
|
reason: string;
|
|
40
48
|
}
|
|
41
49
|
|
|
50
|
+
/**
|
|
51
|
+
* The description-similarity rules shared by the duplicate check and the
|
|
52
|
+
* follow-up re-delegation guard: a very similar description matches any
|
|
53
|
+
* target, a moderately similar one matches only the same target agent.
|
|
54
|
+
*/
|
|
55
|
+
export function matchSimilarTask(
|
|
56
|
+
existing: AgentTask,
|
|
57
|
+
opts: { taskDescription: string; targetAgentId?: string },
|
|
58
|
+
): DedupMatch | null {
|
|
59
|
+
const similarity = jaccardSimilarity(existing.task, opts.taskDescription);
|
|
60
|
+
|
|
61
|
+
// Very similar task description: likely duplicate regardless of target
|
|
62
|
+
if (similarity > HIGH_SIMILARITY_THRESHOLD) {
|
|
63
|
+
return {
|
|
64
|
+
task: existing,
|
|
65
|
+
reason: `similar task description (${(similarity * 100).toFixed(0)}% match)`,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Same target agent + moderately similar task: lower threshold
|
|
70
|
+
if (
|
|
71
|
+
opts.targetAgentId &&
|
|
72
|
+
existing.agentId === opts.targetAgentId &&
|
|
73
|
+
similarity > SAME_AGENT_SIMILARITY_THRESHOLD
|
|
74
|
+
) {
|
|
75
|
+
return {
|
|
76
|
+
task: existing,
|
|
77
|
+
reason: `similar task to same agent (${(similarity * 100).toFixed(0)}% match)`,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
42
84
|
/**
|
|
43
85
|
* Check for duplicate tasks created recently.
|
|
44
86
|
* Returns the first matching duplicate, or null if none found.
|
|
@@ -54,6 +96,7 @@ export async function findDuplicateTask(opts: {
|
|
|
54
96
|
const recentTasks = await findRecentSimilarTasks({
|
|
55
97
|
creatorAgentId: opts.creatorAgentId,
|
|
56
98
|
windowMinutes: opts.windowMinutes ?? 10,
|
|
99
|
+
limit: DEDUP_SCAN_LIMIT,
|
|
57
100
|
});
|
|
58
101
|
|
|
59
102
|
for (const existing of recentTasks) {
|
|
@@ -70,27 +113,8 @@ export async function findDuplicateTask(opts: {
|
|
|
70
113
|
};
|
|
71
114
|
}
|
|
72
115
|
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
// 2. Very similar task description — likely duplicate regardless of target
|
|
76
|
-
if (similarity > HIGH_SIMILARITY_THRESHOLD) {
|
|
77
|
-
return {
|
|
78
|
-
task: existing,
|
|
79
|
-
reason: `similar task description (${(similarity * 100).toFixed(0)}% match)`,
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// 3. Same target agent + moderately similar task — lower threshold
|
|
84
|
-
if (
|
|
85
|
-
opts.targetAgentId &&
|
|
86
|
-
existing.agentId === opts.targetAgentId &&
|
|
87
|
-
similarity > SAME_AGENT_SIMILARITY_THRESHOLD
|
|
88
|
-
) {
|
|
89
|
-
return {
|
|
90
|
-
task: existing,
|
|
91
|
-
reason: `similar task to same agent (${(similarity * 100).toFixed(0)}% match)`,
|
|
92
|
-
};
|
|
93
|
-
}
|
|
116
|
+
const match = matchSimilarTask(existing, opts);
|
|
117
|
+
if (match) return match;
|
|
94
118
|
}
|
|
95
119
|
|
|
96
120
|
return null;
|
package/src/tools/tool-config.ts
CHANGED
|
@@ -96,7 +96,7 @@ export const DEFERRED_TOOLS = new Set([
|
|
|
96
96
|
"read-messages",
|
|
97
97
|
"post-message",
|
|
98
98
|
|
|
99
|
-
// Slack (
|
|
99
|
+
// Slack (13): task-context gated; keep out of CORE_TOOLS
|
|
100
100
|
"slack-reply",
|
|
101
101
|
"slack-read",
|
|
102
102
|
"slack-upload-file",
|
|
@@ -109,6 +109,7 @@ export const DEFERRED_TOOLS = new Set([
|
|
|
109
109
|
"slack-archive-channel",
|
|
110
110
|
"slack-delete",
|
|
111
111
|
"slack-update",
|
|
112
|
+
"slack-leave-thread",
|
|
112
113
|
|
|
113
114
|
// Channel management (2)
|
|
114
115
|
"create-channel",
|
package/src/types.ts
CHANGED
|
@@ -185,6 +185,14 @@ export const DEFAULT_MODEL_TIER_MAP: Record<ProviderName, Record<ModelTier, stri
|
|
|
185
185
|
smart: "grok-4.6",
|
|
186
186
|
ultra: "grok-4.7",
|
|
187
187
|
},
|
|
188
|
+
// fx runs these on its `openrouter` custom connection (OPENROUTER_API_KEY),
|
|
189
|
+
// pi's tested OpenRouter set; each ran under fx 0.0.15 (2026-10-29 probe).
|
|
190
|
+
fx: {
|
|
191
|
+
smol: "openrouter/deepseek/deepseek-v4.1-flash",
|
|
192
|
+
regular: "openrouter/deepseek/deepseek-v4.1-flash",
|
|
193
|
+
smart: "openrouter/deepseek/deepseek-v4-pro-0813",
|
|
194
|
+
ultra: "openrouter/anthropic/claude-opus-5.5",
|
|
195
|
+
},
|
|
188
196
|
// ACP has no portable tier-to-model mapping. Operators may set an explicit
|
|
189
197
|
// MODEL_OVERRIDE, which the adapter applies through an advertised `model`
|
|
190
198
|
// config option with a target-specific startup fallback.
|
|
@@ -436,6 +444,7 @@ export const ProviderNameSchema = z.enum([
|
|
|
436
444
|
"amp",
|
|
437
445
|
"cursor",
|
|
438
446
|
"grok",
|
|
447
|
+
"fx",
|
|
439
448
|
]);
|
|
440
449
|
export type ProviderName = z.infer<typeof ProviderNameSchema>;
|
|
441
450
|
|
|
@@ -596,6 +605,10 @@ export const PROVIDER_STEER_CAPABILITIES: Record<ProviderName, SteerMode[]> = {
|
|
|
596
605
|
// Grok runs on the ACP client: one `session/prompt` turn, interrupted only
|
|
597
606
|
// by `session/cancel`. No Grok surface (ACP or headless) exposes a steer.
|
|
598
607
|
grok: [],
|
|
608
|
+
// `fx acp` steers natively: a `session/prompt` with `_meta.fx.steer` during a
|
|
609
|
+
// running turn lands at the next tool boundary without cancelling it (fx
|
|
610
|
+
// 0.0.15 probe, 2026-10-29). Queue is not measured, so it is not advertised.
|
|
611
|
+
fx: ["steer"],
|
|
599
612
|
};
|
|
600
613
|
|
|
601
614
|
export type DevinProviderMeta = {
|
|
@@ -619,6 +632,7 @@ export type ProviderMetaMap = {
|
|
|
619
632
|
amp: NoProviderMeta;
|
|
620
633
|
cursor: NoProviderMeta;
|
|
621
634
|
grok: NoProviderMeta;
|
|
635
|
+
fx: NoProviderMeta;
|
|
622
636
|
};
|
|
623
637
|
|
|
624
638
|
export const FollowUpConfigSchema = z
|
|
@@ -2198,6 +2212,39 @@ export const RuntimeControllerSchema = RuntimeControllerCreateSchema.omit({ secr
|
|
|
2198
2212
|
.openapi("RuntimeController");
|
|
2199
2213
|
export type RuntimeController = z.infer<typeof RuntimeControllerSchema>;
|
|
2200
2214
|
|
|
2215
|
+
export const RuntimePoolCacheSchema = z
|
|
2216
|
+
.object({
|
|
2217
|
+
mountPath: z
|
|
2218
|
+
.string()
|
|
2219
|
+
.min(2)
|
|
2220
|
+
.refine((path) => {
|
|
2221
|
+
const parts = path.split("/").filter(Boolean);
|
|
2222
|
+
const normalized = `/${parts.join("/")}`;
|
|
2223
|
+
const protectedPaths = [
|
|
2224
|
+
"/workspace/personal",
|
|
2225
|
+
"/workspace/shared",
|
|
2226
|
+
...["/home/worker", "/root"].flatMap((home) =>
|
|
2227
|
+
[".ssh", ".codex", ".claude", ".claude.json", ".pi", ".config", ".local"].map(
|
|
2228
|
+
(directory) => `${home}/${directory}`,
|
|
2229
|
+
),
|
|
2230
|
+
),
|
|
2231
|
+
];
|
|
2232
|
+
return (
|
|
2233
|
+
path.startsWith("/") &&
|
|
2234
|
+
parts.length > 0 &&
|
|
2235
|
+
!path.includes("\0") &&
|
|
2236
|
+
!parts.some((part) => part === "." || part === "..") &&
|
|
2237
|
+
!protectedPaths.some(
|
|
2238
|
+
(protectedPath) =>
|
|
2239
|
+
protectedPath === normalized ||
|
|
2240
|
+
protectedPath.startsWith(`${normalized}/`) ||
|
|
2241
|
+
normalized.startsWith(`${protectedPath}/`),
|
|
2242
|
+
)
|
|
2243
|
+
);
|
|
2244
|
+
}, "Cache mountPath must be absolute, without . or .., and must not cover worker homes, workspaces or credential directories"),
|
|
2245
|
+
})
|
|
2246
|
+
.strict();
|
|
2247
|
+
|
|
2201
2248
|
export const RuntimePoolCreateSchema = z
|
|
2202
2249
|
.object({
|
|
2203
2250
|
name: z.string().min(1).max(200),
|
|
@@ -2212,6 +2259,7 @@ export const RuntimePoolCreateSchema = z
|
|
|
2212
2259
|
delivery: DeliveryModeSchema.nullable().default(null),
|
|
2213
2260
|
env: z.record(z.string(), z.string()).default({}),
|
|
2214
2261
|
resources: RuntimeResourcesSchema.nullable().default(null),
|
|
2262
|
+
cache: RuntimePoolCacheSchema.nullable().default(null),
|
|
2215
2263
|
ttlSec: z.number().int().positive().nullable().default(null),
|
|
2216
2264
|
enabled: z.boolean().default(true),
|
|
2217
2265
|
})
|
|
@@ -2225,6 +2273,7 @@ export const RuntimePoolUpdateSchema = RuntimePoolCreateSchema.partial().extend(
|
|
|
2225
2273
|
delivery: DeliveryModeSchema.nullable().optional(),
|
|
2226
2274
|
env: z.record(z.string(), z.string()).optional(),
|
|
2227
2275
|
resources: RuntimeResourcesSchema.nullable().optional(),
|
|
2276
|
+
cache: RuntimePoolCacheSchema.nullable().optional(),
|
|
2228
2277
|
ttlSec: z.number().int().positive().nullable().optional(),
|
|
2229
2278
|
enabled: z.boolean().optional(),
|
|
2230
2279
|
});
|
|
@@ -2606,6 +2655,10 @@ export const CooldownConfigSchema = z
|
|
|
2606
2655
|
hours: z.number().min(0).optional(),
|
|
2607
2656
|
minutes: z.number().min(0).optional(),
|
|
2608
2657
|
seconds: z.number().min(0).optional(),
|
|
2658
|
+
// "finish" (default): also wait out the window after the last successful
|
|
2659
|
+
// run finished. "start": count only from run starts, so a long run never
|
|
2660
|
+
// pushes the next fixed-time trigger out.
|
|
2661
|
+
from: z.enum(["finish", "start"]).optional(),
|
|
2609
2662
|
})
|
|
2610
2663
|
.refine((v) => v.hours !== undefined || v.minutes !== undefined || v.seconds !== undefined, {
|
|
2611
2664
|
message: "At least one of hours, minutes, or seconds is required",
|
|
@@ -3096,6 +3149,8 @@ export const ScriptRunSchema = z
|
|
|
3096
3149
|
lastHeartbeatAt: z.string().optional(),
|
|
3097
3150
|
idempotencyKey: z.string().optional(),
|
|
3098
3151
|
requestedByUserId: z.string().optional(),
|
|
3152
|
+
/** Task whose session launched the run; the follow-up re-delegation guard reads it. */
|
|
3153
|
+
sourceTaskId: z.string().optional(),
|
|
3099
3154
|
})
|
|
3100
3155
|
.openapi("ScriptRun");
|
|
3101
3156
|
export type ScriptRun = z.infer<typeof ScriptRunSchema>;
|
|
@@ -3104,6 +3159,7 @@ export const ScriptRunListItemSchema = ScriptRunSchema.omit({
|
|
|
3104
3159
|
source: true,
|
|
3105
3160
|
args: true,
|
|
3106
3161
|
output: true,
|
|
3162
|
+
sourceTaskId: true,
|
|
3107
3163
|
});
|
|
3108
3164
|
export type ScriptRunListItem = z.infer<typeof ScriptRunListItemSchema>;
|
|
3109
3165
|
|
|
@@ -3838,6 +3894,7 @@ export const PricingProviderSchema = z.enum([
|
|
|
3838
3894
|
"amp",
|
|
3839
3895
|
"cursor",
|
|
3840
3896
|
"grok",
|
|
3897
|
+
"fx",
|
|
3841
3898
|
]);
|
|
3842
3899
|
export type PricingProvider = z.infer<typeof PricingProviderSchema>;
|
|
3843
3900
|
|
|
@@ -32,3 +32,33 @@ export function isCodexAuthFailureReason(failureReason: string | undefined | nul
|
|
|
32
32
|
const lower = failureReason.toLowerCase();
|
|
33
33
|
return !TRANSIENT_AUTH_ERROR_MARKERS.some((m) => lower.includes(m));
|
|
34
34
|
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Turn-level texts of a rejected ChatGPT/Codex credential: a bad API key, or
|
|
38
|
+
* a pool login revoked while its access token is unexpired. That death mode
|
|
39
|
+
* never enters `getValidCodexOAuth` (the token looks fresh), so this error is
|
|
40
|
+
* its only artifact. The adapters tag a match `[auth-error]`.
|
|
41
|
+
*/
|
|
42
|
+
const REJECTED_ACCESS_PATTERNS = [
|
|
43
|
+
"unauthorized",
|
|
44
|
+
"http 401",
|
|
45
|
+
" 401 ",
|
|
46
|
+
"invalid api key",
|
|
47
|
+
"invalid_api_key",
|
|
48
|
+
"missing api key",
|
|
49
|
+
"no api key",
|
|
50
|
+
"authentication failed",
|
|
51
|
+
"failed to refresh token",
|
|
52
|
+
"invalid 'refresh_token'",
|
|
53
|
+
// ChatGPT backend 401 bodies (pi on the subscription surfaces them as-is).
|
|
54
|
+
"token_invalidated",
|
|
55
|
+
"authentication token has been invalidated",
|
|
56
|
+
"authentication token is expired",
|
|
57
|
+
"refresh token was revoked",
|
|
58
|
+
"could not parse your authentication token",
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
export function isCodexRejectedAccessError(text: string): boolean {
|
|
62
|
+
const lower = text.toLowerCase();
|
|
63
|
+
return REJECTED_ACCESS_PATTERNS.some((p) => lower.includes(p));
|
|
64
|
+
}
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* deprecated; new code should use `computeContextUsedUnified`.
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
|
-
import { buildClaudeShortnameMap } from "@desplega/model-catalog";
|
|
23
|
+
import { buildClaudeShortnameMap, chatgptSubscriptionModelId } from "@desplega/model-catalog";
|
|
24
24
|
import { runtimeCatalogModel, runtimeCatalogSection } from "./runtime-model-catalog";
|
|
25
25
|
|
|
26
26
|
/**
|
|
@@ -73,6 +73,9 @@ export function getContextWindowSize(model: string): number {
|
|
|
73
73
|
/** {@link getContextWindowSize} without the fallback: undefined when the catalog has no window. */
|
|
74
74
|
export function getKnownContextWindowSize(model: string | undefined): number | undefined {
|
|
75
75
|
if (!model) return undefined;
|
|
76
|
+
// `openai-codex/<id>` (pi on the ChatGPT subscription) is the OpenAI `<id>`.
|
|
77
|
+
const subscriptionId = chatgptSubscriptionModelId(model);
|
|
78
|
+
if (subscriptionId) return catalogContext("openai", subscriptionId);
|
|
76
79
|
const slash = model.indexOf("/");
|
|
77
80
|
if (slash > 0) {
|
|
78
81
|
const scoped = catalogContext(model.slice(0, slash), model.slice(slash + 1));
|
package/src/utils/credentials.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import {
|
|
3
|
+
fxModelRoute,
|
|
3
4
|
openAICompatibleProviders,
|
|
4
5
|
providerForKeyVar,
|
|
5
6
|
providerForModel,
|
|
@@ -61,6 +62,7 @@ export const PROVIDER_CREDENTIAL_VARS: Record<string, readonly string[]> = {
|
|
|
61
62
|
amp: ["AMP_API_KEY"],
|
|
62
63
|
cursor: ["CURSOR_API_KEY"],
|
|
63
64
|
grok: ["XAI_API_KEY", "OPENROUTER_API_KEY"],
|
|
65
|
+
fx: ["OPENROUTER_API_KEY", "OPENAI_API_KEY", "AI_GATEWAY_API_KEY"],
|
|
64
66
|
opencode: ["OPENROUTER_API_KEY", "ANTHROPIC_API_KEY", "OPENAI_API_KEY"],
|
|
65
67
|
};
|
|
66
68
|
|
|
@@ -100,6 +102,11 @@ export function getModelAwareCredentialVars(provider: string, model?: string): r
|
|
|
100
102
|
if (provider === "grok" && model) {
|
|
101
103
|
return model.startsWith("openrouter/") ? ["OPENROUTER_API_KEY"] : ["XAI_API_KEY"];
|
|
102
104
|
}
|
|
105
|
+
// fx routes by namespace (`src/utils/fx-models.ts`); each route reads one key.
|
|
106
|
+
if (provider === "fx" && model) {
|
|
107
|
+
const route = fxModelRoute(model);
|
|
108
|
+
return "error" in route ? base : [route.keyVar];
|
|
109
|
+
}
|
|
103
110
|
if (!SLASH_MODEL_PROVIDERS.has(provider) || !model) return base;
|
|
104
111
|
// OpenAI-compatible registry providers own their key: `nan/<id>` and
|
|
105
112
|
// `helmcode/<id>` never pool an OpenRouter or Anthropic key.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model routing for the `fx` harness. Pure, so the API server can validate a
|
|
3
|
+
* model and the worker can route it without importing the adapter.
|
|
4
|
+
*
|
|
5
|
+
* | Swarm model string | fx provider (`FX_PROVIDER`) | `fx acp --model` | Key |
|
|
6
|
+
* |------------------------------|-----------------------------------|------------------|----------------------|
|
|
7
|
+
* | `openrouter/<vendor>/<id>` | `openrouter` (custom connection) | `<vendor>/<id>` | `OPENROUTER_API_KEY` |
|
|
8
|
+
* | `vercel/<vendor>/<id>` | `gateway` (built in) | `<vendor>/<id>` | `AI_GATEWAY_API_KEY` |
|
|
9
|
+
* | bare `<id>` (no slash) | `openai` (custom connection) | `<id>` | `OPENAI_API_KEY` |
|
|
10
|
+
*
|
|
11
|
+
* Anything else (`nan/…`, `helmcode/…`, `anthropic/…`) is rejected with a reason.
|
|
12
|
+
*/
|
|
13
|
+
import { type FxRoute, fxModelRoute } from "@desplega/model-catalog";
|
|
14
|
+
import { DEFAULT_MODEL_TIER_MAP } from "../types";
|
|
15
|
+
|
|
16
|
+
export type { FxRoute };
|
|
17
|
+
|
|
18
|
+
/** An fx route plus the swarm model string it came from. */
|
|
19
|
+
export type ResolvedFxRoute = FxRoute & { model: string };
|
|
20
|
+
|
|
21
|
+
/** Every key an fx route can read, in route-table order. */
|
|
22
|
+
export const FX_ROUTE_KEY_VARS = [
|
|
23
|
+
"OPENROUTER_API_KEY",
|
|
24
|
+
"OPENAI_API_KEY",
|
|
25
|
+
"AI_GATEWAY_API_KEY",
|
|
26
|
+
] as const;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The fx route for a swarm model string, or the reason it has none. An empty
|
|
30
|
+
* model means the regular tier default.
|
|
31
|
+
*/
|
|
32
|
+
export function resolveFxRoute(model: string | undefined): ResolvedFxRoute | { error: string } {
|
|
33
|
+
const value = model?.trim() || DEFAULT_MODEL_TIER_MAP.fx.regular;
|
|
34
|
+
const route = fxModelRoute(value);
|
|
35
|
+
return "error" in route ? route : { ...route, model: value };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** {@link resolveFxRoute}, throwing the rejection reason. */
|
|
39
|
+
export function requireFxRoute(model: string | undefined): ResolvedFxRoute {
|
|
40
|
+
const route = resolveFxRoute(model);
|
|
41
|
+
if ("error" in route) throw new Error(route.error);
|
|
42
|
+
return route;
|
|
43
|
+
}
|
|
@@ -15,7 +15,8 @@ function cooldownToMs(cooldown: CooldownConfig): number {
|
|
|
15
15
|
/**
|
|
16
16
|
* Check if a workflow should be skipped due to cooldown.
|
|
17
17
|
* Returns true if:
|
|
18
|
-
* - the last successful run finished within the cooldown window
|
|
18
|
+
* - the last successful run finished within the cooldown window (skipped for
|
|
19
|
+
* `from: "start"`), OR
|
|
19
20
|
* - any run (including failed ones) started within the cooldown window.
|
|
20
21
|
*
|
|
21
22
|
* The second condition prevents runaway re-triggering when all runs fail
|
|
@@ -30,7 +31,7 @@ export async function shouldSkipCooldown(
|
|
|
30
31
|
const now = Date.now();
|
|
31
32
|
|
|
32
33
|
// Skip if last successful run finished within the cooldown window
|
|
33
|
-
const lastSuccess = await getLastSuccessfulRun(workflowId);
|
|
34
|
+
const lastSuccess = cooldown.from === "start" ? null : await getLastSuccessfulRun(workflowId);
|
|
34
35
|
if (lastSuccess?.finishedAt) {
|
|
35
36
|
const lastFinished = new Date(lastSuccess.finishedAt).getTime();
|
|
36
37
|
if (now - lastFinished < cooldownMs) return true;
|
package/src/workflows/engine.ts
CHANGED
|
@@ -192,7 +192,7 @@ export async function startWorkflowExecution(
|
|
|
192
192
|
const newRunId = crypto.randomUUID();
|
|
193
193
|
const release = holdWorkflowRun(newRunId);
|
|
194
194
|
try {
|
|
195
|
-
const { runId, created, preflightAlert } = await createRunWithInitialState(
|
|
195
|
+
const { runId, created, preflightAlert, status } = await createRunWithInitialState(
|
|
196
196
|
workflow,
|
|
197
197
|
triggerData,
|
|
198
198
|
options,
|
|
@@ -204,7 +204,7 @@ export async function startWorkflowExecution(
|
|
|
204
204
|
if (preflightAlert) await notifyAutomationPreflightFailure(preflight);
|
|
205
205
|
return runId;
|
|
206
206
|
}
|
|
207
|
-
if (
|
|
207
|
+
if (status === "skipped") return runId;
|
|
208
208
|
telemetry.workflow("started", {
|
|
209
209
|
workflowId: workflow.id,
|
|
210
210
|
nodeCount: workflow.definition.nodes.length,
|
|
@@ -317,7 +317,12 @@ async function createRunWithInitialState(
|
|
|
317
317
|
options: WorkflowExecutionOptions,
|
|
318
318
|
initial: InitialRunState,
|
|
319
319
|
runId: string,
|
|
320
|
-
): Promise<{
|
|
320
|
+
): Promise<{
|
|
321
|
+
runId: string;
|
|
322
|
+
created: boolean;
|
|
323
|
+
preflightAlert?: boolean;
|
|
324
|
+
status?: InitialRunState["status"];
|
|
325
|
+
}> {
|
|
321
326
|
return getDbClient().transaction(async () => {
|
|
322
327
|
const { parentStepId } = options;
|
|
323
328
|
const attempt = options.parentStepAttempt ?? 0;
|
|
@@ -328,6 +333,16 @@ async function createRunWithInitialState(
|
|
|
328
333
|
const existingId = await getChildWorkflowRunId(parentStepId, attempt);
|
|
329
334
|
if (existingId) return { runId: existingId, created: false };
|
|
330
335
|
}
|
|
336
|
+
// The cooldown check before this transaction is only a fast path: two
|
|
337
|
+
// triggers can both pass it before either run exists. The write lock is
|
|
338
|
+
// held from BEGIN, across processes too, so this recheck sees every run
|
|
339
|
+
// committed before it and the losing trigger records a skipped run.
|
|
340
|
+
const state: InitialRunState =
|
|
341
|
+
!initial.status &&
|
|
342
|
+
workflow.cooldown &&
|
|
343
|
+
(await shouldSkipCooldown(workflow.id, workflow.cooldown))
|
|
344
|
+
? { status: "skipped", error: "cooldown", finishedAt: new Date().toISOString() }
|
|
345
|
+
: initial;
|
|
331
346
|
await createWorkflowRun({
|
|
332
347
|
id: runId,
|
|
333
348
|
workflowId: workflow.id,
|
|
@@ -340,16 +355,16 @@ async function createRunWithInitialState(
|
|
|
340
355
|
});
|
|
341
356
|
await updateWorkflowRun(
|
|
342
357
|
runId,
|
|
343
|
-
|
|
344
|
-
? { status:
|
|
345
|
-
: { context:
|
|
358
|
+
state.status
|
|
359
|
+
? { status: state.status, error: state.error, finishedAt: state.finishedAt }
|
|
360
|
+
: { context: state.context },
|
|
346
361
|
);
|
|
347
362
|
const preflightAlert =
|
|
348
|
-
|
|
363
|
+
state.status === "failed" && state.preflight
|
|
349
364
|
? await isFirstWorkflowPreflightFailureToday(workflow.id, runId)
|
|
350
365
|
: undefined;
|
|
351
|
-
await options.accept?.({ runId, skipped:
|
|
352
|
-
return { runId, created: true, preflightAlert };
|
|
366
|
+
await options.accept?.({ runId, skipped: state.status === "skipped" });
|
|
367
|
+
return { runId, created: true, preflightAlert, status: state.status };
|
|
353
368
|
});
|
|
354
369
|
}
|
|
355
370
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"displayName": "Dreaming",
|
|
5
5
|
"slug": "dreaming-daily",
|
|
6
6
|
"title": "Dreaming Daily Trigger",
|
|
7
|
-
"description": "
|
|
7
|
+
"description": "Start the dreaming workflow once a day, directly as a workflow schedule. The workflow's 20-hour cooldown skips a second run.",
|
|
8
8
|
"version": "1.0.0",
|
|
9
9
|
"category": "schedules",
|
|
10
10
|
"placeholders": [],
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
Trigger the `dreaming` workflow once a day. It replaces the Daily Evolution schedule (`daily-compounding-reflection`), which now seeds disabled.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
This is a workflow schedule: each fire starts a `dreaming` run directly, with no agent task in between. A seeded schedule has no creator, so the run acts as the swarm's lead, resolved when it fires; with no lead the fire fails. It runs after the Daily Blocker Digest (02:05 UTC), whose digest the workflow reads.
|
|
6
|
+
|
|
7
|
+
The `dreaming` workflow carries the double-run guard as a 20-hour cooldown: a run that would start within 20 hours of another `dreaming` run start (any status, manual runs included) is recorded as skipped. Dreaming runs share KV state, so they must never run twice in a day.
|
|
6
8
|
|
|
7
9
|
## Schedule
|
|
8
10
|
|
|
@@ -10,18 +12,8 @@ A seeded schedule cannot target a workflow directly, so this schedule creates on
|
|
|
10
12
|
{
|
|
11
13
|
"cron": "30 3 * * *",
|
|
12
14
|
"timezone": "UTC",
|
|
13
|
-
"enabled": true
|
|
15
|
+
"enabled": true,
|
|
16
|
+
"targetType": "workflow",
|
|
17
|
+
"workflow": "dreaming"
|
|
14
18
|
}
|
|
15
19
|
```
|
|
16
|
-
|
|
17
|
-
## Scheduled Task
|
|
18
|
-
|
|
19
|
-
Task Type: Dreaming trigger
|
|
20
|
-
|
|
21
|
-
Trigger today's run of the `dreaming` workflow. Do nothing else: no report, no memory, no Slack.
|
|
22
|
-
|
|
23
|
-
1. Double-run guard. Run `db-query` with exactly this SQL:
|
|
24
|
-
`SELECT w.name, r.id, r.status, r.startedAt FROM workflow_runs r JOIN workflows w ON w.id = r.workflowId WHERE w.name LIKE 'dreaming%' AND julianday(r.startedAt) >= julianday('now', '-20 hours') ORDER BY r.startedAt DESC LIMIT 5`
|
|
25
|
-
If it returns any row, complete this task without triggering, with the output `skipped: workflow <name> run <id> started at <startedAt>, within 20 hours`. Dreaming workflows share KV state, so they must never run twice in a day. If the query fails, complete without triggering and give the error.
|
|
26
|
-
2. Find the workflow named exactly `dreaming` with `list-workflows`. If it is missing or disabled, complete with that reason and trigger nothing.
|
|
27
|
-
3. Call `trigger-workflow` with its id and no `triggerData`. Complete with the run id it returns.
|
|
@@ -80,6 +80,16 @@ Google Drive export, then preserve or reconstruct the structure explicitly.
|
|
|
80
80
|
There is no `GET_DOCUMENT_MARKDOWN` tool; markdown exists on create/update, not
|
|
81
81
|
on read.
|
|
82
82
|
|
|
83
|
+
## Comments
|
|
84
|
+
|
|
85
|
+
Doc comments are a Drive feature, not a Docs one: there is no `GOOGLEDOCS_*`
|
|
86
|
+
comment slug. List them with `GOOGLEDRIVE_LIST_COMMENTS` (replies:
|
|
87
|
+
`GOOGLEDRIVE_LIST_REPLIES`) from the `googledrive` toolkit, so resolve the
|
|
88
|
+
user's `googledrive` connected account instead of their `googledocs` one.
|
|
89
|
+
Listing only works when the Drive read scopes are authorized on that account.
|
|
90
|
+
`GOOGLEDRIVE_CREATE_COMMENT` and the update/delete tools are write actions,
|
|
91
|
+
only on explicit request.
|
|
92
|
+
|
|
83
93
|
## Gotchas
|
|
84
94
|
|
|
85
95
|
- Search returns Drive metadata, not document content — do a second
|