@capybara-hq/kernel 0.1.0-rc.11 → 0.1.0-rc.12
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/dist/agents/agents.schemas.d.ts +3 -0
- package/dist/agents/agents.service.js +11 -1
- package/dist/agents/definition.d.ts +1 -0
- package/dist/agents/definition.js +7 -3
- package/dist/agents/org-templates.js +4 -0
- package/dist/agents/pending-connectors.service.js +1 -1
- package/dist/catalog/component-scope.controller.d.ts +18 -6
- package/dist/catalog/component-scope.controller.js +36 -13
- package/dist/catalog/component-scope.service.d.ts +11 -0
- package/dist/catalog/component-scope.service.js +4 -0
- package/dist/catalog/component-state.d.ts +8 -0
- package/dist/catalog/component-state.js +19 -11
- package/dist/catalog/plugin-import.js +4 -1
- package/dist/conversations/conversations.service.d.ts +9 -0
- package/dist/conversations/conversations.service.js +9 -2
- package/dist/conversations/events.d.ts +1 -0
- package/dist/db/kernel-schema.d.ts +13 -0
- package/dist/errors.d.ts +2 -0
- package/dist/errors.js +2 -0
- package/dist/platform/exclusions.js +10 -0
- package/dist/platform/ops/agents.ops.js +19 -6
- package/dist/runtime/subagents/definition.d.ts +3 -2
- package/dist/runtime/subagents/definition.js +2 -1
- package/dist/tools/connector-account-policies.d.ts +51 -0
- package/dist/tools/connector-account-policies.js +165 -0
- package/dist/tools/connectors.controller.d.ts +35 -4
- package/dist/tools/connectors.controller.js +66 -7
- package/dist/tools/connectors.schemas.d.ts +11 -0
- package/dist/tools/connectors.schemas.js +11 -0
- package/dist/tools/connectors.service.d.ts +2 -0
- package/dist/tools/connectors.service.js +1 -0
- package/dist/tools/pipeline.d.ts +1 -1
- package/dist/tools/policy.d.ts +23 -1
- package/dist/tools/policy.js +99 -24
- package/dist/tools/tools.module.js +3 -0
- package/package.json +1 -1
- package/src/migrations/0104_connector_tool_account_policies.sql +16 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { KernelDb } from "../infra/database/kernel-db.js";
|
|
2
|
+
import { TransactionHost } from "../infra/database/transaction-host.js";
|
|
3
|
+
import { OrgLock } from "../orgs/org-lock.js";
|
|
4
|
+
import { type ToolPolicy } from "./registry.js";
|
|
5
|
+
/**
|
|
6
|
+
* Why a member cannot set a tool's policy: the org switched the tool off (or it is not offered:
|
|
7
|
+
* pending review, gone), or the org set its default to deny. Either way it stays denied.
|
|
8
|
+
*/
|
|
9
|
+
export type MemberPolicyLock = "disabled" | "org_deny";
|
|
10
|
+
/** One tool on the member's 权限 sheet (`GET …/connectors/:id/tool-policies/me`). */
|
|
11
|
+
export interface MemberToolPolicyView {
|
|
12
|
+
name: string;
|
|
13
|
+
key: string;
|
|
14
|
+
title: string | null;
|
|
15
|
+
description: string;
|
|
16
|
+
/** The org's default for the tool (`connector_tools.default_policy`): what applies without a setting. */
|
|
17
|
+
defaultPolicy: ToolPolicy;
|
|
18
|
+
/** What the tool's annotations alone give (`readOnlyHint: true` allows, anything else asks). */
|
|
19
|
+
annotationPolicy: "allow" | "ask";
|
|
20
|
+
/** The member's own setting, null without one. */
|
|
21
|
+
policy: ToolPolicy | null;
|
|
22
|
+
/** Locked by the org: the member's setting does not apply, the tool is denied. */
|
|
23
|
+
locked: MemberPolicyLock | null;
|
|
24
|
+
}
|
|
25
|
+
/** Whether a connector's credential is per member: its calls are made with the member's connection. */
|
|
26
|
+
export declare const personalAuth: (auth: unknown) => boolean;
|
|
27
|
+
/**
|
|
28
|
+
* A member's own tool policies for their own connection of a personal-credential connector
|
|
29
|
+
* (maintainer decision 2026-11-09, the connector card's 权限). They apply to every agent the
|
|
30
|
+
* member uses; an agent's tool policy overrides them; a tool the org switched off or set to deny
|
|
31
|
+
* stays denied (`tools/policy.ts` `connectorPolicy`). Only the account itself reads or writes its
|
|
32
|
+
* rows: the routes take the account from the session, never from the request.
|
|
33
|
+
*/
|
|
34
|
+
export declare class ConnectorAccountPolicies {
|
|
35
|
+
private readonly kdb;
|
|
36
|
+
private readonly orgLock;
|
|
37
|
+
readonly txHost: TransactionHost;
|
|
38
|
+
constructor(kdb: KernelDb, orgLock: OrgLock, txHost: TransactionHost);
|
|
39
|
+
/** The kernel-schema handle of the current context (its transaction, else the pool). */
|
|
40
|
+
private get db();
|
|
41
|
+
/** The connector's tools with the account's settings; not personal -> 409 `connector.not_personal`. */
|
|
42
|
+
read(orgId: string, connectorId: string, accountId: string): Promise<MemberToolPolicyView[]>;
|
|
43
|
+
/**
|
|
44
|
+
* Sets (a policy) or clears (null) the account's policies for some of the connector's tools;
|
|
45
|
+
* an unknown tool -> 404 `connector.tool_not_found`. Locked tools may hold a setting: it does not
|
|
46
|
+
* apply while the lock lasts. Audits `connector.member_policies_changed`.
|
|
47
|
+
*/
|
|
48
|
+
set(orgId: string, connectorId: string, accountId: string, changes: Record<string, ToolPolicy | null>, actorTokenId?: string): Promise<void>;
|
|
49
|
+
/** The org's live personal-credential connector, else 404 / 409. */
|
|
50
|
+
private connector;
|
|
51
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
8
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
9
|
+
};
|
|
10
|
+
import { connectorToolKey } from "@capybara-hq/plugin-sdk";
|
|
11
|
+
import { Inject, Injectable } from "@nestjs/common";
|
|
12
|
+
import { sql } from "kysely";
|
|
13
|
+
import { ApiError } from "../errors.js";
|
|
14
|
+
import { audit } from "../identity/audit.js";
|
|
15
|
+
import { KernelDb } from "../infra/database/kernel-db.js";
|
|
16
|
+
import { TransactionHost } from "../infra/database/transaction-host.js";
|
|
17
|
+
import { Transactional } from "../infra/database/transactional.decorator.js";
|
|
18
|
+
import { OrgLock } from "../orgs/org-lock.js";
|
|
19
|
+
import { policyFromAnnotations } from "./registry.js";
|
|
20
|
+
/** Whether a connector's credential is per member: its calls are made with the member's connection. */
|
|
21
|
+
export const personalAuth = (auth) => {
|
|
22
|
+
const a = auth;
|
|
23
|
+
return a !== null && a.type !== "none" && a.ownership === "personal";
|
|
24
|
+
};
|
|
25
|
+
const notPersonal = () => new ApiError("connector.not_personal", "This connector uses one credential for the whole organization; only a connector each member connects to with their own credential has per-member tool policies");
|
|
26
|
+
const notFound = () => new ApiError("connector.not_found", "Connector not found");
|
|
27
|
+
/**
|
|
28
|
+
* A member's own tool policies for their own connection of a personal-credential connector
|
|
29
|
+
* (maintainer decision 2026-11-09, the connector card's 权限). They apply to every agent the
|
|
30
|
+
* member uses; an agent's tool policy overrides them; a tool the org switched off or set to deny
|
|
31
|
+
* stays denied (`tools/policy.ts` `connectorPolicy`). Only the account itself reads or writes its
|
|
32
|
+
* rows: the routes take the account from the session, never from the request.
|
|
33
|
+
*/
|
|
34
|
+
let ConnectorAccountPolicies = class ConnectorAccountPolicies {
|
|
35
|
+
kdb;
|
|
36
|
+
orgLock;
|
|
37
|
+
txHost;
|
|
38
|
+
constructor(kdb, orgLock, txHost) {
|
|
39
|
+
this.kdb = kdb;
|
|
40
|
+
this.orgLock = orgLock;
|
|
41
|
+
this.txHost = txHost;
|
|
42
|
+
}
|
|
43
|
+
/** The kernel-schema handle of the current context (its transaction, else the pool). */
|
|
44
|
+
get db() {
|
|
45
|
+
return this.kdb.db;
|
|
46
|
+
}
|
|
47
|
+
/** The connector's tools with the account's settings; not personal -> 409 `connector.not_personal`. */
|
|
48
|
+
async read(orgId, connectorId, accountId) {
|
|
49
|
+
const c = await this.connector(orgId, connectorId);
|
|
50
|
+
const tools = await this.db
|
|
51
|
+
.selectFrom("connector_tools")
|
|
52
|
+
.select(["name", "spec", "status", "default_policy", "missing_since"])
|
|
53
|
+
.where("connector_id", "=", connectorId)
|
|
54
|
+
.orderBy("name")
|
|
55
|
+
.execute();
|
|
56
|
+
const mine = new Map((await this.db
|
|
57
|
+
.selectFrom("connector_tool_account_policies")
|
|
58
|
+
.select(["tool_name", "policy"])
|
|
59
|
+
.where("connector_id", "=", connectorId)
|
|
60
|
+
.where("account_id", "=", accountId)
|
|
61
|
+
.execute()).map((r) => [r.tool_name, r.policy]));
|
|
62
|
+
return tools.map((t) => {
|
|
63
|
+
const spec = t.spec;
|
|
64
|
+
const locked = t.status !== "enabled" || t.missing_since !== null
|
|
65
|
+
? "disabled"
|
|
66
|
+
: t.default_policy === "deny"
|
|
67
|
+
? "org_deny"
|
|
68
|
+
: null;
|
|
69
|
+
return {
|
|
70
|
+
name: t.name,
|
|
71
|
+
key: connectorToolKey(c.slug, t.name),
|
|
72
|
+
title: typeof spec.title === "string" ? spec.title : null,
|
|
73
|
+
description: spec.description,
|
|
74
|
+
defaultPolicy: t.default_policy,
|
|
75
|
+
annotationPolicy: policyFromAnnotations(spec.annotations),
|
|
76
|
+
policy: mine.get(t.name) ?? null,
|
|
77
|
+
locked,
|
|
78
|
+
};
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Sets (a policy) or clears (null) the account's policies for some of the connector's tools;
|
|
83
|
+
* an unknown tool -> 404 `connector.tool_not_found`. Locked tools may hold a setting: it does not
|
|
84
|
+
* apply while the lock lasts. Audits `connector.member_policies_changed`.
|
|
85
|
+
*/
|
|
86
|
+
async set(orgId, connectorId, accountId, changes, actorTokenId) {
|
|
87
|
+
await this.orgLock.lockAndAuthorize(orgId, accountId, "connectors.use");
|
|
88
|
+
await this.connector(orgId, connectorId);
|
|
89
|
+
const names = Object.keys(changes);
|
|
90
|
+
if (names.length === 0)
|
|
91
|
+
return;
|
|
92
|
+
const known = new Set((await this.db
|
|
93
|
+
.selectFrom("connector_tools")
|
|
94
|
+
.select("name")
|
|
95
|
+
.where("connector_id", "=", connectorId)
|
|
96
|
+
.where("name", "in", names)
|
|
97
|
+
.execute()).map((r) => r.name));
|
|
98
|
+
const unknown = names.find((n) => !known.has(n));
|
|
99
|
+
if (unknown !== undefined) {
|
|
100
|
+
throw new ApiError("connector.tool_not_found", `No tool ${unknown} in this connector`);
|
|
101
|
+
}
|
|
102
|
+
const cleared = names.filter((n) => changes[n] === null);
|
|
103
|
+
if (cleared.length > 0) {
|
|
104
|
+
await this.db
|
|
105
|
+
.deleteFrom("connector_tool_account_policies")
|
|
106
|
+
.where("connector_id", "=", connectorId)
|
|
107
|
+
.where("account_id", "=", accountId)
|
|
108
|
+
.where("tool_name", "in", cleared)
|
|
109
|
+
.execute();
|
|
110
|
+
}
|
|
111
|
+
const set = names.flatMap((n) => {
|
|
112
|
+
const p = changes[n];
|
|
113
|
+
return p === null || p === undefined ? [] : [{ name: n, policy: p }];
|
|
114
|
+
});
|
|
115
|
+
if (set.length > 0) {
|
|
116
|
+
await this.db
|
|
117
|
+
.insertInto("connector_tool_account_policies")
|
|
118
|
+
.values(set.map((x) => ({
|
|
119
|
+
org_id: orgId,
|
|
120
|
+
connector_id: connectorId,
|
|
121
|
+
account_id: accountId,
|
|
122
|
+
tool_name: x.name,
|
|
123
|
+
policy: x.policy,
|
|
124
|
+
})))
|
|
125
|
+
.onConflict((oc) => oc.columns(["connector_id", "account_id", "tool_name"]).doUpdateSet({
|
|
126
|
+
policy: (eb) => eb.ref("excluded.policy"),
|
|
127
|
+
updated_at: sql `now()`,
|
|
128
|
+
}))
|
|
129
|
+
.execute();
|
|
130
|
+
}
|
|
131
|
+
await audit(this.db, {
|
|
132
|
+
actorAccountId: accountId,
|
|
133
|
+
...(actorTokenId !== undefined && { actorTokenId }),
|
|
134
|
+
orgId,
|
|
135
|
+
action: "connector.member_policies_changed",
|
|
136
|
+
target: connectorId,
|
|
137
|
+
detail: { tools: changes },
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
/** The org's live personal-credential connector, else 404 / 409. */
|
|
141
|
+
async connector(orgId, connectorId) {
|
|
142
|
+
const c = await this.db
|
|
143
|
+
.selectFrom("connectors")
|
|
144
|
+
.select(["slug", "auth"])
|
|
145
|
+
.where("id", "=", connectorId)
|
|
146
|
+
.where("org_id", "=", orgId)
|
|
147
|
+
.where("deleted_at", "is", null)
|
|
148
|
+
.executeTakeFirst();
|
|
149
|
+
if (!c)
|
|
150
|
+
throw notFound();
|
|
151
|
+
if (!personalAuth(c.auth))
|
|
152
|
+
throw notPersonal();
|
|
153
|
+
return { slug: c.slug };
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
__decorate([
|
|
157
|
+
Transactional()
|
|
158
|
+
], ConnectorAccountPolicies.prototype, "set", null);
|
|
159
|
+
ConnectorAccountPolicies = __decorate([
|
|
160
|
+
Injectable(),
|
|
161
|
+
__param(0, Inject(KernelDb)),
|
|
162
|
+
__param(1, Inject(OrgLock)),
|
|
163
|
+
__param(2, Inject(TransactionHost))
|
|
164
|
+
], ConnectorAccountPolicies);
|
|
165
|
+
export { ConnectorAccountPolicies };
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { PluginIcons } from "../extensions/plugin-icons.js";
|
|
2
3
|
import { type Principal } from "../identity/principal.js";
|
|
3
4
|
import { type OrgContext } from "../orgs/org.guard.js";
|
|
4
|
-
import {
|
|
5
|
+
import { ConnectorAccountPolicies, type MemberToolPolicyView } from "./connector-account-policies.js";
|
|
6
|
+
import { CredentialValue, MemberToolPoliciesBody } from "./connectors.schemas.js";
|
|
5
7
|
import { type ConnectorDetail, type ConnectorPreview, type ConnectorSummary, ConnectorsService, type ToolIssue } from "./connectors.service.js";
|
|
6
8
|
declare const CreateBody: z.ZodObject<{
|
|
7
9
|
name: z.ZodString;
|
|
@@ -173,9 +175,19 @@ declare const UpdateBody: z.ZodObject<{
|
|
|
173
175
|
*/
|
|
174
176
|
export declare class ConnectorsController {
|
|
175
177
|
private readonly connectors;
|
|
176
|
-
|
|
178
|
+
private readonly icons;
|
|
179
|
+
private readonly memberPolicies;
|
|
180
|
+
constructor(connectors: ConnectorsService, icons: PluginIcons, memberPolicies: ConnectorAccountPolicies);
|
|
181
|
+
/**
|
|
182
|
+
* Each connector with `icon`: the icon of the plugin its template came from, on the org's catalog
|
|
183
|
+
* route (null for a custom connector, a plugin without an icon, or one the reader's catalog does
|
|
184
|
+
* not show: the share gate holds; maintainer decision 2026-11-09).
|
|
185
|
+
*/
|
|
186
|
+
private withIcons;
|
|
177
187
|
list(org: OrgContext): Promise<{
|
|
178
|
-
connectors: ConnectorSummary
|
|
188
|
+
connectors: (ConnectorSummary & {
|
|
189
|
+
icon: string | null;
|
|
190
|
+
})[];
|
|
179
191
|
}>;
|
|
180
192
|
/**
|
|
181
193
|
* The connector kinds the org can create connectors of, each with the plugin providing it (the
|
|
@@ -197,7 +209,9 @@ export declare class ConnectorsController {
|
|
|
197
209
|
*/
|
|
198
210
|
preview(principal: Principal, org: OrgContext, body: z.infer<typeof PreviewBody>): Promise<ConnectorPreview>;
|
|
199
211
|
get(principal: Principal, org: OrgContext, connectorId: string): Promise<{
|
|
200
|
-
connector: ConnectorDetail
|
|
212
|
+
connector: ConnectorDetail & {
|
|
213
|
+
icon: string | null;
|
|
214
|
+
};
|
|
201
215
|
}>;
|
|
202
216
|
update(principal: Principal, org: OrgContext, connectorId: string, body: z.infer<typeof UpdateBody>): Promise<{
|
|
203
217
|
connector: ConnectorDetail;
|
|
@@ -218,6 +232,23 @@ export declare class ConnectorsController {
|
|
|
218
232
|
}>;
|
|
219
233
|
setOrgCredential(principal: Principal, org: OrgContext, connectorId: string, body: z.infer<typeof CredentialValue>): Promise<void>;
|
|
220
234
|
setMyCredential(principal: Principal, org: OrgContext, connectorId: string, body: z.infer<typeof CredentialValue>): Promise<void>;
|
|
235
|
+
/**
|
|
236
|
+
* The caller's own tool policies for their own connection of a personal-credential connector
|
|
237
|
+
* (the card's 权限, decision 2026-11-09): each tool with the org default, the annotation default,
|
|
238
|
+
* the caller's setting and whether the org locked it. Only ever the caller's rows. A connector
|
|
239
|
+
* with one org credential -> 409 `connector.not_personal`.
|
|
240
|
+
*/
|
|
241
|
+
myToolPolicies(principal: Principal, org: OrgContext, connectorId: string): Promise<{
|
|
242
|
+
tools: MemberToolPolicyView[];
|
|
243
|
+
}>;
|
|
244
|
+
/**
|
|
245
|
+
* Sets (`allow` / `ask` / `deny`) or clears (null) the caller's own policies for some tools:
|
|
246
|
+
* `{ tools: { <name>: policy | null } }` -> the sheet again. Unknown tool -> 404
|
|
247
|
+
* `connector.tool_not_found`.
|
|
248
|
+
*/
|
|
249
|
+
setMyToolPolicies(principal: Principal, org: OrgContext, connectorId: string, body: z.infer<typeof MemberToolPoliciesBody>): Promise<{
|
|
250
|
+
tools: MemberToolPolicyView[];
|
|
251
|
+
}>;
|
|
221
252
|
clearMyCredential(principal: Principal, org: OrgContext, connectorId: string): Promise<void>;
|
|
222
253
|
}
|
|
223
254
|
export {};
|
|
@@ -10,12 +10,14 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
|
10
10
|
import { ConnectorAuthSchema } from "@capybara-hq/plugin-sdk";
|
|
11
11
|
import { Controller, Delete, Get, HttpCode, Inject, Param, Patch, Post, Put } from "@nestjs/common";
|
|
12
12
|
import { z } from "zod";
|
|
13
|
+
import { orgIconScope, PluginIcons } from "../extensions/plugin-icons.js";
|
|
13
14
|
import { CurrentPrincipal, patTokenId, requireUser, } from "../identity/principal.js";
|
|
14
15
|
import { createZodDto } from "../infra/validation/create-zod-dto.js";
|
|
15
16
|
import { ZBody } from "../infra/validation/zod-params.js";
|
|
16
17
|
import { CurrentOrg, OrgAction } from "../orgs/org.guard.js";
|
|
17
18
|
import { can } from "../orgs/roles.js";
|
|
18
|
-
import {
|
|
19
|
+
import { ConnectorAccountPolicies, } from "./connector-account-policies.js";
|
|
20
|
+
import { CredentialValue, MemberToolPoliciesBody } from "./connectors.schemas.js";
|
|
19
21
|
import { ConnectorsService, } from "./connectors.service.js";
|
|
20
22
|
import { publicEndpoint } from "./endpoint.js";
|
|
21
23
|
/**
|
|
@@ -84,17 +86,37 @@ class ConnectorToolReviewBody extends createZodDto(ReviewBody, "ConnectorToolRev
|
|
|
84
86
|
}
|
|
85
87
|
class ConnectorCredentialBody extends createZodDto(CredentialValue, "ConnectorCredentialBody") {
|
|
86
88
|
}
|
|
89
|
+
class MemberToolPoliciesDto extends createZodDto(MemberToolPoliciesBody, "MemberToolPoliciesBody") {
|
|
90
|
+
}
|
|
87
91
|
/**
|
|
88
92
|
* 连接器(`/api/v1/orgs/:orgId/connectors`):读与个人凭据用 `connectors.use`(访客起),安装 / 修改 / 删除 / 刷新 /
|
|
89
93
|
* 组织凭据用 `connectors.manage`(管理员起);两者都仅限用户主体。
|
|
90
94
|
*/
|
|
91
95
|
let ConnectorsController = class ConnectorsController {
|
|
92
96
|
connectors;
|
|
93
|
-
|
|
97
|
+
icons;
|
|
98
|
+
memberPolicies;
|
|
99
|
+
constructor(connectors, icons, memberPolicies) {
|
|
94
100
|
this.connectors = connectors;
|
|
101
|
+
this.icons = icons;
|
|
102
|
+
this.memberPolicies = memberPolicies;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Each connector with `icon`: the icon of the plugin its template came from, on the org's catalog
|
|
106
|
+
* route (null for a custom connector, a plugin without an icon, or one the reader's catalog does
|
|
107
|
+
* not show: the share gate holds; maintainer decision 2026-11-09).
|
|
108
|
+
*/
|
|
109
|
+
async withIcons(org, rows) {
|
|
110
|
+
const ids = rows.flatMap((r) => (r.catalogItemId === null ? [] : [r.catalogItemId]));
|
|
111
|
+
const urls = await this.icons.ofItems(ids, orgIconScope(org));
|
|
112
|
+
return rows.map((r) => ({
|
|
113
|
+
...r,
|
|
114
|
+
icon: r.catalogItemId === null ? null : (urls.get(r.catalogItemId) ?? null),
|
|
115
|
+
}));
|
|
95
116
|
}
|
|
96
117
|
async list(org) {
|
|
97
|
-
|
|
118
|
+
const rows = (await this.connectors.list(org.orgId)).map((a) => forViewer(org, a));
|
|
119
|
+
return { connectors: await this.withIcons(org, rows) };
|
|
98
120
|
}
|
|
99
121
|
/**
|
|
100
122
|
* The connector kinds the org can create connectors of, each with the plugin providing it (the
|
|
@@ -117,9 +139,9 @@ let ConnectorsController = class ConnectorsController {
|
|
|
117
139
|
}
|
|
118
140
|
async get(principal, org, connectorId) {
|
|
119
141
|
const p = requireUser(principal);
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
};
|
|
142
|
+
const c = forViewer(org, await this.connectors.get(org.orgId, connectorId, p.account.id));
|
|
143
|
+
const [connector] = await this.withIcons(org, [c]);
|
|
144
|
+
return { connector: connector };
|
|
123
145
|
}
|
|
124
146
|
async update(principal, org, connectorId, body) {
|
|
125
147
|
const p = requireUser(principal);
|
|
@@ -150,6 +172,26 @@ let ConnectorsController = class ConnectorsController {
|
|
|
150
172
|
const p = requireUser(principal);
|
|
151
173
|
await this.connectors.setCredential(org.orgId, connectorId, p.account.id, "personal", body, patTokenId(p));
|
|
152
174
|
}
|
|
175
|
+
/**
|
|
176
|
+
* The caller's own tool policies for their own connection of a personal-credential connector
|
|
177
|
+
* (the card's 权限, decision 2026-11-09): each tool with the org default, the annotation default,
|
|
178
|
+
* the caller's setting and whether the org locked it. Only ever the caller's rows. A connector
|
|
179
|
+
* with one org credential -> 409 `connector.not_personal`.
|
|
180
|
+
*/
|
|
181
|
+
async myToolPolicies(principal, org, connectorId) {
|
|
182
|
+
const p = requireUser(principal);
|
|
183
|
+
return { tools: await this.memberPolicies.read(org.orgId, connectorId, p.account.id) };
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Sets (`allow` / `ask` / `deny`) or clears (null) the caller's own policies for some tools:
|
|
187
|
+
* `{ tools: { <name>: policy | null } }` -> the sheet again. Unknown tool -> 404
|
|
188
|
+
* `connector.tool_not_found`.
|
|
189
|
+
*/
|
|
190
|
+
async setMyToolPolicies(principal, org, connectorId, body) {
|
|
191
|
+
const p = requireUser(principal);
|
|
192
|
+
await this.memberPolicies.set(org.orgId, connectorId, p.account.id, body.tools, patTokenId(p));
|
|
193
|
+
return { tools: await this.memberPolicies.read(org.orgId, connectorId, p.account.id) };
|
|
194
|
+
}
|
|
153
195
|
async clearMyCredential(principal, org, connectorId) {
|
|
154
196
|
const p = requireUser(principal);
|
|
155
197
|
await this.connectors.clearMyCredential(org.orgId, connectorId, p.account.id, patTokenId(p));
|
|
@@ -239,6 +281,21 @@ __decorate([
|
|
|
239
281
|
__param(2, Param("connectorId")),
|
|
240
282
|
__param(3, ZBody(ConnectorCredentialBody))
|
|
241
283
|
], ConnectorsController.prototype, "setMyCredential", null);
|
|
284
|
+
__decorate([
|
|
285
|
+
Get(":connectorId/tool-policies/me"),
|
|
286
|
+
OrgAction("connectors.use"),
|
|
287
|
+
__param(0, CurrentPrincipal()),
|
|
288
|
+
__param(1, CurrentOrg()),
|
|
289
|
+
__param(2, Param("connectorId"))
|
|
290
|
+
], ConnectorsController.prototype, "myToolPolicies", null);
|
|
291
|
+
__decorate([
|
|
292
|
+
Put(":connectorId/tool-policies/me"),
|
|
293
|
+
OrgAction("connectors.use"),
|
|
294
|
+
__param(0, CurrentPrincipal()),
|
|
295
|
+
__param(1, CurrentOrg()),
|
|
296
|
+
__param(2, Param("connectorId")),
|
|
297
|
+
__param(3, ZBody(MemberToolPoliciesDto))
|
|
298
|
+
], ConnectorsController.prototype, "setMyToolPolicies", null);
|
|
242
299
|
__decorate([
|
|
243
300
|
Delete(":connectorId/credentials/me"),
|
|
244
301
|
HttpCode(204),
|
|
@@ -249,6 +306,8 @@ __decorate([
|
|
|
249
306
|
], ConnectorsController.prototype, "clearMyCredential", null);
|
|
250
307
|
ConnectorsController = __decorate([
|
|
251
308
|
Controller("api/v1/orgs/:orgId/connectors"),
|
|
252
|
-
__param(0, Inject(ConnectorsService))
|
|
309
|
+
__param(0, Inject(ConnectorsService)),
|
|
310
|
+
__param(1, Inject(PluginIcons)),
|
|
311
|
+
__param(2, Inject(ConnectorAccountPolicies))
|
|
253
312
|
], ConnectorsController);
|
|
254
313
|
export { ConnectorsController };
|
|
@@ -10,3 +10,14 @@ export declare const ConnectorPathParams: z.ZodObject<{
|
|
|
10
10
|
connectorId: z.ZodString;
|
|
11
11
|
}, z.core.$strip>;
|
|
12
12
|
export declare const CONNECTOR_ROUTES: readonly RouteSchemas[];
|
|
13
|
+
/**
|
|
14
|
+
* `PUT …/connectors/:connectorId/tool-policies/me`: the caller's own policy per tool name (null
|
|
15
|
+
* clears it; decision 2026-11-09).
|
|
16
|
+
*/
|
|
17
|
+
export declare const MemberToolPoliciesBody: z.ZodObject<{
|
|
18
|
+
tools: z.ZodRecord<z.ZodString, z.ZodNullable<z.ZodEnum<{
|
|
19
|
+
allow: "allow";
|
|
20
|
+
ask: "ask";
|
|
21
|
+
deny: "deny";
|
|
22
|
+
}>>>;
|
|
23
|
+
}, z.core.$strict>;
|
|
@@ -17,3 +17,14 @@ export const CONNECTOR_ROUTES = [
|
|
|
17
17
|
params: ConnectorPathParams,
|
|
18
18
|
},
|
|
19
19
|
];
|
|
20
|
+
/**
|
|
21
|
+
* `PUT …/connectors/:connectorId/tool-policies/me`: the caller's own policy per tool name (null
|
|
22
|
+
* clears it; decision 2026-11-09).
|
|
23
|
+
*/
|
|
24
|
+
export const MemberToolPoliciesBody = z
|
|
25
|
+
.object({
|
|
26
|
+
tools: z
|
|
27
|
+
.record(z.string().min(1).max(200), z.enum(["allow", "ask", "deny"]).nullable())
|
|
28
|
+
.refine((v) => Object.keys(v).length <= 1000, { message: "Too many tools" }),
|
|
29
|
+
})
|
|
30
|
+
.strict();
|
|
@@ -66,6 +66,8 @@ export interface ConnectorSummary {
|
|
|
66
66
|
* a custom one: the web offers it for a pending connector of that template (ruling P4-7).
|
|
67
67
|
*/
|
|
68
68
|
platformTemplateId: string | null;
|
|
69
|
+
/** The plugin (catalog item) its template came from (`connectors.catalog_item_id`), else null. */
|
|
70
|
+
catalogItemId: string | null;
|
|
69
71
|
createdBy: string;
|
|
70
72
|
createdAt: Date;
|
|
71
73
|
updatedAt: Date;
|
|
@@ -760,6 +760,7 @@ let ConnectorsService = class ConnectorsService {
|
|
|
760
760
|
auth: r.auth,
|
|
761
761
|
status: r.status,
|
|
762
762
|
platformTemplateId: r.platform_template_id ?? null,
|
|
763
|
+
catalogItemId: r.catalog_item_id ?? null,
|
|
763
764
|
createdBy: r.created_by,
|
|
764
765
|
createdAt: r.created_at,
|
|
765
766
|
updatedAt: r.updated_at,
|
package/dist/tools/pipeline.d.ts
CHANGED
|
@@ -65,7 +65,7 @@ export interface AskRequest {
|
|
|
65
65
|
* policy, a fixed ask (R3, `requiresUserInteraction`), the org's `sandboxToolPolicy`, or the tool's
|
|
66
66
|
* own default. `org` and `fixed` were added after the first B events (older logs say `default`).
|
|
67
67
|
*/
|
|
68
|
-
export type ApprovalPolicySource = "agent" | "connector" | "default" | "role" | "org" | "fixed";
|
|
68
|
+
export type ApprovalPolicySource = "agent" | "connector" | "member" | "default" | "role" | "org" | "fixed";
|
|
69
69
|
/**
|
|
70
70
|
* `unavailable`:调用那一刻工具已不可用(连接器 / 工具不存在、停用、删除、待审核),值为事实原因;
|
|
71
71
|
* 管线写「工具不可用」说明,而不是「策略拒绝」。
|
package/dist/tools/policy.d.ts
CHANGED
|
@@ -25,6 +25,28 @@ export declare function decide(input: PolicyInput): ToolPolicy;
|
|
|
25
25
|
* 除非 agent 当前版本的 `skills.writePolicy` 为 `ask`(`skillWritePolicy`);不知道可见性时按共享处理(更严)。
|
|
26
26
|
*/
|
|
27
27
|
export declare function isFixedAsk(tool: ResolvedTool, visibility?: "private" | "org" | "members" | undefined, skillWritePolicy?: "allow" | "ask" | undefined): boolean;
|
|
28
|
+
/** Which level decided a connector tool's policy ({@link connectorPolicy}). */
|
|
29
|
+
export type ConnectorPolicySource = "org" | "member" | "agent";
|
|
30
|
+
/**
|
|
31
|
+
* A personal-credential connector tool's policy for one call (maintainer decisions 2026-11-09;
|
|
32
|
+
* an org-credential connector's tool keeps the org default with the agent's tightening only),
|
|
33
|
+
* highest first:
|
|
34
|
+
* 1. the org's default `deny` (a tool the org switched off never gets here: it is unavailable);
|
|
35
|
+
* 2. the member's own `deny` for calls with their connection (`member`): a floor no agent lifts;
|
|
36
|
+
* 3. the agent's setting (`agent`, from its tools page): `allow` / `ask` / `deny`;
|
|
37
|
+
* 4. the member's own `allow` / `ask`;
|
|
38
|
+
* 5. the org's default (`connector_tools.default_policy`, the annotation default unless an org
|
|
39
|
+
* admin changed it: `readOnlyHint: true` allows, anything else asks).
|
|
40
|
+
* Fixed asks (R3, `requiresUserInteraction`), role escalation and grants apply after this.
|
|
41
|
+
*/
|
|
42
|
+
export declare function connectorPolicy(input: {
|
|
43
|
+
orgDefault: ToolPolicy;
|
|
44
|
+
member?: ToolPolicy | undefined;
|
|
45
|
+
agent?: ToolPolicy | undefined;
|
|
46
|
+
}): {
|
|
47
|
+
policy: ToolPolicy;
|
|
48
|
+
source: ConnectorPolicySource;
|
|
49
|
+
};
|
|
28
50
|
/**
|
|
29
51
|
* 执行时的策略判定(spec 3.6 R5:在动作执行那一刻判定)。每次调用都实时读取:
|
|
30
52
|
* - 沙箱工具(bash / write / edit / deliver)的组织设置 `sandboxToolPolicy`(为 ask 时组织默认至少 ask);
|
|
@@ -36,7 +58,7 @@ export declare function isFixedAsk(tool: ResolvedTool, visibility?: "private" |
|
|
|
36
58
|
* parent's current version still tightens it (sub-agents spec §5.3, §9); its "this conversation"
|
|
37
59
|
* grants are the root conversation's (agent map spec §7); an org agent sub-agent (§5.5): `ctx.agentId` is the
|
|
38
60
|
* dispatched agent (its current version, its "always" grants) and the parent agent's current
|
|
39
|
-
* policies apply on top ({@link
|
|
61
|
+
* policies apply on top ({@link parentAgentSetting});
|
|
40
62
|
* - 授权表(`conversation_tool_grants` / `tool_grants`),`spec_hash` 与当前定义不一致视为无效(D8),
|
|
41
63
|
* `connector_id` 与工具的来源连接器不一致也无效(删除后用同一 slug 重新安装的连接器不继承旧授权);
|
|
42
64
|
* - 工具声明了 `requiredRole`(R1)时,发起人在组织中的当前角色:不是成员 → 拒绝;角色不足 → 按角色升级的审批
|