@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.
Files changed (141) hide show
  1. package/bootstrap.json +25 -25
  2. package/dist/admin/admin-agents.controller.d.ts +28 -0
  3. package/dist/admin/admin-agents.controller.js +79 -0
  4. package/dist/admin/admin-connectors.controller.d.ts +29 -0
  5. package/dist/admin/admin-connectors.controller.js +79 -0
  6. package/dist/admin/admin-items.schemas.d.ts +42 -0
  7. package/dist/admin/admin-items.schemas.js +48 -0
  8. package/dist/admin/admin.controller.d.ts +11 -20
  9. package/dist/admin/admin.controller.js +28 -34
  10. package/dist/admin/admin.module.d.ts +4 -1
  11. package/dist/admin/admin.module.js +50 -4
  12. package/dist/admin/admin.schemas.d.ts +4 -0
  13. package/dist/admin/admin.schemas.js +4 -1
  14. package/dist/admin/audit.controller.d.ts +14 -0
  15. package/dist/admin/audit.controller.js +42 -0
  16. package/dist/admin/audit.schemas.d.ts +8 -0
  17. package/dist/admin/audit.schemas.js +17 -0
  18. package/dist/admin/health.service.d.ts +104 -0
  19. package/dist/admin/health.service.js +246 -0
  20. package/dist/admin/overview.controller.d.ts +31 -0
  21. package/dist/admin/overview.controller.js +78 -0
  22. package/dist/admin/overview.schemas.d.ts +8 -0
  23. package/dist/admin/overview.schemas.js +13 -0
  24. package/dist/admin/overview.service.d.ts +112 -0
  25. package/dist/admin/overview.service.js +223 -0
  26. package/dist/admin/platform-audit.service.d.ts +41 -0
  27. package/dist/admin/platform-audit.service.js +70 -0
  28. package/dist/admin/usage.service.d.ts +149 -0
  29. package/dist/admin/usage.service.js +321 -0
  30. package/dist/agents/admin-agents.d.ts +92 -0
  31. package/dist/agents/admin-agents.js +170 -0
  32. package/dist/agents/agents.module.d.ts +4 -3
  33. package/dist/agents/agents.module.js +7 -3
  34. package/dist/agents/agents.service.d.ts +41 -3
  35. package/dist/agents/agents.service.js +113 -27
  36. package/dist/agents/definition.d.ts +16 -0
  37. package/dist/agents/definition.js +32 -2
  38. package/dist/agents/org-templates.js +4 -0
  39. package/dist/background/launch.d.ts +1 -1
  40. package/dist/background/launch.js +3 -3
  41. package/dist/background/tasks.service.d.ts +35 -3
  42. package/dist/background/tasks.service.js +91 -36
  43. package/dist/catalog/catalog.module.js +8 -2
  44. package/dist/channels/channels.module.js +11 -2
  45. package/dist/channels/supervisor.d.ts +2 -0
  46. package/dist/channels/supervisor.js +4 -0
  47. package/dist/conversations/events.js +1 -1
  48. package/dist/conversations/titles.js +4 -8
  49. package/dist/db/kernel-schema.d.ts +8 -1
  50. package/dist/devices/devices.module.js +5 -2
  51. package/dist/errors.d.ts +22 -2
  52. package/dist/errors.js +22 -2
  53. package/dist/evals/evals.module.js +11 -2
  54. package/dist/extensions/permission-view.js +3 -3
  55. package/dist/http/api.module.js +10 -1
  56. package/dist/identity/settings.js +3 -2
  57. package/dist/identity/write-actor.d.ts +11 -0
  58. package/dist/identity/write-actor.js +7 -0
  59. package/dist/inbox/inbox.service.d.ts +2 -1
  60. package/dist/inbox/platform-inbox.service.d.ts +7 -1
  61. package/dist/inbox/platform-inbox.service.js +20 -2
  62. package/dist/infra/presence/presence.module.d.ts +6 -0
  63. package/dist/infra/presence/presence.module.js +22 -0
  64. package/dist/infra/presence/replica-presence.d.ts +87 -0
  65. package/dist/infra/presence/replica-presence.js +189 -0
  66. package/dist/kernel.module.js +3 -0
  67. package/dist/lifecycle/kernel-lifecycle.d.ts +3 -1
  68. package/dist/lifecycle/kernel-lifecycle.js +17 -3
  69. package/dist/local-devices/local-device-gateway.d.ts +6 -0
  70. package/dist/local-devices/local-device-gateway.js +12 -0
  71. package/dist/overrides.d.ts +7 -1
  72. package/dist/overrides.js +4 -0
  73. package/dist/platform/exclusions.js +15 -0
  74. package/dist/platform/operation.d.ts +1 -1
  75. package/dist/platform/operation.js +1 -1
  76. package/dist/platform/ops/admin-items.ops.d.ts +37 -0
  77. package/dist/platform/ops/admin-items.ops.js +278 -0
  78. package/dist/platform/ops/admin.ops.d.ts +10 -10
  79. package/dist/platform/ops/admin.ops.js +111 -42
  80. package/dist/platform/ops/agent-skills.ops.d.ts +29 -3
  81. package/dist/platform/ops/agent-skills.ops.js +290 -7
  82. package/dist/platform/ops/inbox.ops.js +7 -4
  83. package/dist/platform/ops/platform.ops.js +1 -1
  84. package/dist/platform/ops/skills.ops.js +1 -57
  85. package/dist/platform/platform-ops.module.d.ts +2 -1
  86. package/dist/platform/platform-ops.module.js +5 -0
  87. package/dist/platform/route-schemas.js +8 -0
  88. package/dist/roles/gateway-role.d.ts +4 -0
  89. package/dist/roles/gateway-role.js +8 -0
  90. package/dist/runtime/run-jobs.js +4 -2
  91. package/dist/sandbox/sandbox-file-reader.d.ts +35 -0
  92. package/dist/sandbox/sandbox-file-reader.js +98 -0
  93. package/dist/skills/agent-skills.controller.d.ts +48 -0
  94. package/dist/skills/agent-skills.controller.js +131 -0
  95. package/dist/skills/agent-skills.service.d.ts +262 -0
  96. package/dist/skills/agent-skills.service.js +996 -0
  97. package/dist/skills/catalog.d.ts +8 -6
  98. package/dist/skills/catalog.js +8 -4
  99. package/dist/skills/changes.controller.d.ts +5 -22
  100. package/dist/skills/changes.controller.js +7 -55
  101. package/dist/skills/changes.service.d.ts +69 -115
  102. package/dist/skills/changes.service.js +249 -599
  103. package/dist/skills/conversation-catalog.d.ts +53 -0
  104. package/dist/skills/conversation-catalog.js +149 -0
  105. package/dist/skills/invocation-keys.d.ts +1 -1
  106. package/dist/skills/invocation-keys.js +1 -1
  107. package/dist/skills/package.d.ts +0 -5
  108. package/dist/skills/package.js +0 -14
  109. package/dist/skills/skills.controller.js +1 -10
  110. package/dist/skills/skills.module.js +15 -2
  111. package/dist/skills/skills.schemas.d.ts +11 -15
  112. package/dist/skills/skills.schemas.js +27 -20
  113. package/dist/skills/skills.service.d.ts +37 -30
  114. package/dist/skills/skills.service.js +107 -59
  115. package/dist/skills/usable.d.ts +4 -3
  116. package/dist/skills/usable.js +13 -4
  117. package/dist/tools/admin-connectors.d.ts +69 -0
  118. package/dist/tools/admin-connectors.js +135 -0
  119. package/dist/tools/builtin-names.d.ts +9 -14
  120. package/dist/tools/builtin-names.js +13 -22
  121. package/dist/tools/builtins/builtin-tools.d.ts +2 -3
  122. package/dist/tools/builtins/builtin-tools.js +21 -26
  123. package/dist/tools/builtins/sandbox/bash.js +2 -2
  124. package/dist/tools/builtins/skill-load.js +1 -0
  125. package/dist/tools/connectors.controller.d.ts +5 -1
  126. package/dist/tools/connectors.controller.js +17 -3
  127. package/dist/tools/connectors.service.d.ts +20 -1
  128. package/dist/tools/connectors.service.js +61 -0
  129. package/dist/tools/legacy-keys.js +0 -1
  130. package/dist/tools/org-tool-switches.d.ts +3 -2
  131. package/dist/tools/org-tool-switches.js +4 -4
  132. package/dist/tools/policy.d.ts +7 -9
  133. package/dist/tools/policy.js +11 -22
  134. package/dist/tools/tools.module.js +3 -0
  135. package/package.json +1 -1
  136. package/src/migrations/0109_audit_platform_idx.sql +5 -0
  137. package/src/migrations/0110_usage_time_idx.sql +8 -0
  138. package/src/migrations/0111_agent_skill_sharing.sql +22 -0
  139. package/src/migrations/0112_skill_share_requests.sql +4 -0
  140. package/dist/tools/builtins/skill-write.d.ts +0 -59
  141. package/dist/tools/builtins/skill-write.js +0 -244
@@ -0,0 +1,35 @@
1
+ import { ConversationDevices } from "../devices/conversation-devices.js";
2
+ import { KernelDb } from "../infra/database/kernel-db.js";
3
+ import type { KernelLogger } from "../log.js";
4
+ import { SandboxService } from "./sandbox.service.js";
5
+ /** What {@link SandboxFileReader.read} reads. */
6
+ export interface SandboxFileReadInput {
7
+ orgId: string;
8
+ /** The conversation whose sandbox holds the file (a sub-agent: its root's device). */
9
+ conversationId: string;
10
+ /** The account the read is made for (the sandbox actor). */
11
+ actorAccountId: string;
12
+ /** An absolute path under the sandbox roots, chosen by the model: untrusted. */
13
+ path: string;
14
+ /** The largest file accepted, in bytes. */
15
+ maxBytes: number;
16
+ signal: AbortSignal;
17
+ }
18
+ /**
19
+ * Reads one file from a conversation's sandbox for the kernel (skill authoring spec: a package
20
+ * the agent built is registered from its sandbox). The same stat -> readFile sequence as the
21
+ * `Deliver` tool, with the same path checks, in the device of the conversation's tree (holder =
22
+ * the root conversation); no attachment sync and no `device_paths` record.
23
+ *
24
+ * Throws `ApiError` 422 `sandbox.file_invalid_path` / `sandbox.file_not_found` /
25
+ * `sandbox.file_is_directory` / `sandbox.file_too_large`; provider and device failures go through
26
+ * `mapSandboxError` (an actionable `ToolError`).
27
+ */
28
+ export declare class SandboxFileReader {
29
+ private readonly db;
30
+ private readonly devices;
31
+ private readonly sandbox;
32
+ private readonly log;
33
+ constructor(db: KernelDb, devices: Pick<ConversationDevices, "forTool">, sandbox: Pick<SandboxService, "withSandbox">, log: Pick<KernelLogger, "warn">);
34
+ read(input: SandboxFileReadInput): Promise<Uint8Array>;
35
+ }
@@ -0,0 +1,98 @@
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 { ToolError } from "@capybara-hq/plugin-sdk";
11
+ import { Inject, Injectable } from "@nestjs/common";
12
+ import { ConversationDevices } from "../devices/conversation-devices.js";
13
+ import { ApiError } from "../errors.js";
14
+ import { KernelDb } from "../infra/database/kernel-db.js";
15
+ import { KERNEL_LOGGER } from "../infra/tokens.js";
16
+ import { rootConversationOf } from "../runtime/subagents/child.js";
17
+ import { checkPath, checkPathShape, clip, mapSandboxError, } from "../tools/builtins/sandbox/common.js";
18
+ import { SandboxService } from "./sandbox.service.js";
19
+ /**
20
+ * Reads one file from a conversation's sandbox for the kernel (skill authoring spec: a package
21
+ * the agent built is registered from its sandbox). The same stat -> readFile sequence as the
22
+ * `Deliver` tool, with the same path checks, in the device of the conversation's tree (holder =
23
+ * the root conversation); no attachment sync and no `device_paths` record.
24
+ *
25
+ * Throws `ApiError` 422 `sandbox.file_invalid_path` / `sandbox.file_not_found` /
26
+ * `sandbox.file_is_directory` / `sandbox.file_too_large`; provider and device failures go through
27
+ * `mapSandboxError` (an actionable `ToolError`).
28
+ */
29
+ let SandboxFileReader = class SandboxFileReader {
30
+ db;
31
+ devices;
32
+ sandbox;
33
+ log;
34
+ constructor(db, devices, sandbox, log) {
35
+ this.db = db;
36
+ this.devices = devices;
37
+ this.sandbox = sandbox;
38
+ this.log = log;
39
+ }
40
+ async read(input) {
41
+ const { orgId, path: given, maxBytes: max } = input;
42
+ // the shape is checked before the sandbox is entered (nothing starts for a bad path)
43
+ invalidPathAsApiError(() => checkPathShape(given));
44
+ try {
45
+ const root = await rootConversationOf(this.db.db, input.conversationId);
46
+ const deviceId = await this.devices.forTool(orgId, root);
47
+ return await this.sandbox.withSandbox({
48
+ orgId,
49
+ deviceId,
50
+ holderConversationId: root,
51
+ actor: { accountId: input.actorAccountId, conversationId: input.conversationId },
52
+ }, async (h, p, info, signal) => {
53
+ const path = invalidPathAsApiError(() => checkPath(given, info.roots));
54
+ const st = await p.stat(h, path, signal);
55
+ if (st === null)
56
+ throw notFound(path);
57
+ if (st.isDir)
58
+ throw isDirectory(path);
59
+ if (st.size > max)
60
+ throw tooLarge(path, st.size, max);
61
+ const got = await p.readFile(h, path, { maxBytes: max, signal });
62
+ if (got === null)
63
+ throw notFound(path);
64
+ // the file grew while it was read
65
+ if (got.truncated)
66
+ throw tooLarge(path, max + 1, max);
67
+ return got.data;
68
+ }, input.signal);
69
+ }
70
+ catch (e) {
71
+ if (e instanceof ApiError && e.code.startsWith("sandbox.file_"))
72
+ throw e;
73
+ throw mapSandboxError(e, this.log);
74
+ }
75
+ }
76
+ };
77
+ SandboxFileReader = __decorate([
78
+ Injectable(),
79
+ __param(0, Inject(KernelDb)),
80
+ __param(1, Inject(ConversationDevices)),
81
+ __param(2, Inject(SandboxService)),
82
+ __param(3, Inject(KERNEL_LOGGER))
83
+ ], SandboxFileReader);
84
+ export { SandboxFileReader };
85
+ /** Runs a path check of the sandbox tools, turning its `ToolError` into `sandbox.file_invalid_path`. */
86
+ function invalidPathAsApiError(check) {
87
+ try {
88
+ return check();
89
+ }
90
+ catch (e) {
91
+ if (e instanceof ToolError)
92
+ throw new ApiError("sandbox.file_invalid_path", e.message);
93
+ throw e;
94
+ }
95
+ }
96
+ const notFound = (path) => new ApiError("sandbox.file_not_found", `File not found in the sandbox: ${clip(path, 200)}. Check the path with Glob.`);
97
+ const isDirectory = (path) => new ApiError("sandbox.file_is_directory", `${clip(path, 200)} is a directory; pass one file, e.g. archive the directory first with Bash (zip -r) and retry.`);
98
+ const tooLarge = (path, size, max) => new ApiError("sandbox.file_too_large", `File ${clip(path, 200)} is ${size} bytes, over the limit of ${max} bytes; make it smaller and retry.`);
@@ -0,0 +1,48 @@
1
+ import type { z } from "zod";
2
+ import { AgentsService } from "../agents/agents.service.js";
3
+ import type { CreatedAttachment } from "../conversations/attachments.service.js";
4
+ import { type Principal } from "../identity/principal.js";
5
+ import { type OrgContext } from "../orgs/org.guard.js";
6
+ import { AgentSkillsService, type RegisterOutcome, type ShareOutcome } from "./agent-skills.service.js";
7
+ import { AgentSkillRegisterBody, AgentSkillUploadBody } from "./skills.schemas.js";
8
+ import { type SkillSummary, SkillsService } from "./skills.service.js";
9
+ /**
10
+ * An agent's own skills (`/api/v1/orgs/:orgId/agents/:agentId/skills`):
11
+ * - `GET /`: the list (`scope = agent`, live, plus the org skills the agent shared, `shared:
12
+ * true`; by name; `sharePending` marks a skill whose share request waits for an org admin),
13
+ * with the visibility of
14
+ * `GET /agents/:agentId` (`agents.use`; an agent not visible -> 404 `agent.not_found`; a service
15
+ * token sees only `visibility = 'org'` agents);
16
+ * - the writes are `agents.create` (HUMAN_ONLY) and only for the agent's current owner (others 403
17
+ * `forbidden`, checked in the service):
18
+ * 1. `POST /uploads` `{ name, size }` registers a skill package upload (`application/zip`,
19
+ * <= 50 MB, a presigned form as for org skills); agent skills are not governed by the
20
+ * deployment's `customSkills` setting;
21
+ * 2. the browser uploads straight to object storage;
22
+ * 3. `POST /` `{ uploadId }` unpacks and checks it, creating the agent's skill or its next
23
+ * version, or a version of a shared org skill the actor maintains (applied at once for an
24
+ * org admin, else proposed; 200 `{ skill }`; the upload is consumed);
25
+ * `DELETE /:skillId` soft-deletes one (a pending request of the same name is cancelled with it);
26
+ * `POST /:skillId/share` shares one with the org (200 `{ share }`: shared at once for an org
27
+ * admin, else a request the org admins decide).
28
+ */
29
+ export declare class AgentSkillsController {
30
+ private readonly agentSkills;
31
+ private readonly agents;
32
+ private readonly skills;
33
+ constructor(agentSkills: AgentSkillsService, agents: AgentsService, skills: SkillsService);
34
+ list(principal: Principal, org: OrgContext, agentId: string): Promise<{
35
+ skills: (SkillSummary & {
36
+ sharePending: boolean;
37
+ shared: boolean;
38
+ })[];
39
+ }>;
40
+ createUpload(principal: Principal, org: OrgContext, agentId: string, body: z.infer<typeof AgentSkillUploadBody>): Promise<CreatedAttachment>;
41
+ register(principal: Principal, org: OrgContext, agentId: string, body: z.infer<typeof AgentSkillRegisterBody>): Promise<{
42
+ skill: RegisterOutcome;
43
+ }>;
44
+ share(principal: Principal, org: OrgContext, agentId: string, skillId: string): Promise<{
45
+ share: ShareOutcome;
46
+ }>;
47
+ remove(principal: Principal, org: OrgContext, agentId: string, skillId: string): Promise<void>;
48
+ }
@@ -0,0 +1,131 @@
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 { Controller, Delete, Get, HttpCode, Inject, Param, Post } from "@nestjs/common";
11
+ import { AgentsService } from "../agents/agents.service.js";
12
+ import { CurrentPrincipal, patTokenId, requireUser, } from "../identity/principal.js";
13
+ import { createZodDto } from "../infra/validation/create-zod-dto.js";
14
+ import { ZBody } from "../infra/validation/zod-params.js";
15
+ import { CurrentOrg, OrgAction } from "../orgs/org.guard.js";
16
+ import { AgentSkillsService, } from "./agent-skills.service.js";
17
+ import { AgentSkillRegisterBody, AgentSkillUploadBody } from "./skills.schemas.js";
18
+ import { SkillsService } from "./skills.service.js";
19
+ class AgentSkillUploadBodyDto extends createZodDto(AgentSkillUploadBody, "AgentSkillUploadBody") {
20
+ }
21
+ class AgentSkillRegisterBodyDto extends createZodDto(AgentSkillRegisterBody, "AgentSkillRegisterBody") {
22
+ }
23
+ /**
24
+ * An agent's own skills (`/api/v1/orgs/:orgId/agents/:agentId/skills`):
25
+ * - `GET /`: the list (`scope = agent`, live, plus the org skills the agent shared, `shared:
26
+ * true`; by name; `sharePending` marks a skill whose share request waits for an org admin),
27
+ * with the visibility of
28
+ * `GET /agents/:agentId` (`agents.use`; an agent not visible -> 404 `agent.not_found`; a service
29
+ * token sees only `visibility = 'org'` agents);
30
+ * - the writes are `agents.create` (HUMAN_ONLY) and only for the agent's current owner (others 403
31
+ * `forbidden`, checked in the service):
32
+ * 1. `POST /uploads` `{ name, size }` registers a skill package upload (`application/zip`,
33
+ * <= 50 MB, a presigned form as for org skills); agent skills are not governed by the
34
+ * deployment's `customSkills` setting;
35
+ * 2. the browser uploads straight to object storage;
36
+ * 3. `POST /` `{ uploadId }` unpacks and checks it, creating the agent's skill or its next
37
+ * version, or a version of a shared org skill the actor maintains (applied at once for an
38
+ * org admin, else proposed; 200 `{ skill }`; the upload is consumed);
39
+ * `DELETE /:skillId` soft-deletes one (a pending request of the same name is cancelled with it);
40
+ * `POST /:skillId/share` shares one with the org (200 `{ share }`: shared at once for an org
41
+ * admin, else a request the org admins decide).
42
+ */
43
+ let AgentSkillsController = class AgentSkillsController {
44
+ agentSkills;
45
+ agents;
46
+ skills;
47
+ constructor(agentSkills, agents, skills) {
48
+ this.agentSkills = agentSkills;
49
+ this.agents = agents;
50
+ this.skills = skills;
51
+ }
52
+ async list(principal, org, agentId) {
53
+ await this.agents.get(org.orgId, agentId, principal.kind === "user" ? principal.account.id : null);
54
+ return { skills: await this.skills.listOwnForAgent(org.orgId, agentId) };
55
+ }
56
+ async createUpload(principal, org, agentId, body) {
57
+ const p = requireUser(principal);
58
+ return this.agentSkills.createAgentUpload(org.orgId, p.account.id, agentId, body);
59
+ }
60
+ async register(principal, org, agentId, body) {
61
+ const p = requireUser(principal);
62
+ return {
63
+ skill: await this.agentSkills.registerAgentUpload(org.orgId, p.account.id, agentId, body.uploadId, patTokenId(p)),
64
+ };
65
+ }
66
+ async share(principal, org, agentId, skillId) {
67
+ const p = requireUser(principal);
68
+ const actorTokenId = patTokenId(p);
69
+ return {
70
+ share: await this.agentSkills.share({
71
+ orgId: org.orgId,
72
+ agentId,
73
+ accountId: p.account.id,
74
+ ...(actorTokenId !== undefined && { actorTokenId }),
75
+ }, skillId),
76
+ };
77
+ }
78
+ async remove(principal, org, agentId, skillId) {
79
+ const p = requireUser(principal);
80
+ await this.agentSkills.deleteAgentSkill(org.orgId, agentId, skillId, p.account.id, patTokenId(p));
81
+ }
82
+ };
83
+ __decorate([
84
+ Get(),
85
+ OrgAction("agents.use"),
86
+ __param(0, CurrentPrincipal()),
87
+ __param(1, CurrentOrg()),
88
+ __param(2, Param("agentId"))
89
+ ], AgentSkillsController.prototype, "list", null);
90
+ __decorate([
91
+ Post("uploads"),
92
+ OrgAction("agents.create"),
93
+ __param(0, CurrentPrincipal()),
94
+ __param(1, CurrentOrg()),
95
+ __param(2, Param("agentId")),
96
+ __param(3, ZBody(AgentSkillUploadBodyDto))
97
+ ], AgentSkillsController.prototype, "createUpload", null);
98
+ __decorate([
99
+ Post(),
100
+ HttpCode(200),
101
+ OrgAction("agents.create"),
102
+ __param(0, CurrentPrincipal()),
103
+ __param(1, CurrentOrg()),
104
+ __param(2, Param("agentId")),
105
+ __param(3, ZBody(AgentSkillRegisterBodyDto))
106
+ ], AgentSkillsController.prototype, "register", null);
107
+ __decorate([
108
+ Post(":skillId/share"),
109
+ HttpCode(200),
110
+ OrgAction("agents.create"),
111
+ __param(0, CurrentPrincipal()),
112
+ __param(1, CurrentOrg()),
113
+ __param(2, Param("agentId")),
114
+ __param(3, Param("skillId"))
115
+ ], AgentSkillsController.prototype, "share", null);
116
+ __decorate([
117
+ Delete(":skillId"),
118
+ HttpCode(204),
119
+ OrgAction("agents.create"),
120
+ __param(0, CurrentPrincipal()),
121
+ __param(1, CurrentOrg()),
122
+ __param(2, Param("agentId")),
123
+ __param(3, Param("skillId"))
124
+ ], AgentSkillsController.prototype, "remove", null);
125
+ AgentSkillsController = __decorate([
126
+ Controller("api/v1/orgs/:orgId/agents/:agentId/skills"),
127
+ __param(0, Inject(AgentSkillsService)),
128
+ __param(1, Inject(AgentsService)),
129
+ __param(2, Inject(SkillsService))
130
+ ], AgentSkillsController);
131
+ export { AgentSkillsController };
@@ -0,0 +1,262 @@
1
+ import { AttachmentsService, type CreatedAttachment } from "../conversations/attachments.service.js";
2
+ import { EventLog } from "../conversations/event-log.js";
3
+ import type { KernelSchemaDb, SkillManifest } from "../db/kernel-schema.js";
4
+ import { KernelDb } from "../infra/database/kernel-db.js";
5
+ import { TransactionHost } from "../infra/database/transaction-host.js";
6
+ import type { KernelLogger } from "../log.js";
7
+ import { OrgLock } from "../orgs/org-lock.js";
8
+ import { StorageService } from "../storage/storage.service.js";
9
+ import { SkillsService } from "./skills.service.js";
10
+ /**
11
+ * The cap on an agent's own skills (`scope = agent`). Far below the catalog snapshot's cap
12
+ * (`SKILLS_CATALOG_MAX`), so normal use never truncates the catalog. Requests never add agent
13
+ * skills (a share moves an existing one out, a version proposal targets an org skill), so they
14
+ * do not count.
15
+ */
16
+ export declare const AGENT_SKILLS_MAX = 50;
17
+ /** Who registers or deletes an agent's skill, and from where. */
18
+ export interface AgentSkillActor {
19
+ orgId: string;
20
+ agentId: string;
21
+ /** The person acting (the run's trigger account for an agent). */
22
+ accountId: string;
23
+ /** The conversation that gets a catalog snapshot with the new version (agent registrations). */
24
+ conversationId?: string;
25
+ runId?: string | null;
26
+ actorTokenId?: string;
27
+ /** Merged into the audit detail (e.g. `viaAgent(...)`). */
28
+ auditDetail?: Record<string, unknown>;
29
+ }
30
+ /** The result of {@link AgentSkillsService.register}. */
31
+ export type RegisterOutcome = {
32
+ kind: "version";
33
+ skillId: string;
34
+ name: string;
35
+ version: number;
36
+ created: boolean;
37
+ /** The version is manual-only: not in the model's catalog. */
38
+ manualOnly: boolean;
39
+ } | {
40
+ /** A version proposal on a shared org skill the actor maintains, for an org admin to decide. */
41
+ kind: "proposed";
42
+ requestId: string;
43
+ skillId: string;
44
+ name: string;
45
+ version: number;
46
+ manualOnly: boolean;
47
+ };
48
+ /** The result of {@link AgentSkillsService.share}. */
49
+ export type ShareOutcome = {
50
+ kind: "shared";
51
+ skillId: string;
52
+ } | {
53
+ kind: "requested";
54
+ requestId: string;
55
+ };
56
+ /** Whether the account is an admin (or owner) of the org, read live. */
57
+ export declare function isOrgAdmin(db: KernelSchemaDb, orgId: string, accountId: string): Promise<boolean>;
58
+ /**
59
+ * In the caller's transaction, refuses sharing a skill named `name` when the org already has a
60
+ * live org skill of that name or a platform skill of that name is offered to the org (409
61
+ * `skill.name_exists`): skill names are unique in an org.
62
+ */
63
+ export declare function assertShareableName(db: KernelDb, orgId: string, name: string): Promise<void>;
64
+ /**
65
+ * In the caller's transaction (the skill row locked), turns an agent skill into an org skill: the
66
+ * org owns it, `agent_id` is cleared, the agent it came from and its maintainer are recorded.
67
+ * Its versions stay.
68
+ */
69
+ export declare function shareSkillIn(db: KernelDb, v: {
70
+ skillId: string;
71
+ agentId: string;
72
+ maintainerAccountId: string;
73
+ }): Promise<void>;
74
+ /** The body of a skill's SKILL.md (as run start would read it); null if unparsable. */
75
+ export declare function skillMdBody(files: readonly {
76
+ path: string;
77
+ data: Uint8Array;
78
+ }[]): string | null;
79
+ /**
80
+ * Takes a KEY SHARE lock on the org row before the agent row is locked: the order `AgentsService`
81
+ * uses (org row FOR UPDATE -> agent row). Without it, a transaction holding the agent row that
82
+ * inserts a row referencing the org (the foreign key takes KEY SHARE) can deadlock with it (40P01).
83
+ */
84
+ export declare function keyShareOrg(db: KernelDb, orgId: string): Promise<void>;
85
+ /**
86
+ * The cap on new agent skills: at {@link AGENT_SKILLS_MAX} live own skills -> 409
87
+ * `skill.agent_cap`. Runs in the caller's transaction.
88
+ */
89
+ export declare function assertBelowAgentSkillCap(db: KernelDb, agentId: string): Promise<void>;
90
+ /** Writes a version of an agent skill and points `current_version` at it (creating the skill). */
91
+ export declare function createAgentSkillVersion(db: KernelDb, v: {
92
+ orgId: string;
93
+ agentId: string;
94
+ skillId: string;
95
+ exists: boolean;
96
+ version: number;
97
+ manifest: SkillManifest;
98
+ prefix: string;
99
+ createdBy: string;
100
+ }): Promise<void>;
101
+ /**
102
+ * An agent's own skills (`scope = agent`): only the agent's current owner (read live) registers
103
+ * a zip package as one of them, deletes one or shares one with the org; anyone else gets 403
104
+ * `forbidden` (404 `agent.not_found` when the agent is not visible to them).
105
+ *
106
+ * `register` reads the zip with the one package reader (`readSkillPackage`), writes the files under
107
+ * a fresh `skills/<id>/` prefix before the transaction, then in one transaction (lock order org ->
108
+ * agent -> conversation -> skill rows -> request rows) creates the skill or its next version,
109
+ * audits, and appends a `skills_catalog` snapshot to `conversationId` when given so the same run
110
+ * can `Skill` it at its next step. On failure the prefix is deleted. A package named like a shared
111
+ * org skill the actor maintains (and the agent has no own skill of that name) is a new version of
112
+ * that org skill instead (spec §5.5): an org admin's applies at once, anyone else's becomes a
113
+ * version proposal an org admin decides, tried only in the proposing conversation.
114
+ *
115
+ * `share` (spec §5.4) turns an agent skill into an org skill at once when the owner is an org
116
+ * admin, else records a share request for the org admins. All state is in PG and object storage.
117
+ */
118
+ export declare class AgentSkillsService {
119
+ private readonly db;
120
+ private readonly storage;
121
+ private readonly eventLog;
122
+ private readonly log;
123
+ readonly txHost: TransactionHost;
124
+ /** An org admin maintainer's version of a shared skill is an org skill version. */
125
+ private readonly skills;
126
+ private readonly attachments;
127
+ private readonly orgLock;
128
+ private readonly catalogDeps;
129
+ constructor(db: KernelDb, storage: StorageService, eventLog: EventLog, log: KernelLogger, txHost: TransactionHost,
130
+ /** An org admin maintainer's version of a shared skill is an org skill version. */
131
+ skills: SkillsService, attachments: AttachmentsService, orgLock: OrgLock);
132
+ /**
133
+ * Registers a skill package upload onto an agent (presigned, `application/zip`, <= 50 MB): only
134
+ * the agent's current owner (403 `forbidden`, 404 `agent.not_found` when not visible). Agent
135
+ * skills are not governed by the deployment's `customSkills` setting, so it is not checked.
136
+ */
137
+ createAgentUpload(orgId: string, accountId: string, agentId: string, input: {
138
+ name: string;
139
+ size: number;
140
+ }): Promise<CreatedAttachment>;
141
+ /**
142
+ * Registers an uploaded package as one of the agent's own skills ({@link register}): the owner
143
+ * is checked before the upload is read (a non-owner learns nothing about it), the zip is read
144
+ * from the uploader's own upload (`attachment.not_found` otherwise) and the upload is consumed
145
+ * in the register transaction (of concurrent requests for it only one registers).
146
+ */
147
+ registerAgentUpload(orgId: string, accountId: string, agentId: string, uploadId: string, actorTokenId?: string): Promise<RegisterOutcome>;
148
+ /**
149
+ * Registers a zip package as one of the agent's own skills, or as a new version of a shared org
150
+ * skill the actor maintains (see the class comment). `opts.inTransaction` runs in the register
151
+ * transaction after the agent row lock, before the skill rows (e.g. consuming the upload the zip
152
+ * came from, so a concurrent retry of the same upload registers nothing).
153
+ *
154
+ * A version proposal refuses with 409 `skill.proposal_pending_elsewhere` while another
155
+ * conversation (or another agent of the maintainer) has a pending proposal of that skill; one
156
+ * pending from the same conversation is superseded (cancelled, its draft deleted). Versions of a
157
+ * shared skill (proposed or applied) are refused while `customSkills` is denied. A new own skill
158
+ * named like an org skill the agent uses (not maintained by the actor) -> 409
159
+ * `skill.name_exists`. A new version of an own skill cancels its pending share request.
160
+ */
161
+ register(actor: AgentSkillActor, zip: Uint8Array, opts?: {
162
+ inTransaction?: () => Promise<void>;
163
+ }): Promise<RegisterOutcome>;
164
+ /**
165
+ * Shares one of the agent's own skills with the org (only the agent's current owner; the
166
+ * deployment's `customSkills` must allow it, else 403 `catalog.custom_disabled`). The org may
167
+ * not already have a skill of that name (409 `skill.name_exists`).
168
+ * - An org admin owner: in one transaction the skill becomes an org skill (`scope = org`,
169
+ * `agent_id` cleared, `origin_agent_id` = the agent, `maintainer_account_id` = the actor);
170
+ * audit `skill.shared`. Its id, versions and files stay, and the agent keeps resolving it.
171
+ * - Anyone else: a share request for the org admins (`kind = 'share'`, the current version, no
172
+ * new files: its prefix is the version's and never deleted); a pending one -> 409
173
+ * `skill.share_pending`. Audit `skill.share_requested`.
174
+ */
175
+ share(actor: AgentSkillActor, skillId: string): Promise<ShareOutcome>;
176
+ /**
177
+ * Soft-deletes one of the agent's own skills (only the agent's current owner; others 403
178
+ * `forbidden`): missing / already deleted / not this agent's -> `skill.not_found`. A pending
179
+ * request of the same name is cancelled with it (a version draft: a reverting snapshot in the
180
+ * requesting conversation, its draft deleted after the commit; a share request keeps its
181
+ * files). Audits `skill.deleted`. Every conversation drops it at its next run start; the name
182
+ * can then be registered again as a new skill.
183
+ */
184
+ deleteAgentSkill(orgId: string, agentId: string, skillId: string, accountId: string, actorTokenId?: string, auditDetail?: Record<string, unknown>): Promise<void>;
185
+ /**
186
+ * The owner check without a lock (operation prechecks, and before storage writes): a live agent
187
+ * of the org whose current owner is `accountId`; visible but not owned -> 403 `forbidden`, not
188
+ * visible -> 404 `agent.not_found`.
189
+ */
190
+ assertOwner(orgId: string, agentId: string, accountId: string): Promise<void>;
191
+ /**
192
+ * Whether the account maintains a live shared org skill and is an org admin, read live: then a
193
+ * registration may apply a version to an org skill at once (the Platform operation's
194
+ * confirmation class is `org` for it, before the package name is known).
195
+ */
196
+ mayApplyOrgVersions(orgId: string, accountId: string): Promise<boolean>;
197
+ /** Reads the agent row (locked FOR NO KEY UPDATE when `lock`) and applies the owner check. */
198
+ private checkOwner;
199
+ /**
200
+ * The live shared org skill named `name` that `accountId` maintains, when the agent has no own
201
+ * skill of that name (locked FOR UPDATE when `lock`, after the own skill row was looked at).
202
+ */
203
+ private maintainedSkill;
204
+ /**
205
+ * Whether registering `name` applies a version to a shared org skill at once: the actor, an org
206
+ * admin, maintains a live org skill of that name and the agent has no own skill of it.
207
+ */
208
+ private appliesToOrgSkill;
209
+ /**
210
+ * An org admin maintainer's new version of a shared skill: written as an org skill version
211
+ * (`SkillsService.createVersionFromFiles`, which re-checks `skills.manage` under the org row
212
+ * lock and handles a skill the org promoted), then the registering conversation's catalog gets
213
+ * it at once. {@link claimIn} ran first.
214
+ */
215
+ private applyOrgVersion;
216
+ /**
217
+ * Before {@link applyOrgVersion} writes the version, in a transaction of its own: re-checks the
218
+ * owner (agent row lock) and that the actor still maintains the org skill of that name with no
219
+ * own skill of it (skill row lock); cancels the skill's pending version proposals (an older one
220
+ * must not overwrite this version later: reverting snapshots, drafts deleted after the commit)
221
+ * and runs `inTransaction` (consuming the upload). Returns null, cancelling and consuming
222
+ * nothing, when the skill no longer qualifies or a proposal appeared in a conversation that was
223
+ * not locked first (the caller registers it the ordinary way).
224
+ * Lock order org -> agent -> the proposals' conversations -> skill row -> request rows.
225
+ */
226
+ private claimIn;
227
+ /**
228
+ * After {@link applyOrgVersion}: appends a snapshot to the conversation (of this org and agent)
229
+ * with the org skill's new version. A version served from the platform row (a skill the org
230
+ * promoted) has no version row of the org: nothing is appended, the next run start resolves it.
231
+ */
232
+ private appendAppliedIn;
233
+ /**
234
+ * Locks the conversation row (the snapshot append's lock): a conversation of this org with this
235
+ * agent only (defense in depth: callers pass the run's own), else `conversation.not_found`.
236
+ */
237
+ private lockConversation;
238
+ /** The transaction of {@link register}. */
239
+ private registerIn;
240
+ /**
241
+ * Spec §5.2: an agent's own skill may not take the name of an org skill the agent uses (selected
242
+ * in its current definition, or one it shared) unless the actor maintains it (then the
243
+ * registration is a version of it): 409 `skill.name_exists`.
244
+ */
245
+ private assertNoUsedOrgSkillNamed;
246
+ /**
247
+ * In {@link registerIn} (the org skill row locked): a version proposal on the shared skill. The
248
+ * pending proposals of that skill (and any pending request of this agent with the name) are
249
+ * locked: one from another conversation refuses, ones from this conversation are superseded.
250
+ * The trial applies only in the proposing conversation (the catalog overlay reads pending
251
+ * `version` requests by conversation).
252
+ */
253
+ private proposeIn;
254
+ /** The transaction of {@link share} (lock order org -> agent -> skill row -> request rows). */
255
+ private shareIn;
256
+ /** The transaction of {@link deleteAgentSkill}. */
257
+ private deleteAgentSkillIn;
258
+ /** Writes the files under `prefix` in the org's kernel space, a few at a time. */
259
+ private putFiles;
260
+ /** Deletes objects best effort (a failure is only logged: org data cleanup removes orphans). */
261
+ private deletePrefix;
262
+ }