@capybara-hq/kernel 0.1.0-rc.10 → 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.
Files changed (106) hide show
  1. package/dist/admin/admin.schemas.d.ts +3 -0
  2. package/dist/agents/agents.schemas.d.ts +3 -0
  3. package/dist/agents/agents.service.js +11 -1
  4. package/dist/agents/definition.d.ts +1 -0
  5. package/dist/agents/definition.js +7 -3
  6. package/dist/agents/org-templates.js +4 -0
  7. package/dist/agents/pending-connectors.service.js +1 -1
  8. package/dist/catalog/catalog-content.d.ts +33 -1
  9. package/dist/catalog/catalog-content.js +65 -2
  10. package/dist/catalog/catalog-items.js +1 -0
  11. package/dist/catalog/catalog-scope.service.d.ts +29 -3
  12. package/dist/catalog/catalog-scope.service.js +82 -10
  13. package/dist/catalog/catalog.service.d.ts +7 -5
  14. package/dist/catalog/catalog.service.js +26 -5
  15. package/dist/catalog/claude/translate.js +3 -0
  16. package/dist/catalog/component-scope.controller.d.ts +18 -6
  17. package/dist/catalog/component-scope.controller.js +36 -13
  18. package/dist/catalog/component-scope.schemas.d.ts +4 -0
  19. package/dist/catalog/component-scope.service.d.ts +59 -9
  20. package/dist/catalog/component-scope.service.js +156 -27
  21. package/dist/catalog/component-state.d.ts +20 -3
  22. package/dist/catalog/component-state.js +40 -14
  23. package/dist/catalog/marketplaces.service.d.ts +1 -1
  24. package/dist/catalog/org-catalog.service.js +3 -0
  25. package/dist/catalog/owned-items.js +7 -0
  26. package/dist/catalog/platform-components.d.ts +23 -3
  27. package/dist/catalog/platform-components.js +118 -9
  28. package/dist/catalog/platform-migration.js +2 -1
  29. package/dist/catalog/plugin-import.d.ts +24 -4
  30. package/dist/catalog/plugin-import.js +41 -7
  31. package/dist/catalog/plugin-install.d.ts +20 -1
  32. package/dist/catalog/plugin-install.js +71 -1
  33. package/dist/catalog/plugin-package.d.ts +41 -4
  34. package/dist/catalog/plugin-package.js +127 -7
  35. package/dist/catalog/plugin-upgrade.d.ts +13 -2
  36. package/dist/catalog/plugin-upgrade.js +124 -6
  37. package/dist/catalog/unshare.js +1 -0
  38. package/dist/conversations/conversations.service.d.ts +9 -0
  39. package/dist/conversations/conversations.service.js +9 -2
  40. package/dist/conversations/events.d.ts +1 -0
  41. package/dist/db/kernel-schema.d.ts +54 -4
  42. package/dist/errors.d.ts +27 -0
  43. package/dist/errors.js +27 -0
  44. package/dist/http/api.module.js +7 -0
  45. package/dist/identity/accounts.d.ts +11 -4
  46. package/dist/identity/accounts.js +23 -10
  47. package/dist/identity/me.controller.d.ts +10 -10
  48. package/dist/identity/me.controller.js +8 -6
  49. package/dist/identity/me.schemas.d.ts +13 -3
  50. package/dist/identity/me.schemas.js +10 -2
  51. package/dist/identity/settings.d.ts +27 -0
  52. package/dist/identity/settings.js +64 -0
  53. package/dist/orgs/settings.service.d.ts +31 -0
  54. package/dist/orgs/settings.service.js +31 -1
  55. package/dist/platform/exclusions.js +26 -0
  56. package/dist/platform/operation.js +6 -2
  57. package/dist/platform/ops/admin.ops.d.ts +3 -1
  58. package/dist/platform/ops/admin.ops.js +25 -7
  59. package/dist/platform/ops/agents.ops.js +19 -6
  60. package/dist/platform/ops/components.ops.js +1 -0
  61. package/dist/platform/ops/org-settings.ops.d.ts +3 -1
  62. package/dist/platform/ops/org-settings.ops.js +33 -8
  63. package/dist/platform/ops/orgs.ops.js +1 -0
  64. package/dist/platform/ops/platform.ops.d.ts +3 -1
  65. package/dist/platform/ops/platform.ops.js +43 -16
  66. package/dist/platform/ops/themes.ops.d.ts +14 -0
  67. package/dist/platform/ops/themes.ops.js +47 -0
  68. package/dist/platform/platform-ops.module.d.ts +2 -1
  69. package/dist/platform/platform-ops.module.js +5 -0
  70. package/dist/platform/route-schemas.js +3 -0
  71. package/dist/runtime/subagents/definition.d.ts +3 -2
  72. package/dist/runtime/subagents/definition.js +2 -1
  73. package/dist/themes/render-css.d.ts +12 -0
  74. package/dist/themes/render-css.js +132 -0
  75. package/dist/themes/theme-delivery.service.d.ts +34 -0
  76. package/dist/themes/theme-delivery.service.js +111 -0
  77. package/dist/themes/theme-state.d.ts +95 -0
  78. package/dist/themes/theme-state.js +203 -0
  79. package/dist/themes/theme-usage.d.ts +61 -0
  80. package/dist/themes/theme-usage.js +98 -0
  81. package/dist/themes/themes-public.controller.d.ts +28 -0
  82. package/dist/themes/themes-public.controller.js +92 -0
  83. package/dist/themes/themes.controller.d.ts +13 -0
  84. package/dist/themes/themes.controller.js +38 -0
  85. package/dist/themes/themes.module.d.ts +3 -0
  86. package/dist/themes/themes.module.js +21 -0
  87. package/dist/themes/themes.schemas.d.ts +3 -0
  88. package/dist/themes/themes.schemas.js +2 -0
  89. package/dist/themes/themes.service.d.ts +50 -0
  90. package/dist/themes/themes.service.js +99 -0
  91. package/dist/tools/connector-account-policies.d.ts +51 -0
  92. package/dist/tools/connector-account-policies.js +165 -0
  93. package/dist/tools/connectors.controller.d.ts +35 -4
  94. package/dist/tools/connectors.controller.js +66 -7
  95. package/dist/tools/connectors.schemas.d.ts +11 -0
  96. package/dist/tools/connectors.schemas.js +11 -0
  97. package/dist/tools/connectors.service.d.ts +2 -0
  98. package/dist/tools/connectors.service.js +1 -0
  99. package/dist/tools/pipeline.d.ts +1 -1
  100. package/dist/tools/policy.d.ts +23 -1
  101. package/dist/tools/policy.js +99 -24
  102. package/dist/tools/tools.module.js +3 -0
  103. package/package.json +3 -3
  104. package/src/migrations/0102_themes.sql +64 -0
  105. package/src/migrations/0103_ui_theme_setting.sql +7 -0
  106. package/src/migrations/0104_connector_tool_account_policies.sql +16 -0
@@ -0,0 +1,99 @@
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 { Inject, Injectable } from "@nestjs/common";
11
+ import { readUiTheme } from "../identity/settings.js";
12
+ import { KernelDb } from "../infra/database/kernel-db.js";
13
+ import { DEFAULT_ORG_THEME, readOrgSettings } from "../orgs/settings.service.js";
14
+ import { eligiblePlatformDefault, themeDefinitions, themeOnForAccount, themeOnIn, themeRefOf, themeSwatches, themesOnIn, } from "./theme-state.js";
15
+ /**
16
+ * Which theme applies (themes spec §7) and the org's theme listing. Read-only; the levels are
17
+ * written by their settings services (deployment, org, person), which check availability with the
18
+ * same predicates (`theme-state.ts`).
19
+ */
20
+ let ThemesService = class ThemesService {
21
+ db;
22
+ constructor(db) {
23
+ this.db = db;
24
+ }
25
+ /** The themes on in the org with their references and swatches, by English name then id. */
26
+ async listForOrg(orgId) {
27
+ const on = await themesOnIn(this.db.db, orgId);
28
+ const defs = await themeDefinitions(this.db.db, on.map((c) => c.id));
29
+ const out = [];
30
+ for (const c of on) {
31
+ const d = defs.get(c.id);
32
+ if (!d)
33
+ continue;
34
+ out.push({
35
+ ...themeRefOf(c.id, d.version, d.definition),
36
+ swatches: themeSwatches(d.definition),
37
+ });
38
+ }
39
+ return out.sort((a, b) => a.name.en < b.name.en ? -1 : a.name.en > b.name.en ? 1 : a.id < b.id ? -1 : 1);
40
+ }
41
+ /**
42
+ * The theme of a signed-in page in the org (spec §7): the person's pick if it is on in the org
43
+ * and the org does not lock; else the org's default if on; else the platform default if on in
44
+ * the org; else null (the built-in theme). An unavailable choice is skipped, never an error.
45
+ * `accountId` null (a service token): no personal pick.
46
+ */
47
+ async resolve(orgId, accountId) {
48
+ return (await this.forOrg(orgId, accountId)).resolved;
49
+ }
50
+ /** The platform default for pages before sign-in, when it is still eligible (spec §7); else null. */
51
+ async platformDefault() {
52
+ const { id } = await readUiTheme(this.db.db);
53
+ if (id === null || (await eligiblePlatformDefault(this.db.db, id)) === null)
54
+ return null;
55
+ const d = (await themeDefinitions(this.db.db, [id])).get(id);
56
+ return d ? themeRefOf(id, d.version, d.definition) : null;
57
+ }
58
+ /**
59
+ * The listing, the viewer's resolved theme and the org's setting. Separate reads, not one
60
+ * snapshot: a change committed between them shows at the next read.
61
+ */
62
+ async forOrg(orgId, accountId) {
63
+ const themes = await this.listForOrg(orgId);
64
+ const org = (await readOrgSettings(this.db.db, orgId)).theme ?? DEFAULT_ORG_THEME;
65
+ const pick = accountId === null || org.personal === "locked"
66
+ ? null
67
+ : ((await this.db
68
+ .selectFrom("accounts")
69
+ .select("theme")
70
+ .where("id", "=", accountId)
71
+ .executeTakeFirst())?.theme ?? null);
72
+ const platform = (await readUiTheme(this.db.db)).id;
73
+ const on = (id) => (id === null ? undefined : themes.find((t) => t.id === id));
74
+ const hit = on(pick) ?? on(org.id) ?? on(platform);
75
+ let resolved = null;
76
+ if (hit) {
77
+ const { swatches: _swatches, ...ref } = hit;
78
+ resolved = ref;
79
+ }
80
+ return { themes, resolved, org: { id: org.id, personal: org.personal } };
81
+ }
82
+ /** A live theme's name (the super admin's summaries); null when there is no such theme. */
83
+ async nameOf(id) {
84
+ return (await themeDefinitions(this.db.db, [id])).get(id)?.definition.name ?? null;
85
+ }
86
+ /** Theme `id`'s name if it is on in the org, else null (a summary never names another org's theme). */
87
+ async nameIn(orgId, id) {
88
+ return (await themeOnIn(this.db.db, orgId, id)) ? this.nameOf(id) : null;
89
+ }
90
+ /** Theme `id`'s name if it is on in one of the person's orgs, else null (same reason). */
91
+ async nameForAccount(accountId, id) {
92
+ return (await themeOnForAccount(this.db.db, accountId, id)) ? this.nameOf(id) : null;
93
+ }
94
+ };
95
+ ThemesService = __decorate([
96
+ Injectable(),
97
+ __param(0, Inject(KernelDb))
98
+ ], ThemesService);
99
+ export { ThemesService };
@@ -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 { CredentialValue } from "./connectors.schemas.js";
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
- constructor(connectors: ConnectorsService);
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 { CredentialValue } from "./connectors.schemas.js";
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
- constructor(connectors) {
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
- return { connectors: (await this.connectors.list(org.orgId)).map((a) => forViewer(org, a)) };
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
- return {
121
- connector: forViewer(org, await this.connectors.get(org.orgId, connectorId, p.account.id)),
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,
@@ -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
  * 管线写「工具不可用」说明,而不是「策略拒绝」。
@@ -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 parentAgentOverride});
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)时,发起人在组织中的当前角色:不是成员 → 拒绝;角色不足 → 按角色升级的审批