@capybara-hq/kernel 0.1.0-rc.20 → 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 (247) hide show
  1. package/bootstrap.json +10 -1
  2. package/dist/agents/admin-agents.d.ts +16 -3
  3. package/dist/agents/admin-agents.js +65 -7
  4. package/dist/agents/agents.schemas.d.ts +24 -6
  5. package/dist/agents/agents.service.d.ts +4 -3
  6. package/dist/agents/agents.service.js +39 -26
  7. package/dist/agents/definition.d.ts +44 -6
  8. package/dist/agents/definition.js +86 -18
  9. package/dist/agents/from-template.d.ts +6 -3
  10. package/dist/agents/from-template.js +57 -44
  11. package/dist/agents/org-templates.js +97 -41
  12. package/dist/agents/templates.d.ts +6 -0
  13. package/dist/agents/templates.js +15 -1
  14. package/dist/catalog/admin-catalog.controller.d.ts +2 -4
  15. package/dist/catalog/admin-catalog.controller.js +2 -6
  16. package/dist/catalog/catalog.module.js +3 -0
  17. package/dist/catalog/claude/report.d.ts +9 -2
  18. package/dist/catalog/claude/report.js +1 -0
  19. package/dist/catalog/claude/translate.js +1 -1
  20. package/dist/catalog/component-promotion.js +1 -2
  21. package/dist/catalog/component-state.d.ts +13 -1
  22. package/dist/catalog/component-state.js +14 -5
  23. package/dist/catalog/marketplaces.service.d.ts +1 -1
  24. package/dist/catalog/marketplaces.service.js +2 -1
  25. package/dist/catalog/org-catalog.controller.d.ts +13 -3
  26. package/dist/catalog/org-catalog.controller.js +26 -4
  27. package/dist/catalog/org-catalog.schemas.d.ts +13 -0
  28. package/dist/catalog/org-catalog.schemas.js +34 -0
  29. package/dist/catalog/org-plugin-uploads.controller.js +0 -1
  30. package/dist/catalog/org-plugin-uploads.d.ts +0 -1
  31. package/dist/catalog/org-plugin-uploads.js +0 -3
  32. package/dist/catalog/org-plugin-uploads.schemas.d.ts +0 -1
  33. package/dist/catalog/org-plugin-uploads.schemas.js +0 -2
  34. package/dist/catalog/platform-components.d.ts +0 -1
  35. package/dist/catalog/platform-components.js +3 -8
  36. package/dist/catalog/plugin-import.d.ts +0 -8
  37. package/dist/catalog/plugin-import.js +4 -35
  38. package/dist/catalog/plugin-install.d.ts +2 -2
  39. package/dist/catalog/plugin-install.js +2 -2
  40. package/dist/catalog/plugin-package.d.ts +7 -0
  41. package/dist/catalog/plugin-package.js +27 -1
  42. package/dist/catalog/plugin-upgrade.d.ts +5 -6
  43. package/dist/catalog/plugin-upgrade.js +5 -23
  44. package/dist/catalog/requests.service.d.ts +66 -10
  45. package/dist/catalog/requests.service.js +196 -15
  46. package/dist/channels/actions.d.ts +2 -0
  47. package/dist/channels/actions.js +19 -4
  48. package/dist/channels/cards.d.ts +16 -2
  49. package/dist/channels/cards.js +40 -7
  50. package/dist/channels/channels.module.js +3 -0
  51. package/dist/channels/outbound.d.ts +22 -1
  52. package/dist/channels/outbound.js +116 -7
  53. package/dist/channels/phrases.d.ts +25 -0
  54. package/dist/channels/phrases.js +19 -0
  55. package/dist/channels/threads.d.ts +12 -0
  56. package/dist/channels/threads.js +34 -0
  57. package/dist/config/schema.d.ts +23 -1
  58. package/dist/config/schema.js +105 -5
  59. package/dist/conversations/conversations.service.d.ts +42 -9
  60. package/dist/conversations/conversations.service.js +57 -25
  61. package/dist/conversations/events.d.ts +106 -11
  62. package/dist/conversations/events.js +121 -18
  63. package/dist/conversations/message-context.d.ts +6 -3
  64. package/dist/conversations/message-context.js +14 -10
  65. package/dist/conversations/queue.d.ts +15 -2
  66. package/dist/conversations/queue.js +22 -3
  67. package/dist/conversations/queue.service.d.ts +12 -6
  68. package/dist/conversations/queue.service.js +36 -13
  69. package/dist/db/kernel-schema.d.ts +105 -3
  70. package/dist/devices/conversation-devices.d.ts +8 -1
  71. package/dist/devices/conversation-devices.js +8 -0
  72. package/dist/devices/devices.controller.d.ts +21 -4
  73. package/dist/devices/devices.controller.js +46 -8
  74. package/dist/devices/devices.schemas.d.ts +9 -1
  75. package/dist/devices/devices.schemas.js +11 -3
  76. package/dist/devices/devices.service.d.ts +176 -4
  77. package/dist/devices/devices.service.js +341 -6
  78. package/dist/devices/dto.d.ts +56 -3
  79. package/dist/devices/dto.js +51 -1
  80. package/dist/devices/public-suffix.d.ts +6 -0
  81. package/dist/devices/public-suffix.js +79 -0
  82. package/dist/errors.d.ts +46 -5
  83. package/dist/errors.js +46 -5
  84. package/dist/evals/reconstruct.d.ts +18 -13
  85. package/dist/evals/reconstruct.js +50 -36
  86. package/dist/evals/recorded-tools.d.ts +7 -7
  87. package/dist/evals/recorded-tools.js +4 -3
  88. package/dist/evals/trajectory.d.ts +6 -1
  89. package/dist/evals/trajectory.js +7 -0
  90. package/dist/extensions/bootstrap.d.ts +49 -10
  91. package/dist/extensions/bootstrap.js +104 -27
  92. package/dist/extensions/package.js +2 -2
  93. package/dist/http/api.module.js +5 -0
  94. package/dist/inbox/inbox.module.js +2 -1
  95. package/dist/inbox/inbox.service.d.ts +19 -4
  96. package/dist/inbox/inbox.service.js +14 -3
  97. package/dist/infra/config/kernel-config.service.js +11 -0
  98. package/dist/infra/events/kernel-events.d.ts +2 -1
  99. package/dist/lifecycle/kernel-lifecycle.d.ts +5 -1
  100. package/dist/lifecycle/kernel-lifecycle.js +16 -4
  101. package/dist/local-devices/device-policy.service.d.ts +65 -0
  102. package/dist/local-devices/device-policy.service.js +217 -0
  103. package/dist/local-devices/local-device-gateway.js +4 -1
  104. package/dist/local-devices/local-device-provider.d.ts +40 -3
  105. package/dist/local-devices/local-device-provider.js +109 -7
  106. package/dist/local-devices/local-devices.module.d.ts +2 -1
  107. package/dist/local-devices/local-devices.module.js +19 -3
  108. package/dist/local-devices/machines.d.ts +14 -3
  109. package/dist/local-devices/machines.js +41 -3
  110. package/dist/local-devices/network-grants.d.ts +104 -0
  111. package/dist/local-devices/network-grants.js +288 -0
  112. package/dist/local-devices/policy-summary.d.ts +20 -0
  113. package/dist/local-devices/policy-summary.js +63 -0
  114. package/dist/orgs/orgs.module.js +4 -10
  115. package/dist/orgs/roles.d.ts +1 -1
  116. package/dist/orgs/roles.js +4 -0
  117. package/dist/orgs/settings.controller.js +2 -1
  118. package/dist/orgs/settings.service.d.ts +164 -12
  119. package/dist/orgs/settings.service.js +212 -24
  120. package/dist/platform/exclusions.js +11 -1
  121. package/dist/platform/operation.js +4 -0
  122. package/dist/platform/ops/admin-items.ops.js +1 -1
  123. package/dist/platform/ops/agent-skills.ops.js +2 -2
  124. package/dist/platform/ops/agent-templates.ops.js +2 -2
  125. package/dist/platform/ops/agents.ops.js +11 -15
  126. package/dist/platform/ops/devices.ops.js +3 -1
  127. package/dist/platform/ops/inbox.ops.js +19 -0
  128. package/dist/platform/ops/org-settings.ops.js +14 -5
  129. package/dist/platform/ops/platform.ops.d.ts +9 -1
  130. package/dist/platform/ops/platform.ops.js +34 -12
  131. package/dist/platform/ops/requests.ops.js +3 -2
  132. package/dist/platform/ops/schedules.ops.d.ts +21 -0
  133. package/dist/platform/ops/schedules.ops.js +470 -0
  134. package/dist/platform/ops/skills.ops.js +3 -3
  135. package/dist/platform/platform-ops.module.d.ts +2 -1
  136. package/dist/platform/platform-ops.module.js +5 -0
  137. package/dist/platform/route-schemas.js +3 -0
  138. package/dist/roles/worker.module.js +3 -0
  139. package/dist/runtime/context/build.js +13 -4
  140. package/dist/runtime/context/compaction.js +6 -3
  141. package/dist/runtime/context/device-paths.d.ts +43 -21
  142. package/dist/runtime/context/device-paths.js +104 -21
  143. package/dist/runtime/context/run-context.d.ts +9 -0
  144. package/dist/runtime/context/run-context.js +8 -0
  145. package/dist/runtime/context/skills.d.ts +14 -1
  146. package/dist/runtime/context/skills.js +36 -5
  147. package/dist/runtime/maintenance.d.ts +4 -2
  148. package/dist/runtime/maintenance.js +16 -5
  149. package/dist/runtime/run-finish.d.ts +6 -1
  150. package/dist/runtime/run-finish.js +16 -2
  151. package/dist/runtime/run-queue.js +21 -2
  152. package/dist/runtime/run-worker.js +14 -33
  153. package/dist/runtime/runtime.module.js +3 -0
  154. package/dist/runtime/subagents/definition.d.ts +32 -14
  155. package/dist/runtime/subagents/definition.js +106 -109
  156. package/dist/runtime/subagents/dispatch.js +4 -3
  157. package/dist/runtime/subagents/preamble.d.ts +2 -1
  158. package/dist/runtime/subagents/preamble.js +2 -1
  159. package/dist/runtime/waits.js +14 -4
  160. package/dist/sandbox/sandbox.service.d.ts +120 -10
  161. package/dist/sandbox/sandbox.service.js +335 -28
  162. package/dist/sandbox/tiers.d.ts +86 -0
  163. package/dist/sandbox/tiers.js +96 -0
  164. package/dist/schedules/effective-settings.d.ts +16 -0
  165. package/dist/schedules/effective-settings.js +17 -0
  166. package/dist/schedules/errors.d.ts +8 -0
  167. package/dist/schedules/errors.js +8 -0
  168. package/dist/schedules/fire.d.ts +118 -0
  169. package/dist/schedules/fire.js +625 -0
  170. package/dist/schedules/grant-rules.d.ts +94 -0
  171. package/dist/schedules/grant-rules.js +126 -0
  172. package/dist/schedules/job-ids.d.ts +32 -0
  173. package/dist/schedules/job-ids.js +27 -0
  174. package/dist/schedules/pause.d.ts +9 -0
  175. package/dist/schedules/pause.js +33 -0
  176. package/dist/schedules/run-tracker.d.ts +81 -0
  177. package/dist/schedules/run-tracker.js +333 -0
  178. package/dist/schedules/scan.job.d.ts +64 -0
  179. package/dist/schedules/scan.job.js +303 -0
  180. package/dist/schedules/schedule-math.d.ts +31 -0
  181. package/dist/schedules/schedule-math.js +223 -0
  182. package/dist/schedules/scheduled-tasks.service.d.ts +351 -0
  183. package/dist/schedules/scheduled-tasks.service.js +1193 -0
  184. package/dist/schedules/schedules.controller.d.ts +68 -0
  185. package/dist/schedules/schedules.controller.js +242 -0
  186. package/dist/schedules/schedules.module.d.ts +16 -0
  187. package/dist/schedules/schedules.module.js +65 -0
  188. package/dist/schedules/schedules.schemas.d.ts +316 -0
  189. package/dist/schedules/schedules.schemas.js +186 -0
  190. package/dist/schedules/store.d.ts +81 -0
  191. package/dist/schedules/store.js +390 -0
  192. package/dist/schedules/store.module.d.ts +6 -0
  193. package/dist/schedules/store.module.js +21 -0
  194. package/dist/schedules/types.d.ts +108 -0
  195. package/dist/schedules/types.js +1 -0
  196. package/dist/schedules/unattended.d.ts +117 -0
  197. package/dist/schedules/unattended.js +228 -0
  198. package/dist/setup/oauth-token-probe.js +8 -4
  199. package/dist/skills/agent-skills.service.d.ts +4 -8
  200. package/dist/skills/agent-skills.service.js +18 -53
  201. package/dist/skills/catalog.d.ts +65 -62
  202. package/dist/skills/catalog.js +109 -206
  203. package/dist/skills/conversation-catalog.d.ts +3 -3
  204. package/dist/skills/conversation-catalog.js +5 -12
  205. package/dist/skills/invoke.d.ts +2 -2
  206. package/dist/skills/skills.controller.d.ts +3 -1
  207. package/dist/skills/skills.service.d.ts +38 -37
  208. package/dist/skills/skills.service.js +168 -107
  209. package/dist/skills/usable.d.ts +57 -8
  210. package/dist/skills/usable.js +172 -38
  211. package/dist/tools/approvals.controller.d.ts +4 -2
  212. package/dist/tools/approvals.controller.js +17 -2
  213. package/dist/tools/approvals.schemas.d.ts +1 -0
  214. package/dist/tools/approvals.schemas.js +3 -1
  215. package/dist/tools/approvals.service.d.ts +182 -7
  216. package/dist/tools/approvals.service.js +597 -13
  217. package/dist/tools/builtin-names.d.ts +25 -4
  218. package/dist/tools/builtin-names.js +45 -4
  219. package/dist/tools/builtins/builtin-tools.d.ts +8 -0
  220. package/dist/tools/builtins/builtin-tools.js +23 -2
  221. package/dist/tools/builtins/sandbox/bash.js +16 -7
  222. package/dist/tools/builtins/sandbox/common.d.ts +1 -1
  223. package/dist/tools/builtins/sandbox/common.js +33 -10
  224. package/dist/tools/builtins/sandbox/edit.js +7 -3
  225. package/dist/tools/builtins/sandbox/file-state.d.ts +31 -1
  226. package/dist/tools/builtins/sandbox/file-state.js +33 -3
  227. package/dist/tools/builtins/sandbox/write.js +8 -4
  228. package/dist/tools/builtins/schedules.d.ts +107 -0
  229. package/dist/tools/builtins/schedules.js +789 -0
  230. package/dist/tools/elicitation.d.ts +2 -0
  231. package/dist/tools/elicitation.js +1 -1
  232. package/dist/tools/fixed-ask.d.ts +7 -0
  233. package/dist/tools/fixed-ask.js +9 -0
  234. package/dist/tools/network-asks.d.ts +109 -0
  235. package/dist/tools/network-asks.js +221 -0
  236. package/dist/tools/pipeline.d.ts +53 -6
  237. package/dist/tools/pipeline.js +202 -13
  238. package/dist/tools/policy.d.ts +21 -8
  239. package/dist/tools/policy.js +63 -9
  240. package/dist/tools/registry.d.ts +10 -1
  241. package/dist/tools/tools.module.js +12 -0
  242. package/package.json +2 -2
  243. package/src/migrations/0113_scheduled_tasks.sql +108 -0
  244. package/src/migrations/0114_network_asks.sql +23 -0
  245. package/src/migrations/0115_network_requests.sql +28 -0
  246. package/src/migrations/0116_skills_default_on.sql +37 -0
  247. package/src/migrations/0117_sandbox_tiers.sql +11 -0
@@ -7,8 +7,8 @@ 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 { validateAllowHost } from "@capybara-hq/plugin-sdk";
11
- import { Inject, Injectable } from "@nestjs/common";
10
+ import { SANDBOX_TIERS, tierAtMost, validateAllowHost, } from "@capybara-hq/plugin-sdk";
11
+ import { Inject, Injectable, Module, Optional } from "@nestjs/common";
12
12
  import { z } from "zod";
13
13
  import { ApiError } from "../errors.js";
14
14
  import { audit } from "../identity/audit.js";
@@ -17,10 +17,12 @@ import { KernelConfigService } from "../infra/config/kernel-config.service.js";
17
17
  import { KernelDb } from "../infra/database/kernel-db.js";
18
18
  import { TransactionHost } from "../infra/database/transaction-host.js";
19
19
  import { Transactional } from "../infra/database/transactional.decorator.js";
20
+ import { KERNEL_LOGGER } from "../infra/tokens.js";
21
+ import { effectiveSandboxPolicy } from "../sandbox/tiers.js";
20
22
  import { assertThemeOnIn } from "../themes/theme-state.js";
21
23
  import { IanaTimeZone } from "../time-zone.js";
22
24
  import { mergeArrangement, OrgUiArrangementPatchSchema, UiArrangementSchema, } from "../ui/arrangement.js";
23
- import { OrgLock } from "./org-lock.js";
25
+ import { OrgLock, OrgLockModule } from "./org-lock.js";
24
26
  /**
25
27
  * 组织设置(`capybara.orgs.settings` jsonb)。读取宽松:缺失或非法的字段按默认值,未知键保留不动
26
28
  * (更新时与存量合并),新版本加字段不需要迁移。
@@ -30,7 +32,10 @@ import { OrgLock } from "./org-lock.js";
30
32
  * `Read` / `Glob` / `Grep` 外都需要审批(执行时实时读取)。
31
33
  * - `wakeOnBackgroundTask`:组织对 `runtime.wakeOnBackgroundTask` 的覆盖(缺省跟随部署),见 {@link effectiveWakeOnBackgroundTask}。
32
34
  * - `sandboxEgress`:组织的沙箱出站(Ruling M19 / D6),缺省跟随部署的 `sandbox.limits.egress` + `allowHosts`。
33
- * 见 {@link effectiveSandboxEgress}。存量值非法时按 `none` 读(宁可断网,不悄悄放宽到部署默认)。
35
+ * See `baseSandboxEgress` (sandbox/tiers.ts). 存量值非法时按 `none` 读(宁可断网,不悄悄放宽到部署默认)。
36
+ * - `sandboxTier`: the org's sandbox tier ceiling, default and unsandboxed switch (sandbox dev tiers
37
+ * spec §4.1), each absent = follow the platform (`sandbox.tiers`); never above the platform. An
38
+ * invalid stored value reads as `{ max: "strict" }` (narrower, never wider).
34
39
  * - `platformTool`: the `Platform` builtin for the whole org (Platform tool spec §3): `on`,
35
40
  * `read-only` (writes listed as unavailable) or `off` (default `on`).
36
41
  * - `timeZone`: the org's default time zone (IANA; absent = UTC) for senders without one of their
@@ -65,6 +70,19 @@ export const SandboxEgressSettingSchema = z
65
70
  .optional(),
66
71
  })
67
72
  .strict();
73
+ const TierSchema = z.enum(["strict", "dev", "open"]);
74
+ /**
75
+ * The org's sandbox tier setting (sandbox dev tiers spec §4.1): `max` the highest tier its devices
76
+ * may reach, `default` the tier of a device that chose none, `allowUnsandboxed` false forbids
77
+ * unsandboxed local devices. Each absent = follow the platform's `sandbox.tiers`.
78
+ */
79
+ export const SandboxTierSettingSchema = z
80
+ .object({
81
+ max: TierSchema.optional(),
82
+ default: TierSchema.optional(),
83
+ allowUnsandboxed: z.boolean().optional(),
84
+ })
85
+ .strict();
68
86
  /**
69
87
  * The org's theme setting (themes spec §7): `id` a theme on in the org (a `platform_themes` id),
70
88
  * null = follow the platform's default; `personal: "locked"` hides the personal picker in the org
@@ -78,12 +96,56 @@ export const OrgThemeSettingSchema = z
78
96
  .strict();
79
97
  /** The theme setting of an org that has none: follow the platform, personal picks allowed. */
80
98
  export const DEFAULT_ORG_THEME = { id: null, personal: "allowed" };
99
+ const posInt = (max, def) => z.number().int().min(1).max(max).default(def).catch(def);
100
+ /** A stored `forbiddenTools` entry worth keeping: a tool key of the length the PATCH accepts. */
101
+ const isForbiddenToolEntry = (v) => typeof v === "string" && v.length >= 1 && v.length <= 200;
102
+ /**
103
+ * Org settings of scheduled tasks (spec §9); every field reads leniently (invalid = default,
104
+ * except `forbiddenTools`, which keeps its valid entries).
105
+ */
106
+ export const ScheduledTasksSettingsSchema = z.object({
107
+ enabled: z.boolean().default(true).catch(true),
108
+ minIntervalMinutes: posInt(1_000_000, 60),
109
+ maxTasksPerMember: posInt(1_000_000, 20),
110
+ maxTasksPerOrg: posInt(1_000_000, 200),
111
+ maxRunsPerHour: posInt(1_000_000, 60),
112
+ defaultExpiryDays: z.number().int().min(1).max(36_500).nullable().default(null).catch(null),
113
+ catchUpWindowMinutes: posInt(1_000_000, 60),
114
+ unattendedAsk: z.enum(["fail", "wait"]).default("fail").catch("fail"),
115
+ pauseAfterFailures: posInt(1000, 5),
116
+ // fail-safe: an invalid entry is dropped, the valid ones stay forbidden (never reset to []);
117
+ // past the PATCH's 500 the list is truncated
118
+ forbiddenTools: z
119
+ .preprocess((v) => (Array.isArray(v) ? v.filter(isForbiddenToolEntry).slice(0, 500) : v), z.array(z.string()).max(500).default([]))
120
+ .catch([]),
121
+ });
122
+ /**
123
+ * The PATCH form: a partial merge into the group (fields left out keep their stored value; see
124
+ * `OrgSettingsService.update`). A field given replaces that field whole, `forbiddenTools` too.
125
+ */
126
+ export const ScheduledTasksPatchSchema = z
127
+ .object({
128
+ enabled: z.boolean(),
129
+ minIntervalMinutes: z.number().int().min(1).max(1_000_000),
130
+ maxTasksPerMember: z.number().int().min(1).max(1_000_000),
131
+ maxTasksPerOrg: z.number().int().min(1).max(1_000_000),
132
+ maxRunsPerHour: z.number().int().min(1).max(1_000_000),
133
+ defaultExpiryDays: z.number().int().min(1).max(36_500).nullable(),
134
+ catchUpWindowMinutes: z.number().int().min(1).max(1_000_000),
135
+ unattendedAsk: z.enum(["fail", "wait"]),
136
+ pauseAfterFailures: z.number().int().min(1).max(1000),
137
+ forbiddenTools: z.array(z.string().min(1).max(200)).max(500),
138
+ })
139
+ .partial()
140
+ .strict();
81
141
  export const OrgSettingsSchema = z.object({
82
142
  toolSearch: z.enum(["on", "off"]).default("on").catch("on"),
83
143
  locale: z.enum(["zh", "en"]).default("en").catch("en"),
84
144
  sandboxToolPolicy: z.enum(["allow", "ask"]).default("allow").catch("allow"),
85
145
  platformTool: z.enum(["on", "read-only", "off"]).default("on").catch("on"),
86
146
  sandboxEgress: SandboxEgressSettingSchema.optional().catch({ mode: "none" }),
147
+ /** The org's sandbox tiers (sandbox dev tiers spec §4.1); absent follows the platform. */
148
+ sandboxTier: SandboxTierSettingSchema.optional().catch({ max: "strict" }),
87
149
  /** Overrides `runtime.wakeOnBackgroundTask`; absent follows the deployment. */
88
150
  wakeOnBackgroundTask: z.boolean().optional().catch(undefined),
89
151
  /** The org's default time zone (run context spec §3.3); absent = UTC. */
@@ -92,6 +154,8 @@ export const OrgSettingsSchema = z.object({
92
154
  theme: OrgThemeSettingSchema.optional().catch(undefined),
93
155
  /** Hidden and ordered extension contributions per slot; absent = follow the platform. */
94
156
  uiArrangement: UiArrangementSchema.optional().catch(undefined),
157
+ /** Scheduled tasks (spec §9); the platform bounds clamp it, see `effectiveScheduleSettings`. */
158
+ scheduledTasks: ScheduledTasksSettingsSchema.prefault({}).catch(ScheduledTasksSettingsSchema.parse({})),
95
159
  });
96
160
  /** PATCH 体:只改给出的字段;未知字段 → 400。 */
97
161
  export const OrgSettingsPatchSchema = z
@@ -104,6 +168,11 @@ export const OrgSettingsPatchSchema = z
104
168
  wakeOnBackgroundTask: z.boolean().nullable().optional(),
105
169
  /** null 清除(恢复跟随部署默认)。 */
106
170
  sandboxEgress: SandboxEgressSettingSchema.nullable().optional(),
171
+ /**
172
+ * The org's sandbox tiers, replaced whole (fields left out follow the platform); never above
173
+ * the platform (`org.sandbox_tier_not_allowed`). null clears it.
174
+ */
175
+ sandboxTier: SandboxTierSettingSchema.nullable().optional(),
107
176
  /** The org's default time zone (IANA); null clears it (UTC). */
108
177
  timeZone: IanaTimeZone.nullable().optional(),
109
178
  /**
@@ -116,19 +185,10 @@ export const OrgSettingsPatchSchema = z
116
185
  * null removes a slot's entry (it follows the platform again). `platform.page` is refused.
117
186
  */
118
187
  uiArrangement: OrgUiArrangementPatchSchema.optional(),
188
+ /** Fields to change in the scheduled-tasks group; the rest keep their value. */
189
+ scheduledTasks: ScheduledTasksPatchSchema.optional(),
119
190
  })
120
191
  .strict();
121
- /**
122
- * 组织实际生效的沙箱出站:没有组织设置 → 部署默认;`none` → 只有回环;`allowlist` → 组织的白名单
123
- * (省略时用部署的),但部署是 `none` 时仍为 `none`(组织永远不比部署放得更开,部署改成 none 后存量设置也随之收紧)。
124
- */
125
- export function effectiveSandboxEgress(deployment, org) {
126
- if (org === undefined)
127
- return { egress: deployment.egress, allowHosts: [...deployment.allowHosts] };
128
- if (org.mode === "none" || deployment.egress === "none")
129
- return { egress: "none", allowHosts: [] };
130
- return { egress: "allowlist", allowHosts: [...(org.allowHosts ?? deployment.allowHosts)] };
131
- }
132
192
  /** Whether a finished background task wakes an idle conversation: the org's setting, else the deployment's. */
133
193
  export function effectiveWakeOnBackgroundTask(deployment, org) {
134
194
  return org ?? deployment;
@@ -162,25 +222,50 @@ export async function readOrgSettings(db, orgId) {
162
222
  .executeTakeFirst();
163
223
  return parseOrgSettings(row?.settings);
164
224
  }
225
+ const sandboxTierNotAllowed = (message) => new ApiError("org.sandbox_tier_not_allowed", message);
226
+ /** Whether a stored settings object holds a `sandboxTier` this version reads as `{ max: "strict" }`. */
227
+ export function invalidStoredSandboxTier(raw) {
228
+ const v = asObject(raw).sandboxTier;
229
+ return v !== undefined && !SandboxTierSettingSchema.safeParse(v).success;
230
+ }
231
+ /** A setting's tier for intent comparison: absent (follow the platform) ranks above every tier. */
232
+ const intentRank = (t) => t === undefined ? SANDBOX_TIERS.length : SANDBOX_TIERS.indexOf(t);
165
233
  /** 组织设置服务:读取与修改(修改需 `org.update`,锁内实时复核角色,写审计)。 */
166
234
  let OrgSettingsService = class OrgSettingsService {
167
235
  db;
168
236
  txHost;
169
237
  orgLock;
170
238
  config;
239
+ log;
171
240
  constructor(db, txHost, orgLock,
172
241
  /** The deployment config (`sandboxEgress` may not exceed the deployment's). */
173
- config) {
242
+ config, log) {
174
243
  this.db = db;
175
244
  this.txHost = txHost;
176
245
  this.orgLock = orgLock;
177
246
  this.config = config;
247
+ this.log = log;
248
+ }
249
+ /** Parses stored settings, warning when an invalid `sandboxTier` narrows to `{ max: "strict" }`. */
250
+ parseStored(orgId, raw) {
251
+ if (invalidStoredSandboxTier(raw)) {
252
+ this.log?.warn("org settings: stored sandboxTier is invalid; it reads as max strict", {
253
+ orgId,
254
+ });
255
+ }
256
+ return parseOrgSettings(raw);
178
257
  }
179
258
  /** 部署默认值(设置页展示「跟随部署」时的实际取值)。 */
180
259
  deployment() {
181
- const lim = this.config.get().sandbox.limits;
260
+ const { limits: lim, tiers } = this.config.get().sandbox;
182
261
  return {
183
262
  sandboxEgress: { mode: lim.egress, allowHosts: [...lim.allowHosts] },
263
+ sandboxTiers: {
264
+ max: tiers.max,
265
+ default: tiers.default,
266
+ allowUnsandboxed: tiers.allowUnsandboxed,
267
+ devHosts: [...lim.devHosts],
268
+ },
184
269
  };
185
270
  }
186
271
  /** 组织实际生效的沙箱 cpu / 内存(只读;由超级管理员在部署设置里覆盖)。 */
@@ -202,7 +287,7 @@ let OrgSettingsService = class OrgSettingsService {
202
287
  .executeTakeFirst();
203
288
  if (!row)
204
289
  throw new ApiError("org.not_found", "Organization not found");
205
- return parseOrgSettings(row.settings);
290
+ return this.parseStored(orgId, row.settings);
206
291
  }
207
292
  /** The refusals of {@link update} that need no read of the org (the Platform tool's precheck). */
208
293
  check(patch) {
@@ -210,14 +295,83 @@ let OrgSettingsService = class OrgSettingsService {
210
295
  this.deployment().sandboxEgress.mode === "none") {
211
296
  throw new ApiError("org.sandbox_egress_not_allowed", "This deployment disables sandbox network access (sandbox.limits.egress is none); an organization can only set sandboxEgress to none");
212
297
  }
298
+ const t = patch.sandboxTier;
299
+ if (t != null) {
300
+ const platform = this.config.get().sandbox.tiers;
301
+ if (t.max !== undefined && !tierAtMost(t.max, platform.max)) {
302
+ throw sandboxTierNotAllowed(`The platform allows sandbox tiers up to ${platform.max}; sandboxTier.max cannot be ${t.max}`);
303
+ }
304
+ const max = t.max ?? platform.max;
305
+ if (t.default !== undefined && !tierAtMost(t.default, max)) {
306
+ throw sandboxTierNotAllowed(`sandboxTier.default (${t.default}) cannot be above the organization's highest tier (${max})`);
307
+ }
308
+ if (t.allowUnsandboxed === true && !platform.allowUnsandboxed) {
309
+ throw sandboxTierNotAllowed("The platform does not allow unsandboxed devices (sandbox.tiers.allowUnsandboxed is false); sandboxTier.allowUnsandboxed cannot be true");
310
+ }
311
+ }
213
312
  }
214
313
  async update(orgId, patch, actorAccountId, actorTokenId,
215
314
  /** Merged into the audit detail (the Platform tool's `via: "agent"`). */
216
- auditDetail) {
315
+ auditDetail,
316
+ /**
317
+ * `onlyNarrows` (the Platform tool, API tokens): refuse a `sandboxTier` or `sandboxEgress`
318
+ * change that widens (see {@link assertOnlyNarrows}); only a person in a session widens.
319
+ */
320
+ opts) {
217
321
  this.check(patch);
218
- return this.updateIn(orgId, patch, actorAccountId, actorTokenId, auditDetail);
322
+ return this.updateIn(orgId, patch, actorAccountId, actorTokenId, auditDetail, opts);
219
323
  }
220
- async updateIn(orgId, patch, actorAccountId, actorTokenId, auditDetail) {
324
+ /** The effective sandbox policy for the org's settings and a device's own tier (null = none). */
325
+ sandboxPolicy(settings, deviceTier) {
326
+ return effectiveSandboxPolicy(this.config.get().sandbox, settings, deviceTier);
327
+ }
328
+ /**
329
+ * Refuses a patch that widens the org's sandbox (sandbox dev tiers spec §2, §7.2: only a person
330
+ * widens it, never the Platform tool or an API token):
331
+ * - `sandboxTier`: the effective ceiling, default or unsandboxed switch rises, or a stored cap
332
+ * loosens (an absent `max` / `default` follows the platform, so it ranks above every tier and
333
+ * clearing a cap that equals today's platform value is a widening; an explicit
334
+ * `allowUnsandboxed: false` stays false). A cleared `default` under a new `max` no higher than
335
+ * the old default is no loosening: the default never exceeds the ceiling. The value is
336
+ * replaced whole, so the refusal names the fields the patch would clear;
337
+ * - `sandboxEgress`: the base list grows: from none to anything else, or to hosts (the
338
+ * deployment's when the org gives none) not all in the current list.
339
+ */
340
+ assertOnlyNarrows(before, patch) {
341
+ if (patch.sandboxTier !== undefined) {
342
+ const next = patch.sandboxTier ?? undefined;
343
+ const prev = before.sandboxTier;
344
+ const b = this.sandboxPolicy(before, null);
345
+ const a = this.sandboxPolicy({ ...before, sandboxTier: next }, null);
346
+ // a cleared default follows the platform, capped by the ceiling: `max` bounds it then
347
+ const nextDefault = next?.default === undefined && next?.max !== undefined ? next.max : next?.default;
348
+ if (!tierAtMost(a.ceiling, b.ceiling) ||
349
+ !tierAtMost(a.defaultTier, b.defaultTier) ||
350
+ (a.allowUnsandboxed && !b.allowUnsandboxed) ||
351
+ intentRank(next?.max) > intentRank(prev?.max) ||
352
+ intentRank(nextDefault) > intentRank(prev?.default) ||
353
+ (prev?.allowUnsandboxed === false && next?.allowUnsandboxed !== false)) {
354
+ const cleared = ["max", "default", "allowUnsandboxed"].filter((f) => prev?.[f] !== undefined && next?.[f] === undefined);
355
+ throw sandboxTierNotAllowed("Raising the sandbox level needs a person in the organization settings." +
356
+ (cleared.length === 0
357
+ ? ""
358
+ : ` sandboxTier is replaced whole: this change clears ${cleared.join(", ")}` +
359
+ " (which would then follow the platform); give the values to keep again."));
360
+ }
361
+ }
362
+ if (patch.sandboxEgress !== undefined) {
363
+ const deploymentHosts = this.config.get().sandbox.limits.allowHosts;
364
+ const prev = before.sandboxEgress;
365
+ const next = patch.sandboxEgress ?? undefined;
366
+ const hostsOf = (e) => e?.allowHosts ?? deploymentHosts;
367
+ const widens = next?.mode !== "none" &&
368
+ (prev?.mode === "none" || !hostsOf(next).every((h) => hostsOf(prev).includes(h)));
369
+ if (widens) {
370
+ throw new ApiError("org.sandbox_egress_not_allowed", "Widening the sandbox network needs a person in the organization settings.");
371
+ }
372
+ }
373
+ }
374
+ async updateIn(orgId, patch, actorAccountId, actorTokenId, auditDetail, opts) {
221
375
  await this.orgLock.lockAndAuthorize(orgId, actorAccountId, "org.update");
222
376
  const row = await this.db
223
377
  .selectFrom("orgs")
@@ -225,14 +379,26 @@ let OrgSettingsService = class OrgSettingsService {
225
379
  .where("id", "=", orgId)
226
380
  .executeTakeFirstOrThrow();
227
381
  const stored = asObject(row.settings);
228
- const before = parseOrgSettings(stored);
382
+ const before = this.parseStored(orgId, stored);
383
+ // again under the org lock: a hot config reload may have lowered the platform since
384
+ this.check(patch);
385
+ // under the org lock, so the comparison is with the value the write replaces
386
+ if (opts?.onlyNarrows)
387
+ this.assertOnlyNarrows(before, patch);
229
388
  const changes = Object.fromEntries(Object.entries(patch)
230
389
  .filter(([, v]) => v !== undefined)
231
390
  // null clears sandboxEgress / wakeOnBackgroundTask: follow the deployment default again
232
391
  .map(([k, v]) => [k, v === null ? undefined : v]));
233
392
  const arrangementPatch = patch.uiArrangement;
234
393
  delete changes.uiArrangement;
394
+ delete changes.scheduledTasks;
235
395
  const after = { ...before, ...changes };
396
+ if (patch.scheduledTasks !== undefined) {
397
+ after.scheduledTasks = ScheduledTasksSettingsSchema.parse({
398
+ ...before.scheduledTasks,
399
+ ...Object.fromEntries(Object.entries(patch.scheduledTasks).filter(([, v]) => v !== undefined)),
400
+ });
401
+ }
236
402
  if (arrangementPatch !== undefined) {
237
403
  after.uiArrangement = mergeArrangement(before.uiArrangement, arrangementPatch);
238
404
  if (Object.keys(after.uiArrangement).length === 0)
@@ -240,6 +406,12 @@ let OrgSettingsService = class OrgSettingsService {
240
406
  }
241
407
  if (after.sandboxEgress === undefined)
242
408
  delete after.sandboxEgress;
409
+ // an empty sandboxTier is the same as none: follow the platform
410
+ if (after.sandboxTier !== undefined && Object.keys(after.sandboxTier).length === 0) {
411
+ delete after.sandboxTier;
412
+ }
413
+ if (after.sandboxTier === undefined)
414
+ delete after.sandboxTier;
243
415
  if (after.wakeOnBackgroundTask === undefined)
244
416
  delete after.wakeOnBackgroundTask;
245
417
  if (after.timeZone === undefined)
@@ -256,7 +428,7 @@ let OrgSettingsService = class OrgSettingsService {
256
428
  await assertThemeOnIn(this.db.db, orgId, patch.theme.id);
257
429
  }
258
430
  // 与存量合并:保留本版本不认识的键(清除的 sandboxEgress 不留在存量里)
259
- const { sandboxEgress: _old, wakeOnBackgroundTask: _oldWake, timeZone: _oldZone, theme: _oldTheme, uiArrangement: _oldArrangement, ...rest } = stored;
431
+ const { sandboxEgress: _old, sandboxTier: _oldTier, wakeOnBackgroundTask: _oldWake, timeZone: _oldZone, theme: _oldTheme, uiArrangement: _oldArrangement, scheduledTasks: _oldScheduled, ...rest } = stored;
260
432
  await this.db
261
433
  .updateTable("orgs")
262
434
  .set({ settings: JSON.stringify({ ...rest, ...after }) })
@@ -281,6 +453,22 @@ OrgSettingsService = __decorate([
281
453
  __param(0, Inject(KernelDb)),
282
454
  __param(1, Inject(TransactionHost)),
283
455
  __param(2, Inject(OrgLock)),
284
- __param(3, Inject(KernelConfigService))
456
+ __param(3, Inject(KernelConfigService)),
457
+ __param(4, Optional()),
458
+ __param(4, Inject(KERNEL_LOGGER))
285
459
  ], OrgSettingsService);
286
460
  export { OrgSettingsService };
461
+ /**
462
+ * The one {@link OrgSettingsService}: the orgs module re-exports it, and the catalog imports it
463
+ * directly (approving a network request writes `sandboxEgress`) without a cycle through OrgsModule.
464
+ */
465
+ let OrgSettingsModule = class OrgSettingsModule {
466
+ };
467
+ OrgSettingsModule = __decorate([
468
+ Module({
469
+ imports: [OrgLockModule],
470
+ providers: [OrgSettingsService],
471
+ exports: [OrgSettingsService],
472
+ })
473
+ ], OrgSettingsModule);
474
+ export { OrgSettingsModule };
@@ -153,6 +153,11 @@ export const PLATFORM_EXCLUSIONS = [
153
153
  category: "secret",
154
154
  reason: "Body carries connector credentials and client secrets entered by a person",
155
155
  },
156
+ {
157
+ route: "POST /api/v1/orgs/:orgId/catalog-requests/network",
158
+ category: "human-only",
159
+ reason: "A member asks org admins to allow a domain from a blocked command's card; agents never file network requests",
160
+ },
156
161
  {
157
162
  route: "POST /api/v1/orgs/:orgId/catalog-requests/:id/decide",
158
163
  category: "human-only",
@@ -281,6 +286,11 @@ export const PLATFORM_EXCLUSIONS = [
281
286
  category: "human-only",
282
287
  reason: "Disconnects a person's own machine (revokes its credential); a trust decision taken on the device's page",
283
288
  },
289
+ {
290
+ route: "POST /api/v1/orgs/:orgId/devices/:id/policy",
291
+ category: "human-only",
292
+ reason: "A person changes their own machine's network policy; agents never widen a device's network",
293
+ },
284
294
  {
285
295
  route: "POST /api/v1/device/authorize",
286
296
  category: "transport",
@@ -560,7 +570,7 @@ export const PLATFORM_EXCLUSIONS = [
560
570
  {
561
571
  route: "POST /api/v1/orgs/:orgId/approvals/:id/decision",
562
572
  category: "human-only",
563
- reason: "Decides a tool permission approval; only the person decides (spec §6.4); reading what waits is approvals.waiting / approvals.get",
573
+ reason: "Decides a tool permission approval or a network ask; only the person decides (spec §6.4); a person must decide network access on their own device; reading what waits is approvals.waiting / approvals.get",
564
574
  },
565
575
  {
566
576
  route: "GET /api/v1/orgs/:orgId/me/tool-grants",
@@ -87,6 +87,10 @@ export const PLATFORM_DOMAINS = [
87
87
  id: "devices",
88
88
  description: "Your own cloud devices (the workspaces your conversations run in): list, read, create, rename, delete, wipe the files.",
89
89
  },
90
+ {
91
+ id: "schedules",
92
+ description: "Your scheduled tasks (an agent runs a prompt on a schedule, as you, unattended): list, read, create, edit, pause, resume, delete, run now or as a trial, run history, and the tools approved for a task.",
93
+ },
90
94
  {
91
95
  id: "setup",
92
96
  description: "Plugin setup guides: list the guides you may help with, read one as Markdown, the state of its checks for a channel connection or a connector, and running one check. Entering secrets, connecting an account and confirming the app was updated stay with the person on the web; read the docs topic `setup` first.",
@@ -96,7 +96,7 @@ let AdminItemsOperations = class AdminItemsOperations {
96
96
  }),
97
97
  defineOperation({
98
98
  id: "admin.agent-get",
99
- description: "Read one agent of this org as the admin console shows it (super admin): its definition with the instructions in full, members, profile with quick action labels, versions count, the skills and connectors it uses by name. Never its conversations or credentials.",
99
+ description: "Read one agent of this org as the admin console shows it (super admin): its definition with the instructions in full, members, profile with quick action labels, versions count, the skills and connectors it has by name, and how many of the org's skills it turns off (skillsOff). Never its conversations or credentials.",
100
100
  input: AgentIdInput,
101
101
  example: { agentId: "7c1a5b0e-9a53-4c6b-8d64-2f5a1b0c3e77" },
102
102
  authz: { superAdmin: true },
@@ -38,7 +38,7 @@ const SHARE_FILES_SHOWN = 10;
38
38
  /** The model-facing result line of `agents.share-skill`. */
39
39
  function sharedText(o, name) {
40
40
  return o.kind === "shared"
41
- ? `Shared skill "${name}" with the organization: it is now an org skill (the person maintains it) that any agent of the org can select; this agent keeps using it.`
41
+ ? `Shared skill "${name}" with the organization: it is now an org skill (the person maintains it) that every agent of the org has unless it turns it off; this agent keeps using it.`
42
42
  : `Asked the org admins to share skill "${name}" with the organization; it waits for their decision in the inbox. Until then it stays this agent's own skill.`;
43
43
  }
44
44
  /** The model-facing result line of `agents.register-skill`. */
@@ -367,7 +367,7 @@ let AgentSkillsOperations = class AgentSkillsOperations {
367
367
  }),
368
368
  defineOperation({
369
369
  id: "agents.share-skill",
370
- description: "Share one of an agent's own skills with the organization, so any agent of the org can select it; the agent keeps using it and the person becomes its maintainer (later versions they register on their agents are proposed to the org admins). An org admin shares at once; anyone else's ask waits for an org admin's decision in the inbox. Only the agent's owner can; takes the agent id from agents.list and the skill id from agents.skills.",
370
+ description: "Share one of an agent's own skills with the organization, so every agent of the org has it unless it turns it off; the agent keeps using it and the person becomes its maintainer (later versions they register on their agents are proposed to the org admins). An org admin shares at once; anyone else's ask waits for an org admin's decision in the inbox. Only the agent's owner can; takes the agent id from agents.list and the skill id from agents.skills.",
371
371
  input: z.object({ agentId: AgentId, skillId: AgentSkillPathParams.shape.skillId }).strict(),
372
372
  example: {
373
373
  agentId: "7c1a5b0e-9a53-4c6b-8d64-2f5a1b0c3e77",
@@ -50,7 +50,7 @@ let AgentTemplatesOperations = class AgentTemplatesOperations {
50
50
  }),
51
51
  defineOperation({
52
52
  id: "agent-templates.get",
53
- description: "Read one agent template with its definition (model tier, tools, skills, connectors it needs). Takes the template id from agent-templates.list.",
53
+ description: "Read one agent template with its definition (model tier, tools, the skills an agent made from it loads first, the skills it turns off (disallowedSkills), connectors it needs). Takes the template id from agent-templates.list.",
54
54
  input: z.object({ templateId: AgentTemplateIdParams.shape.id }).strict(),
55
55
  example: { templateId: "5d0c7a52-3a0e-4f0a-bd0a-0f8c3d2a9e10" },
56
56
  authz: { orgAction: "agents.create" },
@@ -63,7 +63,7 @@ let AgentTemplatesOperations = class AgentTemplatesOperations {
63
63
  }),
64
64
  defineOperation({
65
65
  id: "agent-templates.save",
66
- description: "Save an agent of the org as an org agent template under a new name, so members can create their own copy from it. Credentials and per-person settings are not carried over; the result lists what was omitted.",
66
+ description: "Save an agent of the org as an org agent template under a new name, so members can create their own copy from it. The skills the agent turns off are saved as the template's disallowedSkills. Credentials and per-person settings are not carried over; the result lists what was omitted.",
67
67
  input: SaveAsAgentIdParams.extend({
68
68
  name: SaveAsTemplateBody.shape.name.min(1).max(80),
69
69
  description: SaveAsTemplateBody.shape.description.max(1024),
@@ -35,11 +35,6 @@ const Def = AgentCreateDefinition.shape;
35
35
  /** Every string the model sends is bounded (the route's tool refs and model strings are not). */
36
36
  const Short = z.string().min(1).max(200);
37
37
  const Model = Def.model.extend({ provider: Short, model: Short }).strict();
38
- const RouteSkills = Def.skills.unwrap();
39
- /** `writePolicy` stays the web's; `orgSkills` is required here (the route defaults it to []). */
40
- const Skills = RouteSkills.pick({ pinned: true })
41
- .extend({ orgSkills: RouteSkills.shape.orgSkills.unwrap() })
42
- .strict();
43
38
  /**
44
39
  * Whether `next` sets an `allow` that `current` does not have (decision 2026-11-09: `allow` on a
45
40
  * connector tool lets an agent call it without asking). Such a change is confirmed every time and
@@ -61,7 +56,8 @@ const Fields = {
61
56
  */
62
57
  disabledTools: Def.disabledTools.unwrap(),
63
58
  toolPolicies: z.record(Short, Def.toolPolicies.unwrap().valueType),
64
- skills: Skills,
59
+ /** Skills turned off (skills default-on spec §4); `skills.writePolicy` stays the web's. */
60
+ disabledSkills: Def.disabledSkills.unwrap(),
65
61
  subagents: Def.subagents.unwrap(),
66
62
  };
67
63
  /**
@@ -189,7 +185,7 @@ let AgentsOperations = class AgentsOperations {
189
185
  }),
190
186
  defineOperation({
191
187
  id: "agents.get",
192
- description: "Read one agent you can see with its full definition (description, instructions, model, the tools it turns off (disabledTools: an agent has every tool the org offers except these), tool policies, skills and sub-agents) and its profile (avatar, description, quick actions). Takes the agent id from agents.list.",
188
+ description: "Read one agent you can see with its full definition (description, instructions, model, the tools it turns off (disabledTools: an agent has every tool the org offers except these), tool policies, the skills it turns off (disabledSkills: an agent has every skill on in the org except these), skills.writePolicy and sub-agents) and its profile (avatar, description, quick actions). Takes the agent id from agents.list.",
193
189
  input: z.object({ agentId: AgentId }).strict(),
194
190
  example: { agentId: "7c1a5b0e-9a53-4c6b-8d64-2f5a1b0c3e77" },
195
191
  authz: { orgAction: "agents.use" },
@@ -201,7 +197,7 @@ let AgentsOperations = class AgentsOperations {
201
197
  }),
202
198
  defineOperation({
203
199
  id: "agents.create",
204
- description: 'Create a new private agent owned by you, with a name, a model and optionally instructions, disabledTools, tool policies, skills and sub-agents. An agent has every tool the org offers (all built-in tools, the tools of the extensions on in the org and of the org\'s connectors) unless disabledTools turns it off: { kind: "builtin", name }, { kind: "plugin", plugin, tools: "all" | [names] } or { kind: "connector", connectorId, tools: "all" | [names] }. Agents created with the Platform tool are private; sharing an agent with the org or with members is done on the agents page of the web app.',
200
+ description: 'Create a new private agent owned by you, with a name, a model and optionally instructions, disabledTools, tool policies, disabledSkills and sub-agents. An agent has every tool the org offers (all built-in tools, the tools of the extensions on in the org and of the org\'s connectors) unless disabledTools turns it off: { kind: "builtin", name }, { kind: "plugin", plugin, tools: "all" | [names] } or { kind: "connector", connectorId, tools: "all" | [names] }. Likewise, an agent has every skill on in the org unless disabledSkills turns it off: { kind: "plugin", plugin, skills: "all" | [names] } for skills of a plugin (skills.list gives the plugin of each), or { kind: "skill", skillId } for a skill without a plugin; the skills of builtin plugins (builtin in skills.list) and the own skills of the agent cannot be turned off. Agents created with the Platform tool are private; sharing an agent with the org or with members is done on the agents page of the web app.',
205
201
  input: z
206
202
  .object({
207
203
  definition: z
@@ -211,7 +207,7 @@ let AgentsOperations = class AgentsOperations {
211
207
  model: Fields.model,
212
208
  disabledTools: Fields.disabledTools.optional(),
213
209
  toolPolicies: Fields.toolPolicies.optional(),
214
- skills: Fields.skills.optional(),
210
+ disabledSkills: Fields.disabledSkills.optional(),
215
211
  subagents: Fields.subagents.optional(),
216
212
  })
217
213
  .strict(),
@@ -248,7 +244,7 @@ let AgentsOperations = class AgentsOperations {
248
244
  model: d.model,
249
245
  ...(d.disabledTools !== undefined && { disabledTools: d.disabledTools }),
250
246
  ...(d.toolPolicies !== undefined && { toolPolicies: d.toolPolicies }),
251
- ...(d.skills !== undefined && { skills: d.skills }),
247
+ ...(d.disabledSkills !== undefined && { disabledSkills: d.disabledSkills }),
252
248
  ...(d.subagents !== undefined && { subagents: d.subagents }),
253
249
  });
254
250
  return this.agents.create(ctx.orgId, ctx.actorAccountId, {
@@ -259,7 +255,7 @@ let AgentsOperations = class AgentsOperations {
259
255
  }),
260
256
  defineOperation({
261
257
  id: "agents.update",
262
- description: 'Change an agent you may edit: your own, private or shared, and as an org admin any agent of the org. Send the version you read from agents.get and only the fields to change; the others keep their value. The description (one short paragraph, "" removes it) is saved with the definition; the avatar and quick actions are changed with agents.update-profile. disabledTools replaces the whole list of turned-off tools (send [] to turn every tool on). A version that is no longer current is refused, so read the agent again first. The visibility cannot be changed here (that is done on the agents page of the web app). A change to your own private agent may be remembered by the person; a change to a shared agent, or one that sets a tool policy to allow, is confirmed every time.',
258
+ description: 'Change an agent you may edit: your own, private or shared, and as an org admin any agent of the org. Send the version you read from agents.get and only the fields to change; the others keep their value. The description (one short paragraph, "" removes it) is saved with the definition; the avatar and quick actions are changed with agents.update-profile. disabledTools replaces the whole list of turned-off tools (send [] to turn every tool on). disabledSkills replaces the whole list of turned-off skills (send [] to turn every skill on); an agent has every skill on in the org unless disabledSkills turns it off: { kind: "plugin", plugin, skills: "all" | [names] } for skills of a plugin (skills.list gives the plugin of each), or { kind: "skill", skillId } for a skill without a plugin; the skills of builtin plugins (builtin in skills.list) and the own skills of the agent cannot be turned off. A version that is no longer current is refused, so read the agent again first. The visibility cannot be changed here (that is done on the agents page of the web app). A change to your own private agent may be remembered by the person; a change to a shared agent, or one that sets a tool policy to allow, is confirmed every time.',
263
259
  input: z
264
260
  .object({
265
261
  agentId: AgentId,
@@ -270,7 +266,7 @@ let AgentsOperations = class AgentsOperations {
270
266
  model: Fields.model.optional(),
271
267
  disabledTools: Fields.disabledTools.optional(),
272
268
  toolPolicies: Fields.toolPolicies.optional(),
273
- skills: Fields.skills.optional(),
269
+ disabledSkills: Fields.disabledSkills.optional(),
274
270
  subagents: Fields.subagents.optional(),
275
271
  })
276
272
  .strict(),
@@ -292,7 +288,7 @@ let AgentsOperations = class AgentsOperations {
292
288
  model: "definition.model",
293
289
  disabledTools: "definition.disabledTools",
294
290
  toolPolicies: "definition.toolPolicies",
295
- skills: "definition.skills",
291
+ disabledSkills: "definition.disabledSkills",
296
292
  subagents: "definition.subagents",
297
293
  },
298
294
  confirm: "personal",
@@ -332,7 +328,7 @@ let AgentsOperations = class AgentsOperations {
332
328
  ...(patch.model !== undefined && { model: patch.model }),
333
329
  ...(patch.disabledTools !== undefined && { disabledTools: patch.disabledTools }),
334
330
  ...(patch.toolPolicies !== undefined && { toolPolicies: patch.toolPolicies }),
335
- ...(patch.skills !== undefined && { skills: patch.skills }),
331
+ ...(patch.disabledSkills !== undefined && { disabledSkills: patch.disabledSkills }),
336
332
  ...(patch.subagents !== undefined && { subagents: patch.subagents }),
337
333
  };
338
334
  return this.agents.update(ctx.orgId, agentId, ctx.actorAccountId, { definition, expectedVersion: version }, undefined, viaAgent("agents.update", ctx));
@@ -414,7 +410,7 @@ let AgentsOperations = class AgentsOperations {
414
410
  }),
415
411
  defineOperation({
416
412
  id: "agents.preview-from-template",
417
- description: "Preview creating an agent from an agent template: what carries over, what is pending (connectors the org has not set up) or unavailable, the model that would be used, and a previewHash. agents.create-from-template needs that hash.",
413
+ description: "Preview creating an agent from an agent template: what carries over, what is pending (connectors the org has not set up) or unavailable, the model that would be used, and a previewHash. Each skill the template names is carried when the org has it and unavailable otherwise; the new agent has every skill on in the org except those the template's disallowedSkills turns off. agents.create-from-template needs that hash.",
418
414
  input: AgentFromTemplatePreviewBody,
419
415
  example: { templateId: "5d0c7a52-3a0e-4f0a-bd0a-0f8c3d2a9e10" },
420
416
  authz: { orgAction: "agents.create" },
@@ -131,7 +131,7 @@ let DevicesOperations = class DevicesOperations {
131
131
  }),
132
132
  defineOperation({
133
133
  id: "devices.get",
134
- description: "Read one of your devices with its source (cloud or local), provider, state, instance, the machine of a local one and whether it is online, limits and creation time. Takes the id from devices.list.",
134
+ description: "Read one of your devices with its source (cloud or local), provider, state, instance, the machine of a local one and whether it is online, limits, sandbox level (the tier chosen and the one in effect, the highest allowed and why it is lower, whether it runs without a sandbox) and creation time. Takes the id from devices.list. Only the person changes a device's sandbox level, on the web.",
135
135
  input: DeviceId,
136
136
  example: { deviceId: "4e8a0c1d-6b2f-4a37-8d19-3c5e7f9a1b20" },
137
137
  authz: { orgAction: "agents.use" },
@@ -276,6 +276,8 @@ let DevicesOperations = class DevicesOperations {
276
276
  authz: { orgAction: "agents.use" },
277
277
  mirrors: ["PATCH /api/v1/orgs/:orgId/devices/:id"],
278
278
  renames: { deviceId: "id" },
279
+ // a person decides their device's sandbox level (sandbox dev tiers spec §7.2)
280
+ omits: ["tier"],
279
281
  confirm: "personal",
280
282
  errors: [
281
283
  {
@@ -109,6 +109,25 @@ function orgRow(i) {
109
109
  summary: `Your ${r.kind} request was ${r.status}${r.reason ? `: ${r.reason}` : ""}.`,
110
110
  };
111
111
  }
112
+ case "schedule_attention": {
113
+ const a = i.attention;
114
+ const summary = a.reason === "missing_grants"
115
+ ? `Scheduled task "${a.name}" ran without: ${a.missing.join(", ")}.${a.needsRole.length > 0 ? ` Needing a higher role than the owner has (cannot be pre-approved): ${a.needsRole.join(", ")}.` : ""}${a.needsAdmin.length > 0 ? ` Outside the organization's network allowlist (only an org admin can add them): ${a.needsAdmin.join(", ")}.` : ""}${a.deviceRefuses ? " The task's device does not accept network grants; its owner allows domains locally (capybara-device policy add allowedDomains <host>)." : ""}`
116
+ : a.reason === "failed"
117
+ ? `Scheduled task "${a.name}"'s latest run failed${a.error ? `: ${a.error}` : ""}.`
118
+ : `Scheduled task "${a.name}" is paused (${a.reason}${a.pausedBy ? ` by ${a.pausedBy.name || a.pausedBy.id}` : ""}).`;
119
+ return {
120
+ ...base,
121
+ name: a.name,
122
+ state: a.reason,
123
+ summary,
124
+ next: a.reason === "missing_grants"
125
+ ? "Add the missing tools to the task's approved tools and connect the missing connectors on the web; tools that need a higher role cannot be pre-approved; network domains (network:<host>) are approved only by the person on the task page."
126
+ : a.reason === "failed"
127
+ ? "Check the run's error and the task's settings; the next good run clears this, and repeated failures pause the task."
128
+ : "Resume the task once the cause is fixed.",
129
+ };
130
+ }
112
131
  default: {
113
132
  const n = i.notice;
114
133
  return {