@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.
- package/README.md +4 -4
- package/dist/access/index.d.ts +131 -7
- package/dist/access/service-initiator.d.ts +3 -0
- package/dist/application/archived-session-imports.d.ts +6 -0
- package/dist/application/composer-submit.d.ts +1 -1
- package/dist/application/connect-operation.d.ts +6 -0
- package/dist/application/external-workspace-members.d.ts +13 -1
- package/dist/application/modal-native-live-declaration-join.d.ts +37 -0
- package/dist/application/modal-native-original-configuration.d.ts +79 -0
- package/dist/application/modal-native-worker-host-transport.d.ts +297 -0
- package/dist/application/new-session-drafts.d.ts +29 -2
- package/dist/application/organization-privacy.d.ts +57 -0
- package/dist/application/prepared-mcp-connection.d.ts +31 -0
- package/dist/application/sandbox-recovery.d.ts +58 -0
- package/dist/application/session-commands.d.ts +3 -0
- package/dist/application/session-tenancy.d.ts +13 -4
- package/dist/billing/agent-run-admission.d.ts +21 -0
- package/dist/billing/limits.d.ts +27 -1
- package/dist/billing/model-call-admission.d.ts +34 -0
- package/dist/billing/model-usage-settlement.d.ts +112 -0
- package/dist/canonical-human-identities.js +1 -1
- package/dist/{chunk-VCZFFEPB.js → chunk-7T2SZ7BI.js} +16 -2
- package/dist/chunk-7T2SZ7BI.js.map +1 -0
- package/dist/{chunk-ZYCNLYGH.js → chunk-OMJFIIWK.js} +178 -12
- package/dist/chunk-OMJFIIWK.js.map +1 -0
- package/dist/{chunk-H7X52RI2.js → chunk-QI2M2B2K.js} +83 -8
- package/dist/chunk-QI2M2B2K.js.map +1 -0
- package/dist/{chunk-OF65T3PM.js → chunk-TTWTXQB2.js} +94 -6
- package/dist/chunk-TTWTXQB2.js.map +1 -0
- package/dist/codex-model-availability.d.ts +55 -0
- package/dist/default-session-model.d.ts +192 -0
- package/dist/dependencies.d.ts +41 -7
- package/dist/developer-setup-credential-ceiling.d.ts +10 -0
- package/dist/domain/agent-config-resolution.d.ts +49 -0
- package/dist/domain/automations.d.ts +8 -1
- package/dist/domain/capabilities.d.ts +112 -8
- package/dist/domain/connection-account-selection-error.d.ts +8 -0
- package/dist/domain/connector-tool-permissions.d.ts +62 -0
- package/dist/domain/direct-model-provider.d.ts +3 -0
- package/dist/domain/editable-artifacts/agent-application.d.ts +10 -1
- package/dist/domain/editable-artifacts/durable-export.d.ts +22 -1
- package/dist/domain/editable-artifacts/ports.d.ts +10 -0
- package/dist/domain/editable-artifacts/service.d.ts +3 -1
- package/dist/domain/editable-artifacts/types.d.ts +1 -1
- package/dist/domain/encryption-key.d.ts +12 -0
- package/dist/domain/insights-timing.d.ts +16 -0
- package/dist/domain/insights-usage.d.ts +27 -0
- package/dist/domain/insights.d.ts +21 -3
- package/dist/domain/knowledge-files.d.ts +1 -1
- package/dist/domain/knowledge-preparation.d.ts +10 -0
- package/dist/domain/knowledge-preparation.test.d.ts +1 -0
- package/dist/domain/knowledge-search.d.ts +15 -76
- package/dist/domain/mcp-account-bindings.d.ts +17 -0
- package/dist/domain/mcp-account-routes.d.ts +21 -0
- package/dist/domain/native-mcp-connection-admission.d.ts +3 -0
- package/dist/domain/organization-integration-catalog.d.ts +15 -0
- package/dist/domain/personal-connection-delegations.d.ts +73 -12
- package/dist/domain/pr-review.d.ts +121 -6
- package/dist/domain/realtime-voice-billing.d.ts +103 -0
- package/dist/domain/resources.d.ts +17 -1
- package/dist/domain/scheduled-task-access.d.ts +265 -0
- package/dist/domain/scheduled-task-account-audit.d.ts +27 -0
- package/dist/domain/scheduled-task-audience.d.ts +70 -0
- package/dist/domain/scheduled-tasks.d.ts +61 -18
- package/dist/domain/session-connection-accounts.d.ts +53 -0
- package/dist/domain/session-file-access.d.ts +4 -0
- package/dist/domain/session-native-connection-replacement.d.ts +14 -0
- package/dist/domain/session-start-timing.d.ts +7 -0
- package/dist/domain/session-tool-policy.d.ts +102 -4
- package/dist/domain/sessions.d.ts +179 -15
- package/dist/domain/site-audience.d.ts +52 -0
- package/dist/domain/skills.d.ts +29 -1
- package/dist/domain/slack-bot.d.ts +46 -1
- package/dist/domain/slack-publication-secret-safety.d.ts +1 -1
- package/dist/domain/vaults/index.d.ts +7 -0
- package/dist/domain/vaults/mapping.d.ts +25 -0
- package/dist/domain/vaults/provider-registry.d.ts +72 -0
- package/dist/domain/vaults/provider-sync.d.ts +176 -0
- package/dist/domain/vaults/provider-writes.d.ts +57 -0
- package/dist/domain/vaults/secret-resolver.d.ts +34 -0
- package/dist/domain/vaults/types.d.ts +24 -0
- package/dist/domain/vaults/vault-service.d.ts +35 -0
- package/dist/domain/voice-input-billing.d.ts +77 -0
- package/dist/domain/web-search-billing.d.ts +51 -0
- package/dist/domain/workspace-credits.d.ts +7 -0
- package/dist/editable-artifact-live/application.d.ts +2 -1
- package/dist/editable-artifact-live/server.d.ts +5 -1
- package/dist/editable-artifact-live/ticket.d.ts +2 -1
- package/dist/editable-artifact-live/types.d.ts +9 -0
- package/dist/editable-artifact-live.js +2 -2
- package/dist/editable-artifacts.js +10 -2
- package/dist/goal-admission.d.ts +627 -0
- package/dist/host-session-usage-limit.d.ts +15 -0
- package/dist/index.d.ts +43 -8
- package/dist/index.js +22682 -11743
- package/dist/index.js.map +1 -1
- package/dist/managed-session.d.ts +110 -7
- package/dist/model-call.d.ts +58 -0
- package/dist/model-catalog.d.ts +29 -3
- package/dist/model-provider-credentials.d.ts +43 -0
- package/dist/product-usage-metrics.d.ts +30 -0
- package/dist/rigs/index.d.ts +1 -13
- package/dist/sandbox/fleet.d.ts +58 -4
- package/dist/sandbox/routing.d.ts +42 -2
- package/dist/sandbox/runtime-settings.d.ts +27 -17
- package/dist/session-authorization.d.ts +10 -0
- package/dist/session-usage-limit-metrics.d.ts +60 -0
- package/dist/session-usage-limits.d.ts +89 -0
- package/dist/site-agent-clamp.d.ts +71 -0
- package/dist/site-session-origin.d.ts +39 -2
- package/dist/transcription.d.ts +120 -3
- package/dist/turn-surface.d.ts +20 -0
- package/dist/user-presence.d.ts +32 -0
- package/dist/workflow-wake-contract.d.ts +13 -1
- package/package.json +15 -22
- package/src/access/index.ts +961 -38
- package/src/access/service-initiator.ts +50 -0
- package/src/application/api-integration-servers.ts +9 -0
- package/src/application/archived-session-imports.ts +161 -0
- package/src/application/composer-submit.ts +3 -2
- package/src/application/connect-authority.ts +3 -3
- package/src/application/connect-operation.ts +37 -3
- package/src/application/external-continuation.ts +20 -3
- package/src/application/external-link-work-admission.ts +3 -0
- package/src/application/external-workspace-members.ts +51 -3
- package/src/application/modal-native-live-declaration-join.ts +196 -0
- package/src/application/modal-native-original-configuration.ts +261 -0
- package/src/application/modal-native-worker-host-transport.ts +263 -0
- package/src/application/new-session-drafts.ts +172 -55
- package/src/application/organization-privacy.ts +161 -0
- package/src/application/prepared-mcp-connection.ts +133 -0
- package/src/application/sandbox-recovery.ts +78 -0
- package/src/application/session-commands.ts +73 -1
- package/src/application/session-mcp-credential-rotation.ts +61 -18
- package/src/application/session-tenancy.ts +262 -44
- package/src/application/user-resource-grants.ts +1 -29
- package/src/billing/agent-run-admission.ts +117 -0
- package/src/billing/limits.ts +138 -14
- package/src/billing/model-call-admission.ts +118 -0
- package/src/billing/model-usage-settlement.ts +472 -0
- package/src/codex-model-availability.ts +264 -0
- package/src/default-session-model.ts +692 -0
- package/src/dependencies.ts +48 -6
- package/src/developer-setup-credential-ceiling.ts +60 -0
- package/src/domain/agent-config-resolution.ts +148 -0
- package/src/domain/automations.ts +76 -2
- package/src/domain/capabilities.ts +830 -181
- package/src/domain/connection-account-selection-error.ts +19 -0
- package/src/domain/connector-tool-permissions.ts +572 -0
- package/src/domain/direct-model-provider.ts +62 -0
- package/src/domain/editable-artifacts/agent-application.ts +44 -6
- package/src/domain/editable-artifacts/durable-export.ts +100 -5
- package/src/domain/editable-artifacts/in-memory.ts +6 -0
- package/src/domain/editable-artifacts/ports.ts +11 -0
- package/src/domain/editable-artifacts/service.ts +20 -0
- package/src/domain/editable-artifacts/types.ts +1 -1
- package/src/domain/encryption-key.ts +26 -0
- package/src/domain/file-owner.ts +16 -1
- package/src/domain/github-action-policies.ts +1 -1
- package/src/domain/github-skill-source.ts +3 -1
- package/src/domain/insights-timing.ts +95 -0
- package/src/domain/insights-usage.ts +67 -0
- package/src/domain/insights.ts +167 -43
- package/src/domain/knowledge-files.ts +10 -3
- package/src/domain/knowledge-messages.ts +1 -0
- package/src/domain/knowledge-preparation.test.ts +276 -0
- package/src/domain/knowledge-preparation.ts +147 -0
- package/src/domain/knowledge-search.test.ts +617 -5
- package/src/domain/knowledge-search.ts +240 -23
- package/src/domain/mcp-account-bindings.ts +169 -0
- package/src/domain/mcp-account-routes.ts +152 -0
- package/src/domain/native-mcp-connection-admission.ts +14 -0
- package/src/domain/organization-integration-catalog.ts +88 -0
- package/src/domain/personal-connection-delegations.ts +399 -257
- package/src/domain/pr-review.ts +24 -8
- package/src/domain/realtime-voice-billing.ts +425 -0
- package/src/domain/resources.ts +50 -4
- package/src/domain/scheduled-task-access.ts +1089 -0
- package/src/domain/scheduled-task-account-audit.ts +90 -0
- package/src/domain/scheduled-task-audience.ts +249 -0
- package/src/domain/scheduled-tasks.ts +1313 -374
- package/src/domain/session-connection-accounts.ts +239 -0
- package/src/domain/session-file-access.ts +47 -0
- package/src/domain/session-native-connection-replacement.ts +64 -0
- package/src/domain/session-start-timing.ts +60 -0
- package/src/domain/session-tool-policy.ts +489 -14
- package/src/domain/sessions.ts +2254 -717
- package/src/domain/site-audience.ts +130 -0
- package/src/domain/skills.ts +9 -0
- package/src/domain/slack-bot.ts +141 -4
- package/src/domain/slack-publication-secret-safety.ts +1 -1
- package/src/domain/vaults/index.ts +19 -0
- package/src/domain/vaults/mapping.ts +190 -0
- package/src/domain/vaults/provider-registry.ts +201 -0
- package/src/domain/vaults/provider-sync.ts +531 -0
- package/src/domain/vaults/provider-writes.ts +288 -0
- package/src/domain/vaults/secret-resolver.ts +464 -0
- package/src/domain/vaults/types.ts +26 -0
- package/src/domain/vaults/vault-service.ts +1316 -0
- package/src/domain/voice-input-billing.ts +330 -0
- package/src/domain/web-search-billing.ts +169 -0
- package/src/domain/workspace-credits.ts +13 -0
- package/src/editable-artifact-live/application.ts +8 -1
- package/src/editable-artifact-live/server.ts +154 -6
- package/src/editable-artifact-live/ticket.ts +86 -1
- package/src/editable-artifact-live/types.ts +10 -0
- package/src/goal-admission.ts +220 -0
- package/src/host-session-usage-limit.ts +25 -0
- package/src/index.ts +75 -11
- package/src/managed-session.ts +137 -6
- package/src/model-call.ts +79 -0
- package/src/model-catalog.ts +345 -91
- package/src/model-provider-credentials.ts +394 -0
- package/src/product-usage-metrics.ts +101 -0
- package/src/rigs/index.ts +23 -111
- package/src/sandbox/fleet.ts +253 -28
- package/src/sandbox/routing.ts +204 -43
- package/src/sandbox/runtime-settings.ts +159 -85
- package/src/session-authorization.ts +172 -23
- package/src/session-usage-limit-metrics.ts +95 -0
- package/src/session-usage-limits.ts +415 -0
- package/src/site-agent-clamp.ts +226 -0
- package/src/site-session-origin.ts +84 -5
- package/src/transcription.ts +123 -4
- package/src/turn-surface.ts +47 -0
- package/src/user-presence.ts +134 -0
- package/src/workflow-wake-contract.ts +14 -1
- package/dist/application/host-mcp-owner.d.ts +0 -6
- package/dist/application/host-mcp-resolvers.d.ts +0 -13
- package/dist/chunk-5WZYFL45.js +0 -266
- package/dist/chunk-5WZYFL45.js.map +0 -1
- package/dist/chunk-H7X52RI2.js.map +0 -1
- package/dist/chunk-OF65T3PM.js.map +0 -1
- package/dist/chunk-VCZFFEPB.js.map +0 -1
- package/dist/chunk-ZYCNLYGH.js.map +0 -1
- package/dist/domain/connection-authority.d.ts +0 -97
- package/dist/domain/connection-authority.js +0 -169
- package/dist/domain/connection-authority.js.map +0 -1
- package/dist/domain/environments.d.ts +0 -33
- package/dist/domain/host-mcp-authority-source-admission.d.ts +0 -10
- package/dist/domain/host-mcp-task-admission.d.ts +0 -18
- package/dist/domain/packs.d.ts +0 -59
- package/dist/domain/product-integration-pack.d.ts +0 -44
- package/dist/domain/product-integration-skill.gen.d.ts +0 -5
- package/dist/remote-mcp-credentials.d.ts +0 -23
- package/dist/remote-mcp-credentials.js +0 -11
- package/dist/remote-mcp-credentials.js.map +0 -1
- package/src/application/host-mcp-owner.ts +0 -41
- package/src/application/host-mcp-resolvers.ts +0 -84
- package/src/domain/connection-authority.ts +0 -329
- package/src/domain/environments.ts +0 -120
- package/src/domain/host-mcp-authority-source-admission.ts +0 -25
- package/src/domain/host-mcp-task-admission.ts +0 -121
- package/src/domain/packs.ts +0 -699
- package/src/domain/product-integration-pack.ts +0 -42
- package/src/domain/product-integration-skill.gen.ts +0 -52
- package/src/remote-mcp-credentials.ts +0 -361
package/src/sandbox/fleet.ts
CHANGED
|
@@ -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 {
|
|
15
|
-
import type
|
|
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 {
|
|
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:
|
|
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:
|
|
331
|
-
name:
|
|
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
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
? {
|
|
737
|
-
|
|
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
|
-
|
|
894
|
-
|
|
895
|
-
|
|
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:
|
|
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
|
|
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
|