@opengeni/worker-bundle 0.27.3 → 0.28.1-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.
@@ -86,7 +86,7 @@ import {
86
86
  withFirstPartyTools,
87
87
  wrapLazyTurnBoxWithRouting,
88
88
  wrapTurnBoxWithRouting
89
- } from "./chunk-C54VDT6I.js";
89
+ } from "./chunk-AKP6BKM2.js";
90
90
  import {
91
91
  StreamTimingMetrics,
92
92
  makeMachineOpObserver,
@@ -3550,6 +3550,7 @@ function createTurnContext(input) {
3550
3550
  // src/activities/agent-turn/finalization.ts
3551
3551
  import {
3552
3552
  commitSessionAttemptQuiescence,
3553
+ listPendingSessionTurns,
3553
3554
  recordCodexAccountUsageWithWakeTargets,
3554
3555
  releaseCodexCredentialLease,
3555
3556
  releaseXaiCredentialLease,
@@ -3713,6 +3714,50 @@ async function readCaptureRepository(svc, root) {
3713
3714
  }
3714
3715
  }
3715
3716
  var loggedStorageNullOnce = false;
3717
+ async function captureWhileIdle(input) {
3718
+ if (input.signal?.aborted) return;
3719
+ const controller = new AbortController();
3720
+ const signal = input.signal ? AbortSignal.any([input.signal, controller.signal]) : controller.signal;
3721
+ let checking = false;
3722
+ let stopped = false;
3723
+ const check = async () => {
3724
+ if (checking || stopped || signal.aborted) return;
3725
+ checking = true;
3726
+ try {
3727
+ let timer2;
3728
+ let onAbort;
3729
+ try {
3730
+ const pending = await Promise.race([
3731
+ input.hasPendingWork(),
3732
+ new Promise((resolve) => {
3733
+ onAbort = () => resolve(true);
3734
+ signal.addEventListener("abort", onAbort, { once: true });
3735
+ timer2 = setTimeout(() => resolve(true), 1e3);
3736
+ })
3737
+ ]);
3738
+ if (pending) controller.abort();
3739
+ } finally {
3740
+ clearTimeout(timer2);
3741
+ if (onAbort) signal.removeEventListener("abort", onAbort);
3742
+ }
3743
+ } catch {
3744
+ controller.abort();
3745
+ } finally {
3746
+ checking = false;
3747
+ }
3748
+ };
3749
+ await check();
3750
+ if (signal.aborted) return;
3751
+ const timer = setInterval(() => void check(), 250);
3752
+ timer.unref?.();
3753
+ try {
3754
+ await input.capture(signal);
3755
+ } finally {
3756
+ stopped = true;
3757
+ clearInterval(timer);
3758
+ controller.abort();
3759
+ }
3760
+ }
3716
3761
  async function captureWorkspaceRevision(input) {
3717
3762
  const { observability } = input;
3718
3763
  const stage = { value: "preflight" };
@@ -3755,13 +3800,23 @@ async function captureWorkspaceRevision(input) {
3755
3800
  name: "opengeni_workspace_captures_inflight",
3756
3801
  help: "Current workspace-capture operations still resident in this worker process."
3757
3802
  });
3803
+ const ownership = { keys: /* @__PURE__ */ new Set(), retain: false };
3758
3804
  const capture = runCapture(
3759
3805
  input,
3760
3806
  { ...input, objectStorage: input.objectStorage },
3761
3807
  startedAt,
3762
3808
  controller.signal,
3763
- stage
3764
- ).finally(() => {
3809
+ stage,
3810
+ ownership
3811
+ ).finally(async () => {
3812
+ if (!ownership.retain && ownership.keys.size > 0) {
3813
+ await safeDelete(
3814
+ input.objectStorage,
3815
+ [...ownership.keys],
3816
+ observability,
3817
+ new AbortController().signal
3818
+ );
3819
+ }
3765
3820
  observability.incrementGauge({
3766
3821
  name: "opengeni_workspace_captures_inflight",
3767
3822
  help: "Current workspace-capture operations still resident in this worker process.",
@@ -3788,13 +3843,17 @@ async function captureWorkspaceRevision(input) {
3788
3843
  });
3789
3844
  return;
3790
3845
  }
3791
- observability.warn("workspace capture failed \u2014 turn outcome unaffected", {
3792
- "opengeni.session_id": input.sessionId,
3793
- "opengeni.turn_id": input.turnId ?? "",
3794
- "error.message": error instanceof Error ? error.message : String(error),
3795
- "workspace_capture.duration_ms": Date.now() - startedAt,
3796
- "workspace_capture.stage": stage.value
3797
- });
3846
+ const reason = controller.signal.aborted ? "deadline_exceeded" : isBoxExitingError(error) ? "sandbox_exited" : "operation_failed";
3847
+ observability.warn(
3848
+ `workspace capture failed \u2014 stage=${stage.value} reason=${reason} durationMs=${Date.now() - startedAt}; review cache not updated`,
3849
+ {
3850
+ "opengeni.session_id": input.sessionId,
3851
+ "opengeni.turn_id": input.turnId ?? "",
3852
+ "error.message": error instanceof Error ? error.message : String(error),
3853
+ "workspace_capture.duration_ms": Date.now() - startedAt,
3854
+ "workspace_capture.stage": stage.value
3855
+ }
3856
+ );
3798
3857
  observability.incrementCounter({
3799
3858
  name: "opengeni_workspace_capture_total",
3800
3859
  labels: { result: "failed" }
@@ -3805,7 +3864,8 @@ async function captureWorkspaceRevision(input) {
3805
3864
  controller.abort();
3806
3865
  }
3807
3866
  }
3808
- async function runCapture(input, ctx, startedAt, signal, stage) {
3867
+ async function runCapture(input, ctx, startedAt, signal, stage, ownership) {
3868
+ const ownedKeys = ownership.keys;
3809
3869
  const { observability } = input;
3810
3870
  const storage = ctx.objectStorage;
3811
3871
  const keepN = input.keepLatest ?? KEEP_LATEST_REVISIONS;
@@ -3892,6 +3952,13 @@ async function runCapture(input, ctx, startedAt, signal, stage) {
3892
3952
  }
3893
3953
  const files = [];
3894
3954
  const blobKeys = /* @__PURE__ */ new Set();
3955
+ const captureId = crypto.randomUUID();
3956
+ const retainedRefs = retainedCaptureBlobRefs(
3957
+ input.workspaceId,
3958
+ input.sessionId,
3959
+ prev?.blobKeys ?? []
3960
+ );
3961
+ const storedKeys = new Set(retainedRefs.values());
3895
3962
  let totalBytes = 0;
3896
3963
  let tooLargeCount = 0;
3897
3964
  let binaryCount = 0;
@@ -3921,6 +3988,7 @@ async function runCapture(input, ctx, startedAt, signal, stage) {
3921
3988
  });
3922
3989
  throwIfCaptureAborted(signal);
3923
3990
  } catch (error) {
3991
+ if (signal.aborted) throw error;
3924
3992
  const boxExiting = classifyCaptureEntryError(error);
3925
3993
  if (boxExiting) throw boxExiting;
3926
3994
  continue;
@@ -3942,7 +4010,7 @@ async function runCapture(input, ctx, startedAt, signal, stage) {
3942
4010
  }
3943
4011
  const bytes = Buffer.from(read.content, "base64");
3944
4012
  const hash = sha256(bytes);
3945
- const contentRef = blobKey(input.workspaceId, input.sessionId, hash);
4013
+ const contentRef = retainedRefs.get(hash) ?? blobKey(input.workspaceId, input.sessionId, `${captureId}/${hash}`);
3946
4014
  if (read.isBinary) binaryCount += 1;
3947
4015
  if (!blobKeys.has(contentRef)) {
3948
4016
  totalBytes += bytes.byteLength;
@@ -3959,6 +4027,17 @@ async function runCapture(input, ctx, startedAt, signal, stage) {
3959
4027
  }
3960
4028
  }
3961
4029
  blobKeys.add(contentRef);
4030
+ if (!storedKeys.has(contentRef)) {
4031
+ stage.value = "content_upload";
4032
+ ownedKeys.add(contentRef);
4033
+ await storage.putObject({
4034
+ key: contentRef,
4035
+ contentType: "application/octet-stream",
4036
+ body: bytes
4037
+ });
4038
+ throwIfCaptureAborted(signal);
4039
+ storedKeys.add(contentRef);
4040
+ }
3962
4041
  files.push({
3963
4042
  // baseHash (git HEAD blob sha) is intentionally null in M1: the wake-on-edit
3964
4043
  // flush guard (M3/C2) compares live content against the after-image `hash`,
@@ -3975,7 +4054,7 @@ async function runCapture(input, ctx, startedAt, signal, stage) {
3975
4054
  deleted: false
3976
4055
  });
3977
4056
  }
3978
- let fingerprint = changeFingerprint(repos, files);
4057
+ const fingerprint = changeFingerprint(repos, files);
3979
4058
  if (prev && prev.stats.fingerprint === fingerprint) {
3980
4059
  observability.incrementCounter({
3981
4060
  name: "opengeni_workspace_capture_total",
@@ -3986,102 +4065,9 @@ async function runCapture(input, ctx, startedAt, signal, stage) {
3986
4065
  stage.value = "tree_index";
3987
4066
  const tree = await buildTreeIndex(svc, startedAt, signal);
3988
4067
  throwIfCaptureAborted(signal);
3989
- const turnKey = input.turnId;
4068
+ const turnKey = `${input.turnId}/${captureId}`;
3990
4069
  const treeKey = `workspace-captures/${input.workspaceId}/${input.sessionId}/trees/${turnKey}.json`;
3991
4070
  const manifestKey = `workspace-captures/${input.workspaceId}/${input.sessionId}/manifests/${turnKey}.json`;
3992
- const uploadedKeys = /* @__PURE__ */ new Set();
3993
- blobKeys.clear();
3994
- totalBytes = 0;
3995
- let refreshedFiles = 0;
3996
- const omittedFiles = /* @__PURE__ */ new Set();
3997
- for (const file of files) {
3998
- stage.value = "content_upload";
3999
- throwIfCaptureAborted(signal);
4000
- if (file.deleted || file.tooLarge || !file.contentRef || !file.hash) {
4001
- continue;
4002
- }
4003
- let reread;
4004
- try {
4005
- reread = await svc.fsRead({
4006
- path: file.path,
4007
- encoding: "base64",
4008
- maxBytes: PER_FILE_CONTENT_GUARD_BYTES
4009
- });
4010
- throwIfCaptureAborted(signal);
4011
- } catch (error) {
4012
- if (signal.aborted) throw error;
4013
- const boxExiting = classifyCaptureEntryError(error);
4014
- if (boxExiting) throw boxExiting;
4015
- omittedFiles.add(file.path);
4016
- refreshedFiles += 1;
4017
- continue;
4018
- }
4019
- if (reread.truncated) {
4020
- file.hash = null;
4021
- file.contentRef = null;
4022
- file.sizeBytes = reread.sizeBytes;
4023
- file.isBinary = reread.isBinary;
4024
- file.tooLarge = true;
4025
- refreshedFiles += 1;
4026
- continue;
4027
- }
4028
- const bytes = Buffer.from(reread.content, "base64");
4029
- const hash = sha256(bytes);
4030
- const contentRef = blobKey(input.workspaceId, input.sessionId, hash);
4031
- if (hash !== file.hash || bytes.byteLength !== file.sizeBytes || reread.isBinary !== file.isBinary) {
4032
- file.hash = hash;
4033
- file.contentRef = contentRef;
4034
- file.sizeBytes = bytes.byteLength;
4035
- file.isBinary = reread.isBinary;
4036
- refreshedFiles += 1;
4037
- }
4038
- if (!blobKeys.has(contentRef)) {
4039
- totalBytes += bytes.byteLength;
4040
- if (totalBytes > WHOLE_CAPTURE_GUARD_BYTES) {
4041
- observability.warn("workspace capture skipped \u2014 whole-capture guard tripped", {
4042
- "opengeni.session_id": input.sessionId,
4043
- "workspace_capture.total_bytes": totalBytes
4044
- });
4045
- observability.incrementCounter({
4046
- name: "opengeni_workspace_capture_total",
4047
- labels: { result: "guard_tripped" }
4048
- });
4049
- return;
4050
- }
4051
- }
4052
- blobKeys.add(contentRef);
4053
- if (!uploadedKeys.has(contentRef)) {
4054
- await storage.putObject({
4055
- key: contentRef,
4056
- contentType: "application/octet-stream",
4057
- body: bytes
4058
- });
4059
- throwIfCaptureAborted(signal);
4060
- uploadedKeys.add(contentRef);
4061
- }
4062
- }
4063
- if (omittedFiles.size > 0) {
4064
- for (let index = files.length - 1; index >= 0; index -= 1) {
4065
- const file = files[index];
4066
- if (file && omittedFiles.has(file.path)) files.splice(index, 1);
4067
- }
4068
- }
4069
- if (refreshedFiles > 0) {
4070
- observability.incrementCounter({
4071
- name: "opengeni_workspace_capture_files_refreshed_total",
4072
- amount: refreshedFiles
4073
- });
4074
- }
4075
- tooLargeCount = files.filter((file) => file.tooLarge).length;
4076
- binaryCount = files.filter((file) => !file.deleted && file.isBinary).length;
4077
- fingerprint = changeFingerprint(repos, files);
4078
- if (prev && prev.stats.fingerprint === fingerprint) {
4079
- observability.incrementCounter({
4080
- name: "opengeni_workspace_capture_total",
4081
- labels: { result: "skipped_empty" }
4082
- });
4083
- return;
4084
- }
4085
4071
  const capturedAt = /* @__PURE__ */ new Date();
4086
4072
  const stats = {
4087
4073
  repoCount: repos.length,
@@ -4118,11 +4104,13 @@ async function runCapture(input, ctx, startedAt, signal, stage) {
4118
4104
  })
4119
4105
  );
4120
4106
  stage.value = "tree_upload";
4107
+ ownedKeys.add(treeKey);
4121
4108
  await storage.putObject({ key: treeKey, contentType: "application/json", body: treeBytes });
4122
4109
  throwIfCaptureAborted(signal);
4123
4110
  stats.durationMs = Date.now() - startedAt;
4124
4111
  const manifestBytes = utf8(JSON.stringify(manifest));
4125
4112
  stage.value = "manifest_upload";
4113
+ ownedKeys.add(manifestKey);
4126
4114
  await storage.putObject({
4127
4115
  key: manifestKey,
4128
4116
  contentType: "application/json",
@@ -4131,6 +4119,7 @@ async function runCapture(input, ctx, startedAt, signal, stage) {
4131
4119
  throwIfCaptureAborted(signal);
4132
4120
  const sizeBytes = totalBytes + treeBytes.byteLength + manifestBytes.byteLength;
4133
4121
  stage.value = "database_commit";
4122
+ ownership.retain = true;
4134
4123
  const inserted = await insertWorkspaceCapture(input.db, {
4135
4124
  accountId: input.accountId,
4136
4125
  workspaceId: input.workspaceId,
@@ -4147,6 +4136,7 @@ async function runCapture(input, ctx, startedAt, signal, stage) {
4147
4136
  stats,
4148
4137
  capturedAt
4149
4138
  });
4139
+ ownership.retain = inserted !== null;
4150
4140
  if (process.env.OPENGENI_TEST_SCENARIO === "sandbox") {
4151
4141
  console.log(
4152
4142
  `[sandbox-e2e] capture commit inserted=${Boolean(inserted)} revision=${revision} epoch=${input.leaseEpoch} group=${input.sandboxGroupId}`
@@ -4164,6 +4154,7 @@ async function runCapture(input, ctx, startedAt, signal, stage) {
4164
4154
  if (input.publish) {
4165
4155
  await input.publish(inserted.events).catch(() => void 0);
4166
4156
  }
4157
+ stage.value = "garbage_collection";
4167
4158
  let gcDeleted = 0;
4168
4159
  try {
4169
4160
  throwIfCaptureAborted(signal);
@@ -4323,6 +4314,16 @@ function utf8(s) {
4323
4314
  function blobKey(workspaceId, sessionId, sha256Hex) {
4324
4315
  return `workspace-captures/${workspaceId}/${sessionId}/blobs/${sha256Hex}`;
4325
4316
  }
4317
+ function retainedCaptureBlobRefs(workspaceId, sessionId, latestKeys) {
4318
+ const prefix = blobKey(workspaceId, sessionId, "");
4319
+ const refs = /* @__PURE__ */ new Map();
4320
+ for (const key of latestKeys) {
4321
+ if (!key.startsWith(prefix)) continue;
4322
+ const match = /^(?:[0-9a-f-]{36}\/)?([0-9a-f]{64})$/.exec(key.slice(prefix.length));
4323
+ if (match?.[1]) refs.set(match[1], key);
4324
+ }
4325
+ return refs;
4326
+ }
4326
4327
  async function safeDelete(storage, keys, observability, signal) {
4327
4328
  for (const key of keys) {
4328
4329
  throwIfCaptureAborted(signal);
@@ -4692,39 +4693,49 @@ async function finalizeTurnAttempt(deps) {
4692
4693
  cancellationRequested: finalizerSignal?.aborted === true,
4693
4694
  deadlineRotationRequested: sandboxState.deadlineRotationRequested
4694
4695
  });
4695
- if (runTurnEndPersistence && attempt.turnId && sandboxState.resolvedSandbox && sandboxState.setupBoxSession && sandboxState.sandboxGroupId) {
4696
+ if (settings.workspaceCaptureEnabled && runTurnEndPersistence && attempt.turnId && sandboxState.resolvedSandbox && sandboxState.setupBoxSession && sandboxState.sandboxGroupId) {
4696
4697
  sandboxState.turnEndCaptureInProgress = true;
4697
- if (sandboxState.snapshotInFlight) {
4698
- await waitForWarmSnapshot(
4699
- sandboxState.snapshotInFlight,
4700
- settings.sandboxSnapshotTimeoutMs,
4701
- finalizerSignal
4702
- );
4703
- }
4704
- const captureEstablished = sandboxState.resolvedSandbox.established;
4705
- await captureWorkspaceRevision({
4706
- db,
4707
- objectStorage,
4708
- settings,
4709
- publish: async (events) => {
4710
- await publishDurableSessionEvents(bus, input.workspaceId, input.sessionId, events);
4711
- },
4712
- session: sandboxState.setupBoxSession,
4713
- openReadSession: async () => await openFreshWorkspaceCaptureSession({
4714
- backendId: captureEstablished.backendId,
4715
- client: captureEstablished.client,
4716
- session: sandboxState.setupBoxSession,
4717
- expectedInstanceId: captureEstablished.instanceId
4718
- }),
4719
- leaseEpoch: sandboxState.resolvedSandbox.leaseEpoch,
4720
- sandboxGroupId: sandboxState.sandboxGroupId,
4721
- accountId: input.accountId,
4722
- workspaceId: input.workspaceId,
4723
- sessionId: input.sessionId,
4724
- turnId: attempt.turnId,
4725
- attemptId: input.attemptId,
4726
- observability,
4727
- ...finalizerSignal ? { signal: finalizerSignal } : {}
4698
+ const captureSandbox = sandboxState.resolvedSandbox;
4699
+ const captureSession = sandboxState.setupBoxSession;
4700
+ const captureGroupId = sandboxState.sandboxGroupId;
4701
+ const captureTurnId = attempt.turnId;
4702
+ await captureWhileIdle({
4703
+ ...finalizerSignal ? { signal: finalizerSignal } : {},
4704
+ hasPendingWork: async () => (await listPendingSessionTurns(db, input.workspaceId, input.sessionId)).some(
4705
+ (turn) => turn.id !== captureTurnId
4706
+ ),
4707
+ capture: async (signal) => {
4708
+ if (sandboxState.snapshotInFlight) {
4709
+ await waitForWarmSnapshot(
4710
+ sandboxState.snapshotInFlight,
4711
+ settings.sandboxSnapshotTimeoutMs,
4712
+ signal
4713
+ );
4714
+ }
4715
+ if (signal.aborted) return;
4716
+ await captureWorkspaceRevision({
4717
+ db,
4718
+ objectStorage,
4719
+ settings,
4720
+ publish: async (events) => publishDurableSessionEvents(bus, input.workspaceId, input.sessionId, events),
4721
+ session: captureSession,
4722
+ openReadSession: async () => openFreshWorkspaceCaptureSession({
4723
+ backendId: captureSandbox.established.backendId,
4724
+ client: captureSandbox.established.client,
4725
+ session: captureSession,
4726
+ expectedInstanceId: captureSandbox.established.instanceId
4727
+ }),
4728
+ leaseEpoch: captureSandbox.leaseEpoch,
4729
+ sandboxGroupId: captureGroupId,
4730
+ accountId: input.accountId,
4731
+ workspaceId: input.workspaceId,
4732
+ sessionId: input.sessionId,
4733
+ turnId: captureTurnId,
4734
+ attemptId: input.attemptId,
4735
+ observability,
4736
+ signal
4737
+ });
4738
+ }
4728
4739
  });
4729
4740
  }
4730
4741
  eventing.toolPreparationClosing = true;
@@ -14287,6 +14298,8 @@ async function prepareRunCredentials(deps) {
14287
14298
  import { getWorkspaceConnectionModelRestrictions } from "@opengeni/db";
14288
14299
  import {
14289
14300
  beginConnectorActionExecution,
14301
+ getExternalLinkTurnAuthorization,
14302
+ getSessionTurnForAttempt as getSessionTurnForAttempt2,
14290
14303
  getWorkspaceVideoGenerationPolicy,
14291
14304
  listSkillDescriptors as listSkillDescriptors2,
14292
14305
  completeConnectorActionExecution,
@@ -14922,6 +14935,8 @@ function driveQuery(value) {
14922
14935
  import {
14923
14936
  buildConnectionTokenResolver,
14924
14937
  buildHostConnectionTokenResolver,
14938
+ getSessionTurnForAttempt,
14939
+ authorizeDirectHostMcpUse,
14925
14940
  resolveAcceptedConnectionUse,
14926
14941
  sessionTenancyProductActivated
14927
14942
  } from "@opengeni/db";
@@ -14929,7 +14944,10 @@ import { recordTenancyCompatibilityLaneUse } from "@opengeni/observability";
14929
14944
  var OPENGENI_CONNECTION_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu;
14930
14945
  function connectionTokenResolverForTurn(input) {
14931
14946
  const hostResolver = input.connectionCredentials?.mcpCredentials;
14932
- const baseResolver = hostResolver ? buildHostConnectionTokenResolver(hostResolver, {
14947
+ const readHostTurn = input.getHostTurnForAttempt ?? getSessionTurnForAttempt;
14948
+ const hostDb = input.db;
14949
+ const nativeResolver = buildConnectionTokenResolver(input.db, input.settings);
14950
+ const configuredResolver = hostResolver ? buildHostConnectionTokenResolver(hostResolver, {
14933
14951
  accountId: input.accountId,
14934
14952
  workspaceId: input.workspaceId,
14935
14953
  sessionId: input.sessionId,
@@ -14939,8 +14957,25 @@ function connectionTokenResolverForTurn(input) {
14939
14957
  executionGeneration: input.turn.executionGeneration,
14940
14958
  initiator: input.turn.initiator,
14941
14959
  initiatorContext: input.turn.initiatorContext,
14942
- surface: "model"
14943
- }) : buildConnectionTokenResolver(input.db, input.settings);
14960
+ surface: "model",
14961
+ // Only immutable captured direct-turn authority is currently supported.
14962
+ // Direct, scheduled and exact causal/child snapshots are validated;
14963
+ // no authority follows merely from a binding or creator identity.
14964
+ authorizeDurableBinding: (request) => authorizeDirectHostMcpUse(hostDb, request),
14965
+ authorizeExecution: async (request) => {
14966
+ if (request.connectionRef.authoritySource !== "host" && (!request.connectionRef.connectionId || OPENGENI_CONNECTION_ID_PATTERN.test(request.connectionRef.connectionId)))
14967
+ return true;
14968
+ if (!request.attemptId) return false;
14969
+ const current = await readHostTurn(
14970
+ hostDb,
14971
+ request.workspaceId,
14972
+ request.sessionId,
14973
+ request.attemptId
14974
+ );
14975
+ return current !== null && current.id === request.turnId && current.executionGeneration === request.executionGeneration;
14976
+ }
14977
+ }) : nativeResolver;
14978
+ const baseResolver = (request) => input.connectionCredentials?.mcpAuthoritySource === "host" && request.connectionRef.authoritySource !== "host" ? nativeResolver(request) : configuredResolver(request);
14944
14979
  return async (request) => {
14945
14980
  if (request.connectionRef.authoritySource === "host") {
14946
14981
  if (hostResolver) return await baseResolver(request);
@@ -14956,7 +14991,7 @@ function connectionTokenResolverForTurn(input) {
14956
14991
  ...request.connectionRef.selectedResources ? { selectedResources: request.connectionRef.selectedResources } : {}
14957
14992
  };
14958
14993
  }
14959
- if (hostResolver && request.connectionRef.connectionId && !OPENGENI_CONNECTION_ID_PATTERN.test(request.connectionRef.connectionId)) {
14994
+ if (hostResolver && input.connectionCredentials?.mcpAuthoritySource !== "host" && request.connectionRef.connectionId && !OPENGENI_CONNECTION_ID_PATTERN.test(request.connectionRef.connectionId)) {
14960
14995
  try {
14961
14996
  return await baseResolver(request);
14962
14997
  } catch {
@@ -16063,10 +16098,14 @@ import {
16063
16098
  resolveCatalogSettings as resolveCatalogSettings2,
16064
16099
  resolveWorkspaceModelSelection,
16065
16100
  withFrozenPersonalConnectionDelegations,
16066
- resolveSessionToolPolicy
16101
+ resolveSessionToolPolicy,
16102
+ hasPermission as hasPermission2
16067
16103
  } from "@opengeni/core";
16068
16104
  import { SandboxChannelAService as SandboxChannelAService3 } from "@opengeni/runtime/sandbox";
16069
16105
  import { sandboxRunAs as sandboxRunAs4 } from "@opengeni/runtime";
16106
+ import {
16107
+ DEFAULT_FIRST_PARTY_MCP_PERMISSIONS as DEFAULT_FIRST_PARTY_MCP_PERMISSIONS2
16108
+ } from "@opengeni/contracts";
16070
16109
 
16071
16110
  // src/activities/agent-turn/turn-sandbox-access.ts
16072
16111
  async function resolveTurnSandboxAccess(sandboxState, sdkOwnedSandboxSession, unavailableMessage) {
@@ -16189,10 +16228,9 @@ import {
16189
16228
 
16190
16229
  // src/activities/agent-turn/skill-read.ts
16191
16230
  import {
16192
- assertSkillRelativePath,
16231
+ listSkillPaths,
16193
16232
  PORTABLE_SKILL_MAX_FILES,
16194
16233
  readSkillFiles,
16195
- SKILL_READ_MAX_OUTPUT_BYTES,
16196
16234
  SKILL_READ_MAX_PATHS
16197
16235
  } from "@opengeni/runtime/skill-library";
16198
16236
  var SKILL_READ_TOOL_NAME = "skill_read";
@@ -16239,7 +16277,7 @@ function createSkillReadAttemptToolDefinition(input) {
16239
16277
  const loaded = await input.load(args.skill);
16240
16278
  const metadata = "files" in loaded ? loaded : null;
16241
16279
  const files = metadata ? metadata.files : loaded;
16242
- const selected = args.listFiles === true ? listSkillPaths(files) : readSkillFiles(files, args.paths);
16280
+ const selected = args.listFiles === true ? listSkillPaths(files, PORTABLE_SKILL_MAX_FILES) : readSkillFiles(files, args.paths);
16243
16281
  const output = {
16244
16282
  ...metadata ? {
16245
16283
  skillId: metadata.skillId,
@@ -16257,22 +16295,6 @@ function createSkillReadAttemptToolDefinition(input) {
16257
16295
  }
16258
16296
  };
16259
16297
  }
16260
- function listSkillPaths(files) {
16261
- if (files.length > PORTABLE_SKILL_MAX_FILES) {
16262
- throw new Error(`Skill inventory exceeds ${PORTABLE_SKILL_MAX_FILES} files.`);
16263
- }
16264
- const seen = /* @__PURE__ */ new Set();
16265
- for (const { path } of files) {
16266
- assertSkillRelativePath(path);
16267
- if (seen.has(path)) throw new Error(`Duplicate stored Skill path: ${path}`);
16268
- seen.add(path);
16269
- }
16270
- const result = { paths: [...seen].sort() };
16271
- if (Buffer.byteLength(JSON.stringify(result), "utf8") > SKILL_READ_MAX_OUTPUT_BYTES) {
16272
- throw new Error("Skill inventory exceeds the read output limit.");
16273
- }
16274
- return result;
16275
- }
16276
16298
 
16277
16299
  // src/activities/agent-turn/skill-search.ts
16278
16300
  import { PublicSkillSearchError } from "@opengeni/core";
@@ -16494,7 +16516,7 @@ function createSkillInstallAttemptToolDefinition(input) {
16494
16516
  // src/activities/agent-turn/skill-transfer.ts
16495
16517
  import { posix } from "path";
16496
16518
  import {
16497
- assertSkillRelativePath as assertSkillRelativePath2,
16519
+ assertSkillRelativePath,
16498
16520
  buildPortableSkillArtifact as buildPortableSkillArtifact2,
16499
16521
  PORTABLE_SKILL_MAX_FILES as PORTABLE_SKILL_MAX_FILES3,
16500
16522
  PORTABLE_SKILL_MAX_FILE_BYTES,
@@ -16518,7 +16540,7 @@ function guardSkillFilesystem(fs, authority) {
16518
16540
  };
16519
16541
  }
16520
16542
  async function checkoutSkillDirectory(fs, directory, files) {
16521
- assertSkillRelativePath2(directory);
16543
+ assertSkillRelativePath(directory);
16522
16544
  const artifact = buildPortableSkillArtifact2(files);
16523
16545
  const parent = posix.dirname(directory);
16524
16546
  if (parent !== ".") await fs.fsMkdir({ path: parent, recursive: true });
@@ -16538,7 +16560,7 @@ async function checkoutSkillDirectory(fs, directory, files) {
16538
16560
  return { directory, fileCount: artifact.files.length };
16539
16561
  }
16540
16562
  async function readSkillDirectory(fs, directory) {
16541
- assertSkillRelativePath2(directory);
16563
+ assertSkillRelativePath(directory);
16542
16564
  const pending = [directory];
16543
16565
  const seen = new Set(pending);
16544
16566
  const files = [];
@@ -16559,7 +16581,7 @@ async function readSkillDirectory(fs, directory) {
16559
16581
  throw new Error(`Skill directory listing is incomplete: ${current}`);
16560
16582
  }
16561
16583
  for (const entry of listing.root.children) {
16562
- assertSkillRelativePath2(entry.path);
16584
+ assertSkillRelativePath(entry.path);
16563
16585
  if (posix.dirname(entry.path) !== current || posix.basename(entry.path) !== entry.name) {
16564
16586
  throw new Error("Skill directory listing returned a path outside the requested directory.");
16565
16587
  }
@@ -17218,6 +17240,19 @@ async function prepareTurnToolRuntime(deps) {
17218
17240
  }
17219
17241
  };
17220
17242
  })() : void 0;
17243
+ const linkedAuthority = await getExternalLinkTurnAuthorization(
17244
+ db,
17245
+ {
17246
+ accountId: input.accountId,
17247
+ workspaceId: input.workspaceId
17248
+ },
17249
+ turn.id
17250
+ );
17251
+ if (linkedAuthority && !linkedAuthority.authorized)
17252
+ throw new Error("Native identity link was revoked");
17253
+ const effectiveFirstPartyPermissions = linkedAuthority ? (session.firstPartyMcpPermissions ?? DEFAULT_FIRST_PARTY_MCP_PERMISSIONS2).filter(
17254
+ (permission) => hasPermission2(linkedAuthority.permissions, permission)
17255
+ ) : session.firstPartyMcpPermissions;
17221
17256
  const selectedFirstPartyMcpTools = allowedFirstPartyMcpToolsForSession(
17222
17257
  runSettings,
17223
17258
  session.firstPartyMcpTools
@@ -17229,11 +17264,17 @@ async function prepareTurnToolRuntime(deps) {
17229
17264
  title: session.title,
17230
17265
  titleSource: session.titleSource,
17231
17266
  firstPartyMcpTools: selectedFirstPartyMcpTools,
17232
- firstPartyMcpPermissions: session.firstPartyMcpPermissions
17267
+ firstPartyMcpPermissions: effectiveFirstPartyPermissions
17233
17268
  }),
17234
17269
  parallelGenerationAvailable: typeof runtime.generateSessionTitle === "function"
17235
17270
  });
17236
- const googleDrivePublicationAllowed = selectedFirstPartyMcpTools.includes("editable_artifact_export") && selectedFirstPartyMcpTools.includes("editable_artifact_export_status") && (!session.firstPartyMcpPermissions?.length || session.firstPartyMcpPermissions.includes("artifacts:read") && session.firstPartyMcpPermissions.includes("artifacts:publish"));
17271
+ const googleDrivePublicationAllowed = selectedFirstPartyMcpTools.includes("editable_artifact_export") && selectedFirstPartyMcpTools.includes("editable_artifact_export_status") && hasPermission2(
17272
+ [...effectiveFirstPartyPermissions ?? DEFAULT_FIRST_PARTY_MCP_PERMISSIONS2],
17273
+ "artifacts:read"
17274
+ ) && hasPermission2(
17275
+ [...effectiveFirstPartyPermissions ?? DEFAULT_FIRST_PARTY_MCP_PERMISSIONS2],
17276
+ "artifacts:publish"
17277
+ );
17237
17278
  const googleDriveConnectorBindings = googleDrivePublicationTool && googleDrivePublicationTarget && googleDrivePublicationAllowed ? [
17238
17279
  {
17239
17280
  modelName: googleDrivePublicationTool.modelName,
@@ -17448,7 +17489,7 @@ async function prepareTurnToolRuntime(deps) {
17448
17489
  attemptId: input.attemptId,
17449
17490
  executionGeneration: attempt.executionGeneration
17450
17491
  },
17451
- ...session.firstPartyMcpPermissions?.length ? { permissions: session.firstPartyMcpPermissions } : {},
17492
+ ...effectiveFirstPartyPermissions ? { permissions: effectiveFirstPartyPermissions } : {},
17452
17493
  selectedTools: selectedFirstPartyMcpTools,
17453
17494
  subjectId: "worker:first-party-mcp",
17454
17495
  subjectLabel: "OpenGeni worker",
@@ -17529,6 +17570,18 @@ async function prepareTurnToolRuntime(deps) {
17529
17570
  ...credentialSubjectId ? { credentialSubjectId } : {},
17530
17571
  ...codexAppsAuth ? { codexAppsAuth } : {},
17531
17572
  resolveCredential,
17573
+ ...linkedAuthority ? {
17574
+ authorizeAttemptExecution: async () => {
17575
+ const current = await getSessionTurnForAttempt2(
17576
+ db,
17577
+ input.workspaceId,
17578
+ input.sessionId,
17579
+ input.attemptId
17580
+ );
17581
+ if (!current || current.id !== turn.id || current.executionGeneration !== attempt.executionGeneration)
17582
+ throw new Error("The linked agent attempt is no longer authorized");
17583
+ }
17584
+ } : {},
17532
17585
  onAuthNeeded: publishToolAuthNeeded,
17533
17586
  materializeConnectorAttachments,
17534
17587
  refreshOwnedCommand: async (commandId) => {
@@ -17555,7 +17608,7 @@ async function prepareTurnToolRuntime(deps) {
17555
17608
  },
17556
17609
  // Manager-style sessions carry a creation-validated permission set
17557
17610
  // for their first-party MCP token; null keeps the fixed default.
17558
- ...session.firstPartyMcpPermissions?.length ? { firstPartyPermissions: session.firstPartyMcpPermissions } : {},
17611
+ ...effectiveFirstPartyPermissions ? { firstPartyPermissions: effectiveFirstPartyPermissions } : {},
17559
17612
  firstPartyTools: titleToolPlan.remoteFirstPartyMcpTools,
17560
17613
  nestedAgentDepth: session.nestedAgentDepth,
17561
17614
  effectiveMaxNestedAgentDepth: session.effectiveMaxNestedAgentDepth,
@@ -17823,7 +17876,9 @@ import {
17823
17876
  getXaiSessionAccountPin as getXaiSessionAccountPin2,
17824
17877
  setXaiSessionAccountPin as setXaiSessionAccountPin2,
17825
17878
  getWorkspaceVideoGenerationPolicy as getWorkspaceVideoGenerationPolicy2,
17826
- loadWorkspaceVercelAiGatewayCredentialLease
17879
+ loadWorkspaceVercelAiGatewayCredentialLease,
17880
+ getExternalLinkTurnAuthorization as getExternalLinkTurnAuthorization2,
17881
+ getSessionTurnForAttempt as getSessionTurnForAttempt3
17827
17882
  } from "@opengeni/db";
17828
17883
  import {
17829
17884
  formatSkillCatalog
@@ -19372,11 +19427,33 @@ async function buildTurnAgent(deps) {
19372
19427
  outcome: "completed",
19373
19428
  durationSeconds: (performance.now() - postToolPreparationStartedAt) / 1e3
19374
19429
  });
19430
+ const linkedToolAuthority = await getExternalLinkTurnAuthorization2(
19431
+ db,
19432
+ {
19433
+ accountId: input.accountId,
19434
+ workspaceId: input.workspaceId
19435
+ },
19436
+ turn.id
19437
+ );
19438
+ if (linkedToolAuthority && !linkedToolAuthority.authorized)
19439
+ throw new Error("Native identity link was revoked");
19375
19440
  const agent = (() => {
19376
19441
  const agentConstructionStartedAt = performance.now();
19377
19442
  let agentConstructionOutcome = "completed";
19378
19443
  try {
19379
19444
  return runtime.buildAgent(eventing.modelRunSettings, runtimeResources, {
19445
+ ...linkedToolAuthority ? {
19446
+ authorizeAttemptExecution: async () => {
19447
+ const current = await getSessionTurnForAttempt3(
19448
+ db,
19449
+ input.workspaceId,
19450
+ input.sessionId,
19451
+ input.attemptId
19452
+ );
19453
+ if (!current || current.id !== turn.id || current.executionGeneration !== turn.executionGeneration)
19454
+ throw new Error("The linked agent attempt is no longer authorized");
19455
+ }
19456
+ } : {},
19380
19457
  ...preparedTools.inputWaitYield ? { inputWaitYield: preparedTools.inputWaitYield } : {},
19381
19458
  reasoningEffort: turn.reasoningEffort,
19382
19459
  latencyMode: turnExecutionPolicy.latencyMode,
@@ -19463,7 +19540,6 @@ async function buildTurnAgent(deps) {
19463
19540
  ),
19464
19541
  onRetainableSessionImageOutput: media.retainSessionImageAtToolBoundary,
19465
19542
  skillCatalog: deps.skillCatalog,
19466
- serverSkillReading: true,
19467
19543
  ...!structuredWorkspacePolicyActive && workspaceAgentInstructions ? { instructionsTemplate: workspaceAgentInstructions } : {},
19468
19544
  ...workspaceGovernance ? { workspaceGovernance } : {},
19469
19545
  ...workspaceMemory ? { workspaceMemory } : {},
@@ -21970,4 +22046,4 @@ export {
21970
22046
  createTurnActivities,
21971
22047
  createTurnActivitiesFromServices
21972
22048
  };
21973
- //# sourceMappingURL=activities-turn-V7J34ZOF.js.map
22049
+ //# sourceMappingURL=activities-turn-FOIZYY56.js.map