@desplega.ai/agent-swarm 1.112.0 → 1.114.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-eckb4g8r.js → actions-bm8vdxys.js} +7 -8
- package/dist/{app-b05sgm02.js → app-bhwdxmvp.js} +5 -5
- package/dist/{assistant-7255sbyw.js → assistant-15mp2apr.js} +18 -17
- package/dist/{boot-reembed-y1bt3ttn.js → boot-reembed-awr2179r.js} +6 -6
- package/dist/{boot-reembed-yv7awg4y.js → boot-reembed-b9sbk87z.js} +5 -5
- package/dist/{boot-scrub-logs-ddyaeqar.js → boot-scrub-logs-c1zsaw9m.js} +4 -4
- package/dist/{claude-adapter-mma9a1yr.js → claude-adapter-2k45y7p7.js} +2 -2
- package/dist/{claude-managed-adapter-pv2nbqy4.js → claude-managed-adapter-kysmkz89.js} +2 -2
- package/dist/cli-0kgcv878.js +344 -0
- package/dist/cli-0pygq9r1.js +429 -0
- package/dist/{cli-7xkwgkrs.js → cli-113wmegp.js} +90 -3
- package/dist/{cli-7ga43spr.js → cli-1yjrbg1f.js} +1 -1
- package/dist/{cli-56s4ehzw.js → cli-2t4e4ayk.js} +1 -1
- package/dist/{cli-0c3n0eba.js → cli-312v3c41.js} +1 -1
- package/dist/{cli-ceewqajw.js → cli-46b6kc7f.js} +5 -5
- package/dist/cli-49j095n9.js +393 -0
- package/dist/{cli-h9jx5mag.js → cli-5afkmcfj.js} +10 -7
- package/dist/{cli-76d1vtvq.js → cli-5ktc9fd4.js} +1 -1
- package/dist/cli-7rgh0w0k.js +262 -0
- package/dist/{cli-9cy6nc5m.js → cli-8fjwym53.js} +1 -1
- package/dist/{cli-tg0f2wmz.js → cli-a6tzw6vq.js} +175 -28
- package/dist/{cli-q21d49ac.js → cli-bw80ck94.js} +1 -1
- package/dist/{cli-709t753c.js → cli-byjcad11.js} +2 -2
- package/dist/{cli-15598a4k.js → cli-e2xbxmhd.js} +1808 -302
- package/dist/{cli-6wscj9z0.js → cli-e7306swv.js} +14 -14
- package/dist/{cli-8f0dwca2.js → cli-f5pzfvwb.js} +6 -5
- package/dist/{cli-s9pj3c7z.js → cli-f9n2jg31.js} +2 -2
- package/dist/{cli-dcfyyh3t.js → cli-ftyf58xj.js} +2 -2
- package/dist/{cli-hm27prrc.js → cli-hq39e630.js} +5 -5
- package/dist/{cli-a5e8n5hg.js → cli-hvg05qwm.js} +1 -1
- package/dist/cli-jkhwmmgg.js +136 -0
- package/dist/{cli-951w7c79.js → cli-jzwtn8rm.js} +136 -21
- package/dist/{cli-rp8pca98.js → cli-k6h2zkw3.js} +1 -1
- package/dist/{cli-3r5y6ayj.js → cli-pnfyyp6h.js} +1240 -335
- package/dist/{cli-94vzr3nq.js → cli-q1z5d5ss.js} +13 -7
- package/dist/{cli-pt7c7qxz.js → cli-q4e2x252.js} +3 -3
- package/dist/{cli-tch0ypp6.js → cli-r3mtrrzp.js} +16 -16
- package/dist/{cli-ywez6yps.js → cli-sq7f72d6.js} +1 -1
- package/dist/{cli-r1btc895.js → cli-w3nq88yc.js} +2 -2
- package/dist/{cli-30d2ct3k.js → cli-w4wk3kx6.js} +12 -7
- package/dist/{cli-anrj584m.js → cli-xz9aq0rf.js} +1 -1
- package/dist/{cli-tp7e60s3.js → cli-zmmp2r79.js} +22 -9
- package/dist/cli.js +35 -17
- package/dist/{codex-adapter-5cmy858w.js → codex-adapter-4z8m9mb5.js} +9 -9
- package/dist/{codex-session-runner-46q8mrba.js → codex-session-runner-rbct46dg.js} +9 -9
- package/dist/{commands-14g7taf4.js → commands-zk4d6esf.js} +4 -4
- package/dist/{db-3tge4jrn.js → db-stn16jp2.js} +4 -4
- package/dist/{handlers-sy64egg6.js → handlers-gshprvpf.js} +16 -30
- package/dist/{hook-rnkzw8vp.js → hook-amzx9tap.js} +7 -7
- package/dist/{http-xx9xptcs.js → http-6g0hfzax.js} +1977 -786
- package/dist/{index-wz1ea15p.js → index-2dgfj9av.js} +3 -3
- package/dist/{index-ha8e9xn2.js → index-df8fmphk.js} +55 -17
- package/dist/{index-zfx5p959.js → index-f03rnnrs.js} +13 -10
- package/dist/{index-3wr6v2mm.js → index-jbm7qcqk.js} +12 -9
- package/dist/{index-ykvs43z4.js → index-t2snazez.js} +4 -4
- package/dist/{index-brma88d4.js → index-ty42rxw9.js} +14 -11
- package/dist/{keepalive-rbz05zad.js → keepalive-pm6g2wpw.js} +7 -6
- package/dist/{lead-s790v7r0.js → lead-h79fzpzw.js} +33 -29
- package/dist/{maintenance-420rmadb.js → maintenance-433zndy1.js} +6 -6
- package/dist/{mistral-conversations-26yrnbf1.js → mistral-conversations-7gqnz2wt.js} +7 -7
- package/dist/oauth-refresh-sweep-mjsbmsk2.js +114 -0
- package/dist/{onboard-jgqamqnw.js → onboard-vdr9hc3f.js} +2 -2
- package/dist/{otel-zjd9zaxs.js → otel-g299k41b.js} +2 -2
- package/dist/{otel-impl-ch3n3783.js → otel-impl-yswd0fty.js} +1 -1
- package/dist/{pi-mono-adapter-kxjxpfgy.js → pi-mono-adapter-vbrvg1p8.js} +16 -108
- package/dist/{pricing-refresh-rmtrby6h.js → pricing-refresh-2kk2ap9b.js} +6 -6
- package/dist/rbac-roles-2wgh9b5t.js +276 -0
- package/dist/rbac-roles-xdc5208b.js +28 -0
- package/dist/{seed-pricing-1szjzfj1.js → seed-pricing-m9h69d2t.js} +5 -5
- package/dist/{setup-8jfzkyzv.js → setup-eynkvafa.js} +2 -2
- package/dist/{worker-40vq0pm8.js → worker-136q8dc2.js} +33 -29
- package/openapi.json +1634 -20
- package/package.json +6 -5
- 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 +104 -11
- package/src/be/identity.ts +79 -0
- package/src/be/mcp-proxy.ts +132 -0
- package/src/be/memory/providers/sqlite-store.ts +5 -1
- package/src/be/migrations/108_rbac_permission_audit.sql +26 -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/oauth-credential-bindings.ts +35 -0
- package/src/be/oauth-refresh-sweep.ts +159 -0
- package/src/be/rbac-audit.ts +246 -0
- package/src/be/rbac-roles.ts +417 -0
- package/src/be/script-connections.ts +456 -37
- package/src/be/script-credential-broker.ts +29 -3
- package/src/be/scripts/typecheck.ts +40 -7
- 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/http/all-routes.ts +61 -0
- package/src/http/approval-requests.ts +12 -0
- package/src/http/config.ts +98 -9
- package/src/http/core.ts +30 -3
- package/src/http/favorites.ts +5 -0
- package/src/http/fs.ts +27 -7
- package/src/http/index.ts +50 -0
- package/src/http/kv.ts +21 -4
- package/src/http/mcp-oauth.ts +2 -0
- package/src/http/mcp-servers.ts +6 -3
- package/src/http/oauth-generic.ts +105 -0
- package/src/http/poll.ts +13 -1
- package/src/http/route-def.ts +9 -0
- package/src/http/schedules.ts +56 -22
- package/src/http/script-connection-proxy.ts +116 -0
- package/src/http/script-connections.ts +1657 -0
- package/src/http/scripts.ts +54 -7
- package/src/http/tasks.ts +16 -14
- package/src/http/workflows.ts +13 -2
- 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/base-prompt.ts +7 -6
- package/src/prompts/session-templates.ts +38 -11
- package/src/providers/pi-mono-mcp-client.ts +2 -133
- package/src/rbac/admission.ts +44 -0
- package/src/rbac/can.ts +44 -0
- package/src/rbac/index.ts +15 -0
- package/src/rbac/legacy-policy.ts +188 -0
- package/src/rbac/permissions.ts +194 -0
- package/src/rbac/types.ts +45 -0
- package/src/scheduler/scheduler.ts +9 -1
- package/src/scripts-runtime/api-client.ts +70 -14
- 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 +2 -0
- package/src/scripts-runtime/swarm-sdk.ts +81 -0
- package/src/scripts-runtime/types/stdlib.d.ts +24 -2
- package/src/scripts-runtime/types/swarm-sdk.d.ts +26 -2
- package/src/server.ts +14 -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/thread-buffer.ts +6 -3
- package/src/stdio.ts +43 -0
- 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/base-prompt.test.ts +7 -4
- 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/harness-provider-resolution.test.ts +5 -0
- package/src/tests/http-api-integration.test.ts +8 -1
- 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/oauth-credential-bindings.test.ts +490 -0
- package/src/tests/oauth-refresh-sweep.test.ts +160 -0
- package/src/tests/prompt-template-remaining.test.ts +2 -2
- package/src/tests/prompt-template-session.test.ts +21 -8
- package/src/tests/rbac-admission-e2e.test.ts +241 -0
- package/src/tests/rbac-admission.test.ts +433 -0
- package/src/tests/rbac-audit.test.ts +345 -0
- package/src/tests/rbac-charact-http.test.ts +272 -0
- package/src/tests/rbac-charact-misc-tools.test.ts +428 -0
- package/src/tests/rbac-charact-skills.test.ts +492 -0
- package/src/tests/rbac-charact-slack.test.ts +283 -0
- package/src/tests/rbac-e2e-helpers.ts +308 -0
- package/src/tests/rbac-engine.test.ts +474 -0
- package/src/tests/rbac-lifecycle-e2e.test.ts +262 -0
- package/src/tests/rbac-roles.test.ts +326 -0
- package/src/tests/rbac-wire-e2e.test.ts +574 -0
- package/src/tests/schedule-http-triage-tools.test.ts +121 -0
- package/src/tests/schedule-target-type.test.ts +50 -0
- package/src/tests/scheduled-tasks.test.ts +74 -0
- 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 +727 -5
- package/src/tests/scripts-external-api.test.ts +49 -1
- package/src/tests/scripts-http.test.ts +56 -7
- package/src/tests/scripts-runtime-secret-egress.test.ts +1 -0
- package/src/tests/scripts-runtime.test.ts +104 -0
- package/src/tests/slack-identity-resolution.test.ts +37 -3
- package/src/tests/slack-thread-buffer.test.ts +28 -0
- package/src/tests/swarm-config-reserved-keys.test.ts +17 -0
- package/src/tests/tool-annotations.test.ts +2 -0
- package/src/tests/update-schedule-mcp-tool.test.ts +57 -0
- package/src/tests/user-token-rest-auth.test.ts +30 -1
- package/src/tests/workflow-http-v2.test.ts +80 -0
- package/src/tests/workflow-swarm-script.test.ts +44 -0
- package/src/tests/workflow-triggers-v2.test.ts +300 -1
- package/src/tools/cancel-task.ts +13 -3
- package/src/tools/context-diff.ts +12 -1
- package/src/tools/context-history.ts +12 -1
- package/src/tools/credential-bindings/tool.ts +289 -19
- package/src/tools/delete-channel.ts +12 -1
- package/src/tools/delete-page.ts +145 -0
- package/src/tools/get-task-details.ts +1 -1
- package/src/tools/inject-learning.ts +12 -1
- package/src/tools/kv/kv-delete.ts +3 -18
- package/src/tools/kv/kv-incr.ts +3 -18
- package/src/tools/kv/kv-set.ts +3 -20
- package/src/tools/kv/kv-write-auth.ts +40 -0
- package/src/tools/manage-user.ts +12 -1
- package/src/tools/mcp-servers/mcp-server-create.ts +12 -1
- package/src/tools/mcp-servers/mcp-server-delete.ts +19 -5
- package/src/tools/mcp-servers/mcp-server-install.ts +12 -1
- package/src/tools/mcp-servers/mcp-server-uninstall.ts +12 -1
- package/src/tools/mcp-servers/mcp-server-update.ts +12 -1
- package/src/tools/memory-delete.ts +12 -4
- package/src/tools/register-kapso-number.ts +23 -2
- package/src/tools/request-human-input.ts +2 -0
- package/src/tools/resolve-user.ts +77 -27
- package/src/tools/schedules/create-schedule.ts +4 -3
- package/src/tools/schedules/index.ts +1 -0
- package/src/tools/schedules/list-schedules.ts +36 -2
- package/src/tools/schedules/patch-schedule.ts +405 -0
- package/src/tools/schedules/update-schedule.ts +2 -2
- package/src/tools/script-connections/tool.ts +287 -32
- package/src/tools/skills/skill-create.ts +12 -1
- package/src/tools/skills/skill-delete.ts +12 -1
- package/src/tools/skills/skill-install-remote.ts +12 -1
- package/src/tools/skills/skill-install.ts +12 -1
- package/src/tools/skills/skill-uninstall.ts +12 -1
- package/src/tools/skills/skill-update.ts +25 -2
- package/src/tools/slack-delete.ts +8 -1
- package/src/tools/slack-post.ts +8 -1
- package/src/tools/slack-read.ts +8 -1
- package/src/tools/slack-start-thread.ts +8 -1
- package/src/tools/slack-update.ts +8 -1
- package/src/tools/slack-upload-file.ts +8 -1
- package/src/tools/swarm-config/delete-config.ts +28 -1
- package/src/tools/swarm-config/get-config.ts +32 -5
- package/src/tools/swarm-config/list-config.ts +31 -5
- package/src/tools/swarm-config/set-config.ts +38 -1
- package/src/tools/task-action.ts +1 -1
- package/src/tools/task-tool-ctx.ts +19 -3
- package/src/tools/templates.ts +5 -1
- package/src/tools/tool-config.ts +4 -2
- package/src/tools/update-profile.ts +8 -1
- package/src/tools/workflows/create-workflow.ts +8 -1
- package/src/tools/workflows/list-workflows.ts +14 -2
- package/src/tools/workflows/update-workflow.ts +11 -2
- package/src/types.ts +63 -6
- 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/SKILL.md +4 -9
- package/templates/skills/swarm-scripts/content.md +45 -9
- package/dist/cli-52tbq17n.js +0 -771
- package/dist/cli-9p0qhead.js +0 -43
- 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-2fc97xm5.js → cli-ggy8c7tk.js} +3 -3
- 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
|
@@ -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-jzwtn8rm.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 };
|
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createAdditiveBuffer,
|
|
3
3
|
extractSlackMessageText
|
|
4
|
-
} from "./cli-
|
|
4
|
+
} from "./cli-5ktc9fd4.js";
|
|
5
5
|
import {
|
|
6
6
|
getSlackApp,
|
|
7
7
|
registerTreeMessage
|
|
8
|
-
} from "./cli-
|
|
8
|
+
} from "./cli-hq39e630.js";
|
|
9
|
+
import {
|
|
10
|
+
rewriteSlackMentions
|
|
11
|
+
} from "./cli-w4wk3kx6.js";
|
|
9
12
|
import {
|
|
10
13
|
buildBufferFlushBlocks
|
|
11
14
|
} from "./cli-6xd0bvf8.js";
|
|
12
15
|
import {
|
|
13
16
|
createTaskWithSiblingAwareness,
|
|
14
17
|
slackContextKey
|
|
15
|
-
} from "./cli-
|
|
18
|
+
} from "./cli-312v3c41.js";
|
|
16
19
|
import {
|
|
17
20
|
getLatestActiveTaskInThread,
|
|
18
21
|
getLeadAgent,
|
|
@@ -20,7 +23,7 @@ import {
|
|
|
20
23
|
init_db,
|
|
21
24
|
init_registry,
|
|
22
25
|
registerTemplate
|
|
23
|
-
} from "./cli-
|
|
26
|
+
} from "./cli-jzwtn8rm.js";
|
|
24
27
|
|
|
25
28
|
// src/slack/event-dedup.ts
|
|
26
29
|
var DEFAULT_TTL_MS = 300000;
|
|
@@ -164,7 +167,7 @@ async function getThreadContextForBuffer(channelId, threadTs) {
|
|
|
164
167
|
return `<@${m.user}>: ${text}`;
|
|
165
168
|
}).join(`
|
|
166
169
|
`);
|
|
167
|
-
return formatted;
|
|
170
|
+
return rewriteSlackMentions(formatted);
|
|
168
171
|
} catch (error) {
|
|
169
172
|
console.error("[Slack] Failed to fetch thread context for buffer:", error);
|
|
170
173
|
return "";
|
|
@@ -181,9 +184,9 @@ async function slackFlush(items, key, immediate) {
|
|
|
181
184
|
const { channelId, threadTs } = split;
|
|
182
185
|
const originalRequesterId = items[0].userId;
|
|
183
186
|
console.log(`[Slack] Flushing buffer: ${key} (${items.length} messages, immediate=${immediate})`);
|
|
184
|
-
const combinedText = items.map((m) => m.text).join(`
|
|
187
|
+
const combinedText = rewriteSlackMentions(items.map((m) => m.text).join(`
|
|
185
188
|
---
|
|
186
|
-
`);
|
|
189
|
+
`));
|
|
187
190
|
const description = `[Thread follow-up — ${items.length} message(s) buffered]
|
|
188
191
|
|
|
189
192
|
${combinedText}`;
|