@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
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PermissionVerbSchema
|
|
3
|
+
} from "./cli-86fdnerz.js";
|
|
4
|
+
import {
|
|
5
|
+
getDb,
|
|
6
|
+
init_db
|
|
7
|
+
} from "./cli-15y7qhzp.js";
|
|
8
|
+
|
|
9
|
+
// src/be/rbac-roles.ts
|
|
10
|
+
init_db();
|
|
11
|
+
var DEFAULT_ROLE_ID = "rbac-role-admin";
|
|
12
|
+
var BUILTIN_ROLES = [
|
|
13
|
+
{
|
|
14
|
+
id: DEFAULT_ROLE_ID,
|
|
15
|
+
name: "admin",
|
|
16
|
+
description: "Full access including verb-less routes (legacy-equivalent default).",
|
|
17
|
+
isBuiltin: true,
|
|
18
|
+
grantsAll: true,
|
|
19
|
+
verbs: []
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
id: "rbac-role-requester",
|
|
23
|
+
name: "requester",
|
|
24
|
+
description: "Own-task lifecycle: what legacy policy grants user principals.",
|
|
25
|
+
isBuiltin: true,
|
|
26
|
+
grantsAll: false,
|
|
27
|
+
verbs: [
|
|
28
|
+
"task.create.own",
|
|
29
|
+
"task.read.own",
|
|
30
|
+
"task.cancel.own",
|
|
31
|
+
"task.action.own",
|
|
32
|
+
"task.fs.mutate",
|
|
33
|
+
"favorite.write.own"
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
];
|
|
37
|
+
var CREATE_USER_DEFAULT_ROLE_TRIGGER_SQL = `
|
|
38
|
+
CREATE TRIGGER IF NOT EXISTS trg_users_default_role
|
|
39
|
+
AFTER INSERT ON users
|
|
40
|
+
BEGIN
|
|
41
|
+
INSERT OR IGNORE INTO principal_roles (principalType, principalId, roleId)
|
|
42
|
+
SELECT 'user', NEW.id, 'rbac-role-admin'
|
|
43
|
+
WHERE EXISTS (SELECT 1 FROM roles WHERE id = 'rbac-role-admin');
|
|
44
|
+
END;
|
|
45
|
+
`;
|
|
46
|
+
function roleRowToUserRole(row) {
|
|
47
|
+
return {
|
|
48
|
+
id: row.id,
|
|
49
|
+
name: row.name,
|
|
50
|
+
description: row.description,
|
|
51
|
+
isBuiltin: row.isBuiltin === 1,
|
|
52
|
+
grantsAll: row.grantsAll === 1,
|
|
53
|
+
createdAt: row.createdAt
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function requireRoleId(roleName) {
|
|
57
|
+
const row = getDb().prepare("SELECT id FROM roles WHERE name = ?").get(roleName);
|
|
58
|
+
if (!row) {
|
|
59
|
+
throw new Error(`Unknown RBAC role: ${roleName}`);
|
|
60
|
+
}
|
|
61
|
+
return row.id;
|
|
62
|
+
}
|
|
63
|
+
function validateBuiltinVerb(roleName, verb) {
|
|
64
|
+
const parsed = PermissionVerbSchema.safeParse(verb);
|
|
65
|
+
if (!parsed.success) {
|
|
66
|
+
throw new Error(`Invalid RBAC permission verb "${verb}" in built-in role "${roleName}"`);
|
|
67
|
+
}
|
|
68
|
+
return parsed.data;
|
|
69
|
+
}
|
|
70
|
+
function validatedBuiltinVerbSets() {
|
|
71
|
+
const byRoleId = new Map;
|
|
72
|
+
for (const role of BUILTIN_ROLES) {
|
|
73
|
+
const verbs = new Set;
|
|
74
|
+
for (const verb of role.verbs) {
|
|
75
|
+
verbs.add(validateBuiltinVerb(role.name, verb));
|
|
76
|
+
}
|
|
77
|
+
byRoleId.set(role.id, verbs);
|
|
78
|
+
}
|
|
79
|
+
return byRoleId;
|
|
80
|
+
}
|
|
81
|
+
var loggedInvalidGrantVerbs = new Set;
|
|
82
|
+
function parseDatabaseGrantVerb(roleId, verb) {
|
|
83
|
+
const parsed = PermissionVerbSchema.safeParse(verb);
|
|
84
|
+
if (parsed.success) {
|
|
85
|
+
return parsed.data;
|
|
86
|
+
}
|
|
87
|
+
const logKey = `${roleId}:${verb}`;
|
|
88
|
+
if (!loggedInvalidGrantVerbs.has(logKey)) {
|
|
89
|
+
loggedInvalidGrantVerbs.add(logKey);
|
|
90
|
+
console.error(`[rbac] Ignoring invalid role_permissions verb "${verb}" for roleId="${roleId}". ` + "Invalid RBAC verbs grant nothing; remove or repair the row.");
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
function getUserGrant(userId) {
|
|
95
|
+
const rows = getDb().prepare(`SELECT r.id AS roleId, r.grantsAll, rp.verb
|
|
96
|
+
FROM principal_roles pr
|
|
97
|
+
JOIN roles r ON r.id = pr.roleId
|
|
98
|
+
LEFT JOIN role_permissions rp ON rp.roleId = r.id
|
|
99
|
+
WHERE pr.principalType = 'user' AND pr.principalId = ?
|
|
100
|
+
ORDER BY r.grantsAll DESC`).all(userId);
|
|
101
|
+
if (rows.length === 0) {
|
|
102
|
+
return { grantsAll: false, verbs: new Set };
|
|
103
|
+
}
|
|
104
|
+
const verbs = new Set;
|
|
105
|
+
for (const row of rows) {
|
|
106
|
+
if (row.grantsAll === 1) {
|
|
107
|
+
return { grantsAll: true, verbs: new Set };
|
|
108
|
+
}
|
|
109
|
+
if (row.verb) {
|
|
110
|
+
const verb = parseDatabaseGrantVerb(row.roleId, row.verb);
|
|
111
|
+
if (verb) {
|
|
112
|
+
verbs.add(verb);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return { grantsAll: false, verbs };
|
|
117
|
+
}
|
|
118
|
+
function attachRole(userId, roleName) {
|
|
119
|
+
const db = getDb();
|
|
120
|
+
db.transaction(() => {
|
|
121
|
+
const roleId = requireRoleId(roleName);
|
|
122
|
+
db.prepare(`INSERT OR IGNORE INTO principal_roles (principalType, principalId, roleId)
|
|
123
|
+
VALUES ('user', ?, ?)`).run(userId, roleId);
|
|
124
|
+
})();
|
|
125
|
+
}
|
|
126
|
+
function detachRole(userId, roleName) {
|
|
127
|
+
const db = getDb();
|
|
128
|
+
db.transaction(() => {
|
|
129
|
+
const roleId = requireRoleId(roleName);
|
|
130
|
+
db.prepare(`DELETE FROM principal_roles
|
|
131
|
+
WHERE principalType = 'user' AND principalId = ? AND roleId = ?`).run(userId, roleId);
|
|
132
|
+
})();
|
|
133
|
+
}
|
|
134
|
+
function listUserRoles(userId) {
|
|
135
|
+
return getDb().prepare(`SELECT r.id, r.name, r.description, r.isBuiltin, r.grantsAll, r.createdAt
|
|
136
|
+
FROM principal_roles pr
|
|
137
|
+
JOIN roles r ON r.id = pr.roleId
|
|
138
|
+
WHERE pr.principalType = 'user' AND pr.principalId = ?
|
|
139
|
+
ORDER BY r.name`).all(userId).map(roleRowToUserRole);
|
|
140
|
+
}
|
|
141
|
+
function ensureRbacSeedsSynced(opts) {
|
|
142
|
+
const db = getDb();
|
|
143
|
+
const desiredVerbsByRoleId = validatedBuiltinVerbSets();
|
|
144
|
+
const insertRole = db.prepare(`INSERT OR IGNORE INTO roles (id, name, description, isBuiltin, grantsAll)
|
|
145
|
+
VALUES (?, ?, ?, 1, ?)`);
|
|
146
|
+
const updateRole = db.prepare(`UPDATE roles
|
|
147
|
+
SET name = ?, description = ?, isBuiltin = 1, grantsAll = ?, lastUpdatedAt = CURRENT_TIMESTAMP
|
|
148
|
+
WHERE id = ?
|
|
149
|
+
AND (
|
|
150
|
+
name <> ?
|
|
151
|
+
OR COALESCE(description, '') <> COALESCE(?, '')
|
|
152
|
+
OR isBuiltin <> 1
|
|
153
|
+
OR grantsAll <> ?
|
|
154
|
+
)`);
|
|
155
|
+
const selectRoleById = db.prepare("SELECT id, name FROM roles WHERE id = ?");
|
|
156
|
+
const selectRoleByName = db.prepare("SELECT id, name FROM roles WHERE name = ?");
|
|
157
|
+
const selectRolePermissions = db.prepare("SELECT verb FROM role_permissions WHERE roleId = ?");
|
|
158
|
+
const deleteRolePermission = db.prepare("DELETE FROM role_permissions WHERE roleId = ? AND verb = ?");
|
|
159
|
+
const insertRolePermission = db.prepare("INSERT OR IGNORE INTO role_permissions (roleId, verb) VALUES (?, ?)");
|
|
160
|
+
const backfillDefaultRoleForZeroRoleUsers = db.prepare(`INSERT OR IGNORE INTO principal_roles (principalType, principalId, roleId)
|
|
161
|
+
SELECT 'user', u.id, ?
|
|
162
|
+
FROM users u
|
|
163
|
+
WHERE NOT EXISTS (
|
|
164
|
+
SELECT 1
|
|
165
|
+
FROM principal_roles pr
|
|
166
|
+
WHERE pr.principalType = 'user'
|
|
167
|
+
AND pr.principalId = u.id
|
|
168
|
+
)`);
|
|
169
|
+
const stats = {
|
|
170
|
+
rolesInserted: 0,
|
|
171
|
+
rolesUpdated: 0,
|
|
172
|
+
permissionsInserted: 0,
|
|
173
|
+
permissionsDeleted: 0,
|
|
174
|
+
usersBackfilled: 0
|
|
175
|
+
};
|
|
176
|
+
db.transaction(() => {
|
|
177
|
+
for (const role of BUILTIN_ROLES) {
|
|
178
|
+
const grantsAll = role.grantsAll ? 1 : 0;
|
|
179
|
+
const insertResult = insertRole.run(role.id, role.name, role.description, grantsAll);
|
|
180
|
+
stats.rolesInserted += insertResult.changes;
|
|
181
|
+
if (insertResult.changes === 0) {
|
|
182
|
+
const updateResult = updateRole.run(role.name, role.description, grantsAll, role.id, role.name, role.description, grantsAll);
|
|
183
|
+
stats.rolesUpdated += updateResult.changes;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
for (const role of BUILTIN_ROLES) {
|
|
187
|
+
const row = selectRoleById.get(role.id);
|
|
188
|
+
if (row) {
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
const colliding = selectRoleByName.get(role.name);
|
|
192
|
+
if (colliding) {
|
|
193
|
+
throw new Error(`RBAC built-in role "${role.name}" (${role.id}) is missing because role ` + `"${colliding.name}" (${colliding.id}) already uses that name. ` + "Rename or remove the conflicting role, then rerun `rbac bootstrap`.");
|
|
194
|
+
}
|
|
195
|
+
throw new Error(`RBAC built-in role "${role.name}" (${role.id}) is missing after seed sync. ` + "Rerun `rbac bootstrap`; if it still fails, inspect the roles table.");
|
|
196
|
+
}
|
|
197
|
+
for (const role of BUILTIN_ROLES) {
|
|
198
|
+
const desiredVerbs = desiredVerbsByRoleId.get(role.id) ?? new Set;
|
|
199
|
+
const existingRows = selectRolePermissions.all(role.id);
|
|
200
|
+
for (const row of existingRows) {
|
|
201
|
+
if (!desiredVerbs.has(row.verb)) {
|
|
202
|
+
stats.permissionsDeleted += deleteRolePermission.run(role.id, row.verb).changes;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
for (const verb of desiredVerbs) {
|
|
206
|
+
stats.permissionsInserted += insertRolePermission.run(role.id, verb).changes;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
db.run(CREATE_USER_DEFAULT_ROLE_TRIGGER_SQL);
|
|
210
|
+
stats.usersBackfilled += backfillDefaultRoleForZeroRoleUsers.run(DEFAULT_ROLE_ID).changes;
|
|
211
|
+
})();
|
|
212
|
+
if (!opts?.quiet) {
|
|
213
|
+
console.log(`[rbac] seed sync: roles inserted=${stats.rolesInserted}, updated=${stats.rolesUpdated}; permissions inserted=${stats.permissionsInserted}, deleted=${stats.permissionsDeleted}; trigger=ensured; users backfilled=${stats.usersBackfilled}`);
|
|
214
|
+
}
|
|
215
|
+
return stats;
|
|
216
|
+
}
|
|
217
|
+
function describeRbacFlag() {
|
|
218
|
+
const raw = process.env.RBAC_ENABLED;
|
|
219
|
+
if (raw === undefined)
|
|
220
|
+
return "unset (off)";
|
|
221
|
+
return `${raw} (${raw === "true" ? "on" : "off"})`;
|
|
222
|
+
}
|
|
223
|
+
function printRolesTable(rows) {
|
|
224
|
+
const tableRows = rows.map((row) => [
|
|
225
|
+
row.name,
|
|
226
|
+
row.isBuiltin === 1 ? "yes" : "no",
|
|
227
|
+
row.grantsAll === 1 ? "yes" : "no",
|
|
228
|
+
String(row.verbCount),
|
|
229
|
+
String(row.attachedUserCount)
|
|
230
|
+
]);
|
|
231
|
+
const headers = ["name", "builtin", "grantsAll", "verbs", "attachedUsers"];
|
|
232
|
+
const widths = headers.map((header, column) => Math.max(header.length, ...tableRows.map((row) => row[column]?.length ?? 0)));
|
|
233
|
+
const format = (row) => row.map((cell, column) => cell.padEnd(widths[column] ?? cell.length)).join(" ");
|
|
234
|
+
console.log("Roles:");
|
|
235
|
+
console.log(format(headers));
|
|
236
|
+
console.log(format(widths.map((width) => "-".repeat(width))));
|
|
237
|
+
for (const row of tableRows) {
|
|
238
|
+
console.log(format(row));
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
function getRbacRoleSummary() {
|
|
242
|
+
return getDb().prepare(`SELECT
|
|
243
|
+
r.name,
|
|
244
|
+
r.isBuiltin,
|
|
245
|
+
r.grantsAll,
|
|
246
|
+
COUNT(DISTINCT rp.verb) AS verbCount,
|
|
247
|
+
COUNT(DISTINCT CASE WHEN pr.principalType = 'user' THEN pr.principalId END)
|
|
248
|
+
AS attachedUserCount
|
|
249
|
+
FROM roles r
|
|
250
|
+
LEFT JOIN role_permissions rp ON rp.roleId = r.id
|
|
251
|
+
LEFT JOIN principal_roles pr ON pr.roleId = r.id
|
|
252
|
+
GROUP BY r.id, r.name, r.isBuiltin, r.grantsAll
|
|
253
|
+
ORDER BY r.isBuiltin DESC, r.name`).all();
|
|
254
|
+
}
|
|
255
|
+
async function runRbacCliCommand(args) {
|
|
256
|
+
const subcommand = args[0];
|
|
257
|
+
if (args.length !== 1 || subcommand !== "bootstrap") {
|
|
258
|
+
throw new Error(`Unknown RBAC command: ${args.join(" ") || "(none)"}`);
|
|
259
|
+
}
|
|
260
|
+
const stats = ensureRbacSeedsSynced({ quiet: true });
|
|
261
|
+
const roles = getRbacRoleSummary();
|
|
262
|
+
console.log("RBAC bootstrap complete");
|
|
263
|
+
console.log(`RBAC_ENABLED: ${describeRbacFlag()}`);
|
|
264
|
+
console.log(`Users backfilled this run: ${stats.usersBackfilled}`);
|
|
265
|
+
console.log("");
|
|
266
|
+
printRolesTable(roles);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export { DEFAULT_ROLE_ID, BUILTIN_ROLES, getUserGrant, attachRole, detachRole, listUserRoles, ensureRbacSeedsSynced, runRbacCliCommand };
|
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createAdditiveBuffer,
|
|
3
3
|
extractSlackMessageText
|
|
4
|
-
} from "./cli-
|
|
4
|
+
} from "./cli-tffvrh6j.js";
|
|
5
5
|
import {
|
|
6
6
|
getSlackApp,
|
|
7
7
|
registerTreeMessage
|
|
8
|
-
} from "./cli-
|
|
8
|
+
} from "./cli-pet3aev6.js";
|
|
9
|
+
import {
|
|
10
|
+
rewriteSlackMentions
|
|
11
|
+
} from "./cli-cchksf5q.js";
|
|
9
12
|
import {
|
|
10
13
|
buildBufferFlushBlocks
|
|
11
|
-
} from "./cli-
|
|
14
|
+
} from "./cli-ma04faw5.js";
|
|
12
15
|
import {
|
|
13
16
|
createTaskWithSiblingAwareness,
|
|
14
17
|
slackContextKey
|
|
15
|
-
} from "./cli-
|
|
18
|
+
} from "./cli-k3y4z3h5.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-15y7qhzp.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}`;
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getDb,
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
getKv,
|
|
4
|
+
incrKv,
|
|
5
|
+
init_db,
|
|
6
|
+
upsertKv
|
|
7
|
+
} from "./cli-15y7qhzp.js";
|
|
5
8
|
|
|
6
9
|
// src/be/users.ts
|
|
7
10
|
init_db();
|
|
@@ -51,6 +54,18 @@ function findUserByEmail(email) {
|
|
|
51
54
|
}
|
|
52
55
|
return null;
|
|
53
56
|
}
|
|
57
|
+
function findUsersByName(name) {
|
|
58
|
+
const trimmed = name.trim();
|
|
59
|
+
if (!trimmed)
|
|
60
|
+
return [];
|
|
61
|
+
const db = getDb();
|
|
62
|
+
const exact = db.prepare("SELECT * FROM users WHERE LOWER(name) = LOWER(?)").all(trimmed);
|
|
63
|
+
if (exact.length > 0)
|
|
64
|
+
return exact.map(rowToUser);
|
|
65
|
+
const firstToken = trimmed.split(/\s+/)[0] ?? trimmed;
|
|
66
|
+
const prefixed = db.prepare("SELECT * FROM users WHERE LOWER(name) LIKE LOWER(?) || '%'").all(firstToken);
|
|
67
|
+
return prefixed.map(rowToUser);
|
|
68
|
+
}
|
|
54
69
|
function getUserIdentities(userId) {
|
|
55
70
|
return getDb().prepare("SELECT kind, externalId FROM user_external_ids WHERE userId = ? ORDER BY kind, externalId").all(userId);
|
|
56
71
|
}
|
|
@@ -179,4 +194,76 @@ function fingerprintApiKey(rawKey) {
|
|
|
179
194
|
return `op:${sha256Hex(rawKey).slice(0, 16)}`;
|
|
180
195
|
}
|
|
181
196
|
|
|
182
|
-
|
|
197
|
+
// src/be/identity.ts
|
|
198
|
+
function resolveIdentity(kind, externalId) {
|
|
199
|
+
const user = findUserByExternalId(kind, externalId);
|
|
200
|
+
if (!user) {
|
|
201
|
+
return { status: "unknown", kind, externalId };
|
|
202
|
+
}
|
|
203
|
+
return {
|
|
204
|
+
status: "resolved",
|
|
205
|
+
kind,
|
|
206
|
+
externalId,
|
|
207
|
+
userId: user.id,
|
|
208
|
+
name: user.name,
|
|
209
|
+
email: user.email
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
function resolveIdentityByEmail(email) {
|
|
213
|
+
const user = findUserByEmail(email);
|
|
214
|
+
if (!user) {
|
|
215
|
+
return { status: "unknown", kind: "email", externalId: email };
|
|
216
|
+
}
|
|
217
|
+
return {
|
|
218
|
+
status: "resolved",
|
|
219
|
+
kind: "email",
|
|
220
|
+
externalId: email,
|
|
221
|
+
userId: user.id,
|
|
222
|
+
name: user.name,
|
|
223
|
+
email: user.email
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
function renderIdentity(resolution) {
|
|
227
|
+
const pair = `${resolution.kind}:${resolution.externalId}`;
|
|
228
|
+
if (resolution.status === "resolved") {
|
|
229
|
+
return `${resolution.name} (${pair})`;
|
|
230
|
+
}
|
|
231
|
+
return `${pair} (unknown user)`;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// src/be/unmapped-identities.ts
|
|
235
|
+
init_db();
|
|
236
|
+
var TTL_MS = 30 * 24 * 60 * 60 * 1000;
|
|
237
|
+
function namespace(kind) {
|
|
238
|
+
return `integration:unmapped:${kind}`;
|
|
239
|
+
}
|
|
240
|
+
function recordUnmappedIdentity(kind, externalId, meta) {
|
|
241
|
+
const ns = namespace(kind);
|
|
242
|
+
const now = new Date().toISOString();
|
|
243
|
+
const expiresAt = Date.now() + TTL_MS;
|
|
244
|
+
const countKey = `${externalId}:count`;
|
|
245
|
+
const countBefore = getKv(ns, countKey);
|
|
246
|
+
upsertKv({
|
|
247
|
+
namespace: ns,
|
|
248
|
+
key: `${externalId}:meta`,
|
|
249
|
+
value: {
|
|
250
|
+
lastSeenAt: now,
|
|
251
|
+
sampleEventType: meta.sampleEventType,
|
|
252
|
+
sampleContext: meta.sampleContext.slice(0, 100)
|
|
253
|
+
},
|
|
254
|
+
valueType: "json",
|
|
255
|
+
expiresAt
|
|
256
|
+
});
|
|
257
|
+
const incremented = incrKv(ns, countKey, 1);
|
|
258
|
+
if (countBefore === null) {
|
|
259
|
+
upsertKv({
|
|
260
|
+
namespace: ns,
|
|
261
|
+
key: countKey,
|
|
262
|
+
value: Number(incremented.value),
|
|
263
|
+
valueType: "integer",
|
|
264
|
+
expiresAt
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export { findUserById, findUserByExternalId, findUsersByName, getUserIdentities, listUserEvents, listUserTokens, recordIdentityEvent, findOrCreateUserByEmail, linkIdentity, unlinkIdentity, mintToken, revokeToken, resolveUserByToken, fingerprintApiKey, resolveIdentity, resolveIdentityByEmail, renderIdentity, recordUnmappedIdentity };
|