@opengeni/core 3.0.1 → 3.1.2

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 (257) hide show
  1. package/README.md +4 -4
  2. package/dist/access/index.d.ts +131 -7
  3. package/dist/access/service-initiator.d.ts +3 -0
  4. package/dist/application/archived-session-imports.d.ts +6 -0
  5. package/dist/application/composer-submit.d.ts +1 -1
  6. package/dist/application/connect-operation.d.ts +6 -0
  7. package/dist/application/external-workspace-members.d.ts +13 -1
  8. package/dist/application/modal-native-live-declaration-join.d.ts +37 -0
  9. package/dist/application/modal-native-original-configuration.d.ts +79 -0
  10. package/dist/application/modal-native-worker-host-transport.d.ts +297 -0
  11. package/dist/application/new-session-drafts.d.ts +29 -2
  12. package/dist/application/organization-privacy.d.ts +57 -0
  13. package/dist/application/prepared-mcp-connection.d.ts +31 -0
  14. package/dist/application/sandbox-recovery.d.ts +58 -0
  15. package/dist/application/session-commands.d.ts +3 -0
  16. package/dist/application/session-tenancy.d.ts +13 -4
  17. package/dist/billing/agent-run-admission.d.ts +21 -0
  18. package/dist/billing/limits.d.ts +27 -1
  19. package/dist/billing/model-call-admission.d.ts +34 -0
  20. package/dist/billing/model-usage-settlement.d.ts +112 -0
  21. package/dist/canonical-human-identities.js +1 -1
  22. package/dist/{chunk-VCZFFEPB.js → chunk-7T2SZ7BI.js} +16 -2
  23. package/dist/chunk-7T2SZ7BI.js.map +1 -0
  24. package/dist/{chunk-ZYCNLYGH.js → chunk-OMJFIIWK.js} +178 -12
  25. package/dist/chunk-OMJFIIWK.js.map +1 -0
  26. package/dist/{chunk-H7X52RI2.js → chunk-QI2M2B2K.js} +83 -8
  27. package/dist/chunk-QI2M2B2K.js.map +1 -0
  28. package/dist/{chunk-OF65T3PM.js → chunk-TTWTXQB2.js} +94 -6
  29. package/dist/chunk-TTWTXQB2.js.map +1 -0
  30. package/dist/codex-model-availability.d.ts +55 -0
  31. package/dist/default-session-model.d.ts +192 -0
  32. package/dist/dependencies.d.ts +41 -7
  33. package/dist/developer-setup-credential-ceiling.d.ts +10 -0
  34. package/dist/domain/agent-config-resolution.d.ts +49 -0
  35. package/dist/domain/automations.d.ts +8 -1
  36. package/dist/domain/capabilities.d.ts +112 -8
  37. package/dist/domain/connection-account-selection-error.d.ts +8 -0
  38. package/dist/domain/connector-tool-permissions.d.ts +62 -0
  39. package/dist/domain/direct-model-provider.d.ts +3 -0
  40. package/dist/domain/editable-artifacts/agent-application.d.ts +10 -1
  41. package/dist/domain/editable-artifacts/durable-export.d.ts +22 -1
  42. package/dist/domain/editable-artifacts/ports.d.ts +10 -0
  43. package/dist/domain/editable-artifacts/service.d.ts +3 -1
  44. package/dist/domain/editable-artifacts/types.d.ts +1 -1
  45. package/dist/domain/encryption-key.d.ts +12 -0
  46. package/dist/domain/insights-timing.d.ts +16 -0
  47. package/dist/domain/insights-usage.d.ts +27 -0
  48. package/dist/domain/insights.d.ts +21 -3
  49. package/dist/domain/knowledge-files.d.ts +1 -1
  50. package/dist/domain/knowledge-preparation.d.ts +10 -0
  51. package/dist/domain/knowledge-preparation.test.d.ts +1 -0
  52. package/dist/domain/knowledge-search.d.ts +15 -76
  53. package/dist/domain/mcp-account-bindings.d.ts +17 -0
  54. package/dist/domain/mcp-account-routes.d.ts +21 -0
  55. package/dist/domain/native-mcp-connection-admission.d.ts +3 -0
  56. package/dist/domain/organization-integration-catalog.d.ts +15 -0
  57. package/dist/domain/personal-connection-delegations.d.ts +73 -12
  58. package/dist/domain/pr-review.d.ts +121 -6
  59. package/dist/domain/realtime-voice-billing.d.ts +103 -0
  60. package/dist/domain/resources.d.ts +17 -1
  61. package/dist/domain/scheduled-task-access.d.ts +265 -0
  62. package/dist/domain/scheduled-task-account-audit.d.ts +27 -0
  63. package/dist/domain/scheduled-task-audience.d.ts +70 -0
  64. package/dist/domain/scheduled-tasks.d.ts +61 -18
  65. package/dist/domain/session-connection-accounts.d.ts +53 -0
  66. package/dist/domain/session-file-access.d.ts +4 -0
  67. package/dist/domain/session-native-connection-replacement.d.ts +14 -0
  68. package/dist/domain/session-start-timing.d.ts +7 -0
  69. package/dist/domain/session-tool-policy.d.ts +102 -4
  70. package/dist/domain/sessions.d.ts +179 -15
  71. package/dist/domain/site-audience.d.ts +52 -0
  72. package/dist/domain/skills.d.ts +29 -1
  73. package/dist/domain/slack-bot.d.ts +46 -1
  74. package/dist/domain/slack-publication-secret-safety.d.ts +1 -1
  75. package/dist/domain/vaults/index.d.ts +7 -0
  76. package/dist/domain/vaults/mapping.d.ts +25 -0
  77. package/dist/domain/vaults/provider-registry.d.ts +72 -0
  78. package/dist/domain/vaults/provider-sync.d.ts +176 -0
  79. package/dist/domain/vaults/provider-writes.d.ts +57 -0
  80. package/dist/domain/vaults/secret-resolver.d.ts +34 -0
  81. package/dist/domain/vaults/types.d.ts +24 -0
  82. package/dist/domain/vaults/vault-service.d.ts +35 -0
  83. package/dist/domain/voice-input-billing.d.ts +77 -0
  84. package/dist/domain/web-search-billing.d.ts +51 -0
  85. package/dist/domain/workspace-credits.d.ts +7 -0
  86. package/dist/editable-artifact-live/application.d.ts +2 -1
  87. package/dist/editable-artifact-live/server.d.ts +5 -1
  88. package/dist/editable-artifact-live/ticket.d.ts +2 -1
  89. package/dist/editable-artifact-live/types.d.ts +9 -0
  90. package/dist/editable-artifact-live.js +2 -2
  91. package/dist/editable-artifacts.js +10 -2
  92. package/dist/goal-admission.d.ts +627 -0
  93. package/dist/host-session-usage-limit.d.ts +15 -0
  94. package/dist/index.d.ts +43 -8
  95. package/dist/index.js +22682 -11743
  96. package/dist/index.js.map +1 -1
  97. package/dist/managed-session.d.ts +110 -7
  98. package/dist/model-call.d.ts +58 -0
  99. package/dist/model-catalog.d.ts +29 -3
  100. package/dist/model-provider-credentials.d.ts +43 -0
  101. package/dist/product-usage-metrics.d.ts +30 -0
  102. package/dist/rigs/index.d.ts +1 -13
  103. package/dist/sandbox/fleet.d.ts +58 -4
  104. package/dist/sandbox/routing.d.ts +42 -2
  105. package/dist/sandbox/runtime-settings.d.ts +27 -17
  106. package/dist/session-authorization.d.ts +10 -0
  107. package/dist/session-usage-limit-metrics.d.ts +60 -0
  108. package/dist/session-usage-limits.d.ts +89 -0
  109. package/dist/site-agent-clamp.d.ts +71 -0
  110. package/dist/site-session-origin.d.ts +39 -2
  111. package/dist/transcription.d.ts +120 -3
  112. package/dist/turn-surface.d.ts +20 -0
  113. package/dist/user-presence.d.ts +32 -0
  114. package/dist/workflow-wake-contract.d.ts +13 -1
  115. package/package.json +15 -22
  116. package/src/access/index.ts +961 -38
  117. package/src/access/service-initiator.ts +50 -0
  118. package/src/application/api-integration-servers.ts +9 -0
  119. package/src/application/archived-session-imports.ts +161 -0
  120. package/src/application/composer-submit.ts +3 -2
  121. package/src/application/connect-authority.ts +3 -3
  122. package/src/application/connect-operation.ts +37 -3
  123. package/src/application/external-continuation.ts +20 -3
  124. package/src/application/external-link-work-admission.ts +3 -0
  125. package/src/application/external-workspace-members.ts +51 -3
  126. package/src/application/modal-native-live-declaration-join.ts +196 -0
  127. package/src/application/modal-native-original-configuration.ts +261 -0
  128. package/src/application/modal-native-worker-host-transport.ts +263 -0
  129. package/src/application/new-session-drafts.ts +172 -55
  130. package/src/application/organization-privacy.ts +161 -0
  131. package/src/application/prepared-mcp-connection.ts +133 -0
  132. package/src/application/sandbox-recovery.ts +78 -0
  133. package/src/application/session-commands.ts +73 -1
  134. package/src/application/session-mcp-credential-rotation.ts +61 -18
  135. package/src/application/session-tenancy.ts +262 -44
  136. package/src/application/user-resource-grants.ts +1 -29
  137. package/src/billing/agent-run-admission.ts +117 -0
  138. package/src/billing/limits.ts +138 -14
  139. package/src/billing/model-call-admission.ts +118 -0
  140. package/src/billing/model-usage-settlement.ts +472 -0
  141. package/src/codex-model-availability.ts +264 -0
  142. package/src/default-session-model.ts +692 -0
  143. package/src/dependencies.ts +48 -6
  144. package/src/developer-setup-credential-ceiling.ts +60 -0
  145. package/src/domain/agent-config-resolution.ts +148 -0
  146. package/src/domain/automations.ts +76 -2
  147. package/src/domain/capabilities.ts +830 -181
  148. package/src/domain/connection-account-selection-error.ts +19 -0
  149. package/src/domain/connector-tool-permissions.ts +572 -0
  150. package/src/domain/direct-model-provider.ts +62 -0
  151. package/src/domain/editable-artifacts/agent-application.ts +44 -6
  152. package/src/domain/editable-artifacts/durable-export.ts +100 -5
  153. package/src/domain/editable-artifacts/in-memory.ts +6 -0
  154. package/src/domain/editable-artifacts/ports.ts +11 -0
  155. package/src/domain/editable-artifacts/service.ts +20 -0
  156. package/src/domain/editable-artifacts/types.ts +1 -1
  157. package/src/domain/encryption-key.ts +26 -0
  158. package/src/domain/file-owner.ts +16 -1
  159. package/src/domain/github-action-policies.ts +1 -1
  160. package/src/domain/github-skill-source.ts +3 -1
  161. package/src/domain/insights-timing.ts +95 -0
  162. package/src/domain/insights-usage.ts +67 -0
  163. package/src/domain/insights.ts +167 -43
  164. package/src/domain/knowledge-files.ts +10 -3
  165. package/src/domain/knowledge-messages.ts +1 -0
  166. package/src/domain/knowledge-preparation.test.ts +276 -0
  167. package/src/domain/knowledge-preparation.ts +147 -0
  168. package/src/domain/knowledge-search.test.ts +617 -5
  169. package/src/domain/knowledge-search.ts +240 -23
  170. package/src/domain/mcp-account-bindings.ts +169 -0
  171. package/src/domain/mcp-account-routes.ts +152 -0
  172. package/src/domain/native-mcp-connection-admission.ts +14 -0
  173. package/src/domain/organization-integration-catalog.ts +88 -0
  174. package/src/domain/personal-connection-delegations.ts +399 -257
  175. package/src/domain/pr-review.ts +24 -8
  176. package/src/domain/realtime-voice-billing.ts +425 -0
  177. package/src/domain/resources.ts +50 -4
  178. package/src/domain/scheduled-task-access.ts +1089 -0
  179. package/src/domain/scheduled-task-account-audit.ts +90 -0
  180. package/src/domain/scheduled-task-audience.ts +249 -0
  181. package/src/domain/scheduled-tasks.ts +1313 -374
  182. package/src/domain/session-connection-accounts.ts +239 -0
  183. package/src/domain/session-file-access.ts +47 -0
  184. package/src/domain/session-native-connection-replacement.ts +64 -0
  185. package/src/domain/session-start-timing.ts +60 -0
  186. package/src/domain/session-tool-policy.ts +489 -14
  187. package/src/domain/sessions.ts +2254 -717
  188. package/src/domain/site-audience.ts +130 -0
  189. package/src/domain/skills.ts +9 -0
  190. package/src/domain/slack-bot.ts +141 -4
  191. package/src/domain/slack-publication-secret-safety.ts +1 -1
  192. package/src/domain/vaults/index.ts +19 -0
  193. package/src/domain/vaults/mapping.ts +190 -0
  194. package/src/domain/vaults/provider-registry.ts +201 -0
  195. package/src/domain/vaults/provider-sync.ts +531 -0
  196. package/src/domain/vaults/provider-writes.ts +288 -0
  197. package/src/domain/vaults/secret-resolver.ts +464 -0
  198. package/src/domain/vaults/types.ts +26 -0
  199. package/src/domain/vaults/vault-service.ts +1316 -0
  200. package/src/domain/voice-input-billing.ts +330 -0
  201. package/src/domain/web-search-billing.ts +169 -0
  202. package/src/domain/workspace-credits.ts +13 -0
  203. package/src/editable-artifact-live/application.ts +8 -1
  204. package/src/editable-artifact-live/server.ts +154 -6
  205. package/src/editable-artifact-live/ticket.ts +86 -1
  206. package/src/editable-artifact-live/types.ts +10 -0
  207. package/src/goal-admission.ts +220 -0
  208. package/src/host-session-usage-limit.ts +25 -0
  209. package/src/index.ts +75 -11
  210. package/src/managed-session.ts +137 -6
  211. package/src/model-call.ts +79 -0
  212. package/src/model-catalog.ts +345 -91
  213. package/src/model-provider-credentials.ts +394 -0
  214. package/src/product-usage-metrics.ts +101 -0
  215. package/src/rigs/index.ts +23 -111
  216. package/src/sandbox/fleet.ts +253 -28
  217. package/src/sandbox/routing.ts +204 -43
  218. package/src/sandbox/runtime-settings.ts +159 -85
  219. package/src/session-authorization.ts +172 -23
  220. package/src/session-usage-limit-metrics.ts +95 -0
  221. package/src/session-usage-limits.ts +415 -0
  222. package/src/site-agent-clamp.ts +226 -0
  223. package/src/site-session-origin.ts +84 -5
  224. package/src/transcription.ts +123 -4
  225. package/src/turn-surface.ts +47 -0
  226. package/src/user-presence.ts +134 -0
  227. package/src/workflow-wake-contract.ts +14 -1
  228. package/dist/application/host-mcp-owner.d.ts +0 -6
  229. package/dist/application/host-mcp-resolvers.d.ts +0 -13
  230. package/dist/chunk-5WZYFL45.js +0 -266
  231. package/dist/chunk-5WZYFL45.js.map +0 -1
  232. package/dist/chunk-H7X52RI2.js.map +0 -1
  233. package/dist/chunk-OF65T3PM.js.map +0 -1
  234. package/dist/chunk-VCZFFEPB.js.map +0 -1
  235. package/dist/chunk-ZYCNLYGH.js.map +0 -1
  236. package/dist/domain/connection-authority.d.ts +0 -97
  237. package/dist/domain/connection-authority.js +0 -169
  238. package/dist/domain/connection-authority.js.map +0 -1
  239. package/dist/domain/environments.d.ts +0 -33
  240. package/dist/domain/host-mcp-authority-source-admission.d.ts +0 -10
  241. package/dist/domain/host-mcp-task-admission.d.ts +0 -18
  242. package/dist/domain/packs.d.ts +0 -59
  243. package/dist/domain/product-integration-pack.d.ts +0 -44
  244. package/dist/domain/product-integration-skill.gen.d.ts +0 -5
  245. package/dist/remote-mcp-credentials.d.ts +0 -23
  246. package/dist/remote-mcp-credentials.js +0 -11
  247. package/dist/remote-mcp-credentials.js.map +0 -1
  248. package/src/application/host-mcp-owner.ts +0 -41
  249. package/src/application/host-mcp-resolvers.ts +0 -84
  250. package/src/domain/connection-authority.ts +0 -329
  251. package/src/domain/environments.ts +0 -120
  252. package/src/domain/host-mcp-authority-source-admission.ts +0 -25
  253. package/src/domain/host-mcp-task-admission.ts +0 -121
  254. package/src/domain/packs.ts +0 -699
  255. package/src/domain/product-integration-pack.ts +0 -42
  256. package/src/domain/product-integration-skill.gen.ts +0 -52
  257. package/src/remote-mcp-credentials.ts +0 -361
@@ -11,15 +11,22 @@
11
11
  // session group exists. `run_on` builds a one-off backend session and runs a
12
12
  // single op WITHOUT touching the active pointer.
13
13
 
14
- import type { Settings } from "@opengeni/config";
15
- import type { SandboxBackend } from "@opengeni/contracts";
14
+ import type { VaultProviderRegistry } from "@opengeni/vaults";
15
+ import { environmentsEncryptionKeyBytes, type Settings } from "@opengeni/config";
16
+ import {
17
+ agentCapabilityEnabled,
18
+ type SandboxBackend,
19
+ type SecretResolver,
20
+ } from "@opengeni/contracts";
16
21
  import {
17
22
  authorizePersonalMachineForAttempt,
18
23
  getEnrollment,
24
+ getSession,
19
25
  getLiveEnrollmentConnection,
20
26
  getSandbox,
21
27
  listEnrollments,
22
28
  listSandboxes,
29
+ nestedPostgresSqlState,
23
30
  readActiveSandbox,
24
31
  readLease,
25
32
  resolvePersonalMachineConnectionForAttempt,
@@ -30,9 +37,14 @@ import {
30
37
  type SandboxRecord,
31
38
  } from "@opengeni/db";
32
39
  import type { EventBus } from "@opengeni/events";
40
+ import { HTTPException } from "hono/http-exception";
33
41
  import {
34
42
  NatsControlRpc,
35
43
  NatsOpStreamTransport,
44
+ prepareSecretExec,
45
+ resolveSecretLiteralText,
46
+ runWithSecretCarry,
47
+ type SecretExecCarry,
36
48
  selfhostedLiveness,
37
49
  SelfhostedSession,
38
50
  resolveConnectedMachineWorkspaceRoot,
@@ -46,7 +58,8 @@ import {
46
58
  type SelfhostedOperationResourcePolicy,
47
59
  } from "@opengeni/runtime/sandbox";
48
60
  import { relayConfigFromSettings } from "./routing";
49
- import { managedSessionGroupBackend } from "./runtime-settings";
61
+ import { createSecretResolver } from "../domain/vaults/secret-resolver";
62
+ import { managedSessionGroupBackend, sessionGroupMachinePresentation } from "./runtime-settings";
50
63
 
51
64
  export type FleetServices = {
52
65
  db: Database;
@@ -130,7 +143,7 @@ export type FleetSandboxEntry = {
130
143
  /** The sandbox id used as the attach/swap/run_on `target`. For the session's
131
144
  * own group box this is the group id (a null active pointer == this box). */
132
145
  id: string;
133
- kind: "modal" | "selfhosted" | "opensandbox";
146
+ kind: Exclude<SandboxBackend, "none">;
134
147
  name: string;
135
148
  liveness: FleetLiveness;
136
149
  /** True for the session's currently-active sandbox (the routing target). */
@@ -192,11 +205,82 @@ export type FleetSwapResult = {
192
205
  | BackendUnresolvableCode
193
206
  | "invalid_working_directory"
194
207
  | "concurrent_swap"
208
+ | PersonalMachineRefusalCode
195
209
  | "recovery_in_progress"
196
210
  | "recovery_degraded"
197
211
  | "recovery_unrecoverable";
198
212
  };
199
213
 
214
+ export type PersonalMachineRefusalCode =
215
+ | "personal_machine_permission_required"
216
+ | "personal_machine_not_permitted";
217
+
218
+ /**
219
+ * Plain refusal for a personal ("Only me") Connected Machine that the database
220
+ * admission (`authorize_session_attempt_personal_machine`) turned down. The
221
+ * function raises SQLSTATE 42501 for policy refusals and P0002 when a required
222
+ * owner, authority or enrollment row is not current. Only failures of that
223
+ * admission statement are mapped; anything else is a real failure and stays an
224
+ * error. The raw database error is never shown.
225
+ */
226
+ export function personalMachineRefusal(
227
+ error: unknown,
228
+ ): { code: PersonalMachineRefusalCode; reason: string } | null {
229
+ const sqlState = nestedPostgresSqlState(error);
230
+ if (sqlState !== "42501" && sqlState !== "P0002") return null;
231
+ const messages: string[] = [];
232
+ let current: unknown = error;
233
+ for (let depth = 0; depth < 8 && current instanceof Error; depth += 1) {
234
+ messages.push(current.message);
235
+ current = current.cause;
236
+ }
237
+ const text = messages.join("\n");
238
+ if (!text.includes("authorize_session_attempt_personal_machine")) return null;
239
+ if (sqlState === "42501" && text.includes("requires explicit grant")) {
240
+ return {
241
+ code: "personal_machine_permission_required",
242
+ reason:
243
+ "This is a personal (Only me) machine, and its owner has not allowed this chat to use it. " +
244
+ "The owner can allow it from this chat: choose the machine with Use in this chat, then allow it once, for this chat, or always.",
245
+ };
246
+ }
247
+ if (
248
+ text.includes("only the personal machine owner may use it") ||
249
+ text.includes("not the frozen initiating human")
250
+ ) {
251
+ return {
252
+ code: "personal_machine_not_permitted",
253
+ reason:
254
+ "This is a personal (Only me) machine. Only its owner can use it, in turns the owner started.",
255
+ };
256
+ }
257
+ if (text.includes("lacks target workspace access")) {
258
+ return {
259
+ code: "personal_machine_not_permitted",
260
+ reason:
261
+ "This is a personal (Only me) machine, and its owner does not have access to this workspace.",
262
+ };
263
+ }
264
+ if (text.includes("requires exact current attempt authority")) {
265
+ return {
266
+ code: "personal_machine_not_permitted",
267
+ reason:
268
+ "This turn is no longer the chat's current turn, so it cannot use the personal machine.",
269
+ };
270
+ }
271
+ if (sqlState === "P0002") {
272
+ return {
273
+ code: "personal_machine_not_permitted",
274
+ reason:
275
+ "This personal machine's connection or its owner's access is no longer current. The owner can reconnect it.",
276
+ };
277
+ }
278
+ return {
279
+ code: "personal_machine_not_permitted",
280
+ reason: "This personal (Only me) machine cannot be used from this chat.",
281
+ };
282
+ }
283
+
200
284
  const PROBE_TIMEOUT_MS = 5_000;
201
285
 
202
286
  function controlRpc(bus: EventBus | undefined): ControlRpc {
@@ -325,10 +409,11 @@ export async function listFleet(
325
409
  : groupRecovering
326
410
  ? "recovering"
327
411
  : "wakeable";
412
+ const presentation = sessionGroupMachinePresentation(groupBackend);
328
413
  entries.push({
329
414
  id: ctx.sessionGroupId,
330
- kind: groupBackend === "opensandbox" ? "opensandbox" : "modal",
331
- name: "session sandbox",
415
+ kind: presentation.kind,
416
+ name: presentation.name,
332
417
  liveness: groupOnline ? "online" : groupRecovering ? "reconnecting" : "offline",
333
418
  active: groupActive,
334
419
  isSessionGroup: true,
@@ -667,6 +752,9 @@ export async function swapActiveSandbox(
667
752
  try {
668
753
  readinessHold = await services.ensureSessionGroupReady(ctx);
669
754
  } catch (error) {
755
+ // An explicit refusal (e.g. the session's Sandbox Environment is not
756
+ // available to this caller) is not a recovery state; surface it as is.
757
+ if (error instanceof HTTPException && error.status === 403) throw error;
670
758
  const lease = await readLease(services.db, ctx.workspaceId, ctx.sessionGroupId);
671
759
  const restore = lease?.recovery.restore.status;
672
760
  const code =
@@ -724,20 +812,34 @@ export async function swapActiveSandbox(
724
812
  // Even a same-target attach advances active_epoch. It is a repair/fence
725
813
  // request, not a no-op acknowledgment: any cached stale route is invalidated
726
814
  // only after target readiness has been proved above.
727
- const result = await setActiveSandbox(services.db, {
728
- accountId: ctx.accountId,
729
- workspaceId: ctx.workspaceId,
730
- sessionId: ctx.sessionId,
731
- targetSandboxId: resolved.targetSandboxId,
732
- expectedEpoch: pointer.activeEpoch,
733
- ...(ctx.subjectId ? { subjectId: ctx.subjectId } : {}),
734
- ...(ctx.attemptAuthority ? { personalMachineAttempt: ctx.attemptAuthority } : {}),
735
- ...(resolved.workspaceRoot !== undefined
736
- ? { workingDir: effectiveWorkingDir ?? resolved.workspaceRoot }
737
- : workingDir !== undefined
738
- ? { workingDir }
739
- : {}),
740
- });
815
+ let result: Awaited<ReturnType<typeof setActiveSandbox>>;
816
+ try {
817
+ result = await setActiveSandbox(services.db, {
818
+ accountId: ctx.accountId,
819
+ workspaceId: ctx.workspaceId,
820
+ sessionId: ctx.sessionId,
821
+ targetSandboxId: resolved.targetSandboxId,
822
+ expectedEpoch: pointer.activeEpoch,
823
+ ...(ctx.subjectId ? { subjectId: ctx.subjectId } : {}),
824
+ ...(ctx.attemptAuthority ? { personalMachineAttempt: ctx.attemptAuthority } : {}),
825
+ ...(resolved.workspaceRoot !== undefined
826
+ ? { workingDir: effectiveWorkingDir ?? resolved.workspaceRoot }
827
+ : workingDir !== undefined
828
+ ? { workingDir }
829
+ : {}),
830
+ });
831
+ } catch (error) {
832
+ // The admission refusal rolled the whole CAS transaction back, so the
833
+ // pointer read above is still current.
834
+ const refusal = personalMachineRefusal(error);
835
+ if (!refusal) throw error;
836
+ return {
837
+ swapped: false,
838
+ activeSandboxId: pointer.activeSandboxId,
839
+ activeEpoch: pointer.activeEpoch,
840
+ ...refusal,
841
+ };
842
+ }
741
843
  if (result.swapped && result.pointer) {
742
844
  return {
743
845
  swapped: true,
@@ -815,14 +917,52 @@ export type RunOnSelfhostedMachine = {
815
917
  * the active attempt that owns the one-off operation.
816
918
  */
817
919
  transientExecEnvironment?: Readonly<Record<string, string>>;
920
+ /**
921
+ * The calling attempt's secret resolver (W1, DL-9). exec splices
922
+ * `{{secret:…}}` / `{{secretfile:…}}` in the machine's dialect and delivers
923
+ * values in ExecRequest.env; write resolves its content; read resolves
924
+ * nothing. Absent: references pass through literally.
925
+ */
926
+ secretResolver?: SecretResolver | null;
818
927
  };
819
928
 
929
+ /**
930
+ * The secret resolver for a `run_on` call made by an exact agent attempt (the
931
+ * MCP claims). Gated by the session's `secrets` capability (default on).
932
+ */
933
+ export async function createRunOnSecretResolver(
934
+ services: Pick<FleetServices, "db" | "settings">,
935
+ attempt: {
936
+ accountId: string;
937
+ workspaceId: string;
938
+ sessionId: string;
939
+ turnId: string;
940
+ attemptId: string;
941
+ executionGeneration: number;
942
+ },
943
+ /** Connected vaults (1Password, host) for the attempt's acting human. */
944
+ providersForSubject?: (subjectId: string | null) => VaultProviderRegistry,
945
+ ): Promise<SecretResolver> {
946
+ const session = await getSession(services.db, attempt.workspaceId, attempt.sessionId);
947
+ return createSecretResolver(
948
+ { db: services.db, encryptionKey: environmentsEncryptionKeyBytes(services.settings) ?? null },
949
+ { kind: "attempt", ...attempt },
950
+ {
951
+ capabilityEnabled: session?.agent
952
+ ? agentCapabilityEnabled(session.agent.capabilities, "secrets")
953
+ : true,
954
+ ...(providersForSubject ? { providersForSubject } : {}),
955
+ },
956
+ );
957
+ }
958
+
820
959
  export type RunOnOptions = {
821
960
  transientExecEnvironment?: Readonly<Record<string, string>>;
961
+ secretResolver?: SecretResolver | null;
822
962
  };
823
963
 
824
964
  function runOnOperationAdmission(
825
- services: FleetServices,
965
+ services: Pick<FleetServices, "settings" | "bus">,
826
966
  enrollment: EnrollmentRecord | null,
827
967
  ): SelfhostedOperationAdmission | null {
828
968
  if (!enrollment?.connectionInstanceId || !enrollment.workspaceRoot) return null;
@@ -844,9 +984,16 @@ function runOnOperationAdmission(
844
984
  operationResourcePolicy: enrollment.operationPolicy,
845
985
  operationResourcePolicySupported: enrollment.agentCapabilities.operationResourcePolicy === true,
846
986
  operationCpuQuotaSupported: enrollment.agentCapabilities.operationCpuQuota === true,
987
+ transactionalFsWriteSupported: enrollment.agentCapabilities.transactionalFsWrite === true,
847
988
  };
848
989
  }
849
990
 
991
+ function hasSecretFileReference(carry: SecretExecCarry): boolean {
992
+ return carry.tokens.some(
993
+ (token) => token.type === "reference" && token.reference.kind === "secretfile",
994
+ );
995
+ }
996
+
850
997
  /**
851
998
  * Execute the one-off machine operation once the workspace/enrollment lookup has
852
999
  * succeeded. Kept separate from {@link runOnSandbox} so the command/deadline
@@ -887,13 +1034,35 @@ export async function executeRunOnSelfhostedMachine(
887
1034
  ...(machine.opStream !== undefined ? { opStream: machine.opStream } : {}),
888
1035
  });
889
1036
 
1037
+ const secretUse = { surface: "run_on" as const, toolName: "run_on" };
890
1038
  try {
891
1039
  if (op.kind === "exec") {
892
1040
  const deadlineMs = session.effectiveExecDeadlineMs;
893
- const res = await session.exec({
894
- cmd: op.cmd,
895
- ...(op.workdir ? { workdir: op.workdir } : {}),
1041
+ // Resolve before dispatch (all-or-nothing); the machine adapter splices
1042
+ // in its own dialect from the in-memory carry. `op.cmd` keeps references.
1043
+ const prepared = await prepareSecretExec(op.cmd, {
1044
+ resolver: machine.secretResolver,
1045
+ dialect: null,
1046
+ use: secretUse,
896
1047
  });
1048
+ let res: Awaited<ReturnType<typeof session.exec>>;
1049
+ try {
1050
+ res = await runWithSecretCarry(
1051
+ prepared?.carry ?? null,
1052
+ async () =>
1053
+ await session.exec({
1054
+ cmd: prepared?.text ?? op.cmd,
1055
+ ...(op.workdir ? { workdir: op.workdir } : {}),
1056
+ }),
1057
+ );
1058
+ } finally {
1059
+ // A one-off has no attempt-end cleanup on this machine: remove the
1060
+ // `secretfile` scope it wrote (SEC-4) with the filesystem op, which
1061
+ // works on every host OS and skips command admission. Best effort.
1062
+ if (prepared?.carry && hasSecretFileReference(prepared.carry)) {
1063
+ await session.clearSecretFilesForAttempt().catch(() => undefined);
1064
+ }
1065
+ }
897
1066
  const timedOut = res.timedOut === true;
898
1067
  const hasTerminalExit = res.exitCode !== null;
899
1068
  return {
@@ -928,7 +1097,10 @@ export async function executeRunOnSelfhostedMachine(
928
1097
  }
929
1098
  const bytesWritten = await session.writeFile({
930
1099
  path: op.path,
931
- content: op.content,
1100
+ content: await resolveSecretLiteralText(op.content, {
1101
+ resolver: machine.secretResolver,
1102
+ use: { ...secretUse, surface: "file_write" },
1103
+ }),
932
1104
  });
933
1105
  return { target, kind: "write", ok: true, bytesWritten };
934
1106
  } catch (error) {
@@ -950,6 +1122,56 @@ export async function executeRunOnSelfhostedMachine(
950
1122
  }
951
1123
  }
952
1124
 
1125
+ /**
1126
+ * Run one short command on a specific enrolled machine for an action a person
1127
+ * or agent took outside a session's routing (opening a macOS settings pane).
1128
+ * The caller has already authorized the actor for this enrollment; `access` is
1129
+ * the same scope the caller used to read it. `execTimeoutMs` bounds the command.
1130
+ */
1131
+ export async function runOnEnrollmentDirect(
1132
+ services: Pick<FleetServices, "db" | "settings" | "bus">,
1133
+ input: {
1134
+ access: string | { accountId: string; workspaceId: string; subjectId: string };
1135
+ enrollmentId: string;
1136
+ /** Display label for results (machine name or sandbox id). */
1137
+ target: string;
1138
+ cmd: string;
1139
+ execTimeoutMs: number;
1140
+ },
1141
+ ): Promise<RunOnResult> {
1142
+ const live = await getLiveEnrollmentConnection(services.db, input.access, input.enrollmentId);
1143
+ if (!live || live.status !== "active" || !live.connectionInstanceId || !live.workspaceRoot) {
1144
+ return { target: input.target, kind: "exec", ok: false, reason: "machine is not connected" };
1145
+ }
1146
+ return await executeRunOnSelfhostedMachine(
1147
+ {
1148
+ workspaceId: live.workspaceId,
1149
+ agentId: input.enrollmentId,
1150
+ connectionInstanceId: live.connectionInstanceId,
1151
+ workspaceRoot: live.workspaceRoot,
1152
+ controlRpc: controlRpc(services.bus),
1153
+ relay: relayConfigFromSettings(services.settings),
1154
+ controlTimeoutMs: services.settings.sandboxSelfhostedControlTimeoutMs,
1155
+ execTimeoutMs: input.execTimeoutMs,
1156
+ operationResourcePolicy: live.operationPolicy,
1157
+ operationResourcePolicySupported: live.agentCapabilities.operationResourcePolicy === true,
1158
+ operationCpuQuotaSupported: live.agentCapabilities.operationCpuQuota === true,
1159
+ resolveOperationAdmission: async () => {
1160
+ try {
1161
+ return runOnOperationAdmission(
1162
+ services,
1163
+ await getLiveEnrollmentConnection(services.db, input.access, input.enrollmentId),
1164
+ );
1165
+ } catch {
1166
+ return null;
1167
+ }
1168
+ },
1169
+ },
1170
+ input.target,
1171
+ { kind: "exec", cmd: input.cmd },
1172
+ );
1173
+ }
1174
+
953
1175
  /**
954
1176
  * Run a ONE-OFF op against a SPECIFIC target WITHOUT changing the active pointer
955
1177
  * (the design `run_on`). Only selfhosted targets are routable as a one-off here
@@ -1025,13 +1247,15 @@ export async function runOnSandbox(
1025
1247
  reason: "personal Connected Machine authority was not admitted for this attempt",
1026
1248
  };
1027
1249
  }
1028
- } catch {
1250
+ } catch (error) {
1029
1251
  return {
1030
1252
  target,
1031
1253
  targetName: sandbox.name,
1032
1254
  kind: op.kind,
1033
1255
  ok: false,
1034
- reason: "personal Connected Machine authority is not live for this attempt",
1256
+ reason:
1257
+ personalMachineRefusal(error)?.reason ??
1258
+ "personal Connected Machine authority is not live for this attempt",
1035
1259
  };
1036
1260
  }
1037
1261
  }
@@ -1114,6 +1338,7 @@ export async function runOnSandbox(
1114
1338
  ...(options.transientExecEnvironment !== undefined
1115
1339
  ? { transientExecEnvironment: options.transientExecEnvironment }
1116
1340
  : {}),
1341
+ ...(options.secretResolver ? { secretResolver: options.secretResolver } : {}),
1117
1342
  ...(services.settings.agentOpStreamEnabled === true &&
1118
1343
  enrollment.opStream === true &&
1119
1344
  services.bus?.getOpStreamConnection
@@ -1164,7 +1389,7 @@ export async function provisionSandbox(
1164
1389
  return {
1165
1390
  kind: "selfhosted",
1166
1391
  instructions:
1167
- "Share one of these deployment-specific commands with a human operator. It installs the OpenGeni agent and starts `opengeni-agent connect` for this exact deployment and workspace; do not run a second bare `connect`. Complete the device-flow at the verification URL (the loud whole-machine + screen-control consent), and the machine then appears here as an attachable selfhosted sandbox. Existing connections to other OpenGeni workspaces or deployments are preserved.",
1392
+ "Share one of these deployment-specific commands with a human operator. It installs the Opengeni agent and starts `opengeni-agent connect` for this exact deployment and workspace; do not run a second bare `connect`. Complete the device-flow at the verification URL (the loud whole-machine + screen-control consent), and the machine then appears here as an attachable selfhosted sandbox. Existing connections to other Opengeni workspaces or deployments are preserved.",
1168
1393
  // Install from THIS control plane's origin (not a hardcoded public CDN): the
1169
1394
  // served install script is rewritten to pull the per-SHA agent baked into
1170
1395
  // this exact deployment (see apps/api/src/routes/install.ts), so a deployed