@desplega.ai/agent-swarm 2.7.4 → 2.7.5
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-g3ads55s.js +15 -0
- package/dist/{actions-gfrr9vb7.js → actions-zwfykqqk.js} +18 -18
- package/dist/{amp-adapter-0s58gm46.js → amp-adapter-z3s0ssdt.js} +3 -3
- package/dist/{app-stvq692g.js → app-vmp4fgep.js} +3 -3
- package/dist/{assistant-5s37fr9n.js → assistant-vwde653f.js} +28 -21
- package/dist/{boot-reembed-yzy1841n.js → boot-reembed-375t7tbk.js} +1 -1
- package/dist/{boot-reembed-cv82gz7y.js → boot-reembed-gq9rcpnj.js} +1 -1
- package/dist/{boot-scrub-sweep-8k4q95ap.js → boot-scrub-sweep-nd938kyf.js} +4 -4
- package/dist/{boot-seeds-ftt9htah.js → boot-seeds-aykx51v3.js} +4 -4
- package/dist/{boxd-515v1t8z.js → boxd-k1j26g3h.js} +1 -1
- package/dist/{claude-adapter-t00wrzeh.js → claude-adapter-8j4hm099.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-0wyynpn1.js} +3 -3
- package/dist/{claude-sdk-session-vj0c2azx.js → claude-sdk-session-q6wj5hmy.js} +9 -9
- package/dist/{cli-wndv9p7g.js → cli-0731hvbw.js} +6 -6
- package/dist/{cli-d2pzqej1.js → cli-0bxqnvq9.js} +719 -7
- package/dist/{cli-jkt52mgt.js → cli-0gey5fzy.js} +2 -2
- package/dist/{cli-1yekbyqm.js → cli-1338796d.js} +205 -48
- package/dist/{cli-1s1dqn1j.js → cli-1ayk1eas.js} +171 -123
- 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-kvtc7aez.js → cli-3h4nhjn8.js} +42 -14
- package/dist/{cli-655xm9ms.js → cli-583frbyr.js} +1 -1
- package/dist/{cli-4r12t6w6.js → cli-5c4hjzne.js} +101 -4
- package/dist/{cli-az0bpf8p.js → cli-5pag6rmn.js} +3 -3
- package/dist/{cli-36g23z21.js → cli-6s7vdcta.js} +4 -4
- package/dist/{cli-rtk9g7av.js → cli-73jzwksr.js} +6 -2
- package/dist/{cli-jg0nfda6.js → cli-75gkpjvw.js} +52 -21
- package/dist/{cli-a81qevyg.js → cli-7a42nfy0.js} +20 -36
- package/dist/{cli-0sbkss7h.js → cli-7wcyk2s5.js} +2 -2
- package/dist/{cli-41z8xwj0.js → cli-7wspm5gm.js} +14 -4
- package/dist/{cli-byqfbnjw.js → cli-82ae98pz.js} +9 -9
- package/dist/{cli-bq2pcphn.js → cli-8hs41zy2.js} +1 -1
- package/dist/{cli-vqgnyh76.js → cli-98rw5dt5.js} +15 -3
- package/dist/{cli-9cctx1tn.js → cli-a22h291q.js} +1 -1
- package/dist/{cli-j4kt1mpb.js → cli-a47xy8eq.js} +3 -3
- package/dist/{cli-ycm6180s.js → cli-a7v854rf.js} +5 -3
- package/dist/{cli-98pke4vk.js → cli-ae7mrdf9.js} +2 -2
- package/dist/{cli-ahd90bwt.js → cli-afrrppsw.js} +6 -6
- package/dist/{cli-8yy525ad.js → cli-bhjt02p8.js} +2 -2
- package/dist/{cli-v1yw3wha.js → cli-bnz7a7qf.js} +4 -4
- package/dist/{cli-j4kw3rsx.js → cli-cm5yqn35.js} +18 -0
- package/dist/{cli-h55y2bjc.js → cli-dhar9qay.js} +2 -2
- package/dist/{cli-5gr1g711.js → cli-fqzx7gwa.js} +2 -2
- 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-mvckssyq.js → cli-g61qxmge.js} +5 -5
- package/dist/{cli-aq58fk5m.js → cli-g83s9ewh.js} +7 -1
- package/dist/{cli-224epwwf.js → cli-hn9hxmz9.js} +3 -3
- package/dist/{cli-5hjced2v.js → cli-j3jvnpe8.js} +6 -6
- package/dist/{cli-dae4sbgy.js → cli-jm2vfpfd.js} +2 -2
- package/dist/{cli-zn15c46z.js → cli-mjzynrse.js} +1 -1
- package/dist/{cli-a1fhj4n9.js → cli-mmwm7zbf.js} +2 -2
- package/dist/{cli-cq1pf336.js → cli-mry0qzym.js} +5 -5
- package/dist/{cli-bwty35gx.js → cli-mthwvjre.js} +1 -1
- package/dist/{cli-s0exq74r.js → cli-nmbmy64z.js} +3 -3
- package/dist/{cli-wcmnan2g.js → cli-nn4d8dne.js} +5 -5
- package/dist/{cli-jg4p5t36.js → cli-nzxr556s.js} +121 -51
- package/dist/{cli-bcz074zx.js → cli-p599f1qm.js} +1 -1
- package/dist/{cli-k76tgq03.js → cli-pywf3eh5.js} +2 -2
- package/dist/{cli-wk9md5px.js → cli-qxnf8z2z.js} +2 -2
- package/dist/{cli-je8yeyc5.js → cli-rhw40jgb.js} +5 -5
- package/dist/{cli-avgcz4ra.js → cli-rm6hqj3z.js} +406 -35
- package/dist/{cli-0fgr1w9b.js → cli-rpm2sgv0.js} +2 -2
- package/dist/{cli-t282jgab.js → cli-rqkxrmgw.js} +12 -12
- package/dist/{cli-2qpvmmkw.js → cli-s0qnd4wv.js} +1 -1
- package/dist/{cli-pwyevs29.js → cli-spknvxbq.js} +22 -21
- package/dist/{cli-9y354g3j.js → cli-srye3vdt.js} +1 -1
- package/dist/{cli-pe09p6dv.js → cli-tqgg40ek.js} +1 -1
- package/dist/{cli-f255yaqn.js → cli-vbyfwh92.js} +3 -3
- package/dist/{cli-gcbdjt47.js → cli-vfym7wmz.js} +6 -6
- package/dist/{cli-edmm9d9y.js → cli-vqypxzh4.js} +7 -7
- package/dist/{cli-1gm8jcyr.js → cli-wvaawacd.js} +13 -13
- package/dist/{cli-eny54typ.js → cli-wx3rhnm0.js} +1 -1
- package/dist/{cli-b1afgtj3.js → cli-xbkdm710.js} +3 -3
- package/dist/{cli-mc97xy2s.js → cli-xtjhfmpc.js} +16 -4
- package/dist/{cli-eqx6k993.js → cli-y24sc29k.js} +25 -13
- package/dist/{cli-ktmvb20s.js → cli-z6jkx23q.js} +12 -12
- package/dist/{cli-7n5sbw0z.js → cli-z8eh3ex8.js} +5 -5
- package/dist/cli-zh3fp8v2.js +151 -0
- package/dist/{cli-13jvmew3.js → cli-zppwg9p9.js} +7 -7
- package/dist/cli.js +18 -18
- package/dist/{codex-adapter-g0gctw29.js → codex-adapter-q6k3gnyg.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-3mwmk3d9.js} +6 -6
- package/dist/{commands-1p07cvff.js → commands-evrenjn9.js} +2 -2
- package/dist/{controller-z0s7sv6r.js → controller-xjp8vswh.js} +4 -4
- package/dist/{controllers-connect-x9kvvhyk.js → controllers-connect-pp5jnxvc.js} +20 -20
- package/dist/{cursor-adapter-m5b98pvk.js → cursor-adapter-yf47w4t0.js} +2 -2
- package/dist/{db-rsh04kh3.js → db-rgfsh91f.js} +2 -2
- package/dist/{db-capk2raj.js → db-zxpw6aht.js} +3 -3
- package/dist/{devin-adapter-yxm87fe9.js → devin-adapter-r9phbnc9.js} +1 -1
- package/dist/{dispatcher-304q6fwa.js → dispatcher-smasgws7.js} +17 -17
- package/dist/{docker-7rxt0rry.js → docker-rrjhaqt9.js} +2 -2
- package/dist/{dsh-adapter-btewem0y.js → dsh-adapter-ftyy8dnp.js} +2 -2
- package/dist/{e2b-renc0k8q.js → e2b-qcaj675y.js} +2 -2
- package/dist/{e2b-7fc9b9bd.js → e2b-w1p0bnpm.js} +2 -2
- package/dist/{easter-egg-3d-fm1p3n4d.js → easter-egg-3d-yyts7p16.js} +1 -1
- package/dist/{embedding-space-s68v49ct.js → embedding-space-1z0mbg95.js} +6 -6
- package/dist/{embeddings-4ms3604w.js → embeddings-ygeg7tm7.js} +1 -1
- package/dist/{event-bus-dfjx2qdh.js → event-bus-g0ka3pbw.js} +1 -1
- package/dist/{execute-mcdgccjs.js → execute-14qhqp4d.js} +1 -1
- package/dist/{external-channel-9dxwfztn.js → external-channel-ttgrs3sx.js} +2 -2
- package/dist/fx-adapter-gt6pncj2.js +18 -0
- package/dist/{grok-adapter-05ecv4h0.js → grok-adapter-sm4m5k4g.js} +8 -8
- package/dist/handlers-vwmba1m2.js +31 -0
- package/dist/{heartbeat-3n94heh3.js → heartbeat-qahz4p62.js} +20 -20
- package/dist/{hook-zxwje86c.js → hook-4fd5wq5w.js} +4 -4
- package/dist/{hook-aadrn69x.js → hook-kj3d398m.js} +6 -6
- package/dist/{http-xzxk9mst.js → http-1ted0j1k.js} +100 -64
- package/dist/index-5r71rf98.js +32 -0
- package/dist/{index-d34f6r87.js → index-6rq3r5zb.js} +1 -1
- package/dist/{index-9j4zwcht.js → index-7v51awbn.js} +158 -672
- package/dist/index-anmwxs87.js +29 -0
- package/dist/{index-qeb82z37.js → index-q4b917zs.js} +1 -1
- package/dist/index-spp0ra5c.js +36 -0
- package/dist/{keepalive-w4aakbyk.js → keepalive-6cf311t5.js} +4 -4
- package/dist/lead-fjy418cw.js +69 -0
- package/dist/{maintenance-hbptpk6p.js → maintenance-a4bm1bmt.js} +1 -1
- package/dist/{oauth-refresh-sweep-217semrv.js → oauth-refresh-sweep-3h0dfw1k.js} +3 -3
- package/dist/{onboard-vz2s5df3.js → onboard-myn5hraa.js} +3 -3
- package/dist/{opencode-adapter-gtfd7d7v.js → opencode-adapter-p2nkb5am.js} +5 -5
- package/dist/{otel-impl-aefave37.js → otel-impl-a1a0ef6b.js} +2 -2
- package/dist/{pi-mono-adapter-f8h5yseh.js → pi-mono-adapter-rzmpb72a.js} +117 -23
- package/dist/{pricing-refresh-m1yhh9ya.js → pricing-refresh-83drrnxj.js} +3 -3
- package/dist/{rbac-roles-svj72cdf.js → rbac-roles-0fb6e7pj.js} +2 -2
- package/dist/{rbac-roles-rrw8twdz.js → rbac-roles-s8y6ecb5.js} +1 -1
- package/dist/{render-v2-amwbnfxh.js → render-v2-twmbgrz4.js} +3 -3
- package/dist/{resume-9cd6wf6z.js → resume-zfqc36xj.js} +7 -7
- package/dist/{runtime-fp4vrx7m.js → runtime-qwggdpqr.js} +1 -1
- package/dist/{session-access-21t6yjyg.js → session-access-sc9pn9da.js} +4 -4
- package/dist/{session-source-backfill-pxbzcsc6.js → session-source-backfill-qh07xr8g.js} +3 -3
- package/dist/{setup-4592tqz4.js → setup-4fr0m7a1.js} +1 -1
- package/dist/{virtual-modules-gzfnpmnz.js → virtual-modules-fh4ha15b.js} +2 -2
- package/dist/{work-objects-t9tcf174.js → work-objects-0wr8zxct.js} +20 -20
- package/dist/worker-0c5tyzqe.js +69 -0
- package/dist/{worker-follow-up-q32jh4yk.js → worker-follow-up-ktyyazyp.js} +17 -17
- package/dist/{x-b6wxntdt.js → x-vmmye1kb.js} +2 -2
- package/openapi.json +97 -32
- package/package.json +1 -1
- 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.ts +8 -4
- package/src/be/migrations/259_session_summaries_open_tasks.sql +111 -0
- package/src/be/model-validation.ts +5 -1
- package/src/be/pricing-normalize.ts +7 -2
- package/src/be/scripts/typecheck.ts +1 -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/steering.ts +50 -3
- package/src/be/swarm-config-guard.ts +1 -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/mcp-bridge.ts +4 -0
- 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/scheduler/scheduler.ts +36 -3
- package/src/scripts-runtime/sdk-allowlist.ts +1 -0
- package/src/scripts-runtime/types/stdlib.d.ts +5 -0
- package/src/scripts-runtime/types/swarm-extension.d.ts +33 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +5 -0
- package/src/server.ts +7 -0
- package/src/slack/assistant.ts +17 -1
- package/src/slack/handlers.ts +18 -5
- package/src/slack/left-threads.ts +65 -0
- package/src/slack/steering.ts +3 -0
- package/src/slack/thread-buffer.ts +40 -16
- 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/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/explicit-model-validation.test.ts +99 -0
- 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/fx-adapter.test.ts +771 -0
- package/src/tests/grok-adapter.test.ts +36 -0
- package/src/tests/mcp-loopback-proxy.test.ts +193 -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 +3 -2
- package/src/tests/script-connections.test.ts +2 -0
- package/src/tests/seed-model-profiles.test.ts +2 -2
- package/src/tests/seed-schedules.test.ts +138 -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 +431 -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/slack-leave-thread.ts +104 -0
- package/src/tools/steer-task.ts +3 -2
- package/src/tools/tool-config.ts +2 -1
- package/src/types.ts +19 -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/swarm-docs/content.md +1 -0
- package/templates/workflows/dreaming/content.md +5 -1
- package/dist/acp-adapter-96dc64m2.js +0 -15
- 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
|
@@ -40,8 +40,9 @@ const ROUTING_PREFIXES_BY_PROVIDER: Record<PricingProvider, readonly string[]> =
|
|
|
40
40
|
// openai, … — strip whichever proxy prefix the user picked.
|
|
41
41
|
opencode: ["openrouter/", "github-copilot/"],
|
|
42
42
|
// pi-mono can hit openrouter mirrors, the github-copilot proxy, or native
|
|
43
|
-
// anthropic/openai/google providers.
|
|
44
|
-
|
|
43
|
+
// anthropic/openai/google providers. `openai-codex/<id>` is an OpenAI id
|
|
44
|
+
// served by the ChatGPT subscription pool, priced like codex on that pool.
|
|
45
|
+
pi: ["openrouter/", "github-copilot/", "openai-codex/"],
|
|
45
46
|
// codex normally reports a bare id, but a user may set MODEL_OVERRIDE to a
|
|
46
47
|
// prefixed form. Be forgiving on the lookup side.
|
|
47
48
|
codex: ["openai/", "github-copilot/"],
|
|
@@ -69,6 +70,10 @@ const ROUTING_PREFIXES_BY_PROVIDER: Record<PricingProvider, readonly string[]> =
|
|
|
69
70
|
// The Grok CLI reports a bare xAI id (`grok-4.6`) or `openrouter/<vendor>/<id>`
|
|
70
71
|
// for an OpenRouter model; stripping the prefix lands it on the openrouter rows.
|
|
71
72
|
grok: ["openrouter/", "xai/"],
|
|
73
|
+
// fx reports the swarm model string: `openrouter/<vendor>/<id>` (OpenRouter
|
|
74
|
+
// connection), `vercel/<vendor>/<id>` (AI Gateway) or a bare OpenAI id.
|
|
75
|
+
// Stripping the route prefix lands the first two on `<vendor>/<id>` rows.
|
|
76
|
+
fx: ["openrouter/", "vercel/"],
|
|
72
77
|
};
|
|
73
78
|
|
|
74
79
|
/** `gpt-5-nano-2025-08-07` -> `gpt-5-nano`. Anthropic's 8-digit dates (`-20251001`) are real catalog ids and are left alone. */
|
|
@@ -315,6 +315,7 @@ export interface SwarmSdk {
|
|
|
315
315
|
slack_downloadFile(args: { url: string }): Promise<unknown>;
|
|
316
316
|
slack_delete(args: { channelId: string; messageTs: string }): Promise<unknown>;
|
|
317
317
|
slack_update(args: { channelId: string; messageTs: string; message: string }): Promise<unknown>;
|
|
318
|
+
slack_leaveThread(args: { taskId?: string; channelId?: string; threadTs?: string }): Promise<unknown>;
|
|
318
319
|
|
|
319
320
|
// --- write: chat sdk ---
|
|
320
321
|
chat_reply(args: { text: string }): Promise<unknown>;
|
|
@@ -76,8 +76,11 @@ import {
|
|
|
76
76
|
computeContentHash,
|
|
77
77
|
createScheduledTask,
|
|
78
78
|
getScheduledTaskByName,
|
|
79
|
+
getWorkflow,
|
|
80
|
+
listWorkflows,
|
|
79
81
|
updateScheduledTask,
|
|
80
82
|
} from "../db";
|
|
83
|
+
import { getScript } from "../scripts/db";
|
|
81
84
|
import { resolveSeededEnabled } from "./automation-toggle";
|
|
82
85
|
import type { Seeder, SeedItem } from "./types";
|
|
83
86
|
import { canonicalJson } from "./workflows-seeder";
|
|
@@ -98,8 +101,27 @@ type ScheduleBlock = {
|
|
|
98
101
|
cron: string;
|
|
99
102
|
timezone?: string;
|
|
100
103
|
enabled?: boolean;
|
|
104
|
+
/** What a fire runs. Default `agent-task`: the `## Scheduled Task` prompt becomes a task. */
|
|
105
|
+
targetType?: ScheduleTargetType;
|
|
106
|
+
/** `targetType: "workflow"`: the workflow's name, resolved to its id at apply time. */
|
|
107
|
+
workflow?: string;
|
|
108
|
+
/** `targetType: "script"`: a global script, run directly with `scriptArgs`. */
|
|
109
|
+
scriptName?: string;
|
|
110
|
+
scriptArgs?: Record<string, unknown>;
|
|
101
111
|
};
|
|
102
112
|
|
|
113
|
+
type ScheduleTargetType = "agent-task" | "workflow" | "script";
|
|
114
|
+
const TARGET_TYPES: readonly ScheduleTargetType[] = ["agent-task", "workflow", "script"];
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* The non-default target of a seeded schedule. Agent-task schedules carry none
|
|
118
|
+
* of these keys, so their seed hash is unchanged from before targets existed
|
|
119
|
+
* and recorded seed state on existing installs still matches.
|
|
120
|
+
*/
|
|
121
|
+
type SeedScheduleTarget =
|
|
122
|
+
| { targetType: "workflow"; workflow: string }
|
|
123
|
+
| { targetType: "script"; scriptName: string; scriptArgs: Record<string, unknown> };
|
|
124
|
+
|
|
103
125
|
export type SeedSchedule = {
|
|
104
126
|
name: string;
|
|
105
127
|
description: string;
|
|
@@ -112,7 +134,7 @@ export type SeedSchedule = {
|
|
|
112
134
|
params: Record<string, unknown>;
|
|
113
135
|
requiredParams: string[];
|
|
114
136
|
requires: AutomationIntegrationId[];
|
|
115
|
-
};
|
|
137
|
+
} & (SeedScheduleTarget | { targetType?: undefined });
|
|
116
138
|
|
|
117
139
|
export type ScheduleTemplateSource = { config: string; content: string };
|
|
118
140
|
type ScheduleSeedItem = SeedItem & { schedule: SeedSchedule };
|
|
@@ -167,10 +189,21 @@ function parseScheduleSource(source: ScheduleTemplateSource): SeedSchedule | nul
|
|
|
167
189
|
const block = JSON.parse(blockMatch[1]) as ScheduleBlock;
|
|
168
190
|
const afterBlock = scheduleStart + blockMatch.index! + blockMatch[0].length;
|
|
169
191
|
const taskHeading = source.content.indexOf("## Scheduled Task", afterBlock);
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
192
|
+
const target = parseScheduleTarget(config.name, block);
|
|
193
|
+
let taskTemplate = "";
|
|
194
|
+
if (target) {
|
|
195
|
+
// A workflow or script fire never reads a prompt: shipping one is a mistake.
|
|
196
|
+
if (taskHeading >= 0) {
|
|
197
|
+
throw new Error(
|
|
198
|
+
`Schedule template ${config.name} targets a ${target.targetType} but ships a ## Scheduled Task prompt it would ignore`,
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
} else {
|
|
202
|
+
taskTemplate = source.content
|
|
203
|
+
.slice(taskHeading >= 0 ? taskHeading + "## Scheduled Task".length : afterBlock)
|
|
204
|
+
.trim();
|
|
205
|
+
if (!taskTemplate) throw new Error(`Schedule template ${config.name} has no task prompt`);
|
|
206
|
+
}
|
|
174
207
|
|
|
175
208
|
const requires = config.requires ?? [];
|
|
176
209
|
const requiredParams = config.placeholders ?? [];
|
|
@@ -196,7 +229,37 @@ function parseScheduleSource(source: ScheduleTemplateSource): SeedSchedule | nul
|
|
|
196
229
|
params: {},
|
|
197
230
|
requiredParams,
|
|
198
231
|
requires,
|
|
232
|
+
...target,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** Validate the block's target fields. Returns null for the default agent-task target. */
|
|
237
|
+
function parseScheduleTarget(name: string, block: ScheduleBlock): SeedScheduleTarget | null {
|
|
238
|
+
const targetType = block.targetType ?? "agent-task";
|
|
239
|
+
if (!TARGET_TYPES.includes(targetType)) {
|
|
240
|
+
throw new Error(`Schedule template ${name} has unknown targetType "${targetType}"`);
|
|
241
|
+
}
|
|
242
|
+
const stray = (fields: (keyof ScheduleBlock)[]) => {
|
|
243
|
+
const set = fields.filter((field) => block[field] !== undefined);
|
|
244
|
+
if (set.length > 0) {
|
|
245
|
+
throw new Error(
|
|
246
|
+
`Schedule template ${name} (targetType ${targetType}) sets ${set.join(", ")}, which it would ignore`,
|
|
247
|
+
);
|
|
248
|
+
}
|
|
199
249
|
};
|
|
250
|
+
switch (targetType) {
|
|
251
|
+
case "agent-task":
|
|
252
|
+
stray(["workflow", "scriptName", "scriptArgs"]);
|
|
253
|
+
return null;
|
|
254
|
+
case "workflow":
|
|
255
|
+
stray(["scriptName", "scriptArgs"]);
|
|
256
|
+
if (!block.workflow) throw new Error(`Schedule template ${name} has no workflow name`);
|
|
257
|
+
return { targetType, workflow: block.workflow };
|
|
258
|
+
case "script":
|
|
259
|
+
stray(["workflow"]);
|
|
260
|
+
if (!block.scriptName) throw new Error(`Schedule template ${name} has no scriptName`);
|
|
261
|
+
return { targetType, scriptName: block.scriptName, scriptArgs: block.scriptArgs ?? {} };
|
|
262
|
+
}
|
|
200
263
|
}
|
|
201
264
|
|
|
202
265
|
export function loadSeedSchedules(
|
|
@@ -212,7 +275,27 @@ function scheduleSeedHash(schedule: SeedSchedule): string {
|
|
|
212
275
|
return computeContentHash(canonicalJson(schedule));
|
|
213
276
|
}
|
|
214
277
|
|
|
215
|
-
function
|
|
278
|
+
async function scheduleTargetFromRow(
|
|
279
|
+
schedule: ScheduledTask,
|
|
280
|
+
): Promise<SeedScheduleTarget | undefined> {
|
|
281
|
+
switch (schedule.targetType) {
|
|
282
|
+
case "workflow": {
|
|
283
|
+
const workflow = schedule.workflowId ? await getWorkflow(schedule.workflowId) : null;
|
|
284
|
+
// A dangling id can never equal a seeded name, so the row reads as edited.
|
|
285
|
+
return { targetType: "workflow", workflow: workflow?.name ?? `id:${schedule.workflowId}` };
|
|
286
|
+
}
|
|
287
|
+
case "script":
|
|
288
|
+
return {
|
|
289
|
+
targetType: "script",
|
|
290
|
+
scriptName: schedule.scriptName ?? "",
|
|
291
|
+
scriptArgs: schedule.scriptArgs ?? {},
|
|
292
|
+
};
|
|
293
|
+
default:
|
|
294
|
+
return undefined;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
async function scheduleFromRow(schedule: ScheduledTask): Promise<SeedSchedule> {
|
|
216
299
|
return {
|
|
217
300
|
name: schedule.name,
|
|
218
301
|
description: schedule.description ?? "",
|
|
@@ -225,9 +308,48 @@ function scheduleFromRow(schedule: ScheduledTask): SeedSchedule {
|
|
|
225
308
|
params: schedule.params ?? {},
|
|
226
309
|
requiredParams: schedule.requiredParams ?? [],
|
|
227
310
|
requires: schedule.requires ?? [],
|
|
311
|
+
...(await scheduleTargetFromRow(schedule)),
|
|
228
312
|
};
|
|
229
313
|
}
|
|
230
314
|
|
|
315
|
+
/** Resolve a seeded target to row fields, failing the item when its target is missing. */
|
|
316
|
+
async function resolveTargetFields(schedule: SeedSchedule) {
|
|
317
|
+
switch (schedule.targetType) {
|
|
318
|
+
case "workflow": {
|
|
319
|
+
const name = schedule.workflow;
|
|
320
|
+
const workflow = (await listWorkflows()).find((candidate) => candidate.name === name);
|
|
321
|
+
if (!workflow) throw new Error(`Workflow "${name}" not found`);
|
|
322
|
+
return {
|
|
323
|
+
targetType: "workflow" as const,
|
|
324
|
+
workflowId: workflow.id,
|
|
325
|
+
scriptName: null,
|
|
326
|
+
scriptArgs: null,
|
|
327
|
+
taskTemplate: null,
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
case "script": {
|
|
331
|
+
if (!(await getScript({ name: schedule.scriptName, scope: "global" }))) {
|
|
332
|
+
throw new Error(`Global script "${schedule.scriptName}" not found`);
|
|
333
|
+
}
|
|
334
|
+
return {
|
|
335
|
+
targetType: "script" as const,
|
|
336
|
+
workflowId: null,
|
|
337
|
+
scriptName: schedule.scriptName,
|
|
338
|
+
scriptArgs: schedule.scriptArgs,
|
|
339
|
+
taskTemplate: null,
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
default:
|
|
343
|
+
return {
|
|
344
|
+
targetType: "agent-task" as const,
|
|
345
|
+
workflowId: null,
|
|
346
|
+
scriptName: null,
|
|
347
|
+
scriptArgs: null,
|
|
348
|
+
taskTemplate: schedule.taskTemplate,
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
231
353
|
function nextRunAt(schedule: SeedSchedule): string | null {
|
|
232
354
|
try {
|
|
233
355
|
// Disabled templates can await a required timezone parameter. Validate the
|
|
@@ -263,7 +385,7 @@ export function createSchedulesSeeder(
|
|
|
263
385
|
|
|
264
386
|
async upstreamHash(item): Promise<string | null> {
|
|
265
387
|
const existing = await getScheduledTaskByName(item.key);
|
|
266
|
-
return existing ? scheduleSeedHash(scheduleFromRow(existing)) : null;
|
|
388
|
+
return existing ? scheduleSeedHash(await scheduleFromRow(existing)) : null;
|
|
267
389
|
},
|
|
268
390
|
|
|
269
391
|
async apply(item): Promise<void> {
|
|
@@ -274,8 +396,7 @@ export function createSchedulesSeeder(
|
|
|
274
396
|
cronExpression: schedule.cronExpression,
|
|
275
397
|
timezone: schedule.timezone,
|
|
276
398
|
enabled: schedule.enabled,
|
|
277
|
-
|
|
278
|
-
taskTemplate: schedule.taskTemplate,
|
|
399
|
+
...(await resolveTargetFields(schedule)),
|
|
279
400
|
taskType: schedule.taskType,
|
|
280
401
|
tags: schedule.tags,
|
|
281
402
|
params: schedule.params,
|
|
@@ -284,11 +405,16 @@ export function createSchedulesSeeder(
|
|
|
284
405
|
};
|
|
285
406
|
const existing = await getScheduledTaskByName(schedule.name);
|
|
286
407
|
if (existing) {
|
|
408
|
+
// Nulls clear the previous target's fields when a template changes target.
|
|
287
409
|
await updateScheduledTask(existing.id, { ...data, nextRunAt: resolvedNextRunAt });
|
|
288
410
|
} else {
|
|
289
411
|
await createScheduledTask({
|
|
290
412
|
name: schedule.name,
|
|
291
413
|
...data,
|
|
414
|
+
workflowId: data.workflowId ?? undefined,
|
|
415
|
+
scriptName: data.scriptName ?? undefined,
|
|
416
|
+
scriptArgs: data.scriptArgs ?? undefined,
|
|
417
|
+
taskTemplate: data.taskTemplate ?? undefined,
|
|
292
418
|
nextRunAt: resolvedNextRunAt ?? undefined,
|
|
293
419
|
});
|
|
294
420
|
}
|
|
@@ -71,10 +71,18 @@ import type {
|
|
|
71
71
|
TriggerConfig,
|
|
72
72
|
Workflow,
|
|
73
73
|
WorkflowDefinition,
|
|
74
|
+
WorkflowSnapshot,
|
|
74
75
|
} from "../../types";
|
|
75
76
|
import { validateDefinition } from "../../workflows/definition";
|
|
76
|
-
import {
|
|
77
|
+
import {
|
|
78
|
+
computeContentHash,
|
|
79
|
+
createWorkflow,
|
|
80
|
+
getWorkflowVersions,
|
|
81
|
+
listWorkflows,
|
|
82
|
+
updateWorkflow,
|
|
83
|
+
} from "../db";
|
|
77
84
|
import { resolveSeededEnabled } from "./automation-toggle";
|
|
85
|
+
import { getSeedState } from "./state-db";
|
|
78
86
|
import type { Seeder, SeedItem } from "./types";
|
|
79
87
|
|
|
80
88
|
type AutomationTemplateConfig = {
|
|
@@ -220,6 +228,62 @@ function workflowFromRow(workflow: Workflow): SeedWorkflow {
|
|
|
220
228
|
};
|
|
221
229
|
}
|
|
222
230
|
|
|
231
|
+
function workflowFromSnapshot(snapshot: WorkflowSnapshot): SeedWorkflow {
|
|
232
|
+
return {
|
|
233
|
+
name: snapshot.name,
|
|
234
|
+
description: snapshot.description ?? "",
|
|
235
|
+
enabled: snapshot.enabled,
|
|
236
|
+
definition: snapshot.definition,
|
|
237
|
+
triggers: snapshot.triggers,
|
|
238
|
+
cooldown: snapshot.cooldown,
|
|
239
|
+
input: snapshot.input,
|
|
240
|
+
triggerSchema: snapshot.triggerSchema,
|
|
241
|
+
params: snapshot.params ?? {},
|
|
242
|
+
requiredParams: snapshot.requiredParams ?? [],
|
|
243
|
+
requires: snapshot.requires ?? [],
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* What a run executes, without the settings an operator is told to change:
|
|
249
|
+
* inputs (plain data, resolved the same for every caller), on/off, cooldown,
|
|
250
|
+
* description and event triggers.
|
|
251
|
+
*/
|
|
252
|
+
function executableSurface(workflow: SeedWorkflow): string {
|
|
253
|
+
const {
|
|
254
|
+
input: _input,
|
|
255
|
+
enabled: _enabled,
|
|
256
|
+
cooldown: _cooldown,
|
|
257
|
+
description: _description,
|
|
258
|
+
triggers: _triggers,
|
|
259
|
+
...surface
|
|
260
|
+
} = workflow;
|
|
261
|
+
return canonicalJson(surface);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Whether a live workflow still runs seed source: `pristine` while what it
|
|
266
|
+
* executes matches what the seeder last wrote for its name, `edited` once
|
|
267
|
+
* anyone changed that, `unseeded` for a workflow the seeder never wrote.
|
|
268
|
+
* Operator settings (see `executableSurface`) keep it `pristine`. The seeded
|
|
269
|
+
* state is the live row itself or, once edited, the version snapshot taken
|
|
270
|
+
* before the first edit; an edit with no such snapshot counts as `edited`.
|
|
271
|
+
*/
|
|
272
|
+
export async function seededWorkflowState(
|
|
273
|
+
workflow: Workflow,
|
|
274
|
+
): Promise<"pristine" | "edited" | "unseeded"> {
|
|
275
|
+
const state = await getSeedState("workflow", workflow.name);
|
|
276
|
+
if (!state) return "unseeded";
|
|
277
|
+
const live = workflowFromRow(workflow);
|
|
278
|
+
if (workflowSeedHash(live) === state.seededHash) return "pristine";
|
|
279
|
+
for (const version of await getWorkflowVersions(workflow.id)) {
|
|
280
|
+
const seeded = workflowFromSnapshot(version.snapshot);
|
|
281
|
+
if (workflowSeedHash(seeded) !== state.seededHash) continue;
|
|
282
|
+
return executableSurface(seeded) === executableSurface(live) ? "pristine" : "edited";
|
|
283
|
+
}
|
|
284
|
+
return "edited";
|
|
285
|
+
}
|
|
286
|
+
|
|
223
287
|
export function createWorkflowsSeeder(
|
|
224
288
|
sources: readonly WorkflowTemplateSource[] = BUILT_IN_WORKFLOW_SOURCES,
|
|
225
289
|
): Seeder<WorkflowSeedItem> {
|
package/src/be/seed-pricing.ts
CHANGED
|
@@ -299,6 +299,8 @@ export function buildModelsDevSeedRows(cache: ModelsDevCache): PricingSeedRow[]
|
|
|
299
299
|
// strips the prefix, so we seed the bare id under `pi` too. Without this
|
|
300
300
|
// every gh-copilot-backed pi run fell through to `costSource='unpriced'`.
|
|
301
301
|
add(id, projectCostBlock("pi", id, model.cost));
|
|
302
|
+
// fx runs a bare OpenAI id on its `openai` custom connection.
|
|
303
|
+
add(id, projectCostBlock("fx", id, model.cost));
|
|
302
304
|
}
|
|
303
305
|
|
|
304
306
|
// ---- Cursor (bare vendor ids through Cursor's hosted inference) --------
|
|
@@ -358,6 +360,13 @@ export function buildModelsDevSeedRows(cache: ModelsDevCache): PricingSeedRow[]
|
|
|
358
360
|
anthropicBilled: id.startsWith("anthropic/"),
|
|
359
361
|
}),
|
|
360
362
|
);
|
|
363
|
+
// fx runs `openrouter/<id>` on its OpenRouter custom connection.
|
|
364
|
+
add(
|
|
365
|
+
id,
|
|
366
|
+
projectCostBlock("fx", id, model.cost, {
|
|
367
|
+
anthropicBilled: id.startsWith("anthropic/"),
|
|
368
|
+
}),
|
|
369
|
+
);
|
|
361
370
|
// Gemini specifically: also project under the 'gemini' provider so
|
|
362
371
|
// internal-ai callers that tag with provider='gemini' find a hit.
|
|
363
372
|
if (id.startsWith("google/")) {
|
|
@@ -369,6 +378,20 @@ export function buildModelsDevSeedRows(cache: ModelsDevCache): PricingSeedRow[]
|
|
|
369
378
|
}
|
|
370
379
|
}
|
|
371
380
|
|
|
381
|
+
// ---- fx on Vercel AI Gateway (`vercel/<vendor>/<id>`) -------------------
|
|
382
|
+
// The prefix is stripped at lookup, so a gateway id lands on the same key as
|
|
383
|
+
// its OpenRouter twin. OpenRouter's row wins where both list the model (one
|
|
384
|
+
// source per key); the gateway section fills the ids OpenRouter lacks.
|
|
385
|
+
for (const [id, model] of Object.entries(cache.vercel?.models ?? {})) {
|
|
386
|
+
if (!model?.cost || openrouter[id]?.cost) continue;
|
|
387
|
+
add(
|
|
388
|
+
id,
|
|
389
|
+
projectCostBlock("fx", id, model.cost, {
|
|
390
|
+
anthropicBilled: id.startsWith("anthropic/"),
|
|
391
|
+
}),
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
|
|
372
395
|
// ---- OpenAI-compatible registry providers ($0 flat-rate subscriptions) --
|
|
373
396
|
// pi and opencode run these as `<provider>/<id>`. The models.dev `nan`
|
|
374
397
|
// section and the registry-synthesized `helmcode` section
|
package/src/be/steering.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { TaskSteerEvent } from "../extensions/contract";
|
|
1
3
|
import {
|
|
2
4
|
type Agent,
|
|
3
5
|
type AgentTask,
|
|
@@ -37,6 +39,20 @@ export class SteeringRequestError extends Error {
|
|
|
37
39
|
}
|
|
38
40
|
}
|
|
39
41
|
|
|
42
|
+
export class SteeringBlockedError extends SteeringRequestError {
|
|
43
|
+
constructor(
|
|
44
|
+
public readonly reason: string,
|
|
45
|
+
public readonly extension: { id: string; name: string },
|
|
46
|
+
) {
|
|
47
|
+
super(`Steering blocked by extension ${extension.name}: ${reason}`, 403);
|
|
48
|
+
this.name = "SteeringBlockedError";
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const steeringModifySchema = z.strictObject({
|
|
53
|
+
message: z.string().refine((message) => message.trim().length > 0, "Message must not be empty"),
|
|
54
|
+
});
|
|
55
|
+
|
|
40
56
|
/** Server-side entry point for the global steering kill switch. */
|
|
41
57
|
export function isSteeringEnabled(): boolean {
|
|
42
58
|
return readSteeringEnabled();
|
|
@@ -51,6 +67,8 @@ export interface RequestSteeringArgs {
|
|
|
51
67
|
createdByKind?: SteeringMessage["createdByKind"];
|
|
52
68
|
createdByUserId?: string;
|
|
53
69
|
createdByAgentId?: string;
|
|
70
|
+
messageTimestamps?: string[];
|
|
71
|
+
slack?: TaskSteerEvent["slack"];
|
|
54
72
|
}
|
|
55
73
|
|
|
56
74
|
async function providerForTask(task: AgentTask): Promise<ProviderName> {
|
|
@@ -291,7 +309,36 @@ export async function requestSteering(args: RequestSteeringArgs): Promise<SteerR
|
|
|
291
309
|
|
|
292
310
|
const requestedMode = args.mode ?? "queue";
|
|
293
311
|
const onUnsupported = args.onUnsupported ?? "degrade";
|
|
294
|
-
|
|
312
|
+
// The dispatcher loads task/workflow assets; keep it off the HTTP boot import cycle.
|
|
313
|
+
const { dispatchPre, extensionIdForAgent } = await import("../extensions/dispatcher");
|
|
314
|
+
const preSteer = await dispatchPre(
|
|
315
|
+
"pre.task.steer",
|
|
316
|
+
{
|
|
317
|
+
task,
|
|
318
|
+
message: args.message,
|
|
319
|
+
mode: requestedMode,
|
|
320
|
+
source: args.source ?? "api",
|
|
321
|
+
sender: {
|
|
322
|
+
kind: args.createdByKind ?? "system",
|
|
323
|
+
userId: args.createdByUserId,
|
|
324
|
+
agentId: args.createdByAgentId,
|
|
325
|
+
},
|
|
326
|
+
messageTimestamps: args.messageTimestamps,
|
|
327
|
+
slack: args.slack,
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
skipExtensionId: extensionIdForAgent(args.createdByAgentId),
|
|
331
|
+
validateModify: (data) => steeringModifySchema.safeParse(data),
|
|
332
|
+
},
|
|
333
|
+
);
|
|
334
|
+
if (preSteer.action === "block") {
|
|
335
|
+
throw new SteeringBlockedError(preSteer.reason, preSteer.extension);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// A hook can outlive the running session. Decide delivery from the current task.
|
|
339
|
+
const currentTask = await getTaskById(args.taskId);
|
|
340
|
+
if (!currentTask) throw new SteeringRequestError("Task not found", 404);
|
|
341
|
+
const provider = await providerForTask(currentTask);
|
|
295
342
|
const supportedModes = PROVIDER_STEER_CAPABILITIES[provider];
|
|
296
343
|
|
|
297
344
|
if (onUnsupported === "fail" && !supportedModes.includes(requestedMode)) {
|
|
@@ -302,12 +349,12 @@ export async function requestSteering(args: RequestSteeringArgs): Promise<SteerR
|
|
|
302
349
|
);
|
|
303
350
|
}
|
|
304
351
|
|
|
305
|
-
const activeTask =
|
|
352
|
+
const activeTask = currentTask.status === "paused" ? await resumeTask(task.id) : currentTask;
|
|
306
353
|
if (!activeTask) {
|
|
307
354
|
throw new SteeringRequestError("Failed to resume paused task", 500);
|
|
308
355
|
}
|
|
309
356
|
|
|
310
|
-
const body = scrubSecrets(args.message);
|
|
357
|
+
const body = scrubSecrets(preSteer.action === "modify" ? preSteer.data.message : args.message);
|
|
311
358
|
// A task whose session hasn't started yet can still accept queued steering:
|
|
312
359
|
// the row stays `pending` and the worker delivers it once the session is
|
|
313
360
|
// live (the dispatch poll covers every active task). Interrupting a session
|
|
@@ -508,6 +508,7 @@ const VALIDATED_KEYS: Record<string, ConfigValidator> = {
|
|
|
508
508
|
"ADDITIVE_SLACK",
|
|
509
509
|
"SLACK_THREAD_FOLLOWUP_REQUIRE_MENTION",
|
|
510
510
|
"SLACK_THREAD_WAKE_ON_ANY_MESSAGE",
|
|
511
|
+
"SLACK_LEAVE_THREAD_DISABLE",
|
|
511
512
|
"RBAC_ENABLED",
|
|
512
513
|
"SEED_AUTOMATIONS_ENABLED",
|
|
513
514
|
"RBAC_AUDIT_DISABLED",
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
|
|
21
21
|
import {
|
|
22
22
|
openAICompatibleKeyVars,
|
|
23
|
+
openAICompatibleProvider,
|
|
23
24
|
openAICompatibleProviders,
|
|
24
25
|
providerBaseUrl,
|
|
25
26
|
providerKeyFromEnv,
|
|
@@ -34,6 +35,7 @@ import { hasCodexOAuthPoolSlots } from "../providers/codex-oauth/env-keys";
|
|
|
34
35
|
import { checkCursorCredentials } from "../providers/cursor-adapter";
|
|
35
36
|
import { checkDevinCredentials } from "../providers/devin-adapter";
|
|
36
37
|
import { checkDshCredentials } from "../providers/dsh-adapter";
|
|
38
|
+
import { checkFxCredentials } from "../providers/fx-adapter";
|
|
37
39
|
import { checkGrokCredentials } from "../providers/grok-adapter";
|
|
38
40
|
import { checkOpencodeCredentials } from "../providers/opencode-adapter";
|
|
39
41
|
import type { CredCheckOptions, CredStatus } from "../providers/types";
|
|
@@ -85,7 +87,8 @@ export type SupportedProvider =
|
|
|
85
87
|
| "dsh"
|
|
86
88
|
| "cursor"
|
|
87
89
|
| "amp"
|
|
88
|
-
| "grok"
|
|
90
|
+
| "grok"
|
|
91
|
+
| "fx";
|
|
89
92
|
|
|
90
93
|
/**
|
|
91
94
|
* True when the pi harness authenticates against Bedrock rather than a provider
|
|
@@ -158,6 +161,7 @@ export const REQUIRED_CRED_VARS_BY_PROVIDER: Record<SupportedProvider, readonly
|
|
|
158
161
|
amp: ["AMP_API_KEY"],
|
|
159
162
|
cursor: ["CURSOR_API_KEY"],
|
|
160
163
|
grok: ["XAI_API_KEY", "OPENROUTER_API_KEY"],
|
|
164
|
+
fx: ["OPENROUTER_API_KEY", "OPENAI_API_KEY", "AI_GATEWAY_API_KEY"],
|
|
161
165
|
};
|
|
162
166
|
|
|
163
167
|
type CredentialChecker = (
|
|
@@ -171,6 +175,7 @@ export const CREDENTIAL_PROVIDER_CHECKERS: Record<SupportedProvider, CredentialC
|
|
|
171
175
|
amp: (env) => checkAmpCredentials(env),
|
|
172
176
|
cursor: (env) => checkCursorCredentials(env),
|
|
173
177
|
grok: (env) => checkGrokCredentials(env),
|
|
178
|
+
fx: (env) => checkFxCredentials(env),
|
|
174
179
|
claude: (env) => checkClaudeCredentials(env),
|
|
175
180
|
"claude-managed": (env) => checkClaudeManagedCredentials(env),
|
|
176
181
|
codex: (env, opts) => checkCodexCredentials(env, opts),
|
|
@@ -383,6 +388,7 @@ function parseCodexOAuthAccess(blob: string | undefined): string | null {
|
|
|
383
388
|
* | `devin` | `DEVIN_API_KEY` (+ `DEVIN_API_BASE_URL` override) | `${baseUrl}/v3/self` |
|
|
384
389
|
* | `dsh` | `DEEPSEEK_API_KEY` → `OPENROUTER_API_KEY` | DeepSeek `/models`, or OpenRouter `/key` |
|
|
385
390
|
* | `amp` | `AMP_API_KEY` | `amp usage` (credit balance, no inference; 5s timeout) |
|
|
391
|
+
* | `fx` | `OPENROUTER_API_KEY` → `OPENAI_API_KEY` → `AI_GATEWAY_API_KEY` | OpenRouter `/key`, OpenAI `/v1/models`, or AI Gateway `/v1/models` |
|
|
386
392
|
*
|
|
387
393
|
* Returns `{ok: true, latency_ms}` on 2xx, `{ok: false, error, latency_ms}`
|
|
388
394
|
* otherwise. Errors are scrubbed via `scrubSecrets` before being returned.
|
|
@@ -639,12 +645,33 @@ export async function validateProviderCredentials(
|
|
|
639
645
|
latency_ms: Date.now() - startedAt,
|
|
640
646
|
};
|
|
641
647
|
}
|
|
648
|
+
case "fx": {
|
|
649
|
+
// Same keys as `checkFxCredentials`, probed in route-table order.
|
|
650
|
+
const openRouterKey = env.OPENROUTER_API_KEY?.trim();
|
|
651
|
+
if (openRouterKey) {
|
|
652
|
+
return checkEachKey("OPENROUTER_API_KEY", openRouterKey, checkOpenRouter);
|
|
653
|
+
}
|
|
654
|
+
const openAiKey = env.OPENAI_API_KEY?.trim();
|
|
655
|
+
if (openAiKey) return checkEachKey("OPENAI_API_KEY", openAiKey, checkOpenAiApiKey);
|
|
656
|
+
const gatewayKey = env.AI_GATEWAY_API_KEY?.trim();
|
|
657
|
+
const gateway = openAICompatibleProvider("vercel");
|
|
658
|
+
if (gatewayKey && gateway) {
|
|
659
|
+
return checkEachKey("AI_GATEWAY_API_KEY", gatewayKey, (member) =>
|
|
660
|
+
checkOpenAICompatibleProvider(gateway, member),
|
|
661
|
+
);
|
|
662
|
+
}
|
|
663
|
+
return {
|
|
664
|
+
ok: false,
|
|
665
|
+
error: "Set OPENROUTER_API_KEY, OPENAI_API_KEY or AI_GATEWAY_API_KEY for fx.",
|
|
666
|
+
latency_ms: Date.now() - startedAt,
|
|
667
|
+
};
|
|
668
|
+
}
|
|
642
669
|
case "acp":
|
|
643
670
|
return presenceCheckOk();
|
|
644
671
|
default:
|
|
645
672
|
return {
|
|
646
673
|
ok: false,
|
|
647
|
-
error: `Unknown provider "${provider}". Supported: claude, claude-managed, codex, devin, opencode, pi, acp, dsh, cursor, amp, grok.`,
|
|
674
|
+
error: `Unknown provider "${provider}". Supported: claude, claude-managed, codex, devin, opencode, pi, acp, dsh, cursor, amp, grok, fx.`,
|
|
648
675
|
latency_ms: Date.now() - startedAt,
|
|
649
676
|
};
|
|
650
677
|
}
|