@opengeni/core 1.0.2 → 1.1.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/core",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
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": {
@@ -50,17 +50,17 @@
50
50
  },
51
51
  "dependencies": {
52
52
  "@modelcontextprotocol/sdk": "^1.29.0",
53
- "@opengeni/capabilities": "1.0.2",
54
- "@opengeni/codex": "1.0.2",
55
- "@opengeni/config": "1.0.2",
56
- "@opengeni/contracts": "1.0.2",
57
- "@opengeni/db": "1.0.2",
58
- "@opengeni/documents": "1.0.2",
59
- "@opengeni/events": "1.0.2",
60
- "@opengeni/network": "1.0.2",
61
- "@opengeni/observability": "1.0.2",
62
- "@opengeni/runtime": "1.0.2",
63
- "@opengeni/storage": "1.0.2",
53
+ "@opengeni/capabilities": "1.1.0",
54
+ "@opengeni/codex": "1.1.0",
55
+ "@opengeni/config": "1.1.0",
56
+ "@opengeni/contracts": "1.1.0",
57
+ "@opengeni/db": "1.1.0",
58
+ "@opengeni/documents": "1.1.0",
59
+ "@opengeni/events": "1.1.0",
60
+ "@opengeni/network": "1.1.0",
61
+ "@opengeni/observability": "1.1.0",
62
+ "@opengeni/runtime": "1.1.0",
63
+ "@opengeni/storage": "1.1.0",
64
64
  "hono": "^4.12.18",
65
65
  "zod": "^4.2.1"
66
66
  },
@@ -19,10 +19,8 @@ import {
19
19
  isRetryableDatabaseTransportFailure,
20
20
  nestedPostgresSqlState,
21
21
  replayAppliedSessionFork,
22
- sessionTenancyProductActivated,
23
22
  SessionTenancyAccessError,
24
23
  SessionTenancyInvalidRequestError,
25
- SessionTenancyNotActivatedError,
26
24
  transitionSessionVisibility,
27
25
  type Database,
28
26
  type ForkSessionContentResult,
@@ -101,9 +99,10 @@ export async function getManagedHumanSessionCreateCapabilities(
101
99
  }
102
100
  throw error;
103
101
  }
104
- // Mirror the database create fence exactly: a managed human's own Personal
105
- // workspace needs only the operator readiness receipt, while a shared
106
- // organization workspace additionally needs the owner/admin product setting.
102
+ // Mirror the database create fence exactly: every organization is
103
+ // session-tenancy activated (migration 0611), so a member's own Personal
104
+ // workspace is always private-capable, while a shared organization workspace
105
+ // additionally needs the owner/admin Only-me product setting.
107
106
  let activated = false;
108
107
  try {
109
108
  const policy = await getPrivateSessionCreatePolicy(deps.db, {
@@ -153,9 +152,6 @@ async function requireSessionTenancyMutationGate(
153
152
  // missing, shared, or another owner's private session.
154
153
  requireVerifiedOwningUser(authorization, workspaceId);
155
154
  for (const permission of permissions) requirePermission(authorization.grant, permission);
156
- if (!(await sessionTenancyProductActivated(deps.db, workspaceId))) {
157
- throw new SessionTenancyNotActivatedError();
158
- }
159
155
  }
160
156
 
161
157
  async function publishExactCommittedEvent(
@@ -8,8 +8,6 @@ import {
8
8
  issueSelfUserResourceGrant,
9
9
  listSelfUserResourceAuthorities,
10
10
  revokeSelfUserResourceGrant,
11
- sessionTenancyProductActivated,
12
- SessionTenancyNotActivatedError,
13
11
  } from "@opengeni/db";
14
12
  import { requirePermission, type AccessGrantAuthorization } from "../access";
15
13
  import type { AppDependencies } from "../dependencies";
@@ -46,9 +44,6 @@ async function requireOwnerProductGate(
46
44
  // Target-free gates must run before an optional target-session lookup or host
47
45
  // callback so rejected principals cannot use grant management as an oracle.
48
46
  requireOwnerAuthority(authorization, workspaceId, permissions);
49
- if (!(await sessionTenancyProductActivated(deps.db, workspaceId))) {
50
- throw new SessionTenancyNotActivatedError();
51
- }
52
47
  }
53
48
 
54
49
  function requireOwnerAuthority(
@@ -75,14 +70,6 @@ export async function listManagedHumanUserResourceAuthorities(
75
70
  input: { resourceKind: UserResourceKind; cursor?: string | undefined; limit: number },
76
71
  ) {
77
72
  requireOwnerAuthority(authorization, workspaceId, LIST_PERMISSIONS[input.resourceKind]);
78
- // Before activation there cannot be usable personal-resource authority in
79
- // this organization. Discovery therefore has an exact empty answer; only
80
- // issue/revoke and runtime use remain behind the forward-only product gate.
81
- // This keeps ordinary workspace-owned Rig/Variable Set selection independent
82
- // from an optional personal-resource product that is not enabled.
83
- if (!(await sessionTenancyProductActivated(deps.db, workspaceId))) {
84
- return { authorities: [], nextCursor: null };
85
- }
86
73
  return await listSelfUserResourceAuthorities(deps.db, {
87
74
  accountId: authorization.grant.accountId,
88
75
  workspaceId,
@@ -0,0 +1,99 @@
1
+ import { configuredStaticUsageLimits, type Settings } from "@opengeni/config";
2
+ import type { EntitlementsPort } from "@opengeni/contracts";
3
+ import { modelFundingForAdmission } from "./limits";
4
+ import {
5
+ checkWorkspaceAllowance,
6
+ getBillingBalance,
7
+ isCodexBilledTurn,
8
+ sumUsageQuantity,
9
+ type Database,
10
+ } from "@opengeni/db";
11
+
12
+ export type AgentRunAdmissionDenial =
13
+ | "insufficient_credits"
14
+ | "allowance_exhausted"
15
+ | "monthly_model_cost_limit"
16
+ | "monthly_agent_run_limit";
17
+
18
+ /** Read-only admission shared by service-authored runs and goal Resume. */
19
+ export async function agentRunAdmissionDenial(
20
+ services: { db: Database; settings: Settings; entitlements?: EntitlementsPort | null },
21
+ input: {
22
+ accountId: string;
23
+ workspaceId: string;
24
+ model: string;
25
+ requestedAgentRuns: number;
26
+ /** The accepted work's causal human, not the scheduler/service caller. */
27
+ initiatingHumanSubjectId?: string | null;
28
+ },
29
+ ): Promise<AgentRunAdmissionDenial | null> {
30
+ const codexBilled = await isCodexBilledTurn({
31
+ db: services.db,
32
+ settings: services.settings,
33
+ workspaceId: input.workspaceId,
34
+ model: input.model,
35
+ });
36
+ const externallyBilled = modelFundingForAdmission(
37
+ services.settings,
38
+ input.model,
39
+ codexBilled,
40
+ ).fundedWithoutCredits;
41
+ if (
42
+ !externallyBilled &&
43
+ (services.settings.billingMode === "stripe" || services.settings.usageLimitsMode === "managed")
44
+ ) {
45
+ if (services.entitlements) {
46
+ const decision = await services.entitlements.admitRun({
47
+ accountId: input.accountId,
48
+ workspaceId: input.workspaceId,
49
+ action: "agent_run:create",
50
+ quantity: input.requestedAgentRuns,
51
+ });
52
+ if (!decision.allowed) return "insufficient_credits";
53
+ } else {
54
+ const balance = await getBillingBalance(services.db, input.accountId);
55
+ if (balance.balanceMicros <= 0) return "insufficient_credits";
56
+ }
57
+ }
58
+ if (!externallyBilled) {
59
+ const refusal = await checkWorkspaceAllowance(services.db, {
60
+ accountId: input.accountId,
61
+ workspaceId: input.workspaceId,
62
+ subjectId: input.initiatingHumanSubjectId ?? null,
63
+ });
64
+ if (refusal) return refusal.code;
65
+ }
66
+ if (
67
+ services.settings.usageLimitsMode !== "static" &&
68
+ services.settings.usageLimitsMode !== "managed"
69
+ ) {
70
+ return null;
71
+ }
72
+ const limits = configuredStaticUsageLimits(services.settings);
73
+ if (!externallyBilled && limits.maxMonthlyCostMicrosPerAccount) {
74
+ const used = await sumUsageQuantity(services.db, {
75
+ accountId: input.accountId,
76
+ eventType: "model.cost",
77
+ since: startOfUtcMonth(),
78
+ });
79
+ if (used >= limits.maxMonthlyCostMicrosPerAccount) {
80
+ return "monthly_model_cost_limit";
81
+ }
82
+ }
83
+ if (limits.maxMonthlyAgentRunsPerWorkspace) {
84
+ const used = await sumUsageQuantity(services.db, {
85
+ workspaceId: input.workspaceId,
86
+ eventType: "agent_run.created",
87
+ since: startOfUtcMonth(),
88
+ });
89
+ if (used + input.requestedAgentRuns > limits.maxMonthlyAgentRunsPerWorkspace) {
90
+ return "monthly_agent_run_limit";
91
+ }
92
+ }
93
+ return null;
94
+ }
95
+
96
+ function startOfUtcMonth(): Date {
97
+ const now = new Date();
98
+ return new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1));
99
+ }
@@ -3,6 +3,7 @@ import type {
3
3
  ConnectionCredentialsPort,
4
4
  Document,
5
5
  DocumentAuthorityKind,
6
+ EntitlementsPort,
6
7
  GitHubAppApiPort,
7
8
  ScheduledTask,
8
9
  ScheduledTaskTriggerType,
@@ -155,6 +156,8 @@ export type AppDependencies = {
155
156
  */
156
157
  catalogSourceSettings?: Settings;
157
158
  db: Database;
159
+ /** Read-only host funding admission; unset uses the standalone billing ledger. */
160
+ entitlements?: EntitlementsPort | null;
158
161
  /**
159
162
  * Host-composed editable artifact engine. Standalone startup binds the same
160
163
  * native kernel/DB/object-store implementation; embedded hosts may inject an
@@ -8,6 +8,7 @@ import {
8
8
  import {
9
9
  AGENT_SKILL_MANAGE_TOOL_NAMES,
10
10
  AUTOMATIC_SESSION_TITLE_FALLBACK,
11
+ bundledSkillSelectionForAgentConfig,
11
12
  DEFAULT_FIRST_PARTY_MCP_PERMISSIONS,
12
13
  FIRST_PARTY_IN_PROCESS_TOOL_NAMES,
13
14
  type FirstPartyMcpToolName,
@@ -395,14 +396,19 @@ export async function workspaceSessionEffectiveToolsContext(
395
396
 
396
397
  function sessionHasSkills(session: Session, context: SessionEffectiveToolsContext): boolean {
397
398
  if (context.hasWorkspaceSkills || session.skills.length > 0) return true;
398
- // Undefined is the worker's bundled default. Explicit [] means no bundles.
399
- if (session.bundledSkillIds === undefined) return true;
399
+ // Undefined is the worker's bundled default. Explicit [] means no bundles,
400
+ // and a "none" agent without an explicit list has none (as in the worker).
401
+ const bundledSkillIds = bundledSkillSelectionForAgentConfig(
402
+ session.bundledSkillIds,
403
+ session.agent,
404
+ );
405
+ if (bundledSkillIds === undefined) return true;
400
406
  const tools = new Set(
401
407
  resolveAgentToolFamilies(session.agent).firstPartyTools(
402
408
  allowedFirstPartyMcpToolsForSession(context.settings, session.firstPartyMcpTools),
403
409
  ),
404
410
  );
405
- return session.bundledSkillIds.some((id) => {
411
+ return bundledSkillIds.some((id) => {
406
412
  if (
407
413
  [
408
414
  "builtin:opengeni-documents",
@@ -479,7 +485,7 @@ export function sessionEffectiveToolProjectionInput(
479
485
  (!session.title?.trim() || session.title.trim() === AUTOMATIC_SESSION_TITLE_FALLBACK);
480
486
  const firstPartyMcpTools =
481
487
  context && toolRefs.some((ref) => ref.id === "opengeni")
482
- ? resolveAgentToolFamilies(session.agent)
488
+ ? resolveAgentToolFamilies(session.agent, { sandboxAttached: sandboxAvailable })
483
489
  .firstPartyTools(
484
490
  allowedFirstPartyMcpToolsForSession(context.settings, session.firstPartyMcpTools),
485
491
  )
@@ -512,7 +518,9 @@ export function sessionEffectiveToolProjectionInput(
512
518
  firstPartyModelNames.set(name, `interaction__${name}`);
513
519
  if (
514
520
  !firstPartyMcpTools.includes(name) &&
515
- resolveAgentToolFamilies(session.agent).allowsFirstPartyTool(name) &&
521
+ resolveAgentToolFamilies(session.agent, {
522
+ sandboxAttached: sandboxAvailable,
523
+ }).allowsFirstPartyTool(name) &&
516
524
  session.firstPartyMcpTools.includes(name)
517
525
  )
518
526
  firstPartyMcpTools.push(name);
@@ -561,6 +569,7 @@ export function sessionEffectiveToolProjectionInput(
561
569
  routerInHistory:
562
570
  context?.routerInHistory === true ||
563
571
  context?.routerHistorySessionIds?.has(session.id) === true,
572
+ ...(context ? { sandboxAttached: sandboxAvailable } : {}),
564
573
  };
565
574
  const families = resolveAgentToolFamilies(session.agent, environment);
566
575
  environment.hasDeferredTools =
@@ -72,6 +72,7 @@ import {
72
72
  OPENGENI_SLACK_BOT_SESSION_METADATA_KEY,
73
73
  SessionSkills,
74
74
  resolveBundledSkillSelection,
75
+ bundledSkillSelectionForAgentConfig,
75
76
  SessionSpawnDenial,
76
77
  ServiceTurnInitiator,
77
78
  ServiceTurnInitiatorContext,
@@ -182,7 +183,6 @@ import {
182
183
  SessionToolPolicyVersionConflictError,
183
184
  SessionCreateIdempotencyConflictError,
184
185
  PersonalResourceAttachmentAcceptanceError,
185
- sessionTenancyProductActivated,
186
186
  workspaceControlRequestLockTimeoutMs,
187
187
  WorkspaceControlBusyError,
188
188
  type SessionCommandActor,
@@ -327,11 +327,6 @@ async function requireAtomicPersonalResourceAttachment(
327
327
  cause: error,
328
328
  });
329
329
  }
330
- if (!(await sessionTenancyProductActivated(deps.db, workspaceId))) {
331
- throw new HTTPException(409, {
332
- message: "Session tenancy is not activated for this organization.",
333
- });
334
- }
335
330
  if (existingSession && intent.expectedAuthorityEpoch === undefined) {
336
331
  throw new HTTPException(422, {
337
332
  message: "Personal-resource attachment requires expectedAuthorityEpoch.",
@@ -1051,6 +1046,13 @@ export async function createAndStartSessionWithOutcome(input: {
1051
1046
  allowNestedAgentDepthIncrease?: boolean;
1052
1047
  subjectId?: string | null;
1053
1048
  }): Promise<CreateSessionOutcome> {
1049
+ const frozenBundledSkillIds = bundledSkillSelectionForAgentConfig(
1050
+ input.bundledSkillIds,
1051
+ input.agentConfig,
1052
+ );
1053
+ if (frozenBundledSkillIds !== input.bundledSkillIds) {
1054
+ input = { ...input, bundledSkillIds: frozenBundledSkillIds };
1055
+ }
1054
1056
  const sessionMetadata = metadataWithTurnExecutionPolicyV1(
1055
1057
  {
1056
1058
  ...input.metadata,
@@ -2545,6 +2547,9 @@ async function createSessionForRequestInFileScope(
2545
2547
  if (agentResolution.instructions !== undefined) {
2546
2548
  payload.instructions = agentResolution.instructions;
2547
2549
  }
2550
+ // Freeze what the agent configuration implies before keyed replay compares
2551
+ // it: a `"none"` agent omits the bundled guides unless the request lists them.
2552
+ bundledSkillIds = bundledSkillSelectionForAgentConfig(bundledSkillIds, agentConfig);
2548
2553
  const parentCallingTurn =
2549
2554
  parentSession && creationInitiator.actor
2550
2555
  ? await getSessionTurnForAttempt(
@@ -0,0 +1,187 @@
1
+ import {
2
+ isModelAvailableForNewSelection,
3
+ policyProviderIdForModel,
4
+ resolveModelProvider,
5
+ runnableLatencyModesForModel,
6
+ withCodexCatalogProvider,
7
+ withXaiSubscriptionCatalogProvider,
8
+ type Settings,
9
+ } from "@opengeni/config";
10
+ import {
11
+ evaluateWorkspaceModelPolicy,
12
+ type GoalAdmissionPausedReason,
13
+ type Session,
14
+ } from "@opengeni/contracts";
15
+ import { isCodexBilledModel } from "@opengeni/codex";
16
+ import { getWorkspaceModelPolicy, type Database } from "@opengeni/db";
17
+ import { resolveWorkspaceCatalogSettings } from "./model-catalog";
18
+ import { agentRunAdmissionDenial } from "./billing/agent-run-admission";
19
+
20
+ export type GoalAdmissionBlock = { pausedReason: GoalAdmissionPausedReason; message: string };
21
+
22
+ /** Catalog membership never grants access to a connection or changes the selected model. */
23
+ export function goalContinuationModelDecision(input: {
24
+ settings: Settings;
25
+ workspaceModelPolicy: Awaited<ReturnType<typeof getWorkspaceModelPolicy>>;
26
+ inheritedModel: string;
27
+ codexCompactionMode?: Session["codexCompactionMode"];
28
+ latencyMode?: Session["latencyMode"];
29
+ }): { model: string; blocked: string | null; pausedReason?: GoalAdmissionPausedReason } {
30
+ const catalogSettings = input.settings.supergrokSubscriptionEnabled
31
+ ? withXaiSubscriptionCatalogProvider(
32
+ input.settings.codexSubscriptionEnabled
33
+ ? withCodexCatalogProvider(input.settings)
34
+ : input.settings,
35
+ )
36
+ : input.settings.codexSubscriptionEnabled
37
+ ? withCodexCatalogProvider(input.settings)
38
+ : input.settings;
39
+ const model = input.inheritedModel;
40
+ if (!resolveModelProvider(catalogSettings, model)) {
41
+ return {
42
+ model,
43
+ blocked: "The selected model is unavailable. Choose an available model before resuming.",
44
+ pausedReason: "model_unavailable",
45
+ };
46
+ }
47
+ if (!isModelAvailableForNewSelection(catalogSettings, model)) {
48
+ return {
49
+ model,
50
+ blocked: "The selected model has been retired. Choose an available model before resuming.",
51
+ pausedReason: "model_unavailable",
52
+ };
53
+ }
54
+ if (
55
+ input.workspaceModelPolicy !== null &&
56
+ !evaluateWorkspaceModelPolicy(input.workspaceModelPolicy, {
57
+ providerId: policyProviderIdForModel(catalogSettings, model),
58
+ modelId: model,
59
+ }).allowed
60
+ ) {
61
+ return {
62
+ model,
63
+ blocked:
64
+ "Workspace policy blocks the selected model. Choose an allowed model before resuming.",
65
+ pausedReason: "model_policy",
66
+ };
67
+ }
68
+ if (input.codexCompactionMode === "remote_v2" && !isCodexBilledModel(model)) {
69
+ return {
70
+ model,
71
+ blocked: "This conversation requires a Codex model. Choose a Codex model before resuming.",
72
+ pausedReason: "model_policy",
73
+ };
74
+ }
75
+ if (
76
+ !runnableLatencyModesForModel(catalogSettings, model).includes(input.latencyMode ?? "standard")
77
+ ) {
78
+ return {
79
+ model,
80
+ blocked:
81
+ "The selected model does not support this conversation's latency mode. Choose a supported mode or model before resuming.",
82
+ pausedReason: "model_policy",
83
+ };
84
+ }
85
+ return { model, blocked: null };
86
+ }
87
+
88
+ /** The same scoped, secret-free catalog used at ordinary turn admission. */
89
+ export async function resolveGoalModelAdmission(
90
+ db: Database,
91
+ settings: Settings,
92
+ input: {
93
+ accountId: string;
94
+ workspaceId: string;
95
+ model: string;
96
+ codexCompactionMode?: Session["codexCompactionMode"];
97
+ latencyMode?: Session["latencyMode"];
98
+ },
99
+ ) {
100
+ const catalog = await resolveWorkspaceCatalogSettings(db, settings, {
101
+ accountId: input.accountId,
102
+ workspaceId: input.workspaceId,
103
+ retainedProductModelId: input.model,
104
+ });
105
+ const workspaceModelPolicy = await getWorkspaceModelPolicy(db, input.workspaceId);
106
+ return {
107
+ settings: catalog.settings,
108
+ ...goalContinuationModelDecision({
109
+ settings: catalog.settings,
110
+ workspaceModelPolicy,
111
+ inheritedModel: input.model,
112
+ ...(input.codexCompactionMode ? { codexCompactionMode: input.codexCompactionMode } : {}),
113
+ ...(input.latencyMode ? { latencyMode: input.latencyMode } : {}),
114
+ }),
115
+ };
116
+ }
117
+
118
+ /** Shared with scheduled work; reasons describe the actual admission gate. */
119
+ export async function goalRunBudgetBlocked(
120
+ services: Parameters<typeof agentRunAdmissionDenial>[0],
121
+ input: Omit<Parameters<typeof agentRunAdmissionDenial>[1], "requestedAgentRuns">,
122
+ ): Promise<GoalAdmissionBlock | null> {
123
+ const denial = await agentRunAdmissionDenial(services, { ...input, requestedAgentRuns: 1 });
124
+ if (denial === null) return null;
125
+ const blocks: Record<NonNullable<typeof denial>, GoalAdmissionBlock> = {
126
+ insufficient_credits: services.entitlements
127
+ ? {
128
+ pausedReason: "usage_policy",
129
+ message: "The application's usage policy blocks another run. Resume when it allows.",
130
+ }
131
+ : {
132
+ pausedReason: "credits",
133
+ message: "Insufficient Opengeni credits. Add credits before resuming.",
134
+ },
135
+ allowance_exhausted: {
136
+ pausedReason: "allowance",
137
+ message: "Opengeni usage allowance exhausted. Resume when your allowance is available.",
138
+ },
139
+ monthly_model_cost_limit: {
140
+ pausedReason: "budget",
141
+ message: "Monthly model spending limit reached. Resume when the spending limit allows.",
142
+ },
143
+ monthly_agent_run_limit: {
144
+ pausedReason: "usage_limit",
145
+ message: "Monthly agent run limit reached. Resume when the run limit allows.",
146
+ },
147
+ };
148
+ return blocks[denial];
149
+ }
150
+
151
+ export class GoalResumeBlockedError extends Error {
152
+ constructor(
153
+ readonly pausedReason: GoalAdmissionPausedReason,
154
+ message: string,
155
+ ) {
156
+ super(message);
157
+ this.name = "GoalResumeBlockedError";
158
+ }
159
+ }
160
+
161
+ /** Called under the goal transition's locks, before counters, events or wakes change. */
162
+ export async function assertGoalResumeAllowed(
163
+ services: Parameters<typeof agentRunAdmissionDenial>[0] & { catalogSourceSettings?: Settings },
164
+ session: Pick<Session, "accountId" | "workspaceId" | "model" | "codexCompactionMode"> &
165
+ Partial<Pick<Session, "latencyMode">>,
166
+ causalTurn: { initiatingHumanSubjectId: string | null } | null,
167
+ ): Promise<void> {
168
+ const decision = await resolveGoalModelAdmission(
169
+ services.db,
170
+ services.catalogSourceSettings ?? services.settings,
171
+ session,
172
+ );
173
+ if (decision.blocked) {
174
+ throw new GoalResumeBlockedError(decision.pausedReason!, decision.blocked);
175
+ }
176
+ const block = await goalRunBudgetBlocked(
177
+ { ...services, settings: decision.settings },
178
+ {
179
+ accountId: session.accountId,
180
+ workspaceId: session.workspaceId,
181
+ model: decision.model,
182
+ // Resuming does not lend the clicking user's allowance or credentials.
183
+ initiatingHumanSubjectId: causalTurn?.initiatingHumanSubjectId ?? null,
184
+ },
185
+ );
186
+ if (block) throw new GoalResumeBlockedError(block.pausedReason, block.message);
187
+ }
package/src/index.ts CHANGED
@@ -63,6 +63,7 @@ export {
63
63
  } from "./managed-session";
64
64
  export * from "./transcription";
65
65
  export * from "./model-catalog";
66
+ export * from "./goal-admission";
66
67
  export * from "./codex-model-availability";
67
68
  export * from "./default-session-model";
68
69
 
@@ -86,6 +87,7 @@ export * from "./session-authorization";
86
87
 
87
88
  // Billing / usage-limit admission (checkLimit / requireLimit / recordWorkspaceUsage).
88
89
  export * from "./billing/limits";
90
+ export * from "./billing/agent-run-admission";
89
91
 
90
92
  // Domain layer — the off-HTTP V2 surface (createSessionForRequest,
91
93