@opengeni/worker-bundle 3.1.2 → 3.2.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/worker-bundle",
3
- "version": "3.1.2",
3
+ "version": "3.2.2",
4
4
  "description": "Opengeni worker entry and reusable embedded lifecycle, shipped with a release-coherent pre-bundled Temporal workflow artifact.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -54,30 +54,33 @@
54
54
  },
55
55
  "dependencies": {
56
56
  "@1password/sdk": "0.5.0",
57
+ "@keeper-security/secrets-manager-core": "17.6.0",
57
58
  "@llamaindex/liteparse": "2.14.2",
58
- "@opengeni/agent-proto": "3.1.2",
59
- "@opengeni/capabilities": "3.1.2",
60
- "@opengeni/codemode": "3.1.2",
61
- "@opengeni/codex": "3.1.2",
62
- "@opengeni/config": "3.1.2",
63
- "@opengeni/contracts": "3.1.2",
64
- "@opengeni/core": "3.1.2",
65
- "@opengeni/db": "3.1.2",
66
- "@opengeni/documents": "3.1.2",
67
- "@opengeni/events": "3.1.2",
68
- "@opengeni/github": "3.1.2",
69
- "@opengeni/interaction": "3.1.2",
70
- "@opengeni/jev": "3.1.2",
71
- "@opengeni/network": "3.1.2",
72
- "@opengeni/observability": "3.1.2",
73
- "@opengeni/runtime": "3.1.2",
74
- "@opengeni/sdk": "3.1.2",
75
- "@opengeni/storage": "3.1.2",
76
- "@opengeni/subscriptions": "3.1.2",
77
- "@opengeni/tool-gateway": "3.1.2",
78
- "@opengeni/vaults": "3.1.2",
79
- "@opengeni/vaults-onepassword": "3.1.2",
80
- "@opengeni/xai-subscription": "3.1.2",
59
+ "@opengeni/agent-proto": "3.2.2",
60
+ "@opengeni/capabilities": "3.2.2",
61
+ "@opengeni/codemode": "3.2.2",
62
+ "@opengeni/codex": "3.2.2",
63
+ "@opengeni/config": "3.2.2",
64
+ "@opengeni/contracts": "3.2.2",
65
+ "@opengeni/core": "3.2.2",
66
+ "@opengeni/db": "3.2.2",
67
+ "@opengeni/documents": "3.2.2",
68
+ "@opengeni/events": "3.2.2",
69
+ "@opengeni/github": "3.2.2",
70
+ "@opengeni/interaction": "3.2.2",
71
+ "@opengeni/jev": "3.2.2",
72
+ "@opengeni/network": "3.2.2",
73
+ "@opengeni/observability": "3.2.2",
74
+ "@opengeni/runtime": "3.2.2",
75
+ "@opengeni/sdk": "3.2.2",
76
+ "@opengeni/storage": "3.2.2",
77
+ "@opengeni/subscriptions": "3.2.2",
78
+ "@opengeni/tool-gateway": "3.2.2",
79
+ "@opengeni/vaults": "3.2.2",
80
+ "@opengeni/vaults-keeper": "3.2.2",
81
+ "@opengeni/vaults-onepassword": "3.2.2",
82
+ "@opengeni/vaults-protonpass": "3.2.2",
83
+ "@opengeni/xai-subscription": "3.2.2",
81
84
  "@temporalio/activity": "^1.17.0",
82
85
  "@temporalio/client": "^1.17.0",
83
86
  "@temporalio/worker": "^1.17.0",
@@ -1611,6 +1611,12 @@ export function createRunAgentTurnActivity(services: () => Promise<ActivityServi
1611
1611
  subjectId: turn.initiatingHumanSubjectId ?? credentialSubjectId ?? null,
1612
1612
  hostVaultProvider: connectionCredentials?.vaultProvider,
1613
1613
  onePasswordVaultProvider: resolvedServices.onePasswordVaultProvider,
1614
+ externalVaultProviders: resolvedServices.externalVaultProviders ?? null,
1615
+ executionContext: {
1616
+ sessionId: input.sessionId,
1617
+ turnId: turn.id,
1618
+ attemptId: input.attemptId,
1619
+ },
1614
1620
  });
1615
1621
  const toolRuntime = await prepareTurnToolRuntime({
1616
1622
  vaultAccess,
@@ -15,6 +15,7 @@ import {
15
15
  createSecretResolver,
16
16
  createWorkspaceVaultProviderRegistry,
17
17
  type OnePasswordVaultProviderFactory,
18
+ type ExternalVaultProviderFactories,
18
19
  } from "@opengeni/core";
19
20
  import { listVaultsForSubject, type Database } from "@opengeni/db";
20
21
  import {
@@ -22,6 +23,7 @@ import {
22
23
  type ProviderCallBudget,
23
24
  type VaultProvider,
24
25
  type VaultProviderRegistry,
26
+ type VaultProviderAuthority,
25
27
  } from "@opengeni/vaults";
26
28
 
27
29
  /**
@@ -49,6 +51,8 @@ export function createAttemptVaultAccess(input: {
49
51
  subjectId: string | null;
50
52
  hostVaultProvider: VaultProvider | null | undefined;
51
53
  onePasswordVaultProvider: OnePasswordVaultProviderFactory | null | undefined;
54
+ externalVaultProviders?: ExternalVaultProviderFactories | null;
55
+ executionContext?: Pick<VaultProviderAuthority, "sessionId" | "turnId" | "attemptId" | "siteId">;
52
56
  }): AttemptVaultAccess {
53
57
  return {
54
58
  providers: createWorkspaceVaultProviderRegistry({
@@ -58,6 +62,8 @@ export function createAttemptVaultAccess(input: {
58
62
  subjectId: input.subjectId,
59
63
  hostVaultProvider: input.hostVaultProvider ?? null,
60
64
  onePasswordVaultProvider: input.onePasswordVaultProvider ?? null,
65
+ externalVaultProviders: input.externalVaultProviders ?? null,
66
+ ...(input.executionContext ? { executionContext: input.executionContext } : {}),
61
67
  }),
62
68
  attemptBudget: createProviderCallBudget(VAULT_LIMITS.providerReadsPerAttempt),
63
69
  };
@@ -4105,6 +4105,9 @@ async function terminateDrainableBox(
4105
4105
  minIntervalMs: 0,
4106
4106
  pointInTimeCapture: providerWorkspaceCaptureIsPointInTime(backend, lease.resumeState),
4107
4107
  deadlineMandatoryCaptureLeadMs: sandboxDeadlineMandatoryCaptureLeadMs(settings),
4108
+ // An unknown-outcome setup request of a closed attempt never settles
4109
+ // on a live box; after the idle grace it no longer pins the drain.
4110
+ abandonedSetupCaptureAfterMs: Math.max(settings.sandboxIdleGraceMs, 60_000),
4108
4111
  providerReplaySafe: capturePolicy?.takeover === "same_request",
4109
4112
  takeoverSafe: capturePolicy !== null && capturePolicy.takeover !== "exclusive",
4110
4113
  });
@@ -18,7 +18,10 @@ import type { Observability } from "@opengeni/observability";
18
18
  import type { OpenGeniRuntime } from "@opengeni/runtime";
19
19
  import type { ObjectStorage } from "@opengeni/storage";
20
20
  import type { VaultConnectionCredentialsPort } from "@opengeni/vaults";
21
- import type { OnePasswordVaultProviderFactory } from "@opengeni/core";
21
+ import type {
22
+ ExternalVaultProviderFactories,
23
+ OnePasswordVaultProviderFactory,
24
+ } from "@opengeni/core";
22
25
 
23
26
  // Signal (start-if-needed) a session's Temporal workflow so a queued turn it
24
27
  // cannot otherwise observe gets claimed. Used to wake a PARENT session's
@@ -141,6 +144,7 @@ export type SharedActivityServices = {
141
144
  * `@opengeni/vaults-onepassword`); null leaves 1Password vaults unavailable.
142
145
  */
143
146
  onePasswordVaultProvider?: OnePasswordVaultProviderFactory | null;
147
+ externalVaultProviders?: ExternalVaultProviderFactories | null;
144
148
  /** Standalone, default-off personal GitHub smart-HTTP broker consumer. */
145
149
  personalGitHubCredentials?: ConnectionCredentialsPort["gitCredentials"] | null;
146
150
  };
@@ -12,13 +12,14 @@ import {
12
12
  } from "@opengeni/db";
13
13
  import { createNatsEventBus } from "@opengeni/events";
14
14
  import { createObservability } from "@opengeni/observability";
15
- import { resolveCatalogSettings } from "@opengeni/core";
15
+ import { createRemoteExternalVaultFactories, resolveCatalogSettings } from "@opengeni/core";
16
16
  import { createObjectStorage } from "@opengeni/storage";
17
17
  import type { ActivityDependencies, SharedActivityServices } from "./activities/types";
18
18
  import { observabilityEventBusOptions } from "./observability-metrics";
19
19
  import { buildPersonalGitHubGitCredentials } from "./personal-github-git-credentials";
20
20
  import { createStandaloneConnectionCredentialsPort } from "./pr-review-credentials";
21
21
  import { loadOnePasswordVaultProvider } from "./onepassword-vault-provider";
22
+ import { createNativeExternalVaultFactories } from "./external-vault-providers";
22
23
 
23
24
  /**
24
25
  * Build the dependency graph common to both worker roles exactly once.
@@ -92,6 +93,13 @@ export function createSharedActivityServices(
92
93
  dependencies.onePasswordVaultProvider === undefined
93
94
  ? loadOnePasswordVaultProvider
94
95
  : dependencies.onePasswordVaultProvider,
96
+ externalVaultProviders:
97
+ dependencies.externalVaultProviders === undefined
98
+ ? {
99
+ ...createRemoteExternalVaultFactories(settings),
100
+ ...createNativeExternalVaultFactories(settings),
101
+ }
102
+ : dependencies.externalVaultProviders,
95
103
  };
96
104
  })();
97
105
  return await servicesPromise;
@@ -0,0 +1,136 @@
1
+ import type { Settings } from "@opengeni/config";
2
+ import type {
3
+ ExternalVaultProviderFactories,
4
+ ExternalVaultProviderFactory,
5
+ StoredVaultProviderCredentials,
6
+ } from "@opengeni/core";
7
+ import { pinnedFetch, type PinnedFetchOptions } from "@opengeni/network";
8
+ import { VaultProviderError } from "@opengeni/vaults";
9
+ import type { KeeperRuntimeOptions } from "@opengeni/vaults-keeper";
10
+ import type { ProtonPassProviderPool } from "@opengeni/vaults-protonpass";
11
+
12
+ /** Test seams only; production passes nothing. */
13
+ export type NativeExternalVaultOptions = {
14
+ keeper?: Pick<KeeperRuntimeOptions, "queryFunction" | "fetchFile" | "sdk">;
15
+ /** Applies when this call creates the pool for its CLI path. */
16
+ protonpass?: { stateRoot?: string; hostEnv?: Readonly<Record<string, string | undefined>> };
17
+ };
18
+
19
+ /**
20
+ * Native external vault adapters that run inside this server: Keeper Secrets
21
+ * Manager always, Proton Pass only when the operator installed `pass-cli`
22
+ * (`OPENGENI_PROTON_PASS_CLI_PATH`). Both packages load through `import()`
23
+ * on first use (packages/vaults-keeper/test/import-guard.test.ts). Keep this
24
+ * file identical to apps/api/src/integrations/external-vault-providers.ts.
25
+ */
26
+ export function createNativeExternalVaultFactories(
27
+ settings: Settings,
28
+ options: NativeExternalVaultOptions = {},
29
+ ): ExternalVaultProviderFactories {
30
+ const factories: ExternalVaultProviderFactories = {
31
+ keeper: keeperFactory(settings, options.keeper ?? {}),
32
+ };
33
+ const cliPath = settings.protonPassCliPath;
34
+ if (cliPath) factories.protonpass = protonPassFactory(cliPath, options.protonpass ?? {});
35
+ return factories;
36
+ }
37
+
38
+ /** Logs out every pooled Proton Pass CLI session and wipes its state (graceful shutdown). */
39
+ export async function closeNativeExternalVaultProviders(): Promise<void> {
40
+ const pools = [...protonPools.values()];
41
+ protonPools.clear();
42
+ await Promise.all(pools.map(async (pool) => (await pool.catch(() => null))?.close()));
43
+ }
44
+
45
+ function mismatch(): never {
46
+ throw new VaultProviderError(
47
+ "provider_unavailable",
48
+ "The configured adapter does not match this connection.",
49
+ );
50
+ }
51
+
52
+ /**
53
+ * The fetch every Keeper request and file download uses: DNS-pinned and
54
+ * policy-checked (no private or special-use targets). The adapter adds
55
+ * HTTPS-only, no redirects, deadlines, size bounds and the caller's abort.
56
+ */
57
+ export function nativeKeeperFetch(
58
+ settings: Settings,
59
+ pinned: Pick<PinnedFetchOptions, "dnsLookup" | "fetchImpl" | "agentFactory"> = {},
60
+ ) {
61
+ return (input: string | URL, init: RequestInit) =>
62
+ pinnedFetch(input, init, settings, {
63
+ ...pinned,
64
+ label: "Keeper Secrets Manager",
65
+ requireHttpsOutsideLocalTest: true,
66
+ });
67
+ }
68
+
69
+ function keeperFactory(
70
+ settings: Settings,
71
+ seams: NonNullable<NativeExternalVaultOptions["keeper"]>,
72
+ ): ExternalVaultProviderFactory {
73
+ const fetch = nativeKeeperFetch(settings);
74
+ return async (input) => {
75
+ const credentials = input.credentials;
76
+ if (credentials.provider !== "keeper") mismatch();
77
+ const persist = input.persistCredentials;
78
+ const { openKeeperConnection } = await import("@opengeni/vaults-keeper");
79
+ const opened = await openKeeperConnection({
80
+ credentials:
81
+ "oneTimeToken" in credentials
82
+ ? {
83
+ oneTimeToken: credentials.oneTimeToken,
84
+ ...(credentials.hostname ? { hostname: credentials.hostname } : {}),
85
+ }
86
+ : { configuration: credentials.configuration },
87
+ // Without it openKeeperConnection refuses: a redeemed token or rotated
88
+ // key must never live only in memory.
89
+ ...(persist
90
+ ? {
91
+ persistConfiguration: (configuration: Record<string, string>) =>
92
+ persist({ provider: "keeper", configuration }),
93
+ }
94
+ : {}),
95
+ ...(input.operations ? { operations: input.operations } : {}),
96
+ // Cancels a pending bootstrap; a stored configuration opens without a request.
97
+ ...(input.signal ? { signal: input.signal } : {}),
98
+ fetch,
99
+ ...seams,
100
+ });
101
+ const stored: StoredVaultProviderCredentials = {
102
+ provider: "keeper",
103
+ configuration: opened.configuration,
104
+ };
105
+ return { provider: opened.provider, credentials: stored };
106
+ };
107
+ }
108
+
109
+ /** One pool per CLI path per process, so repeated factory creation never leaks sessions. */
110
+ const protonPools = new Map<string, Promise<ProtonPassProviderPool>>();
111
+
112
+ function protonPassFactory(
113
+ binaryPath: string,
114
+ seams: NonNullable<NativeExternalVaultOptions["protonpass"]>,
115
+ ): ExternalVaultProviderFactory {
116
+ return async (input) => {
117
+ const credentials = input.credentials;
118
+ if (credentials.provider !== "protonpass") mismatch();
119
+ let pool = protonPools.get(binaryPath);
120
+ if (!pool) {
121
+ pool = import("@opengeni/vaults-protonpass").then(({ createProtonPassProviderPool }) =>
122
+ createProtonPassProviderPool({ ...seams, binaryPath }),
123
+ );
124
+ protonPools.set(binaryPath, pool);
125
+ pool.catch(() => protonPools.delete(binaryPath));
126
+ }
127
+ // Sessions are scoped to the connection, so two connections never share
128
+ // CLI state even with the same token. Without a connection id (a setup
129
+ // probe) the provider gets an isolated session of its own.
130
+ const provider = (await pool).provider(credentials.token, {
131
+ ...(input.connectionId === undefined ? {} : { connectionId: input.connectionId }),
132
+ ...(input.operations ? { operations: input.operations } : {}),
133
+ });
134
+ return { provider, credentials: { provider: "protonpass", token: credentials.token } };
135
+ };
136
+ }
package/src/index.ts CHANGED
@@ -42,6 +42,7 @@ import { fileURLToPath } from "node:url";
42
42
  import type { createControlActivities } from "./activities-control";
43
43
  import type { createTurnActivities } from "./activities-turn";
44
44
  import type { ActivityDependencies } from "./activities/types";
45
+ import { closeNativeExternalVaultProviders } from "./external-vault-providers";
45
46
  import type {
46
47
  InspectSessionAttemptActivity,
47
48
  SignalCodexCapacityWorkflow,
@@ -1129,7 +1130,12 @@ export async function startWorker() {
1129
1130
  },
1130
1131
  });
1131
1132
  } finally {
1132
- await Promise.allSettled([bus?.close(), readinessDbClient.close(), dbClient.close()]);
1133
+ await Promise.allSettled([
1134
+ closeNativeExternalVaultProviders(),
1135
+ bus?.close(),
1136
+ readinessDbClient.close(),
1137
+ dbClient.close(),
1138
+ ]);
1133
1139
  disposeUnhandledRejectionBoundary();
1134
1140
  }
1135
1141
  }