@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.
- package/dist/activities/mcp-credentials.d.ts +3 -1
- package/dist/activities/workspace-capture.d.ts +12 -1
- package/dist/{activities-control-WZRFEIJH.js → activities-control-VVK7RA7F.js} +5 -5
- package/dist/{activities-control-WZRFEIJH.js.map → activities-control-VVK7RA7F.js.map} +1 -1
- package/dist/{activities-turn-V7J34ZOF.js → activities-turn-FOIZYY56.js} +250 -174
- package/dist/activities-turn-FOIZYY56.js.map +1 -0
- package/dist/{chunk-C54VDT6I.js → chunk-AKP6BKM2.js} +3 -1
- package/dist/chunk-AKP6BKM2.js.map +1 -0
- package/dist/index.js +1 -1
- package/package.json +17 -17
- package/src/activities/agent-turn/agent-build.ts +30 -1
- package/src/activities/agent-turn/finalization.ts +52 -36
- package/src/activities/agent-turn/skill-read.ts +2 -21
- package/src/activities/agent-turn/tool-environment.ts +52 -10
- package/src/activities/mcp-credentials.ts +42 -2
- package/src/activities/scheduled-tasks.ts +8 -6
- package/src/activities/workspace-capture.ts +148 -126
- package/src/pr-review-credentials.ts +2 -0
- package/dist/activities-turn-V7J34ZOF.js.map +0 -1
- package/dist/chunk-C54VDT6I.js.map +0 -1
|
@@ -86,7 +86,7 @@ import {
|
|
|
86
86
|
withFirstPartyTools,
|
|
87
87
|
wrapLazyTurnBoxWithRouting,
|
|
88
88
|
wrapTurnBoxWithRouting
|
|
89
|
-
} from "./chunk-
|
|
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
|
-
|
|
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
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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") && (
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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-
|
|
22049
|
+
//# sourceMappingURL=activities-turn-FOIZYY56.js.map
|