@desplega.ai/agent-swarm 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{acp-adapter-sj2g0m84.js → acp-adapter-kr4abrsk.js} +10 -10
- package/dist/{actions-fegk44gr.js → actions-eh4g6w2r.js} +27 -27
- package/dist/{amp-adapter-ad8ft89x.js → amp-adapter-7h7wdt8q.js} +5 -5
- package/dist/{app-kfvgbvzy.js → app-7k8s9w5p.js} +9 -9
- package/dist/{assistant-z7zdy9ja.js → assistant-wswt2y7p.js} +28 -28
- package/dist/{boot-reembed-j8xa181e.js → boot-reembed-9apm9qw6.js} +7 -7
- package/dist/{boot-reembed-5c2z0mbe.js → boot-reembed-whzbdthj.js} +7 -7
- package/dist/{boot-scrub-sweep-hnkn3rgt.js → boot-scrub-sweep-ndzmvmgt.js} +8 -8
- package/dist/{boot-seeds-kwjq4d2z.js → boot-seeds-e9sta0yt.js} +13 -11
- package/dist/{claude-adapter-9vmvpvgd.js → claude-adapter-4mqd3r6r.js} +11 -11
- package/dist/{claude-managed-adapter-k9qae05j.js → claude-managed-adapter-9wswb52m.js} +3 -3
- package/dist/{claude-managed-setup-q54zx5q2.js → claude-managed-setup-tpe1effx.js} +4 -4
- package/dist/{claude-sdk-session-9jye6cxr.js → claude-sdk-session-q49ghmh9.js} +10340 -10197
- package/dist/{cli-ke896fcf.js → cli-01xvj1rq.js} +10 -10
- package/dist/{cli-tr4sxp68.js → cli-035vekcw.js} +2 -2
- package/dist/{cli-z5jjpaxw.js → cli-0pd8hzhc.js} +3 -3
- package/dist/{cli-n5vwg842.js → cli-0veb92a3.js} +6 -1
- package/dist/{cli-wayw93nt.js → cli-10jesr25.js} +3 -3
- package/dist/{cli-se8w6wny.js → cli-1y7yywmj.js} +1 -1
- package/dist/{cli-v1yb0rqb.js → cli-20krdhz5.js} +47 -19
- package/dist/{cli-d5f9ed5c.js → cli-29rja109.js} +39 -20
- package/dist/{cli-apx87jne.js → cli-2pwa0q4d.js} +15 -4
- package/dist/{cli-ee9gtz70.js → cli-4mengw46.js} +348 -25
- package/dist/{cli-03gedhng.js → cli-5twdke5x.js} +6 -6
- package/dist/{cli-6razj6t4.js → cli-711vwbxn.js} +2 -2
- package/dist/{cli-s2sp957d.js → cli-78vv7446.js} +1 -1
- package/dist/{cli-3fbmmawm.js → cli-7n6s5man.js} +4 -4
- package/dist/{cli-hnhtk6cj.js → cli-8wwxw8a3.js} +1 -1
- package/dist/{cli-hnmhtzvh.js → cli-9jzdj90z.js} +2 -0
- package/dist/{cli-skxvvpap.js → cli-cvv9k0hh.js} +28 -25
- package/dist/{cli-74ahz61x.js → cli-d9286m9h.js} +2 -2
- package/dist/{cli-4jxm2qtz.js → cli-gxzkg1de.js} +28 -16
- package/dist/{cli-zkpks3r3.js → cli-h1arqn31.js} +1 -1
- package/dist/{cli-r62a35ea.js → cli-hqr88ew6.js} +1 -1
- package/dist/{cli-ra0xzggj.js → cli-j67mh2vg.js} +2 -2
- package/dist/{cli-29jc5q7w.js → cli-ksphgh48.js} +2 -2
- package/dist/{cli-eejsgvm4.js → cli-mgz4nbgg.js} +4 -4
- package/dist/{cli-vatyhshy.js → cli-mz5as5ha.js} +4 -4
- package/dist/{cli-f7kr7svz.js → cli-n8ndw8j7.js} +2 -2
- package/dist/{cli-1e4b6bzh.js → cli-naqzr17j.js} +7 -7
- package/dist/{cli-q0ke1yww.js → cli-nm1p8jfr.js} +6 -6
- package/dist/{cli-m50t4309.js → cli-p3zbc1rw.js} +5 -5
- package/dist/{cli-cgtksawx.js → cli-pe05f8yk.js} +23 -3
- package/dist/{cli-vdach5a9.js → cli-qmzsbkvy.js} +4 -4
- package/dist/{cli-n3hzg89b.js → cli-qnm4e9e4.js} +57 -579
- package/dist/{cli-s4setq91.js → cli-qqy4q48b.js} +3 -3
- package/dist/{cli-k382tshb.js → cli-r35rw5de.js} +8 -7
- package/dist/{cli-k3dp5tpf.js → cli-r610p9bs.js} +2 -2
- package/dist/{cli-e6mpjhed.js → cli-rc6pf5kv.js} +1 -1
- package/dist/{cli-3dj8k4vy.js → cli-rdxm77k3.js} +1 -1
- package/dist/{cli-tqaazemc.js → cli-s69nfyeh.js} +1 -1
- package/dist/{cli-6jyj59nb.js → cli-sawv63e8.js} +2 -2
- package/dist/{cli-q50942rs.js → cli-sg19gbf0.js} +1 -1
- package/dist/{cli-p1x85d2t.js → cli-shk7r5cs.js} +12 -9
- package/dist/{cli-przpmdgr.js → cli-sksetb2q.js} +11 -1
- package/dist/{cli-d2qr7aj4.js → cli-t02z59f2.js} +1 -1
- package/dist/{cli-j0pfptdp.js → cli-v7g551te.js} +39 -43
- package/dist/{cli-bzszjf2p.js → cli-vgqzp4em.js} +519 -126
- package/dist/cli-wfnemr1t.js +86 -0
- package/dist/{cli-ne236afy.js → cli-wms6x9db.js} +8 -8
- package/dist/{cli-5hkm7whv.js → cli-x1r545x8.js} +1 -1
- package/dist/{cli-kt6zqg0e.js → cli-xczsgshn.js} +2 -2
- package/dist/{cli-ef1hg1wv.js → cli-y3167wt1.js} +430 -238
- package/dist/{cli-tsde1hdr.js → cli-yjmqpxt3.js} +12 -11
- package/dist/{cli-9nfx44eh.js → cli-z780ec7c.js} +1 -1
- package/dist/{cli-r2pnvj8g.js → cli-zm9t2vde.js} +2 -2
- package/dist/cli.js +24 -23
- package/dist/{codex-adapter-y81sssk0.js → codex-adapter-4z16v9n0.js} +8 -8
- package/dist/{codex-login-f2rcfpp9.js → codex-login-pkepy2j1.js} +2 -2
- package/dist/{codex-session-runner-8vx4j6a9.js → codex-session-runner-rvms9y2x.js} +8 -8
- package/dist/{commands-kcpvpcrr.js → commands-2a9rtyat.js} +8 -8
- package/dist/{controller-ywrmt6yd.js → controller-3k3madbn.js} +4 -4
- package/dist/{controllers-connect-kwpvj3rf.js → controllers-connect-v9yn8zb9.js} +30 -30
- package/dist/{cursor-adapter-cehv1qns.js → cursor-adapter-98gbhyzn.js} +4 -4
- package/dist/{db-ww5d7jq3.js → db-eywwsnjw.js} +9 -7
- package/dist/{db-k8ankj20.js → db-ncypk9np.js} +9 -9
- package/dist/{devin-adapter-x1022qb1.js → devin-adapter-zeq8b9gz.js} +2 -2
- package/dist/{dispatcher-e8gq5m68.js → dispatcher-w94kc575.js} +26 -26
- package/dist/{docker-ff6y1190.js → docker-b1e4cqab.js} +2 -2
- package/dist/{dsh-adapter-3epjm3af.js → dsh-adapter-47ztej36.js} +4 -4
- package/dist/{e2b-8ygwhg0f.js → e2b-bbt8ey45.js} +3 -3
- package/dist/{e2b-swhnpw3n.js → e2b-dp804sen.js} +3 -3
- package/dist/{event-bus-6axnngdj.js → event-bus-j2jdg3qb.js} +2 -2
- package/dist/{external-channel-h3359cwn.js → external-channel-zrkg8ar9.js} +8 -8
- package/dist/{grok-adapter-0d4x7gkx.js → grok-adapter-s9ncp9vy.js} +11 -11
- package/dist/{handlers-an1gzv21.js → handlers-kqftvjgw.js} +29 -29
- package/dist/{heartbeat-6dakeb61.js → heartbeat-9q96p8v9.js} +31 -31
- package/dist/{hook-6v6cv6d5.js → hook-56qe10dp.js} +7 -7
- package/dist/{hook-sa5xye4y.js → hook-yy6rw42k.js} +8 -8
- package/dist/{http-r7f3eky0.js → http-r2dnb8f8.js} +963 -75
- package/dist/{index-7ep4gzdk.js → index-3gtmkhc6.js} +26 -26
- package/dist/{index-v1v6n7hg.js → index-3k5hkz7r.js} +31 -31
- package/dist/{index-rmpw9a8q.js → index-5e101240.js} +26 -26
- package/dist/{index-s2wxgzr0.js → index-5wyfjc2c.js} +392 -15
- package/dist/{index-zk96aq1k.js → index-vxvhxxbn.js} +7 -7
- package/dist/{keepalive-be9vwy5h.js → keepalive-psj6j54c.js} +9 -9
- package/dist/{lead-0861cwrf.js → lead-hajz4688.js} +43 -43
- package/dist/{maintenance-mzzgc8bn.js → maintenance-ps02sb5m.js} +7 -7
- package/dist/{node-mn1zs7an.js → node-mwb5r697.js} +1 -1
- package/dist/{oauth-refresh-sweep-cpdnfwdj.js → oauth-refresh-sweep-0284h93b.js} +8 -8
- package/dist/{onboard-fh8qs4r3.js → onboard-rbf6bg01.js} +2 -2
- package/dist/{opencode-adapter-nm42wwj8.js → opencode-adapter-13dcatgq.js} +8 -8
- package/dist/{otel-impl-x6c1c9g4.js → otel-impl-zvqcbhp7.js} +2 -2
- package/dist/{pi-mono-adapter-9n2ya240.js → pi-mono-adapter-xxy47q4v.js} +7 -7
- package/dist/{pricing-refresh-qb0rv0ap.js → pricing-refresh-kefx9v3t.js} +9 -9
- package/dist/{rbac-roles-1gzdwbh1.js → rbac-roles-3m36rxm2.js} +8 -8
- package/dist/{rbac-roles-3mraey52.js → rbac-roles-z76xv0hq.js} +9 -9
- package/dist/{render-v2-agpa45zz.js → render-v2-wz73bs25.js} +9 -9
- package/dist/{resume-x0ydy9s9.js → resume-k4wzge8w.js} +14 -14
- package/dist/{setup-as9ga0d6.js → setup-2fp2pypp.js} +2 -2
- package/dist/{work-objects-y5g1y7ev.js → work-objects-6k0ywz3m.js} +29 -29
- package/dist/{worker-860c3e38.js → worker-5v3rxsdh.js} +43 -43
- package/dist/{x-m477fr0h.js → x-4mtdeym1.js} +2 -2
- package/openapi.json +777 -6
- package/package.json +8 -7
- package/src/agentmail/handlers.ts +21 -11
- package/src/apps/definition.ts +16 -0
- package/src/azure-devops/api.ts +52 -2
- package/src/azure-devops/boards-sync.ts +50 -20
- package/src/azure-devops/index.ts +1 -0
- package/src/azure-devops/outbound.ts +95 -0
- package/src/be/boot-seeds.ts +11 -0
- package/src/be/db/model-profiles.ts +315 -0
- package/src/be/db/tasks/read.ts +7 -0
- package/src/be/db.ts +53 -47
- package/src/be/migrations/219_relabel_pi_context_compactions.sql +22 -0
- package/src/be/migrations/220_model_profiles.sql +137 -0
- package/src/be/model-tier-resolution.ts +145 -11
- package/src/be/model-validation.ts +8 -1
- package/src/be/scripts/typecheck.ts +12 -0
- package/src/be/secret-registry.ts +6 -2
- package/src/be/seed-model-profiles.ts +41 -0
- package/src/be/seed-scripts/catalog/heartbeat-jev-gate.ts +368 -0
- package/src/be/seed-scripts/index.ts +9 -0
- package/src/be/swarm-config-guard.ts +1 -0
- package/src/commands/runner.ts +3 -17
- package/src/extensions/catalog.generated.json +17 -0
- package/src/extensions/contract-types.generated.ts +1 -1
- package/src/extensions/loader.ts +10 -5
- package/src/heartbeat/heartbeat.ts +2 -1
- package/src/http/agents.ts +49 -2
- package/src/http/all-routes.ts +1 -0
- package/src/http/apps.ts +30 -3
- package/src/http/config.ts +26 -2
- package/src/http/core.ts +4 -0
- package/src/http/index.ts +11 -1
- package/src/http/model-profiles.ts +303 -0
- package/src/http/pages-public.ts +11 -5
- package/src/http/poll.ts +16 -5
- package/src/http/script-runs.ts +1 -1
- package/src/http/scripts.ts +25 -2
- package/src/http/tasks.ts +18 -0
- package/src/http/webhooks.ts +1 -2
- package/src/pages/feedback-overlay.ts +65 -6
- package/src/providers/acp-targets.ts +1 -1
- package/src/providers/amp-adapter.ts +1 -1
- package/src/providers/dsh-adapter.ts +1 -1
- package/src/providers/pi-mono-extension.ts +17 -3
- package/src/providers/reasoning-effort.ts +10 -0
- package/src/rbac/legacy-policy.ts +8 -0
- package/src/rbac/permissions.ts +5 -0
- package/src/scheduler/deferred-task-waits.ts +18 -5
- package/src/scheduler/scheduler.ts +7 -1
- package/src/scripts-runtime/executors/quickjs-prelude.ts +75 -6
- package/src/scripts-runtime/executors/quickjs-runner.ts +85 -8
- package/src/scripts-runtime/executors/quickjs-web-shims.ts +1041 -0
- package/src/scripts-runtime/extract-args-schema.ts +9 -8
- package/src/scripts-runtime/quickjs-compat.ts +464 -0
- package/src/scripts-runtime/sdk-allowlist.ts +4 -0
- package/src/scripts-runtime/types/stdlib.d.ts +12 -0
- package/src/scripts-runtime/types/swarm-extension.d.ts +3 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +12 -0
- package/src/server.ts +10 -1
- package/src/slack/blocks.ts +4 -2
- package/src/slack/reaction-shortcode.ts +14 -0
- package/src/slack/render-v2.ts +12 -7
- package/src/slack/responses.ts +5 -4
- package/src/slack/thread-buffer.ts +41 -22
- package/src/tasks/worker-follow-up.ts +11 -2
- package/src/tests/agentmail-filters.test.ts +47 -0
- package/src/tests/apps-spike2.test.ts +35 -0
- package/src/tests/azure-devops-outbound.test.ts +420 -0
- package/src/tests/db-task-read-contract.test.ts +2 -0
- package/src/tests/db-task-write-contract.test.ts +1 -0
- package/src/tests/deferred-task-wake.test.ts +78 -1
- package/src/tests/docs-index-footprint.test.js +2 -2
- package/src/tests/dsh-adapter.test.ts +1 -1
- package/src/tests/env-flag.test.ts +1 -0
- package/src/tests/extensions-catalog.test.ts +1 -0
- package/src/tests/extensions-example-comms-adherence-gate.test.ts +413 -0
- package/src/tests/extensions-loader.test.ts +15 -0
- package/src/tests/heartbeat-jev-gate-credentials.test.ts +168 -0
- package/src/tests/heartbeat-jev-gate.test.ts +385 -0
- package/src/tests/model-profile-backcompat.test.ts +327 -0
- package/src/tests/model-profile-providers.test.ts +302 -0
- package/src/tests/model-profile-runtime-secrets.test.ts +223 -0
- package/src/tests/model-profiles-api.test.ts +614 -0
- package/src/tests/pages-feedback-keys.test.ts +121 -0
- package/src/tests/pages-feedback.test.ts +3 -0
- package/src/tests/pi-context-compaction.test.ts +193 -0
- package/src/tests/profile-claim.test.ts +269 -0
- package/src/tests/quickjs-compat.test.ts +213 -0
- package/src/tests/rbac-engine.test.ts +21 -0
- package/src/tests/script-connections.test.ts +4 -0
- package/src/tests/script-executor-conformance.test.ts +252 -1
- package/src/tests/script-executor-quickjs.test.ts +96 -0
- package/src/tests/script-runs-http.test.ts +89 -0
- package/src/tests/scripts-http.test.ts +16 -0
- package/src/tests/secret-scrubber.test.ts +10 -0
- package/src/tests/seed-model-profiles.test.ts +174 -0
- package/src/tests/seed-scripts.test.ts +2 -2
- package/src/tests/skill-invocations.test.ts +4 -2
- package/src/tests/slack-blocks.test.ts +36 -0
- package/src/tests/slack-inline-output.test.ts +14 -0
- package/src/tests/slack-reaction-config.test.ts +20 -0
- package/src/tests/slack-render-v2.test.ts +37 -0
- package/src/tests/slack-thread-buffer.test.ts +98 -1
- package/src/tests/workflow-llm.test.ts +61 -0
- package/src/tools/model-profiles/list-model-profiles.ts +54 -0
- package/src/tools/request-human-input.ts +11 -3
- package/src/tools/script-apis.ts +5 -1
- package/src/tools/script-upsert.ts +4 -1
- package/src/tools/send-task.ts +24 -1
- package/src/tools/system-one-decide.ts +104 -0
- package/src/tools/task-action.ts +22 -1
- package/src/tools/tool-config.ts +6 -0
- package/src/types.ts +36 -1
- package/src/utils/secret-scrubber.ts +19 -12
- package/src/utils/vcs-repo-match.ts +29 -0
- package/src/workflows/executors/raw-llm.ts +8 -3
- package/src/workflows/executors/system-one-decision.ts +21 -11
- package/src/workflows/executors/workflow-llm.ts +22 -0
- package/src/workflows/index.ts +21 -12
- package/templates/extensions/README.md +1 -0
- package/templates/extensions/comms-adherence-gate/README.md +88 -0
- package/templates/extensions/comms-adherence-gate/hooks.ts +489 -0
- package/templates/extensions/comms-adherence-gate/manifest.json +8 -0
- package/dist/cli-5t4f29ez.js +0 -33
|
@@ -77,6 +77,67 @@ describe("resolveWorkflowLlmConfig", () => {
|
|
|
77
77
|
expect(config.model).toBe("gpt-5.4");
|
|
78
78
|
});
|
|
79
79
|
|
|
80
|
+
test("routes a registry-provider model to that provider, not OpenRouter", async () => {
|
|
81
|
+
const config = await resolveWorkflowLlmConfig("nan/qwen3.8-flash", {
|
|
82
|
+
OPENROUTER_API_KEY: "sk-or-test",
|
|
83
|
+
NAN_API_KEY: "nan-test",
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
expect(config).toEqual({
|
|
87
|
+
apiKey: "nan-test",
|
|
88
|
+
baseURL: "https://api.nan.builders/v1",
|
|
89
|
+
headers: {},
|
|
90
|
+
model: "qwen3.8-flash",
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("reads a registry provider's alias key and base URL override", async () => {
|
|
95
|
+
const config = await resolveWorkflowLlmConfig("nan/deepseek-v4-flash", {
|
|
96
|
+
HELMCODE_API_NAN: "nan-alias",
|
|
97
|
+
NAN_BASE_URL: "https://nan.example.test/v1/",
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
expect(config.apiKey).toBe("nan-alias");
|
|
101
|
+
expect(config.baseURL).toBe("https://nan.example.test/v1");
|
|
102
|
+
expect(config.model).toBe("deepseek-v4-flash");
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("hands the SDK one key from a canonical NaN key pool", async () => {
|
|
106
|
+
const pool = ["nan-pool-a", "nan-pool-b", "nan-pool-c"];
|
|
107
|
+
for (let i = 0; i < 20; i++) {
|
|
108
|
+
const config = await resolveWorkflowLlmConfig("nan/qwen3.8-flash", {
|
|
109
|
+
NAN_API_KEY: ` ${pool.join(", ")} `,
|
|
110
|
+
});
|
|
111
|
+
expect(pool).toContain(config.apiKey);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("hands the SDK one key from an alias NaN key pool", async () => {
|
|
116
|
+
const pool = ["nan-alias-a", "nan-alias-b"];
|
|
117
|
+
for (let i = 0; i < 20; i++) {
|
|
118
|
+
const config = await resolveWorkflowLlmConfig("nan/deepseek-v4-flash", {
|
|
119
|
+
HELMCODE_API_NAN: pool.join(","),
|
|
120
|
+
});
|
|
121
|
+
expect(pool).toContain(config.apiKey);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("fails a registry-provider model without its key instead of falling back to OpenRouter", async () => {
|
|
126
|
+
await expect(
|
|
127
|
+
resolveWorkflowLlmConfig("nan/qwen3.8-flash", { OPENROUTER_API_KEY: "sk-or-test" }),
|
|
128
|
+
).rejects.toThrow("No NaN credential found for nan/qwen3.8-flash. Set NAN_API_KEY.");
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("keeps OpenRouter vendor ids on OpenRouter", async () => {
|
|
132
|
+
const config = await resolveWorkflowLlmConfig("deepseek/deepseek-v4.1-flash", {
|
|
133
|
+
OPENROUTER_API_KEY: "sk-or-test",
|
|
134
|
+
NAN_API_KEY: "nan-test",
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
expect(config.apiKey).toBe("sk-or-test");
|
|
138
|
+
expect(config.model).toBe("deepseek/deepseek-v4.1-flash");
|
|
139
|
+
});
|
|
140
|
+
|
|
80
141
|
test("rejects credential kinds without an OpenAI-compatible endpoint", async () => {
|
|
81
142
|
await expect(
|
|
82
143
|
resolveWorkflowLlmConfig(undefined, { ANTHROPIC_API_KEY: "example-sk-ant-test" }),
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import * as z from "zod";
|
|
3
|
+
import { listModelProfiles } from "@/be/db/model-profiles";
|
|
4
|
+
import { createToolRegistrar, swarmToolOutputSchema, toolOk } from "@/tools/utils";
|
|
5
|
+
import { ProviderNameSchema } from "@/types";
|
|
6
|
+
|
|
7
|
+
export const registerListModelProfilesTool = (server: McpServer) => {
|
|
8
|
+
createToolRegistrar(server)(
|
|
9
|
+
"list-model-profiles",
|
|
10
|
+
{
|
|
11
|
+
title: "List Model Profiles",
|
|
12
|
+
description:
|
|
13
|
+
"List model profiles: named harness + model + effort combinations. Pass a profile's name as `profile` on send-task to run that task on it, or set it as an agent's default through PATCH /api/agents/{id}/runtime. A profile runs only on its own harness. Filter by `harness` to see what an agent on that harness can run.",
|
|
14
|
+
annotations: { readOnlyHint: true },
|
|
15
|
+
inputSchema: z.object({
|
|
16
|
+
harness: ProviderNameSchema.optional().describe(
|
|
17
|
+
"Only profiles for this harness (claude, codex, pi, ...).",
|
|
18
|
+
),
|
|
19
|
+
}),
|
|
20
|
+
outputSchema: swarmToolOutputSchema({
|
|
21
|
+
profiles: z
|
|
22
|
+
.array(
|
|
23
|
+
z.looseObject({
|
|
24
|
+
name: z.string().optional(),
|
|
25
|
+
harness: z.string().optional(),
|
|
26
|
+
model: z.string().optional(),
|
|
27
|
+
effort: z.string().nullable().optional(),
|
|
28
|
+
description: z.string().nullable().optional(),
|
|
29
|
+
}),
|
|
30
|
+
)
|
|
31
|
+
.optional(),
|
|
32
|
+
}),
|
|
33
|
+
},
|
|
34
|
+
async ({ harness }) => {
|
|
35
|
+
const profiles = await listModelProfiles({ harness });
|
|
36
|
+
const lines = profiles.map(
|
|
37
|
+
(p) =>
|
|
38
|
+
`${p.name}: ${p.harness} / ${p.model}${p.effort ? ` / effort ${p.effort}` : ""}${p.description ? ` (${p.description})` : ""}`,
|
|
39
|
+
);
|
|
40
|
+
return toolOk(`${profiles.length} model profile(s)${harness ? ` for ${harness}` : ""}.`, {
|
|
41
|
+
details: lines.join("\n"),
|
|
42
|
+
data: {
|
|
43
|
+
profiles: profiles.map((p) => ({
|
|
44
|
+
name: p.name,
|
|
45
|
+
harness: p.harness,
|
|
46
|
+
model: p.model,
|
|
47
|
+
effort: p.effort,
|
|
48
|
+
description: p.description,
|
|
49
|
+
})),
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
},
|
|
53
|
+
);
|
|
54
|
+
};
|
|
@@ -56,6 +56,12 @@ export const registerRequestHumanInputTool = (server: McpServer) => {
|
|
|
56
56
|
.describe(
|
|
57
57
|
"Seconds until the request expires. After that the request becomes 'timeout' and you get a hitl-follow-up task. A request with no timeout is cancelled after APPROVAL_REQUEST_AUTO_CANCELLATION_DAYS days (default 7).",
|
|
58
58
|
),
|
|
59
|
+
detached: z
|
|
60
|
+
.boolean()
|
|
61
|
+
.optional()
|
|
62
|
+
.describe(
|
|
63
|
+
"Do not attach the request to your current task, so no hitl-follow-up task is created. For scripts that read the request's status themselves.",
|
|
64
|
+
),
|
|
59
65
|
}),
|
|
60
66
|
outputSchema: swarmToolOutputSchema({
|
|
61
67
|
yourAgentId: z.string().optional(),
|
|
@@ -63,7 +69,7 @@ export const registerRequestHumanInputTool = (server: McpServer) => {
|
|
|
63
69
|
url: z.string().optional(),
|
|
64
70
|
}),
|
|
65
71
|
},
|
|
66
|
-
async ({ title, questions, timeoutSeconds }, requestInfo) => {
|
|
72
|
+
async ({ title, questions, timeoutSeconds, detached }, requestInfo) => {
|
|
67
73
|
if (!requestInfo.agentId) {
|
|
68
74
|
return toolErr(
|
|
69
75
|
'Agent ID not found. The MCP client should define the "X-Agent-ID" header.',
|
|
@@ -74,8 +80,10 @@ export const registerRequestHumanInputTool = (server: McpServer) => {
|
|
|
74
80
|
// Resolve sourceTaskId: prefer header, fall back to agent's current in-progress task.
|
|
75
81
|
// The X-Source-Task-Id header may be missing when the per-session MCP config wasn't
|
|
76
82
|
// created (e.g. .mcp.json not found, session resumed, or lead agent on a non-task trigger).
|
|
77
|
-
|
|
78
|
-
|
|
83
|
+
// A detached request has no source task: a script call carries no task
|
|
84
|
+
// header, and the agent's current task is unrelated to the script.
|
|
85
|
+
let sourceTaskId = detached ? undefined : requestInfo.sourceTaskId;
|
|
86
|
+
if (!sourceTaskId && !detached && requestInfo.agentId) {
|
|
79
87
|
const currentTask = await getAgentCurrentTask(requestInfo.agentId);
|
|
80
88
|
if (currentTask) {
|
|
81
89
|
sourceTaskId = currentTask.id;
|
package/src/tools/script-apis.ts
CHANGED
|
@@ -149,7 +149,11 @@ export const registerScriptApisTool = (server: McpServer) => {
|
|
|
149
149
|
agentId: args.agentId,
|
|
150
150
|
},
|
|
151
151
|
requestInfo,
|
|
152
|
-
successMessage: (data) =>
|
|
152
|
+
successMessage: (data) => {
|
|
153
|
+
const warnings = (data as { warnings?: string[] }).warnings ?? [];
|
|
154
|
+
const note = warnings.length > 0 ? `\n${warnings.join("\n")}` : "";
|
|
155
|
+
return `Endpoint ${(data as ScriptApiWithSecret).id} created.${note}`;
|
|
156
|
+
},
|
|
153
157
|
successDetails: (data) => {
|
|
154
158
|
const token = (data as ScriptApiWithSecret).token;
|
|
155
159
|
return token ? `Bearer token (shown once — save it now): ${token}` : undefined;
|
|
@@ -47,11 +47,14 @@ export const registerScriptUpsertTool = (server: McpServer) => {
|
|
|
47
47
|
name?: unknown;
|
|
48
48
|
version?: unknown;
|
|
49
49
|
contentDeduped?: unknown;
|
|
50
|
+
warnings?: unknown;
|
|
50
51
|
};
|
|
51
52
|
const name = typeof body.name === "string" ? body.name : args.name;
|
|
52
53
|
const version = typeof body.version === "number" ? ` v${body.version}` : "";
|
|
53
54
|
const deduped = body.contentDeduped ? " (content unchanged — deduped)" : "";
|
|
54
|
-
|
|
55
|
+
const warnings = Array.isArray(body.warnings) ? body.warnings.map(String) : [];
|
|
56
|
+
const note = warnings.length > 0 ? `\n${warnings.join("\n")}` : "";
|
|
57
|
+
return `Script \`${name}\`${version} saved.${deduped}${note}`;
|
|
55
58
|
},
|
|
56
59
|
}),
|
|
57
60
|
);
|
package/src/tools/send-task.ts
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
isExtensionAgent,
|
|
19
19
|
isLinearTrackerContextKey,
|
|
20
20
|
} from "@/be/db";
|
|
21
|
+
import { modelProfileRefError, taskProfileError } from "@/be/db/model-profiles";
|
|
21
22
|
import { repointTrackerSyncBySwarmId } from "@/be/db-queries/tracker";
|
|
22
23
|
import { explicitModelErrorForAgent } from "@/be/model-validation";
|
|
23
24
|
import { applyPreTaskCreate } from "@/extensions/apply-task-create";
|
|
@@ -150,6 +151,14 @@ export const sendTaskInputSchema = z
|
|
|
150
151
|
effort: ReasoningEffortSchema.optional().describe(
|
|
151
152
|
"Reasoning effort for this task: 'off', 'low', 'medium', 'high', 'xhigh', or 'max'. If omitted, the assignee's REASONING_EFFORT_OVERRIDE/default applies.",
|
|
152
153
|
),
|
|
154
|
+
profile: z
|
|
155
|
+
.string()
|
|
156
|
+
.trim()
|
|
157
|
+
.min(1)
|
|
158
|
+
.optional()
|
|
159
|
+
.describe(
|
|
160
|
+
"Model profile name (see list-model-profiles): a named harness + model + effort, applied when the task is claimed. Must run on the assignee's harness. Exclusive with model and modelTier. A set `effort` still wins over the profile's.",
|
|
161
|
+
),
|
|
153
162
|
allowDuplicate: z
|
|
154
163
|
.boolean()
|
|
155
164
|
.default(false)
|
|
@@ -281,6 +290,7 @@ export async function sendTaskHandler(
|
|
|
281
290
|
modelTier,
|
|
282
291
|
allowCustomModel,
|
|
283
292
|
effort,
|
|
293
|
+
profile,
|
|
284
294
|
allowDuplicate,
|
|
285
295
|
slackChannelId,
|
|
286
296
|
slackThreadTs,
|
|
@@ -474,6 +484,13 @@ export async function sendTaskHandler(
|
|
|
474
484
|
agentId: effectiveAgentId,
|
|
475
485
|
});
|
|
476
486
|
if (modelError) return toolErr(modelError, { data: { yourAgentId: creatorAgentId } });
|
|
487
|
+
const profileError = await taskProfileError({
|
|
488
|
+
profile,
|
|
489
|
+
model: normalizedModel.model,
|
|
490
|
+
modelTier: normalizedModel.modelTier,
|
|
491
|
+
agentId: effectiveAgentId,
|
|
492
|
+
});
|
|
493
|
+
if (profileError) return toolErr(profileError, { data: { yourAgentId: creatorAgentId } });
|
|
477
494
|
const effectiveRoutingReason =
|
|
478
495
|
requestedAgentId !== undefined
|
|
479
496
|
? requestedRoutingReason
|
|
@@ -508,6 +525,7 @@ export async function sendTaskHandler(
|
|
|
508
525
|
model: normalizedModel.model,
|
|
509
526
|
modelTier: normalizedModel.modelTier,
|
|
510
527
|
effort,
|
|
528
|
+
profile,
|
|
511
529
|
slackChannelId,
|
|
512
530
|
slackThreadTs,
|
|
513
531
|
slackUserId,
|
|
@@ -647,7 +665,7 @@ export async function sendTaskHandler(
|
|
|
647
665
|
: toolErr(guard.message, { data: guardData });
|
|
648
666
|
}
|
|
649
667
|
|
|
650
|
-
const
|
|
668
|
+
const created = getDbClient().transaction(async () => {
|
|
651
669
|
// Authoritative re-check: the reads above are separated from the INSERT by
|
|
652
670
|
// the whole handler, so only a guard inside this transaction can see a
|
|
653
671
|
// concurrent send-task's committed task.
|
|
@@ -743,6 +761,11 @@ export async function sendTaskHandler(
|
|
|
743
761
|
task: newTask,
|
|
744
762
|
};
|
|
745
763
|
});
|
|
764
|
+
const result = await created.catch((error: unknown) => {
|
|
765
|
+
// The profile was deleted between taskProfileError and the insert.
|
|
766
|
+
if (modelProfileRefError(error) !== "missing") throw error;
|
|
767
|
+
return { success: false, message: `Unknown model profile "${profile}".`, task: undefined };
|
|
768
|
+
});
|
|
746
769
|
|
|
747
770
|
const data = {
|
|
748
771
|
yourAgentId: creatorAgentId,
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import * as z from "zod";
|
|
3
|
+
import { createToolRegistrar, swarmToolOutputSchema, toolErr, toolOk } from "@/tools/utils";
|
|
4
|
+
import { getExecutorRegistry } from "@/workflows";
|
|
5
|
+
import {
|
|
6
|
+
SYSTEM_ONE_DECISION_NODE_TYPE,
|
|
7
|
+
SystemOneDecisionConfigSchema,
|
|
8
|
+
SystemOneDecisionExecutor,
|
|
9
|
+
} from "@/workflows/executors/system-one-decision";
|
|
10
|
+
import { SYSTEM_ONE_PROVIDER_IDS } from "@/workflows/executors/system-one-providers";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* One typed decision (Jev and the other SystemOne hosts) made in the API
|
|
14
|
+
* process, the same way a `system-one-decision` workflow node makes it. The
|
|
15
|
+
* provider credential is read and sent here and never reaches the caller, so a
|
|
16
|
+
* script can ask for a decision without a credential binding in its own realm.
|
|
17
|
+
*/
|
|
18
|
+
export const registerSystemOneDecideTool = (server: McpServer) => {
|
|
19
|
+
createToolRegistrar(server)(
|
|
20
|
+
"system-one-decide",
|
|
21
|
+
{
|
|
22
|
+
title: "Ask a SystemOne decision model",
|
|
23
|
+
annotations: { destructiveHint: false, openWorldHint: true },
|
|
24
|
+
description:
|
|
25
|
+
"Send a state and typed questions (noul, choice, score) to a SystemOne decision host such as Jev, and return the validated answers. " +
|
|
26
|
+
"Same request, provider list, credentials and validation as the `system-one-decision` workflow node, without its human review band. " +
|
|
27
|
+
"The provider credential is a global secret that stays on the server; this tool never returns it.",
|
|
28
|
+
inputSchema: z.object({
|
|
29
|
+
provider: z
|
|
30
|
+
.enum(SYSTEM_ONE_PROVIDER_IDS)
|
|
31
|
+
.optional()
|
|
32
|
+
.describe("Decision host (default typesafe)"),
|
|
33
|
+
model: z.string().min(1).optional().describe("Provider model id (default: the provider's)"),
|
|
34
|
+
state: z
|
|
35
|
+
.union([z.string(), z.array(z.string()), z.record(z.string(), z.unknown())])
|
|
36
|
+
.describe("The state to decide on"),
|
|
37
|
+
questions: z
|
|
38
|
+
.record(z.string(), z.record(z.string(), z.unknown()))
|
|
39
|
+
.describe(
|
|
40
|
+
"Questions by id, each `{ type: 'noul' | 'choice' | 'score', instructions, criteria }`",
|
|
41
|
+
),
|
|
42
|
+
timeoutMs: z
|
|
43
|
+
.number()
|
|
44
|
+
.int()
|
|
45
|
+
.optional()
|
|
46
|
+
.describe("Time budget for the call, retries included (default 30000)"),
|
|
47
|
+
maxRetries: z
|
|
48
|
+
.number()
|
|
49
|
+
.int()
|
|
50
|
+
.optional()
|
|
51
|
+
.describe("Retries on transient failures inside the time budget (default 2)"),
|
|
52
|
+
}),
|
|
53
|
+
outputSchema: swarmToolOutputSchema({
|
|
54
|
+
model: z.string().optional(),
|
|
55
|
+
answers: z.record(z.string(), z.unknown()).optional(),
|
|
56
|
+
usage: z.record(z.string(), z.unknown()).optional(),
|
|
57
|
+
requestId: z.string().optional(),
|
|
58
|
+
}),
|
|
59
|
+
},
|
|
60
|
+
async ({ provider, model, state, questions, timeoutMs, maxRetries }, requestInfo) => {
|
|
61
|
+
if (!requestInfo.agentId) {
|
|
62
|
+
return toolErr('Agent ID not found. The MCP client should define the "X-Agent-ID" header.');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// `returns` mirrors the questions: the caller reads answers directly, so
|
|
66
|
+
// there is no downstream port to declare.
|
|
67
|
+
const returns = Object.fromEntries(
|
|
68
|
+
Object.entries(questions).map(([id, question]) => [id, { type: question.type }]),
|
|
69
|
+
);
|
|
70
|
+
const parsed = SystemOneDecisionConfigSchema.safeParse({
|
|
71
|
+
provider,
|
|
72
|
+
model,
|
|
73
|
+
state,
|
|
74
|
+
questions,
|
|
75
|
+
returns,
|
|
76
|
+
timeoutMs,
|
|
77
|
+
maxRetries,
|
|
78
|
+
});
|
|
79
|
+
if (!parsed.success) {
|
|
80
|
+
const issues = parsed.error.issues
|
|
81
|
+
.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`)
|
|
82
|
+
.join("; ");
|
|
83
|
+
return toolErr(`Invalid decision request: ${issues}`.slice(0, 1000));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const executor = getExecutorRegistry().get(SYSTEM_ONE_DECISION_NODE_TYPE);
|
|
87
|
+
if (!(executor instanceof SystemOneDecisionExecutor)) {
|
|
88
|
+
return toolErr("The system-one-decision executor is not available");
|
|
89
|
+
}
|
|
90
|
+
const decided = await executor.decide(parsed.data);
|
|
91
|
+
if (!decided.ok) return toolErr(decided.error);
|
|
92
|
+
|
|
93
|
+
const { output } = decided;
|
|
94
|
+
return toolOk(`Decision from ${output.model}`, {
|
|
95
|
+
data: {
|
|
96
|
+
model: output.model,
|
|
97
|
+
answers: output.answers,
|
|
98
|
+
usage: output.usage,
|
|
99
|
+
requestId: output.requestId,
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
},
|
|
103
|
+
);
|
|
104
|
+
};
|
package/src/tools/task-action.ts
CHANGED
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
releaseTask,
|
|
29
29
|
updateTaskClaudeSessionId,
|
|
30
30
|
} from "@/be/db";
|
|
31
|
+
import { modelProfileRefError, taskProfileError } from "@/be/db/model-profiles";
|
|
31
32
|
import { explicitModelErrorForAgent } from "@/be/model-validation";
|
|
32
33
|
import { touchRuntimeInstance } from "@/be/multi-runtime";
|
|
33
34
|
import { applyPreTaskCreate } from "@/extensions/apply-task-create";
|
|
@@ -109,6 +110,14 @@ export const taskActionInputSchema = z.object({
|
|
|
109
110
|
effort: ReasoningEffortSchema.optional().describe(
|
|
110
111
|
"Reasoning effort for the created task: 'off', 'low', 'medium', 'high', 'xhigh', or 'max'. Only used with 'create' action.",
|
|
111
112
|
),
|
|
113
|
+
profile: z
|
|
114
|
+
.string()
|
|
115
|
+
.trim()
|
|
116
|
+
.min(1)
|
|
117
|
+
.optional()
|
|
118
|
+
.describe(
|
|
119
|
+
"Model profile name for the created pool task (see list-model-profiles); only workers on its harness claim it. Exclusive with model and modelTier. Only used with 'create' action.",
|
|
120
|
+
),
|
|
112
121
|
requiredCapabilities: z
|
|
113
122
|
.array(z.string())
|
|
114
123
|
.optional()
|
|
@@ -296,6 +305,12 @@ export async function taskActionHandler(
|
|
|
296
305
|
allowCustomModel: input.allowCustomModel,
|
|
297
306
|
});
|
|
298
307
|
if (modelError) return taskActionResult({ success: false, message: modelError }, agentId);
|
|
308
|
+
const profileError = await taskProfileError({
|
|
309
|
+
profile: input.profile,
|
|
310
|
+
model: normalizedModel.model,
|
|
311
|
+
modelTier: normalizedModel.modelTier,
|
|
312
|
+
});
|
|
313
|
+
if (profileError) return taskActionResult({ success: false, message: profileError }, agentId);
|
|
299
314
|
try {
|
|
300
315
|
assetKey = key
|
|
301
316
|
? await authorizeAssetKeyWrite(key, await resolveTaskAuditUserId(ctx.sourceTaskId, agentId))
|
|
@@ -324,6 +339,7 @@ export async function taskActionHandler(
|
|
|
324
339
|
model: normalizedModel.model,
|
|
325
340
|
modelTier: normalizedModel.modelTier,
|
|
326
341
|
effort: input.effort,
|
|
342
|
+
profile: input.profile,
|
|
327
343
|
routingAffinity:
|
|
328
344
|
leadOnly || requiredCapabilities?.length
|
|
329
345
|
? { leadOnly, capabilities: requiredCapabilities ?? [] }
|
|
@@ -342,7 +358,7 @@ export async function taskActionHandler(
|
|
|
342
358
|
};
|
|
343
359
|
}
|
|
344
360
|
|
|
345
|
-
const
|
|
361
|
+
const transacted = getDbClient().transaction(async (): Promise<TaskActionResult> => {
|
|
346
362
|
switch (action) {
|
|
347
363
|
case "create": {
|
|
348
364
|
const newTask = await createTaskExtended(
|
|
@@ -644,6 +660,11 @@ export async function taskActionHandler(
|
|
|
644
660
|
return { success: false, message: `Unknown action: ${action}` };
|
|
645
661
|
}
|
|
646
662
|
});
|
|
663
|
+
const result = await transacted.catch((error: unknown): TaskActionResult => {
|
|
664
|
+
// A create whose profile was deleted between taskProfileError and the insert.
|
|
665
|
+
if (modelProfileRefError(error) !== "missing") throw error;
|
|
666
|
+
return { success: false, message: `Unknown model profile "${input.profile}".` };
|
|
667
|
+
});
|
|
647
668
|
|
|
648
669
|
// Phase 5: when the accept gate refused, run after-commit side
|
|
649
670
|
// effects (lead follow-up + workflow bus). The dedup row was recorded
|
package/src/tools/tool-config.ts
CHANGED
|
@@ -81,6 +81,9 @@ export const DEFERRED_TOOLS = new Set([
|
|
|
81
81
|
"model-catalog-refresh",
|
|
82
82
|
"model-catalog-overlay-upsert",
|
|
83
83
|
|
|
84
|
+
// Model profiles (1)
|
|
85
|
+
"list-model-profiles",
|
|
86
|
+
|
|
84
87
|
// Repos (1)
|
|
85
88
|
"update-repo",
|
|
86
89
|
|
|
@@ -151,6 +154,9 @@ export const DEFERRED_TOOLS = new Set([
|
|
|
151
154
|
"request-human-input",
|
|
152
155
|
"cancel-approval-request",
|
|
153
156
|
|
|
157
|
+
// Decisions (1)
|
|
158
|
+
"system-one-decide",
|
|
159
|
+
|
|
154
160
|
// Skills (12)
|
|
155
161
|
"skill-create",
|
|
156
162
|
"skill-update",
|
package/src/types.ts
CHANGED
|
@@ -428,6 +428,31 @@ export const ProviderNameSchema = z.enum([
|
|
|
428
428
|
]);
|
|
429
429
|
export type ProviderName = z.infer<typeof ProviderNameSchema>;
|
|
430
430
|
|
|
431
|
+
// ============================================================================
|
|
432
|
+
// Model Profiles (runbooks/model-tiers.md § Model profiles)
|
|
433
|
+
// ============================================================================
|
|
434
|
+
|
|
435
|
+
/** Lowercase, starts with a letter or digit; dots, dashes and underscores allowed. */
|
|
436
|
+
export const ModelProfileNameSchema = z
|
|
437
|
+
.string()
|
|
438
|
+
.trim()
|
|
439
|
+
.regex(/^[a-z0-9][a-z0-9._-]{0,99}$/, "lowercase letters, digits, '.', '_' or '-', max 100");
|
|
440
|
+
|
|
441
|
+
export const ModelProfileSchema = z.object({
|
|
442
|
+
id: z.string(),
|
|
443
|
+
name: z.string(),
|
|
444
|
+
harness: ProviderNameSchema,
|
|
445
|
+
model: z.string(),
|
|
446
|
+
effort: ReasoningEffortSchema.nullable(),
|
|
447
|
+
allowCustomModel: z.boolean(),
|
|
448
|
+
description: z.string().nullable(),
|
|
449
|
+
/** `tier:<tier>` (boot seed), `agent-config` (made from an agent's MODEL_OVERRIDE), or null. */
|
|
450
|
+
seededFrom: z.string().nullable(),
|
|
451
|
+
createdAt: z.string(),
|
|
452
|
+
lastUpdatedAt: z.string(),
|
|
453
|
+
});
|
|
454
|
+
export type ModelProfile = z.infer<typeof ModelProfileSchema>;
|
|
455
|
+
|
|
431
456
|
// ============================================================================
|
|
432
457
|
// Task Steering Messages
|
|
433
458
|
// ============================================================================
|
|
@@ -686,6 +711,11 @@ export const AgentTaskSchema = z
|
|
|
686
711
|
// REASONING_EFFORT_OVERRIDE, dropped when the harness/model cannot take
|
|
687
712
|
// it). Reported at session start; unset when the harness default applied.
|
|
688
713
|
resolvedEffort: ReasoningEffortSchema.optional(),
|
|
714
|
+
// Model profile (runbooks/model-tiers.md § Model profiles): `profile` is
|
|
715
|
+
// the creator's ask, `resolvedProfile` the name claim applied (the ask or
|
|
716
|
+
// the agent's default). Unset when no profile applied.
|
|
717
|
+
profile: z.string().optional(),
|
|
718
|
+
resolvedProfile: z.string().optional(),
|
|
689
719
|
|
|
690
720
|
// Schedule linking (optional — set when task was created by a schedule).
|
|
691
721
|
// z.string(): also reachable verbatim from the create-task query param.
|
|
@@ -817,6 +847,7 @@ export const CreateTaskOptionsSchema = z.object({
|
|
|
817
847
|
model: z.string().optional(),
|
|
818
848
|
modelTier: ModelTierSchema.optional(),
|
|
819
849
|
effort: ReasoningEffortSchema.optional(),
|
|
850
|
+
profile: z.string().optional(),
|
|
820
851
|
scheduleId: z.string().optional(),
|
|
821
852
|
workflowRunId: z.string().optional(),
|
|
822
853
|
workflowRunStepId: z.string().optional(),
|
|
@@ -2403,7 +2434,11 @@ export const TriggerConfigSchema = z
|
|
|
2403
2434
|
}),
|
|
2404
2435
|
z.object({
|
|
2405
2436
|
type: z.literal("event"),
|
|
2406
|
-
eventName: z.
|
|
2437
|
+
eventName: z.enum([
|
|
2438
|
+
"slack.message",
|
|
2439
|
+
"azure-devops.work_item.assigned",
|
|
2440
|
+
"azure-devops.work_item.commented",
|
|
2441
|
+
]),
|
|
2407
2442
|
}),
|
|
2408
2443
|
])
|
|
2409
2444
|
.superRefine((trigger, ctx) => {
|
|
@@ -808,6 +808,21 @@ function computeIsSensitiveKey(key: string): boolean {
|
|
|
808
808
|
return false;
|
|
809
809
|
}
|
|
810
810
|
|
|
811
|
+
/**
|
|
812
|
+
* Every form of a credential value a consumer may send: the raw value, its
|
|
813
|
+
* trimmed form, and each trimmed member of a comma pool. `selectCredential`
|
|
814
|
+
* trims the key it hands out, so a padded singleton reaches the provider (and
|
|
815
|
+
* its error echo) without the padding.
|
|
816
|
+
*/
|
|
817
|
+
function credentialCandidates(value: string): string[] {
|
|
818
|
+
const candidates = new Set([value, value.trim()]);
|
|
819
|
+
if (value.includes(",")) {
|
|
820
|
+
for (const member of value.split(",")) candidates.add(member.trim());
|
|
821
|
+
}
|
|
822
|
+
candidates.delete("");
|
|
823
|
+
return [...candidates];
|
|
824
|
+
}
|
|
825
|
+
|
|
811
826
|
function buildCache(): ScrubCache {
|
|
812
827
|
const entries: EnvValueEntry[] = [];
|
|
813
828
|
const seen = new Set<string>();
|
|
@@ -816,14 +831,7 @@ function buildCache(): ScrubCache {
|
|
|
816
831
|
if (!rawValue) continue;
|
|
817
832
|
if (!isSensitiveKey(key)) continue;
|
|
818
833
|
|
|
819
|
-
|
|
820
|
-
// tokens that the runner rotates through. Scrub each component too.
|
|
821
|
-
const candidates = rawValue.includes(",")
|
|
822
|
-
? [rawValue, ...rawValue.split(",").map((s) => s.trim())]
|
|
823
|
-
: [rawValue];
|
|
824
|
-
|
|
825
|
-
for (const candidate of candidates) {
|
|
826
|
-
if (!candidate) continue;
|
|
834
|
+
for (const candidate of credentialCandidates(rawValue)) {
|
|
827
835
|
if (candidate.length < MIN_VALUE_LENGTH) continue;
|
|
828
836
|
for (const form of escapedForms(candidate)) {
|
|
829
837
|
if (seen.has(form)) continue;
|
|
@@ -1089,14 +1097,13 @@ export function registerVolatileSecret(value: string, name: string): void {
|
|
|
1089
1097
|
* Register the sensitive values of an env map that is not `process.env`
|
|
1090
1098
|
* (a runner's per-task env, an adapter's session env). A key picked from a
|
|
1091
1099
|
* credential pool, or a row admitted after boot, lives only there, so the
|
|
1092
|
-
* env pass never sees it. Comma-separated pools register each member too
|
|
1100
|
+
* env pass never sees it. Comma-separated pools register each member too,
|
|
1101
|
+
* and padded values register their trimmed form.
|
|
1093
1102
|
*/
|
|
1094
1103
|
export function registerEnvSecrets(env: Record<string, string | undefined>): void {
|
|
1095
1104
|
for (const [key, value] of Object.entries(env)) {
|
|
1096
1105
|
if (!value || !isSensitiveKey(key)) continue;
|
|
1097
|
-
registerVolatileSecret(
|
|
1098
|
-
if (!value.includes(",")) continue;
|
|
1099
|
-
for (const member of value.split(",")) registerVolatileSecret(member.trim(), key);
|
|
1106
|
+
for (const candidate of credentialCandidates(value)) registerVolatileSecret(candidate, key);
|
|
1100
1107
|
}
|
|
1101
1108
|
}
|
|
1102
1109
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a task's `vcsRepo` names a registered repo (`swarm_repos`). The worker
|
|
3
|
+
* loads that repo's scoped config with it (`fetchRepoConfig`), and the API
|
|
4
|
+
* reads the same repo at claim, so both sides must match the same way.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** `vcsRepo` without surrounding spaces, trailing slashes or `.git`. */
|
|
8
|
+
export function normalizeVcsRepo(vcsRepo: string): string {
|
|
9
|
+
return vcsRepo
|
|
10
|
+
.trim()
|
|
11
|
+
.replace(/\/+$/, "")
|
|
12
|
+
.replace(/\.git$/, "");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** The `swarm_repos.name` candidates are looked up by: the last path segment. */
|
|
16
|
+
export function vcsRepoName(vcsRepo: string): string {
|
|
17
|
+
const requested = normalizeVcsRepo(vcsRepo);
|
|
18
|
+
return requested.split("/").pop() || requested;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Whether a registered repo URL is exactly the repo `vcsRepo` names. */
|
|
22
|
+
export function vcsRepoUrlMatches(url: string, vcsRepo: string): boolean {
|
|
23
|
+
const requested = normalizeVcsRepo(vcsRepo);
|
|
24
|
+
const normalized = normalizeVcsRepo(url);
|
|
25
|
+
if (normalized === requested) return true;
|
|
26
|
+
// Qualified URLs must retain their host. Only shorthand references use suffix matching.
|
|
27
|
+
if (requested.includes(":")) return false;
|
|
28
|
+
return normalized.endsWith(`/${requested}`) || normalized.endsWith(`:${requested}`);
|
|
29
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import type { ExecutorMeta } from "../../types";
|
|
3
|
+
import { scrubSecrets } from "../../utils/secret-scrubber";
|
|
3
4
|
import { BaseExecutor, type ExecutorResult } from "./base";
|
|
4
5
|
import { resolveWorkflowLlmConfig } from "./workflow-llm";
|
|
5
6
|
|
|
@@ -72,7 +73,7 @@ export async function executeRawLlm(
|
|
|
72
73
|
const isRateLimited =
|
|
73
74
|
httpStatus === 429 ||
|
|
74
75
|
httpStatus === 529 ||
|
|
75
|
-
(err instanceof Error && /rate.?limit|too many requests
|
|
76
|
+
(err instanceof Error && /rate.?limit|too many requests|\b529\b/i.test(err.message));
|
|
76
77
|
if (isRateLimited) {
|
|
77
78
|
throw err;
|
|
78
79
|
}
|
|
@@ -80,12 +81,16 @@ export async function executeRawLlm(
|
|
|
80
81
|
return {
|
|
81
82
|
status: "success",
|
|
82
83
|
output: { result: null, model: modelName },
|
|
83
|
-
error:
|
|
84
|
+
error: scrubSecrets(
|
|
85
|
+
`LLM call failed, using fallback port: ${err instanceof Error ? err.message : String(err)}`,
|
|
86
|
+
),
|
|
84
87
|
};
|
|
85
88
|
}
|
|
86
89
|
return {
|
|
87
90
|
status: "failed",
|
|
88
|
-
|
|
91
|
+
// Provider errors can echo the request key back; scrub before any
|
|
92
|
+
// consumer (journal, HTTP route, workflow step) sees the text.
|
|
93
|
+
error: scrubSecrets(`LLM call failed: ${err instanceof Error ? err.message : String(err)}`),
|
|
89
94
|
};
|
|
90
95
|
}
|
|
91
96
|
}
|