@capybara-hq/kernel 0.1.0-rc.21 → 0.1.0-rc.22

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 (242) hide show
  1. package/dist/agents/admin-agents.d.ts +16 -3
  2. package/dist/agents/admin-agents.js +65 -7
  3. package/dist/agents/agents.schemas.d.ts +24 -6
  4. package/dist/agents/agents.service.d.ts +4 -3
  5. package/dist/agents/agents.service.js +39 -26
  6. package/dist/agents/definition.d.ts +44 -6
  7. package/dist/agents/definition.js +86 -18
  8. package/dist/agents/from-template.d.ts +6 -3
  9. package/dist/agents/from-template.js +57 -44
  10. package/dist/agents/org-templates.js +97 -41
  11. package/dist/agents/templates.d.ts +6 -0
  12. package/dist/agents/templates.js +15 -1
  13. package/dist/catalog/admin-catalog.controller.d.ts +2 -4
  14. package/dist/catalog/admin-catalog.controller.js +2 -6
  15. package/dist/catalog/catalog.module.js +3 -0
  16. package/dist/catalog/claude/report.d.ts +9 -2
  17. package/dist/catalog/claude/report.js +1 -0
  18. package/dist/catalog/claude/translate.js +1 -1
  19. package/dist/catalog/component-promotion.js +1 -2
  20. package/dist/catalog/component-state.d.ts +13 -1
  21. package/dist/catalog/component-state.js +14 -5
  22. package/dist/catalog/marketplaces.service.d.ts +1 -1
  23. package/dist/catalog/marketplaces.service.js +2 -1
  24. package/dist/catalog/org-catalog.controller.d.ts +13 -3
  25. package/dist/catalog/org-catalog.controller.js +26 -4
  26. package/dist/catalog/org-catalog.schemas.d.ts +13 -0
  27. package/dist/catalog/org-catalog.schemas.js +34 -0
  28. package/dist/catalog/org-plugin-uploads.controller.js +0 -1
  29. package/dist/catalog/org-plugin-uploads.d.ts +0 -1
  30. package/dist/catalog/org-plugin-uploads.js +0 -3
  31. package/dist/catalog/org-plugin-uploads.schemas.d.ts +0 -1
  32. package/dist/catalog/org-plugin-uploads.schemas.js +0 -2
  33. package/dist/catalog/platform-components.d.ts +0 -1
  34. package/dist/catalog/platform-components.js +3 -8
  35. package/dist/catalog/plugin-import.d.ts +0 -8
  36. package/dist/catalog/plugin-import.js +4 -35
  37. package/dist/catalog/plugin-install.d.ts +2 -2
  38. package/dist/catalog/plugin-install.js +2 -2
  39. package/dist/catalog/plugin-package.d.ts +7 -0
  40. package/dist/catalog/plugin-package.js +27 -1
  41. package/dist/catalog/plugin-upgrade.d.ts +5 -6
  42. package/dist/catalog/plugin-upgrade.js +5 -23
  43. package/dist/catalog/requests.service.d.ts +66 -10
  44. package/dist/catalog/requests.service.js +196 -15
  45. package/dist/channels/actions.d.ts +2 -0
  46. package/dist/channels/actions.js +19 -4
  47. package/dist/channels/cards.d.ts +16 -2
  48. package/dist/channels/cards.js +40 -7
  49. package/dist/channels/channels.module.js +3 -0
  50. package/dist/channels/outbound.d.ts +22 -1
  51. package/dist/channels/outbound.js +116 -7
  52. package/dist/channels/phrases.d.ts +25 -0
  53. package/dist/channels/phrases.js +19 -0
  54. package/dist/channels/threads.d.ts +12 -0
  55. package/dist/channels/threads.js +34 -0
  56. package/dist/config/schema.d.ts +23 -1
  57. package/dist/config/schema.js +105 -5
  58. package/dist/conversations/conversations.service.d.ts +42 -9
  59. package/dist/conversations/conversations.service.js +57 -25
  60. package/dist/conversations/events.d.ts +106 -11
  61. package/dist/conversations/events.js +121 -18
  62. package/dist/conversations/message-context.d.ts +6 -3
  63. package/dist/conversations/message-context.js +14 -10
  64. package/dist/conversations/queue.d.ts +15 -2
  65. package/dist/conversations/queue.js +22 -3
  66. package/dist/conversations/queue.service.d.ts +12 -6
  67. package/dist/conversations/queue.service.js +36 -13
  68. package/dist/db/kernel-schema.d.ts +105 -3
  69. package/dist/devices/conversation-devices.d.ts +8 -1
  70. package/dist/devices/conversation-devices.js +8 -0
  71. package/dist/devices/devices.controller.d.ts +21 -4
  72. package/dist/devices/devices.controller.js +46 -8
  73. package/dist/devices/devices.schemas.d.ts +9 -1
  74. package/dist/devices/devices.schemas.js +11 -3
  75. package/dist/devices/devices.service.d.ts +176 -4
  76. package/dist/devices/devices.service.js +341 -6
  77. package/dist/devices/dto.d.ts +56 -3
  78. package/dist/devices/dto.js +51 -1
  79. package/dist/devices/public-suffix.d.ts +6 -0
  80. package/dist/devices/public-suffix.js +79 -0
  81. package/dist/errors.d.ts +46 -5
  82. package/dist/errors.js +46 -5
  83. package/dist/evals/reconstruct.d.ts +18 -13
  84. package/dist/evals/reconstruct.js +50 -36
  85. package/dist/evals/recorded-tools.d.ts +7 -7
  86. package/dist/evals/recorded-tools.js +4 -3
  87. package/dist/evals/trajectory.d.ts +6 -1
  88. package/dist/evals/trajectory.js +7 -0
  89. package/dist/extensions/package.js +2 -2
  90. package/dist/http/api.module.js +5 -0
  91. package/dist/inbox/inbox.module.js +2 -1
  92. package/dist/inbox/inbox.service.d.ts +19 -4
  93. package/dist/inbox/inbox.service.js +14 -3
  94. package/dist/infra/config/kernel-config.service.js +11 -0
  95. package/dist/infra/events/kernel-events.d.ts +2 -1
  96. package/dist/local-devices/device-policy.service.d.ts +65 -0
  97. package/dist/local-devices/device-policy.service.js +217 -0
  98. package/dist/local-devices/local-device-gateway.js +4 -1
  99. package/dist/local-devices/local-device-provider.d.ts +40 -3
  100. package/dist/local-devices/local-device-provider.js +109 -7
  101. package/dist/local-devices/local-devices.module.d.ts +2 -1
  102. package/dist/local-devices/local-devices.module.js +19 -3
  103. package/dist/local-devices/machines.d.ts +14 -3
  104. package/dist/local-devices/machines.js +41 -3
  105. package/dist/local-devices/network-grants.d.ts +104 -0
  106. package/dist/local-devices/network-grants.js +288 -0
  107. package/dist/local-devices/policy-summary.d.ts +20 -0
  108. package/dist/local-devices/policy-summary.js +63 -0
  109. package/dist/orgs/orgs.module.js +4 -10
  110. package/dist/orgs/roles.d.ts +1 -1
  111. package/dist/orgs/roles.js +4 -0
  112. package/dist/orgs/settings.controller.js +2 -1
  113. package/dist/orgs/settings.service.d.ts +164 -12
  114. package/dist/orgs/settings.service.js +212 -24
  115. package/dist/platform/exclusions.js +11 -1
  116. package/dist/platform/operation.js +4 -0
  117. package/dist/platform/ops/admin-items.ops.js +1 -1
  118. package/dist/platform/ops/agent-skills.ops.js +2 -2
  119. package/dist/platform/ops/agent-templates.ops.js +2 -2
  120. package/dist/platform/ops/agents.ops.js +11 -15
  121. package/dist/platform/ops/devices.ops.js +3 -1
  122. package/dist/platform/ops/inbox.ops.js +19 -0
  123. package/dist/platform/ops/org-settings.ops.js +14 -5
  124. package/dist/platform/ops/platform.ops.d.ts +9 -1
  125. package/dist/platform/ops/platform.ops.js +34 -12
  126. package/dist/platform/ops/requests.ops.js +3 -2
  127. package/dist/platform/ops/schedules.ops.d.ts +21 -0
  128. package/dist/platform/ops/schedules.ops.js +470 -0
  129. package/dist/platform/ops/skills.ops.js +3 -3
  130. package/dist/platform/platform-ops.module.d.ts +2 -1
  131. package/dist/platform/platform-ops.module.js +5 -0
  132. package/dist/platform/route-schemas.js +3 -0
  133. package/dist/roles/worker.module.js +3 -0
  134. package/dist/runtime/context/build.js +13 -4
  135. package/dist/runtime/context/compaction.js +6 -3
  136. package/dist/runtime/context/device-paths.d.ts +43 -21
  137. package/dist/runtime/context/device-paths.js +104 -21
  138. package/dist/runtime/context/run-context.d.ts +9 -0
  139. package/dist/runtime/context/run-context.js +8 -0
  140. package/dist/runtime/context/skills.d.ts +14 -1
  141. package/dist/runtime/context/skills.js +36 -5
  142. package/dist/runtime/maintenance.d.ts +4 -2
  143. package/dist/runtime/maintenance.js +16 -5
  144. package/dist/runtime/run-finish.d.ts +6 -1
  145. package/dist/runtime/run-finish.js +16 -2
  146. package/dist/runtime/run-queue.js +21 -2
  147. package/dist/runtime/run-worker.js +14 -33
  148. package/dist/runtime/runtime.module.js +3 -0
  149. package/dist/runtime/subagents/definition.d.ts +32 -14
  150. package/dist/runtime/subagents/definition.js +106 -109
  151. package/dist/runtime/subagents/dispatch.js +4 -3
  152. package/dist/runtime/subagents/preamble.d.ts +2 -1
  153. package/dist/runtime/subagents/preamble.js +2 -1
  154. package/dist/runtime/waits.js +14 -4
  155. package/dist/sandbox/sandbox.service.d.ts +120 -10
  156. package/dist/sandbox/sandbox.service.js +335 -28
  157. package/dist/sandbox/tiers.d.ts +86 -0
  158. package/dist/sandbox/tiers.js +96 -0
  159. package/dist/schedules/effective-settings.d.ts +16 -0
  160. package/dist/schedules/effective-settings.js +17 -0
  161. package/dist/schedules/errors.d.ts +8 -0
  162. package/dist/schedules/errors.js +8 -0
  163. package/dist/schedules/fire.d.ts +118 -0
  164. package/dist/schedules/fire.js +625 -0
  165. package/dist/schedules/grant-rules.d.ts +94 -0
  166. package/dist/schedules/grant-rules.js +126 -0
  167. package/dist/schedules/job-ids.d.ts +32 -0
  168. package/dist/schedules/job-ids.js +27 -0
  169. package/dist/schedules/pause.d.ts +9 -0
  170. package/dist/schedules/pause.js +33 -0
  171. package/dist/schedules/run-tracker.d.ts +81 -0
  172. package/dist/schedules/run-tracker.js +333 -0
  173. package/dist/schedules/scan.job.d.ts +64 -0
  174. package/dist/schedules/scan.job.js +303 -0
  175. package/dist/schedules/schedule-math.d.ts +31 -0
  176. package/dist/schedules/schedule-math.js +223 -0
  177. package/dist/schedules/scheduled-tasks.service.d.ts +351 -0
  178. package/dist/schedules/scheduled-tasks.service.js +1193 -0
  179. package/dist/schedules/schedules.controller.d.ts +68 -0
  180. package/dist/schedules/schedules.controller.js +242 -0
  181. package/dist/schedules/schedules.module.d.ts +16 -0
  182. package/dist/schedules/schedules.module.js +65 -0
  183. package/dist/schedules/schedules.schemas.d.ts +316 -0
  184. package/dist/schedules/schedules.schemas.js +186 -0
  185. package/dist/schedules/store.d.ts +81 -0
  186. package/dist/schedules/store.js +390 -0
  187. package/dist/schedules/store.module.d.ts +6 -0
  188. package/dist/schedules/store.module.js +21 -0
  189. package/dist/schedules/types.d.ts +108 -0
  190. package/dist/schedules/types.js +1 -0
  191. package/dist/schedules/unattended.d.ts +117 -0
  192. package/dist/schedules/unattended.js +228 -0
  193. package/dist/setup/oauth-token-probe.js +8 -4
  194. package/dist/skills/agent-skills.service.d.ts +4 -8
  195. package/dist/skills/agent-skills.service.js +18 -53
  196. package/dist/skills/catalog.d.ts +65 -62
  197. package/dist/skills/catalog.js +109 -206
  198. package/dist/skills/conversation-catalog.d.ts +3 -3
  199. package/dist/skills/conversation-catalog.js +5 -12
  200. package/dist/skills/invoke.d.ts +2 -2
  201. package/dist/skills/skills.controller.d.ts +3 -1
  202. package/dist/skills/skills.service.d.ts +38 -37
  203. package/dist/skills/skills.service.js +168 -107
  204. package/dist/skills/usable.d.ts +57 -8
  205. package/dist/skills/usable.js +172 -38
  206. package/dist/tools/approvals.controller.d.ts +4 -2
  207. package/dist/tools/approvals.controller.js +17 -2
  208. package/dist/tools/approvals.schemas.d.ts +1 -0
  209. package/dist/tools/approvals.schemas.js +3 -1
  210. package/dist/tools/approvals.service.d.ts +182 -7
  211. package/dist/tools/approvals.service.js +597 -13
  212. package/dist/tools/builtin-names.d.ts +25 -4
  213. package/dist/tools/builtin-names.js +45 -4
  214. package/dist/tools/builtins/builtin-tools.d.ts +8 -0
  215. package/dist/tools/builtins/builtin-tools.js +23 -2
  216. package/dist/tools/builtins/sandbox/bash.js +16 -7
  217. package/dist/tools/builtins/sandbox/common.d.ts +1 -1
  218. package/dist/tools/builtins/sandbox/common.js +33 -10
  219. package/dist/tools/builtins/sandbox/edit.js +7 -3
  220. package/dist/tools/builtins/sandbox/file-state.d.ts +31 -1
  221. package/dist/tools/builtins/sandbox/file-state.js +33 -3
  222. package/dist/tools/builtins/sandbox/write.js +8 -4
  223. package/dist/tools/builtins/schedules.d.ts +107 -0
  224. package/dist/tools/builtins/schedules.js +789 -0
  225. package/dist/tools/elicitation.d.ts +2 -0
  226. package/dist/tools/elicitation.js +1 -1
  227. package/dist/tools/fixed-ask.d.ts +7 -0
  228. package/dist/tools/fixed-ask.js +9 -0
  229. package/dist/tools/network-asks.d.ts +109 -0
  230. package/dist/tools/network-asks.js +221 -0
  231. package/dist/tools/pipeline.d.ts +53 -6
  232. package/dist/tools/pipeline.js +202 -13
  233. package/dist/tools/policy.d.ts +21 -8
  234. package/dist/tools/policy.js +63 -9
  235. package/dist/tools/registry.d.ts +10 -1
  236. package/dist/tools/tools.module.js +12 -0
  237. package/package.json +2 -2
  238. package/src/migrations/0113_scheduled_tasks.sql +108 -0
  239. package/src/migrations/0114_network_asks.sql +23 -0
  240. package/src/migrations/0115_network_requests.sql +28 -0
  241. package/src/migrations/0116_skills_default_on.sql +37 -0
  242. package/src/migrations/0117_sandbox_tiers.sql +11 -0
@@ -7,38 +7,28 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  var __param = (this && this.__param) || function (paramIndex, decorator) {
8
8
  return function (target, key) { decorator(target, key, paramIndex); }
9
9
  };
10
- import { createHash } from "node:crypto";
11
- import { parseSkillMd, SKILL_ENTRY_FILE, } from "@capybara-hq/plugin-sdk";
12
10
  import { Inject, Injectable } from "@nestjs/common";
13
11
  import { sql } from "kysely";
14
12
  import { EventLog } from "../conversations/event-log.js";
15
13
  import { SKILLS_CATALOG_MAX } from "../conversations/events.js";
16
- import { ApiError } from "../errors.js";
14
+ import { KernelConfigService } from "../infra/config/kernel-config.service.js";
17
15
  import { KernelDb } from "../infra/database/kernel-db.js";
18
16
  import { TransactionHost } from "../infra/database/transaction-host.js";
19
17
  import { Transactional } from "../infra/database/transactional.decorator.js";
20
18
  import { KERNEL_LOGGER } from "../infra/tokens.js";
21
19
  import { CLAUDE_CODE_TOOL_MAP } from "../runtime/context/claude-tools.js";
22
- import { sameSnapshot, } from "../runtime/context/skills.js";
23
- import { StorageService } from "../storage/storage.service.js";
24
- import { selectedSkills, skillFileStore } from "./usable.js";
20
+ import { catalogLine, sameSnapshot, } from "../runtime/context/skills.js";
21
+ import { agentOwnSkillIds, agentSkills, builtinSkillIds, builtinSkillPlugins, } from "./usable.js";
25
22
  /**
26
- * 技能目录快照(M5 T14,Ruling M17):运行开始时解析「此刻对这个对话生效的技能」,与日志里最新一份快照不同才写
27
- * `skills_catalog`。模型看到的目录与 `Skill` 的名字解析只来自日志,不读实时的库。
28
- */
29
- /**
30
- * Pinned skill limits (spec §9.1): at most `count` pinned skills per agent, whose current SKILL.md
31
- * sizes total at most `totalBytes`. Saving checks both; at run time the pinned bodies are kept in
32
- * the agent's order while they fit.
33
- */
34
- export const PINNED_SKILLS_LIMITS = { count: 3, totalBytes: 32768 };
35
- /**
36
- * 此刻对这个对话生效的技能,按名字(码点序)排序:
37
- * - 运行所用 agent 版本选中的组织技能(`scope='org'`,id 在 `skills.orgSkills` 里);
38
- * - 加上这个 agent 自己的技能(`scope='agent'`);
39
- * - 已删除的跳过(保存过的 agent 版本仍可能引用它们);
40
- * - 版本取 `current_version`,描述取该版本清单里的描述。
41
- * 组织技能与 agent 自己的技能同名时只保留 agent 自己的(更具体;`/skills/<name>/` 与按名字加载都不能有两个)。
23
+ * The skills in effect for this conversation now, sorted by name (code point order), as
24
+ * {@link agentSkills} resolves them (skills default-on spec §3):
25
+ * - the agent's own skills (`scope='agent'`) and the org skills it shared;
26
+ * - plus every skill on in the org, minus what the run's agent version's `disabledSkills` turns
27
+ * off (never a builtin plugin's skill);
28
+ * - deleted skills are skipped;
29
+ * - the version is `current_version`, the description the one in that version's manifest.
30
+ * When an org skill and the agent's own skill share a name only the agent's own is kept (more
31
+ * specific; neither `/skills/<name>/` nor loading by name can have two).
42
32
  *
43
33
  * Trials: a pending version proposal of this agent made in this conversation replaces, by name,
44
34
  * the version of its skill (the id of the existing skill, the version it would become on
@@ -77,12 +67,13 @@ function forModel(all, input, log) {
77
67
  * `conversationId` (a conversation not created yet) no trial draft applies.
78
68
  */
79
69
  export async function resolveSkills(db, input, log) {
80
- // Phase 2 (Task 3): `orgSkills` ids may be org skills, org copies linked to a platform skill
81
- // (served as its current version under the copy's id, ruling B2) or platform skill ids; only
82
- // those usable in the org resolve (skills/usable.ts)
83
- const rows = await selectedSkills(db, { orgId: input.orgId, agentId: input.agentId, orgSkills: input.agent.skills?.orgSkills ?? [] }, log);
84
- // One skill per name: the agent's own, then an org skill or copy (an id the agent kept from
85
- // before the platform component existed), then a platform skill id; ties by id
70
+ const rows = await agentSkills(db, {
71
+ orgId: input.orgId,
72
+ agentId: input.agentId,
73
+ disabledSkills: input.agent.disabledSkills ?? [],
74
+ }, log);
75
+ // One skill per name: the agent's own, then an org skill or copy, then a platform skill; ties by
76
+ // id
86
77
  const rank = (r) => r.scope === "agent" ? 0 : r.platformSkillId === r.id ? 2 : 1;
87
78
  const byName = new Map();
88
79
  for (const r of rows) {
@@ -103,6 +94,8 @@ export async function resolveSkills(db, input, log) {
103
94
  },
104
95
  format: r.format,
105
96
  manifest: r.manifest,
97
+ scope: r.scope,
98
+ origin: r.origin,
106
99
  },
107
100
  ]));
108
101
  const trials = input.conversationId === undefined
@@ -118,6 +111,7 @@ export async function resolveSkills(db, input, log) {
118
111
  .where("kind", "=", "version")
119
112
  .execute();
120
113
  for (const { proposed_version: p, object_prefix } of trials) {
114
+ const replaced = effective.get(p.manifest.name);
121
115
  effective.set(p.manifest.name, {
122
116
  skill: {
123
117
  id: p.skillId,
@@ -128,160 +122,61 @@ export async function resolveSkills(db, input, log) {
128
122
  },
129
123
  format: "capybara",
130
124
  manifest: p.manifest,
125
+ scope: replaced?.scope ?? "agent",
126
+ origin: replaced?.origin ?? "org",
131
127
  });
132
128
  }
133
129
  return [...effective.values()].sort((a, b) => a.skill.name < b.skill.name ? -1 : a.skill.name > b.skill.name ? 1 : 0);
134
130
  }
135
131
  /**
136
- * The pinned skills of the run's agent version that are effective now, in the definition's order
137
- * (unique, at most {@link PINNED_SKILLS_LIMITS}.count). An id that is not in the model's catalog
138
- * (`model`: deleted, deselected, its plugin uninstalled, shadowed by an agent skill of the same
139
- * name) is skipped; one whose current version is manual-only (phase D) is skipped with a warning
140
- * when `log` is given.
132
+ * The {@link BudgetTier} of a resolved skill: the agent's own (`scope` `agent`, or an org skill it
133
+ * shared, `known.shared`: always on, never turned off, spec §3), else builtin (a platform skill of
134
+ * a builtin plugin, `known.plugins`, or one of `known.ids`, as `builtinSkillIds` gives them), else
135
+ * the org's own (no plugin), else a plugin skill.
141
136
  */
142
- function pinnedCandidates(model, agent, input, all, log) {
143
- const byId = new Map(model.map((r) => [r.skill.id, r]));
144
- const manual = new Map(all.filter((r) => !invocationOf(r.manifest).model).map((r) => [r.skill.id, r]));
145
- const out = [];
146
- for (const id of new Set(agent.skills?.pinned ?? [])) {
147
- const m = manual.get(id);
148
- if (m) {
149
- log?.warn("pinned skill is manual-only", {
150
- orgId: input.orgId,
151
- agentId: input.agentId,
152
- conversationId: input.conversationId,
153
- skillId: id,
154
- name: m.skill.name,
155
- version: m.skill.version,
156
- });
157
- continue;
158
- }
159
- const r = byId.get(id);
160
- if (!r)
161
- continue;
162
- const { name, version, source } = r.skill;
163
- out.push({ id, version, name, source, manifest: r.manifest });
164
- if (out.length >= PINNED_SKILLS_LIMITS.count)
165
- break;
137
+ export function budgetTier(r, known) {
138
+ if (r.scope === "agent" || known.shared.has(r.skill.id))
139
+ return 0;
140
+ const plugin = r.skill.plugin;
141
+ if ((r.origin === "platform" && plugin !== undefined && known.plugins.has(plugin)) ||
142
+ known.ids.has(r.skill.id)) {
143
+ return 1;
166
144
  }
167
- return out;
145
+ return plugin === undefined ? 2 : 3;
168
146
  }
169
- const sameCandidates = (a, b) => a.length === b.length &&
170
- a.every((p, i) => {
171
- const q = b[i];
172
- return q !== undefined && p.id === q.id && p.version === q.version && p.source === q.source;
173
- });
174
- const storageUnavailable = (message) => new ApiError("storage.unavailable", message);
175
- /** `details[0].reason` of a pinned SKILL.md that cannot be used whatever the retries. */
176
- const PINNED_UNREADABLE = "pinned_unreadable";
177
- /** Read attempts per pinned SKILL.md (a transient storage error is retried once). */
178
- const PINNED_READ_ATTEMPTS = 2;
147
+ /** The characters a described entry takes in the listing: its line and the line break. */
148
+ export const catalogLineChars = (s) => [...catalogLine(s)].length + 1;
179
149
  /**
180
- * The `run_failed` message (shown to the user, English like the other run texts) for a
181
- * `storage.unavailable` from {@link SkillsCatalogService.record}: a pinned SKILL.md that is missing,
182
- * corrupt or unparsable points at the agent's owner, since sending again cannot help; anything
183
- * else is a transient storage failure.
150
+ * The catalog budget (skills default-on spec §7): the entries ranked by `tierOf` (then by name, in
151
+ * code point order) keep their descriptions while the running total of their rendered lines
152
+ * ({@link catalogLineChars}) stays within `budget`; the first one past it and every one after it
153
+ * are name-only. Pure and deterministic; `tierOf` is only called when the whole listing does not
154
+ * fit. Entries keep their input order; any `nameOnly` they carry is replaced.
184
155
  */
185
- export function pinnedFailureText(e) {
186
- const d = e.details?.[0];
187
- if (d?.reason === PINNED_UNREADABLE) {
188
- return `Pinned skill "${d.name}" cannot be read; ask the agent's owner to unpin it or upload it again.`;
189
- }
190
- return "File storage is temporarily unavailable, so this agent's pinned skills cannot be read; send the message again later.";
191
- }
192
- /**
193
- * Reads each pinned skill's SKILL.md from `kernel/<orgId>/<source>SKILL.md` (a platform version's
194
- * `platform:<prefix>` from `deployment/<prefix>SKILL.md`), verifies it against
195
- * the manifest's size and sha256 and returns the parsed body, in order. Every failure throws
196
- * `storage.unavailable` (Decision 9: the run fails before any model request):
197
- * - a read error is retried once ({@link PINNED_READ_ATTEMPTS}), then is transient (the user
198
- * sends again);
199
- * - a manifest without SKILL.md, a missing object, an integrity mismatch, non-UTF-8 or an
200
- * unparsable file is permanent: logged at `error` with the skill id and source, and the error
201
- * carries `details: [{ reason: "pinned_unreadable", skillId, name, source }]`
202
- * ({@link pinnedFailureText} turns it into an owner-facing message).
203
- */
204
- export async function loadPinnedBodies(storage, orgId, pinned, opts = {}) {
205
- if (pinned.length === 0)
206
- return [];
207
- storage.assertAvailable();
208
- const out = [];
209
- for (const p of pinned) {
210
- // a platform version (`platform:<prefix>`) is read from deployment/ (ruling D2)
211
- const { store, prefix } = skillFileStore(storage, orgId, p.source);
212
- const unreadable = (problem) => {
213
- opts.log?.error("pinned skill unreadable", {
214
- orgId,
215
- skillId: p.id,
216
- version: p.version,
217
- source: p.source,
218
- problem,
219
- });
220
- return new ApiError("storage.unavailable", `Pinned skill ${p.name} ${problem}`, [
221
- { reason: PINNED_UNREADABLE, skillId: p.id, name: p.name, source: p.source },
222
- ]);
223
- };
224
- const entry = p.manifest.files.find((f) => f.path === SKILL_ENTRY_FILE);
225
- if (!entry)
226
- throw unreadable(`has no ${SKILL_ENTRY_FILE} in its manifest`);
227
- let bytes = null;
228
- for (let attempt = 1; bytes === null; attempt++) {
229
- let got;
230
- try {
231
- got = await store.get(`${prefix}${SKILL_ENTRY_FILE}`, opts.signal && { signal: opts.signal });
232
- if (got)
233
- bytes = new Uint8Array(await new Response(got.body).arrayBuffer());
234
- }
235
- catch {
236
- if (attempt >= PINNED_READ_ATTEMPTS) {
237
- throw storageUnavailable(`Cannot read pinned skill ${p.name} from storage`);
238
- }
239
- continue;
240
- }
241
- if (!got)
242
- throw unreadable("is missing from storage");
243
- }
244
- if (bytes.byteLength !== entry.size ||
245
- createHash("sha256").update(bytes).digest("hex") !== entry.sha256) {
246
- throw unreadable("failed its integrity check");
247
- }
248
- let text;
249
- try {
250
- text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
251
- }
252
- catch {
253
- throw unreadable("is not UTF-8");
254
- }
255
- const parsed = parseSkillMd(text, { invocationKeys: false });
256
- if ("errors" in parsed)
257
- throw unreadable("cannot be parsed");
258
- out.push({ id: p.id, version: p.version, name: p.name, source: p.source, body: parsed.body });
259
- }
260
- return out;
261
- }
262
- /**
263
- * The pinned skills that fit: in order while the running total of UTF-8 body bytes stays within
264
- * {@link PINNED_SKILLS_LIMITS}.totalBytes; the first that does not fit and every later one are
265
- * dropped with a warning (Review Focus 4: a pinned skill's new version can push a saved set over).
266
- */
267
- export function fitPinned(pinned, log, input) {
268
- const enc = new TextEncoder();
156
+ export async function applyCatalogBudget(skills, budget, tierOf) {
157
+ const described = skills.map(({ nameOnly: _n, ...s }) => s);
158
+ const cost = described.map(catalogLineChars);
159
+ const characters = cost.reduce((a, b) => a + b, 0);
160
+ if (characters <= budget)
161
+ return { skills: described, characters, nameOnly: 0 };
162
+ const tier = await tierOf();
163
+ const order = described
164
+ .map((s, i) => ({ s, i, t: tier(s) }))
165
+ .sort((a, b) => a.t !== b.t ? a.t - b.t : a.s.name < b.s.name ? -1 : a.s.name > b.s.name ? 1 : a.i - b.i);
166
+ const cut = new Set();
269
167
  let total = 0;
270
- for (let i = 0; i < pinned.length; i++) {
271
- total += enc.encode(pinned[i].body).byteLength;
272
- if (total > PINNED_SKILLS_LIMITS.totalBytes) {
273
- log?.warn("pinned skills over the limit", {
274
- orgId: input.orgId,
275
- agentId: input.agentId,
276
- conversationId: input.conversationId,
277
- kept: pinned.slice(0, i).map((p) => p.name),
278
- dropped: pinned.slice(i).map((p) => p.name),
279
- maxBytes: PINNED_SKILLS_LIMITS.totalBytes,
280
- });
281
- return pinned.slice(0, i);
282
- }
168
+ for (const { i } of order) {
169
+ const c = cost[i];
170
+ if (cut.size === 0 && total + c <= budget)
171
+ total += c;
172
+ else
173
+ cut.add(i);
283
174
  }
284
- return [...pinned];
175
+ return {
176
+ skills: described.map((s, i) => (cut.has(i) ? { ...s, nameOnly: true } : s)),
177
+ characters,
178
+ nameOnly: cut.size,
179
+ };
285
180
  }
286
181
  /** `skills_catalog` 的幂等键:每次运行最多写一份。 */
287
182
  /**
@@ -337,66 +232,74 @@ export async function lockLatestCatalog(db, conversationId) {
337
232
  .executeTakeFirst();
338
233
  return last ? last.payload : undefined;
339
234
  }
340
- /** How often run start re-resolves when the pinned set changed while its bodies were read. */
341
- const RECORD_ATTEMPTS = 3;
342
235
  /**
343
236
  * 运行开始时记录技能目录快照:解析生效的技能,与日志里最新一份不同才写(带幂等键,崩溃重试不重复)。
344
237
  * 从没有快照、现在也没有技能 → 不写(没有技能的 agent 不出现目录);之前有、现在全没了 → 写空快照(模型据此得知被移除)。
345
238
  * 解析、比较、追加在一个事务里、先锁会话行:否则在解析与追加之间提交的拒绝 + 回退快照会被过时的草稿条目盖回去。
346
239
  * 解析只读 skills / agents / 申请(不加锁),不会与别的路径形成死锁。
347
240
  *
348
- * Phase B: the snapshot also carries each skill's `plugin`, the pinned skills' bodies (`pinned`)
349
- * and the Claude Code tool-name map (`toolMap`, iff an effective skill has source format
350
- * `claude-code`). Pinned bodies are read from object storage outside the transaction (1); inside
351
- * it the skills are resolved again (2), and when the pinned identities changed meanwhile the
352
- * whole thing starts over (at most {@link RECORD_ATTEMPTS} times, then `conflict`). A read failure
353
- * throws `storage.unavailable` before anything is written. Writes only when `!sameSnapshot`.
241
+ * The snapshot also carries each skill's `plugin` and the Claude Code tool-name map (`toolMap`,
242
+ * iff an effective skill has source format `claude-code`). It never carries `pinned` any more
243
+ * (skills default-on spec §8): a conversation whose latest snapshot has pins gets one without them,
244
+ * and the request tells the model they were removed. Run start reads no object storage.
245
+ *
246
+ * The catalog budget (`skills.catalogBudgetChars`, spec §7) is applied here and recorded in the
247
+ * snapshot: entries past it carry `nameOnly: true` ({@link applyCatalogBudget}); the cut is logged
248
+ * as `skills.catalog_over_budget`. A changed budget writes a new snapshot at the next run start
249
+ * only when an entry crosses it. Snapshots appended elsewhere (a skill registration, a trial's
250
+ * revert) carry the entries forward as they are; the next run start applies the budget again.
354
251
  */
355
252
  let SkillsCatalogService = class SkillsCatalogService {
356
253
  db;
357
- storage;
358
254
  eventLog;
359
255
  log;
360
256
  txHost;
361
- constructor(db, storage, eventLog, log, txHost) {
257
+ config;
258
+ constructor(db, eventLog, log, txHost, config) {
362
259
  this.db = db;
363
- this.storage = storage;
364
260
  this.eventLog = eventLog;
365
261
  this.log = log;
366
262
  this.txHost = txHost;
263
+ this.config = config;
367
264
  }
368
265
  /**
369
266
  * Records the run start's snapshot (see the class comment). `opts.log`: the run's logger
370
267
  * (default the kernel logger).
371
268
  */
372
269
  async record(input, opts = {}) {
373
- const log = opts.log ?? this.log;
374
- for (let attempt = 1; attempt <= RECORD_ATTEMPTS; attempt++) {
375
- const all = await resolveSkills(this.db.db, input, attempt === 1 ? log : undefined);
376
- // Warn about manual-only pins once, on the first attempt
377
- const first = pinnedCandidates(forModel(all, input), input.agent, input, all, attempt === 1 ? log : undefined);
378
- const pinned = fitPinned(await loadPinnedBodies(this.storage, input.orgId, first, { log }), log, input);
379
- const toSeq = await this.append(input, first, pinned, log);
380
- if (toSeq === "retry")
381
- continue;
382
- if (toSeq !== null)
383
- await this.eventLog.notify(input.conversationId, toSeq);
384
- return;
385
- }
386
- throw new ApiError("conflict", "The pinned skills kept changing while the run started; send the message again.");
270
+ const toSeq = await this.append(input, opts.log ?? this.log);
271
+ if (toSeq !== null)
272
+ await this.eventLog.notify(input.conversationId, toSeq);
387
273
  }
388
- /** Step (2) of {@link record}: under the conversation row lock, resolve again and append. */
389
- async append(input, first, pinned, log) {
274
+ /** Under the conversation row lock, resolves the skills and appends a changed snapshot. */
275
+ async append(input, log) {
390
276
  const last = await lockLatestCatalog(this.db.db, input.conversationId);
391
- const all = await resolveSkills(this.db.db, input);
277
+ const all = await resolveSkills(this.db.db, input, log);
392
278
  const resolved = forModel(all, input, log);
393
- if (!sameCandidates(first, pinnedCandidates(resolved, input.agent, input, all))) {
394
- return "retry";
279
+ const budget = this.config.get().skills.catalogBudgetChars;
280
+ const cut = await applyCatalogBudget(resolved.map((r) => r.skill), budget, async () => {
281
+ const plugins = await builtinSkillPlugins(this.db.db);
282
+ const ids = new Set(await builtinSkillIds(this.db.db, input.orgId));
283
+ const shared = new Set((await agentOwnSkillIds(this.db.db, input)).shared);
284
+ // names are unique in the catalog
285
+ const tiers = new Map(resolved.map((r) => [r.skill.name, budgetTier(r, { plugins, ids, shared })]));
286
+ return (s) => tiers.get(s.name) ?? 3;
287
+ });
288
+ const skills = cut.skills;
289
+ if (cut.nameOnly > 0) {
290
+ this.log.info("skills.catalog_over_budget", {
291
+ orgId: input.orgId,
292
+ agentId: input.agentId,
293
+ conversationId: input.conversationId,
294
+ runId: input.runId,
295
+ entries: skills.length,
296
+ nameOnly: cut.nameOnly,
297
+ characters: cut.characters,
298
+ budget,
299
+ });
395
300
  }
396
- const skills = resolved.map((r) => r.skill);
397
301
  const next = {
398
302
  skills,
399
- ...(pinned.length > 0 && { pinned }),
400
303
  // The model's (truncated) skills as before phase D, plus manual-only ones: a person can
401
304
  // invoke one whose body names Claude tools (batch 1 review M8)
402
305
  ...([...resolved, ...all.filter((r) => !invocationOf(r.manifest).model)].some((r) => r.format === "claude-code") && {
@@ -422,9 +325,9 @@ __decorate([
422
325
  SkillsCatalogService = __decorate([
423
326
  Injectable(),
424
327
  __param(0, Inject(KernelDb)),
425
- __param(1, Inject(StorageService)),
426
- __param(2, Inject(EventLog)),
427
- __param(3, Inject(KERNEL_LOGGER)),
428
- __param(4, Inject(TransactionHost))
328
+ __param(1, Inject(EventLog)),
329
+ __param(2, Inject(KERNEL_LOGGER)),
330
+ __param(3, Inject(TransactionHost)),
331
+ __param(4, Inject(KernelConfigService))
429
332
  ], SkillsCatalogService);
430
333
  export { SkillsCatalogService };
@@ -21,9 +21,9 @@ export interface CancelledDrafts {
21
21
  * `EventLog.append` takes) and reads the latest snapshot under it, so no other snapshot slips in
22
22
  * between "read latest" and "append". Returns the seq written, or null when nothing was written;
23
23
  * the caller calls `eventLog.notify` after the commit.
24
- * `derive` gets the whole latest payload, so `pinned` and `toolMap` are carried forward; a pinned
25
- * entry is kept only while `skills` has the same id, version and source (pinned ⊆ skills), and the
26
- * pinned bodies still fit the size limit. The tool map is only recomputed at run start.
24
+ * `derive` gets the whole latest payload, so `toolMap` is carried forward (it is only recomputed at
25
+ * run start). `pinned` of an old snapshot never is: new snapshots carry no pinned skills (skills
26
+ * default-on spec §8).
27
27
  */
28
28
  export declare function appendConversationCatalog(deps: ConversationCatalogDeps, conversationId: string, derive: (base: SkillsSnapshot) => SkillsSnapshot | null, idemKey: string, runId?: string | null): Promise<number | null>;
29
29
  /**
@@ -1,7 +1,7 @@
1
1
  import { sql } from "kysely";
2
2
  import { audit } from "../identity/audit.js";
3
- import { sameSnapshot, } from "../runtime/context/skills.js";
4
- import { fitPinned, invocationOf, lockLatestCatalog } from "./catalog.js";
3
+ import { sameSnapshot } from "../runtime/context/skills.js";
4
+ import { invocationOf, lockLatestCatalog } from "./catalog.js";
5
5
  const byName = (a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
6
6
  /**
7
7
  * In the caller's transaction, derives the conversation's next skills catalog snapshot from its
@@ -9,9 +9,9 @@ const byName = (a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
9
9
  * `EventLog.append` takes) and reads the latest snapshot under it, so no other snapshot slips in
10
10
  * between "read latest" and "append". Returns the seq written, or null when nothing was written;
11
11
  * the caller calls `eventLog.notify` after the commit.
12
- * `derive` gets the whole latest payload, so `pinned` and `toolMap` are carried forward; a pinned
13
- * entry is kept only while `skills` has the same id, version and source (pinned ⊆ skills), and the
14
- * pinned bodies still fit the size limit. The tool map is only recomputed at run start.
12
+ * `derive` gets the whole latest payload, so `toolMap` is carried forward (it is only recomputed at
13
+ * run start). `pinned` of an old snapshot never is: new snapshots carry no pinned skills (skills
14
+ * default-on spec §8).
15
15
  */
16
16
  export async function appendConversationCatalog(deps, conversationId, derive, idemKey, runId) {
17
17
  const base = (await lockLatestCatalog(deps.db.db, conversationId)) ?? { skills: [] };
@@ -19,11 +19,8 @@ export async function appendConversationCatalog(deps, conversationId, derive, id
19
19
  if (derived === null)
20
20
  return null;
21
21
  const skills = [...derived.skills].sort(byName);
22
- const live = (p) => skills.some((s) => s.id === p.id && s.version === p.version && s.source === p.source);
23
- const pinned = fitPinned((derived.pinned ?? []).filter(live), deps.log, { conversationId });
24
22
  const next = {
25
23
  skills,
26
- ...(pinned.length > 0 && { pinned }),
27
24
  ...(derived.toolMap !== undefined && { toolMap: derived.toolMap }),
28
25
  };
29
26
  if (sameSnapshot(base, next))
@@ -88,7 +85,6 @@ export async function revertTrialCatalog(deps, req) {
88
85
  if (!base.skills.some((s) => s.source === req.object_prefix))
89
86
  return null;
90
87
  const rest = base.skills.filter((s) => s.source !== req.object_prefix);
91
- // The draft's pinned entry goes (Decision 8): the next run start pins the real version
92
88
  return {
93
89
  ...base,
94
90
  // A manual-only current version (phase D) stays out of the model's catalog
@@ -104,9 +100,6 @@ export async function revertTrialCatalog(deps, req) {
104
100
  },
105
101
  ]
106
102
  : rest,
107
- ...(base.pinned && {
108
- pinned: base.pinned.filter((p) => p.source !== req.object_prefix),
109
- }),
110
103
  };
111
104
  }, `skills_catalog:revert:${req.id}`);
112
105
  }
@@ -61,7 +61,7 @@ export declare const notInvocable: (reason: NotInvocableReason, name: string) =>
61
61
  export declare function recordedSkillBlocked(db: KernelSchemaDb, a: {
62
62
  orgId: string;
63
63
  agentId: string;
64
- agent: Pick<AgentDefinition, "skills">;
64
+ agent: Pick<AgentDefinition, "disabledSkills">;
65
65
  conversationId: string;
66
66
  skillId: string;
67
67
  }): Promise<NotInvocableReason | null>;
@@ -93,7 +93,7 @@ export declare class SkillInvoker {
93
93
  list(a: {
94
94
  orgId: string;
95
95
  agentId: string;
96
- agent: Pick<AgentDefinition, "skills">;
96
+ agent: Pick<AgentDefinition, "disabledSkills">;
97
97
  conversationId?: string;
98
98
  }): Promise<InvocableSkill[]>;
99
99
  /**
@@ -41,7 +41,9 @@ export declare class SkillsController {
41
41
  list(principal: Principal, org: OrgContext): Promise<{
42
42
  skills: SkillSummary[];
43
43
  }>;
44
- get(principal: Principal, org: OrgContext, id: string): Promise<SkillDetail>;
44
+ get(principal: Principal, org: OrgContext, id: string): Promise<SkillDetail & {
45
+ agentsOff: number;
46
+ }>;
45
47
  /**
46
48
  * Sets 可共享 on the org's own skill (`{ shareable }` -> 200 `{ id, shareable }`): only an org
47
49
  * skill of the org's own (P3-6: an agent's skill or a copy of another owner's platform skill ->
@@ -1,4 +1,4 @@
1
- import type { AgentDefinition } from "../agents/definition.js";
1
+ import { type DisabledSkillRef } from "../agents/definition.js";
2
2
  import { type Owner } from "../catalog/ownership.js";
3
3
  import { type OwnerVersion, PlatformComponents } from "../catalog/platform-components.js";
4
4
  import { AttachmentsService, type CreatedAttachment } from "../conversations/attachments.service.js";
@@ -53,12 +53,6 @@ export interface SkillSummary {
53
53
  plugin: string | null;
54
54
  /** The format of the plugin the current version came from. */
55
55
  sourceFormat: SourceFormat;
56
- /** The plugin suggests pinning the current version (spec §9.1). */
57
- suggestPinned: boolean;
58
- /** SKILL.md size of the current version, in bytes (0 when the manifest has none). */
59
- entryBytes: number;
60
- /** Token estimate of the SKILL.md of the current version (ASCII rule: bytes / 4). */
61
- tokenEstimate: number;
62
56
  /** Phase D: the current version is manual-only (not in the model's catalog). */
63
57
  manual: boolean;
64
58
  /** Phase D: the current version runs as a sub-agent when a person invokes it. */
@@ -84,6 +78,12 @@ export interface SkillSummary {
84
78
  * selects this skill.
85
79
  */
86
80
  linkedIds?: string[];
81
+ /**
82
+ * In the library list only (true or false on every item there, absent elsewhere): a platform
83
+ * skill of a builtin plugin (`platformBuiltin`: builtin and platform-owned) or the org's pinned
84
+ * copy of one. Every agent has it and none can turn it off (skills default-on spec §3).
85
+ */
86
+ builtin?: boolean;
87
87
  }
88
88
  /** 技能的一个版本(含文件清单)。 */
89
89
  export interface SkillVersionView {
@@ -111,33 +111,23 @@ export interface UploadedSkill {
111
111
  created: boolean;
112
112
  }
113
113
  /**
114
- * 在调用方事务里核对 agent 定义引用的组织技能:每个 id 都必须是该组织未删除的 `scope=org` 技能
115
- * (or, phase 2, a platform skill on in the org: skills/usable.ts),
116
- * 否则 `validation_failed`(path `skills.orgSkills.<i>`);不可重复。调用方须已锁组织行(技能删除同样锁组织行,二者串行)。
117
- * `kept`:当前版本里已有的引用(保存时合法),修改时不再核对——技能之后被删也不挡住其他改动(运行时由 T14 忽略已删除的技能)。
118
- */
119
- export declare function assertOrgSkills(db: KernelSchemaDb, orgId: string, ids: readonly string[], kept?: ReadonlySet<string>): Promise<void>;
120
- /** Why a pinned skill reference is refused (`agent.pinned_invalid` details). */
121
- export type PinnedSkillProblem = "not_selected" | "not_found" | "duplicate" | "too_many" | "too_large"
122
- /** Phase D: the skill's current version is manual-only (not in the model's catalog). */
123
- | "manual_only";
124
- /**
125
- * Checks `skills.pinned` of an agent definition in the caller's transaction (the org row is locked,
126
- * like {@link assertOrgSkills}); throws 400 `agent.pinned_invalid` with one detail per problem
127
- * (`{ skill, reason, totalBytes? }`):
128
- * - `duplicate`: an id listed twice; `too_many`: every id past {@link PINNED_SKILLS_LIMITS}.count;
129
- * - each newly added id (not in `kept`, the current definition's pinned ids) must be a live org
130
- * skill listed in `skills.orgSkills` (else `not_selected`) or a live skill of this agent
131
- * (`agentId`; null when creating), else `not_found`. Kept ids are not re-checked: a pinned skill
132
- * deleted later does not block other edits (the run ignores it);
133
- * - `manual_only` (phase D): a newly added id whose current version is manual-only
134
- * (`invocation.model === false`); a kept one that became manual-only is dropped at run time;
135
- * - `too_large`: the current SKILL.md sizes of the live pinned skills total more than
136
- * {@link PINNED_SKILLS_LIMITS}.totalBytes (named by the first skill past the limit). Checked only
137
- * when the pinned list changed: a pinned skill's new version never blocks an unrelated edit (the
138
- * run keeps what fits).
114
+ * Checks `disabledSkills` of an agent definition in the caller's transaction (skills default-on
115
+ * spec §4); throws 400 `validation_failed` with one detail per problem at `disabledSkills.<i>`:
116
+ * - a plugin entry must name a plugin with at least one skill usable in the org, and each name must
117
+ * be a skill of it;
118
+ * - a skill entry must name a usable org skill without a plugin;
119
+ * - a builtin plugin, a builtin skill, the agent's own skill or an org skill it shared cannot be
120
+ * turned off.
121
+ * What the current version already turns off (`kept`) is not checked again: a whole entry, or the
122
+ * names a plugin entry kept, so a plugin switched off or uninstalled later never blocks other edits.
139
123
  */
140
- export declare function assertPinnedSkills(db: KernelSchemaDb, orgId: string, agentId: string | null, skills: AgentDefinition["skills"], kept?: readonly string[]): Promise<void>;
124
+ export declare function assertDisabledSkills(db: KernelSchemaDb, a: {
125
+ orgId: string;
126
+ /** Null when creating. */
127
+ agentId: string | null;
128
+ disabled: readonly DisabledSkillRef[];
129
+ kept?: readonly DisabledSkillRef[];
130
+ }): Promise<void>;
141
131
  /**
142
132
  * 技能库(M5 T13):组织技能的上传(新技能或新版本)、列表、详情与软删除。
143
133
  * 字节在对象存储 `kernel/<orgId>/skills/<uploadId>/<path>`,元数据在 PG;无进程内状态,可多副本共用。
@@ -217,8 +207,6 @@ export declare class SkillsService {
217
207
  approvedBy?: string;
218
208
  /** The catalog version's source format (default capybara). */
219
209
  format?: SourceFormat;
220
- /** The plugin manifest suggests pinning this skill (spec §9.1). */
221
- suggestPinned?: boolean;
222
210
  };
223
211
  /**
224
212
  * The files are already stored (no storage I/O here, so it may join a transaction): at
@@ -258,7 +246,9 @@ export declare class SkillsService {
258
246
  /**
259
247
  * The org skill library, sorted by name: the org's live `scope=org` skills except copies linked
260
248
  * to a platform skill without a pin, and the platform skills on in the org (phase 2,
261
- * `usableSkills`; `origin` tells them apart), each with the ids of those copies (`linkedIds`).
249
+ * `usableSkills`; `origin` tells them apart), each with the ids of those copies (`linkedIds`);
250
+ * every item carries `builtin`: true for a platform skill of a builtin plugin or the org's pinned
251
+ * copy of one (every agent has it, none can turn it off).
262
252
  */
263
253
  list(orgId: string): Promise<SkillSummary[]>;
264
254
  /**
@@ -279,7 +269,18 @@ export declare class SkillsService {
279
269
  * content under its own id, so its detail is the platform skill's (its id, versions and body;
280
270
  * 404 while that skill is off in the org); a pinned copy keeps its own (Task 10).
281
271
  */
282
- get(orgId: string, skillId: string): Promise<SkillDetail>;
272
+ get(orgId: string, skillId: string): Promise<SkillDetail & {
273
+ agentsOff: number;
274
+ }>;
275
+ /**
276
+ * How many of the org's live agents turn the skill off in their current version (skills
277
+ * default-on spec §10: 「N 个智能体关闭了它」): matched by the rule of `agentSkills` against the
278
+ * skill as the org's usable set holds it. A builtin skill, one not usable in the org and an org
279
+ * skill for the agent that shared it count 0.
280
+ */
281
+ private agentsOff;
282
+ /** The skill's detail without `agentsOff` (no agents scan; for names and checks). */
283
+ detail(orgId: string, skillId: string): Promise<SkillDetail>;
283
284
  /**
284
285
  * A platform skill's detail (phase 2): only while it is on in the org, else `skill.not_found`.
285
286
  * Its versions are the platform skill's.