@capybara-hq/kernel 0.1.0-rc.11 → 0.1.0-rc.13

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 (220) hide show
  1. package/dist/admin/admin.schemas.d.ts +13 -0
  2. package/dist/agents/agent-lookup.d.ts +5 -0
  3. package/dist/agents/agent-lookup.js +15 -0
  4. package/dist/agents/agents.schemas.d.ts +3 -0
  5. package/dist/agents/agents.service.js +11 -1
  6. package/dist/agents/definition.d.ts +1 -0
  7. package/dist/agents/definition.js +7 -3
  8. package/dist/agents/org-templates.js +4 -0
  9. package/dist/agents/pending-connectors.service.js +1 -1
  10. package/dist/background/background.module.js +2 -2
  11. package/dist/background/launch-lock.d.ts +1 -1
  12. package/dist/background/launch-lock.js +1 -1
  13. package/dist/background/poll.job.d.ts +2 -2
  14. package/dist/background/poll.job.js +10 -10
  15. package/dist/background/stop.d.ts +1 -1
  16. package/dist/background/stop.js +3 -3
  17. package/dist/background/store.d.ts +5 -5
  18. package/dist/background/store.js +5 -5
  19. package/dist/background/tasks.service.d.ts +6 -6
  20. package/dist/background/tasks.service.js +13 -13
  21. package/dist/catalog/component-scope.controller.d.ts +18 -6
  22. package/dist/catalog/component-scope.controller.js +36 -13
  23. package/dist/catalog/component-scope.service.d.ts +11 -0
  24. package/dist/catalog/component-scope.service.js +4 -0
  25. package/dist/catalog/component-state.d.ts +8 -0
  26. package/dist/catalog/component-state.js +19 -11
  27. package/dist/catalog/marketplaces.service.d.ts +1 -1
  28. package/dist/catalog/plugin-import.js +4 -1
  29. package/dist/channels/channels.module.js +2 -2
  30. package/dist/channels/inbound.d.ts +8 -8
  31. package/dist/channels/inbound.js +31 -31
  32. package/dist/channels/phrases.d.ts +27 -27
  33. package/dist/channels/phrases.js +27 -27
  34. package/dist/config/docs.js +1 -1
  35. package/dist/config/schema.d.ts +2 -2
  36. package/dist/config/schema.js +21 -21
  37. package/dist/conversations/conversations.controller.d.ts +11 -11
  38. package/dist/conversations/conversations.controller.js +15 -15
  39. package/dist/conversations/conversations.module.d.ts +1 -1
  40. package/dist/conversations/conversations.module.js +7 -7
  41. package/dist/conversations/conversations.schemas.js +1 -1
  42. package/dist/conversations/conversations.service.d.ts +23 -14
  43. package/dist/conversations/conversations.service.js +28 -21
  44. package/dist/conversations/{environment-status.d.ts → device-status.d.ts} +9 -9
  45. package/dist/conversations/{environment-status.js → device-status.js} +18 -18
  46. package/dist/conversations/event-log.d.ts +1 -1
  47. package/dist/conversations/event-log.js +1 -1
  48. package/dist/conversations/events.d.ts +2 -1
  49. package/dist/conversations/events.js +2 -2
  50. package/dist/ctx/create-ctx.d.ts +10 -1
  51. package/dist/ctx/create-ctx.js +86 -25
  52. package/dist/db/kernel-schema.d.ts +39 -23
  53. package/dist/{environments → devices}/access.d.ts +19 -19
  54. package/dist/{environments → devices}/access.js +28 -28
  55. package/dist/devices/conversation-devices.d.ts +75 -0
  56. package/dist/{environments/conversation-environments.js → devices/conversation-devices.js} +71 -72
  57. package/dist/devices/devices.controller.d.ts +36 -0
  58. package/dist/{environments/environments.controller.js → devices/devices.controller.js} +37 -37
  59. package/dist/devices/devices.module.d.ts +5 -13
  60. package/dist/devices/devices.module.js +16 -29
  61. package/dist/devices/devices.schemas.d.ts +3 -0
  62. package/dist/devices/devices.schemas.js +14 -0
  63. package/dist/{environments/environments.service.d.ts → devices/devices.service.d.ts} +70 -70
  64. package/dist/{environments/environments.service.js → devices/devices.service.js} +122 -122
  65. package/dist/devices/dto.d.ts +13 -54
  66. package/dist/devices/dto.js +7 -39
  67. package/dist/devices/label.d.ts +11 -0
  68. package/dist/{environments → devices}/label.js +7 -7
  69. package/dist/{environments → devices}/legacy-cleanup.job.d.ts +3 -3
  70. package/dist/{environments → devices}/legacy-cleanup.job.js +3 -3
  71. package/dist/errors.d.ts +27 -25
  72. package/dist/errors.js +27 -25
  73. package/dist/evals/recorded-tools.d.ts +2 -2
  74. package/dist/evals/recorded-tools.js +6 -6
  75. package/dist/extensions/admin-ops.js +1 -0
  76. package/dist/extensions/admin-view.js +2 -0
  77. package/dist/extensions/effective-config.d.ts +6 -0
  78. package/dist/extensions/effective-config.js +18 -7
  79. package/dist/extensions/host.d.ts +13 -2
  80. package/dist/extensions/host.js +24 -3
  81. package/dist/extensions/instance.d.ts +12 -1
  82. package/dist/extensions/instance.js +13 -0
  83. package/dist/extensions/permission-view.d.ts +10 -1
  84. package/dist/files/previewers.d.ts +1 -1
  85. package/dist/files/previewers.js +1 -1
  86. package/dist/http/api.module.js +10 -6
  87. package/dist/http/gateway-routes.d.ts +1 -1
  88. package/dist/http/gateway-routes.js +1 -1
  89. package/dist/identity/settings.d.ts +32 -0
  90. package/dist/identity/settings.js +26 -2
  91. package/dist/infra/config/kernel-config.service.js +4 -4
  92. package/dist/kernel.module.js +3 -3
  93. package/dist/{devices → local-devices}/device-auth.controller.d.ts +6 -6
  94. package/dist/{devices → local-devices}/device-auth.controller.js +5 -5
  95. package/dist/{devices → local-devices}/device-auth.d.ts +20 -20
  96. package/dist/{devices → local-devices}/device-auth.js +51 -53
  97. package/dist/local-devices/device-auth.module.d.ts +7 -0
  98. package/dist/{devices → local-devices}/device-auth.module.js +5 -5
  99. package/dist/{devices → local-devices}/device-routes.d.ts +3 -3
  100. package/dist/{devices → local-devices}/device-routes.js +2 -2
  101. package/dist/local-devices/dto.d.ts +66 -0
  102. package/dist/local-devices/dto.js +44 -0
  103. package/dist/{devices → local-devices}/keys.d.ts +11 -11
  104. package/dist/{devices → local-devices}/keys.js +11 -11
  105. package/dist/{devices/device-gateway.d.ts → local-devices/local-device-gateway.d.ts} +14 -14
  106. package/dist/{devices/device-gateway.js → local-devices/local-device-gateway.js} +44 -44
  107. package/dist/{devices/device-provider.d.ts → local-devices/local-device-provider.d.ts} +15 -15
  108. package/dist/{devices/device-provider.js → local-devices/local-device-provider.js} +23 -23
  109. package/dist/local-devices/local-devices.module.d.ts +16 -0
  110. package/dist/local-devices/local-devices.module.js +44 -0
  111. package/dist/{devices → local-devices}/machines.d.ts +26 -26
  112. package/dist/{devices → local-devices}/machines.js +71 -71
  113. package/dist/{devices → local-devices}/relay.d.ts +15 -15
  114. package/dist/{devices → local-devices}/relay.js +55 -55
  115. package/dist/{devices → local-devices}/targets.d.ts +8 -8
  116. package/dist/{devices → local-devices}/targets.js +14 -14
  117. package/dist/orgs/offboarding.d.ts +6 -6
  118. package/dist/orgs/offboarding.js +12 -12
  119. package/dist/orgs/settings.service.d.ts +28 -0
  120. package/dist/orgs/settings.service.js +20 -1
  121. package/dist/platform/exclusions.js +48 -3
  122. package/dist/platform/operation.js +6 -2
  123. package/dist/platform/ops/admin.ops.js +8 -2
  124. package/dist/platform/ops/agents.ops.js +19 -6
  125. package/dist/platform/ops/conversations.ops.d.ts +3 -3
  126. package/dist/platform/ops/conversations.ops.js +9 -9
  127. package/dist/platform/ops/devices.ops.d.ts +26 -0
  128. package/dist/platform/ops/devices.ops.js +311 -0
  129. package/dist/platform/ops/org-settings.ops.js +2 -2
  130. package/dist/platform/ops/orgs.ops.js +1 -0
  131. package/dist/platform/ops/ui.ops.d.ts +8 -0
  132. package/dist/platform/ops/ui.ops.js +41 -0
  133. package/dist/platform/platform-ops.module.d.ts +3 -2
  134. package/dist/platform/platform-ops.module.js +8 -5
  135. package/dist/platform/route-schemas.js +6 -3
  136. package/dist/roles/gateway-role.d.ts +3 -3
  137. package/dist/roles/gateway-role.js +4 -4
  138. package/dist/roles/gateway.module.d.ts +1 -1
  139. package/dist/roles/gateway.module.js +5 -5
  140. package/dist/roles/worker.module.js +1 -1
  141. package/dist/runtime/context/build.d.ts +1 -1
  142. package/dist/runtime/context/build.js +6 -6
  143. package/dist/runtime/context/compaction.js +1 -1
  144. package/dist/runtime/context/{environment-paths.d.ts → device-paths.d.ts} +11 -11
  145. package/dist/runtime/context/{environment-paths.js → device-paths.js} +13 -13
  146. package/dist/runtime/maintenance.d.ts +1 -1
  147. package/dist/runtime/subagents/definition.d.ts +3 -2
  148. package/dist/runtime/subagents/definition.js +2 -1
  149. package/dist/sandbox/jobs.d.ts +13 -13
  150. package/dist/sandbox/jobs.js +20 -20
  151. package/dist/sandbox/leases.d.ts +9 -9
  152. package/dist/sandbox/leases.js +14 -14
  153. package/dist/sandbox/providers.d.ts +8 -8
  154. package/dist/sandbox/providers.js +8 -8
  155. package/dist/sandbox/sandbox.module.d.ts +1 -1
  156. package/dist/sandbox/sandbox.module.js +1 -1
  157. package/dist/sandbox/sandbox.service.d.ts +76 -76
  158. package/dist/sandbox/sandbox.service.js +230 -230
  159. package/dist/tools/builtins/builtin-tools.d.ts +2 -2
  160. package/dist/tools/builtins/builtin-tools.js +5 -5
  161. package/dist/tools/builtins/platform.js +1 -1
  162. package/dist/tools/builtins/sandbox/bash.js +6 -6
  163. package/dist/tools/builtins/sandbox/common.d.ts +25 -25
  164. package/dist/tools/builtins/sandbox/common.js +51 -51
  165. package/dist/tools/builtins/sandbox/edit.js +2 -2
  166. package/dist/tools/builtins/sandbox/index.d.ts +1 -1
  167. package/dist/tools/builtins/sandbox/index.js +1 -1
  168. package/dist/tools/builtins/sandbox/task-output.d.ts +6 -6
  169. package/dist/tools/builtins/sandbox/task-output.js +10 -10
  170. package/dist/tools/builtins/sandbox/task-stop.js +2 -2
  171. package/dist/tools/builtins/sandbox/write.js +2 -2
  172. package/dist/tools/builtins/skill-load.d.ts +1 -1
  173. package/dist/tools/builtins/skill-load.js +2 -2
  174. package/dist/tools/connector-account-policies.d.ts +51 -0
  175. package/dist/tools/connector-account-policies.js +165 -0
  176. package/dist/tools/connectors.controller.d.ts +35 -4
  177. package/dist/tools/connectors.controller.js +66 -7
  178. package/dist/tools/connectors.schemas.d.ts +11 -0
  179. package/dist/tools/connectors.schemas.js +11 -0
  180. package/dist/tools/connectors.service.d.ts +2 -0
  181. package/dist/tools/connectors.service.js +1 -0
  182. package/dist/tools/pipeline.d.ts +1 -1
  183. package/dist/tools/pipeline.js +4 -3
  184. package/dist/tools/policy.d.ts +23 -1
  185. package/dist/tools/policy.js +99 -24
  186. package/dist/tools/registry.d.ts +2 -2
  187. package/dist/tools/tools.module.js +6 -3
  188. package/dist/ui/admin-ui.controller.d.ts +56 -0
  189. package/dist/ui/admin-ui.controller.js +192 -0
  190. package/dist/ui/arrangement.d.ts +74 -0
  191. package/dist/ui/arrangement.js +98 -0
  192. package/dist/ui/embed-token.d.ts +2 -2
  193. package/dist/ui/embed-token.js +30 -5
  194. package/dist/ui/registry.d.ts +29 -14
  195. package/dist/ui/registry.js +31 -10
  196. package/dist/ui/slots.controller.d.ts +11 -0
  197. package/dist/ui/slots.controller.js +25 -0
  198. package/dist/ui/ui-stream.d.ts +20 -3
  199. package/dist/ui/ui-stream.js +25 -7
  200. package/dist/ui/ui.schemas.d.ts +66 -0
  201. package/dist/ui/ui.schemas.js +65 -0
  202. package/dist/ui/views.controller.d.ts +35 -49
  203. package/dist/ui/views.controller.js +48 -70
  204. package/dist/ui/views.d.ts +84 -18
  205. package/dist/ui/views.js +201 -38
  206. package/package.json +3 -3
  207. package/src/migrations/0104_connector_tool_account_policies.sql +16 -0
  208. package/src/migrations/0105_devices_rename.sql +136 -0
  209. package/dist/devices/device-auth.module.d.ts +0 -7
  210. package/dist/environments/conversation-environments.d.ts +0 -75
  211. package/dist/environments/dto.d.ts +0 -25
  212. package/dist/environments/dto.js +0 -12
  213. package/dist/environments/environments.controller.d.ts +0 -36
  214. package/dist/environments/environments.module.d.ts +0 -8
  215. package/dist/environments/environments.module.js +0 -31
  216. package/dist/environments/environments.schemas.d.ts +0 -3
  217. package/dist/environments/environments.schemas.js +0 -14
  218. package/dist/environments/label.d.ts +0 -11
  219. package/dist/platform/ops/environments.ops.d.ts +0 -26
  220. package/dist/platform/ops/environments.ops.js +0 -311
@@ -14,16 +14,16 @@ import { ApiError } from "../errors.js";
14
14
  import { KernelConfigService } from "../infra/config/kernel-config.service.js";
15
15
  import { LockService } from "../infra/lock/lock.service.js";
16
16
  import { KERNEL_BUS, KERNEL_LOGGER, REDIS } from "../infra/tokens.js";
17
- import { environmentNotFound } from "../sandbox/sandbox.service.js";
17
+ import { deviceNotFound } from "../sandbox/sandbox.service.js";
18
18
  import { deviceLeaseKey, deviceReqKey, deviceReqTopic, deviceResKey, deviceResTopic, } from "./keys.js";
19
19
  import { DeviceTargets } from "./targets.js";
20
- /** The environment's connected machine is not connected (no replica holds its lease). */
21
- export const environmentOffline = () => new ApiError("environment.offline", "The connected machine of this environment is offline. Start capybara-device on that machine, or pick another environment.");
20
+ /** The device's connected machine is not connected (no replica holds its lease). */
21
+ export const deviceOffline = () => new ApiError("device.offline", "The machine of this local device is offline. Start capybara-device on that machine, or pick another device.");
22
22
  /**
23
- * The environment's machine was disconnected (revoked, signed out): 403 `environment.forbidden`
23
+ * The device's machine was disconnected (revoked, signed out): 403 `device.forbidden`
24
24
  * with `details: [{ reason: "machine_revoked" }]`, so the tools tell the model what to ask for.
25
25
  */
26
- export const machineRevoked = () => new ApiError("environment.forbidden", "The connected machine of this environment was disconnected. Run capybara-device logout, then capybara-device login on it, or pick another environment.", [{ reason: "machine_revoked" }]);
26
+ export const machineRevoked = () => new ApiError("device.forbidden", "The machine of this local device was disconnected. Run capybara-device logout, then capybara-device login on it, or pick another device.", [{ reason: "machine_revoked" }]);
27
27
  /** The text a machine's `error` result reaches the model with (ruling R28). */
28
28
  export const ON_MACHINE_PREFIX = "On the connected machine: ";
29
29
  /** The longest one lease check of a waiting caller may take before it counts as "unknown". */
@@ -68,8 +68,8 @@ const GRACE_EXPIRED = Symbol("grace expired");
68
68
  const errMsg = (e) => (e instanceof Error ? e.message : String(e));
69
69
  /**
70
70
  * The caller side of the device relay (environments spec §13.4). A request goes onto the
71
- * environment's Redis list (`capk:device-req:<environmentId>`) and is announced on
72
- * `device.req.<environmentId>`; whichever gateway replica holds the machine's lease drains the
71
+ * device's Redis list (`capk:device-req:<deviceId>`) and is announced on
72
+ * `device.req.<deviceId>`; whichever gateway replica holds the machine's lease drains the
73
73
  * list and forwards the frames to the machine.
74
74
  *
75
75
  * **The holder's half of the wire** (Task 6 follows it): items on the list are `KernelReq` or
@@ -82,13 +82,13 @@ const errMsg = (e) => (e instanceof Error ? e.message : String(e));
82
82
  * The caller subscribes to its result topic before pushing and reads the result key once after,
83
83
  * and once more before giving up (the run-abort pattern; a publish lost while the subscriber
84
84
  * reconnects is still found). It waits within its own call for at most `deadlineMs` plus
85
- * `environments.device.reconnectGraceSeconds`; when the lease disappears meanwhile (checked every
85
+ * `devices.local.reconnectGraceSeconds`; when the lease disappears meanwhile (checked every
86
86
  * {@link HOLDER_POLL_MS}) the wait ends at most one grace later. A result produced while the
87
87
  * machine was disconnected arrives after it reconnects. Giving up (grace, abort, a bad result)
88
88
  * removes the request from the list, or queues a `cancel` when the machine already took it
89
89
  * (ruling R15). Nothing is kept in process memory beyond the call.
90
90
  */
91
- let DeviceRelay = class DeviceRelay {
91
+ let LocalDeviceRelay = class LocalDeviceRelay {
92
92
  redis;
93
93
  bus;
94
94
  locks;
@@ -104,35 +104,35 @@ let DeviceRelay = class DeviceRelay {
104
104
  this.targets = targets;
105
105
  }
106
106
  /** Whether a gateway replica holds the machine's connection now. */
107
- async holderLive(environmentId) {
108
- return (await this.locks.holderOf(deviceLeaseKey(environmentId))) !== null;
107
+ async holderLive(deviceId) {
108
+ return (await this.locks.holderOf(deviceLeaseKey(deviceId))) !== null;
109
109
  }
110
110
  /**
111
111
  * Queues `op` for the machine without waiting for an answer (ruling R14: `stop`). The caller
112
112
  * checks {@link holderLive} first; the frame waits on the list for the grace like any request.
113
113
  */
114
- async send(environmentId, op, args, deadlineMs) {
114
+ async send(deviceId, op, args, deadlineMs) {
115
115
  const id = randomUUID();
116
116
  const d = Math.max(1, Math.ceil(deadlineMs));
117
117
  const req = { type: "req", id, op, args, deadlineMs: d };
118
- await this.push(environmentId, JSON.stringify(req), this.graceMs() + d + LIST_TTL_MARGIN_MS);
119
- await this.bus.publish(deviceReqTopic(environmentId), { id });
118
+ await this.push(deviceId, JSON.stringify(req), this.graceMs() + d + LIST_TTL_MARGIN_MS);
119
+ await this.bus.publish(deviceReqTopic(deviceId), { id });
120
120
  }
121
121
  /**
122
- * Runs `op` on the environment's machine and returns its validated result. Throws
123
- * `environment.offline` when no replica holds the machine at send time or when the wait ran
124
- * out, but `environment.forbidden` ({@link machineRevoked}) when the machine was revoked and
125
- * `environment.not_found` when the environment was deleted (checked whenever the connection is
122
+ * Runs `op` on the device's machine and returns its validated result. Throws
123
+ * `device.offline` when no replica holds the machine at send time or when the wait ran
124
+ * out, but `device.forbidden` ({@link machineRevoked}) when the machine was revoked and
125
+ * `device.not_found` when the device was deleted (checked whenever the connection is
126
126
  * missing, ruling R27); the machine's failures map to `SandboxGoneError` (`gone`),
127
127
  * `FileSystemGoneError` (`fs_gone`) or a `ToolError` (`policy`, and `error` prefixed
128
128
  * {@link ON_MACHINE_PREFIX}); an invalid answer throws a `DeviceProtocolError`. On abort the
129
129
  * abort reason is thrown.
130
130
  */
131
- async call(environmentId, op, args, opts) {
131
+ async call(deviceId, op, args, opts) {
132
132
  const { signal } = opts;
133
133
  signal.throwIfAborted();
134
- if (!(await this.holderLive(environmentId))) {
135
- throw this.goneError(await this.targetState(environmentId)) ?? environmentOffline();
134
+ if (!(await this.holderLive(deviceId))) {
135
+ throw this.goneError(await this.targetState(deviceId)) ?? deviceOffline();
136
136
  }
137
137
  signal.throwIfAborted();
138
138
  const id = randomUUID();
@@ -164,8 +164,8 @@ let DeviceRelay = class DeviceRelay {
164
164
  signal.addEventListener("abort", onAbort, { once: true });
165
165
  // aborted while subscribing: nothing was sent yet
166
166
  signal.throwIfAborted();
167
- await this.push(environmentId, reqJson, listTtlMs);
168
- this.nudge(environmentId, id);
167
+ await this.push(deviceId, reqJson, listTtlMs);
168
+ this.nudge(deviceId, id);
169
169
  onStored(await this.redis.get(deviceResKey(id)));
170
170
  // wait until the result, the abort, or `giveUpAt`. The lease is checked every poll: its loss
171
171
  // caps `giveUpAt` at one grace from then (never extending it); a reconnect within that grace
@@ -178,7 +178,7 @@ let DeviceRelay = class DeviceRelay {
178
178
  return;
179
179
  try {
180
180
  // a stalled Redis must not hold the give-up back: no answer in time means "unknown"
181
- const live = await this.leaseCheck(environmentId);
181
+ const live = await this.leaseCheck(deviceId);
182
182
  if (live === true && capped) {
183
183
  capped = false;
184
184
  giveUpAt = fullGiveUpAt;
@@ -188,8 +188,8 @@ let DeviceRelay = class DeviceRelay {
188
188
  giveUpAt = Math.min(giveUpAt, Date.now() + graceMs);
189
189
  }
190
190
  if (live === false) {
191
- // a revoked machine or a deleted environment never comes back: fail at once
192
- const gone = this.goneError(await this.targetState(environmentId));
191
+ // a revoked machine or a deleted device never comes back: fail at once
192
+ const gone = this.goneError(await this.targetState(deviceId));
193
193
  if (gone) {
194
194
  settle({ error: gone });
195
195
  return;
@@ -197,7 +197,7 @@ let DeviceRelay = class DeviceRelay {
197
197
  }
198
198
  }
199
199
  catch (e) {
200
- this.log.warn("device relay lease check failed", { environmentId, error: errMsg(e) });
200
+ this.log.warn("device relay lease check failed", { deviceId, error: errMsg(e) });
201
201
  }
202
202
  if (stopped)
203
203
  return;
@@ -219,31 +219,31 @@ let DeviceRelay = class DeviceRelay {
219
219
  stopped = true;
220
220
  clearTimeout(timer);
221
221
  if ("res" in r) {
222
- await this.quietly("delete the result", environmentId, () => this.redis.del(deviceResKey(id)));
222
+ await this.quietly("delete the result", deviceId, () => this.redis.del(deviceResKey(id)));
223
223
  return this.resultOf(op, r.res);
224
224
  }
225
225
  if (r.error instanceof Error) {
226
226
  // a bad answer: drop it, give the request up, report the protocol error
227
- await this.quietly("delete the result", environmentId, () => this.redis.del(deviceResKey(id)));
227
+ await this.quietly("delete the result", deviceId, () => this.redis.del(deviceResKey(id)));
228
228
  }
229
- await this.giveUp(environmentId, id, reqJson, listTtlMs);
229
+ await this.giveUp(deviceId, id, reqJson, listTtlMs);
230
230
  if (r.error === ABORTED)
231
231
  throw signal.reason;
232
232
  if (r.error === GRACE_EXPIRED)
233
- throw environmentOffline();
233
+ throw deviceOffline();
234
234
  throw r.error;
235
235
  }
236
236
  finally {
237
237
  stopped = true;
238
238
  clearTimeout(timer);
239
239
  signal.removeEventListener("abort", onAbort);
240
- await this.quietly("unsubscribe", environmentId, unsubscribe);
240
+ await this.quietly("unsubscribe", deviceId, unsubscribe);
241
241
  }
242
242
  }
243
243
  /** {@link holderLive}, or undefined when it takes longer than {@link LEASE_CHECK_TIMEOUT_MS}. */
244
- async leaseCheck(environmentId) {
244
+ async leaseCheck(deviceId) {
245
245
  let timer;
246
- const check = this.holderLive(environmentId);
246
+ const check = this.holderLive(deviceId);
247
247
  check.catch(() => { }); // a late failure after the timeout is not unhandled
248
248
  try {
249
249
  return await Promise.race([
@@ -258,12 +258,12 @@ let DeviceRelay = class DeviceRelay {
258
258
  }
259
259
  }
260
260
  /**
261
- * The environment's state ({@link DeviceTargets}), or undefined when it cannot be read within
261
+ * The device's state ({@link DeviceTargets}), or undefined when it cannot be read within
262
262
  * {@link LEASE_CHECK_TIMEOUT_MS} (a failure only logs: the caller keeps its other rules).
263
263
  */
264
- async targetState(environmentId) {
264
+ async targetState(deviceId) {
265
265
  let timer;
266
- const read = this.targets.state(environmentId);
266
+ const read = this.targets.state(deviceId);
267
267
  read.catch(() => { });
268
268
  try {
269
269
  return await Promise.race([
@@ -274,8 +274,8 @@ let DeviceRelay = class DeviceRelay {
274
274
  ]);
275
275
  }
276
276
  catch (e) {
277
- this.log.warn("device relay could not read the environment state", {
278
- environmentId,
277
+ this.log.warn("device relay could not read the device state", {
278
+ deviceId,
279
279
  error: errMsg(e),
280
280
  });
281
281
  return undefined;
@@ -287,39 +287,39 @@ let DeviceRelay = class DeviceRelay {
287
287
  /** The error of a state that never comes back; undefined for `active` or unknown. */
288
288
  goneError(state) {
289
289
  if (state === "deleted")
290
- return environmentNotFound();
290
+ return deviceNotFound();
291
291
  if (state === "revoked")
292
292
  return machineRevoked();
293
293
  return undefined;
294
294
  }
295
295
  graceMs() {
296
- return this.config.get().environments.device.reconnectGraceSeconds * 1000;
296
+ return this.config.get().devices.local.reconnectGraceSeconds * 1000;
297
297
  }
298
- async push(environmentId, frame, ttlMs) {
299
- await this.redis.eval(PUSH_WITH_TTL, 1, deviceReqKey(environmentId), frame, String(ttlMs));
298
+ async push(deviceId, frame, ttlMs) {
299
+ await this.redis.eval(PUSH_WITH_TTL, 1, deviceReqKey(deviceId), frame, String(ttlMs));
300
300
  }
301
301
  /** Announces new frames to the holder; a lost nudge is picked up on the holder's next drain. */
302
- nudge(environmentId, id) {
303
- this.bus.publish(deviceReqTopic(environmentId), { id }).catch((e) => {
304
- this.log.warn("device relay nudge failed", { environmentId, error: errMsg(e) });
302
+ nudge(deviceId, id) {
303
+ this.bus.publish(deviceReqTopic(deviceId), { id }).catch((e) => {
304
+ this.log.warn("device relay nudge failed", { deviceId, error: errMsg(e) });
305
305
  });
306
306
  }
307
307
  /** Removes the request from the list, or queues a cancel when the machine took it (R15). */
308
- async giveUp(environmentId, id, reqJson, ttlMs) {
308
+ async giveUp(deviceId, id, reqJson, ttlMs) {
309
309
  const cancel = JSON.stringify({ type: "cancel", id });
310
- await this.quietly("give the request up", environmentId, async () => {
311
- const queued = await this.redis.eval(GIVE_UP, 1, deviceReqKey(environmentId), reqJson, cancel, String(ttlMs));
310
+ await this.quietly("give the request up", deviceId, async () => {
311
+ const queued = await this.redis.eval(GIVE_UP, 1, deviceReqKey(deviceId), reqJson, cancel, String(ttlMs));
312
312
  if (queued === 1)
313
- this.nudge(environmentId, id);
313
+ this.nudge(deviceId, id);
314
314
  });
315
315
  }
316
316
  /** Runs a side step whose failure must not replace the call's outcome: logged, swallowed. */
317
- async quietly(what, environmentId, fn) {
317
+ async quietly(what, deviceId, fn) {
318
318
  try {
319
319
  await fn();
320
320
  }
321
321
  catch (e) {
322
- this.log.warn(`device relay could not ${what}`, { environmentId, error: errMsg(e) });
322
+ this.log.warn(`device relay could not ${what}`, { deviceId, error: errMsg(e) });
323
323
  }
324
324
  }
325
325
  /** A stored / published `res` frame for request `id`, or the protocol error it carries. */
@@ -361,7 +361,7 @@ let DeviceRelay = class DeviceRelay {
361
361
  }
362
362
  }
363
363
  };
364
- DeviceRelay = __decorate([
364
+ LocalDeviceRelay = __decorate([
365
365
  Injectable(),
366
366
  __param(0, Inject(REDIS)),
367
367
  __param(1, Inject(KERNEL_BUS)),
@@ -369,5 +369,5 @@ DeviceRelay = __decorate([
369
369
  __param(3, Inject(KernelConfigService)),
370
370
  __param(4, Inject(KERNEL_LOGGER)),
371
371
  __param(5, Inject(DeviceTargets))
372
- ], DeviceRelay);
373
- export { DeviceRelay };
372
+ ], LocalDeviceRelay);
373
+ export { LocalDeviceRelay };
@@ -1,7 +1,7 @@
1
1
  import type { SandboxRoots } from "@capybara-hq/plugin-sdk";
2
2
  import { KernelDb } from "../infra/database/kernel-db.js";
3
3
  /**
4
- * Whether a device environment can still be served: `active` (live and an unrevoked machine),
4
+ * Whether a local device can still be served: `active` (live and an unrevoked machine),
5
5
  * `revoked` (live, no active machine: disconnected or signed out) or `deleted` (missing or
6
6
  * deleted, or its org deleted).
7
7
  */
@@ -9,20 +9,20 @@ export type DeviceTargetState = "active" | "revoked" | "deleted";
9
9
  /** Whether two sets of roots name the same three directories. */
10
10
  export declare const sameRoots: (a: SandboxRoots, b: SandboxRoots) => boolean;
11
11
  /**
12
- * Reads a device environment and its machine from PG for the relay and the `@device` provider.
12
+ * Reads a local device and its machine from PG for the relay and the `@local` provider.
13
13
  * The relay asks {@link state} when a machine has no connection, so a revoked machine or a deleted
14
- * environment fails a waiting call at once instead of after the reconnect grace (ruling R27).
14
+ * device fails a waiting call at once instead of after the reconnect grace (ruling R27).
15
15
  * Kept apart from `MachinesService` (which depends on the relay) so there is no dependency cycle.
16
16
  */
17
17
  export declare class DeviceTargets {
18
18
  private readonly db;
19
19
  constructor(db: KernelDb);
20
- state(environmentId: string): Promise<DeviceTargetState>;
21
- /** The org of a live environment, or null (missing or deleted). */
22
- orgOf(environmentId: string): Promise<string | null>;
20
+ state(deviceId: string): Promise<DeviceTargetState>;
21
+ /** The org of a live device, or null (missing or deleted). */
22
+ orgOf(deviceId: string): Promise<string | null>;
23
23
  /**
24
- * The roots the environment's active machine last reported in its hello frame, or null (no
24
+ * The roots the device's active machine last reported in its hello frame, or null (no
25
25
  * active machine, or none reported yet).
26
26
  */
27
- machineRoots(environmentId: string): Promise<SandboxRoots | null>;
27
+ machineRoots(deviceId: string): Promise<SandboxRoots | null>;
28
28
  }
@@ -13,9 +13,9 @@ const ROOT_KEYS = ["workspace", "skills", "tmp"];
13
13
  /** Whether two sets of roots name the same three directories. */
14
14
  export const sameRoots = (a, b) => ROOT_KEYS.every((k) => a[k] === b[k]);
15
15
  /**
16
- * Reads a device environment and its machine from PG for the relay and the `@device` provider.
16
+ * Reads a local device and its machine from PG for the relay and the `@local` provider.
17
17
  * The relay asks {@link state} when a machine has no connection, so a revoked machine or a deleted
18
- * environment fails a waiting call at once instead of after the reconnect grace (ruling R27).
18
+ * device fails a waiting call at once instead of after the reconnect grace (ruling R27).
19
19
  * Kept apart from `MachinesService` (which depends on the relay) so there is no dependency cycle.
20
20
  */
21
21
  let DeviceTargets = class DeviceTargets {
@@ -23,13 +23,13 @@ let DeviceTargets = class DeviceTargets {
23
23
  constructor(db) {
24
24
  this.db = db;
25
25
  }
26
- async state(environmentId) {
26
+ async state(deviceId) {
27
27
  const row = await this.db
28
- .selectFrom("environments as e")
28
+ .selectFrom("devices as e")
29
29
  .innerJoin("orgs as o", "o.id", "e.org_id")
30
- .leftJoin("environment_machines as m", (j) => j.onRef("m.environment_id", "=", "e.id").on("m.revoked_at", "is", null))
30
+ .leftJoin("device_machines as m", (j) => j.onRef("m.device_id", "=", "e.id").on("m.revoked_at", "is", null))
31
31
  .select(["e.id", "m.id as machine_id"])
32
- .where("e.id", "=", environmentId)
32
+ .where("e.id", "=", deviceId)
33
33
  .where("e.deleted_at", "is", null)
34
34
  .where("o.deleted_at", "is", null)
35
35
  .executeTakeFirst();
@@ -37,25 +37,25 @@ let DeviceTargets = class DeviceTargets {
37
37
  return "deleted";
38
38
  return row.machine_id === null ? "revoked" : "active";
39
39
  }
40
- /** The org of a live environment, or null (missing or deleted). */
41
- async orgOf(environmentId) {
40
+ /** The org of a live device, or null (missing or deleted). */
41
+ async orgOf(deviceId) {
42
42
  const row = await this.db
43
- .selectFrom("environments")
43
+ .selectFrom("devices")
44
44
  .select("org_id")
45
- .where("id", "=", environmentId)
45
+ .where("id", "=", deviceId)
46
46
  .where("deleted_at", "is", null)
47
47
  .executeTakeFirst();
48
48
  return row?.org_id ?? null;
49
49
  }
50
50
  /**
51
- * The roots the environment's active machine last reported in its hello frame, or null (no
51
+ * The roots the device's active machine last reported in its hello frame, or null (no
52
52
  * active machine, or none reported yet).
53
53
  */
54
- async machineRoots(environmentId) {
54
+ async machineRoots(deviceId) {
55
55
  const row = await this.db
56
- .selectFrom("environment_machines")
56
+ .selectFrom("device_machines")
57
57
  .select("roots")
58
- .where("environment_id", "=", environmentId)
58
+ .where("device_id", "=", deviceId)
59
59
  .where("revoked_at", "is", null)
60
60
  .executeTakeFirst();
61
61
  const r = row?.roots;
@@ -30,8 +30,8 @@ export interface OffboardResult {
30
30
  runs: InFlightRun[];
31
31
  /** 负责的 agent 转交给的所有者;没有转交时为 null。 */
32
32
  newOwnerId: string | null;
33
- /** The person's environments in the org, soft-deleted here; destroyed by a job after commit. */
34
- environments: string[];
33
+ /** The person's devices in the org, soft-deleted here; destroyed by a job after commit. */
34
+ devices: string[];
35
35
  /** Conversations whose queued messages were deleted (`changed: queue` after commit). */
36
36
  queueConversations: string[];
37
37
  }
@@ -66,7 +66,7 @@ export declare class Offboarding {
66
66
  private readonly redis;
67
67
  private readonly registry;
68
68
  private readonly eventLog;
69
- /** The departed member's environments: one-off maintenance jobs, destroyed by a worker. */
69
+ /** The departed member's devices: one-off maintenance jobs, destroyed by a worker. */
70
70
  private readonly sandboxJobs;
71
71
  /** Stops the departed member's runs (`by: null`, a system action). */
72
72
  private readonly runs;
@@ -75,7 +75,7 @@ export declare class Offboarding {
75
75
  private readonly questions;
76
76
  readonly log: KernelLogger;
77
77
  constructor(db: KernelDb, txHost: TransactionHost, sessions: Pick<SessionService, "markRevoked">, redis: Pick<Redis, "set" | "exists">, registry: Pick<ExtensionRegistry, "list">, eventLog: Pick<EventLog, "notify" | "publishChanged">,
78
- /** The departed member's environments: one-off maintenance jobs, destroyed by a worker. */
78
+ /** The departed member's devices: one-off maintenance jobs, destroyed by a worker. */
79
79
  sandboxJobs: Pick<SandboxJobs, "destroy">,
80
80
  /** Stops the departed member's runs (`by: null`, a system action). */
81
81
  runs: OffboardingRunControl, credentials: Pick<Credentials, "clearAccount">, approvals: Pick<ApprovalsService, "cancelPendingFor">, questions: Pick<QuestionsService, "cancelPendingFor">, log: KernelLogger);
@@ -95,7 +95,7 @@ export declare class Offboarding {
95
95
  * 以及成员关系自身的 `directory:<插件>` 来源,不论此人当初是不是由通讯录加入(手动重新加入后再被移出也不会被同步加回);
96
96
  * 同步不再把此人加回,直到有人手动重新加入({@link MembersService.add} 删除排除);
97
97
  * 8. 审计 `org.member.offboarded`;
98
- * 9. Soft-deletes the person's environments in the org and clears their default row (the
98
+ * 9. Soft-deletes the person's devices in the org and clears their default row (the
99
99
  * instances and files are destroyed after the commit, best effort; the hourly sweep finishes
100
100
  * any left).
101
101
  */
@@ -108,7 +108,7 @@ export declare class Offboarding {
108
108
  /**
109
109
  * After the commit (callers register it with the infra `afterCommit` inside their transaction):
110
110
  * writes the session revocation markers, publishes the cancelled events, stops the person's
111
- * runs in the org and schedules destroying their environments. A failing step only logs a
111
+ * runs in the org and schedules destroying their devices. A failing step only logs a
112
112
  * warning.
113
113
  */
114
114
  afterOffboard(r: OffboardResult): Promise<void>;
@@ -63,7 +63,7 @@ let Offboarding = class Offboarding {
63
63
  questions;
64
64
  log;
65
65
  constructor(db, txHost, sessions, redis, registry, eventLog,
66
- /** The departed member's environments: one-off maintenance jobs, destroyed by a worker. */
66
+ /** The departed member's devices: one-off maintenance jobs, destroyed by a worker. */
67
67
  sandboxJobs,
68
68
  /** Stops the departed member's runs (`by: null`, a system action). */
69
69
  runs, credentials, approvals, questions, log) {
@@ -96,7 +96,7 @@ let Offboarding = class Offboarding {
96
96
  * 以及成员关系自身的 `directory:<插件>` 来源,不论此人当初是不是由通讯录加入(手动重新加入后再被移出也不会被同步加回);
97
97
  * 同步不再把此人加回,直到有人手动重新加入({@link MembersService.add} 删除排除);
98
98
  * 8. 审计 `org.member.offboarded`;
99
- * 9. Soft-deletes the person's environments in the org and clears their default row (the
99
+ * 9. Soft-deletes the person's devices in the org and clears their default row (the
100
100
  * instances and files are destroyed after the commit, best effort; the hourly sweep finishes
101
101
  * any left).
102
102
  */
@@ -259,8 +259,8 @@ let Offboarding = class Offboarding {
259
259
  .where("id", "in", queueConversations)
260
260
  .execute();
261
261
  }
262
- const environments = await this.db
263
- .updateTable("environments")
262
+ const devices = await this.db
263
+ .updateTable("devices")
264
264
  .set({ deleted_at: sql `now()`, updated_at: sql `now()` })
265
265
  .where("org_id", "=", orgId)
266
266
  .where("owner_kind", "=", "personal")
@@ -269,7 +269,7 @@ let Offboarding = class Offboarding {
269
269
  .returning("id")
270
270
  .execute();
271
271
  await this.db
272
- .deleteFrom("environment_defaults")
272
+ .deleteFrom("device_defaults")
273
273
  .where("org_id", "=", orgId)
274
274
  .where("account_id", "=", accountId)
275
275
  .execute();
@@ -293,7 +293,7 @@ let Offboarding = class Offboarding {
293
293
  channelThreadsArchived: Number(threads.numUpdatedRows),
294
294
  toolGrantsDeleted: Number(grants.numDeletedRows),
295
295
  ...(excluded.length > 0 && { directoryExcluded: excluded }),
296
- ...(environments.length > 0 && { environmentsDeleted: environments.length }),
296
+ ...(devices.length > 0 && { devicesDeleted: devices.length }),
297
297
  ...(queued.length > 0 && { queuedMessagesDeleted: queued.length }),
298
298
  },
299
299
  });
@@ -307,7 +307,7 @@ let Offboarding = class Offboarding {
307
307
  status: r.status,
308
308
  })),
309
309
  newOwnerId: agents.length > 0 ? owner.account_id : null,
310
- environments: environments.map((r) => r.id),
310
+ devices: devices.map((r) => r.id),
311
311
  queueConversations,
312
312
  };
313
313
  }
@@ -336,7 +336,7 @@ let Offboarding = class Offboarding {
336
336
  /**
337
337
  * After the commit (callers register it with the infra `afterCommit` inside their transaction):
338
338
  * writes the session revocation markers, publishes the cancelled events, stops the person's
339
- * runs in the org and schedules destroying their environments. A failing step only logs a
339
+ * runs in the org and schedules destroying their devices. A failing step only logs a
340
340
  * warning.
341
341
  */
342
342
  async afterOffboard(r) {
@@ -365,13 +365,13 @@ let Offboarding = class Offboarding {
365
365
  }
366
366
  for (const run of r.runs)
367
367
  await this.stopRun(run);
368
- if (r.environments.length > 0) {
368
+ if (r.devices.length > 0) {
369
369
  try {
370
- await this.sandboxJobs.destroy(r.environments);
370
+ await this.sandboxJobs.destroy(r.devices);
371
371
  }
372
372
  catch (err) {
373
- log.warn("offboarding: cannot schedule environment destruction", {
374
- count: r.environments.length,
373
+ log.warn("offboarding: cannot schedule device destruction", {
374
+ count: r.devices.length,
375
375
  err,
376
376
  });
377
377
  }
@@ -21,6 +21,8 @@ import { OrgLock } from "./org-lock.js";
21
21
  * own (run context spec §3.3); an invalid stored value reads as absent.
22
22
  * - `theme`: the org's default theme and whether members may pick their own (themes spec §7);
23
23
  * absent (or an invalid stored value) = follow the platform, personal picks allowed.
24
+ * - `uiArrangement`: per slot, the extension contributions the org hides and their order (UI slots
25
+ * spec §5); a slot with no entry follows the platform's; an invalid stored value reads as absent.
24
26
  */
25
27
  /** 组织沙箱出站白名单的主机数上限。 */
26
28
  export declare const ORG_SANDBOX_ALLOW_HOSTS_MAX = 200;
@@ -85,6 +87,19 @@ export declare const OrgSettingsSchema: z.ZodObject<{
85
87
  locked: "locked";
86
88
  }>;
87
89
  }, z.core.$strict>>>;
90
+ uiArrangement: z.ZodCatch<z.ZodOptional<z.ZodRecord<z.ZodEnum<{
91
+ "agent.detail.section": "agent.detail.section";
92
+ "conversation.panel": "conversation.panel";
93
+ "conversation.properties.section": "conversation.properties.section";
94
+ "conversation.tool-result.card": "conversation.tool-result.card";
95
+ "file.preview": "file.preview";
96
+ "org.page": "org.page";
97
+ "org.settings.section": "org.settings.section";
98
+ "platform.page": "platform.page";
99
+ }> & z.core.$partial, z.ZodObject<{
100
+ hidden: z.ZodArray<z.ZodString>;
101
+ order: z.ZodArray<z.ZodString>;
102
+ }, z.core.$strict>>>>;
88
103
  }, z.core.$strip>;
89
104
  export type OrgSettings = z.infer<typeof OrgSettingsSchema>;
90
105
  /** PATCH 体:只改给出的字段;未知字段 → 400。 */
@@ -122,6 +137,19 @@ export declare const OrgSettingsPatchSchema: z.ZodObject<{
122
137
  locked: "locked";
123
138
  }>;
124
139
  }, z.core.$strict>>>;
140
+ uiArrangement: z.ZodOptional<z.ZodRecord<z.ZodEnum<{
141
+ "agent.detail.section": "agent.detail.section";
142
+ "conversation.panel": "conversation.panel";
143
+ "conversation.properties.section": "conversation.properties.section";
144
+ "conversation.tool-result.card": "conversation.tool-result.card";
145
+ "file.preview": "file.preview";
146
+ "org.page": "org.page";
147
+ "org.settings.section": "org.settings.section";
148
+ "platform.page": "platform.page";
149
+ }> & z.core.$partial, z.ZodNullable<z.ZodObject<{
150
+ hidden: z.ZodArray<z.ZodString>;
151
+ order: z.ZodArray<z.ZodString>;
152
+ }, z.core.$strict>>>>;
125
153
  }, z.core.$strict>;
126
154
  export type OrgSettingsPatch = z.infer<typeof OrgSettingsPatchSchema>;
127
155
  /** 部署的沙箱出站(`sandbox.limits.egress` + `allowHosts`)。 */
@@ -19,6 +19,7 @@ import { TransactionHost } from "../infra/database/transaction-host.js";
19
19
  import { Transactional } from "../infra/database/transactional.decorator.js";
20
20
  import { assertThemeOnIn } from "../themes/theme-state.js";
21
21
  import { IanaTimeZone } from "../time-zone.js";
22
+ import { mergeArrangement, OrgUiArrangementPatchSchema, UiArrangementSchema, } from "../ui/arrangement.js";
22
23
  import { OrgLock } from "./org-lock.js";
23
24
  /**
24
25
  * 组织设置(`capybara.orgs.settings` jsonb)。读取宽松:缺失或非法的字段按默认值,未知键保留不动
@@ -36,6 +37,8 @@ import { OrgLock } from "./org-lock.js";
36
37
  * own (run context spec §3.3); an invalid stored value reads as absent.
37
38
  * - `theme`: the org's default theme and whether members may pick their own (themes spec §7);
38
39
  * absent (or an invalid stored value) = follow the platform, personal picks allowed.
40
+ * - `uiArrangement`: per slot, the extension contributions the org hides and their order (UI slots
41
+ * spec §5); a slot with no entry follows the platform's; an invalid stored value reads as absent.
39
42
  */
40
43
  /** 组织沙箱出站白名单的主机数上限。 */
41
44
  export const ORG_SANDBOX_ALLOW_HOSTS_MAX = 200;
@@ -87,6 +90,8 @@ export const OrgSettingsSchema = z.object({
87
90
  timeZone: IanaTimeZone.optional().catch(undefined),
88
91
  /** The org's default theme and personal lock (themes spec §7); absent = {@link DEFAULT_ORG_THEME}. */
89
92
  theme: OrgThemeSettingSchema.optional().catch(undefined),
93
+ /** Hidden and ordered extension contributions per slot; absent = follow the platform. */
94
+ uiArrangement: UiArrangementSchema.optional().catch(undefined),
90
95
  });
91
96
  /** PATCH 体:只改给出的字段;未知字段 → 400。 */
92
97
  export const OrgSettingsPatchSchema = z
@@ -106,6 +111,11 @@ export const OrgSettingsPatchSchema = z
106
111
  * follows the platform) and whether members may pick their own; null clears it.
107
112
  */
108
113
  theme: OrgThemeSettingSchema.nullable().optional(),
114
+ /**
115
+ * Per slot, the entry to hide / order contributions with (replaces that slot's stored entry);
116
+ * null removes a slot's entry (it follows the platform again). `platform.page` is refused.
117
+ */
118
+ uiArrangement: OrgUiArrangementPatchSchema.optional(),
109
119
  })
110
120
  .strict();
111
121
  /**
@@ -220,7 +230,14 @@ let OrgSettingsService = class OrgSettingsService {
220
230
  .filter(([, v]) => v !== undefined)
221
231
  // null clears sandboxEgress / wakeOnBackgroundTask: follow the deployment default again
222
232
  .map(([k, v]) => [k, v === null ? undefined : v]));
233
+ const arrangementPatch = patch.uiArrangement;
234
+ delete changes.uiArrangement;
223
235
  const after = { ...before, ...changes };
236
+ if (arrangementPatch !== undefined) {
237
+ after.uiArrangement = mergeArrangement(before.uiArrangement, arrangementPatch);
238
+ if (Object.keys(after.uiArrangement).length === 0)
239
+ delete after.uiArrangement;
240
+ }
224
241
  if (after.sandboxEgress === undefined)
225
242
  delete after.sandboxEgress;
226
243
  if (after.wakeOnBackgroundTask === undefined)
@@ -229,6 +246,8 @@ let OrgSettingsService = class OrgSettingsService {
229
246
  delete after.timeZone;
230
247
  if (after.theme === undefined)
231
248
  delete after.theme;
249
+ if (after.uiArrangement === undefined)
250
+ delete after.uiArrangement;
232
251
  if (sameSettings(before, after))
233
252
  return before;
234
253
  // Lock order: the org row (orgLock above), then the theme's catalog item and its row FOR
@@ -237,7 +256,7 @@ let OrgSettingsService = class OrgSettingsService {
237
256
  await assertThemeOnIn(this.db.db, orgId, patch.theme.id);
238
257
  }
239
258
  // 与存量合并:保留本版本不认识的键(清除的 sandboxEgress 不留在存量里)
240
- const { sandboxEgress: _old, wakeOnBackgroundTask: _oldWake, timeZone: _oldZone, theme: _oldTheme, ...rest } = stored;
259
+ const { sandboxEgress: _old, wakeOnBackgroundTask: _oldWake, timeZone: _oldZone, theme: _oldTheme, uiArrangement: _oldArrangement, ...rest } = stored;
241
260
  await this.db
242
261
  .updateTable("orgs")
243
262
  .set({ settings: JSON.stringify({ ...rest, ...after }) })