@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
@@ -16,19 +16,22 @@ import { PLATFORM_OWNER } from "../catalog/ownership.js";
16
16
  import { PlatformComponents } from "../catalog/platform-components.js";
17
17
  import { assertItemInstallable } from "../catalog/request-rules.js";
18
18
  import { AttachmentsService, SKILL_PACKAGE_CONTENT_TYPE, skillFilesPrefix, } from "../conversations/attachments.service.js";
19
+ import { EventLog } from "../conversations/event-log.js";
19
20
  import { ApiError } from "../errors.js";
20
21
  import { audit } from "../identity/audit.js";
21
22
  import { assertCustomAllowed } from "../identity/settings.js";
22
23
  import { notifySharedUpdate } from "../inbox/org-notices.js";
24
+ import { afterCommit } from "../infra/database/after-commit.js";
23
25
  import { KernelDb } from "../infra/database/kernel-db.js";
24
26
  import { TransactionHost } from "../infra/database/transaction-host.js";
25
27
  import { Transactional } from "../infra/database/transactional.decorator.js";
28
+ import { KERNEL_LOGGER } from "../infra/tokens.js";
26
29
  import { OrgLock } from "../orgs/org-lock.js";
27
30
  import { textBytesTokens } from "../runtime/context/tokens.js";
28
31
  import { StorageService } from "../storage/storage.service.js";
29
- import { buildSkillWrite } from "../tools/builtins/skill-write.js";
30
32
  import { invocationOf, PINNED_SKILLS_LIMITS } from "./catalog.js";
31
- import { checkBuiltSkillFiles, readSkillPackage, skillManifest, } from "./package.js";
33
+ import { cancelRequestIn, } from "./conversation-catalog.js";
34
+ import { readSkillPackage, skillManifest, } from "./package.js";
32
35
  import { PLATFORM_SOURCE, platformSkillsOn, platformSource, skillFileStore, skillVersionsById, usableSkills, } from "./usable.js";
33
36
  /**
34
37
  * The skills named in `skillNames` an org would see twice if catalog item `itemId` brought its
@@ -126,19 +129,6 @@ const PUT_CONCURRENCY = 4;
126
129
  /** 详情里读取 SKILL.md 正文的上限。 */
127
130
  export const SKILL_BODY_MAX_BYTES = 1024 * 1024;
128
131
  const notFound = () => new ApiError("skill.not_found", "Skill not found");
129
- const nameExists = (name) => new ApiError("skill.name_exists", `A skill named "${name}" already exists in this organization. Choose another name.`);
130
- /** The checked files of a skill written as text (a built `SKILL.md`); `validation_failed` if not. */
131
- function textSkillFiles(input) {
132
- const built = buildSkillWrite({
133
- name: input.name,
134
- description: input.description,
135
- files: [{ path: SKILL_ENTRY_FILE, content: input.body }],
136
- });
137
- if ("errors" in built) {
138
- throw new ApiError("validation_failed", `Invalid skill: ${built.errors[0] ?? "unknown problem"}`, built.errors.map((message) => ({ path: "body", message })));
139
- }
140
- return checkBuiltSkillFiles(built.files);
141
- }
142
132
  /**
143
133
  * 在调用方事务里核对 agent 定义引用的组织技能:每个 id 都必须是该组织未删除的 `scope=org` 技能
144
134
  * (or, phase 2, a platform skill on in the org: skills/usable.ts),
@@ -348,16 +338,22 @@ let SkillsService = class SkillsService {
348
338
  txHost;
349
339
  orgLock;
350
340
  platform;
341
+ eventLog;
342
+ log;
351
343
  constructor(db, storage, attachments, txHost, orgLock,
352
344
  /** The owner's new versions of a skill it promoted go to the platform row (ruling P3-4). */
353
- platform) {
345
+ platform, eventLog, log) {
354
346
  this.db = db;
355
347
  this.storage = storage;
356
348
  this.attachments = attachments;
357
349
  this.txHost = txHost;
358
350
  this.orgLock = orgLock;
359
351
  this.platform = platform;
352
+ this.eventLog = eventLog;
353
+ this.log = log;
354
+ this.catalogDeps = { db, log, eventLog };
360
355
  }
356
+ catalogDeps;
361
357
  /**
362
358
  * Writes a package's files where its version will live: for a new version of a skill the org
363
359
  * promoted (ruling P3-3: a live org skill of the name linked without a pin to a platform skill
@@ -462,45 +458,6 @@ let SkillsService = class SkillsService {
462
458
  ...(owner !== undefined && { owner }),
463
459
  });
464
460
  }
465
- /**
466
- * The read-only checks of {@link createFromText}, for the `Platform` tool's precheck (before the
467
- * card): custom skills allowed, a valid `SKILL.md`, and no live org skill of the name. Throws the
468
- * same `ApiError` the create would; the create checks the name again under its lock.
469
- */
470
- async checkCreateFromText(orgId, input) {
471
- await assertCustomAllowed(this.db.db, "customSkills");
472
- const pkg = textSkillFiles(input);
473
- const existing = await this.db
474
- .selectFrom("skills")
475
- .select("id")
476
- .where("org_id", "=", orgId)
477
- .where("scope", "=", "org")
478
- .where("name", "=", pkg.name)
479
- .where("deleted_at", "is", null)
480
- .executeTakeFirst();
481
- if (existing)
482
- throw nameExists(pkg.name);
483
- }
484
- /**
485
- * Creates an org skill from text (spec §14, P3-24): `SKILL.md` is built from the name,
486
- * description and body ({@link buildSkillWrite}, the description a YAML string) and checked like
487
- * an uploaded package. The same policy and name rules as an upload, except that a live org skill
488
- * of that name is `skill.name_exists` (409) instead of a new version.
489
- */
490
- async createFromText(orgId, actorAccountId, input) {
491
- this.storage.assertAvailable();
492
- await assertCustomAllowed(this.db.db, "customSkills");
493
- const pkg = textSkillFiles(input);
494
- const prefix = skillFilesPrefix(randomUUID());
495
- await this.putFiles(orgId, prefix, pkg.files);
496
- return this.createVersionIn(orgId, actorAccountId, pkg, skillManifest(pkg), prefix, {
497
- prefix,
498
- failIfExists: true,
499
- ...(input.actorTokenId !== undefined && { actorTokenId: input.actorTokenId }),
500
- ...(input.auditDetail !== undefined && { auditDetail: input.auditDetail }),
501
- ...(input.shareable !== undefined && { shareable: input.shareable }),
502
- });
503
- }
504
461
  /**
505
462
  * 用一组已校验的技能文件创建组织技能或给同名技能新增版本(上传与平台目录安装共用):
506
463
  * 1. 把文件写到 `kernel/<orgId>/<prefix>`(缺省 `skills/<新 id>/`);
@@ -514,6 +471,13 @@ let SkillsService = class SkillsService {
514
471
  actorAccountId, pkg, opts = {}) {
515
472
  this.storage.assertAvailable();
516
473
  const prefix = opts.prefix ?? skillFilesPrefix(randomUUID());
474
+ if (opts.stored !== undefined) {
475
+ const { stored, ...rest } = opts;
476
+ return this.createVersionIn(orgId, actorAccountId, pkg, skillManifest(pkg), prefix, {
477
+ ...rest,
478
+ ...(stored.owner !== undefined && { owner: stored.owner }),
479
+ });
480
+ }
517
481
  const owner = await this.putVersionFiles(orgId, pkg, prefix, opts.catalog !== undefined);
518
482
  try {
519
483
  return await this.createVersionIn(orgId, actorAccountId, pkg, skillManifest(pkg), prefix, {
@@ -528,6 +492,22 @@ let SkillsService = class SkillsService {
528
492
  throw e;
529
493
  }
530
494
  }
495
+ /**
496
+ * For a new version whose files are already in `kernel/<orgId>/` (a proposed version's draft):
497
+ * when the org skill of that name is one the org promoted, writes the files as the platform
498
+ * row's next version (never inside a transaction) and returns it for
499
+ * `createVersionFromFiles({ stored: { owner } })`; else nothing is written.
500
+ */
501
+ async prepareStoredVersion(orgId, pkg) {
502
+ const promoted = await promotedSkillNamed(this.db.db, orgId, pkg.name);
503
+ return promoted === undefined
504
+ ? undefined
505
+ : this.platform.prepareOwnerVersion(promoted, pkg.files);
506
+ }
507
+ /** Deletes a platform version {@link prepareStoredVersion} wrote that was not used. */
508
+ async discardStoredVersion(owner) {
509
+ await this.platform.discardPrefix(owner.prefix);
510
+ }
531
511
  /** Writes a skill package's files to `kernel/<orgId>/<prefix>` (a few at a time). */
532
512
  async putFiles(orgId, prefix, files) {
533
513
  const store = this.storage.kernel(orgId);
@@ -557,8 +537,6 @@ let SkillsService = class SkillsService {
557
537
  .where("deleted_at", "is", null)
558
538
  .forUpdate()
559
539
  .executeTakeFirst();
560
- if (existing && opts.failIfExists)
561
- throw nameExists(pkg.name);
562
540
  // The org's own skill it promoted (ruling P3-3): its new version goes to the platform row and
563
541
  // reaches every org using it (ruling P3-4); the files were written there before this
564
542
  // transaction, for the platform version current then
@@ -788,6 +766,38 @@ let SkillsService = class SkillsService {
788
766
  const platform = (await platformSummaryQuery(this.db.db).where("p.id", "in", platformIds).execute()).map((r) => ({ ...platformSummary(r), linkedIds: linked.get(r.id) ?? [] }));
789
767
  return [...own, ...platform].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : a.id < b.id ? -1 : 1);
790
768
  }
769
+ /**
770
+ * The agent's skills as the agent page lists them, by name: {@link listForAgent} (`shared:
771
+ * false`) plus `sharePending`, true while a share request of the skill waits for an org admin,
772
+ * and the org skills the agent shared (`origin_agent_id`; `scope: 'org'`, `shared: true`, same
773
+ * id as before sharing): the agent keeps them without selecting them.
774
+ */
775
+ async listOwnForAgent(orgId, agentId) {
776
+ const [skills, shared] = await Promise.all([
777
+ this.listForAgent(orgId, agentId),
778
+ summaryQuery(this.db.db)
779
+ .where("s.org_id", "=", orgId)
780
+ .where("s.scope", "=", "org")
781
+ .where("s.origin_agent_id", "=", agentId)
782
+ .where("s.deleted_at", "is", null)
783
+ .execute(),
784
+ ]);
785
+ const pending = skills.length === 0
786
+ ? []
787
+ : await this.db
788
+ .selectFrom("skill_change_requests")
789
+ .select("skill_id")
790
+ .where("org_id", "=", orgId)
791
+ .where("kind", "=", "share")
792
+ .where("status", "=", "pending")
793
+ .where("skill_id", "in", skills.map((s) => s.id))
794
+ .execute();
795
+ const ids = new Set(pending.map((r) => r.skill_id));
796
+ return [
797
+ ...skills.map((s) => ({ ...s, sharePending: ids.has(s.id), shared: false })),
798
+ ...shared.map((r) => ({ ...summary(r), sharePending: false, shared: true })),
799
+ ].sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : a.id < b.id ? -1 : 1));
800
+ }
791
801
  /** 对该 agent 自动生效的技能(`scope=agent`、未删除),按名字排序。 */
792
802
  async listForAgent(orgId, agentId) {
793
803
  const rows = await summaryQuery(this.db.db)
@@ -924,7 +934,9 @@ let SkillsService = class SkillsService {
924
934
  /**
925
935
  * In the caller's transaction (`mandatory`: the caller holds the org row lock and checked
926
936
  * `skills.manage`), soft-deletes an org-scope skill of the org: what {@link softDelete} writes
927
- * (audit `skill.deleted`); missing or deleted -> `skill.not_found`. The owner delete of a
937
+ * (audit `skill.deleted`); missing or deleted -> `skill.not_found`. Its pending version
938
+ * proposals are cancelled in the same transaction (trials reverted; drafts deleted and events
939
+ * published after the commit). The owner delete of a
928
940
  * promoted skill (`OwnedItems.deleteSkill`) calls it after its platform row was dealt with.
929
941
  */
930
942
  async deleteOrgSkillIn(orgId, skillId, actorAccountId, actorTokenId, auditDetail) {
@@ -939,6 +951,26 @@ let SkillsService = class SkillsService {
939
951
  .executeTakeFirst();
940
952
  if (!r)
941
953
  throw notFound();
954
+ // its pending version proposals go with it: cancelled, their trials reverted; after the
955
+ // commit the events are published and the drafts deleted (a share request has no draft)
956
+ const pending = await this.db
957
+ .selectFrom("skill_change_requests")
958
+ .selectAll()
959
+ .where("org_id", "=", orgId)
960
+ .where("skill_id", "=", skillId)
961
+ .where("kind", "=", "version")
962
+ .where("status", "=", "pending")
963
+ .orderBy("created_at")
964
+ .forUpdate()
965
+ .execute();
966
+ const cancelled = { prefixes: [], seqs: [] };
967
+ for (const p of pending) {
968
+ await cancelRequestIn(this.catalogDeps, p, cancelled, {
969
+ actorAccountId,
970
+ ...(actorTokenId !== undefined && { actorTokenId }),
971
+ reason: "skill_deleted",
972
+ });
973
+ }
942
974
  await audit(this.db.db, {
943
975
  actorAccountId,
944
976
  ...(actorTokenId !== undefined && { actorTokenId }),
@@ -947,6 +979,20 @@ let SkillsService = class SkillsService {
947
979
  target: skillId,
948
980
  detail: { name: r.name, ...auditDetail },
949
981
  });
982
+ if (pending.length > 0) {
983
+ afterCommit(async () => {
984
+ for (const [conv, seq] of cancelled.seqs)
985
+ await this.eventLog.notify(conv, seq);
986
+ for (const prefix of cancelled.prefixes) {
987
+ try {
988
+ await this.storage.kernel(orgId).deletePrefix(prefix);
989
+ }
990
+ catch (err) {
991
+ this.log.warn("skill draft cleanup failed", { orgId, prefix, err });
992
+ }
993
+ }
994
+ });
995
+ }
950
996
  }
951
997
  };
952
998
  __decorate([
@@ -974,7 +1020,9 @@ SkillsService = __decorate([
974
1020
  __param(2, Inject(AttachmentsService)),
975
1021
  __param(3, Inject(TransactionHost)),
976
1022
  __param(4, Inject(OrgLock)),
977
- __param(5, Inject(PlatformComponents))
1023
+ __param(5, Inject(PlatformComponents)),
1024
+ __param(6, Inject(EventLog)),
1025
+ __param(7, Inject(KERNEL_LOGGER))
978
1026
  ], SkillsService);
979
1027
  export { SkillsService };
980
1028
  /**
@@ -59,9 +59,10 @@ export declare function pinnedSkillLinksServed(db: KernelSchemaDb, orgId: string
59
59
  */
60
60
  export declare function skillVersionsById(db: KernelSchemaDb, orgId: string, ids: readonly string[], log?: Pick<Logger, "warn">): Promise<Map<string, SkillVersionRow>>;
61
61
  /**
62
- * The skills an agent selects, sorted by id: its own skills (`scope = 'agent'`) and the ids of
63
- * `orgSkills` that resolve ({@link skillVersionsById}: org skills, linked copies, platform skills
64
- * on in the org). Not deduplicated by name.
62
+ * The skills an agent selects, sorted by id: its own skills (`scope = 'agent'`), the org skills
63
+ * it shared (`origin_agent_id`: the agent keeps a shared skill without a definition change) and
64
+ * the ids of `orgSkills` that resolve ({@link skillVersionsById}: org skills, linked copies,
65
+ * platform skills on in the org). Not deduplicated by name.
65
66
  */
66
67
  export declare function selectedSkills(db: KernelSchemaDb, a: {
67
68
  orgId: string;
@@ -285,9 +285,10 @@ async function ownedPlatformIds(db, orgId, platformIds) {
285
285
  return new Set(rows.map((r) => r.id));
286
286
  }
287
287
  /**
288
- * The skills an agent selects, sorted by id: its own skills (`scope = 'agent'`) and the ids of
289
- * `orgSkills` that resolve ({@link skillVersionsById}: org skills, linked copies, platform skills
290
- * on in the org). Not deduplicated by name.
288
+ * The skills an agent selects, sorted by id: its own skills (`scope = 'agent'`), the org skills
289
+ * it shared (`origin_agent_id`: the agent keeps a shared skill without a definition change) and
290
+ * the ids of `orgSkills` that resolve ({@link skillVersionsById}: org skills, linked copies,
291
+ * platform skills on in the org). Not deduplicated by name.
291
292
  */
292
293
  export async function selectedSkills(db, a, log) {
293
294
  const agentOwn = await db
@@ -298,7 +299,15 @@ export async function selectedSkills(db, a, log) {
298
299
  .where("agent_id", "=", a.agentId)
299
300
  .where("deleted_at", "is", null)
300
301
  .execute();
301
- const orgSkills = new Set(a.orgSkills);
302
+ const shared = await db
303
+ .selectFrom("skills")
304
+ .select("id")
305
+ .where("org_id", "=", a.orgId)
306
+ .where("scope", "=", "org")
307
+ .where("origin_agent_id", "=", a.agentId)
308
+ .where("deleted_at", "is", null)
309
+ .execute();
310
+ const orgSkills = new Set([...a.orgSkills, ...shared.map((r) => r.id)]);
302
311
  const byId = await skillVersionsById(db, a.orgId, [...agentOwn.map((r) => r.id), ...orgSkills], log);
303
312
  return [...byId.values()]
304
313
  .filter((r) => (r.scope === "agent" ? r.agentId === a.agentId : orgSkills.has(r.id)))
@@ -0,0 +1,69 @@
1
+ import { KernelDb } from "../infra/database/kernel-db.js";
2
+ import type { ToolPolicy } from "./registry.js";
3
+ /** One connector in the super admin's cross-org list (no endpoint, auth or credential). */
4
+ export interface AdminConnectorRow {
5
+ id: string;
6
+ orgId: string;
7
+ orgName: string;
8
+ name: string;
9
+ slug: string;
10
+ /** The connector extension (e.g. `mcp`, `openapi`). */
11
+ connector: string;
12
+ status: "active" | "disabled";
13
+ platformTemplateId: string | null;
14
+ /** The platform connector template's name (its template id in the plugin), when it exists. */
15
+ templateName: string | null;
16
+ catalogItemId: string | null;
17
+ /** Counts every tool row of the connector, pending and missing ones included. */
18
+ toolCount: number;
19
+ toolsSyncedAt: Date | null;
20
+ createdBy: string;
21
+ createdByName: string;
22
+ createdAt: Date;
23
+ updatedAt: Date;
24
+ }
25
+ /**
26
+ * The super admin's read-only connector detail. Never a credential, token or secret value, and
27
+ * not whether any member has a credential: `auth` is the type and ownership only.
28
+ */
29
+ export interface AdminConnectorDetail extends AdminConnectorRow {
30
+ /** Through {@link endpointView}: large text fields as their size and sha256 only. */
31
+ endpoint: unknown;
32
+ auth: {
33
+ type: string;
34
+ ownership: "org" | "personal" | null;
35
+ };
36
+ tools: {
37
+ name: string;
38
+ title: string | null;
39
+ status: "enabled" | "disabled" | "pending_review";
40
+ defaultPolicy: ToolPolicy;
41
+ }[];
42
+ }
43
+ export interface AdminConnectorQuery {
44
+ orgId?: string | undefined;
45
+ status?: "active" | "disabled" | undefined;
46
+ /** A case-insensitive substring of the connector name. */
47
+ q?: string | undefined;
48
+ /** The last id of the previous page. */
49
+ after?: string | undefined;
50
+ limit: number;
51
+ }
52
+ /**
53
+ * Cross-org reads of connectors for the super admin (spec
54
+ * 2026-11-10-admin-agents-connectors-design.md §3.4, §3.5). Only live connectors of live orgs;
55
+ * the caller checks the super admin.
56
+ */
57
+ export declare class AdminConnectors {
58
+ private readonly kdb;
59
+ constructor(kdb: KernelDb);
60
+ private get db();
61
+ private base;
62
+ /** Live connectors of every live org by id, filtered; `next` is the last id when more follow. */
63
+ list(q: AdminConnectorQuery): Promise<{
64
+ connectors: AdminConnectorRow[];
65
+ next: string | null;
66
+ }>;
67
+ /** One live connector of a live org; else `connector.not_found`. */
68
+ get(connectorId: string): Promise<AdminConnectorDetail>;
69
+ }
@@ -0,0 +1,135 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
8
+ return function (target, key) { decorator(target, key, paramIndex); }
9
+ };
10
+ import { Inject, Injectable } from "@nestjs/common";
11
+ import { sql } from "kysely";
12
+ import { ApiError } from "../errors.js";
13
+ import { KernelDb } from "../infra/database/kernel-db.js";
14
+ import { endpointView } from "./endpoint.js";
15
+ const notFound = () => new ApiError("connector.not_found", "Connector not found");
16
+ /**
17
+ * Cross-org reads of connectors for the super admin (spec
18
+ * 2026-11-10-admin-agents-connectors-design.md §3.4, §3.5). Only live connectors of live orgs;
19
+ * the caller checks the super admin.
20
+ */
21
+ let AdminConnectors = class AdminConnectors {
22
+ kdb;
23
+ constructor(kdb) {
24
+ this.kdb = kdb;
25
+ }
26
+ get db() {
27
+ return this.kdb.db;
28
+ }
29
+ base() {
30
+ return this.db
31
+ .selectFrom("connectors as c")
32
+ .innerJoin("orgs as o", "o.id", "c.org_id")
33
+ .innerJoin("accounts as a", "a.id", "c.created_by")
34
+ .leftJoin("platform_connector_templates as pt", "pt.id", "c.platform_template_id")
35
+ .select([
36
+ "c.id",
37
+ "c.org_id",
38
+ "o.name as org_name",
39
+ "c.name",
40
+ "c.slug",
41
+ "c.connector",
42
+ "c.status",
43
+ "c.platform_template_id",
44
+ "pt.template_id as template_name",
45
+ "c.catalog_item_id",
46
+ "c.tools_synced_at",
47
+ "c.created_by",
48
+ "a.display_name as created_by_name",
49
+ "c.created_at",
50
+ "c.updated_at",
51
+ sql `(SELECT count(*)::int FROM capybara.connector_tools t
52
+ WHERE t.connector_id = c.id)`.as("tool_count"),
53
+ ])
54
+ .where("c.deleted_at", "is", null)
55
+ .where("o.deleted_at", "is", null);
56
+ }
57
+ /** Live connectors of every live org by id, filtered; `next` is the last id when more follow. */
58
+ async list(q) {
59
+ let query = this.base();
60
+ if (q.orgId !== undefined)
61
+ query = query.where("c.org_id", "=", q.orgId);
62
+ if (q.status !== undefined)
63
+ query = query.where("c.status", "=", q.status);
64
+ if (q.q !== undefined && q.q !== "") {
65
+ const like = `%${q.q.replace(/[\\%_]/g, (ch) => `\\${ch}`)}%`;
66
+ query = query.where(sql `c.name ILIKE ${like} ESCAPE '\\'`);
67
+ }
68
+ if (q.after !== undefined)
69
+ query = query.where("c.id", ">", q.after);
70
+ const rows = await query
71
+ .orderBy("c.id")
72
+ .limit(q.limit + 1)
73
+ .execute();
74
+ const page = rows.slice(0, q.limit).map(toRow);
75
+ return {
76
+ connectors: page,
77
+ next: rows.length > q.limit ? (page[page.length - 1]?.id ?? null) : null,
78
+ };
79
+ }
80
+ /** One live connector of a live org; else `connector.not_found`. */
81
+ async get(connectorId) {
82
+ const r = await this.base()
83
+ .select(["c.endpoint", "c.auth"])
84
+ .where("c.id", "=", connectorId)
85
+ .executeTakeFirst();
86
+ if (!r)
87
+ throw notFound();
88
+ const tools = await this.db
89
+ .selectFrom("connector_tools")
90
+ .select(["name", "spec", "status", "default_policy"])
91
+ .where("connector_id", "=", connectorId)
92
+ .orderBy("name")
93
+ .execute();
94
+ const auth = r.auth;
95
+ return {
96
+ ...toRow(r),
97
+ endpoint: endpointView(r.endpoint),
98
+ auth: { type: auth.type, ownership: auth.type === "none" ? null : auth.ownership },
99
+ tools: tools.map((t) => {
100
+ const title = t.spec.title;
101
+ return {
102
+ name: t.name,
103
+ title: typeof title === "string" ? title : null,
104
+ status: t.status,
105
+ defaultPolicy: t.default_policy,
106
+ };
107
+ }),
108
+ };
109
+ }
110
+ };
111
+ AdminConnectors = __decorate([
112
+ Injectable(),
113
+ __param(0, Inject(KernelDb))
114
+ ], AdminConnectors);
115
+ export { AdminConnectors };
116
+ function toRow(r) {
117
+ return {
118
+ id: r.id,
119
+ orgId: r.org_id,
120
+ orgName: r.org_name,
121
+ name: r.name,
122
+ slug: r.slug,
123
+ connector: r.connector,
124
+ status: r.status,
125
+ platformTemplateId: r.platform_template_id ?? null,
126
+ templateName: r.template_name ?? null,
127
+ catalogItemId: r.catalog_item_id ?? null,
128
+ toolCount: r.tool_count,
129
+ toolsSyncedAt: r.tools_synced_at,
130
+ createdBy: r.created_by,
131
+ createdByName: r.created_by_name,
132
+ createdAt: r.created_at,
133
+ updatedAt: r.updated_at,
134
+ };
135
+ }
@@ -15,7 +15,6 @@ export declare const BUILTIN: {
15
15
  readonly Grep: "Grep";
16
16
  readonly Deliver: "Deliver";
17
17
  readonly Skill: "Skill";
18
- readonly SkillWrite: "SkillWrite";
19
18
  readonly AskUserQuestion: "AskUserQuestion";
20
19
  readonly ReadToolOutput: "ReadToolOutput";
21
20
  readonly ToolSearch: "ToolSearch";
@@ -58,6 +57,13 @@ export declare const SANDBOX_ASK_TOOL_NAMES: ReadonlySet<string>;
58
57
  * `CurrentTime` reads the clock (the run context carries only the date).
59
58
  */
60
59
  export declare const BUILTIN_TOOL_NAMES: readonly BuiltinName[];
60
+ /**
61
+ * Is `key` a builtin-shaped key (a bare name, no `mcp__` / `plugin__` prefix) that names no
62
+ * builtin the kernel has now, e.g. the removed `SkillWrite`? Saving an agent or an org template
63
+ * drops such a `toolPolicies` key instead of refusing the save: a stored definition may still
64
+ * carry it, and the web sends the whole definition back.
65
+ */
66
+ export declare function isRemovedBuiltinKey(key: string): boolean;
61
67
  /** The builtin that loads a skill (added by the registry when the log has a skills catalog). */
62
68
  export declare const SKILL_LOAD: "Skill";
63
69
  /** The builtin that searches the tool catalog (added by the registry by org setting and threshold). */
@@ -74,23 +80,12 @@ export declare const SUBAGENT_HANDBACK: "SubagentHandback";
74
80
  * `SubagentHandback` to a sub-agent. A definition's ref to either is ignored.
75
81
  */
76
82
  export declare const SUBAGENT_MESSAGE_TOOLS: ReadonlySet<string>;
77
- /**
78
- * Tool keys on the spec 3.6 R3 fixed list ("allowed is not confirmed"): their policy is at least
79
- * `ask` and remembered grants never let them through. `SkillWrite` changes shared skills.
80
- * Connector and extension tools express the same rule with `annotations.requiresUserInteraction`.
81
- */
82
- export declare const R3_FIXED_ASK_TOOL_KEYS: ReadonlySet<string>;
83
- /**
84
- * R3 keys that apply only to shared agents: a private agent's skills are not shared, so its
85
- * `SkillWrite` takes effect without asking (R4).
86
- */
87
- export declare const R3_SHARED_AGENT_ONLY_TOOL_KEYS: ReadonlySet<string>;
88
83
  /**
89
84
  * Tools a sub-agent never gets (sub-agents spec §5.3, §5.5): `AskUserQuestion` (the person does
90
- * not see the child), `Deliver` (delivering files is the parent's job) and `SkillWrite`. `Agent`
85
+ * not see the child) and `Deliver` (delivering files is the parent's job). `Agent`
91
86
  * is removed only from a child at `subagents.maxDepth` (agent map spec §4.1, `effectiveDefinition`).
92
87
  */
93
- export declare const CHILD_REMOVED_TOOLS: readonly ["AskUserQuestion", "Deliver", "SkillWrite"];
88
+ export declare const CHILD_REMOVED_TOOLS: readonly ["AskUserQuestion", "Deliver"];
94
89
  /**
95
90
  * The builtins every agent has unless it turns them off (tools default-on spec §4), in this
96
91
  * order: {@link BUILTIN_TOOL_NAMES} without the ones the registry manages by rule (`ToolSearch`,
@@ -1,3 +1,4 @@
1
+ import { parseToolKey } from "@capybara-hq/plugin-sdk";
1
2
  /**
2
3
  * The names (model-visible keys) of the kernel's builtin tools, following Claude Code (tool naming
3
4
  * spec §4.1). Plain constants: `BuiltinTools`, the tool factories, the registry, policy and the
@@ -14,7 +15,6 @@ export const BUILTIN = {
14
15
  Grep: "Grep",
15
16
  Deliver: "Deliver",
16
17
  Skill: "Skill",
17
- SkillWrite: "SkillWrite",
18
18
  AskUserQuestion: "AskUserQuestion",
19
19
  ReadToolOutput: "ReadToolOutput",
20
20
  ToolSearch: "ToolSearch",
@@ -75,7 +75,6 @@ export const BUILTIN_TOOL_NAMES = [
75
75
  BUILTIN.ReadToolOutput,
76
76
  BUILTIN.ToolSearch,
77
77
  BUILTIN.Skill,
78
- BUILTIN.SkillWrite,
79
78
  ...SANDBOX_TOOL_NAMES,
80
79
  BUILTIN.Agent,
81
80
  BUILTIN.SendMessage,
@@ -84,6 +83,16 @@ export const BUILTIN_TOOL_NAMES = [
84
83
  BUILTIN.Platform,
85
84
  BUILTIN.CurrentTime,
86
85
  ];
86
+ const BUILTIN_NAME_SET = new Set(Object.values(BUILTIN));
87
+ /**
88
+ * Is `key` a builtin-shaped key (a bare name, no `mcp__` / `plugin__` prefix) that names no
89
+ * builtin the kernel has now, e.g. the removed `SkillWrite`? Saving an agent or an org template
90
+ * drops such a `toolPolicies` key instead of refusing the save: a stored definition may still
91
+ * carry it, and the web sends the whole definition back.
92
+ */
93
+ export function isRemovedBuiltinKey(key) {
94
+ return parseToolKey(key)?.kind === "builtin" && !BUILTIN_NAME_SET.has(key);
95
+ }
87
96
  /** The builtin that loads a skill (added by the registry when the log has a skills catalog). */
88
97
  export const SKILL_LOAD = BUILTIN.Skill;
89
98
  /** The builtin that searches the tool catalog (added by the registry by org setting and threshold). */
@@ -103,29 +112,12 @@ export const SUBAGENT_MESSAGE_TOOLS = new Set([
103
112
  SEND_MESSAGE,
104
113
  SUBAGENT_HANDBACK,
105
114
  ]);
106
- /**
107
- * Tool keys on the spec 3.6 R3 fixed list ("allowed is not confirmed"): their policy is at least
108
- * `ask` and remembered grants never let them through. `SkillWrite` changes shared skills.
109
- * Connector and extension tools express the same rule with `annotations.requiresUserInteraction`.
110
- */
111
- export const R3_FIXED_ASK_TOOL_KEYS = new Set([BUILTIN.SkillWrite]);
112
- /**
113
- * R3 keys that apply only to shared agents: a private agent's skills are not shared, so its
114
- * `SkillWrite` takes effect without asking (R4).
115
- */
116
- export const R3_SHARED_AGENT_ONLY_TOOL_KEYS = new Set([
117
- BUILTIN.SkillWrite,
118
- ]);
119
115
  /**
120
116
  * Tools a sub-agent never gets (sub-agents spec §5.3, §5.5): `AskUserQuestion` (the person does
121
- * not see the child), `Deliver` (delivering files is the parent's job) and `SkillWrite`. `Agent`
117
+ * not see the child) and `Deliver` (delivering files is the parent's job). `Agent`
122
118
  * is removed only from a child at `subagents.maxDepth` (agent map spec §4.1, `effectiveDefinition`).
123
119
  */
124
- export const CHILD_REMOVED_TOOLS = [
125
- BUILTIN.AskUserQuestion,
126
- BUILTIN.Deliver,
127
- BUILTIN.SkillWrite,
128
- ];
120
+ export const CHILD_REMOVED_TOOLS = [BUILTIN.AskUserQuestion, BUILTIN.Deliver];
129
121
  /**
130
122
  * The builtins every agent has unless it turns them off (tools default-on spec §4), in this
131
123
  * order: {@link BUILTIN_TOOL_NAMES} without the ones the registry manages by rule (`ToolSearch`,
@@ -150,7 +142,6 @@ export const BUILTIN_TOOL_KINDS = {
150
142
  [BUILTIN.TaskStop]: "command",
151
143
  [BUILTIN.ToolSearch]: "meta",
152
144
  [BUILTIN.Skill]: "meta",
153
- [BUILTIN.SkillWrite]: "meta",
154
145
  [BUILTIN.ReadToolOutput]: "meta",
155
146
  [BUILTIN.AskUserQuestion]: "meta",
156
147
  [BUILTIN.SendMessage]: "meta",
@@ -16,7 +16,6 @@ import type { HookRunner } from "../../runtime/hook-calls.js";
16
16
  import { StepLog } from "../../runtime/step-log.js";
17
17
  import { UsageReporter } from "../../runtime/usage-report.js";
18
18
  import { SandboxService } from "../../sandbox/sandbox.service.js";
19
- import { SkillChangesService } from "../../skills/changes.service.js";
20
19
  import { StorageService } from "../../storage/storage.service.js";
21
20
  import { ToolOutputs } from "../outputs.js";
22
21
  import type { BuiltinTool } from "../registry.js";
@@ -25,11 +24,11 @@ import { WebPages } from "./web/index.js";
25
24
  * The kernel's builtin tools, built once from the injected services (their specs: names,
26
25
  * descriptions and schemas, are the factories' own and unchanged). `ToolSearch` and `Skill`
27
26
  * are added to a run by the registry (`forAgent`: organization setting and threshold, a skills
28
- * catalog snapshot), not by agent definitions; `SkillWrite` only when a definition names it.
27
+ * catalog snapshot), not by agent definitions.
29
28
  */
30
29
  export declare class BuiltinTools {
31
30
  private readonly tools;
32
- constructor(db: KernelDb, config: KernelConfigService, outputs: ToolOutputs, storage: StorageService, skillChanges: SkillChangesService, attachments: AttachmentsService, eventLog: EventLog, sandbox: SandboxService, devices: ConversationDevices, points: ExtensionPointDefaults, webPages: WebPages,
31
+ constructor(db: KernelDb, config: KernelConfigService, outputs: ToolOutputs, storage: StorageService, attachments: AttachmentsService, eventLog: EventLog, sandbox: SandboxService, devices: ConversationDevices, points: ExtensionPointDefaults, webPages: WebPages,
33
32
  /** The WebFetch extraction call: the `web_fetch` purpose, its hooks, usage and denials. */
34
33
  purposes: ModelPurposesService, host: {
35
34
  readonly hooks?: HookRunner;