@opengeni/api-router 0.30.1 → 0.30.3

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.
@@ -22,7 +22,7 @@ import {
22
22
  } from "@opengeni/contracts";
23
23
  import {
24
24
  createDocumentServices,
25
- getDocument as getDocument2,
25
+ getDocumentForIndexing,
26
26
  indexDocumentNow
27
27
  } from "@opengeni/documents";
28
28
  import {
@@ -66,10 +66,10 @@ import {
66
66
  CodexCompactionV2ProviderLockedError,
67
67
  hasPermission as hasPermission21,
68
68
  requireAccessGrant as requireAccessGrant40,
69
- requireLiveAgentAttemptAuthorization as requireLiveAgentAttemptAuthorization2,
69
+ requireLiveAgentAttemptAuthorization as requireLiveAgentAttemptAuthorization3,
70
70
  requirePermission as requirePermission3,
71
71
  requireSessionAuthorization as requireSessionAuthorization8,
72
- SessionAuthorizationDeniedError as SessionAuthorizationDeniedError7,
72
+ SessionAuthorizationDeniedError as SessionAuthorizationDeniedError8,
73
73
  SessionAuthorizationUnavailableError as SessionAuthorizationUnavailableError7
74
74
  } from "@opengeni/core";
75
75
 
@@ -3301,6 +3301,10 @@ import {
3301
3301
  import {
3302
3302
  GOOGLE_DRIVE_CREDENTIAL_LABEL,
3303
3303
  GOOGLE_DRIVE_CREDENTIAL_ROLE,
3304
+ GOOGLE_DRIVE_FILE_SCOPE,
3305
+ GOOGLE_DRIVE_PUBLICATION_CREATE_ACTION,
3306
+ GOOGLE_DRIVE_PUBLICATION_SERVER_ID,
3307
+ GOOGLE_DRIVE_PUBLICATION_TOOL_NAME,
3304
3308
  GOOGLE_DRIVE_PROVIDER_DOMAIN,
3305
3309
  GOOGLE_DRIVE_READONLY_SCOPE,
3306
3310
  GoogleDriveKnowledgeSourceConfig,
@@ -3309,6 +3313,7 @@ import {
3309
3313
  GoogleDriveConnectionLifecycle,
3310
3314
  GoogleDriveConnectionMetadata,
3311
3315
  GoogleDriveOAuthStartResponse,
3316
+ GoogleDriveOutputDestination,
3312
3317
  SaveGoogleDriveIntegrationSourceRequest,
3313
3318
  SaveGoogleDriveSourceRequest,
3314
3319
  googleDriveOAuthScopeDecision,
@@ -3344,6 +3349,7 @@ import {
3344
3349
  decryptEnvironmentValue as decryptEnvironmentValue3,
3345
3350
  disconnectConnectionIdempotently,
3346
3351
  encryptEnvironmentValue as encryptEnvironmentValue3,
3352
+ ensureConnectorActionPolicyDefault,
3347
3353
  getConnectionMetadata as getConnectionMetadata2,
3348
3354
  getKnowledgeSourceByExternalIdentityForSyncAuthority,
3349
3355
  getKnowledgeSourceForSyncAuthority,
@@ -3387,6 +3393,11 @@ async function startGoogleDriveOAuth(deps, input) {
3387
3393
  if (input.payload.connectionId && !existing) {
3388
3394
  throw new HTTPException5(404, { message: "Google Drive connection not found" });
3389
3395
  }
3396
+ if (input.payload.capability === "publish" && !existing) {
3397
+ throw new HTTPException5(409, {
3398
+ message: "Connect Google Drive for source access before enabling publishing"
3399
+ });
3400
+ }
3390
3401
  if (existing) {
3391
3402
  requireGoogleDriveConnection(existing, input.subjectId);
3392
3403
  }
@@ -3400,16 +3411,29 @@ async function startGoogleDriveOAuth(deps, input) {
3400
3411
  subjectId: input.subjectId,
3401
3412
  returnPath: GOOGLE_DRIVE_RETURN_PATH(input.workspaceId),
3402
3413
  encryptedPkceVerifier: encryptEnvironmentValue3(key, verifier),
3414
+ capability: input.payload.capability,
3403
3415
  ...existing ? { connectionId: existing.id, connectionVersion: existing.version } : {}
3404
3416
  });
3405
3417
  const authorizationUrl = new URL(GOOGLE_AUTHORIZATION_URL);
3406
3418
  authorizationUrl.searchParams.set("client_id", google.clientId);
3407
3419
  authorizationUrl.searchParams.set("redirect_uri", redirectUri);
3408
3420
  authorizationUrl.searchParams.set("response_type", "code");
3409
- authorizationUrl.searchParams.set("scope", GOOGLE_DRIVE_READONLY_SCOPE);
3421
+ authorizationUrl.searchParams.set(
3422
+ "scope",
3423
+ input.payload.capability === "publish" ? GOOGLE_DRIVE_FILE_SCOPE : GOOGLE_DRIVE_READONLY_SCOPE
3424
+ );
3410
3425
  authorizationUrl.searchParams.set("access_type", "offline");
3411
3426
  authorizationUrl.searchParams.set("include_granted_scopes", "true");
3412
- authorizationUrl.searchParams.set("prompt", "consent select_account");
3427
+ authorizationUrl.searchParams.set(
3428
+ "prompt",
3429
+ input.payload.capability === "publish" ? "consent" : "consent select_account"
3430
+ );
3431
+ if (input.payload.capability === "publish") {
3432
+ authorizationUrl.searchParams.set("trigger_onepick", "true");
3433
+ authorizationUrl.searchParams.set("allow_folder_selection", "true");
3434
+ authorizationUrl.searchParams.set("allow_multiple", "false");
3435
+ authorizationUrl.searchParams.set("mimetypes", GOOGLE_DRIVE_FOLDER_MIME_TYPE);
3436
+ }
3413
3437
  authorizationUrl.searchParams.set("state", state);
3414
3438
  authorizationUrl.searchParams.set("code_challenge_method", "S256");
3415
3439
  authorizationUrl.searchParams.set(
@@ -3460,8 +3484,10 @@ async function completeGoogleDriveOAuthCallback(deps, input) {
3460
3484
  },
3461
3485
  fetchImpl
3462
3486
  );
3463
- const scopeDecision = googleDriveOAuthScopeDecision(token.scopes);
3464
- if (scopeDecision.accessMode !== "readonly" || !scopeDecision.capabilities.includes("recursive_source_sync")) {
3487
+ const grantedScopes2 = [...new Set(token.scopes)].sort();
3488
+ const scopeDecision = googleDriveOAuthScopeDecision(grantedScopes2);
3489
+ const requiredCapability = state.capability === "publish" ? "publish_file" : "recursive_source_sync";
3490
+ if (!scopeDecision.accessMode || !scopeDecision.capabilities.includes(requiredCapability) || state.capability === "publish" && !scopeDecision.capabilities.includes("recursive_source_sync")) {
3465
3491
  throw new GoogleDriveCallbackError("scope_not_granted");
3466
3492
  }
3467
3493
  const identity = await verifyGoogleDriveIdentity(token.accessToken, fetchImpl);
@@ -3481,6 +3507,24 @@ async function completeGoogleDriveOAuthCallback(deps, input) {
3481
3507
  }
3482
3508
  }
3483
3509
  const previousMetadata = existing ? GoogleDriveConnectionMetadata.parse(existing.metadata) : null;
3510
+ const outputDestination = state.capability === "publish" ? await verifyPickedGoogleDriveOutputDestination(
3511
+ token.accessToken,
3512
+ input.pickedFileIds,
3513
+ fetchImpl
3514
+ ) : previousMetadata?.outputDestination;
3515
+ if (state.capability === "publish") {
3516
+ if (!existing) throw new GoogleDriveCallbackError("connection_conflict");
3517
+ await ensureConnectorActionPolicyDefault(deps.db, {
3518
+ accountId: state.accountId,
3519
+ workspaceId: state.workspaceId,
3520
+ subjectId: state.subjectId,
3521
+ connectionId: existing.id,
3522
+ serverId: GOOGLE_DRIVE_PUBLICATION_SERVER_ID,
3523
+ toolName: GOOGLE_DRIVE_PUBLICATION_TOOL_NAME,
3524
+ actionName: GOOGLE_DRIVE_PUBLICATION_CREATE_ACTION,
3525
+ policy: "ask"
3526
+ });
3527
+ }
3484
3528
  let refreshToken = token.refreshToken;
3485
3529
  if (!refreshToken && existing) {
3486
3530
  const previousCredential = await loadConnectionCredentialForBroker(deps.db, deps.settings, {
@@ -3503,7 +3547,7 @@ async function completeGoogleDriveOAuthCallback(deps, input) {
3503
3547
  refresh_token: refreshToken,
3504
3548
  token_type: token.tokenType,
3505
3549
  ...token.expiresAt ? { expires_at: token.expiresAt.toISOString() } : {},
3506
- scope: token.scopes.join(" "),
3550
+ scope: grantedScopes2.join(" "),
3507
3551
  token_endpoint: GOOGLE_TOKEN_URL,
3508
3552
  client_id: google.clientId,
3509
3553
  client_secret: google.clientSecret,
@@ -3519,6 +3563,7 @@ async function completeGoogleDriveOAuthCallback(deps, input) {
3519
3563
  verifiedAt: (/* @__PURE__ */ new Date()).toISOString(),
3520
3564
  accessMode: scopeDecision.accessMode,
3521
3565
  lifecycle: googleDriveLifecycle("active"),
3566
+ ...outputDestination ? { outputDestination } : {},
3522
3567
  ...previousMetadata?.documentDestination ? { documentDestination: previousMetadata.documentDestination } : {},
3523
3568
  ...previousMetadata?.selectedSources ? { selectedSources: previousMetadata.selectedSources } : previousMetadata?.selectedSource ? { selectedSources: [previousMetadata.selectedSource] } : {}
3524
3569
  });
@@ -3532,7 +3577,7 @@ async function completeGoogleDriveOAuthCallback(deps, input) {
3532
3577
  kind: "oauth2",
3533
3578
  status: "active",
3534
3579
  credentialEncrypted,
3535
- grantedScopes: token.scopes,
3580
+ grantedScopes: grantedScopes2,
3536
3581
  expiresAt: token.expiresAt,
3537
3582
  metadata,
3538
3583
  updatedBySubjectId: state.subjectId
@@ -3543,7 +3588,7 @@ async function completeGoogleDriveOAuthCallback(deps, input) {
3543
3588
  providerDomain: GOOGLE_DRIVE_PROVIDER_DOMAIN,
3544
3589
  kind: "oauth2",
3545
3590
  credentialEncrypted,
3546
- grantedScopes: token.scopes,
3591
+ grantedScopes: grantedScopes2,
3547
3592
  expiresAt: token.expiresAt,
3548
3593
  metadata,
3549
3594
  createdBySubjectId: state.subjectId
@@ -3953,6 +3998,42 @@ async function saveGoogleDriveSource(deps, input) {
3953
3998
  });
3954
3999
  return updated;
3955
4000
  }
4001
+ async function verifyPickedGoogleDriveOutputDestination(accessToken, rawPickedFileIds, fetchImpl) {
4002
+ const pickedFileIds = uniqueStrings2((rawPickedFileIds ?? "").split(","));
4003
+ if (pickedFileIds.length !== 1) {
4004
+ throw new GoogleDriveCallbackError("output_folder_required");
4005
+ }
4006
+ const folderId = validDriveId(pickedFileIds[0], "picked_file_ids");
4007
+ const url = new URL(`${GOOGLE_DRIVE_API_BASE}/files/${encodeURIComponent(folderId)}`);
4008
+ url.searchParams.set("supportsAllDrives", "true");
4009
+ url.searchParams.set("fields", "id,name,mimeType,driveId,trashed,capabilities(canAddChildren)");
4010
+ const response = await providerFetch(fetchImpl, url, {
4011
+ headers: { authorization: `Bearer ${accessToken}`, accept: "application/json" }
4012
+ });
4013
+ if (!response.ok) {
4014
+ await response.body?.cancel().catch(() => void 0);
4015
+ throw new GoogleDriveCallbackError("output_folder_unavailable");
4016
+ }
4017
+ const record5 = objectRecord(
4018
+ await readResponseJsonBounded4(
4019
+ response,
4020
+ GOOGLE_RESPONSE_MAX_BYTES,
4021
+ "Google Drive picked output folder"
4022
+ )
4023
+ );
4024
+ const driveId = optionalString(record5.driveId);
4025
+ const capabilities = objectRecord(record5.capabilities);
4026
+ if (requiredString2(record5.id, "Google Drive folder id") !== folderId || requiredString2(record5.mimeType, "Google Drive folder MIME type") !== GOOGLE_DRIVE_FOLDER_MIME_TYPE || record5.trashed === true || capabilities.canAddChildren !== true) {
4027
+ throw new GoogleDriveCallbackError("output_folder_unavailable");
4028
+ }
4029
+ return GoogleDriveOutputDestination.parse({
4030
+ folderId,
4031
+ folderName: requiredString2(record5.name, "Google Drive folder name"),
4032
+ driveId: driveId ?? null,
4033
+ location: driveId ? "shared_drive" : "my_drive",
4034
+ selectedAt: (/* @__PURE__ */ new Date()).toISOString()
4035
+ });
4036
+ }
3956
4037
  async function requireGoogleDriveIntegrationFacet(deps, input) {
3957
4038
  const instance = await listIntegrationInstanceFacets(
3958
4039
  deps.db,
@@ -4571,6 +4652,11 @@ function readGoogleDriveOAuthState(raw, settings) {
4571
4652
  }
4572
4653
  const connectionId = optionalString(payload.connectionId) ?? void 0;
4573
4654
  const connectionVersion = numberValue2(payload.connectionVersion);
4655
+ const capabilityValue = optionalString(payload.capability);
4656
+ const capability = capabilityValue ?? "source_read";
4657
+ if (capability !== "source_read" && capability !== "publish") {
4658
+ throw new HTTPException5(400, { message: "invalid Google Drive OAuth capability" });
4659
+ }
4574
4660
  if (connectionVersion !== void 0 && !Number.isInteger(connectionVersion) || Boolean(connectionId) !== Boolean(connectionVersion)) {
4575
4661
  throw new HTTPException5(400, { message: "invalid Google Drive reconnect state" });
4576
4662
  }
@@ -4583,6 +4669,7 @@ function readGoogleDriveOAuthState(raw, settings) {
4583
4669
  payload.encryptedPkceVerifier,
4584
4670
  "state.encryptedPkceVerifier"
4585
4671
  ),
4672
+ capability,
4586
4673
  ...connectionId ? { connectionId, connectionVersion } : {},
4587
4674
  nonce: requiredString2(payload.nonce, "state.nonce"),
4588
4675
  iat
@@ -4779,7 +4866,7 @@ async function googleDriveApiRequest(deps, input) {
4779
4866
  if (!resolved || resolved.version !== connectionVersion) {
4780
4867
  throw new HTTPException5(409, { message: "Google Drive connection changed; try again" });
4781
4868
  }
4782
- await requireGoogleDriveSourceConnection(deps, resolved, input.subjectId);
4869
+ await requireGoogleDriveApiConnection(deps, resolved, input.subjectId);
4783
4870
  };
4784
4871
  let credential = await resolve(false);
4785
4872
  if (credential.status !== "ok") {
@@ -21594,6 +21681,7 @@ function registerConnectionRoutes(app, deps) {
21594
21681
  ...c.req.query("code") ? { code: c.req.query("code") } : {},
21595
21682
  ...c.req.query("state") ? { state: c.req.query("state") } : {},
21596
21683
  ...c.req.query("error") ? { error: c.req.query("error") } : {},
21684
+ ...c.req.query("picked_file_ids") ? { pickedFileIds: c.req.query("picked_file_ids") } : {},
21597
21685
  requestUrl: c.req.url
21598
21686
  };
21599
21687
  const result = await completeGoogleDriveOAuthCallback(deps, input);
@@ -22621,17 +22709,22 @@ import {
22621
22709
  getFileUpload,
22622
22710
  getGeneratedImageArtifact,
22623
22711
  getGeneratedVideoArtifact,
22712
+ getFilesForSubject,
22624
22713
  getRetainedFileArtifact,
22625
22714
  getRetainedScreenshotArtifact,
22626
- requireFile as requireFile2
22715
+ requireFileForSubject as requireFileForSubject2
22627
22716
  } from "@opengeni/db";
22628
22717
  import { HTTPException as HTTPException19 } from "hono/http-exception";
22629
- import { requireAccessGrant as requireAccessGrant6 } from "@opengeni/core";
22718
+ import {
22719
+ requireAccessGrant as requireAccessGrant6,
22720
+ requireLiveAgentAttemptAuthorization as requireLiveAgentAttemptAuthorization2,
22721
+ SessionAuthorizationDeniedError as SessionAuthorizationDeniedError3
22722
+ } from "@opengeni/core";
22630
22723
  import { recordWorkspaceUsage as recordWorkspaceUsage2, requireLimit as requireLimit2 } from "@opengeni/core";
22631
22724
  import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
22632
22725
 
22633
22726
  // src/mcp/files.ts
22634
- import { requireFile } from "@opengeni/db";
22727
+ import { requireFileForSubject } from "@opengeni/db";
22635
22728
  import { hasPermission as hasPermission13 } from "@opengeni/core";
22636
22729
  import { McpServer as McpServer3 } from "@modelcontextprotocol/sdk/server/mcp.js";
22637
22730
  import * as z8 from "zod/v4";
@@ -22663,7 +22756,12 @@ function buildFilesMcpServer(deps, grant) {
22663
22756
  if (!deps.objectStorage) {
22664
22757
  throw new Error("object storage is not configured");
22665
22758
  }
22666
- const file = await requireFile(deps.db, grant.workspaceId, fileId);
22759
+ const file = await requireFileForSubject(deps.db, {
22760
+ accountId: grant.accountId,
22761
+ workspaceId: grant.workspaceId,
22762
+ subjectId: grant.subjectId,
22763
+ fileId
22764
+ });
22667
22765
  if (file.status !== "ready") {
22668
22766
  throw new Error(`file is ${file.status}`);
22669
22767
  }
@@ -22901,8 +22999,13 @@ function registerFileRoutes(app, deps) {
22901
22999
  });
22902
23000
  app.get("/v1/workspaces/:workspaceId/files/:fileId", async (c) => {
22903
23001
  const workspaceId = c.req.param("workspaceId");
22904
- await requireAccessGrant6(c, deps, workspaceId, "files:read");
22905
- const file = await requireFile2(db, workspaceId, c.req.param("fileId")).catch(() => null);
23002
+ const grant = await requireAccessGrant6(c, deps, workspaceId, "files:read");
23003
+ const file = await requireFileForSubject2(db, {
23004
+ accountId: grant.accountId,
23005
+ workspaceId,
23006
+ subjectId: grant.subjectId,
23007
+ fileId: c.req.param("fileId")
23008
+ }).catch(() => null);
22906
23009
  if (!file) {
22907
23010
  throw new HTTPException19(404, { message: "file not found" });
22908
23011
  }
@@ -22910,9 +23013,14 @@ function registerFileRoutes(app, deps) {
22910
23013
  });
22911
23014
  app.get("/v1/workspaces/:workspaceId/artifacts/:artifactId", async (c) => {
22912
23015
  const workspaceId = c.req.param("workspaceId");
22913
- await requireAccessGrant6(c, deps, workspaceId, "files:read");
23016
+ const grant = await requireAccessGrant6(c, deps, workspaceId, "files:read");
22914
23017
  const artifactId2 = retainedArtifactId(c.req.param("artifactId"));
22915
- const artifact = await getWorkspaceArtifact2(db, workspaceId, artifactId2);
23018
+ const artifact = await getWorkspaceArtifact2(db, {
23019
+ accountId: grant.accountId,
23020
+ workspaceId,
23021
+ subjectId: await fileAuthoritySubjectIdForGrant(deps, grant),
23022
+ artifactId: artifactId2
23023
+ });
22916
23024
  if (!artifact) {
22917
23025
  return c.json(retainedArtifactUnavailable(artifactId2, "deleted"), 404);
22918
23026
  }
@@ -22920,9 +23028,14 @@ function registerFileRoutes(app, deps) {
22920
23028
  });
22921
23029
  app.get("/v1/workspaces/:workspaceId/artifacts/:artifactId/content", async (c) => {
22922
23030
  const workspaceId = c.req.param("workspaceId");
22923
- await requireAccessGrant6(c, deps, workspaceId, "files:read");
23031
+ const grant = await requireAccessGrant6(c, deps, workspaceId, "files:read");
22924
23032
  const artifactId2 = retainedArtifactId(c.req.param("artifactId"));
22925
- const artifact = await getWorkspaceArtifact2(db, workspaceId, artifactId2);
23033
+ const artifact = await getWorkspaceArtifact2(db, {
23034
+ accountId: grant.accountId,
23035
+ workspaceId,
23036
+ subjectId: await fileAuthoritySubjectIdForGrant(deps, grant),
23037
+ artifactId: artifactId2
23038
+ });
22926
23039
  if (!artifact) {
22927
23040
  return c.json(retainedArtifactUnavailable(artifactId2, "deleted"), 404);
22928
23041
  }
@@ -23007,13 +23120,18 @@ function registerFileRoutes(app, deps) {
23007
23120
  );
23008
23121
  app.post("/v1/workspaces/:workspaceId/files/:fileId/download-url", async (c) => {
23009
23122
  const workspaceId = c.req.param("workspaceId");
23010
- await requireAccessGrant6(c, deps, workspaceId, "files:read");
23123
+ const grant = await requireAccessGrant6(c, deps, workspaceId, "files:read");
23011
23124
  if (!objectStorage) {
23012
23125
  throw new HTTPException19(503, {
23013
23126
  message: "object storage is not configured"
23014
23127
  });
23015
23128
  }
23016
- const file = await requireFile2(db, workspaceId, c.req.param("fileId")).catch(() => null);
23129
+ const file = await requireFileForSubject2(db, {
23130
+ accountId: grant.accountId,
23131
+ workspaceId,
23132
+ subjectId: grant.subjectId,
23133
+ fileId: c.req.param("fileId")
23134
+ }).catch(() => null);
23017
23135
  if (!file) {
23018
23136
  throw new HTTPException19(404, { message: "file not found" });
23019
23137
  }
@@ -23134,12 +23252,12 @@ function retainedArtifactMetadata(artifact) {
23134
23252
  }
23135
23253
  return retainedArtifactUnavailable(file.id, "unsupported");
23136
23254
  }
23137
- async function getWorkspaceArtifact2(db, workspaceId, artifactId2) {
23138
- const generated = await getGeneratedImageArtifact(db, workspaceId, artifactId2);
23255
+ async function getWorkspaceArtifact2(db, input) {
23256
+ const generated = await getGeneratedImageArtifact(db, input.workspaceId, input.artifactId);
23139
23257
  if (generated) {
23140
23258
  return { file: generated.file, metadata: retainedGeneratedImageMetadata(generated) };
23141
23259
  }
23142
- const generatedVideo = await getGeneratedVideoArtifact(db, workspaceId, artifactId2);
23260
+ const generatedVideo = await getGeneratedVideoArtifact(db, input.workspaceId, input.artifactId);
23143
23261
  if (generatedVideo && !generatedVideo.artifact.deletedAt) {
23144
23262
  const file = generatedVideoFileAsset(generatedVideo.file);
23145
23263
  return {
@@ -23150,8 +23268,36 @@ async function getWorkspaceArtifact2(db, workspaceId, artifactId2) {
23150
23268
  })
23151
23269
  };
23152
23270
  }
23153
- const artifact = await getRetainedFileArtifact(db, workspaceId, artifactId2);
23154
- return artifact ? { file: artifact.file, metadata: retainedArtifactMetadata(artifact) } : null;
23271
+ const artifact = await getRetainedFileArtifact(db, input.workspaceId, input.artifactId);
23272
+ if (!artifact) return null;
23273
+ const [authorizedFile] = await getFilesForSubject(db, {
23274
+ accountId: input.accountId,
23275
+ workspaceId: input.workspaceId,
23276
+ subjectId: input.subjectId,
23277
+ fileIds: [artifact.file.id]
23278
+ });
23279
+ if (!authorizedFile) return null;
23280
+ const authorizedArtifact = { ...artifact, file: authorizedFile };
23281
+ return {
23282
+ file: authorizedFile,
23283
+ metadata: retainedArtifactMetadata(authorizedArtifact)
23284
+ };
23285
+ }
23286
+ async function fileAuthoritySubjectIdForGrant(deps, grant) {
23287
+ if (grant.principalKind !== "agent_attempt") return grant.subjectId;
23288
+ const sessionId = grant.metadata?.["sessionId"];
23289
+ if (typeof sessionId !== "string") {
23290
+ throw new HTTPException19(404, { message: "file not found" });
23291
+ }
23292
+ try {
23293
+ const actor = await requireLiveAgentAttemptAuthorization2(deps.db, grant, sessionId);
23294
+ return actor.initiatingHumanSubjectId;
23295
+ } catch (error) {
23296
+ if (error instanceof SessionAuthorizationDeniedError3) {
23297
+ throw new HTTPException19(404, { message: "file not found", cause: error });
23298
+ }
23299
+ throw error;
23300
+ }
23155
23301
  }
23156
23302
  function generatedVideoFileAsset(file) {
23157
23303
  return FileAsset.parse({
@@ -25985,7 +26131,7 @@ import {
25985
26131
  getBrowserRevisionArtifactAuthority,
25986
26132
  getEnrollment as getEnrollment4,
25987
26133
  getExternalAuthInteractiveContext,
25988
- getFiles,
26134
+ getFilesForSubject as getFilesForSubject2,
25989
26135
  getFileUpload as getFileUpload2,
25990
26136
  getAuthRun,
25991
26137
  getExternalAuthPreparation,
@@ -26023,7 +26169,7 @@ import {
26023
26169
  recordWorkspaceUsage as recordWorkspaceUsage4,
26024
26170
  requireLimit as requireLimit5,
26025
26171
  relayConfigFromSettings as relayConfigFromSettings4,
26026
- SessionAuthorizationDeniedError as SessionAuthorizationDeniedError3,
26172
+ SessionAuthorizationDeniedError as SessionAuthorizationDeniedError4,
26027
26173
  SessionAuthorizationUnavailableError as SessionAuthorizationUnavailableError3
26028
26174
  } from "@opengeni/core";
26029
26175
  import {
@@ -27316,7 +27462,7 @@ function registerBrowserSessionRoutes(app, deps) {
27316
27462
  browserSessionId,
27317
27463
  "session.control",
27318
27464
  "browser.action",
27319
- async ({ sessionClient, binding, record: record5 }) => {
27465
+ async ({ sessionClient, binding, record: record5, sourceAuthorization }) => {
27320
27466
  const command = BrowserActionCommand.parse({
27321
27467
  protocolVersion: BROWSER_CONTROL_PROTOCOL_VERSION,
27322
27468
  operationId: request.operationId,
@@ -27346,15 +27492,13 @@ function registerBrowserSessionRoutes(app, deps) {
27346
27492
  message: "object storage is not configured"
27347
27493
  });
27348
27494
  }
27349
- const files = await getFiles(deps.db, workspaceId, workspaceFileIds);
27350
- const byId = new Map(files.map((file) => [file.id, file]));
27351
- const ordered = workspaceFileIds.map((fileId) => byId.get(fileId));
27352
- if (ordered.some((file) => !file)) {
27353
- throw new HTTPException28(404, { message: "workspace file not found" });
27354
- }
27355
- if (ordered.some((file) => file.status !== "ready")) {
27356
- throw new HTTPException28(409, { message: "workspace file is not ready" });
27357
- }
27495
+ const files = await getFilesForSubject2(deps.db, {
27496
+ accountId: grant.accountId,
27497
+ workspaceId,
27498
+ subjectId: browserFileAuthoritySubjectId(grant, sourceAuthorization),
27499
+ fileIds: workspaceFileIds
27500
+ });
27501
+ const ordered = requireAuthorizedBrowserUploadFiles(workspaceFileIds, files);
27358
27502
  const authorities = await Promise.all(
27359
27503
  ordered.map(async (file) => {
27360
27504
  const signed = await deps.objectStorage.createGetUrl({
@@ -29113,7 +29257,12 @@ function registerBrowserSessionRoutes(app, deps) {
29113
29257
  workspaceId,
29114
29258
  browserSessionId
29115
29259
  });
29116
- await authorizeSourceSession2(deps, grant, record5.sourceSessionId, authorizationOperation);
29260
+ const sourceAuthorization = await authorizeSourceSession2(
29261
+ deps,
29262
+ grant,
29263
+ record5.sourceSessionId,
29264
+ authorizationOperation
29265
+ );
29117
29266
  if (record5.session.lifecycle !== "active" || !record5.session.controller) {
29118
29267
  throw new BrowserSessionStateError2("BrowserSession is not active");
29119
29268
  }
@@ -29150,7 +29299,8 @@ function registerBrowserSessionRoutes(app, deps) {
29150
29299
  }),
29151
29300
  record: record5,
29152
29301
  binding,
29153
- placement
29302
+ placement,
29303
+ sourceAuthorization
29154
29304
  };
29155
29305
  let result;
29156
29306
  try {
@@ -29776,13 +29926,13 @@ async function requireSourceSession(deps, workspaceId, sessionId) {
29776
29926
  }
29777
29927
  async function authorizeSourceSession2(deps, grant, sessionId, operation) {
29778
29928
  try {
29779
- await requireSessionAuthorization4(deps, grant, {
29929
+ return await requireSessionAuthorization4(deps, grant, {
29780
29930
  sessionId,
29781
29931
  operation,
29782
29932
  surface: "http"
29783
29933
  });
29784
29934
  } catch (error) {
29785
- if (error instanceof SessionAuthorizationDeniedError3) {
29935
+ if (error instanceof SessionAuthorizationDeniedError4) {
29786
29936
  throw new HTTPException28(404, {
29787
29937
  message: "session not found",
29788
29938
  cause: error
@@ -30145,6 +30295,21 @@ function browserUploadFileIds(action) {
30145
30295
  ...new Set(actions.flatMap((entry) => entry.type === "upload" ? entry.workspaceFileIds : []))
30146
30296
  ];
30147
30297
  }
30298
+ function browserFileAuthoritySubjectId(grant, authorization) {
30299
+ if (!authorization) return grant.principalKind === "agent_attempt" ? null : grant.subjectId;
30300
+ return authorization.actor.kind === "agent_attempt" ? authorization.actor.initiatingHumanSubjectId : authorization.actor.subjectId;
30301
+ }
30302
+ function requireAuthorizedBrowserUploadFiles(workspaceFileIds, authorizedFiles) {
30303
+ const byId = new Map(authorizedFiles.map((file) => [file.id, file]));
30304
+ return workspaceFileIds.map((fileId) => {
30305
+ const file = byId.get(fileId);
30306
+ if (!file) throw new HTTPException28(404, { message: "workspace file not found" });
30307
+ if (file.status !== "ready") {
30308
+ throw new HTTPException28(409, { message: "workspace file is not ready" });
30309
+ }
30310
+ return file;
30311
+ });
30312
+ }
30148
30313
  function browserUploadFilename(value) {
30149
30314
  const normalized = value.trim().replace(/[/\\]/gu, "_").replace(/[^A-Za-z0-9._ -]+/gu, "_").replace(/\s+/gu, " ").trim().slice(0, 240);
30150
30315
  return normalized && normalized !== "." && normalized !== ".." ? normalized : "file";
@@ -30327,7 +30492,7 @@ import {
30327
30492
  requireAccessGrant as requireAccessGrant15,
30328
30493
  requireSessionAuthorization as requireSessionAuthorization5,
30329
30494
  relayConfigFromSettings as relayConfigFromSettings5,
30330
- SessionAuthorizationDeniedError as SessionAuthorizationDeniedError4,
30495
+ SessionAuthorizationDeniedError as SessionAuthorizationDeniedError5,
30331
30496
  SessionAuthorizationUnavailableError as SessionAuthorizationUnavailableError4
30332
30497
  } from "@opengeni/core";
30333
30498
  import {
@@ -31432,7 +31597,7 @@ async function authorizeSourceSession3(deps, grant, sessionId, operation) {
31432
31597
  surface: "http"
31433
31598
  });
31434
31599
  } catch (error) {
31435
- if (error instanceof SessionAuthorizationDeniedError4) {
31600
+ if (error instanceof SessionAuthorizationDeniedError5) {
31436
31601
  throw new HTTPException29(404, { message: "session not found", cause: error });
31437
31602
  }
31438
31603
  if (error instanceof SessionAuthorizationUnavailableError4) {
@@ -33150,7 +33315,7 @@ import {
33150
33315
  updateSiteAuthConnection
33151
33316
  } from "@opengeni/db";
33152
33317
  import {
33153
- SessionAuthorizationDeniedError as SessionAuthorizationDeniedError5,
33318
+ SessionAuthorizationDeniedError as SessionAuthorizationDeniedError6,
33154
33319
  SessionAuthorizationUnavailableError as SessionAuthorizationUnavailableError5,
33155
33320
  requireAccessGrant as requireAccessGrant19,
33156
33321
  requireSessionAuthorization as requireSessionAuthorization6,
@@ -34285,7 +34450,7 @@ async function authorizeSession(deps, grant, sessionId, operation) {
34285
34450
  surface: "http"
34286
34451
  });
34287
34452
  } catch (error) {
34288
- if (error instanceof SessionAuthorizationDeniedError5) {
34453
+ if (error instanceof SessionAuthorizationDeniedError6) {
34289
34454
  throw new HTTPException33(404, { message: "session not found", cause: error });
34290
34455
  }
34291
34456
  if (error instanceof SessionAuthorizationUnavailableError5) {
@@ -37347,7 +37512,7 @@ import {
37347
37512
  requireSessionAuthorizationListScope as requireSessionAuthorizationListScope2,
37348
37513
  withResolvedSessionAuthorization,
37349
37514
  SESSION_AUTHORIZATION_DEFAULT_REAUTHORIZE_MS,
37350
- SessionAuthorizationDeniedError as SessionAuthorizationDeniedError6,
37515
+ SessionAuthorizationDeniedError as SessionAuthorizationDeniedError7,
37351
37516
  SessionAuthorizationUnavailableError as SessionAuthorizationUnavailableError6
37352
37517
  } from "@opengeni/core";
37353
37518
  import {
@@ -39431,7 +39596,7 @@ function registerSessionRoutes(app, deps) {
39431
39596
  const result = await acceptSessionUserMessage(deps, grant, workspaceId, sessionId, {
39432
39597
  text: payload.text,
39433
39598
  annotations: payload.annotations,
39434
- turnInstructions: payload.turnInstructions ?? null,
39599
+ modelContext: payload.modelContext ?? null,
39435
39600
  resources: payload.resources,
39436
39601
  model: payload.model ?? null,
39437
39602
  reasoningEffort: payload.reasoningEffort ?? null,
@@ -39466,7 +39631,7 @@ function registerSessionRoutes(app, deps) {
39466
39631
  const { accepted } = await acceptSessionUserMessage(deps, grant, workspaceId, sessionId, {
39467
39632
  text: event.payload.text,
39468
39633
  annotations: event.payload.annotations,
39469
- turnInstructions: event.payload.turnInstructions ?? null,
39634
+ modelContext: event.payload.modelContext ?? null,
39470
39635
  resources: event.payload.resources ?? [],
39471
39636
  model: event.payload.model ?? null,
39472
39637
  reasoningEffort: event.payload.reasoningEffort ?? null,
@@ -40512,7 +40677,7 @@ function sessionAuthorizationOperationForHttp(method, pathname, sessionId) {
40512
40677
  return null;
40513
40678
  }
40514
40679
  function sessionAuthorizationHttpError(error) {
40515
- if (error instanceof SessionAuthorizationDeniedError6) {
40680
+ if (error instanceof SessionAuthorizationDeniedError7) {
40516
40681
  return new HTTPException41(404, { message: "session not found" });
40517
40682
  }
40518
40683
  if (error instanceof SessionAuthorizationUnavailableError6) {
@@ -45454,8 +45619,8 @@ var SLACK_INTERACTION_BOT_SUBJECT_ID = "service:slack-interaction";
45454
45619
  var SLACK_ACTION_TTL_MS = 7 * 24 * 60 * 6e4;
45455
45620
  var MAX_SLACK_APPROVALS_PER_CARD = 8;
45456
45621
  var MAX_SLACK_ACTIONS_PER_CARD = 20;
45457
- var SLACK_TASK_INSTRUCTIONS = [
45458
- "This turn originated from Slack. Slack message and thread context is task-local only.",
45622
+ var SLACK_SESSION_INSTRUCTIONS = [
45623
+ "This session is an OpenGeni Slack task surface. Treat Slack message and thread context as task-local unless a separate explicit authorized user action says otherwise.",
45459
45624
  "Execute direct, safe, sufficiently specified requests immediately.",
45460
45625
  "Ask one concise clarifying question only when materially required information is missing or the requested action is risky, irreversible, or authorization-sensitive.",
45461
45626
  "Do not write Slack context to Documents, Knowledge, Memory, preferences, Workspace Charter, instructions, or policy unless a separate explicit authorized user action requests it.",
@@ -46264,7 +46429,7 @@ async function processSlackInboxEntry(deps, entry) {
46264
46429
  session = await createSessionForRequest2(deps, grant, entry.workspaceId, {
46265
46430
  requestedSessionId: interaction.sessionReservationId,
46266
46431
  initialMessage: slackInvocationPreparedEntry(preparedEntry, preparedAttachments).text,
46267
- turnInstructions: SLACK_TASK_INSTRUCTIONS,
46432
+ instructions: SLACK_SESSION_INSTRUCTIONS,
46268
46433
  firstPartyMcpTools: slackTaskFirstPartyMcpTools(deps.settings),
46269
46434
  resources: preparedAttachments.resources,
46270
46435
  ...preferredModel ? { model: preferredModel } : {},
@@ -46657,7 +46822,7 @@ async function processSlackReactionInboxEntry(deps, entry) {
46657
46822
  session = await createSessionForRequest2(deps, grant, entry.workspaceId, {
46658
46823
  requestedSessionId: interaction.sessionReservationId,
46659
46824
  initialMessage: preparedEntry.text,
46660
- turnInstructions: SLACK_TASK_INSTRUCTIONS,
46825
+ instructions: SLACK_SESSION_INSTRUCTIONS,
46661
46826
  // The exact reacted message and bounded containing thread are already in
46662
46827
  // the prompt; do not expose general Slack history tools for this trigger.
46663
46828
  firstPartyMcpTools: resolveFirstPartyMcpToolPolicy(deps.settings).default,
@@ -46944,7 +47109,6 @@ async function acceptSlackReactionTask(deps, grant, sessionId, entry, resources)
46944
47109
  }
46945
47110
  await acceptSessionUserMessage2(deps, grant, entry.workspaceId, sessionId, {
46946
47111
  text: entry.text,
46947
- turnInstructions: SLACK_TASK_INSTRUCTIONS,
46948
47112
  resources,
46949
47113
  clientEventId
46950
47114
  });
@@ -47018,7 +47182,6 @@ async function continueSlackSession(deps, grant, interaction, entry, resources =
47018
47182
  }
47019
47183
  await acceptSessionUserMessage2(deps, grant, entry.workspaceId, interaction.sessionId, {
47020
47184
  text: entry.text,
47021
- turnInstructions: SLACK_TASK_INSTRUCTIONS,
47022
47185
  resources,
47023
47186
  clientEventId: `slack:${entry.providerEventId}`
47024
47187
  });
@@ -48301,9 +48464,7 @@ function createAppComposition(deps) {
48301
48464
  if (context.accountId !== accountId) {
48302
48465
  throw new Error("document account/workspace authority mismatch");
48303
48466
  }
48304
- const claimedDocument = await getDocument2(deps.db, workspaceId, documentId, {
48305
- viewerSubjectId: authoritySubjectId
48306
- });
48467
+ const claimedDocument = await getDocumentForIndexing(deps.db, workspaceId, documentId);
48307
48468
  if (!claimedDocument || claimedDocument.authorityKind !== authorityKind || claimedDocument.authorityWorkspaceId !== authorityWorkspaceId || claimedDocument.authoritySubjectId !== authoritySubjectId) {
48308
48469
  throw new Error("document authority changed before indexing");
48309
48470
  }
@@ -48619,7 +48780,7 @@ function createAppComposition(deps) {
48619
48780
  surface: "first_party_mcp"
48620
48781
  });
48621
48782
  } catch (error) {
48622
- if (error instanceof SessionAuthorizationDeniedError7) {
48783
+ if (error instanceof SessionAuthorizationDeniedError8) {
48623
48784
  throw new HTTPException55(404, { message: "session not found" });
48624
48785
  }
48625
48786
  if (error instanceof SessionAuthorizationUnavailableError7) {
@@ -48823,7 +48984,7 @@ async function withAccessGrantSessionRlsContext(deps, grant, fn) {
48823
48984
  throw new HTTPException55(403, { message: "agent attempt authority is invalid" });
48824
48985
  }
48825
48986
  try {
48826
- const actor = await requireLiveAgentAttemptAuthorization2(deps.db, grant, callerSessionId);
48987
+ const actor = await requireLiveAgentAttemptAuthorization3(deps.db, grant, callerSessionId);
48827
48988
  return await withSessionRlsActorContext(
48828
48989
  {
48829
48990
  subjectId: actor.subjectId,
@@ -48832,7 +48993,7 @@ async function withAccessGrantSessionRlsContext(deps, grant, fn) {
48832
48993
  fn
48833
48994
  );
48834
48995
  } catch (error) {
48835
- if (error instanceof SessionAuthorizationDeniedError7) {
48996
+ if (error instanceof SessionAuthorizationDeniedError8) {
48836
48997
  throw new HTTPException55(403, {
48837
48998
  message: "agent attempt authority is invalid",
48838
48999
  cause: error
@@ -48875,7 +49036,7 @@ function clientAuthConfig(settings) {
48875
49036
  return { mode: "none" };
48876
49037
  }
48877
49038
  function codemodeHttpError(error) {
48878
- if (error instanceof SessionAuthorizationDeniedError7) {
49039
+ if (error instanceof SessionAuthorizationDeniedError8) {
48879
49040
  return new HTTPException55(404, {
48880
49041
  message: "session not found",
48881
49042
  cause: error
@@ -49756,4 +49917,4 @@ export {
49756
49917
  withDefaultEnabledCapabilityMcpTools,
49757
49918
  workflowIdForSession3 as workflowIdForSession
49758
49919
  };
49759
- //# sourceMappingURL=chunk-2PQMSRCB.js.map
49920
+ //# sourceMappingURL=chunk-WCXHI3FF.js.map