@opengeni/core 1.0.1 → 2.1.0-canary.0

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 (99) hide show
  1. package/dist/access/index.d.ts +37 -0
  2. package/dist/application/new-session-drafts.d.ts +8 -1
  3. package/dist/application/session-commands.d.ts +18 -11
  4. package/dist/application/session-tenancy.d.ts +18 -0
  5. package/dist/application/user-resource-grants.d.ts +20 -0
  6. package/dist/{chunk-6WKPWE5S.js → chunk-H7X52RI2.js} +27 -18
  7. package/dist/chunk-H7X52RI2.js.map +1 -0
  8. package/dist/{chunk-JJU6I5XD.js → chunk-VCZFFEPB.js} +21 -9
  9. package/dist/chunk-VCZFFEPB.js.map +1 -0
  10. package/dist/{chunk-NYPMQ7NO.js → chunk-ZYCNLYGH.js} +29 -27
  11. package/dist/chunk-ZYCNLYGH.js.map +1 -0
  12. package/dist/dependencies.d.ts +12 -1
  13. package/dist/domain/automations.d.ts +51 -0
  14. package/dist/domain/company-brain-governed-writes.d.ts +70 -0
  15. package/dist/domain/company-profile-agent-admin.d.ts +24 -0
  16. package/dist/domain/connection-authority.d.ts +97 -0
  17. package/dist/domain/connection-authority.js +169 -0
  18. package/dist/domain/connection-authority.js.map +1 -0
  19. package/dist/domain/editable-artifacts/service.d.ts +1 -2
  20. package/dist/domain/environments.d.ts +1 -1
  21. package/dist/domain/github-repository-bindings.d.ts +117 -0
  22. package/dist/domain/governed-learning-activation.d.ts +16 -0
  23. package/dist/domain/governed-learning-evaluator.d.ts +6 -0
  24. package/dist/domain/governed-learning-slack-publication.d.ts +45 -0
  25. package/dist/domain/memory-slack-delivery.d.ts +1 -4
  26. package/dist/domain/organization-membership-lifecycle.d.ts +1 -0
  27. package/dist/domain/packs.d.ts +5 -1
  28. package/dist/domain/personal-connection-delegations.d.ts +34 -2
  29. package/dist/domain/pr-review.d.ts +149 -0
  30. package/dist/domain/remember.d.ts +69 -0
  31. package/dist/domain/resources.d.ts +22 -1
  32. package/dist/domain/scheduled-tasks.d.ts +43 -2
  33. package/dist/domain/session-tool-policy.d.ts +1 -1
  34. package/dist/domain/sessions.d.ts +40 -6
  35. package/dist/editable-artifact-live/application.d.ts +10 -2
  36. package/dist/editable-artifact-live/ports.d.ts +0 -1
  37. package/dist/editable-artifact-live/server.d.ts +0 -1
  38. package/dist/editable-artifact-live/ticket.d.ts +0 -1
  39. package/dist/editable-artifact-live/types.d.ts +5 -2
  40. package/dist/editable-artifact-live.js +2 -2
  41. package/dist/editable-artifacts.js +2 -4
  42. package/dist/index.d.ts +13 -1
  43. package/dist/index.js +4827 -1164
  44. package/dist/index.js.map +1 -1
  45. package/dist/rigs/index.d.ts +22 -5
  46. package/dist/sandbox/fleet.d.ts +34 -6
  47. package/dist/sandbox/routing.d.ts +15 -1
  48. package/dist/sandbox/runtime-settings.d.ts +39 -0
  49. package/package.json +16 -11
  50. package/src/access/index.ts +151 -31
  51. package/src/application/new-session-drafts.ts +24 -2
  52. package/src/application/session-commands.ts +305 -71
  53. package/src/application/session-tenancy.ts +321 -0
  54. package/src/application/user-resource-grants.ts +140 -0
  55. package/src/billing/limits.ts +25 -15
  56. package/src/dependencies.ts +12 -1
  57. package/src/domain/automations.ts +196 -0
  58. package/src/domain/capabilities.ts +7 -1
  59. package/src/domain/company-brain-governed-writes.ts +330 -0
  60. package/src/domain/company-profile-agent-admin.ts +55 -0
  61. package/src/domain/connection-authority.ts +329 -0
  62. package/src/domain/editable-artifacts/agent-application.ts +17 -9
  63. package/src/domain/editable-artifacts/service.ts +12 -1
  64. package/src/domain/editable-artifacts/snapshot-compaction.ts +9 -5
  65. package/src/domain/editable-artifacts/snapshot-verifier-genesis.ts +10 -8
  66. package/src/domain/editable-artifacts/snapshot-verifier.ts +10 -3
  67. package/src/domain/environments.ts +16 -3
  68. package/src/domain/github-repository-bindings.ts +295 -0
  69. package/src/domain/governed-learning-activation.ts +44 -0
  70. package/src/domain/governed-learning-evaluator.ts +49 -0
  71. package/src/domain/governed-learning-slack-publication.ts +157 -0
  72. package/src/domain/insights.ts +9 -0
  73. package/src/domain/memory-slack-delivery.ts +0 -30
  74. package/src/domain/organization-membership-lifecycle.ts +9 -0
  75. package/src/domain/packs.ts +106 -3
  76. package/src/domain/personal-connection-delegations.ts +532 -51
  77. package/src/domain/pr-review.ts +671 -0
  78. package/src/domain/remember.ts +549 -0
  79. package/src/domain/resources.ts +131 -4
  80. package/src/domain/scheduled-tasks.ts +497 -76
  81. package/src/domain/session-tool-policy.ts +12 -9
  82. package/src/domain/sessions.ts +613 -166
  83. package/src/editable-artifact-live/application.ts +21 -3
  84. package/src/editable-artifact-live/ports.ts +0 -1
  85. package/src/editable-artifact-live/server.ts +13 -7
  86. package/src/editable-artifact-live/ticket.ts +1 -5
  87. package/src/editable-artifact-live/types.ts +6 -2
  88. package/src/editable-artifact-live/wire.ts +0 -10
  89. package/src/index.ts +13 -1
  90. package/src/rigs/index.ts +165 -48
  91. package/src/sandbox/fleet.ts +376 -30
  92. package/src/sandbox/routing.ts +91 -29
  93. package/src/sandbox/runtime-settings.ts +274 -0
  94. package/src/session-authorization.ts +17 -49
  95. package/dist/chunk-6WKPWE5S.js.map +0 -1
  96. package/dist/chunk-JJU6I5XD.js.map +0 -1
  97. package/dist/chunk-NYPMQ7NO.js.map +0 -1
  98. package/dist/domain/durable-learning-slack-publication.d.ts +0 -72
  99. package/src/domain/durable-learning-slack-publication.ts +0 -202
@@ -13,11 +13,15 @@
13
13
 
14
14
  import type { Settings } from "@opengeni/config";
15
15
  import {
16
+ authorizePersonalMachineForAttempt,
16
17
  getEnrollment,
18
+ getLiveEnrollmentConnection,
17
19
  getSandbox,
20
+ listEnrollments,
18
21
  listSandboxes,
19
22
  readActiveSandbox,
20
23
  readLease,
24
+ resolvePersonalMachineConnectionForAttempt,
21
25
  requireSession,
22
26
  setActiveSandbox,
23
27
  type Database,
@@ -30,12 +34,15 @@ import {
30
34
  NatsOpStreamTransport,
31
35
  selfhostedLiveness,
32
36
  SelfhostedSession,
37
+ resolveConnectedMachineWorkspaceRoot,
33
38
  swapTargetEstablishability,
34
39
  type BackendUnresolvableCode,
35
40
  type ControlRpc,
36
41
  type NatsRequestConnection,
37
42
  type SelfhostedRelayConfig,
38
43
  type SelfhostedOpStreamDeps,
44
+ type SelfhostedOperationAdmission,
45
+ type SelfhostedOperationResourcePolicy,
39
46
  } from "@opengeni/runtime/sandbox";
40
47
  import { relayConfigFromSettings } from "./routing";
41
48
 
@@ -57,6 +64,13 @@ export type FleetReadinessHold = {
57
64
  export type FleetContext = {
58
65
  accountId: string;
59
66
  workspaceId: string;
67
+ subjectId?: string;
68
+ attemptAuthority?: {
69
+ turnId: string;
70
+ attemptId: string;
71
+ executionGeneration: number;
72
+ initiatingHumanSubjectId: string;
73
+ };
60
74
  /** The calling session (the pointer the attach/swap mutates + whose group box
61
75
  * is the default fleet member). */
62
76
  sessionId: string;
@@ -78,12 +92,20 @@ export type FleetContext = {
78
92
  */
79
93
  export async function buildFleetContextForSession(
80
94
  deps: { db: Database },
81
- ctx: { accountId: string; workspaceId: string; sessionId: string },
95
+ ctx: {
96
+ accountId: string;
97
+ workspaceId: string;
98
+ sessionId: string;
99
+ subjectId?: string;
100
+ attemptAuthority?: FleetContext["attemptAuthority"];
101
+ },
82
102
  ): Promise<FleetContext> {
83
103
  const session = await requireSession(deps.db, ctx.workspaceId, ctx.sessionId);
84
104
  return {
85
105
  accountId: ctx.accountId,
86
106
  workspaceId: ctx.workspaceId,
107
+ ...(ctx.subjectId ? { subjectId: ctx.subjectId } : {}),
108
+ ...(ctx.attemptAuthority ? { attemptAuthority: ctx.attemptAuthority } : {}),
87
109
  sessionId: ctx.sessionId,
88
110
  sessionBackend: session.sandboxBackend,
89
111
  sessionGroupId: session.sandboxGroupId,
@@ -98,14 +120,15 @@ export type FleetOperationAvailability = "ready" | "wakeable" | "recovering" | "
98
120
  * A fleet member as the agent + the dock see it (the M8b/M9 UI seam — the
99
121
  * `sandboxes_list` response entry the dock renders). STABLE shape: the dock keys
100
122
  * on `id`, renders `name`/`kind`/`liveness`, and marks `active`. The session's own
101
- * Modal group box is a synthetic entry with `id: groupId`, `kind: "modal"`, and a
102
- * null `enrollmentId`; an enrolled machine carries its sandbox + enrollment ids.
123
+ * managed group box is a synthetic entry with `id: groupId`, its actual
124
+ * provisioned backend kind, and a null `enrollmentId`; an enrolled machine
125
+ * carries its sandbox + enrollment ids.
103
126
  */
104
127
  export type FleetSandboxEntry = {
105
128
  /** The sandbox id used as the attach/swap/run_on `target`. For the session's
106
129
  * own group box this is the group id (a null active pointer == this box). */
107
130
  id: string;
108
- kind: "modal" | "selfhosted";
131
+ kind: "modal" | "selfhosted" | "opensandbox";
109
132
  name: string;
110
133
  liveness: FleetLiveness;
111
134
  /** True for the session's currently-active sandbox (the routing target). */
@@ -165,6 +188,7 @@ export type FleetSwapResult = {
165
188
  reason?: string;
166
189
  code?:
167
190
  | BackendUnresolvableCode
191
+ | "invalid_working_directory"
168
192
  | "concurrent_swap"
169
193
  | "recovery_in_progress"
170
194
  | "recovery_degraded"
@@ -191,13 +215,22 @@ async function probeEnrollment(
191
215
  services: FleetServices,
192
216
  workspaceId: string,
193
217
  enrollment: EnrollmentRecord,
194
- ): Promise<{ liveness: FleetLiveness; consented: boolean; hasDisplay: boolean }> {
218
+ liveConnection: EnrollmentRecord | null = enrollment,
219
+ ): Promise<{
220
+ liveness: FleetLiveness;
221
+ consented: boolean;
222
+ hasDisplay: boolean;
223
+ }> {
195
224
  const { settings, bus } = services;
196
225
  let probeResponded = false;
197
- if (enrollment.status === "active") {
226
+ if (liveConnection?.connectionInstanceId) {
198
227
  const session = new SelfhostedSession({
199
228
  workspaceId,
200
229
  agentId: enrollment.id,
230
+ connectionInstanceId: liveConnection.connectionInstanceId,
231
+ // Ping does not touch the filesystem. Use the reported root when present;
232
+ // older agents can still answer liveness before they reconnect and report it.
233
+ workspaceRoot: enrollment.workspaceRoot ?? "/",
201
234
  controlRpc: controlRpc(bus),
202
235
  relay: relayConfigFromSettings(settings),
203
236
  timeoutMs: PROBE_TIMEOUT_MS,
@@ -220,7 +253,11 @@ async function probeEnrollment(
220
253
  },
221
254
  probeResponded,
222
255
  });
223
- return { liveness: state.state, consented: state.consented, hasDisplay: state.hasDisplay };
256
+ return {
257
+ liveness: state.state,
258
+ consented: state.consented,
259
+ hasDisplay: state.hasDisplay,
260
+ };
224
261
  }
225
262
 
226
263
  /**
@@ -235,6 +272,13 @@ export async function listFleet(
235
272
  ctx: FleetContext,
236
273
  ): Promise<FleetListResult> {
237
274
  const { db } = services;
275
+ const resourceAccess = ctx.subjectId
276
+ ? {
277
+ accountId: ctx.accountId,
278
+ workspaceId: ctx.workspaceId,
279
+ subjectId: ctx.subjectId,
280
+ }
281
+ : ctx.workspaceId;
238
282
  const pointer = (await readActiveSandbox(db, ctx.workspaceId, ctx.sessionId)) ?? {
239
283
  activeSandboxId: null,
240
284
  activeEpoch: 0,
@@ -278,7 +322,12 @@ export async function listFleet(
278
322
  : "wakeable";
279
323
  entries.push({
280
324
  id: ctx.sessionGroupId,
281
- kind: ctx.sessionBackend === "selfhosted" ? "selfhosted" : "modal",
325
+ kind:
326
+ ctx.sessionBackend === "selfhosted"
327
+ ? "selfhosted"
328
+ : ctx.sessionBackend === "opensandbox"
329
+ ? "opensandbox"
330
+ : "modal",
282
331
  name: "session sandbox",
283
332
  liveness: groupOnline ? "online" : groupRecovering ? "reconnecting" : "offline",
284
333
  active: groupActive,
@@ -302,20 +351,65 @@ export async function listFleet(
302
351
 
303
352
  // The workspace's first-class selfhosted sandboxes (enrolled machines). Probe
304
353
  // each for liveness; a missing enrollment is offline.
305
- const sandboxes = await listSandboxes(db, ctx.workspaceId);
354
+ const legacySandboxes = await listSandboxes(db, ctx.workspaceId);
355
+ const enrollments = await listEnrollments(
356
+ db,
357
+ ctx.subjectId
358
+ ? {
359
+ accountId: ctx.accountId,
360
+ workspaceId: ctx.workspaceId,
361
+ subjectId: ctx.subjectId,
362
+ }
363
+ : ctx.workspaceId,
364
+ { status: "active" },
365
+ );
366
+ const enrollmentById = new Map(enrollments.map((enrollment) => [enrollment.id, enrollment]));
367
+ const scopedSandboxes: SandboxRecord[] = enrollments.flatMap((enrollment) =>
368
+ enrollment.sandboxId
369
+ ? [
370
+ {
371
+ id: enrollment.sandboxId,
372
+ accountId: enrollment.accountId,
373
+ workspaceId: enrollment.workspaceId,
374
+ kind: "selfhosted" as const,
375
+ name: enrollment.sandboxName ?? `${enrollment.os} machine`,
376
+ enrollmentId: enrollment.id,
377
+ createdAt: enrollment.createdAt,
378
+ updatedAt: enrollment.updatedAt,
379
+ },
380
+ ]
381
+ : [],
382
+ );
383
+ const sandboxes = [
384
+ ...legacySandboxes.filter(
385
+ (sandbox) => !scopedSandboxes.some((scoped) => scoped.id === sandbox.id),
386
+ ),
387
+ ...scopedSandboxes,
388
+ ];
306
389
  for (const sandbox of sandboxes) {
307
390
  if (sandbox.kind !== "selfhosted" || !sandbox.enrollmentId) {
308
391
  continue;
309
392
  }
310
- const enrollment = await getEnrollment(db, ctx.workspaceId, sandbox.enrollmentId);
393
+ const enrollment =
394
+ enrollmentById.get(sandbox.enrollmentId) ??
395
+ (await getEnrollment(db, resourceAccess, sandbox.enrollmentId));
311
396
  if (!enrollment || enrollment.status !== "active") {
312
397
  // Revoked enrollments remain durable audit/history records, but they are
313
398
  // intentionally absent from the normal attach/run picker.
314
399
  continue;
315
400
  }
401
+ const liveConnection = await getLiveEnrollmentConnection(
402
+ db,
403
+ resourceAccess,
404
+ sandbox.enrollmentId,
405
+ );
316
406
  const probe = enrollment
317
- ? await probeEnrollment(services, ctx.workspaceId, enrollment)
318
- : { liveness: "offline" as FleetLiveness, consented: false, hasDisplay: false };
407
+ ? await probeEnrollment(services, sandbox.workspaceId, enrollment, liveConnection)
408
+ : {
409
+ liveness: "offline" as FleetLiveness,
410
+ consented: false,
411
+ hasDisplay: false,
412
+ };
319
413
  entries.push({
320
414
  id: sandbox.id,
321
415
  kind: "selfhosted",
@@ -368,7 +462,7 @@ async function resolveTarget(
368
462
  ctx: FleetContext,
369
463
  target: string,
370
464
  ): Promise<
371
- | { ok: true; targetSandboxId: string | null }
465
+ | { ok: true; targetSandboxId: string | null; workspaceRoot?: string }
372
466
  | { ok: false; reason: string; code: BackendUnresolvableCode }
373
467
  > {
374
468
  // The session's own group box → the default pointer (null).
@@ -382,7 +476,17 @@ async function resolveTarget(
382
476
  }
383
477
  return { ok: true, targetSandboxId: null };
384
478
  }
385
- const sandbox = await getSandbox(services.db, ctx.workspaceId, target);
479
+ const sandbox = await getSandbox(
480
+ services.db,
481
+ ctx.subjectId
482
+ ? {
483
+ accountId: ctx.accountId,
484
+ workspaceId: ctx.workspaceId,
485
+ subjectId: ctx.subjectId,
486
+ }
487
+ : ctx.workspaceId,
488
+ target,
489
+ );
386
490
  if (!sandbox) {
387
491
  return {
388
492
  ok: false,
@@ -402,8 +506,13 @@ async function resolveTarget(
402
506
  isSessionGroup: false,
403
507
  });
404
508
  if (!establishable.ok) {
405
- return { ok: false, reason: establishable.reason, code: establishable.code };
509
+ return {
510
+ ok: false,
511
+ reason: establishable.reason,
512
+ code: establishable.code,
513
+ };
406
514
  }
515
+ let targetWorkspaceRoot: string | undefined;
407
516
  if (sandbox.kind === "selfhosted") {
408
517
  if (!sandbox.enrollmentId) {
409
518
  return {
@@ -412,7 +521,17 @@ async function resolveTarget(
412
521
  code: "offline_enrollment",
413
522
  };
414
523
  }
415
- const enrollment = await getEnrollment(services.db, ctx.workspaceId, sandbox.enrollmentId);
524
+ const enrollment = await getLiveEnrollmentConnection(
525
+ services.db,
526
+ ctx.subjectId
527
+ ? {
528
+ accountId: ctx.accountId,
529
+ workspaceId: ctx.workspaceId,
530
+ subjectId: ctx.subjectId,
531
+ }
532
+ : sandbox.workspaceId,
533
+ sandbox.enrollmentId,
534
+ );
416
535
  if (!enrollment) {
417
536
  return {
418
537
  ok: false,
@@ -420,7 +539,15 @@ async function resolveTarget(
420
539
  code: "offline_enrollment",
421
540
  };
422
541
  }
423
- const probe = await probeEnrollment(services, ctx.workspaceId, enrollment);
542
+ if (!enrollment.workspaceRoot) {
543
+ return {
544
+ ok: false,
545
+ reason: `sandbox ${target} has not reported an absolute workspace root; reconnect it with a current agent`,
546
+ code: "workspace_root_unavailable",
547
+ };
548
+ }
549
+ targetWorkspaceRoot = enrollment.workspaceRoot;
550
+ const probe = await probeEnrollment(services, sandbox.workspaceId, enrollment, enrollment);
424
551
  if (probe.liveness !== "online") {
425
552
  return {
426
553
  ok: false,
@@ -429,7 +556,11 @@ async function resolveTarget(
429
556
  };
430
557
  }
431
558
  }
432
- return { ok: true, targetSandboxId: sandbox.id };
559
+ return {
560
+ ok: true,
561
+ targetSandboxId: sandbox.id,
562
+ ...(targetWorkspaceRoot ? { workspaceRoot: targetWorkspaceRoot } : {}),
563
+ };
433
564
  }
434
565
 
435
566
  /**
@@ -482,6 +613,7 @@ export async function swapActiveSandbox(
482
613
  const pointer = (await readActiveSandbox(services.db, ctx.workspaceId, ctx.sessionId)) ?? {
483
614
  activeSandboxId: null,
484
615
  activeEpoch: 0,
616
+ workingDir: null,
485
617
  };
486
618
  return {
487
619
  swapped: false,
@@ -503,7 +635,27 @@ export async function swapActiveSandbox(
503
635
  const pointer = (await readActiveSandbox(services.db, ctx.workspaceId, ctx.sessionId)) ?? {
504
636
  activeSandboxId: null,
505
637
  activeEpoch: 0,
638
+ workingDir: null,
506
639
  };
640
+ let effectiveWorkingDir = workingDir;
641
+ if (resolved.workspaceRoot) {
642
+ try {
643
+ effectiveWorkingDir = resolveConnectedMachineWorkspaceRoot(
644
+ resolved.workspaceRoot,
645
+ workingDir === undefined && pointer.activeSandboxId === resolved.targetSandboxId
646
+ ? pointer.workingDir
647
+ : workingDir,
648
+ );
649
+ } catch (error) {
650
+ return {
651
+ swapped: false,
652
+ activeSandboxId: pointer.activeSandboxId,
653
+ activeEpoch: pointer.activeEpoch,
654
+ reason: error instanceof Error ? error.message : String(error),
655
+ code: "invalid_working_directory",
656
+ };
657
+ }
658
+ }
507
659
  // Even a same-target attach advances active_epoch. It is a repair/fence
508
660
  // request, not a no-op acknowledgment: any cached stale route is invalidated
509
661
  // only after target readiness has been proved above.
@@ -513,7 +665,13 @@ export async function swapActiveSandbox(
513
665
  sessionId: ctx.sessionId,
514
666
  targetSandboxId: resolved.targetSandboxId,
515
667
  expectedEpoch: pointer.activeEpoch,
516
- ...(workingDir !== undefined ? { workingDir } : {}),
668
+ ...(ctx.subjectId ? { subjectId: ctx.subjectId } : {}),
669
+ ...(ctx.attemptAuthority ? { personalMachineAttempt: ctx.attemptAuthority } : {}),
670
+ ...(resolved.workspaceRoot !== undefined
671
+ ? { workingDir: effectiveWorkingDir ?? resolved.workspaceRoot }
672
+ : workingDir !== undefined
673
+ ? { workingDir }
674
+ : {}),
517
675
  });
518
676
  if (result.swapped && result.pointer) {
519
677
  return {
@@ -569,6 +727,9 @@ export type RunOnResult = {
569
727
  export type RunOnSelfhostedMachine = {
570
728
  workspaceId: string;
571
729
  agentId: string;
730
+ /** Exact live daemon pinned for this operation. */
731
+ connectionInstanceId: string;
732
+ workspaceRoot: string;
572
733
  controlRpc: ControlRpc;
573
734
  relay: SelfhostedRelayConfig;
574
735
  /** Short request/reply deadline for read/write and other control operations. */
@@ -577,8 +738,50 @@ export type RunOnSelfhostedMachine = {
577
738
  execTimeoutMs: number;
578
739
  /** Streaming transport required when execTimeoutMs is 0 (unbounded). */
579
740
  opStream?: SelfhostedOpStreamDeps;
741
+ operationResourcePolicy?: SelfhostedOperationResourcePolicy;
742
+ operationResourcePolicySupported?: boolean;
743
+ operationCpuQuotaSupported?: boolean;
744
+ /** Last-boundary authority/connection resolver invoked before every physical
745
+ * provider operation. */
746
+ resolveOperationAdmission?: () => Promise<SelfhostedOperationAdmission | null>;
747
+ /**
748
+ * Exact-attempt values for this one child process. Never persisted on the
749
+ * enrollment or machine. The caller may supply these only after authenticating
750
+ * the active attempt that owns the one-off operation.
751
+ */
752
+ transientExecEnvironment?: Readonly<Record<string, string>>;
753
+ };
754
+
755
+ export type RunOnOptions = {
756
+ transientExecEnvironment?: Readonly<Record<string, string>>;
580
757
  };
581
758
 
759
+ function runOnOperationAdmission(
760
+ services: FleetServices,
761
+ enrollment: EnrollmentRecord | null,
762
+ ): SelfhostedOperationAdmission | null {
763
+ if (!enrollment?.connectionInstanceId || !enrollment.workspaceRoot) return null;
764
+ const opStream =
765
+ services.settings.agentOpStreamEnabled === true &&
766
+ enrollment.opStream === true &&
767
+ services.bus?.getOpStreamConnection
768
+ ? {
769
+ transport: new NatsOpStreamTransport(
770
+ async () => services.bus?.getOpStreamConnection?.() ?? null,
771
+ ),
772
+ }
773
+ : undefined;
774
+ return {
775
+ workspaceId: enrollment.workspaceId,
776
+ connectionInstanceId: enrollment.connectionInstanceId,
777
+ workspaceRoot: enrollment.workspaceRoot,
778
+ ...(opStream ? { opStream } : {}),
779
+ operationResourcePolicy: enrollment.operationPolicy,
780
+ operationResourcePolicySupported: enrollment.agentCapabilities.operationResourcePolicy === true,
781
+ operationCpuQuotaSupported: enrollment.agentCapabilities.operationCpuQuota === true,
782
+ };
783
+ }
784
+
582
785
  /**
583
786
  * Execute the one-off machine operation once the workspace/enrollment lookup has
584
787
  * succeeded. Kept separate from {@link runOnSandbox} so the command/deadline
@@ -593,10 +796,29 @@ export async function executeRunOnSelfhostedMachine(
593
796
  const session = new SelfhostedSession({
594
797
  workspaceId: machine.workspaceId,
595
798
  agentId: machine.agentId,
799
+ connectionInstanceId: machine.connectionInstanceId,
800
+ workspaceRoot: machine.workspaceRoot,
596
801
  controlRpc: machine.controlRpc,
597
802
  relay: machine.relay,
598
803
  timeoutMs: machine.controlTimeoutMs,
599
804
  execTimeoutMs: machine.execTimeoutMs,
805
+ ...(machine.operationResourcePolicy !== undefined
806
+ ? { operationResourcePolicy: machine.operationResourcePolicy }
807
+ : {}),
808
+ ...(machine.operationResourcePolicySupported !== undefined
809
+ ? {
810
+ operationResourcePolicySupported: machine.operationResourcePolicySupported,
811
+ }
812
+ : {}),
813
+ ...(machine.operationCpuQuotaSupported !== undefined
814
+ ? { operationCpuQuotaSupported: machine.operationCpuQuotaSupported }
815
+ : {}),
816
+ ...(machine.resolveOperationAdmission !== undefined
817
+ ? { resolveOperationAdmission: machine.resolveOperationAdmission }
818
+ : {}),
819
+ ...(machine.transientExecEnvironment !== undefined
820
+ ? { transientExecEnvironment: () => machine.transientExecEnvironment! }
821
+ : {}),
600
822
  ...(machine.opStream !== undefined ? { opStream: machine.opStream } : {}),
601
823
  });
602
824
 
@@ -622,7 +844,9 @@ export async function executeRunOnSelfhostedMachine(
622
844
  timedOut,
623
845
  deadlineMs,
624
846
  ...(timedOut
625
- ? { reason: `command exceeded the ${deadlineMs} ms execution deadline` }
847
+ ? {
848
+ reason: `command exceeded the ${deadlineMs} ms execution deadline`,
849
+ }
626
850
  : !hasTerminalExit
627
851
  ? { reason: "machine returned no terminal exit code" }
628
852
  : {}),
@@ -630,9 +854,17 @@ export async function executeRunOnSelfhostedMachine(
630
854
  }
631
855
  if (op.kind === "read") {
632
856
  const bytes = await session.readFile({ path: op.path });
633
- return { target, kind: "read", ok: true, content: new TextDecoder().decode(bytes) };
857
+ return {
858
+ target,
859
+ kind: "read",
860
+ ok: true,
861
+ content: new TextDecoder().decode(bytes),
862
+ };
634
863
  }
635
- const bytesWritten = await session.writeFile({ path: op.path, content: op.content });
864
+ const bytesWritten = await session.writeFile({
865
+ path: op.path,
866
+ content: op.content,
867
+ });
636
868
  return { target, kind: "write", ok: true, bytesWritten };
637
869
  } catch (error) {
638
870
  const reason = error instanceof Error ? error.message : String(error);
@@ -667,8 +899,19 @@ export async function runOnSandbox(
667
899
  ctx: FleetContext,
668
900
  target: string,
669
901
  op: RunOnOp,
902
+ options: RunOnOptions = {},
670
903
  ): Promise<RunOnResult> {
671
- const sandbox = await getSandbox(services.db, ctx.workspaceId, target);
904
+ const sandbox = await getSandbox(
905
+ services.db,
906
+ ctx.subjectId
907
+ ? {
908
+ accountId: ctx.accountId,
909
+ workspaceId: ctx.workspaceId,
910
+ subjectId: ctx.subjectId,
911
+ }
912
+ : ctx.workspaceId,
913
+ target,
914
+ );
672
915
  if (!sandbox) {
673
916
  return {
674
917
  target,
@@ -686,8 +929,59 @@ export async function runOnSandbox(
686
929
  reason: `run_on routes one-off ops to enrolled selfhosted machines; ${sandbox.kind} targets are reached via the active sandbox (swap to it first)`,
687
930
  };
688
931
  }
689
- const enrollment = await getEnrollment(services.db, ctx.workspaceId, sandbox.enrollmentId);
690
- if (!enrollment || enrollment.status !== "active") {
932
+ if (sandbox.scope === "user") {
933
+ if (!ctx.subjectId || !ctx.attemptAuthority) {
934
+ return {
935
+ target,
936
+ targetName: sandbox.name,
937
+ kind: op.kind,
938
+ ok: false,
939
+ reason: "personal Connected Machine use requires an exact admitted agent attempt",
940
+ };
941
+ }
942
+ try {
943
+ const authorized = await authorizePersonalMachineForAttempt(services.db, {
944
+ accountId: ctx.accountId,
945
+ workspaceId: ctx.workspaceId,
946
+ subjectId: ctx.subjectId,
947
+ sessionId: ctx.sessionId,
948
+ turnId: ctx.attemptAuthority.turnId,
949
+ attemptId: ctx.attemptAuthority.attemptId,
950
+ executionGeneration: ctx.attemptAuthority.executionGeneration,
951
+ enrollmentId: sandbox.enrollmentId,
952
+ requireActiveSandbox: false,
953
+ });
954
+ if (!authorized) {
955
+ return {
956
+ target,
957
+ targetName: sandbox.name,
958
+ kind: op.kind,
959
+ ok: false,
960
+ reason: "personal Connected Machine authority was not admitted for this attempt",
961
+ };
962
+ }
963
+ } catch {
964
+ return {
965
+ target,
966
+ targetName: sandbox.name,
967
+ kind: op.kind,
968
+ ok: false,
969
+ reason: "personal Connected Machine authority is not live for this attempt",
970
+ };
971
+ }
972
+ }
973
+ const enrollment = await getLiveEnrollmentConnection(
974
+ services.db,
975
+ ctx.subjectId
976
+ ? {
977
+ accountId: ctx.accountId,
978
+ workspaceId: ctx.workspaceId,
979
+ subjectId: ctx.subjectId,
980
+ }
981
+ : sandbox.workspaceId,
982
+ sandbox.enrollmentId,
983
+ );
984
+ if (!enrollment || enrollment.status !== "active" || !enrollment.connectionInstanceId) {
691
985
  return {
692
986
  target,
693
987
  targetName: sandbox.name,
@@ -696,15 +990,65 @@ export async function runOnSandbox(
696
990
  reason: `sandbox ${target} is not enrolled/active`,
697
991
  };
698
992
  }
993
+ if (!enrollment.workspaceRoot) {
994
+ return {
995
+ target,
996
+ targetName: sandbox.name,
997
+ kind: op.kind,
998
+ ok: false,
999
+ reason: `sandbox ${target} has not reported an absolute workspace root; reconnect it with a current agent`,
1000
+ };
1001
+ }
1002
+ const resolveOperationAdmission = async (): Promise<SelfhostedOperationAdmission | null> => {
1003
+ try {
1004
+ const current =
1005
+ sandbox.scope === "user" && ctx.subjectId && ctx.attemptAuthority
1006
+ ? await resolvePersonalMachineConnectionForAttempt(services.db, {
1007
+ accountId: ctx.accountId,
1008
+ workspaceId: ctx.workspaceId,
1009
+ subjectId: ctx.subjectId,
1010
+ sessionId: ctx.sessionId,
1011
+ turnId: ctx.attemptAuthority.turnId,
1012
+ attemptId: ctx.attemptAuthority.attemptId,
1013
+ executionGeneration: ctx.attemptAuthority.executionGeneration,
1014
+ enrollmentId: sandbox.enrollmentId!,
1015
+ requireActiveSandbox: false,
1016
+ })
1017
+ : await getLiveEnrollmentConnection(
1018
+ services.db,
1019
+ ctx.subjectId
1020
+ ? {
1021
+ accountId: ctx.accountId,
1022
+ workspaceId: ctx.workspaceId,
1023
+ subjectId: ctx.subjectId,
1024
+ }
1025
+ : sandbox.workspaceId,
1026
+ sandbox.enrollmentId!,
1027
+ );
1028
+ return runOnOperationAdmission(services, current);
1029
+ } catch {
1030
+ return null;
1031
+ }
1032
+ };
699
1033
 
700
1034
  const result = await executeRunOnSelfhostedMachine(
701
1035
  {
702
- workspaceId: ctx.workspaceId,
1036
+ workspaceId: sandbox.workspaceId,
703
1037
  agentId: sandbox.enrollmentId,
1038
+ connectionInstanceId: enrollment.connectionInstanceId,
1039
+ workspaceRoot: enrollment.workspaceRoot,
704
1040
  controlRpc: controlRpc(services.bus),
705
1041
  relay: relayConfigFromSettings(services.settings),
706
1042
  controlTimeoutMs: services.settings.sandboxSelfhostedControlTimeoutMs,
707
1043
  execTimeoutMs: services.settings.sandboxSelfhostedExecTimeoutMs,
1044
+ operationResourcePolicy: enrollment.operationPolicy,
1045
+ operationResourcePolicySupported:
1046
+ enrollment.agentCapabilities.operationResourcePolicy === true,
1047
+ operationCpuQuotaSupported: enrollment.agentCapabilities.operationCpuQuota === true,
1048
+ resolveOperationAdmission,
1049
+ ...(options.transientExecEnvironment !== undefined
1050
+ ? { transientExecEnvironment: options.transientExecEnvironment }
1051
+ : {}),
708
1052
  ...(services.settings.agentOpStreamEnabled === true &&
709
1053
  enrollment.opStream === true &&
710
1054
  services.bus?.getOpStreamConnection
@@ -749,17 +1093,19 @@ export async function provisionSandbox(
749
1093
  input: { kind: "selfhosted" | "modal"; name?: string },
750
1094
  ): Promise<ProvisionResult> {
751
1095
  if (input.kind === "selfhosted") {
752
- const base = (services.settings.publicBaseUrl ?? "https://get.opengeni.ai").replace(/\/+$/, "");
1096
+ const base = (services.settings.publicBaseUrl ?? "https://app.opengeni.ai").replace(/\/+$/, "");
1097
+ const unixEnvironment = `OPENGENI_API_URL=${base} OPENGENI_WORKSPACE_ID=${ctx.workspaceId}`;
1098
+ const windowsEnvironment = `$env:OPENGENI_API_URL='${base}'; $env:OPENGENI_WORKSPACE_ID='${ctx.workspaceId}';`;
753
1099
  return {
754
1100
  kind: "selfhosted",
755
1101
  instructions:
756
- "Share these instructions with a human operator. They install the OpenGeni agent on the machine, run `opengeni-agent 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.",
1102
+ "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.",
757
1103
  // Install from THIS control plane's origin (not a hardcoded public CDN): the
758
1104
  // served install script is rewritten to pull the per-SHA agent baked into
759
1105
  // this exact deployment (see apps/api/src/routes/install.ts), so a deployed
760
1106
  // env is self-contained and a private/air-gapped one works with no public DNS.
761
- installCommandUnix: `curl -fsSL ${base}/install.sh | sh`,
762
- installCommandWindows: `irm ${base}/install.ps1 | iex`,
1107
+ installCommandUnix: `curl -fsSL ${base}/install.sh | ${unixEnvironment} sh`,
1108
+ installCommandWindows: `${windowsEnvironment} irm ${base}/install.ps1 | iex`,
763
1109
  verificationUri: `${base}/device`,
764
1110
  note: "Whole-machine access requires explicit human consent in the device-flow web page; the agent cannot self-consent.",
765
1111
  };