@desplega.ai/agent-swarm 1.113.0 → 1.115.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-q4n7cz6e.js → actions-17ya5wde.js} +8 -9
- package/dist/{app-es4nzc71.js → app-4f718yk0.js} +6 -6
- package/dist/{assistant-0x8ey0vs.js → assistant-ah7csnjz.js} +19 -18
- package/dist/{boot-reembed-69agkhv6.js → boot-reembed-4jpxhb25.js} +6 -6
- package/dist/{boot-reembed-p0ny05t8.js → boot-reembed-qata34xa.js} +5 -5
- package/dist/{boot-scrub-logs-yybrkmvr.js → boot-scrub-logs-tv48nn3z.js} +4 -4
- package/dist/{claude-adapter-mma9a1yr.js → claude-adapter-3csn5bks.js} +3 -3
- package/dist/{claude-managed-adapter-pv2nbqy4.js → claude-managed-adapter-kysmkz89.js} +2 -2
- package/dist/cli-0pygq9r1.js +429 -0
- package/dist/{cli-hsetkkd3.js → cli-12vb0xg6.js} +33 -9
- package/dist/{cli-ncfgsqbd.js → cli-15y7qhzp.js} +262 -33
- package/dist/{cli-7ga43spr.js → cli-1yjrbg1f.js} +1 -1
- package/dist/{cli-36ymmpmp.js → cli-3samkyap.js} +2 -2
- package/dist/cli-52fg7s7x.js +269 -0
- package/dist/{cli-hjhvekf4.js → cli-65ekje01.js} +11 -8
- package/dist/{cli-h3k622d0.js → cli-6aq8xhvm.js} +90 -3
- package/dist/{cli-f3qa069v.js → cli-7xje5amz.js} +1 -1
- package/dist/cli-86fdnerz.js +405 -0
- package/dist/{cli-9cy6nc5m.js → cli-8fjwym53.js} +1 -1
- package/dist/{cli-c3frqf65.js → cli-b23axgz4.js} +3 -3
- package/dist/{cli-a0t7vapf.js → cli-bc89my0z.js} +1 -1
- package/dist/{cli-bnzbn8j8.js → cli-beea4a3y.js} +73 -24
- package/dist/{cli-q21d49ac.js → cli-bw80ck94.js} +1 -1
- package/dist/{cli-pwc5e83c.js → cli-cchksf5q.js} +12 -7
- package/dist/{cli-30bbaveh.js → cli-cmw9rzy5.js} +2 -2
- package/dist/{cli-y6mrptcn.js → cli-cnwssmsr.js} +1 -1
- package/dist/{cli-r5g6ngtn.js → cli-dcca6j8m.js} +46 -5
- package/dist/{cli-tkmm5nf9.js → cli-e61fs2hp.js} +59 -2
- package/dist/{cli-6wscj9z0.js → cli-e7306swv.js} +14 -14
- package/dist/{cli-dcfyyh3t.js → cli-ftyf58xj.js} +2 -2
- package/dist/{cli-4xyj9jtq.js → cli-hrhye2rm.js} +1062 -478
- package/dist/cli-jkhwmmgg.js +136 -0
- package/dist/{cli-y4ycrnjc.js → cli-k3y4z3h5.js} +1 -1
- package/dist/{cli-6xd0bvf8.js → cli-ma04faw5.js} +1 -1
- package/dist/{cli-2fc97xm5.js → cli-mnbgv3w7.js} +4 -4
- package/dist/{cli-trmapn9k.js → cli-pet3aev6.js} +95 -10
- package/dist/{cli-z46pnksz.js → cli-q1rx7g0d.js} +175 -28
- package/dist/{cli-qwgtacd6.js → cli-r9f6ngrf.js} +1244 -336
- package/dist/{cli-gtkqc144.js → cli-shvaxw27.js} +13 -7
- package/dist/{cli-ywez6yps.js → cli-sq7f72d6.js} +1 -1
- package/dist/cli-t0vqevnm.js +393 -0
- package/dist/{cli-tbw13sx8.js → cli-tece6zgm.js} +1 -1
- package/dist/{cli-bgvskydy.js → cli-tffvrh6j.js} +1 -1
- package/dist/cli-wh91zby6.js +187222 -0
- package/dist/{cli-anrj584m.js → cli-xz9aq0rf.js} +1 -1
- package/dist/{cli-mq580e06.js → cli-zsrdty74.js} +18 -18
- package/dist/cli.js +32 -15
- package/dist/{codex-adapter-5cmy858w.js → codex-adapter-de3w0nch.js} +10 -10
- package/dist/{codex-session-runner-46q8mrba.js → codex-session-runner-bnw8cysk.js} +10 -10
- package/dist/{commands-z5dbwta3.js → commands-hch4e2k1.js} +4 -4
- package/dist/{db-bbgahh4z.js → db-p3h2j06x.js} +14 -4
- package/dist/{handlers-6b44317z.js → handlers-9er4zx9q.js} +17 -31
- package/dist/{hook-sbp5fmps.js → hook-4axs756z.js} +7 -7
- package/dist/{http-2xz43gz3.js → http-809fvtz2.js} +1934 -886
- package/dist/{index-wz1ea15p.js → index-2dgfj9av.js} +3 -3
- package/dist/{index-nhat35em.js → index-47em3d2n.js} +14 -11
- package/dist/{index-5ghxn8s6.js → index-9fqaktjr.js} +12 -9
- package/dist/{index-0gzmqj4k.js → index-9ht8gr56.js} +13 -10
- package/dist/{index-vfedgdw6.js → index-rm85cfdx.js} +368 -40
- package/dist/{index-ykvs43z4.js → index-t2snazez.js} +4 -4
- package/dist/{keepalive-62gfj91d.js → keepalive-3cps6e3e.js} +7 -6
- package/dist/{lead-ag0akn0v.js → lead-fkk95wgq.js} +36 -32
- package/dist/{maintenance-xxrrzk5x.js → maintenance-fweqh0yj.js} +6 -6
- package/dist/{mistral-conversations-26yrnbf1.js → mistral-conversations-7gqnz2wt.js} +7 -7
- package/dist/oauth-refresh-sweep-sycqtbph.js +114 -0
- package/dist/{onboard-64zyfcbs.js → onboard-cy0rkgvx.js} +2 -2
- package/dist/{opencode-adapter-mqsxr170.js → opencode-adapter-p6bxnbm4.js} +2 -2
- package/dist/{otel-zjd9zaxs.js → otel-g299k41b.js} +2 -2
- package/dist/{otel-impl-b86qseaa.js → otel-impl-wbns8hf7.js} +1 -1
- package/dist/{pi-mono-adapter-kxjxpfgy.js → pi-mono-adapter-05sck1ds.js} +17 -109
- package/dist/{pricing-refresh-cgtkffpa.js → pricing-refresh-0hvazmkf.js} +6 -6
- package/dist/rbac-roles-kfesjy9y.js +28 -0
- package/dist/rbac-roles-mwcwhbxc.js +283 -0
- package/dist/{seed-pricing-1r1y1xkq.js → seed-pricing-h2kdq4cq.js} +5 -5
- package/dist/{setup-mkcgkjf7.js → setup-8506q97w.js} +2 -2
- package/dist/{worker-351sze63.js → worker-4r5c6zyb.js} +36 -32
- package/openapi.json +1327 -20
- package/package.json +3 -3
- package/src/agentmail/handlers.ts +16 -6
- package/src/agentmail/templates.ts +25 -5
- package/src/be/audit-user.ts +28 -4
- package/src/be/db-queries/oauth.ts +42 -0
- package/src/be/db.ts +314 -28
- package/src/be/identity.ts +79 -0
- package/src/be/mcp-proxy.ts +132 -0
- package/src/be/migrations/109_rbac_roles.sql +79 -0
- package/src/be/migrations/111_oauth_credential_bindings.sql +6 -0
- package/src/be/migrations/112_script_connections_graphql.sql +96 -0
- package/src/be/migrations/113_task_routing_affinity.sql +9 -0
- package/src/be/migrations/114_backfill_gpt_5_6_pricing.sql +15 -0
- package/src/be/modelsdev-cache.json +135 -0
- package/src/be/oauth-credential-bindings.ts +35 -0
- package/src/be/oauth-refresh-sweep.ts +159 -0
- package/src/be/rbac-audit.ts +52 -13
- package/src/be/rbac-roles.ts +424 -0
- package/src/be/script-connections.ts +459 -40
- package/src/be/script-credential-broker.ts +29 -3
- package/src/be/scripts/typecheck.ts +51 -7
- package/src/be/seed-scripts/catalog/catalog-report.inline.ts +325 -24
- package/src/be/seed-scripts/catalog/catalog-report.ts +325 -24
- package/src/be/users.ts +24 -0
- package/src/cli.tsx +17 -0
- package/src/github/handlers.ts +10 -9
- package/src/github/templates.ts +45 -9
- package/src/gitlab/handlers.ts +20 -8
- package/src/gitlab/templates.ts +18 -6
- package/src/heartbeat/heartbeat.ts +143 -21
- package/src/http/all-routes.ts +3 -0
- package/src/http/approval-requests.ts +12 -0
- package/src/http/config.ts +44 -10
- package/src/http/core.ts +30 -3
- package/src/http/favorites.ts +1 -5
- package/src/http/index.ts +25 -1
- package/src/http/mcp-oauth.ts +2 -0
- package/src/http/mcp-servers.ts +44 -3
- package/src/http/oauth-generic.ts +105 -0
- package/src/http/poll.ts +20 -3
- package/src/http/script-connection-proxy.ts +116 -0
- package/src/http/script-connections.ts +1657 -0
- package/src/http/scripts.ts +27 -7
- package/src/http/skills.ts +11 -0
- package/src/http/tasks.ts +27 -13
- package/src/http/x.ts +6 -2
- package/src/integrations/kapso/inbound.ts +23 -6
- package/src/jira/sync.ts +111 -17
- package/src/jira/templates.ts +10 -2
- package/src/mcp-client/http-client.ts +194 -0
- package/src/oauth/app-validation.ts +16 -0
- package/src/oauth/ensure-token.ts +69 -14
- package/src/oauth/mcp-wrapper.ts +15 -0
- package/src/oauth/wrapper.ts +48 -20
- package/src/prompts/session-templates.ts +3 -3
- package/src/providers/codex-models.ts +100 -30
- package/src/providers/modelsdev-reasoning.json +51 -0
- package/src/providers/pi-mono-mcp-client.ts +2 -133
- package/src/providers/reasoning-effort.ts +11 -5
- package/src/rbac/admission.ts +68 -0
- package/src/rbac/index.ts +2 -0
- package/src/rbac/legacy-policy.ts +12 -0
- package/src/rbac/permissions.ts +48 -0
- package/src/scheduler/scheduler.ts +9 -1
- package/src/scripts-runtime/api-client.ts +151 -19
- package/src/scripts-runtime/api-types.ts +38 -6
- package/src/scripts-runtime/credential-broker/default-bindings.ts +1 -0
- package/src/scripts-runtime/credential-broker/types.ts +8 -0
- package/src/scripts-runtime/ctx.ts +11 -1
- package/src/scripts-runtime/eval-harness.ts +5 -1
- package/src/scripts-runtime/executors/types.ts +2 -1
- package/src/scripts-runtime/loader.ts +3 -1
- package/src/scripts-runtime/mcp-client.ts +87 -0
- package/src/scripts-runtime/sdk-allowlist.ts +1 -0
- package/src/scripts-runtime/types/stdlib.d.ts +6 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +19 -0
- package/src/server-user.ts +114 -59
- package/src/server.ts +12 -0
- package/src/slack/assistant.ts +14 -6
- package/src/slack/enrich.ts +17 -0
- package/src/slack/handlers.ts +8 -22
- package/src/slack/responses.ts +91 -1
- package/src/slack/thread-buffer.ts +6 -3
- package/src/slack/watcher.ts +9 -6
- package/src/tasks/worker-follow-up.ts +100 -3
- package/src/tests/agentmail-handlers.test.ts +32 -3
- package/src/tests/approval-requests.test.ts +61 -0
- package/src/tests/audit-user.test.ts +47 -1
- package/src/tests/codex-adapter.test.ts +46 -23
- package/src/tests/credential-broker.test.ts +5 -2
- package/src/tests/delete-page-tool.test.ts +197 -0
- package/src/tests/github-handlers.test.ts +28 -2
- package/src/tests/gitlab-handlers.test.ts +125 -0
- package/src/tests/heartbeat.test.ts +48 -3
- package/src/tests/identity.test.ts +117 -0
- package/src/tests/jira-sync.test.ts +99 -1
- package/src/tests/kapso-inbound.test.ts +7 -0
- package/src/tests/mcp-tools-user.test.ts +60 -6
- package/src/tests/model-control.test.ts +8 -2
- package/src/tests/oauth-credential-bindings.test.ts +490 -0
- package/src/tests/oauth-refresh-sweep.test.ts +160 -0
- package/src/tests/pool-affinity.test.ts +401 -0
- package/src/tests/prompt-template-remaining.test.ts +2 -2
- package/src/tests/rbac-admission-e2e.test.ts +386 -0
- package/src/tests/rbac-admission.test.ts +492 -0
- package/src/tests/rbac-e2e-helpers.ts +3 -0
- package/src/tests/rbac-engine.test.ts +50 -0
- package/src/tests/rbac-mcp-admission-e2e.test.ts +362 -0
- package/src/tests/rbac-roles.test.ts +328 -0
- package/src/tests/reasoning-effort.test.ts +15 -2
- package/src/tests/schedule-target-type.test.ts +50 -0
- package/src/tests/scheduled-tasks.test.ts +40 -0
- package/src/tests/script-apis-mcp.test.ts +37 -13
- package/src/tests/script-connections-http.test.ts +1158 -0
- package/src/tests/script-connections-mcp.test.ts +894 -0
- package/src/tests/script-connections.test.ts +754 -6
- package/src/tests/scripts-external-api.test.ts +49 -1
- package/src/tests/scripts-runtime-secret-egress.test.ts +1 -0
- package/src/tests/scripts-runtime.test.ts +291 -0
- package/src/tests/seed-scripts.test.ts +36 -0
- package/src/tests/slack-identity-resolution.test.ts +37 -3
- package/src/tests/slack-inline-output.test.ts +65 -0
- package/src/tests/slack-thread-buffer.test.ts +28 -0
- package/src/tests/tool-annotations.test.ts +1 -0
- package/src/tests/user-token-rest-auth.test.ts +140 -1
- package/src/tests/workflow-swarm-script.test.ts +44 -0
- package/src/tests/workflow-triggers-v2.test.ts +300 -1
- package/src/tools/credential-bindings/tool.ts +277 -18
- package/src/tools/delete-page.ts +145 -0
- package/src/tools/mcp-servers/mcp-server-delete.ts +7 -4
- package/src/tools/request-human-input.ts +2 -0
- package/src/tools/resolve-user.ts +77 -27
- package/src/tools/script-apis.ts +62 -3
- package/src/tools/script-connections/tool.ts +275 -31
- package/src/tools/send-task.ts +15 -1
- package/src/tools/task-action.ts +34 -2
- package/src/tools/templates.ts +53 -1
- package/src/tools/tool-config.ts +2 -1
- package/src/tools/utils.ts +2 -0
- package/src/tools/workflows/create-workflow.ts +8 -1
- package/src/tools/workflows/update-workflow.ts +11 -2
- package/src/types.ts +90 -11
- package/src/utils/scoped-resource.ts +25 -0
- package/src/workflows/executors/human-in-the-loop.ts +1 -0
- package/src/workflows/executors/swarm-script.ts +7 -1
- package/src/workflows/triggers.ts +146 -18
- package/templates/skills/swarm-scripts/content.md +33 -8
- package/dist/cli-bgef578c.js +0 -771
- package/dist/cli-c9mtm09b.js +0 -43
- package/dist/cli-ceewqajw.js +0 -1698
- package/dist/{anthropic-messages-8nc6r2dw.js → anthropic-messages-kq3pjhbp.js} +6 -6
- package/dist/{azure-openai-responses-6yfzw3qw.js → azure-openai-responses-gm1ejtmn.js} +7 -7
- package/dist/{cli-vbraamxr.js → cli-6b4wwmm8.js} +9 -9
- package/dist/{cli-2r7s8jhp.js → cli-sznberje.js} +3 -3
- package/dist/{google-generative-ai-vfq6ymz9.js → google-generative-ai-hyrra2jm.js} +3 -3
- package/dist/{google-vertex-mj5y5rfj.js → google-vertex-8bs2pzgn.js} +3 -3
- package/dist/{openai-codex-responses-8h6kvfv8.js → openai-codex-responses-3nkvje79.js} +4 -4
- package/dist/{openai-completions-x6kjnqjd.js → openai-completions-0asftkwg.js} +11 -11
- package/dist/{openai-responses-7jwyz3ds.js → openai-responses-pmatek45.js} +10 -10
|
@@ -4,14 +4,16 @@ import {
|
|
|
4
4
|
handleScheduleTrigger,
|
|
5
5
|
runScript,
|
|
6
6
|
startWorkflowExecution
|
|
7
|
-
} from "./cli-
|
|
7
|
+
} from "./cli-q1rx7g0d.js";
|
|
8
8
|
import {
|
|
9
|
-
getScript
|
|
10
|
-
|
|
9
|
+
getScript,
|
|
10
|
+
getScriptApiConnectionDescriptors,
|
|
11
|
+
getScriptMcpConnectionDescriptors
|
|
12
|
+
} from "./cli-r9f6ngrf.js";
|
|
11
13
|
import {
|
|
12
14
|
createTaskWithSiblingAwareness,
|
|
13
15
|
scheduleContextKey
|
|
14
|
-
} from "./cli-
|
|
16
|
+
} from "./cli-k3y4z3h5.js";
|
|
15
17
|
import {
|
|
16
18
|
getDb,
|
|
17
19
|
getDueScheduledTasks,
|
|
@@ -21,7 +23,7 @@ import {
|
|
|
21
23
|
init_telemetry,
|
|
22
24
|
telemetry,
|
|
23
25
|
updateScheduledTask
|
|
24
|
-
} from "./cli-
|
|
26
|
+
} from "./cli-15y7qhzp.js";
|
|
25
27
|
import {
|
|
26
28
|
__commonJS,
|
|
27
29
|
__require,
|
|
@@ -6870,6 +6872,7 @@ function createStandaloneScheduleTask(schedule, extraTags = []) {
|
|
|
6870
6872
|
modelTier: schedule.modelTier,
|
|
6871
6873
|
scheduleId: schedule.id,
|
|
6872
6874
|
source: "schedule",
|
|
6875
|
+
requestedByUserId: schedule.createdBy,
|
|
6873
6876
|
contextKey: scheduleContextKey({ scheduleId: schedule.id })
|
|
6874
6877
|
});
|
|
6875
6878
|
}
|
|
@@ -6887,7 +6890,9 @@ async function executeScheduleScript(schedule) {
|
|
|
6887
6890
|
args: schedule.scriptArgs ?? {},
|
|
6888
6891
|
fsMode: "none",
|
|
6889
6892
|
agentId,
|
|
6890
|
-
egressSecrets: buildScriptCredentialBindings({ agentId }),
|
|
6893
|
+
egressSecrets: await buildScriptCredentialBindings({ agentId }),
|
|
6894
|
+
apiConnections: getScriptApiConnectionDescriptors({ agentId }),
|
|
6895
|
+
mcpConnections: getScriptMcpConnectionDescriptors({ agentId }),
|
|
6891
6896
|
timeoutMs: 60000
|
|
6892
6897
|
});
|
|
6893
6898
|
if (output.exitCode !== 0 || output.error) {
|
|
@@ -6917,7 +6922,8 @@ async function dispatchScheduleTarget(schedule, extraTags = []) {
|
|
|
6917
6922
|
firedAt: new Date().toISOString()
|
|
6918
6923
|
};
|
|
6919
6924
|
const runId = await startWorkflowExecution(workflow, triggerData, registry, {
|
|
6920
|
-
triggerType: "schedule"
|
|
6925
|
+
triggerType: "schedule",
|
|
6926
|
+
requestedByUserId: schedule.createdBy
|
|
6921
6927
|
});
|
|
6922
6928
|
console.log(`[Scheduler] Schedule "${schedule.name}" → triggered workflow "${workflow.name}"`);
|
|
6923
6929
|
return { triggeredWorkflows: true, workflowRunIds: [runId] };
|
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
import {
|
|
2
|
+
calculatePKCECodeChallenge,
|
|
3
|
+
generateRandomCodeVerifier,
|
|
4
|
+
generateRandomState
|
|
5
|
+
} from "./cli-0pygq9r1.js";
|
|
6
|
+
import {
|
|
7
|
+
getDb,
|
|
8
|
+
init_date_utils,
|
|
9
|
+
init_db,
|
|
10
|
+
normalizeDateRequired
|
|
11
|
+
} from "./cli-15y7qhzp.js";
|
|
12
|
+
|
|
13
|
+
// src/be/db-queries/oauth.ts
|
|
14
|
+
init_date_utils();
|
|
15
|
+
init_db();
|
|
16
|
+
function normalizeOAuthApp(row) {
|
|
17
|
+
return {
|
|
18
|
+
...row,
|
|
19
|
+
createdAt: normalizeDateRequired(row.createdAt),
|
|
20
|
+
updatedAt: normalizeDateRequired(row.updatedAt)
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function normalizeOAuthTokens(row) {
|
|
24
|
+
return {
|
|
25
|
+
...row,
|
|
26
|
+
createdAt: normalizeDateRequired(row.createdAt),
|
|
27
|
+
updatedAt: normalizeDateRequired(row.updatedAt)
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function getOAuthApp(provider) {
|
|
31
|
+
const row = getDb().query("SELECT * FROM oauth_apps WHERE provider = ?").get(provider);
|
|
32
|
+
return row ? normalizeOAuthApp(row) : null;
|
|
33
|
+
}
|
|
34
|
+
function upsertOAuthApp(provider, data) {
|
|
35
|
+
const metadataProvided = data.metadata !== undefined;
|
|
36
|
+
const sql = metadataProvided ? `INSERT INTO oauth_apps (provider, clientId, clientSecret, authorizeUrl, tokenUrl, redirectUri, scopes, metadata)
|
|
37
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
38
|
+
ON CONFLICT(provider) DO UPDATE SET
|
|
39
|
+
clientId = excluded.clientId,
|
|
40
|
+
clientSecret = excluded.clientSecret,
|
|
41
|
+
authorizeUrl = excluded.authorizeUrl,
|
|
42
|
+
tokenUrl = excluded.tokenUrl,
|
|
43
|
+
redirectUri = excluded.redirectUri,
|
|
44
|
+
scopes = excluded.scopes,
|
|
45
|
+
metadata = excluded.metadata,
|
|
46
|
+
updatedAt = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')` : `INSERT INTO oauth_apps (provider, clientId, clientSecret, authorizeUrl, tokenUrl, redirectUri, scopes, metadata)
|
|
47
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, '{}')
|
|
48
|
+
ON CONFLICT(provider) DO UPDATE SET
|
|
49
|
+
clientId = excluded.clientId,
|
|
50
|
+
clientSecret = excluded.clientSecret,
|
|
51
|
+
authorizeUrl = excluded.authorizeUrl,
|
|
52
|
+
tokenUrl = excluded.tokenUrl,
|
|
53
|
+
redirectUri = excluded.redirectUri,
|
|
54
|
+
scopes = excluded.scopes,
|
|
55
|
+
updatedAt = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')`;
|
|
56
|
+
if (metadataProvided) {
|
|
57
|
+
getDb().query(sql).run(provider, data.clientId, data.clientSecret, data.authorizeUrl, data.tokenUrl, data.redirectUri, data.scopes, data.metadata);
|
|
58
|
+
} else {
|
|
59
|
+
getDb().query(sql).run(provider, data.clientId, data.clientSecret, data.authorizeUrl, data.tokenUrl, data.redirectUri, data.scopes);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function getOAuthTokens(provider) {
|
|
63
|
+
const row = getDb().query("SELECT * FROM oauth_tokens WHERE provider = ?").get(provider);
|
|
64
|
+
return row ? normalizeOAuthTokens(row) : null;
|
|
65
|
+
}
|
|
66
|
+
function storeOAuthTokens(provider, data) {
|
|
67
|
+
getDb().query(`INSERT INTO oauth_tokens (provider, accessToken, refreshToken, expiresAt, scope)
|
|
68
|
+
VALUES (?, ?, ?, ?, ?)
|
|
69
|
+
ON CONFLICT(provider) DO UPDATE SET
|
|
70
|
+
accessToken = excluded.accessToken,
|
|
71
|
+
refreshToken = COALESCE(excluded.refreshToken, oauth_tokens.refreshToken),
|
|
72
|
+
expiresAt = excluded.expiresAt,
|
|
73
|
+
scope = COALESCE(excluded.scope, oauth_tokens.scope),
|
|
74
|
+
updatedAt = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')`).run(provider, data.accessToken, data.refreshToken ?? null, data.expiresAt, data.scope ?? null);
|
|
75
|
+
}
|
|
76
|
+
function updateOAuthTokensAfterRefresh(provider, expectedRefreshToken, data) {
|
|
77
|
+
const result = getDb().query(`UPDATE oauth_tokens
|
|
78
|
+
SET accessToken = ?,
|
|
79
|
+
refreshToken = ?,
|
|
80
|
+
expiresAt = ?,
|
|
81
|
+
scope = COALESCE(?, scope),
|
|
82
|
+
updatedAt = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
|
|
83
|
+
WHERE provider = ? AND refreshToken = ?`).run(data.accessToken, data.refreshToken, data.expiresAt, data.scope ?? null, provider, expectedRefreshToken);
|
|
84
|
+
if (result.changes === 1)
|
|
85
|
+
return;
|
|
86
|
+
const current = getOAuthTokens(provider);
|
|
87
|
+
if (!current) {
|
|
88
|
+
throw new Error(`OAuth token refresh persistence failed for ${provider}: token row missing`);
|
|
89
|
+
}
|
|
90
|
+
if (current.refreshToken !== expectedRefreshToken) {
|
|
91
|
+
throw new Error(`OAuth token refresh persistence failed for ${provider}: stored refresh token changed during refresh`);
|
|
92
|
+
}
|
|
93
|
+
throw new Error(`OAuth token refresh persistence failed for ${provider}: no rows updated`);
|
|
94
|
+
}
|
|
95
|
+
function listOAuthTokenSweepRows() {
|
|
96
|
+
const rows = getDb().query(`SELECT t.provider,
|
|
97
|
+
CASE WHEN a.provider IS NOT NULL THEN 1 ELSE 0 END AS hasApp,
|
|
98
|
+
CASE WHEN t.refreshToken IS NOT NULL AND t.refreshToken != '' THEN 1 ELSE 0 END AS hasRefreshToken,
|
|
99
|
+
t.expiresAt,
|
|
100
|
+
t.updatedAt
|
|
101
|
+
FROM oauth_tokens t
|
|
102
|
+
LEFT JOIN oauth_apps a ON a.provider = t.provider
|
|
103
|
+
ORDER BY t.provider ASC`).all();
|
|
104
|
+
return rows.map((row) => ({
|
|
105
|
+
provider: row.provider,
|
|
106
|
+
hasApp: row.hasApp === 1,
|
|
107
|
+
hasRefreshToken: row.hasRefreshToken === 1,
|
|
108
|
+
expiresAt: normalizeDateRequired(row.expiresAt),
|
|
109
|
+
updatedAt: normalizeDateRequired(row.updatedAt)
|
|
110
|
+
}));
|
|
111
|
+
}
|
|
112
|
+
function deleteOAuthTokens(provider) {
|
|
113
|
+
getDb().query("DELETE FROM oauth_tokens WHERE provider = ?").run(provider);
|
|
114
|
+
}
|
|
115
|
+
function isTokenExpiringSoon(provider, bufferMs = 5 * 60 * 1000) {
|
|
116
|
+
const tokens = getOAuthTokens(provider);
|
|
117
|
+
if (!tokens)
|
|
118
|
+
return true;
|
|
119
|
+
const expiresAt = new Date(tokens.expiresAt).getTime();
|
|
120
|
+
return expiresAt - Date.now() < bufferMs;
|
|
121
|
+
}
|
|
122
|
+
function acquireOAuthRefreshLock(provider, ttlMs) {
|
|
123
|
+
const owner = crypto.randomUUID();
|
|
124
|
+
const now = Date.now();
|
|
125
|
+
const expiresAt = new Date(now + ttlMs).toISOString();
|
|
126
|
+
const nowIso = new Date(now).toISOString();
|
|
127
|
+
getDb().query(`INSERT INTO oauth_refresh_locks (provider, owner, expiresAt, createdAt, updatedAt)
|
|
128
|
+
VALUES (?, ?, ?, ?, ?)
|
|
129
|
+
ON CONFLICT(provider) DO UPDATE SET
|
|
130
|
+
owner = excluded.owner,
|
|
131
|
+
expiresAt = excluded.expiresAt,
|
|
132
|
+
updatedAt = excluded.updatedAt
|
|
133
|
+
WHERE oauth_refresh_locks.expiresAt <= ?`).run(provider, owner, expiresAt, nowIso, nowIso, nowIso);
|
|
134
|
+
const row = getDb().query("SELECT owner FROM oauth_refresh_locks WHERE provider = ?").get(provider);
|
|
135
|
+
return row?.owner === owner ? owner : null;
|
|
136
|
+
}
|
|
137
|
+
function releaseOAuthRefreshLock(provider, owner) {
|
|
138
|
+
getDb().query("DELETE FROM oauth_refresh_locks WHERE provider = ? AND owner = ?").run(provider, owner);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// src/oauth/wrapper.ts
|
|
142
|
+
var STATE_TTL_MS = 10 * 60 * 1000;
|
|
143
|
+
var pendingStates = new Map;
|
|
144
|
+
function cleanupExpiredStates() {
|
|
145
|
+
const now = Date.now();
|
|
146
|
+
for (const [key, entry] of pendingStates) {
|
|
147
|
+
if (now - entry.createdAt > STATE_TTL_MS) {
|
|
148
|
+
pendingStates.delete(key);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
async function buildAuthorizationUrl(config) {
|
|
153
|
+
cleanupExpiredStates();
|
|
154
|
+
const state = generateRandomState();
|
|
155
|
+
const codeVerifier = generateRandomCodeVerifier();
|
|
156
|
+
const codeChallenge = await calculatePKCECodeChallenge(codeVerifier);
|
|
157
|
+
pendingStates.set(state, {
|
|
158
|
+
codeVerifier,
|
|
159
|
+
config,
|
|
160
|
+
createdAt: Date.now()
|
|
161
|
+
});
|
|
162
|
+
const url = new URL(config.authorizeUrl);
|
|
163
|
+
url.searchParams.set("client_id", config.clientId);
|
|
164
|
+
url.searchParams.set("redirect_uri", config.redirectUri);
|
|
165
|
+
url.searchParams.set("response_type", "code");
|
|
166
|
+
url.searchParams.set("scope", config.scopes.join(config.scopeSeparator ?? ","));
|
|
167
|
+
url.searchParams.set("state", state);
|
|
168
|
+
url.searchParams.set("code_challenge", codeChallenge);
|
|
169
|
+
url.searchParams.set("code_challenge_method", "S256");
|
|
170
|
+
if (config.extraParams) {
|
|
171
|
+
for (const [key, value] of Object.entries(config.extraParams)) {
|
|
172
|
+
url.searchParams.set(key, value);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return { url: url.toString(), state, codeVerifier };
|
|
176
|
+
}
|
|
177
|
+
function tokenRequestInit(config, params) {
|
|
178
|
+
const useBasic = config.tokenAuthStyle === "basic";
|
|
179
|
+
const bodyParams = useBasic ? params : { ...params, client_id: config.clientId, client_secret: config.clientSecret };
|
|
180
|
+
const headers = {};
|
|
181
|
+
if (useBasic) {
|
|
182
|
+
headers.Authorization = `Basic ${Buffer.from(`${config.clientId}:${config.clientSecret}`).toString("base64")}`;
|
|
183
|
+
}
|
|
184
|
+
if (config.tokenBodyFormat === "json") {
|
|
185
|
+
headers["Content-Type"] = "application/json";
|
|
186
|
+
return { headers, body: JSON.stringify(bodyParams) };
|
|
187
|
+
}
|
|
188
|
+
headers["Content-Type"] = "application/x-www-form-urlencoded";
|
|
189
|
+
return { headers, body: new URLSearchParams(bodyParams).toString() };
|
|
190
|
+
}
|
|
191
|
+
async function exchangeCode(config, code, state) {
|
|
192
|
+
const pending = pendingStates.get(state);
|
|
193
|
+
if (!pending) {
|
|
194
|
+
throw new Error("Invalid or expired OAuth state");
|
|
195
|
+
}
|
|
196
|
+
pendingStates.delete(state);
|
|
197
|
+
const { codeVerifier } = pending;
|
|
198
|
+
const response = await fetch(config.tokenUrl, {
|
|
199
|
+
method: "POST",
|
|
200
|
+
...tokenRequestInit(config, {
|
|
201
|
+
grant_type: "authorization_code",
|
|
202
|
+
redirect_uri: config.redirectUri,
|
|
203
|
+
code,
|
|
204
|
+
code_verifier: codeVerifier
|
|
205
|
+
})
|
|
206
|
+
});
|
|
207
|
+
if (!response.ok) {
|
|
208
|
+
const errorText = await response.text();
|
|
209
|
+
throw new Error(`Token exchange failed (${response.status}): ${errorText}`);
|
|
210
|
+
}
|
|
211
|
+
const data = await response.json();
|
|
212
|
+
const expiresAt = data.expires_in ? new Date(Date.now() + data.expires_in * 1000).toISOString() : new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString();
|
|
213
|
+
storeOAuthTokens(config.provider, {
|
|
214
|
+
accessToken: data.access_token,
|
|
215
|
+
refreshToken: data.refresh_token ?? null,
|
|
216
|
+
expiresAt,
|
|
217
|
+
scope: data.scope ?? null
|
|
218
|
+
});
|
|
219
|
+
return {
|
|
220
|
+
accessToken: data.access_token,
|
|
221
|
+
refreshToken: data.refresh_token,
|
|
222
|
+
expiresIn: data.expires_in,
|
|
223
|
+
scope: data.scope
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
async function refreshAccessToken(config, refreshToken) {
|
|
227
|
+
const response = await fetch(config.tokenUrl, {
|
|
228
|
+
method: "POST",
|
|
229
|
+
...tokenRequestInit(config, {
|
|
230
|
+
grant_type: "refresh_token",
|
|
231
|
+
refresh_token: refreshToken
|
|
232
|
+
})
|
|
233
|
+
});
|
|
234
|
+
if (!response.ok) {
|
|
235
|
+
const errorText = await response.text();
|
|
236
|
+
throw new Error(`Token refresh failed (${response.status}): ${errorText}`);
|
|
237
|
+
}
|
|
238
|
+
const data = await response.json();
|
|
239
|
+
if (typeof data.access_token !== "string" || data.access_token.length === 0) {
|
|
240
|
+
throw new Error(`Token refresh failed: ${config.provider} response missing access_token`);
|
|
241
|
+
}
|
|
242
|
+
if (config.requiresRefreshTokenRotation && (typeof data.refresh_token !== "string" || data.refresh_token.length === 0)) {
|
|
243
|
+
throw new Error(`Token refresh failed: ${config.provider} response did not include a rotated refresh_token`);
|
|
244
|
+
}
|
|
245
|
+
const expiresAt = data.expires_in ? new Date(Date.now() + data.expires_in * 1000).toISOString() : new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString();
|
|
246
|
+
const nextRefreshToken = data.refresh_token ?? refreshToken;
|
|
247
|
+
try {
|
|
248
|
+
updateOAuthTokensAfterRefresh(config.provider, refreshToken, {
|
|
249
|
+
accessToken: data.access_token,
|
|
250
|
+
refreshToken: nextRefreshToken,
|
|
251
|
+
expiresAt,
|
|
252
|
+
scope: data.scope ?? null
|
|
253
|
+
});
|
|
254
|
+
} catch (err) {
|
|
255
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
256
|
+
console.warn(`[OAuth] Refusing to use refreshed ${config.provider} access token because persistence failed: ${message}`);
|
|
257
|
+
throw err;
|
|
258
|
+
}
|
|
259
|
+
return {
|
|
260
|
+
accessToken: data.access_token,
|
|
261
|
+
refreshToken: data.refresh_token,
|
|
262
|
+
expiresIn: data.expires_in,
|
|
263
|
+
scope: data.scope
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// src/oauth/ensure-token.ts
|
|
268
|
+
function parseMetadata(metadataJson) {
|
|
269
|
+
try {
|
|
270
|
+
const parsed = JSON.parse(metadataJson || "{}");
|
|
271
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
272
|
+
} catch {
|
|
273
|
+
return {};
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
function stringRecord(value) {
|
|
277
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
278
|
+
return;
|
|
279
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, String(item)]));
|
|
280
|
+
}
|
|
281
|
+
function oauthAppRowToProviderConfig(app) {
|
|
282
|
+
const metadata = parseMetadata(app.metadata);
|
|
283
|
+
const extraParams = stringRecord(metadata.extraParams) ?? (typeof metadata.actor === "string" ? { actor: metadata.actor } : undefined);
|
|
284
|
+
return {
|
|
285
|
+
provider: app.provider,
|
|
286
|
+
clientId: app.clientId,
|
|
287
|
+
clientSecret: app.clientSecret,
|
|
288
|
+
authorizeUrl: app.authorizeUrl,
|
|
289
|
+
tokenUrl: app.tokenUrl,
|
|
290
|
+
redirectUri: app.redirectUri,
|
|
291
|
+
scopes: app.scopes.split(",").map((scope) => scope.trim()).filter(Boolean),
|
|
292
|
+
extraParams,
|
|
293
|
+
scopeSeparator: " ",
|
|
294
|
+
...metadata.tokenAuthStyle === "basic" ? { tokenAuthStyle: "basic" } : {},
|
|
295
|
+
...metadata.tokenBodyFormat === "json" ? { tokenBodyFormat: "json" } : {},
|
|
296
|
+
requiresRefreshTokenRotation: app.provider === "jira"
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
var refreshLocks = new Map;
|
|
300
|
+
var REFRESH_LOCK_TTL_MS = 2 * 60 * 1000;
|
|
301
|
+
var REFRESH_LOCK_WAIT_MS = 30 * 1000;
|
|
302
|
+
var REFRESH_LOCK_POLL_MS = 250;
|
|
303
|
+
function getOAuthConfig(provider) {
|
|
304
|
+
const app = getOAuthApp(provider);
|
|
305
|
+
return app ? oauthAppRowToProviderConfig(app) : null;
|
|
306
|
+
}
|
|
307
|
+
async function withProviderRefreshLock(provider, fn) {
|
|
308
|
+
const previous = refreshLocks.get(provider) ?? Promise.resolve();
|
|
309
|
+
let release;
|
|
310
|
+
const current = new Promise((resolve) => {
|
|
311
|
+
release = resolve;
|
|
312
|
+
});
|
|
313
|
+
const next = previous.catch(() => {
|
|
314
|
+
return;
|
|
315
|
+
}).then(() => current);
|
|
316
|
+
refreshLocks.set(provider, next);
|
|
317
|
+
await previous.catch(() => {
|
|
318
|
+
return;
|
|
319
|
+
});
|
|
320
|
+
try {
|
|
321
|
+
return await fn();
|
|
322
|
+
} finally {
|
|
323
|
+
release();
|
|
324
|
+
if (refreshLocks.get(provider) === next) {
|
|
325
|
+
refreshLocks.delete(provider);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
function sleep(ms) {
|
|
330
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
331
|
+
}
|
|
332
|
+
function tokenRowChanged(current, observed) {
|
|
333
|
+
if (!observed)
|
|
334
|
+
return current !== null;
|
|
335
|
+
if (!current)
|
|
336
|
+
return true;
|
|
337
|
+
return current.accessToken !== observed.accessToken || current.refreshToken !== observed.refreshToken || current.expiresAt !== observed.expiresAt;
|
|
338
|
+
}
|
|
339
|
+
async function ensureToken(provider, bufferMs) {
|
|
340
|
+
try {
|
|
341
|
+
await ensureTokenOrThrow(provider, bufferMs);
|
|
342
|
+
} catch (err) {
|
|
343
|
+
console.error(`[OAuth] Failed to refresh ${provider} token:`, err instanceof Error ? err.message : err);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
var FORCE_REFRESH_BUFFER_MS = 1000 * 60 * 60 * 24 * 365 * 100;
|
|
347
|
+
async function forceRefreshTokenOrThrow(provider) {
|
|
348
|
+
await ensureTokenOrThrow(provider, FORCE_REFRESH_BUFFER_MS);
|
|
349
|
+
}
|
|
350
|
+
async function ensureTokenOrThrow(provider, bufferMs) {
|
|
351
|
+
if (!isTokenExpiringSoon(provider, bufferMs))
|
|
352
|
+
return;
|
|
353
|
+
const observedTokens = getOAuthTokens(provider);
|
|
354
|
+
await withProviderRefreshLock(provider, async () => {
|
|
355
|
+
const waitStartedAt = Date.now();
|
|
356
|
+
while (isTokenExpiringSoon(provider, bufferMs)) {
|
|
357
|
+
const tokens = getOAuthTokens(provider);
|
|
358
|
+
if (tokenRowChanged(tokens, observedTokens))
|
|
359
|
+
return;
|
|
360
|
+
const config = getOAuthConfig(provider);
|
|
361
|
+
if (!config || !tokens?.refreshToken) {
|
|
362
|
+
console.warn(`[OAuth] ${provider} token expiring but cannot refresh (missing config or refresh token)`);
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
const lockOwner = acquireOAuthRefreshLock(provider, REFRESH_LOCK_TTL_MS);
|
|
366
|
+
if (!lockOwner) {
|
|
367
|
+
if (Date.now() - waitStartedAt > REFRESH_LOCK_WAIT_MS) {
|
|
368
|
+
throw new Error(`Timed out waiting for ${provider} OAuth token refresh lock`);
|
|
369
|
+
}
|
|
370
|
+
await sleep(REFRESH_LOCK_POLL_MS);
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
try {
|
|
374
|
+
const lockedTokens = getOAuthTokens(provider);
|
|
375
|
+
if (!isTokenExpiringSoon(provider, bufferMs) || tokenRowChanged(lockedTokens, observedTokens)) {
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
const lockedConfig = getOAuthConfig(provider);
|
|
379
|
+
if (!lockedConfig || !lockedTokens?.refreshToken) {
|
|
380
|
+
console.warn(`[OAuth] ${provider} token expiring but cannot refresh (missing config or refresh token)`);
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
await refreshAccessToken(lockedConfig, lockedTokens.refreshToken);
|
|
384
|
+
console.log(`[OAuth] ${provider} token refreshed successfully`);
|
|
385
|
+
return;
|
|
386
|
+
} finally {
|
|
387
|
+
releaseOAuthRefreshLock(provider, lockOwner);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
export { getOAuthApp, upsertOAuthApp, getOAuthTokens, listOAuthTokenSweepRows, deleteOAuthTokens, isTokenExpiringSoon, acquireOAuthRefreshLock, releaseOAuthRefreshLock, buildAuthorizationUrl, exchangeCode, oauthAppRowToProviderConfig, ensureToken, forceRefreshTokenOrThrow, ensureTokenOrThrow };
|