@desplega.ai/agent-swarm 1.161.0 → 1.162.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 +3 -2
- package/dist/{acp-adapter-xx489vvb.js → acp-adapter-74m73ae9.js} +1 -1
- package/dist/{actions-ykjkn0m9.js → actions-4sr14gnt.js} +34 -31
- package/dist/{anthropic-messages-5qx80j9k.js → anthropic-messages-8xxrzky9.js} +22 -26
- package/dist/{app-0hdqa4k1.js → app-pt68fbwp.js} +13 -11
- package/dist/{artifact-3v5t9jtg.js → artifact-fey7dvj9.js} +3 -2
- package/dist/{assistant-am2g5g8a.js → assistant-cv3jytks.js} +37 -34
- package/dist/{azure-openai-responses-fszbkp0e.js → azure-openai-responses-f5cqsyj8.js} +15 -10
- package/dist/{boot-reembed-xy9t7avg.js → boot-reembed-4ht4gqth.js} +13 -11
- package/dist/{boot-reembed-gt5gjkt4.js → boot-reembed-nxpcvpj9.js} +12 -10
- package/dist/{boot-scrub-logs-9x0985y5.js → boot-scrub-logs-k93kyw8k.js} +9 -7
- package/dist/{boot-seeds-trv0d4vg.js → boot-seeds-1skcrs0e.js} +13 -10
- package/dist/{claude-adapter-jb6e1e54.js → claude-adapter-r5g0tzge.js} +23 -20
- package/dist/{claude-managed-setup-d1q9hv2b.js → claude-managed-setup-pkyrgshb.js} +3 -2
- package/dist/{claude-sdk-session-zga700s2.js → claude-sdk-session-jxyxnrwd.js} +5565 -5512
- package/dist/{cli-we0q7t0e.js → cli-2zy95vhm.js} +137 -64
- package/dist/{cli-35pj2yva.js → cli-4cwxn7gp.js} +38 -11
- package/dist/{cli-ad4qxm6j.js → cli-4dcpaefp.js} +2 -2
- package/dist/{cli-82c1pacn.js → cli-4x0csdjq.js} +71 -37
- package/dist/{cli-sqm3r5ps.js → cli-5bcd7sfy.js} +14 -18
- package/dist/{cli-d56tgd45.js → cli-5k6k8ta3.js} +2 -2
- package/dist/{cli-1er0rhz8.js → cli-5xhpb3kv.js} +15 -15
- package/dist/{cli-yrg7c73k.js → cli-7af8yhav.js} +10 -183
- package/dist/{cli-r6fsxc76.js → cli-7tre70zp.js} +513 -961
- package/dist/{cli-bh7jed3k.js → cli-8ev9xqnx.js} +34 -18
- package/dist/cli-bsky8g7w.js +449 -0
- package/dist/{cli-hn04t9db.js → cli-cebmgwsc.js} +14 -10
- package/dist/{cli-cfkrztza.js → cli-cvatsam7.js} +2 -2
- package/dist/{cli-2658f6at.js → cli-cvk6yzw5.js} +6 -8
- package/dist/{cli-ptr8h7kp.js → cli-eg0b3p0k.js} +5 -218
- package/dist/{cli-zb6xbk61.js → cli-g739zqzt.js} +4 -4
- package/dist/{cli-dv6cdjs2.js → cli-gd4favx9.js} +28 -7
- package/dist/{cli-5p3x4zna.js → cli-gpqpmns7.js} +2 -2
- package/dist/{cli-5nbmpy8w.js → cli-hb5c2x5y.js} +1 -1
- package/dist/{cli-jpf7e1zq.js → cli-hmsw1gpy.js} +3 -3
- package/dist/{cli-1cz0c18q.js → cli-jf7wmv1r.js} +2521 -733
- package/dist/{cli-qwsnmjdd.js → cli-jxs2ygaw.js} +168 -61
- package/dist/cli-kdkwd3yr.js +615 -0
- package/dist/{cli-hgmnf38m.js → cli-kh04eqwe.js} +1 -1
- package/dist/{cli-0383bp7f.js → cli-kp42yek0.js} +425 -90
- package/dist/{cli-ybgv4kw2.js → cli-mbssg1xv.js} +9 -2
- package/dist/{cli-jsp14b6c.js → cli-mzfth0bp.js} +38 -1
- package/dist/{cli-8g6mc14k.js → cli-nq7yqw7b.js} +16 -5
- package/dist/{cli-ee8bc46j.js → cli-nt8hecg9.js} +33 -32
- package/dist/{cli-9s68w752.js → cli-p0r8sxj5.js} +1 -1
- package/dist/{cli-58e8yr0d.js → cli-pkd6s2wy.js} +4 -2
- package/dist/{cli-17xpf7cm.js → cli-q24re23n.js} +6 -4
- package/dist/cli-qjjvzwzd.js +222 -0
- package/dist/cli-qrgt5bqf.js +189 -0
- package/dist/{cli-et8m9xsv.js → cli-rfjw9jw2.js} +22 -10
- package/dist/{cli-798vkbks.js → cli-rj0gqwwp.js} +2 -2
- package/dist/{cli-121ph74b.js → cli-rnyzn44p.js} +1 -1
- package/dist/{cli-vnnz5ncg.js → cli-s3hwpt9v.js} +95 -16
- package/dist/{cli-7119ja3w.js → cli-sptrj5f5.js} +2 -2
- package/dist/{cli-a8c82p2t.js → cli-sqvfa090.js} +13 -6
- package/dist/{cli-phfg8vy8.js → cli-swfmp1fx.js} +26 -26
- package/dist/{cli-nsdxtcbg.js → cli-tcen9p5w.js} +8 -5
- package/dist/{cli-9m03pzga.js → cli-vc9bp061.js} +5 -5
- package/dist/{cli-rtx9dygp.js → cli-vxe6qa4h.js} +1 -1
- package/dist/{cli-ecfxwnyb.js → cli-vy3ed88x.js} +1 -1
- package/dist/{cli-0c7ata9h.js → cli-vyqs9ya8.js} +6 -6
- package/dist/cli-wbvnn6tk.js +861 -0
- package/dist/{cli-kgd3p0bm.js → cli-wjgs5wxe.js} +6 -6
- package/dist/cli-wxkkd0e0.js +33 -0
- package/dist/{cli-akfj32vj.js → cli-xqpk3rp7.js} +5 -4
- package/dist/cli-zp38xvtm.js +9487 -0
- package/dist/{cli-3zc55ctx.js → cli-zvhd3f64.js} +2 -2
- package/dist/{cli-ze1d3wzv.js → cli-zxqx25nz.js} +5 -5
- package/dist/cli.js +32 -25
- package/dist/{cloudflare-workers-ai-system-one-k6fbxw9d.js → cloudflare-workers-ai-system-one-k8ddqjkq.js} +8 -6
- package/dist/{codex-adapter-04v97vep.js → codex-adapter-esvmnhn7.js} +13 -13
- package/dist/{codex-hook-20me6byw.js → codex-hook-m73jmqgx.js} +19 -3
- package/dist/{codex-session-runner-ax69mqq3.js → codex-session-runner-ban9excr.js} +13 -13
- package/dist/{commands-18gy08n1.js → commands-75413p2n.js} +9 -7
- package/dist/{db-d2pvjpyt.js → db-7f5pecwg.js} +11 -7
- package/dist/{dispatcher-8wbwyzjg.js → dispatcher-fh9at8nz.js} +33 -30
- package/dist/{dsh-adapter-ysqk9snv.js → dsh-adapter-wpze65d4.js} +3 -3
- package/dist/{e2b-esmt1r16.js → e2b-gjgdjp91.js} +10 -9
- package/dist/{pi-logo-animation-e9a0t68z.js → easter-egg-3d-3w5c2n4c.js} +122 -68
- package/dist/{execute-cmagrbrz.js → execute-f50wg9nh.js} +29 -6
- package/dist/{google-generative-ai-k6n5nbpp.js → google-generative-ai-jxxpnegy.js} +2 -2
- package/dist/{google-vertex-44xxtcvp.js → google-vertex-x7q1angm.js} +5 -5
- package/dist/{handlers-6b69m5kp.js → handlers-w13787bb.js} +36 -33
- package/dist/{hook-srnkt0hb.js → hook-46g1jrvx.js} +28 -15
- package/dist/{hook-k3mydf2n.js → hook-4tpts2ry.js} +20 -17
- package/dist/{http-hrf80ca1.js → http-19pzbfrt.js} +964 -176
- package/dist/{index-9rxdvj6w.js → index-8fd4dm4h.js} +34 -31
- package/dist/{index-1asbaj62.js → index-dqmp15bq.js} +35 -32
- package/dist/{index-45evq4ee.js → index-qb4q1y3e.js} +33 -30
- package/dist/{index-k4nj18qs.js → index-s75wc4wd.js} +12 -10
- package/dist/{index-xmevk2b9.js → index-scptvdxg.js} +27 -25
- package/dist/{keepalive-sfdz6ksp.js → keepalive-xa5ts9cd.js} +11 -9
- package/dist/{lead-ej91e9hw.js → lead-qp4xkvjq.js} +45 -41
- package/dist/{maintenance-80dnd7ke.js → maintenance-rm5885hm.js} +13 -11
- package/dist/{mistral-conversations-pc3ntnxt.js → mistral-conversations-rw7fbt22.js} +4 -4
- package/dist/{node-a5e00qhp.js → node-8zkdq42h.js} +834 -833
- package/dist/{oauth-refresh-sweep-m1etk2mx.js → oauth-refresh-sweep-1csdfebf.js} +10 -8
- package/dist/{onboard-0q8sdfqc.js → onboard-r6b78cs4.js} +10 -10
- package/dist/{openai-codex-responses-n7n683p2.js → openai-codex-responses-efvrgrq3.js} +4 -4
- package/dist/{openai-completions-jpaj5g2b.js → openai-completions-bbbrrz4z.js} +12 -8
- package/dist/{openai-responses-9a3nme9j.js → openai-responses-9jjyrcm1.js} +15 -10
- package/dist/{opencode-adapter-4wcgmvgf.js → opencode-adapter-kb3g6pkf.js} +2 -2
- package/dist/{otel-impl-hnqm9mxq.js → otel-impl-0y9knpky.js} +5 -5
- package/dist/{pi-mono-adapter-d6sqm9wr.js → pi-mono-adapter-z6s1bme2.js} +100 -19
- package/dist/{pricing-refresh-y60z64pm.js → pricing-refresh-mth26tgp.js} +11 -9
- package/dist/{rbac-roles-8pa0754b.js → rbac-roles-kwntb23q.js} +13 -9
- package/dist/rbac-roles-vpk4ebm8.js +45 -0
- package/dist/{registry-v5wytg0s.js → registry-km6t1jd2.js} +1 -1
- package/dist/{render-v2-418s1vj7.js → render-v2-hcm7h00y.js} +13 -11
- package/dist/{runtime-3pknc168.js → runtime-vfjsfsc5.js} +4 -4
- package/dist/{setup-yghdkj4x.js → setup-hbyzd65h.js} +2 -2
- package/dist/{virtual-modules-jcceevt5.js → virtual-modules-9bv7ry91.js} +9 -8
- package/dist/{work-objects-j1sz20v9.js → work-objects-m09sxnhe.js} +36 -33
- package/dist/{worker-d4gcvkjt.js → worker-0h35rvk4.js} +45 -41
- package/openapi.json +491 -12
- package/package.json +13 -6
- package/src/be/api-drain.ts +184 -0
- package/src/be/db/tasks/read.ts +31 -6
- package/src/be/db/tasks/write.ts +5 -4
- package/src/be/db.ts +42 -18
- package/src/be/memory/link-resolver.ts +29 -3
- package/src/be/migrations/192_favorites_agent_fs_path.sql +36 -0
- package/src/be/onboarding.ts +7 -1
- package/src/be/seed-prompt-templates.ts +1 -0
- package/src/be/swarm-config-guard.ts +35 -16
- package/src/be/task-citations.ts +11 -5
- package/src/be/users.ts +3 -0
- package/src/cli.tsx +3 -2
- package/src/comb/agent-fs.ts +39 -0
- package/src/comb/config.ts +37 -0
- package/src/comb/markers.ts +37 -0
- package/src/comb/review-batch.ts +486 -0
- package/src/comb/templates.ts +88 -0
- package/src/commands/context-preamble.ts +4 -1
- package/src/commands/hook.ts +2 -1
- package/src/commands/runner.ts +262 -68
- package/src/commands/templates.ts +10 -0
- package/src/extensions/contract-types.generated.ts +1 -1
- package/src/fs/agent-fs-provider.ts +106 -2
- package/src/heartbeat/heartbeat.ts +4 -0
- package/src/hooks/codex-hook.ts +25 -1
- package/src/hooks/hook.ts +13 -0
- package/src/hooks/pr-body-guard.ts +983 -0
- package/src/http/all-routes.ts +1 -0
- package/src/http/assets.ts +5 -17
- package/src/http/comb.ts +131 -0
- package/src/http/core.ts +2 -1
- package/src/http/fs.ts +9 -17
- package/src/http/index.ts +23 -9
- package/src/http/onboarding.ts +21 -2
- package/src/http/poll.ts +30 -3
- package/src/http/realtime.ts +62 -0
- package/src/http/request-principal.ts +22 -0
- package/src/http/status.ts +34 -2
- package/src/kv-reserved-namespaces.ts +10 -5
- package/src/providers/dsh-adapter.ts +1 -1
- package/src/providers/pi-mono-adapter.ts +123 -0
- package/src/rbac/legacy-policy.ts +1 -0
- package/src/rbac/permissions.ts +4 -0
- package/src/realtime/auth.ts +63 -1
- package/src/realtime/tickets.ts +52 -0
- package/src/realtime/transport.ts +51 -22
- package/src/scripts-runtime/types/swarm-extension.d.ts +3 -1
- package/src/telemetry-context.ts +175 -0
- package/src/telemetry-contract/EVENTS.md +81 -0
- package/src/telemetry-contract/SOURCE.json +9 -0
- package/src/telemetry-contract/catalog.json +1335 -0
- package/src/telemetry-contract/index.ts +60 -0
- package/src/telemetry-contract/types.gen.ts +1971 -0
- package/src/telemetry-identity.ts +129 -0
- package/src/telemetry-snapshot.ts +211 -0
- package/src/telemetry-trigger.ts +125 -0
- package/src/telemetry-types.typetest.ts +40 -0
- package/src/telemetry.ts +271 -67
- package/src/tests/api-drain-e2e.test.ts +249 -0
- package/src/tests/api-drain-worker-e2e.test.ts +216 -0
- package/src/tests/api-drain.test.ts +351 -0
- package/src/tests/comb-config-guard.test.ts +38 -0
- package/src/tests/comb-links.test.ts +162 -0
- package/src/tests/comb-markers.test.ts +68 -0
- package/src/tests/comb-review-batch.test.ts +770 -0
- package/src/tests/comb-status.test.ts +152 -0
- package/src/tests/db-task-read-contract.test.ts +2 -0
- package/src/tests/db-task-write-contract.test.ts +1 -0
- package/src/tests/docs-index-footprint.test.js +3 -3
- package/src/tests/dsh-adapter.test.ts +1 -1
- package/src/tests/favorites-agent-fs-path.test.ts +226 -0
- package/src/tests/memory-link-resolver.test.ts +34 -0
- package/src/tests/onboarding.test.ts +108 -0
- package/src/tests/pi-mono-adapter.test.ts +265 -2
- package/src/tests/pr-body-leaks.test.ts +789 -0
- package/src/tests/preload.ts +8 -0
- package/src/tests/rbac-engine.test.ts +19 -0
- package/src/tests/realtime-room-auth.test.ts +104 -1
- package/src/tests/realtime-tickets.test.ts +74 -0
- package/src/tests/realtime-transport.test.ts +131 -4
- package/src/tests/runner-api-drain.test.ts +301 -0
- package/src/tests/runner-fallback-output.test.ts +61 -0
- package/src/tests/runner-fenced-json-output.test.ts +178 -0
- package/src/tests/script-connections.test.ts +2 -0
- package/src/tests/send-task-requested-by.test.ts +133 -15
- package/src/tests/task-lifecycle-telemetry.test.ts +8 -3
- package/src/tests/telemetry-context.test.ts +314 -0
- package/src/tests/telemetry-init.test.ts +51 -19
- package/src/tests/telemetry-snapshot.test.ts +229 -0
- package/src/tests/telemetry-trigger.test.ts +200 -0
- package/src/tests/template-registry-helpers.ts +1 -0
- package/src/tests/workflow-agent-task-retry.test.ts +363 -0
- package/src/tests/workflow-definition-telemetry.test.ts +4 -4
- package/src/tools/poll-task.ts +20 -1
- package/src/tools/send-task.ts +9 -1
- package/src/tools/task-action.ts +11 -0
- package/src/types.ts +2 -1
- package/src/utils/api-drain.ts +24 -0
- package/src/utils/constants.ts +57 -16
- package/src/utils/pr-body-leaks.ts +172 -0
- package/src/utils/task-attachment-links.ts +14 -8
- package/src/workflows/recovery.ts +26 -1
- package/src/workflows/resume.ts +31 -4
- package/src/workflows/task-step-routing.ts +75 -2
- package/tsconfig.json +3 -0
- package/dist/cli-tjf4cn8z.js +0 -9119
- package/dist/cli-tsk3nz8a.js +0 -582
- package/dist/{cli-t6v2ah3r.js → cli-twrh04ap.js} +3 -3
|
@@ -1,59 +1,38 @@
|
|
|
1
1
|
import {
|
|
2
|
-
PermissionVerbSchema
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
seedPricingFromModelsDev
|
|
6
|
-
} from "./cli-ecfxwnyb.js";
|
|
2
|
+
PermissionVerbSchema,
|
|
3
|
+
init_rbac
|
|
4
|
+
} from "./cli-kdkwd3yr.js";
|
|
7
5
|
import {
|
|
8
6
|
getDb,
|
|
9
7
|
getDbClient,
|
|
10
8
|
init_db
|
|
11
|
-
} from "./cli-
|
|
9
|
+
} from "./cli-jf7wmv1r.js";
|
|
12
10
|
import {
|
|
13
|
-
init_env_flag
|
|
11
|
+
init_env_flag,
|
|
12
|
+
isEnvFlagEnabled
|
|
14
13
|
} from "./cli-ej6gbxfb.js";
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
import {
|
|
15
|
+
__esm
|
|
16
|
+
} from "./cli-bc05qbss.js";
|
|
18
17
|
|
|
19
18
|
// src/be/rbac-roles.ts
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
{
|
|
33
|
-
|
|
34
|
-
name: "requester",
|
|
35
|
-
description: "Own-task lifecycle: what legacy policy grants user principals.",
|
|
36
|
-
isBuiltin: true,
|
|
37
|
-
grantsAll: false,
|
|
38
|
-
verbs: [
|
|
39
|
-
"task.create.own",
|
|
40
|
-
"task.read.own",
|
|
41
|
-
"task.cancel.own",
|
|
42
|
-
"task.action.own",
|
|
43
|
-
"task.fs.mutate",
|
|
44
|
-
"favorite.write.own"
|
|
45
|
-
]
|
|
19
|
+
function roleRowToUserRole(row) {
|
|
20
|
+
return {
|
|
21
|
+
id: row.id,
|
|
22
|
+
name: row.name,
|
|
23
|
+
description: row.description,
|
|
24
|
+
isBuiltin: row.isBuiltin === 1,
|
|
25
|
+
grantsAll: row.grantsAll === 1,
|
|
26
|
+
createdAt: row.createdAt
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
async function requireRoleId(roleName) {
|
|
30
|
+
const row = await getDbClient().get("SELECT id FROM roles WHERE name = ?", [roleName]);
|
|
31
|
+
if (!row) {
|
|
32
|
+
throw new Error(`Unknown RBAC role: ${roleName}`);
|
|
46
33
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
CREATE TRIGGER IF NOT EXISTS trg_users_default_role
|
|
50
|
-
AFTER INSERT ON users
|
|
51
|
-
BEGIN
|
|
52
|
-
INSERT OR IGNORE INTO principal_roles (principalType, principalId, roleId)
|
|
53
|
-
SELECT 'user', NEW.id, 'rbac-role-admin'
|
|
54
|
-
WHERE EXISTS (SELECT 1 FROM roles WHERE id = 'rbac-role-admin');
|
|
55
|
-
END;
|
|
56
|
-
`;
|
|
34
|
+
return row.id;
|
|
35
|
+
}
|
|
57
36
|
function validateBuiltinVerb(roleName, verb) {
|
|
58
37
|
const parsed = PermissionVerbSchema.safeParse(verb);
|
|
59
38
|
if (!parsed.success) {
|
|
@@ -72,7 +51,6 @@ function validatedBuiltinVerbSets() {
|
|
|
72
51
|
}
|
|
73
52
|
return byRoleId;
|
|
74
53
|
}
|
|
75
|
-
var loggedInvalidGrantVerbs = new Set;
|
|
76
54
|
function parseDatabaseGrantVerb(roleId, verb) {
|
|
77
55
|
const parsed = PermissionVerbSchema.safeParse(verb);
|
|
78
56
|
if (parsed.success) {
|
|
@@ -109,6 +87,28 @@ async function getUserGrant(userId) {
|
|
|
109
87
|
}
|
|
110
88
|
return { grantsAll: false, verbs };
|
|
111
89
|
}
|
|
90
|
+
async function attachRole(userId, roleName) {
|
|
91
|
+
await getDbClient().transaction(async (tx) => {
|
|
92
|
+
const roleId = await requireRoleId(roleName);
|
|
93
|
+
await tx.run(`INSERT OR IGNORE INTO principal_roles (principalType, principalId, roleId)
|
|
94
|
+
VALUES ('user', ?, ?)`, [userId, roleId]);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
async function detachRole(userId, roleName) {
|
|
98
|
+
await getDbClient().transaction(async (tx) => {
|
|
99
|
+
const roleId = await requireRoleId(roleName);
|
|
100
|
+
await tx.run(`DELETE FROM principal_roles
|
|
101
|
+
WHERE principalType = 'user' AND principalId = ? AND roleId = ?`, [userId, roleId]);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
async function listUserRoles(userId) {
|
|
105
|
+
const rows = await getDbClient().query(`SELECT r.id, r.name, r.description, r.isBuiltin, r.grantsAll, r.createdAt
|
|
106
|
+
FROM principal_roles pr
|
|
107
|
+
JOIN roles r ON r.id = pr.roleId
|
|
108
|
+
WHERE pr.principalType = 'user' AND pr.principalId = ?
|
|
109
|
+
ORDER BY r.name`, [userId]);
|
|
110
|
+
return rows.map(roleRowToUserRole);
|
|
111
|
+
}
|
|
112
112
|
function ensureRbacSeedsSynced(opts) {
|
|
113
113
|
const db = getDb();
|
|
114
114
|
const desiredVerbsByRoleId = validatedBuiltinVerbSets();
|
|
@@ -185,23 +185,96 @@ function ensureRbacSeedsSynced(opts) {
|
|
|
185
185
|
}
|
|
186
186
|
return stats;
|
|
187
187
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
const db = getDb();
|
|
194
|
-
if (pricingSeededDbs.has(db))
|
|
195
|
-
return;
|
|
196
|
-
seedPricingFromModelsDev();
|
|
197
|
-
pricingSeededDbs.add(db);
|
|
188
|
+
function describeRbacFlag() {
|
|
189
|
+
const raw = process.env.RBAC_ENABLED;
|
|
190
|
+
if (raw === undefined)
|
|
191
|
+
return "unset (on)";
|
|
192
|
+
return `${raw} (${isEnvFlagEnabled("RBAC_ENABLED", true) ? "on" : "off"})`;
|
|
198
193
|
}
|
|
199
|
-
function
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
194
|
+
function printRolesTable(rows) {
|
|
195
|
+
const tableRows = rows.map((row) => [
|
|
196
|
+
row.name,
|
|
197
|
+
row.isBuiltin === 1 ? "yes" : "no",
|
|
198
|
+
row.grantsAll === 1 ? "yes" : "no",
|
|
199
|
+
String(row.verbCount),
|
|
200
|
+
String(row.attachedUserCount)
|
|
201
|
+
]);
|
|
202
|
+
const headers = ["name", "builtin", "grantsAll", "verbs", "attachedUsers"];
|
|
203
|
+
const widths = headers.map((header, column) => Math.max(header.length, ...tableRows.map((row) => row[column]?.length ?? 0)));
|
|
204
|
+
const format = (row) => row.map((cell, column) => cell.padEnd(widths[column] ?? cell.length)).join(" ");
|
|
205
|
+
console.log("Roles:");
|
|
206
|
+
console.log(format(headers));
|
|
207
|
+
console.log(format(widths.map((width) => "-".repeat(width))));
|
|
208
|
+
for (const row of tableRows) {
|
|
209
|
+
console.log(format(row));
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
async function getRbacRoleSummary() {
|
|
213
|
+
return getDbClient().query(`SELECT
|
|
214
|
+
r.name,
|
|
215
|
+
r.isBuiltin,
|
|
216
|
+
r.grantsAll,
|
|
217
|
+
COUNT(DISTINCT rp.verb) AS verbCount,
|
|
218
|
+
COUNT(DISTINCT CASE WHEN pr.principalType = 'user' THEN pr.principalId END)
|
|
219
|
+
AS attachedUserCount
|
|
220
|
+
FROM roles r
|
|
221
|
+
LEFT JOIN role_permissions rp ON rp.roleId = r.id
|
|
222
|
+
LEFT JOIN principal_roles pr ON pr.roleId = r.id
|
|
223
|
+
GROUP BY r.id, r.name, r.isBuiltin, r.grantsAll
|
|
224
|
+
ORDER BY r.isBuiltin DESC, r.name`);
|
|
225
|
+
}
|
|
226
|
+
async function runRbacCliCommand(args) {
|
|
227
|
+
const subcommand = args[0];
|
|
228
|
+
if (args.length !== 1 || subcommand !== "bootstrap") {
|
|
229
|
+
throw new Error(`Unknown RBAC command: ${args.join(" ") || "(none)"}`);
|
|
230
|
+
}
|
|
231
|
+
const stats = ensureRbacSeedsSynced({ quiet: true });
|
|
232
|
+
const roles = await getRbacRoleSummary();
|
|
233
|
+
console.log("RBAC bootstrap complete");
|
|
234
|
+
console.log(`RBAC_ENABLED: ${describeRbacFlag()}`);
|
|
235
|
+
console.log(`Users backfilled this run: ${stats.usersBackfilled}`);
|
|
236
|
+
console.log("");
|
|
237
|
+
printRolesTable(roles);
|
|
205
238
|
}
|
|
239
|
+
var DEFAULT_ROLE_ID = "rbac-role-admin", BUILTIN_ROLES, CREATE_USER_DEFAULT_ROLE_TRIGGER_SQL = `
|
|
240
|
+
CREATE TRIGGER IF NOT EXISTS trg_users_default_role
|
|
241
|
+
AFTER INSERT ON users
|
|
242
|
+
BEGIN
|
|
243
|
+
INSERT OR IGNORE INTO principal_roles (principalType, principalId, roleId)
|
|
244
|
+
SELECT 'user', NEW.id, 'rbac-role-admin'
|
|
245
|
+
WHERE EXISTS (SELECT 1 FROM roles WHERE id = 'rbac-role-admin');
|
|
246
|
+
END;
|
|
247
|
+
`, loggedInvalidGrantVerbs;
|
|
248
|
+
var init_rbac_roles = __esm(() => {
|
|
249
|
+
init_rbac();
|
|
250
|
+
init_env_flag();
|
|
251
|
+
init_db();
|
|
252
|
+
BUILTIN_ROLES = [
|
|
253
|
+
{
|
|
254
|
+
id: DEFAULT_ROLE_ID,
|
|
255
|
+
name: "admin",
|
|
256
|
+
description: "Full access including verb-less routes (legacy-equivalent default).",
|
|
257
|
+
isBuiltin: true,
|
|
258
|
+
grantsAll: true,
|
|
259
|
+
verbs: []
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
id: "rbac-role-requester",
|
|
263
|
+
name: "requester",
|
|
264
|
+
description: "Own-task lifecycle: what legacy policy grants user principals.",
|
|
265
|
+
isBuiltin: true,
|
|
266
|
+
grantsAll: false,
|
|
267
|
+
verbs: [
|
|
268
|
+
"task.create.own",
|
|
269
|
+
"task.read.own",
|
|
270
|
+
"task.cancel.own",
|
|
271
|
+
"task.action.own",
|
|
272
|
+
"task.fs.mutate",
|
|
273
|
+
"favorite.write.own"
|
|
274
|
+
]
|
|
275
|
+
}
|
|
276
|
+
];
|
|
277
|
+
loggedInvalidGrantVerbs = new Set;
|
|
278
|
+
});
|
|
206
279
|
|
|
207
|
-
export { getUserGrant,
|
|
280
|
+
export { DEFAULT_ROLE_ID, BUILTIN_ROLES, getUserGrant, attachRole, detachRole, listUserRoles, ensureRbacSeedsSynced, runRbacCliCommand, init_rbac_roles };
|
|
@@ -2,13 +2,13 @@ import {
|
|
|
2
2
|
mcpNamespace,
|
|
3
3
|
require_proper_lockfile,
|
|
4
4
|
validateMcpServerConfig
|
|
5
|
-
} from "./cli-
|
|
5
|
+
} from "./cli-8ev9xqnx.js";
|
|
6
6
|
import {
|
|
7
7
|
CONFIG_DIR_NAME,
|
|
8
8
|
canonicalizePath,
|
|
9
9
|
resolvePath,
|
|
10
10
|
stripBom
|
|
11
|
-
} from "./cli-
|
|
11
|
+
} from "./cli-s3hwpt9v.js";
|
|
12
12
|
import {
|
|
13
13
|
__toESM
|
|
14
14
|
} from "./cli-bc05qbss.js";
|
|
@@ -212,6 +212,10 @@ function openBrowser(target) {
|
|
|
212
212
|
// node_modules/@earendil-works/pi-coding-agent/dist/extensions/mcp/config.js
|
|
213
213
|
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
214
214
|
import { dirname as dirname2, join as join2 } from "node:path";
|
|
215
|
+
var OVERRIDE_KEYS = ["enabled", "exposure", "toolExposure"];
|
|
216
|
+
function isOverride(value) {
|
|
217
|
+
return value.command === undefined && value.url === undefined && value.type === undefined;
|
|
218
|
+
}
|
|
215
219
|
function isRecord(value) {
|
|
216
220
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
217
221
|
}
|
|
@@ -235,6 +239,22 @@ function readConfigFile(path, scope, state) {
|
|
|
235
239
|
else if (parsed.autoEnableCodemode !== undefined)
|
|
236
240
|
errors.push(`${path}: autoEnableCodemode must be a boolean`);
|
|
237
241
|
for (const [name, value] of Object.entries(parsed.mcpServers ?? {})) {
|
|
242
|
+
if (scope === "project" && isRecord(value) && isOverride(value)) {
|
|
243
|
+
const base = servers.get(name);
|
|
244
|
+
const extra = Object.keys(value).filter((key) => !OVERRIDE_KEYS.includes(key));
|
|
245
|
+
if (!base) {
|
|
246
|
+
errors.push(`${path}: server "${name}" needs "command" or "url", or a global server to override`);
|
|
247
|
+
} else if (extra.length > 0) {
|
|
248
|
+
errors.push(`${path}: server "${name}": an override can only set ${OVERRIDE_KEYS.join(", ")}`);
|
|
249
|
+
} else {
|
|
250
|
+
const config2 = validateMcpServerConfig(name, { ...base.config, ...value });
|
|
251
|
+
if (typeof config2 === "string")
|
|
252
|
+
errors.push(`${path}: ${config2}`);
|
|
253
|
+
else
|
|
254
|
+
servers.set(name, { ...base, config: config2, override: path });
|
|
255
|
+
}
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
238
258
|
const config = validateMcpServerConfig(name, value);
|
|
239
259
|
if (typeof config === "string") {
|
|
240
260
|
errors.push(`${path}: ${config}`);
|
|
@@ -255,27 +275,34 @@ function readConfigFile(path, scope, state) {
|
|
|
255
275
|
function loadMcpConfig(options) {
|
|
256
276
|
const state = { servers: new Map, errors: [] };
|
|
257
277
|
readConfigFile(join2(options.agentDir, "mcp.json"), "global", state);
|
|
258
|
-
|
|
259
|
-
|
|
278
|
+
const projectConfig = options.projectTrusted ? join2(options.cwd, CONFIG_DIR_NAME, "mcp.json") : undefined;
|
|
279
|
+
if (projectConfig)
|
|
280
|
+
readConfigFile(projectConfig, "project", state);
|
|
260
281
|
return {
|
|
261
282
|
servers: [...state.servers.values()],
|
|
262
283
|
...state.autoEnableCodemode === undefined ? {} : { autoEnableCodemode: state.autoEnableCodemode },
|
|
263
|
-
errors: state.errors
|
|
284
|
+
errors: state.errors,
|
|
285
|
+
...projectConfig ? { projectConfig } : {}
|
|
264
286
|
};
|
|
265
287
|
}
|
|
266
|
-
function updateMcpServerConfig(path, name, patch) {
|
|
267
|
-
editMcpServers(path, (servers) => {
|
|
268
|
-
|
|
288
|
+
function updateMcpServerConfig(path, name, patch, options = {}) {
|
|
289
|
+
editMcpServers(path, (servers, parsed) => {
|
|
290
|
+
let server = servers?.[name];
|
|
291
|
+
if (server === undefined && options.override) {
|
|
292
|
+
server = {};
|
|
293
|
+
parsed.mcpServers = { ...servers, [name]: server };
|
|
294
|
+
}
|
|
269
295
|
if (!isRecord(server))
|
|
270
296
|
throw new Error(`${path} does not define MCP server "${name}"`);
|
|
297
|
+
const keepDefaults = isOverride(server);
|
|
271
298
|
if (patch.enabled !== undefined) {
|
|
272
|
-
if (patch.enabled)
|
|
299
|
+
if (patch.enabled && !keepDefaults)
|
|
273
300
|
delete server.enabled;
|
|
274
301
|
else
|
|
275
|
-
server.enabled =
|
|
302
|
+
server.enabled = patch.enabled;
|
|
276
303
|
}
|
|
277
304
|
if (patch.exposure !== undefined) {
|
|
278
|
-
if (patch.exposure === "codemode")
|
|
305
|
+
if (patch.exposure === "codemode" && !keepDefaults)
|
|
279
306
|
delete server.exposure;
|
|
280
307
|
else
|
|
281
308
|
server.exposure = patch.exposure;
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
import {
|
|
7
7
|
parseModelStr,
|
|
8
8
|
resolveCredential
|
|
9
|
-
} from "./cli-
|
|
9
|
+
} from "./cli-5bcd7sfy.js";
|
|
10
10
|
import {
|
|
11
11
|
DEFAULT_OPENROUTER_BASE_URL,
|
|
12
12
|
getOpenRouterBaseUrl
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
import {
|
|
15
15
|
complete,
|
|
16
16
|
getBuiltinModel
|
|
17
|
-
} from "./cli-
|
|
17
|
+
} from "./cli-nt8hecg9.js";
|
|
18
18
|
import {
|
|
19
19
|
exports_typebox
|
|
20
20
|
} from "./cli-rb9m6rpe.js";
|
|
@@ -15,12 +15,12 @@ import {
|
|
|
15
15
|
require_proper_lockfile,
|
|
16
16
|
resolveConfigValueOrThrow,
|
|
17
17
|
resolveHeadersOrThrow
|
|
18
|
-
} from "./cli-
|
|
18
|
+
} from "./cli-8ev9xqnx.js";
|
|
19
19
|
import {
|
|
20
20
|
APP_NAME,
|
|
21
21
|
VERSION,
|
|
22
22
|
getAgentDir
|
|
23
|
-
} from "./cli-
|
|
23
|
+
} from "./cli-s3hwpt9v.js";
|
|
24
24
|
import {
|
|
25
25
|
oauthErrorHtml,
|
|
26
26
|
oauthSuccessHtml
|
|
@@ -47,14 +47,14 @@ ${page.details}` : page.message;
|
|
|
47
47
|
class OAuthCallbackServer {
|
|
48
48
|
redirectUrl;
|
|
49
49
|
server;
|
|
50
|
-
|
|
50
|
+
paths;
|
|
51
51
|
timeoutMs;
|
|
52
52
|
renderPage;
|
|
53
53
|
pending = new Map;
|
|
54
|
-
constructor(server, redirectUrl,
|
|
54
|
+
constructor(server, redirectUrl, paths, timeoutMs, renderPage) {
|
|
55
55
|
this.server = server;
|
|
56
56
|
this.redirectUrl = redirectUrl;
|
|
57
|
-
this.
|
|
57
|
+
this.paths = paths;
|
|
58
58
|
this.timeoutMs = timeoutMs;
|
|
59
59
|
this.renderPage = renderPage;
|
|
60
60
|
}
|
|
@@ -74,10 +74,10 @@ class OAuthCallbackServer {
|
|
|
74
74
|
const address = server.address();
|
|
75
75
|
if (!address || typeof address === "string")
|
|
76
76
|
throw new Error("OAuth callback server did not bind to TCP");
|
|
77
|
-
instance = new OAuthCallbackServer(server, `http://${redirectHost.includes(":") ? `[${redirectHost}]` : redirectHost}:${address.port}${path}`, path, options.timeoutMs ?? 5 * 60000, options.renderPage);
|
|
77
|
+
instance = new OAuthCallbackServer(server, `http://${redirectHost.includes(":") ? `[${redirectHost}]` : redirectHost}:${address.port}${path}`, [path, ...options.extraPaths ?? []], options.timeoutMs ?? 5 * 60000, options.renderPage);
|
|
78
78
|
return instance;
|
|
79
79
|
}
|
|
80
|
-
waitForCallback(state) {
|
|
80
|
+
waitForCallback(state, path) {
|
|
81
81
|
if (this.pending.has(state))
|
|
82
82
|
throw new Error("OAuth state is already pending");
|
|
83
83
|
return new Promise((resolve, reject) => {
|
|
@@ -85,7 +85,7 @@ class OAuthCallbackServer {
|
|
|
85
85
|
this.pending.delete(state);
|
|
86
86
|
reject(new Error("OAuth callback timed out"));
|
|
87
87
|
}, this.timeoutMs);
|
|
88
|
-
this.pending.set(state, { resolve, reject, timer });
|
|
88
|
+
this.pending.set(state, { resolve, reject, timer, path });
|
|
89
89
|
});
|
|
90
90
|
}
|
|
91
91
|
async close() {
|
|
@@ -108,7 +108,7 @@ class OAuthCallbackServer {
|
|
|
108
108
|
}
|
|
109
109
|
handle(rawUrl, response) {
|
|
110
110
|
const url = new URL(rawUrl, this.redirectUrl);
|
|
111
|
-
if (url.pathname
|
|
111
|
+
if (!this.paths.includes(url.pathname)) {
|
|
112
112
|
this.reply(response, 404, { ok: false, message: "Not found" });
|
|
113
113
|
return;
|
|
114
114
|
}
|
|
@@ -120,6 +120,11 @@ class OAuthCallbackServer {
|
|
|
120
120
|
}
|
|
121
121
|
clearTimeout(pending.timer);
|
|
122
122
|
this.pending.delete(state);
|
|
123
|
+
if (pending.path !== undefined && url.pathname !== pending.path) {
|
|
124
|
+
pending.reject(new Error("The authorization response arrived on another redirect URI"));
|
|
125
|
+
this.reply(response, 400, { ok: false, message: "Unexpected redirect URI" });
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
123
128
|
const error = url.searchParams.get("error");
|
|
124
129
|
if (error) {
|
|
125
130
|
const description = url.searchParams.get("error_description") ?? error;
|
|
@@ -574,28 +579,28 @@ async function runFlow(provider, options) {
|
|
|
574
579
|
const metadata = discovered.authorizationServerMetadata;
|
|
575
580
|
const resource = selectResource(options.serverUrl, discovered.resourceMetadata);
|
|
576
581
|
const scope = options.scope || discovered.resourceMetadata?.scopes_supported?.join(" ") || provider.clientMetadata.scope;
|
|
577
|
-
|
|
582
|
+
const stored = await provider.clientInformation();
|
|
583
|
+
const clientDocument = stored ? undefined : provider.clientMetadataDocument?.(metadata);
|
|
584
|
+
if (clientDocument) {
|
|
585
|
+
const url = new URL(clientDocument.url);
|
|
586
|
+
if (url.protocol !== "https:" || url.pathname === "/")
|
|
587
|
+
throw new Error("Invalid OAuth client metadata URL");
|
|
588
|
+
}
|
|
589
|
+
let client = stored ?? (clientDocument && { client_id: clientDocument.url });
|
|
578
590
|
if (!client) {
|
|
579
591
|
if (options.authorizationCode)
|
|
580
592
|
throw new Error("OAuth client information is missing during code exchange");
|
|
581
|
-
if (
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
client = await registerClient(discovered.authorizationServerUrl, {
|
|
591
|
-
metadata,
|
|
592
|
-
clientMetadata: provider.clientMetadata,
|
|
593
|
-
scope,
|
|
594
|
-
fetch: options.fetch
|
|
595
|
-
});
|
|
596
|
-
await provider.saveClientInformation(client);
|
|
597
|
-
}
|
|
593
|
+
if (!provider.saveClientInformation)
|
|
594
|
+
throw new Error("OAuth client information cannot be persisted");
|
|
595
|
+
client = await registerClient(discovered.authorizationServerUrl, {
|
|
596
|
+
metadata,
|
|
597
|
+
clientMetadata: provider.clientMetadata,
|
|
598
|
+
scope,
|
|
599
|
+
fetch: options.fetch
|
|
600
|
+
});
|
|
601
|
+
await provider.saveClientInformation(client);
|
|
598
602
|
}
|
|
603
|
+
const redirectUrl = clientDocument?.redirectUrl ?? provider.redirectUrl;
|
|
599
604
|
const tokenOptions = {
|
|
600
605
|
metadata,
|
|
601
606
|
clientInformation: client,
|
|
@@ -613,7 +618,7 @@ async function runFlow(provider, options) {
|
|
|
613
618
|
...tokenOptions,
|
|
614
619
|
code: options.authorizationCode,
|
|
615
620
|
codeVerifier: await provider.codeVerifier(),
|
|
616
|
-
redirectUrl
|
|
621
|
+
redirectUrl
|
|
617
622
|
});
|
|
618
623
|
await provider.saveTokens(withScope(tokens, scope));
|
|
619
624
|
return "AUTHORIZED";
|
|
@@ -638,7 +643,7 @@ async function runFlow(provider, options) {
|
|
|
638
643
|
const authorization = await startAuthorization(discovered.authorizationServerUrl, {
|
|
639
644
|
metadata,
|
|
640
645
|
clientInformation: client,
|
|
641
|
-
redirectUrl
|
|
646
|
+
redirectUrl,
|
|
642
647
|
scope,
|
|
643
648
|
state,
|
|
644
649
|
resource
|
|
@@ -676,6 +681,7 @@ class MemoryOAuthStateStore {
|
|
|
676
681
|
class McpOAuthProvider {
|
|
677
682
|
redirectUrl;
|
|
678
683
|
clientMetadata;
|
|
684
|
+
clientMetadataDocument;
|
|
679
685
|
serverUrl;
|
|
680
686
|
configuredClient;
|
|
681
687
|
store;
|
|
@@ -691,6 +697,7 @@ class McpOAuthProvider {
|
|
|
691
697
|
response_types: options.clientMetadata.response_types ?? ["code"],
|
|
692
698
|
token_endpoint_auth_method: options.clientMetadata.token_endpoint_auth_method ?? (options.clientSecret ? "client_secret_post" : "none")
|
|
693
699
|
};
|
|
700
|
+
this.clientMetadataDocument = options.clientMetadataDocument;
|
|
694
701
|
this.configuredClient = options.clientId ? { client_id: options.clientId, ...options.clientSecret ? { client_secret: options.clientSecret } : {} } : undefined;
|
|
695
702
|
this.store = options.store ?? new MemoryOAuthStateStore;
|
|
696
703
|
this.onRedirect = options.onRedirect;
|
|
@@ -782,6 +789,7 @@ import { join } from "node:path";
|
|
|
782
789
|
var import_proper_lockfile = __toESM(require_proper_lockfile(), 1);
|
|
783
790
|
var CALLBACK_HOST = "127.0.0.1";
|
|
784
791
|
var CALLBACK_PATH = "/callback";
|
|
792
|
+
var CLIENT_METADATA_BASE_URL = "https://pi.dev/oauth";
|
|
785
793
|
var FALLBACK_REDIRECT_URL = `http://${CALLBACK_HOST}${CALLBACK_PATH}`;
|
|
786
794
|
var REFRESH_SKEW_MS = 30000;
|
|
787
795
|
var REFRESH_REQUEST_TIMEOUT_MS = 15000;
|
|
@@ -901,11 +909,29 @@ class McpOAuthCredentialStore {
|
|
|
901
909
|
function registeredRedirectUrls(client) {
|
|
902
910
|
return client && "redirect_uris" in client ? client.redirect_uris : [];
|
|
903
911
|
}
|
|
912
|
+
function callbackId(serverUrl) {
|
|
913
|
+
const url = new URL(serverUrl);
|
|
914
|
+
url.hash = "";
|
|
915
|
+
return createHash("sha256").update(url.href).digest().subarray(0, 9).toString("base64url");
|
|
916
|
+
}
|
|
917
|
+
function clientMetadataDocument(serverUrl, redirectUrl, metadata) {
|
|
918
|
+
if (!metadata?.client_id_metadata_document_supported || !metadata.token_endpoint_auth_methods_supported?.includes("none")) {
|
|
919
|
+
throw new Error('The authorization server does not support Client ID Metadata Documents for public clients; remove oauth.clientRegistration "cimd"');
|
|
920
|
+
}
|
|
921
|
+
if (metadata.authorization_response_iss_parameter_supported) {
|
|
922
|
+
return { url: `${CLIENT_METADATA_BASE_URL}/client.json`, redirectUrl };
|
|
923
|
+
}
|
|
924
|
+
const id = callbackId(serverUrl);
|
|
925
|
+
const redirect = new URL(redirectUrl);
|
|
926
|
+
redirect.pathname = `${CALLBACK_PATH}/${id}`;
|
|
927
|
+
return { url: `${CLIENT_METADATA_BASE_URL}/${id}/client.json`, redirectUrl: redirect.href };
|
|
928
|
+
}
|
|
904
929
|
function createProvider(serverUrl, store, settings, redirectUrl, onRedirect) {
|
|
905
930
|
return new McpOAuthProvider({
|
|
906
931
|
serverUrl,
|
|
907
932
|
redirectUrl,
|
|
908
933
|
clientMetadata: { client_name: settings.clientName ?? APP_NAME },
|
|
934
|
+
clientMetadataDocument: settings.clientRegistration === "cimd" ? (metadata) => clientMetadataDocument(serverUrl, redirectUrl, metadata) : undefined,
|
|
909
935
|
clientId: settings.clientId,
|
|
910
936
|
clientSecret: settings.clientSecret,
|
|
911
937
|
store,
|
|
@@ -975,13 +1001,16 @@ class McpSignInCancelledError extends Error {
|
|
|
975
1001
|
this.name = "McpSignInCancelledError";
|
|
976
1002
|
}
|
|
977
1003
|
}
|
|
978
|
-
function responseFromRedirectUrl(input, state) {
|
|
1004
|
+
function responseFromRedirectUrl(input, state, redirectUrl) {
|
|
979
1005
|
let url;
|
|
980
1006
|
try {
|
|
981
1007
|
url = new URL(input.trim());
|
|
982
1008
|
} catch {
|
|
983
1009
|
throw new Error("Expected the full redirect URL from the browser address bar");
|
|
984
1010
|
}
|
|
1011
|
+
if (url.origin !== redirectUrl.origin || url.pathname !== redirectUrl.pathname) {
|
|
1012
|
+
throw new Error("The redirect URL does not match this sign-in's redirect URI");
|
|
1013
|
+
}
|
|
985
1014
|
const error = url.searchParams.get("error");
|
|
986
1015
|
if (error)
|
|
987
1016
|
throw new Error(url.searchParams.get("error_description") ?? error);
|
|
@@ -992,13 +1021,13 @@ function responseFromRedirectUrl(input, state) {
|
|
|
992
1021
|
throw new Error("The redirect URL does not contain an authorization code");
|
|
993
1022
|
return { code, iss: url.searchParams.get("iss") ?? undefined };
|
|
994
1023
|
}
|
|
995
|
-
async function waitForAuthorizationResponse(callback, state, prompt) {
|
|
1024
|
+
async function waitForAuthorizationResponse(callback, state, redirectUrl, prompt) {
|
|
996
1025
|
const controller = new AbortController;
|
|
997
|
-
const fromBrowser = callback.waitForCallback(state);
|
|
1026
|
+
const fromBrowser = callback.waitForCallback(state, redirectUrl.pathname);
|
|
998
1027
|
const fromUser = prompt.promptForRedirectUrl(controller.signal).then((input) => {
|
|
999
1028
|
if (!input?.trim())
|
|
1000
1029
|
throw new McpSignInCancelledError;
|
|
1001
|
-
return responseFromRedirectUrl(input, state);
|
|
1030
|
+
return responseFromRedirectUrl(input, state, redirectUrl);
|
|
1002
1031
|
});
|
|
1003
1032
|
try {
|
|
1004
1033
|
return await Promise.race([fromBrowser, fromUser]);
|
|
@@ -1012,11 +1041,12 @@ async function waitForAuthorizationResponse(callback, state, prompt) {
|
|
|
1012
1041
|
});
|
|
1013
1042
|
}
|
|
1014
1043
|
}
|
|
1015
|
-
async function listenForCallback(settings, port, required) {
|
|
1044
|
+
async function listenForCallback(settings, extraPaths, port, required) {
|
|
1016
1045
|
const options = {
|
|
1017
1046
|
host: settings.host,
|
|
1018
1047
|
redirectHost: settings.redirectHost,
|
|
1019
1048
|
path: settings.path,
|
|
1049
|
+
extraPaths,
|
|
1020
1050
|
renderPage: (page) => page.ok ? oauthSuccessHtml("Signed in to the MCP server. You may now close this page.") : oauthErrorHtml(page.message, page.details)
|
|
1021
1051
|
};
|
|
1022
1052
|
try {
|
|
@@ -1034,13 +1064,15 @@ async function signInMcpServer(options) {
|
|
|
1034
1064
|
const callbackOptions = callbackSettings(settings);
|
|
1035
1065
|
const registered = registeredRedirectUrls(stored?.clientInformation)[0];
|
|
1036
1066
|
const preferredPort = callbackOptions.port ?? (registered ? Number(new URL(registered).port) || undefined : undefined);
|
|
1037
|
-
const
|
|
1067
|
+
const cimd = settings.clientRegistration === "cimd";
|
|
1068
|
+
const callback = await listenForCallback(callbackOptions, cimd ? [`${CALLBACK_PATH}/${callbackId(serverUrl)}`] : [], preferredPort, callbackOptions.port !== undefined);
|
|
1038
1069
|
const redirectUrl = callbackOptions.fixedRedirectUrl ?? callback.redirectUrl;
|
|
1039
1070
|
try {
|
|
1040
1071
|
if (stored) {
|
|
1041
1072
|
const next = { ...stored };
|
|
1042
1073
|
delete next.oauthState;
|
|
1043
|
-
|
|
1074
|
+
const keepClient = settings.clientId || (cimd ? !stored.clientInformation : registeredRedirectUrls(stored.clientInformation).includes(redirectUrl));
|
|
1075
|
+
if (!keepClient) {
|
|
1044
1076
|
delete next.clientInformation;
|
|
1045
1077
|
delete next.tokens;
|
|
1046
1078
|
delete next.tokensExpireAt;
|
|
@@ -1063,8 +1095,9 @@ async function signInMcpServer(options) {
|
|
|
1063
1095
|
if (!authorizationUrl)
|
|
1064
1096
|
throw new Error("OAuth flow did not produce an authorization URL");
|
|
1065
1097
|
const state = await provider.state();
|
|
1098
|
+
const authorizationRedirectUrl = new URL(authorizationUrl.searchParams.get("redirect_uri") ?? redirectUrl);
|
|
1066
1099
|
options.prompt.showAuthorizationUrl(authorizationUrl);
|
|
1067
|
-
const { code, iss } = await waitForAuthorizationResponse(callback, state, options.prompt);
|
|
1100
|
+
const { code, iss } = await waitForAuthorizationResponse(callback, state, authorizationRedirectUrl, options.prompt);
|
|
1068
1101
|
await authorizeMcp(provider, { ...flow, authorizationCode: code, iss });
|
|
1069
1102
|
} finally {
|
|
1070
1103
|
await callback.close();
|
|
@@ -1261,6 +1294,7 @@ class McpServerConnection {
|
|
|
1261
1294
|
callbackUrl: oauth.callbackUrl,
|
|
1262
1295
|
scope: oauth.scope,
|
|
1263
1296
|
clientName: oauth.clientName,
|
|
1297
|
+
clientRegistration: oauth.clientRegistration,
|
|
1264
1298
|
authServerMetadataUrl: oauth.authServerMetadataUrl ? new URL(oauth.authServerMetadataUrl) : undefined
|
|
1265
1299
|
};
|
|
1266
1300
|
}
|