@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
|
@@ -33,6 +33,7 @@ export declare const AgentCreateDefinition: z.ZodObject<{
|
|
|
33
33
|
tools: z.ZodUnion<readonly [z.ZodLiteral<"all">, z.ZodArray<z.ZodString>]>;
|
|
34
34
|
}, z.core.$strict>], "kind">>>;
|
|
35
35
|
toolPolicies: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodEnum<{
|
|
36
|
+
allow: "allow";
|
|
36
37
|
ask: "ask";
|
|
37
38
|
deny: "deny";
|
|
38
39
|
}>>>;
|
|
@@ -116,6 +117,7 @@ export declare const AgentCreateBody: z.ZodObject<{
|
|
|
116
117
|
tools: z.ZodUnion<readonly [z.ZodLiteral<"all">, z.ZodArray<z.ZodString>]>;
|
|
117
118
|
}, z.core.$strict>], "kind">>>;
|
|
118
119
|
toolPolicies: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodEnum<{
|
|
120
|
+
allow: "allow";
|
|
119
121
|
ask: "ask";
|
|
120
122
|
deny: "deny";
|
|
121
123
|
}>>>;
|
|
@@ -231,6 +233,7 @@ export declare const AgentUpdateBody: z.ZodObject<{
|
|
|
231
233
|
tools: z.ZodUnion<readonly [z.ZodLiteral<"all">, z.ZodArray<z.ZodString>]>;
|
|
232
234
|
}, z.core.$strict>], "kind">>>;
|
|
233
235
|
toolPolicies: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodEnum<{
|
|
236
|
+
allow: "allow";
|
|
234
237
|
ask: "ask";
|
|
235
238
|
deny: "deny";
|
|
236
239
|
}>>>;
|
|
@@ -8,6 +8,7 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
|
8
8
|
return function (target, key) { decorator(target, key, paramIndex); }
|
|
9
9
|
};
|
|
10
10
|
import { randomUUID } from "node:crypto";
|
|
11
|
+
import { parseToolKey } from "@capybara-hq/plugin-sdk";
|
|
11
12
|
import { Inject, Injectable } from "@nestjs/common";
|
|
12
13
|
import { sql } from "kysely";
|
|
13
14
|
import { ApiError } from "../errors.js";
|
|
@@ -310,7 +311,16 @@ let AgentsService = class AgentsService {
|
|
|
310
311
|
message: "subagentTools is set only on a sub-agent's definition at dispatch",
|
|
311
312
|
});
|
|
312
313
|
}
|
|
313
|
-
for (const key of Object.
|
|
314
|
+
for (const [key, policy] of Object.entries(def.toolPolicies)) {
|
|
315
|
+
// `allow` counts only for a connector tool (decision 2026-11-09): elsewhere a policy only
|
|
316
|
+
// tightens, so an `allow` there is refused rather than kept as a no-op
|
|
317
|
+
if (policy === "allow" && parseToolKey(key)?.kind !== "connector") {
|
|
318
|
+
details.push({
|
|
319
|
+
path: `toolPolicies.${key}`,
|
|
320
|
+
message: `"allow" applies only to a connector's tools; "${key}" can only be tightened (ask or deny)`,
|
|
321
|
+
});
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
314
324
|
if (!isToolPolicyKey(key, keys)) {
|
|
315
325
|
// an rc key (tool naming spec §8.3): saving writes current keys only, so name them
|
|
316
326
|
const renamed = legacyKeySaveText(key, keys);
|
|
@@ -196,6 +196,7 @@ export declare const AgentDefinitionSchema: z.ZodObject<{
|
|
|
196
196
|
deny: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
197
197
|
}, z.core.$strict>>;
|
|
198
198
|
toolPolicies: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodEnum<{
|
|
199
|
+
allow: "allow";
|
|
199
200
|
ask: "ask";
|
|
200
201
|
deny: "deny";
|
|
201
202
|
}>>>;
|
|
@@ -207,10 +207,14 @@ export const AgentDefinitionSchema = z.object({
|
|
|
207
207
|
*/
|
|
208
208
|
subagentTools: SubagentToolsSchema.optional(),
|
|
209
209
|
/**
|
|
210
|
-
*
|
|
211
|
-
*
|
|
210
|
+
* Policies by tool key; keys are tools the agent could have (on or turned off). `ask` / `deny`
|
|
211
|
+
* tighten any tool. `allow` is accepted only on a connector tool key and counts only for a
|
|
212
|
+
* personal-credential connector's tool (decision 2026-11-09): it overrides the org default and
|
|
213
|
+
* the member's own 询问, never the org's or the member's 禁止 (`tools/policy.ts`
|
|
214
|
+
* `connectorPolicy`); on any other tool it changes nothing. Older definitions without it read as
|
|
215
|
+
* empty.
|
|
212
216
|
*/
|
|
213
|
-
toolPolicies: z.record(z.string(), z.enum(["ask", "deny"])).default({}),
|
|
217
|
+
toolPolicies: z.record(z.string(), z.enum(["allow", "ask", "deny"])).default({}),
|
|
214
218
|
/**
|
|
215
219
|
* 技能(M5 T13):`orgSkills` 是引用的组织技能 id(保存时校验属于本组织且未删除,不可重复)。
|
|
216
220
|
* agent 自己的技能(`scope=agent`)自动对它生效,不进定义。可省略(较早保存的定义也没有),按空处理;
|
|
@@ -200,6 +200,10 @@ extensionTools = []) {
|
|
|
200
200
|
const toolPolicies = {};
|
|
201
201
|
const connectorPolicies = new Map();
|
|
202
202
|
for (const [k, v] of Object.entries(def.toolPolicies ?? {})) {
|
|
203
|
+
// a template only tightens: an agent's `allow` (a connector tool's, decision 2026-11-09)
|
|
204
|
+
// is not carried into it
|
|
205
|
+
if (v === "allow")
|
|
206
|
+
continue;
|
|
203
207
|
const parsed = parseToolKey(k);
|
|
204
208
|
if (parsed !== null && parsed.kind !== "connector" && TOOL_KEY_RE.test(k)) {
|
|
205
209
|
toolPolicies[k] = v;
|
|
@@ -134,7 +134,7 @@ let PendingConnectorsService = class PendingConnectorsService {
|
|
|
134
134
|
}
|
|
135
135
|
const k = connectorToolKey(c.slug, n);
|
|
136
136
|
const prev = toolPolicies[k];
|
|
137
|
-
if (prev === undefined || RANK[p] > RANK[prev])
|
|
137
|
+
if (prev === undefined || prev === "allow" || RANK[p] > RANK[prev])
|
|
138
138
|
toolPolicies[k] = p;
|
|
139
139
|
}
|
|
140
140
|
// tools are on by default (tools default-on spec §3): binding an entry that names some tools
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { 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
5
|
import { ComponentParams, ComponentDisabledSchema as DisabledBody, ComponentIdSchema as IdParams, ComponentListQuery as ListQuery, OrgComponentListQuery, ComponentScopePreviewSchema as PreviewBody, ComponentScopeSchema as ScopeBody, ComponentShownSchema as ShownBody, ComponentSwitchBody as SwitchBody, ComponentTemplateVersionSchema as TemplateVersionBody } from "./component-scope.schemas.js";
|
|
@@ -13,7 +14,8 @@ import { PlatformComponents, type UploadedComponentVersion } from "./platform-co
|
|
|
13
14
|
export declare class AdminComponentsController {
|
|
14
15
|
private readonly scope;
|
|
15
16
|
private readonly components;
|
|
16
|
-
|
|
17
|
+
private readonly icons;
|
|
18
|
+
constructor(scope: ComponentScopeService, components: PlatformComponents, icons: PluginIcons);
|
|
17
19
|
/**
|
|
18
20
|
* Updates a platform skill on its own (spec §7.2): the skill package (zip) as the raw body
|
|
19
21
|
* (`application/octet-stream`, at most `packages.maxBytes`). The package keeps the skill's name
|
|
@@ -26,9 +28,14 @@ export declare class AdminComponentsController {
|
|
|
26
28
|
}): Promise<UploadedComponentVersion>;
|
|
27
29
|
/** Updates a platform agent template on its own: `{ content }` is the template markdown file. */
|
|
28
30
|
uploadAgentTemplateVersion(principal: Principal, p: z.infer<typeof IdParams>, body: z.infer<typeof TemplateVersionBody>): Promise<UploadedComponentVersion>;
|
|
29
|
-
/**
|
|
31
|
+
/**
|
|
32
|
+
* `GET ?kind=`: every platform component of the kind (plugin, owner, scope, version, orgs on),
|
|
33
|
+
* each with its display name and its plugin's icon (`icon`, the admin route; null without one).
|
|
34
|
+
*/
|
|
30
35
|
list(q: z.infer<typeof ListQuery>): Promise<{
|
|
31
|
-
components: ComponentView
|
|
36
|
+
components: (ComponentView & {
|
|
37
|
+
icon: string | null;
|
|
38
|
+
})[];
|
|
32
39
|
}>;
|
|
33
40
|
/**
|
|
34
41
|
* A component's versions, newest first: `{ versions: [{ version, catalogVersion, updatedOnOwn,
|
|
@@ -65,14 +72,19 @@ export declare class AdminComponentsController {
|
|
|
65
72
|
*/
|
|
66
73
|
export declare class OrgComponentsController {
|
|
67
74
|
private readonly scope;
|
|
68
|
-
|
|
75
|
+
private readonly icons;
|
|
76
|
+
constructor(scope: ComponentScopeService, icons: PluginIcons);
|
|
69
77
|
/**
|
|
70
78
|
* The platform components offered to the org (`?kind=`, `?itemId=`: only those; Task 10): each
|
|
71
79
|
* with its state in the org (`on`, `locked` for `all`, the org's `switch`) and whether an org
|
|
72
|
-
* admin may switch it. Members read it (the workspace pages are read-only for them).
|
|
80
|
+
* admin may switch it. Members read it (the workspace pages are read-only for them). Each has
|
|
81
|
+
* its display name and `icon`, its plugin's icon on the org's catalog route (null without one,
|
|
82
|
+
* or when the org's catalog does not show the plugin to the reader: the share gate holds).
|
|
73
83
|
*/
|
|
74
84
|
list(org: OrgContext, q: z.infer<typeof OrgComponentListQuery>): Promise<{
|
|
75
|
-
components: OrgOfferedComponentView
|
|
85
|
+
components: (OrgOfferedComponentView & {
|
|
86
|
+
icon: string | null;
|
|
87
|
+
})[];
|
|
76
88
|
}>;
|
|
77
89
|
setSwitch(principal: Principal, org: OrgContext, p: z.infer<typeof ComponentParams>, body: z.infer<typeof SwitchBody>): Promise<{
|
|
78
90
|
component: OrgComponentView;
|
|
@@ -9,6 +9,7 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
|
9
9
|
};
|
|
10
10
|
import { Controller, Get, HttpCode, Inject, Post, Put, Req } from "@nestjs/common";
|
|
11
11
|
import { ApiError } from "../errors.js";
|
|
12
|
+
import { orgIconScope, PluginIcons } from "../extensions/plugin-icons.js";
|
|
12
13
|
import { CurrentPrincipal, patTokenId, requireUser, SuperAdmin, } from "../identity/principal.js";
|
|
13
14
|
import { createZodDto } from "../infra/validation/create-zod-dto.js";
|
|
14
15
|
import { ZBody, ZParam, ZQuery } from "../infra/validation/zod-params.js";
|
|
@@ -36,6 +37,16 @@ class ComponentShownBody extends createZodDto(ShownBody, "ComponentShownBody") {
|
|
|
36
37
|
}
|
|
37
38
|
class ComponentSwitchBody extends createZodDto(SwitchBody, "ComponentSwitchBody") {
|
|
38
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Each component with `icon`: its plugin's icon URL on the reader's route (null without one, for
|
|
42
|
+
* a component without a plugin, or when the reader may not see the plugin; maintainer decision
|
|
43
|
+
* 2026-11-09: a connector template without an icon of its own shows its plugin's).
|
|
44
|
+
*/
|
|
45
|
+
async function withIcons(icons, rows, scope) {
|
|
46
|
+
const ids = rows.flatMap((r) => (r.itemId === null ? [] : [r.itemId]));
|
|
47
|
+
const urls = await icons.ofItems(ids, scope);
|
|
48
|
+
return rows.map((r) => ({ ...r, icon: r.itemId === null ? null : (urls.get(r.itemId) ?? null) }));
|
|
49
|
+
}
|
|
39
50
|
const actorOpts = (actor) => {
|
|
40
51
|
const tokenId = patTokenId(actor);
|
|
41
52
|
return tokenId !== undefined ? { actorTokenId: tokenId } : {};
|
|
@@ -49,9 +60,11 @@ const actorOpts = (actor) => {
|
|
|
49
60
|
let AdminComponentsController = class AdminComponentsController {
|
|
50
61
|
scope;
|
|
51
62
|
components;
|
|
52
|
-
|
|
63
|
+
icons;
|
|
64
|
+
constructor(scope, components, icons) {
|
|
53
65
|
this.scope = scope;
|
|
54
66
|
this.components = components;
|
|
67
|
+
this.icons = icons;
|
|
55
68
|
}
|
|
56
69
|
/**
|
|
57
70
|
* Updates a platform skill on its own (spec §7.2): the skill package (zip) as the raw body
|
|
@@ -73,9 +86,14 @@ let AdminComponentsController = class AdminComponentsController {
|
|
|
73
86
|
const actor = requireUser(principal);
|
|
74
87
|
return this.components.uploadAgentTemplateVersion({ accountId: actor.account.id, ...actorOpts(actor) }, p.id, body.content);
|
|
75
88
|
}
|
|
76
|
-
/**
|
|
89
|
+
/**
|
|
90
|
+
* `GET ?kind=`: every platform component of the kind (plugin, owner, scope, version, orgs on),
|
|
91
|
+
* each with its display name and its plugin's icon (`icon`, the admin route; null without one).
|
|
92
|
+
*/
|
|
77
93
|
async list(q) {
|
|
78
|
-
return {
|
|
94
|
+
return {
|
|
95
|
+
components: await withIcons(this.icons, await this.scope.list(q.kind), { kind: "admin" }),
|
|
96
|
+
};
|
|
79
97
|
}
|
|
80
98
|
/**
|
|
81
99
|
* A component's versions, newest first: `{ versions: [{ version, catalogVersion, updatedOnOwn,
|
|
@@ -178,7 +196,8 @@ AdminComponentsController = __decorate([
|
|
|
178
196
|
Controller("api/v1/admin/components"),
|
|
179
197
|
SuperAdmin(),
|
|
180
198
|
__param(0, Inject(ComponentScopeService)),
|
|
181
|
-
__param(1, Inject(PlatformComponents))
|
|
199
|
+
__param(1, Inject(PlatformComponents)),
|
|
200
|
+
__param(2, Inject(PluginIcons))
|
|
182
201
|
], AdminComponentsController);
|
|
183
202
|
export { AdminComponentsController };
|
|
184
203
|
/**
|
|
@@ -189,21 +208,24 @@ export { AdminComponentsController };
|
|
|
189
208
|
*/
|
|
190
209
|
let OrgComponentsController = class OrgComponentsController {
|
|
191
210
|
scope;
|
|
192
|
-
|
|
211
|
+
icons;
|
|
212
|
+
constructor(scope, icons) {
|
|
193
213
|
this.scope = scope;
|
|
214
|
+
this.icons = icons;
|
|
194
215
|
}
|
|
195
216
|
/**
|
|
196
217
|
* The platform components offered to the org (`?kind=`, `?itemId=`: only those; Task 10): each
|
|
197
218
|
* with its state in the org (`on`, `locked` for `all`, the org's `switch`) and whether an org
|
|
198
|
-
* admin may switch it. Members read it (the workspace pages are read-only for them).
|
|
219
|
+
* admin may switch it. Members read it (the workspace pages are read-only for them). Each has
|
|
220
|
+
* its display name and `icon`, its plugin's icon on the org's catalog route (null without one,
|
|
221
|
+
* or when the org's catalog does not show the plugin to the reader: the share gate holds).
|
|
199
222
|
*/
|
|
200
223
|
async list(org, q) {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
};
|
|
224
|
+
const rows = await this.scope.orgList(org.orgId, {
|
|
225
|
+
...(q.kind !== undefined && { kind: q.kind }),
|
|
226
|
+
...(q.itemId !== undefined && { itemId: q.itemId }),
|
|
227
|
+
});
|
|
228
|
+
return { components: await withIcons(this.icons, rows, orgIconScope(org)) };
|
|
207
229
|
}
|
|
208
230
|
async setSwitch(principal, org, p, body) {
|
|
209
231
|
const actor = requireUser(principal);
|
|
@@ -229,6 +251,7 @@ __decorate([
|
|
|
229
251
|
], OrgComponentsController.prototype, "setSwitch", null);
|
|
230
252
|
OrgComponentsController = __decorate([
|
|
231
253
|
Controller("api/v1/orgs/:orgId/components"),
|
|
232
|
-
__param(0, Inject(ComponentScopeService))
|
|
254
|
+
__param(0, Inject(ComponentScopeService)),
|
|
255
|
+
__param(1, Inject(PluginIcons))
|
|
233
256
|
], OrgComponentsController);
|
|
234
257
|
export { OrgComponentsController };
|
|
@@ -68,7 +68,13 @@ export type ComponentTarget = {
|
|
|
68
68
|
export interface ComponentView {
|
|
69
69
|
kind: ComponentKind;
|
|
70
70
|
id: string;
|
|
71
|
+
/** Skill or template name; a connector template's id (the file's base name). */
|
|
71
72
|
name: string;
|
|
73
|
+
/** A connector template's localized display name (null: show `name`). */
|
|
74
|
+
displayName: {
|
|
75
|
+
zh: string;
|
|
76
|
+
en: string;
|
|
77
|
+
} | null;
|
|
72
78
|
description: string | null;
|
|
73
79
|
/** The plugin it came from (null: none). */
|
|
74
80
|
plugin: string | null;
|
|
@@ -138,6 +144,11 @@ export interface OrgComponentView {
|
|
|
138
144
|
* show with their switches. Never a component the org cannot see.
|
|
139
145
|
*/
|
|
140
146
|
export interface OrgOfferedComponentView extends OrgComponentView {
|
|
147
|
+
/** A connector template's localized display name (null: show `name`). */
|
|
148
|
+
displayName: {
|
|
149
|
+
zh: string;
|
|
150
|
+
en: string;
|
|
151
|
+
} | null;
|
|
141
152
|
description: string | null;
|
|
142
153
|
itemId: string | null;
|
|
143
154
|
version: string;
|
|
@@ -204,6 +204,7 @@ let ComponentScopeService = class ComponentScopeService {
|
|
|
204
204
|
kind,
|
|
205
205
|
id: c.id,
|
|
206
206
|
name: c.name,
|
|
207
|
+
displayName: c.displayName,
|
|
207
208
|
description: c.description,
|
|
208
209
|
plugin: c.plugin,
|
|
209
210
|
itemId: c.itemId,
|
|
@@ -258,6 +259,7 @@ let ComponentScopeService = class ComponentScopeService {
|
|
|
258
259
|
kind,
|
|
259
260
|
id: c.id,
|
|
260
261
|
name: c.name,
|
|
262
|
+
displayName: c.displayName,
|
|
261
263
|
description: c.description,
|
|
262
264
|
plugin: c.plugin,
|
|
263
265
|
itemId: c.itemId,
|
|
@@ -360,6 +362,7 @@ let ComponentScopeService = class ComponentScopeService {
|
|
|
360
362
|
kind,
|
|
361
363
|
id: r.id,
|
|
362
364
|
name: r.name,
|
|
365
|
+
displayName: null,
|
|
363
366
|
description: r.description,
|
|
364
367
|
plugin: r.plugin,
|
|
365
368
|
itemId: r.catalog_item_id,
|
|
@@ -1084,6 +1087,7 @@ const virtualOf = (o) => ({
|
|
|
1084
1087
|
plugin: o.plugin,
|
|
1085
1088
|
version: String(o.version),
|
|
1086
1089
|
description: null,
|
|
1090
|
+
displayName: null,
|
|
1087
1091
|
itemId: null,
|
|
1088
1092
|
scopeMode: "own",
|
|
1089
1093
|
scope: "named",
|
|
@@ -68,6 +68,14 @@ export interface ComponentRow extends ComponentScopeFacts {
|
|
|
68
68
|
/** The current version (a connector template: the catalog version that last changed it). */
|
|
69
69
|
version: string;
|
|
70
70
|
description: string | null;
|
|
71
|
+
/**
|
|
72
|
+
* A connector template's localized display name (its file's `displayName`); null for skills and
|
|
73
|
+
* agent templates, whose name is what they are shown by.
|
|
74
|
+
*/
|
|
75
|
+
displayName: {
|
|
76
|
+
zh: string;
|
|
77
|
+
en: string;
|
|
78
|
+
} | null;
|
|
71
79
|
shownToOthers: boolean;
|
|
72
80
|
/** The owning org (null: the platform; ruling P3-1). */
|
|
73
81
|
ownerOrgId: string | null;
|
|
@@ -63,6 +63,11 @@ export function componentOffered(c, p, listed) {
|
|
|
63
63
|
export function componentLocked(_c, p) {
|
|
64
64
|
return p?.builtin === true;
|
|
65
65
|
}
|
|
66
|
+
/** A `{ zh, en }` display name from stored JSON (a template file's), else null. */
|
|
67
|
+
function localizedName(v) {
|
|
68
|
+
const n = v;
|
|
69
|
+
return typeof n?.zh === "string" && typeof n.en === "string" ? { zh: n.zh, en: n.en } : null;
|
|
70
|
+
}
|
|
66
71
|
/** The live platform components of `kind` (`ids` / `itemId`: only those). */
|
|
67
72
|
export async function loadComponents(db, kind, filter = {}) {
|
|
68
73
|
if (filter.ids !== undefined && filter.ids.length === 0)
|
|
@@ -105,17 +110,19 @@ export async function loadComponents(db, kind, filter = {}) {
|
|
|
105
110
|
q = q.where("p.template_id", "=", filter.name);
|
|
106
111
|
if (filter.forUpdate)
|
|
107
112
|
q = q.forUpdate("p");
|
|
108
|
-
return (await q.orderBy("p.id").execute()).map((r) =>
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
113
|
+
return (await q.orderBy("p.id").execute()).map((r) => {
|
|
114
|
+
const file = r.file;
|
|
115
|
+
return {
|
|
116
|
+
...common(r),
|
|
117
|
+
...(file?.defaultEnabled === false && { defaultOff: true }),
|
|
118
|
+
name: r.template_id,
|
|
119
|
+
plugin: r.item_name,
|
|
120
|
+
version: r.current_version,
|
|
121
|
+
description: typeof file?.description === "string" ? file.description : null,
|
|
122
|
+
displayName: localizedName(file?.displayName),
|
|
123
|
+
originOrgId: null,
|
|
124
|
+
};
|
|
125
|
+
});
|
|
119
126
|
}
|
|
120
127
|
const table = kind === "skill"
|
|
121
128
|
? "platform_skills"
|
|
@@ -154,6 +161,7 @@ export async function loadComponents(db, kind, filter = {}) {
|
|
|
154
161
|
plugin: r.plugin_name,
|
|
155
162
|
version: String(r.current_version),
|
|
156
163
|
description: r.description,
|
|
164
|
+
displayName: null,
|
|
157
165
|
originOrgId: r.origin_org_id,
|
|
158
166
|
}));
|
|
159
167
|
}
|
|
@@ -1714,7 +1714,10 @@ function actionOf(s, sha256, version) {
|
|
|
1714
1714
|
export function pluginReviewHash(r, files) {
|
|
1715
1715
|
const e = r.extension;
|
|
1716
1716
|
const lic = r.licenseText;
|
|
1717
|
-
|
|
1717
|
+
// canonicalJson, not JSON.stringify: the review is stored as jsonb, which reorders object keys,
|
|
1718
|
+
// so a template's auth / endpoint object would hash differently at confirm (plugin.review_stale
|
|
1719
|
+
// on every import of a plugin whose connector template has a non-trivial auth block).
|
|
1720
|
+
const canonical = canonicalJson([
|
|
1718
1721
|
r.name,
|
|
1719
1722
|
r.version,
|
|
1720
1723
|
r.contentDigest,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AgentLookup } from "../agents/agent-lookup.js";
|
|
2
2
|
import type { ModelRef } from "../agents/definition.js";
|
|
3
|
+
import type { AgentAvatar } from "../agents/profile.js";
|
|
3
4
|
import { BackgroundTaskStopper } from "../background/stop.js";
|
|
4
5
|
import type { TitleSource } from "../db/kernel-schema.js";
|
|
5
6
|
import { ConversationEnvironments, type NewConversationEnvironment } from "../environments/conversation-environments.js";
|
|
@@ -53,9 +54,17 @@ export interface ConversationView {
|
|
|
53
54
|
export declare function escapeLike(s: string): string;
|
|
54
55
|
/** One agent's group in the sidebar list: the caller's newest conversations with that agent. */
|
|
55
56
|
export interface ConversationGroup {
|
|
57
|
+
/**
|
|
58
|
+
* The agent's name and the avatar half of its profile, in the agents API's shape (the sidebar
|
|
59
|
+
* draws the group title from it without reading the agents list).
|
|
60
|
+
*/
|
|
56
61
|
agent: {
|
|
57
62
|
id: string;
|
|
58
63
|
name: string;
|
|
64
|
+
profile: {
|
|
65
|
+
avatar: AgentAvatar;
|
|
66
|
+
updatedAt: string;
|
|
67
|
+
};
|
|
59
68
|
};
|
|
60
69
|
/** max(updated_at) of the group's conversations. */
|
|
61
70
|
latestAt: Date;
|
|
@@ -482,7 +482,7 @@ let ConversationsService = class ConversationsService {
|
|
|
482
482
|
LIMIT ${opts.limit + 1}
|
|
483
483
|
)
|
|
484
484
|
SELECT page.agent_id, page.latest_at, v.definition ->> 'name' AS agent_name,
|
|
485
|
-
cs.id, cs.org_id, cs.account_id, cs.title, cs.title_source, cs.selection, cs.last_seq,
|
|
485
|
+
a.profile_avatar, a.profile_updated_at, cs.id, cs.org_id, cs.account_id, cs.title, cs.title_source, cs.selection, cs.last_seq,
|
|
486
486
|
cs.created_at, cs.updated_at, cs.parent_conversation_id, cs.subagent_type,
|
|
487
487
|
cs.archived_at, cs.environment_id, cs.activity
|
|
488
488
|
FROM page
|
|
@@ -506,7 +506,14 @@ let ConversationsService = class ConversationsService {
|
|
|
506
506
|
for (const r of rows) {
|
|
507
507
|
if (current?.agent.id !== r.agent_id) {
|
|
508
508
|
current = {
|
|
509
|
-
agent: {
|
|
509
|
+
agent: {
|
|
510
|
+
id: r.agent_id,
|
|
511
|
+
name: r.agent_name ?? "",
|
|
512
|
+
profile: {
|
|
513
|
+
avatar: r.profile_avatar,
|
|
514
|
+
updatedAt: r.profile_updated_at.toISOString(),
|
|
515
|
+
},
|
|
516
|
+
},
|
|
510
517
|
latestAt: r.latest_at,
|
|
511
518
|
conversations: [],
|
|
512
519
|
more: false,
|
|
@@ -809,6 +809,7 @@ export declare const ConversationEventSchema: z.ZodDiscriminatedUnion<[z.ZodObje
|
|
|
809
809
|
deny: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
810
810
|
}, z.core.$strict>>;
|
|
811
811
|
toolPolicies: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodEnum<{
|
|
812
|
+
allow: "allow";
|
|
812
813
|
ask: "ask";
|
|
813
814
|
deny: "deny";
|
|
814
815
|
}>>>;
|
|
@@ -420,6 +420,18 @@ export interface ConnectorsTable {
|
|
|
420
420
|
/** 0081: the platform connector template this connector was made from (null = none). */
|
|
421
421
|
platform_template_id: ColumnType<string | null, string | null | undefined, string | null>;
|
|
422
422
|
}
|
|
423
|
+
/**
|
|
424
|
+
* 0104: a member's own policy for one tool of a personal-credential connector, for calls made with
|
|
425
|
+
* their own connection (maintainer decision 2026-11-09).
|
|
426
|
+
*/
|
|
427
|
+
export interface ConnectorToolAccountPoliciesTable {
|
|
428
|
+
org_id: string;
|
|
429
|
+
connector_id: string;
|
|
430
|
+
account_id: string;
|
|
431
|
+
tool_name: string;
|
|
432
|
+
policy: "allow" | "ask" | "deny";
|
|
433
|
+
updated_at: Ts;
|
|
434
|
+
}
|
|
423
435
|
/** 0010_tools.sql(0033 由 `app_tools` 改名):连接器暴露的工具。 */
|
|
424
436
|
export interface ConnectorToolsTable {
|
|
425
437
|
connector_id: string;
|
|
@@ -1142,6 +1154,7 @@ export interface KernelDatabase {
|
|
|
1142
1154
|
lg_checkpoint_writes: LgCheckpointWritesTable;
|
|
1143
1155
|
connectors: ConnectorsTable;
|
|
1144
1156
|
connector_tools: ConnectorToolsTable;
|
|
1157
|
+
connector_tool_account_policies: ConnectorToolAccountPoliciesTable;
|
|
1145
1158
|
tool_grants: ToolGrantsTable;
|
|
1146
1159
|
conversation_tool_grants: ConversationToolGrantsTable;
|
|
1147
1160
|
approvals: ApprovalsTable;
|
package/dist/errors.d.ts
CHANGED
|
@@ -251,6 +251,8 @@ export declare const ERROR_CODES: {
|
|
|
251
251
|
readonly "connector.tool_not_found": 404;
|
|
252
252
|
readonly "connector.tool_not_pending": 409;
|
|
253
253
|
readonly "connector.resources_disabled": 409;
|
|
254
|
+
/** Per-member tool policies on a connector with one org credential (decision 2026-11-09). */
|
|
255
|
+
readonly "connector.not_personal": 409;
|
|
254
256
|
readonly "approval.not_found": 404;
|
|
255
257
|
readonly "approval.already_decided": 409;
|
|
256
258
|
readonly "approval.scope_not_allowed": 409;
|
package/dist/errors.js
CHANGED
|
@@ -251,6 +251,8 @@ export const ERROR_CODES = {
|
|
|
251
251
|
"connector.tool_not_found": 404,
|
|
252
252
|
"connector.tool_not_pending": 409,
|
|
253
253
|
"connector.resources_disabled": 409,
|
|
254
|
+
/** Per-member tool policies on a connector with one org credential (decision 2026-11-09). */
|
|
255
|
+
"connector.not_personal": 409,
|
|
254
256
|
"approval.not_found": 404,
|
|
255
257
|
"approval.already_decided": 409,
|
|
256
258
|
"approval.scope_not_allowed": 409,
|
|
@@ -608,6 +608,16 @@ export const PLATFORM_EXCLUSIONS = [
|
|
|
608
608
|
category: "secret",
|
|
609
609
|
reason: "Writes the caller's connector credentials",
|
|
610
610
|
},
|
|
611
|
+
{
|
|
612
|
+
route: "GET /api/v1/orgs/:orgId/connectors/:connectorId/tool-policies/me",
|
|
613
|
+
category: "internal",
|
|
614
|
+
reason: "Display list of the connector card's 权限 sheet; connectors.get gives an agent the tools and their org defaults",
|
|
615
|
+
},
|
|
616
|
+
{
|
|
617
|
+
route: "PUT /api/v1/orgs/:orgId/connectors/:connectorId/tool-policies/me",
|
|
618
|
+
category: "human-only",
|
|
619
|
+
reason: "A member's own tool policies decide what agents may call with their connection without asking; an agent must never loosen them for itself",
|
|
620
|
+
},
|
|
611
621
|
{
|
|
612
622
|
route: "DELETE /api/v1/orgs/:orgId/connectors/:connectorId/credentials/me",
|
|
613
623
|
category: "secret",
|
|
@@ -40,6 +40,15 @@ const RouteSkills = Def.skills.unwrap();
|
|
|
40
40
|
const Skills = RouteSkills.pick({ pinned: true })
|
|
41
41
|
.extend({ orgSkills: RouteSkills.shape.orgSkills.unwrap() })
|
|
42
42
|
.strict();
|
|
43
|
+
/**
|
|
44
|
+
* Whether `next` sets an `allow` that `current` does not have (decision 2026-11-09: `allow` on a
|
|
45
|
+
* connector tool lets an agent call it without asking). Such a change is confirmed every time and
|
|
46
|
+
* never remembered, so an agent can never loosen its own policies on a grant the person remembered
|
|
47
|
+
* for ordinary edits.
|
|
48
|
+
*/
|
|
49
|
+
function addsAllow(next, current) {
|
|
50
|
+
return Object.entries(next ?? {}).some(([k, v]) => v === "allow" && current?.[k] !== "allow");
|
|
51
|
+
}
|
|
43
52
|
/** The definition fields an agent can set through the tool; the rest keep the web's defaults. */
|
|
44
53
|
const Fields = {
|
|
45
54
|
name: Def.name,
|
|
@@ -156,8 +165,10 @@ let AgentsOperations = class AgentsOperations {
|
|
|
156
165
|
* `agents.update`'s class (spec §13): `personal` (may be remembered) only on a private agent the
|
|
157
166
|
* actor owns; any other agent the actor may edit is shared, so each change is confirmed.
|
|
158
167
|
*/
|
|
159
|
-
async updateClass(agentId, ctx) {
|
|
168
|
+
async updateClass(agentId, toolPolicies, ctx) {
|
|
160
169
|
const a = await this.agents.get(ctx.orgId, agentId, ctx.actorAccountId);
|
|
170
|
+
if (addsAllow(toolPolicies, a.definition.toolPolicies))
|
|
171
|
+
return "org";
|
|
161
172
|
return a.visibility === "private" && a.ownerAccountId === ctx.actorAccountId
|
|
162
173
|
? "personal"
|
|
163
174
|
: "org";
|
|
@@ -221,6 +232,8 @@ let AgentsOperations = class AgentsOperations {
|
|
|
221
232
|
omits: ["memberIds", "profile"],
|
|
222
233
|
loosens: { visibility: "optional: the operation creates private agents only" },
|
|
223
234
|
confirm: "personal",
|
|
235
|
+
// an `allow` loosens: never on a remembered grant
|
|
236
|
+
confirmFor: async (i) => addsAllow(i.definition.toolPolicies, undefined) ? "org" : "personal",
|
|
224
237
|
summary: (i) => ({
|
|
225
238
|
zh: `新建私有智能体「${i.definition.name}」`,
|
|
226
239
|
en: `Create private agent "${i.definition.name}"`,
|
|
@@ -246,7 +259,7 @@ let AgentsOperations = class AgentsOperations {
|
|
|
246
259
|
}),
|
|
247
260
|
defineOperation({
|
|
248
261
|
id: "agents.update",
|
|
249
|
-
description: 'Change an agent you may edit: your own, private or shared, and as an org admin any agent of the org. Send the version you read from agents.get and only the fields to change; the others keep their value. The description (one short paragraph, "" removes it) is saved with the definition; the avatar and quick actions are changed with agents.update-profile. disabledTools replaces the whole list of turned-off tools (send [] to turn every tool on). A version that is no longer current is refused, so read the agent again first. The visibility cannot be changed here (that is done on the agents page of the web app). A change to your own private agent may be remembered by the person; a change to a shared agent is confirmed every time.',
|
|
262
|
+
description: 'Change an agent you may edit: your own, private or shared, and as an org admin any agent of the org. Send the version you read from agents.get and only the fields to change; the others keep their value. The description (one short paragraph, "" removes it) is saved with the definition; the avatar and quick actions are changed with agents.update-profile. disabledTools replaces the whole list of turned-off tools (send [] to turn every tool on). A version that is no longer current is refused, so read the agent again first. The visibility cannot be changed here (that is done on the agents page of the web app). A change to your own private agent may be remembered by the person; a change to a shared agent, or one that sets a tool policy to allow, is confirmed every time.',
|
|
250
263
|
input: z
|
|
251
264
|
.object({
|
|
252
265
|
agentId: AgentId,
|
|
@@ -289,7 +302,7 @@ let AgentsOperations = class AgentsOperations {
|
|
|
289
302
|
next: "The agent changed since you read it: read it again with agents.get, then send its current version.",
|
|
290
303
|
},
|
|
291
304
|
],
|
|
292
|
-
confirmFor: (i, ctx) => this.updateClass(i.agentId, ctx),
|
|
305
|
+
confirmFor: (i, ctx) => this.updateClass(i.agentId, i.toolPolicies, ctx),
|
|
293
306
|
summary: async (i, ctx) => {
|
|
294
307
|
const a = await this.agents.get(ctx.orgId, i.agentId, ctx.actorAccountId);
|
|
295
308
|
const name = named(a.definition.name);
|
|
@@ -353,7 +366,7 @@ let AgentsOperations = class AgentsOperations {
|
|
|
353
366
|
// needs no declaration: an input that accepts less than the route passes the parity test.
|
|
354
367
|
omits: ["expectedUpdatedAt"],
|
|
355
368
|
confirm: "personal",
|
|
356
|
-
confirmFor: (i, ctx) => this.updateClass(i.agentId, ctx),
|
|
369
|
+
confirmFor: (i, ctx) => this.updateClass(i.agentId, undefined, ctx),
|
|
357
370
|
errors: [
|
|
358
371
|
{
|
|
359
372
|
code: "agent.not_found",
|
|
@@ -493,7 +506,7 @@ let AgentsOperations = class AgentsOperations {
|
|
|
493
506
|
confirm: "personal",
|
|
494
507
|
// destructive: asked every time and never remembered, a shared agent as an org write
|
|
495
508
|
rememberable: false,
|
|
496
|
-
confirmFor: (i, ctx) => this.updateClass(i.agentId, ctx),
|
|
509
|
+
confirmFor: (i, ctx) => this.updateClass(i.agentId, undefined, ctx),
|
|
497
510
|
errors: [
|
|
498
511
|
{
|
|
499
512
|
code: "agent.not_found",
|
|
@@ -578,7 +591,7 @@ let AgentsOperations = class AgentsOperations {
|
|
|
578
591
|
authz: { orgAction: "agents.create" },
|
|
579
592
|
mirrors: ["POST /api/v1/orgs/:orgId/agents/:agentId/pending-connectors/:key/bind"],
|
|
580
593
|
confirm: "personal",
|
|
581
|
-
confirmFor: (i, ctx) => this.updateClass(i.agentId, ctx),
|
|
594
|
+
confirmFor: (i, ctx) => this.updateClass(i.agentId, undefined, ctx),
|
|
582
595
|
errors: [
|
|
583
596
|
{
|
|
584
597
|
code: "agent.pending_connector_not_found",
|
|
@@ -34,9 +34,10 @@ export interface EffectiveDefinitionDeps {
|
|
|
34
34
|
type Policy = "ask" | "deny";
|
|
35
35
|
/**
|
|
36
36
|
* `base` with `over` applied on top, the stricter value winning per key (`deny` > `ask`). Values
|
|
37
|
-
* other than `ask` / `deny` (stored template data is not re-validated
|
|
37
|
+
* other than `ask` / `deny` (stored template data is not re-validated; an agent's connector-tool
|
|
38
|
+
* `allow`, which the policy check reads from the agent's current version) are skipped on both sides.
|
|
38
39
|
*/
|
|
39
|
-
export declare function stricterPolicies(base: Record<string,
|
|
40
|
+
export declare function stricterPolicies(base: Record<string, string>, over: Record<string, string>): Record<string, Policy>;
|
|
40
41
|
/** An org agent's version and definition as checked by its gates. */
|
|
41
42
|
export interface CheckedOrgAgent {
|
|
42
43
|
version: number;
|
|
@@ -23,7 +23,8 @@ const RANK = { ask: 1, deny: 2 };
|
|
|
23
23
|
const isPolicy = (p) => p === "ask" || p === "deny";
|
|
24
24
|
/**
|
|
25
25
|
* `base` with `over` applied on top, the stricter value winning per key (`deny` > `ask`). Values
|
|
26
|
-
* other than `ask` / `deny` (stored template data is not re-validated
|
|
26
|
+
* other than `ask` / `deny` (stored template data is not re-validated; an agent's connector-tool
|
|
27
|
+
* `allow`, which the policy check reads from the agent's current version) are skipped on both sides.
|
|
27
28
|
*/
|
|
28
29
|
export function stricterPolicies(base, over) {
|
|
29
30
|
const out = {};
|