@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
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
// Model profiles CRUD (runbooks/model-tiers.md § Model profiles).
|
|
2
|
+
//
|
|
3
|
+
// A profile is a named harness + model + optional effort. Lead picks one per
|
|
4
|
+
// task (`profile` on send-task / POST /api/tasks) or sets it as an agent's
|
|
5
|
+
// default (PATCH /api/agents/{id}/runtime { profile }); claim resolves it.
|
|
6
|
+
// Reads are open to every caller; writes are operator-only (`models.profiles.write`).
|
|
7
|
+
|
|
8
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
9
|
+
import { isAlias } from "@desplega/model-catalog";
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
import { resolveHttpAuditUserId } from "../be/audit-user";
|
|
12
|
+
import {
|
|
13
|
+
createModelProfile,
|
|
14
|
+
deleteModelProfile,
|
|
15
|
+
getModelProfileByName,
|
|
16
|
+
listModelProfiles,
|
|
17
|
+
modelProfileReferences,
|
|
18
|
+
updateModelProfile,
|
|
19
|
+
} from "../be/db/model-profiles";
|
|
20
|
+
import { explicitModelError } from "../be/model-validation";
|
|
21
|
+
import { supportedReasoningLevels } from "../providers/reasoning-effort";
|
|
22
|
+
import { can } from "../rbac";
|
|
23
|
+
import {
|
|
24
|
+
ModelProfileNameSchema,
|
|
25
|
+
ModelProfileSchema,
|
|
26
|
+
type ProviderName,
|
|
27
|
+
ProviderNameSchema,
|
|
28
|
+
type ReasoningEffort,
|
|
29
|
+
ReasoningEffortSchema,
|
|
30
|
+
} from "../types";
|
|
31
|
+
import { agentFirstPrincipal } from "./request-principal";
|
|
32
|
+
import { route } from "./route-def";
|
|
33
|
+
import { jsonError } from "./utils";
|
|
34
|
+
|
|
35
|
+
const listProfiles = route({
|
|
36
|
+
method: "get",
|
|
37
|
+
path: "/api/model-profiles",
|
|
38
|
+
pattern: ["api", "model-profiles"],
|
|
39
|
+
summary: "List model profiles",
|
|
40
|
+
description:
|
|
41
|
+
"Every model profile, ordered by harness then name. Filter by `harness` to see what an agent on that harness can run.",
|
|
42
|
+
tags: ["Models"],
|
|
43
|
+
query: z.object({ harness: ProviderNameSchema.optional() }),
|
|
44
|
+
responses: {
|
|
45
|
+
200: {
|
|
46
|
+
description: "Model profiles",
|
|
47
|
+
schema: z.object({ profiles: z.array(ModelProfileSchema) }),
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const getProfile = route({
|
|
53
|
+
method: "get",
|
|
54
|
+
path: "/api/model-profiles/{name}",
|
|
55
|
+
pattern: ["api", "model-profiles", null],
|
|
56
|
+
summary: "Get one model profile",
|
|
57
|
+
tags: ["Models"],
|
|
58
|
+
params: z.object({ name: z.string() }),
|
|
59
|
+
responses: {
|
|
60
|
+
200: { description: "The model profile", schema: ModelProfileSchema },
|
|
61
|
+
404: { description: "No profile with that name" },
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const createProfile = route({
|
|
66
|
+
method: "post",
|
|
67
|
+
path: "/api/model-profiles",
|
|
68
|
+
pattern: ["api", "model-profiles"],
|
|
69
|
+
summary: "Create a model profile",
|
|
70
|
+
description:
|
|
71
|
+
"A profile runs on one harness. `model` must run on that harness and be in the model catalog unless `allowCustomModel` is true (a gateway id, a fresh launch); a `latest:` alias is accepted and resolves at claim. `effort` must be a level the harness and model support. Operator only (the shared API key).",
|
|
72
|
+
tags: ["Models"],
|
|
73
|
+
rbac: { permission: "models.profiles.write" },
|
|
74
|
+
body: z.object({
|
|
75
|
+
name: ModelProfileNameSchema,
|
|
76
|
+
harness: ProviderNameSchema,
|
|
77
|
+
model: z.string().trim().min(1),
|
|
78
|
+
effort: ReasoningEffortSchema.nullable().optional(),
|
|
79
|
+
allowCustomModel: z.boolean().optional().default(false),
|
|
80
|
+
description: z.string().trim().max(500).nullable().optional(),
|
|
81
|
+
}),
|
|
82
|
+
responses: {
|
|
83
|
+
201: { description: "Profile created", schema: ModelProfileSchema },
|
|
84
|
+
400: { description: "Validation error" },
|
|
85
|
+
403: { description: "Forbidden" },
|
|
86
|
+
409: { description: "A profile with that name exists" },
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const updateProfile = route({
|
|
91
|
+
method: "patch",
|
|
92
|
+
path: "/api/model-profiles/{name}",
|
|
93
|
+
pattern: ["api", "model-profiles", null],
|
|
94
|
+
summary: "Update a model profile",
|
|
95
|
+
description:
|
|
96
|
+
"Changes the model, effort, custom-model flag or description; the name and harness are fixed. Omit a field to keep it; `effort: null` clears it. Applies to tasks claimed after the change. Operator only (the shared API key).",
|
|
97
|
+
tags: ["Models"],
|
|
98
|
+
rbac: { permission: "models.profiles.write" },
|
|
99
|
+
params: z.object({ name: z.string() }),
|
|
100
|
+
body: z.object({
|
|
101
|
+
model: z.string().trim().min(1).optional(),
|
|
102
|
+
effort: ReasoningEffortSchema.nullable().optional(),
|
|
103
|
+
allowCustomModel: z.boolean().optional(),
|
|
104
|
+
description: z.string().trim().max(500).nullable().optional(),
|
|
105
|
+
}),
|
|
106
|
+
responses: {
|
|
107
|
+
200: { description: "Profile updated", schema: ModelProfileSchema },
|
|
108
|
+
400: { description: "Validation error" },
|
|
109
|
+
403: { description: "Forbidden" },
|
|
110
|
+
404: { description: "No profile with that name" },
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const deleteProfile = route({
|
|
115
|
+
method: "delete",
|
|
116
|
+
path: "/api/model-profiles/{name}",
|
|
117
|
+
pattern: ["api", "model-profiles", null],
|
|
118
|
+
summary: "Delete a model profile",
|
|
119
|
+
description:
|
|
120
|
+
"Refused (409) for a shipped `<harness>-<tier>` seed (edit it instead; it would come back at the next boot), and while an agent has the profile as its default or a task holds it (unfinished, or superseded and still owed its resume). Operator only (the shared API key).",
|
|
121
|
+
tags: ["Models"],
|
|
122
|
+
rbac: { permission: "models.profiles.write" },
|
|
123
|
+
params: z.object({ name: z.string() }),
|
|
124
|
+
responses: {
|
|
125
|
+
200: { description: "Profile deleted", schema: z.object({ deleted: z.literal(true) }) },
|
|
126
|
+
403: { description: "Forbidden" },
|
|
127
|
+
404: { description: "No profile with that name" },
|
|
128
|
+
409: { description: "The profile is a seed or still in use" },
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
/** Why `harness` + `model` + `effort` is not a valid profile, or null. */
|
|
133
|
+
async function profileValueError(input: {
|
|
134
|
+
harness: ProviderName;
|
|
135
|
+
model: string;
|
|
136
|
+
effort: ReasoningEffort | null | undefined;
|
|
137
|
+
allowCustomModel: boolean;
|
|
138
|
+
}): Promise<string | null> {
|
|
139
|
+
const modelError = await explicitModelError({
|
|
140
|
+
model: input.model,
|
|
141
|
+
allowCustomModel: input.allowCustomModel,
|
|
142
|
+
harnessProvider: input.harness,
|
|
143
|
+
});
|
|
144
|
+
if (modelError) return modelError;
|
|
145
|
+
// The levels the harness and model accept (as PATCH /runtime checks them); a
|
|
146
|
+
// supported level may still be a transport no-op, like Opencode `off`. An
|
|
147
|
+
// alias is judged once it resolves.
|
|
148
|
+
if (
|
|
149
|
+
input.effort &&
|
|
150
|
+
!isAlias(input.model) &&
|
|
151
|
+
!supportedReasoningLevels(input.harness, input.model).includes(input.effort)
|
|
152
|
+
) {
|
|
153
|
+
return `Effort "${input.effort}" is not supported by ${input.model} on ${input.harness}.`;
|
|
154
|
+
}
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Gate a write on `models.profiles.write`; writes a 403 and returns false on denial. */
|
|
159
|
+
async function canWriteProfiles(
|
|
160
|
+
req: IncomingMessage,
|
|
161
|
+
res: ServerResponse,
|
|
162
|
+
myAgentId: string | undefined,
|
|
163
|
+
): Promise<boolean> {
|
|
164
|
+
const decision = can({
|
|
165
|
+
principal: await agentFirstPrincipal(req, myAgentId),
|
|
166
|
+
verb: "models.profiles.write",
|
|
167
|
+
resource: { kind: "none" },
|
|
168
|
+
source: "http",
|
|
169
|
+
});
|
|
170
|
+
if (!decision.allow) {
|
|
171
|
+
jsonError(res, "Writing model profiles requires the operator", 403);
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export async function handleModelProfiles(
|
|
178
|
+
req: IncomingMessage,
|
|
179
|
+
res: ServerResponse,
|
|
180
|
+
pathSegments: string[],
|
|
181
|
+
queryParams: URLSearchParams,
|
|
182
|
+
myAgentId: string | undefined,
|
|
183
|
+
): Promise<boolean> {
|
|
184
|
+
if (listProfiles.match(req.method, pathSegments)) {
|
|
185
|
+
const parsed = await listProfiles.parse(req, res, pathSegments, queryParams);
|
|
186
|
+
if (!parsed) return true;
|
|
187
|
+
listProfiles.respond(res, 200, {
|
|
188
|
+
profiles: await listModelProfiles({ harness: parsed.query.harness }),
|
|
189
|
+
});
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (getProfile.match(req.method, pathSegments)) {
|
|
194
|
+
const parsed = await getProfile.parse(req, res, pathSegments, queryParams);
|
|
195
|
+
if (!parsed) return true;
|
|
196
|
+
const profile = await getModelProfileByName(parsed.params.name);
|
|
197
|
+
if (!profile) {
|
|
198
|
+
jsonError(res, "Model profile not found", 404);
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
201
|
+
getProfile.respond(res, 200, profile);
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (createProfile.match(req.method, pathSegments)) {
|
|
206
|
+
const parsed = await createProfile.parse(req, res, pathSegments, queryParams);
|
|
207
|
+
if (!parsed) return true;
|
|
208
|
+
if (!(await canWriteProfiles(req, res, myAgentId))) return true;
|
|
209
|
+
const body = parsed.body;
|
|
210
|
+
const error = await profileValueError({ ...body, effort: body.effort ?? null });
|
|
211
|
+
if (error) {
|
|
212
|
+
jsonError(res, error, 400);
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
const created = await createModelProfile(body, {
|
|
216
|
+
userId: await resolveHttpAuditUserId(req, myAgentId),
|
|
217
|
+
});
|
|
218
|
+
if (!created) {
|
|
219
|
+
jsonError(res, `Model profile "${body.name}" already exists`, 409);
|
|
220
|
+
return true;
|
|
221
|
+
}
|
|
222
|
+
createProfile.respond(res, 201, created);
|
|
223
|
+
return true;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (updateProfile.match(req.method, pathSegments)) {
|
|
227
|
+
const parsed = await updateProfile.parse(req, res, pathSegments, queryParams);
|
|
228
|
+
if (!parsed) return true;
|
|
229
|
+
if (!(await canWriteProfiles(req, res, myAgentId))) return true;
|
|
230
|
+
const current = await getModelProfileByName(parsed.params.name);
|
|
231
|
+
if (!current) {
|
|
232
|
+
jsonError(res, "Model profile not found", 404);
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
const body = parsed.body;
|
|
236
|
+
const error = await profileValueError({
|
|
237
|
+
harness: current.harness,
|
|
238
|
+
model: body.model ?? current.model,
|
|
239
|
+
effort: body.effort === undefined ? current.effort : body.effort,
|
|
240
|
+
allowCustomModel: body.allowCustomModel ?? current.allowCustomModel,
|
|
241
|
+
});
|
|
242
|
+
if (error) {
|
|
243
|
+
jsonError(res, error, 400);
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
const updated = await updateModelProfile(current.name, body, {
|
|
247
|
+
userId: await resolveHttpAuditUserId(req, myAgentId),
|
|
248
|
+
});
|
|
249
|
+
if (!updated) {
|
|
250
|
+
jsonError(res, "Model profile not found", 404);
|
|
251
|
+
return true;
|
|
252
|
+
}
|
|
253
|
+
updateProfile.respond(res, 200, updated);
|
|
254
|
+
return true;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (deleteProfile.match(req.method, pathSegments)) {
|
|
258
|
+
const parsed = await deleteProfile.parse(req, res, pathSegments, queryParams);
|
|
259
|
+
if (!parsed) return true;
|
|
260
|
+
if (!(await canWriteProfiles(req, res, myAgentId))) return true;
|
|
261
|
+
const current = await getModelProfileByName(parsed.params.name);
|
|
262
|
+
if (!current) {
|
|
263
|
+
jsonError(res, "Model profile not found", 404);
|
|
264
|
+
return true;
|
|
265
|
+
}
|
|
266
|
+
if (current.seededFrom?.startsWith("tier:")) {
|
|
267
|
+
jsonError(
|
|
268
|
+
res,
|
|
269
|
+
`"${current.name}" is a shipped tier profile and comes back at the next boot; edit it instead.`,
|
|
270
|
+
409,
|
|
271
|
+
);
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
const refs = await modelProfileReferences(current.name);
|
|
275
|
+
if (refs.agentIds.length > 0 || refs.openTaskCount > 0) {
|
|
276
|
+
jsonError(
|
|
277
|
+
res,
|
|
278
|
+
`"${current.name}" is in use: default of ${refs.agentIds.length} agent(s), held by ${refs.openTaskCount} unfinished or awaiting-resume task(s).`,
|
|
279
|
+
409,
|
|
280
|
+
);
|
|
281
|
+
return true;
|
|
282
|
+
}
|
|
283
|
+
// The count above is for the message; the delete re-checks in the same
|
|
284
|
+
// statement (migration 220 trigger), so a reference written since is a 409.
|
|
285
|
+
const outcome = await deleteModelProfile(current.name);
|
|
286
|
+
if (outcome === "in-use") {
|
|
287
|
+
jsonError(
|
|
288
|
+
res,
|
|
289
|
+
`"${current.name}" is in use by a task or an agent default created just now.`,
|
|
290
|
+
409,
|
|
291
|
+
);
|
|
292
|
+
return true;
|
|
293
|
+
}
|
|
294
|
+
if (outcome === "missing") {
|
|
295
|
+
jsonError(res, "Model profile not found", 404);
|
|
296
|
+
return true;
|
|
297
|
+
}
|
|
298
|
+
deleteProfile.respond(res, 200, { deleted: true });
|
|
299
|
+
return true;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return false;
|
|
303
|
+
}
|
package/src/http/pages-public.ts
CHANGED
|
@@ -25,7 +25,11 @@ import type { IncomingMessage, ServerResponse } from "node:http";
|
|
|
25
25
|
import { z } from "zod";
|
|
26
26
|
import { getPage, incrementPageViewCount } from "../be/db";
|
|
27
27
|
import { BROWSER_SDK_JS, SWARM_UI_JS } from "../pages/browser-sdk";
|
|
28
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
feedbackKeyGuardScript,
|
|
30
|
+
feedbackOverlayScripts,
|
|
31
|
+
isPageFeedbackRequested,
|
|
32
|
+
} from "../pages/feedback-overlay";
|
|
29
33
|
import { type Page, PageSchema } from "../types";
|
|
30
34
|
import { getAppUrl, getConfiguredAppUrls } from "../utils/constants";
|
|
31
35
|
import { getNodeEnv } from "../utils/node-env";
|
|
@@ -170,8 +174,8 @@ const PAGE_HEAD_DEFAULTS = `<base target="_blank">
|
|
|
170
174
|
}
|
|
171
175
|
</style>`;
|
|
172
176
|
|
|
173
|
-
function injectBrowserSdk(html: string): string {
|
|
174
|
-
const injection = `${PAGE_HEAD_DEFAULTS}<script>${BROWSER_SDK_JS}</script><script>${SWARM_UI_JS}</script>`;
|
|
177
|
+
function injectBrowserSdk(html: string, headFirst = ""): string {
|
|
178
|
+
const injection = `${headFirst}${PAGE_HEAD_DEFAULTS}<script>${BROWSER_SDK_JS}</script><script>${SWARM_UI_JS}</script>`;
|
|
175
179
|
// Use the first occurrence of `<head>` (case-insensitive). A page that
|
|
176
180
|
// doesn't have a `<head>` element (raw fragment) still gets the SDK at the
|
|
177
181
|
// front of the document.
|
|
@@ -646,10 +650,12 @@ export async function handlePagesPublic(
|
|
|
646
650
|
// text/html — inject SDK + serve. Append the self-print snippet when the
|
|
647
651
|
// export button requested it, and the feedback overlay when
|
|
648
652
|
// `?__swarm-feedback` is on (the overlay hides itself in print).
|
|
653
|
+
// The feedback key guard goes first in <head> so it runs before page scripts.
|
|
654
|
+
const feedback = isPageFeedbackRequested(queryParams);
|
|
649
655
|
const html =
|
|
650
|
-
injectBrowserSdk(page.body) +
|
|
656
|
+
injectBrowserSdk(page.body, feedback ? feedbackKeyGuardScript() : "") +
|
|
651
657
|
(wantsPrint ? PRINT_AUTOTRIGGER_SCRIPT : "") +
|
|
652
|
-
(
|
|
658
|
+
(feedback ? feedbackOverlayScripts(page.id) : "");
|
|
653
659
|
const headers: Record<string, string> = {
|
|
654
660
|
"Content-Type": "text/html; charset=utf-8",
|
|
655
661
|
"Cache-Control": "no-store",
|
package/src/http/poll.ts
CHANGED
|
@@ -41,7 +41,9 @@ import { AgentTaskSchema, BudgetRefusedTriggerSchema, TaskAttachmentSchema } fro
|
|
|
41
41
|
import { ensure } from "../utils/business-use";
|
|
42
42
|
import { isLeadChannelMonitoringEnabled } from "../utils/lead-monitor-channels";
|
|
43
43
|
import { isMultiRuntimeEnabled } from "../utils/multi-runtime";
|
|
44
|
+
import { getRequestAuth, type HttpRequestAuth } from "../utils/request-auth-context";
|
|
44
45
|
import { isRuntimePoolsEnabled } from "../utils/runtime-pools-flag";
|
|
46
|
+
import { readsRepoSecrets } from "./config";
|
|
45
47
|
import { buildTaskRequestedBy, TaskRequestedBySchema } from "./requested-by";
|
|
46
48
|
import { route, runtimeInstanceHeader } from "./route-def";
|
|
47
49
|
import { jsonError } from "./utils";
|
|
@@ -208,8 +210,14 @@ async function attachmentsForTrigger(
|
|
|
208
210
|
* previous claim record is overwritten for the same reason.
|
|
209
211
|
*/
|
|
210
212
|
async function claimModelFields(
|
|
211
|
-
task: TaskModelPinFields & {
|
|
212
|
-
|
|
213
|
+
task: TaskModelPinFields & {
|
|
214
|
+
id: string;
|
|
215
|
+
profile?: string | null;
|
|
216
|
+
effort?: string | null;
|
|
217
|
+
vcsRepo?: string | null;
|
|
218
|
+
},
|
|
219
|
+
agent: Parameters<typeof recordClaimModelResolution>[1] & { isLead?: boolean },
|
|
220
|
+
auth: HttpRequestAuth | null,
|
|
213
221
|
): Promise<Awaited<ReturnType<typeof recordClaimModelResolution>> & { model: string | undefined }> {
|
|
214
222
|
const pin = taskModelPin(task);
|
|
215
223
|
const cleared = {
|
|
@@ -217,11 +225,14 @@ async function claimModelFields(
|
|
|
217
225
|
resolvedModel: undefined,
|
|
218
226
|
modelSource: undefined,
|
|
219
227
|
modelAlias: undefined,
|
|
228
|
+
resolvedProfile: undefined,
|
|
220
229
|
};
|
|
221
230
|
try {
|
|
222
231
|
return {
|
|
223
232
|
...cleared,
|
|
224
|
-
...(await recordClaimModelResolution({ ...task, model: pin }, agent
|
|
233
|
+
...(await recordClaimModelResolution({ ...task, model: pin }, agent, {
|
|
234
|
+
includeRepoSecrets: readsRepoSecrets(auth, agent),
|
|
235
|
+
})),
|
|
225
236
|
};
|
|
226
237
|
} catch (error) {
|
|
227
238
|
console.warn(`[/api/poll] model resolution failed for task ${task.id}:`, error);
|
|
@@ -495,7 +506,7 @@ export async function handlePoll(
|
|
|
495
506
|
triggerSurface: assignedTriggerSurface,
|
|
496
507
|
task: {
|
|
497
508
|
...pendingTask,
|
|
498
|
-
...(await claimModelFields(pendingTask, agent)),
|
|
509
|
+
...(await claimModelFields(pendingTask, agent, getRequestAuth(req))),
|
|
499
510
|
status: "in_progress",
|
|
500
511
|
attachments: await attachmentsForTrigger(pendingTask.id),
|
|
501
512
|
},
|
|
@@ -639,7 +650,7 @@ export async function handlePoll(
|
|
|
639
650
|
triggerSurface: claimedTriggerSurface,
|
|
640
651
|
task: {
|
|
641
652
|
...claimed,
|
|
642
|
-
...(await claimModelFields(claimed, agent)),
|
|
653
|
+
...(await claimModelFields(claimed, agent, getRequestAuth(req))),
|
|
643
654
|
attachments: await attachmentsForTrigger(claimed.id),
|
|
644
655
|
},
|
|
645
656
|
...(claimedRequestedBy && { requestedBy: claimedRequestedBy }),
|
package/src/http/script-runs.ts
CHANGED
|
@@ -611,7 +611,7 @@ export async function handleScriptRuns(
|
|
|
611
611
|
if (!parsed) return true;
|
|
612
612
|
const result = await executeRawLlm(parsed.body);
|
|
613
613
|
if (result.status === "failed") {
|
|
614
|
-
|
|
614
|
+
jsonError(res, result.error, 500);
|
|
615
615
|
return true;
|
|
616
616
|
}
|
|
617
617
|
rawLlmRoute.respond(res, 200, result.output);
|
package/src/http/scripts.ts
CHANGED
|
@@ -41,6 +41,7 @@ import {
|
|
|
41
41
|
import { can } from "../rbac";
|
|
42
42
|
import { extractScriptSignature, type ScriptSignature } from "../scripts-runtime/extract-signature";
|
|
43
43
|
import { runScript } from "../scripts-runtime/loader";
|
|
44
|
+
import { checkQuickJSCompat, quickJSCompatWarnings } from "../scripts-runtime/quickjs-compat";
|
|
44
45
|
import {
|
|
45
46
|
ScriptApiAuthModeSchema,
|
|
46
47
|
ScriptApiRecordSchema,
|
|
@@ -236,6 +237,10 @@ const upsertRoute = route({
|
|
|
236
237
|
name: z.string(),
|
|
237
238
|
version: z.number(),
|
|
238
239
|
contentDeduped: z.boolean(),
|
|
240
|
+
warnings: z
|
|
241
|
+
.array(z.string())
|
|
242
|
+
.optional()
|
|
243
|
+
.describe("Advisory quickjs-executor compatibility findings. Never blocks the save."),
|
|
239
244
|
}),
|
|
240
245
|
},
|
|
241
246
|
400: { description: "Validation or typecheck failure" },
|
|
@@ -424,7 +429,15 @@ const createScriptApiRoute = route({
|
|
|
424
429
|
params: idParamsSchema,
|
|
425
430
|
body: createScriptApiBodySchema,
|
|
426
431
|
responses: {
|
|
427
|
-
201: {
|
|
432
|
+
201: {
|
|
433
|
+
description: "Endpoint created",
|
|
434
|
+
schema: scriptApiWithSecretSchema.extend({
|
|
435
|
+
warnings: z
|
|
436
|
+
.array(z.string())
|
|
437
|
+
.optional()
|
|
438
|
+
.describe("Advisory quickjs-executor compatibility findings for the script."),
|
|
439
|
+
}),
|
|
440
|
+
},
|
|
428
441
|
400: { description: "Validation error or script has no owning agent" },
|
|
429
442
|
404: { description: "Script not found" },
|
|
430
443
|
},
|
|
@@ -702,10 +715,14 @@ export async function handleScripts(
|
|
|
702
715
|
});
|
|
703
716
|
}
|
|
704
717
|
|
|
718
|
+
const warnings = quickJSCompatWarnings(
|
|
719
|
+
checkQuickJSCompat(parsed.body.source, { fsMode: parsed.body.fsMode }),
|
|
720
|
+
);
|
|
705
721
|
upsertRoute.respond(res, 200, {
|
|
706
722
|
name: result.script.name,
|
|
707
723
|
version: result.script.version,
|
|
708
724
|
contentDeduped: result.contentDeduped,
|
|
725
|
+
...(warnings.length > 0 ? { warnings } : {}),
|
|
709
726
|
});
|
|
710
727
|
return true;
|
|
711
728
|
}
|
|
@@ -1092,7 +1109,13 @@ export async function handleScripts(
|
|
|
1092
1109
|
label: parsed.body.label ?? null,
|
|
1093
1110
|
createdBy: await resolveHttpAuditUserId(req, agentId),
|
|
1094
1111
|
});
|
|
1095
|
-
|
|
1112
|
+
const warnings = quickJSCompatWarnings(
|
|
1113
|
+
checkQuickJSCompat(script.source, { fsMode: script.fsMode }),
|
|
1114
|
+
);
|
|
1115
|
+
createScriptApiRoute.respond(res, 201, {
|
|
1116
|
+
...endpoint,
|
|
1117
|
+
...(warnings.length > 0 ? { warnings } : {}),
|
|
1118
|
+
});
|
|
1096
1119
|
return true;
|
|
1097
1120
|
}
|
|
1098
1121
|
|
package/src/http/tasks.ts
CHANGED
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
updateTaskTitle,
|
|
36
36
|
updateTaskVcs,
|
|
37
37
|
} from "../be/db";
|
|
38
|
+
import { modelProfileRefError, taskProfileError } from "../be/db/model-profiles";
|
|
38
39
|
import { explicitModelErrorForAgent } from "../be/model-validation";
|
|
39
40
|
import {
|
|
40
41
|
getTaskSteeringFields,
|
|
@@ -287,6 +288,8 @@ const createTask = route({
|
|
|
287
288
|
*/
|
|
288
289
|
allowCustomModel: z.boolean().optional(),
|
|
289
290
|
effort: ReasoningEffortSchema.optional(),
|
|
291
|
+
/** Model profile name, applied at claim. Exclusive with `model` and `modelTier`. */
|
|
292
|
+
profile: z.string().trim().min(1).optional(),
|
|
290
293
|
/**
|
|
291
294
|
* Create in `draft` status instead of the normal pending/unassigned/offered
|
|
292
295
|
* status (#1240) — the task exists and is visible to its owner, but is not
|
|
@@ -966,6 +969,15 @@ export async function handleTasks(
|
|
|
966
969
|
jsonError(res, modelError, 400);
|
|
967
970
|
return true;
|
|
968
971
|
}
|
|
972
|
+
const profileError = await taskProfileError({
|
|
973
|
+
profile: parsed.body.profile,
|
|
974
|
+
...splitLegacyModelAlias({ model: parsed.body.model, modelTier: parsed.body.modelTier }),
|
|
975
|
+
agentId: defaultAgentId,
|
|
976
|
+
});
|
|
977
|
+
if (profileError) {
|
|
978
|
+
jsonError(res, profileError, 400);
|
|
979
|
+
return true;
|
|
980
|
+
}
|
|
969
981
|
|
|
970
982
|
const parentTask = parsed.body.parentTaskId
|
|
971
983
|
? await getTaskById(parsed.body.parentTaskId)
|
|
@@ -1057,6 +1069,7 @@ export async function handleTasks(
|
|
|
1057
1069
|
modelTier: parsed.body.modelTier,
|
|
1058
1070
|
}),
|
|
1059
1071
|
effort: parsed.body.effort,
|
|
1072
|
+
profile: parsed.body.profile,
|
|
1060
1073
|
},
|
|
1061
1074
|
{ origin: "rest", allowCustomModel: parsed.body.allowCustomModel },
|
|
1062
1075
|
);
|
|
@@ -1087,6 +1100,11 @@ export async function handleTasks(
|
|
|
1087
1100
|
jsonError(res, error.message, 400);
|
|
1088
1101
|
return true;
|
|
1089
1102
|
}
|
|
1103
|
+
// The profile was deleted between taskProfileError and the insert.
|
|
1104
|
+
if (modelProfileRefError(error) === "missing") {
|
|
1105
|
+
jsonError(res, `Unknown model profile "${parsed.body.profile}"`, 400);
|
|
1106
|
+
return true;
|
|
1107
|
+
}
|
|
1090
1108
|
console.error("[HTTP] Failed to create task:", error);
|
|
1091
1109
|
jsonError(res, "Failed to create task", 500);
|
|
1092
1110
|
}
|
package/src/http/webhooks.ts
CHANGED
|
@@ -757,9 +757,8 @@ export async function handleWebhooks(
|
|
|
757
757
|
payload.message &&
|
|
758
758
|
!isInboxAllowed(payload.message.inbox_id, process.env.AGENTMAIL_INBOX_DOMAIN_FILTER)
|
|
759
759
|
) {
|
|
760
|
-
const domain = payload.message.inbox_id.split("@")[1] ?? "unknown";
|
|
761
760
|
console.log(
|
|
762
|
-
`[AgentMail] Ignoring event for inbox
|
|
761
|
+
`[AgentMail] Ignoring event for inbox "${payload.message.inbox_id}" (not in AGENTMAIL_INBOX_DOMAIN_FILTER)`,
|
|
763
762
|
);
|
|
764
763
|
return true;
|
|
765
764
|
}
|
|
@@ -25,6 +25,43 @@ export function isPageFeedbackRequested(queryParams: URLSearchParams): boolean {
|
|
|
25
25
|
return value !== "0" && value !== "false";
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
// Keys typed in the overlay must never reach the page's own key handlers. A
|
|
29
|
+
// listener on the overlay runs too late: page capture listeners on window and
|
|
30
|
+
// document fire first. So this guard is served at the top of <head>, before any
|
|
31
|
+
// page script, and is the first window capture listener. For a key from the
|
|
32
|
+
// overlay it stops dispatch outright and hands the event to the overlay
|
|
33
|
+
// (window.__swarmFeedbackKeys.handle). A press that started in the overlay
|
|
34
|
+
// stays owned until its keyup, so the release is swallowed even after the
|
|
35
|
+
// composer closed and focus moved to the page. Every other key passes through.
|
|
36
|
+
// The overlay script repeats it as a fallback; the second run is a no-op.
|
|
37
|
+
const FEEDBACK_KEY_GUARD_JS = `
|
|
38
|
+
(function () {
|
|
39
|
+
if (window.__swarmFeedbackKeys) return;
|
|
40
|
+
var keys = window.__swarmFeedbackKeys = { host: null, handle: null };
|
|
41
|
+
var owned = {};
|
|
42
|
+
function id(e) { return e.code || e.key; }
|
|
43
|
+
function fromOverlay(e) {
|
|
44
|
+
return !!keys.host && typeof e.composedPath === 'function' && e.composedPath().indexOf(keys.host) !== -1;
|
|
45
|
+
}
|
|
46
|
+
function guard(e) {
|
|
47
|
+
var k = id(e);
|
|
48
|
+
var mine = fromOverlay(e);
|
|
49
|
+
if (!mine && !owned[k]) return;
|
|
50
|
+
if (e.type === 'keydown') {
|
|
51
|
+
if (mine) owned[k] = true;
|
|
52
|
+
else if (!e.repeat) { delete owned[k]; return; }
|
|
53
|
+
}
|
|
54
|
+
if (e.type === 'keyup') delete owned[k];
|
|
55
|
+
e.stopImmediatePropagation();
|
|
56
|
+
if (mine && keys.handle) keys.handle(e);
|
|
57
|
+
}
|
|
58
|
+
['keydown', 'keypress', 'keyup'].forEach(function (type) {
|
|
59
|
+
window.addEventListener(type, guard, true);
|
|
60
|
+
});
|
|
61
|
+
window.addEventListener('blur', function () { owned = {}; });
|
|
62
|
+
})();
|
|
63
|
+
`;
|
|
64
|
+
|
|
28
65
|
const FEEDBACK_OVERLAY_JS = `
|
|
29
66
|
(function () {
|
|
30
67
|
var cfg = window.__swarmFeedback;
|
|
@@ -135,6 +172,7 @@ const FEEDBACK_OVERLAY_JS = `
|
|
|
135
172
|
var panel = document.createElement('div');
|
|
136
173
|
panel.className = 'panel';
|
|
137
174
|
var composer = null;
|
|
175
|
+
var composerInput = null;
|
|
138
176
|
root.appendChild(hl);
|
|
139
177
|
root.appendChild(markers);
|
|
140
178
|
root.appendChild(panel);
|
|
@@ -207,11 +245,25 @@ const FEEDBACK_OVERLAY_JS = `
|
|
|
207
245
|
}
|
|
208
246
|
render();
|
|
209
247
|
}
|
|
210
|
-
|
|
211
|
-
if (e.key !== 'Escape') return;
|
|
248
|
+
function onEscape() {
|
|
212
249
|
if (state.draft) { closeComposer(); return; }
|
|
213
250
|
if (state.selecting) setSelecting(false);
|
|
251
|
+
}
|
|
252
|
+
// Escape pressed on the page while selecting. Keys typed in the overlay never
|
|
253
|
+
// get here: the key guard stops them and calls onOverlayKey instead.
|
|
254
|
+
document.addEventListener('keydown', function (e) {
|
|
255
|
+
if (e.key === 'Escape') onEscape();
|
|
214
256
|
}, true);
|
|
257
|
+
function onOverlayKey(e) {
|
|
258
|
+
if (e.type !== 'keydown') return;
|
|
259
|
+
if (e.key === 'Escape') { onEscape(); return; }
|
|
260
|
+
if (e.key === 'Enter' && (e.metaKey || e.ctrlKey) && composerInput && e.composedPath()[0] === composerInput) {
|
|
261
|
+
e.preventDefault();
|
|
262
|
+
commitDraft(composerInput.value);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
window.__swarmFeedbackKeys.host = host;
|
|
266
|
+
window.__swarmFeedbackKeys.handle = onOverlayKey;
|
|
215
267
|
|
|
216
268
|
// ─── Composer ──────────────────────────────────────────────────────────
|
|
217
269
|
function openComposer(target) {
|
|
@@ -230,9 +282,7 @@ const FEEDBACK_OVERLAY_JS = `
|
|
|
230
282
|
add.type = 'button';
|
|
231
283
|
cancel.addEventListener('click', closeComposer);
|
|
232
284
|
add.addEventListener('click', function () { commitDraft(input.value); });
|
|
233
|
-
|
|
234
|
-
if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) { e.preventDefault(); commitDraft(input.value); }
|
|
235
|
-
});
|
|
285
|
+
composerInput = input;
|
|
236
286
|
row.appendChild(cancel);
|
|
237
287
|
row.appendChild(add);
|
|
238
288
|
composer.appendChild(row);
|
|
@@ -253,6 +303,7 @@ const FEEDBACK_OVERLAY_JS = `
|
|
|
253
303
|
function closeComposer() {
|
|
254
304
|
if (composer) composer.remove();
|
|
255
305
|
composer = null;
|
|
306
|
+
composerInput = null;
|
|
256
307
|
state.draft = null;
|
|
257
308
|
hideHighlight();
|
|
258
309
|
}
|
|
@@ -524,5 +575,13 @@ const FEEDBACK_OVERLAY_JS = `
|
|
|
524
575
|
*/
|
|
525
576
|
export function feedbackOverlayScripts(pageId: string): string {
|
|
526
577
|
const config = JSON.stringify({ pageId }).replace(/</g, "\\u003c");
|
|
527
|
-
return `<script>window.__swarmFeedback = ${config};</script><script>${FEEDBACK_OVERLAY_JS}</script>`;
|
|
578
|
+
return `<script>window.__swarmFeedback = ${config};</script><script>${FEEDBACK_KEY_GUARD_JS}${FEEDBACK_OVERLAY_JS}</script>`;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* Key guard served at the top of `<head>` when feedback mode is on, so it runs
|
|
583
|
+
* before any page script and its window capture listeners come first.
|
|
584
|
+
*/
|
|
585
|
+
export function feedbackKeyGuardScript(): string {
|
|
586
|
+
return `<script>${FEEDBACK_KEY_GUARD_JS}</script>`;
|
|
528
587
|
}
|
|
@@ -359,7 +359,7 @@ const copilotTargetProfile: AcpTargetProfile = {
|
|
|
359
359
|
},
|
|
360
360
|
};
|
|
361
361
|
|
|
362
|
-
/** Qwen Code's `--auth-type` choices (0.25.
|
|
362
|
+
/** Qwen Code's `--auth-type` choices (0.25.2). `qwen-oauth` needs a browser, so it is left out. */
|
|
363
363
|
const QWEN_AUTH_TYPES = ["openai", "openai-responses", "anthropic", "gemini", "vertex-ai"] as const;
|
|
364
364
|
|
|
365
365
|
// Qwen Code is a Gemini CLI fork: QWEN_SYSTEM_MD replaces its built-in prompt
|
|
@@ -44,7 +44,7 @@ import type {
|
|
|
44
44
|
* default; the image installs exactly this version and the per-task settings
|
|
45
45
|
* set `amp.updates.mode: "disabled"`. Bump it deliberately, with a live run.
|
|
46
46
|
*/
|
|
47
|
-
export const AMP_PACKAGE = "@ampcode/cli@0.0.
|
|
47
|
+
export const AMP_PACKAGE = "@ampcode/cli@0.0.1792152053-g57ff9b";
|
|
48
48
|
|
|
49
49
|
/** MCP server name. Amp exposes its tools as `agent_swarm` / `store_progress` (dashes become underscores). */
|
|
50
50
|
const AMP_MCP_SERVER_NAME = "agent-swarm";
|