@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
|
@@ -19,6 +19,61 @@ import { deriveApiBaseUrl, parseQueryParams } from "../utils";
|
|
|
19
19
|
const MANUAL_WEBHOOK_INSTRUCTIONS =
|
|
20
20
|
"See docs-site/.../guides/jira-integration.mdx for manual webhook registration steps.";
|
|
21
21
|
|
|
22
|
+
// ─── Response Schemas ────────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
const JiraWebhookIdEntrySchema = z.object({
|
|
25
|
+
id: z.number().int(),
|
|
26
|
+
expiresAt: z.string(),
|
|
27
|
+
jql: z.string(),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
/** Shape returned by both GET /status and POST /refresh (buildJiraStatusPayload). */
|
|
31
|
+
const JiraStatusPayloadSchema = z.object({
|
|
32
|
+
provider: z.literal("jira"),
|
|
33
|
+
connected: z.boolean(),
|
|
34
|
+
cloudId: z.string().nullable(),
|
|
35
|
+
siteUrl: z.string().nullable(),
|
|
36
|
+
tokenExpiresAt: z.string().nullable(),
|
|
37
|
+
scope: z.string().nullable(),
|
|
38
|
+
hasManageWebhookScope: z.boolean(),
|
|
39
|
+
webhookTokenConfigured: z.boolean(),
|
|
40
|
+
webhookUrl: z.string(),
|
|
41
|
+
redirectUri: z.string(),
|
|
42
|
+
webhookIds: z.array(JiraWebhookIdEntrySchema),
|
|
43
|
+
// Only present when the OAuth grant lacks `manage:jira-webhook` scope.
|
|
44
|
+
manualWebhookInstructions: z.string().optional(),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
type JiraStatusPayload = z.infer<typeof JiraStatusPayloadSchema>;
|
|
48
|
+
|
|
49
|
+
/** Body shape of handleJiraWebhook's WebhookResult when status === 200. */
|
|
50
|
+
const JiraWebhookEventResultSchema = z.union([
|
|
51
|
+
z.object({ status: z.literal("accepted") }),
|
|
52
|
+
z.object({ status: z.literal("duplicate") }),
|
|
53
|
+
z.object({ status: z.literal("ignored"), reason: z.literal("invalid-json") }),
|
|
54
|
+
]);
|
|
55
|
+
|
|
56
|
+
type JiraWebhookEventResult = z.infer<typeof JiraWebhookEventResultSchema>;
|
|
57
|
+
|
|
58
|
+
const JiraWebhookRegisteredSchema = z.object({
|
|
59
|
+
webhookId: z.number().int(),
|
|
60
|
+
expiresAt: z.string(),
|
|
61
|
+
jql: z.string(),
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const JiraWebhookDeletedSchema = z.object({
|
|
65
|
+
deleted: z.literal(true),
|
|
66
|
+
webhookId: z.number().int(),
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const JiraDisconnectResultSchema = z.object({
|
|
70
|
+
disconnected: z.literal(true),
|
|
71
|
+
webhooksDeleted: z.number().int().nonnegative(),
|
|
72
|
+
webhooksTotal: z.number().int().nonnegative(),
|
|
73
|
+
webhookFailures: z.array(z.object({ id: z.number().int(), error: z.string() })),
|
|
74
|
+
revokeNote: z.string(),
|
|
75
|
+
});
|
|
76
|
+
|
|
22
77
|
// ─── Route Definitions ───────────────────────────────────────────────────────
|
|
23
78
|
|
|
24
79
|
const jiraAuthorize = route({
|
|
@@ -47,7 +102,11 @@ const jiraCallback = route({
|
|
|
47
102
|
state: z.string(),
|
|
48
103
|
}),
|
|
49
104
|
responses: {
|
|
50
|
-
200: {
|
|
105
|
+
200: {
|
|
106
|
+
description: "OAuth complete",
|
|
107
|
+
unstructured:
|
|
108
|
+
"Delegates to completeGenericOAuthCallback (src/http/oauth-callback.ts), which sends an HTML success page or a redirect on success — never a fixed JSON shape",
|
|
109
|
+
},
|
|
51
110
|
400: { description: "Invalid state or code" },
|
|
52
111
|
500: { description: "Token exchange or accessible-resources fetch failed" },
|
|
53
112
|
},
|
|
@@ -61,7 +120,7 @@ const jiraStatus = route({
|
|
|
61
120
|
"Jira connection status, cloudId/siteUrl, token expiry, expected webhook URL, scope/token-config flags",
|
|
62
121
|
tags: ["Trackers"],
|
|
63
122
|
responses: {
|
|
64
|
-
200: { description: "Connection status" },
|
|
123
|
+
200: { description: "Connection status", schema: JiraStatusPayloadSchema },
|
|
65
124
|
503: { description: "Jira integration not configured" },
|
|
66
125
|
},
|
|
67
126
|
});
|
|
@@ -74,7 +133,10 @@ const jiraRefresh = route({
|
|
|
74
133
|
"Force a Jira OAuth token refresh and return the updated status payload. Useful when an agent observes an expired token via tracker-status / db-query and wants to recover without restarting the server or re-running 3LO.",
|
|
75
134
|
tags: ["Trackers"],
|
|
76
135
|
responses: {
|
|
77
|
-
200: {
|
|
136
|
+
200: {
|
|
137
|
+
description: "Token refreshed; returns same shape as /status",
|
|
138
|
+
schema: JiraStatusPayloadSchema,
|
|
139
|
+
},
|
|
78
140
|
409: { description: "Jira not connected (no refresh token stored)" },
|
|
79
141
|
500: { description: "Refresh failed (e.g. revoked grant, network error)" },
|
|
80
142
|
503: { description: "Jira integration not configured" },
|
|
@@ -91,7 +153,7 @@ const jiraWebhook = route({
|
|
|
91
153
|
auth: { apiKey: false },
|
|
92
154
|
params: z.object({ token: z.string() }),
|
|
93
155
|
responses: {
|
|
94
|
-
200: { description: "Event accepted" },
|
|
156
|
+
200: { description: "Event accepted", schema: JiraWebhookEventResultSchema },
|
|
95
157
|
401: { description: "Invalid URL token" },
|
|
96
158
|
503: { description: "Jira webhook handler not configured" },
|
|
97
159
|
},
|
|
@@ -110,7 +172,7 @@ const jiraWebhookRegister = route({
|
|
|
110
172
|
jqlFilter: z.string().min(1),
|
|
111
173
|
}),
|
|
112
174
|
responses: {
|
|
113
|
-
200: { description: "Webhook registered" },
|
|
175
|
+
200: { description: "Webhook registered", schema: JiraWebhookRegisteredSchema },
|
|
114
176
|
400: { description: "Invalid jqlFilter" },
|
|
115
177
|
503: { description: "Jira not connected or JIRA_WEBHOOK_TOKEN missing" },
|
|
116
178
|
},
|
|
@@ -128,7 +190,7 @@ const jiraWebhookDelete = route({
|
|
|
128
190
|
id: z.coerce.number().int().positive(),
|
|
129
191
|
}),
|
|
130
192
|
responses: {
|
|
131
|
-
200: { description: "Webhook deleted" },
|
|
193
|
+
200: { description: "Webhook deleted", schema: JiraWebhookDeletedSchema },
|
|
132
194
|
400: { description: "Invalid webhook id" },
|
|
133
195
|
503: { description: "Jira not connected" },
|
|
134
196
|
},
|
|
@@ -145,7 +207,7 @@ const jiraDisconnect = route({
|
|
|
145
207
|
summary: "Fully disconnect Jira: delete all webhooks, drop tokens, clear metadata",
|
|
146
208
|
tags: ["Trackers"],
|
|
147
209
|
responses: {
|
|
148
|
-
200: { description: "Disconnected" },
|
|
210
|
+
200: { description: "Disconnected", schema: JiraDisconnectResultSchema },
|
|
149
211
|
503: { description: "Jira not configured" },
|
|
150
212
|
},
|
|
151
213
|
});
|
|
@@ -166,7 +228,7 @@ function getRedirectUri(req: IncomingMessage): string {
|
|
|
166
228
|
return `${deriveApiBaseUrl(req)}/api/trackers/jira/callback`;
|
|
167
229
|
}
|
|
168
230
|
|
|
169
|
-
function buildJiraStatusPayload(req: IncomingMessage):
|
|
231
|
+
function buildJiraStatusPayload(req: IncomingMessage): JiraStatusPayload {
|
|
170
232
|
const tokens = getOAuthTokens("jira");
|
|
171
233
|
const meta = getJiraMetadata();
|
|
172
234
|
const scope = tokens?.scope ?? null;
|
|
@@ -174,7 +236,7 @@ function buildJiraStatusPayload(req: IncomingMessage): Record<string, unknown> {
|
|
|
174
236
|
const scopeList = scope ? scope.split(/[\s,]+/).filter(Boolean) : [];
|
|
175
237
|
const hasManageWebhookScope = scopeList.includes("manage:jira-webhook");
|
|
176
238
|
|
|
177
|
-
const status:
|
|
239
|
+
const status: JiraStatusPayload = {
|
|
178
240
|
provider: "jira",
|
|
179
241
|
connected: !!tokens,
|
|
180
242
|
cloudId: meta.cloudId ?? null,
|
|
@@ -258,8 +320,7 @@ export async function handleJiraTracker(
|
|
|
258
320
|
return true;
|
|
259
321
|
}
|
|
260
322
|
|
|
261
|
-
|
|
262
|
-
res.end(JSON.stringify(buildJiraStatusPayload(req)));
|
|
323
|
+
jiraStatus.respond(res, 200, buildJiraStatusPayload(req));
|
|
263
324
|
return true;
|
|
264
325
|
}
|
|
265
326
|
|
|
@@ -296,8 +357,7 @@ export async function handleJiraTracker(
|
|
|
296
357
|
return true;
|
|
297
358
|
}
|
|
298
359
|
|
|
299
|
-
|
|
300
|
-
res.end(JSON.stringify(buildJiraStatusPayload(req)));
|
|
360
|
+
jiraRefresh.respond(res, 200, buildJiraStatusPayload(req));
|
|
301
361
|
return true;
|
|
302
362
|
}
|
|
303
363
|
|
|
@@ -332,6 +392,11 @@ export async function handleJiraTracker(
|
|
|
332
392
|
return true;
|
|
333
393
|
}
|
|
334
394
|
|
|
395
|
+
if (result.status === 200) {
|
|
396
|
+
jiraWebhook.respond(res, 200, result.body as JiraWebhookEventResult);
|
|
397
|
+
return true;
|
|
398
|
+
}
|
|
399
|
+
|
|
335
400
|
res.writeHead(result.status, { "Content-Type": "application/json" });
|
|
336
401
|
res.end(typeof result.body === "string" ? result.body : JSON.stringify(result.body));
|
|
337
402
|
return true;
|
|
@@ -356,8 +421,7 @@ export async function handleJiraTracker(
|
|
|
356
421
|
|
|
357
422
|
try {
|
|
358
423
|
const result = await registerJiraWebhook(parsed.body.jqlFilter);
|
|
359
|
-
|
|
360
|
-
res.end(JSON.stringify(result));
|
|
424
|
+
jiraWebhookRegister.respond(res, 200, result);
|
|
361
425
|
} catch (err) {
|
|
362
426
|
const message = err instanceof Error ? err.message : String(err);
|
|
363
427
|
console.error("[Jira] Webhook register failed:", message);
|
|
@@ -383,8 +447,7 @@ export async function handleJiraTracker(
|
|
|
383
447
|
|
|
384
448
|
try {
|
|
385
449
|
await deleteJiraWebhook(parsed.params.id);
|
|
386
|
-
|
|
387
|
-
res.end(JSON.stringify({ deleted: true, webhookId: parsed.params.id }));
|
|
450
|
+
jiraWebhookDelete.respond(res, 200, { deleted: true, webhookId: parsed.params.id });
|
|
388
451
|
} catch (err) {
|
|
389
452
|
const message = err instanceof Error ? err.message : String(err);
|
|
390
453
|
console.error(`[Jira] Webhook delete failed (id=${parsed.params.id}):`, message);
|
|
@@ -425,19 +488,16 @@ export async function handleJiraTracker(
|
|
|
425
488
|
`[Jira] Disconnected: ${webhooksDeleted}/${ids.length} webhooks deleted, tokens cleared, metadata reset`,
|
|
426
489
|
);
|
|
427
490
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
"Atlassian OAuth grants must be revoked manually at https://id.atlassian.com/manage/connected-apps if you want to fully sever the consent.",
|
|
439
|
-
}),
|
|
440
|
-
);
|
|
491
|
+
jiraDisconnect.respond(res, 200, {
|
|
492
|
+
disconnected: true,
|
|
493
|
+
webhooksDeleted,
|
|
494
|
+
webhooksTotal: ids.length,
|
|
495
|
+
webhookFailures,
|
|
496
|
+
// Atlassian 3LO has no token revocation endpoint — surface this so
|
|
497
|
+
// the UI can prompt the user to revoke the grant manually if desired.
|
|
498
|
+
revokeNote:
|
|
499
|
+
"Atlassian OAuth grants must be revoked manually at https://id.atlassian.com/manage/connected-apps if you want to fully sever the consent.",
|
|
500
|
+
});
|
|
441
501
|
return true;
|
|
442
502
|
}
|
|
443
503
|
|
|
@@ -14,6 +14,28 @@ import { completeGenericOAuthCallback } from "../oauth-callback";
|
|
|
14
14
|
import { route } from "../route-def";
|
|
15
15
|
import { deriveApiBaseUrl, parseQueryParams } from "../utils";
|
|
16
16
|
|
|
17
|
+
// ─── Schemas ─────────────────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
/** Shape returned by `buildLinearStatusPayload` — shared by /status and /refresh. */
|
|
20
|
+
const LinearStatusSchema = z.object({
|
|
21
|
+
provider: z.literal("linear"),
|
|
22
|
+
connected: z.boolean(),
|
|
23
|
+
tokenExpiry: z.string().nullable(),
|
|
24
|
+
scope: z.string().nullable(),
|
|
25
|
+
webhookUrl: z.string(),
|
|
26
|
+
redirectUri: z.string(),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
/** The two `body` shapes `handleLinearWebhook` (../../linear/webhook.ts) sends for its 200 code. */
|
|
30
|
+
const LinearWebhookAcceptedSchema = z.object({
|
|
31
|
+
status: z.enum(["duplicate", "accepted"]),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const LinearDisconnectSchema = z.object({
|
|
35
|
+
disconnected: z.literal(true),
|
|
36
|
+
revoked: z.boolean(),
|
|
37
|
+
});
|
|
38
|
+
|
|
17
39
|
// ─── Route Definitions ───────────────────────────────────────────────────────
|
|
18
40
|
|
|
19
41
|
const linearAuthorize = route({
|
|
@@ -42,7 +64,11 @@ const linearCallback = route({
|
|
|
42
64
|
state: z.string(),
|
|
43
65
|
}),
|
|
44
66
|
responses: {
|
|
45
|
-
200: {
|
|
67
|
+
200: {
|
|
68
|
+
description: "OAuth complete",
|
|
69
|
+
unstructured:
|
|
70
|
+
"HTML success page (sendAuthorizedHtml) sent by completeGenericOAuthCallback in ../oauth-callback.ts — shape not owned by this file",
|
|
71
|
+
},
|
|
46
72
|
400: { description: "Invalid state or code" },
|
|
47
73
|
500: { description: "Token exchange failed" },
|
|
48
74
|
},
|
|
@@ -55,7 +81,7 @@ const linearStatus = route({
|
|
|
55
81
|
summary: "Linear connection status, token expiry, workspace info, expected webhook URL",
|
|
56
82
|
tags: ["Trackers"],
|
|
57
83
|
responses: {
|
|
58
|
-
200: { description: "Connection status" },
|
|
84
|
+
200: { description: "Connection status", schema: LinearStatusSchema },
|
|
59
85
|
503: { description: "Linear integration not configured" },
|
|
60
86
|
},
|
|
61
87
|
});
|
|
@@ -68,7 +94,10 @@ const linearRefresh = route({
|
|
|
68
94
|
"Force a Linear OAuth token refresh and return the updated status payload. Useful when an agent observes an expired token and wants to recover without restarting the server or re-running OAuth.",
|
|
69
95
|
tags: ["Trackers"],
|
|
70
96
|
responses: {
|
|
71
|
-
200: {
|
|
97
|
+
200: {
|
|
98
|
+
description: "Token refreshed; returns same shape as /status",
|
|
99
|
+
schema: LinearStatusSchema,
|
|
100
|
+
},
|
|
72
101
|
409: { description: "Linear not connected (no refresh token stored)" },
|
|
73
102
|
500: { description: "Refresh failed" },
|
|
74
103
|
503: { description: "Linear integration not configured" },
|
|
@@ -83,7 +112,7 @@ const linearWebhook = route({
|
|
|
83
112
|
tags: ["Trackers"],
|
|
84
113
|
auth: { apiKey: false },
|
|
85
114
|
responses: {
|
|
86
|
-
200: { description: "Event accepted" },
|
|
115
|
+
200: { description: "Event accepted", schema: LinearWebhookAcceptedSchema },
|
|
87
116
|
401: { description: "Invalid signature" },
|
|
88
117
|
503: { description: "Linear integration not configured" },
|
|
89
118
|
},
|
|
@@ -99,14 +128,14 @@ const linearDisconnect = route({
|
|
|
99
128
|
summary: "Fully disconnect Linear: revoke OAuth grant + drop tokens",
|
|
100
129
|
tags: ["Trackers"],
|
|
101
130
|
responses: {
|
|
102
|
-
200: { description: "Disconnected" },
|
|
131
|
+
200: { description: "Disconnected", schema: LinearDisconnectSchema },
|
|
103
132
|
503: { description: "Linear not configured" },
|
|
104
133
|
},
|
|
105
134
|
});
|
|
106
135
|
|
|
107
136
|
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
108
137
|
|
|
109
|
-
function buildLinearStatusPayload(req: IncomingMessage):
|
|
138
|
+
function buildLinearStatusPayload(req: IncomingMessage): z.infer<typeof LinearStatusSchema> {
|
|
110
139
|
const tokens = getOAuthTokens("linear");
|
|
111
140
|
const baseUrl = deriveApiBaseUrl(req);
|
|
112
141
|
|
|
@@ -180,8 +209,7 @@ export async function handleLinearTracker(
|
|
|
180
209
|
return true;
|
|
181
210
|
}
|
|
182
211
|
|
|
183
|
-
|
|
184
|
-
res.end(JSON.stringify(buildLinearStatusPayload(req)));
|
|
212
|
+
linearStatus.respond(res, 200, buildLinearStatusPayload(req));
|
|
185
213
|
return true;
|
|
186
214
|
}
|
|
187
215
|
|
|
@@ -217,8 +245,7 @@ export async function handleLinearTracker(
|
|
|
217
245
|
return true;
|
|
218
246
|
}
|
|
219
247
|
|
|
220
|
-
|
|
221
|
-
res.end(JSON.stringify(buildLinearStatusPayload(req)));
|
|
248
|
+
linearRefresh.respond(res, 200, buildLinearStatusPayload(req));
|
|
222
249
|
return true;
|
|
223
250
|
}
|
|
224
251
|
|
|
@@ -239,6 +266,14 @@ export async function handleLinearTracker(
|
|
|
239
266
|
};
|
|
240
267
|
|
|
241
268
|
const result = await handleLinearWebhook(rawBody, headers);
|
|
269
|
+
// handleLinearWebhook's return type is `{ status: number; body: unknown }` (it
|
|
270
|
+
// can send 503/401/200 — all declared on this route); narrow to the literal
|
|
271
|
+
// 200 code to use the typed egress for the one 2xx case, keep the raw
|
|
272
|
+
// passthrough (same status/body/header as before) for 401/503.
|
|
273
|
+
if (result.status === 200) {
|
|
274
|
+
linearWebhook.respond(res, 200, result.body as z.infer<typeof LinearWebhookAcceptedSchema>);
|
|
275
|
+
return true;
|
|
276
|
+
}
|
|
242
277
|
res.writeHead(result.status, { "Content-Type": "application/json" });
|
|
243
278
|
res.end(JSON.stringify(result.body));
|
|
244
279
|
return true;
|
|
@@ -262,8 +297,7 @@ export async function handleLinearTracker(
|
|
|
262
297
|
|
|
263
298
|
console.log(`[Linear] Disconnected: revoke=${revoked}, tokens cleared`);
|
|
264
299
|
|
|
265
|
-
|
|
266
|
-
res.end(JSON.stringify({ disconnected: true, revoked }));
|
|
300
|
+
linearDisconnect.respond(res, 200, { disconnected: true, revoked });
|
|
267
301
|
return true;
|
|
268
302
|
}
|
|
269
303
|
|