@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
@@ -1,6 +1,6 @@
1
1
  import { KernelDb } from "../infra/database/kernel-db.js";
2
2
  import { TransactionHost } from "../infra/database/transaction-host.js";
3
- import { ConversationsService, type RunControl } from "./conversations.service.js";
3
+ import { ConversationsService, type InternalMessageOptions, type RunControl } from "./conversations.service.js";
4
4
  import { EventLog } from "./event-log.js";
5
5
  import type { ConversationEvent } from "./events.js";
6
6
  import { type QueueInput, type QueueItem, type QueuePauseReason } from "./queue.js";
@@ -15,7 +15,7 @@ export declare class ConversationQueue {
15
15
  /** Starts the conversation's next run (`ensureRun`). */
16
16
  private readonly runs;
17
17
  readonly txHost: TransactionHost;
18
- constructor(db: KernelDb, eventLog: Pick<EventLog, "appendIn" | "notify" | "publishChanged" | "findByIdem">, conversations: Pick<ConversationsService, "get" | "writable" | "resolveExtra" | "bindIn" | "checkPick" | "prepareQueuedSkill" | "skipQuestionsIn" | "announceSkipped">,
18
+ constructor(db: KernelDb, eventLog: Pick<EventLog, "appendIn" | "notify" | "publishChanged" | "findByIdem">, conversations: Pick<ConversationsService, "get" | "writable" | "resolveExtra" | "bindIn" | "checkPick" | "prepareQueuedSkill" | "skipWaitsIn" | "announceSkipped">,
19
19
  /** Starts the conversation's next run (`ensureRun`). */
20
20
  runs: Pick<RunControl, "ensureRun" | "wakeHeldTree">, txHost: TransactionHost);
21
21
  list(orgId: string, conversationId: string, accountId: string): Promise<{
@@ -29,9 +29,11 @@ export declare class ConversationQueue {
29
29
  * item, and uploaded files are bound to the conversation now, so sending later needs nobody's
30
30
  * credentials. The same `clientMessageId` again returns the queued item (`created: false`), or,
31
31
  * when it was already sent, the sent `user_message` with `item: null`. Then `ensureRun`: on an
32
- * idle conversation the item goes out at once.
32
+ * idle conversation the item goes out at once. `internal` (kernel callers only, never HTTP): a
33
+ * scheduled task's marker and zone, stored with the item and written with its `user_message`
34
+ * (scheduled tasks spec §6).
33
35
  */
34
- add(orgId: string, conversationId: string, accountId: string, input: QueueInput): Promise<{
36
+ add(orgId: string, conversationId: string, accountId: string, input: QueueInput, internal?: InternalMessageOptions): Promise<{
35
37
  item: QueueItem | null;
36
38
  event?: ConversationEvent;
37
39
  created: boolean;
@@ -39,7 +41,9 @@ export declare class ConversationQueue {
39
41
  private addIn;
40
42
  /**
41
43
  * Changes a queued item's text (its resources and files stay as queued). A skill invocation's
42
- * text is its arguments: the skill is rendered again with them now.
44
+ * text is its arguments: the skill is rendered again with them now. A scheduled task's item
45
+ * becomes the person's message: its schedule marker (and the task's zone) is dropped, so the run
46
+ * it triggers is attended (scheduled tasks spec §7).
43
47
  */
44
48
  edit(orgId: string, conversationId: string, accountId: string, itemId: string, text: string): Promise<QueueItem>;
45
49
  private editIn;
@@ -48,7 +52,9 @@ export declare class ConversationQueue {
48
52
  /**
49
53
  * Sends a queued item now: it leaves the queue and is posted as a user message in the same
50
54
  * transaction (an interjection when a run is active; the items before it keep their place).
51
- * Racing the dispatcher, exactly one of them takes the item; the loser sees it gone (404).
55
+ * Racing the dispatcher, exactly one of them takes the item; the loser sees it gone (404). A
56
+ * scheduled task's item is sent as the person's message, without its schedule marker
57
+ * (scheduled tasks spec §7).
52
58
  */
53
59
  sendNow(orgId: string, conversationId: string, accountId: string, itemId: string): Promise<ConversationEvent>;
54
60
  private sendNowIn;
@@ -14,8 +14,9 @@ import { KernelDb } from "../infra/database/kernel-db.js";
14
14
  import { TransactionHost } from "../infra/database/transaction-host.js";
15
15
  import { Transactional } from "../infra/database/transactional.decorator.js";
16
16
  import { RunControl as RunControlService } from "../runtime/run-control.js";
17
+ import { releaseQueuedTaskRunIn } from "../schedules/run-tracker.js";
17
18
  import { notInvocable } from "../skills/invoke.js";
18
- import { ConversationsService, } from "./conversations.service.js";
19
+ import { ConversationsService, NO_REPLY_SKIPS, } from "./conversations.service.js";
19
20
  import { EventLog, payloadJson } from "./event-log.js";
20
21
  import { userMessageIdemKey } from "./idem-keys.js";
21
22
  import { alreadySent, appendQueuedIn, COLUMNS, checkText, itemNotFound, noNul, QUEUE_MAX_ITEMS, queuedSkillBlocked, queueFull, storedSkill, toItem, unpauseIfEmpty, } from "./queue.js";
@@ -67,9 +68,11 @@ let ConversationQueue = class ConversationQueue {
67
68
  * item, and uploaded files are bound to the conversation now, so sending later needs nobody's
68
69
  * credentials. The same `clientMessageId` again returns the queued item (`created: false`), or,
69
70
  * when it was already sent, the sent `user_message` with `item: null`. Then `ensureRun`: on an
70
- * idle conversation the item goes out at once.
71
+ * idle conversation the item goes out at once. `internal` (kernel callers only, never HTTP): a
72
+ * scheduled task's marker and zone, stored with the item and written with its `user_message`
73
+ * (scheduled tasks spec §6).
71
74
  */
72
- async add(orgId, conversationId, accountId, input) {
75
+ async add(orgId, conversationId, accountId, input, internal = {}) {
73
76
  const conv = await this.conversations.writable(orgId, conversationId, accountId);
74
77
  const files = input.files ?? [];
75
78
  checkText(input.text, files.length, input.attachments?.length ?? 0, input.skill !== undefined);
@@ -92,7 +95,7 @@ let ConversationQueue = class ConversationQueue {
92
95
  const skill = invoked === undefined || input.skill === undefined
93
96
  ? undefined
94
97
  : storedSkill(input.skill.name, invoked);
95
- const out = await this.addIn(orgId, conversationId, accountId, input, files, skill, extra);
98
+ const out = await this.addIn(orgId, conversationId, accountId, input, files, skill, extra, internal);
96
99
  if (out === null) {
97
100
  return ((await this.prior(conversationId, input.clientMessageId)) ?? { item: null, created: false });
98
101
  }
@@ -103,7 +106,7 @@ let ConversationQueue = class ConversationQueue {
103
106
  return { item: toItem(out.row), created: out.created };
104
107
  }
105
108
  // its own transaction: the caller notifies and publishes after the commit
106
- async addIn(orgId, conversationId, accountId, input, files, skill, extra) {
109
+ async addIn(orgId, conversationId, accountId, input, files, skill, extra, internal) {
107
110
  const t = this.db.db;
108
111
  await t
109
112
  .selectFrom("conversations")
@@ -154,6 +157,14 @@ let ConversationQueue = class ConversationQueue {
154
157
  model: input.model === undefined ? null : JSON.stringify(input.model),
155
158
  effort: input.effort ?? null,
156
159
  skill: skill?.skill ?? null,
160
+ schedule: internal.schedule === undefined
161
+ ? null
162
+ : JSON.stringify({
163
+ ...internal.schedule,
164
+ ...(internal.timeZoneOverride !== undefined && {
165
+ timeZone: internal.timeZoneOverride,
166
+ }),
167
+ }),
157
168
  })
158
169
  .returning(COLUMNS)
159
170
  .executeTakeFirstOrThrow();
@@ -161,7 +172,9 @@ let ConversationQueue = class ConversationQueue {
161
172
  }
162
173
  /**
163
174
  * Changes a queued item's text (its resources and files stay as queued). A skill invocation's
164
- * text is its arguments: the skill is rendered again with them now.
175
+ * text is its arguments: the skill is rendered again with them now. A scheduled task's item
176
+ * becomes the person's message: its schedule marker (and the task's zone) is dropped, so the run
177
+ * it triggers is attended (scheduled tasks spec §7).
165
178
  */
166
179
  async edit(orgId, conversationId, accountId, itemId, text) {
167
180
  const conv = await this.conversations.writable(orgId, conversationId, accountId);
@@ -189,13 +202,15 @@ let ConversationQueue = class ConversationQueue {
189
202
  const t = this.db.db;
190
203
  const cur = await t
191
204
  .selectFrom("conversation_queue")
192
- .select(["attachments", "files", "content", "skill"])
205
+ .select(["attachments", "files", "content", "skill", "schedule"])
193
206
  .where("id", "=", itemId)
194
207
  .where("conversation_id", "=", conversationId)
195
208
  .forUpdate()
196
209
  .executeTakeFirst();
197
210
  if (!cur)
198
211
  throw itemNotFound();
212
+ // a person's words now: the task's run for this item will not happen (scheduled tasks §6)
213
+ await releaseQueuedTaskRunIn(t, cur.schedule);
199
214
  const skill = cur.skill;
200
215
  checkText(text, cur.files.length, cur.attachments.length, skill !== null);
201
216
  // an invocation's first block is its rendered text: replace it and the record
@@ -204,6 +219,8 @@ let ConversationQueue = class ConversationQueue {
204
219
  .updateTable("conversation_queue")
205
220
  .set({
206
221
  text: noNul(text),
222
+ // edited by a person: no longer the task's message
223
+ schedule: null,
207
224
  updated_at: sql `now()`,
208
225
  ...(stored !== undefined && {
209
226
  skill: stored.skill,
@@ -229,16 +246,20 @@ let ConversationQueue = class ConversationQueue {
229
246
  .deleteFrom("conversation_queue")
230
247
  .where("id", "=", itemId)
231
248
  .where("conversation_id", "=", conversationId)
232
- .returning("id")
249
+ .returning(["id", "schedule"])
233
250
  .executeTakeFirst();
234
251
  if (!gone)
235
252
  throw itemNotFound();
253
+ // a task's queued message deleted by a person: its run is skipped (scheduled tasks §6)
254
+ await releaseQueuedTaskRunIn(t, gone.schedule);
236
255
  await unpauseIfEmpty(t, conversationId);
237
256
  }
238
257
  /**
239
258
  * Sends a queued item now: it leaves the queue and is posted as a user message in the same
240
259
  * transaction (an interjection when a run is active; the items before it keep their place).
241
- * Racing the dispatcher, exactly one of them takes the item; the loser sees it gone (404).
260
+ * Racing the dispatcher, exactly one of them takes the item; the loser sees it gone (404). A
261
+ * scheduled task's item is sent as the person's message, without its schedule marker
262
+ * (scheduled tasks spec §7).
242
263
  */
243
264
  async sendNow(orgId, conversationId, accountId, itemId) {
244
265
  await this.conversations.writable(orgId, conversationId, accountId);
@@ -283,17 +304,19 @@ let ConversationQueue = class ConversationQueue {
283
304
  if (!row)
284
305
  throw itemNotFound();
285
306
  const sent = await alreadySent(t, conversationId, row.client_message_id);
286
- const out = await appendQueuedIn(t, this.eventLog, conversationId, row);
307
+ // sent by a person: their message, not the task's, whose run is skipped
308
+ await releaseQueuedTaskRunIn(t, row.schedule);
309
+ const out = await appendQueuedIn(t, this.eventLog, conversationId, { ...row, schedule: null });
287
310
  await unpauseIfEmpty(t, conversationId);
288
311
  // sent during the run, the item is the author's message like a direct send: it skips the
289
312
  // pending questions they may answer (reply-while-question spec §4.2)
290
313
  const skips = sent
291
- ? { records: [], toSeq: null }
292
- : await this.conversations.skipQuestionsIn(conversationId, row.account_id, out.event.seq);
314
+ ? { skipped: NO_REPLY_SKIPS, toSeq: null }
315
+ : await this.conversations.skipWaitsIn(conversationId, row.account_id, out.event.seq);
293
316
  return {
294
317
  ...out,
295
318
  fresh: !sent,
296
- skipped: skips.records,
319
+ skipped: skips.skipped,
297
320
  toSeq: Math.max(out.event.seq, skips.toSeq ?? 0),
298
321
  };
299
322
  }
@@ -1,5 +1,7 @@
1
- import type { FileSystemHandle, ParsedTheme, SkillInvocation } from "@capybara-hq/plugin-sdk";
1
+ import type { FileSystemHandle, ParsedTheme, SandboxTier, SkillInvocation } from "@capybara-hq/plugin-sdk";
2
2
  import type { ColumnType, Generated, Kysely } from "kysely";
3
+ import type { ModelRef } from "../agents/definition.js";
4
+ import type { ScheduleChat, ScheduleMarker, ScheduleMode, SchedulePauseReason, ScheduleStatus, ScheduleTrigger, TaskRunSkipReason, TaskRunStatus } from "../schedules/types.js";
3
5
  /** 带默认值的时间戳列:插入可省略,读出为 Date。 */
4
6
  type Ts = ColumnType<Date, Date | string | undefined, Date | string>;
5
7
  type TsNullable = ColumnType<Date | null, Date | string | null | undefined, Date | string | null>;
@@ -259,6 +261,8 @@ export interface ConversationsTable {
259
261
  subagent_depth: ColumnType<number, number | undefined, number>;
260
262
  /** 0088, renamed in 0105: the device the conversation runs in; null = not chosen yet. */
261
263
  device_id: ColumnType<string | null, string | null | undefined, string | null>;
264
+ /** 0113: the scheduled task that created it (`new` mode); null otherwise. */
265
+ scheduled_task_id: ColumnType<string | null, string | null | undefined, string | null>;
262
266
  }
263
267
  /** 0060: an org's order of file-preview@1 previewers (UI refresh B Task 11). */
264
268
  export interface FilePreviewersTable {
@@ -297,6 +301,13 @@ export interface ConversationQueueTable {
297
301
  effort: ColumnType<string | null, string | null | undefined, string | null>;
298
302
  /** 0062: the `user_message.skill` record of a queued skill invocation, or null. */
299
303
  skill: ColumnType<unknown, string | null | undefined, string | null>;
304
+ /**
305
+ * 0113: the schedule marker of a scheduled task's message, plus the task's zone (`timeZone`, the
306
+ * message's zone override; not copied into the logged marker); null for a person's message.
307
+ */
308
+ schedule: ColumnType<(ScheduleMarker & {
309
+ timeZone?: string;
310
+ }) | null, string | null | undefined, string | null>;
300
311
  created_at: Ts;
301
312
  updated_at: Ts;
302
313
  }
@@ -342,6 +353,11 @@ export interface RunsTable {
342
353
  * and its descendants' tokens, agent map spec §6); null = the run is its own budget run.
343
354
  */
344
355
  budget_run_id: ColumnType<string | null, string | null | undefined, string | null>;
356
+ /**
357
+ * 0113: the scheduled task run this run serves (set on the run the task's message triggers and
358
+ * inherited by its unattended descendants); null = a person's run.
359
+ */
360
+ scheduled_task_run_id: ColumnType<string | null, string | null | undefined, string | null>;
345
361
  }
346
362
  /** 0053: one row per sub-agent dispatch (an `Agent` call of a parent run). */
347
363
  export interface SubagentDispatchesTable {
@@ -488,7 +504,7 @@ export interface ApprovalsTable {
488
504
  input: Json<unknown>;
489
505
  requested_for: string | null;
490
506
  approver_role: string | null;
491
- status: Generated<"pending" | "approved" | "denied" | "expired" | "cancelled">;
507
+ status: Generated<"pending" | "approved" | "denied" | "expired" | "cancelled" | "skipped">;
492
508
  scope: "once" | "session" | "always" | null;
493
509
  decided_by: string | null;
494
510
  decided_at: TsNullable;
@@ -509,6 +525,10 @@ export interface ApprovalsTable {
509
525
  } | null, string | null | undefined, string | null>;
510
526
  /** 0086: the scopes the person may choose (null: derived, see `approvals.service.ts`). */
511
527
  scopes: ColumnType<("once" | "session" | "always")[] | null, ("once" | "session" | "always")[] | null | undefined, ("once" | "session" | "always")[] | null>;
528
+ /** 0114: what is asked: a tool call ('tool') or a refused network domain ('network'). */
529
+ kind: Generated<"tool" | "network">;
530
+ /** 0114: a network ask's hosts, command, kept result and outcome (null for tool approvals). */
531
+ detail: ColumnType<unknown | null, string | null | undefined, string | null>;
512
532
  }
513
533
  /** 0010_tools.sql:AskUserQuestion 提问。 */
514
534
  export interface QuestionsTable {
@@ -628,6 +648,11 @@ export interface ChannelDeliveriesTable {
628
648
  fail_seq: ColumnType<string | null, number | string | null | undefined, number | string | null>;
629
649
  fail_count: Generated<number>;
630
650
  updated_at: Ts;
651
+ /**
652
+ * 0113: a scheduled group run's thread is still to bind; the account its thread row gets when
653
+ * outbound has the run's first post (thread key = that message's id). Null otherwise.
654
+ */
655
+ pending_thread_account_id: ColumnType<string | null, string | null | undefined, string | null>;
631
656
  }
632
657
  /** 0018_channels.sql:出站投递发出的审批 / 提问卡片对应的 IM 消息(Task 11,用于更新卡片状态)。 */
633
658
  export interface ChannelCardMessagesTable {
@@ -850,6 +875,8 @@ export interface DevicesTable {
850
875
  provider: ColumnType<string | null, string | null | undefined, string | null>;
851
876
  /** 0096: the provider a demoted default was demoted from; null for every other device. */
852
877
  demoted_from_provider: ColumnType<string | null, string | null | undefined, string | null>;
878
+ /** 0117: a cloud device's own sandbox tier; null follows the org's default. */
879
+ tier: ColumnType<SandboxTier | null, SandboxTier | null | undefined, SandboxTier | null>;
853
880
  created_at: Ts;
854
881
  updated_at: Ts;
855
882
  deleted_at: TsNullable;
@@ -879,6 +906,12 @@ export interface DeviceInstancesTable {
879
906
  prev_provider: ColumnType<string | null, string | null | undefined, string | null>;
880
907
  prev_handle: InstanceHandle;
881
908
  sweep_failed_at: TsNullable;
909
+ /**
910
+ * 0117: the limits in force on the instance, `{ tier, egress }`, plus `unapplied` when the
911
+ * provider could not apply a newer value without a file reset; null = unknown (made before
912
+ * 0117). Read with `readInstanceLimits` (`sandbox/tiers.ts`).
913
+ */
914
+ instance_limits: ColumnType<unknown | null, string | null | undefined, string | null>;
882
915
  }
883
916
  /**
884
917
  * 0093_connected_environments.sql, renamed in 0105: the machine of a local device. At most one
@@ -902,6 +935,10 @@ export interface DeviceMachinesTable {
902
935
  last_seen_at: TsNullable;
903
936
  created_at: Ts;
904
937
  revoked_at: TsNullable;
938
+ /** 0114: the features the program reported in its hello (`networkReport`, `policyOp`). */
939
+ features: ColumnType<string[], string[] | undefined, string[]>;
940
+ /** 0114: the program's local network policy summary (hello, refreshed by each `policy` op). */
941
+ local_policy: ColumnType<unknown | null, string | null | undefined, string | null>;
905
942
  }
906
943
  /** 0088_environments.sql, renamed in 0105: the device a member's new conversations use by default. */
907
944
  export interface DeviceDefaultsTable {
@@ -1147,6 +1184,61 @@ export interface ModelUsageTable {
1147
1184
  /** Set when the row's `usage.report` job gave up after its last attempt (the sweep skips it). */
1148
1185
  report_failed_at: TsNullable;
1149
1186
  }
1187
+ type NullableText = ColumnType<string | null, string | null | undefined, string | null>;
1188
+ type NullableTs = ColumnType<Date | null, Date | string | null | undefined, Date | string | null>;
1189
+ /** 0113: a scheduled task (scheduled tasks spec §4). */
1190
+ export interface ScheduledTasksTable {
1191
+ id: string;
1192
+ org_id: string;
1193
+ account_id: string;
1194
+ agent_id: string;
1195
+ name: string;
1196
+ prompt: string;
1197
+ trigger: ColumnType<ScheduleTrigger, string, string>;
1198
+ mode: ScheduleMode;
1199
+ conversation_id: NullableText;
1200
+ chat: ColumnType<ScheduleChat | null, string | null | undefined, string | null>;
1201
+ model: ColumnType<ModelRef | null, string | null | undefined, string | null>;
1202
+ effort: NullableText;
1203
+ device_id: NullableText;
1204
+ status: ColumnType<ScheduleStatus, ScheduleStatus | undefined, ScheduleStatus>;
1205
+ pause_reason: ColumnType<SchedulePauseReason | null, SchedulePauseReason | null | undefined, SchedulePauseReason | null>;
1206
+ paused_by: NullableText;
1207
+ next_run_at: NullableTs;
1208
+ jitter_seconds: ColumnType<number, number | undefined, number>;
1209
+ expires_at: NullableTs;
1210
+ consecutive_failures: ColumnType<number, number | undefined, number>;
1211
+ created_at: Ts;
1212
+ updated_at: Ts;
1213
+ deleted_at: NullableTs;
1214
+ }
1215
+ /** 0113: one tick (or trial run) of a scheduled task; unique per (task, tick) when not trial. */
1216
+ export interface ScheduledTaskRunsTable {
1217
+ id: string;
1218
+ task_id: string;
1219
+ org_id: string;
1220
+ scheduled_for: Date | string;
1221
+ fired_at: NullableTs;
1222
+ trial: ColumnType<boolean, boolean | undefined, boolean>;
1223
+ /** A person's "run now" (trial or not): runs even while the task is paused. */
1224
+ manual: ColumnType<boolean, boolean | undefined, boolean>;
1225
+ conversation_id: NullableText;
1226
+ run_id: NullableText;
1227
+ status: ColumnType<TaskRunStatus, TaskRunStatus | undefined, TaskRunStatus>;
1228
+ skip_reason: ColumnType<TaskRunSkipReason | null, TaskRunSkipReason | null | undefined, TaskRunSkipReason | null>;
1229
+ error: NullableText;
1230
+ missing_grants: ColumnType<string[], string | undefined, string>;
1231
+ /** When the scanner's repair last found this started run still in flight. */
1232
+ rechecked_at: NullableTs;
1233
+ }
1234
+ /** 0113: a scheduled task's remembered grants (same shape as `conversation_tool_grants`). */
1235
+ export interface ScheduledTaskGrantsTable {
1236
+ task_id: string;
1237
+ tool_key: string;
1238
+ spec_hash: string;
1239
+ connector_id: string | null;
1240
+ created_at: Ts;
1241
+ }
1150
1242
  export interface KernelDatabase {
1151
1243
  accounts: AccountsTable;
1152
1244
  account_initial_passwords: AccountInitialPasswordsTable;
@@ -1200,6 +1292,9 @@ export interface KernelDatabase {
1200
1292
  device_defaults: DeviceDefaultsTable;
1201
1293
  device_machines: DeviceMachinesTable;
1202
1294
  background_tasks: BackgroundTasksTable;
1295
+ scheduled_tasks: ScheduledTasksTable;
1296
+ scheduled_task_runs: ScheduledTaskRunsTable;
1297
+ scheduled_task_grants: ScheduledTaskGrantsTable;
1203
1298
  skills: SkillsTable;
1204
1299
  skill_versions: SkillVersionsTable;
1205
1300
  skill_change_requests: SkillChangeRequestsTable;
@@ -1669,6 +1764,8 @@ export interface PluginImportStagingTable {
1669
1764
  expires_at: ColumnType<Date, Date | string, Date | string>;
1670
1765
  }
1671
1766
  type NullableCol<T> = ColumnType<T | null, T | null | undefined, T | null>;
1767
+ /** 0077 / 0085 / 0115: the kinds of a catalog request. */
1768
+ type CatalogRequestKindCol = "access" | "import" | "enable" | "connector_setup" | "network_allowlist";
1672
1769
  /**
1673
1770
  * 0037, renamed in 0042, kinds since 0077: an org's catalog requests. `stage` (org / deployment) is
1674
1771
  * still written (`enable` -> org, else deployment) but no longer read.
@@ -1680,7 +1777,12 @@ export interface CatalogRequestsTable {
1680
1777
  item_id: NullableCol<string>;
1681
1778
  version: NullableCol<string>;
1682
1779
  /** 0077: what the request asks for (spec §5.1). */
1683
- kind: ColumnType<"access" | "import" | "enable" | "connector_setup", "access" | "import" | "enable" | "connector_setup" | undefined, "access" | "import" | "enable" | "connector_setup">;
1780
+ kind: ColumnType<CatalogRequestKindCol, CatalogRequestKindCol | undefined, CatalogRequestKindCol>;
1781
+ /**
1782
+ * 0115: what a `network_allowlist` request asks for (`{ hosts, conversationId, agentId }`); null
1783
+ * for other kinds.
1784
+ */
1785
+ detail: ColumnType<unknown | null, string | null | undefined, string | null>;
1684
1786
  /** 0085: the agent of a `connector_setup` request (cascades with the agent). */
1685
1787
  agent_id: NullableCol<string>;
1686
1788
  /** 0085: the template connector keys a `connector_setup` request still waits for. */
@@ -1,6 +1,7 @@
1
1
  import { ApiError } from "../errors.js";
2
2
  import { KernelDb } from "../infra/database/kernel-db.js";
3
3
  import { TransactionHost } from "../infra/database/transaction-host.js";
4
+ import type { DeviceSandboxLevel } from "../runtime/context/device-paths.js";
4
5
  import { SandboxProviders } from "../sandbox/providers.js";
5
6
  import { DeviceAccess } from "./access.js";
6
7
  import { DevicesService } from "./devices.service.js";
@@ -29,7 +30,13 @@ export declare class ConversationDevices {
29
30
  private readonly providers;
30
31
  private readonly access;
31
32
  private readonly devices;
32
- constructor(db: KernelDb, txHost: TransactionHost, providers: Pick<SandboxProviders, "cloud"> & Partial<Pick<SandboxProviders, "forName">>, access: Pick<DeviceAccess, "usable">, devices: Pick<DevicesService, "ensureDefault">);
33
+ constructor(db: KernelDb, txHost: TransactionHost, providers: Pick<SandboxProviders, "cloud"> & Partial<Pick<SandboxProviders, "forName">>, access: Pick<DeviceAccess, "usable">, devices: Pick<DevicesService, "ensureDefault" | "sandboxLevel">);
34
+ /**
35
+ * The sandbox level a call in the device runs at now ({@link DevicesService.sandboxLevel}),
36
+ * recorded for the model in `device_paths` (sandbox dev tiers spec §11.5); `runId`: the calling
37
+ * run, whose scheduled-run cap applies.
38
+ */
39
+ sandboxLevel(orgId: string, deviceId: string, runId?: string): Promise<DeviceSandboxLevel | null>;
33
40
  /**
34
41
  * The device a new conversation of `accountId` is bound to: the chosen one when it is
35
42
  * usable by the person (else `device.not_found` / `device.unavailable`, and nothing
@@ -41,6 +41,14 @@ let ConversationDevices = class ConversationDevices {
41
41
  this.access = access;
42
42
  this.devices = devices;
43
43
  }
44
+ /**
45
+ * The sandbox level a call in the device runs at now ({@link DevicesService.sandboxLevel}),
46
+ * recorded for the model in `device_paths` (sandbox dev tiers spec §11.5); `runId`: the calling
47
+ * run, whose scheduled-run cap applies.
48
+ */
49
+ sandboxLevel(orgId, deviceId, runId) {
50
+ return this.devices.sandboxLevel(orgId, deviceId, runId);
51
+ }
44
52
  /**
45
53
  * The device a new conversation of `accountId` is bound to: the chosen one when it is
46
54
  * usable by the person (else `device.not_found` / `device.unavailable`, and nothing
@@ -1,8 +1,9 @@
1
+ import type { DeviceLocalPolicySummary } from "@capybara-hq/plugin-sdk";
1
2
  import type { z } from "zod";
2
3
  import { type Principal } from "../identity/principal.js";
3
4
  import { type OrgContext } from "../orgs/org.guard.js";
4
5
  import { type DeviceList, DevicesService, type DeviceView } from "./devices.service.js";
5
- import { type DeviceNameBody, type DeviceParams } from "./dto.js";
6
+ import { type DeviceNameBody, type DeviceParams, type DevicePatchBody, type DevicePolicyBody } from "./dto.js";
6
7
  /**
7
8
  * A person's devices in an org (`/api/v1/orgs/:orgId/devices`, spec
8
9
  * 2026-11-07-environments-design §9; `agents.use`, human users only: a service token gets 403).
@@ -10,12 +11,25 @@ import { type DeviceNameBody, type DeviceParams } from "./dto.js";
10
11
  * gets 404 `device.not_found`.
11
12
  * - `GET /` → `{ items, defaultId, lastUsedId, cloudAvailable }` (`cloudAvailable`: there is a
12
13
  * current sandbox provider; ruling R4);
13
- * - `POST / { name }` → 201 `{ device }`; `GET /:id`, `PATCH /:id { name }` → `{ device }`;
14
+ * - `POST / { name }` → 201 `{ device }`; `GET /:id` → `{ device }`;
15
+ * - `PATCH /:id { name?, tier? }` → `{ device }`: renames it and, for a cloud device, sets its
16
+ * sandbox tier (sandbox dev tiers spec §7.2; null follows the org's default). The tier is set in
17
+ * a web session only (an API token → 403 `auth.session_required`); above the org's or the
18
+ * platform's ceiling → 400 `device.tier_not_allowed`; a local device → 409
19
+ * `device.tier_on_device` (its tier is its machine's: `POST /:id/policy { tier }`). Audited
20
+ * `device.tier_changed`;
14
21
  * - `DELETE /:id` → 202 (destroyed by a job after the response);
15
22
  * - `POST /:id/files/reset` → 202 (the reset is enqueued: a worker empties the files, the next use
16
23
  * starts empty);
17
24
  * - `DELETE /:id/machine` → 204: a local device's machine is disconnected (its credential
18
- * revoked; it must sign in again).
25
+ * revoked; it must sign in again);
26
+ * - `POST /:id/policy { add?, remove?, tier?, isolation? }` → `{ policy }`: the owner changes the
27
+ * local device's policy on its machine (network ask-to-allow spec §7, sandbox dev tiers spec
28
+ * §7.3; `isolation` is only ever `sandbox`). Web session only
29
+ * (`@SessionOnly`: a personal access token → 403 `auth.session_required`, a service token → 403):
30
+ * a person changes their own machine's network policy, nothing model-driven does. Widening needs
31
+ * the machine's `remoteGrants: "owner"` (409 `device.policy_refused`); offline → 503
32
+ * `device.offline`; cloud or an older program → 409 `device.policy_unsupported`.
19
33
  */
20
34
  export declare class DevicesController {
21
35
  private readonly devices;
@@ -27,10 +41,13 @@ export declare class DevicesController {
27
41
  get(principal: Principal, org: OrgContext, params: z.infer<typeof DeviceParams>): Promise<{
28
42
  device: DeviceView;
29
43
  }>;
30
- rename(principal: Principal, org: OrgContext, params: z.infer<typeof DeviceParams>, body: z.infer<typeof DeviceNameBody>): Promise<{
44
+ update(principal: Principal, org: OrgContext, params: z.infer<typeof DeviceParams>, body: z.infer<typeof DevicePatchBody>): Promise<{
31
45
  device: DeviceView;
32
46
  }>;
33
47
  remove(principal: Principal, org: OrgContext, params: z.infer<typeof DeviceParams>): Promise<void>;
34
48
  resetFiles(principal: Principal, org: OrgContext, params: z.infer<typeof DeviceParams>): Promise<void>;
35
49
  revokeMachine(principal: Principal, org: OrgContext, params: z.infer<typeof DeviceParams>): Promise<void>;
50
+ changePolicy(principal: Principal, org: OrgContext, params: z.infer<typeof DeviceParams>, body: z.infer<typeof DevicePolicyBody>): Promise<{
51
+ policy: DeviceLocalPolicySummary;
52
+ }>;
36
53
  }
@@ -8,11 +8,12 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
8
8
  return function (target, key) { decorator(target, key, paramIndex); }
9
9
  };
10
10
  import { Controller, Delete, Get, HttpCode, Inject, Patch, Post } from "@nestjs/common";
11
- import { CurrentPrincipal, patTokenId, requireUser, } from "../identity/principal.js";
11
+ import { ApiError } from "../errors.js";
12
+ import { CurrentPrincipal, patTokenId, requireUser, SessionOnly, } from "../identity/principal.js";
12
13
  import { ZBody, ZParam } from "../infra/validation/zod-params.js";
13
14
  import { CurrentOrg, OrgAction } from "../orgs/org.guard.js";
14
15
  import { DevicesService } from "./devices.service.js";
15
- import { DeviceCreateBodyDto, DeviceParamsDto, DeviceRenameBodyDto, } from "./dto.js";
16
+ import { DeviceCreateBodyDto, DeviceParamsDto, DevicePatchBodyDto, DevicePolicyBodyDto, } from "./dto.js";
16
17
  /**
17
18
  * A person's devices in an org (`/api/v1/orgs/:orgId/devices`, spec
18
19
  * 2026-11-07-environments-design §9; `agents.use`, human users only: a service token gets 403).
@@ -20,12 +21,25 @@ import { DeviceCreateBodyDto, DeviceParamsDto, DeviceRenameBodyDto, } from "./dt
20
21
  * gets 404 `device.not_found`.
21
22
  * - `GET /` → `{ items, defaultId, lastUsedId, cloudAvailable }` (`cloudAvailable`: there is a
22
23
  * current sandbox provider; ruling R4);
23
- * - `POST / { name }` → 201 `{ device }`; `GET /:id`, `PATCH /:id { name }` → `{ device }`;
24
+ * - `POST / { name }` → 201 `{ device }`; `GET /:id` → `{ device }`;
25
+ * - `PATCH /:id { name?, tier? }` → `{ device }`: renames it and, for a cloud device, sets its
26
+ * sandbox tier (sandbox dev tiers spec §7.2; null follows the org's default). The tier is set in
27
+ * a web session only (an API token → 403 `auth.session_required`); above the org's or the
28
+ * platform's ceiling → 400 `device.tier_not_allowed`; a local device → 409
29
+ * `device.tier_on_device` (its tier is its machine's: `POST /:id/policy { tier }`). Audited
30
+ * `device.tier_changed`;
24
31
  * - `DELETE /:id` → 202 (destroyed by a job after the response);
25
32
  * - `POST /:id/files/reset` → 202 (the reset is enqueued: a worker empties the files, the next use
26
33
  * starts empty);
27
34
  * - `DELETE /:id/machine` → 204: a local device's machine is disconnected (its credential
28
- * revoked; it must sign in again).
35
+ * revoked; it must sign in again);
36
+ * - `POST /:id/policy { add?, remove?, tier?, isolation? }` → `{ policy }`: the owner changes the
37
+ * local device's policy on its machine (network ask-to-allow spec §7, sandbox dev tiers spec
38
+ * §7.3; `isolation` is only ever `sandbox`). Web session only
39
+ * (`@SessionOnly`: a personal access token → 403 `auth.session_required`, a service token → 403):
40
+ * a person changes their own machine's network policy, nothing model-driven does. Widening needs
41
+ * the machine's `remoteGrants: "owner"` (409 `device.policy_refused`); offline → 503
42
+ * `device.offline`; cloud or an older program → 409 `device.policy_unsupported`.
29
43
  */
30
44
  let DevicesController = class DevicesController {
31
45
  devices;
@@ -46,10 +60,18 @@ let DevicesController = class DevicesController {
46
60
  const p = requireUser(principal);
47
61
  return { device: await this.devices.get(org.orgId, p.account.id, params.id) };
48
62
  }
49
- async rename(principal, org, params, body) {
63
+ async update(principal, org, params, body) {
50
64
  const p = requireUser(principal);
65
+ const tokenId = patTokenId(p);
66
+ // a person decides their device's sandbox level, in a session (spec §7.2)
67
+ if (body.tier !== undefined && tokenId !== undefined) {
68
+ throw new ApiError("auth.session_required", "A device's sandbox level is set by its owner on the web, not with an API token");
69
+ }
51
70
  return {
52
- device: await this.devices.rename(org.orgId, p.account.id, params.id, body.name, patTokenId(p)),
71
+ device: await this.devices.update(org.orgId, p.account.id, params.id, {
72
+ ...(body.name !== undefined && { name: body.name }),
73
+ ...(body.tier !== undefined && { tier: body.tier }),
74
+ }, tokenId),
53
75
  };
54
76
  }
55
77
  async remove(principal, org, params) {
@@ -64,6 +86,12 @@ let DevicesController = class DevicesController {
64
86
  const p = requireUser(principal);
65
87
  await this.devices.revokeMachine(org.orgId, p.account.id, params.id, patTokenId(p));
66
88
  }
89
+ async changePolicy(principal, org, params, body) {
90
+ const p = requireUser(principal);
91
+ return {
92
+ policy: await this.devices.changePolicy(org.orgId, p.account, params.id, body),
93
+ };
94
+ }
67
95
  };
68
96
  __decorate([
69
97
  Get(),
@@ -91,8 +119,8 @@ __decorate([
91
119
  __param(0, CurrentPrincipal()),
92
120
  __param(1, CurrentOrg()),
93
121
  __param(2, ZParam(DeviceParamsDto)),
94
- __param(3, ZBody(DeviceRenameBodyDto))
95
- ], DevicesController.prototype, "rename", null);
122
+ __param(3, ZBody(DevicePatchBodyDto))
123
+ ], DevicesController.prototype, "update", null);
96
124
  __decorate([
97
125
  Delete(":id"),
98
126
  HttpCode(202),
@@ -117,6 +145,16 @@ __decorate([
117
145
  __param(1, CurrentOrg()),
118
146
  __param(2, ZParam(DeviceParamsDto))
119
147
  ], DevicesController.prototype, "revokeMachine", null);
148
+ __decorate([
149
+ Post(":id/policy"),
150
+ HttpCode(200),
151
+ SessionOnly(),
152
+ OrgAction("agents.use"),
153
+ __param(0, CurrentPrincipal()),
154
+ __param(1, CurrentOrg()),
155
+ __param(2, ZParam(DeviceParamsDto)),
156
+ __param(3, ZBody(DevicePolicyBodyDto))
157
+ ], DevicesController.prototype, "changePolicy", null);
120
158
  DevicesController = __decorate([
121
159
  Controller("api/v1/orgs/:orgId/devices"),
122
160
  __param(0, Inject(DevicesService))
@@ -1,3 +1,11 @@
1
1
  import type { RouteSchemas } from "../platform/route-schemas.js";
2
- /** The devices routes the Platform operations mirror (`check:platform-ops` keys). */
2
+ /** The body of the excluded `POST /devices/:id/policy` route (human-only). */
3
+ export { DevicePolicyBody } from "./dto.js";
4
+ /**
5
+ * The devices routes the Platform operations mirror (`check:platform-ops` keys). Changing a
6
+ * device's network policy (`POST /devices/:id/policy`, body {@link DevicePolicyBody}) is a person's
7
+ * act on their own machine and stays excluded, so it has no entry here. `PATCH /devices/:id` takes
8
+ * a cloud device's `tier` too ({@link DevicePatchBody}); `devices.rename` omits it: a person decides
9
+ * their device's sandbox level.
10
+ */
3
11
  export declare const DEVICE_ROUTES: readonly RouteSchemas[];
@@ -1,5 +1,13 @@
1
- import { DeviceNameBody, DeviceParams } from "./dto.js";
2
- /** The devices routes the Platform operations mirror (`check:platform-ops` keys). */
1
+ import { DeviceNameBody, DeviceParams, DevicePatchBody } from "./dto.js";
2
+ /** The body of the excluded `POST /devices/:id/policy` route (human-only). */
3
+ export { DevicePolicyBody } from "./dto.js";
4
+ /**
5
+ * The devices routes the Platform operations mirror (`check:platform-ops` keys). Changing a
6
+ * device's network policy (`POST /devices/:id/policy`, body {@link DevicePolicyBody}) is a person's
7
+ * act on their own machine and stays excluded, so it has no entry here. `PATCH /devices/:id` takes
8
+ * a cloud device's `tier` too ({@link DevicePatchBody}); `devices.rename` omits it: a person decides
9
+ * their device's sandbox level.
10
+ */
3
11
  export const DEVICE_ROUTES = [
4
12
  { route: "GET /api/v1/orgs/:orgId/devices" },
5
13
  { route: "POST /api/v1/orgs/:orgId/devices", body: DeviceNameBody },
@@ -7,7 +15,7 @@ export const DEVICE_ROUTES = [
7
15
  {
8
16
  route: "PATCH /api/v1/orgs/:orgId/devices/:id",
9
17
  params: DeviceParams,
10
- body: DeviceNameBody,
18
+ body: DevicePatchBody,
11
19
  },
12
20
  { route: "DELETE /api/v1/orgs/:orgId/devices/:id", params: DeviceParams },
13
21
  { route: "POST /api/v1/orgs/:orgId/devices/:id/files/reset", params: DeviceParams },