@capybara-hq/kernel 0.1.0-rc.21 → 0.1.0-rc.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/admin-agents.d.ts +16 -3
- package/dist/agents/admin-agents.js +65 -7
- package/dist/agents/agents.schemas.d.ts +24 -6
- package/dist/agents/agents.service.d.ts +4 -3
- package/dist/agents/agents.service.js +39 -26
- package/dist/agents/definition.d.ts +44 -6
- package/dist/agents/definition.js +86 -18
- package/dist/agents/from-template.d.ts +6 -3
- package/dist/agents/from-template.js +57 -44
- package/dist/agents/org-templates.js +97 -41
- package/dist/agents/templates.d.ts +6 -0
- package/dist/agents/templates.js +15 -1
- package/dist/catalog/admin-catalog.controller.d.ts +2 -4
- package/dist/catalog/admin-catalog.controller.js +2 -6
- package/dist/catalog/catalog.module.js +3 -0
- package/dist/catalog/claude/report.d.ts +9 -2
- package/dist/catalog/claude/report.js +1 -0
- package/dist/catalog/claude/translate.js +1 -1
- package/dist/catalog/component-promotion.js +1 -2
- package/dist/catalog/component-state.d.ts +13 -1
- package/dist/catalog/component-state.js +14 -5
- package/dist/catalog/marketplaces.service.d.ts +1 -1
- package/dist/catalog/marketplaces.service.js +2 -1
- package/dist/catalog/org-catalog.controller.d.ts +13 -3
- package/dist/catalog/org-catalog.controller.js +26 -4
- package/dist/catalog/org-catalog.schemas.d.ts +13 -0
- package/dist/catalog/org-catalog.schemas.js +34 -0
- package/dist/catalog/org-plugin-uploads.controller.js +0 -1
- package/dist/catalog/org-plugin-uploads.d.ts +0 -1
- package/dist/catalog/org-plugin-uploads.js +0 -3
- package/dist/catalog/org-plugin-uploads.schemas.d.ts +0 -1
- package/dist/catalog/org-plugin-uploads.schemas.js +0 -2
- package/dist/catalog/platform-components.d.ts +0 -1
- package/dist/catalog/platform-components.js +3 -8
- package/dist/catalog/plugin-import.d.ts +0 -8
- package/dist/catalog/plugin-import.js +4 -35
- package/dist/catalog/plugin-install.d.ts +2 -2
- package/dist/catalog/plugin-install.js +2 -2
- package/dist/catalog/plugin-package.d.ts +7 -0
- package/dist/catalog/plugin-package.js +27 -1
- package/dist/catalog/plugin-upgrade.d.ts +5 -6
- package/dist/catalog/plugin-upgrade.js +5 -23
- package/dist/catalog/requests.service.d.ts +66 -10
- package/dist/catalog/requests.service.js +196 -15
- package/dist/channels/actions.d.ts +2 -0
- package/dist/channels/actions.js +19 -4
- package/dist/channels/cards.d.ts +16 -2
- package/dist/channels/cards.js +40 -7
- package/dist/channels/channels.module.js +3 -0
- package/dist/channels/outbound.d.ts +22 -1
- package/dist/channels/outbound.js +116 -7
- package/dist/channels/phrases.d.ts +25 -0
- package/dist/channels/phrases.js +19 -0
- package/dist/channels/threads.d.ts +12 -0
- package/dist/channels/threads.js +34 -0
- package/dist/config/schema.d.ts +23 -1
- package/dist/config/schema.js +105 -5
- package/dist/conversations/conversations.service.d.ts +42 -9
- package/dist/conversations/conversations.service.js +57 -25
- package/dist/conversations/events.d.ts +106 -11
- package/dist/conversations/events.js +121 -18
- package/dist/conversations/message-context.d.ts +6 -3
- package/dist/conversations/message-context.js +14 -10
- package/dist/conversations/queue.d.ts +15 -2
- package/dist/conversations/queue.js +22 -3
- package/dist/conversations/queue.service.d.ts +12 -6
- package/dist/conversations/queue.service.js +36 -13
- package/dist/db/kernel-schema.d.ts +105 -3
- package/dist/devices/conversation-devices.d.ts +8 -1
- package/dist/devices/conversation-devices.js +8 -0
- package/dist/devices/devices.controller.d.ts +21 -4
- package/dist/devices/devices.controller.js +46 -8
- package/dist/devices/devices.schemas.d.ts +9 -1
- package/dist/devices/devices.schemas.js +11 -3
- package/dist/devices/devices.service.d.ts +176 -4
- package/dist/devices/devices.service.js +341 -6
- package/dist/devices/dto.d.ts +56 -3
- package/dist/devices/dto.js +51 -1
- package/dist/devices/public-suffix.d.ts +6 -0
- package/dist/devices/public-suffix.js +79 -0
- package/dist/errors.d.ts +46 -5
- package/dist/errors.js +46 -5
- package/dist/evals/reconstruct.d.ts +18 -13
- package/dist/evals/reconstruct.js +50 -36
- package/dist/evals/recorded-tools.d.ts +7 -7
- package/dist/evals/recorded-tools.js +4 -3
- package/dist/evals/trajectory.d.ts +6 -1
- package/dist/evals/trajectory.js +7 -0
- package/dist/extensions/package.js +2 -2
- package/dist/http/api.module.js +5 -0
- package/dist/inbox/inbox.module.js +2 -1
- package/dist/inbox/inbox.service.d.ts +19 -4
- package/dist/inbox/inbox.service.js +14 -3
- package/dist/infra/config/kernel-config.service.js +11 -0
- package/dist/infra/events/kernel-events.d.ts +2 -1
- package/dist/local-devices/device-policy.service.d.ts +65 -0
- package/dist/local-devices/device-policy.service.js +217 -0
- package/dist/local-devices/local-device-gateway.js +4 -1
- package/dist/local-devices/local-device-provider.d.ts +40 -3
- package/dist/local-devices/local-device-provider.js +109 -7
- package/dist/local-devices/local-devices.module.d.ts +2 -1
- package/dist/local-devices/local-devices.module.js +19 -3
- package/dist/local-devices/machines.d.ts +14 -3
- package/dist/local-devices/machines.js +41 -3
- package/dist/local-devices/network-grants.d.ts +104 -0
- package/dist/local-devices/network-grants.js +288 -0
- package/dist/local-devices/policy-summary.d.ts +20 -0
- package/dist/local-devices/policy-summary.js +63 -0
- package/dist/orgs/orgs.module.js +4 -10
- package/dist/orgs/roles.d.ts +1 -1
- package/dist/orgs/roles.js +4 -0
- package/dist/orgs/settings.controller.js +2 -1
- package/dist/orgs/settings.service.d.ts +164 -12
- package/dist/orgs/settings.service.js +212 -24
- package/dist/platform/exclusions.js +11 -1
- package/dist/platform/operation.js +4 -0
- package/dist/platform/ops/admin-items.ops.js +1 -1
- package/dist/platform/ops/agent-skills.ops.js +2 -2
- package/dist/platform/ops/agent-templates.ops.js +2 -2
- package/dist/platform/ops/agents.ops.js +11 -15
- package/dist/platform/ops/devices.ops.js +3 -1
- package/dist/platform/ops/inbox.ops.js +19 -0
- package/dist/platform/ops/org-settings.ops.js +14 -5
- package/dist/platform/ops/platform.ops.d.ts +9 -1
- package/dist/platform/ops/platform.ops.js +34 -12
- package/dist/platform/ops/requests.ops.js +3 -2
- package/dist/platform/ops/schedules.ops.d.ts +21 -0
- package/dist/platform/ops/schedules.ops.js +470 -0
- package/dist/platform/ops/skills.ops.js +3 -3
- package/dist/platform/platform-ops.module.d.ts +2 -1
- package/dist/platform/platform-ops.module.js +5 -0
- package/dist/platform/route-schemas.js +3 -0
- package/dist/roles/worker.module.js +3 -0
- package/dist/runtime/context/build.js +13 -4
- package/dist/runtime/context/compaction.js +6 -3
- package/dist/runtime/context/device-paths.d.ts +43 -21
- package/dist/runtime/context/device-paths.js +104 -21
- package/dist/runtime/context/run-context.d.ts +9 -0
- package/dist/runtime/context/run-context.js +8 -0
- package/dist/runtime/context/skills.d.ts +14 -1
- package/dist/runtime/context/skills.js +36 -5
- package/dist/runtime/maintenance.d.ts +4 -2
- package/dist/runtime/maintenance.js +16 -5
- package/dist/runtime/run-finish.d.ts +6 -1
- package/dist/runtime/run-finish.js +16 -2
- package/dist/runtime/run-queue.js +21 -2
- package/dist/runtime/run-worker.js +14 -33
- package/dist/runtime/runtime.module.js +3 -0
- package/dist/runtime/subagents/definition.d.ts +32 -14
- package/dist/runtime/subagents/definition.js +106 -109
- package/dist/runtime/subagents/dispatch.js +4 -3
- package/dist/runtime/subagents/preamble.d.ts +2 -1
- package/dist/runtime/subagents/preamble.js +2 -1
- package/dist/runtime/waits.js +14 -4
- package/dist/sandbox/sandbox.service.d.ts +120 -10
- package/dist/sandbox/sandbox.service.js +335 -28
- package/dist/sandbox/tiers.d.ts +86 -0
- package/dist/sandbox/tiers.js +96 -0
- package/dist/schedules/effective-settings.d.ts +16 -0
- package/dist/schedules/effective-settings.js +17 -0
- package/dist/schedules/errors.d.ts +8 -0
- package/dist/schedules/errors.js +8 -0
- package/dist/schedules/fire.d.ts +118 -0
- package/dist/schedules/fire.js +625 -0
- package/dist/schedules/grant-rules.d.ts +94 -0
- package/dist/schedules/grant-rules.js +126 -0
- package/dist/schedules/job-ids.d.ts +32 -0
- package/dist/schedules/job-ids.js +27 -0
- package/dist/schedules/pause.d.ts +9 -0
- package/dist/schedules/pause.js +33 -0
- package/dist/schedules/run-tracker.d.ts +81 -0
- package/dist/schedules/run-tracker.js +333 -0
- package/dist/schedules/scan.job.d.ts +64 -0
- package/dist/schedules/scan.job.js +303 -0
- package/dist/schedules/schedule-math.d.ts +31 -0
- package/dist/schedules/schedule-math.js +223 -0
- package/dist/schedules/scheduled-tasks.service.d.ts +351 -0
- package/dist/schedules/scheduled-tasks.service.js +1193 -0
- package/dist/schedules/schedules.controller.d.ts +68 -0
- package/dist/schedules/schedules.controller.js +242 -0
- package/dist/schedules/schedules.module.d.ts +16 -0
- package/dist/schedules/schedules.module.js +65 -0
- package/dist/schedules/schedules.schemas.d.ts +316 -0
- package/dist/schedules/schedules.schemas.js +186 -0
- package/dist/schedules/store.d.ts +81 -0
- package/dist/schedules/store.js +390 -0
- package/dist/schedules/store.module.d.ts +6 -0
- package/dist/schedules/store.module.js +21 -0
- package/dist/schedules/types.d.ts +108 -0
- package/dist/schedules/types.js +1 -0
- package/dist/schedules/unattended.d.ts +117 -0
- package/dist/schedules/unattended.js +228 -0
- package/dist/setup/oauth-token-probe.js +8 -4
- package/dist/skills/agent-skills.service.d.ts +4 -8
- package/dist/skills/agent-skills.service.js +18 -53
- package/dist/skills/catalog.d.ts +65 -62
- package/dist/skills/catalog.js +109 -206
- package/dist/skills/conversation-catalog.d.ts +3 -3
- package/dist/skills/conversation-catalog.js +5 -12
- package/dist/skills/invoke.d.ts +2 -2
- package/dist/skills/skills.controller.d.ts +3 -1
- package/dist/skills/skills.service.d.ts +38 -37
- package/dist/skills/skills.service.js +168 -107
- package/dist/skills/usable.d.ts +57 -8
- package/dist/skills/usable.js +172 -38
- package/dist/tools/approvals.controller.d.ts +4 -2
- package/dist/tools/approvals.controller.js +17 -2
- package/dist/tools/approvals.schemas.d.ts +1 -0
- package/dist/tools/approvals.schemas.js +3 -1
- package/dist/tools/approvals.service.d.ts +182 -7
- package/dist/tools/approvals.service.js +597 -13
- package/dist/tools/builtin-names.d.ts +25 -4
- package/dist/tools/builtin-names.js +45 -4
- package/dist/tools/builtins/builtin-tools.d.ts +8 -0
- package/dist/tools/builtins/builtin-tools.js +23 -2
- package/dist/tools/builtins/sandbox/bash.js +16 -7
- package/dist/tools/builtins/sandbox/common.d.ts +1 -1
- package/dist/tools/builtins/sandbox/common.js +33 -10
- package/dist/tools/builtins/sandbox/edit.js +7 -3
- package/dist/tools/builtins/sandbox/file-state.d.ts +31 -1
- package/dist/tools/builtins/sandbox/file-state.js +33 -3
- package/dist/tools/builtins/sandbox/write.js +8 -4
- package/dist/tools/builtins/schedules.d.ts +107 -0
- package/dist/tools/builtins/schedules.js +789 -0
- package/dist/tools/elicitation.d.ts +2 -0
- package/dist/tools/elicitation.js +1 -1
- package/dist/tools/fixed-ask.d.ts +7 -0
- package/dist/tools/fixed-ask.js +9 -0
- package/dist/tools/network-asks.d.ts +109 -0
- package/dist/tools/network-asks.js +221 -0
- package/dist/tools/pipeline.d.ts +53 -6
- package/dist/tools/pipeline.js +202 -13
- package/dist/tools/policy.d.ts +21 -8
- package/dist/tools/policy.js +63 -9
- package/dist/tools/registry.d.ts +10 -1
- package/dist/tools/tools.module.js +12 -0
- package/package.json +2 -2
- package/src/migrations/0113_scheduled_tasks.sql +108 -0
- package/src/migrations/0114_network_asks.sql +23 -0
- package/src/migrations/0115_network_requests.sql +28 -0
- package/src/migrations/0116_skills_default_on.sql +37 -0
- package/src/migrations/0117_sandbox_tiers.sql +11 -0
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { type SandboxLimits } from "@capybara-hq/plugin-sdk";
|
|
1
|
+
import { type SandboxLimits, type SandboxTier } from "@capybara-hq/plugin-sdk";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import type { KernelSchemaDb } from "../db/kernel-schema.js";
|
|
4
4
|
import { KernelConfigService } from "../infra/config/kernel-config.service.js";
|
|
5
5
|
import { KernelDb } from "../infra/database/kernel-db.js";
|
|
6
6
|
import { TransactionHost } from "../infra/database/transaction-host.js";
|
|
7
|
+
import type { KernelLogger } from "../log.js";
|
|
8
|
+
import { type EffectiveSandboxPolicy } from "../sandbox/tiers.js";
|
|
7
9
|
import { OrgLock } from "./org-lock.js";
|
|
8
10
|
/**
|
|
9
11
|
* 组织设置(`capybara.orgs.settings` jsonb)。读取宽松:缺失或非法的字段按默认值,未知键保留不动
|
|
@@ -14,7 +16,10 @@ import { OrgLock } from "./org-lock.js";
|
|
|
14
16
|
* `Read` / `Glob` / `Grep` 外都需要审批(执行时实时读取)。
|
|
15
17
|
* - `wakeOnBackgroundTask`:组织对 `runtime.wakeOnBackgroundTask` 的覆盖(缺省跟随部署),见 {@link effectiveWakeOnBackgroundTask}。
|
|
16
18
|
* - `sandboxEgress`:组织的沙箱出站(Ruling M19 / D6),缺省跟随部署的 `sandbox.limits.egress` + `allowHosts`。
|
|
17
|
-
*
|
|
19
|
+
* See `baseSandboxEgress` (sandbox/tiers.ts). 存量值非法时按 `none` 读(宁可断网,不悄悄放宽到部署默认)。
|
|
20
|
+
* - `sandboxTier`: the org's sandbox tier ceiling, default and unsandboxed switch (sandbox dev tiers
|
|
21
|
+
* spec §4.1), each absent = follow the platform (`sandbox.tiers`); never above the platform. An
|
|
22
|
+
* invalid stored value reads as `{ max: "strict" }` (narrower, never wider).
|
|
18
23
|
* - `platformTool`: the `Platform` builtin for the whole org (Platform tool spec §3): `on`,
|
|
19
24
|
* `read-only` (writes listed as unavailable) or `off` (default `on`).
|
|
20
25
|
* - `timeZone`: the org's default time zone (IANA; absent = UTC) for senders without one of their
|
|
@@ -38,6 +43,25 @@ export declare const SandboxEgressSettingSchema: z.ZodObject<{
|
|
|
38
43
|
allowHosts: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
39
44
|
}, z.core.$strict>;
|
|
40
45
|
export type SandboxEgressSetting = z.infer<typeof SandboxEgressSettingSchema>;
|
|
46
|
+
/**
|
|
47
|
+
* The org's sandbox tier setting (sandbox dev tiers spec §4.1): `max` the highest tier its devices
|
|
48
|
+
* may reach, `default` the tier of a device that chose none, `allowUnsandboxed` false forbids
|
|
49
|
+
* unsandboxed local devices. Each absent = follow the platform's `sandbox.tiers`.
|
|
50
|
+
*/
|
|
51
|
+
export declare const SandboxTierSettingSchema: z.ZodObject<{
|
|
52
|
+
max: z.ZodOptional<z.ZodEnum<{
|
|
53
|
+
dev: "dev";
|
|
54
|
+
open: "open";
|
|
55
|
+
strict: "strict";
|
|
56
|
+
}>>;
|
|
57
|
+
default: z.ZodOptional<z.ZodEnum<{
|
|
58
|
+
dev: "dev";
|
|
59
|
+
open: "open";
|
|
60
|
+
strict: "strict";
|
|
61
|
+
}>>;
|
|
62
|
+
allowUnsandboxed: z.ZodOptional<z.ZodBoolean>;
|
|
63
|
+
}, z.core.$strict>;
|
|
64
|
+
export type SandboxTierSetting = z.infer<typeof SandboxTierSettingSchema>;
|
|
41
65
|
/**
|
|
42
66
|
* The org's theme setting (themes spec §7): `id` a theme on in the org (a `platform_themes` id),
|
|
43
67
|
* null = follow the platform's default; `personal: "locked"` hides the personal picker in the org
|
|
@@ -53,6 +77,45 @@ export declare const OrgThemeSettingSchema: z.ZodObject<{
|
|
|
53
77
|
export type OrgThemeSetting = z.infer<typeof OrgThemeSettingSchema>;
|
|
54
78
|
/** The theme setting of an org that has none: follow the platform, personal picks allowed. */
|
|
55
79
|
export declare const DEFAULT_ORG_THEME: OrgThemeSetting;
|
|
80
|
+
/**
|
|
81
|
+
* Org settings of scheduled tasks (spec §9); every field reads leniently (invalid = default,
|
|
82
|
+
* except `forbiddenTools`, which keeps its valid entries).
|
|
83
|
+
*/
|
|
84
|
+
export declare const ScheduledTasksSettingsSchema: z.ZodObject<{
|
|
85
|
+
enabled: z.ZodCatch<z.ZodDefault<z.ZodBoolean>>;
|
|
86
|
+
minIntervalMinutes: z.ZodCatch<z.ZodDefault<z.ZodNumber>>;
|
|
87
|
+
maxTasksPerMember: z.ZodCatch<z.ZodDefault<z.ZodNumber>>;
|
|
88
|
+
maxTasksPerOrg: z.ZodCatch<z.ZodDefault<z.ZodNumber>>;
|
|
89
|
+
maxRunsPerHour: z.ZodCatch<z.ZodDefault<z.ZodNumber>>;
|
|
90
|
+
defaultExpiryDays: z.ZodCatch<z.ZodDefault<z.ZodNullable<z.ZodNumber>>>;
|
|
91
|
+
catchUpWindowMinutes: z.ZodCatch<z.ZodDefault<z.ZodNumber>>;
|
|
92
|
+
unattendedAsk: z.ZodCatch<z.ZodDefault<z.ZodEnum<{
|
|
93
|
+
fail: "fail";
|
|
94
|
+
wait: "wait";
|
|
95
|
+
}>>>;
|
|
96
|
+
pauseAfterFailures: z.ZodCatch<z.ZodDefault<z.ZodNumber>>;
|
|
97
|
+
forbiddenTools: z.ZodCatch<z.ZodPreprocess<z.ZodDefault<z.ZodArray<z.ZodString>>, unknown>>;
|
|
98
|
+
}, z.core.$strip>;
|
|
99
|
+
export type ScheduledTasksSettings = z.infer<typeof ScheduledTasksSettingsSchema>;
|
|
100
|
+
/**
|
|
101
|
+
* The PATCH form: a partial merge into the group (fields left out keep their stored value; see
|
|
102
|
+
* `OrgSettingsService.update`). A field given replaces that field whole, `forbiddenTools` too.
|
|
103
|
+
*/
|
|
104
|
+
export declare const ScheduledTasksPatchSchema: z.ZodObject<{
|
|
105
|
+
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
106
|
+
minIntervalMinutes: z.ZodOptional<z.ZodNumber>;
|
|
107
|
+
maxTasksPerMember: z.ZodOptional<z.ZodNumber>;
|
|
108
|
+
maxTasksPerOrg: z.ZodOptional<z.ZodNumber>;
|
|
109
|
+
maxRunsPerHour: z.ZodOptional<z.ZodNumber>;
|
|
110
|
+
defaultExpiryDays: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
111
|
+
catchUpWindowMinutes: z.ZodOptional<z.ZodNumber>;
|
|
112
|
+
unattendedAsk: z.ZodOptional<z.ZodEnum<{
|
|
113
|
+
fail: "fail";
|
|
114
|
+
wait: "wait";
|
|
115
|
+
}>>;
|
|
116
|
+
pauseAfterFailures: z.ZodOptional<z.ZodNumber>;
|
|
117
|
+
forbiddenTools: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
118
|
+
}, z.core.$strict>;
|
|
56
119
|
export declare const OrgSettingsSchema: z.ZodObject<{
|
|
57
120
|
toolSearch: z.ZodCatch<z.ZodDefault<z.ZodEnum<{
|
|
58
121
|
off: "off";
|
|
@@ -78,6 +141,19 @@ export declare const OrgSettingsSchema: z.ZodObject<{
|
|
|
78
141
|
}>;
|
|
79
142
|
allowHosts: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
80
143
|
}, z.core.$strict>>>;
|
|
144
|
+
sandboxTier: z.ZodCatch<z.ZodOptional<z.ZodObject<{
|
|
145
|
+
max: z.ZodOptional<z.ZodEnum<{
|
|
146
|
+
dev: "dev";
|
|
147
|
+
open: "open";
|
|
148
|
+
strict: "strict";
|
|
149
|
+
}>>;
|
|
150
|
+
default: z.ZodOptional<z.ZodEnum<{
|
|
151
|
+
dev: "dev";
|
|
152
|
+
open: "open";
|
|
153
|
+
strict: "strict";
|
|
154
|
+
}>>;
|
|
155
|
+
allowUnsandboxed: z.ZodOptional<z.ZodBoolean>;
|
|
156
|
+
}, z.core.$strict>>>;
|
|
81
157
|
wakeOnBackgroundTask: z.ZodCatch<z.ZodOptional<z.ZodBoolean>>;
|
|
82
158
|
timeZone: z.ZodCatch<z.ZodOptional<z.ZodString>>;
|
|
83
159
|
theme: z.ZodCatch<z.ZodOptional<z.ZodObject<{
|
|
@@ -88,6 +164,21 @@ export declare const OrgSettingsSchema: z.ZodObject<{
|
|
|
88
164
|
}>;
|
|
89
165
|
}, z.core.$strict>>>;
|
|
90
166
|
uiArrangement: z.ZodCatch<z.ZodOptional<z.ZodType<Partial<Record<"agent.detail.section" | "conversation.composer.frame" | "conversation.empty" | "conversation.panel" | "conversation.properties.section" | "conversation.tool-result.card" | "file.preview" | "org.page" | "org.settings.section" | "platform.page" | "shell.brand" | "shell.overlay", import("../ui/arrangement.js").UiArrangementEntry>>, unknown, z.core.$ZodTypeInternals<Partial<Record<"agent.detail.section" | "conversation.composer.frame" | "conversation.empty" | "conversation.panel" | "conversation.properties.section" | "conversation.tool-result.card" | "file.preview" | "org.page" | "org.settings.section" | "platform.page" | "shell.brand" | "shell.overlay", import("../ui/arrangement.js").UiArrangementEntry>>, unknown>>>>;
|
|
167
|
+
scheduledTasks: z.ZodCatch<z.ZodPrefault<z.ZodObject<{
|
|
168
|
+
enabled: z.ZodCatch<z.ZodDefault<z.ZodBoolean>>;
|
|
169
|
+
minIntervalMinutes: z.ZodCatch<z.ZodDefault<z.ZodNumber>>;
|
|
170
|
+
maxTasksPerMember: z.ZodCatch<z.ZodDefault<z.ZodNumber>>;
|
|
171
|
+
maxTasksPerOrg: z.ZodCatch<z.ZodDefault<z.ZodNumber>>;
|
|
172
|
+
maxRunsPerHour: z.ZodCatch<z.ZodDefault<z.ZodNumber>>;
|
|
173
|
+
defaultExpiryDays: z.ZodCatch<z.ZodDefault<z.ZodNullable<z.ZodNumber>>>;
|
|
174
|
+
catchUpWindowMinutes: z.ZodCatch<z.ZodDefault<z.ZodNumber>>;
|
|
175
|
+
unattendedAsk: z.ZodCatch<z.ZodDefault<z.ZodEnum<{
|
|
176
|
+
fail: "fail";
|
|
177
|
+
wait: "wait";
|
|
178
|
+
}>>>;
|
|
179
|
+
pauseAfterFailures: z.ZodCatch<z.ZodDefault<z.ZodNumber>>;
|
|
180
|
+
forbiddenTools: z.ZodCatch<z.ZodPreprocess<z.ZodDefault<z.ZodArray<z.ZodString>>, unknown>>;
|
|
181
|
+
}, z.core.$strip>>>;
|
|
91
182
|
}, z.core.$strip>;
|
|
92
183
|
export type OrgSettings = z.infer<typeof OrgSettingsSchema>;
|
|
93
184
|
/** PATCH 体:只改给出的字段;未知字段 → 400。 */
|
|
@@ -117,6 +208,19 @@ export declare const OrgSettingsPatchSchema: z.ZodObject<{
|
|
|
117
208
|
}>;
|
|
118
209
|
allowHosts: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
119
210
|
}, z.core.$strict>>>;
|
|
211
|
+
sandboxTier: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
212
|
+
max: z.ZodOptional<z.ZodEnum<{
|
|
213
|
+
dev: "dev";
|
|
214
|
+
open: "open";
|
|
215
|
+
strict: "strict";
|
|
216
|
+
}>>;
|
|
217
|
+
default: z.ZodOptional<z.ZodEnum<{
|
|
218
|
+
dev: "dev";
|
|
219
|
+
open: "open";
|
|
220
|
+
strict: "strict";
|
|
221
|
+
}>>;
|
|
222
|
+
allowUnsandboxed: z.ZodOptional<z.ZodBoolean>;
|
|
223
|
+
}, z.core.$strict>>>;
|
|
120
224
|
timeZone: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
121
225
|
theme: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
122
226
|
id: z.ZodNullable<z.ZodString>;
|
|
@@ -126,15 +230,23 @@ export declare const OrgSettingsPatchSchema: z.ZodObject<{
|
|
|
126
230
|
}>;
|
|
127
231
|
}, z.core.$strict>>>;
|
|
128
232
|
uiArrangement: z.ZodOptional<z.ZodType<Partial<Record<"agent.detail.section" | "conversation.composer.frame" | "conversation.empty" | "conversation.panel" | "conversation.properties.section" | "conversation.tool-result.card" | "file.preview" | "org.page" | "org.settings.section" | "platform.page" | "shell.brand" | "shell.overlay", import("../ui/arrangement.js").UiArrangementEntry | null>>, unknown, z.core.$ZodTypeInternals<Partial<Record<"agent.detail.section" | "conversation.composer.frame" | "conversation.empty" | "conversation.panel" | "conversation.properties.section" | "conversation.tool-result.card" | "file.preview" | "org.page" | "org.settings.section" | "platform.page" | "shell.brand" | "shell.overlay", import("../ui/arrangement.js").UiArrangementEntry | null>>, unknown>>>;
|
|
233
|
+
scheduledTasks: z.ZodOptional<z.ZodObject<{
|
|
234
|
+
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
235
|
+
minIntervalMinutes: z.ZodOptional<z.ZodNumber>;
|
|
236
|
+
maxTasksPerMember: z.ZodOptional<z.ZodNumber>;
|
|
237
|
+
maxTasksPerOrg: z.ZodOptional<z.ZodNumber>;
|
|
238
|
+
maxRunsPerHour: z.ZodOptional<z.ZodNumber>;
|
|
239
|
+
defaultExpiryDays: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
240
|
+
catchUpWindowMinutes: z.ZodOptional<z.ZodNumber>;
|
|
241
|
+
unattendedAsk: z.ZodOptional<z.ZodEnum<{
|
|
242
|
+
fail: "fail";
|
|
243
|
+
wait: "wait";
|
|
244
|
+
}>>;
|
|
245
|
+
pauseAfterFailures: z.ZodOptional<z.ZodNumber>;
|
|
246
|
+
forbiddenTools: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
247
|
+
}, z.core.$strict>>;
|
|
129
248
|
}, z.core.$strict>;
|
|
130
249
|
export type OrgSettingsPatch = z.infer<typeof OrgSettingsPatchSchema>;
|
|
131
|
-
/** 部署的沙箱出站(`sandbox.limits.egress` + `allowHosts`)。 */
|
|
132
|
-
export type DeploymentEgress = Pick<SandboxLimits, "egress" | "allowHosts">;
|
|
133
|
-
/**
|
|
134
|
-
* 组织实际生效的沙箱出站:没有组织设置 → 部署默认;`none` → 只有回环;`allowlist` → 组织的白名单
|
|
135
|
-
* (省略时用部署的),但部署是 `none` 时仍为 `none`(组织永远不比部署放得更开,部署改成 none 后存量设置也随之收紧)。
|
|
136
|
-
*/
|
|
137
|
-
export declare function effectiveSandboxEgress(deployment: DeploymentEgress, org: SandboxEgressSetting | undefined): DeploymentEgress;
|
|
138
250
|
/** Whether a finished background task wakes an idle conversation: the org's setting, else the deployment's. */
|
|
139
251
|
export declare function effectiveWakeOnBackgroundTask(deployment: boolean, org: OrgSettings["wakeOnBackgroundTask"]): boolean;
|
|
140
252
|
/**
|
|
@@ -154,14 +266,23 @@ export interface OrgSandboxLimits {
|
|
|
154
266
|
/** 设置页展示用的部署默认值(`GET` 一并返回)。 */
|
|
155
267
|
export interface OrgSettingsDeployment {
|
|
156
268
|
sandboxEgress: {
|
|
157
|
-
mode:
|
|
269
|
+
mode: "none" | "allowlist";
|
|
158
270
|
allowHosts: string[];
|
|
159
271
|
};
|
|
272
|
+
/** The platform's tiers (`sandbox.tiers`) and the dev hosts the dev tier adds. */
|
|
273
|
+
sandboxTiers: {
|
|
274
|
+
max: SandboxTier;
|
|
275
|
+
default: SandboxTier;
|
|
276
|
+
allowUnsandboxed: boolean;
|
|
277
|
+
devHosts: string[];
|
|
278
|
+
};
|
|
160
279
|
}
|
|
161
280
|
/** 把存储的 jsonb 解析为设置(宽松,见 {@link OrgSettingsSchema})。 */
|
|
162
281
|
export declare function parseOrgSettings(raw: unknown): OrgSettings;
|
|
163
282
|
/** 读组织设置(每次实时读 PG,多副本下改动立即生效);组织不存在或已删除按默认值。 */
|
|
164
283
|
export declare function readOrgSettings(db: KernelSchemaDb, orgId: string): Promise<OrgSettings>;
|
|
284
|
+
/** Whether a stored settings object holds a `sandboxTier` this version reads as `{ max: "strict" }`. */
|
|
285
|
+
export declare function invalidStoredSandboxTier(raw: unknown): boolean;
|
|
165
286
|
/** 组织设置服务:读取与修改(修改需 `org.update`,锁内实时复核角色,写审计)。 */
|
|
166
287
|
export declare class OrgSettingsService {
|
|
167
288
|
private readonly db;
|
|
@@ -169,9 +290,12 @@ export declare class OrgSettingsService {
|
|
|
169
290
|
private readonly orgLock;
|
|
170
291
|
/** The deployment config (`sandboxEgress` may not exceed the deployment's). */
|
|
171
292
|
private readonly config;
|
|
293
|
+
private readonly log?;
|
|
172
294
|
constructor(db: KernelDb, txHost: TransactionHost, orgLock: OrgLock,
|
|
173
295
|
/** The deployment config (`sandboxEgress` may not exceed the deployment's). */
|
|
174
|
-
config: Pick<KernelConfigService, "get"
|
|
296
|
+
config: Pick<KernelConfigService, "get">, log?: KernelLogger | undefined);
|
|
297
|
+
/** Parses stored settings, warning when an invalid `sandboxTier` narrows to `{ max: "strict" }`. */
|
|
298
|
+
private parseStored;
|
|
175
299
|
/** 部署默认值(设置页展示「跟随部署」时的实际取值)。 */
|
|
176
300
|
deployment(): OrgSettingsDeployment;
|
|
177
301
|
/** 组织实际生效的沙箱 cpu / 内存(只读;由超级管理员在部署设置里覆盖)。 */
|
|
@@ -181,6 +305,34 @@ export declare class OrgSettingsService {
|
|
|
181
305
|
check(patch: OrgSettingsPatch): void;
|
|
182
306
|
update(orgId: string, patch: OrgSettingsPatch, actorAccountId: string, actorTokenId?: string,
|
|
183
307
|
/** Merged into the audit detail (the Platform tool's `via: "agent"`). */
|
|
184
|
-
auditDetail?: Record<string, unknown
|
|
308
|
+
auditDetail?: Record<string, unknown>,
|
|
309
|
+
/**
|
|
310
|
+
* `onlyNarrows` (the Platform tool, API tokens): refuse a `sandboxTier` or `sandboxEgress`
|
|
311
|
+
* change that widens (see {@link assertOnlyNarrows}); only a person in a session widens.
|
|
312
|
+
*/
|
|
313
|
+
opts?: {
|
|
314
|
+
onlyNarrows?: boolean;
|
|
315
|
+
}): Promise<OrgSettings>;
|
|
316
|
+
/** The effective sandbox policy for the org's settings and a device's own tier (null = none). */
|
|
317
|
+
sandboxPolicy(settings: OrgSettings, deviceTier: SandboxTier | null): EffectiveSandboxPolicy;
|
|
318
|
+
/**
|
|
319
|
+
* Refuses a patch that widens the org's sandbox (sandbox dev tiers spec §2, §7.2: only a person
|
|
320
|
+
* widens it, never the Platform tool or an API token):
|
|
321
|
+
* - `sandboxTier`: the effective ceiling, default or unsandboxed switch rises, or a stored cap
|
|
322
|
+
* loosens (an absent `max` / `default` follows the platform, so it ranks above every tier and
|
|
323
|
+
* clearing a cap that equals today's platform value is a widening; an explicit
|
|
324
|
+
* `allowUnsandboxed: false` stays false). A cleared `default` under a new `max` no higher than
|
|
325
|
+
* the old default is no loosening: the default never exceeds the ceiling. The value is
|
|
326
|
+
* replaced whole, so the refusal names the fields the patch would clear;
|
|
327
|
+
* - `sandboxEgress`: the base list grows: from none to anything else, or to hosts (the
|
|
328
|
+
* deployment's when the org gives none) not all in the current list.
|
|
329
|
+
*/
|
|
330
|
+
assertOnlyNarrows(before: OrgSettings, patch: OrgSettingsPatch): void;
|
|
185
331
|
private updateIn;
|
|
186
332
|
}
|
|
333
|
+
/**
|
|
334
|
+
* The one {@link OrgSettingsService}: the orgs module re-exports it, and the catalog imports it
|
|
335
|
+
* directly (approving a network request writes `sandboxEgress`) without a cycle through OrgsModule.
|
|
336
|
+
*/
|
|
337
|
+
export declare class OrgSettingsModule {
|
|
338
|
+
}
|
|
@@ -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
|
-
*
|
|
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
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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 };
|