@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.
Files changed (37) hide show
  1. package/dist/agents/agents.schemas.d.ts +3 -0
  2. package/dist/agents/agents.service.js +11 -1
  3. package/dist/agents/definition.d.ts +1 -0
  4. package/dist/agents/definition.js +7 -3
  5. package/dist/agents/org-templates.js +4 -0
  6. package/dist/agents/pending-connectors.service.js +1 -1
  7. package/dist/catalog/component-scope.controller.d.ts +18 -6
  8. package/dist/catalog/component-scope.controller.js +36 -13
  9. package/dist/catalog/component-scope.service.d.ts +11 -0
  10. package/dist/catalog/component-scope.service.js +4 -0
  11. package/dist/catalog/component-state.d.ts +8 -0
  12. package/dist/catalog/component-state.js +19 -11
  13. package/dist/catalog/plugin-import.js +4 -1
  14. package/dist/conversations/conversations.service.d.ts +9 -0
  15. package/dist/conversations/conversations.service.js +9 -2
  16. package/dist/conversations/events.d.ts +1 -0
  17. package/dist/db/kernel-schema.d.ts +13 -0
  18. package/dist/errors.d.ts +2 -0
  19. package/dist/errors.js +2 -0
  20. package/dist/platform/exclusions.js +10 -0
  21. package/dist/platform/ops/agents.ops.js +19 -6
  22. package/dist/runtime/subagents/definition.d.ts +3 -2
  23. package/dist/runtime/subagents/definition.js +2 -1
  24. package/dist/tools/connector-account-policies.d.ts +51 -0
  25. package/dist/tools/connector-account-policies.js +165 -0
  26. package/dist/tools/connectors.controller.d.ts +35 -4
  27. package/dist/tools/connectors.controller.js +66 -7
  28. package/dist/tools/connectors.schemas.d.ts +11 -0
  29. package/dist/tools/connectors.schemas.js +11 -0
  30. package/dist/tools/connectors.service.d.ts +2 -0
  31. package/dist/tools/connectors.service.js +1 -0
  32. package/dist/tools/pipeline.d.ts +1 -1
  33. package/dist/tools/policy.d.ts +23 -1
  34. package/dist/tools/policy.js +99 -24
  35. package/dist/tools/tools.module.js +3 -0
  36. package/package.json +1 -1
  37. package/src/migrations/0104_connector_tool_account_policies.sql +16 -0
@@ -2,6 +2,7 @@ import { sql } from "kysely";
2
2
  import { roleAtLeast } from "../orgs/roles.js";
3
3
  import { readOrgSettings } from "../orgs/settings.service.js";
4
4
  import { R3_FIXED_ASK_TOOL_KEYS, R3_SHARED_AGENT_ONLY_TOOL_KEYS, SANDBOX_ASK_TOOL_NAMES, } from "./builtin-names.js";
5
+ import { personalAuth } from "./connector-account-policies.js";
5
6
  import { normalizeStoredDefinitionIn } from "./legacy-data.js";
6
7
  export { R3_FIXED_ASK_TOOL_KEYS, R3_SHARED_AGENT_ONLY_TOOL_KEYS } from "./builtin-names.js";
7
8
  const RANK = { allow: 0, ask: 1, deny: 2 };
@@ -35,17 +36,51 @@ export function isFixedAsk(tool, visibility, skillWritePolicy) {
35
36
  // 私有 agent:缺省 / allow 不问(R4),负责人设为 ask(D10)时与共享 agent 一样固定询问
36
37
  return !(builtinOnly && visibility === "private" && skillWritePolicy !== "ask");
37
38
  }
38
- const asOverride = (v) => v === "ask" || v === "deny" ? v : undefined;
39
+ const asSetting = (v) => v === "allow" || v === "ask" || v === "deny" ? v : undefined;
40
+ /** The stricter of the settings that are set (none set: undefined). */
41
+ function combine(...xs) {
42
+ let out;
43
+ for (const x of xs)
44
+ if (x !== undefined)
45
+ out = out === undefined ? x : stricter(out, x);
46
+ return out;
47
+ }
39
48
  /**
40
- * An agent's tightening for a call: its policy on the tool key and, for a per-call key
41
- * (`Platform:<operation>`), on that key, the stricter of the two (a policy can only tighten).
49
+ * An agent's setting for a call: its policy on the tool key and, for a per-call key
50
+ * (`Platform:<operation>`), on that key, the stricter of the two; undefined when neither is set.
51
+ * `allow` counts only for a connector tool ({@link connectorPolicy}); elsewhere an agent's policy
52
+ * can only tighten ({@link tighteningOf}).
42
53
  */
43
- function overrideOf(policies, toolKey, policyKey) {
44
- const own = asOverride(policies?.[toolKey]);
54
+ function settingOf(policies, toolKey, policyKey) {
55
+ const own = asSetting(policies?.[toolKey]);
45
56
  if (policyKey === toolKey)
46
57
  return own;
47
- const s = stricter(own ?? "allow", asOverride(policies?.[policyKey]));
48
- return s === "allow" ? undefined : s;
58
+ return combine(own, asSetting(policies?.[policyKey]));
59
+ }
60
+ /** An agent setting as a tightening (`allow` tightens nothing). */
61
+ const tighteningOf = (s) => s === "ask" || s === "deny" ? s : undefined;
62
+ /**
63
+ * A personal-credential connector tool's policy for one call (maintainer decisions 2026-11-09;
64
+ * an org-credential connector's tool keeps the org default with the agent's tightening only),
65
+ * highest first:
66
+ * 1. the org's default `deny` (a tool the org switched off never gets here: it is unavailable);
67
+ * 2. the member's own `deny` for calls with their connection (`member`): a floor no agent lifts;
68
+ * 3. the agent's setting (`agent`, from its tools page): `allow` / `ask` / `deny`;
69
+ * 4. the member's own `allow` / `ask`;
70
+ * 5. the org's default (`connector_tools.default_policy`, the annotation default unless an org
71
+ * admin changed it: `readOnlyHint: true` allows, anything else asks).
72
+ * Fixed asks (R3, `requiresUserInteraction`), role escalation and grants apply after this.
73
+ */
74
+ export function connectorPolicy(input) {
75
+ if (input.orgDefault === "deny")
76
+ return { policy: "deny", source: "org" };
77
+ if (input.member === "deny")
78
+ return { policy: "deny", source: "member" };
79
+ if (input.agent !== undefined)
80
+ return { policy: input.agent, source: "agent" };
81
+ if (input.member !== undefined)
82
+ return { policy: input.member, source: "member" };
83
+ return { policy: input.orgDefault, source: "org" };
49
84
  }
50
85
  /**
51
86
  * 执行时的策略判定(spec 3.6 R5:在动作执行那一刻判定)。每次调用都实时读取:
@@ -58,7 +93,7 @@ function overrideOf(policies, toolKey, policyKey) {
58
93
  * parent's current version still tightens it (sub-agents spec §5.3, §9); its "this conversation"
59
94
  * grants are the root conversation's (agent map spec §7); an org agent sub-agent (§5.5): `ctx.agentId` is the
60
95
  * dispatched agent (its current version, its "always" grants) and the parent agent's current
61
- * policies apply on top ({@link parentAgentOverride});
96
+ * policies apply on top ({@link parentAgentSetting});
62
97
  * - 授权表(`conversation_tool_grants` / `tool_grants`),`spec_hash` 与当前定义不一致视为无效(D8),
63
98
  * `connector_id` 与工具的来源连接器不一致也无效(删除后用同一 slug 重新安装的连接器不继承旧授权);
64
99
  * - 工具声明了 `requiredRole`(R1)时,发起人在组织中的当前角色:不是成员 → 拒绝;角色不足 → 按角色升级的审批
@@ -75,11 +110,15 @@ export function createDecidePolicy(db) {
75
110
  let connectorDefault = call?.policy ?? tool.defaultPolicy;
76
111
  let connectorId = null;
77
112
  let hashCurrent = true;
113
+ /** The member's own setting for a call with their connection (a personal-credential connector). */
114
+ let memberSetting;
115
+ /** A personal-credential connector: calls use the acting account's own connection. */
116
+ let personal = false;
78
117
  if (tool.source.kind === "connector") {
79
118
  connectorId = tool.source.connectorId;
80
119
  const app = await db
81
120
  .selectFrom("connectors")
82
- .select(["slug", "status", "deleted_at"])
121
+ .select(["slug", "status", "deleted_at", "auth"])
83
122
  .where("id", "=", tool.source.connectorId)
84
123
  .where("org_id", "=", ctx.orgId)
85
124
  .executeTakeFirst();
@@ -105,6 +144,19 @@ export function createDecidePolicy(db) {
105
144
  return { unavailable: "Tool is pending review" };
106
145
  connectorDefault = row.default_policy;
107
146
  hashCurrent = row.spec_hash === tool.specHash;
147
+ // The call is made with the acting account's own connection (the credential the pipeline
148
+ // injects for `ctx.accountId`): that account's settings apply, never another's
149
+ personal = personalAuth(app.auth);
150
+ if (personal) {
151
+ const mine = await db
152
+ .selectFrom("connector_tool_account_policies")
153
+ .select("policy")
154
+ .where("connector_id", "=", tool.source.connectorId)
155
+ .where("account_id", "=", ctx.accountId)
156
+ .where("tool_name", "=", tool.source.name)
157
+ .executeTakeFirst();
158
+ memberSetting = mine?.policy;
159
+ }
108
160
  }
109
161
  // 沙箱工具的组织默认(M5 T8):组织设置 sandboxToolPolicy 为 ask 时,有副作用的沙箱工具至少 ask
110
162
  // `orgAsk`: the org setting, not the tool's own default, is what makes it ask
@@ -116,7 +168,7 @@ export function createDecidePolicy(db) {
116
168
  }
117
169
  }
118
170
  // 运行所用版本可能是加 toolPolicies 字段之前保存的:缺省为空
119
- const runOverride = overrideOf(agentDef.toolPolicies, tool.key, policyKey);
171
+ const runSetting = settingOf(agentDef.toolPolicies, tool.key, policyKey);
120
172
  const current = await db
121
173
  .selectFrom("agents")
122
174
  .innerJoin("agent_versions", (j) => j
@@ -130,12 +182,33 @@ export function createDecidePolicy(db) {
130
182
  ? undefined
131
183
  : await normalizeStoredDefinitionIn(db, ctx.orgId, current.definition);
132
184
  const currentPolicies = currentDef?.toolPolicies;
133
- const currentOverride = overrideOf(currentPolicies, tool.key, policyKey);
134
- let strictest = stricter(runOverride ?? "allow", currentOverride);
185
+ const currentSetting = settingOf(currentPolicies, tool.key, policyKey);
186
+ // the run's version, the agent's current version and (a sub-agent) its parent agent's
187
+ // current version: the stricter of the ones that are set
188
+ // (an `allow` the owner removed since the run started no longer applies: removing it tightens)
189
+ const runKept = runSetting === "allow" && current !== undefined && currentSetting === undefined
190
+ ? undefined
191
+ : runSetting;
192
+ let agentSetting = combine(runKept, currentSetting);
135
193
  if (ctx.parentConversationId !== undefined && ctx.parentAgentDiffers !== false) {
136
- strictest = stricter(strictest, await parentAgentOverride(db, tool.key, policyKey, ctx));
194
+ agentSetting = combine(agentSetting, await parentAgentSetting(db, tool.key, policyKey, ctx));
195
+ }
196
+ let agentOverride = tighteningOf(agentSetting);
197
+ // A personal-credential connector's tool: the levels of `connectorPolicy` decide its base
198
+ // policy (an agent's `allow` counts here, below the org's and the member's deny). A connector
199
+ // with one org credential has no member level and its calls act as the whole org: there the
200
+ // agent's setting only tightens the org default, as for every other tool (review 2026-11-09)
201
+ let decidedBy = null;
202
+ if (tool.source.kind === "connector" && personal) {
203
+ const r = connectorPolicy({
204
+ orgDefault: connectorDefault,
205
+ member: memberSetting,
206
+ agent: agentSetting,
207
+ });
208
+ connectorDefault = r.policy;
209
+ decidedBy = r.source;
210
+ agentOverride = undefined;
137
211
  }
138
- const agentOverride = strictest === "allow" ? undefined : strictest;
139
212
  let escalateTo = null;
140
213
  if (tool.requiredRole !== undefined) {
141
214
  const m = await db
@@ -184,15 +257,17 @@ export function createDecidePolicy(db) {
184
257
  // default
185
258
  policy: escalateTo !== null
186
259
  ? "role"
187
- : agentOverride === "ask"
260
+ : agentOverride === "ask" || (decidedBy === "agent" && connectorDefault === "ask")
188
261
  ? "agent"
189
- : tool.source.kind === "connector" && connectorDefault === "ask"
190
- ? "connector"
191
- : fixedAsk
192
- ? "fixed"
193
- : orgAsk
194
- ? "org"
195
- : "default",
262
+ : decidedBy === "member" && connectorDefault === "ask"
263
+ ? "member"
264
+ : tool.source.kind === "connector" && connectorDefault === "ask"
265
+ ? "connector"
266
+ : fixedAsk
267
+ ? "fixed"
268
+ : orgAsk
269
+ ? "org"
270
+ : "default",
196
271
  };
197
272
  return { ask };
198
273
  };
@@ -203,7 +278,7 @@ export function createDecidePolicy(db) {
203
278
  * policy for the same key applies on top, so the parent's owner can tighten a running child too.
204
279
  * For a template or general-purpose child the parent conversation has the same agent, already read.
205
280
  */
206
- async function parentAgentOverride(db, toolKey, policyKey, ctx) {
281
+ async function parentAgentSetting(db, toolKey, policyKey, ctx) {
207
282
  const row = await db
208
283
  .selectFrom("conversations as c")
209
284
  .innerJoin("agents as a", "a.id", "c.agent_id")
@@ -216,7 +291,7 @@ async function parentAgentOverride(db, toolKey, policyKey, ctx) {
216
291
  // stored raw: an rc version's keys read as current keys, so its deny keeps applying (§8.3)
217
292
  const policies = (await normalizeStoredDefinitionIn(db, ctx.orgId, row.definition))
218
293
  .toolPolicies;
219
- return overrideOf(policies, toolKey, policyKey);
294
+ return settingOf(policies, toolKey, policyKey);
220
295
  }
221
296
  /**
222
297
  * A grant matching the current definition hash and source connector, keyed by the call's policy key
@@ -26,6 +26,7 @@ import { ApprovalsService } from "./approvals.service.js";
26
26
  import { Breaker } from "./breaker.js";
27
27
  import { BuiltinTools } from "./builtins/builtin-tools.js";
28
28
  import { WebPages } from "./builtins/web/page-cache.js";
29
+ import { ConnectorAccountPolicies } from "./connector-account-policies.js";
29
30
  import { ConnectorContent } from "./connector-content.js";
30
31
  import { ConnectorsService } from "./connectors.service.js";
31
32
  import { Credentials } from "./credentials.js";
@@ -86,6 +87,7 @@ ToolsModule = __decorate([
86
87
  ConnectorContent,
87
88
  ToolPipeline,
88
89
  ConnectorsService,
90
+ ConnectorAccountPolicies,
89
91
  OAuthFlow,
90
92
  ConversationToolInfo,
91
93
  OrgExtensionTools,
@@ -103,6 +105,7 @@ ToolsModule = __decorate([
103
105
  ConnectorContent,
104
106
  ToolPipeline,
105
107
  ConnectorsService,
108
+ ConnectorAccountPolicies,
106
109
  OAuthFlow,
107
110
  ConversationToolInfo,
108
111
  OrgExtensionTools,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capybara-hq/kernel",
3
- "version": "0.1.0-rc.11",
3
+ "version": "0.1.0-rc.12",
4
4
  "description": "Capybara kernel: config, database, plugin host and HTTP API of the Capybara agent platform",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -0,0 +1,16 @@
1
+ -- A member's own tool policies for their own connection of a personal-credential connector
2
+ -- (maintainer decision 2026-11-09, the connector card's 权限): one row per account, connector and
3
+ -- tool name. They apply to every agent the account uses; an agent's tool policy overrides them, and
4
+ -- a tool the org switched off (or set to deny) stays denied whatever they say.
5
+ CREATE TABLE IF NOT EXISTS capybara.connector_tool_account_policies (
6
+ org_id text NOT NULL REFERENCES capybara.orgs(id) ON DELETE CASCADE,
7
+ connector_id text NOT NULL REFERENCES capybara.connectors(id) ON DELETE CASCADE,
8
+ account_id text NOT NULL REFERENCES capybara.accounts(id) ON DELETE CASCADE,
9
+ tool_name text NOT NULL,
10
+ policy text NOT NULL CHECK (policy IN ('allow', 'ask', 'deny')),
11
+ updated_at timestamptz NOT NULL DEFAULT now(),
12
+ PRIMARY KEY (connector_id, account_id, tool_name)
13
+ );
14
+
15
+ CREATE INDEX IF NOT EXISTS connector_tool_account_policies_account
16
+ ON capybara.connector_tool_account_policies (account_id);