@opengeni/runtime 0.14.1 → 0.14.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.
package/dist/index.js CHANGED
@@ -176,7 +176,7 @@ import {
176
176
  withToolspaceTokenEnvironment,
177
177
  withToolspaceTokenSession,
178
178
  wrapCapabilityToolsForTurnCancellation
179
- } from "./chunk-HHWIZHQM.js";
179
+ } from "./chunk-GQIFSLAC.js";
180
180
  import {
181
181
  getSkillLibraryEntry,
182
182
  isSkillLibraryEntryId,
@@ -170,7 +170,7 @@ import {
170
170
  withToolspaceTokenClient,
171
171
  withToolspaceTokenEnvironment,
172
172
  withToolspaceTokenSession
173
- } from "../chunk-HHWIZHQM.js";
173
+ } from "../chunk-GQIFSLAC.js";
174
174
  import "../chunk-4YJ5Z52Z.js";
175
175
  import "../chunk-K4V4QYCL.js";
176
176
  import "../chunk-PZ5AY32C.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/runtime",
3
- "version": "0.14.1",
3
+ "version": "0.14.3",
4
4
  "license": "Apache-2.0",
5
5
  "repository": {
6
6
  "type": "git",
@@ -50,8 +50,8 @@
50
50
  "@openai/agents-extensions": "0.13.3",
51
51
  "@opengeni/agent-proto": "^0.3.0",
52
52
  "@opengeni/codex": "^0.2.7",
53
- "@opengeni/config": "^0.7.11",
54
- "@opengeni/contracts": "^0.22.0",
53
+ "@opengeni/config": "^0.7.13",
54
+ "@opengeni/contracts": "^0.23.0",
55
55
  "@opengeni/network": "^0.1.1",
56
56
  "debug": "4.4.3",
57
57
  "modal": "0.7.6",
@@ -1,5 +1,7 @@
1
1
  import { DockerSandboxClient } from "@openai/agents/sandbox/local";
2
+ import { isAbsolute } from "node:path";
2
3
  import { CAPABILITY_DESCRIPTORS } from "../capabilities";
4
+ import { SandboxConfigError } from "../errors";
3
5
  import type { ProviderRegistration } from "./types";
4
6
 
5
7
  export const dockerProvider: ProviderRegistration = {
@@ -7,11 +9,21 @@ export const dockerProvider: ProviderRegistration = {
7
9
  descriptor: CAPABILITY_DESCRIPTORS.docker,
8
10
  // Local dev container — no credentials. (The dockerNetwork decoration is
9
11
  // applied by the factory, not here: it wraps the constructed client.)
10
- validateCredentials() {},
12
+ validateCredentials(settings) {
13
+ const workspaceBaseDir = settings.dockerWorkspaceBaseDir?.trim();
14
+ if (workspaceBaseDir && !isAbsolute(workspaceBaseDir)) {
15
+ throw new SandboxConfigError(
16
+ "docker",
17
+ "OPENGENI_DOCKER_WORKSPACE_BASE_DIR must be an absolute path",
18
+ );
19
+ }
20
+ },
11
21
  build({ settings, exposedPorts }) {
22
+ const workspaceBaseDir = settings.dockerWorkspaceBaseDir?.trim();
12
23
  return new DockerSandboxClient({
13
24
  image: settings.dockerImage,
14
25
  exposedPorts,
26
+ ...(workspaceBaseDir ? { workspaceBaseDir } : {}),
15
27
  });
16
28
  },
17
29
  };
@@ -1228,7 +1228,22 @@ export class RoutingSandboxSession implements RoutableBackendSession {
1228
1228
  if (!s.materializeEntry) {
1229
1229
  throw new RoutingUnsupportedError("materializeEntry", this.cached?.kind ?? "unknown");
1230
1230
  }
1231
- return s.materializeEntry(args);
1231
+ await s.materializeEntry(args);
1232
+
1233
+ // The local Docker SDK materializes through a host path and then bind-mounts
1234
+ // that path into the sandbox. A containerized worker using the host Docker
1235
+ // socket can otherwise write into its own container layer while the sandbox
1236
+ // sees a different, empty host directory. `pathExists()` and `readFile()` on
1237
+ // that SDK session inspect the worker-side path, so only a command executed
1238
+ // inside the actual sandbox proves the materialized path is usable by the
1239
+ // agent. Never let lazy `load_skill` report success on a split workspace.
1240
+ const path =
1241
+ args && typeof args === "object" && typeof (args as { path?: unknown }).path === "string"
1242
+ ? (args as { path: string }).path
1243
+ : null;
1244
+ if (path) {
1245
+ await assertProviderCanSeeMaterializedPath(s, path);
1246
+ }
1232
1247
  });
1233
1248
  }
1234
1249
 
@@ -1304,3 +1319,51 @@ export class RoutingSandboxSession implements RoutableBackendSession {
1304
1319
  return this.resolve();
1305
1320
  }
1306
1321
  }
1322
+
1323
+ const MATERIALIZED_PATH_MARKER = "__OPENGENI_MATERIALIZED_PATH_VISIBLE__";
1324
+
1325
+ async function assertProviderCanSeeMaterializedPath(
1326
+ session: RoutableBackendSession,
1327
+ path: string,
1328
+ ): Promise<void> {
1329
+ const command = `test -e ${shellSingleQuote(path)} && printf %s ${shellSingleQuote(
1330
+ MATERIALIZED_PATH_MARKER,
1331
+ )}`;
1332
+ const args = {
1333
+ cmd: command,
1334
+ workdir: providerManifestRoot(session) ?? "/workspace",
1335
+ shell: "sh",
1336
+ login: false,
1337
+ tty: false,
1338
+ };
1339
+ let output: string;
1340
+ if (session.execCommand) {
1341
+ output = await session.execCommand(args);
1342
+ } else if (session.exec) {
1343
+ output = formatExecResult(await session.exec(args));
1344
+ } else {
1345
+ throw new RoutingUnsupportedError("materializeEntry.verify", "unknown");
1346
+ }
1347
+ if (!output.includes(MATERIALIZED_PATH_MARKER)) {
1348
+ throw new Error(
1349
+ `Sandbox materialization completed but the provider cannot read the destination path: ${path}`,
1350
+ );
1351
+ }
1352
+ }
1353
+
1354
+ function providerManifestRoot(session: RoutableBackendSession): string | null {
1355
+ const root = (
1356
+ session.state as
1357
+ | {
1358
+ manifest?: {
1359
+ root?: unknown;
1360
+ };
1361
+ }
1362
+ | undefined
1363
+ )?.manifest?.root;
1364
+ return typeof root === "string" && root.length > 0 ? root : null;
1365
+ }
1366
+
1367
+ function shellSingleQuote(value: string): string {
1368
+ return `'${value.replaceAll("'", `'"'"'`)}'`;
1369
+ }