@opengeni/core 0.18.1 → 0.20.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.
@@ -132,6 +132,8 @@ export type RunOnResult = {
132
132
  target: string;
133
133
  kind: string;
134
134
  ok: boolean;
135
+ /** Display name of the target sandbox when known (from the sandboxes row). */
136
+ targetName?: string;
135
137
  stdout?: string;
136
138
  stderr?: string;
137
139
  exitCode?: number | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/core",
3
- "version": "0.18.1",
3
+ "version": "0.20.2",
4
4
  "description": "OpenGeni framework-agnostic core: the domain, access, and billing layers (neutral access, off-HTTP V2 surface). Behavior-preserving extraction from apps/api — keeps Hono's HTTPException for error throwing (typed-errors cleanup deferred).",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -34,15 +34,15 @@
34
34
  },
35
35
  "dependencies": {
36
36
  "@modelcontextprotocol/sdk": "^1.29.0",
37
- "@opengeni/codex": "^0.2.9",
38
- "@opengeni/config": "^0.10.1",
39
- "@opengeni/contracts": "^0.31.1",
40
- "@opengeni/db": "^0.22.2",
41
- "@opengeni/documents": "^0.2.70",
42
- "@opengeni/events": "^0.3.60",
43
- "@opengeni/observability": "^0.4.4",
44
- "@opengeni/runtime": "^0.17.1",
45
- "@opengeni/storage": "^0.2.54",
37
+ "@opengeni/codex": "^0.2.10",
38
+ "@opengeni/config": "^0.10.6",
39
+ "@opengeni/contracts": "^0.35.0",
40
+ "@opengeni/db": "^0.26.0",
41
+ "@opengeni/documents": "^0.3.2",
42
+ "@opengeni/events": "^0.3.65",
43
+ "@opengeni/observability": "^0.4.9",
44
+ "@opengeni/runtime": "^0.18.3",
45
+ "@opengeni/storage": "^0.2.59",
46
46
  "hono": "^4.12.18"
47
47
  },
48
48
  "engines": {
@@ -595,10 +595,12 @@ export async function controlHumanSessionWorkstream(
595
595
  ),
596
596
  interruptionCount: result.interruptionCount,
597
597
  wakeCount: result.wakeCount,
598
+ cancelledSessionCount: result.cancelledSessionCount,
599
+ cancelledTurnCount: result.cancelledTurnCount,
598
600
  };
599
- await publishSessionEventIds(deps, context.workspaceId, context.sessionId, [
600
- result.sessionControlEventId,
601
- ]);
601
+ for (const affected of result.affectedSessionEvents) {
602
+ await publishSessionEventIds(deps, context.workspaceId, affected.sessionId, affected.eventIds);
603
+ }
602
604
  await publishWorkspaceControlEvent(deps, context.workspaceId, result.workspaceControlEventId);
603
605
  await requestControlWakeDispatch(deps, result.wakeCount);
604
606
  return response;
@@ -2,6 +2,7 @@ import type { Settings } from "@opengeni/config";
2
2
  import type {
3
3
  ConnectionCredentialsPort,
4
4
  Document,
5
+ DocumentAuthorityKind,
5
6
  GitHubAppApiPort,
6
7
  ScheduledTask,
7
8
  SessionAuthorizationPort,
@@ -73,6 +74,9 @@ export type DocumentIndexClient = {
73
74
  accountId: string;
74
75
  workspaceId: string;
75
76
  documentId: string;
77
+ authorityKind: DocumentAuthorityKind;
78
+ authorityWorkspaceId: string | null;
79
+ authoritySubjectId: string | null;
76
80
  }) => Promise<Document | void>;
77
81
  };
78
82
 
@@ -8,6 +8,7 @@ import {
8
8
  normalizeRepositorySubpath,
9
9
  normalizeResourceMountPath,
10
10
  resourceIdentityKey,
11
+ resourceMountPath,
11
12
  resourceMountPathCollisionKey,
12
13
  ResourceRefConflictError,
13
14
  ResourceMountPathError,
@@ -115,7 +116,7 @@ export function normalizeResources(resources: ResourceRef[]): ResourceRef[] {
115
116
  for (const resource of resources) {
116
117
  let normalized: ResourceRef;
117
118
  if (resource.kind === "file") {
118
- const mountPath = normalizeMountPath(resource.mountPath ?? `files/${resource.fileId}`);
119
+ const mountPath = normalizeMountPath(resourceMountPath(resource));
119
120
  normalized = {
120
121
  kind: "file",
121
122
  fileId: resource.fileId,
@@ -514,6 +514,8 @@ export async function createAndStartSession(input: {
514
514
  accountId: string;
515
515
  workspaceId: string;
516
516
  initialMessage: string;
517
+ /** Create the session shell without an initial user event/agent turn. */
518
+ deferInitialTurn?: boolean;
517
519
  turnInstructions?: string | null;
518
520
  resources: ResourceRef[];
519
521
  skills?: SessionSkill[];
@@ -546,6 +548,9 @@ export async function createAndStartSession(input: {
546
548
  // workspace agentInstructions at turn time; never emitted as a timeline event.
547
549
  // Null/omitted ⇒ the session carries none.
548
550
  instructions?: string | null;
551
+ // Immutable normalized prompt-policy role. This never derives from a
552
+ // workspace membership role; null retains the bounded metadata.role fallback.
553
+ policyRole?: string | null;
549
554
  // Validated against the creating grant before this is called.
550
555
  firstPartyMcpPermissions?: Permission[] | null;
551
556
  // Model-visible first-party tool names. Authorization remains controlled by
@@ -626,6 +631,7 @@ export async function createAndStartSession(input: {
626
631
  firstPartyMcpPermissions: input.firstPartyMcpPermissions ?? null,
627
632
  firstPartyMcpTools: input.firstPartyMcpTools,
628
633
  instructions: input.instructions ?? null,
634
+ policyRole: input.policyRole ?? null,
629
635
  parentSessionId: input.parentSessionId ?? null,
630
636
  createIdempotencyKey: input.createIdempotencyKey,
631
637
  sandboxGroupId: input.sandboxGroupId ?? null,
@@ -672,6 +678,7 @@ export async function createAndStartSession(input: {
672
678
  firstPartyMcpPermissions: input.firstPartyMcpPermissions ?? null,
673
679
  firstPartyMcpTools: input.firstPartyMcpTools,
674
680
  instructions: input.instructions ?? null,
681
+ policyRole: input.policyRole ?? null,
675
682
  parentSessionId: input.parentSessionId ?? null,
676
683
  sandboxGroupId: input.sandboxGroupId ?? null,
677
684
  ...(input.sandboxOs ? { sandboxOs: input.sandboxOs } : {}),
@@ -702,6 +709,7 @@ async function finishStartSession(
702
709
  bus: EventBus;
703
710
  workflowClient: SessionWorkflowClient;
704
711
  initialMessage: string;
712
+ deferInitialTurn?: boolean;
705
713
  turnInstructions?: string | null;
706
714
  resources: ResourceRef[];
707
715
  tools: ToolRef[];
@@ -781,6 +789,7 @@ async function finishStartSession(
781
789
  }
782
790
  : null,
783
791
  consumeNewSessionDraft: input.consumeNewSessionDraft ?? null,
792
+ deferInitialTurn: input.deferInitialTurn === true,
784
793
  });
785
794
  await publishDurableSessionEvents(input.bus, session.workspaceId, session.id, started.events);
786
795
  if (started.workflowWakeRevision !== null) {
@@ -1255,9 +1264,10 @@ export async function createSessionForRequest(
1255
1264
  payload.variableSetId,
1256
1265
  )
1257
1266
  : null;
1258
- // RIG BINDING (M3). Resolve the rig this session rides — the EXPLICIT payload
1259
- // rigId when given, else the workspace default rig (workspaces.default_rig_id)
1260
- // — and FREEZE both the rig id and its currently-ACTIVE version onto the row.
1267
+ // RIG BINDING (M3). Resolve the rig this session rides — a UUID binds that
1268
+ // rig, null explicitly opts out, and omission inherits the workspace default
1269
+ // (workspaces.default_rig_id) then FREEZE both the rig id and its currently-
1270
+ // ACTIVE version onto the row.
1261
1271
  // The session then rides that exact version for its whole life; a later
1262
1272
  // promote never moves it. Rig-less (both null) when neither resolves, which is
1263
1273
  // byte-for-byte today's behavior (zero extra work, zero row change).
@@ -1265,7 +1275,8 @@ export async function createSessionForRequest(
1265
1275
  // - A stale workspace-default rig (deleted → FK-nulled, or somehow with no
1266
1276
  // active version) degrades SILENTLY to rig-less: an operator-side default
1267
1277
  // must never brick every create in the workspace.
1268
- const requestedRigId = payload.rigId ?? (await getWorkspaceDefaultRigId(db, workspaceId));
1278
+ const requestedRigId =
1279
+ payload.rigId === undefined ? await getWorkspaceDefaultRigId(db, workspaceId) : payload.rigId;
1269
1280
  let frozenRigId: string | null = null;
1270
1281
  let frozenRigVersionId: string | null = null;
1271
1282
  if (requestedRigId) {
@@ -1598,13 +1609,15 @@ export async function createSessionForRequest(
1598
1609
  }
1599
1610
  }
1600
1611
  }
1601
- await requireLimit(deps, {
1602
- accountId: grant.accountId,
1603
- workspaceId,
1604
- action: "agent_run:create",
1605
- quantity: 1,
1606
- model,
1607
- });
1612
+ if (payload.startMode !== "realtime") {
1613
+ await requireLimit(deps, {
1614
+ accountId: grant.accountId,
1615
+ workspaceId,
1616
+ action: "agent_run:create",
1617
+ quantity: 1,
1618
+ model,
1619
+ });
1620
+ }
1608
1621
  const creationInitiator = creationInitiatorForGrant(grant);
1609
1622
  let session: CreateSessionResponse;
1610
1623
  try {
@@ -1615,7 +1628,8 @@ export async function createSessionForRequest(
1615
1628
  workflowClient,
1616
1629
  accountId: grant.accountId,
1617
1630
  workspaceId,
1618
- initialMessage: payload.initialMessage,
1631
+ initialMessage: payload.initialMessage ?? "",
1632
+ deferInitialTurn: payload.startMode === "realtime",
1619
1633
  turnInstructions: payload.turnInstructions ?? null,
1620
1634
  resources,
1621
1635
  skills,
@@ -1651,6 +1665,7 @@ export async function createSessionForRequest(
1651
1665
  // contracts schema). Persisted on the row; composed system-level at turn
1652
1666
  // time. Not surfaced as an event.
1653
1667
  instructions: payload.instructions ?? null,
1668
+ policyRole: payload.policyRole ?? null,
1654
1669
  firstPartyMcpPermissions,
1655
1670
  firstPartyMcpTools,
1656
1671
  mcpServers: sessionMcpServers.dbServers,
@@ -1687,21 +1702,23 @@ export async function createSessionForRequest(
1687
1702
  }
1688
1703
  throw error;
1689
1704
  }
1690
- await recordWorkspaceUsage(deps, {
1691
- accountId: grant.accountId,
1692
- workspaceId,
1693
- subjectId: grant.subjectId,
1694
- eventType: "agent_run.created",
1695
- quantity: 1,
1696
- unit: "run",
1697
- sourceResourceType: "session",
1698
- sourceResourceId: session.id,
1699
- sessionId: session.id,
1700
- initiator: session.createdBy,
1701
- initiatorContext: session.createdByContext,
1702
- origin: creationInitiator.actor ? "system" : "user",
1703
- idempotencyKey: `agent_run.created:${workspaceId}:${session.id}`,
1704
- });
1705
+ if (payload.startMode !== "realtime") {
1706
+ await recordWorkspaceUsage(deps, {
1707
+ accountId: grant.accountId,
1708
+ workspaceId,
1709
+ subjectId: grant.subjectId,
1710
+ eventType: "agent_run.created",
1711
+ quantity: 1,
1712
+ unit: "run",
1713
+ sourceResourceType: "session",
1714
+ sourceResourceId: session.id,
1715
+ sessionId: session.id,
1716
+ initiator: session.createdBy,
1717
+ initiatorContext: session.createdByContext,
1718
+ origin: creationInitiator.actor ? "system" : "user",
1719
+ idempotencyKey: `agent_run.created:${workspaceId}:${session.id}`,
1720
+ });
1721
+ }
1705
1722
  return session;
1706
1723
  }
1707
1724
 
@@ -510,6 +510,8 @@ export type RunOnResult = {
510
510
  target: string;
511
511
  kind: string;
512
512
  ok: boolean;
513
+ /** Display name of the target sandbox when known (from the sandboxes row). */
514
+ targetName?: string;
513
515
  stdout?: string;
514
516
  stderr?: string;
515
517
  exitCode?: number | null;
@@ -628,6 +630,7 @@ export async function runOnSandbox(
628
630
  if (sandbox.kind !== "selfhosted" || !sandbox.enrollmentId) {
629
631
  return {
630
632
  target,
633
+ targetName: sandbox.name,
631
634
  kind: op.kind,
632
635
  ok: false,
633
636
  reason: `run_on routes one-off ops to enrolled selfhosted machines; ${sandbox.kind} targets are reached via the active sandbox (swap to it first)`,
@@ -635,10 +638,16 @@ export async function runOnSandbox(
635
638
  }
636
639
  const enrollment = await getEnrollment(services.db, ctx.workspaceId, sandbox.enrollmentId);
637
640
  if (!enrollment || enrollment.status !== "active") {
638
- return { target, kind: op.kind, ok: false, reason: `sandbox ${target} is not enrolled/active` };
641
+ return {
642
+ target,
643
+ targetName: sandbox.name,
644
+ kind: op.kind,
645
+ ok: false,
646
+ reason: `sandbox ${target} is not enrolled/active`,
647
+ };
639
648
  }
640
649
 
641
- return executeRunOnSelfhostedMachine(
650
+ const result = await executeRunOnSelfhostedMachine(
642
651
  {
643
652
  workspaceId: ctx.workspaceId,
644
653
  agentId: sandbox.enrollmentId,
@@ -650,6 +659,7 @@ export async function runOnSandbox(
650
659
  target,
651
660
  op,
652
661
  );
662
+ return { ...result, targetName: sandbox.name };
653
663
  }
654
664
 
655
665
  export type ProvisionResult =