@desplega.ai/agent-swarm 1.129.0 → 1.131.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/{actions-kfv2dawe.js → actions-w53mbns9.js} +11 -11
- package/dist/{anthropic-messages-3xgme8my.js → anthropic-messages-rjmd2e7n.js} +5 -5
- package/dist/{app-q8vjk6e8.js → app-m9gwqsqv.js} +8 -8
- package/dist/{artifact-hg4t1nx7.js → artifact-jysj3wrg.js} +2 -2
- package/dist/{assistant-g037r345.js → assistant-bp65m6f3.js} +13 -13
- package/dist/{azure-openai-responses-xa4sh9f6.js → azure-openai-responses-c0yjmbfd.js} +8 -5
- package/dist/{boot-reembed-18pzsrxa.js → boot-reembed-62p0at2k.js} +6 -6
- package/dist/{boot-reembed-wpks2cj8.js → boot-reembed-xmfp6wdh.js} +7 -7
- package/dist/{boot-scrub-logs-n7szd57r.js → boot-scrub-logs-rr8q2dms.js} +5 -5
- package/dist/{claude-adapter-qz4r0ajt.js → claude-adapter-2y47mrkd.js} +9 -9
- package/dist/{cli-mhjnbgb6.js → cli-0rcyq6d9.js} +450 -186
- package/dist/{cli-ttkmehy0.js → cli-2q72qz2s.js} +274 -25
- package/dist/{cli-gztgc3hz.js → cli-2wadx7p2.js} +2 -2
- package/dist/cli-3fj60etf.js +153 -0
- package/dist/{cli-7ecgm5v0.js → cli-3kf51j15.js} +1 -1
- package/dist/{cli-vgexpfsr.js → cli-57khth04.js} +6 -3
- package/dist/{cli-2mbe7vk0.js → cli-5dsbcjjw.js} +1 -1
- package/dist/{cli-etr62zy6.js → cli-5kx36evn.js} +2 -2
- package/dist/{cli-v2tr6amf.js → cli-5n6e5yfn.js} +30 -8
- package/dist/{cli-hx02s9zq.js → cli-5v4rw6yd.js} +2 -2
- package/dist/{cli-v37yr6mf.js → cli-5ze9rv97.js} +4 -4
- package/dist/{cli-ch7t5v7n.js → cli-6map995v.js} +9 -4
- package/dist/{cli-h0457a67.js → cli-70xqj0z3.js} +3 -3
- package/dist/{cli-kk4f9r17.js → cli-72q8m8gk.js} +8 -6
- package/dist/{cli-7padk69a.js → cli-78xwvkhj.js} +2 -2
- package/dist/{cli-dxtxacy7.js → cli-9dr5qzc0.js} +45 -8
- package/dist/{cli-17zxdgsq.js → cli-a40rnf71.js} +6 -6
- package/dist/{cli-t53vnnxz.js → cli-a4jzdpa3.js} +2514 -1055
- package/dist/{cli-w10fkrf1.js → cli-akdvwc9v.js} +354 -115
- package/dist/{cli-1qkk5er0.js → cli-bmze986d.js} +1 -1
- package/dist/{cli-ga12dc1d.js → cli-bqw0mt1n.js} +3 -3
- package/dist/{cli-0sf92efp.js → cli-cdeg08wx.js} +1 -1
- package/dist/{cli-a8ynhkyj.js → cli-cvgjwnh1.js} +17 -15
- package/dist/{cli-9fxg3v5m.js → cli-d2tfkcga.js} +4 -4
- package/dist/{cli-nhcd7x43.js → cli-d3x3x1c9.js} +2 -2
- package/dist/{cli-6nh2qxvh.js → cli-darcwax3.js} +1 -1
- package/dist/{cli-yjbgmysx.js → cli-e7ckcg4v.js} +12 -12
- package/dist/{cli-4n2gcjpd.js → cli-efxfnq6q.js} +4 -3
- package/dist/{cli-z9xqqhfk.js → cli-er3a4bb0.js} +1 -1
- package/dist/{cli-xg2eezyv.js → cli-f1am9szv.js} +1 -1
- package/dist/{cli-xy4cwsm9.js → cli-fywms69x.js} +1 -1
- package/dist/{cli-33jj76y8.js → cli-g6b8x1h8.js} +1 -1
- package/dist/{cli-epsvdckx.js → cli-gm9cbhbq.js} +20 -10
- package/dist/{cli-rkndnn90.js → cli-k9qkf1hw.js} +6 -4
- package/dist/{cli-zejvt395.js → cli-kh3ba2zf.js} +1 -1
- package/dist/{cli-gb25p1h2.js → cli-nm5gtp1x.js} +2 -2
- package/dist/{cli-akxnpx17.js → cli-pm6xbh84.js} +1829 -1629
- package/dist/{cli-2k0ztbfp.js → cli-psh3c4va.js} +1 -1
- package/dist/{cli-39vgrsep.js → cli-qv61hp37.js} +2 -2
- package/dist/{cli-hv2fndhm.js → cli-r6kqqbj0.js} +6 -4
- package/dist/{cli-1y1cm570.js → cli-rzbpmxdr.js} +1 -1
- package/dist/{cli-mmrk00vp.js → cli-t1xze5qh.js} +1 -1
- package/dist/{cli-g252a9ky.js → cli-t55yw3a1.js} +2 -2
- package/dist/{cli-7bz8dzqa.js → cli-t76vw5db.js} +1184 -1522
- package/dist/{cli-4n4mdv21.js → cli-xpxde7rm.js} +7 -3
- package/dist/{cli-fpaa5k2q.js → cli-yw8vghx2.js} +54 -18
- package/dist/cli-zsspvvm2.js +490 -0
- package/dist/cli.js +27 -25
- package/dist/{codex-adapter-7v6t7ff6.js → codex-adapter-d24q75wk.js} +7 -7
- package/dist/{codex-hook-amndawyb.js → codex-hook-hq54t9ft.js} +2 -2
- package/dist/{codex-session-runner-e7j4khe2.js → codex-session-runner-g50vcsex.js} +7 -7
- package/dist/{commands-ex0k5tbf.js → commands-c3nrc5hg.js} +5 -5
- package/dist/{db-df7jatfc.js → db-nt7fg3tw.js} +5 -5
- package/dist/{google-generative-ai-y42hnw9p.js → google-generative-ai-hysrd8qx.js} +7 -5
- package/dist/{google-vertex-4sg66gb6.js → google-vertex-p5arrga0.js} +7 -5
- package/dist/{handlers-1j2cfqt2.js → handlers-fkr6swat.js} +13 -13
- package/dist/{hook-nx9a0mj3.js → hook-77wpvnjc.js} +7 -7
- package/dist/{hook-w6yaf6sy.js → hook-g019yehy.js} +8 -8
- package/dist/{http-4tz5h8wg.js → http-jewj0syk.js} +2993 -716
- package/dist/{index-0bpkzb09.js → index-070v4eyb.js} +3 -3
- package/dist/{index-0s67qv7h.js → index-6gm5he8h.js} +11 -11
- package/dist/{index-35gtv6gj.js → index-7n2xpbd4.js} +310 -40
- package/dist/{index-ker3gccb.js → index-9znhar3x.js} +4 -4
- package/dist/{index-5n2c8yay.js → index-hase6zbe.js} +2 -2
- package/dist/{index-kcf3tm9h.js → index-hfvggd0z.js} +12 -12
- package/dist/{index-7c0jqna0.js → index-hp1kdbjj.js} +13 -13
- package/dist/{keepalive-3j4a9168.js → keepalive-j0fjvqfd.js} +7 -7
- package/dist/{lead-6kpevymb.js → lead-1n8x30xe.js} +33 -33
- package/dist/{maintenance-m1k82ean.js → maintenance-gvev11sa.js} +7 -7
- package/dist/{mistral-conversations-bjkdfb6z.js → mistral-conversations-81fwav0j.js} +6 -6
- package/dist/{oauth-refresh-sweep-881k8e4q.js → oauth-refresh-sweep-s9ecsvh0.js} +6 -6
- package/dist/{onboard-c0ykffjn.js → onboard-pvm5b38w.js} +2 -2
- package/dist/{openai-codex-responses-xetseqvt.js → openai-codex-responses-5g27pd6f.js} +43 -23
- package/dist/{openai-completions-5r2g5axa.js → openai-completions-3vkmfz2a.js} +55 -11
- package/dist/{openai-responses-ce67hd23.js → openai-responses-dg4t7j7a.js} +8 -5
- package/dist/{openrouter-images-pzfp22jb.js → openrouter-images-g3gm8931.js} +1 -1
- package/dist/{otel-impl-sqagjerv.js → otel-impl-p6jvgd9c.js} +6 -4
- package/dist/{otel-3mp81ppd.js → otel-nqa12wf3.js} +2 -2
- package/dist/{pi-mono-adapter-k584k16g.js → pi-mono-adapter-t091s27g.js} +17117 -7517
- package/dist/{pricing-refresh-y82whbf9.js → pricing-refresh-k49rcyed.js} +7 -7
- package/dist/{rbac-roles-nvkqmzyd.js → rbac-roles-30pspvy7.js} +7 -7
- package/dist/{rbac-roles-4ht8gegc.js → rbac-roles-cr80g96t.js} +6 -6
- package/dist/{render-v2-wvqdgvvp.js → render-v2-xm457ng1.js} +8 -8
- package/dist/{seed-pricing-fy3rxhde.js → seed-pricing-yhpv35fr.js} +6 -6
- package/dist/{setup-ak5cnc6f.js → setup-nytnmwwr.js} +2 -2
- package/dist/{worker-d8769q8a.js → worker-msytr2cw.js} +33 -33
- package/openapi.json +34039 -11036
- package/package.json +8 -6
- package/src/apps/definition.ts +368 -26
- package/src/apps/row-store.ts +66 -10
- package/src/apps/schema-migrate.ts +95 -1
- package/src/apps/store.ts +13 -4
- package/src/apps/sync-run-as.ts +18 -0
- package/src/apps/sync.ts +934 -0
- package/src/apps/version.ts +16 -2
- package/src/assets/key.ts +8 -1
- package/src/be/asset-key-audit.ts +3 -1
- package/src/be/db-queries/mcp-oauth.ts +297 -24
- package/src/be/db.ts +42 -1
- package/src/be/migrations/129_asset_keys_apps_scripts.sql +161 -0
- package/src/be/scripts/db.ts +5 -2
- package/src/be/scripts/typecheck.ts +1 -0
- package/src/be/seed-scripts/catalog/app-sync-run.ts +32 -0
- package/src/be/seed-scripts/catalog/github-issues-pull.ts +102 -0
- package/src/be/seed-scripts/index.ts +18 -0
- package/src/be/seed-skills/index.ts +7 -0
- package/src/http/active-sessions.ts +26 -18
- package/src/http/agents.ts +94 -27
- package/src/http/api-keys.ts +91 -17
- package/src/http/approval-requests.ts +87 -10
- package/src/http/apps.ts +361 -53
- package/src/http/assets.ts +165 -31
- package/src/http/budgets.ts +7 -5
- package/src/http/codex-oauth-keep-warm.ts +21 -3
- package/src/http/config.ts +46 -16
- package/src/http/context.ts +33 -6
- package/src/http/ecosystem.ts +20 -6
- package/src/http/events.ts +24 -9
- package/src/http/favorites.ts +20 -6
- package/src/http/fs.ts +60 -17
- package/src/http/heartbeat.ts +18 -4
- package/src/http/inbox-state.ts +12 -6
- package/src/http/integrations.ts +25 -5
- package/src/http/kv.ts +36 -15
- package/src/http/mcp-bridge.ts +5 -1
- package/src/http/mcp-oauth.ts +332 -17
- package/src/http/mcp-servers.ts +56 -18
- package/src/http/memory.ts +282 -29
- package/src/http/metrics.ts +80 -17
- package/src/http/oauth-callback.ts +10 -4
- package/src/http/oauth-generic.ts +5 -1
- package/src/http/oauth-locks.ts +6 -3
- package/src/http/openapi.ts +57 -9
- package/src/http/page-proxy.ts +25 -5
- package/src/http/pages-public.ts +37 -20
- package/src/http/pages.ts +93 -28
- package/src/http/poll.ts +89 -5
- package/src/http/pricing.ts +9 -6
- package/src/http/prompt-templates.ts +85 -21
- package/src/http/route-def.ts +67 -4
- package/src/http/schedules.ts +63 -14
- package/src/http/script-connection-proxy.ts +30 -4
- package/src/http/script-connections.ts +393 -43
- package/src/http/script-runs.ts +73 -19
- package/src/http/scripts.ts +257 -39
- package/src/http/session-data.ts +87 -14
- package/src/http/sessions.ts +80 -5
- package/src/http/skills.ts +115 -35
- package/src/http/stats.ts +94 -13
- package/src/http/task-templates.ts +6 -4
- package/src/http/tasks.ts +230 -61
- package/src/http/trackers/jira.ts +90 -30
- package/src/http/trackers/linear.ts +46 -12
- package/src/http/users.ts +162 -25
- package/src/http/utils.ts +12 -2
- package/src/http/webhooks.ts +30 -16
- package/src/http/workflow-events.ts +18 -5
- package/src/http/workflows.ts +128 -39
- package/src/http/x.ts +37 -6
- package/src/oauth/ensure-mcp-token.ts +16 -1
- package/src/oauth/mcp-wrapper.ts +281 -26
- package/src/providers/pi-mono-adapter.ts +1 -1
- package/src/scripts-runtime/credential-broker/README.md +1 -1
- package/src/scripts-runtime/credential-broker/fetch-patch.ts +12 -2
- package/src/scripts-runtime/sdk-allowlist.ts +1 -0
- package/src/scripts-runtime/stdlib/fetch.ts +6 -0
- package/src/scripts-runtime/types/stdlib.d.ts +1 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +1 -0
- package/src/server.ts +2 -0
- package/src/tests/apps-rbac.test.ts +110 -0
- package/src/tests/apps-spike2.test.ts +4 -1
- package/src/tests/apps-sync-engine.test.ts +2540 -0
- package/src/tests/apps-sync.test.ts +1627 -0
- package/src/tests/asset-key-api.test.ts +48 -2
- package/src/tests/asset-key-invariants.test.ts +92 -0
- package/src/tests/asset-key-migration.test.ts +174 -8
- package/src/tests/asset-key-normalization.test.ts +2 -0
- package/src/tests/credential-broker.test.ts +27 -2
- package/src/tests/mcp-oauth-dcr-reuse.test.ts +743 -0
- package/src/tests/mcp-oauth-ensure-token.test.ts +45 -0
- package/src/tests/mcp-oauth-manual-client.test.ts +135 -2
- package/src/tests/mcp-oauth-queries.test.ts +153 -0
- package/src/tests/mcp-oauth-wrapper.test.ts +432 -0
- package/src/tests/memory-http-recall-gating.test.ts +2 -0
- package/src/tests/openapi-response-contract.test.ts +138 -0
- package/src/tests/rbac-admission-e2e.test.ts +68 -0
- package/src/tests/script-connections.test.ts +3 -0
- package/src/tests/scripts-runtime-fetch.test.ts +121 -0
- package/src/tests/scripts-runtime-secret-egress.test.ts +4 -2
- package/src/tests/seed-scripts.test.ts +2 -2
- package/src/tests/swarm-tool-result-gate.test.ts +32 -0
- package/src/tests/task-working-dir.test.ts +8 -4
- package/src/tests/whoami-route.test.ts +127 -0
- package/src/tests/workflow-engine-v2.test.ts +80 -7
- package/src/tests/workflow-swarm-script.test.ts +104 -0
- package/src/tools/app-get.ts +10 -2
- package/src/tools/app-rollback.ts +1 -0
- package/src/tools/app-sync.ts +112 -0
- package/src/tools/script-common.ts +20 -0
- package/src/tools/tool-config.ts +2 -1
- package/src/tools/utils.ts +11 -0
- package/src/tools/workflows/create-workflow.ts +4 -1
- package/src/types.ts +1394 -1206
- package/src/utils/template.ts +8 -1
- package/src/utils/zod-openapi.ts +11 -0
- package/src/workflows/engine.ts +27 -4
- package/src/workflows/executors/swarm-script.ts +28 -0
- package/src/workflows/retry-poller.ts +19 -2
- package/templates/skills/apps/content.md +94 -5
- package/templates/skills/asset-namespaces/content.md +2 -2
- package/templates/skills/scheduled-task-resilience/SKILL.md +102 -0
- package/templates/skills/scheduled-task-resilience/config.json +14 -0
- package/templates/skills/scheduled-task-resilience/content.md +97 -0
- package/dist/cli-7wmybrt5.js +0 -115
- package/dist/cli-d91pvmq1.js +0 -14
package/src/http/workflows.ts
CHANGED
|
@@ -22,9 +22,14 @@ import {
|
|
|
22
22
|
InputValueSchema,
|
|
23
23
|
TriggerConfigSchema,
|
|
24
24
|
WorkflowDefinitionSchema,
|
|
25
|
+
WorkflowEdgeSchema,
|
|
25
26
|
WorkflowNodePatchSchema,
|
|
26
27
|
WorkflowPatchSchema,
|
|
28
|
+
WorkflowRunSchema,
|
|
27
29
|
WorkflowRunStatusSchema,
|
|
30
|
+
WorkflowRunStepSchema,
|
|
31
|
+
WorkflowSchema,
|
|
32
|
+
WorkflowVersionSchema,
|
|
28
33
|
} from "../types";
|
|
29
34
|
import { getRequestAuth } from "../utils/request-auth-context";
|
|
30
35
|
import { getExecutorRegistry, startWorkflowExecution } from "../workflows";
|
|
@@ -41,7 +46,45 @@ import { handleWebhookTrigger, WebhookError } from "../workflows/triggers";
|
|
|
41
46
|
import { snapshotWorkflow } from "../workflows/version";
|
|
42
47
|
import { resolveHttpFavoriteOwner } from "./favorite-owner";
|
|
43
48
|
import { route } from "./route-def";
|
|
44
|
-
import {
|
|
49
|
+
import { jsonError, parseBody, triggerSchemaErrorResponse } from "./utils";
|
|
50
|
+
|
|
51
|
+
// ─── Response Schemas ────────────────────────────────────────────────────────
|
|
52
|
+
|
|
53
|
+
/** `Workflow` decorated with the caller-scoped favorite flag (always set once `withFavoriteFlags` runs). */
|
|
54
|
+
const WorkflowWithFavoriteSchema = WorkflowSchema.extend({ favorite: z.boolean() });
|
|
55
|
+
|
|
56
|
+
/** `/api/workflows` slim list item — mirrors `WorkflowSummary` in src/types.ts (no exported schema there). */
|
|
57
|
+
const WorkflowSummarySchema = WorkflowSchema.omit({
|
|
58
|
+
definition: true,
|
|
59
|
+
triggers: true,
|
|
60
|
+
cooldown: true,
|
|
61
|
+
input: true,
|
|
62
|
+
triggerSchema: true,
|
|
63
|
+
})
|
|
64
|
+
.extend({ nodeCount: z.number().int() })
|
|
65
|
+
.extend({ favorite: z.boolean() });
|
|
66
|
+
|
|
67
|
+
/** Mirrors `ExecutorTypeInfo` in src/workflows/executors/registry.ts. */
|
|
68
|
+
const ExecutorTypeInfoSchema = z.object({
|
|
69
|
+
type: z.string(),
|
|
70
|
+
mode: z.enum(["instant", "async"]),
|
|
71
|
+
configSchema: z.record(z.string(), z.unknown()),
|
|
72
|
+
outputSchema: z.record(z.string(), z.unknown()),
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
/** Mirrors `WorkflowRunPage` in src/be/db.ts. */
|
|
76
|
+
const WorkflowRunPageSchema = z.object({
|
|
77
|
+
runs: z.array(WorkflowRunSchema),
|
|
78
|
+
page: z.object({
|
|
79
|
+
limit: z.number().int(),
|
|
80
|
+
offset: z.number().int(),
|
|
81
|
+
total: z.number().int(),
|
|
82
|
+
hasMore: z.boolean(),
|
|
83
|
+
nextOffset: z.number().int().optional(),
|
|
84
|
+
}),
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const SuccessResponseSchema = z.object({ success: z.literal(true) });
|
|
45
88
|
|
|
46
89
|
// ─── Route Definitions ───────────────────────────────────────────────────────
|
|
47
90
|
|
|
@@ -66,7 +109,10 @@ const listWorkflowsRoute = route({
|
|
|
66
109
|
fields: z.enum(["full", "slim"]).optional(),
|
|
67
110
|
}),
|
|
68
111
|
responses: {
|
|
69
|
-
200: {
|
|
112
|
+
200: {
|
|
113
|
+
description: "Workflow list",
|
|
114
|
+
schema: z.union([z.array(WorkflowSummarySchema), z.array(WorkflowWithFavoriteSchema)]),
|
|
115
|
+
},
|
|
70
116
|
},
|
|
71
117
|
});
|
|
72
118
|
|
|
@@ -89,7 +135,7 @@ const createWorkflowRoute = route({
|
|
|
89
135
|
vcsRepo: z.string().min(1).optional(),
|
|
90
136
|
}),
|
|
91
137
|
responses: {
|
|
92
|
-
201: { description: "Workflow created" },
|
|
138
|
+
201: { description: "Workflow created", schema: WorkflowSchema },
|
|
93
139
|
400: { description: "Invalid definition" },
|
|
94
140
|
},
|
|
95
141
|
});
|
|
@@ -102,7 +148,16 @@ const getWorkflowRoute = route({
|
|
|
102
148
|
tags: ["Workflows"],
|
|
103
149
|
params: z.object({ id: z.string() }),
|
|
104
150
|
responses: {
|
|
105
|
-
200: {
|
|
151
|
+
200: {
|
|
152
|
+
description: "Workflow details with auto-generated edges",
|
|
153
|
+
// `favorite` stays optional here (unlike the list routes) because the
|
|
154
|
+
// handler falls back to the undecorated `workflow` (no `favorite`) if
|
|
155
|
+
// `withFavoriteFlags` ever returns an empty array for a truthy input.
|
|
156
|
+
schema: WorkflowSchema.extend({
|
|
157
|
+
favorite: z.boolean().optional(),
|
|
158
|
+
edges: z.array(WorkflowEdgeSchema),
|
|
159
|
+
}),
|
|
160
|
+
},
|
|
106
161
|
404: { description: "Workflow not found" },
|
|
107
162
|
},
|
|
108
163
|
});
|
|
@@ -128,7 +183,7 @@ const updateWorkflowRoute = route({
|
|
|
128
183
|
enabled: z.boolean().optional(),
|
|
129
184
|
}),
|
|
130
185
|
responses: {
|
|
131
|
-
200: { description: "Workflow updated (version snapshot created)" },
|
|
186
|
+
200: { description: "Workflow updated (version snapshot created)", schema: WorkflowSchema },
|
|
132
187
|
400: { description: "Invalid definition" },
|
|
133
188
|
404: { description: "Workflow not found" },
|
|
134
189
|
},
|
|
@@ -143,7 +198,7 @@ const patchWorkflowRoute = route({
|
|
|
143
198
|
params: z.object({ id: z.string() }),
|
|
144
199
|
body: WorkflowPatchSchema.extend({ key: AssetKeySchema.optional() }),
|
|
145
200
|
responses: {
|
|
146
|
-
200: { description: "Workflow patched (version snapshot created)" },
|
|
201
|
+
200: { description: "Workflow patched (version snapshot created)", schema: WorkflowSchema },
|
|
147
202
|
400: { description: "Invalid patch or resulting definition" },
|
|
148
203
|
404: { description: "Workflow not found" },
|
|
149
204
|
},
|
|
@@ -158,7 +213,7 @@ const patchWorkflowNodeRoute = route({
|
|
|
158
213
|
params: z.object({ id: z.string(), nodeId: z.string() }),
|
|
159
214
|
body: WorkflowNodePatchSchema,
|
|
160
215
|
responses: {
|
|
161
|
-
200: { description: "Node patched (version snapshot created)" },
|
|
216
|
+
200: { description: "Node patched (version snapshot created)", schema: WorkflowSchema },
|
|
162
217
|
400: { description: "Invalid patch or resulting definition" },
|
|
163
218
|
404: { description: "Workflow or node not found" },
|
|
164
219
|
},
|
|
@@ -185,7 +240,10 @@ const triggerWorkflowRoute = route({
|
|
|
185
240
|
tags: ["Workflows"],
|
|
186
241
|
params: z.object({ id: z.string() }),
|
|
187
242
|
responses: {
|
|
188
|
-
201: {
|
|
243
|
+
201: {
|
|
244
|
+
description: "Workflow run started (or skipped if cooldown active)",
|
|
245
|
+
schema: z.object({ runId: z.string(), skipped: z.boolean() }),
|
|
246
|
+
},
|
|
189
247
|
400: { description: "Workflow is disabled" },
|
|
190
248
|
401: { description: "Unauthorized" },
|
|
191
249
|
404: { description: "Workflow not found" },
|
|
@@ -200,7 +258,10 @@ const validateTriggerRoute = route({
|
|
|
200
258
|
tags: ["Workflows"],
|
|
201
259
|
params: z.object({ id: z.string() }),
|
|
202
260
|
responses: {
|
|
203
|
-
200: {
|
|
261
|
+
200: {
|
|
262
|
+
description: "Payload matches the workflow's triggerSchema (or workflow has none)",
|
|
263
|
+
schema: z.object({ valid: z.literal(true), schema: z.null().optional() }),
|
|
264
|
+
},
|
|
204
265
|
400: { description: "Payload failed validation; body matches the TriggerSchemaError contract" },
|
|
205
266
|
404: { description: "Workflow not found" },
|
|
206
267
|
},
|
|
@@ -219,7 +280,10 @@ const listWorkflowRunsRoute = route({
|
|
|
219
280
|
offset: z.coerce.number().int().min(0).optional(),
|
|
220
281
|
}),
|
|
221
282
|
responses: {
|
|
222
|
-
200: {
|
|
283
|
+
200: {
|
|
284
|
+
description: "Workflow run list",
|
|
285
|
+
schema: z.union([z.array(WorkflowRunSchema), WorkflowRunPageSchema]),
|
|
286
|
+
},
|
|
223
287
|
},
|
|
224
288
|
});
|
|
225
289
|
|
|
@@ -231,7 +295,10 @@ const getWorkflowRunRoute = route({
|
|
|
231
295
|
tags: ["Workflows"],
|
|
232
296
|
params: z.object({ id: z.string() }),
|
|
233
297
|
responses: {
|
|
234
|
-
200: {
|
|
298
|
+
200: {
|
|
299
|
+
description: "Workflow run details with steps including retry info",
|
|
300
|
+
schema: z.object({ run: WorkflowRunSchema, steps: z.array(WorkflowRunStepSchema) }),
|
|
301
|
+
},
|
|
235
302
|
404: { description: "Run not found" },
|
|
236
303
|
},
|
|
237
304
|
});
|
|
@@ -244,7 +311,7 @@ const retryWorkflowRunRoute = route({
|
|
|
244
311
|
tags: ["Workflows"],
|
|
245
312
|
params: z.object({ id: z.string() }),
|
|
246
313
|
responses: {
|
|
247
|
-
200: { description: "Retry started" },
|
|
314
|
+
200: { description: "Retry started", schema: SuccessResponseSchema },
|
|
248
315
|
400: { description: "Cannot retry" },
|
|
249
316
|
},
|
|
250
317
|
});
|
|
@@ -258,7 +325,7 @@ const cancelWorkflowRunRoute = route({
|
|
|
258
325
|
params: z.object({ id: z.string() }),
|
|
259
326
|
body: z.object({ reason: z.string().optional() }).optional(),
|
|
260
327
|
responses: {
|
|
261
|
-
200: { description: "Run cancelled" },
|
|
328
|
+
200: { description: "Run cancelled", schema: SuccessResponseSchema },
|
|
262
329
|
400: { description: "Cannot cancel" },
|
|
263
330
|
},
|
|
264
331
|
auth: { apiKey: true },
|
|
@@ -271,7 +338,10 @@ const listExecutorTypesRoute = route({
|
|
|
271
338
|
summary: "List all executor types with their config and output schemas",
|
|
272
339
|
tags: ["Workflows"],
|
|
273
340
|
responses: {
|
|
274
|
-
200: {
|
|
341
|
+
200: {
|
|
342
|
+
description: "List of executor types with schemas",
|
|
343
|
+
schema: z.object({ executorTypes: z.array(ExecutorTypeInfoSchema) }),
|
|
344
|
+
},
|
|
275
345
|
},
|
|
276
346
|
});
|
|
277
347
|
|
|
@@ -283,7 +353,7 @@ const getExecutorTypeRoute = route({
|
|
|
283
353
|
tags: ["Workflows"],
|
|
284
354
|
params: z.object({ type: z.string() }),
|
|
285
355
|
responses: {
|
|
286
|
-
200: { description: "Executor type details" },
|
|
356
|
+
200: { description: "Executor type details", schema: ExecutorTypeInfoSchema },
|
|
287
357
|
404: { description: "Executor type not found" },
|
|
288
358
|
},
|
|
289
359
|
});
|
|
@@ -297,7 +367,7 @@ const webhookTriggerRoute = route({
|
|
|
297
367
|
params: z.object({ workflowId: z.string() }),
|
|
298
368
|
auth: { apiKey: false },
|
|
299
369
|
responses: {
|
|
300
|
-
201: { description: "Webhook processed" },
|
|
370
|
+
201: { description: "Webhook processed", schema: z.object({ runId: z.string() }) },
|
|
301
371
|
401: { description: "Invalid signature" },
|
|
302
372
|
404: { description: "Workflow not found" },
|
|
303
373
|
},
|
|
@@ -313,7 +383,10 @@ const listWorkflowVersionsRoute = route({
|
|
|
313
383
|
tags: ["Workflows"],
|
|
314
384
|
params: z.object({ id: z.string() }),
|
|
315
385
|
responses: {
|
|
316
|
-
200: {
|
|
386
|
+
200: {
|
|
387
|
+
description: "Version list (newest first)",
|
|
388
|
+
schema: z.object({ versions: z.array(WorkflowVersionSchema) }),
|
|
389
|
+
},
|
|
317
390
|
404: { description: "Workflow not found" },
|
|
318
391
|
},
|
|
319
392
|
});
|
|
@@ -326,7 +399,7 @@ const getWorkflowVersionRoute = route({
|
|
|
326
399
|
tags: ["Workflows"],
|
|
327
400
|
params: z.object({ id: z.string(), version: z.coerce.number().int().min(1) }),
|
|
328
401
|
responses: {
|
|
329
|
-
200: { description: "Version snapshot" },
|
|
402
|
+
200: { description: "Version snapshot", schema: WorkflowVersionSchema },
|
|
330
403
|
404: { description: "Version not found" },
|
|
331
404
|
},
|
|
332
405
|
});
|
|
@@ -343,7 +416,7 @@ export async function handleWorkflows(
|
|
|
343
416
|
// Executor type schemas
|
|
344
417
|
if (listExecutorTypesRoute.match(req.method, pathSegments)) {
|
|
345
418
|
const registry = getExecutorRegistry();
|
|
346
|
-
|
|
419
|
+
listExecutorTypesRoute.respond(res, 200, { executorTypes: registry.describeAll() });
|
|
347
420
|
return true;
|
|
348
421
|
}
|
|
349
422
|
|
|
@@ -356,7 +429,7 @@ export async function handleWorkflows(
|
|
|
356
429
|
res.end();
|
|
357
430
|
return true;
|
|
358
431
|
}
|
|
359
|
-
|
|
432
|
+
getExecutorTypeRoute.respond(res, 200, registry.describe(parsed.params.type));
|
|
360
433
|
return true;
|
|
361
434
|
}
|
|
362
435
|
|
|
@@ -371,14 +444,14 @@ export async function handleWorkflows(
|
|
|
371
444
|
}
|
|
372
445
|
const rawBody = Buffer.concat(chunks).toString();
|
|
373
446
|
|
|
447
|
+
let result: Awaited<ReturnType<typeof handleWebhookTrigger>>;
|
|
374
448
|
try {
|
|
375
|
-
|
|
449
|
+
result = await handleWebhookTrigger(
|
|
376
450
|
workflowId,
|
|
377
451
|
rawBody, // Raw body string — HMAC is verified against raw bytes; JSON parsing happens inside
|
|
378
452
|
req.headers, // Full header bag — signature header resolved per trigger config
|
|
379
453
|
getExecutorRegistry(),
|
|
380
454
|
);
|
|
381
|
-
json(res, result, 201);
|
|
382
455
|
} catch (err) {
|
|
383
456
|
if (err instanceof TriggerSchemaError) {
|
|
384
457
|
triggerSchemaErrorResponse(res, err.message, err.validationErrors);
|
|
@@ -387,7 +460,12 @@ export async function handleWorkflows(
|
|
|
387
460
|
} else {
|
|
388
461
|
jsonError(res, String(err), 500);
|
|
389
462
|
}
|
|
463
|
+
return true;
|
|
390
464
|
}
|
|
465
|
+
// Egress stays OUTSIDE the try: `respond()` runtime-validates in dev/test
|
|
466
|
+
// and must fail loudly rather than be reported as a webhook failure for a
|
|
467
|
+
// run that already started.
|
|
468
|
+
webhookTriggerRoute.respond(res, 201, result);
|
|
391
469
|
return true;
|
|
392
470
|
}
|
|
393
471
|
|
|
@@ -402,7 +480,7 @@ export async function handleWorkflows(
|
|
|
402
480
|
res.end();
|
|
403
481
|
return true;
|
|
404
482
|
}
|
|
405
|
-
|
|
483
|
+
getWorkflowVersionRoute.respond(res, 200, version);
|
|
406
484
|
return true;
|
|
407
485
|
}
|
|
408
486
|
|
|
@@ -416,7 +494,7 @@ export async function handleWorkflows(
|
|
|
416
494
|
return true;
|
|
417
495
|
}
|
|
418
496
|
const versions = getWorkflowVersions(parsed.params.id);
|
|
419
|
-
|
|
497
|
+
listWorkflowVersionsRoute.respond(res, 200, { versions });
|
|
420
498
|
return true;
|
|
421
499
|
}
|
|
422
500
|
|
|
@@ -433,10 +511,15 @@ export async function handleWorkflows(
|
|
|
433
511
|
};
|
|
434
512
|
// List responses default to slim (no `definition`); `?fields=full` restores it.
|
|
435
513
|
if (parsed.query.fields === "full") {
|
|
436
|
-
|
|
514
|
+
listWorkflowsRoute.respond(
|
|
515
|
+
res,
|
|
516
|
+
200,
|
|
517
|
+
withFavoriteFlags(listWorkflows(filters), { favoriteScope, itemType: "workflow" }),
|
|
518
|
+
);
|
|
437
519
|
} else {
|
|
438
|
-
|
|
520
|
+
listWorkflowsRoute.respond(
|
|
439
521
|
res,
|
|
522
|
+
200,
|
|
440
523
|
withFavoriteFlags(listWorkflows(filters, { slim: true }), {
|
|
441
524
|
favoriteScope,
|
|
442
525
|
itemType: "workflow",
|
|
@@ -486,7 +569,7 @@ export async function handleWorkflows(
|
|
|
486
569
|
},
|
|
487
570
|
"api",
|
|
488
571
|
);
|
|
489
|
-
|
|
572
|
+
createWorkflowRoute.respond(res, 201, workflow);
|
|
490
573
|
return true;
|
|
491
574
|
}
|
|
492
575
|
|
|
@@ -503,7 +586,7 @@ export async function handleWorkflows(
|
|
|
503
586
|
const edges = generateEdges(workflow.definition);
|
|
504
587
|
const favoriteScope = resolveHttpFavoriteOwner(req, myAgentId)?.scope;
|
|
505
588
|
const [decorated] = withFavoriteFlags([workflow], { favoriteScope, itemType: "workflow" });
|
|
506
|
-
|
|
589
|
+
getWorkflowRoute.respond(res, 200, { ...(decorated ?? workflow), edges });
|
|
507
590
|
return true;
|
|
508
591
|
}
|
|
509
592
|
|
|
@@ -553,7 +636,7 @@ export async function handleWorkflows(
|
|
|
553
636
|
res.end();
|
|
554
637
|
return true;
|
|
555
638
|
}
|
|
556
|
-
|
|
639
|
+
patchWorkflowNodeRoute.respond(res, 200, workflow);
|
|
557
640
|
return true;
|
|
558
641
|
}
|
|
559
642
|
|
|
@@ -616,7 +699,7 @@ export async function handleWorkflows(
|
|
|
616
699
|
res.end();
|
|
617
700
|
return true;
|
|
618
701
|
}
|
|
619
|
-
|
|
702
|
+
patchWorkflowRoute.respond(res, 200, workflow);
|
|
620
703
|
return true;
|
|
621
704
|
}
|
|
622
705
|
|
|
@@ -684,7 +767,7 @@ export async function handleWorkflows(
|
|
|
684
767
|
res.end();
|
|
685
768
|
return true;
|
|
686
769
|
}
|
|
687
|
-
|
|
770
|
+
updateWorkflowRoute.respond(res, 200, workflow);
|
|
688
771
|
return true;
|
|
689
772
|
}
|
|
690
773
|
|
|
@@ -714,7 +797,7 @@ export async function handleWorkflows(
|
|
|
714
797
|
const body = await parseBody<Record<string, unknown>>(req);
|
|
715
798
|
const triggerData = (body?.triggerData ?? body) as unknown;
|
|
716
799
|
if (!workflow.triggerSchema) {
|
|
717
|
-
|
|
800
|
+
validateTriggerRoute.respond(res, 200, { valid: true, schema: null });
|
|
718
801
|
return true;
|
|
719
802
|
}
|
|
720
803
|
const errors = validateJsonSchema(workflow.triggerSchema, triggerData);
|
|
@@ -726,7 +809,7 @@ export async function handleWorkflows(
|
|
|
726
809
|
);
|
|
727
810
|
return true;
|
|
728
811
|
}
|
|
729
|
-
|
|
812
|
+
validateTriggerRoute.respond(res, 200, { valid: true });
|
|
730
813
|
return true;
|
|
731
814
|
}
|
|
732
815
|
|
|
@@ -764,7 +847,7 @@ export async function handleWorkflows(
|
|
|
764
847
|
const run = getWorkflowRun(runId);
|
|
765
848
|
const skipped = run?.status === "skipped";
|
|
766
849
|
|
|
767
|
-
|
|
850
|
+
triggerWorkflowRoute.respond(res, 201, { runId, skipped });
|
|
768
851
|
return true;
|
|
769
852
|
}
|
|
770
853
|
|
|
@@ -779,13 +862,13 @@ export async function handleWorkflows(
|
|
|
779
862
|
limit: parsed.query?.limit ?? 20,
|
|
780
863
|
offset: parsed.query?.offset ?? 0,
|
|
781
864
|
});
|
|
782
|
-
|
|
865
|
+
listWorkflowRunsRoute.respond(res, 200, page);
|
|
783
866
|
return true;
|
|
784
867
|
}
|
|
785
868
|
// Preserve the pre-pagination response for the UI when limit/offset are
|
|
786
869
|
// omitted: a bare array containing every matching run.
|
|
787
870
|
const runs = listWorkflowRuns(parsed.params.id, { status: parsed.query?.status });
|
|
788
|
-
|
|
871
|
+
listWorkflowRunsRoute.respond(res, 200, runs);
|
|
789
872
|
return true;
|
|
790
873
|
}
|
|
791
874
|
|
|
@@ -799,7 +882,7 @@ export async function handleWorkflows(
|
|
|
799
882
|
return true;
|
|
800
883
|
}
|
|
801
884
|
const steps = getWorkflowRunStepsByRunId(parsed.params.id);
|
|
802
|
-
|
|
885
|
+
getWorkflowRunRoute.respond(res, 200, { run, steps });
|
|
803
886
|
return true;
|
|
804
887
|
}
|
|
805
888
|
|
|
@@ -808,10 +891,13 @@ export async function handleWorkflows(
|
|
|
808
891
|
if (!parsed) return true;
|
|
809
892
|
try {
|
|
810
893
|
await retryFailedRun(parsed.params.id, getExecutorRegistry());
|
|
811
|
-
json(res, { success: true });
|
|
812
894
|
} catch (err) {
|
|
813
895
|
jsonError(res, String(err), 400);
|
|
896
|
+
return true;
|
|
814
897
|
}
|
|
898
|
+
// Outside the try — a response-schema violation must not be reported as a
|
|
899
|
+
// 400 "cannot retry" after the retry already started.
|
|
900
|
+
retryWorkflowRunRoute.respond(res, 200, { success: true });
|
|
815
901
|
return true;
|
|
816
902
|
}
|
|
817
903
|
|
|
@@ -820,10 +906,13 @@ export async function handleWorkflows(
|
|
|
820
906
|
if (!parsed) return true;
|
|
821
907
|
try {
|
|
822
908
|
cancelWorkflowRun(parsed.params.id, parsed.body?.reason);
|
|
823
|
-
json(res, { success: true });
|
|
824
909
|
} catch (err) {
|
|
825
910
|
jsonError(res, String(err), 400);
|
|
911
|
+
return true;
|
|
826
912
|
}
|
|
913
|
+
// Outside the try — a response-schema violation must not be reported as a
|
|
914
|
+
// 400 "cannot cancel" after the run was already cancelled.
|
|
915
|
+
cancelWorkflowRunRoute.respond(res, 200, { success: true });
|
|
827
916
|
return true;
|
|
828
917
|
}
|
|
829
918
|
|
package/src/http/x.ts
CHANGED
|
@@ -48,6 +48,24 @@ const TIMEOUT_HEADER = "x-swarm-timeout-ms";
|
|
|
48
48
|
// otherwise fully unauthenticated.
|
|
49
49
|
const MAX_BODY_BYTES = 1 * 1024 * 1024;
|
|
50
50
|
|
|
51
|
+
// Mirrors the local `ExternalError` type below — kept as a separate schema
|
|
52
|
+
// since that type is TS-only and not exported for reuse.
|
|
53
|
+
const XScriptErrorSchema = z.object({
|
|
54
|
+
type: z.string(),
|
|
55
|
+
message: z.string(),
|
|
56
|
+
details: z.array(z.string()).optional(),
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// `result` is `unknown | undefined` on `RunScriptOutput` — when `undefined`,
|
|
60
|
+
// `JSON.stringify` drops the key entirely, so callers may see it absent as
|
|
61
|
+
// well as present-with-any-value (including `null` on the failure paths).
|
|
62
|
+
const XScriptResponseSchema = z.object({
|
|
63
|
+
ok: z.boolean(),
|
|
64
|
+
result: z.unknown().optional(),
|
|
65
|
+
error: XScriptErrorSchema.nullable(),
|
|
66
|
+
durationMs: z.number(),
|
|
67
|
+
});
|
|
68
|
+
|
|
51
69
|
const scriptApiRoute = route({
|
|
52
70
|
method: "post",
|
|
53
71
|
path: "/api/x/script/{endpointId}",
|
|
@@ -64,9 +82,21 @@ const scriptApiRoute = route({
|
|
|
64
82
|
params: z.object({ endpointId: z.string() }),
|
|
65
83
|
auth: { apiKey: false },
|
|
66
84
|
responses: {
|
|
67
|
-
200: {
|
|
68
|
-
|
|
69
|
-
|
|
85
|
+
200: {
|
|
86
|
+
description: "Script executed — see `ok` in the envelope",
|
|
87
|
+
schema: XScriptResponseSchema,
|
|
88
|
+
},
|
|
89
|
+
// This surface uses a NESTED error envelope `{error: {type, message}}`
|
|
90
|
+
// (public API, richer machine-readable type) — declare it explicitly so
|
|
91
|
+
// the standard flat ErrorResponse fallback doesn't misdocument it.
|
|
92
|
+
401: {
|
|
93
|
+
description: "Missing or invalid bearer token",
|
|
94
|
+
schema: z.object({ error: XScriptErrorSchema }),
|
|
95
|
+
},
|
|
96
|
+
404: {
|
|
97
|
+
description: "Endpoint not found or disabled",
|
|
98
|
+
schema: z.object({ error: XScriptErrorSchema }),
|
|
99
|
+
},
|
|
70
100
|
501: { description: "workspace-rw scripts are not supported" },
|
|
71
101
|
},
|
|
72
102
|
});
|
|
@@ -154,7 +184,7 @@ export async function handleX(
|
|
|
154
184
|
try {
|
|
155
185
|
args = await parseBody(req);
|
|
156
186
|
} catch {
|
|
157
|
-
|
|
187
|
+
scriptApiRoute.respond(res, 200, {
|
|
158
188
|
ok: false,
|
|
159
189
|
result: null,
|
|
160
190
|
error: { type: "invalid_json", message: "Request body must be valid JSON" },
|
|
@@ -182,7 +212,7 @@ export async function handleX(
|
|
|
182
212
|
const schema = JSON.parse(script.argsJsonSchema) as Record<string, unknown>;
|
|
183
213
|
const errors = validateJsonSchema(schema, args ?? null);
|
|
184
214
|
if (errors.length > 0) {
|
|
185
|
-
|
|
215
|
+
scriptApiRoute.respond(res, 200, {
|
|
186
216
|
ok: false,
|
|
187
217
|
result: null,
|
|
188
218
|
error: { type: "args_validation", message: errors.join("; "), details: errors },
|
|
@@ -246,8 +276,9 @@ export async function handleX(
|
|
|
246
276
|
}
|
|
247
277
|
|
|
248
278
|
// Never expose stdout/stderr to external callers.
|
|
249
|
-
|
|
279
|
+
scriptApiRoute.respond(
|
|
250
280
|
res,
|
|
281
|
+
200,
|
|
251
282
|
scrubObject({ ok, result: ok ? output.result : null, error, durationMs: output.durationMs }),
|
|
252
283
|
);
|
|
253
284
|
return true;
|
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import {
|
|
2
2
|
applyMcpOAuthRefresh,
|
|
3
3
|
getMcpOAuthToken,
|
|
4
|
+
invalidateMcpOAuthClient,
|
|
4
5
|
isMcpTokenExpiringSoon,
|
|
5
6
|
type McpOAuthToken,
|
|
6
7
|
markMcpOAuthTokenStatus,
|
|
7
8
|
} from "../be/db-queries/mcp-oauth";
|
|
8
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
authMethodForStoredClient,
|
|
11
|
+
computeExpiresAt,
|
|
12
|
+
isInvalidClientError,
|
|
13
|
+
refreshMcpToken,
|
|
14
|
+
} from "./mcp-wrapper";
|
|
9
15
|
|
|
10
16
|
/**
|
|
11
17
|
* Per-mcpServerId in-memory mutex to serialize concurrent refreshes.
|
|
@@ -57,6 +63,7 @@ export async function ensureMcpToken(
|
|
|
57
63
|
tokenUrl: token.tokenUrl,
|
|
58
64
|
clientId: token.dcrClientId ?? "",
|
|
59
65
|
clientSecret: token.dcrClientSecret ?? undefined,
|
|
66
|
+
tokenEndpointAuthMethod: authMethodForStoredClient(token.tokenEndpointAuthMethod),
|
|
60
67
|
refreshToken: token.refreshToken,
|
|
61
68
|
resource: token.resourceUrl,
|
|
62
69
|
scopes: token.scope ? token.scope.split(" ").filter(Boolean) : undefined,
|
|
@@ -73,6 +80,14 @@ export async function ensureMcpToken(
|
|
|
73
80
|
return getMcpOAuthToken(mcpServerId, userId);
|
|
74
81
|
} catch (err) {
|
|
75
82
|
const message = err instanceof Error ? err.message : String(err);
|
|
83
|
+
if (isInvalidClientError(message)) {
|
|
84
|
+
// The provider has disowned this client. The explicit POST /refresh
|
|
85
|
+
// route already invalidates on this exact error; automatic refreshes
|
|
86
|
+
// through this path (the proxy / server resolution) must do the same,
|
|
87
|
+
// otherwise a revoked client stays "reusable" forever and every
|
|
88
|
+
// subsequent /authorize call keeps selecting it.
|
|
89
|
+
invalidateMcpOAuthClient(mcpServerId, userId);
|
|
90
|
+
}
|
|
76
91
|
markMcpOAuthTokenStatus(token.id, "error", message);
|
|
77
92
|
console.error(`[mcp-oauth] refresh failed for ${mcpServerId}: ${message}`);
|
|
78
93
|
return { ...token, status: "error" as const, lastErrorMessage: message };
|