@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/agents.ts
CHANGED
|
@@ -23,12 +23,15 @@ import {
|
|
|
23
23
|
upsertSwarmConfig,
|
|
24
24
|
} from "../be/db";
|
|
25
25
|
import { reasoningCapability } from "../providers/reasoning-effort";
|
|
26
|
-
import { getEnabledCapabilities } from "../server";
|
|
26
|
+
import { ALL_CAPABILITIES, getEnabledCapabilities } from "../server";
|
|
27
27
|
import { telemetry } from "../telemetry";
|
|
28
28
|
import {
|
|
29
29
|
AgentAvatarSchema,
|
|
30
30
|
AgentCredStatusSchema,
|
|
31
31
|
AgentLatestModelSchema,
|
|
32
|
+
AgentSchema,
|
|
33
|
+
AgentStatusSchema,
|
|
34
|
+
AgentWithTasksSchema,
|
|
32
35
|
type ProviderName,
|
|
33
36
|
ProviderNameSchema,
|
|
34
37
|
ReasoningEffortSchema,
|
|
@@ -39,6 +42,47 @@ import { agentWithCapacity, json, jsonError } from "./utils";
|
|
|
39
42
|
|
|
40
43
|
// ─── Route Definitions ───────────────────────────────────────────────────────
|
|
41
44
|
|
|
45
|
+
/** Mirrors `CAPABILITIES_T` (src/server.ts) — the server's registered MCP tool-group flags. */
|
|
46
|
+
const CapabilitySchema = z.enum(ALL_CAPABILITIES);
|
|
47
|
+
|
|
48
|
+
const AgentCapacitySchema = z.object({
|
|
49
|
+
current: z.number().int(),
|
|
50
|
+
max: z.number().int(),
|
|
51
|
+
available: z.number().int(),
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
/** Shape sent by `agentWithCapacity()` (src/http/utils.ts) for a plain Agent row. */
|
|
55
|
+
const AgentWithCapacitySchema = AgentSchema.extend({
|
|
56
|
+
capacity: AgentCapacitySchema,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Same as `AgentWithCapacitySchema`, plus the `tasks` array added by
|
|
61
|
+
* `getAgentWithTasks`/`getAllAgentsWithTasks` when `?include=tasks` is used.
|
|
62
|
+
* Reuses the canonical `AgentWithTasksSchema` (src/types.ts) rather than
|
|
63
|
+
* re-declaring the `tasks` field inline.
|
|
64
|
+
*/
|
|
65
|
+
const AgentWithCapacityAndTasksSchema = AgentWithTasksSchema.extend({
|
|
66
|
+
capacity: AgentCapacitySchema,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
/** POST /api/agents response: the agent row plus the server's capability flags. */
|
|
70
|
+
const RegisterAgentResponseSchema = AgentSchema.extend({
|
|
71
|
+
enabledCapabilities: z.array(CapabilitySchema),
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
/** Shape shared by the single- and bulk- credential-status endpoints. */
|
|
75
|
+
const AgentCredentialStatusEntrySchema = z.object({
|
|
76
|
+
agentId: z.string(),
|
|
77
|
+
name: z.string(),
|
|
78
|
+
status: AgentStatusSchema,
|
|
79
|
+
missing: z.array(z.string()),
|
|
80
|
+
provider: ProviderNameSchema.nullable(),
|
|
81
|
+
harnessProvider: ProviderNameSchema.nullable(),
|
|
82
|
+
credStatus: AgentCredStatusSchema.nullable(),
|
|
83
|
+
lastCheckedAt: z.string(),
|
|
84
|
+
});
|
|
85
|
+
|
|
42
86
|
const registerAgent = route({
|
|
43
87
|
method: "post",
|
|
44
88
|
path: "/api/agents",
|
|
@@ -64,8 +108,12 @@ const registerAgent = route({
|
|
|
64
108
|
200: {
|
|
65
109
|
description:
|
|
66
110
|
"Agent re-registered (already existed). Response includes `enabledCapabilities` — the server's capability flags (registered MCP tool groups), not the agent's declared skill tags.",
|
|
111
|
+
schema: RegisterAgentResponseSchema,
|
|
112
|
+
},
|
|
113
|
+
201: {
|
|
114
|
+
description: "Agent created. Response includes `enabledCapabilities` (see 200).",
|
|
115
|
+
schema: RegisterAgentResponseSchema,
|
|
67
116
|
},
|
|
68
|
-
201: { description: "Agent created. Response includes `enabledCapabilities` (see 200)." },
|
|
69
117
|
400: { description: "Validation error" },
|
|
70
118
|
},
|
|
71
119
|
});
|
|
@@ -83,7 +131,7 @@ const setAgentHarnessProviderRoute = route({
|
|
|
83
131
|
harness_provider: ProviderNameSchema,
|
|
84
132
|
}),
|
|
85
133
|
responses: {
|
|
86
|
-
200: { description: "Updated agent row" },
|
|
134
|
+
200: { description: "Updated agent row", schema: AgentWithCapacitySchema },
|
|
87
135
|
400: { description: "Validation error (unknown provider)" },
|
|
88
136
|
404: { description: "Agent not found" },
|
|
89
137
|
},
|
|
@@ -107,7 +155,7 @@ const updateAgentRuntimeRoute = route({
|
|
|
107
155
|
reasoning_effort: ReasoningEffortSchema.nullable().optional(),
|
|
108
156
|
}),
|
|
109
157
|
responses: {
|
|
110
|
-
200: { description: "Updated agent row" },
|
|
158
|
+
200: { description: "Updated agent row", schema: AgentWithCapacitySchema },
|
|
111
159
|
400: { description: "Validation error" },
|
|
112
160
|
404: { description: "Agent not found" },
|
|
113
161
|
},
|
|
@@ -127,7 +175,10 @@ const listAgents = route({
|
|
|
127
175
|
fields: z.enum(["full", "slim"]).optional(),
|
|
128
176
|
}),
|
|
129
177
|
responses: {
|
|
130
|
-
200: {
|
|
178
|
+
200: {
|
|
179
|
+
description: "Agent list with capacity info",
|
|
180
|
+
schema: z.object({ agents: z.array(AgentWithCapacityAndTasksSchema) }),
|
|
181
|
+
},
|
|
131
182
|
},
|
|
132
183
|
});
|
|
133
184
|
|
|
@@ -140,7 +191,7 @@ const updateAgentNameRoute = route({
|
|
|
140
191
|
params: z.object({ id: z.string() }),
|
|
141
192
|
body: z.object({ name: z.string().min(1) }),
|
|
142
193
|
responses: {
|
|
143
|
-
200: { description: "Agent updated" },
|
|
194
|
+
200: { description: "Agent updated", schema: AgentWithCapacitySchema },
|
|
144
195
|
404: { description: "Agent not found" },
|
|
145
196
|
409: { description: "Name conflict" },
|
|
146
197
|
},
|
|
@@ -154,7 +205,13 @@ const getAgentSetupScript = route({
|
|
|
154
205
|
tags: ["Agents"],
|
|
155
206
|
params: z.object({ id: z.string() }),
|
|
156
207
|
responses: {
|
|
157
|
-
200: {
|
|
208
|
+
200: {
|
|
209
|
+
description: "Setup scripts",
|
|
210
|
+
schema: z.object({
|
|
211
|
+
setupScript: z.string().nullable(),
|
|
212
|
+
globalSetupScript: z.string().nullable(),
|
|
213
|
+
}),
|
|
214
|
+
},
|
|
158
215
|
404: { description: "Agent not found" },
|
|
159
216
|
},
|
|
160
217
|
});
|
|
@@ -183,7 +240,7 @@ const updateAgentProfileRoute = route({
|
|
|
183
240
|
changeReason: z.string().optional(),
|
|
184
241
|
}),
|
|
185
242
|
responses: {
|
|
186
|
-
200: { description: "Profile updated" },
|
|
243
|
+
200: { description: "Profile updated", schema: AgentWithCapacitySchema },
|
|
187
244
|
400: { description: "Validation error" },
|
|
188
245
|
404: { description: "Agent not found" },
|
|
189
246
|
},
|
|
@@ -212,7 +269,7 @@ const getAgent = route({
|
|
|
212
269
|
include: z.enum(["tasks"]).optional(),
|
|
213
270
|
}),
|
|
214
271
|
responses: {
|
|
215
|
-
200: { description: "Agent with capacity info" },
|
|
272
|
+
200: { description: "Agent with capacity info", schema: AgentWithCapacityAndTasksSchema },
|
|
216
273
|
404: { description: "Agent not found" },
|
|
217
274
|
},
|
|
218
275
|
});
|
|
@@ -246,7 +303,10 @@ const updateAgentCredentialStatusRoute = route({
|
|
|
246
303
|
params: z.object({ id: z.string() }),
|
|
247
304
|
body: credentialStatusBody,
|
|
248
305
|
responses: {
|
|
249
|
-
200: {
|
|
306
|
+
200: {
|
|
307
|
+
description: "State updated; returns the agent row.",
|
|
308
|
+
schema: AgentWithCapacitySchema,
|
|
309
|
+
},
|
|
250
310
|
404: { description: "Agent not found" },
|
|
251
311
|
},
|
|
252
312
|
});
|
|
@@ -259,7 +319,7 @@ const getAgentCredentialStatusRoute = route({
|
|
|
259
319
|
tags: ["Agents"],
|
|
260
320
|
params: z.object({ id: z.string() }),
|
|
261
321
|
responses: {
|
|
262
|
-
200: { description: "Credential status payload" },
|
|
322
|
+
200: { description: "Credential status payload", schema: AgentCredentialStatusEntrySchema },
|
|
263
323
|
404: { description: "Agent not found" },
|
|
264
324
|
},
|
|
265
325
|
});
|
|
@@ -274,7 +334,10 @@ const listCredentialStatusRoute = route({
|
|
|
274
334
|
status: z.enum(["idle", "busy", "offline", "waiting_for_credentials"]).optional(),
|
|
275
335
|
}),
|
|
276
336
|
responses: {
|
|
277
|
-
200: {
|
|
337
|
+
200: {
|
|
338
|
+
description: "List of {agentId, status, missing[], lastCheckedAt}",
|
|
339
|
+
schema: z.object({ agents: z.array(AgentCredentialStatusEntrySchema) }),
|
|
340
|
+
},
|
|
278
341
|
},
|
|
279
342
|
});
|
|
280
343
|
|
|
@@ -375,11 +438,15 @@ export async function handleAgentRegister(
|
|
|
375
438
|
// `enabledCapabilities` = the server's capability flags (which MCP tool
|
|
376
439
|
// groups are registered), NOT the agent's declared skill tags. Workers use
|
|
377
440
|
// it to drop prompt sections that instruct unregistered tools.
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
)
|
|
441
|
+
// Non-null assertion: `result.agent` is only `| null` in the TS union
|
|
442
|
+
// because the re-registration branch re-fetches by id inside the same
|
|
443
|
+
// transaction right after confirming the row exists — it cannot actually
|
|
444
|
+
// be null here. (Same value as before; this only affects the compile-time
|
|
445
|
+
// type used to check the response schema.)
|
|
446
|
+
registerAgent.respond(res, result.created ? 201 : 200, {
|
|
447
|
+
...result.agent!,
|
|
448
|
+
enabledCapabilities: getEnabledCapabilities(),
|
|
449
|
+
});
|
|
383
450
|
return true;
|
|
384
451
|
}
|
|
385
452
|
|
|
@@ -401,7 +468,7 @@ export async function handleAgentsRest(
|
|
|
401
468
|
const slim = parsed.query.fields !== "full";
|
|
402
469
|
const agents = includeTasks ? getAllAgentsWithTasks({ slim }) : getAllAgents({ slim });
|
|
403
470
|
const agentsWithCapacity = agents.map(agentWithCapacity);
|
|
404
|
-
|
|
471
|
+
listAgents.respond(res, 200, { agents: agentsWithCapacity });
|
|
405
472
|
return true;
|
|
406
473
|
}
|
|
407
474
|
|
|
@@ -415,7 +482,7 @@ export async function handleAgentsRest(
|
|
|
415
482
|
jsonError(res, "Agent not found", 404);
|
|
416
483
|
return true;
|
|
417
484
|
}
|
|
418
|
-
|
|
485
|
+
updateAgentNameRoute.respond(res, 200, agentWithCapacity(agent));
|
|
419
486
|
} catch (error) {
|
|
420
487
|
jsonError(res, (error as Error).message, 409);
|
|
421
488
|
}
|
|
@@ -432,7 +499,7 @@ export async function handleAgentsRest(
|
|
|
432
499
|
}
|
|
433
500
|
const globalConfigs = getSwarmConfigs({ scope: "global", key: "SETUP_SCRIPT" });
|
|
434
501
|
const globalSetupScript = globalConfigs[0]?.value ?? null;
|
|
435
|
-
|
|
502
|
+
getAgentSetupScript.respond(res, 200, {
|
|
436
503
|
setupScript: agent.setupScript ?? null,
|
|
437
504
|
globalSetupScript,
|
|
438
505
|
});
|
|
@@ -502,7 +569,7 @@ export async function handleAgentsRest(
|
|
|
502
569
|
return true;
|
|
503
570
|
}
|
|
504
571
|
|
|
505
|
-
|
|
572
|
+
updateAgentProfileRoute.respond(res, 200, agentWithCapacity(agent));
|
|
506
573
|
return true;
|
|
507
574
|
}
|
|
508
575
|
|
|
@@ -533,7 +600,7 @@ export async function handleAgentsRest(
|
|
|
533
600
|
value: parsed.body.harness_provider,
|
|
534
601
|
description: "Set via PATCH /api/agents/{id}/harness-provider",
|
|
535
602
|
});
|
|
536
|
-
|
|
603
|
+
setAgentHarnessProviderRoute.respond(res, 200, agentWithCapacity(agent));
|
|
537
604
|
return true;
|
|
538
605
|
}
|
|
539
606
|
|
|
@@ -624,7 +691,7 @@ export async function handleAgentsRest(
|
|
|
624
691
|
jsonError(res, "Agent not found", 404);
|
|
625
692
|
return true;
|
|
626
693
|
}
|
|
627
|
-
|
|
694
|
+
updateAgentRuntimeRoute.respond(res, 200, agentWithCapacity(agent));
|
|
628
695
|
return true;
|
|
629
696
|
}
|
|
630
697
|
|
|
@@ -646,7 +713,7 @@ export async function handleAgentsRest(
|
|
|
646
713
|
credStatus: a.credStatus ?? null,
|
|
647
714
|
lastCheckedAt: a.lastUpdatedAt,
|
|
648
715
|
}));
|
|
649
|
-
|
|
716
|
+
listCredentialStatusRoute.respond(res, 200, { agents });
|
|
650
717
|
return true;
|
|
651
718
|
}
|
|
652
719
|
|
|
@@ -709,7 +776,7 @@ export async function handleAgentsRest(
|
|
|
709
776
|
latestModel: parsed.body.latest_model,
|
|
710
777
|
}) ?? agent;
|
|
711
778
|
}
|
|
712
|
-
|
|
779
|
+
updateAgentCredentialStatusRoute.respond(res, 200, agentWithCapacity(finalAgent));
|
|
713
780
|
return true;
|
|
714
781
|
}
|
|
715
782
|
|
|
@@ -721,7 +788,7 @@ export async function handleAgentsRest(
|
|
|
721
788
|
jsonError(res, "Agent not found", 404);
|
|
722
789
|
return true;
|
|
723
790
|
}
|
|
724
|
-
|
|
791
|
+
getAgentCredentialStatusRoute.respond(res, 200, {
|
|
725
792
|
agentId: agent.id,
|
|
726
793
|
name: agent.name,
|
|
727
794
|
status: agent.status,
|
|
@@ -747,7 +814,7 @@ export async function handleAgentsRest(
|
|
|
747
814
|
return true;
|
|
748
815
|
}
|
|
749
816
|
|
|
750
|
-
|
|
817
|
+
getAgent.respond(res, 200, agentWithCapacity(agent));
|
|
751
818
|
return true;
|
|
752
819
|
}
|
|
753
820
|
|
package/src/http/api-keys.ts
CHANGED
|
@@ -11,10 +11,16 @@ import {
|
|
|
11
11
|
setApiKeyName,
|
|
12
12
|
} from "../be/db";
|
|
13
13
|
import { route } from "./route-def";
|
|
14
|
-
import {
|
|
14
|
+
import { jsonError } from "./utils";
|
|
15
15
|
|
|
16
16
|
// ─── Route Definitions ───────────────────────────────────────────────────────
|
|
17
17
|
|
|
18
|
+
/** Wire shape sent by every `report-*` acknowledgement route. */
|
|
19
|
+
const successMessageSchema = z.object({
|
|
20
|
+
success: z.literal(true),
|
|
21
|
+
message: z.string(),
|
|
22
|
+
});
|
|
23
|
+
|
|
18
24
|
const reportUsage = route({
|
|
19
25
|
method: "post",
|
|
20
26
|
path: "/api/keys/report-usage",
|
|
@@ -30,7 +36,7 @@ const reportUsage = route({
|
|
|
30
36
|
scopeId: z.string().optional(),
|
|
31
37
|
}),
|
|
32
38
|
responses: {
|
|
33
|
-
200: { description: "Usage recorded" },
|
|
39
|
+
200: { description: "Usage recorded", schema: successMessageSchema },
|
|
34
40
|
400: { description: "Validation error" },
|
|
35
41
|
401: { description: "Unauthorized" },
|
|
36
42
|
},
|
|
@@ -52,7 +58,7 @@ const reportRateLimit = route({
|
|
|
52
58
|
scopeId: z.string().optional(),
|
|
53
59
|
}),
|
|
54
60
|
responses: {
|
|
55
|
-
200: { description: "Key marked as rate-limited" },
|
|
61
|
+
200: { description: "Key marked as rate-limited", schema: successMessageSchema },
|
|
56
62
|
400: { description: "Validation error" },
|
|
57
63
|
401: { description: "Unauthorized" },
|
|
58
64
|
},
|
|
@@ -83,7 +89,10 @@ const reportRateLimitWindows = route({
|
|
|
83
89
|
scopeId: z.string().optional(),
|
|
84
90
|
}),
|
|
85
91
|
responses: {
|
|
86
|
-
200: {
|
|
92
|
+
200: {
|
|
93
|
+
description: "Rate-limit window telemetry recorded",
|
|
94
|
+
schema: successMessageSchema,
|
|
95
|
+
},
|
|
87
96
|
400: { description: "Validation error" },
|
|
88
97
|
401: { description: "Unauthorized" },
|
|
89
98
|
},
|
|
@@ -103,13 +112,44 @@ const getAvailable = route({
|
|
|
103
112
|
scopeId: z.string().optional(),
|
|
104
113
|
}),
|
|
105
114
|
responses: {
|
|
106
|
-
200: {
|
|
115
|
+
200: {
|
|
116
|
+
description: "List of available key indices",
|
|
117
|
+
schema: z.object({
|
|
118
|
+
success: z.literal(true),
|
|
119
|
+
availableIndices: z.array(z.number().int()),
|
|
120
|
+
totalKeys: z.number().int(),
|
|
121
|
+
}),
|
|
122
|
+
},
|
|
107
123
|
400: { description: "Validation error" },
|
|
108
124
|
401: { description: "Unauthorized" },
|
|
109
125
|
},
|
|
110
126
|
auth: { apiKey: true },
|
|
111
127
|
});
|
|
112
128
|
|
|
129
|
+
/** Mirrors `ApiKeyStatus` in `src/be/db.ts` (not exported from `src/types.ts`). */
|
|
130
|
+
const ApiKeyStatusSchema = z.object({
|
|
131
|
+
id: z.string(),
|
|
132
|
+
keyType: z.string(),
|
|
133
|
+
keySuffix: z.string(),
|
|
134
|
+
keyIndex: z.number().int(),
|
|
135
|
+
scope: z.string(),
|
|
136
|
+
scopeId: z.string().nullable(),
|
|
137
|
+
status: z.string(),
|
|
138
|
+
rateLimitedUntil: z.string().nullable(),
|
|
139
|
+
lastUsedAt: z.string().nullable(),
|
|
140
|
+
lastRateLimitAt: z.string().nullable(),
|
|
141
|
+
totalUsageCount: z.number().int(),
|
|
142
|
+
rateLimitCount: z.number().int(),
|
|
143
|
+
/** Optional human-friendly label set from the dashboard. */
|
|
144
|
+
name: z.string().nullable(),
|
|
145
|
+
/** Auto-derived harness provider (claude/pi/codex/...). */
|
|
146
|
+
provider: z.string(),
|
|
147
|
+
/** Latest provider-emitted rate-limit window snapshots, keyed by window type. */
|
|
148
|
+
rateLimitWindows: z.record(z.string(), rateLimitWindowSchema),
|
|
149
|
+
createdAt: z.string(),
|
|
150
|
+
updatedAt: z.string(),
|
|
151
|
+
});
|
|
152
|
+
|
|
113
153
|
const listStatuses = route({
|
|
114
154
|
method: "get",
|
|
115
155
|
path: "/api/keys/status",
|
|
@@ -122,12 +162,25 @@ const listStatuses = route({
|
|
|
122
162
|
scopeId: z.string().optional(),
|
|
123
163
|
}),
|
|
124
164
|
responses: {
|
|
125
|
-
200: {
|
|
165
|
+
200: {
|
|
166
|
+
description: "List of key status records",
|
|
167
|
+
schema: z.object({ success: z.literal(true), keys: z.array(ApiKeyStatusSchema) }),
|
|
168
|
+
},
|
|
126
169
|
401: { description: "Unauthorized" },
|
|
127
170
|
},
|
|
128
171
|
auth: { apiKey: true },
|
|
129
172
|
});
|
|
130
173
|
|
|
174
|
+
/** Mirrors `KeyCostSummary` in `src/be/db.ts`. */
|
|
175
|
+
const KeyCostSummarySchema = z.object({
|
|
176
|
+
keyType: z.string(),
|
|
177
|
+
keySuffix: z.string(),
|
|
178
|
+
totalCost: z.number().nonnegative(),
|
|
179
|
+
totalInputTokens: z.number().int().nonnegative(),
|
|
180
|
+
totalOutputTokens: z.number().int().nonnegative(),
|
|
181
|
+
taskCount: z.number().int().nonnegative(),
|
|
182
|
+
});
|
|
183
|
+
|
|
131
184
|
const getCosts = route({
|
|
132
185
|
method: "get",
|
|
133
186
|
path: "/api/keys/costs",
|
|
@@ -138,7 +191,10 @@ const getCosts = route({
|
|
|
138
191
|
keyType: z.string().optional(),
|
|
139
192
|
}),
|
|
140
193
|
responses: {
|
|
141
|
-
200: {
|
|
194
|
+
200: {
|
|
195
|
+
description: "Per-key cost aggregation",
|
|
196
|
+
schema: z.object({ success: z.literal(true), costs: z.array(KeyCostSummarySchema) }),
|
|
197
|
+
},
|
|
142
198
|
401: { description: "Unauthorized" },
|
|
143
199
|
},
|
|
144
200
|
auth: { apiKey: true },
|
|
@@ -159,7 +215,15 @@ const setKeyName = route({
|
|
|
159
215
|
scopeId: z.string().optional(),
|
|
160
216
|
}),
|
|
161
217
|
responses: {
|
|
162
|
-
200: {
|
|
218
|
+
200: {
|
|
219
|
+
description: "Name updated",
|
|
220
|
+
schema: z.object({
|
|
221
|
+
success: z.literal(true),
|
|
222
|
+
keyType: z.string(),
|
|
223
|
+
keySuffix: z.string(),
|
|
224
|
+
name: z.string().nullable(),
|
|
225
|
+
}),
|
|
226
|
+
},
|
|
163
227
|
400: { description: "Validation error" },
|
|
164
228
|
401: { description: "Unauthorized" },
|
|
165
229
|
404: { description: "Key not found" },
|
|
@@ -180,7 +244,14 @@ const clearRateLimitRoute = route({
|
|
|
180
244
|
scopeId: z.string().optional(),
|
|
181
245
|
}),
|
|
182
246
|
responses: {
|
|
183
|
-
200: {
|
|
247
|
+
200: {
|
|
248
|
+
description: "Rate limit cleared (or key was not rate-limited)",
|
|
249
|
+
schema: z.object({
|
|
250
|
+
success: z.literal(true),
|
|
251
|
+
cleared: z.boolean(),
|
|
252
|
+
message: z.string(),
|
|
253
|
+
}),
|
|
254
|
+
},
|
|
184
255
|
400: { description: "Validation error" },
|
|
185
256
|
401: { description: "Unauthorized" },
|
|
186
257
|
},
|
|
@@ -203,7 +274,7 @@ export async function handleApiKeys(
|
|
|
203
274
|
const { keyType, keySuffix, keyIndex, taskId, scope, scopeId } = parsed.body;
|
|
204
275
|
try {
|
|
205
276
|
recordKeyUsage(keyType, keySuffix, keyIndex, taskId ?? null, scope, scopeId ?? null);
|
|
206
|
-
|
|
277
|
+
reportUsage.respond(res, 200, { success: true, message: "Key usage recorded" });
|
|
207
278
|
} catch (err) {
|
|
208
279
|
jsonError(res, err instanceof Error ? err.message : "Failed to record usage", 500);
|
|
209
280
|
}
|
|
@@ -218,7 +289,7 @@ export async function handleApiKeys(
|
|
|
218
289
|
const { keyType, keySuffix, keyIndex, rateLimitedUntil, scope, scopeId } = parsed.body;
|
|
219
290
|
try {
|
|
220
291
|
markKeyRateLimited(keyType, keySuffix, keyIndex, rateLimitedUntil, scope, scopeId ?? null);
|
|
221
|
-
|
|
292
|
+
reportRateLimit.respond(res, 200, {
|
|
222
293
|
success: true,
|
|
223
294
|
message: `Key ...${keySuffix} marked as rate-limited until ${rateLimitedUntil}`,
|
|
224
295
|
});
|
|
@@ -236,7 +307,10 @@ export async function handleApiKeys(
|
|
|
236
307
|
const { keyType, keySuffix, keyIndex, windows, scope, scopeId } = parsed.body;
|
|
237
308
|
try {
|
|
238
309
|
recordKeyRateLimitWindows(keyType, keySuffix, keyIndex, windows, scope, scopeId ?? null);
|
|
239
|
-
|
|
310
|
+
reportRateLimitWindows.respond(res, 200, {
|
|
311
|
+
success: true,
|
|
312
|
+
message: `Rate-limit windows recorded for ...${keySuffix}`,
|
|
313
|
+
});
|
|
240
314
|
} catch (err) {
|
|
241
315
|
jsonError(
|
|
242
316
|
res,
|
|
@@ -255,7 +329,7 @@ export async function handleApiKeys(
|
|
|
255
329
|
const { keyType, totalKeys, scope, scopeId } = parsed.query;
|
|
256
330
|
try {
|
|
257
331
|
const indices = getAvailableKeyIndices(keyType, totalKeys, scope, scopeId ?? null);
|
|
258
|
-
|
|
332
|
+
getAvailable.respond(res, 200, { success: true, availableIndices: indices, totalKeys });
|
|
259
333
|
} catch (err) {
|
|
260
334
|
jsonError(res, err instanceof Error ? err.message : "Failed to get available keys", 500);
|
|
261
335
|
}
|
|
@@ -270,7 +344,7 @@ export async function handleApiKeys(
|
|
|
270
344
|
const { keyType } = parsed.query;
|
|
271
345
|
try {
|
|
272
346
|
const costs = getKeyCostSummary(keyType);
|
|
273
|
-
|
|
347
|
+
getCosts.respond(res, 200, { success: true, costs });
|
|
274
348
|
} catch (err) {
|
|
275
349
|
jsonError(res, err instanceof Error ? err.message : "Failed to get key costs", 500);
|
|
276
350
|
}
|
|
@@ -285,7 +359,7 @@ export async function handleApiKeys(
|
|
|
285
359
|
const { keyType, scope, scopeId } = parsed.query;
|
|
286
360
|
try {
|
|
287
361
|
const statuses = getKeyStatuses(keyType, scope, scopeId ?? null);
|
|
288
|
-
|
|
362
|
+
listStatuses.respond(res, 200, { success: true, keys: statuses });
|
|
289
363
|
} catch (err) {
|
|
290
364
|
jsonError(res, err instanceof Error ? err.message : "Failed to get key statuses", 500);
|
|
291
365
|
}
|
|
@@ -307,7 +381,7 @@ export async function handleApiKeys(
|
|
|
307
381
|
jsonError(res, `No key matching ${keyType} ...${keySuffix}`, 404);
|
|
308
382
|
return true;
|
|
309
383
|
}
|
|
310
|
-
|
|
384
|
+
setKeyName.respond(res, 200, { success: true, keyType, keySuffix, name: value });
|
|
311
385
|
} catch (err) {
|
|
312
386
|
jsonError(res, err instanceof Error ? err.message : "Failed to set key name", 500);
|
|
313
387
|
}
|
|
@@ -322,7 +396,7 @@ export async function handleApiKeys(
|
|
|
322
396
|
const { keyType, keySuffix, scope, scopeId } = parsed.body;
|
|
323
397
|
try {
|
|
324
398
|
const cleared = clearKeyRateLimit(keyType, keySuffix, scope, scopeId ?? null);
|
|
325
|
-
|
|
399
|
+
clearRateLimitRoute.respond(res, 200, {
|
|
326
400
|
success: true,
|
|
327
401
|
cleared,
|
|
328
402
|
message: cleared
|
|
@@ -2,6 +2,7 @@ import type { IncomingMessage, ServerResponse } from "node:http";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { resolveTaskAuditUserId } from "../be/audit-user";
|
|
4
4
|
import {
|
|
5
|
+
type ApprovalRequest,
|
|
5
6
|
createApprovalRequest,
|
|
6
7
|
createTaskExtended,
|
|
7
8
|
getApprovalRequestById,
|
|
@@ -13,7 +14,7 @@ import { resolveTemplate } from "../prompts/resolver";
|
|
|
13
14
|
import { getRequestAuth } from "../utils/request-auth-context";
|
|
14
15
|
import { workflowEventBus } from "../workflows/event-bus";
|
|
15
16
|
import { route } from "./route-def";
|
|
16
|
-
import {
|
|
17
|
+
import { jsonError } from "./utils";
|
|
17
18
|
|
|
18
19
|
// ─── Route Definitions ───────────────────────────────────────────────────────
|
|
19
20
|
|
|
@@ -41,6 +42,69 @@ const QuestionSchema = z.object({
|
|
|
41
42
|
|
|
42
43
|
export type ApprovalQuestion = z.infer<typeof QuestionSchema>;
|
|
43
44
|
|
|
45
|
+
// ─── Response Schemas ────────────────────────────────────────────────────────
|
|
46
|
+
// `ApprovalRequest` (src/be/db.ts) types `questions`/`approvers`/`responses`/
|
|
47
|
+
// `notificationChannels` as `unknown` — the DB layer stores opaque JSON blobs.
|
|
48
|
+
// Reading every writer (this file's create route, tools/request-human-input.ts,
|
|
49
|
+
// workflows/executors/human-in-the-loop.ts) confirms they always conform to the
|
|
50
|
+
// shapes below, so we describe them precisely here and cast at the call sites
|
|
51
|
+
// (matching the existing `existing.questions as ApprovalQuestion[]` pattern in
|
|
52
|
+
// this file) rather than degrading the documented contract to `z.unknown()`.
|
|
53
|
+
|
|
54
|
+
const ApproversSchema = z.object({
|
|
55
|
+
users: z.array(z.string()).optional(),
|
|
56
|
+
roles: z.array(z.string()).optional(),
|
|
57
|
+
policy: z.union([z.literal("any"), z.literal("all"), z.object({ min: z.number().int().min(1) })]),
|
|
58
|
+
});
|
|
59
|
+
type ApproversShape = z.infer<typeof ApproversSchema>;
|
|
60
|
+
|
|
61
|
+
const NotificationChannelSchema = z.object({
|
|
62
|
+
channel: z.enum(["slack", "email"]),
|
|
63
|
+
target: z.string(),
|
|
64
|
+
// Added post-creation by workflows/executors/human-in-the-loop.ts once the
|
|
65
|
+
// notification message is sent (see `updateApprovalRequestNotifications`).
|
|
66
|
+
messageTs: z.string().optional(),
|
|
67
|
+
});
|
|
68
|
+
type NotificationChannelShape = z.infer<typeof NotificationChannelSchema>;
|
|
69
|
+
|
|
70
|
+
const ApprovalRequestSchema = z.object({
|
|
71
|
+
id: z.string(),
|
|
72
|
+
title: z.string(),
|
|
73
|
+
questions: z.array(QuestionSchema),
|
|
74
|
+
workflowRunId: z.string().nullable(),
|
|
75
|
+
workflowRunStepId: z.string().nullable(),
|
|
76
|
+
sourceTaskId: z.string().nullable(),
|
|
77
|
+
approvers: ApproversSchema,
|
|
78
|
+
status: z.enum(["pending", "approved", "rejected", "timeout"]),
|
|
79
|
+
responses: z.record(z.string(), z.unknown()).nullable(),
|
|
80
|
+
resolvedBy: z.string().nullable(),
|
|
81
|
+
resolvedAt: z.string().nullable(),
|
|
82
|
+
timeoutSeconds: z.number().nullable(),
|
|
83
|
+
expiresAt: z.string().nullable(),
|
|
84
|
+
notificationChannels: z.array(NotificationChannelSchema).nullable(),
|
|
85
|
+
createdBy: z.string().optional(),
|
|
86
|
+
createdAt: z.string(),
|
|
87
|
+
updatedAt: z.string(),
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Reshapes a DB `ApprovalRequest` row for `respond()` — identical values,
|
|
92
|
+
* narrowed from the DB layer's `unknown` fields to the precise wire shape
|
|
93
|
+
* (see comment above `ApproversSchema`). Not a behavior change: same object
|
|
94
|
+
* contents, serialized the same way.
|
|
95
|
+
*/
|
|
96
|
+
function toApprovalRequestResponse(
|
|
97
|
+
request: ApprovalRequest,
|
|
98
|
+
): z.infer<typeof ApprovalRequestSchema> {
|
|
99
|
+
return {
|
|
100
|
+
...request,
|
|
101
|
+
questions: request.questions as ApprovalQuestion[],
|
|
102
|
+
approvers: request.approvers as ApproversShape,
|
|
103
|
+
responses: request.responses as Record<string, unknown> | null,
|
|
104
|
+
notificationChannels: request.notificationChannels as NotificationChannelShape[] | null,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
44
108
|
function hasRequiredResponse(question: ApprovalQuestion, response: unknown): boolean {
|
|
45
109
|
switch (question.type) {
|
|
46
110
|
case "approval":
|
|
@@ -118,7 +182,10 @@ const createRoute = route({
|
|
|
118
182
|
.optional(),
|
|
119
183
|
}),
|
|
120
184
|
responses: {
|
|
121
|
-
201: {
|
|
185
|
+
201: {
|
|
186
|
+
description: "Approval request created",
|
|
187
|
+
schema: z.object({ approvalRequest: ApprovalRequestSchema }),
|
|
188
|
+
},
|
|
122
189
|
400: { description: "Validation error" },
|
|
123
190
|
},
|
|
124
191
|
auth: { apiKey: true },
|
|
@@ -132,7 +199,10 @@ const getByIdRoute = route({
|
|
|
132
199
|
tags: ["ApprovalRequests"],
|
|
133
200
|
params: z.object({ id: z.string().uuid() }),
|
|
134
201
|
responses: {
|
|
135
|
-
200: {
|
|
202
|
+
200: {
|
|
203
|
+
description: "Approval request details",
|
|
204
|
+
schema: z.object({ approvalRequest: ApprovalRequestSchema }),
|
|
205
|
+
},
|
|
136
206
|
404: { description: "Not found" },
|
|
137
207
|
},
|
|
138
208
|
auth: { apiKey: true },
|
|
@@ -150,7 +220,10 @@ const respondRoute = route({
|
|
|
150
220
|
respondedBy: z.string().optional(),
|
|
151
221
|
}),
|
|
152
222
|
responses: {
|
|
153
|
-
200: {
|
|
223
|
+
200: {
|
|
224
|
+
description: "Response recorded",
|
|
225
|
+
schema: z.object({ approvalRequest: ApprovalRequestSchema }),
|
|
226
|
+
},
|
|
154
227
|
400: { description: "Validation error" },
|
|
155
228
|
404: { description: "Not found" },
|
|
156
229
|
409: { description: "Already resolved" },
|
|
@@ -170,7 +243,10 @@ const listRoute = route({
|
|
|
170
243
|
limit: z.coerce.number().optional(),
|
|
171
244
|
}),
|
|
172
245
|
responses: {
|
|
173
|
-
200: {
|
|
246
|
+
200: {
|
|
247
|
+
description: "List of approval requests",
|
|
248
|
+
schema: z.object({ approvalRequests: z.array(ApprovalRequestSchema) }),
|
|
249
|
+
},
|
|
174
250
|
},
|
|
175
251
|
auth: { apiKey: true },
|
|
176
252
|
});
|
|
@@ -278,7 +354,7 @@ export async function handleApprovalRequests(
|
|
|
278
354
|
}
|
|
279
355
|
}
|
|
280
356
|
|
|
281
|
-
|
|
357
|
+
respondRoute.respond(res, 200, { approvalRequest: toApprovalRequestResponse(updated) });
|
|
282
358
|
return true;
|
|
283
359
|
}
|
|
284
360
|
|
|
@@ -293,7 +369,7 @@ export async function handleApprovalRequests(
|
|
|
293
369
|
return true;
|
|
294
370
|
}
|
|
295
371
|
|
|
296
|
-
|
|
372
|
+
getByIdRoute.respond(res, 200, { approvalRequest: toApprovalRequestResponse(request) });
|
|
297
373
|
return true;
|
|
298
374
|
}
|
|
299
375
|
|
|
@@ -325,8 +401,7 @@ export async function handleApprovalRequests(
|
|
|
325
401
|
createdBy,
|
|
326
402
|
});
|
|
327
403
|
|
|
328
|
-
|
|
329
|
-
res.end(JSON.stringify({ approvalRequest: request }));
|
|
404
|
+
createRoute.respond(res, 201, { approvalRequest: toApprovalRequestResponse(request) });
|
|
330
405
|
return true;
|
|
331
406
|
}
|
|
332
407
|
|
|
@@ -341,7 +416,9 @@ export async function handleApprovalRequests(
|
|
|
341
416
|
limit: parsed.query.limit || undefined,
|
|
342
417
|
});
|
|
343
418
|
|
|
344
|
-
|
|
419
|
+
listRoute.respond(res, 200, {
|
|
420
|
+
approvalRequests: requests.map(toApprovalRequestResponse),
|
|
421
|
+
});
|
|
345
422
|
return true;
|
|
346
423
|
}
|
|
347
424
|
|