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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (242) hide show
  1. package/dist/agents/admin-agents.d.ts +16 -3
  2. package/dist/agents/admin-agents.js +65 -7
  3. package/dist/agents/agents.schemas.d.ts +24 -6
  4. package/dist/agents/agents.service.d.ts +4 -3
  5. package/dist/agents/agents.service.js +39 -26
  6. package/dist/agents/definition.d.ts +44 -6
  7. package/dist/agents/definition.js +86 -18
  8. package/dist/agents/from-template.d.ts +6 -3
  9. package/dist/agents/from-template.js +57 -44
  10. package/dist/agents/org-templates.js +97 -41
  11. package/dist/agents/templates.d.ts +6 -0
  12. package/dist/agents/templates.js +15 -1
  13. package/dist/catalog/admin-catalog.controller.d.ts +2 -4
  14. package/dist/catalog/admin-catalog.controller.js +2 -6
  15. package/dist/catalog/catalog.module.js +3 -0
  16. package/dist/catalog/claude/report.d.ts +9 -2
  17. package/dist/catalog/claude/report.js +1 -0
  18. package/dist/catalog/claude/translate.js +1 -1
  19. package/dist/catalog/component-promotion.js +1 -2
  20. package/dist/catalog/component-state.d.ts +13 -1
  21. package/dist/catalog/component-state.js +14 -5
  22. package/dist/catalog/marketplaces.service.d.ts +1 -1
  23. package/dist/catalog/marketplaces.service.js +2 -1
  24. package/dist/catalog/org-catalog.controller.d.ts +13 -3
  25. package/dist/catalog/org-catalog.controller.js +26 -4
  26. package/dist/catalog/org-catalog.schemas.d.ts +13 -0
  27. package/dist/catalog/org-catalog.schemas.js +34 -0
  28. package/dist/catalog/org-plugin-uploads.controller.js +0 -1
  29. package/dist/catalog/org-plugin-uploads.d.ts +0 -1
  30. package/dist/catalog/org-plugin-uploads.js +0 -3
  31. package/dist/catalog/org-plugin-uploads.schemas.d.ts +0 -1
  32. package/dist/catalog/org-plugin-uploads.schemas.js +0 -2
  33. package/dist/catalog/platform-components.d.ts +0 -1
  34. package/dist/catalog/platform-components.js +3 -8
  35. package/dist/catalog/plugin-import.d.ts +0 -8
  36. package/dist/catalog/plugin-import.js +4 -35
  37. package/dist/catalog/plugin-install.d.ts +2 -2
  38. package/dist/catalog/plugin-install.js +2 -2
  39. package/dist/catalog/plugin-package.d.ts +7 -0
  40. package/dist/catalog/plugin-package.js +27 -1
  41. package/dist/catalog/plugin-upgrade.d.ts +5 -6
  42. package/dist/catalog/plugin-upgrade.js +5 -23
  43. package/dist/catalog/requests.service.d.ts +66 -10
  44. package/dist/catalog/requests.service.js +196 -15
  45. package/dist/channels/actions.d.ts +2 -0
  46. package/dist/channels/actions.js +19 -4
  47. package/dist/channels/cards.d.ts +16 -2
  48. package/dist/channels/cards.js +40 -7
  49. package/dist/channels/channels.module.js +3 -0
  50. package/dist/channels/outbound.d.ts +22 -1
  51. package/dist/channels/outbound.js +116 -7
  52. package/dist/channels/phrases.d.ts +25 -0
  53. package/dist/channels/phrases.js +19 -0
  54. package/dist/channels/threads.d.ts +12 -0
  55. package/dist/channels/threads.js +34 -0
  56. package/dist/config/schema.d.ts +23 -1
  57. package/dist/config/schema.js +105 -5
  58. package/dist/conversations/conversations.service.d.ts +42 -9
  59. package/dist/conversations/conversations.service.js +57 -25
  60. package/dist/conversations/events.d.ts +106 -11
  61. package/dist/conversations/events.js +121 -18
  62. package/dist/conversations/message-context.d.ts +6 -3
  63. package/dist/conversations/message-context.js +14 -10
  64. package/dist/conversations/queue.d.ts +15 -2
  65. package/dist/conversations/queue.js +22 -3
  66. package/dist/conversations/queue.service.d.ts +12 -6
  67. package/dist/conversations/queue.service.js +36 -13
  68. package/dist/db/kernel-schema.d.ts +105 -3
  69. package/dist/devices/conversation-devices.d.ts +8 -1
  70. package/dist/devices/conversation-devices.js +8 -0
  71. package/dist/devices/devices.controller.d.ts +21 -4
  72. package/dist/devices/devices.controller.js +46 -8
  73. package/dist/devices/devices.schemas.d.ts +9 -1
  74. package/dist/devices/devices.schemas.js +11 -3
  75. package/dist/devices/devices.service.d.ts +176 -4
  76. package/dist/devices/devices.service.js +341 -6
  77. package/dist/devices/dto.d.ts +56 -3
  78. package/dist/devices/dto.js +51 -1
  79. package/dist/devices/public-suffix.d.ts +6 -0
  80. package/dist/devices/public-suffix.js +79 -0
  81. package/dist/errors.d.ts +46 -5
  82. package/dist/errors.js +46 -5
  83. package/dist/evals/reconstruct.d.ts +18 -13
  84. package/dist/evals/reconstruct.js +50 -36
  85. package/dist/evals/recorded-tools.d.ts +7 -7
  86. package/dist/evals/recorded-tools.js +4 -3
  87. package/dist/evals/trajectory.d.ts +6 -1
  88. package/dist/evals/trajectory.js +7 -0
  89. package/dist/extensions/package.js +2 -2
  90. package/dist/http/api.module.js +5 -0
  91. package/dist/inbox/inbox.module.js +2 -1
  92. package/dist/inbox/inbox.service.d.ts +19 -4
  93. package/dist/inbox/inbox.service.js +14 -3
  94. package/dist/infra/config/kernel-config.service.js +11 -0
  95. package/dist/infra/events/kernel-events.d.ts +2 -1
  96. package/dist/local-devices/device-policy.service.d.ts +65 -0
  97. package/dist/local-devices/device-policy.service.js +217 -0
  98. package/dist/local-devices/local-device-gateway.js +4 -1
  99. package/dist/local-devices/local-device-provider.d.ts +40 -3
  100. package/dist/local-devices/local-device-provider.js +109 -7
  101. package/dist/local-devices/local-devices.module.d.ts +2 -1
  102. package/dist/local-devices/local-devices.module.js +19 -3
  103. package/dist/local-devices/machines.d.ts +14 -3
  104. package/dist/local-devices/machines.js +41 -3
  105. package/dist/local-devices/network-grants.d.ts +104 -0
  106. package/dist/local-devices/network-grants.js +288 -0
  107. package/dist/local-devices/policy-summary.d.ts +20 -0
  108. package/dist/local-devices/policy-summary.js +63 -0
  109. package/dist/orgs/orgs.module.js +4 -10
  110. package/dist/orgs/roles.d.ts +1 -1
  111. package/dist/orgs/roles.js +4 -0
  112. package/dist/orgs/settings.controller.js +2 -1
  113. package/dist/orgs/settings.service.d.ts +164 -12
  114. package/dist/orgs/settings.service.js +212 -24
  115. package/dist/platform/exclusions.js +11 -1
  116. package/dist/platform/operation.js +4 -0
  117. package/dist/platform/ops/admin-items.ops.js +1 -1
  118. package/dist/platform/ops/agent-skills.ops.js +2 -2
  119. package/dist/platform/ops/agent-templates.ops.js +2 -2
  120. package/dist/platform/ops/agents.ops.js +11 -15
  121. package/dist/platform/ops/devices.ops.js +3 -1
  122. package/dist/platform/ops/inbox.ops.js +19 -0
  123. package/dist/platform/ops/org-settings.ops.js +14 -5
  124. package/dist/platform/ops/platform.ops.d.ts +9 -1
  125. package/dist/platform/ops/platform.ops.js +34 -12
  126. package/dist/platform/ops/requests.ops.js +3 -2
  127. package/dist/platform/ops/schedules.ops.d.ts +21 -0
  128. package/dist/platform/ops/schedules.ops.js +470 -0
  129. package/dist/platform/ops/skills.ops.js +3 -3
  130. package/dist/platform/platform-ops.module.d.ts +2 -1
  131. package/dist/platform/platform-ops.module.js +5 -0
  132. package/dist/platform/route-schemas.js +3 -0
  133. package/dist/roles/worker.module.js +3 -0
  134. package/dist/runtime/context/build.js +13 -4
  135. package/dist/runtime/context/compaction.js +6 -3
  136. package/dist/runtime/context/device-paths.d.ts +43 -21
  137. package/dist/runtime/context/device-paths.js +104 -21
  138. package/dist/runtime/context/run-context.d.ts +9 -0
  139. package/dist/runtime/context/run-context.js +8 -0
  140. package/dist/runtime/context/skills.d.ts +14 -1
  141. package/dist/runtime/context/skills.js +36 -5
  142. package/dist/runtime/maintenance.d.ts +4 -2
  143. package/dist/runtime/maintenance.js +16 -5
  144. package/dist/runtime/run-finish.d.ts +6 -1
  145. package/dist/runtime/run-finish.js +16 -2
  146. package/dist/runtime/run-queue.js +21 -2
  147. package/dist/runtime/run-worker.js +14 -33
  148. package/dist/runtime/runtime.module.js +3 -0
  149. package/dist/runtime/subagents/definition.d.ts +32 -14
  150. package/dist/runtime/subagents/definition.js +106 -109
  151. package/dist/runtime/subagents/dispatch.js +4 -3
  152. package/dist/runtime/subagents/preamble.d.ts +2 -1
  153. package/dist/runtime/subagents/preamble.js +2 -1
  154. package/dist/runtime/waits.js +14 -4
  155. package/dist/sandbox/sandbox.service.d.ts +120 -10
  156. package/dist/sandbox/sandbox.service.js +335 -28
  157. package/dist/sandbox/tiers.d.ts +86 -0
  158. package/dist/sandbox/tiers.js +96 -0
  159. package/dist/schedules/effective-settings.d.ts +16 -0
  160. package/dist/schedules/effective-settings.js +17 -0
  161. package/dist/schedules/errors.d.ts +8 -0
  162. package/dist/schedules/errors.js +8 -0
  163. package/dist/schedules/fire.d.ts +118 -0
  164. package/dist/schedules/fire.js +625 -0
  165. package/dist/schedules/grant-rules.d.ts +94 -0
  166. package/dist/schedules/grant-rules.js +126 -0
  167. package/dist/schedules/job-ids.d.ts +32 -0
  168. package/dist/schedules/job-ids.js +27 -0
  169. package/dist/schedules/pause.d.ts +9 -0
  170. package/dist/schedules/pause.js +33 -0
  171. package/dist/schedules/run-tracker.d.ts +81 -0
  172. package/dist/schedules/run-tracker.js +333 -0
  173. package/dist/schedules/scan.job.d.ts +64 -0
  174. package/dist/schedules/scan.job.js +303 -0
  175. package/dist/schedules/schedule-math.d.ts +31 -0
  176. package/dist/schedules/schedule-math.js +223 -0
  177. package/dist/schedules/scheduled-tasks.service.d.ts +351 -0
  178. package/dist/schedules/scheduled-tasks.service.js +1193 -0
  179. package/dist/schedules/schedules.controller.d.ts +68 -0
  180. package/dist/schedules/schedules.controller.js +242 -0
  181. package/dist/schedules/schedules.module.d.ts +16 -0
  182. package/dist/schedules/schedules.module.js +65 -0
  183. package/dist/schedules/schedules.schemas.d.ts +316 -0
  184. package/dist/schedules/schedules.schemas.js +186 -0
  185. package/dist/schedules/store.d.ts +81 -0
  186. package/dist/schedules/store.js +390 -0
  187. package/dist/schedules/store.module.d.ts +6 -0
  188. package/dist/schedules/store.module.js +21 -0
  189. package/dist/schedules/types.d.ts +108 -0
  190. package/dist/schedules/types.js +1 -0
  191. package/dist/schedules/unattended.d.ts +117 -0
  192. package/dist/schedules/unattended.js +228 -0
  193. package/dist/setup/oauth-token-probe.js +8 -4
  194. package/dist/skills/agent-skills.service.d.ts +4 -8
  195. package/dist/skills/agent-skills.service.js +18 -53
  196. package/dist/skills/catalog.d.ts +65 -62
  197. package/dist/skills/catalog.js +109 -206
  198. package/dist/skills/conversation-catalog.d.ts +3 -3
  199. package/dist/skills/conversation-catalog.js +5 -12
  200. package/dist/skills/invoke.d.ts +2 -2
  201. package/dist/skills/skills.controller.d.ts +3 -1
  202. package/dist/skills/skills.service.d.ts +38 -37
  203. package/dist/skills/skills.service.js +168 -107
  204. package/dist/skills/usable.d.ts +57 -8
  205. package/dist/skills/usable.js +172 -38
  206. package/dist/tools/approvals.controller.d.ts +4 -2
  207. package/dist/tools/approvals.controller.js +17 -2
  208. package/dist/tools/approvals.schemas.d.ts +1 -0
  209. package/dist/tools/approvals.schemas.js +3 -1
  210. package/dist/tools/approvals.service.d.ts +182 -7
  211. package/dist/tools/approvals.service.js +597 -13
  212. package/dist/tools/builtin-names.d.ts +25 -4
  213. package/dist/tools/builtin-names.js +45 -4
  214. package/dist/tools/builtins/builtin-tools.d.ts +8 -0
  215. package/dist/tools/builtins/builtin-tools.js +23 -2
  216. package/dist/tools/builtins/sandbox/bash.js +16 -7
  217. package/dist/tools/builtins/sandbox/common.d.ts +1 -1
  218. package/dist/tools/builtins/sandbox/common.js +33 -10
  219. package/dist/tools/builtins/sandbox/edit.js +7 -3
  220. package/dist/tools/builtins/sandbox/file-state.d.ts +31 -1
  221. package/dist/tools/builtins/sandbox/file-state.js +33 -3
  222. package/dist/tools/builtins/sandbox/write.js +8 -4
  223. package/dist/tools/builtins/schedules.d.ts +107 -0
  224. package/dist/tools/builtins/schedules.js +789 -0
  225. package/dist/tools/elicitation.d.ts +2 -0
  226. package/dist/tools/elicitation.js +1 -1
  227. package/dist/tools/fixed-ask.d.ts +7 -0
  228. package/dist/tools/fixed-ask.js +9 -0
  229. package/dist/tools/network-asks.d.ts +109 -0
  230. package/dist/tools/network-asks.js +221 -0
  231. package/dist/tools/pipeline.d.ts +53 -6
  232. package/dist/tools/pipeline.js +202 -13
  233. package/dist/tools/policy.d.ts +21 -8
  234. package/dist/tools/policy.js +63 -9
  235. package/dist/tools/registry.d.ts +10 -1
  236. package/dist/tools/tools.module.js +12 -0
  237. package/package.json +2 -2
  238. package/src/migrations/0113_scheduled_tasks.sql +108 -0
  239. package/src/migrations/0114_network_asks.sql +23 -0
  240. package/src/migrations/0115_network_requests.sql +28 -0
  241. package/src/migrations/0116_skills_default_on.sql +37 -0
  242. package/src/migrations/0117_sandbox_tiers.sql +11 -0
@@ -0,0 +1,288 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
8
+ return function (target, key) { decorator(target, key, paramIndex); }
9
+ };
10
+ import { validateAllowHost, } from "@capybara-hq/plugin-sdk";
11
+ import { Inject, Injectable } from "@nestjs/common";
12
+ import { sql } from "kysely";
13
+ import { KernelDb } from "../infra/database/kernel-db.js";
14
+ import { budgetRunOf } from "../runtime/subagents/limits.js";
15
+ import { appendMissingGrant, taskGrantsRunOf, unattendedTierOf } from "../schedules/unattended.js";
16
+ import { deviceFeaturesOf, isolationOf } from "./policy-summary.js";
17
+ /** The most hosts one exec's grants carry (the device protocol's limit). */
18
+ export const MAX_GRANT_HOSTS = 100;
19
+ /** The key prefix of a scheduled task's network grant (`schedules/grant-rules.ts`). */
20
+ const NETWORK_KEY_PREFIX = "network:";
21
+ /** The statuses of a run that has not ended: its run grants still apply. */
22
+ const LIVE_RUN = ["queued", "running", "waiting"];
23
+ /** The statuses of a network ask that refuse its hosts for the rest of the run tree (§6.8). */
24
+ const REFUSING = new Set(["denied", "expired", "skipped", "cancelled"]);
25
+ /** An exact host a grant may carry: no wildcard, valid. */
26
+ const exactHost = (h) => typeof h === "string" && !h.startsWith("*.") && validateAllowHost(h).length === 0;
27
+ /** `detail.deviceId` of a network ask: the device it asked about. */
28
+ function deviceOf(detail) {
29
+ const d = detail?.deviceId;
30
+ return typeof d === "string" ? d : undefined;
31
+ }
32
+ /** `detail.grantedHosts` of a decided network ask: what the decision allowed (absent: none). */
33
+ function grantedOf(detail) {
34
+ const g = detail?.grantedHosts;
35
+ return Array.isArray(g) ? g.filter(exactHost) : [];
36
+ }
37
+ /** `detail.hosts[].host` of a network ask: what it asked about. */
38
+ function askedOf(detail) {
39
+ const hs = detail?.hosts;
40
+ if (!Array.isArray(hs))
41
+ return [];
42
+ return hs.flatMap((h) => {
43
+ const host = h?.host;
44
+ return typeof host === "string" ? [host] : [];
45
+ });
46
+ }
47
+ /**
48
+ * Network hosts the kernel allows a run beyond a device's local policy (network ask-to-allow spec
49
+ * §6.3): the hosts of the run tree's approved `network` asks with scope `once` (「这次允许」), read
50
+ * from PG for every exec, so a grant decided on one replica reaches a command on any other. A run
51
+ * tree is the runs of one budget window (`runs.budget_run_id` lineage): a sub-agent's run sees the
52
+ * root's grants, another run tree never does. The hosts a decision allowed are its
53
+ * `detail.grantedHosts`; a decision without them grants nothing. In an unattended run of a
54
+ * scheduled task, the task's `network:<host>` grants (spec §8.1) join them.
55
+ */
56
+ let NetworkGrants = class NetworkGrants {
57
+ db;
58
+ constructor(db) {
59
+ this.db = db;
60
+ }
61
+ /**
62
+ * Hosts allowed for this exec on `deviceId`: the run tree's once-approved network asks in
63
+ * `orgId` about that device, while the tree's root run has not ended (a grant ends with its run;
64
+ * a later run of the same budget window does not inherit it), with the root run as their source
65
+ * and the latest decider as its account; and in an unattended run of a scheduled task, the
66
+ * task's network grants (spec §8.1) when the device is its creator's, with the task as their
67
+ * source and the creator as its account. Both together are one grant: the hosts of both, the
68
+ * task as the source. Undefined when none. The device applies them only when its owner allows
69
+ * remote grants and only within the org's limits.
70
+ */
71
+ async forExec(input) {
72
+ const run = await this.runGrants(input);
73
+ const task = await this.taskGrants(input);
74
+ if (task === undefined)
75
+ return run;
76
+ if (run === undefined)
77
+ return task;
78
+ // one exec frame carries one source: the task's, the standing approval behind an unattended
79
+ // run (the run grants of an unattended run come from asks only in `wait` mode); the device
80
+ // logs it with the command, and the kernel's approvals keep the per-host record
81
+ const hosts = [...new Set([...task.hosts, ...run.hosts])].slice(0, MAX_GRANT_HOSTS);
82
+ return { hosts, source: task.source };
83
+ }
84
+ /**
85
+ * The run tree's network asks (spec §6.8): what was granted and refused, the run's asks, a
86
+ * pending ask. With `deviceId`, granted and refused count only the asks about that device (a
87
+ * host allowed on one device is not allowed on another); the cap and the pending ask count all.
88
+ */
89
+ async runState(runId, deviceId) {
90
+ const task = deviceId === undefined ? undefined : await this.taskGrants({ runId, deviceId });
91
+ const root = await budgetRunOf(this.db.db, runId);
92
+ const rows = await this.db
93
+ .selectFrom("approvals")
94
+ .select(["run_id", "status", "scope", "detail"])
95
+ .where("kind", "=", "network")
96
+ .where("run_id", "in", this.treeOf(root))
97
+ .execute();
98
+ const state = {
99
+ granted: new Set(),
100
+ refused: new Set(),
101
+ asks: 0,
102
+ pending: false,
103
+ };
104
+ for (const r of rows) {
105
+ if (r.run_id === runId)
106
+ state.asks++;
107
+ if (r.status === "pending") {
108
+ state.pending = true;
109
+ }
110
+ else if (deviceId !== undefined && deviceOf(r.detail) !== deviceId) {
111
+ // another device's decision says nothing about this one
112
+ }
113
+ else if (r.status === "approved") {
114
+ const granted = grantedOf(r.detail);
115
+ if (r.scope === "once")
116
+ for (const h of granted)
117
+ state.granted.add(h);
118
+ // the hosts left unchecked in a partial decision count as denied (P-D5)
119
+ for (const h of askedOf(r.detail))
120
+ if (!granted.includes(h))
121
+ state.refused.add(h);
122
+ }
123
+ else if (REFUSING.has(r.status)) {
124
+ for (const h of askedOf(r.detail))
125
+ state.refused.add(h);
126
+ }
127
+ }
128
+ if (task !== undefined)
129
+ state.taskGranted = new Set(task.hosts);
130
+ return state;
131
+ }
132
+ /** The run tree's once-approved hosts as exec grants (source: the root run). */
133
+ async runGrants(input) {
134
+ const root = await budgetRunOf(this.db.db, input.runId);
135
+ const live = await this.db
136
+ .selectFrom("runs")
137
+ .select("id")
138
+ .where("id", "=", root)
139
+ .where("status", "in", LIVE_RUN)
140
+ .executeTakeFirst();
141
+ if (live === undefined)
142
+ return undefined;
143
+ const rows = await this.db
144
+ .selectFrom("approvals")
145
+ .select(["decided_by", "detail"])
146
+ .where("kind", "=", "network")
147
+ .where("org_id", "=", input.orgId)
148
+ .where("status", "=", "approved")
149
+ .where("scope", "=", "once")
150
+ .where("decided_by", "is not", null)
151
+ .where(sql `detail->>'deviceId'`, "=", input.deviceId)
152
+ .where("run_id", "in", this.treeOf(root))
153
+ .orderBy("decided_at", "desc")
154
+ .orderBy("id", "desc")
155
+ .execute();
156
+ const hosts = [...new Set(rows.flatMap((r) => grantedOf(r.detail)))].slice(0, MAX_GRANT_HOSTS);
157
+ const latest = rows.find((r) => grantedOf(r.detail).length > 0);
158
+ if (hosts.length === 0 || latest?.decided_by == null)
159
+ return undefined;
160
+ return { hosts, source: { kind: "run", id: root, accountId: latest.decided_by } };
161
+ }
162
+ /**
163
+ * A scheduled task's network grants for an unattended run (spec §8.1): the task's
164
+ * `network:<host>` grants when the run serves a task run that is no trial run and no person
165
+ * joined (`taskGrantsRunOf`, inherited by its sub-agents), the task is of `orgId` (when given)
166
+ * and `deviceId` is a live device of the task's org owned by the task's creator (a creator
167
+ * approves domains for their own devices only). Exact valid hosts only.
168
+ */
169
+ async taskGrants(input) {
170
+ const task = await taskGrantsRunOf(this.db.db, input.runId);
171
+ if (task === null || (input.orgId !== undefined && task.orgId !== input.orgId)) {
172
+ return undefined;
173
+ }
174
+ const device = await this.db
175
+ .selectFrom("devices")
176
+ .select("id")
177
+ .where("id", "=", input.deviceId)
178
+ .where("org_id", "=", task.orgId)
179
+ .where("owner_account_id", "=", task.accountId)
180
+ .where("deleted_at", "is", null)
181
+ .executeTakeFirst();
182
+ if (device === undefined)
183
+ return undefined;
184
+ const rows = await this.db
185
+ .selectFrom("scheduled_task_grants")
186
+ .select("tool_key")
187
+ .where("task_id", "=", task.taskId)
188
+ .where("tool_key", "like", `${NETWORK_KEY_PREFIX}%`)
189
+ .orderBy("tool_key")
190
+ .execute();
191
+ const hosts = rows
192
+ .map((r) => r.tool_key.slice(NETWORK_KEY_PREFIX.length))
193
+ .filter(exactHost)
194
+ .slice(0, MAX_GRANT_HOSTS);
195
+ if (hosts.length === 0)
196
+ return undefined;
197
+ return { hosts, source: { kind: "task", id: task.taskId, accountId: task.accountId } };
198
+ }
199
+ /**
200
+ * The tier rule of an unattended run on `deviceId` (sandbox dev tiers spec §11.4), read fresh:
201
+ * null for an attended run; otherwise the task run and whether the task grants `tier:open` on
202
+ * this device (`unattendedTierOf`).
203
+ */
204
+ async unattendedTier(input) {
205
+ return await unattendedTierOf(this.db.db, input.runId, input.deviceId);
206
+ }
207
+ /** Records `key` in the task run's `missing_grants` (once). */
208
+ async recordMissing(taskRunId, key) {
209
+ await appendMissingGrant(this.db.db, taskRunId, key);
210
+ }
211
+ /**
212
+ * Whether the device's active machine runs commands without a sandbox
213
+ * (`device_machines.local_policy.isolation`, read fresh at each exec, spec §4.3), read from the
214
+ * stored JSON's isolation field alone ({@link isolationOf}). Fails closed for a program that
215
+ * applies tiers (feature `tiers`, which always sends a summary): no summary or any isolation but
216
+ * `sandbox` counts as unsandboxed. False without a machine, or for an older program without
217
+ * tiers (it has no unsandboxed mode).
218
+ */
219
+ async unsandboxed(deviceId) {
220
+ const row = await this.db
221
+ .selectFrom("device_machines")
222
+ .select(["local_policy", "features"])
223
+ .where("device_id", "=", deviceId)
224
+ .where("revoked_at", "is", null)
225
+ .executeTakeFirst();
226
+ if (row === undefined)
227
+ return false;
228
+ const isolation = isolationOf(row.local_policy);
229
+ if (isolation !== null)
230
+ return isolation === "none";
231
+ return deviceFeaturesOf(row.features).includes("tiers");
232
+ }
233
+ /** The runs of the tree whose budget run is `root`. */
234
+ treeOf(root) {
235
+ return this.db
236
+ .selectFrom("runs")
237
+ .select("id")
238
+ .where((eb) => eb.or([eb("id", "=", root), eb("budget_run_id", "=", root)]));
239
+ }
240
+ };
241
+ NetworkGrants = __decorate([
242
+ Injectable(),
243
+ __param(0, Inject(KernelDb))
244
+ ], NetworkGrants);
245
+ export { NetworkGrants };
246
+ const normalize = (host) => {
247
+ const h = host.toLowerCase();
248
+ return h.endsWith(".") ? h.slice(0, -1) : h;
249
+ };
250
+ /** Whether the allowlist entry `pattern` admits `host` (`*.x.com`: subdomains, never the apex). */
251
+ const covers = (pattern, host) => pattern === host || (pattern.startsWith("*.") && host.endsWith(pattern.slice(1)));
252
+ /** Whether the org's effective egress lets a command reach `host`. */
253
+ export function orgAllows(host, limits) {
254
+ if (limits.egress === "none")
255
+ return false;
256
+ if (limits.egress === "open")
257
+ return true;
258
+ const h = normalize(host);
259
+ return limits.allowHosts.some((p) => covers(p.toLowerCase(), h));
260
+ }
261
+ /**
262
+ * The kernel's own layer for a device's report (spec §4.1 item 3): it trusts the device's word that
263
+ * a host was refused, never its word that the org allows it. A host the device calls `device` that
264
+ * the org's effective egress (`limits`, read for this exec) does not allow becomes `org`; one that
265
+ * is no valid host name becomes `address`. Other layers are kept.
266
+ */
267
+ export function relayer(blocked, limits) {
268
+ return blocked.map((b) => {
269
+ if (b.layer !== "device")
270
+ return b;
271
+ const h = normalize(b.host);
272
+ if (validateAllowHost(h).length > 0 || h.startsWith("*.")) {
273
+ return { ...b, layer: "address" };
274
+ }
275
+ return orgAllows(b.host, limits) ? b : { ...b, layer: "org" };
276
+ });
277
+ }
278
+ /**
279
+ * `grants` cut to the hosts the org's effective egress (`limits`, read for this exec) admits, or
280
+ * undefined when none is left. The device intersects them with the limits too; the kernel does
281
+ * it first so a grant never reaches a device beyond what the org allows.
282
+ */
283
+ export function grantsWithin(grants, limits) {
284
+ if (grants === undefined)
285
+ return undefined;
286
+ const hosts = grants.hosts.filter((h) => orgAllows(h, limits));
287
+ return hosts.length === 0 ? undefined : { ...grants, hosts };
288
+ }
@@ -0,0 +1,20 @@
1
+ import { type DeviceFeature, type DeviceLocalPolicySummary } from "@capybara-hq/plugin-sdk";
2
+ /**
3
+ * The known features in `v` (a hello's `features` or the stored column), in a fixed order and
4
+ * deduped; anything else (unknown names, a non-array) is dropped.
5
+ */
6
+ export declare function deviceFeaturesOf(v: unknown): DeviceFeature[];
7
+ /**
8
+ * `v` as a local policy summary (network ask-to-allow spec §5.4), or null when it is absent or not
9
+ * a valid one. The device is the authority on its policy: the kernel only keeps what it last said,
10
+ * for the device page and to pick a card's options, never to decide what a command may reach.
11
+ */
12
+ export declare function localPolicyOf(v: unknown): DeviceLocalPolicySummary | null;
13
+ /**
14
+ * The isolation a policy summary states, read from its `isolation` field alone (a hello's raw
15
+ * `policy` or the stored column), so a summary the kernel cannot otherwise read (a newer program's
16
+ * new value elsewhere, a broken one) still tells whether commands run without a sandbox: `sandbox`
17
+ * only when it says exactly that, `none` for any other present summary (fail closed), null when
18
+ * there is none.
19
+ */
20
+ export declare function isolationOf(v: unknown): "sandbox" | "none" | null;
@@ -0,0 +1,63 @@
1
+ import { isSandboxTier, } from "@capybara-hq/plugin-sdk";
2
+ const KNOWN_FEATURES = ["networkReport", "policyOp", "tiers"];
3
+ /**
4
+ * The known features in `v` (a hello's `features` or the stored column), in a fixed order and
5
+ * deduped; anything else (unknown names, a non-array) is dropped.
6
+ */
7
+ export function deviceFeaturesOf(v) {
8
+ if (!Array.isArray(v))
9
+ return [];
10
+ return KNOWN_FEATURES.filter((f) => v.includes(f));
11
+ }
12
+ /**
13
+ * `v` as a local policy summary (network ask-to-allow spec §5.4), or null when it is absent or not
14
+ * a valid one. The device is the authority on its policy: the kernel only keeps what it last said,
15
+ * for the device page and to pick a card's options, never to decide what a command may reach.
16
+ */
17
+ export function localPolicyOf(v) {
18
+ if (typeof v !== "object" || v === null || Array.isArray(v))
19
+ return null;
20
+ const o = v;
21
+ if (o.tier !== undefined && !isSandboxTier(o.tier))
22
+ return null;
23
+ if (!isSandboxTier(o.effectiveTier))
24
+ return null;
25
+ if (o.isolation !== "sandbox" && o.isolation !== "none")
26
+ return null;
27
+ if (o.localhost !== "on" && o.localhost !== "off-proxy-detected" && o.localhost !== "off") {
28
+ return null;
29
+ }
30
+ if (o.unixSockets !== "roots" && o.unixSockets !== "all-masked" && o.unixSockets !== "off") {
31
+ return null;
32
+ }
33
+ if (o.remoteGrants !== "owner" && o.remoteGrants !== "off")
34
+ return null;
35
+ if (!Array.isArray(o.allowedDomains))
36
+ return null;
37
+ if (!o.allowedDomains.every((d) => typeof d === "string"))
38
+ return null;
39
+ return {
40
+ ...(o.tier !== undefined && { tier: o.tier }),
41
+ effectiveTier: o.effectiveTier,
42
+ isolation: o.isolation,
43
+ localhost: o.localhost,
44
+ unixSockets: o.unixSockets,
45
+ allowedDomains: [...o.allowedDomains],
46
+ remoteGrants: o.remoteGrants,
47
+ };
48
+ }
49
+ /**
50
+ * The isolation a policy summary states, read from its `isolation` field alone (a hello's raw
51
+ * `policy` or the stored column), so a summary the kernel cannot otherwise read (a newer program's
52
+ * new value elsewhere, a broken one) still tells whether commands run without a sandbox: `sandbox`
53
+ * only when it says exactly that, `none` for any other present summary (fail closed), null when
54
+ * there is none.
55
+ */
56
+ export function isolationOf(v) {
57
+ if (v === undefined || v === null)
58
+ return null;
59
+ if (typeof v === "object" && !Array.isArray(v)) {
60
+ return v.isolation === "sandbox" ? "sandbox" : "none";
61
+ }
62
+ return "none";
63
+ }
@@ -21,7 +21,7 @@ import { MembersService } from "./members.service.js";
21
21
  import { Offboarding } from "./offboarding.js";
22
22
  import { OrgLockModule } from "./org-lock.js";
23
23
  import { OrgsService } from "./orgs.service.js";
24
- import { OrgSettingsService } from "./settings.service.js";
24
+ import { OrgSettingsModule } from "./settings.service.js";
25
25
  /**
26
26
  * Organizations: orgs, org settings, members, invitations, offboarding (with its sweep cron) and
27
27
  * directory sync (the hourly scan cron, one-off syncs on the maintenance queue).
@@ -35,6 +35,7 @@ OrgsModule = __decorate([
35
35
  // offboarding publishes the events it cancelled
36
36
  EventLogModule,
37
37
  OrgLockModule,
38
+ OrgSettingsModule,
38
39
  ExtensionsModule,
39
40
  // offboarding clears credentials and cancels approvals and questions
40
41
  ToolsModule,
@@ -49,18 +50,11 @@ OrgsModule = __decorate([
49
50
  // the producer side of directory-sync jobs (the maintenance processor consumes them)
50
51
  QueueModule.registerQueue(MAINTENANCE_QUEUE_NAME),
51
52
  ],
52
- providers: [
53
- OrgsService,
54
- OrgSettingsService,
55
- MembersService,
56
- InvitationsService,
57
- Offboarding,
58
- DirectorySync,
59
- ],
53
+ providers: [OrgsService, MembersService, InvitationsService, Offboarding, DirectorySync],
60
54
  exports: [
61
55
  OrgLockModule,
56
+ OrgSettingsModule,
62
57
  OrgsService,
63
- OrgSettingsService,
64
58
  MembersService,
65
59
  InvitationsService,
66
60
  Offboarding,
@@ -4,7 +4,7 @@ export type OrgRole = (typeof ORG_ROLES)[number];
4
4
  /** `role` 是否不低于 `min`。 */
5
5
  export declare function roleAtLeast(role: OrgRole, min: OrgRole): boolean;
6
6
  /** 组织级动作;路由用 `@OrgAction(action)` 声明所需动作。 */
7
- export type OrgAction = "org.read" | "org.update" | "org.delete" | "org.transfer" | "org.members.read" | "org.members.manage" | "org.invitations.manage" | "org.tokens.manage" | "org.extensions.read" | "org.extensions.manage" | "agents.create" | "agents.use" | "agents.manage" | "connectors.manage" | "connectors.use" | "approvals.decide" | "channels.manage" | "skills.read" | "skills.manage" | "ui.use" | "trajectories.read" | "evals.manage" | "org.models.manage" | "catalog.read" | "catalog.request" | "catalog.decide" | "catalog.upload";
7
+ export type OrgAction = "org.read" | "org.update" | "org.delete" | "org.transfer" | "org.members.read" | "org.members.manage" | "org.invitations.manage" | "org.tokens.manage" | "org.extensions.read" | "org.extensions.manage" | "agents.create" | "agents.use" | "agents.manage" | "connectors.manage" | "connectors.use" | "approvals.decide" | "channels.manage" | "skills.read" | "skills.manage" | "ui.use" | "trajectories.read" | "evals.manage" | "org.models.manage" | "catalog.read" | "catalog.request" | "catalog.decide" | "catalog.upload" | "schedules.use" | "schedules.manage";
8
8
  /** 每个动作所需的最低角色。 */
9
9
  export declare const ACTION_MIN_ROLE: Record<OrgAction, OrgRole>;
10
10
  /** 只允许 user principal 执行的动作:服务令牌即使角色足够也拒绝(除 SERVICE_ALLOWED 外全部)。 */
@@ -47,6 +47,10 @@ export const ACTION_MIN_ROLE = {
47
47
  "catalog.decide": "admin",
48
48
  // An org admin uploads a plugin package the org owns (spec §14.1 item 1, ruling P3-7)
49
49
  "catalog.upload": "admin",
50
+ // Scheduled tasks (spec 2026-11-11 §10): create, edit, pause, resume, run and delete one's own
51
+ "schedules.use": "member",
52
+ // list every task of the org, pause, resume and delete any (never edit another person's)
53
+ "schedules.manage": "admin",
50
54
  };
51
55
  /** 服务令牌也可执行的动作:只读的 org.read、org.members.read,以及使用 agent 的 agents.use。 */
52
56
  const SERVICE_ALLOWED = new Set([
@@ -30,7 +30,8 @@ let OrgSettingsController = class OrgSettingsController {
30
30
  async update(principal, org, body) {
31
31
  const p = requireUser(principal);
32
32
  return {
33
- settings: await this.settings.update(org.orgId, body, p.account.id, patTokenId(p)),
33
+ // an API token may narrow the sandbox, never widen it (sandbox dev tiers spec §2)
34
+ settings: await this.settings.update(org.orgId, body, p.account.id, patTokenId(p), undefined, { onlyNarrows: patTokenId(p) !== undefined }),
34
35
  deployment: this.settings.deployment(),
35
36
  sandboxLimits: await this.settings.sandboxLimits(org.orgId),
36
37
  };