@capybara-hq/kernel 0.1.0-rc.19 → 0.1.0-rc.20
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/bootstrap.json +25 -25
- package/dist/admin/admin-agents.controller.d.ts +28 -0
- package/dist/admin/admin-agents.controller.js +79 -0
- package/dist/admin/admin-connectors.controller.d.ts +29 -0
- package/dist/admin/admin-connectors.controller.js +79 -0
- package/dist/admin/admin-items.schemas.d.ts +42 -0
- package/dist/admin/admin-items.schemas.js +48 -0
- package/dist/admin/admin.controller.d.ts +11 -20
- package/dist/admin/admin.controller.js +28 -34
- package/dist/admin/admin.module.d.ts +4 -1
- package/dist/admin/admin.module.js +50 -4
- package/dist/admin/admin.schemas.d.ts +4 -0
- package/dist/admin/admin.schemas.js +4 -1
- package/dist/admin/audit.controller.d.ts +14 -0
- package/dist/admin/audit.controller.js +42 -0
- package/dist/admin/audit.schemas.d.ts +8 -0
- package/dist/admin/audit.schemas.js +17 -0
- package/dist/admin/health.service.d.ts +104 -0
- package/dist/admin/health.service.js +246 -0
- package/dist/admin/overview.controller.d.ts +31 -0
- package/dist/admin/overview.controller.js +78 -0
- package/dist/admin/overview.schemas.d.ts +8 -0
- package/dist/admin/overview.schemas.js +13 -0
- package/dist/admin/overview.service.d.ts +112 -0
- package/dist/admin/overview.service.js +223 -0
- package/dist/admin/platform-audit.service.d.ts +41 -0
- package/dist/admin/platform-audit.service.js +70 -0
- package/dist/admin/usage.service.d.ts +149 -0
- package/dist/admin/usage.service.js +321 -0
- package/dist/agents/admin-agents.d.ts +92 -0
- package/dist/agents/admin-agents.js +170 -0
- package/dist/agents/agents.module.d.ts +4 -3
- package/dist/agents/agents.module.js +7 -3
- package/dist/agents/agents.service.d.ts +41 -3
- package/dist/agents/agents.service.js +113 -27
- package/dist/agents/definition.d.ts +16 -0
- package/dist/agents/definition.js +32 -2
- package/dist/agents/org-templates.js +4 -0
- package/dist/background/launch.d.ts +1 -1
- package/dist/background/launch.js +3 -3
- package/dist/background/tasks.service.d.ts +35 -3
- package/dist/background/tasks.service.js +91 -36
- package/dist/catalog/catalog.module.js +8 -2
- package/dist/channels/channels.module.js +11 -2
- package/dist/channels/supervisor.d.ts +2 -0
- package/dist/channels/supervisor.js +4 -0
- package/dist/conversations/events.js +1 -1
- package/dist/conversations/titles.js +4 -8
- package/dist/db/kernel-schema.d.ts +8 -1
- package/dist/devices/devices.module.js +5 -2
- package/dist/errors.d.ts +22 -2
- package/dist/errors.js +22 -2
- package/dist/evals/evals.module.js +11 -2
- package/dist/extensions/permission-view.js +3 -3
- package/dist/http/api.module.js +10 -1
- package/dist/identity/settings.js +3 -2
- package/dist/identity/write-actor.d.ts +11 -0
- package/dist/identity/write-actor.js +7 -0
- package/dist/inbox/inbox.service.d.ts +2 -1
- package/dist/inbox/platform-inbox.service.d.ts +7 -1
- package/dist/inbox/platform-inbox.service.js +20 -2
- package/dist/infra/presence/presence.module.d.ts +6 -0
- package/dist/infra/presence/presence.module.js +22 -0
- package/dist/infra/presence/replica-presence.d.ts +87 -0
- package/dist/infra/presence/replica-presence.js +189 -0
- package/dist/kernel.module.js +3 -0
- package/dist/lifecycle/kernel-lifecycle.d.ts +3 -1
- package/dist/lifecycle/kernel-lifecycle.js +17 -3
- package/dist/local-devices/local-device-gateway.d.ts +6 -0
- package/dist/local-devices/local-device-gateway.js +12 -0
- package/dist/overrides.d.ts +7 -1
- package/dist/overrides.js +4 -0
- package/dist/platform/exclusions.js +15 -0
- package/dist/platform/operation.d.ts +1 -1
- package/dist/platform/operation.js +1 -1
- package/dist/platform/ops/admin-items.ops.d.ts +37 -0
- package/dist/platform/ops/admin-items.ops.js +278 -0
- package/dist/platform/ops/admin.ops.d.ts +10 -10
- package/dist/platform/ops/admin.ops.js +111 -42
- package/dist/platform/ops/agent-skills.ops.d.ts +29 -3
- package/dist/platform/ops/agent-skills.ops.js +290 -7
- package/dist/platform/ops/inbox.ops.js +7 -4
- package/dist/platform/ops/platform.ops.js +1 -1
- package/dist/platform/ops/skills.ops.js +1 -57
- package/dist/platform/platform-ops.module.d.ts +2 -1
- package/dist/platform/platform-ops.module.js +5 -0
- package/dist/platform/route-schemas.js +8 -0
- package/dist/roles/gateway-role.d.ts +4 -0
- package/dist/roles/gateway-role.js +8 -0
- package/dist/runtime/run-jobs.js +4 -2
- package/dist/sandbox/sandbox-file-reader.d.ts +35 -0
- package/dist/sandbox/sandbox-file-reader.js +98 -0
- package/dist/skills/agent-skills.controller.d.ts +48 -0
- package/dist/skills/agent-skills.controller.js +131 -0
- package/dist/skills/agent-skills.service.d.ts +262 -0
- package/dist/skills/agent-skills.service.js +996 -0
- package/dist/skills/catalog.d.ts +8 -6
- package/dist/skills/catalog.js +8 -4
- package/dist/skills/changes.controller.d.ts +5 -22
- package/dist/skills/changes.controller.js +7 -55
- package/dist/skills/changes.service.d.ts +69 -115
- package/dist/skills/changes.service.js +249 -599
- package/dist/skills/conversation-catalog.d.ts +53 -0
- package/dist/skills/conversation-catalog.js +149 -0
- package/dist/skills/invocation-keys.d.ts +1 -1
- package/dist/skills/invocation-keys.js +1 -1
- package/dist/skills/package.d.ts +0 -5
- package/dist/skills/package.js +0 -14
- package/dist/skills/skills.controller.js +1 -10
- package/dist/skills/skills.module.js +15 -2
- package/dist/skills/skills.schemas.d.ts +11 -15
- package/dist/skills/skills.schemas.js +27 -20
- package/dist/skills/skills.service.d.ts +37 -30
- package/dist/skills/skills.service.js +107 -59
- package/dist/skills/usable.d.ts +4 -3
- package/dist/skills/usable.js +13 -4
- package/dist/tools/admin-connectors.d.ts +69 -0
- package/dist/tools/admin-connectors.js +135 -0
- package/dist/tools/builtin-names.d.ts +9 -14
- package/dist/tools/builtin-names.js +13 -22
- package/dist/tools/builtins/builtin-tools.d.ts +2 -3
- package/dist/tools/builtins/builtin-tools.js +21 -26
- package/dist/tools/builtins/sandbox/bash.js +2 -2
- package/dist/tools/builtins/skill-load.js +1 -0
- package/dist/tools/connectors.controller.d.ts +5 -1
- package/dist/tools/connectors.controller.js +17 -3
- package/dist/tools/connectors.service.d.ts +20 -1
- package/dist/tools/connectors.service.js +61 -0
- package/dist/tools/legacy-keys.js +0 -1
- package/dist/tools/org-tool-switches.d.ts +3 -2
- package/dist/tools/org-tool-switches.js +4 -4
- package/dist/tools/policy.d.ts +7 -9
- package/dist/tools/policy.js +11 -22
- package/dist/tools/tools.module.js +3 -0
- package/package.json +1 -1
- package/src/migrations/0109_audit_platform_idx.sql +5 -0
- package/src/migrations/0110_usage_time_idx.sql +8 -0
- package/src/migrations/0111_agent_skill_sharing.sql +22 -0
- package/src/migrations/0112_skill_share_requests.sql +4 -0
- package/dist/tools/builtins/skill-write.d.ts +0 -59
- package/dist/tools/builtins/skill-write.js +0 -244
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { KernelSchemaDb } from "../db/kernel-schema.js";
|
|
2
2
|
import { EvalBudget } from "../evals/budget.js";
|
|
3
|
+
import type { WriteActor } from "../identity/write-actor.js";
|
|
3
4
|
import { KernelDb } from "../infra/database/kernel-db.js";
|
|
4
5
|
import { TransactionHost } from "../infra/database/transaction-host.js";
|
|
5
6
|
import { OrgLock } from "../orgs/org-lock.js";
|
|
@@ -76,7 +77,10 @@ export declare class AgentsService {
|
|
|
76
77
|
private readonly db;
|
|
77
78
|
private readonly lookup;
|
|
78
79
|
private readonly tools;
|
|
79
|
-
/**
|
|
80
|
+
/**
|
|
81
|
+
* Cancels the agent's pending skill requests: all of them when it is deleted, share requests
|
|
82
|
+
* when its owner changes.
|
|
83
|
+
*/
|
|
80
84
|
private readonly skillChanges;
|
|
81
85
|
/** The org row lock (orgs/org-lock.ts): joins the caller's transaction. */
|
|
82
86
|
private readonly orgLock;
|
|
@@ -88,10 +92,13 @@ export declare class AgentsService {
|
|
|
88
92
|
/** Settles the agent's connector setup request when its pending connectors change (P4-7). */
|
|
89
93
|
private readonly setupRequests;
|
|
90
94
|
constructor(db: KernelDb, lookup: AgentLookup, tools: Pick<ToolRegistry, "checkDisabled">,
|
|
91
|
-
/**
|
|
95
|
+
/**
|
|
96
|
+
* Cancels the agent's pending skill requests: all of them when it is deleted, share requests
|
|
97
|
+
* when its owner changes.
|
|
98
|
+
*/
|
|
92
99
|
skillChanges: Pick<SkillChangesService, "cancelPendingForAgent" | "finishCancel">,
|
|
93
100
|
/** The org row lock (orgs/org-lock.ts): joins the caller's transaction. */
|
|
94
|
-
orgLock: Pick<OrgLock, "lockAndAuthorize">,
|
|
101
|
+
orgLock: Pick<OrgLock, "lockAndAuthorize" | "tryLock">,
|
|
95
102
|
/** Resolves the agent's models (and their parameter declarations) when it is saved. */
|
|
96
103
|
providers: Pick<ProviderRegistry, "resolve">, txHost: TransactionHost,
|
|
97
104
|
/** Cancels the deleted agent's running eval runs (in the delete's transaction). */
|
|
@@ -162,6 +169,17 @@ export declare class AgentsService {
|
|
|
162
169
|
visibility: AgentVisibility;
|
|
163
170
|
current_version: number;
|
|
164
171
|
}>;
|
|
172
|
+
/**
|
|
173
|
+
* Locks the live agent row of `orgId` (the caller holds the org row lock); missing or deleted →
|
|
174
|
+
* `agent.not_found`.
|
|
175
|
+
*/
|
|
176
|
+
private lockAgentRow;
|
|
177
|
+
/**
|
|
178
|
+
* The platform path's lock (a super admin who need not be a member of the org): finds the live
|
|
179
|
+
* agent's org, locks the org row, then the agent row. A missing or deleted agent, or one of a
|
|
180
|
+
* deleted org → `agent.not_found`. The super admin check is the caller's (`@SuperAdmin()`).
|
|
181
|
+
*/
|
|
182
|
+
private lockForPlatform;
|
|
165
183
|
/**
|
|
166
184
|
* 修改 agent:`definition` 变化时写新版本(`current_version + 1`),只改 visibility / memberIds 不产生新版本。
|
|
167
185
|
* 只有负责人或组织管理员以上可改,在组织行锁内复核。
|
|
@@ -183,12 +201,32 @@ export declare class AgentsService {
|
|
|
183
201
|
/** 软删除:权限同 update。待处理的技能改动与该 agent 评测集在执行的评测运行一并取消,提交后删除草稿(T15)。 */
|
|
184
202
|
softDelete(orgId: string, agentId: string, actorAccountId: string, actorTokenId?: string, auditDetail?: Record<string, unknown>): Promise<void>;
|
|
185
203
|
private softDeleteIn;
|
|
204
|
+
/**
|
|
205
|
+
* The platform's delete (a super admin who need not be a member of the agent's org): the same
|
|
206
|
+
* effects as {@link softDelete}, under the org row lock, audited `agent.deleted` with
|
|
207
|
+
* `detail.via = "platform"`. A missing or deleted agent, or one of a deleted org →
|
|
208
|
+
* `agent.not_found`. `auditDetail` (a Platform tool write's `viaAgent`) is merged into the
|
|
209
|
+
* detail; `via` stays `"platform"`.
|
|
210
|
+
*/
|
|
211
|
+
softDeleteAsPlatform(agentId: string, actor: WriteActor, auditDetail?: Record<string, unknown>): Promise<void>;
|
|
212
|
+
private softDeleteAsPlatformIn;
|
|
213
|
+
/** The delete itself; the caller holds the org row and agent row locks. */
|
|
214
|
+
private softDeleteLocked;
|
|
186
215
|
/**
|
|
187
216
|
* 重新指派负责人(`agents.manage`,admin):在组织行锁内授权;agent 不存在 / 已删除 → `agent.not_found`;
|
|
188
217
|
* 新负责人不是该组织成员 → `member.not_found`;是匿名访客 → `member.anonymous_guest`。
|
|
189
218
|
* 清除 `needs_reassignment`,审计 `agent.owner_changed`。
|
|
190
219
|
*/
|
|
191
220
|
reassignOwner(orgId: string, agentId: string, newOwnerId: string, actorAccountId: string, actorTokenId?: string): Promise<AgentView>;
|
|
221
|
+
/**
|
|
222
|
+
* The platform's owner transfer (a super admin who need not be a member of the agent's org):
|
|
223
|
+
* the same checks and effects as {@link reassignOwner} (the new owner is a member of the agent's
|
|
224
|
+
* org and not its anonymous guest), audited with `detail.via = "platform"`. A missing or deleted
|
|
225
|
+
* agent, or one of a deleted org → `agent.not_found`.
|
|
226
|
+
*/
|
|
227
|
+
reassignOwnerAsPlatform(agentId: string, newOwnerId: string, actor: WriteActor): Promise<AgentView>;
|
|
228
|
+
/** The owner transfer itself; the caller holds the org row and agent row locks. */
|
|
229
|
+
private reassignOwnerLocked;
|
|
192
230
|
/**
|
|
193
231
|
* `views` with `createdFrom.available` set (ruling P4-8): whether each recorded template is
|
|
194
232
|
* still usable in the org. One template read for all of them; only the agents API calls it.
|
|
@@ -26,7 +26,7 @@ import { assertOrgSkills, assertPinnedSkills } from "../skills/skills.service.js
|
|
|
26
26
|
import { legacyKeySaveText } from "../tools/legacy-keys.js";
|
|
27
27
|
import { ToolRegistry } from "../tools/registry.js";
|
|
28
28
|
import { AgentLookup, isAgentVisibleTo, visibleTo, } from "./agent-lookup.js";
|
|
29
|
-
import { canonicalDisabledTools, normalizeStoredDefinition, paramIssues, SUBAGENT_NAME_RE, SUBAGENT_ROSTER_MAX, } from "./definition.js";
|
|
29
|
+
import { canonicalDisabledTools, normalizeStoredDefinition, paramIssues, SUBAGENT_NAME_RE, SUBAGENT_ROSTER_MAX, withoutRemovedBuiltinPolicies, } from "./definition.js";
|
|
30
30
|
import { deriveInstructions } from "./instruction-files.js";
|
|
31
31
|
import { PendingConnectorRequests } from "./pending-connector-requests.js";
|
|
32
32
|
import { randomPreset } from "./profile.js";
|
|
@@ -275,7 +275,10 @@ let AgentsService = class AgentsService {
|
|
|
275
275
|
evalBudget;
|
|
276
276
|
setupRequests;
|
|
277
277
|
constructor(db, lookup, tools,
|
|
278
|
-
/**
|
|
278
|
+
/**
|
|
279
|
+
* Cancels the agent's pending skill requests: all of them when it is deleted, share requests
|
|
280
|
+
* when its owner changes.
|
|
281
|
+
*/
|
|
279
282
|
skillChanges,
|
|
280
283
|
/** The org row lock (orgs/org-lock.ts): joins the caller's transaction. */
|
|
281
284
|
orgLock,
|
|
@@ -417,11 +420,13 @@ let AgentsService = class AgentsService {
|
|
|
417
420
|
return this.createChecked(orgId, actorAccountId, request, template, actorTokenId, auditDetail);
|
|
418
421
|
}
|
|
419
422
|
async createChecked(orgId, actorAccountId, request, template, actorTokenId, auditDetail) {
|
|
423
|
+
// a policy key of a removed builtin (e.g. copied from a template) is dropped, never refused
|
|
424
|
+
const given = withoutRemovedBuiltinPolicies(request.definition);
|
|
420
425
|
const input = {
|
|
421
426
|
...request,
|
|
422
427
|
// run context spec §3.5: version 1 carries the description (the profile's when the
|
|
423
428
|
// definition gives none)
|
|
424
|
-
definition: withCanonicalTools(withDescription(deriveInstructions(
|
|
429
|
+
definition: withCanonicalTools(withDescription(deriveInstructions(given, null), given.description ?? request.profile?.description, undefined)),
|
|
425
430
|
};
|
|
426
431
|
// 先解析模型(可能访问供应商),不在持有组织行锁时做
|
|
427
432
|
const warnings = await this.checkModels(orgId, input.definition);
|
|
@@ -503,11 +508,29 @@ let AgentsService = class AgentsService {
|
|
|
503
508
|
async lockForWrite(orgId, agentId, actorAccountId) {
|
|
504
509
|
const t = this.db.db;
|
|
505
510
|
const { role } = await this.orgLock.lockAndAuthorize(orgId, actorAccountId, "agents.create");
|
|
511
|
+
const agent = await this.lockAgentRow(orgId, agentId);
|
|
512
|
+
if (agent.owner_account_id === actorAccountId || roleAtLeast(role, "admin"))
|
|
513
|
+
return agent;
|
|
514
|
+
const visible = await t
|
|
515
|
+
.selectFrom("agents")
|
|
516
|
+
.select("id")
|
|
517
|
+
.where("id", "=", agentId)
|
|
518
|
+
.where(visibleTo(actorAccountId))
|
|
519
|
+
.executeTakeFirst();
|
|
520
|
+
if (!visible)
|
|
521
|
+
throw notFound();
|
|
522
|
+
throw new ApiError("forbidden", "Only the agent owner or an organization admin can modify it");
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* Locks the live agent row of `orgId` (the caller holds the org row lock); missing or deleted →
|
|
526
|
+
* `agent.not_found`.
|
|
527
|
+
*/
|
|
528
|
+
async lockAgentRow(orgId, agentId) {
|
|
506
529
|
// NO KEY UPDATE: a soft delete then appends revert snapshots to conversations; FOR UPDATE would
|
|
507
530
|
// block the agent foreign key check of an append already holding one of them (40P01)
|
|
508
|
-
const agent = await
|
|
531
|
+
const agent = await this.db.db
|
|
509
532
|
.selectFrom("agents")
|
|
510
|
-
.select(["owner_account_id", "visibility", "current_version"])
|
|
533
|
+
.select(["owner_account_id", "needs_reassignment", "visibility", "current_version"])
|
|
511
534
|
.where("id", "=", agentId)
|
|
512
535
|
.where("org_id", "=", orgId)
|
|
513
536
|
.where("deleted_at", "is", null)
|
|
@@ -515,24 +538,36 @@ let AgentsService = class AgentsService {
|
|
|
515
538
|
.executeTakeFirst();
|
|
516
539
|
if (!agent)
|
|
517
540
|
throw notFound();
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
541
|
+
return agent;
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* The platform path's lock (a super admin who need not be a member of the org): finds the live
|
|
545
|
+
* agent's org, locks the org row, then the agent row. A missing or deleted agent, or one of a
|
|
546
|
+
* deleted org → `agent.not_found`. The super admin check is the caller's (`@SuperAdmin()`).
|
|
547
|
+
*/
|
|
548
|
+
async lockForPlatform(agentId) {
|
|
549
|
+
const found = await this.db.db
|
|
521
550
|
.selectFrom("agents")
|
|
522
|
-
.select("
|
|
551
|
+
.select("org_id")
|
|
523
552
|
.where("id", "=", agentId)
|
|
524
|
-
.where(
|
|
553
|
+
.where("deleted_at", "is", null)
|
|
525
554
|
.executeTakeFirst();
|
|
526
|
-
if (!
|
|
555
|
+
if (!found)
|
|
527
556
|
throw notFound();
|
|
528
|
-
|
|
557
|
+
const orgId = found.org_id;
|
|
558
|
+
if (!(await this.orgLock.tryLock(orgId)))
|
|
559
|
+
throw notFound();
|
|
560
|
+
return { orgId, agent: await this.lockAgentRow(orgId, agentId) };
|
|
529
561
|
}
|
|
530
562
|
/**
|
|
531
563
|
* 修改 agent:`definition` 变化时写新版本(`current_version + 1`),只改 visibility / memberIds 不产生新版本。
|
|
532
564
|
* 只有负责人或组织管理员以上可改,在组织行锁内复核。
|
|
533
565
|
*/
|
|
534
566
|
async update(orgId, agentId, actorAccountId, request, actorTokenId, auditDetail) {
|
|
535
|
-
|
|
567
|
+
// a policy key of a removed builtin (stored before it went) is dropped, never refused
|
|
568
|
+
const patch = request.definition === undefined
|
|
569
|
+
? request
|
|
570
|
+
: { ...request, definition: withoutRemovedBuiltinPolicies(request.definition) };
|
|
536
571
|
const warnings = patch.definition ? await this.checkModels(orgId, patch.definition) : [];
|
|
537
572
|
const toolKeys = patch.definition
|
|
538
573
|
? await this.checkTools(orgId, patch.definition, await this.keptDisabled(orgId, agentId))
|
|
@@ -641,8 +676,32 @@ let AgentsService = class AgentsService {
|
|
|
641
676
|
await this.skillChanges.finishCancel(orgId, cancelled);
|
|
642
677
|
}
|
|
643
678
|
async softDeleteIn(orgId, agentId, actorAccountId, actorTokenId, auditDetail) {
|
|
644
|
-
const t = this.db.db;
|
|
645
679
|
await this.lockForWrite(orgId, agentId, actorAccountId);
|
|
680
|
+
return this.softDeleteLocked(orgId, agentId, { accountId: actorAccountId, ...(actorTokenId !== undefined && { tokenId: actorTokenId }) }, auditDetail);
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* The platform's delete (a super admin who need not be a member of the agent's org): the same
|
|
684
|
+
* effects as {@link softDelete}, under the org row lock, audited `agent.deleted` with
|
|
685
|
+
* `detail.via = "platform"`. A missing or deleted agent, or one of a deleted org →
|
|
686
|
+
* `agent.not_found`. `auditDetail` (a Platform tool write's `viaAgent`) is merged into the
|
|
687
|
+
* detail; `via` stays `"platform"`.
|
|
688
|
+
*/
|
|
689
|
+
async softDeleteAsPlatform(agentId, actor, auditDetail) {
|
|
690
|
+
const { orgId, cancelled } = await this.softDeleteAsPlatformIn(agentId, actor, auditDetail);
|
|
691
|
+
await this.skillChanges.finishCancel(orgId, cancelled);
|
|
692
|
+
}
|
|
693
|
+
async softDeleteAsPlatformIn(agentId, actor, auditDetail) {
|
|
694
|
+
const { orgId } = await this.lockForPlatform(agentId);
|
|
695
|
+
const cancelled = await this.softDeleteLocked(orgId, agentId, actor, {
|
|
696
|
+
...auditDetail,
|
|
697
|
+
via: "platform",
|
|
698
|
+
});
|
|
699
|
+
return { orgId, cancelled };
|
|
700
|
+
}
|
|
701
|
+
/** The delete itself; the caller holds the org row and agent row locks. */
|
|
702
|
+
async softDeleteLocked(orgId, agentId, actor, auditDetail) {
|
|
703
|
+
const t = this.db.db;
|
|
704
|
+
const { accountId: actorAccountId, tokenId: actorTokenId } = actor;
|
|
646
705
|
await t
|
|
647
706
|
.updateTable("agents")
|
|
648
707
|
.set({ deleted_at: sql `now()`, updated_at: sql `now()` })
|
|
@@ -683,18 +742,23 @@ let AgentsService = class AgentsService {
|
|
|
683
742
|
* 清除 `needs_reassignment`,审计 `agent.owner_changed`。
|
|
684
743
|
*/
|
|
685
744
|
async reassignOwner(orgId, agentId, newOwnerId, actorAccountId, actorTokenId) {
|
|
686
|
-
const t = this.db.db;
|
|
687
745
|
await this.orgLock.lockAndAuthorize(orgId, actorAccountId, "agents.manage");
|
|
688
|
-
const agent = await
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
746
|
+
const agent = await this.lockAgentRow(orgId, agentId);
|
|
747
|
+
return this.reassignOwnerLocked(orgId, agentId, agent, newOwnerId, { accountId: actorAccountId, ...(actorTokenId !== undefined && { tokenId: actorTokenId }) }, undefined);
|
|
748
|
+
}
|
|
749
|
+
/**
|
|
750
|
+
* The platform's owner transfer (a super admin who need not be a member of the agent's org):
|
|
751
|
+
* the same checks and effects as {@link reassignOwner} (the new owner is a member of the agent's
|
|
752
|
+
* org and not its anonymous guest), audited with `detail.via = "platform"`. A missing or deleted
|
|
753
|
+
* agent, or one of a deleted org → `agent.not_found`.
|
|
754
|
+
*/
|
|
755
|
+
async reassignOwnerAsPlatform(agentId, newOwnerId, actor) {
|
|
756
|
+
const { orgId, agent } = await this.lockForPlatform(agentId);
|
|
757
|
+
return this.reassignOwnerLocked(orgId, agentId, agent, newOwnerId, actor, { via: "platform" });
|
|
758
|
+
}
|
|
759
|
+
/** The owner transfer itself; the caller holds the org row and agent row locks. */
|
|
760
|
+
async reassignOwnerLocked(orgId, agentId, agent, newOwnerId, actor, auditDetail) {
|
|
761
|
+
const t = this.db.db;
|
|
698
762
|
const member = await t
|
|
699
763
|
.selectFrom("memberships")
|
|
700
764
|
.select("role")
|
|
@@ -711,9 +775,12 @@ let AgentsService = class AgentsService {
|
|
|
711
775
|
.execute();
|
|
712
776
|
// the new owner hears about the agent's connector setup request (P4-T6-4)
|
|
713
777
|
await this.setupRequests.retargetIn([agentId], newOwnerId);
|
|
778
|
+
// the previous owner's asks to share the agent's skills lapse (the new owner did not ask);
|
|
779
|
+
// a share request has no draft or trial, so nothing is left to clean up after the commit
|
|
780
|
+
await this.skillChanges.cancelPendingForAgent(orgId, agentId, actor.accountId, "owner_changed", actor.tokenId, ["share"]);
|
|
714
781
|
await audit(t, {
|
|
715
|
-
actorAccountId,
|
|
716
|
-
...(
|
|
782
|
+
actorAccountId: actor.accountId,
|
|
783
|
+
...(actor.tokenId !== undefined && { actorTokenId: actor.tokenId }),
|
|
717
784
|
orgId,
|
|
718
785
|
action: "agent.owner_changed",
|
|
719
786
|
target: agentId,
|
|
@@ -721,6 +788,7 @@ let AgentsService = class AgentsService {
|
|
|
721
788
|
from: agent.owner_account_id,
|
|
722
789
|
to: newOwnerId,
|
|
723
790
|
neededReassignment: agent.needs_reassignment,
|
|
791
|
+
...auditDetail,
|
|
724
792
|
},
|
|
725
793
|
});
|
|
726
794
|
return this.lookup.view(agentId);
|
|
@@ -789,15 +857,33 @@ __decorate([
|
|
|
789
857
|
__decorate([
|
|
790
858
|
Transactional({ propagation: "mandatory" })
|
|
791
859
|
], AgentsService.prototype, "lockForWrite", null);
|
|
860
|
+
__decorate([
|
|
861
|
+
Transactional({ propagation: "mandatory" })
|
|
862
|
+
], AgentsService.prototype, "lockAgentRow", null);
|
|
863
|
+
__decorate([
|
|
864
|
+
Transactional({ propagation: "mandatory" })
|
|
865
|
+
], AgentsService.prototype, "lockForPlatform", null);
|
|
792
866
|
__decorate([
|
|
793
867
|
Transactional()
|
|
794
868
|
], AgentsService.prototype, "updateIn", null);
|
|
795
869
|
__decorate([
|
|
796
870
|
Transactional()
|
|
797
871
|
], AgentsService.prototype, "softDeleteIn", null);
|
|
872
|
+
__decorate([
|
|
873
|
+
Transactional()
|
|
874
|
+
], AgentsService.prototype, "softDeleteAsPlatformIn", null);
|
|
875
|
+
__decorate([
|
|
876
|
+
Transactional({ propagation: "mandatory" })
|
|
877
|
+
], AgentsService.prototype, "softDeleteLocked", null);
|
|
798
878
|
__decorate([
|
|
799
879
|
Transactional()
|
|
800
880
|
], AgentsService.prototype, "reassignOwner", null);
|
|
881
|
+
__decorate([
|
|
882
|
+
Transactional()
|
|
883
|
+
], AgentsService.prototype, "reassignOwnerAsPlatform", null);
|
|
884
|
+
__decorate([
|
|
885
|
+
Transactional({ propagation: "mandatory" })
|
|
886
|
+
], AgentsService.prototype, "reassignOwnerLocked", null);
|
|
801
887
|
AgentsService = __decorate([
|
|
802
888
|
Injectable(),
|
|
803
889
|
__param(0, Inject(KernelDb)),
|
|
@@ -287,6 +287,22 @@ interface OwnRefs {
|
|
|
287
287
|
* goes through this: a new definition with an old key is refused (`AgentsService`).
|
|
288
288
|
*/
|
|
289
289
|
export declare function normalizeToolKeys(definition: unknown, scope?: LegacyScope, inherited?: OwnRefs): unknown;
|
|
290
|
+
/** The fields {@link withoutRemovedBuiltinPolicies} reads. */
|
|
291
|
+
interface AgentDefinitionLike {
|
|
292
|
+
toolPolicies?: Record<string, string> | undefined;
|
|
293
|
+
subagents?: {
|
|
294
|
+
inline?: readonly {
|
|
295
|
+
toolPolicies?: Record<string, string> | undefined;
|
|
296
|
+
}[] | undefined;
|
|
297
|
+
} | undefined;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* `definition` without the `toolPolicies` keys (its own and its inline sub-agents') of builtins
|
|
301
|
+
* that no longer exist ({@link isRemovedBuiltinKey}): applied to a definition being saved, so a
|
|
302
|
+
* key left over from a removed builtin never blocks the save. Returns the same object when
|
|
303
|
+
* nothing is dropped.
|
|
304
|
+
*/
|
|
305
|
+
export declare function withoutRemovedBuiltinPolicies<T extends AgentDefinitionLike>(definition: T): T;
|
|
290
306
|
/**
|
|
291
307
|
* Does a stored definition or template hold an rc `<x>__<tool>` key, which only a
|
|
292
308
|
* {@link LegacyScope} can map? (Callers load the scope only then.)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { validateParams } from "@capybara-hq/plugin-sdk";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
+
import { isRemovedBuiltinKey } from "../tools/builtin-names.js";
|
|
3
4
|
import { isLegacyBuiltinKey, isLegacyPrefixedKey, legacyToolKey, } from "../tools/legacy-keys.js";
|
|
4
5
|
import { InstructionFilesSchema } from "./instruction-files.js";
|
|
5
6
|
/** 模型引用:供应商插件名 + 模型 id。 */
|
|
@@ -219,8 +220,9 @@ export const AgentDefinitionSchema = z.object({
|
|
|
219
220
|
* 技能(M5 T13):`orgSkills` 是引用的组织技能 id(保存时校验属于本组织且未删除,不可重复)。
|
|
220
221
|
* agent 自己的技能(`scope=agent`)自动对它生效,不进定义。可省略(较早保存的定义也没有),按空处理;
|
|
221
222
|
* 不给默认值:没用技能的定义保存前后不变,不会因此多出一个版本。
|
|
222
|
-
* `writePolicy
|
|
223
|
-
*
|
|
223
|
+
* `writePolicy`: whether registering a skill on a private agent (`agents.register-skill`) needs
|
|
224
|
+
* the person's confirmation; absent means `allow` (not stored in the definition). On a shared
|
|
225
|
+
* agent a registration is always confirmed at the org level.
|
|
224
226
|
*/
|
|
225
227
|
skills: z
|
|
226
228
|
.object({
|
|
@@ -412,6 +414,34 @@ export function normalizeToolKeys(definition, scope, inherited) {
|
|
|
412
414
|
}
|
|
413
415
|
return changed ? next : definition;
|
|
414
416
|
}
|
|
417
|
+
/**
|
|
418
|
+
* `definition` without the `toolPolicies` keys (its own and its inline sub-agents') of builtins
|
|
419
|
+
* that no longer exist ({@link isRemovedBuiltinKey}): applied to a definition being saved, so a
|
|
420
|
+
* key left over from a removed builtin never blocks the save. Returns the same object when
|
|
421
|
+
* nothing is dropped.
|
|
422
|
+
*/
|
|
423
|
+
export function withoutRemovedBuiltinPolicies(definition) {
|
|
424
|
+
const strip = (p) => {
|
|
425
|
+
if (p === undefined || !Object.keys(p).some(isRemovedBuiltinKey))
|
|
426
|
+
return p;
|
|
427
|
+
return Object.fromEntries(Object.entries(p).filter(([k]) => !isRemovedBuiltinKey(k)));
|
|
428
|
+
};
|
|
429
|
+
let next = definition;
|
|
430
|
+
const policies = strip(definition.toolPolicies);
|
|
431
|
+
if (policies !== definition.toolPolicies)
|
|
432
|
+
next = { ...next, toolPolicies: policies };
|
|
433
|
+
const inline = definition.subagents?.inline;
|
|
434
|
+
if (inline !== undefined) {
|
|
435
|
+
const stripped = inline.map((t) => {
|
|
436
|
+
const p = strip(t.toolPolicies);
|
|
437
|
+
return p === t.toolPolicies ? t : { ...t, toolPolicies: p };
|
|
438
|
+
});
|
|
439
|
+
if (stripped.some((t, i) => t !== inline[i])) {
|
|
440
|
+
next = { ...next, subagents: { ...definition.subagents, inline: stripped } };
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
return next;
|
|
444
|
+
}
|
|
415
445
|
/**
|
|
416
446
|
* Does a stored definition or template hold an rc `<x>__<tool>` key, which only a
|
|
417
447
|
* {@link LegacyScope} can map? (Callers load the scope only then.)
|
|
@@ -23,6 +23,7 @@ import { TransactionHost } from "../infra/database/transaction-host.js";
|
|
|
23
23
|
import { Transactional } from "../infra/database/transactional.decorator.js";
|
|
24
24
|
import { OrgLock } from "../orgs/org-lock.js";
|
|
25
25
|
import { skillVersionsById } from "../skills/usable.js";
|
|
26
|
+
import { isRemovedBuiltinKey } from "../tools/builtin-names.js";
|
|
26
27
|
import { canonicalJson } from "../tools/canonical-json.js";
|
|
27
28
|
import { ToolRegistry } from "../tools/registry.js";
|
|
28
29
|
import { AgentLookup } from "./agent-lookup.js";
|
|
@@ -204,6 +205,9 @@ extensionTools = []) {
|
|
|
204
205
|
// is not carried into it
|
|
205
206
|
if (v === "allow")
|
|
206
207
|
continue;
|
|
208
|
+
// a removed builtin's policy left in a stored definition is not carried
|
|
209
|
+
if (isRemovedBuiltinKey(k))
|
|
210
|
+
continue;
|
|
207
211
|
const parsed = parseToolKey(k);
|
|
208
212
|
if (parsed !== null && parsed.kind !== "connector" && TOOL_KEY_RE.test(k)) {
|
|
209
213
|
toolPolicies[k] = v;
|
|
@@ -33,7 +33,7 @@ export declare const TASK_ID_RE: RegExp;
|
|
|
33
33
|
* hand) to `.capybara/tasks/<id>.sh` and starts it with bash (the shell a foreground `Bash` call
|
|
34
34
|
* runs its command in) in a session of its own under `timeout -s KILL <seconds>`, with its exit
|
|
35
35
|
* status written to `<id>.exit` and its output to `<id>.out`; writes the session id (`$!`, the pid
|
|
36
|
-
* `setsid` started) to `<id>.sid` and prints it.
|
|
36
|
+
* `setsid` started) to `<id>.sid` and prints it. An environment without one of the needed programs fails with a readable error on
|
|
37
37
|
* stderr and exit code 127, before anything starts. `id` must be a task id and `seconds` a
|
|
38
38
|
* positive integer (both go into the script unquoted).
|
|
39
39
|
*/
|
|
@@ -34,7 +34,7 @@ export function newTaskId() {
|
|
|
34
34
|
out += BASE32[b & 31];
|
|
35
35
|
return out;
|
|
36
36
|
}
|
|
37
|
-
/** The programs the launch and the later polls and stops need
|
|
37
|
+
/** The programs the launch and the later polls and stops need in the environment. */
|
|
38
38
|
const REQUIRED = ["bash", "setsid", "nohup", "timeout", "pgrep", "pkill", "base64"];
|
|
39
39
|
/** A task id ({@link newTaskId}). */
|
|
40
40
|
export const TASK_ID_RE = /^bt_[a-z2-7]{6}$/;
|
|
@@ -43,7 +43,7 @@ export const TASK_ID_RE = /^bt_[a-z2-7]{6}$/;
|
|
|
43
43
|
* hand) to `.capybara/tasks/<id>.sh` and starts it with bash (the shell a foreground `Bash` call
|
|
44
44
|
* runs its command in) in a session of its own under `timeout -s KILL <seconds>`, with its exit
|
|
45
45
|
* status written to `<id>.exit` and its output to `<id>.out`; writes the session id (`$!`, the pid
|
|
46
|
-
* `setsid` started) to `<id>.sid` and prints it.
|
|
46
|
+
* `setsid` started) to `<id>.sid` and prints it. An environment without one of the needed programs fails with a readable error on
|
|
47
47
|
* stderr and exit code 127, before anything starts. `id` must be a task id and `seconds` a
|
|
48
48
|
* positive integer (both go into the script unquoted).
|
|
49
49
|
*/
|
|
@@ -57,7 +57,7 @@ export function launchScript(id, command, seconds) {
|
|
|
57
57
|
const t = `${TASKS_DIR}/${id}`;
|
|
58
58
|
return [
|
|
59
59
|
"set -e",
|
|
60
|
-
`for c in ${REQUIRED.join(" ")}; do command -v "$c" >/dev/null 2>&1 || { echo "Background tasks need ${REQUIRED.join(", ")} in
|
|
60
|
+
`for c in ${REQUIRED.join(" ")}; do command -v "$c" >/dev/null 2>&1 || { echo "Background tasks need ${REQUIRED.join(", ")} in this environment; $c is missing." >&2; exit 127; }; done`,
|
|
61
61
|
`mkdir -p ${TASKS_DIR}`,
|
|
62
62
|
`printf '%s' '${b64}' | base64 -d > ${t}.sh`,
|
|
63
63
|
`setsid nohup sh -c 'timeout -s KILL ${seconds} bash ${t}.sh; echo $? > ${t}.exit' > ${t}.out 2>&1 < /dev/null &`,
|
|
@@ -24,12 +24,29 @@ export interface BackgroundTaskView {
|
|
|
24
24
|
/** True while a stop has claimed the task and it is still running. */
|
|
25
25
|
stopping: boolean;
|
|
26
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Why a task's output cannot be read live: `starting` (the launch has not recorded the task's
|
|
29
|
+
* session yet), `not_started` (the launch failed: the task never ran, its `lostReason` says why),
|
|
30
|
+
* `device_offline` (the local device's machine is not connected), `suspended` (the instance is
|
|
31
|
+
* suspended; reading never resumes it), `rebuilt` (the instance the task ran in is gone, and its
|
|
32
|
+
* files with it), `output_missing` (the instance runs but the output file is gone), `no_access`
|
|
33
|
+
* (the conversation's owner may no longer use the device), `unavailable` (the device's provider
|
|
34
|
+
* is not loaded), `read_failed` (the read command failed).
|
|
35
|
+
*/
|
|
36
|
+
export type OutputUnavailableReason = "starting" | "not_started" | "device_offline" | "suspended" | "rebuilt" | "output_missing" | "no_access" | "unavailable" | "read_failed";
|
|
37
|
+
/** Why the live read failed, and where the task ran: a `local` device or a `cloud` sandbox. */
|
|
38
|
+
export interface OutputUnavailable {
|
|
39
|
+
reason: OutputUnavailableReason;
|
|
40
|
+
environment: "local" | "cloud";
|
|
41
|
+
}
|
|
27
42
|
export interface BackgroundTaskOutput {
|
|
28
43
|
text: string;
|
|
29
44
|
/** True when `text` is only the end of the output (or, for `event`, the last lines). */
|
|
30
45
|
truncated: boolean;
|
|
31
46
|
/** `live`: read from the running sandbox; `event`: the last lines the finished event kept. */
|
|
32
47
|
source: "live" | "event";
|
|
48
|
+
/** With `event`: why the output was not read live. */
|
|
49
|
+
unavailable?: OutputUnavailable;
|
|
33
50
|
}
|
|
34
51
|
/**
|
|
35
52
|
* The HTTP side of background tasks (spec 2026-11-05-background-bash-design §5): the tasks of a
|
|
@@ -69,9 +86,24 @@ export declare class BackgroundTasks {
|
|
|
69
86
|
private deviceUsable;
|
|
70
87
|
/**
|
|
71
88
|
* The end of the task's output, at most `tail` bytes. Read live when the task's device
|
|
72
|
-
* instance runs (never resuming a suspended one); a finished task
|
|
73
|
-
* with the last lines its finished event kept. Anything else is
|
|
74
|
-
* `background_task.output_unavailable` (409)
|
|
89
|
+
* instance runs (never resuming a suspended one); a finished task that cannot be read live
|
|
90
|
+
* answers with the last lines its finished event kept and why (`unavailable`). Anything else is
|
|
91
|
+
* `background_task.output_unavailable` (409) with `details: [{ reason, environment }]`.
|
|
75
92
|
*/
|
|
76
93
|
output(orgId: string, conversationId: string, accountId: string, taskId: string, tail: number): Promise<BackgroundTaskOutput>;
|
|
94
|
+
/** Whether the task ran on a local device (its machine) or in a cloud sandbox. */
|
|
95
|
+
private environmentOf;
|
|
96
|
+
/** Reads the output from the task's running instance, or says why it cannot. */
|
|
97
|
+
private readLive;
|
|
77
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* Prints `size <bytes>` and `out <base64 of the last `tail` bytes>`, or `missing` when the output
|
|
101
|
+
* file is not there. Relative to the workspace root; POSIX tools only (`wc -c`, `tail -c`,
|
|
102
|
+
* `base64`, `tr`), so it reads the same on Linux and macOS.
|
|
103
|
+
*/
|
|
104
|
+
export declare function tailScript(id: string, tail: number): string;
|
|
105
|
+
/**
|
|
106
|
+
* Decodes {@link tailScript}'s output (`missing`: no output file; null: unreadable); a cut that
|
|
107
|
+
* splits a UTF-8 character drops its tail bytes.
|
|
108
|
+
*/
|
|
109
|
+
export declare function parseTail(stdout: string, tail: number): Omit<BackgroundTaskOutput, "source"> | "missing" | null;
|