@opengeni/core 3.2.2 → 3.3.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.
@@ -56,6 +56,19 @@ export type VerifiedDelegatedHumanContext = Readonly<{
56
56
  * transplanted. This proof never becomes managed-cookie/person-present proof.
57
57
  */
58
58
  export declare function stampDelegatedHumanAuthorization(request: Request, input: DelegatedHumanAuthorization): void;
59
+ /**
60
+ * Verified published-Site dispatch only: the caller has verified the signed
61
+ * gateway, served host and opaque viewer cookie. This is exact-workspace
62
+ * ownership for session routes, not a console login or a generic Site bearer.
63
+ * Native people use the existing delegated-human resolver; external owning
64
+ * users reload their existing membership without provisioning a new identity.
65
+ */
66
+ export declare function stampSiteViewerSessionAuthorization(request: Request, input: {
67
+ organizationId: string;
68
+ workspaceId: string;
69
+ subjectId: string;
70
+ permissions: Permission[];
71
+ }): void;
59
72
  /** Token prefix of personal API keys, which act as the person who made them. */
60
73
  export declare const PERSONAL_API_KEY_TOKEN_PREFIX = "ogp_";
61
74
  /**
@@ -0,0 +1,14 @@
1
+ import { cancelSiteRuntimeConversation, type Database } from "@opengeni/db";
2
+ import { type EventBus } from "@opengeni/events";
3
+ import type { SessionWorkflowClient } from "../dependencies.js";
4
+ /** Internal host lifecycle, not a session-read/control API or a viewer grant. */
5
+ export declare function cancelPublishedSiteSession(deps: {
6
+ db: Database;
7
+ bus: EventBus;
8
+ workflowClient: Pick<SessionWorkflowClient, "requestSessionWorkflowWakeDispatch">;
9
+ }, input: Parameters<typeof cancelSiteRuntimeConversation>[1]): Promise<{
10
+ receiptId: string;
11
+ replay: boolean;
12
+ cancelledSessionCount: number;
13
+ cancelledTurnCount: number;
14
+ } | null>;
@@ -1,5 +1,28 @@
1
- import type { InsightsCallsQuery, InsightsCallsResponse, InsightsUsageQuery, InsightsUsageRange, InsightsUsageResponse } from "@opengeni/contracts/insights-usage";
1
+ import type { InsightsCallsQuery, InsightsCallsResponse, InsightsUsageQuery, InsightsUsageRange, InsightsUsageResponse, InsightsSiteUsageQuery } from "@opengeni/contracts/insights-usage";
2
2
  import { type Database } from "@opengeni/db";
3
+ export declare function getInsightsSiteUsage(db: Database, input: {
4
+ accountId: string;
5
+ workspaceId: string;
6
+ query: InsightsSiteUsageQuery;
7
+ viewerSubjectId: string | null;
8
+ now?: Date;
9
+ }): Promise<{
10
+ workspaceId: string;
11
+ range: "30d" | "90d" | "month" | "today" | "week" | "ytd";
12
+ windowStart: string;
13
+ windowEnd: string;
14
+ rows: {
15
+ siteId: string | null;
16
+ title: string;
17
+ kind: "other" | "restricted" | "site";
18
+ calls: number;
19
+ pricedCalls: number;
20
+ chargedMicros: number;
21
+ apiEquivalentMicros: number;
22
+ subscriptionApiEquivalentMicros: number;
23
+ ownKeyApiEquivalentMicros: number;
24
+ }[];
25
+ }>;
3
26
  export type InsightsQueryScope = {
4
27
  accountId: string;
5
28
  /** null means organization scope; it does not grant workspace/session access. */
package/dist/index.d.ts CHANGED
@@ -85,6 +85,7 @@ export * from "./domain/organization-membership-lifecycle.js";
85
85
  export * from "./application/new-session-drafts.js";
86
86
  export * from "./application/composer-submit.js";
87
87
  export * from "./application/session-commands.js";
88
+ export * from "./application/site-session-cancellation.js";
88
89
  export * from "./application/archived-session-imports.js";
89
90
  export * from "./application/session-tenancy.js";
90
91
  export * from "./application/organization-privacy.js";
package/dist/index.js CHANGED
@@ -156,6 +156,7 @@ import {
156
156
  import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
157
157
  import {
158
158
  currentSessionRlsActorIdentityKey,
159
+ currentSiteViewerRlsContext,
159
160
  getSessionAuthorityProjection,
160
161
  getSessionAuthorizationTargetProjection,
161
162
  getSession as getSession2,
@@ -395,7 +396,8 @@ function relatedSessionAccessForAgentAttempt(actor, targetSessionId) {
395
396
  function sessionRlsActorForAuthorization(authorization) {
396
397
  return authorization.actor.kind === "agent_attempt" ? {
397
398
  subjectId: authorization.actor.subjectId,
398
- initiatingHumanSubjectId: authorization.actor.initiatingHumanSubjectId
399
+ initiatingHumanSubjectId: authorization.actor.initiatingHumanSubjectId,
400
+ agentRootSessionId: authorization.actor.callerRootSessionId
399
401
  } : { subjectId: authorization.actor.subjectId };
400
402
  }
401
403
  async function withResolvedSessionAuthorization(authorization, fn) {
@@ -426,6 +428,13 @@ async function requireSessionAuthorization(deps, grant, input) {
426
428
  if (!authority && input.surface !== "core") {
427
429
  throw new SessionAuthorizationDeniedError("not_found");
428
430
  }
431
+ if (authority?.siteAccessRootId) {
432
+ const viewer = currentSiteViewerRlsContext();
433
+ const resolved = await resolveSessionAuthorizationActor(deps.db, grant, reads);
434
+ const actor2 = resolved.actor;
435
+ const allowed = actor2.kind === "agent_attempt" ? authority.siteOwnerSubjectId !== null && authority.siteOwnerSubjectId === actor2.initiatingHumanSubjectId || actor2.callerRootSessionId === authority.rootSessionId && authority.siteAccessRootId === authority.rootSessionId : authority.siteOwnerSubjectId === actor2.subjectId || actor2.subjectId === `site:${authority.siteId}` && viewer?.siteId === authority.siteId && viewer.principal === authority.siteViewerPrincipal;
436
+ if (!allowed) throw new SessionAuthorizationDeniedError("not_found");
437
+ }
429
438
  const siteOrigin = currentSiteSessionOrigin();
430
439
  if (siteOrigin && authority && !input.operation.endsWith(".read")) {
431
440
  const session = await getSession2(deps.db, grant.workspaceId, input.sessionId);
@@ -4430,6 +4439,7 @@ import {
4430
4439
  listWorkspacesForSubject,
4431
4440
  findActiveApiKeyByHash,
4432
4441
  getWorkspaceGrant,
4442
+ subjectHasLiveWorkspaceAuthorityInScope,
4433
4443
  requireWorkspace,
4434
4444
  resolveNamedManagedPersonalWorkspaceGrant
4435
4445
  } from "@opengeni/db";
@@ -4552,6 +4562,7 @@ var delegatedHumanAuthorizationSchema = z2.object({
4552
4562
  });
4553
4563
  var delegatedHumanRequests = /* @__PURE__ */ new WeakMap();
4554
4564
  var delegatedHumanContexts = /* @__PURE__ */ new WeakMap();
4565
+ var verifiedSiteViewerProofs = /* @__PURE__ */ new WeakSet();
4555
4566
  var delegatedHumanProfileSchema = z2.object({
4556
4567
  id: z2.string(),
4557
4568
  name: z2.string().nullable(),
@@ -4581,6 +4592,27 @@ function stampDelegatedHumanAuthorization(request, input) {
4581
4592
  Object.freeze(proof.permissions);
4582
4593
  delegatedHumanRequests.set(request, Object.freeze(proof));
4583
4594
  }
4595
+ function stampSiteViewerSessionAuthorization(request, input) {
4596
+ if (input.subjectId.startsWith("user:")) {
4597
+ stampDelegatedHumanAuthorization(request, {
4598
+ ...input,
4599
+ workspaceScope: { kind: "selected", workspaceIds: [input.workspaceId] }
4600
+ });
4601
+ return;
4602
+ }
4603
+ if (delegatedHumanRequests.has(request) || accessResolvedRequests.has(request))
4604
+ throw new HTTPException8(403, { message: "request access authorization already resolved" });
4605
+ const parsed = delegatedHumanAuthorizationSchema.extend({
4606
+ subjectId: z2.string().max(1024).regex(/^external_user:[^\s\u0000-\u001f\u007f]+$/)
4607
+ }).parse({ ...input, workspaceScope: { kind: "selected", workspaceIds: [input.workspaceId] } });
4608
+ if (parsed.workspaceScope.kind !== "selected") throw new Error("Site workspace scope required");
4609
+ Object.freeze(parsed.permissions);
4610
+ Object.freeze(parsed.workspaceScope.workspaceIds);
4611
+ Object.freeze(parsed.workspaceScope);
4612
+ const proof = Object.freeze(parsed);
4613
+ verifiedSiteViewerProofs.add(proof);
4614
+ delegatedHumanRequests.set(request, proof);
4615
+ }
4584
4616
  var PERSONAL_API_KEY_TOKEN_PREFIX = "ogp_";
4585
4617
  async function stampPersonalApiKeyRequest(request, deps) {
4586
4618
  const authorization = request.headers.get("authorization");
@@ -5262,6 +5294,55 @@ async function resolveDelegatedHumanAccessContext(deps, proof) {
5262
5294
  message: "delegated humans require native organization access"
5263
5295
  });
5264
5296
  }
5297
+ if (verifiedSiteViewerProofs.has(proof)) {
5298
+ const workspaceId = proof.workspaceScope.kind === "selected" && proof.workspaceScope.workspaceIds.length === 1 ? proof.workspaceScope.workspaceIds[0] : null;
5299
+ if (!workspaceId)
5300
+ throw new HTTPException8(403, { message: "Site viewer workspace unavailable" });
5301
+ const grant = await withWorkspaceSubjectRls(
5302
+ deps.db,
5303
+ workspaceId,
5304
+ proof.subjectId,
5305
+ async (tx) => {
5306
+ if (!await subjectHasLiveWorkspaceAuthorityInScope(tx, {
5307
+ accountId: proof.organizationId,
5308
+ workspaceId,
5309
+ subjectId: proof.subjectId
5310
+ }))
5311
+ return null;
5312
+ return getWorkspaceGrant(tx, proof.subjectId, workspaceId, {
5313
+ principalKind: "human_session"
5314
+ });
5315
+ }
5316
+ );
5317
+ if (!grant || grant.accountId !== proof.organizationId)
5318
+ throw new HTTPException8(403, { message: "Site viewer membership unavailable" });
5319
+ const bounded = Object.freeze({
5320
+ ...grant,
5321
+ permissionMode: "explicit",
5322
+ permissions: Object.freeze(
5323
+ explicitPermissions(
5324
+ Permission.options.filter(
5325
+ (permission) => hasPermission(grant.permissions, permission) && hasPermission(proof.permissions, permission, "explicit")
5326
+ )
5327
+ )
5328
+ )
5329
+ });
5330
+ const accountGrant2 = Object.freeze({
5331
+ accountId: proof.organizationId,
5332
+ subjectId: proof.subjectId,
5333
+ permissions: Object.freeze([])
5334
+ });
5335
+ const context2 = Object.freeze({
5336
+ mode: "managed",
5337
+ subjectId: proof.subjectId,
5338
+ accountGrants: Object.freeze([accountGrant2]),
5339
+ workspaceGrants: Object.freeze([bounded]),
5340
+ defaultAccountId: proof.organizationId,
5341
+ defaultWorkspaceId: workspaceId
5342
+ });
5343
+ delegatedHumanContexts.set(context2, proof);
5344
+ return context2;
5345
+ }
5265
5346
  const userId = proof.subjectId.slice("user:".length);
5266
5347
  const profiles = await getManagedUserProfilesByIds(deps.db, [userId]);
5267
5348
  const parsedProfile = delegatedHumanProfileSchema.safeParse(
@@ -5796,6 +5877,7 @@ async function fileOwnerContextForAgent(deps, grant, permission) {
5796
5877
  },
5797
5878
  subjectId: grant.subjectId,
5798
5879
  initiatingHumanSubjectId: attempt.initiatingHumanSubjectId,
5880
+ agentRootSessionId: attempt.callerRootSessionId,
5799
5881
  privateFileOwnerSubjectId: snapshot.defaultScope === "personal" ? snapshot.subjectId : null
5800
5882
  };
5801
5883
  }
@@ -8989,7 +9071,8 @@ function runOnOperationAdmission(services, enrollment) {
8989
9071
  operationResourcePolicy: enrollment.operationPolicy,
8990
9072
  operationResourcePolicySupported: enrollment.agentCapabilities.operationResourcePolicy === true,
8991
9073
  operationCpuQuotaSupported: enrollment.agentCapabilities.operationCpuQuota === true,
8992
- transactionalFsWriteSupported: enrollment.agentCapabilities.transactionalFsWrite === true
9074
+ transactionalFsWriteSupported: enrollment.agentCapabilities.transactionalFsWrite === true,
9075
+ browserControlHttpSupported: enrollment.agentCapabilities.browserControlHttp === true
8993
9076
  };
8994
9077
  }
8995
9078
  function hasSecretFileReference(carry) {
@@ -17588,7 +17671,7 @@ import { RotateSessionMcpCredentialsRequest } from "@opengeni/contracts";
17588
17671
  import {
17589
17672
  encryptVariableSetValue as encryptVariableSetValue3,
17590
17673
  getWorkspaceGrant as getWorkspaceGrant3,
17591
- subjectHasLiveWorkspaceAuthorityInScope
17674
+ subjectHasLiveWorkspaceAuthorityInScope as subjectHasLiveWorkspaceAuthorityInScope2
17592
17675
  } from "@opengeni/db";
17593
17676
  import {
17594
17677
  rotateSessionMcpCredentialsAtomically,
@@ -17733,7 +17816,7 @@ async function rotateSessionMcpCredentialsForRequest(deps, authorization, sessio
17733
17816
  } else if (!hasVerifiedOwningUserAuthorization(authorization)) {
17734
17817
  throw new HTTPException24(403, { message: "credential rotation authority changed" });
17735
17818
  }
17736
- if (!await subjectHasLiveWorkspaceAuthorityInScope(tx, scope)) {
17819
+ if (!await subjectHasLiveWorkspaceAuthorityInScope2(tx, scope)) {
17737
17820
  throw new HTTPException24(403, { message: "credential rotation authority changed" });
17738
17821
  }
17739
17822
  }
@@ -25359,7 +25442,14 @@ async function getWorkspaceInsights(db, settings, input, observePhase) {
25359
25442
  }
25360
25443
 
25361
25444
  // src/domain/insights-usage.ts
25362
- import { readInsightsCalls, readInsightsUsage } from "@opengeni/db";
25445
+ import {
25446
+ readInsightsCalls,
25447
+ readInsightsUsage,
25448
+ readInsightsSiteUsage
25449
+ } from "@opengeni/db";
25450
+ async function getInsightsSiteUsage(db, input) {
25451
+ return readInsightsSiteUsage(db, { ...input, now: input.now ?? /* @__PURE__ */ new Date() });
25452
+ }
25363
25453
  async function getInsightsUsage(db, input) {
25364
25454
  return await readInsightsUsage(db, { ...input, now: input.now ?? /* @__PURE__ */ new Date() });
25365
25455
  }
@@ -28282,6 +28372,40 @@ async function controlHumanWorkspaceTimer(deps, context, input) {
28282
28372
  ]);
28283
28373
  }
28284
28374
 
28375
+ // src/application/site-session-cancellation.ts
28376
+ import { cancelSiteRuntimeConversation } from "@opengeni/db";
28377
+ import {
28378
+ publishDurableSessionEvents as publishDurableSessionEvents4,
28379
+ publishDurableWorkspaceControlEvent as publishDurableWorkspaceControlEvent3
28380
+ } from "@opengeni/events";
28381
+ async function cancelPublishedSiteSession(deps, input) {
28382
+ const result = await cancelSiteRuntimeConversation(deps.db, input);
28383
+ if (!result) return null;
28384
+ const bySession = /* @__PURE__ */ new Map();
28385
+ for (const event of result.events) {
28386
+ const group = bySession.get(event.sessionId) ?? [];
28387
+ group.push(event);
28388
+ bySession.set(event.sessionId, group);
28389
+ }
28390
+ const effects = await Promise.allSettled([
28391
+ ...[...bySession].map(
28392
+ ([sessionId, events]) => publishDurableSessionEvents4(deps.bus, input.workspaceId, sessionId, events)
28393
+ ),
28394
+ ...result.workspaceEvent ? [publishDurableWorkspaceControlEvent3(deps.bus, input.workspaceId, result.workspaceEvent)] : [],
28395
+ ...result.wakeCount > 0 ? [deps.workflowClient.requestSessionWorkflowWakeDispatch()] : []
28396
+ ]);
28397
+ if (effects.some((effect) => effect.status === "rejected"))
28398
+ console.warn(
28399
+ "[site-lifecycle] post-commit fanout failed; durable events and wake outbox retained"
28400
+ );
28401
+ return {
28402
+ receiptId: result.receiptId,
28403
+ replay: result.replay,
28404
+ cancelledSessionCount: result.cancelledSessionCount,
28405
+ cancelledTurnCount: result.cancelledTurnCount
28406
+ };
28407
+ }
28408
+
28285
28409
  // src/application/archived-session-imports.ts
28286
28410
  import {
28287
28411
  AppendArchivedSessionEventsRequest,
@@ -28296,7 +28420,7 @@ import {
28296
28420
  importArchivedSession,
28297
28421
  withSessionRlsActorContext as withSessionRlsActorContext7
28298
28422
  } from "@opengeni/db";
28299
- import { publishDurableSessionEvents as publishDurableSessionEvents4 } from "@opengeni/events";
28423
+ import { publishDurableSessionEvents as publishDurableSessionEvents5 } from "@opengeni/events";
28300
28424
  import { HTTPException as HTTPException38 } from "hono/http-exception";
28301
28425
  import { ArchivedSessionImportError as ArchivedSessionImportError2 } from "@opengeni/db";
28302
28426
  function requireImportAuthorization(authorization, workspaceId, permission) {
@@ -28327,7 +28451,7 @@ function importerScope(authorization, workspaceId, permission, fileOwnerSubjectI
28327
28451
  async function publishImportedEvents(deps, workspaceId, sessionId, events) {
28328
28452
  if (!events.length) return;
28329
28453
  try {
28330
- await publishDurableSessionEvents4(deps.bus, workspaceId, sessionId, events);
28454
+ await publishDurableSessionEvents5(deps.bus, workspaceId, sessionId, events);
28331
28455
  } catch {
28332
28456
  console.warn("[session-import] committed timeline fanout deferred to replay", { sessionId });
28333
28457
  }
@@ -29502,6 +29626,7 @@ export {
29502
29626
  buildFleetContextForSession,
29503
29627
  buildMissingSecretAskCard2 as buildMissingSecretAskCard,
29504
29628
  cancelExternalWorkspaceMemberGrantForRequest,
29629
+ cancelPublishedSiteSession,
29505
29630
  canonicalConfiguredModel,
29506
29631
  canonicalConversationDeliveryCommandJson,
29507
29632
  canonicalConversationDeliveryOutcomeJson,
@@ -29644,6 +29769,7 @@ export {
29644
29769
  getAutomationAdapter,
29645
29770
  getConnectorToolPermissions,
29646
29771
  getHumanComposerDraft,
29772
+ getInsightsSiteUsage,
29647
29773
  getInsightsUsage,
29648
29774
  getManagedAuthRequestActorAbortSignal,
29649
29775
  getManagedAuthRequestActorAdmissionStamp,
@@ -30024,6 +30150,7 @@ export {
30024
30150
  stableJson,
30025
30151
  stampDelegatedHumanAuthorization,
30026
30152
  stampPersonalApiKeyRequest,
30153
+ stampSiteViewerSessionAuthorization,
30027
30154
  statusForVoiceInputError,
30028
30155
  steerAgentSession,
30029
30156
  steerHumanQueuePrompt,