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

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 (263) hide show
  1. package/dist/admin/admin.controller.d.ts +3 -3
  2. package/dist/admin/admin.controller.js +6 -2
  3. package/dist/admin/admin.schemas.d.ts +13 -0
  4. package/dist/agents/admin-agents.d.ts +16 -3
  5. package/dist/agents/admin-agents.js +65 -7
  6. package/dist/agents/agents.schemas.d.ts +24 -6
  7. package/dist/agents/agents.service.d.ts +4 -3
  8. package/dist/agents/agents.service.js +39 -26
  9. package/dist/agents/definition.d.ts +44 -6
  10. package/dist/agents/definition.js +86 -18
  11. package/dist/agents/from-template.d.ts +6 -3
  12. package/dist/agents/from-template.js +57 -44
  13. package/dist/agents/org-templates.js +97 -41
  14. package/dist/agents/templates.d.ts +6 -0
  15. package/dist/agents/templates.js +15 -1
  16. package/dist/background/launch.d.ts +43 -5
  17. package/dist/background/launch.js +88 -8
  18. package/dist/background/session.d.ts +14 -2
  19. package/dist/background/session.js +26 -4
  20. package/dist/background/status.d.ts +1 -1
  21. package/dist/background/status.js +3 -3
  22. package/dist/background/stop.d.ts +0 -2
  23. package/dist/background/stop.js +1 -21
  24. package/dist/catalog/admin-catalog.controller.d.ts +2 -4
  25. package/dist/catalog/admin-catalog.controller.js +2 -6
  26. package/dist/catalog/catalog-content.d.ts +5 -0
  27. package/dist/catalog/catalog-content.js +1 -0
  28. package/dist/catalog/catalog.module.js +3 -0
  29. package/dist/catalog/claude/report.d.ts +9 -2
  30. package/dist/catalog/claude/report.js +1 -0
  31. package/dist/catalog/claude/translate.js +1 -1
  32. package/dist/catalog/component-promotion.js +1 -2
  33. package/dist/catalog/component-state.d.ts +13 -1
  34. package/dist/catalog/component-state.js +14 -5
  35. package/dist/catalog/marketplaces.service.d.ts +1 -1
  36. package/dist/catalog/marketplaces.service.js +2 -1
  37. package/dist/catalog/org-catalog.controller.d.ts +13 -3
  38. package/dist/catalog/org-catalog.controller.js +26 -4
  39. package/dist/catalog/org-catalog.schemas.d.ts +13 -0
  40. package/dist/catalog/org-catalog.schemas.js +34 -0
  41. package/dist/catalog/org-plugin-uploads.controller.js +0 -1
  42. package/dist/catalog/org-plugin-uploads.d.ts +0 -1
  43. package/dist/catalog/org-plugin-uploads.js +0 -3
  44. package/dist/catalog/org-plugin-uploads.schemas.d.ts +0 -1
  45. package/dist/catalog/org-plugin-uploads.schemas.js +0 -2
  46. package/dist/catalog/platform-components.d.ts +0 -1
  47. package/dist/catalog/platform-components.js +3 -8
  48. package/dist/catalog/plugin-import.d.ts +0 -8
  49. package/dist/catalog/plugin-import.js +4 -35
  50. package/dist/catalog/plugin-install.d.ts +2 -2
  51. package/dist/catalog/plugin-install.js +2 -2
  52. package/dist/catalog/plugin-package.d.ts +7 -0
  53. package/dist/catalog/plugin-package.js +27 -1
  54. package/dist/catalog/plugin-upgrade.d.ts +5 -6
  55. package/dist/catalog/plugin-upgrade.js +5 -23
  56. package/dist/catalog/requests.service.d.ts +66 -10
  57. package/dist/catalog/requests.service.js +196 -15
  58. package/dist/channels/actions.d.ts +2 -0
  59. package/dist/channels/actions.js +19 -4
  60. package/dist/channels/cards.d.ts +16 -2
  61. package/dist/channels/cards.js +40 -7
  62. package/dist/channels/channels.module.js +3 -0
  63. package/dist/channels/outbound.d.ts +22 -1
  64. package/dist/channels/outbound.js +116 -7
  65. package/dist/channels/phrases.d.ts +25 -0
  66. package/dist/channels/phrases.js +19 -0
  67. package/dist/channels/threads.d.ts +12 -0
  68. package/dist/channels/threads.js +34 -0
  69. package/dist/config/schema.d.ts +23 -1
  70. package/dist/config/schema.js +107 -5
  71. package/dist/conversations/conversations.service.d.ts +42 -9
  72. package/dist/conversations/conversations.service.js +57 -25
  73. package/dist/conversations/events.d.ts +106 -11
  74. package/dist/conversations/events.js +121 -18
  75. package/dist/conversations/message-context.d.ts +6 -3
  76. package/dist/conversations/message-context.js +14 -10
  77. package/dist/conversations/queue.d.ts +15 -2
  78. package/dist/conversations/queue.js +22 -3
  79. package/dist/conversations/queue.service.d.ts +12 -6
  80. package/dist/conversations/queue.service.js +36 -13
  81. package/dist/db/kernel-schema.d.ts +105 -3
  82. package/dist/devices/conversation-devices.d.ts +8 -1
  83. package/dist/devices/conversation-devices.js +8 -0
  84. package/dist/devices/devices.controller.d.ts +21 -4
  85. package/dist/devices/devices.controller.js +46 -8
  86. package/dist/devices/devices.schemas.d.ts +9 -1
  87. package/dist/devices/devices.schemas.js +11 -3
  88. package/dist/devices/devices.service.d.ts +176 -4
  89. package/dist/devices/devices.service.js +347 -6
  90. package/dist/devices/dto.d.ts +56 -3
  91. package/dist/devices/dto.js +51 -1
  92. package/dist/devices/public-suffix.d.ts +6 -0
  93. package/dist/devices/public-suffix.js +79 -0
  94. package/dist/errors.d.ts +50 -5
  95. package/dist/errors.js +50 -5
  96. package/dist/evals/reconstruct.d.ts +18 -13
  97. package/dist/evals/reconstruct.js +50 -36
  98. package/dist/evals/recorded-tools.d.ts +7 -7
  99. package/dist/evals/recorded-tools.js +4 -3
  100. package/dist/evals/trajectory.d.ts +6 -1
  101. package/dist/evals/trajectory.js +7 -0
  102. package/dist/extensions/package.js +2 -2
  103. package/dist/http/api.module.js +5 -0
  104. package/dist/identity/settings.d.ts +53 -3
  105. package/dist/identity/settings.js +92 -9
  106. package/dist/inbox/inbox.module.js +2 -1
  107. package/dist/inbox/inbox.service.d.ts +19 -4
  108. package/dist/inbox/inbox.service.js +14 -3
  109. package/dist/infra/config/kernel-config.service.d.ts +7 -0
  110. package/dist/infra/config/kernel-config.service.js +31 -0
  111. package/dist/infra/events/kernel-events.d.ts +2 -1
  112. package/dist/local-devices/device-policy.service.d.ts +65 -0
  113. package/dist/local-devices/device-policy.service.js +217 -0
  114. package/dist/local-devices/local-device-gateway.d.ts +4 -2
  115. package/dist/local-devices/local-device-gateway.js +39 -11
  116. package/dist/local-devices/local-device-provider.d.ts +40 -3
  117. package/dist/local-devices/local-device-provider.js +109 -7
  118. package/dist/local-devices/local-devices.module.d.ts +2 -1
  119. package/dist/local-devices/local-devices.module.js +19 -3
  120. package/dist/local-devices/machines.d.ts +30 -3
  121. package/dist/local-devices/machines.js +79 -3
  122. package/dist/local-devices/network-grants.d.ts +104 -0
  123. package/dist/local-devices/network-grants.js +288 -0
  124. package/dist/local-devices/policy-summary.d.ts +20 -0
  125. package/dist/local-devices/policy-summary.js +63 -0
  126. package/dist/orgs/orgs.module.js +4 -10
  127. package/dist/orgs/roles.d.ts +1 -1
  128. package/dist/orgs/roles.js +4 -0
  129. package/dist/orgs/settings.controller.js +4 -3
  130. package/dist/orgs/settings.service.d.ts +172 -14
  131. package/dist/orgs/settings.service.js +229 -27
  132. package/dist/platform/exclusions.js +11 -1
  133. package/dist/platform/operation.js +4 -0
  134. package/dist/platform/ops/admin-items.ops.js +1 -1
  135. package/dist/platform/ops/admin.ops.js +32 -7
  136. package/dist/platform/ops/agent-skills.ops.js +2 -2
  137. package/dist/platform/ops/agent-templates.ops.js +2 -2
  138. package/dist/platform/ops/agents.ops.js +11 -15
  139. package/dist/platform/ops/devices.ops.js +3 -1
  140. package/dist/platform/ops/inbox.ops.js +19 -0
  141. package/dist/platform/ops/org-settings.ops.js +15 -6
  142. package/dist/platform/ops/orgs.ops.js +1 -0
  143. package/dist/platform/ops/platform.ops.d.ts +9 -1
  144. package/dist/platform/ops/platform.ops.js +34 -12
  145. package/dist/platform/ops/requests.ops.js +3 -2
  146. package/dist/platform/ops/schedules.ops.d.ts +21 -0
  147. package/dist/platform/ops/schedules.ops.js +470 -0
  148. package/dist/platform/ops/skills.ops.js +3 -3
  149. package/dist/platform/platform-ops.module.d.ts +2 -1
  150. package/dist/platform/platform-ops.module.js +5 -0
  151. package/dist/platform/route-schemas.js +3 -0
  152. package/dist/roles/worker.module.js +3 -0
  153. package/dist/runtime/context/build.js +13 -4
  154. package/dist/runtime/context/compaction.js +6 -3
  155. package/dist/runtime/context/device-paths.d.ts +43 -21
  156. package/dist/runtime/context/device-paths.js +104 -21
  157. package/dist/runtime/context/run-context.d.ts +9 -0
  158. package/dist/runtime/context/run-context.js +8 -0
  159. package/dist/runtime/context/skills.d.ts +14 -1
  160. package/dist/runtime/context/skills.js +36 -5
  161. package/dist/runtime/maintenance.d.ts +4 -2
  162. package/dist/runtime/maintenance.js +16 -5
  163. package/dist/runtime/run-finish.d.ts +6 -1
  164. package/dist/runtime/run-finish.js +16 -2
  165. package/dist/runtime/run-queue.js +21 -2
  166. package/dist/runtime/run-worker.js +14 -33
  167. package/dist/runtime/runtime.module.js +3 -0
  168. package/dist/runtime/subagents/definition.d.ts +32 -14
  169. package/dist/runtime/subagents/definition.js +106 -109
  170. package/dist/runtime/subagents/dispatch.js +4 -3
  171. package/dist/runtime/subagents/preamble.d.ts +2 -1
  172. package/dist/runtime/subagents/preamble.js +2 -1
  173. package/dist/runtime/waits.js +14 -4
  174. package/dist/sandbox/platform-tiers.d.ts +86 -0
  175. package/dist/sandbox/platform-tiers.js +97 -0
  176. package/dist/sandbox/sandbox.service.d.ts +121 -11
  177. package/dist/sandbox/sandbox.service.js +351 -30
  178. package/dist/sandbox/tiers.d.ts +98 -0
  179. package/dist/sandbox/tiers.js +104 -0
  180. package/dist/schedules/effective-settings.d.ts +16 -0
  181. package/dist/schedules/effective-settings.js +17 -0
  182. package/dist/schedules/errors.d.ts +8 -0
  183. package/dist/schedules/errors.js +8 -0
  184. package/dist/schedules/fire.d.ts +118 -0
  185. package/dist/schedules/fire.js +625 -0
  186. package/dist/schedules/grant-rules.d.ts +94 -0
  187. package/dist/schedules/grant-rules.js +126 -0
  188. package/dist/schedules/job-ids.d.ts +32 -0
  189. package/dist/schedules/job-ids.js +27 -0
  190. package/dist/schedules/pause.d.ts +9 -0
  191. package/dist/schedules/pause.js +33 -0
  192. package/dist/schedules/run-tracker.d.ts +81 -0
  193. package/dist/schedules/run-tracker.js +333 -0
  194. package/dist/schedules/scan.job.d.ts +64 -0
  195. package/dist/schedules/scan.job.js +303 -0
  196. package/dist/schedules/schedule-math.d.ts +31 -0
  197. package/dist/schedules/schedule-math.js +223 -0
  198. package/dist/schedules/scheduled-tasks.service.d.ts +351 -0
  199. package/dist/schedules/scheduled-tasks.service.js +1193 -0
  200. package/dist/schedules/schedules.controller.d.ts +68 -0
  201. package/dist/schedules/schedules.controller.js +242 -0
  202. package/dist/schedules/schedules.module.d.ts +16 -0
  203. package/dist/schedules/schedules.module.js +65 -0
  204. package/dist/schedules/schedules.schemas.d.ts +316 -0
  205. package/dist/schedules/schedules.schemas.js +186 -0
  206. package/dist/schedules/store.d.ts +81 -0
  207. package/dist/schedules/store.js +390 -0
  208. package/dist/schedules/store.module.d.ts +6 -0
  209. package/dist/schedules/store.module.js +21 -0
  210. package/dist/schedules/types.d.ts +108 -0
  211. package/dist/schedules/types.js +1 -0
  212. package/dist/schedules/unattended.d.ts +117 -0
  213. package/dist/schedules/unattended.js +228 -0
  214. package/dist/setup/oauth-token-probe.js +8 -4
  215. package/dist/skills/agent-skills.service.d.ts +4 -8
  216. package/dist/skills/agent-skills.service.js +18 -53
  217. package/dist/skills/catalog.d.ts +65 -62
  218. package/dist/skills/catalog.js +109 -206
  219. package/dist/skills/conversation-catalog.d.ts +3 -3
  220. package/dist/skills/conversation-catalog.js +5 -12
  221. package/dist/skills/invoke.d.ts +2 -2
  222. package/dist/skills/skills.controller.d.ts +3 -1
  223. package/dist/skills/skills.service.d.ts +38 -37
  224. package/dist/skills/skills.service.js +168 -107
  225. package/dist/skills/usable.d.ts +57 -8
  226. package/dist/skills/usable.js +172 -38
  227. package/dist/tools/approvals.controller.d.ts +4 -2
  228. package/dist/tools/approvals.controller.js +17 -2
  229. package/dist/tools/approvals.schemas.d.ts +1 -0
  230. package/dist/tools/approvals.schemas.js +3 -1
  231. package/dist/tools/approvals.service.d.ts +182 -7
  232. package/dist/tools/approvals.service.js +597 -13
  233. package/dist/tools/builtin-names.d.ts +25 -4
  234. package/dist/tools/builtin-names.js +45 -4
  235. package/dist/tools/builtins/builtin-tools.d.ts +8 -0
  236. package/dist/tools/builtins/builtin-tools.js +23 -2
  237. package/dist/tools/builtins/sandbox/bash.js +16 -7
  238. package/dist/tools/builtins/sandbox/common.d.ts +1 -1
  239. package/dist/tools/builtins/sandbox/common.js +33 -10
  240. package/dist/tools/builtins/sandbox/edit.js +7 -3
  241. package/dist/tools/builtins/sandbox/file-state.d.ts +31 -1
  242. package/dist/tools/builtins/sandbox/file-state.js +33 -3
  243. package/dist/tools/builtins/sandbox/write.js +8 -4
  244. package/dist/tools/builtins/schedules.d.ts +107 -0
  245. package/dist/tools/builtins/schedules.js +789 -0
  246. package/dist/tools/elicitation.d.ts +2 -0
  247. package/dist/tools/elicitation.js +1 -1
  248. package/dist/tools/fixed-ask.d.ts +7 -0
  249. package/dist/tools/fixed-ask.js +9 -0
  250. package/dist/tools/network-asks.d.ts +109 -0
  251. package/dist/tools/network-asks.js +221 -0
  252. package/dist/tools/pipeline.d.ts +53 -6
  253. package/dist/tools/pipeline.js +202 -13
  254. package/dist/tools/policy.d.ts +21 -8
  255. package/dist/tools/policy.js +63 -9
  256. package/dist/tools/registry.d.ts +10 -1
  257. package/dist/tools/tools.module.js +12 -0
  258. package/package.json +2 -2
  259. package/src/migrations/0113_scheduled_tasks.sql +108 -0
  260. package/src/migrations/0114_network_asks.sql +23 -0
  261. package/src/migrations/0115_network_requests.sql +28 -0
  262. package/src/migrations/0116_skills_default_on.sql +37 -0
  263. package/src/migrations/0117_sandbox_tiers.sql +11 -0
@@ -66,6 +66,69 @@ export function canonicalDisabledTools(disabled) {
66
66
  }
67
67
  return out;
68
68
  }
69
+ /**
70
+ * A skill an agent turns off (skills default-on spec §4): a plugin's skills by name, or `"all"` to
71
+ * turn the plugin off as a unit (skills it gains later included); a skill without a plugin (the
72
+ * org's own, shared or promoted ones) by id, since its name may change under the same id.
73
+ */
74
+ export const DisabledSkillRef = z.discriminatedUnion("kind", [
75
+ z
76
+ .object({
77
+ kind: z.literal("plugin"),
78
+ plugin: z.string().min(1).max(200),
79
+ skills: z.union([z.literal("all"), z.array(z.string().min(1).max(64)).min(1).max(500)]),
80
+ })
81
+ .strict(),
82
+ z.object({ kind: z.literal("skill"), skillId: z.string().min(1).max(100) }).strict(),
83
+ ]);
84
+ /**
85
+ * Is a skill turned off by `disabled`? `plugin` and `name` match a plugin entry; `ids` (the
86
+ * skill's id and, for a linked or promoted copy, the platform skill it serves) a skill entry.
87
+ */
88
+ export function skillDisabled(disabled, skill) {
89
+ return (disabled ?? []).some((r) => r.kind === "plugin"
90
+ ? skill.plugin !== null &&
91
+ r.plugin === skill.plugin &&
92
+ (r.skills === "all" || r.skills.includes(skill.name))
93
+ : skill.ids.includes(r.skillId));
94
+ }
95
+ /**
96
+ * `disabled` in its canonical form: one entry per plugin (sorted by plugin, `"all"` absorbing
97
+ * names, names deduplicated and sorted, an empty list dropped), then the skill entries deduplicated
98
+ * and sorted by id. Saving stores this form, so two lists that turn off the same skills compare
99
+ * equal.
100
+ */
101
+ export function canonicalDisabledSkills(disabled) {
102
+ const plugins = [];
103
+ const ids = new Set();
104
+ for (const r of disabled) {
105
+ if (r.kind === "skill") {
106
+ ids.add(r.skillId);
107
+ continue;
108
+ }
109
+ const names = r.skills;
110
+ const prev = plugins.find((x) => x.plugin === r.plugin);
111
+ if (prev) {
112
+ prev.skills =
113
+ prev.skills === "all" || names === "all"
114
+ ? "all"
115
+ : [...new Set([...prev.skills, ...names])].sort();
116
+ }
117
+ else {
118
+ plugins.push({
119
+ kind: "plugin",
120
+ plugin: r.plugin,
121
+ skills: names === "all" ? "all" : [...new Set(names)].sort(),
122
+ });
123
+ }
124
+ }
125
+ return [
126
+ ...plugins
127
+ .filter((p) => p.skills === "all" || p.skills.length > 0)
128
+ .sort((a, b) => (a.plugin < b.plugin ? -1 : a.plugin > b.plugin ? 1 : 0)),
129
+ ...[...ids].sort().map((skillId) => ({ kind: "skill", skillId })),
130
+ ];
131
+ }
69
132
  /**
70
133
  * A sub-agent's tool filter by tool key (tools default-on spec §4): written only into a child's
71
134
  * effective definition at dispatch (never accepted on save). `deny` removes keys (the child-removed
@@ -83,8 +146,8 @@ export const SubagentToolsSchema = z
83
146
  export const SUBAGENT_NAME_RE = /^[a-z0-9][a-z0-9-]{0,63}$/;
84
147
  /**
85
148
  * A sub-agent template written in the agent definition (phase C, spec §5.2): the fields of a
86
- * plugin agent template (`AgentTemplate`, spec §16.1), with `skills` as skill ids (org skills the
87
- * agent selects, or the agent's own) instead of names. Names and selections are checked on save
149
+ * plugin agent template (`AgentTemplate`, spec §16.1), with `skills` as skill ids (skills the agent
150
+ * has, skills default-on spec §6.1) instead of names. Names and skills are checked on save
88
151
  * (`agent.subagents_invalid`); the schema only bounds the input.
89
152
  */
90
153
  export const InlineAgentTemplate = z
@@ -217,25 +280,19 @@ export const AgentDefinitionSchema = z.object({
217
280
  */
218
281
  toolPolicies: z.record(z.string(), z.enum(["allow", "ask", "deny"])).default({}),
219
282
  /**
220
- * 技能(M5 T13):`orgSkills` 是引用的组织技能 id(保存时校验属于本组织且未删除,不可重复)。
221
- * agent 自己的技能(`scope=agent`)自动对它生效,不进定义。可省略(较早保存的定义也没有),按空处理;
222
- * 不给默认值:没用技能的定义保存前后不变,不会因此多出一个版本。
283
+ * The skills this agent turns off (skills default-on spec §4); every other skill on in the org
284
+ * is on for it, and its own and shared skills always are. Saved in canonical form
285
+ * ({@link canonicalDisabledSkills}); checked on save.
286
+ */
287
+ disabledSkills: z.array(DisabledSkillRef).max(500).default([]),
288
+ /**
223
289
  * `writePolicy`: whether registering a skill on a private agent (`agents.register-skill`) needs
224
- * the person's confirmation; absent means `allow` (not stored in the definition). On a shared
225
- * agent a registration is always confirmed at the org level.
290
+ * the person's confirmation; absent means `allow`. On a shared agent a registration is always
291
+ * confirmed at the org level. No default: definitions without it gain no version.
226
292
  */
227
293
  skills: z
228
294
  .object({
229
- orgSkills: z.array(z.string().min(1).max(100)).max(100).default([]),
230
295
  writePolicy: z.enum(["allow", "ask"]).optional(),
231
- /**
232
- * Pinned skills (phase B, spec §9.1): ids of selected org skills or of the agent's own
233
- * skills, whose bodies apply throughout every conversation, in this order. At most
234
- * `PINNED_SKILLS_LIMITS.count` (checked with the other rules by `assertPinnedSkills`, as
235
- * `agent.pinned_invalid`; the schema only bounds the input). No default: definitions
236
- * without it are unchanged.
237
- */
238
- pinned: z.array(z.string().min(1).max(100)).max(100).optional(),
239
296
  })
240
297
  .strict()
241
298
  .optional(),
@@ -478,8 +535,10 @@ function normalizeToolRef(v) {
478
535
  * current keys ({@link normalizeToolKeys}: builtins always, `<x>__<tool>` keys with `scope`), and
479
536
  * a definition from before tools were on by default (an enabled `tools` list, history only: the
480
537
  * migration rewrote stored versions) reads as nothing turned off, which is what the migration
481
- * writes (tools default-on spec §2, §3). Other fields are kept; without old forms the same object
482
- * is returned.
538
+ * writes (tools default-on spec §2, §3). Likewise a definition from before skills were on by
539
+ * default (`skills.orgSkills` / `skills.pinned`) reads as no skill turned off (skills default-on
540
+ * spec §4; migration 0116 rewrote stored versions). Other fields are kept; without old forms the
541
+ * same object is returned.
483
542
  */
484
543
  export function normalizeStoredDefinition(def, scope) {
485
544
  let d = def;
@@ -495,6 +554,15 @@ export function normalizeStoredDefinition(def, scope) {
495
554
  else if (!Array.isArray(d.disabledTools)) {
496
555
  d = { ...d, disabledTools: [] };
497
556
  }
557
+ // before skills were on by default (skills default-on spec §4, §5): selected and pinned skills
558
+ // read as nothing turned off, which is what migration 0116 writes
559
+ if (isRecord(d.skills) && ("orgSkills" in d.skills || "pinned" in d.skills)) {
560
+ const { orgSkills: _o, pinned: _p, ...keep } = d.skills;
561
+ const { skills: _s, ...rest } = d;
562
+ d = Object.keys(keep).length > 0 ? { ...rest, skills: keep } : rest;
563
+ }
564
+ if (!Array.isArray(d.disabledSkills))
565
+ d = { ...d, disabledSkills: [] };
498
566
  return d;
499
567
  }
500
568
  /** 按模型声明严格校验一组参数,返回 `{ path: "<field>.<key>", message }[]`(评测候选也用它)。 */
@@ -16,8 +16,10 @@ export type PlanItemKind = "instruction_file" | "description" | "quick_action" |
16
16
  * describes sub-agents, though its allowlist leaves it out); unavailable `plugin_unavailable`
17
17
  * (its plugin is not enabled in the org or not loaded), `unknown_tool`, `connector_key` (a
18
18
  * connector tool key: connectors carry over only through `connectors` entries);
19
- * - skill: `pinned`, `not_pinned_manual_only`, `not_pinned_too_large`, `subagent` (named only by an
20
- * inline sub-agent); unavailable `not_found`;
19
+ * - skill (a name of `skills` or an inline sub-agent's `skills`, or a `disallowedSkills` entry): a
20
+ * carried name is on for the new agent, `subagent` when only an inline sub-agent names it;
21
+ * `disallowed` for a `disallowedSkills` entry turned off; unavailable `not_found` (the org does
22
+ * not have it);
21
23
  * - connector: `several` (the first of several connectors made from the template was bound);
22
24
  * pending `no_connector`, `tools_missing` (the connector made from the template lacks every
23
25
  * named tool), `needs`; unavailable `template_not_found` (also for a template the org may not
@@ -56,7 +58,8 @@ export interface TemplatePlan {
56
58
  * What has no place in the agent or was cut to its limits: `maxSteps`, `effort` when the model
57
59
  * does not declare it, `description` / `instructions` / `quickActions.<i>.title|body` /
58
60
  * `instructionFiles.<i>.content` / `subagents.inline.<name>.description|instructions` when cut to
59
- * the kernel's limits (UTF-16 units), `subagents.inline.<name>.tools.<key>` for a key the agent
61
+ * the kernel's limits (UTF-16 units), `disallowedSkills.<entry>` for an entry that names no skill
62
+ * the org has, `subagents.inline.<name>.tools.<key>` for a key the agent
60
63
  * does not have, `connectors.<key>.tools.<name>` / `connectors.<key>.policies.<name>` for a tool
61
64
  * the bound connector does not offer.
62
65
  */
@@ -3,14 +3,13 @@
3
3
  // create. Pure resolution over read snapshots: the database reads go through `db`, the replica's
4
4
  // extension state and the model lookups through `opts`; nothing is written here.
5
5
  import { createHash } from "node:crypto";
6
- import { connectorToolKey, extensionToolKey, parseToolKey, SKILL_ENTRY_FILE, validateParams, } from "@capybara-hq/plugin-sdk";
6
+ import { connectorToolKey, extensionToolKey, parseToolKey, validateParams, } from "@capybara-hq/plugin-sdk";
7
7
  import { connectorTemplateState } from "../catalog/component-state.js";
8
- import { invocationOf, PINNED_SKILLS_LIMITS } from "../skills/catalog.js";
9
- import { usableSkillVersions } from "../skills/usable.js";
8
+ import { builtinSkillIds, builtinSkillPlugins, usableSkillVersions, } from "../skills/usable.js";
10
9
  import { CONTROL_OR_HIDDEN_RE } from "../text.js";
11
10
  import { AGENT_BUILTIN_NAMES, AGENT_TOOL, BASH_COMPANION_TOOLS, BUILTIN, BUILTIN_TOOL_NAMES, } from "../tools/builtin-names.js";
12
11
  import { canonicalJson } from "../tools/canonical-json.js";
13
- import { AgentDefinitionSchema, builtinDisabled, canonicalDisabledTools, connectorToolDisabled, extensionToolDisabled, SUBAGENT_NAME_RE, SUBAGENT_ROSTER_MAX, } from "./definition.js";
12
+ import { AgentDefinitionSchema, builtinDisabled, canonicalDisabledSkills, canonicalDisabledTools, connectorToolDisabled, extensionToolDisabled, SUBAGENT_NAME_RE, SUBAGENT_ROSTER_MAX, } from "./definition.js";
14
13
  import { INSTRUCTION_FILE_LIMITS, INSTRUCTION_FILE_NAME_RE, joinInstructionFiles, } from "./instruction-files.js";
15
14
  import { sanitizeEndpoint } from "./org-templates.js";
16
15
  import { PROFILE_LIMITS, ProfileInputSchema } from "./profile.js";
@@ -81,6 +80,34 @@ function skillByName(skills, ref, ownPlugin) {
81
80
  const rank = (s) => (ownPlugin !== null && s.plugin === ownPlugin ? 0 : 1);
82
81
  return [...found].sort((a, b) => rank(a) - rank(b) || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0))[0];
83
82
  }
83
+ /**
84
+ * A template's `disallowedSkills` entry as an agent's disabled skill entry (skills default-on spec
85
+ * §6.2): `<plugin>:*` the whole plugin (skills it gains later included), `<plugin>:<name>` that
86
+ * plugin's skill, a bare `<name>` the org's usable skill of that name without a plugin. Null when
87
+ * the agent's save check would refuse it: it names nothing the org has, a builtin plugin
88
+ * (`builtin.plugins`) or a builtin skill (`builtin.ids`, which cannot be turned off).
89
+ */
90
+ function disallowedSkillRef(skills, ref, builtin) {
91
+ const isBuiltin = (s) => builtin.ids.has(s.id) ||
92
+ (s.platformSkillId !== undefined && builtin.ids.has(s.platformSkillId));
93
+ const i = ref.indexOf(":");
94
+ if (i >= 0) {
95
+ const plugin = ref.slice(0, i);
96
+ const name = ref.slice(i + 1);
97
+ if (builtin.plugins.has(plugin))
98
+ return null;
99
+ const ofPlugin = skills.filter((s) => s.plugin === plugin);
100
+ if (name === "*") {
101
+ return ofPlugin.length === 0 ? null : { kind: "plugin", plugin, skills: "all" };
102
+ }
103
+ const s = ofPlugin.find((x) => x.name === name);
104
+ return s === undefined || isBuiltin(s) ? null : { kind: "plugin", plugin, skills: [name] };
105
+ }
106
+ const s = skills
107
+ .filter((x) => x.plugin === null && x.name === ref)
108
+ .sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0))[0];
109
+ return s === undefined || isBuiltin(s) ? null : { kind: "skill", skillId: s.id };
110
+ }
84
111
  /**
85
112
  * Whether the org may see platform connector template `row`: usable (on) or offered in the org,
86
113
  * owned by it, or one it has made connectors from (they stay its own, also after the template is
@@ -315,45 +342,34 @@ export async function planFromTemplate(db, orgId, row, opts) {
315
342
  offExtensionTool(parsed.plugin, parsed.tool);
316
343
  // a connector key is matched against the bound connectors below
317
344
  }
318
- // skills: by name against the org's usable skills (P4-5); every name gets its item
345
+ // skills (skills default-on spec §6.2): the new agent has every skill on in the org. Each name
346
+ // in `skills` (and in an inline sub-agent's, below) is checked against the org's usable skills
347
+ // and listed `carried` or `unavailable` (`not_found`); it narrows nothing.
319
348
  const usable = await usableSkillVersions(db, orgId);
320
- const orgSkills = [];
321
- const select = (id) => {
322
- if (!orgSkills.includes(id))
323
- orgSkills.push(id);
324
- };
325
- const pinned = [];
326
- let pinnedBytes = 0;
327
- const preloaded = new Set(t.skills ?? []);
328
- const skillNames = [...new Set([...(t.skills ?? []), ...(t.selectedSkills ?? [])])];
329
- for (const name of skillNames) {
330
- const s = skillByName(usable, name, row.plugin);
331
- if (!s) {
349
+ for (const name of new Set(t.skills ?? [])) {
350
+ if (skillByName(usable, name, row.plugin))
351
+ item("skill", name, "carried");
352
+ else
332
353
  item("skill", name, "unavailable", "not_found");
354
+ }
355
+ // `disallowedSkills` turns skills off: `<plugin>:*` the whole plugin, `<plugin>:<name>` a
356
+ // plugin's skill, a bare `<name>` the org's skill without a plugin; an entry that names nothing
357
+ // the org has, or a builtin plugin or skill (always on), is dropped
358
+ const disabledSkills = [];
359
+ const builtin = (t.disallowedSkills ?? []).length === 0
360
+ ? { plugins: new Set(), ids: new Set() }
361
+ : {
362
+ plugins: await builtinSkillPlugins(db),
363
+ ids: new Set(await builtinSkillIds(db, orgId)),
364
+ };
365
+ for (const ref of new Set(t.disallowedSkills ?? [])) {
366
+ const off = disallowedSkillRef(usable, ref, builtin);
367
+ if (off === null) {
368
+ dropped.push(`disallowedSkills.${ref}`);
333
369
  continue;
334
370
  }
335
- select(s.id);
336
- if (!preloaded.has(name)) {
337
- item("skill", name, "carried", pinned.includes(s.id) ? "pinned" : undefined);
338
- continue;
339
- }
340
- if (pinned.includes(s.id)) {
341
- item("skill", name, "carried", "pinned");
342
- continue;
343
- }
344
- const bytes = s.manifest.files.find((f) => f.path === SKILL_ENTRY_FILE)?.size ?? 0;
345
- if (!invocationOf(s.manifest).model) {
346
- item("skill", name, "carried", "not_pinned_manual_only");
347
- }
348
- else if (pinned.length >= PINNED_SKILLS_LIMITS.count ||
349
- pinnedBytes + bytes > PINNED_SKILLS_LIMITS.totalBytes) {
350
- item("skill", name, "carried", "not_pinned_too_large");
351
- }
352
- else {
353
- pinned.push(s.id);
354
- pinnedBytes += bytes;
355
- item("skill", name, "carried", "pinned");
356
- }
371
+ disabledSkills.push(off);
372
+ item("skill", ref, "carried", "disallowed");
357
373
  }
358
374
  // connectors (P4-5, P4-6)
359
375
  const pendingConnectors = [];
@@ -530,8 +546,7 @@ export async function planFromTemplate(db, orgId, row, opts) {
530
546
  }
531
547
  if (!skillIds.includes(s.id))
532
548
  skillIds.push(s.id);
533
- if (!orgSkills.includes(s.id)) {
534
- select(s.id);
549
+ if (!items.some((i) => i.kind === "skill" && i.name === name)) {
535
550
  item("skill", name, "carried", "subagent");
536
551
  }
537
552
  }
@@ -617,9 +632,7 @@ export async function planFromTemplate(db, orgId, row, opts) {
617
632
  fallbackParams: {},
618
633
  disabledTools: canonicalDisabledTools(disabled),
619
634
  toolPolicies,
620
- ...((orgSkills.length > 0 || pinned.length > 0) && {
621
- skills: { orgSkills, ...(pinned.length > 0 && { pinned }) },
622
- }),
635
+ disabledSkills: canonicalDisabledSkills(disabledSkills),
623
636
  ...(subagents !== undefined && { subagents }),
624
637
  ...(pendingConnectors.length > 0 && { pendingConnectors }),
625
638
  };
@@ -11,7 +11,7 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
11
11
  // an org row (`agent_templates`, no plugin, a bare `<name>`, version 1) from an agent, the template
12
12
  // detail reads any template usable in the org, and the owner org's admins write new versions.
13
13
  // Deleting and 可共享 are `OwnedItems`' (the P3 owner rules). No in-process state.
14
- import { AGENT_TEMPLATE_LIMITS, extensionToolKey, parseAgentTemplate, parseToolKey, } from "@capybara-hq/plugin-sdk";
14
+ import { AGENT_TEMPLATE_LIMITS, extensionToolKey, isDisallowedSkillRef, parseAgentTemplate, parseToolKey, } from "@capybara-hq/plugin-sdk";
15
15
  import { Inject, Injectable } from "@nestjs/common";
16
16
  import { offeredNamed } from "../catalog/component-state.js";
17
17
  import { ownerOf, ownersOf } from "../catalog/ownership.js";
@@ -22,12 +22,13 @@ import { KernelDb } from "../infra/database/kernel-db.js";
22
22
  import { TransactionHost } from "../infra/database/transaction-host.js";
23
23
  import { Transactional } from "../infra/database/transactional.decorator.js";
24
24
  import { OrgLock } from "../orgs/org-lock.js";
25
- import { skillVersionsById } from "../skills/usable.js";
25
+ import { builtinSkillIds, builtinSkillPlugins, linkedCopyIds, skillIdsOf, skillVersionsById, usableSkillVersions, } from "../skills/usable.js";
26
26
  import { isRemovedBuiltinKey } from "../tools/builtin-names.js";
27
27
  import { canonicalJson } from "../tools/canonical-json.js";
28
28
  import { ToolRegistry } from "../tools/registry.js";
29
29
  import { AgentLookup } from "./agent-lookup.js";
30
- import { loadTemplates, putTemplateVersionIn, readOwnTemplate, templateTypeOf, templateViewOf, usableTemplates, } from "./templates.js";
30
+ import { canonicalDisabledSkills, } from "./definition.js";
31
+ import { loadTemplates, putTemplateVersionIn, readOwnTemplate, storedTemplate, templateTypeOf, templateViewOf, usableTemplates, } from "./templates.js";
31
32
  /**
32
33
  * Roster names no template may take (ruling T4-R15): `general-purpose`, always the roster's first
33
34
  * entry. Claude Code's other built-in types (`Explore`, `Plan`) are capitalized, which the
@@ -127,6 +128,68 @@ export function canonicalEndpoints(template) {
127
128
  }
128
129
  return template.connectors === undefined ? template : { ...template, connectors };
129
130
  }
131
+ /**
132
+ * An agent's `disabledSkills` as a template's `disallowedSkills` (skills default-on spec §6.2): a
133
+ * plugin entry of `"all"` as `<plugin>:*` while the org has skills of that plugin, its names as
134
+ * `<plugin>:<name>` while the org has that skill, a skill entry as the current name of the usable skill it names (`<plugin>:<name>` for a
135
+ * plugin's skill). What no longer resolves, or is past the template's limit, goes to `omitted`.
136
+ */
137
+ async function disallowedSkillsOf(db, orgId, disabled, omitted) {
138
+ const out = [];
139
+ if (disabled.length === 0)
140
+ return out;
141
+ const usable = await usableSkillVersions(db, orgId);
142
+ const linked = await linkedCopyIds(db, orgId, usable.flatMap((r) => (r.origin === "platform" ? [r.id] : [])));
143
+ // builtin plugins and skills are always on: never in an off-list (the agent save refuses them)
144
+ const builtinPlugins = await builtinSkillPlugins(db);
145
+ const builtinIds = new Set(await builtinSkillIds(db, orgId));
146
+ const isBuiltin = (s) => builtinIds.has(s.id) || (s.platformSkillId !== undefined && builtinIds.has(s.platformSkillId));
147
+ const push = (ref, id) => {
148
+ if (out.includes(ref))
149
+ return;
150
+ if (out.length >= AGENT_TEMPLATE_LIMITS.disallowedSkills) {
151
+ omitted.push({ kind: "skill", name: ref, ...(id !== undefined && { id }), reason: "limit" });
152
+ return;
153
+ }
154
+ out.push(ref);
155
+ };
156
+ for (const r of canonicalDisabledSkills(disabled)) {
157
+ if (r.kind === "plugin" && builtinPlugins.has(r.plugin))
158
+ continue;
159
+ if (r.kind === "plugin") {
160
+ if (r.skills === "all") {
161
+ const ref = `${r.plugin}:*`;
162
+ const ok = isDisallowedSkillRef(ref) && usable.some((s) => s.plugin === r.plugin);
163
+ if (ok)
164
+ push(ref);
165
+ else
166
+ omitted.push({ kind: "skill", name: ref, reason: "unavailable" });
167
+ continue;
168
+ }
169
+ for (const name of r.skills) {
170
+ const ref = `${r.plugin}:${name}`;
171
+ const s = usable.find((u) => u.plugin === r.plugin && u.name === name);
172
+ const ok = isDisallowedSkillRef(ref) && s !== undefined && !isBuiltin(s);
173
+ if (ok)
174
+ push(ref);
175
+ else
176
+ omitted.push({ kind: "skill", name: ref, reason: "unavailable" });
177
+ }
178
+ continue;
179
+ }
180
+ const s = usable.find((u) => skillIdsOf(u, linked).includes(r.skillId));
181
+ const ref = s === undefined || isBuiltin(s) || (s.plugin !== null && builtinPlugins.has(s.plugin))
182
+ ? null
183
+ : s.plugin === null
184
+ ? s.name
185
+ : `${s.plugin}:${s.name}`;
186
+ if (ref !== null && isDisallowedSkillRef(ref))
187
+ push(ref, r.skillId);
188
+ else
189
+ omitted.push({ kind: "skill", name: r.skillId, id: r.skillId, reason: "unavailable" });
190
+ }
191
+ return out;
192
+ }
130
193
  /**
131
194
  * The template an agent is saved as (rulings P4-10, P4-11), with what was left out. Pure reads of
132
195
  * the org's state through `db`.
@@ -229,50 +292,44 @@ extensionTools = []) {
229
292
  // capped with the entry's tools when it is pushed
230
293
  return { policies: Object.fromEntries(list.map((p) => [p.tool, p.policy])) };
231
294
  };
232
- // Skills: ids -> names (pinned -> `skills`, the other selected ones -> `selectedSkills`)
233
- const pinned = def.skills?.pinned ?? [];
234
- const selected = def.skills?.orgSkills ?? [];
295
+ // Skills (skills default-on spec §6.2): the agent has every skill on in the org; its
296
+ // `disabledSkills` become `disallowedSkills` (a whole plugin as `<plugin>:*`, a plugin's skill
297
+ // as `<plugin>:<name>`, a skill entry by its current name); entries that no longer resolve to a
298
+ // usable skill are listed. The agent's own skills are not in the org library: listed.
299
+ const disallowedSkills = await disallowedSkillsOf(db, orgId, def.disabledSkills ?? [], omitted);
235
300
  const inlineSkillIds = (def.subagents?.inline ?? []).flatMap((t) => t.skills ?? []);
236
- const byId = await skillVersionsById(db, orgId, [...pinned, ...selected, ...inlineSkillIds]);
237
- const agentSkillNames = new Map((await db
301
+ const byId = await skillVersionsById(db, orgId, inlineSkillIds);
302
+ const ownSkills = await db
238
303
  .selectFrom("skills")
239
- .select(["id", "name"])
304
+ .select(["id", "name", "agent_id", "deleted_at"])
240
305
  .where("org_id", "=", orgId)
241
306
  .where("scope", "=", "agent")
242
- .where("id", "in", ["", ...pinned, ...selected, ...inlineSkillIds])
243
- .execute()).map((r) => [r.id, r.name]));
307
+ .where((eb) => eb.or([eb("agent_id", "=", agent.id), eb("id", "in", ["", ...inlineSkillIds])]))
308
+ .orderBy("name")
309
+ .orderBy("id")
310
+ .execute();
311
+ const agentSkillNames = new Map(ownSkills.map((r) => [r.id, r.name]));
244
312
  const reportedSkills = new Set();
245
- const skillName = (id, report) => {
313
+ const reportSkill = (id) => {
314
+ if (reportedSkills.has(id))
315
+ return;
316
+ reportedSkills.add(id);
317
+ const own = agentSkillNames.get(id) ?? byId.get(id)?.name;
318
+ omitted.push(own !== undefined
319
+ ? { kind: "skill", name: own, id, reason: "agent_skill" }
320
+ : { kind: "skill", name: id, id, reason: "unavailable" });
321
+ };
322
+ const skillName = (id) => {
246
323
  const s = byId.get(id);
247
324
  if (s && s.scope === "org")
248
325
  return s.name;
249
- if (report && !reportedSkills.has(id)) {
250
- reportedSkills.add(id);
251
- const own = agentSkillNames.get(id) ?? s?.name;
252
- omitted.push(own !== undefined
253
- ? { kind: "skill", name: own, id, reason: "agent_skill" }
254
- : { kind: "skill", name: id, id, reason: "unavailable" });
255
- }
326
+ reportSkill(id);
256
327
  return null;
257
328
  };
258
- const skills = [];
259
- for (const id of pinned) {
260
- const n = skillName(id, true);
261
- if (n !== null && !skills.includes(n) && skills.length < L.skills)
262
- skills.push(n);
263
- }
264
- const selectedSkills = [];
265
- for (const id of selected) {
266
- if (pinned.includes(id))
267
- continue;
268
- const n = skillName(id, true);
269
- if (n === null || skills.includes(n) || selectedSkills.includes(n))
270
- continue;
271
- if (selectedSkills.length >= L.selectedSkills) {
272
- omitted.push({ kind: "skill", name: n, id, reason: "limit" });
273
- continue;
274
- }
275
- selectedSkills.push(n);
329
+ // the agent's own live skills (on for it whatever `disabledSkills` say)
330
+ for (const r of ownSkills) {
331
+ if (r.agent_id === agent.id && r.deleted_at === null)
332
+ reportSkill(r.id);
276
333
  }
277
334
  // Connectors (P4-11), then the agent's pending connectors as they are
278
335
  const connectors = [];
@@ -468,7 +525,7 @@ extensionTools = []) {
468
525
  }
469
526
  const inlineSkills = [];
470
527
  for (const id of t.skills ?? []) {
471
- const n = skillName(id, true);
528
+ const n = skillName(id);
472
529
  if (n === null || inlineSkills.includes(n))
473
530
  continue;
474
531
  if (inlineSkills.length >= L.skills) {
@@ -512,8 +569,7 @@ extensionTools = []) {
512
569
  model: "standard",
513
570
  modelRef: `${def.model.provider}/${def.model.model}`,
514
571
  ...(effort !== undefined && { effort }),
515
- ...(skills.length > 0 && { skills }),
516
- ...(selectedSkills.length > 0 && { selectedSkills }),
572
+ ...(disallowedSkills.length > 0 && { disallowedSkills }),
517
573
  ...(connectors.length > 0 && { connectors }),
518
574
  ...(subagents !== undefined && { subagents }),
519
575
  ...(quickActions.length > 0 && { quickActions }),
@@ -806,7 +862,7 @@ let OrgAgentTemplates = class OrgAgentTemplates {
806
862
  name: own.name,
807
863
  plugin: null,
808
864
  version: row.current_version,
809
- template: row.definition,
865
+ template: storedTemplate(row.definition),
810
866
  sourceFormat: row.source_format,
811
867
  catalogItemId: null,
812
868
  catalogVersion: null,
@@ -61,6 +61,12 @@ export interface TemplateRow {
61
61
  }
62
62
  /** The roster type of a template: `<plugin>:<name>`, the bare `<name>` without a plugin. */
63
63
  export declare const templateTypeOf: (plugin: string | null, name: string) => string;
64
+ /**
65
+ * A stored template definition as the current format reads it: `selectedSkills` (removed with
66
+ * skills on by default, spec §6.2) is dropped. Migration 0116 removed it from org-made versions;
67
+ * versions written from a plugin package keep it as the package wrote it, so every read drops it.
68
+ */
69
+ export declare function storedTemplate(definition: unknown): AgentTemplate;
64
70
  /**
65
71
  * The ids among `ids` of the platform templates on in the org (see {@link platformOn}), whatever
66
72
  * the org's own templates are.
@@ -53,6 +53,20 @@ async function ownedPlatformIds(db, orgId, platformIds) {
53
53
  .execute();
54
54
  return new Set(rows.map((r) => r.id));
55
55
  }
56
+ /**
57
+ * A stored template definition as the current format reads it: `selectedSkills` (removed with
58
+ * skills on by default, spec §6.2) is dropped. Migration 0116 removed it from org-made versions;
59
+ * versions written from a plugin package keep it as the package wrote it, so every read drops it.
60
+ */
61
+ export function storedTemplate(definition) {
62
+ if (definition === null || typeof definition !== "object" || Array.isArray(definition)) {
63
+ return definition;
64
+ }
65
+ if (!("selectedSkills" in definition))
66
+ return definition;
67
+ const { selectedSkills: _s, ...rest } = definition;
68
+ return rest;
69
+ }
56
70
  /**
57
71
  * Stored rows as {@link TemplateRow}s; tool keys read as current keys (tool naming spec §8.3): old
58
72
  * builtin names always, rc `<x>__<tool>` keys with the org's legacy scope, read once when needed.
@@ -63,7 +77,7 @@ async function normalized(db, orgId, rows) {
63
77
  : undefined;
64
78
  return rows.map(({ definition, ...r }) => ({
65
79
  ...r,
66
- template: normalizeToolKeys(definition, scope),
80
+ template: storedTemplate(normalizeToolKeys(definition, scope)),
67
81
  }));
68
82
  }
69
83
  const orgRaw = (r) => ({
@@ -28,14 +28,52 @@ export declare const taskSidPath: (id: string) => string;
28
28
  export declare function newTaskId(): string;
29
29
  /** A task id ({@link newTaskId}). */
30
30
  export declare const TASK_ID_RE: RegExp;
31
+ /**
32
+ * A shell fragment that sets how a task's processes are found and signalled by its sid, defining
33
+ * `tk_alive <sid>` (status 0 while the task has a process) and `tk_kill <SIGNAL> <sid>` (0: sent,
34
+ * 1: no process left, anything else: a failure), and `pg` to the mode:
35
+ * - `-s`, the session, where `pgrep` has `-s` (Linux procps and busybox, the sandbox-fake shims:
36
+ * `pgrep -s 0`, the own session, exits 0 or 1): `pgrep -s` and `pkill -s`;
37
+ * - `-g`, the process group, elsewhere (macOS). Its `pgrep` has no `-s`, and inside the OS sandbox
38
+ * (Seatbelt) a command can neither list processes nor signal those of another command. So the
39
+ * task's process group (its id is the sid) is watched by its launcher ({@link GROUP_LAUNCHER}),
40
+ * which signals it on request: `tk_kill` appends the signal to `.capybara/tasks/g<sid>.ctl`, and
41
+ * `tk_alive` asks `kill(0, -sid)`, where "not permitted" (another command's group) still means
42
+ * the group exists. A group signal is one system call, so it misses no process forked meanwhile.
43
+ * Relative to the workspace root. Every script that asks for or signals a task's processes starts
44
+ * with it; the launch picks its launcher with the same test.
45
+ */
46
+ export declare const SESSION_MODE_SCRIPT: string;
47
+ /**
48
+ * The process group launcher (macOS: {@link SESSION_MODE_SCRIPT}'s `-g`), a perl program run as
49
+ * `perl -e <this> <seconds> <task path>` from the workspace root. It makes a session of its own
50
+ * (leaving the launch exec's process group), starts `bash <task>.sh` as the leader of a new process
51
+ * group (the task; its id, the sid, goes to `<task>.sid` through a rename) and watches it every
52
+ * 0.1 s, outside the group:
53
+ * - the command exits: its status as the shell gives it (`128 + signal` when killed) goes to
54
+ * `<task>.exit`, and what it left running in the group is SIGKILLed (the task ends when its
55
+ * command exits, as the poll does on Linux);
56
+ * - the time limit passes: 137 goes to `<task>.exit`, then the group is SIGKILLed (as
57
+ * `timeout -s KILL`);
58
+ * - a signal is requested in `g<sid>.ctl` ({@link SESSION_MODE_SCRIPT}'s `tk_kill`, read through
59
+ * a rename): the group gets it, and no exit file is written any more (a stop, as on Linux).
60
+ * It exits once the group is empty. Contains no single quote (it goes between single quotes).
61
+ */
62
+ export declare const GROUP_LAUNCHER: string;
31
63
  /**
32
64
  * The shell of the launch exec: writes `command` (base64 in the script, so nothing is quoted by
33
65
  * hand) to `.capybara/tasks/<id>.sh` and starts it with bash (the shell a foreground `Bash` call
34
- * runs its command in) in a session of its own under `timeout -s KILL <seconds>`, with its exit
35
- * status written to `<id>.exit` and its output to `<id>.out`; writes the session id (`$!`, the pid
36
- * `setsid` started) to `<id>.sid` and prints it. An environment without one of the needed programs fails with a readable error on
37
- * stderr and exit code 127, before anything starts. `id` must be a task id and `seconds` a
38
- * positive integer (both go into the script unquoted).
66
+ * runs its command in), detached from the exec under a hard time limit of `seconds` that SIGKILLs
67
+ * every process of the task, with its exit status written to `<id>.exit` and its output to
68
+ * `<id>.out`; the task's sid goes to `<id>.sid` and is printed. Where `pgrep` has `-s`
69
+ * ({@link SESSION_MODE_SCRIPT}) the command runs in a session of its own under
70
+ * `setsid nohup sh -c 'timeout -s KILL <seconds> …'` and the sid is `$!`, the pid `setsid` started;
71
+ * elsewhere (macOS: no `setsid`, no `timeout`, no `pgrep -s`) {@link GROUP_LAUNCHER} runs it in a
72
+ * process group of its own, watched by the launcher, and writes the sid (the group id) itself,
73
+ * which the launch waits for (up to 10 s). An
74
+ * environment without one of the needed programs fails with a readable error on stderr and exit
75
+ * code 127, before anything starts. `id` must be a task id and `seconds` a positive integer (both
76
+ * go into the script unquoted).
39
77
  */
40
78
  export declare function launchScript(id: string, command: string, seconds: number): string;
41
79
  /** The model-visible result of a launch (spec §2.1); `workspace`: the instance's workspace root. */