@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/metrics.ts
CHANGED
|
@@ -17,14 +17,16 @@ import {
|
|
|
17
17
|
type Metric,
|
|
18
18
|
MetricDefinitionSchema,
|
|
19
19
|
type MetricParam,
|
|
20
|
+
MetricSchema,
|
|
20
21
|
type MetricSummary,
|
|
21
22
|
type MetricVariable,
|
|
22
23
|
MetricVersionSchema,
|
|
23
24
|
type MetricWidget,
|
|
25
|
+
MetricWidgetSchema,
|
|
24
26
|
} from "../types";
|
|
25
27
|
import { assertSelectOnlyQuery, executeReadOnlyQuery } from "./db-query";
|
|
26
28
|
import { route } from "./route-def";
|
|
27
|
-
import {
|
|
29
|
+
import { jsonError } from "./utils";
|
|
28
30
|
|
|
29
31
|
const DEFAULT_METRIC_MAX_ROWS = 100;
|
|
30
32
|
const HARD_METRIC_MAX_ROWS = 500;
|
|
@@ -192,6 +194,18 @@ const metricDefinitionBody = z.object({
|
|
|
192
194
|
definition: MetricDefinitionSchema,
|
|
193
195
|
});
|
|
194
196
|
|
|
197
|
+
// `{ id, version }` shape sent by both create (always version 1) and update
|
|
198
|
+
// (the next edit counter) — see `metricEditCounter` below.
|
|
199
|
+
const metricIdVersionSchema = z.object({
|
|
200
|
+
id: z.string(),
|
|
201
|
+
version: z.number().int().min(1),
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// `MetricSummary` (see ../types) is `Omit<Metric, "definition">` — the "slim"
|
|
205
|
+
// list projection returned by `listMetricsByAgent`/`listAllMetrics` when
|
|
206
|
+
// `fields=slim`.
|
|
207
|
+
const metricSummarySchema = MetricSchema.omit({ definition: true });
|
|
208
|
+
|
|
195
209
|
const createMetricRoute = route({
|
|
196
210
|
method: "post",
|
|
197
211
|
path: "/api/metrics/definitions",
|
|
@@ -200,7 +214,7 @@ const createMetricRoute = route({
|
|
|
200
214
|
tags: ["Metrics"],
|
|
201
215
|
body: metricDefinitionBody,
|
|
202
216
|
responses: {
|
|
203
|
-
201: { description: "Metric created" },
|
|
217
|
+
201: { description: "Metric created", schema: metricIdVersionSchema },
|
|
204
218
|
400: { description: "Invalid metric definition" },
|
|
205
219
|
409: { description: "Slug already exists for this agent" },
|
|
206
220
|
},
|
|
@@ -219,7 +233,15 @@ const listMetricsRoute = route({
|
|
|
219
233
|
fields: z.enum(["full", "slim"]).optional(),
|
|
220
234
|
}),
|
|
221
235
|
responses: {
|
|
222
|
-
200: {
|
|
236
|
+
200: {
|
|
237
|
+
description: "Metric definitions",
|
|
238
|
+
schema: z.object({
|
|
239
|
+
metrics: z.array(z.union([MetricSchema, metricSummarySchema])),
|
|
240
|
+
total: z.number(),
|
|
241
|
+
limit: z.number(),
|
|
242
|
+
offset: z.number(),
|
|
243
|
+
}),
|
|
244
|
+
},
|
|
223
245
|
},
|
|
224
246
|
});
|
|
225
247
|
|
|
@@ -231,7 +253,7 @@ const getMetricRoute = route({
|
|
|
231
253
|
tags: ["Metrics"],
|
|
232
254
|
params: z.object({ id: z.string() }),
|
|
233
255
|
responses: {
|
|
234
|
-
200: { description: "Metric definition" },
|
|
256
|
+
200: { description: "Metric definition", schema: MetricSchema },
|
|
235
257
|
404: { description: "Metric not found" },
|
|
236
258
|
},
|
|
237
259
|
});
|
|
@@ -245,7 +267,7 @@ const updateMetricRoute = route({
|
|
|
245
267
|
params: z.object({ id: z.string() }),
|
|
246
268
|
body: metricDefinitionBody.partial(),
|
|
247
269
|
responses: {
|
|
248
|
-
200: { description: "Metric updated" },
|
|
270
|
+
200: { description: "Metric updated", schema: metricIdVersionSchema },
|
|
249
271
|
404: { description: "Metric not found" },
|
|
250
272
|
},
|
|
251
273
|
});
|
|
@@ -263,6 +285,32 @@ const deleteMetricRoute = route({
|
|
|
263
285
|
},
|
|
264
286
|
});
|
|
265
287
|
|
|
288
|
+
// Wire shape of a single widget's query result inside a `run` response — see
|
|
289
|
+
// `runMetricWidget` below. `rows` is re-keyed from column-array form to
|
|
290
|
+
// column-name-keyed objects, so each row's field set is query-dependent.
|
|
291
|
+
const metricRunWidgetResultSchema = z.object({
|
|
292
|
+
columns: z.array(z.string()),
|
|
293
|
+
rows: z.array(z.record(z.string(), z.unknown())),
|
|
294
|
+
elapsed: z.number(),
|
|
295
|
+
total: z.number(),
|
|
296
|
+
truncated: z.boolean(),
|
|
297
|
+
maxRows: z.number(),
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
// Full `runMetric()` response — see that function below.
|
|
301
|
+
const metricRunResultSchema = z.object({
|
|
302
|
+
metric: MetricSchema,
|
|
303
|
+
variables: z.record(z.string(), z.union([z.string(), z.number(), z.boolean(), z.null()])),
|
|
304
|
+
widgets: z.array(
|
|
305
|
+
z.object({
|
|
306
|
+
widget: MetricWidgetSchema,
|
|
307
|
+
result: metricRunWidgetResultSchema,
|
|
308
|
+
}),
|
|
309
|
+
),
|
|
310
|
+
// Kept as the first widget result for older callers during the PR cycle.
|
|
311
|
+
result: metricRunWidgetResultSchema.optional(),
|
|
312
|
+
});
|
|
313
|
+
|
|
266
314
|
const runMetricRoute = route({
|
|
267
315
|
method: "post",
|
|
268
316
|
path: "/api/metrics/definitions/{id}/run",
|
|
@@ -272,7 +320,7 @@ const runMetricRoute = route({
|
|
|
272
320
|
params: z.object({ id: z.string() }),
|
|
273
321
|
body: MetricRunBodySchema,
|
|
274
322
|
responses: {
|
|
275
|
-
200: { description: "Metric result" },
|
|
323
|
+
200: { description: "Metric result", schema: metricRunResultSchema },
|
|
276
324
|
400: { description: "Invalid or disallowed query" },
|
|
277
325
|
404: { description: "Metric not found" },
|
|
278
326
|
},
|
|
@@ -286,7 +334,10 @@ const listMetricVersionsRoute = route({
|
|
|
286
334
|
tags: ["Metrics"],
|
|
287
335
|
params: z.object({ id: z.string() }),
|
|
288
336
|
responses: {
|
|
289
|
-
200: {
|
|
337
|
+
200: {
|
|
338
|
+
description: "Metric version list",
|
|
339
|
+
schema: z.object({ versions: z.array(MetricVersionSchema) }),
|
|
340
|
+
},
|
|
290
341
|
404: { description: "Metric not found" },
|
|
291
342
|
},
|
|
292
343
|
});
|
|
@@ -299,11 +350,18 @@ const getMetricVersionRoute = route({
|
|
|
299
350
|
tags: ["Metrics"],
|
|
300
351
|
params: z.object({ id: z.string(), version: z.coerce.number().int().min(1) }),
|
|
301
352
|
responses: {
|
|
302
|
-
200: { description: "Metric version" },
|
|
353
|
+
200: { description: "Metric version", schema: MetricVersionSchema },
|
|
303
354
|
404: { description: "Metric or version not found" },
|
|
304
355
|
},
|
|
305
356
|
});
|
|
306
357
|
|
|
358
|
+
// z.toJSONSchema() output is always a JSON object at the root (draft-7 for an
|
|
359
|
+
// object-typed zod schema); nested shape is JSON-Schema-generic, not modeled
|
|
360
|
+
// field-by-field here.
|
|
361
|
+
const metricJsonSchemaResponseSchema = z.object({
|
|
362
|
+
schema: z.record(z.string(), z.unknown()),
|
|
363
|
+
});
|
|
364
|
+
|
|
307
365
|
const metricSchemaRoute = route({
|
|
308
366
|
method: "get",
|
|
309
367
|
path: "/api/metrics/schema",
|
|
@@ -311,7 +369,7 @@ const metricSchemaRoute = route({
|
|
|
311
369
|
summary: "Get the metric definition JSON Schema",
|
|
312
370
|
tags: ["Metrics"],
|
|
313
371
|
responses: {
|
|
314
|
-
200: { description: "Metric definition JSON Schema" },
|
|
372
|
+
200: { description: "Metric definition JSON Schema", schema: metricJsonSchemaResponseSchema },
|
|
315
373
|
},
|
|
316
374
|
});
|
|
317
375
|
|
|
@@ -328,7 +386,9 @@ export async function handleMetrics(
|
|
|
328
386
|
myAgentId: string | undefined,
|
|
329
387
|
): Promise<boolean> {
|
|
330
388
|
if (metricSchemaRoute.match(req.method, pathSegments)) {
|
|
331
|
-
|
|
389
|
+
metricSchemaRoute.respond(res, 200, {
|
|
390
|
+
schema: z.toJSONSchema(MetricDefinitionSchema, { target: "draft-7" }),
|
|
391
|
+
});
|
|
332
392
|
return true;
|
|
333
393
|
}
|
|
334
394
|
|
|
@@ -347,7 +407,7 @@ export async function handleMetrics(
|
|
|
347
407
|
description: parsed.body.description ?? undefined,
|
|
348
408
|
definition,
|
|
349
409
|
});
|
|
350
|
-
|
|
410
|
+
createMetricRoute.respond(res, 201, { id: metric.id, version: 1 });
|
|
351
411
|
} catch (err) {
|
|
352
412
|
const msg = err instanceof Error ? err.message : String(err);
|
|
353
413
|
if (msg.includes("UNIQUE")) {
|
|
@@ -379,7 +439,7 @@ export async function handleMetrics(
|
|
|
379
439
|
: listAllMetrics(limit, offset, { slim: true });
|
|
380
440
|
total = countAllMetrics();
|
|
381
441
|
}
|
|
382
|
-
|
|
442
|
+
listMetricsRoute.respond(res, 200, { metrics, total, limit, offset });
|
|
383
443
|
return true;
|
|
384
444
|
}
|
|
385
445
|
|
|
@@ -393,7 +453,7 @@ export async function handleMetrics(
|
|
|
393
453
|
return true;
|
|
394
454
|
}
|
|
395
455
|
try {
|
|
396
|
-
|
|
456
|
+
runMetricRoute.respond(res, 200, runMetric(metric, parsed.body?.variables ?? {}));
|
|
397
457
|
} catch (err) {
|
|
398
458
|
jsonError(res, err instanceof Error ? err.message : String(err));
|
|
399
459
|
}
|
|
@@ -414,7 +474,7 @@ export async function handleMetrics(
|
|
|
414
474
|
res.end();
|
|
415
475
|
return true;
|
|
416
476
|
}
|
|
417
|
-
|
|
477
|
+
getMetricVersionRoute.respond(res, 200, MetricVersionSchema.parse(version));
|
|
418
478
|
return true;
|
|
419
479
|
}
|
|
420
480
|
|
|
@@ -426,7 +486,7 @@ export async function handleMetrics(
|
|
|
426
486
|
res.end();
|
|
427
487
|
return true;
|
|
428
488
|
}
|
|
429
|
-
|
|
489
|
+
listMetricVersionsRoute.respond(res, 200, { versions: getMetricVersions(parsed.params.id) });
|
|
430
490
|
return true;
|
|
431
491
|
}
|
|
432
492
|
|
|
@@ -439,7 +499,7 @@ export async function handleMetrics(
|
|
|
439
499
|
res.end();
|
|
440
500
|
return true;
|
|
441
501
|
}
|
|
442
|
-
|
|
502
|
+
getMetricRoute.respond(res, 200, metric);
|
|
443
503
|
return true;
|
|
444
504
|
}
|
|
445
505
|
|
|
@@ -472,7 +532,10 @@ export async function handleMetrics(
|
|
|
472
532
|
res.end();
|
|
473
533
|
return true;
|
|
474
534
|
}
|
|
475
|
-
|
|
535
|
+
updateMetricRoute.respond(res, 200, {
|
|
536
|
+
id: updated.id,
|
|
537
|
+
version: metricEditCounter(updated.id),
|
|
538
|
+
});
|
|
476
539
|
} catch (err) {
|
|
477
540
|
jsonError(res, err instanceof Error ? err.message : String(err));
|
|
478
541
|
}
|
|
@@ -17,7 +17,7 @@ import { getPublicMcpBaseUrl } from "@/utils/constants";
|
|
|
17
17
|
import { registerVolatileSecret, scrubSecrets } from "@/utils/secret-scrubber";
|
|
18
18
|
import { completeMcpOAuthCallback } from "./mcp-oauth";
|
|
19
19
|
import { route } from "./route-def";
|
|
20
|
-
import {
|
|
20
|
+
import { jsonError } from "./utils";
|
|
21
21
|
|
|
22
22
|
// ─── The single static callback + redirect-uri display ───────────────────────
|
|
23
23
|
|
|
@@ -41,7 +41,10 @@ const callbackRoute = route({
|
|
|
41
41
|
error_description: z.string().optional(),
|
|
42
42
|
}),
|
|
43
43
|
responses: {
|
|
44
|
-
200: {
|
|
44
|
+
200: {
|
|
45
|
+
description: "OAuth authorization completed",
|
|
46
|
+
unstructured: "HTML success page (sendAuthorizedHtml) — not JSON",
|
|
47
|
+
},
|
|
45
48
|
302: { description: "Redirect back to the final destination" },
|
|
46
49
|
400: { description: "Missing or invalid OAuth callback parameters" },
|
|
47
50
|
404: { description: "OAuth app not configured" },
|
|
@@ -57,7 +60,10 @@ const redirectUriRoute = route({
|
|
|
57
60
|
summary: "The static OAuth callback URL to register with providers (pre-creation display)",
|
|
58
61
|
tags: ["OAuth"],
|
|
59
62
|
responses: {
|
|
60
|
-
200: {
|
|
63
|
+
200: {
|
|
64
|
+
description: "{ redirectUri: string }",
|
|
65
|
+
schema: z.object({ redirectUri: z.string() }),
|
|
66
|
+
},
|
|
61
67
|
},
|
|
62
68
|
});
|
|
63
69
|
|
|
@@ -280,7 +286,7 @@ export async function handleOAuthCallback(
|
|
|
280
286
|
queryParams: URLSearchParams,
|
|
281
287
|
): Promise<boolean> {
|
|
282
288
|
if (redirectUriRoute.match(req.method, pathSegments)) {
|
|
283
|
-
|
|
289
|
+
redirectUriRoute.respond(res, 200, { redirectUri: staticOAuthCallbackUri() });
|
|
284
290
|
return true;
|
|
285
291
|
}
|
|
286
292
|
|
|
@@ -34,7 +34,11 @@ const genericOAuthCallbackRoute = route({
|
|
|
34
34
|
error_description: z.string().optional(),
|
|
35
35
|
}),
|
|
36
36
|
responses: {
|
|
37
|
-
200: {
|
|
37
|
+
200: {
|
|
38
|
+
description: "OAuth authorization completed",
|
|
39
|
+
unstructured:
|
|
40
|
+
"delegates to completeGenericOAuthCallback, which sends an HTML success page or a 302 redirect to the caller-supplied finalRedirect — never JSON",
|
|
41
|
+
},
|
|
38
42
|
400: { description: "Missing or invalid OAuth callback parameters" },
|
|
39
43
|
404: { description: "OAuth app not configured" },
|
|
40
44
|
502: { description: "Token exchange failed" },
|
package/src/http/oauth-locks.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { IncomingMessage, ServerResponse } from "node:http";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { acquireOAuthRefreshLock, releaseOAuthRefreshLock } from "../be/db-queries/oauth";
|
|
4
4
|
import { route } from "./route-def";
|
|
5
|
-
import {
|
|
5
|
+
import { jsonError } from "./utils";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* HTTP surface for the cross-process OAuth refresh-token lock
|
|
@@ -39,7 +39,10 @@ const acquireLockRoute = route({
|
|
|
39
39
|
params: LockKeySchema,
|
|
40
40
|
body: acquireBodySchema,
|
|
41
41
|
responses: {
|
|
42
|
-
200: {
|
|
42
|
+
200: {
|
|
43
|
+
description: "Lock acquired; returns the owner token",
|
|
44
|
+
schema: z.object({ owner: z.string() }),
|
|
45
|
+
},
|
|
43
46
|
409: { description: "Lock is currently held by another caller" },
|
|
44
47
|
400: { description: "Validation error" },
|
|
45
48
|
},
|
|
@@ -73,7 +76,7 @@ export async function handleOAuthLocks(
|
|
|
73
76
|
jsonError(res, "lock is held by another caller", 409);
|
|
74
77
|
return true;
|
|
75
78
|
}
|
|
76
|
-
|
|
79
|
+
acquireLockRoute.respond(res, 200, { owner });
|
|
77
80
|
return true;
|
|
78
81
|
}
|
|
79
82
|
|
package/src/http/openapi.ts
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
extendZodWithOpenApi,
|
|
3
|
-
OpenAPIRegistry,
|
|
4
|
-
OpenApiGeneratorV31,
|
|
5
|
-
} from "@asteasolutions/zod-to-openapi";
|
|
6
|
-
import { z } from "zod";
|
|
1
|
+
import { OpenAPIRegistry, OpenApiGeneratorV31 } from "@asteasolutions/zod-to-openapi";
|
|
7
2
|
import { getPublicMcpBaseUrl } from "../utils/constants";
|
|
3
|
+
import { z } from "../utils/zod-openapi";
|
|
8
4
|
import { routeRegistry } from "./route-def";
|
|
9
5
|
|
|
10
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Default component for 4xx/5xx responses that declare no schema of their own:
|
|
8
|
+
* the `jsonError()` envelope every error path emits. Loose because a few
|
|
9
|
+
* handlers attach extra diagnostic fields (e.g. TriggerSchemaError's
|
|
10
|
+
* `message`/`details`) — those may declare an explicit schema instead.
|
|
11
|
+
*/
|
|
12
|
+
const ErrorResponseSchema = z
|
|
13
|
+
.looseObject({ error: z.string() })
|
|
14
|
+
.openapi("ErrorResponse", { description: "Standard error envelope" });
|
|
11
15
|
|
|
12
16
|
let cachedSpec: string | null = null;
|
|
13
17
|
|
|
@@ -43,11 +47,16 @@ export function generateOpenApiSpec(opts: OpenApiOptions): string {
|
|
|
43
47
|
{ description: string; content?: Record<string, { schema: z.ZodType }> }
|
|
44
48
|
> = {};
|
|
45
49
|
for (const [code, resDef] of Object.entries(routeDef.responses)) {
|
|
50
|
+
const schema =
|
|
51
|
+
resDef.schema ??
|
|
52
|
+
// Untyped error responses default to the shared jsonError envelope;
|
|
53
|
+
// `unstructured` (non-JSON body) opts out.
|
|
54
|
+
(Number(code) >= 400 && !resDef.unstructured ? ErrorResponseSchema : undefined);
|
|
46
55
|
responses[code] = {
|
|
47
56
|
description: resDef.description,
|
|
48
|
-
...(
|
|
57
|
+
...(schema && {
|
|
49
58
|
content: {
|
|
50
|
-
"application/json": { schema
|
|
59
|
+
"application/json": { schema },
|
|
51
60
|
},
|
|
52
61
|
}),
|
|
53
62
|
};
|
|
@@ -77,6 +86,42 @@ export function generateOpenApiSpec(opts: OpenApiOptions): string {
|
|
|
77
86
|
|
|
78
87
|
const serverUrl = opts.serverUrl || getPublicMcpBaseUrl();
|
|
79
88
|
|
|
89
|
+
/**
|
|
90
|
+
* zod-to-openapi emits `.nullable()` on an `.extend()`ed (or `$ref`ed)
|
|
91
|
+
* schema as `allOf: [{$ref}, {type: ["object","null"], ...delta}]` — an
|
|
92
|
+
* allOf member allowing null is semantically wrong (the intersection with
|
|
93
|
+
* the non-null base can never be null), and TS-definition generators
|
|
94
|
+
* (fumadocs-openapi) collapse it to `never`. Rewrite to the intended
|
|
95
|
+
* `anyOf: [<intersection>, {type: "null"}]`.
|
|
96
|
+
*/
|
|
97
|
+
function fixNullableAllOf(node: unknown): void {
|
|
98
|
+
if (!node || typeof node !== "object") return;
|
|
99
|
+
if (Array.isArray(node)) {
|
|
100
|
+
for (const item of node) fixNullableAllOf(item);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const obj = node as Record<string, unknown>;
|
|
104
|
+
const allOf = obj.allOf;
|
|
105
|
+
if (Array.isArray(allOf)) {
|
|
106
|
+
let hadNull = false;
|
|
107
|
+
for (const member of allOf) {
|
|
108
|
+
if (member && Array.isArray(member.type) && member.type.includes("null")) {
|
|
109
|
+
hadNull = true;
|
|
110
|
+
const rest = member.type.filter((t: string) => t !== "null");
|
|
111
|
+
member.type = rest.length === 1 ? rest[0] : rest;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (hadNull) {
|
|
115
|
+
// Drop members reduced to a bare `{type: "object"}` (no constraints left).
|
|
116
|
+
const members = allOf.filter((m) => !(Object.keys(m).length === 1 && m.type === "object"));
|
|
117
|
+
const intersection = members.length === 1 ? members[0] : { allOf: members };
|
|
118
|
+
delete obj.allOf;
|
|
119
|
+
obj.anyOf = [intersection, { type: "null" }];
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
for (const value of Object.values(obj)) fixNullableAllOf(value);
|
|
123
|
+
}
|
|
124
|
+
|
|
80
125
|
const generator = new OpenApiGeneratorV31(registry.definitions);
|
|
81
126
|
const doc = generator.generateDocument({
|
|
82
127
|
openapi: "3.1.0",
|
|
@@ -96,6 +141,9 @@ export function generateOpenApiSpec(opts: OpenApiOptions): string {
|
|
|
96
141
|
],
|
|
97
142
|
});
|
|
98
143
|
|
|
144
|
+
fixNullableAllOf(doc.paths);
|
|
145
|
+
fixNullableAllOf(doc.components);
|
|
146
|
+
|
|
99
147
|
cachedSpec = JSON.stringify(doc, null, 2);
|
|
100
148
|
return cachedSpec;
|
|
101
149
|
}
|
package/src/http/page-proxy.ts
CHANGED
|
@@ -40,7 +40,11 @@ route({
|
|
|
40
40
|
summary: "Cookie-gated proxy to the swarm API (used by db-backed page iframes)",
|
|
41
41
|
tags: ["Pages"],
|
|
42
42
|
responses: {
|
|
43
|
-
200: {
|
|
43
|
+
200: {
|
|
44
|
+
description: "Proxied response from the underlying /api/* endpoint",
|
|
45
|
+
unstructured:
|
|
46
|
+
"Body is forwarded verbatim from the proxied /api/* endpoint; shape and content-type vary per target route",
|
|
47
|
+
},
|
|
44
48
|
401: { description: "No or invalid page-session cookie" },
|
|
45
49
|
404: { description: "Page referenced by the cookie no longer exists" },
|
|
46
50
|
},
|
|
@@ -54,7 +58,11 @@ route({
|
|
|
54
58
|
summary: "Cookie-gated proxy to the swarm API (POST)",
|
|
55
59
|
tags: ["Pages"],
|
|
56
60
|
responses: {
|
|
57
|
-
200: {
|
|
61
|
+
200: {
|
|
62
|
+
description: "Proxied response",
|
|
63
|
+
unstructured:
|
|
64
|
+
"Body is forwarded verbatim from the proxied /api/* endpoint; shape and content-type vary per target route",
|
|
65
|
+
},
|
|
58
66
|
401: { description: "No or invalid page-session cookie" },
|
|
59
67
|
},
|
|
60
68
|
auth: { apiKey: false },
|
|
@@ -67,7 +75,11 @@ route({
|
|
|
67
75
|
summary: "Cookie-gated proxy to the swarm API (PUT)",
|
|
68
76
|
tags: ["Pages"],
|
|
69
77
|
responses: {
|
|
70
|
-
200: {
|
|
78
|
+
200: {
|
|
79
|
+
description: "Proxied response",
|
|
80
|
+
unstructured:
|
|
81
|
+
"Body is forwarded verbatim from the proxied /api/* endpoint; shape and content-type vary per target route",
|
|
82
|
+
},
|
|
71
83
|
401: { description: "No or invalid page-session cookie" },
|
|
72
84
|
},
|
|
73
85
|
auth: { apiKey: false },
|
|
@@ -80,7 +92,11 @@ route({
|
|
|
80
92
|
summary: "Cookie-gated proxy to the swarm API (DELETE)",
|
|
81
93
|
tags: ["Pages"],
|
|
82
94
|
responses: {
|
|
83
|
-
200: {
|
|
95
|
+
200: {
|
|
96
|
+
description: "Proxied response",
|
|
97
|
+
unstructured:
|
|
98
|
+
"Body is forwarded verbatim from the proxied /api/* endpoint; shape and content-type vary per target route",
|
|
99
|
+
},
|
|
84
100
|
401: { description: "No or invalid page-session cookie" },
|
|
85
101
|
},
|
|
86
102
|
auth: { apiKey: false },
|
|
@@ -93,7 +109,11 @@ route({
|
|
|
93
109
|
summary: "Cookie-gated proxy to the swarm API (PATCH)",
|
|
94
110
|
tags: ["Pages"],
|
|
95
111
|
responses: {
|
|
96
|
-
200: {
|
|
112
|
+
200: {
|
|
113
|
+
description: "Proxied response",
|
|
114
|
+
unstructured:
|
|
115
|
+
"Body is forwarded verbatim from the proxied /api/* endpoint; shape and content-type vary per target route",
|
|
116
|
+
},
|
|
97
117
|
401: { description: "No or invalid page-session cookie" },
|
|
98
118
|
},
|
|
99
119
|
auth: { apiKey: false },
|
package/src/http/pages-public.ts
CHANGED
|
@@ -24,7 +24,7 @@ import type { IncomingMessage, ServerResponse } from "node:http";
|
|
|
24
24
|
import { z } from "zod";
|
|
25
25
|
import { BROWSER_SDK_JS, SWARM_UI_JS } from "../artifact-sdk/browser-sdk";
|
|
26
26
|
import { getPage, incrementPageViewCount } from "../be/db";
|
|
27
|
-
import type
|
|
27
|
+
import { type Page, PageSchema } from "../types";
|
|
28
28
|
import { getAppUrl, getConfiguredAppUrls } from "../utils/constants";
|
|
29
29
|
import { extractAndVerifyCookie, issuePageSessionCookie } from "../utils/page-session";
|
|
30
30
|
import { scrubSecrets } from "../utils/secret-scrubber";
|
|
@@ -32,6 +32,22 @@ import { route } from "./route-def";
|
|
|
32
32
|
|
|
33
33
|
// ─── Route definitions (registered with auth: { apiKey: false }) ────────────
|
|
34
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Wire shape sent by the `/p/{id}.json` 200 response — a subset of `Page`
|
|
37
|
+
* fields plus a hardcoded `version: 1` (edit-counter is API-internal; the SPA
|
|
38
|
+
* reads real version numbers via `/api/pages/:id/versions`, NOT this field).
|
|
39
|
+
*/
|
|
40
|
+
const PublicPageJsonSchema = PageSchema.pick({
|
|
41
|
+
id: true,
|
|
42
|
+
title: true,
|
|
43
|
+
description: true,
|
|
44
|
+
contentType: true,
|
|
45
|
+
authMode: true,
|
|
46
|
+
body: true,
|
|
47
|
+
}).extend({
|
|
48
|
+
version: z.literal(1),
|
|
49
|
+
});
|
|
50
|
+
|
|
35
51
|
const publicPageRoute = route({
|
|
36
52
|
method: "get",
|
|
37
53
|
path: "/p/{id}",
|
|
@@ -40,7 +56,11 @@ const publicPageRoute = route({
|
|
|
40
56
|
tags: ["Pages"],
|
|
41
57
|
params: z.object({ id: z.string() }),
|
|
42
58
|
responses: {
|
|
43
|
-
200: {
|
|
59
|
+
200: {
|
|
60
|
+
description: "Rendered HTML page",
|
|
61
|
+
unstructured:
|
|
62
|
+
"Serves the page's raw agent-authored HTML body (SDK-injected) or, for ?print=1 on a JSON-content page, a standalone printable HTML document — not a JSON shape",
|
|
63
|
+
},
|
|
44
64
|
302: { description: "Redirect to SPA for JSON content" },
|
|
45
65
|
401: { description: "Page requires an authenticated session" },
|
|
46
66
|
403: { description: "Cookie does not match this page id" },
|
|
@@ -57,7 +77,7 @@ const publicPageJsonRoute = route({
|
|
|
57
77
|
tags: ["Pages"],
|
|
58
78
|
params: z.object({ id: z.string() }),
|
|
59
79
|
responses: {
|
|
60
|
-
200: { description: "Page JSON" },
|
|
80
|
+
200: { description: "Page JSON", schema: PublicPageJsonSchema },
|
|
61
81
|
401: { description: "Page requires an authenticated session" },
|
|
62
82
|
403: { description: "Cookie does not match this page id" },
|
|
63
83
|
404: { description: "Page not found" },
|
|
@@ -545,23 +565,20 @@ export async function handlePagesPublic(
|
|
|
545
565
|
// Returns the current head state (no version history). Body included
|
|
546
566
|
// verbatim. NOTE: passwordHash / agentId are NOT exposed here — these
|
|
547
567
|
// are private. step-4 may revisit if needed.
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
if (inlineSetCookie)
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
body: page.body,
|
|
563
|
-
}),
|
|
564
|
-
);
|
|
568
|
+
// `res.setHeader` merges with the `Content-Type: application/json` header
|
|
569
|
+
// that `respond()` writes via `res.writeHead()` — Node gives the latter
|
|
570
|
+
// precedence, which is the value we want here anyway.
|
|
571
|
+
res.setHeader("Cache-Control", "no-store");
|
|
572
|
+
if (inlineSetCookie) res.setHeader("Set-Cookie", inlineSetCookie);
|
|
573
|
+
publicPageJsonRoute.respond(res, 200, {
|
|
574
|
+
id: page.id,
|
|
575
|
+
version: 1, // edit-counter is API-internal; SPA reads via /api/pages/:id/versions
|
|
576
|
+
title: page.title,
|
|
577
|
+
description: page.description,
|
|
578
|
+
contentType: page.contentType,
|
|
579
|
+
authMode: page.authMode,
|
|
580
|
+
body: page.body,
|
|
581
|
+
});
|
|
565
582
|
bumpViewCount(page.id);
|
|
566
583
|
return true;
|
|
567
584
|
}
|