@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
@@ -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,13 @@ 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 { readPlatformTiers } from "../sandbox/platform-tiers.js";
22
+ import { effectiveSandboxPolicy } from "../sandbox/tiers.js";
20
23
  import { assertThemeOnIn } from "../themes/theme-state.js";
21
24
  import { IanaTimeZone } from "../time-zone.js";
22
25
  import { mergeArrangement, OrgUiArrangementPatchSchema, UiArrangementSchema, } from "../ui/arrangement.js";
23
- import { OrgLock } from "./org-lock.js";
26
+ import { OrgLock, OrgLockModule } from "./org-lock.js";
24
27
  /**
25
28
  * 组织设置(`capybara.orgs.settings` jsonb)。读取宽松:缺失或非法的字段按默认值,未知键保留不动
26
29
  * (更新时与存量合并),新版本加字段不需要迁移。
@@ -30,7 +33,10 @@ import { OrgLock } from "./org-lock.js";
30
33
  * `Read` / `Glob` / `Grep` 外都需要审批(执行时实时读取)。
31
34
  * - `wakeOnBackgroundTask`:组织对 `runtime.wakeOnBackgroundTask` 的覆盖(缺省跟随部署),见 {@link effectiveWakeOnBackgroundTask}。
32
35
  * - `sandboxEgress`:组织的沙箱出站(Ruling M19 / D6),缺省跟随部署的 `sandbox.limits.egress` + `allowHosts`。
33
- * 见 {@link effectiveSandboxEgress}。存量值非法时按 `none` 读(宁可断网,不悄悄放宽到部署默认)。
36
+ * See `baseSandboxEgress` (sandbox/tiers.ts). 存量值非法时按 `none` 读(宁可断网,不悄悄放宽到部署默认)。
37
+ * - `sandboxTier`: the org's sandbox tier ceiling, default and unsandboxed switch (sandbox dev tiers
38
+ * spec §4.1), each absent = follow the platform (`sandbox.tiers`); never above the platform. An
39
+ * invalid stored value reads as `{ max: "strict" }` (narrower, never wider).
34
40
  * - `platformTool`: the `Platform` builtin for the whole org (Platform tool spec §3): `on`,
35
41
  * `read-only` (writes listed as unavailable) or `off` (default `on`).
36
42
  * - `timeZone`: the org's default time zone (IANA; absent = UTC) for senders without one of their
@@ -65,6 +71,19 @@ export const SandboxEgressSettingSchema = z
65
71
  .optional(),
66
72
  })
67
73
  .strict();
74
+ const TierSchema = z.enum(["strict", "dev", "open"]);
75
+ /**
76
+ * The org's sandbox tier setting (sandbox dev tiers spec §4.1): `max` the highest tier its devices
77
+ * may reach, `default` the tier of a device that chose none, `allowUnsandboxed` false forbids
78
+ * unsandboxed local devices. Each absent = follow the platform's `sandbox.tiers`.
79
+ */
80
+ export const SandboxTierSettingSchema = z
81
+ .object({
82
+ max: TierSchema.optional(),
83
+ default: TierSchema.optional(),
84
+ allowUnsandboxed: z.boolean().optional(),
85
+ })
86
+ .strict();
68
87
  /**
69
88
  * The org's theme setting (themes spec §7): `id` a theme on in the org (a `platform_themes` id),
70
89
  * null = follow the platform's default; `personal: "locked"` hides the personal picker in the org
@@ -78,12 +97,56 @@ export const OrgThemeSettingSchema = z
78
97
  .strict();
79
98
  /** The theme setting of an org that has none: follow the platform, personal picks allowed. */
80
99
  export const DEFAULT_ORG_THEME = { id: null, personal: "allowed" };
100
+ const posInt = (max, def) => z.number().int().min(1).max(max).default(def).catch(def);
101
+ /** A stored `forbiddenTools` entry worth keeping: a tool key of the length the PATCH accepts. */
102
+ const isForbiddenToolEntry = (v) => typeof v === "string" && v.length >= 1 && v.length <= 200;
103
+ /**
104
+ * Org settings of scheduled tasks (spec §9); every field reads leniently (invalid = default,
105
+ * except `forbiddenTools`, which keeps its valid entries).
106
+ */
107
+ export const ScheduledTasksSettingsSchema = z.object({
108
+ enabled: z.boolean().default(true).catch(true),
109
+ minIntervalMinutes: posInt(1_000_000, 60),
110
+ maxTasksPerMember: posInt(1_000_000, 20),
111
+ maxTasksPerOrg: posInt(1_000_000, 200),
112
+ maxRunsPerHour: posInt(1_000_000, 60),
113
+ defaultExpiryDays: z.number().int().min(1).max(36_500).nullable().default(null).catch(null),
114
+ catchUpWindowMinutes: posInt(1_000_000, 60),
115
+ unattendedAsk: z.enum(["fail", "wait"]).default("fail").catch("fail"),
116
+ pauseAfterFailures: posInt(1000, 5),
117
+ // fail-safe: an invalid entry is dropped, the valid ones stay forbidden (never reset to []);
118
+ // past the PATCH's 500 the list is truncated
119
+ forbiddenTools: z
120
+ .preprocess((v) => (Array.isArray(v) ? v.filter(isForbiddenToolEntry).slice(0, 500) : v), z.array(z.string()).max(500).default([]))
121
+ .catch([]),
122
+ });
123
+ /**
124
+ * The PATCH form: a partial merge into the group (fields left out keep their stored value; see
125
+ * `OrgSettingsService.update`). A field given replaces that field whole, `forbiddenTools` too.
126
+ */
127
+ export const ScheduledTasksPatchSchema = z
128
+ .object({
129
+ enabled: z.boolean(),
130
+ minIntervalMinutes: z.number().int().min(1).max(1_000_000),
131
+ maxTasksPerMember: z.number().int().min(1).max(1_000_000),
132
+ maxTasksPerOrg: z.number().int().min(1).max(1_000_000),
133
+ maxRunsPerHour: z.number().int().min(1).max(1_000_000),
134
+ defaultExpiryDays: z.number().int().min(1).max(36_500).nullable(),
135
+ catchUpWindowMinutes: z.number().int().min(1).max(1_000_000),
136
+ unattendedAsk: z.enum(["fail", "wait"]),
137
+ pauseAfterFailures: z.number().int().min(1).max(1000),
138
+ forbiddenTools: z.array(z.string().min(1).max(200)).max(500),
139
+ })
140
+ .partial()
141
+ .strict();
81
142
  export const OrgSettingsSchema = z.object({
82
143
  toolSearch: z.enum(["on", "off"]).default("on").catch("on"),
83
144
  locale: z.enum(["zh", "en"]).default("en").catch("en"),
84
145
  sandboxToolPolicy: z.enum(["allow", "ask"]).default("allow").catch("allow"),
85
146
  platformTool: z.enum(["on", "read-only", "off"]).default("on").catch("on"),
86
147
  sandboxEgress: SandboxEgressSettingSchema.optional().catch({ mode: "none" }),
148
+ /** The org's sandbox tiers (sandbox dev tiers spec §4.1); absent follows the platform. */
149
+ sandboxTier: SandboxTierSettingSchema.optional().catch({ max: "strict" }),
87
150
  /** Overrides `runtime.wakeOnBackgroundTask`; absent follows the deployment. */
88
151
  wakeOnBackgroundTask: z.boolean().optional().catch(undefined),
89
152
  /** The org's default time zone (run context spec §3.3); absent = UTC. */
@@ -92,6 +155,8 @@ export const OrgSettingsSchema = z.object({
92
155
  theme: OrgThemeSettingSchema.optional().catch(undefined),
93
156
  /** Hidden and ordered extension contributions per slot; absent = follow the platform. */
94
157
  uiArrangement: UiArrangementSchema.optional().catch(undefined),
158
+ /** Scheduled tasks (spec §9); the platform bounds clamp it, see `effectiveScheduleSettings`. */
159
+ scheduledTasks: ScheduledTasksSettingsSchema.prefault({}).catch(ScheduledTasksSettingsSchema.parse({})),
95
160
  });
96
161
  /** PATCH 体:只改给出的字段;未知字段 → 400。 */
97
162
  export const OrgSettingsPatchSchema = z
@@ -104,6 +169,11 @@ export const OrgSettingsPatchSchema = z
104
169
  wakeOnBackgroundTask: z.boolean().nullable().optional(),
105
170
  /** null 清除(恢复跟随部署默认)。 */
106
171
  sandboxEgress: SandboxEgressSettingSchema.nullable().optional(),
172
+ /**
173
+ * The org's sandbox tiers, replaced whole (fields left out follow the platform); never above
174
+ * the platform (`org.sandbox_tier_not_allowed`). null clears it.
175
+ */
176
+ sandboxTier: SandboxTierSettingSchema.nullable().optional(),
107
177
  /** The org's default time zone (IANA); null clears it (UTC). */
108
178
  timeZone: IanaTimeZone.nullable().optional(),
109
179
  /**
@@ -116,19 +186,10 @@ export const OrgSettingsPatchSchema = z
116
186
  * null removes a slot's entry (it follows the platform again). `platform.page` is refused.
117
187
  */
118
188
  uiArrangement: OrgUiArrangementPatchSchema.optional(),
189
+ /** Fields to change in the scheduled-tasks group; the rest keep their value. */
190
+ scheduledTasks: ScheduledTasksPatchSchema.optional(),
119
191
  })
120
192
  .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
193
  /** Whether a finished background task wakes an idle conversation: the org's setting, else the deployment's. */
133
194
  export function effectiveWakeOnBackgroundTask(deployment, org) {
134
195
  return org ?? deployment;
@@ -162,25 +223,55 @@ export async function readOrgSettings(db, orgId) {
162
223
  .executeTakeFirst();
163
224
  return parseOrgSettings(row?.settings);
164
225
  }
226
+ const sandboxTierNotAllowed = (message) => new ApiError("org.sandbox_tier_not_allowed", message);
227
+ /** Whether a stored settings object holds a `sandboxTier` this version reads as `{ max: "strict" }`. */
228
+ export function invalidStoredSandboxTier(raw) {
229
+ const v = asObject(raw).sandboxTier;
230
+ return v !== undefined && !SandboxTierSettingSchema.safeParse(v).success;
231
+ }
232
+ /** A setting's tier for intent comparison: absent (follow the platform) ranks above every tier. */
233
+ const intentRank = (t) => t === undefined ? SANDBOX_TIERS.length : SANDBOX_TIERS.indexOf(t);
165
234
  /** 组织设置服务:读取与修改(修改需 `org.update`,锁内实时复核角色,写审计)。 */
166
235
  let OrgSettingsService = class OrgSettingsService {
167
236
  db;
168
237
  txHost;
169
238
  orgLock;
170
239
  config;
240
+ log;
171
241
  constructor(db, txHost, orgLock,
172
242
  /** The deployment config (`sandboxEgress` may not exceed the deployment's). */
173
- config) {
243
+ config, log) {
174
244
  this.db = db;
175
245
  this.txHost = txHost;
176
246
  this.orgLock = orgLock;
177
247
  this.config = config;
248
+ this.log = log;
249
+ }
250
+ /** Parses stored settings, warning when an invalid `sandboxTier` narrows to `{ max: "strict" }`. */
251
+ parseStored(orgId, raw) {
252
+ if (invalidStoredSandboxTier(raw)) {
253
+ this.log?.warn("org settings: stored sandboxTier is invalid; it reads as max strict", {
254
+ orgId,
255
+ });
256
+ }
257
+ return parseOrgSettings(raw);
258
+ }
259
+ /** The platform's effective sandbox tiers (the startup config over the admin console's choice). */
260
+ platformTiers() {
261
+ return readPlatformTiers(this.db.db, this.config, this.log);
178
262
  }
179
263
  /** 部署默认值(设置页展示「跟随部署」时的实际取值)。 */
180
- deployment() {
264
+ async deployment() {
181
265
  const lim = this.config.get().sandbox.limits;
266
+ const tiers = await this.platformTiers();
182
267
  return {
183
268
  sandboxEgress: { mode: lim.egress, allowHosts: [...lim.allowHosts] },
269
+ sandboxTiers: {
270
+ max: tiers.max,
271
+ default: tiers.default,
272
+ allowUnsandboxed: tiers.allowUnsandboxed,
273
+ devHosts: [...lim.devHosts],
274
+ },
184
275
  };
185
276
  }
186
277
  /** 组织实际生效的沙箱 cpu / 内存(只读;由超级管理员在部署设置里覆盖)。 */
@@ -202,22 +293,99 @@ let OrgSettingsService = class OrgSettingsService {
202
293
  .executeTakeFirst();
203
294
  if (!row)
204
295
  throw new ApiError("org.not_found", "Organization not found");
205
- return parseOrgSettings(row.settings);
296
+ return this.parseStored(orgId, row.settings);
206
297
  }
207
298
  /** The refusals of {@link update} that need no read of the org (the Platform tool's precheck). */
208
- check(patch) {
299
+ async check(patch) {
209
300
  if (patch.sandboxEgress?.mode === "allowlist" &&
210
- this.deployment().sandboxEgress.mode === "none") {
301
+ this.config.get().sandbox.limits.egress === "none") {
211
302
  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
303
  }
304
+ const t = patch.sandboxTier;
305
+ if (t != null) {
306
+ const platform = await this.platformTiers();
307
+ if (t.max !== undefined && !tierAtMost(t.max, platform.max)) {
308
+ throw sandboxTierNotAllowed(`The platform allows sandbox tiers up to ${platform.max}; sandboxTier.max cannot be ${t.max}`);
309
+ }
310
+ const max = t.max ?? platform.max;
311
+ if (t.default !== undefined && !tierAtMost(t.default, max)) {
312
+ throw sandboxTierNotAllowed(`sandboxTier.default (${t.default}) cannot be above the organization's highest tier (${max})`);
313
+ }
314
+ if (t.allowUnsandboxed === true && !platform.allowUnsandboxed) {
315
+ throw sandboxTierNotAllowed("The platform does not allow unsandboxed devices (sandbox.tiers.allowUnsandboxed is false); sandboxTier.allowUnsandboxed cannot be true");
316
+ }
317
+ }
213
318
  }
214
319
  async update(orgId, patch, actorAccountId, actorTokenId,
215
320
  /** Merged into the audit detail (the Platform tool's `via: "agent"`). */
216
- auditDetail) {
217
- this.check(patch);
218
- return this.updateIn(orgId, patch, actorAccountId, actorTokenId, auditDetail);
321
+ auditDetail,
322
+ /**
323
+ * `onlyNarrows` (the Platform tool, API tokens): refuse a `sandboxTier` or `sandboxEgress`
324
+ * change that widens (see {@link assertOnlyNarrows}); only a person in a session widens.
325
+ */
326
+ opts) {
327
+ await this.check(patch);
328
+ return this.updateIn(orgId, patch, actorAccountId, actorTokenId, auditDetail, opts);
329
+ }
330
+ /**
331
+ * The effective sandbox policy for the org's settings and a device's own tier (null = none);
332
+ * `tiers`: the platform's tiers when the caller already read them.
333
+ */
334
+ async sandboxPolicy(settings, deviceTier, tiers) {
335
+ const deployment = {
336
+ limits: this.config.get().sandbox.limits,
337
+ tiers: tiers ?? (await this.platformTiers()),
338
+ };
339
+ return effectiveSandboxPolicy(deployment, settings, deviceTier);
340
+ }
341
+ /**
342
+ * Refuses a patch that widens the org's sandbox (sandbox dev tiers spec §2, §7.2: only a person
343
+ * widens it, never the Platform tool or an API token):
344
+ * - `sandboxTier`: the effective ceiling, default or unsandboxed switch rises, or a stored cap
345
+ * loosens (an absent `max` / `default` follows the platform, so it ranks above every tier and
346
+ * clearing a cap that equals today's platform value is a widening; an explicit
347
+ * `allowUnsandboxed: false` stays false). A cleared `default` under a new `max` no higher than
348
+ * the old default is no loosening: the default never exceeds the ceiling. The value is
349
+ * replaced whole, so the refusal names the fields the patch would clear;
350
+ * - `sandboxEgress`: the base list grows: from none to anything else, or to hosts (the
351
+ * deployment's when the org gives none) not all in the current list.
352
+ */
353
+ async assertOnlyNarrows(before, patch) {
354
+ if (patch.sandboxTier !== undefined) {
355
+ const next = patch.sandboxTier ?? undefined;
356
+ const prev = before.sandboxTier;
357
+ const tiers = await this.platformTiers();
358
+ const b = await this.sandboxPolicy(before, null, tiers);
359
+ const a = await this.sandboxPolicy({ ...before, sandboxTier: next }, null, tiers);
360
+ // a cleared default follows the platform, capped by the ceiling: `max` bounds it then
361
+ const nextDefault = next?.default === undefined && next?.max !== undefined ? next.max : next?.default;
362
+ if (!tierAtMost(a.ceiling, b.ceiling) ||
363
+ !tierAtMost(a.defaultTier, b.defaultTier) ||
364
+ (a.allowUnsandboxed && !b.allowUnsandboxed) ||
365
+ intentRank(next?.max) > intentRank(prev?.max) ||
366
+ intentRank(nextDefault) > intentRank(prev?.default) ||
367
+ (prev?.allowUnsandboxed === false && next?.allowUnsandboxed !== false)) {
368
+ const cleared = ["max", "default", "allowUnsandboxed"].filter((f) => prev?.[f] !== undefined && next?.[f] === undefined);
369
+ throw sandboxTierNotAllowed("Raising the sandbox level needs a person in the organization settings." +
370
+ (cleared.length === 0
371
+ ? ""
372
+ : ` sandboxTier is replaced whole: this change clears ${cleared.join(", ")}` +
373
+ " (which would then follow the platform); give the values to keep again."));
374
+ }
375
+ }
376
+ if (patch.sandboxEgress !== undefined) {
377
+ const deploymentHosts = this.config.get().sandbox.limits.allowHosts;
378
+ const prev = before.sandboxEgress;
379
+ const next = patch.sandboxEgress ?? undefined;
380
+ const hostsOf = (e) => e?.allowHosts ?? deploymentHosts;
381
+ const widens = next?.mode !== "none" &&
382
+ (prev?.mode === "none" || !hostsOf(next).every((h) => hostsOf(prev).includes(h)));
383
+ if (widens) {
384
+ throw new ApiError("org.sandbox_egress_not_allowed", "Widening the sandbox network needs a person in the organization settings.");
385
+ }
386
+ }
219
387
  }
220
- async updateIn(orgId, patch, actorAccountId, actorTokenId, auditDetail) {
388
+ async updateIn(orgId, patch, actorAccountId, actorTokenId, auditDetail, opts) {
221
389
  await this.orgLock.lockAndAuthorize(orgId, actorAccountId, "org.update");
222
390
  const row = await this.db
223
391
  .selectFrom("orgs")
@@ -225,14 +393,26 @@ let OrgSettingsService = class OrgSettingsService {
225
393
  .where("id", "=", orgId)
226
394
  .executeTakeFirstOrThrow();
227
395
  const stored = asObject(row.settings);
228
- const before = parseOrgSettings(stored);
396
+ const before = this.parseStored(orgId, stored);
397
+ // again under the org lock: a hot config reload may have lowered the platform since
398
+ await this.check(patch);
399
+ // under the org lock, so the comparison is with the value the write replaces
400
+ if (opts?.onlyNarrows)
401
+ await this.assertOnlyNarrows(before, patch);
229
402
  const changes = Object.fromEntries(Object.entries(patch)
230
403
  .filter(([, v]) => v !== undefined)
231
404
  // null clears sandboxEgress / wakeOnBackgroundTask: follow the deployment default again
232
405
  .map(([k, v]) => [k, v === null ? undefined : v]));
233
406
  const arrangementPatch = patch.uiArrangement;
234
407
  delete changes.uiArrangement;
408
+ delete changes.scheduledTasks;
235
409
  const after = { ...before, ...changes };
410
+ if (patch.scheduledTasks !== undefined) {
411
+ after.scheduledTasks = ScheduledTasksSettingsSchema.parse({
412
+ ...before.scheduledTasks,
413
+ ...Object.fromEntries(Object.entries(patch.scheduledTasks).filter(([, v]) => v !== undefined)),
414
+ });
415
+ }
236
416
  if (arrangementPatch !== undefined) {
237
417
  after.uiArrangement = mergeArrangement(before.uiArrangement, arrangementPatch);
238
418
  if (Object.keys(after.uiArrangement).length === 0)
@@ -240,6 +420,12 @@ let OrgSettingsService = class OrgSettingsService {
240
420
  }
241
421
  if (after.sandboxEgress === undefined)
242
422
  delete after.sandboxEgress;
423
+ // an empty sandboxTier is the same as none: follow the platform
424
+ if (after.sandboxTier !== undefined && Object.keys(after.sandboxTier).length === 0) {
425
+ delete after.sandboxTier;
426
+ }
427
+ if (after.sandboxTier === undefined)
428
+ delete after.sandboxTier;
243
429
  if (after.wakeOnBackgroundTask === undefined)
244
430
  delete after.wakeOnBackgroundTask;
245
431
  if (after.timeZone === undefined)
@@ -256,7 +442,7 @@ let OrgSettingsService = class OrgSettingsService {
256
442
  await assertThemeOnIn(this.db.db, orgId, patch.theme.id);
257
443
  }
258
444
  // 与存量合并:保留本版本不认识的键(清除的 sandboxEgress 不留在存量里)
259
- const { sandboxEgress: _old, wakeOnBackgroundTask: _oldWake, timeZone: _oldZone, theme: _oldTheme, uiArrangement: _oldArrangement, ...rest } = stored;
445
+ const { sandboxEgress: _old, sandboxTier: _oldTier, wakeOnBackgroundTask: _oldWake, timeZone: _oldZone, theme: _oldTheme, uiArrangement: _oldArrangement, scheduledTasks: _oldScheduled, ...rest } = stored;
260
446
  await this.db
261
447
  .updateTable("orgs")
262
448
  .set({ settings: JSON.stringify({ ...rest, ...after }) })
@@ -281,6 +467,22 @@ OrgSettingsService = __decorate([
281
467
  __param(0, Inject(KernelDb)),
282
468
  __param(1, Inject(TransactionHost)),
283
469
  __param(2, Inject(OrgLock)),
284
- __param(3, Inject(KernelConfigService))
470
+ __param(3, Inject(KernelConfigService)),
471
+ __param(4, Optional()),
472
+ __param(4, Inject(KERNEL_LOGGER))
285
473
  ], OrgSettingsService);
286
474
  export { OrgSettingsService };
475
+ /**
476
+ * The one {@link OrgSettingsService}: the orgs module re-exports it, and the catalog imports it
477
+ * directly (approving a network request writes `sandboxEgress`) without a cycle through OrgsModule.
478
+ */
479
+ let OrgSettingsModule = class OrgSettingsModule {
480
+ };
481
+ OrgSettingsModule = __decorate([
482
+ Module({
483
+ imports: [OrgLockModule],
484
+ providers: [OrgSettingsService],
485
+ exports: [OrgSettingsService],
486
+ })
487
+ ], OrgSettingsModule);
488
+ 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 },
@@ -43,6 +43,7 @@ const SettingsInput = DEPLOYMENT_SETTINGS_PATCH.pick({
43
43
  uiTheme: true,
44
44
  uiArrangement: true,
45
45
  clientExtensions: true,
46
+ sandboxTiers: true,
46
47
  }).strict();
47
48
  const ProviderId = ModelProviderParams.shape.provider.describe("The model provider (models.platform-list), e.g. the plugin name of its extension.");
48
49
  /** `provider/model` of a purpose target, or null. */
@@ -182,17 +183,17 @@ let AdminOperations = class AdminOperations {
182
183
  }),
183
184
  defineOperation({
184
185
  id: "admin.settings",
185
- description: "Read the deployment settings (super admin): who may create orgs (orgCreation), the price currency, whether orgs may create their own connectors and skills, whether org admins may browse the marketplaces, and the per-org limit overrides keyed by org id (orgs.get reads one org's).",
186
+ description: "Read the deployment settings (super admin): who may create orgs (orgCreation), the price currency, whether orgs may create their own connectors and skills, whether org admins may browse the marketplaces, the per-org limit overrides keyed by org id (orgs.get reads one org's), and the platform's sandbox levels in effect (sandboxTiers: max, default, allowUnsandboxed, with sources: env, file or nacos are set in the startup config and read-only, db chosen in the admin console, default).",
186
187
  input: z.object({}).strict(),
187
188
  example: {},
188
189
  authz: { superAdmin: true },
189
190
  mirrors: ["GET /api/v1/admin/settings"],
190
191
  confirm: "read",
191
- run: () => this.settings.get(),
192
+ run: () => this.settings.view(),
192
193
  }),
193
194
  defineOperation({
194
195
  id: "admin.update-settings",
195
- description: "Change deployment settings (super admin); give only the keys to change: orgCreation (anyone or super_admin), priceCurrency (ISO 4217 code), customConnectors / customSkills (allow or deny: whether orgs may create their own instead of installing from the catalog), orgMarketplaceBrowse (true or false), uiTheme ({ id }: the platform default theme, a platform theme whose scope is all, or null for the built-in theme; it is what every org falls back to and what the sign-in pages show), uiArrangement ({ <slot>: { hidden, order } } for a list slot, { <slot>: { use } } for a single slot: the platform default arrangement of UI slots, with contribution keys <extension>/<contribution>; hidden ones are not listed but still open by URL; use names the contribution that replaces the built-in, null for the built-in; null for a slot removes its entry), clientExtensions ({ enabled?, disabled? }: the kill switch of plugin client code; enabled false stops every plugin's interface code in every browser, disabled is the whole list of plugins whose interface code alone is stopped while their tools keep working; read the current list from admin.settings first). Per-org limits are orgs.set-limits.",
196
+ description: "Change deployment settings (super admin); give only the keys to change: orgCreation (anyone or super_admin), priceCurrency (ISO 4217 code), customConnectors / customSkills (allow or deny: whether orgs may create their own instead of installing from the catalog), orgMarketplaceBrowse (true or false), uiTheme ({ id }: the platform default theme, a platform theme whose scope is all, or null for the built-in theme; it is what every org falls back to and what the sign-in pages show), uiArrangement ({ <slot>: { hidden, order } } for a list slot, { <slot>: { use } } for a single slot: the platform default arrangement of UI slots, with contribution keys <extension>/<contribution>; hidden ones are not listed but still open by URL; use names the contribution that replaces the built-in, null for the built-in; null for a slot removes its entry), clientExtensions ({ enabled?, disabled? }: the kill switch of plugin client code; enabled false stops every plugin's interface code in every browser, disabled is the whole list of plugins whose interface code alone is stopped while their tools keep working; read the current list from admin.settings first), sandboxTiers ({ max?, default?, allowUnsandboxed? }: the platform's sandbox levels strict < dev < open; null clears a field back to its default; through this tool they may only be lowered, never raised, and fields set in the startup config are read-only). Per-org limits are orgs.set-limits.",
196
197
  input: SettingsInput.refine((b) => Object.keys(b).length > 0, {
197
198
  message: "at least one setting is required",
198
199
  }),
@@ -207,6 +208,14 @@ let AdminOperations = class AdminOperations {
207
208
  code: "validation_failed",
208
209
  next: "Fix the values as the message says (admin.settings shows the current ones).",
209
210
  },
211
+ {
212
+ code: "deployment.sandbox_tier_not_allowed",
213
+ next: "Through this tool the platform's sandbox levels may only be lowered; to raise them, ask a super admin to change them in the admin console settings on the web.",
214
+ },
215
+ {
216
+ code: "deployment.setting_readonly",
217
+ next: "That sandbox level is set in the deployment's startup config (environment, file or Nacos; admin.settings shows the source); it can only be changed there, by the operator.",
218
+ },
210
219
  {
211
220
  code: "theme.not_available",
212
221
  next: "The platform default must be a platform theme whose scope is all, not disabled, and on in every organization. When the details list orgs, the theme is off in those organizations: tell the person their names, then choose another theme (plugins.components with kind theme lists them) or have it switched on in those organizations first; { id: null } sets the built-in theme.",
@@ -214,13 +223,27 @@ let AdminOperations = class AdminOperations {
214
223
  ],
215
224
  precheck: async (i) => {
216
225
  const r = DeploymentSettingsBody.safeParse(i);
217
- return r.success
226
+ if (!r.success) {
227
+ return {
228
+ ok: false,
229
+ code: "validation_failed",
230
+ message: r.error.issues[0]?.message ?? "",
231
+ };
232
+ }
233
+ const tiers = i.sandboxTiers;
234
+ return tiers === undefined
218
235
  ? { ok: true }
219
- : { ok: false, code: "validation_failed", message: r.error.issues[0]?.message ?? "" };
236
+ : precheckBy(() => this.settings.checkSandboxTiers(tiers, { onlyNarrows: true }));
220
237
  },
221
238
  summary: async (i) => {
222
- const { uiTheme, uiArrangement, clientExtensions, ...rest } = i;
239
+ const { uiTheme, uiArrangement, clientExtensions, sandboxTiers, ...rest } = i;
223
240
  const parts = Object.entries(rest).map(([k, v]) => `${k}=${String(v)}`);
241
+ if (sandboxTiers !== undefined) {
242
+ parts.push(`sandboxTiers=${Object.entries(sandboxTiers)
243
+ .filter(([, v]) => v !== undefined)
244
+ .map(([k, v]) => `${k}:${String(v)}`)
245
+ .join(" ")}`);
246
+ }
224
247
  const zh = [...parts];
225
248
  const en = [...parts];
226
249
  if (uiTheme !== undefined) {
@@ -253,7 +276,9 @@ let AdminOperations = class AdminOperations {
253
276
  en: `Change the deployment settings: ${en.join(", ")}`,
254
277
  };
255
278
  },
256
- run: (input, ctx) => this.settings.update(input, ctx.actorAccountId, undefined, viaAgent("admin.update-settings", ctx)),
279
+ run: (input, ctx) => this.settings.update(input, ctx.actorAccountId, undefined, viaAgent("admin.update-settings", ctx),
280
+ // a person raises the platform's sandbox levels, never an agent (dev tiers spec §2)
281
+ { onlyNarrows: true }),
257
282
  }),
258
283
  defineOperation({
259
284
  id: "models.platform-list",
@@ -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),